-
Notifications
You must be signed in to change notification settings - Fork 714
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
Change baud rate to NonZeroU32 #4255
base: master
Are you sure you want to change the base?
Conversation
This is a draft pull request until I test it on Raspberry Pi Pico. |
|
||
// Calculate what rate will actually be | ||
let system_frequency = self.pm.get_system_frequency(); | ||
let cd = system_frequency / rate; | ||
// DIVISION: No division by 0 can occur because of the `non_zero_rate` type |
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.
Does it make sense to get rid of all the potential panics here? cd
might result in 0
if system_frequency < non_zero_rate
so the system_frequency / cd
will panic. Perhaps should use checked_div
and return appropriate ErrorCode?
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.
The comment applies to line 1217 only. If you look at the original code, no panic is involved in release mode, at least directly, the CPU may throw a division exception which can lead to a panic depending on the architecture, Panics may be generated in debug mode only. This PR doesn't change this behaviour.
On a more philosophical note: why the division operator is defined between u32
and u32
? Why isn't it defined between u32
and NonZeroU32
? Why doesn't Rust define the type of literal constants 0
, 0b0
, 0x0
as u32
and company, and all other literal constants as NonZeroU32
and company? Should Tock forbid division by u32
?
I tested the PR against |
I went through this PR when I looked at the other nonzerou32 pr, and the benefit of this change is less clear to me. A baud rate of 0 doesn't make much sense, sure, but neither does a baud rate of 1 really, or even something like 115201. I don't think including the non zero requirement in the API expresses much that is semantically meaningful. Also, the simplicity of just using a standard type is nice and simpler to manage. Divide by zero protection is nice, but, in theory any u32 value could become a denominator somewhere; perhaps the check is better placed where the |
Many UART peripherals that I've seen do support baud rates of 1 or 115201 if an appropiate clock divider is used. If you intend to use only some "standard" baud rates in Tock, why don't use an enum for baud rate instead of
Fatal mistake not including NonZero* types as primitive types. Another fatal mistake is defining division by u* instead of NonZero*.
I don't quite understand what you mean by this? The fact that in some parts of the code, I extracted the raw, inner, non-zero value of
Does it? All but two added Again, to keep code as similar as possible to the previous version, I did not extract any baud rate as a I do agree that using |
I agree with @Ioan-Cristian's reasoning here. While there are non-zero baudrate values that would raise eyebrows, a baudrate set to zero can never make sense. Tock probably has other potential divide by zero issues; nonetheless I think that we should take every opportunity to avoid them. |
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.
Changes like these will also make it easier for us to produce panic-free builds of Tock in the future. They force the panic-locations from potentially panicking divisions deep in drivers to bubble up to the board instantiation code, where Rust can either optimize them out by determining that a constant is non-zero, or developers can use checked, non-panicking operations instead.
@@ -417,8 +418,12 @@ unsafe fn setup() -> ( | |||
kernel::debug::assign_gpios(Some(&peripherals.gpio_port[26]), None, None); | |||
|
|||
// Create a shared UART channel for the console and for kernel debug. | |||
let uart_mux = components::console::UartMuxComponent::new(&peripherals.uart0, 115200) | |||
.finalize(components::uart_mux_component_static!()); | |||
// PANIC: 115200 != 0 |
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's good that you document that this cannot lead to a panic, but I think the format of this comment intuitively and superficially coveys the exact opposite to me. Maybe be a bit more verbose? "Cannot panic, as 115_200 != 0". Also, it'd be great if you could move the comment to be on the line directly above the unwrap this pertains to.
Same for all the other occurrences.
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's good that you document that this cannot lead to a panic, but I think the format of this comment intuitively and superficially coveys the exact opposite to me.
I use "PANIC" comments describing why a function/method cannot panic in a particular call. In theory, there should be no function/method/closure that certainly panics, so there is no need for a "NO PANIC" comment.
Also, it'd be great if you could move the comment to be on the line directly above the unwrap this pertains to.
Do you mean putting it between &peripherals.uart0
and NonZeroU32::new(115200).unwrap()
?
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 use "PANIC" comments describing why a function/method cannot panic in a particular call.
Well, I think that at least to me, that's at least slightly confusing.
In theory, there should be no function/method/closure that certainly panics, so there is no need for a "NO PANIC" comment.
Well, panic!()
or unimplemented!()
exists and is certainly very useful for unreachable code paths. 😅
I realize this is slightly pedantic and I don't feel super strongly about this. I think it could just be clearer if we state that a call to a method that usually can panic, like .unwrap()
, will not panic for a particular invocation, and state it like that in the comment. Something like "This does not panic, because [...]".
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 thought using PANIC
as a comment prefix was a good idea to state that the given call does not panic. Similar to SAFETY
for unsafe blocks. This does not panic, because [...]
is a bit too verbose for me. Also, searching for PANIC
with tools such as grep
is more reliable than an entire phrase which may be split over two lines depending on the initial indentation. Does NO PANIC
sound better? Do you have any other suggestions?
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 don't feel so strongly about this as to continue arguing for it. I'm fine with the current version if no one else feel strongly either.
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.
Do you mean putting it between &peripherals.uart0 and NonZeroU32::new(115200).unwrap()?
Since you didn't answer to that question, I have assumed yes as an answer. Force pushed the changes. Is it fine now?
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.
Yes, this looks good. Thanks!
We discussed this a bit, and the summary comes out to:
I think if this touched one or two files it's probably a net improvement, but I'm worried this doesn't really capture what we want (several boards have very specific baud rates they support, not just any non-zero number). Having an associated type where the drivers specify what they can support (including not 0) would, in my opinion, make using the uart in tock easier. But using nonzerou32, while it does eliminate a potential panic, does not really help with Tock as an embedded OS because no one is trying to use a baud rate of 0. I still think it would be a lot simpler to just use a non panicing divide and return on an error. |
@Ioan-Cristian One of the questions we had was the main motivation for this PR. The configure method can already return an error, so it'd be fine to turn a zero frequency into this error. .Was it to get rid of panics? In that case, using a |
I wouldn't call
It's a Rust problem that will never get fixed. Removing division by 0 is a breaking change that affects, unfortunately, many crates. This is why it needs to be solved by Tock itself, even at the expensive of "churn".
66 of the touched files are in boards. I changed most of them through a Vim macro. Counting files in this context is meaningless in my opinion. Furthermore, the remaining 14 modified files are minorly impacted.
I'm also worried that a zeroable type doesn't capture the intent of not wanting to pass a 0 baud rate to an UART peripheral driver. Also, shouldn't PRs be (ideally) small atomic changes so they can be reviewed easily? This PR doesn't pose any problems to a future PR adding the baud rate as an associated type with a
0 is a tricky number. Mathematicians in Antiquity needed several centuries to figure out how to use it. Allowing baud rate to be 0 is analogous to non-nullable C pointers being null, though less harmful.
This supposes the benevolence of the programmer to consistently use non-panicking divides. Otherwise, on reviewers' attention to spot when such divides are not used. Not to mention that errors are generated deeper in the kernel. Also, depending on how far one wants to push the argument of simpleness, they can return to C when trying to write performant, low-level code, because it's much simpler. |
The main motivation is expressivity through the type system. Rust has a powerful type system, which is actually Turing complete, so I try to take advantage of it as much as possible. By taking advantage of the type system, the programmer can:
Not to mention that it looks there is some interest in certifing Tock for critical domains such as automotive, aeronautics, medical etc. which can take advantage of aggressive use of the type system. I do agree that for the current state of the UART, such a minor change seems to be an overkill. By I strongly believe that taking the habit of making such changes will eventually lead to a less buggy, more maintenable, more performant, more efficient code. |
Pull Request Overview
This pull request makes the baud rate non-nullable. It doesn't make sense to configure the UART with a nullable UART. Changing its type to
NonZeroU32
improves type safety and avoids division by 0. It also helps reducing the memory footprint of the kernel in debug mode by 4 bytes:Testing Strategy
This pull request was tested by running
c_hello
application on Raspberry Pi Pico.TODO or Help Wanted
I need help with testing other boards to ensure everything behaves as expected.
Documentation Updated
Formatting
make prepush
.