blob: 2768812b027af2e26944536e2dc482fd2151f9a2 [file] [log] [blame]
/*
* Copyright (c) 2013-2016 Google Inc. All rights reserved
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <err.h>
#include <trace.h>
#include <kernel/event.h>
#include <kernel/mutex.h>
#include <kernel/thread.h>
#include <kernel/vm.h>
#include <lib/heap.h>
#include <lib/sm.h>
#include <lib/sm/smcall.h>
#include <lib/sm/sm_err.h>
#include <lk/init.h>
#include <string.h>
#include <sys/types.h>
#define LOCAL_TRACE 0
#define LTRACEF_LEVEL(level, x...) do { if (LOCAL_TRACE >= level) { TRACEF(x); } } while (0)
struct sm_std_call_state {
spin_lock_t lock;
event_t event;
smc32_args_t args;
long ret;
bool done;
int active_cpu; /* cpu that expects stdcall result */
int initial_cpu; /* Debug info: cpu that started stdcall */
int last_cpu; /* Debug info: most recent cpu expecting stdcall result */
int restart_count;
};
extern unsigned long monitor_vector_table;
extern ulong lk_boot_args[4];
static void *boot_args;
static int boot_args_refcnt;
static mutex_t boot_args_lock = MUTEX_INITIAL_VALUE(boot_args_lock);
static uint32_t sm_api_version;
static bool sm_api_version_locked;
static spin_lock_t sm_api_version_lock;
static event_t nsirqevent[SMP_MAX_CPUS];
static thread_t *nsirqthreads[SMP_MAX_CPUS];
static thread_t *nsidlethreads[SMP_MAX_CPUS];
static thread_t *stdcallthread;
static bool ns_threads_started;
static bool irq_thread_ready[SMP_MAX_CPUS];
struct sm_std_call_state stdcallstate = {
.event = EVENT_INITIAL_VALUE(stdcallstate.event, 0, 0),
.active_cpu = -1,
.initial_cpu = -1,
.last_cpu = -1,
};
extern smc32_handler_t sm_stdcall_table[];
extern smc32_handler_t sm_nopcall_table[];
long smc_sm_api_version(smc32_args_t *args)
{
uint32_t api_version = args->params[0];
spin_lock(&sm_api_version_lock);
if (!sm_api_version_locked) {
LTRACEF("request api version %d\n", api_version);
if (api_version > TRUSTY_API_VERSION_CURRENT)
api_version = TRUSTY_API_VERSION_CURRENT;
sm_api_version = api_version;
} else {
TRACEF("ERROR: Tried to select api version %d after use, current version %d\n",
api_version, sm_api_version);
api_version = sm_api_version;
}
spin_unlock(&sm_api_version_lock);
LTRACEF("return api version %d\n", api_version);
return api_version;
}
static uint32_t sm_get_api_version(void)
{
if (!sm_api_version_locked) {
spin_lock_saved_state_t state;
spin_lock_save(&sm_api_version_lock, &state, SPIN_LOCK_FLAG_IRQ_FIQ);
sm_api_version_locked = true;
TRACEF("lock api version %d\n", sm_api_version);
spin_unlock_restore(&sm_api_version_lock, state, SPIN_LOCK_FLAG_IRQ_FIQ);
}
return sm_api_version;
}
static int __NO_RETURN sm_stdcall_loop(void *arg)
{
long ret;
spin_lock_saved_state_t state;
while (true) {
LTRACEF("cpu %d, wait for stdcall\n", arch_curr_cpu_num());
event_wait(&stdcallstate.event);
/* Dispatch 'standard call' handler */
LTRACEF("cpu %d, got stdcall: 0x%x, 0x%x, 0x%x, 0x%x\n",
arch_curr_cpu_num(),
stdcallstate.args.smc_nr, stdcallstate.args.params[0],
stdcallstate.args.params[1], stdcallstate.args.params[2]);
ret = sm_stdcall_table[SMC_ENTITY(stdcallstate.args.smc_nr)](&stdcallstate.args);
LTRACEF("cpu %d, stdcall(0x%x, 0x%x, 0x%x, 0x%x) returned 0x%lx (%ld)\n",
arch_curr_cpu_num(),
stdcallstate.args.smc_nr, stdcallstate.args.params[0],
stdcallstate.args.params[1], stdcallstate.args.params[2], ret, ret);
spin_lock_save(&stdcallstate.lock, &state, SPIN_LOCK_FLAG_IRQ);
stdcallstate.ret = ret;
stdcallstate.done = true;
event_unsignal(&stdcallstate.event);
spin_unlock_restore(&stdcallstate.lock, state, SPIN_LOCK_FLAG_IRQ);
}
}
/* must be called with irqs disabled */
static long sm_queue_stdcall(smc32_args_t *args)
{
long ret;
uint cpu = arch_curr_cpu_num();
spin_lock(&stdcallstate.lock);
if (stdcallstate.event.signalled || stdcallstate.done) {
if (args->smc_nr == SMC_SC_RESTART_LAST && stdcallstate.active_cpu == -1) {
stdcallstate.restart_count++;
LTRACEF_LEVEL(3, "cpu %d, restart std call, restart_count %d\n",
cpu, stdcallstate.restart_count);
goto restart_stdcall;
}
dprintf(CRITICAL, "%s: cpu %d, std call busy\n", __func__, cpu);
ret = SM_ERR_BUSY;
goto err;
} else {
if (args->smc_nr == SMC_SC_RESTART_LAST) {
dprintf(CRITICAL, "%s: cpu %d, unexpected restart, no std call active\n",
__func__, arch_curr_cpu_num());
ret = SM_ERR_UNEXPECTED_RESTART;
goto err;
}
}
LTRACEF("cpu %d, queue std call 0x%x\n", cpu, args->smc_nr);
stdcallstate.initial_cpu = cpu;
stdcallstate.ret = SM_ERR_INTERNAL_FAILURE;
stdcallstate.args = *args;
stdcallstate.restart_count = 0;
event_signal(&stdcallstate.event, false);
restart_stdcall:
stdcallstate.active_cpu = cpu;
ret = 0;
err:
spin_unlock(&stdcallstate.lock);
return ret;
}
/* must be called with irqs disabled */
static void sm_return_and_wait_for_next_stdcall(long ret, int cpu)
{
smc32_args_t args = SMC32_ARGS_INITIAL_VALUE(args);
do {
arch_disable_fiqs();
sm_sched_nonsecure(ret, &args);
arch_enable_fiqs();
/* Allow concurrent SMC_SC_NOP calls on multiple cpus */
if (args.smc_nr == SMC_SC_NOP) {
LTRACEF_LEVEL(3, "cpu %d, got nop\n", cpu);
ret = sm_nopcall_table[SMC_ENTITY(args.params[0])](&args);
} else {
ret = sm_queue_stdcall(&args);
}
} while (ret);
}
static void sm_irq_return_ns(void)
{
long ret;
int cpu;
cpu = arch_curr_cpu_num();
spin_lock(&stdcallstate.lock); /* TODO: remove? */
LTRACEF_LEVEL(2, "got irq on cpu %d, stdcallcpu %d\n",
cpu, stdcallstate.active_cpu);
if (stdcallstate.active_cpu == cpu) {
stdcallstate.last_cpu = stdcallstate.active_cpu;
stdcallstate.active_cpu = -1;
ret = SM_ERR_INTERRUPTED;
} else {
ret = SM_ERR_NOP_INTERRUPTED;
}
LTRACEF_LEVEL(2, "got irq on cpu %d, return %ld\n", cpu, ret);
spin_unlock(&stdcallstate.lock);
sm_return_and_wait_for_next_stdcall(ret, cpu);
}
static int __NO_RETURN sm_irq_loop(void *arg)
{
int cpu;
int eventcpu = (uintptr_t)arg; /* cpu that requested this thread, the current cpu could be different */
/*
* Run this thread with interrupts masked, so we don't reenter the
* interrupt handler. The interrupt handler for non-secure interrupts
* returns to this thread with the interrupt still pending.
*/
arch_disable_ints();
irq_thread_ready[eventcpu] = true;
cpu = arch_curr_cpu_num();
LTRACEF("wait for irqs for cpu %d, on cpu %d\n", eventcpu, cpu);
while (true) {
event_wait(&nsirqevent[eventcpu]);
sm_irq_return_ns();
}
}
/* must be called with irqs disabled */
static long sm_get_stdcall_ret(void)
{
long ret;
uint cpu = arch_curr_cpu_num();
spin_lock(&stdcallstate.lock);
if (stdcallstate.active_cpu != (int)cpu) {
dprintf(CRITICAL, "%s: stdcallcpu, a%d != curr-cpu %d, l%d, i%d\n",
__func__, stdcallstate.active_cpu, cpu,
stdcallstate.last_cpu, stdcallstate.initial_cpu);
ret = SM_ERR_INTERNAL_FAILURE;
goto err;
}
stdcallstate.last_cpu = stdcallstate.active_cpu;
stdcallstate.active_cpu = -1;
if (stdcallstate.done) {
stdcallstate.done = false;
ret = stdcallstate.ret;
LTRACEF("cpu %d, return stdcall result, %ld, initial cpu %d\n",
cpu, stdcallstate.ret, stdcallstate.initial_cpu);
} else {
if (sm_get_api_version() >= TRUSTY_API_VERSION_SMP) /* ns using new api */
ret = SM_ERR_CPU_IDLE;
else if (stdcallstate.restart_count)
ret = SM_ERR_BUSY;
else
ret = SM_ERR_INTERRUPTED;
LTRACEF("cpu %d, initial cpu %d, restart_count %d, std call not finished, return %ld\n",
cpu, stdcallstate.initial_cpu,
stdcallstate.restart_count, ret);
}
err:
spin_unlock(&stdcallstate.lock);
return ret;
}
static void sm_wait_for_smcall(void)
{
int cpu;
long ret = 0;
LTRACEF("wait for stdcalls, on cpu %d\n", arch_curr_cpu_num());
while (true) {
/*
* Disable interrupts so stdcallstate.active_cpu does not
* change to or from this cpu after checking it below.
*/
arch_disable_ints();
/* Switch to sm-stdcall if sm_queue_stdcall woke it up */
thread_yield();
cpu = arch_curr_cpu_num();
if (cpu == stdcallstate.active_cpu)
ret = sm_get_stdcall_ret();
else
ret = SM_ERR_NOP_DONE;
sm_return_and_wait_for_next_stdcall(ret, cpu);
/* Re-enable interrupts (needed for SMC_SC_NOP) */
arch_enable_ints();
}
}
#if WITH_LIB_SM_MONITOR
/* per-cpu secure monitor initialization */
static void sm_mon_percpu_init(uint level)
{
/* let normal world enable SMP, lock TLB, access CP10/11 */
__asm__ volatile (
"mrc p15, 0, r1, c1, c1, 2 \n"
"orr r1, r1, #0xC00 \n"
"orr r1, r1, #0x60000 \n"
"mcr p15, 0, r1, c1, c1, 2 @ NSACR \n"
::: "r1"
);
__asm__ volatile (
"mcr p15, 0, %0, c12, c0, 1 \n"
: : "r" (&monitor_vector_table)
);
}
LK_INIT_HOOK_FLAGS(libsm_mon_perrcpu, sm_mon_percpu_init,
LK_INIT_LEVEL_PLATFORM - 3, LK_INIT_FLAG_ALL_CPUS);
#endif
static void sm_secondary_init(uint level)
{
char name[32];
int cpu = arch_curr_cpu_num();
event_init(&nsirqevent[cpu], false, EVENT_FLAG_AUTOUNSIGNAL);
snprintf(name, sizeof(name), "irq-ns-switch-%d", cpu);
nsirqthreads[cpu] = thread_create(name, sm_irq_loop, (void *)(uintptr_t)cpu,
HIGHEST_PRIORITY, DEFAULT_STACK_SIZE);
if (!nsirqthreads[cpu]) {
panic("failed to create irq NS switcher thread for cpu %d!\n", cpu);
}
nsirqthreads[cpu]->pinned_cpu = cpu;
thread_set_real_time(nsirqthreads[cpu]);
snprintf(name, sizeof(name), "idle-ns-switch-%d", cpu);
nsidlethreads[cpu] = thread_create(name,
(thread_start_routine)sm_wait_for_smcall,
NULL, LOWEST_PRIORITY + 1, DEFAULT_STACK_SIZE);
if (!nsidlethreads[cpu]) {
panic("failed to create idle NS switcher thread for cpu %d!\n", cpu);
}
nsidlethreads[cpu]->pinned_cpu = cpu;
thread_set_real_time(nsidlethreads[cpu]);
if (ns_threads_started) {
thread_resume(nsirqthreads[cpu]);
thread_resume(nsidlethreads[cpu]);
}
}
LK_INIT_HOOK_FLAGS(libsm_cpu, sm_secondary_init, LK_INIT_LEVEL_PLATFORM - 2, LK_INIT_FLAG_ALL_CPUS);
static void sm_init(uint level)
{
status_t err;
mutex_acquire(&boot_args_lock);
/* Map the boot arguments if supplied by the bootloader */
if (lk_boot_args[1] && lk_boot_args[2]) {
ulong offset = lk_boot_args[1] & (PAGE_SIZE - 1);
paddr_t paddr = ROUNDDOWN(lk_boot_args[1], PAGE_SIZE);
size_t size = ROUNDUP(lk_boot_args[2] + offset, PAGE_SIZE);
void *vptr;
err = vmm_alloc_physical(vmm_get_kernel_aspace(), "sm",
size, &vptr, PAGE_SIZE_SHIFT, paddr,
0,
ARCH_MMU_FLAG_NS | ARCH_MMU_FLAG_PERM_NO_EXECUTE |
ARCH_MMU_FLAG_CACHED);
if (!err) {
boot_args = (uint8_t *)vptr + offset;
boot_args_refcnt++;
} else {
boot_args = NULL;
TRACEF("Error mapping boot parameter block: %d\n", err);
}
}
mutex_release(&boot_args_lock);
stdcallthread = thread_create("sm-stdcall", sm_stdcall_loop, NULL,
LOWEST_PRIORITY + 2, DEFAULT_STACK_SIZE);
if (!stdcallthread) {
panic("failed to create sm-stdcall thread!\n");
}
thread_set_real_time(stdcallthread);
thread_resume(stdcallthread);
}
LK_INIT_HOOK(libsm, sm_init, LK_INIT_LEVEL_PLATFORM - 1);
enum handler_return sm_handle_irq(void)
{
int cpu = arch_curr_cpu_num();
if (irq_thread_ready[cpu]) {
event_signal(&nsirqevent[cpu], false);
} else {
TRACEF("warning: got ns irq before irq thread is ready\n");
sm_irq_return_ns();
}
return INT_RESCHEDULE;
}
void sm_handle_fiq(void)
{
uint32_t expected_return;
smc32_args_t args = SMC32_ARGS_INITIAL_VALUE(args);
if (sm_get_api_version() >= TRUSTY_API_VERSION_RESTART_FIQ) {
sm_sched_nonsecure(SM_ERR_FIQ_INTERRUPTED, &args);
expected_return = SMC_SC_RESTART_FIQ;
} else {
sm_sched_nonsecure(SM_ERR_INTERRUPTED, &args);
expected_return = SMC_SC_RESTART_LAST;
}
if (args.smc_nr != expected_return) {
TRACEF("got bad restart smc %x, expected %x\n",
args.smc_nr, expected_return);
while (args.smc_nr != expected_return)
sm_sched_nonsecure(SM_ERR_INTERLEAVED_SMC, &args);
}
}
status_t sm_get_boot_args(void **boot_argsp, size_t *args_sizep)
{
status_t err = NO_ERROR;
if (!boot_argsp || !args_sizep)
return ERR_INVALID_ARGS;
mutex_acquire(&boot_args_lock);
if (!boot_args) {
err = ERR_NOT_CONFIGURED;
goto unlock;
}
boot_args_refcnt++;
*boot_argsp = boot_args;
*args_sizep = lk_boot_args[2];
unlock:
mutex_release(&boot_args_lock);
return err;
}
static void resume_nsthreads(void)
{
int i;
ns_threads_started = true;
smp_wmb();
for (i = 0; i < SMP_MAX_CPUS; i++) {
if (nsirqthreads[i])
thread_resume(nsirqthreads[i]);
if (nsidlethreads[i])
thread_resume(nsidlethreads[i]);
}
}
void sm_put_boot_args(void)
{
mutex_acquire(&boot_args_lock);
if (!boot_args) {
TRACEF("WARNING: caller does not own "
"a reference to boot parameters\n");
goto unlock;
}
boot_args_refcnt--;
if (boot_args_refcnt == 0) {
vmm_free_region(vmm_get_kernel_aspace(), (vaddr_t)boot_args);
boot_args = NULL;
resume_nsthreads();
}
unlock:
mutex_release(&boot_args_lock);
}
static void sm_release_boot_args(uint level)
{
if (boot_args) {
sm_put_boot_args();
} else {
/* we need to resume the ns-switcher here if
* the boot loader didn't pass bootargs
*/
resume_nsthreads();
}
if (boot_args)
TRACEF("WARNING: outstanding reference to boot args"
"at the end of initialzation!\n");
}
LK_INIT_HOOK(libsm_bootargs, sm_release_boot_args, LK_INIT_LEVEL_LAST);