From 6c468a134b0c0f806a6f9b012aeaf4d8b662368b Mon Sep 17 00:00:00 2001 From: "Justin C. Miller" Date: Sun, 28 Jun 2020 17:47:46 -0700 Subject: [PATCH] [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 --- modules.yaml | 2 + src/include/log_areas.inc | 2 + src/kernel/acpi_tables.h | 10 +++ src/kernel/apic.cpp | 43 ++++-------- src/kernel/apic.h | 4 +- src/kernel/clock.cpp | 22 +++++++ src/kernel/clock.h | 44 +++++++++++++ src/kernel/device_manager.cpp | 50 ++++++++++++++ src/kernel/device_manager.h | 34 +++++++++- src/kernel/hpet.cpp | 120 ++++++++++++++++++++++++++++++++++ src/kernel/hpet.h | 50 ++++++++++++++ src/kernel/main.cpp | 1 + src/kernel/scheduler.cpp | 1 + 13 files changed, 347 insertions(+), 36 deletions(-) create mode 100644 src/kernel/clock.cpp create mode 100644 src/kernel/clock.h create mode 100644 src/kernel/hpet.cpp create mode 100644 src/kernel/hpet.h diff --git a/modules.yaml b/modules.yaml index f41ee86..ebdd98f 100644 --- a/modules.yaml +++ b/modules.yaml @@ -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 diff --git a/src/include/log_areas.inc b/src/include/log_areas.inc index 8c49c4b..cdfa47b 100644 --- a/src/include/log_areas.inc +++ b/src/include/log_areas.inc @@ -10,3 +10,5 @@ LOG(boot, debug); LOG(syscall,debug); LOG(vmem, debug); LOG(objs, debug); +LOG(timer, debug); +LOG(clock, debug); diff --git a/src/kernel/acpi_tables.h b/src/kernel/acpi_tables.h index 551f4f4..c990968 100644 --- a/src/kernel/acpi_tables.h +++ b/src/kernel/acpi_tables.h @@ -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)); + diff --git a/src/kernel/apic.cpp b/src/kernel/apic.cpp index 276cb86..a39ee0a 100644 --- a/src/kernel/apic.cpp +++ b/src/kernel/apic.cpp @@ -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> 8) & 0xff); - - - while (pit_count <= pit_33ms) { - outb(0x43, 0); // latch counter values - pit_count = - static_cast(inb(0x40)) | - static_cast(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(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); diff --git a/src/kernel/apic.h b/src/kernel/apic.h index 26db853..3650d30 100644 --- a/src/kernel/apic.h +++ b/src/kernel/apic.h @@ -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; diff --git a/src/kernel/clock.cpp b/src/kernel/clock.cpp new file mode 100644 index 0000000..b51b1c1 --- /dev/null +++ b/src/kernel/clock.cpp @@ -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); +} + diff --git a/src/kernel/clock.h b/src/kernel/clock.h new file mode 100644 index 0000000..4b73843 --- /dev/null +++ b/src/kernel/clock.h @@ -0,0 +1,44 @@ +#pragma once +/// \file clock.h +/// The kernel time keeping interface + +#include + +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; +}; diff --git a/src/kernel/device_manager.cpp b/src/kernel/device_manager.cpp index beee7b8..6d979de 100644 --- a/src/kernel/device_manager.cpp +++ b/src/kernel/device_manager.cpp @@ -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(header)); break; + case acpi_hpet::type_id: + load_hpet(reinterpret_cast(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(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 diff --git a/src/kernel/device_manager.h b/src/kernel/device_manager.h index 6701b17..5464a3a 100644 --- a/src/kernel/device_manager.h +++ b/src/kernel/device_manager.h @@ -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 m_ioapics; + kutil::vector m_hpets; kutil::vector m_pci; kutil::vector 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 m_irqs; diff --git a/src/kernel/hpet.cpp b/src/kernel/hpet.cpp new file mode 100644 index 0000000..3d75bb2 --- /dev/null +++ b/src/kernel/hpet.cpp @@ -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_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(data)->value(); +} diff --git a/src/kernel/hpet.h b/src/kernel/hpet.h new file mode 100644 index 0000000..025137a --- /dev/null +++ b/src/kernel/hpet.h @@ -0,0 +1,50 @@ +#pragma once +/// \file hpet.h +/// Definitions for handling the HPET timer + +#include + +/// 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; +}; + + diff --git a/src/kernel/main.cpp b/src/kernel/main.cpp index 21f3a84..e9d8f00 100644 --- a/src/kernel/main.cpp +++ b/src/kernel/main.cpp @@ -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()); diff --git a/src/kernel/scheduler.cpp b/src/kernel/scheduler.cpp index 8f5fdf1..ca78340 100644 --- a/src/kernel/scheduler.cpp +++ b/src/kernel/scheduler.cpp @@ -311,6 +311,7 @@ scheduler::schedule() m_blocked.push_back(m_current); } + clock::get().update(); prune(++m_clock); priority = 0;