Handout
This commit is contained in:
177
kernel/arch/lapic_timer.cc
Normal file
177
kernel/arch/lapic_timer.cc
Normal file
@@ -0,0 +1,177 @@
|
||||
#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<Register>(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<uint32_t>(tmp_ticks);
|
||||
|
||||
// Calculate LAPIC timer divider
|
||||
for (int i = 1; i <= 128; i *= 2) {
|
||||
if (tmp_ticks == static_cast<uint64_t>(tmp32_ticks)) {
|
||||
nticks = tmp32_ticks;
|
||||
div = i;
|
||||
interv = us;
|
||||
return true;
|
||||
}
|
||||
tmp_ticks = tmp_ticks >> 1;
|
||||
tmp32_ticks = static_cast<uint32_t>(tmp_ticks);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t interval() { return interv; }
|
||||
|
||||
void activate() {
|
||||
set(nticks, div, static_cast<uint8_t>(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
|
||||
Reference in New Issue
Block a user