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

ClockManager "partial move" with RealTimeClock #724

Open
jnthbdn opened this issue Dec 2, 2023 · 12 comments
Open

ClockManager "partial move" with RealTimeClock #724

jnthbdn opened this issue Dec 2, 2023 · 12 comments

Comments

@jnthbdn
Copy link
Contributor

jnthbdn commented Dec 2, 2023

Hello,

I'm using the main version to put the rp2040 in deepsleep. All in all it works great, but I have a small problem.

When I create a RealTimeClock object I have to give it ownership over the RtcClock in my ClockManager. From then on, I can no longer pass my ClockManager as a parameter (&mut) to any function... Because the borrow checker tells me that the object is partial move.

How can I get round this problem?

I used the example https://github.com/rp-rs/rp-hal/blob/cfd6c128a1688186f419e3bd60ce469ddeb7fb47/rp2040-hal/examples/rtc_irq_example.rs.

Here is a simplified code example.

let mut pac = pac::Peripherals::take().unwrap();
let mut clocks = hal::clocks::ClocksManager::new(pac.CLOCKS);
// [...]
let rtc = configure_rtc(pac.RTC, clocks.rtc_clock, &mut pac.RESETS, &mut core); // Partial move occur here
// [...]

loop {
    set_next_wake_up(600);
    configure_clock_low_power(&mut clocks, &xosc, XTAL_FREQ_HZ, &mut plls); // ERROR HERE
    cortex_m::asm::wfi();
    configure_clock(&mut clocks, &xosc, XTAL_FREQ_HZ, &mut plls, &mut pac.RESETS); // ERROR HERE
}

// In configure_rtc
let mut rtc = hal::rtc::RealTimeClock::new(
    pac_rtc,
    rtc_clock,
    reset,
    rtc::DateTime {
        year: 0,
        month: 1,
        day: 1,
        day_of_week: rtc::DayOfWeek::Monday,
        hour: 0,
        minute: 0,
        second: 0,
    },
)
.unwrap();
// [...]

I did try to make my own ClockManager, with Option<T> for each field, but I find it a bit cumbersome and there are a lot of macros on the original!

Can you help me ? Maybe there is something I don't understand with Rust ^^

Thanks !

@jnthbdn jnthbdn changed the title ClockManager "partial move" ClockManager "partial move" with RealTimeClock Dec 2, 2023
@jnthbdn
Copy link
Contributor Author

jnthbdn commented Dec 2, 2023

I just found something very strange... Why does the new function of RealTimeClock need the ownership of rtc_clock, since in the code we're just reading the frequency (where a simple reference would be sufficient)?

let freq = clock.freq().to_Hz() - 1;

@ithinuel
Copy link
Member

ithinuel commented Dec 3, 2023

Hi,

Why does the new function of RealTimeClock need the ownership of rtc_clock, since in the code we're just reading the frequency (where a simple reference would be sufficient)?

In some cases this is to (try to) prevent other parts of the firmware from changing the frequency/settings of the clock source.

The clock related code is admittedly not yet very polished. On the other hand, this is a fairly complex topic to address as not all solutions are really practical for the common usecase the hal is used in (eg RTIC application, low power/deepsleep, some level of DFS) while still meeting the requirement from the hardware interms of clock state, stability, and dependencies.

We welcome contributions that could help resolve (at least some of) the issues.
We currently have a few "in flight" PR up for review and testing:

@jnthbdn
Copy link
Contributor Author

jnthbdn commented Dec 3, 2023

Hi,

In some cases this is to (try to) prevent other parts of the firmware from changing the frequency/settings of the clock source.

Indeed, it makes sense, but in this case it invalidates the ClockManager, and IMO that's not a good idea.

I therefore see three solutions to this problem:

  • define all the ClockManager fields as options, which is a breaking change.
  • duplicate the ClockManager and implement the previous point
  • modify the RealTimeClock function so that it takes a reference instead of the property.

The last solution is the simplest, and the hal seems to work this way (for example, the setup_pll_blocking function only takes a reference from the ClockManager).

@jnthbdn
Copy link
Contributor Author

jnthbdn commented Dec 3, 2023

I just found there is a similar idea in UsbBus...

_pll: UsbClock,

Once again it invalidate your ClockManager, and you lost (forever) the usb_clock field.

@ithinuel
Copy link
Member

ithinuel commented Dec 3, 2023

The typical flow is to:

  1. Setup the clock
  2. Instantiate the peripherals (rtc, usb etc)
  3. don't change the clock

Taking ownership of the clock implicitly freezes the clock's settings.
An option could be that we make all peripherals take ownership of their clock source. Shared clocks, like sys_peri, could be backed by an atomic reference counter behind the scene.

If a user desires the change the clock's config, they'd need to recover those clocks (by disabling or freeing the peripherals), do what ever they want with the clock, and then reenabling or reconstructing the peripheral(s).

We could apply such pattern over all the drivers, but implementing this would be an endeavour going beyond my current time capacity.

