-
Notifications
You must be signed in to change notification settings - Fork 1.2k
nrf5x: poll isr for dma status if irqs are disabled #396
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
Conversation
I'm not sure if this is the proper approach, but I do think that |
The `tud_poll()` function is used to clear the tinyusb buffers and respond to various events in the main thread. This is important in order to properly utilise the `wfi` instruction. When an interrupt appears, the appropriate handler is called and tinyusb polls it in a "top half" function. It then adds events to a queue which will be handled in a "bottom half" function in the main thread. If an interrupt comes in between running the "bottom half" function and issuing a "wfi", then there will be stale data in the queue and the host will be left with potentially unanswered data. In order to avoid this scenario, the following sequence of events needs to happen: 1. Disable interrupts 2. Flush the queue by calling `tud_task()` 3. Issue WFI 4. Enable interrupts If an interrupt comes in between (2) and (3), the WFI will return immediately even though interrupts are disabled. Because of this, `tud_poll()` needs to be able to run without interrupts. Modify the nrf52 code so that it polls the ISR if interrupts are not enabled. This prevents a deadlock waiting for DMA to finish, ensuring this can be run with interrupts disabled. Signed-off-by: Sean Cross <[email protected]>
hmm, this is very neat. I didn't know that it could be an issue with DMA. Nordic insist software driver must make sure there is only one DMA enabled at a time, else something bad out of specs happens. I did it by using tud task as a way to deferred ISR call, waiting for DMA complete to make sure there is no race condition between endpoints. This is rather annoying, give me a day or two, I will try to mimic the WFI on my repo example (since it is simpler and easier to troubleshoot that cpy) to have a full understanding of the issue (is currently busy with bootloader work). |
By the way, with this patch, coupled with adafruit/circuitpython#2868, this board is drawing about 10 uA during sleep. It's able to be reliably plugged in and unplugged from USB, it can log to internal flash, and it can generally operate as you'd expect. I think that something like this will be very important when operating in low power modes. |
while ( _dcd.dma_running ) { } | ||
while ( _dcd.dma_running ) { | ||
// Manually poll the handler if interrupts are disabled | ||
if ( __get_PRIMASK() ) |
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.
Do you want to check the USB IRQ status here too? Otherwise other interrupts will cause dcd_int_handler to run.
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 states that it will only poll the USB ISR when all interrupts are disabled. __disable_irq()
works by setting PRIMASK
to 1
. This simply polls the ISR in a tight loop, regardless of whether the USB IRQ is enabled.
It doesn't care whether other interrupts are enabled, and it doesn't care if there's a pending interurpt or not. It will still poll regardless.
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.
hmm, there shouldn't be issue with this block waiting, I add a bit of logging and don't see it ever wait or defer the dma
// helper to start DMA
static void edpt_dma_start(volatile uint32_t* reg_startep)
{
// Only one dma can be active
if ( _dcd.dma_running )
{
if (SCB->ICSR & SCB_ICSR_VECTACTIVE_Msk)
{
TU_LOG2("dma deferred\r\n");
// If called within ISR, use usbd task to defer later
usbd_defer_func( (osal_task_func_t) edpt_dma_start, (void*) reg_startep, true );
return;
}
else
{
TU_LOG2("dma waiting\r\n");
// Otherwise simply block wait
while ( _dcd.dma_running ) { }
}
}
_dcd.dma_running = true;
TU_LOG2("dma started\r\n");
(*reg_startep) = 1;
__ISB(); __DSB();
}
// DMA is complete
static void edpt_dma_end(void)
{
TU_ASSERT(_dcd.dma_running, );
_dcd.dma_running = false;
TU_LOG2("dma ended\r\n");
}
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 problem is that _dcd.dma_running
is set by the interrupt handler, and if you disable interrupts then _dcd.dma_running
effectively becomes 1
, meaning it will never exit.
What is the status of this patch? Have you had a chance to replicate the issue, or to come up with another approach to preventing deadlocks in |
@xobs hi, I was buy with nrf bootloader work and haven’t tried it out. I will do as soon as I could and let you know. |
@xobs I will start to work on this issue tomorrow, I am lacking behind this. I checked your issue adafruit/circuitpython#2868 but still didn't see steps on how to reproduce it. Also where is the |
The steps (in Circuit Python) are:
I'm not sure what You ought to be able to replicate this by examining the
If |
Ah I see, I could reproduce the issue pasting these to
hmm, it is mentioned in this PR description.
That is lots of steps to do, I am not familiar with gdb, I use segger ozone for debugging, though it seems to have some issue running current cpython (always hit hardfault). I try to find a way to troubleshoot it. UPDATE: it seems like the enumeration is still complete, the |
Yes, it will finish enumerating when the board wakes up. Provided it hasn't timed out by then. You also might not be able to issue "Ctrl-C", but I can't recall if that was one of the symptoms. I'm not familiar with Segger Ozone, but I do know that J-Link GDB Server works. The issue I kept running into there was that their tools seem to assume you want to start at
I'm not sure how to tell Ozone to set |
I added the timestamp before and after the WFI (while the code.py does sleep(50) ).
the log is printed via SEGGER RTT therefore it is less interfere with the system. nRF does many full sleep cycles while enumerating. Possibly due to the
It seems to be the racing between usb isr -> event queue -> tud_task() handling and WFI. More investigating ....
|
Correct. Note that The patch here just makes it so that |
Yeah, though I am not sure if it is needed to call |
This is obsoleted by the inclusion of |
Yeah, you are right on this, Though we probably only only need to check DMA Start/End instead of invoking the dcd_int_handler(). I am looking at the nrf5x specs just now. This approach should be enough to get out of hanging situation. |
closed in prefer of #416, thank you very much for bringing the issue up |
The
tud_poll()
function is used to clear the tinyusb buffers andrespond to various events in the main thread.
This is important in order to properly utilise the
wfi
instruction.When an interrupt appears, the appropriate handler is called and
tinyusb polls it in a "top half" function. It then adds events to
a queue which will be handled in a "bottom half" function in the
main thread.
If an interrupt comes in between running the "bottom half" function
and issuing a "wfi", then there will be stale data in the queue
and the host will be left with potentially unanswered data.
In order to avoid this scenario, the following sequence of events
needs to happen:
tud_task()
If an interrupt comes in between (2) and (3), the WFI will return
immediately even though interrupts are disabled.
Because of this,
tud_task()
needs to be able to run withoutinterrupts.
Modify the nrf52 code so that it polls the ISR if interrupts are not
enabled. This prevents a deadlock waiting for DMA to finish, ensuring
this can be run with interrupts disabled.