diff --git a/src/kernel/arch/x86/arch.zig b/src/kernel/arch/x86/arch.zig index a19f187..3dd9e11 100644 --- a/src/kernel/arch/x86/arch.zig +++ b/src/kernel/arch/x86/arch.zig @@ -7,6 +7,7 @@ const pic = @import("pic.zig"); const irq = @import("irq.zig"); const isr = @import("isr.zig"); const pit = @import("pit.zig"); +const rtc = @import("rtc.zig"); const paging = @import("paging.zig"); const syscalls = @import("syscalls.zig"); const mem = @import("../../mem.zig"); @@ -81,6 +82,13 @@ pub fn inb(port: u16) u8 { ); } +/// +/// Force the CPU to wait for an I/O operation to compete. Use port 0x80 as this is unused. +/// +pub fn ioWait() void { + outb(0x80, 0); +} + /// /// Load the GDT and refreshing the code segment with the code segment offset of the kernel as we /// are still in kernel land. Also loads the kernel data segment into all the other segment @@ -230,10 +238,11 @@ pub fn init(mb_info: *multiboot.multiboot_info_t, mem_profile: *const MemProfile isr.init(); irq.init(); - pit.init(); - paging.init(mb_info, mem_profile, allocator); + pit.init(); + rtc.init(); + syscalls.init(); enableInterrupts(); @@ -246,6 +255,8 @@ test "" { _ = @import("isr.zig"); _ = @import("irq.zig"); _ = @import("pit.zig"); + _ = @import("cmos.zig"); + _ = @import("rtc.zig"); _ = @import("syscalls.zig"); _ = @import("paging.zig"); } diff --git a/src/kernel/arch/x86/cmos.zig b/src/kernel/arch/x86/cmos.zig new file mode 100644 index 0000000..a7deece --- /dev/null +++ b/src/kernel/arch/x86/cmos.zig @@ -0,0 +1,463 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const is_test = builtin.is_test; +const expectEqual = std.testing.expectEqual; +const build_options = @import("build_options"); +const mock_path = build_options.arch_mock_path; +const arch = if (is_test) @import(mock_path ++ "arch_mock.zig") else @import("arch.zig"); + +/// The current year to be used for calculating the 4 digit year, as the CMOS return the last two +/// digits of the year. +const CURRENT_CENTURY: u8 = 2000; + +/// The port address for the CMOS command register. +const ADDRESS: u16 = 0x70; + +/// The port address for the CMOS data register. +const DATA: u16 = 0x71; + +/// The register location for returning the seconds, (0 - 59). +const REGISTER_SECOND: u8 = 0x00; + +/// The register location for returning the minute, (0 - 59). +const REGISTER_MINUTE: u8 = 0x02; + +/// The register location for returning the hours, (0 - 23 or 0 - 12 depending if a 12hr or 24hr +/// clock). +const REGISTER_HOUR: u8 = 0x04; + +/// The register location for returning the weekday, (0 - 6). Very unreliable, so will calculate +/// the day of the week instead. +const REGISTER_WEEKDAY: u8 = 0x06; + +/// The register location for returning the day, (0 - 31). +const REGISTER_DAY: u8 = 0x07; + +/// The register location for returning the month, (0 - 11). +const REGISTER_MONTH: u8 = 0x08; + +/// The register location for returning the year, (0 - 99). +const REGISTER_YEAR: u8 = 0x09; + +/// The register location for returning the century. +const REGISTER_CENTURY: u8 = 0x32; + +/// The register location for return the status A register. +const STATUS_REGISTER_A: u8 = 0x0A; + +/// The register location for return the status B register. +const STATUS_REGISTER_B: u8 = 0x0B; + +/// The register location for return the status C register. +const STATUS_REGISTER_C: u8 = 0x0C; + +/// The non-mockable interrupts are on the 8th bit of port 0x70 which is the register select port +/// for the CMOS. This will need to be disabled when selecting CMOS registers. +const NMI_BIT: u8 = 0x80; + +/// The enum for selecting the status register to read from +pub const StatusRegister = enum { + /// Status register A + A, + + /// Status register B + B, + + /// Status register C + C, + + /// + /// Get the register index for the status registers + /// + /// Arguments: + /// IN reg: StatusRegister - The enum that represents one of the 3 status registers. + /// + /// Return: u8 + /// The register index for one of the 3 status registers. + /// + pub fn getRegister(reg: StatusRegister) u8 { + return switch (reg) { + .A => STATUS_REGISTER_A, + .B => STATUS_REGISTER_B, + .C => STATUS_REGISTER_C, + }; + } +}; + +/// The enum for selecting the real time clock registers. +pub const RtcRegister = enum { + /// The seconds register + SECOND, + + /// The minutes register + MINUTE, + + /// The hours register + HOUR, + + /// The days register + DAY, + + /// The months register + MONTH, + + /// The year register + YEAR, + + /// The century register + CENTURY, + + /// + /// Get the register index for the RTC registers + /// + /// Arguments: + /// IN reg: RtcRegister - The enum that represents one of the RTC registers. + /// + /// Return: u8 + /// The register index for one of the RTC registers. + /// + pub fn getRegister(reg: RtcRegister) u8 { + return switch (reg) { + .SECOND => REGISTER_SECOND, + .MINUTE => REGISTER_MINUTE, + .HOUR => REGISTER_HOUR, + .DAY => REGISTER_DAY, + .MONTH => REGISTER_MONTH, + .YEAR => REGISTER_YEAR, + .CENTURY => REGISTER_CENTURY, + }; + } +}; + +/// +/// Tell the CMOS chip to select the given register ready for read or writing to. This also +/// disables the NMI when disable_nmi is true. +/// +/// Arguments: +/// IN reg: u8 - The register index to select in the CMOS chip. +/// IN comptime disable_nmi: bool - Whether to disable NMI when selecting a register. +/// +inline fn selectRegister(reg: u8, comptime disable_nmi: bool) void { + if (disable_nmi) { + arch.outb(ADDRESS, reg | NMI_BIT); + } else { + arch.outb(ADDRESS, reg); + } +} + +/// +/// Write to the selected register to the CMOS chip. +/// +/// Arguments: +/// IN data: u8 - The data to write to the selected register. +/// +inline fn writeRegister(data: u8) void { + arch.outb(DATA, data); +} + +/// +/// Read the selected register from the CMOS chip. +/// +/// Return: u8 +/// The value in the selected register. +/// +inline fn readRegister() u8 { + return arch.inb(DATA); +} + +/// +/// Select then read a register from the CMOS chip. This include a I/O wait to ensure the CMOS chip +/// has time to select the register. +/// +/// Arguments: +/// IN reg: u8 - The register index to select in the CMOS chip. +/// IN comptime disable_nmi: bool - Whether to disable NMI when selecting a register. +/// +/// Return: u8 +/// The value in the selected register. +/// +inline fn selectAndReadRegister(reg: u8, comptime disable_nmi: bool) u8 { + selectRegister(reg, disable_nmi); + arch.ioWait(); + return readRegister(); +} + +/// +/// Select then write to a register to the CMOS chip. This include a I/O wait to ensure the CMOS chip +/// has time to select the register. +/// +/// Arguments: +/// IN reg: u8 - The register index to select in the CMOS chip. +/// IN data: u8 - The data to write to the selected register. +/// IN comptime disable_nmi: bool - Whether to disable NMI when selecting a register. +/// +inline fn selectAndWriteRegister(reg: u8, data: u8, comptime disable_nmi: bool) void { + selectRegister(reg, disable_nmi); + arch.ioWait(); + writeRegister(data); +} + +/// +/// Read a register that corresponds to a real time clock register. +/// +/// Arguments: +/// IN reg: RtcRegister - A RTC register to select in the CMOS chip. +/// +/// Return: u8 +/// The value in the selected register. +/// +pub fn readRtcRegister(reg: RtcRegister) u8 { + return selectAndReadRegister(reg.getRegister(), false); +} + +/// +/// Read a status register in the CMOS chip. +/// +/// Arguments: +/// IN reg: StatusRegister - The status register to select. +/// IN comptime disable_nmi: bool - Whether to disable NMI when selecting a register. +/// +/// Return: u8 +/// The value in the selected register. +/// +pub fn readStatusRegister(reg: StatusRegister, comptime disable_nmi: bool) u8 { + return selectAndReadRegister(reg.getRegister(), disable_nmi); +} + +/// +/// Write to a status register in the CMOS chip. +/// +/// Arguments: +/// IN reg: StatusRegister - The status register to select. +/// IN data: u8 - The data to write to the selected register. +/// IN comptime disable_nmi: bool - Whether to disable NMI when selecting a register. +/// +pub fn writeStatusRegister(reg: StatusRegister, data: u8, comptime disable_nmi: bool) void { + selectAndWriteRegister(reg.getRegister(), data, disable_nmi); +} + +test "selectRegister" { + arch.initTest(); + defer arch.freeTest(); + + arch.addTestParams("outb", .{ ADDRESS, STATUS_REGISTER_A }); + + const reg = STATUS_REGISTER_A; + + selectRegister(reg, false); +} + +test "selectRegister no NMI" { + arch.initTest(); + defer arch.freeTest(); + + arch.addTestParams("outb", .{ ADDRESS, STATUS_REGISTER_A | NMI_BIT }); + + const reg = STATUS_REGISTER_A; + + selectRegister(reg, true); +} + +test "writeRegister" { + arch.initTest(); + defer arch.freeTest(); + + arch.addTestParams("outb", .{ DATA, @as(u8, 0xAA) }); + + const data = @as(u8, 0xAA); + + writeRegister(data); +} + +test "readRegister" { + arch.initTest(); + defer arch.freeTest(); + + arch.addTestParams("inb", .{ DATA, @as(u8, 0x55) }); + + const expected = @as(u8, 0x55); + const actual = readRegister(); + + expectEqual(expected, actual); +} + +test "selectAndReadRegister NMI" { + arch.initTest(); + defer arch.freeTest(); + + arch.addTestParams("outb", .{ ADDRESS, STATUS_REGISTER_C }); + arch.addTestParams("inb", .{ DATA, @as(u8, 0x44) }); + arch.addConsumeFunction("ioWait", arch.mock_ioWait); + + const reg = STATUS_REGISTER_C; + + const expected = @as(u8, 0x44); + const actual = selectAndReadRegister(reg, false); + + expectEqual(expected, actual); +} + +test "selectAndReadRegister no NMI" { + arch.initTest(); + defer arch.freeTest(); + + arch.addTestParams("outb", .{ ADDRESS, STATUS_REGISTER_C | NMI_BIT }); + arch.addTestParams("inb", .{ DATA, @as(u8, 0x44) }); + arch.addConsumeFunction("ioWait", arch.mock_ioWait); + + const reg = STATUS_REGISTER_C; + + const expected = @as(u8, 0x44); + const actual = selectAndReadRegister(reg, true); + + expectEqual(expected, actual); +} + +test "selectAndWriteRegister NMI" { + arch.initTest(); + defer arch.freeTest(); + + arch.addTestParams("outb", .{ ADDRESS, STATUS_REGISTER_C, DATA, @as(u8, 0x88) }); + arch.addConsumeFunction("ioWait", arch.mock_ioWait); + + const reg = STATUS_REGISTER_C; + const data = @as(u8, 0x88); + + selectAndWriteRegister(reg, data, false); +} + +test "selectAndWriteRegister no NMI" { + arch.initTest(); + defer arch.freeTest(); + + arch.addTestParams("outb", .{ ADDRESS, STATUS_REGISTER_C | NMI_BIT, DATA, @as(u8, 0x88) }); + arch.addConsumeFunction("ioWait", arch.mock_ioWait); + + const reg = STATUS_REGISTER_C; + const data = @as(u8, 0x88); + + selectAndWriteRegister(reg, data, true); +} + +test "readRtcRegister" { + arch.initTest(); + defer arch.freeTest(); + + arch.addRepeatFunction("ioWait", arch.mock_ioWait); + + const rtc_regs = [_]RtcRegister{ RtcRegister.SECOND, RtcRegister.MINUTE, RtcRegister.HOUR, RtcRegister.DAY, RtcRegister.MONTH, RtcRegister.YEAR, RtcRegister.CENTURY }; + + for (rtc_regs) |reg| { + const r = switch (reg) { + .SECOND => REGISTER_SECOND, + .MINUTE => REGISTER_MINUTE, + .HOUR => REGISTER_HOUR, + .DAY => REGISTER_DAY, + .MONTH => REGISTER_MONTH, + .YEAR => REGISTER_YEAR, + .CENTURY => REGISTER_CENTURY, + }; + + arch.addTestParams("outb", .{ ADDRESS, r }); + arch.addTestParams("inb", .{ DATA, @as(u8, 0x44) }); + + const expected = @as(u8, 0x44); + const actual = readRtcRegister(reg); + + expectEqual(expected, actual); + } +} + +test "readStatusRegister NMI" { + arch.initTest(); + defer arch.freeTest(); + + arch.addRepeatFunction("ioWait", arch.mock_ioWait); + + const status_regs = [_]StatusRegister{ StatusRegister.A, StatusRegister.B, StatusRegister.C }; + + for (status_regs) |reg| { + const r = switch (reg) { + .A => STATUS_REGISTER_A, + .B => STATUS_REGISTER_B, + .C => STATUS_REGISTER_C, + }; + + arch.addTestParams("outb", .{ ADDRESS, r }); + arch.addTestParams("inb", .{ DATA, @as(u8, 0x78) }); + + const expected = @as(u8, 0x78); + const actual = readStatusRegister(reg, false); + + expectEqual(expected, actual); + } +} + +test "readStatusRegister no NMI" { + arch.initTest(); + defer arch.freeTest(); + + arch.addRepeatFunction("ioWait", arch.mock_ioWait); + + const status_regs = [_]StatusRegister{ StatusRegister.A, StatusRegister.B, StatusRegister.C }; + + for (status_regs) |reg| { + const r = switch (reg) { + .A => STATUS_REGISTER_A, + .B => STATUS_REGISTER_B, + .C => STATUS_REGISTER_C, + }; + + arch.addTestParams("outb", .{ ADDRESS, r | NMI_BIT }); + arch.addTestParams("inb", .{ DATA, @as(u8, 0x78) }); + + const expected = @as(u8, 0x78); + const actual = readStatusRegister(reg, true); + + expectEqual(expected, actual); + } +} + +test "writeStatusRegister NMI" { + arch.initTest(); + defer arch.freeTest(); + + arch.addRepeatFunction("ioWait", arch.mock_ioWait); + + const status_regs = [_]StatusRegister{ StatusRegister.A, StatusRegister.B, StatusRegister.C }; + + for (status_regs) |reg| { + const r = switch (reg) { + .A => STATUS_REGISTER_A, + .B => STATUS_REGISTER_B, + .C => STATUS_REGISTER_C, + }; + + arch.addTestParams("outb", .{ ADDRESS, r, DATA, @as(u8, 0x43) }); + + const data = @as(u8, 0x43); + writeStatusRegister(reg, data, false); + } +} + +test "writeStatusRegister no NMI" { + arch.initTest(); + defer arch.freeTest(); + + arch.addRepeatFunction("ioWait", arch.mock_ioWait); + + const status_regs = [_]StatusRegister{ StatusRegister.A, StatusRegister.B, StatusRegister.C }; + + for (status_regs) |reg| { + const r = switch (reg) { + .A => STATUS_REGISTER_A, + .B => STATUS_REGISTER_B, + .C => STATUS_REGISTER_C, + }; + + arch.addTestParams("outb", .{ ADDRESS, r | NMI_BIT, DATA, @as(u8, 0x43) }); + + const data = @as(u8, 0x43); + writeStatusRegister(reg, data, true); + } +} diff --git a/src/kernel/arch/x86/pic.zig b/src/kernel/arch/x86/pic.zig index a1e152b..aa99e64 100644 --- a/src/kernel/arch/x86/pic.zig +++ b/src/kernel/arch/x86/pic.zig @@ -436,23 +436,36 @@ pub fn init() void { // Initiate sendCommandMaster(ICW1_INITIALISATION | ICW1_EXPECT_ICW4); + arch.ioWait(); sendCommandSlave(ICW1_INITIALISATION | ICW1_EXPECT_ICW4); + arch.ioWait(); // Offsets sendDataMaster(ICW2_MASTER_REMAP_OFFSET); + arch.ioWait(); sendDataSlave(ICW2_SLAVE_REMAP_OFFSET); + arch.ioWait(); // IRQ lines sendDataMaster(ICW3_MASTER_IRQ_MAP_FROM_SLAVE); + arch.ioWait(); sendDataSlave(ICW3_SLAVE_IRQ_MAP_TO_MASTER); + arch.ioWait(); // 80x86 mode sendDataMaster(ICW4_80x86_MODE); + arch.ioWait(); sendDataSlave(ICW4_80x86_MODE); + arch.ioWait(); // Mask all interrupts sendDataMaster(0xFF); + arch.ioWait(); sendDataSlave(0xFF); + arch.ioWait(); + + // Clear the IRQ for the slave + clearMask(IRQ_CASCADE_FOR_SLAVE); log.logInfo("Done\n", .{}); @@ -763,6 +776,8 @@ test "init" { arch.initTest(); defer arch.freeTest(); + arch.addRepeatFunction("ioWait", arch.mock_ioWait); + // Just a long list of OUT instructions setting up the PIC arch.addTestParams("outb", .{ MASTER_COMMAND_REG, @@ -785,8 +800,12 @@ test "init" { @as(u8, 0xFF), SLAVE_DATA_REG, @as(u8, 0xFF), + MASTER_DATA_REG, + @as(u8, 0xFB), }); + arch.addTestParams("inb", .{ MASTER_DATA_REG, @as(u8, 0xFF) }); + init(); } @@ -794,7 +813,8 @@ test "init" { /// Test that all the PIC masks are set so no interrupts can fire. /// fn rt_picAllMasked() void { - if (readDataMaster() != 0xFF) { + // The master will have interrupt 2 clear because this is the link to the slave (third bit) + if (readDataMaster() != 0xFB) { panic(@errorReturnTrace(), "Master masks are not set, found: {}\n", .{readDataMaster()}); } diff --git a/src/kernel/arch/x86/rtc.zig b/src/kernel/arch/x86/rtc.zig new file mode 100644 index 0000000..33c04e7 --- /dev/null +++ b/src/kernel/arch/x86/rtc.zig @@ -0,0 +1,745 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const is_test = builtin.is_test; +const expect = std.testing.expect; +const expectEqual = std.testing.expectEqual; +const expectError = std.testing.expectError; +const build_options = @import("build_options"); +const mock_path = build_options.arch_mock_path; +const arch = @import("arch.zig"); +const log = @import("../../log.zig"); +const pic = @import("pic.zig"); +const pit = @import("pit.zig"); +const irq = @import("irq.zig"); +const cmos = if (is_test) @import(mock_path ++ "cmos_mock.zig") else @import("cmos.zig"); +const panic = if (is_test) @import(mock_path ++ "panic_mock.zig").panic else @import("../../panic.zig").panic; + +/// The Century register is unreliable. We need a APIC interface to infer if we have a century +/// register. So this is a current TODO. +const CURRENT_CENTURY: u32 = 2000; + +/// TODO: To do with the unreliable century register. Once have APIC, can use this as a sanity check +/// if the the century register gives a wild answer then the other RTC values maybe wild. So then +/// could report that the CMOS chip is faulty or the battery is dyeing. +const CENTURY_REGISTER: bool = false; + +/// A structure to hold all the date and time information in the RTC. +const DateTime = struct { + second: u32, + minute: u32, + hour: u32, + day: u32, + month: u32, + year: u32, + century: u32, + day_of_week: u32, +}; + +/// The error set that can be returned from some RTC functions. +const RtcError = error{ + /// If setting the rate for interrupts is less than 3 or greater than 15. + RateError, +}; + +/// The number of ticks that has passed when RTC was initially set up. +var ticks: u32 = 0; + +/// +/// Checks if the CMOS chip isn't updating the RTC registers. Call this before reading any RTC +/// registers so don't get inconsistent values. +/// +/// Return: bool +/// Whether the CMOS chip is buzzy and a update is in progress. +/// +fn isBuzzy() bool { + return (cmos.readStatusRegister(cmos.StatusRegister.A, false) & 0x80) != 0; +} + +/// +/// Calculate the day of the week from the given day, month and year. +/// +/// Arguments: +/// IN date_time: DateTime - The DateTime structure that holds the current day, month and year. +/// +/// Return: u8 +/// A number that represents the day of the week. 1 = Sunday, 2 = Monday, ... +/// +fn calcDayOfWeek(date_time: DateTime) u32 { + const t = [_]u8{ 0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4 }; + const year = date_time.year - @boolToInt(date_time.month < 3); + const month = date_time.month; + const day = date_time.day; + + return (year + (year / 4) - (year / 100) + (year / 400) + t[month - 1] + day) % 7; +} + +/// +/// Check if the RTC is in binary coded decimal mode. If the RTC ic counting in BCD, then the 3rd +/// bit in the status register B will be set. +/// +/// Return: bool +/// When the RTC is counting in BCD. +/// +fn isBcd() bool { + const reg_b = cmos.readStatusRegister(cmos.StatusRegister.B, false); + return reg_b & 0x04 != 0; +} + +/// +/// Check if the RTC in 12 hour mode. If the RTC is in 12 hour mode, then the 2nd bit in the status +/// register B is set and the most significant bit on the hour field is set. +/// +/// Arguments: +/// IN date_time: DateTime - The DateTime structure containing at least the hour field set. +/// +/// Return: bool +/// Whether the RTC is in 12 hour mode. +/// +fn is12Hr(date_time: DateTime) bool { + const reg_b = cmos.readStatusRegister(cmos.StatusRegister.B, false); + return reg_b & 0x02 != 0 and date_time.hour & 0x80 != 0; +} + +/// +/// Convert BCD to binary. +/// +/// Arguments: +/// IN bcd: u32 - The binary coded decimal value to convert +/// +/// Return: u32 +/// The converted BCD value. +/// +fn bcdToBinary(bcd: u32) u32 { + return ((bcd & 0xF0) >> 1) + ((bcd & 0xF0) >> 3) + (bcd & 0xf); +} + +/// +/// Read the real time clock registers and return them in the DateTime structure. This will read +/// the seconds, minutes, hours, days, months, years, century and day of the week. +/// +/// Return: DateTime +/// The data from the CMOS RTC registers. +/// +fn readRtcRegisters() DateTime { + // Make sure there isn't a update in progress + while (isBuzzy()) {} + + var date_time = DateTime{ + .second = cmos.readRtcRegister(cmos.RtcRegister.SECOND), + .minute = cmos.readRtcRegister(cmos.RtcRegister.MINUTE), + .hour = cmos.readRtcRegister(cmos.RtcRegister.HOUR), + .day = cmos.readRtcRegister(cmos.RtcRegister.DAY), + .month = cmos.readRtcRegister(cmos.RtcRegister.MONTH), + .year = cmos.readRtcRegister(cmos.RtcRegister.YEAR), + .century = if (CENTURY_REGISTER) cmos.readRtcRegister(cmos.RtcRegister.CENTURY) else CURRENT_CENTURY, + // This will be filled in later + .day_of_week = 0, + }; + + // The day of the week register is also very unreliable, so is better to calculate it + date_time.day_of_week = calcDayOfWeek(date_time); + + return date_time; +} + +/// +/// Read a stable time from the real time clock registers on the CMOS chip and return a BCD and +/// 12 hour converted date and time. +/// +/// Return: DateTime +/// The data from the CMOS RTC registers with correct BCD conversions, 12 hour conversions and +/// the century added to the year. +/// +fn readRtc() DateTime { + var date_time1 = readRtcRegisters(); + var date_time2 = readRtcRegisters(); + + // Use the method: Read the registers twice and check if they are the same so to avoid + // inconsistent values due to RTC updates + + var compare = false; + + inline for (@typeInfo(DateTime).Struct.fields) |field| { + compare = compare or @field(date_time1, field.name) != @field(date_time2, field.name); + } + + while (compare) { + date_time1 = readRtcRegisters(); + date_time2 = readRtcRegisters(); + + compare = false; + inline for (@typeInfo(DateTime).Struct.fields) |field| { + compare = compare or @field(date_time1, field.name) != @field(date_time2, field.name); + } + } + + // Convert BCD to binary if necessary + if (isBcd()) { + date_time1.second = bcdToBinary(date_time1.second); + date_time1.minute = bcdToBinary(date_time1.minute); + // Needs a special calculation because the upper bit is set + date_time1.hour = ((date_time1.hour & 0x0F) + (((date_time1.hour & 0x70) / 16) * 10)) | (date_time1.hour & 0x80); + date_time1.day = bcdToBinary(date_time1.day); + date_time1.month = bcdToBinary(date_time1.month); + date_time1.year = bcdToBinary(date_time1.year); + if (CENTURY_REGISTER) { + date_time1.century = bcdToBinary(date_time1.century); + } + } + + // Need to add on the century to the year + if (CENTURY_REGISTER) { + date_time1.year += date_time1.century * 100; + } else { + date_time1.year += CURRENT_CENTURY; + } + + // Convert to 24hr time + if (is12Hr(date_time1)) { + date_time1.hour = ((date_time1.hour & 0x7F) + 12) % 24; + } + + return date_time1; +} + +/// +/// The interrupt handler for the RTC. +/// +/// Arguments: +/// IN ctx: *arch.InterruptContext - Pointer to the interrupt context containing the contents +/// of the register at the time of the interrupt. +/// +fn rtcHandler(ctx: *arch.InterruptContext) void { + ticks +%= 1; + + // Need to read status register C + const reg_c = cmos.readStatusRegister(cmos.StatusRegister.C, false); +} + +/// +/// Set the rate at which the interrupts will fire. Ranges from 0x3 to 0xF. Where the frequency +/// is determined by: frequency = 32768 >> (rate-1); This will assume the interrupts are disabled. +/// +/// Arguments: +/// IN rate: u4 - The rate value to set the frequency to. +/// +/// Error: RtcError +/// RtcError.RateError - If the rate is less than 3. +/// +fn setRate(rate: u8) RtcError!void { + if (rate < 3 or rate > 0xF) { + return RtcError.RateError; + } + + // Need to disable the NMI for this process + const status_a = cmos.readStatusRegister(cmos.StatusRegister.A, true); + cmos.writeStatusRegister(cmos.StatusRegister.A, (status_a & 0xF0) | rate, true); +} + +/// +/// Enable interrupts for the RTC. This will assume the interrupts have been disabled before hand. +/// +fn enableInterrupts() void { + // Need to disable the NMI for this process + const status_b = cmos.readStatusRegister(cmos.StatusRegister.B, true); + + // Set the 7th bit to enable interrupt + cmos.writeStatusRegister(cmos.StatusRegister.B, status_b | 0x40, true); +} + +/// +/// Initialise the RTC. +/// +pub fn init() void { + log.logInfo("Init rtc\n", .{}); + + // Register the interrupt handler + irq.registerIrq(pic.IRQ_REAL_TIME_CLOCK, rtcHandler) catch |err| switch (err) { + error.IrqExists => { + panic(@errorReturnTrace(), "IRQ for RTC, IRQ number: {} exists", .{pic.IRQ_REAL_TIME_CLOCK}); + }, + error.InvalidIrq => { + panic(@errorReturnTrace(), "IRQ for RTC, IRQ number: {} is invalid", .{pic.IRQ_REAL_TIME_CLOCK}); + }, + }; + + // Need to disable interrupts went setting up the RTC + arch.disableInterrupts(); + + // Set the interrupt rate to 512Hz + setRate(7) catch |err| switch (err) { + error.RateError => { + panic(@errorReturnTrace(), "Setting rate error", .{}); + }, + }; + + // Enable RTC interrupts + enableInterrupts(); + + // Read status register C to clear any interrupts that may have happened during set up + //const reg_c = cmos.readStatusRegister(cmos.StatusRegister.C, false); + + // Can now enable interrupts + arch.enableInterrupts(); + + const reg_c = cmos.readStatusRegister(cmos.StatusRegister.C, false); + + log.logInfo("Done\n", .{}); + + if (build_options.rt_test) runtimeTests(); +} + +test "isBuzzy not buzzy" { + cmos.initTest(); + defer cmos.freeTest(); + + cmos.addTestParams( + "readStatusRegister", + .{ cmos.StatusRegister.A, false, @as(u8, 0x60) }, + ); + + expect(!isBuzzy()); +} + +test "isBuzzy buzzy" { + cmos.initTest(); + defer cmos.freeTest(); + + cmos.addTestParams( + "readStatusRegister", + .{ cmos.StatusRegister.A, false, @as(u8, 0x80) }, + ); + + expect(isBuzzy()); +} + +test "calcDayOfWeek" { + var date_time = DateTime{ + .second = 0, + .minute = 0, + .hour = 0, + .day = 10, + .month = 1, + .year = 2020, + .century = 0, + .day_of_week = 0, + }; + + var actual = calcDayOfWeek(date_time); + var expected = @as(u32, 5); + + expectEqual(expected, actual); + + date_time.day = 20; + date_time.month = 7; + date_time.year = 1940; + + actual = calcDayOfWeek(date_time); + expected = @as(u32, 6); + + expectEqual(expected, actual); + + date_time.day = 9; + date_time.month = 11; + date_time.year = 2043; + + actual = calcDayOfWeek(date_time); + expected = @as(u32, 1); + + expectEqual(expected, actual); + + date_time.day = 1; + date_time.month = 1; + date_time.year = 2000; + + actual = calcDayOfWeek(date_time); + expected = @as(u32, 6); + + expectEqual(expected, actual); +} + +test "isBcd not BCD" { + cmos.initTest(); + defer cmos.freeTest(); + + cmos.addTestParams( + "readStatusRegister", + .{ cmos.StatusRegister.B, false, @as(u8, 0x00) }, + ); + + expect(!isBcd()); +} + +test "isBcd BCD" { + cmos.initTest(); + defer cmos.freeTest(); + + cmos.addTestParams( + "readStatusRegister", + .{ cmos.StatusRegister.B, false, @as(u8, 0x04) }, + ); + + expect(isBcd()); +} + +test "is12Hr not 12Hr" { + const date_time = DateTime{ + .second = 0, + .minute = 0, + .hour = 0, + .day = 0, + .month = 0, + .year = 0, + .century = 0, + .day_of_week = 0, + }; + + cmos.initTest(); + defer cmos.freeTest(); + + cmos.addTestParams( + "readStatusRegister", + .{ cmos.StatusRegister.B, false, @as(u8, 0x00) }, + ); + + expect(!is12Hr(date_time)); +} + +test "is12Hr 12Hr" { + const date_time = DateTime{ + .second = 0, + .minute = 0, + .hour = 0x80, + .day = 0, + .month = 0, + .year = 0, + .century = 0, + .day_of_week = 0, + }; + + cmos.initTest(); + defer cmos.freeTest(); + + cmos.addTestParams( + "readStatusRegister", + .{ cmos.StatusRegister.B, false, @as(u8, 0x02) }, + ); + + expect(is12Hr(date_time)); +} + +test "bcdToBinary" { + var expected = @as(u32, 59); + var actual = bcdToBinary(0x59); + + expectEqual(expected, actual); + + expected = @as(u32, 48); + actual = bcdToBinary(0x48); + + expectEqual(expected, actual); + + expected = @as(u32, 1); + actual = bcdToBinary(0x01); + + expectEqual(expected, actual); +} + +test "readRtcRegisters" { + cmos.initTest(); + defer cmos.freeTest(); + + // Have 2 buzzy loops + cmos.addTestParams( + "readStatusRegister", + .{ cmos.StatusRegister.A, false, @as(u8, 0x80), cmos.StatusRegister.A, false, @as(u8, 0x80), cmos.StatusRegister.A, false, @as(u8, 0x00) }, + ); + + // All the RTC registers without century as it isn't supported yet + cmos.addTestParams("readRtcRegister", .{ + cmos.RtcRegister.SECOND, @as(u8, 1), + cmos.RtcRegister.MINUTE, @as(u8, 2), + cmos.RtcRegister.HOUR, @as(u8, 3), + cmos.RtcRegister.DAY, @as(u8, 10), + cmos.RtcRegister.MONTH, @as(u8, 1), + cmos.RtcRegister.YEAR, @as(u8, 20), + }); + + const expected = DateTime{ + .second = 1, + .minute = 2, + .hour = 3, + .day = 10, + .month = 1, + .year = 20, + .century = 2000, + .day_of_week = 5, + }; + const actual = readRtcRegisters(); + + expectEqual(expected, actual); +} + +test "readRtc unstable read" { + cmos.initTest(); + defer cmos.freeTest(); + + // No buzzy loop + cmos.addTestParams( + "readStatusRegister", + .{ cmos.StatusRegister.A, false, @as(u8, 0x00), cmos.StatusRegister.A, false, @as(u8, 0x00) }, + ); + + // Reading the RTC registers twice, second time is one second ahead + cmos.addTestParams("readRtcRegister", .{ + cmos.RtcRegister.SECOND, @as(u8, 1), + cmos.RtcRegister.MINUTE, @as(u8, 2), + cmos.RtcRegister.HOUR, @as(u8, 3), + cmos.RtcRegister.DAY, @as(u8, 10), + cmos.RtcRegister.MONTH, @as(u8, 1), + cmos.RtcRegister.YEAR, @as(u8, 20), + cmos.RtcRegister.SECOND, @as(u8, 2), + cmos.RtcRegister.MINUTE, @as(u8, 2), + cmos.RtcRegister.HOUR, @as(u8, 3), + cmos.RtcRegister.DAY, @as(u8, 10), + cmos.RtcRegister.MONTH, @as(u8, 1), + cmos.RtcRegister.YEAR, @as(u8, 20), + }); + + // Will try again, and now stable + // No buzzy loop + cmos.addTestParams( + "readStatusRegister", + .{ cmos.StatusRegister.A, false, @as(u8, 0x00), cmos.StatusRegister.A, false, @as(u8, 0x00) }, + ); + cmos.addTestParams("readRtcRegister", .{ + cmos.RtcRegister.SECOND, @as(u8, 2), + cmos.RtcRegister.MINUTE, @as(u8, 2), + cmos.RtcRegister.HOUR, @as(u8, 3), + cmos.RtcRegister.DAY, @as(u8, 10), + cmos.RtcRegister.MONTH, @as(u8, 1), + cmos.RtcRegister.YEAR, @as(u8, 20), + cmos.RtcRegister.SECOND, @as(u8, 2), + cmos.RtcRegister.MINUTE, @as(u8, 2), + cmos.RtcRegister.HOUR, @as(u8, 3), + cmos.RtcRegister.DAY, @as(u8, 10), + cmos.RtcRegister.MONTH, @as(u8, 1), + cmos.RtcRegister.YEAR, @as(u8, 20), + }); + + // Not BCD + cmos.addTestParams( + "readStatusRegister", + .{ cmos.StatusRegister.B, false, @as(u8, 0x00) }, + ); + + // Not 12hr + cmos.addTestParams( + "readStatusRegister", + .{ cmos.StatusRegister.B, false, @as(u8, 0x00) }, + ); + + const expected = DateTime{ + .second = 2, + .minute = 2, + .hour = 3, + .day = 10, + .month = 1, + .year = 2020, + .century = 2000, + .day_of_week = 5, + }; + const actual = readRtc(); + + expectEqual(expected, actual); +} + +test "readRtc is BCD" { + cmos.initTest(); + defer cmos.freeTest(); + + // No buzzy loop + cmos.addTestParams( + "readStatusRegister", + .{ cmos.StatusRegister.A, false, @as(u8, 0x00), cmos.StatusRegister.A, false, @as(u8, 0x00) }, + ); + + // Reading the RTC registers once + cmos.addTestParams("readRtcRegister", .{ + cmos.RtcRegister.SECOND, @as(u8, 0x30), + cmos.RtcRegister.MINUTE, @as(u8, 0x59), + cmos.RtcRegister.HOUR, @as(u8, 0x11), + cmos.RtcRegister.DAY, @as(u8, 0x10), + cmos.RtcRegister.MONTH, @as(u8, 0x1), + cmos.RtcRegister.YEAR, @as(u8, 0x20), + cmos.RtcRegister.SECOND, @as(u8, 0x30), + cmos.RtcRegister.MINUTE, @as(u8, 0x59), + cmos.RtcRegister.HOUR, @as(u8, 0x11), + cmos.RtcRegister.DAY, @as(u8, 0x10), + cmos.RtcRegister.MONTH, @as(u8, 0x1), + cmos.RtcRegister.YEAR, @as(u8, 0x20), + }); + + // BCD + cmos.addTestParams( + "readStatusRegister", + .{ cmos.StatusRegister.B, false, @as(u8, 0x04) }, + ); + + // Not 12hr + cmos.addTestParams( + "readStatusRegister", + .{ cmos.StatusRegister.B, false, @as(u8, 0x00) }, + ); + + const expected = DateTime{ + .second = 30, + .minute = 59, + .hour = 11, + .day = 10, + .month = 1, + .year = 2020, + .century = 2000, + .day_of_week = 5, + }; + const actual = readRtc(); + + expectEqual(expected, actual); +} + +test "readRtc is 12 hours" { + cmos.initTest(); + defer cmos.freeTest(); + + // No buzzy loop + cmos.addTestParams( + "readStatusRegister", + .{ cmos.StatusRegister.A, false, @as(u8, 0x00), cmos.StatusRegister.A, false, @as(u8, 0x00) }, + ); + + // Reading the RTC registers once + cmos.addTestParams("readRtcRegister", .{ + cmos.RtcRegister.SECOND, @as(u8, 1), + cmos.RtcRegister.MINUTE, @as(u8, 2), + cmos.RtcRegister.HOUR, @as(u8, 0x83), + cmos.RtcRegister.DAY, @as(u8, 10), + cmos.RtcRegister.MONTH, @as(u8, 1), + cmos.RtcRegister.YEAR, @as(u8, 20), + cmos.RtcRegister.SECOND, @as(u8, 1), + cmos.RtcRegister.MINUTE, @as(u8, 2), + cmos.RtcRegister.HOUR, @as(u8, 0x83), + cmos.RtcRegister.DAY, @as(u8, 10), + cmos.RtcRegister.MONTH, @as(u8, 1), + cmos.RtcRegister.YEAR, @as(u8, 20), + }); + + // Not BCD + cmos.addTestParams( + "readStatusRegister", + .{ cmos.StatusRegister.B, false, @as(u8, 0x00) }, + ); + + // 12hr + cmos.addTestParams( + "readStatusRegister", + .{ cmos.StatusRegister.B, false, @as(u8, 0x02) }, + ); + + const expected = DateTime{ + .second = 1, + .minute = 2, + .hour = 15, + .day = 10, + .month = 1, + .year = 2020, + .century = 2000, + .day_of_week = 5, + }; + const actual = readRtc(); + + expectEqual(expected, actual); +} + +test "setRate below 3" { + expectError(RtcError.RateError, setRate(0)); + expectError(RtcError.RateError, setRate(1)); + expectError(RtcError.RateError, setRate(2)); +} + +test "setRate" { + cmos.initTest(); + defer cmos.freeTest(); + + cmos.addTestParams("readStatusRegister", .{ cmos.StatusRegister.A, true, @as(u8, 0x10) }); + cmos.addTestParams("writeStatusRegister", .{ cmos.StatusRegister.A, @as(u8, 0x17), true }); + + const rate = @as(u8, 7); + try setRate(rate); +} + +test "enableInterrupts" { + cmos.initTest(); + defer cmos.freeTest(); + + cmos.addTestParams("readStatusRegister", .{ cmos.StatusRegister.B, true, @as(u8, 0x20) }); + cmos.addTestParams("writeStatusRegister", .{ cmos.StatusRegister.B, @as(u8, 0x60), true }); + + enableInterrupts(); +} + +/// +/// Check that the IRQ is registered correctly +/// +fn rt_init() void { + var irq_exists = false; + irq.registerIrq(pic.IRQ_REAL_TIME_CLOCK, rtcHandler) catch |err| switch (err) { + error.IrqExists => { + // We should get this error + irq_exists = true; + }, + error.InvalidIrq => { + panic(@errorReturnTrace(), "IRQ for RTC, IRQ number: {} is invalid", .{pic.IRQ_REAL_TIME_CLOCK}); + }, + }; + + if (!irq_exists) { + panic(@errorReturnTrace(), "IRQ for RTC doesn't exists\n", .{}); + } + + // Check the rate + const status_a = cmos.readStatusRegister(cmos.StatusRegister.A, false); + if (status_a & @as(u8, 0x0F) != 7) { + panic(@errorReturnTrace(), "Rate not set properly, got: {}\n", .{status_a & @as(u8, 0x0F)}); + } + + // Check if interrupts are enabled + const status_b = cmos.readStatusRegister(cmos.StatusRegister.B, true); + if (status_b & ~@as(u8, 0x40) == 0) { + panic(@errorReturnTrace(), "Interrupts not enabled\n", .{}); + } + + log.logInfo("RTC: Tested init\n", .{}); +} + +/// +/// Check if the interrupt handler is called after a sleep so check that the RTC interrupts fire. +/// +fn rt_interrupts() void { + const prev_ticks = ticks; + + pit.waitTicks(100); + + if (prev_ticks == ticks) { + panic(@errorReturnTrace(), "No interrupt happened\n", .{}); + } + + log.logInfo("RTC: Tested interrupts\n", .{}); +} + +/// +/// Run all the runtime tests. +/// +fn runtimeTests() void { + rt_init(); + rt_interrupts(); +} diff --git a/test/kernel/arch/x86/rt-test.py b/test/kernel/arch/x86/rt-test.py index c9a15c3..237eda6 100644 --- a/test/kernel/arch/x86/rt-test.py +++ b/test/kernel/arch/x86/rt-test.py @@ -10,12 +10,14 @@ def get_test_cases(TestCase): TestCase("ISR tests", [r"ISR: Tested registered handlers", r"ISR: Tested opened IDT entries"]), TestCase("IRQ init", [r"Init irq", r"Done"]), TestCase("IRQ tests", [r"IRQ: Tested registered handlers", r"IRQ: Tested opened IDT entries"]), + TestCase("Paging init", [r"Init paging", r"Done"]), + TestCase("Paging tests", [r"Paging: Tested accessing unmapped memory", r"Paging: Tested accessing mapped memory"]), TestCase("PIT init", [r"Init pit"]), TestCase("PIT init", [r".+"], r"\[DEBUG\] "), TestCase("PIT init", [r"Done"]), TestCase("PIT tests", [r"PIT: Tested init", r"PIT: Tested wait ticks", r"PIT: Tested wait ticks 2"]), - TestCase("Paging init", [r"Init paging", r"Done"]), - TestCase("Paging tests", [r"Paging: Tested accessing unmapped memory", r"Paging: Tested accessing mapped memory"]), + TestCase("RTC init", [r"Init rtc", r"Done"]), + TestCase("RTC tests", [r"RTC: Tested init", r"RTC: Tested interrupts"]), TestCase("Syscalls init", [r"Init syscalls", r"Done"]), TestCase("Syscall tests", [r"Syscalls: Tested no args", r"Syscalls: Tested 1 arg", r"Syscalls: Tested 2 args", r"Syscalls: Tested 3 args", r"Syscalls: Tested 4 args", r"Syscalls: Tested 5 args"]) ] diff --git a/test/mock/kernel/arch_mock.zig b/test/mock/kernel/arch_mock.zig index 47a31d3..c307ea9 100644 --- a/test/mock/kernel/arch_mock.zig +++ b/test/mock/kernel/arch_mock.zig @@ -101,3 +101,5 @@ pub fn init(mb_info: *multiboot.multiboot_info_t, mem_profile: *const MemProfile pub fn mock_disableInterrupts() void {} pub fn mock_enableInterrupts() void {} + +pub fn mock_ioWait() void {} diff --git a/test/mock/kernel/cmos_mock.zig b/test/mock/kernel/cmos_mock.zig new file mode 100644 index 0000000..735b2a4 --- /dev/null +++ b/test/mock/kernel/cmos_mock.zig @@ -0,0 +1,34 @@ +const mock_framework = @import("mock_framework.zig"); +pub const initTest = mock_framework.initTest; +pub const freeTest = mock_framework.freeTest; +pub const addTestParams = mock_framework.addTestParams; +pub const addConsumeFunction = mock_framework.addConsumeFunction; +pub const addRepeatFunction = mock_framework.addRepeatFunction; + +pub const StatusRegister = enum { + A, + B, + C, +}; + +pub const RtcRegister = enum { + SECOND, + MINUTE, + HOUR, + DAY, + MONTH, + YEAR, + CENTURY, +}; + +pub fn readRtcRegister(reg: RtcRegister) u8 { + return mock_framework.performAction("readRtcRegister", u8, .{reg}); +} + +pub fn readStatusRegister(reg: StatusRegister, comptime disable_nmi: bool) u8 { + return mock_framework.performAction("readStatusRegister", u8, .{ reg, disable_nmi }); +} + +pub fn writeStatusRegister(reg: StatusRegister, data: u8, comptime disable_nmi: bool) void { + return mock_framework.performAction("writeStatusRegister", void, .{ reg, data, disable_nmi }); +} diff --git a/test/mock/kernel/mock_framework.zig b/test/mock/kernel/mock_framework.zig index 17e8d4a..08ad282 100644 --- a/test/mock/kernel/mock_framework.zig +++ b/test/mock/kernel/mock_framework.zig @@ -7,6 +7,7 @@ const TailQueue = std.TailQueue; const warn = std.debug.warn; const gdt = @import("gdt_mock.zig"); const idt = @import("idt_mock.zig"); +const cmos = @import("cmos_mock.zig"); /// /// The enumeration of types that the mocking framework supports. These include basic types like u8 @@ -18,6 +19,8 @@ const DataElementType = enum { U8, U16, U32, + ECmosStatusRegister, + ECmosRtcRegister, PTR_CONST_GdtPtr, PTR_CONST_IdtPtr, ERROR_IDTERROR_VOID, @@ -34,6 +37,9 @@ const DataElementType = enum { FN_IU8_IU8_OU16, FN_IU16_IU8_OVOID, FN_IU16_IU16_OVOID, + FN_IECmosStatusRegister_IBOOL_OU8, + FN_IECmosStatusRegister_IU8_IBOOL_OVOID, + FN_IECmosRtcRegister_OU8, FN_IU8_IEFNOVOID_OERRORIDTERRORVOID, FN_IU8_INFNOVOID_OERRORIDTERRORVOID, FN_IPTRCONSTGDTPTR_OVOID, @@ -44,6 +50,9 @@ const DataElementType = enum { /// A tagged union of all the data elements that the mocking framework can work with. This can be /// expanded to add new types. This is needed as need a list of data that all have different types, /// so this wraps the data into a union, (which is of one type) so can have a list of them. +/// When https://github.com/ziglang/zig/issues/383 anf https://github.com/ziglang/zig/issues/2907 +/// is done, can programitaclly create types for this. Can use a compile time block that loops +/// through the available basic types and create function types so don't have a long list. /// const DataElement = union(DataElementType) { BOOL: bool, @@ -51,6 +60,8 @@ const DataElement = union(DataElementType) { U8: u8, U16: u16, U32: u32, + ECmosStatusRegister: cmos.StatusRegister, + ECmosRtcRegister: cmos.RtcRegister, PTR_CONST_GdtPtr: *const gdt.GdtPtr, PTR_CONST_IdtPtr: *const idt.IdtPtr, ERROR_IDTERROR_VOID: idt.IdtError!void, @@ -67,6 +78,9 @@ const DataElement = union(DataElementType) { FN_IU8_IU8_OU16: fn (u8, u8) u16, FN_IU16_IU8_OVOID: fn (u16, u8) void, FN_IU16_IU16_OVOID: fn (u16, u16) void, + FN_IECmosStatusRegister_IBOOL_OU8: fn (cmos.StatusRegister, bool) u8, + FN_IECmosStatusRegister_IU8_IBOOL_OVOID: fn (cmos.StatusRegister, u8, bool) void, + FN_IECmosRtcRegister_OU8: fn (cmos.RtcRegister) u8, FN_IU8_IEFNOVOID_OERRORIDTERRORVOID: fn (u8, extern fn () void) idt.IdtError!void, FN_IU8_INFNOVOID_OERRORIDTERRORVOID: fn (u8, fn () callconv(.Naked) void) idt.IdtError!void, FN_IPTRCONSTGDTPTR_OVOID: fn (*const gdt.GdtPtr) void, @@ -151,6 +165,8 @@ fn Mock() type { u8 => DataElement{ .U8 = arg }, u16 => DataElement{ .U16 = arg }, u32 => DataElement{ .U32 = arg }, + cmos.StatusRegister => DataElement{ .ECmosStatusRegister = arg }, + cmos.RtcRegister => DataElement{ .ECmosRtcRegister = arg }, *const gdt.GdtPtr => DataElement{ .PTR_CONST_GdtPtr = arg }, *const idt.IdtPtr => DataElement{ .PTR_CONST_IdtPtr = arg }, idt.IdtError!void => DataElement{ .ERROR_IDTERROR_VOID = arg }, @@ -167,6 +183,9 @@ fn Mock() type { fn (u8, u8) u16 => DataElement{ .FN_IU8_IU8_OU16 = arg }, fn (u16, u8) void => DataElement{ .FN_IU16_IU8_OVOID = arg }, fn (u16, u16) void => DataElement{ .FN_IU16_IU16_OVOID = arg }, + fn (cmos.StatusRegister, bool) u8 => DataElement{ .FN_IECmosStatusRegister_IBOOL_OU8 = arg }, + fn (cmos.StatusRegister, u8, bool) void => DataElement{ .FN_IECmosStatusRegister_IU8_IBOOL_OVOID = arg }, + fn (cmos.RtcRegister) u8 => DataElement{ .FN_IECmosRtcRegister_OU8 = arg }, fn (*const gdt.GdtPtr) void => DataElement{ .FN_IPTRCONSTGDTPTR_OVOID = arg }, fn (*const idt.IdtPtr) void => DataElement{ .FN_IPTRCONSTIDTPTR_OVOID = arg }, fn (u8, extern fn () void) idt.IdtError!void => DataElement{ .FN_IU8_IEFNOVOID_OERRORIDTERRORVOID = arg }, @@ -191,6 +210,8 @@ fn Mock() type { u8 => DataElementType.U8, u16 => DataElementType.U16, u32 => DataElementType.U32, + cmos.StatusRegister => DataElementType.ECmosStatusRegister, + cmos.RtcRegister => DataElementType.ECmosRtcRegister, *const gdt.GdtPtr => DataElement.PTR_CONST_GdtPtr, *const idt.IdtPtr => DataElement.PTR_CONST_IdtPtr, idt.IdtError!void => DataElement.ERROR_IDTERROR_VOID, @@ -206,6 +227,9 @@ fn Mock() type { fn (u8, u8) u16 => DataElementType.FN_IU8_IU8_OU16, fn (u16, u8) void => DataElementType.FN_IU16_IU8_OVOID, fn (u16, u16) void => DataElementType.FN_IU16_IU16_OVOID, + fn (cmos.StatusRegister, bool) u8 => DataElementType.FN_IECmosStatusRegister_IBOOL_OU8, + fn (cmos.StatusRegister, u8, bool) void => DataElementType.FN_IECmosStatusRegister_IU8_IBOOL_OVOID, + fn (cmos.RtcRegister) u8 => DataElementType.FN_IECmosRtcRegister_OU8, fn (*const gdt.GdtPtr) void => DataElementType.FN_IPTRCONSTGDTPTR_OVOID, fn (*const idt.IdtPtr) void => DataElementType.FN_IPTRCONSTIDTPTR_OVOID, fn (u8, extern fn () void) idt.IdtError!void => DataElementType.FN_IU8_IEFNOVOID_OERRORIDTERRORVOID, @@ -232,6 +256,8 @@ fn Mock() type { u8 => element.U8, u16 => element.U16, u32 => element.U32, + cmos.StatusRegister => element.ECmosStatusRegister, + cmos.RtcRegister => element.ECmosRtcRegister, *const gdt.GdtPtr => element.PTR_CONST_GdtPtr, *const idt.IdtPtr => element.PTR_CONST_IdtPtr, idt.IdtError!void => element.ERROR_IDTERROR_VOID, @@ -247,6 +273,9 @@ fn Mock() type { fn (u8, u8) u16 => element.FN_IU8_IU8_OU16, fn (u16, u8) void => element.FN_IU16_IU8_OVOID, fn (u16, u16) void => element.FN_IU16_IU16_OVOID, + fn (cmos.StatusRegister, bool) u8 => element.FN_IECmosStatusRegister_IBOOL_OU8, + fn (cmos.StatusRegister, u8, bool) void => element.FN_IECmosStatusRegister_IU8_IBOOL_OVOID, + fn (cmos.RtcRegister) u8 => element.FN_IECmosRtcRegister_OU8, fn (*const gdt.GdtPtr) void => element.FN_IPTRCONSTGDTPTR_OVOID, fn (*const idt.IdtPtr) void => element.FN_IPTRCONSTIDTPTR_OVOID, fn (u8, extern fn () void) idt.IdtError!void => element.FN_IU8_IEFNOVOID_OERRORIDTERRORVOID, @@ -261,7 +290,7 @@ fn Mock() type { /// /// Arguments: /// IN RetType: type - The return type of the function. - /// IN params: arglist - The argument list for the function. + /// IN params: var - The argument list for the function. /// /// Return: type /// A function type that represents the return type and its arguments. @@ -271,7 +300,7 @@ fn Mock() type { 0 => fn () RetType, 1 => fn (@TypeOf(params[0])) RetType, 2 => fn (@TypeOf(params[0]), @TypeOf(params[1])) RetType, - else => @compileError("Couldn't generate function type for " ++ params.len ++ "parameters\n"), + else => @compileError("Couldn't generate function type for " ++ len ++ " parameters\n"), }; } @@ -377,7 +406,7 @@ fn Mock() type { /// perform a action. /// IN fun_name: []const u8 - The function name to act on. /// IN RetType: type - The return type of the function being mocked. - /// IN params: arglist - The list of parameters of the mocked function. + /// IN params: var - The list of parameters of the mocked function. /// /// Return: RetType /// The return value of the mocked function. This can be void. @@ -418,11 +447,18 @@ fn Mock() type { // Waiting for this: // error: compiler bug: unable to call var args function at compile time. https://github.com/ziglang/zig/issues/313 // to be resolved + + comptime const param_len = [1]u8{switch (params.len) { + 0...9 => params.len + @as(u8, '0'), + else => unreachable, + }}; + const expected_function = switch (params.len) { 0 => fn () RetType, 1 => fn (@TypeOf(params[0])) RetType, 2 => fn (@TypeOf(params[0]), @TypeOf(params[1])) RetType, - else => @compileError("Couldn't generate function type for " ++ params.len ++ "parameters\n"), + 3 => fn (@TypeOf(params[0]), @TypeOf(params[1]), @TypeOf(params[2])) RetType, + else => @compileError("Couldn't generate function type for " ++ param_len ++ " parameters\n"), }; // Get the corresponding DataElementType @@ -438,11 +474,12 @@ fn Mock() type { action_list.destroyNode(test_node, GlobalAllocator); // The data element will contain the function to call - var r = switch (params.len) { + const r = switch (params.len) { 0 => actual_function(), 1 => actual_function(params[0]), 2 => actual_function(params[0], params[1]), - else => @compileError(params.len ++ " or more parameters not supported"), + 3 => actual_function(params[0], params[1], params[2]), + else => @compileError(param_len ++ " or more parameters not supported"), }; break :ret r; @@ -451,11 +488,18 @@ fn Mock() type { // Do the same for ActionType.ConsumeFunctionCall but instead of // popping the function, just peak const test_element = action.data; + + comptime const param_len = [1]u8{switch (params.len) { + 0...9 => params.len + @as(u8, '0'), + else => unreachable, + }}; + const expected_function = switch (params.len) { 0 => fn () RetType, 1 => fn (@TypeOf(params[0])) RetType, 2 => fn (@TypeOf(params[0]), @TypeOf(params[1])) RetType, - else => @compileError("Couldn't generate function type for " ++ params.len ++ "parameters\n"), + 3 => fn (@TypeOf(params[0]), @TypeOf(params[1]), @TypeOf(params[2])) RetType, + else => @compileError("Couldn't generate function type for " ++ param_len ++ " parameters\n"), }; // Get the corresponding DataElementType @@ -472,7 +516,8 @@ fn Mock() type { 0 => actual_function(), 1 => actual_function(params[0]), 2 => actual_function(params[0], params[1]), - else => @compileError(params.len ++ " or more parameters not supported"), + 3 => actual_function(params[0], params[1], params[2]), + else => @compileError(param_len ++ " or more parameters not supported"), }; break :ret r; @@ -634,7 +679,7 @@ pub fn addRepeatFunction(comptime fun_name: []const u8, function: var) void { /// Arguments: /// IN fun_name: []const u8 - The function name to act on. /// IN RetType: type - The return type of the function being mocked. -/// IN params: arglist - The list of parameters of the mocked function. +/// IN params: var - The list of parameters of the mocked function. /// /// Return: RetType /// The return value of the mocked function. This can be void.