Files
mop3/kernel/amd64/hpet.c
kamkow1 e5ebd7f3ba
All checks were successful
Build ISO image / build-and-deploy (push) Successful in 2m21s
Build documentation / build-and-deploy (push) Successful in 54s
Use a big-lock for kernel sychronization instead of fine-grained locking
2026-04-27 18:06:02 +02:00

139 lines
3.8 KiB
C

#include <amd64/hpet.h>
#include <libk/std.h>
#include <limine/requests.h>
#include <sync/spin_lock.h>
#include <sys/debug.h>
#include <sys/mm.h>
#include <sys/spin.h>
#include <uacpi/acpi.h>
#include <uacpi/status.h>
#include <uacpi/tables.h>
#include <uacpi/uacpi.h>
/*
* HPET (High Precision Event Timer) driver code. See more at https://wiki.osdev.org/HPET
*/
/* HPET Main Counter Value Register */
#define HPET_MCVR 0xF0
/* HPET General Configuration Register */
#define HPET_GCR 0x10
/* HPET General Capabilities and ID Register */
#define HPET_GCIDR 0x00
/* Set whether we sould use 32-bit or 64-bit reads/writes */
static bool hpet_32bits = 1;
/* Physical address for HPET MMIO */
static uintptr_t hpet_paddr;
/* HPET period in femtoseconds */
static uint64_t hpet_period_fs;
/* Lock, which protects concurrent access. See amd64/smp.c */
static spin_lock_t hpet_lock = SPIN_LOCK_INIT;
/* Read a HPET register. Assumes caller holds hpet_lock */
static uint64_t hpet_read64(uint32_t reg) {
struct limine_hhdm_response* hhdm = limine_hhdm_request.response;
uintptr_t hpet_vaddr = hpet_paddr + (uintptr_t)hhdm->offset;
return *(volatile uint64_t*)(hpet_vaddr + reg);
}
static uint32_t hpet_read32(uint32_t reg) {
struct limine_hhdm_response* hhdm = limine_hhdm_request.response;
uintptr_t hpet_vaddr = hpet_paddr + (uintptr_t)hhdm->offset;
return *(volatile uint32_t*)(hpet_vaddr + reg);
}
/* Write a HPET register. Assumes caller holds hpet_lock */
static void hpet_write64(uint32_t reg, uint64_t value) {
struct limine_hhdm_response* hhdm = limine_hhdm_request.response;
uintptr_t hpet_vaddr = hpet_paddr + (uintptr_t)hhdm->offset;
*(volatile uint64_t*)(hpet_vaddr + reg) = value;
}
static void hpet_write32(uint32_t reg, uint32_t value) {
struct limine_hhdm_response* hhdm = limine_hhdm_request.response;
uintptr_t hpet_vaddr = hpet_paddr + (uintptr_t)hhdm->offset;
*(volatile uint32_t*)(hpet_vaddr + reg) = value;
}
/* Read current value of HPET_MCVR register. */
static uint64_t hpet_read_counter(void) {
uint64_t value;
spin_lock(&hpet_lock);
if (!hpet_32bits)
value = hpet_read64(HPET_MCVR);
else {
uint32_t hi1, lo, hi2;
do {
hi1 = hpet_read32(HPET_MCVR + 4);
lo = hpet_read32(HPET_MCVR + 0);
hi2 = hpet_read32(HPET_MCVR + 4);
} while (hi1 != hi2);
value = ((uint64_t)hi1 << 32) | lo;
}
spin_unlock(&hpet_lock);
return value;
}
static void hpet_write_counter(uint64_t value) {
spin_lock(&hpet_lock);
if (!hpet_32bits)
hpet_write64(HPET_MCVR, value);
else {
hpet_write32(HPET_MCVR, (uint32_t)value);
hpet_write32(HPET_MCVR + 4, (uint32_t)(value >> 32));
}
spin_unlock(&hpet_lock);
}
/* Sleep for a given amount of microseconds. This time can last longer due to \ref hpet_lock being
* held. */
void hpet_sleep_micro(uint64_t us) {
if (hpet_period_fs == 0)
return;
uint64_t ticks_to_wait = (us * 1000ULL) / (hpet_period_fs / 1000000ULL);
uint64_t start = hpet_read_counter();
for (;;) {
uint64_t now = hpet_read_counter();
if ((now - start) >= ticks_to_wait)
break;
__asm__ volatile("pause" ::: "memory");
}
}
/* Initialize HPET */
void hpet_init(void) {
struct uacpi_table hpet_table;
uacpi_status status = uacpi_table_find_by_signature(ACPI_HPET_SIGNATURE, &hpet_table);
if (status != UACPI_STATUS_OK) {
DEBUG("Could not find HPET table!\n");
spin();
}
struct acpi_hpet* hpet = (struct acpi_hpet*)hpet_table.virt_addr;
hpet_paddr = (uintptr_t)hpet->address.address;
struct limine_hhdm_response* hhdm = limine_hhdm_request.response;
mm_map_kernel_page(hpet_paddr, (uintptr_t)hhdm->offset + hpet_paddr, MM_PG_PRESENT | MM_PG_RW);
uint64_t caps = hpet_read64(HPET_GCIDR);
hpet_32bits = (caps & (1 << 13)) ? 0 : 1;
hpet_period_fs = (uint32_t)(caps >> 32);
hpet_write64(HPET_GCR, 0);
hpet_write_counter(0);
hpet_write64(HPET_GCR, 1);
}