Page 1 of 1

PIT appears to run a lot slower at higher frequencies

Posted: Sat Jul 13, 2024 12:45 pm
by mediocrevegetable1
I have configured IRQ0 to increase a global timer every time it is called with PIT mode 2. I have tried to make it such that, irrespective of what frequency I've chosen, the timer should remain accurate. I have done this by calculating the interval between IRQ calls and incrementing the timer by that interval every calll, similar to the code provided in the wiki page. When I choose very low frequencies, it seems to work just fine, but at higher frequencies, it takes several seconds for the global timer to increase by one second. The inaccuracy seems to increase with the frequency. I've tried to search for this issue online, but I have not yet found someone with a problem similar to mine. If any further information needs to be provided, I will gladly amend it to the post.

The PIT code is as follows:

Code: Select all

#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MAX(a, b) ((a) > (b) ? (a) : (b))

/*
 * CLOSEST(x, a, b) determines whether x is closer to a or b.
 * If x is closer to b then CLOSEST(x, a, b) > 0.
 * If x is closer to a then CLOSEST(x, a, b) < 0.
 * If x is equally close to both a and b then CLOSEST(x, a, b) = 0.
 */
#define CLOSEST(x, low, high)                                                  \
    ((x) - (low) > (high) - (x) ? 1 : (x) - (low) < (high) - (x) ? -1 : 0)
#define RELOAD(frequency) (PIT_MAX_FREQUENCY / (frequency))
#define FREQ(reload)      (PIT_MAX_FREQUENCY / (reload))

static struct timespec irq_interval = {0};
static struct timespec pit_timer    = {0};
static uint32_t        pit_frequency;
static uint16_t        pit_reload;

static void irq0_handler(unsigned irq_line);

void pit_init(void) { irq_set_handler(0, irq0_handler); }

void pit_set_frequency(uint32_t frequency) {
    frequency = MIN(MAX(frequency, PIT_MIN_FREQUENCY), PIT_MAX_FREQUENCY);

    pit_reload = MIN(RELOAD(frequency), UINT16_MAX);
    if (CLOSEST(frequency, FREQ(pit_reload), FREQ(pit_reload + 1)) > 0)
        ++pit_reload;

    pit_frequency        = FREQ(pit_reload);
    irq_interval.tv_nsec = 1000000000 / pit_frequency;

    __asm__("cli");
    port_out8(PORT_PIT_CMD, pit_cmd(0, PIT_ACCESS_FULL, 2));
    port_out8(PORT_PIT_CH0_DATA, (pit_reload >> 0) & 0xFF);
    port_out8(PORT_PIT_CH0_DATA, (pit_reload >> 8) & 0xFF);
    __asm__("sti");
}

const struct timespec *pit_get_global_timer(void) { return &pit_timer; }

static void irq0_handler(unsigned irq_line) {
    bext_timespec_add(&pit_timer, &irq_interval);
    pic_send_eoi(irq_line);
}
The main code is as follows:

Code: Select all

void kernel_main(void) {
    putchar('\f');
    gdt_init();
    idt_init();
    pic_init();
    pit_init();
    pit_set_frequency(10000);

    for (;;) {
        volatile const struct timespec *timer = pit_get_global_timer();
        bext_printf_basic(
            "%us %uns\r", (unsigned)timer->tv_sec, (unsigned)timer->tv_nsec
        );
    }
}

Re: PIT appears to run a lot slower at higher frequencies

Posted: Sat Jul 13, 2024 8:43 pm
by Octocontrabass
mediocrevegetable1 wrote: Sat Jul 13, 2024 12:45 pmWhen I choose very low frequencies, it seems to work just fine, but at higher frequencies, it takes several seconds for the global timer to increase by one second.
Bare metal or virtual machine? Timing is difficult for virtual machines, especially if you're running the timer at an unusually high frequency.

Can you share the values your code calculates for the interval and counter reload? Some small rounding error might be compounding into a loss of accuracy at higher rates. (I'd calculate it myself but you didn't provide enough of your code...)
mediocrevegetable1 wrote: Sat Jul 13, 2024 12:45 pm

Code: Select all

    pit_set_frequency(10000);
That's an unusually high frequency.

Re: PIT appears to run a lot slower at higher frequencies

