aarch64: pm_cpu_ops: Introduce pm_cpu_ops subsystem

AArch64 has support for PSCI. This is especially useful for SMP because
PSCI is used to power on the secordary cores.

When the PSCI driver was introduced in Zephyr it was designed to rely on
a very PSCI-centric subsystem / interface.

There are two kinds of problems with this choice:

1. PSCI is only defined for the non-secure world and it is designed to
   boot CPU cores into non-secure state (that means that PSCI is only
   supposed to work if Zephyr is running in non-secure state)

2. There can be other ways or standards used to start / stop a core
   different from PSCI

This patch is trying to fix the original wrong assumption by making the
interface / subsystem a generic one, called 'pm_cpu_ops', and using PSCI
only as an actual driver that is a user of this new interface /
subsystem.

For now the new subsystem is only exposing two methods: cpu_on and
cpu_off, others will probably follow according to the needs.

Signed-off-by: Carlo Caione <ccaione@baylibre.com>
This commit is contained in:
Carlo Caione 2021-03-01 08:57:15 +01:00 committed by Anas Nashif
parent 9d908c78fa
commit 0f9406277d
18 changed files with 204 additions and 301 deletions

View file

@ -244,7 +244,7 @@
/drivers/peci/ @albertofloyd @franciscomunoz @scottwcpg
/drivers/pinmux/*hsdk* @iriszzw
/drivers/pinmux/*it8xxx2* @ite
/drivers/psci/ @carlocaione
/drivers/pm_cpu_ops/ @carlocaione
/drivers/ps2/ @albertofloyd @franciscomunoz @scottwcpg
/drivers/pwm/*rv32m1* @henrikbrixandersen
/drivers/pwm/*sam0* @nzmichaelh
@ -365,7 +365,7 @@
/dts/bindings/*/sifive* @mateusz-holenko @kgugala @pgielda
/dts/bindings/*/litex* @mateusz-holenko @kgugala @pgielda
/dts/bindings/*/vexriscv* @mateusz-holenko @kgugala @pgielda
/dts/bindings/psci/* @carlocaione
/dts/bindings/pm_cpu_ops/* @carlocaione
/dts/posix/ @aescolar @vanwinkeljan @daor-oti
/dts/bindings/sensor/*bme680* @BoschSensortec
/dts/bindings/sensor/st* @avisconti
@ -392,7 +392,8 @@
/include/drivers/spi.h @tbursztyka
/include/drivers/lora.h @Mani-Sadhasivam
/include/drivers/peci.h @albertofloyd @franciscomunoz @scottwcpg
/include/drivers/psci.h @carlocaione
/include/drivers/pm_cpu_ops.h @carlocaione
/include/drivers/pm_cpu_ops/ @carlocaione
/include/app_memory/ @dcpleung
/include/arch/arc/ @abrodkin @ruuddw
/include/arch/arc/arch.h @abrodkin @ruuddw

View file

@ -729,16 +729,17 @@ Documentation:
labels:
- "area: Clocks"
"Drivers: PSCI":
"Drivers: PM CPU ops":
status: maintained
maintainers:
- carlocaione
files:
- drivers/psci/
- include/drivers/psci.h
- drivers/pm_cpu_ops/
- include/drivers/pm_cpu_ops/
- include/drivers/pm_cpu_ops.h
- include/arch/arm/arm-smccc.h
labels:
- "area: PSCI"
- "area: PM CPU ops"
"Drivers: PWM":
status: maintained

View file

@ -12,7 +12,7 @@ CONFIG_CONSOLE=y
CONFIG_UART_CONSOLE=y
# PSCI is supported
CONFIG_ARM_PSCI=y
CONFIG_PM_CPU_OPS=y
# Enable serial port
CONFIG_UART_PL011=y

View file

@ -42,7 +42,7 @@ add_subdirectory_ifdef(CONFIG_PECI peci)
add_subdirectory_ifdef(CONFIG_REGULATOR regulator)
add_subdirectory_ifdef(CONFIG_MEMC memc)
add_subdirectory_ifdef(CONFIG_VIRTUALIZATION virtualization)
add_subdirectory_ifdef(CONFIG_ARM_PSCI psci)
add_subdirectory_ifdef(CONFIG_PM_CPU_OPS pm_cpu_ops)
add_subdirectory_ifdef(CONFIG_FLASH_HAS_DRIVER_ENABLED flash)
add_subdirectory_ifdef(CONFIG_SERIAL_HAS_DRIVER serial)

View file

@ -105,8 +105,8 @@ source "drivers/memc/Kconfig"
source "drivers/virtualization/Kconfig"
source "drivers/psci/Kconfig"
source "drivers/edac/Kconfig"
source "drivers/pm_cpu_ops/Kconfig"
endmenu

View file

@ -0,0 +1,5 @@
# SPDX-License-Identifier: Apache-2.0
zephyr_sources_ifdef(CONFIG_PM_CPU_OPS pm_cpu_ops_weak_impl.c)
zephyr_sources_ifdef(CONFIG_PM_CPU_OPS_PSCI pm_cpu_ops_psci.c)

View file

@ -1,12 +1,30 @@
# PSCI driver configuration options
# CPU power management driver configuration options
# Copyright (c) 2020 Carlo Caione <ccaione@baylibre.com>
# Copyright (c) 2021 Carlo Caione <ccaione@baylibre.com>
# SPDX-License-Identifier: Apache-2.0
config ARM_PSCI
menuconfig PM_CPU_OPS
bool "CPU power management drivers"
help
Enable CPU power management drivers configuration
if PM_CPU_OPS
module = PM_CPU_OPS
module-str = pm_cpu_ops
source "subsys/logging/Kconfig.template.log_config"
DT_COMPAT_ARM_PSCI := arm,psci-0.2
config PM_CPU_OPS_HAS_DRIVER
bool
config PM_CPU_OPS_PSCI
bool "Support for the ARM Power State Coordination Interface (PSCI)"
depends on ARMV8_A
depends on HAS_ARM_SMCCC
select PM_CPU_OPS_HAS_DRIVER
default $(dt_compat_enabled,$(DT_COMPAT_ARM_PSCI))
help
Say Y here if you want Zephyr to communicate with system firmware
implementing the PSCI specification for CPU-centric power
@ -14,10 +32,4 @@ config ARM_PSCI
0022A ("Power State Coordination Interface System Software on
ARM processors").
if ARM_PSCI
module = PSCI
module-str = psci
source "subsys/logging/Kconfig.template.log_config"
endif

View file

@ -6,7 +6,7 @@
#define DT_DRV_COMPAT arm_psci_0_2
#define LOG_LEVEL CONFIG_PSCI_LOG_LEVEL
#define LOG_LEVEL CONFIG_PM_CPU_OPS_LOG_LEVEL
#include <logging/log.h>
LOG_MODULE_REGISTER(psci);
@ -17,8 +17,10 @@ LOG_MODULE_REGISTER(psci);
#include <device.h>
#include <init.h>
#include <drivers/psci.h>
#include "psci.h"
#include <drivers/pm_cpu_ops.h>
#include "pm_cpu_ops_psci.h"
static struct psci psci_data;
static int psci_to_dev_err(int ret)
{
@ -37,45 +39,32 @@ static int psci_to_dev_err(int ret)
return -EINVAL;
}
static uint32_t psci_api_get_version(const struct device *dev)
int pm_cpu_off(void)
{
struct psci *data = dev->data;
return data->invoke_psci_fn(PSCI_0_2_FN_PSCI_VERSION, 0, 0, 0);
}
static int psci_api_cpu_off(const struct device *dev, uint32_t state)
{
struct psci *data = dev->data;
int ret;
ret = data->invoke_psci_fn(PSCI_0_2_FN_CPU_OFF, state, 0, 0);
if (psci_data.conduit == SMCCC_CONDUIT_NONE)
return -EINVAL;
ret = psci_data.invoke_psci_fn(PSCI_0_2_FN_CPU_OFF, 0, 0, 0);
return psci_to_dev_err(ret);
}
static int psci_api_cpu_on(const struct device *dev, unsigned long cpuid,
unsigned long entry_point)
int pm_cpu_on(unsigned long cpuid,
uintptr_t entry_point)
{
struct psci *data = dev->data;
int ret;
ret = data->invoke_psci_fn(PSCI_FN_NATIVE(0_2, CPU_ON), cpuid,
entry_point, 0);
if (psci_data.conduit == SMCCC_CONDUIT_NONE)
return -EINVAL;
ret = psci_data.invoke_psci_fn(PSCI_FN_NATIVE(0_2, CPU_ON), cpuid,
(unsigned long) entry_point, 0);
return psci_to_dev_err(ret);
}
static int psci_api_affinity_info(const struct device *dev,
unsigned long target_affinity,
unsigned long lowest_affinity_level)
{
struct psci *data = dev->data;
return data->invoke_psci_fn(PSCI_FN_NATIVE(0_2, AFFINITY_INFO),
target_affinity, lowest_affinity_level, 0);
}
static unsigned long __invoke_psci_fn_hvc(unsigned long function_id,
unsigned long arg0,
unsigned long arg1,
@ -98,18 +87,23 @@ static unsigned long __invoke_psci_fn_smc(unsigned long function_id,
return res.a0;
}
static int set_conduit_method(struct psci *data)
static uint32_t psci_get_version(void)
{
return psci_data.invoke_psci_fn(PSCI_0_2_FN_PSCI_VERSION, 0, 0, 0);
}
static int set_conduit_method(void)
{
const char *method;
method = DT_PROP(DT_INST(0, DT_DRV_COMPAT), method);
if (!strcmp("hvc", method)) {
data->conduit = SMCCC_CONDUIT_HVC;
data->invoke_psci_fn = __invoke_psci_fn_hvc;
psci_data.conduit = SMCCC_CONDUIT_HVC;
psci_data.invoke_psci_fn = __invoke_psci_fn_hvc;
} else if (!strcmp("smc", method)) {
data->conduit = SMCCC_CONDUIT_SMC;
data->invoke_psci_fn = __invoke_psci_fn_smc;
psci_data.conduit = SMCCC_CONDUIT_SMC;
psci_data.invoke_psci_fn = __invoke_psci_fn_smc;
} else {
LOG_ERR("Invalid conduit method");
return -EINVAL;
@ -118,9 +112,9 @@ static int set_conduit_method(struct psci *data)
return 0;
}
static int psci_detect(const struct device *dev)
static int psci_detect(void)
{
uint32_t ver = psci_api_get_version(dev);
uint32_t ver = psci_get_version();
LOG_DBG("Detected PSCIv%d.%d",
PSCI_VERSION_MAJOR(ver),
@ -131,29 +125,27 @@ static int psci_detect(const struct device *dev)
return -ENOTSUP;
}
psci_data.ver = ver;
return 0;
}
uint32_t psci_version(void)
{
return psci_data.ver;
}
static int psci_init(const struct device *dev)
{
struct psci *data = dev->data;
psci_data.conduit = SMCCC_CONDUIT_NONE;
if (set_conduit_method(data)) {
if (set_conduit_method()) {
return -ENOTSUP;
}
return psci_detect(dev);
return psci_detect();
}
static const struct psci_driver_api psci_api = {
.get_version = psci_api_get_version,
.cpu_off = psci_api_cpu_off,
.cpu_on = psci_api_cpu_on,
.affinity_info = psci_api_affinity_info,
};
static struct psci psci_data;
DEVICE_DT_INST_DEFINE(0, psci_init, device_pm_control_nop,
&psci_data, NULL, PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
&psci_api);
NULL);

View file

@ -7,7 +7,7 @@
#ifndef ZEPHYR_DRIVERS_PSCI_PSCI_H_
#define ZEPHYR_DRIVERS_PSCI_PSCI_H_
#include <drivers/psci.h>
#include <drivers/pm_cpu_ops/psci.h>
#ifdef CONFIG_64BIT
#define PSCI_FN_NATIVE(version, name) PSCI_##version##_FN64_##name
@ -56,8 +56,9 @@ typedef unsigned long (psci_fn)(unsigned long, unsigned long,
unsigned long, unsigned long);
struct psci {
psci_fn *invoke_psci_fn;
enum arm_smccc_conduit conduit;
psci_fn *invoke_psci_fn;
uint32_t ver;
};
#endif /* ZEPHYR_DRIVERS_PSCI_PSCI_H_ */

