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 <[email protected]>
Reviewed-by: Adrien Destugues <[email protected]>
Tested-by: Commit checker robot <[email protected]>
This commit is contained in:
Alexander von Gluck IV
2025-12-18 10:45:08 +00:00
committed by Adrien Destugues
parent ea58f1fc7c
commit b157c2179a
6 changed files with 452 additions and 10 deletions
@@ -13,7 +13,7 @@ extern efi_guid DevicePathProtocol;
typedef struct efi_device_path_protocol { typedef struct efi_device_path_protocol {
uint8_t Type; uint8_t Type;
uint8_t SubType; uint8_t SubType;
uint8_t Length[2]; uint16_t Length;
} efi_device_path_protocol; } efi_device_path_protocol;
#define DEVICE_PATH_HARDWARE 0x01 #define DEVICE_PATH_HARDWARE 0x01
@@ -12,6 +12,9 @@
{0x5b1b31a1, 0x9562, 0x11d2, {0x8e, 0x3f, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b}} {0x5b1b31a1, 0x9562, 0x11d2, {0x8e, 0x3f, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b}}
extern efi_guid LoadedImageProtocol; 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 #define EFI_LOADED_IMAGE_PROTOCOL_REVISION 0x1000
typedef struct { typedef struct {
+17 -9
View File
@@ -94,8 +94,8 @@ for platform in [ MultiBootSubDirSetup ] {
} }
} }
local kernelC++Header = [ FDirName $(HAIKU_TOP) headers private kernel util local kernelC++Header = [ FDirName $(HAIKU_TOP) headers private
kernel_cpp.h ] ; kernel util kernel_cpp.h ] ;
SubDirC++Flags -fno-rtti -include $(kernelC++Header) ; SubDirC++Flags -fno-rtti -include $(kernelC++Header) ;
} }
@@ -123,6 +123,7 @@ for platform in [ MultiBootSubDirSetup ] {
driver_settings.cpp driver_settings.cpp
# utils # utils
convertutf.cpp
kernel_cpp.cpp kernel_cpp.cpp
KMessage.cpp KMessage.cpp
list.cpp list.cpp
@@ -153,7 +154,8 @@ for platform in [ MultiBootSubDirSetup ] {
; ;
# Tell Jam where to find the utility sources # 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 ] ; = [ FDirName $(HAIKU_TOP) src system kernel util ] ;
SEARCH on [ FGristFiles KMessage.cpp ] SEARCH on [ FGristFiles KMessage.cpp ]
@@ -166,19 +168,25 @@ for platform in [ MultiBootSubDirSetup ] {
= [ FDirName $(HAIKU_TOP) src system libroot os ] ; = [ FDirName $(HAIKU_TOP) src system libroot os ] ;
SEARCH on [ FGristFiles amiga_rdb.cpp ] 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 ] 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 ] 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 ] SEARCH on [ FGristFiles intel.cpp PartitionMap.cpp
= [ FDirName $(HAIKU_TOP) src add-ons kernel partitioning_systems intel ] ; PartitionMapParser.cpp ]
= [ FDirName $(HAIKU_TOP) src add-ons kernel partitioning_systems
intel ] ;
SEARCH on [ FGristFiles stage2_crt0.S ] 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 ] SEARCH on [ FGristFiles DataIO.cpp Referenceable.cpp ]
= [ FDirName $(HAIKU_TOP) src kits support ] ; = [ FDirName $(HAIKU_TOP) src kits support ] ;
+1
View File
@@ -32,6 +32,7 @@ local platform_src =
video.cpp video.cpp
debug.cpp debug.cpp
mmu.cpp mmu.cpp
network.cpp
heap.cpp heap.cpp
acpi.cpp acpi.cpp
timer.cpp timer.cpp
+18
View File
@@ -8,6 +8,9 @@
#include <boot/platform.h> #include <boot/platform.h>
#include <boot/stage2.h> #include <boot/stage2.h>
#include <boot/net/NetStack.h>
#include <boot/net/RemoteDisk.h>
#include "Header.h" #include "Header.h"
#include "efi_platform.h" #include "efi_platform.h"
@@ -181,6 +184,20 @@ platform_add_boot_device(struct stage2_args *args, NodeList *devicesList)
efi_block_io_protocol *blockIo; efi_block_io_protocol *blockIo;
size_t memSize = 0; 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 // Read to zero sized buffer to get memory needed for handles
if (kBootServices->LocateHandle(ByProtocol, &BlockIoGUID, 0, &memSize, 0) if (kBootServices->LocateHandle(ByProtocol, &BlockIoGUID, 0, &memSize, 0)
!= EFI_BUFFER_TOO_SMALL) != 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!"); panic("Can't allocate memory for block devices!");
devicesList->Insert(device); devicesList->Insert(device);
} }
return devicesList->Count() > 0 ? B_OK : B_ENTRY_NOT_FOUND; return devicesList->Count() > 0 ? B_OK : B_ENTRY_NOT_FOUND;
} }
+412
View File
@@ -0,0 +1,412 @@
/*
* Copyright 2005, Ingo Weinhold <[email protected]>.
* Copyright 2010, Andreas Faerber <[email protected]>
* Copyright 2025, Adrien Destugues <[email protected]>
* All rights reserved. Distributed under the terms of the MIT License.
*/
#include <new>
#include <stdlib.h>
#include <string.h>
#include <OS.h>
#include <util/convertutf.h>
#include <boot/platform.h>
#include <boot/net/Ethernet.h>
#include <boot/net/IP.h>
#include <boot/net/NetStack.h>
#include "efi_platform.h"
#include <efi/protocol/simple-network.h>
#include <efi/protocol/loaded-image.h>
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<void*>(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;
}