Skip to content

Commit

Permalink
pm: device_runtime: Extend with synchronous runtime PM
Browse files Browse the repository at this point in the history
In many cases suspending or resuming of a device is limited to
just a few register writes. Current solution assumes that those
operations may be blocking, asynchronous and take a lot of time.
Due to this assumption runtime PM API cannot be effectively used
from the interrupt context. Zephyr has few driver APIs which
can be used from an interrupt context and now use of runtime PM
is limited in those cases.

Patch introduces a new type of PM device - synchronous PM. If
device is specified as capable of synchronous PM operations then
device runtime getting and putting is executed in the critical
section. In that case, runtime API can be used from an interrupt
context. Additionally, this approach reduces RAM needed for
PM device (104 -> 20 bytes of RAM on ARM Cortex-M).

Signed-off-by: Krzysztof Chruściński <[email protected]>
  • Loading branch information
nordic-krch authored and carlescufi committed Feb 1, 2024
1 parent b707828 commit 25173f7
Show file tree
Hide file tree
Showing 7 changed files with 410 additions and 117 deletions.
2 changes: 1 addition & 1 deletion drivers/power_domain/power_domain_gpio.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ static int pd_on_domain_visitor(const struct device *dev, void *context)
struct pd_visitor_context *visitor_context = context;

/* Only run action if the device is on the specified domain */
if (!dev->pm || (dev->pm->domain != visitor_context->domain)) {
if (!dev->pm || (dev->pm_base->domain != visitor_context->domain)) {
return 0;
}

Expand Down
2 changes: 1 addition & 1 deletion drivers/power_domain/power_domain_gpio_monitor.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ static int pd_on_domain_visitor(const struct device *dev, void *context)
struct pd_visitor_context *visitor_context = context;

/* Only run action if the device is on the specified domain */
if (!dev->pm || (dev->pm->domain != visitor_context->domain)) {
if (!dev->pm || (dev->pm_base->domain != visitor_context->domain)) {
return 0;
}

Expand Down
16 changes: 11 additions & 5 deletions include/zephyr/device.h
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,9 @@ struct device_state {
bool initialized : 1;
};

struct pm_device_base;
struct pm_device;
struct pm_device_isr;

#ifdef CONFIG_DEVICE_DEPS_DYNAMIC
#define Z_DEVICE_DEPS_CONST
Expand Down Expand Up @@ -409,7 +411,11 @@ struct device {
* Reference to the device PM resources (only available if
* @kconfig{CONFIG_PM_DEVICE} is enabled).
*/
struct pm_device *pm;
union {
struct pm_device_base *pm_base;
struct pm_device *pm;
struct pm_device_isr *pm_isr;
};
#endif
};

Expand Down Expand Up @@ -885,7 +891,7 @@ static inline bool z_impl_device_is_ready(const struct device *dev)
* @brief Initializer for @ref device.
*
* @param name_ Name of the device.
* @param pm_ Reference to @ref pm_device (optional).
* @param pm_ Reference to @ref pm_device_base (optional).
* @param data_ Reference to device data.
* @param config_ Reference to device config.
* @param api_ Reference to device API ops.
Expand All @@ -900,7 +906,7 @@ static inline bool z_impl_device_is_ready(const struct device *dev)
.state = (state_), \
.data = (data_), \
IF_ENABLED(CONFIG_DEVICE_DEPS, (.deps = (deps_),)) /**/ \
IF_ENABLED(CONFIG_PM_DEVICE, (.pm = (pm_),)) /**/ \
IF_ENABLED(CONFIG_PM_DEVICE, (.pm_base = (pm_),)) /**/ \
}

/**
Expand All @@ -919,7 +925,7 @@ static inline bool z_impl_device_is_ready(const struct device *dev)
* software device).
* @param dev_id Device identifier (used to name the defined @ref device).
* @param name Name of the device.
* @param pm Reference to @ref pm_device associated with the device.
* @param pm Reference to @ref pm_device_base associated with the device.
* (optional).
* @param data Reference to device data.
* @param config Reference to device config.
Expand Down Expand Up @@ -991,7 +997,7 @@ static inline bool z_impl_device_is_ready(const struct device *dev)
* @param dev_id Device identifier (used to name the defined @ref device).
* @param name Name of the device.
* @param init_fn Device init function.
* @param pm Reference to @ref pm_device associated with the device.
* @param pm Reference to @ref pm_device_base associated with the device.
* (optional).
* @param data Reference to device data.
* @param config Reference to device config.
Expand Down
184 changes: 149 additions & 35 deletions include/zephyr/pm/device.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ enum pm_device_flag {
PM_DEVICE_FLAG_PD,
/** Indicates if device runtime PM should be automatically enabled */
PM_DEVICE_FLAG_RUNTIME_AUTO,
/** Indicates that device runtime PM supports suspending and resuming from any context. */
PM_DEVICE_FLAG_ISR_SAFE,
};

