| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * KUnit basic device implementation |
| * |
| * Implementation of struct kunit_device helpers. |
| * |
| * Copyright (C) 2023, Google LLC. |
| * Author: David Gow <davidgow@google.com> |
| */ |
| |
| #include <linux/device.h> |
| |
| #include <kunit/test.h> |
| #include <kunit/resource.h> |
| |
| static struct device kunit_bus = { |
| .init_name = "kunit" |
| }; |
| |
| /* A device owned by a KUnit test. */ |
| struct kunit_device { |
| struct device dev; |
| struct kunit *owner; |
| /* Force binding to a specific driver. */ |
| struct device_driver *driver; |
| }; |
| |
| static inline struct kunit_device *to_kunit_device(struct device *d) |
| { |
| return container_of(d, struct kunit_device, dev); |
| } |
| |
| static int kunit_bus_match(struct device *dev, struct device_driver *driver) |
| { |
| struct kunit_device *kunit_dev = to_kunit_device(dev); |
| |
| if (kunit_dev->driver == driver) |
| return 1; |
| |
| return 0; |
| } |
| |
| static struct bus_type kunit_bus_type = { |
| .name = "kunit", |
| .match = kunit_bus_match |
| }; |
| |
| int kunit_bus_init(void) |
| { |
| int error; |
| |
| error = bus_register(&kunit_bus_type); |
| if (!error) { |
| error = device_register(&kunit_bus); |
| if (error) |
| bus_unregister(&kunit_bus_type); |
| } |
| return error; |
| } |
| late_initcall(kunit_bus_init); |
| |
| static void kunit_device_release(struct device *d) |
| { |
| kfree(to_kunit_device(d)); |
| } |
| |
| struct device_driver *kunit_driver_create(struct kunit *test, const char *name) |
| { |
| struct device_driver *driver; |
| int err = -ENOMEM; |
| |
| driver = kunit_kzalloc(test, sizeof(*driver), GFP_KERNEL); |
| |
| if (!driver) |
| return ERR_PTR(err); |
| |
| driver->name = name; |
| driver->bus = &kunit_bus_type; |
| /* TODO: Make this point to the individual test module, not KUnit itself. */ |
| driver->owner = THIS_MODULE; |
| /* TODO: mod_name */ |
| |
| err = driver_register(driver); |
| if (err) { |
| kunit_kfree(test, driver); |
| return ERR_PTR(err); |
| } |
| |
| kunit_defer(test, (kunit_defer_function_t)driver_unregister, driver, GFP_KERNEL); |
| return driver; |
| } |
| |
| struct device *kunit_device_register_with_driver(struct kunit *test, const char *name, struct device_driver *drv) |
| { |
| struct kunit_device *kunit_dev; |
| int err = -ENOMEM; |
| |
| kunit_dev = kzalloc(sizeof(struct kunit_device), GFP_KERNEL); |
| if (!kunit_dev) |
| return ERR_PTR(err); |
| |
| kunit_dev->owner = test; |
| |
| err = dev_set_name(&kunit_dev->dev, "%s.%s", test->name, name); |
| if (err) { |
| kfree(kunit_dev); |
| return ERR_PTR(err); |
| } |
| |
| /* Set the expected driver pointer, so we match. */ |
| kunit_dev->driver = drv; |
| |
| kunit_dev->dev.release = kunit_device_release; |
| kunit_dev->dev.bus = &kunit_bus_type; |
| kunit_dev->dev.parent = &kunit_bus; |
| |
| err = device_register(&kunit_dev->dev); |
| if (err) { |
| put_device(&kunit_dev->dev); |
| return ERR_PTR(err); |
| } |
| |
| kunit_defer(test, (kunit_defer_function_t)device_unregister, &kunit_dev->dev, GFP_KERNEL); |
| |
| return &kunit_dev->dev; |
| } |
| EXPORT_SYMBOL_GPL(kunit_device_register_with_driver); |
| |
| struct device *kunit_device_register(struct kunit *test, const char *name) |
| { |
| struct device_driver *drv = kunit_driver_create(test, name); |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drv); |
| return kunit_device_register_with_driver(test, name, drv); |
| } |
| EXPORT_SYMBOL_GPL(kunit_device_register); |
| |
| void kunit_device_unregister(struct kunit *test, struct device *dev) |
| { |
| kunit_defer_trigger(test, (kunit_defer_function_t)device_unregister, dev); |
| } |
| EXPORT_SYMBOL_GPL(kunit_device_unregister); |
| |