diff options
Diffstat (limited to 'roms/opensbi/lib/sbi/sbi_ecall.c')
-rw-r--r-- | roms/opensbi/lib/sbi/sbi_ecall.c | 175 |
1 files changed, 175 insertions, 0 deletions
diff --git a/roms/opensbi/lib/sbi/sbi_ecall.c b/roms/opensbi/lib/sbi/sbi_ecall.c new file mode 100644 index 000000000..e92a53930 --- /dev/null +++ b/roms/opensbi/lib/sbi/sbi_ecall.c @@ -0,0 +1,175 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2019 Western Digital Corporation or its affiliates. + * + * Authors: + * Anup Patel <anup.patel@wdc.com> + */ + +#include <sbi/sbi_console.h> +#include <sbi/sbi_ecall.h> +#include <sbi/sbi_ecall_interface.h> +#include <sbi/sbi_error.h> +#include <sbi/sbi_trap.h> + +u16 sbi_ecall_version_major(void) +{ + return SBI_ECALL_VERSION_MAJOR; +} + +u16 sbi_ecall_version_minor(void) +{ + return SBI_ECALL_VERSION_MINOR; +} + +static unsigned long ecall_impid = SBI_OPENSBI_IMPID; + +unsigned long sbi_ecall_get_impid(void) +{ + return ecall_impid; +} + +void sbi_ecall_set_impid(unsigned long impid) +{ + ecall_impid = impid; +} + +static SBI_LIST_HEAD(ecall_exts_list); + +struct sbi_ecall_extension *sbi_ecall_find_extension(unsigned long extid) +{ + struct sbi_ecall_extension *t, *ret = NULL; + + sbi_list_for_each_entry(t, &ecall_exts_list, head) { + if (t->extid_start <= extid && extid <= t->extid_end) { + ret = t; + break; + } + } + + return ret; +} + +int sbi_ecall_register_extension(struct sbi_ecall_extension *ext) +{ + struct sbi_ecall_extension *t; + + if (!ext || (ext->extid_end < ext->extid_start) || !ext->handle) + return SBI_EINVAL; + + sbi_list_for_each_entry(t, &ecall_exts_list, head) { + unsigned long start = t->extid_start; + unsigned long end = t->extid_end; + if (end < ext->extid_start || ext->extid_end < start) + /* no overlap */; + else + return SBI_EINVAL; + } + + SBI_INIT_LIST_HEAD(&ext->head); + sbi_list_add_tail(&ext->head, &ecall_exts_list); + + return 0; +} + +void sbi_ecall_unregister_extension(struct sbi_ecall_extension *ext) +{ + bool found = FALSE; + struct sbi_ecall_extension *t; + + if (!ext) + return; + + sbi_list_for_each_entry(t, &ecall_exts_list, head) { + if (t == ext) { + found = TRUE; + break; + } + } + + if (found) + sbi_list_del_init(&ext->head); +} + +int sbi_ecall_handler(struct sbi_trap_regs *regs) +{ + int ret = 0; + struct sbi_ecall_extension *ext; + unsigned long extension_id = regs->a7; + unsigned long func_id = regs->a6; + struct sbi_trap_info trap = {0}; + unsigned long out_val = 0; + bool is_0_1_spec = 0; + + ext = sbi_ecall_find_extension(extension_id); + if (ext && ext->handle) { + ret = ext->handle(extension_id, func_id, + regs, &out_val, &trap); + if (extension_id >= SBI_EXT_0_1_SET_TIMER && + extension_id <= SBI_EXT_0_1_SHUTDOWN) + is_0_1_spec = 1; + } else { + ret = SBI_ENOTSUPP; + } + + if (ret == SBI_ETRAP) { + trap.epc = regs->mepc; + sbi_trap_redirect(regs, &trap); + } else { + if (ret < SBI_LAST_ERR) { + sbi_printf("%s: Invalid error %d for ext=0x%lx " + "func=0x%lx\n", __func__, ret, + extension_id, func_id); + ret = SBI_ERR_FAILED; + } + + /* + * This function should return non-zero value only in case of + * fatal error. However, there is no good way to distinguish + * between a fatal and non-fatal errors yet. That's why we treat + * every return value except ETRAP as non-fatal and just return + * accordingly for now. Once fatal errors are defined, that + * case should be handled differently. + */ + regs->mepc += 4; + regs->a0 = ret; + if (!is_0_1_spec) + regs->a1 = out_val; + } + + return 0; +} + +int sbi_ecall_init(void) +{ + int ret; + + /* The order of below registrations is performance optimized */ + ret = sbi_ecall_register_extension(&ecall_time); + if (ret) + return ret; + ret = sbi_ecall_register_extension(&ecall_rfence); + if (ret) + return ret; + ret = sbi_ecall_register_extension(&ecall_ipi); + if (ret) + return ret; + ret = sbi_ecall_register_extension(&ecall_base); + if (ret) + return ret; + ret = sbi_ecall_register_extension(&ecall_hsm); + if (ret) + return ret; + ret = sbi_ecall_register_extension(&ecall_srst); + if (ret) + return ret; + ret = sbi_ecall_register_extension(&ecall_legacy); + if (ret) + return ret; + ret = sbi_ecall_register_extension(&ecall_vendor); + if (ret) + return ret; + + return 0; +} |