-
Notifications
You must be signed in to change notification settings - Fork 7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Experiment: Devicetree and device driver access in Rust #76199
Conversation
Add the `CONFIG_RUST` Kconfig. This can be set to indicate that an application wishes to use Rust support. Signed-off-by: David Brown <[email protected]>
Until further variants are needed, this provides a `main()` function that simply calls into the `rust_main()` function. This is adequate for applications where main can be directly written in Rust. Signed-off-by: David Brown <[email protected]>
The initial support crate for zephyr use within a Rust application. This crate does two simple things: - Processes the .config file generated for this build, and ensures that CONFIG_RUST is enabled. - Provide a hanging Rust panic handler. Signed-off-by: David Brown <[email protected]>
This provides the function `rust_cargo_application()` within Cmake to give applications written in Rust a simple way to start. This implements the basic functionality needed to build a rust application on Arm, including target mapping, and invoking cargo during the build. Signed-off-by: David Brown <[email protected]>
This implements a simple hello world application in Rust. Since there are no bindings yet, this just directly calls `printk` from Rust code. Signed-off-by: David Brown <[email protected]>
Indicate the new code is maintained. Signed-off-by: David Brown <[email protected]>
Create a crate `zephyr-build` that contains the code that will run at build time. Move the bool kconfig handing from this. This will make it easier for an application crate that needs access to config entries to do access them by: - Adding a dependency to zephyr-build to `[build-dependencies]` in their Cargo.toml. - Creating a build.rs, or adding a call to: zephyr_build::export_bool_kconfig() in it. Signed-off-by: David Brown <[email protected]>
As part of the build of the `zephyr` crate, convert string and numeric entries from the .config file of the current build into constants. This mimics how these are available as defines in C code. The defines are available under `zephyr::kconfig::CONFIG_...`. Signed-off-by: David Brown <[email protected]>
This is a bit awkward, as we don't have a clean interface between Rust and the Zephyr C code, but copy and null-terminate the current board, and include that in the printed message. This demonstrates that the kconfig module generation is working. Signed-off-by: David Brown <[email protected]>
Add initial docs for Rust language support. Explains the support currently present, how to install the toolchain needed, and explains what is in the current sample. Signed-off-by: David Brown <[email protected]>
The documentation system requires all of the samples to have unique names, no matter what directory they are in. Rename our sample to `hello_rust` to avoid a conflict with the existing `hello_world` sample. Signed-off-by: David Brown <[email protected]>
Code-blocks for shell commands are of type 'shell', to avoid this warning. Signed-off-by: David Brown <[email protected]>
Distinguish between 'int' and 'hex' Kconfig values. Use `usize` for the hex values, as these are unsigned and may have the high bit set. But, for 'int' values, use `isize`, and allow the constants to be negative. Signed-off-by: David Brown <[email protected]>
This is still very experimental, mark it in the Kconfig as such. Signed-off-by: David Brown <[email protected]>
For Arm targets, match the ABI selection defines from the cmake/compiler/gcc/target_arm.cmake file. What is important here is that the floating point selection is the same between gcc and rustc. Signed-off-by: David Brown <[email protected]>
Because of a mismatched quote, shell quoting for this block fails sphinx. Removing the quite causes compliance to fail due to the mispelling. Fix this by just removing formatting entirely from the block. Signed-off-by: David Brown <[email protected]>
Compliance fails if even an example refers to non-existent Kconfig options. Change these to real options, even though that is slightly distracting. Signed-off-by: David Brown <[email protected]>
The Cortex-M4 should be the thumbv7em not thumbv7m. Signed-off-by: David Brown <[email protected]>
Make this a proper C prototype function that takes no arguments. Signed-off-by: David Brown <[email protected]>
Here are the changes without the work from #75904. |
Here is probably as good a place as any to discuss. As far as generating the DT from python or Rust, there are advantages disadvantages of either:
|
lib/rust/zephyr-sys/build.rs
Outdated
|
||
fn main() { | ||
let bindings = bindgen::Builder::default() | ||
.clang_arg("-I/home/ubuntu/zephyrproject/zephyr/include") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lol. I was going to try to figure out how to get the include path out of cmake. Turns out it is rather challenging to do. We do need to figure out how to do it correctly, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did figure out how to do this, I'll push some code hopefully tomorrow.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like you were looking at an old version. Here is the best solution I could come up with for this: https://github.com/mjaun/zephyr/blob/dea56463ebbf469118ed34ee5c9e343586bf27da/cmake/modules/rust.cmake#L42
I would separate these things: Processing device tree for Rust in Python vs. Rust Build Script The Python approach has no need for an additional parser as the information is already there in a parsed form (even with information about matched bindings). Even if the CMake integration needs some code, I think overall there are is less to be added. Then there is the separation between the Zephyr build system (CMake) and the Rust build system (Cargo). I think this is mainly relevant if you want to open and develop your Rust application in a pure Rust IDE and therefore use the Cargo project.
Instantiating Rust device driver interfaces by devicetree vs. by application I think the major difference between our approaches is where the Rust device driver interfaces get instantiated. In your approach if I do While I really do like your idea because it feels more type safe (you cannot provide a device handle to the wrong driver interface), I have one main concern. My understanding of the process is as follows:
My concern is: How can we know for every case which Rust device driver interface to instantiate for a particular device tree node? As far as I can see the devicetree information can tell us the compatible string of the matched binding, but it doesn't tell which API will be exposed by the instantiated driver. We could try to guess this by saying if the compatible string contains "uart" it is probably the UART API, but this doesn't seem very robust and it surely won't work for example for proprietary out-of-tree drivers providing their own API. If we don't know how we would solve this problem I would actually feel more comfortable if it was up to the application to instantiate the Rust device driver interfaces. It is how it is done today in C and it is guaranteed to work out in every case. Utilize Rust capabilities to provide the safest and cleanest API possible vs. sticking closer to existing C APIs You mentioned the C macros exist due to limitations in C. I agree that Rust allows us to craft a cleaner and safer API than what we currently have in C. In the long run I guess it makes also sense to do so, but it this takes a lot of thought work to design, implement, test and document this. A possible solution could be to create a layering like this and start implementing bottom up:
|
This pull request has been marked as stale because it has been open (more than) 60 days with no activity. Remove the stale label or add a comment saying that you would like to have the label removed otherwise this pull request will automatically be closed in 14 days. Note, that you can always re-open a closed pull request at any time. |
This experiment is based on the PR #75904 and demonstrates how devicetree information could be utilized in Rust which allows the creation of device driver bindings in Rust.
During the Zephyr build, the
gen_defines.py
script which generates the C header file containing the devicetree information already stores parsed devicetree data in the fileedt.pickle
. This file is used bygen_dts_cmake.py
to expose necessary devicetree information to CMake. In this example an additional scriptgen_dts_rust.py
is added which uses the same pickle file to generate devicetree information for Rust. The generated code is a static structure where each node is instantiated once as a constant whereDT_NODE_0
is the root node. Nodes have references to other nodes to build up the tree structure. For nodes with status "okay", adevice()
method is generated allowing to access the underlying device object. Using Rust macros, similar APIs as we have in C can be provided, such asGPIO_DT_SPEC_GET
.To access Zephyr kernel functionalities as well as driver APIs, low level Rust bindings are generated using Rust bindgen. A similar approach as in this project is used to gather all compiler arguments, but the
wrap_static_fns
feature from bindgen is used to generate wrappers for inline functions including system calls. The low level bindings are abstracted to provide proper APIs to Rust applications. These abstractions are included depending on whether the underlying drivers are enabled like in this example with#[cfg(CONFIG_GPIO)]
.The example was tested on a nucleo_f411re board. There might be architecture/configuration dependent flaws which I didn't see. The resulting main function from this example looks like this:
In the pictured main function in #65837 the Rust device drivers would be instantiated directly into the devicetree structure. Here it is up to the application to instantiate the device drivers and manage them properly. The challenge I saw with the proposed approach is that although we can see in the devicetree information which compatible string is matched to a certain node, it is not clear which driver API will be exposed and therefore which Rust driver binding should be instantiated.