arm64: efi: Use SMBIOS processor version to key off Ampere quirk

Instead of using the SMBIOS type 1 record 'family' field, which is often
modified by OEMs, use the type 4 'processor ID' and 'processor version'
fields, which are set to a small set of probe-able values on all known
Ampere EFI systems in the field.

Fixes: 550b33cfd4 ("arm64: efi: Force the use of ...")
Tested-by: Andrea Righi <andrea.righi@canonical.com>
Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
This commit is contained in:
Ard Biesheuvel 2023-02-28 17:00:49 +01:00
parent 34343eb06a
commit eb684408f3
3 changed files with 80 additions and 13 deletions

View File

@ -16,22 +16,45 @@
static bool system_needs_vamap(void) static bool system_needs_vamap(void)
{ {
const u8 *type1_family = efi_get_smbios_string(1, family); const struct efi_smbios_type4_record *record;
const u32 __aligned(1) *socid;
const u8 *version;
/* /*
* Ampere eMAG, Altra, and Altra Max machines crash in SetTime() if * Ampere eMAG, Altra, and Altra Max machines crash in SetTime() if
* SetVirtualAddressMap() has not been called prior. * SetVirtualAddressMap() has not been called prior. Most Altra systems
* can be identified by the SMCCC soc ID, which is conveniently exposed
* via the type 4 SMBIOS records. Otherwise, test the processor version
* field. eMAG systems all appear to have the processor version field
* set to "eMAG".
*/ */
if (!type1_family || ( record = (struct efi_smbios_type4_record *)efi_get_smbios_record(4);
strcmp(type1_family, "eMAG") && if (!record)
strcmp(type1_family, "Altra") &&
strcmp(type1_family, "Altra Max")))
return false; return false;
socid = (u32 *)record->processor_id;
switch (*socid & 0xffff000f) {
static char const altra[] = "Ampere(TM) Altra(TM) Processor";
static char const emag[] = "eMAG";
default:
version = efi_get_smbios_string(&record->header, 4,
processor_version);
if (!version || (strncmp(version, altra, sizeof(altra) - 1) &&
strncmp(version, emag, sizeof(emag) - 1)))
break;
fallthrough;
case 0x0a160001: // Altra
case 0x0a160002: // Altra Max
efi_warn("Working around broken SetVirtualAddressMap()\n"); efi_warn("Working around broken SetVirtualAddressMap()\n");
return true; return true;
} }
return false;
}
efi_status_t check_platform_features(void) efi_status_t check_platform_features(void)
{ {
u64 tg; u64 tg;

View File

@ -1074,6 +1074,8 @@ struct efi_smbios_record {
u16 handle; u16 handle;
}; };
const struct efi_smbios_record *efi_get_smbios_record(u8 type);
struct efi_smbios_type1_record { struct efi_smbios_type1_record {
struct efi_smbios_record header; struct efi_smbios_record header;
@ -1087,14 +1089,47 @@ struct efi_smbios_type1_record {
u8 family; u8 family;
}; };
#define efi_get_smbios_string(__type, __name) ({ \ struct efi_smbios_type4_record {
struct efi_smbios_record header;
u8 socket;
u8 processor_type;
u8 processor_family;
u8 processor_manufacturer;
u8 processor_id[8];
u8 processor_version;
u8 voltage;
u16 external_clock;
u16 max_speed;
u16 current_speed;
u8 status;
u8 processor_upgrade;
u16 l1_cache_handle;
u16 l2_cache_handle;
u16 l3_cache_handle;
u8 serial_number;
u8 asset_tag;
u8 part_number;
u8 core_count;
u8 enabled_core_count;
u8 thread_count;
u16 processor_characteristics;
u16 processor_family2;
u16 core_count2;
u16 enabled_core_count2;
u16 thread_count2;
u16 thread_enabled;
};
#define efi_get_smbios_string(__record, __type, __name) ({ \
int size = sizeof(struct efi_smbios_type ## __type ## _record); \ int size = sizeof(struct efi_smbios_type ## __type ## _record); \
int off = offsetof(struct efi_smbios_type ## __type ## _record, \ int off = offsetof(struct efi_smbios_type ## __type ## _record, \
__name); \ __name); \
__efi_get_smbios_string(__type, off, size); \ __efi_get_smbios_string((__record), __type, off, size); \
}) })
const u8 *__efi_get_smbios_string(u8 type, int offset, int recsize); const u8 *__efi_get_smbios_string(const struct efi_smbios_record *record,
u8 type, int offset, int recsize);
void efi_remap_image(unsigned long image_base, unsigned alloc_size, void efi_remap_image(unsigned long image_base, unsigned alloc_size,
unsigned long code_size); unsigned long code_size);

View File

@ -22,19 +22,28 @@ struct efi_smbios_protocol {
u8 minor_version; u8 minor_version;
}; };
const u8 *__efi_get_smbios_string(u8 type, int offset, int recsize) const struct efi_smbios_record *efi_get_smbios_record(u8 type)
{ {
struct efi_smbios_record *record; struct efi_smbios_record *record;
efi_smbios_protocol_t *smbios; efi_smbios_protocol_t *smbios;
efi_status_t status; efi_status_t status;
u16 handle = 0xfffe; u16 handle = 0xfffe;
const u8 *strtable;
status = efi_bs_call(locate_protocol, &EFI_SMBIOS_PROTOCOL_GUID, NULL, status = efi_bs_call(locate_protocol, &EFI_SMBIOS_PROTOCOL_GUID, NULL,
(void **)&smbios) ?: (void **)&smbios) ?:
efi_call_proto(smbios, get_next, &handle, &type, &record, NULL); efi_call_proto(smbios, get_next, &handle, &type, &record, NULL);
if (status != EFI_SUCCESS) if (status != EFI_SUCCESS)
return NULL; return NULL;
return record;
}
const u8 *__efi_get_smbios_string(const struct efi_smbios_record *record,
u8 type, int offset, int recsize)
{
const u8 *strtable;
if (!record)
return NULL;
strtable = (u8 *)record + record->length; strtable = (u8 *)record + record->length;
for (int i = 1; i < ((u8 *)record)[offset]; i++) { for (int i = 1; i < ((u8 *)record)[offset]; i++) {