zephyrproject-rtos / zephyr

Primary Git Repository for the Zephyr Project. Zephyr is a new generation, scalable, optimized, secure RTOS for multiple hardware architectures.
https://docs.zephyrproject.org
Apache License 2.0
10.44k stars 6.4k forks source link

Rust Support in Zephyr #65837

Open d3zd3z opened 9 months ago

d3zd3z commented 9 months ago

Introduction

Various parties have expressed interest in having "Rust Support" in Zephyr. This can mean different things, such as writing applications in Rust, to writing Zephyr drivers in Rust. This RFC attempts to define a specific type of support for Rust within Zephyr.

Problem description

The Rust programming language is a relatively modern programming language that attempts to bring together several features that would be appropriate for the type of embedded development Zephyr is commonly used for. Among these are:

This RFC proposes a way for an application developer to be able to write their application, in Rust, using idiomatic Rust code (meaning unsafe is not generally needed) on top of Zephyr, while maintaining a minimal overhead to do so.

Proposed change

This support will be added in a few places:

Detailed RFC

Overall Goal.

When completed, it should be possible to have a rust implementation of "blinky" that looks something like:

#![no_std]
#![no_main]

#[zephyr::entry]
fn main() {
    let dt = zephyr::devicetree::take().unwrap();
    let led = dt.chosen.led0;

    loop {
        led.toggle().unwrap();
        zephyr::sys::sleep(1000);
    }
}

This can be filled out as the Rust interface to Zephyr features are implemented. But, this level of initial support is probably a good starting point.

Devicetree

Currently, the device tree is effectively flattened into a set of C defines for all of the values. Although it would be possible for Rust code to access values like this, the macro name stitching that is done in C would be both rather non-rust-like, as well as hide the entire device tree from tools like IDEs. It would make the generated tree mostly undiscoverable to a developer trying to write code.

Instead, we propose generating a fairly simple structure in Rust that mirrors the device tree. This structure is modeled after the PAC/HAL structures that are used within the Zephyr Embedded project. Ultimately, there is a single struct, the DeviceTree that is generated. This could, perhaps initially, be modeled after the chosen node, with only supported nodes included. Each field of this struct would be an implementation of a structure defined in a support crate for that particular device. The end result would be similar to the structures that are generated through macros to use device tree nodes currently within C.

Driver support

To make this work, much of the support Rust code will be human written code that defines the types, and methods upon those types that implement the interface to the Zephyr drivers. For some drivers, this is fairly straightforward, and will mainly result in using a cleaner Rust namespace to invoke the Zephyr devices. Support for drivers that are implemented in C with callbacks is a more complicated issue and address further below.

CMake Support

At a minimum, the cmake files will need to be extended to support building a Rust application. It will need to invoke the additional device tree generation code, convert Kconfig options to rust features, as appropriate (details to be filled in), and provide a small cmake library so that it is easy for an application to state that it is a Rust application. The cmake code will create rules that invoke cargo with the appropriate arguments, and arrange for the compiled application library to be linked into the Zephyr application.

System call support

The kernel libraries in Zephyr will be made available through a zephyr::sys module that will be a fairly straightforward mapping from Rust to the C functions/syscalls. For some routines, such as sleep, it makes sense to just call these from ordinary Rust applications. For others, such as threading, it probably makes sense to have higher level abstractions built around this.

Asynchronous support

The general trend in the Rust embedded world is to use the async mechanism to write code that is multi-threaded and needs to block. This proposal will implement a basic executor using the Zephyr scheduling primitives. A good mechanism can then be used to associate simple callbacks in driver calls to functions that send to semaphores, for example, and the semaphore and other scheduling primitives will have support in the async code to be able to wait for them.

It would also be possible to just have a 1:1 mapping of Rust threads to zephyr threads, and the primitive calls would just block the current thread to be woken appropriately. Ideally, either of these mechanisms will be available to Rust programmers to use as appropriate for a given application.

Logging

The Rust and Zephyr logging systems have some fairly significant semantic mismatches. Fortunately, The Rust language does not explicitly define a logging mechanism, and it would be fairly easy to have some simple wrappers. Log 2 would likely need to be used, as the formatting mechanism are dissimilar between the languages, and the Rust logged messages would likely just be formatted strings.

It should also be possible to configure the system to not have the Zephyr logging mechanism, and use something like the Rust defmt logger, which greatly compresses the log stream by referencing strings by index and resolving the strings by a host tool.

