mirror of
https://github.com/systemd/systemd.git
synced 2025-01-18 10:04:04 +03:00
hwids: add a new efi firmware type of device entry (#35747)
This change adds a new firmware type device entry for the .hwids section. It also adds compile time validations and appropriate unit tests for them. chid_match() and related helpers have been updated accordingly. Duplicate of https://github.com/systemd/systemd/pull/35281 Last review feedback's from this above PR has been incorporated and merged. @anonymix007
This commit is contained in:
commit
5ae3172867
@ -1,4 +1,5 @@
|
||||
{
|
||||
"type": "devicetree",
|
||||
"name": "Example Laptop 16 Gen 7",
|
||||
"compatible": "example,laptop-16-g7",
|
||||
"hwids": [
|
||||
|
@ -23,8 +23,11 @@
|
||||
|
||||
/* Validate the descriptor macros a bit that they match our expectations */
|
||||
assert_cc(DEVICE_DESCRIPTOR_DEVICETREE == UINT32_C(0x1000001C));
|
||||
assert_cc(DEVICE_DESCRIPTOR_UEFI_FW == UINT32_C(0x2000001C));
|
||||
assert_cc(DEVICE_SIZE_FROM_DESCRIPTOR(DEVICE_DESCRIPTOR_DEVICETREE) == sizeof(Device));
|
||||
assert_cc(DEVICE_TYPE_FROM_DESCRIPTOR(DEVICE_DESCRIPTOR_DEVICETREE) == DEVICE_TYPE_DEVICETREE);
|
||||
assert_cc(DEVICE_SIZE_FROM_DESCRIPTOR(DEVICE_DESCRIPTOR_UEFI_FW) == sizeof(Device));
|
||||
assert_cc(DEVICE_TYPE_FROM_DESCRIPTOR(DEVICE_DESCRIPTOR_UEFI_FW) == DEVICE_TYPE_UEFI_FW);
|
||||
|
||||
/**
|
||||
* smbios_to_hashable_string() - Convert ascii smbios string to stripped char16_t.
|
||||
@ -88,7 +91,7 @@ static EFI_STATUS populate_board_chids(EFI_GUID ret_chids[static CHID_TYPES_MAX]
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
EFI_STATUS chid_match(const void *hwid_buffer, size_t hwid_length, const Device **ret_device) {
|
||||
EFI_STATUS chid_match(const void *hwid_buffer, size_t hwid_length, uint32_t match_type, const Device **ret_device) {
|
||||
EFI_STATUS status;
|
||||
|
||||
if ((uintptr_t) hwid_buffer % alignof(Device) != 0)
|
||||
@ -114,7 +117,8 @@ EFI_STATUS chid_match(const void *hwid_buffer, size_t hwid_length, const Device
|
||||
|
||||
if (devices[n_devices].descriptor == DEVICE_DESCRIPTOR_EOL)
|
||||
break;
|
||||
if (devices[n_devices].descriptor != DEVICE_DESCRIPTOR_DEVICETREE)
|
||||
if (!IN_SET(DEVICE_TYPE_FROM_DESCRIPTOR(devices[n_devices].descriptor),
|
||||
DEVICE_TYPE_UEFI_FW, DEVICE_TYPE_DEVICETREE))
|
||||
return EFI_UNSUPPORTED;
|
||||
n_devices++;
|
||||
}
|
||||
@ -126,6 +130,8 @@ EFI_STATUS chid_match(const void *hwid_buffer, size_t hwid_length, const Device
|
||||
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 (DEVICE_TYPE_FROM_DESCRIPTOR(dev->descriptor) != match_type)
|
||||
continue;
|
||||
if (efi_guid_equal(&chids[*i], &chid)) {
|
||||
*ret_device = dev;
|
||||
return EFI_SUCCESS;
|
||||
|
@ -11,11 +11,13 @@
|
||||
|
||||
enum {
|
||||
DEVICE_TYPE_DEVICETREE = 0x1, /* A devicetree blob */
|
||||
DEVICE_TYPE_UEFI_FW = 0x2, /* A firmware blob */
|
||||
|
||||
/* Maybe later additional types for:
|
||||
* - CoCo Bring-Your-Own-Firmware
|
||||
* - ACPI DSDT Overrides
|
||||
* - … */
|
||||
_DEVICE_TYPE_MAX,
|
||||
};
|
||||
|
||||
#define DEVICE_SIZE_FROM_DESCRIPTOR(u) ((uint32_t) (u) & UINT32_C(0x0FFFFFFF))
|
||||
@ -23,6 +25,7 @@ enum {
|
||||
#define DEVICE_MAKE_DESCRIPTOR(type, size) (((uint32_t) (size) | ((uint32_t) type << 28)))
|
||||
|
||||
#define DEVICE_DESCRIPTOR_DEVICETREE DEVICE_MAKE_DESCRIPTOR(DEVICE_TYPE_DEVICETREE, sizeof(Device))
|
||||
#define DEVICE_DESCRIPTOR_UEFI_FW DEVICE_MAKE_DESCRIPTOR(DEVICE_TYPE_UEFI_FW, sizeof(Device))
|
||||
#define DEVICE_DESCRIPTOR_EOL UINT32_C(0)
|
||||
|
||||
typedef struct Device {
|
||||
@ -36,6 +39,13 @@ typedef struct Device {
|
||||
uint32_t name_offset; /* nul-terminated string or 0 if not present */
|
||||
uint32_t compatible_offset; /* nul-terminated string or 0 if not present */
|
||||
} devicetree;
|
||||
struct {
|
||||
/* Offsets are relative to the beginning of the .hwids PE section.
|
||||
* They are nul-terminated strings when present or 0 if not present */
|
||||
uint32_t name_offset; /* name or identifier for the firmware blob */
|
||||
uint32_t fwid_offset; /* identifier to match a specific uefi firmware blob */
|
||||
} uefi_fw;
|
||||
|
||||
/* fields for other descriptor types… */
|
||||
};
|
||||
} _packed_ Device;
|
||||
@ -45,20 +55,47 @@ assert_cc(offsetof(Device, descriptor) == 0);
|
||||
assert_cc(offsetof(Device, chid) == 4);
|
||||
assert_cc(offsetof(Device, devicetree.name_offset) == 20);
|
||||
assert_cc(offsetof(Device, devicetree.compatible_offset) == 24);
|
||||
assert_cc(offsetof(Device, uefi_fw.name_offset) == 20);
|
||||
assert_cc(offsetof(Device, uefi_fw.fwid_offset) == 24);
|
||||
assert_cc(sizeof(Device) == 28);
|
||||
|
||||
static inline const char* device_get_name(const void *base, const Device *device) {
|
||||
if (device->descriptor != DEVICE_DESCRIPTOR_DEVICETREE)
|
||||
size_t off = 0;
|
||||
switch (DEVICE_TYPE_FROM_DESCRIPTOR(device->descriptor)) {
|
||||
case DEVICE_TYPE_DEVICETREE:
|
||||
off = device->devicetree.name_offset;
|
||||
break;
|
||||
case DEVICE_TYPE_UEFI_FW:
|
||||
off = device->uefi_fw.name_offset;
|
||||
break;
|
||||
default:
|
||||
return NULL;
|
||||
|
||||
return device->devicetree.name_offset == 0 ? NULL : (const char *) ((const uint8_t *) base + device->devicetree.name_offset);
|
||||
}
|
||||
return off == 0 ? NULL : (const char *) ((const uint8_t *) base + off);
|
||||
}
|
||||
|
||||
static inline const char* device_get_compatible(const void *base, const Device *device) {
|
||||
if (device->descriptor != DEVICE_DESCRIPTOR_DEVICETREE)
|
||||
size_t off = 0;
|
||||
switch (DEVICE_TYPE_FROM_DESCRIPTOR(device->descriptor)) {
|
||||
case DEVICE_TYPE_DEVICETREE:
|
||||
off = device->devicetree.compatible_offset;
|
||||
break;
|
||||
default:
|
||||
return NULL;
|
||||
|
||||
return device->devicetree.compatible_offset == 0 ? NULL : (const char *) ((const uint8_t *) base + device->devicetree.compatible_offset);
|
||||
}
|
||||
return off == 0 ? NULL : (const char *) ((const uint8_t *) base + off);
|
||||
}
|
||||
|
||||
EFI_STATUS chid_match(const void *chids_buffer, size_t chids_length, const Device **ret_device);
|
||||
static inline const char* device_get_fwid(const void *base, const Device *device) {
|
||||
size_t off = 0;
|
||||
switch (DEVICE_TYPE_FROM_DESCRIPTOR(device->descriptor)) {
|
||||
case DEVICE_TYPE_UEFI_FW:
|
||||
off = device->uefi_fw.fwid_offset;
|
||||
break;
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
return off == 0 ? NULL : (const char *) ((const uint8_t *) base + off);
|
||||
}
|
||||
|
||||
EFI_STATUS chid_match(const void *chids_buffer, size_t chids_length, uint32_t match_type, const Device **ret_device);
|
||||
|
@ -1,4 +1,5 @@
|
||||
{
|
||||
"type": "devicetree",
|
||||
"name": "Device 1",
|
||||
"compatible": "test,device-1",
|
||||
"hwids": [
|
||||
|
@ -1,4 +1,5 @@
|
||||
{
|
||||
"type": "devicetree",
|
||||
"name": "Device 2",
|
||||
"compatible": "test,device-2",
|
||||
"hwids": [
|
||||
|
@ -1,4 +1,5 @@
|
||||
{
|
||||
"type": "devicetree",
|
||||
"name": "Device 3",
|
||||
"compatible": "test,device-3",
|
||||
"hwids": [
|
||||
|
9
src/boot/hwids/device4.json
Normal file
9
src/boot/hwids/device4.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"type": "uefi-fw",
|
||||
"name": "Device 4",
|
||||
"fwid": "test,vmware",
|
||||
"hwids": [
|
||||
"208a77d5-ba4b-518a-8ea7-18eab0e50654",
|
||||
"4a8a248d-9e6b-5216-b83e-6cdf9bde8d0d"
|
||||
]
|
||||
}
|
@ -31,7 +31,7 @@ generate_hwids_section_py = find_program('generate-hwids-section.py')
|
||||
if conf.get('ENABLE_UKIFY') == 1
|
||||
test_hwids_section_c = custom_target(
|
||||
'test-hwids-section.c',
|
||||
input : ['hwids/device1.json', 'hwids/device2.json', 'hwids/device3.json'],
|
||||
input : ['hwids/device1.json', 'hwids/device2.json', 'hwids/device3.json', 'hwids/device4.json'],
|
||||
output : 'test-hwids-section.c',
|
||||
command : [generate_hwids_section_py, meson.current_source_dir()/'hwids'],
|
||||
capture : true,
|
||||
|
@ -323,7 +323,7 @@ static void pe_locate_sections(
|
||||
if (PE_SECTION_VECTOR_IS_SET(&hwids_section)) {
|
||||
hwids = (const uint8_t *) SIZE_TO_PTR(validate_base) + hwids_section.memory_offset;
|
||||
|
||||
EFI_STATUS err = chid_match(hwids, hwids_section.memory_size, &device);
|
||||
EFI_STATUS err = chid_match(hwids, hwids_section.memory_size, DEVICE_TYPE_DEVICETREE, &device);
|
||||
if (err != EFI_SUCCESS) {
|
||||
log_error_status(err, "HWID matching failed, no DT blob will be selected: %m");
|
||||
hwids = NULL;
|
||||
|
@ -10,40 +10,65 @@
|
||||
extern uint8_t hwids_section_data[];
|
||||
extern size_t hwids_section_len;
|
||||
|
||||
static const RawSmbiosInfo smbios_info[] = {
|
||||
static struct {
|
||||
const RawSmbiosInfo smbios_info;
|
||||
uint32_t device_type;
|
||||
} info[] = {
|
||||
{
|
||||
.manufacturer = "First Vendor",
|
||||
.product_name = "Device 1",
|
||||
.product_sku = "KD01",
|
||||
.family = "Laptop X",
|
||||
.baseboard_product = "FODM1",
|
||||
.baseboard_manufacturer = "First ODM",
|
||||
.smbios_info = {
|
||||
.manufacturer = "First Vendor",
|
||||
.product_name = "Device 1",
|
||||
.product_sku = "KD01",
|
||||
.family = "Laptop X",
|
||||
.baseboard_product = "FODM1",
|
||||
.baseboard_manufacturer = "First ODM",
|
||||
},
|
||||
.device_type = DEVICE_TYPE_DEVICETREE,
|
||||
},
|
||||
{
|
||||
.manufacturer = "Second Vendor",
|
||||
.product_name = "Device 2",
|
||||
.product_sku = "KD02",
|
||||
.family = "Laptop 2",
|
||||
.baseboard_product = "SODM2",
|
||||
.baseboard_manufacturer = "Second ODM",
|
||||
.smbios_info = {
|
||||
.manufacturer = "Second Vendor",
|
||||
.product_name = "Device 2",
|
||||
.product_sku = "KD02",
|
||||
.family = "Laptop 2",
|
||||
.baseboard_product = "SODM2",
|
||||
.baseboard_manufacturer = "Second ODM",
|
||||
},
|
||||
.device_type = DEVICE_TYPE_DEVICETREE,
|
||||
},
|
||||
{
|
||||
.manufacturer = "First Vendor",
|
||||
.product_name = "Device 3",
|
||||
.product_sku = "KD03",
|
||||
.family = "Tablet Y",
|
||||
.baseboard_product = "FODM2",
|
||||
.baseboard_manufacturer = "First ODM",
|
||||
.smbios_info = {
|
||||
.manufacturer = "First Vendor",
|
||||
.product_name = "Device 3",
|
||||
.product_sku = "KD03",
|
||||
.family = "Tablet Y",
|
||||
.baseboard_product = "FODM2",
|
||||
.baseboard_manufacturer = "First ODM",
|
||||
},
|
||||
.device_type = DEVICE_TYPE_DEVICETREE,
|
||||
},
|
||||
{
|
||||
.smbios_info = {
|
||||
.manufacturer = "VMware, Inc.",
|
||||
.product_name = "VMware20,1",
|
||||
.product_sku = "0000000000000001",
|
||||
.family = "VMware",
|
||||
.baseboard_product = "VBSA",
|
||||
.baseboard_manufacturer = "VMware, Inc.",
|
||||
},
|
||||
.device_type = DEVICE_TYPE_UEFI_FW,
|
||||
},
|
||||
};
|
||||
|
||||
static struct {
|
||||
const char *name;
|
||||
const char *compatible;
|
||||
const char *fwid;
|
||||
} results[] = {
|
||||
{ "Device 1", "test,device-1" },
|
||||
{ "Device 2", "test,device-2" },
|
||||
{ "Device 3", "test,device-3" },
|
||||
{ "Device 1", "test,device-1", NULL },
|
||||
{ "Device 2", "test,device-2", NULL },
|
||||
{ "Device 3", "test,device-3", NULL },
|
||||
{ "Device 4", NULL, "test,vmware" },
|
||||
};
|
||||
|
||||
static RawSmbiosInfo current_info = {};
|
||||
@ -55,14 +80,16 @@ void smbios_raw_info_get_cached(RawSmbiosInfo *ret_info) {
|
||||
}
|
||||
|
||||
TEST(chid_match) {
|
||||
for (size_t i = 0; i < ELEMENTSOF(smbios_info); i++) {
|
||||
current_info = smbios_info[i];
|
||||
for (size_t i = 0; i < ELEMENTSOF(info); i++) {
|
||||
current_info = info[i].smbios_info;
|
||||
const Device *dev = NULL;
|
||||
/* Match and check */
|
||||
ASSERT_EQ(chid_match(hwids_section_data, hwids_section_len, &dev), EFI_SUCCESS);
|
||||
ASSERT_EQ(chid_match(hwids_section_data, hwids_section_len, info[i].device_type, &dev), EFI_SUCCESS);
|
||||
ASSERT_NOT_NULL(dev);
|
||||
ASSERT_EQ(DEVICE_TYPE_FROM_DESCRIPTOR(dev->descriptor), info[i].device_type);
|
||||
ASSERT_STREQ(device_get_name(hwids_section_data, dev), results[i].name);
|
||||
ASSERT_STREQ(device_get_compatible(hwids_section_data, dev), results[i].compatible);
|
||||
ASSERT_STREQ(device_get_fwid(hwids_section_data, dev), results[i].fwid);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1014,27 +1014,27 @@ def merge_sbat(input_pe: list[Path], input_text: list[str]) -> str:
|
||||
)
|
||||
|
||||
|
||||
# Keep in sync with Device (DEVICE_TYPE_DEVICETREE) from src/boot/chid.h
|
||||
# Keep in sync with Device from src/boot/chid.h
|
||||
# uint32_t descriptor, EFI_GUID chid, uint32_t name_offset, uint32_t compatible_offset
|
||||
DEVICE_STRUCT_SIZE = 4 + 16 + 4 + 4
|
||||
NULL_DEVICE = b'\0' * DEVICE_STRUCT_SIZE
|
||||
DEVICE_TYPE_DEVICETREE = 1
|
||||
DEVICE_TYPE_UEFI_FW = 2
|
||||
|
||||
|
||||
def device_make_descriptor(device_type: int, size: int) -> int:
|
||||
return (size) | (device_type << 28)
|
||||
|
||||
|
||||
DEVICETREE_DESCRIPTOR = device_make_descriptor(DEVICE_TYPE_DEVICETREE, DEVICE_STRUCT_SIZE)
|
||||
|
||||
|
||||
def pack_device(offsets: dict[str, int], name: str, compatible: str, chids: set[uuid.UUID]) -> bytes:
|
||||
def pack_device(
|
||||
offsets: dict[str, int], devtype: int, name: str, compatible_or_fwid: str, chids: set[uuid.UUID]
|
||||
) -> bytes:
|
||||
data = b''
|
||||
|
||||
descriptor = device_make_descriptor(devtype, DEVICE_STRUCT_SIZE)
|
||||
for chid in sorted(chids):
|
||||
data += struct.pack('<I', DEVICETREE_DESCRIPTOR)
|
||||
data += struct.pack('<I', descriptor)
|
||||
data += chid.bytes_le
|
||||
data += struct.pack('<II', offsets[name], offsets[compatible])
|
||||
data += struct.pack('<II', offsets[name], offsets[compatible_or_fwid])
|
||||
|
||||
assert len(data) == DEVICE_STRUCT_SIZE * len(chids)
|
||||
return data
|
||||
@ -1053,21 +1053,48 @@ def pack_strings(strings: set[str], base: int) -> tuple[bytes, dict[str, int]]:
|
||||
|
||||
def parse_hwid_dir(path: Path) -> bytes:
|
||||
hwid_files = path.rglob('*.json')
|
||||
devstr_to_type: dict[str, int] = {
|
||||
'devicetree': DEVICE_TYPE_DEVICETREE,
|
||||
'uefi-fw': DEVICE_TYPE_UEFI_FW,
|
||||
}
|
||||
|
||||
# all attributes in the mandatory attributes list must be present
|
||||
mandatory_attribute = ['type', 'name', 'hwids']
|
||||
|
||||
# at least one of the following attributes must be present
|
||||
one_of = ['compatible', 'fwid']
|
||||
|
||||
one_of_key_to_devtype: dict[str, int] = {
|
||||
'compatible': DEVICE_TYPE_DEVICETREE,
|
||||
'fwid': DEVICE_TYPE_UEFI_FW,
|
||||
}
|
||||
|
||||
strings: set[str] = set()
|
||||
devices: collections.defaultdict[tuple[str, str], set[uuid.UUID]] = collections.defaultdict(set)
|
||||
devices: collections.defaultdict[tuple[int, str, str], set[uuid.UUID]] = collections.defaultdict(set)
|
||||
|
||||
for hwid_file in hwid_files:
|
||||
data = json.loads(hwid_file.read_text(encoding='UTF-8'))
|
||||
|
||||
for k in ['name', 'compatible', 'hwids']:
|
||||
for k in mandatory_attribute:
|
||||
if k not in data:
|
||||
raise ValueError(f'hwid description file "{hwid_file}" does not contain "{k}"')
|
||||
|
||||
strings |= {data['name'], data['compatible']}
|
||||
if not any(key in data for key in one_of):
|
||||
required_keys = ','.join(one_of)
|
||||
raise ValueError(f'hwid description file "{hwid_file}" must contain one of {required_keys}')
|
||||
|
||||
# (name, compatible) pair uniquely identifies the device
|
||||
devices[(data['name'], data['compatible'])] |= {uuid.UUID(u) for u in data['hwids']}
|
||||
# (devtype, name, compatible/fwid) pair uniquely identifies the device
|
||||
devtype = devstr_to_type[data['type']]
|
||||
|
||||
for k in one_of:
|
||||
if k in data:
|
||||
if one_of_key_to_devtype[k] != devtype:
|
||||
raise ValueError(
|
||||
f'wrong attribute "{k}" for hwid description file "{hwid_file}", '
|
||||
'device type: "%s"' % devtype
|
||||
)
|
||||
strings |= {data['name'], data[k]}
|
||||
devices[(devtype, data['name'], data[k])] |= {uuid.UUID(u) for u in data['hwids']}
|
||||
|
||||
total_device_structs = 1
|
||||
for dev, uuids in devices.items():
|
||||
@ -1076,8 +1103,8 @@ def parse_hwid_dir(path: Path) -> bytes:
|
||||
strings_blob, offsets = pack_strings(strings, total_device_structs * DEVICE_STRUCT_SIZE)
|
||||
|
||||
devices_blob = b''
|
||||
for (name, compatible), uuids in devices.items():
|
||||
devices_blob += pack_device(offsets, name, compatible, uuids)
|
||||
for (devtype, name, compatible_or_fwid), uuids in devices.items():
|
||||
devices_blob += pack_device(offsets, devtype, name, compatible_or_fwid, uuids)
|
||||
|
||||
devices_blob += NULL_DEVICE
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user