| /* |
| * 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> |
| |
| extern struct sbi_ecall_extension *sbi_ecall_exts[]; |
| extern unsigned long sbi_ecall_exts_size; |
| |
| 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_ecall_return out = {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); |
| 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 (!out.skip_regs_update) { |
| if (ret < SBI_LAST_ERR || |
| (extension_id != SBI_EXT_0_1_CONSOLE_GETCHAR && |
| SBI_SUCCESS < ret)) { |
| 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.value; |
| } |
| |
| return 0; |
| } |
| |
| int sbi_ecall_init(void) |
| { |
| int ret; |
| struct sbi_ecall_extension *ext; |
| unsigned long i; |
| |
| for (i = 0; i < sbi_ecall_exts_size; i++) { |
| ext = sbi_ecall_exts[i]; |
| ret = SBI_ENODEV; |
| |
| if (ext->register_extensions) |
| ret = ext->register_extensions(); |
| if (ret) |
| return ret; |
| } |
| |
| return 0; |
| } |