View file

@ -0,0 +1,17 @@
/*
* Copyright (c) 2021 Carlo Caione <ccaione@baylibre.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <kernel.h>
int __weak pm_cpu_on(unsigned long cpuid, uintptr_t entry_point)
{
return -ENOTSUP;
}
int __weak pm_cpu_off(void)
{
return -ENOTSUP;
}

View file

@ -1,6 +0,0 @@
# SPDX-License-Identifier: Apache-2.0
zephyr_library()
zephyr_library_sources_ifdef(CONFIG_ARM_PSCI psci.c)
zephyr_library_sources_ifdef(CONFIG_USERSPACE psci_handlers.c)

View file

@ -1,45 +0,0 @@
/*
* Copyright 2020 Carlo Caione <ccaione@baylibre.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <drivers/psci.h>
#include <syscall_handler.h>
#include <string.h>
static inline uint32_t z_vrfy_psci_get_version(const struct device *dev)
{
Z_OOPS(Z_SYSCALL_DRIVER_PSCI(dev, get_version));
return z_impl_psci_get_version(dev);
}
#include <syscalls/psci_get_version_mrsh.c>
static inline int z_vrfy_psci_cpu_off(const struct device *dev, uint32_t state)
{
Z_OOPS(Z_SYSCALL_DRIVER_PSCI(dev, cpu_off));
return z_impl_psci_cpu_off(dev, state);
}
#include <syscalls/psci_cpu_off_mrsh.c>
static inline int z_vrfy_psci_cpu_on(const struct device *dev,
unsigned long cpuid,
unsigned long entry_point) {
Z_OOPS(Z_SYSCALL_DRIVER_PSCI(dev, cpu_on));
return z_impl_psci_cpu_on(dev, cpuid, entry_point);
}
#include <syscalls/psci_cpu_on_mrsh.c>
static inline int z_vrfy_psci_affinity_info(const struct device *dev,
unsigned long target_affinity,
unsigned long lowest_affinity_level)
{
Z_OOPS(Z_SYSCALL_DRIVER_PSCI(dev, affinity_info));
return z_impl_psci_affinity_info(dev, target_affinity,
lowest_affinity_level);
}
#include <syscalls/psci_affinity_info_mrsh.c>

View file

@ -0,0 +1,63 @@
/*
* Copyright 2021 Carlo Caione <ccaione@baylibre.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_DRIVERS_PM_CPU_OPS_H_
#define ZEPHYR_INCLUDE_DRIVERS_PM_CPU_OPS_H_
/**
* @file
* @brief Public API for CPU Power Management
*/
#include <zephyr/types.h>
#include <stddef.h>
#include <device.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @defgroup power_management_cpu_api CPU Power Management
* @{
*/
/**
* @brief Power down the calling core
*
* This call is intended for use in hotplug. A core that is powered down by
* cpu_off can only be powered up again in response to a cpu_on
*
* @retval The call does not return when successful
* @retval -ENOTSUP If the operation is not supported
*/
int pm_cpu_off(void);
/**
* @brief Power up a core
*
* This call is used to power up cores that either have not yet been booted
* into the calling supervisory software or have been previously powered down
* with a cpu_off call
*
* @param cpuid CPU id to power on
* @param entry_point Address at which the core must commence execution
*
* @retval 0 on success, a negative errno otherwise
* @retval -ENOTSUP If the operation is not supported
*/
int pm_cpu_on(unsigned long cpuid, uintptr_t entry_point);
#ifdef __cplusplus
}
#endif
/**
* @}
*/
#endif /* ZEPHYR_INCLUDE_DRIVERS_PM_CPU_OPS_H_ */

