You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
180 lines
4.9 KiB
C++
180 lines
4.9 KiB
C++
#include "lapic.h"
|
|
#include "lapic_registers.h"
|
|
#include "core.h"
|
|
#include "pit.h"
|
|
|
|
#include "../debug/output.h"
|
|
|
|
namespace LAPIC {
|
|
namespace Timer {
|
|
|
|
/*! \brief Timer Delivery Status */
|
|
enum DeliveryStatus { IDLE = 0, SEND_PENDING = 1 };
|
|
|
|
/*! \brief Timer Mode */
|
|
enum TimerMode {
|
|
ONE_SHOT = 0,
|
|
PERIODIC = 1,
|
|
DEADLINE = 2
|
|
// reserved
|
|
};
|
|
|
|
/*! \brief Timer Mask */
|
|
enum Mask { 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.
|
|
*/
|
|
static Register getClockDiv(uint8_t div) {
|
|
switch (div) {
|
|
case 1: return div_masks[0];
|
|
case 2: return div_masks[1];
|
|
case 4: return div_masks[2];
|
|
case 8: return div_masks[3];
|
|
case 16: return div_masks[4];
|
|
case 32: return div_masks[5];
|
|
case 64: return div_masks[6];
|
|
case 128: return div_masks[7];
|
|
default : return 0xff;
|
|
}
|
|
}
|
|
/*! \brief Calculate the LAPIC-timer divider for the bit mask.
|
|
* \param div_mask The bit mask, must be one of: 0xb, 0x0, 0x1, 0x2, 0x3, 0x8, 0x9, 0xa
|
|
* \return LAPIC-timer divider or `0xff` if `div_mask` is invalid.
|
|
*/
|
|
static uint8_t fromClockDiv(Register div_mask) {
|
|
if (div_mask == div_masks[0]) return 1;
|
|
if (div_mask == div_masks[1]) return 2;
|
|
if (div_mask == div_masks[2]) return 4;
|
|
if (div_mask == div_masks[3]) return 8;
|
|
if (div_mask == div_masks[4]) return 16;
|
|
if (div_mask == div_masks[5]) return 32;
|
|
if (div_mask == div_masks[6]) return 64;
|
|
if (div_mask == div_masks[7]) return 128;
|
|
return 0xff;
|
|
}
|
|
|
|
|
|
uint32_t ticks(void) {
|
|
static uint32_t ticks = 0; // ticks per millisecond
|
|
if (ticks != 0) return ticks;
|
|
|
|
// Prepare Counter
|
|
LAPIC::Timer::set(0, 1, 0, false, true);
|
|
|
|
// Set timer for 10ms
|
|
PIT::set(10 * 1000);
|
|
// Start LAPIC-Timer
|
|
LAPIC::write(TIMER_INITIAL_COUNTER, UINT32_MAX);
|
|
PIT::waitForTimeout();
|
|
// Get final Count
|
|
uint32_t counter = LAPIC::read(TIMER_CURRENT_COUNTER);
|
|
// Disable LAPIC-Timer
|
|
LAPIC::write(TIMER_INITIAL_COUNTER, 0);
|
|
// Calculate tick count.
|
|
ticks = (UINT32_MAX - counter) / 10;
|
|
|
|
DBG << "LAPIC-Timer calibration using PIT" << endl;
|
|
return ticks;
|
|
}
|
|
|
|
void set(uint32_t counter, uint8_t divide, uint8_t vector, bool periodic,
|
|
bool masked) {
|
|
if (divide == 0 || (divide & (divide - 1)) != 0) {
|
|
// Not a power of 2
|
|
return;
|
|
}
|
|
|
|
ControlRegister tcr;
|
|
tcr.vector = Core::Interrupt::Vector::TIMER;
|
|
tcr.timer_mode = periodic ? PERIODIC : ONE_SHOT;
|
|
tcr.masked = masked ? MASKED : NOT_MASKED;
|
|
LAPIC::write(TIMER_CONTROL, tcr.value);
|
|
|
|
LAPIC::write(TIMER_DIVIDE_CONFIGURATION, getClockDiv(divide));
|
|
LAPIC::write(TIMER_INITIAL_COUNTER, counter);
|
|
}
|
|
uint64_t time_tick;
|
|
|
|
uint8_t dividender;
|
|
|
|
bool setup(uint32_t us) {
|
|
time_tick = (static_cast<uint64_t>(ticks()) * us) / 1000ULL;
|
|
|
|
dividender = 1;
|
|
while (time_tick > UINT32_MAX) {
|
|
// While timer_ticks is to large to fit in 32bits.
|
|
time_tick >>= 1;
|
|
dividender <<= 1;
|
|
}
|
|
if (dividender > 128)
|
|
return false; // Timer interval is to large.
|
|
|
|
// Setup Masked interrupts to effectively disable the timer.
|
|
return true;
|
|
}
|
|
|
|
uint32_t interval() {
|
|
uint32_t timer_ticks = LAPIC::read(TIMER_INITIAL_COUNTER);
|
|
uint32_t divisor = fromClockDiv(LAPIC::read(TIMER_DIVIDE_CONFIGURATION));
|
|
|
|
uint64_t us = (static_cast<uint64_t>(timer_ticks) * divisor * 1000ULL) / static_cast<uint64_t>(ticks());
|
|
return static_cast<uint32_t>(us);
|
|
}
|
|
|
|
void activate() {
|
|
// Disable Counter to avoid spouriose interrupts.
|
|
LAPIC::write(TIMER_INITIAL_COUNTER, 0);
|
|
set(static_cast<uint32_t>(time_tick), dividender, Core::Interrupt::TIMER, true, true);
|
|
|
|
// Activate Timer interrupts.
|
|
setMasked(false);
|
|
|
|
// enable counter with correct value.
|
|
LAPIC::write(TIMER_INITIAL_COUNTER, time_tick);
|
|
}
|
|
|
|
void setMasked(bool masked) {
|
|
ControlRegister tcr;
|
|
tcr.value = LAPIC::read(TIMER_CONTROL);
|
|
tcr.masked = masked ? MASKED : NOT_MASKED;
|
|
LAPIC::write(TIMER_CONTROL, tcr.value);
|
|
}
|
|
} // namespace Timer
|
|
} // namespace LAPIC
|