Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFC] device: allow deferred initialization #64869

Closed
wants to merge 9 commits into from
13 changes: 9 additions & 4 deletions doc/build/dts/macros.bnf
Original file line number Diff line number Diff line change
Expand Up @@ -315,18 +315,23 @@ prop-suf = 1*( "_" gen-name ["_" dt-name] )
other-macro = %s"DT_N_" alternate-id
; Total count of enabled instances of a compatible.
other-macro =/ %s"DT_N_INST_" dt-name %s"_NUM_OKAY"
; These are used internally by DT_FOREACH_NODE and
; DT_FOREACH_STATUS_OKAY_NODE respectively.
; These are used internally by DT_FOREACH_NODE,
; DT_FOREACH_STATUS_OKAY_NODE and DT_FOREACH_STATUS_DISABLED_NODE
; respectively.
other-macro =/ %s"DT_FOREACH_HELPER"
other-macro =/ %s"DT_FOREACH_OKAY_HELPER"
other-macro =/ %s"DT_FOREACH_DISABLED_HELPER"
; These are used internally by DT_FOREACH_STATUS_OKAY,
; which iterates over each enabled node of a compatible.
other-macro =/ %s"DT_FOREACH_OKAY_" dt-name
other-macro =/ %s"DT_FOREACH_OKAY_VARGS_" dt-name
; These are used internally by DT_INST_FOREACH_STATUS_OKAY,
; which iterates over each enabled instance of a compatible.
; These are used internally by DT_INST_FOREACH_STATUS_OKAY and
; DT_INST_FOREACH_STATUS_DISABLED which iterates over each
; enabled/disabled instance of a compatible.
other-macro =/ %s"DT_FOREACH_OKAY_INST_" dt-name
other-macro =/ %s"DT_FOREACH_OKAY_INST_VARGS_" dt-name
other-macro =/ %s"DT_FOREACH_DISABLED_INST_" dt-name
other-macro =/ %s"DT_FOREACH_DISABLED_INST_VARGS_" dt-name
; E.g.: #define DT_CHOSEN_zephyr_flash
other-macro =/ %s"DT_CHOSEN_" dt-name
; Declares that a compatible has at least one node on a bus.
Expand Down
6 changes: 1 addition & 5 deletions drivers/sensor/lsm6dso/lsm6dso.c
Original file line number Diff line number Diff line change
Expand Up @@ -871,10 +871,6 @@ static int lsm6dso_init(const struct device *dev)
return 0;
}

#if DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) == 0
#warning "LSM6DSO driver enabled without any devices"
#endif

/*
* Device creation macro, shared by LSM6DSO_DEFINE_SPI() and
* LSM6DSO_DEFINE_I2C().
Expand Down Expand Up @@ -958,4 +954,4 @@ static int lsm6dso_init(const struct device *dev)
(LSM6DSO_CONFIG_I2C(inst))); \
LSM6DSO_DEVICE_INIT(inst)

DT_INST_FOREACH_STATUS_OKAY(LSM6DSO_DEFINE)
DEVICE_DT_INST_FOREACH_ENABLED(LSM6DSO_DEFINE)
4 changes: 4 additions & 0 deletions dts/bindings/base/base.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,7 @@ properties:
mbox-names:
type: string-array
description: Provided names of mailbox / IPM channel specifiers

zephyr,deferred-init:
type: boolean
description: Indicate that the device should be initialized later
90 changes: 77 additions & 13 deletions include/zephyr/device.h
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ typedef int16_t device_handle_t;
*/
#define DEVICE_DEFINE(dev_id, name, init_fn, pm, data, config, level, prio, \
api) \
Z_DEVICE_STATE_DEFINE(dev_id); \
Z_DEVICE_STATE_DEFINE(DT_INVALID_NODE, dev_id); \
Z_DEVICE_DEFINE(DT_INVALID_NODE, dev_id, name, init_fn, pm, data, \
config, level, prio, api, \
&Z_DEVICE_STATE_NAME(dev_id))
Expand Down Expand Up @@ -177,7 +177,7 @@ typedef int16_t device_handle_t;
*/
#define DEVICE_DT_DEFINE(node_id, init_fn, pm, data, config, level, prio, api, \
...) \
Z_DEVICE_STATE_DEFINE(Z_DEVICE_DT_DEV_ID(node_id)); \
Z_DEVICE_STATE_DEFINE(node_id, Z_DEVICE_DT_DEV_ID(node_id)); \
Z_DEVICE_DEFINE(node_id, Z_DEVICE_DT_DEV_ID(node_id), \
DEVICE_DT_NAME(node_id), init_fn, pm, data, config, \
level, prio, api, \
Expand Down Expand Up @@ -343,6 +343,29 @@ typedef int16_t device_handle_t;
*/
#define DEVICE_INIT_GET(dev_id) (&Z_INIT_ENTRY_NAME(DEVICE_NAME_GET(dev_id)))

/**
* @brief Invoke @p fn if device is flagged with deferred initialization
*
* @param inst Device instance number
* @param fn Function to invoke
*/
#define Z_DEVICE_DT_INST_DEFERRED_CALL(inst, fn) \
IF_ENABLED(DT_INST_PROP(inst, zephyr_deferred_init), (fn(inst)))

/**
* @brief Invoke @p fn for each enabled device instance in devicetree.
*
* Enabled devices include those with status `okay` or `disabled` with
* `zephyr,deferred-init` flag.
*
* @param fn Function to invoke
*/
#define DEVICE_DT_INST_FOREACH_ENABLED(fn) \
DT_INST_FOREACH_STATUS_OKAY(fn) \
IF_ENABLED(CONFIG_DEVICE_ALLOW_DEFERRED, \
(DT_INST_FOREACH_STATUS_DISABLED_VARGS( \
Z_DEVICE_DT_INST_DEFERRED_CALL, fn)))

/**
* @brief Runtime device dynamic structure (in RAM) per driver instance
*
Expand Down Expand Up @@ -407,6 +430,10 @@ struct device {
*/
struct pm_device *pm;
#endif
#if defined(CONFIG_DEVICE_ALLOW_DEFERRED) || defined(__DOXYGEN__)
/** Device init function */
int (*init_fn)(const struct device *dev);
#endif /* CONFIG_DEVICE_ALLOW_DEFERRED */
};

/**
Expand Down Expand Up @@ -746,6 +773,18 @@ static inline bool z_impl_device_is_ready(const struct device *dev)
return z_device_is_ready(dev);
}

/**
* @brief Initialize a device.
*
* @param dev Device instance.
*
* @retval 0 If successful.
* @retval -ENOSYS If device deferred initialization is not supported.
* @retval -EALREADY If device has been already initialized.
* @retval -errno In case of any other initialization error.
*/
__syscall int device_init(const struct device *dev);

/**
* @}
*/
Expand All @@ -758,14 +797,23 @@ static inline bool z_impl_device_is_ready(const struct device *dev)
*/
#define Z_DEVICE_STATE_NAME(dev_id) _CONCAT(__devstate_, dev_id)

/** Flag to indicate device wants deferred initialization */
#define Z_DEVICE_DEFERRED 255U

/**
* @brief Utility macro to define and initialize the device state.
*
* @param node_id Devicetree node identifier.
* @param dev_id Device identifier.
*/
#define Z_DEVICE_STATE_DEFINE(dev_id) \
#define Z_DEVICE_STATE_DEFINE(node_id, dev_id) \
static Z_DECL_ALIGN(struct device_state) Z_DEVICE_STATE_NAME(dev_id) \
__attribute__((__section__(".z_devstate")))
__attribute__((__section__(".z_devstate"))) \
IF_ENABLED(CONFIG_DEVICE_ALLOW_DEFERRED, ( \
= { .init_res = COND_CODE_1( \
DT_PROP(node_id, zephyr_deferred_init), \
(Z_DEVICE_DEFERRED), (0U)) \
}))

#if defined(CONFIG_DEVICE_DEPS) || defined(__DOXYGEN__)

