diff --git a/CMakeLists.txt b/CMakeLists.txt index 775af9b..2a63685 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,3 +17,4 @@ add_subdirectory(libvswitch) add_subdirectory(libfdtgen) add_subdirectory(libtx2bpmp) add_subdirectory(libplatsupportports) +add_subdirectory(libsharedringbuffer) diff --git a/libsharedringbuffer/CMakeLists.txt b/libsharedringbuffer/CMakeLists.txt new file mode 100644 index 0000000..ac66176 --- /dev/null +++ b/libsharedringbuffer/CMakeLists.txt @@ -0,0 +1,26 @@ +# +# Copyright 2022, UNSW +# SPDX-License-Identifier: BSD-2-Clause +# + +cmake_minimum_required(VERSION 3.7.2) + +project(libsharedringbuffer C) + +set(configure_string "") + +config_string( + LibsharedringbufferDescCount + LIB_SHARED_RINGBUFFER_DESC_COUNT + "Number of buffer descriptors in the ring buffer" + DEFAULT + 512 + UNQUOTE +) + +mark_as_advanced(LibsharedringbufferDescCount) +add_config_library(shared_ringbuffer "${configure_string}") + +add_library(shared_ringbuffer STATIC EXCLUDE_FROM_ALL src/shared_ringbuffer.c) +target_include_directories(shared_ringbuffer PUBLIC include) +target_link_libraries(shared_ringbuffer shared_ringbuffer_Config muslc utils sel4utils) diff --git a/libsharedringbuffer/README.md b/libsharedringbuffer/README.md new file mode 100644 index 0000000..f308488 --- /dev/null +++ b/libsharedringbuffer/README.md @@ -0,0 +1,66 @@ + + +libsharedringbuffer +------------------- + +This directory contains a library implementation of shared ring +buffers for the transportation of data. This is intended to be used as a +communication mechanism between system components for bulk data transfer, +and was originally created as a data plane between an ethernet driver and +network stack for the sDDF. This library doesn't contain any code that +interfaces with seL4. It is expected that the user will provide shared +memory regions and notification/signalling handlers to this library. + +To use this library in a project you can link `shared_ringbuffer` in your +target applications CMake configuration. + +This libary is intended to be used by both a producer and a consumer. For +example, an ethernet driver produces data for a network stack to consume. +2 separate shared memory regions are required for each ring handle; one +to store available buffers and one to store used buffers. Each ring buffer +contains a separate read index and write index. The reader only ever +increments the read index, and the writer the write index. As read and +writes of a small integer are atomic, we can keep memory consistent +without locks. +The size of the ring buffers can be set with the cmake config option, +`LIB_SHARED_RINGBUFFER_DESC_COUNT` but defaults to 512. The user must +ensure that the shared memory regions handed to the library are of +appropriate size to match this. + +Use case +--------- + +This library is intended to be used with a separate shared memory region, +usually allocated for DMA for a driver. The ring buffers can then contain +pointers into this shared memory, indicating which buffers are in use or +available to be used by either component. +Typically, 2 shared ring buffers are required, with separate structures +required on the recieve path and transmit path. Thus there are 4 regions +of shared memory required: 1 storing pointers to available RX buffers, +1 storing pointers to used RX buffers, 1 storing pointers to TX +buffers, and another storing pointers to available TX buffers. + +On initialisation, both the producer and consumer should allocate their +own ring handles (`struct ring_handle`). These data structures simply +store pointers to the actual shared memory regions and are used to +interface with this library. The ring handle should then be passed into +`ring_init` along with 2 shared memory regions, an optional function +pointer to signal the other component, and either 1 or 0 to indicate +whether the read/write indices need to be initialised (note that only one +side of the shared memory regions needs to do this). + +After initialisation, a typical use case would look like: +The driver wants to add some buffer that will be read by another component +(for example, a network stack processing incoming packets). + + 1. The driver dequeues a pointer to an available buffer from the + available ring. + 2. Once data is inserted into the buffer (eg. via DMA), the driver + then enqueues the pointer to it into the used ring. + 3. The driver can then notify the reciever. + 4. Similarly, the reciever dequeues the pointer from the used ring, + processes the data, and once finished, can enqueue it back into + the available ring to be used once more by the driver. diff --git a/libsharedringbuffer/include/shared_ringbuffer/shared_ringbuffer.h b/libsharedringbuffer/include/shared_ringbuffer/shared_ringbuffer.h new file mode 100644 index 0000000..5835e2c --- /dev/null +++ b/libsharedringbuffer/include/shared_ringbuffer/shared_ringbuffer.h @@ -0,0 +1,231 @@ +/* + * Copyright 2022, UNSW + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include + +/* Function pointer to be used to 'notify' components on either end of the shared memory */ +typedef void (*notify_fn)(void); + +/* Buffer descriptor */ +typedef struct buff_desc { + uintptr_t encoded_addr; /* encoded dma addresses */ + unsigned int len; /* associated memory lengths */ + void *cookie; /* index into client side metadata */ +} buff_desc_t; + +/* Circular buffer containing descriptors */ +typedef struct ring_buffer { + uint32_t write_idx; + uint32_t read_idx; + buff_desc_t buffers[CONFIG_LIB_SHARED_RINGBUFFER_DESC_COUNT]; +} ring_buffer_t; + +/* A ring handle for enqueing/dequeuing into */ +typedef struct ring_handle { + ring_buffer_t *used_ring; + ring_buffer_t *avail_ring; + /* Function to be used to signal that work is queued in the used_ring */ + notify_fn notify; +} ring_handle_t; + +/** + * Initialise the shared ring buffer. + * + * @param ring ring handle to use. + * @param avail pointer to available ring in shared memory. + * @param used pointer to 'used' ring in shared memory. + * @param notify function pointer used to notify the other user. + * @param buffer_init 1 indicates the read and write indices in shared memory need to be initialised. + * 0 inidicates they do not. Only one side of the shared memory regions needs to do this. + */ +void ring_init(ring_handle_t *ring, ring_buffer_t *avail, ring_buffer_t *used, notify_fn notify, int buffer_init); + +/** + * Check if the ring buffer is empty. + * + * @param ring ring buffer to check. + * + * @return true indicates the buffer is empty, false otherwise. + */ +static inline int ring_empty(ring_buffer_t *ring) +{ + return !((ring->write_idx - ring->read_idx) % CONFIG_LIB_SHARED_RINGBUFFER_DESC_COUNT); +} + +/** + * Check if the ring buffer is full + * + * @param ring ring buffer to check. + * + * @return true indicates the buffer is full, false otherwise. + */ +static inline int ring_full(ring_buffer_t *ring) +{ + return !((ring->write_idx - ring->read_idx + 1) % CONFIG_LIB_SHARED_RINGBUFFER_DESC_COUNT); +} + +/** + * Notify the other user of changes to the shared ring buffers. + * + * @param ring the ring handle used. + * + */ +static inline void notify(ring_handle_t *ring) +{ + return ring->notify(); +} + +/** + * Enqueue an element to a ring buffer + * + * @param ring Ring buffer to enqueue into. + * @param buffer address into shared memory where data is stored. + * @param len length of data inside the buffer above. + * @param cookie optional pointer to data required on dequeueing. + * + * @return -1 when ring is empty, 0 on success. + */ +static inline int enqueue(ring_buffer_t *ring, uintptr_t buffer, unsigned int len, void *cookie) +{ + if (ring_full(ring)) { + ZF_LOGE("Ring full"); + return -1; + } + + ring->buffers[ring->write_idx % CONFIG_LIB_SHARED_RINGBUFFER_DESC_COUNT].encoded_addr = buffer; + ring->buffers[ring->write_idx % CONFIG_LIB_SHARED_RINGBUFFER_DESC_COUNT].len = len; + ring->buffers[ring->write_idx % CONFIG_LIB_SHARED_RINGBUFFER_DESC_COUNT].cookie = cookie; + ring->write_idx++; + + THREAD_MEMORY_RELEASE(); + + return 0; +} + +/** + * Dequeue an element to a ring buffer. + * + * @param ring Ring buffer to Dequeue from. + * @param buffer pointer to the address of where to store buffer address. + * @param len pointer to variable to store length of data dequeueing. + * @param cookie pointer optional pointer to data required on dequeueing. + * + * @return -1 when ring is empty, 0 on success. + */ +static inline int dequeue(ring_buffer_t *ring, uintptr_t *addr, unsigned int *len, void **cookie) +{ + if (ring_empty(ring)) { + ZF_LOGF("Ring is empty"); + return -1; + } + + *addr = ring->buffers[ring->read_idx % CONFIG_LIB_SHARED_RINGBUFFER_DESC_COUNT].encoded_addr; + *len = ring->buffers[ring->read_idx % CONFIG_LIB_SHARED_RINGBUFFER_DESC_COUNT].len; + *cookie = ring->buffers[ring->read_idx % CONFIG_LIB_SHARED_RINGBUFFER_DESC_COUNT].cookie; + + THREAD_MEMORY_RELEASE(); + ring->read_idx++; + + return 0; +} + +/** + * Enqueue an element into an available ring buffer. + * This indicates the buffer address parameter is currently available for use. + * + * @param ring Ring handle to enqueue into. + * @param buffer address into shared memory where data is stored. + * @param len length of data inside the buffer above. + * @param cookie optional pointer to data required on dequeueing. + * + * @return -1 when ring is empty, 0 on success. + */ +static inline int enqueue_avail(ring_handle_t *ring, uintptr_t addr, unsigned int len, void *cookie) +{ + return enqueue(ring->avail_ring, addr, len, cookie); +} + +/** + * Enqueue an element into a used ring buffer. + * This indicates the buffer address parameter is currently in use. + * + * @param ring Ring handle to enqueue into. + * @param buffer address into shared memory where data is stored. + * @param len length of data inside the buffer above. + * @param cookie optional pointer to data required on dequeueing. + * + * @return -1 when ring is empty, 0 on success. + */ +static inline int enqueue_used(ring_handle_t *ring, uintptr_t addr, unsigned int len, void *cookie) +{ + return enqueue(ring->used_ring, addr, len, cookie); +} + +/** + * Dequeue an element from an available ring buffer. + * + * @param ring Ring handle to dequeue from. + * @param buffer pointer to the address of where to store buffer address. + * @param len pointer to variable to store length of data dequeueing. + * @param cookie pointer optional pointer to data required on dequeueing. + * + * @return -1 when ring is empty, 0 on success. + */ +static inline int dequeue_avail(ring_handle_t *ring, uintptr_t *addr, unsigned int *len, void **cookie) +{ + return dequeue(ring->avail_ring, addr, len, cookie); +} + +/** + * Dequeue an element from a used ring buffer. + * + * @param ring Ring handle to dequeue from. + * @param buffer pointer to the address of where to store buffer address. + * @param len pointer to variable to store length of data dequeueing. + * @param cookie pointer optional pointer to data required on dequeueing. + * + * @return -1 when ring is empty, 0 on success. + */ +static inline int dequeue_used(ring_handle_t *ring, uintptr_t *addr, unsigned int *len, void **cookie) +{ + return dequeue(ring->used_ring, addr, len, cookie); +} + +/** + * Dequeue an element from a ring buffer. + * This function is intended for use by the driver, to collect a pointer + * into this structure to be passed around as a cookie. + * + * @param ring Ring buffer to dequeue from. + * @param addr pointer to the address of where to store buffer address. + * @param len pointer to variable to store length of data dequeueing. + * @param cookie pointer to store a pointer to this particular entry. + * + * @return -1 when ring is empty, 0 on success. + */ +static int driver_dequeue(ring_buffer_t *ring, uintptr_t *addr, unsigned int *len, void **cookie) +{ + if (!((ring->write_idx - ring->read_idx) % CONFIG_LIB_SHARED_RINGBUFFER_DESC_COUNT)) { + ZF_LOGW("write idx = %d, read idx = %d", ring->write_idx, ring->read_idx); + ZF_LOGW("Ring is empty"); + return -1; + } + + *addr = ring->buffers[ring->read_idx % CONFIG_LIB_SHARED_RINGBUFFER_DESC_COUNT].encoded_addr; + *len = ring->buffers[ring->read_idx % CONFIG_LIB_SHARED_RINGBUFFER_DESC_COUNT].len; + *cookie = &ring->buffers[ring->read_idx % CONFIG_LIB_SHARED_RINGBUFFER_DESC_COUNT]; + + THREAD_MEMORY_RELEASE(); + ring->read_idx++; + + return 0; +} diff --git a/libsharedringbuffer/src/shared_ringbuffer.c b/libsharedringbuffer/src/shared_ringbuffer.c new file mode 100644 index 0000000..de3e367 --- /dev/null +++ b/libsharedringbuffer/src/shared_ringbuffer.c @@ -0,0 +1,20 @@ +/* + * Copyright 2022, UNSW + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +void ring_init(ring_handle_t *ring, ring_buffer_t *avail, ring_buffer_t *used, notify_fn notify, int buffer_init) +{ + ring->used_ring = used; + ring->avail_ring = avail; + ring->notify = notify; + + if (buffer_init) { + ring->used_ring->write_idx = 0; + ring->used_ring->read_idx = 0; + ring->avail_ring->write_idx = 0; + ring->avail_ring->read_idx = 0; + } +}