libsel4utils: Refactor IRQ server sources

This commit refactors the existing functions in the IRQ server sources
to use the dynamic IRQ interface in libsel4platsupport.
diff --git a/libsel4utils/src/irq_server/irq_server.c b/libsel4utils/src/irq_server/irq_server.c
index 80983a1..1ea353a 100644
--- a/libsel4utils/src/irq_server/irq_server.c
+++ b/libsel4utils/src/irq_server/irq_server.c
@@ -65,272 +65,330 @@
     ps_malloc_ops_t *malloc_ops;
 };
 
-/* Executes the registered callback for incoming IRQS */
-static void
-irq_server_node_handle_irq(struct irq_server_node *n, seL4_Word badge)
+/* 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)
 {
-    struct irq_data* irqs;
-    irqs = n->irqs;
-    /* Mask out reserved bits */
-    badge = badge & n->badge_mask;
-    /* For each bit, call the registered handler */
-    while (badge) {
-        int irq_idx;
-        struct irq_data* irq;
-        irq_idx = CTZL(badge);
-        irq = &irqs[irq_idx];
-        ZF_LOGD("Received IRQ %d, badge 0x%x, index %d\n", irq->irq, (unsigned)badge, irq_idx);
-        irq->cb(irq);
-        badge &= ~BIT(irq_idx);
-    }
-}
-
-
-/* Registers an IRQ callback and enabled the IRQ */
-struct irq_data*
-irq_server_node_register_irq(irq_server_node_t n, irq_t irq, irq_handler_fn cb,
-                             void* token, vka_t* vka, seL4_CPtr cspace,
-                             simple_t *simple) {
-    struct irq_data* irqs;
-    int i;
-    irqs = n->irqs;
-
-    for (i = 0; i < NIRQS_PER_NODE; i++) {
-        /* If a cap has not been registered and the bit in the mask is not set */
-        if (irqs[i].cap == seL4_CapNull && (n->badge_mask & BIT(i))) {
-            irqs[i].cap = irq_bind(irq, n->notification, i, vka, simple);
-            if (irqs[i].cap == seL4_CapNull) {
-                ZF_LOGD("Failed to bind IRQ\n");
-                return NULL;
-            }
-            irqs[i].irq = irq;
-            irqs[i].cb = cb;
-            irqs[i].token = token;
-            return &irqs[i];
+    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");
         }
     }
-    return NULL;
 }
 
-/* Creates a new IRQ server node which contains Thread data and registered IRQ data. */
-struct irq_server_node*
-irq_server_node_new(seL4_CPtr notification, seL4_Word badge_mask) {
-    struct irq_server_node *n;
-    n = calloc(1, sizeof(*n));
-    if (n) {
-        n->notification = notification;
-        n->badge_mask = badge_mask;
+/* 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;
     }
-    return n;
+
+    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(struct irq_server_thread* st)
+static void _irq_thread_entry(irq_server_thread_t *my_thread_info, ps_irq_ops_t *irq_ops)
 {
-    seL4_CPtr sep;
-    seL4_CPtr notification;
-    uintptr_t node_ptr;
+    seL4_CPtr ep;
+    seL4_CPtr ntfn;
+    uintptr_t thread_info_ptr;
     seL4_Word label;
 
-    sep = st->delivery_sep;
-    notification = st->node->notification;
-    node_ptr = (uintptr_t)st->node;
-    label = st->label;
-    ZF_LOGD("thread started. Waiting on endpoint %d\n", notification);
+    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;
-        seL4_Wait(notification, &badge);
-        assert(badge != 0);
-        if (sep != seL4_CapNull) {
+        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, 2);
+            seL4_MessageInfo_t info = seL4_MessageInfo_new(label, 0, 0, IRQ_SERVER_MESSAGE_LENGTH);
             seL4_SetMR(0, badge);
-            seL4_SetMR(1, node_ptr);
-            seL4_Send(sep, info);
+            seL4_SetMR(1, thread_info_ptr);
+            seL4_Send(ep, info);
         } else {
-            /* No synchronous endpoint. Call the handler directly */
-            irq_server_node_handle_irq(st->node, badge);
+            /* No synchronous endpoint. Get the IRQ interface to invoke callbacks */
+            irq_server_node_handle_irq(my_thread_info, irq_ops, badge);
         }
     }
 }
 
