-
Notifications
You must be signed in to change notification settings - Fork 774
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
Takes care of power for nRF USB devices #810
Conversation
As discussed on Matrix, I think this should be handled by the nrf-specific USB driver, instead of a wrapper on top of the UsbDevice. Power management is a hardware-specific thing. |
I don't think there's any way to do that that works with both bare metal and nrf-softdevice. nrf-softdevice hijacks the POWER peripheral and gives you SoC events with the usb status changes. I think it may be problematic in other scenarios for the USB driver to require the POWER peripheral as well. |
It's still a driver concern though. Maybe we can make the driver "customizable" so it works on all scenarios, perhaps making it generic over a "PowerManager" trait? It'd have
|
Right, so different constructors for each trait then… it would be nice to have the interrupt taken care of. My only outstanding concern though is overloading the suspend/resume events. It feels like we should have power enabled/disabled also, even if they are handled the same way as suspend/resume, accepting also that they aren’t supported by every device. Wdyt? |
I'm not sure I agree it's a driver concern.
Honestly, to me it makes the most sense to just leave it up to the app to handle. It's only a couple of lines of code and it gives the app the opportunity to do whatever else it needs to when the events occur. |
Oh, just remembered. Won’t we have to create a PowerPeripheral also… and consuming the POWER_CLOCK interrupt is a concern too in terms of sharing that around… |
Hence it makes sense to handle it in The way I see it, the Driver's job is "take care of the hardware and all its quirks, then make it into something that impls the Device trait which Just Works". The fact that you have to deinit USBD if VBUS goes away is one of such hardware quirks. Forcing higher layers (or worse, user code) to be aware of that is not great.
On the contrary, having "usb cable connected/disconnected" events in the UsbDevice public API would be a great feature IMO! This way you can do everything top of UsbDevice, which will make your code portable across nrf, stm32, etc with zero changes required. nRF detects it through POWER. STM32 doesn't have an equivalent, but most boards (like nucleos) wire VBUS to a GPIO so you can still detect it. STM32 Driver impls could optionally take a GPIO to do that. |
Sharing POWER is a non-issue.
|
Ok, I’ll give the device approach another look. |
Wdyt about a PowerPeripheral being introduced along with an Instance type? Something to convey the pac type (which doesn’t exist right now)… |
I don't think an Instance trait is warranted, the chip only has one POWER. You could add a singleton to About the PAC owned singleton, just steal it. |
822b4b3
to
ba5f129
Compare
Thanks, I understand your vision much better now. That makes sense to me: we have the STM32 driver take an InputPin and the nrf driver take a PowerManager trait. |
42efc50
to
7cec893
Compare
7cec893
to
ee0e398
Compare
I've now re-implemented the machinery to enable/disable the USBD given the respective power events. However, upon removing power, I'm not seeing the USB become disabled, or at least conveying events in this regard. I'm waking up BUS_WAKER and EP0_WAKER within the power interrupt, and I'm seeing that the power event is handled. For example, with the USB initially connected all appears well as I'm guarding against a double-enablement in the USB library. However, when I disconnect the USB:
... I'd expect to see trace messages reporting that USB has been suspended. But I don't... Clearly, there's something about the way things are supposed to work that I don't understand. |
0dee050
to
cae640c
Compare
6fa9a82
to
7f9c751
Compare
Tested and appears to work. Some notes:
|
EnabledUsbDevice is a wrapper around the UsbDevice where their enablement is also subject to external events, such as POWER events for nRF. It is introduced generically to support other platforms should they also require external signalling for enablement.
Tested from my production-focused code and also appears to behave well. Applying the API from here felt good. |
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 only looked at the embassy-nrf
and embassy-usb
version since I haven't worked with the stm32 implementation at all.
embassy-usb/src/util.rs
Outdated
Either::Second(enable) => { | ||
if !enable { | ||
self.underlying.disable().await; | ||
while !self.enable_usb_signal.wait().await {} |
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.
There's an inconsistency here depending on whether or not the device is suspended when a disable event occurs. I'd recommend introducing a let mut enabled = false
variable outside the loop, then at the start of the loop do:
if !enabled {
while !self.enable_usb_signal.wait().await {}
enabled = true;
}
Then your select cases just need to update the enabled variable and continue
. That will ensure the usb device always starts resumed.
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
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.
Sorry if I've caused some confusion here by leaving in that first commit. The first commit was per the original approach that packaged a (your?) example and created a util module. That module has gone now and the commit was left there for posterity only.
embassy-nrf/src/usb.rs
Outdated
/// Establish a new device that then uses the POWER peripheral to | ||
/// detect USB power detected/removed events are handled. | ||
#[cfg(not(feature = "_nrf5340-app"))] | ||
pub fn with_power_management( |
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.
If we're incorporating power management into embassy-usb
, then this functionality probably needs to be part of the general new
method. To handle things like the nrf-softdevice
situation, instead of taking the power_irq
directly, it should take a generic type which implements a UsbPowerDetect
trait. That trait can be implemented on a struct which takes an impl Unborrow<Target = POWER>
for users that can make use of the POWER peripheral directly. Other users can implement their own version of the trait.
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 think overloading new
adds complexity to situations where power detection isn't required e.g. STM, or where it is to be manually managed on nRF as per the existing scenario. Having with_power_management
present and the device driver handling the IRQ setup is highly convenient and distinct from a user's perspective.
For the nrf-softdevice
situation, I'd really like to see how we should handle that. My suspicion is that we would end up with a with_power_signal
or something. As I'm personally not using nrf-softdevice
though, I'm reluctant to cater for it right now as I don't have anything set up to test it. Meanwhile, the user can also rely on the existing new
method when programming the softdevice, as per today.
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.
This is the nrf driver's new we're talking about so stm32 doesn't apply. What I'm saying is that with_power_management
should replace the nrf driver's new
method. However, instead of taking the irq directly, it should take a trait to cover the following cases:
- The
POWER
peripheral and/or irq need to be shared with other parts of the application nrf_softdevice
users who don't have access to thePOWER
peripheral
My understanding of what @Dirbaio was looking for is to incorporate the Powered state of the USB device state spec into embassy-usb itself. Once we do that it becomes a requirement of the drivers to be able to provide those state transitions to the library. There may be cases where the driver never generates those transitions (e.g. an stm32 device with no IO pin wired to detect Vbus) but the assumption should be that the capability is there.
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'm still not convinced about the naming/organization of the driver constructor beyond what I've got. It feels ok to me. Perhaps we can have another PR improving on that to what you and/or Dario are thinking about? Thanks.
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.
Actually, I saw that I had a bug re. the initial vbus state, so I've fixed that and also provided a means to set power state per softdevice usage .Please see commit 81796d2
embassy-usb/src/lib.rs
Outdated
@@ -114,6 +114,7 @@ struct Inner<'d, D: Driver<'d>> { | |||
|
|||
device_state: UsbDeviceState, | |||
suspended: bool, | |||
power_available: bool, |
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.
This is a bit bike shedding, but the USB spec refers to this state as powered
which might be a better name than power_available
. In fact, rather than a separate bool
, I'd add a Unpowered
state to the UsbDeviceState
enum. We will also need a fn powered(&self, powered: bool)
method on the DeviceStateHandler
.
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 have a separate bool because it represents the fact that power is signalled independently of UsbDeviceState
.
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 not possible for a USB device to be unpowered and suspended simultaneously. Once Vbus is interrupted the device needs to be reset at the very least, and usually you'll want to disable the device until Vbus is restored. Chapter 9 of the USB 2.0 spec has a nice diagram of the device state machine that might be helpful.
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.
Again, the signal comes from a separate source, hence my modeling it this way. Apologies if I'm misunderstanding something though. I'm not very familiar with the internals of USB.
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.
No worries, it's a complicated spec. I guess my main point is that we want the embassy-usb
library to model an "ideal" USB device as closely as possible. The signals do come from separate sources, but that's an implementation detail. What we want to model is a device state that goes Unpowered -> Default -> Addressed -> Configured (<-> Suspended). In practice, that means the power removed event trumps everything else. This stuff is tricky to get right.
Here's a summary of how I think it should work:
Unpowered
should be anotherUsbDeviceState
variantrun_until_suspend()
should only return when theUsbDeviceState
isSuspended
. Other state changes should be handled automatically without returning.wait_for_resume()
should return as soon as theUsbDeviceState
is anything other thanSuspended
- A power removed event should immediately set the device state to
Unpowered
and it should remain there until a power detected event is received
The reason for the run_until_suspend()
and wait_for_resume()
behaviors is because the only reason those methods are split out from run()
is because the run()
future is not safe to drop yet we need to be able to call remote_wakeup()
(which requires an exclusive reference to the UsbDevice
) when the device is suspended. Therefore the behavior of those methods should be strictly tied to the Suspended
device state.
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've now implemented your recommendations regarding the new Unpowered
state. Thanks so much - it looks and behaves great! Please see commit 8d71a35.
Thanks for the feedback! |
Replaces the sub-state of representing being being available. Power states also now set� enable/disable directly too, which simplifies code.
Also, correctly sets the initial power management state when using power management
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.
This looks much better to me. One area that I think still needs some work is the difference between Disabled
and Unpowered
. I think we need both states to allow applications to disable the USB device without dropping the whole thing even if USB is still connected.
I view the Disabled
state as something the app manually enters when it wants to disable the USB device regardless of the state of the bus. The Disabled
state is exited by calling one of the run*()
methods. The Unpowered
state is simply part of the normal lifecycle of the device, handled automatically by the driver with no input from the application.
Therefore, I think the device should still start in the Disabled
state rather than Unpowered
. run_until_suspend()
should still check for the Disabled
state and call bus.enable()
if it is set, though it should now transition to the Unpowered
state rather than Default
.
We probably also need some documentation in driver.rs that poll
should return PowerDetected
on the first call after enable
is called if power is present. And maybe that after a PowerRemoved
event there should be no other events until a PowerDetected
event has been returned.
Finally, since disable()
is something called manually by the app, I don't think it needs to call the DeviceStateHandler::enabled()
callback. So we could rename that callback to powered()
to allow the app to track the powered state of the bus.
Let me know what you think of these changes. I think the embassy-usb
side of things starting to look pretty solid. I do think some more work is still needed on the embassy-nrf
side to handle the POWER
peripheral issues I've mentioned before. Perhaps you could look into another constructor that would take a Signal
to provide the power state and how that could integrate with the poll
function?
if let Some(h) = &self.inner.handler { | ||
h.enabled(true); | ||
} | ||
} |
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.
Keep this, but transition to Unpowered
instead of Default
@@ -376,6 +370,24 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { | |||
h.suspended(true); | |||
} | |||
} | |||
Event::PowerDetected => { | |||
trace!("usb: power detected"); | |||
self.bus.enable().await; |
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 driver should do this itself inside the poll
function
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.
My understanding is that this is no longer an issue given the agreement on Matrix that we'll use my approach.
} | ||
Event::PowerRemoved => { | ||
trace!("usb: power removed"); | ||
self.bus.disable().await; |
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.
Same as above
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.
My understanding is that this is no longer an issue given the agreement on Matrix that we'll use my approach.
A driver can also be enabled/disabled from the outside, no?
The nRF documentation does state that we should not enable the USB until the Reference: "Enable USBD only after VBUS has been detected"
Right now, the
Thanks again for the feedback. Regarding the signal, just wondering if you noticed the new |
Hey @alexmoon - I've now added commit 8785fbc which introduces a trait in the spirit of what you had - albeit with slightly different trait methods. I also used The change also removes a signal I had, resulting in a little more simplicity. I also introduced a Your fix for where we weren't waiting for the power to become available is also incorporated. |
Eliminated a signal by using a simpler trait method that returns whether VBus power is available. Also includes a UsbSupply that can be signalled for use with the nRF softdevice. Includes the requirement for waiting for power to become available.
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.
@alexmoon I believe I've addressed everything. I've tested thoroughly on the nRF52840 with the examples and with my production code. All appears well. Will be merging as at the very least, the API looks good now. |
bors r+ |
Already running a review |
Build succeeded: |
Modifies the usb-serial example to illustrate how to setup USB for situations where the USB power can be detected and removed.
Gaps:
* No support for the nrf-softdevices as yet, although this should be possible via another constructor.The change is tested and appears to work. Some notes:
Old description:
EnabledUsbDevice is a wrapper around theUsbDevice
where its enablement is also subject to external events, such asPOWER
events for nRF. It is introduced generically to support other platforms should they also require external signaling for enablement.