-
Notifications
You must be signed in to change notification settings - Fork 174
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
IRQ double-fires, but only when handled by Hubris #536
Comments
Also of note: if I remove the |
Okay, finally tracked this down! TL;DR: Hubris needs a way to disable hardware-triggered interrupts from within Finding the source of the double-triggerThe smoking gun turned out to be the Now here's the trick: since this was a hardware triggered interrupt from
The former happens regardless of the source of the interrupt. However, the second one can only happen from within task code, which is only switched to outside of the originating interrupt. This is important: it's critical to clear the timer's IRQ flag inside of the interrupt, or the timer's interrupt immediately tries to be serviced again. That's where the * above comes in: the initial interrupt was never "pending" in the first place since it was happily being serviced in WorkaroundIronically #518 provided a rather clean workaround: fn isr_enter() {
let timer = unsafe { &*device::TIM1::ptr() };
timer.sr.write(|w| w.cc4if().clear());
} Since Of course this happens every time an interrupt fires, but at least for my use case that's acceptable (it's the only interrupt and everything else is coordinated in hardware). Performance implicationsThis also explains the double-firing I was seeing when discussing #517. It turns out that the 300+ cycle Permanent solution?While the workaround above is a surprisingly easy solution in my use case, I don't have a sense of what the "right" solution might be from a security/safety standpoint. The profiling code is strictly designed for debugging; calling into user code directly from the kernel is not only not safe but also allows entering user code in a privileged context, which obviously can wreck all sorts of havoc. |
Hi! I'm currently in the field bringing up some hardware, but, wanted to leave some thoughts with the available part of my brain. This is a broader problem with peripherals that generate level-triggered internal interrupt signals. The way the ARM NVIC works, peripherals don't get an explicit interrupt acknowledge signal, so every interrupt source effectively has a different way of acknowledging it. IIRC the NVIC does not set the Pending bit in response to an IRQ while that IRQ's handler is active, but will set it as soon as the handler is not active if the IRQ condition has not been cleared. Because we disable the interrupt in it (default) handler, the Pending state will not immediately percolate into Active state until we re-enable the interrupt. I've thought about three alternative approaches to this -- there may be others.
|
Thanks! Again, absolutely zero hurry from my end. Congrats on the hardware delivery! :D
Yup, that exactly jives with what I'm seeing. The interrupt would immediately try to trigger again, but since Hubris sets all IRQs at a lower priority than
All these sound reasonable approaches, and all would work for my use case, though it always makes me itchy when the kernel calls into "arbitrary user code", especially in a privileged context like an ISR. Could there be some way to define how to acknowledge the additional peripheral IRQ from |
Regarding #[interrupt]
unsafe fn TIM_CC() {
// Clear the hardware timer directly.
let timer = &*device::TIM1::ptr();
timer.sr.write(|w| w.cc4if().clear());
// Call into Hubris' default IRQ handler and let it take care of the rest.
kern::arch::DefaultHandler();
} While this is inherently |
...huh. I didn't realize
I have yet to see a portable ISR. :-) I don't think this hurts your portability in the least -- if you wanted to make the jump to (say) a RISC-V core, or an ARMv7-A core, you'd be dealing with different timers and a different interrupt handler model regardless. |
Hah, that's... a really good point. With that in mind, seems like calling The only remaining quirk is that it requires these All that is to say calling into |
Hey all! I'm seeing some odd behavior when servicing interrupts in Hubris: I'm seeing interrupts triggering twice, but only when handled through hubris's
DefaultHandler
mechanism. I know this smacks of the usual "You just returned too fast and forgot to allow the IRQ acknowledge to go through the bus", but hear me out 😅Custom ISR: single-firing
For a baseline test, I override the
TIM_CC
interrupt (technicallyTIM1_CC
, but the SVD/PAC seems to have a typo) and trigger at the very center of the PWM signal. When in the interrupt, I set a GPIO pin,nop
for a bit, acknowledge the pending interrupt in the timer, ensure it's propagated, then drop the GPIO pin.Cyan: PWM signal
Blue: GPIO toggling inside ISR
Seems to work as expected.
Hubris
notification
: double-firingNow if I take the exact same code and put it in an Idolatry
handle_notificaiton
, I'm seeing the interrupt fire a second time between the end of thesyscall_entry
and when thesys_irq_control
call returns.Cyan: PWM signal
Blue: GPIO toggling inside ISR
Magenta:
event_isr_enter
from the new kernel profiling code from last week (enter/exit onDefaultHandler
)Yellow:
event_syscall_enter
from the new kernel profiling code (enter/exit onsyscall_entry
)A bit busy of an image, sorry. Here's the sequence of events:
DefaultHandler
(magenta)handle_notification
, and a GPIO pin is toggled high (blue)sys_irq_control(TIM1_CC_MASK, true)
to handle theirq_control
sycall (yellow)sys_irq_control
callsys_irq_control
call returns, the GPIO pin is lowered, and the original Hubris notification call tohandle_notification
is complete.handle_notification
sys_irq_control
to re-enable the notification handler. Note that this does not cause a third interrupt to fire.sys_recv
being called again within the Idolatry server'shandle_n
.Thoughts
I'm running this in an Idolatry server and haven't gotten a chance to test it out via raw syscalls, but I can't see anything special within
dispatch_n
that would make me think the Idolatry framework would be the cause. I can also confirm that the second interrupt'sIPSR
is the same IRQ number as the first (26).I also tried poking the corresponding
NVIC[ICPR]
to manually acknowledge the IRQ at the NVIC level but that caused a memory fault sincehandle_notification
is in a task and thus unprivileged code (A Good Thing:tm:).I double-checked that I acknowledged the timer's ISR bit by attempting to panic if it was still set just prior to re-enabling the notification:
No panic :/
Both the syscall and the second interrupt occur before
sys_irq_control
has returned to the original task's code, so not sure what else I could/should be doing to prevent it.AFAICT the
#[interrupt]
proc macro fromcortex_m_rt
doesn't do anything fancy aside from defining an$IRQ_trampoline
Not sure what the difference is between the first and second, but when the second iteration of
handle_notification
callssys_irq_control
the interrupt is nowhere to be found after the syscall.There's still a good chance this is a case of PEBKAC, but considering that the same code block in a bare IRQ handler does not fire twice I'm at a loss as to why the Hubris notification would be coming through twice. Any ideas what may be happening here?
The text was updated successfully, but these errors were encountered: