From 4e259f7b1ef7d00c89690645735d06010ef65992 Mon Sep 17 00:00:00 2001 From: Reto Buerki Date: Thu, 19 Mar 2015 13:17:31 +0100 Subject: [PATCH] hw_x86_64: Implement LAPIC-based kernel timer The LAPIC timer is programmed in one-shot mode with vector 32 (Board::TIMER_VECTOR_KERNEL). The timer frequency is measured using PIT channel 2 as reference (50ms delay). Disable PIT timer channel 0 since BIOS programs it to fire periodically. This avoids potential spurious timer interrupts. --- repos/base-hw/src/core/include/spec/x86/cpu.h | 5 +- .../base-hw/src/core/include/spec/x86/timer.h | 100 +++++++++++++++--- 2 files changed, 89 insertions(+), 16 deletions(-) diff --git a/repos/base-hw/src/core/include/spec/x86/cpu.h b/repos/base-hw/src/core/include/spec/x86/cpu.h index 51fc9ab8d0..1b1bd8ac23 100644 --- a/repos/base-hw/src/core/include/spec/x86/cpu.h +++ b/repos/base-hw/src/core/include/spec/x86/cpu.h @@ -21,6 +21,7 @@ #include #include #include +#include extern int _mt_idt; extern int _mt_tss; @@ -264,8 +265,8 @@ class Genode::Cpu /** * Configure this module appropriately for the first kernel run */ - static void init_phys_kernel() - { } + static void init_phys_kernel() { + Timer::disable_pit(); }; /** * Finish all previous data transfers diff --git a/repos/base-hw/src/core/include/spec/x86/timer.h b/repos/base-hw/src/core/include/spec/x86/timer.h index 67a81cf4eb..31ee05dfb3 100644 --- a/repos/base-hw/src/core/include/spec/x86/timer.h +++ b/repos/base-hw/src/core/include/spec/x86/timer.h @@ -14,47 +14,119 @@ #ifndef _TIMER_H_ #define _TIMER_H_ +#include #include #include - +#include +#include namespace Genode { /** - * Timer driver for core - * - * Timer channel 0 apparently doesn't work on the RPI, so we use channel 1 + * LAPIC-based timer driver for core */ class Timer; } -class Genode::Timer +class Genode::Timer : public Mmio { + private: + + enum { + /* PIT constants */ + PIT_TICK_RATE = 1193182ul, + PIT_SLEEP_MS = 50, + PIT_SLEEP_TICS = (PIT_TICK_RATE / 1000) * PIT_SLEEP_MS, + PIT_CH0_DATA = 0x40, + PIT_CH2_DATA = 0x42, + PIT_CH2_GATE = 0x61, + PIT_MODE = 0x43, + }; + + /* Timer registers */ + struct Tmr_lvt : Register<0x320, 32> + { + struct Vector : Bitfield<0, 8> { }; + struct Delivery : Bitfield<8, 3> { }; + struct Mask : Bitfield<16, 1> { }; + struct Timer_mode : Bitfield<17, 2> { }; + }; + struct Tmr_initial : Register <0x380, 32> { }; + struct Tmr_current : Register <0x390, 32> { }; + + uint32_t _tics_per_ms = 0; + + /* Measure LAPIC timer frequency using PIT channel 2 */ + uint32_t pit_calc_timer_freq(void) + { + uint32_t t_start, t_end; + + /* Set channel gate high and disable speaker */ + outb(PIT_CH2_GATE, (inb(0x61) & ~0x02) | 0x01); + + /* Set timer counter (mode 0, binary count) */ + outb(PIT_MODE, 0xb0); + outb(PIT_CH2_DATA, PIT_SLEEP_TICS & 0xff); + outb(PIT_CH2_DATA, PIT_SLEEP_TICS >> 8); + + write(~0U); + + t_start = read(); + while ((inb(PIT_CH2_GATE) & 0x20) == 0) + { + asm volatile("pause" : : : "memory"); + } + t_end = read(); + + write(0); + + return (t_start - t_end) / PIT_SLEEP_MS; + } + public: - Timer() { } + Timer() : Mmio(Board::MMIO_LAPIC_BASE) + { + /* Enable LAPIC timer in one-shot mode */ + write(Board::TIMER_VECTOR_KERNEL); + write(0); + write(0); + write(0); + + /* Calculate timer frequency */ + _tics_per_ms = pit_calc_timer_freq(); + PINF("LAPIC: timer frequency %u kHz", _tics_per_ms); + } + + /** + * Disable PIT timer channel. This is necessary since BIOS sets up + * channel 0 to fire periodically. + */ + static void disable_pit(void) + { + outb(PIT_MODE, 0x30); + outb(PIT_CH0_DATA, 0); + outb(PIT_CH0_DATA, 0); + } static unsigned interrupt_id(int) { - PDBG("not implemented"); - return 0; + return Board::TIMER_VECTOR_KERNEL; } inline void start_one_shot(uint32_t const tics, unsigned) { - PDBG("not implemented"); + write(tics); } - static uint32_t ms_to_tics(unsigned const ms) + uint32_t ms_to_tics(unsigned const ms) { - PDBG("not implemented"); - return 10000; + return ms * _tics_per_ms; } unsigned value(unsigned) { - PDBG("not implemented"); - return 0; + return read(); } };