From 0377726615e51f88fa7353f655ed0b9d9067c8e8 Mon Sep 17 00:00:00 2001 From: subhobhai943 Date: Tue, 25 Aug 2026 21:04:03 +0530 Subject: [PATCH] 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 Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX --- Makefile | 3 +- drivers/char/lpt.c | 80 +++++++++++++++++++++++++++++++++ drivers/char/nvram.c | 64 +++++++++++++++++++++++++++ drivers/timer/hpet.c | 91 ++++++++++++++++++++++++++++++++++++++ include/drivers/hpet.h | 21 +++++++++ include/drivers/lpt.h | 17 +++++++ include/drivers/nvram.h | 20 +++++++++ kernel/main.c | 6 +++ userland/lazybox/lazybox.c | 39 ++++++++++++++++ 9 files changed, 340 insertions(+), 1 deletion(-) create mode 100644 drivers/char/lpt.c create mode 100644 drivers/char/nvram.c create mode 100644 drivers/timer/hpet.c create mode 100644 include/drivers/hpet.h create mode 100644 include/drivers/lpt.h create mode 100644 include/drivers/nvram.h diff --git a/Makefile b/Makefile index c6d4a06..cd443a3 100644 --- a/Makefile +++ b/Makefile @@ -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) diff --git a/drivers/char/lpt.c b/drivers/char/lpt.c new file mode 100644 index 0000000..569d314 --- /dev/null +++ b/drivers/char/lpt.c @@ -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 +#include + +#if defined(__x86_64__) +#include + +#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 diff --git a/drivers/char/nvram.c b/drivers/char/nvram.c new file mode 100644 index 0000000..a4bf6c9 --- /dev/null +++ b/drivers/char/nvram.c @@ -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 +#include + +#if defined(__x86_64__) +#include + +#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 diff --git a/drivers/timer/hpet.c b/drivers/timer/hpet.c new file mode 100644 index 0000000..97d88d7 --- /dev/null +++ b/drivers/timer/hpet.c @@ -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 +#include + +#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 diff --git a/include/drivers/hpet.h b/include/drivers/hpet.h new file mode 100644 index 0000000..f650b29 --- /dev/null +++ b/include/drivers/hpet.h @@ -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 +#include + +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 diff --git a/include/drivers/lpt.h b/include/drivers/lpt.h new file mode 100644 index 0000000..3e0b468 --- /dev/null +++ b/include/drivers/lpt.h @@ -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 +#include +#include + +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 diff --git a/include/drivers/nvram.h b/include/drivers/nvram.h new file mode 100644 index 0000000..5b86f37 --- /dev/null +++ b/include/drivers/nvram.h @@ -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 +#include + +#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 diff --git a/kernel/main.c b/kernel/main.c index 0ffb95a..47d937c 100644 --- a/kernel/main.c +++ b/kernel/main.c @@ -73,6 +73,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -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(); diff --git a/userland/lazybox/lazybox.c b/userland/lazybox/lazybox.c index 1b7544d..d9c5698 100644 --- a/userland/lazybox/lazybox.c +++ b/userland/lazybox/lazybox.c @@ -32,6 +32,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -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"},