View file

@ -0,0 +1,36 @@
/*
* Copyright 2020 Carlo Caione <ccaione@baylibre.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_DRIVERS_PM_CPU_OPS_PSCI_H_
#define ZEPHYR_INCLUDE_DRIVERS_PM_CPU_OPS_PSCI_H_
#include <zephyr/types.h>
#include <arch/arm/arm-smccc.h>
#include <stddef.h>
#include <device.h>
#ifdef __cplusplus
extern "C" {
#endif
/* PSCI version decoding (independent of PSCI version) */
#define PSCI_VERSION_MAJOR_SHIFT 16
#define PSCI_VERSION_MINOR_MASK \
((1U << PSCI_VERSION_MAJOR_SHIFT) - 1)
#define PSCI_VERSION_MAJOR_MASK ~PSCI_VERSION_MINOR_MASK
#define PSCI_VERSION_MAJOR(ver) \
(((ver) & PSCI_VERSION_MAJOR_MASK) >> PSCI_VERSION_MAJOR_SHIFT)
#define PSCI_VERSION_MINOR(ver) \
((ver) & PSCI_VERSION_MINOR_MASK)
uint32_t psci_version(void);
#ifdef __cplusplus
}
#endif
#endif /* ZEPHYR_INCLUDE_DRIVERS_PM_CPU_OPS_PSCI_H_ */

