blob: ef32239e2b99834da43b14184dfe2b1bdbc86c62 [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 "netifc.h"
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include <xefi.h>
#include <efi/protocol/simple-network.h>
#include "inet6.h"
#include "osboot.h"
static efi_simple_network_protocol* snp;
#define MAX_FILTER 8
static efi_mac_addr mcast_filters[MAX_FILTER];
static unsigned mcast_filter_count = 0;
// 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
#define NUM_BUFFER_PAGES 8
#define ETH_BUFFER_SIZE 1516
#define ETH_HEADER_SIZE 16
#define ETH_BUFFER_MAGIC 0x424201020304A7A7UL
typedef struct eth_buffer_t eth_buffer;
struct eth_buffer_t {
uint64_t magic;
eth_buffer* next;
uint8_t data[0];
};
static efi_physical_addr eth_buffers_base = 0;
static eth_buffer* eth_buffers = NULL;
static int num_eth_buffers = 0;
static int eth_buffers_avail = 0;
void* eth_get_buffer(size_t sz) {
eth_buffer* buf;
if (sz > ETH_BUFFER_SIZE) {
return NULL;
}
if (eth_buffers == NULL) {
return NULL;
}
buf = eth_buffers;
eth_buffers = buf->next;
buf->next = NULL;
eth_buffers_avail--;
return buf->data;
}
void eth_put_buffer(void* data) {
efi_physical_addr buf_paddr = (efi_physical_addr)data;
if ((buf_paddr < eth_buffers_base) ||
(buf_paddr >= (eth_buffers_base + (NUM_BUFFER_PAGES * PAGE_SIZE)))) {
printf("fatal: attempt to use buffer outside of allocated range\n");
for (;;)
;
}
eth_buffer* buf = (void*)(buf_paddr & (~2047));
if (buf->magic != ETH_BUFFER_MAGIC) {
printf("fatal: eth buffer %p (from %p) bad magic %" PRIx64 "\n", buf, data, buf->magic);
for (;;)
;
}
buf->next = eth_buffers;
eth_buffers_avail++;
eth_buffers = buf;
}
int eth_send(void* data, size_t len) {
#if DROP_PACKETS
txc++;
if ((random() % DROP_PACKETS) == 0) {
printf("tx drop %d\n", txc);
eth_put_buffer(data);
return 0;
}
#endif
efi_status r;
if ((r = snp->Transmit(snp, 0, len, (void*)data, NULL, NULL, NULL))) {
eth_put_buffer(data);
return -1;
} else {
return 0;
}
}
void eth_dump_status(void) {
#ifdef VERBOSE
printf("State/HwAdSz/HdrSz/MaxSz %d %d %d %d\n", snp->Mode->State, snp->Mode->HwAddressSize,
snp->Mode->MediaHeaderSize, snp->Mode->MaxPacketSize);
printf("RcvMask/RcvCfg/MaxMcast/NumMcast %d %d %d %d\n", snp->Mode->ReceiveFilterMask,
snp->Mode->ReceiveFilterSetting, snp->Mode->MaxMCastFilterCount,
snp->Mode->MCastFilterCount);
uint8_t* x = snp->Mode->CurrentAddress.addr;
printf("MacAddr %02x:%02x:%02x:%02x:%02x:%02x\n", x[0], x[1], x[2], x[3], x[4], x[5]);
printf("SetMac/MultiTx/LinkDetect/Link %d %d %d %d\n", snp->Mode->MacAddressChangeable,
snp->Mode->MultipleTxSupported, snp->Mode->MediaPresentSupported, snp->Mode->MediaPresent);
#endif
}
int eth_add_mcast_filter(const mac_addr* addr) {
if (mcast_filter_count >= MAX_FILTER)
return -1;
if (mcast_filter_count >= snp->Mode->MaxMCastFilterCount)
return -1;
memcpy(mcast_filters + mcast_filter_count, addr, ETH_ADDR_LEN);
mcast_filter_count++;
return 0;
}
static efi_event net_timer = NULL;
#define TIMER_MS(n) (((uint64_t)(n)) * 10000UL)
void netifc_set_timer(uint32_t ms) {
if (net_timer == 0) {
return;
}
gBS->SetTimer(net_timer, TimerRelative, TIMER_MS(ms));
}
int netifc_timer_expired(void) {
if (net_timer == 0) {
return 0;
}
if (gBS->CheckEvent(net_timer) == EFI_SUCCESS) {
return 1;
}
return 0;
}
/* Search the available network interfaces via SimpleNetworkProtocol handles
* and find the first valid one with a Link detected */
efi_simple_network_protocol* netifc_find_available(void) {
efi_boot_services* bs = gSys->BootServices;
efi_status ret;
efi_simple_network_protocol* cur_snp = NULL;
efi_handle handles[32];
char16_t* paths[32];
size_t nic_cnt = 0;
size_t sz = sizeof(handles);
uint32_t last_parent = 0;
uint32_t int_sts;
void* tx_buf;
/* Get the handles of all devices that provide SimpleNetworkProtocol interfaces */
ret = bs->LocateHandle(ByProtocol, &SimpleNetworkProtocol, NULL, &sz, handles);
if (ret != EFI_SUCCESS) {
printf("Failed to locate network interfaces (%s)\n", xefi_strerror(ret));
return NULL;
}
nic_cnt = sz / sizeof(efi_handle);
for (size_t i = 0; i < nic_cnt; i++) {
paths[i] = xefi_handle_to_str(handles[i]);
}
/* Iterate over our SNP list until we find one with an established link */
for (size_t i = 0; i < nic_cnt; i++) {
/* Check each interface once, but ignore any additional device paths a given interface
* may provide. e1000 tends to add a path for ipv4 and ipv6 configuration information
* for instance */
if (i != last_parent) {
if (memcmp(paths[i], paths[last_parent], strlen_16(paths[last_parent])) == 0) {
continue;
} else {
last_parent = i;
}
}
puts16(paths[i]);
printf(": ");
ret = bs->HandleProtocol(handles[i], &SimpleNetworkProtocol, (void**)&cur_snp);
if (ret) {
printf("Failed to open (%s)\n", xefi_strerror(ret));
continue;
}
/* If a driver is provided by the firmware then it should be started already, but check
* to make sure. This also covers the case where we're providing the AX88772 driver in-line
* during this boot itself */
ret = cur_snp->Start(cur_snp);
if (EFI_ERROR(ret) && ret != EFI_ALREADY_STARTED) {
printf("Failed to start (%s)", xefi_strerror(ret));
goto link_fail;
}
if (ret != EFI_ALREADY_STARTED) {
ret = cur_snp->Initialize(cur_snp, 0, 0);
if (EFI_ERROR(ret)) {
printf("Failed to initialize (%s)\n", xefi_strerror(ret));
goto link_fail;
}
}
/* Prod the driver to cache its current status. We don't need the status or buffer,
* but some drivers appear to require the OPTIONAL parameters. */
ret = cur_snp->GetStatus(cur_snp, &int_sts, &tx_buf);
if (EFI_ERROR(ret)) {
printf("Failed to read status (%s)\n", xefi_strerror(ret));
goto link_fail;
}
/* With status cached, do we have a Link detected on the netifc? */
if (!cur_snp->Mode->MediaPresent) {
printf("No link detected\n");
goto link_fail;
}
printf("Link detected!\n");
return cur_snp;
link_fail:
bs->CloseProtocol(handles[i], &SimpleNetworkProtocol, gImg, NULL);
cur_snp = NULL;
}
return NULL;
}
int netifc_open(void) {
efi_boot_services* bs = gSys->BootServices;
efi_status ret;
bs->CreateEvent(EVT_TIMER, TPL_CALLBACK, NULL, NULL, &net_timer);
snp = netifc_find_available();
if (!snp) {
printf("Failed to find a usable network interface\n");
return -1;
}
if (bs->AllocatePages(AllocateAnyPages, EfiLoaderData, NUM_BUFFER_PAGES, &eth_buffers_base)) {
printf("Failed to allocate net buffers\n");
return -1;
}
num_eth_buffers = NUM_BUFFER_PAGES * 2;
uint8_t* ptr = (void*)eth_buffers_base;
for (int i = 0; i < num_eth_buffers; ++i) {
eth_buffer* buf = (void*)ptr;
buf->magic = ETH_BUFFER_MAGIC;
eth_put_buffer(buf);
ptr += 2048;
}
ip6_init(snp->Mode->CurrentAddress.addr);
ret = snp->ReceiveFilters(
snp, EFI_SIMPLE_NETWORK_RECEIVE_UNICAST | EFI_SIMPLE_NETWORK_RECEIVE_MULTICAST, 0, 0,
mcast_filter_count, (void*)mcast_filters);
if (ret) {
printf("Failed to install multicast filters %s\n", xefi_strerror(ret));
return -1;
}
eth_dump_status();
if (snp->Mode->MCastFilterCount != mcast_filter_count) {
printf("OOPS: expected %d filters, found %d\n", mcast_filter_count,
snp->Mode->MCastFilterCount);
goto force_promisc;
}
for (size_t i = 0; i < mcast_filter_count; i++) {
// uint8_t *m = (void*) &mcast_filters[i];
// printf("i=%d %02x %02x %02x %02x %02x %02x\n", i, m[0], m[1], m[2], m[3], m[4], m[5]);
for (size_t j = 0; j < mcast_filter_count; j++) {
// m = (void*) &snp->Mode->MCastFilter[j];
// printf("j=%d %02x %02x %02x %02x %02x %02x\n", j, m[0], m[1], m[2], m[3], m[4], m[5]);
if (!memcmp(mcast_filters + i, &snp->Mode->MCastFilter[j], 6)) {
goto found_it;
}
}
printf("OOPS: filter #%zu missing\n", i);
goto force_promisc;
found_it:;
}
return 0;
force_promisc:
ret = snp->ReceiveFilters(snp,
EFI_SIMPLE_NETWORK_RECEIVE_UNICAST |
EFI_SIMPLE_NETWORK_RECEIVE_PROMISCUOUS |
EFI_SIMPLE_NETWORK_RECEIVE_PROMISCUOUS_MULTICAST,
0, 0, 0, NULL);
if (ret) {
printf("Failed to set promiscuous mode (%s)\n", xefi_strerror(ret));
return -1;
}
return 0;
}
void netifc_close(void) {
gBS->SetTimer(net_timer, TimerCancel, 0);
gBS->CloseEvent(net_timer);
snp->Shutdown(snp);
snp->Stop(snp);
}
int netifc_active(void) { return (snp != 0); }
void netifc_poll(void) {
uint8_t data[1514];
efi_status r;
size_t hsz, bsz;
uint32_t irq;
void* txdone;
if (eth_buffers_avail < num_eth_buffers) {
// Only check for completion if we have operations in progress.
// Otherwise, the result of GetStatus is unreliable. See ZX-759.
if ((r = snp->GetStatus(snp, &irq, &txdone))) {
return;
}
if (txdone) {
// Check to make sure this is one of our buffers (see ZX-1516)
efi_physical_addr buf_paddr = (efi_physical_addr)txdone;
if ((buf_paddr >= eth_buffers_base) &&
(buf_paddr < (eth_buffers_base + (NUM_BUFFER_PAGES * PAGE_SIZE)))) {
eth_put_buffer(txdone);
}
}
}
hsz = 0;
bsz = sizeof(data);
r = snp->Receive(snp, &hsz, &bsz, data, NULL, NULL, NULL);
if (r != EFI_SUCCESS) {
return;
}
#if DROP_PACKETS
rxc++;
if ((random() % DROP_PACKETS) == 0) {
printf("rx drop %d\n", rxc);
return;
}
#endif
#if TRACE
printf("RX %02x:%02x:%02x:%02x:%02x:%02x < %02x:%02x:%02x:%02x:%02x:%02x %02x%02x %d\n", data[0],
data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8], data[9], data[10],
data[11], data[12], data[13], (int)(bsz - hsz));
#endif
eth_recv(data, bsz);
}