xref: /haiku/src/add-ons/kernel/generic/dpc/dpc.c (revision b671e9bbdbd10268a042b4f4cc4317ccd03d105e)
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