-
Notifications
You must be signed in to change notification settings - Fork 22
Libsharedringbuffer #15
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| <!-- | ||
| Copyright 2022, UNSW | ||
| SPDX-License-Identifier: CC-BY-SA-4.0 | ||
| --> | ||
|
|
||
| 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 | ||
lucypa marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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. | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,231 @@ | ||||||
| /* | ||||||
| * Copyright 2022, UNSW | ||||||
| * | ||||||
| * SPDX-License-Identifier: BSD-2-Clause | ||||||
| */ | ||||||
|
|
||||||
| #pragma once | ||||||
|
|
||||||
| #include <stdint.h> | ||||||
| #include <stddef.h> | ||||||
| #include <sel4utils/sel4_zf_logif.h> | ||||||
| #include <utils/util.h> | ||||||
| #include <shared_ringbuffer/gen_config.h> | ||||||
|
|
||||||
| /* Function pointer to be used to 'notify' components on either end of the shared memory */ | ||||||
| typedef void (*notify_fn)(void); | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this get a context parameter for convenience reasons? |
||||||
|
|
||||||
| /* Buffer descriptor */ | ||||||
| typedef struct buff_desc { | ||||||
| uintptr_t encoded_addr; /* encoded dma addresses */ | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, this is agnostic of what this memory is. IN the end, this could also be a |
||||||
| unsigned int len; /* associated memory lengths */ | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should use |
||||||
| void *cookie; /* index into client side metadata */ | ||||||
| } buff_desc_t; | ||||||
|
|
||||||
| /* Circular buffer containing descriptors */ | ||||||
| typedef struct ring_buffer { | ||||||
| uint32_t write_idx; | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought that size_t would be reserved for lengths/sizes of things. This is just an index into the array There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For portable code, my rule of thumb usually is, that it's either |
||||||
| 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); | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we have the modulo operation here? Isn't this simply: |
||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * 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); | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sam here, why the modulo operation, this will even work for overflows: |
||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * 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(); | ||||||
axel-h marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * 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; | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could the code redundancy a bit (even if the compiler might optimize this anyway) |
||||||
| 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++; | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it intentional that this increment isn't after the release barrier? Otherwise I think it could potentially complete before the writes to the buff_desc do and cause a reader to read too early. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, sorry i think this was an accident. Thanks for pointing it out! |
||||||
|
|
||||||
| THREAD_MEMORY_RELEASE(); | ||||||
axel-h marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
|
||||||
| 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; | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here |
||||||
| *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++; | ||||||
|
Comment on lines
+135
to
+136
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure if it'd end up making a noticeable performance difference, but there's an option for these two lines to be combined into There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do not really have a requirement for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's still a plain store, but with release semantics. So it's not allowed to be re-ordered with any memory operations that come before it in program-order. |
||||||
|
|
||||||
| 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)) { | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could simply use our helper function:
Suggested change
|
||||||
| ZF_LOGW("write idx = %d, read idx = %d", ring->write_idx, ring->read_idx); | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be a debug log printed afterwards (to have the context when reading the logs) or just merge into the next log. |
||||||
| 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]; | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
|
||||||
| THREAD_MEMORY_RELEASE(); | ||||||
| ring->read_idx++; | ||||||
|
|
||||||
| return 0; | ||||||
| } | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| /* | ||
| * Copyright 2022, UNSW | ||
| * SPDX-License-Identifier: BSD-2-Clause | ||
| */ | ||
|
|
||
| #include <shared_ringbuffer/shared_ringbuffer.h> | ||
|
|
||
| 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; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we consider passing |
||
|
|
||
| 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; | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.