summaryrefslogtreecommitdiff
path: root/drivers/base/core.cc
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/base/core.cc')
-rw-r--r--drivers/base/core.cc156
1 files changed, 156 insertions, 0 deletions
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);
+}