]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
boot,vconsole: Propagate UEFI HII keyboard layout to the OS
authorDaan De Meyer <daan@amutable.com>
Mon, 11 May 2026 13:03:49 +0000 (13:03 +0000)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Mon, 11 May 2026 19:10:11 +0000 (21:10 +0200)
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).

15 files changed:
docs/BOOT_LOADER_INTERFACE.md
man/systemd-vconsole-setup.service.xml
man/vconsole.conf.xml
src/boot/boot.c
src/boot/export-vars.c
src/boot/hii.c [new file with mode: 0644]
src/boot/hii.h [new file with mode: 0644]
src/boot/meson.build
src/boot/proto/hii-database.h [new file with mode: 0644]
src/bootctl/bootctl-status.c
src/fundamental/efivars-fundamental.h
src/locale/kbd-model-map
src/shared/vconsole-util.c
src/shared/vconsole-util.h
src/vconsole/vconsole-setup.c

index 5c2e74f29011df553d5a0bc1f5a8a900141145af..36380f38f3328eb9b140c42f1116cc1ad28802a5 100644 (file)
@@ -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
index 87cb9e4777bb49cb26251b1f89f9cc1892243bde..e6656eb5785453a99265a09e9aff151dd477f2ff 100644 (file)
     </variablelist>
   </refsect1>
 
+  <refsect1>
+    <title>Firmware-provided keyboard layout</title>
+
+    <para>If the boot loader reports the firmware-configured keyboard layout via the
+    <varname>LoaderKeyboardLayout</varname> EFI variable (see the
+    <ulink url="https://systemd.io/BOOT_LOADER_INTERFACE">Boot Loader Interface</ulink>),
+    <command>systemd-vconsole-setup</command> uses it as the lowest-priority fallback for the
+    keymap. The RFC 4646 / BCP 47 language tag reported by the firmware (e.g. <literal>de-DE</literal>) is
+    matched against the optional sixth column of <filename>/usr/share/systemd/kbd-model-map</filename>,
+    which lists the language tags each virtual-console keymap covers. Credentials,
+    <filename>/etc/vconsole.conf</filename>, and kernel command line options all override this
+    firmware-provided default.</para>
+  </refsect1>
+
   <refsect1>
     <title>See Also</title>
     <para><simplelist type="inline">
index e5e160cf3d55d0e8573a1f83cd1beac73b969b44..20b30d39948f451414c94c984490be74b0b728b9 100644 (file)
     might be checked for configuration of the virtual console as well,
     however only as fallback.</para>
 
+    <para>If the boot loader reports the firmware-configured keyboard layout via the
+    <varname>LoaderKeyboardLayout</varname> EFI variable (see the
+    <ulink url="https://systemd.io/BOOT_LOADER_INTERFACE">Boot Loader Interface</ulink>),
+    it is used as the lowest-priority fallback for <varname>KEYMAP=</varname>.
+    Any setting from credentials, <filename>/etc/vconsole.conf</filename>, or the kernel
+    command line overrides it.</para>
+
     <para><filename>/etc/vconsole.conf</filename> is usually created and updated
     using
     <citerefentry><refentrytitle>systemd-localed.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
index a2a1becc9aaa092349002ee715949089910b102b..8660814aaebd63d7ee1eab031f498ed148523f73 100644 (file)
@@ -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);
index 5c037bdd25235967712da3fa063a5b9ead8f7923..aa49666f57ce46a532843a55378bd8e05857118d 100644 (file)
@@ -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 (file)
index 0000000..84bf299
--- /dev/null
@@ -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 (file)
index 0000000..23ebcd3
--- /dev/null
@@ -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);
index 29fb64efbee1b52a932130bd59959a68fc16c946..1b8f94e58a247273874a2d9887e2b6c195751bc3 100644 (file)
@@ -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 (file)
index 0000000..7284704
--- /dev/null
@@ -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;
index 2c0eb4d1d00d485c3c09882ea2efd19e777852e9..4cd0e3aca0e750d9187294bdb9d0ce921b24c8d0 100644 (file)
@@ -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");
                 }
index 15be52119a0a2dd2d141e5e5717256e770e1e881..fea23fa29c182fafb3535c9b0e95aca03dfbf6e0 100644 (file)
@@ -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)
index 612f6d749a76f5865e4a9860255ff7c50db549d6..c0ef480530ac5529b071ef1d76952b0af1095784 100644 (file)
@@ -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
index 6e8c17561e8a7f460c9bf57c11f3b55fa9c7419a..aa156f4736fc6a4ab44ced1a151420fd3ed22f44 100644 (file)
@@ -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;
 
index 494bc6ea489e627840f15831489e7bed095f6c28..3d52ec9866ce8789c276c279675bbc4b0b2263e3 100644 (file)
@@ -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);
index 73bf240cf513089aa468bfa9abe6f2e4316f4968..c1bf2b0f4f5d9649e9e4c528e3b8a00bcc629c3e 100644 (file)
@@ -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) */