blob: 144392dd30ac2cf2f367a74242c5c306946d808e [file] [log] [blame] [edit]
/*
* Copyright 2017, Data61, CSIRO (ABN 41 687 119 230)
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <autoconf.h>
#include <ethdrivers/gen_config.h>
#include <picotcp/gen_config.h>
#ifdef CONFIG_LIB_PICOTCP
#include <ethdrivers/pico_dev_eth.h>
#include <ethdrivers/helpers.h>
#include <string.h>
#include <inttypes.h>
#include "debug.h"
#include <utils/zf_log.h>
static int alloc_buf_pool(pico_device_eth *pico_iface)
{
/* Take the next free buffer */
if (pico_iface->next_free_buf == -1) {
ZF_LOGE("Out of preallocated eth buffers.");
return -1;
}
int retval = pico_iface->next_free_buf;
pico_iface->next_free_buf = pico_iface->buf_pool[retval];
return retval;
}
static void free_buf_pool(pico_device_eth *pico_iface, int buf_no)
{
/* Return back into the buffer pool */
if (buf_no < 0 || buf_no > CONFIG_LIB_ETHDRIVER_NUM_PREALLOCATED_BUFFERS) {
ZF_LOGE("Attempted to return a buffer outside of the pool %d.", buf_no);
return;
}
pico_iface->buf_pool[buf_no] = pico_iface->next_free_buf;
pico_iface->next_free_buf = buf_no;
}
static void destroy_free_bufs(pico_device_eth *pico_iface)
{
if (pico_iface->bufs) {
free(pico_iface->bufs);
}
if (pico_iface->dma_bufs) {
for (int i = 0; i < CONFIG_LIB_ETHDRIVER_NUM_PREALLOCATED_BUFFERS; i++) {
if (pico_iface->dma_bufs[i].virt) {
dma_unpin_free(&pico_iface->dma_man, pico_iface->dma_bufs[i].virt,
CONFIG_LIB_ETHDRIVER_PREALLOCATED_BUF_SIZE);
}
}
free(pico_iface->dma_bufs);
pico_iface->dma_bufs = NULL;
}
if (pico_iface->buf_pool) {
free(pico_iface->buf_pool);
}
pico_iface->next_free_buf = -1;
if (pico_iface->rx_lens) {
free(pico_iface->rx_lens);
}
pico_iface->bufs = NULL;
}
static void initialize_free_bufs(pico_device_eth *pico_iface)
{
dma_addr_t *dma_bufs = NULL;
dma_bufs = calloc(CONFIG_LIB_ETHDRIVER_NUM_PREALLOCATED_BUFFERS, sizeof(dma_addr_t));
pico_iface->dma_bufs = dma_bufs;
if (!dma_bufs) {
destroy_free_bufs(pico_iface);
return;
}
pico_iface->bufs = malloc(sizeof(dma_addr_t *) * CONFIG_LIB_ETHDRIVER_NUM_PREALLOCATED_BUFFERS);
if (!pico_iface->bufs) {
destroy_free_bufs(pico_iface);
return;
}
/* Pin buffers */
for (int i = 0; i < CONFIG_LIB_ETHDRIVER_NUM_PREALLOCATED_BUFFERS; i++) {
dma_bufs[i] = dma_alloc_pin(&pico_iface->dma_man,
CONFIG_LIB_ETHDRIVER_PREALLOCATED_BUF_SIZE, 1, pico_iface->driver.dma_alignment);
if (!dma_bufs[i].phys) {
destroy_free_bufs(pico_iface);
return;
}
ps_dma_cache_clean_invalidate(&pico_iface->dma_man, dma_bufs[i].virt,
CONFIG_LIB_ETHDRIVER_PREALLOCATED_BUF_SIZE);
pico_iface->bufs[i] = &dma_bufs[i];
}
pico_iface->num_free_bufs = CONFIG_LIB_ETHDRIVER_NUM_PREALLOCATED_BUFFERS;
/* Setup the buffer pool management */
pico_iface->buf_pool = malloc(sizeof(int) * CONFIG_LIB_ETHDRIVER_NUM_PREALLOCATED_BUFFERS);
if (!pico_iface->buf_pool) {
destroy_free_bufs(pico_iface);
return;
}
for (int i = 0; i < CONFIG_LIB_ETHDRIVER_NUM_PREALLOCATED_BUFFERS; i++) {
pico_iface->buf_pool[i] = i - 1;
}
pico_iface->next_free_buf = CONFIG_LIB_ETHDRIVER_NUM_PREALLOCATED_BUFFERS - 1;
/* Rx queue */
pico_iface->rx_count = 0;
pico_iface->rx_lens = calloc(CONFIG_LIB_ETHDRIVER_NUM_PREALLOCATED_BUFFERS, sizeof(int));
if (!pico_iface->rx_lens) {
destroy_free_bufs(pico_iface);
return;
}
pico_iface->rx_queue = calloc(CONFIG_LIB_ETHDRIVER_NUM_PREALLOCATED_BUFFERS, sizeof(int));
if (!pico_iface->rx_queue) {
destroy_free_bufs(pico_iface);
return;
}
return;
}
static uintptr_t pico_allocate_rx_buf(void *iface, size_t buf_size, void **cookie)
{
pico_device_eth *pico_iface = (pico_device_eth *)iface;
if (buf_size > CONFIG_LIB_ETHDRIVER_PREALLOCATED_BUF_SIZE) {
ZF_LOGE("Requested RX buffer of size %zu which can never be fullfilled by preallocated buffers of size %d",
buf_size, CONFIG_LIB_ETHDRIVER_PREALLOCATED_BUF_SIZE);
return 0;
}
if (!pico_iface->bufs) {
initialize_free_bufs(pico_iface);
if (!pico_iface->bufs) {
ZF_LOGE("Failed lazy initialization of preallocated free buffers");
return 0;
}
}
int buf_no = alloc_buf_pool(pico_iface);
if (buf_no < 0) {
/* No buffers available */
return 0;
}
dma_addr_t *buf = pico_iface->bufs[buf_no];
ps_dma_cache_invalidate(&pico_iface->dma_man, buf->virt, buf_size);
*(long *) cookie = buf_no;
return buf->phys;
}
static void pico_tx_complete(void *iface, void *cookie)
{
free_buf_pool(iface, (long) cookie);
}
static void pico_rx_complete(void *iface, unsigned int num_bufs, void **cookies, unsigned int *lens)
{
/* A buffer has been filled. Put it into the receive queue to be collected. */
pico_device_eth *pico_iface = (pico_device_eth *)iface;
if (num_bufs > 1) {
ZF_LOGE("RX buffer of size is smaller than MTU. Frame splitting unhandled.\n");
/* Frame splitting is not handled. Warn and return bufs to pool. */
for (int i = 0; i < num_bufs; i++) {
free_buf_pool(pico_iface, (long) cookies[i]);
}
} else {
int buf_no = (long) cookies[0];
/* Store the information about the rx bufs */
pico_iface->rx_queue[pico_iface->rx_count] = buf_no;
pico_iface->rx_lens[buf_no] = lens[0];
pico_iface->rx_count += 1;
#ifdef CONFIG_LIB_PICOTCP_ASYNC_DRIVER
pico_iface->pico_dev.__serving_interrupt = 1;
#endif
}
ZF_LOGD("RX complete, %d in queue!\n", ((pico_device_eth *)iface)->rx_count);
return;
}
/* Pico TCP implementation */
static int pico_eth_send(struct pico_device *dev, void *input_buf, int len)
{
dma_addr_t buf;
int status;
struct pico_device_eth *eth_device = (struct pico_device_eth *)dev;
if (len > CONFIG_LIB_ETHDRIVER_PREALLOCATED_BUF_SIZE) {
return 0;
}
long buf_no = alloc_buf_pool(eth_device);
if (buf_no < 0) {
return 0;
}
dma_addr_t *orig_buf = eth_device->bufs[buf_no];
buf = *orig_buf;
memcpy(buf.virt, input_buf, len);
ps_dma_cache_clean(&eth_device->dma_man, buf.virt, len);
unsigned int length = len;
status = eth_device->driver.i_fn.raw_tx(&eth_device->driver, 1, &buf.phys, &length, (void *) buf_no);
switch (status) {
case ETHIF_TX_FAILED:
pico_tx_complete(dev, (void *) buf_no);
ZF_LOGE("Failed tx\n");
return 0; // Error for PICO
case ETHIF_TX_COMPLETE:
pico_tx_complete(dev, (void *) buf_no);
case ETHIF_TX_ENQUEUED:
break;
}
return length;
}
/* Async driver will set a flag to signal that there is work to be done */
static int pico_eth_poll(struct pico_device *dev, int loop_score)
{
struct pico_device_eth *eth_device = (struct pico_device_eth *)dev;
while (loop_score > 0) {
if (eth_device->rx_count == 0) {
#ifdef CONFIG_LIB_PICOTCP_ASYNC_DRIVER
/* Also clear the serving_interrupt flag for async driver*/
eth_device->pico_dev.__serving_interrupt = 0;
#endif
break;
}
/* Retrieve the data from the rx buffer */
eth_device->rx_count -= 1;
int buf_no = eth_device->rx_queue[eth_device->rx_count];
dma_addr_t *buf = eth_device->bufs[buf_no];
int len = eth_device->rx_lens[buf_no];
ps_dma_cache_invalidate(&eth_device->dma_man, buf->virt, len);
pico_stack_recv(dev, buf->virt, len);
free_buf_pool(eth_device, buf_no);
loop_score--;
}
return loop_score;
}
static struct raw_iface_callbacks pico_prealloc_callbacks = {
.tx_complete = pico_tx_complete,
.rx_complete = pico_rx_complete,
.allocate_rx_buf = pico_allocate_rx_buf
};
struct pico_device *pico_eth_create_no_malloc(char *name,
ethif_driver_init driver_init, void *driver_config, ps_io_ops_t io_ops, struct pico_device_eth *eth_dev)
{
if (eth_dev == NULL) {
ZF_LOGE("Invalid pico_device passed into Pico create no_malloc");
return NULL;
}
memset(eth_dev, 0, sizeof(struct pico_device_eth));
/* Set the dma manager up */
eth_dev->driver.i_cb = pico_prealloc_callbacks;
eth_dev->dma_man = io_ops.dma_manager;
eth_dev->driver.cb_cookie = eth_dev;
/* Initialize hardware */
int err;
err = driver_init(&(eth_dev->driver), io_ops, driver_config);
if (err) {
return NULL;
}
/* Initialise buffers in case driver did not do so */
if (!eth_dev->bufs) {
initialize_free_bufs(eth_dev);
}
/* Attach funciton pointers */
eth_dev->pico_dev.send = pico_eth_send;
#ifdef CONFIG_LIB_PICOTCP_ASYNC_DRIVER
/* Although the same function, .poll needs to be set to NULL for an async driver */
eth_dev->pico_dev.poll = NULL;
eth_dev->pico_dev.dsr = pico_eth_poll;
#else
eth_dev->pico_dev.poll = pico_eth_poll;
#endif
/* Also do some low level init */
int mtu;
uint8_t mac[6] = {0};
eth_dev->driver.i_fn.low_level_init(&eth_dev->driver, mac, &mtu);
/* Configure the mtu in picotcp */
eth_dev->pico_dev.mtu = mtu;
/* Register in picoTCP (equivalent to netif init in lwip)*/
if (pico_device_init(&(eth_dev->pico_dev), name, mac) != 0) {
ZF_LOGE("Failed to initialize pico device");
free(eth_dev);
return NULL;
}
return (struct pico_device *)eth_dev;
}
struct pico_device *pico_eth_create(char *name,
ethif_driver_init driver_init, void *driver_config, ps_io_ops_t io_ops)
{
/* Create the pico device struct */
struct pico_device_eth *eth_dev = malloc(sizeof(struct pico_device_eth));
if (!eth_dev) {
ZF_LOGE("Failed to malloc pico eth device interface");
return NULL;
}
return pico_eth_create_no_malloc(name, driver_init, driver_config, io_ops, eth_dev);
}
#endif // CONFIG_LIB_PICOTCP