Skip to content

Commit

Permalink
kernel: add CONFIG_DEVICE_DT_METADATA and helpers
Browse files Browse the repository at this point in the history
This option allows you to look up a struct device from any of the
node labels that were attached to the devicetree node used to create
the device, etc.

This is helpful because node labels are a much more human-friendly set
of unique identifiers than the node names we are currently relying on
for use with device_get_binding(). Adding this infrastructure in the
device core allows anyone to make use of it without having to
replicate node label storage and search functions in various places in
the tree. The main use case, however, is for looking up devices by
node label in the shell.

Since there is a footprint penalty associated with storing the node
label metadata, leave this option disabled by default.

Signed-off-by: Martí Bolívar <[email protected]>
  • Loading branch information
mbolivar-ampere authored and aescolar committed Jun 12, 2024
1 parent 74abb2b commit 15c9d37
Show file tree
Hide file tree
Showing 3 changed files with 190 additions and 20 deletions.
151 changes: 131 additions & 20 deletions include/zephyr/device.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ extern "C" {
* @brief Device Model
* @defgroup device_model Device Model
* @since 1.0
* @version 1.0.0
* @version 1.1.0
* @{
*/

Expand Down Expand Up @@ -387,6 +387,9 @@ struct device_state {
struct pm_device_base;
struct pm_device;
struct pm_device_isr;
#if defined(CONFIG_DEVICE_DT_METADATA) || defined(__DOXYGEN__)
struct device_dt_metadata;
#endif

#ifdef CONFIG_DEVICE_DEPS_DYNAMIC
#define Z_DEVICE_DEPS_CONST
Expand Down Expand Up @@ -434,6 +437,9 @@ struct device {
struct pm_device_isr *pm_isr;
};
#endif
#if defined(CONFIG_DEVICE_DT_METADATA) || defined(__DOXYGEN__)
const struct device_dt_metadata *dt_meta;
#endif /* CONFIG_DEVICE_DT_METADATA */
};

/**
Expand Down Expand Up @@ -923,6 +929,104 @@ __syscall int device_init(const struct device *dev);

#endif /* CONFIG_PM_POLICY_DEVICE_CONSTRAINTS */

#if defined(CONFIG_DEVICE_DT_METADATA) || defined(__DOXYGEN__)
/**
* @brief Devicetree node labels associated with a device
*/
struct device_dt_nodelabels {
/* @brief number of elements in the nodelabels array */
size_t num_nodelabels;
/* @brief array of node labels as strings, exactly as they
* appear in the final devicetree
*/
const char *nodelabels[];
};

/**
* @brief Devicetree metadata associated with a device
*
* This is currently limited to node labels, but the structure is
* generic enough to be extended later without requiring breaking
* changes.
*/
struct device_dt_metadata {
/**
* @brief Node labels associated with the device
* @see device_get_dt_nodelabels()
*/
const struct device_dt_nodelabels *nl;
};

/**
* @brief Get a @ref device reference from a devicetree node label.
*
* If:
*
* 1. a device was defined from a devicetree node, for example
* with DEVICE_DT_DEFINE() or another similar macro, and
* 2. that devicetree node has @p nodelabel as one of its node labels, and
* 3. the device initialized successfully at boot time,
*
* then this function returns a pointer to the device. Otherwise, it
* returns NULL.
*
* @param nodelabel a devicetree node label
* @return a device reference for a device created from a node with that
* node label, or NULL if either no such device exists or the device
* failed to initialize
*/
__syscall const struct device *device_get_by_dt_nodelabel(const char *nodelabel);

/**
* @brief Get the devicetree node labels associated with a device
* @param dev device whose metadata to look up
* @return information about the devicetree node labels
*/
static inline const struct device_dt_nodelabels *
device_get_dt_nodelabels(const struct device *dev)
{
return dev->dt_meta->nl;
}

/**
* @brief Maximum devicetree node label length.
*
* The maximum length is set so that device_get_by_dt_nodelabel() can
* be used from userspace.
*/
#define Z_DEVICE_MAX_NODELABEL_LEN Z_DEVICE_MAX_NAME_LEN

/**
* @brief Name of the identifier for a device's DT metadata structure
* @param dev_id device identifier
*/
#define Z_DEVICE_DT_METADATA_NAME_GET(dev_id) UTIL_CAT(__dev_dt_meta_, dev_id)

/**
* @brief Name of the identifier for the array of node label strings
* saved for a device.
*/
#define Z_DEVICE_DT_NODELABELS_NAME_GET(dev_id) UTIL_CAT(__dev_dt_nodelabels_, dev_id)

/**
* @brief Initialize an entry in the device DT node label lookup table
*
* Allocates and initializes a struct device_dt_metadata in the
* appropriate iterable section for use finding devices.
*/
#define Z_DEVICE_DT_METADATA_DEFINE(node_id, dev_id) \
static const struct device_dt_nodelabels \
Z_DEVICE_DT_NODELABELS_NAME_GET(dev_id) = { \
.num_nodelabels = DT_NUM_NODELABELS(node_id), \
.nodelabels = DT_NODELABEL_STRING_ARRAY(node_id), \
}; \
\
static const struct device_dt_metadata \
Z_DEVICE_DT_METADATA_NAME_GET(dev_id) = { \
.nl = &Z_DEVICE_DT_NODELABELS_NAME_GET(dev_id), \
};
#endif /* CONFIG_DEVICE_DT_METADATA */

/**
* @brief Init sub-priority of the device
*
Expand Down Expand Up @@ -961,21 +1065,24 @@ __syscall int device_init(const struct device *dev);
* @param api_ Reference to device API ops.
* @param state_ Reference to device state.
* @param deps_ Reference to device dependencies.
* @param dev_id_ Device identifier token, as passed to Z_DEVICE_BASE_DEFINE
*/
#define Z_DEVICE_INIT(name_, pm_, data_, config_, api_, state_, deps_, \
constraints_size_, constraints_) \
{ \
.name = name_, \
.config = (config_), \
.api = (api_), \
.state = (state_), \
.data = (data_), \
IF_ENABLED(CONFIG_DEVICE_DEPS, (.deps = (deps_),)) /**/ \
IF_ENABLED(CONFIG_PM_POLICY_DEVICE_CONSTRAINTS, \
(.pm_constraints = (constraints_),)) \
IF_ENABLED(CONFIG_PM_POLICY_DEVICE_CONSTRAINTS, \
(.pm_constraints_size = (constraints_size_),)) \
IF_ENABLED(CONFIG_PM_DEVICE, ({ .pm_base = (pm_),})) /**/ \
#define Z_DEVICE_INIT(name_, pm_, data_, config_, api_, state_, deps_, \
constraints_size_, constraints_, dev_id_) \
{ \
.name = name_, \
.config = (config_), \
.api = (api_), \
.state = (state_), \
.data = (data_), \
IF_ENABLED(CONFIG_DEVICE_DEPS, (.deps = (deps_),)) /**/ \
IF_ENABLED(CONFIG_PM_POLICY_DEVICE_CONSTRAINTS, \
(.pm_constraints = (constraints_),)) \
IF_ENABLED(CONFIG_PM_POLICY_DEVICE_CONSTRAINTS, \
(.pm_constraints_size = (constraints_size_),)) \
IF_ENABLED(CONFIG_PM_DEVICE, ({ .pm_base = (pm_),})) /**/ \
IF_ENABLED(CONFIG_DEVICE_DT_METADATA, \
(.dt_meta = &Z_DEVICE_DT_METADATA_NAME_GET(dev_id_),)) \
}

/**
Expand Down Expand Up @@ -1011,7 +1118,7 @@ __syscall int device_init(const struct device *dev);
device, COND_CODE_1(Z_DEVICE_IS_MUTABLE(node_id), (device_mutable), (device)), \
Z_DEVICE_SECTION_NAME(level, prio), DEVICE_NAME_GET(dev_id)) = \
Z_DEVICE_INIT(name, pm, data, config, api, state, deps, \
DT_PROP_LEN_OR(node_id, zephyr_disabling_power_states, 0), constraints)
DT_PROP_LEN_OR(node_id, zephyr_disabling_power_states, 0), constraints, dev_id)

/* deprecated device initialization levels */
#define Z_DEVICE_LEVEL_DEPRECATED_EARLY \
Expand Down Expand Up @@ -1098,13 +1205,17 @@ __syscall int device_init(const struct device *dev);
\
IF_ENABLED(CONFIG_PM_POLICY_DEVICE_CONSTRAINTS, \
(Z_DEVICE_PM_CONSTRAINTS_DEFINE(node_id, dev_id, __VA_ARGS__);))\
\
IF_ENABLED(CONFIG_DEVICE_DT_METADATA, \
(Z_DEVICE_DT_METADATA_DEFINE(node_id, dev_id);)) \
\
Z_DEVICE_BASE_DEFINE(node_id, dev_id, name, pm, data, config, level, \
prio, api, state, Z_DEVICE_DEPS_NAME(dev_id), \
Z_DEVICE_PM_CONSTRAINTS_NAME(dev_id)); \
COND_CODE_1(DEVICE_DT_DEFER(node_id), \
(Z_DEFER_DEVICE_INIT_ENTRY_DEFINE(node_id, dev_id, \
init_fn)), \
(Z_DEVICE_INIT_ENTRY_DEFINE(node_id, dev_id, init_fn, \
COND_CODE_1(DEVICE_DT_DEFER(node_id), \
(Z_DEFER_DEVICE_INIT_ENTRY_DEFINE(node_id, dev_id, \
init_fn)), \
(Z_DEVICE_INIT_ENTRY_DEFINE(node_id, dev_id, init_fn, \
level, prio)));

/**
Expand Down
7 changes: 7 additions & 0 deletions kernel/Kconfig.device
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ config DEVICE_MUTABLE
Support mutable devices. Mutable devices are instantiated in SRAM
instead of Flash and are runtime modifiable in kernel mode.

config DEVICE_DT_METADATA
bool "Store additional devicetree metadata for each device"
help
If enabled, additional data from the devicetree will be stored for
each device. This allows you to use device_get_by_dt_nodelabel(),
device_get_dt_metadata(), etc.

endmenu

menu "Initialization Priorities"
Expand Down
52 changes: 52 additions & 0 deletions kernel/device.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

#include <stddef.h>
#include <string.h>
#include <zephyr/device.h>
#include <zephyr/sys/atomic.h>
Expand Down Expand Up @@ -77,6 +78,57 @@ static inline bool z_vrfy_device_is_ready(const struct device *dev)
#include <zephyr/syscalls/device_is_ready_mrsh.c>
#endif /* CONFIG_USERSPACE */

#ifdef CONFIG_DEVICE_DT_METADATA
const struct device *z_impl_device_get_by_dt_nodelabel(const char *nodelabel)
{
/* For consistency with device_get_binding(). */
if ((nodelabel == NULL) || (nodelabel[0] == '\0')) {
return NULL;
}

/* Unlike device_get_binding(), which has a history of being
* used in application code, we don't expect
* device_get_by_dt_nodelabel() to be used outside of
* scenarios where a human is in the loop. The shell is the
* main expected use case. Therefore, nodelabel is probably
* not the same pointer as any of the entry->nodelabel
* elements. We therefore skip the pointer comparison that
* device_get_binding() does.
*/
STRUCT_SECTION_FOREACH(device, dev) {
const struct device_dt_nodelabels *nl = device_get_dt_nodelabels(dev);

if (!z_impl_device_is_ready(dev)) {
continue;
}

for (size_t i = 0; i < nl->num_nodelabels; i++) {
const char *dev_nodelabel = nl->nodelabels[i];

if (strcmp(nodelabel, dev_nodelabel) == 0) {
return dev;
}
}
}

return NULL;
}

#ifdef CONFIG_USERSPACE
static inline const struct device *z_vrfy_device_get_by_dt_nodelabel(const char *nodelabel)
{
const char nl_copy[Z_DEVICE_MAX_NODELABEL_LEN];

if (k_usermode_string_copy(nl_copy, (char *)nodelabel, sizeof(nl_copy)) != 0) {
return NULL;
}

return z_impl_device_get_by_dt_nodelabel(nl_copy);
}
#include <syscalls/device_get_by_dt_nodelabel_mrsh.c>
#endif /* CONFIG_USERSPACE */
#endif /* CONFIG_DEVICE_DT_METADATA */

size_t z_device_get_all_static(struct device const **devices)
{
size_t cnt;
Expand Down

0 comments on commit 15c9d37

Please sign in to comment.