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

Change baud rate to NonZeroU32 #4255

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

Ioan-Cristian
Copy link
Contributor

@Ioan-Cristian Ioan-Cristian commented Nov 26, 2024

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:

# Before the change

## Debug
   text	   data	    bss	    dec	    hex	filename
 126952	     32	  13500	 140484	  224c4	../../target/thumbv6m-none-eabi/debug/raspberry_pi_pico.elf

## Release
   text	   data	    bss	    dec	    hex	filename
  98272	     32	  13500	 111804	  1b4bc	../../target/thumbv6m-none-eabi/release/raspberry_pi_pico.elf


# After the change

## Debug
   text	   data	    bss	    dec	    hex	filename
 126948	     32	  13500	 140480	  224c0	/home/cristian-dev/Projets/tock/target/thumbv6m-none-eabi/debug/raspberry_pi_pico

## Release
   text	   data	    bss	    dec	    hex	filename
  98272	     32	  13500	 111804	  1b4bc	/home/cristian-dev/Projets/tock/target/thumbv6m-none-eabi/release/raspberry_pi_pico

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

  • No updates required.

Formatting

  • Ran make prepush.

@github-actions github-actions bot added sam4l Change pertains to the SAM4L MCU. nrf Change pertains to the nRF5x family of MCUs. HIL This affects a Tock HIL interface. WG-OpenTitan In the purview of the OpenTitan working group. stm32 Change pertains to the stm32 family of MCUSs component labels Nov 26, 2024
@Ioan-Cristian
Copy link
Contributor Author

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
Copy link

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?

Copy link
Contributor Author

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?

@Ioan-Cristian Ioan-Cristian changed the title Make baud rate to never be nullable Change baud rate to NonZeroU32 Nov 26, 2024
@Ioan-Cristian
Copy link
Contributor Author

I tested the PR against c_hello and blink applications loaded on Raspberry Pi Pico.

@Ioan-Cristian Ioan-Cristian marked this pull request as ready for review November 26, 2024 18:01
@bradjc
Copy link
Contributor

bradjc commented Dec 6, 2024

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 / operator is used, not in the value type. And, requiring new handling (such as calling .unwrap()) might make the potential for panics worse.

@Ioan-Cristian
Copy link
Contributor Author

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.

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 u32?

Also, the simplicity of just using a standard type is nice and simpler to manage.

Fatal mistake not including NonZero* types as primitive types. Another fatal mistake is defining division by u* instead of NonZero*.

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 / operator is used, not in the value type.

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 NonZeroU32 and used it in several places, including division? This was intentional, to keep the code as close as possible to the original one, otherwise I get comments such as it would be easier to review and approve the PR if it mimicked the original code.

And, requiring new handling (such as calling .unwrap()) might make the potential for panics worse.

Does it? All but two added unwrap()s are in boards crates, one in capsules and one in chips. And all of them are trivial unwrap()s: NonZeroU32::new(115_200).unwrap(). I could in theory use new_unchecked(), but most Rust developers tend to prefer the safe way even if it is obvious the "unsafe" counterpart is in reality safe.

Again, to keep code as similar as possible to the previous version, I did not extract any baud rate as a const value, which would mean that all those "possible" unwrap()s will be catched by the compiler.

I do agree that using NonZeroU32 might seem tedious, especially that the console capsule does not expose a set_baud_rate command to user space. However, using the type system at its entire capacity helps in producing reliable code that's easier to test.

@lschuermann
Copy link
Member

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. NonZeroU32, though not as convenient as other integer types, is a fairly standard way of expressing these constraints, can often produce more efficient code & memory representations, and can only make this API more expressive & safe, not less.

lschuermann
lschuermann previously approved these changes Dec 11, 2024
Copy link
Member

@lschuermann lschuermann left a 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
Copy link
Member

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.

Copy link
Contributor Author

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()?

Copy link
Member

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 [...]".

Copy link
Contributor Author

@Ioan-Cristian Ioan-Cristian Dec 12, 2024

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?

Copy link
Member

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.

Copy link
Contributor Author

@Ioan-Cristian Ioan-Cristian Dec 17, 2024

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?

Copy link
Member

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!

@bradjc
Copy link
Contributor

bradjc commented Dec 20, 2024

We discussed this a bit, and the summary comes out to:

  • Reasons in favor of this change
    • It makes a failure/panic case (divide by zero) impossible and the compiler can omit that panic.
    • It encourages using Rust features to eliminate panic sources.
  • Reasons opposed to the change
    • Adds churn in the codebase
    • This fixes a rust problem, not a Tock problem.

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.

@lschuermann
Copy link
Member

lschuermann commented Dec 30, 2024

@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 checked_div should be fine and will allow this PR be pushed over the finish line quickly.

@Ioan-Cristian
Copy link
Contributor Author

Adds churn in the codebase

I wouldn't call NonZero::new(115_200).unwrap() churn.

This fixes a rust problem, not a Tock problem.

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

I think if this touched one or two files it's probably a net improvement

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.

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.

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 From<NonZeroU32> trait bound.

no one is trying to use a baud rate of 0

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.

I still think it would be a lot simpler to just use a non panicing divide and return on an error.

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.

@Ioan-Cristian
Copy link
Contributor Author

Ioan-Cristian commented Jan 16, 2025

@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 checked_div should be fine and will allow this PR be pushed over the finish line quickly.

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:

  1. Better express their intent to other developers => less need for documentation which gets stale overtime + speeds up code comprehension for newcomers
  2. Better express their intent to the compiler => more optimized code, more errors detected at compile time, less frustation spent on runtime bugs

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component HIL This affects a Tock HIL interface. nrf Change pertains to the nRF5x family of MCUs. sam4l Change pertains to the SAM4L MCU. stm32 Change pertains to the stm32 family of MCUSs WG-OpenTitan In the purview of the OpenTitan working group.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants