Will Deacon
2012-Dec-17 16:35 UTC
[PATCH v2 4/6] ARM: psci: add support for PSCI invocations from the kernel
This patch adds support for the Power State Coordination Interface defined by ARM, allowing Linux to request CPU-centric power-management operations from firmware implementing the PSCI protocol. Signed-off-by: Will Deacon <will.deacon@arm.com> --- arch/arm/Kconfig | 10 +++ arch/arm/include/asm/psci.h | 36 ++++++++ arch/arm/kernel/Makefile | 1 + arch/arm/kernel/psci.c | 214 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 261 insertions(+) create mode 100644 arch/arm/include/asm/psci.h create mode 100644 arch/arm/kernel/psci.c diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index e4017ea..2a04375 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -1611,6 +1611,16 @@ config HOTPLUG_CPU Say Y here to experiment with turning CPUs off and on. CPUs can be controlled through /sys/devices/system/cpu. +config ARM_PSCI + bool "Support for the ARM Power State Coordination Interface (PSCI)" + depends on CPU_V7 + help + Say Y here if you want Linux 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"). + config LOCAL_TIMERS bool "Use local timer interrupts" depends on SMP diff --git a/arch/arm/include/asm/psci.h b/arch/arm/include/asm/psci.h new file mode 100644 index 0000000..ce0dbe7 --- /dev/null +++ b/arch/arm/include/asm/psci.h @@ -0,0 +1,36 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Copyright (C) 2012 ARM Limited + */ + +#ifndef __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); +}; + +extern struct psci_operations psci_ops; + +#endif /* __ASM_ARM_PSCI_H */ diff --git a/arch/arm/kernel/Makefile b/arch/arm/kernel/Makefile index 5bbec7b..5f3338e 100644 --- a/arch/arm/kernel/Makefile +++ b/arch/arm/kernel/Makefile @@ -82,5 +82,6 @@ obj-$(CONFIG_DEBUG_LL) += debug.o obj-$(CONFIG_EARLY_PRINTK) += early_printk.o obj-$(CONFIG_ARM_VIRT_EXT) += hyp-stub.o +obj-$(CONFIG_ARM_PSCI) += psci.o extra-y := $(head-y) vmlinux.lds diff --git a/arch/arm/kernel/psci.c b/arch/arm/kernel/psci.c new file mode 100644 index 0000000..e7fe909 --- /dev/null +++ b/arch/arm/kernel/psci.c @@ -0,0 +1,214 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Copyright (C) 2012 ARM Limited + * + * Author: Will Deacon <will.deacon@arm.com> + */ + +#define pr_fmt(fmt) "psci: " fmt + +#include <linux/init.h> +#include <linux/of.h> + +#include <asm/compiler.h> +#include <asm/errno.h> +#include <asm/opcodes-sec.h> +#include <asm/opcodes-virt.h> +#include <asm/psci.h> + +struct psci_operations psci_ops; + +static int (*invoke_psci_fn)(u32, u32, u32, u32); + +enum psci_function { + PSCI_FN_CPU_SUSPEND, + PSCI_FN_CPU_ON, + PSCI_FN_CPU_OFF, + PSCI_FN_MIGRATE, + PSCI_FN_MAX, +}; + +static u32 psci_function_id[PSCI_FN_MAX]; + +#define PSCI_RET_SUCCESS 0 +#define PSCI_RET_EOPNOTSUPP -1 +#define PSCI_RET_EINVAL -2 +#define PSCI_RET_EPERM -3 + +static int psci_to_linux_errno(int errno) +{ + switch (errno) { + case PSCI_RET_SUCCESS: + return 0; + case PSCI_RET_EOPNOTSUPP: + return -EOPNOTSUPP; + case PSCI_RET_EINVAL: + return -EINVAL; + case PSCI_RET_EPERM: + return -EPERM; + }; + + return -EINVAL; +} + +#define PSCI_POWER_STATE_ID_MASK 0xffff +#define PSCI_POWER_STATE_ID_SHIFT 0 +#define PSCI_POWER_STATE_TYPE_MASK 0x1 +#define PSCI_POWER_STATE_TYPE_SHIFT 16 +#define PSCI_POWER_STATE_AFFL_MASK 0x3 +#define PSCI_POWER_STATE_AFFL_SHIFT 24 + +static u32 psci_power_state_pack(struct psci_power_state state) +{ + return ((state.id & PSCI_POWER_STATE_ID_MASK) + << PSCI_POWER_STATE_ID_SHIFT) | + ((state.type & PSCI_POWER_STATE_TYPE_MASK) + << PSCI_POWER_STATE_TYPE_SHIFT) | + ((state.affinity_level & PSCI_POWER_STATE_AFFL_MASK) + << PSCI_POWER_STATE_AFFL_SHIFT); +} + +/* + * The following two functions are invoked via the invoke_psci_fn pointer + * and will not be inlined, allowing us to piggyback on the AAPCS. + */ +static int __invoke_psci_fn_hvc(u32 function_id, u32 arg0, u32 arg1, u32 arg2) +{ + asm volatile( + __asmeq("%0", "r0") + __asmeq("%1", "r1") + __asmeq("%2", "r2") + __asmeq("%3", "r3") + __HVC(0) + : "+r" (function_id) + : "r" (arg0), "r" (arg1), "r" (arg2)); + + return function_id; +} + +static int __invoke_psci_fn_smc(u32 function_id, u32 arg0, u32 arg1, u32 arg2) +{ + asm volatile( + __asmeq("%0", "r0") + __asmeq("%1", "r1") + __asmeq("%2", "r2") + __asmeq("%3", "r3") + __SMC(0) + : "+r" (function_id) + : "r" (arg0), "r" (arg1), "r" (arg2)); + + return function_id; +} + +static int psci_cpu_suspend(struct psci_power_state state, + unsigned long entry_point) +{ + int err; + u32 fn, power_state; + + fn = psci_function_id[PSCI_FN_CPU_SUSPEND]; + power_state = psci_power_state_pack(state); + err = invoke_psci_fn(fn, power_state, (u32)entry_point, 0); + return psci_to_linux_errno(err); +} + +static int psci_cpu_off(struct psci_power_state state) +{ + int err; + u32 fn, power_state; + + fn = psci_function_id[PSCI_FN_CPU_OFF]; + power_state = psci_power_state_pack(state); + err = invoke_psci_fn(fn, power_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 const struct of_device_id psci_of_match[] __initconst = { + { .compatible = "arm,psci", }, + {}, +}; + +static int __init psci_init(void) +{ + struct device_node *np; + const char *method; + u32 base, id; + + np = of_find_matching_node(NULL, psci_of_match); + if (!np) + return 0; + + pr_info("probing function IDs from device-tree\n"); + + if (of_property_read_u32(np, "function-base", &base)) { + pr_warning("missing \"function-base\" property\n"); + goto out_put_node; + } + + if (of_property_read_string(np, "method", &method)) { + pr_warning("missing \"method\" property\n"); + goto out_put_node; + } + + 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_warning("invalid \"method\" property: %s\n", method); + goto out_put_node; + } + + if (!of_property_read_u32(np, "cpu_suspend", &id)) { + psci_function_id[PSCI_FN_CPU_SUSPEND] = base + id; + psci_ops.cpu_suspend = psci_cpu_suspend; + } + + if (!of_property_read_u32(np, "cpu_off", &id)) { + psci_function_id[PSCI_FN_CPU_OFF] = base + id; + psci_ops.cpu_off = psci_cpu_off; + } + + if (!of_property_read_u32(np, "cpu_on", &id)) { + psci_function_id[PSCI_FN_CPU_ON] = base + id; + psci_ops.cpu_on = psci_cpu_on; + } + + if (!of_property_read_u32(np, "migrate", &id)) { + psci_function_id[PSCI_FN_MIGRATE] = base + id; + psci_ops.migrate = psci_migrate; + } + +out_put_node: + of_node_put(np); + return 0; +} +early_initcall(psci_init); -- 1.8.0
Nicolas Pitre
2012-Dec-17 20:51 UTC
Re: [PATCH v2 4/6] ARM: psci: add support for PSCI invocations from the kernel
On Mon, 17 Dec 2012, Will Deacon wrote:> This patch adds support for the Power State Coordination Interface > defined by ARM, allowing Linux to request CPU-centric power-management > operations from firmware implementing the PSCI protocol. > > Signed-off-by: Will Deacon <will.deacon@arm.com>[...]> +/* > + * The following two functions are invoked via the invoke_psci_fn pointer > + * and will not be inlined, allowing us to piggyback on the AAPCS. > + */To make sure the code is always in sync with the intent, you could mark those with noinline as well.> +static int __invoke_psci_fn_hvc(u32 function_id, u32 arg0, u32 arg1, u32 arg2) > +{ > + asm volatile( > + __asmeq("%0", "r0") > + __asmeq("%1", "r1") > + __asmeq("%2", "r2") > + __asmeq("%3", "r3") > + __HVC(0) > + : "+r" (function_id) > + : "r" (arg0), "r" (arg1), "r" (arg2)); > + > + return function_id; > +} > + > +static int __invoke_psci_fn_smc(u32 function_id, u32 arg0, u32 arg1, u32 arg2) > +{ > + asm volatile( > + __asmeq("%0", "r0") > + __asmeq("%1", "r1") > + __asmeq("%2", "r2") > + __asmeq("%3", "r3") > + __SMC(0) > + : "+r" (function_id) > + : "r" (arg0), "r" (arg1), "r" (arg2)); > + > + return function_id; > +} > + > +static int psci_cpu_suspend(struct psci_power_state state, > + unsigned long entry_point) > +{ > + int err; > + u32 fn, power_state; > + > + fn = psci_function_id[PSCI_FN_CPU_SUSPEND]; > + power_state = psci_power_state_pack(state); > + err = invoke_psci_fn(fn, power_state, (u32)entry_point, 0);Why do you need the u32 cast here?> + return psci_to_linux_errno(err); > +} > + > +static int psci_cpu_off(struct psci_power_state state) > +{ > + int err; > + u32 fn, power_state; > + > + fn = psci_function_id[PSCI_FN_CPU_OFF]; > + power_state = psci_power_state_pack(state); > + err = invoke_psci_fn(fn, power_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 const struct of_device_id psci_of_match[] __initconst = { > + { .compatible = "arm,psci", }, > + {}, > +}; > + > +static int __init psci_init(void) > +{ > + struct device_node *np; > + const char *method; > + u32 base, id; > + > + np = of_find_matching_node(NULL, psci_of_match); > + if (!np) > + return 0; > + > + pr_info("probing function IDs from device-tree\n");Having "probing function IDs from device-tree" in the middle of a kernel log isn''t very informative. Better make this more useful or remove it.> + > + if (of_property_read_u32(np, "function-base", &base)) { > + pr_warning("missing \"function-base\" property\n");Same thing here: this lacks context in a kernel log. And so on for the other occurrences. Nicolas
Will Deacon
2012-Dec-18 10:11 UTC
Re: [PATCH v2 4/6] ARM: psci: add support for PSCI invocations from the kernel
On Mon, Dec 17, 2012 at 08:51:27PM +0000, Nicolas Pitre wrote:> On Mon, 17 Dec 2012, Will Deacon wrote: > > > This patch adds support for the Power State Coordination Interface > > defined by ARM, allowing Linux to request CPU-centric power-management > > operations from firmware implementing the PSCI protocol. > > > > Signed-off-by: Will Deacon <will.deacon@arm.com> > > [...] > > > +/* > > + * The following two functions are invoked via the invoke_psci_fn pointer > > + * and will not be inlined, allowing us to piggyback on the AAPCS. > > + */ > > To make sure the code is always in sync with the intent, you could mark > those with noinline as well.Can do.> > +static int __invoke_psci_fn_hvc(u32 function_id, u32 arg0, u32 arg1, u32 arg2) > > +{ > > + asm volatile( > > + __asmeq("%0", "r0") > > + __asmeq("%1", "r1") > > + __asmeq("%2", "r2") > > + __asmeq("%3", "r3") > > + __HVC(0) > > + : "+r" (function_id) > > + : "r" (arg0), "r" (arg1), "r" (arg2)); > > + > > + return function_id; > > +} > > + > > +static int __invoke_psci_fn_smc(u32 function_id, u32 arg0, u32 arg1, u32 arg2) > > +{ > > + asm volatile( > > + __asmeq("%0", "r0") > > + __asmeq("%1", "r1") > > + __asmeq("%2", "r2") > > + __asmeq("%3", "r3") > > + __SMC(0) > > + : "+r" (function_id) > > + : "r" (arg0), "r" (arg1), "r" (arg2)); > > + > > + return function_id; > > +} > > + > > +static int psci_cpu_suspend(struct psci_power_state state, > > + unsigned long entry_point) > > +{ > > + int err; > > + u32 fn, power_state; > > + > > + fn = psci_function_id[PSCI_FN_CPU_SUSPEND]; > > + power_state = psci_power_state_pack(state); > > + err = invoke_psci_fn(fn, power_state, (u32)entry_point, 0); > > Why do you need the u32 cast here?That''s a hangover from when entry_point was a void *. I''ll fix that, thanks.> > +static int __init psci_init(void) > > +{ > > + struct device_node *np; > > + const char *method; > > + u32 base, id; > > + > > + np = of_find_matching_node(NULL, psci_of_match); > > + if (!np) > > + return 0; > > + > > + pr_info("probing function IDs from device-tree\n"); > > Having "probing function IDs from device-tree" in the middle of a kernel > log isn''t very informative. Better make this more useful or remove it. > > > + > > + if (of_property_read_u32(np, "function-base", &base)) { > > + pr_warning("missing \"function-base\" property\n"); > > Same thing here: this lacks context in a kernel log. > And so on for the other occurrences.Actually, these are all prefixed with "psci: " thanks to the pr_fmt definition at the top of the file. I can remove them if you like, but then it''s not obvious which parts of the PSCI code are available from looking at a kernel boot log. Cheers for the review, Will
Nicolas Pitre
2012-Dec-18 21:59 UTC
Re: [PATCH v2 4/6] ARM: psci: add support for PSCI invocations from the kernel
On Tue, 18 Dec 2012, Will Deacon wrote:> On Mon, Dec 17, 2012 at 08:51:27PM +0000, Nicolas Pitre wrote: > > On Mon, 17 Dec 2012, Will Deacon wrote: > > > +static int psci_cpu_suspend(struct psci_power_state state, > > > + unsigned long entry_point) > > > +{ > > > + int err; > > > + u32 fn, power_state; > > > + > > > + fn = psci_function_id[PSCI_FN_CPU_SUSPEND]; > > > + power_state = psci_power_state_pack(state); > > > + err = invoke_psci_fn(fn, power_state, (u32)entry_point, 0); > > > > Why do you need the u32 cast here? > > That''s a hangover from when entry_point was a void *. I''ll fix that, thanks.Hopefully you didn''t pass virtual pointers to the PSCI call, did you? :-)> > > +static int __init psci_init(void) > > > +{ > > > + struct device_node *np; > > > + const char *method; > > > + u32 base, id; > > > + > > > + np = of_find_matching_node(NULL, psci_of_match); > > > + if (!np) > > > + return 0; > > > + > > > + pr_info("probing function IDs from device-tree\n"); > > > > Having "probing function IDs from device-tree" in the middle of a kernel > > log isn''t very informative. Better make this more useful or remove it. > > > > > + > > > + if (of_property_read_u32(np, "function-base", &base)) { > > > + pr_warning("missing \"function-base\" property\n"); > > > > Same thing here: this lacks context in a kernel log. > > And so on for the other occurrences. > > Actually, these are all prefixed with "psci: " thanks to the pr_fmt > definition at the top of the file.Ah, goodie! No more issue then. Nicolas
Will Deacon
2012-Dec-19 11:27 UTC
Re: [PATCH v2 4/6] ARM: psci: add support for PSCI invocations from the kernel
On Tue, Dec 18, 2012 at 09:59:45PM +0000, Nicolas Pitre wrote:> On Tue, 18 Dec 2012, Will Deacon wrote: > > On Mon, Dec 17, 2012 at 08:51:27PM +0000, Nicolas Pitre wrote: > > > On Mon, 17 Dec 2012, Will Deacon wrote: > > > > +static int psci_cpu_suspend(struct psci_power_state state, > > > > + unsigned long entry_point) > > > > +{ > > > > + int err; > > > > + u32 fn, power_state; > > > > + > > > > + fn = psci_function_id[PSCI_FN_CPU_SUSPEND]; > > > > + power_state = psci_power_state_pack(state); > > > > + err = invoke_psci_fn(fn, power_state, (u32)entry_point, 0); > > > > > > Why do you need the u32 cast here? > > > > That''s a hangover from when entry_point was a void *. I''ll fix that, thanks. > > Hopefully you didn''t pass virtual pointers to the PSCI call, did you? :-)...and I''d have gotten away with it if it wasn''t for those meddling kids! It was also made worse by Marc''s code working first time too (after I blamed the firmware like any sane kernel hacker would do :) Will