pluto/src/kernel/arch/x86/pit.zig
DrDeano fbd135c437
Move to std.log
Removed nothing to pass tests


Removed log imports

Plus some spelling
2020-07-23 20:47:56 +01:00

638 lines
21 KiB
Zig

const std = @import("std");
const maxInt = std.math.maxInt;
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 = if (is_test) @import(mock_path ++ "arch_mock.zig") else @import("arch.zig");
const panic = if (is_test) @import(mock_path ++ "panic_mock.zig").panic else @import("../../panic.zig").panic;
const irq = @import("irq.zig");
const pic = @import("pic.zig");
/// The enum for selecting the counter
const CounterSelect = enum {
/// Counter 0.
Counter0,
/// Counter 1.
Counter1,
/// Counter 2.
Counter2,
///
/// Get the register port for the selected counter.
///
/// Arguments:
/// IN counter: CounterSelect - The counter to get the register port.
///
/// Return: u16
/// The register port for the selected counter.
///
pub fn getRegister(counter: CounterSelect) u16 {
return switch (counter) {
.Counter0 => COUNTER_0_REGISTER,
.Counter1 => COUNTER_1_REGISTER,
.Counter2 => COUNTER_2_REGISTER,
};
}
///
/// Get the operational control work for the selected counter.
///
/// Arguments:
/// IN counter: CounterSelect - The counter to get the operational control work.
///
/// Return: u16
/// The operational control work for the selected counter.
///
pub fn getCounterOCW(counter: CounterSelect) u8 {
return switch (counter) {
.Counter0 => OCW_SELECT_COUNTER_0,
.Counter1 => OCW_SELECT_COUNTER_1,
.Counter2 => OCW_SELECT_COUNTER_2,
};
}
};
/// The error set that can be returned from PIT functions
const PitError = error{
/// The frequency to be used for a counter is invalid. This would be if the frequency is less
/// than 19 or greater than MAX_FREQUENCY.
InvalidFrequency,
};
/// The port address for the PIT data register for counter 0. This is going to be used as the
/// system clock.
const COUNTER_0_REGISTER: u16 = 0x40;
/// The port address for the PIT data register for counter 1. This was used for refreshing the DRAM
/// chips. But now is unused and unknown use, so won't use.
const COUNTER_1_REGISTER: u16 = 0x41;
/// The port address for the PIT data register for counter 2. Connected to the PC speakers, we'll
/// use this for the speakers.
const COUNTER_2_REGISTER: u16 = 0x42;
/// The port address for the PIT control word register. Used to tell the PIT controller what is
/// about to happen. Tell what data is going where, lower or upper part of it's registers.
const COMMAND_REGISTER: u16 = 0x43;
// The operational command word for the different modes.
//
// Bit 0: (BCP) Binary Counter.
// 0: Binary.
// 1: Binary Coded Decimal (BCD).
// Bit 1-3: (M0, M1, M2) Operating Mode. See above sections for a description of each.
// 000: Mode 0: Interrupt or Terminal Count.
// 001: Mode 1: Programmable one-shot.
// 010: Mode 2: Rate Generator.
// 011: Mode 3: Square Wave Generator.
// 100: Mode 4: Software Triggered Strobe.
// 101: Mode 5: Hardware Triggered Strobe.
// 110: Undefined; Don't use.
// 111: Undefined; Don't use.
// Bits 4-5: (RL0, RL1) Read/Load Mode. We are going to read or send data to a counter register.
// 00: Counter value is latched into an internal control register at the time of the I/O write operation.
// 01: Read or Load Least Significant Byte (LSB) only.
// 10: Read or Load Most Significant Byte (MSB) only.
// 11: Read or Load LSB first then MSB.
// Bits 6-7: (SC0-SC1) Select Counter. See above sections for a description of each.
// 00: Counter 0.
// 01: Counter 1.
// 10: Counter 2.
// 11: Illegal value.
/// Have the counter count in binary (internally?).
const OCW_BINARY_COUNT_BINARY: u8 = 0x00;
/// Have the counter count in BCD (internally?).
const OCW_BINARY_COUNT_BCD: u8 = 0x01;
/// The PIT counter will be programmed with an initial COUNT value that counts down at a rate of
/// the input clock frequency. When the COUNT reaches 0, and after the control word is written,
/// then its OUT pit is set high (1). Count down starts then the COUNT is set. The OUT pin remains
/// high until the counter is reloaded with a new COUNT value or a new control work is written.
const OCW_MODE_TERMINAL_COUNT: u8 = 0x00;
/// The counter is programmed to output a pulse every curtain number of clock pulses. The OUT pin
/// remains high as soon as a control word is written. When COUNT is written, the counter waits
/// until the rising edge of the GATE pin to start. One clock pulse after the GATE pin, the OUT
/// pin will remain low until COUNT reaches 0.
const OCW_MODE_ONE_SHOT: u8 = 0x02;
/// The counter is initiated with a COUNT value. Counting starts next clock pulse. OUT pin remains
/// high until COUNT reaches 1, then is set low for one clock pulse. Then COUNT is reset back to
/// initial value and OUT pin is set high again.
const OCW_MODE_RATE_GENERATOR: u8 = 0x04;
/// Similar to PIT_OCW_MODE_RATE_GENERATOR, but OUT pin will be high for half the time and low for
/// half the time. Good for the speaker when setting a tone.
const OCW_MODE_SQUARE_WAVE_GENERATOR: u8 = 0x06;
/// The counter is initiated with a COUNT value. Counting starts on next clock pulse. OUT pin remains
/// high until count is 0. Then OUT pin is low for one clock pulse. Then resets to high again.
const OCW_MODE_SOFTWARE_TRIGGER: u8 = 0x08;
/// The counter is initiated with a COUNT value. OUT pin remains high until the rising edge of the
/// GATE pin. Then the counting begins. When COUNT reaches 0, OUT pin goes low for one clock pulse.
/// Then COUNT is reset and OUT pin goes high. This cycles for each rising edge of the GATE pin.
const OCW_MODE_HARDWARE_TRIGGER: u8 = 0x0A;
/// The counter value is latched into an internal control register at the time of the I/O write
/// operations.
const OCW_READ_LOAD_LATCH: u8 = 0x00;
/// Read or load the most significant bit only.
const OCW_READ_LOAD_LSB_ONLY: u8 = 0x10;
/// Read or load the least significant bit only.
const OCW_READ_LOAD_MSB_ONLY: u8 = 0x20;
/// Read or load the least significant bit first then the most significant bit.
const OCW_READ_LOAD_DATA: u8 = 0x30;
/// The OCW bits for selecting counter 0. Used for the system clock.
const OCW_SELECT_COUNTER_0: u8 = 0x00;
/// The OCW bits for selecting counter 1. Was for the memory refreshing.
const OCW_SELECT_COUNTER_1: u8 = 0x40;
/// The OCW bits for selecting counter 2. Channel for the speaker.
const OCW_SELECT_COUNTER_2: u8 = 0x80;
/// The divisor constant
const MAX_FREQUENCY: u32 = 1193182;
/// The number of ticks that has passed when counter 0 was initially set up.
var ticks: u32 = 0;
/// The number of tick that has passed when counter 1 was initially set up.
var ram_ticks: u32 = 0;
/// The number of tick that has passed when counter 2 was initially set up.
var speaker_ticks: u32 = 0;
/// The current frequency of counter 0
var current_freq_0: u32 = undefined;
/// The current frequency of counter 1
var current_freq_1: u32 = undefined;
/// The current frequency of counter 2
var current_freq_2: u32 = undefined;
/// The number of nanoseconds between interrupts.
var time_ns: u32 = undefined;
/// The number of nanoseconds to be added the to time_ns for time between interrupts.
var time_under_1_ns: u32 = undefined;
///
/// Send a command to the PIT command register.
///
/// Arguments:
/// IN cmd: u8 - The command to send to the PIT.
///
inline fn sendCommand(cmd: u8) void {
arch.outb(COMMAND_REGISTER, cmd);
}
///
/// Read the current mode of the selected counter.
///
/// Arguments:
/// IN counter: CounterSelect - The counter to read the mode the counter is operating in.
///
/// Return: u8
/// The mode the counter is operating in. Use the masks above to get each part.
///
inline fn readBackCommand(counter: CounterSelect) u8 {
sendCommand(0xC2);
return 0x3F & arch.inb(counter.getRegister());
}
///
/// Send data to a given counter. Will be only one of the 3 counters as is an internal function.
///
/// Arguments:
/// IN counter: CounterSelect - The counter port to send the data to.
/// IN data: u8 - The data to send.
///
inline fn sendDataToCounter(counter: CounterSelect, data: u8) void {
arch.outb(counter.getRegister(), data);
}
///
/// The interrupt handler for the PIT. This will increment a counter for now.
///
/// Arguments:
/// IN ctx: *arch.CpuState - Pointer to the interrupt context containing the contents
/// of the register at the time of the interrupt.
///
fn pitHandler(ctx: *arch.CpuState) usize {
ticks +%= 1;
return @ptrToInt(ctx);
}
///
/// Set up a counter with a tick rate and mode of operation.
///
/// Arguments:
/// IN counter: CounterSelect - Which counter is to be set up.
/// IN freq: u32 - The frequency that the counter operates at. Any frequency that
/// is between 0..18 (inclusive) or above MAX_FREQUENCY will return
/// an error.
/// IN mode: u8 - The mode of operation that the counter will operate in. See
/// the modes definition above to chose which mode the counter is
/// to run at.
///
/// Error: PitError:
/// PitError.InvalidFrequency - If the given frequency is out of bounds. Less than 19 or
/// greater than MAX_FREQUENCY.
///
fn setupCounter(counter: CounterSelect, freq: u32, mode: u8) PitError!void {
if (freq < 19 or freq > MAX_FREQUENCY) {
return PitError.InvalidFrequency;
}
// 65536, the slowest possible frequency. Roughly 19Hz
var reload_value: u32 = 0x10000;
// The lowest possible frequency is 18Hz.
// MAX_FREQUENCY / 18 > u16 N
// MAX_FREQUENCY / 19 < u16 Y
if (freq > 18) {
if (freq < MAX_FREQUENCY) {
// Rounded integer division
reload_value = (MAX_FREQUENCY + (freq / 2)) / freq;
} else {
// The fastest possible frequency if frequency is too high
reload_value = 1;
}
}
// Update the frequency with the actual one that the PIT will be using
// Rounded integer division
const frequency = (MAX_FREQUENCY + (reload_value / 2)) / reload_value;
// Calculate the amount of nanoseconds between interrupts
time_ns = 1000000000 / frequency;
// Calculate the number of picoseconds, the left over from nanoseconds
time_under_1_ns = ((1000000000 % frequency) * 1000 + (frequency / 2)) / frequency;
// Set the frequency for the counter being set up
switch (counter) {
.Counter0 => current_freq_0 = frequency,
.Counter1 => current_freq_1 = frequency,
.Counter2 => current_freq_2 = frequency,
}
// Get the u16 version as this is what will be loaded into the PIT
// If truncating 0x10000, this will equal 0, which is the slowest.
const reload_val_16 = @truncate(u16, reload_value);
// Send the set up command to the PIT
sendCommand(mode | OCW_READ_LOAD_DATA | counter.getCounterOCW());
sendDataToCounter(counter, @truncate(u8, reload_val_16));
sendDataToCounter(counter, @truncate(u8, reload_val_16 >> 8));
// Reset the counter ticks
switch (counter) {
.Counter0 => ticks = 0,
.Counter1 => ram_ticks = 0,
.Counter2 => speaker_ticks = 0,
}
}
///
/// A simple wait that used the PIT to wait a number of ticks.
///
/// Arguments:
/// IN ticks_to_wait: u32 - The number of ticks to wait.
///
pub fn waitTicks(ticks_to_wait: u32) void {
if (ticks > maxInt(u32) - ticks_to_wait) {
// Integer overflow
// Calculate the 2 conditions
const wait_ticks1 = maxInt(u32) - ticks;
const wait_ticks2 = ticks_to_wait - wait_ticks1;
while (ticks > wait_ticks1) {
arch.halt();
}
while (ticks < wait_ticks2) {
arch.halt();
}
} else {
const wait_ticks = ticks + ticks_to_wait;
while (ticks < wait_ticks) {
arch.halt();
}
}
}
///
/// Get the number of ticks that have passed when the PIT was initiated.
///
/// Return: u32
/// Number of ticks passed.
///
pub fn getTicks() u32 {
return ticks;
}
///
/// Get the frequency the PIT is ticking at.
///
/// Return: u32
/// The frequency the PIT is running at
///
pub fn getFrequency() u32 {
return current_freq_0;
}
///
/// Initialise the PIT with a handler to IRQ 0.
///
pub fn init() void {
std.log.info(.pit, "Init\n", .{});
defer std.log.info(.pit, "Done\n", .{});
// Set up counter 0 at 10000hz in a square wave mode counting in binary
const freq: u32 = 10000;
setupCounter(CounterSelect.Counter0, freq, OCW_MODE_SQUARE_WAVE_GENERATOR | OCW_BINARY_COUNT_BINARY) catch |e| {
panic(@errorReturnTrace(), "Invalid frequency: {}\n", .{freq});
};
std.log.debug(.pit, "Set frequency at: {}Hz, real frequency: {}Hz\n", .{ freq, getFrequency() });
// Installs 'pitHandler' to IRQ0 (pic.IRQ_PIT)
irq.registerIrq(pic.IRQ_PIT, pitHandler) catch |err| switch (err) {
error.IrqExists => {
panic(@errorReturnTrace(), "IRQ for PIT, IRQ number: {} exists", .{pic.IRQ_PIT});
},
error.InvalidIrq => {
panic(@errorReturnTrace(), "IRQ for PIT, IRQ number: {} is invalid", .{pic.IRQ_PIT});
},
};
switch (build_options.test_mode) {
.Initialisation => runtimeTests(),
else => {},
}
}
test "sendCommand" {
arch.initTest();
defer arch.freeTest();
const cmd: u8 = 10;
arch.addTestParams("outb", .{ COMMAND_REGISTER, cmd });
sendCommand(cmd);
}
test "readBackCommand" {
arch.initTest();
defer arch.freeTest();
const cmd: u8 = 0xC2;
arch.addTestParams("outb", .{ COMMAND_REGISTER, cmd });
arch.addTestParams("inb", .{ COUNTER_0_REGISTER, @as(u8, 0x20) });
const actual = readBackCommand(CounterSelect.Counter0);
expectEqual(@as(u8, 0x20), actual);
}
test "sendDataToCounter" {
arch.initTest();
defer arch.freeTest();
const data: u8 = 10;
arch.addTestParams("outb", .{ COUNTER_0_REGISTER, data });
sendDataToCounter(CounterSelect.Counter0, data);
}
test "setupCounter lowest frequency" {
arch.initTest();
defer arch.freeTest();
const counter = CounterSelect.Counter0;
const port = counter.getRegister();
var freq: u32 = 0;
// Reload value will be 0 (0x10000), the slowest speed for frequency less than 19
const expected_reload_value: u16 = 0;
// Slowest frequency the PIT can run at
const expected_freq: u32 = 19;
const mode = OCW_MODE_SQUARE_WAVE_GENERATOR | OCW_BINARY_COUNT_BINARY;
const command = mode | OCW_READ_LOAD_DATA | counter.getCounterOCW();
while (freq <= 18) : (freq += 1) {
// arch.addTestParams("outb", COMMAND_REGISTER, command, port, @truncate(u8, expected_reload_value), port, @truncate(u8, expected_reload_value >> 8));
expectError(PitError.InvalidFrequency, setupCounter(counter, freq, mode));
// expectEqual(u32(0), ticks);
// expectEqual(expected_freq, current_freq_0);
// expectEqual(expected_freq, getFrequency());
// // These are the hard coded expected values. Calculated externally to check the internal calculation
// expectEqual(u32(52631578), time_ns);
// expectEqual(u32(947), time_under_1_ns);
}
// Reset globals
time_ns = 0;
current_freq_0 = 0;
ticks = 0;
}
test "setupCounter highest frequency" {
arch.initTest();
defer arch.freeTest();
const counter = CounterSelect.Counter0;
const port = counter.getRegister();
// Set the frequency above the maximum
const freq = MAX_FREQUENCY + 10;
// Reload value will be 1, the fastest speed for frequency greater than MAX_FREQUENCY
const expected_reload_value = 1;
// Slowest frequency the PIT can run at
const expected_freq = MAX_FREQUENCY;
const mode = OCW_MODE_SQUARE_WAVE_GENERATOR | OCW_BINARY_COUNT_BINARY;
const command = mode | OCW_READ_LOAD_DATA | counter.getCounterOCW();
// arch.addTestParams("outb", COMMAND_REGISTER, command, port, @truncate(u8, expected_reload_value), port, @truncate(u8, expected_reload_value >> 8));
expectError(PitError.InvalidFrequency, setupCounter(counter, freq, mode));
// expectEqual(u32(0), ticks);
// expectEqual(expected_freq, current_freq_0);
// expectEqual(expected_freq, getFrequency());
// // These are the hard coded expected values. Calculated externally to check the internal calculation
// expectEqual(u32(838), time_ns);
// expectEqual(u32(95), time_under_1_ns);
// Reset globals
time_ns = 0;
current_freq_0 = 0;
ticks = 0;
}
test "setupCounter normal frequency" {
arch.initTest();
defer arch.freeTest();
const counter = CounterSelect.Counter0;
const port = counter.getRegister();
// Set the frequency to a normal frequency
const freq = 10000;
const expected_reload_value = 119;
const expected_freq: u32 = 10027;
const mode = OCW_MODE_SQUARE_WAVE_GENERATOR | OCW_BINARY_COUNT_BINARY;
const command = mode | OCW_READ_LOAD_DATA | counter.getCounterOCW();
arch.addTestParams("outb", .{ COMMAND_REGISTER, command, port, @truncate(u8, expected_reload_value), port, @truncate(u8, expected_reload_value >> 8) });
setupCounter(counter, freq, mode) catch unreachable;
expectEqual(@as(u32, 0), ticks);
expectEqual(expected_freq, current_freq_0);
expectEqual(expected_freq, getFrequency());
// These are the hard coded expected values. Calculated externally to check the internal calculation
expectEqual(@as(u32, 99730), time_ns);
expectEqual(@as(u32, 727), time_under_1_ns);
// Reset globals
time_ns = 0;
current_freq_0 = 0;
ticks = 0;
}
///
/// Test that waiting a number of ticks and then getting the number of ticks match.
///
fn rt_waitTicks() void {
const waiting = 1000;
const epsilon = 2 * getFrequency() / 10000;
const previous_count = getTicks();
waitTicks(waiting);
const difference = getTicks() - waiting;
if (previous_count + epsilon < difference or previous_count > difference + epsilon) {
panic(@errorReturnTrace(), "FAILURE: Waiting failed. difference: {}, previous_count: {}. Epsilon: {}\n", .{ difference, previous_count, epsilon });
}
std.log.info(.pit, "Tested wait ticks\n", .{});
}
///
/// Test that waiting a number of ticks and then getting the number of ticks match. This version
/// checks for the ticks wrap around.
///
fn rt_waitTicks2() void {
// Set the ticks to 16 less than the max
const waiting = 1000;
const epsilon = 2 * getFrequency() / 10000;
ticks = 0xFFFFFFF0;
const previous_count = getTicks() - 0xFFFFFFF0;
waitTicks(waiting);
// maxInt(u32) - u32(0xFFFFFFF0) = 15
const difference = getTicks() + 15 - waiting;
if (previous_count + epsilon < difference or previous_count > difference + epsilon) {
panic(@errorReturnTrace(), "FAILURE: Waiting failed. difference: {}, previous_count: {}. Epsilon: {}\n", .{ difference, previous_count, epsilon });
}
// Reset ticks
ticks = 0;
std.log.info(.pit, "Tested wait ticks 2\n", .{});
}
///
/// Check that when the PIT is initialised, counter 0 is set up properly.
///
fn rt_initCounter_0() void {
const expected_ns: u32 = 99730;
const expected_ps: u32 = 727;
const expected_hz: u32 = 10027;
if (time_ns != expected_ns or time_under_1_ns != expected_ps or getFrequency() != expected_hz) {
panic(@errorReturnTrace(), "FAILURE: Frequency not set properly. Hz: {}!={}, ns: {}!={}, ps: {}!= {}\n", .{
getFrequency(),
expected_hz,
time_ns,
expected_ns,
time_under_1_ns,
expected_ps,
});
}
var irq_exists = false;
irq.registerIrq(pic.IRQ_PIT, pitHandler) catch |err| switch (err) {
error.IrqExists => {
// We should get this error
irq_exists = true;
},
error.InvalidIrq => {
panic(@errorReturnTrace(), "FAILURE: IRQ for PIT, IRQ number: {} is invalid", .{pic.IRQ_PIT});
},
};
if (!irq_exists) {
panic(@errorReturnTrace(), "FAILURE: IRQ for PIT doesn't exists\n", .{});
}
const expected_mode = OCW_READ_LOAD_DATA | OCW_MODE_SQUARE_WAVE_GENERATOR | OCW_SELECT_COUNTER_0 | OCW_BINARY_COUNT_BINARY;
const actual_mode = readBackCommand(CounterSelect.Counter0);
if (expected_mode != actual_mode) {
panic(@errorReturnTrace(), "FAILURE: Operating mode don't not set properly. Found: {}, expecting: {}\n", .{ actual_mode, expected_mode });
}
std.log.info(.pit, "Tested init\n", .{});
}
///
/// Run all the runtime tests.
///
pub fn runtimeTests() void {
// Interrupts aren't enabled yet, so for the runtime tests, enable it temporary
arch.enableInterrupts();
defer arch.disableInterrupts();
rt_initCounter_0();
rt_waitTicks();
rt_waitTicks2();
}