#include "../debug/assert.h" #include "core_interrupt.h" #include "lapic.h" #include "lapic_registers.h" #include "pit.h" namespace LAPIC { namespace Timer { /*! \brief Timer Delivery Status */ enum class DeliveryStatus : uint8_t { IDLE = 0, SEND_PENDING = 1 }; /*! \brief Timer Mode */ enum class TimerMode : uint8_t { ONE_SHOT = 0, PERIODIC = 1, DEADLINE = 2 // reserved }; /*! \brief Timer Mask */ enum class Mask : uint8_t { NOT_MASKED = 0, MASKED = 1 }; static const Register INVALID_DIV = 0xff; /*! \brief LAPIC-Timer Control Register * * \see [ISDMv3 10.5.1 Local Vector Table](intel_manual_vol3.pdf#page=375) */ union ControlRegister { struct { uint32_t vector : 8; ///< Vector uint32_t : 4; DeliveryStatus delivery_status : 1; ///< Delivery Status (readonly) uint32_t : 3; Mask masked : 1; ///< Interrupt Mask (if set, interrupt will not ///< trigger) TimerMode timer_mode : 2; ///< Timer Mode uint32_t : 13; }; Register value; } __attribute__((packed)); /*! \brief LAPIC timer divider table * * \see [ISDMv3 10.5.4 APIC Timer](intel_manual_vol3.pdf#page=378) */ static const Register div_masks[] = { 0xb, ///< divides by 1 0x0, ///< divides by 2 0x1, ///< divides by 4 0x2, ///< divides by 8 0x3, ///< divides by 16 0x8, ///< divides by 32 0x9, ///< divides by 64 0xa ///< divides by 128 }; /*! \brief Calculate the bit mask for the LAPIC-timer divider. * \param div Divider, must be power of two: 1, 2, 4, 8, 16, 32, 64, 128 * \return Bit mask for LAPIC::setTimer() or `0xff` if `div` is invalid. */ Register getClockDiv(uint8_t div) { // div is zero or not a power of two? if (div == 0 || (div & (div - 1)) != 0) { return INVALID_DIV; } int trail = __builtin_ctz(div); // count trailing 0-bits if (trail > 7) { return INVALID_DIV; } return div_masks[trail]; } uint32_t measureTickRate(void) { uint32_t ticks = 0; // ticks per millisecond // Calculation (Assignment 5) // Steps taken for precise measurement of LAPIC-timer ticks per ms: // 1. Disable Interrupts to ensure measurement is not disturbed // 2. Configure a timeout of 50 ms (nearly PIT's maximum possible delay) // Using a "large" value decreases the overhead induced by measurement // and thereby increases the accuracy. // 3. Now measure the number of passed LAPIC-timer ticks while waiting for // the PIT // Note that configuring the PIT takes quite some time and therefore // should be done prior to starting LAPIC-timer measurement. // 4. Restore previous state (disable PIT, LAPIC timer, restore interrupts) // 5. Derive the ticks per millisecond (take care, the counter is counting // towards zero) // Start LAPIC counter, single shot, no interrupts set(UINT32_MAX, 1, 0, false, true); bool status = Core::Interrupt::disable(); const uint32_t ms = 50; PIT::set(ms * 1000); Register start = read(TIMER_CURRENT_COUNTER); PIT::waitForTimeout(); Register end = read(TIMER_CURRENT_COUNTER); set(0, 1, 0, false, true); PIT::disable(); Core::Interrupt::restore(status); ticks = (start - end) / ms; return ticks; } void set(uint32_t counter, uint8_t divide, uint8_t vector, bool periodic, bool masked) { // stop timer by setting counter to 0 write(TIMER_INITIAL_COUNTER, 0); // write control register ControlRegister tcr; tcr.value = read(TIMER_CONTROL); tcr.vector = vector; tcr.timer_mode = periodic ? TimerMode::PERIODIC : TimerMode::ONE_SHOT; tcr.masked = masked ? Mask::MASKED : Mask::NOT_MASKED; write(TIMER_CONTROL, tcr.value); // set divider Register clock_div = getClockDiv(divide); assert(clock_div != INVALID_DIV); write(TIMER_DIVIDE_CONFIGURATION, clock_div); // start timer write(TIMER_INITIAL_COUNTER, static_cast(counter)); } static uint32_t interv; static uint32_t nticks; static uint32_t div; bool setup(uint32_t us) { if (us == 0) { return false; } uint64_t ticks_ms = measureTickRate(); uint64_t tmp_ticks = (ticks_ms * us) / 1000; uint32_t tmp32_ticks = static_cast(tmp_ticks); // Calculate LAPIC timer divider for (int i = 1; i <= 128; i *= 2) { if (tmp_ticks == static_cast(tmp32_ticks)) { nticks = tmp32_ticks; div = i; interv = us; return true; } tmp_ticks = tmp_ticks >> 1; tmp32_ticks = static_cast(tmp_ticks); } return false; } uint32_t interval() { return interv; } void activate() { set(nticks, div, static_cast(Core::Interrupt::Vector::TIMER), true); } void setMasked(bool masked) { ControlRegister tcr; tcr.value = read(TIMER_CONTROL); tcr.masked = masked ? Mask::MASKED : Mask::NOT_MASKED; write(TIMER_CONTROL, tcr.value); } } // namespace Timer } // namespace LAPIC