blob: 952cdcd7205f6c690131b49ba4f50aedf3832156 [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 <sel4utils/irq_server.h>
#include <errno.h>
#include <simple/simple.h>
#include <sel4utils/thread.h>
#include <vka/capops.h>
#include <stdlib.h>
#include <string.h>
#include <platsupport/irq.h>
#include <sel4platsupport/irq.h>
#include <utils/util.h>
#define IRQ_SERVER_MESSAGE_LENGTH 2
typedef struct irq_server_node {
seL4_CPtr ntfn;
size_t max_irqs_bound;
size_t num_irqs_bound;
} irq_server_node_t;
typedef struct irq_server_thread irq_server_thread_t;
/* This is also forwarded declared as we have a pointer to a struct of the same type */
struct irq_server_thread {
thread_id_t thread_id;
irq_server_node_t *node;
seL4_CPtr delivery_ep;
seL4_Word label;
sel4utils_thread_t thread;
/* Linked list chain of threads */
irq_server_thread_t *next;
};
/* This is forward-declared in the header file, hence no typedef */
struct irq_server {
seL4_CPtr delivery_ep;
seL4_Word label;
vka_object_t reply;
irq_server_thread_t *server_threads;
size_t num_irqs;
size_t max_irqs;
/* New thread parameters */
seL4_Word priority;
seL4_CPtr cspace;
/* Allocation interfaces */
vka_t *vka;
vspace_t *vspace;
simple_t *simple;
ps_irq_ops_t irq_ops;
ps_malloc_ops_t *malloc_ops;
};
/* Executes the registered callback for incoming IRQs */
static void irq_server_node_handle_irq(irq_server_thread_t *thread_info,
ps_irq_ops_t *irq_ops, seL4_Word badge)
{
ntfn_id_t target_ntfn = thread_info->thread_id;
int error = sel4platsupport_irq_handle(irq_ops, target_ntfn, badge);
if (error) {
if (error == -EINVAL) {
ZF_LOGE("Passed in a wrong ntfn_id to the IRQ interface! Something is very wrong with the IRQ server");
} else {
ZF_LOGW("Failed to handle an IRQ");
}
}
}
/* Registers an IRQ callback and enables the IRQ */
static irq_id_t irq_server_node_register_irq(irq_server_node_t *node, ps_irq_t irq, irq_callback_fn_t callback,
void *callback_data, ntfn_id_t ntfn_id, irq_server_t *irq_server)
{
int error;
irq_id_t irq_id = ps_irq_register(&(irq_server->irq_ops), irq, callback, callback_data);
if (irq_id < 0) {
ZF_LOGE("Failed to register an IRQ");
/* The ID also serves as an error code */
return irq_id;
}
error = sel4platsupport_irq_set_ntfn(&(irq_server->irq_ops), ntfn_id, irq_id, NULL);
if (error) {
ZF_LOGE("Failed to pair an IRQ with a notification");
ps_irq_unregister(&(irq_server->irq_ops), irq_id);
return error;
}
node->num_irqs_bound++;
/* Success, return the ID that was assigned to the IRQ */
return irq_id;
}
/* Creates a new IRQ server node */
static irq_server_node_t *irq_server_node_new(seL4_CPtr ntfn, size_t max_irqs_bound,
ps_malloc_ops_t *malloc_ops)
{
irq_server_node_t *new_node = NULL;
ps_calloc(malloc_ops, 1, sizeof(irq_server_node_t), (void **) &new_node);
if (new_node) {
new_node->ntfn = ntfn;
new_node->max_irqs_bound = max_irqs_bound;
}
return new_node;
}
/* IRQ handler thread. Wait on a notification object for IRQs. When one arrives, send a
* synchronous message to the registered endpoint. If no synchronous endpoint was
* registered, call the appropriate handler function directly (must be thread safe) */
static void _irq_thread_entry(irq_server_thread_t *my_thread_info, ps_irq_ops_t *irq_ops)
{
seL4_CPtr ep;
seL4_CPtr ntfn;
uintptr_t thread_info_ptr;
seL4_Word label;
ep = my_thread_info->delivery_ep;
ntfn = my_thread_info->node->ntfn;
thread_info_ptr = (uintptr_t)my_thread_info;
label = my_thread_info->label;
ZF_LOGD("thread started. Waiting on endpoint %lu\n", ntfn);
while (1) {
seL4_Word badge = 0;
seL4_Wait(ntfn, &badge);
if (ep != seL4_CapNull) {
/* Synchronous endpoint registered. Send IPC */
seL4_MessageInfo_t info = seL4_MessageInfo_new(label, 0, 0, IRQ_SERVER_MESSAGE_LENGTH);
seL4_SetMR(0, badge);
seL4_SetMR(1, thread_info_ptr);
seL4_Send(ep, info);
} else {
/* No synchronous endpoint. Get the IRQ interface to invoke callbacks */
irq_server_node_handle_irq(my_thread_info, irq_ops, badge);
}
}
}
thread_id_t irq_server_thread_new(irq_server_t *irq_server, seL4_CPtr provided_ntfn,
seL4_Word usable_mask, thread_id_t id_hint)
{
int error;
irq_server_thread_t *new_thread = NULL;
irq_server_node_t *new_node = NULL;
/* Check if the user provided a notification, if not, then allocate one */
seL4_CPtr ntfn_to_use = seL4_CapNull;
seL4_Word mask_to_use = 0;
vka_object_t ntfn_obj = {0};
if (provided_ntfn != seL4_CapNull) {
ntfn_to_use = provided_ntfn;
if (usable_mask == 0) {
ZF_LOGE("Can't pair any interrupts on this notification");
return -EINVAL;
}
mask_to_use = usable_mask;
} else {
error = vka_alloc_notification(irq_server->vka, &ntfn_obj);
if (error) {
ZF_LOGE("Failed to allocate a notification");
return -ENOMEM;
}
ntfn_to_use = ntfn_obj.cptr;
mask_to_use = MASK(seL4_BadgeBits);
}
thread_id_t thread_id_to_use = -1;
/* Register this notification with the IRQ interface */
if (id_hint >= 0) {
error = sel4platsupport_irq_provide_ntfn_with_id(&(irq_server->irq_ops), ntfn_to_use,
mask_to_use, id_hint);
if (error) {
goto fail;
}
thread_id_to_use = id_hint;
} else {
ntfn_id_t ntfn_id = sel4platsupport_irq_provide_ntfn(&(irq_server->irq_ops), ntfn_to_use, mask_to_use);
if (ntfn_id < 0) {
/* 'sel4platsupport_irq_provide_ntfn' returns an error code on failure */
error = ntfn_id;
goto fail;
}
thread_id_to_use = ntfn_id;
}
error = ps_calloc(irq_server->malloc_ops, 1, sizeof(irq_server_thread_t), (void **) &new_thread);
if (new_thread == NULL) {
ZF_LOGE("Failed to allocate memory for bookkeeping structure");
/* ps_calloc returns errnos but these are greater than 0 */
error *= -1;
goto fail;
}
/* Find the amount of IRQs that can be bound to this node */
size_t max_irqs_bound = POPCOUNTL(mask_to_use);
/* Allocate memory for the node */
new_node = irq_server_node_new(ntfn_to_use, max_irqs_bound, irq_server->malloc_ops);
if (new_node == NULL) {
error = -ENOMEM;
goto fail;
}
/* Initialise structure */
new_thread->delivery_ep = irq_server->delivery_ep;
new_thread->label = irq_server->label;
new_thread->node = new_node;
new_thread->thread_id = thread_id_to_use;
/* Create the IRQ thread */
sel4utils_thread_config_t config = thread_config_default(irq_server->simple, irq_server->cspace,
seL4_NilData, 0, irq_server->priority);
error = sel4utils_configure_thread_config(irq_server->vka, irq_server->vspace,
irq_server->vspace, config, &(new_thread->thread));
if (error) {
ZF_LOGE("Failed to configure IRQ server thread");
goto fail;
}
bool thread_created = true;
/* Start the thread */
error = sel4utils_start_thread(&new_thread->thread, (void *)_irq_thread_entry,
new_thread, &(irq_server->irq_ops), 1);
if (error) {
ZF_LOGE("Failed to start IRQ server thread");
goto fail;
}
/* Append this thread structure to the head of the list */
new_thread->next = irq_server->server_threads;
irq_server->server_threads = new_thread;
return thread_id_to_use;
fail:
/* Check if we've registered a notification with the IRQ interface */
if (thread_id_to_use) {
ZF_LOGF_IF(sel4platsupport_irq_return_ntfn(&(irq_server->irq_ops), thread_id_to_use, NULL),
"Failed to clean-up a failure situation");
}
if (ntfn_obj.cptr) {
vka_free_object(irq_server->vka, &ntfn_obj);
}
if (new_node) {
ps_free(irq_server->malloc_ops, sizeof(irq_server_node_t), new_node);
}
if (thread_created) {
sel4utils_clean_up_thread(irq_server->vka, irq_server->vspace, &(new_thread->thread));
}
if (new_thread) {
ps_free(irq_server->malloc_ops, sizeof(irq_server_thread_t), new_node);
}
return error;
}
void irq_server_handle_irq_ipc(irq_server_t *irq_server, seL4_MessageInfo_t msginfo)
{
seL4_Word badge = 0;
uintptr_t thread_info_ptr = 0;
seL4_Word label = seL4_MessageInfo_get_label(msginfo);
seL4_Word length = seL4_MessageInfo_get_length(msginfo);
if (label == irq_server->label && length == IRQ_SERVER_MESSAGE_LENGTH) {
badge = seL4_GetMR(0);
thread_info_ptr = seL4_GetMR(1);
if (thread_info_ptr == 0) {
ZF_LOGE("Invalid data in IRQ server IPC\n");
} else {
irq_server_node_handle_irq((irq_server_thread_t *) thread_info_ptr, &(irq_server->irq_ops), badge);
}
}
}
/* Register for a function to be called when an IRQ arrives */
irq_id_t irq_server_register_irq(irq_server_t *irq_server, ps_irq_t irq,
irq_callback_fn_t callback, void *callback_data)
{
if (irq_server == NULL) {
ZF_LOGE("irq_server is NULL");
return -EINVAL;
}
irq_server_thread_t *st = NULL;
irq_id_t ret_id = 0;
/* Try to assign the IRQ to an existing node/thread */
for (st = irq_server->server_threads; st != NULL; st = st->next) {
if (st->node->num_irqs_bound < st->node->max_irqs_bound) {
/* thread_id is synonymous with a ntfn_id */
ret_id = irq_server_node_register_irq(st->node, irq, callback, callback_data,
(ntfn_id_t) st->thread_id, irq_server);
if (ret_id >= 0) {
return ret_id;
}
}
}
/* No threads are available to take this IRQ, alert the user */
ZF_LOGE("No threads are available to take this interrupt, consider making more");
return -ENOENT;
}
irq_server_t *irq_server_new(vspace_t *vspace, vka_t *vka, seL4_Word priority,
simple_t *simple, seL4_CPtr cspace, seL4_CPtr delivery_ep, seL4_Word label,
size_t num_irqs, ps_malloc_ops_t *malloc_ops)
{
if (num_irqs < 0) {
ZF_LOGE("num_irqs is less than 0");
return NULL;
}
if (!vspace || !vka || !simple || !malloc_ops) {
ZF_LOGE("ret_irq_server is NULL");
return NULL;
}
int error;
irq_server_t *new = NULL;
error = ps_calloc(malloc_ops, 1, sizeof(irq_server_t), (void **) &new);
if (config_set(CONFIG_KERNEL_MCS) && vka_alloc_reply(vka, &(new->reply)) != 0) {
ZF_LOGE("Failed to allocate reply object");
return NULL;
}
/* Set max_ntfn_ids to equal the number of IRQs. We can calculate the ntfn IDs we need,
* but this is really complex, and leads to code that is hard to maintain. */
irq_interface_config_t irq_config = { .max_irq_ids = num_irqs, .max_ntfn_ids = num_irqs } ;
error = sel4platsupport_new_irq_ops(&(new->irq_ops), vka, simple, irq_config, malloc_ops);
if (error) {
ZF_LOGE("Failed to initialise supporting backend for IRQ server");
return NULL;
}
new->delivery_ep = delivery_ep;
new->label = label;
new->max_irqs = num_irqs;
new->priority = priority;
new->vka = vka;
new->vspace = vspace;
new->simple = simple;
new->cspace = cspace;
new->malloc_ops = malloc_ops;
return new;
}
seL4_MessageInfo_t irq_server_wait_for_irq(irq_server_t *irq_server, seL4_Word *ret_badge)
{
seL4_MessageInfo_t msginfo = {0};
seL4_Word badge = 0;
if (irq_server->delivery_ep == seL4_CapNull) {
ZF_LOGE("No endpoint was registered with the IRQ server");
return msginfo;
}
/* Wait for an event, api_recv instead of seL4_Recv b/c of MCS */
msginfo = api_recv(irq_server->delivery_ep, &badge, irq_server->reply.cptr);
if (ret_badge) {
*ret_badge = badge;
}
/* Forward to IRQ handlers */
irq_server_handle_irq_ipc(irq_server, msginfo);
return msginfo;
}