Skip to content
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

Closed
hannobraun opened this issue Mar 6, 2019 · 5 comments · Fixed by #205
Closed

Add Cargo feature to disable handling of Peripherals #163

hannobraun opened this issue Mar 6, 2019 · 5 comments · Fixed by #205

Comments

@hannobraun
Copy link

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 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.

@perlindgren
Copy link
Collaborator

perlindgren commented Mar 6, 2019 via email

@perlindgren
Copy link
Collaborator

perlindgren commented Mar 7, 2019 via email

@hannobraun
Copy link
Author

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, 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, 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!

@perlindgren
Copy link
Collaborator

perlindgren commented Mar 8, 2019 via email

@hannobraun
Copy link
Author

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).

  1. 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.

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 bare-metal), move the logic that is used to protect access to Peripherals into that type, and add additional constructors to Peripherals that consume World and return Self.

Then RTFM could create the world and pass ownership of it to init, where it could be used to initialize a PAC or HAL. And anyone not using RTFM could keep using Peripherals::take as before, except that Peripherals::take could be refactored to use the new world type internally.

Just a thought, not sure this captures all the details that would be required for an implementation.

bors bot added a commit that referenced this issue Sep 15, 2019
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>
bors bot added a commit that referenced this issue Sep 15, 2019
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants