pm: optimize resource usage

It is well known that PM subsystem has never been optimized in terms of
resource usage. The situation is particularly bad in case the PM runtime
API is enabled. What this patch does is to move the responsability of PM
resource definition to the device like this:

- Device is responsible to define PM resources, using a new set of
  macros: PM_DEVICE_*DEFINE().
- DEVICE_*DEFINE macro accepts a reference to the device PM state, which
  can be obtained using PM_DEVICE_*REF() set of macros. This
  allows device to initialize the dev->pm reference.

This method decouples a bit more PM from devices since devices just keep
a reference to the device PM state. It also means that future PM changes
will have less chances to impact all devices, but only devices that
support PM.

Signed-off-by: Gerard Marull-Paretas <gerard.marull@nordicsemi.no>
This commit is contained in:
Gerard Marull-Paretas 2021-10-13 12:11:40 +02:00 committed by Carles Cufí
commit e2f33dd97c
4 changed files with 135 additions and 52 deletions

View file

@ -86,7 +86,7 @@ typedef int16_t device_handle_t;
* @brief Run an initialization function at boot at specified priority. * @brief Run an initialization function at boot at specified priority.
* *
* @details Invokes DEVICE_DEFINE() with no power management support * @details Invokes DEVICE_DEFINE() with no power management support
* (@p pm_action_cb), no API (@p api_ptr), and a device name derived from * (@p pm_device), no API (@p api_ptr), and a device name derived from
* the @p init_fn name (@p dev_name). * the @p init_fn name (@p dev_name).
*/ */
#define SYS_DEVICE_DEFINE(drv_name, init_fn, level, prio) \ #define SYS_DEVICE_DEFINE(drv_name, init_fn, level, prio) \
@ -112,8 +112,7 @@ typedef int16_t device_handle_t;
* *
* @param init_fn Address to the init function of the driver. * @param init_fn Address to the init function of the driver.
* *
* @param pm_action_cb Pointer to PM action callback. * @param pm_device PM device resources reference (NULL if device does not use PM).
* Can be NULL if not implemented.
* *
* @param data_ptr Pointer to the device's private data. * @param data_ptr Pointer to the device's private data.
* *
@ -129,10 +128,10 @@ typedef int16_t device_handle_t;
* @param api_ptr Provides an initial pointer to the API function struct * @param api_ptr Provides an initial pointer to the API function struct
* used by the driver. Can be NULL. * used by the driver. Can be NULL.
*/ */
#define DEVICE_DEFINE(dev_name, drv_name, init_fn, pm_action_cb, \ #define DEVICE_DEFINE(dev_name, drv_name, init_fn, pm_device, \
data_ptr, cfg_ptr, level, prio, api_ptr) \ data_ptr, cfg_ptr, level, prio, api_ptr) \
Z_DEVICE_DEFINE(DT_INVALID_NODE, dev_name, drv_name, init_fn, \ Z_DEVICE_DEFINE(DT_INVALID_NODE, dev_name, drv_name, init_fn, \
pm_action_cb, \ pm_device, \
data_ptr, cfg_ptr, level, prio, api_ptr) data_ptr, cfg_ptr, level, prio, api_ptr)
/** /**
@ -170,8 +169,7 @@ typedef int16_t device_handle_t;
* *
* @param init_fn Address to the init function of the driver. * @param init_fn Address to the init function of the driver.
* *
* @param pm_action_cb Pointer to PM action callback. * @param pm_device PM device resources reference (NULL if device does not use PM).
* Can be NULL if not implemented.
* *
* @param data_ptr Pointer to the device's private data. * @param data_ptr Pointer to the device's private data.
* *
@ -187,12 +185,12 @@ typedef int16_t device_handle_t;
* @param api_ptr Provides an initial pointer to the API function struct * @param api_ptr Provides an initial pointer to the API function struct
* used by the driver. Can be NULL. * used by the driver. Can be NULL.
*/ */
#define DEVICE_DT_DEFINE(node_id, init_fn, pm_action_cb, \ #define DEVICE_DT_DEFINE(node_id, init_fn, pm_device, \
data_ptr, cfg_ptr, level, prio, \ data_ptr, cfg_ptr, level, prio, \
api_ptr, ...) \ api_ptr, ...) \
Z_DEVICE_DEFINE(node_id, Z_DEVICE_DT_DEV_NAME(node_id), \ Z_DEVICE_DEFINE(node_id, Z_DEVICE_DT_DEV_NAME(node_id), \
DEVICE_DT_NAME(node_id), init_fn, \ DEVICE_DT_NAME(node_id), init_fn, \
pm_action_cb, \ pm_device, \
data_ptr, cfg_ptr, level, prio, \ data_ptr, cfg_ptr, level, prio, \
api_ptr, __VA_ARGS__) api_ptr, __VA_ARGS__)
@ -362,11 +360,6 @@ struct device_state {
* invoked. * invoked.
*/ */
bool initialized : 1; bool initialized : 1;
#ifdef CONFIG_PM_DEVICE
/* Power management data */
struct pm_device pm;
#endif /* CONFIG_PM_DEVICE */
}; };
/** /**
@ -392,7 +385,7 @@ struct device {
*/ */
const device_handle_t *const handles; const device_handle_t *const handles;
#ifdef CONFIG_PM_DEVICE #ifdef CONFIG_PM_DEVICE
/** Pointer to device instance power management data */ /** Reference to the device PM resources. */
struct pm_device * const pm; struct pm_device * const pm;
#endif #endif
}; };
@ -650,26 +643,15 @@ static inline bool device_is_ready(const struct device *dev)
#define Z_DEVICE_EXTRA_HANDLES(...) \ #define Z_DEVICE_EXTRA_HANDLES(...) \
FOR_EACH_NONEMPTY_TERM(IDENTITY, (,), __VA_ARGS__) FOR_EACH_NONEMPTY_TERM(IDENTITY, (,), __VA_ARGS__)
#ifdef CONFIG_PM_DEVICE
#define Z_DEVICE_STATE_PM_INIT(node_id, dev_name, pm_action_cb) \
.pm = Z_PM_DEVICE_INIT(Z_DEVICE_STATE_NAME(dev_name).pm, \
node_id, pm_action_cb),
#else
#define Z_DEVICE_STATE_PM_INIT(node_id, dev_name, pm_action_cb)
#endif
/** /**
* @brief Utility macro to define and initialize the device state. * @brief Utility macro to define and initialize the device state.
* @param node_id Devicetree node id of the device. * @param node_id Devicetree node id of the device.
* @param dev_name Device name. * @param dev_name Device name.
* @param pm_action_cb Device PM action callback.
*/ */
#define Z_DEVICE_STATE_DEFINE(node_id, dev_name, pm_action_cb) \ #define Z_DEVICE_STATE_DEFINE(node_id, dev_name) \
static struct device_state Z_DEVICE_STATE_NAME(dev_name) \ static struct device_state Z_DEVICE_STATE_NAME(dev_name) \
__attribute__((__section__(".z_devstate"))) = { \ __attribute__((__section__(".z_devstate")));
Z_DEVICE_STATE_PM_INIT(node_id, dev_name, pm_action_cb) \
};
/* If device power management is enabled, this macro defines a pointer to a /* If device power management is enabled, this macro defines a pointer to a
* device in the z_pm_device_slots region. When invoked for each device, this * device in the z_pm_device_slots region. When invoked for each device, this
@ -689,9 +671,9 @@ static inline bool device_is_ready(const struct device *dev)
/* Construct objects that are referenced from struct device. These /* Construct objects that are referenced from struct device. These
* include power management and dependency handles. * include power management and dependency handles.
*/ */
#define Z_DEVICE_DEFINE_PRE(node_id, dev_name, pm_action_cb, ...) \ #define Z_DEVICE_DEFINE_PRE(node_id, dev_name, ...) \
Z_DEVICE_DEFINE_HANDLES(node_id, dev_name, __VA_ARGS__) \ Z_DEVICE_DEFINE_HANDLES(node_id, dev_name, __VA_ARGS__) \
Z_DEVICE_STATE_DEFINE(node_id, dev_name, pm_action_cb) \ Z_DEVICE_STATE_DEFINE(node_id, dev_name) \
Z_DEVICE_DEFINE_PM_SLOT(dev_name) Z_DEVICE_DEFINE_PM_SLOT(dev_name)
/* Initial build provides a record that associates the device object /* Initial build provides a record that associates the device object
@ -731,23 +713,15 @@ BUILD_ASSERT(sizeof(device_handle_t) == 2, "fix the linker scripts");
Z_DEVICE_EXTRA_HANDLES(__VA_ARGS__) \ Z_DEVICE_EXTRA_HANDLES(__VA_ARGS__) \
}; };
#ifdef CONFIG_PM_DEVICE
#define Z_DEVICE_DEFINE_PM_INIT(dev_name) \
.pm = &Z_DEVICE_STATE_NAME(dev_name).pm,
#else
#define Z_DEVICE_DEFINE_PM_INIT(dev_name)
#endif
#define Z_DEVICE_DEFINE_INIT(node_id, dev_name) \ #define Z_DEVICE_DEFINE_INIT(node_id, dev_name) \
.handles = Z_DEVICE_HANDLE_NAME(node_id, dev_name), \ .handles = Z_DEVICE_HANDLE_NAME(node_id, dev_name),
Z_DEVICE_DEFINE_PM_INIT(dev_name)
/* Like DEVICE_DEFINE but takes a node_id AND a dev_name, and trailing /* Like DEVICE_DEFINE but takes a node_id AND a dev_name, and trailing
* dependency handles that come from outside devicetree. * dependency handles that come from outside devicetree.
*/ */
#define Z_DEVICE_DEFINE(node_id, dev_name, drv_name, init_fn, pm_action_cb, \ #define Z_DEVICE_DEFINE(node_id, dev_name, drv_name, init_fn, pm_device,\
data_ptr, cfg_ptr, level, prio, api_ptr, ...) \ data_ptr, cfg_ptr, level, prio, api_ptr, ...) \
Z_DEVICE_DEFINE_PRE(node_id, dev_name, pm_action_cb, __VA_ARGS__) \ Z_DEVICE_DEFINE_PRE(node_id, dev_name, __VA_ARGS__) \
COND_CODE_1(DT_NODE_EXISTS(node_id), (), (static)) \ COND_CODE_1(DT_NODE_EXISTS(node_id), (), (static)) \
const Z_DECL_ALIGN(struct device) \ const Z_DECL_ALIGN(struct device) \
DEVICE_NAME_GET(dev_name) __used \ DEVICE_NAME_GET(dev_name) __used \
@ -757,6 +731,7 @@ BUILD_ASSERT(sizeof(device_handle_t) == 2, "fix the linker scripts");
.api = (api_ptr), \ .api = (api_ptr), \
.state = &Z_DEVICE_STATE_NAME(dev_name), \ .state = &Z_DEVICE_STATE_NAME(dev_name), \
.data = (data_ptr), \ .data = (data_ptr), \
COND_CODE_1(CONFIG_PM_DEVICE, (.pm = pm_device,), ()) \
Z_DEVICE_DEFINE_INIT(node_id, dev_name) \ Z_DEVICE_DEFINE_INIT(node_id, dev_name) \
}; \ }; \
BUILD_ASSERT(sizeof(Z_STRINGIFY(drv_name)) <= Z_DEVICE_MAX_NAME_LEN, \ BUILD_ASSERT(sizeof(Z_STRINGIFY(drv_name)) <= Z_DEVICE_MAX_NAME_LEN, \

View file

@ -144,8 +144,115 @@ struct pm_device {
(0)) << PM_DEVICE_FLAG_WS_CAPABLE), \ (0)) << PM_DEVICE_FLAG_WS_CAPABLE), \
} }
/**
* Get the name of device PM resources.
*
* @param dev_name Device name.
*/
#define Z_PM_DEVICE_NAME(dev_name) _CONCAT(__pm_device__, dev_name)
#ifdef CONFIG_PM_DEVICE
/**
* Define device PM resources for the given node identifier.
*
* @param node_id Node identifier (DT_NODE_INVALID if not a DT device).
* @param dev_name Device name.
* @param pm_action_cb PM control callback.
*/
#define Z_PM_DEVICE_DEFINE(node_id, dev_name, pm_action_cb) \
static struct pm_device Z_PM_DEVICE_NAME(dev_name) = \
Z_PM_DEVICE_INIT(Z_PM_DEVICE_NAME(dev_name), node_id, \
pm_action_cb)
/**
* Get a reference to the device PM resources.
*
* @param dev_name Device name.
*/
#define Z_PM_DEVICE_REF(dev_name) &Z_PM_DEVICE_NAME(dev_name)
#else
#define Z_PM_DEVICE_DEFINE(node_id, dev_name, pm_action_cb)
#define Z_PM_DEVICE_REF(dev_name) NULL
#endif /* CONFIG_PM_DEVICE */
/** @endcond */ /** @endcond */
/**
* Define device PM resources for the given device name.
*
* @note This macro is a no-op if @kconfig{CONFIG_PM_DEVICE} is not enabled.
*
* @param dev_name Device name.
* @param pm_action_cb PM control callback.
*
* @see #PM_DEVICE_DT_DEFINE, #PM_DEVICE_DT_INST_DEFINE
*/
#define PM_DEVICE_DEFINE(dev_name, pm_action_cb) \
Z_PM_DEVICE_DEFINE(DT_INVALID_NODE, dev_name, pm_action_cb)
/**
* Define device PM resources for the given node identifier.
*
* @note This macro is a no-op if @kconfig{CONFIG_PM_DEVICE} is not enabled.
*
* @param node_id Node identifier.
* @param pm_action_cb PM control callback.
*
* @see #PM_DEVICE_DT_INST_DEFINE, #PM_DEVICE_DEFINE
*/
#define PM_DEVICE_DT_DEFINE(node_id, pm_action_cb) \
Z_PM_DEVICE_DEFINE(node_id, Z_DEVICE_DT_DEV_NAME(node_id), \
pm_action_cb)
/**
* Define device PM resources for the given instance.
*
* @note This macro is a no-op if @kconfig{CONFIG_PM_DEVICE} is not enabled.
*
* @param idx Instance index.
* @param pm_action_cb PM control callback.
*
* @see #PM_DEVICE_DT_DEFINE, #PM_DEVICE_DEFINE
*/
#define PM_DEVICE_DT_INST_DEFINE(idx, pm_action_cb) \
Z_PM_DEVICE_DEFINE(DT_DRV_INST(idx), \
Z_DEVICE_DT_DEV_NAME(DT_DRV_INST(idx)), \
pm_action_cb)
/**
* @brief Obtain a reference to the device PM resources for the given device.
*
* @param dev_name Device name.
*
* @return Reference to the device PM resources (NULL if device
* @kconfig{CONFIG_PM_DEVICE} is disabled).
*/
#define PM_DEVICE_REF(dev_name) \
Z_PM_DEVICE_REF(dev_name)
/**
* @brief Obtain a reference to the device PM resources for the given node.
*
* @param node_id Node identifier.
*
* @return Reference to the device PM resources (NULL if device
* @kconfig{CONFIG_PM_DEVICE} is disabled).
*/
#define PM_DEVICE_DT_REF(node_id) \
PM_DEVICE_REF(Z_DEVICE_DT_DEV_NAME(node_id))
/**
* @brief Obtain a reference to the device PM resources for the given instance.
*
* @param idx Instance index.
*
* @return Reference to the device PM resources (NULL if device
* @kconfig{CONFIG_PM_DEVICE} is disabled).
*/
#define PM_DEVICE_DT_INST_REF(idx) \
PM_DEVICE_DT_REF(DT_DRV_INST(idx))
/** /**
* @brief Get name of device PM state * @brief Get name of device PM state
* *

View file

@ -31,7 +31,7 @@ int pm_device_state_set(const struct device *dev,
enum pm_device_action action; enum pm_device_action action;
struct pm_device *pm = dev->pm; struct pm_device *pm = dev->pm;
if (pm->action_cb == NULL) { if (pm == NULL) {
return -ENOSYS; return -ENOSYS;
} }
@ -78,7 +78,7 @@ int pm_device_state_get(const struct device *dev,
{ {
struct pm_device *pm = dev->pm; struct pm_device *pm = dev->pm;
if (pm->action_cb == NULL) { if (pm == NULL) {
return -ENOSYS; return -ENOSYS;
} }
@ -97,7 +97,7 @@ bool pm_device_is_any_busy(void)
for (const struct device *dev = devs; dev < (devs + devc); dev++) { for (const struct device *dev = devs; dev < (devs + devc); dev++) {
struct pm_device *pm = dev->pm; struct pm_device *pm = dev->pm;
if (pm->action_cb == NULL) { if (pm == NULL) {
continue; continue;
} }
@ -113,7 +113,7 @@ bool pm_device_is_busy(const struct device *dev)
{ {
struct pm_device *pm = dev->pm; struct pm_device *pm = dev->pm;
if (pm->action_cb == NULL) { if (pm == NULL) {
return false; return false;
} }
@ -124,7 +124,7 @@ void pm_device_busy_set(const struct device *dev)
{ {
struct pm_device *pm = dev->pm; struct pm_device *pm = dev->pm;
if (pm->action_cb == NULL) { if (pm == NULL) {
return; return;
} }
@ -135,7 +135,7 @@ void pm_device_busy_clear(const struct device *dev)
{ {
struct pm_device *pm = dev->pm; struct pm_device *pm = dev->pm;
if (pm->action_cb == NULL) { if (pm == NULL) {
return; return;
} }
@ -147,7 +147,7 @@ bool pm_device_wakeup_enable(struct device *dev, bool enable)
atomic_val_t flags, new_flags; atomic_val_t flags, new_flags;
struct pm_device *pm = dev->pm; struct pm_device *pm = dev->pm;
if (pm->action_cb == NULL) { if (pm == NULL) {
return false; return false;
} }
@ -171,7 +171,7 @@ bool pm_device_wakeup_is_enabled(const struct device *dev)
{ {
struct pm_device *pm = dev->pm; struct pm_device *pm = dev->pm;
if (pm->action_cb == NULL) { if (pm == NULL) {
return false; return false;
} }
@ -183,7 +183,7 @@ bool pm_device_wakeup_is_capable(const struct device *dev)
{ {
struct pm_device *pm = dev->pm; struct pm_device *pm = dev->pm;
if (pm->action_cb == NULL) { if (pm == NULL) {
return false; return false;
} }

View file

@ -127,8 +127,9 @@ static int pm_suspend_devices(void)
* ignore busy devices, wake up source and devices with * ignore busy devices, wake up source and devices with
* runtime PM enabled. * runtime PM enabled.
*/ */
if (pm_device_is_busy(dev) || pm_device_wakeup_is_enabled(dev) if (pm_device_is_busy(dev) ||
|| pm_device_runtime_is_enabled(dev)) { pm_device_wakeup_is_enabled(dev) ||
((dev->pm != NULL) && pm_device_runtime_is_enabled(dev))) {
continue; continue;
} }