View file

@ -1,160 +0,0 @@
/*
* Copyright 2020 Carlo Caione <ccaione@baylibre.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_DRIVERS_PSCI_H_
#define ZEPHYR_INCLUDE_DRIVERS_PSCI_H_
/**
* @file
* @brief Public API for ARM PSCI
*/
#include <zephyr/types.h>
#include <arch/arm/arm-smccc.h>
#include <stddef.h>
#include <device.h>
#ifdef __cplusplus
extern "C" {
#endif
/* PSCI version decoding (independent of PSCI version) */
#define PSCI_VERSION_MAJOR_SHIFT 16
#define PSCI_VERSION_MINOR_MASK \
((1U << PSCI_VERSION_MAJOR_SHIFT) - 1)
#define PSCI_VERSION_MAJOR_MASK ~PSCI_VERSION_MINOR_MASK
#define PSCI_VERSION_MAJOR(ver) \
(((ver) & PSCI_VERSION_MAJOR_MASK) >> PSCI_VERSION_MAJOR_SHIFT)
#define PSCI_VERSION_MINOR(ver) \
((ver) & PSCI_VERSION_MINOR_MASK)
/**
* @brief ARM PSCI Driver API
* @defgroup arm_psci ARM PSCI Driver API
* @{
*/
typedef uint32_t (*psci_get_version_f)(const struct device *dev);
typedef int (*psci_cpu_off_f)(const struct device *dev, uint32_t state);
typedef int (*psci_cpu_on_f)(const struct device *dev, unsigned long cpuid,
unsigned long entry_point);
typedef int (*psci_affinity_info_f)(const struct device *dev,
unsigned long target_affinity,
unsigned long lowest_affinity_level);
__subsystem struct psci_driver_api {
psci_get_version_f get_version;
psci_cpu_off_f cpu_off;
psci_cpu_on_f cpu_on;
psci_affinity_info_f affinity_info;
};
/**
* @brief Return the version of PSCI implemented
*
* @param dev Pointer to the device structure for the driver instance
*
* @return The PSCI version
*/
__syscall uint32_t psci_get_version(const struct device *dev);
static inline uint32_t z_impl_psci_get_version(const struct device *dev)
{
const struct psci_driver_api *api =
(const struct psci_driver_api *)dev->api;
return api->get_version(dev);
}
/**
* @brief Power down the calling core
*
* This call is intended foruse in hotplug. A core that is powered down by
* CPU_OFF can only be powered up again in response to a CPU_ON
*
* @param dev Pointer to the device structure for the driver instance
* @param state Not used
*
* @return The call does not return when successful
*/
__syscall int psci_cpu_off(const struct device *dev, uint32_t state);
static inline int z_impl_psci_cpu_off(const struct device *dev, uint32_t state)
{
const struct psci_driver_api *api =
(const struct psci_driver_api *)dev->api;
return api->cpu_off(dev, state);
}
/**
* @brief Power up a core
*
* This call is used to power up cores that either have not yet been booted
* into the calling supervisory software or have been previously powered down
* with a CPU_OFF call
*
* @param dev Pointer to the device structure for the driver instance
* @param cpuid This parameter contains a copy of the affinity fields of the
* MPIDR register
* @param entry_point Address at which the core must commence execution, when
* it enters the return Non-secure Exception level.
*
* @return 0 on success, a negative errno otherwise
*/
__syscall int psci_cpu_on(const struct device *dev, unsigned long cpuid,
unsigned long entry_point);
static inline int z_impl_psci_cpu_on(const struct device *dev,
unsigned long cpuid,
unsigned long entry_point) {
const struct psci_driver_api *api =
(const struct psci_driver_api *)dev->api;
return api->cpu_on(dev, cpuid, entry_point);
}
/**
* @brief Enable the caller to request status of an affinity instance
*
* @param dev Pointer to the device structure for the driver instance
* @param target_affinity This parameter contains a copy of the affinity fields
* of the MPIDR register
* @param lowest_affinity_level Denotes the lowest affinity level field that is
* valid in the target_affinity parameter
*
* @return 2 if the affinity instance is transitioning to an ON sate, 1 off, 0
* on, a negative errno otherwise
*/
__syscall int psci_affinity_info(const struct device *dev,
unsigned long target_affinity,
unsigned long lowest_affinity_level);
static inline int z_impl_psci_affinity_info(const struct device *dev,
unsigned long target_affinity,
unsigned long lowest_affinity_level)
{
const struct psci_driver_api *api =
(const struct psci_driver_api *)dev->api;
return api->affinity_info(dev, target_affinity, lowest_affinity_level);
}
#ifdef __cplusplus
}
#endif
/**
* @}
*/
#include <syscalls/psci.h>
#endif /* ZEPHYR_INCLUDE_DRIVERS_PSCI_H_ */

