| /* | |
| * Copyright (C) 2026 Michael Brown <mbrown@fensystems.co.uk>. | |
| * | |
| * This program is free software; you can redistribute it and/or | |
| * modify it under the terms of the GNU General Public License as | |
| * published by the Free Software Foundation; either version 2 of the | |
| * License, or any later version. | |
| * | |
| * This program is distributed in the hope that it will be useful, but | |
| * WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| * General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU General Public License | |
| * along with this program; if not, write to the Free Software | |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | |
| * 02110-1301, USA. | |
| * | |
| * You can also choose to distribute this program under the terms of | |
| * the Unmodified Binary Distribution Licence (as given in the file | |
| * COPYING.UBDL), provided that you have satisfied its requirements. | |
| */ | |
| FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); | |
| FILE_SECBOOT ( PERMITTED ); | |
| #include <assert.h> | |
| #include <errno.h> | |
| #include <string.h> | |
| #include <ipxe/console.h> | |
| #include <ipxe/disklog.h> | |
| #include <ipxe/init.h> | |
| #include <ipxe/umalloc.h> | |
| #include <ipxe/efi/efi.h> | |
| #include <ipxe/efi/Protocol/BlockIo.h> | |
| #include <ipxe/efi/Protocol/PartitionInfo.h> | |
| #include <config/console.h> | |
| /** @file | |
| * | |
| * EFI disk log console | |
| * | |
| */ | |
| /* Set default console usage if applicable */ | |
| #if ! ( defined ( CONSOLE_DISKLOG ) && CONSOLE_EXPLICIT ( CONSOLE_DISKLOG ) ) | |
| #undef CONSOLE_DISKLOG | |
| #define CONSOLE_DISKLOG ( CONSOLE_USAGE_ALL & ~CONSOLE_USAGE_LOG ) | |
| #endif | |
| /** EFI disk log console device handle */ | |
| static EFI_HANDLE efi_disklog_handle; | |
| /** EFI disk log console block I/O protocol */ | |
| static EFI_BLOCK_IO_PROTOCOL *efi_disklog_block; | |
| /** EFI disk log console media ID */ | |
| static UINT32 efi_disklog_media_id; | |
| /** EFI disk log console */ | |
| static struct disklog efi_disklog; | |
| struct console_driver efi_disklog_console __console_driver; | |
| /** | |
| * Write current logical block | |
| * | |
| * @ret rc Return status code | |
| */ | |
| static int efi_disklog_write ( void ) { | |
| EFI_BLOCK_IO_PROTOCOL *block = efi_disklog_block; | |
| struct disklog *disklog = &efi_disklog; | |
| EFI_STATUS efirc; | |
| int rc; | |
| /* Write disk block */ | |
| if ( ( efirc = block->WriteBlocks ( block, efi_disklog_media_id, | |
| disklog->lba, disklog->blksize, | |
| disklog->buffer ) ) != 0 ) { | |
| rc = -EEFI ( efirc ); | |
| DBGC ( disklog, "EFIDISKLOG %s could not write LBA %#llx: " | |
| "%s\n", efi_handle_name ( efi_disklog_handle ), | |
| ( ( unsigned long long ) disklog->lba ), | |
| strerror ( rc ) ); | |
| return rc; | |
| } | |
| return 0; | |
| } | |
| /** EFI disk log console operations */ | |
| static struct disklog_operations efi_disklog_op = { | |
| .write = efi_disklog_write, | |
| }; | |
| /** | |
| * Write character to console | |
| * | |
| * @v character Character | |
| */ | |
| static void efi_disklog_putchar ( int character ) { | |
| /* Write character */ | |
| disklog_putchar ( &efi_disklog, character ); | |
| } | |
| /** | |
| * Open EFI disk log partition | |
| * | |
| * @v handle Block device handle | |
| * @ret rc Return status code | |
| */ | |
| static int efi_disklog_open ( EFI_HANDLE handle ) { | |
| struct disklog *disklog = &efi_disklog; | |
| EFI_BLOCK_IO_PROTOCOL *block; | |
| EFI_BLOCK_IO_MEDIA *media; | |
| EFI_PARTITION_INFO_PROTOCOL *part; | |
| void *buffer; | |
| EFI_STATUS efirc; | |
| int rc; | |
| /* Record handle */ | |
| efi_disklog_handle = handle; | |
| /* Open block I/O protocol for ephemeral usage */ | |
| if ( ( rc = efi_open ( handle, &efi_block_io_protocol_guid, | |
| &block ) ) != 0 ) { | |
| DBGC ( disklog, "EFIDISKLOG %s could not open: %s\n", | |
| efi_handle_name ( handle ), strerror ( rc ) ); | |
| goto err_open; | |
| } | |
| media = block->Media; | |
| efi_disklog_block = block; | |
| efi_disklog_media_id = media->MediaId; | |
| /* Check this is a partition */ | |
| if ( ! media->LogicalPartition ) { | |
| DBGC2 ( disklog, "EFIDISKLOG %s is not a partition\n", | |
| efi_handle_name ( handle ) ); | |
| rc = -ENOTTY; | |
| goto err_not_partition; | |
| } | |
| /* Check partition type (if exposed by the platform) */ | |
| if ( ( rc = efi_open ( handle, &efi_partition_info_protocol_guid, | |
| &part ) ) == 0 ) { | |
| if ( ( part->Type != PARTITION_TYPE_MBR ) || | |
| ( part->Info.Mbr.OSIndicator != DISKLOG_PARTITION_TYPE )){ | |
| DBGC ( disklog, "EFIDISKLOG %s is not a log " | |
| "partition\n", efi_handle_name ( handle ) ); | |
| rc = -ENOTTY; | |
| goto err_not_log; | |
| } | |
| } else { | |
| DBGC2 ( disklog, "EFIDISKLOG %s has no partition info\n", | |
| efi_handle_name ( handle ) ); | |
| /* Continue anyway */ | |
| } | |
| /* Allocate buffer */ | |
| buffer = umalloc ( media->BlockSize ); | |
| if ( ! buffer ) { | |
| rc = -ENOMEM; | |
| goto err_alloc; | |
| } | |
| /* Read partition signature */ | |
| if ( ( efirc = block->ReadBlocks ( block, efi_disklog_media_id, 0, | |
| media->BlockSize, buffer ) ) != 0 ){ | |
| rc = -EEFI ( efirc ); | |
| DBGC ( disklog, "EFIDISKLOG %s could not read block 0: %s\n", | |
| efi_handle_name ( handle ), strerror ( rc ) ); | |
| goto err_read; | |
| } | |
| /* Initialise disk log console */ | |
| disklog_init ( disklog, &efi_disklog_op, &efi_disklog_console, buffer, | |
| media->BlockSize, 0, media->LastBlock ); | |
| /* Open disk log console */ | |
| if ( ( rc = disklog_open ( disklog ) ) != 0 ) { | |
| DBGC ( disklog, "EFIDISKLOG %s could not initialise log: " | |
| "%s\n", efi_handle_name ( handle ), strerror ( rc ) ); | |
| goto err_init; | |
| } | |
| /* Reopen handle for long-term use */ | |
| if ( ( rc = efi_open_by_driver ( handle, &efi_block_io_protocol_guid, | |
| &block ) ) != 0 ) { | |
| DBGC ( disklog, "EFIDISKLOG %s could not reopen: %s\n", | |
| efi_handle_name ( handle ), strerror ( rc ) ); | |
| goto err_reopen; | |
| } | |
| if ( block != efi_disklog_block ) { | |
| DBGC ( disklog, "EFIDISKLOG %s changed during reopening\n", | |
| efi_handle_name ( handle ) ); | |
| rc = -EPIPE; | |
| goto err_reopen_mismatch; | |
| } | |
| DBGC ( disklog, "EFIDISKLOG using %s\n", efi_handle_name ( handle ) ); | |
| DBGC2 ( disklog, "EFIDISKLOG has %zd-byte LBA [%#x,%#llx]\n", | |
| disklog->blksize, 0, | |
| ( ( unsigned long long ) disklog->max_lba ) ); | |
| return 0; | |
| err_reopen_mismatch: | |
| efi_close_by_driver ( handle, &efi_block_io_protocol_guid ); | |
| err_reopen: | |
| err_init: | |
| err_read: | |
| ufree ( buffer ); | |
| err_alloc: | |
| err_not_log: | |
| err_not_partition: | |
| efi_disklog_block = NULL; | |
| err_open: | |
| efi_disklog_handle = NULL; | |
| return rc; | |
| } | |
| /** | |
| * Initialise EFI disk log console | |
| * | |
| */ | |
| static void efi_disklog_init ( void ) { | |
| EFI_BOOT_SERVICES *bs = efi_systab->BootServices; | |
| EFI_GUID *protocol = &efi_block_io_protocol_guid; | |
| EFI_HANDLE *handles; | |
| UINTN count; | |
| unsigned int i; | |
| EFI_STATUS efirc; | |
| int rc; | |
| /* Locate all block I/O protocol handles */ | |
| if ( ( efirc = bs->LocateHandleBuffer ( ByProtocol, protocol, | |
| NULL, &count, | |
| &handles ) ) != 0 ) { | |
| rc = -EEFI ( efirc ); | |
| DBGC ( &efi_disklog, "EFIDISKLOG could not locate block I/O: " | |
| "%s\n", strerror ( rc ) ); | |
| goto err_locate; | |
| } | |
| /* Try each handle in turn */ | |
| for ( i = 0 ; i < count ; i++ ) { | |
| if ( ( rc = efi_disklog_open ( handles[i] ) ) == 0 ) | |
| break; | |
| } | |
| bs->FreePool ( handles ); | |
| err_locate: | |
| return; | |
| } | |
| /** | |
| * EFI disk log console initialisation function | |
| */ | |
| struct init_fn efi_disklog_init_fn __init_fn ( INIT_CONSOLE ) = { | |
| .name = "disklog", | |
| .initialise = efi_disklog_init, | |
| }; | |
| /** | |
| * Shut down EFI disk log console | |
| * | |
| * @v booting System is shutting down for OS boot | |
| */ | |
| static void efi_disklog_shutdown ( int booting __unused ) { | |
| struct disklog *disklog = &efi_disklog; | |
| /* Do nothing if we have no EFI disk log console */ | |
| if ( ! efi_disklog_handle ) | |
| return; | |
| /* Close EFI disk log console */ | |
| DBGC ( disklog, "EFIDISKLOG %s closed\n", | |
| efi_handle_name ( efi_disklog_handle ) ); | |
| efi_close_by_driver ( efi_disklog_handle, | |
| &efi_block_io_protocol_guid ); | |
| ufree ( disklog->buffer ); | |
| disklog->buffer = NULL; | |
| efi_disklog_handle = NULL; | |
| efi_disklog_block = NULL; | |
| } | |
| /** EFI disk log console shutdown function */ | |
| struct startup_fn efi_disklog_startup_fn __startup_fn ( STARTUP_EARLY ) = { | |
| .name = "disklog", | |
| .shutdown = efi_disklog_shutdown, | |
| }; | |
| /** EFI disk log console driver */ | |
| struct console_driver efi_disklog_console __console_driver = { | |
| .putchar = efi_disklog_putchar, | |
| .disabled = CONSOLE_DISABLED, | |
| .usage = CONSOLE_DISKLOG, | |
| }; | |
| /* Request a log partition from genfsimg */ | |
| IPXE_NOTE ( DISKLOG ); |