/* * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; version 2. * * 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. */ #define pr_fmt(fmt) "psci: " fmt #include #include #include #include #include #include #include #include #include #ifdef CONFIG_ARM_PSCI_DEBUG /* * PSCI debugging functions. Board code can specify a putc() function * which is used for debugging output. Beware that this function is * called while the kernel is running. This means the kernel could have * turned off clocks, configured other baudrates and other stuff that * might confuse the putc function. So it can well be that the debugging * code itself is the problem when somethings not working. You have been * warned. */ static void (*__putc)(void *ctx, int c); static void *putc_ctx; void psci_set_putc(void (*putcf)(void *ctx, int c), void *ctx) { __putc = putcf; putc_ctx = ctx; } void psci_putc(char c) { if (__putc) __putc(putc_ctx, c); } int psci_puts(const char *str) { int n = 0; while (*str) { if (*str == '\n') psci_putc('\r'); psci_putc(*str); str++; n++; } return n; } int psci_printf(const char *fmt, ...) { va_list args; uint i; char printbuffer[128]; va_start(args, fmt); i = vsprintf(printbuffer, fmt, args); va_end(args); psci_puts(printbuffer); return i; } #endif static struct psci_ops *psci_ops; void psci_set_ops(struct psci_ops *ops) { psci_ops = ops; } static unsigned long psci_version(void) { psci_printf("%s\n", __func__); return ARM_PSCI_VER_1_0; } static unsigned long psci_cpu_suspend(u32 power_state, unsigned long entry, u32 context_id) { psci_printf("%s\n", __func__); if (psci_ops->cpu_off) return psci_ops->cpu_suspend(power_state, entry, context_id); return ARM_PSCI_RET_NOT_SUPPORTED; } static unsigned long psci_cpu_off(void) { psci_printf("%s\n", __func__); if (psci_ops->cpu_off) return psci_ops->cpu_off(); return ARM_PSCI_RET_NOT_SUPPORTED; } static unsigned long cpu_entry[ARM_SECURE_MAX_CPU]; static unsigned long context[ARM_SECURE_MAX_CPU]; static unsigned long psci_cpu_on(u32 cpu_id, unsigned long entry, u32 context_id) { psci_printf("%s: %d 0x%08lx\n", __func__, cpu_id, entry); if (cpu_id >= ARM_SECURE_MAX_CPU) return ARM_PSCI_RET_INVAL; cpu_entry[cpu_id] = entry; context[cpu_id] = context_id; dsb(); if (psci_ops->cpu_on) return psci_ops->cpu_on(cpu_id); return ARM_PSCI_RET_NOT_SUPPORTED; } static unsigned long psci_system_off(void) { psci_printf("%s\n", __func__); if (psci_ops->system_reset) psci_ops->system_reset(); while(1); return 0; } static unsigned long psci_system_reset(void) { psci_printf("%s\n", __func__); if (psci_ops->system_reset) psci_ops->system_reset(); restart_machine(); } /* Avoid missing prototype warning, called from assembly */ void psci_entry(u32 r0, u32 r1, u32 r2, u32 r3, u32 r4, u32 r5, u32 r6, struct arm_smccc_res *res); void psci_entry(u32 r0, u32 r1, u32 r2, u32 r3, u32 r4, u32 r5, u32 r6, struct arm_smccc_res *res) { psci_printf("%s entry, function: 0x%08x\n", __func__, r0); switch (r0) { case ARM_PSCI_0_2_FN_PSCI_VERSION: res->a0 = psci_version(); break; case ARM_PSCI_0_2_FN_CPU_SUSPEND: res->a0 = psci_cpu_suspend(r1, r2, r3); break; case ARM_PSCI_0_2_FN_CPU_OFF: res->a0 = psci_cpu_off(); break; case ARM_PSCI_0_2_FN_CPU_ON: res->a0 = psci_cpu_on(r1, r2, r3); break; case ARM_PSCI_0_2_FN_SYSTEM_OFF: psci_system_off(); break; case ARM_PSCI_0_2_FN_SYSTEM_RESET: psci_system_reset(); break; default: res->a0 = ARM_PSCI_RET_NOT_SUPPORTED; break; } } static int of_psci_fixup(struct device_node *root, void *unused) { struct device_node *psci; int ret; if (bootm_arm_security_state() < ARM_STATE_NONSECURE) return 0; psci = of_create_node(root, "/psci"); if (!psci) return -EINVAL; ret = of_property_write_string(psci, "compatible", "arm,psci-1.0"); if (ret) return ret; ret = of_property_write_string(psci, "method", "smc"); if (ret) return ret; return 0; } /* Avoid missing prototype warning, called from assembly */ int psci_cpu_entry_c(void); int psci_cpu_entry_c(void) { void (*entry)(u32 context); int cpu; u32 context_id; __armv7_secure_monitor_install(); cpu = psci_get_cpu_id(); entry = (void *)cpu_entry[cpu]; context_id = context[cpu]; if (bootm_arm_security_state() == ARM_STATE_HYP) armv7_switch_to_hyp(); psci_printf("core #%d enter function 0x%p\n", cpu, entry); entry(context_id); while (1); } static int armv7_psci_init(void) { return of_register_fixup(of_psci_fixup, NULL); } device_initcall(armv7_psci_init); #ifdef DEBUG #include #include #include "mmu.h" void second_entry(void) { struct arm_smccc_res res; psci_printf("2nd CPU online, now turn off again\n"); arm_smccc_smc(ARM_PSCI_0_2_FN_CPU_OFF, 0, 0, 0, 0, 0, 0, 0, &res); psci_printf("2nd CPU still alive?\n"); while (1); } static int do_smc(int argc, char *argv[]) { int opt; struct arm_smccc_res res = { .a0 = 0xdeadbee0, .a1 = 0xdeadbee1, .a2 = 0xdeadbee2, .a3 = 0xdeadbee3, }; while ((opt = getopt(argc, argv, "nicz")) > 0) { switch (opt) { case 'n': armv7_secure_monitor_install(); break; case 'i': arm_smccc_smc(ARM_PSCI_0_2_FN_PSCI_VERSION, 0, 0, 0, 0, 0, 0, 0, &res); printf("found psci version %ld.%ld\n", res.a0 >> 16, res.a0 & 0xffff); break; case 'c': arm_smccc_smc(ARM_PSCI_0_2_FN_CPU_ON, 1, (unsigned long)second_entry, 0, 0, 0, 0, 0, &res); break; } } return 0; } BAREBOX_CMD_HELP_START(smc) BAREBOX_CMD_HELP_TEXT("Secure monitor code test command") BAREBOX_CMD_HELP_TEXT("") BAREBOX_CMD_HELP_TEXT("Options:") BAREBOX_CMD_HELP_OPT ("-n", "Install secure monitor and switch to nonsecure mode") BAREBOX_CMD_HELP_OPT ("-i", "Show information about installed PSCI version") BAREBOX_CMD_HELP_OPT ("-c", "Start secondary CPU core") BAREBOX_CMD_HELP_OPT ("-z", "Turn off secondary CPU core") BAREBOX_CMD_HELP_END BAREBOX_CMD_START(smc) .cmd = do_smc, BAREBOX_CMD_DESC("secure monitor test command") BAREBOX_CMD_GROUP(CMD_GRP_MISC) BAREBOX_CMD_END #endif