[kernel] Add HPET support, create clock class

Create a clock class which can be queried for current timestamp in
nanoseconds. Also implements a simple HPET class as one possible clock
source.

Tags: time
This commit is contained in:
Justin C. Miller
2020-06-28 17:47:46 -07:00
parent 9b67f87062
commit 6c468a134b
13 changed files with 347 additions and 36 deletions

View File

@@ -10,3 +10,5 @@ LOG(boot, debug);
LOG(syscall,debug);
LOG(vmem, debug);
LOG(objs, debug);
LOG(timer, debug);
LOG(clock, debug);

View File

@@ -188,3 +188,13 @@ struct acpi_mcfg
acpi_mcfg_entry entries[0];
} __attribute__ ((packed));
struct acpi_hpet
{
TABLE_HEADER('HPET');
uint32_t hardware_id;
acpi_gas base_address;
uint8_t index;
uint16_t periodic_min;
uint8_t attributes;
} __attribute__ ((packed));

View File

@@ -1,5 +1,6 @@
#include "kutil/assert.h"
#include "apic.h"
#include "clock.h"
#include "interrupts.h"
#include "io.h"
#include "log.h"
@@ -67,36 +68,20 @@ lapic::calibrate_timer()
log::info(logs::apic, "Calibrating APIC timer...");
// Set up PIT sleep
uint8_t command = 0x30; // channel 0, loybyte/highbyte, mode 0
outb(0x43, command);
const uint32_t initial = -1u;
enable_timer(isr::isrSpurious);
set_divisor(1);
apic_write(m_base, lapic_timer_init, initial);
const int iterations = 5;
for (int i=0; i<iterations; ++i) {
const uint16_t pit_33ms = 39375;
uint16_t pit_count = pit_33ms;
outb(0x40, pit_count & 0xff);
io_wait();
outb(0x40, (pit_count >> 8) & 0xff);
while (pit_count <= pit_33ms) {
outb(0x43, 0); // latch counter values
pit_count =
static_cast<uint16_t>(inb(0x40)) |
static_cast<uint16_t>(inb(0x40)) << 8;
}
}
uint64_t us = 200000;
uint64_t ns = us * 1000;
clock::get().spinwait(ns);
uint32_t remaining = apic_read(m_base, lapic_timer_cur);
uint32_t ticks_total = initial - remaining;
m_ticks_per_us = ticks_total / (iterations * 33000);
log::info(logs::apic, "APIC timer ticks %d times per nanosecond.", m_ticks_per_us);
m_ticks_per_us = ticks_total / us;
log::info(logs::apic, "APIC timer ticks %d times per microsecond.", m_ticks_per_us);
interrupts_enable();
}
@@ -123,14 +108,6 @@ lapic::set_divisor(uint8_t divisor)
m_divisor = divisor;
}
uint32_t
lapic::enable_timer_internal(isr vector, uint8_t divisor, uint32_t count, bool repeat)
{
reset_timer(count);
return count;
}
void
lapic::enable_timer(isr vector, bool repeat)
{
@@ -224,6 +201,9 @@ ioapic::ioapic(uint32_t *base, uint32_t base_gsi) :
void
ioapic::redirect(uint8_t irq, isr vector, uint16_t flags, bool masked)
{
log::debug(logs::apic, "IOAPIC %d redirecting irq %3d to vector %3d [%04x]%s",
m_id, irq, vector, flags, masked ? " (masked)" : "");
uint64_t entry = static_cast<uint64_t>(vector);
uint16_t polarity = flags & 0x3;
@@ -244,6 +224,9 @@ ioapic::redirect(uint8_t irq, isr vector, uint16_t flags, bool masked)
void
ioapic::mask(uint8_t irq, bool masked)
{
log::debug(logs::apic, "IOAPIC %d %smasking irq %3d",
m_id, masked ? "" : "un", irq);
uint32_t entry = ioapic_read(m_base, (2 * irq) + 0x10);
if (masked)
entry |= (1 << 16);

View File

@@ -6,7 +6,6 @@
enum class isr : uint8_t;
/// Base class for other APIC types
class apic
{
@@ -54,7 +53,7 @@ public:
void enable(); ///< Enable servicing of interrupts
void disable(); ///< Disable (temporarily) servicing of interrupts
/// Calibrate the timer speed against the PIT
/// Calibrate the timer speed against the clock
void calibrate_timer();
private:
@@ -68,7 +67,6 @@ private:
void set_divisor(uint8_t divisor);
void set_repeat(bool repeat);
uint32_t enable_timer_internal(isr vector, uint8_t divisor, uint32_t count, bool repeat);
uint32_t m_divisor;
uint32_t m_ticks_per_us;

22
src/kernel/clock.cpp Normal file
View File

@@ -0,0 +1,22 @@
#include "clock.h"
clock * clock::s_instance = nullptr;
clock::clock(uint64_t rate, clock::source source_func, void *data) :
m_rate(rate),
m_data(data),
m_source(source_func)
{
// TODO: make this atomic
if (s_instance == nullptr)
s_instance = this;
update();
}
void
clock::spinwait(uint64_t ns) const
{
uint64_t when = m_source(m_data) + ns;
while (value() < when);
}

44
src/kernel/clock.h Normal file
View File

@@ -0,0 +1,44 @@
#pragma once
/// \file clock.h
/// The kernel time keeping interface
#include <stdint.h>
class clock
{
public:
/// A source is a function that returns the current
/// value of some clock source.
using source = uint64_t (*)(void*);
/// Constructor.
/// \arg rate Number of source ticks per ns
/// \arg source Function for the clock source
/// \arg data Data to pass to the source function
clock(uint64_t rate, source source_func, void *data);
/// Get the current value of the clock.
/// \returns Current value of the source, in ns
/// TODO: optimize divison by finding a multiply and
/// shift value instead
inline uint64_t value() const { return m_source(m_data) / m_rate; }
/// Update the internal state via the source
/// \returns Current value of the clock
inline void update() { m_current = value(); return m_current; }
/// Wait in a tight loop
/// \arg interval Time to wait, in ns
void spinwait(uint64_t ns) const;
/// Get the master clock
static clock & get() { return *s_instance; }
private:
uint64_t m_current; ///< current ns count
uint64_t m_rate; ///< source ticks per ns
void *m_data;
source m_source;
static clock *s_instance;
};

View File

@@ -5,6 +5,7 @@
#include "kutil/memory.h"
#include "acpi_tables.h"
#include "apic.h"
#include "clock.h"
#include "console.h"
#include "device_manager.h"
#include "interrupts.h"
@@ -135,6 +136,10 @@ device_manager::load_xsdt(const acpi_xsdt *xsdt)
load_mcfg(reinterpret_cast<const acpi_mcfg *>(header));
break;
case acpi_hpet::type_id:
load_hpet(reinterpret_cast<const acpi_hpet *>(header));
break;
default:
break;
}
@@ -268,6 +273,27 @@ device_manager::load_mcfg(const acpi_mcfg *mcfg)
probe_pci();
}
void
device_manager::load_hpet(const acpi_hpet *hpet)
{
log::debug(logs::device, " Found HPET device #%3d: base %016lx pmin %d attr %02x",
hpet->index, hpet->base_address.address, hpet->periodic_min, hpet->attributes);
uint32_t hwid = hpet->hardware_id;
uint8_t rev_id = hwid & 0xff;
uint8_t comparators = (hwid >> 8) & 0x1f;
uint8_t count_size_cap = (hwid >> 13) & 1;
uint8_t legacy_replacement = (hwid >> 15) & 1;
uint32_t pci_vendor_id = (hwid >> 16);
log::debug(logs::device, " rev:%02d comparators:%02d count_size_cap:%1d legacy_repl:%1d",
rev_id, comparators, count_size_cap, legacy_replacement);
log::debug(logs::device, " pci vendor id: %04x", pci_vendor_id);
m_hpets.emplace(hpet->index,
reinterpret_cast<uint64_t*>(hpet->base_address.address + ::memory::page_offset));
}
void
device_manager::probe_pci()
{
@@ -308,6 +334,30 @@ device_manager::init_drivers()
ahcid.register_device(&device);
}
*/
if (m_hpets.count() > 0) {
hpet &h = m_hpets[0];
h.enable();
// becomes the singleton
clock *master_clock = new clock(h.rate(), hpet_clock_source, &h);
kassert(master_clock, "Failed to allocate master clock");
log::info(logs::clock, "Created master clock using HPET 0: Rate %d", h.rate());
} else {
//TODO: APIC clock?
}
}
bool
device_manager::install_irq(unsigned irq, const char *name, irq_callback cb, void *data)
{
if (irq >= m_irqs.count())
m_irqs.set_size(irq+1);
if (m_irqs[irq].callback != nullptr)
return false;
m_irqs[irq] = {name, cb, data};
return true;
}
bool

View File

@@ -3,11 +3,13 @@
/// The device manager definition
#include "kutil/vector.h"
#include "apic.h"
#include "hpet.h"
#include "pci.h"
struct acpi_xsdt;
struct acpi_apic;
struct acpi_mcfg;
struct acpi_hpet;
class block_device;
using irq_callback = void (*)(void *);
@@ -41,6 +43,18 @@ public:
/// Intialize drivers for the current device list.
void init_drivers();
/// Install an IRQ callback for a device
/// \arg irq IRQ to install the handler for
/// \arg name Name of the interrupt, for display to user
/// \arg cb Callback to call when the interrupt is received
/// \arg data Data to pass to the callback
/// \returns True if an IRQ was installed successfully
bool install_irq(
unsigned irq,
const char *name,
irq_callback cb,
void *data);
/// Allocate an MSI IRQ for a device
/// \arg name Name of the interrupt, for display to user
/// \arg device Device this MSI is being allocated for
@@ -85,6 +99,15 @@ public:
m_blockdevs[i] : nullptr;
}
/// Get an HPET device
/// \arg i Index of the device to get
/// \returns A pointer to the requested device, or nullptr
inline hpet * get_hpet(unsigned i)
{
return i < m_hpets.count() ?
&m_hpets[i] : nullptr;
}
private:
/// Parse the ACPI XSDT and load relevant sub-tables.
/// \arg xsdt Pointer to the XSDT from the firmware
@@ -98,6 +121,10 @@ private:
/// \arg mcfg Pointer to the MCFG from the XSDT
void load_mcfg(const acpi_mcfg *mcfg);
/// Parse the ACPI HPET and initialize an HPET from it.
/// \arg hpet Pointer to the HPET from the XSDT
void load_hpet(const acpi_hpet *hpet);
/// Probe the PCIe busses and add found devices to our
/// device list. The device list is destroyed and rebuilt.
void probe_pci();
@@ -108,15 +135,16 @@ private:
lapic *m_lapic;
kutil::vector<ioapic> m_ioapics;
kutil::vector<hpet> m_hpets;
kutil::vector<pci_group> m_pci;
kutil::vector<pci_device> m_devices;
struct irq_allocation
{
const char *name;
irq_callback callback;
void *data;
const char *name = nullptr;
irq_callback callback = nullptr;
void *data = nullptr;
};
kutil::vector<irq_allocation> m_irqs;

