blob: 2ffd2542f539954136fa506560eb8253bc3a1333 [file] [log] [blame]
/*
* Copyright (c) 2014 Brian Swetland
* Copyright (c) 2014-2015 Christopher Anderson
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <assert.h>
#include <debug.h>
#include <trace.h>
#include <printf.h>
#include <string.h>
#include <malloc.h>
#include <kernel/thread.h>
#include <kernel/semaphore.h>
#include <kernel/spinlock.h>
#include <lib/pktbuf.h>
#include <lib/pool.h>
#include <lk/init.h>
#if WITH_KERNEL_VM
#include <kernel/vm.h>
#endif
#define LOCAL_TRACE 0
static pool_t pktbuf_pool;
static semaphore_t pktbuf_sem;
static spin_lock_t lock;
/* Take an object from the pool of pktbuf objects to act as a header or buffer. */
static void *get_pool_object(void) {
pool_t *entry;
spin_lock_saved_state_t state;
sem_wait(&pktbuf_sem);
spin_lock_irqsave(&lock, state);
entry = pool_alloc(&pktbuf_pool);
spin_unlock_irqrestore(&lock, state);
return (pktbuf_pool_object_t *) entry;
}
/* Return an object to thje pktbuf object pool. */
static void free_pool_object(pktbuf_pool_object_t *entry, bool reschedule) {
DEBUG_ASSERT(entry);
spin_lock_saved_state_t state;
spin_lock_irqsave(&lock, state);
pool_free(&pktbuf_pool, entry);
spin_unlock_irqrestore(&lock, state);
sem_post(&pktbuf_sem, reschedule);
}
/* Callback used internally to place a pktbuf_pool_object back in the pool after
* it was used as a buffer for another pktbuf
*/
static void free_pktbuf_buf_cb(void *buf, void *arg) {
free_pool_object((pktbuf_pool_object_t *)buf, true);
}
/* Add a buffer to a pktbuf. Header space for prepending data is adjusted based on
* header_sz. cb is called when the pktbuf is freed / released by the driver level
* and should handle proper management / freeing of the buffer pointed to by the iovec.
*
* It's important to note that there is a flag to note that the buffer is cached and should
* be properly handled via the appropriate driver when it's time to deal with buffer
* descriptiors.
*/
void pktbuf_add_buffer(pktbuf_t *p, u8 *buf, u32 len, uint32_t header_sz, uint32_t flags,
pktbuf_free_callback cb, void *cb_args) {
DEBUG_ASSERT(p);
DEBUG_ASSERT(buf);
DEBUG_ASSERT(header_sz < len);
p->buffer = buf;
p->blen = len;
p->data = p->buffer + header_sz;
p->dlen = 0;
p->flags = PKTBUF_FLAG_EOF | flags;
p->cb = cb;
p->cb_args = cb_args;
/* If we're using a VM then this may be a virtual address, look up to see
* if there is an associated physical address we can store. If not, then
* stick with the address as presented to us.
*/
#if WITH_KERNEL_VM
p->phys_base = kvaddr_to_paddr(buf) | (uintptr_t) buf % PAGE_SIZE;
#else
p->phys_base = (uintptr_t) buf;
#endif
}
pktbuf_t *pktbuf_alloc(void) {
pktbuf_t *p = NULL;
void *buf = NULL;
p = get_pool_object();
if (!p) {
return NULL;
}
buf = get_pool_object();
if (!buf) {
free_pool_object((pktbuf_pool_object_t *)p, false);
return NULL;
}
memset(p, 0, sizeof(pktbuf_t));
pktbuf_add_buffer(p, buf, PKTBUF_SIZE, PKTBUF_MAX_HDR, 0, free_pktbuf_buf_cb, NULL);
return p;
}
pktbuf_t *pktbuf_alloc_empty(void) {
pktbuf_t *p = (pktbuf_t *) get_pool_object();
p->flags = PKTBUF_FLAG_EOF;
return p;
}
int pktbuf_free(pktbuf_t *p, bool reschedule) {
DEBUG_ASSERT(p);
if (p->cb) {
p->cb(p->buffer, p->cb_args);
}
free_pool_object((pktbuf_pool_object_t *)p, false);
return 1;
}
void pktbuf_append_data(pktbuf_t *p, const void *data, size_t sz) {
if (pktbuf_avail_tail(p) < sz) {
panic("pktbuf_append_data: overflow");
}
memcpy(p->data + p->dlen, data, sz);
p->dlen += sz;
}
void *pktbuf_append(pktbuf_t *p, size_t sz) {
if (pktbuf_avail_tail(p) < sz) {
panic("pktbuf_append: overflow");
}
void *data = p->data + p->dlen;
p->dlen += sz;
return data;
}
void *pktbuf_prepend(pktbuf_t *p, size_t sz) {
if (pktbuf_avail_head(p) < sz) {
panic("pktbuf_prepend: not enough space");
}
p->dlen += sz;
p->data -= sz;
return p->data;
}
void *pktbuf_consume(pktbuf_t *p, size_t sz) {
void *data = p->data;
if (sz > p->dlen) {
return NULL;
}
p->data += sz;
p->dlen -= sz;
return data;
}
void pktbuf_consume_tail(pktbuf_t *p, size_t sz) {
if (sz > p->dlen) {
p->dlen = 0;
return;
}
p->dlen -= sz;
}
void pktbuf_dump(pktbuf_t *p) {
printf("pktbuf data %p, buffer %p, dlen %u, data offset %lu, phys_base %p\n",
p->data, p->buffer, p->dlen, (uintptr_t) p->data - (uintptr_t) p->buffer,
(void *)p->phys_base);
}
static void pktbuf_init(uint level)
{
void *slab;
#if LK_DEBUGLEVEL > 0
printf("pktbuf: creating %u pktbuf entries of size %zu (total %zu)\n",
PKTBUF_POOL_SIZE, sizeof(struct pktbuf_pool_object),
PKTBUF_POOL_SIZE * sizeof(struct pktbuf_pool_object));
#endif
#if WITH_KERNEL_VM
if (vmm_alloc_contiguous(vmm_get_kernel_aspace(), "pktbuf",
PKTBUF_POOL_SIZE * sizeof(struct pktbuf_pool_object),
&slab, 0, 0, ARCH_MMU_FLAG_CACHED) < 0) {
printf("Failed to initialize pktbuf hdr slab\n");
return;
}
#else
slab = memalign(CACHE_LINE, PKTBUF_POOL_SIZE * sizeof(pktbuf_pool_object_t));
#endif
pool_init(&pktbuf_pool, sizeof(struct pktbuf_pool_object), CACHE_LINE, PKTBUF_POOL_SIZE, slab);
sem_init(&pktbuf_sem, PKTBUF_POOL_SIZE);
}
LK_INIT_HOOK(pktbuf, pktbuf_init, LK_INIT_LEVEL_THREADING);
// vim: set noexpandtab: