| /* 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; |
| } |