mirror of
https://github.com/justinian/jsix.git
synced 2025-12-10 00:14:32 -08:00
[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:
@@ -16,6 +16,7 @@ modules:
|
||||
- src/kernel/apic.cpp
|
||||
- src/kernel/assert.cpp
|
||||
- src/kernel/boot.s
|
||||
- src/kernel/clock.cpp
|
||||
- src/kernel/console.cpp
|
||||
- src/kernel/cpprt.cpp
|
||||
- src/kernel/cpu.cpp
|
||||
@@ -27,6 +28,7 @@ modules:
|
||||
- src/kernel/fs/gpt.cpp
|
||||
- src/kernel/gdt.cpp
|
||||
- src/kernel/gdt.s
|
||||
- src/kernel/hpet.cpp
|
||||
- src/kernel/interrupts.cpp
|
||||
- src/kernel/interrupts.s
|
||||
- src/kernel/io.cpp
|
||||
|
||||
@@ -10,3 +10,5 @@ LOG(boot, debug);
|
||||
LOG(syscall,debug);
|
||||
LOG(vmem, debug);
|
||||
LOG(objs, debug);
|
||||
LOG(timer, debug);
|
||||
LOG(clock, debug);
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
22
src/kernel/clock.cpp
Normal 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
44
src/kernel/clock.h
Normal 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;
|
||||
};
|
||||
@@ -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
|
||||
|
||||
@@ -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
120
src/kernel/hpet.cpp
Normal 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
50
src/kernel/hpet.h
Normal 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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -311,6 +311,7 @@ scheduler::schedule()
|
||||
m_blocked.push_back(m_current);
|
||||
}
|
||||
|
||||
clock::get().update();
|
||||
prune(++m_clock);
|
||||
|
||||
priority = 0;
|
||||
|
||||
Reference in New Issue
Block a user