-
Notifications
You must be signed in to change notification settings - Fork 203
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
USB methods disable interrupts for too long #397
Comments
Can you elaborate on 'too long'? We tried to do as little as possible in the
Depending on how many priority bits your chip has, could you boop the USB to have less priority than your other CSR?
I'm not sure i follow, can you give me an example? |
Maybe more importantly than the specific timing constraint, I was surprised to find that the entirety of Specifically, I've got about a 16us (ed: at 48MHz) window to respond to a DMA interrupt, and I believe that response usually takes about 3us so I should be able to tolerate about 12us of interrupts being disabled. Admittedly I haven't put much effort in to profiling - an early test showed a case where the DMA interrupt wasn't handled when the processor was in the USB ISR, so I went digging to see how that could happen...
Yep, I'm pretty sure that is the current arrangement, but perhaps got something wrong. Will investigate further.
Sorry, what I wrote was a bit unclear, and thinking about it more I may be misunderstanding the point of the mutex. Will try to catch you on matrix to discuss, but for completeness I was imagining adding an AtomicBool field to
|
Ooooh I have an idea. Instead of turning off interrupts globally, what if we use mask and That way we can be happily preempted by more urgent interrupts while servicing, as long as they dont take too long that we miss a frame. |
Something like this maybe: struct UsbCS {
other_active: bool, // USB_OTHER irq
trcpt0_active: bool, // USB_TRCPT0 irq
trcpt1_active: bool, // USB_TRCPT1 irq
}
impl core::ops::Drop for UsbCS {
fn drop(&mut self) {
use cortex_m::peripheral::NVIC;
use crate::target_device::interrupt;
unsafe {
if self.other_active {
NVIC::unmask(interrupt::USB_OTHER);
}
if self.trcpt0_active {
NVIC::unmask(interrupt::USB_TRCPT0);
}
if self.trcpt1_active {
NVIC::unmask(interrupt::USB_TRCPT1);
}
}
}
}
impl UsbCS {
fn mutex_cs(&self) -> cortex_m::interrupt::CriticalSection {
unsafe { cortex_m::interrupt::CriticalSection::new() }
}
}
/// Disables IRQ lines related to USB, returning a token that resets the
/// masking state when dropped.
fn usb_critical_section() -> UsbCS {
use cortex_m::peripheral::NVIC;
use crate::target_device::interrupt;
let cs = UsbCS {
other_active: NVIC::is_enabled(interrupt::USB_OTHER),
trcpt0_active: NVIC::is_enabled(interrupt::USB_TRCPT0),
trcpt1_active: NVIC::is_enabled(interrupt::USB_TRCPT1),
};
NVIC::mask(interrupt::USB_OTHER);
NVIC::mask(interrupt::USB_TRCPT0);
NVIC::mask(interrupt::USB_TRCPT1);
cs
} |
Is this the reason we're currently disabling interrupts? If so, it probably hasn't been working well in uses like this #[interrupt]
fn USB() {
usb_bus.poll(&mut [a_usb_device]);
// this is a window for other interrupts
a_usb_device.do_some_reads_and_writes(); // likely contains windows for other interrupts
} I guess we'll want a method along the lines of #[interrupt]
fn USB() {
usb_bus.usb_interrupt_free(|cs| {
// Would poll(), read(), write(), etc need a reference to cs, the UsbCS?
usb_bus.poll(&mut [a_usb_device]);
a_usb_device.do_some_reads_and_writes();
});
} |
I think it was just to prevent us being interrupted by a different USB interrupt while servicing a USB interrupt. |
Thinking about this more - I don't know if it's really what is needed. Imagine there's a GPIO interrupt that triggers a USB write - we wouldn't want it to stomp on an in-progress USB write... I've just yanked out the Since the methods where we had |
Can you share the diff somewhere? Im curious how you did this |
Sure, ianrrees@b129876 and its parent. It's not pretty :). (edit: updated link) |
This was premature, it definitely needs a mechanism to prevent USB interrupts while already processing one. |
I think there's a fundamental issue here, but feel a bit out of my depth. Imagine that we have an implementation of A secondary question is how to handle an interrupt that fails to get the lock. |
Sorry for my lack of follow-through on this, the above hack is working well enough in the meantime ;).
Can't remember what prompted me to write this, but think it might be incorrect; AFAICT any particular ISR on SAMD21 won't ever interrupt itself. I'm becoming convinced that although the mutex in the UsbBus implementation may be necessary in the current implementation of usb-device, with some hopefully-straightforward changes to usb-device (see rust-embedded-community/usb-device#9) it won't be. Currently, a typical use-case involves multiple structs that refer to the same UsbBus, but if we instead had one struct that owned all the USB stuff, then Rust's ownership guarantees should be sufficient to ensure that only one execution context can access it at a time. There might still be a mutex required to mediate access to the USB stuff, but it would be easier to implement one that only blocks interrupts for a few instructions, for example. |
dirbaio on Matrix suggested that the stm32-usbd doesn't have this particular issue, perhaps it's possible to make UsbBus and/or Inner not by Sync, so that the user either picks their own mutex or only accesses the USB from one particular context. |
@ianrrees, did you settle on a solution here? Does this need a change to the HAL or a change upstream? |
Currently I'm using a branch that removes the mutex/critical section (CS), it works but isn't ideal. Some possible ways we could resolve this, all involve changes to the HAL:
|
for something along the lines of 3, you can check if you're in interrupt context with this cortex-m function and panic if you're in thread context |
I think option 3 is the best at the moment. Should be fairly simple and that way you either access USB methods from different context safely or get maximum performance at the cost of panics if it's used from multiple contexts (which I think wouldn't be too difficult to avoid). Maybe mutexes should be opt-out to make the safer option the default. Another option would be to let the user provide a mutex with the current one as the default. |
@ianrrees is there any update needed to this issue as a result of the recent USB PRs? Or is it the same story |
Recent changes are unrelated, I was thinking about picking this back up though... Any opposition to approach 3 from a few comments back? |
Nope, I think it makes good sense |
I'm working on a SAMD21 based USB device, which does the USB work in the USB ISR, along the lines of the usb_echo example. The USB interrupt is set to a lower priority (higher number) than another interrupt which needs to be serviced fairly quickly, but sometimes that doesn't happen quickly enough. The issue seems to be that
UsbBus
's methods do all their work in interrupt free contexts. For example, these.Might it make sense for
UsbBus
to have an "in use" flag in the mutex rather than the RefCell, so that the interrupts only need to be disabled for setting/checking that flag?The text was updated successfully, but these errors were encountered: