blob: b0f06512e17bf8a8cc8a5c1118bf4771306b8d91 [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <assert.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <threads.h>
#include "eth-client.h"
#include <fuchsia/device/c/fidl.h>
#include <fuchsia/hardware/ethernet/c/fidl.h>
#include <zircon/process.h>
#include <zircon/syscalls.h>
#include <zircon/time.h>
#include <zircon/types.h>
#include <inet6/inet6.h>
#include <inet6/netifc.h>
#include <lib/fdio/io.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/fdio.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/watcher.h>
#define ALIGN(n, a) (((n) + ((a) - 1)) & ~((a) - 1))
// if nonzero, drop 1 in DROP_PACKETS packets at random
#define DROP_PACKETS 0
#if DROP_PACKETS > 0
//TODO: use libc random() once it's actually random
// Xorshift32 prng
typedef struct {
uint32_t n;
} rand32_t;
static inline uint32_t rand32(rand32_t* state) {
uint32_t n = state->n;
n ^= (n << 13);
n ^= (n >> 17);
n ^= (n << 5);
return (state->n = n);
}
rand32_t rstate = { .n = 0x8716253 };
#define random() rand32(&rstate)
static int txc;
static int rxc;
#endif
static mtx_t eth_lock = MTX_INIT;
static zx_handle_t netsvc = ZX_HANDLE_INVALID;
static eth_client_t* eth;
static uint8_t netmac[6];
static size_t netmtu;
static bool netifc_open_quiet = false;
static zx_handle_t iovmo;
static void* iobuf;
#define NET_BUFFERS 256
#define NET_BUFFERSZ 2048
#define ETH_BUFFER_MAGIC 0x424201020304A7A7UL
#define ETH_BUFFER_FREE 0u // on free list
#define ETH_BUFFER_TX 1u // in tx ring
#define ETH_BUFFER_RX 2u // in rx ring
#define ETH_BUFFER_CLIENT 3u // in use by stack
typedef struct eth_buffer eth_buffer_t;
struct eth_buffer {
uint64_t magic;
eth_buffer_t* next;
void* data;
uint32_t state;
uint32_t reserved;
};
static_assert(sizeof(eth_buffer_t) == 32, "");
static eth_buffer_t* eth_buffer_base;
static size_t eth_buffer_count;
static int _check_ethbuf(eth_buffer_t* ethbuf, uint32_t state) {
if (((uintptr_t) ethbuf) & 31) {
printf("ethbuf %p misaligned\n", ethbuf);
return -1;
}
if ((ethbuf < eth_buffer_base) ||
(ethbuf >= (eth_buffer_base + eth_buffer_count))) {
printf("ethbuf %p outside of arena\n", ethbuf);
return -1;
}
if (ethbuf->magic != ETH_BUFFER_MAGIC) {
printf("ethbuf %p bad magic\n", ethbuf);
return -1;
}
if (ethbuf->state != state) {
printf("ethbuf %p incorrect state (%u != %u)\n", ethbuf, ethbuf->state, state);
return -1;
}
return 0;
}
static void check_ethbuf(eth_buffer_t* ethbuf, uint32_t state) {
if (_check_ethbuf(ethbuf, state)) {
__builtin_trap();
}
}
static eth_buffer_t* eth_buffers = NULL;
static void eth_put_buffer_locked(eth_buffer_t* buf, uint32_t state) __TA_REQUIRES(eth_lock) {
check_ethbuf(buf, state);
buf->state = ETH_BUFFER_FREE;
buf->next = eth_buffers;
eth_buffers = buf;
}
void eth_put_buffer(eth_buffer_t* ethbuf) {
mtx_lock(&eth_lock);
eth_put_buffer_locked(ethbuf, ETH_BUFFER_CLIENT);
mtx_unlock(&eth_lock);
}
static void tx_complete(void* ctx, void* cookie) __TA_REQUIRES(eth_lock) {
eth_put_buffer_locked(cookie, ETH_BUFFER_TX);
}
static zx_status_t eth_get_buffer_locked(size_t sz, void** data, eth_buffer_t** out,
uint32_t newstate, bool block) __TA_REQUIRES(eth_lock) {
eth_buffer_t* buf;
if (sz > NET_BUFFERSZ) {
return ZX_ERR_INVALID_ARGS;
}
if (eth_buffers == NULL) {
while (1) {
eth_complete_tx(eth, NULL, tx_complete);
if (eth_buffers != NULL) {
break;
}
if (!block) {
return ZX_ERR_SHOULD_WAIT;
}
zx_status_t status;
zx_signals_t signals;
mtx_unlock(&eth_lock);
status = zx_object_wait_one(eth->tx_fifo, ZX_FIFO_READABLE | ZX_FIFO_PEER_CLOSED,
ZX_TIME_INFINITE, &signals);
mtx_lock(&eth_lock);
if (status < 0) {
return status;
}
if (signals & ZX_FIFO_PEER_CLOSED) {
return ZX_ERR_PEER_CLOSED;
}
}
}
buf = eth_buffers;
eth_buffers = buf->next;
buf->next = NULL;
check_ethbuf(buf, ETH_BUFFER_FREE);
buf->state = newstate;
*data = buf->data;
*out = buf;
return ZX_OK;
}
zx_status_t eth_get_buffer(size_t sz, void** data, eth_buffer_t** out, bool block) {
mtx_lock(&eth_lock);
zx_status_t r = eth_get_buffer_locked(sz, data, out, ETH_BUFFER_CLIENT, block);
mtx_unlock(&eth_lock);
return r;
}
zx_status_t eth_send(eth_buffer_t* ethbuf, size_t skip, size_t len) {
zx_status_t status;
mtx_lock(&eth_lock);
check_ethbuf(ethbuf, ETH_BUFFER_CLIENT);
#if DROP_PACKETS
txc++;
if ((random() % DROP_PACKETS) == 0) {
printf("tx drop %d\n", txc);
eth_put_buffer_locked(ethbuf, ETH_BUFFER_CLIENT);
status = ZX_ERR_INTERNAL;
goto fail;
}
#endif
if (eth == NULL) {
printf("eth_fifo_send: not connected\n");
eth_put_buffer_locked(ethbuf, ETH_BUFFER_CLIENT);
status = ZX_ERR_ADDRESS_UNREACHABLE;
goto fail;
}
ethbuf->state = ETH_BUFFER_TX;
status = eth_queue_tx(eth, ethbuf, ethbuf->data + skip, len, 0);
if (status < 0) {
printf("eth_fifo_send: queue tx failed: %d\n", status);
eth_put_buffer_locked(ethbuf, ETH_BUFFER_TX);
goto fail;
}
mtx_unlock(&eth_lock);
return ZX_OK;
fail:
mtx_unlock(&eth_lock);
return status;
}
int eth_add_mcast_filter(const mac_addr_t* addr) {
return 0;
}
static volatile zx_time_t net_timer = 0;
void netifc_set_timer(uint32_t ms) {
net_timer = zx_deadline_after(ZX_MSEC(ms));
}
int netifc_timer_expired(void) {
if (net_timer == 0) {
return 0;
}
if (zx_clock_get_monotonic() > net_timer) {
return 1;
}
return 0;
}
void netifc_get_info(uint8_t* addr, uint16_t* mtu) {
memcpy(addr, netmac, 6);
*mtu = netmtu;
}
static zx_status_t netifc_open_cb(int dirfd, int event, const char* fn, void* cookie) {
if (event != WATCH_EVENT_ADD_FILE) {
return ZX_OK;
}
if (!netifc_open_quiet) {
printf("netifc: ? /dev/class/ethernet/%s\n", fn);
}
mtx_lock(&eth_lock);
int fd;
if ((fd = openat(dirfd, fn, O_RDWR)) < 0) {
goto finish;
}
zx_status_t status = fdio_get_service_handle(fd, &netsvc);
if (status != ZX_OK) {
goto fail_close_svc;
}
// If an interface was specified, check the topological path of this device and reject it if it
// doesn't match.
if (cookie != NULL) {
const char* interface = cookie;
char buf[1024];
zx_status_t call_status;
size_t actual_len;
status = fuchsia_device_ControllerGetTopologicalPath(netsvc, &call_status,
buf, sizeof(buf) - 1, &actual_len);
if (status == ZX_OK) {
status = call_status;
}
if (status != ZX_OK) {
goto fail_close_svc;
}
buf[actual_len] = 0;
const char* topo_path = buf;
// Skip the instance sigil if it's present in either the topological path or the given
// interface path.
if (topo_path[0] == '@') topo_path++;
if (interface[0] == '@') interface++;
if (strncmp(topo_path, interface, sizeof(buf))) {
goto fail_close_svc;
}
}
fuchsia_hardware_ethernet_Info info;
if (fuchsia_hardware_ethernet_DeviceGetInfo(netsvc, &info) != ZX_OK) {
goto fail_close_svc;
}
if (info.features & (fuchsia_hardware_ethernet_INFO_FEATURE_WLAN |
fuchsia_hardware_ethernet_INFO_FEATURE_SYNTH)) {
// Don't run netsvc for wireless or synthetic network devices
goto fail_close_svc;
}
memcpy(netmac, info.mac.octets, sizeof(netmac));
netmtu = info.mtu;
// we only do this the very first time
if (eth_buffer_base == NULL) {
eth_buffer_base = memalign(sizeof(eth_buffer_t), 2 * NET_BUFFERS * sizeof(eth_buffer_t));
if (eth_buffer_base == NULL) {
goto fail_close_svc;
}
eth_buffer_count = 2 * NET_BUFFERS;
}
// we only do this the very first time
if (iobuf == NULL) {
// allocate shareable ethernet buffer data heap
size_t iosize = 2 * NET_BUFFERS * NET_BUFFERSZ;
if ((status = zx_vmo_create(iosize, ZX_VMO_NON_RESIZABLE, &iovmo)) < 0) {
goto fail_close_svc;
}
zx_object_set_property(iovmo, ZX_PROP_NAME, "eth-buffers", 11);
if ((status = zx_vmar_map(zx_vmar_root_self(),
ZX_VM_PERM_READ | ZX_VM_PERM_WRITE,
0, iovmo, 0, iosize, (uintptr_t*)&iobuf)) < 0) {
zx_handle_close(iovmo);
iovmo = ZX_HANDLE_INVALID;
goto fail_close_svc;
}
if (!netifc_open_quiet)
printf("netifc: create %zu eth buffers\n", eth_buffer_count);
// assign data chunks to ethbufs
for (unsigned n = 0; n < eth_buffer_count; n++) {
eth_buffer_base[n].magic = ETH_BUFFER_MAGIC;
eth_buffer_base[n].data = iobuf + n * NET_BUFFERSZ;
eth_buffer_base[n].state = ETH_BUFFER_FREE;
eth_buffer_base[n].reserved = 0;
eth_put_buffer_locked(eth_buffer_base + n, ETH_BUFFER_FREE);
}
}
status = eth_create(netsvc, iovmo, iobuf, &eth);
if (status < 0) {
printf("eth_create() failed: %d\n", status);
goto fail_close_svc;
}
zx_status_t call_status = ZX_OK;
status = fuchsia_hardware_ethernet_DeviceStart(netsvc, &call_status);
if (status != ZX_OK || call_status != ZX_OK) {
printf("netifc: ethernet_start(): %d, %d\n", status, call_status);
goto fail_destroy_client;
}
ip6_init(netmac, netifc_open_quiet);
// enqueue rx buffers
for (unsigned n = 0; n < NET_BUFFERS; n++) {
void* data;
eth_buffer_t* ethbuf;
if (eth_get_buffer_locked(NET_BUFFERSZ, &data, &ethbuf, ETH_BUFFER_RX, false)) {
printf("netifc: only queued %u buffers (desired: %u)\n", n, NET_BUFFERS);
break;
}
eth_queue_rx(eth, ethbuf, ethbuf->data, NET_BUFFERSZ, 0);
}
mtx_unlock(&eth_lock);
if (!netifc_open_quiet)
printf("netsvc: using /dev/class/ethernet/%s\n", fn);
// stop polling
return ZX_ERR_STOP;
fail_destroy_client:
eth_destroy(eth);
eth = NULL;
fail_close_svc:
zx_handle_close(netsvc);
netsvc = ZX_HANDLE_INVALID;
finish:
mtx_unlock(&eth_lock);
return ZX_OK;
}
int netifc_open(const char* interface, bool quiet) {
netifc_open_quiet = quiet;
int dirfd;
if ((dirfd = open("/dev/class/ethernet", O_DIRECTORY|O_RDONLY)) < 0) {
return -1;
}
zx_status_t status =
fdio_watch_directory(dirfd, netifc_open_cb, ZX_TIME_INFINITE, (void*)interface);
close(dirfd);
// callback returns STOP if it finds and successfully
// opens a network interface
return (status == ZX_ERR_STOP) ? 0 : -1;
}
void netifc_close(void) {
mtx_lock(&eth_lock);
if (netsvc != ZX_HANDLE_INVALID) {
zx_handle_close(netsvc);
netsvc = ZX_HANDLE_INVALID;
}
if (eth != NULL) {
eth_destroy(eth);
eth = NULL;
}
unsigned count = 0;
for (unsigned n = 0; n < eth_buffer_count; n++) {
switch (eth_buffer_base[n].state) {
case ETH_BUFFER_FREE:
case ETH_BUFFER_CLIENT:
// on free list or owned by client
// leave it alone
break;
case ETH_BUFFER_TX:
case ETH_BUFFER_RX:
// was sitting in ioring. reclaim.
eth_put_buffer_locked(eth_buffer_base + n, eth_buffer_base[n].state);
count++;
break;
default:
printf("ethbuf %p: illegal state %u\n",
eth_buffer_base + n, eth_buffer_base[n].state);
__builtin_trap();
break;
}
}
printf("netifc: recovered %u buffers\n", count);
mtx_unlock(&eth_lock);
}
static void rx_complete(void* ctx, void* cookie, size_t len, uint32_t flags) {
eth_buffer_t* ethbuf = cookie;
check_ethbuf(ethbuf, ETH_BUFFER_RX);
netifc_recv(ethbuf->data, len);
eth_queue_rx(eth, ethbuf, ethbuf->data, NET_BUFFERSZ, 0);
}
int netifc_poll(void) {
for (;;) {
// Handle any completed rx packets
zx_status_t status;
if ((status = eth_complete_rx(eth, NULL, rx_complete)) < 0) {
printf("netifc: eth rx failed: %d\n", status);
return -1;
}
// Timeout passed
if (net_timer && zx_clock_get_monotonic() > net_timer) {
return 0;
}
if (netifc_send_pending()) {
continue;
}
zx_time_t deadline;
if (net_timer) {
deadline = zx_time_add_duration(net_timer, ZX_MSEC(1));
} else {
deadline = ZX_TIME_INFINITE;
}
status = eth_wait_rx(eth, deadline);
if ((status < 0) && (status != ZX_ERR_TIMED_OUT)) {
printf("netifc: eth rx wait failed: %d\n", status);
return -1;
}
}
}