From: Daan De Meyer Date: Mon, 11 May 2026 13:03:49 +0000 (+0000) Subject: boot,vconsole: Propagate UEFI HII keyboard layout to the OS X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=7b9d76cba72680ffad2672b7143d0dc1cea70231;p=thirdparty%2Fsystemd.git boot,vconsole: Propagate UEFI HII keyboard layout to the OS UEFI firmware can report the currently-active keyboard layout via EFI_HII_DATABASE_PROTOCOL.GetKeyboardLayout(). The layout descriptor includes an RFC 4646 / BCP 47 language tag (e.g. "en-US"). Query this from sd-boot/sd-stub and write it to a new LoaderKeyboardLayout EFI variable, advertised through a new EFI_LOADER_FEATURE_KEYBOARD_LAYOUT feature bit. On the OS side, systemd-vconsole-setup reads the variable as a lowest-priority fallback for the console keymap. To map the BCP 47 tag to a vconsole keymap we extend /usr/share/systemd/kbd-model-map with an optional sixth column listing the comma-separated BCP 47 tags each row covers; a new find_vconsole_keymap_for_bcp47() helper walks the file, preferring an exact tag match and otherwise falling back to the row whose tag matches the input's primary subtag. Credentials, /etc/vconsole.conf, and vconsole.keymap= on the kernel command line continue to take precedence. bootctl status surfaces the new variable, printing the language tag or "n/a (not reported by firmware)" when sd-boot advertises the feature but the firmware HII database didn't expose a layout (common on QEMU without a USB keyboard, since EDK2's PS/2 driver does not register an HII keyboard layout). --- diff --git a/docs/BOOT_LOADER_INTERFACE.md b/docs/BOOT_LOADER_INTERFACE.md index 5c2e74f2901..36380f38f33 100644 --- a/docs/BOOT_LOADER_INTERFACE.md +++ b/docs/BOOT_LOADER_INTERFACE.md @@ -143,6 +143,8 @@ Variables will be listed below using the Linux efivarfs naming, * `1 << 18` → The boot loader reports active TPM2 PCR banks in the EFI variable `LoaderTpm2ActivePcrBanks-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f`. * `1 << 19` → The boot loader supports the `LoaderEntryPreferred` variable when set. + * `1 << 20` → The boot loader reports the firmware-configured keyboard layout in the + EFI variable `LoaderKeyboardLayout-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f`. * The EFI variable `LoaderSystemToken-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f` contains binary random data, @@ -171,6 +173,17 @@ Variables will be listed below using the Linux efivarfs naming, the TCG EFI ProtocolSpecification for TPM 2.0 as `EFI_TCG2_BOOT_HASH_ALG_*`. If no TPM2 support or no active banks were detected, will be set to `0`. +* The EFI variable `LoaderKeyboardLayout-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f` + contains the RFC 4646 (BCP 47) language tag of the currently-active keyboard + layout as reported by the UEFI HII database (e.g. `en-US`, `de-DE`). + It is formatted as a NUL-terminated UTF-16 string. + The boot loader sets this variable from the layout returned by + `EFI_HII_DATABASE_PROTOCOL.GetKeyboardLayout()`, + if that protocol is implemented by the firmware. + Userspace (notably `systemd-vconsole-setup`) + uses this as a lowest-priority fallback keyboard layout + when no explicit configuration is provided. + If `LoaderTimeInitUSec` and `LoaderTimeExecUSec` are set, `systemd-analyze` will include them in its boot-time analysis. If `LoaderDevicePartUUID` is set, systemd will mount the ESP that was used for the boot to `/boot`, but only if diff --git a/man/systemd-vconsole-setup.service.xml b/man/systemd-vconsole-setup.service.xml index 87cb9e4777b..e6656eb5785 100644 --- a/man/systemd-vconsole-setup.service.xml +++ b/man/systemd-vconsole-setup.service.xml @@ -95,6 +95,20 @@ + + Firmware-provided keyboard layout + + If the boot loader reports the firmware-configured keyboard layout via the + LoaderKeyboardLayout EFI variable (see the + Boot Loader Interface), + systemd-vconsole-setup uses it as the lowest-priority fallback for the + keymap. The RFC 4646 / BCP 47 language tag reported by the firmware (e.g. de-DE) is + matched against the optional sixth column of /usr/share/systemd/kbd-model-map, + which lists the language tags each virtual-console keymap covers. Credentials, + /etc/vconsole.conf, and kernel command line options all override this + firmware-provided default. + + See Also diff --git a/man/vconsole.conf.xml b/man/vconsole.conf.xml index e5e160cf3d5..20b30d39948 100644 --- a/man/vconsole.conf.xml +++ b/man/vconsole.conf.xml @@ -56,6 +56,13 @@ might be checked for configuration of the virtual console as well, however only as fallback. + If the boot loader reports the firmware-configured keyboard layout via the + LoaderKeyboardLayout EFI variable (see the + Boot Loader Interface), + it is used as the lowest-priority fallback for KEYMAP=. + Any setting from credentials, /etc/vconsole.conf, or the kernel + command line overrides it. + /etc/vconsole.conf is usually created and updated using systemd-localed.service8. diff --git a/src/boot/boot.c b/src/boot/boot.c index a2a1becc9aa..8660814aaeb 100644 --- a/src/boot/boot.c +++ b/src/boot/boot.c @@ -3266,6 +3266,7 @@ static void export_loader_variables( EFI_LOADER_FEATURE_TYPE1_UKI | EFI_LOADER_FEATURE_TYPE1_UKI_URL | EFI_LOADER_FEATURE_TPM2_ACTIVE_PCR_BANKS | + EFI_LOADER_FEATURE_KEYBOARD_LAYOUT | 0; assert(loaded_image); diff --git a/src/boot/export-vars.c b/src/boot/export-vars.c index 5c037bdd252..aa49666f57c 100644 --- a/src/boot/export-vars.c +++ b/src/boot/export-vars.c @@ -3,6 +3,7 @@ #include "device-path-util.h" #include "efi-efivars.h" #include "export-vars.h" +#include "hii.h" #include "measure.h" #include "part-discovery.h" #include "url-discovery.h" @@ -60,4 +61,13 @@ void export_common_variables(EFI_LOADED_IMAGE_PROTOCOL *loaded_image) { s = xasprintf("0x%08x", active_pcr_banks); efivar_set_str16(MAKE_GUID_PTR(LOADER), u"LoaderTpm2ActivePcrBanks", s, 0); } + + /* Report the firmware's currently-active HII keyboard layout (as an RFC 4646 language tag, e.g. + * "de-DE"), so the OS can pick a matching console keymap. Best-effort: many firmwares do not + * implement the HII database protocol or expose no keyboard layout. */ + if (efivar_get_raw(MAKE_GUID_PTR(LOADER), u"LoaderKeyboardLayout", /* ret_data= */ NULL, /* ret_size= */ NULL) != EFI_SUCCESS) { + _cleanup_free_ char16_t *lang = hii_query_keyboard_layout_language(); + if (lang) + efivar_set_str16(MAKE_GUID_PTR(LOADER), u"LoaderKeyboardLayout", lang, /* flags= */ 0); + } } diff --git a/src/boot/hii.c b/src/boot/hii.c new file mode 100644 index 00000000000..84bf299c424 --- /dev/null +++ b/src/boot/hii.c @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "efi-log.h" +#include "hii.h" +#include "proto/hii-database.h" +#include "util.h" + +char16_t *hii_query_keyboard_layout_language(void) { + EFI_HII_DATABASE_PROTOCOL *hii_db = NULL; + EFI_STATUS err; + + err = BS->LocateProtocol(MAKE_GUID_PTR(EFI_HII_DATABASE_PROTOCOL), /* Registration= */ NULL, (void **) &hii_db); + if (err != EFI_SUCCESS) { + log_debug_status(err, "HII database protocol not available, ignoring: %m"); + return NULL; + } + + /* First call sizes the layout. We pass length=0 / buffer=NULL and expect EFI_BUFFER_TOO_SMALL. */ + uint16_t length = 0; + err = hii_db->GetKeyboardLayout(hii_db, /* KeyGuid= */ NULL, &length, /* KeyboardLayout= */ NULL); + if (err != EFI_BUFFER_TOO_SMALL) { + log_debug_status(err, "Initial GetKeyboardLayout did not report required buffer size, ignoring: %m"); + return NULL; + } + if (length < sizeof(EFI_HII_KEYBOARD_LAYOUT)) { + log_debug("Reported keyboard layout size %u is smaller than the header, ignoring.", length); + return NULL; + } + + _cleanup_free_ EFI_HII_KEYBOARD_LAYOUT *layout = xmalloc(length); + err = hii_db->GetKeyboardLayout(hii_db, /* KeyGuid= */ NULL, &length, layout); + if (err != EFI_SUCCESS) { + log_debug_status(err, "Failed to retrieve current keyboard layout, ignoring: %m"); + return NULL; + } + + if (length < sizeof(EFI_HII_KEYBOARD_LAYOUT)) { + log_debug("Reported keyboard layout size %u shrank below the header, ignoring.", length); + return NULL; + } + + if (layout->LayoutLength != length) { + log_debug("Keyboard layout reports inconsistent LayoutLength %u vs %u, ignoring.", + layout->LayoutLength, length); + return NULL; + } + + uint32_t off = layout->LayoutDescriptorStringOffset; + if (off > length || length - off < sizeof(EFI_DESCRIPTION_STRING_BUNDLE)) { + log_debug("Keyboard layout descriptor string offset %u out of bounds (length %u), ignoring.", + off, length); + return NULL; + } + + const EFI_DESCRIPTION_STRING_BUNDLE *bundle = (const EFI_DESCRIPTION_STRING_BUNDLE *) ((const uint8_t *) layout + off); + if (bundle->DescriptionCount == 0) { + log_debug("Keyboard layout has no description strings, ignoring."); + return NULL; + } + + /* Walk Strings[] looking for the U+0020 that terminates the first language tag. */ + size_t max_chars = (length - off - sizeof(EFI_DESCRIPTION_STRING_BUNDLE)) / sizeof(char16_t); + size_t n; + for (n = 0; n < max_chars; n++) + if (bundle->Strings[n] == u' ') + break; + if (n == max_chars) { + log_debug("Keyboard layout language tag is not terminated by a space, ignoring."); + return NULL; + } + if (n == 0) { + log_debug("Keyboard layout language tag is empty, ignoring."); + return NULL; + } + + char16_t *s = xnew(char16_t, n + 1); + memcpy(s, bundle->Strings, n * sizeof(char16_t)); + s[n] = u'\0'; + return s; +} diff --git a/src/boot/hii.h b/src/boot/hii.h new file mode 100644 index 00000000000..23ebcd30320 --- /dev/null +++ b/src/boot/hii.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "efi.h" + +/* Queries the firmware's HII database for the currently-active keyboard layout and returns the RFC 4646 + * language tag (e.g. u"de-DE") embedded in the layout description bundle. Returns NULL if the protocol + * is not provided, the table is malformed, or no language tag is present. */ +char16_t *hii_query_keyboard_layout_language(void); diff --git a/src/boot/meson.build b/src/boot/meson.build index 29fb64efbee..1b8f94e58a2 100644 --- a/src/boot/meson.build +++ b/src/boot/meson.build @@ -320,6 +320,7 @@ libefi_sources = files( 'efi-string.c', 'export-vars.c', 'graphics.c', + 'hii.c', 'initrd.c', 'measure.c', 'part-discovery.c', diff --git a/src/boot/proto/hii-database.h b/src/boot/proto/hii-database.h new file mode 100644 index 00000000000..7284704f00e --- /dev/null +++ b/src/boot/proto/hii-database.h @@ -0,0 +1,126 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "efi.h" + +#define EFI_HII_DATABASE_PROTOCOL_GUID \ + GUID_DEF(0xef9fc172, 0xa1b2, 0x4693, 0xb3, 0x27, 0x6d, 0x32, 0xfc, 0x41, 0x60, 0x42) + +typedef void *EFI_HII_HANDLE; + +typedef struct { + EFI_GUID PackageListGuid; + uint32_t PackageLength; +} EFI_HII_PACKAGE_LIST_HEADER; + +typedef struct _packed_ { + uint32_t LengthAndType; /* Length:24 | Type:8 (little-endian) */ +} EFI_HII_PACKAGE_HEADER; + +typedef size_t EFI_HII_DATABASE_NOTIFY_TYPE; + +typedef EFI_STATUS (EFIAPI *EFI_HII_DATABASE_NOTIFY)( + uint8_t PackageType, + EFI_GUID *PackageGuid, + EFI_HII_PACKAGE_HEADER *Package, + EFI_HII_HANDLE Handle, + EFI_HII_DATABASE_NOTIFY_TYPE NotifyType); + +typedef struct EFI_HII_DATABASE_PROTOCOL EFI_HII_DATABASE_PROTOCOL; + +struct EFI_HII_DATABASE_PROTOCOL { + EFI_STATUS (EFIAPI *NewPackageList)( + EFI_HII_DATABASE_PROTOCOL *This, + EFI_HII_PACKAGE_LIST_HEADER *PackageList, + EFI_HANDLE DriverHandle, + EFI_HII_HANDLE *Handle); + + EFI_STATUS (EFIAPI *RemovePackageList)( + EFI_HII_DATABASE_PROTOCOL *This, + EFI_HII_HANDLE Handle); + + EFI_STATUS (EFIAPI *UpdatePackageList)( + EFI_HII_DATABASE_PROTOCOL *This, + EFI_HII_HANDLE Handle, + EFI_HII_PACKAGE_LIST_HEADER *PackageList); + + EFI_STATUS (EFIAPI *ListPackageLists)( + EFI_HII_DATABASE_PROTOCOL *This, + uint8_t PackageType, + EFI_GUID *PackageGuid, + size_t *HandleBufferLength, + EFI_HII_HANDLE *Handle); + + EFI_STATUS (EFIAPI *ExportPackageLists)( + EFI_HII_DATABASE_PROTOCOL *This, + EFI_HII_HANDLE Handle, + size_t *BufferSize, + EFI_HII_PACKAGE_LIST_HEADER *Buffer); + + EFI_STATUS (EFIAPI *RegisterPackageNotify)( + EFI_HII_DATABASE_PROTOCOL *This, + uint8_t PackageType, + EFI_GUID *PackageGuid, + EFI_HII_DATABASE_NOTIFY PackageNotifyFn, + EFI_HII_DATABASE_NOTIFY_TYPE NotifyType, + EFI_HANDLE *NotifyHandle); + + EFI_STATUS (EFIAPI *UnregisterPackageNotify)( + EFI_HII_DATABASE_PROTOCOL *This, + EFI_HANDLE NotificationHandle); + + EFI_STATUS (EFIAPI *FindKeyboardLayouts)( + EFI_HII_DATABASE_PROTOCOL *This, + uint16_t *KeyGuidBufferLength, + EFI_GUID *KeyGuidBuffer); + + EFI_STATUS (EFIAPI *GetKeyboardLayout)( + EFI_HII_DATABASE_PROTOCOL *This, + EFI_GUID *KeyGuid, + uint16_t *KeyboardLayoutLength, + void *KeyboardLayout); + + EFI_STATUS (EFIAPI *SetKeyboardLayout)( + EFI_HII_DATABASE_PROTOCOL *This, + EFI_GUID *KeyGuid); + + EFI_STATUS (EFIAPI *GetPackageListHandle)( + EFI_HII_DATABASE_PROTOCOL *This, + EFI_HII_HANDLE PackageListHandle, + EFI_HANDLE *DriverHandle); +}; + +/* EFI_HII_KEYBOARD_LAYOUT and EFI_KEY_DESCRIPTOR are packed: LayoutDescriptorStringOffset follows + * a 16-byte EFI_GUID at offset 2, so it is at offset 18 — *not* a natural 4-byte alignment. */ +typedef struct _packed_ { + uint16_t LayoutLength; + EFI_GUID Guid; + uint32_t LayoutDescriptorStringOffset; + uint8_t DescriptorCount; + /* EFI_KEY_DESCRIPTOR Descriptors[DescriptorCount] follows here, then at + * LayoutDescriptorStringOffset (from the start of this struct) the description-string bundle. */ +} EFI_HII_KEYBOARD_LAYOUT; + +typedef struct _packed_ { + uint32_t Key; + char16_t Unicode; + char16_t ShiftedUnicode; + char16_t AltGrUnicode; + char16_t ShiftedAltGrUnicode; + uint16_t Modifier; + uint16_t AffectedAttribute; +} EFI_KEY_DESCRIPTOR; + +/* The description-string bundle that LayoutDescriptorStringOffset points to. After DescriptionCount, + * each of the DescriptionCount entries is laid out as: + * + * CHAR16 Language[]; // RFC 4646 tag, terminated by the Space below (no NUL) + * CHAR16 Space; // U+0020 + * CHAR16 DescriptionString[]; // NUL-terminated UTF-16 description + * + * Despite what the UEFI spec text says, Language is encoded as UTF-16 (CHAR16) in practice — see EDK2 + * MdeModulePkg/Bus/Usb/UsbKbDxe/KeyBoard.h USB_KEYBOARD_LAYOUT_PACK_BIN. */ +typedef struct _packed_ { + uint16_t DescriptionCount; + char16_t Strings[]; +} EFI_DESCRIPTION_STRING_BUNDLE; diff --git a/src/bootctl/bootctl-status.c b/src/bootctl/bootctl-status.c index 2c0eb4d1d00..4cd0e3aca0e 100644 --- a/src/bootctl/bootctl-status.c +++ b/src/bootctl/bootctl-status.c @@ -320,7 +320,7 @@ static int efi_get_variable_path_and_warn(const char *variable, char **ret) { static void print_yes_no_line(bool first, bool good, const char *name) { printf("%s%s %s\n", - first ? " Features: " : " ", + first ? " Features: " : " ", COLOR_MARK_BOOL(good), name); } @@ -387,25 +387,26 @@ int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { uint64_t flag; const char *name; } loader_flags[] = { - { EFI_LOADER_FEATURE_BOOT_COUNTING, "Boot counting" }, - { EFI_LOADER_FEATURE_CONFIG_TIMEOUT, "Menu timeout control" }, - { EFI_LOADER_FEATURE_CONFIG_TIMEOUT_ONE_SHOT, "One-shot menu timeout control" }, - { EFI_LOADER_FEATURE_ENTRY_DEFAULT, "Default entry control" }, - { EFI_LOADER_FEATURE_ENTRY_ONESHOT, "One-shot entry control" }, - { EFI_LOADER_FEATURE_XBOOTLDR, "Support for XBOOTLDR partition" }, - { EFI_LOADER_FEATURE_RANDOM_SEED, "Support for passing random seed to OS" }, - { EFI_LOADER_FEATURE_LOAD_DRIVER, "Load drop-in drivers" }, - { EFI_LOADER_FEATURE_SORT_KEY, "Support Type #1 sort-key field" }, - { EFI_LOADER_FEATURE_SAVED_ENTRY, "Support @saved pseudo-entry" }, - { EFI_LOADER_FEATURE_DEVICETREE, "Support Type #1 devicetree field" }, - { EFI_LOADER_FEATURE_SECUREBOOT_ENROLL, "Enroll SecureBoot keys" }, - { EFI_LOADER_FEATURE_RETAIN_SHIM, "Retain SHIM protocols" }, - { EFI_LOADER_FEATURE_MENU_DISABLE, "Menu can be disabled" }, - { EFI_LOADER_FEATURE_MULTI_PROFILE_UKI, "Multi-Profile UKIs are supported" }, - { EFI_LOADER_FEATURE_REPORT_URL, "Loader reports network boot URL" }, - { EFI_LOADER_FEATURE_TYPE1_UKI, "Support Type #1 uki field" }, - { EFI_LOADER_FEATURE_TYPE1_UKI_URL, "Support Type #1 uki-url field" }, - { EFI_LOADER_FEATURE_TPM2_ACTIVE_PCR_BANKS, "Loader reports active TPM2 PCR banks" }, + { EFI_LOADER_FEATURE_BOOT_COUNTING, "Boot counting" }, + { EFI_LOADER_FEATURE_CONFIG_TIMEOUT, "Menu timeout control" }, + { EFI_LOADER_FEATURE_CONFIG_TIMEOUT_ONE_SHOT, "One-shot menu timeout control" }, + { EFI_LOADER_FEATURE_ENTRY_DEFAULT, "Default entry control" }, + { EFI_LOADER_FEATURE_ENTRY_ONESHOT, "One-shot entry control" }, + { EFI_LOADER_FEATURE_XBOOTLDR, "Support for XBOOTLDR partition" }, + { EFI_LOADER_FEATURE_RANDOM_SEED, "Support for passing random seed to OS" }, + { EFI_LOADER_FEATURE_LOAD_DRIVER, "Load drop-in drivers" }, + { EFI_LOADER_FEATURE_SORT_KEY, "Support Type #1 sort-key field" }, + { EFI_LOADER_FEATURE_SAVED_ENTRY, "Support @saved pseudo-entry" }, + { EFI_LOADER_FEATURE_DEVICETREE, "Support Type #1 devicetree field" }, + { EFI_LOADER_FEATURE_SECUREBOOT_ENROLL, "Enroll SecureBoot keys" }, + { EFI_LOADER_FEATURE_RETAIN_SHIM, "Retain SHIM protocols" }, + { EFI_LOADER_FEATURE_MENU_DISABLE, "Menu can be disabled" }, + { EFI_LOADER_FEATURE_MULTI_PROFILE_UKI, "Multi-Profile UKIs are supported" }, + { EFI_LOADER_FEATURE_REPORT_URL, "Loader reports network boot URL" }, + { EFI_LOADER_FEATURE_TYPE1_UKI, "Support Type #1 uki field" }, + { EFI_LOADER_FEATURE_TYPE1_UKI_URL, "Support Type #1 uki-url field" }, + { EFI_LOADER_FEATURE_TPM2_ACTIVE_PCR_BANKS, "Loader reports active TPM2 PCR banks" }, + { EFI_LOADER_FEATURE_KEYBOARD_LAYOUT, "Loader reports firmware keyboard layout" }, }; static const struct { uint64_t flag; @@ -426,7 +427,7 @@ int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { }; _cleanup_free_ char *fw_type = NULL, *fw_info = NULL, *loader = NULL, *loader_path = NULL, *stub = NULL, *stub_path = NULL, *current_entry = NULL, *oneshot_entry = NULL, *preferred_entry = NULL, *default_entry = NULL, *sysfail_entry = NULL, - *sysfail_reason = NULL; + *sysfail_reason = NULL, *keyboard_layout = NULL; uint64_t loader_features = 0, stub_features = 0; int have; @@ -444,6 +445,7 @@ int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { (void) efi_get_variable_string_and_warn(EFI_LOADER_VARIABLE_STR("LoaderEntryDefault"), &default_entry); (void) efi_get_variable_string_and_warn(EFI_LOADER_VARIABLE_STR("LoaderEntrySysFail"), &sysfail_entry); (void) efi_get_variable_string_and_warn(EFI_LOADER_VARIABLE_STR("LoaderSysFailReason"), &sysfail_reason); + (void) efi_get_variable_string_and_warn(EFI_LOADER_VARIABLE_STR("LoaderKeyboardLayout"), &keyboard_layout); SecureBootMode secure = efi_get_secure_boot_mode(); printf("%sSystem:%s\n", ansi_underline(), ansi_normal()); @@ -503,7 +505,7 @@ int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { if (loader) { printf("%sCurrent Boot Loader:%s\n", ansi_underline(), ansi_normal()); - printf(" Product: %s%s%s\n", ansi_highlight(), loader, ansi_normal()); + printf(" Product: %s%s%s\n", ansi_highlight(), loader, ansi_normal()); for (size_t i = 0; i < ELEMENTSOF(loader_flags); i++) print_yes_no_line(i == 0, FLAGS_SET(loader_features, loader_flags[i].flag), loader_flags[i].name); @@ -521,38 +523,42 @@ int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { SD_ID128_FORMAT_VAL(loader_partition_uuid), SD_ID128_FORMAT_VAL(esp_uuid)); - printf(" Partition: /dev/disk/by-partuuid/" SD_ID128_UUID_FORMAT_STR "\n", + printf(" Partition: /dev/disk/by-partuuid/" SD_ID128_UUID_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(loader_partition_uuid)); } else if (loader_path) - printf(" Partition: n/a\n"); + printf(" Partition: n/a\n"); if (loader_path) - printf(" Loader: %s%s%s/%s%s\n", + printf(" Loader: %s%s%s/%s%s\n", glyph(GLYPH_TREE_RIGHT), ansi_grey(), arg_esp_path, ansi_normal(), loader_path); if (loader_url) - printf(" Net Boot URL: %s\n", loader_url); + printf(" Net Boot URL: %s\n", loader_url); + + if (FLAGS_SET(loader_features, EFI_LOADER_FEATURE_KEYBOARD_LAYOUT)) + printf("Keyboard Layout: %s\n", + keyboard_layout ?: "n/a (not reported by firmware)"); if (sysfail_entry) - printf("SysFail Reason: %s\n", sysfail_reason); + printf(" SysFail Reason: %s\n", sysfail_reason); if (current_entry) - printf(" Current Entry: %s\n", current_entry); + printf(" Current Entry: %s\n", current_entry); if (preferred_entry) - printf(" Preferred Entry: %s\n", preferred_entry); + printf("Preferred Entry: %s\n", preferred_entry); if (default_entry) - printf(" Default Entry: %s\n", default_entry); + printf(" Default Entry: %s\n", default_entry); if (oneshot_entry && !streq_ptr(oneshot_entry, default_entry)) - printf(" OneShot Entry: %s\n", oneshot_entry); + printf(" OneShot Entry: %s\n", oneshot_entry); if (sysfail_entry) - printf(" SysFail Entry: %s\n", sysfail_entry); + printf(" SysFail Entry: %s\n", sysfail_entry); printf("\n"); } if (stub) { printf("%sCurrent Stub:%s\n", ansi_underline(), ansi_normal()); - printf(" Product: %s%s%s\n", ansi_highlight(), stub, ansi_normal()); + printf(" Product: %s%s%s\n", ansi_highlight(), stub, ansi_normal()); for (size_t i = 0; i < ELEMENTSOF(stub_flags); i++) print_yes_no_line(i == 0, FLAGS_SET(stub_features, stub_flags[i].flag), stub_flags[i].name); @@ -573,16 +579,16 @@ int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { SD_ID128_FORMAT_VAL(esp_uuid), SD_ID128_FORMAT_VAL(xbootldr_uuid)); - printf(" Partition: /dev/disk/by-partuuid/" SD_ID128_UUID_FORMAT_STR "\n", + printf(" Partition: /dev/disk/by-partuuid/" SD_ID128_UUID_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(stub_partition_uuid)); } else if (stub_path) - printf(" Partition: n/a\n"); + printf(" Partition: n/a\n"); if (stub_path) - printf(" Stub: %s%s\n", glyph(GLYPH_TREE_RIGHT), strna(stub_path)); + printf(" Stub: %s%s\n", glyph(GLYPH_TREE_RIGHT), strna(stub_path)); if (stub_url) - printf(" Net Boot URL: %s\n", stub_url); + printf(" Net Boot URL: %s\n", stub_url); printf("\n"); } diff --git a/src/fundamental/efivars-fundamental.h b/src/fundamental/efivars-fundamental.h index 15be52119a0..fea23fa29c1 100644 --- a/src/fundamental/efivars-fundamental.h +++ b/src/fundamental/efivars-fundamental.h @@ -29,6 +29,7 @@ #define EFI_LOADER_FEATURE_TYPE1_UKI_URL (UINT64_C(1) << 17) #define EFI_LOADER_FEATURE_TPM2_ACTIVE_PCR_BANKS (UINT64_C(1) << 18) #define EFI_LOADER_FEATURE_ENTRY_PREFERRED (UINT64_C(1) << 19) +#define EFI_LOADER_FEATURE_KEYBOARD_LAYOUT (UINT64_C(1) << 20) /* Features of the stub, i.e. systemd-stub */ #define EFI_STUB_FEATURE_REPORT_BOOT_PARTITION (UINT64_C(1) << 0) diff --git a/src/locale/kbd-model-map b/src/locale/kbd-model-map index 612f6d749a7..c0ef480530a 100644 --- a/src/locale/kbd-model-map +++ b/src/locale/kbd-model-map @@ -1,73 +1,76 @@ # Originally generated from system-config-keyboard's model list. -# consolelayout xlayout xmodel xvariant xoptions -sg ch pc105 de_nodeadkeys terminate:ctrl_alt_bksp -nl nl pc105 - terminate:ctrl_alt_bksp -mk-utf mk,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll -trq tr pc105 - terminate:ctrl_alt_bksp -uk gb pc105 - terminate:ctrl_alt_bksp -is-latin1 is pc105 - terminate:ctrl_alt_bksp -de de pc105 - terminate:ctrl_alt_bksp -la-latin1 latam pc105 - terminate:ctrl_alt_bksp -us us pc105+inet - terminate:ctrl_alt_bksp -ko kr pc105 - terminate:ctrl_alt_bksp -ro-std ro pc105 std terminate:ctrl_alt_bksp -de-latin1 de pc105 - terminate:ctrl_alt_bksp -slovene si pc105 - terminate:ctrl_alt_bksp -hu hu pc105 - terminate:ctrl_alt_bksp -jp106 jp jp106 - terminate:ctrl_alt_bksp -croat hr pc105 - terminate:ctrl_alt_bksp -it2 it pc105 - terminate:ctrl_alt_bksp -hu101 hu pc105 qwerty terminate:ctrl_alt_bksp -sr-latin rs pc105 latin terminate:ctrl_alt_bksp -fi fi pc105 - terminate:ctrl_alt_bksp -fr_CH ch pc105 fr terminate:ctrl_alt_bksp -dk-latin1 dk pc105 - terminate:ctrl_alt_bksp -fr fr pc105 - terminate:ctrl_alt_bksp -it it pc105 - terminate:ctrl_alt_bksp -ua-utf ua,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll -fr-latin1 fr pc105 - terminate:ctrl_alt_bksp -sg-latin1 ch pc105 de_nodeadkeys terminate:ctrl_alt_bksp -be-latin1 be pc105 - terminate:ctrl_alt_bksp -dk dk pc105 - terminate:ctrl_alt_bksp -fr-pc fr pc105 - terminate:ctrl_alt_bksp -bg_pho-utf8 bg,us pc105 ,phonetic terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll -it-ibm it pc105 - terminate:ctrl_alt_bksp -cz-us-qwertz cz,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll -cz-qwerty cz,us pc105 qwerty, terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll -br-abnt2 br abnt2 - terminate:ctrl_alt_bksp -ro ro pc105 - terminate:ctrl_alt_bksp -us-acentos us pc105 intl terminate:ctrl_alt_bksp -pt-latin1 pt pc105 - terminate:ctrl_alt_bksp -ro-std-cedilla ro pc105 std_cedilla terminate:ctrl_alt_bksp -tj_alt-UTF8 tj pc105 - terminate:ctrl_alt_bksp -de-latin1-nodeadkeys de pc105 nodeadkeys terminate:ctrl_alt_bksp -no no pc105 - terminate:ctrl_alt_bksp -bg_bds-utf8 bg,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll -dvorak us pc105 dvorak terminate:ctrl_alt_bksp -dvorak us pc105 dvorak-alt-intl terminate:ctrl_alt_bksp -ru ru,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll -cz-lat2 cz pc105 qwerty terminate:ctrl_alt_bksp -pl2 pl pc105 - terminate:ctrl_alt_bksp -es es pc105 - terminate:ctrl_alt_bksp -ro-cedilla ro pc105 cedilla terminate:ctrl_alt_bksp -ie ie pc105 - terminate:ctrl_alt_bksp -et ee pc105 - terminate:ctrl_alt_bksp -sk-qwerty sk pc105 qwerty terminate:ctrl_alt_bksp -sk-qwertz sk pc105 - terminate:ctrl_alt_bksp -fr-latin9 fr pc105 latin9 terminate:ctrl_alt_bksp -fr_CH-latin1 ch pc105 fr terminate:ctrl_alt_bksp -cf ca pc105 - terminate:ctrl_alt_bksp -sv-latin1 se pc105 - terminate:ctrl_alt_bksp -sr-cy rs pc105 - terminate:ctrl_alt_bksp -gr gr,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll -by by,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll -il il pc105 - terminate:ctrl_alt_bksp -kazakh kz,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll -lt.baltic lt pc105 - terminate:ctrl_alt_bksp -lt.l4 lt pc105 - terminate:ctrl_alt_bksp -lt lt pc105 - terminate:ctrl_alt_bksp -khmer kh,us pc105 - terminate:ctrl_alt_bksp -es-dvorak es microsoftpro dvorak terminate:ctrl_alt_bksp -lv lv pc105 apostrophe terminate:ctrl_alt_bksp -lv-tilde lv pc105 tilde terminate:ctrl_alt_bksp -ge ge,us pc105 - terminate:ctrl_alt_bksp +# The sixth column is an optional comma-separated list of RFC 4646 / BCP 47 +# language tags the row matches; used to map a firmware-reported keyboard +# layout to a vconsole keymap. Use "-" or omit when no tags apply. +# consolelayout xlayout xmodel xvariant xoptions bcp47 +sg ch pc105 de_nodeadkeys terminate:ctrl_alt_bksp de-CH +nl nl pc105 - terminate:ctrl_alt_bksp nl-NL,nl +mk-utf mk,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll mk-MK,mk +trq tr pc105 - terminate:ctrl_alt_bksp tr-TR,tr +uk gb pc105 - terminate:ctrl_alt_bksp en-GB +is-latin1 is pc105 - terminate:ctrl_alt_bksp is-IS,is +de de pc105 - terminate:ctrl_alt_bksp de-DE,de-AT,de +la-latin1 latam pc105 - terminate:ctrl_alt_bksp es-419,es-MX,es-AR,es-CO,es-CL,es-PE,es-VE +us us pc105+inet - terminate:ctrl_alt_bksp en-US,en +ko kr pc105 - terminate:ctrl_alt_bksp ko-KR,ko +ro-std ro pc105 std terminate:ctrl_alt_bksp - +de-latin1 de pc105 - terminate:ctrl_alt_bksp - +slovene si pc105 - terminate:ctrl_alt_bksp sl-SI,sl +hu hu pc105 - terminate:ctrl_alt_bksp hu-HU,hu +jp106 jp jp106 - terminate:ctrl_alt_bksp ja-JP,ja +croat hr pc105 - terminate:ctrl_alt_bksp hr-HR,hr +it2 it pc105 - terminate:ctrl_alt_bksp - +hu101 hu pc105 qwerty terminate:ctrl_alt_bksp - +sr-latin rs pc105 latin terminate:ctrl_alt_bksp sr-Latn-RS,sr-Latn +fi fi pc105 - terminate:ctrl_alt_bksp fi-FI,fi +fr_CH ch pc105 fr terminate:ctrl_alt_bksp fr-CH +dk-latin1 dk pc105 - terminate:ctrl_alt_bksp da-DK,da +fr fr pc105 - terminate:ctrl_alt_bksp fr-FR,fr +it it pc105 - terminate:ctrl_alt_bksp it-IT,it-CH,it +ua-utf ua,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll uk-UA,uk +fr-latin1 fr pc105 - terminate:ctrl_alt_bksp - +sg-latin1 ch pc105 de_nodeadkeys terminate:ctrl_alt_bksp - +be-latin1 be pc105 - terminate:ctrl_alt_bksp fr-BE,nl-BE +dk dk pc105 - terminate:ctrl_alt_bksp - +fr-pc fr pc105 - terminate:ctrl_alt_bksp - +bg_pho-utf8 bg,us pc105 ,phonetic terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll bg-BG,bg +it-ibm it pc105 - terminate:ctrl_alt_bksp - +cz-us-qwertz cz,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll cs-CZ,cs +cz-qwerty cz,us pc105 qwerty, terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll - +br-abnt2 br abnt2 - terminate:ctrl_alt_bksp pt-BR +ro ro pc105 - terminate:ctrl_alt_bksp ro-RO,ro +us-acentos us pc105 intl terminate:ctrl_alt_bksp - +pt-latin1 pt pc105 - terminate:ctrl_alt_bksp pt-PT,pt +ro-std-cedilla ro pc105 std_cedilla terminate:ctrl_alt_bksp - +tj_alt-UTF8 tj pc105 - terminate:ctrl_alt_bksp tg-TJ,tg +de-latin1-nodeadkeys de pc105 nodeadkeys terminate:ctrl_alt_bksp - +no no pc105 - terminate:ctrl_alt_bksp nb-NO,nn-NO,no +bg_bds-utf8 bg,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll - +dvorak us pc105 dvorak terminate:ctrl_alt_bksp - +dvorak us pc105 dvorak-alt-intl terminate:ctrl_alt_bksp - +ru ru,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll ru-RU,ru +cz-lat2 cz pc105 qwerty terminate:ctrl_alt_bksp - +pl2 pl pc105 - terminate:ctrl_alt_bksp pl-PL,pl +es es pc105 - terminate:ctrl_alt_bksp es-ES,es +ro-cedilla ro pc105 cedilla terminate:ctrl_alt_bksp - +ie ie pc105 - terminate:ctrl_alt_bksp en-IE,ga-IE,ga +et ee pc105 - terminate:ctrl_alt_bksp et-EE,et +sk-qwerty sk pc105 qwerty terminate:ctrl_alt_bksp - +sk-qwertz sk pc105 - terminate:ctrl_alt_bksp sk-SK,sk +fr-latin9 fr pc105 latin9 terminate:ctrl_alt_bksp - +fr_CH-latin1 ch pc105 fr terminate:ctrl_alt_bksp - +cf ca pc105 - terminate:ctrl_alt_bksp fr-CA +sv-latin1 se pc105 - terminate:ctrl_alt_bksp sv-SE,sv +sr-cy rs pc105 - terminate:ctrl_alt_bksp sr-Cyrl-RS,sr-Cyrl,sr-RS,sr +gr gr,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll el-GR,el +by by,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll be-BY,be +il il pc105 - terminate:ctrl_alt_bksp he-IL,he +kazakh kz,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll kk-KZ,kk +lt.baltic lt pc105 - terminate:ctrl_alt_bksp - +lt.l4 lt pc105 - terminate:ctrl_alt_bksp - +lt lt pc105 - terminate:ctrl_alt_bksp lt-LT,lt +khmer kh,us pc105 - terminate:ctrl_alt_bksp km-KH,km +es-dvorak es microsoftpro dvorak terminate:ctrl_alt_bksp - +lv lv pc105 apostrophe terminate:ctrl_alt_bksp lv-LV,lv +lv-tilde lv pc105 tilde terminate:ctrl_alt_bksp - +ge ge,us pc105 - terminate:ctrl_alt_bksp ka-GE,ka diff --git a/src/shared/vconsole-util.c b/src/shared/vconsole-util.c index 6e8c17561e8..aa156f4736f 100644 --- a/src/shared/vconsole-util.c +++ b/src/shared/vconsole-util.c @@ -578,6 +578,83 @@ int find_language_fallback(const char *lang, char **ret) { } } +int find_vconsole_keymap_for_bcp47(const char *tag, char **ret) { + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *fallback = NULL; + const char *map; + int r; + + /* Look up a vconsole keymap by RFC 4646 / BCP 47 language tag (e.g. "de-DE") using the optional + * sixth column of /usr/share/systemd/kbd-model-map. That column lists comma-separated tags the + * row matches. An exact (case-insensitive) tag match returns immediately; if no exact match + * exists, the first row whose tag matches the input's primary subtag wins. Returns 1 on match, + * 0 otherwise. */ + + assert(tag); + assert(ret); + + if (isempty(tag)) { + *ret = NULL; + return 0; + } + + size_t primary_len = strcspn(tag, "-"); + if (primary_len == 0) { + *ret = NULL; + return 0; + } + + map = systemd_kbd_model_map(); + f = fopen(map, "re"); + if (!f) + return -errno; + + for (unsigned n = 0;;) { + _cleanup_strv_free_ char **a = NULL, **tags = NULL; + + r = read_next_mapping(map, 5, UINT_MAX, f, &n, &a); + if (r < 0) + return r; + if (r == 0) + break; + + /* The BCP 47 tag list is the optional 6th column. "-" / empty means "no tags". */ + if (strv_length(a) < 6 || isempty(a[5]) || streq(a[5], "-")) + continue; + + r = strv_split_full(&tags, a[5], ",", /* flags= */ 0); + if (r < 0) + return r; + + STRV_FOREACH(t, tags) { + if (strcaseeq(*t, tag)) { + log_debug("Found vconsole keymap '%s' for BCP 47 tag '%s' (exact match).", + a[0], tag); + + r = strdup_to(ret, a[0]); + if (r < 0) + return r; + + return 1; + } + if (!fallback && strlen(*t) == primary_len && !strchr(*t, '-') && strncaseeq(*t, tag, primary_len)) { + fallback = strdup(a[0]); + if (!fallback) + return -ENOMEM; + } + } + } + + if (!fallback) { + *ret = NULL; + return 0; + } + + log_debug("Found vconsole keymap '%s' for BCP 47 tag '%s' (primary subtag match).", fallback, tag); + *ret = TAKE_PTR(fallback); + return 1; +} + int vconsole_serialize(const VCContext *vc, const X11Context *xc, char ***env) { int r; diff --git a/src/shared/vconsole-util.h b/src/shared/vconsole-util.h index 494bc6ea489..3d52ec9866c 100644 --- a/src/shared/vconsole-util.h +++ b/src/shared/vconsole-util.h @@ -39,4 +39,6 @@ typedef int (*X11VerifyCallback)(const X11Context *xc); int vconsole_convert_to_x11(const VCContext *vc, X11VerifyCallback verify, X11Context *ret); int x11_convert_to_vconsole(const X11Context *xc, VCContext *ret); +int find_vconsole_keymap_for_bcp47(const char *tag, char **ret); + int vconsole_serialize(const VCContext *vc, const X11Context *xc, char ***env); diff --git a/src/vconsole/vconsole-setup.c b/src/vconsole/vconsole-setup.c index 73bf240cf51..c1bf2b0f4f5 100644 --- a/src/vconsole/vconsole-setup.c +++ b/src/vconsole/vconsole-setup.c @@ -15,6 +15,7 @@ #include "alloc-util.h" #include "creds-util.h" +#include "efivars.h" #include "env-file.h" #include "errno-util.h" #include "fd-util.h" @@ -30,6 +31,7 @@ #include "string-util.h" #include "strv.h" #include "terminal-util.h" +#include "vconsole-util.h" typedef struct Context { char *keymap; @@ -72,6 +74,34 @@ static void context_merge_config( context_merge(dst, src, src_compat, font_unimap); } +static int context_read_efi(Context *c) { + _cleanup_(context_done) Context v = {}; + _cleanup_free_ char *tag = NULL; + int r; + + assert(c); + + if (!is_efi_boot()) + return 0; + + r = efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderKeyboardLayout"), &tag); + if (r == -ENOENT) + return 0; + if (r < 0) + return log_debug_errno(r, "Failed to read LoaderKeyboardLayout EFI variable, ignoring: %m"); + + r = find_vconsole_keymap_for_bcp47(tag, &v.keymap); + if (r < 0) + return log_debug_errno(r, "Failed to look up vconsole keymap for firmware tag '%s', ignoring: %m", tag); + if (r == 0) { + log_debug("No vconsole keymap matches firmware-provided keyboard layout '%s', ignoring.", tag); + return 0; + } + + context_merge_config(c, &v, /* src_compat= */ NULL); + return 0; +} + static int context_read_creds(Context *c) { _cleanup_(context_done) Context v = {}; int r; @@ -144,10 +174,13 @@ static int context_read_proc_cmdline(Context *c) { static void context_load_config(Context *c) { assert(c); - /* Load data from credentials (lowest priority) */ + /* Pick up the firmware-provided keyboard layout if any (lowest priority) */ + (void) context_read_efi(c); + + /* Load data from credentials */ (void) context_read_creds(c); - /* Load data from configuration file (middle priority) */ + /* Load data from configuration file */ (void) context_read_env(c); /* Let the kernel command line override /etc/vconsole.conf (highest priority) */