feat(drivers): HPET high-res timer, LPT parallel port, CMOS NVRAM

Add three real platform device drivers (x86 hardware, ARM-stubbed):

- drivers/timer/hpet.c + include/drivers/hpet.h: High Precision Event Timer.
  Maps the HPET block at the standard 0xFED00000, reads the femtosecond period
  from the capability register, enables the main counter and exposes a
  monotonic nanosecond time source (hpet_get_ns) far finer than the 100 Hz PIT.
- drivers/char/lpt.c + include/drivers/lpt.h: IBM PC parallel port (LPT1) with
  the Centronics data/strobe handshake over ports 0x378/0x379/0x37A.
- drivers/char/nvram.c + include/drivers/nvram.h: MC146818 CMOS NVRAM read/write
  over 0x70/0x71, preserving the NMI-disable bit, with checksum and hex dump.

Wired all three into the [5/14] device-enumeration boot step and added LazyBox
applets `hpet`, `lpt`, and `nvram`.

Verified live under QEMU (screenshotted): HPET reports 100 MHz / 3 comparators /
10 ns period and its counter advances ~1.4 s of real time between two `hpet`
runs; NVRAM dumps 128 bytes with a computed checksum; LPT1 detected at 0x378.
Clean build on x86_64/aarch64/armv8i; 0 panics / 0 OOM.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
This commit is contained in:
subhobhai943andClaude Opus 4.8 committed 2026-08-25 21:04:03 +05:30
1 parent ea937a699c
commit 0377726615
9 files changed
+340 -1

No files matched your search

