drivers: PSCI: Add driver and subsystem

Firmware implementing the PSCI functions described in ARM document
number ARM DEN 0022A ("Power State Coordination Interface System
Software on ARM processors") can be used by Zephyr to initiate various
CPU-centric power operations.

It is needed for virtualization, it is used to coordinate OSes and
hypervisors and it provides the functions used for SMP bring-up such as
CPU_ON and CPU_OFF.

A new PSCI driver is introduced to setup a proper subsystem used to
communicate with the PSCI firmware, implementing the basic operations:
get_version, cpu_on, cpu_off and affinity_info.

The current implementation only supports PSCI 0.2 and PSCI 1.0

The PSCI conduit (SMC or HVC) is setup reading the corresponding
property in the DTS node.

Signed-off-by: Carlo Caione <ccaione@baylibre.com>
This commit is contained in:
Carlo Caione 2020-12-19 15:52:48 +01:00 committed by Carles Cufí
parent 602b71ca5c
commit 57f7e31017
16 changed files with 596 additions and 0 deletions

View file

@ -239,6 +239,7 @@
/drivers/peci/ @albertofloyd @franciscomunoz @scottwcpg
/drivers/pinmux/*hsdk* @iriszzw
/drivers/pinmux/*it8xxx2* @ite
/drivers/psci/ @carlocaione
/drivers/ps2/ @albertofloyd @franciscomunoz @scottwcpg
/drivers/pwm/*rv32m1* @henrikbrixandersen
/drivers/pwm/*sam0* @nzmichaelh
@ -359,6 +360,7 @@
/dts/bindings/*/sifive* @mateusz-holenko @kgugala @pgielda @nategraff-sifive
/dts/bindings/*/litex* @mateusz-holenko @kgugala @pgielda
/dts/bindings/*/vexriscv* @mateusz-holenko @kgugala @pgielda
/dts/bindings/psci/* @carlocaione
/dts/posix/ @aescolar @vanwinkeljan @daor-oti
/dts/bindings/sensor/*bme680* @BoschSensortec
/dts/bindings/sensor/st* @avisconti
@ -385,6 +387,7 @@
/include/drivers/spi.h @tbursztyka
/include/drivers/lora.h @Mani-Sadhasivam
/include/drivers/peci.h @albertofloyd @franciscomunoz @scottwcpg
/include/drivers/psci.h @carlocaione
/include/app_memory/ @andrewboie
/include/arch/arc/ @abrodkin @ruuddw
/include/arch/arc/arch.h @andrewboie
@ -392,6 +395,7 @@
/include/arch/arm/aarch32/ @MaureenHelm @galak @ioannisg
/include/arch/arm/aarch32/cortex_a_r/ @stephanosio
/include/arch/arm/aarch64/ @carlocaione
/include/arch/arm/arm-smccc.h @carlocaione
/include/arch/arm/aarch32/irq.h @andrewboie
/include/arch/nios2/ @andrewboie
/include/arch/nios2/arch.h @andrewboie

View file

@ -654,6 +654,17 @@ Documentation:
labels:
- "area: Clocks"
"Drivers: PSCI":
status: maintained
maintainers:
- carlocaione
files:
- drivers/psci/
- include/drivers/psci.h
- include/arch/arm/arm-smccc.h
labels:
- "area: PSCI"
"Drivers: PWM":
status: maintained
maintainers:

View file

@ -30,5 +30,6 @@ endif ()
zephyr_library_sources_ifdef(CONFIG_GEN_SW_ISR_TABLE isr_wrapper.S)
zephyr_library_sources_ifdef(CONFIG_IRQ_OFFLOAD irq_offload.c)
zephyr_library_sources_ifdef(CONFIG_THREAD_LOCAL_STORAGE ../common/tls.c)
zephyr_library_sources_ifdef(CONFIG_ARM_PSCI smccc-call.S)
add_subdirectory_ifdef(CONFIG_ARM_MMU mmu)

View file

@ -0,0 +1,40 @@
/*
* Copyright (c) 2019 Carlo Caione <ccaione@baylibre.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
/*
* This file implements the common calling mechanism to be used with the Secure
* Monitor Call (SMC) and Hypervisor Call (HVC).
*
* See http://infocenter.arm.com/help/topic/com.arm.doc.den0028a/index.html
*/
#include <toolchain.h>
#include <linker/sections.h>
#include <arch/cpu.h>
.macro SMCCC instr
\instr #0
ldr x4, [sp]
stp x0, x1, [x4]
stp x2, x3, [x4, #16]
ret
.endm
/*
* The SMC instruction is used to generate a synchronous exception that is
* handled by Secure Monitor code running in EL3.
*/
GTEXT(arm_smccc_smc)
SECTION_FUNC(TEXT, arm_smccc_smc)
SMCCC smc
/*
* The HVC instruction is used to generate a synchronous exception that is
* handled by a hypervisor running in EL2.
*/
GTEXT(arm_smccc_hvc)
SECTION_FUNC(TEXT, arm_smccc_hvc)
SMCCC hvc

View file

@ -12,6 +12,12 @@
model = "QEMU Cortex-A53";
compatible = "qemu,arm-cortex-a53";
psci {
compatible = "arm,psci-0.2";
method = "hvc";
label = "PSCI";
};
chosen {
zephyr,sram = &sram0;
zephyr,console = &uart0;

View file

@ -11,6 +11,9 @@ CONFIG_SERIAL=y
CONFIG_CONSOLE=y
CONFIG_UART_CONSOLE=y
# PSCI is supported
CONFIG_ARM_PSCI=y
# Enable serial port
CONFIG_UART_PL011=y
CONFIG_UART_PL011_PORT0=y

View file

@ -42,6 +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_FLASH_HAS_DRIVER_ENABLED flash)
add_subdirectory_ifdef(CONFIG_SERIAL_HAS_DRIVER serial)

View file

@ -105,4 +105,6 @@ source "drivers/memc/Kconfig"
source "drivers/virtualization/Kconfig"
source "drivers/psci/Kconfig"
endmenu

View file

@ -0,0 +1,6 @@
# 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)

22
drivers/psci/Kconfig Normal file
View file

@ -0,0 +1,22 @@
# PSCI driver configuration options
# Copyright (c) 2020 Carlo Caione <ccaione@baylibre.com>
# SPDX-License-Identifier: Apache-2.0
config ARM_PSCI
bool "Support for the ARM Power State Coordination Interface (PSCI)"
depends on ARMV8_A
help
Say Y here if you want Zephyr to communicate with system firmware
implementing the PSCI specification for CPU-centric power
management operations described in ARM document number ARM DEN
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

159
drivers/psci/psci.c Normal file
View file

@ -0,0 +1,159 @@
/*
* Copyright 2020 Carlo Caione <ccaione@baylibre.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT arm_psci_0_2
#define LOG_LEVEL CONFIG_PSCI_LOG_LEVEL
#include <logging/log.h>
LOG_MODULE_REGISTER(psci);
#include <kernel.h>
#include <arch/cpu.h>
#include <soc.h>
#include <device.h>
#include <init.h>
#include <drivers/psci.h>
#include "psci.h"
static int psci_to_dev_err(int ret)
{
switch (ret) {
case PSCI_RET_SUCCESS:
return 0;
case PSCI_RET_NOT_SUPPORTED:
return -ENOTSUP;
case PSCI_RET_INVALID_PARAMS:
case PSCI_RET_INVALID_ADDRESS:
return -EINVAL;
case PSCI_RET_DENIED:
return -EPERM;
};
return -EINVAL;
}
static uint32_t psci_api_get_version(const struct device *dev)
{
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);
return psci_to_dev_err(ret);
}
static int psci_api_cpu_on(const struct device *dev, unsigned long cpuid,
unsigned long 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);
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,
unsigned long arg2)
{
struct arm_smccc_res res;
arm_smccc_hvc(function_id, arg0, arg1, arg2, 0, 0, 0, 0, &res);
return res.a0;
}
static unsigned long __invoke_psci_fn_smc(unsigned long function_id,
unsigned long arg0,
unsigned long arg1,
unsigned long arg2)
{
struct arm_smccc_res res;
arm_smccc_smc(function_id, arg0, arg1, arg2, 0, 0, 0, 0, &res);
return res.a0;
}
static int set_conduit_method(struct psci *data)
{
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;
} else if (!strcmp("smc", method)) {
data->conduit = SMCCC_CONDUIT_SMC;
data->invoke_psci_fn = __invoke_psci_fn_smc;
} else {
LOG_ERR("Invalid conduit method");
return -EINVAL;
}
return 0;
}
static int psci_detect(const struct device *dev)
{
uint32_t ver = psci_api_get_version(dev);
LOG_DBG("Detected PSCIv%d.%d",
PSCI_VERSION_MAJOR(ver),
PSCI_VERSION_MINOR(ver));
if (PSCI_VERSION_MAJOR(ver) == 0 && PSCI_VERSION_MINOR(ver) < 2) {
LOG_ERR("PSCI unsupported version");
return -ENOTSUP;
}
return 0;
}
static int psci_init(const struct device *dev)
{
struct psci *data = dev->data;
if (set_conduit_method(data)) {
return -ENOTSUP;
}
return psci_detect(dev);
}
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);

63
drivers/psci/psci.h Normal file
View file

@ -0,0 +1,63 @@
/*
* Copyright 2020 Carlo Caione <ccaione@baylibre.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_DRIVERS_PSCI_PSCI_H_
#define ZEPHYR_DRIVERS_PSCI_PSCI_H_
#include <drivers/psci.h>
#ifdef CONFIG_64BIT
#define PSCI_FN_NATIVE(version, name) PSCI_##version##_FN64_##name
#else
#define PSCI_FN_NATIVE(version, name) PSCI_##version##_FN_##name
#endif
/* PSCI v0.2 interface */
#define PSCI_0_2_FN_BASE 0x84000000
#define PSCI_0_2_FN(n) (PSCI_0_2_FN_BASE + (n))
#define PSCI_0_2_64BIT 0x40000000
#define PSCI_0_2_FN64_BASE \
(PSCI_0_2_FN_BASE + PSCI_0_2_64BIT)
#define PSCI_0_2_FN64(n) (PSCI_0_2_FN64_BASE + (n))
#define PSCI_0_2_FN_PSCI_VERSION PSCI_0_2_FN(0)
#define PSCI_0_2_FN_CPU_SUSPEND PSCI_0_2_FN(1)
#define PSCI_0_2_FN_CPU_OFF PSCI_0_2_FN(2)
#define PSCI_0_2_FN_CPU_ON PSCI_0_2_FN(3)
#define PSCI_0_2_FN_AFFINITY_INFO PSCI_0_2_FN(4)
#define PSCI_0_2_FN_MIGRATE PSCI_0_2_FN(5)
#define PSCI_0_2_FN_MIGRATE_INFO_TYPE PSCI_0_2_FN(6)
#define PSCI_0_2_FN_MIGRATE_INFO_UP_CPU PSCI_0_2_FN(7)
#define PSCI_0_2_FN_SYSTEM_OFF PSCI_0_2_FN(8)
#define PSCI_0_2_FN_SYSTEM_RESET PSCI_0_2_FN(9)
#define PSCI_0_2_FN64_CPU_SUSPEND PSCI_0_2_FN64(1)
#define PSCI_0_2_FN64_CPU_ON PSCI_0_2_FN64(3)
#define PSCI_0_2_FN64_AFFINITY_INFO PSCI_0_2_FN64(4)
#define PSCI_0_2_FN64_MIGRATE PSCI_0_2_FN64(5)
#define PSCI_0_2_FN64_MIGRATE_INFO_UP_CPU PSCI_0_2_FN64(7)
/* PSCI return values (inclusive of all PSCI versions) */
#define PSCI_RET_SUCCESS 0
#define PSCI_RET_NOT_SUPPORTED -1
#define PSCI_RET_INVALID_PARAMS -2
#define PSCI_RET_DENIED -3
#define PSCI_RET_ALREADY_ON -4
#define PSCI_RET_ON_PENDING -5
#define PSCI_RET_INTERNAL_FAILURE -6
#define PSCI_RET_NOT_PRESENT -7
#define PSCI_RET_DISABLED -8
#define PSCI_RET_INVALID_ADDRESS -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;
};
#endif /* ZEPHYR_DRIVERS_PSCI_PSCI_H_ */

View file

@ -0,0 +1,45 @@
/*
* 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,20 @@
# Copyright (c) 2020 Carlo Caione <ccaione@baylibre.com>
# SPDX-License-Identifier: Apache-2.0
description: PSCI
compatible: "arm,psci-0.2"
include: base.yaml
properties:
label:
required: true
method:
type: string
required: true
description: The method of calling the PSCI firmware.
enum:
- smc
- hvc

View file

@ -0,0 +1,53 @@
/*
* Copyright 2020 Carlo Caione <ccaione@baylibre.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_ARCH_ARM_ARM_SMCCC_H_
#define ZEPHYR_INCLUDE_ARCH_ARM_ARM_SMCCC_H_
/*
* Result from SMC/HVC call
* @a0-a3 result values from registers 0 to 3
*/
struct arm_smccc_res {
unsigned long a0;
unsigned long a1;
unsigned long a2;
unsigned long a3;
};
enum arm_smccc_conduit {
SMCCC_CONDUIT_NONE,
SMCCC_CONDUIT_SMC,
SMCCC_CONDUIT_HVC,
};
/*
* @brief Make HVC calls
*
* @param a0 function identifier
* @param a1-a7 parameters registers
* @param res results
*/
void arm_smccc_hvc(unsigned long a0, unsigned long a1,
unsigned long a2, unsigned long a3,
unsigned long a4, unsigned long a5,
unsigned long a6, unsigned long a7,
struct arm_smccc_res *res);
/*
* @brief Make SMC calls
*
* @param a0 function identifier
* @param a1-a7 parameters registers
* @param res results
*/
void arm_smccc_smc(unsigned long a0, unsigned long a1,
unsigned long a2, unsigned long a3,
unsigned long a4, unsigned long a5,
unsigned long a6, unsigned long a7,
struct arm_smccc_res *res);
#endif /* ZEPHYR_INCLUDE_ARCH_ARM_ARM_SMCCC_H_ */

160
include/drivers/psci.h Normal file
View file

@ -0,0 +1,160 @@
/*
* 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_ */