Commit 81497953 authored by Russell King's avatar Russell King

Merge branch 'psci/for-rmk' of...

Merge branch 'psci/for-rmk' of git://git.kernel.org/pub/scm/linux/kernel/git/arm64/linux into devel-stable
parents fa8ad788 be120397
...@@ -8066,6 +8066,15 @@ S: Maintained ...@@ -8066,6 +8066,15 @@ S: Maintained
F: include/linux/power_supply.h F: include/linux/power_supply.h
F: drivers/power/ F: drivers/power/
POWER STATE COORDINATION INTERFACE (PSCI)
M: Mark Rutland <mark.rutland@arm.com>
M: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
L: linux-arm-kernel@lists.infradead.org
S: Maintained
F: drivers/firmware/psci.c
F: include/linux/psci.h
F: include/uapi/linux/psci.h
PNP SUPPORT PNP SUPPORT
M: "Rafael J. Wysocki" <rafael.j.wysocki@intel.com> M: "Rafael J. Wysocki" <rafael.j.wysocki@intel.com>
S: Maintained S: Maintained
......
...@@ -1496,6 +1496,7 @@ config HOTPLUG_CPU ...@@ -1496,6 +1496,7 @@ config HOTPLUG_CPU
config ARM_PSCI config ARM_PSCI
bool "Support for the ARM Power State Coordination Interface (PSCI)" bool "Support for the ARM Power State Coordination Interface (PSCI)"
depends on CPU_V7 depends on CPU_V7
select ARM_PSCI_FW
help help
Say Y here if you want Linux to communicate with system firmware Say Y here if you want Linux to communicate with system firmware
implementing the PSCI specification for CPU-centric power implementing the PSCI specification for CPU-centric power
......
...@@ -14,34 +14,11 @@ ...@@ -14,34 +14,11 @@
#ifndef __ASM_ARM_PSCI_H #ifndef __ASM_ARM_PSCI_H
#define __ASM_ARM_PSCI_H #define __ASM_ARM_PSCI_H
#define PSCI_POWER_STATE_TYPE_STANDBY 0
#define PSCI_POWER_STATE_TYPE_POWER_DOWN 1
struct psci_power_state {
u16 id;
u8 type;
u8 affinity_level;
};
struct psci_operations {
int (*cpu_suspend)(struct psci_power_state state,
unsigned long entry_point);
int (*cpu_off)(struct psci_power_state state);
int (*cpu_on)(unsigned long cpuid, unsigned long entry_point);
int (*migrate)(unsigned long cpuid);
int (*affinity_info)(unsigned long target_affinity,
unsigned long lowest_affinity_level);
int (*migrate_info_type)(void);
};
extern struct psci_operations psci_ops;
extern struct smp_operations psci_smp_ops; extern struct smp_operations psci_smp_ops;
#ifdef CONFIG_ARM_PSCI #ifdef CONFIG_ARM_PSCI
int psci_init(void);
bool psci_smp_available(void); bool psci_smp_available(void);
#else #else
static inline int psci_init(void) { return 0; }
static inline bool psci_smp_available(void) { return false; } static inline bool psci_smp_available(void) { return false; }
#endif #endif
......
...@@ -88,7 +88,7 @@ obj-$(CONFIG_EARLY_PRINTK) += early_printk.o ...@@ -88,7 +88,7 @@ obj-$(CONFIG_EARLY_PRINTK) += early_printk.o
obj-$(CONFIG_ARM_VIRT_EXT) += hyp-stub.o obj-$(CONFIG_ARM_VIRT_EXT) += hyp-stub.o
ifeq ($(CONFIG_ARM_PSCI),y) ifeq ($(CONFIG_ARM_PSCI),y)
obj-y += psci.o psci-call.o obj-y += psci-call.o
obj-$(CONFIG_SMP) += psci_smp.o obj-$(CONFIG_SMP) += psci_smp.o
endif endif
......
...@@ -17,6 +17,8 @@ ...@@ -17,6 +17,8 @@
#include <linux/smp.h> #include <linux/smp.h>
#include <linux/of.h> #include <linux/of.h>
#include <linux/delay.h> #include <linux/delay.h>
#include <linux/psci.h>
#include <uapi/linux/psci.h> #include <uapi/linux/psci.h>
#include <asm/psci.h> #include <asm/psci.h>
...@@ -51,19 +53,31 @@ static int psci_boot_secondary(unsigned int cpu, struct task_struct *idle) ...@@ -51,19 +53,31 @@ static int psci_boot_secondary(unsigned int cpu, struct task_struct *idle)
{ {
if (psci_ops.cpu_on) if (psci_ops.cpu_on)
return psci_ops.cpu_on(cpu_logical_map(cpu), return psci_ops.cpu_on(cpu_logical_map(cpu),
__pa(secondary_startup)); virt_to_idmap(&secondary_startup));
return -ENODEV; return -ENODEV;
} }
#ifdef CONFIG_HOTPLUG_CPU #ifdef CONFIG_HOTPLUG_CPU
int psci_cpu_disable(unsigned int cpu)
{
/* Fail early if we don't have CPU_OFF support */
if (!psci_ops.cpu_off)
return -EOPNOTSUPP;
/* Trusted OS will deny CPU_OFF */
if (psci_tos_resident_on(cpu))
return -EPERM;
return 0;
}
void __ref psci_cpu_die(unsigned int cpu) void __ref psci_cpu_die(unsigned int cpu)
{ {
const struct psci_power_state ps = { u32 state = PSCI_POWER_STATE_TYPE_POWER_DOWN <<
.type = PSCI_POWER_STATE_TYPE_POWER_DOWN, PSCI_0_2_POWER_STATE_TYPE_SHIFT;
};
if (psci_ops.cpu_off) if (psci_ops.cpu_off)
psci_ops.cpu_off(ps); psci_ops.cpu_off(state);
/* We should never return */ /* We should never return */
panic("psci: cpu %d failed to shutdown\n", cpu); panic("psci: cpu %d failed to shutdown\n", cpu);
...@@ -109,6 +123,7 @@ bool __init psci_smp_available(void) ...@@ -109,6 +123,7 @@ bool __init psci_smp_available(void)
struct smp_operations __initdata psci_smp_ops = { struct smp_operations __initdata psci_smp_ops = {
.smp_boot_secondary = psci_boot_secondary, .smp_boot_secondary = psci_boot_secondary,
#ifdef CONFIG_HOTPLUG_CPU #ifdef CONFIG_HOTPLUG_CPU
.cpu_disable = psci_cpu_disable,
.cpu_die = psci_cpu_die, .cpu_die = psci_cpu_die,
.cpu_kill = psci_cpu_kill, .cpu_kill = psci_cpu_kill,
#endif #endif
......
...@@ -31,6 +31,7 @@ ...@@ -31,6 +31,7 @@
#include <linux/bug.h> #include <linux/bug.h>
#include <linux/compiler.h> #include <linux/compiler.h>
#include <linux/sort.h> #include <linux/sort.h>
#include <linux/psci.h>
#include <asm/unified.h> #include <asm/unified.h>
#include <asm/cp15.h> #include <asm/cp15.h>
...@@ -972,7 +973,7 @@ void __init setup_arch(char **cmdline_p) ...@@ -972,7 +973,7 @@ void __init setup_arch(char **cmdline_p)
unflatten_device_tree(); unflatten_device_tree();
arm_dt_init_cpu_maps(); arm_dt_init_cpu_maps();
psci_init(); psci_dt_init();
xen_early_init(); xen_early_init();
#ifdef CONFIG_SMP #ifdef CONFIG_SMP
if (is_smp()) { if (is_smp()) {
......
...@@ -28,8 +28,8 @@ ...@@ -28,8 +28,8 @@
#include <linux/reboot.h> #include <linux/reboot.h>
#include <linux/amba/bus.h> #include <linux/amba/bus.h>
#include <linux/platform_device.h> #include <linux/platform_device.h>
#include <linux/psci.h>
#include <asm/psci.h>
#include <asm/hardware/cache-l2x0.h> #include <asm/hardware/cache-l2x0.h>
#include <asm/mach/arch.h> #include <asm/mach/arch.h>
#include <asm/mach/map.h> #include <asm/mach/map.h>
......
...@@ -16,19 +16,21 @@ ...@@ -16,19 +16,21 @@
#include <linux/cpu_pm.h> #include <linux/cpu_pm.h>
#include <linux/init.h> #include <linux/init.h>
#include <linux/psci.h>
#include <linux/suspend.h> #include <linux/suspend.h>
#include <asm/suspend.h> #include <asm/suspend.h>
#include <asm/psci.h>
#include <uapi/linux/psci.h>
#define HIGHBANK_SUSPEND_PARAM \
((0 << PSCI_0_2_POWER_STATE_ID_SHIFT) | \
(1 << PSCI_0_2_POWER_STATE_AFFL_SHIFT) | \
(PSCI_POWER_STATE_TYPE_POWER_DOWN << PSCI_0_2_POWER_STATE_TYPE_SHIFT))
static int highbank_suspend_finish(unsigned long val) static int highbank_suspend_finish(unsigned long val)
{ {
const struct psci_power_state ps = { return psci_ops.cpu_suspend(HIGHBANK_SUSPEND_PARAM, __pa(cpu_resume));
.type = PSCI_POWER_STATE_TYPE_POWER_DOWN,
.affinity_level = 1,
};
return psci_ops.cpu_suspend(ps, __pa(cpu_resume));
} }
static int highbank_pm_enter(suspend_state_t state) static int highbank_pm_enter(suspend_state_t state)
......
...@@ -20,6 +20,7 @@ config ARM64 ...@@ -20,6 +20,7 @@ config ARM64
select ARM_GIC_V2M if PCI_MSI select ARM_GIC_V2M if PCI_MSI
select ARM_GIC_V3 select ARM_GIC_V3
select ARM_GIC_V3_ITS if PCI_MSI select ARM_GIC_V3_ITS if PCI_MSI
select ARM_PSCI_FW
select BUILDTIME_EXTABLE_SORT select BUILDTIME_EXTABLE_SORT
select CLONE_BACKWARDS select CLONE_BACKWARDS
select COMMON_CLK select COMMON_CLK
......
...@@ -12,11 +12,11 @@ ...@@ -12,11 +12,11 @@
#ifndef _ASM_ACPI_H #ifndef _ASM_ACPI_H
#define _ASM_ACPI_H #define _ASM_ACPI_H
#include <linux/mm.h>
#include <linux/irqchip/arm-gic-acpi.h> #include <linux/irqchip/arm-gic-acpi.h>
#include <linux/mm.h>
#include <linux/psci.h>
#include <asm/cputype.h> #include <asm/cputype.h>
#include <asm/psci.h>
#include <asm/smp_plat.h> #include <asm/smp_plat.h>
/* Macros for consistency checks of the GICC subtable of MADT */ /* Macros for consistency checks of the GICC subtable of MADT */
......
...@@ -18,23 +18,17 @@ ...@@ -18,23 +18,17 @@
#include <linux/init.h> #include <linux/init.h>
#include <linux/of.h> #include <linux/of.h>
#include <linux/smp.h> #include <linux/smp.h>
#include <linux/reboot.h>
#include <linux/pm.h>
#include <linux/delay.h> #include <linux/delay.h>
#include <linux/psci.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <uapi/linux/psci.h> #include <uapi/linux/psci.h>
#include <asm/compiler.h> #include <asm/compiler.h>
#include <asm/cputype.h>
#include <asm/cpu_ops.h> #include <asm/cpu_ops.h>
#include <asm/errno.h> #include <asm/errno.h>
#include <asm/psci.h>
#include <asm/smp_plat.h> #include <asm/smp_plat.h>
#include <asm/suspend.h> #include <asm/suspend.h>
#include <asm/system_misc.h>
#define PSCI_POWER_STATE_TYPE_STANDBY 0
#define PSCI_POWER_STATE_TYPE_POWER_DOWN 1
static bool psci_power_state_loses_context(u32 state) static bool psci_power_state_loses_context(u32 state)
{ {
...@@ -50,122 +44,8 @@ static bool psci_power_state_is_valid(u32 state) ...@@ -50,122 +44,8 @@ static bool psci_power_state_is_valid(u32 state)
return !(state & ~valid_mask); return !(state & ~valid_mask);
} }
/*
* The CPU any Trusted OS is resident on. The trusted OS may reject CPU_OFF
* calls to its resident CPU, so we must avoid issuing those. We never migrate
* a Trusted OS even if it claims to be capable of migration -- doing so will
* require cooperation with a Trusted OS driver.
*/
static int resident_cpu = -1;
struct psci_operations {
int (*cpu_suspend)(u32 state, unsigned long entry_point);
int (*cpu_off)(u32 state);
int (*cpu_on)(unsigned long cpuid, unsigned long entry_point);
int (*migrate)(unsigned long cpuid);
int (*affinity_info)(unsigned long target_affinity,
unsigned long lowest_affinity_level);
int (*migrate_info_type)(void);
};
static struct psci_operations psci_ops;
typedef unsigned long (psci_fn)(unsigned long, unsigned long,
unsigned long, unsigned long);
asmlinkage psci_fn __invoke_psci_fn_hvc;
asmlinkage psci_fn __invoke_psci_fn_smc;
static psci_fn *invoke_psci_fn;
enum psci_function {
PSCI_FN_CPU_SUSPEND,
PSCI_FN_CPU_ON,
PSCI_FN_CPU_OFF,
PSCI_FN_MIGRATE,
PSCI_FN_MAX,
};
static DEFINE_PER_CPU_READ_MOSTLY(u32 *, psci_power_state); static DEFINE_PER_CPU_READ_MOSTLY(u32 *, psci_power_state);
static u32 psci_function_id[PSCI_FN_MAX];
static int psci_to_linux_errno(int errno)
{
switch (errno) {
case PSCI_RET_SUCCESS:
return 0;
case PSCI_RET_NOT_SUPPORTED:
return -EOPNOTSUPP;
case PSCI_RET_INVALID_PARAMS:
return -EINVAL;
case PSCI_RET_DENIED:
return -EPERM;
};
return -EINVAL;
}
static u32 psci_get_version(void)
{
return invoke_psci_fn(PSCI_0_2_FN_PSCI_VERSION, 0, 0, 0);
}
static int psci_cpu_suspend(u32 state, unsigned long entry_point)
{
int err;
u32 fn;
fn = psci_function_id[PSCI_FN_CPU_SUSPEND];
err = invoke_psci_fn(fn, state, entry_point, 0);
return psci_to_linux_errno(err);
}
static int psci_cpu_off(u32 state)
{
int err;
u32 fn;
fn = psci_function_id[PSCI_FN_CPU_OFF];
err = invoke_psci_fn(fn, state, 0, 0);
return psci_to_linux_errno(err);
}
static int psci_cpu_on(unsigned long cpuid, unsigned long entry_point)
{
int err;
u32 fn;
fn = psci_function_id[PSCI_FN_CPU_ON];
err = invoke_psci_fn(fn, cpuid, entry_point, 0);
return psci_to_linux_errno(err);
}
static int psci_migrate(unsigned long cpuid)
{
int err;
u32 fn;
fn = psci_function_id[PSCI_FN_MIGRATE];
err = invoke_psci_fn(fn, cpuid, 0, 0);
return psci_to_linux_errno(err);
}
static int psci_affinity_info(unsigned long target_affinity,
unsigned long lowest_affinity_level)
{
return invoke_psci_fn(PSCI_0_2_FN64_AFFINITY_INFO, target_affinity,
lowest_affinity_level, 0);
}
static int psci_migrate_info_type(void)
{
return invoke_psci_fn(PSCI_0_2_FN_MIGRATE_INFO_TYPE, 0, 0, 0);
}
static unsigned long psci_migrate_info_up_cpu(void)
{
return invoke_psci_fn(PSCI_0_2_FN64_MIGRATE_INFO_UP_CPU, 0, 0, 0);
}
static int __maybe_unused cpu_psci_cpu_init_idle(unsigned int cpu) static int __maybe_unused cpu_psci_cpu_init_idle(unsigned int cpu)
{ {
int i, ret, count = 0; int i, ret, count = 0;
...@@ -230,238 +110,6 @@ static int __maybe_unused cpu_psci_cpu_init_idle(unsigned int cpu) ...@@ -230,238 +110,6 @@ static int __maybe_unused cpu_psci_cpu_init_idle(unsigned int cpu)
return ret; return ret;
} }
static int get_set_conduit_method(struct device_node *np)
{
const char *method;
pr_info("probing for conduit method from DT.\n");
if (of_property_read_string(np, "method", &method)) {
pr_warn("missing \"method\" property\n");
return -ENXIO;
}
if (!strcmp("hvc", method)) {
invoke_psci_fn = __invoke_psci_fn_hvc;
} else if (!strcmp("smc", method)) {
invoke_psci_fn = __invoke_psci_fn_smc;
} else {
pr_warn("invalid \"method\" property: %s\n", method);
return -EINVAL;
}
return 0;
}
static void psci_sys_reset(enum reboot_mode reboot_mode, const char *cmd)
{
invoke_psci_fn(PSCI_0_2_FN_SYSTEM_RESET, 0, 0, 0);
}
static void psci_sys_poweroff(void)
{
invoke_psci_fn(PSCI_0_2_FN_SYSTEM_OFF, 0, 0, 0);
}
/*
* Detect the presence of a resident Trusted OS which may cause CPU_OFF to
* return DENIED (which would be fatal).
*/
static void __init psci_init_migrate(void)
{
unsigned long cpuid;
int type, cpu;
type = psci_ops.migrate_info_type();
if (type == PSCI_0_2_TOS_MP) {
pr_info("Trusted OS migration not required\n");
return;
}
if (type == PSCI_RET_NOT_SUPPORTED) {
pr_info("MIGRATE_INFO_TYPE not supported.\n");
return;
}
if (type != PSCI_0_2_TOS_UP_MIGRATE &&
type != PSCI_0_2_TOS_UP_NO_MIGRATE) {
pr_err("MIGRATE_INFO_TYPE returned unknown type (%d)\n", type);
return;
}
cpuid = psci_migrate_info_up_cpu();
if (cpuid & ~MPIDR_HWID_BITMASK) {
pr_warn("MIGRATE_INFO_UP_CPU reported invalid physical ID (0x%lx)\n",
cpuid);
return;
}
cpu = get_logical_index(cpuid);
resident_cpu = cpu >= 0 ? cpu : -1;
pr_info("Trusted OS resident on physical CPU 0x%lx\n", cpuid);
}
static void __init psci_0_2_set_functions(void)
{
pr_info("Using standard PSCI v0.2 function IDs\n");
psci_function_id[PSCI_FN_CPU_SUSPEND] = PSCI_0_2_FN64_CPU_SUSPEND;
psci_ops.cpu_suspend = psci_cpu_suspend;
psci_function_id[PSCI_FN_CPU_OFF] = PSCI_0_2_FN_CPU_OFF;
psci_ops.cpu_off = psci_cpu_off;
psci_function_id[PSCI_FN_CPU_ON] = PSCI_0_2_FN64_CPU_ON;
psci_ops.cpu_on = psci_cpu_on;
psci_function_id[PSCI_FN_MIGRATE] = PSCI_0_2_FN64_MIGRATE;
psci_ops.migrate = psci_migrate;
psci_ops.affinity_info = psci_affinity_info;
psci_ops.migrate_info_type = psci_migrate_info_type;
arm_pm_restart = psci_sys_reset;
pm_power_off = psci_sys_poweroff;
}
/*
* Probe function for PSCI firmware versions >= 0.2
*/
static int __init psci_probe(void)
{
u32 ver = psci_get_version();
pr_info("PSCIv%d.%d detected in firmware.\n",
PSCI_VERSION_MAJOR(ver),
PSCI_VERSION_MINOR(ver));
if (PSCI_VERSION_MAJOR(ver) == 0 && PSCI_VERSION_MINOR(ver) < 2) {
pr_err("Conflicting PSCI version detected.\n");
return -EINVAL;
}
psci_0_2_set_functions();
psci_init_migrate();
return 0;
}
typedef int (*psci_initcall_t)(const struct device_node *);
/*
* PSCI init function for PSCI versions >=0.2
*
* Probe based on PSCI PSCI_VERSION function
*/
static int __init psci_0_2_init(struct device_node *np)
{
int err;
err = get_set_conduit_method(np);
if (err)
goto out_put_node;
/*
* Starting with v0.2, the PSCI specification introduced a call
* (PSCI_VERSION) that allows probing the firmware version, so
* that PSCI function IDs and version specific initialization
* can be carried out according to the specific version reported
* by firmware
*/
err = psci_probe();
out_put_node:
of_node_put(np);
return err;
}
/*
* PSCI < v0.2 get PSCI Function IDs via DT.
*/
static int __init psci_0_1_init(struct device_node *np)
{
u32 id;
int err;
err = get_set_conduit_method(np);
if (err)
goto out_put_node;
pr_info("Using PSCI v0.1 Function IDs from DT\n");
if (!of_property_read_u32(np, "cpu_suspend", &id)) {
psci_function_id[PSCI_FN_CPU_SUSPEND] = id;
psci_ops.cpu_suspend = psci_cpu_suspend;
}
if (!of_property_read_u32(np, "cpu_off", &id)) {
psci_function_id[PSCI_FN_CPU_OFF] = id;
psci_ops.cpu_off = psci_cpu_off;
}
if (!of_property_read_u32(np, "cpu_on", &id)) {
psci_function_id[PSCI_FN_CPU_ON] = id;
psci_ops.cpu_on = psci_cpu_on;
}
if (!of_property_read_u32(np, "migrate", &id)) {
psci_function_id[PSCI_FN_MIGRATE] = id;
psci_ops.migrate = psci_migrate;
}
out_put_node:
of_node_put(np);
return err;
}
static const struct of_device_id psci_of_match[] __initconst = {
{ .compatible = "arm,psci", .data = psci_0_1_init},
{ .compatible = "arm,psci-0.2", .data = psci_0_2_init},
{},
};
int __init psci_dt_init(void)
{
struct device_node *np;
const struct of_device_id *matched_np;
psci_initcall_t init_fn;
np = of_find_matching_node_and_match(NULL, psci_of_match, &matched_np);
if (!np)
return -ENODEV;
init_fn = (psci_initcall_t)matched_np->data;
return init_fn(np);
}
#ifdef CONFIG_ACPI
/*
* We use PSCI 0.2+ when ACPI is deployed on ARM64 and it's
* explicitly clarified in SBBR
*/
int __init psci_acpi_init(void)
{
if (!acpi_psci_present()) {
pr_info("is not implemented in ACPI.\n");
return -EOPNOTSUPP;
}
pr_info("probing for conduit method from ACPI.\n");
if (acpi_psci_use_hvc())
invoke_psci_fn = __invoke_psci_fn_hvc;
else
invoke_psci_fn = __invoke_psci_fn_smc;
return psci_probe();
}
#endif
#ifdef CONFIG_SMP #ifdef CONFIG_SMP
static int __init cpu_psci_cpu_init(unsigned int cpu) static int __init cpu_psci_cpu_init(unsigned int cpu)
...@@ -489,11 +137,6 @@ static int cpu_psci_cpu_boot(unsigned int cpu) ...@@ -489,11 +137,6 @@ static int cpu_psci_cpu_boot(unsigned int cpu)
} }
#ifdef CONFIG_HOTPLUG_CPU #ifdef CONFIG_HOTPLUG_CPU
static bool psci_tos_resident_on(int cpu)
{
return cpu == resident_cpu;
}
static int cpu_psci_cpu_disable(unsigned int cpu) static int cpu_psci_cpu_disable(unsigned int cpu)
{ {
/* Fail early if we don't have CPU_OFF support */ /* Fail early if we don't have CPU_OFF support */
......
...@@ -46,6 +46,7 @@ ...@@ -46,6 +46,7 @@
#include <linux/of_platform.h> #include <linux/of_platform.h>
#include <linux/efi.h> #include <linux/efi.h>
#include <linux/personality.h> #include <linux/personality.h>
#include <linux/psci.h>
#include <asm/acpi.h> #include <asm/acpi.h>
#include <asm/fixmap.h> #include <asm/fixmap.h>
...@@ -61,7 +62,6 @@ ...@@ -61,7 +62,6 @@
#include <asm/tlbflush.h> #include <asm/tlbflush.h>
#include <asm/traps.h> #include <asm/traps.h>
#include <asm/memblock.h> #include <asm/memblock.h>
#include <asm/psci.h>
#include <asm/efi.h> #include <asm/efi.h>
#include <asm/virt.h> #include <asm/virt.h>
#include <asm/xen/hypervisor.h> #include <asm/xen/hypervisor.h>
......
...@@ -25,16 +25,21 @@ ...@@ -25,16 +25,21 @@
#include <linux/init.h> #include <linux/init.h>
#include <linux/mm.h> #include <linux/mm.h>
#include <linux/platform_device.h> #include <linux/platform_device.h>
#include <linux/psci.h>
#include <asm/cpuidle.h> #include <asm/cpuidle.h>
#include <asm/suspend.h> #include <asm/suspend.h>
#include <asm/psci.h>
#include <uapi/linux/psci.h>
#define CALXEDA_IDLE_PARAM \
((0 << PSCI_0_2_POWER_STATE_ID_SHIFT) | \
(0 << PSCI_0_2_POWER_STATE_AFFL_SHIFT) | \
(PSCI_POWER_STATE_TYPE_POWER_DOWN << PSCI_0_2_POWER_STATE_TYPE_SHIFT))
static int calxeda_idle_finish(unsigned long val) static int calxeda_idle_finish(unsigned long val)
{ {
const struct psci_power_state ps = { return psci_ops.cpu_suspend(CALXEDA_IDLE_PARAM, __pa(cpu_resume));
.type = PSCI_POWER_STATE_TYPE_POWER_DOWN,
};
return psci_ops.cpu_suspend(ps, __pa(cpu_resume));
} }
static int calxeda_pwrdown_idle(struct cpuidle_device *dev, static int calxeda_pwrdown_idle(struct cpuidle_device *dev,
......
...@@ -5,6 +5,9 @@ ...@@ -5,6 +5,9 @@
menu "Firmware Drivers" menu "Firmware Drivers"
config ARM_PSCI_FW
bool
config EDD config EDD
tristate "BIOS Enhanced Disk Drive calls determine boot disk" tristate "BIOS Enhanced Disk Drive calls determine boot disk"
depends on X86 depends on X86
......
# #
# Makefile for the linux kernel. # Makefile for the linux kernel.
# #
obj-$(CONFIG_ARM_PSCI_FW) += psci.o
obj-$(CONFIG_DMI) += dmi_scan.o obj-$(CONFIG_DMI) += dmi_scan.o
obj-$(CONFIG_DMI_SYSFS) += dmi-sysfs.o obj-$(CONFIG_DMI_SYSFS) += dmi-sysfs.o
obj-$(CONFIG_EDD) += edd.o obj-$(CONFIG_EDD) += edd.o
......
...@@ -8,39 +8,63 @@ ...@@ -8,39 +8,63 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* Copyright (C) 2012 ARM Limited * Copyright (C) 2015 ARM Limited
*
* Author: Will Deacon <will.deacon@arm.com>
*/ */
#define pr_fmt(fmt) "psci: " fmt #define pr_fmt(fmt) "psci: " fmt
#include <linux/init.h> #include <linux/errno.h>
#include <linux/linkage.h>
#include <linux/of.h> #include <linux/of.h>
#include <linux/reboot.h>
#include <linux/pm.h> #include <linux/pm.h>
#include <linux/printk.h>
#include <linux/psci.h>
#include <linux/reboot.h>
#include <uapi/linux/psci.h> #include <uapi/linux/psci.h>
#include <asm/compiler.h> #include <asm/cputype.h>
#include <asm/errno.h>
#include <asm/psci.h>
#include <asm/system_misc.h> #include <asm/system_misc.h>
#include <asm/smp_plat.h>
struct psci_operations psci_ops; /*
* While a 64-bit OS can make calls with SMC32 calling conventions, for some
* calls it is necessary to use SMC64 to pass or return 64-bit values. For such
* calls PSCI_0_2_FN_NATIVE(x) will choose the appropriate (native-width)
* function ID.
*/
#ifdef CONFIG_64BIT
#define PSCI_0_2_FN_NATIVE(name) PSCI_0_2_FN64_##name
#else
#define PSCI_0_2_FN_NATIVE(name) PSCI_0_2_FN_##name
#endif
static int (*invoke_psci_fn)(u32, u32, u32, u32); /*
typedef int (*psci_initcall_t)(const struct device_node *); * The CPU any Trusted OS is resident on. The trusted OS may reject CPU_OFF
* calls to its resident CPU, so we must avoid issuing those. We never migrate
* a Trusted OS even if it claims to be capable of migration -- doing so will
* require cooperation with a Trusted OS driver.
*/
static int resident_cpu = -1;
bool psci_tos_resident_on(int cpu)
{
return cpu == resident_cpu;
}
struct psci_operations psci_ops;
asmlinkage int __invoke_psci_fn_hvc(u32, u32, u32, u32); typedef unsigned long (psci_fn)(unsigned long, unsigned long,
asmlinkage int __invoke_psci_fn_smc(u32, u32, u32, u32); unsigned long, unsigned long);
asmlinkage psci_fn __invoke_psci_fn_hvc;
asmlinkage psci_fn __invoke_psci_fn_smc;
static psci_fn *invoke_psci_fn;
enum psci_function { enum psci_function {
PSCI_FN_CPU_SUSPEND, PSCI_FN_CPU_SUSPEND,
PSCI_FN_CPU_ON, PSCI_FN_CPU_ON,
PSCI_FN_CPU_OFF, PSCI_FN_CPU_OFF,
PSCI_FN_MIGRATE, PSCI_FN_MIGRATE,
PSCI_FN_AFFINITY_INFO,
PSCI_FN_MIGRATE_INFO_TYPE,
PSCI_FN_MAX, PSCI_FN_MAX,
}; };
...@@ -62,44 +86,28 @@ static int psci_to_linux_errno(int errno) ...@@ -62,44 +86,28 @@ static int psci_to_linux_errno(int errno)
return -EINVAL; return -EINVAL;
} }
static u32 psci_power_state_pack(struct psci_power_state state) static u32 psci_get_version(void)
{
return ((state.id << PSCI_0_2_POWER_STATE_ID_SHIFT)
& PSCI_0_2_POWER_STATE_ID_MASK) |
((state.type << PSCI_0_2_POWER_STATE_TYPE_SHIFT)
& PSCI_0_2_POWER_STATE_TYPE_MASK) |
((state.affinity_level << PSCI_0_2_POWER_STATE_AFFL_SHIFT)
& PSCI_0_2_POWER_STATE_AFFL_MASK);
}
static int psci_get_version(void)
{ {
int err; return invoke_psci_fn(PSCI_0_2_FN_PSCI_VERSION, 0, 0, 0);
err = invoke_psci_fn(PSCI_0_2_FN_PSCI_VERSION, 0, 0, 0);
return err;
} }
static int psci_cpu_suspend(struct psci_power_state state, static int psci_cpu_suspend(u32 state, unsigned long entry_point)
unsigned long entry_point)
{ {
int err; int err;
u32 fn, power_state; u32 fn;
fn = psci_function_id[PSCI_FN_CPU_SUSPEND]; fn = psci_function_id[PSCI_FN_CPU_SUSPEND];
power_state = psci_power_state_pack(state); err = invoke_psci_fn(fn, state, entry_point, 0);
err = invoke_psci_fn(fn, power_state, entry_point, 0);
return psci_to_linux_errno(err); return psci_to_linux_errno(err);
} }
static int psci_cpu_off(struct psci_power_state state) static int psci_cpu_off(u32 state)
{ {
int err; int err;
u32 fn, power_state; u32 fn;
fn = psci_function_id[PSCI_FN_CPU_OFF]; fn = psci_function_id[PSCI_FN_CPU_OFF];
power_state = psci_power_state_pack(state); err = invoke_psci_fn(fn, state, 0, 0);
err = invoke_psci_fn(fn, power_state, 0, 0);
return psci_to_linux_errno(err); return psci_to_linux_errno(err);
} }
...@@ -126,22 +134,19 @@ static int psci_migrate(unsigned long cpuid) ...@@ -126,22 +134,19 @@ static int psci_migrate(unsigned long cpuid)
static int psci_affinity_info(unsigned long target_affinity, static int psci_affinity_info(unsigned long target_affinity,
unsigned long lowest_affinity_level) unsigned long lowest_affinity_level)
{ {
int err; return invoke_psci_fn(PSCI_0_2_FN_NATIVE(AFFINITY_INFO),
u32 fn; target_affinity, lowest_affinity_level, 0);
fn = psci_function_id[PSCI_FN_AFFINITY_INFO];
err = invoke_psci_fn(fn, target_affinity, lowest_affinity_level, 0);
return err;
} }
static int psci_migrate_info_type(void) static int psci_migrate_info_type(void)
{ {
int err; return invoke_psci_fn(PSCI_0_2_FN_MIGRATE_INFO_TYPE, 0, 0, 0);
u32 fn; }
fn = psci_function_id[PSCI_FN_MIGRATE_INFO_TYPE]; static unsigned long psci_migrate_info_up_cpu(void)
err = invoke_psci_fn(fn, 0, 0, 0); {
return err; return invoke_psci_fn(PSCI_0_2_FN_NATIVE(MIGRATE_INFO_UP_CPU),
0, 0, 0);
} }
static int get_set_conduit_method(struct device_node *np) static int get_set_conduit_method(struct device_node *np)
...@@ -177,61 +182,115 @@ static void psci_sys_poweroff(void) ...@@ -177,61 +182,115 @@ static void psci_sys_poweroff(void)
} }
/* /*
* PSCI Function IDs for v0.2+ are well defined so use * Detect the presence of a resident Trusted OS which may cause CPU_OFF to
* standard values. * return DENIED (which would be fatal).
*/ */
static int psci_0_2_init(struct device_node *np) static void __init psci_init_migrate(void)
{ {
int err, ver; unsigned long cpuid;
int type, cpu = -1;
err = get_set_conduit_method(np);
if (err) type = psci_ops.migrate_info_type();
goto out_put_node;
ver = psci_get_version(); if (type == PSCI_0_2_TOS_MP) {
pr_info("Trusted OS migration not required\n");
return;
}
if (ver == PSCI_RET_NOT_SUPPORTED) { if (type == PSCI_RET_NOT_SUPPORTED) {
/* PSCI v0.2 mandates implementation of PSCI_ID_VERSION. */ pr_info("MIGRATE_INFO_TYPE not supported.\n");
pr_err("PSCI firmware does not comply with the v0.2 spec.\n"); return;
err = -EOPNOTSUPP; }
goto out_put_node;
} else {
pr_info("PSCIv%d.%d detected in firmware.\n",
PSCI_VERSION_MAJOR(ver),
PSCI_VERSION_MINOR(ver));
if (PSCI_VERSION_MAJOR(ver) == 0 && if (type != PSCI_0_2_TOS_UP_MIGRATE &&
PSCI_VERSION_MINOR(ver) < 2) { type != PSCI_0_2_TOS_UP_NO_MIGRATE) {
err = -EINVAL; pr_err("MIGRATE_INFO_TYPE returned unknown type (%d)\n", type);
pr_err("Conflicting PSCI version detected.\n"); return;
goto out_put_node;
} }
cpuid = psci_migrate_info_up_cpu();
if (cpuid & ~MPIDR_HWID_BITMASK) {
pr_warn("MIGRATE_INFO_UP_CPU reported invalid physical ID (0x%lx)\n",
cpuid);
return;
} }
cpu = get_logical_index(cpuid);
resident_cpu = cpu >= 0 ? cpu : -1;
pr_info("Trusted OS resident on physical CPU 0x%lx\n", cpuid);
}
static void __init psci_0_2_set_functions(void)
{
pr_info("Using standard PSCI v0.2 function IDs\n"); pr_info("Using standard PSCI v0.2 function IDs\n");
psci_function_id[PSCI_FN_CPU_SUSPEND] = PSCI_0_2_FN_CPU_SUSPEND; psci_function_id[PSCI_FN_CPU_SUSPEND] = PSCI_0_2_FN_NATIVE(CPU_SUSPEND);
psci_ops.cpu_suspend = psci_cpu_suspend; psci_ops.cpu_suspend = psci_cpu_suspend;
psci_function_id[PSCI_FN_CPU_OFF] = PSCI_0_2_FN_CPU_OFF; psci_function_id[PSCI_FN_CPU_OFF] = PSCI_0_2_FN_CPU_OFF;
psci_ops.cpu_off = psci_cpu_off; psci_ops.cpu_off = psci_cpu_off;
psci_function_id[PSCI_FN_CPU_ON] = PSCI_0_2_FN_CPU_ON; psci_function_id[PSCI_FN_CPU_ON] = PSCI_0_2_FN_NATIVE(CPU_ON);
psci_ops.cpu_on = psci_cpu_on; psci_ops.cpu_on = psci_cpu_on;
psci_function_id[PSCI_FN_MIGRATE] = PSCI_0_2_FN_MIGRATE; psci_function_id[PSCI_FN_MIGRATE] = PSCI_0_2_FN_NATIVE(MIGRATE);
psci_ops.migrate = psci_migrate; psci_ops.migrate = psci_migrate;
psci_function_id[PSCI_FN_AFFINITY_INFO] = PSCI_0_2_FN_AFFINITY_INFO;
psci_ops.affinity_info = psci_affinity_info; psci_ops.affinity_info = psci_affinity_info;
psci_function_id[PSCI_FN_MIGRATE_INFO_TYPE] =
PSCI_0_2_FN_MIGRATE_INFO_TYPE;
psci_ops.migrate_info_type = psci_migrate_info_type; psci_ops.migrate_info_type = psci_migrate_info_type;
arm_pm_restart = psci_sys_reset; arm_pm_restart = psci_sys_reset;
pm_power_off = psci_sys_poweroff; pm_power_off = psci_sys_poweroff;
}
/*
* Probe function for PSCI firmware versions >= 0.2
*/
static int __init psci_probe(void)
{
u32 ver = psci_get_version();
pr_info("PSCIv%d.%d detected in firmware.\n",
PSCI_VERSION_MAJOR(ver),
PSCI_VERSION_MINOR(ver));
if (PSCI_VERSION_MAJOR(ver) == 0 && PSCI_VERSION_MINOR(ver) < 2) {
pr_err("Conflicting PSCI version detected.\n");
return -EINVAL;
}
psci_0_2_set_functions();
psci_init_migrate();
return 0;
}
typedef int (*psci_initcall_t)(const struct device_node *);
/*
* PSCI init function for PSCI versions >=0.2
*
* Probe based on PSCI PSCI_VERSION function
*/
static int __init psci_0_2_init(struct device_node *np)
{
int err;
err = get_set_conduit_method(np);
if (err)
goto out_put_node;
/*
* Starting with v0.2, the PSCI specification introduced a call
* (PSCI_VERSION) that allows probing the firmware version, so
* that PSCI function IDs and version specific initialization
* can be carried out according to the specific version reported
* by firmware
*/
err = psci_probe();
out_put_node: out_put_node:
of_node_put(np); of_node_put(np);
...@@ -241,7 +300,7 @@ static int psci_0_2_init(struct device_node *np) ...@@ -241,7 +300,7 @@ static int psci_0_2_init(struct device_node *np)
/* /*
* PSCI < v0.2 get PSCI Function IDs via DT. * PSCI < v0.2 get PSCI Function IDs via DT.
*/ */
static int psci_0_1_init(struct device_node *np) static int __init psci_0_1_init(struct device_node *np)
{ {
u32 id; u32 id;
int err; int err;
...@@ -284,16 +343,40 @@ static const struct of_device_id psci_of_match[] __initconst = { ...@@ -284,16 +343,40 @@ static const struct of_device_id psci_of_match[] __initconst = {
{}, {},
}; };
int __init psci_init(void) int __init psci_dt_init(void)
{ {
struct device_node *np; struct device_node *np;
const struct of_device_id *matched_np; const struct of_device_id *matched_np;
psci_initcall_t init_fn; psci_initcall_t init_fn;
np = of_find_matching_node_and_match(NULL, psci_of_match, &matched_np); np = of_find_matching_node_and_match(NULL, psci_of_match, &matched_np);
if (!np) if (!np)
return -ENODEV; return -ENODEV;
init_fn = (psci_initcall_t)matched_np->data; init_fn = (psci_initcall_t)matched_np->data;
return init_fn(np); return init_fn(np);
} }
#ifdef CONFIG_ACPI
/*
* We use PSCI 0.2+ when ACPI is deployed on ARM64 and it's
* explicitly clarified in SBBR
*/
int __init psci_acpi_init(void)
{
if (!acpi_psci_present()) {
pr_info("is not implemented in ACPI.\n");
return -EOPNOTSUPP;
}
pr_info("probing for conduit method from ACPI.\n");
if (acpi_psci_use_hvc())
invoke_psci_fn = __invoke_psci_fn_hvc;
else
invoke_psci_fn = __invoke_psci_fn_smc;
return psci_probe();
}
#endif
...@@ -8,15 +8,39 @@ ...@@ -8,15 +8,39 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* Copyright (C) 2013 ARM Limited * Copyright (C) 2015 ARM Limited
*/ */
#ifndef __ASM_PSCI_H #ifndef __LINUX_PSCI_H
#define __ASM_PSCI_H #define __LINUX_PSCI_H
#include <linux/init.h>
#include <linux/types.h>
#define PSCI_POWER_STATE_TYPE_STANDBY 0
#define PSCI_POWER_STATE_TYPE_POWER_DOWN 1
bool psci_tos_resident_on(int cpu);
struct psci_operations {
int (*cpu_suspend)(u32 state, unsigned long entry_point);
int (*cpu_off)(u32 state);
int (*cpu_on)(unsigned long cpuid, unsigned long entry_point);
int (*migrate)(unsigned long cpuid);
int (*affinity_info)(unsigned long target_affinity,
unsigned long lowest_affinity_level);
int (*migrate_info_type)(void);
};
extern struct psci_operations psci_ops;
#if defined(CONFIG_ARM_PSCI_FW)
int __init psci_dt_init(void); int __init psci_dt_init(void);
#else
static inline int psci_dt_init(void) { return 0; }
#endif
#ifdef CONFIG_ACPI #if defined(CONFIG_ARM_PSCI_FW) && defined(CONFIG_ACPI)
int __init psci_acpi_init(void); int __init psci_acpi_init(void);
bool __init acpi_psci_present(void); bool __init acpi_psci_present(void);
bool __init acpi_psci_use_hvc(void); bool __init acpi_psci_use_hvc(void);
...@@ -25,4 +49,4 @@ static inline int psci_acpi_init(void) { return 0; } ...@@ -25,4 +49,4 @@ static inline int psci_acpi_init(void) { return 0; }
static inline bool acpi_psci_present(void) { return false; } static inline bool acpi_psci_present(void) { return false; }
#endif #endif
#endif /* __ASM_PSCI_H */ #endif /* __LINUX_PSCI_H */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment