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 /drivers/base | |
| 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 'drivers/base')
| -rw-r--r-- | drivers/base/bus.cc | 32 | ||||
| -rw-r--r-- | drivers/base/core.cc | 156 | ||||
| -rw-r--r-- | drivers/base/driver.cc | 72 | ||||
| -rw-r--r-- | drivers/base/module.mk | 9 | ||||
| -rw-r--r-- | drivers/base/platform.cc | 231 |
5 files changed, 500 insertions, 0 deletions
diff --git a/drivers/base/bus.cc b/drivers/base/bus.cc new file mode 100644 index 0000000..975f676 --- /dev/null +++ b/drivers/base/bus.cc @@ -0,0 +1,32 @@ +/* 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 new file mode 100644 index 0000000..f92b2a4 --- /dev/null +++ b/drivers/base/core.cc @@ -0,0 +1,156 @@ +/* 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 new file mode 100644 index 0000000..ce15b6d --- /dev/null +++ b/drivers/base/driver.cc @@ -0,0 +1,72 @@ +/* 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/module.mk b/drivers/base/module.mk new file mode 100644 index 0000000..1d8fb05 --- /dev/null +++ b/drivers/base/module.mk @@ -0,0 +1,9 @@ +# 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/drivers/base/platform.cc b/drivers/base/platform.cc new file mode 100644 index 0000000..65ec21e --- /dev/null +++ b/drivers/base/platform.cc @@ -0,0 +1,231 @@ +/* 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"); +} |
