Xenon/src/microcode/amd.zig

154 lines
4.5 KiB
Zig

// References:
// [1] Security Analysis of x86 Processor Microcode https://www.dcddcc.com/docs/2014_paper_microcode.pdf
// [2] NetBSD sys/arch/x86/x86/cpu_ucode_amd.c
const std = @import("std");
const assert = std.debug.assert;
const eql = std.mem.eql;
const cpu = @import("../cpu.zig");
const log = @import("../logger.zig").log;
const msr = @import("../msr.zig");
const MicrocodeError = @import("../microcode.zig").MicrocodeError;
// TODO Test
pub fn apply(microcode: []const u8) MicrocodeError!bool {
var mc = try Microcode.init(microcode);
const sign = cpu.CpuId.signature();
var rev_id: u16 = 0;
// Determine rev_id of processor
for (mc.cpu_equivalence_table) |entry| {
if (entry.processor_signature == sign.value) {
rev_id = entry.processor_rev_id;
}
}
if (rev_id == 0)
return false; // No patch available
// Find applicable patch
while (try mc.nextPatch()) |patch| {
if (patch.header.processor_rev_id == rev_id) {
log(.Debug, "[microcode/amd] Compatible microcode update found: {x}", .{patch.header.patch_id});
cpu.wrmsr(msr.ucode_amd_patchloader, @ptrToInt(&patch) + @sizeOf(PatchHeader));
return true;
}
}
return false;
}
pub fn parse(microcode: []const u8) MicrocodeError!Microcode {
return Microcode.init(microcode);
}
inline fn toU32(bytes: []const u8) u32 {
return @bytesToSlice(u32, bytes)[0];
}
const Microcode = struct {
const Self = @This();
const magic_value = [4]u8{ 'D', 'M', 'A', 0 };
cpu_equivalence_table: []CpuEquivalenceTable,
remaining_patches: []const u8,
pub fn init(microcode: []const u8) MicrocodeError!Self {
if (microcode.len < 4)
return MicrocodeError.EndOfData;
if (!eql(u8, microcode[0..4], &magic_value))
return MicrocodeError.Invalid;
if (microcode.len < 8)
return MicrocodeError.EndOfData;
if (toU32(microcode[4..8]) != 0)
return MicrocodeError.Invalid;
if (microcode.len < 12)
return MicrocodeError.EndOfData;
const table_size = toU32(microcode[8..12]);
const table_end = 12 + table_size;
return Self{
.cpu_equivalence_table = @bytesToSlice(CpuEquivalenceTable, microcode[12..table_end]),
.remaining_patches = microcode[table_end..],
};
}
// NOTE Should return ?(MicrocodeError!Patch)
// but that crashes the compiler
pub fn nextPatch(self: *Self) MicrocodeError!?Patch {
if (self.remaining_patches.len == 0)
return null;
if (self.remaining_patches.len < 4)
return MicrocodeError.EndOfData;
if (toU32(self.remaining_patches[0..4]) != 1)
return MicrocodeError.Invalid;
if (self.remaining_patches.len < 8)
return MicrocodeError.EndOfData;
const patch_size = toU32(self.remaining_patches[4..8]);
const patch_end = 8 + patch_size;
if (self.remaining_patches.len < patch_end)
return MicrocodeError.EndOfData;
const header = @ptrCast(*const PatchHeader, &self.remaining_patches[8]);
const microcode = self.remaining_patches[8 + @sizeOf(PatchHeader) .. patch_end];
if (header.patch_data_checksum != 0) {
// Determined through experimentation that 0 checksums always
// fail, these patches are probably the encrypted ones as
// mentioned in [1] 4.1.1
var checksum: u32 = 0;
for (@bytesToSlice(u32, microcode)) |v|
checksum +%= v;
if (checksum != header.patch_data_checksum)
return MicrocodeError.ChecksumMismatch;
}
self.remaining_patches = self.remaining_patches[patch_end..];
return Patch{
.header = header,
.microcode = microcode,
};
}
};
const CpuEquivalenceTable = packed struct {
processor_signature: u32,
errata_mask: u32,
errata_compare: u32,
processor_rev_id: u16,
reserved: u16,
};
const Patch = struct {
header: *const PatchHeader,
microcode: []const u8,
};
const PatchHeader = packed struct {
date: u32,
patch_id: u32,
patch_data_id: u16,
patch_data_len: u8,
init_flag: u8,
patch_data_checksum: u32,
nb_dev_id: u32,
sb_dev_id: u32,
processor_rev_id: u16,
nb_rev_id: u8,
sb_rev_id: u8,
bios_api_rev: u8,
_reserved1: u8, // Not a u24 or [3]u8 due to compiler bug
_reserved2: u8,
_reserved3: u8,
match_reg: [8]u32,
};