blob: 0822cd185e6a7580de096664d0f1adaf26c17b2d [file] [log] [blame] [edit]
/*
* Copyright 2020, Data61, CSIRO (ABN 41 687 119 230)
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <autoconf.h>
#include <string.h>
#include <sel4/sel4.h>
#include <virtqueue.h>
#include <camkes/virtqueue.h>
#include <camkes/dataport.h>
#include <picotcp-ethernet-async.h>
#ifdef PACKED
#undef PACKED
#endif
/* PACKED is redefined by picotcp */
#include <pico_stack.h>
#include <pico_device.h>
#define NUM_TX_BUFS 254
#define NUM_RX_BUFS 254
#define BUF_SIZE 2048
typedef struct state {
struct pico_device pico_dev;
/* keeps track of how many TX buffers are in use */
int num_tx;
/*
* this represents the pool of buffers that can be used for TX,
* this array is a sliding array in that num_tx acts a pointer to
* separate between buffers that are in use and buffers that are
* not in use. E.g. 'o' = free, 'x' = in use
* -------------------------------------
* | o | o | o | o | o | o | x | x | x |
* -------------------------------------
* ^
* num_tx
*/
void *pending_tx[NUM_TX_BUFS];
/* mac address for this client */
uint8_t mac[6];
virtqueue_driver_t tx_virtqueue;
virtqueue_driver_t rx_virtqueue;
ps_io_ops_t *io_ops;
bool action;
} state_t;
struct id_state_mapper {
uint32_t dataport_id;
state_t *state;
};
static struct id_state_mapper *mapper;
static size_t mapper_len;
/* 1500 is the standard ethernet MTU at the network layer. */
#define ETHER_MTU 1500
static int pico_eth_send(struct pico_device *dev, void *input_buf, int len)
{
state_t *state = (state_t *)dev;
virtqueue_ring_object_t handle;
uint32_t sent_len;
void *buf;
if (virtqueue_get_used_buf(&state->tx_virtqueue, &handle, &sent_len) == 0) {
if (state->num_tx != 0) {
state->num_tx --;
buf = state->pending_tx[state->num_tx];
} else {
// No free packets to use.
return 0;
}
} else {
vq_flags_t flag;
int more = virtqueue_gather_used(&state->tx_virtqueue, &handle, &buf, &sent_len, &flag);
if (more == 0) {
ZF_LOGF("pico_eth_send: Invalid virtqueue ring entry");
}
buf = DECODE_DMA_ADDRESS(buf);
}
/* copy the packet over */
memcpy(buf, input_buf, len);
ps_dma_cache_clean(&state->io_ops->dma_manager, buf, len);
virtqueue_init_ring_object(&handle);
if (!virtqueue_add_available_buf(&state->tx_virtqueue, &handle, ENCODE_DMA_ADDRESS(buf), len, VQ_RW)) {
ZF_LOGF("pico_eth_send: Error while enqueuing available buffer, queue full");
}
state->action = true;
return len;
}
static void pico_free_buf(uint8_t *buf)
{
/* Pico doesn't allow callbacks to be registered with cookies...
* We rely on the fact that the buffer was allocated from a dataport and use
* the dataport ID to find the driver state from a global mapper array.
*/
dataport_ptr_t wrapped_ptr = dataport_wrap_ptr(buf);
state_t *state = NULL;
for (int i = 0; i < mapper_len; i++) {
if (mapper[i].dataport_id == wrapped_ptr.id) {
state = mapper[i].state;
break;
}
}
virtqueue_ring_object_t handle;
virtqueue_init_ring_object(&handle);
if (!virtqueue_add_available_buf(&state->rx_virtqueue, &handle, ENCODE_DMA_ADDRESS(buf), BUF_SIZE, VQ_RW)) {
ZF_LOGF("Error while enqueuing available buffer");
}
}
/* 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)
{
state_t *state = (state_t *)dev;
while (loop_score > 0) {
virtqueue_ring_object_t handle;
uint32_t len;
unsigned next = (state->rx_virtqueue.u_ring_last_seen + 1) & (state->rx_virtqueue.queue_len - 1);
if (next == state->rx_virtqueue.used_ring->idx) {
break;
}
handle.first = state->rx_virtqueue.used_ring->ring[next].id;
len = state->rx_virtqueue.used_ring->ring[next].len;
handle.cur = handle.first;
void *buf;
vq_flags_t flag;
int more = virtqueue_gather_used(&state->rx_virtqueue, &handle, &buf, &len, &flag);
if (more == 0) {
ZF_LOGF("pico_eth_poll: Invalid virtqueue ring entry");
}
if (len > 0) {
ps_dma_cache_invalidate(&state->io_ops->dma_manager, DECODE_DMA_ADDRESS(buf), len);
int err = pico_stack_recv(dev, DECODE_DMA_ADDRESS(buf), len);
if (err <= 0) {
ZF_LOGE("Failed to rx buffer in poll");
break;
}
}
virtqueue_get_used_buf(&state->rx_virtqueue, &handle, &len);
pico_free_buf(DECODE_DMA_ADDRESS(buf));
loop_score--;
}
return loop_score;
}
static void notify_server(UNUSED seL4_Word badge, void *cookie)
{
state_t *state = cookie;
if (state->action) {
state->action = false;
state->tx_virtqueue.notify();
}
}
static void irq_from_ethernet(UNUSED seL4_Word badge, void *cookie)
{
pico_stack_tick();
}
int picotcp_ethernet_async_client_init_late(void *cookie, register_callback_handler_fn_t register_handler)
{
state_t *data = cookie;
register_handler(0, "notify_ethernet", notify_server, data);
return 0;
}
int picotcp_ethernet_async_client_init(ps_io_ops_t *io_ops, const char *tx_virtqueue, const char *rx_virtqueue,
register_callback_handler_fn_t register_handler, get_mac_client_fn_t get_mac, void **cookie)
{
state_t *data;
int error = ps_calloc(&io_ops->malloc_ops, 1, sizeof(*data), (void **)&data);
data->io_ops = io_ops;
seL4_Word tx_badge;
seL4_Word rx_badge;
/* Initialise read virtqueue */
error = camkes_virtqueue_driver_init_with_recv(&data->tx_virtqueue, camkes_virtqueue_get_id_from_name(tx_virtqueue),
NULL, &tx_badge);
if (error) {
ZF_LOGE("Unable to initialise serial server read virtqueue");
}
/* Initialise write virtqueue */
error = camkes_virtqueue_driver_init_with_recv(&data->rx_virtqueue, camkes_virtqueue_get_id_from_name(rx_virtqueue),
NULL, &rx_badge);
if (error) {
ZF_LOGE("Unable to initialise serial server write virtqueue");
}
bool add_to_mapper = false;
/* preallocate buffers */
for (int i = 0; i < NUM_RX_BUFS; i++) {
void *buf = ps_dma_alloc(&io_ops->dma_manager, BUF_SIZE, 64, 1, PS_MEM_NORMAL);
ZF_LOGF_IF(buf == NULL, "Alloc Failed\n");
memset(buf, 0, BUF_SIZE);
virtqueue_ring_object_t handle;
virtqueue_init_ring_object(&handle);
/* Save the dataport ID to state data mapping for later.
pico_free_buf doesn't allow us to provide a cookie so we need to be able
to get from a dma pointer back to the driver state.
*/
if (!add_to_mapper) {
dataport_ptr_t wrapped_ptr = dataport_wrap_ptr(buf);
mapper = realloc(mapper, mapper_len + 1);
mapper[mapper_len] = (struct id_state_mapper) {
.dataport_id = wrapped_ptr.id, .state = data
};
mapper_len++;
add_to_mapper = true;
}
if (!virtqueue_add_available_buf(&data->rx_virtqueue, &handle, ENCODE_DMA_ADDRESS(buf), BUF_SIZE, VQ_RW)) {
ZF_LOGF("Error while enqueuing available buffer, queue full");
}
}
for (int i = 0; i < NUM_TX_BUFS; i++) {
void *buf = ps_dma_alloc(&io_ops->dma_manager, BUF_SIZE, 64, 1, PS_MEM_NORMAL);
ZF_LOGF_IF(buf == NULL, "Alloc Failed\n");
memset(buf, 0, BUF_SIZE);
data->pending_tx[data->num_tx] = buf;
data->num_tx++;
}
register_handler(tx_badge, "ethernet_event_handler", irq_from_ethernet, data);
/* Create a driver. This utilises preallocated buffers, backed up by malloc above */
/* Attach funciton pointers */
data->pico_dev.send = pico_eth_send;
data->pico_dev.poll = pico_eth_poll;
/* Configure the mtu in picotcp */
uint8_t mac[6] = {0};
get_mac(&mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]);
data->pico_dev.mtu = ETHER_MTU;
if (pico_device_init(&data->pico_dev, "eth0", mac) != 0) {
ZF_LOGE("Failed to initialize pico device");
return 1;
}
/* Set max frames to reduce unbounded picotcp memory growth. */
data->pico_dev.q_in->max_frames = 512;
data->pico_dev.q_out->max_frames = 512;
printf("Installed eth0 into picotcp\n");
data->tx_virtqueue.notify();
*cookie = data;
return 0;
}