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