@jnthbdn
Copy link
Contributor Author

jnthbdn commented Dec 3, 2023

I don't totally agree with the idea of blocking clocks when a device is instantiated. In my mind, clocks remain independent of devices, and changing a clock doesn't necessarily impact the operation of the device. It might even be possible to disable (or reduce the speed of) a device if necessary (if only temporarily, as I think that's how it's presented in the datasheet).
For example, if you want to switch to DOMANT mode, the datasheet specifies that you have to switch off the PLLs manually (except pll_sys, if I've understood correctly). So the PLL_USB must be stopped, but that doesn't mean you have to destroy the UsbBus instantiation, just stop its clock, and restart it when you wake up.

@jannic
Copy link
Member

jannic commented Dec 3, 2023

From my point of view, the API should prevent easy mistakes like forgetting to initialize a clock that's needed for a peripheral, or changing a clock that's in use by a peripheral by accident, eg. due to some copy & paste mistake. That goal is covered by the current API quite well.

But then, the API should not prevent more advanced use cases where the clocks are intentionally changed while being in use. Ideally, such a facility should still only allow changes that are supported by the hardware. But if it is too difficult to distinguish between valid and invalid changes programmatically, adding more "dangerous" methods would be fine as well, as long as they are clearly recognizable as such. Eg. by giving them names like *_unchecked() and describing the limitations in the docs.

Currently, the only way to get around the API limitations is using unsafe code. That's fine for really unconventional use cases, but has the big disadvantage that it opens the door for accidentally causing undefined behavior. Some middle ground that is more lenient configuration-wise, but still provides the full soundness guarantees of safe rust, is still missing.

@ithinuel
Copy link
Member

ithinuel commented Dec 3, 2023

but that doesn't mean you have to destroy the UsbBus instantiation, just stop its clock, and restart it when you wake up.

Well, USB is a good example for that matter. If you stop the USB clock, you effectively break the communication with any connected host.

It might even be possible to disable (or reduce the speed of) a device if necessary

You can indeed stop or reduce the speed of a clock according to the datasheet.
But for example, all the UART, SPI and I2C driver are configured in terms of frequency/baudrate and the implementation guaranties that this is what the bus will run at.
But all those speeds are in reality relative to the clock source of that peripheral.

Changing the frequency of that clock, breaks an invariant and the driver's guaranties are rendered obsolete without any way to let the developer know about it.

What I'd like to achieve is not necessarily a perfect API but at least something that prevents easy foot guns such as stopping or changing the frequency of a clock while a peripheral is in operation (eg changing the baudrate in the middle of a transmit/receive).

@jnthbdn
Copy link
Contributor Author

jnthbdn commented Dec 3, 2023

Well, USB is a good example for that matter. If you stop the USB clock, you effectively break the communication with any connected host.

Exactly! You deactivate the clocks, so the USB is stopped, and when you restart your clock the USB resumes. It seems to me that this is the very use case for clock enable in the clock tree (2.15 datasheet).
(It's the clocks that drive energy consumption, not necessarily the peripherals.)

I quite agree with @jannic that this seems a good compromise. There should be a system in the ClockManager that checks whether a clock is "busy". But under no circumstances should a clock be "owned" by another instance. So we could imagine having *_unchecked functions.

@ithinuel
Copy link
Member

ithinuel commented Dec 3, 2023

Exactly! You deactivate the clocks, so the USB is stopped, and when you restart your clock the USB resumes. It seems to me that this is the very use case for clock enable in the clock tree (2.15 datasheet).

It doesn't just resume. The usb protocol is broken. The host has lost communication with the device. A whole renumeration process needs to be resumed. Any "class level" protocol has to start again from scratch.
Some class are to some extents resilient to that (HID, CDC) but thing may go terribly wrong with things like MSD.

There should be a system in the ClockManager that checks whether a clock is "busy".

Indeed, what I refered to earlier is one solution to that.

But under no circumstances should a clock be "owned" by another instance.

I don't agree with that statement though. This seems to be a strong statement without strong reasons beyond we don't currently have a nice API.
Note that I'm NOT saying that peripherals should own their reference clock. Only that under no circumstances is too strong a requirement.

I quite agree with @jannic that this seems a good compromise. [...] So we could imagine having *_unchecked functions.

Indeed, that could be a satisfying "temporary" solution until something better is proposed.
Although I like when things are consistent, not all peripherals may need the same sort of API.

@jnthbdn
Copy link
Contributor Author

jnthbdn commented Dec 4, 2023

Only that under no circumstances is too strong a requirement.

Sorry, English isn't my mother tongue, and in translation, certain terms can become a bit... excessive ^^.

I honestly don't know how to create such a mechanism. But to avoid any breaking changes, I suggest modifying my PR and adding the free function to UsbBus and RealTimeClock.

@jnthbdn
Copy link
Contributor Author

jnthbdn commented Dec 12, 2023

Partial fix with #725

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

No branches or pull requests

3 participants