Expand Down Expand Up @@ -887,8 +935,10 @@ static inline bool z_impl_device_is_ready(const struct device *dev)
* @param api_ Reference to device API ops.
* @param state_ Reference to device state.
* @param deps_ Reference to device dependencies.
* @param init_ Reference to device init function.
*/
#define Z_DEVICE_INIT(name_, pm_, data_, config_, api_, state_, deps_) \
#define Z_DEVICE_INIT(name_, init_fn_, pm_, data_, config_, api_, state_, \
deps_) \
{ \
.name = name_, \
.config = (config_), \
Expand All @@ -897,6 +947,8 @@ static inline bool z_impl_device_is_ready(const struct device *dev)
.data = (data_), \
IF_ENABLED(CONFIG_DEVICE_DEPS, (.deps = (deps_),)) /**/ \
IF_ENABLED(CONFIG_PM_DEVICE, (.pm = (pm_),)) /**/ \
IF_ENABLED(CONFIG_DEVICE_ALLOW_DEFERRED, \
(.init_fn = (init_fn_),)) /**/ \
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is probably ugly, but you could save this pointer if device_init loops through the device initialisers and runs it when there's a match on the device pointer.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's definitely another option, O(N) though, while now it's O(1) at the cost of an extra pointer in every device. In any case, it should end up O(1) if we manage to succeed with #61986

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm in favor of avoiding the extra pointer. The number of devices that an app will "defer init" will typically be small, and the performance hit happens once.

}

/**
Expand Down Expand Up @@ -924,13 +976,13 @@ static inline bool z_impl_device_is_ready(const struct device *dev)
* @param api Reference to device API.
* @param ... Optional dependencies, manually specified.
*/
#define Z_DEVICE_BASE_DEFINE(node_id, dev_id, name, pm, data, config, level, \
prio, api, state, deps) \
#define Z_DEVICE_BASE_DEFINE(node_id, dev_id, name, init_fn, pm, data, config, \
level, prio, api, state, deps) \
COND_CODE_1(DT_NODE_EXISTS(node_id), (), (static)) \
const STRUCT_SECTION_ITERABLE_NAMED(device, \
Z_DEVICE_SECTION_NAME(level, prio), \
DEVICE_NAME_GET(dev_id)) = \
Z_DEVICE_INIT(name, pm, data, config, api, state, deps)
Z_DEVICE_INIT(name, init_fn, pm, data, config, api, state, deps)

/* deprecated device initialization levels */
#define Z_DEVICE_LEVEL_DEPRECATED_EARLY \
Expand Down Expand Up @@ -1000,26 +1052,38 @@ static inline bool z_impl_device_is_ready(const struct device *dev)
IF_ENABLED(CONFIG_DEVICE_DEPS, \
(Z_DEVICE_DEPS_DEFINE(node_id, dev_id, __VA_ARGS__);)) \
\
Z_DEVICE_BASE_DEFINE(node_id, dev_id, name, pm, data, config, level, \
prio, api, state, Z_DEVICE_DEPS_NAME(dev_id)); \
Z_DEVICE_BASE_DEFINE(node_id, dev_id, name, init_fn, pm, data, config, \
level, prio, api, state, \
Z_DEVICE_DEPS_NAME(dev_id)); \
\
Z_DEVICE_INIT_ENTRY_DEFINE(node_id, dev_id, init_fn, level, prio)

/**
* @brief Declare a device for each status "okay" devicetree node.
*
* @note Disabled nodes should not result in devices, so not predeclaring these
* keeps drivers honest.
*
* This is only "maybe" a device because some nodes have status "okay", but
* don't have a corresponding @ref device allocated. There's no way to figure
* that out until after we've built the zephyr image, though.
*/
#define Z_MAYBE_DEVICE_DECLARE_INTERNAL(node_id) \
extern const struct device DEVICE_DT_NAME_GET(node_id);

/**
* @brief Declare a device for each status "disabled" + "zephyr,deferred-init"
* devicetree node.
*
* @see Z_MAYBE_DEVICE_DECLARE_INTERNAL
*/
#define Z_MAYBE_DEVICE_DECLARE_DEFERRED_INTERNAL(node_id) \
IF_ENABLED(DT_PROP(node_id, zephyr_deferred_init), \
(Z_MAYBE_DEVICE_DECLARE_INTERNAL(node_id)))

DT_FOREACH_STATUS_OKAY_NODE(Z_MAYBE_DEVICE_DECLARE_INTERNAL)

