source: mainline/kernel/generic/src/ipc/ipc.c@ cebab351

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since cebab351 was 08a19ba, checked in by Jakub Jermar <jakub@…>, 18 years ago

Support for active mutexes. Active mutexes implement busy waiting, pretty much
in the same way as spinlocks, but can be passed to condition variables, which is
the motivation for this enhancement.

  • Property mode set to 100644
File size: 16.9 KB
Line 
1/*
2 * Copyright (c) 2006 Ondrej Palkovsky
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/** @addtogroup genericipc
30 * @{
31 */
32/** @file
33 */
34
35/* Lock ordering
36 *
37 * First the answerbox, then the phone.
38 */
39
40#include <synch/synch.h>
41#include <synch/spinlock.h>
42#include <synch/mutex.h>
43#include <synch/waitq.h>
44#include <synch/synch.h>
45#include <ipc/ipc.h>
46#include <errno.h>
47#include <mm/slab.h>
48#include <arch.h>
49#include <proc/task.h>
50#include <memstr.h>
51#include <debug.h>
52
53#include <print.h>
54#include <proc/thread.h>
55#include <arch/interrupt.h>
56#include <ipc/irq.h>
57
58/** Open channel that is assigned automatically to new tasks */
59answerbox_t *ipc_phone_0 = NULL;
60
61static slab_cache_t *ipc_call_slab;
62
63/** Initialize a call structure.
64 *
65 * @param call Call structure to be initialized.
66 */
67static void _ipc_call_init(call_t *call)
68{
69 memsetb(call, sizeof(*call), 0);
70 call->callerbox = &TASK->answerbox;
71 call->sender = TASK;
72 call->buffer = NULL;
73}
74
75/** Allocate and initialize a call structure.
76 *
77 * The call is initialized, so that the reply will be directed to
78 * TASK->answerbox.
79 *
80 * @param flags Parameters for slab_alloc (e.g FRAME_ATOMIC).
81 *
82 * @return If flags permit it, return NULL, or initialized kernel
83 * call structure.
84 */
85call_t *ipc_call_alloc(int flags)
86{
87 call_t *call;
88
89 call = slab_alloc(ipc_call_slab, flags);
90 if (call)
91 _ipc_call_init(call);
92
93 return call;
94}
95
96/** Initialize a statically allocated call structure.
97 *
98 * @param call Statically allocated kernel call structure to be
99 * initialized.
100 */
101void ipc_call_static_init(call_t *call)
102{
103 _ipc_call_init(call);
104 call->flags |= IPC_CALL_STATIC_ALLOC;
105}
106
107/** Deallocate a call structure.
108 *
109 * @param call Call structure to be freed.
110 */
111void ipc_call_free(call_t *call)
112{
113 ASSERT(!(call->flags & IPC_CALL_STATIC_ALLOC));
114 /* Check to see if we have data in the IPC_M_DATA_SEND buffer. */
115 if (call->buffer)
116 free(call->buffer);
117 slab_free(ipc_call_slab, call);
118}
119
120/** Initialize an answerbox structure.
121 *
122 * @param box Answerbox structure to be initialized.
123 * @param task Task to which the answerbox belongs.
124 */
125void ipc_answerbox_init(answerbox_t *box, task_t *task)
126{
127 spinlock_initialize(&box->lock, "ipc_box_lock");
128 spinlock_initialize(&box->irq_lock, "ipc_box_irqlock");
129 waitq_initialize(&box->wq);
130 list_initialize(&box->connected_phones);
131 list_initialize(&box->calls);
132 list_initialize(&box->dispatched_calls);
133 list_initialize(&box->answers);
134 list_initialize(&box->irq_notifs);
135 list_initialize(&box->irq_head);
136 box->task = task;
137}
138
139/** Connect a phone to an answerbox.
140 *
141 * @param phone Initialized phone structure.
142 * @param box Initialized answerbox structure.
143 */
144void ipc_phone_connect(phone_t *phone, answerbox_t *box)
145{
146 mutex_lock(&phone->lock);
147
148 phone->state = IPC_PHONE_CONNECTED;
149 phone->callee = box;
150
151 spinlock_lock(&box->lock);
152 list_append(&phone->link, &box->connected_phones);
153 spinlock_unlock(&box->lock);
154
155 mutex_unlock(&phone->lock);
156}
157
158/** Initialize a phone structure.
159 *
160 * @param phone Phone structure to be initialized.
161 */
162void ipc_phone_init(phone_t *phone)
163{
164 mutex_initialize(&phone->lock, MUTEX_PASSIVE);
165 phone->callee = NULL;
166 phone->state = IPC_PHONE_FREE;
167 atomic_set(&phone->active_calls, 0);
168}
169
170/** Helper function to facilitate synchronous calls.
171 *
172 * @param phone Destination kernel phone structure.
173 * @param request Call structure with request.
174 */
175void ipc_call_sync(phone_t *phone, call_t *request)
176{
177 answerbox_t sync_box;
178
179 ipc_answerbox_init(&sync_box, TASK);
180
181 /* We will receive data in a special box. */
182 request->callerbox = &sync_box;
183
184 ipc_call(phone, request);
185 ipc_wait_for_call(&sync_box, SYNCH_NO_TIMEOUT, SYNCH_FLAGS_NONE);
186}
187
188/** Answer a message which was not dispatched and is not listed in any queue.
189 *
190 * @param call Call structure to be answered.
191 */
192static void _ipc_answer_free_call(call_t *call)
193{
194 answerbox_t *callerbox = call->callerbox;
195
196 call->flags |= IPC_CALL_ANSWERED;
197
198 spinlock_lock(&callerbox->lock);
199 list_append(&call->link, &callerbox->answers);
200 spinlock_unlock(&callerbox->lock);
201 waitq_wakeup(&callerbox->wq, WAKEUP_FIRST);
202}
203
204/** Answer a message which is in a callee queue.
205 *
206 * @param box Answerbox that is answering the message.
207 * @param call Modified request that is being sent back.
208 */
209void ipc_answer(answerbox_t *box, call_t *call)
210{
211 /* Remove from active box */
212 spinlock_lock(&box->lock);
213 list_remove(&call->link);
214 spinlock_unlock(&box->lock);
215 /* Send back answer */
216 _ipc_answer_free_call(call);
217}
218
219/** Simulate sending back a message.
220 *
221 * Most errors are better handled by forming a normal backward
222 * message and sending it as a normal answer.
223 *
224 * @param phone Phone structure the call should appear to come from.
225 * @param call Call structure to be answered.
226 * @param err Return value to be used for the answer.
227 */
228void ipc_backsend_err(phone_t *phone, call_t *call, unative_t err)
229{
230 call->data.phone = phone;
231 atomic_inc(&phone->active_calls);
232 IPC_SET_RETVAL(call->data, err);
233 _ipc_answer_free_call(call);
234}
235
236/** Unsafe unchecking version of ipc_call.
237 *
238 * @param phone Phone structure the call comes from.
239 * @param box Destination answerbox structure.
240 * @param call Call structure with request.
241 */
242static void _ipc_call(phone_t *phone, answerbox_t *box, call_t *call)
243{
244 if (!(call->flags & IPC_CALL_FORWARDED)) {
245 atomic_inc(&phone->active_calls);
246 call->data.phone = phone;
247 }
248
249 spinlock_lock(&box->lock);
250 list_append(&call->link, &box->calls);
251 spinlock_unlock(&box->lock);
252 waitq_wakeup(&box->wq, WAKEUP_FIRST);
253}
254
255/** Send an asynchronous request using a phone to an answerbox.
256 *
257 * @param phone Phone structure the call comes from and which is
258 * connected to the destination answerbox.
259 * @param call Call structure with request.
260 *
261 * @return Return 0 on success, ENOENT on error.
262 */
263int ipc_call(phone_t *phone, call_t *call)
264{
265 answerbox_t *box;
266
267 mutex_lock(&phone->lock);
268 if (phone->state != IPC_PHONE_CONNECTED) {
269 mutex_unlock(&phone->lock);
270 if (call->flags & IPC_CALL_FORWARDED) {
271 IPC_SET_RETVAL(call->data, EFORWARD);
272 _ipc_answer_free_call(call);
273 } else {
274 if (phone->state == IPC_PHONE_HUNGUP)
275 ipc_backsend_err(phone, call, EHANGUP);
276 else
277 ipc_backsend_err(phone, call, ENOENT);
278 }
279 return ENOENT;
280 }
281 box = phone->callee;
282 _ipc_call(phone, box, call);
283
284 mutex_unlock(&phone->lock);
285 return 0;
286}
287
288/** Disconnect phone from answerbox.
289 *
290 * This call leaves the phone in the HUNGUP state. The change to 'free' is done
291 * lazily later.
292 *
293 * @param phone Phone structure to be hung up.
294 *
295 * @return Return 0 if the phone is disconnected.
296 * Return -1 if the phone was already disconnected.
297 */
298int ipc_phone_hangup(phone_t *phone)
299{
300 answerbox_t *box;
301 call_t *call;
302
303 mutex_lock(&phone->lock);
304 if (phone->state == IPC_PHONE_FREE ||
305 phone->state == IPC_PHONE_HUNGUP ||
306 phone->state == IPC_PHONE_CONNECTING) {
307 mutex_unlock(&phone->lock);
308 return -1;
309 }
310 box = phone->callee;
311 if (phone->state != IPC_PHONE_SLAMMED) {
312 /* Remove myself from answerbox */
313 spinlock_lock(&box->lock);
314 list_remove(&phone->link);
315 spinlock_unlock(&box->lock);
316
317 if (phone->state != IPC_PHONE_SLAMMED) {
318 call = ipc_call_alloc(0);
319 IPC_SET_METHOD(call->data, IPC_M_PHONE_HUNGUP);
320 call->flags |= IPC_CALL_DISCARD_ANSWER;
321 _ipc_call(phone, box, call);
322 }
323 }
324
325 phone->state = IPC_PHONE_HUNGUP;
326 mutex_unlock(&phone->lock);
327
328 return 0;
329}
330
331/** Forwards call from one answerbox to another one.
332 *
333 * @param call Call structure to be redirected.
334 * @param newphone Phone structure to target answerbox.
335 * @param oldbox Old answerbox structure.
336 * @param mode Flags that specify mode of the forward operation.
337 *
338 * @return Return 0 if forwarding succeeded or an error code if
339 * there was error.
340 *
341 * The return value serves only as an information for the forwarder,
342 * the original caller is notified automatically with EFORWARD.
343 */
344int ipc_forward(call_t *call, phone_t *newphone, answerbox_t *oldbox, int mode)
345{
346 spinlock_lock(&oldbox->lock);
347 list_remove(&call->link);
348 spinlock_unlock(&oldbox->lock);
349
350 if (mode & IPC_FF_ROUTE_FROM_ME)
351 call->data.phone = newphone;
352
353 return ipc_call(newphone, call);
354}
355
356
357/** Wait for a phone call.
358 *
359 * @param box Answerbox expecting the call.
360 * @param usec Timeout in microseconds. See documentation for
361 * waitq_sleep_timeout() for decription of its special
362 * meaning.
363 * @param flags Select mode of sleep operation. See documentation for
364 * waitq_sleep_timeout() for description of its special
365 * meaning.
366 * @return Recived call structure or NULL.
367 *
368 * To distinguish between a call and an answer, have a look at call->flags.
369 */
370call_t *ipc_wait_for_call(answerbox_t *box, uint32_t usec, int flags)
371{
372 call_t *request;
373 ipl_t ipl;
374 int rc;
375
376restart:
377 rc = waitq_sleep_timeout(&box->wq, usec, flags);
378 if (SYNCH_FAILED(rc))
379 return NULL;
380
381 spinlock_lock(&box->lock);
382 if (!list_empty(&box->irq_notifs)) {
383 ipl = interrupts_disable();
384 spinlock_lock(&box->irq_lock);
385
386 request = list_get_instance(box->irq_notifs.next, call_t, link);
387 list_remove(&request->link);
388
389 spinlock_unlock(&box->irq_lock);
390 interrupts_restore(ipl);
391 } else if (!list_empty(&box->answers)) {
392 /* Handle asynchronous answers */
393 request = list_get_instance(box->answers.next, call_t, link);
394 list_remove(&request->link);
395 atomic_dec(&request->data.phone->active_calls);
396 } else if (!list_empty(&box->calls)) {
397 /* Handle requests */
398 request = list_get_instance(box->calls.next, call_t, link);
399 list_remove(&request->link);
400 /* Append request to dispatch queue */
401 list_append(&request->link, &box->dispatched_calls);
402 } else {
403 /* This can happen regularly after ipc_cleanup */
404 spinlock_unlock(&box->lock);
405 goto restart;
406 }
407 spinlock_unlock(&box->lock);
408 return request;
409}
410
411/** Answer all calls from list with EHANGUP answer.
412 *
413 * @param lst Head of the list to be cleaned up.
414 */
415static void ipc_cleanup_call_list(link_t *lst)
416{
417 call_t *call;
418
419 while (!list_empty(lst)) {
420 call = list_get_instance(lst->next, call_t, link);
421 if (call->buffer)
422 free(call->buffer);
423 list_remove(&call->link);
424
425 IPC_SET_RETVAL(call->data, EHANGUP);
426 _ipc_answer_free_call(call);
427 }
428}
429
430/** Cleans up all IPC communication of the current task.
431 *
432 * Note: ipc_hangup sets returning answerbox to TASK->answerbox, you
433 * have to change it as well if you want to cleanup other tasks than TASK.
434 */
435void ipc_cleanup(void)
436{
437 int i;
438 call_t *call;
439 phone_t *phone;
440 DEADLOCK_PROBE_INIT(p_phonelck);
441
442 /* Disconnect all our phones ('ipc_phone_hangup') */
443 for (i = 0; i < IPC_MAX_PHONES; i++)
444 ipc_phone_hangup(&TASK->phones[i]);
445
446 /* Disconnect all connected irqs */
447 ipc_irq_cleanup(&TASK->answerbox);
448
449 /* Disconnect all phones connected to our answerbox */
450restart_phones:
451 spinlock_lock(&TASK->answerbox.lock);
452 while (!list_empty(&TASK->answerbox.connected_phones)) {
453 phone = list_get_instance(TASK->answerbox.connected_phones.next,
454 phone_t, link);
455 if (SYNCH_FAILED(mutex_trylock(&phone->lock))) {
456 spinlock_unlock(&TASK->answerbox.lock);
457 DEADLOCK_PROBE(p_phonelck, DEADLOCK_THRESHOLD);
458 goto restart_phones;
459 }
460
461 /* Disconnect phone */
462 ASSERT(phone->state == IPC_PHONE_CONNECTED);
463 phone->state = IPC_PHONE_SLAMMED;
464 list_remove(&phone->link);
465
466 mutex_unlock(&phone->lock);
467 }
468
469 /* Answer all messages in 'calls' and 'dispatched_calls' queues */
470 ipc_cleanup_call_list(&TASK->answerbox.dispatched_calls);
471 ipc_cleanup_call_list(&TASK->answerbox.calls);
472 spinlock_unlock(&TASK->answerbox.lock);
473
474 /* Wait for all async answers to arrive */
475 while (1) {
476 /* Go through all phones, until all are FREE... */
477 /* Locking not needed, no one else should modify
478 * it, when we are in cleanup */
479 for (i = 0; i < IPC_MAX_PHONES; i++) {
480 if (TASK->phones[i].state == IPC_PHONE_HUNGUP &&
481 atomic_get(&TASK->phones[i].active_calls) == 0)
482 TASK->phones[i].state = IPC_PHONE_FREE;
483
484 /* Just for sure, we might have had some
485 * IPC_PHONE_CONNECTING phones */
486 if (TASK->phones[i].state == IPC_PHONE_CONNECTED)
487 ipc_phone_hangup(&TASK->phones[i]);
488 /* If the hangup succeeded, it has sent a HANGUP
489 * message, the IPC is now in HUNGUP state, we
490 * wait for the reply to come */
491
492 if (TASK->phones[i].state != IPC_PHONE_FREE)
493 break;
494 }
495 /* Voila, got into cleanup */
496 if (i == IPC_MAX_PHONES)
497 break;
498
499 call = ipc_wait_for_call(&TASK->answerbox, SYNCH_NO_TIMEOUT,
500 SYNCH_FLAGS_NONE);
501 ASSERT((call->flags & IPC_CALL_ANSWERED) ||
502 (call->flags & IPC_CALL_NOTIF));
503 ASSERT(!(call->flags & IPC_CALL_STATIC_ALLOC));
504
505 atomic_dec(&TASK->active_calls);
506 ipc_call_free(call);
507 }
508}
509
510
511/** Initilize IPC subsystem */
512void ipc_init(void)
513{
514 ipc_call_slab = slab_cache_create("ipc_call", sizeof(call_t), 0, NULL,
515 NULL, 0);
516}
517
518
519/** List answerbox contents.
520 *
521 * @param taskid Task ID.
522 */
523void ipc_print_task(task_id_t taskid)
524{
525 task_t *task;
526 int i;
527 call_t *call;
528 link_t *tmp;
529
530 spinlock_lock(&tasks_lock);
531 task = task_find_by_id(taskid);
532 if (task)
533 spinlock_lock(&task->lock);
534 spinlock_unlock(&tasks_lock);
535 if (!task)
536 return;
537
538 /* Print opened phones & details */
539 printf("PHONE:\n");
540 for (i = 0; i < IPC_MAX_PHONES; i++) {
541 if (SYNCH_FAILED(mutex_trylock(&task->phones[i].lock))) {
542 printf("%d: mutex busy\n", i);
543 continue;
544 }
545 if (task->phones[i].state != IPC_PHONE_FREE) {
546 printf("%d: ", i);
547 switch (task->phones[i].state) {
548 case IPC_PHONE_CONNECTING:
549 printf("connecting ");
550 break;
551 case IPC_PHONE_CONNECTED:
552 printf("connected to: %p ",
553 task->phones[i].callee);
554 break;
555 case IPC_PHONE_SLAMMED:
556 printf("slammed by: %p ",
557 task->phones[i].callee);
558 break;
559 case IPC_PHONE_HUNGUP:
560 printf("hung up - was: %p ",
561 task->phones[i].callee);
562 break;
563 default:
564 break;
565 }
566 printf("active: %ld\n",
567 atomic_get(&task->phones[i].active_calls));
568 }
569 mutex_unlock(&task->phones[i].lock);
570 }
571
572
573 /* Print answerbox - calls */
574 spinlock_lock(&task->answerbox.lock);
575 printf("ABOX - CALLS:\n");
576 for (tmp = task->answerbox.calls.next; tmp != &task->answerbox.calls;
577 tmp = tmp->next) {
578 call = list_get_instance(tmp, call_t, link);
579 printf("Callid: %p Srctask:%" PRIu64 " M:%" PRIun
580 " A1:%" PRIun " A2:%" PRIun " A3:%" PRIun
581 " A4:%" PRIun " A5:%" PRIun " Flags:%x\n", call, call->sender->taskid,
582 IPC_GET_METHOD(call->data), IPC_GET_ARG1(call->data),
583 IPC_GET_ARG2(call->data), IPC_GET_ARG3(call->data),
584 IPC_GET_ARG4(call->data), IPC_GET_ARG5(call->data),
585 call->flags);
586 }
587 /* Print answerbox - calls */
588 printf("ABOX - DISPATCHED CALLS:\n");
589 for (tmp = task->answerbox.dispatched_calls.next;
590 tmp != &task->answerbox.dispatched_calls;
591 tmp = tmp->next) {
592 call = list_get_instance(tmp, call_t, link);
593 printf("Callid: %p Srctask:%" PRIu64 " M:%" PRIun
594 " A1:%" PRIun " A2:%" PRIun " A3:%" PRIun
595 " A4:%" PRIun " A5:%" PRIun " Flags:%x\n", call, call->sender->taskid,
596 IPC_GET_METHOD(call->data), IPC_GET_ARG1(call->data),
597 IPC_GET_ARG2(call->data), IPC_GET_ARG3(call->data),
598 IPC_GET_ARG4(call->data), IPC_GET_ARG5(call->data),
599 call->flags);
600 }
601 /* Print answerbox - calls */
602 printf("ABOX - ANSWERS:\n");
603 for (tmp = task->answerbox.answers.next; tmp != &task->answerbox.answers;
604 tmp = tmp->next) {
605 call = list_get_instance(tmp, call_t, link);
606 printf("Callid:%p M:%" PRIun " A1:%" PRIun " A2:%" PRIun
607 " A3:%" PRIun " A4:%" PRIun " A5:%" PRIun " Flags:%x\n",
608 call, IPC_GET_METHOD(call->data), IPC_GET_ARG1(call->data),
609 IPC_GET_ARG2(call->data), IPC_GET_ARG3(call->data),
610 IPC_GET_ARG4(call->data), IPC_GET_ARG5(call->data),
611 call->flags);
612 }
613
614 spinlock_unlock(&task->answerbox.lock);
615 spinlock_unlock(&task->lock);
616}
617
618/** @}
619 */
Note: See TracBrowser for help on using the repository browser.