* `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,
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
</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">
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>.
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);
#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"
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);
+ }
}
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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);
'efi-string.c',
'export-vars.c',
'graphics.c',
+ 'hii.c',
'initrd.c',
'measure.c',
'part-discovery.c',
--- /dev/null
+/* 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;
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);
}
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;
};
_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;
(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());
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);
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);
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");
}
#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)
# 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
}
}
+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;
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);
#include "alloc-util.h"
#include "creds-util.h"
+#include "efivars.h"
#include "env-file.h"
#include "errno-util.h"
#include "fd-util.h"
#include "string-util.h"
#include "strv.h"
#include "terminal-util.h"
+#include "vconsole-util.h"
typedef struct Context {
char *keymap;
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;
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) */