-
Notifications
You must be signed in to change notification settings - Fork 193
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
Add Cargo feature to disable handling of Peripherals
#163
Comments
Hi Hanno
Well RTFM is intended to provide safe access to (shared) resources, e.g. allowing us through a HAL impl. to split peripherals, freeze clocks etc. Having the HAL owning the peripherals might as you wrote pose some problems to composability.
E.g., if we want the HAL to modular, how would we ensure that we don't have two owners of the peripheral (or part thereof). E.g., we might want to have generic USB driver for all STM32, with a feature to select between different USB impl. FS, OTG_FS, OTG_HS. Same for Ethernet. I think in the long run, a modular HAL has benefits over the monolithic approach. The more we can modularize, the less work we will have maintaining. We can have separation of concerns where applicable. We can have different alternative implementations of a driver, suitable to different use cases. This goes further than ST, the USB e.g., is common to other vendors, developed by
Moreover, leaving it up to the HAL to give safe access sounds problematic, e.g., changing modes at run-time. The idea here is that the resource management provided by RTFM can help us out. Same goes with regard to shared resources in general.
I would like to see use cases (real ones), that prevents you/the developer of making a HAL based on RTFM. And for such use cases (if there are any), let's see how RTFM can be improved.
For sure there are things that need consideration, how should we handle callbacks, shared resources between application and drivers, shared resources between drivers, etc. All in all I think this all boils down to a task and resource model, which is exactly what RTFM brings, inventing a new one/hack for each HAL implementation seems not a viable way to go.. and we don't want systems that may hang/panic at run-time due to dynamic checking of resource locks right.
I can see a problem though with RTFM/HAL design, it discourages you from making a hack, just to get things working. I think however in the long run its a good thing.
Best regards,
Per
…________________________________
Från: Hanno Braun <notifications@github.com>
Skickat: den 6 mars 2019 10:11:57
Till: japaric/cortex-m-rtfm
Kopia: Subscribed
Ämne: [japaric/cortex-m-rtfm] Add Cargo feature to disable handling of `Peripherals` (#163)
RTFM currently acquires a Peripherals instance and hands it to the user. This can be problematic, as it doesn't compose with libraries that want to do the same. While it can be a good idea to change the affected library, there may be valid reasons for wanting to control the creation of Peripherals.
One such example would be HAL libraries that want to provide strong static guarantees without introducing runtime overhead. I believe it can be argued that a HAL library, which is intended to be the primary means to access a given device, would be within its rights to require full control over Peripherals, even though that doesn't compose with other libraries.
@japaric<https://github.com/japaric> and I talked about this on IRC yesterday, and he said that RTFM's handling of Peripherals is just a convenience feature (my understanding is that RTFM only actually requires CorePeripherals), and that he wouldn't be opposed adding a Cargo feature that disables this. This sounds like a good solution to me.
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub<https://github.com/japaric/cortex-m-rtfm/issues/163>, or mute the thread<https://github.com/notifications/unsubscribe-auth/AD5naCp7Whe1CPSz66puclUlUihpjmVIks5vT4ZdgaJpZM4bgZG3>.
{"api_version":"1.0","publisher":{"api_key":"05dde50f1d1a384dd78767c55493e4bb","name":"GitHub"},"entity":{"external_key":"github/japaric/cortex-m-rtfm","title":"japaric/cortex-m-rtfm","subtitle":"GitHub repository","main_image_url":"https://github.githubassets.com/images/email/message_cards/header.png","avatar_image_url":"https://github.githubassets.com/images/email/message_cards/avatar.png","action":{"name":"Open in GitHub","url":"https://github.com/japaric/cortex-m-rtfm"}},"updates":{"snippets":[{"icon":"DESCRIPTION","message":"Add Cargo feature to disable handling of `Peripherals` (#163)"}],"action":{"name":"View Issue","url":"https://github.com/japaric/cortex-m-rtfm/issues/163"}}} [ { "@context": "http://schema.org", "@type": "EmailMessage", "potentialAction": { "@type": "ViewAction", "target": "https://github.com/japaric/cortex-m-rtfm/issues/163", "url": "https://github.com/japaric/cortex-m-rtfm/issues/163", "name": "View Issue" }, "description": "View this Issue on GitHub", "publisher": { "@type": "Organization", "name": "GitHub", "url": "https://github.com" } } ]
|
Hi (again)
Let's take a step back.
In previous research (that eventually led up to the RTFM model, and thus the underpinning of cortex-m-rtfm) investigated how the "environment" could best be modeled.
We developed an experimental language (http://www.timber-lang.org/), which combines functional and imperative constructs much like Rust. The resource management is very similar to RTFM resources (but based on components/objects).
We evaluated different approaches, and ended up with the idea of a "root" object. The constructor of "root" was passed a "world" parameter (in Rust amounting to marker type) which is unique (cannot be constructed inside of the application, but passed as a parameter). In this case passed to the posix library (creating a binding "env" to the singleton posix environment.
"echo" in the example is a definition of a class (or component interface), with a single action ("handler" that can be called asynchronously). Notice here that "echo" takes a reference to "env".
In "root" we create an instance of "echo" named "handler", delegating a reference to "env".
Finally we bind the "handler" to the "env.stdin.installR" (posix environment singleton).
That's it! The system is fully declarative, when we have set up our bindings to the environment, the system goes to sleep, and awoken only from the environment, on a standard in event, which causes the "handler" to be executed and echos back the received string.
So in 15 lines of code, we have implemented a fully reactive system. (The "handler" could have been declared directly as a closure inside of "root" if so wished (not requiring an echo class).
So what can we learn from this?
In RTFM "init" amounts to our Timber "root", with access to a "device", which it owns and can delegate (amounting to "world" in Timber). Its not written in stone that it must be a "PAC", it could be anything, e.g., a HAL. "idle" was introduced in RTFM as a special case for defining the idle behavior, we did not have any prescribed way of doing that in Timber, so RTFM is not a 1-1 mapping of Timber, but many of the ideas remain.
So in conclusion, I think the "world" should be owned and under control of "init", but we can think of different ways to deal with the "world", e.g., using world to instantiate either a raw PAC, or a HAL (owning the PAC). But it must be clear that each resource is owned only once and that underlying resources are singletons else we will run into problems with Rust aliasing.
Best regards
Per
PS.
I think the Timber Tutorial Examples gives a good intuition on how programming in a high level langue might look like without all the "noise" of the Rust type system. Timer has type classes (similar to Rust traits), closures similar to Rust, functions are pure (stronger condition than Rust), type inference (stronger than Rust). So many nice features and elegant as heck, but the compiler is not on par with rustc, so efficiency of generated code is more in the range between C/C++ and Haskell. It also requires a run-time with a memory allocator, so in practice Timber is currently not useful to bare metal...
We did not pursue development, it takes an extreme amount amount of work to develop a language, compiler, eco system etc., so we opted to bring some of the experiences/insights from Timber into an existing language/eco system. To that end, Rust is the closest you get. (And yes, Timber is also memory safe, with guarantees on par with Rust...)
…---
module Echo where
import POSIX
echo env = class
handler str = action
env.stdout.write str
result handler
root world = do
env = new posix world
handler = new echo env
env.stdin.installR handler
Timber<http://www.timber-lang.org/>
www.timber-lang.org
Timber - the programming language.
________________________________
Från: Per Lindgren
Skickat: den 6 mars 2019 12:23:23
Till: japaric/cortex-m-rtfm
Ämne: SV: [japaric/cortex-m-rtfm] Add Cargo feature to disable handling of `Peripherals` (#163)
Hi Hanno
Well RTFM is intended to provide safe access to (shared) resources, e.g. allowing us through a HAL impl. to split peripherals, freeze clocks etc. Having the HAL owning the peripherals might as you wrote pose some problems to composability.
E.g., if we want the HAL to modular, how would we ensure that we don't have two owners of the peripheral (or part thereof). E.g., we might want to have generic USB driver for all STM32, with a feature to select between different USB impl. FS, OTG_FS, OTG_HS. Same for Ethernet. I think in the long run, a modular HAL has benefits over the monolithic approach. The more we can modularize, the less work we will have maintaining. We can have separation of concerns where applicable. We can have different alternative implementations of a driver, suitable to different use cases. This goes further than ST, the USB e.g., is common to other vendors, developed by
Moreover, leaving it up to the HAL to give safe access sounds problematic, e.g., changing modes at run-time. The idea here is that the resource management provided by RTFM can help us out. Same goes with regard to shared resources in general.
I would like to see use cases (real ones), that prevents you/the developer of making a HAL based on RTFM. And for such use cases (if there are any), let's see how RTFM can be improved.
For sure there are things that need consideration, how should we handle callbacks, shared resources between application and drivers, shared resources between drivers, etc. All in all I think this all boils down to a task and resource model, which is exactly what RTFM brings, inventing a new one/hack for each HAL implementation seems not a viable way to go.. and we don't want systems that may hang/panic at run-time due to dynamic checking of resource locks right.
I can see a problem though with RTFM/HAL design, it discourages you from making a hack, just to get things working. I think however in the long run its a good thing.
Best regards,
Per
________________________________
Från: Hanno Braun <notifications@github.com>
Skickat: den 6 mars 2019 10:11:57
Till: japaric/cortex-m-rtfm
Kopia: Subscribed
Ämne: [japaric/cortex-m-rtfm] Add Cargo feature to disable handling of `Peripherals` (#163)
RTFM currently acquires a Peripherals instance and hands it to the user. This can be problematic, as it doesn't compose with libraries that want to do the same. While it can be a good idea to change the affected library, there may be valid reasons for wanting to control the creation of Peripherals.
One such example would be HAL libraries that want to provide strong static guarantees without introducing runtime overhead. I believe it can be argued that a HAL library, which is intended to be the primary means to access a given device, would be within its rights to require full control over Peripherals, even though that doesn't compose with other libraries.
@japaric<https://github.com/japaric> and I talked about this on IRC yesterday, and he said that RTFM's handling of Peripherals is just a convenience feature (my understanding is that RTFM only actually requires CorePeripherals), and that he wouldn't be opposed adding a Cargo feature that disables this. This sounds like a good solution to me.
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub<https://github.com/japaric/cortex-m-rtfm/issues/163>, or mute the thread<https://github.com/notifications/unsubscribe-auth/AD5naCp7Whe1CPSz66puclUlUihpjmVIks5vT4ZdgaJpZM4bgZG3>.
{"api_version":"1.0","publisher":{"api_key":"05dde50f1d1a384dd78767c55493e4bb","name":"GitHub"},"entity":{"external_key":"github/japaric/cortex-m-rtfm","title":"japaric/cortex-m-rtfm","subtitle":"GitHub repository","main_image_url":"https://github.githubassets.com/images/email/message_cards/header.png","avatar_image_url":"https://github.githubassets.com/images/email/message_cards/avatar.png","action":{"name":"Open in GitHub","url":"https://github.com/japaric/cortex-m-rtfm"}},"updates":{"snippets":[{"icon":"DESCRIPTION","message":"Add Cargo feature to disable handling of `Peripherals` (#163)"}],"action":{"name":"View Issue","url":"https://github.com/japaric/cortex-m-rtfm/issues/163"}}} [ { "@context": "http://schema.org", "@type": "EmailMessage", "potentialAction": { "@type": "ViewAction", "target": "https://github.com/japaric/cortex-m-rtfm/issues/163", "url": "https://github.com/japaric/cortex-m-rtfm/issues/163", "name": "View Issue" }, "description": "View this Issue on GitHub", "publisher": { "@type": "Organization", "name": "GitHub", "url": "https://github.com" } } ]
|
Hey Per, thank you for your reply!
I agree with your point on modular vs monolithic HALs. My intent is not a monolithic HAL API, my intent is to prevent the user from changing the hardware configuration before the HAL API is initialized. That I need control over all of
I don't understand this part, probably because I know only the very basics of how RTFM works. Providing safe access to peripherals is, to me, the primary reason for having a HAL API, and I don't understand how RTFM, being device-agnostic, can help me out in that regard. (I think we might be talking about different things, and use different definitions of "safe".)
Gladly. To provide some context, my experience here comes primarily from working on To understand why preventing the user from changing the hardware configuration before initializing the HAL API can be beneficial, let's start with a simple example. Let's say I have a GPIO API, written in the common style (i.e. extension trait for the PAC peripheral with Because This being a simple example, it has a simple solution: Add a third state, A more complex example from I have modeled all of this in the Unfortunately, there's no way to reset the whole peripheral in this case, so I'd have to reset all the registers individually. With 13 registers in the case of LPC82x, we're looking at a code size overhead that's significant in many more cases. The smallest LPC82x variant has 16k flash memory. The situation is similar, but more critical, on the smaller LPC81x (4k flash and a similar number of registers, if I recall correctly).
Even though I'm not very familiar with RTFM (it's one of those tasks on my list that I never seem to find time for), I see its promise, and I totally agree that these things shouldn't be re-invented. All the more important to find ways for RTFM to impose as few restrictions as possible on HAL APIs.
I agree with the sentiment, but I don't think it applies here.
This sounds great! If And by the way, I should find some time to look into Timber, one of these days. It sounds really interesting! |
Hi Hanno and everybody interested in future HAL development.
Thanks for your input, this is exactly the kind of constructive discussion to bring us forward.
I will go over the details later, but first some short notes.
1. Singletons in Timber are ensured by enforcing all calls to "new" to return a reference to the same object. We cannot do that in Rust, as we have the no_alias requirement. (That is a fundamental difference, and one Reason that rustc-llvm is able to optimize code better than we see for other languages in general.) So picking up the "world" idea in RTFM would require a consuming/move pattern.
2. Instead of passing peripherals, we could pass "world" and let that be consumed by either a "new" HAL, or a "new" PAC, returning their view of the world. A HAL could re-export parts not owned (managed) by the HAL, and let the user be free to manage that by himself. This would allow you to run initiation code before the user code, (the user cannot access the peripherals before creating either a HAL or a PAC). Still it would be in "init", so no black magic, no code run invisible to the developer (besides what we expect from the Reset handler regarding memory initialization.
3. How could RTFM be of help to maintaining resources inside of a HAL, while still being HAL agnostic. I believe this to be possible, but might require some re-factoring. We could expose the "claim" (or "lock") of a resource, with different backing implementations. Called from within the RTFM realm, with a guarantee to succeed, called from raw code (e.g. your HAL) with a panic in case the resource is already locked (but if you know that in your code there is a single owner of the resource "claim" will always succeed). Perhaps the implementation can even be the same, if the compiler is smart enough to optimize out the panicing path (it needs some experimentation). You might even use Exclusive, that would allow you to deref resources without panics, but then responsibility/soundness is completely up to you.
By exposing "claim"/"lock" to your HAL, shared resources passed from the RTFM realm could be freely used in your application, with success guarantees. Note in this case you still have no dependency to RTFM "app", just to the locking implementation. We already have a Mutex implementation in "cortex-m", perhaps it can be reused somehow.
The "claim"/"lock" implementation for a Resource is by no means restricted to RTFM, its just a bare bones implementation of a priority ceiling protocol using the underlying HW in a smart way for the M3 and above (and interrupt masking for <M3, but that has not been ported yet from the original C implementation of RTFM).
Thus, you may come up with your own framework/tool or whatever means to determine the ceiling levels (even manually) and you should be ready to go using a HAL based on a "claim"/"lock" API. So there is no "lock in" to RTFM in the mix.
The alternative is to have (potentially) blocking resource access, but this is highly undesirable (deadlocks, non predictable timing etc.) We should not go there (again) I hope...
Best regards
Per
Side note:
In the original RTFM implementaion we used "claim" to indicate that its a named critical section with guaranteed success. "lock" typically leads to think in terms of blocking (RTFM resources are non blocking). That's a key feature to ensure liveness (deadlock freedom). We also experimented with an Option/Result but as RTFM gives guarantees we don't need it and would make the API noisier. Note that panic is not all bad, it actually gives you safety on expense of reliability. This is still fine, e.g., SafeRTOS get away with this reasoning even in the case of certification for safety critical applications (automotive etc.), in essence, what they do on a panic (undefined state reached) is to call a user defined handler and that's it...
…________________________________
Från: Hanno Braun <notifications@github.com>
Skickat: den 7 mars 2019 16:34:26
Till: japaric/cortex-m-rtfm
Kopia: Per Lindgren; Comment
Ämne: Re: [japaric/cortex-m-rtfm] Add Cargo feature to disable handling of `Peripherals` (#163)
Hey Per, thank you for your reply!
E.g., if we want the HAL to modular, how would we ensure that we don't have two owners of the peripheral (or part thereof). E.g., we might want to have generic USB driver for all STM32, with a feature to select between different USB impl. FS, OTG_FS, OTG_HS. Same for Ethernet. I think in the long run, a modular HAL has benefits over the monolithic approach. The more we can modularize, the less work we will have maintaining. We can have separation of concerns where applicable. We can have different alternative implementations of a driver, suitable to different use cases. This goes further than ST, the USB e.g., is common to other vendors, developed by
I agree with your point on modular vs monolithic HALs. My intent is not a monolithic HAL API, my intent is to prevent the user from changing the hardware configuration before the HAL API is initialized.
That I need control over all of Peripherals to do this is incidental, because that's how the lower-level layer happens to regulate access to the peripheral APIs.
Moreover, leaving it up to the HAL to give safe access sounds problematic, e.g., changing modes at run-time. The idea here is that the resource management provided by RTFM can help us out. Same goes with regard to shared resources in general.
I don't understand this part, probably because I know only the very basics of how RTFM works.
Providing safe access to peripherals is, to me, the primary reason for having a HAL API, and I don't understand how RTFM, being device-agnostic, can help me out in that regard.
(I think we might be talking about different things, and use different definitions of "safe".)
I would like to see use cases (real ones), that prevents you/the developer of making a HAL based on RTFM. And for such use cases (if there are any), let's see how RTFM can be improved.
Gladly. To provide some context, my experience here comes primarily from working on lpc82x-hal<https://github.com/lpc-rs/lpc82x-hal>, which provides its own Peripherals struct and its own Peripherals::take. Both of those are a wrappers around their PAC-level equivalents.
To understand why preventing the user from changing the hardware configuration before initializing the HAL API can be beneficial, let's start with a simple example. Let's say I have a GPIO API, written in the common style (i.e. extension trait for the PAC peripheral with constrain or split method) that tracks pin configuration at compile-time. For this purpose the pin type has a type parameter (Pin<Input>, Pin<Output>).
Because Pin<Input> reflects the default hardware configuration, the HAL API makes Input the initial state of all pins. However, since the user could have changed any pin's configuration, the API's static guarantees can be easily circumvented. This is not a theoretical example; I've seen it in the wild (see nrf52-hal<https://github.com/nrf-rs/nrf52-hal/blob/c2ddfd356bac75202049500ecaa7b971c9af1705/nrf52-hal-common/src/gpio.rs#L554>, for example).
This being a simple example, it has a simple solution: Add a third state, Pin<Unknown> and force the user to explicitly change the state before using the pin. Please note, however, that this has a small overhead, as I have to initialize the pin, even if I know that the default state is exactly what I want. This overhead is unlikely to be significant, but it could be, if I have a very small microcontroller and every byte of code size matters.
A more complex example from lpc82x-hal would be the switch matrix, which is responsible for assigning alternative functions to pins. This is more complex and powerful on LPC82x than on other microcontrollers I have experience with (STM32, Nordic nRF). Not only can I assign any movable function to any pin, I can also assign multiple functions to the same pin (subject to some restrictions).
I have modeled all of this in the lpc82x-hal API, so these rules are enforced at compile-time. This API also has zero runtime overhead (at least in theory; not sure if the optimizer does all it theoretically could do), which is only possible because I can rely on all registers being in their default state when the API is initialized. If I couldn't rely on that, because of restrictions imposed by RTFM, I'd have to add additional states to the API (too complicated and error-prone; I've been down this road before) or I'd have to reset the hardware state when initializing the API.
Unfortunately, there's no way to reset the whole peripheral in this case, so I'd have to reset all the registers individually. With 13 registers in the case of LPC82x, we're looking at a code size overhead that's significant in many more cases. The smallest LPC82x variant has 16k flash memory. The situation is similar, but more critical, on the smaller LPC81x (4k flash and a similar number of registers, if I recall correctly).
For sure there are things that need consideration, how should we handle callbacks, shared resources between application and drivers, shared resources between drivers, etc. All in all I think this all boils down to a task and resource model, which is exactly what RTFM brings, inventing a new one/hack for each HAL implementation seems not a viable way to go.. and we don't want systems that may hang/panic at run-time due to dynamic checking of resource locks right.
Even though I'm not very familiar with RTFM (it's one of those tasks on my list that I never seem to find time for), I see its promise, and I totally agree that these things shouldn't be re-invented. All the more important to find ways for RTFM to impose as few restrictions as possible on HAL APIs.
I can see a problem though with RTFM/HAL design, it discourages you from making a hack, just to get things working. I think however in the long run its a good thing.
I agree with the sentiment, but I don't think it applies here.
In RTFM "init" amounts to our Timber "root", with access to a "device", which it owns and can delegate (amounting to "world" in Timber). Its not written in stone that it must be a "PAC", it could be anything, e.g., a HAL. "idle" was introduced in RTFM as a special case for defining the idle behavior, we did not have any prescribed way of doing that in Timber, so RTFM is not a 1-1 mapping of Timber, but many of the ideas remain.
So in conclusion, I think the "world" should be owned and under control of "init", but we can think of different ways to deal with the "world", e.g., using world to instantiate either a raw PAC, or a HAL (owning the PAC). But it must be clear that each resource is owned only once and that underlying resources are singletons else we will run into problems with Rust aliasing.
This sounds great! If lpc82x-hal could implement some standard RTFM-mandated API that would make it so that the fully initialized HAL API is passed to the user instead of the PAC, that would probably be the ideal solution.
________________________________
And by the way, I should find some time to look into Timber, one of these days. It sounds really interesting!
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub<https://github.com/japaric/cortex-m-rtfm/issues/163#issuecomment-470573119>, or mute the thread<https://github.com/notifications/unsubscribe-auth/AD5naBrSCdli5WJxwQLX72RRuOHCMG94ks5vUTGCgaJpZM4bgZG3>.
{"api_version":"1.0","publisher":{"api_key":"05dde50f1d1a384dd78767c55493e4bb","name":"GitHub"},"entity":{"external_key":"github/japaric/cortex-m-rtfm","title":"japaric/cortex-m-rtfm","subtitle":"GitHub repository","main_image_url":"https://github.githubassets.com/images/email/message_cards/header.png","avatar_image_url":"https://github.githubassets.com/images/email/message_cards/avatar.png","action":{"name":"Open in GitHub","url":"https://github.com/japaric/cortex-m-rtfm"}},"updates":{"snippets":[{"icon":"PERSON","message":"@hannobraun in #163: Hey Per, thank you for your reply!\r\n\r\n\u003e E.g., if we want the HAL to modular, how would we ensure that we don't have two owners of the peripheral (or part thereof). E.g., we might want to have generic USB driver for all STM32, with a feature to select between different USB impl. FS, OTG_FS, OTG_HS. Same for Ethernet. I think in the long run, a modular HAL has benefits over the monolithic approach. The more we can modularize, the less work we will have maintaining. We can have separation of concerns where applicable. We can have different alternative implementations of a driver, suitable to different use cases. This goes further than ST, the USB e.g., is common to other vendors, developed by\r\n\r\nI agree with your point on modular vs monolithic HALs. My intent is not a monolithic HAL API, my intent is to prevent the user from changing the hardware configuration before the HAL API is initialized.\r\n\r\nThat I need control over all of `Peripherals` to do this is incidental, because that's how the lower-level layer happens to regulate access to the peripheral APIs.\r\n\r\n\u003e Moreover, leaving it up to the HAL to give safe access sounds problematic, e.g., changing modes at run-time. The idea here is that the resource management provided by RTFM can help us out. Same goes with regard to shared resources in general.\r\n\r\nI don't understand this part, probably because I know only the very basics of how RTFM works.\r\n\r\nProviding safe access to peripherals is, to me, the primary reason for having a HAL API, and I don't understand how RTFM, being device-agnostic, can help me out in that regard.\r\n\r\n(I think we might be talking about different things, and use different definitions of \"safe\".)\r\n\r\n\u003e I would like to see use cases (real ones), that prevents you/the developer of making a HAL based on RTFM. And for such use cases (if there are any), let's see how RTFM can be improved.\r\n\r\nGladly. To provide some context, my experience here comes primarily from working on [`lpc82x-hal`](https://github.com/lpc-rs/lpc82x-hal), which provides its own `Peripherals` struct and its own `Peripherals::take`. Both of those are a wrappers around their PAC-level equivalents.\r\n\r\nTo understand why preventing the user from changing the hardware configuration before initializing the HAL API can be beneficial, let's start with a simple example. Let's say I have a GPIO API, written in the common style (i.e. extension trait for the PAC peripheral with `constrain` or `split` method) that tracks pin configuration at compile-time. For this purpose the pin type has a type parameter (`Pin\u003cInput\u003e`, `Pin\u003cOutput\u003e`).\r\n\r\nBecause `Pin\u003cInput\u003e` reflects the default hardware configuration, the HAL API makes `Input` the initial state of all pins. However, since the user could have changed any pin's configuration, the API's static guarantees can be easily circumvented. This is not a theoretical example; I've seen it in the wild (see [`nrf52-hal`](https://github.com/nrf-rs/nrf52-hal/blob/c2ddfd356bac75202049500ecaa7b971c9af1705/nrf52-hal-common/src/gpio.rs#L554), for example).\r\n\r\nThis being a simple example, it has a simple solution: Add a third state, `Pin\u003cUnknown\u003e` and force the user to explicitly change the state before using the pin. Please note, however, that this has a small overhead, as I have to initialize the pin, even if I know that the default state is exactly what I want. This overhead is unlikely to be significant, but it could be, if I have a very small microcontroller and every byte of code size matters.\r\n\r\nA more complex example from `lpc82x-hal` would be the switch matrix, which is responsible for assigning alternative functions to pins. This is more complex and powerful on LPC82x than on other microcontrollers I have experience with (STM32, Nordic nRF). Not only can I assign any movable function to any pin, I can also assign multiple functions to the same pin (subject to some restrictions).\r\n\r\nI have modeled all of this in the `lpc82x-hal` API, so these rules are enforced at compile-time. This API also has zero runtime overhead (at least in theory; not sure if the optimizer does all it theoretically could do), which is only possible because I can rely on all registers being in their default state when the API is initialized. If I couldn't rely on that, because of restrictions imposed by RTFM, I'd have to add additional states to the API (too complicated and error-prone; I've been down this road before) or I'd have to reset the hardware state when initializing the API.\r\n\r\nUnfortunately, there's no way to reset the whole peripheral in this case, so I'd have to reset all the registers individually. With 13 registers in the case of LPC82x, we're looking at a code size overhead that's significant in many more cases. The smallest LPC82x variant has 16k flash memory. The situation is similar, but more critical, on the smaller LPC81x (4k flash and a similar number of registers, if I recall correctly).\r\n\r\n\u003e For sure there are things that need consideration, how should we handle callbacks, shared resources between application and drivers, shared resources between drivers, etc. All in all I think this all boils down to a task and resource model, which is exactly what RTFM brings, inventing a new one/hack for each HAL implementation seems not a viable way to go.. and we don't want systems that may hang/panic at run-time due to dynamic checking of resource locks right.\r\n\r\nEven though I'm not very familiar with RTFM (it's one of those tasks on my list that I never seem to find time for), I see its promise, and I totally agree that these things shouldn't be re-invented. All the more important to find ways for RTFM to impose as few restrictions as possible on HAL APIs.\r\n\r\n\u003e I can see a problem though with RTFM/HAL design, it discourages you from making a hack, just to get things working. I think however in the long run its a good thing.\r\n\r\nI agree with the sentiment, but I don't think it applies here.\r\n\r\n\u003e In RTFM \"init\" amounts to our Timber \"root\", with access to a \"device\", which it owns and can delegate (amounting to \"world\" in Timber). Its not written in stone that it must be a \"PAC\", it could be anything, e.g., a HAL. \"idle\" was introduced in RTFM as a special case for defining the idle behavior, we did not have any prescribed way of doing that in Timber, so RTFM is not a 1-1 mapping of Timber, but many of the ideas remain.\r\n\u003e\r\n\u003e So in conclusion, I think the \"world\" should be owned and under control of \"init\", but we can think of different ways to deal with the \"world\", e.g., using world to instantiate either a raw PAC, or a HAL (owning the PAC). But it must be clear that each resource is owned only once and that underlying resources are singletons else we will run into problems with Rust aliasing.\r\n\r\nThis sounds great! If `lpc82x-hal` could implement some standard RTFM-mandated API that would make it so that the fully initialized HAL API is passed to the user instead of the PAC, that would probably be the ideal solution.\r\n\r\n---\r\n\r\nAnd by the way, I should find some time to look into Timber, one of these days. It sounds really interesting!"}],"action":{"name":"View Issue","url":"https://github.com/japaric/cortex-m-rtfm/issues/163#issuecomment-470573119"}}} [ { "@context": "http://schema.org", "@type": "EmailMessage", "potentialAction": { "@type": "ViewAction", "target": "https://github.com/japaric/cortex-m-rtfm/issues/163#issuecomment-470573119", "url": "https://github.com/japaric/cortex-m-rtfm/issues/163#issuecomment-470573119", "name": "View Issue" }, "description": "View this Issue on GitHub", "publisher": { "@type": "Organization", "name": "GitHub", "url": "https://github.com" } } ]
|
Thanks for your comment, Per. Everything you say sounds sensible to me, as far as I understand it (although there's a lot I don't understand about it, since I still don't know a lot about RTFM, as I said earlier).
This sounds very good, and would mesh perfectly with what I'm already doing (re-exporting whatever I don't wrap). The one thing I'm concerned about is how this would work when RTFM is not involved. Maybe we could add a type that represents the world to a common crate (maybe Then RTFM could create the world and pass ownership of it to Just a thought, not sure this captures all the details that would be required for an implementation. |
205: rtfm-syntax refactor + heterogeneous multi-core support r=japaric a=japaric this PR implements RFCs #178, #198, #199, #200, #201, #203 (only the refactor part), #204, #207, #211 and #212. most cfail tests have been removed because the test suite of `rtfm-syntax` already tests what was being tested here. The `rtfm-syntax` crate also has tests for the analysis pass which we didn't have here -- that test suite contains a regression test for #183. the remaining cfail tests have been upgraded into UI test so we can more thoroughly check / test the error message presented to the end user. the cpass tests have been converted into plain examples EDIT: I forgot, there are some examples of the multi-core API for the LPC541xx in [this repository](https://github.com/japaric/lpcxpresso54114) people that would like to try out this API but have no hardware can try out the x86_64 [Linux port] which also has multi-core support. [Linux port]: https://github.com/japaric/linux-rtfm closes #178 #198 #199 #200 #201 #203 #204 #207 #211 #212 closes #163 cc #209 (documents how to deal with errors) Co-authored-by: Jorge Aparicio <jorge@japaric.io>
205: rtfm-syntax refactor + heterogeneous multi-core support r=japaric a=japaric this PR implements RFCs #178, #198, #199, #200, #201, #203 (only the refactor part), #204, #207, #211 and #212. most cfail tests have been removed because the test suite of `rtfm-syntax` already tests what was being tested here. The `rtfm-syntax` crate also has tests for the analysis pass which we didn't have here -- that test suite contains a regression test for #183. the remaining cfail tests have been upgraded into UI test so we can more thoroughly check / test the error message presented to the end user. the cpass tests have been converted into plain examples EDIT: I forgot, there are some examples of the multi-core API for the LPC541xx in [this repository](https://github.com/japaric/lpcxpresso54114) people that would like to try out this API but have no hardware can try out the x86_64 [Linux port] which also has multi-core support. [Linux port]: https://github.com/japaric/linux-rtfm closes #178 #198 #199 #200 #201 #203 #204 #207 #211 #212 closes #163 cc #209 (documents how to deal with errors) Co-authored-by: Jorge Aparicio <jorge@japaric.io>
RTFM currently acquires a
Peripherals
instance and hands it to the user. This can be problematic, as it doesn't compose with libraries that want to do the same. While it can be a good idea to change the affected library, there may be valid reasons for wanting to control the creation ofPeripherals
.One such example would be HAL libraries that want to provide strong static guarantees without introducing runtime overhead. I believe it can be argued that a HAL library, which is intended to be the primary means to access a given device, would be within its rights to require full control over
Peripherals
, even though that doesn't compose with other libraries.@japaric and I talked about this on IRC yesterday, and he said that RTFM's handling of
Peripherals
is just a convenience feature (my understanding is that RTFM only actually requiresCorePeripherals
), and that he wouldn't be opposed adding a Cargo feature that disables this. This sounds like a good solution to me.The text was updated successfully, but these errors were encountered: