Skip to content

Programmable Interval Timer

The Programmable Interval Timer aka the PIC is a device on most x86_64 machines that you can tell to trigger and interrupt at a well programmable interval

The following code will allow you to set it up at a specified tick rate and wait a number of milliseconds to use the PIT for most tasks you would need
Make sure to make the proper C header files for these to work together and for other code to be able to use it.

static inline void io_wait() {
    outb(0x80, 0);
}

int pitFrequency;
void pit_sleep_ms(unsigned int ms) {
    int startTicks = pitInteruptsTriggered;
    int targetTicks = startTicks + (ms * pitFrequency) / 1000;

    while (pitInteruptsTriggered < targetTicks) {
        int currentTicks = pitInteruptsTriggered;

        if (currentTicks < startTicks) {
            startTicks = currentTicks;
            targetTicks = startTicks + (ms * pitFrequency) / 1000;
        }

        asm volatile("hlt");
    }
}

void setup_pit(unsigned int frequency) {
    __asm__ __volatile__("cli");
    pitFrequency = frequency;
    unsigned int divisor = PIT_FREQUENCY / frequency;
    outb(PIT_CONTROL_PORT, 0x36 | 0x02);
    io_wait();
    outb(PIT_CHANNEL0_PORT, divisor & 0xFF);
    io_wait();
    outb(PIT_CHANNEL0_PORT, divisor >> 8);
    io_wait();

    unmask_irq(0);
    __asm__ __volatile__("sti");
}

For the interrupt you might want something like this assigned to IRQ 0 / INT 0x20

volatile int pitInteruptsTriggered = 0;

__attribute__((interrupt))
void pit_isr(__attribute__((unused)) void* frame) {
    pitInteruptsTriggered++;
    outb(0x20,0x20);
}

You should then be able to call setup_pit() as setup_pit(1000); and every ms you will get an interrupt.
And then you can call pit_sleep_ms() as pit_sleep_ms(100); and it will sleep for ~100 MS