View file

@ -5,42 +5,28 @@
*/
#include <ztest.h>
#include <drivers/psci.h>
#define PSCI_DEV_NAME "PSCI"
#include <drivers/pm_cpu_ops/psci.h>
#include <drivers/pm_cpu_ops.h>
void test_psci_func(void)
{
const struct device *psci;
uint32_t ver;
int ret;
psci = device_get_binding(PSCI_DEV_NAME);
zassert_not_null(psci, "Could not get psci device");
/* This should return 2 for v0.2 */
ver = psci_get_version(psci);
ver = psci_version();
zassert_false((PSCI_VERSION_MAJOR(ver) == 0 &&
PSCI_VERSION_MINOR(ver) < 2),
"Wrong PSCI firware version");
/* This should return 0: (one core in the affinity instance is ON) */
ret = psci_affinity_info(psci, 0, 0);
zassert_true(ret == 0, "Wrong return code from psci_affinity_info");
/* This should return -PSCI_RET_ALREADY_ON that is mapped to -EINVAL */
ret = psci_cpu_on(psci, 0, 0);
ret = pm_cpu_on(0, 0);
zassert_true(ret == -EINVAL, "Wrong return code from psci_cpu_on");
}
void test_main(void)
{
const struct device *psci = device_get_binding(PSCI_DEV_NAME);
zassert_not_null(psci, "Could not get psci device");
k_object_access_grant(psci, k_current_get());
ztest_test_suite(psci_func,
ztest_user_unit_test(test_psci_func));
ztest_unit_test(test_psci_func));
ztest_run_test_suite(psci_func);
}

View file

@ -2,4 +2,4 @@ tests:
arch.arm64.psci:
arch_allow: arm
tags: arm psci drivers userspace
filter: CONFIG_ARM_PSCI
filter: PM_CPU_OPS_PSCI