summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'drivers')
-rw-r--r--drivers/Build.mk17
-rw-r--r--drivers/base/bus.cc32
-rw-r--r--drivers/base/core.cc156
-rw-r--r--drivers/base/driver.cc72
-rw-r--r--drivers/base/platform.cc248
-rw-r--r--drivers/console.cc119
-rw-r--r--drivers/plic.cc259
-rw-r--r--drivers/tty/n_tty.cc539
-rw-r--r--drivers/tty/tty_core.cc536
-rw-r--r--drivers/tty/tty_uart.cc251
-rw-r--r--drivers/uart.cc181
11 files changed, 0 insertions, 2410 deletions
diff --git a/drivers/Build.mk b/drivers/Build.mk
deleted file mode 100644
index 342f02b..0000000
--- a/drivers/Build.mk
+++ /dev/null
@@ -1,17 +0,0 @@
-targets += drivers/base drivers/tty drivers/core
-target.drivers/base.kind := source-set
-target.drivers/base.sources := \
- drivers/base/bus.cc drivers/base/core.cc drivers/base/driver.cc \
- drivers/base/platform.cc
-
-target.drivers/tty.kind := source-set
-target.drivers/tty.sources := \
- drivers/tty/tty_core.cc drivers/tty/n_tty.cc drivers/tty/tty_uart.cc
-
-target.drivers/core.kind := source-set
-target.drivers/core.deps := drivers/base drivers/tty
-target.drivers/core.sources := drivers/uart.cc drivers/console.cc
-ifeq ($(ARCH),riscv)
-target.drivers/core.sources += drivers/plic.cc
-endif
-
diff --git a/drivers/base/bus.cc b/drivers/base/bus.cc
deleted file mode 100644
index 975f676..0000000
--- a/drivers/base/bus.cc
+++ /dev/null
@@ -1,32 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * drivers/base/bus.cc — bus_type registration.
- *
- * Mirrors Linux drivers/base/bus.c. Trivial in this iteration:
- * initialize the device/driver lists and the per-bus spinlock.
- *
- * Reference: docs/specs/storage_stack.md §4.3
- */
-
-#include <lume/device.h>
-#include <lume/klog.h>
-#include <lume/kprintf.h>
-#include <lume/errno.h>
-
-int bus_register(bus_type* bus)
-{
- if (bus == nullptr || bus->name == nullptr) {
- return -EINVAL;
- }
- if (bus->registered) {
- return 0;
- }
- bus->devices.init();
- bus->drivers.init();
- bus->lock.init(bus->name);
- bus->registered = true;
- kprintf("[bus] '%s' registered\n", bus->name);
- return 0;
-}
diff --git a/drivers/base/core.cc b/drivers/base/core.cc
deleted file mode 100644
index f92b2a4..0000000
--- a/drivers/base/core.cc
+++ /dev/null
@@ -1,156 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * drivers/base/core.cc — device core.
- *
- * Mirrors Linux drivers/base/core.c (greatly simplified). Provides
- * device_initialize, device_register, device_unregister, and the
- * try_bind helper shared with driver registration.
- *
- * Reference: docs/specs/storage_stack.md §4.3
- */
-
-#include <lume/device.h>
-#include <lume/container_of.h>
-#include <lume/klog.h>
-#include <lume/kprintf.h>
-#include <lume/errno.h>
-#include <lume/list.h>
-
-/* ------------------------------------------------------------------ */
-/* device ktype — generic release (no-op; subclasses override) */
-/* ------------------------------------------------------------------ */
-
-static void device_release(kobject* kobj)
-{
- /* Concrete device flavors (platform_device, virtio_device, ...) are
- * expected to allocate via kmalloc and free in their own release
- * paths after device_unregister + final kobject_put. The generic
- * device kobj_type only fires for stack-allocated devices used in
- * tests — nothing to do. */
- (void)kobj;
-}
-
-static const kobj_type device_ktype = {
- .release = device_release,
-};
-
-/* ------------------------------------------------------------------ */
-/* device_initialize / device_register */
-/* ------------------------------------------------------------------ */
-
-void device_initialize(device* dev)
-{
- if (dev == nullptr) {
- return;
- }
- kobject_init(&dev->kobj, &device_ktype);
- dev->parent = nullptr;
- dev->driver = nullptr;
- dev->driver_data = nullptr;
- dev->platform_data = nullptr;
- dev->devt = 0;
- dev->bus_list.init();
- dev->driver_list.init();
-}
-
-const char* dev_name(const device* dev)
-{
- if (dev == nullptr) {
- return "(null)";
- }
- if (dev->kobj.name != nullptr) {
- return dev->kobj.name;
- }
- if (dev->init_name != nullptr) {
- return dev->init_name;
- }
- return "(unnamed)";
-}
-
-/* try_bind: attempt to match `dev` against `drv` and probe.
- * Returns 1 on successful bind, 0 otherwise.
- * Caller must hold bus->lock. */
-int device_try_bind(device* dev, device_driver* drv);
-
-int device_try_bind(device* dev, device_driver* drv)
-{
- bus_type* bus = dev->bus;
-
- if (bus->match != nullptr && bus->match(dev, drv) == 0) {
- return 0;
- }
-
- dev->driver = drv;
-
- int rc = 0;
- if (bus->probe != nullptr) {
- rc = bus->probe(dev);
- } else if (drv->probe != nullptr) {
- rc = drv->probe(dev);
- }
-
- if (rc == 0) {
- list_add_tail(&dev->driver_list, &drv->devices);
- return 1;
- }
- dev->driver = nullptr;
- return 0;
-}
-
-int device_register(device* dev)
-{
- if (dev == nullptr || dev->bus == nullptr) {
- return -EINVAL;
- }
- if (!dev->bus->registered) {
- kprintf("[device] register '%s' fail: bus '%s' not registered\n",
- dev_name(dev),
- dev->bus->name != nullptr ? dev->bus->name : "?");
- return -ENODEV;
- }
-
- /* device_initialize is idempotent — safe to call again, but most
- * paths will already have initialized. Detect via ktype set. */
- if (dev->kobj.ktype == nullptr) {
- device_initialize(dev);
- }
- if (dev->kobj.name == nullptr) {
- kobject_add(&dev->kobj, nullptr, dev->init_name);
- }
- dev->bus_list.init();
- dev->driver_list.init();
-
- LockGuard g(dev->bus->lock);
- list_add_tail(&dev->bus_list, &dev->bus->devices);
-
- list_node* cur = dev->bus->drivers.next;
- while (cur != &dev->bus->drivers) {
- device_driver* drv =
- list_entry<device_driver, &device_driver::bus_node>(cur);
- cur = cur->next;
- if (device_try_bind(dev, drv) != 0) {
- break;
- }
- }
- return 0;
-}
-
-void device_unregister(device* dev)
-{
- if (dev == nullptr || dev->bus == nullptr) {
- return;
- }
-
- LockGuard g(dev->bus->lock);
-
- if (dev->driver != nullptr) {
- if (dev->driver->remove != nullptr) {
- dev->driver->remove(dev);
- }
- list_del_init(&dev->driver_list);
- dev->driver = nullptr;
- }
- list_del_init(&dev->bus_list);
-}
diff --git a/drivers/base/driver.cc b/drivers/base/driver.cc
deleted file mode 100644
index ce15b6d..0000000
--- a/drivers/base/driver.cc
+++ /dev/null
@@ -1,72 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * drivers/base/driver.cc — driver_register / driver_unregister.
- *
- * Mirrors Linux drivers/base/driver.c. Registers a device_driver on
- * its bus and tries to bind any unbound matching devices.
- *
- * Reference: docs/specs/storage_stack.md §4.3
- */
-
-#include <lume/device.h>
-#include <lume/container_of.h>
-#include <lume/klog.h>
-#include <lume/kprintf.h>
-#include <lume/errno.h>
-
-/* Implemented in core.cc — bind a device to a driver on its bus.
- * Returns 1 on success, 0 on no-match-or-probe-fail. */
-int device_try_bind(device* dev, device_driver* drv);
-
-int driver_register(device_driver* drv)
-{
- if (drv == nullptr || drv->bus == nullptr) {
- return -EINVAL;
- }
- if (!drv->bus->registered) {
- kprintf("[driver] register '%s' fail: bus '%s' not registered\n",
- drv->name != nullptr ? drv->name : "?",
- drv->bus->name != nullptr ? drv->bus->name : "?");
- return -ENODEV;
- }
-
- drv->bus_node.init();
- drv->devices.init();
-
- LockGuard g(drv->bus->lock);
- list_add_tail(&drv->bus_node, &drv->bus->drivers);
-
- /* Try to bind any unbound devices already on the bus. */
- list_node* cur = drv->bus->devices.next;
- while (cur != &drv->bus->devices) {
- device* dev = list_entry<device, &device::bus_list>(cur);
- cur = cur->next;
- if (dev->driver == nullptr) {
- device_try_bind(dev, drv);
- }
- }
- return 0;
-}
-
-void driver_unregister(device_driver* drv)
-{
- if (drv == nullptr || drv->bus == nullptr) {
- return;
- }
-
- LockGuard g(drv->bus->lock);
-
- /* Unbind every device bound to this driver. */
- while (!drv->devices.is_empty()) {
- list_node* first = drv->devices.next;
- device* dev = list_entry<device, &device::driver_list>(first);
- if (drv->remove != nullptr) {
- drv->remove(dev);
- }
- list_del_init(&dev->driver_list);
- dev->driver = nullptr;
- }
- list_del_init(&drv->bus_node);
-}
diff --git a/drivers/base/platform.cc b/drivers/base/platform.cc
deleted file mode 100644
index fae8af6..0000000
--- a/drivers/base/platform.cc
+++ /dev/null
@@ -1,248 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * drivers/base/platform.cc — platform bus + FDT-driven device creation.
- *
- * Mirrors Linux drivers/base/platform.c. Provides:
- * - platform_bus — the bus_type instance
- * - platform_match — compatible-string matching via of_match_table
- * - platform_dispatch — bus->probe forwarder that down-casts and calls
- * platform_driver::probe(platform_device*)
- * - driver_probe_all() — boot entry: register bus, register linker-set
- * drivers, walk FDT, register platform_devices
- *
- * Reference: docs/specs/storage_stack.md §4.3
- */
-
-#include <lume/platform_device.h>
-#include <lume/driver.h>
-#include <lume/device_node.h>
-#include <lume/fdt.h>
-#include <string.h>
-
-/* kmalloc is declared extern "C" in <lume/slab.h>, but mm/slab.cc defines
- * it WITHOUT extern "C" (i.e. C++-mangled). Declare locally with C++
- * linkage so the symbol matches what mm/slab.o exports. */
-void* kmalloc(uint32 size);
-#include <lume/klog.h>
-#include <lume/kprintf.h>
-#include <lume/errno.h>
-#include <lume/container_of.h>
-
-/* ------------------------------------------------------------------ */
-/* Linker-section registry */
-/* ------------------------------------------------------------------ */
-
-extern "C" {
- extern platform_driver* __drivers_start[];
- extern platform_driver* __drivers_end[];
-}
-
-/* ------------------------------------------------------------------ */
-/* platform_bus_type */
-/* ------------------------------------------------------------------ */
-
-static int platform_match(device* dev, device_driver* drv)
-{
- platform_device* pdev = to_platform_device(dev);
- platform_driver* pdrv = to_platform_driver(drv);
-
- if (pdrv->of_match_table == nullptr || pdev->of_node == nullptr) {
- return 0;
- }
- const FdtProperty* compat_prop = pdev->of_node->get_prop("compatible");
- if (compat_prop == nullptr || compat_prop->value == nullptr ||
- compat_prop->len == 0) {
- return 0;
- }
-
- const char* compat_list = static_cast<const char*>(compat_prop->value);
- const uint32 compat_len = static_cast<uint32>(compat_prop->len);
- for (const of_device_id* id = pdrv->of_match_table;
- id->compatible != nullptr; ++id) {
- uint32 offset = 0;
- while (offset < compat_len) {
- const char* node_compat = compat_list + offset;
- uint32 remaining = compat_len - offset;
- uint32 length = 0;
- while (length < remaining && node_compat[length] != '\0') {
- ++length;
- }
- if (length == remaining) {
- break; /* malformed property without a terminating NUL */
- }
- if (strcmp(id->compatible, node_compat) == 0) {
- return 1;
- }
- offset += length + 1;
- }
- }
- return 0;
-}
-
-static int platform_dispatch(device* dev)
-{
- platform_device* pdev = to_platform_device(dev);
- platform_driver* pdrv = to_platform_driver(dev->driver);
- if (pdrv->probe == nullptr) {
- return 0;
- }
- return pdrv->probe(pdev);
-}
-
-bus_type platform_bus = {
- .name = "platform",
- .match = platform_match,
- .probe = platform_dispatch,
- .devices = {},
- .drivers = {},
- .lock = {},
- .registered = false,
-};
-
-void platform_bus_init()
-{
- bus_register(&platform_bus);
-}
-
-/* ------------------------------------------------------------------ */
-/* platform_driver_register / platform_device_register */
-/* ------------------------------------------------------------------ */
-
-int platform_driver_register(platform_driver* pdrv)
-{
- if (pdrv == nullptr) {
- return -EINVAL;
- }
- pdrv->driver.bus = &platform_bus;
- /* driver.probe is unused — we route through bus->probe. Set name
- * from the first of_match if not already provided. */
- if (pdrv->driver.name == nullptr && pdrv->of_match_table != nullptr) {
- pdrv->driver.name = pdrv->of_match_table[0].compatible;
- }
- return driver_register(&pdrv->driver);
-}
-
-int platform_device_register(platform_device* pdev)
-{
- if (pdev == nullptr) {
- return -EINVAL;
- }
- pdev->dev.bus = &platform_bus;
- if (pdev->dev.init_name == nullptr && pdev->of_node != nullptr) {
- pdev->dev.init_name = pdev->of_node->get_prop_string("compatible");
- }
- device_initialize(&pdev->dev);
- return device_register(&pdev->dev);
-}
-
-/* ------------------------------------------------------------------ */
-/* FDT-driven device instantiation */
-/* ------------------------------------------------------------------ */
-
-static platform_device* alloc_platform_device_from_node(DeviceNode* node,
- const char* compat)
-{
- auto* pdev = static_cast<platform_device*>(
- kmalloc(static_cast<uint32>(sizeof(platform_device))));
- if (pdev == nullptr) {
- return nullptr;
- }
- memset(pdev, 0, sizeof(*pdev));
- pdev->of_node = node;
- pdev->mmio_base = node->get_prop_u64("reg", 0);
- /* "reg" second cell is size — for now we don't bother parsing it
- * separately; drivers that need it pull from of_node directly. */
- pdev->mmio_size = 0;
- pdev->irq = -1;
- pdev->dev.init_name = compat;
- return pdev;
-}
-
-/* For each registered platform_driver, walk its of_match_table and
- * spawn a platform_device for each FDT compatible match found.
- *
- * NOTE: This is a simplified FDT walker — it only finds the FIRST FDT
- * node matching each compatible string (delegating to
- * fdt_find_compatible). Devices with multiple FDT instances of the
- * same compatible will only see one bound here. A full FDT tree walk
- * is left for when virtio-mmio or multi-instance UART arrives.
- */
-static void instantiate_devices_for_driver(platform_driver* pdrv)
-{
- if (pdrv == nullptr || pdrv->of_match_table == nullptr) {
- return;
- }
- for (const of_device_id* id = pdrv->of_match_table;
- id->compatible != nullptr; ++id) {
- DeviceNode* node = fdt_find_compatible(id->compatible);
- if (node == nullptr) {
- continue;
- }
- platform_device* pdev =
- alloc_platform_device_from_node(node, id->compatible);
- if (pdev == nullptr) {
- kprintf("[platform] OOM allocating pdev for %s\n", id->compatible);
- continue;
- }
- kprintf("[platform] instantiating '%s' -> pdev mmio=0x%llx\n",
- id->compatible, pdev->mmio_base);
- platform_device_register(pdev);
- }
-}
-
-/* ------------------------------------------------------------------ */
-/* Boot entry — replaces the old driver_probe_all() */
-/* ------------------------------------------------------------------ */
-
-void driver_probe_all()
-{
- kprintf("[dpa] step 1: &platform_bus=%p (link 0xffffffc08033a0a8)\n",
- &platform_bus);
- kprintf("[dpa] step 1: pre-touch platform_bus.name slot...\n");
- const char** name_slot = &platform_bus.name;
- kprintf("[dpa] step 1: name_slot=%p\n", name_slot);
- kprintf("[dpa] step 1: about to deref *name_slot\n");
- const char* nm = *name_slot;
- kprintf("[dpa] step 1: *name_slot=%p\n", nm);
- kprintf("[dpa] step 1: about to read first byte of nm\n");
- char first = nm != nullptr ? nm[0] : '!';
- kprintf("[dpa] step 1: nm[0]='%c'\n", first);
-
- kprintf("[dpa] step 1: platform_bus_init enter\n");
- platform_bus_init();
- kprintf("[dpa] step 1: platform_bus_init done\n");
-
- platform_driver** start = __drivers_start;
- platform_driver** end = __drivers_end;
- kprintf("[dpa] step 2: __drivers_start=%p __drivers_end=%p count=%lld\n",
- start, end, (long long)(end - start));
-
- for (platform_driver** slot = start; slot < end; ++slot) {
- kprintf("[dpa] step 2: slot=%p *slot=%p\n", slot, *slot);
- if (*slot != nullptr) {
- platform_driver* pdrv = *slot;
- kprintf("[dpa] step 2: pdrv->of_match_table=%p\n",
- pdrv->of_match_table);
- if (pdrv->of_match_table != nullptr) {
- kprintf("[dpa] step 2: pdrv->of_match_table[0].compatible=%p\n",
- pdrv->of_match_table[0].compatible);
- if (pdrv->of_match_table[0].compatible != nullptr) {
- kprintf("[dpa] step 2: compatible='%s'\n",
- pdrv->of_match_table[0].compatible);
- }
- }
- platform_driver_register(pdrv);
- kprintf("[dpa] step 2: registered\n");
- }
- }
-
- kprintf("[dpa] step 3: instantiate devices\n");
- for (platform_driver** slot = start; slot < end; ++slot) {
- if (*slot != nullptr) {
- instantiate_devices_for_driver(*slot);
- }
- }
- kprintf("[dpa] done\n");
-}
diff --git a/drivers/console.cc b/drivers/console.cc
deleted file mode 100644
index f741dd2..0000000
--- a/drivers/console.cc
+++ /dev/null
@@ -1,119 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * console.cc — Console character device (/dev/console)
- *
- * Wraps the existing early_putc() UART driver as a file_operations
- * chardev. read() is a stub for now (returns 0 = EOF).
- * write() sends each byte through early_putc().
- */
-
-#include <lume/file.h>
-#include <lume/chardev.h>
-#include <lume/types.h>
-
-extern "C" void early_putc(char c);
-
-/* ================================================================== */
-/* console_write */
-/* ================================================================== */
-
-static int64 console_write(file* fp, const char* buf, uint64 count, int64* pos)
-{
- (void)fp;
- (void)pos;
-
- for (uint64 i = 0; i < count; i++) {
- early_putc(buf[i]);
- }
-
- return static_cast<int64>(count);
-}
-
-/* ================================================================== */
-/* console_read — stub (returns 0 = EOF for now) */
-/* ================================================================== */
-
-static int64 console_read(file* fp, char* buf, uint64 count, int64* pos)
-{
- (void)fp;
- (void)buf;
- (void)count;
- (void)pos;
- /* TODO: implement UART RX polling/IRQ-based blocking read */
- return 0;
-}
-
-/* ================================================================== */
-/* null device — /dev/null */
-/* ================================================================== */
-
-static int64 null_read(file* /*fp*/, char* /*buf*/, uint64 /*count*/, int64* /*pos*/)
-{
- return 0; /* EOF */
-}
-
-static int64 null_write(file* /*fp*/, const char* /*buf*/, uint64 count, int64* /*pos*/)
-{
- return static_cast<int64>(count); /* discard */
-}
-
-/* ================================================================== */
-/* zero device — /dev/zero */
-/* ================================================================== */
-
-static int64 zero_read(file* /*fp*/, char* buf, uint64 count, int64* /*pos*/)
-{
- for (uint64 i = 0; i < count; i++) {
- buf[i] = 0;
- }
- return static_cast<int64>(count);
-}
-
-static int64 zero_write(file* /*fp*/, const char* /*buf*/, uint64 count, int64* /*pos*/)
-{
- return static_cast<int64>(count); /* discard */
-}
-
-/* ================================================================== */
-/* Registration — runtime init (no static initializers!) */
-/* ================================================================== */
-
-static file_operations console_fops;
-static file_operations null_fops;
-static file_operations zero_fops;
-
-static cdev console_cdev;
-static cdev null_cdev;
-static cdev zero_cdev;
-
-void console_cdev_init()
-{
- /* Console: major=5, minor=1 */
- console_fops.read = console_read;
- console_fops.write = console_write;
-
- console_cdev.name = "console";
- console_cdev.dev = MKDEV(5, 1);
- console_cdev.fops = &console_fops;
- cdev_register(&console_cdev);
-
- /* /dev/null: major=1, minor=3 */
- null_fops.read = null_read;
- null_fops.write = null_write;
-
- null_cdev.name = "null";
- null_cdev.dev = MKDEV(1, 3);
- null_cdev.fops = &null_fops;
- cdev_register(&null_cdev);
-
- /* /dev/zero: major=1, minor=5 */
- zero_fops.read = zero_read;
- zero_fops.write = zero_write;
-
- zero_cdev.name = "zero";
- zero_cdev.dev = MKDEV(1, 5);
- zero_cdev.fops = &zero_fops;
- cdev_register(&zero_cdev);
-}
diff --git a/drivers/plic.cc b/drivers/plic.cc
deleted file mode 100644
index 2818700..0000000
--- a/drivers/plic.cc
+++ /dev/null
@@ -1,259 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * plic.cc — Platform-Level Interrupt Controller (RISC-V)
- *
- * QEMU virt platform PLIC:
- * Base PA: 0x0C000000
- * Size: 0x04000000 (64MB)
- *
- * Register layout (per RISC-V PLIC spec):
- * 0x000000: Priority registers (source 0..1023, 4 bytes each)
- * 0x001000: Pending bits (source 0..1023, bit per source)
- * 0x002000: Enable bits per context
- * 0x200000: Threshold + claim/complete per context
- *
- * Context mapping for QEMU virt:
- * Context 2*hartid + 1 = S-mode context for hart N
- *
- * Reference: docs/specs/trap.md §5.1
- */
-
-#include <arch/config.h>
-#include <arch/cpu.h>
-#include <lume/addr.h>
-#include <lume/config.h>
-#include <lume/fdt.h>
-#include <lume/platform_device.h>
-#include <lume/irq.h>
-#include <lume/types.h>
-#include <lume/vmm.h>
-
-/* PLIC virtual base (after MMIO mapping) */
-static uint64 plic_va;
-
-extern "C" uint32 bsp_hart_id;
-
-/* PLIC register offsets */
-static constexpr uint64 PLIC_PRIORITY_BASE = 0x000000;
-static constexpr uint64 PLIC_PENDING_BASE = 0x001000;
-static constexpr uint64 PLIC_ENABLE_BASE = 0x002000;
-static constexpr uint64 PLIC_THRESHOLD_BASE = 0x200000;
-static constexpr uint64 PLIC_CLAIM_BASE = 0x200004;
-
-/* Each context's enable/threshold/claim region is 0x80 bytes apart for enable,
- * and 0x1000 bytes apart for threshold/claim. */
-static constexpr uint64 PLIC_ENABLE_STRIDE = 0x80;
-static constexpr uint64 PLIC_CONTEXT_STRIDE = 0x1000;
-
-/* Dynamic S-mode context mapping */
-static uint32 g_s_context[kMaxCpus];
-
-static inline uint64 s_context(uint64 hart_id) {
- if (hart_id < kMaxCpus)
- return g_s_context[hart_id];
- return 2 * hart_id + 1; /* safe fallback */
-}
-
-static bool str_equals_plic(const char *a, const char *b) {
- if (!a || !b)
- return false;
- while (*a && *a == *b) {
- a++;
- b++;
- }
- return *a == *b;
-}
-
-/* Read a 32-bit MMIO register */
-static inline uint32 plic_read32(uint64 offset) {
- return *reinterpret_cast<volatile uint32 *>(plic_va + offset);
-}
-
-/* Write a 32-bit MMIO register */
-static inline void plic_write32(uint64 offset, uint32 val) {
- *reinterpret_cast<volatile uint32 *>(plic_va + offset) = val;
-}
-
-/* ========================================================================
- * Public API
- * ======================================================================== */
-
-static void plic_probe(DeviceNode* node) {
- uint64 plic_pa = 0;
-
- /* Initialize default fallback mapping */
- for (uint32 i = 0; i < kMaxCpus; i++) {
- g_s_context[i] = 2 * i + 1;
- }
-
- /* Dynamically discover PLIC base address and context mapping from FDT */
- if (node) {
- plic_pa = node->get_prop_u64("reg", plic_pa);
-
- /* Build phandle to hart_id map */
- struct PhandleMap {
- uint32 phandle;
- uint32 hart_id;
- } pmap[kMaxCpus];
- int num_cpus = 0;
-
- DeviceNode *cpus_node = fdt_get_node_by_path("/cpus");
- if (cpus_node) {
- list_node *cur = cpus_node->children_list_head();
- list_node *end = cpus_node->children_list_sentinel();
- while (cur != end && num_cpus < kMaxCpus) {
- DeviceNode *cpu =
- list_entry<DeviceNode, &DeviceNode::sibling_link_>(cur);
- cur = cur->next;
-
- const char *type = cpu->get_prop_string("device_type");
- if (type && str_equals_plic(type, "cpu")) {
- uint32 hart_id = cpu->get_prop_u32("reg");
- DeviceNode *intc = cpu->find_child("interrupt-controller");
- if (intc) {
- uint32 phandle = intc->get_prop_u32("phandle");
- if (phandle == 0)
- phandle = intc->get_prop_u32("linux,phandle");
- if (phandle != 0) {
- pmap[num_cpus].phandle = phandle;
- pmap[num_cpus].hart_id = hart_id;
- num_cpus++;
- }
- }
- }
- }
- }
-
- /* Parse interrupts-extended */
- const FdtProperty *ie_prop = node->get_prop("interrupts-extended");
- if (ie_prop && ie_prop->len > 0) {
- uint32 len = ie_prop->len;
- const uint8 *val = static_cast<const uint8 *>(ie_prop->value);
- int context_idx = 0;
- /* Each entry is 2 cells (8 bytes): [phandle, interrupt_type] */
- for (uint32 i = 0; i + 7 < len; i += 8) {
- uint32 phandle = (uint32(val[i]) << 24) | (uint32(val[i + 1]) << 16) |
- (uint32(val[i + 2]) << 8) | val[i + 3];
- uint32 irq_type = (uint32(val[i + 4]) << 24) |
- (uint32(val[i + 5]) << 16) |
- (uint32(val[i + 6]) << 8) | val[i + 7];
-
- if (irq_type == 9) { /* IRQ_S_EXTERNAL */
- for (int h = 0; h < num_cpus; h++) {
- if (pmap[h].phandle == phandle) {
- if (pmap[h].hart_id < kMaxCpus) {
- g_s_context[pmap[h].hart_id] = context_idx;
- }
- break;
- }
- }
- }
- context_idx++;
- }
- }
- }
-
- if (plic_pa == 0) return;
-
- /* Map PLIC MMIO */
- vmm_map_kernel_mmio(plic_pa, 0x4000000);
- plic_va = pa_to_va(phys_addr(plic_pa)).raw;
-
- /* Use the actual BSP hart ID (it may not be hart 0) */
- uint64 ctx = s_context(bsp_hart_id);
-
- /* Set priority for all sources to 1 (minimum non-zero = enabled) */
- for (uint32 irq = 1; irq < IrqManager::kMaxIrqs; irq++) {
- plic_write32(PLIC_PRIORITY_BASE + irq * 4, 1);
- }
-
- /* Set threshold to 0 for BSP context (accept all priorities > 0) */
- plic_write32(PLIC_THRESHOLD_BASE + ctx * PLIC_CONTEXT_STRIDE, 0);
-}
-
-static void plic_init_ap() {
- uint64 ctx = s_context(arch::cpu::id());
-
- /* Set threshold to 0 for this AP's context */
- plic_write32(PLIC_THRESHOLD_BASE + ctx * PLIC_CONTEXT_STRIDE, 0);
-}
-
-static void plic_enable(uint32 irq) {
- if (irq == 0 || irq >= IrqManager::kMaxIrqs) {
- return;
- }
-
- uint64 ctx = s_context(arch::cpu::id());
- uint64 offset = PLIC_ENABLE_BASE + ctx * PLIC_ENABLE_STRIDE;
- uint32 word_index = irq / 32;
- uint32 bit_index = irq % 32;
-
- uint32 val = plic_read32(offset + word_index * 4);
- val |= (1U << bit_index);
- plic_write32(offset + word_index * 4, val);
-}
-
-static void plic_disable(uint32 irq) {
- if (irq == 0 || irq >= IrqManager::kMaxIrqs) {
- return;
- }
-
- uint64 ctx = s_context(arch::cpu::id());
- uint64 offset = PLIC_ENABLE_BASE + ctx * PLIC_ENABLE_STRIDE;
- uint32 word_index = irq / 32;
- uint32 bit_index = irq % 32;
-
- uint32 val = plic_read32(offset + word_index * 4);
- val &= ~(1U << bit_index);
- plic_write32(offset + word_index * 4, val);
-}
-
-static void plic_set_priority(uint32 irq, uint32 priority) {
- if (irq == 0 || irq >= IrqManager::kMaxIrqs)
- return;
- plic_write32(PLIC_PRIORITY_BASE + (irq * 4), priority);
-}
-
-static uint32 plic_claim() {
- uint64 ctx = s_context(arch::cpu::id());
- return plic_read32(PLIC_CLAIM_BASE + ctx * PLIC_CONTEXT_STRIDE);
-}
-
-static void plic_complete(uint32 irq) {
- uint64 ctx = s_context(arch::cpu::id());
- plic_write32(PLIC_CLAIM_BASE + ctx * PLIC_CONTEXT_STRIDE, irq);
-}
-
-static const IrqChip plic_chip = {
- "riscv,plic0",
- plic_claim,
- plic_complete,
- plic_init_ap,
- plic_enable,
- plic_disable,
- plic_set_priority
-};
-
-static int plic_platform_probe(platform_device* pdev) {
- plic_probe(pdev->of_node);
- g_irq_manager.set_chip(&plic_chip);
- return 0;
-}
-
-static const of_device_id plic_of_match[] = {
- { .compatible = "riscv,plic0", .data = nullptr },
- { .compatible = nullptr, .data = nullptr }, /* sentinel */
-};
-
-platform_driver plic_driver = {
- .driver = { .name = "plic", .bus = nullptr,
- .probe = nullptr, .remove = nullptr,
- .bus_node = {}, .devices = {} },
- .probe = plic_platform_probe,
- .remove = nullptr,
- .of_match_table = plic_of_match,
-};
-
-PLATFORM_DRIVER(plic_driver);
diff --git a/drivers/tty/n_tty.cc b/drivers/tty/n_tty.cc
deleted file mode 100644
index 0e312b0..0000000
--- a/drivers/tty/n_tty.cc
+++ /dev/null
@@ -1,539 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * n_tty.cc — N_TTY line discipline
- *
- * Implements the standard terminal line discipline:
- *
- * Canonical mode (ICANON):
- * Input is buffered until newline/EOF. Backspace (VERASE) and
- * kill-line (VKILL) editing are supported.
- *
- * Raw mode (!ICANON):
- * Characters are available immediately (subject to VMIN/VTIME).
- *
- * Signal generation (ISIG):
- * VINTR → SIGINT, VQUIT → SIGQUIT, VSUSP → SIGTSTP
- *
- * Echo (ECHO):
- * Typed characters are reflected to output.
- *
- * Input processing (c_iflag):
- * ICRNL: CR → NL mapping
- * INLCR: NL → CR mapping
- * IGNCR: ignore CR
- * ISTRIP: strip high bit
- *
- * Output processing (c_oflag):
- * OPOST + ONLCR: NL → CR+NL on output
- *
- * Reference: Linux drivers/tty/n_tty.c
- */
-
-#include <lume/tty.h>
-#include <lume/tty_ioctl.h>
-#include <lume/signal.h>
-#include <lume/signal_nr.h>
-#include <lume/slab.h>
-#include <lume/new.h>
-#include <lume/errno.h>
-#include <lume/task.h>
-#include <lume/sched.h>
-
-#ifndef ENOIOCTLCMD
-#define ENOIOCTLCMD 515
-#endif
-
-extern void schedule();
-
-/* ================================================================== */
-/* N_TTY internal data */
-/* ================================================================== */
-
-inline constexpr int N_TTY_BUF_SIZE = 4096;
-
-struct n_tty_data {
- char read_buf[N_TTY_BUF_SIZE];
- int read_head; /* producer writes here (from receive_buf) */
- int read_tail; /* consumer reads from here (from read()) */
- int canon_head; /* in canonical mode: end of last complete line */
- bool eof_pending; /* Ctrl+D was pressed */
- Spinlock lock;
-};
-
-static inline int buf_count(const n_tty_data* d)
-{
- return (d->read_head - d->read_tail + N_TTY_BUF_SIZE) % N_TTY_BUF_SIZE;
-}
-
-static inline int canon_count(const n_tty_data* d)
-{
- return (d->canon_head - d->read_tail + N_TTY_BUF_SIZE) % N_TTY_BUF_SIZE;
-}
-
-static inline bool buf_full(const n_tty_data* d)
-{
- return buf_count(d) >= N_TTY_BUF_SIZE - 1;
-}
-
-static inline void buf_put(n_tty_data* d, char c)
-{
- d->read_buf[d->read_head] = c;
- d->read_head = (d->read_head + 1) % N_TTY_BUF_SIZE;
-}
-
-static inline char buf_get(n_tty_data* d)
-{
- char c = d->read_buf[d->read_tail];
- d->read_tail = (d->read_tail + 1) % N_TTY_BUF_SIZE;
- return c;
-}
-
-/* ================================================================== */
-/* Output helpers */
-/* ================================================================== */
-
-static void tty_put_char(tty_struct* tty, char c)
-{
- if (tty->driver && tty->driver->ops && tty->driver->ops->put_char) {
- tty->driver->ops->put_char(tty, c);
- }
-}
-
-/*
- * Process and output a single character, applying c_oflag transformations.
- */
-static void tty_output_char(tty_struct* tty, char c)
-{
- if (tty->termios_data.c_oflag & OPOST) {
- if (c == '\n' && (tty->termios_data.c_oflag & ONLCR)) {
- tty_put_char(tty, '\r');
- tty_put_char(tty, '\n');
- return;
- }
- if (c == '\r' && (tty->termios_data.c_oflag & OCRNL)) {
- tty_put_char(tty, '\n');
- return;
- }
- }
- tty_put_char(tty, c);
-}
-
-/*
- * Echo a character back to the terminal.
- * Control characters are displayed as ^X.
- */
-static void echo_char(tty_struct* tty, char c)
-{
- if (!(tty->termios_data.c_lflag & ECHO))
- return;
-
- if (c == '\n') {
- tty_output_char(tty, '\n');
- return;
- }
-
- if (c == '\t') {
- tty_put_char(tty, '\t');
- return;
- }
-
- /* Control characters: echo as ^X */
- if ((tty->termios_data.c_lflag & ECHOCTL) && c < 0x20 && c != '\t' && c != '\n') {
- tty_put_char(tty, '^');
- tty_put_char(tty, c + '@');
- return;
- }
-
- if (c == 0x7f) { /* DEL */
- tty_put_char(tty, '^');
- tty_put_char(tty, '?');
- return;
- }
-
- tty_output_char(tty, c);
-}
-
-/*
- * Erase one character visually (backspace + space + backspace).
- */
-static void erase_char(tty_struct* tty, char erased)
-{
- if (!(tty->termios_data.c_lflag & ECHO))
- return;
-
- if ((tty->termios_data.c_lflag & ECHOE)) {
- /* If the erased char was a control char displayed as ^X, erase 2 cols */
- if ((erased < 0x20 && erased != '\t' && erased != '\n') || erased == 0x7f) {
- tty_put_char(tty, '\b');
- tty_put_char(tty, ' ');
- tty_put_char(tty, '\b');
- tty_put_char(tty, '\b');
- tty_put_char(tty, ' ');
- tty_put_char(tty, '\b');
- } else {
- tty_put_char(tty, '\b');
- tty_put_char(tty, ' ');
- tty_put_char(tty, '\b');
- }
- }
-}
-
-/* ================================================================== */
-/* Input processing — receive_buf (called from IRQ or driver) */
-/* ================================================================== */
-
-static void n_tty_receive_buf(tty_struct* tty, const char* cp, int count)
-{
- auto* d = static_cast<n_tty_data*>(tty->ldisc.data);
- if (!d) return;
-
- const termios& t = tty->termios_data;
-
- LockGuard guard(d->lock);
-
- for (int i = 0; i < count; i++) {
- char c = cp[i];
-
- /* Strip high bit if requested */
- if (t.c_iflag & ISTRIP)
- c &= 0x7f;
-
- /* Input CR/NL processing */
- if (c == '\r') {
- if (t.c_iflag & IGNCR)
- continue;
- if (t.c_iflag & ICRNL)
- c = '\n';
- } else if (c == '\n') {
- if (t.c_iflag & INLCR)
- c = '\r';
- }
-
- /* Signal generation */
- if (t.c_lflag & ISIG) {
- if (c == t.c_cc[VINTR]) {
- tty_signal_pgrp(tty, SIGINT);
- echo_char(tty, c);
- if (!(t.c_lflag & NOFLSH)) {
- /* Flush input buffer */
- d->read_head = d->read_tail;
- d->canon_head = d->read_tail;
- }
- continue;
- }
- if (c == t.c_cc[VQUIT]) {
- tty_signal_pgrp(tty, SIGQUIT);
- echo_char(tty, c);
- if (!(t.c_lflag & NOFLSH)) {
- d->read_head = d->read_tail;
- d->canon_head = d->read_tail;
- }
- continue;
- }
- if (c == t.c_cc[VSUSP]) {
- tty_signal_pgrp(tty, SIGTSTP);
- echo_char(tty, c);
- continue;
- }
- }
-
- /* Canonical mode processing */
- if (t.c_lflag & ICANON) {
- /* EOF (Ctrl+D) */
- if (c == t.c_cc[VEOF]) {
- d->eof_pending = true;
- d->canon_head = d->read_head;
- tty->read_wait.wake_all();
- continue;
- }
-
- /* ERASE (backspace/DEL) */
- if (c == t.c_cc[VERASE]) {
- if (d->read_head != d->canon_head &&
- d->read_head != d->read_tail) {
- int prev = (d->read_head - 1 + N_TTY_BUF_SIZE) % N_TTY_BUF_SIZE;
- char erased = d->read_buf[prev];
- d->read_head = prev;
- erase_char(tty, erased);
- }
- continue;
- }
-
- /* KILL (Ctrl+U — erase entire line) */
- if (c == t.c_cc[VKILL]) {
- while (d->read_head != d->canon_head &&
- d->read_head != d->read_tail) {
- int prev = (d->read_head - 1 + N_TTY_BUF_SIZE) % N_TTY_BUF_SIZE;
- char erased = d->read_buf[prev];
- d->read_head = prev;
- erase_char(tty, erased);
- }
- if (t.c_lflag & ECHOK) {
- tty_output_char(tty, '\n');
- }
- continue;
- }
-
- /* WERASE (Ctrl+W — erase word) */
- if ((t.c_lflag & IEXTEN) && c == t.c_cc[VWERASE]) {
- /* Skip trailing spaces */
- while (d->read_head != d->canon_head) {
- int prev = (d->read_head - 1 + N_TTY_BUF_SIZE) % N_TTY_BUF_SIZE;
- if (d->read_buf[prev] != ' ' && d->read_buf[prev] != '\t')
- break;
- erase_char(tty, d->read_buf[prev]);
- d->read_head = prev;
- }
- /* Erase word */
- while (d->read_head != d->canon_head) {
- int prev = (d->read_head - 1 + N_TTY_BUF_SIZE) % N_TTY_BUF_SIZE;
- if (d->read_buf[prev] == ' ' || d->read_buf[prev] == '\t')
- break;
- erase_char(tty, d->read_buf[prev]);
- d->read_head = prev;
- }
- continue;
- }
-
- /* REPRINT (Ctrl+R) */
- if ((t.c_lflag & IEXTEN) && c == t.c_cc[VREPRINT]) {
- tty_output_char(tty, '\n');
- int pos = d->canon_head;
- while (pos != d->read_head) {
- echo_char(tty, d->read_buf[pos]);
- pos = (pos + 1) % N_TTY_BUF_SIZE;
- }
- continue;
- }
-
- /* Regular character — buffer it */
- if (!buf_full(d)) {
- buf_put(d, c);
- echo_char(tty, c);
- }
-
- /* Line complete on newline or EOL */
- if (c == '\n' || c == t.c_cc[VEOL] ||
- (t.c_cc[VEOL2] && c == t.c_cc[VEOL2])) {
- d->canon_head = d->read_head;
- tty->read_wait.wake_all();
- }
- } else {
- /* Raw / non-canonical mode */
- if (!buf_full(d)) {
- buf_put(d, c);
- echo_char(tty, c);
- tty->read_wait.wake_all();
- }
- }
- }
-}
-
-/* ================================================================== */
-/* N_TTY read — blocking read from the input buffer */
-/* ================================================================== */
-
-static int n_tty_read(tty_struct* tty, file* fp, char* buf, uint64 count)
-{
- auto* d = static_cast<n_tty_data*>(tty->ldisc.data);
- if (!d) return -EIO;
-
- const termios& t = tty->termios_data;
- TaskControlBlock* curr = current_task();
- int copied = 0;
-
- if (t.c_lflag & ICANON) {
- /* Canonical mode: block until a complete line is available */
- while (canon_count(d) == 0 && !d->eof_pending) {
- if (tty->flags & TTY_IO_ERROR)
- return -EIO;
-
- /* Check for pending signals — return -EINTR so the
- * trap return path can deliver them via do_signal() */
- if (signal_pending(curr))
- return -EINTR;
-
- tty->read_wait.prepare_to_wait(curr, TaskState::Sleeping);
- if (canon_count(d) == 0 && !d->eof_pending)
- schedule();
- tty->read_wait.finish_wait(curr);
- }
-
- if (d->eof_pending && canon_count(d) == 0) {
- d->eof_pending = false;
- return 0; /* EOF */
- }
-
- /* Copy up to one line */
- LockGuard guard(d->lock);
- while (copied < static_cast<int>(count) && d->read_tail != d->canon_head) {
- buf[copied++] = buf_get(d);
- }
- } else {
- /* Non-canonical mode */
- int vmin = t.c_cc[VMIN];
- /* VTIME not implemented (would need timers) */
-
- if (vmin == 0) vmin = 1;
-
- while (buf_count(d) < vmin) {
- if (tty->flags & TTY_IO_ERROR)
- return -EIO;
-
- if (signal_pending(curr))
- return -EINTR;
-
- tty->read_wait.prepare_to_wait(curr, TaskState::Sleeping);
- if (buf_count(d) < vmin)
- schedule();
- tty->read_wait.finish_wait(curr);
- }
-
- LockGuard guard(d->lock);
- while (copied < static_cast<int>(count) && buf_count(d) > 0) {
- buf[copied++] = buf_get(d);
- }
- }
-
- return copied;
-}
-
-/* ================================================================== */
-/* N_TTY write — output through the driver with post-processing */
-/* ================================================================== */
-
-static int n_tty_write(tty_struct* tty, file* fp, const char* buf, uint64 count)
-{
- if (!tty->driver || !tty->driver->ops || !tty->driver->ops->write)
- return -EIO;
-
- /* Apply output processing (OPOST) */
- if (tty->termios_data.c_oflag & OPOST) {
- for (uint64 i = 0; i < count; i++) {
- tty_output_char(tty, buf[i]);
- }
- return static_cast<int>(count);
- }
-
- /* Raw output: pass directly to driver */
- return tty->driver->ops->write(tty, buf, static_cast<int>(count));
-}
-
-/* ================================================================== */
-/* N_TTY ioctl */
-/* ================================================================== */
-
-static int n_tty_ioctl(tty_struct* tty, unsigned int cmd, uint64 arg)
-{
- auto* d = static_cast<n_tty_data*>(tty->ldisc.data);
-
- switch (cmd) {
- case FIONREAD: {
- int count = buf_count(d);
- auto* p = reinterpret_cast<int*>(arg);
- *p = count;
- return 0;
- }
- default:
- return -ENOIOCTLCMD;
- }
-}
-
-/* ================================================================== */
-/* N_TTY open / close */
-/* ================================================================== */
-
-static int n_tty_open(tty_struct* tty)
-{
- auto* d = new n_tty_data{};
- if (!d) return -ENOMEM;
-
- d->read_head = 0;
- d->read_tail = 0;
- d->canon_head = 0;
- d->eof_pending = false;
- d->lock.init("n_tty");
-
- tty->ldisc.data = d;
- return 0;
-}
-
-static void n_tty_close(tty_struct* tty)
-{
- auto* d = static_cast<n_tty_data*>(tty->ldisc.data);
- if (d) {
- delete d;
- tty->ldisc.data = nullptr;
- }
-}
-
-static void n_tty_flush_buffer(tty_struct* tty)
-{
- auto* d = static_cast<n_tty_data*>(tty->ldisc.data);
- if (!d) return;
- LockGuard guard(d->lock);
- d->read_head = 0;
- d->read_tail = 0;
- d->canon_head = 0;
- d->eof_pending = false;
-}
-
-static int n_tty_chars_in_buffer(tty_struct* tty)
-{
- auto* d = static_cast<n_tty_data*>(tty->ldisc.data);
- if (!d) return 0;
- return buf_count(d);
-}
-
-/*
- * Probe input readiness for poll(). In canonical mode the read syscall
- * blocks until a whole line is queued, so we mirror that: POLLIN only
- * when canon_count() shows a finalized line (or pending EOF). In raw
- * mode any character satisfies VMIN==1 immediately. Without this
- * mirror, ppoll() would falsely signal POLLIN on partial canonical
- * input, then read() would block — exactly the busy-loop the shell's
- * ppoll-then-read pattern is trying to avoid.
- */
-bool n_tty_input_ready(tty_struct* tty)
-{
- auto* d = static_cast<n_tty_data*>(tty->ldisc.data);
- if (!d) return false;
- const termios& t = tty->termios_data;
- if (t.c_lflag & ICANON)
- return canon_count(d) != 0 || d->eof_pending;
- int vmin = t.c_cc[VMIN];
- if (vmin == 0) vmin = 1;
- return buf_count(d) >= vmin;
-}
-
-static void n_tty_set_termios(tty_struct* /*tty*/, const termios* /*old*/)
-{
- /* Nothing special needed for now */
-}
-
-static void n_tty_hangup(tty_struct* tty)
-{
- n_tty_flush_buffer(tty);
- tty->read_wait.wake_all();
-}
-
-/* ================================================================== */
-/* N_TTY ops table */
-/* ================================================================== */
-
-const tty_ldisc_ops n_tty_ops = {
- .name = "n_tty",
- .num = 0, /* N_TTY */
- .open = n_tty_open,
- .close = n_tty_close,
- .read = n_tty_read,
- .write = n_tty_write,
- .receive_buf = n_tty_receive_buf,
- .ioctl = n_tty_ioctl,
- .flush_buffer = n_tty_flush_buffer,
- .chars_in_buffer = n_tty_chars_in_buffer,
- .set_termios = n_tty_set_termios,
- .hangup = n_tty_hangup,
-};
diff --git a/drivers/tty/tty_core.cc b/drivers/tty/tty_core.cc
deleted file mode 100644
index 5985bd9..0000000
--- a/drivers/tty/tty_core.cc
+++ /dev/null
@@ -1,536 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * tty_core.cc — TTY subsystem core
- *
- * Provides the tty_struct lifecycle, file_operations dispatch,
- * and ioctl routing between the line discipline and driver.
- *
- * Call chain:
- * sys_read → tty_fops.read → tty_read → ldisc.read
- * sys_write → tty_fops.write → tty_write → ldisc.write
- * sys_ioctl → tty_fops.ioctl → tty_ioctl → {core / ldisc / driver}
- */
-
-#include <lume/tty.h>
-#include <lume/tty_ioctl.h>
-#include <lume/file.h>
-#include <lume/fs_types.h>
-#include <lume/chardev.h>
-#include <lume/signal.h>
-#include <lume/signal_types.h>
-#include <lume/signal_nr.h>
-#include <lume/task.h>
-#include <lume/pgrp.h>
-#include <lume/poll.h>
-#include <lume/new.h>
-#include <lume/errno.h>
-#include <lume/klog.h>
-#include <lume/slab.h>
-
-#ifndef ENOIOCTLCMD
-#define ENOIOCTLCMD 515
-#endif
-
-/* ================================================================== */
-/* Global console TTY */
-/* ================================================================== */
-
-tty_struct* g_console_tty = nullptr;
-
-/* ================================================================== */
-/* tty_struct allocation */
-/* ================================================================== */
-
-tty_struct* tty_alloc(const tty_driver* driver, int index)
-{
- auto* tty = new tty_struct{};
- if (!tty) return nullptr;
-
- tty->index = index;
- tty->driver = driver;
- tty->link = nullptr;
- tty->pgrp = 0;
- tty->session = 0;
- tty->write_cnt = 0;
- tty->flags = 0;
- tty->open_count = 0;
- tty->driver_data = nullptr;
-
- tty_default_termios(&tty->termios_data);
-
- tty->ws.ws_row = 24;
- tty->ws.ws_col = 80;
- tty->ws.ws_xpixel = 0;
- tty->ws.ws_ypixel = 0;
-
- tty->lock.init("tty");
- tty->read_wait.init("tty_rd");
- tty->write_wait.init("tty_wr");
-
- /* Attach default line discipline (N_TTY) */
- tty->ldisc.ops = &n_tty_ops;
- tty->ldisc.data = nullptr;
-
- return tty;
-}
-
-/* ================================================================== */
-/* tty_open / tty_close */
-/* ================================================================== */
-
-int tty_open(tty_struct* tty)
-{
- if (!tty) return -EINVAL;
-
- if (tty->open_count == 0) {
- /* Initialize line discipline */
- if (tty->ldisc.ops && tty->ldisc.ops->open) {
- int ret = tty->ldisc.ops->open(tty);
- if (ret < 0) return ret;
- }
-
- /* Initialize driver */
- if (tty->driver && tty->driver->ops && tty->driver->ops->open) {
- int ret = tty->driver->ops->open(tty);
- if (ret < 0) {
- if (tty->ldisc.ops && tty->ldisc.ops->close)
- tty->ldisc.ops->close(tty);
- return ret;
- }
- }
- }
-
- tty->open_count++;
- return 0;
-}
-
-void tty_close(tty_struct* tty)
-{
- if (!tty) return;
-
- tty->open_count--;
- if (tty->open_count <= 0) {
- tty->open_count = 0;
-
- if (tty->driver && tty->driver->ops && tty->driver->ops->close)
- tty->driver->ops->close(tty);
-
- if (tty->ldisc.ops && tty->ldisc.ops->close)
- tty->ldisc.ops->close(tty);
- }
-}
-
-/* ================================================================== */
-/* Core I/O */
-/* ================================================================== */
-
-int64 tty_read(tty_struct* tty, file* fp, char* buf, uint64 count)
-{
- if (!tty || !tty->ldisc.ops || !tty->ldisc.ops->read)
- return -EIO;
-
- return tty->ldisc.ops->read(tty, fp, buf, count);
-}
-
-int64 tty_write(tty_struct* tty, file* fp, const char* buf, uint64 count)
-{
- if (!tty || !tty->ldisc.ops || !tty->ldisc.ops->write)
- return -EIO;
-
- return tty->ldisc.ops->write(tty, fp, buf, count);
-}
-
-/* ================================================================== */
-/* tty_receive_buf — called by driver on RX */
-/* ================================================================== */
-
-void tty_receive_buf(tty_struct* tty, const char* buf, int count)
-{
- if (!tty || !tty->ldisc.ops || !tty->ldisc.ops->receive_buf)
- return;
-
- tty->ldisc.ops->receive_buf(tty, buf, count);
-}
-
-/* ================================================================== */
-/* tty_signal_pgrp — send signal to foreground process group */
-/* ================================================================== */
-
-extern int send_signal(int sig, kernel_siginfo* info, TaskControlBlock* task);
-
-void tty_signal_pgrp(tty_struct* tty, int sig)
-{
- if (!tty) return;
-
- int32 pgid;
- {
- LockGuard guard(tty->lock);
- pgid = tty->pgrp;
- }
- if (pgid <= 0) return;
-
- /* Deliver to the leader of every thread group in the foreground
- * process group. ldisc paths call this for SIGINT/SIGQUIT/SIGWINCH
- * etc.; previously we only signalled the current task, which was
- * incorrect when the fg pgrp had multiple processes. */
- kernel_siginfo info{};
- info.si_signo = sig;
- info.si_code = 0; /* SI_KERNEL would be 0x80; user rarely checks */
- for_each_pgrp_member(pgid, [&](signal_struct* s) {
- if (s->leader)
- send_signal(sig, &info, s->leader);
- });
-}
-
-/* ================================================================== */
-/* tty_ioctl — ioctl dispatch */
-/* ================================================================== */
-
-long tty_ioctl(tty_struct* tty, file* /*fp*/, unsigned int cmd, uint64 arg)
-{
- if (!tty) return -EINVAL;
-
- switch (cmd) {
- case TCGETS: {
- auto* p = reinterpret_cast<termios*>(arg);
- if (!p) return -EINVAL;
- *p = tty->termios_data;
- return 0;
- }
-
- case TCSETS:
- case TCSETSW:
- case TCSETSF: {
- auto* p = reinterpret_cast<const termios*>(arg);
- if (!p) return -EINVAL;
- termios old = tty->termios_data;
- tty->termios_data = *p;
-
- /* Notify line discipline of termios change */
- if (tty->ldisc.ops && tty->ldisc.ops->set_termios)
- tty->ldisc.ops->set_termios(tty, &old);
-
- /* If TCSETSF, flush input buffer */
- if (cmd == TCSETSF) {
- if (tty->ldisc.ops && tty->ldisc.ops->flush_buffer)
- tty->ldisc.ops->flush_buffer(tty);
- }
- return 0;
- }
-
- case TIOCGWINSZ: {
- auto* p = reinterpret_cast<winsize*>(arg);
- if (!p) return -EINVAL;
- *p = tty->ws;
- return 0;
- }
-
- case TIOCSWINSZ: {
- auto* p = reinterpret_cast<const winsize*>(arg);
- if (!p) return -EINVAL;
- winsize old = tty->ws;
- tty->ws = *p;
- if (old.ws_row != tty->ws.ws_row || old.ws_col != tty->ws.ws_col) {
- tty_signal_pgrp(tty, SIGWINCH);
- }
- return 0;
- }
-
- case TIOCGPGRP: {
- auto* p = reinterpret_cast<int32*>(arg);
- if (!p) return -EINVAL;
-
- TaskControlBlock* curr = current_task();
- if (!curr || !curr->signal) return -ENOTTY;
- if (curr->signal->tty != tty) return -ENOTTY;
-
- int32 pgid;
- {
- LockGuard guard(tty->lock);
- pgid = tty->pgrp != 0 ? tty->pgrp : curr->signal->pgid;
- }
- *p = pgid;
- return 0;
- }
-
- case TIOCSPGRP: {
- auto* p = reinterpret_cast<const int32*>(arg);
- if (!p) return -EINVAL;
-
- TaskControlBlock* curr = current_task();
- if (!curr || !curr->signal) return -ENOTTY;
- if (curr->signal->tty != tty) return -ENOTTY;
-
- int32 new_pgid = *p;
- if (new_pgid <= 0) return -EINVAL;
-
- /* new_pgid must be in the caller's session. */
- bool ok = false;
- for_each_pgrp_member(new_pgid, [&](signal_struct* s) {
- if (s->sid == curr->signal->sid) ok = true;
- });
- if (!ok) return -EPERM;
-
- LockGuard guard(tty->lock);
- tty->pgrp = new_pgid;
- return 0;
- }
-
- case TIOCSCTTY: {
- /* Become the controlling terminal of the caller's session.
- * Linux: only session leader without ctty may do this; with
- * arg==1, CAP_SYS_ADMIN may steal a ctty from another session.
- * We support the basic path; "steal" is left unimplemented. */
- TaskControlBlock* curr = current_task();
- if (!curr || !curr->signal) return -EPERM;
- signal_struct* sig = curr->signal;
-
- if (sig->sid != curr->pid) return -EPERM; /* not leader */
-
- LockGuard sg(sig->lock);
- if (sig->tty != nullptr) return -EPERM; /* already have one */
-
- LockGuard tg(tty->lock);
- if (tty->session != 0 && tty->session != sig->sid) {
- return -EPERM; /* owned elsewhere */
- }
- tty->session = sig->sid;
- tty->pgrp = sig->pgid;
- sig->tty = tty;
- return 0;
- }
-
- case TIOCNOTTY: {
- /* Detach from controlling terminal. */
- TaskControlBlock* curr = current_task();
- if (!curr || !curr->signal) return -ENOTTY;
- signal_struct* sig = curr->signal;
- if (sig->tty != tty) return -ENOTTY;
-
- /* Session leader: HUP everyone left in the old fg pgrp.
- * Job-control conformance — without this, daemons spawned by
- * a relinquishing leader keep blocking on a dead terminal. */
- bool was_leader = (sig->sid == curr->pid);
- int32 hup_pgrp = 0;
- {
- LockGuard tg(tty->lock);
- hup_pgrp = tty->pgrp;
- if (was_leader) {
- tty->session = 0;
- tty->pgrp = 0;
- }
- }
- {
- LockGuard sg(sig->lock);
- sig->tty = nullptr;
- }
- if (was_leader && hup_pgrp > 0) {
- tty_signal_pgrp(tty, SIGHUP);
- tty_signal_pgrp(tty, SIGCONT);
- (void)hup_pgrp;
- }
- return 0;
- }
-
- case FIONREAD: {
- /* Delegate to line discipline */
- if (tty->ldisc.ops && tty->ldisc.ops->ioctl) {
- int ret = tty->ldisc.ops->ioctl(tty, cmd, arg);
- if (ret != -ENOIOCTLCMD)
- return ret;
- }
- return 0;
- }
-
- case TCFLSH: {
- int queue = static_cast<int>(arg);
- if (queue == 0 || queue == 2) {
- /* Flush input */
- if (tty->ldisc.ops && tty->ldisc.ops->flush_buffer)
- tty->ldisc.ops->flush_buffer(tty);
- }
- if (queue == 1 || queue == 2) {
- /* Flush output */
- if (tty->driver && tty->driver->ops && tty->driver->ops->flush_buffer)
- tty->driver->ops->flush_buffer(tty);
- }
- return 0;
- }
-
- default:
- break;
- }
-
- /* Try line discipline ioctl */
- if (tty->ldisc.ops && tty->ldisc.ops->ioctl) {
- int ret = tty->ldisc.ops->ioctl(tty, cmd, arg);
- if (ret != -ENOIOCTLCMD)
- return ret;
- }
-
- /* Try driver ioctl */
- if (tty->driver && tty->driver->ops && tty->driver->ops->ioctl) {
- int ret = tty->driver->ops->ioctl(tty, cmd, arg);
- if (ret != -ENOIOCTLCMD)
- return ret;
- }
-
- return -ENOTTY;
-}
-
-/* ================================================================== */
-/* file_operations for TTY devices */
-/* ================================================================== */
-
-static int64 tty_fops_read(file* fp, char* buf, uint64 count, int64* /*pos*/)
-{
- auto* tty = static_cast<tty_struct*>(fp->private_data);
- return tty_read(tty, fp, buf, count);
-}
-
-static int64 tty_fops_write(file* fp, const char* buf, uint64 count, int64* /*pos*/)
-{
- auto* tty = static_cast<tty_struct*>(fp->private_data);
- return tty_write(tty, fp, buf, count);
-}
-
-static long tty_fops_ioctl(file* fp, unsigned int cmd, uint64 arg)
-{
- auto* tty = static_cast<tty_struct*>(fp->private_data);
- return tty_ioctl(tty, fp, cmd, arg);
-}
-
-/*
- * tty poll callback — Linux drivers/tty/tty_io.c:tty_poll() does the
- * same thing: register tty->read_wait so the poller is woken on input,
- * then ask the ldisc whether data is currently available. Write side
- * is always considered ready since our writes don't block.
- */
-extern bool n_tty_input_ready(tty_struct* tty);
-extern void poll_wait(file*, WaitQueue*, poll_table*);
-
-static uint32 tty_fops_poll(file* fp, void* pt)
-{
- auto* tty = static_cast<tty_struct*>(fp->private_data);
- if (!tty) return POLLNVAL;
-
- poll_wait(fp, &tty->read_wait, static_cast<poll_table*>(pt));
-
- uint32 mask = POLLOUT | POLLWRNORM; /* always writable */
- if (n_tty_input_ready(tty))
- mask |= POLLIN | POLLRDNORM;
- return mask;
-}
-
-/*
- * Auto-acquire a controlling terminal on open.
- *
- * POSIX rule: when a session leader without a controlling tty opens a
- * terminal that itself has no controlling session, and the caller did
- * not pass O_NOCTTY, that terminal becomes the controlling terminal of
- * the caller's session. tty->session and tty->pgrp are seeded from the
- * caller's sid/pgid so that the very first tcgetpgrp() call returns a
- * value matching the caller's pgid — which is precisely what BusyBox
- * ash:setjobctl() needs to exit its setup loop.
- *
- * Reference: Linux drivers/tty/tty_jobctrl.c:tty_open_proc_set_tty()
- */
-static void tty_acquire_ctty(tty_struct* tty, file* fp)
-{
- TaskControlBlock* curr = current_task();
- if (!curr || !curr->signal) return;
- if (fp->f_flags & O_NOCTTY) return;
-
- signal_struct* sig = curr->signal;
-
- /* Must be session leader (sid == pid). */
- if (sig->sid != curr->pid) return;
-
- LockGuard sig_guard(sig->lock);
- if (sig->tty != nullptr) return; /* already have one */
-
- LockGuard tty_guard(tty->lock);
- if (tty->session != 0) return; /* tty owned by another sess */
-
- tty->session = sig->sid;
- tty->pgrp = sig->pgid;
- sig->tty = tty;
-}
-
-static int tty_fops_open(inode* /*ip*/, file* fp)
-{
- /* The console TTY is pre-allocated; just attach it */
- if (!g_console_tty) return -ENXIO;
- fp->private_data = g_console_tty;
- int err = tty_open(g_console_tty);
- if (err == 0) {
- tty_acquire_ctty(g_console_tty, fp);
- }
- return err;
-}
-
-static int tty_fops_release(inode* /*ip*/, file* fp)
-{
- auto* tty = static_cast<tty_struct*>(fp->private_data);
- tty_close(tty);
- return 0;
-}
-
-file_operations tty_fops;
-
-static void init_tty_fops()
-{
- tty_fops.read = tty_fops_read;
- tty_fops.write = tty_fops_write;
- tty_fops.unlocked_ioctl = tty_fops_ioctl;
- tty_fops.poll = tty_fops_poll;
- tty_fops.open = tty_fops_open;
- tty_fops.release = tty_fops_release;
-}
-
-/* ================================================================== */
-/* TTY chardev registration */
-/* ================================================================== */
-
-static cdev tty_cdev;
-
-void tty_init()
-{
- init_tty_fops();
-
- /* Allocate the console TTY */
- g_console_tty = tty_alloc(&uart_tty_driver, 0);
- if (!g_console_tty) {
- return;
- }
-
- /* Open the TTY (initializes ldisc + driver) */
- tty_open(g_console_tty);
-
- /* Leave tty->session and tty->pgrp at 0. The first session-leader
- * to open /dev/console without O_NOCTTY claims it as its controlling
- * terminal via tty_acquire_ctty(), which fills both fields. Pre-
- * seeding them here (as we used to with the hardcoded pid==1) would
- * make every subsequent acquire path see "owned by another session"
- * and bail out, leaving sh's tcgetpgrp() returning -ENOTTY and
- * disabling job control. */
-
- /* Initialize UART RX interrupts */
- uart_tty_init(g_console_tty);
-
- /* Register UART as a struct console driver.
- * After this, printk/klog output goes through console_write_all()
- * → uart_console_write() instead of raw early_putc(). */
- extern void uart_register_console();
- uart_register_console();
-
- /* Register as /dev/console chardev — replace the old console_cdev.
- * We use the same major/minor (5,1) so that existing opens
- * to /dev/console will get the TTY fops. */
- tty_cdev.name = "console";
- tty_cdev.dev = MKDEV(5, 1);
- tty_cdev.fops = &tty_fops;
- cdev_register(&tty_cdev);
-}
diff --git a/drivers/tty/tty_uart.cc b/drivers/tty/tty_uart.cc
deleted file mode 100644
index 973a6e9..0000000
--- a/drivers/tty/tty_uart.cc
+++ /dev/null
@@ -1,251 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * tty_uart.cc — UART → tty_driver adapter
- *
- * Bridges the 16550A UART hardware to the TTY subsystem.
- *
- * TX: Calls early_putc() for each character (same as before).
- * RX: Registers a PLIC IRQ handler. On UART RX interrupt,
- * reads available bytes and feeds them to the line discipline
- * via tty_receive_buf().
- *
- * QEMU virt UART:
- * - IRQ 10 on the PLIC
- * - 16550A register layout at the base address discovered from FDT
- */
-
-#include <lume/tty.h>
-#include <lume/irq.h>
-#include <lume/addr.h>
-#include <lume/fdt.h>
-#include <lume/vmm.h>
-#include <lume/errno.h>
-#include <lume/klog.h>
-
-#ifndef ENOIOCTLCMD
-#define ENOIOCTLCMD 515
-#endif
-
-extern "C" void early_putc(char c);
-
-/* 16550A register offsets */
-static constexpr int UART_RBR = 0; /* Receive Buffer Register */
-static constexpr int UART_THR = 0; /* Transmit Holding Register */
-static constexpr int UART_IER = 1; /* Interrupt Enable Register */
-static constexpr int UART_IIR = 2; /* Interrupt Identification Register */
-static constexpr int UART_FCR = 2; /* FIFO Control Register */
-static constexpr int UART_LCR = 3; /* Line Control Register */
-static constexpr int UART_MCR = 4; /* Modem Control Register */
-static constexpr int UART_LSR = 5; /* Line Status Register */
-
-/* LSR bits */
-static constexpr uint8 UART_LSR_DR = 0x01; /* Data Ready */
-static constexpr uint8 UART_LSR_THRE = 0x20; /* TX Holding Register Empty */
-
-/* IER bits */
-static constexpr uint8 UART_IER_RDI = 0x01; /* Received Data Available */
-
-/* MCR bits */
-static constexpr uint8 UART_MCR_DTR = 0x01; /* Data Terminal Ready */
-static constexpr uint8 UART_MCR_RTS = 0x02; /* Request To Send */
-static constexpr uint8 UART_MCR_OUT2 = 0x08; /* Auxiliary output 2 — gates the
- IRQ pin on PC-style 16550A.
- Without this the UART
- internally asserts but does
- NOT propagate to the PLIC. */
-
-/* QEMU virt default UART IRQ */
-static constexpr uint32 UART_IRQ = 10;
-
-/* UART MMIO base (virtual address) */
-static volatile uint8* g_uart_base;
-static tty_struct* g_uart_tty;
-
-/* ================================================================== */
-/* UART MMIO helpers */
-/* ================================================================== */
-
-static inline uint8 uart_read(int reg)
-{
- return g_uart_base[reg];
-}
-
-static inline void uart_write(int reg, uint8 val)
-{
- g_uart_base[reg] = val;
-}
-
-static inline bool uart_rx_ready()
-{
- return (uart_read(UART_LSR) & UART_LSR_DR) != 0;
-}
-
-static inline char uart_rx_char()
-{
- return static_cast<char>(uart_read(UART_RBR));
-}
-
-/* ================================================================== */
-/* UART RX interrupt handler */
-/* ================================================================== */
-
-static void uart_irq_handler(uint32 /*irq*/, void* arg)
-{
- auto* tty = static_cast<tty_struct*>(arg);
-
- /* Read all available bytes from the FIFO */
- char buf[16];
- int count = 0;
-
- while (uart_rx_ready() && count < 16) {
- buf[count++] = uart_rx_char();
- }
-
- if (count > 0) {
- tty_receive_buf(tty, buf, count);
- }
-}
-
-/* ================================================================== */
-/* tty_driver_ops for UART */
-/* ================================================================== */
-
-static int uart_tty_open_op(tty_struct* /*tty*/)
-{
- return 0;
-}
-
-static void uart_tty_close_op(tty_struct* /*tty*/)
-{
- /* Nothing to do */
-}
-
-static int uart_tty_write_op(tty_struct* /*tty*/, const char* buf, int count)
-{
- for (int i = 0; i < count; i++) {
- early_putc(buf[i]);
- }
- return count;
-}
-
-static int uart_tty_write_room_op(tty_struct* /*tty*/)
-{
- return TTY_BUF_SIZE; /* Always room (blocking putc) */
-}
-
-static void uart_tty_put_char_op(tty_struct* /*tty*/, char ch)
-{
- early_putc(ch);
-}
-
-static void uart_tty_flush_chars_op(tty_struct* /*tty*/)
-{
- /* No buffering in early_putc, nothing to flush */
-}
-
-static int uart_tty_chars_in_buffer_op(tty_struct* /*tty*/)
-{
- return 0; /* No TX buffer */
-}
-
-static void uart_tty_flush_buffer_op(tty_struct* /*tty*/)
-{
- /* No TX buffer to flush */
-}
-
-static int uart_tty_ioctl_op(tty_struct* /*tty*/, unsigned int /*cmd*/, uint64 /*arg*/)
-{
- return -ENOIOCTLCMD;
-}
-
-static void uart_tty_hangup_op(tty_struct* /*tty*/)
-{
- /* Disable UART RX interrupts */
- if (g_uart_base) {
- uart_write(UART_IER, 0);
- }
-}
-
-static const tty_driver_ops uart_ops = {
- .open = uart_tty_open_op,
- .close = uart_tty_close_op,
- .write = uart_tty_write_op,
- .write_room = uart_tty_write_room_op,
- .put_char = uart_tty_put_char_op,
- .flush_chars = uart_tty_flush_chars_op,
- .chars_in_buffer = uart_tty_chars_in_buffer_op,
- .flush_buffer = uart_tty_flush_buffer_op,
- .ioctl = uart_tty_ioctl_op,
- .throttle = nullptr,
- .unthrottle = nullptr,
- .hangup = uart_tty_hangup_op,
-};
-
-const tty_driver uart_tty_driver = {
- .name = "ttyS",
- .major = 4,
- .minor_start = 64,
- .num = 1,
- .ops = &uart_ops,
-};
-
-/* ================================================================== */
-/* UART TTY init — discover base address, enable RX IRQ */
-/* ================================================================== */
-
-void uart_tty_init(tty_struct* tty)
-{
- g_uart_tty = tty;
-
- /* Discover UART base from FDT */
- DeviceNode* node = fdt_find_compatible("ns16550a");
- if (!node) {
- /* Try alternative compatible string */
- node = fdt_find_compatible("snps,dw-apb-uart");
- }
-
- uint64 uart_pa = 0;
- if (node) {
- uart_pa = node->get_prop_u64("reg", 0);
- }
-
- if (uart_pa == 0) {
- /* Fallback: QEMU virt default */
- uart_pa = 0x10000000;
- }
-
- g_uart_base = reinterpret_cast<volatile uint8*>(
- pa_to_va(phys_addr(uart_pa)).raw);
-
- /* Set FIFO control: enable FIFOs, clear RX/TX FIFOs, 1-byte trigger */
- uart_write(UART_FCR, 0x07);
-
- /* MCR: assert OUT2 (gates the IRQ pin on PC-style 16550A), plus
- * DTR/RTS for completeness. Mirrors Linux drivers/tty/serial/8250/
- * 8250_port.c: serial8250_do_startup() does the same. */
- uart_write(UART_MCR, UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2);
-
- /* Enable UART receive-data-available interrupt */
- uart_write(UART_IER, UART_IER_RDI);
-
- /* Register IRQ handler with PLIC */
- uint32 irq = UART_IRQ;
- if (node) {
- /* Try to get IRQ number from FDT */
- const FdtProperty* irq_prop = node->get_prop("interrupts");
- if (irq_prop && irq_prop->len >= 4) {
- const uint8* val = static_cast<const uint8*>(irq_prop->value);
- irq = (uint32(val[0]) << 24) | (uint32(val[1]) << 16) |
- (uint32(val[2]) << 8) | val[3];
- }
- }
-
- g_irq_manager.register_handler(irq, uart_irq_handler, tty);
- g_irq_manager.enable(irq);
-
- printk(KERN_INFO "uart_tty_init: base=0x%llx irq=%u IER=0x%x LSR=0x%x\n",
- (unsigned long long)uart_pa, irq,
- uart_read(UART_IER), uart_read(UART_LSR));
-}
diff --git a/drivers/uart.cc b/drivers/uart.cc
deleted file mode 100644
index 467b496..0000000
--- a/drivers/uart.cc
+++ /dev/null
@@ -1,181 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * uart.cc — 16550a UART driver + console registry
- *
- * Two roles:
- * 1. Early console: early_putc() for pre-init output (panic, kprintf)
- * 2. Runtime console: struct console registered with register_console().
- * klog/printk route through console_write_all() → uart_console_write().
- *
- * The console registry is a simple linked list of struct console.
- * console_write_all() iterates the list and calls each console's write().
- * If no console is registered, it falls back to early_putc().
- *
- * Reference: Linux kernel/printk/printk.c, drivers/tty/serial/8250/
- */
-
-#include <lume/console.h>
-#include <lume/fdt.h>
-#include <lume/vmm.h>
-#include <lume/addr.h>
-#include <arch/config.h>
-
-/* ================================================================== */
-/* UART state */
-/* ================================================================== */
-
-static uint64 g_uart_pa = 0;
-static uint64 g_uart_size = 0;
-static bool g_runtime_mapping_ready = false;
-static bool g_mmu_enabled = false;
-
-/* ================================================================== */
-/* Early console (Phase 1) */
-/* ================================================================== */
-
-extern "C" void console_set_mmu_enabled() {
- g_mmu_enabled = true;
-}
-
-extern "C" void early_putc(char c) {
- if (!g_uart_pa) return;
-
- /*
- * Before vmm_init() activates the final page table, UART MMIO is only
- * reachable via the bootstrap fixed direct map from entry.S.
- * After activation, switch to the runtime (possibly KASLR-slid) base.
- */
- uint64 uart_va = 0;
- if (g_runtime_mapping_ready) {
- uart_va = pa_to_va(phys_addr(g_uart_pa)).raw;
- } else if (!g_mmu_enabled) {
- uart_va = g_uart_pa;
- } else {
- uart_va = boot_pa_to_va(phys_addr(g_uart_pa)).raw;
- }
-
- volatile auto* uart = reinterpret_cast<volatile uint8*>(uart_va);
- *uart = static_cast<uint8>(c);
-}
-
-extern "C" void early_puts(const char* s) {
- while (*s) early_putc(*s++);
-}
-
-void console_early_init(uint64 fdt_paddr) {
- uint64 pa = 0;
- uint64 size = 0;
- FdtManager::early_scan_uart(fdt_paddr, &pa, &size);
- if (pa != 0) {
- g_uart_pa = pa;
- if (size != 0) g_uart_size = size;
- }
-}
-
-void console_init() {
- /*
- * Once vmm_init() activates the new page table, the 1GB identity map is dropped.
- * The UART must be explicitly registered into the active page table.
- */
- if (g_uart_pa != 0 && g_uart_size != 0) {
- vmm_map_kernel_mmio(g_uart_pa, g_uart_size);
- }
-}
-
-void console_use_runtime_mapping() {
- g_runtime_mapping_ready = true;
-}
-
-/* ================================================================== */
-/* Console registry (Phase 2) — linked list of struct console */
-/* ================================================================== */
-
-static list_node g_console_list;
-static bool g_console_list_init = false;
-
-void register_console(console* con)
-{
- if (!con) return;
-
- if (!g_console_list_init) {
- g_console_list.init();
- g_console_list_init = true;
- }
-
- con->flags |= CON_ENABLED;
- con->list.init();
-
- /* Insert at tail */
- list_add_tail(&con->list, &g_console_list);
-}
-
-void unregister_console(console* con)
-{
- if (!con) return;
-
- list_del_init(&con->list);
- con->flags &= ~CON_ENABLED;
-}
-
-bool console_drivers_registered()
-{
- if (!g_console_list_init) return false;
- return !list_empty(&g_console_list);
-}
-
-void console_write_all(const char* s, int count)
-{
- if (g_console_list_init && !list_empty(&g_console_list)) {
- list_node* pos;
- list_for_each(pos, &g_console_list) {
- auto* con = container_of(pos, console, list);
- if ((con->flags & CON_ENABLED) && con->write) {
- con->write(con, s, static_cast<unsigned int>(count));
- }
- }
- } else {
- /* Fallback: direct early_putc (pre-registration) */
- for (int i = 0; i < count; i++) {
- early_putc(s[i]);
- }
- }
-}
-
-/* ================================================================== */
-/* UART console driver */
-/* ================================================================== */
-
-static void uart_console_write(console* /*con*/, const char* s, unsigned int count)
-{
- for (unsigned int i = 0; i < count; i++) {
- if (s[i] == '\n') {
- early_putc('\r');
- }
- early_putc(s[i]);
- }
-}
-
-/*
- * uart_console must be initialized at RUNTIME, not with static
- * aggregate initializers. Reason: KASLR self-relocation processes
- * .rela.dyn entries for simple pointer variables, but may not cover
- * function pointer fields inside static aggregate initializers.
- * A static `{ .write = uart_console_write }` would store the
- * pre-relocation address, causing a crash when called.
- */
-static console uart_console;
-
-void uart_register_console()
-{
- uart_console.name = "ttyS";
- uart_console.write = uart_console_write;
- uart_console.setup = nullptr;
- uart_console.flags = CON_ENABLED | CON_BOOT;
- uart_console.index = 0;
- uart_console.data = nullptr;
- uart_console.list.init();
- register_console(&uart_console);
-}
-