blob: a1b020918b13db44c3a5d5aba8e152a61e4842cf [file] [log] [blame]
/*
* This file is part of the coreboot project.
*
* Copyright 2014 Google Inc.
*
* 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 of the License.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc.
*/
#include <string.h>
#include <stdlib.h>
#include <arch/cpu.h>
#include <arch/smc.h>
#include <arch/exception.h>
#include <arch/lib_helpers.h>
#include <console/console.h>
enum {
/* SMC called from AARCH32 */
EC_SMC_AARCH32 = 0x13,
/* SMC called from AARCH64 */
EC_SMC_AARCH64 = 0x17,
SMC_NUM_RANGES = 8,
};
struct smc_range {
uint32_t func_begin;
uint32_t func_end;
int (*handler)(struct smc_call *);
};
struct smc_ranges {
size_t used;
struct smc_range handlers[SMC_NUM_RANGES];
};
static struct smc_ranges smc_functions;
static struct smc_range *smc_handler_by_function(uint32_t fid)
{
int i;
for (i = 0; i < smc_functions.used; i++) {
struct smc_range *r = &smc_functions.handlers[i];
if (fid >= r->func_begin && fid <= r->func_end)
return r;
}
return NULL;
}
int smc_register_range(uint32_t min, uint32_t max, int (*h)(struct smc_call *))
{
struct smc_range *r;
if (smc_functions.used == SMC_NUM_RANGES)
return -1;
if (min > max)
return -1;
/* This check isn't exhaustive but it's fairly quick. */
if (smc_handler_by_function(min) || smc_handler_by_function(max))
return -1;
r = &smc_functions.handlers[smc_functions.used];
r->func_begin = min;
r->func_end = max;
r->handler = h;
smc_functions.used++;
return 0;
}
static int smc_cleanup(struct exc_state *state, struct smc_call *smc, int ret)
{
memcpy(&state->regs.x, &smc->results, sizeof(smc->results));
return ret;
}
static int smc_return_with_error(struct exc_state *state, struct smc_call *smc)
{
smc32_return(smc, SMC_UNKNOWN_FUNC);
return smc_cleanup(state, smc, EXC_RET_HANDLED);
}
static int smc_handler(struct exc_state *state, uint64_t vector_id)
{
struct smc_call smc_storage;
struct smc_call *smc = &smc_storage;
uint32_t exception_class;
uint32_t esr;
struct smc_range *r;
memcpy(&smc->args, &state->regs.x, sizeof(smc->args));
memcpy(&smc->results, &state->regs.x, sizeof(smc->results));
esr = raw_read_esr_el3();
exception_class = (esr >> 26) & 0x3f;
/* No support for SMC calls from AARCH32 */
if (exception_class == EC_SMC_AARCH32)
return smc_return_with_error(state, smc);
/* Check to ensure this is an SMC from AARCH64. */
if (exception_class != EC_SMC_AARCH64)
return EXC_RET_IGNORED;
/* Ensure immediate value is 0. */
if ((esr & 0xffff) != 0)
return smc_return_with_error(state, smc);
r = smc_handler_by_function(smc_function_id(smc));
if (r != NULL) {
if (!r->handler(smc))
return smc_cleanup(state, smc, EXC_RET_HANDLED);
}
return smc_return_with_error(state, smc);
}
/* SMC calls can be generated by 32-bit or 64-bit code. */
static struct exception_handler smc_handler64 = {
.handler = &smc_handler,
};
static struct exception_handler smc_handler32 = {
.handler = &smc_handler,
};
static void enable_smc(void *arg)
{
uint32_t scr;
/* Enable SMC */
scr = raw_read_scr_el3();
scr &= ~(SCR_SMC_MASK);
scr |= SCR_SMC_ENABLE;
raw_write_scr_el3(scr);
}
void smc_init(void)
{
struct cpu_action action = {
.run = enable_smc,
};
arch_run_on_all_cpus_async(&action);
/* Register SMC handlers. */
exception_handler_register(EXC_VID_LOW64_SYNC, &smc_handler64);
exception_handler_register(EXC_VID_LOW32_SYNC, &smc_handler32);
}