blob: bb90c99d2bfdefce8dd759c374f074349ea5347b [file]
/*
* Copyright 2019, Data61, CSIRO (ABN 41 687 119 230)
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <assert.h>
#include <camkes/irq.h>
#include <limits.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <utils/util.h>
#define CHECK_MEMBER_EQUAL(a, b, member) a->member == b->member
/* Force the _allocated_irqs section to be created even if no modules are defined. */
static USED SECTION("_allocated_irqs") struct {} dummy_allocated_irq;
/* Definitions so that we can find the exposed IRQ information */
extern allocated_irq_t *__start__allocated_irqs[];
extern allocated_irq_t *__stop__allocated_irqs[];
static bool check_irq_info_is_equal(ps_irq_t *a, ps_irq_t *b)
{
if (a->type == PS_MSI || a->type == PS_IOAPIC) {
if (a->type == b->type) {
switch (a->type) {
case PS_MSI:
if (CHECK_MEMBER_EQUAL(a, b, ioapic.ioapic) &&
CHECK_MEMBER_EQUAL(a, b, ioapic.pin) &&
CHECK_MEMBER_EQUAL(a, b, ioapic.level) &&
CHECK_MEMBER_EQUAL(a, b, ioapic.polarity) &&
CHECK_MEMBER_EQUAL(a, b, ioapic.vector)) {
return true;
}
return false;
case PS_IOAPIC:
if (CHECK_MEMBER_EQUAL(a, b, msi.pci_bus) &&
CHECK_MEMBER_EQUAL(a, b, msi.pci_dev) &&
CHECK_MEMBER_EQUAL(a, b, msi.pci_func) &&
CHECK_MEMBER_EQUAL(a, b, msi.handle) &&
CHECK_MEMBER_EQUAL(a, b, msi.vector)) {
return true;
}
return false;
default:
ZF_LOGF("Unreachable statement");
}
}
return false;
}
/* PS_INTERRUPT(number) == PS_PER_CPU(number, 0, 0)
PS_TRIGGER(number, trigger) == PS_PER_CPU(number, trigger, 0)
*/
ps_irq_t a_normalized = {.cpu = {0}};
if (a->type == PS_INTERRUPT) {
a_normalized.cpu.number = a->irq.number;
} else if (a->type == PS_TRIGGER) {
a_normalized.cpu.number = a->trigger.number;
a_normalized.cpu.trigger = a->trigger.trigger;
} else if (a->type == PS_PER_CPU) {
a_normalized = *a;
} else {
return false;
}
if (b->type == PS_INTERRUPT) {
return a_normalized.cpu.number == b->irq.number;
} else if (b->type == PS_TRIGGER) {
return a_normalized.cpu.number == b->trigger.number &&
a_normalized.cpu.trigger == b->trigger.trigger;
} else if (b->type == PS_PER_CPU) {
return a_normalized.cpu.number == b->cpu.number &&
a_normalized.cpu.trigger == b->cpu.trigger &&
a_normalized.cpu.cpu_idx == b->cpu.cpu_idx;
} else {
return false;
}
return false;
}
static allocated_irq_t **find_matching_irq_entry_by_interrupt(ps_irq_t *irq)
{
for (allocated_irq_t **irq_entry = __start__allocated_irqs;
irq_entry < __stop__allocated_irqs; irq_entry++) {
/* We use this function for 'register', so the IRQ entries have to be unallocated */
if ((*irq_entry)->is_allocated == false) {
ps_irq_t *irq_info = &((*irq_entry)->irq);
if (check_irq_info_is_equal(irq_info, irq)) {
return irq_entry;
}
}
}
return NULL;
}
static allocated_irq_t **find_matching_irq_entry_by_cptr(seL4_CPtr irq_cptr)
{
for (allocated_irq_t **irq_entry = __start__allocated_irqs;
irq_entry < __stop__allocated_irqs; irq_entry++) {
/* We use this function for 'unregister', so the IRQ entries have to be allocated */
if ((*irq_entry)->is_allocated == true) {
if ((*irq_entry)->irq_handler == irq_cptr) {
return irq_entry;
}
}
}
return NULL;
}
static irq_id_t camkes_irq_register(void *cookie, ps_irq_t irq, irq_callback_fn_t callback, void *callback_data)
{
if (!callback) {
return -EINVAL;
}
allocated_irq_t **irq_entry = find_matching_irq_entry_by_interrupt(&irq);
if (irq_entry == NULL) {
ZF_LOGE("Couldn't find an unallocated interrupt with the same details");
return -ENOENT;
}
/* Acknowledge the IRQ handler so that signals can arrive on the paired ntfn */
int error = seL4_IRQHandler_Ack((*irq_entry)->irq_handler);
if (error) {
ZF_LOGE("Failed to register interrupt");
return -EFAULT;
}
/* Fill in the book keeping information for this interrupt */
(*irq_entry)->is_allocated = true;
(*irq_entry)->callback_fn = callback;
(*irq_entry)->callback_data = callback_data;
return (irq_id_t)(*irq_entry)->irq_handler;
}
static int camkes_irq_unregister(void *cookie, irq_id_t irq_id)
{
if (irq_id < 0) {
return -EINVAL;
}
allocated_irq_t **irq_entry = find_matching_irq_entry_by_cptr((seL4_CPtr) irq_id);
if (irq_entry == NULL) {
ZF_LOGE("Couldn't find an allocated interrupt with the same IRQ ID");
return -ENOENT;
}
/* Acknolwedge the IRQ handler so interrupts can arrive */
int error = seL4_IRQHandler_Ack((*irq_entry)->irq_handler);
if (error) {
ZF_LOGE("Failed to register intterupt");
return -EFAULT;
}
(*irq_entry)->is_allocated = false;
(*irq_entry)->callback_fn = NULL;
(*irq_entry)->callback_data = NULL;
return 0;
}
int camkes_irq_ops(ps_irq_ops_t *irq_ops)
{
if (!irq_ops) {
return -EINVAL;
}
/* The cookie can't be NULL, otherwise the wrappers in libplatsupport will return errors */
irq_ops->cookie = (void *)0xDEADBEEF;
irq_ops->irq_register_fn = camkes_irq_register;
irq_ops->irq_unregister_fn = camkes_irq_unregister;
return 0;
}
/* Force the _global_notification_irqs section to be created even if no modules are defined. */
static USED SECTION("_global_notification_irqs") struct {} dummy_allocated_global_irq;
/* Definitions so that we can find the exposed IRQ information */
extern global_notification_irq_handler_t *__start__global_notification_irqs[];
extern global_notification_irq_handler_t *__stop__global_notification_irqs[];
int camkes_handle_global_endpoint_irq(seL4_Word badge)
{
int handlers_called = 0;
for (global_notification_irq_handler_t **irq_entry = __start__global_notification_irqs;
irq_entry < __stop__global_notification_irqs; irq_entry++) {
seL4_Word registered_badge = (*irq_entry)->badge;
if ((badge & registered_badge) == registered_badge) {
allocated_irq_t *registered_irq = (*irq_entry)->allocated_ref;
if (registered_irq->callback_fn) {
registered_irq->callback_fn(registered_irq->callback_data, (*irq_entry)->ack_fun, NULL);
handlers_called++;
}
}
}
return handlers_called;
}