-/* Creates a new thread for an IRQ server */
-struct irq_server_thread*
-irq_server_thread_new(vspace_t* vspace, vka_t* vka, seL4_CPtr cspace, seL4_Word priority,
-                      simple_t *simple, seL4_Word label, seL4_CPtr sep) {
-    struct irq_server_thread* st;
-    int err;
+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;
 
-    /* Allocate memory for the structure */
-    st = malloc(sizeof(*st));
-    if (st == NULL) {
-        return NULL;
+    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);
     }
-    st->node = irq_server_node_new(0, MASK(NIRQS_PER_NODE));
-    if (st->node == NULL) {
-        free(st);
-        return NULL;
+
+    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 */
-    st->delivery_sep = sep;
-    st->label = label;
-    st->next = NULL;
-    /* Create an endpoint to listen on */
-    err = vka_alloc_notification(vka, &st->notification);
-    if (err) {
-        ZF_LOGE("Failed to allocate IRQ notification endpoint for IRQ server thread\n");
-        return NULL;
-    }
-    st->node->notification = st->notification.cptr;
-    /* Create the IRQ thread */
-    sel4utils_thread_config_t config = thread_config_default(simple, cspace, seL4_NilData, 0, priority);
-    err = sel4utils_configure_thread_config(vka, vspace, vspace, config, &st->thread);
+    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;
 
-    if (err) {
-        ZF_LOGE("Failed to configure IRQ server thread\n");
-        return NULL;
+    /* 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 */
-    err = sel4utils_start_thread(&st->thread, (void*)_irq_thread_entry, st, NULL, 1);
-    if (err) {
-        ZF_LOGE("Failed to start IRQ server thread\n");
-        return NULL;
+    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;
     }
-    return st;
+
+    /* 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;
 }
 
-/******************
- *** IRQ server ***
- ******************/
-
-
-/* Handle an incoming IPC from a server node */
-void
-irq_server_handle_irq_ipc(irq_server_t irq_server UNUSED)
+void irq_server_handle_irq_ipc(irq_server_t *irq_server, seL4_MessageInfo_t msginfo)
 {
-    seL4_Word badge;
-    uintptr_t node_ptr;
+    seL4_Word badge = 0;
+    uintptr_t thread_info_ptr = 0;
 
-    badge = seL4_GetMR(0);
-    node_ptr = seL4_GetMR(1);
-    if (node_ptr == 0) {
-        ZF_LOGE("Invalid data in irq server IPC\n");
-    } else {
-        irq_server_node_handle_irq((struct irq_server_node*)node_ptr, badge);
+    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 */
-struct irq_data*
-irq_server_register_irq(irq_server_t irq_server, irq_t irq,
-                        irq_handler_fn cb, void* token) {
-    struct irq_server_thread* st;
-    struct irq_data* irq_data;
-
-    /* Try to assign the IRQ to an existing node */
-    for (st = irq_server->server_threads; st != NULL; st = st->next) {
-        irq_data = irq_server_node_register_irq(st->node, irq, cb, token,
-                                                irq_server->vka, irq_server->cspace,
-                                                &irq_server->simple);
-        if (irq_data) {
-            return irq_data;
-        }
-    }
-    /* Try to create a new node */
-    if (st == NULL && irq_server->max_irqs < 0) {
-        /* Create the node */
-        ZF_LOGD("Spawning new IRQ server thread\n");
-        st = irq_server_thread_new(irq_server->vspace, irq_server->vka, irq_server->cspace,
-                                   irq_server->thread_priority, &irq_server->simple,
-                                   irq_server->label, irq_server->delivery_ep);
-        if (st == NULL) {
-            ZF_LOGE("Failed to create server thread\n");
-            return NULL;
-        }
-
-        st->next = irq_server->server_threads;
-        irq_server->server_threads = st;
-        irq_data = irq_server_node_register_irq(st->node, irq, cb, token,
-                                                irq_server->vka, irq_server->cspace,
-                                                &irq_server->simple);
-        if (irq_data) {
-            return irq_data;
-        }
-    }
-    /* Give up */
-    ZF_LOGD("Failed to register for IRQ %d\n", irq);
-    return NULL;
-}
-
-/* Create a new IRQ server */
-int
-irq_server_new(vspace_t* vspace, vka_t* vka, seL4_CPtr cspace, seL4_Word priority,
-               simple_t *simple, seL4_CPtr sync_ep, seL4_Word label,
-               int nirqs, irq_server_t *ret_irq_server)
+irq_id_t irq_server_register_irq(irq_server_t *irq_server, ps_irq_t irq,
+                                 irq_callback_fn_t callback, void *callback_data)
 {
-    struct irq_server* irq_server;
-
-    /* Structure allocation and initialisation */
-    irq_server = malloc(sizeof(*irq_server));
     if (irq_server == NULL) {
-        ZF_LOGE("malloc failed on irq server memory allocation");
-        return -1;
+        ZF_LOGE("irq_server is NULL");
+        return -EINVAL;
     }
 
-    if (config_set(CONFIG_KERNEL_RT) && vka_alloc_reply(vka, &irq_server->reply) != 0) {
-        ZF_LOGE("Failed to allocate reply object");
-        free(irq_server);
-        return -1;
-    }
+    irq_server_thread_t *st = NULL;
+    irq_id_t ret_id = 0;
 
-    irq_server->delivery_ep = sync_ep;
-    irq_server->label = label;
-    irq_server->max_irqs = nirqs;
-    irq_server->vspace = vspace;
-    irq_server->cspace = cspace;
-    irq_server->vka = vka;
-    irq_server->thread_priority = priority;
-    irq_server->server_threads = NULL;
-    irq_server->simple = *simple;
-
-    /* If a fixed number of IRQs are requested, create and start the server threads */
-    if (nirqs > -1) {
-        struct irq_server_thread** server_thread;
-        int n_nodes;
-        int i;
-        server_thread = &irq_server->server_threads;
-        n_nodes = (nirqs + NIRQS_PER_NODE - 1) / NIRQS_PER_NODE;
-        for (i = 0; i < n_nodes; i++) {
-            *server_thread = irq_server_thread_new(vspace, vka, cspace, priority,
-                                                   simple, label, sync_ep);
-            server_thread = &(*server_thread)->next;
+    /* 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;
+            }
         }
     }
 
-    *ret_irq_server = irq_server;
-    return 0;
+    /* 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;
 }
 
-seL4_MessageInfo_t
-irq_server_wait_for_irq(irq_server_t irq_server, seL4_Word* badge_ret)
+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)
 {
-    seL4_MessageInfo_t msginfo;
-    seL4_Word badge;
+    if (num_irqs < 0) {
+        ZF_LOGE("num_irqs is less than 0");
+        return NULL;
+    }
 
-    /* Wait for an event */
+    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_RT) && 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. */
+    error = sel4platsupport_new_irq_ops(&(new->irq_ops), vka, simple, num_irqs, num_irqs, 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 (badge_ret) {
-        *badge_ret = badge;
+    if (ret_badge) {
+        *ret_badge = badge;
     }
 
     /* Forward to IRQ handlers */
-    if (seL4_MessageInfo_get_label(msginfo) == irq_server->label) {
-        irq_server_handle_irq_ipc(irq_server);
-    }
+    irq_server_handle_irq_ipc(irq_server, msginfo);
+
     return msginfo;
 }