diff --git a/LICENSES/README.md b/LICENSES/README.md index 29ae23c7277..f8668be8202 100644 --- a/LICENSES/README.md +++ b/LICENSES/README.md @@ -69,6 +69,9 @@ The following exceptions apply: * the following sources are under **Public Domain** (LicenseRef-alg-sha1-public-domain): - src/fundamental/sha1-fundamental.c - src/fundamental/sha1-fundamental.h + * the following files are licensed under **BSD-3-Clause** license: + - src/boot/efi/chid.c + - src/boot/efi/chid.h * Heebo fonts under docs/fonts/ are licensed under the **SIL Open Font License 1.1**, * any files under test/ without an explicit license we assume non-copyrightable (eg: computer-generated fuzzer data) diff --git a/src/boot/efi/chid.c b/src/boot/efi/chid.c new file mode 100644 index 00000000000..50d840aea04 --- /dev/null +++ b/src/boot/efi/chid.c @@ -0,0 +1,129 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ + +/* + * Based on Nikita Travkin's dtbloader implementation. + * Copyright (c) 2024 Nikita Travkin + * + * https://github.com/TravMurav/dtbloader/blob/main/src/chid.c + */ + +/* + * Based on Linaro dtbloader implementation. + * Copyright (c) 2019, Linaro. All rights reserved. + * + * https://github.com/aarch64-laptops/edk2/blob/dtbloader-app/EmbeddedPkg/Application/ConfigTableLoader/CHID.c + */ + +#include "chid.h" +#include "chid-fundamental.h" +#include "efi.h" +#include "sha1-fundamental.h" +#include "smbios.h" +#include "util.h" + +/** + * smbios_to_hashable_string() - Convert ascii smbios string to stripped char16_t. + */ +static char16_t *smbios_to_hashable_string(const char *str) { + if (!str) + /* User of this function is expected to free the result. */ + return xnew0(char16_t, 1); + + /* + * We need to strip leading and trailing spaces, leading zeroes. + * See fwupd/libfwupdplugin/fu-hwids-smbios.c + */ + while (*str == ' ') + str++; + + while (*str == '0') + str++; + + size_t len = strlen8(str); + + while (len > 0 && str[len - 1] == ' ') + len--; + + return xstrn8_to_16(str, len); +} + +/* This has to be in a struct due to _cleanup_ in populate_board_chids */ +typedef struct SmbiosInfo { + const char16_t *smbios_fields[_CHID_SMBIOS_FIELDS_MAX]; +} SmbiosInfo; + +static void smbios_info_populate(SmbiosInfo *ret_info) { + static RawSmbiosInfo raw = {}; + static bool raw_info_populated = false; + + if (!raw_info_populated) { + smbios_raw_info_populate(&raw); + raw_info_populated = true; + } + + ret_info->smbios_fields[CHID_SMBIOS_MANUFACTURER] = smbios_to_hashable_string(raw.manufacturer); + ret_info->smbios_fields[CHID_SMBIOS_PRODUCT_NAME] = smbios_to_hashable_string(raw.product_name); + ret_info->smbios_fields[CHID_SMBIOS_PRODUCT_SKU] = smbios_to_hashable_string(raw.product_sku); + ret_info->smbios_fields[CHID_SMBIOS_FAMILY] = smbios_to_hashable_string(raw.family); + ret_info->smbios_fields[CHID_SMBIOS_BASEBOARD_PRODUCT] = smbios_to_hashable_string(raw.baseboard_product); + ret_info->smbios_fields[CHID_SMBIOS_BASEBOARD_MANUFACTURER] = smbios_to_hashable_string(raw.baseboard_manufacturer); +} + +static void smbios_info_done(SmbiosInfo *info) { + FOREACH_ELEMENT(i, info->smbios_fields) + free(i); +} + +static EFI_STATUS populate_board_chids(EFI_GUID ret_chids[static CHID_TYPES_MAX]) { + _cleanup_(smbios_info_done) SmbiosInfo info = {}; + + if (!ret_chids) + return EFI_INVALID_PARAMETER; + + smbios_info_populate(&info); + chid_calculate(info.smbios_fields, ret_chids); + + return EFI_SUCCESS; +} + +EFI_STATUS chid_match(const void *hwid_buffer, size_t hwid_length, const Device **ret_device) { + EFI_STATUS status; + + if ((uintptr_t) hwid_buffer % alignof(Device) != 0) + return EFI_INVALID_PARAMETER; + + const Device *devices = ASSERT_PTR(hwid_buffer); + + EFI_GUID chids[CHID_TYPES_MAX] = {}; + static const size_t priority[] = { 3, 6, 8, 10, 4, 5, 7, 9, 11 }; /* From most to least specific. */ + + status = populate_board_chids(chids); + if (EFI_STATUS_IS_ERROR(status)) + return log_error_status(status, "Failed to populate board CHIDs: %m"); + + size_t n_devices = 0; + + /* Count devices and check validity */ + for (; (n_devices + 1) * sizeof(*devices) < hwid_length;) { + if (devices[n_devices].struct_size == 0) + break; + if (devices[n_devices].struct_size != sizeof(*devices)) + return EFI_UNSUPPORTED; + n_devices++; + } + + if (n_devices == 0) + return EFI_NOT_FOUND; + + FOREACH_ELEMENT(i, priority) + FOREACH_ARRAY(dev, devices, n_devices) { + /* Can't take a pointer to a packed struct member, so copy to a local variable */ + EFI_GUID chid = dev->chid; + if (efi_guid_equal(&chids[*i], &chid)) { + *ret_device = dev; + return EFI_SUCCESS; + } + } + + return EFI_NOT_FOUND; +} diff --git a/src/boot/efi/chid.h b/src/boot/efi/chid.h new file mode 100644 index 00000000000..ea6e2d348fa --- /dev/null +++ b/src/boot/efi/chid.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +#pragma once + +#include "efi.h" + +#include "chid-fundamental.h" + +typedef struct Device { + uint32_t struct_size; /* = sizeof(struct Device), or 0 for EOL */ + uint32_t name_offset; /* nul-terminated string or 0 if not present */ + uint32_t compatible_offset; /* nul-terminated string or 0 if not present */ + EFI_GUID chid; +} _packed_ Device; + +static inline const char* device_get_name(const void *base, const Device *device) { + return device->name_offset == 0 ? NULL : (const char *) ((const uint8_t *) base + device->name_offset); +} + +static inline const char* device_get_compatible(const void *base, const Device *device) { + return device->compatible_offset == 0 ? NULL : (const char *) ((const uint8_t *) base + device->compatible_offset); +} + +EFI_STATUS chid_match(const void *chids_buffer, size_t chids_length, const Device **ret_device); diff --git a/src/boot/efi/meson.build b/src/boot/efi/meson.build index 0109793b7a9..29c5455dbd1 100644 --- a/src/boot/efi/meson.build +++ b/src/boot/efi/meson.build @@ -254,6 +254,7 @@ endif ############################################################ libefi_sources = files( + 'chid.c', 'console.c', 'device-path-util.c', 'devicetree.c',