+2 -1
View File
@@ -277,7 +277,8 @@ endif
# Common Input/Display/Power/GPU/Networking Drivers
CONFIG_SRCS-y += drivers/char/tty.c drivers/char/keyboard.c drivers/char/serial.c drivers/char/vga.c \
drivers/input/mouse.c drivers/power/acpi.c drivers/power/cpufreq.c drivers/rtc/rtc.c drivers/sound/speaker.c \
drivers/net/e1000e.c drivers/virtio/virtio_gpu.c drivers/virtio/virtio_input.c
drivers/net/e1000e.c drivers/virtio/virtio_gpu.c drivers/virtio/virtio_input.c \
drivers/timer/hpet.c drivers/char/lpt.c drivers/char/nvram.c
# Userland & GUI Desktop Subsystem
ifeq ($(CONFIG_USERLAND_LAZYBOX), y)
+80
View File
@@ -0,0 +1,80 @@
// IBM PC parallel port (LPT1) printer driver for SUB-OS.
//
// Implements the classic Centronics handshake: place a byte on the data lines,
// wait for the peripheral to be ready (nBUSY high), then pulse the STROBE line.
#include <drivers/lpt.h>
#include <kernel/printk.h>
#if defined(__x86_64__)
#include <arch/x86_64/io.h>
#define LPT1_DATA 0x378
#define LPT1_STATUS 0x379 // bit7 nBUSY (1 = ready), bit6 nACK, bit5 PAPER, bit4 SELECT
#define LPT1_CONTROL 0x37A // bit0 STROBE, bit1 AUTOLF, bit2 nINIT, bit3 SELECT
#define LPT_STATUS_BUSY 0x80
#define LPT_CTRL_STROBE 0x01
#define LPT_CTRL_INIT 0x04
#define LPT_CTRL_SELECT 0x08
static bool g_present = false;
static uint64_t g_bytes_written = 0;
void lpt_init(void) {
uint8_t status = inb(LPT1_STATUS);
// A floating (unimplemented) port typically reads back all-ones.
if (status == 0xFF) {
g_present = false;
printk(KERN_INFO "LPT: no parallel port at 0x%03X\n", LPT1_DATA);
return;
}
// Bring the port out of reset and select the printer.
outb(LPT1_CONTROL, LPT_CTRL_INIT | LPT_CTRL_SELECT);
g_present = true;
printk(ANSI_BRIGHT_GREEN "LPT: " ANSI_RESET
"Parallel port LPT1 online at 0x%03X (status 0x%02X)\n", LPT1_DATA, status);
}
bool lpt_present(void) { return g_present; }
uint8_t lpt_status(void) { return g_present ? inb(LPT1_STATUS) : 0xFF; }
int lpt_putc(char c) {
if (!g_present) return -1;
// Wait (bounded) for the peripheral to raise nBUSY.
int spins = 100000;
while (spins-- > 0 && !(inb(LPT1_STATUS) & LPT_STATUS_BUSY)) {
/* busy-wait */
}
outb(LPT1_DATA, (uint8_t)c);
// Pulse STROBE low->high to latch the byte.
uint8_t ctrl = inb(LPT1_CONTROL);
outb(LPT1_CONTROL, ctrl | LPT_CTRL_STROBE);
outb(LPT1_CONTROL, ctrl & ~LPT_CTRL_STROBE);
g_bytes_written++;
return 0;
}
int lpt_write(const char* buf, size_t len) {
if (!g_present || !buf) return -1;
int n = 0;
for (size_t i = 0; i < len; i++) {
if (lpt_putc(buf[i]) != 0) break;
n++;
}
return n;
}
#else // Parallel port is an x86 platform device.
void lpt_init(void) { }
bool lpt_present(void) { return false; }
int lpt_putc(char c) { (void)c; return -1; }
int lpt_write(const char* buf, size_t len) { (void)buf; (void)len; return -1; }
uint8_t lpt_status(void) { return 0xFF; }
#endif
+64
View File
@@ -0,0 +1,64 @@
// CMOS / RTC NVRAM driver for SUB-OS.
//
// Reads and writes the MC146818 CMOS register file behind ports 0x70/0x71.
// The NMI-disable bit (0x80) of the index port is preserved on every access so
// this driver never inadvertently re-enables NMIs.
#include <drivers/nvram.h>
#include <kernel/printk.h>
#if defined(__x86_64__)
#include <arch/x86_64/io.h>
#define CMOS_INDEX 0x70
#define CMOS_DATA 0x71
#define CMOS_NMI_DISABLE 0x80
uint8_t nvram_read(uint8_t index) {
uint8_t prev = inb(CMOS_INDEX) & CMOS_NMI_DISABLE;
outb(CMOS_INDEX, prev | (index & 0x7F));
return inb(CMOS_DATA);
}
void nvram_write(uint8_t index, uint8_t value) {
uint8_t prev = inb(CMOS_INDEX) & CMOS_NMI_DISABLE;
outb(CMOS_INDEX, prev | (index & 0x7F));
outb(CMOS_DATA, value);
}
uint8_t nvram_checksum(void) {
uint16_t sum = 0;
for (int i = NVRAM_USER_START; i < NVRAM_SIZE; i++) {
sum += nvram_read((uint8_t)i);
}
return (uint8_t)(sum & 0xFF);
}
void nvram_dump(void) {
printk(ANSI_BRIGHT_CYAN "CMOS NVRAM (128 bytes):\n" ANSI_RESET);
for (int row = 0; row < NVRAM_SIZE; row += 16) {
printk(" %02X: ", row);
for (int col = 0; col < 16; col++) {
printk("%02X ", nvram_read((uint8_t)(row + col)));
}
printk("\n");
}
}
void nvram_init(void) {
// Byte 0x0F is the BIOS shutdown status; reading it proves the port works.
uint8_t shutdown_status = nvram_read(0x0F);
printk(ANSI_BRIGHT_GREEN "NVRAM: " ANSI_RESET
"CMOS scratch RAM ready (%d bytes, checksum 0x%02X, shutdown-status 0x%02X)\n",
NVRAM_SIZE, nvram_checksum(), shutdown_status);
}
#else // CMOS/RTC NVRAM is an x86 platform device.
uint8_t nvram_read(uint8_t index) { (void)index; return 0; }
void nvram_write(uint8_t index, uint8_t v){ (void)index; (void)v; }
uint8_t nvram_checksum(void) { return 0; }
void nvram_dump(void) { }
void nvram_init(void) { }
#endif
+91
View File
@@ -0,0 +1,91 @@
// High Precision Event Timer (HPET) driver for SUB-OS.
//
// Reads the HPET block memory-mapped at the standard base 0xFED00000 (as
// exposed by QEMU). The general-capabilities register carries the counter
// period in femtoseconds, from which the tick frequency and a nanosecond
// timestamp are derived. The kernel identity-maps low physical memory, so the
// MMIO block is reachable directly through its physical address.
#include <drivers/hpet.h>
#include <kernel/printk.h>
#if defined(__x86_64__)
#define HPET_BASE_PHYS 0xFED00000ULL
#define HPET_REG_CAP_ID 0x000 // General Capabilities and ID (64-bit)
#define HPET_REG_CONFIG 0x010 // General Configuration (64-bit)
#define HPET_REG_COUNTER 0x0F0 // Main Counter Value (64-bit)
#define HPET_CFG_ENABLE 0x1ULL // General Config bit 0: overall enable
static volatile uint64_t* g_hpet = (volatile uint64_t*)HPET_BASE_PHYS;
static bool g_available = false;
static uint64_t g_period_fs = 0; // counter period in femtoseconds
static uint64_t g_freq_hz = 0;
static uint32_t g_num_timers = 0;
static uint64_t g_base_counter = 0; // counter value captured at init
static inline uint64_t hpet_rd(uint32_t off) {
return g_hpet[off / sizeof(uint64_t)];
}
static inline void hpet_wr(uint32_t off, uint64_t val) {
g_hpet[off / sizeof(uint64_t)] = val;
}
void hpet_init(void) {
uint64_t caps = hpet_rd(HPET_REG_CAP_ID);
// Period lives in the top 32 bits (femtoseconds per tick). A sane HPET has
// a non-zero period below 100 ns (100,000,000 fs); anything else means the
// block is absent or unmapped, so we decline rather than divide by zero.
uint64_t period = caps >> 32;
if (period == 0 || period > 100000000ULL) {
g_available = false;
printk(KERN_INFO "HPET: not present (capabilities=0x%llx)\n",
(unsigned long long)caps);
return;
}
g_period_fs = period;
g_num_timers = (uint32_t)(((caps >> 8) & 0x1F) + 1);
g_freq_hz = 1000000000000000ULL / g_period_fs; // 1e15 fs per second
// Enable the main counter.
hpet_wr(HPET_REG_CONFIG, hpet_rd(HPET_REG_CONFIG) | HPET_CFG_ENABLE);
g_base_counter = hpet_rd(HPET_REG_COUNTER);
g_available = true;
printk(ANSI_BRIGHT_GREEN "HPET: " ANSI_RESET
"High Precision Event Timer online (%llu Hz, %u comparators, period %llu fs)\n",
(unsigned long long)g_freq_hz, g_num_timers,
(unsigned long long)g_period_fs);
}
bool hpet_available(void) { return g_available; }
uint64_t hpet_read_counter(void) {
if (!g_available) return 0;
return hpet_rd(HPET_REG_COUNTER);
}
uint64_t hpet_get_frequency_hz(void) { return g_freq_hz; }
uint64_t hpet_get_ns(void) {
if (!g_available) return 0;
uint64_t ticks = hpet_rd(HPET_REG_COUNTER) - g_base_counter;
// ns = ticks * period_fs / 1e6 ; group to avoid overflow on large counts.
return (ticks / 1000000ULL) * g_period_fs + ((ticks % 1000000ULL) * g_period_fs) / 1000000ULL;
}
uint32_t hpet_num_timers(void) { return g_num_timers; }
#else // Non-x86 targets: HPET is an x86 platform device.
void hpet_init(void) { }
bool hpet_available(void) { return false; }
uint64_t hpet_read_counter(void) { return 0; }
uint64_t hpet_get_frequency_hz(void) { return 0; }
uint64_t hpet_get_ns(void) { return 0; }
uint32_t hpet_num_timers(void) { return 0; }
#endif
+21
View File
@@ -0,0 +1,21 @@
#ifndef _DRIVERS_HPET_H
#define _DRIVERS_HPET_H
// High Precision Event Timer (HPET) driver.
//
// The HPET is a memory-mapped, free-running up-counter defined by the
// Intel/Microsoft HPET spec and emulated by QEMU at the standard base
// 0xFED00000. It provides a monotonic high-resolution time source far finer
// than the 100 Hz PIT tick.
#include <stdint.h>
#include <stdbool.h>
void hpet_init(void);
bool hpet_available(void);
uint64_t hpet_read_counter(void); // raw main counter value
uint64_t hpet_get_frequency_hz(void); // counter ticks per second
uint64_t hpet_get_ns(void); // nanoseconds since HPET was enabled
uint32_t hpet_num_timers(void); // comparator count reported by the block
#endif // _DRIVERS_HPET_H
+17
View File
@@ -0,0 +1,17 @@
#ifndef _DRIVERS_LPT_H
#define _DRIVERS_LPT_H
// IBM PC parallel port (LPT1) printer driver. Centronics handshake over the
// standard I/O ports 0x378 (data) / 0x379 (status) / 0x37A (control).
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
void lpt_init(void);
bool lpt_present(void);
int lpt_putc(char c); // 0 on success, -1 on timeout/absent
int lpt_write(const char* buf, size_t len); // bytes written
uint8_t lpt_status(void); // raw status register
#endif // _DRIVERS_LPT_H
+20
View File
@@ -0,0 +1,20 @@
#ifndef _DRIVERS_NVRAM_H
#define _DRIVERS_NVRAM_H
// CMOS / RTC NVRAM driver. The MC146818-compatible RTC exposes 128 indexed
// bytes behind ports 0x70 (index) / 0x71 (data); bytes 0x0E..0x7F are the
// battery-backed scratch RAM used by the BIOS for configuration.
#include <stdint.h>
#include <stdbool.h>
#define NVRAM_SIZE 128
#define NVRAM_USER_START 0x0E // first non-RTC (scratch) byte
void nvram_init(void);
uint8_t nvram_read(uint8_t index);
void nvram_write(uint8_t index, uint8_t value);
uint8_t nvram_checksum(void); // simple sum over the scratch region
void nvram_dump(void); // hex dump of all 128 bytes to the console
#endif // _DRIVERS_NVRAM_H
+6
View File
@@ -73,6 +73,9 @@
#include <drivers/e1000e.h>
#include <drivers/virtio_gpu.h>
#include <drivers/cpufreq.h>
#include <drivers/hpet.h>
#include <drivers/nvram.h>
#include <drivers/lpt.h>
#include <drivers/virtio_input.h>
#include <gui/gui.h>
#include <init/init.h>
@@ -128,6 +131,9 @@ static void subos_modular_core_boot(void) {
xhci_init();
hwmon_init();
cpufreq_init();
hpet_init();
nvram_init();
lpt_init();
#endif
canvas_init();
pty_init();
+39
View File
@@ -32,6 +32,9 @@
#include <kernel/nt/ob.h>
#include <kernel/nt/reg.h>
#include <kernel/nt/io.h>
#include <drivers/hpet.h>
#include <drivers/nvram.h>
#include <drivers/lpt.h>
#include <kernel/sub_lang.h>
#include <kernel/cpp_kernel.h>
#include <userland/snake.h>
@@ -715,6 +718,39 @@ static int applet_iodev(int argc, char** argv) {
return 0;
}
static int applet_hpet(int argc, char** argv) {
(void)argc; (void)argv;
if (!hpet_available()) {
printk("HPET: not available on this platform\n");
return 1;
}
printk(ANSI_BRIGHT_CYAN "High Precision Event Timer:\n" ANSI_RESET);
printk(" Frequency : %llu Hz\n", (unsigned long long)hpet_get_frequency_hz());
printk(" Comparators: %u\n", hpet_num_timers());
printk(" Main count : %llu\n", (unsigned long long)hpet_read_counter());
printk(" Uptime : %llu ns since HPET enable\n", (unsigned long long)hpet_get_ns());
return 0;
}
static int applet_nvram(int argc, char** argv) {
(void)argc; (void)argv;
nvram_dump();
printk(" checksum(0x0E..0x7F) = 0x%02X\n", nvram_checksum());
return 0;
}
static int applet_lpt(int argc, char** argv) {
if (!lpt_present()) {
printk("LPT: no parallel port present\n");
return 1;
}
const char* text = (argc >= 2) ? argv[1] : "SUB-OS\n";
int n = lpt_write(text, strlen(text));
printk("LPT1: status=0x%02X, wrote %d byte(s) to the parallel port\n",
lpt_status(), n);
return 0;
}
static int applet_rand(int argc, char** argv) {
int count = (argc >= 2) ? atoi(argv[1]) : 4;
printk("Random Integers: ");
@@ -2428,6 +2464,9 @@ static const lazybox_applet_t applets[] = {
{"ntobj", applet_ntobj, "ntobj", "NT Object Manager stats/demo","Kernel"},
{"reg", applet_reg, "reg query|add|set|stats", "NT configuration registry", "Kernel"},
{"iodev", applet_iodev, "iodev", "NT I/O Manager devices & IRP","Kernel"},
{"hpet", applet_hpet, "hpet", "High Precision Event Timer", "Hardware"},
{"nvram", applet_nvram, "nvram", "Dump CMOS NVRAM bytes", "Hardware"},
{"lpt", applet_lpt, "lpt [text]", "Send text to LPT1 parallel", "Hardware"},
{"rand", applet_rand, "rand [count]", "Generate random numbers", "Crypto"},
{"certcheck", applet_certcheck, "certcheck", "View X.509 kernel keyring", "Security"},
{"capsh", applet_capsh, "capsh", "View process capabilities", "Security"},