IO Read/Write Abstraction
libxr_rw.hpp defines the generic ReadPort and WritePort, which wrap asynchronous, blocking and polling I/O behind one interface; the completion feedback is selected by Operation. This page describes the interface for calling the ports; the driver-backend side is summarized in Driver backend interface below.
Note:
ReadPort/WritePortallocate their internal queues once at construction and do not free them on destruction.
Core Types
ReadPort / WritePort
ReadPort and WritePort encapsulate the invocation process, buffer management, and synchronization mechanisms for read/write operations. Each call is accompanied by an Operation instance to explicitly specify the desired completion feedback behavior (callback, blocking, polling, or ignore).
ReadOperation / WriteOperation
typedef Operation<ErrorCode> ReadOperation;
typedef Operation<ErrorCode> WriteOperation;
These represent asynchronous I/O operations with a completion response behavior. Callbacks, semaphores, or polling status variables can be passed via the constructor - see the core-op page for details.
ReadPort Interface
Construction
explicit ReadPort(size_t buffer_size = 128);
Creates a receive queue of buffer_size bytes (default 128). With 0 no queue is allocated and the port is not readable.
Submitting a read
ErrorCode operator()(RawData data, ReadOperation &op, bool in_isr = false);
A positive-length request copies into data only after the complete length is available. A zero-length request waits for the queue to become nonempty and consumes no bytes.
For non-BLOCK, OK means admission; completion can be inline or deferred. For BLOCK, OK means the data handoff completed. A request larger than capacity returns SIZE_ERR; occupied request processing returns BUSY; an unbound port returns NOT_SUPPORT; a BLOCK read that times out returns TIMEOUT.
BLOCK reads are thread-only; until one returns, the same port must not start another read or call ClearQueuedData(), nor overlap an outstanding non-BLOCK read.
Keep the destination and callback/polling objects alive until completion. A BLOCK destination remains valid through function return.
Queries
size_t Size() const;
size_t EmptySize() const;
size_t Capacity() const;
bool Readable() const;
The first three return queued bytes, free bytes and total capacity, all 0 when no queue is bound. Readable() means a receive queue exists; it does not mean bytes are currently available.
Clearing queued bytes
ErrorCode ClearQueuedData(bool in_isr = false);
This discards already queued receive bytes but does not cancel a pending read. It returns BUSY while a request or dequeue path is active. Success also notifies the receive side that space became available, allowing a backpressured backend to resume.
WritePort Interface
Construction
WritePort(size_t queue_size = 3, size_t buffer_size = 128);
queue_size counts queued write requests and buffer_size counts payload bytes. A port with queue_size == 0 is used by Pipe.
Submitting a write
ErrorCode operator()(ConstRawData data, WriteOperation &op, bool in_isr = false);
Admission copies the entire source into the internal byte queue, so caller storage can be reused after return.
- non-
BLOCKOKmeans admitted; aBLOCKwrite returns the completion result orTIMEOUT, and queued data is still sent after a timeout (see BLOCK Timeout and Completion Handoff); - insufficient request or byte capacity returns
FULL; - producer-state conflict returns
BUSY; - an unavailable port returns
NOT_SUPPORT; - requests are not partially admitted;
- zero-length writes succeed after capability checking without notifying the backend;
BLOCKwrites are thread-only;BLOCKand non-BLOCKwrites on one port must not overlap.
Write completion means that the backend accepted the entire request, not that physical transmission ended. A UART backend can complete the WriteOperation after copying bytes into active/pending DMA storage while DMA and the wire keep running.
Queries
size_t Size() const;
size_t EmptySize() const;
size_t Capacity() const;
bool Writable() const;
Size(), EmptySize() and Capacity() return queued bytes (including uncommitted Stream appends), free bytes and total capacity of the byte queue. Writable() means byte storage exists and the driver has bound its progress function WriteFun. It does not reserve a request slot or byte capacity for the next call.
Driver backend interface
A driver writes received bytes with ReadPort::GetReadQueue() and calls Publish(), binds a WriteFun to the WritePort, and takes pending data with WritePort::GetWriteQueue() from WriteFun or its transmit-complete interrupt. The call order, the concurrency rules and an STM32 UART example are in sections 3.1 and 4.2 of I/O Completion Semantics and Port State Machines; the overall structure of a UART driver is in UART Driver Design.
STDIO Interface
LibXR provides a global STDIO; once ReadPort / WritePort are bound, Printf or Print writes debug output.
LibXR::STDIO::write_ = uart.write_port_;
LibXR::STDIO::Printf<"Hello, %d">(123);
LibXR::STDIO::Print<"Hello, {}">(123);
Printf / Print format and write to STDIO::write_ under an internal mutex and must be called from threads. They return the number of bytes committed; output beyond the write port's current free space is truncated. They return -1 when write_ is unset or not writable, or when formatting or submission fails. If STDIO::write_stream_ is set, output goes to that stream; otherwise each call uses a temporary WritePort::Stream.
Usage Examples
For data size of 0, Write will return success directly, and Read will complete once any data is readable.
// Blocking write to UART, timeout set to 100ms (default is infinite wait)
WriteOperation op_block(sem, 100);
uart.Write("Hello", op_block);
// Asynchronous read with callback
ReadOperation op_cb(callback);
uart.Read(buffer, op_cb);
WritePort::Stream Batch Write Interface
WritePort::Stream merges several pieces of data into one write request, for writing multiple blocks in a row.
Key Features
- Repeated
<<orWrite()calls append to one batch. Commit()submits the batch; destruction submits it automatically.
Example Usage
WriteOperation op;
// Typical batch write using stream interface
{
WritePort::Stream s(&uart_port, op);
s << data1 << data2 << data3;
// s.Commit(); // Optional, auto-committed on destruction
}
Interface Specification
class WritePort::Stream {
public:
Stream(WritePort* port, WriteOperation op);
~Stream();
Stream& operator<<(const ConstRawData& data);
[[nodiscard]] ErrorCode Write(ConstRawData data);
[[nodiscard]] ErrorCode Write(std::string_view text);
[[nodiscard]] ErrorCode Commit();
[[nodiscard]] ErrorCode Acquire();
[[nodiscard]] size_t EmptySize() const;
};
Semantics highlights:
- The constructor tries to acquire the port and does not report failure; call
Acquire()to check or retry.Acquire()returnsOKwhen acquired or already owned,PTR_NULLfor a null port,NOT_SUPPORTwhen not writable,BUSYwhen occupied, andFULLwhen no request slot is free. Write()appends data and reports the result:FULLwhen space is insufficient, appending nothing from this call and keeping earlier appends.<<does the same without reporting failures.Commit()submits the batch and releases the port; a later<</Write()re-acquires it and starts a new batch. An empty batch only completes non-BLOCK notifications.- If the stream still owns the port on destruction, it behaves like
Commit()and discards the result. - Thread-only; completion notifications use
in_isr = false.
ReadPort and WritePort are the core interfaces of the LibXR I/O abstraction layer. They provide unified data buffering and completion feedback mechanisms, suitable for data stream scenarios such as UART, network, and file systems.