From 1ab4d3079d29e9ebee0c85f1aec14a3b1df8f679 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Wed, 13 Mar 2024 15:08:10 +0000 Subject: [mp] Define an API for multiprocessor functions Define an API for executing very limited functions on application processors in a multiprocessor system, along with an x86-only implementation. The normal iPXE runtime environment is effectively non-existent on application processors. There is no ability to make firmware calls (e.g. to write to a console), and there may be no stack space available. Signed-off-by: Michael Brown --- src/include/ipxe/mp.h | 154 +++++++++++++++++++++++++++++++++++++++++++++ src/include/ipxe/null_mp.h | 36 +++++++++++ 2 files changed, 190 insertions(+) create mode 100644 src/include/ipxe/mp.h create mode 100644 src/include/ipxe/null_mp.h (limited to 'src/include') diff --git a/src/include/ipxe/mp.h b/src/include/ipxe/mp.h new file mode 100644 index 000000000..a36574909 --- /dev/null +++ b/src/include/ipxe/mp.h @@ -0,0 +1,154 @@ +#ifndef _IPXE_MP_H +#define _IPXE_MP_H + +/** @file + * + * Multiprocessor functions + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include +#include +#include + +/** + * An address within the address space for a multiprocessor function + * + * Application processors may be started in a different address space + * from the normal iPXE runtime environment. For example: under + * legacy BIOS the application processors will use flat 32-bit + * physical addressing (with no paging or virtual address offset). + */ +typedef unsigned long mp_addr_t; + +/** A multiprocessor function + * + * @v opaque Opaque data pointer + * @v cpuid CPU identifier + * + * iPXE does not set up a normal multiprocessor environment. In + * particular, there is no support for dispatching code to individual + * processors and there is no per-CPU stack allocation. + * + * Multiprocessor code must be prepared to run wth no stack space (and + * with a zero stack pointer). Functions may use the CPU identifier + * to construct a pointer to per-CPU result storage. + * + * Multiprocessor functions are permitted to overwrite all registers + * apart from the stack pointer. On exit, the function should check + * the stack pointer value: if zero then the function should halt the + * CPU, if non-zero then the function should return in the normal way. + * + * Multiprocessor functions do not have access to any capabilities + * typically provided by the firmware: they cannot, for example, write + * any console output. + * + * All parameters are passed in registers, since there may be no stack + * available. These functions cannot be called directly from C code. + */ +typedef void ( mp_func_t ) ( mp_addr_t opaque, unsigned int cpuid ); + +/** + * Call a multiprocessor function from C code on the current CPU + * + * @v func Multiprocessor function + * @v opaque Opaque data pointer + * + * This function must be provided for each CPU architecture to bridge + * the normal C ABI to the iPXE multiprocessor function ABI. It must + * therefore preserve any necessary registers, determine the CPU + * identifier, call the multiprocessor function (which may destroy any + * registers other than the stack pointer), restore registers, and + * return to the C caller. + * + * This function must be called from within the multiprocessor address + * space (e.g. with flat 32-bit physical addressing for BIOS). It can + * be called directly from C code if the multiprocessor address space + * is identical to the address space used for C code (e.g. under EFI, + * where everything uses flat physical addresses). + */ +extern void __asmcall mp_call ( mp_addr_t func, mp_addr_t opaque ); + +/** + * Calculate static inline multiprocessor API function name + * + * @v _prefix Subsystem prefix + * @v _api_func API function + * @ret _subsys_func Subsystem API function + */ +#define MPAPI_INLINE( _subsys, _api_func ) \ + SINGLE_API_INLINE ( MPAPI_PREFIX_ ## _subsys, _api_func ) + +/** + * Provide a multiprocessor API implementation + * + * @v _prefix Subsystem prefix + * @v _api_func API function + * @v _func Implementing function + */ +#define PROVIDE_MPAPI( _subsys, _api_func, _func ) \ + PROVIDE_SINGLE_API ( MPAPI_PREFIX_ ## _subsys, _api_func, _func ) + +/** + * Provide a static inline multiprocessor API implementation + * + * @v _prefix Subsystem prefix + * @v _api_func API function + */ +#define PROVIDE_MPAPI_INLINE( _subsys, _api_func ) \ + PROVIDE_SINGLE_API_INLINE ( MPAPI_PREFIX_ ## _subsys, _api_func ) + +/* Include all architecture-independent multiprocessor API headers */ +#include + +/* Include all architecture-dependent multiprocessor API headers */ +#include + +/** + * Calculate address as seen by a multiprocessor function + * + * @v address Address in normal iPXE address space + * @ret address Address in application processor address space + */ +mp_addr_t mp_address ( void *address ); + +/** + * Execute a multiprocessor function on the boot processor + * + * @v func Multiprocessor function + * @v opaque Opaque data pointer + * + * This is a blocking operation: the call will return only when the + * multiprocessor function exits. + */ +void mp_exec_boot ( mp_func_t func, void *opaque ); + +/** + * Start a multiprocessor function on all application processors + * + * @v func Multiprocessor function + * @v opaque Opaque data pointer + * + * This is a non-blocking operation: it is the caller's responsibility + * to provide a way to determine when the multiprocessor function has + * finished executing and halted its CPU. + */ +void mp_start_all ( mp_func_t func, void *opaque ); + +/** + * Update maximum observed CPU identifier + * + * @v opaque Opaque data pointer + * @v cpuid CPU identifier + * + * This may be invoked on each processor to update a shared maximum + * CPU identifier value. + */ +extern mp_func_t mp_update_max_cpuid; + +extern unsigned int mp_boot_cpuid ( void ); +extern unsigned int mp_max_cpuid ( void ); + +#endif /* _IPXE_MP_H */ diff --git a/src/include/ipxe/null_mp.h b/src/include/ipxe/null_mp.h new file mode 100644 index 000000000..f89da5d96 --- /dev/null +++ b/src/include/ipxe/null_mp.h @@ -0,0 +1,36 @@ +#ifndef _IPXE_NULL_MP_H +#define _IPXE_NULL_MP_H + +/** @file + * + * Null multiprocessor API implementation + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#ifdef MPAPI_NULL +#define MPAPI_PREFIX_null +#else +#define MPAPI_PREFIX_null __null_ +#endif + +static inline __attribute__ (( always_inline )) mp_addr_t +MPAPI_INLINE ( null, mp_address ) ( void *address ) { + + return ( ( mp_addr_t ) address ); +} + +static inline __attribute__ (( always_inline )) void +MPAPI_INLINE ( null, mp_exec_boot ) ( mp_func_t func __unused, + void *opaque __unused ) { + /* Do nothing */ +} + +static inline __attribute__ (( always_inline )) void +MPAPI_INLINE ( null, mp_start_all ) ( mp_func_t func __unused, + void *opaque __unused ) { + /* Do nothing */ +} + +#endif /* _IPXE_NULL_MP_H */ -- cgit v1.2.3-55-g7522 From 89bb926a041b03c3926bf21266cbdf735d9aee66 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Wed, 13 Mar 2024 15:16:47 +0000 Subject: [efi] Provide a multiprocessor API for EFI Provide an implementation of the iPXE multiprocessor API for EFI, based on using EFI_MP_SERVICES to start up a wrapper function on all application processors. Note that the processor numbers used by EFI_MP_SERVICES are opaque integers that bear no relation to the underlying CPU identity (e.g. the APIC ID), and so we must rely on our own (architecture- specific) implementation to determine the relevant CPU identifiers. Signed-off-by: Michael Brown --- src/config/defaults/efi.h | 2 +- src/include/ipxe/efi/Protocol/MpService.h | 676 ++++++++++++++++++++++++++++++ src/include/ipxe/efi/efi_mp.h | 30 ++ src/include/ipxe/errfile.h | 1 + src/include/ipxe/mp.h | 1 + src/interface/efi/efi_mp.c | 112 +++++ 6 files changed, 821 insertions(+), 1 deletion(-) create mode 100644 src/include/ipxe/efi/Protocol/MpService.h create mode 100644 src/include/ipxe/efi/efi_mp.h create mode 100644 src/interface/efi/efi_mp.c (limited to 'src/include') diff --git a/src/config/defaults/efi.h b/src/config/defaults/efi.h index 9c0f238ab..b62ddb46a 100644 --- a/src/config/defaults/efi.h +++ b/src/config/defaults/efi.h @@ -25,7 +25,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define REBOOT_EFI #define ACPI_EFI #define FDT_EFI -#define MPAPI_NULL +#define MPAPI_EFI #define NET_PROTO_IPV6 /* IPv6 protocol */ #define NET_PROTO_LLDP /* Link Layer Discovery protocol */ diff --git a/src/include/ipxe/efi/Protocol/MpService.h b/src/include/ipxe/efi/Protocol/MpService.h new file mode 100644 index 000000000..cd1bb27fe --- /dev/null +++ b/src/include/ipxe/efi/Protocol/MpService.h @@ -0,0 +1,676 @@ +/** @file + When installed, the MP Services Protocol produces a collection of services + that are needed for MP management. + + The MP Services Protocol provides a generalized way of performing following tasks: + - Retrieving information of multi-processor environment and MP-related status of + specific processors. + - Dispatching user-provided function to APs. + - Maintain MP-related processor status. + + The MP Services Protocol must be produced on any system with more than one logical + processor. + + The Protocol is available only during boot time. + + MP Services Protocol is hardware-independent. Most of the logic of this protocol + is architecturally neutral. It abstracts the multi-processor environment and + status of processors, and provides interfaces to retrieve information, maintain, + and dispatch. + + MP Services Protocol may be consumed by ACPI module. The ACPI module may use this + protocol to retrieve data that are needed for an MP platform and report them to OS. + MP Services Protocol may also be used to program and configure processors, such + as MTRR synchronization for memory space attributes setting in DXE Services. + MP Services Protocol may be used by non-CPU DXE drivers to speed up platform boot + by taking advantage of the processing capabilities of the APs, for example, using + APs to help test system memory in parallel with other device initialization. + Diagnostics applications may also use this protocol for multi-processor. + +Copyright (c) 2006 - 2017, Intel Corporation. All rights reserved.
+SPDX-License-Identifier: BSD-2-Clause-Patent + + @par Revision Reference: + This Protocol is defined in the UEFI Platform Initialization Specification 1.2, + Volume 2:Driver Execution Environment Core Interface. + +**/ + +#ifndef _MP_SERVICE_PROTOCOL_H_ +#define _MP_SERVICE_PROTOCOL_H_ + +FILE_LICENCE ( BSD2_PATENT ); + +/// +/// Global ID for the EFI_MP_SERVICES_PROTOCOL. +/// +#define EFI_MP_SERVICES_PROTOCOL_GUID \ + { \ + 0x3fdda605, 0xa76e, 0x4f46, {0xad, 0x29, 0x12, 0xf4, 0x53, 0x1b, 0x3d, 0x08} \ + } + +/// +/// Value used in the NumberProcessors parameter of the GetProcessorInfo function +/// +#define CPU_V2_EXTENDED_TOPOLOGY BIT24 + +/// +/// Forward declaration for the EFI_MP_SERVICES_PROTOCOL. +/// +typedef struct _EFI_MP_SERVICES_PROTOCOL EFI_MP_SERVICES_PROTOCOL; + +/// +/// Terminator for a list of failed CPUs returned by StartAllAPs(). +/// +#define END_OF_CPU_LIST 0xffffffff + +/// +/// This bit is used in the StatusFlag field of EFI_PROCESSOR_INFORMATION and +/// indicates whether the processor is playing the role of BSP. If the bit is 1, +/// then the processor is BSP. Otherwise, it is AP. +/// +#define PROCESSOR_AS_BSP_BIT 0x00000001 + +/// +/// This bit is used in the StatusFlag field of EFI_PROCESSOR_INFORMATION and +/// indicates whether the processor is enabled. If the bit is 1, then the +/// processor is enabled. Otherwise, it is disabled. +/// +#define PROCESSOR_ENABLED_BIT 0x00000002 + +/// +/// This bit is used in the StatusFlag field of EFI_PROCESSOR_INFORMATION and +/// indicates whether the processor is healthy. If the bit is 1, then the +/// processor is healthy. Otherwise, some fault has been detected for the processor. +/// +#define PROCESSOR_HEALTH_STATUS_BIT 0x00000004 + +/// +/// Structure that describes the pyhiscal location of a logical CPU. +/// +typedef struct { + /// + /// Zero-based physical package number that identifies the cartridge of the processor. + /// + UINT32 Package; + /// + /// Zero-based physical core number within package of the processor. + /// + UINT32 Core; + /// + /// Zero-based logical thread number within core of the processor. + /// + UINT32 Thread; +} EFI_CPU_PHYSICAL_LOCATION; + +/// +/// Structure that defines the 6-level physical location of the processor +/// +typedef struct { + /// + /// Package Zero-based physical package number that identifies the cartridge of the processor. + /// + UINT32 Package; + /// + /// Module Zero-based physical module number within package of the processor. + /// + UINT32 Module; + /// + /// Tile Zero-based physical tile number within module of the processor. + /// + UINT32 Tile; + /// + /// Die Zero-based physical die number within tile of the processor. + /// + UINT32 Die; + /// + /// Core Zero-based physical core number within die of the processor. + /// + UINT32 Core; + /// + /// Thread Zero-based logical thread number within core of the processor. + /// + UINT32 Thread; +} EFI_CPU_PHYSICAL_LOCATION2; + +typedef union { + /// The 6-level physical location of the processor, including the + /// physical package number that identifies the cartridge, the physical + /// module number within package, the physical tile number within the module, + /// the physical die number within the tile, the physical core number within + /// package, and logical thread number within core. + EFI_CPU_PHYSICAL_LOCATION2 Location2; +} EXTENDED_PROCESSOR_INFORMATION; + +/// +/// Structure that describes information about a logical CPU. +/// +typedef struct { + /// + /// The unique processor ID determined by system hardware. For IA32 and X64, + /// the processor ID is the same as the Local APIC ID. Only the lower 8 bits + /// are used, and higher bits are reserved. For IPF, the lower 16 bits contains + /// id/eid, and higher bits are reserved. + /// + UINT64 ProcessorId; + /// + /// Flags indicating if the processor is BSP or AP, if the processor is enabled + /// or disabled, and if the processor is healthy. Bits 3..31 are reserved and + /// must be 0. + /// + ///
+  /// BSP  ENABLED  HEALTH  Description
+  /// ===  =======  ======  ===================================================
+  ///  0      0       0     Unhealthy Disabled AP.
+  ///  0      0       1     Healthy Disabled AP.
+  ///  0      1       0     Unhealthy Enabled AP.
+  ///  0      1       1     Healthy Enabled AP.
+  ///  1      0       0     Invalid. The BSP can never be in the disabled state.
+  ///  1      0       1     Invalid. The BSP can never be in the disabled state.
+  ///  1      1       0     Unhealthy Enabled BSP.
+  ///  1      1       1     Healthy Enabled BSP.
+  /// 
+ /// + UINT32 StatusFlag; + /// + /// The physical location of the processor, including the physical package number + /// that identifies the cartridge, the physical core number within package, and + /// logical thread number within core. + /// + EFI_CPU_PHYSICAL_LOCATION Location; + /// + /// The extended information of the processor. This field is filled only when + /// CPU_V2_EXTENDED_TOPOLOGY is set in parameter ProcessorNumber. + EXTENDED_PROCESSOR_INFORMATION ExtendedInformation; +} EFI_PROCESSOR_INFORMATION; + +/** + This service retrieves the number of logical processor in the platform + and the number of those logical processors that are enabled on this boot. + This service may only be called from the BSP. + + This function is used to retrieve the following information: + - The number of logical processors that are present in the system. + - The number of enabled logical processors in the system at the instant + this call is made. + + Because MP Service Protocol provides services to enable and disable processors + dynamically, the number of enabled logical processors may vary during the + course of a boot session. + + If this service is called from an AP, then EFI_DEVICE_ERROR is returned. + If NumberOfProcessors or NumberOfEnabledProcessors is NULL, then + EFI_INVALID_PARAMETER is returned. Otherwise, the total number of processors + is returned in NumberOfProcessors, the number of currently enabled processor + is returned in NumberOfEnabledProcessors, and EFI_SUCCESS is returned. + + @param[in] This A pointer to the EFI_MP_SERVICES_PROTOCOL + instance. + @param[out] NumberOfProcessors Pointer to the total number of logical + processors in the system, including the BSP + and disabled APs. + @param[out] NumberOfEnabledProcessors Pointer to the number of enabled logical + processors that exist in system, including + the BSP. + + @retval EFI_SUCCESS The number of logical processors and enabled + logical processors was retrieved. + @retval EFI_DEVICE_ERROR The calling processor is an AP. + @retval EFI_INVALID_PARAMETER NumberOfProcessors is NULL. + @retval EFI_INVALID_PARAMETER NumberOfEnabledProcessors is NULL. + +**/ +typedef +EFI_STATUS +(EFIAPI *EFI_MP_SERVICES_GET_NUMBER_OF_PROCESSORS)( + IN EFI_MP_SERVICES_PROTOCOL *This, + OUT UINTN *NumberOfProcessors, + OUT UINTN *NumberOfEnabledProcessors + ); + +/** + Gets detailed MP-related information on the requested processor at the + instant this call is made. This service may only be called from the BSP. + + This service retrieves detailed MP-related information about any processor + on the platform. Note the following: + - The processor information may change during the course of a boot session. + - The information presented here is entirely MP related. + + Information regarding the number of caches and their sizes, frequency of operation, + slot numbers is all considered platform-related information and is not provided + by this service. + + @param[in] This A pointer to the EFI_MP_SERVICES_PROTOCOL + instance. + @param[in] ProcessorNumber The handle number of processor. + @param[out] ProcessorInfoBuffer A pointer to the buffer where information for + the requested processor is deposited. + + @retval EFI_SUCCESS Processor information was returned. + @retval EFI_DEVICE_ERROR The calling processor is an AP. + @retval EFI_INVALID_PARAMETER ProcessorInfoBuffer is NULL. + @retval EFI_NOT_FOUND The processor with the handle specified by + ProcessorNumber does not exist in the platform. + +**/ +typedef +EFI_STATUS +(EFIAPI *EFI_MP_SERVICES_GET_PROCESSOR_INFO)( + IN EFI_MP_SERVICES_PROTOCOL *This, + IN UINTN ProcessorNumber, + OUT EFI_PROCESSOR_INFORMATION *ProcessorInfoBuffer + ); + +/** + This service executes a caller provided function on all enabled APs. APs can + run either simultaneously or one at a time in sequence. This service supports + both blocking and non-blocking requests. The non-blocking requests use EFI + events so the BSP can detect when the APs have finished. This service may only + be called from the BSP. + + This function is used to dispatch all the enabled APs to the function specified + by Procedure. If any enabled AP is busy, then EFI_NOT_READY is returned + immediately and Procedure is not started on any AP. + + If SingleThread is TRUE, all the enabled APs execute the function specified by + Procedure one by one, in ascending order of processor handle number. Otherwise, + all the enabled APs execute the function specified by Procedure simultaneously. + + If WaitEvent is NULL, execution is in blocking mode. The BSP waits until all + APs finish or TimeoutInMicroSecs expires. Otherwise, execution is in non-blocking + mode, and the BSP returns from this service without waiting for APs. If a + non-blocking mode is requested after the UEFI Event EFI_EVENT_GROUP_READY_TO_BOOT + is signaled, then EFI_UNSUPPORTED must be returned. + + If the timeout specified by TimeoutInMicroseconds expires before all APs return + from Procedure, then Procedure on the failed APs is terminated. All enabled APs + are always available for further calls to EFI_MP_SERVICES_PROTOCOL.StartupAllAPs() + and EFI_MP_SERVICES_PROTOCOL.StartupThisAP(). If FailedCpuList is not NULL, its + content points to the list of processor handle numbers in which Procedure was + terminated. + + Note: It is the responsibility of the consumer of the EFI_MP_SERVICES_PROTOCOL.StartupAllAPs() + to make sure that the nature of the code that is executed on the BSP and the + dispatched APs is well controlled. The MP Services Protocol does not guarantee + that the Procedure function is MP-safe. Hence, the tasks that can be run in + parallel are limited to certain independent tasks and well-controlled exclusive + code. EFI services and protocols may not be called by APs unless otherwise + specified. + + In blocking execution mode, BSP waits until all APs finish or + TimeoutInMicroSeconds expires. + + In non-blocking execution mode, BSP is freed to return to the caller and then + proceed to the next task without having to wait for APs. The following + sequence needs to occur in a non-blocking execution mode: + + -# The caller that intends to use this MP Services Protocol in non-blocking + mode creates WaitEvent by calling the EFI CreateEvent() service. The caller + invokes EFI_MP_SERVICES_PROTOCOL.StartupAllAPs(). If the parameter WaitEvent + is not NULL, then StartupAllAPs() executes in non-blocking mode. It requests + the function specified by Procedure to be started on all the enabled APs, + and releases the BSP to continue with other tasks. + -# The caller can use the CheckEvent() and WaitForEvent() services to check + the state of the WaitEvent created in step 1. + -# When the APs complete their task or TimeoutInMicroSecondss expires, the MP + Service signals WaitEvent by calling the EFI SignalEvent() function. If + FailedCpuList is not NULL, its content is available when WaitEvent is + signaled. If all APs returned from Procedure prior to the timeout, then + FailedCpuList is set to NULL. If not all APs return from Procedure before + the timeout, then FailedCpuList is filled in with the list of the failed + APs. The buffer is allocated by MP Service Protocol using AllocatePool(). + It is the caller's responsibility to free the buffer with FreePool() service. + -# This invocation of SignalEvent() function informs the caller that invoked + EFI_MP_SERVICES_PROTOCOL.StartupAllAPs() that either all the APs completed + the specified task or a timeout occurred. The contents of FailedCpuList + can be examined to determine which APs did not complete the specified task + prior to the timeout. + + @param[in] This A pointer to the EFI_MP_SERVICES_PROTOCOL + instance. + @param[in] Procedure A pointer to the function to be run on + enabled APs of the system. See type + EFI_AP_PROCEDURE. + @param[in] SingleThread If TRUE, then all the enabled APs execute + the function specified by Procedure one by + one, in ascending order of processor handle + number. If FALSE, then all the enabled APs + execute the function specified by Procedure + simultaneously. + @param[in] WaitEvent The event created by the caller with CreateEvent() + service. If it is NULL, then execute in + blocking mode. BSP waits until all APs finish + or TimeoutInMicroSeconds expires. If it's + not NULL, then execute in non-blocking mode. + BSP requests the function specified by + Procedure to be started on all the enabled + APs, and go on executing immediately. If + all return from Procedure, or TimeoutInMicroSeconds + expires, this event is signaled. The BSP + can use the CheckEvent() or WaitForEvent() + services to check the state of event. Type + EFI_EVENT is defined in CreateEvent() in + the Unified Extensible Firmware Interface + Specification. + @param[in] TimeoutInMicrosecsond Indicates the time limit in microseconds for + APs to return from Procedure, either for + blocking or non-blocking mode. Zero means + infinity. If the timeout expires before + all APs return from Procedure, then Procedure + on the failed APs is terminated. All enabled + APs are available for next function assigned + by EFI_MP_SERVICES_PROTOCOL.StartupAllAPs() + or EFI_MP_SERVICES_PROTOCOL.StartupThisAP(). + If the timeout expires in blocking mode, + BSP returns EFI_TIMEOUT. If the timeout + expires in non-blocking mode, WaitEvent + is signaled with SignalEvent(). + @param[in] ProcedureArgument The parameter passed into Procedure for + all APs. + @param[out] FailedCpuList If NULL, this parameter is ignored. Otherwise, + if all APs finish successfully, then its + content is set to NULL. If not all APs + finish before timeout expires, then its + content is set to address of the buffer + holding handle numbers of the failed APs. + The buffer is allocated by MP Service Protocol, + and it's the caller's responsibility to + free the buffer with FreePool() service. + In blocking mode, it is ready for consumption + when the call returns. In non-blocking mode, + it is ready when WaitEvent is signaled. The + list of failed CPU is terminated by + END_OF_CPU_LIST. + + @retval EFI_SUCCESS In blocking mode, all APs have finished before + the timeout expired. + @retval EFI_SUCCESS In non-blocking mode, function has been dispatched + to all enabled APs. + @retval EFI_UNSUPPORTED A non-blocking mode request was made after the + UEFI event EFI_EVENT_GROUP_READY_TO_BOOT was + signaled. + @retval EFI_DEVICE_ERROR Caller processor is AP. + @retval EFI_NOT_STARTED No enabled APs exist in the system. + @retval EFI_NOT_READY Any enabled APs are busy. + @retval EFI_TIMEOUT In blocking mode, the timeout expired before + all enabled APs have finished. + @retval EFI_INVALID_PARAMETER Procedure is NULL. + +**/ +typedef +EFI_STATUS +(EFIAPI *EFI_MP_SERVICES_STARTUP_ALL_APS)( + IN EFI_MP_SERVICES_PROTOCOL *This, + IN EFI_AP_PROCEDURE Procedure, + IN BOOLEAN SingleThread, + IN EFI_EVENT WaitEvent OPTIONAL, + IN UINTN TimeoutInMicroSeconds, + IN VOID *ProcedureArgument OPTIONAL, + OUT UINTN **FailedCpuList OPTIONAL + ); + +/** + This service lets the caller get one enabled AP to execute a caller-provided + function. The caller can request the BSP to either wait for the completion + of the AP or just proceed with the next task by using the EFI event mechanism. + See EFI_MP_SERVICES_PROTOCOL.StartupAllAPs() for more details on non-blocking + execution support. This service may only be called from the BSP. + + This function is used to dispatch one enabled AP to the function specified by + Procedure passing in the argument specified by ProcedureArgument. If WaitEvent + is NULL, execution is in blocking mode. The BSP waits until the AP finishes or + TimeoutInMicroSecondss expires. Otherwise, execution is in non-blocking mode. + BSP proceeds to the next task without waiting for the AP. If a non-blocking mode + is requested after the UEFI Event EFI_EVENT_GROUP_READY_TO_BOOT is signaled, + then EFI_UNSUPPORTED must be returned. + + If the timeout specified by TimeoutInMicroseconds expires before the AP returns + from Procedure, then execution of Procedure by the AP is terminated. The AP is + available for subsequent calls to EFI_MP_SERVICES_PROTOCOL.StartupAllAPs() and + EFI_MP_SERVICES_PROTOCOL.StartupThisAP(). + + @param[in] This A pointer to the EFI_MP_SERVICES_PROTOCOL + instance. + @param[in] Procedure A pointer to the function to be run on the + designated AP of the system. See type + EFI_AP_PROCEDURE. + @param[in] ProcessorNumber The handle number of the AP. The range is + from 0 to the total number of logical + processors minus 1. The total number of + logical processors can be retrieved by + EFI_MP_SERVICES_PROTOCOL.GetNumberOfProcessors(). + @param[in] WaitEvent The event created by the caller with CreateEvent() + service. If it is NULL, then execute in + blocking mode. BSP waits until this AP finish + or TimeoutInMicroSeconds expires. If it's + not NULL, then execute in non-blocking mode. + BSP requests the function specified by + Procedure to be started on this AP, + and go on executing immediately. If this AP + return from Procedure or TimeoutInMicroSeconds + expires, this event is signaled. The BSP + can use the CheckEvent() or WaitForEvent() + services to check the state of event. Type + EFI_EVENT is defined in CreateEvent() in + the Unified Extensible Firmware Interface + Specification. + @param[in] TimeoutInMicrosecsond Indicates the time limit in microseconds for + this AP to finish this Procedure, either for + blocking or non-blocking mode. Zero means + infinity. If the timeout expires before + this AP returns from Procedure, then Procedure + on the AP is terminated. The + AP is available for next function assigned + by EFI_MP_SERVICES_PROTOCOL.StartupAllAPs() + or EFI_MP_SERVICES_PROTOCOL.StartupThisAP(). + If the timeout expires in blocking mode, + BSP returns EFI_TIMEOUT. If the timeout + expires in non-blocking mode, WaitEvent + is signaled with SignalEvent(). + @param[in] ProcedureArgument The parameter passed into Procedure on the + specified AP. + @param[out] Finished If NULL, this parameter is ignored. In + blocking mode, this parameter is ignored. + In non-blocking mode, if AP returns from + Procedure before the timeout expires, its + content is set to TRUE. Otherwise, the + value is set to FALSE. The caller can + determine if the AP returned from Procedure + by evaluating this value. + + @retval EFI_SUCCESS In blocking mode, specified AP finished before + the timeout expires. + @retval EFI_SUCCESS In non-blocking mode, the function has been + dispatched to specified AP. + @retval EFI_UNSUPPORTED A non-blocking mode request was made after the + UEFI event EFI_EVENT_GROUP_READY_TO_BOOT was + signaled. + @retval EFI_DEVICE_ERROR The calling processor is an AP. + @retval EFI_TIMEOUT In blocking mode, the timeout expired before + the specified AP has finished. + @retval EFI_NOT_READY The specified AP is busy. + @retval EFI_NOT_FOUND The processor with the handle specified by + ProcessorNumber does not exist. + @retval EFI_INVALID_PARAMETER ProcessorNumber specifies the BSP or disabled AP. + @retval EFI_INVALID_PARAMETER Procedure is NULL. + +**/ +typedef +EFI_STATUS +(EFIAPI *EFI_MP_SERVICES_STARTUP_THIS_AP)( + IN EFI_MP_SERVICES_PROTOCOL *This, + IN EFI_AP_PROCEDURE Procedure, + IN UINTN ProcessorNumber, + IN EFI_EVENT WaitEvent OPTIONAL, + IN UINTN TimeoutInMicroseconds, + IN VOID *ProcedureArgument OPTIONAL, + OUT BOOLEAN *Finished OPTIONAL + ); + +/** + This service switches the requested AP to be the BSP from that point onward. + This service changes the BSP for all purposes. This call can only be performed + by the current BSP. + + This service switches the requested AP to be the BSP from that point onward. + This service changes the BSP for all purposes. The new BSP can take over the + execution of the old BSP and continue seamlessly from where the old one left + off. This service may not be supported after the UEFI Event EFI_EVENT_GROUP_READY_TO_BOOT + is signaled. + + If the BSP cannot be switched prior to the return from this service, then + EFI_UNSUPPORTED must be returned. + + @param[in] This A pointer to the EFI_MP_SERVICES_PROTOCOL instance. + @param[in] ProcessorNumber The handle number of AP that is to become the new + BSP. The range is from 0 to the total number of + logical processors minus 1. The total number of + logical processors can be retrieved by + EFI_MP_SERVICES_PROTOCOL.GetNumberOfProcessors(). + @param[in] EnableOldBSP If TRUE, then the old BSP will be listed as an + enabled AP. Otherwise, it will be disabled. + + @retval EFI_SUCCESS BSP successfully switched. + @retval EFI_UNSUPPORTED Switching the BSP cannot be completed prior to + this service returning. + @retval EFI_UNSUPPORTED Switching the BSP is not supported. + @retval EFI_DEVICE_ERROR The calling processor is an AP. + @retval EFI_NOT_FOUND The processor with the handle specified by + ProcessorNumber does not exist. + @retval EFI_INVALID_PARAMETER ProcessorNumber specifies the current BSP or + a disabled AP. + @retval EFI_NOT_READY The specified AP is busy. + +**/ +typedef +EFI_STATUS +(EFIAPI *EFI_MP_SERVICES_SWITCH_BSP)( + IN EFI_MP_SERVICES_PROTOCOL *This, + IN UINTN ProcessorNumber, + IN BOOLEAN EnableOldBSP + ); + +/** + This service lets the caller enable or disable an AP from this point onward. + This service may only be called from the BSP. + + This service allows the caller enable or disable an AP from this point onward. + The caller can optionally specify the health status of the AP by Health. If + an AP is being disabled, then the state of the disabled AP is implementation + dependent. If an AP is enabled, then the implementation must guarantee that a + complete initialization sequence is performed on the AP, so the AP is in a state + that is compatible with an MP operating system. This service may not be supported + after the UEFI Event EFI_EVENT_GROUP_READY_TO_BOOT is signaled. + + If the enable or disable AP operation cannot be completed prior to the return + from this service, then EFI_UNSUPPORTED must be returned. + + @param[in] This A pointer to the EFI_MP_SERVICES_PROTOCOL instance. + @param[in] ProcessorNumber The handle number of AP. + The range is from 0 to the total number of + logical processors minus 1. The total number of + logical processors can be retrieved by + EFI_MP_SERVICES_PROTOCOL.GetNumberOfProcessors(). + @param[in] EnableAP Specifies the new state for the processor for + enabled, FALSE for disabled. + @param[in] HealthFlag If not NULL, a pointer to a value that specifies + the new health status of the AP. This flag + corresponds to StatusFlag defined in + EFI_MP_SERVICES_PROTOCOL.GetProcessorInfo(). Only + the PROCESSOR_HEALTH_STATUS_BIT is used. All other + bits are ignored. If it is NULL, this parameter + is ignored. + + @retval EFI_SUCCESS The specified AP was enabled or disabled successfully. + @retval EFI_UNSUPPORTED Enabling or disabling an AP cannot be completed + prior to this service returning. + @retval EFI_UNSUPPORTED Enabling or disabling an AP is not supported. + @retval EFI_DEVICE_ERROR The calling processor is an AP. + @retval EFI_NOT_FOUND Processor with the handle specified by ProcessorNumber + does not exist. + @retval EFI_INVALID_PARAMETER ProcessorNumber specifies the BSP. + +**/ +typedef +EFI_STATUS +(EFIAPI *EFI_MP_SERVICES_ENABLEDISABLEAP)( + IN EFI_MP_SERVICES_PROTOCOL *This, + IN UINTN ProcessorNumber, + IN BOOLEAN EnableAP, + IN UINT32 *HealthFlag OPTIONAL + ); + +/** + This return the handle number for the calling processor. This service may be + called from the BSP and APs. + + This service returns the processor handle number for the calling processor. + The returned value is in the range from 0 to the total number of logical + processors minus 1. The total number of logical processors can be retrieved + with EFI_MP_SERVICES_PROTOCOL.GetNumberOfProcessors(). This service may be + called from the BSP and APs. If ProcessorNumber is NULL, then EFI_INVALID_PARAMETER + is returned. Otherwise, the current processors handle number is returned in + ProcessorNumber, and EFI_SUCCESS is returned. + + @param[in] This A pointer to the EFI_MP_SERVICES_PROTOCOL instance. + @param[in] ProcessorNumber Pointer to the handle number of AP. + The range is from 0 to the total number of + logical processors minus 1. The total number of + logical processors can be retrieved by + EFI_MP_SERVICES_PROTOCOL.GetNumberOfProcessors(). + + @retval EFI_SUCCESS The current processor handle number was returned + in ProcessorNumber. + @retval EFI_INVALID_PARAMETER ProcessorNumber is NULL. + +**/ +typedef +EFI_STATUS +(EFIAPI *EFI_MP_SERVICES_WHOAMI)( + IN EFI_MP_SERVICES_PROTOCOL *This, + OUT UINTN *ProcessorNumber + ); + +/// +/// When installed, the MP Services Protocol produces a collection of services +/// that are needed for MP management. +/// +/// Before the UEFI event EFI_EVENT_GROUP_READY_TO_BOOT is signaled, the module +/// that produces this protocol is required to place all APs into an idle state +/// whenever the APs are disabled or the APs are not executing code as requested +/// through the StartupAllAPs() or StartupThisAP() services. The idle state of +/// an AP before the UEFI event EFI_EVENT_GROUP_READY_TO_BOOT is signaled is +/// implementation dependent. +/// +/// After the UEFI event EFI_EVENT_GROUP_READY_TO_BOOT is signaled, all the APs +/// must be placed in the OS compatible CPU state as defined by the UEFI +/// Specification. Implementations of this protocol may use the UEFI event +/// EFI_EVENT_GROUP_READY_TO_BOOT to force APs into the OS compatible state as +/// defined by the UEFI Specification. Modules that use this protocol must +/// guarantee that all non-blocking mode requests on all APs have been completed +/// before the UEFI event EFI_EVENT_GROUP_READY_TO_BOOT is signaled. Since the +/// order that event notification functions in the same event group are executed +/// is not deterministic, an event of type EFI_EVENT_GROUP_READY_TO_BOOT cannot +/// be used to guarantee that APs have completed their non-blocking mode requests. +/// +/// When the UEFI event EFI_EVENT_GROUP_READY_TO_BOOT is signaled, the StartAllAPs() +/// and StartupThisAp() services must no longer support non-blocking mode requests. +/// The support for SwitchBSP() and EnableDisableAP() may no longer be supported +/// after this event is signaled. Since UEFI Applications and UEFI OS Loaders +/// execute after the UEFI event EFI_EVENT_GROUP_READY_TO_BOOT is signaled, these +/// UEFI images must be aware that the functionality of this protocol may be reduced. +/// +struct _EFI_MP_SERVICES_PROTOCOL { + EFI_MP_SERVICES_GET_NUMBER_OF_PROCESSORS GetNumberOfProcessors; + EFI_MP_SERVICES_GET_PROCESSOR_INFO GetProcessorInfo; + EFI_MP_SERVICES_STARTUP_ALL_APS StartupAllAPs; + EFI_MP_SERVICES_STARTUP_THIS_AP StartupThisAP; + EFI_MP_SERVICES_SWITCH_BSP SwitchBSP; + EFI_MP_SERVICES_ENABLEDISABLEAP EnableDisableAP; + EFI_MP_SERVICES_WHOAMI WhoAmI; +}; + +extern EFI_GUID gEfiMpServiceProtocolGuid; + +#endif diff --git a/src/include/ipxe/efi/efi_mp.h b/src/include/ipxe/efi/efi_mp.h new file mode 100644 index 000000000..8dc4243e4 --- /dev/null +++ b/src/include/ipxe/efi/efi_mp.h @@ -0,0 +1,30 @@ +#ifndef _IPXE_EFI_MP_H +#define _IPXE_EFI_MP_H + +/** @file + * + * EFI multiprocessor API implementation + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#ifdef MPAPI_EFI +#define MPAPI_PREFIX_efi +#else +#define MPAPI_PREFIX_efi __efi_ +#endif + +/** + * Calculate address as seen by a multiprocessor function + * + * @v address Address in boot processor address space + * @ret address Address in application processor address space + */ +static inline __attribute__ (( always_inline )) mp_addr_t +MPAPI_INLINE ( efi, mp_address ) ( void *address ) { + + return ( ( mp_addr_t ) address ); +} + +#endif /* _IPXE_EFI_MP_H */ diff --git a/src/include/ipxe/errfile.h b/src/include/ipxe/errfile.h index 1fe34113d..c673b9a40 100644 --- a/src/include/ipxe/errfile.h +++ b/src/include/ipxe/errfile.h @@ -81,6 +81,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define ERRFILE_efi_strings ( ERRFILE_CORE | 0x00290000 ) #define ERRFILE_uuid ( ERRFILE_CORE | 0x002a0000 ) #define ERRFILE_efi_path ( ERRFILE_CORE | 0x002b0000 ) +#define ERRFILE_efi_mp ( ERRFILE_CORE | 0x002c0000 ) #define ERRFILE_eisa ( ERRFILE_DRIVER | 0x00000000 ) #define ERRFILE_isa ( ERRFILE_DRIVER | 0x00010000 ) diff --git a/src/include/ipxe/mp.h b/src/include/ipxe/mp.h index a36574909..9670dea55 100644 --- a/src/include/ipxe/mp.h +++ b/src/include/ipxe/mp.h @@ -102,6 +102,7 @@ extern void __asmcall mp_call ( mp_addr_t func, mp_addr_t opaque ); /* Include all architecture-independent multiprocessor API headers */ #include +#include /* Include all architecture-dependent multiprocessor API headers */ #include diff --git a/src/interface/efi/efi_mp.c b/src/interface/efi/efi_mp.c new file mode 100644 index 000000000..fdbbc9ae8 --- /dev/null +++ b/src/interface/efi/efi_mp.c @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2024 Michael Brown . + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +/** @file + * + * EFI multiprocessor API implementation + * + */ + +#include +#include +#include +#include +#include + +/** EFI multiprocessor function call data */ +struct efi_mp_func_data { + /** Multiprocessor function */ + mp_addr_t func; + /** Opaque data pointer */ + mp_addr_t opaque; +}; + +/** Multiprocessor services protocol */ +static EFI_MP_SERVICES_PROTOCOL *efimp; +EFI_REQUEST_PROTOCOL ( EFI_MP_SERVICES_PROTOCOL, &efimp ); + +/** + * Call multiprocessor function on current CPU + * + * @v buffer Multiprocessor function call data + */ +static EFIAPI VOID efi_mp_call ( VOID *buffer ) { + struct efi_mp_func_data *data = buffer; + + /* Call multiprocessor function */ + mp_call ( data->func, data->opaque ); +} + +/** + * Execute a multiprocessor function on the boot processor + * + * @v func Multiprocessor function + * @v opaque Opaque data pointer + */ +static void efi_mp_exec_boot ( mp_func_t func, void *opaque ) { + struct efi_mp_func_data data; + + /* Construct call data */ + data.func = mp_address ( func ); + data.opaque = mp_address ( opaque ); + + /* Call multiprocesor function */ + efi_mp_call ( &data ); +} + +/** + * Start a multiprocessor function on all application processors + * + * @v func Multiprocessor function + * @v opaque Opaque data pointer + */ +static void efi_mp_start_all ( mp_func_t func, void *opaque ) { + struct efi_mp_func_data data; + EFI_STATUS efirc; + int rc; + + /* Do nothing if MP services is not present */ + if ( ! efimp ) { + DBGC ( func, "EFIMP has no multiprocessor services\n" ); + return; + } + + /* Construct call data */ + data.func = mp_address ( func ); + data.opaque = mp_address ( opaque ); + + /* Start up all application processors */ + if ( ( efirc = efimp->StartupAllAPs ( efimp, efi_mp_call, FALSE, NULL, + 0, &data, NULL ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( func, "EFIMP could not start APs: %s\n", + strerror ( rc ) ); + return; + } +} + +PROVIDE_MPAPI_INLINE ( efi, mp_address ); +PROVIDE_MPAPI ( efi, mp_exec_boot, efi_mp_exec_boot ); +PROVIDE_MPAPI ( efi, mp_start_all, efi_mp_start_all ); -- cgit v1.2.3-55-g7522 From 1a84facf12b07a5e5375822015b56cf320821055 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Tue, 19 Mar 2024 15:01:25 +0000 Subject: [efi] Add efi_path_uri() to parse a URI from an EFI device path Signed-off-by: Michael Brown --- src/include/ipxe/efi/efi_path.h | 1 + src/interface/efi/efi_path.c | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) (limited to 'src/include') diff --git a/src/include/ipxe/efi/efi_path.h b/src/include/ipxe/efi/efi_path.h index 20ff43f64..503bd4347 100644 --- a/src/include/ipxe/efi/efi_path.h +++ b/src/include/ipxe/efi/efi_path.h @@ -45,6 +45,7 @@ efi_path_end ( EFI_DEVICE_PATH_PROTOCOL *path ); extern size_t efi_path_len ( EFI_DEVICE_PATH_PROTOCOL *path ); extern unsigned int efi_path_vlan ( EFI_DEVICE_PATH_PROTOCOL *path ); extern int efi_path_guid ( EFI_DEVICE_PATH_PROTOCOL *path, union uuid *uuid ); +extern struct uri * efi_path_uri ( EFI_DEVICE_PATH_PROTOCOL *path ); extern EFI_DEVICE_PATH_PROTOCOL * efi_paths ( EFI_DEVICE_PATH_PROTOCOL *first, ... ); extern EFI_DEVICE_PATH_PROTOCOL * efi_netdev_path ( struct net_device *netdev ); diff --git a/src/interface/efi/efi_path.c b/src/interface/efi/efi_path.c index d1e22eeaa..4e37d248a 100644 --- a/src/interface/efi/efi_path.c +++ b/src/interface/efi/efi_path.c @@ -175,6 +175,46 @@ int efi_path_guid ( EFI_DEVICE_PATH_PROTOCOL *path, union uuid *guid ) { return rc; } +/** + * Parse URI from device path + * + * @v path Device path + * @ret uri URI, or NULL if not a URI + */ +struct uri * efi_path_uri ( EFI_DEVICE_PATH_PROTOCOL *path ) { + EFI_DEVICE_PATH_PROTOCOL *next; + URI_DEVICE_PATH *uripath; + char *uristring; + struct uri *uri; + size_t len; + + /* Search for URI device path */ + for ( ; ( next = efi_path_next ( path ) ) ; path = next ) { + if ( ( path->Type == MESSAGING_DEVICE_PATH ) && + ( path->SubType == MSG_URI_DP ) ) { + + /* Calculate path length */ + uripath = container_of ( path, URI_DEVICE_PATH, + Header ); + len = ( ( ( path->Length[1] << 8 ) | path->Length[0] ) + - offsetof ( typeof ( *uripath ), Uri ) ); + + /* Parse URI */ + uristring = zalloc ( len + 1 /* NUL */ ); + if ( ! uristring ) + return NULL; + memcpy ( uristring, uripath->Uri, len ); + uri = parse_uri ( uristring ); + free ( uristring ); + + return uri; + } + } + + /* No URI path found */ + return NULL; +} + /** * Concatenate EFI device paths * -- cgit v1.2.3-55-g7522 From ca483a196c091c16ea0a426ce5f915b184a34412 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Wed, 20 Mar 2024 12:47:25 +0000 Subject: [efi] Add helper functions for service binding protocols The EFI service binding abstraction is used to add and remove child handles for multiple different protocols. Provide a common interface for doing so. Signed-off-by: Michael Brown --- src/include/ipxe/efi/Protocol/ServiceBinding.h | 90 ++++++++++++++++ src/include/ipxe/efi/efi_service.h | 19 ++++ src/include/ipxe/errfile.h | 1 + src/interface/efi/efi_service.c | 138 +++++++++++++++++++++++++ 4 files changed, 248 insertions(+) create mode 100644 src/include/ipxe/efi/Protocol/ServiceBinding.h create mode 100644 src/include/ipxe/efi/efi_service.h create mode 100644 src/interface/efi/efi_service.c (limited to 'src/include') diff --git a/src/include/ipxe/efi/Protocol/ServiceBinding.h b/src/include/ipxe/efi/Protocol/ServiceBinding.h new file mode 100644 index 000000000..6baf73aa7 --- /dev/null +++ b/src/include/ipxe/efi/Protocol/ServiceBinding.h @@ -0,0 +1,90 @@ +/** @file + UEFI Service Binding Protocol is defined in UEFI specification. + + The file defines the generic Service Binding Protocol functions. + It provides services that are required to create and destroy child + handles that support a given set of protocols. + + Copyright (c) 2006 - 2018, Intel Corporation. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#ifndef __EFI_SERVICE_BINDING_H__ +#define __EFI_SERVICE_BINDING_H__ + +FILE_LICENCE ( BSD2_PATENT ); + +/// +/// Forward reference for pure ANSI compatability +/// +typedef struct _EFI_SERVICE_BINDING_PROTOCOL EFI_SERVICE_BINDING_PROTOCOL; + +/** + Creates a child handle and installs a protocol. + + The CreateChild() function installs a protocol on ChildHandle. + If ChildHandle is a pointer to NULL, then a new handle is created and returned in ChildHandle. + If ChildHandle is not a pointer to NULL, then the protocol installs on the existing ChildHandle. + + @param This Pointer to the EFI_SERVICE_BINDING_PROTOCOL instance. + @param ChildHandle Pointer to the handle of the child to create. If it is NULL, + then a new handle is created. If it is a pointer to an existing UEFI handle, + then the protocol is added to the existing UEFI handle. + + @retval EFI_SUCCES The protocol was added to ChildHandle. + @retval EFI_INVALID_PARAMETER ChildHandle is NULL. + @retval EFI_OUT_OF_RESOURCES There are not enough resources available to create + the child + @retval other The child handle was not created + +**/ +typedef +EFI_STATUS +(EFIAPI *EFI_SERVICE_BINDING_CREATE_CHILD)( + IN EFI_SERVICE_BINDING_PROTOCOL *This, + IN OUT EFI_HANDLE *ChildHandle + ); + +/** + Destroys a child handle with a protocol installed on it. + + The DestroyChild() function does the opposite of CreateChild(). It removes a protocol + that was installed by CreateChild() from ChildHandle. If the removed protocol is the + last protocol on ChildHandle, then ChildHandle is destroyed. + + @param This Pointer to the EFI_SERVICE_BINDING_PROTOCOL instance. + @param ChildHandle Handle of the child to destroy + + @retval EFI_SUCCES The protocol was removed from ChildHandle. + @retval EFI_UNSUPPORTED ChildHandle does not support the protocol that is being removed. + @retval EFI_INVALID_PARAMETER Child handle is NULL. + @retval EFI_ACCESS_DENIED The protocol could not be removed from the ChildHandle + because its services are being used. + @retval other The child handle was not destroyed + +**/ +typedef +EFI_STATUS +(EFIAPI *EFI_SERVICE_BINDING_DESTROY_CHILD)( + IN EFI_SERVICE_BINDING_PROTOCOL *This, + IN EFI_HANDLE ChildHandle + ); + +/// +/// The EFI_SERVICE_BINDING_PROTOCOL provides member functions to create and destroy +/// child handles. A driver is responsible for adding protocols to the child handle +/// in CreateChild() and removing protocols in DestroyChild(). It is also required +/// that the CreateChild() function opens the parent protocol BY_CHILD_CONTROLLER +/// to establish the parent-child relationship, and closes the protocol in DestroyChild(). +/// The pseudo code for CreateChild() and DestroyChild() is provided to specify the +/// required behavior, not to specify the required implementation. Each consumer of +/// a software protocol is responsible for calling CreateChild() when it requires the +/// protocol and calling DestroyChild() when it is finished with that protocol. +/// +struct _EFI_SERVICE_BINDING_PROTOCOL { + EFI_SERVICE_BINDING_CREATE_CHILD CreateChild; + EFI_SERVICE_BINDING_DESTROY_CHILD DestroyChild; +}; + +#endif diff --git a/src/include/ipxe/efi/efi_service.h b/src/include/ipxe/efi/efi_service.h new file mode 100644 index 000000000..ca4c7b2a4 --- /dev/null +++ b/src/include/ipxe/efi/efi_service.h @@ -0,0 +1,19 @@ +#ifndef _IPXE_EFI_SERVICE_H +#define _IPXE_EFI_SERVICE_H + +/** @file + * + * EFI service binding + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include + +extern int efi_service_add ( EFI_HANDLE service, EFI_GUID *binding, + EFI_HANDLE *handle ); +extern int efi_service_del ( EFI_HANDLE service, EFI_GUID *binding, + EFI_HANDLE handle ); + +#endif /* _IPXE_EFI_SERVICE_H */ diff --git a/src/include/ipxe/errfile.h b/src/include/ipxe/errfile.h index c673b9a40..083c77e1f 100644 --- a/src/include/ipxe/errfile.h +++ b/src/include/ipxe/errfile.h @@ -82,6 +82,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define ERRFILE_uuid ( ERRFILE_CORE | 0x002a0000 ) #define ERRFILE_efi_path ( ERRFILE_CORE | 0x002b0000 ) #define ERRFILE_efi_mp ( ERRFILE_CORE | 0x002c0000 ) +#define ERRFILE_efi_service ( ERRFILE_CORE | 0x002d0000 ) #define ERRFILE_eisa ( ERRFILE_DRIVER | 0x00000000 ) #define ERRFILE_isa ( ERRFILE_DRIVER | 0x00010000 ) diff --git a/src/interface/efi/efi_service.c b/src/interface/efi/efi_service.c new file mode 100644 index 000000000..d4129c0d9 --- /dev/null +++ b/src/interface/efi/efi_service.c @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2024 Michael Brown . + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +/** @file + * + * EFI service binding + * + */ + +#include +#include +#include +#include +#include + +/** + * Add service to child handle + * + * @v service Service binding handle + * @v binding Service binding protocol GUID + * @v handle Handle on which to install child + * @ret rc Return status code + */ +int efi_service_add ( EFI_HANDLE service, EFI_GUID *binding, + EFI_HANDLE *handle ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + union { + EFI_SERVICE_BINDING_PROTOCOL *sb; + void *interface; + } u; + EFI_STATUS efirc; + int rc; + + /* Open service binding protocol */ + if ( ( efirc = bs->OpenProtocol ( service, binding, &u.interface, + efi_image_handle, service, + EFI_OPEN_PROTOCOL_GET_PROTOCOL ))!=0){ + rc = -EEFI ( efirc ); + DBGC ( service, "EFISVC %s cannot open %s binding: %s\n", + efi_handle_name ( service ), efi_guid_ntoa ( binding ), + strerror ( rc ) ); + goto err_open; + } + + /* Create child handle */ + if ( ( efirc = u.sb->CreateChild ( u.sb, handle ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( service, "EFISVC %s could not create %s child: %s\n", + efi_handle_name ( service ), efi_guid_ntoa ( binding ), + strerror ( rc ) ); + goto err_create; + } + + /* Success */ + rc = 0; + DBGC ( service, "EFISVC %s created %s child ", + efi_handle_name ( service ), efi_guid_ntoa ( binding ) ); + DBGC ( service, "%s\n", efi_handle_name ( *handle ) ); + + err_create: + bs->CloseProtocol ( service, binding, efi_image_handle, service ); + err_open: + return rc; +} + +/** + * Remove service from child handle + * + * @v service Service binding handle + * @v binding Service binding protocol GUID + * @v handle Child handle + * @ret rc Return status code + */ +int efi_service_del ( EFI_HANDLE service, EFI_GUID *binding, + EFI_HANDLE handle ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + union { + EFI_SERVICE_BINDING_PROTOCOL *sb; + void *interface; + } u; + EFI_STATUS efirc; + int rc; + + DBGC ( service, "EFISVC %s removing %s child ", + efi_handle_name ( service ), efi_guid_ntoa ( binding ) ); + DBGC ( service, "%s\n", efi_handle_name ( handle ) ); + + /* Open service binding protocol */ + if ( ( efirc = bs->OpenProtocol ( service, binding, &u.interface, + efi_image_handle, service, + EFI_OPEN_PROTOCOL_GET_PROTOCOL ))!=0){ + rc = -EEFI ( efirc ); + DBGC ( service, "EFISVC %s cannot open %s binding: %s\n", + efi_handle_name ( service ), efi_guid_ntoa ( binding ), + strerror ( rc ) ); + goto err_open; + } + + /* Destroy child handle */ + if ( ( efirc = u.sb->DestroyChild ( u.sb, handle ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( service, "EFISVC %s could not destroy %s child ", + efi_handle_name ( service ), efi_guid_ntoa ( binding ) ); + DBGC ( service, "%s: %s\n", + efi_handle_name ( handle ), strerror ( rc ) ); + goto err_destroy; + } + + /* Success */ + rc = 0; + + err_destroy: + bs->CloseProtocol ( service, binding, efi_image_handle, service ); + err_open: + return rc; +} -- cgit v1.2.3-55-g7522 From da5188f3ea73900f1c6a4e44a8345b48320d396f Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Fri, 22 Mar 2024 16:50:13 +0000 Subject: [efi] Allow for drivers to be located via child handles When using a service binding protocol, CreateChild() will create a new protocol instance (and optionally a new handle). The caller will then typically open this new protocol instance with BY_DRIVER attributes, since the service binding mechanism has no equivalent of the driver binding protocol's Stop() method, and there is therefore no other way for the caller to be informed if the protocol instance is about to become invalid (e.g. because the service driver wants to remove the child). The caller cannot ask CreateChild() to install the new protocol instance on the original handle (i.e. the service binding handle), since the whole point of the service binding protocol is to allow for the existence of multiple children, and UEFI does not permit multiple instances of the same protocol to be installed on a handle. Our current drivers all open the original handle (as passed to our driver binding's Start() method) with BY_DRIVER attributes, and so the same handle will be passed to our Stop() method. This changes when our driver must use a separate handle, as described above. Add an optional "child handle" field to struct efi_device (on the assumption that we will not have any drivers that need to create multiple children), and generalise efidev_find() to match on either the original handle or the child handle. Signed-off-by: Michael Brown --- src/include/ipxe/efi/efi_driver.h | 2 ++ src/interface/efi/efi_driver.c | 10 ++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) (limited to 'src/include') diff --git a/src/include/ipxe/efi/efi_driver.h b/src/include/ipxe/efi/efi_driver.h index 74ece90db..411e93642 100644 --- a/src/include/ipxe/efi/efi_driver.h +++ b/src/include/ipxe/efi/efi_driver.h @@ -19,6 +19,8 @@ struct efi_device { struct device dev; /** EFI device handle */ EFI_HANDLE device; + /** EFI child device handle (if present) */ + EFI_HANDLE child; /** EFI device path copy */ EFI_DEVICE_PATH_PROTOCOL *path; /** Driver for this device */ diff --git a/src/interface/efi/efi_driver.c b/src/interface/efi/efi_driver.c index 8e537d535..8f8c9f3da 100644 --- a/src/interface/efi/efi_driver.c +++ b/src/interface/efi/efi_driver.c @@ -64,16 +64,22 @@ static int efi_driver_disconnecting; /** * Find EFI device * - * @v device EFI device handle + * @v device EFI device handle (or child handle) * @ret efidev EFI device, or NULL if not found */ static struct efi_device * efidev_find ( EFI_HANDLE device ) { struct efi_device *efidev; + /* Avoid false positive matches against NULL children */ + if ( ! device ) + return NULL; + /* Look for an existing EFI device */ list_for_each_entry ( efidev, &efi_devices, dev.siblings ) { - if ( efidev->device == device ) + if ( ( device == efidev->device ) || + ( device == efidev->child ) ) { return efidev; + } } return NULL; -- cgit v1.2.3-55-g7522 From dcad73ca5ad3e1fe011c52a24036f67ad69fadc1 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Fri, 22 Mar 2024 15:30:45 +0000 Subject: [efi] Add support for driving EFI_MANAGED_NETWORK_PROTOCOL devices We want exclusive access to the network device, both for performance reasons and because we perform operations such as EAPoL that affect the entire link. We currently drive the network card via either a native hardware driver or via the SNP or NII/UNDI interfaces, both of which grant us this exclusive access. Add an alternative driver that drives the network card non-exclusively via the EFI_MANAGED_NETWORK_PROTOCOL interface. This can function as a fallback for situations where neither SNP nor NII/UNDI interfaces are functional, and also opens up the possibility of non-destructively installing a temporary network device over which to download the autoexec.ipxe script. Signed-off-by: Michael Brown --- src/Makefile.efi | 4 +- src/drivers/net/efi/mnp.c | 56 +++++ src/drivers/net/efi/mnpnet.c | 504 ++++++++++++++++++++++++++++++++++++++++++ src/drivers/net/efi/mnpnet.h | 17 ++ src/drivers/net/efi/snp.c | 54 +---- src/drivers/net/efi/snpnet.c | 48 ++++ src/drivers/net/efi/snpnet.h | 1 + src/drivers/net/efi/snponly.c | 26 +++ src/include/ipxe/errfile.h | 1 + 9 files changed, 657 insertions(+), 54 deletions(-) create mode 100644 src/drivers/net/efi/mnp.c create mode 100644 src/drivers/net/efi/mnpnet.c create mode 100644 src/drivers/net/efi/mnpnet.h (limited to 'src/include') diff --git a/src/Makefile.efi b/src/Makefile.efi index 6e8ad46bc..95ecf3863 100644 --- a/src/Makefile.efi +++ b/src/Makefile.efi @@ -23,9 +23,9 @@ NON_AUTO_MEDIA += efidrv NON_AUTO_MEDIA += drv.efi NON_AUTO_MEDIA += efirom -# Include SNP driver in the all-drivers build +# Include SNP and MNP drivers in the all-drivers build # -DRIVERS_net += snp +DRIVERS_net += snp mnp # Rules for building EFI files # diff --git a/src/drivers/net/efi/mnp.c b/src/drivers/net/efi/mnp.c new file mode 100644 index 000000000..b79fe188a --- /dev/null +++ b/src/drivers/net/efi/mnp.c @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2024 Michael Brown . + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +/** @file + * + * MNP driver + * + */ + +#include +#include +#include +#include "snpnet.h" +#include "mnpnet.h" + +/** + * Check to see if driver supports a device + * + * @v device EFI device handle + * @ret rc Return status code + */ +static int mnp_supported ( EFI_HANDLE device ) { + EFI_GUID *binding = &efi_managed_network_service_binding_protocol_guid; + + return snpnet_supported ( device, binding ); +} + +/** EFI MNP driver */ +struct efi_driver mnp_driver __efi_driver ( EFI_DRIVER_NORMAL ) = { + .name = "MNP", + .supported = mnp_supported, + .start = mnpnet_start, + .stop = mnpnet_stop, +}; diff --git a/src/drivers/net/efi/mnpnet.c b/src/drivers/net/efi/mnpnet.c new file mode 100644 index 000000000..a07eae54e --- /dev/null +++ b/src/drivers/net/efi/mnpnet.c @@ -0,0 +1,504 @@ +/* + * Copyright (C) 2024 Michael Brown . + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +/** @file + * + * MNP NIC driver + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mnpnet.h" + +/** An MNP transmit or receive token */ +struct mnp_token { + /** MNP completion token */ + EFI_MANAGED_NETWORK_COMPLETION_TOKEN token; + /** Token is owned by MNP */ + int busy; +}; + +/** An MNP NIC */ +struct mnp_nic { + /** EFI device */ + struct efi_device *efidev; + /** Managed network protocol */ + EFI_MANAGED_NETWORK_PROTOCOL *mnp; + /** Generic device */ + struct device dev; + + /** Transmit token */ + struct mnp_token tx; + /** Transmit descriptor */ + EFI_MANAGED_NETWORK_TRANSMIT_DATA txdata; + /** Transmit I/O buffer */ + struct io_buffer *txbuf; + + /** Receive token */ + struct mnp_token rx; +}; + +/** + * Transmit or receive token event + * + * @v event Event + * @v context Event context + */ +static VOID EFIAPI mnpnet_event ( EFI_EVENT event __unused, VOID *context ) { + struct mnp_token *token = context; + + /* Sanity check */ + assert ( token->busy ); + + /* Mark token as no longer owned by MNP */ + token->busy = 0; +} + +/** + * Transmit packet + * + * @v netdev Network device + * @v iobuf I/O buffer + * @ret rc Return status code + */ +static int mnpnet_transmit ( struct net_device *netdev, + struct io_buffer *iobuf ) { + struct mnp_nic *mnp = netdev->priv; + struct ll_protocol *ll_protocol = netdev->ll_protocol; + EFI_STATUS efirc; + int rc; + + /* Do nothing if shutdown is in progress */ + if ( efi_shutdown_in_progress ) + return -ECANCELED; + + /* Defer the packet if there is already a transmission in progress */ + if ( mnp->txbuf ) { + netdev_tx_defer ( netdev, iobuf ); + return 0; + } + + /* Construct transmit token */ + mnp->txdata.DataLength = + ( iob_len ( iobuf ) - ll_protocol->ll_header_len ); + mnp->txdata.HeaderLength = ll_protocol->ll_header_len; + mnp->txdata.FragmentCount = 1; + mnp->txdata.FragmentTable[0].FragmentLength = iob_len ( iobuf ); + mnp->txdata.FragmentTable[0].FragmentBuffer = iobuf->data; + mnp->tx.token.Packet.TxData = &mnp->txdata; + + /* Record as in use */ + mnp->tx.busy = 1; + + /* Transmit packet */ + if ( ( efirc = mnp->mnp->Transmit ( mnp->mnp, &mnp->tx.token ) ) != 0 ){ + rc = -EEFI ( efirc ); + DBGC ( mnp, "MNP %s could not transmit: %s\n", + netdev->name, strerror ( rc ) ); + mnp->tx.busy = 0; + return rc; + } + + /* Record I/O buffer */ + mnp->txbuf = iobuf; + + return 0; +} + +/** + * Refill receive token + * + * @v netdev Network device + */ +static void mnpnet_refill_rx ( struct net_device *netdev ) { + struct mnp_nic *mnp = netdev->priv; + EFI_STATUS efirc; + int rc; + + /* Do nothing if receive token is still in use */ + if ( mnp->rx.busy ) + return; + + /* Mark as in use */ + mnp->rx.busy = 1; + + /* Queue receive token */ + if ( ( efirc = mnp->mnp->Receive ( mnp->mnp, &mnp->rx.token ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( mnp, "MNP %s could not receive: %s\n", + netdev->name, strerror ( rc ) ); + /* Wait for next refill */ + mnp->rx.busy = 0; + return; + } +} + +/** + * Poll for completed packets + * + * @v netdev Network device + */ +static void mnpnet_poll_tx ( struct net_device *netdev ) { + struct mnp_nic *mnp = netdev->priv; + struct io_buffer *iobuf; + EFI_STATUS efirc; + int rc; + + /* Do nothing if transmit token is still in use */ + if ( mnp->tx.busy ) + return; + + /* Do nothing unless we have a completion */ + if ( ! mnp->txbuf ) + return; + + /* Get completion status */ + efirc = mnp->tx.token.Status; + rc = ( efirc ? -EEFI ( efirc ) : 0 ); + + /* Complete transmission */ + iobuf = mnp->txbuf; + mnp->txbuf = NULL; + netdev_tx_complete_err ( netdev, iobuf, rc ); +} + +/** + * Poll for received packets + * + * @v netdev Network device + */ +static void mnpnet_poll_rx ( struct net_device *netdev ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + struct mnp_nic *mnp = netdev->priv; + EFI_MANAGED_NETWORK_RECEIVE_DATA *rxdata; + struct io_buffer *iobuf; + size_t len; + EFI_STATUS efirc; + int rc; + + /* Do nothing unless we have a completion */ + if ( mnp->rx.busy ) + return; + rxdata = mnp->rx.token.Packet.RxData; + + /* Get completion status */ + if ( ( efirc = mnp->rx.token.Status ) != 0 ) { + rc = -EEFI ( efirc ); + netdev_rx_err ( netdev, NULL, rc ); + goto recycle; + } + + /* Allocate and fill I/O buffer */ + len = rxdata->PacketLength; + iobuf = alloc_iob ( len ); + if ( ! iobuf ) { + netdev_rx_err ( netdev, NULL, -ENOMEM ); + goto recycle; + } + memcpy ( iob_put ( iobuf, len ), rxdata->MediaHeader, len ); + + /* Hand off to network stack */ + netdev_rx ( netdev, iobuf ); + + recycle: + /* Recycle token */ + bs->SignalEvent ( rxdata->RecycleEvent ); +} + +/** + * Poll for completed packets + * + * @v netdev Network device + */ +static void mnpnet_poll ( struct net_device *netdev ) { + struct mnp_nic *mnp = netdev->priv; + + /* Do nothing if shutdown is in progress */ + if ( efi_shutdown_in_progress ) + return; + + /* Poll interface */ + mnp->mnp->Poll ( mnp->mnp ); + + /* Process any transmit completions */ + mnpnet_poll_tx ( netdev ); + + /* Process any receive completions */ + mnpnet_poll_rx ( netdev ); + + /* Refill receive token */ + mnpnet_refill_rx ( netdev ); +} + +/** + * Open network device + * + * @v netdev Network device + * @ret rc Return status code + */ +static int mnpnet_open ( struct net_device *netdev ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + static EFI_MANAGED_NETWORK_CONFIG_DATA config = { + .EnableUnicastReceive = TRUE, + .EnableMulticastReceive = TRUE, + .EnableBroadcastReceive = TRUE, + .EnablePromiscuousReceive = TRUE, + .FlushQueuesOnReset = TRUE, + .DisableBackgroundPolling = TRUE, + }; + struct mnp_nic *mnp = netdev->priv; + EFI_STATUS efirc; + int rc; + + /* Create transmit event */ + if ( ( efirc = bs->CreateEvent ( EVT_NOTIFY_SIGNAL, TPL_NOTIFY, + mnpnet_event, &mnp->tx, + &mnp->tx.token.Event ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( mnp, "MNP %s could not create TX event: %s\n", + netdev->name, strerror ( rc ) ); + goto err_tx_event; + } + + /* Create receive event */ + if ( ( efirc = bs->CreateEvent ( EVT_NOTIFY_SIGNAL, TPL_NOTIFY, + mnpnet_event, &mnp->rx, + &mnp->rx.token.Event ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( mnp, "MNP %s could not create RX event: %s\n", + netdev->name, strerror ( rc ) ); + goto err_rx_event; + } + + /* Configure MNP */ + if ( ( efirc = mnp->mnp->Configure ( mnp->mnp, &config ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( mnp, "MNP %s could not configure: %s\n", + netdev->name, strerror ( rc ) ); + goto err_configure; + } + + /* Refill receive token */ + mnpnet_refill_rx ( netdev ); + + return 0; + + mnp->mnp->Configure ( mnp->mnp, NULL ); + err_configure: + bs->CloseEvent ( mnp->rx.token.Event ); + err_rx_event: + bs->CloseEvent ( mnp->tx.token.Event ); + err_tx_event: + return rc; +} + +/** + * Close network device + * + * @v netdev Network device + */ +static void mnpnet_close ( struct net_device *netdev ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + struct mnp_nic *mnp = netdev->priv; + + /* Reset MNP (unless whole system shutdown is in progress) */ + if ( ! efi_shutdown_in_progress ) + mnp->mnp->Configure ( mnp->mnp, NULL ); + + /* Close events */ + bs->CloseEvent ( mnp->rx.token.Event ); + bs->CloseEvent ( mnp->tx.token.Event ); + + /* Reset tokens */ + mnp->tx.busy = 0; + mnp->rx.busy = 0; + + /* Discard any incomplete I/O buffer */ + if ( mnp->txbuf ) { + netdev_tx_complete_err ( netdev, mnp->txbuf, -ECANCELED ); + mnp->txbuf = NULL; + } +} + +/** MNP network device operations */ +static struct net_device_operations mnpnet_operations = { + .open = mnpnet_open, + .close = mnpnet_close, + .transmit = mnpnet_transmit, + .poll = mnpnet_poll, +}; + +/** + * Attach driver to device + * + * @v efidev EFI device + * @ret rc Return status code + */ +int mnpnet_start ( struct efi_device *efidev ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + EFI_HANDLE device = efidev->device; + EFI_GUID *binding = &efi_managed_network_service_binding_protocol_guid; + EFI_SIMPLE_NETWORK_MODE mode; + union { + EFI_MANAGED_NETWORK_PROTOCOL *mnp; + void *interface; + } u; + struct net_device *netdev; + struct mnp_nic *mnp; + EFI_STATUS efirc; + int rc; + + /* Allocate and initalise structure */ + netdev = alloc_etherdev ( sizeof ( *mnp ) ); + if ( ! netdev ) { + rc = -ENOMEM; + goto err_alloc; + } + netdev_init ( netdev, &mnpnet_operations ); + mnp = netdev->priv; + mnp->efidev = efidev; + efidev_set_drvdata ( efidev, netdev ); + + /* Populate underlying device information */ + efi_device_info ( device, "MNP", &mnp->dev ); + mnp->dev.driver_name = "MNP"; + mnp->dev.parent = &efidev->dev; + list_add ( &mnp->dev.siblings, &efidev->dev.children ); + INIT_LIST_HEAD ( &mnp->dev.children ); + netdev->dev = &mnp->dev; + + /* Create MNP child */ + if ( ( rc = efi_service_add ( device, binding, + &efidev->child ) ) != 0 ) { + DBGC ( mnp, "MNP %s could not create child: %s\n", + efi_handle_name ( device ), strerror ( rc ) ); + goto err_service; + } + + /* Open MNP protocol */ + if ( ( efirc = bs->OpenProtocol ( efidev->child, + &efi_managed_network_protocol_guid, + &u.interface, efi_image_handle, + efidev->child, + ( EFI_OPEN_PROTOCOL_BY_DRIVER | + EFI_OPEN_PROTOCOL_EXCLUSIVE )))!=0){ + rc = -EEFI ( efirc ); + DBGC ( mnp, "MNP %s could not open MNP protocol: %s\n", + efi_handle_name ( device ), strerror ( rc ) ); + goto err_open; + } + mnp->mnp = u.mnp; + + /* Get configuration */ + efirc = mnp->mnp->GetModeData ( mnp->mnp, NULL, &mode ); + if ( ( efirc != 0 ) && ( efirc != EFI_NOT_STARTED ) ) { + rc = -EEFI ( efirc ); + DBGC ( mnp, "MNP %s could not get mode data: %s\n", + efi_handle_name ( device ), strerror ( rc ) ); + goto err_mode; + } + + /* Populate network device parameters */ + if ( mode.HwAddressSize != netdev->ll_protocol->hw_addr_len ) { + DBGC ( device, "MNP %s has invalid hardware address length " + "%d\n", efi_handle_name ( device ), mode.HwAddressSize ); + rc = -ENOTSUP; + goto err_hw_addr_len; + } + memcpy ( netdev->hw_addr, &mode.PermanentAddress, + netdev->ll_protocol->hw_addr_len ); + if ( mode.HwAddressSize != netdev->ll_protocol->ll_addr_len ) { + DBGC ( device, "MNP %s has invalid link-layer address length " + "%d\n", efi_handle_name ( device ), mode.HwAddressSize ); + rc = -ENOTSUP; + goto err_ll_addr_len; + } + memcpy ( netdev->ll_addr, &mode.CurrentAddress, + netdev->ll_protocol->ll_addr_len ); + + /* Register network device */ + if ( ( rc = register_netdev ( netdev ) ) != 0 ) + goto err_register; + DBGC ( mnp, "MNP %s registered as %s\n", + efi_handle_name ( device ), netdev->name ); + + /* Mark as link up: we don't handle link state */ + netdev_link_up ( netdev ); + + return 0; + + unregister_netdev ( netdev ); + err_register: + err_ll_addr_len: + err_hw_addr_len: + err_mode: + bs->CloseProtocol ( efidev->child, &efi_managed_network_protocol_guid, + efi_image_handle, efidev->child ); + err_open: + efi_service_del ( device, binding, efidev->child ); + err_service: + list_del ( &mnp->dev.siblings ); + netdev_nullify ( netdev ); + netdev_put ( netdev ); + err_alloc: + return rc; +} + +/** + * Detach driver from device + * + * @v efidev EFI device + */ +void mnpnet_stop ( struct efi_device *efidev ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + EFI_GUID *binding = &efi_managed_network_service_binding_protocol_guid; + struct net_device *netdev = efidev_get_drvdata ( efidev ); + struct mnp_nic *mnp = netdev->priv; + + /* Unregister network device */ + unregister_netdev ( netdev ); + + /* Close MNP protocol */ + bs->CloseProtocol ( efidev->child, &efi_managed_network_protocol_guid, + efi_image_handle, efidev->child ); + + /* Remove MNP child (unless whole system shutdown is in progress) */ + if ( ! efi_shutdown_in_progress ) + efi_service_del ( efidev->device, binding, efidev->child ); + + /* Free network device */ + list_del ( &mnp->dev.siblings ); + netdev_nullify ( netdev ); + netdev_put ( netdev ); +} diff --git a/src/drivers/net/efi/mnpnet.h b/src/drivers/net/efi/mnpnet.h new file mode 100644 index 000000000..afed62aae --- /dev/null +++ b/src/drivers/net/efi/mnpnet.h @@ -0,0 +1,17 @@ +#ifndef _MNPNET_H +#define _MNPNET_H + +/** @file + * + * MNP NIC driver + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +struct efi_device; + +extern int mnpnet_start ( struct efi_device *efidev ); +extern void mnpnet_stop ( struct efi_device *efidev ); + +#endif /* _MNPNET_H */ diff --git a/src/drivers/net/efi/snp.c b/src/drivers/net/efi/snp.c index 1920cdbc5..cac8b38e2 100644 --- a/src/drivers/net/efi/snp.c +++ b/src/drivers/net/efi/snp.c @@ -23,11 +23,8 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); -#include #include #include -#include -#include #include "snpnet.h" #include "nii.h" @@ -37,53 +34,6 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); * */ -/** - * Check to see if driver supports a device - * - * @v device EFI device handle - * @v protocol Protocol GUID - * @ret rc Return status code - */ -static int snp_nii_supported ( EFI_HANDLE device, EFI_GUID *protocol ) { - EFI_BOOT_SERVICES *bs = efi_systab->BootServices; - EFI_HANDLE parent; - EFI_STATUS efirc; - int rc; - - /* Check that this is not a device we are providing ourselves */ - if ( find_snpdev ( device ) != NULL ) { - DBGCP ( device, "HANDLE %s is provided by this binary\n", - efi_handle_name ( device ) ); - return -ENOTTY; - } - - /* Test for presence of protocol */ - if ( ( efirc = bs->OpenProtocol ( device, protocol, - NULL, efi_image_handle, device, - EFI_OPEN_PROTOCOL_TEST_PROTOCOL))!=0){ - DBGCP ( device, "HANDLE %s is not a %s device\n", - efi_handle_name ( device ), - efi_guid_ntoa ( protocol ) ); - return -EEFI ( efirc ); - } - - /* Check that there are no instances of this protocol further - * up this device path. - */ - if ( ( rc = efi_locate_device ( device, protocol, - &parent, 1 ) ) == 0 ) { - DBGC2 ( device, "HANDLE %s has %s-supporting parent ", - efi_handle_name ( device ), - efi_guid_ntoa ( protocol ) ); - DBGC2 ( device, "%s\n", efi_handle_name ( parent ) ); - return -ENOTTY; - } - - DBGC ( device, "HANDLE %s is a %s device\n", - efi_handle_name ( device ), efi_guid_ntoa ( protocol ) ); - return 0; -} - /** * Check to see if driver supports a device * @@ -92,7 +42,7 @@ static int snp_nii_supported ( EFI_HANDLE device, EFI_GUID *protocol ) { */ static int snp_supported ( EFI_HANDLE device ) { - return snp_nii_supported ( device, &efi_simple_network_protocol_guid ); + return snpnet_supported ( device, &efi_simple_network_protocol_guid ); } /** @@ -103,7 +53,7 @@ static int snp_supported ( EFI_HANDLE device ) { */ static int nii_supported ( EFI_HANDLE device ) { - return snp_nii_supported ( device, &efi_nii31_protocol_guid ); + return snpnet_supported ( device, &efi_nii31_protocol_guid ); } /** EFI SNP driver */ diff --git a/src/drivers/net/efi/snpnet.c b/src/drivers/net/efi/snpnet.c index b8bf963e9..6ce731d78 100644 --- a/src/drivers/net/efi/snpnet.c +++ b/src/drivers/net/efi/snpnet.c @@ -33,6 +33,7 @@ FILE_LICENCE ( GPL2_OR_LATER ); #include #include #include +#include #include "snpnet.h" /** @file @@ -484,6 +485,53 @@ static struct net_device_operations snpnet_operations = { .poll = snpnet_poll, }; +/** + * Check to see if driver supports a device + * + * @v device EFI device handle + * @v protocol Protocol GUID + * @ret rc Return status code + */ +int snpnet_supported ( EFI_HANDLE device, EFI_GUID *protocol ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + EFI_HANDLE parent; + EFI_STATUS efirc; + int rc; + + /* Check that this is not a device we are providing ourselves */ + if ( find_snpdev ( device ) != NULL ) { + DBGCP ( device, "HANDLE %s is provided by this binary\n", + efi_handle_name ( device ) ); + return -ENOTTY; + } + + /* Test for presence of protocol */ + if ( ( efirc = bs->OpenProtocol ( device, protocol, + NULL, efi_image_handle, device, + EFI_OPEN_PROTOCOL_TEST_PROTOCOL))!=0){ + DBGCP ( device, "HANDLE %s is not a %s device\n", + efi_handle_name ( device ), + efi_guid_ntoa ( protocol ) ); + return -EEFI ( efirc ); + } + + /* Check that there are no instances of this protocol further + * up this device path. + */ + if ( ( rc = efi_locate_device ( device, protocol, + &parent, 1 ) ) == 0 ) { + DBGC2 ( device, "HANDLE %s has %s-supporting parent ", + efi_handle_name ( device ), + efi_guid_ntoa ( protocol ) ); + DBGC2 ( device, "%s\n", efi_handle_name ( parent ) ); + return -ENOTTY; + } + + DBGC ( device, "HANDLE %s is a %s device\n", + efi_handle_name ( device ), efi_guid_ntoa ( protocol ) ); + return 0; +} + /** * Attach driver to device * diff --git a/src/drivers/net/efi/snpnet.h b/src/drivers/net/efi/snpnet.h index e6d31d5e4..4699c7892 100644 --- a/src/drivers/net/efi/snpnet.h +++ b/src/drivers/net/efi/snpnet.h @@ -11,6 +11,7 @@ FILE_LICENCE ( GPL2_OR_LATER ); struct efi_device; +extern int snpnet_supported ( EFI_HANDLE device, EFI_GUID *protocol ); extern int snpnet_start ( struct efi_device *efidev ); extern void snpnet_stop ( struct efi_device *efidev ); diff --git a/src/drivers/net/efi/snponly.c b/src/drivers/net/efi/snponly.c index 163704636..6786f3e83 100644 --- a/src/drivers/net/efi/snponly.c +++ b/src/drivers/net/efi/snponly.c @@ -32,6 +32,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include #include "snpnet.h" +#include "mnpnet.h" #include "nii.h" /** @file @@ -74,6 +75,11 @@ static struct chained_protocol chained_nii = { .protocol = &efi_nii31_protocol_guid, }; +/** Chainloaded MNP protocol */ +static struct chained_protocol chained_mnp = { + .protocol = &efi_managed_network_service_binding_protocol_guid, +}; + /** * Locate chainloaded protocol * @@ -208,6 +214,17 @@ static int niionly_supported ( EFI_HANDLE device ) { return chained_supported ( device, &chained_nii ); } +/** + * Check to see if driver supports a device + * + * @v device EFI device handle + * @ret rc Return status code + */ +static int mnponly_supported ( EFI_HANDLE device ) { + + return chained_supported ( device, &chained_mnp ); +} + /** EFI SNP chainloading-device-only driver */ struct efi_driver snponly_driver __efi_driver ( EFI_DRIVER_NORMAL ) = { .name = "SNPONLY", @@ -224,6 +241,14 @@ struct efi_driver niionly_driver __efi_driver ( EFI_DRIVER_NORMAL ) = { .stop = nii_stop, }; +/** EFI MNP chainloading-device-only driver */ +struct efi_driver mnponly_driver __efi_driver ( EFI_DRIVER_NORMAL ) = { + .name = "MNPONLY", + .supported = mnponly_supported, + .start = mnpnet_start, + .stop = mnpnet_stop, +}; + /** * Initialise EFI chainloaded-device-only driver * @@ -232,6 +257,7 @@ static void chained_init ( void ) { chained_locate ( &chained_snp ); chained_locate ( &chained_nii ); + chained_locate ( &chained_mnp ); } /** EFI chainloaded-device-only initialisation function */ diff --git a/src/include/ipxe/errfile.h b/src/include/ipxe/errfile.h index 083c77e1f..0ab37230a 100644 --- a/src/include/ipxe/errfile.h +++ b/src/include/ipxe/errfile.h @@ -223,6 +223,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define ERRFILE_ice ( ERRFILE_DRIVER | 0x00d20000 ) #define ERRFILE_ecam ( ERRFILE_DRIVER | 0x00d30000 ) #define ERRFILE_pcibridge ( ERRFILE_DRIVER | 0x00d40000 ) +#define ERRFILE_mnpnet ( ERRFILE_DRIVER | 0x00d50000 ) #define ERRFILE_aoe ( ERRFILE_NET | 0x00000000 ) #define ERRFILE_arp ( ERRFILE_NET | 0x00010000 ) -- cgit v1.2.3-55-g7522 From 170bbfd4875b2a3479101b5f5892cdedcf857fd0 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Tue, 26 Mar 2024 15:16:33 +0000 Subject: [efi] Add efi_path_mac() to parse a MAC address from an EFI device path Signed-off-by: Michael Brown --- src/include/ipxe/efi/efi_path.h | 1 + src/interface/efi/efi_path.c | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+) (limited to 'src/include') diff --git a/src/include/ipxe/efi/efi_path.h b/src/include/ipxe/efi/efi_path.h index 503bd4347..57fce4028 100644 --- a/src/include/ipxe/efi/efi_path.h +++ b/src/include/ipxe/efi/efi_path.h @@ -43,6 +43,7 @@ efi_path_prev ( EFI_DEVICE_PATH_PROTOCOL *path, extern EFI_DEVICE_PATH_PROTOCOL * efi_path_end ( EFI_DEVICE_PATH_PROTOCOL *path ); extern size_t efi_path_len ( EFI_DEVICE_PATH_PROTOCOL *path ); +extern void * efi_path_mac ( EFI_DEVICE_PATH_PROTOCOL *path ); extern unsigned int efi_path_vlan ( EFI_DEVICE_PATH_PROTOCOL *path ); extern int efi_path_guid ( EFI_DEVICE_PATH_PROTOCOL *path, union uuid *uuid ); extern struct uri * efi_path_uri ( EFI_DEVICE_PATH_PROTOCOL *path ); diff --git a/src/interface/efi/efi_path.c b/src/interface/efi/efi_path.c index 4e37d248a..23f1bb84d 100644 --- a/src/interface/efi/efi_path.c +++ b/src/interface/efi/efi_path.c @@ -111,6 +111,30 @@ size_t efi_path_len ( EFI_DEVICE_PATH_PROTOCOL *path ) { return ( ( ( void * ) end ) - ( ( void * ) path ) ); } +/** + * Get MAC address from device path + * + * @v path Device path + * @ret mac MAC address, or NULL if not found + */ +void * efi_path_mac ( EFI_DEVICE_PATH_PROTOCOL *path ) { + EFI_DEVICE_PATH_PROTOCOL *next; + MAC_ADDR_DEVICE_PATH *mac; + + /* Search for MAC address path */ + for ( ; ( next = efi_path_next ( path ) ) ; path = next ) { + if ( ( path->Type == MESSAGING_DEVICE_PATH ) && + ( path->SubType == MSG_MAC_ADDR_DP ) ) { + mac = container_of ( path, MAC_ADDR_DEVICE_PATH, + Header ); + return &mac->MacAddress; + } + } + + /* No MAC address found */ + return NULL; +} + /** * Get VLAN tag from device path * -- cgit v1.2.3-55-g7522 From 9bbe77669c6e2b71826449d854f5aa0e2cee7767 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Tue, 26 Mar 2024 15:17:23 +0000 Subject: [efi] Extract basic network settings from loaded image device path The UEFI HTTP boot mechanism is extraordinarily badly designed, even by the standards of the UEFI specification in general. It has the symptoms of a feature that has been designed entirely in terms of user stories, without any consideration at all being given to the underlying technical architecture. It does work, provided that you are doing precisely and only what was envisioned by the product owner. If you want to try anything outside the bounds of the product owner's extremely limited imagination, then you are almost certainly about to enter a world of pain. As one very minor example of this: the cached DHCP packet is not available when using HTTP boot. The UEFI HTTP boot code does perform DHCP, but it pointlessly and unhelpfully throws away the DHCP packet and trashes the network interface configuration before handing over to the downloaded executable. Work around this imbecility by parsing and applying the few network configuration settings that are persisted into the loaded image's device path. This is limited to very basic information such as the IP address, gateway address, and DNS server address, but it does at least provide enough for a functional routing table. Signed-off-by: Michael Brown --- src/include/ipxe/settings.h | 2 + src/interface/efi/efi_path.c | 241 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 243 insertions(+) (limited to 'src/include') diff --git a/src/include/ipxe/settings.h b/src/include/ipxe/settings.h index e042b9758..424188deb 100644 --- a/src/include/ipxe/settings.h +++ b/src/include/ipxe/settings.h @@ -445,6 +445,8 @@ len6_setting __setting ( SETTING_IP6, len6 ); extern const struct setting gateway6_setting __setting ( SETTING_IP6, gateway6 ); extern const struct setting +dns6_setting __setting ( SETTING_IP6_EXTRA, dns6 ); +extern const struct setting hostname_setting __setting ( SETTING_HOST, hostname ); extern const struct setting domain_setting __setting ( SETTING_IP_EXTRA, domain ); diff --git a/src/interface/efi/efi_path.c b/src/interface/efi/efi_path.c index 23f1bb84d..ac3c04987 100644 --- a/src/interface/efi/efi_path.c +++ b/src/interface/efi/efi_path.c @@ -34,6 +34,8 @@ FILE_LICENCE ( GPL2_OR_LATER ); #include #include #include +#include +#include #include #include #include @@ -44,6 +46,40 @@ FILE_LICENCE ( GPL2_OR_LATER ); * */ +/** An EFI device path settings block */ +struct efi_path_settings { + /** Settings interface */ + struct settings settings; + /** Device path */ + EFI_DEVICE_PATH_PROTOCOL *path; +}; + +/** An EFI device path setting */ +struct efi_path_setting { + /** Setting */ + const struct setting *setting; + /** + * Fetch setting + * + * @v pathset Path setting + * @v path Device path + * @v data Buffer to fill with setting data + * @v len Length of buffer + * @ret len Length of setting data, or negative error + */ + int ( * fetch ) ( struct efi_path_setting *pathset, + EFI_DEVICE_PATH_PROTOCOL *path, + void *data, size_t len ); + /** Path type */ + uint8_t type; + /** Path subtype */ + uint8_t subtype; + /** Offset within device path */ + uint8_t offset; + /** Length (if fixed) */ + uint8_t len; +}; + /** * Find next element in device path * @@ -657,3 +693,208 @@ EFI_DEVICE_PATH_PROTOCOL * efi_describe ( struct interface *intf ) { intf_put ( dest ); return path; } + +/** + * Fetch an EFI device path fixed-size setting + * + * @v pathset Path setting + * @v path Device path + * @v data Buffer to fill with setting data + * @v len Length of buffer + * @ret len Length of setting data, or negative error + */ +static int efi_path_fetch_fixed ( struct efi_path_setting *pathset, + EFI_DEVICE_PATH_PROTOCOL *path, + void *data, size_t len ) { + + /* Copy data */ + if ( len > pathset->len ) + len = pathset->len; + memcpy ( data, ( ( ( void * ) path ) + pathset->offset ), len ); + + return pathset->len; +} + +/** + * Fetch an EFI device path DNS setting + * + * @v pathset Path setting + * @v path Device path + * @v data Buffer to fill with setting data + * @v len Length of buffer + * @ret len Length of setting data, or negative error + */ +static int efi_path_fetch_dns ( struct efi_path_setting *pathset, + EFI_DEVICE_PATH_PROTOCOL *path, + void *data, size_t len ) { + DNS_DEVICE_PATH *dns = container_of ( path, DNS_DEVICE_PATH, Header ); + unsigned int count; + unsigned int i; + size_t frag_len; + + /* Check applicability */ + if ( ( !! dns->IsIPv6 ) != + ( pathset->setting->type == &setting_type_ipv6 ) ) + return -ENOENT; + + /* Calculate number of addresses */ + count = ( ( ( ( path->Length[1] << 8 ) | path->Length[0] ) - + pathset->offset ) / sizeof ( dns->DnsServerIp[0] ) ); + + /* Copy data */ + for ( i = 0 ; i < count ; i++ ) { + frag_len = len; + if ( frag_len > pathset->len ) + frag_len = pathset->len; + memcpy ( data, &dns->DnsServerIp[i], frag_len ); + data += frag_len; + len -= frag_len; + } + + return ( count * pathset->len ); +} + +/** EFI device path settings */ +static struct efi_path_setting efi_path_settings[] = { + { &ip_setting, efi_path_fetch_fixed, MESSAGING_DEVICE_PATH, + MSG_IPv4_DP, offsetof ( IPv4_DEVICE_PATH, LocalIpAddress ), + sizeof ( struct in_addr ) }, + { &netmask_setting, efi_path_fetch_fixed, MESSAGING_DEVICE_PATH, + MSG_IPv4_DP, offsetof ( IPv4_DEVICE_PATH, SubnetMask ), + sizeof ( struct in_addr ) }, + { &gateway_setting, efi_path_fetch_fixed, MESSAGING_DEVICE_PATH, + MSG_IPv4_DP, offsetof ( IPv4_DEVICE_PATH, GatewayIpAddress ), + sizeof ( struct in_addr ) }, + { &ip6_setting, efi_path_fetch_fixed, MESSAGING_DEVICE_PATH, + MSG_IPv6_DP, offsetof ( IPv6_DEVICE_PATH, LocalIpAddress ), + sizeof ( struct in6_addr ) }, + { &len6_setting, efi_path_fetch_fixed, MESSAGING_DEVICE_PATH, + MSG_IPv6_DP, offsetof ( IPv6_DEVICE_PATH, PrefixLength ), + sizeof ( uint8_t ) }, + { &gateway6_setting, efi_path_fetch_fixed, MESSAGING_DEVICE_PATH, + MSG_IPv6_DP, offsetof ( IPv6_DEVICE_PATH, GatewayIpAddress ), + sizeof ( struct in6_addr ) }, + { &dns_setting, efi_path_fetch_dns, MESSAGING_DEVICE_PATH, + MSG_DNS_DP, offsetof ( DNS_DEVICE_PATH, DnsServerIp ), + sizeof ( struct in_addr ) }, + { &dns6_setting, efi_path_fetch_dns, MESSAGING_DEVICE_PATH, + MSG_DNS_DP, offsetof ( DNS_DEVICE_PATH, DnsServerIp ), + sizeof ( struct in6_addr ) }, +}; + +/** + * Fetch value of EFI device path setting + * + * @v settings Settings block + * @v setting Setting to fetch + * @v data Buffer to fill with setting data + * @v len Length of buffer + * @ret len Length of setting data, or negative error + */ +static int efi_path_fetch ( struct settings *settings, struct setting *setting, + void *data, size_t len ) { + struct efi_path_settings *pathsets = + container_of ( settings, struct efi_path_settings, settings ); + EFI_DEVICE_PATH_PROTOCOL *path = pathsets->path; + EFI_DEVICE_PATH_PROTOCOL *next; + struct efi_path_setting *pathset; + unsigned int i; + int ret; + + /* Find matching path setting, if any */ + for ( i = 0 ; i < ( sizeof ( efi_path_settings ) / + sizeof ( efi_path_settings[0] ) ) ; i++ ) { + + /* Check for a matching setting */ + pathset = &efi_path_settings[i]; + if ( setting_cmp ( setting, pathset->setting ) != 0 ) + continue; + + /* Find matching device path element, if any */ + for ( ; ( next = efi_path_next ( path ) ) ; path = next ) { + + /* Check for a matching path type */ + if ( ( path->Type != pathset->type ) || + ( path->SubType != pathset->subtype ) ) + continue; + + /* Fetch value */ + if ( ( ret = pathset->fetch ( pathset, path, + data, len ) ) < 0 ) + return ret; + + /* Apply default type, if not already set */ + if ( ! setting->type ) + setting->type = pathset->setting->type; + + return ret; + } + break; + } + + return -ENOENT; +} + +/** EFI device path settings operations */ +static struct settings_operations efi_path_settings_operations = { + .fetch = efi_path_fetch, +}; + +/** + * Create per-netdevice EFI path settings + * + * @v netdev Network device + * @v priv Private data + * @ret rc Return status code + */ +static int efi_path_net_probe ( struct net_device *netdev, void *priv ) { + struct efi_path_settings *pathsets = priv; + struct settings *settings = &pathsets->settings; + EFI_DEVICE_PATH_PROTOCOL *path = efi_loaded_image_path; + unsigned int vlan; + void *mac; + int rc; + + /* Check applicability */ + pathsets->path = path; + mac = efi_path_mac ( path ); + vlan = efi_path_vlan ( path ); + if ( ( mac == NULL ) || + ( memcmp ( mac, netdev->ll_addr, + netdev->ll_protocol->ll_addr_len ) != 0 ) || + ( vlan != vlan_tag ( netdev ) ) ) { + DBGC ( settings, "EFI path %s does not apply to %s\n", + efi_devpath_text ( path ), netdev->name ); + return 0; + } + + /* Never override a real DHCP settings block */ + if ( find_child_settings ( netdev_settings ( netdev ), + DHCP_SETTINGS_NAME ) ) { + DBGC ( settings, "EFI path %s not overriding %s DHCP " + "settings\n", efi_devpath_text ( path ), netdev->name ); + return 0; + } + + /* Initialise and register settings */ + settings_init ( settings, &efi_path_settings_operations, + &netdev->refcnt, NULL ); + if ( ( rc = register_settings ( settings, netdev_settings ( netdev ), + DHCP_SETTINGS_NAME ) ) != 0 ) { + DBGC ( settings, "EFI path %s could not register for %s: %s\n", + efi_devpath_text ( path ), netdev->name, + strerror ( rc ) ); + return rc; + } + DBGC ( settings, "EFI path %s registered for %s\n", + efi_devpath_text ( path ), netdev->name ); + + return 0; +} + +/** EFI path settings per-netdevice driver */ +struct net_driver efi_path_net_driver __net_driver = { + .name = "EFI path", + .priv_len = sizeof ( struct efi_path_settings ), + .probe = efi_path_net_probe, +}; -- cgit v1.2.3-55-g7522 From 37850e0e854292d074c2d35d18d7bb78d8e6ff85 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Wed, 27 Mar 2024 14:28:47 +0000 Subject: [build] Fix build failures with random versions of gcc For unknown reasons, miscellaneous versions of gcc seem to struggle with the static assertions used to ensure the correct layout of the GCM structures. Adjust the assertions to use offsetof() rather than direct pointer comparison, on the basis that offsetof() must be a compile-time constant value. Signed-off-by: Michael Brown --- src/crypto/gcm.c | 26 +++++++++++++++----------- src/include/ipxe/gcm.h | 7 +++---- 2 files changed, 18 insertions(+), 15 deletions(-) (limited to 'src/include') diff --git a/src/crypto/gcm.c b/src/crypto/gcm.c index a32890d59..b93925d07 100644 --- a/src/crypto/gcm.c +++ b/src/crypto/gcm.c @@ -109,6 +109,9 @@ static union gcm_block gcm_cached_mult[256]; */ static uint16_t gcm_cached_reduce[256]; +/** Offset of a field within GCM context */ +#define gcm_offset( field ) offsetof ( struct gcm_context, field ) + /** * Reverse bits in a byte * @@ -470,17 +473,13 @@ int gcm_setkey ( struct gcm_context *context, const void *key, size_t keylen, */ void gcm_setiv ( struct gcm_context *context, const void *iv, size_t ivlen ) { - /* Sanity check: ensure that memset()s will clear expected state */ - build_assert ( &context->hash < &context->ctr ); - build_assert ( &context->len < &context->ctr ); - build_assert ( &context->ctr < &context->key ); - build_assert ( ( ( void * ) &context->raw_cipher ) > - ( ( void * ) &context->key ) ); - build_assert ( ( ( void * ) context->raw_ctx ) > - ( ( void * ) &context->key ) ); - /* Reset non-key state */ - memset ( context, 0, offsetof ( typeof ( *context ), key ) ); + memset ( context, 0, gcm_offset ( key ) ); + build_assert ( gcm_offset ( key ) > gcm_offset ( hash ) ); + build_assert ( gcm_offset ( key ) > gcm_offset ( len ) ); + build_assert ( gcm_offset ( key ) > gcm_offset ( ctr ) ); + build_assert ( gcm_offset ( key ) < gcm_offset ( raw_cipher ) ); + build_assert ( gcm_offset ( key ) < gcm_offset ( raw_ctx ) ); /* Reset counter */ context->ctr.ctr.value = cpu_to_be32 ( 1 ); @@ -499,7 +498,12 @@ void gcm_setiv ( struct gcm_context *context, const void *iv, size_t ivlen ) { assert ( context->len.len.add == 0 ); /* Reset non-key, non-counter state */ - memset ( context, 0, offsetof ( typeof ( *context ), ctr ) ); + memset ( context, 0, gcm_offset ( ctr ) ); + build_assert ( gcm_offset ( ctr ) > gcm_offset ( hash ) ); + build_assert ( gcm_offset ( ctr ) > gcm_offset ( len ) ); + build_assert ( gcm_offset ( ctr ) < gcm_offset ( key ) ); + build_assert ( gcm_offset ( ctr ) < gcm_offset ( raw_cipher ) ); + build_assert ( gcm_offset ( ctr ) < gcm_offset ( raw_ctx ) ); } DBGC2 ( context, "GCM %p Y[0]:\n", context ); diff --git a/src/include/ipxe/gcm.h b/src/include/ipxe/gcm.h index 4864445d2..2c785a977 100644 --- a/src/include/ipxe/gcm.h +++ b/src/include/ipxe/gcm.h @@ -89,10 +89,9 @@ static int _gcm_name ## _setkey ( void *ctx, const void *key, \ size_t keylen ) { \ struct _gcm_name ## _context *context = ctx; \ build_assert ( _blocksize == sizeof ( context->gcm.key ) ); \ - build_assert ( ( ( void * ) &context->gcm ) == \ - ( ( void * ) context ) ); \ - build_assert ( ( ( void * ) &context->raw ) == \ - ( ( void * ) context->gcm.raw_ctx ) ); \ + build_assert ( offsetof ( typeof ( *context ), gcm ) == 0 ); \ + build_assert ( offsetof ( typeof ( *context ), raw ) == \ + offsetof ( typeof ( *context ), gcm.raw_ctx ) ); \ return gcm_setkey ( &context->gcm, key, keylen, &_raw_cipher ); \ } \ static void _gcm_name ## _setiv ( void *ctx, const void *iv, \ -- cgit v1.2.3-55-g7522 From b52b4a46d9ee854130db7a8927f33391fc6ba1fe Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Fri, 29 Mar 2024 12:43:24 +0000 Subject: [efi] Allow for allocating EFI devices from arbitrary handles Split out the code that allocates our internal struct efi_device representations, to allow for the creation of temporary MNP devices in order to download the autoexec.ipxe script. Signed-off-by: Michael Brown --- src/include/ipxe/efi/efi_driver.h | 2 + src/interface/efi/efi_driver.c | 107 +++++++++++++++++++++++--------------- 2 files changed, 67 insertions(+), 42 deletions(-) (limited to 'src/include') diff --git a/src/include/ipxe/efi/efi_driver.h b/src/include/ipxe/efi/efi_driver.h index 411e93642..7b64e1e0b 100644 --- a/src/include/ipxe/efi/efi_driver.h +++ b/src/include/ipxe/efi/efi_driver.h @@ -86,6 +86,8 @@ static inline void * efidev_get_drvdata ( struct efi_device *efidev ) { return efidev->priv; } +extern struct efi_device * efidev_alloc ( EFI_HANDLE device ); +extern void efidev_free ( struct efi_device *efidev ); extern struct efi_device * efidev_parent ( struct device *dev ); extern int efi_driver_install ( void ); extern void efi_driver_uninstall ( void ); diff --git a/src/interface/efi/efi_driver.c b/src/interface/efi/efi_driver.c index 8f8c9f3da..fd9be5f51 100644 --- a/src/interface/efi/efi_driver.c +++ b/src/interface/efi/efi_driver.c @@ -61,6 +61,67 @@ static LIST_HEAD ( efi_devices ); /** We are currently disconnecting drivers */ static int efi_driver_disconnecting; +/** + * Allocate new EFI device + * + * @v device EFI device handle + * @ret efidev EFI device, or NULL on error + */ +struct efi_device * efidev_alloc ( EFI_HANDLE device ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + struct efi_device *efidev = NULL; + union { + EFI_DEVICE_PATH_PROTOCOL *path; + void *interface; + } path; + EFI_DEVICE_PATH_PROTOCOL *path_end; + size_t path_len; + EFI_STATUS efirc; + int rc; + + /* Open device path */ + if ( ( efirc = bs->OpenProtocol ( device, + &efi_device_path_protocol_guid, + &path.interface, efi_image_handle, + device, + EFI_OPEN_PROTOCOL_GET_PROTOCOL ))!=0){ + rc = -EEFI ( efirc ); + DBGC ( device, "EFIDRV %s could not open device path: %s\n", + efi_handle_name ( device ), strerror ( rc ) ); + goto err_open_path; + } + path_len = ( efi_path_len ( path.path ) + sizeof ( *path_end ) ); + + /* Allocate and initialise structure */ + efidev = zalloc ( sizeof ( *efidev ) + path_len ); + if ( ! efidev ) + goto err_alloc; + efidev->device = device; + efidev->dev.desc.bus_type = BUS_TYPE_EFI; + efidev->path = ( ( ( void * ) efidev ) + sizeof ( *efidev ) ); + memcpy ( efidev->path, path.path, path_len ); + INIT_LIST_HEAD ( &efidev->dev.children ); + list_add ( &efidev->dev.siblings, &efi_devices ); + + err_alloc: + bs->CloseProtocol ( device, &efi_device_path_protocol_guid, + efi_image_handle, device ); + err_open_path: + return efidev; +} + +/** + * Free EFI device + * + * @v efidev EFI device + */ +void efidev_free ( struct efi_device *efidev ) { + + assert ( list_empty ( &efidev->dev.children ) ); + list_del ( &efidev->dev.siblings ); + free ( efidev ); +} + /** * Find EFI device * @@ -159,16 +220,9 @@ efi_driver_supported ( EFI_DRIVER_BINDING_PROTOCOL *driver __unused, static EFI_STATUS EFIAPI efi_driver_start ( EFI_DRIVER_BINDING_PROTOCOL *driver __unused, EFI_HANDLE device, EFI_DEVICE_PATH_PROTOCOL *child ) { - EFI_BOOT_SERVICES *bs = efi_systab->BootServices; struct efi_driver *efidrv; struct efi_device *efidev; struct efi_saved_tpl tpl; - union { - EFI_DEVICE_PATH_PROTOCOL *path; - void *interface; - } path; - EFI_DEVICE_PATH_PROTOCOL *path_end; - size_t path_len; EFI_STATUS efirc; int rc; @@ -197,36 +251,12 @@ efi_driver_start ( EFI_DRIVER_BINDING_PROTOCOL *driver __unused, goto err_disconnecting; } - /* Open device path */ - if ( ( efirc = bs->OpenProtocol ( device, - &efi_device_path_protocol_guid, - &path.interface, efi_image_handle, - device, - EFI_OPEN_PROTOCOL_GET_PROTOCOL ))!=0){ - rc = -EEFI ( efirc ); - DBGC ( device, "EFIDRV %s could not open device path: %s\n", - efi_handle_name ( device ), strerror ( rc ) ); - goto err_open_path; - } - path_len = ( efi_path_len ( path.path ) + sizeof ( *path_end ) ); - - /* Allocate and initialise structure */ - efidev = zalloc ( sizeof ( *efidev ) + path_len ); + /* Add new device */ + efidev = efidev_alloc ( device ); if ( ! efidev ) { efirc = EFI_OUT_OF_RESOURCES; goto err_alloc; } - efidev->device = device; - efidev->dev.desc.bus_type = BUS_TYPE_EFI; - efidev->path = ( ( ( void * ) efidev ) + sizeof ( *efidev ) ); - memcpy ( efidev->path, path.path, path_len ); - INIT_LIST_HEAD ( &efidev->dev.children ); - list_add ( &efidev->dev.siblings, &efi_devices ); - - /* Close device path */ - bs->CloseProtocol ( device, &efi_device_path_protocol_guid, - efi_image_handle, device ); - path.path = NULL; /* Try to start this device */ for_each_table_entry ( efidrv, EFI_DRIVERS ) { @@ -251,14 +281,8 @@ efi_driver_start ( EFI_DRIVER_BINDING_PROTOCOL *driver __unused, } efirc = EFI_UNSUPPORTED; - list_del ( &efidev->dev.siblings ); - free ( efidev ); + efidev_free ( efidev ); err_alloc: - if ( path.path ) { - bs->CloseProtocol ( device, &efi_device_path_protocol_guid, - efi_image_handle, device ); - } - err_open_path: err_disconnecting: efi_restore_tpl ( &tpl ); err_already_started: @@ -306,8 +330,7 @@ efi_driver_stop ( EFI_DRIVER_BINDING_PROTOCOL *driver __unused, efidrv = efidev->driver; assert ( efidrv != NULL ); efidrv->stop ( efidev ); - list_del ( &efidev->dev.siblings ); - free ( efidev ); + efidev_free ( efidev ); efi_restore_tpl ( &tpl ); return 0; -- cgit v1.2.3-55-g7522 From b66f6025fa50f929c8e6448c383ffec273d41e6c Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Fri, 29 Mar 2024 12:58:10 +0000 Subject: [efi] Add the ability to create a temporary MNP network device An MNP network device may be temporarily and non-destructively installed on top of an existing UEFI network stack without having to disconnect existing drivers. Add the ability to create such a temporary network device. Signed-off-by: Michael Brown --- src/drivers/net/efi/mnp.c | 2 +- src/drivers/net/efi/mnpnet.c | 57 ++++++++++++++++++++++++++++++++++++++++++- src/drivers/net/efi/mnpnet.h | 17 ------------- src/drivers/net/efi/snponly.c | 2 +- src/include/ipxe/efi/mnpnet.h | 20 +++++++++++++++ 5 files changed, 78 insertions(+), 20 deletions(-) delete mode 100644 src/drivers/net/efi/mnpnet.h create mode 100644 src/include/ipxe/efi/mnpnet.h (limited to 'src/include') diff --git a/src/drivers/net/efi/mnp.c b/src/drivers/net/efi/mnp.c index b79fe188a..33218fb10 100644 --- a/src/drivers/net/efi/mnp.c +++ b/src/drivers/net/efi/mnp.c @@ -32,8 +32,8 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include #include +#include #include "snpnet.h" -#include "mnpnet.h" /** * Check to see if driver supports a device diff --git a/src/drivers/net/efi/mnpnet.c b/src/drivers/net/efi/mnpnet.c index a07eae54e..84f803f45 100644 --- a/src/drivers/net/efi/mnpnet.c +++ b/src/drivers/net/efi/mnpnet.c @@ -38,8 +38,8 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include #include +#include #include -#include "mnpnet.h" /** An MNP transmit or receive token */ struct mnp_token { @@ -502,3 +502,58 @@ void mnpnet_stop ( struct efi_device *efidev ) { netdev_nullify ( netdev ); netdev_put ( netdev ); } + +/** + * Create temporary MNP network device + * + * @v handle MNP service binding handle + * @v netdev Network device to fill in + * @ret rc Return status code + */ +int mnptemp_create ( EFI_HANDLE handle, struct net_device **netdev ) { + struct efi_device *efidev; + int rc; + + /* Create temporary EFI device */ + efidev = efidev_alloc ( handle ); + if ( ! efidev ) { + DBGC ( handle, "MNP %s could not create temporary device\n", + efi_handle_name ( handle ) ); + rc = -ENOMEM; + goto err_alloc; + } + + /* Start temporary network device */ + if ( ( rc = mnpnet_start ( efidev ) ) != 0 ) { + DBGC ( handle, "MNP %s could not start MNP: %s\n", + efi_handle_name ( handle ), strerror ( rc ) ); + goto err_start; + } + + /* Fill in network device */ + *netdev = efidev_get_drvdata ( efidev ); + + return 0; + + mnpnet_stop ( efidev ); + err_start: + efidev_free ( efidev ); + err_alloc: + return rc; +} + +/** + * Destroy temporary MNP network device + * + * @v netdev Network device + */ +void mnptemp_destroy ( struct net_device *netdev ) { + struct mnp_nic *mnp = netdev->priv; + struct efi_device *efidev = mnp->efidev; + + /* Stop temporary network device */ + mnpnet_stop ( efidev ); + + /* Free temporary EFI device */ + efidev_free ( efidev ); +} diff --git a/src/drivers/net/efi/mnpnet.h b/src/drivers/net/efi/mnpnet.h deleted file mode 100644 index afed62aae..000000000 --- a/src/drivers/net/efi/mnpnet.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef _MNPNET_H -#define _MNPNET_H - -/** @file - * - * MNP NIC driver - * - */ - -FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); - -struct efi_device; - -extern int mnpnet_start ( struct efi_device *efidev ); -extern void mnpnet_stop ( struct efi_device *efidev ); - -#endif /* _MNPNET_H */ diff --git a/src/drivers/net/efi/snponly.c b/src/drivers/net/efi/snponly.c index 6786f3e83..2ae63fc06 100644 --- a/src/drivers/net/efi/snponly.c +++ b/src/drivers/net/efi/snponly.c @@ -29,10 +29,10 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include #include +#include #include #include #include "snpnet.h" -#include "mnpnet.h" #include "nii.h" /** @file diff --git a/src/include/ipxe/efi/mnpnet.h b/src/include/ipxe/efi/mnpnet.h new file mode 100644 index 000000000..99d6cf083 --- /dev/null +++ b/src/include/ipxe/efi/mnpnet.h @@ -0,0 +1,20 @@ +#ifndef _IPXE_EFI_MNPNET_H +#define _IPXE_EFI_MNPNET_H + +/** @file + * + * MNP NIC driver + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +struct efi_device; +struct net_device; + +extern int mnpnet_start ( struct efi_device *efidev ); +extern void mnpnet_stop ( struct efi_device *efidev ); +extern int mnptemp_create ( EFI_HANDLE handle, struct net_device **netdev ); +extern void mnptemp_destroy ( struct net_device *netdev ); + +#endif /* _IPXE_EFI_MNPNET_H */ -- cgit v1.2.3-55-g7522 From b940d54235579f2534c12494322876fff555cf36 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Tue, 2 Apr 2024 19:36:00 +0100 Subject: [cachedhcp] Allow cached DHCPACK to apply to temporary network devices Retain a reference to the cached DHCPACK until the late startup phase, and allow it to be recycled for reuse. This allows the cached DHCPACK to be used for a temporary MNP network device and then subsequently reused for the corresponding real network device. Signed-off-by: Michael Brown --- src/core/cachedhcp.c | 75 ++++++++++++++++++++++++++++++++++++++------ src/drivers/net/efi/mnpnet.c | 4 +++ src/include/ipxe/cachedhcp.h | 2 ++ 3 files changed, 71 insertions(+), 10 deletions(-) (limited to 'src/include') diff --git a/src/core/cachedhcp.c b/src/core/cachedhcp.c index 57226e165..04945e646 100644 --- a/src/core/cachedhcp.c +++ b/src/core/cachedhcp.c @@ -46,11 +46,20 @@ struct cached_dhcp_packet { struct dhcp_packet *dhcppkt; /** VLAN tag (if applicable) */ unsigned int vlan; + /** Flags */ + unsigned int flags; }; +/** Cached DHCP packet should be retained */ +#define CACHEDHCP_RETAIN 0x0001 + +/** Cached DHCP packet has been used */ +#define CACHEDHCP_USED 0x0002 + /** Cached DHCPACK */ struct cached_dhcp_packet cached_dhcpack = { .name = DHCP_SETTINGS_NAME, + .flags = CACHEDHCP_RETAIN, }; /** Cached ProxyDHCPOFFER */ @@ -101,8 +110,8 @@ static int cachedhcp_apply ( struct cached_dhcp_packet *cache, size_t ll_addr_len; int rc; - /* Do nothing if cache is empty */ - if ( ! cache->dhcppkt ) + /* Do nothing if cache is empty or already in use */ + if ( ( ! cache->dhcppkt ) || ( cache->flags & CACHEDHCP_USED ) ) return 0; chaddr = cache->dhcppkt->dhcphdr->chaddr; @@ -169,8 +178,12 @@ static int cachedhcp_apply ( struct cached_dhcp_packet *cache, return rc; } - /* Free cached DHCP packet */ - cachedhcp_free ( cache ); + /* Mark as used */ + cache->flags |= CACHEDHCP_USED; + + /* Free cached DHCP packet, if applicable */ + if ( ! ( cache->flags & CACHEDHCP_RETAIN ) ) + cachedhcp_free ( cache ); return 0; } @@ -246,10 +259,10 @@ int cachedhcp_record ( struct cached_dhcp_packet *cache, unsigned int vlan, } /** - * Cached DHCP packet startup function + * Cached DHCP packet early startup function * */ -static void cachedhcp_startup ( void ) { +static void cachedhcp_startup_early ( void ) { /* Apply cached ProxyDHCPOFFER, if any */ cachedhcp_apply ( &cached_proxydhcp, NULL ); @@ -258,6 +271,20 @@ static void cachedhcp_startup ( void ) { /* Apply cached PXEBSACK, if any */ cachedhcp_apply ( &cached_pxebs, NULL ); cachedhcp_free ( &cached_pxebs ); +} + +/** + * Cache DHCP packet late startup function + * + */ +static void cachedhcp_startup_late ( void ) { + + /* Clear retention flag */ + cached_dhcpack.flags &= ~CACHEDHCP_RETAIN; + + /* Free cached DHCPACK, if used by a network device */ + if ( cached_dhcpack.flags & CACHEDHCP_USED ) + cachedhcp_free ( &cached_dhcpack ); /* Report unclaimed DHCPACK, if any. Do not free yet, since * it may still be claimed by a dynamically created device @@ -284,10 +311,16 @@ static void cachedhcp_shutdown ( int booting __unused ) { cachedhcp_free ( &cached_dhcpack ); } -/** Cached DHCPACK startup function */ -struct startup_fn cachedhcp_startup_fn __startup_fn ( STARTUP_LATE ) = { - .name = "cachedhcp", - .startup = cachedhcp_startup, +/** Cached DHCP packet early startup function */ +struct startup_fn cachedhcp_early_fn __startup_fn ( STARTUP_EARLY ) = { + .name = "cachedhcp1", + .startup = cachedhcp_startup_early, +}; + +/** Cached DHCP packet late startup function */ +struct startup_fn cachedhcp_late_fn __startup_fn ( STARTUP_LATE ) = { + .name = "cachedhcp2", + .startup = cachedhcp_startup_late, .shutdown = cachedhcp_shutdown, }; @@ -309,3 +342,25 @@ struct net_driver cachedhcp_driver __net_driver = { .name = "cachedhcp", .probe = cachedhcp_probe, }; + +/** + * Recycle cached DHCPACK + * + * @v netdev Network device + * @v priv Private data + */ +void cachedhcp_recycle ( struct net_device *netdev ) { + struct cached_dhcp_packet *cache = &cached_dhcpack; + struct settings *settings; + + /* Return DHCPACK to cache, if applicable */ + settings = find_child_settings ( netdev_settings ( netdev ), + cache->name ); + if ( cache->dhcppkt && ( settings == &cache->dhcppkt->settings ) ) { + DBGC ( colour, "CACHEDHCP %s recycled from %s\n", + cache->name, netdev->name ); + assert ( cache->flags & CACHEDHCP_USED ); + unregister_settings ( settings ); + cache->flags &= ~CACHEDHCP_USED; + } +} diff --git a/src/drivers/net/efi/mnpnet.c b/src/drivers/net/efi/mnpnet.c index 84f803f45..eb4b129c7 100644 --- a/src/drivers/net/efi/mnpnet.c +++ b/src/drivers/net/efi/mnpnet.c @@ -34,6 +34,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include #include +#include #include #include #include @@ -551,6 +552,9 @@ void mnptemp_destroy ( struct net_device *netdev ) { struct mnp_nic *mnp = netdev->priv; struct efi_device *efidev = mnp->efidev; + /* Recycle any cached DHCP packet */ + cachedhcp_recycle ( netdev ); + /* Stop temporary network device */ mnpnet_stop ( efidev ); diff --git a/src/include/ipxe/cachedhcp.h b/src/include/ipxe/cachedhcp.h index 4ce4a9f2a..8ebee3b7b 100644 --- a/src/include/ipxe/cachedhcp.h +++ b/src/include/ipxe/cachedhcp.h @@ -12,6 +12,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include +struct net_device; struct cached_dhcp_packet; extern struct cached_dhcp_packet cached_dhcpack; @@ -21,5 +22,6 @@ extern struct cached_dhcp_packet cached_pxebs; extern int cachedhcp_record ( struct cached_dhcp_packet *cache, unsigned int vlan, userptr_t data, size_t max_len ); +extern void cachedhcp_recycle ( struct net_device *netdev ); #endif /* _IPXE_CACHEDHCP_H */ -- cgit v1.2.3-55-g7522 From 165995b7e917a94533799fe43d14f5e3b1fef285 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Fri, 29 Mar 2024 13:03:38 +0000 Subject: [efi] Restructure handling of autoexec.ipxe script We currently attempt to obtain the autoexec.ipxe script via early use of the EFI_SIMPLE_FILE_SYSTEM_PROTOCOL or EFI_PXE_BASE_CODE_PROTOCOL interfaces to obtain an opaque block of memory, which is then registered as an image at an appropriate point during our startup sequence. The early use of these existent interfaces allows us to obtain the script even if our subsequent actions (e.g. disconnecting drivers in order to connect up our own) may cause the script to become inaccessible. This mirrors the approach used under BIOS, where the autoexec.ipxe script is provided by the prefix (e.g. as an initrd image when using the .lkrn build of iPXE) and so must be copied into a normally allocated image from wherever it happens to previously exist in memory. We do not currently have support for downloading an autoexec.ipxe script if we were ourselves downloaded via UEFI HTTP boot. There is an EFI_HTTP_PROTOCOL defined within the UEFI specification, but it is so poorly designed as to be unusable for the simple purpose of downloading an additional file from the same directory. It provides almost nothing more than a very slim wrapper around EFI_TCP4_PROTOCOL (or EFI_TCP6_PROTOCOL). It will not handle redirection, content encoding, retries, or even fundamentals such as the Content-Length header, leaving all of this up to the caller. The UEFI HTTP Boot driver will install an EFI_LOAD_FILE_PROTOCOL instance on the loaded image's device handle. This looks promising at first since it provides the LoadFile() API call which is specified to accept an arbitrary filename parameter. However, experimentation (and inspection of the code in EDK2) reveals a multitude of problems that prevent this from being usable. Calling LoadFile() will idiotically restart the entire DHCP process (and potentially pop up a UI requiring input from the user for e.g. a wireless network password). The filename provided to LoadFile() will be ignored. Any downloaded file will be rejected unless it happens to match one of the limited set of types expected by the UEFI HTTP Boot driver. The list of design failures and conceptual mismatches is fairly impressive. Choose to bypass every possible aspect of UEFI HTTP support, and instead use our own HTTP client and network stack to download the autoexec.ipxe script over a temporary MNP network device. Since this approach works for TFTP as well as HTTP, drop the direct use of EFI_PXE_BASE_CODE_PROTOCOL. For consistency and simplicity, also drop the direct use of EFI_SIMPLE_FILE_SYSTEM_PROTOCOL and rely upon our existing support to access local files via "file:" URIs. This approach results in console output during the "iPXE initialising devices...ok" message that appears while startup is in progress. Remove the trailing "ok" so that this intermediate output appears at a sensible location on the screen. The welcome banner that will be printed immediately afterwards provides an indication that startup has completed successfully even absent the explicit "ok". Signed-off-by: Michael Brown --- src/core/main.c | 3 +- src/include/ipxe/efi/efi_autoexec.h | 5 +- src/interface/efi/efi_autoexec.c | 489 +++++++++--------------------------- src/interface/efi/efiprefix.c | 11 +- 4 files changed, 124 insertions(+), 384 deletions(-) (limited to 'src/include') diff --git a/src/core/main.c b/src/core/main.c index 638dea9cf..3db836491 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -32,9 +32,8 @@ __asmcall int main ( void ) { initialise(); /* Some devices take an unreasonably long time to initialise */ - printf ( "%s initialising devices...", product_short_name ); + printf ( "%s initialising devices...\n", product_short_name ); startup(); - printf ( "ok\n" ); /* Attempt to boot */ if ( ( rc = ipxe ( NULL ) ) != 0 ) diff --git a/src/include/ipxe/efi/efi_autoexec.h b/src/include/ipxe/efi/efi_autoexec.h index 08ddf5836..18bc4200c 100644 --- a/src/include/ipxe/efi/efi_autoexec.h +++ b/src/include/ipxe/efi/efi_autoexec.h @@ -9,9 +9,6 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); -#include - -extern int efi_autoexec_load ( EFI_HANDLE device, - EFI_DEVICE_PATH_PROTOCOL *path ); +extern int efi_autoexec_load ( void ); #endif /* _IPXE_EFI_AUTOEXEC_H */ diff --git a/src/interface/efi/efi_autoexec.c b/src/interface/efi/efi_autoexec.c index fb12cef08..d9ad3b990 100644 --- a/src/interface/efi/efi_autoexec.c +++ b/src/interface/efi/efi_autoexec.c @@ -24,16 +24,16 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include -#include #include +#include #include -#include -#include +#include #include +#include #include -#include -#include -#include +#include +#include +#include /** @file * @@ -41,413 +41,160 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); * */ -/** Autoexec script filename */ -static wchar_t efi_autoexec_wname[] = L"autoexec.ipxe"; +/** Timeout for autoexec script downloads */ +#define EFI_AUTOEXEC_TIMEOUT ( 2 * TICKS_PER_SEC ) /** Autoexec script image name */ -static char efi_autoexec_name[] = "autoexec.ipxe"; - -/** Autoexec script (if any) */ -static void *efi_autoexec; - -/** Autoexec script length */ -static size_t efi_autoexec_len; +#define EFI_AUTOEXEC_NAME "autoexec.ipxe" + +/** An EFI autoexec script loader */ +struct efi_autoexec_loader { + /** Required protocol GUID */ + EFI_GUID *protocol; + /** + * Load autoexec script + * + * @v handle Handle on which protocol was found + * @v image Image to fill in + * @ret rc Return status code + */ + int ( * load ) ( EFI_HANDLE handle, struct image **image ); +}; /** - * Load autoexec script from path within filesystem + * Load autoexec script from filesystem * - * @v device Device handle - * @v path Relative path to image, or NULL to load from root + * @v handle Simple filesystem protocol handle + * @v image Image to fill in * @ret rc Return status code */ -static int efi_autoexec_filesystem ( EFI_HANDLE device, - EFI_DEVICE_PATH_PROTOCOL *path ) { - EFI_BOOT_SERVICES *bs = efi_systab->BootServices; - union { - void *interface; - EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *fs; - } u; - struct { - EFI_FILE_INFO info; - CHAR16 name[ sizeof ( efi_autoexec_wname ) / - sizeof ( efi_autoexec_wname[0] ) ]; - } info; - FILEPATH_DEVICE_PATH *filepath; - EFI_FILE_PROTOCOL *root; - EFI_FILE_PROTOCOL *file; - UINTN size; - VOID *data; - unsigned int dirlen; - size_t len; - CHAR16 *wname; - EFI_STATUS efirc; +static int efi_autoexec_filesystem ( EFI_HANDLE handle, struct image **image ) { + EFI_HANDLE device = efi_loaded_image->DeviceHandle; int rc; - /* Identify directory */ - if ( path ) { - - /* Check relative device path is a file path */ - if ( ! ( ( path->Type == MEDIA_DEVICE_PATH ) && - ( path->SubType == MEDIA_FILEPATH_DP ) ) ) { - DBGC ( device, "EFI %s image path ", - efi_handle_name ( device ) ); - DBGC ( device, " \"%s\" is not a file path\n", - efi_devpath_text ( path ) ); - rc = -ENOTTY; - goto err_not_filepath; - } - filepath = container_of ( path, FILEPATH_DEVICE_PATH, Header ); - - /* Find length of containing directory */ - dirlen = ( ( ( ( path->Length[1] << 8 ) | path->Length[0] ) - - offsetof ( typeof ( *filepath ), PathName ) ) - / sizeof ( filepath->PathName[0] ) ); - for ( ; dirlen ; dirlen-- ) { - if ( filepath->PathName[ dirlen - 1 ] == L'\\' ) - break; - } - - } else { - - /* Use root directory */ - filepath = NULL; - dirlen = 0; - } - - /* Allocate filename */ - len = ( ( dirlen * sizeof ( wname[0] ) ) + sizeof ( efi_autoexec_wname ) ); - wname = malloc ( len ); - if ( ! wname ) { - rc = -ENOMEM; - goto err_wname; - } - memcpy ( wname, filepath->PathName, ( dirlen * sizeof ( wname[0] ) ) ); - memcpy ( &wname[dirlen], efi_autoexec_wname, sizeof ( efi_autoexec_wname ) ); - - /* Open simple file system protocol */ - if ( ( efirc = bs->OpenProtocol ( device, - &efi_simple_file_system_protocol_guid, - &u.interface, efi_image_handle, - device, - EFI_OPEN_PROTOCOL_GET_PROTOCOL ))!=0){ - rc = -EEFI ( efirc ); - DBGC ( device, "EFI %s has no filesystem instance: %s\n", - efi_handle_name ( device ), strerror ( rc ) ); - goto err_filesystem; - } - - /* Open root directory */ - if ( ( efirc = u.fs->OpenVolume ( u.fs, &root ) ) != 0 ) { - rc = -EEFI ( efirc ); - DBGC ( device, "EFI %s could not open volume: %s\n", - efi_handle_name ( device ), strerror ( rc ) ); - goto err_volume; - } - - /* Open autoexec script */ - if ( ( efirc = root->Open ( root, &file, wname, - EFI_FILE_MODE_READ, 0 ) ) != 0 ) { - rc = -EEFI ( efirc ); - DBGC ( device, "EFI %s has no %ls: %s\n", - efi_handle_name ( device ), wname, strerror ( rc ) ); - goto err_open; - } - - /* Get file information */ - size = sizeof ( info ); - if ( ( efirc = file->GetInfo ( file, &efi_file_info_id, &size, - &info ) ) != 0 ) { - rc = -EEFI ( efirc ); - DBGC ( device, "EFI %s could not get %ls info: %s\n", - efi_handle_name ( device ), wname, strerror ( rc ) ); - goto err_getinfo; - } - size = info.info.FileSize; - - /* Ignore zero-length files */ - if ( ! size ) { - rc = -EINVAL; - DBGC ( device, "EFI %s has zero-length %ls\n", - efi_handle_name ( device ), wname ); - goto err_empty; - } - - /* Allocate temporary copy */ - if ( ( efirc = bs->AllocatePool ( EfiBootServicesData, size, - &data ) ) != 0 ) { - rc = -EEFI ( efirc ); - DBGC ( device, "EFI %s could not allocate %ls: %s\n", - efi_handle_name ( device ), wname, strerror ( rc ) ); - goto err_alloc; - } - - /* Read file */ - if ( ( efirc = file->Read ( file, &size, data ) ) != 0 ) { - rc = -EEFI ( efirc ); - DBGC ( device, "EFI %s could not read %ls: %s\n", - efi_handle_name ( device ), wname, strerror ( rc ) ); - goto err_read; + /* Check that we were loaded from a filesystem */ + if ( handle != device ) { + DBGC ( device, "EFI %s is not the file system handle\n", + efi_handle_name ( device ) ); + return -ENOTTY; } - /* Record autoexec script */ - efi_autoexec = data; - efi_autoexec_len = size; - data = NULL; - DBGC ( device, "EFI %s found %ls\n", efi_handle_name ( device ), wname ); + /* Try loading from loaded image directory, if supported */ + if ( ( rc = imgacquire ( "file:" EFI_AUTOEXEC_NAME, + EFI_AUTOEXEC_TIMEOUT, image ) ) == 0 ) + return 0; - /* Success */ - rc = 0; + /* Try loading from root directory, if supported */ + if ( ( rc = imgacquire ( "file:/" EFI_AUTOEXEC_NAME, + EFI_AUTOEXEC_TIMEOUT, image ) ) == 0 ) + return 0; - err_read: - if ( data ) - bs->FreePool ( data ); - err_alloc: - err_empty: - err_getinfo: - file->Close ( file ); - err_open: - root->Close ( root ); - err_volume: - bs->CloseProtocol ( device, &efi_simple_file_system_protocol_guid, - efi_image_handle, device ); - err_filesystem: - free ( wname ); - err_wname: - err_not_filepath: return rc; } /** - * Load autoexec script from TFTP server + * Load autoexec script via temporary network device * - * @v device Device handle + * @v handle Managed network protocol service binding handle + * @v image Image to fill in * @ret rc Return status code */ -static int efi_autoexec_tftp ( EFI_HANDLE device ) { - EFI_BOOT_SERVICES *bs = efi_systab->BootServices; - union { - void *interface; - EFI_PXE_BASE_CODE_PROTOCOL *pxe; - } u; - EFI_PXE_BASE_CODE_MODE *mode; - EFI_PXE_BASE_CODE_PACKET *packet; - union { - struct in_addr in; - EFI_IP_ADDRESS ip; - } server; - size_t filename_max; - char *filename; - char *sep; - UINT64 size; - VOID *data; - EFI_STATUS efirc; +static int efi_autoexec_network ( EFI_HANDLE handle, struct image **image ) { + EFI_HANDLE device = efi_loaded_image->DeviceHandle; + struct net_device *netdev; int rc; - /* Open PXE base code protocol */ - if ( ( efirc = bs->OpenProtocol ( device, - &efi_pxe_base_code_protocol_guid, - &u.interface, efi_image_handle, - device, - EFI_OPEN_PROTOCOL_GET_PROTOCOL ))!=0){ - rc = -EEFI ( efirc ); - DBGC ( device, "EFI %s has no PXE base code instance: %s\n", + /* Create temporary network device */ + if ( ( rc = mnptemp_create ( handle, &netdev ) ) != 0 ) { + DBGC ( device, "EFI %s could not create net device: %s\n", efi_handle_name ( device ), strerror ( rc ) ); - goto err_pxe; - } - - /* Do not attempt to parse DHCPv6 packets */ - mode = u.pxe->Mode; - if ( mode->UsingIpv6 ) { - rc = -ENOTSUP; - DBGC ( device, "EFI %s has IPv6 PXE base code\n", - efi_handle_name ( device ) ); - goto err_ipv6; + goto err_create; } - /* Identify relevant reply packet */ - if ( mode->PxeReplyReceived && - mode->PxeReply.Dhcpv4.BootpBootFile[0] ) { - /* Use boot filename if present in PXE reply */ - DBGC ( device, "EFI %s using PXE reply filename\n", - efi_handle_name ( device ) ); - packet = &mode->PxeReply; - } else if ( mode->DhcpAckReceived && - mode->DhcpAck.Dhcpv4.BootpBootFile[0] ) { - /* Otherwise, use boot filename if present in DHCPACK */ - DBGC ( device, "EFI %s using DHCPACK filename\n", - efi_handle_name ( device ) ); - packet = &mode->DhcpAck; - } else if ( mode->ProxyOfferReceived && - mode->ProxyOffer.Dhcpv4.BootpBootFile[0] ) { - /* Otherwise, use boot filename if present in ProxyDHCPOFFER */ - DBGC ( device, "EFI %s using ProxyDHCPOFFER filename\n", - efi_handle_name ( device ) ); - packet = &mode->ProxyOffer; - } else { - /* No boot filename available */ - rc = -ENOENT; - DBGC ( device, "EFI %s has no PXE boot filename\n", - efi_handle_name ( device ) ); - goto err_packet; - } - - /* Allocate filename */ - filename_max = ( sizeof ( packet->Dhcpv4.BootpBootFile ) - + ( sizeof ( efi_autoexec_name ) - 1 /* NUL */ ) - + 1 /* NUL */ ); - filename = zalloc ( filename_max ); - if ( ! filename ) { - rc = -ENOMEM; - goto err_filename; - } - - /* Extract next-server address and boot filename */ - memset ( &server, 0, sizeof ( server ) ); - memcpy ( &server.in, packet->Dhcpv4.BootpSiAddr, - sizeof ( server.in ) ); - memcpy ( filename, packet->Dhcpv4.BootpBootFile, - sizeof ( packet->Dhcpv4.BootpBootFile ) ); - - /* Update filename to autoexec script name */ - sep = strrchr ( filename, '/' ); - if ( ! sep ) - sep = strrchr ( filename, '\\' ); - if ( ! sep ) - sep = ( filename - 1 ); - strcpy ( ( sep + 1 ), efi_autoexec_name ); - - /* Get file size */ - if ( ( efirc = u.pxe->Mtftp ( u.pxe, - EFI_PXE_BASE_CODE_TFTP_GET_FILE_SIZE, - NULL, FALSE, &size, NULL, &server.ip, - ( ( UINT8 * ) filename ), NULL, - FALSE ) ) != 0 ) { - rc = -EEFI ( efirc ); - DBGC ( device, "EFI %s could not get size of %s:%s: %s\n", - efi_handle_name ( device ), inet_ntoa ( server.in ), - filename, strerror ( rc ) ); - goto err_size; - } - - /* Ignore zero-length files */ - if ( ! size ) { - rc = -EINVAL; - DBGC ( device, "EFI %s has zero-length %s:%s\n", - efi_handle_name ( device ), inet_ntoa ( server.in ), - filename ); - goto err_empty; - } - - /* Allocate temporary copy */ - if ( ( efirc = bs->AllocatePool ( EfiBootServicesData, size, - &data ) ) != 0 ) { - rc = -EEFI ( efirc ); - DBGC ( device, "EFI %s could not allocate %s:%s: %s\n", - efi_handle_name ( device ), inet_ntoa ( server.in ), - filename, strerror ( rc ) ); - goto err_alloc; + /* Open network device */ + if ( ( rc = netdev_open ( netdev ) ) != 0 ) { + DBGC ( device, "EFI %s could not open net device: %s\n", + efi_handle_name ( device ), strerror ( rc ) ); + goto err_open; } - /* Download file */ - if ( ( efirc = u.pxe->Mtftp ( u.pxe, EFI_PXE_BASE_CODE_TFTP_READ_FILE, - data, FALSE, &size, NULL, &server.ip, - ( ( UINT8 * ) filename ), NULL, - FALSE ) ) != 0 ) { - rc = -EEFI ( efirc ); - DBGC ( device, "EFI %s could not download %s:%s: %s\n", - efi_handle_name ( device ), inet_ntoa ( server.in ), - filename, strerror ( rc ) ); - goto err_download; + /* Attempt download */ + rc = imgacquire ( EFI_AUTOEXEC_NAME, EFI_AUTOEXEC_TIMEOUT, image ); + if ( rc != 0 ) { + DBGC ( device, "EFI %s could not download %s: %s\n", + efi_handle_name ( device ), EFI_AUTOEXEC_NAME, + strerror ( rc ) ); } - /* Record autoexec script */ - efi_autoexec = data; - efi_autoexec_len = size; - data = NULL; - DBGC ( device, "EFI %s found %s:%s\n", efi_handle_name ( device ), - inet_ntoa ( server.in ), filename ); + /* Ensure network exchanges have completed */ + sync ( EFI_AUTOEXEC_TIMEOUT ); - /* Success */ - rc = 0; - - err_download: - if ( data ) - bs->FreePool ( data ); - err_alloc: - err_empty: - err_size: - free ( filename ); - err_filename: - err_packet: - err_ipv6: - bs->CloseProtocol ( device, &efi_pxe_base_code_protocol_guid, - efi_image_handle, device ); - err_pxe: + err_open: + mnptemp_destroy ( netdev ); + err_create: return rc; } +/** Autoexec script loaders */ +static struct efi_autoexec_loader efi_autoexec_loaders[] = { + { + .protocol = &efi_simple_file_system_protocol_guid, + .load = efi_autoexec_filesystem, + }, + { + .protocol = &efi_managed_network_service_binding_protocol_guid, + .load = efi_autoexec_network, + }, +}; + /** * Load autoexec script * - * @v device Device handle - * @v path Image path within device handle * @ret rc Return status code */ -int efi_autoexec_load ( EFI_HANDLE device, - EFI_DEVICE_PATH_PROTOCOL *path ) { - int rc; - - /* Sanity check */ - assert ( efi_autoexec == NULL ); - assert ( efi_autoexec_len == 0 ); - - /* Try loading from file system loaded image directory, if supported */ - if ( ( rc = efi_autoexec_filesystem ( device, path ) ) == 0 ) - return 0; - - /* Try loading from file system root directory, if supported */ - if ( ( rc = efi_autoexec_filesystem ( device, NULL ) ) == 0 ) - return 0; - - /* Try loading via TFTP, if supported */ - if ( ( rc = efi_autoexec_tftp ( device ) ) == 0 ) - return 0; - - return -ENOENT; -} - -/** - * Register autoexec script - * - */ -static void efi_autoexec_startup ( void ) { - EFI_BOOT_SERVICES *bs = efi_systab->BootServices; +int efi_autoexec_load ( void ) { EFI_HANDLE device = efi_loaded_image->DeviceHandle; + EFI_HANDLE handle; + struct efi_autoexec_loader *loader; struct image *image; + unsigned int i; + int rc; - /* Do nothing if we have no autoexec script */ - if ( ! efi_autoexec ) - return; + /* Use first applicable loader */ + for ( i = 0 ; i < ( sizeof ( efi_autoexec_loaders ) / + sizeof ( efi_autoexec_loaders[0] ) ) ; i ++ ) { + + /* Locate required protocol for this loader */ + loader = &efi_autoexec_loaders[i]; + if ( ( rc = efi_locate_device ( device, loader->protocol, + &handle, 0 ) ) != 0 ) { + DBGC ( device, "EFI %s found no %s: %s\n", + efi_handle_name ( device ), + efi_guid_ntoa ( loader->protocol ), + strerror ( rc ) ); + continue; + } + DBGC ( device, "EFI %s found %s on ", + efi_handle_name ( device ), + efi_guid_ntoa ( loader->protocol ) ); + DBGC ( device, "%s\n", efi_handle_name ( handle ) ); + + /* Try loading */ + if ( ( rc = loader->load ( handle, &image ) ) != 0 ) + return rc; + + /* Discard zero-length images */ + if ( ! image->len ) { + DBGC ( device, "EFI %s discarding zero-length %s\n", + efi_handle_name ( device ), image->name ); + unregister_image ( image ); + return -ENOENT; + } - /* Create autoexec image */ - image = image_memory ( efi_autoexec_name, - virt_to_user ( efi_autoexec ), - efi_autoexec_len ); - if ( ! image ) { - DBGC ( device, "EFI %s could not create %s\n", - efi_handle_name ( device ), efi_autoexec_name ); - return; + DBGC ( device, "EFI %s loaded %s (%zd bytes)\n", + efi_handle_name ( device ), image->name, image->len ); + return 0; } - DBGC ( device, "EFI %s registered %s\n", - efi_handle_name ( device ), efi_autoexec_name ); - /* Free temporary copy */ - bs->FreePool ( efi_autoexec ); - efi_autoexec = NULL; + return -ENOENT; } - -/** Autoexec script startup function */ -struct startup_fn efi_autoexec_startup_fn __startup_fn ( STARTUP_NORMAL ) = { - .name = "efi_autoexec", - .startup = efi_autoexec_startup, -}; diff --git a/src/interface/efi/efiprefix.c b/src/interface/efi/efiprefix.c index f6395b65b..10d8f0bf6 100644 --- a/src/interface/efi/efiprefix.c +++ b/src/interface/efi/efiprefix.c @@ -81,25 +81,19 @@ EFI_STATUS EFIAPI _efi_start ( EFI_HANDLE image_handle, static void efi_init_application ( void ) { EFI_HANDLE device = efi_loaded_image->DeviceHandle; EFI_DEVICE_PATH_PROTOCOL *devpath = efi_loaded_image_path; - EFI_DEVICE_PATH_PROTOCOL *filepath = efi_loaded_image->FilePath; struct uri *uri; /* Set current working URI from device path, if present */ uri = efi_path_uri ( devpath ); if ( uri ) churi ( uri ); + uri_put ( uri ); /* Identify autoboot device, if any */ efi_set_autoboot_ll_addr ( device, devpath ); /* Store cached DHCP packet, if any */ efi_cachedhcp_record ( device, devpath ); - - /* Load autoexec script, if any */ - efi_autoexec_load ( device, filepath ); - - /* Drop temporary reference to URI */ - uri_put ( uri ); } /** EFI application initialisation function */ @@ -114,6 +108,9 @@ struct init_fn efi_init_application_fn __init_fn ( INIT_NORMAL ) = { */ static int efi_probe ( struct root_device *rootdev __unused ) { + /* Try loading autoexec script */ + efi_autoexec_load(); + /* Remove any vetoed drivers */ efi_veto(); -- cgit v1.2.3-55-g7522 From 59f27d69358efc919b50760f3d6dac0b637b5488 Mon Sep 17 00:00:00 2001 From: Pavel Krotkiy Date: Wed, 3 Apr 2024 12:52:56 +0100 Subject: [netdevice] Add "linktype" setting Add a new setting to provide access to the link layer protocol type from scripts. This can be useful in order to skip configuring interfaces based on their link layer protocol or, conversely, configure only selected interface types (Ethernet, IPoIB, etc.) Example script: set idx:int32 0 :loop isset ${net${idx}/mac} || exit 0 iseq ${net${idx}/linktype} IPoIB && goto try_next || autoboot net${idx} || :try_next inc idx && goto loop Signed-off-by: Pavel Krotkiy Modified-by: Michael Brown Signed-off-by: Michael Brown --- src/include/ipxe/settings.h | 2 ++ src/net/netdev_settings.c | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+) (limited to 'src/include') diff --git a/src/include/ipxe/settings.h b/src/include/ipxe/settings.h index 424188deb..0301da12e 100644 --- a/src/include/ipxe/settings.h +++ b/src/include/ipxe/settings.h @@ -471,6 +471,8 @@ mac_setting __setting ( SETTING_NETDEV, mac ); extern const struct setting busid_setting __setting ( SETTING_NETDEV, busid ); extern const struct setting +linktype_setting __setting ( SETTING_NETDEV, linktype ); +extern const struct setting user_class_setting __setting ( SETTING_HOST_EXTRA, user-class ); extern const struct setting vendor_class_setting __setting ( SETTING_HOST_EXTRA, vendor-class ); diff --git a/src/net/netdev_settings.c b/src/net/netdev_settings.c index fb98663ca..080b6d2a5 100644 --- a/src/net/netdev_settings.c +++ b/src/net/netdev_settings.c @@ -65,6 +65,11 @@ const struct setting busid_setting __setting ( SETTING_NETDEV, busid ) = { .description = "Bus ID", .type = &setting_type_hex, }; +const struct setting linktype_setting __setting ( SETTING_NETDEV, linktype ) = { + .name = "linktype", + .description = "Link-layer type", + .type = &setting_type_string, +}; const struct setting chip_setting __setting ( SETTING_NETDEV, chip ) = { .name = "chip", .description = "Chip", @@ -219,6 +224,22 @@ static int netdev_fetch_busid ( struct net_device *netdev, void *data, return sizeof ( dhcp_desc ); } +/** + * Fetch link layer type setting + * + * @v netdev Network device + * @v data Buffer to fill with setting data + * @v len Length of buffer + * @ret len Length of setting data, or negative error + */ +static int netdev_fetch_linktype ( struct net_device *netdev, void *data, + size_t len ) { + const char *linktype = netdev->ll_protocol->name; + + strncpy ( data, linktype, len ); + return strlen ( linktype ); +} + /** * Fetch chip setting * @@ -281,6 +302,7 @@ static struct netdev_setting_operation netdev_setting_operations[] = { { &bustype_setting, NULL, netdev_fetch_bustype }, { &busloc_setting, NULL, netdev_fetch_busloc }, { &busid_setting, NULL, netdev_fetch_busid }, + { &linktype_setting, NULL, netdev_fetch_linktype }, { &chip_setting, NULL, netdev_fetch_chip }, { &ifname_setting, NULL, netdev_fetch_ifname }, }; -- cgit v1.2.3-55-g7522