blob: a6cf6f971bcfb149c003c9fe941f6a01fdd1f3b8 [file] [log] [blame] [edit]
// Copyright 2017 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 <lib/arch/intrin.h>
#include <lib/console.h>
#include <lib/fit/function.h>
#include <zircon/errors.h>
#include <zircon/types.h>
#include <kernel/spinlock.h>
#include <lk/init.h>
#include <pdev/interrupt.h>
#include <ktl/enforce.h>
namespace {
DECLARE_SINGLETON_SPINLOCK(pdev_lock);
struct int_handler_struct {
interrupt_handler_t handler TA_GUARDED(pdev_lock::Get()) = nullptr;
ktl::atomic<bool> permanent = false;
};
struct int_handler_struct int_handler_table[MAX_INTERRUPTS];
struct int_handler_struct* pdev_get_int_handler(interrupt_vector_t vector) {
DEBUG_ASSERT(vector < MAX_INTERRUPTS);
return &int_handler_table[vector];
}
zx_status_t register_int_handler_common(interrupt_vector_t vector, interrupt_handler_t handler,
bool permanent) {
if (!is_valid_interrupt(vector, 0)) {
return ZX_ERR_INVALID_ARGS;
}
Guard<SpinLock, IrqSave> guard{pdev_lock::Get()};
auto h = pdev_get_int_handler(vector);
if ((handler && h->handler) || h->permanent.load(ktl::memory_order_relaxed)) {
return ZX_ERR_ALREADY_BOUND;
}
h->handler = ktl::move(handler);
h->permanent.store(permanent, ktl::memory_order_relaxed);
return ZX_OK;
}
// By default most of these are empty stubs and the particular interrupt controller must override
// all of them.
const struct pdev_interrupt_ops default_ops = {
.mask = [](interrupt_vector_t) { return ZX_ERR_NOT_SUPPORTED; },
.unmask = [](interrupt_vector_t) { return ZX_ERR_NOT_SUPPORTED; },
.deactivate = [](interrupt_vector_t) { return ZX_ERR_NOT_SUPPORTED; },
.configure = [](interrupt_vector_t, interrupt_trigger_mode,
interrupt_polarity) { return ZX_ERR_NOT_SUPPORTED; },
.get_config = [](interrupt_vector_t, interrupt_trigger_mode*,
interrupt_polarity*) { return ZX_ERR_NOT_SUPPORTED; },
.set_affinity = [](interrupt_vector_t, cpu_mask_t) { return ZX_ERR_NOT_SUPPORTED; },
.is_valid = [](interrupt_vector_t, uint32_t flags) { return false; },
.get_base_vector = []() -> interrupt_vector_t { return 0; },
.get_max_vector = []() -> interrupt_vector_t { return 0; },
.remap = [](interrupt_vector_t) -> interrupt_vector_t { return 0; },
.send_ipi = [](cpu_mask_t, mp_ipi) { return ZX_ERR_NOT_SUPPORTED; },
.init_percpu_early = []() {},
.init_percpu = []() {},
.handle_irq = [](iframe_t*) {},
.shutdown = []() {},
.shutdown_cpu = []() {},
.suspend_cpu = []() { return ZX_ERR_NOT_SUPPORTED; },
.resume_cpu = []() { return ZX_ERR_NOT_SUPPORTED; },
.msi_is_supported = []() { return false; },
.msi_supports_masking = []() { return false; },
.msi_mask_unmask = [](const msi_block_t*, uint, bool) {},
.msi_alloc_block = [](uint, bool, bool, msi_block_t*) { return ZX_ERR_NOT_SUPPORTED; },
.msi_free_block = [](msi_block_t*) {},
.msi_register_handler = [](const msi_block_t*, uint, interrupt_handler_t) {}};
const struct pdev_interrupt_ops* intr_ops = &default_ops;
} // anonymous namespace
zx_status_t register_int_handler(interrupt_vector_t vector, interrupt_handler_t handler) {
return register_int_handler_common(vector, ktl::move(handler), false);
}
zx_status_t register_permanent_int_handler(interrupt_vector_t vector, interrupt_handler_t handler) {
return register_int_handler_common(vector, ktl::move(handler), true);
}
bool pdev_invoke_int_if_present(interrupt_vector_t vector) {
auto h = pdev_get_int_handler(vector);
// Use a relaxed load as permanent handlers are never modified once set, and they are only set in
// startup code, and so there is nothing to race with.
if (h->permanent.load(ktl::memory_order_relaxed)) {
// Once permanent is set to true we know that handler and arg are immutable and so it is safe
// to read them without holding the lock.
[&h]() TA_NO_THREAD_SAFETY_ANALYSIS {
DEBUG_ASSERT(h->handler);
h->handler();
}();
return true;
}
Guard<SpinLock, IrqSave> guard{pdev_lock::Get()};
if (h->handler) {
h->handler();
return true;
}
return false;
}
zx_status_t mask_interrupt(interrupt_vector_t vector) { return intr_ops->mask(vector); }
zx_status_t unmask_interrupt(interrupt_vector_t vector) { return intr_ops->unmask(vector); }
zx_status_t deactivate_interrupt(interrupt_vector_t vector) { return intr_ops->deactivate(vector); }
zx_status_t configure_interrupt(interrupt_vector_t vector, enum interrupt_trigger_mode tm,
enum interrupt_polarity pol) {
return intr_ops->configure(vector, tm, pol);
}
zx_status_t get_interrupt_config(interrupt_vector_t vector, enum interrupt_trigger_mode* tm,
enum interrupt_polarity* pol) {
return intr_ops->get_config(vector, tm, pol);
}
zx_status_t set_interrupt_affinity(interrupt_vector_t vector, cpu_mask_t mask) {
return intr_ops->set_affinity(vector, mask);
}
uint32_t interrupt_get_base_vector() { return intr_ops->get_base_vector(); }
uint32_t interrupt_get_max_vector() { return intr_ops->get_max_vector(); }
bool is_valid_interrupt(interrupt_vector_t vector, uint32_t flags) {
return intr_ops->is_valid(vector, flags);
}
interrupt_vector_t remap_interrupt(interrupt_vector_t vector) { return intr_ops->remap(vector); }
zx_status_t interrupt_send_ipi(cpu_mask_t target, mp_ipi ipi) {
return intr_ops->send_ipi(target, ipi);
}
void interrupt_init_percpu_early() { intr_ops->init_percpu_early(); }
void interrupt_init_percpu() { intr_ops->init_percpu(); }
void platform_irq(iframe_t* frame) { intr_ops->handle_irq(frame); }
void pdev_register_interrupts(const struct pdev_interrupt_ops* ops) {
// Assert that all of the ops are fulled in with at least a default hook.
DEBUG_ASSERT(ops->mask);
DEBUG_ASSERT(ops->unmask);
DEBUG_ASSERT(ops->deactivate);
DEBUG_ASSERT(ops->configure);
DEBUG_ASSERT(ops->get_config);
DEBUG_ASSERT(ops->set_affinity);
DEBUG_ASSERT(ops->is_valid);
DEBUG_ASSERT(ops->get_base_vector);
DEBUG_ASSERT(ops->get_max_vector);
DEBUG_ASSERT(ops->remap);
DEBUG_ASSERT(ops->send_ipi);
DEBUG_ASSERT(ops->init_percpu_early);
DEBUG_ASSERT(ops->init_percpu);
DEBUG_ASSERT(ops->handle_irq);
DEBUG_ASSERT(ops->shutdown);
DEBUG_ASSERT(ops->shutdown_cpu);
DEBUG_ASSERT(ops->suspend_cpu);
DEBUG_ASSERT(ops->resume_cpu);
DEBUG_ASSERT(ops->msi_is_supported);
DEBUG_ASSERT(ops->msi_supports_masking);
DEBUG_ASSERT(ops->msi_mask_unmask);
DEBUG_ASSERT(ops->msi_alloc_block);
DEBUG_ASSERT(ops->msi_free_block);
DEBUG_ASSERT(ops->msi_register_handler);
intr_ops = ops;
arch::ThreadMemoryBarrier();
}
void shutdown_interrupts() { intr_ops->shutdown(); }
void shutdown_interrupts_curr_cpu() { intr_ops->shutdown_cpu(); }
zx_status_t suspend_interrupts_curr_cpu() { return intr_ops->suspend_cpu(); }
zx_status_t resume_interrupts_curr_cpu() { return intr_ops->resume_cpu(); }
bool msi_is_supported() { return intr_ops->msi_is_supported(); }
bool msi_supports_masking() { return intr_ops->msi_supports_masking(); }
void msi_mask_unmask(const msi_block_t* block, uint msi_id, bool mask) {
intr_ops->msi_mask_unmask(block, msi_id, mask);
}
zx_status_t msi_alloc_block(uint requested_irqs, bool can_target_64bit, bool is_msix,
msi_block_t* out_block) {
return intr_ops->msi_alloc_block(requested_irqs, can_target_64bit, is_msix, out_block);
}
void msi_free_block(msi_block_t* block) { intr_ops->msi_free_block(block); }
void msi_register_handler(const msi_block_t* block, uint msi_id, interrupt_handler_t handler) {
intr_ops->msi_register_handler(block, msi_id, ktl::move(handler));
}
namespace {
void interrupt_init_percpu_early_hook(uint level) { interrupt_init_percpu_early(); }
LK_INIT_HOOK_FLAGS(interrupt_init_percpu_early, interrupt_init_percpu_early_hook,
LK_INIT_LEVEL_PLATFORM_EARLY, LK_INIT_FLAG_SECONDARY_CPUS)
//
// Console support
//
class IrqInfo {
public:
#if __arm__ || __aarch64__
// TODO(johngro): This is not the best of assumptions, but for now, we want
// to avoid querying the status of GIC PPIs as they are all CPU dependent,
// and we don't provide any way to control which CPU we are on when
// querying.
static constexpr uint32_t kFirstValidIrq = 32;
#else
static constexpr uint32_t kFirstValidIrq = 0;
#endif
static constexpr uint32_t kLastValidIrq = MAX_INTERRUPTS;
static bool ValidIrqNum(uint64_t irq_num) {
return (irq_num >= kFirstValidIrq) && (irq_num < kLastValidIrq);
}
static IrqInfo Get(uint32_t irq_num) {
DEBUG_ASSERT_MSG(ValidIrqNum(irq_num), "Bad IRQ num %u\n", irq_num);
IrqInfo ret{};
interrupt_trigger_mode tm;
interrupt_polarity p;
if (intr_ops->get_config(irq_num, &tm, &p) == ZX_OK) {
ret.trigger_mode_ = tm;
ret.polarity_ = p;
}
bool pend, enb;
if (intr_ops->get_status && (intr_ops->get_status(irq_num, pend, enb) == ZX_OK)) {
ret.pending_ = pend;
ret.enabled_ = enb;
}
Guard<SpinLock, IrqSave> guard{pdev_lock::Get()};
ret.registered_ = static_cast<bool>(int_handler_table[irq_num].handler);
return ret;
}
bool registered() const { return registered_; }
ktl::optional<interrupt_trigger_mode> trigger_mode() const { return trigger_mode_; }
ktl::optional<interrupt_polarity> polarity() const { return polarity_; }
ktl::optional<bool> pending() const { return pending_; }
ktl::optional<bool> enabled() const { return enabled_; }
// If the actual interrupt driver cannot tell us our configuration, we
// consider this to be an "invalid" interrupt, and don't display it (even when
// -v is passed to "show all").
bool is_valid() const { return trigger_mode_.has_value() && polarity_.has_value(); }
const char* trigger_mode_str() const {
if (!trigger_mode_.has_value()) {
return "Unknown";
}
switch (trigger_mode_.value()) {
case interrupt_trigger_mode::EDGE:
return "Edge";
case interrupt_trigger_mode::LEVEL:
return "Level";
default:
return "Unknown";
}
}
const char* polarity_str() const {
if (!trigger_mode_.has_value() || !polarity_.has_value()) {
return "Unknown";
}
switch (polarity_.value()) {
case interrupt_polarity::HIGH:
return trigger_mode_.value() == interrupt_trigger_mode::LEVEL ? "High" : "Rising";
case interrupt_polarity::LOW:
return trigger_mode_.value() == interrupt_trigger_mode::LEVEL ? "Low" : "Falling";
default:
return "Unknown";
}
}
const char* pending_str() const { return TriStateBoolStr(pending_); }
const char* enabled_str() const { return TriStateBoolStr(enabled_); }
private:
static const char* TriStateBoolStr(ktl::optional<bool> val) {
if (val.has_value()) {
return val.value() ? "True" : "False";
}
return "Unknown";
}
bool registered_{false};
ktl::optional<interrupt_trigger_mode> trigger_mode_;
ktl::optional<interrupt_polarity> polarity_;
ktl::optional<bool> pending_;
ktl::optional<bool> enabled_;
};
int IrqConsoleCmd(int argc, const cmd_args* argv, uint32_t flags) {
auto usage = [](int ret = ZX_ERR_INVALID_ARGS) -> int {
printf("Usage: irq <cmd>\n");
printf("Valid commands are:\n");
printf(" help : show this message\n");
printf(" show (all | reg | pending | enabled | <N>)\n");
printf(" Shows the status of one or more IRQs, depending on the mandatory argument.\n");
printf(" all : Show the status of all IRQs in the system.\n");
printf(" reg : Show the status of all IRQs which have a registered handler.\n");
printf(" pending : Show the status of all IRQs which are currently pending.\n");
printf(" enabled : Show the status of all IRQs which are currently enabled.\n");
printf(" pendenb : Show the status of all IRQs which are currently pending OR enabled.\n");
printf(" <N> : Show the status IRQ #<N>.\n");
return ret;
};
if (argc < 2) {
printf("No command specified!\n");
return usage();
}
if (!strcmp(argv[1].str, "help")) {
return usage(ZX_OK);
} else if (!strcmp(argv[1].str, "show")) {
if (argc < 3) {
printf("No target specified for \"show\"\n");
return usage();
}
ktl::optional<uint32_t> maybe_target;
fit::inline_function<bool(const IrqInfo&), 0> predicate = [](const IrqInfo&) { return false; };
if (!strcmp(argv[2].str, "all")) {
predicate = [](const IrqInfo&) { return true; };
} else if (!strcmp(argv[2].str, "reg")) {
predicate = [](const IrqInfo& info) { return info.registered(); };
} else if (!strcmp(argv[2].str, "pending")) {
predicate = [](const IrqInfo& info) { return info.pending().value_or(false); };
} else if (!strcmp(argv[2].str, "enabled")) {
predicate = [](const IrqInfo& info) { return info.enabled().value_or(false); };
} else if (!strcmp(argv[2].str, "pendenb")) {
predicate = [](const IrqInfo& info) {
return info.pending().value_or(false) || info.enabled().value_or(false);
};
} else {
if (!IrqInfo::ValidIrqNum(argv[2].u)) {
printf("Invalid IRQ target (\"%s\") for \"show\".\n", argv[2].str);
return usage();
}
maybe_target = static_cast<uint32_t>(argv[2].u);
}
if (maybe_target.has_value()) {
uint32_t target = maybe_target.value();
const IrqInfo info = IrqInfo::Get(target);
printf("IRQ : %u\n", target);
printf("State : %s\n", info.registered() ? "Registered" : "Unregistered");
printf("Trigger Mode : %s\n", info.trigger_mode_str());
printf("Polarity : %s\n", info.polarity_str());
printf("Pending : %s\n", info.pending_str());
printf("Enabled : %s\n", info.enabled_str());
} else {
printf(" ID | Registered | Trigger Mode | Polarity | Pending | Enabled |\n");
printf("------+------------+--------------+----------+---------+---------+\n");
for (uint32_t i = IrqInfo::kFirstValidIrq; i < IrqInfo::kLastValidIrq; ++i) {
if (const IrqInfo info = IrqInfo::Get(i); info.is_valid() && predicate(info)) {
printf(" %4u | %3s | %7s | %7s | %7s | %7s |\n", i,
info.registered() ? "Yes" : "No", info.trigger_mode_str(), info.polarity_str(),
info.pending_str(), info.enabled_str());
}
}
}
return ZX_OK;
} else {
printf("Unrecognized command \"%s\"\n", argv[1].str);
return usage();
}
}
} // namespace
STATIC_COMMAND_START
STATIC_COMMAND("irq", "IRQ related commands", IrqConsoleCmd)
STATIC_COMMAND_END(smmu)