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));