blob: 4ef522bd4202e95fd1c66c2af7e97626ac63f270 [file] [log] [blame]
/* Copyright (c) 2015 Google Inc
* Davide Libenzi <dlibenzi@google.com>
* See LICENSE for details.
*/
#include <kmalloc.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>
#include <error.h>
#include <circular_buffer.h>
bool circular_buffer_init(struct circular_buffer *cb, size_t size, char *mem)
{
cb->mem = mem;
if (mem)
cb->base = mem;
else
cb->base = kmalloc(size, MEM_WAIT);
if (cb->base) {
cb->rdptr = cb->wrptr = cb->base;
cb->size = 0;
cb->allocated = size;
}
return cb->base != NULL;
}
void circular_buffer_destroy(struct circular_buffer *cb)
{
if (cb->base) {
if (cb->mem)
kfree(cb->mem);
cb->rdptr = cb->wrptr = cb->base = cb->mem = NULL;
cb->size = cb->allocated = 0;
}
}
void circular_buffer_clear(struct circular_buffer *cb)
{
cb->rdptr = cb->wrptr = cb->base;
cb->size = 0;
}
static bool circular_buffer_is_overlap(const struct circular_buffer *cb,
const char *rptr, const char *wptr,
size_t size)
{
/* Check if the current write operation [wptr, wptr+size) is overwriting
* the block at which rptr in pointing to.
*/
return (cb->size > 0) && (rptr >= wptr) && (rptr < (wptr + size));
}
static void circular_buffer_write_skip(struct circular_buffer *cb, char *wrptr,
size_t size)
{
/* Move the read pointer forward, so that the incoming write does not
* overwrite the block the read pointer is looking at.
*/
while (circular_buffer_is_overlap(cb, cb->rdptr, wrptr, size)) {
char *rdptr = cb->rdptr;
size_t bsize = *(const cbuf_size_t *) rdptr;
if (likely(bsize)) {
cb->rdptr = rdptr + bsize;
cb->size -= bsize - sizeof(cbuf_size_t);
if (unlikely(cb->size == 0))
cb->rdptr = cb->base;
} else {
cb->rdptr = cb->base;
}
}
}
size_t circular_buffer_write(struct circular_buffer *cb,
const char *data, size_t size)
{
/* Data is written and evetually discarded in atomic blocks, in order to
* maintain the consistency of the information stored in the buffer.
*/
char *wrptr = cb->wrptr;
size_t wspace = (cb->base + cb->allocated) - wrptr;
size_t esize = size + 2 * sizeof(cbuf_size_t);
if (unlikely(esize > cb->allocated))
return 0;
/* If at the end of the buffer, the next block to be written does not
* fit, we move the pointer to the beginning of the circular buffer.
*/
if (unlikely(esize > wspace)) {
circular_buffer_write_skip(cb, wrptr, wspace);
wrptr = cb->base;
}
circular_buffer_write_skip(cb, wrptr, esize);
/* Write the data and the end of sequence marker (0).
*/
*(cbuf_size_t *) wrptr = sizeof(cbuf_size_t) + size;
memcpy(wrptr + sizeof(cbuf_size_t), data, size);
cb->wrptr = wrptr + sizeof(cbuf_size_t) + size;
cb->size += size;
*(cbuf_size_t *) cb->wrptr = 0;
return size;
}
size_t circular_buffer_read(struct circular_buffer *cb, char *data, size_t size,
size_t off)
{
size_t asize = cb->size, rsize = 0;
const char *rdptr = cb->rdptr;
while (asize > 0 && size > 0) {
size_t bsize = *(const cbuf_size_t *) rdptr;
if (likely(bsize)) {
size_t esize = bsize - sizeof(cbuf_size_t);
if (off >= esize) {
off -= esize;
} else {
size_t csize = MIN(esize - off, size);
memcpy(data, rdptr + sizeof(cbuf_size_t) + off,
csize);
data += csize;
size -= csize;
rsize += csize;
off = 0;
}
rdptr = rdptr + bsize;
asize -= esize;
} else {
rdptr = cb->base;
}
}
return rsize;
}