blob: ca1d267cdfdac11d46f811154d74511977bd24a7 [file] [log] [blame]
// Copyright 2023 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include "arch/riscv64/sbi.h"
#include <debug.h>
#include <lib/arch/riscv64/sbi-call.h>
#include <lib/arch/riscv64/sbi.h>
#include <trace.h>
#include <arch/riscv64/mp.h>
#include <pdev/power.h>
#define LOCAL_TRACE 0
// Basic SBI wrapper routines and extension detection.
//
// NOTE: Assumes a few extensions are present.
// Timer, Ipi, Rfence, Hart management, and System Reset are all
// assumed to be present. This may need to be revisited in the future
// if it turns out some of these are not always there.
//
// SBI API documentation lives at
// https://github.com/riscv-non-isa/riscv-sbi-doc/blob/master/riscv-sbi.adoc
namespace {
enum class sbi_extension : uint8_t {
Base,
Timer,
Ipi,
Rfence,
Hart,
SystemReset,
Pmu,
Dbcn,
Susp,
Cppc,
};
// A bitmap of all the detected SBI extensions.
uint32_t supported_extension_bitmap;
// For every extension we track, call a callable with all the information we
// may want.
template <typename Callable>
void for_every_extension(Callable callable) {
callable(sbi_extension::Base, "BASE", arch::RiscvSbiEid::kBase);
callable(sbi_extension::Timer, "TIMER", arch::RiscvSbiEid::kTimer);
callable(sbi_extension::Ipi, "IPI", arch::RiscvSbiEid::kIpi);
callable(sbi_extension::Rfence, "RFENCE", arch::RiscvSbiEid::kRfence);
callable(sbi_extension::Hart, "HSM", arch::RiscvSbiEid::kHart);
callable(sbi_extension::SystemReset, "SRST", arch::RiscvSbiEid::kSystemReset);
callable(sbi_extension::Pmu, "PMU", arch::RiscvSbiEid::kPmu);
callable(sbi_extension::Dbcn, "DBCN", arch::RiscvSbiEid::kDbcn);
callable(sbi_extension::Susp, "SUSP", arch::RiscvSbiEid::kSusp);
callable(sbi_extension::Cppc, "CPPC", arch::RiscvSbiEid::kCppc);
}
bool sbi_extension_present(sbi_extension ext) {
return (1U << static_cast<uint8_t>(ext)) & supported_extension_bitmap;
}
} // anonymous namespace
zx_status_t riscv_status_to_zx_status(arch::RiscvSbiError error) {
switch (error) {
case arch::RiscvSbiError::kSuccess:
return ZX_OK;
case arch::RiscvSbiError::kFailed:
return ZX_ERR_INTERNAL;
case arch::RiscvSbiError::kNotSupported:
return ZX_ERR_NOT_SUPPORTED;
case arch::RiscvSbiError::kInvalidParam:
case arch::RiscvSbiError::kInvalidAddress:
return ZX_ERR_INVALID_ARGS;
case arch::RiscvSbiError::kDenied:
return ZX_ERR_ACCESS_DENIED;
case arch::RiscvSbiError::kNoShmem:
return ZX_ERR_NO_RESOURCES;
case arch::RiscvSbiError::kAlreadyAvailable:
case arch::RiscvSbiError::kAlreadyStarted:
case arch::RiscvSbiError::kAlreadyStopped:
default:
return ZX_ERR_BAD_STATE;
}
}
zx::result<power_cpu_state> sbi_get_cpu_state(uint64_t hart_id) {
arch::RiscvSbiRet ret = arch::RiscvSbi::HartGetStatus(static_cast<arch::HartId>(hart_id));
if (ret.error != arch::RiscvSbiError::kSuccess) {
return zx::error(riscv_status_to_zx_status(ret.error));
}
switch (static_cast<arch::RiscvSbiHartState>(ret.value)) {
case arch::RiscvSbiHartState::kStarted:
return zx::success(power_cpu_state::STARTED);
case arch::RiscvSbiHartState::kStopped:
return zx::success(power_cpu_state::STOPPED);
case arch::RiscvSbiHartState::kStartPending:
return zx::success(power_cpu_state::START_PENDING);
case arch::RiscvSbiHartState::kStopPending:
return zx::success(power_cpu_state::STOP_PENDING);
case arch::RiscvSbiHartState::kSuspended:
return zx::success(power_cpu_state::SUSPENDED);
case arch::RiscvSbiHartState::kSuspendPending:
return zx::success(power_cpu_state::SUSPEND_PENDING);
case arch::RiscvSbiHartState::kResumePending:
return zx::success(power_cpu_state::RESUME_PENDING);
default:
// We should never reach here.
return zx::error(ZX_ERR_INTERNAL);
}
}
void riscv64_sbi_early_init() {
// Probe to see what extensions are present
auto probe_and_set_extension = [](sbi_extension extension_bit, const char *,
arch::RiscvSbiEid eid) {
// Base extension is always present
if (extension_bit == sbi_extension::Base) {
supported_extension_bitmap = 1U << static_cast<uint8_t>(sbi_extension::Base);
return;
}
// Probe the extension
arch::RiscvSbiRet ret = arch::RiscvSbi::ProbeExtension(eid);
// It shouldn't be legal for the base probe extension call to return anything but success,
// but check here anyway.
if (ret.error != arch::RiscvSbiError::kSuccess) {
return;
}
supported_extension_bitmap |=
(ret.value != 0) ? (1U << static_cast<uint8_t>(extension_bit)) : 0;
};
for_every_extension(probe_and_set_extension);
// Register with the pdev power driver.
static const pdev_power_ops sbi_ops = {
.reboot = [](power_reboot_flags flags) -> zx_status_t { return sbi_reset(); },
.shutdown = sbi_shutdown,
.cpu_off = sbi_hart_stop,
.cpu_on = sbi_hart_start,
.get_cpu_state = sbi_get_cpu_state,
};
pdev_register_power(&sbi_ops);
}
void riscv64_sbi_init() {
// Dump SBI version info and extensions found in early probing
if (DPRINTF_ENABLED_FOR_LEVEL(INFO)) {
dprintf(INFO, "RISCV: mvendorid %#lx marchid %#lx mimpid %#lx\n",
arch::RiscvSbi::GetMvendorid().value, arch::RiscvSbi::GetMarchid().value,
arch::RiscvSbi::GetMimpid().value);
uint64_t spec_version = arch::RiscvSbi::GetSpecVersion().value;
dprintf(INFO, "RISCV: SBI spec version %lu.%lu impl id %#lx version %#lx\n",
(spec_version >> 24) & 0x7f, spec_version & ((1 << 24) - 1),
arch::RiscvSbi::GetImplId().value, arch::RiscvSbi::GetImplVersion().value);
dprintf(INFO, "RISCV: extensions: ");
auto print_extension = [](sbi_extension extension, const char *name, arch::RiscvSbiEid) {
if (sbi_extension_present(extension)) {
dprintf(INFO, "%s ", name);
}
};
for_every_extension(print_extension);
dprintf(INFO, "\n");
}
}
arch::RiscvSbiRet sbi_send_ipi(arch::HartMask mask, arch::HartMaskBase mask_base) {
LTRACEF("hart_mask %#lx, mask_base %lu\n", mask, mask_base);
return arch::RiscvSbi::SendIpi(mask, mask_base);
}
zx_status_t sbi_hart_start(uint64_t id, paddr_t start_addr, uint64_t priv) {
arch::HartId hart_id = static_cast<arch::HartId>(id);
LTRACEF("hart_id %lu, start_addr %#lx, priv %#lx\n", hart_id, start_addr, priv);
arch::RiscvSbiRet ret = arch::RiscvSbi::HartStart(hart_id, start_addr, priv);
return riscv_status_to_zx_status(ret.error);
}
zx_status_t sbi_hart_stop() {
LTRACEF("local hart %u\n", riscv64_curr_hart_id());
arch::RiscvSbiRet ret = arch::RiscvSbi::HartStop();
return riscv_status_to_zx_status(ret.error);
}
arch::RiscvSbiRet sbi_remote_fencei(cpu_mask_t cpu_mask) {
arch::HartMask hart_mask = riscv64_cpu_mask_to_hart_mask(cpu_mask);
arch::HartMaskBase hart_mask_base = 0;
LTRACEF("cpu_mask %#x, hart_mask %#lx\n", cpu_mask, hart_mask);
return arch::RiscvSbi::RemoteFenceI(hart_mask, hart_mask_base);
}
arch::RiscvSbiRet sbi_remote_sfence_vma(cpu_mask_t cpu_mask, uintptr_t start, uintptr_t size) {
arch::HartMask hart_mask = riscv64_cpu_mask_to_hart_mask(cpu_mask);
arch::HartMaskBase hart_mask_base = 0;
LTRACEF("start %#lx, size %#lx, cpu_mask %#x, hart_mask %#lx\n", start, size, cpu_mask,
hart_mask);
return arch::RiscvSbi::RemoteSfenceVma(hart_mask, hart_mask_base, start, size);
}
arch::RiscvSbiRet sbi_remote_sfence_vma_asid(cpu_mask_t cpu_mask, uintptr_t start, uintptr_t size,
uint64_t asid) {
arch::HartMask hart_mask = riscv64_cpu_mask_to_hart_mask(cpu_mask);
arch::HartMaskBase hart_mask_base = 0;
LTRACEF("start %#lx, size %#lx, asid %lu, cpu_mask %#x, hart_mask %#lx\n", start, size, asid,
cpu_mask, hart_mask);
return arch::RiscvSbi::RemoteSfenceVmaAsid(hart_mask, hart_mask_base, start, size, asid);
}
zx_status_t sbi_shutdown() {
arch::RiscvSbiRet ret = arch::RiscvSbi::SystemReset(arch::RiscvSbiResetType::kShutdown,
arch::RiscvSbiResetReason::kNone);
return riscv_status_to_zx_status(ret.error);
}
zx_status_t sbi_reset() {
arch::RiscvSbiRet ret = arch::RiscvSbi::SystemReset(arch::RiscvSbiResetType::kWarmReboot,
arch::RiscvSbiResetReason::kNone);
return riscv_status_to_zx_status(ret.error);
}