1/*
2 * Copyright 2019, Data61
3 * Commonwealth Scientific and Industrial Research Organisation (CSIRO)
4 * ABN 41 687 119 230.
5 *
6 * This software may be distributed and modified according to the terms of
7 * the BSD 2-Clause license. Note that NO WARRANTY is provided.
8 * See "LICENSE_BSD2.txt" for details.
9 *
10 * @TAG(DATA61_BSD)
11 */
12
13#include <sel4utils/irq_server.h>
14
15#include <errno.h>
16#include <simple/simple.h>
17#include <sel4utils/thread.h>
18#include <vka/capops.h>
19#include <stdlib.h>
20#include <string.h>
21#include <platsupport/irq.h>
22#include <sel4platsupport/irq.h>
23
24#include <utils/util.h>
25
26#define IRQ_SERVER_MESSAGE_LENGTH 2
27
28typedef struct irq_server_node {
29    seL4_CPtr ntfn;
30    size_t max_irqs_bound;
31    size_t num_irqs_bound;
32} irq_server_node_t;
33
34typedef struct irq_server_thread irq_server_thread_t;
35
36/* This is also forwarded declared as we have a pointer to a struct of the same type */
37struct irq_server_thread {
38    thread_id_t thread_id;
39    irq_server_node_t *node;
40    seL4_CPtr delivery_ep;
41    seL4_Word label;
42    sel4utils_thread_t thread;
43    /* Linked list chain of threads */
44    irq_server_thread_t *next;
45};
46
47/* This is forward-declared in the header file, hence no typedef */
48struct irq_server {
49    seL4_CPtr delivery_ep;
50    seL4_Word label;
51    vka_object_t reply;
52    irq_server_thread_t *server_threads;
53    size_t num_irqs;
54    size_t max_irqs;
55
56    /* New thread parameters */
57    seL4_Word priority;
58    seL4_CPtr cspace;
59
60    /* Allocation interfaces */
61    vka_t *vka;
62    vspace_t *vspace;
63    simple_t *simple;
64    ps_irq_ops_t irq_ops;
65    ps_malloc_ops_t *malloc_ops;
66};
67
68/* Executes the registered callback for incoming IRQs */
69static void irq_server_node_handle_irq(irq_server_thread_t *thread_info,
70                                       ps_irq_ops_t *irq_ops, seL4_Word badge)
71{
72    ntfn_id_t target_ntfn = thread_info->thread_id;
73    int error = sel4platsupport_irq_handle(irq_ops, target_ntfn, badge);
74    if (error) {
75        if (error == -EINVAL) {
76            ZF_LOGE("Passed in a wrong ntfn_id to the IRQ interface! Something is very wrong with the IRQ server");
77        } else {
78            ZF_LOGW("Failed to handle an IRQ");
79        }
80    }
81}
82
83/* Registers an IRQ callback and enables the IRQ */
84static irq_id_t irq_server_node_register_irq(irq_server_node_t *node, ps_irq_t irq, irq_callback_fn_t callback,
85                                             void *callback_data, ntfn_id_t ntfn_id, irq_server_t *irq_server)
86{
87    int error;
88
89    irq_id_t irq_id = ps_irq_register(&(irq_server->irq_ops), irq, callback, callback_data);
90    if (irq_id < 0) {
91        ZF_LOGE("Failed to register an IRQ");
92        /* The ID also serves as an error code */
93        return irq_id;
94    }
95
96    error = sel4platsupport_irq_set_ntfn(&(irq_server->irq_ops), ntfn_id, irq_id, NULL);
97    if (error) {
98        ZF_LOGE("Failed to pair an IRQ with a notification");
99        ps_irq_unregister(&(irq_server->irq_ops), irq_id);
100        return error;
101    }
102
103    node->num_irqs_bound++;
104
105    /* Success, return the ID that was assigned to the IRQ */
106    return irq_id;
107}
108
109/* Creates a new IRQ server node */
110static irq_server_node_t *irq_server_node_new(seL4_CPtr ntfn, size_t max_irqs_bound,
111                                              ps_malloc_ops_t *malloc_ops)
112{
113    irq_server_node_t *new_node = NULL;
114    ps_calloc(malloc_ops, 1, sizeof(irq_server_node_t), (void **) &new_node);
115    if (new_node) {
116        new_node->ntfn = ntfn;
117        new_node->max_irqs_bound = max_irqs_bound;
118    }
119    return new_node;
120}
121
122/* IRQ handler thread. Wait on a notification object for IRQs. When one arrives, send a
123 * synchronous message to the registered endpoint. If no synchronous endpoint was
124 * registered, call the appropriate handler function directly (must be thread safe) */
125static void _irq_thread_entry(irq_server_thread_t *my_thread_info, ps_irq_ops_t *irq_ops)
126{
127    seL4_CPtr ep;
128    seL4_CPtr ntfn;
129    uintptr_t thread_info_ptr;
130    seL4_Word label;
131
132    ep = my_thread_info->delivery_ep;
133    ntfn = my_thread_info->node->ntfn;
134    thread_info_ptr = (uintptr_t)my_thread_info;
135    label = my_thread_info->label;
136    ZF_LOGD("thread started. Waiting on endpoint %lu\n", ntfn);
137
138    while (1) {
139        seL4_Word badge = 0;
140        seL4_Wait(ntfn, &badge);
141        if (ep != seL4_CapNull) {
142            /* Synchronous endpoint registered. Send IPC */
143            seL4_MessageInfo_t info = seL4_MessageInfo_new(label, 0, 0, IRQ_SERVER_MESSAGE_LENGTH);
144            seL4_SetMR(0, badge);
145            seL4_SetMR(1, thread_info_ptr);
146            seL4_Send(ep, info);
147        } else {
148            /* No synchronous endpoint. Get the IRQ interface to invoke callbacks */
149            irq_server_node_handle_irq(my_thread_info, irq_ops, badge);
150        }
151    }
152}
153
154thread_id_t irq_server_thread_new(irq_server_t *irq_server, seL4_CPtr provided_ntfn,
155                                  seL4_Word usable_mask, thread_id_t id_hint)
156{
157    int error;
158
159    irq_server_thread_t *new_thread = NULL;
160
161    irq_server_node_t *new_node = NULL;
162
163    /* Check if the user provided a notification, if not, then allocate one */
164    seL4_CPtr ntfn_to_use = seL4_CapNull;
165    seL4_Word mask_to_use = 0;
166    vka_object_t ntfn_obj = {0};
167    if (provided_ntfn != seL4_CapNull) {
168        ntfn_to_use = provided_ntfn;
169        if (usable_mask == 0) {
170            ZF_LOGE("Can't pair any interrupts on this notification");
171            return -EINVAL;
172        }
173        mask_to_use = usable_mask;
174    } else {
175        error = vka_alloc_notification(irq_server->vka, &ntfn_obj);
176        if (error) {
177            ZF_LOGE("Failed to allocate a notification");
178            return -ENOMEM;
179        }
180        ntfn_to_use = ntfn_obj.cptr;
181        mask_to_use = MASK(seL4_BadgeBits);
182    }
183
184    thread_id_t thread_id_to_use = -1;
185    /* Register this notification with the IRQ interface */
186    if (id_hint >= 0) {
187        error = sel4platsupport_irq_provide_ntfn_with_id(&(irq_server->irq_ops), ntfn_to_use,
188                                                         mask_to_use, id_hint);
189        if (error) {
190            goto fail;
191        }
192        thread_id_to_use = id_hint;
193    } else {
194        ntfn_id_t ntfn_id = sel4platsupport_irq_provide_ntfn(&(irq_server->irq_ops), ntfn_to_use, mask_to_use);
195        if (ntfn_id < 0) {
196            /* 'sel4platsupport_irq_provide_ntfn' returns an error code on failure */
197            error = ntfn_id;
198            goto fail;
199        }
200        thread_id_to_use = ntfn_id;
201    }
202
203    error = ps_calloc(irq_server->malloc_ops, 1, sizeof(irq_server_thread_t), (void **) &new_thread);
204    if (new_thread == NULL) {
205        ZF_LOGE("Failed to allocate memory for bookkeeping structure");
206        /* ps_calloc returns errnos but these are greater than 0 */
207        error *= -1;
208        goto fail;
209    }
210
211    /* Find the amount of IRQs that can be bound to this node */
212    size_t max_irqs_bound = POPCOUNTL(mask_to_use);
213    /* Allocate memory for the node */
214    new_node = irq_server_node_new(ntfn_to_use, max_irqs_bound, irq_server->malloc_ops);
215    if (new_node == NULL) {
216        error = -ENOMEM;
217        goto fail;
218    }
219
220    /* Initialise structure */
221    new_thread->delivery_ep = irq_server->delivery_ep;
222    new_thread->label = irq_server->label;
223    new_thread->node = new_node;
224    new_thread->thread_id = thread_id_to_use;
225
226    /* Create the IRQ thread */
227    sel4utils_thread_config_t config = thread_config_default(irq_server->simple, irq_server->cspace,
228                                                             seL4_NilData, 0, irq_server->priority);
229    error = sel4utils_configure_thread_config(irq_server->vka, irq_server->vspace,
230                                              irq_server->vspace, config, &(new_thread->thread));
231    if (error) {
232        ZF_LOGE("Failed to configure IRQ server thread");
233        goto fail;
234    }
235
236    bool thread_created = true;
237
238    /* Start the thread */
239    error = sel4utils_start_thread(&new_thread->thread, (void *)_irq_thread_entry,
240                                   new_thread, &(irq_server->irq_ops), 1);
241    if (error) {
242        ZF_LOGE("Failed to start IRQ server thread");
243        goto fail;
244    }
245
246    /* Append this thread structure to the head of the list */
247    new_thread->next = irq_server->server_threads;
248    irq_server->server_threads = new_thread;
249
250    return thread_id_to_use;
251
252fail:
253
254    /* Check if we've registered a notification with the IRQ interface */
255    if (thread_id_to_use) {
256        ZF_LOGF_IF(sel4platsupport_irq_return_ntfn(&(irq_server->irq_ops), thread_id_to_use, NULL),
257                   "Failed to clean-up a failure situation");
258    }
259
260    if (ntfn_obj.cptr) {
261        vka_free_object(irq_server->vka, &ntfn_obj);
262    }
263
264    if (new_node) {
265        ps_free(irq_server->malloc_ops, sizeof(irq_server_node_t), new_node);
266    }
267
268    if (thread_created) {
269        sel4utils_clean_up_thread(irq_server->vka, irq_server->vspace, &(new_thread->thread));
270    }
271
272    if (new_thread) {
273        ps_free(irq_server->malloc_ops, sizeof(irq_server_thread_t), new_node);
274    }
275
276    return error;
277}
278
279void irq_server_handle_irq_ipc(irq_server_t *irq_server, seL4_MessageInfo_t msginfo)
280{
281    seL4_Word badge = 0;
282    uintptr_t thread_info_ptr = 0;
283
284    seL4_Word label = seL4_MessageInfo_get_label(msginfo);
285    seL4_Word length = seL4_MessageInfo_get_length(msginfo);
286
287    if (label == irq_server->label && length == IRQ_SERVER_MESSAGE_LENGTH) {
288        badge = seL4_GetMR(0);
289        thread_info_ptr = seL4_GetMR(1);
290        if (thread_info_ptr == 0) {
291            ZF_LOGE("Invalid data in IRQ server IPC\n");
292        } else {
293            irq_server_node_handle_irq((irq_server_thread_t *) thread_info_ptr, &(irq_server->irq_ops), badge);
294        }
295    }
296}
297
298/* Register for a function to be called when an IRQ arrives */
299irq_id_t irq_server_register_irq(irq_server_t *irq_server, ps_irq_t irq,
300                                 irq_callback_fn_t callback, void *callback_data)
301{
302    if (irq_server == NULL) {
303        ZF_LOGE("irq_server is NULL");
304        return -EINVAL;
305    }
306
307    irq_server_thread_t *st = NULL;
308    irq_id_t ret_id = 0;
309
310    /* Try to assign the IRQ to an existing node/thread */
311    for (st = irq_server->server_threads; st != NULL; st = st->next) {
312        if (st->node->num_irqs_bound < st->node->max_irqs_bound) {
313            /* thread_id is synonymous with a ntfn_id */
314            ret_id = irq_server_node_register_irq(st->node, irq, callback, callback_data,
315                                                  (ntfn_id_t) st->thread_id, irq_server);
316            if (ret_id >= 0) {
317                return ret_id;
318            }
319        }
320    }
321
322    /* No threads are available to take this IRQ, alert the user */
323    ZF_LOGE("No threads are available to take this interrupt, consider making more");
324    return -ENOENT;
325}
326
327irq_server_t *irq_server_new(vspace_t *vspace, vka_t *vka, seL4_Word priority,
328                             simple_t *simple, seL4_CPtr cspace, seL4_CPtr delivery_ep, seL4_Word label,
329                             size_t num_irqs, ps_malloc_ops_t *malloc_ops)
330{
331    if (num_irqs < 0) {
332        ZF_LOGE("num_irqs is less than 0");
333        return NULL;
334    }
335
336    if (!vspace || !vka || !simple || !malloc_ops) {
337        ZF_LOGE("ret_irq_server is NULL");
338        return NULL;
339    }
340
341    int error;
342
343    irq_server_t *new = NULL;
344
345    error = ps_calloc(malloc_ops, 1, sizeof(irq_server_t), (void **) &new);
346
347    if (config_set(CONFIG_KERNEL_MCS) && vka_alloc_reply(vka, &(new->reply)) != 0) {
348        ZF_LOGE("Failed to allocate reply object");
349        return NULL;
350    }
351
352    /* Set max_ntfn_ids to equal the number of IRQs. We can calculate the ntfn IDs we need,
353     * but this is really complex, and leads to code that is hard to maintain. */
354    irq_interface_config_t irq_config = { .max_irq_ids = num_irqs, .max_ntfn_ids = num_irqs } ;
355    error = sel4platsupport_new_irq_ops(&(new->irq_ops), vka, simple, irq_config, malloc_ops);
356    if (error) {
357        ZF_LOGE("Failed to initialise supporting backend for IRQ server");
358        return NULL;
359    }
360
361    new->delivery_ep = delivery_ep;
362    new->label = label;
363    new->max_irqs = num_irqs;
364    new->priority = priority;
365
366    new->vka = vka;
367    new->vspace = vspace;
368    new->simple = simple;
369    new->cspace = cspace;
370    new->malloc_ops = malloc_ops;
371
372    return new;
373}
374
375seL4_MessageInfo_t irq_server_wait_for_irq(irq_server_t *irq_server, seL4_Word *ret_badge)
376{
377    seL4_MessageInfo_t msginfo = {0};
378    seL4_Word badge = 0;
379
380    if (irq_server->delivery_ep == seL4_CapNull) {
381        ZF_LOGE("No endpoint was registered with the IRQ server");
382        return msginfo;
383    }
384
385    /* Wait for an event, api_recv instead of seL4_Recv b/c of MCS */
386    msginfo = api_recv(irq_server->delivery_ep, &badge, irq_server->reply.cptr);
387    if (ret_badge) {
388        *ret_badge = badge;
389    }
390
391    /* Forward to IRQ handlers */
392    irq_server_handle_irq_ipc(irq_server, msginfo);
393
394    return msginfo;
395}
396