From b157c2179ac18eca1625400f26788aee97d605ce Mon Sep 17 00:00:00 2001 From: Alexander von Gluck IV Date: Mon, 25 Jan 2021 11:51:43 -0600 Subject: [PATCH] boot/platform/efi: Add network booting support Similar to open firmware implementation. This allows to load the kernel from a remote_disk_server. IP address is recovered using LoadOptions with a syntax similar to the Linux kernel network boot "ip" parameter. If this is not set, no network booting is attempted, in order to not slow down normal booting by waiting on network traffic. Change-Id: I17738bbcde85921672965b9936a2b484f1e57c6c Reviewed-on: https://review.haiku-os.org/c/haiku/+/3678 Reviewed-by: waddlesplash Reviewed-by: Adrien Destugues Tested-by: Commit checker robot --- .../platform/efi/protocol/device-path.h | 2 +- .../platform/efi/protocol/loaded-image.h | 3 + src/system/boot/loader/Jamfile | 26 +- src/system/boot/platform/efi/Jamfile | 1 + src/system/boot/platform/efi/devices.cpp | 18 + src/system/boot/platform/efi/network.cpp | 412 ++++++++++++++++++ 6 files changed, 452 insertions(+), 10 deletions(-) create mode 100644 src/system/boot/platform/efi/network.cpp diff --git a/headers/private/kernel/platform/efi/protocol/device-path.h b/headers/private/kernel/platform/efi/protocol/device-path.h index e31215e024..e91bbf6ec3 100644 --- a/headers/private/kernel/platform/efi/protocol/device-path.h +++ b/headers/private/kernel/platform/efi/protocol/device-path.h @@ -13,7 +13,7 @@ extern efi_guid DevicePathProtocol; typedef struct efi_device_path_protocol { uint8_t Type; uint8_t SubType; - uint8_t Length[2]; + uint16_t Length; } efi_device_path_protocol; #define DEVICE_PATH_HARDWARE 0x01 diff --git a/headers/private/kernel/platform/efi/protocol/loaded-image.h b/headers/private/kernel/platform/efi/protocol/loaded-image.h index 800d269107..d7091c8c5c 100644 --- a/headers/private/kernel/platform/efi/protocol/loaded-image.h +++ b/headers/private/kernel/platform/efi/protocol/loaded-image.h @@ -12,6 +12,9 @@ {0x5b1b31a1, 0x9562, 0x11d2, {0x8e, 0x3f, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b}} extern efi_guid LoadedImageProtocol; +#define EFI_LOADED_IMAGE_DEVICE_PATH_PROTOCOL_GUID \ + {0xbc62157e, 0x3e33, 0x4fec, {0x99, 0x20, 0x2d, 0x3b, 0x36, 0xd7, 0x50, 0xdf}} + #define EFI_LOADED_IMAGE_PROTOCOL_REVISION 0x1000 typedef struct { diff --git a/src/system/boot/loader/Jamfile b/src/system/boot/loader/Jamfile index 64b2fe3397..b0fed7f700 100644 --- a/src/system/boot/loader/Jamfile +++ b/src/system/boot/loader/Jamfile @@ -94,8 +94,8 @@ for platform in [ MultiBootSubDirSetup ] { } } - local kernelC++Header = [ FDirName $(HAIKU_TOP) headers private kernel util - kernel_cpp.h ] ; + local kernelC++Header = [ FDirName $(HAIKU_TOP) headers private + kernel util kernel_cpp.h ] ; SubDirC++Flags -fno-rtti -include $(kernelC++Header) ; } @@ -123,6 +123,7 @@ for platform in [ MultiBootSubDirSetup ] { driver_settings.cpp # utils + convertutf.cpp kernel_cpp.cpp KMessage.cpp list.cpp @@ -153,7 +154,8 @@ for platform in [ MultiBootSubDirSetup ] { ; # Tell Jam where to find the utility sources - SEARCH on [ FGristFiles kernel_cpp.cpp list.cpp ring_buffer.cpp StringHash.cpp ] + SEARCH on [ FGristFiles convertutf.cpp kernel_cpp.cpp list.cpp + ring_buffer.cpp StringHash.cpp ] = [ FDirName $(HAIKU_TOP) src system kernel util ] ; SEARCH on [ FGristFiles KMessage.cpp ] @@ -166,19 +168,25 @@ for platform in [ MultiBootSubDirSetup ] { = [ FDirName $(HAIKU_TOP) src system libroot os ] ; SEARCH on [ FGristFiles amiga_rdb.cpp ] - = [ FDirName $(HAIKU_TOP) src add-ons kernel partitioning_systems amiga ] ; + = [ FDirName $(HAIKU_TOP) src add-ons kernel partitioning_systems + amiga ] ; SEARCH on [ FGristFiles apple.cpp ] - = [ FDirName $(HAIKU_TOP) src add-ons kernel partitioning_systems apple ] ; + = [ FDirName $(HAIKU_TOP) src add-ons kernel partitioning_systems + apple ] ; SEARCH on [ FGristFiles gpt.cpp Header.cpp crc32.cpp utility.cpp ] - = [ FDirName $(HAIKU_TOP) src add-ons kernel partitioning_systems gpt ] ; + = [ FDirName $(HAIKU_TOP) src add-ons kernel partitioning_systems + gpt ] ; - SEARCH on [ FGristFiles intel.cpp PartitionMap.cpp PartitionMapParser.cpp ] - = [ FDirName $(HAIKU_TOP) src add-ons kernel partitioning_systems intel ] ; + SEARCH on [ FGristFiles intel.cpp PartitionMap.cpp + PartitionMapParser.cpp ] + = [ FDirName $(HAIKU_TOP) src add-ons kernel partitioning_systems + intel ] ; SEARCH on [ FGristFiles stage2_crt0.S ] - = [ FDirName $(HAIKU_TOP) src system boot arch $(TARGET_KERNEL_ARCH_DIR) ] ; + = [ FDirName $(HAIKU_TOP) src system boot arch + $(TARGET_KERNEL_ARCH_DIR) ] ; SEARCH on [ FGristFiles DataIO.cpp Referenceable.cpp ] = [ FDirName $(HAIKU_TOP) src kits support ] ; diff --git a/src/system/boot/platform/efi/Jamfile b/src/system/boot/platform/efi/Jamfile index 909473c340..98c63c9855 100644 --- a/src/system/boot/platform/efi/Jamfile +++ b/src/system/boot/platform/efi/Jamfile @@ -32,6 +32,7 @@ local platform_src = video.cpp debug.cpp mmu.cpp + network.cpp heap.cpp acpi.cpp timer.cpp diff --git a/src/system/boot/platform/efi/devices.cpp b/src/system/boot/platform/efi/devices.cpp index e52ef71280..88dff6398c 100644 --- a/src/system/boot/platform/efi/devices.cpp +++ b/src/system/boot/platform/efi/devices.cpp @@ -8,6 +8,9 @@ #include #include +#include +#include + #include "Header.h" #include "efi_platform.h" @@ -181,6 +184,20 @@ platform_add_boot_device(struct stage2_args *args, NodeList *devicesList) efi_block_io_protocol *blockIo; size_t memSize = 0; + // If the bootloader was started from the network (net_stack_init will find + // the ip= parameter in the LoadOptions), add network booting as the first + // entry if available + status_t error = net_stack_init(); + if (error != B_OK) { + TRACE("Can't init network...\n"); + } else { + TRACE("Network is initialized! Search for remote disk...\n"); + RemoteDisk *remoteDisk = RemoteDisk::FindAnyRemoteDisk(); + if (remoteDisk != NULL) { + devicesList->Add(remoteDisk); + } + } + // Read to zero sized buffer to get memory needed for handles if (kBootServices->LocateHandle(ByProtocol, &BlockIoGUID, 0, &memSize, 0) != EFI_BUFFER_TOO_SMALL) @@ -225,6 +242,7 @@ platform_add_boot_device(struct stage2_args *args, NodeList *devicesList) panic("Can't allocate memory for block devices!"); devicesList->Insert(device); } + return devicesList->Count() > 0 ? B_OK : B_ENTRY_NOT_FOUND; } diff --git a/src/system/boot/platform/efi/network.cpp b/src/system/boot/platform/efi/network.cpp new file mode 100644 index 0000000000..f7e566a64b --- /dev/null +++ b/src/system/boot/platform/efi/network.cpp @@ -0,0 +1,412 @@ +/* + * Copyright 2005, Ingo Weinhold . + * Copyright 2010, Andreas Faerber + * Copyright 2025, Adrien Destugues + * All rights reserved. Distributed under the terms of the MIT License. + */ + + +#include + +#include +#include + +#include + +#include + +#include +#include +#include +#include + +#include "efi_platform.h" +#include +#include + + +static efi_guid sNetworkProtocolGUID = EFI_SIMPLE_NETWORK_PROTOCOL_GUID; +static efi_guid sLoadedImageProtocolGUID = EFI_LOADED_IMAGE_PROTOCOL_GUID; + +#if 0 +static efi_guid sDevicePathProtocolGUID = EFI_DEVICE_PATH_PROTOCOL_GUID; +static efi_guid sLoadedImageDevicePathProtocolGUID = EFI_LOADED_IMAGE_DEVICE_PATH_PROTOCOL_GUID; +#endif + + +//#define TRACE_NETWORK +#ifdef TRACE_NETWORK +# define TRACE(x...) dprintf("efi/network: " x) +# define CALLED(x...) dprintf("efi/network: CALLED %s\n", __func__) +#else +# define TRACE(x...) ; +# define CALLED(x...) ; +#endif + +#define TRACE_ALWAYS(x...) dprintf("efi/network: " x) + + +class EFIEthernetInterface : public EthernetInterface { +public: + EFIEthernetInterface(); + virtual ~EFIEthernetInterface(); + + status_t Init(); + + virtual mac_addr_t MACAddress() const; + + virtual void * AllocateSendReceiveBuffer(size_t size); + virtual void FreeSendReceiveBuffer(void *buffer); + + virtual ssize_t Send(const void *buffer, size_t size); + virtual ssize_t Receive(void *buffer, size_t size); + +private: + status_t FindMACAddress(); + +private: + efi_simple_network_protocol* fNetwork; + mac_addr_t fMACAddress; +}; + + +#ifdef TRACE_NETWORK + +static void +hex_dump(const void *_data, int length) +{ + uint8 *data = (uint8*)_data; + for (int i = 0; i < length; i++) { + if (i % 4 == 0) { + if (i % 32 == 0) { + if (i != 0) + dprintf("\n"); + dprintf("%03x: ", i); + } else + dprintf(" "); + } + + dprintf("%02x", data[i]); + } + dprintf("\n"); +} + +#else // !TRACE_NETWORK + +#define hex_dump(data, length) + +#endif // !TRACE_NETWORK + + +// #pragma mark - + + +EFIEthernetInterface::EFIEthernetInterface() + : + EthernetInterface(), + fNetwork(NULL), + fMACAddress(kNoMACAddress) +{ +} + + +EFIEthernetInterface::~EFIEthernetInterface() +{ + if (fNetwork) { + fNetwork->Shutdown(fNetwork); + fNetwork->Stop(fNetwork); + } +} + + +status_t +EFIEthernetInterface::FindMACAddress() +{ + if (fNetwork == NULL) + return B_ERROR; + + memcpy(&fMACAddress, &fNetwork->Mode->CurrentAddress, + sizeof(fMACAddress)); + + if (fMACAddress == kNoMACAddress) { + memcpy(&fMACAddress, &fNetwork->Mode->PermanentAddress, + sizeof(fMACAddress)); + } + + if (fMACAddress == kNoMACAddress) + return B_ENTRY_NOT_FOUND; + + return B_OK; +} + + +status_t +EFIEthernetInterface::Init() +{ + CALLED(); + + efi_status status = kSystemTable->BootServices->LocateProtocol( + &sNetworkProtocolGUID, NULL, (void**)&fNetwork); + + if (status != EFI_SUCCESS || fNetwork == NULL) { + fNetwork = NULL; + TRACE_ALWAYS("EFI reports network unavaiable\n"); + return B_ERROR; + } + + TRACE_ALWAYS("EFI reports network avaiable\n"); + + // Attempt to locate IP address from command line arguments passed by + // the bootloader (for example, U-Boot bootargs variable) + // Recognize a subset of the ip= parameter as defined for Linux: + // https://www.kernel.org/doc/Documentation/filesystems/nfs/nfsroot.txt + efi_loaded_image_protocol* loadedImageProtocol; + status = kSystemTable->BootServices->LocateProtocol( + &sLoadedImageProtocolGUID, NULL, (void**)&loadedImageProtocol); + + if (status == EFI_SUCCESS) { + char buffer[256]; + utf16le_to_utf8((uint16*)loadedImageProtocol->LoadOptions, + loadedImageProtocol->LoadOptionsSize / 2, buffer, sizeof(buffer)); + TRACE("Load options: %s\n", buffer); + + char* base = buffer; + char* ptr = base; + while (*ptr != '\0') { + if (*ptr == '=') { + TRACE("Key at %ld: %s\n", ptr - base, base); + // Found a key-value separator, see if it's a known option + if (ptr - base != 2) + continue; + + if (strncmp(base, "ip", 2) == 0) { + TRACE("Found ip configuration in command line\n"); + break; + } + } + + // At any space, reset the base pointer to the start of the next + // key + if (*ptr == ' ') { + base = ptr + 1; + } + ptr++; + } + + if (*ptr != '=') { + TRACE("No keys found in load options\n"); + return B_ERROR; + } + + // Point after the = sign + ptr++; + + // Try to parse next part of the string as an IP address + // TODO also recognize autoconfig parameters (such as 'ip=dhcp') + TRACE("Found IP address from load options: %s\n", ptr); + ip_addr_t address = ip_parse_address(ptr); + SetIPAddress(address); + } + +#if 0 + // TODO u-boot does not provide the IP address or the TFTP server URL in + // "loaded image" and "device path" protocols. That would allow to detect + // network booting without having to specify the ip in bootargs. Enable + // this if the information is available when network booting with other + // EFI implementations or if U-Boot grows support for it. + bool bootFromNetwork = false; + + // Try the basic "device path" protocol + efi_device_path_protocol* devicePathProtocol; + status = kSystemTable->BootServices->LocateProtocol( + &sDevicePathProtocolGUID, NULL, (void**)&devicePathProtocol); + + if (status == EFI_SUCCESS) { + for (;;) { + TRACE("Now scanning: Type %d SubType %d\n", devicePathProtocol->Type, devicePathProtocol->SubType); + if (devicePathProtocol->Type == DEVICE_PATH_END + && devicePathProtocol->SubType == DEVICE_PATH_ENTIRE_END) { + break; + } + + if (devicePathProtocol->Type == DEVICE_PATH_MESSAGING + && devicePathProtocol->SubType == DEVICE_PATH_MESSAGING_MAC) { + bootFromNetwork = true; + } + + if (devicePathProtocol->Type == DEVICE_PATH_MEDIA + && devicePathProtocol->SubType == MEDIA_FILEPATH_DP) { + TRACE("Found device path, length %d: %s\n", devicePathProtocol->Length, ((char*)devicePathProtocol) + sizeof(efi_device_path_protocol)); + } + + if (devicePathProtocol->Type == DEVICE_PATH_MESSAGING + && devicePathProtocol->SubType == DEVICE_PATH_MESSAGING_IPV4) { + bootFromNetwork = true; + ip_addr_t* address = (ip_addr_t*)(devicePathProtocol + 1); + SetIPAddress(*address); + return B_OK; + } + + devicePathProtocol = (efi_device_path_protocol*)(((uint8_t*)devicePathProtocol) + devicePathProtocol->Length); + } + } else { + TRACE("No device path protocol\n"); + } + + TRACE_ALWAYS("IP address not found in device path protocol, try in loaded image\n"); + + // Try the "loaded image device path protocol" which provides more specific + // information + status = kSystemTable->BootServices->LocateProtocol( + &sLoadedImageDevicePathProtocolGUID, NULL, (void**)&devicePathProtocol); + + if (status == EFI_SUCCESS) { + for (;;) { + TRACE("Now scanning: Type %d SubType %d\n", devicePathProtocol->Type, devicePathProtocol->SubType); + if (devicePathProtocol->Type == DEVICE_PATH_END + && devicePathProtocol->SubType == DEVICE_PATH_ENTIRE_END) { + break; + } + + if (devicePathProtocol->Type == DEVICE_PATH_MESSAGING + && devicePathProtocol->SubType == DEVICE_PATH_MESSAGING_MAC) { + bootFromNetwork = true; + } + + if (devicePathProtocol->Type == DEVICE_PATH_MEDIA + && devicePathProtocol->SubType == MEDIA_FILEPATH_DP) { + TRACE("Found device path, length %d: %s\n", devicePathProtocol->Length, ((char*)devicePathProtocol) + sizeof(efi_device_path_protocol)); + } + + if (devicePathProtocol->Type == DEVICE_PATH_MESSAGING + && devicePathProtocol->SubType == DEVICE_PATH_MESSAGING_IPV4) { + bootFromNetwork = true; + ip_addr_t* address = (ip_addr_t*)(devicePathProtocol + 1); + SetIPAddress(*address); + return B_OK; + } + + devicePathProtocol = (efi_device_path_protocol*)(((uint8_t*)devicePathProtocol) + devicePathProtocol->Length); + } + } else { + TRACE("No loaded image device path protocol\n"); + } + + if (!bootFromNetwork) + return B_ENTRY_NOT_FOUND; +#endif + + status = fNetwork->Start(fNetwork); + + if (status != EFI_SUCCESS) { + TRACE_ALWAYS("error starting network\n"); + return B_ERROR; + } + + status = fNetwork->Initialize(fNetwork, 0, 0); + + if (status != EFI_SUCCESS) { + TRACE_ALWAYS("error initializing network\n"); + return B_ERROR; + } + + if (FindMACAddress() != B_OK) { + TRACE_ALWAYS("failed to get MAC address\n"); + return B_ERROR; + } + + return B_OK; +} + + +mac_addr_t +EFIEthernetInterface::MACAddress() const +{ + return fMACAddress; +} + + +void * +EFIEthernetInterface::AllocateSendReceiveBuffer(size_t size) +{ + return malloc(size); +} + + +void +EFIEthernetInterface::FreeSendReceiveBuffer(void *buffer) +{ + free(buffer); +} + + +ssize_t +EFIEthernetInterface::Send(const void *buffer, size_t size) +{ + TRACE("EFIEthernetInterface::Send(%p, %lu)\n", buffer, size); + + if (!buffer) + return B_BAD_VALUE; + + hex_dump(buffer, size); + + efi_status status = fNetwork->Transmit(fNetwork, 0, size, + const_cast(buffer), NULL, NULL, NULL); + + if (status != EFI_SUCCESS) { + TRACE("Send: Result is %lx\n", status); + return B_ERROR; + } + return size; +} + + +ssize_t +EFIEthernetInterface::Receive(void *buffer, size_t size) +{ + if (!buffer) + return B_BAD_VALUE; + + efi_status status = fNetwork->Receive(fNetwork, NULL, &size, buffer, + NULL, NULL, NULL); + + if (status == EFI_NOT_READY) + return B_WOULD_BLOCK; + + if (status != EFI_SUCCESS) { + TRACE("EFIEthernetInterface::Receive(%p, %lu): %ld\n", buffer, size, status); + return B_ERROR; + } + + hex_dump(buffer, size); + + return size; +} + + +// #pragma mark - + + +status_t +platform_net_stack_init() +{ + // create an EthernetInterface object for the device + EFIEthernetInterface *interface = new(nothrow) EFIEthernetInterface; + if (!interface) + return B_NO_MEMORY; + + status_t error = interface->Init(); + if (error != B_OK) { + delete interface; + return error; + } + + // add it to the net stack + error = NetStack::Default()->AddEthernetInterface(interface); + if (error != B_OK) { + delete interface; + return error; + } + + return B_OK; +}