-
-
Notifications
You must be signed in to change notification settings - Fork 8.2k
rp2: fix usb endpoint handling #8040
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
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.
Now that there's a tud_cdc_rx_cb
function, the tud_cdc_rx_wanted_cb
can be removed and its functionality moved into tud_cdc_rx_cb
(otherwise there will be two passes over all incoming data).
Also, the functionality here should be put in samd and mimxrt ports, because they will have the same issue. Would be best to put it all in shared/...
and let the ports pull in the same code.
ports/rp2/mphalport.c
Outdated
if (ringbuf_free(&usb_ringbuf)) { | ||
ringbuf_put(&usb_ringbuf, tud_cdc_read_char()); | ||
} else { | ||
break; |
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 this returns with bytes still available in the CDC buffer, and there's no more USB activity, I think those bytes will never be processed (because there's no further callback here, and mp_hal_stdin_rx_chr
will only check in the ringbuf).
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 tried to check against this condition and while testing this with a bigger text buffer copy&pasted into the repl, I noticed that the board freezes (may happen after several chunks of say 600 bytes strings pasted into REPL). With freezing I mean that it no longer reacts on the REPL.
I then reverted back before I made the callback changes (current upstream master), and there it seems to work fine. Pasting many blocks of data in the REPL worked until a memoryError was thrown..
So I'm not convinced anymore if handling this callback is actually a good idea.. not until the source of the freeze is found.
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 could be because there is now some processing for each char in this callback, and that means the USB stack misses some IRQs.
This could be related to the fact that on rp2 the tud_task()
is called from MICROPY_VM_HOOK_POLL
, whereas on other tinyusb ports like mimxrt and samd, it's called directly from the USB IRQ (calling from MICROPY_VM_HOOK_POLL
introduces some latency).
Given that those other ports work well calling tud_task()
from IRQ context, it would be good to see if rp2 can do the same.
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 I just realized that tud_int_handler()
does not seem to get called. At least I did not see any references to it. Or am I missing something here? Perhaps that would also explain some issues.
I can try to include the USB IRQ handling (which would then call tud_int_handler()
and tud_task()
, in line with the Mimxrt and Samd port).
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 the IRQ handler gets registered with the pico-sdk, see lib/tinyusb/src/portable/raspberrypi/rp2040/dcd_rp2040.c
:
irq_set_exclusive_handler(USBCTRL_IRQ, dcd_rp2040_irq);
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.
Further digging and thoughts...
hathach/tinyusb#52 suggests that tud_task()
should not be called in IRQ context.
The comment here discourages to install a custom USB irq for the rp2040
CircuitPython does seems to deregister the original IRQ and install a separate handler though (https://github.com/adafruit/circuitpython/blob/main/ports/raspberrypi/supervisor/usb.c#L37). Likely due to reducing the latency introduced when a USB irq is fired until tud_task() is called: A usb background task is scheduled in the irq, seems to be a CircuitPython addition there.
I guess we could try to do similar things and improve latency. Perhaps re-installing the IRQ and adding a callback as done by CircuitPython could be beneficial. But how could we signal to call tud_task()
from the IRQ? Maybe re-use something from the machinery of micropython.schedule()
?
On a general note, it does bother me that this is going to change a lot of things. And from all I've read so far, the existing approach how MicroPython handles the USB stack should be fine. It may not be very good in terms of latency, but it should still satisfy the requirments given by the tinyUSB stack. It's still unclear to me why the endpoint behaves the way it does in #7996... Perhaps it would really be better to report this upstream and get an opinion from there. Maybe this is a real bug and should be solved in the stack, rather than trying to circumfence it in the upper layers.
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 this returns with bytes still available in the CDC buffer, and there's no more USB activity, I think those bytes will never be processed (because there's no further callback here, and mp_hal_stdin_rx_chr will only check in the ringbuf).
this is addressed now. There is a separate check in mp_hal_stdio_poll
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 looks like the pending_cdc_bytes=0
at the end of this function will overwrite the pending_cdc_bytes = bytes_avail
line. Also it seems a waste to use 5 bytes for what is effectively a flag. And also this won't work with multiple CDC interfaces (the cdc_itf_nr
will be clobbered by the next interface if all are full).
I suggest the following: have a uint8_t cdc_itf_pending
set of bits which are 1 if the corresponding interface needs attention. Then in this function it'll be set like cdc_itf_pending |= 1 << itf
, cleared like cdc_itf_pending &= ~(1 << itf)
at the top of the function, and then in poll will be something like:
if (cdc_itf_pending) {
for (uint8_t itf = 0; itf < MAX; ++itf) {
if (cdc_itf_pending & (1 << itf)) {
tud_cdc_rx_cb(itf);
}
}
}
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.
for (uint8_t itf = 0; itf < MAX; ++itf)
Hmm what would MAX
be? In the stm32 port, there is MICROPY_HW_USB_CDC_NUM
, should that be reused here?
Maybe even |
@dpgeorge I pushed an update. I could not address all your review points unfortunately. I played around with subscribing the USB IRQ, but I couldn't get it to a reliable working state. The issue was always that when pasting larger amount of text in the REPL, it will freeze. So I moved back to my original solution. I discovered that when not calling tud_task() in mp_hal_stdout_tx_strn() then pasting worked flawlessly. As soon as I used tud_task() it started to freeze after a few paste's. The state I now pushed solves the original problem with CTRL+C not working in the REPL and seems to work ok for larger text pasting too. I did not experience any freezing. But I did not syncronize it with the other ports. I also do not feel comfortable in doing so because I neither have a mimixrt nor a samd board at hand. |
I placed hathach/tinyusb#1273 |
926d615
to
4501cb1
Compare
I see that there is no response yet from TinyUSB. So maybe we need to solve this our own way, with the ringbuf approach and always taking chars from the USB as soon as they come in, like is done here currently. |
I tested this setup on my development board (raspi pico) pretty extensively. A collegue of mine did the same testing on a linux machine. We did not see any issues (rebased it recently to current master, works also). But how to proceed for the other ports? I cannot do testing with samd or mimxrt boards. Suggestion: Start with the change here in the rp2 port, then consolidate the changes later on the other ports. |
@hoihu The test for the mimxrt port can be done by me. I tried to understand the changes that you made. I see one file being changed. And it requires the definition of MICROPY_HW_ENABLE_USBDEV. That's what it seems to be. |
@hoihu I ported the change to mimxrt. Tested with mimxrt1011, mimxrt1015, mimxrt1021, mimxrt1052 and mimxrt1062. You'll find the changed file at https://github.com/robert-hh/micropython/blob/usb_endpoint/ports/mimxrt/mphalport.c So you may include that file into your PR, if you like. Otherwise I will add that to my next service PR. |
4501cb1
to
44b5a29
Compare
done |
done |
yes excactly. It's now a bit more though, since I added the Sorry about those additional changes but I thought it's good to include them and it also addresses the reviews by damien.
I would prefer if you could add the changes to the mixmrt port (since I don't have a board.. perhaps I should buy one...) |
Done. And it works fine. The fact, that Ctrl-C did not work anymore after a single character was indeed annoying. |
ports/rp2/mphalport.c
Outdated
if ((poll_flags & MP_STREAM_POLL_RD) && ringbuf_peek(&stdin_ringbuf) != -1) { | ||
ret |= MP_STREAM_POLL_RD; | ||
#if MICROPY_HW_ENABLE_USBDEV | ||
if (pending_cdc_bytes) { |
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 might make sense to check if ringbuf_free()
before calling tud_cdc_rx_cb
@@ -84,21 +89,10 @@ uintptr_t mp_hal_stdio_poll(uintptr_t poll_flags) { | |||
// Receive single character | |||
int mp_hal_stdin_rx_chr(void) { | |||
for (;;) { | |||
#if MICROPY_HW_ENABLE_UART_REPL | |||
int c = ringbuf_get(&stdin_ringbuf); |
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.
will probably need to check cdc pending here and call tud_cdc_rx_cb
, because the poll function may never be called
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.
@hoihu I still think this comment needs attention. If cdc_itf_pending
is set then this function may run forever and never check that flag.
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.
@dpgeorge I pushed an update. Since mp_hal_stdio_poll()
and mp_hal_stdin_rx_chr()
share the same logic, I moved the common code to a separate function poll_cdc_interfaces()
.
We will also need to test if the throughput of USB changes with this change. |
Tested with the actual state of the PR on a Teensy 4.0, Test set-up: Time actual master: 20.819 seconds So the new code is slightly faster. Send script:
Receive script:
Once the receive script has finished, one can get into REPL again and get the values of amt and n. |
Thanks for the test. IMO would be good to add that (or something like it) to |
Identical results on an rp2 Pico. About mpremote: Is is possible to start a script with mpremote and then release the /dev/ttyACM0, such that another PC-local script can use it? |
Oh dear, my bad. I meant to
I was thinking about this also, but then I thought that with only one ringbuffer, multiple CDC interfaces are not really supported? At least not concurrently, since the received characters are mixed in the ringbuffer? Otherwise, we should think about using a separate ringbuffer per CDC interface IMO. I recall that the stm32 port does a similar thing, not sure though. I'm happy to add the changes as suggested though. |
44b5a29
to
fbb3ff1
Compare
Yes the chars from multiple CDCs would be mixed. That's OK for the REPL (and is how it works when UART and CDC both feed the REPL), but not OK for when one CDC is the REPL and other CDCs are used for other things. For now it doesn't matter because there's no support to configure multiple CDCs. In the future different CDCs could feed into different ringbufs, but that would still require a |
The CDC USB endpoint got NACK'd if a character was received but not consumed by the application (e.g. via sys.stdin.read()). See also issue micropython#7996. This approach uses a callback `tud_cdc_rx_cb` which is called by the tinyUSB stack on reception of new cdc data. By consuming the data immediately, the endpoint does not stall anymore. The previous handler `tud_cdc_rx_wanted_cb` was obsolete and removed. In addition some cleanup was done along the way: by adding `interrupt_char.c` and removing the existing code `mp_hal_set_interrupt_char()`. Also, there is now only one (stdin) ringbuffer.
fbb3ff1
to
8496f25
Compare
Supporting multiple CDC interfaces properly would a cool addition indeed! |
Merged in 5873390 I tested the patch and the USB CDC throughput is unchanged. I also tested copying a 600k file to/from a Pico using |
Porting PR micropython#8040 of @hoihu for SAMD, following the commit 5873390. One small addition: Before executing keyboard interrupt, the input buffer is cleared.
Porting PR micropython#8040 of @hoihu for SAMD, following the commit 5873390. One small addition: Before executing keyboard interrupt, the input buffer is cleared.
Porting PR micropython#8040 of @hoihu for SAMD, following the commit 5873390. One small addition: Before executing keyboard interrupt, the input buffer is cleared.
Porting PR micropython#8040 of @hoihu for SAMD, following the commit 5873390. One small addition: Before executing keyboard interrupt, the input buffer is cleared.
Porting PR micropython#8040 of @hoihu for SAMD, following the commit 5873390. One small addition: Before executing keyboard interrupt, the input buffer is cleared.
Porting PR micropython#8040 of @hoihu for SAMD, following the commit 5873390. One small addition: Before executing keyboard interrupt, the input buffer is cleared.
Porting PR micropython#8040 of @hoihu for SAMD, following the commit 5873390. One small addition: Before executing keyboard interrupt, the input buffer is cleared.
Porting PR micropython#8040 of @hoihu for SAMD, following the commit 5873390. One small addition: Before executing keyboard interrupt, the input buffer is cleared.
Porting PR micropython#8040 of @hoihu for SAMD, following the commit 5873390. One small addition: Before executing keyboard interrupt, the input buffer is cleared.
Porting PR micropython#8040 of @hoihu for SAMD, following the commit 5873390. One small addition: Before executing keyboard interrupt, the input buffer is cleared.
Porting PR micropython#8040 of @hoihu for SAMD, following the commit 5873390. One small addition: Before executing keyboard interrupt, the input buffer is cleared.
Porting PR micropython#8040 of @hoihu for SAMD, following the commit 5873390. One small addition: Before executing keyboard interrupt, the input buffer is cleared.
Porting PR micropython#8040 of @hoihu for SAMD, following the commit 5873390. One small addition: Before executing keyboard interrupt, the input buffer is cleared.
Porting PR micropython#8040 of @hoihu for SAMD, following the commit 5873390. One small addition: Before executing keyboard interrupt, the input buffer is cleared.
Porting PR micropython#8040 of @hoihu for SAMD, following the commit 5873390. One small addition: Before executing keyboard interrupt, the input buffer is cleared.
Porting PR micropython#8040 of @hoihu for SAMD, following the commit 5873390. One small addition: Before executing keyboard interrupt, the input buffer is cleared.
Porting PR micropython#8040 of @hoihu for SAMD, following the commit 5873390. One small addition: Before executing keyboard interrupt, the input buffer is cleared.
Porting PR micropython#8040 of @hoihu for SAMD, following the commit 5873390. One small addition: Before executing keyboard interrupt, the input buffer is cleared.
Porting PR micropython#8040 of @hoihu for SAMD, following the commit 5873390. One small addition: Before executing keyboard interrupt, the input buffer is cleared.
Porting PR micropython#8040 of @hoihu for SAMD, following the commit 5873390. One small addition: Before executing keyboard interrupt, the input buffer is cleared.
Porting PR micropython#8040 of @hoihu for SAMD, following the commit 5873390. One small addition: Before executing keyboard interrupt, the input buffer is cleared.
Porting PR micropython#8040 of @hoihu for SAMD, following the commit 5873390. One small addition: Before executing keyboard interrupt, the input buffer is cleared.
Porting PR micropython#8040 of @hoihu for SAMD, following the commit 5873390. One small addition: Before executing keyboard interrupt, the input buffer is cleared.
Porting PR micropython#8040 of @hoihu for SAMD, following the commit 5873390. One small addition: Before executing keyboard interrupt, the input buffer is cleared.
Porting PR micropython#8040 of @hoihu for SAMD, following the commit 5873390. One small addition: Before executing keyboard interrupt, the input buffer is cleared.
Porting PR micropython#8040 of @hoihu for SAMD, following the commit 5873390. One small addition: Before executing keyboard interrupt, the input buffer is cleared.
Porting PR micropython#8040 by @hoihu to SAMD, following the commit 5873390. One small addition: before executing keyboard interrupt, the input buffer is cleared.
Porting PR micropython#8040 by @hoihu to SAMD, following the commit 5873390. One small addition: before executing keyboard interrupt, the input buffer is cleared.
Porting PR micropython#8040 by @hoihu to SAMD, following the commit 5873390. One small addition: before executing keyboard interrupt, the input buffer is cleared.
fixes #7996