Posted: Sun Jul 14, 2024 3:28 am
by mediocrevegetable1
Octocontrabass wrote: Sat Jul 13, 2024 8:43 pm Bare metal or virtual machine? Timing is difficult for virtual machines, especially if you're running the timer at an unusually high frequency.
I am indeed using QEMU on Windows to run the kernel. Is this a known problem?

That being said, I just checked and, on low frequencies, the timer seems to run a bit too fast. From basic trial and error, it seems to run best at about 500Hz. This at least seems like it is an error on my end.

Below are the relevant values for a few frequencies:

Code: Select all

Desired frequency: 18
True frequency: 18
Reload value: 65535
IRQ interval: 55555555ns

Desired frequency: 100
True frequency: 100
Reload value: 11931
IRQ interval: 10000000ns

Desired frequency: 500
True frequency: 500
Reload value: 2386
IRQ interval: 20000000ns

Desired frequency: 500
True frequency: 500
Reload value: 2386
IRQ interval: 20000000ns

Desired frequency: 1000
True frequency: 1000
Reload value: 1193
IRQ interval: 1000000ns

Desired frequency: 10000
True frequency: 9943
Reload value: 120
IRQ interval: 100573ns
What more could I provide to help you calculate these values and anything else necessary? PIT_MIN_FREQUENCY and PIT_MAX_FREQUENCY are 18 and 1193182 respectively. I think everything else relevant to pit_set_frequency specifically has been provided? Please let me know what else I can do or provide to help. Thank you for your help.

Re: PIT appears to run a lot slower at higher frequencies

Posted: Sun Jul 14, 2024 7:46 am
by nullplan
Once you get below the millisecond range, round-off error actually becomes significant. The PIT frequency is 1,193,182 Hz, you are right about that. Now a reload value of 120 in periodic mode will get you one interrupt every 100.6 µs. That is exactly what you are seeing. With a reload value of 119, it would be one interrupt every 99.7µs. These are the choices you have. You cannot get an interrupt every 100 µs.

Technically, the same is true for the 1ms period time. I'm guessing QEMU optimizes those cases, since most uses of the PIT are going to be for such frequencies, and also operating systems have efficient ways of waiting for milliseconds (so QEMU can just use those). It is possible real hardware would act differently. It is also possible it wouldn't, since the PIT being inaccurate is sort of expected at this point.

Re: PIT appears to run a lot slower at higher frequencies

Posted: Sun Jul 14, 2024 4:20 pm
by Octocontrabass
mediocrevegetable1 wrote: Sun Jul 14, 2024 3:28 amI am indeed using QEMU on Windows to run the kernel. Is this a known problem?
It's a common issue with virtual machines in general. Even if the host OS provides an accurate time source - which it may not - the VM still needs to interrupt the emulated CPU for every timer tick, and that adds a lot of overhead. Timing is much more accurate with hardware-assisted virtualization, but I don't think any VM has hardware acceleration for the legacy PIT.
mediocrevegetable1 wrote: Sun Jul 14, 2024 3:28 amDesired frequency: 18
True frequency: 18
Reload value: 65535
IRQ interval: 55555555ns
The true frequency is 18.2067875 Hz and the interval is 54924571.4 ns. (I'm pretending the PIT has infinite precision, but in reality it's probably around seven significant figures.)
mediocrevegetable1 wrote: Sun Jul 14, 2024 3:28 amDesired frequency: 100
True frequency: 100
Reload value: 11931
IRQ interval: 10000000ns
The true frequency is 100.006858 Hz and the interval is 9999314.29 ns.
mediocrevegetable1 wrote: Sun Jul 14, 2024 3:28 amDesired frequency: 500
True frequency: 500
Reload value: 2386
IRQ interval: 20000000ns
The true frequency is 500.076202 Hz and the interval is 1999695.24 ns.
mediocrevegetable1 wrote: Sun Jul 14, 2024 3:28 amDesired frequency: 1000
True frequency: 1000
Reload value: 1193
IRQ interval: 1000000ns
The true frequency is 1000.1524 Hz and the interval is 999847.619 ns.
mediocrevegetable1 wrote: Sun Jul 14, 2024 3:28 amDesired frequency: 10000
True frequency: 9943
Reload value: 120
IRQ interval: 100573ns
The true frequency is 9943.18182 Hz and the interval is 100571.429 ns.

Your calculations have some rounding error, but not enough to be easily noticeable.