diff options
author | Simon Rettberg | 2024-04-12 14:52:06 +0200 |
---|---|---|
committer | Simon Rettberg | 2024-04-12 14:52:06 +0200 |
commit | 2ae76865d3d109712f9ee488cbc19bd107bbc9ab (patch) | |
tree | 36e7310eb089cf7fd3496e5c32c70981e447f235 | |
parent | Merge branch 'aqc1xx' into openslx (diff) | |
parent | [netdevice] Add "linktype" setting (diff) | |
download | ipxe-2ae76865d3d109712f9ee488cbc19bd107bbc9ab.tar.gz ipxe-2ae76865d3d109712f9ee488cbc19bd107bbc9ab.tar.xz ipxe-2ae76865d3d109712f9ee488cbc19bd107bbc9ab.zip |
Merge branch 'master' into openslxopenslx
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 ), ®s ); + + /* 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 ( ð_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 ); |