[efi] Generalise block device boot to support arbitrary EFI handles

SAN devices created by iPXE are visible to the firmware, and may be
accessed using the firmware's standard block I/O device interface
(e.g. INT 13 for BIOS, or EFI_BLOCK_IO_PROTOCOL for UEFI).  The iPXE
code to perform a SAN boot acts as a client of this standard block I/O
device interface, even when the underlying block I/O is being
performed by iPXE itself.

We rely on this separation to allow the "sanboot" command to be used
to boot from a local disk: since the code to perform a SAN boot does
not need direct access to an underlying iPXE SAN device, it may be
used to boot from any device providing the firmware's standard block
I/O device interface.

Clean up the EFI SAN boot code to require only a drive number and an
EFI_BLOCK_IO_PROTOCOL handle, in preparation for adding support for
booting from a local disk under UEFI.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
pull/1168/head
Michael Brown 2024-03-04 14:21:59 +00:00
parent eb720d2224
commit 37edfea72b
1 changed files with 66 additions and 47 deletions

View File

@ -211,25 +211,24 @@ efi_block_io_flush ( EFI_BLOCK_IO_PROTOCOL *block_io ) {
/** /**
* Connect all possible drivers to EFI block device * Connect all possible drivers to EFI block device
* *
* @v sandev SAN device * @v drive Drive number
* @v handle Block device handle
*/ */
static void efi_block_connect ( struct san_device *sandev ) { static void efi_block_connect ( unsigned int drive, EFI_HANDLE handle ) {
EFI_BOOT_SERVICES *bs = efi_systab->BootServices; EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
struct efi_block_data *block = sandev->priv;
EFI_STATUS efirc; EFI_STATUS efirc;
int rc; int rc;
/* Try to connect all possible drivers to this block device */ /* Try to connect all possible drivers to this block device */
if ( ( efirc = bs->ConnectController ( block->handle, NULL, if ( ( efirc = bs->ConnectController ( handle, NULL, NULL,
NULL, TRUE ) ) != 0 ) { TRUE ) ) != 0 ) {
rc = -EEFI ( efirc ); rc = -EEFI ( efirc );
DBGC ( sandev->drive, "EFIBLK %#02x could not connect " DBGC ( drive, "EFIBLK %#02x could not connect drivers: %s\n",
"drivers: %s\n", sandev->drive, strerror ( rc ) ); drive, strerror ( rc ) );
/* May not be an error; may already be connected */ /* May not be an error; may already be connected */
} }
DBGC2 ( sandev->drive, "EFIBLK %#02x supports protocols:\n", DBGC2 ( drive, "EFIBLK %#02x supports protocols:\n", drive );
sandev->drive ); DBGC2_EFI_PROTOCOLS ( drive, handle );
DBGC2_EFI_PROTOCOLS ( sandev->drive, block->handle );
} }
/** /**
@ -316,7 +315,7 @@ static int efi_block_hook ( unsigned int drive, struct uri **uris,
} }
/* Connect all possible protocols */ /* Connect all possible protocols */
efi_block_connect ( sandev ); efi_block_connect ( drive, block->handle );
return drive; return drive;
@ -507,12 +506,12 @@ static int efi_block_describe ( void ) {
/** /**
* Check for existence of a file within a filesystem * Check for existence of a file within a filesystem
* *
* @v sandev SAN device * @v drive Drive number
* @v handle Filesystem handle * @v handle Filesystem handle
* @v filename Filename (or NULL to use default) * @v filename Filename (or NULL to use default)
* @ret rc Return status code * @ret rc Return status code
*/ */
static int efi_block_filename ( struct san_device *sandev, EFI_HANDLE handle, static int efi_block_filename ( unsigned int drive, EFI_HANDLE handle,
const char *filename ) { const char *filename ) {
EFI_BOOT_SERVICES *bs = efi_systab->BootServices; EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
EFI_GUID *protocol = &efi_simple_file_system_protocol_guid; EFI_GUID *protocol = &efi_simple_file_system_protocol_guid;
@ -540,8 +539,8 @@ static int efi_block_filename ( struct san_device *sandev, EFI_HANDLE handle,
efi_image_handle, handle, efi_image_handle, handle,
EFI_OPEN_PROTOCOL_GET_PROTOCOL ))!=0){ EFI_OPEN_PROTOCOL_GET_PROTOCOL ))!=0){
rc = -EEFI ( efirc ); rc = -EEFI ( efirc );
DBGC ( sandev->drive, "EFIBLK %#02x could not open %s device " DBGC ( drive, "EFIBLK %#02x could not open %s device path: "
"path: %s\n", sandev->drive, efi_handle_name ( handle ), "%s\n", drive, efi_handle_name ( handle ),
strerror ( rc ) ); strerror ( rc ) );
goto err_open; goto err_open;
} }
@ -549,9 +548,8 @@ static int efi_block_filename ( struct san_device *sandev, EFI_HANDLE handle,
/* Open root volume */ /* Open root volume */
if ( ( efirc = u.fs->OpenVolume ( u.fs, &root ) ) != 0 ) { if ( ( efirc = u.fs->OpenVolume ( u.fs, &root ) ) != 0 ) {
rc = -EEFI ( efirc ); rc = -EEFI ( efirc );
DBGC ( sandev->drive, "EFIBLK %#02x could not open %s root: " DBGC ( drive, "EFIBLK %#02x could not open %s root: %s\n",
"%s\n", sandev->drive, efi_handle_name ( handle ), drive, efi_handle_name ( handle ), strerror ( rc ) );
strerror ( rc ) );
goto err_volume; goto err_volume;
} }
@ -559,9 +557,9 @@ static int efi_block_filename ( struct san_device *sandev, EFI_HANDLE handle,
if ( ( efirc = root->Open ( root, &file, wname, if ( ( efirc = root->Open ( root, &file, wname,
EFI_FILE_MODE_READ, 0 ) ) != 0 ) { EFI_FILE_MODE_READ, 0 ) ) != 0 ) {
rc = -EEFI ( efirc ); rc = -EEFI ( efirc );
DBGC ( sandev->drive, "EFIBLK %#02x could not open %s/%ls: " DBGC ( drive, "EFIBLK %#02x could not open %s/%ls: %s\n",
"%s\n", sandev->drive, efi_handle_name ( handle ), drive, efi_handle_name ( handle ), wname,
wname, strerror ( rc ) ); strerror ( rc ) );
goto err_file; goto err_file;
} }
@ -580,14 +578,14 @@ static int efi_block_filename ( struct san_device *sandev, EFI_HANDLE handle,
/** /**
* Check EFI block device filesystem match * Check EFI block device filesystem match
* *
* @v sandev SAN device * @v drive Drive number
* @v handle Filesystem handle * @v handle Filesystem handle
* @v path Block device path * @v path Block device path
* @v filename Filename (or NULL to use default) * @v filename Filename (or NULL to use default)
* @v fspath Filesystem device path to fill in * @v fspath Filesystem device path to fill in
* @ret rc Return status code * @ret rc Return status code
*/ */
static int efi_block_match ( struct san_device *sandev, EFI_HANDLE handle, static int efi_block_match ( unsigned int drive, EFI_HANDLE handle,
EFI_DEVICE_PATH_PROTOCOL *path, EFI_DEVICE_PATH_PROTOCOL *path,
const char *filename, const char *filename,
EFI_DEVICE_PATH_PROTOCOL **fspath ) { EFI_DEVICE_PATH_PROTOCOL **fspath ) {
@ -605,8 +603,8 @@ static int efi_block_match ( struct san_device *sandev, EFI_HANDLE handle,
efi_image_handle, handle, efi_image_handle, handle,
EFI_OPEN_PROTOCOL_GET_PROTOCOL ))!=0){ EFI_OPEN_PROTOCOL_GET_PROTOCOL ))!=0){
rc = -EEFI ( efirc ); rc = -EEFI ( efirc );
DBGC ( sandev->drive, "EFIBLK %#02x could not open %s device " DBGC ( drive, "EFIBLK %#02x could not open %s device path: "
"path: %s\n", sandev->drive, efi_handle_name ( handle ), "%s\n", drive, efi_handle_name ( handle ),
strerror ( rc ) ); strerror ( rc ) );
goto err_open; goto err_open;
} }
@ -616,15 +614,15 @@ static int efi_block_match ( struct san_device *sandev, EFI_HANDLE handle,
if ( memcmp ( u.path, path, efi_path_len ( path ) ) != 0 ) { if ( memcmp ( u.path, path, efi_path_len ( path ) ) != 0 ) {
/* Not a child device */ /* Not a child device */
rc = -ENOTTY; rc = -ENOTTY;
DBGC2 ( sandev->drive, "EFIBLK %#02x is not parent of %s\n", DBGC2 ( drive, "EFIBLK %#02x is not parent of %s\n",
sandev->drive, efi_handle_name ( handle ) ); drive, efi_handle_name ( handle ) );
goto err_not_child; goto err_not_child;
} }
DBGC ( sandev->drive, "EFIBLK %#02x contains filesystem %s\n", DBGC ( drive, "EFIBLK %#02x contains filesystem %s\n",
sandev->drive, efi_devpath_text ( u.path ) ); drive, efi_devpath_text ( u.path ) );
/* Check if filesystem contains boot filename */ /* Check if filesystem contains boot filename */
if ( ( rc = efi_block_filename ( sandev, handle, filename ) ) != 0 ) if ( ( rc = efi_block_filename ( drive, handle, filename ) ) != 0 )
goto err_filename; goto err_filename;
/* Success */ /* Success */
@ -640,15 +638,21 @@ static int efi_block_match ( struct san_device *sandev, EFI_HANDLE handle,
/** /**
* Scan EFI block device for a matching filesystem * Scan EFI block device for a matching filesystem
* *
* @v sandev SAN device * @v drive Drive number
* @v handle Block device handle
* @v filename Filename (or NULL to use default) * @v filename Filename (or NULL to use default)
* @v fspath Filesystem device path to fill in * @v fspath Filesystem device path to fill in
* @ret rc Return status code * @ret rc Return status code
*/ */
static int efi_block_scan ( struct san_device *sandev, const char *filename, static int efi_block_scan ( unsigned int drive, EFI_HANDLE handle,
const char *filename,
EFI_DEVICE_PATH_PROTOCOL **fspath ) { EFI_DEVICE_PATH_PROTOCOL **fspath ) {
EFI_BOOT_SERVICES *bs = efi_systab->BootServices; EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
struct efi_block_data *block = sandev->priv; EFI_GUID *protocol = &efi_device_path_protocol_guid;
union {
EFI_DEVICE_PATH_PROTOCOL *path;
void *interface;
} u;
EFI_HANDLE *handles; EFI_HANDLE *handles;
UINTN count; UINTN count;
unsigned int i; unsigned int i;
@ -656,15 +660,25 @@ static int efi_block_scan ( struct san_device *sandev, const char *filename,
int rc; int rc;
/* Connect up possible file system drivers */ /* Connect up possible file system drivers */
efi_block_connect ( sandev ); efi_block_connect ( drive, handle );
/* Identify device path */
if ( ( efirc = bs->OpenProtocol ( handle, protocol, &u.interface,
efi_image_handle, handle,
EFI_OPEN_PROTOCOL_GET_PROTOCOL ))!=0){
rc = -EEFI ( efirc );
DBGC ( drive, "EFIBLK %#02x could not open device path: %s\n",
drive, strerror ( rc ) );
goto err_open;
}
/* Locate all Simple File System protocol handles */ /* Locate all Simple File System protocol handles */
if ( ( efirc = bs->LocateHandleBuffer ( if ( ( efirc = bs->LocateHandleBuffer (
ByProtocol, &efi_simple_file_system_protocol_guid, ByProtocol, &efi_simple_file_system_protocol_guid,
NULL, &count, &handles ) ) != 0 ) { NULL, &count, &handles ) ) != 0 ) {
rc = -EEFI ( efirc ); rc = -EEFI ( efirc );
DBGC ( sandev->drive, "EFIBLK %#02x cannot locate file " DBGC ( drive, "EFIBLK %#02x cannot locate file systems: %s\n",
"systems: %s\n", sandev->drive, strerror ( rc ) ); drive, strerror ( rc ) );
goto err_locate; goto err_locate;
} }
@ -673,7 +687,7 @@ static int efi_block_scan ( struct san_device *sandev, const char *filename,
for ( i = 0 ; i < count ; i++ ) { for ( i = 0 ; i < count ; i++ ) {
/* Check for a matching filesystem */ /* Check for a matching filesystem */
if ( ( rc = efi_block_match ( sandev, handles[i], block->path, if ( ( rc = efi_block_match ( drive, handles[i], u.path,
filename, fspath ) ) != 0 ) filename, fspath ) ) != 0 )
continue; continue;
@ -682,18 +696,20 @@ static int efi_block_scan ( struct san_device *sandev, const char *filename,
bs->FreePool ( handles ); bs->FreePool ( handles );
err_locate: err_locate:
bs->CloseProtocol ( handle, protocol, efi_image_handle, handle );
err_open:
return rc; return rc;
} }
/** /**
* Boot from EFI block device filesystem boot image * Boot from EFI block device filesystem boot image
* *
* @v sandev SAN device * @v drive Drive number
* @v fspath Filesystem device path * @v fspath Filesystem device path
* @v filename Filename (or NULL to use default) * @v filename Filename (or NULL to use default)
* @ret rc Return status code * @ret rc Return status code
*/ */
static int efi_block_exec ( struct san_device *sandev, static int efi_block_exec ( unsigned int drive,
EFI_DEVICE_PATH_PROTOCOL *fspath, EFI_DEVICE_PATH_PROTOCOL *fspath,
const char *filename ) { const char *filename ) {
EFI_BOOT_SERVICES *bs = efi_systab->BootServices; EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
@ -735,16 +751,16 @@ static int efi_block_exec ( struct san_device *sandev,
} }
end = ( ( ( void * ) filepath ) + filepath_len ); end = ( ( ( void * ) filepath ) + filepath_len );
efi_path_terminate ( end ); efi_path_terminate ( end );
DBGC ( sandev->drive, "EFIBLK %#02x trying to load %s\n", DBGC ( drive, "EFIBLK %#02x trying to load %s\n",
sandev->drive, efi_devpath_text ( path ) ); drive, efi_devpath_text ( path ) );
/* Load image */ /* Load image */
image = NULL; image = NULL;
if ( ( efirc = bs->LoadImage ( FALSE, efi_image_handle, path, NULL, 0, if ( ( efirc = bs->LoadImage ( FALSE, efi_image_handle, path, NULL, 0,
&image ) ) != 0 ) { &image ) ) != 0 ) {
rc = -EEFI ( efirc ); rc = -EEFI ( efirc );
DBGC ( sandev->drive, "EFIBLK %#02x could not load: %s\n", DBGC ( drive, "EFIBLK %#02x could not load: %s\n",
sandev->drive, strerror ( rc ) ); drive, strerror ( rc ) );
if ( efirc == EFI_SECURITY_VIOLATION ) { if ( efirc == EFI_SECURITY_VIOLATION ) {
goto err_load_security_violation; goto err_load_security_violation;
} else { } else {
@ -755,8 +771,8 @@ static int efi_block_exec ( struct san_device *sandev,
/* Start image */ /* Start image */
efirc = bs->StartImage ( image, NULL, NULL ); efirc = bs->StartImage ( image, NULL, NULL );
rc = ( efirc ? -EEFI ( efirc ) : 0 ); rc = ( efirc ? -EEFI ( efirc ) : 0 );
DBGC ( sandev->drive, "EFIBLK %#02x boot image returned: %s\n", DBGC ( drive, "EFIBLK %#02x boot image returned: %s\n",
sandev->drive, strerror ( rc ) ); drive, strerror ( rc ) );
err_load_security_violation: err_load_security_violation:
bs->UnloadImage ( image ); bs->UnloadImage ( image );
@ -776,6 +792,7 @@ static int efi_block_exec ( struct san_device *sandev,
static int efi_block_boot ( unsigned int drive, const char *filename ) { static int efi_block_boot ( unsigned int drive, const char *filename ) {
EFI_DEVICE_PATH_PROTOCOL *fspath = NULL; EFI_DEVICE_PATH_PROTOCOL *fspath = NULL;
struct san_device *sandev; struct san_device *sandev;
struct efi_block_data *block;
int rc; int rc;
/* Find SAN device */ /* Find SAN device */
@ -785,17 +802,19 @@ static int efi_block_boot ( unsigned int drive, const char *filename ) {
rc = -ENODEV; rc = -ENODEV;
goto err_sandev_find; goto err_sandev_find;
} }
block = sandev->priv;
/* Release SNP devices */ /* Release SNP devices */
efi_snp_release(); efi_snp_release();
/* Scan for a matching filesystem within this block device */ /* Scan for a matching filesystem within this block device */
if ( ( rc = efi_block_scan ( sandev, filename, &fspath ) ) != 0 ) { if ( ( rc = efi_block_scan ( drive, block->handle, filename,
&fspath ) ) != 0 ) {
goto err_scan; goto err_scan;
} }
/* Attempt to boot from this filesystem */ /* Attempt to boot from this filesystem */
if ( ( rc = efi_block_exec ( sandev, fspath, filename ) ) != 0 ) if ( ( rc = efi_block_exec ( drive, fspath, filename ) ) != 0 )
goto err_exec; goto err_exec;
err_exec: err_exec: