feat(amd64): add a bunch of stuff

This commit is contained in:
Loïc Grégoire
2026-04-15 19:37:24 -04:00
parent 986fe82383
commit a7cdfeb57a
4 changed files with 322 additions and 130 deletions
+102
View File
@@ -0,0 +1,102 @@
//! KVM paravirtualization support.
const std = @import("std");
const amd64 = @import("../root.zig");
pub const Features = packed struct(u32) {
clocksource: bool,
nop_io_delay: bool,
mmu_op: bool,
clocksource2: bool,
async_pf: bool,
steal_time: bool,
pv_eoi: bool,
pv_unhalt: bool,
_reserved0: u1,
pv_tlb_flush: bool,
async_pf_vmexit: bool,
pv_send_ipi: bool,
pv_poll_control: bool,
pv_sched_yield: bool,
async_pf_int: bool,
msi_ext_dest_id: bool,
hc_map_gpa_range: bool,
migration_control: bool,
_reserved1: u14,
};
pub const Info = struct {
features: Features,
};
const Msr = enum(u32) {
SystemTime = 0x4B564D01,
WallClock = 0x4B564D00,
AsyncPf = 0x4B564D02,
AsyncPfEn = 0x4B564D03,
StealTime = 0x4B564D04,
PvEoi = 0x4B564D05,
AsyncPfInt = 0x4B564D06,
_,
};
fn read_msr(msr: Msr) u64 {
return amd64.rdmsr(@intFromEnum(msr));
}
fn write_msr(msr: Msr, value: u64) void {
amd64.wrmsr(@intFromEnum(msr), value);
}
const PVClock = extern struct {
version: u32,
pad0: u32,
tsc_timestamp: u64,
system_time: u64,
tsc_to_system_mul: u32,
tsc_shift: i8,
flags: u8,
pad1: [2]u8,
};
pub const PVClockSource = struct {
page: *volatile PVClock,
time_at_boot: u64,
pub fn read(self: *PVClockSource) u64 {
while (true) {
const ver = @atomicLoad(u32, &self.page.version, .acquire);
if (ver & 1 != 0) continue;
const tsc_shift = self.page.tsc_shift;
const tsc_delta = amd64.rdtsc() - self.page.tsc_timestamp;
const shifted = if (tsc_shift >= 0)
tsc_delta << @intCast(tsc_shift)
else
tsc_delta >> @intCast(-tsc_shift);
const ns = self.page.system_time +
(shifted * @as(u128, self.page.tsc_to_system_mul) >> 32);
if (@atomicLoad(u32, &self.page.version, .acquire) == ver)
return @as(u64, @truncate(ns)) - self.time_at_boot;
}
}
pub fn init(phys_page: usize, virt_page: usize) PVClockSource {
const page: *volatile PVClock = @ptrFromInt(virt_page);
write_msr(.SystemTime, phys_page | 1);
var src = PVClockSource{ .page = page, .time_at_boot = 0 };
src.time_at_boot = src.read();
return src;
}
};
pub fn detect(highest_function: u32) Info {
if (highest_function < 0x40000001) {
return .{ .features = @bitCast(@as(u32, 0)) };
}
const r = amd64.cpuid(0x40000001, 0);
return Info{
.features = @bitCast(r.eax),
};
}
+61
View File
@@ -0,0 +1,61 @@
//! Hypervisor detection and identification.
const std = @import("std");
const amd64 = @import("root.zig");
pub const kvm = @import("hv/kvm.zig");
pub const Vendor = enum(u32) {
Unknown,
KVM,
TCG,
VMWare,
};
pub const VendorData = union(Vendor) {
Unknown: void,
KVM: kvm.Info,
TCG: void,
VMWare: void,
};
pub const Info = struct {
vendor: Vendor,
data: VendorData,
};
pub var info: ?Info = null;
const known_vendors = [_]struct {
string: *const [12:0]u8,
vendor: Vendor,
}{
.{ .string = "KVMKVMKVM\x00\x00\x00", .vendor = .KVM },
.{ .string = "TCGTCGTCG\x00\x00\x00", .vendor = .TCG },
.{ .string = "VMwareVMware", .vendor = .VMWare },
};
pub fn detect() void {
const r = amd64.CpuidRequest.execute(.HypervisorId);
var hypervisor_brand: [12]u8 = undefined;
std.mem.writeInt(u32, hypervisor_brand[0..4], r.ebx, .little);
std.mem.writeInt(u32, hypervisor_brand[4..8], r.ecx, .little);
std.mem.writeInt(u32, hypervisor_brand[8..12], r.edx, .little);
var vendor = Vendor.Unknown;
for (known_vendors) |h| {
if (std.mem.eql(u8, hypervisor_brand[0..], h.string[0..])) {
vendor = h.vendor;
break;
}
}
const data: VendorData = switch (vendor) {
.KVM => .{ .KVM = kvm.detect(r.eax) },
.Unknown, .TCG, .VMWare => .{ .Unknown = {} },
};
info = Info{
.vendor = vendor,
.data = data,
};
}
+155 -92
View File
@@ -1,6 +1,8 @@
//! Wrappers over amd64 CPU definitions and CPUID. //! Wrappers over amd64 CPU definitions and CPUID.
const std = @import("std"); const std = @import("std");
pub const hypervisor = @import("hypervisor.zig");
pub const name = "amd64"; pub const name = "amd64";
pub const Msr = enum(u32) { pub const Msr = enum(u32) {
@@ -24,6 +26,24 @@ pub const Msr = enum(u32) {
_, _,
}; };
pub const Efer = packed struct(u64) {
sce: bool,
reserved0: u7,
lme: bool,
reserved1: u1,
lma: bool,
nxe: bool,
svme: bool,
lmsle: bool,
ffxsr: bool,
tce: bool,
reserved2: u1,
mcommit: bool,
interruptible_wb: bool,
uaie: bool,
reserved3: u44,
};
pub const IrqFrame = extern struct { pub const IrqFrame = extern struct {
r15: u64, r15: u64,
r14: u64, r14: u64,
@@ -173,11 +193,11 @@ pub fn pio_write(comptime T: type, port: u16, value: T) void {
pub const RFlags = packed struct(u64) { pub const RFlags = packed struct(u64) {
carry: bool, carry: bool,
_reserved0: u1, reserved0: u1,
parity: bool, parity: bool,
_reserved1: u1, reserved1: u1,
auxiliary: bool, auxiliary: bool,
_reserved2: u1, reserved2: u1,
zero: bool, zero: bool,
sign: bool, sign: bool,
trap: bool, trap: bool,
@@ -186,14 +206,14 @@ pub const RFlags = packed struct(u64) {
overflow: bool, overflow: bool,
iopl: u2, iopl: u2,
nested_task: bool, nested_task: bool,
_reserved3: u1, reserved3: u1,
resume_: bool, resume_: bool,
virtual_8086_mode: bool, virtual_8086_mode: bool,
alignment_check: bool, alignment_check: bool,
virtual_interrupt: bool, virtual_interrupt: bool,
virtual_interrupt_pending: bool, virtual_interrupt_pending: bool,
id: bool, id: bool,
_reserved4: u42, reserved4: u42,
}; };
pub inline fn rflags() RFlags { pub inline fn rflags() RFlags {
@@ -221,17 +241,22 @@ pub const Cr0 = packed struct(u64) {
ts: bool, ts: bool,
et: bool, et: bool,
ne: bool, ne: bool,
reserved: u11, reserved0: u11,
wp: bool, wp: bool,
reserved2: u1, reserved1: u1,
am: bool, am: bool,
reserved3: u10, reserved2: u10,
nw: bool, nw: bool,
cd: bool, cd: bool,
pg: bool, pg: bool,
reserved4: u32, reserved3: u31,
}; };
comptime {
_ = Cr0;
_ = Cr4;
}
pub const Cr4 = packed struct(u64) { pub const Cr4 = packed struct(u64) {
vme: bool, vme: bool,
pvi: bool, pvi: bool,
@@ -242,15 +267,15 @@ pub const Cr4 = packed struct(u64) {
mce: bool, mce: bool,
pge: bool, pge: bool,
pce: bool, pce: bool,
osfxr: bool, osfxsr: bool,
osxmmEex: bool, osxmmexcpt: bool,
umip: bool, umip: bool,
la57: bool, la57: bool,
reserved: u3, reserved0: u3,
fsgsbase: bool, fsgsbase: bool,
pcide: bool, pcide: bool,
osxsave: bool, osxsave: bool,
reserve2: u1, reserved1: u1,
smep: bool, smep: bool,
smap: bool, smap: bool,
pke: bool, pke: bool,
@@ -292,37 +317,37 @@ pub inline fn cpuid(leaf: u32, subleaf: u32) CpuidResult {
} }
pub const CpuidRequest = union(enum) { pub const CpuidRequest = union(enum) {
vendor_info, VendorInfo,
feature_info, FeatureInfo,
extended_info, ExtendedInfo,
highest_extended_function, HighestExtendedFunction,
extended_features: ExtendedFeaturesSubLeaf, ExtendedFeatures: ExtendedFeaturesSubLeaf,
brand_string: BrandStringPart, BrandString: BrandStringPart,
power_management_info, PowerManagementInfo,
hypervisor_id, HypervisorId,
pub const ExtendedFeaturesSubLeaf = enum(u32) { pub const ExtendedFeaturesSubLeaf = enum(u32) {
first = 0, First = 0,
second = 1, Second = 1,
third = 3, Third = 3,
}; };
pub const BrandStringPart = enum(u32) { pub const BrandStringPart = enum(u32) {
part0 = 0x80000002, Part0 = 0x80000002,
part1 = 0x80000003, Part1 = 0x80000003,
part2 = 0x80000004, Part2 = 0x80000004,
}; };
pub fn execute(self: CpuidRequest) CpuidResult { pub fn execute(self: CpuidRequest) CpuidResult {
const leaf, const subleaf = switch (self) { const leaf, const subleaf = switch (self) {
.vendor_info => .{ 0x0, 0 }, .VendorInfo => .{ 0x0, 0 },
.feature_info => .{ 0x1, 0 }, .FeatureInfo => .{ 0x1, 0 },
.highest_extended_function => .{ 0x80000000, 0 }, .HighestExtendedFunction => .{ 0x80000000, 0 },
.extended_info => .{ 0x80000001, 0 }, .ExtendedInfo => .{ 0x80000001, 0 },
.power_management_info => .{ 0x80000007, 0 }, .PowerManagementInfo => .{ 0x80000007, 0 },
.extended_features => |s| .{ 0x7, @intFromEnum(s) }, .ExtendedFeatures => |s| .{ 0x7, @intFromEnum(s) },
.brand_string => |p| .{ @intFromEnum(p), 0 }, .BrandString => |p| .{ @intFromEnum(p), 0 },
.hypervisor_id => .{ 0x40000000, 0 }, .HypervisorId => .{ 0x40000000, 0 },
}; };
return cpuid(leaf, subleaf); return cpuid(leaf, subleaf);
} }
@@ -345,7 +370,7 @@ const FeatureInfoEcx = packed struct(u32) {
cx16: bool, cx16: bool,
xtpr: bool, xtpr: bool,
pdcm: bool, pdcm: bool,
_reserved: u1, reserved0: u1,
pcid: bool, pcid: bool,
dca: bool, dca: bool,
sse4_1: bool, sse4_1: bool,
@@ -374,7 +399,7 @@ const FeatureInfoEdx = packed struct(u32) {
mce: bool, mce: bool,
cx8: bool, cx8: bool,
apic: bool, apic: bool,
_reserved0: u1, reserved0: u1,
sep: bool, sep: bool,
mtrr: bool, mtrr: bool,
pge: bool, pge: bool,
@@ -384,44 +409,69 @@ const FeatureInfoEdx = packed struct(u32) {
pse36: bool, pse36: bool,
psn: bool, psn: bool,
clfsh: bool, clfsh: bool,
_reserved1: u1, reserved1: u1,
ds: bool, ds: bool,
acpi: bool, acpi: bool,
mmx: bool, mmx: bool,
fxsave: bool, fxsave: bool,
sse: bool, sse: bool,
sse2: bool, sse2: bool,
ignore: u5, reserved2: u5,
}; };
const ExtendedFeaturesEcx = packed struct(u32) { const ExtendedFeaturesEcx = packed struct(u32) {
_reserved0: u16, reserved0: u2,
umip: bool,
reserved1: u13,
la57: bool, la57: bool,
_reserved1: u5, reserved2: u5,
rdpid: bool, rdpid: bool,
_reserved2: u9, reserved3: u9,
}; };
const ExtendedFeaturesEbx = packed struct(u32) { const ExtendedFeaturesEbx = packed struct(u32) {
ignore: u7, reserved0: u7,
smep: bool, smep: bool,
ignore2: u12, reserved1: u12,
smap: bool, smap: bool,
ignore3: u11, reserved2: u11,
}; };
const ExtendedProcessorInfoEdx = packed struct(u32) { const ExtendedProcessorInfoEdx = packed struct(u32) {
ignore: u20, fpu: bool,
vme: bool,
de: bool,
pse: bool,
tsc: bool,
msr: bool,
pae: bool,
mce: bool,
cx8: bool,
apic: bool,
reserved0: u1,
syscall_sysret: bool,
mtrr: bool,
pge: bool,
mca: bool,
cmov: bool,
pat: bool,
pse36: bool,
reserved1: u1,
mp: bool,
nx: bool, nx: bool,
ignore2: u5, reserved2: u5,
pdpe1gb: bool, pdpe1gb: bool,
ignore3: u5, rdtscp: bool,
reserved3: u1,
lm: bool,
_3dnowext: bool,
_3dnow: bool,
}; };
const PowerManagementInfoEdx = packed struct(u32) { const PowerManagementInfoEdx = packed struct(u32) {
ignore: u8, reserved0: u8,
invtsc: bool, invtsc: bool,
ignore2: u23, reserved1: u23,
}; };
fn assert_bit(comptime T: type, comptime field_name: []const u8, comptime expected_bit: u5) void { fn assert_bit(comptime T: type, comptime field_name: []const u8, comptime expected_bit: u5) void {
@@ -446,6 +496,22 @@ comptime {
assert_bit(ExtendedFeaturesEbx, "smep", 7); assert_bit(ExtendedFeaturesEbx, "smep", 7);
} }
pub const CpuVendor = enum {
Intel,
Amd,
Hygon,
Unknown,
};
const known_vendors = [_]struct {
string: *const [12:0]u8,
vendor: CpuVendor,
}{
.{ .string = "GenuineIntel", .vendor = .Intel },
.{ .string = "AuthenticAMD", .vendor = .Amd },
.{ .string = "HygonGenuine", .vendor = .Hygon },
};
pub const CpuFeatures = struct { pub const CpuFeatures = struct {
x2apic: bool, x2apic: bool,
five_level_paging: bool, five_level_paging: bool,
@@ -459,7 +525,10 @@ pub const CpuFeatures = struct {
smap: bool, smap: bool,
smep: bool, smep: bool,
pge: bool, pge: bool,
vendor_string: [12]u8, syscall: bool,
umip: bool,
vendor: CpuVendor,
family: u8,
brand_string: [48]u8, brand_string: [48]u8,
}; };
@@ -474,42 +543,20 @@ pub const HypervisorInfo = struct {
highest_function: u32, highest_function: u32,
}; };
pub var hypervisor_info: ?HypervisorInfo = null;
pub var cpu_features: CpuFeatures = undefined; pub var cpu_features: CpuFeatures = undefined;
const known_hypervisors = [_]struct { fn detect_vendor(string: [12]u8) CpuVendor {
vendor_string: *const [12:0]u8, for (known_vendors) |v| {
vendor: HypervisorVendor, if (std.mem.eql(u8, string[0..], v.string[0..])) {
}{ return v.vendor;
.{ .vendor_string = "KVMKVMKVM\x00\x00\x00", .vendor = HypervisorVendor.KVM },
};
fn detect_hypervisor() void {
const r = CpuidRequest.execute(.hypervisor_id);
var hypervisor_brand: [12]u8 = undefined;
std.mem.writeInt(u32, hypervisor_brand[0..4], r.ebx, .little);
std.mem.writeInt(u32, hypervisor_brand[4..8], r.ecx, .little);
std.mem.writeInt(u32, hypervisor_brand[8..12], r.edx, .little);
var vendor = HypervisorVendor.Unknown;
for (known_hypervisors) |h| {
if (std.mem.eql(u8, hypervisor_brand[0..], h.vendor_string[0..])) {
vendor = h.vendor;
break;
} }
} }
return .Unknown;
hypervisor_info = HypervisorInfo{
.brand_string = hypervisor_brand,
.vendor = vendor,
.highest_function = r.eax,
};
} }
pub fn detect_cpu_features() void { pub fn detect_cpu_features() void {
const vendor_info = CpuidRequest.execute(.vendor_info); const vendor_info = CpuidRequest.execute(.VendorInfo);
const max_ext = CpuidRequest.execute(.highest_extended_function).eax; const max_ext = CpuidRequest.execute(.HighestExtendedFunction).eax;
const max_basic = vendor_info.eax; const max_basic = vendor_info.eax;
@@ -525,7 +572,10 @@ pub fn detect_cpu_features() void {
var smap = false; var smap = false;
var smep = false; var smep = false;
var pge = false; var pge = false;
var hypervisor = false; var hypervisor_flag = false;
var syscall = false;
var family: u8 = 0;
var umip = false;
var vendor_string: [12]u8 = undefined; var vendor_string: [12]u8 = undefined;
var brand_string: [48]u8 = undefined; var brand_string: [48]u8 = undefined;
@@ -534,8 +584,10 @@ pub fn detect_cpu_features() void {
std.mem.writeInt(u32, vendor_string[4..8], vendor_info.edx, .little); std.mem.writeInt(u32, vendor_string[4..8], vendor_info.edx, .little);
std.mem.writeInt(u32, vendor_string[8..12], vendor_info.ecx, .little); std.mem.writeInt(u32, vendor_string[8..12], vendor_info.ecx, .little);
const vendor = detect_vendor(vendor_string);
if (max_basic >= 0x1) { if (max_basic >= 0x1) {
const r = CpuidRequest.execute(.feature_info); const r = CpuidRequest.execute(.FeatureInfo);
const ecx: FeatureInfoEcx = @bitCast(r.ecx); const ecx: FeatureInfoEcx = @bitCast(r.ecx);
const edx: FeatureInfoEdx = @bitCast(r.edx); const edx: FeatureInfoEdx = @bitCast(r.edx);
@@ -545,30 +597,38 @@ pub fn detect_cpu_features() void {
pcid = ecx.pcid; pcid = ecx.pcid;
fxsave = edx.fxsave; fxsave = edx.fxsave;
pge = edx.pge; pge = edx.pge;
hypervisor = ecx.hypervisor; hypervisor_flag = ecx.hypervisor;
const sig = r.eax;
family = @truncate((sig >> 8) & 0xf);
if (family == 0xf) {
family +%= @truncate((sig >> 20) & 0xff);
}
} }
if (max_basic >= 0x7) { if (max_basic >= 0x7) {
const r = CpuidRequest.execute(.{ .extended_features = .first }); const r = CpuidRequest.execute(.{ .ExtendedFeatures = .First });
const ecx: ExtendedFeaturesEcx = @bitCast(r.ecx); const ecx: ExtendedFeaturesEcx = @bitCast(r.ecx);
const ebx: ExtendedFeaturesEbx = @bitCast(r.ebx); const ebx: ExtendedFeaturesEbx = @bitCast(r.ebx);
smap = ebx.smap; smap = ebx.smap;
smep = ebx.smep; smep = ebx.smep;
five_level_paging = ecx.la57; five_level_paging = ecx.la57;
umip = ecx.umip;
} }
if (max_ext >= 0x80000001) { if (max_ext >= 0x80000001) {
const r = CpuidRequest.execute(.extended_info); const r = CpuidRequest.execute(.ExtendedInfo);
const edx: ExtendedProcessorInfoEdx = @bitCast(r.edx); const edx: ExtendedProcessorInfoEdx = @bitCast(r.edx);
gib_pages = edx.pdpe1gb; gib_pages = edx.pdpe1gb;
nx = edx.nx; nx = edx.nx;
syscall = edx.syscall_sysret;
} }
if (max_ext >= 0x80000004) { if (max_ext >= 0x80000004) {
const parts = [_]CpuidResult{ const parts = [_]CpuidResult{
CpuidRequest.execute(.{ .brand_string = .part0 }), CpuidRequest.execute(.{ .BrandString = .Part0 }),
CpuidRequest.execute(.{ .brand_string = .part1 }), CpuidRequest.execute(.{ .BrandString = .Part1 }),
CpuidRequest.execute(.{ .brand_string = .part2 }), CpuidRequest.execute(.{ .BrandString = .Part2 }),
}; };
var off: usize = 0; var off: usize = 0;
@@ -582,13 +642,13 @@ pub fn detect_cpu_features() void {
} }
if (max_ext >= 0x80000007) { if (max_ext >= 0x80000007) {
const r = CpuidRequest.execute(.power_management_info); const r = CpuidRequest.execute(.PowerManagementInfo);
const edx: PowerManagementInfoEdx = @bitCast(r.edx); const edx: PowerManagementInfoEdx = @bitCast(r.edx);
invtsc = edx.invtsc; invtsc = edx.invtsc;
} }
if (hypervisor) { if (hypervisor_flag) {
detect_hypervisor(); hypervisor.detect();
} }
cpu_features = .{ cpu_features = .{
@@ -603,9 +663,12 @@ pub fn detect_cpu_features() void {
.pcid = pcid, .pcid = pcid,
.smap = smap, .smap = smap,
.smep = smep, .smep = smep,
.vendor_string = vendor_string, .vendor = vendor,
.brand_string = brand_string, .brand_string = brand_string,
.pge = pge, .pge = pge,
.family = family,
.umip = umip,
.syscall = syscall,
}; };
} }
+4 -38
View File
@@ -1,22 +1,11 @@
const amd64 = @import("arch"); const amd64 = @import("arch");
const kvm = amd64.hypervisor.kvm;
const b = @import("base"); const b = @import("base");
const std = @import("std"); const std = @import("std");
const mm = b.mm; const mm = b.mm;
const ke = b.ke; const ke = b.ke;
const PVClock = extern struct { var pvclock_src: kvm.PVClockSource = undefined;
version: u32,
pad0: u32,
tsc_timestamp: u64,
system_time: u64,
tsc_to_system_mul: u32,
tsc_shift: i8,
flags: u8,
pad1: [2]u8,
};
var pvclock: *PVClock = undefined;
var pvclock_tc: ke.TimeCounter = .{ var pvclock_tc: ke.TimeCounter = .{
.name = "pvclock", .name = "pvclock",
.quality = 75, .quality = 75,
@@ -27,35 +16,12 @@ var pvclock_tc: ke.TimeCounter = .{
.n = 0, .n = 0,
}; };
var time_at_boot: u64 = 0;
pub fn init() linksection(b.init) void { pub fn init() linksection(b.init) void {
const page = mm.phys.alloc(); const page = mm.phys.alloc();
pvclock = @ptrFromInt(mm.p2v(page)); pvclock_src = kvm.PVClockSource.init(page, mm.p2v(page));
amd64.write_msr(.KvmSystemTimeNew, page | 1);
time_at_boot = read_pvclock();
ke.time.register_source(&pvclock_tc); ke.time.register_source(&pvclock_tc);
} }
fn read_pvclock() u64 { fn read_pvclock() u64 {
while (true) { return pvclock_src.read();
const ver = b.mmio_read(u32, @intFromPtr(&pvclock.version));
if (ver & 1 != 0) continue; // Update in progress.
const tsc_shift = b.mmio_read(i8, @intFromPtr(&pvclock.tsc_shift));
const tsc_delta = amd64.rdtsc() - b.mmio_read(u64, @intFromPtr(&pvclock.tsc_timestamp));
const shifted = if (tsc_shift >= 0)
tsc_delta << @intCast(tsc_shift)
else
tsc_delta >> @intCast(-tsc_shift);
const ns = b.mmio_read(u64, @intFromPtr(&pvclock.system_time)) +
(shifted * @as(u128, b.mmio_read(u32, @intFromPtr(&pvclock.tsc_to_system_mul))) >> 32);
if (b.mmio_read(u32, @intFromPtr(&pvclock.version)) == ver) return @as(u64, @truncate(ns)) - time_at_boot;
}
} }