getting cpu bus speed for apic timer

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
bradbobak
Member
Member
Posts: 26
Joined: Fri Apr 14, 2006 11:00 pm

getting cpu bus speed for apic timer

Post by bradbobak »

Hi, Does a value of 218mhz seem right as the outcome of using the pit to time and find the number of ticks elapsed in an apic timer? Seems kind of low to me. Running on real hardware.

Here is my pit timing code:

Code: Select all

// pit trigger speed in hz
#define CYCLE ((uint64_tp)1193182)

static uint32_tp pit_hz = 200;
static uint32_tp pit_divisor = CYCLE / pit_hz;

// count of ticks since start where there are pit_hz ticks per second.
static volatile uint64_tp pit_ticks = 0;

// IRQ handler
void smos_pit_tick(registers_tp *regs_)
{
	++pit_ticks;
}

void pit()
{
	ASM("cli");

	// do note, while this is registered, its the only irq we'll handle
	// (besides PF and GPF)
	smos_register_int_handler(32, smos_pit_tick);

	// chan 0, lo/hibyte rate generator
	smos_outb(0x43, FROM_BINARY(0, 0, 1, 1, 0, 1, 0, 0));
	smos_outb(0x40, pit_divisor & 0xff);
	smos_outb(0x40, (pit_divisor >> 8) & 0xff);

	clear_irq_mask(0);
	ASM("sti");
}

void pit_done()
{
	ASM("cli");

	smos_register_int_handler(32, 0);

	set_irq_mask(0);

	// TODO: actually stop the pit

}
and here is the actual speed finding code

Code: Select all

#define APIC_SPURIOUS 0xf0
#define APIC_LVT_TMR 0x320
#define APIC_TMRDIV 0x3e0
#define APIC_TMRINITCNT 0x380
#define APIC_DISABLE 0x10000
#define APIC_TMRCURRCNT 0x390

#define APIC_SW_ENABLE 0x100

typedef uint8_tp *addr_tp;
void set_dword_at(addr_tp addr_, uint32_tp val_) { *((uint32_tp *)addr_) = val_; }

func()
{
  // where the registers are in regular virtual space (mapped earlier)
  addr_tp apic = (addr_tp)smos.apic_registers_base;

  // set spurious irq.. My int handler just returns if this triggers.
  set_dword_at(apic + APIC_SPURIOUS, 39 + APIC_SW_ENABLE);
  set_dword_at(apic + APIC_LVT_TMR, 32); // timer irq to use (?)
  set_dword_at(apic + APIC_TMRDIV, 0x03); // divide ratio is 16.

  uint32_tp target = pit_hz / 4; // we wait 250ms
  
  pit(); // start the timer

  // start the apic counting down.
  set_dword_at(apic + APIC_TMRINITCNT, 0xffffffff);

  while (pit_ticks < target) ASM("hlt"); // wait til 250ms has elapsed

  pit_done(); // stop pit

  set_dword_at(apic + APIC_LVT_TMR, APIC_DISABLE); // disable apic

  uint32_tp count = get_dword_at(apic + APIC_TMRCURRCNT);

  uint32_tp diff = (0xffffffff - count); // number of ticks that occurred in 250ms

  // * 16 because we used a divisor of 16
  // * 4 because we only waited 250ms.
  uint64_tp cpu_bus_hz = (uint64_tp)diff * 16 * 4;

  kprintf("speed %u mhz\n", cpu_bus_hz / 1024 / 1024); // this outputs approx 218
}
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re: getting cpu bus speed for apic timer

Post by Brendan »

Hi,

For a start, MHz is not MiHz, and you'd want:

Code: Select all

  kprintf("speed %u mhz\n", cpu_bus_hz / 1000 / 1000);
If the original code says 218 MiHz, then cpu_bus_hz must be between 228589568 and 229638143, so (with "round to nearest") it'd actually be 229 or 230 MHz.

Also note that the PIT count would need to be 5965.91 to get exactly 200 Hz (which isn't possible), and you'd be using a count of 5965 (and not the closer count of 5966), which means that your PIT would actually be running at 200.03 Hz. This would cause cpu_bus_hz to be slightly lower than it should be.

Apart from that...

For an old 166 MHz Pentium, 230 MHz is far too fast. ;)


Cheers,

Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
bradbobak
Member
Member
Posts: 26
Joined: Fri Apr 14, 2006 11:00 pm

Re: getting cpu bus speed for apic timer

Post by bradbobak »

Thanks for the help Brendan. Maybe I'll re-visit for accuracy later, but for now the timer is just used for task switching / short delays so it can be off a little bit.
bradbobak
Member
Member
Posts: 26
Joined: Fri Apr 14, 2006 11:00 pm

Re: getting cpu bus speed for apic timer

Post by bradbobak »

Just to know, is there a recommended divisor for the pit for this apic timer stuff?
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re: getting cpu bus speed for apic timer

