diff options
| author | Richard Qin <richard.qin.x@gmail.com> | 2026-07-10 11:31:46 +0800 |
|---|---|---|
| committer | Richard Qin <richard.qin.x@gmail.com> | 2026-07-10 11:31:46 +0800 |
| commit | e90b72ec3082588b296956730bd2930cf8d5d671 (patch) | |
| tree | 36ff6916af9b4176ea5a9662219d11e721f0b691 /na/drivers | |
| parent | 1767d390fbf3d0c65389de46caf0b6d43ef3b6ad (diff) | |
| download | lume-e90b72ec3082588b296956730bd2930cf8d5d671.tar.gz lume-e90b72ec3082588b296956730bd2930cf8d5d671.tar.xz | |
refactor: promote new architecture to repository root
Archive the deprecated implementation under lume-legacy, make the new kernel the default root-level build, and update repository entry points without changing kernel behavior.
Diffstat (limited to 'na/drivers')
| -rw-r--r-- | na/drivers/base/bus.cc | 32 | ||||
| -rw-r--r-- | na/drivers/base/core.cc | 156 | ||||
| -rw-r--r-- | na/drivers/base/driver.cc | 72 | ||||
| -rw-r--r-- | na/drivers/base/module.mk | 9 | ||||
| -rw-r--r-- | na/drivers/base/platform.cc | 231 | ||||
| -rw-r--r-- | na/drivers/console.cc | 119 | ||||
| -rw-r--r-- | na/drivers/module.mk | 11 | ||||
| -rw-r--r-- | na/drivers/plic.cc | 259 | ||||
| -rw-r--r-- | na/drivers/tty/module.mk | 5 | ||||
| -rw-r--r-- | na/drivers/tty/n_tty.cc | 539 | ||||
| -rw-r--r-- | na/drivers/tty/tty_core.cc | 536 | ||||
| -rw-r--r-- | na/drivers/tty/tty_uart.cc | 251 | ||||
| -rw-r--r-- | na/drivers/uart.cc | 181 |
13 files changed, 0 insertions, 2401 deletions
diff --git a/na/drivers/base/bus.cc b/na/drivers/base/bus.cc deleted file mode 100644 index 975f676..0000000 --- a/na/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/na/drivers/base/core.cc b/na/drivers/base/core.cc deleted file mode 100644 index f92b2a4..0000000 --- a/na/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/na/drivers/base/driver.cc b/na/drivers/base/driver.cc deleted file mode 100644 index ce15b6d..0000000 --- a/na/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/na/drivers/base/module.mk b/na/drivers/base/module.mk deleted file mode 100644 index 1d8fb05..0000000 --- a/na/drivers/base/module.mk +++ /dev/null @@ -1,9 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-or-later -# Copyright (C) 2026 Richard Qin -# -# Driver model core (mirrors Linux drivers/base/) - -SRCS_CC += drivers/base/bus.cc -SRCS_CC += drivers/base/core.cc -SRCS_CC += drivers/base/driver.cc -SRCS_CC += drivers/base/platform.cc diff --git a/na/drivers/base/platform.cc b/na/drivers/base/platform.cc deleted file mode 100644 index 65ec21e..0000000 --- a/na/drivers/base/platform.cc +++ /dev/null @@ -1,231 +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 char* node_compat = pdev->of_node->get_prop_string("compatible"); - if (node_compat == nullptr) { - return 0; - } - for (const of_device_id* id = pdrv->of_match_table; - id->compatible != nullptr; ++id) { - if (strcmp(id->compatible, node_compat) == 0) { - return 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/na/drivers/console.cc b/na/drivers/console.cc deleted file mode 100644 index f741dd2..0000000 --- a/na/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/na/drivers/module.mk b/na/drivers/module.mk deleted file mode 100644 index 9326f30..0000000 --- a/na/drivers/module.mk +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-or-later -# Copyright (C) 2026 Richard Qin -# -# Drivers module - -SRCS_CC += drivers/uart.cc -SRCS_CC += drivers/console.cc - -ifeq ($(ARCH),riscv) -SRCS_CC += drivers/plic.cc -endif diff --git a/na/drivers/plic.cc b/na/drivers/plic.cc deleted file mode 100644 index 2818700..0000000 --- a/na/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/na/drivers/tty/module.mk b/na/drivers/tty/module.mk deleted file mode 100644 index 49190d4..0000000 --- a/na/drivers/tty/module.mk +++ /dev/null @@ -1,5 +0,0 @@ -# drivers/tty/module.mk — TTY subsystem build rules - -SRCS_CC += drivers/tty/tty_core.cc -SRCS_CC += drivers/tty/n_tty.cc -SRCS_CC += drivers/tty/tty_uart.cc diff --git a/na/drivers/tty/n_tty.cc b/na/drivers/tty/n_tty.cc deleted file mode 100644 index 0e312b0..0000000 --- a/na/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/na/drivers/tty/tty_core.cc b/na/drivers/tty/tty_core.cc deleted file mode 100644 index 5985bd9..0000000 --- a/na/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/na/drivers/tty/tty_uart.cc b/na/drivers/tty/tty_uart.cc deleted file mode 100644 index 973a6e9..0000000 --- a/na/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/na/drivers/uart.cc b/na/drivers/uart.cc deleted file mode 100644 index 467b496..0000000 --- a/na/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); -} - |