120
src/kernel/hpet.cpp Normal file
View File

@@ -0,0 +1,120 @@
#include "kernel_memory.h"
#include "kutil/assert.h"
#include "device_manager.h"
#include "hpet.h"
#include "io.h"
#include "log.h"
namespace {
inline uint64_t volatile *capabilities(uint64_t volatile *base) { return base; }
inline uint64_t volatile *configuration(uint64_t volatile *base) { return base+2; }
inline uint64_t volatile *interrupt_status(uint64_t volatile *base) { return base+4; }
inline uint64_t volatile *counter_value(uint64_t volatile *base) { return base+30; }
inline uint64_t volatile *timer_base(uint64_t volatile *base, unsigned i) { return base + 0x20 + (4*i); }
inline uint64_t volatile *timer_config(uint64_t volatile *base, unsigned i) { return timer_base(base, i); }
inline uint64_t volatile *timer_comparator(uint64_t volatile *base, unsigned i) { return timer_base(base, i) + 1; }
}
void
hpet_irq_callback(void *hpet_ptr)
{
if (hpet_ptr)
reinterpret_cast<hpet*>(hpet_ptr)->callback();
}
hpet::hpet(uint8_t index, uint64_t *base) :
m_index(index),
m_base(base)
{
*configuration(m_base) = 0;
*counter_value(m_base) = 0;
uint64_t caps = *capabilities(base);
uint64_t config = *configuration(base);
m_timers = ((caps >> 8) & 0x1f) + 1;
m_period = (caps >> 32);
// setup_timer_interrupts(0, 2, 1000000, true);
// bool installed = device_manager::get()
// .install_irq(2, "HPET Timer", hpet_irq_callback, this);
// kassert(installed, "Installing HPET IRQ handler");
log::debug(logs::timer, "HPET %d capabilities:", index);
log::debug(logs::timer, " revision: %d", caps & 0xff);
log::debug(logs::timer, " timers: %d", m_timers);
log::debug(logs::timer, " bits: %d", ((caps >> 13) & 1) ? 64 : 32);
log::debug(logs::timer, " LRR capable: %d", ((caps >> 15) & 1));
log::debug(logs::timer, " period: %dns", m_period / 1000000);
log::debug(logs::timer, " global enabled: %d", config & 1);
log::debug(logs::timer, " LRR enable: %d", (config >> 1) & 1);
for (unsigned i = 0; i < m_timers; ++i) {
disable_timer(i);
uint64_t config = *timer_config(m_base, i);
log::debug(logs::timer, "HPET %d timer %d:", index, i);
log::debug(logs::timer, " int type: %d", (config >> 1) & 1);
log::debug(logs::timer, " timer type: %d", (config >> 3) & 1);
log::debug(logs::timer, " periodic cap: %d", (config >> 4) & 1);
log::debug(logs::timer, " bits: %d", ((config >> 5) & 1) ? 64 : 32);
log::debug(logs::timer, " 32 mode: %d", (config >> 8) & 1);
log::debug(logs::timer, " int route: %d", (config >> 9) & 0x1f);
log::debug(logs::timer, " FSB enable: %d", (config >> 14) & 1);
log::debug(logs::timer, " FSB capable: %d", (config >> 15) & 1);
log::debug(logs::timer, " rotung cap: %08x", (config >> 32));
}
}
void
hpet::setup_timer_interrupts(unsigned timer, uint8_t irq, uint64_t interval, bool periodic)
{
constexpr uint64_t femto_per_ns = 1000000ull;
*timer_comparator(m_base, timer) =
*counter_value(m_base) +
(interval * femto_per_ns) / m_period;
*timer_config(m_base, timer) = (irq << 9) | ((periodic ? 1 : 0) << 3);
}
void
hpet::enable_timer(unsigned timer)
{
*timer_config(m_base, timer) = *timer_config(m_base, timer) | (1 << 2);
}
void
hpet::disable_timer(unsigned timer)
{
*timer_config(m_base, timer) = *timer_config(m_base, timer) & ~(1ull << 2);
}
void
hpet::callback()
{
log::debug(logs::timer, "HPET %d got irq", m_index);
}
void
hpet::enable()
{
log::debug(logs::timer, "HPET %d enabling", m_index);
enable_timer(0);
*configuration(m_base) = 1;
}
uint64_t
hpet::value() const
{
return *counter_value(m_base);
}
uint64_t
hpet_clock_source(void *data)
{
return reinterpret_cast<hpet*>(data)->value();
}

