summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Rettberg2024-04-12 14:52:06 +0200
committerSimon Rettberg2024-04-12 14:52:06 +0200
commit2ae76865d3d109712f9ee488cbc19bd107bbc9ab (patch)
tree36e7310eb089cf7fd3496e5c32c70981e447f235
parentMerge branch 'aqc1xx' into openslx (diff)
parent[netdevice] Add "linktype" setting (diff)
downloadipxe-openslx.tar.gz
ipxe-openslx.tar.xz
ipxe-openslx.zip
Merge branch 'master' into openslxopenslx
-rw-r--r--src/Makefile.efi4
-rw-r--r--src/arch/arm/include/bits/mp.h12
-rw-r--r--src/arch/loong64/include/bits/mp.h12
-rw-r--r--src/arch/x86/core/mpcall.S197
-rw-r--r--src/arch/x86/core/ucode_mp.S257
-rw-r--r--src/arch/x86/image/ucode.c798
-rw-r--r--src/arch/x86/include/bits/errfile.h1
-rw-r--r--src/arch/x86/include/bits/mp.h14
-rw-r--r--src/arch/x86/include/ipxe/bios_mp.h32
-rw-r--r--src/arch/x86/include/ipxe/ucode.h223
-rw-r--r--src/arch/x86/include/librm.h20
-rw-r--r--src/arch/x86/interface/pcbios/bios_mp.c173
-rw-r--r--src/arch/x86/transitions/librm.S67
-rw-r--r--src/arch/x86/transitions/librm_mgmt.c26
-rw-r--r--src/config/config.c3
-rw-r--r--src/config/defaults/efi.h1
-rw-r--r--src/config/defaults/linux.h1
-rw-r--r--src/config/defaults/pcbios.h1
-rw-r--r--src/config/general.h1
-rw-r--r--src/core/cachedhcp.c75
-rw-r--r--src/core/image.c11
-rw-r--r--src/core/main.c3
-rw-r--r--src/core/mp.c67
-rw-r--r--src/core/null_mp.c37
-rw-r--r--src/core/settings.c107
-rw-r--r--src/crypto/gcm.c26
-rwxr-xr-xsrc/drivers/infiniband/golan.c2
-rw-r--r--src/drivers/net/efi/mnp.c56
-rw-r--r--src/drivers/net/efi/mnpnet.c563
-rw-r--r--src/drivers/net/efi/nii.c7
-rw-r--r--src/drivers/net/efi/snp.c54
-rw-r--r--src/drivers/net/efi/snpnet.c70
-rw-r--r--src/drivers/net/efi/snpnet.h1
-rw-r--r--src/drivers/net/efi/snponly.c136
-rw-r--r--src/include/ipxe/cachedhcp.h2
-rw-r--r--src/include/ipxe/efi/Protocol/MpService.h676
-rw-r--r--src/include/ipxe/efi/Protocol/ServiceBinding.h90
-rw-r--r--src/include/ipxe/efi/efi_autoexec.h5
-rw-r--r--src/include/ipxe/efi/efi_driver.h4
-rw-r--r--src/include/ipxe/efi/efi_mp.h30
-rw-r--r--src/include/ipxe/efi/efi_path.h2
-rw-r--r--src/include/ipxe/efi/efi_service.h19
-rw-r--r--src/include/ipxe/efi/mnpnet.h20
-rw-r--r--src/include/ipxe/errfile.h5
-rw-r--r--src/include/ipxe/gcm.h7
-rw-r--r--src/include/ipxe/mp.h155
-rw-r--r--src/include/ipxe/null_mp.h36
-rw-r--r--src/include/ipxe/settings.h4
-rw-r--r--src/interface/efi/efi_autoexec.c489
-rw-r--r--src/interface/efi/efi_driver.c117
-rw-r--r--src/interface/efi/efi_local.c294
-rw-r--r--src/interface/efi/efi_mp.c112
-rw-r--r--src/interface/efi/efi_path.c305
-rw-r--r--src/interface/efi/efi_service.c138
-rw-r--r--src/interface/efi/efiprefix.c16
-rw-r--r--src/net/netdev_settings.c22
-rw-r--r--src/net/tcp/httpcore.c3
-rw-r--r--src/net/udp/tftp.c11
-rw-r--r--src/tests/uri_test.c16
59 files changed, 4950 insertions, 686 deletions
diff --git a/src/Makefile.efi b/src/Makefile.efi
index 6e8ad46b..95ecf386 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/arch/arm/include/bits/mp.h b/src/arch/arm/include/bits/mp.h
new file mode 100644
index 00000000..e7d4c0c1
--- /dev/null
+++ b/src/arch/arm/include/bits/mp.h
@@ -0,0 +1,12 @@
+#ifndef _BITS_MP_H
+#define _BITS_MP_H
+
+/** @file
+ *
+ * ARM-specific multiprocessor API implementation
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+#endif /* _BITS_MP_H */
diff --git a/src/arch/loong64/include/bits/mp.h b/src/arch/loong64/include/bits/mp.h
new file mode 100644
index 00000000..fef2fd59
--- /dev/null
+++ b/src/arch/loong64/include/bits/mp.h
@@ -0,0 +1,12 @@
+#ifndef _BITS_MP_H
+#define _BITS_MP_H
+
+/** @file
+ *
+ * LoongArch64-specific multiprocessor API implementation
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+#endif /* _BITS_MP_H */
diff --git a/src/arch/x86/core/mpcall.S b/src/arch/x86/core/mpcall.S
new file mode 100644
index 00000000..f2a3bf90
--- /dev/null
+++ b/src/arch/x86/core/mpcall.S
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2024 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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
+ *
+ * Multiprocessor functions
+ *
+ */
+
+ .section ".note.GNU-stack", "", @progbits
+ .text
+
+/* Selectively assemble code for 32-bit/64-bit builds */
+#if defined ( __x86_64__ ) && ! defined ( PLATFORM_pcbios )
+#define codemp code64
+#define DI rdi
+#define SP rsp
+#define if32 if 0
+#define if64 if 1
+#else
+#define codemp code32
+#define DI edi
+#define SP esp
+#define if32 if 1
+#define if64 if 0
+#endif
+
+/* Standard features CPUID leaf */
+#define CPUID_FEATURES 0x00000001
+
+/* x2APIC is supported */
+#define CPUID_FEATURES_ECX_X2APIC 0x00200000
+
+/* Extended topology enumeration CPUID leaf */
+#define CPUID_XT_ENUM 0x0000000b
+
+/*
+ * Call multiprocessor function from C code
+ *
+ * Parameters:
+ * 4(%esp)/%rdi Multiprocessor function
+ * 8(%esp)/%rsi Opaque data pointer
+ */
+ .section ".text.mp_call", "ax", @progbits
+ .codemp
+ .globl mp_call
+mp_call:
+.if64 /* Preserve registers, load incoming parameters into registers */
+ pushq %rax
+ pushq %rcx
+ pushq %rdx
+ pushq %rbx
+ pushq %rsp
+ pushq %rbp
+ pushq %rsi
+ pushq %rdi
+ pushq %r8
+ pushq %r9
+ pushq %r10
+ pushq %r11
+ pushq %r12
+ pushq %r13
+ pushq %r14
+ pushq %r15
+.else
+ pushal
+ movl 36(%esp), %eax
+ movl 40(%esp), %edx
+.endif
+ /* Call multiprocessor function */
+ call mp_jump
+
+.if64 /* Restore registers and return */
+ popq %r15
+ popq %r14
+ popq %r13
+ popq %r12
+ popq %r11
+ popq %r10
+ popq %r9
+ popq %r8
+ popq %rdi
+ popq %rsi
+ popq %rbp
+ leaq 8(%rsp), %rsp /* discard */
+ popq %rbx
+ popq %rdx
+ popq %rcx
+ popq %rax
+.else
+ popal
+.endif
+ ret
+ .size mp_call, . - mp_call
+
+/*
+ * Jump to multiprocessor function
+ *
+ * Parameters:
+ * %eax/%rdi Multiprocessor function
+ * %edx/%rsi Opaque data pointer
+ * %esp/%rsp Stack, or NULL to halt AP upon completion
+ *
+ * Obtain the CPU identifier (i.e. the APIC ID) and perform a tail
+ * call into the specified multiprocessor function.
+ *
+ * This code may run with no stack on an application processor.
+ */
+ .section ".text.mp_jump", "ax", @progbits
+ .codemp
+ .globl mp_jump
+mp_jump:
+.if32 /* Move function parameters to available registers */
+ movl %eax, %edi
+ movl %edx, %esi
+.endif
+
+ /* Get 8-bit APIC ID and x2APIC feature bit */
+ movl $CPUID_FEATURES, %eax
+ cpuid
+ shrl $24, %ebx
+ movl %ebx, %edx
+
+ /* Get 32-bit x2APIC ID if applicable */
+ testl $CPUID_FEATURES_ECX_X2APIC, %ecx
+ jz 1f
+ movl $CPUID_XT_ENUM, %eax
+ xorl %ecx, %ecx
+ cpuid
+1:
+
+.if64 /* Tail call to function */
+ movq %rdi, %rax
+ movq %rsi, %rdi
+ movl %edx, %esi
+ jmp *%rax
+.else
+ movl %esi, %eax
+ jmp *%edi
+.endif
+ .size mp_jump, . - mp_jump
+
+/*
+ * Update maximum CPU identifier
+ *
+ * Parameters:
+ * %eax/%rdi Pointer to shared maximum APIC ID
+ * %edx/%rsi CPU identifier (APIC ID)
+ * %esp/%rsp Stack, or NULL to halt AP upon completion
+ *
+ * This code may run with no stack on an application processor.
+ */
+ .section ".text.mp_update_max_cpuid", "ax", @progbits
+ .codemp
+ .globl mp_update_max_cpuid
+mp_update_max_cpuid:
+.if32 /* Move function parameters to available registers */
+ movl %eax, %edi
+ movl %edx, %esi
+.endif
+ /* Update maximum APIC ID (atomically) */
+ movl (%DI), %eax
+1: cmpl %esi, %eax
+ jae 2f
+ lock cmpxchgl %esi, (%DI)
+ jnz 1b
+2:
+ /* Return to caller (if stack exists), or halt application processor */
+ test %SP, %SP
+ jz 3f
+ ret
+3: cli
+ hlt
+ jmp 3b
+ .size mp_update_max_cpuid, . - mp_update_max_cpuid
diff --git a/src/arch/x86/core/ucode_mp.S b/src/arch/x86/core/ucode_mp.S
new file mode 100644
index 00000000..808e881e
--- /dev/null
+++ b/src/arch/x86/core/ucode_mp.S
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2024 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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
+ *
+ * Microcode updates
+ *
+ */
+
+ .section ".note.GNU-stack", "", @progbits
+ .text
+
+/* Selectively assemble code for 32-bit/64-bit builds */
+#if defined ( __x86_64__ ) && ! defined ( PLATFORM_pcbios )
+#define codemp code64
+#define AX rax
+#define BX rbx
+#define CX rcx
+#define DX rdx
+#define SI rsi
+#define DI rdi
+#define BP rbp
+#define SP rsp
+#define if32 if 0
+#define if64 if 1
+#else
+#define codemp code32
+#define AX eax
+#define BX ebx
+#define CX ecx
+#define DX edx
+#define SI esi
+#define DI edi
+#define BP ebp
+#define SP esp
+#define if32 if 1
+#define if64 if 0
+#endif
+
+/* Standard features CPUID leaf */
+#define CPUID_FEATURES 0x00000001
+
+/* BIOS update signature MSR */
+#define MSR_BIOS_SIGN_ID 0x0000008b
+
+/** Microcode update control layout
+ *
+ * This must match the layout of struct ucode_control.
+ */
+ .struct 0
+CONTROL_DESC:
+ .space 8
+CONTROL_STATUS:
+ .space 8
+CONTROL_TRIGGER_MSR:
+ .space 4
+CONTROL_APIC_MAX:
+ .space 4
+CONTROL_APIC_UNEXPECTED:
+ .space 4
+CONTROL_APIC_MASK:
+ .space 4
+CONTROL_APIC_TEST:
+ .space 4
+CONTROL_VER_CLEAR:
+ .space 1
+CONTROL_VER_HIGH:
+ .space 1
+CONTROL_LEN:
+
+/* We use register %ebp/%rbp to hold the address of the update control */
+#define CONTROL BP
+
+/* Microcode update descriptor layout
+ *
+ * This must match the layout of struct ucode_descriptor.
+ */
+ .struct 0
+DESC_SIGNATURE:
+ .space 4
+DESC_VERSION:
+ .space 4
+DESC_ADDRESS:
+ .space 8
+DESC_LEN:
+
+/* We use register %esi/%rsi to hold the address of the descriptor */
+#define DESC SI
+
+/** Microcode update status report layout
+ *
+ * This must match the layout of struct ucode_status.
+ */
+ .struct 0
+STATUS_SIGNATURE:
+ .space 4
+STATUS_ID:
+ .space 4
+STATUS_BEFORE:
+ .space 4
+STATUS_AFTER:
+ .space 4
+STATUS_LEN:
+ .equ LOG2_STATUS_LEN, 4
+ .if ( 1 << LOG2_STATUS_LEN ) - STATUS_LEN
+ .error "LOG2_STATUS_LEN value is incorrect"
+ .endif
+
+/* We use register %edi/%rdi to hold the address of the status report */
+#define STATUS DI
+
+/*
+ * Update microcode
+ *
+ * Parameters:
+ * %eax/%rdi Microcode update structure
+ * %edx/%rsi CPU identifier (APIC ID)
+ * %esp/%rsp Stack, or NULL to halt AP upon completion
+ *
+ * This code may run with no stack on an application processor (AP).
+ * All values must be held in registers, and no subroutine calls are
+ * possible. No firmware routines may be called.
+ *
+ * Since cpuid/rdmsr/wrmsr require the use of %eax, %ebx, %ecx, and
+ * %edx, we have essentially only three registers available for
+ * long-term state.
+ */
+ .text
+ .globl ucode_update
+ .codemp
+ .section ".text.ucode_update", "ax", @progbits
+ucode_update:
+
+.if64 /* Get input parameters */
+ movq %rdi, %CONTROL
+ movl %esi, %edx
+.else
+ movl %eax, %CONTROL
+.endif
+ /* Check against maximum expected APIC ID */
+ cmpl CONTROL_APIC_MAX(%CONTROL), %edx
+ jbe 1f
+ movl %edx, CONTROL_APIC_UNEXPECTED(%CONTROL)
+ jmp done
+1:
+ /* Calculate per-CPU status report buffer address */
+ mov %DX, %STATUS
+ shl $LOG2_STATUS_LEN, %STATUS
+ add CONTROL_STATUS(%CONTROL), %STATUS
+
+ /* Report APIC ID */
+ movl %edx, STATUS_ID(%STATUS)
+
+ /* Get and report CPU signature */
+ movl $CPUID_FEATURES, %eax
+ cpuid
+ movl %eax, STATUS_SIGNATURE(%STATUS)
+
+ /* Check APIC ID mask */
+ movl STATUS_ID(%STATUS), %eax
+ andl CONTROL_APIC_MASK(%CONTROL), %eax
+ cmpl CONTROL_APIC_TEST(%CONTROL), %eax
+ jne done
+
+ /* Clear BIOS_SIGN_ID MSR if applicable */
+ movl $MSR_BIOS_SIGN_ID, %ecx
+ xorl %eax, %eax
+ xorl %edx, %edx
+ testb $0xff, CONTROL_VER_CLEAR(%CONTROL)
+ jz 1f
+ wrmsr
+1:
+ /* Get CPU signature to repopulate BIOS_SIGN_ID MSR (for Intel) */
+ movl $CPUID_FEATURES, %eax
+ cpuid
+
+ /* Get initial microcode version */
+ movl $MSR_BIOS_SIGN_ID, %ecx
+ rdmsr
+ testb $0xff, CONTROL_VER_HIGH(%CONTROL)
+ jz 1f
+ movl %edx, %eax
+1: movl %eax, STATUS_BEFORE(%STATUS)
+
+ /* Get start of descriptor list */
+ mov CONTROL_DESC(%CONTROL), %DESC
+ sub $DESC_LEN, %DESC
+
+1: /* Walk update descriptor list to find a matching CPU signature */
+ add $DESC_LEN, %DESC
+ movl DESC_SIGNATURE(%DESC), %eax
+ testl %eax, %eax
+ jz noload
+ cmpl STATUS_SIGNATURE(%STATUS), %eax
+ jne 1b
+
+ /* Compare (signed) microcode versions */
+ movl STATUS_BEFORE(%STATUS), %eax
+ cmpl DESC_VERSION(%DESC), %eax
+ jge noload
+
+ /* Load microcode update */
+ movl CONTROL_TRIGGER_MSR(%CONTROL), %ecx
+ movl (DESC_ADDRESS + 0)(%DESC), %eax
+ movl (DESC_ADDRESS + 4)(%DESC), %edx
+ wrmsr
+
+noload: /* Clear BIOS_SIGN_ID MSR if applicable */
+ movl $MSR_BIOS_SIGN_ID, %ecx
+ xorl %eax, %eax
+ xorl %edx, %edx
+ testb $0xff, CONTROL_VER_CLEAR(%CONTROL)
+ jz 1f
+ wrmsr
+1:
+ /* Get CPU signature to repopulate BIOS_SIGN_ID MSR (for Intel) */
+ movl $CPUID_FEATURES, %eax
+ cpuid
+
+ /* Get and report final microcode version */
+ movl $MSR_BIOS_SIGN_ID, %ecx
+ rdmsr
+ testb $0xff, CONTROL_VER_HIGH(%CONTROL)
+ jz 1f
+ movl %edx, %eax
+1: movl %eax, STATUS_AFTER(%STATUS)
+
+done: /* Return to caller (if stack exists), or halt application processor */
+ test %SP, %SP
+ jz 1f
+ ret
+1: cli
+ hlt
+ jmp 1b
+ .size ucode_update, . - ucode_update
diff --git a/src/arch/x86/image/ucode.c b/src/arch/x86/image/ucode.c
new file mode 100644
index 00000000..499c0a94
--- /dev/null
+++ b/src/arch/x86/image/ucode.c
@@ -0,0 +1,798 @@
+/*
+ * Copyright (C) 2024 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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
+ *
+ * Microcode updates
+ *
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <assert.h>
+#include <errno.h>
+#include <ipxe/uaccess.h>
+#include <ipxe/umalloc.h>
+#include <ipxe/image.h>
+#include <ipxe/cpuid.h>
+#include <ipxe/msr.h>
+#include <ipxe/mp.h>
+#include <ipxe/timer.h>
+#include <ipxe/ucode.h>
+
+/**
+ * Maximum number of hyperthread siblings
+ *
+ * Microcode updates must not be performed on hyperthread siblings at
+ * the same time, since they share microcode storage.
+ *
+ * Hyperthread siblings are always the lowest level of the CPU
+ * topology and correspond to the least significant bits of the APIC
+ * ID. We may therefore avoid collisions by performing the microcode
+ * updates in batches, with each batch targeting just one value for
+ * the least significant N bits of the APIC ID.
+ *
+ * We assume that no CPUs exist with more than this number of
+ * hyperthread siblings. (This must be a power of two.)
+ */
+#define UCODE_MAX_HT 8
+
+/** Time to wait for a microcode update to complete */
+#define UCODE_WAIT_MS 10
+
+/** A CPU vendor string */
+union ucode_vendor_id {
+ /** CPUID registers */
+ uint32_t dword[3];
+ /** Human-readable string */
+ uint8_t string[12];
+};
+
+/** A CPU vendor */
+struct ucode_vendor {
+ /** Vendor string */
+ union ucode_vendor_id id;
+ /** Microcode load trigger MSR */
+ uint32_t trigger_msr;
+ /** Microcode version requires manual clear */
+ uint8_t ver_clear;
+ /** Microcode version is reported via high dword */
+ uint8_t ver_high;
+};
+
+/** A microcode update */
+struct ucode_update {
+ /** CPU vendor, if known */
+ struct ucode_vendor *vendor;
+ /** Boot processor CPU signature */
+ uint32_t signature;
+ /** Platform ID */
+ uint32_t platform;
+ /** Number of potentially relevant signatures found */
+ unsigned int count;
+ /** Update descriptors (if being populated) */
+ struct ucode_descriptor *desc;
+};
+
+/** A microcode update summary */
+struct ucode_summary {
+ /** Number of CPUs processed */
+ unsigned int count;
+ /** Lowest observed microcode version */
+ int32_t low;
+ /** Highest observed microcode version */
+ int32_t high;
+};
+
+/** Intel CPU vendor */
+static struct ucode_vendor ucode_intel = {
+ .id = { .string = "GenuineIntel" },
+ .ver_clear = 1,
+ .ver_high = 1,
+ .trigger_msr = MSR_UCODE_TRIGGER_INTEL,
+};
+
+/** AMD CPU vendor */
+static struct ucode_vendor ucode_amd = {
+ .id = { .string = "AuthenticAMD" },
+ .trigger_msr = MSR_UCODE_TRIGGER_AMD,
+};
+
+/** List of known CPU vendors */
+static struct ucode_vendor *ucode_vendors[] = {
+ &ucode_intel,
+ &ucode_amd,
+};
+
+/**
+ * Get CPU vendor name (for debugging)
+ *
+ * @v vendor CPU vendor
+ * @ret name Name
+ */
+static const char * ucode_vendor_name ( const union ucode_vendor_id *vendor ) {
+ static union {
+ union ucode_vendor_id vendor;
+ char text[ sizeof ( *vendor ) + 1 /* NUL */ ];
+ } u;
+
+ /* Construct name */
+ memcpy ( &u.vendor, vendor, sizeof ( u.vendor ) );
+ u.text[ sizeof ( u.text ) - 1 ] = '\0';
+ return u.text;
+}
+
+/**
+ * Check status report
+ *
+ * @v update Microcode update
+ * @v control Microcode update control
+ * @v summary Microcode update summary
+ * @v id APIC ID
+ * @v optional Status report is optional
+ * @ret rc Return status code
+ */
+static int ucode_status ( struct ucode_update *update,
+ struct ucode_control *control,
+ struct ucode_summary *summary,
+ unsigned int id, int optional ) {
+ struct ucode_status status;
+ struct ucode_descriptor *desc;
+
+ /* Sanity check */
+ assert ( id <= control->apic_max );
+
+ /* Read status report */
+ copy_from_user ( &status, phys_to_user ( control->status ),
+ ( id * sizeof ( status ) ), sizeof ( status ) );
+
+ /* Ignore empty optional status reports */
+ if ( optional && ( ! status.signature ) )
+ return 0;
+ DBGC ( update, "UCODE %#08x signature %#08x ucode %#08x->%#08x\n",
+ id, status.signature, status.before, status.after );
+
+ /* Check CPU signature */
+ if ( ! status.signature ) {
+ DBGC2 ( update, "UCODE %#08x has no signature\n", id );
+ return -ENOENT;
+ }
+
+ /* Check APIC ID is correct */
+ if ( status.id != id ) {
+ DBGC ( update, "UCODE %#08x wrong APIC ID %#08x\n",
+ id, status.id );
+ return -EINVAL;
+ }
+
+ /* Check that maximum APIC ID was not exceeded */
+ if ( control->apic_unexpected ) {
+ DBGC ( update, "UCODE %#08x saw unexpected APIC ID %#08x\n",
+ id, control->apic_unexpected );
+ return -ERANGE;
+ }
+
+ /* Check microcode was not downgraded */
+ if ( status.after < status.before ) {
+ DBGC ( update, "UCODE %#08x was downgraded %#08x->%#08x\n",
+ id, status.before, status.after );
+ return -ENOTTY;
+ }
+
+ /* Check that expected updates (if any) were applied */
+ for ( desc = update->desc ; desc->signature ; desc++ ) {
+ if ( ( desc->signature == status.signature ) &&
+ ( status.after < desc->version ) ) {
+ DBGC ( update, "UCODE %#08x failed update %#08x->%#08x "
+ "(wanted %#08x)\n", id, status.before,
+ status.after, desc->version );
+ return -EIO;
+ }
+ }
+
+ /* Update summary */
+ summary->count++;
+ if ( status.before < summary->low )
+ summary->low = status.before;
+ if ( status.after > summary->high )
+ summary->high = status.after;
+
+ return 0;
+}
+
+/**
+ * Update microcode on all CPUs
+ *
+ * @v image Microcode image
+ * @v update Microcode update
+ * @v summary Microcode update summary to fill in
+ * @ret rc Return status code
+ */
+static int ucode_update_all ( struct image *image,
+ struct ucode_update *update,
+ struct ucode_summary *summary ) {
+ struct ucode_control control;
+ struct ucode_vendor *vendor;
+ userptr_t status;
+ unsigned int max;
+ unsigned int i;
+ size_t len;
+ int rc;
+
+ /* Initialise summary */
+ summary->count = 0;
+ summary->low = UCODE_VERSION_MAX;
+ summary->high = UCODE_VERSION_MIN;
+
+ /* Allocate status reports */
+ max = mp_max_cpuid();
+ len = ( ( max + 1 ) * sizeof ( struct ucode_status ) );
+ status = umalloc ( len );
+ if ( ! status ) {
+ DBGC ( image, "UCODE %s could not allocate %d status reports\n",
+ image->name, ( max + 1 ) );
+ rc = -ENOMEM;
+ goto err_alloc;
+ }
+ memset_user ( status, 0, 0, len );
+
+ /* Construct control structure */
+ memset ( &control, 0, sizeof ( control ) );
+ control.desc = virt_to_phys ( update->desc );
+ control.status = user_to_phys ( status, 0 );
+ vendor = update->vendor;
+ if ( vendor ) {
+ control.ver_clear = vendor->ver_clear;
+ control.ver_high = vendor->ver_high;
+ control.trigger_msr = vendor->trigger_msr;
+ } else {
+ assert ( update->count == 0 );
+ }
+ control.apic_max = max;
+
+ /* Update microcode on boot processor */
+ mp_exec_boot ( ucode_update, &control );
+ if ( ( rc = ucode_status ( update, &control, summary,
+ mp_boot_cpuid(), 0 ) ) != 0 ) {
+ DBGC ( image, "UCODE %s failed on boot processor: %s\n",
+ image->name, strerror ( rc ) );
+ goto err_boot;
+ }
+
+ /* Update microcode on application processors, avoiding
+ * simultaneous updates on hyperthread siblings.
+ */
+ build_assert ( ( UCODE_MAX_HT & ( UCODE_MAX_HT - 1 ) ) == 0 );
+ control.apic_mask = ( UCODE_MAX_HT - 1 );
+ for ( ; control.apic_test <= control.apic_mask ; control.apic_test++ ) {
+ mp_start_all ( ucode_update, &control );
+ mdelay ( UCODE_WAIT_MS );
+ }
+
+ /* Check status reports */
+ summary->count = 0;
+ for ( i = 0 ; i <= max ; i++ ) {
+ if ( ( rc = ucode_status ( update, &control, summary,
+ i, 1 ) ) != 0 ) {
+ goto err_status;
+ }
+ }
+
+ /* Success */
+ rc = 0;
+
+ err_status:
+ err_boot:
+ ufree ( status );
+ err_alloc:
+ return rc;
+}
+
+/**
+ * Add descriptor to list (if applicable)
+ *
+ * @v image Microcode image
+ * @v start Starting offset within image
+ * @v vendor CPU vendor
+ * @v desc Microcode descriptor
+ * @v platforms Supported platforms, or 0 for all platforms
+ * @v update Microcode update
+ */
+static void ucode_describe ( struct image *image, size_t start,
+ const struct ucode_vendor *vendor,
+ const struct ucode_descriptor *desc,
+ uint32_t platforms, struct ucode_update *update ) {
+
+ /* Dump descriptor information */
+ DBGC2 ( image, "UCODE %s+%#04zx %s %#08x", image->name, start,
+ ucode_vendor_name ( &vendor->id ), desc->signature );
+ if ( platforms )
+ DBGC2 ( image, " (%#02x)", platforms );
+ DBGC2 ( image, " version %#08x\n", desc->version );
+
+ /* Check applicability */
+ if ( vendor != update->vendor )
+ return;
+ if ( ( desc->signature ^ update->signature ) & UCODE_SIGNATURE_MASK )
+ return;
+ if ( platforms && ( ! ( platforms & update->platform ) ) )
+ return;
+
+ /* Add descriptor, if applicable */
+ if ( update->desc ) {
+ memcpy ( &update->desc[update->count], desc, sizeof ( *desc ) );
+ DBGC ( image, "UCODE %s+%#04zx found %s %#08x version %#08x\n",
+ image->name, start, ucode_vendor_name ( &vendor->id ),
+ desc->signature, desc->version );
+ }
+ update->count++;
+}
+
+/**
+ * Verify checksum
+ *
+ * @v image Microcode image
+ * @v start Starting offset
+ * @v len Length
+ * @ret rc Return status code
+ */
+static int ucode_verify ( struct image *image, size_t start, size_t len ) {
+ uint32_t checksum = 0;
+ uint32_t dword;
+ size_t offset;
+
+ /* Check length is a multiple of dwords */
+ if ( ( len % sizeof ( dword ) ) != 0 ) {
+ DBGC ( image, "UCODE %s+%#04zx invalid length %#zx\n",
+ image->name, start, len );
+ return -EINVAL;
+ }
+
+ /* Calculate checksum */
+ for ( offset = start ; len ;
+ offset += sizeof ( dword ), len -= sizeof ( dword ) ) {
+ copy_from_user ( &dword, image->data, offset,
+ sizeof ( dword ) );
+ checksum += dword;
+ }
+ if ( checksum != 0 ) {
+ DBGC ( image, "UCODE %s+%#04zx bad checksum %#08x\n",
+ image->name, start, checksum );
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/**
+ * Parse Intel microcode image
+ *
+ * @v image Microcode image
+ * @v start Starting offset within image
+ * @v update Microcode update
+ * @ret len Length consumed, or negative error
+ */
+static int ucode_parse_intel ( struct image *image, size_t start,
+ struct ucode_update *update ) {
+ struct intel_ucode_header hdr;
+ struct intel_ucode_ext_header exthdr;
+ struct intel_ucode_ext ext;
+ struct ucode_descriptor desc;
+ size_t remaining;
+ size_t offset;
+ size_t data_len;
+ size_t len;
+ unsigned int i;
+ int rc;
+
+ /* Read header */
+ remaining = ( image->len - start );
+ if ( remaining < sizeof ( hdr ) ) {
+ DBGC ( image, "UCODE %s+%#04zx too small for Intel header\n",
+ image->name, start );
+ return -ENOEXEC;
+ }
+ copy_from_user ( &hdr, image->data, start, sizeof ( hdr ) );
+
+ /* Determine lengths */
+ data_len = hdr.data_len;
+ if ( ! data_len )
+ data_len = INTEL_UCODE_DATA_LEN;
+ len = hdr.len;
+ if ( ! len )
+ len = ( sizeof ( hdr ) + data_len );
+
+ /* Verify a selection of fields */
+ if ( ( hdr.hver != INTEL_UCODE_HVER ) ||
+ ( hdr.lver != INTEL_UCODE_LVER ) ||
+ ( len < sizeof ( hdr ) ) ||
+ ( len > remaining ) ||
+ ( data_len > ( len - sizeof ( hdr ) ) ) ||
+ ( ( data_len % sizeof ( uint32_t ) ) != 0 ) ||
+ ( ( len % INTEL_UCODE_ALIGN ) != 0 ) ) {
+ DBGC2 ( image, "UCODE %s+%#04zx is not an Intel update\n",
+ image->name, start );
+ return -EINVAL;
+ }
+ DBGC2 ( image, "UCODE %s+%#04zx is an Intel update\n",
+ image->name, start );
+
+ /* Verify checksum */
+ if ( ( rc = ucode_verify ( image, start, len ) ) != 0 )
+ return rc;
+
+ /* Populate descriptor */
+ desc.signature = hdr.signature;
+ desc.version = hdr.version;
+ desc.address = user_to_phys ( image->data,
+ ( start + sizeof ( hdr ) ) );
+
+ /* Add non-extended descriptor, if applicable */
+ ucode_describe ( image, start, &ucode_intel, &desc, hdr.platforms,
+ update );
+
+ /* Construct extended descriptors, if applicable */
+ offset = ( sizeof ( hdr ) + data_len );
+ if ( offset <= ( len - sizeof ( exthdr ) ) ) {
+
+ /* Read extended header */
+ copy_from_user ( &exthdr, image->data, ( start + offset ),
+ sizeof ( exthdr ) );
+ offset += sizeof ( exthdr );
+
+ /* Read extended signatures */
+ for ( i = 0 ; i < exthdr.count ; i++ ) {
+
+ /* Read extended signature */
+ if ( offset > ( len - sizeof ( ext ) ) ) {
+ DBGC ( image, "UCODE %s+%#04zx extended "
+ "signature overrun\n",
+ image->name, start );
+ return -EINVAL;
+ }
+ copy_from_user ( &ext, image->data, ( start + offset ),
+ sizeof ( ext ) );
+ offset += sizeof ( ext );
+
+ /* Avoid duplicating non-extended descriptor */
+ if ( ( ext.signature == hdr.signature ) &&
+ ( ext.platforms == hdr.platforms ) ) {
+ continue;
+ }
+
+ /* Construct descriptor, if applicable */
+ desc.signature = ext.signature;
+ ucode_describe ( image, start, &ucode_intel, &desc,
+ ext.platforms, update );
+ }
+ }
+
+ return len;
+}
+
+/**
+ * Parse AMD microcode image
+ *
+ * @v image Microcode image
+ * @v start Starting offset within image
+ * @v update Microcode update
+ * @ret len Length consumed, or negative error
+ */
+static int ucode_parse_amd ( struct image *image, size_t start,
+ struct ucode_update *update ) {
+ struct amd_ucode_header hdr;
+ struct amd_ucode_equivalence equiv;
+ struct amd_ucode_patch_header phdr;
+ struct amd_ucode_patch patch;
+ struct ucode_descriptor desc;
+ size_t remaining;
+ size_t offset;
+ unsigned int count;
+ unsigned int used;
+ unsigned int i;
+
+ /* Read header */
+ remaining = ( image->len - start );
+ if ( remaining < sizeof ( hdr ) ) {
+ DBGC ( image, "UCODE %s+%#04zx too small for AMD header\n",
+ image->name, start );
+ return -ENOEXEC;
+ }
+ copy_from_user ( &hdr, image->data, start, sizeof ( hdr ) );
+
+ /* Check header */
+ if ( hdr.magic != AMD_UCODE_MAGIC ) {
+ DBGC2 ( image, "UCODE %s+%#04zx is not an AMD update\n",
+ image->name, start );
+ return -ENOEXEC;
+ }
+ DBGC2 ( image, "UCODE %s+%#04zx is an AMD update\n",
+ image->name, start );
+ if ( hdr.type != AMD_UCODE_EQUIV_TYPE ) {
+ DBGC ( image, "UCODE %s+%#04zx unsupported equivalence table "
+ "type %d\n", image->name, start, hdr.type );
+ return -ENOTSUP;
+ }
+ if ( hdr.len > ( remaining - sizeof ( hdr ) ) ) {
+ DBGC ( image, "UCODE %s+%#04zx truncated equivalence table\n",
+ image->name, start );
+ return -EINVAL;
+ }
+
+ /* Count number of equivalence table entries */
+ offset = sizeof ( hdr );
+ for ( count = 0 ; offset < ( sizeof ( hdr ) + hdr.len ) ;
+ count++, offset += sizeof ( equiv ) ) {
+ copy_from_user ( &equiv, image->data, ( start + offset ),
+ sizeof ( equiv ) );
+ if ( ! equiv.signature )
+ break;
+ }
+ DBGC2 ( image, "UCODE %s+%#04zx has %d equivalence table entries\n",
+ image->name, start, count );
+
+ /* Parse available updates */
+ offset = ( sizeof ( hdr ) + hdr.len );
+ used = 0;
+ while ( used < count ) {
+
+ /* Read patch header */
+ if ( ( offset + sizeof ( phdr ) ) > remaining ) {
+ DBGC ( image, "UCODE %s+%#04zx truncated patch "
+ "header\n", image->name, start );
+ return -EINVAL;
+ }
+ copy_from_user ( &phdr, image->data, ( start + offset ),
+ sizeof ( phdr ) );
+ offset += sizeof ( phdr );
+
+ /* Validate patch header */
+ if ( phdr.type != AMD_UCODE_PATCH_TYPE ) {
+ DBGC ( image, "UCODE %s+%#04zx unsupported patch type "
+ "%d\n", image->name, start, phdr.type );
+ return -ENOTSUP;
+ }
+ if ( phdr.len < sizeof ( patch ) ) {
+ DBGC ( image, "UCODE %s+%#04zx underlength patch\n",
+ image->name, start );
+ return -EINVAL;
+ }
+ if ( phdr.len > ( remaining - offset ) ) {
+ DBGC ( image, "UCODE %s+%#04zx truncated patch\n",
+ image->name, start );
+ return -EINVAL;
+ }
+
+ /* Read patch and construct descriptor */
+ copy_from_user ( &patch, image->data, ( start + offset ),
+ sizeof ( patch ) );
+ desc.version = patch.version;
+ desc.address = user_to_phys ( image->data, ( start + offset ) );
+ offset += phdr.len;
+
+ /* Parse equivalence table to find matching signatures */
+ for ( i = 0 ; i < count ; i++ ) {
+ copy_from_user ( &equiv, image->data,
+ ( start + sizeof ( hdr ) +
+ ( i * ( sizeof ( equiv ) ) ) ),
+ sizeof ( equiv ) );
+ if ( patch.id == equiv.id ) {
+ desc.signature = equiv.signature;
+ ucode_describe ( image, start, &ucode_amd,
+ &desc, 0, update );
+ used++;
+ }
+ }
+ }
+
+ return offset;
+}
+
+/**
+ * Parse microcode image
+ *
+ * @v image Microcode image
+ * @v update Microcode update
+ * @ret rc Return status code
+ */
+static int ucode_parse ( struct image *image, struct ucode_update *update ) {
+ size_t start;
+ int len;
+
+ /* Attempt to parse concatenated microcode updates */
+ for ( start = 0 ; start < image->len ; start += len ) {
+
+ /* Attempt to parse as Intel microcode */
+ len = ucode_parse_intel ( image, start, update );
+ if ( len > 0 )
+ continue;
+
+ /* Attempt to parse as AMD microcode */
+ len = ucode_parse_amd ( image, start, update );
+ if ( len > 0 )
+ continue;
+
+ /* Not a recognised microcode format */
+ DBGC ( image, "UCODE %s+%zx not recognised\n",
+ image->name, start );
+ return -ENOEXEC;
+ }
+
+ return 0;
+}
+
+/**
+ * Execute microcode update
+ *
+ * @v image Microcode image
+ * @ret rc Return status code
+ */
+static int ucode_exec ( struct image *image ) {
+ struct ucode_update update;
+ struct ucode_vendor *vendor;
+ struct ucode_summary summary;
+ union ucode_vendor_id id;
+ uint64_t platform_id;
+ uint32_t discard_a;
+ uint32_t discard_b;
+ uint32_t discard_c;
+ uint32_t discard_d;
+ unsigned int check;
+ unsigned int i;
+ size_t len;
+ int rc;
+
+ /* Initialise update */
+ memset ( &update, 0, sizeof ( update ) );
+ cpuid ( CPUID_VENDOR_ID, 0, &discard_a, &id.dword[0], &id.dword[2],
+ &id.dword[1] );
+ cpuid ( CPUID_FEATURES, 0, &update.signature, &discard_b,
+ &discard_c, &discard_d );
+
+ /* Identify CPU vendor, if recognised */
+ for ( i = 0 ; i < ( sizeof ( ucode_vendors ) /
+ sizeof ( ucode_vendors[0] ) ) ; i++ ) {
+ vendor = ucode_vendors[i];
+ if ( memcmp ( &id, &vendor->id, sizeof ( id ) ) == 0 )
+ update.vendor = vendor;
+ }
+
+ /* Identify platform, if applicable */
+ if ( update.vendor == &ucode_intel ) {
+ platform_id = rdmsr ( MSR_PLATFORM_ID );
+ update.platform =
+ ( 1 << MSR_PLATFORM_ID_VALUE ( platform_id ) );
+ }
+
+ /* Count number of matching update descriptors */
+ DBGC ( image, "UCODE %s applying to %s %#08x",
+ image->name, ucode_vendor_name ( &id ), update.signature );
+ if ( update.platform )
+ DBGC ( image, " (%#02x)", update.platform );
+ DBGC ( image, "\n" );
+ if ( ( rc = ucode_parse ( image, &update ) ) != 0 )
+ goto err_count;
+ DBGC ( image, "UCODE %s found %d matching update(s)\n",
+ image->name, update.count );
+
+ /* Allocate descriptors */
+ len = ( ( update.count + 1 /* terminator */ ) *
+ sizeof ( update.desc[0] ) );
+ update.desc = zalloc ( len );
+ if ( ! update.desc ) {
+ rc = -ENOMEM;
+ goto err_alloc;
+ }
+
+ /* Populate descriptors */
+ check = update.count;
+ update.count = 0;
+ if ( ( rc = ucode_parse ( image, &update ) ) != 0 )
+ goto err_parse;
+ assert ( check == update.count );
+
+ /* Perform update */
+ if ( ( rc = ucode_update_all ( image, &update, &summary ) ) != 0 )
+ goto err_update;
+
+ /* Print summary if directed to do so */
+ if ( image->cmdline && ( strstr ( image->cmdline, "-v" ) ) ) {
+ printf ( "Microcode: " );
+ if ( summary.low == summary.high ) {
+ printf ( "already version %#x", summary.low );
+ } else {
+ printf ( "updated version %#x->%#x",
+ summary.low, summary.high );
+ }
+ printf ( " (x%d)\n", summary.count );
+ }
+
+ err_update:
+ err_parse:
+ free ( update.desc );
+ err_alloc:
+ err_count:
+ return rc;
+}
+
+/**
+ * Probe microcode update image
+ *
+ * @v image Microcode image
+ * @ret rc Return status code
+ */
+static int ucode_probe ( struct image *image ) {
+ union {
+ struct intel_ucode_header intel;
+ struct amd_ucode_header amd;
+ } header;
+
+ /* Sanity check */
+ if ( image->len < sizeof ( header ) ) {
+ DBGC ( image, "UCODE %s too short\n", image->name );
+ return -ENOEXEC;
+ }
+
+ /* Read first microcode image header */
+ copy_from_user ( &header, image->data, 0, sizeof ( header ) );
+
+ /* Check for something that looks like an Intel update
+ *
+ * Intel updates unfortunately have no magic signatures or
+ * other easily verifiable fields. We check a small selection
+ * of header fields that can be easily verified.
+ *
+ * We do not attempt to fully parse the update, since we want
+ * errors to be reported at the point of attempting to execute
+ * the image, and do not want to have a microcode image
+ * erroneously treated as a PXE boot executable.
+ */
+ if ( ( header.intel.hver == INTEL_UCODE_HVER ) &&
+ ( header.intel.lver == INTEL_UCODE_LVER ) &&
+ ( ( header.intel.date.century == 0x19 ) ||
+ ( ( header.intel.date.century >= 0x20 ) &&
+ ( header.intel.date.century <= 0x29 ) ) ) ) {
+ DBGC ( image, "UCODE %s+%#04zx looks like an Intel update\n",
+ image->name, ( ( size_t ) 0 ) );
+ return 0;
+ }
+
+ /* Check for AMD update signature */
+ if ( ( header.amd.magic == AMD_UCODE_MAGIC ) &&
+ ( header.amd.type == AMD_UCODE_EQUIV_TYPE ) ) {
+ DBGC ( image, "UCODE %s+%#04zx looks like an AMD update\n",
+ image->name, ( ( size_t ) 0 ) );
+ return 0;
+ }
+
+ return -ENOEXEC;
+}
+
+/** Microcode update image type */
+struct image_type ucode_image_type __image_type ( PROBE_NORMAL ) = {
+ .name = "ucode",
+ .probe = ucode_probe,
+ .exec = ucode_exec,
+};
diff --git a/src/arch/x86/include/bits/errfile.h b/src/arch/x86/include/bits/errfile.h
index b5316a58..78b3dea1 100644
--- a/src/arch/x86/include/bits/errfile.h
+++ b/src/arch/x86/include/bits/errfile.h
@@ -44,6 +44,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#define ERRFILE_sdi ( ERRFILE_ARCH | ERRFILE_IMAGE | 0x000b0000 )
#define ERRFILE_initrd ( ERRFILE_ARCH | ERRFILE_IMAGE | 0x000c0000 )
#define ERRFILE_pxe_call ( ERRFILE_ARCH | ERRFILE_IMAGE | 0x000d0000 )
+#define ERRFILE_ucode ( ERRFILE_ARCH | ERRFILE_IMAGE | 0x000e0000 )
#define ERRFILE_undi ( ERRFILE_ARCH | ERRFILE_NET | 0x00000000 )
#define ERRFILE_undiload ( ERRFILE_ARCH | ERRFILE_NET | 0x00010000 )
diff --git a/src/arch/x86/include/bits/mp.h b/src/arch/x86/include/bits/mp.h
new file mode 100644
index 00000000..4541aca3
--- /dev/null
+++ b/src/arch/x86/include/bits/mp.h
@@ -0,0 +1,14 @@
+#ifndef _BITS_MP_H
+#define _BITS_MP_H
+
+/** @file
+ *
+ * x86-specific multiprocessor API implementation
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+#include <ipxe/bios_mp.h>
+
+#endif /* _BITS_MP_H */
diff --git a/src/arch/x86/include/ipxe/bios_mp.h b/src/arch/x86/include/ipxe/bios_mp.h
new file mode 100644
index 00000000..e2e83a15
--- /dev/null
+++ b/src/arch/x86/include/ipxe/bios_mp.h
@@ -0,0 +1,32 @@
+#ifndef _IPXE_BIOS_MP_H
+#define _IPXE_BIOS_MP_H
+
+/** @file
+ *
+ * BIOS multiprocessor API implementation
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+#include <ipxe/io.h>
+
+#ifdef MPAPI_PCBIOS
+#define MPAPI_PREFIX_pcbios
+#else
+#define MPAPI_PREFIX_pcbios __pcbios_
+#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 ( pcbios, mp_address ) ( void *address ) {
+
+ return virt_to_phys ( address );
+}
+
+#endif /* _IPXE_BIOS_MP_H */
diff --git a/src/arch/x86/include/ipxe/ucode.h b/src/arch/x86/include/ipxe/ucode.h
new file mode 100644
index 00000000..964e8d7b
--- /dev/null
+++ b/src/arch/x86/include/ipxe/ucode.h
@@ -0,0 +1,223 @@
+#ifndef _IPXE_UCODE_H
+#define _IPXE_UCODE_H
+
+/** @file
+ *
+ * Microcode updates
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+#include <stdint.h>
+#include <ipxe/mp.h>
+
+/** Platform ID MSR */
+#define MSR_PLATFORM_ID 0x00000017UL
+
+/** Extract platform ID from MSR value */
+#define MSR_PLATFORM_ID_VALUE( value ) ( ( (value) >> 50 ) & 0x7 )
+
+/** Intel microcode load trigger MSR */
+#define MSR_UCODE_TRIGGER_INTEL 0x00000079UL
+
+/** AMD microcode load trigger MSR */
+#define MSR_UCODE_TRIGGER_AMD 0xc0010020UL
+
+/** CPUID signature applicability mask
+ *
+ * We assume that only steppings may vary between the boot CPU and any
+ * application processors.
+ */
+#define UCODE_SIGNATURE_MASK 0xfffffff0UL
+
+/** Minimum possible microcode version */
+#define UCODE_VERSION_MIN -0x80000000L
+
+/** Maximum possible microcode version */
+#define UCODE_VERSION_MAX 0x7fffffffL
+
+/** A microcode update control
+ *
+ * This must match the layout as used by the assembly code in
+ * ucode_mp.S.
+ */
+struct ucode_control {
+ /** Microcode descriptor list physical address */
+ uint64_t desc;
+ /** Microcode status array physical address */
+ uint64_t status;
+ /** Microcode load trigger MSR */
+ uint32_t trigger_msr;
+ /** Maximum expected APIC ID */
+ uint32_t apic_max;
+ /** Unexpected APIC ID
+ *
+ * Any application processor may set this to indicate that its
+ * APIC ID was higher than the maximum expected APIC ID.
+ */
+ uint32_t apic_unexpected;
+ /** APIC ID eligibility mask bits */
+ uint32_t apic_mask;
+ /** APIC ID eligibility test bits */
+ uint32_t apic_test;
+ /** Microcode version requires manual clear */
+ uint8_t ver_clear;
+ /** Microcode version is reported via high dword */
+ uint8_t ver_high;
+} __attribute__ (( packed ));
+
+/** A microcode update descriptor
+ *
+ * This must match the layout as used by the assembly code in
+ * ucode_mp.S.
+ */
+struct ucode_descriptor {
+ /** CPUID signature (or 0 to terminate list) */
+ uint32_t signature;
+ /** Microcode version */
+ int32_t version;
+ /** Microcode physical address */
+ uint64_t address;
+} __attribute__ (( packed ));
+
+/** A microcode update status report
+ *
+ * This must match the layout as used by the assembly code in
+ * ucode_mp.S.
+ */
+struct ucode_status {
+ /** CPU signature */
+ uint32_t signature;
+ /** APIC ID (for sanity checking) */
+ uint32_t id;
+ /** Initial microcode version */
+ int32_t before;
+ /** Final microcode version */
+ int32_t after;
+} __attribute__ (( packed ));
+
+/** A microcode date */
+struct ucode_date {
+ /** Year (BCD) */
+ uint8_t year;
+ /** Century (BCD) */
+ uint8_t century;
+ /** Day (BCD) */
+ uint8_t day;
+ /** Month (BCD) */
+ uint8_t month;
+} __attribute__ (( packed ));
+
+/** An Intel microcode update file header */
+struct intel_ucode_header {
+ /** Header version number */
+ uint32_t hver;
+ /** Microcode version */
+ int32_t version;
+ /** Date */
+ struct ucode_date date;
+ /** CPUID signature */
+ uint32_t signature;
+ /** Checksum */
+ uint32_t checksum;
+ /** Loader version */
+ uint32_t lver;
+ /** Supported platforms */
+ uint32_t platforms;
+ /** Microcode data size (or 0 to indicate 2000 bytes) */
+ uint32_t data_len;
+ /** Total size (or 0 to indicate 2048 bytes) */
+ uint32_t len;
+ /** Reserved */
+ uint8_t reserved[12];
+} __attribute__ (( packed ));
+
+/** Intel microcode header version number */
+#define INTEL_UCODE_HVER 0x00000001UL
+
+/** Intel microcode loader version number */
+#define INTEL_UCODE_LVER 0x00000001UL
+
+/** Intel microcode default data length */
+#define INTEL_UCODE_DATA_LEN 2000
+
+/** Intel microcode file alignment */
+#define INTEL_UCODE_ALIGN 1024
+
+/** An Intel microcode update file extended header */
+struct intel_ucode_ext_header {
+ /** Extended signature count */
+ uint32_t count;
+ /** Extended checksum */
+ uint32_t checksum;
+ /** Reserved */
+ uint8_t reserved[12];
+} __attribute__ (( packed ));
+
+/** An Intel microcode extended signature */
+struct intel_ucode_ext {
+ /** CPUID signature */
+ uint32_t signature;
+ /** Supported platforms */
+ uint32_t platforms;
+ /** Checksum */
+ uint32_t checksum;
+} __attribute__ (( packed ));
+
+/** An AMD microcode update file header */
+struct amd_ucode_header {
+ /** Magic signature */
+ uint32_t magic;
+ /** Equivalence table type */
+ uint32_t type;
+ /** Equivalence table length */
+ uint32_t len;
+} __attribute__ (( packed ));
+
+/** AMD microcode magic signature */
+#define AMD_UCODE_MAGIC ( ( 'A' << 16 ) | ( 'M' << 8 ) | ( 'D' << 0 ) )
+
+/** AMD microcode equivalence table type */
+#define AMD_UCODE_EQUIV_TYPE 0x00000000UL
+
+/** An AMD microcode equivalence table entry */
+struct amd_ucode_equivalence {
+ /** CPU signature */
+ uint32_t signature;
+ /** Reserved */
+ uint8_t reserved_a[8];
+ /** Equivalence ID */
+ uint16_t id;
+ /** Reserved */
+ uint8_t reserved_b[2];
+} __attribute__ (( packed ));
+
+/** An AMD microcode patch header */
+struct amd_ucode_patch_header {
+ /** Patch type */
+ uint32_t type;
+ /** Patch length */
+ uint32_t len;
+} __attribute__ (( packed ));
+
+/** An AMD microcode patch */
+struct amd_ucode_patch {
+ /** Date */
+ struct ucode_date date;
+ /** Microcode version */
+ int32_t version;
+ /** Reserved */
+ uint8_t reserved_a[16];
+ /** Equivalence ID */
+ uint16_t id;
+ /** Reserved */
+ uint8_t reserved_b[14];
+} __attribute__ (( packed ));
+
+/** AMD patch type */
+#define AMD_UCODE_PATCH_TYPE 0x00000001UL
+
+extern mp_func_t ucode_update;
+
+#endif /* _IPXE_UCODE_H */
diff --git a/src/arch/x86/include/librm.h b/src/arch/x86/include/librm.h
index 40f07543..84b345d3 100644
--- a/src/arch/x86/include/librm.h
+++ b/src/arch/x86/include/librm.h
@@ -474,6 +474,26 @@ extern struct page_table io_pages;
*/
#define IO_BASE ( ( void * ) 0x100000000ULL )
+/** Startup IPI real-mode handler */
+extern char __text16_array ( sipi, [] );
+#define sipi __use_text16 ( sipi )
+
+/** Length of startup IPI real-mode handler */
+extern char sipi_len[];
+
+/** Startup IPI real-mode handler copy of real-mode data segment */
+extern uint16_t __text16 ( sipi_ds );
+#define sipi_ds __use_text16 ( sipi_ds )
+
+/** Startup IPI protected-mode handler (physical address) */
+extern uint32_t sipi_handler;
+
+/** Startup IPI register state */
+extern struct i386_regs sipi_regs;
+
+extern void setup_sipi ( unsigned int vector, uint32_t handler,
+ struct i386_regs *regs );
+
#endif /* ASSEMBLY */
#endif /* LIBRM_H */
diff --git a/src/arch/x86/interface/pcbios/bios_mp.c b/src/arch/x86/interface/pcbios/bios_mp.c
new file mode 100644
index 00000000..9e1179cc
--- /dev/null
+++ b/src/arch/x86/interface/pcbios/bios_mp.c
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2024 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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
+ *
+ * BIOS multiprocessor API implementation
+ *
+ */
+
+#include <registers.h>
+#include <ipxe/uaccess.h>
+#include <ipxe/timer.h>
+#include <ipxe/msr.h>
+#include <ipxe/mp.h>
+
+/** Local APIC base address MSR */
+#define MSR_APIC_BASE 0x0000001b
+
+/** Local APIC is in x2APIC mode */
+#define MSR_APIC_BASE_X2APIC 0x400
+
+/** Local APIC base address mask */
+#define MSR_APIC_BASE_MASK ( ~0xfffULL )
+
+/** Interrupt command register */
+#define APIC_ICR 0x0300
+
+/** Interrupt command register (x2APIC) */
+#define MSR_X2APIC_ICR 0x830
+
+/** Interrupt command register: send to all excluding self */
+#define APIC_ICR_ALL_NOT_SELF 0x000c0000
+
+/** Interrupt command register: level mode */
+#define APIC_ICR_LEVEL 0x00008000
+
+/** Interrupt command register: level asserted */
+#define APIC_ICR_LEVEL_ASSERT 0x00004000
+
+/** Interrupt command register: INIT */
+#define APIC_ICR_INIT 0x00000500
+
+/** Interrupt command register: SIPI */
+#define APIC_ICR_SIPI( vector ) ( 0x00000600 | (vector) )
+
+/** Time to wait for an IPI to complete */
+#define IPI_WAIT_MS 10
+
+/**
+ * Startup IPI vector
+ *
+ * The real-mode startup IPI code must be copied to a page boundary in
+ * base memory. We fairly arbitrarily choose to place this at 0x8000.
+ */
+#define SIPI_VECTOR 0x08
+
+/** Protected-mode startup IPI handler */
+extern void __asmcall mp_jump ( mp_addr_t func, mp_addr_t opaque );
+
+/**
+ * Execute a multiprocessor function on the boot processor
+ *
+ * @v func Multiprocessor function
+ * @v opaque Opaque data pointer
+ */
+static void bios_mp_exec_boot ( mp_func_t func, void *opaque ) {
+
+ /* Call multiprocessor function with physical addressing */
+ __asm__ __volatile__ ( PHYS_CODE ( "pushl %k2\n\t"
+ "pushl %k1\n\t"
+ "call *%k0\n\t"
+ "addl $8, %%esp\n\t" )
+ : : "r" ( mp_address ( mp_call ) ),
+ "r" ( mp_address ( func ) ),
+ "r" ( mp_address ( opaque ) ) );
+}
+
+/**
+ * Send an interprocessor interrupt
+ *
+ * @v apic APIC base address
+ * @v x2apic x2APIC mode enabled
+ * @v icr Interrupt control register value
+ */
+static void bios_mp_ipi ( void *apic, int x2apic, uint32_t icr ) {
+
+ /* Write ICR according to APIC/x2APIC mode */
+ DBGC ( MSR_APIC_BASE, "BIOSMP sending IPI %#08x\n", icr );
+ if ( x2apic ) {
+ wrmsr ( MSR_X2APIC_ICR, icr );
+ } else {
+ writel ( icr, ( apic + APIC_ICR ) );
+ }
+
+ /* Allow plenty of time for delivery to complete */
+ mdelay ( IPI_WAIT_MS );
+}
+
+/**
+ * Start a multiprocessor function on all application processors
+ *
+ * @v func Multiprocessor function
+ * @v opaque Opaque data pointer
+ */
+static void bios_mp_start_all ( mp_func_t func, void *opaque ) {
+ struct i386_regs regs;
+ uint64_t base;
+ uint32_t ipi;
+ void *apic;
+ int x2apic;
+
+ /* Prepare SIPI handler */
+ regs.eax = mp_address ( func );
+ regs.edx = mp_address ( opaque );
+ setup_sipi ( SIPI_VECTOR, virt_to_phys ( mp_jump ), &regs );
+
+ /* Get local APIC base address and mode */
+ base = rdmsr ( MSR_APIC_BASE );
+ x2apic = ( base & MSR_APIC_BASE_X2APIC );
+ DBGC ( MSR_APIC_BASE, "BIOSMP local %sAPIC base %#llx\n",
+ ( x2apic ? "x2" : "" ), ( ( unsigned long long ) base ) );
+
+ /* Map local APIC */
+ apic = ioremap ( ( base & MSR_APIC_BASE_MASK ), PAGE_SIZE );
+ if ( ! apic )
+ goto err_ioremap;
+
+ /* Assert INIT IPI */
+ ipi = ( APIC_ICR_ALL_NOT_SELF | APIC_ICR_LEVEL |
+ APIC_ICR_LEVEL_ASSERT | APIC_ICR_INIT );
+ bios_mp_ipi ( apic, x2apic, ipi );
+
+ /* Clear INIT IPI */
+ ipi &= ~APIC_ICR_LEVEL_ASSERT;
+ bios_mp_ipi ( apic, x2apic, ipi );
+
+ /* Send SIPI */
+ ipi = ( APIC_ICR_ALL_NOT_SELF | APIC_ICR_SIPI ( SIPI_VECTOR ) );
+ bios_mp_ipi ( apic, x2apic, ipi );
+
+ iounmap ( apic );
+ err_ioremap:
+ /* No way to handle errors: caller must check that
+ * multiprocessor function executed as expected.
+ */
+ return;
+}
+
+PROVIDE_MPAPI_INLINE ( pcbios, mp_address );
+PROVIDE_MPAPI ( pcbios, mp_exec_boot, bios_mp_exec_boot );
+PROVIDE_MPAPI ( pcbios, mp_start_all, bios_mp_start_all );
diff --git a/src/arch/x86/transitions/librm.S b/src/arch/x86/transitions/librm.S
index 39431324..a93b0251 100644
--- a/src/arch/x86/transitions/librm.S
+++ b/src/arch/x86/transitions/librm.S
@@ -1632,3 +1632,70 @@ init_pages:
/* Return */
ret
+
+/****************************************************************************
+ * sipi (real-mode jump)
+ *
+ * Handle Startup IPI
+ *
+ * This code must be copied to a page-aligned boundary in base memory.
+ * It will be entered with %cs:0000 pointing to the start of the code.
+ * The stack pointer is undefined and so no stack space can be used.
+ *
+ ****************************************************************************
+ */
+ .section ".text16.sipi", "ax", @progbits
+ .code16
+ .globl sipi
+sipi:
+ /* Retrieve rm_ds from copy */
+ movw %cs:( sipi_ds - sipi ), %ax
+ movw %ax, %ds
+
+ /* Load GDT and switch to protected mode */
+ data32 lgdt gdtr
+ movl %cr0, %eax
+ orb $CR0_PE, %al
+ movl %eax, %cr0
+ data32 ljmp $VIRTUAL_CS, $VIRTUAL(1f)
+
+ /* Copy of rm_ds required to access GDT */
+ .globl sipi_ds
+sipi_ds:
+ .word 0
+
+ /* Length of real-mode SIPI handler to be copied */
+ .globl sipi_len
+ .equ sipi_len, . - sipi
+
+ .section ".text.sipi", "ax", @progbits
+ .code32
+1: /* Set up protected-mode segment registers (with no stack) */
+ movw $VIRTUAL_DS, %ax
+ movw %ax, %ds
+ movw %ax, %ss
+ movw $PHYSICAL_DS, %ax
+ movw %ax, %es
+ movw %ax, %fs
+ movw %ax, %gs
+
+ /* Load register state and clear stack pointer */
+ movl $VIRTUAL(sipi_regs), %esp
+ popal
+
+ /* Switch to flat physical addressing */
+ movw $PHYSICAL_DS, %sp
+ movw %sp, %ds
+ movw %sp, %ss
+
+ /* Clear stack pointer */
+ xorl %esp, %esp
+
+ /* Jump to protected-mode SIPI handler */
+ ljmp %cs:*VIRTUAL(sipi_handler)
+
+ /* Protected-mode SIPI handler vector */
+ .section ".data.sipi_handler", "aw", @progbits
+ .globl sipi_handler
+sipi_handler:
+ .long 0, PHYSICAL_CS
diff --git a/src/arch/x86/transitions/librm_mgmt.c b/src/arch/x86/transitions/librm_mgmt.c
index da221e8b..b3820589 100644
--- a/src/arch/x86/transitions/librm_mgmt.c
+++ b/src/arch/x86/transitions/librm_mgmt.c
@@ -45,6 +45,9 @@ struct idtr64 idtr64 = {
.limit = ( sizeof ( idt64 ) - 1 ),
};
+/** Startup IPI register state */
+struct i386_regs sipi_regs;
+
/** Length of stack dump */
#define STACK_DUMP_LEN 128
@@ -402,6 +405,29 @@ __asmcall void check_fxsr ( struct i386_all_regs *regs ) {
( ( regs->flags & CF ) ? " not" : "" ) );
}
+/**
+ * Set up startup IPI handler
+ *
+ * @v vector Startup IPI vector
+ * @v handler Protected-mode startup IPI handler physical address
+ * @v regs Initial register state
+ */
+void setup_sipi ( unsigned int vector, uint32_t handler,
+ struct i386_regs *regs ) {
+
+ /* Record protected-mode handler */
+ sipi_handler = handler;
+
+ /* Update copy of rm_ds */
+ sipi_ds = rm_ds;
+
+ /* Save register state */
+ memcpy ( &sipi_regs, regs, sizeof ( sipi_regs ) );
+
+ /* Copy real-mode handler */
+ copy_to_real ( ( vector << 8 ), 0, sipi, ( ( size_t ) sipi_len ) );
+}
+
PROVIDE_UACCESS_INLINE ( librm, phys_to_user );
PROVIDE_UACCESS_INLINE ( librm, user_to_phys );
PROVIDE_UACCESS_INLINE ( librm, virt_to_user );
diff --git a/src/config/config.c b/src/config/config.c
index abb7d16a..209336c2 100644
--- a/src/config/config.c
+++ b/src/config/config.c
@@ -188,6 +188,9 @@ REQUIRE_OBJECT ( zlib );
#ifdef IMAGE_GZIP
REQUIRE_OBJECT ( gzip );
#endif
+#ifdef IMAGE_UCODE
+REQUIRE_OBJECT ( ucode );
+#endif
/*
* Drag in all requested commands
diff --git a/src/config/defaults/efi.h b/src/config/defaults/efi.h
index e39d475b..b62ddb46 100644
--- a/src/config/defaults/efi.h
+++ b/src/config/defaults/efi.h
@@ -25,6 +25,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#define REBOOT_EFI
#define ACPI_EFI
#define FDT_EFI
+#define MPAPI_EFI
#define NET_PROTO_IPV6 /* IPv6 protocol */
#define NET_PROTO_LLDP /* Link Layer Discovery protocol */
diff --git a/src/config/defaults/linux.h b/src/config/defaults/linux.h
index 21de2a2e..fae144b3 100644
--- a/src/config/defaults/linux.h
+++ b/src/config/defaults/linux.h
@@ -22,6 +22,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
#define PCIAPI_LINUX
#define DMAAPI_FLAT
#define ACPI_LINUX
+#define MPAPI_NULL
#define DRIVERS_LINUX
diff --git a/src/config/defaults/pcbios.h b/src/config/defaults/pcbios.h
index ee342d41..fa12a100 100644
--- a/src/config/defaults/pcbios.h
+++ b/src/config/defaults/pcbios.h
@@ -24,6 +24,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#define TIME_RTC
#define REBOOT_PCBIOS
#define ACPI_RSDP
+#define MPAPI_PCBIOS
#ifdef __x86_64__
#define IOMAP_PAGES
diff --git a/src/config/general.h b/src/config/general.h
index bcf7e69c..6525834e 100644
--- a/src/config/general.h
+++ b/src/config/general.h
@@ -127,6 +127,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#define IMAGE_PEM /* PEM image support */
//#define IMAGE_ZLIB /* ZLIB image support */
//#define IMAGE_GZIP /* GZIP image support */
+//#define IMAGE_UCODE /* Microcode update image support */
/*
* Command-line commands to include
diff --git a/src/core/cachedhcp.c b/src/core/cachedhcp.c
index 57226e16..04945e64 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/core/image.c b/src/core/image.c
index 3e65b5ed..bf0e4f75 100644
--- a/src/core/image.c
+++ b/src/core/image.c
@@ -134,10 +134,13 @@ int image_set_uri ( struct image *image, struct uri *uri ) {
int rc;
/* Set name, if image does not already have one */
- if ( uri->path && ( ! ( image->name && image->name[0] ) ) ) {
- name = basename ( ( char * ) uri->path );
- if ( ( rc = image_set_name ( image, name ) ) != 0 )
- return rc;
+ if ( ! ( image->name && image->name[0] ) ) {
+ name = ( uri->path ? uri->path : uri->opaque );
+ if ( name ) {
+ name = basename ( ( char * ) name );
+ if ( ( rc = image_set_name ( image, name ) ) != 0 )
+ return rc;
+ }
}
/* Update image URI */
diff --git a/src/core/main.c b/src/core/main.c
index 638dea9c..3db83649 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/core/mp.c b/src/core/mp.c
new file mode 100644
index 00000000..146d70a6
--- /dev/null
+++ b/src/core/mp.c
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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
+ *
+ * Multiprocessor functions
+ *
+ */
+
+#include <ipxe/timer.h>
+#include <ipxe/mp.h>
+
+/** Time to wait for application processors */
+#define MP_MAX_CPUID_WAIT_MS 10
+
+/**
+ * Get boot CPU identifier
+ *
+ * @ret id Boot CPU identifier
+ */
+unsigned int mp_boot_cpuid ( void ) {
+ unsigned int max = 0;
+
+ /* Update maximum to accommodate boot processor */
+ mp_exec_boot ( mp_update_max_cpuid, &max );
+ DBGC ( &mp_call, "MP boot processor ID is %#x\n", max );
+
+ return max;
+}
+
+/**
+ * Get maximum CPU identifier
+ *
+ * @ret max Maximum CPU identifier
+ */
+unsigned int mp_max_cpuid ( void ) {
+ unsigned int max = mp_boot_cpuid();
+
+ /* Update maximum to accommodate application processors */
+ mp_start_all ( mp_update_max_cpuid, &max );
+ mdelay ( MP_MAX_CPUID_WAIT_MS );
+ DBGC ( &mp_call, "MP observed maximum CPU ID is %#x\n", max );
+
+ return max;
+}
diff --git a/src/core/null_mp.c b/src/core/null_mp.c
new file mode 100644
index 00000000..0fa69303
--- /dev/null
+++ b/src/core/null_mp.c
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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
+ *
+ * Null multiprocessor API
+ *
+ */
+
+#include <ipxe/mp.h>
+
+PROVIDE_MPAPI_INLINE ( null, mp_address );
+PROVIDE_MPAPI_INLINE ( null, mp_exec_boot );
+PROVIDE_MPAPI_INLINE ( null, mp_start_all );
diff --git a/src/core/settings.c b/src/core/settings.c
index bece1afe..aa4bbae2 100644
--- a/src/core/settings.c
+++ b/src/core/settings.c
@@ -2698,6 +2698,113 @@ struct builtin_setting unixtime_builtin_setting __builtin_setting = {
};
/**
+ * Fetch current working URI-related setting
+ *
+ * @v data Buffer to fill with setting data
+ * @v len Length of buffer
+ * @v rel Relative URI string
+ * @ret len Length of setting data, or negative error
+ */
+static int cwuri_fetch_uri ( void *data, size_t len, const char *rel ) {
+ struct uri *reluri;
+ struct uri *uri;
+ char *uristring;
+ int ret;
+
+ /* Check that current working URI is set */
+ if ( ! cwuri ) {
+ ret = -ENOENT;
+ goto err_unset;
+ }
+
+ /* Construct relative URI */
+ reluri = parse_uri ( rel );
+ if ( ! reluri ) {
+ ret = -ENOMEM;
+ goto err_parse;
+ }
+
+ /* Construct resolved URI */
+ uri = resolve_uri ( cwuri, reluri );
+ if ( ! uri ) {
+ ret = -ENOMEM;
+ goto err_resolve;
+ }
+
+ /* Format URI string into allocated buffer (with NUL) */
+ uristring = format_uri_alloc ( uri );
+ if ( ! uristring ) {
+ ret = -ENOMEM;
+ goto err_format;
+ }
+
+ /* Copy URI string to buffer */
+ strncpy ( data, uristring, len );
+ ret = strlen ( uristring );
+
+ free ( uristring );
+ err_format:
+ uri_put ( uri );
+ err_resolve:
+ uri_put ( reluri );
+ err_parse:
+ err_unset:
+ return ret;
+}
+
+/**
+ * Fetch current working URI setting
+ *
+ * @v data Buffer to fill with setting data
+ * @v len Length of buffer
+ * @ret len Length of setting data, or negative error
+ */
+static int cwuri_fetch ( void *data, size_t len ) {
+
+ return cwuri_fetch_uri ( data, len, "" );
+}
+
+/**
+ * Fetch current working directory URI setting
+ *
+ * @v data Buffer to fill with setting data
+ * @v len Length of buffer
+ * @ret len Length of setting data, or negative error
+ */
+static int cwduri_fetch ( void *data, size_t len ) {
+
+ return cwuri_fetch_uri ( data, len, "." );
+}
+
+/** Current working URI setting */
+const struct setting cwuri_setting __setting ( SETTING_MISC, cwuri ) = {
+ .name = "cwuri",
+ .description = "Current working URI",
+ .type = &setting_type_string,
+ .scope = &builtin_scope,
+};
+
+/** Current working directory URI setting */
+const struct setting cwduri_setting __setting ( SETTING_MISC, cwduri ) = {
+ .name = "cwduri",
+ .description = "Current working directory URI",
+ .type = &setting_type_string,
+ .scope = &builtin_scope,
+};
+
+/** Current working URI built-in setting */
+struct builtin_setting cwuri_builtin_setting __builtin_setting = {
+ .setting = &cwuri_setting,
+ .fetch = cwuri_fetch,
+};
+
+/** Current working directory URI built-in setting */
+struct builtin_setting cwduri_builtin_setting __builtin_setting = {
+ .setting = &cwduri_setting,
+ .fetch = cwduri_fetch,
+};
+
+/**
* Fetch built-in setting
*
* @v settings Settings block
diff --git a/src/crypto/gcm.c b/src/crypto/gcm.c
index a32890d5..b93925d0 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/drivers/infiniband/golan.c b/src/drivers/infiniband/golan.c
index ce02a867..68a7c4f5 100755
--- a/src/drivers/infiniband/golan.c
+++ b/src/drivers/infiniband/golan.c
@@ -2502,7 +2502,7 @@ static mlx_status shomron_fill_eth_send_wqe ( struct ib_device *ibdev,
}
#define SHOMRON_GENERATE_CQE 0x3
-#define SHOMRON_INLINE_HEADERS_SIZE 18
+#define SHOMRON_INLINE_HEADERS_SIZE ETH_HLEN
#define SHOMRON_INLINE_HEADERS_OFFSET 32
MLX_FILL_2 ( &eth_wqe->ctrl, 0, opcode, FLEXBOOT_NODNIC_OPCODE_SEND,
wqe_index, wqe_index & 0xFFFF);
diff --git a/src/drivers/net/efi/mnp.c b/src/drivers/net/efi/mnp.c
new file mode 100644
index 00000000..33218fb1
--- /dev/null
+++ b/src/drivers/net/efi/mnp.c
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2024 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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 <errno.h>
+#include <ipxe/efi/efi.h>
+#include <ipxe/efi/efi_driver.h>
+#include <ipxe/efi/mnpnet.h>
+#include "snpnet.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 00000000..eb4b129c
--- /dev/null
+++ b/src/drivers/net/efi/mnpnet.c
@@ -0,0 +1,563 @@
+/*
+ * Copyright (C) 2024 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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 <string.h>
+#include <errno.h>
+#include <ipxe/iobuf.h>
+#include <ipxe/netdevice.h>
+#include <ipxe/ethernet.h>
+#include <ipxe/cachedhcp.h>
+#include <ipxe/efi/efi.h>
+#include <ipxe/efi/efi_driver.h>
+#include <ipxe/efi/efi_service.h>
+#include <ipxe/efi/efi_utils.h>
+#include <ipxe/efi/mnpnet.h>
+#include <ipxe/efi/Protocol/ManagedNetwork.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 );
+}
+
+/**
+ * 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;
+
+ /* Recycle any cached DHCP packet */
+ cachedhcp_recycle ( netdev );
+
+ /* Stop temporary network device */
+ mnpnet_stop ( efidev );
+
+ /* Free temporary EFI device */
+ efidev_free ( efidev );
+}
diff --git a/src/drivers/net/efi/nii.c b/src/drivers/net/efi/nii.c
index 8dd17e4b..16e9e10d 100644
--- a/src/drivers/net/efi/nii.c
+++ b/src/drivers/net/efi/nii.c
@@ -30,6 +30,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#include <errno.h>
#include <ipxe/netdevice.h>
#include <ipxe/ethernet.h>
+#include <ipxe/if_ether.h>
#include <ipxe/umalloc.h>
#include <ipxe/efi/efi.h>
#include <ipxe/efi/efi_driver.h>
@@ -998,6 +999,12 @@ static int nii_transmit ( struct net_device *netdev,
return 0;
}
+ /* Pad to minimum Ethernet length, to work around underlying
+ * drivers that do not correctly handle frame padding
+ * themselves.
+ */
+ iob_pad ( iobuf, ETH_ZLEN );
+
/* Construct parameter block */
memset ( &cpb, 0, sizeof ( cpb ) );
cpb.FrameAddr = ( ( intptr_t ) iobuf->data );
diff --git a/src/drivers/net/efi/snp.c b/src/drivers/net/efi/snp.c
index 1920cdbc..cac8b38e 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 <errno.h>
#include <ipxe/efi/efi.h>
#include <ipxe/efi/efi_driver.h>
-#include <ipxe/efi/efi_snp.h>
-#include <ipxe/efi/efi_utils.h>
#include "snpnet.h"
#include "nii.h"
@@ -41,58 +38,11 @@ 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
- *
- * @v device EFI device handle
* @ret rc Return status code
*/
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 3b09d491..6ce731d7 100644
--- a/src/drivers/net/efi/snpnet.c
+++ b/src/drivers/net/efi/snpnet.c
@@ -26,12 +26,14 @@ FILE_LICENCE ( GPL2_OR_LATER );
#include <ipxe/iobuf.h>
#include <ipxe/netdevice.h>
#include <ipxe/ethernet.h>
+#include <ipxe/if_ether.h>
#include <ipxe/vsprintf.h>
#include <ipxe/timer.h>
#include <ipxe/efi/efi.h>
#include <ipxe/efi/Protocol/SimpleNetwork.h>
#include <ipxe/efi/efi_driver.h>
#include <ipxe/efi/efi_utils.h>
+#include <ipxe/efi/efi_snp.h>
#include "snpnet.h"
/** @file
@@ -71,6 +73,19 @@ struct snp_nic {
/** Delay between each initialisation retry */
#define SNP_INITIALIZE_RETRY_DELAY_MS 10
+/** Additional padding for receive buffers
+ *
+ * Some SNP implementations seem to require additional space in the
+ * allocated receive buffers, otherwise full-length packets will be
+ * silently dropped.
+ *
+ * The EDK2 MnpDxe driver happens to allocate an additional 8 bytes of
+ * padding (4 for a VLAN tag, 4 for the Ethernet frame checksum).
+ * Match this behaviour since drivers are very likely to have been
+ * tested against MnpDxe.
+ */
+#define SNP_RX_PAD 8
+
/**
* Format SNP MAC address (for debugging)
*
@@ -174,6 +189,12 @@ static int snpnet_transmit ( struct net_device *netdev,
return 0;
}
+ /* Pad to minimum Ethernet length, to work around underlying
+ * drivers that do not correctly handle frame padding
+ * themselves.
+ */
+ iob_pad ( iobuf, ETH_ZLEN );
+
/* Transmit packet */
if ( ( efirc = snp->snp->Transmit ( snp->snp, 0, iob_len ( iobuf ),
iobuf->data, NULL, NULL,
@@ -246,7 +267,7 @@ static void snpnet_poll_rx ( struct net_device *netdev ) {
/* Allocate buffer, if required */
if ( ! snp->rxbuf ) {
- snp->rxbuf = alloc_iob ( snp->mtu );
+ snp->rxbuf = alloc_iob ( snp->mtu + SNP_RX_PAD );
if ( ! snp->rxbuf ) {
/* Leave for next poll */
break;
@@ -465,6 +486,53 @@ static struct net_device_operations snpnet_operations = {
};
/**
+ * 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
*
* @v efidev EFI device
diff --git a/src/drivers/net/efi/snpnet.h b/src/drivers/net/efi/snpnet.h
index e6d31d5e..4699c789 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 674e0a05..2ae63fc0 100644
--- a/src/drivers/net/efi/snponly.c
+++ b/src/drivers/net/efi/snponly.c
@@ -29,6 +29,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#include <ipxe/efi/efi.h>
#include <ipxe/efi/efi_driver.h>
#include <ipxe/efi/efi_utils.h>
+#include <ipxe/efi/mnpnet.h>
#include <ipxe/efi/Protocol/SimpleNetwork.h>
#include <ipxe/efi/Protocol/NetworkInterfaceIdentifier.h>
#include "snpnet.h"
@@ -45,14 +46,23 @@ struct chained_protocol {
/** Protocol GUID */
EFI_GUID *protocol;
/**
- * Protocol instance installed on the loaded image's device handle
+ * Target device handle
+ *
+ * This is the uppermost handle on which the same protocol
+ * instance is installed as we find on the loaded image's
+ * device handle.
*
* We match against the protocol instance (rather than simply
* matching against the device handle itself) because some
* systems load us via a child of the underlying device, with
* a duplicate protocol installed on the child handle.
+ *
+ * We record the handle rather than the protocol instance
+ * pointer since the calls to DisconnectController() and
+ * ConnectController() may end up uninstalling and
+ * reinstalling the protocol instance.
*/
- void *interface;
+ EFI_HANDLE device;
};
/** Chainloaded SNP protocol */
@@ -65,50 +75,74 @@ 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 instance
+ * Locate chainloaded protocol
*
* @v chained Chainloaded protocol
- * @ret rc Return status code
*/
-static int chained_locate ( struct chained_protocol *chained ) {
+static void chained_locate ( struct chained_protocol *chained ) {
EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
EFI_HANDLE device = efi_loaded_image->DeviceHandle;
- EFI_HANDLE parent;
+ EFI_HANDLE handle;
+ void *match = NULL;
+ void *interface;
+ unsigned int skip;
EFI_STATUS efirc;
int rc;
- /* Locate handle supporting this protocol */
- if ( ( rc = efi_locate_device ( device, chained->protocol,
- &parent, 0 ) ) != 0 ) {
- DBGC ( device, "CHAINED %s does not support %s: %s\n",
- efi_handle_name ( device ),
- efi_guid_ntoa ( chained->protocol ), strerror ( rc ) );
- goto err_locate_device;
- }
- DBGC ( device, "CHAINED %s found %s on ", efi_handle_name ( device ),
- efi_guid_ntoa ( chained->protocol ) );
- DBGC ( device, "%s\n", efi_handle_name ( parent ) );
+ /* Identify target device handle */
+ for ( skip = 0 ; ; skip++ ) {
- /* Get protocol instance */
- if ( ( efirc = bs->OpenProtocol ( parent, chained->protocol,
- &chained->interface, efi_image_handle,
- device,
- EFI_OPEN_PROTOCOL_GET_PROTOCOL ))!=0){
- rc = -EEFI ( efirc );
- DBGC ( device, "CHAINED %s could not open %s on ",
+ /* Locate handle supporting this protocol */
+ if ( ( rc = efi_locate_device ( device, chained->protocol,
+ &handle, skip ) ) != 0 ) {
+ if ( skip == 0 ) {
+ DBGC ( device, "CHAINED %s does not support "
+ "%s: %s\n", efi_handle_name ( device ),
+ efi_guid_ntoa ( chained->protocol ),
+ strerror ( rc ) );
+ }
+ break;
+ }
+
+ /* Get protocol instance */
+ if ( ( efirc = bs->OpenProtocol (
+ handle, chained->protocol, &interface,
+ efi_image_handle, handle,
+ EFI_OPEN_PROTOCOL_GET_PROTOCOL )) != 0){
+ rc = -EEFI ( efirc );
+ DBGC ( device, "CHAINED %s could not open %s on ",
+ efi_handle_name ( device ),
+ efi_guid_ntoa ( chained->protocol ) );
+ DBGC ( device, "%s: %s\n",
+ efi_handle_name ( handle ), strerror ( rc ) );
+ break;
+ }
+ bs->CloseProtocol ( handle, chained->protocol,
+ efi_image_handle, handle );
+
+ /* Stop if we reach a non-matching protocol instance */
+ if ( match && ( match != interface ) ) {
+ DBGC ( device, "CHAINED %s found non-matching %s on ",
+ efi_handle_name ( device ),
+ efi_guid_ntoa ( chained->protocol ) );
+ DBGC ( device, "%s\n", efi_handle_name ( handle ) );
+ break;
+ }
+
+ /* Record this handle */
+ chained->device = handle;
+ match = interface;
+ DBGC ( device, "CHAINED %s found %s on ",
efi_handle_name ( device ),
efi_guid_ntoa ( chained->protocol ) );
- DBGC ( device, "%s: %s\n",
- efi_handle_name ( parent ), strerror ( rc ) );
- goto err_open_protocol;
+ DBGC ( device, "%s\n", efi_handle_name ( chained->device ) );
}
-
- err_locate_device:
- bs->CloseProtocol ( parent, chained->protocol, efi_image_handle,
- device );
- err_open_protocol:
- return rc;
}
/**
@@ -121,8 +155,8 @@ static int chained_locate ( struct chained_protocol *chained ) {
static int chained_supported ( EFI_HANDLE device,
struct chained_protocol *chained ) {
EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
- EFI_STATUS efirc;
void *interface;
+ EFI_STATUS efirc;
int rc;
/* Get protocol */
@@ -136,19 +170,19 @@ static int chained_supported ( EFI_HANDLE device,
goto err_open_protocol;
}
- /* Test for a match against the chainloading device */
- if ( interface != chained->interface ) {
- DBGC ( device, "CHAINED %s %p is not the chainloaded %s\n",
- efi_handle_name ( device ), interface,
- efi_guid_ntoa ( chained->protocol ) );
+ /* Ignore non-matching handles */
+ if ( device != chained->device ) {
+ DBGC2 ( device, "CHAINED %s is not the chainloaded %s\n",
+ efi_handle_name ( device ),
+ efi_guid_ntoa ( chained->protocol ) );
rc = -ENOTTY;
goto err_no_match;
}
/* Success */
rc = 0;
- DBGC ( device, "CHAINED %s %p is the chainloaded %s\n",
- efi_handle_name ( device ), interface,
+ DBGC ( device, "CHAINED %s is the chainloaded %s\n",
+ efi_handle_name ( device ),
efi_guid_ntoa ( chained->protocol ) );
err_no_match:
@@ -180,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",
@@ -196,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
*
@@ -204,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/cachedhcp.h b/src/include/ipxe/cachedhcp.h
index 4ce4a9f2..8ebee3b7 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 <stddef.h>
#include <ipxe/uaccess.h>
+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 */
diff --git a/src/include/ipxe/efi/Protocol/MpService.h b/src/include/ipxe/efi/Protocol/MpService.h
new file mode 100644
index 00000000..cd1bb27f
--- /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.<BR>
+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.
+ ///
+ /// <pre>
+ /// 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.
+ /// </pre>
+ ///
+ 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/Protocol/ServiceBinding.h b/src/include/ipxe/efi/Protocol/ServiceBinding.h
new file mode 100644
index 00000000..6baf73aa
--- /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.<BR>
+ 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_autoexec.h b/src/include/ipxe/efi/efi_autoexec.h
index 08ddf583..18bc4200 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 <ipxe/efi/efi.h>
-
-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/include/ipxe/efi/efi_driver.h b/src/include/ipxe/efi/efi_driver.h
index 74ece90d..7b64e1e0 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 */
@@ -84,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/include/ipxe/efi/efi_mp.h b/src/include/ipxe/efi/efi_mp.h
new file mode 100644
index 00000000..8dc4243e
--- /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/efi/efi_path.h b/src/include/ipxe/efi/efi_path.h
index 20ff43f6..57fce402 100644
--- a/src/include/ipxe/efi/efi_path.h
+++ b/src/include/ipxe/efi/efi_path.h
@@ -43,8 +43,10 @@ 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 );
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/include/ipxe/efi/efi_service.h b/src/include/ipxe/efi/efi_service.h
new file mode 100644
index 00000000..ca4c7b2a
--- /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 <ipxe/efi/efi.h>
+
+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/efi/mnpnet.h b/src/include/ipxe/efi/mnpnet.h
new file mode 100644
index 00000000..99d6cf08
--- /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 */
diff --git a/src/include/ipxe/errfile.h b/src/include/ipxe/errfile.h
index 21c3d338..afc260ba 100644
--- a/src/include/ipxe/errfile.h
+++ b/src/include/ipxe/errfile.h
@@ -81,6 +81,8 @@ 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_efi_service ( ERRFILE_CORE | 0x002d0000 )
#define ERRFILE_eisa ( ERRFILE_DRIVER | 0x00000000 )
#define ERRFILE_isa ( ERRFILE_DRIVER | 0x00010000 )
@@ -221,7 +223,8 @@ 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_aqc1xx ( ERRFILE_DRIVER | 0x00d50000 )
+#define ERRFILE_mnpnet ( ERRFILE_DRIVER | 0x00d50000 )
+#define ERRFILE_aqc1xx ( ERRFILE_DRIVER | 0x00df0000 )
#define ERRFILE_atl_hw ( ERRFILE_DRIVER | 0x00d60000 )
#define ERRFILE_atl2_hw ( ERRFILE_DRIVER | 0x00d70000 )
diff --git a/src/include/ipxe/gcm.h b/src/include/ipxe/gcm.h
index 4864445d..2c785a97 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, \
diff --git a/src/include/ipxe/mp.h b/src/include/ipxe/mp.h
new file mode 100644
index 00000000..9670dea5
--- /dev/null
+++ b/src/include/ipxe/mp.h
@@ -0,0 +1,155 @@
+#ifndef _IPXE_MP_H
+#define _IPXE_MP_H
+
+/** @file
+ *
+ * Multiprocessor functions
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+#include <stdint.h>
+#include <ipxe/api.h>
+#include <config/defaults.h>
+
+/**
+ * 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 <ipxe/null_mp.h>
+#include <ipxe/efi/efi_mp.h>
+
+/* Include all architecture-dependent multiprocessor API headers */
+#include <bits/mp.h>
+
+/**
+ * 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 00000000..f89da5d9
--- /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 */
diff --git a/src/include/ipxe/settings.h b/src/include/ipxe/settings.h
index 9759a600..ccb5e99f 100644
--- a/src/include/ipxe/settings.h
+++ b/src/include/ipxe/settings.h
@@ -446,6 +446,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 );
@@ -470,6 +472,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/interface/efi/efi_autoexec.c b/src/interface/efi/efi_autoexec.c
index fb12cef0..d9ad3b99 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 <string.h>
-#include <stdlib.h>
#include <errno.h>
+#include <ipxe/timer.h>
#include <ipxe/image.h>
-#include <ipxe/init.h>
-#include <ipxe/in.h>
+#include <ipxe/netdevice.h>
#include <ipxe/efi/efi.h>
+#include <ipxe/efi/efi_utils.h>
#include <ipxe/efi/efi_autoexec.h>
-#include <ipxe/efi/Protocol/PxeBaseCode.h>
-#include <ipxe/efi/Protocol/SimpleFileSystem.h>
-#include <ipxe/efi/Guid/FileInfo.h>
+#include <ipxe/efi/mnpnet.h>
+#include <usr/imgmgmt.h>
+#include <usr/sync.h>
/** @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/efi_driver.c b/src/interface/efi/efi_driver.c
index 8e537d53..fd9be5f5 100644
--- a/src/interface/efi/efi_driver.c
+++ b/src/interface/efi/efi_driver.c
@@ -62,18 +62,85 @@ static LIST_HEAD ( efi_devices );
static int efi_driver_disconnecting;
/**
- * Find EFI device
+ * 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
+ *
+ * @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;
@@ -153,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;
@@ -191,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 ) {
@@ -245,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:
@@ -300,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;
diff --git a/src/interface/efi/efi_local.c b/src/interface/efi/efi_local.c
index d3ac3d54..b2881424 100644
--- a/src/interface/efi/efi_local.c
+++ b/src/interface/efi/efi_local.c
@@ -35,6 +35,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#include <ipxe/uri.h>
#include <ipxe/iobuf.h>
#include <ipxe/process.h>
+#include <ipxe/errortab.h>
#include <ipxe/efi/efi.h>
#include <ipxe/efi/efi_strings.h>
#include <ipxe/efi/efi_path.h>
@@ -48,6 +49,17 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
*
*/
+/* Disambiguate the various error causes */
+#define EINFO_EEFI_OPEN \
+ __einfo_uniqify ( EINFO_EPLATFORM, 0x01, "Could not open" )
+#define EINFO_EEFI_OPEN_NOT_FOUND \
+ __einfo_platformify ( EINFO_EEFI_OPEN, EFI_NOT_FOUND, \
+ "Not found" )
+#define EEFI_OPEN_NOT_FOUND \
+ __einfo_error ( EINFO_EEFI_OPEN_NOT_FOUND )
+#define EEFI_OPEN( efirc ) EPLATFORM ( EINFO_EEFI_OPEN, efirc, \
+ EEFI_OPEN_NOT_FOUND )
+
/** Download blocksize */
#define EFI_LOCAL_BLKSIZE 4096
@@ -60,6 +72,13 @@ struct efi_local {
/** Download process */
struct process process;
+ /** Download URI */
+ struct uri *uri;
+ /** Volume name, or NULL to use loaded image's device */
+ const char *volume;
+ /** File path */
+ const char *path;
+
/** EFI root directory */
EFI_FILE_PROTOCOL *root;
/** EFI file */
@@ -68,6 +87,24 @@ struct efi_local {
size_t len;
};
+/** Human-readable error messages */
+struct errortab efi_local_errors[] __errortab = {
+ __einfo_errortab ( EINFO_EEFI_OPEN_NOT_FOUND ),
+};
+
+/**
+ * Free local file
+ *
+ * @v refcnt Reference count
+ */
+static void efi_local_free ( struct refcnt *refcnt ) {
+ struct efi_local *local =
+ container_of ( refcnt, struct efi_local, refcnt );
+
+ uri_put ( local->uri );
+ free ( local );
+}
+
/**
* Close local file
*
@@ -96,91 +133,6 @@ static void efi_local_close ( struct efi_local *local, int rc ) {
}
/**
- * Local file process
- *
- * @v local Local file
- */
-static void efi_local_step ( struct efi_local *local ) {
- EFI_FILE_PROTOCOL *file = local->file;
- struct io_buffer *iobuf = NULL;
- size_t remaining;
- size_t frag_len;
- UINTN size;
- EFI_STATUS efirc;
- int rc;
-
- /* Wait until data transfer interface is ready */
- if ( ! xfer_window ( &local->xfer ) )
- return;
-
- /* Presize receive buffer */
- remaining = local->len;
- xfer_seek ( &local->xfer, remaining );
- xfer_seek ( &local->xfer, 0 );
-
- /* Get file contents */
- while ( remaining ) {
-
- /* Calculate length for this fragment */
- frag_len = remaining;
- if ( frag_len > EFI_LOCAL_BLKSIZE )
- frag_len = EFI_LOCAL_BLKSIZE;
-
- /* Allocate I/O buffer */
- iobuf = xfer_alloc_iob ( &local->xfer, frag_len );
- if ( ! iobuf ) {
- rc = -ENOMEM;
- goto err;
- }
-
- /* Read block */
- size = frag_len;
- if ( ( efirc = file->Read ( file, &size, iobuf->data ) ) != 0 ){
- rc = -EEFI ( efirc );
- DBGC ( local, "LOCAL %p could not read from file: %s\n",
- local, strerror ( rc ) );
- goto err;
- }
- assert ( size <= frag_len );
- iob_put ( iobuf, size );
-
- /* Deliver data */
- if ( ( rc = xfer_deliver_iob ( &local->xfer,
- iob_disown ( iobuf ) ) ) != 0 ) {
- DBGC ( local, "LOCAL %p could not deliver data: %s\n",
- local, strerror ( rc ) );
- goto err;
- }
-
- /* Move to next block */
- remaining -= frag_len;
- }
-
- /* Close download */
- efi_local_close ( local, 0 );
-
- return;
-
- err:
- free_iob ( iobuf );
- efi_local_close ( local, rc );
-}
-
-/** Data transfer interface operations */
-static struct interface_operation efi_local_operations[] = {
- INTF_OP ( xfer_window_changed, struct efi_local *, efi_local_step ),
- INTF_OP ( intf_close, struct efi_local *, efi_local_close ),
-};
-
-/** Data transfer interface descriptor */
-static struct interface_descriptor efi_local_xfer_desc =
- INTF_DESC ( struct efi_local, xfer, efi_local_operations );
-
-/** Process descriptor */
-static struct process_descriptor efi_local_process_desc =
- PROC_DESC_ONCE ( struct efi_local, process, efi_local_step );
-
-/**
* Check for matching volume name
*
* @v local Local file
@@ -354,15 +306,14 @@ static int efi_local_open_volume_index ( struct efi_local *local,
* Open root filesystem of specified volume
*
* @v local Local file
- * @v volume Volume name, or NULL to use loaded image's device
* @ret rc Return status code
*/
-static int efi_local_open_volume ( struct efi_local *local,
- const char *volume ) {
+static int efi_local_open_volume ( struct efi_local *local ) {
EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
EFI_GUID *protocol = &efi_simple_file_system_protocol_guid;
int ( * check ) ( struct efi_local *local, EFI_HANDLE device,
EFI_FILE_PROTOCOL *root, const char *volume );
+ const char *volume = local->volume;
EFI_DEVICE_PATH_PROTOCOL *path;
EFI_FILE_PROTOCOL *root;
EFI_HANDLE *handles;
@@ -461,7 +412,7 @@ static int efi_local_open_resolved ( struct efi_local *local,
/* Open file */
if ( ( efirc = local->root->Open ( local->root, &file, name,
EFI_FILE_MODE_READ, 0 ) ) != 0 ) {
- rc = -EEFI ( efirc );
+ rc = -EEFI_OPEN ( efirc );
DBGC ( local, "LOCAL %p could not open \"%s\": %s\n",
local, resolved, strerror ( rc ) );
return rc;
@@ -475,11 +426,9 @@ static int efi_local_open_resolved ( struct efi_local *local,
* Open specified path
*
* @v local Local file
- * @v filename Path to file relative to our own image
* @ret rc Return status code
*/
-static int efi_local_open_path ( struct efi_local *local,
- const char *filename ) {
+static int efi_local_open_path ( struct efi_local *local ) {
EFI_DEVICE_PATH_PROTOCOL *path = efi_loaded_image->FilePath;
EFI_DEVICE_PATH_PROTOCOL *next;
FILEPATH_DEVICE_PATH *fp;
@@ -510,7 +459,7 @@ static int efi_local_open_path ( struct efi_local *local,
}
/* Resolve path */
- resolved = resolve_path ( base, filename );
+ resolved = resolve_path ( base, local->path );
if ( ! resolved ) {
rc = -ENOMEM;
goto err_resolve;
@@ -580,6 +529,106 @@ static int efi_local_len ( struct efi_local *local ) {
}
/**
+ * Local file process
+ *
+ * @v local Local file
+ */
+static void efi_local_step ( struct efi_local *local ) {
+ struct io_buffer *iobuf = NULL;
+ size_t remaining;
+ size_t frag_len;
+ UINTN size;
+ EFI_STATUS efirc;
+ int rc;
+
+ /* Wait until data transfer interface is ready */
+ if ( ! xfer_window ( &local->xfer ) )
+ return;
+
+ /* Open specified volume root directory, if not yet open */
+ if ( ( ! local->root ) &&
+ ( ( rc = efi_local_open_volume ( local ) ) != 0 ) )
+ goto err;
+
+ /* Open specified file, if not yet open */
+ if ( ( ! local->file ) &&
+ ( ( rc = efi_local_open_path ( local ) ) != 0 ) )
+ goto err;
+
+ /* Get file length, if not yet known */
+ if ( ( ! local->len ) &&
+ ( ( rc = efi_local_len ( local ) ) != 0 ) )
+ goto err;
+
+ /* Presize receive buffer */
+ remaining = local->len;
+ xfer_seek ( &local->xfer, remaining );
+ xfer_seek ( &local->xfer, 0 );
+
+ /* Get file contents */
+ while ( remaining ) {
+
+ /* Calculate length for this fragment */
+ frag_len = remaining;
+ if ( frag_len > EFI_LOCAL_BLKSIZE )
+ frag_len = EFI_LOCAL_BLKSIZE;
+
+ /* Allocate I/O buffer */
+ iobuf = xfer_alloc_iob ( &local->xfer, frag_len );
+ if ( ! iobuf ) {
+ rc = -ENOMEM;
+ goto err;
+ }
+
+ /* Read block */
+ size = frag_len;
+ if ( ( efirc = local->file->Read ( local->file, &size,
+ iobuf->data ) ) != 0 ) {
+ rc = -EEFI ( efirc );
+ DBGC ( local, "LOCAL %p could not read from file: %s\n",
+ local, strerror ( rc ) );
+ goto err;
+ }
+ assert ( size <= frag_len );
+ iob_put ( iobuf, size );
+
+ /* Deliver data */
+ if ( ( rc = xfer_deliver_iob ( &local->xfer,
+ iob_disown ( iobuf ) ) ) != 0 ) {
+ DBGC ( local, "LOCAL %p could not deliver data: %s\n",
+ local, strerror ( rc ) );
+ goto err;
+ }
+
+ /* Move to next block */
+ remaining -= frag_len;
+ }
+
+ /* Close download */
+ efi_local_close ( local, 0 );
+
+ return;
+
+ err:
+ free_iob ( iobuf );
+ efi_local_close ( local, rc );
+}
+
+/** Data transfer interface operations */
+static struct interface_operation efi_local_operations[] = {
+ INTF_OP ( xfer_window_changed, struct efi_local *, efi_local_step ),
+ INTF_OP ( intf_close, struct efi_local *, efi_local_close ),
+};
+
+/** Data transfer interface descriptor */
+static struct interface_descriptor efi_local_xfer_desc =
+ INTF_DESC ( struct efi_local, xfer, efi_local_operations );
+
+/** Process descriptor */
+static struct process_descriptor efi_local_process_desc =
+ PROC_DESC_ONCE ( struct efi_local, process, efi_local_step );
+
+/**
* Open local file
*
* @v xfer Data transfer interface
@@ -588,33 +637,26 @@ static int efi_local_len ( struct efi_local *local ) {
*/
static int efi_local_open ( struct interface *xfer, struct uri *uri ) {
struct efi_local *local;
- const char *volume;
- const char *path;
- int rc, vol;
-
- /* Parse URI */
- volume = ( ( uri->host && uri->host[0] ) ? uri->host : NULL );
- path = ( uri->opaque ? uri->opaque : uri->path );
+ int vol;
+ int rc = 0;
/* Allocate and initialise structure */
local = zalloc ( sizeof ( *local ) );
- if ( ! local ) {
- rc = -ENOMEM;
- goto err_alloc;
- }
- ref_init ( &local->refcnt, NULL );
- intf_init ( &local->xfer, &efi_local_xfer_desc, &local->refcnt );
- process_init_stopped ( &local->process, &efi_local_process_desc,
- &local->refcnt );
-
- if ( volume && strcmp ( volume, "*" ) == 0 ) {
+ if ( ! local )
+ return -ENOMEM;
+ ref_init ( &local->refcnt, efi_local_free );
+ local->uri = uri_get ( uri );
+ local->volume = ( ( uri->host && uri->host[0] ) ? uri->host : NULL );
+ local->path = ( uri->opaque ? uri->opaque : uri->path );
+
+ if ( local->path && local->volume && strcmp ( local->volume, "*" ) == 0 ) {
/* Open on any volume */
vol = 0;
while ( ( rc = efi_local_open_volume_index ( local, vol++ ) ) != -ENOENT ) {
if ( rc != 0 )
continue;
/* Open specified path */
- if ( ( rc = efi_local_open_path ( local, path ) ) != 0 ) {
+ if ( ( rc = efi_local_open_path ( local ) ) != 0 ) {
local->root->Close ( local->root );
local->root = NULL;
continue;
@@ -622,21 +664,17 @@ static int efi_local_open ( struct interface *xfer, struct uri *uri ) {
/* Success */
break;
}
- if ( rc != 0 )
- goto err_open_root;
- } else {
- /* Open specified volume */
- if ( ( rc = efi_local_open_volume ( local, volume ) ) != 0 )
- goto err_open_root;
-
- /* Open specified path */
- if ( ( rc = efi_local_open_path ( local, path ) ) != 0 )
- goto err_open_file;
+ if ( rc != 0 ) {
+ DBGC ( local, "LOCAL %p could not find %s on any partition\n",
+ local, local->path );
+ ref_put ( &local->refcnt );
+ return -ENOENT;
+ }
}
- /* Get length of file */
- if ( ( rc = efi_local_len ( local ) ) != 0 )
- goto err_len;
+ intf_init ( &local->xfer, &efi_local_xfer_desc, &local->refcnt );
+ process_init_stopped ( &local->process, &efi_local_process_desc,
+ &local->refcnt );
/* Start download process */
process_add ( &local->process );
@@ -645,14 +683,6 @@ static int efi_local_open ( struct interface *xfer, struct uri *uri ) {
intf_plug_plug ( &local->xfer, xfer );
ref_put ( &local->refcnt );
return 0;
-
- err_len:
- err_open_file:
- err_open_root:
- efi_local_close ( local, 0 );
- ref_put ( &local->refcnt );
- err_alloc:
- return rc;
}
/** EFI local file URI opener */
diff --git a/src/interface/efi/efi_mp.c b/src/interface/efi/efi_mp.c
new file mode 100644
index 00000000..fdbbc9ae
--- /dev/null
+++ b/src/interface/efi/efi_mp.c
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2024 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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 <string.h>
+#include <errno.h>
+#include <ipxe/mp.h>
+#include <ipxe/efi/efi.h>
+#include <ipxe/efi/Protocol/MpService.h>
+
+/** 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 );
diff --git a/src/interface/efi/efi_path.c b/src/interface/efi/efi_path.c
index d1e22eea..ac3c0498 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 <ipxe/fcp.h>
#include <ipxe/ib_srp.h>
#include <ipxe/usb.h>
+#include <ipxe/settings.h>
+#include <ipxe/dhcp.h>
#include <ipxe/efi/efi.h>
#include <ipxe/efi/efi_driver.h>
#include <ipxe/efi/efi_path.h>
@@ -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
*
@@ -112,6 +148,30 @@ size_t efi_path_len ( EFI_DEVICE_PATH_PROTOCOL *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
*
* @v path Device path
@@ -176,6 +236,46 @@ int efi_path_guid ( EFI_DEVICE_PATH_PROTOCOL *path, union uuid *guid ) {
}
/**
+ * 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
*
* @v ... List of device paths (NULL terminated)
@@ -593,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,
+};
diff --git a/src/interface/efi/efi_service.c b/src/interface/efi/efi_service.c
new file mode 100644
index 00000000..d4129c0d
--- /dev/null
+++ b/src/interface/efi/efi_service.c
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2024 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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 <string.h>
+#include <errno.h>
+#include <ipxe/efi/efi.h>
+#include <ipxe/efi/efi_service.h>
+#include <ipxe/efi/Protocol/ServiceBinding.h>
+
+/**
+ * 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;
+}
diff --git a/src/interface/efi/efiprefix.c b/src/interface/efi/efiprefix.c
index 26116068..10d8f0bf 100644
--- a/src/interface/efi/efiprefix.c
+++ b/src/interface/efi/efiprefix.c
@@ -22,6 +22,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
#include <stdlib.h>
#include <errno.h>
#include <ipxe/device.h>
+#include <ipxe/uri.h>
#include <ipxe/init.h>
#include <ipxe/efi/efi.h>
#include <ipxe/efi/efi_driver.h>
@@ -30,6 +31,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
#include <ipxe/efi/efi_autoexec.h>
#include <ipxe/efi/efi_cachedhcp.h>
#include <ipxe/efi/efi_watchdog.h>
+#include <ipxe/efi/efi_path.h>
#include <ipxe/efi/efi_veto.h>
/**
@@ -79,16 +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 );
}
/** EFI application initialisation function */
@@ -103,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();
diff --git a/src/net/netdev_settings.c b/src/net/netdev_settings.c
index fb98663c..080b6d2a 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",
@@ -220,6 +225,22 @@ static int netdev_fetch_busid ( struct net_device *netdev, void *data,
}
/**
+ * 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
*
* @v netdev Network device
@@ -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 },
};
diff --git a/src/net/tcp/httpcore.c b/src/net/tcp/httpcore.c
index 9ad39656..af2a237c 100644
--- a/src/net/tcp/httpcore.c
+++ b/src/net/tcp/httpcore.c
@@ -89,7 +89,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
__einfo_uniqify ( EINFO_EIO, 0x05, "HTTP 5xx Server Error" )
#define ENOENT_404 __einfo_error ( EINFO_ENOENT_404 )
#define EINFO_ENOENT_404 \
- __einfo_uniqify ( EINFO_ENOENT, 0x01, "HTTP 404 Not Found" )
+ __einfo_uniqify ( EINFO_ENOENT, 0x01, "Not found" )
#define ENOTSUP_CONNECTION __einfo_error ( EINFO_ENOTSUP_CONNECTION )
#define EINFO_ENOTSUP_CONNECTION \
__einfo_uniqify ( EINFO_ENOTSUP, 0x01, "Unsupported connection header" )
@@ -114,6 +114,7 @@ static struct profiler http_xfer_profiler __profiler = { .name = "http.xfer" };
/** Human-readable error messages */
struct errortab http_errors[] __errortab = {
+ __einfo_errortab ( EINFO_ENOENT_404 ),
__einfo_errortab ( EINFO_EIO_4XX ),
__einfo_errortab ( EINFO_EIO_5XX ),
};
diff --git a/src/net/udp/tftp.c b/src/net/udp/tftp.c
index 3073e682..2ee01862 100644
--- a/src/net/udp/tftp.c
+++ b/src/net/udp/tftp.c
@@ -44,6 +44,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#include <ipxe/dhcp.h>
#include <ipxe/uri.h>
#include <ipxe/profile.h>
+#include <ipxe/errortab.h>
#include <ipxe/tftp.h>
/** @file
@@ -76,6 +77,9 @@ FEATURE ( FEATURE_PROTOCOL, "TFTP", DHCP_EB_FEATURE_TFTP, 1 );
#define EINVAL_MC_INVALID_PORT __einfo_error ( EINFO_EINVAL_MC_INVALID_PORT )
#define EINFO_EINVAL_MC_INVALID_PORT __einfo_uniqify \
( EINFO_EINVAL, 0x07, "Invalid multicast port" )
+#define ENOENT_NOT_FOUND __einfo_error ( EINFO_ENOENT_NOT_FOUND )
+#define EINFO_ENOENT_NOT_FOUND __einfo_uniqify \
+ ( EINFO_ENOENT, 0x01, "Not found" )
/**
* A TFTP request
@@ -167,6 +171,11 @@ static struct profiler tftp_client_profiler __profiler =
static struct profiler tftp_server_profiler __profiler =
{ .name = "tftp.server" };
+/** Human-readable error messages */
+struct errortab tftp_errors[] __errortab = {
+ __einfo_errortab ( EINFO_ENOENT_NOT_FOUND ),
+};
+
/**
* Free TFTP request
*
@@ -872,7 +881,7 @@ static int tftp_rx_data ( struct tftp_request *tftp,
*/
static int tftp_errcode_to_rc ( unsigned int errcode ) {
switch ( errcode ) {
- case TFTP_ERR_FILE_NOT_FOUND: return -ENOENT;
+ case TFTP_ERR_FILE_NOT_FOUND: return -ENOENT_NOT_FOUND;
case TFTP_ERR_ACCESS_DENIED: return -EACCES;
case TFTP_ERR_ILLEGAL_OP: return -ENOTTY;
default: return -ENOTSUP;
diff --git a/src/tests/uri_test.c b/src/tests/uri_test.c
index 9d2f6dba..7ce87a20 100644
--- a/src/tests/uri_test.c
+++ b/src/tests/uri_test.c
@@ -754,6 +754,20 @@ static struct uri_resolve_test uri_fragment = {
"http://192.168.0.254/test#bar",
};
+/** Empty relative URI resolution test */
+static struct uri_resolve_test uri_self = {
+ "http://192.168.0.1/path/to/me",
+ "",
+ "http://192.168.0.1/path/to/me",
+};
+
+/** Current directory URI resolution test */
+static struct uri_resolve_test uri_cwd = {
+ "http://192.168.0.1/path/to/me",
+ ".",
+ "http://192.168.0.1/path/to/",
+};
+
/** PXE URI with absolute URI */
static struct uri_pxe_test uri_pxe_absolute = {
{
@@ -996,6 +1010,8 @@ static void uri_test_exec ( void ) {
uri_resolve_ok ( &uri_absolute_uri_path );
uri_resolve_ok ( &uri_query );
uri_resolve_ok ( &uri_fragment );
+ uri_resolve_ok ( &uri_self );
+ uri_resolve_ok ( &uri_cwd );
/* PXE URI construction tests */
uri_pxe_ok ( &uri_pxe_absolute );