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