source: mainline/uspace/lib/c/generic/async/server.c@ f5837524

Last change on this file since f5837524 was f5837524, checked in by Jakub Jermar <jakub@…>, 7 years ago

Use user-defined labels instead of phone hashes

This commit changes the way how the async framework maps incomming calls
to connections. Instead of abusing the kernel addresses of attached
phones as identifiers, the IPC_M_CONNECT_TO_ME and IPC_M_CONNECT_ME_TO
messages allow the server to specify an arbitrary label which is
remembered in the connected phone and consequently imprinted on each
call which is routed through this phone.

The async framework uses the address of the connection structure as the
label. This removes the need for a connection hash table because each
incoming call already remembers the connection in its label.

To disambiguate this new label and the other user-defined label used for
answers, the call structure now has the request_label member for the
former and answer_label member for the latter.

This commit also moves the kernel definition of ipc_data_t to abi/ and
removes the uspace redefinition thereof. Finally, when forwarding the
IPC_M_CONNECT_TO_ME call, the phone capability and the kernel object
allocated in request_process are now correctly disposed of.

  • Property mode set to 100644
File size: 38.5 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 libc
30 * @{
31 */
32/** @file
33 */
34
35/**
36 * Asynchronous library
37 *
38 * The aim of this library is to provide a facility for writing programs which
39 * utilize the asynchronous nature of HelenOS IPC, yet using a normal way of
40 * programming.
41 *
42 * You should be able to write very simple multithreaded programs. The async
43 * framework will automatically take care of most of the synchronization
44 * problems.
45 *
46 * Example of use (pseudo C):
47 *
48 * 1) Multithreaded client application
49 *
50 * fibril_create(fibril1, ...);
51 * fibril_create(fibril2, ...);
52 * ...
53 *
54 * int fibril1(void *arg)
55 * {
56 * conn = async_connect_me_to(...);
57 *
58 * exch = async_exchange_begin(conn);
59 * c1 = async_send(exch);
60 * async_exchange_end(exch);
61 *
62 * exch = async_exchange_begin(conn);
63 * c2 = async_send(exch);
64 * async_exchange_end(exch);
65 *
66 * async_wait_for(c1);
67 * async_wait_for(c2);
68 * ...
69 * }
70 *
71 *
72 * 2) Multithreaded server application
73 *
74 * main()
75 * {
76 * async_manager();
77 * }
78 *
79 * port_handler(ipc_call_t *icall)
80 * {
81 * if (want_refuse) {
82 * async_answer_0(icall, ELIMIT);
83 * return;
84 * }
85 *
86 * async_answer_0(icall, EOK);
87 *
88 * async_get_call(&call);
89 * somehow_handle_the_call(&call);
90 * async_answer_2(&call, 1, 2, 3);
91 *
92 * async_get_call(&call);
93 * ...
94 * }
95 *
96 */
97
98#define LIBC_ASYNC_C_
99#include <ipc/ipc.h>
100#include <async.h>
101#include "../private/async.h"
102#undef LIBC_ASYNC_C_
103
104#include <ipc/irq.h>
105#include <ipc/event.h>
106#include <fibril.h>
107#include <adt/hash_table.h>
108#include <adt/hash.h>
109#include <adt/list.h>
110#include <assert.h>
111#include <errno.h>
112#include <time.h>
113#include <stdbool.h>
114#include <stdlib.h>
115#include <mem.h>
116#include <stdlib.h>
117#include <macros.h>
118#include <str_error.h>
119#include <as.h>
120#include <abi/mm/as.h>
121#include "../private/libc.h"
122#include "../private/fibril.h"
123
124#define DPRINTF(...) ((void) 0)
125
126/* Client connection data */
127typedef struct {
128 ht_link_t link;
129
130 task_id_t in_task_id;
131 int refcnt;
132 void *data;
133} client_t;
134
135/* Server connection data */
136typedef struct {
137 /** Fibril handling the connection. */
138 fid_t fid;
139
140 /** Hash table link. */
141 ht_link_t link;
142
143 /** Incoming client task ID. */
144 task_id_t in_task_id;
145
146 /** Link to the client tracking structure. */
147 client_t *client;
148
149 /** Channel for messages that should be delivered to this fibril. */
150 mpsc_t *msg_channel;
151
152 /** Call data of the opening call. */
153 ipc_call_t call;
154
155 /** Fibril function that will be used to handle the connection. */
156 async_port_handler_t handler;
157
158 /** Client data */
159 void *data;
160} connection_t;
161
162/* Member of notification_t::msg_list. */
163typedef struct {
164 link_t link;
165 ipc_call_t calldata;
166} notification_msg_t;
167
168/* Notification data */
169typedef struct {
170 /** notification_hash_table link */
171 ht_link_t htlink;
172
173 /** notification_queue link */
174 link_t qlink;
175
176 /** Notification method */
177 sysarg_t imethod;
178
179 /** Notification handler */
180 async_notification_handler_t handler;
181
182 /** Notification handler argument */
183 void *arg;
184
185 /** List of arrived notifications. */
186 list_t msg_list;
187} notification_t;
188
189/** Identifier of the incoming connection handled by the current fibril. */
190static fibril_local connection_t *fibril_connection;
191
192static void *default_client_data_constructor(void)
193{
194 return NULL;
195}
196
197static void default_client_data_destructor(void *data)
198{
199}
200
201static async_client_data_ctor_t async_client_data_create =
202 default_client_data_constructor;
203static async_client_data_dtor_t async_client_data_destroy =
204 default_client_data_destructor;
205
206void async_set_client_data_constructor(async_client_data_ctor_t ctor)
207{
208 assert(async_client_data_create == default_client_data_constructor);
209 async_client_data_create = ctor;
210}
211
212void async_set_client_data_destructor(async_client_data_dtor_t dtor)
213{
214 assert(async_client_data_destroy == default_client_data_destructor);
215 async_client_data_destroy = dtor;
216}
217
218static FIBRIL_RMUTEX_INITIALIZE(client_mutex);
219static hash_table_t client_hash_table;
220
221// TODO: lockfree notification_queue?
222static FIBRIL_RMUTEX_INITIALIZE(notification_mutex);
223static hash_table_t notification_hash_table;
224static LIST_INITIALIZE(notification_queue);
225static FIBRIL_SEMAPHORE_INITIALIZE(notification_semaphore, 0);
226
227static LIST_INITIALIZE(notification_freelist);
228static long notification_freelist_total = 0;
229static long notification_freelist_used = 0;
230
231static sysarg_t notification_avail = 0;
232
233static size_t client_key_hash(void *key)
234{
235 task_id_t in_task_id = *(task_id_t *) key;
236 return in_task_id;
237}
238
239static size_t client_hash(const ht_link_t *item)
240{
241 client_t *client = hash_table_get_inst(item, client_t, link);
242 return client_key_hash(&client->in_task_id);
243}
244
245static bool client_key_equal(void *key, const ht_link_t *item)
246{
247 task_id_t in_task_id = *(task_id_t *) key;
248 client_t *client = hash_table_get_inst(item, client_t, link);
249 return in_task_id == client->in_task_id;
250}
251
252/** Operations for the client hash table. */
253static hash_table_ops_t client_hash_table_ops = {
254 .hash = client_hash,
255 .key_hash = client_key_hash,
256 .key_equal = client_key_equal,
257 .equal = NULL,
258 .remove_callback = NULL
259};
260
261static client_t *async_client_get(task_id_t client_id, bool create)
262{
263 client_t *client = NULL;
264
265 fibril_rmutex_lock(&client_mutex);
266 ht_link_t *link = hash_table_find(&client_hash_table, &client_id);
267 if (link) {
268 client = hash_table_get_inst(link, client_t, link);
269 client->refcnt++;
270 } else if (create) {
271 // TODO: move the malloc out of critical section
272 /* malloc() is rmutex safe. */
273 client = malloc(sizeof(client_t));
274 if (client) {
275 client->in_task_id = client_id;
276 client->data = async_client_data_create();
277
278 client->refcnt = 1;
279 hash_table_insert(&client_hash_table, &client->link);
280 }
281 }
282
283 fibril_rmutex_unlock(&client_mutex);
284 return client;
285}
286
287static void async_client_put(client_t *client)
288{
289 bool destroy;
290
291 fibril_rmutex_lock(&client_mutex);
292
293 if (--client->refcnt == 0) {
294 hash_table_remove(&client_hash_table, &client->in_task_id);
295 destroy = true;
296 } else
297 destroy = false;
298
299 fibril_rmutex_unlock(&client_mutex);
300
301 if (destroy) {
302 if (client->data)
303 async_client_data_destroy(client->data);
304
305 free(client);
306 }
307}
308
309/** Wrapper for client connection fibril.
310 *
311 * When a new connection arrives, a fibril with this implementing
312 * function is created.
313 *
314 * @param arg Connection structure pointer.
315 *
316 * @return Always zero.
317 *
318 */
319static errno_t connection_fibril(void *arg)
320{
321 assert(arg);
322
323 /*
324 * Setup fibril-local connection pointer.
325 */
326 fibril_connection = (connection_t *) arg;
327
328 mpsc_t *c = fibril_connection->msg_channel;
329
330 /*
331 * Add our reference for the current connection in the client task
332 * tracking structure. If this is the first reference, create and
333 * hash in a new tracking structure.
334 */
335
336 client_t *client = async_client_get(fibril_connection->in_task_id, true);
337 if (!client) {
338 ipc_answer_0(fibril_connection->call.cap_handle, ENOMEM);
339 goto out;
340 }
341
342 fibril_connection->client = client;
343
344 /*
345 * Call the connection handler function.
346 */
347 fibril_connection->handler(&fibril_connection->call,
348 fibril_connection->data);
349
350 /*
351 * Remove the reference for this client task connection.
352 */
353 async_client_put(client);
354
355 /*
356 * Close the channel, if it isn't closed already.
357 */
358 mpsc_close(c);
359
360 /*
361 * Answer all remaining messages with EHANGUP.
362 */
363 ipc_call_t call;
364 while (mpsc_receive(c, &call, NULL) == EOK)
365 ipc_answer_0(call.cap_handle, EHANGUP);
366
367 /*
368 * Clean up memory.
369 */
370out:
371 mpsc_destroy(c);
372 free(fibril_connection);
373 return EOK;
374}
375
376/** Return label usable during replies to IPC_M_CONNECT_ME_TO. */
377sysarg_t async_get_label(void)
378{
379 return (sysarg_t) fibril_connection;
380}
381
382/** Create a new fibril for a new connection.
383 *
384 * Create new fibril for connection, fill in connection structures and insert it
385 * into the hash table, so that later we can easily do routing of messages to
386 * particular fibrils.
387 *
388 * @param conn Pointer to the connection structure. Will be used as the
389 * label of the connected phone and request_label of incoming
390 * calls routed through that phone.
391 * @param in_task_id Identification of the incoming connection.
392 * @param call Call data of the opening call. If call is NULL, the
393 * connection was opened by accepting the
394 * IPC_M_CONNECT_TO_ME call and this function is called
395 * directly by the server.
396 * @param handler Connection handler.
397 * @param data Client argument to pass to the connection handler.
398 *
399 * @return New fibril id or NULL on failure.
400 *
401 */
402static fid_t async_new_connection(connection_t *conn, task_id_t in_task_id,
403 ipc_call_t *call, async_port_handler_t handler, void *data)
404{
405 conn->in_task_id = in_task_id;
406 conn->msg_channel = mpsc_create(sizeof(ipc_call_t));
407 conn->handler = handler;
408 conn->data = data;
409
410 if (!conn->msg_channel)
411 goto error;
412
413 if (call)
414 conn->call = *call;
415 else
416 conn->call.cap_handle = CAP_NIL;
417
418 /* We will activate the fibril ASAP */
419 conn->fid = fibril_create(connection_fibril, conn);
420
421 if (conn->fid == 0)
422 goto error;
423
424 fibril_start(conn->fid);
425
426 return conn->fid;
427
428error:
429 if (conn->msg_channel)
430 mpsc_destroy(conn->msg_channel);
431 free(conn);
432
433 if (call)
434 ipc_answer_0(call->cap_handle, ENOMEM);
435
436 return (fid_t) NULL;
437}
438
439/** Wrapper for making IPC_M_CONNECT_TO_ME calls using the async framework.
440 *
441 * Ask through phone for a new connection to some service.
442 *
443 * @param exch Exchange for sending the message.
444 * @param iface Callback interface.
445 * @param arg1 User defined argument.
446 * @param arg2 User defined argument.
447 * @param handler Callback handler.
448 * @param data Handler data.
449 * @param port_id ID of the newly created port.
450 *
451 * @return Zero on success or an error code.
452 *
453 */
454errno_t async_create_callback_port(async_exch_t *exch, iface_t iface, sysarg_t arg1,
455 sysarg_t arg2, async_port_handler_t handler, void *data, port_id_t *port_id)
456{
457 if ((iface & IFACE_MOD_CALLBACK) != IFACE_MOD_CALLBACK)
458 return EINVAL;
459
460 if (exch == NULL)
461 return ENOENT;
462
463 connection_t *conn = calloc(1, sizeof(*conn));
464 if (!conn)
465 return ENOMEM;
466
467 ipc_call_t answer;
468 aid_t req = async_send_5(exch, IPC_M_CONNECT_TO_ME, iface, arg1, arg2,
469 0, (sysarg_t) conn, &answer);
470
471 errno_t rc;
472 async_wait_for(req, &rc);
473 if (rc != EOK) {
474 free(conn);
475 return rc;
476 }
477
478 rc = async_create_port_internal(iface, handler, data, port_id);
479 if (rc != EOK) {
480 free(conn);
481 return rc;
482 }
483
484 fid_t fid = async_new_connection(conn, answer.task_id, NULL, handler,
485 data);
486 if (fid == (fid_t) NULL)
487 return ENOMEM;
488
489 return EOK;
490}
491
492static size_t notification_key_hash(void *key)
493{
494 sysarg_t id = *(sysarg_t *) key;
495 return id;
496}
497
498static size_t notification_hash(const ht_link_t *item)
499{
500 notification_t *notification =
501 hash_table_get_inst(item, notification_t, htlink);
502 return notification_key_hash(&notification->imethod);
503}
504
505static bool notification_key_equal(void *key, const ht_link_t *item)
506{
507 sysarg_t id = *(sysarg_t *) key;
508 notification_t *notification =
509 hash_table_get_inst(item, notification_t, htlink);
510 return id == notification->imethod;
511}
512
513/** Operations for the notification hash table. */
514static hash_table_ops_t notification_hash_table_ops = {
515 .hash = notification_hash,
516 .key_hash = notification_key_hash,
517 .key_equal = notification_key_equal,
518 .equal = NULL,
519 .remove_callback = NULL
520};
521
522/** Try to route a call to an appropriate connection fibril.
523 *
524 * If the proper connection fibril is found, a message with the call is added to
525 * its message queue. If the fibril was not active, it is activated and all
526 * timeouts are unregistered.
527 *
528 * @param call Data of the incoming call.
529 *
530 * @return EOK if the call was successfully passed to the respective fibril.
531 * @return ENOENT if the call doesn't match any connection.
532 * @return Other error code if routing failed for other reasons.
533 *
534 */
535static errno_t route_call(ipc_call_t *call)
536{
537 assert(call);
538
539 connection_t *conn = (connection_t *) call->request_label;
540
541 if (!conn)
542 return ENOENT;
543
544 assert(conn->msg_channel);
545
546 errno_t rc = mpsc_send(conn->msg_channel, call);
547
548 if (IPC_GET_IMETHOD(*call) == IPC_M_PHONE_HUNGUP) {
549 /* Close the channel, but let the connection fibril answer. */
550 mpsc_close(conn->msg_channel);
551 // FIXME: Ideally, we should be able to discard/answer the
552 // hungup message here and just close the channel without
553 // passing it out. Unfortunatelly, somehow that breaks
554 // handling of CPU exceptions.
555 }
556
557 return rc;
558}
559
560/** Function implementing the notification handler fibril. Never returns. */
561static errno_t notification_fibril_func(void *arg)
562{
563 (void) arg;
564
565 while (true) {
566 fibril_semaphore_down(&notification_semaphore);
567
568 fibril_rmutex_lock(&notification_mutex);
569
570 /*
571 * The semaphore ensures that if we get this far,
572 * the queue must be non-empty.
573 */
574 assert(!list_empty(&notification_queue));
575
576 notification_t *notification = list_get_instance(
577 list_first(&notification_queue), notification_t, qlink);
578
579 async_notification_handler_t handler = notification->handler;
580 void *arg = notification->arg;
581
582 notification_msg_t *m = list_pop(&notification->msg_list,
583 notification_msg_t, link);
584 assert(m);
585 ipc_call_t calldata = m->calldata;
586
587 notification_freelist_used--;
588
589 if (notification_freelist_total > 64 &&
590 notification_freelist_total > 2 * notification_freelist_used) {
591 /* Going to free the structure if we have too much. */
592 notification_freelist_total--;
593 } else {
594 /* Otherwise add to freelist. */
595 list_append(&m->link, &notification_freelist);
596 m = NULL;
597 }
598
599 if (list_empty(&notification->msg_list))
600 list_remove(&notification->qlink);
601
602 fibril_rmutex_unlock(&notification_mutex);
603
604 if (handler)
605 handler(&calldata, arg);
606
607 free(m);
608 }
609
610 /* Not reached. */
611 return EOK;
612}
613
614/**
615 * Creates a new dedicated fibril for handling notifications.
616 * By default, there is one such fibril. This function can be used to
617 * create more in order to increase the number of notification that can
618 * be processed concurrently.
619 *
620 * Currently, there is no way to destroy those fibrils after they are created.
621 */
622errno_t async_spawn_notification_handler(void)
623{
624 fid_t f = fibril_create(notification_fibril_func, NULL);
625 if (f == 0)
626 return ENOMEM;
627
628 fibril_add_ready(f);
629 return EOK;
630}
631
632/** Queue notification.
633 *
634 * @param call Data of the incoming call.
635 *
636 */
637static void queue_notification(ipc_call_t *call)
638{
639 assert(call);
640
641 fibril_rmutex_lock(&notification_mutex);
642
643 notification_msg_t *m = list_pop(&notification_freelist,
644 notification_msg_t, link);
645
646 if (!m) {
647 fibril_rmutex_unlock(&notification_mutex);
648 m = malloc(sizeof(notification_msg_t));
649 if (!m) {
650 DPRINTF("Out of memory.\n");
651 abort();
652 }
653
654 fibril_rmutex_lock(&notification_mutex);
655 notification_freelist_total++;
656 }
657
658 ht_link_t *link = hash_table_find(&notification_hash_table,
659 &IPC_GET_IMETHOD(*call));
660 if (!link) {
661 /* Invalid notification. */
662 // TODO: Make sure this can't happen and turn it into assert.
663 notification_freelist_total--;
664 fibril_rmutex_unlock(&notification_mutex);
665 free(m);
666 return;
667 }
668
669 notification_t *notification =
670 hash_table_get_inst(link, notification_t, htlink);
671
672 notification_freelist_used++;
673 m->calldata = *call;
674 list_append(&m->link, &notification->msg_list);
675
676 if (!link_in_use(&notification->qlink))
677 list_append(&notification->qlink, &notification_queue);
678
679 fibril_rmutex_unlock(&notification_mutex);
680
681 fibril_semaphore_up(&notification_semaphore);
682}
683
684/**
685 * Creates a new notification structure and inserts it into the hash table.
686 *
687 * @param handler Function to call when notification is received.
688 * @param arg Argument for the handler function.
689 * @return The newly created notification structure.
690 */
691static notification_t *notification_create(async_notification_handler_t handler, void *arg)
692{
693 notification_t *notification = calloc(1, sizeof(notification_t));
694 if (!notification)
695 return NULL;
696
697 notification->handler = handler;
698 notification->arg = arg;
699
700 list_initialize(&notification->msg_list);
701
702 fid_t fib = 0;
703
704 fibril_rmutex_lock(&notification_mutex);
705
706 if (notification_avail == 0) {
707 /* Attempt to create the first handler fibril. */
708 fib = fibril_create(notification_fibril_func, NULL);
709 if (fib == 0) {
710 fibril_rmutex_unlock(&notification_mutex);
711 free(notification);
712 return NULL;
713 }
714 }
715
716 sysarg_t imethod = notification_avail;
717 notification_avail++;
718
719 notification->imethod = imethod;
720 hash_table_insert(&notification_hash_table, &notification->htlink);
721
722 fibril_rmutex_unlock(&notification_mutex);
723
724 if (imethod == 0) {
725 assert(fib);
726 fibril_add_ready(fib);
727 }
728
729 return notification;
730}
731
732/** Subscribe to IRQ notification.
733 *
734 * @param inr IRQ number.
735 * @param handler Notification handler.
736 * @param data Notification handler client data.
737 * @param ucode Top-half pseudocode handler.
738 *
739 * @param[out] handle IRQ capability handle on success.
740 *
741 * @return An error code.
742 *
743 */
744errno_t async_irq_subscribe(int inr, async_notification_handler_t handler,
745 void *data, const irq_code_t *ucode, cap_irq_handle_t *handle)
746{
747 notification_t *notification = notification_create(handler, data);
748 if (!notification)
749 return ENOMEM;
750
751 cap_irq_handle_t ihandle;
752 errno_t rc = ipc_irq_subscribe(inr, notification->imethod, ucode,
753 &ihandle);
754 if (rc == EOK && handle != NULL) {
755 *handle = ihandle;
756 }
757 return rc;
758}
759
760/** Unsubscribe from IRQ notification.
761 *
762 * @param handle IRQ capability handle.
763 *
764 * @return Zero on success or an error code.
765 *
766 */
767errno_t async_irq_unsubscribe(cap_irq_handle_t ihandle)
768{
769 // TODO: Remove entry from hash table
770 // to avoid memory leak
771
772 return ipc_irq_unsubscribe(ihandle);
773}
774
775/** Subscribe to event notifications.
776 *
777 * @param evno Event type to subscribe.
778 * @param handler Notification handler.
779 * @param data Notification handler client data.
780 *
781 * @return Zero on success or an error code.
782 *
783 */
784errno_t async_event_subscribe(event_type_t evno,
785 async_notification_handler_t handler, void *data)
786{
787 notification_t *notification = notification_create(handler, data);
788 if (!notification)
789 return ENOMEM;
790
791 return ipc_event_subscribe(evno, notification->imethod);
792}
793
794/** Subscribe to task event notifications.
795 *
796 * @param evno Event type to subscribe.
797 * @param handler Notification handler.
798 * @param data Notification handler client data.
799 *
800 * @return Zero on success or an error code.
801 *
802 */
803errno_t async_event_task_subscribe(event_task_type_t evno,
804 async_notification_handler_t handler, void *data)
805{
806 notification_t *notification = notification_create(handler, data);
807 if (!notification)
808 return ENOMEM;
809
810 return ipc_event_task_subscribe(evno, notification->imethod);
811}
812
813/** Unmask event notifications.
814 *
815 * @param evno Event type to unmask.
816 *
817 * @return Value returned by the kernel.
818 *
819 */
820errno_t async_event_unmask(event_type_t evno)
821{
822 return ipc_event_unmask(evno);
823}
824
825/** Unmask task event notifications.
826 *
827 * @param evno Event type to unmask.
828 *
829 * @return Value returned by the kernel.
830 *
831 */
832errno_t async_event_task_unmask(event_task_type_t evno)
833{
834 return ipc_event_task_unmask(evno);
835}
836
837/** Return new incoming message for the current (fibril-local) connection.
838 *
839 * @param call Storage where the incoming call data will be stored.
840 * @param usecs Timeout in microseconds. Zero denotes no timeout.
841 *
842 * @return If no timeout was specified, then true is returned.
843 * @return If a timeout is specified, then true is returned unless
844 * the timeout expires prior to receiving a message.
845 *
846 */
847bool async_get_call_timeout(ipc_call_t *call, usec_t usecs)
848{
849 assert(call);
850 assert(fibril_connection);
851
852 struct timespec ts;
853 struct timespec *expires = NULL;
854 if (usecs) {
855 getuptime(&ts);
856 ts_add_diff(&ts, USEC2NSEC(usecs));
857 expires = &ts;
858 }
859
860 errno_t rc = mpsc_receive(fibril_connection->msg_channel,
861 call, expires);
862
863 if (rc == ETIMEOUT)
864 return false;
865
866 if (rc != EOK) {
867 /*
868 * The async_get_call_timeout() interface doesn't support
869 * propagating errors. Return a null call instead.
870 */
871
872 memset(call, 0, sizeof(ipc_call_t));
873 }
874
875 return true;
876}
877
878void *async_get_client_data(void)
879{
880 assert(fibril_connection);
881 return fibril_connection->client->data;
882}
883
884void *async_get_client_data_by_id(task_id_t client_id)
885{
886 client_t *client = async_client_get(client_id, false);
887 if (!client)
888 return NULL;
889
890 if (!client->data) {
891 async_client_put(client);
892 return NULL;
893 }
894
895 return client->data;
896}
897
898void async_put_client_data_by_id(task_id_t client_id)
899{
900 client_t *client = async_client_get(client_id, false);
901
902 assert(client);
903 assert(client->data);
904
905 /* Drop the reference we got in async_get_client_data_by_hash(). */
906 async_client_put(client);
907
908 /* Drop our own reference we got at the beginning of this function. */
909 async_client_put(client);
910}
911
912/** Handle a call that was received.
913 *
914 * If the call has the IPC_M_CONNECT_ME_TO method, a new connection is created.
915 * Otherwise the call is routed to its connection fibril.
916 *
917 * @param call Data of the incoming call.
918 *
919 */
920static void handle_call(ipc_call_t *call)
921{
922 assert(call);
923
924 if (call->flags & IPC_CALL_ANSWERED) {
925 /* Answer to a call made by us. */
926 async_reply_received(call);
927 return;
928 }
929
930 if (call->cap_handle == CAP_NIL) {
931 if (call->flags & IPC_CALL_NOTIF) {
932 /* Kernel notification */
933 queue_notification(call);
934 }
935 return;
936 }
937
938 /* New connection */
939 if (IPC_GET_IMETHOD(*call) == IPC_M_CONNECT_ME_TO) {
940 connection_t *conn = calloc(1, sizeof(*conn));
941 if (!conn) {
942 ipc_answer_0(call->cap_handle, ENOMEM);
943 return;
944 }
945
946 iface_t iface = (iface_t) IPC_GET_ARG1(*call);
947
948 // TODO: Currently ignores all ports but the first one.
949 void *data;
950 async_port_handler_t handler =
951 async_get_port_handler(iface, 0, &data);
952
953 async_new_connection(conn, call->task_id, call, handler, data);
954 return;
955 }
956
957 /* Route the call according to its request label */
958 errno_t rc = route_call(call);
959 if (rc == EOK)
960 return;
961
962 // TODO: Log the error.
963
964 if (call->cap_handle != CAP_NIL)
965 /* Unknown call from unknown phone - hang it up */
966 ipc_answer_0(call->cap_handle, EHANGUP);
967}
968
969/** Endless loop dispatching incoming calls and answers.
970 *
971 * @return Never returns.
972 *
973 */
974static errno_t async_manager_worker(void)
975{
976 ipc_call_t call;
977 errno_t rc;
978
979 while (true) {
980 rc = fibril_ipc_wait(&call, NULL);
981 if (rc == EOK)
982 handle_call(&call);
983 }
984
985 return 0;
986}
987
988/** Function to start async_manager as a standalone fibril.
989 *
990 * When more kernel threads are used, one async manager should exist per thread.
991 *
992 * @param arg Unused.
993 * @return Never returns.
994 *
995 */
996static errno_t async_manager_fibril(void *arg)
997{
998 async_manager_worker();
999 return 0;
1000}
1001
1002/** Add one manager to manager list. */
1003fid_t async_create_manager(void)
1004{
1005 fid_t fid = fibril_create_generic(async_manager_fibril, NULL, PAGE_SIZE);
1006 fibril_start(fid);
1007 return fid;
1008}
1009
1010/** Initialize the async framework.
1011 *
1012 */
1013void __async_server_init(void)
1014{
1015 if (!hash_table_create(&client_hash_table, 0, 0, &client_hash_table_ops))
1016 abort();
1017
1018 if (!hash_table_create(&notification_hash_table, 0, 0,
1019 &notification_hash_table_ops))
1020 abort();
1021
1022 async_create_manager();
1023}
1024
1025errno_t async_answer_0(ipc_call_t *call, errno_t retval)
1026{
1027 return ipc_answer_0(call->cap_handle, retval);
1028}
1029
1030errno_t async_answer_1(ipc_call_t *call, errno_t retval, sysarg_t arg1)
1031{
1032 return ipc_answer_1(call->cap_handle, retval, arg1);
1033}
1034
1035errno_t async_answer_2(ipc_call_t *call, errno_t retval, sysarg_t arg1,
1036 sysarg_t arg2)
1037{
1038 return ipc_answer_2(call->cap_handle, retval, arg1, arg2);
1039}
1040
1041errno_t async_answer_3(ipc_call_t *call, errno_t retval, sysarg_t arg1,
1042 sysarg_t arg2, sysarg_t arg3)
1043{
1044 return ipc_answer_3(call->cap_handle, retval, arg1, arg2, arg3);
1045}
1046
1047errno_t async_answer_4(ipc_call_t *call, errno_t retval, sysarg_t arg1,
1048 sysarg_t arg2, sysarg_t arg3, sysarg_t arg4)
1049{
1050 return ipc_answer_4(call->cap_handle, retval, arg1, arg2, arg3, arg4);
1051}
1052
1053errno_t async_answer_5(ipc_call_t *call, errno_t retval, sysarg_t arg1,
1054 sysarg_t arg2, sysarg_t arg3, sysarg_t arg4, sysarg_t arg5)
1055{
1056 return ipc_answer_5(call->cap_handle, retval, arg1, arg2, arg3, arg4,
1057 arg5);
1058}
1059
1060errno_t async_forward_fast(ipc_call_t *call, async_exch_t *exch,
1061 sysarg_t imethod, sysarg_t arg1, sysarg_t arg2, unsigned int mode)
1062{
1063 assert(call);
1064
1065 if (exch == NULL)
1066 return ENOENT;
1067
1068 return ipc_forward_fast(call->cap_handle, exch->phone, imethod, arg1,
1069 arg2, mode);
1070}
1071
1072errno_t async_forward_slow(ipc_call_t *call, async_exch_t *exch,
1073 sysarg_t imethod, sysarg_t arg1, sysarg_t arg2, sysarg_t arg3,
1074 sysarg_t arg4, sysarg_t arg5, unsigned int mode)
1075{
1076 assert(call);
1077
1078 if (exch == NULL)
1079 return ENOENT;
1080
1081 return ipc_forward_slow(call->cap_handle, exch->phone, imethod, arg1,
1082 arg2, arg3, arg4, arg5, mode);
1083}
1084
1085/** Wrapper for making IPC_M_CONNECT_TO_ME calls using the async framework.
1086 *
1087 * Ask through phone for a new connection to some service.
1088 *
1089 * @param exch Exchange for sending the message.
1090 * @param iface Callback interface.
1091 * @param arg2 User defined argument.
1092 * @param arg3 User defined argument.
1093 *
1094 * @return Zero on success or an error code.
1095 *
1096 */
1097errno_t async_connect_to_me(async_exch_t *exch, iface_t iface, sysarg_t arg2,
1098 sysarg_t arg3)
1099{
1100 if (exch == NULL)
1101 return ENOENT;
1102
1103 sysarg_t label = 0;
1104 errno_t rc = async_req_5_0(exch, IPC_M_CONNECT_TO_ME, iface, arg2, arg3,
1105 0, label);
1106
1107 return rc;
1108}
1109
1110/** Wrapper for receiving the IPC_M_SHARE_IN calls using the async framework.
1111 *
1112 * This wrapper only makes it more comfortable to receive IPC_M_SHARE_IN
1113 * calls so that the user doesn't have to remember the meaning of each IPC
1114 * argument.
1115 *
1116 * So far, this wrapper is to be used from within a connection fibril.
1117 *
1118 * @param call Storage for the data of the IPC_M_SHARE_IN call.
1119 * @param size Destination address space area size.
1120 *
1121 * @return True on success, false on failure.
1122 *
1123 */
1124bool async_share_in_receive(ipc_call_t *call, size_t *size)
1125{
1126 assert(call);
1127 assert(size);
1128
1129 async_get_call(call);
1130
1131 if (IPC_GET_IMETHOD(*call) != IPC_M_SHARE_IN)
1132 return false;
1133
1134 *size = (size_t) IPC_GET_ARG1(*call);
1135 return true;
1136}
1137
1138/** Wrapper for answering the IPC_M_SHARE_IN calls using the async framework.
1139 *
1140 * This wrapper only makes it more comfortable to answer IPC_M_SHARE_IN
1141 * calls so that the user doesn't have to remember the meaning of each IPC
1142 * argument.
1143 *
1144 * @param call IPC_M_SHARE_IN call to answer.
1145 * @param src Source address space base.
1146 * @param flags Flags to be used for sharing. Bits can be only cleared.
1147 *
1148 * @return Zero on success or a value from @ref errno.h on failure.
1149 *
1150 */
1151errno_t async_share_in_finalize(ipc_call_t *call, void *src, unsigned int flags)
1152{
1153 assert(call);
1154
1155 return ipc_answer_2(call->cap_handle, EOK, (sysarg_t) src, (sysarg_t) flags);
1156}
1157
1158/** Wrapper for receiving the IPC_M_SHARE_OUT calls using the async framework.
1159 *
1160 * This wrapper only makes it more comfortable to receive IPC_M_SHARE_OUT
1161 * calls so that the user doesn't have to remember the meaning of each IPC
1162 * argument.
1163 *
1164 * So far, this wrapper is to be used from within a connection fibril.
1165 *
1166 * @param call Storage for the data of the IPC_M_SHARE_OUT call.
1167 * @param size Storage for the source address space area size.
1168 * @param flags Storage for the sharing flags.
1169 *
1170 * @return True on success, false on failure.
1171 *
1172 */
1173bool async_share_out_receive(ipc_call_t *call, size_t *size,
1174 unsigned int *flags)
1175{
1176 assert(call);
1177 assert(size);
1178 assert(flags);
1179
1180 async_get_call(call);
1181
1182 if (IPC_GET_IMETHOD(*call) != IPC_M_SHARE_OUT)
1183 return false;
1184
1185 *size = (size_t) IPC_GET_ARG2(*call);
1186 *flags = (unsigned int) IPC_GET_ARG3(*call);
1187 return true;
1188}
1189
1190/** Wrapper for answering the IPC_M_SHARE_OUT calls using the async framework.
1191 *
1192 * This wrapper only makes it more comfortable to answer IPC_M_SHARE_OUT
1193 * calls so that the user doesn't have to remember the meaning of each IPC
1194 * argument.
1195 *
1196 * @param call IPC_M_SHARE_OUT call to answer.
1197 * @param dst Address of the storage for the destination address space area
1198 * base address.
1199 *
1200 * @return Zero on success or a value from @ref errno.h on failure.
1201 *
1202 */
1203errno_t async_share_out_finalize(ipc_call_t *call, void **dst)
1204{
1205 assert(call);
1206
1207 return ipc_answer_2(call->cap_handle, EOK, (sysarg_t) __progsymbols.end,
1208 (sysarg_t) dst);
1209}
1210
1211/** Wrapper for receiving the IPC_M_DATA_READ calls using the async framework.
1212 *
1213 * This wrapper only makes it more comfortable to receive IPC_M_DATA_READ
1214 * calls so that the user doesn't have to remember the meaning of each IPC
1215 * argument.
1216 *
1217 * So far, this wrapper is to be used from within a connection fibril.
1218 *
1219 * @param call Storage for the data of the IPC_M_DATA_READ.
1220 * @param size Storage for the maximum size. Can be NULL.
1221 *
1222 * @return True on success, false on failure.
1223 *
1224 */
1225bool async_data_read_receive(ipc_call_t *call, size_t *size)
1226{
1227 assert(call);
1228
1229 async_get_call(call);
1230
1231 if (IPC_GET_IMETHOD(*call) != IPC_M_DATA_READ)
1232 return false;
1233
1234 if (size)
1235 *size = (size_t) IPC_GET_ARG2(*call);
1236
1237 return true;
1238}
1239
1240/** Wrapper for answering the IPC_M_DATA_READ calls using the async framework.
1241 *
1242 * This wrapper only makes it more comfortable to answer IPC_M_DATA_READ
1243 * calls so that the user doesn't have to remember the meaning of each IPC
1244 * argument.
1245 *
1246 * @param call IPC_M_DATA_READ call to answer.
1247 * @param src Source address for the IPC_M_DATA_READ call.
1248 * @param size Size for the IPC_M_DATA_READ call. Can be smaller than
1249 * the maximum size announced by the sender.
1250 *
1251 * @return Zero on success or a value from @ref errno.h on failure.
1252 *
1253 */
1254errno_t async_data_read_finalize(ipc_call_t *call, const void *src, size_t size)
1255{
1256 assert(call);
1257
1258 return ipc_answer_2(call->cap_handle, EOK, (sysarg_t) src,
1259 (sysarg_t) size);
1260}
1261
1262/** Wrapper for forwarding any read request
1263 *
1264 */
1265errno_t async_data_read_forward_fast(async_exch_t *exch, sysarg_t imethod,
1266 sysarg_t arg1, sysarg_t arg2, sysarg_t arg3, sysarg_t arg4,
1267 ipc_call_t *dataptr)
1268{
1269 if (exch == NULL)
1270 return ENOENT;
1271
1272 ipc_call_t call;
1273 if (!async_data_read_receive(&call, NULL)) {
1274 async_answer_0(&call, EINVAL);
1275 return EINVAL;
1276 }
1277
1278 aid_t msg = async_send_fast(exch, imethod, arg1, arg2, arg3, arg4,
1279 dataptr);
1280 if (msg == 0) {
1281 async_answer_0(&call, EINVAL);
1282 return EINVAL;
1283 }
1284
1285 errno_t retval = ipc_forward_fast(call.cap_handle, exch->phone, 0, 0, 0,
1286 IPC_FF_ROUTE_FROM_ME);
1287 if (retval != EOK) {
1288 async_forget(msg);
1289 async_answer_0(&call, retval);
1290 return retval;
1291 }
1292
1293 errno_t rc;
1294 async_wait_for(msg, &rc);
1295
1296 return (errno_t) rc;
1297}
1298
1299/** Wrapper for receiving the IPC_M_DATA_WRITE calls using the async framework.
1300 *
1301 * This wrapper only makes it more comfortable to receive IPC_M_DATA_WRITE
1302 * calls so that the user doesn't have to remember the meaning of each IPC
1303 * argument.
1304 *
1305 * So far, this wrapper is to be used from within a connection fibril.
1306 *
1307 * @param call Storage for the data of the IPC_M_DATA_WRITE.
1308 * @param size Storage for the suggested size. May be NULL.
1309 *
1310 * @return True on success, false on failure.
1311 *
1312 */
1313bool async_data_write_receive(ipc_call_t *call, size_t *size)
1314{
1315 assert(call);
1316
1317 async_get_call(call);
1318
1319 if (IPC_GET_IMETHOD(*call) != IPC_M_DATA_WRITE)
1320 return false;
1321
1322 if (size)
1323 *size = (size_t) IPC_GET_ARG2(*call);
1324
1325 return true;
1326}
1327
1328/** Wrapper for answering the IPC_M_DATA_WRITE calls using the async framework.
1329 *
1330 * This wrapper only makes it more comfortable to answer IPC_M_DATA_WRITE
1331 * calls so that the user doesn't have to remember the meaning of each IPC
1332 * argument.
1333 *
1334 * @param call IPC_M_DATA_WRITE call to answer.
1335 * @param dst Final destination address for the IPC_M_DATA_WRITE call.
1336 * @param size Final size for the IPC_M_DATA_WRITE call.
1337 *
1338 * @return Zero on success or a value from @ref errno.h on failure.
1339 *
1340 */
1341errno_t async_data_write_finalize(ipc_call_t *call, void *dst, size_t size)
1342{
1343 assert(call);
1344
1345 return async_answer_2(call, EOK, (sysarg_t) dst, (sysarg_t) size);
1346}
1347
1348/** Wrapper for receiving binary data or strings
1349 *
1350 * This wrapper only makes it more comfortable to use async_data_write_*
1351 * functions to receive binary data or strings.
1352 *
1353 * @param data Pointer to data pointer (which should be later disposed
1354 * by free()). If the operation fails, the pointer is not
1355 * touched.
1356 * @param nullterm If true then the received data is always zero terminated.
1357 * This also causes to allocate one extra byte beyond the
1358 * raw transmitted data.
1359 * @param min_size Minimum size (in bytes) of the data to receive.
1360 * @param max_size Maximum size (in bytes) of the data to receive. 0 means
1361 * no limit.
1362 * @param granulariy If non-zero then the size of the received data has to
1363 * be divisible by this value.
1364 * @param received If not NULL, the size of the received data is stored here.
1365 *
1366 * @return Zero on success or a value from @ref errno.h on failure.
1367 *
1368 */
1369errno_t async_data_write_accept(void **data, const bool nullterm,
1370 const size_t min_size, const size_t max_size, const size_t granularity,
1371 size_t *received)
1372{
1373 assert(data);
1374
1375 ipc_call_t call;
1376 size_t size;
1377 if (!async_data_write_receive(&call, &size)) {
1378 async_answer_0(&call, EINVAL);
1379 return EINVAL;
1380 }
1381
1382 if (size < min_size) {
1383 async_answer_0(&call, EINVAL);
1384 return EINVAL;
1385 }
1386
1387 if ((max_size > 0) && (size > max_size)) {
1388 async_answer_0(&call, EINVAL);
1389 return EINVAL;
1390 }
1391
1392 if ((granularity > 0) && ((size % granularity) != 0)) {
1393 async_answer_0(&call, EINVAL);
1394 return EINVAL;
1395 }
1396
1397 void *arg_data;
1398
1399 if (nullterm)
1400 arg_data = malloc(size + 1);
1401 else
1402 arg_data = malloc(size);
1403
1404 if (arg_data == NULL) {
1405 async_answer_0(&call, ENOMEM);
1406 return ENOMEM;
1407 }
1408
1409 errno_t rc = async_data_write_finalize(&call, arg_data, size);
1410 if (rc != EOK) {
1411 free(arg_data);
1412 return rc;
1413 }
1414
1415 if (nullterm)
1416 ((char *) arg_data)[size] = 0;
1417
1418 *data = arg_data;
1419 if (received != NULL)
1420 *received = size;
1421
1422 return EOK;
1423}
1424
1425/** Wrapper for voiding any data that is about to be received
1426 *
1427 * This wrapper can be used to void any pending data
1428 *
1429 * @param retval Error value from @ref errno.h to be returned to the caller.
1430 *
1431 */
1432void async_data_write_void(errno_t retval)
1433{
1434 ipc_call_t call;
1435 async_data_write_receive(&call, NULL);
1436 async_answer_0(&call, retval);
1437}
1438
1439/** Wrapper for forwarding any data that is about to be received
1440 *
1441 */
1442errno_t async_data_write_forward_fast(async_exch_t *exch, sysarg_t imethod,
1443 sysarg_t arg1, sysarg_t arg2, sysarg_t arg3, sysarg_t arg4,
1444 ipc_call_t *dataptr)
1445{
1446 if (exch == NULL)
1447 return ENOENT;
1448
1449 ipc_call_t call;
1450 if (!async_data_write_receive(&call, NULL)) {
1451 async_answer_0(&call, EINVAL);
1452 return EINVAL;
1453 }
1454
1455 aid_t msg = async_send_fast(exch, imethod, arg1, arg2, arg3, arg4,
1456 dataptr);
1457 if (msg == 0) {
1458 async_answer_0(&call, EINVAL);
1459 return EINVAL;
1460 }
1461
1462 errno_t retval = ipc_forward_fast(call.cap_handle, exch->phone, 0, 0, 0,
1463 IPC_FF_ROUTE_FROM_ME);
1464 if (retval != EOK) {
1465 async_forget(msg);
1466 async_answer_0(&call, retval);
1467 return retval;
1468 }
1469
1470 errno_t rc;
1471 async_wait_for(msg, &rc);
1472
1473 return (errno_t) rc;
1474}
1475
1476/** Wrapper for receiving the IPC_M_CONNECT_TO_ME calls.
1477 *
1478 * If the current call is IPC_M_CONNECT_TO_ME then a new
1479 * async session is created for the accepted phone.
1480 *
1481 * @param mgmt Exchange management style.
1482 *
1483 * @return New async session.
1484 * @return NULL on failure.
1485 *
1486 */
1487async_sess_t *async_callback_receive(exch_mgmt_t mgmt)
1488{
1489 /* Accept the phone */
1490 ipc_call_t call;
1491 async_get_call(&call);
1492
1493 cap_phone_handle_t phandle = (cap_handle_t) IPC_GET_ARG5(call);
1494
1495 if ((IPC_GET_IMETHOD(call) != IPC_M_CONNECT_TO_ME) ||
1496 !CAP_HANDLE_VALID((phandle))) {
1497 async_answer_0(&call, EINVAL);
1498 return NULL;
1499 }
1500
1501 async_sess_t *sess = calloc(1, sizeof(async_sess_t));
1502 if (sess == NULL) {
1503 async_answer_0(&call, ENOMEM);
1504 return NULL;
1505 }
1506
1507 sess->iface = 0;
1508 sess->mgmt = mgmt;
1509 sess->phone = phandle;
1510
1511 fibril_mutex_initialize(&sess->remote_state_mtx);
1512 list_initialize(&sess->exch_list);
1513 fibril_mutex_initialize(&sess->mutex);
1514
1515 /* Acknowledge the connected phone */
1516 async_answer_0(&call, EOK);
1517
1518 return sess;
1519}
1520
1521/** Wrapper for receiving the IPC_M_CONNECT_TO_ME calls.
1522 *
1523 * If the call is IPC_M_CONNECT_TO_ME then a new
1524 * async session is created. However, the phone is
1525 * not accepted automatically.
1526 *
1527 * @param mgmt Exchange management style.
1528 * @param call Call data.
1529 *
1530 * @return New async session.
1531 * @return NULL on failure.
1532 * @return NULL if the call is not IPC_M_CONNECT_TO_ME.
1533 *
1534 */
1535async_sess_t *async_callback_receive_start(exch_mgmt_t mgmt, ipc_call_t *call)
1536{
1537 cap_phone_handle_t phandle = (cap_handle_t) IPC_GET_ARG5(*call);
1538
1539 if ((IPC_GET_IMETHOD(*call) != IPC_M_CONNECT_TO_ME) ||
1540 !CAP_HANDLE_VALID((phandle)))
1541 return NULL;
1542
1543 async_sess_t *sess = calloc(1, sizeof(async_sess_t));
1544 if (sess == NULL)
1545 return NULL;
1546
1547 sess->iface = 0;
1548 sess->mgmt = mgmt;
1549 sess->phone = phandle;
1550
1551 fibril_mutex_initialize(&sess->remote_state_mtx);
1552 list_initialize(&sess->exch_list);
1553 fibril_mutex_initialize(&sess->mutex);
1554
1555 return sess;
1556}
1557
1558bool async_state_change_receive(ipc_call_t *call)
1559{
1560 assert(call);
1561
1562 async_get_call(call);
1563
1564 if (IPC_GET_IMETHOD(*call) != IPC_M_STATE_CHANGE_AUTHORIZE)
1565 return false;
1566
1567 return true;
1568}
1569
1570errno_t async_state_change_finalize(ipc_call_t *call, async_exch_t *other_exch)
1571{
1572 assert(call);
1573
1574 return async_answer_1(call, EOK, CAP_HANDLE_RAW(other_exch->phone));
1575}
1576
1577__noreturn void async_manager(void)
1578{
1579 fibril_event_t ever = FIBRIL_EVENT_INIT;
1580 fibril_wait_for(&ever);
1581 __builtin_unreachable();
1582}
1583
1584/** @}
1585 */
Note: See TracBrowser for help on using the repository browser.