Zephyr OS UART RX Buffers: Separate Data Ready from Permission to Reuse Storage

UART_RX_RDY tells an application that a reported range contains received data. It does not, by itself, say that the UART driver has finished using the entire buffer. Reusing that storage immediately can corrupt subsequent reception or data that another application task has not consumed yet.

Zephyr 4.4.0's asynchronous UART API provides separate events for data availability, requests for another buffer, and release of a buffer. A reliable receiver models those distinctions explicitly instead of treating every callback as “one complete packet arrived.”

UART buffer lifetime progresses from driver-owned through data-ready slices and driver release, then becomes reusable only after application readers finish.
Original conceptual engineering diagram. Proposed review method, not a measured result.

Track ranges, not just buffer addresses

The tagged UART API contract allows multiple data-ready events for the same buffer. Each event identifies an offset and a length of newly received bytes. Processing the whole buffer on every notification can therefore duplicate previously handled bytes or expose storage outside the reported range.

Treat the event as a bounded slice. Validate the slice against the known buffer allocation and pass only those bytes to the parser. The API event boundary is not necessarily the protocol frame boundary: a timeout can report a partial message, while a full buffer can contain several messages.

Keep parser framing separate from driver-buffer accounting. A parser may need to carry a partial frame across slices or emit several frames from one slice. Neither case changes when the underlying receive storage becomes reusable.

There are two owners to satisfy

Driver release and application consumption are different milestones. UART_RX_BUF_RELEASED establishes that the driver no longer uses that buffer. If a worker still holds a pointer to received data inside it, application ownership may continue after that event.

A useful conceptual state model is FREE, DRIVER_OWNED, RELEASED_WITH_PENDING_READERS, and FREE again. The exact implementation can use counters, reference ownership, or copied data. What matters is that reuse requires both conditions: the driver has released the storage and every application consumer relying on those bytes has finished.

Copying slices into an application queue is one option. It trades copying and queue capacity for a simpler lifetime boundary. Passing pointers can avoid copies, but requires explicit retention until the consumer completes. Do not select either approach solely because a short bench test happened not to corrupt data.

Respond to requests without recycling live storage

A buffer-request event asks for the next receive buffer. Supplying the current live buffer again is not a valid way to create more capacity. Obtain another available buffer and check the result of the response call. If the response fails, reconcile the allocation state rather than leaving the buffer permanently marked as owned by the driver.

The upstream serial shell callback illustrates separate handling for data-ready, buffer-request, and buffer-release events. It also handles failure when offering a replacement buffer. This is an implementation example, not a complete receiver design to copy without its helper and surrounding assumptions.

For a custom receiver, define what happens when the buffer pool is empty. Reception may stop if a replacement is not supplied in time. Increasing the pool can absorb a finite backlog, but it does not solve a consumer that remains slower than the input indefinitely.

Shutdown still has pending ownership

Disabling reception is a transition, not a reason to clear every buffer immediately. The API describes final data-ready and release events before the disabled event. A stop caused by an external error also has an event sequence that can include remaining data and releases.

Preserve ownership accounting through that sequence. If the application immediately starts a new receive session, distinguish old queued slices from the new session so a delayed consumer cannot accidentally return storage to the wrong generation of the pool.

A hypothetical failure is a protocol worker that wakes after reception has been disabled and restarted. Its pointer still names a valid address, but that address now holds bytes from the new session. Memory validity alone does not establish message validity.

Exercise the states that a quiet link hides

A focused test plan includes repeated timeout notifications within one buffer, a full buffer, a delayed parser, replacement-buffer exhaustion, and disable with unread data. Observe slice boundaries and ownership transitions, not only decoded application messages.

The acceptance question is precise: can any buffer be offered to the driver while either the driver or an application reader still needs its previous contents? If the answer is uncertain, retain or copy the data until the ownership rule can be demonstrated. This protects the receive path without confusing driver events with protocol packets or claiming a universal pool size.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *