diff options
Diffstat (limited to 'roms/ipxe/src/interface')
36 files changed, 10197 insertions, 0 deletions
diff --git a/roms/ipxe/src/interface/bofm/bofm.c b/roms/ipxe/src/interface/bofm/bofm.c new file mode 100644 index 00000000..b0e92b27 --- /dev/null +++ b/roms/ipxe/src/interface/bofm/bofm.c @@ -0,0 +1,341 @@ +/* + * 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 <stdint.h> +#include <string.h> +#include <errno.h> +#include <ipxe/uaccess.h> +#include <ipxe/list.h> +#include <ipxe/ethernet.h> +#include <ipxe/bofm.h> + +/** @file + * + * IBM BladeCenter Open Fabric Manager (BOFM) + * + */ + +/** List of BOFM devices */ +static LIST_HEAD ( bofmdevs ); + +/** + * Register BOFM device + * + * @v bofm		BOFM device + * @ret rc		Return status code + */ +int bofm_register ( struct bofm_device *bofm ) { + +	list_add ( &bofm->list, &bofmdevs ); +	DBG ( "BOFM: " PCI_FMT " registered using driver \"%s\"\n", +	      PCI_ARGS ( bofm->pci ), bofm->pci->id->name ); +	return 0; +} + +/** + * Unregister BOFM device + * + * @v bofm		BOFM device + */ +void bofm_unregister ( struct bofm_device *bofm ) { + +	list_del ( &bofm->list ); +	DBG ( "BOFM: " PCI_FMT " unregistered\n", PCI_ARGS ( bofm->pci ) ); +} + +/** + * Find BOFM device matching PCI bus:dev.fn address + * + * @v busdevfn		PCI bus:dev.fn address + * @ret bofm		BOFM device, or NULL + */ +static struct bofm_device * bofm_find_busdevfn ( unsigned int busdevfn ) { +	struct bofm_device *bofm; + +	list_for_each_entry ( bofm, &bofmdevs, list ) { +		if ( bofm->pci->busdevfn == busdevfn ) +			return bofm; +	} +	return NULL; +} + +/** + * Find BOFM driver for PCI device + * + * @v pci		PCI device + * @ret rc		Return status code + */ +int bofm_find_driver ( struct pci_device *pci ) { +	struct pci_driver *driver; +	struct pci_device_id *id; +	unsigned int i; + +	for_each_table_entry ( driver, BOFM_DRIVERS ) { +		for ( i = 0 ; i < driver->id_count ; i++ ) { +			id = &driver->ids[i]; +			if ( ( id->vendor == pci->vendor ) && +			     ( id->device == pci->device ) ) { +				pci_set_driver ( pci, driver, id ); +				return 0; +			} +		} +	} +	return -ENOENT; +} + +/** + * Probe PCI device for BOFM driver + * + * @v pci		PCI device + * @ret rc		Return status code + */ +static int bofm_probe ( struct pci_device *pci ) { +	int rc; + +	/* Probe device */ +	if ( ( rc = pci_probe ( pci ) ) != 0 ) { +		DBG ( "BOFM: " PCI_FMT " could not load driver: %s\n", +		      PCI_ARGS ( pci ), strerror ( rc ) ); +		return rc; +	} + +	return 0; +} + +/** + * Remove PCI device + * + * @v pci		PCI device + */ +static void bofm_remove ( struct pci_device *pci ) { + +	/* Note that the IBM BIOS may re-read the expansion ROM after +	 * the BOFM initialisation call.  The BOFM driver must ensure +	 * that the card is left in a state in which expansion ROM +	 * reads will succeed.  (For example, if a card contains an +	 * embedded CPU that may issue reads to the same underlying +	 * flash device, and these reads are not locked against reads +	 * via the expansion ROM BAR, then the CPU must be stopped.) +	 * +	 * If this is not done, then occasional corrupted reads from +	 * the expansion ROM will be seen, and the BIOS may complain +	 * about a ROM checksum error. +	 */ +	pci_remove ( pci ); +	DBG ( "BOFM: " PCI_FMT " removed\n", PCI_ARGS ( pci ) ); +} + +/** + * Locate BOFM table section + * + * @v bofmtab		BOFM table + * @v len		Length of BOFM table + * @v magic		Section magic + * @v bofmsec		BOFM section header to fill in + * @ret offset		Offset to section, or 0 if not found + */ +static size_t bofm_locate_section ( userptr_t bofmtab, size_t len, +				    uint32_t magic, +				    struct bofm_section_header *bofmsec ) { +	size_t offset = sizeof ( struct bofm_global_header ); + +	while ( offset < len ) { +		copy_from_user ( bofmsec, bofmtab, offset, +				 sizeof ( *bofmsec ) ); +		if ( bofmsec->magic == magic ) +			return offset; +		if ( bofmsec->magic == BOFM_DONE_MAGIC ) +			break; +		offset += ( sizeof ( *bofmsec ) + bofmsec->length ); +	} +	return 0; +} + +/** + * Process BOFM Ethernet parameter entry + * + * @v bofm		BOFM device + * @v en		EN parameter entry + * @ret rc		Return status code + */ +static int bofm_en ( struct bofm_device *bofm, struct bofm_en *en ) { +	uint8_t mac[6]; +	int rc; + +	/* Retrieve current MAC address */ +	if ( ( rc = bofm->op->harvest ( bofm, en->mport, mac ) ) != 0 ) { +		DBG ( "BOFM: " PCI_FMT " mport %d could not harvest: %s\n", +		      PCI_ARGS ( bofm->pci ), en->mport, strerror ( rc ) ); +		return rc; +	} + +	/* Harvest MAC address if necessary */ +	if ( en->options & BOFM_EN_RQ_HVST_MASK ) { +		DBG ( "BOFM: " PCI_FMT " mport %d harvested MAC %s\n", +		      PCI_ARGS ( bofm->pci ), en->mport, eth_ntoa ( mac ) ); +		memcpy ( en->mac_a, mac, sizeof ( en->mac_a ) ); +		en->options |= ( BOFM_EN_EN_A | BOFM_EN_HVST ); +	} + +	/* Mark as changed if necessary */ +	if ( ( en->options & BOFM_EN_EN_A ) && +	     ( memcmp ( en->mac_a, mac, sizeof ( en->mac_a ) ) != 0 ) ) { +		DBG ( "BOFM: " PCI_FMT " mport %d MAC %s", +		      PCI_ARGS ( bofm->pci ), en->mport, eth_ntoa ( mac ) ); +		DBG ( " changed to %s\n", eth_ntoa ( en->mac_a ) ); +		en->options |= BOFM_EN_CHG_CHANGED; +	} + +	/* Apply MAC address if necessary */ +	if ( ( en->options & BOFM_EN_EN_A ) && +	     ( en->options & BOFM_EN_USAGE_ENTRY ) && +	     ( ! ( en->options & BOFM_EN_USAGE_HARVEST ) ) ) { +		DBG ( "BOFM: " PCI_FMT " mport %d applied MAC %s\n", +		      PCI_ARGS ( bofm->pci ), en->mport, +		      eth_ntoa ( en->mac_a ) ); +		memcpy ( mac, en->mac_a, sizeof ( mac ) ); +	} + +	/* Store MAC address */ +	if ( ( rc = bofm->op->update ( bofm, en->mport, mac ) ) != 0 ) { +		DBG ( "BOFM: " PCI_FMT " mport %d could not update: %s\n", +		      PCI_ARGS ( bofm->pci ), en->mport, strerror ( rc ) ); +		return rc; +	} + +	return 0; +} + +/** + * Process BOFM table + * + * @v bofmtab		BOFM table + * @v pci		PCI device + * @ret bofmrc		BOFM return status + */ +int bofm ( userptr_t bofmtab, struct pci_device *pci ) { +	struct bofm_global_header bofmhdr; +	struct bofm_section_header bofmsec; +	struct bofm_en en; +	struct bofm_device *bofm; +	size_t en_region_offset; +	size_t en_offset; +	int skip; +	int rc; +	int bofmrc; + +	/* Read BOFM structure */ +	copy_from_user ( &bofmhdr, bofmtab, 0, sizeof ( bofmhdr ) ); +	if ( bofmhdr.magic != BOFM_IOAA_MAGIC ) { +		DBG ( "BOFM: invalid table signature " BOFM_MAGIC_FMT "\n", +		      BOFM_MAGIC_ARGS ( bofmhdr.magic ) ); +		bofmrc = BOFM_ERR_INVALID_ACTION; +		goto err_bad_signature; +	} +	DBG ( "BOFM: " BOFM_MAGIC_FMT " (profile \"%s\")\n", +	      BOFM_MAGIC_ARGS ( bofmhdr.action ), bofmhdr.profile ); + +	/* Determine whether or not we should skip normal POST +	 * initialisation. +	 */ +	switch ( bofmhdr.action ) { +	case BOFM_ACTION_UPDT: +	case BOFM_ACTION_DFLT: +	case BOFM_ACTION_HVST: +		skip = BOFM_SKIP_INIT; +		break; +	case BOFM_ACTION_PARM: +	case BOFM_ACTION_NONE: +		skip = 0; +		break; +	default: +		DBG ( "BOFM: invalid action " BOFM_MAGIC_FMT "\n", +		      BOFM_MAGIC_ARGS ( bofmhdr.action ) ); +		bofmrc = BOFM_ERR_INVALID_ACTION; +		goto err_bad_action; +	} + +	/* Find BOFM driver */ +	if ( ( rc = bofm_find_driver ( pci ) ) != 0 ) { +		DBG ( "BOFM: " PCI_FMT " has no driver\n", PCI_ARGS ( pci ) ); +		bofmrc = BOFM_ERR_DEVICE_ERROR; +		goto err_find_driver; +	} + +	/* Probe driver for PCI device */ +	if ( ( rc = bofm_probe ( pci ) ) != 0 ) { +		bofmrc = BOFM_ERR_DEVICE_ERROR; +		goto err_probe; +	} + +	/* Locate EN section, if present */ +	en_region_offset = bofm_locate_section ( bofmtab, bofmhdr.length, +						 BOFM_EN_MAGIC, &bofmsec ); +	if ( ! en_region_offset ) { +		DBG ( "BOFM: No EN section found\n" ); +		bofmrc = ( BOFM_SUCCESS | skip ); +		goto err_no_en_section; +	} + +	/* Iterate through EN entries */ +	for ( en_offset = ( en_region_offset + sizeof ( bofmsec ) ) ; +	      en_offset < ( en_region_offset + sizeof ( bofmsec ) + +			    bofmsec.length ) ; en_offset += sizeof ( en ) ) { +		copy_from_user ( &en, bofmtab, en_offset, sizeof ( en ) ); +		DBG2 ( "BOFM: EN entry found:\n" ); +		DBG2_HDA ( en_offset, &en, sizeof ( en ) ); +		if ( ( en.options & BOFM_EN_MAP_MASK ) != BOFM_EN_MAP_PFA ) { +			DBG ( "BOFM: slot %d port %d has no PCI mapping\n", +			      en.slot, ( en.port + 1 ) ); +			continue; +		} +		DBG ( "BOFM: slot %d port %d%s is " PCI_FMT " mport %d\n", +		      en.slot, ( en.port + 1 ), +		      ( ( en.slot || en.port ) ? "" : "(?)" ), +		      PCI_BUS ( en.busdevfn ), PCI_SLOT ( en.busdevfn ), +		      PCI_FUNC ( en.busdevfn ), en.mport ); +		bofm = bofm_find_busdevfn ( en.busdevfn ); +		if ( ! bofm ) { +			DBG ( "BOFM: " PCI_FMT " mport %d ignored\n", +			      PCI_BUS ( en.busdevfn ), PCI_SLOT ( en.busdevfn ), +			      PCI_FUNC ( en.busdevfn ), en.mport ); +			continue; +		} +		if ( ( rc = bofm_en ( bofm, &en ) ) == 0 ) { +			en.options |= BOFM_EN_CSM_SUCCESS; +		} else { +			en.options |= BOFM_EN_CSM_FAILED; +		} +		DBG2 ( "BOFM: EN entry after processing:\n" ); +		DBG2_HDA ( en_offset, &en, sizeof ( en ) ); +		copy_to_user ( bofmtab, en_offset, &en, sizeof ( en ) ); +	} + +	bofmrc = ( BOFM_SUCCESS | skip ); + + err_no_en_section: +	bofm_remove ( pci ); + err_probe: + err_find_driver: + err_bad_action: + err_bad_signature: +	return bofmrc; +} 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 ); +} diff --git a/roms/ipxe/src/interface/linux/linux_console.c b/roms/ipxe/src/interface/linux/linux_console.c new file mode 100644 index 00000000..5105eaa9 --- /dev/null +++ b/roms/ipxe/src/interface/linux/linux_console.c @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2010 Piotr Jaroszyński <p.jaroszynski@gmail.com> + * + * 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); + +/** @file + * + * Linux console implementation. + * + */ + +#include <ipxe/console.h> + +#include <ipxe/init.h> +#include <ipxe/keys.h> +#include <linux_api.h> + +#include <linux/termios.h> +#include <asm/errno.h> + +#include <config/console.h> + +/* Set default console usage if applicable */ +#if ! ( defined ( CONSOLE_LINUX ) && CONSOLE_EXPLICIT ( CONSOLE_LINUX ) ) +#undef CONSOLE_LINUX +#define CONSOLE_LINUX ( CONSOLE_USAGE_ALL & ~CONSOLE_USAGE_LOG ) +#endif + +static void linux_console_putchar(int c) +{ +	/* write to stdout */ +	if (linux_write(1, &c, 1) != 1) +		DBG("linux_console write failed (%s)\n", linux_strerror(linux_errno)); +} + +static int linux_console_getchar() +{ +	char c; + +	/* read from stdin */ +	if (linux_read(0, &c, 1) < 0) { +		DBG("linux_console read failed (%s)\n", linux_strerror(linux_errno)); +		return 0; +	} +	/* backspace seems to be returned as ascii del, map it here */ +	if (c == 0x7f) +		return KEY_BACKSPACE; +	else +		return c; +} + +static int linux_console_iskey() +{ +	struct pollfd pfd; +	pfd.fd = 0; +	pfd.events = POLLIN; + +	/* poll for data to be read on stdin */ +	if (linux_poll(&pfd, 1, 0) == -1) { +		DBG("linux_console poll failed (%s)\n", linux_strerror(linux_errno)); +		return 0; +	} + +	if (pfd.revents & POLLIN) +		return 1; +	else +		return 0; +} + +struct console_driver linux_console __console_driver = { +	.disabled = 0, +	.putchar = linux_console_putchar, +	.getchar = linux_console_getchar, +	.iskey = linux_console_iskey, +	.usage = CONSOLE_LINUX, +}; + +static int linux_tcgetattr(int fd, struct termios *termios_p) +{ +	return linux_ioctl(fd, TCGETS, termios_p); +} + +static int linux_tcsetattr(int fd, int optional_actions, const struct termios *termios_p) +{ +	unsigned long int cmd; + +	switch (optional_actions) +	{ +		case TCSANOW: +			cmd = TCSETS; +			break; +		case TCSADRAIN: +			cmd = TCSETSW; +			break; +		case TCSAFLUSH: +			cmd = TCSETSF; +			break; +		default: +			linux_errno = EINVAL; +			return -1; +	} + +	return linux_ioctl(fd, cmd, termios_p); +} + +/** Saved termios attributes */ +static struct termios saved_termios; + +/** Setup the terminal for our use */ +static void linux_console_startup(void) +{ +	struct termios t; + +	if (linux_tcgetattr(0, &t)) { +		DBG("linux_console tcgetattr failed (%s)", linux_strerror(linux_errno)); +		return; +	} + +	saved_termios = t; + +	/* Disable canonical mode and echo. Let readline handle that */ +	t.c_lflag &= ~(ECHO | ICANON); +	/* stop ^C from sending a signal */ +	t.c_cc[VINTR] = 0; + +	if (linux_tcsetattr(0, TCSAFLUSH, &t)) +		DBG("linux_console tcsetattr failed (%s)", linux_strerror(linux_errno)); +} + +/** Restores original terminal attributes on shutdown */ +static void linux_console_shutdown(int flags __unused) +{ +	if (linux_tcsetattr(0, TCSAFLUSH, &saved_termios)) +		DBG("linux_console tcsetattr failed (%s)", linux_strerror(linux_errno)); +} + +struct startup_fn linux_console_startup_fn __startup_fn(STARTUP_EARLY) = { +	.startup = linux_console_startup, +	.shutdown = linux_console_shutdown, +}; diff --git a/roms/ipxe/src/interface/linux/linux_entropy.c b/roms/ipxe/src/interface/linux/linux_entropy.c new file mode 100644 index 00000000..4671a48d --- /dev/null +++ b/roms/ipxe/src/interface/linux/linux_entropy.c @@ -0,0 +1,97 @@ +/* + * 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 + * + * Linux entropy source + * + */ + +#include <stdint.h> +#include <errno.h> +#include <linux_api.h> +#include <ipxe/entropy.h> + +/** Entropy source filename */ +static const char entropy_filename[] = "/dev/random"; + +/** Entropy source file handle */ +static int entropy_fd; + +/** + * Enable entropy gathering + * + * @ret rc		Return status code + */ +static int linux_entropy_enable ( void ) { + +	/* Open entropy source */ +	entropy_fd = linux_open ( entropy_filename, O_RDONLY ); +	if ( entropy_fd < 0 ) { +		DBGC ( &entropy_fd, "ENTROPY could not open %s: %s\n", +		       entropy_filename, linux_strerror ( linux_errno ) ); +		return entropy_fd; +	} + +	return 0; +} + +/** + * Disable entropy gathering + * + */ +static void linux_entropy_disable ( void ) { + +	/* Close entropy source */ +	linux_close ( entropy_fd ); +} + +/** + * Get noise sample + * + * @ret noise		Noise sample + * @ret rc		Return status code + */ +static int linux_get_noise ( noise_sample_t *noise ) { +	uint8_t byte; +	ssize_t len; + +	/* Read a single byte from entropy source */ +	len = linux_read ( entropy_fd, &byte, sizeof ( byte ) ); +	if ( len < 0 ) { +		DBGC ( &entropy_fd, "ENTROPY could not read from %s: %s\n", +		       entropy_filename, linux_strerror ( linux_errno ) ); +		return len; +	} +	if ( len == 0 ) { +		DBGC ( &entropy_fd, "ENTROPY EOF on reading from %s: %s\n", +		       entropy_filename, linux_strerror ( linux_errno ) ); +		return -EPIPE; +	} +	*noise = byte; + +	return 0; +} + +PROVIDE_ENTROPY_INLINE ( linux, min_entropy_per_sample ); +PROVIDE_ENTROPY ( linux, entropy_enable, linux_entropy_enable ); +PROVIDE_ENTROPY ( linux, entropy_disable, linux_entropy_disable ); +PROVIDE_ENTROPY ( linux, get_noise, linux_get_noise ); diff --git a/roms/ipxe/src/interface/linux/linux_nap.c b/roms/ipxe/src/interface/linux/linux_nap.c new file mode 100644 index 00000000..f1d3cd96 --- /dev/null +++ b/roms/ipxe/src/interface/linux/linux_nap.c @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2010 Piotr Jaroszyński <p.jaroszynski@gmail.com> + * + * 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/nap.h> + +#include <linux_api.h> + +/** @file + * + * iPXE CPU sleeping API for linux + * + */ + +/** + * Sleep until next CPU interrupt + * + */ +static void linux_cpu_nap(void) +{ +	linux_usleep(0); +} + +PROVIDE_NAP(linux, cpu_nap, linux_cpu_nap); diff --git a/roms/ipxe/src/interface/linux/linux_pci.c b/roms/ipxe/src/interface/linux/linux_pci.c new file mode 100644 index 00000000..cbd825c1 --- /dev/null +++ b/roms/ipxe/src/interface/linux/linux_pci.c @@ -0,0 +1,185 @@ +/* + * 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 (at your option) 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 <errno.h> +#include <byteswap.h> +#include <linux_api.h> +#include <ipxe/linux.h> +#include <ipxe/pci.h> + +/** @file + * + * iPXE PCI API for Linux + * + */ + +/** + * Open PCI configuration space + * + * @v pci		PCI device + * @v flags		Access mode flags + * @v where		Address within configuration space + * @ret fd		File handle, or negative error + */ +static int linux_pci_open ( struct pci_device *pci, int flags, +			    unsigned long where ) { +	char filename[ 22 /* "/proc/bus/pci/xx/xx.x" + NUL */ ]; +	int fd; +	int rc; + +	/* Construct filename */ +	snprintf ( filename, sizeof ( filename ), "/proc/bus/pci/%02x/%02x.%x", +		   PCI_BUS ( pci->busdevfn ), PCI_SLOT ( pci->busdevfn ), +		   PCI_FUNC ( pci->busdevfn ) ); + +	/* Open file */ +	fd = linux_open ( filename, flags ); +	if ( fd < 0 ) { +		DBGC ( pci, "PCI could not open %s: %s\n", filename, +		       linux_strerror ( linux_errno ) ); +		rc = -ELINUX ( linux_errno ); +		goto err_open; +	} + +	/* Seek to location */ +	if ( linux_lseek ( fd, where, SEEK_SET ) < 0 ) { +		DBGC ( pci, "PCI could not seek to %s offset %#02lx: %s\n", +		       filename, where, linux_strerror ( linux_errno ) ); +		rc = -ELINUX ( linux_errno ); +		goto err_seek; +	} + +	return fd; + + err_seek: +	linux_close ( fd ); + err_open: +	return rc; +} + +/** + * Read from PCI configuration space + * + * @v pci		PCI device + * @v where		Address within configuration space + * @v value		Data buffer + * @v len		Length to read + * @ret rc		Return status code + */ +int linux_pci_read ( struct pci_device *pci, unsigned long where, +		     unsigned long *value, size_t len ) { +	uint32_t tmp = 0; +	int fd; +	int check_len; +	int rc; + +	/* Return "missing device" in case of error */ +	*value = -1UL; + +	/* Open configuration space */ +	fd = linux_pci_open ( pci, O_RDONLY, where ); +	if ( fd < 0 ) { +		rc = fd; +		goto err_open; +	} + +	/* Read value */ +	check_len = linux_read ( fd, &tmp, len ); +	if ( check_len < 0 ) { +		DBGC ( pci, "PCI could not read from " PCI_FMT " %#02lx+%#zx: " +		       "%s\n", PCI_ARGS ( pci ), where, len, +		       linux_strerror ( linux_errno ) ); +		rc = -ELINUX ( linux_errno ); +		goto err_read; +	} +	if ( ( size_t ) check_len != len ) { +		DBGC ( pci, "PCI read only %#x bytes from " PCI_FMT +		       " %#02lx+%#zx\n", check_len, PCI_ARGS ( pci ), +		       where, len ); +		rc = -EIO; +		goto err_read; +	} + +	/* Return value */ +	*value = le32_to_cpu ( tmp ); + +	/* Success */ +	rc = 0; + + err_read: +	linux_close ( fd ); + err_open: +	return rc; +} + +/** + * Write to PCI configuration space + * + * @v pci		PCI device + * @v where		Address within configuration space + * @v value		Value to write + * @v len		Length of value + * @ret rc		Return status code + */ +int linux_pci_write ( struct pci_device *pci, unsigned long where, +		      unsigned long value, size_t len ) { +	uint32_t tmp; +	int fd; +	int check_len; +	int rc; + +	/* Open configuration space */ +	fd = linux_pci_open ( pci, O_WRONLY, where ); +	if ( fd < 0 ) { +		rc = fd; +		goto err_open; +	} + +	/* Prepare value for writing */ +	tmp = cpu_to_le32 ( value ); +	assert ( len <= sizeof ( tmp ) ); + +	/* Write value */ +	check_len = linux_write ( fd, &tmp, len ); +	if ( check_len < 0 ) { +		DBGC ( pci, "PCI could not write to " PCI_FMT " %#02lx+%#zx: " +		       "%s\n", PCI_ARGS ( pci ), where, len, +		       linux_strerror ( linux_errno ) ); +		rc = -ELINUX ( linux_errno ); +		goto err_write; +	} +	if ( ( size_t ) check_len != len ) { +		DBGC ( pci, "PCI wrote only %#x bytes to " PCI_FMT +		       " %#02lx+%#zx\n", check_len, PCI_ARGS ( pci ), +		       where, len ); +		rc = -EIO; +		goto err_write; +	} + +	/* Success */ +	rc = 0; + + err_write: +	linux_close ( fd ); + err_open: +	return rc; +} diff --git a/roms/ipxe/src/interface/linux/linux_smbios.c b/roms/ipxe/src/interface/linux/linux_smbios.c new file mode 100644 index 00000000..6e5174d2 --- /dev/null +++ b/roms/ipxe/src/interface/linux/linux_smbios.c @@ -0,0 +1,115 @@ +/* + * 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 ); + +#include <errno.h> +#include <linux_api.h> +#include <ipxe/linux.h> +#include <ipxe/smbios.h> + +/** SMBIOS filename */ +static const char smbios_filename[] = "/dev/mem"; + +/** SMBIOS entry point scan region start address */ +#define SMBIOS_ENTRY_START 0xf0000 + +/** SMBIOS entry point scan region length */ +#define SMBIOS_ENTRY_LEN 0x10000 + +/** SMBIOS mapping alignment */ +#define SMBIOS_ALIGN 0x1000 + +/** + * Find SMBIOS + * + * @v smbios		SMBIOS entry point descriptor structure to fill in + * @ret rc		Return status code + */ +static int linux_find_smbios ( struct smbios *smbios ) { +	struct smbios_entry entry; +	void *entry_mem; +	void *smbios_mem; +	size_t smbios_offset; +	size_t smbios_indent; +	size_t smbios_len; +	int fd; +	int rc; + +	/* Open SMBIOS file */ +	fd = linux_open ( smbios_filename, O_RDONLY ); +	if ( fd < 0 ) { +		rc = -ELINUX ( linux_errno ); +		DBGC ( smbios, "SMBIOS could not open %s: %s\n", +		       smbios_filename, linux_strerror ( linux_errno ) ); +		goto err_open; +	} + +	/* Map the region potentially containing the SMBIOS entry point */ +	entry_mem = linux_mmap ( NULL, SMBIOS_ENTRY_LEN, PROT_READ, MAP_SHARED, +				 fd, SMBIOS_ENTRY_START ); +	if ( entry_mem == MAP_FAILED ) { +		rc = -ELINUX ( linux_errno ); +		DBGC ( smbios, "SMBIOS could not mmap %s (%#x+%#x): %s\n", +		       smbios_filename, SMBIOS_ENTRY_START, SMBIOS_ENTRY_LEN, +		       linux_strerror ( linux_errno ) ); +		goto err_mmap_entry; +	} + +	/* Scan for the SMBIOS entry point */ +	if ( ( rc = find_smbios_entry ( virt_to_user ( entry_mem ), +					SMBIOS_ENTRY_LEN, &entry ) ) != 0 ) +		goto err_find_entry; + +	/* Map the region containing the SMBIOS structures */ +	smbios_indent = ( entry.smbios_address & ( SMBIOS_ALIGN - 1 ) ); +	smbios_offset = ( entry.smbios_address - smbios_indent ); +	smbios_len = ( entry.smbios_len + smbios_indent ); +	smbios_mem = linux_mmap ( NULL, smbios_len, PROT_READ, MAP_SHARED, +				  fd, smbios_offset ); +	if ( smbios_mem == MAP_FAILED ) { +		rc = -ELINUX ( linux_errno ); +		DBGC ( smbios, "SMBIOS could not mmap %s (%#zx+%#zx): %s\n", +		       smbios_filename, smbios_offset, smbios_len, +		       linux_strerror ( linux_errno ) ); +		goto err_mmap_smbios; +	} + +	/* Fill in entry point descriptor structure */ +	smbios->address = virt_to_user ( smbios_mem + smbios_indent ); +	smbios->len = entry.smbios_len; +	smbios->count = entry.smbios_count; +	smbios->version = SMBIOS_VERSION ( entry.major, entry.minor ); + +	/* Unmap the entry point region (no longer required) */ +	linux_munmap ( entry_mem, SMBIOS_ENTRY_LEN ); + +	return 0; + +	linux_munmap ( smbios_mem, smbios_len ); + err_mmap_smbios: + err_find_entry: +	linux_munmap ( entry_mem, SMBIOS_ENTRY_LEN ); + err_mmap_entry: +	linux_close ( fd ); + err_open: +	return rc; +} + +PROVIDE_SMBIOS ( linux, find_smbios, linux_find_smbios ); diff --git a/roms/ipxe/src/interface/linux/linux_time.c b/roms/ipxe/src/interface/linux/linux_time.c new file mode 100644 index 00000000..e3cbafec --- /dev/null +++ b/roms/ipxe/src/interface/linux/linux_time.c @@ -0,0 +1,46 @@ +/* + * 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 (at your option) 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 + * + * Linux time source + * + */ + +#include <stdint.h> +#include <stddef.h> +#include <errno.h> +#include <linux_api.h> +#include <ipxe/time.h> + +/** + * Get current time in seconds + * + * @ret time		Time, in seconds + */ +static time_t linux_now ( void ) { +	struct timeval now; + +	linux_gettimeofday ( &now, NULL ); +	return now.tv_sec; +} + +PROVIDE_TIME ( linux, time_now, linux_now ); diff --git a/roms/ipxe/src/interface/linux/linux_timer.c b/roms/ipxe/src/interface/linux/linux_timer.c new file mode 100644 index 00000000..7a994517 --- /dev/null +++ b/roms/ipxe/src/interface/linux/linux_timer.c @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2010 Piotr Jaroszyński <p.jaroszynski@gmail.com> + * + * 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 <stddef.h> +#include <ipxe/timer.h> + +#include <linux_api.h> + +/** @file + * + * iPXE timer API for linux + * + */ + +/** + * Delay for a fixed number of microseconds + * + * @v usecs		Number of microseconds for which to delay + */ +static void linux_udelay(unsigned long usecs) +{ +	linux_usleep(usecs); +} + +/** + * Get number of ticks per second + * + * @ret ticks_per_sec	Number of ticks per second + */ +static unsigned long linux_ticks_per_sec(void) +{ +	return 1000; +} + +/** + * Get current system time in ticks + * + * linux doesn't provide an easy access to jiffies so implement it by measuring + * the time since the first call to this function. + * + * Since this function is used to seed the (non-cryptographic) random + * number generator, we round the start time down to the nearest whole + * second.  This minimises the chances of generating identical RNG + * sequences (and hence identical TCP port numbers, etc) on + * consecutive invocations of iPXE. + * + * @ret ticks		Current time, in ticks + */ +static unsigned long linux_currticks(void) +{ +	static struct timeval start; +	static int initialized = 0; + +	if (! initialized) { +		linux_gettimeofday(&start, NULL); +		initialized = 1; +	} + +	struct timeval now; +	linux_gettimeofday(&now, NULL); + +	unsigned long ticks = (now.tv_sec - start.tv_sec) * linux_ticks_per_sec(); +	ticks += now.tv_usec / (long)(1000000 / linux_ticks_per_sec()); + +	return ticks; +} + +PROVIDE_TIMER(linux, udelay, linux_udelay); +PROVIDE_TIMER(linux, currticks, linux_currticks); +PROVIDE_TIMER(linux, ticks_per_sec, linux_ticks_per_sec); diff --git a/roms/ipxe/src/interface/linux/linux_uaccess.c b/roms/ipxe/src/interface/linux/linux_uaccess.c new file mode 100644 index 00000000..5ab0b6b6 --- /dev/null +++ b/roms/ipxe/src/interface/linux/linux_uaccess.c @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2010 Piotr Jaroszyński <p.jaroszynski@gmail.com> + * + * 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> + +/** @file + * + * iPXE user access API for linux + * + */ + +PROVIDE_UACCESS_INLINE(linux, phys_to_user); +PROVIDE_UACCESS_INLINE(linux, user_to_phys); +PROVIDE_UACCESS_INLINE(linux, virt_to_user); +PROVIDE_UACCESS_INLINE(linux, user_to_virt); +PROVIDE_UACCESS_INLINE(linux, userptr_add); +PROVIDE_UACCESS_INLINE(linux, memcpy_user); +PROVIDE_UACCESS_INLINE(linux, memmove_user); +PROVIDE_UACCESS_INLINE(linux, memset_user); +PROVIDE_UACCESS_INLINE(linux, strlen_user); +PROVIDE_UACCESS_INLINE(linux, memchr_user); diff --git a/roms/ipxe/src/interface/linux/linux_umalloc.c b/roms/ipxe/src/interface/linux/linux_umalloc.c new file mode 100644 index 00000000..aa0052c5 --- /dev/null +++ b/roms/ipxe/src/interface/linux/linux_umalloc.c @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2010 Piotr Jaroszyński <p.jaroszynski@gmail.com> + * + * 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 <valgrind/memcheck.h> + +/** @file + * + * iPXE user memory allocation API for linux + * + */ + +#include <assert.h> +#include <ipxe/umalloc.h> + +#include <linux_api.h> + +/** Special address returned for empty allocations */ +#define NOWHERE ((void *)-1) + +/** Poison to make the metadata more unique */ +#define POISON 0xa5a5a5a5 +#define min(a,b) (((a)<(b))?(a):(b)) + +/** Metadata stored at the beginning of all allocations */ +struct metadata +{ +	unsigned poison; +	size_t size; +}; + +#define SIZE_MD (sizeof(struct metadata)) + +/** Simple realloc which passes most of the work to mmap(), mremap() and munmap() */ +static void * linux_realloc(void *ptr, size_t size) +{ +	struct metadata md = {0, 0}; +	struct metadata * mdptr = NULL; + +	DBG2("linux_realloc(%p, %zd)\n", ptr, size); + +	/* Check whether we have a valid pointer */ +	if (ptr != NULL && ptr != NOWHERE) { +		mdptr = ptr - SIZE_MD; +		VALGRIND_MAKE_MEM_DEFINED(mdptr, SIZE_MD); +		md = *mdptr; +		VALGRIND_MAKE_MEM_NOACCESS(mdptr, SIZE_MD); + +		/* Check for poison in the metadata */ +		if (md.poison != POISON) { +			DBG("linux_realloc bad poison: 0x%x (expected 0x%x)\n", md.poison, POISON); +			return NULL; +		} +	} else { +		/* Handle NOWHERE as NULL */ +		ptr = NULL; +	} + +	/* +	 * At this point, ptr is either NULL or pointing to a region allocated by us. +	 * In the latter case mdptr is pointing to a valid metadata, otherwise it is NULL. +	 */ + +	/* Handle deallocation or allocation of size 0 */ +	if (size == 0) { +		if (mdptr) { +			if (linux_munmap(mdptr, md.size)) +				DBG("linux_realloc munmap failed: %s\n", linux_strerror(linux_errno)); +			VALGRIND_FREELIKE_BLOCK(ptr, sizeof(*mdptr)); +		} +		return NOWHERE; +	} + +	if (ptr) { +		char *vbits = NULL; + +		if (RUNNING_ON_VALGRIND > 0) +			vbits = linux_realloc(NULL, min(size, md.size)); + +/* prevent an unused variable warning when building w/o valgrind support */ +#ifndef NVALGRIND +		VALGRIND_GET_VBITS(ptr, vbits, min(size, md.size)); +#endif + +		VALGRIND_FREELIKE_BLOCK(ptr, SIZE_MD); + +		mdptr = linux_mremap(mdptr, md.size + SIZE_MD, size + SIZE_MD, MREMAP_MAYMOVE); +		if (mdptr == MAP_FAILED) { +			DBG("linux_realloc mremap failed: %s\n", linux_strerror(linux_errno)); +			return NULL; +		} +		ptr = ((void *)mdptr) + SIZE_MD; + +		VALGRIND_MALLOCLIKE_BLOCK(ptr, size, SIZE_MD, 0); +/* prevent an unused variable warning when building w/o valgrind support */ +#ifndef NVALGRIND +		VALGRIND_SET_VBITS(ptr, vbits, min(size, md.size)); +#endif + +		if (RUNNING_ON_VALGRIND > 0) +			linux_realloc(vbits, 0); +	} else { +		mdptr = linux_mmap(NULL, size + SIZE_MD, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); +		if (mdptr == MAP_FAILED) { +			DBG("linux_realloc mmap failed: %s\n", linux_strerror(linux_errno)); +			return NULL; +		} +		ptr = ((void *)mdptr) + SIZE_MD; +		VALGRIND_MALLOCLIKE_BLOCK(ptr, size, SIZE_MD, 0); +	} + +	/* Update the metadata */ +	VALGRIND_MAKE_MEM_DEFINED(mdptr, SIZE_MD); +	mdptr->poison = POISON; +	mdptr->size = size; +	VALGRIND_MAKE_MEM_NOACCESS(mdptr, SIZE_MD); +	// VALGRIND_MALLOCLIKE_BLOCK ignores redzones currently, make our own +	VALGRIND_MAKE_MEM_NOACCESS(ptr + size, SIZE_MD); + +	return ptr; +} + +/** + * 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 linux_urealloc(userptr_t old_ptr, size_t new_size) +{ +	return (userptr_t)linux_realloc((void *)old_ptr, new_size); +} + +PROVIDE_UMALLOC(linux, urealloc, linux_urealloc); diff --git a/roms/ipxe/src/interface/smbios/smbios.c b/roms/ipxe/src/interface/smbios/smbios.c new file mode 100644 index 00000000..85694342 --- /dev/null +++ b/roms/ipxe/src/interface/smbios/smbios.c @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2007 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 <stdint.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <ipxe/uaccess.h> +#include <ipxe/smbios.h> + +/** @file + * + * System Management BIOS + * + */ + +/** SMBIOS entry point descriptor */ +static struct smbios smbios = { +	.address = UNULL, +}; + +/** + * Scan for SMBIOS entry point structure + * + * @v start		Start address of region to scan + * @v len		Length of region to scan + * @v entry		SMBIOS entry point structure to fill in + * @ret rc		Return status code + */ +int find_smbios_entry ( userptr_t start, size_t len, +			struct smbios_entry *entry ) { +	uint8_t buf[256]; /* 256 is maximum length possible */ +	static size_t offset = 0; /* Avoid repeated attempts to locate SMBIOS */ +	size_t entry_len; +	unsigned int i; +	uint8_t sum; + +	/* Try to find SMBIOS */ +	for ( ; offset < len ; offset += 0x10 ) { + +		/* Read start of header and verify signature */ +		copy_from_user ( entry, start, offset, sizeof ( *entry ) ); +		if ( entry->signature != SMBIOS_SIGNATURE ) +			continue; + +		/* Read whole header and verify checksum */ +		entry_len = entry->len; +		assert ( entry_len <= sizeof ( buf ) ); +		copy_from_user ( buf, start, offset, entry_len ); +		for ( i = 0, sum = 0 ; i < entry_len ; i++ ) { +			sum += buf[i]; +		} +		if ( sum != 0 ) { +			DBG ( "SMBIOS at %08lx has bad checksum %02x\n", +			      user_to_phys ( start, offset ), sum ); +			continue; +		} + +		/* Fill result structure */ +		DBG ( "Found SMBIOS v%d.%d entry point at %08lx\n", +		      entry->major, entry->minor, +		      user_to_phys ( start, offset ) ); +		return 0; +	} + +	DBG ( "No SMBIOS found\n" ); +	return -ENODEV; +} + +/** + * Find SMBIOS strings terminator + * + * @v offset		Offset to start of strings + * @ret offset		Offset to strings terminator, or 0 if not found + */ +static size_t find_strings_terminator ( size_t offset ) { +	size_t max_offset = ( smbios.len - 2 ); +	uint16_t nulnul; + +	for ( ; offset <= max_offset ; offset++ ) { +		copy_from_user ( &nulnul, smbios.address, offset, 2 ); +		if ( nulnul == 0 ) +			return ( offset + 1 ); +	} +	return 0; +} + +/** + * Find specific structure type within SMBIOS + * + * @v type		Structure type to search for + * @v instance		Instance of this type of structure + * @v structure		SMBIOS structure descriptor to fill in + * @ret rc		Return status code + */ +int find_smbios_structure ( unsigned int type, unsigned int instance, +			    struct smbios_structure *structure ) { +	unsigned int count = 0; +	size_t offset = 0; +	size_t strings_offset; +	size_t terminator_offset; +	int rc; + +	/* Find SMBIOS */ +	if ( ( smbios.address == UNULL ) && +	     ( ( rc = find_smbios ( &smbios ) ) != 0 ) ) +		return rc; +	assert ( smbios.address != UNULL ); + +	/* Scan through list of structures */ +	while ( ( ( offset + sizeof ( structure->header ) ) < smbios.len ) +		&& ( count < smbios.count ) ) { + +		/* Read next SMBIOS structure header */ +		copy_from_user ( &structure->header, smbios.address, offset, +				 sizeof ( structure->header ) ); + +		/* Determine start and extent of strings block */ +		strings_offset = ( offset + structure->header.len ); +		if ( strings_offset > smbios.len ) { +			DBG ( "SMBIOS structure at offset %zx with length " +			      "%x extends beyond SMBIOS\n", offset, +			      structure->header.len ); +			return -ENOENT; +		} +		terminator_offset = find_strings_terminator ( strings_offset ); +		if ( ! terminator_offset ) { +			DBG ( "SMBIOS structure at offset %zx has " +			      "unterminated strings section\n", offset ); +			return -ENOENT; +		} +		structure->strings_len = ( terminator_offset - strings_offset); + +		DBG ( "SMBIOS structure at offset %zx has type %d, length %x, " +		      "strings length %zx\n", offset, structure->header.type, +		      structure->header.len, structure->strings_len ); + +		/* If this is the structure we want, return */ +		if ( ( structure->header.type == type ) && +		     ( instance-- == 0 ) ) { +			structure->offset = offset; +			return 0; +		} + +		/* Move to next SMBIOS structure */ +		offset = ( terminator_offset + 1 ); +		count++; +	} + +	DBG ( "SMBIOS structure type %d not found\n", type ); +	return -ENOENT; +} + +/** + * Copy SMBIOS structure + * + * @v structure		SMBIOS structure descriptor + * @v data		Buffer to hold SMBIOS structure + * @v len		Length of buffer + * @ret rc		Return status code + */ +int read_smbios_structure ( struct smbios_structure *structure, +			    void *data, size_t len ) { + +	assert ( smbios.address != UNULL ); + +	if ( len > structure->header.len ) +		len = structure->header.len; +	copy_from_user ( data, smbios.address, structure->offset, len ); +	return 0; +} + +/** + * Find indexed string within SMBIOS structure + * + * @v structure		SMBIOS structure descriptor + * @v index		String index + * @v data		Buffer for string + * @v len		Length of string buffer + * @ret rc		Length of string, or negative error + */ +int read_smbios_string ( struct smbios_structure *structure, +			 unsigned int index, void *data, size_t len ) { +	size_t strings_start = ( structure->offset + structure->header.len ); +	size_t strings_end = ( strings_start + structure->strings_len ); +	size_t offset; +	size_t string_len; + +	assert ( smbios.address != UNULL ); + +	/* String numbers start at 1 (0 is used to indicate "no string") */ +	if ( ! index ) +		return -ENOENT; + +	for ( offset = strings_start ; offset < strings_end ; +	      offset += ( string_len + 1 ) ) { +		/* Get string length.  This is known safe, since the +		 * smbios_strings struct is constructed so as to +		 * always end on a string boundary. +		 */ +		string_len = strlen_user ( smbios.address, offset ); +		if ( --index == 0 ) { +			/* Copy string, truncating as necessary. */ +			if ( len > string_len ) +				len = string_len; +			copy_from_user ( data, smbios.address, offset, len ); +			return string_len; +		} +	} + +	DBG ( "SMBIOS string index %d not found\n", index ); +	return -ENOENT; +} + +/** + * Get SMBIOS version + * + * @ret version		Version, or negative error + */ +int smbios_version ( void ) { +	int rc; + +	/* Find SMBIOS */ +	if ( ( smbios.address == UNULL ) && +	     ( ( rc = find_smbios ( &smbios ) ) != 0 ) ) +		return rc; +	assert ( smbios.address != UNULL ); + +	return smbios.version; +} diff --git a/roms/ipxe/src/interface/smbios/smbios_settings.c b/roms/ipxe/src/interface/smbios/smbios_settings.c new file mode 100644 index 00000000..83e4320e --- /dev/null +++ b/roms/ipxe/src/interface/smbios/smbios_settings.c @@ -0,0 +1,255 @@ +/* + * 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 <stdint.h> +#include <string.h> +#include <errno.h> +#include <ipxe/settings.h> +#include <ipxe/init.h> +#include <ipxe/uuid.h> +#include <ipxe/smbios.h> + +/** SMBIOS settings scope */ +static const struct settings_scope smbios_settings_scope; + +/** + * Construct SMBIOS raw-data tag + * + * @v _type		SMBIOS structure type number + * @v _structure	SMBIOS structure data type + * @v _field		Field within SMBIOS structure data type + * @ret tag		SMBIOS setting tag + */ +#define SMBIOS_RAW_TAG( _type, _structure, _field )		\ +	( ( (_type) << 16 ) |					\ +	  ( offsetof ( _structure, _field ) << 8 ) |		\ +	  ( sizeof ( ( ( _structure * ) 0 )->_field ) ) ) + +/** + * Construct SMBIOS string tag + * + * @v _type		SMBIOS structure type number + * @v _structure	SMBIOS structure data type + * @v _field		Field within SMBIOS structure data type + * @ret tag		SMBIOS setting tag + */ +#define SMBIOS_STRING_TAG( _type, _structure, _field )		\ +	( ( (_type) << 16 ) |					\ +	  ( offsetof ( _structure, _field ) << 8 ) ) + +/** + * Check applicability of SMBIOS setting + * + * @v settings		Settings block + * @v setting		Setting + * @ret applies		Setting applies within this settings block + */ +static int smbios_applies ( struct settings *settings __unused, +			    const struct setting *setting ) { + +	return ( setting->scope == &smbios_settings_scope ); +} + +/** + * Fetch value of SMBIOS setting + * + * @v settings		Settings block, or NULL to search all blocks + * @v setting		Setting to fetch + * @v data		Buffer to fill with setting data + * @v len		Length of buffer + * @ret len		Length of setting data, or negative error + */ +static int smbios_fetch ( struct settings *settings __unused, +			  struct setting *setting, +			  void *data, size_t len ) { +	struct smbios_structure structure; +	unsigned int tag_instance; +	unsigned int tag_type; +	unsigned int tag_offset; +	unsigned int tag_len; +	int rc; + +	/* Split tag into instance, type, offset and length */ +	tag_instance = ( ( setting->tag >> 24 ) & 0xff ); +	tag_type = ( ( setting->tag >> 16 ) & 0xff ); +	tag_offset = ( ( setting->tag >> 8 ) & 0xff ); +	tag_len = ( setting->tag & 0xff ); + +	/* Find SMBIOS structure */ +	if ( ( rc = find_smbios_structure ( tag_type, tag_instance, +					    &structure ) ) != 0 ) +		return rc; + +	{ +		uint8_t buf[structure.header.len]; +		const void *raw; +		union uuid uuid; +		unsigned int index; + +		/* Read SMBIOS structure */ +		if ( ( rc = read_smbios_structure ( &structure, buf, +						    sizeof ( buf ) ) ) != 0 ) +			return rc; + +		/* A <length> of zero indicates that the byte at +		 * <offset> contains a string index.  An <offset> of +		 * zero indicates that the <length> contains a literal +		 * string index. +		 */ +		if ( ( tag_len == 0 ) || ( tag_offset == 0 ) ) { +			index = ( ( tag_offset == 0 ) ? +				  tag_len : buf[tag_offset] ); +			if ( ( rc = read_smbios_string ( &structure, index, +							 data, len ) ) < 0 ) { +				return rc; +			} +			if ( ! setting->type ) +				setting->type = &setting_type_string; +			return rc; +		} + +		/* Mangle UUIDs if necessary.  iPXE treats UUIDs as +		 * being in network byte order (big-endian).  SMBIOS +		 * specification version 2.6 states that UUIDs are +		 * stored with little-endian values in the first three +		 * fields; earlier versions did not specify an +		 * endianness.  dmidecode assumes that the byte order +		 * is little-endian if and only if the SMBIOS version +		 * is 2.6 or higher; we match this behaviour. +		 */ +		raw = &buf[tag_offset]; +		if ( ( setting->type == &setting_type_uuid ) && +		     ( tag_len == sizeof ( uuid ) ) && +		     ( smbios_version() >= SMBIOS_VERSION ( 2, 6 ) ) ) { +			DBG ( "SMBIOS detected mangled UUID\n" ); +			memcpy ( &uuid, &buf[tag_offset], sizeof ( uuid ) ); +			uuid_mangle ( &uuid ); +			raw = &uuid; +		} + +		/* Return data */ +		if ( len > tag_len ) +			len = tag_len; +		memcpy ( data, raw, len ); +		if ( ! setting->type ) +			setting->type = &setting_type_hex; +		return tag_len; +	} +} + +/** SMBIOS settings operations */ +static struct settings_operations smbios_settings_operations = { +	.applies = smbios_applies, +	.fetch = smbios_fetch, +}; + +/** SMBIOS settings */ +static struct settings smbios_settings = { +	.refcnt = NULL, +	.siblings = LIST_HEAD_INIT ( smbios_settings.siblings ), +	.children = LIST_HEAD_INIT ( smbios_settings.children ), +	.op = &smbios_settings_operations, +	.default_scope = &smbios_settings_scope, +}; + +/** Initialise SMBIOS settings */ +static void smbios_init ( void ) { +	int rc; + +	if ( ( rc = register_settings ( &smbios_settings, NULL, +					"smbios" ) ) != 0 ) { +		DBG ( "SMBIOS could not register settings: %s\n", +		      strerror ( rc ) ); +		return; +	} +} + +/** SMBIOS settings initialiser */ +struct init_fn smbios_init_fn __init_fn ( INIT_NORMAL ) = { +	.initialise = smbios_init, +}; + +/** UUID setting obtained via SMBIOS */ +const struct setting uuid_setting __setting ( SETTING_HOST, uuid ) = { +	.name = "uuid", +	.description = "UUID", +	.tag = SMBIOS_RAW_TAG ( SMBIOS_TYPE_SYSTEM_INFORMATION, +				struct smbios_system_information, uuid ), +	.type = &setting_type_uuid, +	.scope = &smbios_settings_scope, +}; + +/** Manufacturer name setting */ +const struct setting manufacturer_setting __setting ( SETTING_HOST_EXTRA, +						      manufacturer ) = { +	.name = "manufacturer", +	.description = "Manufacturer", +	.tag = SMBIOS_STRING_TAG ( SMBIOS_TYPE_SYSTEM_INFORMATION, +				   struct smbios_system_information, +				   manufacturer ), +	.type = &setting_type_string, +	.scope = &smbios_settings_scope, +}; + +/** Product name setting */ +const struct setting product_setting __setting ( SETTING_HOST_EXTRA, product )={ +	.name = "product", +	.description = "Product name", +	.tag = SMBIOS_STRING_TAG ( SMBIOS_TYPE_SYSTEM_INFORMATION, +				   struct smbios_system_information, +				   product ), +	.type = &setting_type_string, +	.scope = &smbios_settings_scope, +}; + +/** Serial number setting */ +const struct setting serial_setting __setting ( SETTING_HOST_EXTRA, serial ) = { +	.name = "serial", +	.description = "Serial number", +	.tag = SMBIOS_STRING_TAG ( SMBIOS_TYPE_SYSTEM_INFORMATION, +				   struct smbios_system_information, +				   serial ), +	.type = &setting_type_string, +	.scope = &smbios_settings_scope, +}; + +/** Asset tag setting */ +const struct setting asset_setting __setting ( SETTING_HOST_EXTRA, asset ) = { +	.name = "asset", +	.description = "Asset tag", +	.tag = SMBIOS_STRING_TAG ( SMBIOS_TYPE_ENCLOSURE_INFORMATION, +				   struct smbios_enclosure_information, +				   asset_tag ), +	.type = &setting_type_string, +	.scope = &smbios_settings_scope, +}; + +/** Board serial number setting (may differ from chassis serial number) */ +const struct setting board_serial_setting __setting ( SETTING_HOST_EXTRA, +						      board_serial ) = { +	.name = "board-serial", +	.description = "Base board serial", +	.tag = SMBIOS_STRING_TAG ( SMBIOS_TYPE_BASE_BOARD_INFORMATION, +				   struct smbios_base_board_information, +				   serial ), +	.type = &setting_type_string, +	.scope = &smbios_settings_scope, +}; diff --git a/roms/ipxe/src/interface/xen/xenbus.c b/roms/ipxe/src/interface/xen/xenbus.c new file mode 100644 index 00000000..ffc8aba3 --- /dev/null +++ b/roms/ipxe/src/interface/xen/xenbus.c @@ -0,0 +1,393 @@ +/* + * 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 (at your option) 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 <errno.h> +#include <ipxe/malloc.h> +#include <ipxe/device.h> +#include <ipxe/timer.h> +#include <ipxe/nap.h> +#include <ipxe/xen.h> +#include <ipxe/xenstore.h> +#include <ipxe/xenbus.h> + +/** @file + * + * Xen device bus + * + */ + +/* Disambiguate the various error causes */ +#define ETIMEDOUT_UNKNOWN						\ +	__einfo_error ( EINFO_ETIMEDOUT_UNKNOWN ) +#define EINFO_ETIMEDOUT_UNKNOWN						\ +	__einfo_uniqify ( EINFO_ETIMEDOUT, XenbusStateUnknown,		\ +			  "Unknown" ) +#define ETIMEDOUT_INITIALISING						\ +	__einfo_error ( EINFO_ETIMEDOUT_INITIALISING ) +#define EINFO_ETIMEDOUT_INITIALISING					\ +	__einfo_uniqify ( EINFO_ETIMEDOUT, XenbusStateInitialising,	\ +			  "Initialising" ) +#define ETIMEDOUT_INITWAIT						\ +	__einfo_error ( EINFO_ETIMEDOUT_INITWAIT ) +#define EINFO_ETIMEDOUT_INITWAIT					\ +	__einfo_uniqify ( EINFO_ETIMEDOUT, XenbusStateInitWait,		\ +			  "InitWait" ) +#define ETIMEDOUT_INITIALISED						\ +	__einfo_error ( EINFO_ETIMEDOUT_INITIALISED ) +#define EINFO_ETIMEDOUT_INITIALISED					\ +	__einfo_uniqify ( EINFO_ETIMEDOUT, XenbusStateInitialised,	\ +			  "Initialised" ) +#define ETIMEDOUT_CONNECTED						\ +	__einfo_error ( EINFO_ETIMEDOUT_CONNECTED ) +#define EINFO_ETIMEDOUT_CONNECTED					\ +	__einfo_uniqify ( EINFO_ETIMEDOUT, XenbusStateConnected,	\ +			  "Connected" ) +#define ETIMEDOUT_CLOSING						\ +	__einfo_error ( EINFO_ETIMEDOUT_CLOSING ) +#define EINFO_ETIMEDOUT_CLOSING						\ +	__einfo_uniqify ( EINFO_ETIMEDOUT, XenbusStateClosing,		\ +			  "Closing" ) +#define ETIMEDOUT_CLOSED						\ +	__einfo_error ( EINFO_ETIMEDOUT_CLOSED ) +#define EINFO_ETIMEDOUT_CLOSED						\ +	__einfo_uniqify ( EINFO_ETIMEDOUT, XenbusStateClosed,		\ +			  "Closed" ) +#define ETIMEDOUT_RECONFIGURING						\ +	__einfo_error ( EINFO_ETIMEDOUT_RECONFIGURING ) +#define EINFO_ETIMEDOUT_RECONFIGURING					\ +	__einfo_uniqify ( EINFO_ETIMEDOUT, XenbusStateReconfiguring,	\ +			  "Reconfiguring" ) +#define ETIMEDOUT_RECONFIGURED						\ +	__einfo_error ( EINFO_ETIMEDOUT_RECONFIGURED ) +#define EINFO_ETIMEDOUT_RECONFIGURED					\ +	__einfo_uniqify ( EINFO_ETIMEDOUT, XenbusStateReconfigured,	\ +			  "Reconfigured" ) +#define ETIMEDOUT_STATE( state )					\ +	EUNIQ ( EINFO_ETIMEDOUT, (state), ETIMEDOUT_UNKNOWN,		\ +		ETIMEDOUT_INITIALISING, ETIMEDOUT_INITWAIT,		\ +		ETIMEDOUT_INITIALISED, ETIMEDOUT_CONNECTED,		\ +		ETIMEDOUT_CLOSING, ETIMEDOUT_CLOSED,			\ +		ETIMEDOUT_RECONFIGURING, ETIMEDOUT_RECONFIGURED ) + +/** Maximum time to wait for backend to reach a given state, in ticks */ +#define XENBUS_BACKEND_TIMEOUT ( 5 * TICKS_PER_SEC ) + +/** + * Set device state + * + * @v xendev		Xen device + * @v state		New state + * @ret rc		Return status code + */ +int xenbus_set_state ( struct xen_device *xendev, int state ) { +	int rc; + +	/* Attempt to set state */ +	if ( ( rc = xenstore_write_num ( xendev->xen, state, xendev->key, +					 "state", NULL ) ) != 0 ) { +		DBGC ( xendev, "XENBUS %s could not set state=\"%d\": %s\n", +		       xendev->key, state, strerror ( rc ) ); +		return rc; +	} + +	return 0; +} + +/** + * Get backend state + * + * @v xendev		Xen device + * @ret state		Backend state, or negative error + */ +int xenbus_backend_state ( struct xen_device *xendev ) { +	unsigned long state; +	int rc; + +	/* Attempt to get backend state */ +	if ( ( rc = xenstore_read_num ( xendev->xen, &state, xendev->backend, +					"state", NULL ) ) != 0 ) { +		DBGC ( xendev, "XENBUS %s could not read %s/state: %s\n", +		       xendev->key, xendev->backend, strerror ( rc ) ); +		return rc; +	} + +	return state; +} + +/** + * Wait for backend to reach a given state + * + * @v xendev		Xen device + * @v state		Desired backend state + * @ret rc		Return status code + */ +int xenbus_backend_wait ( struct xen_device *xendev, int state ) { +	unsigned long started = currticks(); +	unsigned long elapsed; +	unsigned int attempts = 0; +	int current_state; +	int rc; + +	/* Wait for backend to reach this state */ +	do { + +		/* Get current backend state */ +		current_state = xenbus_backend_state ( xendev ); +		if ( current_state < 0 ) { +			rc = current_state; +			return rc; +		} +		if ( current_state == state ) +			return 0; + +		/* Allow time for backend to react */ +		cpu_nap(); + +		/* XenStore is a very slow interface; any fixed delay +		 * time would be dwarfed by the XenStore access time. +		 * We therefore use wall clock to time out this +		 * operation. +		 */ +		elapsed = ( currticks() - started ); +		attempts++; + +	} while ( elapsed < XENBUS_BACKEND_TIMEOUT ); + +	/* Construct status code from current backend state */ +	rc = -ETIMEDOUT_STATE ( current_state ); +	DBGC ( xendev, "XENBUS %s timed out after %d attempts waiting for " +	       "%s/state=\"%d\": %s\n", xendev->key, attempts, xendev->backend, +	       state, strerror ( rc ) ); + +	return rc; +} + +/** + * Find driver for Xen device + * + * @v type		Device type + * @ret driver		Driver, or NULL + */ +static struct xen_driver * xenbus_find_driver ( const char *type ) { +	struct xen_driver *xendrv; + +	for_each_table_entry ( xendrv, XEN_DRIVERS ) { +		if ( strcmp ( xendrv->type, type ) == 0 ) +			return xendrv; +	} +	return NULL; +} + +/** + * Probe Xen device + * + * @v xen		Xen hypervisor + * @v parent		Parent device + * @v type		Device type + * @v instance		Device instance + * @ret rc		Return status code + */ +static int xenbus_probe_device ( struct xen_hypervisor *xen, +				 struct device *parent, const char *type, +				 const char *instance ) { +	struct xen_device *xendev; +	size_t key_len; +	int rc; + +	/* Allocate and initialise structure */ +	key_len = ( 7 /* "device/" */ + strlen ( type ) + 1 /* "/" */ + +		    strlen ( instance ) + 1 /* NUL */ ); +	xendev = zalloc ( sizeof ( *xendev ) + key_len ); +	if ( ! xendev ) { +		rc = -ENOMEM; +		goto err_alloc; +	} +	snprintf ( xendev->dev.name, sizeof ( xendev->dev.name ), "%s/%s", +		   type, instance ); +	xendev->dev.desc.bus_type = BUS_TYPE_XEN; +	INIT_LIST_HEAD ( &xendev->dev.children ); +	list_add_tail ( &xendev->dev.siblings, &parent->children ); +	xendev->dev.parent = parent; +	xendev->xen = xen; +	xendev->key = ( ( void * ) ( xendev + 1 ) ); +	snprintf ( xendev->key, key_len, "device/%s/%s", type, instance ); + +	/* Read backend key */ +	if ( ( rc = xenstore_read ( xen, &xendev->backend, xendev->key, +				    "backend", NULL ) ) != 0 ) { +		DBGC ( xendev, "XENBUS %s could not read backend: %s\n", +		       xendev->key, strerror ( rc ) ); +		goto err_read_backend; +	} + +	/* Read backend domain ID */ +	if ( ( rc = xenstore_read_num ( xen, &xendev->backend_id, xendev->key, +					"backend-id", NULL ) ) != 0 ) { +		DBGC ( xendev, "XENBUS %s could not read backend-id: %s\n", +		       xendev->key, strerror ( rc ) ); +		goto err_read_backend_id; +	} +	DBGC ( xendev, "XENBUS %s backend=\"%s\" in domain %ld\n", +	       xendev->key, xendev->backend, xendev->backend_id ); + +	/* Look for a driver */ +	xendev->driver = xenbus_find_driver ( type ); +	if ( ! xendev->driver ) { +		DBGC ( xendev, "XENBUS %s has no driver\n", xendev->key ); +		/* Not a fatal error */ +		rc = 0; +		goto err_no_driver; +	} +	xendev->dev.driver_name = xendev->driver->name; +	DBGC ( xendev, "XENBUS %s has driver \"%s\"\n", xendev->key, +	       xendev->driver->name ); + +	/* Probe driver */ +	if ( ( rc = xendev->driver->probe ( xendev ) ) != 0 ) { +		DBGC ( xendev, "XENBUS could not probe %s: %s\n", +		       xendev->key, strerror ( rc ) ); +		goto err_probe; +	} + +	return 0; + +	xendev->driver->remove ( xendev ); + err_probe: + err_no_driver: + err_read_backend_id: +	free ( xendev->backend ); + err_read_backend: +	list_del ( &xendev->dev.siblings ); +	free ( xendev ); + err_alloc: +	return rc; +} + +/** + * Remove Xen device + * + * @v xendev		Xen device + */ +static void xenbus_remove_device ( struct xen_device *xendev ) { + +	/* Remove device */ +	xendev->driver->remove ( xendev ); +	free ( xendev->backend ); +	list_del ( &xendev->dev.siblings ); +	free ( xendev ); +} + +/** + * Probe Xen devices of a given type + * + * @v xen		Xen hypervisor + * @v parent		Parent device + * @v type		Device type + * @ret rc		Return status code + */ +static int xenbus_probe_type ( struct xen_hypervisor *xen, +			       struct device *parent, const char *type ) { +	char *children; +	char *child; +	size_t len; +	int rc; + +	/* Get children of this key */ +	if ( ( rc = xenstore_directory ( xen, &children, &len, "device", +					 type, NULL ) ) != 0 ) { +		DBGC ( xen, "XENBUS could not list \"%s\" devices: %s\n", +		       type, strerror ( rc ) ); +		goto err_directory; +	} + +	/* Probe each child */ +	for ( child = children ; child < ( children + len ) ; +	      child += ( strlen ( child ) + 1 /* NUL */ ) ) { +		if ( ( rc = xenbus_probe_device ( xen, parent, type, +						  child ) ) != 0 ) +			goto err_probe_device; +	} + +	free ( children ); +	return 0; + + err_probe_device: +	free ( children ); + err_directory: +	return rc; +} + +/** + * Probe Xen bus + * + * @v xen		Xen hypervisor + * @v parent		Parent device + * @ret rc		Return status code + */ +int xenbus_probe ( struct xen_hypervisor *xen, struct device *parent ) { +	char *types; +	char *type; +	size_t len; +	int rc; + +	/* Get children of "device" key */ +	if ( ( rc = xenstore_directory ( xen, &types, &len, "device", +					 NULL ) ) != 0 ) { +		DBGC ( xen, "XENBUS could not list device types: %s\n", +		       strerror ( rc ) ); +		goto err_directory; +	} + +	/* Probe each child type */ +	for ( type = types ; type < ( types + len ) ; +	      type += ( strlen ( type ) + 1 /* NUL */ ) ) { +		if ( ( rc = xenbus_probe_type ( xen, parent, type ) ) != 0 ) +			goto err_probe_type; +	} + +	free ( types ); +	return 0; + +	xenbus_remove ( xen, parent ); + err_probe_type: +	free ( types ); + err_directory: +	return rc; +} + +/** + * Remove Xen bus + * + * @v xen		Xen hypervisor + * @v parent		Parent device + */ +void xenbus_remove ( struct xen_hypervisor *xen __unused, +		     struct device *parent ) { +	struct xen_device *xendev; +	struct xen_device *tmp; + +	/* Remove devices */ +	list_for_each_entry_safe ( xendev, tmp, &parent->children, +				   dev.siblings ) { +		xenbus_remove_device ( xendev ); +	} +} diff --git a/roms/ipxe/src/interface/xen/xengrant.c b/roms/ipxe/src/interface/xen/xengrant.c new file mode 100644 index 00000000..be12b23d --- /dev/null +++ b/roms/ipxe/src/interface/xen/xengrant.c @@ -0,0 +1,228 @@ +/* + * 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 (at your option) 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 <stdint.h> +#include <strings.h> +#include <errno.h> +#include <assert.h> +#include <ipxe/io.h> +#include <ipxe/xen.h> +#include <ipxe/xengrant.h> + +/** @file + * + * Xen grant tables + * + */ + +/** Grant table version to try setting + * + * Using version 1 grant tables limits guests to using 16TB of + * grantable RAM, and prevents the use of subpage grants.  Some + * versions of the Xen hypervisor refuse to allow the grant table + * version to be set after the first grant references have been + * created, so the loaded operating system may be stuck with whatever + * choice we make here.  We therefore currently use version 2 grant + * tables, since they give the most flexibility to the loaded OS. + * + * Current versions (7.2.0) of the Windows PV drivers have no support + * for version 2 grant tables, and will merrily create version 1 + * entries in what the hypervisor believes to be a version 2 table. + * This causes some confusion. + * + * Avoid this problem by attempting to use version 1 tables, since + * otherwise we may render Windows unable to boot. + * + * Play nicely with other potential bootloaders by accepting either + * version 1 or version 2 grant tables (if we are unable to set our + * requested version). + */ +#define XENGRANT_TRY_VERSION 1 + +/** + * Initialise grant table + * + * @v xen		Xen hypervisor + * @ret rc		Return status code + */ +int xengrant_init ( struct xen_hypervisor *xen ) { +	struct gnttab_query_size size; +	struct gnttab_set_version set_version; +	struct gnttab_get_version get_version; +	struct grant_entry_v1 *v1; +	union grant_entry_v2 *v2; +	unsigned int version; +	int xenrc; +	int rc; + +	/* Get grant table size */ +	size.dom = DOMID_SELF; +	if ( ( xenrc = xengrant_query_size ( xen, &size ) ) != 0 ) { +		rc = -EXEN ( xenrc ); +		DBGC ( xen, "XENGRANT could not get table size: %s\n", +		       strerror ( rc ) ); +		return rc; +	} +	xen->grant.len = ( size.nr_frames * PAGE_SIZE ); + +	/* Set grant table version, if applicable */ +	set_version.version = XENGRANT_TRY_VERSION; +	if ( ( xenrc = xengrant_set_version ( xen, &set_version ) ) != 0 ) { +		rc = -EXEN ( xenrc ); +		DBGC ( xen, "XENGRANT could not set version %d: %s\n", +		       XENGRANT_TRY_VERSION, strerror ( rc ) ); +		/* Continue; use whatever version is current */ +	} + +	/* Get grant table version */ +	get_version.dom = DOMID_SELF; +	get_version.pad = 0; +	if ( ( xenrc = xengrant_get_version ( xen, &get_version ) ) == 0 ) { +		version = get_version.version; +		switch ( version ) { + +		case 0: +			/* Version not yet specified: will be version 1 */ +			version = 1; +			break; + +		case 1 : +			/* Version 1 table: nothing special to do */ +			break; + +		case 2: +			/* Version 2 table: configure shift appropriately */ +			xen->grant.shift = ( fls ( sizeof ( *v2 ) / +						   sizeof ( *v1 ) ) - 1 ); +			break; + +		default: +			/* Unsupported version */ +			DBGC ( xen, "XENGRANT detected unsupported version " +			       "%d\n", version ); +			return -ENOTSUP; + +		} +	} else { +		rc = -EXEN ( xenrc ); +		DBGC ( xen, "XENGRANT could not get version (assuming v1): " +		       "%s\n", strerror ( rc ) ); +		version = 1; +	} + +	DBGC ( xen, "XENGRANT using v%d table with %d entries\n", +	       version, xengrant_entries ( xen ) ); +	return 0; +} + +/** + * Allocate grant references + * + * @v xen		Xen hypervisor + * @v refs		Grant references to fill in + * @v count		Number of references + * @ret rc		Return status code + */ +int xengrant_alloc ( struct xen_hypervisor *xen, grant_ref_t *refs, +		     unsigned int count ) { +	struct grant_entry_header *hdr; +	unsigned int entries = xengrant_entries ( xen ); +	unsigned int mask = ( entries - 1 ); +	unsigned int check = 0; +	unsigned int avail; +	unsigned int ref; + +	/* Fail unless we have enough references available */ +	avail = ( entries - xen->grant.used - GNTTAB_NR_RESERVED_ENTRIES ); +	if ( avail < count ) { +		DBGC ( xen, "XENGRANT cannot allocate %d references (only %d " +		       "of %d available)\n", count, avail, entries ); +		return -ENOBUFS; +	} +	DBGC ( xen, "XENGRANT allocating %d references (from %d of %d " +	       "available)\n", count, avail, entries ); + +	/* Update number of references used */ +	xen->grant.used += count; + +	/* Find unused references */ +	for ( ref = xen->grant.ref ; count ; ref = ( ( ref + 1 ) & mask ) ) { + +		/* Sanity check */ +		assert ( check++ < entries ); + +		/* Skip reserved references */ +		if ( ref < GNTTAB_NR_RESERVED_ENTRIES ) +			continue; + +		/* Skip in-use references */ +		hdr = xengrant_header ( xen, ref ); +		if ( readw ( &hdr->flags ) & GTF_type_mask ) +			continue; +		if ( readw ( &hdr->domid ) == DOMID_SELF ) +			continue; + +		/* Zero reference */ +		xengrant_zero ( xen, hdr ); + +		/* Mark reference as in-use.  We leave the flags as +		 * empty (to avoid creating a valid grant table entry) +		 * and set the domid to DOMID_SELF. +		 */ +		writew ( DOMID_SELF, &hdr->domid ); +		DBGC2 ( xen, "XENGRANT allocated ref %d\n", ref ); + +		/* Record reference */ +		refs[--count] = ref; +	} + +	/* Update cursor */ +	xen->grant.ref = ref; + +	return 0; +} + +/** + * Free grant references + * + * @v xen		Xen hypervisor + * @v refs		Grant references + * @v count		Number of references + */ +void xengrant_free ( struct xen_hypervisor *xen, grant_ref_t *refs, +		     unsigned int count ) { +	struct grant_entry_header *hdr; +	unsigned int ref; +	unsigned int i; + +	/* Free references */ +	for ( i = 0 ; i < count ; i++ ) { + +		/* Sanity check */ +		ref = refs[i]; +		assert ( ref < xengrant_entries ( xen ) ); + +		/* Zero reference */ +		hdr = xengrant_header ( xen, ref ); +		xengrant_zero ( xen, hdr ); +		DBGC2 ( xen, "XENGRANT freed ref %d\n", ref ); +	} +} diff --git a/roms/ipxe/src/interface/xen/xenstore.c b/roms/ipxe/src/interface/xen/xenstore.c new file mode 100644 index 00000000..b9698292 --- /dev/null +++ b/roms/ipxe/src/interface/xen/xenstore.c @@ -0,0 +1,547 @@ +/* + * 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 (at your option) 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 <stdint.h> +#include <stdarg.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <ipxe/io.h> +#include <ipxe/nap.h> +#include <ipxe/malloc.h> +#include <ipxe/xen.h> +#include <ipxe/xenevent.h> +#include <ipxe/xenstore.h> + +/* + * xs_wire.h attempts to define a static error table xsd_errors, which + * interacts badly with the dynamically generated error numbers used + * by iPXE.  Prevent this table from being constructed by including + * errno.h only after including xs_wire.h. + * + */ +#include <xen/io/xs_wire.h> +#include <errno.h> + +/** @file + * + * XenStore interface + * + */ + +/** Request identifier */ +static uint32_t xenstore_req_id; + +/** + * Send XenStore request raw data + * + * @v xen		Xen hypervisor + * @v data		Data buffer + * @v len		Length of data + */ +static void xenstore_send ( struct xen_hypervisor *xen, const void *data, +			    size_t len ) { +	struct xenstore_domain_interface *intf = xen->store.intf; +	XENSTORE_RING_IDX prod = readl ( &intf->req_prod ); +	XENSTORE_RING_IDX cons; +	XENSTORE_RING_IDX idx; +	const char *bytes = data; +	size_t offset = 0; +	size_t fill; + +	DBGCP ( intf, "XENSTORE raw request:\n" ); +	DBGCP_HDA ( intf, MASK_XENSTORE_IDX ( prod ), data, len ); + +	/* Write one byte at a time */ +	while ( offset < len ) { + +		/* Wait for space to become available */ +		while ( 1 ) { +			cons = readl ( &intf->req_cons ); +			fill = ( prod - cons ); +			if ( fill < XENSTORE_RING_SIZE ) +				break; +			DBGC2 ( xen, "." ); +			cpu_nap(); +			rmb(); +		} + +		/* Write byte */ +		idx = MASK_XENSTORE_IDX ( prod++ ); +		writeb ( bytes[offset++], &intf->req[idx] ); +	} + +	/* Update producer counter */ +	wmb(); +	writel ( prod, &intf->req_prod ); +	wmb(); +} + +/** + * Send XenStore request string (excluding terminating NUL) + * + * @v xen		Xen hypervisor + * @v string		String + */ +static void xenstore_send_string ( struct xen_hypervisor *xen, +				   const char *string ) { + +	xenstore_send ( xen, string, strlen ( string ) ); +} + +/** + * Receive XenStore response raw data + * + * @v xen		Xen hypervisor + * @v data		Data buffer, or NULL to discard data + * @v len		Length of data + */ +static void xenstore_recv ( struct xen_hypervisor *xen, void *data, +			    size_t len ) { +	struct xenstore_domain_interface *intf = xen->store.intf; +	XENSTORE_RING_IDX cons = readl ( &intf->rsp_cons ); +	XENSTORE_RING_IDX prod; +	XENSTORE_RING_IDX idx; +	char *bytes = data; +	size_t offset = 0; +	size_t fill; + +	DBGCP ( intf, "XENSTORE raw response:\n" ); + +	/* Read one byte at a time */ +	while ( offset < len ) { + +		/* Wait for data to be ready */ +		while ( 1 ) { +			prod = readl ( &intf->rsp_prod ); +			fill = ( prod - cons ); +			if ( fill > 0 ) +				break; +			DBGC2 ( xen, "." ); +			cpu_nap(); +			rmb(); +		} + +		/* Read byte */ +		idx = MASK_XENSTORE_IDX ( cons++ ); +		if ( data ) +			bytes[offset++] = readb ( &intf->rsp[idx] ); +	} +	if ( data ) +		DBGCP_HDA ( intf, MASK_XENSTORE_IDX ( cons - len ), data, len ); + +	/* Update consumer counter */ +	writel ( cons, &intf->rsp_cons ); +	wmb(); +} + +/** + * Send XenStore request + * + * @v xen		Xen hypervisor + * @v type		Message type + * @v req_id		Request ID + * @v value		Value, or NULL to omit + * @v key		Key path components + * @ret rc		Return status code + */ +static int xenstore_request ( struct xen_hypervisor *xen, +			      enum xsd_sockmsg_type type, uint32_t req_id, +			      const char *value, va_list key ) { +	struct xsd_sockmsg msg; +	struct evtchn_send event; +	const char *string; +	va_list tmp; +	int xenrc; +	int rc; + +	/* Construct message header */ +	msg.type = type; +	msg.req_id = req_id; +	msg.tx_id = 0; +	msg.len = 0; +	DBGC2 ( xen, "XENSTORE request ID %d type %d ", req_id, type ); + +	/* Calculate total length */ +	va_copy ( tmp, key ); +	while ( ( string = va_arg ( tmp, const char * ) ) != NULL ) { +		DBGC2 ( xen, "%s%s", ( msg.len ? "/" : "" ), string ); +		msg.len += ( strlen ( string ) + 1 /* '/' or NUL */ ); +	} +	va_end ( tmp ); +	if ( value ) { +		DBGC2 ( xen, " = \"%s\"", value ); +		msg.len += strlen ( value ); +	} +	DBGC2 ( xen, "\n" ); + +	/* Send message */ +	xenstore_send ( xen, &msg, sizeof ( msg ) ); +	string = va_arg ( key, const char * ); +	assert ( string != NULL ); +	xenstore_send_string ( xen, string ); +	while ( ( string = va_arg ( key, const char * ) ) != NULL ) { +		xenstore_send_string ( xen, "/" ); +		xenstore_send_string ( xen, string ); +	} +	xenstore_send ( xen, "", 1 ); /* Separating NUL */ +	if ( value ) +		xenstore_send_string ( xen, value ); + +	/* Notify the back end */ +	event.port = xen->store.port; +	if ( ( xenrc = xenevent_send ( xen, &event ) ) != 0 ) { +		rc = -EXEN ( xenrc ); +		DBGC ( xen, "XENSTORE could not notify back end: %s\n", +		       strerror ( rc ) ); +		return rc; +	} + +	return 0; +} + +/** + * Receive XenStore response + * + * @v xen		Xen hypervisor + * @v req_id		Request ID + * @v value		Value to fill in + * @v len		Length to fill in + * @ret rc		Return status code + * + * The caller is responsible for eventually calling free() on the + * returned value.  Note that the value may comprise multiple + * NUL-terminated strings concatenated together.  A terminating NUL + * will always be appended to the returned value. + */ +static int xenstore_response ( struct xen_hypervisor *xen, uint32_t req_id, +			       char **value, size_t *len ) { +	struct xsd_sockmsg msg; +	char *string; +	int rc; + +	/* Receive message header */ +	xenstore_recv ( xen, &msg, sizeof ( msg ) ); +	*len = msg.len; + +	/* Allocate space for response */ +	*value = zalloc ( msg.len + 1 /* terminating NUL */ ); + +	/* Receive data.  Do this even if allocation failed, or if the +	 * request ID was incorrect, to avoid leaving data in the +	 * ring. +	 */ +	xenstore_recv ( xen, *value, msg.len ); + +	/* Validate request ID */ +	if ( msg.req_id != req_id ) { +		DBGC ( xen, "XENSTORE response ID mismatch (got %d, expected " +		       "%d)\n", msg.req_id, req_id ); +		rc = -EPROTO; +		goto err_req_id; +	} + +	/* Check for allocation failure */ +	if ( ! *value ) { +		DBGC ( xen, "XENSTORE could not allocate %d bytes for " +		       "response\n", msg.len ); +		rc = -ENOMEM; +		goto err_alloc; +	} + +	/* Check for explicit errors */ +	if ( msg.type == XS_ERROR ) { +		DBGC ( xen, "XENSTORE response error \"%s\"\n", *value ); +		rc = -EIO; +		goto err_explicit; +	} + +	DBGC2 ( xen, "XENSTORE response ID %d\n", req_id ); +	if ( DBG_EXTRA ) { +		for ( string = *value ; string < ( *value + msg.len ) ; +		      string += ( strlen ( string ) + 1 /* NUL */ ) ) { +			DBGC2 ( xen, " - \"%s\"\n", string ); +		} +	} +	return 0; + + err_explicit: + err_alloc: + err_req_id: +	free ( *value ); +	*value = NULL; +	return rc; +} + +/** + * Issue a XenStore message + * + * @v xen		Xen hypervisor + * @v type		Message type + * @v response		Response value to fill in, or NULL to discard + * @v len		Response length to fill in, or NULL to ignore + * @v request		Request value, or NULL to omit + * @v key		Key path components + * @ret rc		Return status code + */ +static int xenstore_message ( struct xen_hypervisor *xen, +			      enum xsd_sockmsg_type type, char **response, +			      size_t *len, const char *request, va_list key ) { +	char *response_value; +	size_t response_len; +	int rc; + +	/* Send request */ +	if ( ( rc = xenstore_request ( xen, type, ++xenstore_req_id, +				       request, key ) ) != 0 ) +		return rc; + +	/* Receive response */ +	if ( ( rc = xenstore_response ( xen, xenstore_req_id, &response_value, +					&response_len ) ) != 0 ) +		return rc; + +	/* Return response, if applicable */ +	if ( response ) { +		*response = response_value; +	} else { +		free ( response_value ); +	} +	if ( len ) +		*len = response_len; + +	return 0; +} + +/** + * Read XenStore value + * + * @v xen		Xen hypervisor + * @v value		Value to fill in + * @v key		Key path components + * @ret rc		Return status code + * + * On a successful return, the caller is responsible for calling + * free() on the returned value. + */ +static int xenstore_vread ( struct xen_hypervisor *xen, char **value, +			    va_list key ) { + +	return xenstore_message ( xen, XS_READ, value, NULL, NULL, key ); +} + +/** + * Read XenStore value + * + * @v xen		Xen hypervisor + * @v value		Value to fill in + * @v ...		Key path components + * @ret rc		Return status code + * + * On a successful return, the caller is responsible for calling + * free() on the returned value. + */ +__attribute__ (( sentinel )) int +xenstore_read ( struct xen_hypervisor *xen, char **value, ... ) { +	va_list key; +	int rc; + +	va_start ( key, value ); +	rc = xenstore_vread ( xen, value, key ); +	va_end ( key ); +	return rc; +} + +/** + * Read XenStore numeric value + * + * @v xen		Xen hypervisor + * @v num		Numeric value to fill in + * @v ...		Key path components + * @ret rc		Return status code + */ +__attribute__ (( sentinel )) int +xenstore_read_num ( struct xen_hypervisor *xen, unsigned long *num, ... ) { +	va_list key; +	char *value; +	char *endp; +	int rc; + +	/* Try to read text value */ +	va_start ( key, num ); +	rc = xenstore_vread ( xen, &value, key ); +	va_end ( key ); +	if ( rc != 0 ) +		goto err_read; + +	/* Try to parse as numeric value */ +	*num = strtoul ( value, &endp, 10 ); +	if ( ( *value == '\0' ) || ( *endp != '\0' ) ) { +		DBGC ( xen, "XENSTORE found invalid numeric value \"%s\"\n", +		       value ); +		rc = -EINVAL; +		goto err_strtoul; +	} + + err_strtoul: +	free ( value ); + err_read: +	return rc; +} + +/** + * Write XenStore value + * + * @v xen		Xen hypervisor + * @v value		Value + * @v key		Key path components + * @ret rc		Return status code + */ +static int xenstore_vwrite ( struct xen_hypervisor *xen, const char *value, +			     va_list key ) { + +	return xenstore_message ( xen, XS_WRITE, NULL, NULL, value, key ); +} + +/** + * Write XenStore value + * + * @v xen		Xen hypervisor + * @v value		Value + * @v ...		Key path components + * @ret rc		Return status code + */ +__attribute__ (( sentinel )) int +xenstore_write ( struct xen_hypervisor *xen, const char *value, ... ) { +	va_list key; +	int rc; + +	va_start ( key, value ); +	rc = xenstore_vwrite ( xen, value, key ); +	va_end ( key ); +	return rc; +} + +/** + * Write XenStore numeric value + * + * @v xen		Xen hypervisor + * @v num		Numeric value + * @v ...		Key path components + * @ret rc		Return status code + */ +__attribute__ (( sentinel )) int +xenstore_write_num ( struct xen_hypervisor *xen, unsigned long num, ... ) { +	char value[ 21 /* "18446744073709551615" + NUL */ ]; +	va_list key; +	int rc; + +	/* Construct value */ +	snprintf ( value, sizeof ( value ), "%ld", num ); + +	/* Write value */ +	va_start ( key, num ); +	rc = xenstore_vwrite ( xen, value, key ); +	va_end ( key ); +	return rc; +} + +/** + * Delete XenStore value + * + * @v xen		Xen hypervisor + * @v ...		Key path components + * @ret rc		Return status code + */ +__attribute__ (( sentinel )) int +xenstore_rm ( struct xen_hypervisor *xen, ... ) { +	va_list key; +	int rc; + +	va_start ( key, xen ); +	rc = xenstore_message ( xen, XS_RM, NULL, NULL, NULL, key ); +	va_end ( key ); +	return rc; +} + +/** + * Read XenStore directory + * + * @v xen		Xen hypervisor + * @v children		Child key names to fill in + * @v len		Length of child key names to fill in + * @v ...		Key path components + * @ret rc		Return status code + */ +__attribute__ (( sentinel )) int +xenstore_directory ( struct xen_hypervisor *xen, char **children, size_t *len, +		     ... ) { +	va_list key; +	int rc; + +	va_start ( key, len ); +	rc = xenstore_message ( xen, XS_DIRECTORY, children, len, NULL, key ); +	va_end ( key ); +	return rc; +} + +/** + * Dump XenStore directory contents (for debugging) + * + * @v xen		Xen hypervisor + * @v key		Key + */ +void xenstore_dump ( struct xen_hypervisor *xen, const char *key ) { +	char *value; +	char *children; +	char *child; +	char *child_key; +	size_t len; +	int rc; + +	/* Try to dump current key as a value */ +	if ( ( rc = xenstore_read ( xen, &value, key, NULL ) ) == 0 ) { +		DBGC ( xen, "%s = \"%s\"\n", key, value ); +		free ( value ); +	} + +	/* Try to recurse into each child in turn */ +	if ( ( rc = xenstore_directory ( xen, &children, &len, key, +					 NULL ) ) == 0 ) { +		for ( child = children ; child < ( children + len ) ; +		      child += ( strlen ( child ) + 1 /* NUL */ ) ) { + +			/* Construct child key */ +			asprintf ( &child_key, "%s/%s", key, child ); +			if ( ! child_key ) { +				DBGC ( xen, "XENSTORE could not allocate child " +				       "key \"%s/%s\"\n", key, child ); +				rc = -ENOMEM; +				break; +			} + +			/* Recurse into child key, continuing on error */ +			xenstore_dump ( xen, child_key ); +			free ( child_key ); +		} +		free ( children ); +	} +}  | 
