--- /dev/null
+/*
+ * 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 );