/** @endcond */
Expand Down Expand Up @@ -122,32 +124,69 @@ typedef bool (*pm_device_action_failed_cb_t)(const struct device *dev,

/**
* @brief Device PM info
*
* Structure holds fields which are common for two PM devices: generic and
* synchronous.
*/
struct pm_device_base {
/** Device PM status flags. */
atomic_t flags;
/** Device power state */
enum pm_device_state state;
/** Device PM action callback */
pm_device_action_cb_t action_cb;
#if defined(CONFIG_PM_DEVICE_RUNTIME) || defined(__DOXYGEN__)
/** Device usage count */
uint32_t usage;
#endif /* CONFIG_PM_DEVICE_RUNTIME */
#ifdef CONFIG_PM_DEVICE_POWER_DOMAIN
/** Power Domain it belongs */
const struct device *domain;
#endif /* CONFIG_PM_DEVICE_POWER_DOMAIN */
};

/**
* @brief Runtime PM info for device with generic PM.
*
* Generic PM involves suspending and resuming operations which can be blocking,
* long lasting or asynchronous. Runtime PM API is limited when used from
* interrupt context.
*/
struct pm_device {
/** Base info. */
struct pm_device_base base;
#if defined(CONFIG_PM_DEVICE_RUNTIME) || defined(__DOXYGEN__)
/** Pointer to the device */
const struct device *dev;
/** Lock to synchronize the get/put operations */
struct k_sem lock;
/** Event var to listen to the sync request events */
struct k_event event;
/** Device usage count */
uint32_t usage;
/** Work object for asynchronous calls */
struct k_work_delayable work;
#endif /* CONFIG_PM_DEVICE_RUNTIME */
#ifdef CONFIG_PM_DEVICE_POWER_DOMAIN
/** Power Domain it belongs */
const struct device *domain;
#endif /* CONFIG_PM_DEVICE_POWER_DOMAIN */
/* Device PM status flags. */
atomic_t flags;
/** Device power state */
enum pm_device_state state;
/** Device PM action callback */
pm_device_action_cb_t action_cb;
};

/**
* @brief Runtime PM info for device with synchronous PM.
*
* Synchronous PM can be used with devices which suspend and resume operations can
* be performed in the critical section as they are short and non-blocking.
* Runtime PM API can be used from any context in that case.
*/
struct pm_device_isr {
/** Base info. */
struct pm_device_base base;
#if defined(CONFIG_PM_DEVICE_RUNTIME) || defined(__DOXYGEN__)
/** Lock to synchronize the synchronous get/put operations */
struct k_spinlock lock;
#endif
};

/* Base part must be the first element. */
BUILD_ASSERT(offsetof(struct pm_device, base) == 0);
BUILD_ASSERT(offsetof(struct pm_device_isr, base) == 0);

/** @cond INTERNAL_HIDDEN */

#ifdef CONFIG_PM_DEVICE_RUNTIME
Expand All @@ -167,7 +206,7 @@ struct pm_device {
#endif /* CONFIG_PM_DEVICE_POWER_DOMAIN */

/**
* @brief Utility macro to initialize #pm_device flags
* @brief Utility macro to initialize #pm_device_base flags
*
* @param node_id Devicetree node for the initialized device (can be invalid).
*/
Expand All @@ -188,17 +227,34 @@ struct pm_device {
* @note #DT_PROP_OR is used to retrieve the wakeup_source property because
* it may not be defined on all devices.
*
* @param obj Name of the #pm_device structure being initialized.
* @param obj Name of the #pm_device_base structure being initialized.
* @param node_id Devicetree node for the initialized device (can be invalid).
* @param pm_action_cb Device PM control callback function.
* @param _flags Additional flags passed to the structure.
*/
#define Z_PM_DEVICE_BASE_INIT(obj, node_id, pm_action_cb, _flags) \
{ \
.action_cb = pm_action_cb, \
.state = PM_DEVICE_STATE_ACTIVE, \
.flags = ATOMIC_INIT(Z_PM_DEVICE_FLAGS(node_id) | (_flags)), \
Z_PM_DEVICE_POWER_DOMAIN_INIT(node_id) \
}

/**
* @brief Utility macro to initialize #pm_device_rt.
*
* @note #DT_PROP_OR is used to retrieve the wakeup_source property because
* it may not be defined on all devices.
*
* @param obj Name of the #pm_device_base structure being initialized.
* @param node_id Devicetree node for the initialized device (can be invalid).
* @param pm_action_cb Device PM control callback function.
*/
#define Z_PM_DEVICE_INIT(obj, node_id, pm_action_cb) \
{ \
Z_PM_DEVICE_RUNTIME_INIT(obj) \
.action_cb = pm_action_cb, \
.state = PM_DEVICE_STATE_ACTIVE, \
.flags = ATOMIC_INIT(Z_PM_DEVICE_FLAGS(node_id)), \
Z_PM_DEVICE_POWER_DOMAIN_INIT(node_id) \
#define Z_PM_DEVICE_INIT(obj, node_id, pm_action_cb, isr_safe) \
{ \
.base = Z_PM_DEVICE_BASE_INIT(obj, node_id, pm_action_cb, \
isr_safe ? BIT(PM_DEVICE_FLAG_ISR_SAFE) : 0), \
COND_CODE_1(isr_safe, (), (Z_PM_DEVICE_RUNTIME_INIT(obj))) \
}

/**
Expand Down Expand Up @@ -231,21 +287,22 @@ struct pm_device {
* @param dev_id Device id.
* @param pm_action_cb PM control callback.
*/
#define Z_PM_DEVICE_DEFINE(node_id, dev_id, pm_action_cb) \
Z_PM_DEVICE_DEFINE_SLOT(dev_id); \
static struct pm_device Z_PM_DEVICE_NAME(dev_id) = \
Z_PM_DEVICE_INIT(Z_PM_DEVICE_NAME(dev_id), node_id, \
pm_action_cb)
#define Z_PM_DEVICE_DEFINE(node_id, dev_id, pm_action_cb, isr_safe) \
Z_PM_DEVICE_DEFINE_SLOT(dev_id); \
static struct COND_CODE_1(isr_safe, (pm_device_isr), (pm_device)) \
Z_PM_DEVICE_NAME(dev_id) = \
Z_PM_DEVICE_INIT(Z_PM_DEVICE_NAME(dev_id), node_id, \
pm_action_cb, isr_safe)

/**
* Get a reference to the device PM resources.
*
* @param dev_id Device id.
*/
#define Z_PM_DEVICE_GET(dev_id) (&Z_PM_DEVICE_NAME(dev_id))
#define Z_PM_DEVICE_GET(dev_id) ((struct pm_device_base *)&Z_PM_DEVICE_NAME(dev_id))

#else
#define Z_PM_DEVICE_DEFINE(node_id, dev_id, pm_action_cb)
#define Z_PM_DEVICE_DEFINE(node_id, dev_id, pm_action_cb, isr_safe)
#define Z_PM_DEVICE_GET(dev_id) NULL
#endif /* CONFIG_PM_DEVICE */

Expand All @@ -262,8 +319,26 @@ struct pm_device {
* @see #PM_DEVICE_DT_DEFINE, #PM_DEVICE_DT_INST_DEFINE
*/
#define PM_DEVICE_DEFINE(dev_id, pm_action_cb) \
Z_PM_DEVICE_DEFINE(DT_INVALID_NODE, dev_id, pm_action_cb)
Z_PM_DEVICE_DEFINE(DT_INVALID_NODE, dev_id, pm_action_cb, 0)

/**
* Define device PM resources for the given device name.
*
* PM actions are synchronous and can be executed from any context. This approach
* can be used for cases where suspending and resuming is short as it is
* executed in the critical section. This mode requires less resources (~80 byte
* less RAM) and allows to use device runtime PM from any context (including
* interrupts).
*
* @note This macro is a no-op if @kconfig{CONFIG_PM_DEVICE} is not enabled.
*
* @param dev_id Device id.
* @param pm_action_cb PM control callback.
*
* @see #PM_DEVICE_DT_DEFINE, #PM_DEVICE_DT_INST_DEFINE
*/
#define PM_DEVICE_ISR_SYNC_DEFINE(dev_id, pm_action_cb) \
Z_PM_DEVICE_DEFINE(DT_INVALID_NODE, dev_id, pm_action_cb, 1)
/**
* Define device PM resources for the given node identifier.
*
Expand All @@ -274,9 +349,27 @@ struct pm_device {
*
* @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_ID(node_id), \
pm_action_cb)
#define PM_DEVICE_DT_DEFINE(node_id, pm_action_cb) \
Z_PM_DEVICE_DEFINE(node_id, Z_DEVICE_DT_DEV_ID(node_id), pm_action_cb, 0)

/**
* Define device PM resources for the given node identifier.
*
* PM actions are synchronous and can be executed from any context. This approach
* can be used for cases where suspending and resuming is short as it is
* executed in the critical section. This mode requires less resources (~80 byte
* less RAM) and allows to use device runtime PM from any context (including
* interrupts).
*
* @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_ISR_SAFE_DT_DEFINE(node_id, pm_action_cb) \
Z_PM_DEVICE_DEFINE(node_id, Z_DEVICE_DT_DEV_ID(node_id), pm_action_cb, 1)

/**
* Define device PM resources for the given instance.
Expand All @@ -291,7 +384,28 @@ struct pm_device {
#define PM_DEVICE_DT_INST_DEFINE(idx, pm_action_cb) \
Z_PM_DEVICE_DEFINE(DT_DRV_INST(idx), \
Z_DEVICE_DT_DEV_ID(DT_DRV_INST(idx)), \
pm_action_cb)
pm_action_cb, 0)

/**
* Define device PM resources for the given instance.
*
* PM actions are synchronous and can be executed from any context. This approach
* can be used for cases where suspending and resuming is short as it is
* executed in the critical section. This mode requires less resources (~80 byte
* less RAM) and allows to use device runtime PM from any context (including
* interrupts).
*
* @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_ISR_SAFE_DT_INST_DEFINE(idx, pm_action_cb) \
Z_PM_DEVICE_DEFINE(DT_DRV_INST(idx), \
Z_DEVICE_DT_DEV_ID(DT_DRV_INST(idx)), \
pm_action_cb, 1)

/**
* @brief Obtain a reference to the device PM resources for the given device.
Expand Down Expand Up @@ -393,7 +507,7 @@ int pm_device_state_get(const struct device *dev,
*/
static inline void pm_device_init_suspended(const struct device *dev)
{
struct pm_device *pm = dev->pm;
struct pm_device_base *pm = dev->pm_base;

pm->state = PM_DEVICE_STATE_SUSPENDED;
}
Expand All @@ -413,7 +527,7 @@ static inline void pm_device_init_suspended(const struct device *dev)
*/
static inline void pm_device_init_off(const struct device *dev)
{
struct pm_device *pm = dev->pm;
struct pm_device_base *pm = dev->pm_base;

pm->state = PM_DEVICE_STATE_OFF;
}
Expand Down
2 changes: 1 addition & 1 deletion kernel/include/kernel_offsets.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ GEN_ABSOLUTE_SYM(_DEVICE_STRUCT_PM_OFFSET,
/* member offsets in the pm_device structure. Used in image post-processing */

GEN_ABSOLUTE_SYM(_PM_DEVICE_STRUCT_FLAGS_OFFSET,
offsetof(struct pm_device, flags));
offsetof(struct pm_device_base, flags));

GEN_ABSOLUTE_SYM(_PM_DEVICE_FLAG_PD, PM_DEVICE_FLAG_PD);

Expand Down
Loading

0 comments on commit 25173f7

Please sign in to comment.