/*- * Copyright (c) 2001 Doug Rabson * Copyright (c) 2002, 2006 Marcel Moolenaar * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "dev_net.h" static EFI_GUID sn_guid = EFI_SIMPLE_NETWORK_PROTOCOL_GUID; static EFI_GUID pxe_guid = EFI_PXE_BASE_CODE_PROTOCOL_GUID; /* * Snapshot of the UEFI PXE Base Code Protocol's cached DhcpAck taken * at device enumeration time, before efinet_probe() opens the Simple * Network Protocol with EFI_OPEN_PROTOCOL_EXCLUSIVE. The EXCLUSIVE * open forces the firmware to disconnect any driver holding the SNP * handle BY_DRIVER (including the UEFI PXE stack), which uninstalls * the PXE Base Code Protocol from that handle. We capture the ACK * here so it survives that disconnect. * * Per PXE 2.1, ProxyOffer's siaddr is the authoritative PXE boot * server and overrides any siaddr in the primary DhcpAck. We splice * it into dhcp_ack.Dhcpv4.BootpSiAddr at snapshot time, so downstream * only sees one merged packet — no need to store ProxyOffer separately. * * Indexed by netif unit; allocated in efi_pxe_snapshot_all(). */ struct pxe_cache_entry { bool valid; EFI_PXE_BASE_CODE_PACKET dhcp_ack; }; static struct pxe_cache_entry *pxe_cache; static void efinet_end(struct netif *); static ssize_t efinet_get(struct iodesc *, void **, time_t); static void efinet_init(struct iodesc *, void *); static int efinet_match(struct netif *, void *); static int efinet_probe(struct netif *, void *); static ssize_t efinet_put(struct iodesc *, void *, size_t); struct netif_driver efinetif = { .netif_bname = "efinet", .netif_match = efinet_match, .netif_probe = efinet_probe, .netif_init = efinet_init, .netif_get = efinet_get, .netif_put = efinet_put, .netif_end = efinet_end, .netif_ifs = NULL, .netif_nifs = 0 }; #ifdef EFINET_DEBUG static void dump_mode(EFI_SIMPLE_NETWORK_MODE *mode) { int i; printf("State = %x\n", mode->State); printf("HwAddressSize = %u\n", mode->HwAddressSize); printf("MediaHeaderSize = %u\n", mode->MediaHeaderSize); printf("MaxPacketSize = %u\n", mode->MaxPacketSize); printf("NvRamSize = %u\n", mode->NvRamSize); printf("NvRamAccessSize = %u\n", mode->NvRamAccessSize); printf("ReceiveFilterMask = %x\n", mode->ReceiveFilterMask); printf("ReceiveFilterSetting = %u\n", mode->ReceiveFilterSetting); printf("MaxMCastFilterCount = %u\n", mode->MaxMCastFilterCount); printf("MCastFilterCount = %u\n", mode->MCastFilterCount); printf("MCastFilter = {"); for (i = 0; i < mode->MCastFilterCount; i++) printf(" %s", ether_sprintf(mode->MCastFilter[i].Addr)); printf(" }\n"); printf("CurrentAddress = %s\n", ether_sprintf(mode->CurrentAddress.Addr)); printf("BroadcastAddress = %s\n", ether_sprintf(mode->BroadcastAddress.Addr)); printf("PermanentAddress = %s\n", ether_sprintf(mode->PermanentAddress.Addr)); printf("IfType = %u\n", mode->IfType); printf("MacAddressChangeable = %d\n", mode->MacAddressChangeable); printf("MultipleTxSupported = %d\n", mode->MultipleTxSupported); printf("MediaPresentSupported = %d\n", mode->MediaPresentSupported); printf("MediaPresent = %d\n", mode->MediaPresent); } #endif static int efinet_match(struct netif *nif, void *machdep_hint) { struct devdesc *dev = machdep_hint; if (dev->d_unit == nif->nif_unit) return (1); return(0); } static int efinet_probe(struct netif *nif, void *machdep_hint) { EFI_SIMPLE_NETWORK *net; EFI_HANDLE h; EFI_STATUS status; h = nif->nif_driver->netif_ifs[nif->nif_unit].dif_private; /* * Open the network device in exclusive mode. Without this * we will be racing with the UEFI network stack. It will * pull packets off the network leading to lost packets. */ status = BS->OpenProtocol(h, &sn_guid, (void **)&net, IH, NULL, EFI_OPEN_PROTOCOL_EXCLUSIVE); if (status != EFI_SUCCESS) { printf("Unable to open network interface %d for " "exclusive access: %lu\n", nif->nif_unit, DECODE_ERROR(status)); return (efi_status_to_errno(status)); } return (0); } static ssize_t efinet_put(struct iodesc *desc, void *pkt, size_t len) { struct netif *nif = desc->io_netif; EFI_SIMPLE_NETWORK *net; EFI_STATUS status; void *buf; net = nif->nif_devdata; if (net == NULL) return (-1); status = net->Transmit(net, 0, len, pkt, NULL, NULL, NULL); if (status != EFI_SUCCESS) return (-1); /* Wait for the buffer to be transmitted */ do { buf = NULL; /* XXX Is this needed? */ status = net->GetStatus(net, NULL, &buf); /* * XXX EFI1.1 and the E1000 card returns a different * address than we gave. Sigh. */ } while (status == EFI_SUCCESS && buf == NULL); /* XXX How do we deal with status != EFI_SUCCESS now? */ return ((status == EFI_SUCCESS) ? len : -1); } static ssize_t efinet_get(struct iodesc *desc, void **pkt, time_t timeout) { struct netif *nif = desc->io_netif; EFI_SIMPLE_NETWORK *net; EFI_STATUS status; UINTN bufsz; time_t t; char *buf, *ptr; ssize_t ret = -1; net = nif->nif_devdata; if (net == NULL) return (ret); bufsz = net->Mode->MaxPacketSize + ETHER_HDR_LEN + ETHER_CRC_LEN; buf = malloc(bufsz + ETHER_ALIGN); if (buf == NULL) return (ret); ptr = buf + ETHER_ALIGN; t = getsecs(); while ((getsecs() - t) < timeout) { status = net->Receive(net, NULL, &bufsz, ptr, NULL, NULL, NULL); if (status == EFI_SUCCESS) { *pkt = buf; ret = (ssize_t)bufsz; break; } if (status != EFI_NOT_READY) break; } if (ret == -1) free(buf); return (ret); } /* * Snapshot the UEFI PXE Base Code Protocol's cached DhcpAck for handle * h (which must carry EFI_PXE_BASE_CODE_PROTOCOL) into pce, splicing * ProxyOffer's siaddr into it if present (per PXE 2.1, ProxyOffer's * siaddr is the authoritative boot server and overrides DhcpAck's). * * Silently leaves pce->valid = false if the protocol is absent, the * PXE base code is not Started on this handle (i.e. it wasn't the * interface used to network-boot), or no DhcpAck was received. */ static void efi_pxe_snapshot(EFI_HANDLE h, struct pxe_cache_entry *pce) { EFI_PXE_BASE_CODE_PROTOCOL *pxe; EFI_PXE_BASE_CODE_MODE *mode; EFI_STATUS status; status = BS->OpenProtocol(h, &pxe_guid, (void **)&pxe, IH, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); if (EFI_ERROR(status)) return; mode = pxe->Mode; if (mode->Started && mode->DhcpAckReceived) { memcpy(&pce->dhcp_ack, &mode->DhcpAck, sizeof(pce->dhcp_ack)); if (mode->ProxyOfferReceived) { uint32_t proxy_siaddr = 0; memcpy(&proxy_siaddr, mode->ProxyOffer.Dhcpv4.BootpSiAddr, 4); if (proxy_siaddr != 0) memcpy(pce->dhcp_ack.Dhcpv4.BootpSiAddr, &proxy_siaddr, 4); } pce->valid = true; } BS->CloseProtocol(h, &pxe_guid, IH, NULL); } /* * Walk a device path looking for the first MSG_MAC_ADDR_DP node and * copy its 6-byte Ethernet MAC into out. Returns true on success, * e.g. false if dp has no NIC node at all. */ bool efi_devpath_get_mac(EFI_DEVICE_PATH *dp, uint8_t out[6]) { MAC_ADDR_DEVICE_PATH *mac; if (dp == NULL) return (false); while (!IsDevicePathEnd(dp)) { if (DevicePathType(dp) == MESSAGING_DEVICE_PATH && DevicePathSubType(dp) == MSG_MAC_ADDR_DP) { mac = (MAC_ADDR_DEVICE_PATH *)dp; memcpy(out, &mac->MacAddress, 6); return (true); } dp = NextDevicePathNode(dp); } return (false); } /* * Populate the pxe_cache[] array for all nifs SNP units. Runs during * efinet_dev_init() before any SNP is opened EXCLUSIVE, so the UEFI * PXE driver is still bound and its Mode data is intact. * * Per the UEFI 2.x network-stack architecture, EFI_PXE_BASE_CODE_PROTOCOL * lives on an upper-layer child handle whose device path extends the * SNP's MAC-terminated path with Ipv4()/Ipv6() nodes. Enumerate all * handles carrying PXE Base Code Protocol via LocateHandle() and match * each back to an SNP unit by MAC address extracted from the handle's * device path. This also transparently covers the legacy single-handle * case where PXE Base Code is installed on the SNP handle itself: * LocateHandle() returns that handle, whose device path ends in a MAC * node that trivially matches the same SNP unit. */ static void efi_pxe_snapshot_all(int nifs) { EFI_HANDLE *pxe_handles; EFI_STATUS status; UINTN sz; int i, j, npxe; sz = 0; status = BS->LocateHandle(ByProtocol, &pxe_guid, NULL, &sz, NULL); if (status != EFI_BUFFER_TOO_SMALL) return; pxe_handles = malloc(sz); if (pxe_handles == NULL) return; status = BS->LocateHandle(ByProtocol, &pxe_guid, NULL, &sz, pxe_handles); if (EFI_ERROR(status)) { free(pxe_handles); return; } npxe = sz / sizeof(EFI_HANDLE); /* * PXE is present on this system; allocate the per-unit cache * only now. Non-PXE UEFI boots pay nothing for this feature. */ pxe_cache = calloc(nifs, sizeof(struct pxe_cache_entry)); if (pxe_cache == NULL) { free(pxe_handles); return; } for (i = 0; i < npxe; i++) { EFI_DEVICE_PATH *dp; uint8_t pxe_mac[6]; dp = efi_lookup_devpath(pxe_handles[i]); if (!efi_devpath_get_mac(dp, pxe_mac)) continue; for (j = 0; j < nifs; j++) { EFI_DEVICE_PATH *snp_dp, *node; MAC_ADDR_DEVICE_PATH *snp_mac; if (pxe_cache[j].valid) continue; snp_dp = efi_lookup_devpath( efinetif.netif_ifs[j].dif_private); if ((node = efi_devpath_last_node(snp_dp)) == NULL) continue; snp_mac = (MAC_ADDR_DEVICE_PATH *)node; if (memcmp(&snp_mac->MacAddress, pxe_mac, 6) != 0) continue; efi_pxe_snapshot(pxe_handles[i], &pxe_cache[j]); break; } } free(pxe_handles); } /* * Publish the UEFI PXE Base Code snapshot to the shared bootp_response * global so that the loader's DHCP client (stand/libsa/bootp.c) can * pick it up and enter RFC 2131 INIT-REBOOT instead of running a full * DISCOVER/OFFER/REQUEST/ACK cycle. Also seed servip from the * ProxyOffer's siaddr, which is the authoritative PXE boot server on * setups where the primary DHCP is not PXE-aware (its DhcpAck siaddr * is zero). bootp()'s post-processing preserves this pre-set servip * when the INIT-REBOOT ACK arrives with siaddr == 0. */ static void efi_pxe_publish_cache(int unit) { const struct pxe_cache_entry *pce; if (pxe_cache == NULL) return; pce = &pxe_cache[unit]; if (!pce->valid) return; free(bootp_response); bootp_response_size = 0; bootp_response = malloc(sizeof(pce->dhcp_ack)); if (bootp_response == NULL) return; memcpy(bootp_response, &pce->dhcp_ack, sizeof(pce->dhcp_ack)); bootp_response_size = sizeof(pce->dhcp_ack); DEBUG_PRINTF(1, ("%s: bootp_response=%p size=%zu\n", __func__, bootp_response, bootp_response_size)); } /* * Loader uses BOOTP/DHCP and also uses RARP as a fallback to populate * network parameters and problems with DHCP servers can cause the loader * to fail to populate them. Allow the device to ask about the basic * network parameters and if present use them. */ static void efi_env_net_params(struct iodesc *desc) { char *envstr; in_addr_t ipaddr, mask, gwaddr, serveraddr; n_long rootaddr; if ((envstr = getenv("rootpath")) != NULL) strlcpy(rootpath, envstr, sizeof(rootpath)); /* * Get network parameters. */ envstr = getenv("ipaddr"); ipaddr = (envstr != NULL) ? inet_addr(envstr) : 0; envstr = getenv("netmask"); mask = (envstr != NULL) ? inet_addr(envstr) : 0; envstr = getenv("gatewayip"); gwaddr = (envstr != NULL) ? inet_addr(envstr) : 0; envstr = getenv("serverip"); serveraddr = (envstr != NULL) ? inet_addr(envstr) : 0; /* No network params. */ if (ipaddr == 0 && mask == 0 && gwaddr == 0 && serveraddr == 0) return; /* Partial network params. */ if (ipaddr == 0 || mask == 0 || gwaddr == 0 || serveraddr == 0) { printf("Incomplete network settings from U-Boot\n"); return; } /* * Set network parameters. */ myip.s_addr = ipaddr; netmask = mask; gateip.s_addr = gwaddr; servip.s_addr = serveraddr; /* * There must be a rootpath. It may be ip:/path or it may be just the * path in which case the ip needs to be serverip. */ #ifdef LOADER_NET_SUPPORT rootaddr = net_parse_rootpath(); if (rootaddr == INADDR_NONE) rootaddr = serveraddr; #else rootaddr = serveraddr; #endif rootip.s_addr = rootaddr; #ifdef EFINET_DEBUG printf("%s: proto=%d\n", __func__, netproto); printf("%s: ip=%s\n", __func__, inet_ntoa(myip)); printf("%s: mask=%s\n", __func__, intoa(netmask)); printf("%s: gateway=%s\n", __func__, inet_ntoa(gateip)); printf("%s: server=%s\n", __func__, inet_ntoa(servip)); #endif desc->myip = myip; } static void efinet_init(struct iodesc *desc, void *machdep_hint) { struct netif *nif = desc->io_netif; EFI_SIMPLE_NETWORK *net; EFI_HANDLE h; EFI_STATUS status; UINT32 mask; /* Attempt to get netboot params from env */ efi_env_net_params(desc); if (nif->nif_driver->netif_ifs[nif->nif_unit].dif_unit < 0) { printf("Invalid network interface %d\n", nif->nif_unit); return; } h = nif->nif_driver->netif_ifs[nif->nif_unit].dif_private; if (rootip.s_addr == 0) efi_pxe_publish_cache(nif->nif_unit); status = OpenProtocolByHandle(h, &sn_guid, (void **)&nif->nif_devdata); if (status != EFI_SUCCESS) { printf("net%d: cannot fetch interface data (status=%lu)\n", nif->nif_unit, DECODE_ERROR(status)); return; } net = nif->nif_devdata; if (net->Mode->State == EfiSimpleNetworkStopped) { status = net->Start(net); if (status != EFI_SUCCESS) { printf("net%d: cannot start interface (status=%lu)\n", nif->nif_unit, DECODE_ERROR(status)); return; } } if (net->Mode->State != EfiSimpleNetworkInitialized) { status = net->Initialize(net, 0, 0); if (status != EFI_SUCCESS) { printf("net%d: cannot init. interface (status=%lu)\n", nif->nif_unit, DECODE_ERROR(status)); return; } } mask = EFI_SIMPLE_NETWORK_RECEIVE_UNICAST | EFI_SIMPLE_NETWORK_RECEIVE_BROADCAST; status = net->ReceiveFilters(net, mask, 0, FALSE, 0, NULL); if (status != EFI_SUCCESS) printf("net%d: cannot set rx. filters (status=%lu)\n", nif->nif_unit, DECODE_ERROR(status)); #ifdef EFINET_DEBUG dump_mode(net->Mode); #endif bcopy(net->Mode->CurrentAddress.Addr, desc->myea, 6); } static void efinet_end(struct netif *nif) { EFI_SIMPLE_NETWORK *net = nif->nif_devdata; EFI_HANDLE h; if (net == NULL) return; net->Shutdown(net); h = nif->nif_driver->netif_ifs[nif->nif_unit].dif_private; BS->CloseProtocol(h, &sn_guid, IH, NULL); nif->nif_devdata = NULL; } static int efinet_dev_init(void); static int efinet_dev_print(int); struct devsw efinet_dev = { .dv_name = "net", .dv_type = DEVT_NET, .dv_init = efinet_dev_init, .dv_strategy = NULL, /* Will be set in efinet_dev_init */ .dv_open = NULL, /* Will be set in efinet_dev_init */ .dv_close = NULL, /* Will be set in efinet_dev_init */ .dv_ioctl = noioctl, .dv_print = efinet_dev_print, .dv_cleanup = nullsys, }; static int efinet_dev_init(void) { struct netif_dif *dif; struct netif_stats *stats; EFI_DEVICE_PATH *devpath, *node; EFI_HANDLE *handles, *handles2; EFI_STATUS status; UINTN sz; int err, i, nifs; extern struct devsw netdev; /* * Advertise our DHCP Client-System Architecture (RFC 4578). Many * x86_64 UEFI firmwares report themselves as "EFI BC" (0x0007) * rather than "EFI x86-64" (0x0009); match that convention for * best DHCP-server config compatibility. */ #if defined(__amd64__) bootp_client_arch = 0x0007; /* EFI BC */ #elif defined(__i386__) bootp_client_arch = 0x0006; /* EFI IA32 */ #elif defined(__aarch64__) bootp_client_arch = 0x000b; /* EFI 64-bit ARM */ #elif defined(__arm__) bootp_client_arch = 0x000a; /* EFI 32-bit ARM */ #elif defined(__riscv) bootp_client_arch = 0x001b; /* EFI 64-bit RISC-V */ #endif sz = 0; handles = NULL; status = BS->LocateHandle(ByProtocol, &sn_guid, NULL, &sz, NULL); if (status == EFI_BUFFER_TOO_SMALL) { handles = (EFI_HANDLE *)malloc(sz); if (handles == NULL) return (ENOMEM); status = BS->LocateHandle(ByProtocol, &sn_guid, NULL, &sz, handles); if (EFI_ERROR(status)) free(handles); } if (EFI_ERROR(status)) return (efi_status_to_errno(status)); handles2 = (EFI_HANDLE *)malloc(sz); if (handles2 == NULL) { free(handles); return (ENOMEM); } nifs = 0; for (i = 0; i < sz / sizeof(EFI_HANDLE); i++) { devpath = efi_lookup_devpath(handles[i]); if (devpath == NULL) continue; if ((node = efi_devpath_last_node(devpath)) == NULL) continue; if (DevicePathType(node) != MESSAGING_DEVICE_PATH || DevicePathSubType(node) != MSG_MAC_ADDR_DP) continue; handles2[nifs] = handles[i]; nifs++; } free(handles); if (nifs == 0) { err = ENOENT; goto done; } err = efi_register_handles(&efinet_dev, handles2, NULL, nifs); if (err != 0) goto done; efinetif.netif_ifs = calloc(nifs, sizeof(struct netif_dif)); stats = calloc(nifs, sizeof(struct netif_stats)); if (efinetif.netif_ifs == NULL || stats == NULL) { free(efinetif.netif_ifs); free(stats); efinetif.netif_ifs = NULL; err = ENOMEM; goto done; } efinetif.netif_nifs = nifs; for (i = 0; i < nifs; i++) { dif = &efinetif.netif_ifs[i]; dif->dif_unit = i; dif->dif_nsel = 1; dif->dif_stats = &stats[i]; dif->dif_private = handles2[i]; } /* * Snapshot the PXE Base Code cache now, before any code path * opens the SNP with EFI_OPEN_PROTOCOL_EXCLUSIVE (efinet_probe()). * An EXCLUSIVE open causes the firmware to disconnect the UEFI * PXE driver, which uninstalls the PXE Base Code Protocol and * loses the cached DhcpAck/ProxyOffer packets. */ efi_pxe_snapshot_all(nifs); #ifdef LOADER_NET_SUPPORT efinet_dev.dv_cleanup = netdev.dv_cleanup; efinet_dev.dv_open = netdev.dv_open; efinet_dev.dv_close = netdev.dv_close; efinet_dev.dv_strategy = netdev.dv_strategy; #endif done: free(handles2); return (err); } static int efinet_dev_print(int verbose) { CHAR16 *text; EFI_HANDLE h; int unit, ret = 0; printf("%s devices:", efinet_dev.dv_name); if ((ret = pager_output("\n")) != 0) return (ret); for (unit = 0, h = efi_find_handle(&efinet_dev, 0); h != NULL; h = efi_find_handle(&efinet_dev, ++unit)) { printf(" %s%d:", efinet_dev.dv_name, unit); if (verbose) { text = efi_devpath_name(efi_lookup_devpath(h)); if (text != NULL) { printf(" %S", text); efi_free_devpath_name(text); } } if ((ret = pager_output("\n")) != 0) break; } return (ret); }