50
src/kernel/hpet.h Normal file
View File

@@ -0,0 +1,50 @@
#pragma once
/// \file hpet.h
/// Definitions for handling the HPET timer
#include <stdint.h>
/// Source function for the clock that takes a pointer to
/// an hpet object.
uint64_t hpet_clock_source(void *);
/// Represents a single HPET timer
class hpet
{
public:
/// Constructor.
/// \arg index The index of the HPET out of all HPETs
/// \arg base The base address of the HPET for MMIO
hpet(uint8_t index, uint64_t *base);
/// Configure the timer and start it running.
void enable();
/// Wait in a tight loop while reading the HPET counter.
/// \arg ns Number of nanoseconds to wait
void spinwait(uint64_t ns) const;
/// Get the timer rate in ticks per ns
inline uint64_t rate() const { return m_period * 1000000ull; }
/// Get the current timer value
uint64_t value() const;
private:
friend void hpet_irq_callback(void *);
friend uint64_t hpet_clock_source(void *);
void setup_timer_interrupts(unsigned timer, uint8_t irq, uint64_t interval, bool periodic);
void enable_timer(unsigned timer);
void disable_timer(unsigned timer);
void callback();
uint8_t m_timers;
uint8_t m_index;
uint64_t m_period;
volatile uint64_t *m_base;
};

View File

@@ -169,6 +169,7 @@ kernel_main(args::header *header)
*/
devices.get_lapic()->calibrate_timer();
devices.init_drivers();
syscall_enable();
scheduler *sched = new (&scheduler::get()) scheduler(devices.get_lapic());

View File

@@ -311,6 +311,7 @@ scheduler::schedule()
m_blocked.push_back(m_current);
}
clock::get().update();
prune(++m_clock);
priority = 0;