#ifdef CONFIG_DEVICE_ALLOW_DEFERRED
DT_FOREACH_STATUS_DISABLED_NODE(Z_MAYBE_DEVICE_DECLARE_DEFERRED_INTERNAL)
#endif /* CONFIG_DEVICE_ALLOW_DEFERRED */

/** @endcond */

#ifdef __cplusplus
Expand Down
90 changes: 90 additions & 0 deletions include/zephyr/devicetree.h
Original file line number Diff line number Diff line change
Expand Up @@ -2569,6 +2569,18 @@
*/
#define DT_FOREACH_STATUS_OKAY_NODE(fn) DT_FOREACH_OKAY_HELPER(fn)

/**
* @brief Invokes @p fn for every status `disabled` node in the tree.
*
* The macro @p fn must take one parameter, which will be a node
* identifier. The macro is expanded once for each node in the tree
* with status `disabled`. The order that nodes are visited in is not
* specified.
*
* @param fn macro to invoke
*/
#define DT_FOREACH_STATUS_DISABLED_NODE(fn) DT_FOREACH_DISABLED_HELPER(fn)

/**
* @brief Invokes @p fn for every status `okay` node in the tree with multiple
* arguments.
Expand Down Expand Up @@ -4170,6 +4182,84 @@
DT_DRV_COMPAT)(fn, __VA_ARGS__)), \
())

/**
* @brief Call @p fn on all nodes with compatible `DT_DRV_COMPAT`
* and status `disabled`
*
* This macro calls `fn(inst)` on each `inst` number that refers to a
* node with status `disabled`. Whitespace is added between invocations.
*
* Example devicetree fragment:
*
* @code{.dts}
* a {
* compatible = "vnd,device";
* status = "disabled";
* foobar = "DEV_A";
* };
*
* b {
* compatible = "vnd,device";
* status = "disabled";
* foobar = "DEV_B";
* };
*
* c {
* compatible = "vnd,device";
* status = "okay";
* foobar = "DEV_C";
* };
* @endcode
*
* Example usage:
*
* @code{.c}
* #define DT_DRV_COMPAT vnd_device
* #define MY_FN(inst) DT_INST_PROP(inst, foobar),
*
* DT_INST_FOREACH_STATUS_DISABLED(MY_FN)
* @endcode
*
* This expands to:
*
* @code{.c}
* MY_FN(0) MY_FN(1)
* @endcode
*
* and from there, to either this:
*
* "DEV_A", "DEV_B",
*
* or this:
*
* "DEV_B", "DEV_A",
*
* No guarantees are made about the order that a and b appear in the
* expansion.
*
* Note that @p fn is responsible for adding commas, semicolons, or
* other separators or terminators.
*
* @param fn Macro to call for each disabled node. Must accept an
* instance number as its only parameter.
*/
#define DT_INST_FOREACH_STATUS_DISABLED(fn) \
UTIL_CAT(DT_FOREACH_DISABLED_INST_, DT_DRV_COMPAT)(fn)

/**
* @brief Call @p fn on all nodes with compatible `DT_DRV_COMPAT`
* and status `disabled` with multiple arguments
*
*
* @param fn Macro to call for each disabled node.
* @param ... variable number of arguments to pass to @p fn
*
* @see DT_INST_FOREACH_STATUS_DISABLED
*/
#define DT_INST_FOREACH_STATUS_DISABLED_VARGS(fn, ...) \
UTIL_CAT(DT_FOREACH_DISABLED_INST_VARGS_, DT_DRV_COMPAT) \
(fn, __VA_ARGS__)

/**
* @brief Invokes @p fn for each element of property @p prop for
* a `DT_DRV_COMPAT` instance.
Expand Down
7 changes: 7 additions & 0 deletions kernel/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -1250,6 +1250,13 @@ config DEVICE_DEPS_DYNAMIC
Option that makes it possible to manipulate device dependencies at
runtime.

config DEVICE_ALLOW_DEFERRED
bool "Allow deferred device initialization"
default y
help
When enabled, devices can be initialized at a later time by calling
device_init().

endmenu

rsource "Kconfig.vm"
Loading
Loading