Skip to content

Commit

Permalink
pm: device: Dynamically add a device to a power domain
Browse files Browse the repository at this point in the history
Add API to add devices to a power domain in runtime. The number of
devices that can be added is defined in build time.

The script gen_handles.py will check the number defined in
`CONFIG_PM_DEVICE_POWER_DOMAIN_DYNAMIC` to resize the handles vector,
adding empty slots in the supported sector to be used later.

Signed-off-by: Flavio Ceolin <[email protected]>
  • Loading branch information
Flavio Ceolin authored and mbolivar-nordic committed Apr 19, 2022
1 parent b2d3fdc commit 0b13b44
Show file tree
Hide file tree
Showing 9 changed files with 183 additions and 3 deletions.
7 changes: 7 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -840,6 +840,12 @@ zephyr_get_include_directories_for_lang(C
STRIP_PREFIX # Don't use a -I prefix
)

if(CONFIG_PM_DEVICE_POWER_DOMAIN_DYNAMIC)
set(number_of_dynamic_devices ${CONFIG_PM_DEVICE_POWER_DOMAIN_DYNAMIC_NUM})
else()
set(number_of_dynamic_devices 0)
endif()

if(CONFIG_HAS_DTS)
# dev_handles.c is generated from ${ZEPHYR_LINK_STAGE_EXECUTABLE} by
# gen_handles.py
Expand All @@ -849,6 +855,7 @@ if(CONFIG_HAS_DTS)
${PYTHON_EXECUTABLE}
${ZEPHYR_BASE}/scripts/gen_handles.py
--output-source dev_handles.c
--num-dynamic-devices ${number_of_dynamic_devices}
--kernel $<TARGET_FILE:${ZEPHYR_LINK_STAGE_EXECUTABLE}>
--zephyr-base ${ZEPHYR_BASE}
--start-symbol "$<TARGET_PROPERTY:linker,devices_start_symbol>"
Expand Down
13 changes: 10 additions & 3 deletions include/zephyr/device.h
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,12 @@ struct device_state {

struct pm_device;

#ifdef CONFIG_HAS_DYNAMIC_DEVICE_HANDLES
#define Z_DEVICE_HANDLES_CONST
#else
#define Z_DEVICE_HANDLES_CONST const
#endif

/**
* @brief Runtime device structure (in ROM) per driver instance
*/
Expand All @@ -465,7 +471,8 @@ struct device {
* extracted with dedicated API, such as
* device_required_handles_get().
*/
const device_handle_t *const handles;
Z_DEVICE_HANDLES_CONST device_handle_t * const handles;

#ifdef CONFIG_PM_DEVICE
/** Reference to the device PM resources. */
struct pm_device * const pm;
Expand Down Expand Up @@ -877,9 +884,9 @@ __deprecated static inline int device_usable_check(const struct device *dev)
*/
BUILD_ASSERT(sizeof(device_handle_t) == 2, "fix the linker scripts");
#define Z_DEVICE_DEFINE_HANDLES(node_id, dev_name, ...) \
extern const device_handle_t \
extern Z_DEVICE_HANDLES_CONST device_handle_t \
Z_DEVICE_HANDLE_NAME(node_id, dev_name)[]; \
const device_handle_t \
Z_DEVICE_HANDLES_CONST device_handle_t \
__aligned(sizeof(device_handle_t)) \
__attribute__((__weak__, \
__section__(".__device_handles_pass1"))) \
Expand Down
13 changes: 13 additions & 0 deletions include/zephyr/linker/common-ram.ld
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,19 @@
} GROUP_DATA_LINK_IN(RAMABLE_REGION, ROMABLE_REGION)
#endif

#if defined(CONFIG_HAS_DYNAMIC_DEVICE_HANDLES)
SECTION_DATA_PROLOGUE(device_handles,,)
{
__device_handles_start = .;
#ifdef LINKER_DEVICE_HANDLES_PASS1
KEEP(*(SORT(.__device_handles_pass1*)));
#else
KEEP(*(SORT(.__device_handles_pass2*)));
#endif /* LINKER_DEVICE_HANDLES_PASS1 */
__device_handles_end = .;
} GROUP_DATA_LINK_IN(RAMABLE_REGION, ROMABLE_REGION)
#endif /* CONFIG_HAS_DYNAMIC_DEVICE_HANDLES */

SECTION_DATA_PROLOGUE(initshell,,)
{
/* link in shell initialization objects for all modules that
Expand Down
2 changes: 2 additions & 0 deletions include/zephyr/linker/common-rom.ld
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@
KEEP(*(".dbg_thread_info"));
} GROUP_ROM_LINK_IN(RAMABLE_REGION, ROMABLE_REGION)

#if !defined(CONFIG_HAS_DYNAMIC_DEVICE_HANDLES)
SECTION_DATA_PROLOGUE(device_handles,,)
{
__device_handles_start = .;
Expand All @@ -230,3 +231,4 @@
#endif /* LINKER_DEVICE_HANDLES_PASS1 */
__device_handles_end = .;
} GROUP_ROM_LINK_IN(RAMABLE_REGION, ROMABLE_REGION)
#endif /* !CONFIG_HAS_DYNAMIC_DEVICE_HANDLES */
43 changes: 43 additions & 0 deletions include/zephyr/pm/device.h
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,36 @@ bool pm_device_state_is_locked(const struct device *dev);
*/
bool pm_device_on_power_domain(const struct device *dev);

/**
* @brief Add a device to a power domain.
*
* This function adds a device to a given power domain.
*
* @param dev Device to be added to the power domain.
* @param domain Power domain.
*
* @retval 0 If successful.
* @retval -EALREADY If device is already part of the power domain.
* @retval -ENOSYS If the application was built without power domain support.
* @retval -ENOSPC If there is no space available in the power domain to add the device.
*/
int pm_device_power_domain_add(const struct device *dev,
const struct device *domain);

/**
* @brief Remove a device from a power domain.
*
* This function removes a device from a given power domain.
*
* @param dev Device to be removed from the power domain.
* @param domain Power domain.
*
* @retval 0 If successful.
* @retval -ENOSYS If the application was built without power domain support.
* @retval -ENOENT If device is not in the given domain.
*/
int pm_device_power_domain_remove(const struct device *dev,
const struct device *domain);
#else
static inline void pm_device_init_suspended(const struct device *dev)
{
Expand Down Expand Up @@ -579,6 +609,19 @@ static inline bool pm_device_on_power_domain(const struct device *dev)
ARG_UNUSED(dev);
return false;
}

static inline int pm_device_power_domain_add(const struct device *dev,
const struct device *domain)
{
return -ENOSYS;
}

static inline int pm_device_power_domain_remove(const struct device *dev,
const struct device *domain)
{
return -ENOSYS;
}

#endif /* CONFIG_PM_DEVICE */

/** @} */
Expand Down
10 changes: 10 additions & 0 deletions kernel/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -928,4 +928,14 @@ config THREAD_LOCAL_STORAGE

endmenu

menu "Device Options"

config HAS_DYNAMIC_DEVICE_HANDLES
bool
help
Hidden option that makes possible to manipulate device handles at
runtime.

endmenu

rsource "Kconfig.vm"
4 changes: 4 additions & 0 deletions scripts/gen_handles.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ def parse_args():

parser.add_argument("-k", "--kernel", required=True,
help="Input zephyr ELF binary")
parser.add_argument("-d", "--num-dynamic-devices", required=False, default=0,
type=int, help="Input number of dynamic devices allowed")
parser.add_argument("-o", "--output-source", required=True,
help="Output source file")

Expand Down Expand Up @@ -112,6 +114,7 @@ def symbol_handle_data(elf, sym):
# These match the corresponding constants in <device.h>
DEVICE_HANDLE_SEP = -32768
DEVICE_HANDLE_ENDS = 32767
DEVICE_HANDLE_NULL = 0
def handle_name(hdl):
if hdl == DEVICE_HANDLE_SEP:
return "DEVICE_HANDLE_SEP"
Expand Down Expand Up @@ -336,6 +339,7 @@ def main():
else:
sup_paths.append('(%s)' % dn.path)
hdls.extend(dn.__device.dev_handle for dn in sn.__supports)
hdls.extend(DEVICE_HANDLE_NULL for dn in range(args.num_dynamic_devices))

# Terminate the array with the end symbol
hdls.append(DEVICE_HANDLE_ENDS)
Expand Down
14 changes: 14 additions & 0 deletions subsys/pm/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,20 @@ config PM_DEVICE_POWER_DOMAIN
devices that depend on a domain will be notified when this
domain is suspended or resumed.

config PM_DEVICE_POWER_DOMAIN_DYNAMIC
bool "Dynamically bind devices to a Power Pomain"
depends on PM_DEVICE_POWER_DOMAIN
select HAS_DYNAMIC_DEVICE_HANDLES
help
Enable support for dynamically bind devices to a Power Domain.

config PM_DEVICE_POWER_DOMAIN_DYNAMIC_NUM
int "Number of devices that can dynamically be bind to a Power Domain"
depends on PM_DEVICE_POWER_DOMAIN_DYNAMIC
default 1
help
The number of devices that can dynamically be bind to a Power Domain.

config PM_DEVICE_RUNTIME
bool "Runtime Device Power Management"
help
Expand Down
80 changes: 80 additions & 0 deletions subsys/pm/device.c
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,82 @@ int pm_device_action_run(const struct device *dev,
return 0;
}

static int power_domain_add_or_remove(const struct device *dev,
const struct device *domain,
bool add)
{
#if defined(CONFIG_HAS_DYNAMIC_DEVICE_HANDLES)
device_handle_t *rv = domain->handles;
device_handle_t dev_handle = -1;
extern const struct device __device_start[];
extern const struct device __device_end[];
size_t i, region = 0;
size_t numdev = __device_end - __device_start;

/*
* Supported devices are stored as device handle and not
* device pointers. So, it is necessary to find what is
* the handle associated to the given device.
*/
for (i = 0; i < numdev; i++) {
if (&__device_start[i] == dev) {
dev_handle = i + 1;
break;
}
}

/*
* The last part is to find an available slot in the
* supported section of handles array and replace it
* with the device handle.
*/
while (region != 2) {
if (*rv == DEVICE_HANDLE_SEP) {
region++;
}
rv++;
}

i = 0;
while (rv[i] != DEVICE_HANDLE_ENDS) {
if (add == false) {
if (rv[i] == dev_handle) {
dev->pm->domain = NULL;
rv[i] = DEVICE_HANDLE_NULL;
return 0;
}
} else {
if (rv[i] == DEVICE_HANDLE_NULL) {
dev->pm->domain = domain;
rv[i] = dev_handle;
return 0;
}
}
++i;
}

return add ? -ENOSPC : -ENOENT;
#else
ARG_UNUSED(dev);
ARG_UNUSED(domain);
ARG_UNUSED(add);

return -ENOSYS;
#endif
}

int pm_device_power_domain_remove(const struct device *dev,
const struct device *domain)
{
return power_domain_add_or_remove(dev, domain, false);
}

int pm_device_power_domain_add(const struct device *dev,
const struct device *domain)
{
return power_domain_add_or_remove(dev, domain, true);
}

void pm_device_children_action_run(const struct device *dev,
enum pm_device_action action,
pm_device_action_failed_cb_t failure_cb)
Expand All @@ -121,6 +197,10 @@ void pm_device_children_action_run(const struct device *dev,
device_handle_t dh = handles[i];
const struct device *cdev = device_from_handle(dh);

if (cdev == NULL) {
continue;
}

rc = pm_device_action_run(cdev, action);
if ((failure_cb != NULL) && (rc < 0)) {
/* Stop the iteration if the callback requests it */
Expand Down

0 comments on commit 0b13b44

Please sign in to comment.