From b29abe37104e04dae7ca08e03d2fbeddb4d8e4c7 Mon Sep 17 00:00:00 2001 From: Peter Bigot Date: Mon, 22 Feb 2021 11:42:08 -0600 Subject: [PATCH] device: add API to visit required devices The static device dependencies from devicetree are not the only ones that might be present at runtime. Add API that allows visiting required devices without assuming that handles for or pointers to them can be accessed as a static contiguous sequence. Signed-off-by: Peter Bigot --- include/device.h | 55 +++++++++++++++++++++++++ kernel/device.c | 22 ++++++++++ tests/lib/devicetree/devices/src/main.c | 55 +++++++++++++++++++++++++ 3 files changed, 132 insertions(+) diff --git a/include/device.h b/include/device.h index f2424a3fe32..432e107e2da 100644 --- a/include/device.h +++ b/include/device.h @@ -465,6 +465,24 @@ device_from_handle(device_handle_t dev_handle) return dev; } +/** + * @brief Prototype for functions used when iterating over a set of devices. + * + * Such a function may be used in API that identifies a set of devices and + * provides a visitor API supporting caller-specific interaction with each + * device in the set. + * + * The visit is said to succeed if the visitor returns a non-negative value. + * + * @param dev a device in the set being iterated + * + * @param context state used to support the visitor function + * + * @return A non-negative number to allow walking to continue, and a negative + * error code to case the iteration to stop. + */ +typedef int (*device_visitor_callback_t)(const struct device *dev, void *context); + /** * @brief Get the set of handles for devicetree dependencies of this device. * @@ -498,6 +516,43 @@ device_required_handles_get(const struct device *dev, return rv; } +/** + * @brief Visit every device that @p dev directly requires. + * + * Zephyr maintains information about which devices are directly required by + * another device; for example an I2C-based sensor driver will require an I2C + * controller for communication. Required devices can derive from + * statically-defined devicetree relationships or dependencies registered + * at runtime. + * + * This API supports operating on the set of required devices. Example uses + * include making sure required devices are ready before the requiring device + * is used, and releasing them when the requiring device is no longer needed. + * + * There is no guarantee on the order in which required devices are visited. + * + * If the @p visitor function returns a negative value iteration is halted, + * and the returned value from the visitor is returned from this function. + * + * @note This API is not available to unprivileged threads. + * + * @param dev a device of interest. The devices that this device depends on + * will be used as the set of devices to visit. This parameter must not be + * null. + * + * @param visitor_cb the function that should be invoked on each device in the + * dependency set. This parameter must not be null. + * + * @param context state that is passed through to the visitor function. This + * parameter may be null if @p visitor tolerates a null @p context. + * + * @return The number of devices that were visited if all visits succeed, or + * the negative value returned from the first visit that did not succeed. + */ +int device_required_foreach(const struct device *dev, + device_visitor_callback_t visitor_cb, + void *context); + /** * @brief Retrieve the device structure for a driver by name * diff --git a/kernel/device.c b/kernel/device.c index b561b9745c1..695bb7f9622 100644 --- a/kernel/device.c +++ b/kernel/device.c @@ -162,6 +162,28 @@ bool z_device_ready(const struct device *dev) return dev->state->initialized && (dev->state->init_res == 0); } +int device_required_foreach(const struct device *dev, + device_visitor_callback_t visitor_cb, + void *context) +{ + size_t handle_count = 0; + const device_handle_t *handles = + device_required_handles_get(dev, &handle_count); + + /* Iterate over fixed devices */ + for (size_t i = 0; i < handle_count; ++i) { + device_handle_t dh = handles[i]; + const struct device *rdev = device_from_handle(dh); + int rc = visitor_cb(rdev, context); + + if (rc < 0) { + return rc; + } + } + + return handle_count; +} + #ifdef CONFIG_PM_DEVICE int device_pm_control_nop(const struct device *unused_device, uint32_t unused_ctrl_command, diff --git a/tests/lib/devicetree/devices/src/main.c b/tests/lib/devicetree/devices/src/main.c index 766918f2463..68b14556914 100644 --- a/tests/lib/devicetree/devices/src/main.c +++ b/tests/lib/devicetree/devices/src/main.c @@ -74,23 +74,51 @@ static bool check_handle(device_handle_t hdl, return false; } +struct requires_context { + uint8_t ndevs; + const struct device *rdevs[2]; +}; + +static int requires_visitor(const struct device *dev, + void *context) +{ + struct requires_context *ctx = context; + const struct device **rdp = ctx->rdevs; + + while (rdp < (ctx->rdevs + ctx->ndevs)) { + if (*rdp == NULL) { + *rdp = dev; + return 0; + } + + ++rdp; + } + + return -ENOSPC; +} + static void test_requires(void) { size_t nhdls = 0; const device_handle_t *hdls; const struct device *dev; + struct requires_context ctx = { 0 }; /* TEST_GPIO: no req */ dev = device_get_binding(DT_LABEL(TEST_GPIO)); zassert_equal(dev, DEVICE_DT_GET(TEST_GPIO), NULL); hdls = device_required_handles_get(dev, &nhdls); zassert_equal(nhdls, 0, NULL); + zassert_equal(0, device_required_foreach(dev, requires_visitor, &ctx), + NULL); /* TEST_I2C: no req */ dev = device_get_binding(DT_LABEL(TEST_I2C)); zassert_equal(dev, DEVICE_DT_GET(TEST_I2C), NULL); hdls = device_required_handles_get(dev, &nhdls); zassert_equal(nhdls, 0, NULL); + zassert_equal(0, device_required_foreach(dev, requires_visitor, &ctx), + NULL); /* TEST_DEVA: TEST_I2C GPIO */ dev = device_get_binding(DT_LABEL(TEST_DEVA)); @@ -100,12 +128,39 @@ static void test_requires(void) zassert_true(check_handle(DEV_HDL(TEST_I2C), hdls, nhdls), NULL); zassert_true(check_handle(DEV_HDL(TEST_GPIO), hdls, nhdls), NULL); + /* Visit fails if not enough space */ + ctx = (struct requires_context){ + .ndevs = 1, + }; + zassert_equal(-ENOSPC, device_required_foreach(dev, requires_visitor, &ctx), + NULL); + + /* Visit succeeds if enough space. */ + ctx = (struct requires_context){ + .ndevs = 2, + }; + zassert_equal(2, device_required_foreach(dev, requires_visitor, &ctx), + NULL); + zassert_true((ctx.rdevs[0] == device_from_handle(DEV_HDL(TEST_I2C))) + || (ctx.rdevs[1] == device_from_handle(DEV_HDL(TEST_I2C))), + NULL); + zassert_true((ctx.rdevs[0] == device_from_handle(DEV_HDL(TEST_GPIO))) + || (ctx.rdevs[1] == device_from_handle(DEV_HDL(TEST_GPIO))), + NULL); + /* TEST_GPIOX: TEST_I2C */ dev = device_get_binding(DT_LABEL(TEST_GPIOX)); zassert_equal(dev, DEVICE_DT_GET(TEST_GPIOX), NULL); hdls = device_required_handles_get(dev, &nhdls); zassert_equal(nhdls, 1, NULL); zassert_true(check_handle(DEV_HDL(TEST_I2C), hdls, nhdls), NULL); + ctx = (struct requires_context){ + .ndevs = 3, + }; + zassert_equal(1, device_required_foreach(dev, requires_visitor, &ctx), + NULL); + zassert_true(ctx.rdevs[0] == device_from_handle(DEV_HDL(TEST_I2C)), + NULL); /* TEST_DEVB: TEST_I2C TEST_GPIOX */ dev = device_get_binding(DT_LABEL(TEST_DEVB));