Post by Brendan »

Hi,
bradbobak wrote:Just to know, is there a recommended divisor for the pit for this apic timer stuff?
The PIT divisor doesn't matter too much. For example, you could set the PIT 1000 Hz and use it to measure 200 ms, or you could set the PIT to 20 Hz and use it to measure 200 ms.

What matters is the length of time you're measuring; where longer is more accurate and more annoying (nobody wants the OS to boot 5 seconds longer because you're spending 5 seconds waiting for local APIC timer calibration).

To help fix that; you can setup the PIT and the local APIC timer (including both of their interrupt handlers); and then do something else that needs to be done while you're waiting. For example, maybe your boot code sets up the timers, then decompresses an "initial RAM disk". The timer IRQs will interrupt the decompression code, and if calibration finishes while you're decompressing you can set a "calibration completed" flag in the IRQ handler. After the decompression finishes you'd do a "while( calibration completed flag is not set yet ) { HLT; }" loop in case the decompression happened too fast.

You can also do an initial calibration but keep the timers running, and replace the initial calibration results with slightly more accurate later results if there's time to do that.

The other thing that matters is the local APIC timer's divisor. For example; if you set the divisor to 16 and get count=10 after the delay, then that could be anything from "160/16" to "175/16", so you've got up to 10% error caused by rounding. To get maximum accuracy you can set the local APIC timer's divisor to 1, and keep track of the number of times it has rolled over in the local APIC timer's IRQ handler. Of course the local APIC timer's counter is 32-bit, so even with a 4 GHz bus clock it'd take 1 second to roll over.

Basically; the local APIC timer's IRQ handler might just increment a "number of times local APIC count rolled over" variable (and the local APIC timer's divisor would be set to 1 for better precision); and the PIT IRQ handler might look like this:

Code: Select all

PIT_IRQ:
      PIT_ticks++;
      if(PIT_ticks < MIN_PIT_TICKS) return;      // Calibration hasn't gone long enough

      temp_LAPIC_timer_count = (local_APIC_timer_roll_over_counter << 32) + get_local_APIC_count();
      LAPIC_bus_speed = (temp_LAPIC_timer_count * PIT_divisor * 3) / (3579545 * PIT_ticks);
      calibration_completed = true;
  • Note 1: The "temp_LAPIC_timer_count" calculation is racey because the local APIC timer count may roll over before or after you get the local APIC timer count. How you deal with that depends on whether or not you allow interrupts to nest.

    Note 2: 3579545/3 = 1193181.6666667, which is the true frequency of the PIT's main clock.

    Note 3: For the "local_APIC_bus_speed" calculation; all multiplications are done first and there is only one division. This minimises rounding errors.

    Note 4: The "local_APIC_bus_speed" calculation is going to need special care to ensure that "(temp_LAPIC_timer_count * PIT_divisor * 3)" doesn't overflow, and that "(3579545 * PIT_ticks)" doesn't overflow. I'd make sure that PIT_ticks is less than 1199 (so that 3579545 * PIT_ticks always fits in 32 bits), then I'd use a 96-bit value for "temp_LAPIC_timer_count * PIT_divisor * 3". This means that the division is dividing a 96-bit value by a 32-bit value, which is trivial to do in assembly (and insanely painful in C).

    Note 5: If the PIT is set to 1000 Hz and MIN_PIT_TICKS is 100; then after 100 ms the "calibration_completed" flag would be set for the first time; then it'd be recalculated every 1 ms after that until/unless something else stops it.

    Note 6: In C, several of those variables will need to be volatile.


The general idea would be to do something like:

Code: Select all

    setup_LAPIC_timer_calibration();

    do_something_else();
    do_other_things();
    do_more_other_things();

    while( calibration_completed != true) {
        HLT;
    }
    stop_both_timers_and_remove_both_IRQ_handlers();

Cheers,

Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
bradbobak
Member
Member
Posts: 26
Joined: Fri Apr 14, 2006 11:00 pm

Re: getting cpu bus speed for apic timer

Post by bradbobak »

Great explanation Brendan, thank you. Especially the idea to do other things while calibrating.
User avatar
Combuster
Member
Member
Posts: 9301
Joined: Wed Oct 18, 2006 3:45 am
Libera.chat IRC: [com]buster
Location: On the balcony, where I can actually keep 1½m distance
Contact:

Re: getting cpu bus speed for apic timer

Post by Combuster »

You can also kick of both timers, read out the PIT remaining count versus the APIC remaining count after a while and save yourself all sorts of interrupt and rollover logic in the first place (and any jitters and issues that follow from that). It lacks the possible accuracy of long-time measurements, but it's relatively simple to do. You may want to test if ComputeApicFrequency provides a better reference result for you.
"Certainly avoid yourself. He is a newbie and might not realize it. You'll hate his code deeply a few years down the road." - Sortie
[ My OS ] [ VDisk/SFS ]
Post Reply