kernel: load cpu amd microcode update if loaded by the bootloader

we detect basically the cpu info before loading the microcode,
to be able to detect the vendor, and avoid any update on hypervisor.

I couldn't test because my cpu doesn't have any update available.

Change-Id: I6aea830158423b3ee13b640be8a788fc9041e23c
Reviewed-on: https://review.haiku-os.org/c/haiku/+/5859
Tested-by: Commit checker robot <no-reply+buildbot@haiku-os.org>
Reviewed-by: Jérôme Duval <jerome.duval@gmail.com>
This commit is contained in:
Jérôme Duval 2022-11-29 20:13:22 +01:00
parent 616edea7fb
commit fb69e06195
2 changed files with 179 additions and 13 deletions

View File

@ -123,6 +123,7 @@
// AMD MSR registers
#define MSR_F10H_HWCR 0xc0010015
#define HWCR_TSCFREQSEL (1 << 24)
#define MSR_K8_UCODE_UPDATE 0xc0010020
#define K8_MSR_IPM 0xc0010055
#define MSR_F10H_PSTATEDEF(x) (0xc0010064 + (x))
#define PSTATEDEF_EN (1ULL << 63)
@ -594,6 +595,46 @@ struct intel_microcode_extended_signature {
};
// AMD Microcode structures
struct amd_container_header {
uint32 magic;
};
struct amd_section_header {
uint32 type;
uint32 size;
};
struct amd_equiv_cpu_entry {
uint32 installed_cpu;
uint32 fixed_errata_mask;
uint32 fixed_errata_compare;
uint16 equiv_cpu;
uint16 res;
};
struct amd_microcode_header {
uint32 data_code;
uint32 patch_id;
uint16 mc_patch_data_id;
uint8 mc_patch_data_len;
uint8 init_flag;
uint32 mc_patch_data_checksum;
uint32 nb_dev_id;
uint32 sb_dev_id;
uint16 processor_rev_id;
uint8 nb_rev_id;
uint8 sb_rev_id;
uint8 bios_api_rev;
uint8 reserved1[3];
uint32 match_reg[8];
};
extern void (*gCpuIdleFunc)(void);

View File

