Thanks to visit codestin.com
Credit goes to github.com

Skip to content

ports SAMD and RP2: Fix allocation of UART buffers. #16884

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

Merged
merged 3 commits into from
Mar 27, 2025

Conversation

robert-hh
Copy link
Contributor

@robert-hh robert-hh commented Mar 9, 2025

Summary

The UART buffers were reset on every call to uart.init(). If no sizes were given, the buffers ere set to the default size 256. That made problems e.g. with PPP.
This PR fixes it, keeping the buffer size if not deliberately changed and allocating new buffers only if the size was changed.
Fix a subtle problem of the SAMD port where the board would lock up in same UART loopback mode, if rxbuf is full and not emptied. Then sending of the data does not complete.

Testing

  • Set the UART buffer sizes to non-default values and then call uart.init() without changing the buffer size. Verified that the sizes did not change and data still can be sent & received.
  • Change the buffer sizes in calls to uart.init(). Verify that the size has changed and data still can be sent & received.

Copy link

github-actions bot commented Mar 9, 2025

Code size report:

   bare-arm:    +0 +0.000% 
minimal x86:    +0 +0.000% 
   unix x64:    +0 +0.000% standard
      stm32:    +0 +0.000% PYBV10
     mimxrt:    +0 +0.000% TEENSY40
        rp2:   +56 +0.006% RPI_PICO_W
       samd:   +48 +0.018% ADAFRUIT_ITSYBITSY_M4_EXPRESS
  qemu rv32:    +0 +0.000% VIRT_RV32

@Gadgetoid
Copy link
Contributor

Slight tangent but the extra byte ringbuf uses for "tracking" (I had to dig to find out what on earth that was for) really makes a royal mess of this code.

(Even bigger tangent) It also suggests some invocations of ringbuf_alloc are incorrect, since they don't account for this extra byte. Eg:

ringbuf_alloc(self->recv_buffer, self->recv_buffer_size);

@robert-hh
Copy link
Contributor Author

robert-hh commented Mar 10, 2025

Slight tangent but the extra byte ringbuf uses for "tracking" (I had to dig to find out what on earth that was for) really makes a royal mess of this code.

It this a comment about the +/-1 byte is tedious (which I agree), or are you saying that the code is wrong?

Edit: There have always been complaints by users, that they get a buffer size of N-1, when N was requested.

@Gadgetoid
Copy link
Contributor

or are you saying that the code is wrong?

I think it's right, but at a glance it's not easy to tell.

The unrelated esp example looks wrong, but the whole thing is so contrived it's difficult to reason about.

@Gadgetoid
Copy link
Contributor

That said there's right and then there's correct.

I don't think: self->read_buffer.size = DEFAULT_BUFFER_SIZE + 1; is "correct." It feels like a trap ready to spring. Doesn't look like read_buffer.size is used anywhere without - 1 so these cancel out?

Also any code that tries to consume a machine_uart_obj_t without knowledge of the ringbuf_alloc wart would get an incorrect read_buffer.size?

(I appreciate you've inherited this wart 😬)

@robert-hh
Copy link
Contributor Author

robert-hh commented Mar 10, 2025

I don't think: self->read_buffer.size = DEFAULT_BUFFER_SIZE + 1; is "correct."

Technically it looks correct to me. The symbols DEFAULT_BUFFER_SIZE and rx_len are related to the external view of a user/user code. read_buffer.size is the internal value of the ringbuf API, which has always to be by 1 larger than the external view.
And yes, there are ringbuf implementation which avoids this fussiness( see e.g. https://www.snellman.net/blog/archive/2016-12-13-ring-buffers/), but with limitations.

Edit: I could have used another variable in the UART object with the buffer lengths, but wanted to avoid these extra RAM bytes to be consumed.

@Gadgetoid
Copy link
Contributor

read_buffer.size is the internal value of the ringbuf API

Aha that's the piece of the puzzle I was missing. Thank you.

@andrewleech
Copy link
Contributor

FWIW the "other" kind of ringbuffer that avoids the tracking byte is used & discussed in a couple of places and is quite elegant, but comes with a restriction that the size must be a x^2, ie 16, 32, 64, 128 etc. You can no longer have an arbitrary size.

On a related note, I did add the +1 behind the scenes in #9458 when the user requests a 16byte buffer it'll allocate a 17byte array for use to avoid the confusion. If the user passes in a pre-allocated buffer though this isn't possible, so they get -1 size. I tried to address this in the docs on the function.

It turns out though the +1 is kinda inefficient in many cases, due to the gc allocation blocks in micropython. If the user asks for 16byte and get given a 17byte buffer behind the scenes, well micropython will have allocated two blocks in gc which are typically 16byte blocks - so 32bytes will be consumed for that 16/17byte ringbuffer. Implementation details...

@robert-hh
Copy link
Contributor Author

I added separate symbols holding the API related size of rxbuf and txbuf instead of using the size data field of the ring buffer. For RP2, this is simply a better coding style. For SAMD this is as well needed because depending on the setting for bits a data item uses 1 or 2 bytes in the ring buffer. Then the separate rxbuf and txbuf size symbols are not affected by the bits size.
Fixed another problem where the board would lock up in UART loopback mode, if rxbuf is full and not emptied. Then sending of the data does not complete.

@Gadgetoid
Copy link
Contributor

I added separate symbols holding the API related size of rxbuf and txbuf instead of using the size data field of the ring buffer. For RP2, this is simply a better coding style.

Thank you, and agreed. Soooo much easier to get my head 'round should I ever end up spelunking this code for bugs (fingers crossed).

Looks like that's ~+16 bytes over the original fix.

The buffer was be reset on every call to uart.init().  If no sizes were
given, the buffer was set to the default size 256.  That made problems e.g.
with PPP.

This commit fixes it, keeping the buffer size if not deliberately changed
and allocating new buffers only if the size was changed.

Signed-off-by: robert-hh <[email protected]>
The buffer was be reset on every call to uart.init().  If no sizes were
given, the buffer was set to the default size 256.  That made problems
e.g. with PPP.

This commit fixes it, keeping the buffer size if not deliberately changed
and allocating new buffers only if the size was changed.  Cater for changes
of the bits value, which requires a change to the buffer size.

Signed-off-by: robert-hh <[email protected]>
@dpgeorge dpgeorge force-pushed the ports_uart_buffer branch from d140327 to 4dfee50 Compare March 27, 2025 00:40
Copy link
Member

@dpgeorge dpgeorge left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fix looks good, thanks!

(I split out the samd loopback bug fix to a separate commit.)

@dpgeorge dpgeorge merged commit 4dfee50 into micropython:master Mar 27, 2025
9 checks passed
@robert-hh
Copy link
Contributor Author

Thank you very much.

@robert-hh robert-hh deleted the ports_uart_buffer branch March 27, 2025 07:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants