1 /* Deferred Procedure Call support kernel module 2 * 3 * Copyright 2007, Haiku, Inc. All Rights Reserved. 4 * Distributed under the terms of the MIT License. 5 * 6 * Authors: 7 * Philippe Houdoin, philippe.houdoin@free.fr 8 */ 9 10 #include <KernelExport.h> 11 #include <stdlib.h> 12 13 #ifndef COMPILE_FOR_R5 14 #include <stdio.h> // For snprintf() 15 #endif 16 17 #include <dpc.h> 18 19 // Private DPC queue structures 20 typedef struct { 21 dpc_func function; 22 void *arg; 23 } dpc_slot; 24 25 26 typedef struct { 27 thread_id thread; 28 sem_id wakeup_sem; 29 spinlock lock; 30 int size; 31 int count; 32 int head; 33 int tail; 34 dpc_slot slots[0]; 35 // size * slots follow 36 } dpc_queue; 37 38 #define DPC_QUEUE_SIZE 64 39 40 static int32 41 dpc_thread(void *arg) 42 { 43 dpc_queue *queue = arg; 44 dpc_slot dpc; 45 46 // Let's wait forever/until semaphore death for new DPC slot to show up 47 while (acquire_sem(queue->wakeup_sem) == B_OK) { 48 cpu_status former; 49 50 // grab the next dpc slot 51 former = disable_interrupts(); 52 acquire_spinlock(&queue->lock); 53 54 dpc = queue->slots[queue->head]; 55 queue->head = (queue->head++) % queue->size; 56 queue->count--; 57 58 release_spinlock(&queue->lock); 59 restore_interrupts(former); 60 61 dpc.function(dpc.arg); 62 } 63 64 // Let's finish the pending DPCs, if any. 65 // Otherwise, resource could leaks... 66 while (queue->count--) { 67 dpc = queue->slots[queue->head]; 68 queue->head = (queue->head++) % queue->size; 69 dpc.function(dpc.arg); 70 } 71 72 // Now, let's die quietly, ignored by all... sigh. 73 return 0; 74 } 75 76 // ---- Public API 77 78 static status_t 79 new_dpc_queue(void **handle, const char *name, int32 priority) 80 { 81 char str[64]; 82 dpc_queue *queue; 83 84 if (!handle) 85 return B_BAD_VALUE; 86 87 queue = malloc(sizeof(dpc_queue) + DPC_QUEUE_SIZE * sizeof(dpc_slot)); 88 if (!queue) 89 return B_NO_MEMORY; 90 91 queue->head = queue->tail = 0; 92 queue->size = DPC_QUEUE_SIZE; 93 queue->count = 0; 94 B_INITIALIZE_SPINLOCK(&queue->lock); // Init the spinlock 95 96 #ifdef __HAIKU__ 97 snprintf(str, sizeof(str), "%.*s_wakeup_sem", 98 (int) sizeof(str) - 11, name); 99 #else 100 strncpy(str, name, sizeof(str) - 1); 101 strncat(str, "_wakeup_sem", sizeof(str) - 1); 102 str[sizeof(str) - 1] = '\0'; 103 #endif 104 105 queue->wakeup_sem = create_sem(0, str); 106 if (queue->wakeup_sem < B_OK) { 107 status_t status = queue->wakeup_sem; 108 free(queue); 109 return status; 110 } 111 set_sem_owner(queue->wakeup_sem, B_SYSTEM_TEAM); 112 113 // Fire a kernel thread to actually handle (aka call them!) 114 // the queued/deferred procedure calls 115 queue->thread = spawn_kernel_thread(dpc_thread, name, priority, queue); 116 if (queue->thread < 0) { 117 status_t status = queue->thread; 118 delete_sem(queue->wakeup_sem); 119 free(queue); 120 return status; 121 } 122 resume_thread(queue->thread); 123 124 *handle = queue; 125 126 return B_OK; 127 } 128 129 130 static status_t 131 delete_dpc_queue(void *handle) 132 { 133 dpc_queue *queue = handle; 134 thread_id thread; 135 status_t exit_value; 136 cpu_status former; 137 138 if (!queue) 139 return B_BAD_VALUE; 140 141 // Close the queue: queue_dpc() should knows we're closing: 142 former = disable_interrupts(); 143 acquire_spinlock(&queue->lock); 144 145 thread = queue->thread; 146 queue->thread = -1; 147 148 release_spinlock(&queue->lock); 149 restore_interrupts(former); 150 151 // Wakeup the thread by murdering its favorite semaphore 152 delete_sem(queue->wakeup_sem); 153 wait_for_thread(thread, &exit_value); 154 155 free(queue); 156 157 return B_OK; 158 } 159 160 161 static status_t 162 queue_dpc(void *handle, dpc_func function, void *arg) 163 { 164 dpc_queue *queue = handle; 165 cpu_status former; 166 status_t status = B_OK; 167 168 if (!queue || !function) 169 return B_BAD_VALUE; 170 171 // Try to be safe being called from interrupt handlers: 172 former = disable_interrupts(); 173 acquire_spinlock(&queue->lock); 174 175 if (queue->thread < 0) { 176 // Queue thread is dying... 177 status = B_CANCELED; 178 } else if (queue->count == queue->size) 179 // This DPC queue is full, sorry 180 status = B_NO_MEMORY; 181 else { 182 queue->slots[queue->tail].function = function; 183 queue->slots[queue->tail].arg = arg; 184 queue->tail = (queue->tail++) % queue->size; 185 queue->count++; 186 } 187 188 release_spinlock(&queue->lock); 189 restore_interrupts(former); 190 191 if (status == B_OK) 192 // Wake up the corresponding dpc thead 193 // Notice that interrupt handlers should returns B_INVOKE_SCHEDULER to 194 // shorten DPC latency as much as possible... 195 status = release_sem_etc(queue->wakeup_sem, 1, B_DO_NOT_RESCHEDULE); 196 197 return status; 198 } 199 200 201 static status_t 202 std_ops(int32 op, ...) 203 { 204 switch (op) { 205 case B_MODULE_INIT: 206 return B_OK; 207 case B_MODULE_UNINIT: 208 return B_OK; 209 210 default: 211 return B_ERROR; 212 } 213 } 214 215 static dpc_module_info sDPCModule = { 216 { 217 B_DPC_MODULE_NAME, 218 0, 219 std_ops 220 }, 221 222 new_dpc_queue, 223 delete_dpc_queue, 224 queue_dpc 225 }; 226 227 228 module_info *modules[] = { 229 (module_info *) &sDPCModule, 230 NULL 231 }; 232 233