Nanokernel Ring Buffers

Definition

The ring buffer is defined in include/misc/ring_buffer.h and kernel/nanokernel/ring_buffer.c. This is an array-based circular buffer, stored in first-in-first-out order. The APIs allow for enqueueing and retrieval of chunks of data up to 1024 bytes in size, along with two metadata values (type ID and an app-specific integer).

Unlike nanokernel FIFOs, storage of enqueued items and their metadata is managed in a fixed buffer and there are no preconditions on the data enqueued (other than the size limit). Since the size annotation is only an 8-bit value, sizes are expressed in terms of 32-bit chunks.

Internally, the ring buffer always maintains an empty 32-bit block in the buffer to distinguish between empty and full buffers. Any given entry in the buffer will use a 32-bit block for metadata plus any data attached. If the size of the buffer array is a power of two, the ring buffer will use more efficient masking instead of expensive modulo operations to maintain itself.

Concurrency

Concurrency control of ring buffers is not implemented at this level. Depending on usage (particularly with respect to number of concurrent readers/writers) applications may need to protect the ring buffer with mutexes and/or use semaphores to notify consumers that there is data to read.

For the trivial case of one producer and one consumer, concurrency shouldn’t be needed.

Example: Initializing a Ring Buffer

There are three ways to initialize a ring buffer. The first two are through use of macros which defines one (and an associated private buffer) in file scope. You can declare a fast ring buffer that uses mask operations by declaring a power-of-two sized buffer:

/* Buffer with 2^8 or 256 elements */
SYS_RING_BUF_DECLARE_POW2(my_ring_buf, 8);

Arbitrary-sized buffers may also be declared with a different macro, but these will always be slower due to use of modulo operations:

#define MY_RING_BUF_SIZE    93
SYS_RING_BUF_DECLARE_SIZE(my_ring_buf, MY_RING_BUF_SIZE);

Alternatively, a ring buffer may be initialized manually. Whether the buffer will use modulo or mask operations will be detected automatically:

#define MY_RING_BUF_SIZE    64

struct my_struct {
    struct ring_buffer rb;
    uint32_t buffer[MY_RING_BUF_SIZE];
    ...
};
struct my_struct ms;

void init_my_struct {
    sys_ring_buf_init(&ms.rb, sizeof(ms.buffer), ms.buffer);
    ...
}

Example: Enqueuing data

int ret;

ret = sys_ring_buf_put(&ring_buf, TYPE_FOO, 0, &my_foo, SIZE32_OF(my_foo));
if (ret == -EMSGSIZE) {
    ... not enough room for the message ..
}

If the type or value fields are sufficient, the data pointer and size may be 0.

int ret;

ret = sys_ring_buf_put(&ring_buf, TYPE_BAR, 17, NULL, 0);
if (ret == -EMSGSIZE) {
    ... not enough room for the message ..
}

Example: Retrieving data

int ret;
uint32_t data[6];

size = SIZE32_OF(data);
ret = sys_ring_buf_get(&ring_buf, &type, &value, data, &size);
if (ret == -EMSGSIZE) {
    printk("Buffer is too small, need %d uint32_t\n", size);
} else if (ret == -EAGAIN) {
    printk("Ring buffer is empty\n");
} else {
    printk("got item of type %u value &u of size %u dwords\n",
           type, value, size);
    ...
}

APIs

The following APIs for ring buffers are provided by ring_buffer.h:

sys_ring_buf_init()
Initializes a ring buffer.
SYS_RING_BUF_DECLARE_POW2(), SYS_RING_BUF_DECLARE_SIZE()
Declare and init a file-scope ring buffer.
sys_ring_buf_space_get()
Returns the amount of free buffer storage space in 32-bit dwords.
sys_ring_buf_is_empty()
Indicates whether a buffer is empty.
sys_ring_buf_put()
Enqueues an item.
sys_ring_buf_get()
De-queues an item.