Dependencies

The main concern is to make sure that any support added to Zephyr for Rust should be tested as part of CI. The primary reason is so that API and other changes that break this support will be caught early, and can be fixed.

Concerns and Unresolved Questions

One concern would be how "official" rust support is by the Zephyr project. In some sense, this is largely outside of the Zephyr tree (in modules), but as it depends on the Zephyr API, if not well supported could quickly lock a given application to a specific version of Zephyr.

Alternatives

Obviously, it is possible to continue to write code in C, or C++. However, many users and companies are finding a lot of advantages to developing in Rust, and it should be possible to add this support to Zephyr without significant impact on how C development is done.

Any efforts or desires to implement parts of Zephyr in Rust are beyond the scope of this RFC, and this kind of support would have a much larger impact.

pdgendt commented 9 months ago

Just wanted to point out ongoing attempts of adding rust support in cmake.

henrikbrixandersen commented 9 months ago

One thing to note: When/if the Rust glue code (devicetree interface, driver interface, syscall interface, etc.) for writing Zephyr applications in Rust is added, this will effectively mean that developers touching these areas in Zephyr C-code will need to also need to be knowledgeable about how to do the required changes to the Rust support code to match.

d3zd3z commented 9 months ago

Just wanted to point out ongoing attempts of adding rust support in cmake.

Thanks. I think there are a couple of different things happening here. What this RFC is proposing is fairly different than what the Linux kernel is providing, which is to allow people to write Linux kernel code using Rust. In this instance, it makes sense for the Linux build system to directly invoke rustc and manage the dependencies itself. If another proposal attempts to allow Zephyr itself to be extended with Rust code, the cmake rust support work would make a lot of sense.

