diff --git a/src/arch/amd64/hv/kvm.zig b/src/arch/amd64/hv/kvm.zig new file mode 100644 index 0000000..808d64c --- /dev/null +++ b/src/arch/amd64/hv/kvm.zig @@ -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), + }; +} diff --git a/src/arch/amd64/hypervisor.zig b/src/arch/amd64/hypervisor.zig new file mode 100644 index 0000000..5704af5 --- /dev/null +++ b/src/arch/amd64/hypervisor.zig @@ -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, + }; +} diff --git a/src/arch/amd64/root.zig b/src/arch/amd64/root.zig index fa34bca..c8f69dc 100644 --- a/src/arch/amd64/root.zig +++ b/src/arch/amd64/root.zig @@ -1,6 +1,8 @@ //! Wrappers over amd64 CPU definitions and CPUID. const std = @import("std"); +pub const hypervisor = @import("hypervisor.zig"); + pub const name = "amd64"; 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 { r15: 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) { carry: bool, - _reserved0: u1, + reserved0: u1, parity: bool, - _reserved1: u1, + reserved1: u1, auxiliary: bool, - _reserved2: u1, + reserved2: u1, zero: bool, sign: bool, trap: bool, @@ -186,14 +206,14 @@ pub const RFlags = packed struct(u64) { overflow: bool, iopl: u2, nested_task: bool, - _reserved3: u1, + reserved3: u1, resume_: bool, virtual_8086_mode: bool, alignment_check: bool, virtual_interrupt: bool, virtual_interrupt_pending: bool, id: bool, - _reserved4: u42, + reserved4: u42, }; pub inline fn rflags() RFlags { @@ -221,17 +241,22 @@ pub const Cr0 = packed struct(u64) { ts: bool, et: bool, ne: bool, - reserved: u11, + reserved0: u11, wp: bool, - reserved2: u1, + reserved1: u1, am: bool, - reserved3: u10, + reserved2: u10, nw: bool, cd: bool, pg: bool, - reserved4: u32, + reserved3: u31, }; +comptime { + _ = Cr0; + _ = Cr4; +} + pub const Cr4 = packed struct(u64) { vme: bool, pvi: bool, @@ -242,15 +267,15 @@ pub const Cr4 = packed struct(u64) { mce: bool, pge: bool, pce: bool, - osfxr: bool, - osxmmEex: bool, + osfxsr: bool, + osxmmexcpt: bool, umip: bool, la57: bool, - reserved: u3, + reserved0: u3, fsgsbase: bool, pcide: bool, osxsave: bool, - reserve2: u1, + reserved1: u1, smep: bool, smap: bool, pke: bool, @@ -292,37 +317,37 @@ pub inline fn cpuid(leaf: u32, subleaf: u32) CpuidResult { } pub const CpuidRequest = union(enum) { - vendor_info, - feature_info, - extended_info, - highest_extended_function, - extended_features: ExtendedFeaturesSubLeaf, - brand_string: BrandStringPart, - power_management_info, - hypervisor_id, + VendorInfo, + FeatureInfo, + ExtendedInfo, + HighestExtendedFunction, + ExtendedFeatures: ExtendedFeaturesSubLeaf, + BrandString: BrandStringPart, + PowerManagementInfo, + HypervisorId, pub const ExtendedFeaturesSubLeaf = enum(u32) { - first = 0, - second = 1, - third = 3, + First = 0, + Second = 1, + Third = 3, }; pub const BrandStringPart = enum(u32) { - part0 = 0x80000002, - part1 = 0x80000003, - part2 = 0x80000004, + Part0 = 0x80000002, + Part1 = 0x80000003, + Part2 = 0x80000004, }; pub fn execute(self: CpuidRequest) CpuidResult { const leaf, const subleaf = switch (self) { - .vendor_info => .{ 0x0, 0 }, - .feature_info => .{ 0x1, 0 }, - .highest_extended_function => .{ 0x80000000, 0 }, - .extended_info => .{ 0x80000001, 0 }, - .power_management_info => .{ 0x80000007, 0 }, - .extended_features => |s| .{ 0x7, @intFromEnum(s) }, - .brand_string => |p| .{ @intFromEnum(p), 0 }, - .hypervisor_id => .{ 0x40000000, 0 }, + .VendorInfo => .{ 0x0, 0 }, + .FeatureInfo => .{ 0x1, 0 }, + .HighestExtendedFunction => .{ 0x80000000, 0 }, + .ExtendedInfo => .{ 0x80000001, 0 }, + .PowerManagementInfo => .{ 0x80000007, 0 }, + .ExtendedFeatures => |s| .{ 0x7, @intFromEnum(s) }, + .BrandString => |p| .{ @intFromEnum(p), 0 }, + .HypervisorId => .{ 0x40000000, 0 }, }; return cpuid(leaf, subleaf); } @@ -345,7 +370,7 @@ const FeatureInfoEcx = packed struct(u32) { cx16: bool, xtpr: bool, pdcm: bool, - _reserved: u1, + reserved0: u1, pcid: bool, dca: bool, sse4_1: bool, @@ -374,7 +399,7 @@ const FeatureInfoEdx = packed struct(u32) { mce: bool, cx8: bool, apic: bool, - _reserved0: u1, + reserved0: u1, sep: bool, mtrr: bool, pge: bool, @@ -384,44 +409,69 @@ const FeatureInfoEdx = packed struct(u32) { pse36: bool, psn: bool, clfsh: bool, - _reserved1: u1, + reserved1: u1, ds: bool, acpi: bool, mmx: bool, fxsave: bool, sse: bool, sse2: bool, - ignore: u5, + reserved2: u5, }; const ExtendedFeaturesEcx = packed struct(u32) { - _reserved0: u16, + reserved0: u2, + umip: bool, + reserved1: u13, la57: bool, - _reserved1: u5, + reserved2: u5, rdpid: bool, - _reserved2: u9, + reserved3: u9, }; const ExtendedFeaturesEbx = packed struct(u32) { - ignore: u7, + reserved0: u7, smep: bool, - ignore2: u12, + reserved1: u12, smap: bool, - ignore3: u11, + reserved2: u11, }; 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, - ignore2: u5, + reserved2: u5, pdpe1gb: bool, - ignore3: u5, + rdtscp: bool, + reserved3: u1, + lm: bool, + _3dnowext: bool, + _3dnow: bool, }; const PowerManagementInfoEdx = packed struct(u32) { - ignore: u8, + reserved0: u8, invtsc: bool, - ignore2: u23, + reserved1: u23, }; 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); } +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 { x2apic: bool, five_level_paging: bool, @@ -459,7 +525,10 @@ pub const CpuFeatures = struct { smap: bool, smep: bool, pge: bool, - vendor_string: [12]u8, + syscall: bool, + umip: bool, + vendor: CpuVendor, + family: u8, brand_string: [48]u8, }; @@ -474,42 +543,20 @@ pub const HypervisorInfo = struct { highest_function: u32, }; -pub var hypervisor_info: ?HypervisorInfo = null; pub var cpu_features: CpuFeatures = undefined; -const known_hypervisors = [_]struct { - vendor_string: *const [12:0]u8, - vendor: HypervisorVendor, -}{ - .{ .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; +fn detect_vendor(string: [12]u8) CpuVendor { + for (known_vendors) |v| { + if (std.mem.eql(u8, string[0..], v.string[0..])) { + return v.vendor; } } - - hypervisor_info = HypervisorInfo{ - .brand_string = hypervisor_brand, - .vendor = vendor, - .highest_function = r.eax, - }; + return .Unknown; } pub fn detect_cpu_features() void { - const vendor_info = CpuidRequest.execute(.vendor_info); - const max_ext = CpuidRequest.execute(.highest_extended_function).eax; + const vendor_info = CpuidRequest.execute(.VendorInfo); + const max_ext = CpuidRequest.execute(.HighestExtendedFunction).eax; const max_basic = vendor_info.eax; @@ -525,7 +572,10 @@ pub fn detect_cpu_features() void { var smap = false; var smep = 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 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[8..12], vendor_info.ecx, .little); + const vendor = detect_vendor(vendor_string); + if (max_basic >= 0x1) { - const r = CpuidRequest.execute(.feature_info); + const r = CpuidRequest.execute(.FeatureInfo); const ecx: FeatureInfoEcx = @bitCast(r.ecx); const edx: FeatureInfoEdx = @bitCast(r.edx); @@ -545,30 +597,38 @@ pub fn detect_cpu_features() void { pcid = ecx.pcid; fxsave = edx.fxsave; 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) { - const r = CpuidRequest.execute(.{ .extended_features = .first }); + const r = CpuidRequest.execute(.{ .ExtendedFeatures = .First }); const ecx: ExtendedFeaturesEcx = @bitCast(r.ecx); const ebx: ExtendedFeaturesEbx = @bitCast(r.ebx); smap = ebx.smap; smep = ebx.smep; five_level_paging = ecx.la57; + umip = ecx.umip; } if (max_ext >= 0x80000001) { - const r = CpuidRequest.execute(.extended_info); + const r = CpuidRequest.execute(.ExtendedInfo); const edx: ExtendedProcessorInfoEdx = @bitCast(r.edx); gib_pages = edx.pdpe1gb; nx = edx.nx; + syscall = edx.syscall_sysret; } if (max_ext >= 0x80000004) { const parts = [_]CpuidResult{ - CpuidRequest.execute(.{ .brand_string = .part0 }), - CpuidRequest.execute(.{ .brand_string = .part1 }), - CpuidRequest.execute(.{ .brand_string = .part2 }), + CpuidRequest.execute(.{ .BrandString = .Part0 }), + CpuidRequest.execute(.{ .BrandString = .Part1 }), + CpuidRequest.execute(.{ .BrandString = .Part2 }), }; var off: usize = 0; @@ -582,13 +642,13 @@ pub fn detect_cpu_features() void { } if (max_ext >= 0x80000007) { - const r = CpuidRequest.execute(.power_management_info); + const r = CpuidRequest.execute(.PowerManagementInfo); const edx: PowerManagementInfoEdx = @bitCast(r.edx); invtsc = edx.invtsc; } - if (hypervisor) { - detect_hypervisor(); + if (hypervisor_flag) { + hypervisor.detect(); } cpu_features = .{ @@ -603,9 +663,12 @@ pub fn detect_cpu_features() void { .pcid = pcid, .smap = smap, .smep = smep, - .vendor_string = vendor_string, + .vendor = vendor, .brand_string = brand_string, .pge = pge, + .family = family, + .umip = umip, + .syscall = syscall, }; } diff --git a/src/platform/pc/pvclock.zig b/src/platform/pc/pvclock.zig index 50bf77a..3880409 100644 --- a/src/platform/pc/pvclock.zig +++ b/src/platform/pc/pvclock.zig @@ -1,22 +1,11 @@ const amd64 = @import("arch"); +const kvm = amd64.hypervisor.kvm; const b = @import("base"); const std = @import("std"); const mm = b.mm; const ke = b.ke; -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, -}; - -var pvclock: *PVClock = undefined; - +var pvclock_src: kvm.PVClockSource = undefined; var pvclock_tc: ke.TimeCounter = .{ .name = "pvclock", .quality = 75, @@ -27,35 +16,12 @@ var pvclock_tc: ke.TimeCounter = .{ .n = 0, }; -var time_at_boot: u64 = 0; - pub fn init() linksection(b.init) void { const page = mm.phys.alloc(); - pvclock = @ptrFromInt(mm.p2v(page)); - - amd64.write_msr(.KvmSystemTimeNew, page | 1); - - time_at_boot = read_pvclock(); - + pvclock_src = kvm.PVClockSource.init(page, mm.p2v(page)); ke.time.register_source(&pvclock_tc); } fn read_pvclock() u64 { - while (true) { - 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; - } + return pvclock_src.read(); }