diff --git a/src/arch/amd64/root.zig b/src/arch/amd64/root.zig index 98009f4..f1629ab 100644 --- a/src/arch/amd64/root.zig +++ b/src/arch/amd64/root.zig @@ -137,6 +137,30 @@ pub inline fn hlt() void { asm volatile ("hlt"); } +pub fn pio_read(comptime T: type, port: u16) T { + if (@sizeOf(T) == 1) { + return inb(port); + } else if (@sizeOf(T) == 2) { + return inw(port); + } else if (@sizeOf(T) == 4) { + return inl(port); + } else { + @compileError("unsupported pio read size"); + } +} + +pub fn pio_write(comptime T: type, port: u16, value: T) void { + if (@sizeOf(T) == 1) { + outb(port, @as(u8, value)); + } else if (@sizeOf(T) == 2) { + outw(port, @as(u16, value)); + } else if (@sizeOf(T) == 4) { + outl(port, @as(u32, value)); + } else { + @compileError("unsupported pio write size"); + } +} + pub const RFlags = packed struct(u64) { carry: bool, _reserved0: u1, diff --git a/src/ex/init.zig b/src/ex/init.zig index d4b8a50..7db3a8a 100644 --- a/src/ex/init.zig +++ b/src/ex/init.zig @@ -7,7 +7,7 @@ const mm = b.mm; pub fn init(boot_info: *pl.BootInfo) void { mm.init(boot_info); - pl.late_init(); + pl.late_init(boot_info); ke.sched.late_init(); while (true) { diff --git a/src/platform/acpi/hpet.zig b/src/platform/acpi/hpet.zig new file mode 100644 index 0000000..9f39f0a --- /dev/null +++ b/src/platform/acpi/hpet.zig @@ -0,0 +1,40 @@ +const b = @import("base"); +const std = @import("std"); +const acpi = b.pl.acpi; +const mm = b.mm; +const ke = b.ke; + +var hpet_timer: ke.TimeCounter = .{ + .name = "HPET", + .quality = 50, + .frequency = 0, // Filled at runtime + .read_count = read_hpet, + .mask = std.math.maxInt(u64), + .p = 0, + .n = 0, +}; + +var hpet_regs: *volatile acpi.HpetRegs = undefined; + +const femtos_per_s = 1_000_000_000_000_000; + +pub fn init(hpet: *acpi.HpetTable) linksection(b.init) void { + hpet_regs = @ptrFromInt(mm.p2v(hpet.base_address.address)); + + hpet_timer.frequency = femtos_per_s / (b.mmio_read(u64, @intFromPtr(&hpet_regs.general_capabilities)) >> 32); + + // Disable timer. + b.mmio_write(u64, @intFromPtr(&hpet_regs.general_configuration), 0); + + // Reset counter. + b.mmio_write(u64, @intFromPtr(&hpet_regs.main_counter_value), 0); + + // Enable timer. + b.mmio_write(u64, @intFromPtr(&hpet_regs.general_configuration), 1); + + ke.time.register_source(&hpet_timer); +} + +fn read_hpet() u64 { + return b.mmio_read(u64, @intFromPtr(&hpet_regs.main_counter_value)); +} diff --git a/src/platform/acpi/root.zig b/src/platform/acpi/root.zig new file mode 100644 index 0000000..7193b94 --- /dev/null +++ b/src/platform/acpi/root.zig @@ -0,0 +1,335 @@ +const b = @import("base"); +const mm = b.mm; +const pl = b.pl; +const arch = @import("arch"); +const std = @import("std"); +const timer = @import("timer.zig"); +const hpet = @import("hpet.zig"); + +const rsdp_signature = "RSD PTR "; +const rsdt_signature = "RSDT"; +const xsdt_signature = "XSDT"; +const madt_signature = "APIC"; +const fadt_signature = "FACP"; +const mcfg_signature = "MCFG"; +const hpet_signature = "HPET"; + +pub const AddressSpaceId = enum(u8) { + SystemMemory = 0, + SystemIo = 1, + PciConfigurationSpace = 2, + EmbeddedController = 3, + SMBus = 4, + SystemCMOS = 5, + PciBarTarget = 6, + IPMI = 7, + GeneralPurposeIO = 8, + GenericSerialBus = 9, + PlatformCommunicationsChannel = 10, + FunctionalFixedHardware = 0x7F, +}; + +pub const Rsdp = extern struct { + signature: [8]u8 align(1), + checksum: u8 align(1), + oem_id: [6]u8 align(1), + revision: u8 align(1), + rsdt_address: u32 align(1), + + // The following fields are only valid if revision >= 2. + length: u32 align(1), + xsdt_address: u64 align(1), + extended_checksum: u8 align(1), + reserved: [3]u8 align(1), +}; + +pub const SdtHeader = extern struct { + signature: [4]u8 align(1), + length: u32 align(1), + revision: u8 align(1), + checksum: u8 align(1), + oem_id: [6]u8 align(1), + oem_table_id: [8]u8 align(1), + oem_revision: u32 align(1), + creator_id: u32 align(1), + creator_revision: u32 align(1), +}; + +pub const Rsdt = extern struct { + header: SdtHeader, + entries: [0]u32, +}; + +pub const Xsdt = extern struct { + header: SdtHeader, + entries: [0]u64, +}; + +pub const Madt = extern struct { + header: SdtHeader, + lapic_address: u32 align(1), + flags: u32 align(1), + entries: [0]u8 align(1), +}; + +/// Generic address structure. +pub const Gas = extern struct { + /// The address space where the structure exists. + address_space_id: AddressSpaceId align(1), + /// The size in bits of the given register. + register_bit_width: u8 align(1), + /// The bit offset of the given register at the given address. + register_bit_offset: u8 align(1), + /// The access size of the given register. (1 = byte, 2 = word, 3 = dword, 4 = qword). + access_size: u8 align(1), + address: u64 align(1), + pub fn read(self: Gas) u64 { + const raw: u64 = switch (self.address_space_id) { + .SystemMemory => switch (self.access_size) { + 1 => b.mmio_read(u8, self.address), + 2 => b.mmio_read(u16, self.address), + 3 => b.mmio_read(u32, self.address), + 4 => b.mmio_read(u64, self.address), + else => unreachable, + }, + .SystemIo => switch (self.access_size) { + 1 => arch.pio_read(u8, @truncate(self.address)), + 2 => arch.pio_read(u16, @truncate(self.address)), + 3 => arch.pio_read(u32, @truncate(self.address)), + else => unreachable, + }, + else => @panic("unsupported GAS address space"), + }; + + const shifted = raw >> @intCast(self.register_bit_offset); + const mask = if (self.register_bit_width == 64) + std.math.maxInt(u64) + else + (@as(u64, 1) << @intCast(self.register_bit_width)) - 1; + return shifted & mask; + } + + pub fn write(self: Gas, value: u64) void { + const masked_value = value & if (self.register_bit_width == 64) + std.math.maxInt(u64) + else + (@as(u64, 1) << @intCast(self.register_bit_width)) - 1; + const shifted_value = masked_value << self.register_bit_offset; + + switch (self.address_space_id) { + .SystemMemory => switch (self.access_size) { + 1 => b.mmio_write(u8, self.address, @truncate(shifted_value)), + 2 => b.mmio_write(u16, self.address, @truncate(shifted_value)), + 3 => b.mmio_write(u32, self.address, @truncate(shifted_value)), + 4 => b.mmio_write(u64, self.address, shifted_value), + else => unreachable, + }, + .SystemIo => switch (self.access_size) { + 1 => arch.pio_write(u8, @truncate(self.address), @truncate(shifted_value)), + 2 => arch.pio_write(u16, @truncate(self.address), @truncate(shifted_value)), + 3 => arch.pio_write(u32, @truncate(self.address), @truncate(shifted_value)), + else => unreachable, + }, + else => @panic("unsupported GAS address space"), + } + } +}; + +pub const Fadt = extern struct { + header: SdtHeader, + firmware_ctrl: u32 align(1), + dsdt: u32 align(1), + reserved: u8 align(1), + preferred_pm_profile: u8 align(1), + sci_interrupt: u16 align(1), + smi_command_port: u32 align(1), + acpi_enable: u8 align(1), + acpi_disable: u8 align(1), + s4bios_req: u8 align(1), + pstate_control: u8 align(1), + pm1a_evt_blk: u32 align(1), + pm1b_evt_blk: u32 align(1), + pm1a_cnt_blk: u32 align(1), + pm1b_cnt_blk: u32 align(1), + pm2_cnt_blk: u32 align(1), + pm_tmr_blk: u32 align(1), + gpe0_blk: u32 align(1), + gpe1_blk: u32 align(1), + pm1_evt_len: u8 align(1), + pm1_cnt_len: u8 align(1), + pm2_cnt_len: u8 align(1), + pm_tmr_len: u8 align(1), + gpe0_blk_len: u8 align(1), + gpe1_blk_len: u8 align(1), + gpe1_base: u8 align(1), + cst_cnt: u8 align(1), + p_lvl2_lat: u16 align(1), + p_lvl3_lat: u16 align(1), + flush_size: u16 align(1), + flush_stride: u16 align(1), + duty_offset: u8 align(1), + duty_width: u8 align(1), + day_alarm: u8 align(1), + month_alarm: u8 align(1), + century: u8 align(1), + iapc_boot_arch: u16 align(1), + reserved2: u8 align(1), + flags: u32 align(1), + reset_reg: Gas, + reset_value: u8 align(1), + arm_boot_arch: u16 align(1), + fadt_minor_version: u8 align(1), + x_firmware_ctrl: u64 align(1), + x_dsdt: u64 align(1), + x_pm1a_evt_blk: Gas, + x_pm1b_evt_blk: Gas, + x_pm1a_cnt_blk: Gas, + x_pm1b_cnt_blk: Gas, + x_pm2_cnt_blk: Gas, + x_pm_tmr_blk: Gas, + x_gpe0_blk: Gas, + x_gpe1_blk: Gas, + sleep_control_reg: Gas, + sleep_status_reg: Gas, + hypervisor_vendor_id: [8]u8 align(1), + + const version_sizes = [_]usize{ 116, 132, 244, 244, 268, 276 }; + + pub fn revision(self: Fadt) u8 { + // We can't rely on firmware to set the revision field correctly, so we infer it from the length field. + const declared: usize = self.header.revision; + const len: u32 = self.header.length; + + const idx = for (version_sizes, 0..) |size, i| { + if (len <= size) break i; + } else version_sizes.len; + + const inferred: usize = @min(idx + 1, version_sizes.len); + + if (declared == inferred or (inferred == 3 and declared == 4)) { + return @intCast(declared); + } + + return @intCast(inferred); + } +}; + +pub const HpetTable = extern struct { + hdr: SdtHeader, + hardware_rev_id: u8 align(1), + bits: packed struct(u8) { + comparator_count: u5, + counter_size_capability: u1, + reserved: u1, + legacy_replacement_capability: u1, + } align(1), + pci_vendor_id: u16 align(1), + base_address: Gas, + hpet_number: u8 align(1), + minimum_tick: u16 align(1), + page_protection: u8 align(1), +}; + +pub const HpetRegs = extern struct { + general_capabilities: u64, + reserved0: u64, + general_configuration: u64, + reserved1: u64, + general_interrupt_status: u64, + reserved2: [25]u64, + main_counter_value: u64, +}; + +pub var xsdt: ?*Xsdt = null; +pub var rsdt: ?*Rsdt = null; + +pub fn find_table(signature: []const u8) ?*SdtHeader { + if (xsdt) |x| { + const entry_count = (x.header.length - @sizeOf(SdtHeader)) / @sizeOf(u64); + const entries_ptr: [*]u64 = @ptrCast(&x.entries); + + for (0..entry_count) |i| { + const hdr: *SdtHeader = @ptrFromInt(mm.p2v(entries_ptr[i])); + if (std.mem.eql(u8, hdr.signature[0..], signature)) { + return hdr; + } + } + } else if (rsdt) |r| { + const entry_count = (r.header.length - @sizeOf(SdtHeader)) / @sizeOf(u32); + const entries_ptr: [*]u32 = @ptrCast(&r.entries); + + for (0..entry_count) |i| { + const hdr: *SdtHeader = @ptrFromInt(mm.p2v(entries_ptr[i])); + if (std.mem.eql(u8, hdr.signature[0..], signature)) { + return hdr; + } + } + } + + return null; +} + +fn format_table(hdr: *SdtHeader, phys_addr: u64) void { + std.log.info("acpi: {s} 0x{x:0>16} {x:0>6} (v{d:0>2} {s} {s} {x:0>8} {s} {x:0>8})", .{ + hdr.signature, + phys_addr, + hdr.length, + hdr.revision, + std.mem.trimEnd(u8, &hdr.oem_id, " "), + std.mem.trimEnd(u8, &hdr.oem_table_id, " "), + hdr.oem_revision, + std.mem.asBytes(&hdr.creator_id), + hdr.creator_revision, + }); +} + +fn enumerate_tables() void { + if (xsdt) |x| { + const entry_count = (x.header.length - @sizeOf(SdtHeader)) / @sizeOf(u64); + const entries_ptr: [*]u64 = @ptrCast(&x.entries); + + for (0..entry_count) |i| { + const phys = entries_ptr[i]; + const hdr: *SdtHeader = @ptrFromInt(mm.p2v(phys)); + format_table(hdr, phys); + } + } else if (rsdt) |r| { + const entry_count = (r.header.length - @sizeOf(SdtHeader)) / @sizeOf(u32); + const entries_ptr: [*]u32 = @ptrCast(&r.entries); + + for (0..entry_count) |i| { + const phys: u64 = entries_ptr[i]; + const hdr: *SdtHeader = @ptrFromInt(mm.p2v(phys)); + format_table(hdr, phys); + } + } +} + +pub fn init(boot_info: *pl.BootInfo) linksection(b.init) void { + const rsdp_addr = boot_info.rsdp orelse return; + + const rsdp: *Rsdp = @ptrFromInt(rsdp_addr); + + if (rsdp.revision >= 2 and rsdp.xsdt_address != 0) { + xsdt = @ptrFromInt(mm.p2v(rsdp.xsdt_address)); + std.log.info("acpi: XSDT found at {x:0>16}", .{rsdp.xsdt_address}); + } else { + rsdt = @ptrFromInt(mm.p2v(rsdp.rsdt_address)); + std.log.info("acpi: RSDT found at {x:0>16}", .{rsdp.rsdt_address}); + } + + enumerate_tables(); + + const fadt = find_table(fadt_signature); + + if (fadt) |f| { + const fadt_struct: *Fadt = @ptrCast(f); + timer.init(fadt_struct); + } + + const hpet_t = find_table(hpet_signature); + if (hpet_t) |h| { + const hpet_struct: *HpetTable = @ptrCast(h); + hpet.init(hpet_struct); + } +} diff --git a/src/platform/acpi/timer.zig b/src/platform/acpi/timer.zig new file mode 100644 index 0000000..1ea0bbd --- /dev/null +++ b/src/platform/acpi/timer.zig @@ -0,0 +1,50 @@ +const b = @import("base"); +const std = @import("std"); +const acpi = b.pl.acpi; +const ke = b.ke; +const arch = @import("arch"); + +var pm_timer: ke.TimeCounter = .{ + .name = "ACPI", + .quality = 25, + .frequency = 3579545, // 3.579545 MHz + .read_count = read_timer, + .mask = std.math.maxInt(u24), + .p = 0, + .n = 0, +}; + +var timer_gas: acpi.Gas = undefined; + +pub fn init(fadt: *acpi.Fadt) linksection(b.init) void { + const fadt_rev = fadt.revision(); + + if (fadt.pm_tmr_len == 0 or fadt.pm_tmr_blk == 0) { + return; + } + + if (fadt_rev >= 2 and fadt.x_pm_tmr_blk.address == 0) { + return; + } + + timer_gas.address_space_id = .SystemIo; + timer_gas.address = fadt.pm_tmr_blk; + timer_gas.access_size = 3; + timer_gas.register_bit_width = 32; + timer_gas.register_bit_offset = 0; + + if (fadt_rev >= 2) { + timer_gas = fadt.x_pm_tmr_blk; + } + + // Indicates whether the timer is 32-bit + if (fadt.flags & (1 << 8) != 0) { + pm_timer.mask = std.math.maxInt(u32); + } + + ke.time.register_source(&pm_timer); +} + +fn read_timer() u64 { + return timer_gas.read(); +} diff --git a/src/platform/pc/impl.zig b/src/platform/pc/impl.zig index fda0dbd..c3bdd9a 100644 --- a/src/platform/pc/impl.zig +++ b/src/platform/pc/impl.zig @@ -2,6 +2,8 @@ const b = @import("base"); const pl = b.pl; const amd64 = b.arch; const std = @import("std"); +const tsc = @import("tsc.zig"); +const pvclock = @import("pvclock.zig"); const ki = b.ke.private; @@ -56,7 +58,8 @@ pub fn early_init(boot_info: *pl.BootInfo) linksection(b.init) void { _ = boot_info; } -pub fn late_init() void {} +pub fn late_init(boot_info: *pl.BootInfo) linksection(b.init) void { + pl.acpi.init(boot_info); pub fn devices_init() void {} diff --git a/src/platform/pc/pvclock.zig b/src/platform/pc/pvclock.zig new file mode 100644 index 0000000..2ebb0cd --- /dev/null +++ b/src/platform/pc/pvclock.zig @@ -0,0 +1,61 @@ +const amd64 = @import("arch"); +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_tc: ke.TimeCounter = .{ + .name = "pvclock", + .quality = 75, + .frequency = std.time.ns_per_s, + .read_count = read_pvclock, + .mask = std.math.maxInt(u64), + .p = 0, + .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.wrmsr(.KvmSystemTimeNew, page | 1); + + time_at_boot = read_pvclock(); + + 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; + } +} diff --git a/src/platform/pc/tsc.zig b/src/platform/pc/tsc.zig new file mode 100644 index 0000000..0cccf72 --- /dev/null +++ b/src/platform/pc/tsc.zig @@ -0,0 +1,83 @@ +// Not sure if this should be in platform/pc or in kern/ +const std = @import("std"); +const amd64 = @import("arch"); +const b = @import("base"); +const ke = b.ke; + +var tsc_timer: ke.TimeCounter = .{ + .name = "TSC", + .quality = 100, + .frequency = 0, + .read_count = read_tsc, + .mask = std.math.maxInt(u64), + .p = 0, + .n = 0, +}; + +pub fn init() linksection(b.init) void { + const features = amd64.cpu_features; + + if (!features.invariant_tsc) { + std.log.info("tsc: invariant TSC not supported (get a new PC)", .{}); + if (ke.time.best()) |best| { + std.log.info("pc: using {s} as timecounter source", .{best.name}); + } + return; + } + + // Try using cpuid leaf 0x15 to get the TSC frequency. + const cpuid_state = amd64.cpuid(0x15, 0); + + if (cpuid_state.ebx != 0 and cpuid_state.ecx != 0) { + const tsc_freq = (cpuid_state.ebx / cpuid_state.eax) * cpuid_state.ecx; + std.log.info("tsc: TSC frequency determined via CPUID: {} Hz", .{tsc_freq}); + tsc_timer.frequency = tsc_freq; + + ke.time.register_source(&tsc_timer); + return; + } + + // Fallback to the other best time source to calibrate. + const best = ke.time.best() orelse return; + + const calib_cost_runs = 5; + var calib_cost: u64 = 0; + + // Calculate how many ticks it costs to call tc_sleep. + for (0..calib_cost_runs) |_| { + const start = amd64.rdtsc(); + ke.time.sleep(0); + const end = amd64.rdtsc(); + calib_cost += (end - start); + } + + calib_cost /= calib_cost_runs; + + const runs = 5; + const calib_time = 10 * std.time.ns_per_ms; + + // Sleep for 10ms `runs` times and measure the TSC frequency, then average the middle values to get a stable estimate. + var freqs: [runs]u64 = undefined; + + for (0..runs) |i| { + const start = amd64.rdtsc(); + ke.time.sleep(calib_time); + const end = amd64.rdtsc(); + freqs[i] = (end - start - calib_cost) * (std.time.ns_per_s / calib_time); + } + + // Sort and average middle values, discarding min and max. + std.mem.sort(u64, &freqs, {}, std.sort.asc(u64)); + var sum: u64 = 0; + for (freqs[1 .. runs - 1]) |f| sum += f; + + tsc_timer.frequency = sum / (runs - 2); + + ke.time.register_source(&tsc_timer); + + std.log.info("pc: TSC frequency calibrated using {s}: {}.{} MHz", .{ best.name, tsc_timer.frequency / 1_000_000, (tsc_timer.frequency % 1_000_000) / 1000 }); +} + +fn read_tsc() u64 { + return amd64.rdtsc(); +} diff --git a/src/platform/root.zig b/src/platform/root.zig index 98aae91..0f3b799 100644 --- a/src/platform/root.zig +++ b/src/platform/root.zig @@ -10,6 +10,8 @@ else if (@hasDecl(config, "CONFIG_ARCH_UM")) else @compileError("unsupported architecture"); +pub const acpi = @import("acpi/root.zig"); + /// Early platform-specific initialization, called before anything else. pub inline fn early_init(boot_info: *BootInfo) void { return impl.early_init(boot_info); @@ -17,8 +19,8 @@ pub inline fn early_init(boot_info: *BootInfo) void { /// Late platform init. Memory allocator is available at this point. /// SMP bringup must be done after this returns. -pub inline fn late_init() void { - return impl.late_init(); +pub inline fn late_init(boot_info: *BootInfo) void { + return impl.late_init(boot_info); } /// Write a single character to the debug console. diff --git a/src/root.zig b/src/root.zig index 257364e..1e4411d 100644 --- a/src/root.zig +++ b/src/root.zig @@ -39,6 +39,14 @@ pub fn tib(comptime N: u32) usize { return gib(N) * 1024; } +pub inline fn mmio_read(comptime T: type, addr: usize) T { + return @as(*volatile T, @ptrFromInt(addr)).*; +} + +pub inline fn mmio_write(comptime T: type, addr: usize, value: T) void { + @as(*volatile T, @ptrFromInt(addr)).* = value; +} + pub const std_options = std.Options{ .logFn = ke.log.log, };