From 56bb1bd5c992ca2dd09bb501c44707396273f438 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Duval?= Date: Thu, 20 Feb 2020 17:22:16 +0100 Subject: [PATCH] kernel: load cpu microcode update if loaded by the bootloader add optional fields for microcode in kernel_args. Change-Id: Ic5fb54cf6c9f489a2d1cdda00f63980c11dcdaeb Reviewed-on: https://review.haiku-os.org/c/haiku/+/2264 Reviewed-by: Adrien Destugues --- headers/private/kernel/arch/x86/arch_cpu.h | 35 ++++ headers/private/kernel/boot/kernel_args.h | 9 + src/system/boot/loader/main.cpp | 3 + src/system/kernel/arch/x86/arch_cpu.cpp | 194 ++++++++++++++++++++- src/system/kernel/main.cpp | 8 +- 5 files changed, 246 insertions(+), 3 deletions(-) diff --git a/headers/private/kernel/arch/x86/arch_cpu.h b/headers/private/kernel/arch/x86/arch_cpu.h index ed59e3ca29..c2826550b3 100644 --- a/headers/private/kernel/arch/x86/arch_cpu.h +++ b/headers/private/kernel/arch/x86/arch_cpu.h @@ -34,9 +34,12 @@ // MSR registers (possibly Intel specific) #define IA32_MSR_TSC 0x10 +#define IA32_MSR_PLATFORM_ID 0x17 #define IA32_MSR_APIC_BASE 0x1b #define IA32_MSR_SPEC_CTRL 0x48 #define IA32_MSR_PRED_CMD 0x49 +#define IA32_MSR_UCODE_WRITE 0x79 // IA32_BIOS_UPDT_TRIG +#define IA32_MSR_UCODE_REV 0x8b // IA32_BIOS_SIGN_ID #define IA32_MSR_PLATFORM_INFO 0xce #define IA32_MSR_MPERF 0xe7 #define IA32_MSR_APERF 0xe8 @@ -431,6 +434,7 @@ typedef struct arch_cpu_info { int stepping; int model; int extended_model; + uint32 patch_level; uint32 logical_apic_id; @@ -448,6 +452,37 @@ typedef struct arch_cpu_info { } arch_cpu_info; +// Reference Intel SDM Volume 3 9.11 "Microcode Update Facilities" +// https://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-vol-3a-part-1-manual.pdf +// 9.11.1 Table 9-7. Microcode Update Field Definitions +struct intel_microcode_header { + uint32 header_version; + uint32 update_revision; + uint32 date; + uint32 processor_signature; + uint32 checksum; + uint32 loader_revision; + uint32 processor_flags; + uint32 data_size; + uint32 total_size; + uint32 reserved[3]; +}; + + +struct intel_microcode_extended_signature_header { + uint32 extended_signature_count; + uint32 extended_checksum; + uint32 reserved[3]; +}; + + +struct intel_microcode_extended_signature { + uint32 processor_signature; + uint32 processor_flags; + uint32 checksum; +}; + + #define nop() __asm__ ("nop"::) #define x86_read_cr0() ({ \ diff --git a/headers/private/kernel/boot/kernel_args.h b/headers/private/kernel/boot/kernel_args.h index 9a6a45568d..d77ef49d5b 100644 --- a/headers/private/kernel/boot/kernel_args.h +++ b/headers/private/kernel/boot/kernel_args.h @@ -105,6 +105,15 @@ typedef struct kernel_args { // bootsplash data FixedWidthPointer boot_splash; + // optional microcode + FixedWidthPointer ucode_data; + uint32 ucode_data_size; + } _PACKED kernel_args; + +const size_t kernel_args_size_v1 = sizeof(kernel_args) + - sizeof(FixedWidthPointer) - sizeof(uint32); + + #endif /* KERNEL_BOOT_KERNEL_ARGS_H */ diff --git a/src/system/boot/loader/main.cpp b/src/system/boot/loader/main.cpp index 8b02173d01..73b66da682 100644 --- a/src/system/boot/loader/main.cpp +++ b/src/system/boot/loader/main.cpp @@ -126,6 +126,9 @@ main(stage2_args *args) load_modules(args, bootVolume); + gKernelArgs.ucode_data = NULL; + gKernelArgs.ucode_data_size = 0; + // apply boot settings apply_boot_settings(); diff --git a/src/system/kernel/arch/x86/arch_cpu.cpp b/src/system/kernel/arch/x86/arch_cpu.cpp index cf833bb1cb..eb2f738dcb 100644 --- a/src/system/kernel/arch/x86/arch_cpu.cpp +++ b/src/system/kernel/arch/x86/arch_cpu.cpp @@ -39,8 +39,9 @@ #include "paging/X86VMTranslationMap.h" -#define DUMP_FEATURE_STRING 1 +#define DUMP_FEATURE_STRING 1 #define DUMP_CPU_TOPOLOGY 1 +#define DUMP_CPU_PATCHLEVEL 1 /* cpu vendor info */ @@ -114,6 +115,11 @@ static uint32 sHierarchyShift[CPU_TOPOLOGY_LEVELS]; /* Cache topology information */ static uint32 sCacheSharingMask[CPU_MAX_CACHE_LEVEL]; +static void* sUcodeData = NULL; +static size_t sUcodeDataSize = 0; +static struct intel_microcode_header* sLoadedUcodeUpdate; +static spinlock sUcodeUpdateLock = B_SPINLOCK_INITIALIZER; + static status_t acpi_shutdown(bool rebootSystem) @@ -879,6 +885,174 @@ detect_cpu_topology(int currentCPU, cpu_ent* cpu, uint32 maxBasicLeaf, } +static void +detect_intel_patch_level(cpu_ent* cpu) +{ + if (cpu->arch.feature[FEATURE_EXT] & IA32_FEATURE_EXT_HYPERVISOR) { + cpu->arch.patch_level = 0; + return; + } + + x86_write_msr(IA32_MSR_UCODE_REV, 0); + cpuid_info cpuid; + get_current_cpuid(&cpuid, 1, 0); + + uint64 value = x86_read_msr(IA32_MSR_UCODE_REV); + cpu->arch.patch_level = value >> 32; +} + + +static void +detect_amd_patch_level(cpu_ent* cpu) +{ + if (cpu->arch.feature[FEATURE_EXT] & IA32_FEATURE_EXT_HYPERVISOR) { + cpu->arch.patch_level = 0; + return; + } + + uint64 value = x86_read_msr(IA32_MSR_UCODE_REV); + cpu->arch.patch_level = value >> 32; +} + + +static struct intel_microcode_header* +find_microcode_intel(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; + // 9.11.4 Platform Identification + uint64 platformBits = (x86_read_msr(IA32_MSR_PLATFORM_ID) >> 50) & 0x7; + uint64 mask = 1 << platformBits; + + while (size > 0) { + if (size < sizeof(struct intel_microcode_header)) { + dprintf("find_microcode_intel update is too small for header\n"); + break; + } + struct intel_microcode_header* header = + (struct intel_microcode_header*)data; + + uint32 totalSize = header->total_size; + uint32 dataSize = header->data_size; + if (dataSize == 0) { + dataSize = 2000; + totalSize = sizeof(struct intel_microcode_header) + + dataSize; + } + if (totalSize > size) { + dprintf("find_microcode_intel update is too small for data\n"); + break; + } + + uint32* dwords = (uint32*)data; + // prepare the next update + size -= totalSize; + data += totalSize; + + if (header->loader_revision != 1) { + dprintf("find_microcode_intel incorrect loader version\n"); + continue; + } + // 9.11.6 The microcode update data requires a 16-byte boundary + // alignment. + if (((addr_t)header % 16) != 0) { + dprintf("find_microcode_intel incorrect alignment\n"); + continue; + } + uint32 sum = 0; + for (uint32 i = 0; i < totalSize / 4; i++) { + sum += dwords[i]; + } + if (sum != 0) { + dprintf("find_microcode_intel incorrect checksum\n"); + continue; + } + if (patchLevel > header->update_revision) { + dprintf("find_microcode_intel update_revision is lower\n"); + continue; + } + if (signature == header->processor_signature + && (mask & header->processor_flags) != 0) { + return header; + } + if (totalSize <= (sizeof(struct intel_microcode_header) + dataSize + + sizeof(struct intel_microcode_extended_signature_header))) { + continue; + } + struct intel_microcode_extended_signature_header* extSigHeader = + (struct intel_microcode_extended_signature_header*)((addr_t)header + + sizeof(struct intel_microcode_header) + dataSize); + struct intel_microcode_extended_signature* extended_signature = + (struct intel_microcode_extended_signature*)((addr_t)extSigHeader + + sizeof(struct intel_microcode_extended_signature_header)); + for (uint32 i = 0; i < extSigHeader->extended_signature_count; i++) { + if (signature == extended_signature[i].processor_signature + && (mask & extended_signature[i].processor_flags) != 0) + return header; + } + } + return NULL; +} + + +static void +load_microcode_intel(int currentCPU, cpu_ent* cpu) +{ + // serialize for HT cores + if (currentCPU != 0) + acquire_spinlock(&sUcodeUpdateLock); + detect_intel_patch_level(cpu); + uint32 revision = cpu->arch.patch_level; + struct intel_microcode_header* update = sLoadedUcodeUpdate; + if (update == NULL) { + update = find_microcode_intel((addr_t)sUcodeData, sUcodeDataSize, + revision); + } + if (update != NULL) { + addr_t data = (addr_t)update + sizeof(struct intel_microcode_header); + wbinvd(); + x86_write_msr(IA32_MSR_UCODE_WRITE, data); + detect_intel_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 %" B_PRIu32 " to %" B_PRIu32 + "\n", currentCPU, revision, cpu->arch.patch_level); + } + } else { + dprintf("CPU %d: no update found\n", currentCPU); + } + if (currentCPU != 0) + release_spinlock(&sUcodeUpdateLock); +} + + +static void +load_microcode_amd(int currentCPU, cpu_ent* cpu) +{ + dprintf("CPU %d: no update found\n", currentCPU); +} + + +static void +load_microcode(int currentCPU) +{ + if (sUcodeData == NULL) + return; + cpu_ent* cpu = get_cpu_struct(); + if ((cpu->arch.feature[FEATURE_EXT] & IA32_FEATURE_EXT_HYPERVISOR) != 0) + return; + if (cpu->arch.vendor == VENDOR_INTEL) + load_microcode_intel(currentCPU, cpu); + else if (cpu->arch.vendor == VENDOR_AMD) + load_microcode_amd(currentCPU, cpu); +} + + static void detect_cpu(int currentCPU) { @@ -1023,9 +1197,18 @@ detect_cpu(int currentCPU) detect_cpu_topology(currentCPU, cpu, maxBasicLeaf, maxExtendedLeaf); + if (cpu->arch.vendor == VENDOR_INTEL) + detect_intel_patch_level(cpu); + else if (cpu->arch.vendor == VENDOR_AMD) + detect_amd_patch_level(cpu); + #if DUMP_FEATURE_STRING dump_feature_string(currentCPU, cpu); #endif +#if DUMP_CPU_PATCHLEVEL + dprintf("CPU %d: patch_level %" B_PRIu32 "\n", currentCPU, + cpu->arch.patch_level); +#endif } @@ -1136,6 +1319,7 @@ detect_amdc1e_noarat() status_t arch_cpu_init_percpu(kernel_args* args, int cpu) { + load_microcode(cpu); detect_cpu(cpu); if (!gCpuIdleFunc) { @@ -1158,6 +1342,14 @@ arch_cpu_init_percpu(kernel_args* args, int cpu) status_t arch_cpu_init(kernel_args* args) { + if (args->ucode_data != NULL + && args->ucode_data_size > 0) { + sUcodeData = args->ucode_data; + sUcodeDataSize = args->ucode_data_size; + } else { + dprintf("CPU: no microcode provided\n"); + } + // init the TSC -> system_time() conversion factors uint32 conversionFactor = args->arch_args.system_time_cv_factor; diff --git a/src/system/kernel/main.cpp b/src/system/kernel/main.cpp index b54a9c2feb..2071f6757f 100644 --- a/src/system/kernel/main.cpp +++ b/src/system/kernel/main.cpp @@ -96,7 +96,11 @@ non_boot_cpu_init(void* args, int currentCPU) extern "C" int _start(kernel_args *bootKernelArgs, int currentCPU) { - if (bootKernelArgs->kernel_args_size != sizeof(kernel_args) + if (bootKernelArgs->version == CURRENT_KERNEL_ARGS_VERSION + && bootKernelArgs->kernel_args_size == kernel_args_size_v1) { + sKernelArgs.ucode_data = NULL; + sKernelArgs.ucode_data_size = 0; + } else if (bootKernelArgs->kernel_args_size != sizeof(kernel_args) || bootKernelArgs->version != CURRENT_KERNEL_ARGS_VERSION) { // This is something we cannot handle right now - release kernels // should always be able to handle the kernel_args of earlier @@ -113,7 +117,7 @@ _start(kernel_args *bootKernelArgs, int currentCPU) // the passed in kernel args are in a non-allocated range of memory if (currentCPU == 0) - memcpy(&sKernelArgs, bootKernelArgs, sizeof(kernel_args)); + memcpy(&sKernelArgs, bootKernelArgs, bootKernelArgs->kernel_args_size); smp_cpu_rendezvous(&sCpuRendezvous2);