diff options
author | fishsoupisgood <github@madingley.org> | 2019-04-29 01:17:54 +0100 |
---|---|---|
committer | fishsoupisgood <github@madingley.org> | 2019-05-27 03:43:43 +0100 |
commit | 3f2546b2ef55b661fd8dd69682b38992225e86f6 (patch) | |
tree | 65ca85f13617aee1dce474596800950f266a456c /roms/ipxe/src/interface/efi | |
download | qemu-3f2546b2ef55b661fd8dd69682b38992225e86f6.tar.gz qemu-3f2546b2ef55b661fd8dd69682b38992225e86f6.tar.bz2 qemu-3f2546b2ef55b661fd8dd69682b38992225e86f6.zip |
Diffstat (limited to 'roms/ipxe/src/interface/efi')
21 files changed, 7266 insertions, 0 deletions
diff --git a/roms/ipxe/src/interface/efi/efi_autoboot.c b/roms/ipxe/src/interface/efi/efi_autoboot.c new file mode 100644 index 00000000..ab0f3654 --- /dev/null +++ b/roms/ipxe/src/interface/efi/efi_autoboot.c @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2014 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. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include <ipxe/efi/efi.h> +#include <ipxe/efi/efi_autoboot.h> +#include <ipxe/efi/Protocol/SimpleNetwork.h> +#include <usr/autoboot.h> + +/** @file + * + * EFI autoboot device + * + */ + +/** + * Identify autoboot device + * + */ +void efi_set_autoboot ( void ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + union { + EFI_SIMPLE_NETWORK_PROTOCOL *snp; + void *interface; + } snp; + EFI_SIMPLE_NETWORK_MODE *mode; + EFI_STATUS efirc; + + /* Look for an SNP instance on the image's device handle */ + if ( ( efirc = bs->OpenProtocol ( efi_loaded_image->DeviceHandle, + &efi_simple_network_protocol_guid, + &snp.interface, efi_image_handle, + NULL, + EFI_OPEN_PROTOCOL_GET_PROTOCOL ))!=0){ + DBGC ( efi_loaded_image, "EFI found no autoboot device\n" ); + return; + } + + /* Record autoboot device */ + mode = snp.snp->Mode; + set_autoboot_ll_addr ( &mode->CurrentAddress, mode->HwAddressSize ); + DBGC ( efi_loaded_image, "EFI found autoboot link-layer address:\n" ); + DBGC_HDA ( efi_loaded_image, 0, &mode->CurrentAddress, + mode->HwAddressSize ); + + /* Close protocol */ + bs->CloseProtocol ( efi_loaded_image->DeviceHandle, + &efi_simple_network_protocol_guid, + efi_image_handle, NULL ); +} diff --git a/roms/ipxe/src/interface/efi/efi_bofm.c b/roms/ipxe/src/interface/efi/efi_bofm.c new file mode 100644 index 00000000..bdb70519 --- /dev/null +++ b/roms/ipxe/src/interface/efi/efi_bofm.c @@ -0,0 +1,334 @@ +/* + * Copyright (C) 2011 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. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include <errno.h> +#include <ipxe/bofm.h> +#include <ipxe/efi/efi.h> +#include <ipxe/efi/efi_pci.h> +#include <ipxe/efi/efi_driver.h> + +/** @file + * + * IBM BladeCenter Open Fabric Manager (BOFM) EFI interface + * + */ + +/*************************************************************************** + * + * EFI BOFM definitions + * + *************************************************************************** + * + * Taken from the BOFM UEFI Vendor Specification document + * + */ + +#define IBM_BOFM_DRIVER_CONFIGURATION_PROTOCOL_GUID \ + { 0x03207ce2, 0xd9c7, 0x11dc, \ + { 0xa9, 0x4d, 0x00, 0x19, 0x7d, 0x89, 0x02, 0x38 } } + +#define IBM_BOFM_DRIVER_CONFIGURATION2_PROTOCOL_GUID \ + { 0xe82a9763, 0x0584, 0x4e41, \ + { 0xbb, 0x39, 0xe0, 0xcd, 0xb8, 0xc1, 0xf0, 0xfc } } + +typedef struct { + UINT8 Id; + UINT8 ResultByte; +} __attribute__ (( packed )) BOFM_EPID_Results_t; + +typedef struct { + UINT8 Version; + UINT8 Level; + UINT16 Length; + UINT8 Checksum; + UINT8 Profile[32]; + UINT8 GlobalOption0; + UINT8 GlobalOption1; + UINT8 GlobalOption2; + UINT8 GlobalOption3; + UINT32 SequenceStamp; + UINT8 Regions[911]; // For use by BOFM Driver + UINT32 Reserved1; +} __attribute__ (( packed )) BOFM_Parameters_t; + +typedef struct { + UINT32 Reserved1; + UINT8 Version; + UINT8 Level; + UINT8 Checksum; + UINT32 SequenceStamp; + UINT8 SUIDResults; + UINT8 EntryResults[32]; + UINT8 Reserved2; + UINT8 Reserved3; + UINT8 FCTgtResults[2]; + UINT8 SASTgtResults[2]; + BOFM_EPID_Results_t EPIDResults[2]; + UINT8 Results4[10]; +} __attribute__ (( packed )) BOFM_Results_t; + +typedef struct { + UINT32 Signature; + UINT32 SubSignature; + BOFM_Parameters_t Parameters; + BOFM_Results_t Results; +} __attribute__ (( packed )) BOFM_DataStructure_t; + +#define IBM_BOFM_TABLE BOFM_DataStructure_t + +typedef struct _IBM_BOFM_DRIVER_CONFIGURATION_PROTOCOL + IBM_BOFM_DRIVER_CONFIGURATION_PROTOCOL; + +typedef struct _IBM_BOFM_DRIVER_CONFIGURATION_PROTOCOL2 + IBM_BOFM_DRIVER_CONFIGURATION_PROTOCOL2; + +typedef EFI_STATUS ( EFIAPI *IBM_BOFM_DRIVER_CONFIGURATION_SUPPORT ) ( + IN IBM_BOFM_DRIVER_CONFIGURATION_PROTOCOL *This, + EFI_HANDLE ControllerHandle, + UINT8 SupporttedOptions, + UINT8 iSCSI_Parameter_Version, + UINT8 BOFM_Parameter_Version +); + +typedef EFI_STATUS ( EFIAPI *IBM_BOFM_DRIVER_CONFIGURATION_STATUS ) ( + IN IBM_BOFM_DRIVER_CONFIGURATION_PROTOCOL *This, + EFI_HANDLE ControllerHandle, + BOOLEAN ResetRequired, + UINT8 BOFMReturnCode +); + +typedef EFI_STATUS ( EFIAPI *IBM_BOFM_DRIVER_CONFIGURATION_STATUS2 ) ( + IN IBM_BOFM_DRIVER_CONFIGURATION_PROTOCOL2 *This, + EFI_HANDLE ControllerHandle, + BOOLEAN ResetRequired, + UINT8 BOFMReturnCode +); + +struct _IBM_BOFM_DRIVER_CONFIGURATION_PROTOCOL { + IBM_BOFM_TABLE BofmTable; + IBM_BOFM_DRIVER_CONFIGURATION_STATUS SetStatus; + IBM_BOFM_DRIVER_CONFIGURATION_SUPPORT RegisterSupport; +}; + +struct _IBM_BOFM_DRIVER_CONFIGURATION_PROTOCOL2 { + UINT32 Signature; + UINT32 Reserved1; + UINT64 Reserved2; + IBM_BOFM_DRIVER_CONFIGURATION_STATUS2 SetStatus; + IBM_BOFM_DRIVER_CONFIGURATION_SUPPORT RegisterSupport; + IBM_BOFM_TABLE BofmTable; +}; + +/*************************************************************************** + * + * EFI BOFM interface + * + *************************************************************************** + */ + +/** BOFM1 protocol GUID */ +static EFI_GUID bofm1_protocol_guid = + IBM_BOFM_DRIVER_CONFIGURATION_PROTOCOL_GUID; + +/** BOFM2 protocol GUID */ +static EFI_GUID bofm2_protocol_guid = + IBM_BOFM_DRIVER_CONFIGURATION2_PROTOCOL_GUID; + +/** + * Check if device is supported + * + * @v device EFI device handle + * @ret rc Return status code + */ +static int efi_bofm_supported ( EFI_HANDLE device ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + struct pci_device pci; + union { + IBM_BOFM_DRIVER_CONFIGURATION_PROTOCOL *bofm1; + void *interface; + } bofm1; + EFI_STATUS efirc; + int rc; + + /* Get PCI device information */ + if ( ( rc = efipci_info ( device, &pci ) ) != 0 ) + return rc; + + /* Look for a BOFM driver */ + if ( ( rc = bofm_find_driver ( &pci ) ) != 0 ) { + DBGCP ( device, "EFIBOFM %p %s has no driver\n", + device, efi_handle_name ( device ) ); + return rc; + } + + /* Locate BOFM protocol */ + if ( ( efirc = bs->LocateProtocol ( &bofm1_protocol_guid, NULL, + &bofm1.interface ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( device, "EFIBOFM %p %s cannot find BOFM protocol\n", + device, efi_handle_name ( device ) ); + return rc; + } + + /* Register support for this device */ + if ( ( efirc = bofm1.bofm1->RegisterSupport ( bofm1.bofm1, device, + 0x04 /* Can change MAC */, + 0x00 /* No iSCSI */, + 0x02 /* Version */ ))!=0){ + rc = -EEFI ( efirc ); + DBGC ( device, "EFIBOFM %p %s could not register support: %s\n", + device, efi_handle_name ( device ), strerror ( rc ) ); + return rc; + } + + DBGC ( device, "EFIBOFM %p %s has driver \"%s\"\n", + device, efi_handle_name ( device ), pci.id->name ); + return 0; +} + +/** + * Attach driver to device + * + * @v efidev EFI device + * @ret rc Return status code + */ +static int efi_bofm_start ( struct efi_device *efidev ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + EFI_HANDLE device = efidev->device; + union { + IBM_BOFM_DRIVER_CONFIGURATION_PROTOCOL *bofm1; + void *interface; + } bofm1; + union { + IBM_BOFM_DRIVER_CONFIGURATION_PROTOCOL2 *bofm2; + void *interface; + } bofm2; + struct pci_device pci; + IBM_BOFM_TABLE *bofmtab; + IBM_BOFM_TABLE *bofmtab2; + int bofmrc; + EFI_STATUS efirc; + int rc; + + /* Open PCI device, if possible */ + if ( ( rc = efipci_open ( device, EFI_OPEN_PROTOCOL_GET_PROTOCOL, + &pci ) ) != 0 ) + goto err_open; + + /* Locate BOFM protocol */ + if ( ( efirc = bs->LocateProtocol ( &bofm1_protocol_guid, NULL, + &bofm1.interface ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( device, "EFIBOFM %p %s cannot find BOFM protocol\n", + device, efi_handle_name ( device ) ); + goto err_locate_bofm; + } + bofmtab = &bofm1.bofm1->BofmTable; + DBGC ( device, "EFIBOFM %p %s found version 1 BOFM table at %p+%04x\n", + device, efi_handle_name ( device ), bofmtab, + bofmtab->Parameters.Length ); + + /* Locate BOFM2 protocol, if available */ + if ( ( efirc = bs->LocateProtocol ( &bofm2_protocol_guid, NULL, + &bofm2.interface ) ) == 0 ) { + bofmtab2 = &bofm2.bofm2->BofmTable; + DBGC ( device, "EFIBOFM %p %s found version 2 BOFM table at " + "%p+%04x\n", device, efi_handle_name ( device ), + bofmtab2, bofmtab2->Parameters.Length ); + assert ( bofm2.bofm2->RegisterSupport == + bofm1.bofm1->RegisterSupport ); + } else { + DBGC ( device, "EFIBOFM %p %s cannot find BOFM2 protocol\n", + device, efi_handle_name ( device ) ); + /* Not a fatal error; may be a BOFM1-only system */ + bofmtab2 = NULL; + } + + /* Process BOFM table */ + DBGC2 ( device, "EFIBOFM %p %s version 1 before processing:\n", + device, efi_handle_name ( device ) ); + DBGC2_HD ( device, bofmtab, bofmtab->Parameters.Length ); + if ( bofmtab2 ) { + DBGC2 ( device, "EFIBOFM %p %s version 2 before processing:\n", + device, efi_handle_name ( device ) ); + DBGC2_HD ( device, bofmtab2, bofmtab2->Parameters.Length ); + } + bofmrc = bofm ( virt_to_user ( bofmtab2 ? bofmtab2 : bofmtab ), &pci ); + DBGC ( device, "EFIBOFM %p %s status %08x\n", + device, efi_handle_name ( device ), bofmrc ); + DBGC2 ( device, "EFIBOFM %p %s version 1 after processing:\n", + device, efi_handle_name ( device ) ); + DBGC2_HD ( device, bofmtab, bofmtab->Parameters.Length ); + if ( bofmtab2 ) { + DBGC2 ( device, "EFIBOFM %p %s version 2 after processing:\n", + device, efi_handle_name ( device ) ); + DBGC2_HD ( device, bofmtab2, bofmtab2->Parameters.Length ); + } + + /* Return BOFM status */ + if ( bofmtab2 ) { + if ( ( efirc = bofm2.bofm2->SetStatus ( bofm2.bofm2, device, + FALSE, bofmrc ) ) != 0){ + rc = -EEFI ( efirc ); + DBGC ( device, "EFIBOFM %p %s could not set BOFM2 " + "status: %s\n", device, + efi_handle_name ( device ), strerror ( rc ) ); + goto err_set_status; + } + } else { + if ( ( efirc = bofm1.bofm1->SetStatus ( bofm1.bofm1, device, + FALSE, bofmrc ) ) != 0){ + rc = -EEFI ( efirc ); + DBGC ( device, "EFIBOFM %p %s could not set BOFM " + "status: %s\n", device, + efi_handle_name ( device ), strerror ( rc ) ); + goto err_set_status; + } + } + + /* BOFM (ab)uses the "start" method to mean "process and exit" */ + rc = -EAGAIN; + + err_set_status: + err_locate_bofm: + efipci_close ( device ); + err_open: + return rc; +} + +/** + * Detach driver from device + * + * @v device EFI device + */ +static void efi_bofm_stop ( struct efi_device *efidev __unused ) { + + /* Should never happen */ + assert ( 0 ); +} + +/** EFI BOFM driver */ +struct efi_driver efi_bofm_driver __efi_driver ( EFI_DRIVER_EARLY ) = { + .name = "BOFM", + .supported = efi_bofm_supported, + .start = efi_bofm_start, + .stop = efi_bofm_stop, +}; diff --git a/roms/ipxe/src/interface/efi/efi_console.c b/roms/ipxe/src/interface/efi/efi_console.c new file mode 100644 index 00000000..3b30f309 --- /dev/null +++ b/roms/ipxe/src/interface/efi/efi_console.c @@ -0,0 +1,360 @@ +/* + * Copyright (C) 2008 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. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include <stddef.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <ipxe/efi/efi.h> +#include <ipxe/efi/Protocol/ConsoleControl/ConsoleControl.h> +#include <ipxe/ansiesc.h> +#include <ipxe/console.h> +#include <ipxe/init.h> +#include <config/console.h> + +#define ATTR_BOLD 0x08 + +#define ATTR_FCOL_MASK 0x07 +#define ATTR_FCOL_BLACK 0x00 +#define ATTR_FCOL_BLUE 0x01 +#define ATTR_FCOL_GREEN 0x02 +#define ATTR_FCOL_CYAN 0x03 +#define ATTR_FCOL_RED 0x04 +#define ATTR_FCOL_MAGENTA 0x05 +#define ATTR_FCOL_YELLOW 0x06 +#define ATTR_FCOL_WHITE 0x07 + +#define ATTR_BCOL_MASK 0x70 +#define ATTR_BCOL_BLACK 0x00 +#define ATTR_BCOL_BLUE 0x10 +#define ATTR_BCOL_GREEN 0x20 +#define ATTR_BCOL_CYAN 0x30 +#define ATTR_BCOL_RED 0x40 +#define ATTR_BCOL_MAGENTA 0x50 +#define ATTR_BCOL_YELLOW 0x60 +#define ATTR_BCOL_WHITE 0x70 + +#define ATTR_DEFAULT ATTR_FCOL_WHITE + +/* Set default console usage if applicable */ +#if ! ( defined ( CONSOLE_EFI ) && CONSOLE_EXPLICIT ( CONSOLE_EFI ) ) +#undef CONSOLE_EFI +#define CONSOLE_EFI ( CONSOLE_USAGE_ALL & ~CONSOLE_USAGE_LOG ) +#endif + +/** Current character attribute */ +static unsigned int efi_attr = ATTR_DEFAULT; + +/** Console control protocol */ +static EFI_CONSOLE_CONTROL_PROTOCOL *conctrl; +EFI_REQUEST_PROTOCOL ( EFI_CONSOLE_CONTROL_PROTOCOL, &conctrl ); + +/** + * Handle ANSI CUP (cursor position) + * + * @v ctx ANSI escape sequence context + * @v count Parameter count + * @v params[0] Row (1 is top) + * @v params[1] Column (1 is left) + */ +static void efi_handle_cup ( struct ansiesc_context *ctx __unused, + unsigned int count __unused, int params[] ) { + EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *conout = efi_systab->ConOut; + int cx = ( params[1] - 1 ); + int cy = ( params[0] - 1 ); + + if ( cx < 0 ) + cx = 0; + if ( cy < 0 ) + cy = 0; + + conout->SetCursorPosition ( conout, cx, cy ); +} + +/** + * Handle ANSI ED (erase in page) + * + * @v ctx ANSI escape sequence context + * @v count Parameter count + * @v params[0] Region to erase + */ +static void efi_handle_ed ( struct ansiesc_context *ctx __unused, + unsigned int count __unused, + int params[] __unused ) { + EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *conout = efi_systab->ConOut; + + /* We assume that we always clear the whole screen */ + assert ( params[0] == ANSIESC_ED_ALL ); + + conout->ClearScreen ( conout ); +} + +/** + * Handle ANSI SGR (set graphics rendition) + * + * @v ctx ANSI escape sequence context + * @v count Parameter count + * @v params List of graphic rendition aspects + */ +static void efi_handle_sgr ( struct ansiesc_context *ctx __unused, + unsigned int count, int params[] ) { + EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *conout = efi_systab->ConOut; + static const uint8_t efi_attr_fcols[10] = { + ATTR_FCOL_BLACK, ATTR_FCOL_RED, ATTR_FCOL_GREEN, + ATTR_FCOL_YELLOW, ATTR_FCOL_BLUE, ATTR_FCOL_MAGENTA, + ATTR_FCOL_CYAN, ATTR_FCOL_WHITE, + ATTR_FCOL_WHITE, ATTR_FCOL_WHITE /* defaults */ + }; + static const uint8_t efi_attr_bcols[10] = { + ATTR_BCOL_BLACK, ATTR_BCOL_RED, ATTR_BCOL_GREEN, + ATTR_BCOL_YELLOW, ATTR_BCOL_BLUE, ATTR_BCOL_MAGENTA, + ATTR_BCOL_CYAN, ATTR_BCOL_WHITE, + ATTR_BCOL_BLACK, ATTR_BCOL_BLACK /* defaults */ + }; + unsigned int i; + int aspect; + + for ( i = 0 ; i < count ; i++ ) { + aspect = params[i]; + if ( aspect == 0 ) { + efi_attr = ATTR_DEFAULT; + } else if ( aspect == 1 ) { + efi_attr |= ATTR_BOLD; + } else if ( aspect == 22 ) { + efi_attr &= ~ATTR_BOLD; + } else if ( ( aspect >= 30 ) && ( aspect <= 39 ) ) { + efi_attr &= ~ATTR_FCOL_MASK; + efi_attr |= efi_attr_fcols[ aspect - 30 ]; + } else if ( ( aspect >= 40 ) && ( aspect <= 49 ) ) { + efi_attr &= ~ATTR_BCOL_MASK; + efi_attr |= efi_attr_bcols[ aspect - 40 ]; + } + } + + conout->SetAttribute ( conout, efi_attr ); +} + +/** + * Handle ANSI DECTCEM set (show cursor) + * + * @v ctx ANSI escape sequence context + * @v count Parameter count + * @v params List of graphic rendition aspects + */ +static void efi_handle_dectcem_set ( struct ansiesc_context *ctx __unused, + unsigned int count __unused, + int params[] __unused ) { + EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *conout = efi_systab->ConOut; + + conout->EnableCursor ( conout, TRUE ); +} + +/** + * Handle ANSI DECTCEM reset (hide cursor) + * + * @v ctx ANSI escape sequence context + * @v count Parameter count + * @v params List of graphic rendition aspects + */ +static void efi_handle_dectcem_reset ( struct ansiesc_context *ctx __unused, + unsigned int count __unused, + int params[] __unused ) { + EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *conout = efi_systab->ConOut; + + conout->EnableCursor ( conout, FALSE ); +} + +/** EFI console ANSI escape sequence handlers */ +static struct ansiesc_handler efi_ansiesc_handlers[] = { + { ANSIESC_CUP, efi_handle_cup }, + { ANSIESC_ED, efi_handle_ed }, + { ANSIESC_SGR, efi_handle_sgr }, + { ANSIESC_DECTCEM_SET, efi_handle_dectcem_set }, + { ANSIESC_DECTCEM_RESET, efi_handle_dectcem_reset }, + { 0, NULL } +}; + +/** EFI console ANSI escape sequence context */ +static struct ansiesc_context efi_ansiesc_ctx = { + .handlers = efi_ansiesc_handlers, +}; + +/** + * Print a character to EFI console + * + * @v character Character to be printed + */ +static void efi_putchar ( int character ) { + EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *conout = efi_systab->ConOut; + wchar_t wstr[] = { character, 0 }; + + /* Intercept ANSI escape sequences */ + character = ansiesc_process ( &efi_ansiesc_ctx, character ); + if ( character < 0 ) + return; + + conout->OutputString ( conout, wstr ); +} + +/** + * Pointer to current ANSI output sequence + * + * While we are in the middle of returning an ANSI sequence for a + * special key, this will point to the next character to return. When + * not in the middle of such a sequence, this will point to a NUL + * (note: not "will be NULL"). + */ +static const char *ansi_input = ""; + +/** Mapping from EFI scan codes to ANSI escape sequences */ +static const char *ansi_sequences[] = { + [SCAN_UP] = "[A", + [SCAN_DOWN] = "[B", + [SCAN_RIGHT] = "[C", + [SCAN_LEFT] = "[D", + [SCAN_HOME] = "[H", + [SCAN_END] = "[F", + [SCAN_INSERT] = "[2~", + /* EFI translates an incoming backspace via the serial console + * into a SCAN_DELETE. There's not much we can do about this. + */ + [SCAN_DELETE] = "[3~", + [SCAN_PAGE_UP] = "[5~", + [SCAN_PAGE_DOWN] = "[6~", + /* EFI translates some (but not all) incoming escape sequences + * via the serial console into equivalent scancodes. When it + * doesn't recognise a sequence, it helpfully(!) translates + * the initial ESC and passes the remainder through verbatim. + * Treating SCAN_ESC as equivalent to an empty escape sequence + * works around this bug. + */ + [SCAN_ESC] = "", +}; + +/** + * Get ANSI escape sequence corresponding to EFI scancode + * + * @v scancode EFI scancode + * @ret ansi_seq ANSI escape sequence, if any, otherwise NULL + */ +static const char * scancode_to_ansi_seq ( unsigned int scancode ) { + if ( scancode < ( sizeof ( ansi_sequences ) / + sizeof ( ansi_sequences[0] ) ) ) { + return ansi_sequences[scancode]; + } + return NULL; +} + +/** + * Get character from EFI console + * + * @ret character Character read from console + */ +static int efi_getchar ( void ) { + EFI_SIMPLE_TEXT_INPUT_PROTOCOL *conin = efi_systab->ConIn; + const char *ansi_seq; + EFI_INPUT_KEY key; + EFI_STATUS efirc; + int rc; + + /* If we are mid-sequence, pass out the next byte */ + if ( *ansi_input ) + return *(ansi_input++); + + /* Read key from real EFI console */ + if ( ( efirc = conin->ReadKeyStroke ( conin, &key ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBG ( "EFI could not read keystroke: %s\n", strerror ( rc ) ); + return 0; + } + DBG2 ( "EFI read key stroke with unicode %04x scancode %04x\n", + key.UnicodeChar, key.ScanCode ); + + /* If key has a Unicode representation, return it */ + if ( key.UnicodeChar ) + return key.UnicodeChar; + + /* Otherwise, check for a special key that we know about */ + if ( ( ansi_seq = scancode_to_ansi_seq ( key.ScanCode ) ) ) { + /* Start of escape sequence: return ESC (0x1b) */ + ansi_input = ansi_seq; + return 0x1b; + } + + return 0; +} + +/** + * Check for character ready to read from EFI console + * + * @ret True Character available to read + * @ret False No character available to read + */ +static int efi_iskey ( void ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + EFI_SIMPLE_TEXT_INPUT_PROTOCOL *conin = efi_systab->ConIn; + EFI_STATUS efirc; + + /* If we are mid-sequence, we are always ready */ + if ( *ansi_input ) + return 1; + + /* Check to see if the WaitForKey event has fired */ + if ( ( efirc = bs->CheckEvent ( conin->WaitForKey ) ) == 0 ) + return 1; + + return 0; +} + +/** EFI console driver */ +struct console_driver efi_console __console_driver = { + .putchar = efi_putchar, + .getchar = efi_getchar, + .iskey = efi_iskey, + .usage = CONSOLE_EFI, +}; + +/** + * Initialise EFI console + * + */ +static void efi_console_init ( void ) { + EFI_CONSOLE_CONTROL_SCREEN_MODE mode; + + /* On some older EFI 1.10 implementations, we must use the + * (now obsolete) EFI_CONSOLE_CONTROL_PROTOCOL to switch the + * console into text mode. + */ + if ( conctrl ) { + conctrl->GetMode ( conctrl, &mode, NULL, NULL ); + if ( mode != EfiConsoleControlScreenText ) { + conctrl->SetMode ( conctrl, + EfiConsoleControlScreenText ); + } + } +} + +/** + * EFI console initialisation function + */ +struct init_fn efi_console_init_fn __init_fn ( INIT_EARLY ) = { + .initialise = efi_console_init, +}; diff --git a/roms/ipxe/src/interface/efi/efi_debug.c b/roms/ipxe/src/interface/efi/efi_debug.c new file mode 100644 index 00000000..d2396014 --- /dev/null +++ b/roms/ipxe/src/interface/efi/efi_debug.c @@ -0,0 +1,661 @@ +/* + * Copyright (C) 2013 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. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +/** + * @file + * + * EFI debugging utilities + * + */ + +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <ipxe/uuid.h> +#include <ipxe/base16.h> +#include <ipxe/efi/efi.h> +#include <ipxe/efi/efi_utils.h> +#include <ipxe/efi/Protocol/ComponentName.h> +#include <ipxe/efi/Protocol/ComponentName2.h> +#include <ipxe/efi/Protocol/DevicePathToText.h> +#include <ipxe/efi/IndustryStandard/PeImage.h> + +/** Device path to text protocol */ +static EFI_DEVICE_PATH_TO_TEXT_PROTOCOL *efidpt; +EFI_REQUEST_PROTOCOL ( EFI_DEVICE_PATH_TO_TEXT_PROTOCOL, &efidpt ); + +/** Iscsi4Dxe module GUID */ +static EFI_GUID efi_iscsi4_dxe_guid = { + 0x4579b72d, 0x7ec4, 0x4dd4, + { 0x84, 0x86, 0x08, 0x3c, 0x86, 0xb1, 0x82, 0xa7 } +}; + +/** VlanConfigDxe module GUID */ +static EFI_GUID efi_vlan_config_dxe_guid = { + 0xe4f61863, 0xfe2c, 0x4b56, + { 0xa8, 0xf4, 0x08, 0x51, 0x9b, 0xc4, 0x39, 0xdf } +}; + +/** A well-known GUID */ +struct efi_well_known_guid { + /** GUID */ + EFI_GUID *guid; + /** Name */ + const char *name; +}; + +/** Well-known GUIDs */ +static struct efi_well_known_guid efi_well_known_guids[] = { + { &efi_arp_protocol_guid, + "Arp" }, + { &efi_arp_service_binding_protocol_guid, + "ArpSb" }, + { &efi_block_io_protocol_guid, + "BlockIo" }, + { &efi_bus_specific_driver_override_protocol_guid, + "BusSpecificDriverOverride" }, + { &efi_component_name_protocol_guid, + "ComponentName" }, + { &efi_component_name2_protocol_guid, + "ComponentName2" }, + { &efi_device_path_protocol_guid, + "DevicePath" }, + { &efi_driver_binding_protocol_guid, + "DriverBinding" }, + { &efi_dhcp4_protocol_guid, + "Dhcp4" }, + { &efi_dhcp4_service_binding_protocol_guid, + "Dhcp4Sb" }, + { &efi_disk_io_protocol_guid, + "DiskIo" }, + { &efi_graphics_output_protocol_guid, + "GraphicsOutput" }, + { &efi_hii_config_access_protocol_guid, + "HiiConfigAccess" }, + { &efi_ip4_protocol_guid, + "Ip4" }, + { &efi_ip4_config_protocol_guid, + "Ip4Config" }, + { &efi_ip4_service_binding_protocol_guid, + "Ip4Sb" }, + { &efi_iscsi4_dxe_guid, + "IScsi4Dxe" }, + { &efi_load_file_protocol_guid, + "LoadFile" }, + { &efi_load_file2_protocol_guid, + "LoadFile2" }, + { &efi_loaded_image_protocol_guid, + "LoadedImage" }, + { &efi_loaded_image_device_path_protocol_guid, + "LoadedImageDevicePath"}, + { &efi_managed_network_protocol_guid, + "ManagedNetwork" }, + { &efi_managed_network_service_binding_protocol_guid, + "ManagedNetworkSb" }, + { &efi_mtftp4_protocol_guid, + "Mtftp4" }, + { &efi_mtftp4_service_binding_protocol_guid, + "Mtftp4Sb" }, + { &efi_nii_protocol_guid, + "Nii" }, + { &efi_nii31_protocol_guid, + "Nii31" }, + { &efi_pci_io_protocol_guid, + "PciIo" }, + { &efi_pci_root_bridge_io_protocol_guid, + "PciRootBridgeIo" }, + { &efi_pxe_base_code_protocol_guid, + "PxeBaseCode" }, + { &efi_simple_file_system_protocol_guid, + "SimpleFileSystem" }, + { &efi_simple_network_protocol_guid, + "SimpleNetwork" }, + { &efi_tcg_protocol_guid, + "Tcg" }, + { &efi_tcp4_protocol_guid, + "Tcp4" }, + { &efi_tcp4_service_binding_protocol_guid, + "Tcp4Sb" }, + { &efi_udp4_protocol_guid, + "Udp4" }, + { &efi_udp4_service_binding_protocol_guid, + "Udp4Sb" }, + { &efi_vlan_config_protocol_guid, + "VlanConfig" }, + { &efi_vlan_config_dxe_guid, + "VlanConfigDxe" }, +}; + +/** + * Convert GUID to a printable string + * + * @v guid GUID + * @ret string Printable string + */ +const char * efi_guid_ntoa ( EFI_GUID *guid ) { + union { + union uuid uuid; + EFI_GUID guid; + } u; + unsigned int i; + + /* Sanity check */ + if ( ! guid ) + return NULL; + + /* Check for a match against well-known GUIDs */ + for ( i = 0 ; i < ( sizeof ( efi_well_known_guids ) / + sizeof ( efi_well_known_guids[0] ) ) ; i++ ) { + if ( memcmp ( guid, efi_well_known_guids[i].guid, + sizeof ( *guid ) ) == 0 ) { + return efi_well_known_guids[i].name; + } + } + + /* Convert GUID to standard endianness */ + memcpy ( &u.guid, guid, sizeof ( u.guid ) ); + uuid_mangle ( &u.uuid ); + return uuid_ntoa ( &u.uuid ); +} + +/** + * Name protocol open attributes + * + * @v attributes Protocol open attributes + * @ret name Protocol open attributes name + * + * Returns a (static) string with characters for each set bit + * corresponding to BY_(H)ANDLE_PROTOCOL, (G)ET_PROTOCOL, + * (T)EST_PROTOCOL, BY_(C)HILD_CONTROLLER, BY_(D)RIVER, and + * E(X)CLUSIVE. + */ +static const char * efi_open_attributes_name ( unsigned int attributes ) { + static char attribute_chars[] = "HGTCDX"; + static char name[ sizeof ( attribute_chars ) ]; + char *tmp = name; + unsigned int i; + + for ( i = 0 ; i < ( sizeof ( attribute_chars ) - 1 ) ; i++ ) { + if ( attributes & ( 1 << i ) ) + *(tmp++) = attribute_chars[i]; + } + *tmp = '\0'; + + return name; +} + +/** + * Print list of openers of a given protocol on a given handle + * + * @v handle EFI handle + * @v protocol Protocol GUID + */ +void dbg_efi_openers ( EFI_HANDLE handle, EFI_GUID *protocol ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + EFI_OPEN_PROTOCOL_INFORMATION_ENTRY *openers; + EFI_OPEN_PROTOCOL_INFORMATION_ENTRY *opener; + UINTN count; + unsigned int i; + EFI_STATUS efirc; + int rc; + + /* Sanity check */ + if ( ( ! handle ) || ( ! protocol ) ) { + printf ( "EFI could not retrieve openers for %s on %p\n", + efi_guid_ntoa ( protocol ), handle ); + return; + } + + /* Retrieve list of openers */ + if ( ( efirc = bs->OpenProtocolInformation ( handle, protocol, &openers, + &count ) ) != 0 ) { + rc = -EEFI ( efirc ); + printf ( "EFI could not retrieve openers for %s on %p: %s\n", + efi_guid_ntoa ( protocol ), handle, strerror ( rc ) ); + return; + } + + /* Dump list of openers */ + for ( i = 0 ; i < count ; i++ ) { + opener = &openers[i]; + printf ( "HANDLE %p %s %s opened %dx (%s)", + handle, efi_handle_name ( handle ), + efi_guid_ntoa ( protocol ), opener->OpenCount, + efi_open_attributes_name ( opener->Attributes ) ); + printf ( " by %p %s", opener->AgentHandle, + efi_handle_name ( opener->AgentHandle ) ); + if ( opener->ControllerHandle == handle ) { + printf ( "\n" ); + } else { + printf ( " for %p %s\n", opener->ControllerHandle, + efi_handle_name ( opener->ControllerHandle ) ); + } + } + + /* Free list */ + bs->FreePool ( openers ); +} + +/** + * Print list of protocol handlers attached to a handle + * + * @v handle EFI handle + */ +void dbg_efi_protocols ( EFI_HANDLE handle ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + EFI_GUID **protocols; + EFI_GUID *protocol; + UINTN count; + unsigned int i; + EFI_STATUS efirc; + int rc; + + /* Sanity check */ + if ( ! handle ) { + printf ( "EFI could not retrieve protocols for %p\n", handle ); + return; + } + + /* Retrieve list of protocols */ + if ( ( efirc = bs->ProtocolsPerHandle ( handle, &protocols, + &count ) ) != 0 ) { + rc = -EEFI ( efirc ); + printf ( "EFI could not retrieve protocols for %p: %s\n", + handle, strerror ( rc ) ); + return; + } + + /* Dump list of protocols */ + for ( i = 0 ; i < count ; i++ ) { + protocol = protocols[i]; + printf ( "HANDLE %p %s %s supported\n", + handle, efi_handle_name ( handle ), + efi_guid_ntoa ( protocol ) ); + dbg_efi_openers ( handle, protocol ); + } + + /* Free list */ + bs->FreePool ( protocols ); +} + +/** + * Get textual representation of device path + * + * @v path Device path + * @ret text Textual representation of device path, or NULL + */ +const char * efi_devpath_text ( EFI_DEVICE_PATH_PROTOCOL *path ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + static char text[256]; + void *start; + void *end; + size_t max_len; + size_t len; + CHAR16 *wtext; + + /* Sanity checks */ + if ( ! path ) { + DBG ( "[NULL DevicePath]" ); + return NULL; + } + + /* If we have no DevicePathToText protocol then use a raw hex string */ + if ( ! efidpt ) { + DBG ( "[No DevicePathToText]" ); + start = path; + end = efi_devpath_end ( path ); + len = ( end - start ); + max_len = ( ( sizeof ( text ) - 1 /* NUL */ ) / 2 /* "xx" */ ); + if ( len > max_len ) + len = max_len; + base16_encode ( start, len, text ); + return text; + } + + /* Convert path to a textual representation */ + wtext = efidpt->ConvertDevicePathToText ( path, TRUE, FALSE ); + if ( ! wtext ) + return NULL; + + /* Store path in buffer */ + snprintf ( text, sizeof ( text ), "%ls", wtext ); + + /* Free path */ + bs->FreePool ( wtext ); + + return text; +} + +/** + * Get driver name + * + * @v wtf Component name protocol + * @ret name Driver name, or NULL + */ +static const char * efi_driver_name ( EFI_COMPONENT_NAME_PROTOCOL *wtf ) { + static char name[64]; + CHAR16 *driver_name; + EFI_STATUS efirc; + + /* Sanity check */ + if ( ! wtf ) { + DBG ( "[NULL ComponentName]" ); + return NULL; + } + + /* Try "eng" first; if that fails then try the first language */ + if ( ( ( efirc = wtf->GetDriverName ( wtf, "eng", + &driver_name ) ) != 0 ) && + ( ( efirc = wtf->GetDriverName ( wtf, wtf->SupportedLanguages, + &driver_name ) ) != 0 ) ) { + return NULL; + } + + /* Convert name from CHAR16 to char */ + snprintf ( name, sizeof ( name ), "%ls", driver_name ); + return name; +} + +/** + * Get driver name + * + * @v wtf Component name protocol + * @ret name Driver name, or NULL + */ +static const char * efi_driver_name2 ( EFI_COMPONENT_NAME2_PROTOCOL *wtf ) { + static char name[64]; + CHAR16 *driver_name; + EFI_STATUS efirc; + + /* Sanity check */ + if ( ! wtf ) { + DBG ( "[NULL ComponentName2]" ); + return NULL; + } + + /* Try "en" first; if that fails then try the first language */ + if ( ( ( efirc = wtf->GetDriverName ( wtf, "en", + &driver_name ) ) != 0 ) && + ( ( efirc = wtf->GetDriverName ( wtf, wtf->SupportedLanguages, + &driver_name ) ) != 0 ) ) { + return NULL; + } + + /* Convert name from CHAR16 to char */ + snprintf ( name, sizeof ( name ), "%ls", driver_name ); + return name; +} + +/** + * Get PE/COFF debug filename + * + * @v loaded Loaded image + * @ret name PE/COFF debug filename, or NULL + */ +static const char * +efi_pecoff_debug_name ( EFI_LOADED_IMAGE_PROTOCOL *loaded ) { + static char buf[32]; + EFI_IMAGE_DOS_HEADER *dos; + EFI_IMAGE_OPTIONAL_HEADER_UNION *pe; + EFI_IMAGE_OPTIONAL_HEADER32 *opt32; + EFI_IMAGE_OPTIONAL_HEADER64 *opt64; + EFI_IMAGE_DATA_DIRECTORY *datadir; + EFI_IMAGE_DEBUG_DIRECTORY_ENTRY *debug; + EFI_IMAGE_DEBUG_CODEVIEW_NB10_ENTRY *codeview_nb10; + EFI_IMAGE_DEBUG_CODEVIEW_RSDS_ENTRY *codeview_rsds; + EFI_IMAGE_DEBUG_CODEVIEW_MTOC_ENTRY *codeview_mtoc; + uint16_t dos_magic; + uint32_t pe_magic; + uint16_t opt_magic; + uint32_t codeview_magic; + size_t max_len; + char *name; + char *tmp; + + /* Sanity check */ + if ( ! loaded ) { + DBG ( "[NULL LoadedImage]" ); + return NULL; + } + + /* Parse DOS header */ + dos = loaded->ImageBase; + if ( ! dos ) { + DBG ( "[Missing DOS header]" ); + return NULL; + } + dos_magic = dos->e_magic; + if ( dos_magic != EFI_IMAGE_DOS_SIGNATURE ) { + DBG ( "[Bad DOS signature %#04x]", dos_magic ); + return NULL; + } + pe = ( loaded->ImageBase + dos->e_lfanew ); + + /* Parse PE header */ + pe_magic = pe->Pe32.Signature; + if ( pe_magic != EFI_IMAGE_NT_SIGNATURE ) { + DBG ( "[Bad PE signature %#08x]", pe_magic ); + return NULL; + } + opt32 = &pe->Pe32.OptionalHeader; + opt64 = &pe->Pe32Plus.OptionalHeader; + opt_magic = opt32->Magic; + if ( opt_magic == EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC ) { + datadir = opt32->DataDirectory; + } else if ( opt_magic == EFI_IMAGE_NT_OPTIONAL_HDR64_MAGIC ) { + datadir = opt64->DataDirectory; + } else { + DBG ( "[Bad optional header signature %#04x]", opt_magic ); + return NULL; + } + + /* Parse data directory entry */ + if ( ! datadir[EFI_IMAGE_DIRECTORY_ENTRY_DEBUG].VirtualAddress ) { + DBG ( "[Empty debug directory entry]" ); + return NULL; + } + debug = ( loaded->ImageBase + + datadir[EFI_IMAGE_DIRECTORY_ENTRY_DEBUG].VirtualAddress ); + + /* Parse debug directory entry */ + if ( debug->Type != EFI_IMAGE_DEBUG_TYPE_CODEVIEW ) { + DBG ( "[Not a CodeView debug directory entry (type %d)]", + debug->Type ); + return NULL; + } + codeview_nb10 = ( loaded->ImageBase + debug->RVA ); + codeview_rsds = ( loaded->ImageBase + debug->RVA ); + codeview_mtoc = ( loaded->ImageBase + debug->RVA ); + codeview_magic = codeview_nb10->Signature; + + /* Parse CodeView entry */ + if ( codeview_magic == CODEVIEW_SIGNATURE_NB10 ) { + name = ( ( void * ) ( codeview_nb10 + 1 ) ); + } else if ( codeview_magic == CODEVIEW_SIGNATURE_RSDS ) { + name = ( ( void * ) ( codeview_rsds + 1 ) ); + } else if ( codeview_magic == CODEVIEW_SIGNATURE_MTOC ) { + name = ( ( void * ) ( codeview_mtoc + 1 ) ); + } else { + DBG ( "[Bad CodeView signature %#08x]", codeview_magic ); + return NULL; + } + + /* Sanity check - avoid scanning endlessly through memory */ + max_len = EFI_PAGE_SIZE; /* Reasonably sane */ + if ( strnlen ( name, max_len ) == max_len ) { + DBG ( "[Excessively long or invalid CodeView name]" ); + return NULL; + } + + /* Skip any directory components. We cannot modify this data + * or create a temporary buffer, so do not use basename(). + */ + while ( ( ( tmp = strchr ( name, '/' ) ) != NULL ) || + ( ( tmp = strchr ( name, '\\' ) ) != NULL ) ) { + name = ( tmp + 1 ); + } + + /* Copy base name to buffer */ + snprintf ( buf, sizeof ( buf ), "%s", name ); + + /* Strip file suffix, if present */ + if ( ( tmp = strrchr ( name, '.' ) ) != NULL ) + *tmp = '\0'; + + return name; +} + +/** + * Get initial loaded image name + * + * @v loaded Loaded image + * @ret name Initial loaded image name, or NULL + */ +static const char * +efi_first_loaded_image_name ( EFI_LOADED_IMAGE_PROTOCOL *loaded ) { + + /* Sanity check */ + if ( ! loaded ) { + DBG ( "[NULL LoadedImage]" ); + return NULL; + } + + return ( ( loaded->ParentHandle == NULL ) ? "DxeCore(?)" : NULL ); +} + +/** + * Get loaded image name from file path + * + * @v loaded Loaded image + * @ret name Loaded image name, or NULL + */ +static const char * +efi_loaded_image_filepath_name ( EFI_LOADED_IMAGE_PROTOCOL *loaded ) { + + /* Sanity check */ + if ( ! loaded ) { + DBG ( "[NULL LoadedImage]" ); + return NULL; + } + + return efi_devpath_text ( loaded->FilePath ); +} + +/** An EFI handle name type */ +struct efi_handle_name_type { + /** Protocol */ + EFI_GUID *protocol; + /** + * Get name + * + * @v interface Protocol interface + * @ret name Name of handle, or NULL on failure + */ + const char * ( * name ) ( void *interface ); +}; + +/** + * Define an EFI handle name type + * + * @v protocol Protocol interface + * @v name Method to get name + * @ret type EFI handle name type + */ +#define EFI_HANDLE_NAME_TYPE( protocol, name ) { \ + (protocol), \ + ( const char * ( * ) ( void * ) ) (name), \ + } + +/** EFI handle name types */ +static struct efi_handle_name_type efi_handle_name_types[] = { + /* Device path */ + EFI_HANDLE_NAME_TYPE ( &efi_device_path_protocol_guid, + efi_devpath_text ), + /* Driver name (for driver image handles) */ + EFI_HANDLE_NAME_TYPE ( &efi_component_name2_protocol_guid, + efi_driver_name2 ), + /* Driver name (via obsolete original ComponentName protocol) */ + EFI_HANDLE_NAME_TYPE ( &efi_component_name_protocol_guid, + efi_driver_name ), + /* PE/COFF debug filename (for image handles) */ + EFI_HANDLE_NAME_TYPE ( &efi_loaded_image_protocol_guid, + efi_pecoff_debug_name ), + /* Loaded image device path (for image handles) */ + EFI_HANDLE_NAME_TYPE ( &efi_loaded_image_device_path_protocol_guid, + efi_devpath_text ), + /* First loaded image name (for the DxeCore image) */ + EFI_HANDLE_NAME_TYPE ( &efi_loaded_image_protocol_guid, + efi_first_loaded_image_name ), + /* Handle's loaded image file path (for image handles) */ + EFI_HANDLE_NAME_TYPE ( &efi_loaded_image_protocol_guid, + efi_loaded_image_filepath_name ), +}; + +/** + * Get name of an EFI handle + * + * @v handle EFI handle + * @ret text Name of handle, or NULL + */ +const char * efi_handle_name ( EFI_HANDLE handle ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + struct efi_handle_name_type *type; + unsigned int i; + void *interface; + const char *name; + EFI_STATUS efirc; + + /* Fail immediately for NULL handles */ + if ( ! handle ) + return NULL; + + /* Try each name type in turn */ + for ( i = 0 ; i < ( sizeof ( efi_handle_name_types ) / + sizeof ( efi_handle_name_types[0] ) ) ; i++ ) { + type = &efi_handle_name_types[i]; + DBG2 ( "<%d", i ); + + /* Try to open the applicable protocol */ + efirc = bs->OpenProtocol ( handle, type->protocol, &interface, + efi_image_handle, handle, + EFI_OPEN_PROTOCOL_GET_PROTOCOL ); + if ( efirc != 0 ) { + DBG2 ( ">" ); + continue; + } + + /* Try to get name from this protocol */ + DBG2 ( "-" ); + name = type->name ( interface ); + DBG2 ( "%c", ( name ? ( name[0] ? 'Y' : 'E' ) : 'N' ) ); + + /* Close protocol */ + bs->CloseProtocol ( handle, type->protocol, + efi_image_handle, handle ); + DBG2 ( ">" ); + + /* Use this name, if possible */ + if ( name && name[0] ) + return name; + } + + return "UNKNOWN"; +} diff --git a/roms/ipxe/src/interface/efi/efi_download.c b/roms/ipxe/src/interface/efi/efi_download.c new file mode 100644 index 00000000..1218852e --- /dev/null +++ b/roms/ipxe/src/interface/efi/efi_download.c @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2010 VMware, Inc. All Rights Reserved. + * + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <ipxe/open.h> +#include <ipxe/process.h> +#include <ipxe/iobuf.h> +#include <ipxe/xfer.h> +#include <ipxe/efi/efi.h> +#include <ipxe/efi/efi_snp.h> +#include <ipxe/efi/efi_download.h> + +/** iPXE download protocol GUID */ +static EFI_GUID ipxe_download_protocol_guid + = IPXE_DOWNLOAD_PROTOCOL_GUID; + +/** A single in-progress file */ +struct efi_download_file { + /** Data transfer interface that provides downloaded data */ + struct interface xfer; + + /** Current file position */ + size_t pos; + + /** Data callback */ + IPXE_DOWNLOAD_DATA_CALLBACK data_callback; + + /** Finish callback */ + IPXE_DOWNLOAD_FINISH_CALLBACK finish_callback; + + /** Callback context */ + void *context; +}; + +/* xfer interface */ + +/** + * Transfer finished or was aborted + * + * @v file Data transfer file + * @v rc Reason for close + */ +static void efi_download_close ( struct efi_download_file *file, int rc ) { + + file->finish_callback ( file->context, EFIRC ( rc ) ); + + intf_shutdown ( &file->xfer, rc ); + + efi_snp_release(); +} + +/** + * Process received data + * + * @v file Data transfer file + * @v iobuf I/O buffer + * @v meta Data transfer metadata + * @ret rc Return status code + */ +static int efi_download_deliver_iob ( struct efi_download_file *file, + struct io_buffer *iobuf, + struct xfer_metadata *meta ) { + EFI_STATUS efirc; + size_t len = iob_len ( iobuf ); + int rc; + + /* Calculate new buffer position */ + if ( meta->flags & XFER_FL_ABS_OFFSET ) + file->pos = 0; + file->pos += meta->offset; + + /* Call out to the data handler */ + if ( ( efirc = file->data_callback ( file->context, iobuf->data, + len, file->pos ) ) != 0 ) { + rc = -EEFI ( efirc ); + goto err_callback; + } + + /* Update current buffer position */ + file->pos += len; + + /* Success */ + rc = 0; + + err_callback: + free_iob ( iobuf ); + return rc; +} + +/** Data transfer interface operations */ +static struct interface_operation efi_xfer_operations[] = { + INTF_OP ( xfer_deliver, struct efi_download_file *, efi_download_deliver_iob ), + INTF_OP ( intf_close, struct efi_download_file *, efi_download_close ), +}; + +/** EFI download data transfer interface descriptor */ +static struct interface_descriptor efi_download_file_xfer_desc = + INTF_DESC ( struct efi_download_file, xfer, efi_xfer_operations ); + +/** + * Start downloading a file, and register callback functions to handle the + * download. + * + * @v This iPXE Download Protocol instance + * @v Url URL to download from + * @v DataCallback Callback that will be invoked when data arrives + * @v FinishCallback Callback that will be invoked when the download ends + * @v Context Context passed to the Data and Finish callbacks + * @v File Token that can be used to abort the download + * @ret Status EFI status code + */ +static EFI_STATUS EFIAPI +efi_download_start ( IPXE_DOWNLOAD_PROTOCOL *This __unused, + CHAR8 *Url, + IPXE_DOWNLOAD_DATA_CALLBACK DataCallback, + IPXE_DOWNLOAD_FINISH_CALLBACK FinishCallback, + VOID *Context, + IPXE_DOWNLOAD_FILE *File ) { + struct efi_download_file *file; + int rc; + + file = malloc ( sizeof ( struct efi_download_file ) ); + if ( file == NULL ) { + return EFI_OUT_OF_RESOURCES; + } + + intf_init ( &file->xfer, &efi_download_file_xfer_desc, NULL ); + rc = xfer_open ( &file->xfer, LOCATION_URI_STRING, Url ); + if ( rc ) { + free ( file ); + return EFIRC ( rc ); + } + + efi_snp_claim(); + file->pos = 0; + file->data_callback = DataCallback; + file->finish_callback = FinishCallback; + file->context = Context; + *File = file; + return EFI_SUCCESS; +} + +/** + * Forcibly abort downloading a file that is currently in progress. + * + * It is not safe to call this function after the Finish callback has executed. + * + * @v This iPXE Download Protocol instance + * @v File Token obtained from Start + * @v Status Reason for aborting the download + * @ret Status EFI status code + */ +static EFI_STATUS EFIAPI +efi_download_abort ( IPXE_DOWNLOAD_PROTOCOL *This __unused, + IPXE_DOWNLOAD_FILE File, + EFI_STATUS Status ) { + struct efi_download_file *file = File; + + efi_download_close ( file, -EEFI ( Status ) ); + return EFI_SUCCESS; +} + +/** + * Poll for more data from iPXE. This function will invoke the registered + * callbacks if data is available or if downloads complete. + * + * @v This iPXE Download Protocol instance + * @ret Status EFI status code + */ +static EFI_STATUS EFIAPI +efi_download_poll ( IPXE_DOWNLOAD_PROTOCOL *This __unused ) { + step(); + return EFI_SUCCESS; +} + +/** Publicly exposed iPXE download protocol */ +static IPXE_DOWNLOAD_PROTOCOL ipxe_download_protocol_interface = { + .Start = efi_download_start, + .Abort = efi_download_abort, + .Poll = efi_download_poll +}; + +/** + * Install iPXE download protocol + * + * @v handle EFI handle + * @ret rc Return status code + */ +int efi_download_install ( EFI_HANDLE handle ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + EFI_STATUS efirc; + int rc; + + efirc = bs->InstallMultipleProtocolInterfaces ( + &handle, + &ipxe_download_protocol_guid, + &ipxe_download_protocol_interface, + NULL ); + if ( efirc ) { + rc = -EEFI ( efirc ); + DBG ( "Could not install download protocol: %s\n", + strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Uninstall iPXE download protocol + * + * @v handle EFI handle + */ +void efi_download_uninstall ( EFI_HANDLE handle ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + + bs->UninstallMultipleProtocolInterfaces ( + handle, + &ipxe_download_protocol_guid, + &ipxe_download_protocol_interface, NULL ); +} diff --git a/roms/ipxe/src/interface/efi/efi_driver.c b/roms/ipxe/src/interface/efi/efi_driver.c new file mode 100644 index 00000000..ba7784cd --- /dev/null +++ b/roms/ipxe/src/interface/efi/efi_driver.c @@ -0,0 +1,517 @@ +/* + * Copyright (C) 2011 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. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include <stddef.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <ipxe/version.h> +#include <ipxe/efi/efi.h> +#include <ipxe/efi/Protocol/DriverBinding.h> +#include <ipxe/efi/Protocol/ComponentName2.h> +#include <ipxe/efi/Protocol/DevicePath.h> +#include <ipxe/efi/efi_strings.h> +#include <ipxe/efi/efi_driver.h> + +/** @file + * + * EFI driver interface + * + */ + +static EFI_DRIVER_BINDING_PROTOCOL efi_driver_binding; + +/** List of controlled EFI devices */ +static LIST_HEAD ( efi_devices ); + +/** + * Find EFI device + * + * @v device EFI device handle + * @ret efidev EFI device, or NULL if not found + */ +static struct efi_device * efidev_find ( EFI_HANDLE device ) { + struct efi_device *efidev; + + /* Look for an existing EFI device */ + list_for_each_entry ( efidev, &efi_devices, dev.siblings ) { + if ( efidev->device == device ) + return efidev; + } + + return NULL; +} + +/** + * Get parent EFI device + * + * @v dev Generic device + * @ret efidev Parent EFI device, or NULL + */ +struct efi_device * efidev_parent ( struct device *dev ) { + struct device *parent = dev->parent; + struct efi_device *efidev; + + /* Check that parent exists and is an EFI device */ + if ( ! parent ) + return NULL; + if ( parent->desc.bus_type != BUS_TYPE_EFI ) + return NULL; + + /* Get containing EFI device */ + efidev = container_of ( parent, struct efi_device, dev ); + return efidev; +} + +/** + * Check to see if driver supports a device + * + * @v driver EFI driver + * @v device EFI device + * @v child Path to child device, if any + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_driver_supported ( EFI_DRIVER_BINDING_PROTOCOL *driver __unused, + EFI_HANDLE device, EFI_DEVICE_PATH_PROTOCOL *child ) { + struct efi_driver *efidrv; + int rc; + + DBGCP ( device, "EFIDRV %p %s DRIVER_SUPPORTED", + device, efi_handle_name ( device ) ); + if ( child ) + DBGCP ( device, " (child %s)", efi_devpath_text ( child ) ); + DBGCP ( device, "\n" ); + + /* Do nothing if we are already driving this device */ + if ( efidev_find ( device ) != NULL ) { + DBGCP ( device, "EFIDRV %p %s is already started\n", + device, efi_handle_name ( device ) ); + return EFI_ALREADY_STARTED; + } + + /* Look for a driver claiming to support this device */ + for_each_table_entry ( efidrv, EFI_DRIVERS ) { + if ( ( rc = efidrv->supported ( device ) ) == 0 ) { + DBGC ( device, "EFIDRV %p %s has driver \"%s\"\n", + device, efi_handle_name ( device ), + efidrv->name ); + return 0; + } + } + DBGCP ( device, "EFIDRV %p %s has no driver\n", + device, efi_handle_name ( device ) ); + + return EFI_UNSUPPORTED; +} + +/** + * Attach driver to device + * + * @v driver EFI driver + * @v device EFI device + * @v child Path to child device, if any + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_driver_start ( EFI_DRIVER_BINDING_PROTOCOL *driver __unused, + EFI_HANDLE device, EFI_DEVICE_PATH_PROTOCOL *child ) { + struct efi_driver *efidrv; + struct efi_device *efidev; + EFI_STATUS efirc; + int rc; + + DBGC ( device, "EFIDRV %p %s DRIVER_START", + device, efi_handle_name ( device ) ); + if ( child ) + DBGC ( device, " (child %s)", efi_devpath_text ( child ) ); + DBGC ( device, "\n" ); + + /* Do nothing if we are already driving this device */ + efidev = efidev_find ( device ); + if ( efidev ) { + DBGCP ( device, "EFIDRV %p %s is already started\n", + device, efi_handle_name ( device ) ); + efirc = EFI_ALREADY_STARTED; + goto err_already_started; + } + + /* Allocate and initialise structure */ + efidev = zalloc ( sizeof ( *efidev ) ); + if ( ! efidev ) { + efirc = EFI_OUT_OF_RESOURCES; + goto err_alloc; + } + efidev->device = device; + efidev->dev.desc.bus_type = BUS_TYPE_EFI; + INIT_LIST_HEAD ( &efidev->dev.children ); + list_add ( &efidev->dev.siblings, &efi_devices ); + + /* Try to start this device */ + for_each_table_entry ( efidrv, EFI_DRIVERS ) { + if ( ( rc = efidrv->supported ( device ) ) != 0 ) { + DBGC ( device, "EFIDRV %p %s is not supported by " + "driver \"%s\": %s\n", device, + efi_handle_name ( device ), efidrv->name, + strerror ( rc ) ); + continue; + } + if ( ( rc = efidrv->start ( efidev ) ) == 0 ) { + efidev->driver = efidrv; + DBGC ( device, "EFIDRV %p %s using driver \"%s\"\n", + device, efi_handle_name ( device ), + efidev->driver->name ); + return 0; + } + DBGC ( device, "EFIDRV %p %s could not start driver \"%s\": " + "%s\n", device, efi_handle_name ( device ), + efidrv->name, strerror ( rc ) ); + } + efirc = EFI_UNSUPPORTED; + + list_del ( &efidev->dev.siblings ); + free ( efidev ); + err_alloc: + err_already_started: + return efirc; +} + +/** + * Detach driver from device + * + * @v driver EFI driver + * @v device EFI device + * @v pci PCI device + * @v num_children Number of child devices + * @v children List of child devices + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_driver_stop ( EFI_DRIVER_BINDING_PROTOCOL *driver __unused, + EFI_HANDLE device, UINTN num_children, + EFI_HANDLE *children ) { + struct efi_driver *efidrv; + struct efi_device *efidev; + UINTN i; + + DBGC ( device, "EFIDRV %p %s DRIVER_STOP", + device, efi_handle_name ( device ) ); + for ( i = 0 ; i < num_children ; i++ ) { + DBGC ( device, "%s%p %s", ( i ? ", " : " child " ), + children[i], efi_handle_name ( children[i] ) ); + } + DBGC ( device, "\n" ); + + /* Do nothing unless we are driving this device */ + efidev = efidev_find ( device ); + if ( ! efidev ) { + DBGCP ( device, "EFIDRV %p %s is not started\n", + device, efi_handle_name ( device ) ); + return 0; + } + + /* Stop this device */ + efidrv = efidev->driver; + assert ( efidrv != NULL ); + efidrv->stop ( efidev ); + list_del ( &efidev->dev.siblings ); + free ( efidev ); + + return 0; +} + +/** EFI driver binding protocol */ +static EFI_DRIVER_BINDING_PROTOCOL efi_driver_binding = { + .Supported = efi_driver_supported, + .Start = efi_driver_start, + .Stop = efi_driver_stop, +}; + +/** + * Look up driver name + * + * @v wtf Component name protocol + * @v language Language to use + * @v driver_name Driver name to fill in + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_driver_name ( EFI_COMPONENT_NAME2_PROTOCOL *wtf __unused, + CHAR8 *language __unused, CHAR16 **driver_name ) { + const wchar_t *name; + + name = ( product_wname[0] ? product_wname : build_wname ); + *driver_name = ( ( wchar_t * ) name ); + return 0; +} + +/** + * Look up controller name + * + * @v wtf Component name protocol + * @v device Device + * @v child Child device, or NULL + * @v language Language to use + * @v driver_name Device name to fill in + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_driver_controller_name ( EFI_COMPONENT_NAME2_PROTOCOL *wtf __unused, + EFI_HANDLE device, EFI_HANDLE child, + CHAR8 *language, CHAR16 **controller_name ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + union { + EFI_COMPONENT_NAME2_PROTOCOL *name2; + void *interface; + } name2; + EFI_STATUS efirc; + + /* Delegate to the EFI_COMPONENT_NAME2_PROTOCOL instance + * installed on child handle, if present. + */ + if ( ( child != NULL ) && + ( ( efirc = bs->OpenProtocol ( + child, &efi_component_name2_protocol_guid, + &name2.interface, NULL, NULL, + EFI_OPEN_PROTOCOL_GET_PROTOCOL ) ) == 0 ) ) { + return name2.name2->GetControllerName ( name2.name2, device, + child, language, + controller_name ); + } + + /* Otherwise, let EFI use the default Device Path Name */ + return EFI_UNSUPPORTED; +} + +/** EFI component name protocol */ +static EFI_COMPONENT_NAME2_PROTOCOL efi_wtf = { + .GetDriverName = efi_driver_name, + .GetControllerName = efi_driver_controller_name, + .SupportedLanguages = "en", +}; + +/** + * Install EFI driver + * + * @ret rc Return status code + */ +int efi_driver_install ( void ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + EFI_STATUS efirc; + int rc; + + /* Calculate driver version number. We use the build + * timestamp (in seconds since the Epoch) shifted right by six + * bits: this gives us an approximately one-minute resolution + * and a scheme which will last until the year 10680. + */ + efi_driver_binding.Version = ( build_timestamp >> 6 ); + + /* Install protocols on image handle */ + efi_driver_binding.ImageHandle = efi_image_handle; + efi_driver_binding.DriverBindingHandle = efi_image_handle; + if ( ( efirc = bs->InstallMultipleProtocolInterfaces ( + &efi_image_handle, + &efi_driver_binding_protocol_guid, &efi_driver_binding, + &efi_component_name2_protocol_guid, &efi_wtf, + NULL ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( &efi_driver_binding, "EFIDRV could not install " + "protocols: %s\n", strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Uninstall EFI driver + * + */ +void efi_driver_uninstall ( void ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + + /* Uninstall protocols */ + bs->UninstallMultipleProtocolInterfaces ( + efi_image_handle, + &efi_driver_binding_protocol_guid, &efi_driver_binding, + &efi_component_name2_protocol_guid, &efi_wtf, NULL ); +} + +/** + * Try to connect EFI driver + * + * @v device EFI device + * @ret rc Return status code + */ +static int efi_driver_connect ( EFI_HANDLE device ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + EFI_HANDLE drivers[2] = + { efi_driver_binding.DriverBindingHandle, NULL }; + EFI_STATUS efirc; + int rc; + + /* Check if we want to drive this device */ + if ( ( efirc = efi_driver_supported ( &efi_driver_binding, device, + NULL ) ) != 0 ) { + /* Not supported; not an error */ + return 0; + } + + /* Disconnect any existing drivers */ + DBGC2 ( device, "EFIDRV %p %s before disconnecting:\n", + device, efi_handle_name ( device ) ); + DBGC2_EFI_PROTOCOLS ( device, device ); + DBGC ( device, "EFIDRV %p %s disconnecting existing drivers\n", + device, efi_handle_name ( device ) ); + if ( ( efirc = bs->DisconnectController ( device, NULL, + NULL ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( device, "EFIDRV %p %s could not disconnect existing " + "drivers: %s\n", device, efi_handle_name ( device ), + strerror ( rc ) ); + /* Ignore the error and attempt to connect our drivers */ + } + DBGC2 ( device, "EFIDRV %p %s after disconnecting:\n", + device, efi_handle_name ( device ) ); + DBGC2_EFI_PROTOCOLS ( device, device ); + + /* Connect our driver */ + DBGC ( device, "EFIDRV %p %s connecting new drivers\n", + device, efi_handle_name ( device ) ); + if ( ( efirc = bs->ConnectController ( device, drivers, NULL, + FALSE ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( device, "EFIDRV %p %s could not connect new drivers: " + "%s\n", device, efi_handle_name ( device ), + strerror ( rc ) ); + return rc; + } + DBGC2 ( device, "EFIDRV %p %s after connecting:\n", + device, efi_handle_name ( device ) ); + DBGC2_EFI_PROTOCOLS ( device, device ); + + return 0; +} + +/** + * Try to disconnect EFI driver + * + * @v device EFI device + * @ret rc Return status code + */ +static int efi_driver_disconnect ( EFI_HANDLE device ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + + /* Disconnect our driver */ + bs->DisconnectController ( device, + efi_driver_binding.DriverBindingHandle, + NULL ); + return 0; +} + +/** + * Reconnect original EFI driver + * + * @v device EFI device + * @ret rc Return status code + */ +static int efi_driver_reconnect ( EFI_HANDLE device ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + + /* Reconnect any available driver */ + bs->ConnectController ( device, NULL, NULL, FALSE ); + + return 0; +} + +/** + * Connect/disconnect EFI driver from all handles + * + * @v method Connect/disconnect method + * @ret rc Return status code + */ +static int efi_driver_handles ( int ( * method ) ( EFI_HANDLE handle ) ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + EFI_HANDLE *handles; + UINTN num_handles; + EFI_STATUS efirc; + UINTN i; + int rc; + + /* Enumerate all handles */ + if ( ( efirc = bs->LocateHandleBuffer ( AllHandles, NULL, NULL, + &num_handles, + &handles ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( &efi_driver_binding, "EFIDRV could not list handles: " + "%s\n", strerror ( rc ) ); + goto err_locate; + } + + /* Connect/disconnect driver from all handles */ + for ( i = 0 ; i < num_handles ; i++ ) { + if ( ( rc = method ( handles[i] ) ) != 0 ) + goto err_method; + } + + /* Success */ + rc = 0; + + err_method: + bs->FreePool ( handles ); + err_locate: + return rc; +} + +/** + * Connect EFI driver to all possible devices + * + * @ret rc Return status code + */ +int efi_driver_connect_all ( void ) { + + DBGC ( &efi_driver_binding, "EFIDRV connecting our drivers\n" ); + return efi_driver_handles ( efi_driver_connect ); +} + +/** + * Disconnect EFI driver from all possible devices + * + * @ret rc Return status code + */ +void efi_driver_disconnect_all ( void ) { + + DBGC ( &efi_driver_binding, "EFIDRV disconnecting our drivers\n" ); + efi_driver_handles ( efi_driver_disconnect ); +} + +/** + * Reconnect original EFI drivers to all possible devices + * + * @ret rc Return status code + */ +void efi_driver_reconnect_all ( void ) { + + DBGC ( &efi_driver_binding, "EFIDRV reconnecting old drivers\n" ); + efi_driver_handles ( efi_driver_reconnect ); +} diff --git a/roms/ipxe/src/interface/efi/efi_file.c b/roms/ipxe/src/interface/efi/efi_file.c new file mode 100644 index 00000000..2ef3c573 --- /dev/null +++ b/roms/ipxe/src/interface/efi/efi_file.c @@ -0,0 +1,714 @@ +/* + * Copyright (C) 2013 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. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +/** + * @file + * + * EFI file protocols + * + */ + +#include <stddef.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <strings.h> +#include <errno.h> +#include <wchar.h> +#include <ipxe/image.h> +#include <ipxe/efi/efi.h> +#include <ipxe/efi/Protocol/SimpleFileSystem.h> +#include <ipxe/efi/Protocol/BlockIo.h> +#include <ipxe/efi/Protocol/DiskIo.h> +#include <ipxe/efi/Guid/FileInfo.h> +#include <ipxe/efi/Guid/FileSystemInfo.h> +#include <ipxe/efi/efi_strings.h> +#include <ipxe/efi/efi_file.h> + +/** EFI file information GUID */ +static EFI_GUID efi_file_info_id = EFI_FILE_INFO_ID; + +/** EFI file system information GUID */ +static EFI_GUID efi_file_system_info_id = EFI_FILE_SYSTEM_INFO_ID; + +/** EFI media ID */ +#define EFI_MEDIA_ID_MAGIC 0x69505845 + +/** An image exposed as an EFI file */ +struct efi_file { + /** EFI file protocol */ + EFI_FILE_PROTOCOL file; + /** Image */ + struct image *image; + /** Current file position */ + size_t pos; +}; + +static struct efi_file efi_file_root; + +/** + * Get EFI file name (for debugging) + * + * @v file EFI file + * @ret name Name + */ +static const char * efi_file_name ( struct efi_file *file ) { + + return ( file->image ? file->image->name : "<root>" ); +} + +/** + * Find EFI file image + * + * @v wname Filename + * @ret image Image, or NULL + */ +static struct image * efi_file_find ( const CHAR16 *wname ) { + char name[ wcslen ( wname ) + 1 /* NUL */ ]; + struct image *image; + + /* Find image */ + snprintf ( name, sizeof ( name ), "%ls", wname ); + list_for_each_entry ( image, &images, list ) { + if ( strcasecmp ( image->name, name ) == 0 ) + return image; + } + + return NULL; + +} + +/** + * Open file + * + * @v this EFI file + * @ret new New EFI file + * @v wname Filename + * @v mode File mode + * @v attributes File attributes (for newly-created files) + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_file_open ( EFI_FILE_PROTOCOL *this, EFI_FILE_PROTOCOL **new, + CHAR16 *wname, UINT64 mode __unused, + UINT64 attributes __unused ) { + struct efi_file *file = container_of ( this, struct efi_file, file ); + struct efi_file *new_file; + struct image *image; + + /* Initial '\' indicates opening from the root directory */ + while ( *wname == L'\\' ) { + file = &efi_file_root; + wname++; + } + + /* Allow root directory itself to be opened */ + if ( ( wname[0] == L'\0' ) || ( wname[0] == L'.' ) ) { + *new = &efi_file_root.file; + return 0; + } + + /* Fail unless opening from the root */ + if ( file->image ) { + DBGC ( file, "EFIFILE %s is not a directory\n", + efi_file_name ( file ) ); + return EFI_NOT_FOUND; + } + + /* Identify image */ + image = efi_file_find ( wname ); + if ( ! image ) { + DBGC ( file, "EFIFILE \"%ls\" does not exist\n", wname ); + return EFI_NOT_FOUND; + } + + /* Fail unless opening read-only */ + if ( mode != EFI_FILE_MODE_READ ) { + DBGC ( file, "EFIFILE %s cannot be opened in mode %#08llx\n", + image->name, mode ); + return EFI_WRITE_PROTECTED; + } + + /* Allocate and initialise file */ + new_file = zalloc ( sizeof ( *new_file ) ); + memcpy ( &new_file->file, &efi_file_root.file, + sizeof ( new_file->file ) ); + new_file->image = image_get ( image ); + *new = &new_file->file; + DBGC ( new_file, "EFIFILE %s opened\n", efi_file_name ( new_file ) ); + + return 0; +} + +/** + * Close file + * + * @v this EFI file + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI efi_file_close ( EFI_FILE_PROTOCOL *this ) { + struct efi_file *file = container_of ( this, struct efi_file, file ); + + /* Do nothing if this is the root */ + if ( ! file->image ) + return 0; + + /* Close file */ + DBGC ( file, "EFIFILE %s closed\n", efi_file_name ( file ) ); + image_put ( file->image ); + free ( file ); + + return 0; +} + +/** + * Close and delete file + * + * @v this EFI file + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI efi_file_delete ( EFI_FILE_PROTOCOL *this ) { + struct efi_file *file = container_of ( this, struct efi_file, file ); + + DBGC ( file, "EFIFILE %s cannot be deleted\n", efi_file_name ( file ) ); + + /* Close file */ + efi_file_close ( this ); + + /* Warn of failure to delete */ + return EFI_WARN_DELETE_FAILURE; +} + +/** + * Return variable-length data structure + * + * @v base Base data structure (starting with UINT64) + * @v base_len Length of base data structure + * @v name Name to append to base data structure + * @v len Length of data buffer + * @v data Data buffer + * @ret efirc EFI status code + */ +static EFI_STATUS efi_file_varlen ( UINT64 *base, size_t base_len, + const char *name, UINTN *len, VOID *data ) { + size_t name_len; + + /* Calculate structure length */ + name_len = strlen ( name ); + *base = ( base_len + ( name_len + 1 /* NUL */ ) * sizeof ( wchar_t ) ); + if ( *len < *base ) { + *len = *base; + return EFI_BUFFER_TOO_SMALL; + } + + /* Copy data to buffer */ + *len = *base; + memcpy ( data, base, base_len ); + efi_snprintf ( ( data + base_len ), ( name_len + 1 /* NUL */ ), + "%s", name ); + + return 0; +} + +/** + * Return file information structure + * + * @v image Image, or NULL for the root directory + * @v len Length of data buffer + * @v data Data buffer + * @ret efirc EFI status code + */ +static EFI_STATUS efi_file_info ( struct image *image, UINTN *len, + VOID *data ) { + EFI_FILE_INFO info; + const char *name; + + /* Populate file information */ + memset ( &info, 0, sizeof ( info ) ); + if ( image ) { + info.FileSize = image->len; + info.PhysicalSize = image->len; + info.Attribute = EFI_FILE_READ_ONLY; + name = image->name; + } else { + info.Attribute = ( EFI_FILE_READ_ONLY | EFI_FILE_DIRECTORY ); + name = ""; + } + + return efi_file_varlen ( &info.Size, SIZE_OF_EFI_FILE_INFO, name, + len, data ); +} + +/** + * Read directory entry + * + * @v file EFI file + * @v len Length to read + * @v data Data buffer + * @ret efirc EFI status code + */ +static EFI_STATUS efi_file_read_dir ( struct efi_file *file, UINTN *len, + VOID *data ) { + EFI_STATUS efirc; + struct image *image; + unsigned int index; + + /* Construct directory entry at current position */ + index = file->pos; + for_each_image ( image ) { + if ( index-- == 0 ) { + efirc = efi_file_info ( image, len, data ); + if ( efirc == 0 ) + file->pos++; + return efirc; + } + } + + /* No more entries */ + *len = 0; + return 0; +} + +/** + * Read from file + * + * @v this EFI file + * @v len Length to read + * @v data Data buffer + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI efi_file_read ( EFI_FILE_PROTOCOL *this, + UINTN *len, VOID *data ) { + struct efi_file *file = container_of ( this, struct efi_file, file ); + size_t remaining; + + /* If this is the root directory, then construct a directory entry */ + if ( ! file->image ) + return efi_file_read_dir ( file, len, data ); + + /* Read from the file */ + remaining = ( file->image->len - file->pos ); + if ( *len > remaining ) + *len = remaining; + DBGC ( file, "EFIFILE %s read [%#08zx,%#08zx)\n", + efi_file_name ( file ), file->pos, + ( ( size_t ) ( file->pos + *len ) ) ); + copy_from_user ( data, file->image->data, file->pos, *len ); + file->pos += *len; + return 0; +} + +/** + * Write to file + * + * @v this EFI file + * @v len Length to write + * @v data Data buffer + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI efi_file_write ( EFI_FILE_PROTOCOL *this, + UINTN *len, VOID *data __unused ) { + struct efi_file *file = container_of ( this, struct efi_file, file ); + + DBGC ( file, "EFIFILE %s cannot write [%#08zx, %#08zx)\n", + efi_file_name ( file ), file->pos, + ( ( size_t ) ( file->pos + *len ) ) ); + return EFI_WRITE_PROTECTED; +} + +/** + * Set file position + * + * @v this EFI file + * @v position New file position + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI efi_file_set_position ( EFI_FILE_PROTOCOL *this, + UINT64 position ) { + struct efi_file *file = container_of ( this, struct efi_file, file ); + + /* If this is the root directory, reset to the start */ + if ( ! file->image ) { + DBGC ( file, "EFIFILE root directory rewound\n" ); + file->pos = 0; + return 0; + } + + /* Check for the magic end-of-file value */ + if ( position == 0xffffffffffffffffULL ) + position = file->image->len; + + /* Fail if we attempt to seek past the end of the file (since + * we do not support writes). + */ + if ( position > file->image->len ) { + DBGC ( file, "EFIFILE %s cannot seek to %#08llx of %#08zx\n", + efi_file_name ( file ), position, file->image->len ); + return EFI_UNSUPPORTED; + } + + /* Set position */ + file->pos = position; + DBGC ( file, "EFIFILE %s position set to %#08zx\n", + efi_file_name ( file ), file->pos ); + + return 0; +} + +/** + * Get file position + * + * @v this EFI file + * @ret position New file position + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI efi_file_get_position ( EFI_FILE_PROTOCOL *this, + UINT64 *position ) { + struct efi_file *file = container_of ( this, struct efi_file, file ); + + *position = file->pos; + return 0; +} + +/** + * Get file information + * + * @v this EFI file + * @v type Type of information + * @v len Buffer size + * @v data Buffer + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI efi_file_get_info ( EFI_FILE_PROTOCOL *this, + EFI_GUID *type, + UINTN *len, VOID *data ) { + struct efi_file *file = container_of ( this, struct efi_file, file ); + EFI_FILE_SYSTEM_INFO fsinfo; + struct image *image; + + /* Determine information to return */ + if ( memcmp ( type, &efi_file_info_id, sizeof ( *type ) ) == 0 ) { + + /* Get file information */ + DBGC ( file, "EFIFILE %s get file information\n", + efi_file_name ( file ) ); + return efi_file_info ( file->image, len, data ); + + } else if ( memcmp ( type, &efi_file_system_info_id, + sizeof ( *type ) ) == 0 ) { + + /* Get file system information */ + DBGC ( file, "EFIFILE %s get file system information\n", + efi_file_name ( file ) ); + memset ( &fsinfo, 0, sizeof ( fsinfo ) ); + fsinfo.ReadOnly = 1; + for_each_image ( image ) + fsinfo.VolumeSize += image->len; + return efi_file_varlen ( &fsinfo.Size, + SIZE_OF_EFI_FILE_SYSTEM_INFO, "iPXE", + len, data ); + } else { + + DBGC ( file, "EFIFILE %s cannot get information of type %s\n", + efi_file_name ( file ), efi_guid_ntoa ( type ) ); + return EFI_UNSUPPORTED; + } +} + +/** + * Set file information + * + * @v this EFI file + * @v type Type of information + * @v len Buffer size + * @v data Buffer + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_file_set_info ( EFI_FILE_PROTOCOL *this, EFI_GUID *type, + UINTN len __unused, VOID *data __unused ) { + struct efi_file *file = container_of ( this, struct efi_file, file ); + + DBGC ( file, "EFIFILE %s cannot set information of type %s\n", + efi_file_name ( file ), efi_guid_ntoa ( type ) ); + return EFI_WRITE_PROTECTED; +} + +/** + * Flush file modified data + * + * @v this EFI file + * @v type Type of information + * @v len Buffer size + * @v data Buffer + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI efi_file_flush ( EFI_FILE_PROTOCOL *this ) { + struct efi_file *file = container_of ( this, struct efi_file, file ); + + DBGC ( file, "EFIFILE %s flushed\n", efi_file_name ( file ) ); + return 0; +} + +/** Root directory */ +static struct efi_file efi_file_root = { + .file = { + .Revision = EFI_FILE_PROTOCOL_REVISION, + .Open = efi_file_open, + .Close = efi_file_close, + .Delete = efi_file_delete, + .Read = efi_file_read, + .Write = efi_file_write, + .GetPosition = efi_file_get_position, + .SetPosition = efi_file_set_position, + .GetInfo = efi_file_get_info, + .SetInfo = efi_file_set_info, + .Flush = efi_file_flush, + }, + .image = NULL, +}; + +/** + * Open root directory + * + * @v filesystem EFI simple file system + * @ret file EFI file handle + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_file_open_volume ( EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *filesystem __unused, + EFI_FILE_PROTOCOL **file ) { + + DBGC ( &efi_file_root, "EFIFILE open volume\n" ); + *file = &efi_file_root.file; + return 0; +} + +/** EFI simple file system protocol */ +static EFI_SIMPLE_FILE_SYSTEM_PROTOCOL efi_simple_file_system_protocol = { + .Revision = EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_REVISION, + .OpenVolume = efi_file_open_volume, +}; + +/** Dummy block I/O reset */ +static EFI_STATUS EFIAPI +efi_block_io_reset ( EFI_BLOCK_IO_PROTOCOL *this __unused, BOOLEAN extended ) { + + DBGC ( &efi_file_root, "EFIFILE block %sreset\n", + ( extended ? "extended " : "" ) ); + return 0; +} + +/** Dummy block I/O read */ +static EFI_STATUS EFIAPI +efi_block_io_read_blocks ( EFI_BLOCK_IO_PROTOCOL *this __unused, UINT32 MediaId, + EFI_LBA lba, UINTN len, VOID *data ) { + + DBGC ( &efi_file_root, "EFIFILE block read ID %#08x LBA %#08llx -> " + "%p+%zx\n", MediaId, ( ( unsigned long long ) lba ), + data, ( ( size_t ) len ) ); + return EFI_NO_MEDIA; +} + +/** Dummy block I/O write */ +static EFI_STATUS EFIAPI +efi_block_io_write_blocks ( EFI_BLOCK_IO_PROTOCOL *this __unused, + UINT32 MediaId, EFI_LBA lba, UINTN len, + VOID *data ) { + + DBGC ( &efi_file_root, "EFIFILE block write ID %#08x LBA %#08llx <- " + "%p+%zx\n", MediaId, ( ( unsigned long long ) lba ), + data, ( ( size_t ) len ) ); + return EFI_NO_MEDIA; +} + +/** Dummy block I/O flush */ +static EFI_STATUS EFIAPI +efi_block_io_flush_blocks ( EFI_BLOCK_IO_PROTOCOL *this __unused ) { + + DBGC ( &efi_file_root, "EFIFILE block flush\n" ); + return 0; +} + +/** Dummy block I/O media */ +static EFI_BLOCK_IO_MEDIA efi_block_io_media = { + .MediaId = EFI_MEDIA_ID_MAGIC, + .MediaPresent = TRUE, + .ReadOnly = TRUE, + .BlockSize = 1, +}; + +/** Dummy EFI block I/O protocol */ +static EFI_BLOCK_IO_PROTOCOL efi_block_io_protocol = { + .Revision = EFI_BLOCK_IO_PROTOCOL_REVISION, + .Media = &efi_block_io_media, + .Reset = efi_block_io_reset, + .ReadBlocks = efi_block_io_read_blocks, + .WriteBlocks = efi_block_io_write_blocks, + .FlushBlocks = efi_block_io_flush_blocks, +}; + +/** Dummy disk I/O read */ +static EFI_STATUS EFIAPI +efi_disk_io_read_disk ( EFI_DISK_IO_PROTOCOL *this __unused, UINT32 MediaId, + UINT64 offset, UINTN len, VOID *data ) { + + DBGC ( &efi_file_root, "EFIFILE disk read ID %#08x offset %#08llx -> " + "%p+%zx\n", MediaId, ( ( unsigned long long ) offset ), + data, ( ( size_t ) len ) ); + return EFI_NO_MEDIA; +} + +/** Dummy disk I/O write */ +static EFI_STATUS EFIAPI +efi_disk_io_write_disk ( EFI_DISK_IO_PROTOCOL *this __unused, UINT32 MediaId, + UINT64 offset, UINTN len, VOID *data ) { + + DBGC ( &efi_file_root, "EFIFILE disk write ID %#08x offset %#08llx <- " + "%p+%zx\n", MediaId, ( ( unsigned long long ) offset ), + data, ( ( size_t ) len ) ); + return EFI_NO_MEDIA; +} + +/** Dummy EFI disk I/O protocol */ +static EFI_DISK_IO_PROTOCOL efi_disk_io_protocol = { + .Revision = EFI_DISK_IO_PROTOCOL_REVISION, + .ReadDisk = efi_disk_io_read_disk, + .WriteDisk = efi_disk_io_write_disk, +}; + +/** + * Install EFI simple file system protocol + * + * @v handle EFI handle + * @ret rc Return status code + */ +int efi_file_install ( EFI_HANDLE handle ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + union { + EFI_DISK_IO_PROTOCOL *diskio; + void *interface; + } diskio; + EFI_STATUS efirc; + int rc; + + /* Install the simple file system protocol, block I/O + * protocol, and disk I/O protocol. We don't have a block + * device, but large parts of the EDK2 codebase make the + * assumption that file systems are normally attached to block + * devices, and so we create a dummy block device on the same + * handle just to keep things looking normal. + */ + if ( ( efirc = bs->InstallMultipleProtocolInterfaces ( + &handle, + &efi_block_io_protocol_guid, + &efi_block_io_protocol, + &efi_disk_io_protocol_guid, + &efi_disk_io_protocol, + &efi_simple_file_system_protocol_guid, + &efi_simple_file_system_protocol, NULL ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( handle, "Could not install simple file system " + "protocols: %s\n", strerror ( rc ) ); + goto err_install; + } + + /* The FAT filesystem driver has a bug: if a block device + * contains no FAT filesystem but does have an + * EFI_SIMPLE_FILE_SYSTEM_PROTOCOL instance, the FAT driver + * will assume that it must have previously installed the + * EFI_SIMPLE_FILE_SYSTEM_PROTOCOL. This causes the FAT + * driver to claim control of our device, and to refuse to + * stop driving it, which prevents us from later uninstalling + * correctly. + * + * Work around this bug by opening the disk I/O protocol + * ourselves, thereby preventing the FAT driver from opening + * it. + * + * Note that the alternative approach of opening the block I/O + * protocol (and thereby in theory preventing DiskIo from + * attaching to the block I/O protocol) causes an endless loop + * of calls to our DRIVER_STOP method when starting the EFI + * shell. I have no idea why this is. + */ + if ( ( efirc = bs->OpenProtocol ( handle, &efi_disk_io_protocol_guid, + &diskio.interface, efi_image_handle, + handle, + EFI_OPEN_PROTOCOL_BY_DRIVER ) ) != 0){ + rc = -EEFI ( efirc ); + DBGC ( handle, "Could not open disk I/O protocol: %s\n", + strerror ( rc ) ); + DBGC_EFI_OPENERS ( handle, handle, &efi_disk_io_protocol_guid ); + goto err_open; + } + assert ( diskio.diskio == &efi_disk_io_protocol ); + + return 0; + + bs->CloseProtocol ( handle, &efi_disk_io_protocol_guid, + efi_image_handle, handle ); + err_open: + bs->UninstallMultipleProtocolInterfaces ( + handle, + &efi_simple_file_system_protocol_guid, + &efi_simple_file_system_protocol, + &efi_disk_io_protocol_guid, + &efi_disk_io_protocol, + &efi_block_io_protocol_guid, + &efi_block_io_protocol, NULL ); + err_install: + return rc; +} + +/** + * Uninstall EFI simple file system protocol + * + * @v handle EFI handle + */ +void efi_file_uninstall ( EFI_HANDLE handle ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + EFI_STATUS efirc; + int rc; + + /* Close our own disk I/O protocol */ + bs->CloseProtocol ( handle, &efi_disk_io_protocol_guid, + efi_image_handle, handle ); + + /* We must install the file system protocol first, since + * otherwise the EDK2 code will attempt to helpfully uninstall + * it when the block I/O protocol is uninstalled, leading to a + * system lock-up. + */ + if ( ( efirc = bs->UninstallMultipleProtocolInterfaces ( + handle, + &efi_simple_file_system_protocol_guid, + &efi_simple_file_system_protocol, + &efi_disk_io_protocol_guid, + &efi_disk_io_protocol, + &efi_block_io_protocol_guid, + &efi_block_io_protocol, NULL ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( handle, "Could not uninstall simple file system " + "protocols: %s\n", strerror ( rc ) ); + /* Oh dear */ + } +} diff --git a/roms/ipxe/src/interface/efi/efi_guid.c b/roms/ipxe/src/interface/efi/efi_guid.c new file mode 100644 index 00000000..52ba58ae --- /dev/null +++ b/roms/ipxe/src/interface/efi/efi_guid.c @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2014 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. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include <ipxe/efi/efi.h> +#include <ipxe/efi/Protocol/Arp.h> +#include <ipxe/efi/Protocol/BlockIo.h> +#include <ipxe/efi/Protocol/BusSpecificDriverOverride.h> +#include <ipxe/efi/Protocol/ComponentName.h> +#include <ipxe/efi/Protocol/ComponentName2.h> +#include <ipxe/efi/Protocol/DevicePath.h> +#include <ipxe/efi/Protocol/DevicePathToText.h> +#include <ipxe/efi/Protocol/Dhcp4.h> +#include <ipxe/efi/Protocol/DiskIo.h> +#include <ipxe/efi/Protocol/DriverBinding.h> +#include <ipxe/efi/Protocol/GraphicsOutput.h> +#include <ipxe/efi/Protocol/HiiConfigAccess.h> +#include <ipxe/efi/Protocol/Ip4.h> +#include <ipxe/efi/Protocol/Ip4Config.h> +#include <ipxe/efi/Protocol/LoadFile.h> +#include <ipxe/efi/Protocol/LoadFile2.h> +#include <ipxe/efi/Protocol/LoadedImage.h> +#include <ipxe/efi/Protocol/ManagedNetwork.h> +#include <ipxe/efi/Protocol/Mtftp4.h> +#include <ipxe/efi/Protocol/NetworkInterfaceIdentifier.h> +#include <ipxe/efi/Protocol/PciIo.h> +#include <ipxe/efi/Protocol/PciRootBridgeIo.h> +#include <ipxe/efi/Protocol/PxeBaseCode.h> +#include <ipxe/efi/Protocol/SimpleFileSystem.h> +#include <ipxe/efi/Protocol/SimpleNetwork.h> +#include <ipxe/efi/Protocol/TcgService.h> +#include <ipxe/efi/Protocol/Tcp4.h> +#include <ipxe/efi/Protocol/Udp4.h> +#include <ipxe/efi/Protocol/VlanConfig.h> + +/** @file + * + * EFI GUIDs + * + */ + +/** ARP protocol GUID */ +EFI_GUID efi_arp_protocol_guid + = EFI_ARP_PROTOCOL_GUID; + +/** ARP service binding protocol GUID */ +EFI_GUID efi_arp_service_binding_protocol_guid + = EFI_ARP_SERVICE_BINDING_PROTOCOL_GUID; + +/** Block I/O protocol GUID */ +EFI_GUID efi_block_io_protocol_guid + = EFI_BLOCK_IO_PROTOCOL_GUID; + +/** Bus specific driver override protocol GUID */ +EFI_GUID efi_bus_specific_driver_override_protocol_guid + = EFI_BUS_SPECIFIC_DRIVER_OVERRIDE_PROTOCOL_GUID; + +/** Component name protocol GUID */ +EFI_GUID efi_component_name_protocol_guid + = EFI_COMPONENT_NAME_PROTOCOL_GUID; + +/** Component name 2 protocol GUID */ +EFI_GUID efi_component_name2_protocol_guid + = EFI_COMPONENT_NAME2_PROTOCOL_GUID; + +/** Device path protocol GUID */ +EFI_GUID efi_device_path_protocol_guid + = EFI_DEVICE_PATH_PROTOCOL_GUID; + +/** DHCPv4 protocol GUID */ +EFI_GUID efi_dhcp4_protocol_guid + = EFI_DHCP4_PROTOCOL_GUID; + +/** DHCPv4 service binding protocol GUID */ +EFI_GUID efi_dhcp4_service_binding_protocol_guid + = EFI_DHCP4_SERVICE_BINDING_PROTOCOL_GUID; + +/** Disk I/O protocol GUID */ +EFI_GUID efi_disk_io_protocol_guid + = EFI_DISK_IO_PROTOCOL_GUID; + +/** Driver binding protocol GUID */ +EFI_GUID efi_driver_binding_protocol_guid + = EFI_DRIVER_BINDING_PROTOCOL_GUID; + +/** Graphics output protocol GUID */ +EFI_GUID efi_graphics_output_protocol_guid + = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID; + +/** HII configuration access protocol GUID */ +EFI_GUID efi_hii_config_access_protocol_guid + = EFI_HII_CONFIG_ACCESS_PROTOCOL_GUID; + +/** IPv4 protocol GUID */ +EFI_GUID efi_ip4_protocol_guid + = EFI_IP4_PROTOCOL_GUID; + +/** IPv4 configuration protocol GUID */ +EFI_GUID efi_ip4_config_protocol_guid + = EFI_IP4_CONFIG_PROTOCOL_GUID; + +/** IPv4 service binding protocol GUID */ +EFI_GUID efi_ip4_service_binding_protocol_guid + = EFI_IP4_SERVICE_BINDING_PROTOCOL_GUID; + +/** Load file protocol GUID */ +EFI_GUID efi_load_file_protocol_guid + = EFI_LOAD_FILE_PROTOCOL_GUID; + +/** Load file 2 protocol GUID */ +EFI_GUID efi_load_file2_protocol_guid + = EFI_LOAD_FILE2_PROTOCOL_GUID; + +/** Loaded image protocol GUID */ +EFI_GUID efi_loaded_image_protocol_guid + = EFI_LOADED_IMAGE_PROTOCOL_GUID; + +/** Loaded image device path protocol GUID */ +EFI_GUID efi_loaded_image_device_path_protocol_guid + = EFI_LOADED_IMAGE_DEVICE_PATH_PROTOCOL_GUID; + +/** Managed network protocol GUID */ +EFI_GUID efi_managed_network_protocol_guid + = EFI_MANAGED_NETWORK_PROTOCOL_GUID; + +/** Managed network service binding protocol GUID */ +EFI_GUID efi_managed_network_service_binding_protocol_guid + = EFI_MANAGED_NETWORK_SERVICE_BINDING_PROTOCOL_GUID; + +/** MTFTPv4 protocol GUID */ +EFI_GUID efi_mtftp4_protocol_guid + = EFI_MTFTP4_PROTOCOL_GUID; + +/** MTFTPv4 service binding protocol GUID */ +EFI_GUID efi_mtftp4_service_binding_protocol_guid + = EFI_MTFTP4_SERVICE_BINDING_PROTOCOL_GUID; + +/** Network interface identifier protocol GUID (old version) */ +EFI_GUID efi_nii_protocol_guid + = EFI_NETWORK_INTERFACE_IDENTIFIER_PROTOCOL_GUID; + +/** Network interface identifier protocol GUID (new version) */ +EFI_GUID efi_nii31_protocol_guid + = EFI_NETWORK_INTERFACE_IDENTIFIER_PROTOCOL_GUID_31; + +/** PCI I/O protocol GUID */ +EFI_GUID efi_pci_io_protocol_guid + = EFI_PCI_IO_PROTOCOL_GUID; + +/** PCI root bridge I/O protocol GUID */ +EFI_GUID efi_pci_root_bridge_io_protocol_guid + = EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_GUID; + +/** PXE base code protocol GUID */ +EFI_GUID efi_pxe_base_code_protocol_guid + = EFI_PXE_BASE_CODE_PROTOCOL_GUID; + +/** Simple file system protocol GUID */ +EFI_GUID efi_simple_file_system_protocol_guid + = EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID; + +/** Simple network protocol GUID */ +EFI_GUID efi_simple_network_protocol_guid + = EFI_SIMPLE_NETWORK_PROTOCOL_GUID; + +/** TCG protocol GUID */ +EFI_GUID efi_tcg_protocol_guid + = EFI_TCG_PROTOCOL_GUID; + +/** TCPv4 protocol GUID */ +EFI_GUID efi_tcp4_protocol_guid + = EFI_TCP4_PROTOCOL_GUID; + +/** TCPv4 service binding protocol GUID */ +EFI_GUID efi_tcp4_service_binding_protocol_guid + = EFI_TCP4_SERVICE_BINDING_PROTOCOL_GUID; + +/** UDPv4 protocol GUID */ +EFI_GUID efi_udp4_protocol_guid + = EFI_UDP4_PROTOCOL_GUID; + +/** UDPv4 service binding protocol GUID */ +EFI_GUID efi_udp4_service_binding_protocol_guid + = EFI_UDP4_SERVICE_BINDING_PROTOCOL_GUID; + +/** VLAN configuration protocol GUID */ +EFI_GUID efi_vlan_config_protocol_guid + = EFI_VLAN_CONFIG_PROTOCOL_GUID; diff --git a/roms/ipxe/src/interface/efi/efi_hii.c b/roms/ipxe/src/interface/efi/efi_hii.c new file mode 100644 index 00000000..834060b5 --- /dev/null +++ b/roms/ipxe/src/interface/efi/efi_hii.c @@ -0,0 +1,577 @@ +/* + * Copyright (C) 2012 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. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include <stdlib.h> +#include <stddef.h> +#include <stdarg.h> +#include <string.h> +#include <ipxe/efi/efi.h> +#include <ipxe/efi/efi_strings.h> +#include <ipxe/efi/efi_hii.h> + +/** Tiano GUID */ +static const EFI_GUID tiano_guid = EFI_IFR_TIANO_GUID; + +/** + * Add string to IFR builder + * + * @v ifr IFR builder + * @v fmt Format string + * @v ... Arguments + * @ret string_id String identifier, or zero on failure + */ +unsigned int efi_ifr_string ( struct efi_ifr_builder *ifr, const char *fmt, + ... ) { + EFI_HII_STRING_BLOCK *new_strings; + EFI_HII_SIBT_STRING_UCS2_BLOCK *ucs2; + size_t new_strings_len; + va_list args; + size_t len; + unsigned int string_id; + + /* Do nothing if a previous allocation has failed */ + if ( ifr->failed ) + return 0; + + /* Calculate string length */ + va_start ( args, fmt ); + len = ( efi_vsnprintf ( NULL, 0, fmt, args ) + 1 /* wNUL */ ); + va_end ( args ); + + /* Reallocate strings */ + new_strings_len = ( ifr->strings_len + + offsetof ( typeof ( *ucs2 ), StringText ) + + ( len * sizeof ( ucs2->StringText[0] ) ) ); + new_strings = realloc ( ifr->strings, new_strings_len ); + if ( ! new_strings ) { + ifr->failed = 1; + return 0; + } + ucs2 = ( ( ( void * ) new_strings ) + ifr->strings_len ); + ifr->strings = new_strings; + ifr->strings_len = new_strings_len; + + /* Fill in string */ + ucs2->Header.BlockType = EFI_HII_SIBT_STRING_UCS2; + va_start ( args, fmt ); + efi_vsnprintf ( ucs2->StringText, len, fmt, args ); + va_end ( args ); + + /* Allocate string ID */ + string_id = ++(ifr->string_id); + + DBGC ( ifr, "IFR %p string %#04x is \"%ls\"\n", + ifr, string_id, ucs2->StringText ); + return string_id; +} + +/** + * Add IFR opcode to IFR builder + * + * @v ifr IFR builder + * @v opcode Opcode + * @v len Opcode length + * @ret op Opcode, or NULL + */ +static void * efi_ifr_op ( struct efi_ifr_builder *ifr, unsigned int opcode, + size_t len ) { + EFI_IFR_OP_HEADER *new_ops; + EFI_IFR_OP_HEADER *op; + size_t new_ops_len; + + /* Do nothing if a previous allocation has failed */ + if ( ifr->failed ) + return NULL; + + /* Reallocate opcodes */ + new_ops_len = ( ifr->ops_len + len ); + new_ops = realloc ( ifr->ops, new_ops_len ); + if ( ! new_ops ) { + ifr->failed = 1; + return NULL; + } + op = ( ( ( void * ) new_ops ) + ifr->ops_len ); + ifr->ops = new_ops; + ifr->ops_len = new_ops_len; + + /* Fill in opcode header */ + op->OpCode = opcode; + op->Length = len; + + return op; +} + +/** + * Add end opcode to IFR builder + * + * @v ifr IFR builder + */ +void efi_ifr_end_op ( struct efi_ifr_builder *ifr ) { + size_t dispaddr = ifr->ops_len; + EFI_IFR_END *end; + + /* Add opcode */ + end = efi_ifr_op ( ifr, EFI_IFR_END_OP, sizeof ( *end ) ); + + DBGC ( ifr, "IFR %p end\n", ifr ); + DBGC2_HDA ( ifr, dispaddr, end, sizeof ( *end ) ); +} + +/** + * Add false opcode to IFR builder + * + * @v ifr IFR builder + */ +void efi_ifr_false_op ( struct efi_ifr_builder *ifr ) { + size_t dispaddr = ifr->ops_len; + EFI_IFR_FALSE *false; + + /* Add opcode */ + false = efi_ifr_op ( ifr, EFI_IFR_FALSE_OP, sizeof ( *false ) ); + + DBGC ( ifr, "IFR %p false\n", ifr ); + DBGC2_HDA ( ifr, dispaddr, false, sizeof ( *false ) ); +} + +/** + * Add form opcode to IFR builder + * + * @v ifr IFR builder + * @v title_id Title string identifier + * @ret form_id Form identifier + */ +unsigned int efi_ifr_form_op ( struct efi_ifr_builder *ifr, + unsigned int title_id ) { + size_t dispaddr = ifr->ops_len; + EFI_IFR_FORM *form; + + /* Add opcode */ + form = efi_ifr_op ( ifr, EFI_IFR_FORM_OP, sizeof ( *form ) ); + if ( ! form ) + return 0; + form->Header.Scope = 1; + form->FormId = ++(ifr->form_id); + form->FormTitle = title_id; + + DBGC ( ifr, "IFR %p name/value store %#04x title %#04x\n", + ifr, form->FormId, title_id ); + DBGC2_HDA ( ifr, dispaddr, form, sizeof ( *form ) ); + return form->FormId; +} + +/** + * Add formset opcode to IFR builder + * + * @v ifr IFR builder + * @v guid GUID + * @v title_id Title string identifier + * @v help_id Help string identifier + * @v ... Class GUIDs (terminated by NULL) + */ +void efi_ifr_form_set_op ( struct efi_ifr_builder *ifr, const EFI_GUID *guid, + unsigned int title_id, unsigned int help_id, ... ) { + size_t dispaddr = ifr->ops_len; + EFI_IFR_FORM_SET *formset; + EFI_GUID *class_guid; + unsigned int num_class_guids = 0; + size_t len; + va_list args; + + /* Count number of class GUIDs */ + va_start ( args, help_id ); + while ( va_arg ( args, const EFI_GUID * ) != NULL ) + num_class_guids++; + va_end ( args ); + + /* Add opcode */ + len = ( sizeof ( *formset ) + + ( num_class_guids * sizeof ( *class_guid ) ) ); + formset = efi_ifr_op ( ifr, EFI_IFR_FORM_SET_OP, len ); + if ( ! formset ) + return; + formset->Header.Scope = 1; + memcpy ( &formset->Guid, guid, sizeof ( formset->Guid ) ); + formset->FormSetTitle = title_id; + formset->Help = help_id; + formset->Flags = num_class_guids; + + /* Add class GUIDs */ + class_guid = ( ( ( void * ) formset ) + sizeof ( *formset ) ); + va_start ( args, help_id ); + while ( num_class_guids-- ) { + memcpy ( class_guid++, va_arg ( args, const EFI_GUID * ), + sizeof ( *class_guid ) ); + } + va_end ( args ); + + DBGC ( ifr, "IFR %p formset title %#04x help %#04x\n", + ifr, title_id, help_id ); + DBGC2_HDA ( ifr, dispaddr, formset, len ); +} + +/** + * Add get opcode to IFR builder + * + * @v ifr IFR builder + * @v varstore_id Variable store identifier + * @v varstore_info Variable string identifier or offset + * @v varstore_type Variable type + */ +void efi_ifr_get_op ( struct efi_ifr_builder *ifr, unsigned int varstore_id, + unsigned int varstore_info, unsigned int varstore_type ) { + size_t dispaddr = ifr->ops_len; + EFI_IFR_GET *get; + + /* Add opcode */ + get = efi_ifr_op ( ifr, EFI_IFR_GET_OP, sizeof ( *get ) ); + get->VarStoreId = varstore_id; + get->VarStoreInfo.VarName = varstore_info; + get->VarStoreType = varstore_type; + + DBGC ( ifr, "IFR %p get varstore %#04x:%#04x type %#02x\n", + ifr, varstore_id, varstore_info, varstore_type ); + DBGC2_HDA ( ifr, dispaddr, get, sizeof ( *get ) ); +} + +/** + * Add GUID class opcode to IFR builder + * + * @v ifr IFR builder + * @v class Class + */ +void efi_ifr_guid_class_op ( struct efi_ifr_builder *ifr, unsigned int class ) { + size_t dispaddr = ifr->ops_len; + EFI_IFR_GUID_CLASS *guid_class; + + /* Add opcode */ + guid_class = efi_ifr_op ( ifr, EFI_IFR_GUID_OP, + sizeof ( *guid_class ) ); + if ( ! guid_class ) + return; + memcpy ( &guid_class->Guid, &tiano_guid, sizeof ( guid_class->Guid ) ); + guid_class->ExtendOpCode = EFI_IFR_EXTEND_OP_CLASS; + guid_class->Class = class; + + DBGC ( ifr, "IFR %p GUID class %#02x\n", ifr, class ); + DBGC2_HDA ( ifr, dispaddr, guid_class, sizeof ( *guid_class ) ); +} + +/** + * Add GUID subclass opcode to IFR builder + * + * @v ifr IFR builder + * @v subclass Subclass + */ +void efi_ifr_guid_subclass_op ( struct efi_ifr_builder *ifr, + unsigned int subclass ) { + size_t dispaddr = ifr->ops_len; + EFI_IFR_GUID_SUBCLASS *guid_subclass; + + /* Add opcode */ + guid_subclass = efi_ifr_op ( ifr, EFI_IFR_GUID_OP, + sizeof ( *guid_subclass ) ); + if ( ! guid_subclass ) + return; + memcpy ( &guid_subclass->Guid, &tiano_guid, + sizeof ( guid_subclass->Guid ) ); + guid_subclass->ExtendOpCode = EFI_IFR_EXTEND_OP_SUBCLASS; + guid_subclass->SubClass = subclass; + + DBGC ( ifr, "IFR %p GUID subclass %#02x\n", ifr, subclass ); + DBGC2_HDA ( ifr, dispaddr, guid_subclass, sizeof ( *guid_subclass ) ); +} + +/** + * Add numeric opcode to IFR builder + * + * @v ifr IFR builder + * @v prompt_id Prompt string identifier + * @v help_id Help string identifier + * @v question_id Question identifier + * @v varstore_id Variable store identifier + * @v varstore_info Variable string identifier or offset + * @v vflags Variable flags + * @v min_value Minimum value + * @v max_value Maximum value + * @v step Step + * @v flags Flags + */ +void efi_ifr_numeric_op ( struct efi_ifr_builder *ifr, unsigned int prompt_id, + unsigned int help_id, unsigned int question_id, + unsigned int varstore_id, unsigned int varstore_info, + unsigned int vflags, unsigned long min_value, + unsigned long max_value, unsigned int step, + unsigned int flags ) { + size_t dispaddr = ifr->ops_len; + EFI_IFR_NUMERIC *numeric; + unsigned int size; + + /* Add opcode */ + numeric = efi_ifr_op ( ifr, EFI_IFR_NUMERIC_OP, sizeof ( *numeric ) ); + if ( ! numeric ) + return; + numeric->Question.Header.Prompt = prompt_id; + numeric->Question.Header.Help = help_id; + numeric->Question.QuestionId = question_id; + numeric->Question.VarStoreId = varstore_id; + numeric->Question.VarStoreInfo.VarName = varstore_info; + numeric->Question.Flags = vflags; + size = ( flags & EFI_IFR_NUMERIC_SIZE ); + switch ( size ) { + case EFI_IFR_NUMERIC_SIZE_1 : + numeric->data.u8.MinValue = min_value; + numeric->data.u8.MaxValue = max_value; + numeric->data.u8.Step = step; + break; + case EFI_IFR_NUMERIC_SIZE_2 : + numeric->data.u16.MinValue = min_value; + numeric->data.u16.MaxValue = max_value; + numeric->data.u16.Step = step; + break; + case EFI_IFR_NUMERIC_SIZE_4 : + numeric->data.u32.MinValue = min_value; + numeric->data.u32.MaxValue = max_value; + numeric->data.u32.Step = step; + break; + case EFI_IFR_NUMERIC_SIZE_8 : + numeric->data.u64.MinValue = min_value; + numeric->data.u64.MaxValue = max_value; + numeric->data.u64.Step = step; + break; + } + + DBGC ( ifr, "IFR %p numeric prompt %#04x help %#04x question %#04x " + "varstore %#04x:%#04x\n", ifr, prompt_id, help_id, question_id, + varstore_id, varstore_info ); + DBGC2_HDA ( ifr, dispaddr, numeric, sizeof ( *numeric ) ); +} + +/** + * Add string opcode to IFR builder + * + * @v ifr IFR builder + * @v prompt_id Prompt string identifier + * @v help_id Help string identifier + * @v question_id Question identifier + * @v varstore_id Variable store identifier + * @v varstore_info Variable string identifier or offset + * @v vflags Variable flags + * @v min_size Minimum size + * @v max_size Maximum size + * @v flags Flags + */ +void efi_ifr_string_op ( struct efi_ifr_builder *ifr, unsigned int prompt_id, + unsigned int help_id, unsigned int question_id, + unsigned int varstore_id, unsigned int varstore_info, + unsigned int vflags, unsigned int min_size, + unsigned int max_size, unsigned int flags ) { + size_t dispaddr = ifr->ops_len; + EFI_IFR_STRING *string; + + /* Add opcode */ + string = efi_ifr_op ( ifr, EFI_IFR_STRING_OP, sizeof ( *string ) ); + if ( ! string ) + return; + string->Question.Header.Prompt = prompt_id; + string->Question.Header.Help = help_id; + string->Question.QuestionId = question_id; + string->Question.VarStoreId = varstore_id; + string->Question.VarStoreInfo.VarName = varstore_info; + string->Question.Flags = vflags; + string->MinSize = min_size; + string->MaxSize = max_size; + string->Flags = flags; + + DBGC ( ifr, "IFR %p string prompt %#04x help %#04x question %#04x " + "varstore %#04x:%#04x\n", ifr, prompt_id, help_id, question_id, + varstore_id, varstore_info ); + DBGC2_HDA ( ifr, dispaddr, string, sizeof ( *string ) ); +} + +/** + * Add suppress-if opcode to IFR builder + * + * @v ifr IFR builder + */ +void efi_ifr_suppress_if_op ( struct efi_ifr_builder *ifr ) { + size_t dispaddr = ifr->ops_len; + EFI_IFR_SUPPRESS_IF *suppress_if; + + /* Add opcode */ + suppress_if = efi_ifr_op ( ifr, EFI_IFR_SUPPRESS_IF_OP, + sizeof ( *suppress_if ) ); + suppress_if->Header.Scope = 1; + + DBGC ( ifr, "IFR %p suppress-if\n", ifr ); + DBGC2_HDA ( ifr, dispaddr, suppress_if, sizeof ( *suppress_if ) ); +} + +/** + * Add text opcode to IFR builder + * + * @v ifr IFR builder + * @v prompt_id Prompt string identifier + * @v help_id Help string identifier + * @v text_id Text string identifier + */ +void efi_ifr_text_op ( struct efi_ifr_builder *ifr, unsigned int prompt_id, + unsigned int help_id, unsigned int text_id ) { + size_t dispaddr = ifr->ops_len; + EFI_IFR_TEXT *text; + + /* Add opcode */ + text = efi_ifr_op ( ifr, EFI_IFR_TEXT_OP, sizeof ( *text ) ); + if ( ! text ) + return; + text->Statement.Prompt = prompt_id; + text->Statement.Help = help_id; + text->TextTwo = text_id; + + DBGC ( ifr, "IFR %p text prompt %#04x help %#04x text %#04x\n", + ifr, prompt_id, help_id, text_id ); + DBGC2_HDA ( ifr, dispaddr, text, sizeof ( *text ) ); +} + +/** + * Add true opcode to IFR builder + * + * @v ifr IFR builder + */ +void efi_ifr_true_op ( struct efi_ifr_builder *ifr ) { + size_t dispaddr = ifr->ops_len; + EFI_IFR_TRUE *true; + + /* Add opcode */ + true = efi_ifr_op ( ifr, EFI_IFR_TRUE_OP, sizeof ( *true ) ); + + DBGC ( ifr, "IFR %p true\n", ifr ); + DBGC2_HDA ( ifr, dispaddr, true, sizeof ( *true ) ); +} + +/** + * Add name/value store opcode to IFR builder + * + * @v ifr IFR builder + * @v guid GUID + * @ret varstore_id Variable store identifier, or 0 on failure + */ +unsigned int efi_ifr_varstore_name_value_op ( struct efi_ifr_builder *ifr, + const EFI_GUID *guid ) { + size_t dispaddr = ifr->ops_len; + EFI_IFR_VARSTORE_NAME_VALUE *varstore; + + /* Add opcode */ + varstore = efi_ifr_op ( ifr, EFI_IFR_VARSTORE_NAME_VALUE_OP, + sizeof ( *varstore ) ); + if ( ! varstore ) + return 0; + varstore->VarStoreId = ++(ifr->varstore_id); + memcpy ( &varstore->Guid, guid, sizeof ( varstore->Guid ) ); + + DBGC ( ifr, "IFR %p name/value store %#04x\n", + ifr, varstore->VarStoreId ); + DBGC2_HDA ( ifr, dispaddr, varstore, sizeof ( *varstore ) ); + return varstore->VarStoreId; +} + +/** + * Free memory used by IFR builder + * + * @v ifr IFR builder + */ +void efi_ifr_free ( struct efi_ifr_builder *ifr ) { + + free ( ifr->ops ); + free ( ifr->strings ); + memset ( ifr, 0, sizeof ( *ifr ) ); +} + +/** + * Construct package list from IFR builder + * + * @v ifr IFR builder + * @v guid Package GUID + * @v language Language + * @v language_id Language string ID + * @ret package Package list, or NULL + * + * The package list is allocated using malloc(), and must eventually + * be freed by the caller. (The caller must also call efi_ifr_free() + * to free the temporary storage used during construction.) + */ +EFI_HII_PACKAGE_LIST_HEADER * efi_ifr_package ( struct efi_ifr_builder *ifr, + const EFI_GUID *guid, + const char *language, + unsigned int language_id ) { + struct { + EFI_HII_PACKAGE_LIST_HEADER header; + struct { + EFI_HII_PACKAGE_HEADER header; + uint8_t data[ifr->ops_len]; + } __attribute__ (( packed )) ops; + struct { + union { + EFI_HII_STRING_PACKAGE_HDR header; + uint8_t pad[offsetof(EFI_HII_STRING_PACKAGE_HDR, + Language) + + strlen ( language ) + 1 /* NUL */ ]; + } __attribute__ (( packed )) header; + uint8_t data[ifr->strings_len]; + EFI_HII_STRING_BLOCK end; + } __attribute__ (( packed )) strings; + EFI_HII_PACKAGE_HEADER end; + } __attribute__ (( packed )) *package; + + /* Fail if any previous allocation failed */ + if ( ifr->failed ) + return NULL; + + /* Allocate package list */ + package = zalloc ( sizeof ( *package ) ); + if ( ! package ) + return NULL; + + /* Populate package list */ + package->header.PackageLength = sizeof ( *package ); + memcpy ( &package->header.PackageListGuid, guid, + sizeof ( package->header.PackageListGuid ) ); + package->ops.header.Length = sizeof ( package->ops ); + package->ops.header.Type = EFI_HII_PACKAGE_FORMS; + memcpy ( package->ops.data, ifr->ops, sizeof ( package->ops.data ) ); + package->strings.header.header.Header.Length = + sizeof ( package->strings ); + package->strings.header.header.Header.Type = + EFI_HII_PACKAGE_STRINGS; + package->strings.header.header.HdrSize = + sizeof ( package->strings.header ); + package->strings.header.header.StringInfoOffset = + sizeof ( package->strings.header ); + package->strings.header.header.LanguageName = language_id; + strcpy ( package->strings.header.header.Language, language ); + memcpy ( package->strings.data, ifr->strings, + sizeof ( package->strings.data ) ); + package->strings.end.BlockType = EFI_HII_SIBT_END; + package->end.Type = EFI_HII_PACKAGE_END; + package->end.Length = sizeof ( package->end ); + + return &package->header; +} + diff --git a/roms/ipxe/src/interface/efi/efi_init.c b/roms/ipxe/src/interface/efi/efi_init.c new file mode 100644 index 00000000..93ada21d --- /dev/null +++ b/roms/ipxe/src/interface/efi/efi_init.c @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2008 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. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include <string.h> +#include <errno.h> +#include <ipxe/init.h> +#include <ipxe/efi/efi.h> +#include <ipxe/efi/efi_driver.h> +#include <ipxe/efi/Protocol/LoadedImage.h> + +/** Image handle passed to entry point */ +EFI_HANDLE efi_image_handle; + +/** Loaded image protocol for this image */ +EFI_LOADED_IMAGE_PROTOCOL *efi_loaded_image; + +/** System table passed to entry point */ +EFI_SYSTEM_TABLE *efi_systab; + +/** Event used to signal shutdown */ +static EFI_EVENT efi_shutdown_event; + +/* Forward declarations */ +static EFI_STATUS EFIAPI efi_unload ( EFI_HANDLE image_handle ); + +/** + * Shut down in preparation for booting an OS. + * + * This hook gets called at ExitBootServices time in order to make + * sure that everything is properly shut down before the OS takes + * over. + */ +static EFIAPI void efi_shutdown_hook ( EFI_EVENT event __unused, + void *context __unused ) { + shutdown_boot(); +} + +/** + * Look up EFI configuration table + * + * @v guid Configuration table GUID + * @ret table Configuration table, or NULL + */ +static void * efi_find_table ( EFI_GUID *guid ) { + unsigned int i; + + for ( i = 0 ; i < efi_systab->NumberOfTableEntries ; i++ ) { + if ( memcmp ( &efi_systab->ConfigurationTable[i].VendorGuid, + guid, sizeof ( *guid ) ) == 0 ) + return efi_systab->ConfigurationTable[i].VendorTable; + } + + return NULL; +} + +/** + * Initialise EFI environment + * + * @v image_handle Image handle + * @v systab System table + * @ret efirc EFI return status code + */ +EFI_STATUS efi_init ( EFI_HANDLE image_handle, + EFI_SYSTEM_TABLE *systab ) { + EFI_BOOT_SERVICES *bs; + struct efi_protocol *prot; + struct efi_config_table *tab; + void *loaded_image; + EFI_STATUS efirc; + int rc; + + /* Store image handle and system table pointer for future use */ + efi_image_handle = image_handle; + efi_systab = systab; + + /* Sanity checks */ + if ( ! systab ) { + efirc = EFI_NOT_AVAILABLE_YET; + goto err_sanity; + } + if ( ! systab->ConOut ) { + efirc = EFI_NOT_AVAILABLE_YET; + goto err_sanity; + } + if ( ! systab->BootServices ) { + DBGC ( systab, "EFI provided no BootServices entry point\n" ); + efirc = EFI_NOT_AVAILABLE_YET; + goto err_sanity; + } + if ( ! systab->RuntimeServices ) { + DBGC ( systab, "EFI provided no RuntimeServices entry " + "point\n" ); + efirc = EFI_NOT_AVAILABLE_YET; + goto err_sanity; + } + DBGC ( systab, "EFI handle %p systab %p\n", image_handle, systab ); + bs = systab->BootServices; + + /* Look up used protocols */ + for_each_table_entry ( prot, EFI_PROTOCOLS ) { + if ( ( efirc = bs->LocateProtocol ( &prot->guid, NULL, + prot->protocol ) ) == 0 ) { + DBGC ( systab, "EFI protocol %s is at %p\n", + efi_guid_ntoa ( &prot->guid ), + *(prot->protocol) ); + } else { + DBGC ( systab, "EFI does not provide protocol %s\n", + efi_guid_ntoa ( &prot->guid ) ); + /* Fail if protocol is required */ + if ( prot->required ) + goto err_missing_protocol; + } + } + + /* Look up used configuration tables */ + for_each_table_entry ( tab, EFI_CONFIG_TABLES ) { + if ( ( *(tab->table) = efi_find_table ( &tab->guid ) ) ) { + DBGC ( systab, "EFI configuration table %s is at %p\n", + efi_guid_ntoa ( &tab->guid ), *(tab->table) ); + } else { + DBGC ( systab, "EFI does not provide configuration " + "table %s\n", efi_guid_ntoa ( &tab->guid ) ); + if ( tab->required ) { + efirc = EFI_NOT_AVAILABLE_YET; + goto err_missing_table; + } + } + } + + /* Get loaded image protocol */ + if ( ( efirc = bs->OpenProtocol ( image_handle, + &efi_loaded_image_protocol_guid, + &loaded_image, image_handle, NULL, + EFI_OPEN_PROTOCOL_GET_PROTOCOL ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( systab, "EFI could not get loaded image protocol: %s", + strerror ( rc ) ); + goto err_no_loaded_image; + } + efi_loaded_image = loaded_image; + DBGC ( systab, "EFI image base address %p\n", + efi_loaded_image->ImageBase ); + + /* EFI is perfectly capable of gracefully shutting down any + * loaded devices if it decides to fall back to a legacy boot. + * For no particularly comprehensible reason, it doesn't + * bother doing so when ExitBootServices() is called. + */ + if ( ( efirc = bs->CreateEvent ( EVT_SIGNAL_EXIT_BOOT_SERVICES, + TPL_CALLBACK, efi_shutdown_hook, + NULL, &efi_shutdown_event ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( systab, "EFI could not create ExitBootServices event: " + "%s\n", strerror ( rc ) ); + goto err_create_event; + } + + /* Install driver binding protocol */ + if ( ( rc = efi_driver_install() ) != 0 ) { + DBGC ( systab, "EFI could not install driver: %s\n", + strerror ( rc ) ); + efirc = EFIRC ( rc ); + goto err_driver_install; + } + + /* Install image unload method */ + efi_loaded_image->Unload = efi_unload; + + return 0; + + efi_driver_uninstall(); + err_driver_install: + bs->CloseEvent ( efi_shutdown_event ); + err_create_event: + err_no_loaded_image: + err_missing_table: + err_missing_protocol: + err_sanity: + return efirc; +} + +/** + * Shut down EFI environment + * + * @v image_handle Image handle + */ +static EFI_STATUS EFIAPI efi_unload ( EFI_HANDLE image_handle __unused ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + EFI_SYSTEM_TABLE *systab = efi_systab; + + DBGC ( systab, "EFI image unloading\n" ); + + /* Shut down */ + shutdown_exit(); + + /* Disconnect any remaining devices */ + efi_driver_disconnect_all(); + + /* Uninstall driver binding protocol */ + efi_driver_uninstall(); + + /* Uninstall exit boot services event */ + bs->CloseEvent ( efi_shutdown_event ); + + DBGC ( systab, "EFI image unloaded\n" ); + + return 0; +} diff --git a/roms/ipxe/src/interface/efi/efi_pci.c b/roms/ipxe/src/interface/efi/efi_pci.c new file mode 100644 index 00000000..86c781d8 --- /dev/null +++ b/roms/ipxe/src/interface/efi/efi_pci.c @@ -0,0 +1,357 @@ +/* + * Copyright (C) 2008 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. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include <stdlib.h> +#include <errno.h> +#include <ipxe/pci.h> +#include <ipxe/efi/efi.h> +#include <ipxe/efi/efi_pci.h> +#include <ipxe/efi/efi_driver.h> +#include <ipxe/efi/Protocol/PciIo.h> +#include <ipxe/efi/Protocol/PciRootBridgeIo.h> + +/** @file + * + * iPXE PCI I/O API for EFI + * + */ + +/* Disambiguate the various error causes */ +#define EINFO_EEFI_PCI \ + __einfo_uniqify ( EINFO_EPLATFORM, 0x01, \ + "Could not open PCI I/O protocol" ) +#define EINFO_EEFI_PCI_NOT_PCI \ + __einfo_platformify ( EINFO_EEFI_PCI, EFI_UNSUPPORTED, \ + "Not a PCI device" ) +#define EEFI_PCI_NOT_PCI __einfo_error ( EINFO_EEFI_PCI_NOT_PCI ) +#define EINFO_EEFI_PCI_IN_USE \ + __einfo_platformify ( EINFO_EEFI_PCI, EFI_ACCESS_DENIED, \ + "PCI device already has a driver" ) +#define EEFI_PCI_IN_USE __einfo_error ( EINFO_EEFI_PCI_IN_USE ) +#define EEFI_PCI( efirc ) \ + EPLATFORM ( EINFO_EEFI_PCI, efirc, \ + EEFI_PCI_NOT_PCI, EEFI_PCI_IN_USE ) + +/****************************************************************************** + * + * iPXE PCI API + * + ****************************************************************************** + */ + +/** PCI root bridge I/O protocol */ +static EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL *efipci; +EFI_REQUEST_PROTOCOL ( EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL, &efipci ); + +static unsigned long efipci_address ( struct pci_device *pci, + unsigned long location ) { + return EFI_PCI_ADDRESS ( PCI_BUS ( pci->busdevfn ), + PCI_SLOT ( pci->busdevfn ), + PCI_FUNC ( pci->busdevfn ), + EFIPCI_OFFSET ( location ) ); +} + +int efipci_read ( struct pci_device *pci, unsigned long location, + void *value ) { + EFI_STATUS efirc; + int rc; + + if ( ! efipci ) + return -ENOTSUP; + + if ( ( efirc = efipci->Pci.Read ( efipci, EFIPCI_WIDTH ( location ), + efipci_address ( pci, location ), 1, + value ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBG ( "EFIPCI config read from " PCI_FMT " offset %02lx " + "failed: %s\n", PCI_ARGS ( pci ), + EFIPCI_OFFSET ( location ), strerror ( rc ) ); + return -EIO; + } + + return 0; +} + +int efipci_write ( struct pci_device *pci, unsigned long location, + unsigned long value ) { + EFI_STATUS efirc; + int rc; + + if ( ! efipci ) + return -ENOTSUP; + + if ( ( efirc = efipci->Pci.Write ( efipci, EFIPCI_WIDTH ( location ), + efipci_address ( pci, location ), 1, + &value ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBG ( "EFIPCI config write to " PCI_FMT " offset %02lx " + "failed: %s\n", PCI_ARGS ( pci ), + EFIPCI_OFFSET ( location ), strerror ( rc ) ); + return -EIO; + } + + return 0; +} + +PROVIDE_PCIAPI_INLINE ( efi, pci_num_bus ); +PROVIDE_PCIAPI_INLINE ( efi, pci_read_config_byte ); +PROVIDE_PCIAPI_INLINE ( efi, pci_read_config_word ); +PROVIDE_PCIAPI_INLINE ( efi, pci_read_config_dword ); +PROVIDE_PCIAPI_INLINE ( efi, pci_write_config_byte ); +PROVIDE_PCIAPI_INLINE ( efi, pci_write_config_word ); +PROVIDE_PCIAPI_INLINE ( efi, pci_write_config_dword ); + +/****************************************************************************** + * + * EFI PCI device instantiation + * + ****************************************************************************** + */ + +/** + * Open EFI PCI device + * + * @v device EFI device handle + * @v attributes Protocol opening attributes + * @v pci PCI device to fill in + * @ret rc Return status code + */ +int efipci_open ( EFI_HANDLE device, UINT32 attributes, + struct pci_device *pci ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + union { + EFI_PCI_IO_PROTOCOL *pci_io; + void *interface; + } pci_io; + UINTN pci_segment, pci_bus, pci_dev, pci_fn; + EFI_STATUS efirc; + int rc; + + /* See if device is a PCI device */ + if ( ( efirc = bs->OpenProtocol ( device, &efi_pci_io_protocol_guid, + &pci_io.interface, efi_image_handle, + device, attributes ) ) != 0 ) { + rc = -EEFI_PCI ( efirc ); + DBGCP ( device, "EFIPCI %p %s cannot open PCI protocols: %s\n", + device, efi_handle_name ( device ), strerror ( rc ) ); + goto err_open_protocol; + } + + /* Get PCI bus:dev.fn address */ + if ( ( efirc = pci_io.pci_io->GetLocation ( pci_io.pci_io, &pci_segment, + &pci_bus, &pci_dev, + &pci_fn ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( device, "EFIPCI %p %s could not get PCI location: %s\n", + device, efi_handle_name ( device ), strerror ( rc ) ); + goto err_get_location; + } + DBGC2 ( device, "EFIPCI %p %s is PCI %04lx:%02lx:%02lx.%lx\n", device, + efi_handle_name ( device ), ( ( unsigned long ) pci_segment ), + ( ( unsigned long ) pci_bus ), ( ( unsigned long ) pci_dev ), + ( ( unsigned long ) pci_fn ) ); + + /* Try to enable I/O cycles, memory cycles, and bus mastering. + * Some platforms will 'helpfully' report errors if these bits + * can't be enabled (for example, if the card doesn't actually + * support I/O cycles). Work around any such platforms by + * enabling bits individually and simply ignoring any errors. + */ + pci_io.pci_io->Attributes ( pci_io.pci_io, + EfiPciIoAttributeOperationEnable, + EFI_PCI_IO_ATTRIBUTE_IO, NULL ); + pci_io.pci_io->Attributes ( pci_io.pci_io, + EfiPciIoAttributeOperationEnable, + EFI_PCI_IO_ATTRIBUTE_MEMORY, NULL ); + pci_io.pci_io->Attributes ( pci_io.pci_io, + EfiPciIoAttributeOperationEnable, + EFI_PCI_IO_ATTRIBUTE_BUS_MASTER, NULL ); + + /* Populate PCI device */ + pci_init ( pci, PCI_BUSDEVFN ( pci_bus, pci_dev, pci_fn ) ); + if ( ( rc = pci_read_config ( pci ) ) != 0 ) { + DBGC ( device, "EFIPCI %p %s cannot read PCI configuration: " + "%s\n", device, efi_handle_name ( device ), + strerror ( rc ) ); + goto err_pci_read_config; + } + + return 0; + + err_pci_read_config: + err_get_location: + bs->CloseProtocol ( device, &efi_pci_io_protocol_guid, + efi_image_handle, device ); + err_open_protocol: + return rc; +} + +/** + * Close EFI PCI device + * + * @v device EFI device handle + */ +void efipci_close ( EFI_HANDLE device ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + + bs->CloseProtocol ( device, &efi_pci_io_protocol_guid, + efi_image_handle, device ); +} + +/** + * Get EFI PCI device information + * + * @v device EFI device handle + * @v pci PCI device to fill in + * @ret rc Return status code + */ +int efipci_info ( EFI_HANDLE device, struct pci_device *pci ) { + int rc; + + /* Open PCI device, if possible */ + if ( ( rc = efipci_open ( device, EFI_OPEN_PROTOCOL_GET_PROTOCOL, + pci ) ) != 0 ) + return rc; + + /* Close PCI device */ + efipci_close ( device ); + + return 0; +} + +/****************************************************************************** + * + * EFI PCI driver + * + ****************************************************************************** + */ + +/** + * Check to see if driver supports a device + * + * @v device EFI device handle + * @ret rc Return status code + */ +static int efipci_supported ( EFI_HANDLE device ) { + struct pci_device pci; + int rc; + + /* Get PCI device information */ + if ( ( rc = efipci_info ( device, &pci ) ) != 0 ) + return rc; + + /* Look for a driver */ + if ( ( rc = pci_find_driver ( &pci ) ) != 0 ) { + DBGCP ( device, "EFIPCI %p %s has no driver\n", + device, efi_handle_name ( device ) ); + return rc; + } + DBGC ( device, "EFIPCI %p %s has driver \"%s\"\n", + device, efi_handle_name ( device ), pci.id->name ); + + return 0; +} + +/** + * Attach driver to device + * + * @v efidev EFI device + * @ret rc Return status code + */ +static int efipci_start ( struct efi_device *efidev ) { + EFI_HANDLE device = efidev->device; + struct pci_device *pci; + int rc; + + /* Allocate PCI device */ + pci = zalloc ( sizeof ( *pci ) ); + if ( ! pci ) { + rc = -ENOMEM; + goto err_alloc; + } + + /* Open PCI device */ + if ( ( rc = efipci_open ( device, ( EFI_OPEN_PROTOCOL_BY_DRIVER | + EFI_OPEN_PROTOCOL_EXCLUSIVE ), + pci ) ) != 0 ) { + DBGC ( device, "EFIPCI %p %s could not open PCI device: %s\n", + device, efi_handle_name ( device ), strerror ( rc ) ); + DBGC_EFI_OPENERS ( device, device, &efi_pci_io_protocol_guid ); + goto err_open; + } + + /* Find driver */ + if ( ( rc = pci_find_driver ( pci ) ) != 0 ) { + DBGC ( device, "EFIPCI %p %s has no driver\n", + device, efi_handle_name ( device ) ); + goto err_find_driver; + } + + /* Mark PCI device as a child of the EFI device */ + pci->dev.parent = &efidev->dev; + list_add ( &pci->dev.siblings, &efidev->dev.children ); + + /* Probe driver */ + if ( ( rc = pci_probe ( pci ) ) != 0 ) { + DBGC ( device, "EFIPCI %p %s could not probe driver \"%s\": " + "%s\n", device, efi_handle_name ( device ), + pci->id->name, strerror ( rc ) ); + goto err_probe; + } + DBGC ( device, "EFIPCI %p %s using driver \"%s\"\n", + device, efi_handle_name ( device ), pci->id->name ); + + efidev_set_drvdata ( efidev, pci ); + return 0; + + pci_remove ( pci ); + err_probe: + list_del ( &pci->dev.siblings ); + err_find_driver: + efipci_close ( device ); + err_open: + free ( pci ); + err_alloc: + return rc; +} + +/** + * Detach driver from device + * + * @v efidev EFI device + */ +static void efipci_stop ( struct efi_device *efidev ) { + struct pci_device *pci = efidev_get_drvdata ( efidev ); + EFI_HANDLE device = efidev->device; + + pci_remove ( pci ); + list_del ( &pci->dev.siblings ); + efipci_close ( device ); + free ( pci ); +} + +/** EFI PCI driver */ +struct efi_driver efipci_driver __efi_driver ( EFI_DRIVER_NORMAL ) = { + .name = "PCI", + .supported = efipci_supported, + .start = efipci_start, + .stop = efipci_stop, +}; diff --git a/roms/ipxe/src/interface/efi/efi_reboot.c b/roms/ipxe/src/interface/efi/efi_reboot.c new file mode 100644 index 00000000..96638c48 --- /dev/null +++ b/roms/ipxe/src/interface/efi/efi_reboot.c @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2013 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. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +/** + * @file + * + * EFI reboot mechanism + * + */ + +#include <errno.h> +#include <ipxe/efi/efi.h> +#include <ipxe/reboot.h> + +/** + * Reboot system + * + * @v warm Perform a warm reboot + */ +static void efi_reboot ( int warm ) { + EFI_RUNTIME_SERVICES *rs = efi_systab->RuntimeServices; + + /* Use runtime services to reset system */ + rs->ResetSystem ( ( warm ? EfiResetWarm : EfiResetCold ), 0, 0, NULL ); +} + +/** + * Power off system + * + * @ret rc Return status code + */ +static int efi_poweroff ( void ) { + EFI_RUNTIME_SERVICES *rs = efi_systab->RuntimeServices; + + /* Use runtime services to power off system */ + rs->ResetSystem ( EfiResetShutdown, 0, 0, NULL ); + + /* Should never happen */ + return -ECANCELED; +} + +PROVIDE_REBOOT ( efi, reboot, efi_reboot ); +PROVIDE_REBOOT ( efi, poweroff, efi_poweroff ); diff --git a/roms/ipxe/src/interface/efi/efi_smbios.c b/roms/ipxe/src/interface/efi/efi_smbios.c new file mode 100644 index 00000000..304f95a5 --- /dev/null +++ b/roms/ipxe/src/interface/efi/efi_smbios.c @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2008 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. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include <errno.h> +#include <ipxe/smbios.h> +#include <ipxe/efi/efi.h> +#include <ipxe/efi/Guid/SmBios.h> + +/** @file + * + * iPXE SMBIOS API for EFI + * + */ + +/** SMBIOS configuration table */ +static struct smbios_entry *smbios_entry; +EFI_USE_TABLE ( SMBIOS_TABLE, &smbios_entry, 0 ); + +/** + * Find SMBIOS + * + * @v smbios SMBIOS entry point descriptor structure to fill in + * @ret rc Return status code + */ +static int efi_find_smbios ( struct smbios *smbios ) { + + if ( ! smbios_entry ) { + DBG ( "No SMBIOS table provided\n" ); + return -ENODEV; + } + + if ( smbios_entry->signature != SMBIOS_SIGNATURE ) { + DBG ( "Invalid SMBIOS signature\n" ); + return -ENODEV; + } + + smbios->address = phys_to_user ( smbios_entry->smbios_address ); + smbios->len = smbios_entry->smbios_len; + smbios->count = smbios_entry->smbios_count; + smbios->version = + SMBIOS_VERSION ( smbios_entry->major, smbios_entry->minor ); + DBG ( "Found SMBIOS v%d.%d entry point at %p (%x+%zx)\n", + smbios_entry->major, smbios_entry->minor, smbios_entry, + smbios_entry->smbios_address, smbios->len ); + + return 0; +} + +PROVIDE_SMBIOS ( efi, find_smbios, efi_find_smbios ); diff --git a/roms/ipxe/src/interface/efi/efi_snp.c b/roms/ipxe/src/interface/efi/efi_snp.c new file mode 100644 index 00000000..67fba342 --- /dev/null +++ b/roms/ipxe/src/interface/efi/efi_snp.c @@ -0,0 +1,1209 @@ +/* + * Copyright (C) 2008 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. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <byteswap.h> +#include <ipxe/netdevice.h> +#include <ipxe/iobuf.h> +#include <ipxe/in.h> +#include <ipxe/version.h> +#include <ipxe/efi/efi.h> +#include <ipxe/efi/efi_driver.h> +#include <ipxe/efi/efi_strings.h> +#include <ipxe/efi/efi_utils.h> +#include <ipxe/efi/efi_snp.h> +#include <usr/autoboot.h> + +/** List of SNP devices */ +static LIST_HEAD ( efi_snp_devices ); + +/** Network devices are currently claimed for use by iPXE */ +static int efi_snp_claimed; + +/** + * Set EFI SNP mode state + * + * @v snp SNP interface + */ +static void efi_snp_set_state ( struct efi_snp_device *snpdev ) { + struct net_device *netdev = snpdev->netdev; + EFI_SIMPLE_NETWORK_MODE *mode = &snpdev->mode; + + /* Calculate state */ + if ( ! snpdev->started ) { + /* Start() method not called; report as Stopped */ + mode->State = EfiSimpleNetworkStopped; + } else if ( ! netdev_is_open ( netdev ) ) { + /* Network device not opened; report as Started */ + mode->State = EfiSimpleNetworkStarted; + } else if ( efi_snp_claimed ) { + /* Network device opened but claimed for use by iPXE; report + * as Started to inhibit receive polling. + */ + mode->State = EfiSimpleNetworkStarted; + } else { + /* Network device opened and available for use via SNP; report + * as Initialized. + */ + mode->State = EfiSimpleNetworkInitialized; + } +} + +/** + * Set EFI SNP mode based on iPXE net device parameters + * + * @v snp SNP interface + */ +static void efi_snp_set_mode ( struct efi_snp_device *snpdev ) { + struct net_device *netdev = snpdev->netdev; + EFI_SIMPLE_NETWORK_MODE *mode = &snpdev->mode; + struct ll_protocol *ll_protocol = netdev->ll_protocol; + unsigned int ll_addr_len = ll_protocol->ll_addr_len; + + mode->HwAddressSize = ll_addr_len; + mode->MediaHeaderSize = ll_protocol->ll_header_len; + mode->MaxPacketSize = netdev->max_pkt_len; + mode->ReceiveFilterMask = ( EFI_SIMPLE_NETWORK_RECEIVE_UNICAST | + EFI_SIMPLE_NETWORK_RECEIVE_MULTICAST | + EFI_SIMPLE_NETWORK_RECEIVE_BROADCAST ); + assert ( ll_addr_len <= sizeof ( mode->CurrentAddress ) ); + memcpy ( &mode->CurrentAddress, netdev->ll_addr, ll_addr_len ); + memcpy ( &mode->BroadcastAddress, netdev->ll_broadcast, ll_addr_len ); + ll_protocol->init_addr ( netdev->hw_addr, &mode->PermanentAddress ); + mode->IfType = ntohs ( ll_protocol->ll_proto ); + mode->MacAddressChangeable = TRUE; + mode->MediaPresentSupported = TRUE; + mode->MediaPresent = ( netdev_link_ok ( netdev ) ? TRUE : FALSE ); +} + +/** + * Poll net device and count received packets + * + * @v snpdev SNP device + */ +static void efi_snp_poll ( struct efi_snp_device *snpdev ) { + struct io_buffer *iobuf; + unsigned int before = 0; + unsigned int after = 0; + unsigned int arrived; + + /* We have to report packet arrivals, and this is the easiest + * way to fake it. + */ + list_for_each_entry ( iobuf, &snpdev->netdev->rx_queue, list ) + before++; + netdev_poll ( snpdev->netdev ); + list_for_each_entry ( iobuf, &snpdev->netdev->rx_queue, list ) + after++; + arrived = ( after - before ); + + snpdev->rx_count_interrupts += arrived; + snpdev->rx_count_events += arrived; +} + +/** + * Change SNP state from "stopped" to "started" + * + * @v snp SNP interface + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_snp_start ( EFI_SIMPLE_NETWORK_PROTOCOL *snp ) { + struct efi_snp_device *snpdev = + container_of ( snp, struct efi_snp_device, snp ); + + DBGC2 ( snpdev, "SNPDEV %p START\n", snpdev ); + + /* Fail if net device is currently claimed for use by iPXE */ + if ( efi_snp_claimed ) + return EFI_NOT_READY; + + snpdev->started = 1; + efi_snp_set_state ( snpdev ); + return 0; +} + +/** + * Change SNP state from "started" to "stopped" + * + * @v snp SNP interface + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_snp_stop ( EFI_SIMPLE_NETWORK_PROTOCOL *snp ) { + struct efi_snp_device *snpdev = + container_of ( snp, struct efi_snp_device, snp ); + + DBGC2 ( snpdev, "SNPDEV %p STOP\n", snpdev ); + + /* Fail if net device is currently claimed for use by iPXE */ + if ( efi_snp_claimed ) + return EFI_NOT_READY; + + snpdev->started = 0; + efi_snp_set_state ( snpdev ); + return 0; +} + +/** + * Open the network device + * + * @v snp SNP interface + * @v extra_rx_bufsize Extra RX buffer size, in bytes + * @v extra_tx_bufsize Extra TX buffer size, in bytes + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_snp_initialize ( EFI_SIMPLE_NETWORK_PROTOCOL *snp, + UINTN extra_rx_bufsize, UINTN extra_tx_bufsize ) { + struct efi_snp_device *snpdev = + container_of ( snp, struct efi_snp_device, snp ); + int rc; + + DBGC2 ( snpdev, "SNPDEV %p INITIALIZE (%ld extra RX, %ld extra TX)\n", + snpdev, ( ( unsigned long ) extra_rx_bufsize ), + ( ( unsigned long ) extra_tx_bufsize ) ); + + /* Fail if net device is currently claimed for use by iPXE */ + if ( efi_snp_claimed ) + return EFI_NOT_READY; + + if ( ( rc = netdev_open ( snpdev->netdev ) ) != 0 ) { + DBGC ( snpdev, "SNPDEV %p could not open %s: %s\n", + snpdev, snpdev->netdev->name, strerror ( rc ) ); + return EFIRC ( rc ); + } + efi_snp_set_state ( snpdev ); + + return 0; +} + +/** + * Reset the network device + * + * @v snp SNP interface + * @v ext_verify Extended verification required + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_snp_reset ( EFI_SIMPLE_NETWORK_PROTOCOL *snp, BOOLEAN ext_verify ) { + struct efi_snp_device *snpdev = + container_of ( snp, struct efi_snp_device, snp ); + int rc; + + DBGC2 ( snpdev, "SNPDEV %p RESET (%s extended verification)\n", + snpdev, ( ext_verify ? "with" : "without" ) ); + + /* Fail if net device is currently claimed for use by iPXE */ + if ( efi_snp_claimed ) + return EFI_NOT_READY; + + netdev_close ( snpdev->netdev ); + efi_snp_set_state ( snpdev ); + + if ( ( rc = netdev_open ( snpdev->netdev ) ) != 0 ) { + DBGC ( snpdev, "SNPDEV %p could not reopen %s: %s\n", + snpdev, snpdev->netdev->name, strerror ( rc ) ); + return EFIRC ( rc ); + } + efi_snp_set_state ( snpdev ); + + return 0; +} + +/** + * Shut down the network device + * + * @v snp SNP interface + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_snp_shutdown ( EFI_SIMPLE_NETWORK_PROTOCOL *snp ) { + struct efi_snp_device *snpdev = + container_of ( snp, struct efi_snp_device, snp ); + + DBGC2 ( snpdev, "SNPDEV %p SHUTDOWN\n", snpdev ); + + /* Fail if net device is currently claimed for use by iPXE */ + if ( efi_snp_claimed ) + return EFI_NOT_READY; + + netdev_close ( snpdev->netdev ); + efi_snp_set_state ( snpdev ); + + return 0; +} + +/** + * Manage receive filters + * + * @v snp SNP interface + * @v enable Receive filters to enable + * @v disable Receive filters to disable + * @v mcast_reset Reset multicast filters + * @v mcast_count Number of multicast filters + * @v mcast Multicast filters + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_snp_receive_filters ( EFI_SIMPLE_NETWORK_PROTOCOL *snp, UINT32 enable, + UINT32 disable, BOOLEAN mcast_reset, + UINTN mcast_count, EFI_MAC_ADDRESS *mcast ) { + struct efi_snp_device *snpdev = + container_of ( snp, struct efi_snp_device, snp ); + unsigned int i; + + DBGC2 ( snpdev, "SNPDEV %p RECEIVE_FILTERS %08x&~%08x%s %ld mcast\n", + snpdev, enable, disable, ( mcast_reset ? " reset" : "" ), + ( ( unsigned long ) mcast_count ) ); + for ( i = 0 ; i < mcast_count ; i++ ) { + DBGC2_HDA ( snpdev, i, &mcast[i], + snpdev->netdev->ll_protocol->ll_addr_len ); + } + + /* Fail if net device is currently claimed for use by iPXE */ + if ( efi_snp_claimed ) + return EFI_NOT_READY; + + /* Lie through our teeth, otherwise MNP refuses to accept us */ + return 0; +} + +/** + * Set station address + * + * @v snp SNP interface + * @v reset Reset to permanent address + * @v new New station address + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_snp_station_address ( EFI_SIMPLE_NETWORK_PROTOCOL *snp, BOOLEAN reset, + EFI_MAC_ADDRESS *new ) { + struct efi_snp_device *snpdev = + container_of ( snp, struct efi_snp_device, snp ); + struct ll_protocol *ll_protocol = snpdev->netdev->ll_protocol; + + DBGC2 ( snpdev, "SNPDEV %p STATION_ADDRESS %s\n", snpdev, + ( reset ? "reset" : ll_protocol->ntoa ( new ) ) ); + + /* Fail if net device is currently claimed for use by iPXE */ + if ( efi_snp_claimed ) + return EFI_NOT_READY; + + /* Set the MAC address */ + if ( reset ) + new = &snpdev->mode.PermanentAddress; + memcpy ( snpdev->netdev->ll_addr, new, ll_protocol->ll_addr_len ); + + /* MAC address changes take effect only on netdev_open() */ + if ( netdev_is_open ( snpdev->netdev ) ) { + DBGC ( snpdev, "SNPDEV %p MAC address changed while net " + "device open\n", snpdev ); + } + + return 0; +} + +/** + * Get (or reset) statistics + * + * @v snp SNP interface + * @v reset Reset statistics + * @v stats_len Size of statistics table + * @v stats Statistics table + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_snp_statistics ( EFI_SIMPLE_NETWORK_PROTOCOL *snp, BOOLEAN reset, + UINTN *stats_len, EFI_NETWORK_STATISTICS *stats ) { + struct efi_snp_device *snpdev = + container_of ( snp, struct efi_snp_device, snp ); + EFI_NETWORK_STATISTICS stats_buf; + + DBGC2 ( snpdev, "SNPDEV %p STATISTICS%s", snpdev, + ( reset ? " reset" : "" ) ); + + /* Fail if net device is currently claimed for use by iPXE */ + if ( efi_snp_claimed ) + return EFI_NOT_READY; + + /* Gather statistics */ + memset ( &stats_buf, 0, sizeof ( stats_buf ) ); + stats_buf.TxGoodFrames = snpdev->netdev->tx_stats.good; + stats_buf.TxDroppedFrames = snpdev->netdev->tx_stats.bad; + stats_buf.TxTotalFrames = ( snpdev->netdev->tx_stats.good + + snpdev->netdev->tx_stats.bad ); + stats_buf.RxGoodFrames = snpdev->netdev->rx_stats.good; + stats_buf.RxDroppedFrames = snpdev->netdev->rx_stats.bad; + stats_buf.RxTotalFrames = ( snpdev->netdev->rx_stats.good + + snpdev->netdev->rx_stats.bad ); + if ( *stats_len > sizeof ( stats_buf ) ) + *stats_len = sizeof ( stats_buf ); + if ( stats ) + memcpy ( stats, &stats_buf, *stats_len ); + + /* Reset statistics if requested to do so */ + if ( reset ) { + memset ( &snpdev->netdev->tx_stats, 0, + sizeof ( snpdev->netdev->tx_stats ) ); + memset ( &snpdev->netdev->rx_stats, 0, + sizeof ( snpdev->netdev->rx_stats ) ); + } + + return 0; +} + +/** + * Convert multicast IP address to MAC address + * + * @v snp SNP interface + * @v ipv6 Address is IPv6 + * @v ip IP address + * @v mac MAC address + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_snp_mcast_ip_to_mac ( EFI_SIMPLE_NETWORK_PROTOCOL *snp, BOOLEAN ipv6, + EFI_IP_ADDRESS *ip, EFI_MAC_ADDRESS *mac ) { + struct efi_snp_device *snpdev = + container_of ( snp, struct efi_snp_device, snp ); + struct ll_protocol *ll_protocol = snpdev->netdev->ll_protocol; + const char *ip_str; + int rc; + + ip_str = ( ipv6 ? "(IPv6)" /* FIXME when we have inet6_ntoa() */ : + inet_ntoa ( *( ( struct in_addr * ) ip ) ) ); + DBGC2 ( snpdev, "SNPDEV %p MCAST_IP_TO_MAC %s\n", snpdev, ip_str ); + + /* Fail if net device is currently claimed for use by iPXE */ + if ( efi_snp_claimed ) + return EFI_NOT_READY; + + /* Try to hash the address */ + if ( ( rc = ll_protocol->mc_hash ( ( ipv6 ? AF_INET6 : AF_INET ), + ip, mac ) ) != 0 ) { + DBGC ( snpdev, "SNPDEV %p could not hash %s: %s\n", + snpdev, ip_str, strerror ( rc ) ); + return EFIRC ( rc ); + } + + return 0; +} + +/** + * Read or write non-volatile storage + * + * @v snp SNP interface + * @v read Operation is a read + * @v offset Starting offset within NVRAM + * @v len Length of data buffer + * @v data Data buffer + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_snp_nvdata ( EFI_SIMPLE_NETWORK_PROTOCOL *snp, BOOLEAN read, + UINTN offset, UINTN len, VOID *data ) { + struct efi_snp_device *snpdev = + container_of ( snp, struct efi_snp_device, snp ); + + DBGC2 ( snpdev, "SNPDEV %p NVDATA %s %lx+%lx\n", snpdev, + ( read ? "read" : "write" ), ( ( unsigned long ) offset ), + ( ( unsigned long ) len ) ); + if ( ! read ) + DBGC2_HDA ( snpdev, offset, data, len ); + + /* Fail if net device is currently claimed for use by iPXE */ + if ( efi_snp_claimed ) + return EFI_NOT_READY; + + return EFI_UNSUPPORTED; +} + +/** + * Read interrupt status and TX recycled buffer status + * + * @v snp SNP interface + * @v interrupts Interrupt status, or NULL + * @v txbufs Recycled transmit buffer address, or NULL + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_snp_get_status ( EFI_SIMPLE_NETWORK_PROTOCOL *snp, + UINT32 *interrupts, VOID **txbufs ) { + struct efi_snp_device *snpdev = + container_of ( snp, struct efi_snp_device, snp ); + + DBGC2 ( snpdev, "SNPDEV %p GET_STATUS", snpdev ); + + /* Fail if net device is currently claimed for use by iPXE */ + if ( efi_snp_claimed ) + return EFI_NOT_READY; + + /* Poll the network device */ + efi_snp_poll ( snpdev ); + + /* Interrupt status. In practice, this seems to be used only + * to detect TX completions. + */ + if ( interrupts ) { + *interrupts = 0; + /* Report TX completions once queue is empty; this + * avoids having to add hooks in the net device layer. + */ + if ( snpdev->tx_count_interrupts && + list_empty ( &snpdev->netdev->tx_queue ) ) { + *interrupts |= EFI_SIMPLE_NETWORK_TRANSMIT_INTERRUPT; + snpdev->tx_count_interrupts--; + } + /* Report RX */ + if ( snpdev->rx_count_interrupts ) { + *interrupts |= EFI_SIMPLE_NETWORK_RECEIVE_INTERRUPT; + snpdev->rx_count_interrupts--; + } + DBGC2 ( snpdev, " INTS:%02x", *interrupts ); + } + + /* TX completions. It would be possible to design a more + * idiotic scheme for this, but it would be a challenge. + * According to the UEFI header file, txbufs will be filled in + * with a list of "recycled transmit buffers" (i.e. completed + * TX buffers). Observant readers may care to note that + * *txbufs is a void pointer. Precisely how a list of + * completed transmit buffers is meant to be represented as an + * array of voids is left as an exercise for the reader. + * + * The only users of this interface (MnpDxe/MnpIo.c and + * PxeBcDxe/Bc.c within the EFI dev kit) both just poll until + * seeing a non-NULL result return in txbufs. This is valid + * provided that they do not ever attempt to transmit more + * than one packet concurrently (and that TX never times out). + */ + if ( txbufs ) { + if ( snpdev->tx_count_txbufs && + list_empty ( &snpdev->netdev->tx_queue ) ) { + *txbufs = "Which idiot designed this API?"; + snpdev->tx_count_txbufs--; + } else { + *txbufs = NULL; + } + DBGC2 ( snpdev, " TX:%s", ( *txbufs ? "some" : "none" ) ); + } + + DBGC2 ( snpdev, "\n" ); + return 0; +} + +/** + * Start packet transmission + * + * @v snp SNP interface + * @v ll_header_len Link-layer header length, if to be filled in + * @v len Length of data buffer + * @v data Data buffer + * @v ll_src Link-layer source address, if specified + * @v ll_dest Link-layer destination address, if specified + * @v net_proto Network-layer protocol (in host order) + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_snp_transmit ( EFI_SIMPLE_NETWORK_PROTOCOL *snp, + UINTN ll_header_len, UINTN len, VOID *data, + EFI_MAC_ADDRESS *ll_src, EFI_MAC_ADDRESS *ll_dest, + UINT16 *net_proto ) { + struct efi_snp_device *snpdev = + container_of ( snp, struct efi_snp_device, snp ); + struct ll_protocol *ll_protocol = snpdev->netdev->ll_protocol; + struct io_buffer *iobuf; + size_t payload_len; + int rc; + + DBGC2 ( snpdev, "SNPDEV %p TRANSMIT %p+%lx", snpdev, data, + ( ( unsigned long ) len ) ); + if ( ll_header_len ) { + if ( ll_src ) { + DBGC2 ( snpdev, " src %s", + ll_protocol->ntoa ( ll_src ) ); + } + if ( ll_dest ) { + DBGC2 ( snpdev, " dest %s", + ll_protocol->ntoa ( ll_dest ) ); + } + if ( net_proto ) { + DBGC2 ( snpdev, " proto %04x", *net_proto ); + } + } + DBGC2 ( snpdev, "\n" ); + + /* Fail if net device is currently claimed for use by iPXE */ + if ( efi_snp_claimed ) + return EFI_NOT_READY; + + /* Sanity checks */ + if ( ll_header_len ) { + if ( ll_header_len != ll_protocol->ll_header_len ) { + DBGC ( snpdev, "SNPDEV %p TX invalid header length " + "%ld\n", snpdev, + ( ( unsigned long ) ll_header_len ) ); + rc = -EINVAL; + goto err_sanity; + } + if ( len < ll_header_len ) { + DBGC ( snpdev, "SNPDEV %p invalid packet length %ld\n", + snpdev, ( ( unsigned long ) len ) ); + rc = -EINVAL; + goto err_sanity; + } + if ( ! ll_dest ) { + DBGC ( snpdev, "SNPDEV %p TX missing destination " + "address\n", snpdev ); + rc = -EINVAL; + goto err_sanity; + } + if ( ! net_proto ) { + DBGC ( snpdev, "SNPDEV %p TX missing network " + "protocol\n", snpdev ); + rc = -EINVAL; + goto err_sanity; + } + if ( ! ll_src ) + ll_src = &snpdev->mode.CurrentAddress; + } + + /* Allocate buffer */ + payload_len = ( len - ll_protocol->ll_header_len ); + iobuf = alloc_iob ( MAX_LL_HEADER_LEN + ( ( payload_len > IOB_ZLEN ) ? + payload_len : IOB_ZLEN ) ); + if ( ! iobuf ) { + DBGC ( snpdev, "SNPDEV %p TX could not allocate %ld-byte " + "buffer\n", snpdev, ( ( unsigned long ) len ) ); + rc = -ENOMEM; + goto err_alloc_iob; + } + iob_reserve ( iobuf, ( MAX_LL_HEADER_LEN - + ll_protocol->ll_header_len ) ); + memcpy ( iob_put ( iobuf, len ), data, len ); + + /* Create link-layer header, if specified */ + if ( ll_header_len ) { + iob_pull ( iobuf, ll_protocol->ll_header_len ); + if ( ( rc = ll_protocol->push ( snpdev->netdev, + iobuf, ll_dest, ll_src, + htons ( *net_proto ) )) != 0 ){ + DBGC ( snpdev, "SNPDEV %p TX could not construct " + "header: %s\n", snpdev, strerror ( rc ) ); + goto err_ll_push; + } + } + + /* Transmit packet */ + if ( ( rc = netdev_tx ( snpdev->netdev, iob_disown ( iobuf ) ) ) != 0){ + DBGC ( snpdev, "SNPDEV %p TX could not transmit: %s\n", + snpdev, strerror ( rc ) ); + goto err_tx; + } + + /* Record transmission as outstanding */ + snpdev->tx_count_interrupts++; + snpdev->tx_count_txbufs++; + + return 0; + + err_tx: + err_ll_push: + free_iob ( iobuf ); + err_alloc_iob: + err_sanity: + return EFIRC ( rc ); +} + +/** + * Receive packet + * + * @v snp SNP interface + * @v ll_header_len Link-layer header length, if to be filled in + * @v len Length of data buffer + * @v data Data buffer + * @v ll_src Link-layer source address, if specified + * @v ll_dest Link-layer destination address, if specified + * @v net_proto Network-layer protocol (in host order) + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_snp_receive ( EFI_SIMPLE_NETWORK_PROTOCOL *snp, + UINTN *ll_header_len, UINTN *len, VOID *data, + EFI_MAC_ADDRESS *ll_src, EFI_MAC_ADDRESS *ll_dest, + UINT16 *net_proto ) { + struct efi_snp_device *snpdev = + container_of ( snp, struct efi_snp_device, snp ); + struct ll_protocol *ll_protocol = snpdev->netdev->ll_protocol; + struct io_buffer *iobuf; + const void *iob_ll_dest; + const void *iob_ll_src; + uint16_t iob_net_proto; + unsigned int iob_flags; + int rc; + + DBGC2 ( snpdev, "SNPDEV %p RECEIVE %p(+%lx)", snpdev, data, + ( ( unsigned long ) *len ) ); + + /* Fail if net device is currently claimed for use by iPXE */ + if ( efi_snp_claimed ) + return EFI_NOT_READY; + + /* Poll the network device */ + efi_snp_poll ( snpdev ); + + /* Dequeue a packet, if one is available */ + iobuf = netdev_rx_dequeue ( snpdev->netdev ); + if ( ! iobuf ) { + DBGC2 ( snpdev, "\n" ); + rc = -EAGAIN; + goto out_no_packet; + } + DBGC2 ( snpdev, "+%zx\n", iob_len ( iobuf ) ); + + /* Return packet to caller */ + memcpy ( data, iobuf->data, iob_len ( iobuf ) ); + *len = iob_len ( iobuf ); + + /* Attempt to decode link-layer header */ + if ( ( rc = ll_protocol->pull ( snpdev->netdev, iobuf, &iob_ll_dest, + &iob_ll_src, &iob_net_proto, + &iob_flags ) ) != 0 ) { + DBGC ( snpdev, "SNPDEV %p could not parse header: %s\n", + snpdev, strerror ( rc ) ); + goto out_bad_ll_header; + } + + /* Return link-layer header parameters to caller, if required */ + if ( ll_header_len ) + *ll_header_len = ll_protocol->ll_header_len; + if ( ll_src ) + memcpy ( ll_src, iob_ll_src, ll_protocol->ll_addr_len ); + if ( ll_dest ) + memcpy ( ll_dest, iob_ll_dest, ll_protocol->ll_addr_len ); + if ( net_proto ) + *net_proto = ntohs ( iob_net_proto ); + + rc = 0; + + out_bad_ll_header: + free_iob ( iobuf ); + out_no_packet: + return EFIRC ( rc ); +} + +/** + * Poll event + * + * @v event Event + * @v context Event context + */ +static VOID EFIAPI efi_snp_wait_for_packet ( EFI_EVENT event, + VOID *context ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + struct efi_snp_device *snpdev = context; + + DBGCP ( snpdev, "SNPDEV %p WAIT_FOR_PACKET\n", snpdev ); + + /* Do nothing unless the net device is open */ + if ( ! netdev_is_open ( snpdev->netdev ) ) + return; + + /* Do nothing if net device is currently claimed for use by iPXE */ + if ( efi_snp_claimed ) + return; + + /* Poll the network device */ + efi_snp_poll ( snpdev ); + + /* Fire event if packets have been received */ + if ( snpdev->rx_count_events != 0 ) { + DBGC2 ( snpdev, "SNPDEV %p firing WaitForPacket event\n", + snpdev ); + bs->SignalEvent ( event ); + snpdev->rx_count_events--; + } +} + +/** SNP interface */ +static EFI_SIMPLE_NETWORK_PROTOCOL efi_snp_device_snp = { + .Revision = EFI_SIMPLE_NETWORK_PROTOCOL_REVISION, + .Start = efi_snp_start, + .Stop = efi_snp_stop, + .Initialize = efi_snp_initialize, + .Reset = efi_snp_reset, + .Shutdown = efi_snp_shutdown, + .ReceiveFilters = efi_snp_receive_filters, + .StationAddress = efi_snp_station_address, + .Statistics = efi_snp_statistics, + .MCastIpToMac = efi_snp_mcast_ip_to_mac, + .NvData = efi_snp_nvdata, + .GetStatus = efi_snp_get_status, + .Transmit = efi_snp_transmit, + .Receive = efi_snp_receive, +}; + +/****************************************************************************** + * + * Component name protocol + * + ****************************************************************************** + */ + +/** + * Look up driver name + * + * @v name2 Component name protocol + * @v language Language to use + * @v driver_name Driver name to fill in + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_snp_get_driver_name ( EFI_COMPONENT_NAME2_PROTOCOL *name2, + CHAR8 *language __unused, CHAR16 **driver_name ) { + struct efi_snp_device *snpdev = + container_of ( name2, struct efi_snp_device, name2 ); + + *driver_name = snpdev->driver_name; + return 0; +} + +/** + * Look up controller name + * + * @v name2 Component name protocol + * @v device Device + * @v child Child device, or NULL + * @v language Language to use + * @v driver_name Device name to fill in + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_snp_get_controller_name ( EFI_COMPONENT_NAME2_PROTOCOL *name2, + EFI_HANDLE device __unused, + EFI_HANDLE child __unused, + CHAR8 *language __unused, + CHAR16 **controller_name ) { + struct efi_snp_device *snpdev = + container_of ( name2, struct efi_snp_device, name2 ); + + *controller_name = snpdev->controller_name; + return 0; +} + +/****************************************************************************** + * + * Load file protocol + * + ****************************************************************************** + */ + +/** + * Load file + * + * @v loadfile Load file protocol + * @v path File path + * @v booting Loading as part of a boot attempt + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_snp_load_file ( EFI_LOAD_FILE_PROTOCOL *load_file, + EFI_DEVICE_PATH_PROTOCOL *path __unused, + BOOLEAN booting, UINTN *len __unused, + VOID *data __unused ) { + struct efi_snp_device *snpdev = + container_of ( load_file, struct efi_snp_device, load_file ); + struct net_device *netdev = snpdev->netdev; + + /* Fail unless this is a boot attempt */ + if ( ! booting ) { + DBGC ( snpdev, "SNPDEV %p cannot load non-boot file\n", + snpdev ); + return EFI_UNSUPPORTED; + } + + /* Claim network devices for use by iPXE */ + efi_snp_claim(); + + /* Boot from network device */ + ipxe ( netdev ); + + /* Release network devices for use via SNP */ + efi_snp_release(); + + /* Assume boot process was aborted */ + return EFI_ABORTED; +} + +/** Load file protocol */ +static EFI_LOAD_FILE_PROTOCOL efi_snp_load_file_protocol = { + .LoadFile = efi_snp_load_file, +}; + +/****************************************************************************** + * + * iPXE network driver + * + ****************************************************************************** + */ + +/** + * Locate SNP device corresponding to network device + * + * @v netdev Network device + * @ret snp SNP device, or NULL if not found + */ +static struct efi_snp_device * efi_snp_demux ( struct net_device *netdev ) { + struct efi_snp_device *snpdev; + + list_for_each_entry ( snpdev, &efi_snp_devices, list ) { + if ( snpdev->netdev == netdev ) + return snpdev; + } + return NULL; +} + +/** + * Create SNP device + * + * @v netdev Network device + * @ret rc Return status code + */ +static int efi_snp_probe ( struct net_device *netdev ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + struct efi_device *efidev; + struct efi_snp_device *snpdev; + union { + EFI_DEVICE_PATH_PROTOCOL *path; + void *interface; + } path; + EFI_DEVICE_PATH_PROTOCOL *path_end; + MAC_ADDR_DEVICE_PATH *macpath; + size_t path_prefix_len = 0; + EFI_STATUS efirc; + int rc; + + /* Find parent EFI device */ + efidev = efidev_parent ( netdev->dev ); + if ( ! efidev ) { + DBG ( "SNP skipping non-EFI device %s\n", netdev->name ); + rc = 0; + goto err_no_efidev; + } + + /* Allocate the SNP device */ + snpdev = zalloc ( sizeof ( *snpdev ) ); + if ( ! snpdev ) { + rc = -ENOMEM; + goto err_alloc_snp; + } + snpdev->netdev = netdev_get ( netdev ); + snpdev->efidev = efidev; + + /* Sanity check */ + if ( netdev->ll_protocol->ll_addr_len > sizeof ( EFI_MAC_ADDRESS ) ) { + DBGC ( snpdev, "SNPDEV %p cannot support link-layer address " + "length %d for %s\n", snpdev, + netdev->ll_protocol->ll_addr_len, netdev->name ); + rc = -ENOTSUP; + goto err_ll_addr_len; + } + + /* Populate the SNP structure */ + memcpy ( &snpdev->snp, &efi_snp_device_snp, sizeof ( snpdev->snp ) ); + snpdev->snp.Mode = &snpdev->mode; + if ( ( efirc = bs->CreateEvent ( EVT_NOTIFY_WAIT, TPL_NOTIFY, + efi_snp_wait_for_packet, snpdev, + &snpdev->snp.WaitForPacket ) ) != 0 ){ + rc = -EEFI ( efirc ); + DBGC ( snpdev, "SNPDEV %p could not create event: %s\n", + snpdev, strerror ( rc ) ); + goto err_create_event; + } + + /* Populate the SNP mode structure */ + snpdev->mode.State = EfiSimpleNetworkStopped; + efi_snp_set_mode ( snpdev ); + + /* Populate the NII structure */ + snpdev->nii.Revision = + EFI_NETWORK_INTERFACE_IDENTIFIER_PROTOCOL_REVISION; + strncpy ( snpdev->nii.StringId, "iPXE", + sizeof ( snpdev->nii.StringId ) ); + + /* Populate the component name structure */ + efi_snprintf ( snpdev->driver_name, + ( sizeof ( snpdev->driver_name ) / + sizeof ( snpdev->driver_name[0] ) ), + "%s %s", product_short_name, netdev->dev->driver_name ); + efi_snprintf ( snpdev->controller_name, + ( sizeof ( snpdev->controller_name ) / + sizeof ( snpdev->controller_name[0] ) ), + "%s %s (%s, %s)", product_short_name, + netdev->dev->driver_name, netdev->dev->name, + netdev_addr ( netdev ) ); + snpdev->name2.GetDriverName = efi_snp_get_driver_name; + snpdev->name2.GetControllerName = efi_snp_get_controller_name; + snpdev->name2.SupportedLanguages = "en"; + + /* Populate the load file protocol structure */ + memcpy ( &snpdev->load_file, &efi_snp_load_file_protocol, + sizeof ( snpdev->load_file ) ); + + /* Populate the device name */ + efi_snprintf ( snpdev->name, ( sizeof ( snpdev->name ) / + sizeof ( snpdev->name[0] ) ), + "%s", netdev->name ); + + /* Get the parent device path */ + if ( ( efirc = bs->OpenProtocol ( efidev->device, + &efi_device_path_protocol_guid, + &path.interface, efi_image_handle, + efidev->device, + EFI_OPEN_PROTOCOL_GET_PROTOCOL ))!=0){ + rc = -EEFI ( efirc ); + DBGC ( snpdev, "SNPDEV %p cannot get %p %s device path: %s\n", + snpdev, efidev->device, + efi_handle_name ( efidev->device ), strerror ( rc ) ); + goto err_open_device_path; + } + + /* Allocate the new device path */ + path_end = efi_devpath_end ( path.path ); + path_prefix_len = ( ( ( void * ) path_end ) - ( ( void * ) path.path )); + snpdev->path = zalloc ( path_prefix_len + sizeof ( *macpath ) + + sizeof ( *path_end ) ); + if ( ! snpdev->path ) { + rc = -ENOMEM; + goto err_alloc_device_path; + } + + /* Populate the device path */ + memcpy ( snpdev->path, path.path, path_prefix_len ); + macpath = ( ( ( void * ) snpdev->path ) + path_prefix_len ); + path_end = ( ( void * ) ( macpath + 1 ) ); + memset ( macpath, 0, sizeof ( *macpath ) ); + macpath->Header.Type = MESSAGING_DEVICE_PATH; + macpath->Header.SubType = MSG_MAC_ADDR_DP; + macpath->Header.Length[0] = sizeof ( *macpath ); + memcpy ( &macpath->MacAddress, netdev->ll_addr, + sizeof ( macpath->MacAddress ) ); + macpath->IfType = ntohs ( netdev->ll_protocol->ll_proto ); + memset ( path_end, 0, sizeof ( *path_end ) ); + path_end->Type = END_DEVICE_PATH_TYPE; + path_end->SubType = END_ENTIRE_DEVICE_PATH_SUBTYPE; + path_end->Length[0] = sizeof ( *path_end ); + + /* Install the SNP */ + if ( ( efirc = bs->InstallMultipleProtocolInterfaces ( + &snpdev->handle, + &efi_simple_network_protocol_guid, &snpdev->snp, + &efi_device_path_protocol_guid, snpdev->path, + &efi_nii_protocol_guid, &snpdev->nii, + &efi_nii31_protocol_guid, &snpdev->nii, + &efi_component_name2_protocol_guid, &snpdev->name2, + &efi_load_file_protocol_guid, &snpdev->load_file, + NULL ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( snpdev, "SNPDEV %p could not install protocols: " + "%s\n", snpdev, strerror ( rc ) ); + goto err_install_protocol_interface; + } + + /* Add as child of EFI parent device */ + if ( ( rc = efi_child_add ( efidev->device, snpdev->handle ) ) != 0 ) { + DBGC ( snpdev, "SNPDEV %p could not become child of %p %s: " + "%s\n", snpdev, efidev->device, + efi_handle_name ( efidev->device ), strerror ( rc ) ); + goto err_efi_child_add; + } + + /* Install HII */ + if ( ( rc = efi_snp_hii_install ( snpdev ) ) != 0 ) { + DBGC ( snpdev, "SNPDEV %p could not install HII: %s\n", + snpdev, strerror ( rc ) ); + /* HII fails on several platforms. It's + * non-essential, so treat this as a non-fatal + * error. + */ + } + + /* Add to list of SNP devices */ + list_add ( &snpdev->list, &efi_snp_devices ); + + /* Close device path */ + bs->CloseProtocol ( efidev->device, &efi_device_path_protocol_guid, + efi_image_handle, efidev->device ); + + DBGC ( snpdev, "SNPDEV %p installed for %s as device %p %s\n", + snpdev, netdev->name, snpdev->handle, + efi_handle_name ( snpdev->handle ) ); + return 0; + + if ( snpdev->package_list ) + efi_snp_hii_uninstall ( snpdev ); + efi_child_del ( efidev->device, snpdev->handle ); + err_efi_child_add: + bs->UninstallMultipleProtocolInterfaces ( + snpdev->handle, + &efi_simple_network_protocol_guid, &snpdev->snp, + &efi_device_path_protocol_guid, snpdev->path, + &efi_nii_protocol_guid, &snpdev->nii, + &efi_nii31_protocol_guid, &snpdev->nii, + &efi_component_name2_protocol_guid, &snpdev->name2, + &efi_load_file_protocol_guid, &snpdev->load_file, + NULL ); + err_install_protocol_interface: + free ( snpdev->path ); + err_alloc_device_path: + bs->CloseProtocol ( efidev->device, &efi_device_path_protocol_guid, + efi_image_handle, efidev->device ); + err_open_device_path: + bs->CloseEvent ( snpdev->snp.WaitForPacket ); + err_create_event: + err_ll_addr_len: + netdev_put ( netdev ); + free ( snpdev ); + err_alloc_snp: + err_no_efidev: + return rc; +} + +/** + * Handle SNP device or link state change + * + * @v netdev Network device + */ +static void efi_snp_notify ( struct net_device *netdev ) { + struct efi_snp_device *snpdev; + + /* Locate SNP device */ + snpdev = efi_snp_demux ( netdev ); + if ( ! snpdev ) { + DBG ( "SNP skipping non-SNP device %s\n", netdev->name ); + return; + } + + /* Update link state */ + snpdev->mode.MediaPresent = + ( netdev_link_ok ( netdev ) ? TRUE : FALSE ); + DBGC ( snpdev, "SNPDEV %p link is %s\n", snpdev, + ( snpdev->mode.MediaPresent ? "up" : "down" ) ); + + /* Update mode state */ + efi_snp_set_state ( snpdev ); +} + +/** + * Destroy SNP device + * + * @v netdev Network device + */ +static void efi_snp_remove ( struct net_device *netdev ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + struct efi_snp_device *snpdev; + + /* Locate SNP device */ + snpdev = efi_snp_demux ( netdev ); + if ( ! snpdev ) { + DBG ( "SNP skipping non-SNP device %s\n", netdev->name ); + return; + } + + /* Uninstall the SNP */ + if ( snpdev->package_list ) + efi_snp_hii_uninstall ( snpdev ); + efi_child_del ( snpdev->efidev->device, snpdev->handle ); + list_del ( &snpdev->list ); + bs->UninstallMultipleProtocolInterfaces ( + snpdev->handle, + &efi_simple_network_protocol_guid, &snpdev->snp, + &efi_device_path_protocol_guid, snpdev->path, + &efi_nii_protocol_guid, &snpdev->nii, + &efi_nii31_protocol_guid, &snpdev->nii, + &efi_component_name2_protocol_guid, &snpdev->name2, + &efi_load_file_protocol_guid, &snpdev->load_file, + NULL ); + free ( snpdev->path ); + bs->CloseEvent ( snpdev->snp.WaitForPacket ); + netdev_put ( snpdev->netdev ); + free ( snpdev ); +} + +/** SNP driver */ +struct net_driver efi_snp_driver __net_driver = { + .name = "SNP", + .probe = efi_snp_probe, + .notify = efi_snp_notify, + .remove = efi_snp_remove, +}; + +/** + * Find SNP device by EFI device handle + * + * @v handle EFI device handle + * @ret snpdev SNP device, or NULL + */ +struct efi_snp_device * find_snpdev ( EFI_HANDLE handle ) { + struct efi_snp_device *snpdev; + + list_for_each_entry ( snpdev, &efi_snp_devices, list ) { + if ( snpdev->handle == handle ) + return snpdev; + } + return NULL; +} + +/** + * Get most recently opened SNP device + * + * @ret snpdev Most recently opened SNP device, or NULL + */ +struct efi_snp_device * last_opened_snpdev ( void ) { + struct net_device *netdev; + + netdev = last_opened_netdev(); + if ( ! netdev ) + return NULL; + + return efi_snp_demux ( netdev ); +} + +/** + * Set SNP claimed/released state + * + * @v claimed Network devices are claimed for use by iPXE + */ +void efi_snp_set_claimed ( int claimed ) { + struct efi_snp_device *snpdev; + + /* Claim SNP devices */ + efi_snp_claimed = claimed; + + /* Update SNP mode state for each interface */ + list_for_each_entry ( snpdev, &efi_snp_devices, list ) + efi_snp_set_state ( snpdev ); +} diff --git a/roms/ipxe/src/interface/efi/efi_snp_hii.c b/roms/ipxe/src/interface/efi/efi_snp_hii.c new file mode 100644 index 00000000..c49c76a3 --- /dev/null +++ b/roms/ipxe/src/interface/efi/efi_snp_hii.c @@ -0,0 +1,722 @@ +/* + * Copyright (C) 2012 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. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +/** + * @file + * + * EFI SNP HII protocol + * + * The HII protocols are some of the less-well designed parts of the + * entire EFI specification. This is a significant accomplishment. + * + * The face-slappingly ludicrous query string syntax seems to be + * motivated by the desire to allow a caller to query multiple drivers + * simultaneously via the single-instance HII_CONFIG_ROUTING_PROTOCOL, + * which is supposed to pass relevant subsets of the query string to + * the relevant drivers. + * + * Nobody uses the HII_CONFIG_ROUTING_PROTOCOL. Not even the EFI + * setup browser uses the HII_CONFIG_ROUTING_PROTOCOL. To the best of + * my knowledge, there has only ever been one implementation of the + * HII_CONFIG_ROUTING_PROTOCOL (as part of EDK2), and it just doesn't + * work. It's so badly broken that I can't even figure out what the + * code is _trying_ to do. + * + * Fundamentally, the problem seems to be that Javascript programmers + * should not be allowed to design APIs for C code. + */ + +#include <string.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> +#include <wchar.h> +#include <errno.h> +#include <ipxe/settings.h> +#include <ipxe/nvo.h> +#include <ipxe/device.h> +#include <ipxe/netdevice.h> +#include <ipxe/version.h> +#include <ipxe/efi/efi.h> +#include <ipxe/efi/efi_hii.h> +#include <ipxe/efi/efi_snp.h> +#include <ipxe/efi/efi_strings.h> + +/** EFI platform setup formset GUID */ +static EFI_GUID efi_hii_platform_setup_formset_guid + = EFI_HII_PLATFORM_SETUP_FORMSET_GUID; + +/** EFI IBM UCM compliant formset GUID */ +static EFI_GUID efi_hii_ibm_ucm_compliant_formset_guid + = EFI_HII_IBM_UCM_COMPLIANT_FORMSET_GUID; + +/** EFI HII database protocol */ +static EFI_HII_DATABASE_PROTOCOL *efihii; +EFI_REQUEST_PROTOCOL ( EFI_HII_DATABASE_PROTOCOL, &efihii ); + +/** + * Identify settings to be exposed via HII + * + * @v snpdev SNP device + * @ret settings Settings, or NULL + */ +static struct settings * efi_snp_hii_settings ( struct efi_snp_device *snpdev ){ + + return find_child_settings ( netdev_settings ( snpdev->netdev ), + NVO_SETTINGS_NAME ); +} + +/** + * Check whether or not setting is applicable + * + * @v snpdev SNP device + * @v setting Setting + * @ret applies Setting applies + */ +static int efi_snp_hii_setting_applies ( struct efi_snp_device *snpdev, + struct setting *setting ) { + + return nvo_applies ( efi_snp_hii_settings ( snpdev ), setting ); +} + +/** + * Generate a random GUID + * + * @v guid GUID to fill in + */ +static void efi_snp_hii_random_guid ( EFI_GUID *guid ) { + uint8_t *byte = ( ( uint8_t * ) guid ); + unsigned int i; + + for ( i = 0 ; i < sizeof ( *guid ) ; i++ ) + *(byte++) = random(); +} + +/** + * Generate EFI SNP questions + * + * @v snpdev SNP device + * @v ifr IFR builder + * @v varstore_id Variable store identifier + */ +static void efi_snp_hii_questions ( struct efi_snp_device *snpdev, + struct efi_ifr_builder *ifr, + unsigned int varstore_id ) { + struct setting *setting; + struct setting *previous = NULL; + unsigned int name_id; + unsigned int prompt_id; + unsigned int help_id; + unsigned int question_id; + + /* Add all applicable settings */ + for_each_table_entry ( setting, SETTINGS ) { + if ( ! efi_snp_hii_setting_applies ( snpdev, setting ) ) + continue; + if ( previous && ( setting_cmp ( setting, previous ) == 0 ) ) + continue; + previous = setting; + name_id = efi_ifr_string ( ifr, "%s", setting->name ); + prompt_id = efi_ifr_string ( ifr, "%s", setting->description ); + help_id = efi_ifr_string ( ifr, "http://ipxe.org/cfg/%s", + setting->name ); + question_id = setting->tag; + efi_ifr_string_op ( ifr, prompt_id, help_id, + question_id, varstore_id, name_id, + 0, 0x00, 0xff, 0 ); + } +} + +/** + * Build HII package list for SNP device + * + * @v snpdev SNP device + * @ret package Package list, or NULL on error + */ +static EFI_HII_PACKAGE_LIST_HEADER * +efi_snp_hii_package_list ( struct efi_snp_device *snpdev ) { + struct net_device *netdev = snpdev->netdev; + struct device *dev = netdev->dev; + struct efi_ifr_builder ifr; + EFI_HII_PACKAGE_LIST_HEADER *package; + const char *name; + EFI_GUID package_guid; + EFI_GUID formset_guid; + EFI_GUID varstore_guid; + unsigned int title_id; + unsigned int varstore_id; + + /* Initialise IFR builder */ + efi_ifr_init ( &ifr ); + + /* Determine product name */ + name = ( product_name[0] ? product_name : product_short_name ); + + /* Generate GUIDs */ + efi_snp_hii_random_guid ( &package_guid ); + efi_snp_hii_random_guid ( &formset_guid ); + efi_snp_hii_random_guid ( &varstore_guid ); + + /* Generate title string (used more than once) */ + title_id = efi_ifr_string ( &ifr, "%s (%s)", name, + netdev_addr ( netdev ) ); + + /* Generate opcodes */ + efi_ifr_form_set_op ( &ifr, &formset_guid, title_id, + efi_ifr_string ( &ifr, "Configure %s", + product_short_name ), + &efi_hii_platform_setup_formset_guid, + &efi_hii_ibm_ucm_compliant_formset_guid, NULL ); + efi_ifr_guid_class_op ( &ifr, EFI_NETWORK_DEVICE_CLASS ); + efi_ifr_guid_subclass_op ( &ifr, 0x03 ); + varstore_id = efi_ifr_varstore_name_value_op ( &ifr, &varstore_guid ); + efi_ifr_form_op ( &ifr, title_id ); + efi_ifr_text_op ( &ifr, + efi_ifr_string ( &ifr, "Name" ), + efi_ifr_string ( &ifr, "Firmware product name" ), + efi_ifr_string ( &ifr, "%s", name ) ); + efi_ifr_text_op ( &ifr, + efi_ifr_string ( &ifr, "Version" ), + efi_ifr_string ( &ifr, "Firmware version" ), + efi_ifr_string ( &ifr, "%s", product_version ) ); + efi_ifr_text_op ( &ifr, + efi_ifr_string ( &ifr, "Driver" ), + efi_ifr_string ( &ifr, "Firmware driver" ), + efi_ifr_string ( &ifr, "%s", dev->driver_name ) ); + efi_ifr_text_op ( &ifr, + efi_ifr_string ( &ifr, "Device" ), + efi_ifr_string ( &ifr, "Hardware device" ), + efi_ifr_string ( &ifr, "%s", dev->name ) ); + efi_snp_hii_questions ( snpdev, &ifr, varstore_id ); + efi_ifr_end_op ( &ifr ); + efi_ifr_end_op ( &ifr ); + + /* Build package */ + package = efi_ifr_package ( &ifr, &package_guid, "en-us", + efi_ifr_string ( &ifr, "English" ) ); + if ( ! package ) { + DBGC ( snpdev, "SNPDEV %p could not build IFR package\n", + snpdev ); + efi_ifr_free ( &ifr ); + return NULL; + } + + /* Free temporary storage */ + efi_ifr_free ( &ifr ); + return package; +} + +/** + * Append response to result string + * + * @v snpdev SNP device + * @v key Key + * @v value Value + * @v results Result string + * @ret rc Return status code + * + * The result string is allocated dynamically using + * BootServices::AllocatePool(), and the caller is responsible for + * eventually calling BootServices::FreePool(). + */ +static int efi_snp_hii_append ( struct efi_snp_device *snpdev __unused, + const char *key, const char *value, + wchar_t **results ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + size_t len; + void *new; + + /* Allocate new string */ + len = ( ( *results ? ( wcslen ( *results ) + 1 /* "&" */ ) : 0 ) + + strlen ( key ) + 1 /* "=" */ + strlen ( value ) + 1 /* NUL */ ); + bs->AllocatePool ( EfiBootServicesData, ( len * sizeof ( wchar_t ) ), + &new ); + if ( ! new ) + return -ENOMEM; + + /* Populate string */ + efi_snprintf ( new, len, "%ls%s%s=%s", ( *results ? *results : L"" ), + ( *results ? L"&" : L"" ), key, value ); + bs->FreePool ( *results ); + *results = new; + + return 0; +} + +/** + * Fetch HII setting + * + * @v snpdev SNP device + * @v key Key + * @v value Value + * @v results Result string + * @v have_setting Flag indicating detection of a setting + * @ret rc Return status code + */ +static int efi_snp_hii_fetch ( struct efi_snp_device *snpdev, + const char *key, const char *value, + wchar_t **results, int *have_setting ) { + struct settings *settings = efi_snp_hii_settings ( snpdev ); + struct settings *origin; + struct setting *setting; + struct setting fetched; + int len; + char *buf; + char *encoded; + int i; + int rc; + + /* Handle ConfigHdr components */ + if ( ( strcasecmp ( key, "GUID" ) == 0 ) || + ( strcasecmp ( key, "NAME" ) == 0 ) || + ( strcasecmp ( key, "PATH" ) == 0 ) ) { + return efi_snp_hii_append ( snpdev, key, value, results ); + } + if ( have_setting ) + *have_setting = 1; + + /* Do nothing more unless we have a settings block */ + if ( ! settings ) { + rc = -ENOTSUP; + goto err_no_settings; + } + + /* Identify setting */ + setting = find_setting ( key ); + if ( ! setting ) { + DBGC ( snpdev, "SNPDEV %p no such setting \"%s\"\n", + snpdev, key ); + rc = -ENODEV; + goto err_find_setting; + } + + /* Encode value */ + if ( setting_exists ( settings, setting ) ) { + + /* Calculate formatted length */ + len = fetchf_setting ( settings, setting, &origin, &fetched, + NULL, 0 ); + if ( len < 0 ) { + rc = len; + DBGC ( snpdev, "SNPDEV %p could not fetch %s: %s\n", + snpdev, setting->name, strerror ( rc ) ); + goto err_fetchf_len; + } + + /* Allocate buffer for formatted value and HII-encoded value */ + buf = zalloc ( len + 1 /* NUL */ + ( len * 4 ) + 1 /* NUL */ ); + if ( ! buf ) { + rc = -ENOMEM; + goto err_alloc; + } + encoded = ( buf + len + 1 /* NUL */ ); + + /* Format value */ + fetchf_setting ( origin, &fetched, NULL, NULL, buf, + ( len + 1 /* NUL */ ) ); + for ( i = 0 ; i < len ; i++ ) { + sprintf ( ( encoded + ( 4 * i ) ), "%04x", + *( ( uint8_t * ) buf + i ) ); + } + + } else { + + /* Non-existent or inapplicable setting */ + buf = NULL; + encoded = ""; + } + + /* Append results */ + if ( ( rc = efi_snp_hii_append ( snpdev, key, encoded, + results ) ) != 0 ) { + goto err_append; + } + + /* Success */ + rc = 0; + + err_append: + free ( buf ); + err_alloc: + err_fetchf_len: + err_find_setting: + err_no_settings: + return rc; +} + +/** + * Fetch HII setting + * + * @v snpdev SNP device + * @v key Key + * @v value Value + * @v results Result string (unused) + * @v have_setting Flag indicating detection of a setting (unused) + * @ret rc Return status code + */ +static int efi_snp_hii_store ( struct efi_snp_device *snpdev, + const char *key, const char *value, + wchar_t **results __unused, + int *have_setting __unused ) { + struct settings *settings = efi_snp_hii_settings ( snpdev ); + struct setting *setting; + char *buf; + char tmp[5]; + char *endp; + int len; + int i; + int rc; + + /* Handle ConfigHdr components */ + if ( ( strcasecmp ( key, "GUID" ) == 0 ) || + ( strcasecmp ( key, "NAME" ) == 0 ) || + ( strcasecmp ( key, "PATH" ) == 0 ) ) { + /* Nothing to do */ + return 0; + } + + /* Do nothing more unless we have a settings block */ + if ( ! settings ) { + rc = -ENOTSUP; + goto err_no_settings; + } + + /* Identify setting */ + setting = find_setting ( key ); + if ( ! setting ) { + DBGC ( snpdev, "SNPDEV %p no such setting \"%s\"\n", + snpdev, key ); + rc = -ENODEV; + goto err_find_setting; + } + + /* Allocate buffer */ + len = ( strlen ( value ) / 4 ); + buf = zalloc ( len + 1 /* NUL */ ); + if ( ! buf ) { + rc = -ENOMEM; + goto err_alloc; + } + + /* Decode value */ + tmp[4] = '\0'; + for ( i = 0 ; i < len ; i++ ) { + memcpy ( tmp, ( value + ( i * 4 ) ), 4 ); + buf[i] = strtoul ( tmp, &endp, 16 ); + if ( endp != &tmp[4] ) { + DBGC ( snpdev, "SNPDEV %p invalid character %s\n", + snpdev, tmp ); + rc = -EINVAL; + goto err_inval; + } + } + + /* Store value */ + if ( ( rc = storef_setting ( settings, setting, buf ) ) != 0 ) { + DBGC ( snpdev, "SNPDEV %p could not store \"%s\" into %s: %s\n", + snpdev, buf, setting->name, strerror ( rc ) ); + goto err_storef; + } + + /* Success */ + rc = 0; + + err_storef: + err_inval: + free ( buf ); + err_alloc: + err_find_setting: + err_no_settings: + return rc; +} + +/** + * Process portion of HII configuration string + * + * @v snpdev SNP device + * @v string HII configuration string + * @v progress Progress through HII configuration string + * @v results Results string + * @v have_setting Flag indicating detection of a setting (unused) + * @v process Function used to process key=value pairs + * @ret rc Return status code + */ +static int efi_snp_hii_process ( struct efi_snp_device *snpdev, + wchar_t *string, wchar_t **progress, + wchar_t **results, int *have_setting, + int ( * process ) ( struct efi_snp_device *, + const char *key, + const char *value, + wchar_t **results, + int *have_setting ) ) { + wchar_t *wkey = string; + wchar_t *wend = string; + wchar_t *wvalue = NULL; + size_t key_len; + size_t value_len; + void *temp; + char *key; + char *value; + int rc; + + /* Locate key, value (if any), and end */ + while ( *wend ) { + if ( *wend == L'&' ) + break; + if ( *(wend++) == L'=' ) + wvalue = wend; + } + + /* Allocate memory for key and value */ + key_len = ( ( wvalue ? ( wvalue - 1 ) : wend ) - wkey ); + value_len = ( wvalue ? ( wend - wvalue ) : 0 ); + temp = zalloc ( key_len + 1 /* NUL */ + value_len + 1 /* NUL */ ); + if ( ! temp ) + return -ENOMEM; + key = temp; + value = ( temp + key_len + 1 /* NUL */ ); + + /* Copy key and value */ + while ( key_len-- ) + key[key_len] = wkey[key_len]; + while ( value_len-- ) + value[value_len] = wvalue[value_len]; + + /* Process key and value */ + if ( ( rc = process ( snpdev, key, value, results, + have_setting ) ) != 0 ) { + goto err; + } + + /* Update progress marker */ + *progress = wend; + + err: + /* Free temporary storage */ + free ( temp ); + + return rc; +} + +/** + * Fetch configuration + * + * @v hii HII configuration access protocol + * @v request Configuration to fetch + * @ret progress Progress made through configuration to fetch + * @ret results Query results + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_snp_hii_extract_config ( const EFI_HII_CONFIG_ACCESS_PROTOCOL *hii, + EFI_STRING request, EFI_STRING *progress, + EFI_STRING *results ) { + struct efi_snp_device *snpdev = + container_of ( hii, struct efi_snp_device, hii ); + int have_setting = 0; + wchar_t *pos; + int rc; + + DBGC ( snpdev, "SNPDEV %p ExtractConfig request \"%ls\"\n", + snpdev, request ); + + /* Initialise results */ + *results = NULL; + + /* Process all request fragments */ + for ( pos = *progress = request ; *progress && **progress ; + pos = *progress + 1 ) { + if ( ( rc = efi_snp_hii_process ( snpdev, pos, progress, + results, &have_setting, + efi_snp_hii_fetch ) ) != 0 ) { + return EFIRC ( rc ); + } + } + + /* If we have no explicit request, return all settings */ + if ( ! have_setting ) { + struct setting *setting; + + for_each_table_entry ( setting, SETTINGS ) { + if ( ! efi_snp_hii_setting_applies ( snpdev, setting ) ) + continue; + if ( ( rc = efi_snp_hii_fetch ( snpdev, setting->name, + NULL, results, + NULL ) ) != 0 ) { + return EFIRC ( rc ); + } + } + } + + DBGC ( snpdev, "SNPDEV %p ExtractConfig results \"%ls\"\n", + snpdev, *results ); + return 0; +} + +/** + * Store configuration + * + * @v hii HII configuration access protocol + * @v config Configuration to store + * @ret progress Progress made through configuration to store + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_snp_hii_route_config ( const EFI_HII_CONFIG_ACCESS_PROTOCOL *hii, + EFI_STRING config, EFI_STRING *progress ) { + struct efi_snp_device *snpdev = + container_of ( hii, struct efi_snp_device, hii ); + wchar_t *pos; + int rc; + + DBGC ( snpdev, "SNPDEV %p RouteConfig \"%ls\"\n", snpdev, config ); + + /* Process all request fragments */ + for ( pos = *progress = config ; *progress && **progress ; + pos = *progress + 1 ) { + if ( ( rc = efi_snp_hii_process ( snpdev, pos, progress, + NULL, NULL, + efi_snp_hii_store ) ) != 0 ) { + return EFIRC ( rc ); + } + } + + return 0; +} + +/** + * Handle form actions + * + * @v hii HII configuration access protocol + * @v action Form browser action + * @v question_id Question ID + * @v type Type of value + * @v value Value + * @ret action_request Action requested by driver + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_snp_hii_callback ( const EFI_HII_CONFIG_ACCESS_PROTOCOL *hii, + EFI_BROWSER_ACTION action __unused, + EFI_QUESTION_ID question_id __unused, + UINT8 type __unused, EFI_IFR_TYPE_VALUE *value __unused, + EFI_BROWSER_ACTION_REQUEST *action_request __unused ) { + struct efi_snp_device *snpdev = + container_of ( hii, struct efi_snp_device, hii ); + + DBGC ( snpdev, "SNPDEV %p Callback\n", snpdev ); + return EFI_UNSUPPORTED; +} + +/** HII configuration access protocol */ +static EFI_HII_CONFIG_ACCESS_PROTOCOL efi_snp_device_hii = { + .ExtractConfig = efi_snp_hii_extract_config, + .RouteConfig = efi_snp_hii_route_config, + .Callback = efi_snp_hii_callback, +}; + +/** + * Install HII protocol and packages for SNP device + * + * @v snpdev SNP device + * @ret rc Return status code + */ +int efi_snp_hii_install ( struct efi_snp_device *snpdev ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + int efirc; + int rc; + + /* Do nothing if HII database protocol is not supported */ + if ( ! efihii ) { + rc = -ENOTSUP; + goto err_no_hii; + } + + /* Initialise HII protocol */ + memcpy ( &snpdev->hii, &efi_snp_device_hii, sizeof ( snpdev->hii ) ); + + /* Create HII package list */ + snpdev->package_list = efi_snp_hii_package_list ( snpdev ); + if ( ! snpdev->package_list ) { + DBGC ( snpdev, "SNPDEV %p could not create HII package list\n", + snpdev ); + rc = -ENOMEM; + goto err_build_package_list; + } + + /* Add HII packages */ + if ( ( efirc = efihii->NewPackageList ( efihii, snpdev->package_list, + snpdev->handle, + &snpdev->hii_handle ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( snpdev, "SNPDEV %p could not add HII packages: %s\n", + snpdev, strerror ( rc ) ); + goto err_new_package_list; + } + + /* Install HII protocol */ + if ( ( efirc = bs->InstallMultipleProtocolInterfaces ( + &snpdev->handle, + &efi_hii_config_access_protocol_guid, &snpdev->hii, + NULL ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( snpdev, "SNPDEV %p could not install HII protocol: %s\n", + snpdev, strerror ( rc ) ); + goto err_install_protocol; + } + + return 0; + + bs->UninstallMultipleProtocolInterfaces ( + snpdev->handle, + &efi_hii_config_access_protocol_guid, &snpdev->hii, + NULL ); + err_install_protocol: + efihii->RemovePackageList ( efihii, snpdev->hii_handle ); + err_new_package_list: + free ( snpdev->package_list ); + snpdev->package_list = NULL; + err_build_package_list: + err_no_hii: + return rc; +} + +/** + * Uninstall HII protocol and package for SNP device + * + * @v snpdev SNP device + */ +void efi_snp_hii_uninstall ( struct efi_snp_device *snpdev ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + + /* Do nothing if HII database protocol is not supported */ + if ( ! efihii ) + return; + + /* Uninstall protocols and remove package list */ + bs->UninstallMultipleProtocolInterfaces ( + snpdev->handle, + &efi_hii_config_access_protocol_guid, &snpdev->hii, + NULL ); + efihii->RemovePackageList ( efihii, snpdev->hii_handle ); + free ( snpdev->package_list ); + snpdev->package_list = NULL; +} diff --git a/roms/ipxe/src/interface/efi/efi_strings.c b/roms/ipxe/src/interface/efi/efi_strings.c new file mode 100644 index 00000000..751589b4 --- /dev/null +++ b/roms/ipxe/src/interface/efi/efi_strings.c @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2011 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. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include <stddef.h> +#include <stdarg.h> +#include <ipxe/vsprintf.h> +#include <ipxe/efi/efi_strings.h> + +/** Context used by efi_vsnprintf() and friends */ +struct efi_sputc_context { + /** printf context */ + struct printf_context ctx; + /** Buffer for formatted string (used by efi_printf_sputc()) */ + wchar_t *buf; + /** Buffer length (used by efi_printf_sputc()) + * + * Note that this is a number of wide characters, not a number + * of bytes. + */ + size_t max_wlen; +}; + +/** + * Write wide character to buffer + * + * @v ctx Context + * @v c Character + */ +static void efi_printf_sputc ( struct printf_context *ctx, unsigned int c ) { + struct efi_sputc_context * sctx = + container_of ( ctx, struct efi_sputc_context, ctx ); + + if ( ctx->len < sctx->max_wlen ) + sctx->buf[ctx->len] = c; +} + +/** + * Write a formatted string to a wide-character buffer + * + * @v wbuf Buffer into which to write the string + * @v wsize Size of buffer (in wide characters) + * @v fmt Format string + * @v args Arguments corresponding to the format string + * @ret wlen Length of formatted string (in wide characters) + * + * If the buffer is too small to contain the string, the returned + * length is the length that would have been written had enough space + * been available. + */ +int efi_vsnprintf ( wchar_t *wbuf, size_t wsize, const char *fmt, + va_list args ) { + struct efi_sputc_context sctx; + size_t wlen; + size_t wend; + + /* Hand off to vcprintf */ + sctx.ctx.handler = efi_printf_sputc; + sctx.buf = wbuf; + sctx.max_wlen = wsize; + wlen = vcprintf ( &sctx.ctx, fmt, args ); + + /* Add trailing NUL */ + if ( wsize ) { + wend = wsize - 1; + if ( wlen < wend ) + wend = wlen; + wbuf[wend] = '\0'; + } + + return wlen; +} + +/** + * Write a formatted string to a buffer + * + * @v wbuf Buffer into which to write the string + * @v wsize Size of buffer (in wide characters) + * @v fmt Format string + * @v ... Arguments corresponding to the format string + * @ret wlen Length of formatted string (in wide characters) + */ +int efi_snprintf ( wchar_t *wbuf, size_t wsize, const char *fmt, ... ) { + va_list args; + int i; + + va_start ( args, fmt ); + i = efi_vsnprintf ( wbuf, wsize, fmt, args ); + va_end ( args ); + return i; +} + +/** + * Version of efi_vsnprintf() that accepts a signed buffer size + * + * @v wbuf Buffer into which to write the string + * @v swsize Size of buffer (in wide characters) + * @v fmt Format string + * @v args Arguments corresponding to the format string + * @ret wlen Length of formatted string (in wide characters) + */ +int efi_vssnprintf ( wchar_t *wbuf, ssize_t swsize, const char *fmt, + va_list args ) { + + /* Treat negative buffer size as zero buffer size */ + if ( swsize < 0 ) + swsize = 0; + + /* Hand off to vsnprintf */ + return efi_vsnprintf ( wbuf, swsize, fmt, args ); +} + +/** + * Version of efi_vsnprintf() that accepts a signed buffer size + * + * @v wbuf Buffer into which to write the string + * @v swsize Size of buffer (in wide characters) + * @v fmt Format string + * @v ... Arguments corresponding to the format string + * @ret wlen Length of formatted string (in wide characters) + */ +int efi_ssnprintf ( wchar_t *wbuf, ssize_t swsize, const char *fmt, ... ) { + va_list args; + int len; + + /* Hand off to vssnprintf */ + va_start ( args, fmt ); + len = efi_vssnprintf ( wbuf, swsize, fmt, args ); + va_end ( args ); + return len; +} diff --git a/roms/ipxe/src/interface/efi/efi_timer.c b/roms/ipxe/src/interface/efi/efi_timer.c new file mode 100644 index 00000000..7a1ff786 --- /dev/null +++ b/roms/ipxe/src/interface/efi/efi_timer.c @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2008 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. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include <string.h> +#include <errno.h> +#include <limits.h> +#include <assert.h> +#include <unistd.h> +#include <ipxe/timer.h> +#include <ipxe/efi/efi.h> +#include <ipxe/efi/Protocol/Cpu.h> + +/** @file + * + * iPXE timer API for EFI + * + */ + +/** Scale factor to apply to CPU timer 0 + * + * The timer is scaled down in order to ensure that reasonable values + * for "number of ticks" don't exceed the size of an unsigned long. + */ +#define EFI_TIMER0_SHIFT 12 + +/** Calibration time */ +#define EFI_CALIBRATE_DELAY_MS 1 + +/** CPU protocol */ +static EFI_CPU_ARCH_PROTOCOL *cpu_arch; +EFI_REQUIRE_PROTOCOL ( EFI_CPU_ARCH_PROTOCOL, &cpu_arch ); + +/** + * Delay for a fixed number of microseconds + * + * @v usecs Number of microseconds for which to delay + */ +static void efi_udelay ( unsigned long usecs ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + EFI_STATUS efirc; + int rc; + + if ( ( efirc = bs->Stall ( usecs ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBG ( "EFI could not delay for %ldus: %s\n", + usecs, strerror ( rc ) ); + /* Probably screwed */ + } +} + +/** + * Get current system time in ticks + * + * @ret ticks Current time, in ticks + */ +static unsigned long efi_currticks ( void ) { + UINT64 time; + EFI_STATUS efirc; + int rc; + + /* Read CPU timer 0 (TSC) */ + if ( ( efirc = cpu_arch->GetTimerValue ( cpu_arch, 0, &time, + NULL ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBG ( "EFI could not read CPU timer: %s\n", strerror ( rc ) ); + /* Probably screwed */ + return -1UL; + } + + return ( time >> EFI_TIMER0_SHIFT ); +} + +/** + * Get number of ticks per second + * + * @ret ticks_per_sec Number of ticks per second + */ +static unsigned long efi_ticks_per_sec ( void ) { + static unsigned long ticks_per_sec = 0; + + /* Calibrate timer, if necessary. EFI does nominally provide + * the timer speed via the (optional) TimerPeriod parameter to + * the GetTimerValue() call, but it gets the speed slightly + * wrong. By up to three orders of magnitude. Not helpful. + */ + if ( ! ticks_per_sec ) { + unsigned long start; + unsigned long elapsed; + + DBG ( "Calibrating EFI timer with a %d ms delay\n", + EFI_CALIBRATE_DELAY_MS ); + start = currticks(); + mdelay ( EFI_CALIBRATE_DELAY_MS ); + elapsed = ( currticks() - start ); + ticks_per_sec = ( elapsed * ( 1000 / EFI_CALIBRATE_DELAY_MS )); + DBG ( "EFI CPU timer calibrated at %ld ticks in %d ms (%ld " + "ticks/sec)\n", elapsed, EFI_CALIBRATE_DELAY_MS, + ticks_per_sec ); + } + + return ticks_per_sec; +} + +PROVIDE_TIMER ( efi, udelay, efi_udelay ); +PROVIDE_TIMER ( efi, currticks, efi_currticks ); +PROVIDE_TIMER ( efi, ticks_per_sec, efi_ticks_per_sec ); diff --git a/roms/ipxe/src/interface/efi/efi_uaccess.c b/roms/ipxe/src/interface/efi/efi_uaccess.c new file mode 100644 index 00000000..8b429b9e --- /dev/null +++ b/roms/ipxe/src/interface/efi/efi_uaccess.c @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2008 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. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include <ipxe/uaccess.h> +#include <ipxe/efi/efi.h> + +/** @file + * + * iPXE user access API for EFI + * + */ + +PROVIDE_UACCESS_INLINE ( efi, phys_to_user ); +PROVIDE_UACCESS_INLINE ( efi, user_to_phys ); +PROVIDE_UACCESS_INLINE ( efi, virt_to_user ); +PROVIDE_UACCESS_INLINE ( efi, user_to_virt ); +PROVIDE_UACCESS_INLINE ( efi, userptr_add ); +PROVIDE_UACCESS_INLINE ( efi, memcpy_user ); +PROVIDE_UACCESS_INLINE ( efi, memmove_user ); +PROVIDE_UACCESS_INLINE ( efi, memset_user ); +PROVIDE_UACCESS_INLINE ( efi, strlen_user ); +PROVIDE_UACCESS_INLINE ( efi, memchr_user ); diff --git a/roms/ipxe/src/interface/efi/efi_umalloc.c b/roms/ipxe/src/interface/efi/efi_umalloc.c new file mode 100644 index 00000000..356efaa6 --- /dev/null +++ b/roms/ipxe/src/interface/efi/efi_umalloc.c @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2008 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. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <ipxe/umalloc.h> +#include <ipxe/efi/efi.h> + +/** @file + * + * iPXE user memory allocation API for EFI + * + */ + +/** Equivalent of NOWHERE for user pointers */ +#define UNOWHERE ( ~UNULL ) + +/** + * Reallocate external memory + * + * @v old_ptr Memory previously allocated by umalloc(), or UNULL + * @v new_size Requested size + * @ret new_ptr Allocated memory, or UNULL + * + * Calling realloc() with a new size of zero is a valid way to free a + * memory block. + */ +static userptr_t efi_urealloc ( userptr_t old_ptr, size_t new_size ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + EFI_PHYSICAL_ADDRESS phys_addr; + unsigned int new_pages, old_pages; + userptr_t new_ptr = UNOWHERE; + size_t old_size; + EFI_STATUS efirc; + int rc; + + /* Allocate new memory if necessary. If allocation fails, + * return without touching the old block. + */ + if ( new_size ) { + new_pages = ( EFI_SIZE_TO_PAGES ( new_size ) + 1 ); + if ( ( efirc = bs->AllocatePages ( AllocateAnyPages, + EfiBootServicesData, + new_pages, + &phys_addr ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBG ( "EFI could not allocate %d pages: %s\n", + new_pages, strerror ( rc ) ); + return UNULL; + } + assert ( phys_addr != 0 ); + new_ptr = phys_to_user ( phys_addr + EFI_PAGE_SIZE ); + copy_to_user ( new_ptr, -EFI_PAGE_SIZE, + &new_size, sizeof ( new_size ) ); + DBG ( "EFI allocated %d pages at %llx\n", + new_pages, phys_addr ); + } + + /* Copy across relevant part of the old data region (if any), + * then free it. Note that at this point either (a) new_ptr + * is valid, or (b) new_size is 0; either way, the memcpy() is + * valid. + */ + if ( old_ptr && ( old_ptr != UNOWHERE ) ) { + copy_from_user ( &old_size, old_ptr, -EFI_PAGE_SIZE, + sizeof ( old_size ) ); + memcpy_user ( new_ptr, 0, old_ptr, 0, + ( (old_size < new_size) ? old_size : new_size )); + old_pages = ( EFI_SIZE_TO_PAGES ( old_size ) + 1 ); + phys_addr = user_to_phys ( old_ptr, -EFI_PAGE_SIZE ); + if ( ( efirc = bs->FreePages ( phys_addr, old_pages ) ) != 0 ){ + rc = -EEFI ( efirc ); + DBG ( "EFI could not free %d pages at %llx: %s\n", + old_pages, phys_addr, strerror ( rc ) ); + /* Not fatal; we have leaked memory but successfully + * allocated (if asked to do so). + */ + } + DBG ( "EFI freed %d pages at %llx\n", old_pages, phys_addr ); + } + + return new_ptr; +} + +PROVIDE_UMALLOC ( efi, urealloc, efi_urealloc ); diff --git a/roms/ipxe/src/interface/efi/efi_utils.c b/roms/ipxe/src/interface/efi/efi_utils.c new file mode 100644 index 00000000..936ad48e --- /dev/null +++ b/roms/ipxe/src/interface/efi/efi_utils.c @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2011 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. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <ipxe/efi/efi.h> +#include <ipxe/efi/efi_pci.h> +#include <ipxe/efi/efi_utils.h> + +/** @file + * + * EFI utilities + * + */ + +/** + * Find end of device path + * + * @v path Path to device + * @ret path_end End of device path + */ +EFI_DEVICE_PATH_PROTOCOL * efi_devpath_end ( EFI_DEVICE_PATH_PROTOCOL *path ) { + + while ( path->Type != END_DEVICE_PATH_TYPE ) { + path = ( ( ( void * ) path ) + + /* There's this amazing new-fangled thing known as + * a UINT16, but who wants to use one of those? */ + ( ( path->Length[1] << 8 ) | path->Length[0] ) ); + } + + return path; +} + +/** + * Locate parent device supporting a given protocol + * + * @v device EFI device handle + * @v protocol Protocol GUID + * @v parent Parent EFI device handle to fill in + * @ret rc Return status code + */ +int efi_locate_device ( EFI_HANDLE device, EFI_GUID *protocol, + EFI_HANDLE *parent ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + union { + EFI_DEVICE_PATH_PROTOCOL *path; + void *interface; + } path; + EFI_DEVICE_PATH_PROTOCOL *devpath; + EFI_STATUS efirc; + int rc; + + /* Get 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, "EFIDEV %p %s cannot open device path: %s\n", + device, efi_handle_name ( device ), strerror ( rc ) ); + goto err_open_device_path; + } + devpath = path.path; + + /* Check for presence of specified protocol */ + if ( ( efirc = bs->LocateDevicePath ( protocol, &devpath, + parent ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( device, "EFIDEV %p %s has no parent supporting %s: %s\n", + device, efi_handle_name ( device ), + efi_guid_ntoa ( protocol ), strerror ( rc ) ); + goto err_locate_protocol; + } + + /* Success */ + rc = 0; + + err_locate_protocol: + bs->CloseProtocol ( device, &efi_device_path_protocol_guid, + efi_image_handle, device ); + err_open_device_path: + return rc; +} + +/** + * Add EFI device as child of another EFI device + * + * @v parent EFI parent device handle + * @v child EFI child device handle + * @ret rc Return status code + */ +int efi_child_add ( EFI_HANDLE parent, EFI_HANDLE child ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + void *devpath; + EFI_STATUS efirc; + int rc; + + /* Re-open the device path protocol */ + if ( ( efirc = bs->OpenProtocol ( parent, + &efi_device_path_protocol_guid, + &devpath, + efi_image_handle, child, + EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER + ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( parent, "EFIDEV %p %s could not add child", + parent, efi_handle_name ( parent ) ); + DBGC ( parent, " %p %s: %s\n", child, + efi_handle_name ( child ), strerror ( rc ) ); + DBGC_EFI_OPENERS ( parent, parent, + &efi_device_path_protocol_guid ); + return rc; + } + + DBGC2 ( parent, "EFIDEV %p %s added child", + parent, efi_handle_name ( parent ) ); + DBGC2 ( parent, " %p %s\n", child, efi_handle_name ( child ) ); + return 0; +} + +/** + * Remove EFI device as child of another EFI device + * + * @v parent EFI parent device handle + * @v child EFI child device handle + */ +void efi_child_del ( EFI_HANDLE parent, EFI_HANDLE child ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + + bs->CloseProtocol ( parent, &efi_device_path_protocol_guid, + efi_image_handle, child ); + DBGC2 ( parent, "EFIDEV %p %s removed child", + parent, efi_handle_name ( parent ) ); + DBGC2 ( parent, " %p %s\n", + child, efi_handle_name ( child ) ); +} + +/** + * Get underlying PCI device information + * + * @v device EFI device handle + * @v prefix Device name prefix + * @v dev Generic device to fill in + * @ret rc Return status code + */ +static int efi_pci_info ( EFI_HANDLE device, const char *prefix, + struct device *dev ) { + EFI_HANDLE pci_device; + struct pci_device pci; + int rc; + + /* Find parent PCI device */ + if ( ( rc = efi_locate_device ( device, &efi_pci_io_protocol_guid, + &pci_device ) ) != 0 ) { + DBGC ( device, "EFIDEV %p %s is not a PCI device: %s\n", + device, efi_handle_name ( device ), strerror ( rc ) ); + return rc; + } + + /* Get PCI device information */ + if ( ( rc = efipci_info ( pci_device, &pci ) ) != 0 ) { + DBGC ( device, "EFIDEV %p %s could not get PCI information: " + "%s\n", device, efi_handle_name ( device ), + strerror ( rc ) ); + return rc; + } + + /* Populate device information */ + memcpy ( &dev->desc, &pci.dev.desc, sizeof ( dev->desc ) ); + snprintf ( dev->name, sizeof ( dev->name ), "%s-%s", + prefix, pci.dev.name ); + + return 0; +} + +/** + * Get underlying device information + * + * @v device EFI device handle + * @v prefix Device name prefix + * @v dev Generic device to fill in + */ +void efi_device_info ( EFI_HANDLE device, const char *prefix, + struct device *dev ) { + int rc; + + /* Try getting underlying PCI device information */ + if ( ( rc = efi_pci_info ( device, prefix, dev ) ) == 0 ) + return; + + /* If we cannot get any underlying device information, fall + * back to providing information about the EFI handle. + */ + DBGC ( device, "EFIDEV %p %s could not get underlying device " + "information\n", device, efi_handle_name ( device ) ); + dev->desc.bus_type = BUS_TYPE_EFI; + snprintf ( dev->name, sizeof ( dev->name ), "%s-%p", prefix, device ); +} diff --git a/roms/ipxe/src/interface/efi/efi_wrap.c b/roms/ipxe/src/interface/efi/efi_wrap.c new file mode 100644 index 00000000..ff46b76e --- /dev/null +++ b/roms/ipxe/src/interface/efi/efi_wrap.c @@ -0,0 +1,315 @@ +/* + * Copyright (C) 2014 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. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +/** + * @file + * + * EFI image wrapping + * + */ + +#include <string.h> +#include <stdio.h> +#include <errno.h> +#include <ipxe/efi/efi.h> +#include <ipxe/efi/Protocol/LoadedImage.h> +#include <ipxe/efi/efi_wrap.h> + +/** EFI system table wrapper */ +static EFI_SYSTEM_TABLE efi_systab_wrapper; + +/** EFI boot services table wrapper */ +static EFI_BOOT_SERVICES efi_bs_wrapper; + +/** Colour for debug messages */ +#define colour &efi_systab_wrapper + +/** + * Convert EFI status code to text + * + * @v efirc EFI status code + * @ret text EFI status code text + */ +static const char * efi_status ( EFI_STATUS efirc ) { + static char buf[ 19 /* "0xXXXXXXXXXXXXXXXX" + NUL */ ]; + + switch ( efirc ) { + case EFI_SUCCESS : return "0"; + case EFI_LOAD_ERROR : return "LOAD_ERROR"; + case EFI_INVALID_PARAMETER : return "INVALID_PARAMETER"; + case EFI_UNSUPPORTED : return "UNSUPPORTED"; + case EFI_BAD_BUFFER_SIZE : return "BAD_BUFFER_SIZE"; + case EFI_BUFFER_TOO_SMALL : return "BUFFER_TOO_SMALL"; + case EFI_NOT_READY : return "NOT_READY"; + case EFI_DEVICE_ERROR : return "DEVICE_ERROR"; + case EFI_WRITE_PROTECTED : return "WRITE_PROTECTED"; + case EFI_OUT_OF_RESOURCES : return "OUT_OF_RESOURCES"; + case EFI_VOLUME_CORRUPTED : return "VOLUME_CORRUPTED"; + case EFI_VOLUME_FULL : return "VOLUME_FULL"; + case EFI_NO_MEDIA : return "NO_MEDIA"; + case EFI_MEDIA_CHANGED : return "MEDIA_CHANGED"; + case EFI_NOT_FOUND : return "NOT_FOUND"; + case EFI_ACCESS_DENIED : return "ACCESS_DENIED"; + case EFI_NO_RESPONSE : return "NO_RESPONSE"; + case EFI_NO_MAPPING : return "NO_MAPPING"; + case EFI_TIMEOUT : return "TIMEOUT"; + case EFI_NOT_STARTED : return "NOT_STARTED"; + case EFI_ALREADY_STARTED : return "ALREADY_STARTED"; + case EFI_ABORTED : return "ABORTED"; + case EFI_ICMP_ERROR : return "ICMP_ERROR"; + case EFI_TFTP_ERROR : return "TFTP_ERROR"; + case EFI_PROTOCOL_ERROR : return "PROTOCOL_ERROR"; + case EFI_INCOMPATIBLE_VERSION : return "INCOMPATIBLE_VERSION"; + case EFI_SECURITY_VIOLATION : return "SECURITY_VIOLATION"; + case EFI_CRC_ERROR : return "CRC_ERROR"; + case EFI_END_OF_MEDIA : return "END_OF_MEDIA"; + case EFI_END_OF_FILE : return "END_OF_FILE"; + case EFI_INVALID_LANGUAGE : return "INVALID_LANGUAGE"; + case EFI_COMPROMISED_DATA : return "COMPROMISED_DATA"; + case EFI_WARN_UNKNOWN_GLYPH : return "WARN_UNKNOWN_GLYPH"; + case EFI_WARN_DELETE_FAILURE : return "WARN_DELETE_FAILURE"; + case EFI_WARN_WRITE_FAILURE : return "WARN_WRITE_FAILURE"; + case EFI_WARN_BUFFER_TOO_SMALL : return "WARN_BUFFER_TOO_SMALL"; + case EFI_WARN_STALE_DATA : return "WARN_STALE_DATA"; + default: + snprintf ( buf, sizeof ( buf ), "%#lx", + ( unsigned long ) efirc ); + return buf; + } +} + +/** + * Wrap HandleProtocol() + * + */ +static EFI_STATUS EFIAPI +efi_handle_protocol_wrapper ( EFI_HANDLE handle, EFI_GUID *protocol, + VOID **interface ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + void *retaddr = __builtin_return_address ( 0 ); + EFI_STATUS efirc; + + DBGC ( colour, "HandleProtocol ( %p %s, %s, ... ) ", handle, + efi_handle_name ( handle ), efi_guid_ntoa ( protocol ) ); + efirc = bs->HandleProtocol ( handle, protocol, interface ); + DBGC ( colour, "= %s ( %p ) -> %p\n", + efi_status ( efirc ), *interface, retaddr ); + return efirc; +} + +/** + * Wrap LocateHandle() + * + */ +static EFI_STATUS EFIAPI +efi_locate_handle_wrapper ( EFI_LOCATE_SEARCH_TYPE search_type, + EFI_GUID *protocol, VOID *search_key, + UINTN *buffer_size, EFI_HANDLE *buffer ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + void *retaddr = __builtin_return_address ( 0 ); + EFI_STATUS efirc; + + DBGC ( colour, "LocateHandle ( %d, %s, ..., %zd, ... ) ", search_type, + efi_guid_ntoa ( protocol ), ( ( size_t ) *buffer_size ) ); + efirc = bs->LocateHandle ( search_type, protocol, search_key, + buffer_size, buffer ); + DBGC ( colour, "= %s ( %zd ) -> %p\n", + efi_status ( efirc ), ( ( size_t ) *buffer_size ), retaddr ); + return efirc; +} + +/** + * Wrap LocateDevicePath() + * + */ +static EFI_STATUS EFIAPI +efi_locate_device_path_wrapper ( EFI_GUID *protocol, + EFI_DEVICE_PATH_PROTOCOL **device_path, + EFI_HANDLE *device ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + void *retaddr = __builtin_return_address ( 0 ); + EFI_STATUS efirc; + + DBGC ( colour, "LocateDevicePath ( %s, %s, ... ) ", + efi_guid_ntoa ( protocol ), efi_devpath_text ( *device_path ) ); + efirc = bs->LocateDevicePath ( protocol, device_path, device ); + DBGC ( colour, "= %s ( %p, ", + efi_status ( efirc ), efi_devpath_text ( *device_path ) ); + DBGC ( colour, "%p %s ) -> %p\n", + *device, efi_handle_name ( *device ), retaddr ); + return efirc; +} + +/** + * Wrap LoadImage() + * + */ +static EFI_STATUS EFIAPI +efi_load_image_wrapper ( BOOLEAN boot_policy, EFI_HANDLE parent_image_handle, + EFI_DEVICE_PATH_PROTOCOL *device_path, + VOID *source_buffer, UINTN source_size, + EFI_HANDLE *image_handle ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + void *retaddr = __builtin_return_address ( 0 ); + EFI_STATUS efirc; + + DBGC ( colour, "LoadImage ( %d, %p %s, ", boot_policy, + parent_image_handle, efi_handle_name ( parent_image_handle ) ); + DBGC ( colour, "%s, %p, %#llx, ... ) ", + efi_devpath_text ( device_path ), source_buffer, + ( ( unsigned long long ) source_size ) ); + efirc = bs->LoadImage ( boot_policy, parent_image_handle, device_path, + source_buffer, source_size, image_handle ); + DBGC ( colour, "= %s ( ", efi_status ( efirc ) ); + if ( efirc == 0 ) { + DBGC ( colour, "%p %s ", *image_handle, + efi_handle_name ( *image_handle ) ); + } + DBGC ( colour, ") -> %p\n", retaddr ); + + /* Wrap the new image */ + if ( efirc == 0 ) + efi_wrap ( *image_handle ); + + return efirc; +} + +/** + * Wrap ExitBootServices() + * + */ +static EFI_STATUS EFIAPI +efi_exit_boot_services_wrapper ( EFI_HANDLE image_handle, UINTN map_key ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + void *retaddr = __builtin_return_address ( 0 ); + EFI_STATUS efirc; + + DBGC ( colour, "ExitBootServices ( %p %s, %#llx ) ", + image_handle, efi_handle_name ( image_handle ), + ( ( unsigned long long ) map_key ) ); + efirc = bs->ExitBootServices ( image_handle, map_key ); + DBGC ( colour, "= %s -> %p\n", efi_status ( efirc ), retaddr ); + return efirc; +} + +/** + * Wrap OpenProtocol() + * + */ +static EFI_STATUS EFIAPI +efi_open_protocol_wrapper ( EFI_HANDLE handle, EFI_GUID *protocol, + VOID **interface, EFI_HANDLE agent_handle, + EFI_HANDLE controller_handle, UINT32 attributes ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + void *retaddr = __builtin_return_address ( 0 ); + EFI_STATUS efirc; + + DBGC ( colour, "OpenProtocol ( %p %s, %s, ..., ", handle, + efi_handle_name ( handle ), efi_guid_ntoa ( protocol ) ); + DBGC ( colour, "%p %s, ", agent_handle, + efi_handle_name ( agent_handle ) ); + DBGC ( colour, "%p %s, %#x ) ", controller_handle, + efi_handle_name ( controller_handle ), attributes ); + efirc = bs->OpenProtocol ( handle, protocol, interface, agent_handle, + controller_handle, attributes ); + DBGC ( colour, "= %s ( %p ) -> %p\n", + efi_status ( efirc ), *interface, retaddr ); + return efirc; +} + +/** + * Wrap LocateProtocol() + * + */ +static EFI_STATUS EFIAPI +efi_locate_protocol_wrapper ( EFI_GUID *protocol, VOID *registration, + VOID **interface ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + void *retaddr = __builtin_return_address ( 0 ); + EFI_STATUS efirc; + + DBGC ( colour, "LocateProtocol ( %s, %p, ... ) ", + efi_guid_ntoa ( protocol ), registration ); + efirc = bs->LocateProtocol ( protocol, registration, interface ); + DBGC ( colour, "= %s ( %p ) -> %p\n", + efi_status ( efirc ), *interface, retaddr ); + return efirc; +} + +/** + * Wrap the calls made by a loaded image + * + * @v handle Image handle + */ + void efi_wrap ( EFI_HANDLE handle ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + union { + EFI_LOADED_IMAGE_PROTOCOL *image; + void *intf; + } loaded; + EFI_STATUS efirc; + int rc; + + /* Do nothing unless debugging is enabled */ + if ( ! DBG_LOG ) + return; + + /* Populate table wrappers */ + memcpy ( &efi_systab_wrapper, efi_systab, + sizeof ( efi_systab_wrapper ) ); + memcpy ( &efi_bs_wrapper, bs, sizeof ( efi_bs_wrapper ) ); + efi_systab_wrapper.BootServices = &efi_bs_wrapper; + efi_bs_wrapper.HandleProtocol = efi_handle_protocol_wrapper; + efi_bs_wrapper.LocateHandle = efi_locate_handle_wrapper; + efi_bs_wrapper.LocateDevicePath = efi_locate_device_path_wrapper; + efi_bs_wrapper.LoadImage = efi_load_image_wrapper; + efi_bs_wrapper.ExitBootServices = efi_exit_boot_services_wrapper; + efi_bs_wrapper.OpenProtocol = efi_open_protocol_wrapper; + efi_bs_wrapper.LocateProtocol = efi_locate_protocol_wrapper; + + /* Open loaded image protocol */ + if ( ( efirc = bs->OpenProtocol ( handle, + &efi_loaded_image_protocol_guid, + &loaded.intf, efi_image_handle, NULL, + EFI_OPEN_PROTOCOL_GET_PROTOCOL ))!=0){ + rc = -EEFI ( efirc ); + DBGC ( colour, "Could not get loaded image protocol for %p %s: " + "%s\n", handle, efi_handle_name ( handle ), + strerror ( rc ) ); + return; + } + + /* Provide system table wrapper to image */ + loaded.image->SystemTable = &efi_systab_wrapper; + DBGC ( colour, "Wrapped image %p %s at base %p has protocols:\n", + handle, efi_handle_name ( handle ), loaded.image->ImageBase ); + DBGC_EFI_PROTOCOLS ( colour, handle ); + DBGC ( colour, "Parent image %p %s\n", loaded.image->ParentHandle, + efi_handle_name ( loaded.image->ParentHandle ) ); + DBGC ( colour, "Device %p %s ", loaded.image->DeviceHandle, + efi_handle_name ( loaded.image->DeviceHandle ) ); + DBGC ( colour, "file %p %s\n", loaded.image->FilePath, + efi_devpath_text ( loaded.image->FilePath ) ); + + /* Close loaded image protocol */ + bs->CloseProtocol ( handle, &efi_loaded_image_protocol_guid, + efi_image_handle, NULL ); +} |