From 51612b6e69e049b6f08e833581815b4d0e9ffd39 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Sun, 21 Nov 2021 13:19:55 +0000 Subject: [efi] Do not attempt to use console output after ExitBootServices() A successful call to ExitBootServices() will result in the EFI console becoming unusable. Ensure that the EFI wrapper produces a complete line of debug output before calling the wrapped ExitBootServices() method, and attempt subsequent debug output only if the call fails. Signed-off-by: Michael Brown --- src/interface/efi/efi_wrap.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'src/interface') diff --git a/src/interface/efi/efi_wrap.c b/src/interface/efi/efi_wrap.c index 5c02a7ee1..65c70ea28 100644 --- a/src/interface/efi/efi_wrap.c +++ b/src/interface/efi/efi_wrap.c @@ -735,11 +735,14 @@ efi_exit_boot_services_wrapper ( EFI_HANDLE image_handle, UINTN map_key ) { void *retaddr = __builtin_return_address ( 0 ); EFI_STATUS efirc; - DBGC ( colour, "ExitBootServices ( %s, %#llx ) ", + DBGC ( colour, "ExitBootServices ( %s, %#llx ) -> %p\n", efi_handle_name ( image_handle ), - ( ( unsigned long long ) map_key ) ); + ( ( unsigned long long ) map_key ), retaddr ); efirc = bs->ExitBootServices ( image_handle, map_key ); - DBGC ( colour, "= %s -> %p\n", efi_status ( efirc ), retaddr ); + if ( efirc != 0 ) { + DBGC ( colour, "ExitBootServices ( ... ) = %s -> %p\n", + efi_status ( efirc ), retaddr ); + } return efirc; } -- cgit v1.2.3-55-g7522 From b6045a8cbb3843f185e8b1ab6488ee6799d0c2b7 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Sun, 21 Nov 2021 13:27:14 +0000 Subject: [efi] Modify global system table when wrapping a loaded image The EFI loaded image protocol allows an image to be provided with a custom system table, and we currently use this mechanism to wrap any boot services calls made by the loaded image in order to provide strace-like debugging via DEBUG=efi_wrap. The ExitBootServices() call will modify the global system table, leaving the loaded image using a system table that is no longer current. When DEBUG=efi_wrap is used, this generally results in the machine locking up at the point that the loaded operating system calls ExitBootServices(). Fix by modifying the global EFI system table to point to our wrapper functions, instead of providing a custom system table via the loaded image protocol. Signed-off-by: Michael Brown --- src/include/ipxe/efi/efi_wrap.h | 2 +- src/interface/efi/efi_wrap.c | 99 +++++++++++++++++++++++------------------ 2 files changed, 57 insertions(+), 44 deletions(-) (limited to 'src/interface') diff --git a/src/include/ipxe/efi/efi_wrap.h b/src/include/ipxe/efi/efi_wrap.h index 6c7ccf2e4..2747a9e33 100644 --- a/src/include/ipxe/efi/efi_wrap.h +++ b/src/include/ipxe/efi/efi_wrap.h @@ -10,7 +10,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include -extern EFI_SYSTEM_TABLE * efi_wrap_systab ( void ); +extern EFI_BOOT_SERVICES * efi_wrap_bs ( void ); extern void efi_wrap ( EFI_HANDLE handle ); #endif /* _IPXE_EFI_WRAP_H */ diff --git a/src/interface/efi/efi_wrap.c b/src/interface/efi/efi_wrap.c index 65c70ea28..5d5d2caee 100644 --- a/src/interface/efi/efi_wrap.c +++ b/src/interface/efi/efi_wrap.c @@ -195,6 +195,47 @@ static const char * efi_timer_delay ( EFI_TIMER_DELAY type ) { } } +/** + * Dump information about a loaded image + * + * @v handle Image handle + */ +static void efi_dump_image ( EFI_HANDLE handle ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + union { + EFI_LOADED_IMAGE_PROTOCOL *image; + void *intf; + } loaded; + EFI_STATUS efirc; + int rc; + + /* 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, "WRAP %s could not get loaded image protocol: " + "%s\n", efi_handle_name ( handle ), strerror ( rc ) ); + return; + } + + /* Dump image information */ + DBGC ( colour, "WRAP %s at base %p has protocols:\n", + efi_handle_name ( handle ), loaded.image->ImageBase ); + DBGC_EFI_PROTOCOLS ( colour, handle ); + DBGC ( colour, "WRAP %s parent", efi_handle_name ( handle ) ); + DBGC ( colour, " %s\n", efi_handle_name ( loaded.image->ParentHandle )); + DBGC ( colour, "WRAP %s device", efi_handle_name ( handle ) ); + DBGC ( colour, " %s\n", efi_handle_name ( loaded.image->DeviceHandle )); + DBGC ( colour, "WRAP %s file", efi_handle_name ( handle ) ); + DBGC ( colour, " %s\n", efi_devpath_text ( loaded.image->FilePath ) ); + + /* Close loaded image protocol */ + bs->CloseProtocol ( handle, &efi_loaded_image_protocol_guid, + efi_image_handle, NULL ); +} + /** * Wrap RaiseTPL() * @@ -655,9 +696,9 @@ efi_load_image_wrapper ( BOOLEAN boot_policy, EFI_HANDLE parent_image_handle, DBGC ( colour, "%s ", efi_handle_name ( *image_handle ) ); DBGC ( colour, ") -> %p\n", retaddr ); - /* Wrap the new image */ + /* Dump information about loaded image */ if ( efirc == 0 ) - efi_wrap ( *image_handle ); + efi_dump_image ( *image_handle ); return efirc; } @@ -1132,12 +1173,11 @@ efi_create_event_ex_wrapper ( UINT32 type, EFI_TPL notify_tpl, } /** - * Build table wrappers + * Build boot services table wrapper * - * @ret systab Wrapped system table + * @ret bs Wrapped boot services table */ -EFI_SYSTEM_TABLE * efi_wrap_systab ( void ) { - static EFI_SYSTEM_TABLE efi_systab_wrapper; +EFI_BOOT_SERVICES * efi_wrap_bs ( void ) { static EFI_BOOT_SERVICES efi_bs_wrapper; EFI_BOOT_SERVICES *bs = efi_systab->BootServices; @@ -1197,12 +1237,7 @@ EFI_SYSTEM_TABLE * efi_wrap_systab ( void ) { = efi_uninstall_multiple_protocol_interfaces_wrapper; efi_bs_wrapper.CreateEventEx = efi_create_event_ex_wrapper; - /* Build system table wrapper */ - memcpy ( &efi_systab_wrapper, efi_systab, - sizeof ( efi_systab_wrapper ) ); - efi_systab_wrapper.BootServices = &efi_bs_wrapper; - - return &efi_systab_wrapper; + return &efi_bs_wrapper; } /** @@ -1211,42 +1246,20 @@ EFI_SYSTEM_TABLE * efi_wrap_systab ( void ) { * @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; + static EFI_SYSTEM_TABLE efi_systab_copy; /* Do nothing unless debugging is enabled */ if ( ! DBG_LOG ) return; - /* 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, "WRAP %s could not get loaded image protocol: " - "%s\n", efi_handle_name ( handle ), strerror ( rc ) ); - return; + /* Construct modified system table */ + if ( efi_systab != &efi_systab_copy ) { + memcpy ( &efi_systab_copy, efi_systab, + sizeof ( efi_systab_copy ) ); + efi_systab->BootServices = efi_wrap_bs(); + efi_systab = &efi_systab_copy; } - /* Provide system table wrapper to image */ - loaded.image->SystemTable = efi_wrap_systab(); - DBGC ( colour, "WRAP %s at base %p has protocols:\n", - efi_handle_name ( handle ), loaded.image->ImageBase ); - DBGC_EFI_PROTOCOLS ( colour, handle ); - DBGC ( colour, "WRAP %s parent", efi_handle_name ( handle ) ); - DBGC ( colour, " %s\n", efi_handle_name ( loaded.image->ParentHandle )); - DBGC ( colour, "WRAP %s device", efi_handle_name ( handle ) ); - DBGC ( colour, " %s\n", efi_handle_name ( loaded.image->DeviceHandle )); - DBGC ( colour, "WRAP %s file", efi_handle_name ( handle ) ); - DBGC ( colour, " %s\n", efi_devpath_text ( loaded.image->FilePath ) ); - - /* Close loaded image protocol */ - bs->CloseProtocol ( handle, &efi_loaded_image_protocol_guid, - efi_image_handle, NULL ); + /* Dump image information */ + efi_dump_image ( handle ); } -- cgit v1.2.3-55-g7522 From a046da21a4c58365be6c029147799db974280048 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Mon, 22 Nov 2021 12:50:38 +0000 Subject: [efi] Raise TPL during driver unload entry point The efi_unload() function is currently missing the calls to raise and restore the TPL. This has the side effect of causing iPXE to return from the driver unload entry point at TPL_CALLBACK, which will cause unexpected behaviour (typically a system lockup) shortly afterwards. Fix by adding the missing calls to raise and restore the TPL. Debugged-by: Petr Borsodi Signed-off-by: Michael Brown --- src/interface/efi/efi_init.c | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'src/interface') diff --git a/src/interface/efi/efi_init.c b/src/interface/efi/efi_init.c index b7cac16e5..1c6e9d440 100644 --- a/src/interface/efi/efi_init.c +++ b/src/interface/efi/efi_init.c @@ -316,9 +316,13 @@ EFI_STATUS efi_init ( EFI_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; + struct efi_saved_tpl tpl; DBGC ( systab, "EFI image unloading\n" ); + /* Raise TPL */ + efi_raise_tpl ( &tpl ); + /* Shut down */ shutdown_exit(); @@ -336,6 +340,9 @@ static EFI_STATUS EFIAPI efi_unload ( EFI_HANDLE image_handle __unused ) { DBGC ( systab, "EFI image unloaded\n" ); + /* Restore TPL */ + efi_restore_tpl ( &tpl ); + return 0; } -- cgit v1.2.3-55-g7522 From 562c74e1ea52a399f7a6c416e35d98f4b37888cf Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Mon, 22 Nov 2021 14:53:33 +0000 Subject: [efi] Run ExitBootServices shutdown hook at TPL_NOTIFY On some systems (observed with the Thunderbolt ports on a ThinkPad X1 Extreme Gen3 and a ThinkPad P53), if the IOMMU is enabled then the system firmware will install an ExitBootServices notification event that disables bus mastering on the Thunderbolt xHCI controller and all PCI bridges, and destroys any extant IOMMU mappings. This leaves the xHCI controller unable to perform any DMA operations. As described in commit 236299b ("[xhci] Avoid DMA during shutdown if firmware has disabled bus mastering"), any subsequent DMA operation attempted by the xHCI controller will end up completing after the operating system kernel has reenabled bus mastering, resulting in a DMA operation to an area of memory that the hardware is no longer permitted to access and, on Windows with the Driver Verifier enabled, a STOP 0xE6 (DRIVER_VERIFIER_DMA_VIOLATION). That commit avoids triggering any DMA attempts during the shutdown of the xHCI controller itself. However, this is not a complete solution since any attached and opened USB device (e.g. a USB NIC) may asynchronously trigger DMA attempts that happen to occur after bus mastering has been disabled but before we reset the xHCI controller. Avoid this problem by installing our own ExitBootServices notification event at TPL_NOTIFY, thereby causing it to be invoked before the firmware's own ExitBootServices notification event that disables bus mastering. This unsurprisingly causes the shutdown hook itself to be invoked at TPL_NOTIFY, which causes a fatal error when later code attempts to raise the TPL to TPL_CALLBACK (which is a lower TPL). Work around this problem by redefining the "internal" iPXE TPL to be variable, and set this internal TPL to TPL_NOTIFY when the shutdown hook is invoked. Avoid calling into an underlying SNP protocol instance from within our shutdown hook at TPL_NOTIFY, since the underlying SNP driver may attempt to raise the TPL to TPL_CALLBACK (which would cause a fatal error). Failing to shut down the underlying SNP device is safe to do since the underlying device must, in any case, have installed its own ExitBootServices hook if any shutdown actions are required. Reported-by: Andreas Hammarskjöld Tested-by: Andreas Hammarskjöld Signed-off-by: Michael Brown --- src/drivers/net/efi/nii.c | 2 +- src/drivers/net/efi/snpnet.c | 18 ++++++++++++++---- src/include/ipxe/efi/efi.h | 1 + src/interface/efi/efi_entropy.c | 4 ++-- src/interface/efi/efi_init.c | 20 +++++++++++++++++--- src/interface/efi/efi_timer.c | 2 +- 6 files changed, 36 insertions(+), 11 deletions(-) (limited to 'src/interface') diff --git a/src/drivers/net/efi/nii.c b/src/drivers/net/efi/nii.c index b9f34650e..833462e77 100644 --- a/src/drivers/net/efi/nii.c +++ b/src/drivers/net/efi/nii.c @@ -576,7 +576,7 @@ static int nii_issue_cpb_db ( struct nii_nic *nii, unsigned int op, void *cpb, cdb.IFnum = nii->nii->IfNum; /* Raise task priority level */ - tpl = bs->RaiseTPL ( TPL_CALLBACK ); + tpl = bs->RaiseTPL ( efi_internal_tpl ); /* Issue command */ DBGC2 ( nii, "NII %s issuing %02x:%04x ifnum %d%s%s\n", diff --git a/src/drivers/net/efi/snpnet.c b/src/drivers/net/efi/snpnet.c index fb5240277..69ec6f5e5 100644 --- a/src/drivers/net/efi/snpnet.c +++ b/src/drivers/net/efi/snpnet.c @@ -164,6 +164,10 @@ static int snpnet_transmit ( struct net_device *netdev, EFI_STATUS efirc; int rc; + /* Do nothing if shutdown is in progress */ + if ( efi_shutdown_in_progress ) + return -ECANCELED; + /* Defer the packet if there is already a transmission in progress */ if ( snp->txbuf ) { netdev_tx_defer ( netdev, iobuf ); @@ -283,6 +287,10 @@ static void snpnet_poll_rx ( struct net_device *netdev ) { */ static void snpnet_poll ( struct net_device *netdev ) { + /* Do nothing if shutdown is in progress */ + if ( efi_shutdown_in_progress ) + return; + /* Process any TX completions */ snpnet_poll_tx ( netdev ); @@ -426,8 +434,9 @@ static void snpnet_close ( struct net_device *netdev ) { EFI_STATUS efirc; int rc; - /* Shut down NIC */ - if ( ( efirc = snp->snp->Shutdown ( snp->snp ) ) != 0 ) { + /* Shut down NIC (unless whole system shutdown is in progress) */ + if ( ( ! efi_shutdown_in_progress ) && + ( ( efirc = snp->snp->Shutdown ( snp->snp ) ) != 0 ) ) { rc = -EEFI ( efirc ); DBGC ( snp, "SNP %s could not shut down: %s\n", netdev->name, strerror ( rc ) ); @@ -589,8 +598,9 @@ void snpnet_stop ( struct efi_device *efidev ) { /* Unregister network device */ unregister_netdev ( netdev ); - /* Stop SNP protocol */ - if ( ( efirc = snp->snp->Stop ( snp->snp ) ) != 0 ) { + /* Stop SNP protocol (unless whole system shutdown is in progress) */ + if ( ( ! efi_shutdown_in_progress ) && + ( ( efirc = snp->snp->Stop ( snp->snp ) ) != 0 ) ) { rc = -EEFI ( efirc ); DBGC ( device, "SNP %s could not stop: %s\n", efi_handle_name ( device ), strerror ( rc ) ); diff --git a/src/include/ipxe/efi/efi.h b/src/include/ipxe/efi/efi.h index a83fa0f27..1dd0d4453 100644 --- a/src/include/ipxe/efi/efi.h +++ b/src/include/ipxe/efi/efi.h @@ -223,6 +223,7 @@ extern EFI_HANDLE efi_image_handle; extern EFI_LOADED_IMAGE_PROTOCOL *efi_loaded_image; extern EFI_DEVICE_PATH_PROTOCOL *efi_loaded_image_path; extern EFI_SYSTEM_TABLE *efi_systab; +extern EFI_TPL efi_internal_tpl; extern EFI_TPL efi_external_tpl; extern int efi_shutdown_in_progress; diff --git a/src/interface/efi/efi_entropy.c b/src/interface/efi/efi_entropy.c index 70cd06293..1e8ddfb68 100644 --- a/src/interface/efi/efi_entropy.c +++ b/src/interface/efi/efi_entropy.c @@ -104,8 +104,8 @@ static void efi_entropy_disable ( void ) { /* Close timer tick event */ bs->CloseEvent ( tick ); - /* Return to TPL_CALLBACK */ - bs->RaiseTPL ( TPL_CALLBACK ); + /* Return to internal TPL */ + bs->RaiseTPL ( efi_internal_tpl ); } /** diff --git a/src/interface/efi/efi_init.c b/src/interface/efi/efi_init.c index 1c6e9d440..5d98f9ff7 100644 --- a/src/interface/efi/efi_init.c +++ b/src/interface/efi/efi_init.c @@ -47,6 +47,9 @@ EFI_DEVICE_PATH_PROTOCOL *efi_loaded_image_path; */ EFI_SYSTEM_TABLE * _C2 ( PLATFORM, _systab ); +/** Internal task priority level */ +EFI_TPL efi_internal_tpl = TPL_CALLBACK; + /** External task priority level */ EFI_TPL efi_external_tpl = TPL_APPLICATION; @@ -79,6 +82,17 @@ static EFI_STATUS EFIAPI efi_unload ( EFI_HANDLE image_handle ); static EFIAPI void efi_shutdown_hook ( EFI_EVENT event __unused, void *context __unused ) { + /* This callback is invoked at TPL_NOTIFY in order to ensure + * that we have an opportunity to shut down cleanly before + * other shutdown hooks perform destructive operations such as + * disabling the IOMMU. + * + * Modify the internal task priority level so that no code + * attempts to raise from TPL_NOTIFY to TPL_CALLBACK (which + * would trigger a fatal exception). + */ + efi_internal_tpl = TPL_NOTIFY; + /* Mark shutdown as being in progress, to indicate that large * parts of the system (e.g. timers) are no longer functional. */ @@ -273,7 +287,7 @@ EFI_STATUS efi_init ( EFI_HANDLE image_handle, * bother doing so when ExitBootServices() is called. */ if ( ( efirc = bs->CreateEvent ( EVT_SIGNAL_EXIT_BOOT_SERVICES, - TPL_CALLBACK, efi_shutdown_hook, + TPL_NOTIFY, efi_shutdown_hook, NULL, &efi_shutdown_event ) ) != 0 ) { rc = -EEFI ( efirc ); DBGC ( systab, "EFI could not create ExitBootServices event: " @@ -373,7 +387,7 @@ __attribute__ (( noreturn )) void __stack_chk_fail ( void ) { } /** - * Raise task priority level to TPL_CALLBACK + * Raise task priority level to internal level * * @v tpl Saved TPL */ @@ -384,7 +398,7 @@ void efi_raise_tpl ( struct efi_saved_tpl *tpl ) { tpl->previous = efi_external_tpl; /* Raise TPL and record previous TPL as new external TPL */ - tpl->current = bs->RaiseTPL ( TPL_CALLBACK ); + tpl->current = bs->RaiseTPL ( efi_internal_tpl ); efi_external_tpl = tpl->current; } diff --git a/src/interface/efi/efi_timer.c b/src/interface/efi/efi_timer.c index 405cd3454..6427eb1d8 100644 --- a/src/interface/efi/efi_timer.c +++ b/src/interface/efi/efi_timer.c @@ -137,7 +137,7 @@ static unsigned long efi_currticks ( void ) { efi_jiffies++; } else { bs->RestoreTPL ( efi_external_tpl ); - bs->RaiseTPL ( TPL_CALLBACK ); + bs->RaiseTPL ( efi_internal_tpl ); } return ( efi_jiffies * ( TICKS_PER_SEC / EFI_JIFFIES_PER_SEC ) ); -- cgit v1.2.3-55-g7522 From 9062544f6a0c69c249b90d21a08d05518aafc2ec Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Wed, 24 Nov 2021 15:43:46 +0000 Subject: [efi] Disable EFI watchdog timer when shutting down to boot an OS The UEFI specification mandates that the EFI watchdog timer should be disabled by the platform firmware as part of the ExitBootServices() call, but some platforms (e.g. Hyper-V) are observed to occasionally forget to do so, resulting in a reboot approximately five minutes after starting the operating system. Work around these firmware bugs by disabling the watchdog timer ourselves. Requested-by: Andreas Hammarskjöld Tested-by: Andreas Hammarskjöld Signed-off-by: Michael Brown --- src/interface/efi/efi_watchdog.c | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) (limited to 'src/interface') diff --git a/src/interface/efi/efi_watchdog.c b/src/interface/efi/efi_watchdog.c index 7061f81d8..dcc9a5668 100644 --- a/src/interface/efi/efi_watchdog.c +++ b/src/interface/efi/efi_watchdog.c @@ -34,6 +34,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include #include +#include #include #include @@ -80,3 +81,36 @@ static void efi_watchdog_expired ( struct retry_timer *timer, /** Watchdog holdoff timer */ struct retry_timer efi_watchdog = TIMER_INIT ( efi_watchdog_expired ); + +/** + * Disable watching when shutting down to boot an operating system + * + * @v booting System is shutting down for OS boot + */ +static void efi_watchdog_shutdown ( int booting ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + EFI_STATUS efirc; + int rc; + + /* If we are shutting down to boot an operating system, then + * disable the boot services watchdog timer. The UEFI + * specification mandates that the platform firmware does this + * as part of the ExitBootServices() call, but some platforms + * (e.g. Hyper-V) are observed to occasionally forget to do + * so, resulting in a reboot approximately five minutes after + * starting the operating system. + */ + if ( booting && + ( ( efirc = bs->SetWatchdogTimer ( 0, 0, 0, NULL ) ) != 0 ) ) { + rc = -EEFI ( efirc ); + DBGC ( &efi_watchdog, "EFI could not disable watchdog timer: " + "%s\n", strerror ( rc ) ); + /* Nothing we can do */ + } +} + +/** Watchdog startup/shutdown function */ +struct startup_fn efi_watchdog_startup_fn __startup_fn ( STARTUP_EARLY ) = { + .name = "efi_watchdog", + .shutdown = efi_watchdog_shutdown, +}; -- cgit v1.2.3-55-g7522 From f43c2fd69749bb9a44f2a3ab61b6735938432b52 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Tue, 4 Jan 2022 13:31:15 +0000 Subject: [settings] Support formatting UUIDs as little-endian GUIDs The RFC4122 specification defines UUIDs as being in network byte order, but an unfortunately significant amount of (mostly Microsoft) software treats them as having the first three fields in little-endian byte order. In an ideal world, any server-side software that compares UUIDs for equality would perform an endian-insensitive comparison (analogous to comparing strings for equality using a case-insensitive comparison), and would therefore not care about byte order differences. Define a setting type name ":guid" to allow a UUID setting to be formatted in little-endian order, to simplify interoperability with server-side software that expects such a formatting. Signed-off-by: Michael Brown --- src/core/settings.c | 23 ++++++++++++++++++----- src/include/ipxe/settings.h | 1 + src/interface/smbios/smbios_settings.c | 3 ++- src/tests/settings_test.c | 10 ++++++++++ 4 files changed, 31 insertions(+), 6 deletions(-) (limited to 'src/interface') diff --git a/src/core/settings.c b/src/core/settings.c index 430cdc84b..fcdf98d2b 100644 --- a/src/core/settings.c +++ b/src/core/settings.c @@ -2199,7 +2199,7 @@ const struct setting_type setting_type_base64 __setting_type = { }; /** - * Format UUID setting value + * Format UUID/GUID setting value * * @v type Setting type * @v raw Raw setting value @@ -2208,17 +2208,24 @@ const struct setting_type setting_type_base64 __setting_type = { * @v len Length of buffer * @ret len Length of formatted value, or negative error */ -static int format_uuid_setting ( const struct setting_type *type __unused, +static int format_uuid_setting ( const struct setting_type *type, const void *raw, size_t raw_len, char *buf, size_t len ) { - const union uuid *uuid = raw; + union uuid uuid; /* Range check */ - if ( raw_len != sizeof ( *uuid ) ) + if ( raw_len != sizeof ( uuid ) ) return -ERANGE; + /* Copy value */ + memcpy ( &uuid, raw, sizeof ( uuid ) ); + + /* Mangle GUID byte ordering */ + if ( type == &setting_type_guid ) + uuid_mangle ( &uuid ); + /* Format value */ - return snprintf ( buf, len, "%s", uuid_ntoa ( uuid ) ); + return snprintf ( buf, len, "%s", uuid_ntoa ( &uuid ) ); } /** UUID setting type */ @@ -2227,6 +2234,12 @@ const struct setting_type setting_type_uuid __setting_type = { .format = format_uuid_setting, }; +/** GUID setting type */ +const struct setting_type setting_type_guid __setting_type = { + .name = "guid", + .format = format_uuid_setting, +}; + /** * Format PCI bus:dev.fn setting value * diff --git a/src/include/ipxe/settings.h b/src/include/ipxe/settings.h index f463e6674..e042b9758 100644 --- a/src/include/ipxe/settings.h +++ b/src/include/ipxe/settings.h @@ -426,6 +426,7 @@ extern const struct setting_type setting_type_hexhyp __setting_type; extern const struct setting_type setting_type_hexraw __setting_type; extern const struct setting_type setting_type_base64 __setting_type; extern const struct setting_type setting_type_uuid __setting_type; +extern const struct setting_type setting_type_guid __setting_type; extern const struct setting_type setting_type_busdevfn __setting_type; extern const struct setting_type setting_type_dnssl __setting_type; diff --git a/src/interface/smbios/smbios_settings.c b/src/interface/smbios/smbios_settings.c index 2d571f2e4..ec31b43f2 100644 --- a/src/interface/smbios/smbios_settings.c +++ b/src/interface/smbios/smbios_settings.c @@ -140,7 +140,8 @@ static int smbios_fetch ( struct settings *settings __unused, * is 2.6 or higher; we match this behaviour. */ raw = &buf[tag_offset]; - if ( ( setting->type == &setting_type_uuid ) && + if ( ( ( setting->type == &setting_type_uuid ) || + ( setting->type == &setting_type_guid ) ) && ( tag_len == sizeof ( uuid ) ) && ( smbios_version() >= SMBIOS_VERSION ( 2, 6 ) ) ) { DBG ( "SMBIOS detected mangled UUID\n" ); diff --git a/src/tests/settings_test.c b/src/tests/settings_test.c index 828901b06..5da7eb008 100644 --- a/src/tests/settings_test.c +++ b/src/tests/settings_test.c @@ -250,6 +250,12 @@ static struct setting test_uuid_setting = { .type = &setting_type_uuid, }; +/** Test GUID setting type */ +static struct setting test_guid_setting = { + .name = "test_guid", + .type = &setting_type_guid, +}; + /** Test PCI bus:dev.fn setting type */ static struct setting test_busdevfn_setting = { .name = "test_busdevfn", @@ -419,6 +425,10 @@ static void settings_test_exec ( void ) { RAW ( 0x1a, 0x6a, 0x74, 0x9d, 0x0e, 0xda, 0x46, 0x1a,0xa8, 0x7a, 0x7c, 0xfe, 0x4f, 0xca, 0x4a, 0x57 ), "1a6a749d-0eda-461a-a87a-7cfe4fca4a57" ); + fetchf_ok ( &test_settings, &test_guid_setting, + RAW ( 0x1a, 0x6a, 0x74, 0x9d, 0x0e, 0xda, 0x46, 0x1a,0xa8, + 0x7a, 0x7c, 0xfe, 0x4f, 0xca, 0x4a, 0x57 ), + "9d746a1a-da0e-1a46-a87a-7cfe4fca4a57" ); /* "busdevfn" setting type (no store capability) */ fetchf_ok ( &test_settings, &test_busdevfn_setting, -- cgit v1.2.3-55-g7522 From ec746c00012317ba228d7043fa141d5e6a48c17c Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Mon, 17 Jan 2022 16:11:56 +0000 Subject: [efi] Allow for autoexec scripts that are not located in a filesystem Signed-off-by: Michael Brown --- src/interface/efi/efi_autoexec.c | 62 ++++++++++++++++++++++++++-------------- 1 file changed, 41 insertions(+), 21 deletions(-) (limited to 'src/interface') diff --git a/src/interface/efi/efi_autoexec.c b/src/interface/efi/efi_autoexec.c index 88eb379bb..881c30c7e 100644 --- a/src/interface/efi/efi_autoexec.c +++ b/src/interface/efi/efi_autoexec.c @@ -39,10 +39,10 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); */ /** Autoexec script filename */ -#define AUTOEXEC_FILENAME L"autoexec.ipxe" +static wchar_t efi_autoexec_wname[] = L"autoexec.ipxe"; /** Autoexec script image name */ -#define AUTOEXEC_NAME "autoexec.ipxe" +static char efi_autoexec_name[] = "autoexec.ipxe"; /** Autoexec script (if any) */ static void *efi_autoexec; @@ -51,21 +51,21 @@ static void *efi_autoexec; static size_t efi_autoexec_len; /** - * Load autoexec script + * Load autoexec script from filesystem * * @v device Device handle * @ret rc Return status code */ -int efi_autoexec_load ( EFI_HANDLE device ) { +static int efi_autoexec_filesystem ( EFI_HANDLE device ) { EFI_BOOT_SERVICES *bs = efi_systab->BootServices; - static wchar_t name[] = AUTOEXEC_FILENAME; union { void *interface; EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *fs; } u; struct { EFI_FILE_INFO info; - CHAR16 name[ sizeof ( name ) / sizeof ( name[0] ) ]; + CHAR16 name[ sizeof ( efi_autoexec_wname ) / + sizeof ( efi_autoexec_wname[0] ) ]; } info; EFI_FILE_PROTOCOL *root; EFI_FILE_PROTOCOL *file; @@ -74,10 +74,6 @@ int efi_autoexec_load ( EFI_HANDLE device ) { EFI_STATUS efirc; int rc; - /* Sanity check */ - assert ( efi_autoexec == NULL ); - assert ( efi_autoexec_len == 0 ); - /* Open simple file system protocol */ if ( ( efirc = bs->OpenProtocol ( device, &efi_simple_file_system_protocol_guid, @@ -99,11 +95,12 @@ int efi_autoexec_load ( EFI_HANDLE device ) { } /* Open autoexec script */ - if ( ( efirc = root->Open ( root, &file, name, + if ( ( efirc = root->Open ( root, &file, efi_autoexec_wname, EFI_FILE_MODE_READ, 0 ) ) != 0 ) { rc = -EEFI ( efirc ); DBGC ( device, "EFI %s has no %ls: %s\n", - efi_handle_name ( device ), name, strerror ( rc ) ); + efi_handle_name ( device ), efi_autoexec_wname, + strerror ( rc ) ); goto err_open; } @@ -113,7 +110,8 @@ int efi_autoexec_load ( EFI_HANDLE device ) { &info ) ) != 0 ) { rc = -EEFI ( efirc ); DBGC ( device, "EFI %s could not get %ls info: %s\n", - efi_handle_name ( device ), name, strerror ( rc ) ); + efi_handle_name ( device ), efi_autoexec_wname, + strerror ( rc ) ); goto err_getinfo; } size = info.info.FileSize; @@ -122,7 +120,7 @@ int efi_autoexec_load ( EFI_HANDLE device ) { if ( ! size ) { rc = -EINVAL; DBGC ( device, "EFI %s has zero-length %ls\n", - efi_handle_name ( device ), name ); + efi_handle_name ( device ), efi_autoexec_wname ); goto err_empty; } @@ -131,7 +129,8 @@ int efi_autoexec_load ( EFI_HANDLE device ) { &data ) ) != 0 ) { rc = -EEFI ( efirc ); DBGC ( device, "EFI %s could not allocate %ls: %s\n", - efi_handle_name ( device ), name, strerror ( rc ) ); + efi_handle_name ( device ), efi_autoexec_wname, + strerror ( rc ) ); goto err_alloc; } @@ -139,7 +138,8 @@ int efi_autoexec_load ( EFI_HANDLE device ) { if ( ( efirc = file->Read ( file, &size, data ) ) != 0 ) { rc = -EEFI ( efirc ); DBGC ( device, "EFI %s could not read %ls: %s\n", - efi_handle_name ( device ), name, strerror ( rc ) ); + efi_handle_name ( device ), efi_autoexec_wname, + strerror ( rc ) ); goto err_read; } @@ -148,7 +148,7 @@ int efi_autoexec_load ( EFI_HANDLE device ) { efi_autoexec_len = size; data = NULL; DBGC ( device, "EFI %s found %ls\n", - efi_handle_name ( device ), name ); + efi_handle_name ( device ), efi_autoexec_wname ); /* Success */ rc = 0; @@ -169,6 +169,26 @@ int efi_autoexec_load ( EFI_HANDLE device ) { return rc; } +/** + * Load autoexec script + * + * @v device Device handle + * @ret rc Return status code + */ +int efi_autoexec_load ( EFI_HANDLE device ) { + int rc; + + /* Sanity check */ + assert ( efi_autoexec == NULL ); + assert ( efi_autoexec_len == 0 ); + + /* Try loading from file system, if supported */ + if ( ( rc = efi_autoexec_filesystem ( device ) ) == 0 ) + return 0; + + return -ENOENT; +} + /** * Register autoexec script * @@ -176,7 +196,6 @@ int efi_autoexec_load ( EFI_HANDLE device ) { static void efi_autoexec_startup ( void ) { EFI_BOOT_SERVICES *bs = efi_systab->BootServices; EFI_HANDLE device = efi_loaded_image->DeviceHandle; - const char *name = AUTOEXEC_NAME; struct image *image; /* Do nothing if we have no autoexec script */ @@ -184,15 +203,16 @@ static void efi_autoexec_startup ( void ) { return; /* Create autoexec image */ - image = image_memory ( name, virt_to_user ( efi_autoexec ), + image = image_memory ( efi_autoexec_name, + virt_to_user ( efi_autoexec ), efi_autoexec_len ); if ( ! image ) { DBGC ( device, "EFI %s could not create %s\n", - efi_handle_name ( device ), name ); + efi_handle_name ( device ), efi_autoexec_name ); return; } DBGC ( device, "EFI %s registered %s\n", - efi_handle_name ( device ), name ); + efi_handle_name ( device ), efi_autoexec_name ); /* Free temporary copy */ bs->FreePool ( efi_autoexec ); -- cgit v1.2.3-55-g7522 From 6ba671acd922ee046b257c5119b8a0f64d275473 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Mon, 17 Jan 2022 16:17:17 +0000 Subject: [efi] Attempt to fetch autoexec script via TFTP Attempt to fetch the autoexec.ipxe script via TFTP using the PXE base code protocol installed on the loaded image's device handle, if present. This provides a generic alternative to the use of an embedded script for chainloaded binaries, which is particularly useful in a UEFI Secure Boot environment since it allows the script to be modified without the need to sign a new binary. As a side effect, this also provides a third method for breaking the PXE chainloading loop (as an alternative to requiring an embedded script or custom DHCP server configuration). Signed-off-by: Michael Brown --- src/interface/efi/efi_autoexec.c | 176 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 176 insertions(+) (limited to 'src/interface') diff --git a/src/interface/efi/efi_autoexec.c b/src/interface/efi/efi_autoexec.c index 881c30c7e..79d4a4caf 100644 --- a/src/interface/efi/efi_autoexec.c +++ b/src/interface/efi/efi_autoexec.c @@ -24,11 +24,14 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include +#include #include #include #include +#include #include #include +#include #include #include @@ -169,6 +172,175 @@ static int efi_autoexec_filesystem ( EFI_HANDLE device ) { return rc; } +/** + * Load autoexec script from TFTP server + * + * @v device Device handle + * @ret rc Return status code + */ +static int efi_autoexec_tftp ( EFI_HANDLE device ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + union { + void *interface; + EFI_PXE_BASE_CODE_PROTOCOL *pxe; + } u; + EFI_PXE_BASE_CODE_MODE *mode; + EFI_PXE_BASE_CODE_PACKET *packet; + union { + struct in_addr in; + EFI_IP_ADDRESS ip; + } server; + size_t filename_max; + char *filename; + char *sep; + UINT64 size; + VOID *data; + EFI_STATUS efirc; + int rc; + + /* Open PXE base code protocol */ + if ( ( efirc = bs->OpenProtocol ( device, + &efi_pxe_base_code_protocol_guid, + &u.interface, efi_image_handle, + device, + EFI_OPEN_PROTOCOL_GET_PROTOCOL ))!=0){ + rc = -EEFI ( efirc ); + DBGC ( device, "EFI %s has no PXE base code instance: %s\n", + efi_handle_name ( device ), strerror ( rc ) ); + goto err_pxe; + } + + /* Do not attempt to parse DHCPv6 packets */ + mode = u.pxe->Mode; + if ( mode->UsingIpv6 ) { + rc = -ENOTSUP; + DBGC ( device, "EFI %s has IPv6 PXE base code\n", + efi_handle_name ( device ) ); + goto err_ipv6; + } + + /* Identify relevant reply packet */ + if ( mode->PxeReplyReceived && + mode->PxeReply.Dhcpv4.BootpBootFile[0] ) { + /* Use boot filename if present in PXE reply */ + DBGC ( device, "EFI %s using PXE reply filename\n", + efi_handle_name ( device ) ); + packet = &mode->PxeReply; + } else if ( mode->DhcpAckReceived && + mode->DhcpAck.Dhcpv4.BootpBootFile[0] ) { + /* Otherwise, use boot filename if present in DHCPACK */ + DBGC ( device, "EFI %s using DHCPACK filename\n", + efi_handle_name ( device ) ); + packet = &mode->DhcpAck; + } else if ( mode->ProxyOfferReceived && + mode->ProxyOffer.Dhcpv4.BootpBootFile[0] ) { + /* Otherwise, use boot filename if present in ProxyDHCPOFFER */ + DBGC ( device, "EFI %s using ProxyDHCPOFFER filename\n", + efi_handle_name ( device ) ); + packet = &mode->ProxyOffer; + } else { + /* No boot filename available */ + rc = -ENOENT; + DBGC ( device, "EFI %s has no PXE boot filename\n", + efi_handle_name ( device ) ); + goto err_packet; + } + + /* Allocate filename */ + filename_max = ( sizeof ( packet->Dhcpv4.BootpBootFile ) + + ( sizeof ( efi_autoexec_name ) - 1 /* NUL */ ) + + 1 /* NUL */ ); + filename = zalloc ( filename_max ); + if ( ! filename ) { + rc = -ENOMEM; + goto err_filename; + } + + /* Extract next-server address and boot filename */ + memset ( &server, 0, sizeof ( server ) ); + memcpy ( &server.in, packet->Dhcpv4.BootpSiAddr, + sizeof ( server.in ) ); + memcpy ( filename, packet->Dhcpv4.BootpBootFile, + sizeof ( packet->Dhcpv4.BootpBootFile ) ); + + /* Update filename to autoexec script name */ + sep = strrchr ( filename, '/' ); + if ( ! sep ) + sep = strrchr ( filename, '\\' ); + if ( ! sep ) + sep = ( filename - 1 ); + strcpy ( ( sep + 1 ), efi_autoexec_name ); + + /* Get file size */ + if ( ( efirc = u.pxe->Mtftp ( u.pxe, + EFI_PXE_BASE_CODE_TFTP_GET_FILE_SIZE, + NULL, FALSE, &size, NULL, &server.ip, + ( ( UINT8 * ) filename ), NULL, + FALSE ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( device, "EFI %s could not get size of %s:%s: %s\n", + efi_handle_name ( device ), inet_ntoa ( server.in ), + filename, strerror ( rc ) ); + goto err_size; + } + + /* Ignore zero-length files */ + if ( ! size ) { + rc = -EINVAL; + DBGC ( device, "EFI %s has zero-length %s:%s\n", + efi_handle_name ( device ), inet_ntoa ( server.in ), + filename ); + goto err_empty; + } + + /* Allocate temporary copy */ + if ( ( efirc = bs->AllocatePool ( EfiBootServicesData, size, + &data ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( device, "EFI %s could not allocate %s:%s: %s\n", + efi_handle_name ( device ), inet_ntoa ( server.in ), + filename, strerror ( rc ) ); + goto err_alloc; + } + + /* Download file */ + if ( ( efirc = u.pxe->Mtftp ( u.pxe, EFI_PXE_BASE_CODE_TFTP_READ_FILE, + data, FALSE, &size, NULL, &server.ip, + ( ( UINT8 * ) filename ), NULL, + FALSE ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( device, "EFI %s could not download %s:%s: %s\n", + efi_handle_name ( device ), inet_ntoa ( server.in ), + filename, strerror ( rc ) ); + goto err_download; + } + + /* Record autoexec script */ + efi_autoexec = data; + efi_autoexec_len = size; + data = NULL; + DBGC ( device, "EFI %s found %s:%s\n", efi_handle_name ( device ), + inet_ntoa ( server.in ), filename ); + + /* Success */ + rc = 0; + + err_download: + if ( data ) + bs->FreePool ( data ); + err_alloc: + err_empty: + err_size: + free ( filename ); + err_filename: + err_packet: + err_ipv6: + bs->CloseProtocol ( device, &efi_pxe_base_code_protocol_guid, + efi_image_handle, device ); + err_pxe: + return rc; +} + /** * Load autoexec script * @@ -186,6 +358,10 @@ int efi_autoexec_load ( EFI_HANDLE device ) { if ( ( rc = efi_autoexec_filesystem ( device ) ) == 0 ) return 0; + /* Try loading via TFTP, if supported */ + if ( ( rc = efi_autoexec_tftp ( device ) ) == 0 ) + return 0; + return -ENOENT; } -- cgit v1.2.3-55-g7522 From 0979b3a11ddd642b047c7e9240cefc0144c595c7 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Thu, 10 Feb 2022 12:14:46 +0000 Subject: [efi] Support keyboard remapping via the EFI console Signed-off-by: Michael Brown --- src/interface/efi/efi_console.c | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'src/interface') diff --git a/src/interface/efi/efi_console.c b/src/interface/efi/efi_console.c index 98ebbf3ac..874f54b6c 100644 --- a/src/interface/efi/efi_console.c +++ b/src/interface/efi/efi_console.c @@ -27,6 +27,7 @@ FILE_LICENCE ( GPL2_OR_LATER ); #include #include #include +#include #include #include @@ -317,6 +318,13 @@ static int efi_getchar ( void ) { key.KeyState.KeyToggleState, key.Key.UnicodeChar, key.Key.ScanCode ); + /* Remap key. There is unfortunately no way to avoid + * remapping the numeric keypad, since EFI destroys the scan + * code information that would allow us to differentiate + * between main keyboard and numeric keypad. + */ + key.Key.UnicodeChar = key_remap ( key.Key.UnicodeChar ); + /* Translate Ctrl- */ if ( ( key.KeyState.KeyShiftState & EFI_SHIFT_STATE_VALID ) && ( key.KeyState.KeyShiftState & ( EFI_LEFT_CONTROL_PRESSED | -- cgit v1.2.3-55-g7522 From f2a59d5973da2041f93264609698b9b3f4ec101b Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Mon, 14 Feb 2022 16:31:08 +0000 Subject: [console] Centralise handling of key modifiers Handle Ctrl and CapsLock key modifiers within key_remap(), to provide consistent behaviour across different console types. Signed-off-by: Michael Brown --- src/arch/x86/include/bios.h | 3 ++ src/arch/x86/interface/pcbios/bios_console.c | 23 +++++++++++---- src/core/keymap.c | 41 ++++++++++++++++++++++---- src/drivers/usb/usbkbd.c | 21 ++++++------- src/include/ipxe/keymap.h | 21 +++++++++++++ src/interface/efi/efi_console.c | 44 ++++++++++++++++++---------- 6 files changed, 116 insertions(+), 37 deletions(-) (limited to 'src/interface') diff --git a/src/arch/x86/include/bios.h b/src/arch/x86/include/bios.h index 14e7acbc7..3ba8264ec 100644 --- a/src/arch/x86/include/bios.h +++ b/src/arch/x86/include/bios.h @@ -6,6 +6,9 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define BDA_SEG 0x0040 #define BDA_EBDA 0x000e #define BDA_EQUIPMENT_WORD 0x0010 +#define BDA_KB0 0x0017 +#define BDA_KB0_CTRL 0x04 +#define BDA_KB0_CAPSLOCK 0x040 #define BDA_FBMS 0x0013 #define BDA_TICKS 0x006c #define BDA_MIDNIGHT 0x0070 diff --git a/src/arch/x86/interface/pcbios/bios_console.c b/src/arch/x86/interface/pcbios/bios_console.c index 438a01d07..2664ac8a5 100644 --- a/src/arch/x86/interface/pcbios/bios_console.c +++ b/src/arch/x86/interface/pcbios/bios_console.c @@ -361,6 +361,7 @@ static const char * bios_ansi_seq ( unsigned int scancode ) { */ static int bios_getchar ( void ) { uint16_t keypress; + uint8_t kb0; unsigned int scancode; unsigned int character; const char *ansi_seq; @@ -385,16 +386,28 @@ static int bios_getchar ( void ) { bios_inject_lock--; scancode = ( keypress >> 8 ); character = ( keypress & 0xff ); + get_real ( kb0, BDA_SEG, BDA_KB0 ); /* If it's a normal character, map (if applicable) and return it */ if ( character && ( character < 0x80 ) ) { - if ( scancode < SCANCODE_RSHIFT ) { - return key_remap ( character ); - } else if ( scancode == SCANCODE_NON_US ) { - return key_remap ( character | KEYMAP_PSEUDO ); - } else { + + /* Handle special scancodes */ + if ( scancode == SCANCODE_NON_US ) { + /* Treat as "\|" with high bit set */ + character |= KEYMAP_PSEUDO; + } else if ( scancode >= SCANCODE_RSHIFT ) { + /* Non-remappable scancode (e.g. numeric keypad) */ return character; } + + /* Apply modifiers */ + if ( kb0 & BDA_KB0_CTRL ) + character |= KEYMAP_CTRL; + if ( kb0 & BDA_KB0_CAPSLOCK ) + character |= KEYMAP_CAPSLOCK_REDO; + + /* Map and return */ + return key_remap ( character ); } /* Otherwise, check for a special key that we know about */ diff --git a/src/core/keymap.c b/src/core/keymap.c index c0953967a..a5209bc20 100644 --- a/src/core/keymap.c +++ b/src/core/keymap.c @@ -23,6 +23,8 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); +#include +#include #include /** @file @@ -31,6 +33,18 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); * */ +/** ASCII character mask */ +#define ASCII_MASK 0x7f + +/** Control character mask */ +#define CTRL_MASK 0x1f + +/** Upper case character mask */ +#define UPPER_MASK 0x5f + +/** Case toggle bit */ +#define CASE_TOGGLE ( ASCII_MASK & ~UPPER_MASK ) + /** Default keyboard mapping */ static TABLE_START ( keymap_start, KEYMAP ); @@ -41,21 +55,36 @@ static struct keymap *keymap = keymap_start; * Remap a key * * @v character Character read from console - * @ret character Mapped character + * @ret mapped Mapped character */ unsigned int key_remap ( unsigned int character ) { + unsigned int mapped = ( character & KEYMAP_MASK ); struct keymap_key *key; + /* Invert case before remapping if applicable */ + if ( ( character & KEYMAP_CAPSLOCK_UNDO ) && isalpha ( mapped ) ) + mapped ^= CASE_TOGGLE; + /* Remap via table */ for ( key = keymap->basic ; key->from ; key++ ) { - if ( key->from == character ) { - character = key->to; + if ( mapped == key->from ) { + mapped = key->to; break; } } - /* Clear pseudo key flag */ - character &= ~KEYMAP_PSEUDO; + /* Handle Ctrl- and CapsLock */ + if ( isalpha ( mapped ) ) { + if ( character & KEYMAP_CTRL ) { + mapped &= CTRL_MASK; + } else if ( character & KEYMAP_CAPSLOCK ) { + mapped ^= CASE_TOGGLE; + } + } + + /* Clear flags */ + mapped &= ASCII_MASK; - return character; + DBGC2 ( &keymap, "KEYMAP mapped %04x => %02x\n", character, mapped ); + return mapped; } diff --git a/src/drivers/usb/usbkbd.c b/src/drivers/usb/usbkbd.c index 6954cd69b..516667b25 100644 --- a/src/drivers/usb/usbkbd.c +++ b/src/drivers/usb/usbkbd.c @@ -71,6 +71,9 @@ static unsigned int usbkbd_map ( unsigned int keycode, unsigned int modifiers, } else if ( keycode <= USBKBD_KEY_Z ) { /* Alphabetic keys */ key = ( keycode - USBKBD_KEY_A + 'a' ); + if ( modifiers & USBKBD_SHIFT ) { + key -= ( 'a' - 'A' ); + } } else if ( keycode <= USBKBD_KEY_0 ) { /* Numeric key row */ if ( modifiers & USBKBD_SHIFT ) { @@ -125,17 +128,15 @@ static unsigned int usbkbd_map ( unsigned int keycode, unsigned int modifiers, /* Remap key if applicable */ if ( ( keycode < USBKBD_KEY_CAPS_LOCK ) || ( keycode == USBKBD_KEY_NON_US ) ) { - key = key_remap ( key ); - } - /* Handle upper/lower case and Ctrl- */ - if ( islower ( key ) ) { - if ( modifiers & USBKBD_CTRL ) { - key -= ( 'a' - CTRL_A ); - } else if ( ( modifiers & USBKBD_SHIFT ) || - ( leds & USBKBD_LED_CAPS_LOCK ) ) { - key -= ( 'a' - 'A' ); - } + /* Apply modifiers */ + if ( modifiers & USBKBD_CTRL ) + key |= KEYMAP_CTRL; + if ( leds & USBKBD_LED_CAPS_LOCK ) + key |= KEYMAP_CAPSLOCK; + + /* Remap key */ + key = key_remap ( key ); } return key; diff --git a/src/include/ipxe/keymap.h b/src/include/ipxe/keymap.h index a64ab9cd4..3da25190b 100644 --- a/src/include/ipxe/keymap.h +++ b/src/include/ipxe/keymap.h @@ -40,9 +40,30 @@ struct keymap { /** Define a keyboard mapping */ #define __keymap __table_entry ( KEYMAP, 01 ) +/** Mappable character mask */ +#define KEYMAP_MASK 0xff + /** Pseudo key flag */ #define KEYMAP_PSEUDO 0x80 +/** Ctrl key flag */ +#define KEYMAP_CTRL 0x0100 + +/** CapsLock key flag */ +#define KEYMAP_CAPSLOCK 0x0200 + +/** Undo CapsLock key flag + * + * Used when the keyboard driver has already interpreted the CapsLock + * key, in which case the effect needs to be undone before remapping + * in order to correctly handle keyboard mappings that swap alphabetic + * and non-alphabetic keys. + */ +#define KEYMAP_CAPSLOCK_UNDO 0x0400 + +/** Undo and redo CapsLock key flags */ +#define KEYMAP_CAPSLOCK_REDO ( KEYMAP_CAPSLOCK | KEYMAP_CAPSLOCK_UNDO ) + extern unsigned int key_remap ( unsigned int character ); #endif /* _IPXE_KEYMAP_H */ diff --git a/src/interface/efi/efi_console.c b/src/interface/efi/efi_console.c index 874f54b6c..9adce4a9b 100644 --- a/src/interface/efi/efi_console.c +++ b/src/interface/efi/efi_console.c @@ -55,8 +55,6 @@ FILE_LICENCE ( GPL2_OR_LATER ); #define ATTR_DEFAULT ATTR_FCOL_WHITE -#define CTRL_MASK 0x1f - /* Set default console usage if applicable */ #if ! ( defined ( CONSOLE_EFI ) && CONSOLE_EXPLICIT ( CONSOLE_EFI ) ) #undef CONSOLE_EFI @@ -286,6 +284,9 @@ static int efi_getchar ( void ) { EFI_SIMPLE_TEXT_INPUT_PROTOCOL *conin = efi_systab->ConIn; EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *conin_ex = efi_conin_ex; const char *ansi_seq; + unsigned int character; + unsigned int shift; + unsigned int toggle; EFI_KEY_DATA key; EFI_STATUS efirc; int rc; @@ -318,23 +319,34 @@ static int efi_getchar ( void ) { key.KeyState.KeyToggleState, key.Key.UnicodeChar, key.Key.ScanCode ); - /* Remap key. There is unfortunately no way to avoid - * remapping the numeric keypad, since EFI destroys the scan - * code information that would allow us to differentiate - * between main keyboard and numeric keypad. + /* If key has a Unicode representation, remap and return it. + * There is unfortunately no way to avoid remapping the + * numeric keypad, since EFI destroys the scan code + * information that would allow us to differentiate between + * main keyboard and numeric keypad. */ - key.Key.UnicodeChar = key_remap ( key.Key.UnicodeChar ); + if ( ( character = key.Key.UnicodeChar ) != 0 ) { + + /* Apply shift state */ + shift = key.KeyState.KeyShiftState; + if ( shift & EFI_SHIFT_STATE_VALID ) { + if ( shift & ( EFI_LEFT_CONTROL_PRESSED | + EFI_RIGHT_CONTROL_PRESSED ) ) { + character |= KEYMAP_CTRL; + } + } - /* Translate Ctrl- */ - if ( ( key.KeyState.KeyShiftState & EFI_SHIFT_STATE_VALID ) && - ( key.KeyState.KeyShiftState & ( EFI_LEFT_CONTROL_PRESSED | - EFI_RIGHT_CONTROL_PRESSED ) ) ) { - key.Key.UnicodeChar &= CTRL_MASK; - } + /* Apply toggle state */ + toggle = key.KeyState.KeyToggleState; + if ( toggle & EFI_TOGGLE_STATE_VALID ) { + if ( toggle & EFI_CAPS_LOCK_ACTIVE ) { + character |= KEYMAP_CAPSLOCK_REDO; + } + } - /* If key has a Unicode representation, return it */ - if ( key.Key.UnicodeChar ) - return key.Key.UnicodeChar; + /* Remap and return key */ + return key_remap ( character ); + } /* Otherwise, check for a special key that we know about */ if ( ( ansi_seq = scancode_to_ansi_seq ( key.Key.ScanCode ) ) ) { -- cgit v1.2.3-55-g7522 From e1cedbc0d4fdb0e16818f6b722f4873a50780761 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Mon, 14 Feb 2022 13:45:59 +0000 Subject: [console] Support AltGr to access ASCII characters via remapping Several keyboard layouts define ASCII characters as accessible only via the AltGr modifier. Add support for this modifier to ensure that all ASCII characters are accessible. Experiments suggest that the BIOS console is likely to fail to generate ASCII characters when the AltGr key is pressed. Work around this limitation by accepting LShift+RShift (which will definitely produce an ASCII character) as a synonym for AltGr. Signed-off-by: Michael Brown --- src/arch/x86/include/bios.h | 4 +++ src/arch/x86/interface/pcbios/bios_console.c | 12 +++++++++ src/core/keymap.c | 5 +++- src/drivers/usb/usbkbd.c | 2 ++ src/hci/keymap/keymap_al.c | 8 ++++++ src/hci/keymap/keymap_az.c | 7 +++++ src/hci/keymap/keymap_by.c | 6 +++++ src/hci/keymap/keymap_cf.c | 9 +++++++ src/hci/keymap/keymap_cz.c | 26 ++++++++++++++++++ src/hci/keymap/keymap_de.c | 11 ++++++++ src/hci/keymap/keymap_dk.c | 9 +++++++ src/hci/keymap/keymap_es.c | 11 ++++++++ src/hci/keymap/keymap_et.c | 10 +++++++ src/hci/keymap/keymap_fi.c | 9 +++++++ src/hci/keymap/keymap_fr.c | 12 +++++++++ src/hci/keymap/keymap_gr.c | 6 +++++ src/hci/keymap/keymap_hu.c | 17 ++++++++++++ src/hci/keymap/keymap_il.c | 6 +++++ src/hci/keymap/keymap_it.c | 12 +++++++++ src/hci/keymap/keymap_lt.c | 6 +++++ src/hci/keymap/keymap_mk.c | 6 +++++ src/hci/keymap/keymap_mt.c | 7 +++++ src/hci/keymap/keymap_nl.c | 8 ++++++ src/hci/keymap/keymap_no-latin1.c | 10 +++++++ src/hci/keymap/keymap_no.c | 8 ++++++ src/hci/keymap/keymap_pl.c | 6 +++++ src/hci/keymap/keymap_pt.c | 10 +++++++ src/hci/keymap/keymap_ro.c | 6 +++++ src/hci/keymap/keymap_ru.c | 6 +++++ src/hci/keymap/keymap_sg.c | 10 +++++++ src/hci/keymap/keymap_sr-latin.c | 6 +++++ src/hci/keymap/keymap_ua.c | 6 +++++ src/hci/keymap/keymap_uk.c | 6 +++++ src/hci/keymap/keymap_us.c | 6 +++++ src/include/ipxe/keymap.h | 5 ++++ src/interface/efi/efi_console.c | 3 +++ src/util/genkeymap.py | 40 +++++++++++++++++++++++++--- 37 files changed, 332 insertions(+), 5 deletions(-) (limited to 'src/interface') diff --git a/src/arch/x86/include/bios.h b/src/arch/x86/include/bios.h index 3ba8264ec..6391a4958 100644 --- a/src/arch/x86/include/bios.h +++ b/src/arch/x86/include/bios.h @@ -7,6 +7,8 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define BDA_EBDA 0x000e #define BDA_EQUIPMENT_WORD 0x0010 #define BDA_KB0 0x0017 +#define BDA_KB0_RSHIFT 0x01 +#define BDA_KB0_LSHIFT 0x02 #define BDA_KB0_CTRL 0x04 #define BDA_KB0_CAPSLOCK 0x040 #define BDA_FBMS 0x0013 @@ -16,5 +18,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define BDA_REBOOT_WARM 0x1234 #define BDA_NUM_DRIVES 0x0075 #define BDA_CHAR_HEIGHT 0x0085 +#define BDA_KB2 0x0096 +#define BDA_KB2_RALT 0x08 #endif /* BIOS_H */ diff --git a/src/arch/x86/interface/pcbios/bios_console.c b/src/arch/x86/interface/pcbios/bios_console.c index 2664ac8a5..0220c8564 100644 --- a/src/arch/x86/interface/pcbios/bios_console.c +++ b/src/arch/x86/interface/pcbios/bios_console.c @@ -362,6 +362,7 @@ static const char * bios_ansi_seq ( unsigned int scancode ) { static int bios_getchar ( void ) { uint16_t keypress; uint8_t kb0; + uint8_t kb2; unsigned int scancode; unsigned int character; const char *ansi_seq; @@ -387,6 +388,7 @@ static int bios_getchar ( void ) { scancode = ( keypress >> 8 ); character = ( keypress & 0xff ); get_real ( kb0, BDA_SEG, BDA_KB0 ); + get_real ( kb2, BDA_SEG, BDA_KB2 ); /* If it's a normal character, map (if applicable) and return it */ if ( character && ( character < 0x80 ) ) { @@ -405,6 +407,16 @@ static int bios_getchar ( void ) { character |= KEYMAP_CTRL; if ( kb0 & BDA_KB0_CAPSLOCK ) character |= KEYMAP_CAPSLOCK_REDO; + if ( kb2 & BDA_KB2_RALT ) + character |= KEYMAP_ALTGR; + + /* Treat LShift+RShift as AltGr since many BIOSes will + * not return ASCII characters when AltGr is pressed. + */ + if ( ( kb0 & ( BDA_KB0_LSHIFT | BDA_KB0_RSHIFT ) ) == + ( BDA_KB0_LSHIFT | BDA_KB0_RSHIFT ) ) { + character |= KEYMAP_ALTGR; + } /* Map and return */ return key_remap ( character ); diff --git a/src/core/keymap.c b/src/core/keymap.c index a5209bc20..3fa85f74e 100644 --- a/src/core/keymap.c +++ b/src/core/keymap.c @@ -65,8 +65,11 @@ unsigned int key_remap ( unsigned int character ) { if ( ( character & KEYMAP_CAPSLOCK_UNDO ) && isalpha ( mapped ) ) mapped ^= CASE_TOGGLE; + /* Select remapping table */ + key = ( ( character & KEYMAP_ALTGR ) ? keymap->altgr : keymap->basic ); + /* Remap via table */ - for ( key = keymap->basic ; key->from ; key++ ) { + for ( ; key->from ; key++ ) { if ( mapped == key->from ) { mapped = key->to; break; diff --git a/src/drivers/usb/usbkbd.c b/src/drivers/usb/usbkbd.c index 516667b25..b284e584f 100644 --- a/src/drivers/usb/usbkbd.c +++ b/src/drivers/usb/usbkbd.c @@ -132,6 +132,8 @@ static unsigned int usbkbd_map ( unsigned int keycode, unsigned int modifiers, /* Apply modifiers */ if ( modifiers & USBKBD_CTRL ) key |= KEYMAP_CTRL; + if ( modifiers & USBKBD_ALT_RIGHT ) + key |= KEYMAP_ALTGR; if ( leds & USBKBD_LED_CAPS_LOCK ) key |= KEYMAP_CAPSLOCK; diff --git a/src/hci/keymap/keymap_al.c b/src/hci/keymap/keymap_al.c index a3df385a9..b68b98878 100644 --- a/src/hci/keymap/keymap_al.c +++ b/src/hci/keymap/keymap_al.c @@ -35,8 +35,16 @@ static struct keymap_key al_basic[] = { { 0, 0 } }; +/** "al" AltGr remapping */ +static struct keymap_key al_altgr[] = { + { 0x31, 0x7e }, /* '1' => '~' */ + { 0x37, 0x60 }, /* '7' => '`' */ + { 0, 0 } +}; + /** "al" keyboard map */ struct keymap al_keymap __keymap = { .name = "al", .basic = al_basic, + .altgr = al_altgr, }; diff --git a/src/hci/keymap/keymap_az.c b/src/hci/keymap/keymap_az.c index 7b382ca8b..03087e01e 100644 --- a/src/hci/keymap/keymap_az.c +++ b/src/hci/keymap/keymap_az.c @@ -26,8 +26,15 @@ static struct keymap_key az_basic[] = { { 0, 0 } }; +/** "az" AltGr remapping */ +static struct keymap_key az_altgr[] = { + { 0xdc, 0x7c }, /* Pseudo-'\\' => '|' */ + { 0, 0 } +}; + /** "az" keyboard map */ struct keymap az_keymap __keymap = { .name = "az", .basic = az_basic, + .altgr = az_altgr, }; diff --git a/src/hci/keymap/keymap_by.c b/src/hci/keymap/keymap_by.c index 4127609e3..9af6c966d 100644 --- a/src/hci/keymap/keymap_by.c +++ b/src/hci/keymap/keymap_by.c @@ -17,8 +17,14 @@ static struct keymap_key by_basic[] = { { 0, 0 } }; +/** "by" AltGr remapping */ +static struct keymap_key by_altgr[] = { + { 0, 0 } +}; + /** "by" keyboard map */ struct keymap by_keymap __keymap = { .name = "by", .basic = by_basic, + .altgr = by_altgr, }; diff --git a/src/hci/keymap/keymap_cf.c b/src/hci/keymap/keymap_cf.c index 0bbe89659..09242ee6f 100644 --- a/src/hci/keymap/keymap_cf.c +++ b/src/hci/keymap/keymap_cf.c @@ -24,8 +24,17 @@ static struct keymap_key cf_basic[] = { { 0, 0 } }; +/** "cf" AltGr remapping */ +static struct keymap_key cf_altgr[] = { + { 0x32, 0x40 }, /* '2' => '@' */ + { 0x3b, 0x7e }, /* ';' => '~' */ + { 0x60, 0x5c }, /* '`' => '\\' */ + { 0, 0 } +}; + /** "cf" keyboard map */ struct keymap cf_keymap __keymap = { .name = "cf", .basic = cf_basic, + .altgr = cf_altgr, }; diff --git a/src/hci/keymap/keymap_cz.c b/src/hci/keymap/keymap_cz.c index 8655d5b68..cce686d9a 100644 --- a/src/hci/keymap/keymap_cz.c +++ b/src/hci/keymap/keymap_cz.c @@ -46,8 +46,34 @@ static struct keymap_key cz_basic[] = { { 0, 0 } }; +/** "cz" AltGr remapping */ +static struct keymap_key cz_altgr[] = { + { 0x2c, 0x3c }, /* ',' => '<' */ + { 0x2e, 0x3e }, /* '.' => '>' */ + { 0x2f, 0x2a }, /* '/' => '*' */ + { 0x30, 0x7d }, /* '0' => '}' */ + { 0x32, 0x40 }, /* '2' => '@' */ + { 0x33, 0x23 }, /* '3' => '#' */ + { 0x34, 0x24 }, /* '4' => '$' */ + { 0x36, 0x5e }, /* '6' => '^' */ + { 0x37, 0x26 }, /* '7' => '&' */ + { 0x38, 0x2a }, /* '8' => '*' */ + { 0x39, 0x7b }, /* '9' => '{' */ + { 0x3b, 0x24 }, /* ';' => '$' */ + { 0x62, 0x7b }, /* 'b' => '{' */ + { 0x63, 0x26 }, /* 'c' => '&' */ + { 0x67, 0x5d }, /* 'g' => ']' */ + { 0x68, 0x60 }, /* 'h' => '`' */ + { 0x6d, 0x5e }, /* 'm' => '^' */ + { 0x6e, 0x7d }, /* 'n' => '}' */ + { 0x76, 0x40 }, /* 'v' => '@' */ + { 0x78, 0x23 }, /* 'x' => '#' */ + { 0, 0 } +}; + /** "cz" keyboard map */ struct keymap cz_keymap __keymap = { .name = "cz", .basic = cz_basic, + .altgr = cz_altgr, }; diff --git a/src/hci/keymap/keymap_de.c b/src/hci/keymap/keymap_de.c index 4d23c2e60..4a889a242 100644 --- a/src/hci/keymap/keymap_de.c +++ b/src/hci/keymap/keymap_de.c @@ -41,8 +41,19 @@ static struct keymap_key de_basic[] = { { 0, 0 } }; +/** "de" AltGr remapping */ +static struct keymap_key de_altgr[] = { + { 0x2d, 0x5c }, /* '-' => '\\' */ + { 0x30, 0x7d }, /* '0' => '}' */ + { 0x39, 0x5d }, /* '9' => ']' */ + { 0x71, 0x40 }, /* 'q' => '@' */ + { 0xdc, 0x7c }, /* Pseudo-'\\' => '|' */ + { 0, 0 } +}; + /** "de" keyboard map */ struct keymap de_keymap __keymap = { .name = "de", .basic = de_basic, + .altgr = de_altgr, }; diff --git a/src/hci/keymap/keymap_dk.c b/src/hci/keymap/keymap_dk.c index 100246bf5..4d40743b1 100644 --- a/src/hci/keymap/keymap_dk.c +++ b/src/hci/keymap/keymap_dk.c @@ -33,8 +33,17 @@ static struct keymap_key dk_basic[] = { { 0, 0 } }; +/** "dk" AltGr remapping */ +static struct keymap_key dk_altgr[] = { + { 0x32, 0x40 }, /* '2' => '@' */ + { 0x3d, 0x7c }, /* '=' => '|' */ + { 0x71, 0x40 }, /* 'q' => '@' */ + { 0, 0 } +}; + /** "dk" keyboard map */ struct keymap dk_keymap __keymap = { .name = "dk", .basic = dk_basic, + .altgr = dk_altgr, }; diff --git a/src/hci/keymap/keymap_es.c b/src/hci/keymap/keymap_es.c index 2f4b86c47..397e2cbaa 100644 --- a/src/hci/keymap/keymap_es.c +++ b/src/hci/keymap/keymap_es.c @@ -33,8 +33,19 @@ static struct keymap_key es_basic[] = { { 0, 0 } }; +/** "es" AltGr remapping */ +static struct keymap_key es_altgr[] = { + { 0x30, 0x7d }, /* '0' => '}' */ + { 0x32, 0x40 }, /* '2' => '@' */ + { 0x39, 0x5d }, /* '9' => ']' */ + { 0x5c, 0x7d }, /* '\\' => '}' */ + { 0x71, 0x40 }, /* 'q' => '@' */ + { 0, 0 } +}; + /** "es" keyboard map */ struct keymap es_keymap __keymap = { .name = "es", .basic = es_basic, + .altgr = es_altgr, }; diff --git a/src/hci/keymap/keymap_et.c b/src/hci/keymap/keymap_et.c index a8bf46ebc..4120dbed9 100644 --- a/src/hci/keymap/keymap_et.c +++ b/src/hci/keymap/keymap_et.c @@ -31,8 +31,18 @@ static struct keymap_key et_basic[] = { { 0, 0 } }; +/** "et" AltGr remapping */ +static struct keymap_key et_altgr[] = { + { 0x27, 0x5e }, /* '\'' => '^' */ + { 0x2d, 0x5c }, /* '-' => '\\' */ + { 0x32, 0x40 }, /* '2' => '@' */ + { 0xdc, 0x7c }, /* Pseudo-'\\' => '|' */ + { 0, 0 } +}; + /** "et" keyboard map */ struct keymap et_keymap __keymap = { .name = "et", .basic = et_basic, + .altgr = et_altgr, }; diff --git a/src/hci/keymap/keymap_fi.c b/src/hci/keymap/keymap_fi.c index eb75eb4dc..978121a88 100644 --- a/src/hci/keymap/keymap_fi.c +++ b/src/hci/keymap/keymap_fi.c @@ -31,8 +31,17 @@ static struct keymap_key fi_basic[] = { { 0, 0 } }; +/** "fi" AltGr remapping */ +static struct keymap_key fi_altgr[] = { + { 0x2d, 0x5c }, /* '-' => '\\' */ + { 0x32, 0x40 }, /* '2' => '@' */ + { 0xdc, 0x7c }, /* Pseudo-'\\' => '|' */ + { 0, 0 } +}; + /** "fi" keyboard map */ struct keymap fi_keymap __keymap = { .name = "fi", .basic = fi_basic, + .altgr = fi_altgr, }; diff --git a/src/hci/keymap/keymap_fr.c b/src/hci/keymap/keymap_fr.c index 523254ee5..c0a959f0d 100644 --- a/src/hci/keymap/keymap_fr.c +++ b/src/hci/keymap/keymap_fr.c @@ -62,8 +62,20 @@ static struct keymap_key fr_basic[] = { { 0, 0 } }; +/** "fr" AltGr remapping */ +static struct keymap_key fr_altgr[] = { + { 0x2d, 0x5d }, /* '-' => ']' */ + { 0x30, 0x40 }, /* '0' => '@' */ + { 0x33, 0x23 }, /* '3' => '#' */ + { 0x38, 0x5c }, /* '8' => '\\' */ + { 0x39, 0x5e }, /* '9' => '^' */ + { 0x61, 0x40 }, /* 'a' => '@' */ + { 0, 0 } +}; + /** "fr" keyboard map */ struct keymap fr_keymap __keymap = { .name = "fr", .basic = fr_basic, + .altgr = fr_altgr, }; diff --git a/src/hci/keymap/keymap_gr.c b/src/hci/keymap/keymap_gr.c index 16a2a7032..4826c26c2 100644 --- a/src/hci/keymap/keymap_gr.c +++ b/src/hci/keymap/keymap_gr.c @@ -17,8 +17,14 @@ static struct keymap_key gr_basic[] = { { 0, 0 } }; +/** "gr" AltGr remapping */ +static struct keymap_key gr_altgr[] = { + { 0, 0 } +}; + /** "gr" keyboard map */ struct keymap gr_keymap __keymap = { .name = "gr", .basic = gr_basic, + .altgr = gr_altgr, }; diff --git a/src/hci/keymap/keymap_hu.c b/src/hci/keymap/keymap_hu.c index 5e407161f..64e27dda6 100644 --- a/src/hci/keymap/keymap_hu.c +++ b/src/hci/keymap/keymap_hu.c @@ -35,8 +35,25 @@ static struct keymap_key hu_basic[] = { { 0, 0 } }; +/** "hu" AltGr remapping */ +static struct keymap_key hu_altgr[] = { + { 0x2e, 0x3e }, /* '.' => '>' */ + { 0x2f, 0x2a }, /* '/' => '*' */ + { 0x33, 0x5e }, /* '3' => '^' */ + { 0x37, 0x60 }, /* '7' => '`' */ + { 0x3b, 0x24 }, /* ';' => '$' */ + { 0x63, 0x26 }, /* 'c' => '&' */ + { 0x6d, 0x3c }, /* 'm' => '<' */ + { 0x76, 0x40 }, /* 'v' => '@' */ + { 0x78, 0x23 }, /* 'x' => '#' */ + { 0x7a, 0x3e }, /* 'z' => '>' */ + { 0xdc, 0x3c }, /* Pseudo-'\\' => '<' */ + { 0, 0 } +}; + /** "hu" keyboard map */ struct keymap hu_keymap __keymap = { .name = "hu", .basic = hu_basic, + .altgr = hu_altgr, }; diff --git a/src/hci/keymap/keymap_il.c b/src/hci/keymap/keymap_il.c index de5e639ca..e3061fa54 100644 --- a/src/hci/keymap/keymap_il.c +++ b/src/hci/keymap/keymap_il.c @@ -29,8 +29,14 @@ static struct keymap_key il_basic[] = { { 0, 0 } }; +/** "il" AltGr remapping */ +static struct keymap_key il_altgr[] = { + { 0, 0 } +}; + /** "il" keyboard map */ struct keymap il_keymap __keymap = { .name = "il", .basic = il_basic, + .altgr = il_altgr, }; diff --git a/src/hci/keymap/keymap_it.c b/src/hci/keymap/keymap_it.c index a4921020a..f67bbadcb 100644 --- a/src/hci/keymap/keymap_it.c +++ b/src/hci/keymap/keymap_it.c @@ -35,8 +35,20 @@ static struct keymap_key it_basic[] = { { 0, 0 } }; +/** "it" AltGr remapping */ +static struct keymap_key it_altgr[] = { + { 0x2d, 0x60 }, /* '-' => '`' */ + { 0x30, 0x7d }, /* '0' => '}' */ + { 0x39, 0x5d }, /* '9' => ']' */ + { 0x3b, 0x40 }, /* ';' => '@' */ + { 0x3d, 0x7e }, /* '=' => '~' */ + { 0x71, 0x40 }, /* 'q' => '@' */ + { 0, 0 } +}; + /** "it" keyboard map */ struct keymap it_keymap __keymap = { .name = "it", .basic = it_basic, + .altgr = it_altgr, }; diff --git a/src/hci/keymap/keymap_lt.c b/src/hci/keymap/keymap_lt.c index 333241d21..5d6ee5a8c 100644 --- a/src/hci/keymap/keymap_lt.c +++ b/src/hci/keymap/keymap_lt.c @@ -15,8 +15,14 @@ static struct keymap_key lt_basic[] = { { 0, 0 } }; +/** "lt" AltGr remapping */ +static struct keymap_key lt_altgr[] = { + { 0, 0 } +}; + /** "lt" keyboard map */ struct keymap lt_keymap __keymap = { .name = "lt", .basic = lt_basic, + .altgr = lt_altgr, }; diff --git a/src/hci/keymap/keymap_mk.c b/src/hci/keymap/keymap_mk.c index 1656fb99c..4b90ef799 100644 --- a/src/hci/keymap/keymap_mk.c +++ b/src/hci/keymap/keymap_mk.c @@ -17,8 +17,14 @@ static struct keymap_key mk_basic[] = { { 0, 0 } }; +/** "mk" AltGr remapping */ +static struct keymap_key mk_altgr[] = { + { 0, 0 } +}; + /** "mk" keyboard map */ struct keymap mk_keymap __keymap = { .name = "mk", .basic = mk_basic, + .altgr = mk_altgr, }; diff --git a/src/hci/keymap/keymap_mt.c b/src/hci/keymap/keymap_mt.c index ebff8506f..f5baf6907 100644 --- a/src/hci/keymap/keymap_mt.c +++ b/src/hci/keymap/keymap_mt.c @@ -20,8 +20,15 @@ static struct keymap_key mt_basic[] = { { 0, 0 } }; +/** "mt" AltGr remapping */ +static struct keymap_key mt_altgr[] = { + { 0x2d, 0x5c }, /* '-' => '\\' */ + { 0, 0 } +}; + /** "mt" keyboard map */ struct keymap mt_keymap __keymap = { .name = "mt", .basic = mt_basic, + .altgr = mt_altgr, }; diff --git a/src/hci/keymap/keymap_nl.c b/src/hci/keymap/keymap_nl.c index 2172e045f..bbee4cbdf 100644 --- a/src/hci/keymap/keymap_nl.c +++ b/src/hci/keymap/keymap_nl.c @@ -38,8 +38,16 @@ static struct keymap_key nl_basic[] = { { 0, 0 } }; +/** "nl" AltGr remapping */ +static struct keymap_key nl_altgr[] = { + { 0x2d, 0x5c }, /* '-' => '\\' */ + { 0x39, 0x7d }, /* '9' => '}' */ + { 0, 0 } +}; + /** "nl" keyboard map */ struct keymap nl_keymap __keymap = { .name = "nl", .basic = nl_basic, + .altgr = nl_altgr, }; diff --git a/src/hci/keymap/keymap_no-latin1.c b/src/hci/keymap/keymap_no-latin1.c index 65f30beae..63fe85548 100644 --- a/src/hci/keymap/keymap_no-latin1.c +++ b/src/hci/keymap/keymap_no-latin1.c @@ -37,8 +37,18 @@ static struct keymap_key no_latin1_basic[] = { { 0, 0 } }; +/** "no-latin1" AltGr remapping */ +static struct keymap_key no_latin1_altgr[] = { + { 0x30, 0x7d }, /* '0' => '}' */ + { 0x32, 0x40 }, /* '2' => '@' */ + { 0x39, 0x5d }, /* '9' => ']' */ + { 0x5b, 0x7d }, /* '[' => '}' */ + { 0, 0 } +}; + /** "no-latin1" keyboard map */ struct keymap no_latin1_keymap __keymap = { .name = "no-latin1", .basic = no_latin1_basic, + .altgr = no_latin1_altgr, }; diff --git a/src/hci/keymap/keymap_no.c b/src/hci/keymap/keymap_no.c index d3d06bce3..95a95428b 100644 --- a/src/hci/keymap/keymap_no.c +++ b/src/hci/keymap/keymap_no.c @@ -35,8 +35,16 @@ static struct keymap_key no_basic[] = { { 0, 0 } }; +/** "no" AltGr remapping */ +static struct keymap_key no_altgr[] = { + { 0x32, 0x40 }, /* '2' => '@' */ + { 0x71, 0x40 }, /* 'q' => '@' */ + { 0, 0 } +}; + /** "no" keyboard map */ struct keymap no_keymap __keymap = { .name = "no", .basic = no_basic, + .altgr = no_altgr, }; diff --git a/src/hci/keymap/keymap_pl.c b/src/hci/keymap/keymap_pl.c index a23c01f2c..a76181fbc 100644 --- a/src/hci/keymap/keymap_pl.c +++ b/src/hci/keymap/keymap_pl.c @@ -17,8 +17,14 @@ static struct keymap_key pl_basic[] = { { 0, 0 } }; +/** "pl" AltGr remapping */ +static struct keymap_key pl_altgr[] = { + { 0, 0 } +}; + /** "pl" keyboard map */ struct keymap pl_keymap __keymap = { .name = "pl", .basic = pl_basic, + .altgr = pl_altgr, }; diff --git a/src/hci/keymap/keymap_pt.c b/src/hci/keymap/keymap_pt.c index c065fd76f..99ba52e4b 100644 --- a/src/hci/keymap/keymap_pt.c +++ b/src/hci/keymap/keymap_pt.c @@ -34,8 +34,18 @@ static struct keymap_key pt_basic[] = { { 0, 0 } }; +/** "pt" AltGr remapping */ +static struct keymap_key pt_altgr[] = { + { 0x32, 0x40 }, /* '2' => '@' */ + { 0x37, 0x7b }, /* '7' => '{' */ + { 0x38, 0x5b }, /* '8' => '[' */ + { 0x71, 0x40 }, /* 'q' => '@' */ + { 0, 0 } +}; + /** "pt" keyboard map */ struct keymap pt_keymap __keymap = { .name = "pt", .basic = pt_basic, + .altgr = pt_altgr, }; diff --git a/src/hci/keymap/keymap_ro.c b/src/hci/keymap/keymap_ro.c index 334cf6080..620450001 100644 --- a/src/hci/keymap/keymap_ro.c +++ b/src/hci/keymap/keymap_ro.c @@ -15,8 +15,14 @@ static struct keymap_key ro_basic[] = { { 0, 0 } }; +/** "ro" AltGr remapping */ +static struct keymap_key ro_altgr[] = { + { 0, 0 } +}; + /** "ro" keyboard map */ struct keymap ro_keymap __keymap = { .name = "ro", .basic = ro_basic, + .altgr = ro_altgr, }; diff --git a/src/hci/keymap/keymap_ru.c b/src/hci/keymap/keymap_ru.c index a08b115ed..2aafcf9bd 100644 --- a/src/hci/keymap/keymap_ru.c +++ b/src/hci/keymap/keymap_ru.c @@ -18,8 +18,14 @@ static struct keymap_key ru_basic[] = { { 0, 0 } }; +/** "ru" AltGr remapping */ +static struct keymap_key ru_altgr[] = { + { 0, 0 } +}; + /** "ru" keyboard map */ struct keymap ru_keymap __keymap = { .name = "ru", .basic = ru_basic, + .altgr = ru_altgr, }; diff --git a/src/hci/keymap/keymap_sg.c b/src/hci/keymap/keymap_sg.c index 152c5d631..9a6db9cb4 100644 --- a/src/hci/keymap/keymap_sg.c +++ b/src/hci/keymap/keymap_sg.c @@ -43,8 +43,18 @@ static struct keymap_key sg_basic[] = { { 0, 0 } }; +/** "sg" AltGr remapping */ +static struct keymap_key sg_altgr[] = { + { 0x32, 0x40 }, /* '2' => '@' */ + { 0x33, 0x23 }, /* '3' => '#' */ + { 0x37, 0x7c }, /* '7' => '|' */ + { 0x5c, 0x7d }, /* '\\' => '}' */ + { 0, 0 } +}; + /** "sg" keyboard map */ struct keymap sg_keymap __keymap = { .name = "sg", .basic = sg_basic, + .altgr = sg_altgr, }; diff --git a/src/hci/keymap/keymap_sr-latin.c b/src/hci/keymap/keymap_sr-latin.c index ec5efdc89..7e55714a2 100644 --- a/src/hci/keymap/keymap_sr-latin.c +++ b/src/hci/keymap/keymap_sr-latin.c @@ -17,8 +17,14 @@ static struct keymap_key sr_latin_basic[] = { { 0, 0 } }; +/** "sr-latin" AltGr remapping */ +static struct keymap_key sr_latin_altgr[] = { + { 0, 0 } +}; + /** "sr-latin" keyboard map */ struct keymap sr_latin_keymap __keymap = { .name = "sr-latin", .basic = sr_latin_basic, + .altgr = sr_latin_altgr, }; diff --git a/src/hci/keymap/keymap_ua.c b/src/hci/keymap/keymap_ua.c index b4199cdad..44e82cb2d 100644 --- a/src/hci/keymap/keymap_ua.c +++ b/src/hci/keymap/keymap_ua.c @@ -17,8 +17,14 @@ static struct keymap_key ua_basic[] = { { 0, 0 } }; +/** "ua" AltGr remapping */ +static struct keymap_key ua_altgr[] = { + { 0, 0 } +}; + /** "ua" keyboard map */ struct keymap ua_keymap __keymap = { .name = "ua", .basic = ua_basic, + .altgr = ua_altgr, }; diff --git a/src/hci/keymap/keymap_uk.c b/src/hci/keymap/keymap_uk.c index 156b42dff..28cf7aac4 100644 --- a/src/hci/keymap/keymap_uk.c +++ b/src/hci/keymap/keymap_uk.c @@ -19,8 +19,14 @@ static struct keymap_key uk_basic[] = { { 0, 0 } }; +/** "uk" AltGr remapping */ +static struct keymap_key uk_altgr[] = { + { 0, 0 } +}; + /** "uk" keyboard map */ struct keymap uk_keymap __keymap = { .name = "uk", .basic = uk_basic, + .altgr = uk_altgr, }; diff --git a/src/hci/keymap/keymap_us.c b/src/hci/keymap/keymap_us.c index 5d78f80a2..6432474e2 100644 --- a/src/hci/keymap/keymap_us.c +++ b/src/hci/keymap/keymap_us.c @@ -15,8 +15,14 @@ static struct keymap_key us_basic[] = { { 0, 0 } }; +/** "us" AltGr remapping */ +static struct keymap_key us_altgr[] = { + { 0, 0 } +}; + /** "us" keyboard map */ struct keymap us_keymap __keymap = { .name = "us", .basic = us_basic, + .altgr = us_altgr, }; diff --git a/src/include/ipxe/keymap.h b/src/include/ipxe/keymap.h index 3da25190b..72b6961ef 100644 --- a/src/include/ipxe/keymap.h +++ b/src/include/ipxe/keymap.h @@ -32,6 +32,8 @@ struct keymap { const char *name; /** Basic remapping table (zero-terminated) */ struct keymap_key *basic; + /** AltGr remapping table (zero-terminated) */ + struct keymap_key *altgr; }; /** Keyboard mapping table */ @@ -64,6 +66,9 @@ struct keymap { /** Undo and redo CapsLock key flags */ #define KEYMAP_CAPSLOCK_REDO ( KEYMAP_CAPSLOCK | KEYMAP_CAPSLOCK_UNDO ) +/** AltGr key flag */ +#define KEYMAP_ALTGR 0x0800 + extern unsigned int key_remap ( unsigned int character ); #endif /* _IPXE_KEYMAP_H */ diff --git a/src/interface/efi/efi_console.c b/src/interface/efi/efi_console.c index 9adce4a9b..fc1500afb 100644 --- a/src/interface/efi/efi_console.c +++ b/src/interface/efi/efi_console.c @@ -334,6 +334,9 @@ static int efi_getchar ( void ) { EFI_RIGHT_CONTROL_PRESSED ) ) { character |= KEYMAP_CTRL; } + if ( shift & EFI_RIGHT_ALT_PRESSED ) { + character |= KEYMAP_ALTGR; + } } /* Apply toggle state */ diff --git a/src/util/genkeymap.py b/src/util/genkeymap.py index d38552eb4..ff5ff0a87 100755 --- a/src/util/genkeymap.py +++ b/src/util/genkeymap.py @@ -171,6 +171,11 @@ class KeyLayout(UserDict[KeyModifiers, Sequence[Key]]): """Basic shifted keyboard layout""" return self[KeyModifiers.SHIFT] + @property + def altgr(self): + """AltGr keyboard layout""" + return self.get(KeyModifiers.ALTGR, self.unshifted) + @classmethod def load(cls, name: str) -> KeyLayout: """Load keymap using 'loadkeys -b'""" @@ -278,6 +283,7 @@ class KeymapKeys(UserDict[str, str]): self.ascii_name(source), self.ascii_name(target) ) for source, target in self.items() + if ord(source) & ~BiosKeyLayout.KEY_PSEUDO != ord(target) ) + '\t{ 0, 0 }\n}' @@ -301,13 +307,12 @@ class Keymap: raw = {source: self.target[key.modifiers][key.keycode].ascii for source, key in self.source.inverse.items()} # Eliminate any null mappings, mappings that attempt to remap - # the backspace key, or mappings that would become identity - # mappings after clearing the high bit + # the backspace key, or identity mappings table = {source: target for source, target in raw.items() if target and ord(source) != 0x7f and ord(target) != 0x7f - and ord(source) & ~BiosKeyLayout.KEY_PSEUDO != ord(target)} + and source != target} # Recursively delete any mappings that would produce # unreachable alphanumerics (e.g. the "il" keymap, which maps # away the whole lower-case alphabet) @@ -327,6 +332,28 @@ class Keymap: (unshifted, shifted)) return KeymapKeys(dict(sorted(table.items()))) + @property + def altgr(self) -> KeymapKeys: + """AltGr remapping table""" + # Construct raw mapping from source ASCII to target ASCII + raw = {source: self.target.altgr[key.keycode].ascii + for source, key in self.source.inverse.items() + if key.modifiers == KeyModifiers.NONE} + # Identify printable keys that are unreachable via the basic map + basic = self.basic + unmapped = set(x for x in basic.keys() + if x.isascii() and x.isprintable()) + remapped = set(basic.values()) + unreachable = unmapped - remapped + # Eliminate any null mappings, mappings for unprintable + # characters, or mappings for characters that are reachable + # via the basic map + table = {source: target for source, target in raw.items() + if source.isprintable() + and target + and target in unreachable} + return KeymapKeys(dict(sorted(table.items()))) + def cname(self, suffix: str) -> str: """C variable name""" return re.sub(r'\W', '_', (self.name + '_' + suffix)) @@ -336,6 +363,7 @@ class Keymap: """Generated source code""" keymap_name = self.cname("keymap") basic_name = self.cname("basic") + altgr_name = self.cname("altgr") code = textwrap.dedent(f""" /** @file * @@ -352,12 +380,16 @@ class Keymap: /** "{self.name}" basic remapping */ static struct keymap_key {basic_name}[] = %s; + /** "{self.name}" AltGr remapping */ + static struct keymap_key {altgr_name}[] = %s; + /** "{self.name}" keyboard map */ struct keymap {keymap_name} __keymap = {{ \t.name = "{self.name}", \t.basic = {basic_name}, + \t.altgr = {altgr_name}, }}; - """).strip() % self.basic.code + """).strip() % (self.basic.code, self.altgr.code) return code -- cgit v1.2.3-55-g7522 From 2ff3385e0078edda43e13ebfc9978fbcc5db311a Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Mon, 28 Feb 2022 13:39:10 +0000 Subject: [efi] Support Unicode character output via text console Signed-off-by: Michael Brown --- src/interface/efi/efi_console.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) (limited to 'src/interface') diff --git a/src/interface/efi/efi_console.c b/src/interface/efi/efi_console.c index fc1500afb..04bbd9e0c 100644 --- a/src/interface/efi/efi_console.c +++ b/src/interface/efi/efi_console.c @@ -26,6 +26,7 @@ FILE_LICENCE ( GPL2_OR_LATER ); #include #include #include +#include #include #include #include @@ -201,6 +202,9 @@ static struct ansiesc_context efi_ansiesc_ctx = { .handlers = efi_ansiesc_handlers, }; +/** EFI console UTF-8 accumulator */ +static struct utf8_accumulator efi_utf8_acc; + /** * Print a character to EFI console * @@ -208,13 +212,25 @@ static struct ansiesc_context efi_ansiesc_ctx = { */ static void efi_putchar ( int character ) { EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *conout = efi_systab->ConOut; - wchar_t wstr[] = { character, 0 }; + wchar_t wstr[2]; /* Intercept ANSI escape sequences */ character = ansiesc_process ( &efi_ansiesc_ctx, character ); if ( character < 0 ) return; + /* Accumulate Unicode characters */ + character = utf8_accumulate ( &efi_utf8_acc, character ); + if ( character == 0 ) + return; + + /* Treat unrepresentable (non-UCS2) characters as invalid */ + if ( character & ~( ( wchar_t ) -1UL ) ) + character = UTF8_INVALID; + + /* Output character */ + wstr[0] = character; + wstr[1] = L'\0'; conout->OutputString ( conout, wstr ); } -- cgit v1.2.3-55-g7522 From ba93c9134ce9d9edcba117b690fbbdd35b3e066b Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Mon, 14 Mar 2022 22:38:24 +0000 Subject: [fbcon] Support Unicode character output Accumulate UTF-8 characters in fbcon_putchar(), and require the frame buffer console's .glyph() method to accept Unicode character values. Signed-off-by: Michael Brown --- src/arch/x86/interface/pcbios/vesafb.c | 25 ++++- src/core/fbcon.c | 5 + src/include/ipxe/fbcon.h | 7 +- src/interface/efi/efi_fbcon.c | 198 +++++++++++++++++++++------------ 4 files changed, 161 insertions(+), 74 deletions(-) (limited to 'src/interface') diff --git a/src/arch/x86/interface/pcbios/vesafb.c b/src/arch/x86/interface/pcbios/vesafb.c index 50e485852..86edbda42 100644 --- a/src/arch/x86/interface/pcbios/vesafb.c +++ b/src/arch/x86/interface/pcbios/vesafb.c @@ -78,6 +78,15 @@ struct console_driver bios_console __attribute__ (( weak )); /** Font corresponding to selected character width and height */ #define VESAFB_FONT VBE_FONT_8x16 +/** Number of ASCII glyphs within the font */ +#define VESAFB_ASCII 128 + +/** Glyph to render for non-ASCII characters + * + * We choose to use one of the box-drawing glyphs. + */ +#define VESAFB_UNKNOWN 0xfe + /* Forward declaration */ struct console_driver vesafb_console __console_driver; @@ -130,12 +139,24 @@ static int vesafb_rc ( unsigned int status ) { /** * Get character glyph * - * @v character Character + * @v character Unicode character * @v glyph Character glyph to fill in */ static void vesafb_glyph ( unsigned int character, uint8_t *glyph ) { - size_t offset = ( character * VESAFB_CHAR_HEIGHT ); + unsigned int index; + size_t offset; + + /* Identify glyph */ + if ( character < VESAFB_ASCII ) { + /* ASCII character: use corresponding glyph */ + index = character; + } else { + /* Non-ASCII character: use "unknown" glyph */ + index = VESAFB_UNKNOWN; + } + /* Copy glyph from BIOS font table */ + offset = ( index * VESAFB_CHAR_HEIGHT ); copy_from_real ( glyph, vesafb.glyphs.segment, ( vesafb.glyphs.offset + offset ), VESAFB_CHAR_HEIGHT); } diff --git a/src/core/fbcon.c b/src/core/fbcon.c index 44a56e105..ff3132ac7 100644 --- a/src/core/fbcon.c +++ b/src/core/fbcon.c @@ -446,6 +446,11 @@ void fbcon_putchar ( struct fbcon *fbcon, int character ) { if ( character < 0 ) return; + /* Accumulate Unicode characters */ + character = utf8_accumulate ( &fbcon->utf8, character ); + if ( character == 0 ) + return; + /* Handle control characters */ switch ( character ) { case '\r': diff --git a/src/include/ipxe/fbcon.h b/src/include/ipxe/fbcon.h index 42ffca3d7..a4c7a9ab3 100644 --- a/src/include/ipxe/fbcon.h +++ b/src/include/ipxe/fbcon.h @@ -11,6 +11,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include +#include #include #include @@ -36,7 +37,7 @@ struct fbcon_font { /** * Get character glyph * - * @v character Character + * @v character Unicode character * @v glyph Character glyph to fill in */ void ( * glyph ) ( unsigned int character, uint8_t *glyph ); @@ -92,7 +93,7 @@ struct fbcon_text_cell { uint32_t foreground; /** Background colour */ uint32_t background; - /** Character */ + /** Unicode character */ unsigned int character; }; @@ -138,6 +139,8 @@ struct fbcon { unsigned int ypos; /** ANSI escape sequence context */ struct ansiesc_context ctx; + /** UTF-8 accumulator */ + struct utf8_accumulator utf8; /** Text array */ struct fbcon_text text; /** Background picture */ diff --git a/src/interface/efi/efi_fbcon.c b/src/interface/efi/efi_fbcon.c index abc5a9390..d9e3e69e6 100644 --- a/src/interface/efi/efi_fbcon.c +++ b/src/interface/efi/efi_fbcon.c @@ -62,6 +62,9 @@ struct console_driver efi_console __attribute__ (( weak )); #define CONSOLE_EFIFB ( CONSOLE_USAGE_ALL & ~CONSOLE_USAGE_LOG ) #endif +/** Number of ASCII glyphs in cache */ +#define EFIFB_ASCII 128 + /* Forward declaration */ struct console_driver efifb_console __console_driver; @@ -84,7 +87,7 @@ struct efifb { struct fbcon_colour_map map; /** Font definition */ struct fbcon_font font; - /** Character glyphs */ + /** Character glyph cache */ userptr_t glyphs; }; @@ -92,14 +95,112 @@ struct efifb { static struct efifb efifb; /** - * Get character glyph + * Draw character glyph * * @v character Character + * @v index Index within glyph cache + * @v toggle Bits to toggle in each bitmask + * @ret height Character height, or negative error + */ +static int efifb_draw ( unsigned int character, unsigned int index, + unsigned int toggle ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + EFI_IMAGE_OUTPUT *blt; + EFI_GRAPHICS_OUTPUT_BLT_PIXEL *pixel; + unsigned int height; + unsigned int x; + unsigned int y; + uint8_t bitmask; + size_t offset; + EFI_STATUS efirc; + int rc; + + /* Clear existing glyph */ + offset = ( index * efifb.font.height ); + memset_user ( efifb.glyphs, offset, 0, efifb.font.height ); + + /* Get glyph */ + blt = NULL; + if ( ( efirc = efifb.hiifont->GetGlyph ( efifb.hiifont, character, + NULL, &blt, NULL ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( &efifb, "EFIFB could not get glyph %#02x: %s\n", + character, strerror ( rc ) ); + goto err_get; + } + assert ( blt != NULL ); + + /* Sanity check */ + if ( blt->Width > 8 ) { + DBGC ( &efifb, "EFIFB glyph %#02x invalid width %d\n", + character, blt->Width ); + rc = -EINVAL; + goto err_width; + } + + /* Convert glyph to bitmap */ + pixel = blt->Image.Bitmap; + height = blt->Height; + for ( y = 0 ; ( ( y < height ) && ( y < efifb.font.height ) ) ; y++ ) { + bitmask = 0; + for ( x = 0 ; x < blt->Width ; x++ ) { + bitmask = rol8 ( bitmask, 1 ); + if ( pixel->Blue || pixel->Green || pixel->Red ) + bitmask |= 0x01; + pixel++; + } + bitmask ^= toggle; + copy_to_user ( efifb.glyphs, offset++, &bitmask, + sizeof ( bitmask ) ); + } + + /* Free glyph */ + bs->FreePool ( blt ); + + return height; + + err_width: + bs->FreePool ( blt ); + err_get: + return rc; +} + +/** + * Draw "unknown character" glyph + * + * @v index Index within glyph cache + * @ret rc Return status code + */ +static int efifb_draw_unknown ( unsigned int index ) { + + /* Draw an inverted '?' glyph */ + return efifb_draw ( '?', index, -1U ); +} + +/** + * Get character glyph + * + * @v character Unicode character * @v glyph Character glyph to fill in */ static void efifb_glyph ( unsigned int character, uint8_t *glyph ) { - size_t offset = ( character * efifb.font.height ); + unsigned int index; + size_t offset; + + /* Identify glyph */ + if ( character < EFIFB_ASCII ) { + + /* ASCII character: use fixed cache entry */ + index = character; + } else { + + /* Non-ASCII character: use an "unknown" glyph */ + index = 0; + } + + /* Copy cached glyph */ + offset = ( index * efifb.font.height ); copy_from_user ( glyph, efifb.glyphs, offset, efifb.font.height ); } @@ -109,16 +210,10 @@ static void efifb_glyph ( unsigned int character, uint8_t *glyph ) { * @ret rc Return status code */ static int efifb_glyphs ( void ) { - EFI_BOOT_SERVICES *bs = efi_systab->BootServices; - EFI_IMAGE_OUTPUT *blt; - EFI_GRAPHICS_OUTPUT_BLT_PIXEL *pixel; - size_t offset; - size_t len; - uint8_t bitmask; unsigned int character; - unsigned int x; - unsigned int y; - EFI_STATUS efirc; + int height; + int max; + size_t len; int rc; /* Get font height. The GetFontInfo() call nominally returns @@ -128,38 +223,32 @@ static int efifb_glyphs ( void ) { * height. */ efifb.font.height = 0; - for ( character = 0 ; character < 256 ; character++ ) { + max = 0; + for ( character = 0 ; character < EFIFB_ASCII ; character++ ) { /* Skip non-printable characters */ if ( ! isprint ( character ) ) continue; /* Get glyph */ - blt = NULL; - if ( ( efirc = efifb.hiifont->GetGlyph ( efifb.hiifont, - character, NULL, &blt, - NULL ) ) != 0 ) { - rc = -EEFI ( efirc ); - DBGC ( &efifb, "EFIFB could not get glyph %d: %s\n", - character, strerror ( rc ) ); - continue; + height = efifb_draw ( character, 0, 0 ); + if ( height < 0 ) { + rc = height; + goto err_height; } - assert ( blt != NULL ); /* Calculate maximum height */ - if ( efifb.font.height < blt->Height ) - efifb.font.height = blt->Height; - - /* Free glyph */ - bs->FreePool ( blt ); + if ( max < height ) + max = height; } - if ( ! efifb.font.height ) { + if ( ! max ) { DBGC ( &efifb, "EFIFB could not get font height\n" ); return -ENOENT; } + efifb.font.height = max; /* Allocate glyph data */ - len = ( 256 * efifb.font.height * sizeof ( bitmask ) ); + len = ( EFIFB_ASCII * efifb.font.height ); efifb.glyphs = umalloc ( len ); if ( ! efifb.glyphs ) { rc = -ENOMEM; @@ -168,60 +257,29 @@ static int efifb_glyphs ( void ) { memset_user ( efifb.glyphs, 0, 0, len ); /* Get font data */ - for ( character = 0 ; character < 256 ; character++ ) { + for ( character = 0 ; character < EFIFB_ASCII ; character++ ) { /* Skip non-printable characters */ - if ( ! isprint ( character ) ) - continue; - - /* Get glyph */ - blt = NULL; - if ( ( efirc = efifb.hiifont->GetGlyph ( efifb.hiifont, - character, NULL, &blt, - NULL ) ) != 0 ) { - rc = -EEFI ( efirc ); - DBGC ( &efifb, "EFIFB could not get glyph %d: %s\n", - character, strerror ( rc ) ); - continue; - } - assert ( blt != NULL ); - - /* Sanity check */ - if ( blt->Width > 8 ) { - DBGC ( &efifb, "EFIFB glyph %d invalid width %d\n", - character, blt->Width ); - continue; - } - if ( blt->Height > efifb.font.height ) { - DBGC ( &efifb, "EFIFB glyph %d invalid height %d\n", - character, blt->Height ); + if ( ! isprint ( character ) ) { + efifb_draw_unknown ( character ); continue; } - /* Convert glyph to bitmap */ - pixel = blt->Image.Bitmap; - offset = ( character * efifb.font.height ); - for ( y = 0 ; y < blt->Height ; y++ ) { - bitmask = 0; - for ( x = 0 ; x < blt->Width ; x++ ) { - bitmask = rol8 ( bitmask, 1 ); - if ( pixel->Blue || pixel->Green || pixel->Red ) - bitmask |= 0x01; - pixel++; - } - copy_to_user ( efifb.glyphs, offset++, &bitmask, - sizeof ( bitmask ) ); + /* Get glyph */ + height = efifb_draw ( character, character, 0 ); + if ( height < 0 ) { + rc = height; + goto err_draw; } - - /* Free glyph */ - bs->FreePool ( blt ); } efifb.font.glyph = efifb_glyph; return 0; + err_draw: ufree ( efifb.glyphs ); err_alloc: + err_height: return rc; } -- cgit v1.2.3-55-g7522 From dd3547543811dd2e996b910bdadb609414904352 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Tue, 15 Mar 2022 15:04:22 +0000 Subject: [efi] Support Unicode character output via framebuffer console Extend the glyph cache to include a number of dynamic entries that are populated on demand whenever a non-ASCII character needs to be drawn. Signed-off-by: Michael Brown --- src/interface/efi/efi_fbcon.c | 51 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 3 deletions(-) (limited to 'src/interface') diff --git a/src/interface/efi/efi_fbcon.c b/src/interface/efi/efi_fbcon.c index d9e3e69e6..d388e0317 100644 --- a/src/interface/efi/efi_fbcon.c +++ b/src/interface/efi/efi_fbcon.c @@ -65,6 +65,9 @@ struct console_driver efi_console __attribute__ (( weak )); /** Number of ASCII glyphs in cache */ #define EFIFB_ASCII 128 +/** Number of dynamic non-ASCII glyphs in cache */ +#define EFIFB_DYNAMIC 32 + /* Forward declaration */ struct console_driver efifb_console __console_driver; @@ -89,6 +92,10 @@ struct efifb { struct fbcon_font font; /** Character glyph cache */ userptr_t glyphs; + /** Dynamic characters in cache */ + unsigned int dynamic[EFIFB_DYNAMIC]; + /** Next dynamic character cache entry to evict */ + unsigned int next; }; /** The EFI frame buffer */ @@ -177,6 +184,41 @@ static int efifb_draw_unknown ( unsigned int index ) { return efifb_draw ( '?', index, -1U ); } +/** + * Get dynamic glyph index + * + * @v character Unicode character + * @ret index Glyph cache index + */ +static unsigned int efifb_dynamic ( unsigned int character ) { + unsigned int dynamic; + unsigned int index; + unsigned int i; + int height; + + /* Search existing cached entries */ + for ( i = 0 ; i < EFIFB_DYNAMIC ; i++ ) { + if ( character == efifb.dynamic[i] ) + return ( EFIFB_ASCII + i ); + } + + /* Overwrite the oldest cache entry */ + dynamic = ( efifb.next++ % EFIFB_DYNAMIC ); + index = ( EFIFB_ASCII + dynamic ); + DBGC2 ( &efifb, "EFIFB dynamic %#02x is glyph %#02x\n", + dynamic, character ); + + /* Draw glyph */ + height = efifb_draw ( character, index, 0 ); + if ( height < 0 ) + efifb_draw_unknown ( index ); + + /* Record cached character */ + efifb.dynamic[dynamic] = character; + + return index; +} + /** * Get character glyph * @@ -195,8 +237,8 @@ static void efifb_glyph ( unsigned int character, uint8_t *glyph ) { } else { - /* Non-ASCII character: use an "unknown" glyph */ - index = 0; + /* Non-ASCII character: use dynamic glyph cache */ + index = efifb_dynamic ( character ); } /* Copy cached glyph */ @@ -248,7 +290,7 @@ static int efifb_glyphs ( void ) { efifb.font.height = max; /* Allocate glyph data */ - len = ( EFIFB_ASCII * efifb.font.height ); + len = ( ( EFIFB_ASCII + EFIFB_DYNAMIC ) * efifb.font.height ); efifb.glyphs = umalloc ( len ); if ( ! efifb.glyphs ) { rc = -ENOMEM; @@ -273,6 +315,9 @@ static int efifb_glyphs ( void ) { } } + /* Clear dynamic glyph character cache */ + memset ( efifb.dynamic, 0, sizeof ( efifb.dynamic ) ); + efifb.font.glyph = efifb_glyph; return 0; -- cgit v1.2.3-55-g7522