blob: 27d09ef5f9823149cf928af0352bf489d06058b4 [file] [log] [blame]
/*
* Copyright 2019, Data61
* Commonwealth Scientific and Industrial Research Organisation (CSIRO)
* ABN 41 687 119 230.
*
* This software may be distributed and modified according to the terms of
* the BSD 2-Clause license. Note that NO WARRANTY is provided.
* See "LICENSE_BSD2.txt" for details.
*
* @TAG(DATA61_BSD)
*/
#include <assert.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#include <limits.h>
#include <errno.h>
#include <sel4/sel4.h>
#include <sel4platsupport/irq.h>
#include <sel4platsupport/device.h>
#include <platsupport/irq.h>
#include <platsupport/io.h>
#include <simple/simple.h>
#include <utils/util.h>
#include <vka/vka.h>
#include <vka/capops.h>
#define UNPAIRED_ID -1
#define UNALLOCATED_BADGE_INDEX -1
typedef enum irq_iface_type {
STANDARD_IFACE,
MINI_IFACE
} irq_iface_type_t;
typedef struct {
/* These are always non-empty if this particular IRQ ID is in use */
bool allocated;
ps_irq_t irq;
cspacepath_t handler_path;
/* These are not always non-empty if this particular IRQ ID is in use */
/* Driver registers these */
irq_callback_fn_t irq_callback_fn;
void *callback_data;
/* User registers these */
cspacepath_t ntfn_path;
ntfn_id_t paired_ntfn;
int8_t allocated_badge_index;
} irq_entry_t;
typedef struct {
bool allocated;
cspacepath_t root_ntfn_path;
size_t num_irqs_bound;
seL4_Word usable_mask;
/* Bitfield tracking which of the bits in the badge are allocated */
seL4_Word status_bitfield;
/* Bitfield tracking which IRQs have arrived but not served */
seL4_Word pending_bitfield;
irq_id_t bound_irqs[MAX_INTERRUPTS_TO_NOTIFICATIONS];
} ntfn_entry_t;
typedef struct {
irq_iface_type_t iface_type;
size_t num_registered_irqs;
size_t num_allocated_ntfns;
size_t max_irq_ids;
size_t max_ntfn_ids;
size_t num_irq_bitfields;
size_t num_ntfn_bitfields;
/* Array of bitfields tracking which IDs have been allocated */
seL4_Word *allocated_irq_bitfields;
seL4_Word *allocated_ntfn_bitfields;
irq_entry_t *irq_table;
ntfn_entry_t *ntfn_table;
simple_t *simple;
vka_t *vka;
ps_malloc_ops_t *malloc_ops;
} irq_cookie_t;
typedef struct {
irq_cookie_t *irq_cookie;
irq_id_t irq_id;
} ack_data_t;
static inline bool check_irq_id_is_valid(irq_cookie_t *irq_cookie, irq_id_t id)
{
if (unlikely(id < 0 || id >= irq_cookie->max_irq_ids)) {
return false;
}
return true;
}
static inline bool check_ntfn_id_is_valid(irq_cookie_t *irq_cookie, ntfn_id_t id)
{
if (unlikely(id < 0 || id >= irq_cookie->max_ntfn_ids)) {
return false;
}
return true;
}
static inline bool check_irq_id_all_allocated(irq_cookie_t *irq_cookie)
{
if (unlikely(irq_cookie->num_registered_irqs >= irq_cookie->max_irq_ids)) {
return true;
}
return false;
}
static inline bool check_ntfn_id_all_allocated(irq_cookie_t *irq_cookie)
{
if (unlikely(irq_cookie->num_allocated_ntfns >= irq_cookie->max_ntfn_ids)) {
return true;
}
return false;
}
static inline bool check_ntfn_id_is_allocated(irq_cookie_t *irq_cookie, ntfn_id_t id)
{
if (likely(irq_cookie->ntfn_table[id].allocated)) {
return true;
}
return false;
}
static inline bool check_irq_id_is_allocated(irq_cookie_t *irq_cookie, irq_id_t id)
{
if (likely(irq_cookie->irq_table[id].allocated)) {
return true;
}
return false;
}
static inline void fill_bit_in_bitfield(seL4_Word *bitfield_array, int index)
{
int bitfield_index = index % (sizeof(seL4_Word) * CHAR_BIT);
int array_index = index / (sizeof(seL4_Word) * CHAR_BIT);
bitfield_array[array_index] |= BIT(bitfield_index);
}
static inline void unfill_bit_in_bitfield(seL4_Word *bitfield_array, int index)
{
int bitfield_index = index % (sizeof(seL4_Word) * CHAR_BIT);
int array_index = index / (sizeof(seL4_Word) * CHAR_BIT);
bitfield_array[array_index] &= ~(BIT(bitfield_index));
}
static irq_id_t find_free_irq_id(irq_cookie_t *irq_cookie)
{
for (int i = 0; i < irq_cookie->num_irq_bitfields; i++) {
unsigned long unallocated_bitfield = ~(irq_cookie->allocated_irq_bitfields[i]);
if (i == irq_cookie->num_irq_bitfields - 1) {
/* Hide the bits that cannot be allocated, i.e. max_irq_ids is
* not a multiple of sizeof(seL4_Word) */
unsigned long num_leftover_bits = irq_cookie->num_irq_bitfields * sizeof(seL4_Word) -
irq_cookie->max_irq_ids;
unsigned long mask = MASK(num_leftover_bits);
/* Reverse the mask, (bit 0 is the smallest ID of that bitfield) */
mask = BSWAP_WORD(mask);
unallocated_bitfield &= mask;
}
/* Check to avoid undefined behaviour of CTZL(0) */
if (likely(unallocated_bitfield)) {
return CTZL(unallocated_bitfield) + (i * sizeof(seL4_Word) * CHAR_BIT);
}
}
return -1;
}
static ntfn_id_t find_free_ntfn_id(irq_cookie_t *irq_cookie)
{
for (int i = 0; i < irq_cookie->num_ntfn_bitfields; i++) {
unsigned long unallocated_bitfield = ~(irq_cookie->allocated_ntfn_bitfields[i]);
if (i == irq_cookie->num_irq_bitfields - 1) {
/* Hide the bits that cannot be allocated, i.e. max_irq_ids is
* not a multiple of sizeof(seL4_Word) */
unsigned long num_leftover_bits = irq_cookie->num_ntfn_bitfields * sizeof(seL4_Word) -
irq_cookie->max_ntfn_ids;
unsigned long mask = MASK(num_leftover_bits);
/* Reverse the mask, (bit 0 is the smallest ID of that bitfield) */
mask = BSWAP_WORD(mask);
unallocated_bitfield &= mask;
}
/* Check to avoid undefined behaviour of CTZL(0) */
if (likely(unallocated_bitfield)) {
return CTZL(unallocated_bitfield) + (i * sizeof(seL4_Word) * CHAR_BIT);
}
}
return -1;
}
static int find_free_ntfn_badge_index(ntfn_entry_t *ntfn_entry)
{
unsigned long unallocated_bitfield = ~(ntfn_entry->status_bitfield);
/* Mask the bits that we can use */
unallocated_bitfield &= ntfn_entry->usable_mask;
/* Check to avoid undefined behaviour of CTZL(0) */
if (unlikely(!unallocated_bitfield)) {
return -1;
}
return CTZL(unallocated_bitfield);
}
static int irq_set_ntfn_common(irq_cookie_t *irq_cookie, ntfn_id_t ntfn_id, irq_id_t irq_id,
seL4_Word *ret_badge)
{
irq_entry_t *irq_entry = &(irq_cookie->irq_table[irq_id]);
/* Check if the IRQ is already bound to something */
if (irq_entry->paired_ntfn > UNPAIRED_ID) {
return -EINVAL;
}
ntfn_entry_t *ntfn_entry = &(irq_cookie->ntfn_table[ntfn_id]);
/* Check if we have space to bound another IRQ to this notification */
if (ntfn_entry->num_irqs_bound >= MAX_INTERRUPTS_TO_NOTIFICATIONS) {
return -ENOSPC;
}
/* Find an empty space (unclaimed bit in the badge) for the IRQ */
int badge_index = find_free_ntfn_badge_index(ntfn_entry);
if (badge_index == -1) {
return -ENOSPC;
}
/* Allocate a CSpace slot and mint the root notification object with badge */
cspacepath_t mint_path = {0};
int error = vka_cspace_alloc_path(irq_cookie->vka, &mint_path);
if (error) {
return -ENOMEM;
}
seL4_Word badge = BIT(badge_index);
error = vka_cnode_mint(&mint_path, &(ntfn_entry->root_ntfn_path), seL4_AllRights, badge);
if (error) {
vka_cspace_free_path(irq_cookie->vka, mint_path);
return -ENOMEM;
}
/* Bind the notification with the handler now */
error = seL4_IRQHandler_SetNotification(irq_entry->handler_path.capPtr, mint_path.capPtr);
if (error) {
ZF_LOGE("Failed to set a notification with the IRQ handler");
error = vka_cnode_delete(&mint_path);
ZF_LOGF_IF(error, "Failed to cleanup after a failed IRQ set ntfn operation");
vka_cspace_free_path(irq_cookie->vka, mint_path);
return -EFAULT;
}
/* Acknowledge the handler so interrupts can arrive on the notification */
error = seL4_IRQHandler_Ack(irq_entry->handler_path.capPtr);
if (error) {
ZF_LOGE("Failed to ack an IRQ handler");
error = seL4_IRQHandler_Clear(irq_entry->handler_path.capPtr);
ZF_LOGF_IF(error, "Failed to unpair a notification after a failed IRQ set ntfn operation");
error = vka_cnode_delete(&mint_path);
ZF_LOGF_IF(error, "Failed to cleanup after a failed IRQ set ntfn operation");
vka_cspace_free_path(irq_cookie->vka, mint_path);
return -EFAULT;
}
if (ret_badge) {
*ret_badge = badge;
}
/* Fill in the bookkeeping information */
ntfn_entry->num_irqs_bound++;
ntfn_entry->status_bitfield |= badge;
ntfn_entry->bound_irqs[badge_index] = irq_id;
irq_entry->ntfn_path = mint_path;
irq_entry->paired_ntfn = ntfn_id;
irq_entry->allocated_badge_index = badge_index;
return 0;
}
static irq_id_t irq_register_common(irq_cookie_t *irq_cookie, ps_irq_t irq, irq_callback_fn_t callback,
void *callback_data)
{
if (check_irq_id_all_allocated(irq_cookie)) {
return -EMFILE;
}
if (!callback) {
return -EINVAL;
}
irq_id_t free_id = find_free_irq_id(irq_cookie);
if (free_id == -1) {
/* Probably wouldn't get here, as we checked above already */
ZF_LOGE("Failed to find a free IRQ id");
return -EMFILE;
}
/* Allocate a path for the IRQ handler object */
cspacepath_t irq_handler_path = {0};
vka_cspace_alloc_path(irq_cookie->vka, &irq_handler_path);
/* Create an IRQ handler object for this IRQ */
int err = sel4platsupport_copy_irq_cap(irq_cookie->vka, irq_cookie->simple, &irq, &irq_handler_path);
if (err) {
/* Give a slightly ambigious message as we don't want to leak implementation details */
ZF_LOGE("Failed to register an IRQ");
vka_cspace_free_path(irq_cookie->vka, irq_handler_path);
return -EFAULT;
}
irq_entry_t *irq_entry = &(irq_cookie->irq_table[free_id]);
irq_entry->allocated = true;
irq_entry->irq = irq;
irq_entry->handler_path = irq_handler_path;
irq_entry->irq_callback_fn = callback;
irq_entry->callback_data = callback_data;
irq_cookie->num_registered_irqs++;
fill_bit_in_bitfield(irq_cookie->allocated_irq_bitfields, free_id);
return free_id;
}
static void provide_ntfn_common(irq_cookie_t *irq_cookie, seL4_CPtr ntfn, seL4_Word usable_mask,
ntfn_id_t allocated_id)
{
cspacepath_t ntfn_path = {0};
vka_cspace_make_path(irq_cookie->vka, ntfn, &ntfn_path);
/* Clear the notification entry and then fill in bookkeeping information */
ntfn_entry_t *ntfn_entry = &(irq_cookie->ntfn_table[allocated_id]);
memset(ntfn_entry, 0, sizeof(ntfn_entry_t));
ntfn_entry->allocated = true;
ntfn_entry->root_ntfn_path = ntfn_path;
ntfn_entry->usable_mask = usable_mask;
irq_cookie->num_allocated_ntfns++;
fill_bit_in_bitfield(irq_cookie->allocated_ntfn_bitfields, allocated_id);
}
static irq_cookie_t *new_irq_ops_common(vka_t *vka, simple_t *simple, irq_interface_config_t irq_config,
ps_malloc_ops_t *malloc_ops, irq_iface_type_t iface_type)
{
int err = 0;
/* Figure out how many bitfields we need to keep track of the allocation status of the IDs */
size_t bits_in_seL4_Word = sizeof(seL4_Word) * CHAR_BIT;
size_t num_irq_bitfields = ALIGN_UP(irq_config.max_irq_ids, bits_in_seL4_Word) / sizeof(seL4_Word);
size_t num_ntfn_bitfields = ALIGN_UP(irq_config.max_ntfn_ids, bits_in_seL4_Word) / sizeof(seL4_Word);
irq_cookie_t *cookie = 0;
err = ps_calloc(malloc_ops, 1, sizeof(irq_cookie_t), (void **) &cookie);
if (err) {
ZF_LOGE("Failed to allocate %zu bytes for cookie", sizeof(irq_cookie_t));
goto error;
}
/* Allocate the IRQ bookkeeping array, and set default values for some of the members */
err = ps_calloc(malloc_ops, 1, sizeof(irq_entry_t) * irq_config.max_irq_ids, (void **) & (cookie->irq_table));
if (err) {
ZF_LOGE("Failed to allocate IRQ bookkeeping array");
goto error;
}
for (int i = 0; i < irq_config.max_irq_ids; i++) {
cookie->irq_table[i].paired_ntfn = UNPAIRED_ID;
cookie->irq_table[i].allocated_badge_index = UNALLOCATED_BADGE_INDEX;
}
/* Allocate the notification bookkeeping array, and set default values for some of the members */
err = ps_calloc(malloc_ops, 1, sizeof(ntfn_entry_t) * irq_config.max_ntfn_ids,
(void **) & (cookie->ntfn_table));
if (err) {
ZF_LOGE("Failed to allocate notification bookkeeping array");
goto error;
}
for (int i = 0; i < irq_config.max_ntfn_ids; i++) {
memset(cookie->ntfn_table[i].bound_irqs, UNPAIRED_ID, sizeof(irq_id_t) * MAX_INTERRUPTS_TO_NOTIFICATIONS);
}
err = ps_calloc(malloc_ops, 1, num_irq_bitfields * sizeof(seL4_Word),
(void **) & (cookie->allocated_irq_bitfields));
if (err) {
ZF_LOGE("Failed to allocate the IRQ bitfields");
goto error;
}
err = ps_calloc(malloc_ops, 1, num_ntfn_bitfields * sizeof(seL4_Word),
(void **) & (cookie->allocated_ntfn_bitfields));
if (err) {
ZF_LOGE("Failed to allocate the notification bitfields");
goto error;
}
cookie->iface_type = iface_type;
cookie->simple = simple;
cookie->vka = vka;
cookie->malloc_ops = malloc_ops;
cookie->max_irq_ids = irq_config.max_irq_ids;
cookie->max_ntfn_ids = irq_config.max_ntfn_ids;
cookie->num_irq_bitfields = num_irq_bitfields;
cookie->num_ntfn_bitfields = num_ntfn_bitfields;
return cookie;
error:
if (cookie) {
if (cookie->irq_table) {
ps_free(malloc_ops, sizeof(irq_entry_t) * irq_config.max_irq_ids, cookie->irq_table);
}
if (cookie->ntfn_table) {
ps_free(malloc_ops, sizeof(ntfn_entry_t) * irq_config.max_ntfn_ids,
cookie->ntfn_table);
}
if (cookie->allocated_irq_bitfields) {
ps_free(malloc_ops, sizeof(seL4_Word) * num_irq_bitfields, cookie->allocated_irq_bitfields);
}
ps_free(malloc_ops, sizeof(irq_cookie_t), cookie);
}
return NULL;
}
static int sel4platsupport_irq_unregister(void *cookie, irq_id_t irq_id)
{
irq_cookie_t *irq_cookie = cookie;
if (!check_irq_id_is_valid(irq_cookie, irq_id)) {
return -EINVAL;
}
if (!check_irq_id_is_allocated(irq_cookie, irq_id)) {
return -EINVAL;
}
irq_entry_t *irq_entry = &(irq_cookie->irq_table[irq_id]);
if (irq_entry->paired_ntfn > UNPAIRED_ID) {
/* Clear the handler */
int error = seL4_IRQHandler_Clear(irq_entry->handler_path.capPtr);
if (error) {
/* Give a slightly ambigious message as we don't want to leak implementation details */
ZF_LOGE("Failed to unregister an IRQ");
return -EFAULT;
}
/* Delete the notification */
vka_cnode_delete(&(irq_entry->ntfn_path));
vka_cspace_free_path(irq_cookie->vka, irq_entry->ntfn_path);
/* Clear the necessary information in the notification array */
ntfn_entry_t *ntfn_entry = &(irq_cookie->ntfn_table[irq_entry->paired_ntfn]);
ntfn_entry->status_bitfield &= ~BIT(irq_entry->allocated_badge_index);
ntfn_entry->pending_bitfield &= ~BIT(irq_entry->allocated_badge_index);
ntfn_entry->bound_irqs[irq_entry->allocated_badge_index] = UNPAIRED_ID;
ntfn_entry->num_irqs_bound--;
}
/* Delete the handler */
vka_cnode_delete(&(irq_entry->handler_path));
vka_cspace_free_path(irq_cookie->vka, irq_entry->handler_path);
/* Zero-out the entire entry */
memset(irq_entry, 0, sizeof(irq_entry_t));
/* Reset parts of the entry */
irq_entry->paired_ntfn = UNPAIRED_ID;
irq_entry->allocated_badge_index = UNALLOCATED_BADGE_INDEX;
irq_cookie->num_registered_irqs--;
unfill_bit_in_bitfield(irq_cookie->allocated_irq_bitfields, irq_id);
return 0;
}
/* The register function for the standard IRQ interface */
static irq_id_t sel4platsupport_irq_register(void *cookie, ps_irq_t irq, irq_callback_fn_t callback,
void *callback_data)
{
irq_cookie_t *irq_cookie = cookie;
return irq_register_common(irq_cookie, irq, callback, callback_data);
}
/* The register function for the mini IRQ interface */
static irq_id_t sel4platsupport_irq_register_mini(void *cookie, ps_irq_t irq, irq_callback_fn_t callback,
void *callback_data)
{
irq_cookie_t *irq_cookie = cookie;
irq_id_t assigned_id = irq_register_common(irq_cookie, irq, callback, callback_data);
if (assigned_id < 0) {
/* Contains the error code if < 0 */
return assigned_id;
}
/* Pair this IRQ with the only notification */
int error = irq_set_ntfn_common(irq_cookie, MINI_IRQ_INTERFACE_NTFN_ID, assigned_id, NULL);
if (error) {
ZF_LOGF_IF(sel4platsupport_irq_unregister(irq_cookie, assigned_id),
"Failed to clean-up a failed operation");
return error;
}
return assigned_id;
}
static int sel4platsupport_irq_acknowledge(void *ack_data)
{
if (!ack_data) {
return -EINVAL;
}
int ret = 0;
ack_data_t *data = ack_data;
irq_cookie_t *irq_cookie = data->irq_cookie;
irq_id_t irq_id = data->irq_id;
if (!check_irq_id_is_valid(irq_cookie, irq_id)) {
ret = -EINVAL;
goto exit;
}
if (!check_irq_id_is_allocated(irq_cookie, irq_id)) {
ret = -EINVAL;
goto exit;
}
irq_entry_t *irq_entry = &(irq_cookie->irq_table[irq_id]);
int error = seL4_IRQHandler_Ack(irq_entry->handler_path.capPtr);
if (error) {
ZF_LOGE("Failed to acknowledge IRQ");
ret = -EFAULT;
goto exit;
}
exit:
ps_free(irq_cookie->malloc_ops, sizeof(ack_data_t), data);
return ret;
}
int sel4platsupport_new_irq_ops(ps_irq_ops_t *irq_ops, vka_t *vka, simple_t *simple,
irq_interface_config_t irq_config, ps_malloc_ops_t *malloc_ops)
{
if (!irq_ops || !vka || !simple || !malloc_ops) {
return -EINVAL;
}
if (irq_config.max_irq_ids == 0 || irq_config.max_ntfn_ids == 0) {
return -EINVAL;
}
irq_cookie_t *cookie = new_irq_ops_common(vka, simple, irq_config, malloc_ops, STANDARD_IFACE);
if (!cookie) {
return -ENOMEM;
}
/* Fill in the actual IRQ ops structure now */
irq_ops->cookie = (void *) cookie;
irq_ops->irq_register_fn = sel4platsupport_irq_register;
irq_ops->irq_unregister_fn = sel4platsupport_irq_unregister;
return 0;
}
int sel4platsupport_new_mini_irq_ops(ps_irq_ops_t *irq_ops, vka_t *vka, simple_t *simple,
ps_malloc_ops_t *malloc_ops, seL4_CPtr ntfn, seL4_Word usable_mask)
{
if (!irq_ops || !vka || !simple || !malloc_ops || !usable_mask) {
return -EINVAL;
}
if (ntfn == seL4_CapNull) {
return -EINVAL;
}
/* Get the number of bits that we can use in the badge,
* this is the amount of interrupts that can be registered at a given time */
size_t bits_in_mask = POPCOUNTL(usable_mask);
/* max_ntfn_ids is kinda irrelevant in the mini interface, but set it anyway */
irq_interface_config_t irq_config = { .max_irq_ids = bits_in_mask, .max_ntfn_ids = 1 };
irq_cookie_t *cookie = new_irq_ops_common(vka, simple, irq_config, malloc_ops, MINI_IFACE);
if (!cookie) {
return -ENOMEM;
}
/* Fill in the actual IRQ ops structure now */
irq_ops->cookie = (void *) cookie;
irq_ops->irq_register_fn = sel4platsupport_irq_register_mini;
irq_ops->irq_unregister_fn = sel4platsupport_irq_unregister;
/* Provide the ntfn */
provide_ntfn_common(cookie, ntfn, usable_mask, MINI_IRQ_INTERFACE_NTFN_ID);
return 0;
}
ntfn_id_t sel4platsupport_irq_provide_ntfn(ps_irq_ops_t *irq_ops, seL4_CPtr ntfn, seL4_Word usable_mask)
{
if (!irq_ops || ntfn == seL4_CapNull || !usable_mask) {
return -EINVAL;
}
irq_cookie_t *irq_cookie = irq_ops->cookie;
if (irq_cookie->iface_type == MINI_IFACE) {
ZF_LOGE("Trying to use %s with the mini IRQ Interface", __func__);
return -EPERM;
}
if (check_ntfn_id_all_allocated(irq_cookie)) {
return -EMFILE;
}
ntfn_id_t free_id = find_free_ntfn_id(irq_cookie);
if (free_id == -1) {
return -EMFILE;
}
provide_ntfn_common(irq_cookie, ntfn, usable_mask, free_id);
return free_id;
}
int sel4platsupport_irq_provide_ntfn_with_id(ps_irq_ops_t *irq_ops, seL4_CPtr ntfn,
seL4_Word usable_mask, ntfn_id_t id_hint)
{
if (!irq_ops || ntfn == seL4_CapNull || !usable_mask) {
return -EINVAL;
}
irq_cookie_t *irq_cookie = irq_ops->cookie;
if (irq_cookie->iface_type == MINI_IFACE) {
ZF_LOGE("Trying to use %s with the mini IRQ Interface", __func__);
return -EPERM;
}
if (check_ntfn_id_all_allocated(irq_cookie)) {
return -EMFILE;
}
if (irq_cookie->ntfn_table[id_hint].allocated) {
return -EADDRINUSE;
}
provide_ntfn_common(irq_cookie, ntfn, usable_mask, id_hint);
return 0;
}
int sel4platsupport_irq_return_ntfn(ps_irq_ops_t *irq_ops, ntfn_id_t ntfn_id,
seL4_CPtr *ret_cptr)
{
if (!irq_ops) {
return -EINVAL;
}
irq_cookie_t *irq_cookie = irq_ops->cookie;
if (irq_cookie->iface_type == MINI_IFACE) {
ZF_LOGE("Trying to use %s with the mini IRQ Interface", __func__);
return -EPERM;
}
if (!check_ntfn_id_is_valid(irq_cookie, ntfn_id)) {
return -EINVAL;
}
if (!check_ntfn_id_is_allocated(irq_cookie, ntfn_id)) {
return -EINVAL;
}
ntfn_entry_t *ntfn_entry = &(irq_cookie->ntfn_table[ntfn_id]);
if (ntfn_entry->num_irqs_bound > 0) {
unsigned long allocated_bits = ntfn_entry->status_bitfield;
while (allocated_bits) {
unsigned long index = CTZL(allocated_bits);
irq_entry_t *irq_entry = &(irq_cookie->irq_table[ntfn_entry->bound_irqs[index]]);
seL4_IRQHandler_Clear(irq_entry->handler_path.capPtr);
int error = vka_cnode_delete(&(irq_entry->ntfn_path));
ZF_LOGF_IF(error, "Failed to delete a minted notification");
irq_entry->ntfn_path = (cspacepath_t) {
0
};
irq_entry->paired_ntfn = UNPAIRED_ID;
irq_entry->allocated_badge_index = UNALLOCATED_BADGE_INDEX;
allocated_bits &= ~BIT(index);
}
}
if (ret_cptr) {
*ret_cptr = ntfn_entry->root_ntfn_path.capPtr;
}
/* Zero out the entire entry */
memset(ntfn_entry, 0, sizeof(ntfn_entry_t));
/* Reset the bound_irqs array for the entry */
memset(ntfn_entry->bound_irqs, UNPAIRED_ID, sizeof(irq_id_t) * MAX_INTERRUPTS_TO_NOTIFICATIONS);
irq_cookie->num_allocated_ntfns--;
unfill_bit_in_bitfield(irq_cookie->allocated_ntfn_bitfields, ntfn_id);
return 0;
}
int sel4platsupport_irq_set_ntfn(ps_irq_ops_t *irq_ops, ntfn_id_t ntfn_id, irq_id_t irq_id, seL4_Word *ret_badge)
{
if (!irq_ops) {
return -EINVAL;
}
irq_cookie_t *irq_cookie = irq_ops->cookie;
if (irq_cookie->iface_type == MINI_IFACE) {
ZF_LOGE("Trying to use %s with the mini IRQ Interface", __func__);
return -EPERM;
}
if (!check_ntfn_id_is_valid(irq_cookie, ntfn_id) ||
!check_ntfn_id_is_allocated(irq_cookie, ntfn_id)) {
return -EINVAL;
}
if (!check_irq_id_is_valid(irq_cookie, irq_id) ||
!check_irq_id_is_allocated(irq_cookie, irq_id)) {
return -EINVAL;
}
return irq_set_ntfn_common(irq_cookie, ntfn_id, irq_id, ret_badge);
}
int sel4platsupport_irq_unset_ntfn(ps_irq_ops_t *irq_ops, irq_id_t irq_id)
{
if (!irq_ops) {
return -EINVAL;
}
irq_cookie_t *irq_cookie = irq_ops->cookie;
if (irq_cookie->iface_type == MINI_IFACE) {
ZF_LOGE("Trying to use %s with the mini IRQ Interface", __func__);
return -EPERM;
}
if (!check_irq_id_is_valid(irq_cookie, irq_id) ||
!check_irq_id_is_allocated(irq_cookie, irq_id)) {
return -EINVAL;
}
irq_entry_t *irq_entry = &(irq_cookie->irq_table[irq_id]);
/* Check if the IRQ is bound to a notification */
if (irq_entry->paired_ntfn == UNPAIRED_ID) {
return -EINVAL;
}
/* Unbind the notification from the handler */
int error = seL4_IRQHandler_Clear(irq_entry->handler_path.capPtr);
if (error) {
ZF_LOGE("Failed to unpair a notification");
return -EFAULT;
}
/* Delete the minted notification and free the path */
vka_cnode_delete(&(irq_entry->ntfn_path));
vka_cspace_free_path(irq_cookie->vka, irq_entry->ntfn_path);
/* Free the allocated badge index and update the bookkeeping in the notification array and the irq array */
ntfn_id_t paired_id = irq_entry->paired_ntfn;
ntfn_entry_t *ntfn_entry = &(irq_cookie->ntfn_table[paired_id]);
ntfn_entry->status_bitfield &= ~BIT(irq_entry->allocated_badge_index);
ntfn_entry->pending_bitfield &= ~BIT(irq_entry->allocated_badge_index);
ntfn_entry->num_irqs_bound--;
ntfn_entry->bound_irqs[irq_entry->allocated_badge_index] = UNPAIRED_ID;
irq_entry->ntfn_path = (cspacepath_t) {
0
};
irq_entry->paired_ntfn = UNPAIRED_ID;
irq_entry->allocated_badge_index = UNALLOCATED_BADGE_INDEX;
return 0;
}
static bool perform_callback(irq_cookie_t *irq_cookie, irq_id_t irq_id, unsigned long badge_bit)
{
irq_entry_t *irq_entry = &(irq_cookie->irq_table[irq_id]);
irq_callback_fn_t callback = irq_entry->irq_callback_fn;
/* Check if callback was registered, if so, then run it */
if (callback) {
ack_data_t *ack_data = NULL;
int error = ps_calloc(irq_cookie->malloc_ops, 1, sizeof(ack_data_t), (void **) &ack_data);
ZF_LOGF_IF(error, "Failed to allocate memory for a cookie for the acknowledge function");
*ack_data = (ack_data_t) {
.irq_cookie = irq_cookie, .irq_id = irq_id
};
callback(irq_entry->callback_data,
sel4platsupport_irq_acknowledge, ack_data);
return true;
}
return false;
}
int sel4platsupport_irq_handle(ps_irq_ops_t *irq_ops, ntfn_id_t ntfn_id, seL4_Word handle_mask)
{
if (!irq_ops) {
return -EINVAL;
}
irq_cookie_t *irq_cookie = irq_ops->cookie;
if (!check_ntfn_id_is_valid(irq_cookie, ntfn_id) ||
!check_ntfn_id_is_allocated(irq_cookie, ntfn_id)) {
return -EINVAL;
}
ntfn_entry_t *ntfn_entry = &(irq_cookie->ntfn_table[ntfn_id]);
/* Just in case, but probably should throw an error at the user for passing in bits that
* we dont' handle */
unsigned long unchecked_bits = handle_mask & ntfn_entry->usable_mask;
while (unchecked_bits) {
unsigned long bit_index = CTZL(unchecked_bits);
irq_id_t paired_irq_id = ntfn_entry->bound_irqs[bit_index];
bool callback_called = perform_callback(irq_cookie, paired_irq_id, bit_index);
if (callback_called && ntfn_entry->pending_bitfield & BIT(bit_index)) {
/* Unset the bit, we've performed the callback for that interrupt */
ntfn_entry->pending_bitfield &= ~BIT(bit_index);
}
unchecked_bits &= ~BIT(bit_index);
}
return 0;
}
static void serve_irq(irq_cookie_t *irq_cookie, ntfn_id_t id, seL4_Word mask,
seL4_Word badge, seL4_Word *ret_leftover_bits)
{
seL4_Word served_mask = 0;
ntfn_entry_t *ntfn_entry = &(irq_cookie->ntfn_table[id]);
/* Mask out the bits the are not relevant to us */
unsigned long unchecked_bits = badge & ntfn_entry->usable_mask;
/* Also check the interrupts that were leftover and not served */
unchecked_bits |= ntfn_entry->pending_bitfield;
while (unchecked_bits) {
unsigned long bit_index = CTZL(unchecked_bits);
if (likely(BIT(bit_index) & mask)) {
irq_id_t paired_irq_id = ntfn_entry->bound_irqs[bit_index];
if (perform_callback(irq_cookie, paired_irq_id, bit_index)) {
/* Record that this particular IRQ was served */
served_mask |= BIT(bit_index);
}
}
unchecked_bits &= ~BIT(bit_index);
}
/* Record the IRQs that were not served but arrived, note that we don't want to
* override the leftover IRQs still inside the pending bitfield */
ntfn_entry->pending_bitfield ^= badge & ntfn_entry->usable_mask & ~(served_mask);
/* Write bits that are leftover */
if (ret_leftover_bits) {
*ret_leftover_bits = badge & ~(served_mask);
}
}
int sel4platsupport_irq_wait(ps_irq_ops_t *irq_ops, ntfn_id_t ntfn_id,
seL4_Word wait_mask,
seL4_Word *ret_leftover_bits)
{
if (!irq_ops) {
return -EINVAL;
}
irq_cookie_t *irq_cookie = irq_ops->cookie;
if (!check_ntfn_id_is_valid(irq_cookie, ntfn_id)) {
return -EINVAL;
}
if (!check_ntfn_id_is_allocated(irq_cookie, ntfn_id)) {
return -EINVAL;
}
seL4_CPtr ntfn = irq_cookie->ntfn_table[ntfn_id].root_ntfn_path.capPtr;
seL4_Word badge = 0;
/* Wait on the notification object */
seL4_Wait(ntfn, &badge);
serve_irq(irq_cookie, ntfn_id, wait_mask, badge, ret_leftover_bits);
return 0;
}
int sel4platsupport_irq_poll(ps_irq_ops_t *irq_ops, ntfn_id_t ntfn_id,
seL4_Word poll_mask,
seL4_Word *ret_leftover_bits)
{
if (!irq_ops) {
return -EINVAL;
}
irq_cookie_t *irq_cookie = irq_ops->cookie;
if (!check_ntfn_id_is_valid(irq_cookie, ntfn_id)) {
return -EINVAL;
}
if (!check_ntfn_id_is_allocated(irq_cookie, ntfn_id)) {
return -EINVAL;
}
seL4_CPtr ntfn = irq_cookie->ntfn_table[ntfn_id].root_ntfn_path.capPtr;
seL4_Word badge = 0;
/* Poll the notification object */
seL4_Poll(ntfn, &badge);
serve_irq(irq_cookie, ntfn_id, poll_mask, badge, ret_leftover_bits);
return 0;
}