For this proposal, which is to allow applications to be developed in Rust, it probably makes more sense to just use cargo directly to build the rust application. This will allow the developer to use the crate ecosystem directly (there are lots of no_std crates that don't have strong dependencies on an operating system, and would be useful for this.

An even more ideal situation for this would be that a developer could just have a directory with their application, with no cmake file at all in it, and just add a dependency on a zephyr crate. This crate would handle the Zephyr build "behind the scenes". I've decided to not start with this approach because it would require a lot of changes to the Zephyr build system, and I'm not sure it is really what we want. We aren't trying to hide Zephyr from the user, but allow them to write their app in Rust.

d3zd3z commented 9 months ago

One thing to note: When/if the Rust glue code (devicetree interface, driver interface, syscall interface, etc.) for writing Zephyr applications in Rust is added, this will effectively mean that developers touching these areas in Zephyr C-code will need to also need to be knowledgeable about how to do the required changes to the Rust support code to match.

This is indeed a challenge, and what happens will kind of depend on what level of support Rust ends up. I do feel it would be sad if the Rust support didn't keep up, and required independent effort later to make it work again after any changes. I think one thing that will help here is if the rust interfacing for things where it make sense happen with the same code in tree that generates the C code. But yes, I think how this happens will depend a lot on whether Rust support becomes popular or not.

teburd commented 9 months ago

I think this is great overall, I'd note that for async you could probably pretty easily build an executor on top of rtio as it is just that. An async executor with io_uring like semantics. Ownership ideas were kept in mind when I wrote it as well.

teburd commented 9 months ago

One thing to note: When/if the Rust glue code (devicetree interface, driver interface, syscall interface, etc.) for writing Zephyr applications in Rust is added, this will effectively mean that developers touching these areas in Zephyr C-code will need to also need to be knowledgeable about how to do the required changes to the Rust support code to match.

I mean this is somewhat true even for C++ today, as much as people seem to believe C++ is a superset... this just isn't true and Zephyr is using C features that aren't in C++ in some cases.

Hence the entire cpp test. A rust version of this sort of thing could be constructed.

henrikbrixandersen commented 9 months ago

I mean this is somewhat true even for C++ today, as much as people seem to believe C++ is a superset... this just isn't true and Zephyr is using C features that aren't in C++ in some cases.

In some sense it is similar, but the major difference between C++ support and Rust support would be that C++ re-uses the C definitions and APIs (so usually changes to the C APIs/macros does not require touching any C++ code), whereas Rust support would introduce new, Rust bindings for existing C APIs, requiring developers to modify/be knowledgeable about the Rust language.

I am not saying this is a show-stopper. I am merely pointing out, that this is a new side-effect compared to existing in-tree language support.

Hence the entire cpp test. A rust version of this sort of thing could be constructed.

That would be a requirement for introducing Rust bindings in my opinion.

daniel-thompson commented 9 months ago

Great summary.

Only observation is that async support seems a little early within the doc structure (at least assuming that it could be implement towards the end of any project plan). Maybe I haven't thought about it enough but it seems like async support ends up being built largely using foundational pieces found elsewhere in the proposal rather than having its design guided by the syscall interfaces (put another way, is it mostly built on top of sound wrappers for the Zephyr API?).

I'm not saying it wouldn't be awesome but a lot could be done without it!

mbolivar-ampere commented 9 months ago

@d3zd3z regarding devicetree support, have you considered an approach using rust's macro system instead of a data structure?

The main thing stopping us from doing a devicetree data structure in C was the ROM overhead.

Edit: example: the qemu_cortex_m3's devicetree, if converted to a DTB, is 3.4K. That is larger than the entire samples/basic/minimal binary with multithreading and timers disabled, and it's over 40% of the size of the minimal binary with multithreading and preemption enabled. It's not a trivial increase in the minimum size of a binary, and that is a very simple board -- the DTB for larger boards is in the 15K range. And that's just the data structure itself, not the tree walking code.

Ayush1325 commented 9 months ago

@d3zd3z Great proposal. I am busy with some other stuff currently but I will try to help whenever possible.

cfriedt commented 9 months ago

One "nice to have" thing would be ZTest integration.

Is it possible to make compiled Rust test code generate compatible iterable sections?

There are probably many different ways of running tests in Rust, but ZTest integration would be nice.

What do you think @yperess ?

cfriedt commented 9 months ago

I think @d3zd3z did a good job of highlighting the most important targets, also from Meta's perspective.

My original feature list (approximately)

ZTest support would be nice..

It would be nice to consider how to do these things in a space-optimized way so that we can use Rust on small-ish devices.

It would be nice to minimize the mental leap required to go from normal Rust (or the embedded one) to Zephyr's version of Rust (or rather for Zephyr to fill in certain standard routines).

mcscholtz commented 9 months ago

This is a wonderful suggestion. We are currently working on a product that is based on Zephyr and rust, most of the application is in rust.

It has not been easy. What we ended up doing was manually wrapping only what we need in our own rust bindings which while not ideal works and is easy to understand.

We currently have a rust main function and build the rust code as a static library. Threads are defined in C and a rust macro is used on the rust side to connect with them which is a bit messy. Having the dynamic stack allocation should help a lot with this.

Some feedback of our biggest pain points / suggestions:

  1. Device/Driver binding. This is the number one issue as mentioned by @d3zd3z, dealing with C defines and macros from rust is a bit of a show stopper. So there needs to be a way to access devicetree / devices from rust.
  2. The zephyr HAL/bindings as a crate that maps to the zephyr C HAL/bindings, even if it is just the unsafe raw bindings. Having an official rust interface would allow the rust community to contribute.

Other things would be nice, like threads or logging as well as synchronization primitives. However, those can be dealt with in the current state even if it is inconvenient. Interfacing with the devicetree is a much bigger issue and is much messier to work with from rust.

kgugala commented 9 months ago

I believe starting out from something like @cfriedt's wishlist above would be very reasonable.

Starting with user space Rust support a.k.a "main() is in rust" sounds like a good option. This way the whole integration will initially stay out of the main build flow, so we'll not cause any problems to other maintainers before the Rust support is stable.

To support user apps like this we will need some functionalities, most importantly:

I assume the app would be built as no_std so we'd need to provide (generate on the fly perhaps) bindings to Zephyr's kernel std handlers. All the generators would have to be integrated either with Zephyr's cmake build flow, or as a custom rust create build (whatever is more convenient for the rest of the system).

The Rust Zephyr application could be easily compiled as a library and simply linked with the kernel.

When it comes to devicetrees, I suppose we'd have to introduce an additional dts parsing tool generating a set of Rust static lookup tables using sth like e.g https://crates.io/crates/phf from the dts. I suppose we don't want to include the whole textual dts in the binary as it would blow up the memory footprint of the app.

For Ztest integration we could have a crate which will collect all the functions decorated with e.g. #[ztest] and generate a C code defining a testsuite and registering testsfunctions. We'll need to, of course, generate C/Rust bindings so we can call Rust tests from C. To be able to run the tests we also need base support for Rust applications in Zephyr.

d3zd3z commented 9 months ago

@d3zd3z regarding devicetree support, have you considered an approach using rust's macro system instead of a data structure?

The main thing stopping us from doing a devicetree data structure in C was the ROM overhead.

Edit: example: the qemu_cortex_m3's devicetree, if converted to a DTB, is 3.4K. That is larger than the entire samples/basic/minimal binary with multithreading and timers disabled, and it's over 40% of the size of the minimal binary with multithreading and preemption enabled. It's not a trivial increase in the minimum size of a binary, and that is a very simple board -- the DTB for larger boards is in the 15K range. And that's just the data structure itself, not the tree walking code.

This proposal doesn't build a data structure for the tree. It builds data structures for the device nodes, and, for the most part, these will be empty things, and take no space.

I'm thinking it will be best to fill the DT out as a module tree in rust with consts throughout. It will take no space.

therealprof commented 9 months ago

In some sense it is similar, but the major difference between C++ support and Rust support would be that C++ re-uses the C definitions and APIs (so usually changes to the C APIs/macros does not require touching any C++ code), whereas Rust support would introduce new, Rust bindings for existing C APIs, requiring developers to modify/be knowledgeable about the Rust language.

Rust has C/C++ compatibility in mind and Rust bindgen can automatically create "unsafe" wrappers to almost any C code, which is exploited by thousands of "-sys" crates doing just that. Not sure how the Linux kernel deals with that but I'm pretty sure they have some clever ideas up their sleeves to not make Joe Linux Developer having to worry about Rust.

d3zd3z commented 8 months ago

In some sense it is similar, but the major difference between C++ support and Rust support would be that C++ re-uses the C definitions and APIs (so usually changes to the C APIs/macros does not require touching any C++ code), whereas Rust support would introduce new, Rust bindings for existing C APIs, requiring developers to modify/be knowledgeable about the Rust language.

Rust has C/C++ compatibility in mind and Rust bindgen can automatically create "unsafe" wrappers to almost any C code, which is exploited by thousands of "-sys" crates doing just that. Not sure how the Linux kernel deals with that but I'm pretty sure they have some clever ideas up their sleeves to not make Joe Linux Developer having to worry about Rust.

Although the bindgen might be useful for this, I think it really isn't all that helpful, mainly because much of the key things we want to bind to are defined in generated files, based on how userspace is configured. In the syscall configuration, these don't even generate C function that can be called. It might make sense to generate the rust bindings in the same place as the C bindings.

As far as drivers and such, I don't really think unsafe bindings are all that useful, except possibly, as you say, in some kind of -sys crate that would then have plainly usable crates available to provide a nice driver interface.

adityashah1212 commented 8 months ago

My two cents here. Note that I haven't worked with Zephyr but intend to use it in near future. I would want to have Rust support, since I intend to have Rust as the primary language for the application.

  1. We should have DeviceTree add syscalls be generated using the same script that currently generates for C. This is due to two reasons
    1. The generated code is zero sized. Rust Zero sized types are truly zero sized and don't take any space in memory
    2. We can avoid the whole FFI business and corresponding abstraction cost
  2. I don't think having code exposed through FFI is a great idea, especially to application code, since that means we venture into unsafe domain and that basically breaks the whole reason to support Rust. Anything that goes through FFI should be wrapped with proper API's that guarantee safety, just like standard library does with libc. Of course this rules out possibility of exposing bindgen results directly. Plus bindgen isn't great at handling function like macros. Of course, token pasting in macros is completely out of question
  3. I believe async support can be a lower priority, since to me, it feels like a nice to have than being a show stopper. I would much rather prefer support for logging, error handling (Rust style) and driver implementation support in Rust than having async. async anyway doesn't exactly honor priority. It is just lazy evaluation and can have issues with time guarantees depending on implementation. It is great for either extremely small applications, where execution times are very small or extremely large servers where time guarantees are not a requirement.
  4. Would love to see defmt in rust ecosystem for logging. Though this might be a bit tough.
  5. I love the idea of having zephyr as a crate which can be integrated with cargo. Since this opens a lot of tools and libraries that are available in Rust ecosystem
  6. As for the concern related to developers needing to understand Rust in addition with C; I think it is a bitter pill that needs to be swallowed at some point, since Rust does offer some compelling reasons to support it.

There is one additional thing, if and where possible, we should keep the public API's close to standard library of Rust for obvious reasons

mcscholtz commented 8 months ago

Came across rustix which is a rust wrapper for POSIX-like API's. Seems the author was trying to upstream it so that std can be used on platforms that rustix support. However, this does not look to have materialized, see here.

However it looks like rustc depends on rustix already so it is possible that in the future it would make it easier to add std support to zephyr if we could add rustix support for supported posix functions.

This is obviously something that will take a lot of work, and might not be possible/realistic. However, since zephyr is moving in the direction of increased posix support, I thought it is important to raise the point. That and I think a long term goal of eventually getting std support would be fantastic, even if it is pretty far fetched right now.

cfriedt commented 8 months ago

Came across rustix which is a rust wrapper for POSIX-like API's. Seems the author was trying to upstream it so that std can be used on platforms that rustix support. However, this does not look to have materialized, see here.

Interesting... so std Rust does make libc calls under the hood? I was under the impression that was not the case. Maybe it's the no-std part of Rust that sidesteps ISO C / POSIX? Sigh... I wish I had the time to dive deep.

However it looks like rustc depends on rustix already so it is possible that in the future it would make it easier to add std support to zephyr if we could add rustix support for supported posix functions.

This is obviously something that will take a lot of work, and might not be possible/realistic. However, since zephyr is moving in the direction of increased posix support, I thought it is important to raise the point. That and I think a long term goal of eventually getting std support would be fantastic, even if it is pretty far fetched right now.

I like it, but at the same time, there are a lot of people would would probably want to assassinate me if I suggested we layer Rust on top of POSIX in Zephyr ๐Ÿ˜… ๐Ÿ™ƒ . That's really why it's there though - it's a portability layer. Helps to get the ball rolling, but does not รผber-optimize any implementation.

cfriedt commented 8 months ago

We currently have a rust main function and build the rust code as a static library. Threads are defined in C and a rust macro is used on the rust side to connect with them which is a bit messy. Having the dynamic stack allocation should help a lot with this.

Ah nifty - I wasn't sure if that might be of interest for Rust, but it's good to know that it could be.

Async Rust might be better for memory-constrained systems - effectively using coroutines instead of threads to save on stack memory / multiple thread stacks. But of course, the implication is that any calls made by the Async code need to themselves be Async or should be minimal latency, so it requires careful attention to FW design.

It would be interesting to compare the performance of two similar memory un-constrained Rust systems - one using threads and the other using coroutines.

@teburd - were you able to get any metrics like that with the Async implementation?

teburd commented 8 months ago

We currently have a rust main function and build the rust code as a static library. Threads are defined in C and a rust macro is used on the rust side to connect with them which is a bit messy. Having the dynamic stack allocation should help a lot with this.

Ah nifty - I wasn't sure if that might be of interest for Rust, but it's good to know that it could be.

Async Rust might be better for memory-constrained systems - effectively using coroutines instead of threads to save on stack memory / multiple thread stacks. But of course, the implication is that any calls made by the Async code need to themselves be Async or should be minimal latency, so it requires careful attention to FW design.

It would be interesting to compare the performance of two similar memory un-constrained Rust systems - one using threads and the other using coroutines.

@teburd - were you able to get any metrics like that with the Async implementation?

Memory usage of Rust async is about optimal given the storage size of the task is perfectly matched to its state. The downside as you noted is the cooperative task scheduling aspect. That's pretty true of any async'y thing (epoll/io_uring/etc). You can see that in play with a few of the rust async schedulers, some even on bare metal now, so no need to replicate it.

There's io_uring libraries for rust async already so rtio (io_uring like API) could probably have something similar. Something has to manage rust tasks (future chains) though on top of that. So less optimal than the pure rust solution or pure C solution.

Ayush1325 commented 8 months ago

Interesting... so std Rust does make libc calls under the hood? I was under the impression that was not the case. Maybe it's the no-std part of Rust that sidesteps ISO C / POSIX? Sigh... I wish I had the time to dive deep.

It is not exactly that std Rust depends on libc. Rather std uses libc in most platforms since it is usually the most stable way to interact with OS. However, this does not mean libc is needed for std. A good example is UEFI which has basic Rust std support (my GSoC 2022 project https://github.com/rust-lang/rust/pull/105861) and directly uses UEFI protocols.

teburd commented 8 months ago

esp-idf-hal also provides a std on top of freertos + esp-idf for xtensa/riscv, so its clearly possible, I don't think any libc/posix is needed in that case

Enabling std would allow using things like serde and the std thread/synchronization modules which are quite nice.

mcscholtz commented 7 months ago

I hope I'm not stating the obvious but are we all aware of the work that has been done here: https://github.com/tylerwhall/zephyr-rust ?

Perhaps it is not doing things the ideal way, and it is still missing some things but already have minimal std support (Uses it's own rust fork). Could be something to look at for inspiration at the very least.

d3zd3z commented 7 months ago

I hope I'm not stating the obvious but are we all aware of the work that has been done here: https://github.com/tylerwhall/zephyr-rust ?

Perhaps it is not doing things the ideal way, and it is still missing some things but already have minimal std support (Uses it's own rust fork). Could be something to look at for inspiration at the very least.

Yes, I've definitely looked at that work. I think 'std' is probably not the best approach, mostly for the reasons seen, that it ties it tightly to the specific toolchains. Maybe that is something to consider in the further future. But, with alloc as a separate crate, a lot of what people need can be found there. But, there is definitely some good work to look into for other aspects.

JohnCarterGonzalez commented 7 months ago

There is @cfriedt list of early features. What would a roadmap or development timeline of something like be? I ask as a novice embedded programmer but one who is excited about the possibility of Zephyr having Rust support.

cfriedt commented 7 months ago

This is going to be loads of fun ๐Ÿ˜

JohnCarterGonzalez commented 7 months ago

I know there is still discussion on how this will be made possible. As a early-career developer, are there references to how this kind of support is achieved? An example would be, how did C++ support come online? What part of Zephyr or systems programming could/should I be studying to learn how to contribute? I appreciate any feedback and helping me out in growing as a software engineer.

mcscholtz commented 5 months ago

I have added some very basic bindings and a demo here

There are basic bindings for zephyr objects to rust std-like objects:

Just kernel space for now but all the calls go through syscall bindings instead of linking directly to the zimpl* functions so userspace can be supported later.

The Mutex works like the std mutex, creating a scoped guard.

The thread takes a FnOnce () -> T closure, this requires dynamic thread stacks and allocation. This allows spawning threads like how std rust does it.

This is a very silly demo that just spawns some threads and demonstrates the use of the mutex. I have tested it on a nrf5340 devkit I have here.

I know that there are a lot of stuff missing here, but is this roughly the direction of how the community envision bindings for rust? That is create an std-like library (where there are equivalents, mutex, thread, etc..) implemented over hand written syscall wrappers

adoerr commented 5 months ago

That is create an std-like library (where there are equivalents, mutex, thread, etc..) implemented over hand written syscall wrappers

I fear there will be an eternal discussion as to wether the goal for Rust on Zephyr should be to enable std or wether it should strictly remain no_std. Obviously, even for no_std having an equivalent for some of the std modules, like std::sync does make a lot of sense. In the end, I guess, a case could be made for both directions.

What is needed in either case, however, is tooling / support in order to interact with the Devicetree and the configuration / build system. Based on such a set of tools there should be a build target which will generate the low-level (FFI) Rust bindings, taking into account a set of configuration items (support for user space etc.)

With that level of support we have at least addressed some of the major pain points regarding Rust support. In addition, being able to generate those low-level Rust bindings it will be much easier for developers familiar with (embedded) Rust but not so much with the intricacies of the Zephyr build and configuration system, to start building on top.

Perhaps, as a community, we should focus on this level of support first, so that the RFC moves forward?

In a sense, we would kind of mimic the approach seen within the bare-metal embedded Rust community. Namely, having a set of (layered) crates starting with architecture support, then a runtime crate on top etc.

mcscholtz commented 5 months ago

What is needed in either case, however, is tooling / support in order to interact with the Devicetree and the configuration / build system. Based on such a set of tools there should be a build target which will generate the low-level (FFI) Rust bindings, taking into account a set of configuration items (support for user space etc.)

I 100% agree that is the more difficult problem that needs to be addressed and one I am not sure what a good approach would be. Not sure how you would link something like Kconfigs to rust features for example.

Also agree about the device tree, bindings can be made for system calls like this:

__syscall bool device_is_ready(const struct device *dev);
__syscall const struct device *device_get_binding(const char *name);

Similar bindings can be made for drivers.

However, for anything more involved like parsing the devicetree into some sort of compile time rust representation there will need to be some hefty work done on the build scripts. Which will be better but require a lot of work compared to the above.

Rust already addresses some of the reasons for using userspace (not all). For userspace support more work is needed, I have done this in the past but having to decorate objects with linker sections is cumbersome, see below. Not to mention all of the memory partitions are created via C macros. This can be alleviated by some rust macros, probably.

#[link_section = "data_smem_rust_app_heap_partition_data"]

There are also other systems where the shear amount of C macros used makes it easier in some cases to use the core OS primitive bindings to re-implement the feature in Rust. Not sure how to reconcile the Kconfig options with the behavior of the rust in such a case.

Ayush1325 commented 4 months ago

I 100% agree that is the more difficult problem that needs to be addressed and one I am not sure what a good approach would be. Not sure how you would link something like Kconfigs to rust features for example.

The Linux kernel already does this, so we have a reference implementation for it.

Also agree about the device tree, bindings can be made for system calls like this:

__syscall bool device_is_ready(const struct device *dev);
__syscall const struct device *device_get_binding(const char *name);

Similar bindings can be made for drivers.

However, for anything more involved like parsing the devicetree into some sort of compile time rust representation there will need to be some hefty work done on the build scripts. Which will be better but require a lot of work compared to the above.

Rust already addresses some of the reasons for using userspace (not all). For userspace support more work is needed, I have done this in the past but having to decorate objects with linker sections is cumbersome, see below. Not to mention all of the memory partitions are created via C macros. This can be alleviated by some rust macros, probably.

#[link_section = "data_smem_rust_app_heap_partition_data"]

There are also other systems where the shear amount of C macros used makes it easier in some cases to use the core OS primitive bindings to re-implement the feature in Rust. Not sure how to reconcile the Kconfig options with the behavior of the rust in such a case.

I think it might be good to follow Linux kernel example in how to integrate Rust support. The initial Rust support did not need to have devicetree or driver support (still mostly doesn't). Instead, it would be best to start with getting the plumbing to compile hello world application and allow writing stuff (crypto algorithm, etc) which require minimal interaction with Zephyr stuff.

The support to interact with other Zephyr stuff can come later, once the plumbing for Rust and bindgen is all there.

I personally do not think there is much benefit to having std support, at least not for quite a while. If I had to compare std to something, it would be POSIX (and not just c std). While it might be nice to have, most embedded developers would require the ability to use Zephyr APIs directly once they hit the limits of what the std API provides.

The biggest reason to use Rust, at least for me, is the compiler (aka borrow checker), not the std.

d3zd3z commented 1 month ago

I personally do not think there is much benefit to having std support, at least not for quite a while. If I had to compare std to something, it would be POSIX (and not just c std). While it might be nice to have, most embedded developers would require the ability to use Zephyr APIs directly once they hit the limits of what the std API provides.

I agree on std. I think earlier efforts focused more on std, because things like allocation and collections were tied to that. Now that alloc is a separate crate, I think it makes a lot more sense to support alloc, and then provide interfaces as appropriate for Zephyr.

mjaun commented 1 month ago

Thank you very much @d3zd3z for driving this topic. I did some experiment based on your pull request #75904 which I would like to share. It contains a blinky example to demonstrate how devicetree information could be accessed from Rust. You can find it here: #76199

I would be interested what you guys think. If you would like me to do some other experiments or could use support on some implementation please let me know, I would be happy to contribute.

kloenk commented 2 weeks ago

I 100% agree that is the more difficult problem that needs to be addressed and one I am not sure what a good approach would be. Not sure how you would link something like Kconfigs to rust features for example.

The Linux kernel already does this, so we have a reference implementation for it.

This is build with non cargo in mind, but the response file should somehow be managable through cargo, though a bit more pain. As far as I looked into zephyr kconfig it uses the python implementation, where I don't know how to add this response file generation as added to the C version of KConfig. Happy to help to implement that though just don't wanna touch unknown python code alone (I did the KConfig support for rust response files in the linux tree quite some time ago)

kloenk commented 2 weeks ago

Created a basic rustc_cfg writer. No clue how to best wire it up though, help would be appreciated.

Tried it by just stupidly searching for autoconf.h and so far it seems to be working, though I did not look at cargo at all yet, and my cmake is even worse then my python https://github.com/kloenk/zephyr/commit/2fe4b33b7d923274273087bf67df0cfded4336b5