git: f70e4664a685 - main - stand: consume UEFI PXE Base Code cache to seed DHCP
Alexander Motin <[email protected]> Tue, 04 Aug 2026 01:04:32 +0000
| Newsgroups | gmane.os.freebsd.devel.cvs.src,gmane.os.freebsd.current.scm |
|---|---|
| Message-ID | <[email protected]> |
The branch main has been updated by mav: URL: https://cgit.FreeBSD.org/src/commit/?id=f70e4664a6854eca4ea73fb79befc3e6ba2dee1c commit f70e4664a6854eca4ea73fb79befc3e6ba2dee1c Author: Alexander Motin <[email protected]> AuthorDate: 2026-08-04 00:56:48 +0000 Commit: Alexander Motin <[email protected]> CommitDate: 2026-08-04 01:04:18 +0000 stand: consume UEFI PXE Base Code cache to seed DHCP Populate stand/libsa/bootp.c's bootp_response global from the UEFI PXE Base Code Protocol's cached DhcpAck, so bootp() can enter RFC 2131 INIT-REBOOT and skip DISCOVER/OFFER instead of running a fresh DHCP transaction after the firmware has already done one. --- stand/efi/libefi/efinet.c | 215 ++++++++++++++++++++++++++++++++++++++++++++++ stand/libsa/bootp.c | 8 +- 2 files changed, 216 insertions(+), 7 deletions(-) diff --git a/stand/efi/libefi/efinet.c b/stand/efi/libefi/efinet.c index 5c391d38a265..e91b5f73ddf2 100644 --- a/stand/efi/libefi/efinet.c +++ b/stand/efi/libefi/efinet.c @@ -33,14 +33,40 @@ #include <stand.h> #include <net.h> #include <netif.h> +#include <bootp.h> #include <efi.h> #include <efilib.h> #include <Protocol/SimpleNetwork.h> +#include <Protocol/PxeBaseCode.h> #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); @@ -198,6 +224,184 @@ efinet_get(struct iodesc *desc, void **pkt, time_t timeout) 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. + * Used to correlate a PXE Base Code Protocol handle back to an SNP + * unit when the two protocols live on different handles (typical for + * UEFI: PXE Base Code is installed on a child handle whose device + * path ends in Ipv4()/Ipv6(), extending the SNP's MAC-terminated path). + */ +static 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 @@ -285,6 +489,8 @@ efinet_init(struct iodesc *desc, void *machdep_hint) } 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", @@ -428,6 +634,15 @@ efinet_dev_init(void) 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); + efinet_dev.dv_cleanup = netdev.dv_cleanup; efinet_dev.dv_open = netdev.dv_open; efinet_dev.dv_close = netdev.dv_close; diff --git a/stand/libsa/bootp.c b/stand/libsa/bootp.c index 51f463d55521..927db86abdd8 100644 --- a/stand/libsa/bootp.c +++ b/stand/libsa/bootp.c @@ -280,13 +280,7 @@ restart: #endif myip = d->myip = rbootp->bp_yiaddr; - /* - * Preserve a pre-set servip (e.g. seeded by PXE-consuming code - * from a ProxyDHCP offer) when the ACK's siaddr is zero, which is - * typical for non-PXE-aware primary DHCP servers. - */ - if (rbootp->bp_siaddr.s_addr != INADDR_ANY) - servip = rbootp->bp_siaddr; + servip = rbootp->bp_siaddr; if (rootip.s_addr == INADDR_ANY) rootip = servip; bcopy(rbootp->bp_file, bootfile, sizeof(bootfile));