| // 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 <printf.h> |
| #include <string.h> |
| #include <xefi.h> |
| |
| #include <efi/protocol/simple-network.h> |
| |
| #include "inet6.h" |
| #include "mdns.h" |
| #include "page_size.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; |
| } |
| |
| static void clear_tx_queue(void* marker) { |
| void* txbuf; |
| efi_status status; |
| do { |
| status = snp->GetStatus(snp, NULL, &txbuf); |
| } while (status == EFI_SUCCESS && txbuf != marker); |
| } |
| |
| 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 = snp->Transmit(snp, 0, len, data, NULL, NULL, NULL); |
| if (r == EFI_SUCCESS) { |
| clear_tx_queue(data); |
| } else if (r == EFI_NOT_READY) { |
| clear_tx_queue(NULL); |
| r = snp->Transmit(snp, 0, len, data, NULL, NULL, NULL); |
| clear_tx_queue(data); |
| } |
| |
| if (r != EFI_SUCCESS) { |
| printf("%s: failure: (%s)\n", __func__, xefi_strerror(r)); |
| } |
| |
| eth_put_buffer(data); |
| return (int)r; |
| } |
| |
| static 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) |
| |
| efi_status netifc_set_timer(uint32_t ms) { |
| if (net_timer == 0) { |
| return EFI_UNSUPPORTED; |
| } |
| 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 */ |
| static 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); |
| size_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; |
| } |
| 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, ð_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); } |