@ -125,7 +125,7 @@ static uint32 sCacheSharingMask[CPU_MAX_CACHE_LEVEL];
static void* sUcodeData = NULL;
static size_t sUcodeDataSize = 0;
static struct intel_microcode_header* sLoadedUcodeUpdate;
static void* sLoadedUcodeUpdate;
static spinlock sUcodeUpdateLock = B_SPINLOCK_INITIALIZER;
@ -994,7 +994,7 @@ detect_amd_patch_level(cpu_ent* cpu)
}
uint64 value = x86_read_msr(IA32_MSR_UCODE_REV);
cpu->arch.patch_level = value >> 32;
cpu->arch.patch_level = (uint32)value;
}
@ -1088,7 +1088,7 @@ load_microcode_intel(int currentCPU, cpu_ent* cpu)
acquire_spinlock(&sUcodeUpdateLock);
detect_intel_patch_level(cpu);
uint32 revision = cpu->arch.patch_level;
struct intel_microcode_header* update = sLoadedUcodeUpdate;
struct intel_microcode_header* update = (struct intel_microcode_header*)sLoadedUcodeUpdate;
if (update == NULL) {
update = find_microcode_intel((addr_t)sUcodeData, sUcodeDataSize,
revision);
@ -1114,10 +1114,127 @@ load_microcode_intel(int currentCPU, cpu_ent* cpu)
}
static struct amd_microcode_header*
find_microcode_amd(addr_t data, size_t size, uint32 patchLevel)
{
// 9.11.3 Processor Identification
cpuid_info cpuid;
get_current_cpuid(&cpuid, 1, 0);
uint32 signature = cpuid.regs.eax;
if (size < sizeof(struct amd_container_header)) {
dprintf("find_microcode_amd update is too small for header\n");
return NULL;
}
struct amd_container_header* container = (struct amd_container_header*)data;
if (container->magic != 0x414d44) {
dprintf("find_microcode_amd update invalid magic\n");
return NULL;
}
size -= sizeof(*container);
data += sizeof(*container);
struct amd_section_header* section =
(struct amd_section_header*)data;
if (section->type != 0 || section->size == 0) {
dprintf("find_microcode_amd update first section invalid\n");
return NULL;
}
size -= sizeof(*section);
data += sizeof(*section);
amd_equiv_cpu_entry* table = (amd_equiv_cpu_entry*)data;
size -= section->size;
data += section->size;
uint16 equiv_id = 0;
for (uint32 i = 0; table[i].installed_cpu != 0; i++) {
if (signature == table[i].equiv_cpu) {
equiv_id = table[i].equiv_cpu;
dprintf("find_microcode_amd found equiv cpu: %x\n", equiv_id);
break;
}
}
if (equiv_id == 0) {
dprintf("find_microcode_amd update cpu not found in equiv table\n");
return NULL;
}
while (size > sizeof(amd_section_header)) {
struct amd_section_header* section = (struct amd_section_header*)data;
size -= sizeof(*section);
data += sizeof(*section);
if (section->type != 1 || section->size > size
|| section->size < sizeof(amd_microcode_header)) {
dprintf("find_microcode_amd update firmware section invalid\n");
return NULL;
}
struct amd_microcode_header* header = (struct amd_microcode_header*)data;
size -= section->size;
data += section->size;
if (header->processor_rev_id != equiv_id) {
dprintf("find_microcode_amd update found rev_id %x\n", header->processor_rev_id);
continue;
}
if (patchLevel >= header->patch_id) {
dprintf("find_microcode_intel update_revision is lower\n");
continue;
}
if (header->nb_dev_id != 0 || header->sb_dev_id != 0) {
dprintf("find_microcode_amd update chipset specific firmware\n");
continue;
}
if (((addr_t)header % 16) != 0) {
dprintf("find_microcode_amd incorrect alignment\n");
continue;
}
return header;
}
dprintf("find_microcode_amd no fw update found for this cpu\n");
return NULL;
}
static void
load_microcode_amd(int currentCPU, cpu_ent* cpu)
{
dprintf("CPU %d: no update found\n", currentCPU);
// serialize for HT cores
if (currentCPU != 0)
acquire_spinlock(&sUcodeUpdateLock);
detect_amd_patch_level(cpu);
uint32 revision = cpu->arch.patch_level;
struct amd_microcode_header* update = (struct amd_microcode_header*)sLoadedUcodeUpdate;
if (update == NULL) {
update = find_microcode_amd((addr_t)sUcodeData, sUcodeDataSize,
revision);
}
if (update != NULL) {
addr_t data = (addr_t)update;
wbinvd();
x86_write_msr(MSR_K8_UCODE_UPDATE, data);
detect_amd_patch_level(cpu);
if (revision == cpu->arch.patch_level) {
dprintf("CPU %d: update failed\n", currentCPU);
} else {
if (sLoadedUcodeUpdate == NULL)
sLoadedUcodeUpdate = update;
dprintf("CPU %d: updated from revision 0x%" B_PRIx32 " to 0x%" B_PRIx32
"\n", currentCPU, revision, cpu->arch.patch_level);
}
} else {
dprintf("CPU %d: no update found\n", currentCPU);
}
if (currentCPU != 0)
release_spinlock(&sUcodeUpdateLock);
}
@ -1137,7 +1254,7 @@ load_microcode(int currentCPU)
static void
detect_cpu(int currentCPU)
detect_cpu(int currentCPU, bool full = true)
{
cpu_ent* cpu = get_cpu_struct();
char vendorString[17];
@ -1171,11 +1288,13 @@ detect_cpu(int currentCPU)
cpu->arch.model = cpuid.eax_1.model;
cpu->arch.extended_model = cpuid.eax_1.extended_model;
cpu->arch.stepping = cpuid.eax_1.stepping;
dprintf("CPU %d: type %d family %d extended_family %d model %d "
"extended_model %d stepping %d, string '%s'\n",
currentCPU, cpu->arch.type, cpu->arch.family,
cpu->arch.extended_family, cpu->arch.model,
cpu->arch.extended_model, cpu->arch.stepping, vendorString);
if (full) {
dprintf("CPU %d: type %d family %d extended_family %d model %d "
"extended_model %d stepping %d, string '%s'\n",
currentCPU, cpu->arch.type, cpu->arch.family,
cpu->arch.extended_family, cpu->arch.model,
cpu->arch.extended_model, cpu->arch.stepping, vendorString);
}
// figure out what vendor we have here
@ -1231,8 +1350,10 @@ detect_cpu(int currentCPU)
strlen(&cpu->arch.model_name[i]) + 1);
}
dprintf("CPU %d: vendor '%s' model name '%s'\n",
currentCPU, cpu->arch.vendor_name, cpu->arch.model_name);
if (full) {
dprintf("CPU %d: vendor '%s' model name '%s'\n",
currentCPU, cpu->arch.vendor_name, cpu->arch.model_name);
}
} else {
strlcpy(cpu->arch.model_name, "unknown", sizeof(cpu->arch.model_name));
}
@ -1242,6 +1363,9 @@ detect_cpu(int currentCPU)
cpu->arch.feature[FEATURE_COMMON] = cpuid.eax_1.features; // edx
cpu->arch.feature[FEATURE_EXT] = cpuid.eax_1.extended_features; // ecx
if (!full)
return;
if (maxExtendedLeaf >= 0x80000001) {
get_current_cpuid(&cpuid, 0x80000001, 0);
if (cpu->arch.vendor == VENDOR_AMD)
@ -1295,7 +1419,7 @@ detect_cpu(int currentCPU)
dump_feature_string(currentCPU, cpu);
#endif
#if DUMP_CPU_PATCHLEVEL
dprintf("CPU %d: patch_level %" B_PRIu32 "\n", currentCPU,
dprintf("CPU %d: patch_level %" B_PRIx32 "\n", currentCPU,
cpu->arch.patch_level);
#endif
}
@ -1517,6 +1641,7 @@ init_tsc(kernel_args* args)
status_t
arch_cpu_init_percpu(kernel_args* args, int cpu)
{
detect_cpu(cpu, false);
load_microcode(cpu);
detect_cpu(cpu);