diff options
author | Richard Braun <rbraun@sceen.net> | 2019-01-07 23:00:40 +0100 |
---|---|---|
committer | Richard Braun <rbraun@sceen.net> | 2019-04-24 01:09:03 +0200 |
commit | b99a7a8477947305489d3fbc8db57e7e1511272e (patch) | |
tree | 088e34b97314b4d3b98cffeca7c7163b8eb77f22 | |
parent | 558e96c137bdd7cf7b8f2fac0cadddeb32c830bc (diff) |
kern/mbuf: new module
-rw-r--r-- | kern/Makefile | 1 | ||||
-rw-r--r-- | kern/mbuf.c | 261 | ||||
-rw-r--r-- | kern/mbuf.h | 125 |
3 files changed, 387 insertions, 0 deletions
diff --git a/kern/Makefile b/kern/Makefile index 1d9559f3..19a15e5c 100644 --- a/kern/Makefile +++ b/kern/Makefile @@ -15,6 +15,7 @@ x15_SOURCES-y += \ kern/kmem.c \ kern/latomic.c \ kern/log.c \ + kern/mbuf.c \ kern/mutex.c \ kern/panic.c \ kern/percpu.c \ diff --git a/kern/mbuf.c b/kern/mbuf.c new file mode 100644 index 00000000..5ac06955 --- /dev/null +++ b/kern/mbuf.c @@ -0,0 +1,261 @@ +/* + * Copyright (c) 2018-2019 Richard Braun. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Upstream site with license notes : + * http://git.sceen.net/rbraun/librbraun.git/ + */ + +#include <assert.h> +#include <errno.h> +#include <limits.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> + +#include <kern/macros.h> +#include <kern/mbuf.h> + +/* + * Message header. + * + * The size denotes the size of the data, without the header. + */ +union mbuf_hdr { + uint8_t size1; + uint16_t size2; + uint32_t size4; +#ifdef __LP64__ + uint64_t size8; +#endif /* __LP64__ */ +}; + +static size_t +mbuf_compute_hdr_size(unsigned int order) +{ + return order / CHAR_BIT; +} + +static void * +mbuf_hdr_get_msg_size_addr(union mbuf_hdr *hdr, unsigned int order) +{ + switch (order) { + case 8: + return &hdr->size1; + case 16: + return &hdr->size2; + default: +#ifdef __LP64__ + return &hdr->size8; + case 32: +#endif /* __LP64__ */ + return &hdr->size4; + } +} + +static size_t +mbuf_hdr_get_msg_size(const union mbuf_hdr *hdr, unsigned int order) +{ + switch (order) { + case 8: + return hdr->size1; + case 16: + return hdr->size2; + default: +#ifdef __LP64__ + return hdr->size8; + case 32: +#endif /* __LP64__ */ + return hdr->size4; + } +} + +static void +mbuf_hdr_init(union mbuf_hdr *hdr, unsigned int order, size_t size) +{ + switch (order) { + case 8: + assert(size <= UINT8_MAX); + hdr->size1 = size; + break; + case 16: + assert(size <= UINT16_MAX); + hdr->size2 = size; + break; + default: +#ifdef __LP64__ + hdr->size8 = size; + break; + case 32: + assert(size <= UINT32_MAX); +#endif /* __LP64__ */ + hdr->size4 = size; + } +} + +static size_t +mbuf_hdr_get_total_msg_size(const union mbuf_hdr *hdr, unsigned int order) +{ + return mbuf_compute_hdr_size(order) + mbuf_hdr_get_msg_size(hdr, order); +} + +static unsigned int +mbuf_compute_order(size_t max_msg_size) +{ + unsigned int order; + + assert(max_msg_size != 0); + order = (sizeof(max_msg_size) * CHAR_BIT) - __builtin_clzl(max_msg_size); + return P2ROUND(order, CHAR_BIT); +} + +void +mbuf_init(struct mbuf *mbuf, void *buf, size_t capacity, size_t max_msg_size) +{ + cbuf_init(&mbuf->cbuf, buf, capacity); + mbuf->max_msg_size = max_msg_size; + mbuf->order = mbuf_compute_order(max_msg_size); +} + +void +mbuf_clear(struct mbuf *mbuf) +{ + return cbuf_clear(&mbuf->cbuf); +} + +static void +mbuf_clear_old_msgs(struct mbuf *mbuf, size_t total_size) +{ + union mbuf_hdr hdr; + void *msg_size_addr; + size_t hdr_size; + + hdr_size = mbuf_compute_hdr_size(mbuf->order); + msg_size_addr = mbuf_hdr_get_msg_size_addr(&hdr, mbuf->order); + + do { + size_t msg_size, size; + int error; + + size = hdr_size; + error = cbuf_pop(&mbuf->cbuf, msg_size_addr, &size); + assert(!error); + + if (size == 0) { + break; + } + + msg_size = mbuf_hdr_get_msg_size(&hdr, mbuf->order); + size = msg_size; + error = cbuf_pop(&mbuf->cbuf, NULL, &size); + assert(!error && (size == msg_size)); + } while (cbuf_avail_size(&mbuf->cbuf) < total_size); +} + +int +mbuf_push(struct mbuf *mbuf, const void *buf, size_t size, bool erase) +{ + union mbuf_hdr hdr; + void *msg_size_addr; + size_t hdr_size, total_size; + int error; + + if (size > mbuf->max_msg_size) { + return EINVAL; + } + + mbuf_hdr_init(&hdr, mbuf->order, size); + + total_size = mbuf_hdr_get_total_msg_size(&hdr, mbuf->order); + + if (total_size > cbuf_avail_size(&mbuf->cbuf)) { + if (!erase || (total_size > cbuf_capacity(&mbuf->cbuf))) { + return EMSGSIZE; + } + + mbuf_clear_old_msgs(mbuf, total_size); + } + + hdr_size = mbuf_compute_hdr_size(mbuf->order); + msg_size_addr = mbuf_hdr_get_msg_size_addr(&hdr, mbuf->order); + + error = cbuf_push(&mbuf->cbuf, msg_size_addr, hdr_size, erase); + assert(!error); + error = cbuf_push(&mbuf->cbuf, buf, size, erase); + assert(!error); + + return 0; +} + +int +mbuf_pop(struct mbuf *mbuf, void *buf, size_t *sizep) +{ + size_t start; + int error; + + start = cbuf_start(&mbuf->cbuf); + error = mbuf_read(mbuf, &start, buf, sizep); + + if (!error) { + cbuf_set_start(&mbuf->cbuf, start); + } + + return error; +} + +int +mbuf_read(const struct mbuf *mbuf, size_t *indexp, void *buf, size_t *sizep) +{ + union mbuf_hdr hdr; + void *msg_size_addr; + size_t hdr_size, msg_size, size; + int error; + + hdr_size = mbuf_compute_hdr_size(mbuf->order); + msg_size_addr = mbuf_hdr_get_msg_size_addr(&hdr, mbuf->order); + + size = hdr_size; + error = cbuf_read(&mbuf->cbuf, *indexp, msg_size_addr, &size); + + if (error) { + return error; + } else if (size == 0) { + return EAGAIN; + } + + assert(size == hdr_size); + + msg_size = mbuf_hdr_get_msg_size(&hdr, mbuf->order); + + if (msg_size > *sizep) { + error = EMSGSIZE; + goto out; + } + + size = msg_size; + error = cbuf_read(&mbuf->cbuf, *indexp + hdr_size, buf, &size); + + if (error) { + goto out; + } + + assert(size == msg_size); + + *indexp += hdr_size + size; + +out: + *sizep = msg_size; + return error; +} diff --git a/kern/mbuf.h b/kern/mbuf.h new file mode 100644 index 00000000..1c36afb5 --- /dev/null +++ b/kern/mbuf.h @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2018-2019 Richard Braun. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Upstream site with license notes : + * http://git.sceen.net/rbraun/librbraun.git/ + * + * + * FIFO message buffer. + */ + +#ifndef KERN_MBUF_H +#define KERN_MBUF_H + +#include <stdbool.h> +#include <stddef.h> + +#include <kern/cbuf.h> + +/* + * Message buffer. + * + * Message buffers are built on top of circular byte buffers. They provide + * discrete message transfer from a producer to a consumer. + * + * The order is computed from the maximum message size, and is used to + * determine a header size that can represent up to 2^order - 1 bytes. + */ +struct mbuf { + struct cbuf cbuf; + size_t max_msg_size; + unsigned int order; +}; + +static inline size_t +mbuf_start(const struct mbuf *mbuf) +{ + return cbuf_start(&mbuf->cbuf); +} + +static inline size_t +mbuf_end(const struct mbuf *mbuf) +{ + return cbuf_end(&mbuf->cbuf); +} + +/* + * Initialize a message buffer. + * + * The descriptor is set to use the given buffer for storage. Capacity + * must be a power-of-two. + */ +void mbuf_init(struct mbuf *mbuf, void *buf, size_t capacity, + size_t max_msg_size); + +/* + * Clear a message buffer. + */ +void mbuf_clear(struct mbuf *mbuf); + +/* + * Push a message to a message buffer. + * + * If the message doesn't fit in the message buffer, either because it is + * larger than the capacity, or because the function isn't allowed to erase + * old messages and the message buffer doesn't have enough available memory + * for the new message, EMSGSIZE is returned. If the message is larger than + * the maximum message size, EINVAL is returned. + */ +int mbuf_push(struct mbuf *mbuf, const void *buf, size_t size, bool erase); + +/* + * Pop a message from a message buffer. + * + * On entry, the sizep argument points to the size of the output buffer. + * On return, it is updated to the size of the message. If the message + * doesn't fit in the output buffer, it is not popped, EMSGSIZE is + * returned, but the sizep argument is updated nonetheless to let the + * user know the message size, to potentially retry with a larger buffer. + * + * If the buffer is empty, EAGAIN is returned, and the size of the output + * buffer is unmodified. + * + * The output buffer may be NULL, in which case this function acts as if + * it wasn't, but without writing output data. + */ +int mbuf_pop(struct mbuf *mbuf, void *buf, size_t *sizep); + +/* + * Read a message from a message buffer. + * + * On entry, the indexp argument points to the index of the message to + * read in the message buffer, and the sizep argument points to the size + * of the output buffer. On return, if successful, indexp is updated + * to the index of the next message, and sizep to the size of the + * message read. + * + * If the message doesn't fit in the output buffer, it is not read, + * EMSGSIZE is returned, and the sizep argument is updated nonetheless + * to let the user know the message size, to potentially retry with a + * larger buffer. + * + * If the given index refers to the end of the buffer, then EAGAIN is + * returned. If it's outside buffer boundaries, EINVAL is returned. + * Otherwise, if it doesn't point to the beginning of a message, the + * behavior is undefined. + * + * The message buffer isn't changed by this operation. + */ +int mbuf_read(const struct mbuf *mbuf, size_t *indexp, + void *buf, size_t *sizep); + +#endif /* KERN_MBUF_H */ |