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

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since bd9e868 was 984a9ba, checked in by Martin Decky <martin@…>, 7 years ago

do not expose the call capability handler from the async framework

Keep the call capability handler encapsulated within the async framework
and do not expose it explicitly via its API. Use the pointer to
ipc_call_t as the sole object identifying an IPC call in the code that
uses the async framework.

This plugs a major leak in the abstraction and also simplifies both the
async framework (slightly) and all IPC servers.

  • Property mode set to 100644
File size: 44.7 KB
RevLine 
[06502f7d]1/*
[df4ed85]2 * Copyright (c) 2006 Ondrej Palkovsky
[06502f7d]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.
[b2951e2]27 */
28
[a46da63]29/** @addtogroup libc
[b2951e2]30 * @{
31 */
32/** @file
[c07544d3]33 */
[06502f7d]34
[80649a91]35/**
36 * Asynchronous library
37 *
[c07544d3]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.
[80649a91]41 *
[79ae36dd]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.
[80649a91]45 *
[9591265]46 * Example of use (pseudo C):
[c07544d3]47 *
[80649a91]48 * 1) Multithreaded client application
[9591265]49 *
[c07544d3]50 * fibril_create(fibril1, ...);
51 * fibril_create(fibril2, ...);
52 * ...
53 *
54 * int fibril1(void *arg)
55 * {
[79ae36dd]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 *
[c07544d3]66 * async_wait_for(c1);
67 * async_wait_for(c2);
68 * ...
69 * }
[80649a91]70 *
71 *
72 * 2) Multithreaded server application
73 *
[c07544d3]74 * main()
75 * {
76 * async_manager();
77 * }
78 *
[984a9ba]79 * port_handler(ipc_call_t *icall)
[c07544d3]80 * {
81 * if (want_refuse) {
[984a9ba]82 * async_answer_0(icall, ELIMIT);
[c07544d3]83 * return;
84 * }
[80649a91]85 *
[984a9ba]86 * async_answer_0(icall, EOK);
[53ca318]87 *
[984a9ba]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);
[c07544d3]93 * ...
94 * }
[a2cd194]95 *
[80649a91]96 */
[9591265]97
[64d2b10]98#define LIBC_ASYNC_C_
99#include <ipc/ipc.h>
[80649a91]100#include <async.h>
[49a796f1]101#include "../private/async.h"
[64d2b10]102#undef LIBC_ASYNC_C_
103
[8820544]104#include <ipc/irq.h>
105#include <ipc/event.h>
[64d2b10]106#include <futex.h>
[bc1f1c2]107#include <fibril.h>
[d9c8c81]108#include <adt/hash_table.h>
[853802e]109#include <adt/hash.h>
[d9c8c81]110#include <adt/list.h>
[80649a91]111#include <assert.h>
112#include <errno.h>
[daa90e8]113#include <sys/time.h>
[c0699467]114#include <libarch/barrier.h>
[3e6a98c5]115#include <stdbool.h>
[38d150e]116#include <stdlib.h>
[79ae36dd]117#include <mem.h>
118#include <stdlib.h>
[e2ab36f1]119#include <macros.h>
[101516d]120#include <as.h>
[ae6021d]121#include <abi/mm/as.h>
[49a796f1]122#include "../private/libc.h"
[d73d992]123#include "../private/fibril.h"
[5da7199]124
[8dab988]125#define DPRINTF(...) ((void) 0)
126
[79ae36dd]127/** Async framework global futex */
[927a181e]128futex_t async_futex = FUTEX_INITIALIZER;
[80649a91]129
[8619f25]130/** Number of threads waiting for IPC in the kernel. */
[c8afd5a]131static atomic_t threads_in_ipc_wait = { 0 };
[8619f25]132
[79ae36dd]133/** Call data */
[80649a91]134typedef struct {
135 link_t link;
136 ipc_call_t call;
137} msg_t;
138
[79ae36dd]139/* Client connection data */
[c80fdd0]140typedef struct {
[062d900]141 ht_link_t link;
[a35b458]142
[649f087]143 task_id_t in_task_id;
[79ae36dd]144 atomic_t refcnt;
[c80fdd0]145 void *data;
146} client_t;
147
[79ae36dd]148/* Server connection data */
[80649a91]149typedef struct {
[49d072e]150 awaiter_t wdata;
[a35b458]151
[e70bfa5]152 /** Hash table link. */
[062d900]153 ht_link_t link;
[a35b458]154
[e2ab36f1]155 /** Incoming client task ID. */
156 task_id_t in_task_id;
[a35b458]157
[e70bfa5]158 /** Incoming phone hash. */
[96b02eb9]159 sysarg_t in_phone_hash;
[a35b458]160
[23882034]161 /** Link to the client tracking structure. */
162 client_t *client;
[a35b458]163
[e70bfa5]164 /** Messages that should be delivered to this fibril. */
[b72efe8]165 list_t msg_queue;
[a35b458]166
[e70bfa5]167 /** Call data of the opening call. */
[80649a91]168 ipc_call_t call;
[a35b458]169
[e70bfa5]170 /** Identification of the closing call. */
[eadaeae8]171 cap_call_handle_t close_chandle;
[a35b458]172
[e70bfa5]173 /** Fibril function that will be used to handle the connection. */
[b688fd8]174 async_port_handler_t handler;
[a35b458]175
[b688fd8]176 /** Client data */
177 void *data;
[80649a91]178} connection_t;
179
[8dab988]180/* Member of notification_t::msg_list. */
181typedef struct {
182 link_t link;
183 ipc_call_t calldata;
184} notification_msg_t;
185
[8820544]186/* Notification data */
187typedef struct {
[3b1cc8d]188 /** notification_hash_table link */
189 ht_link_t htlink;
190
191 /** notification_queue link */
192 link_t qlink;
[a35b458]193
[8820544]194 /** Notification method */
195 sysarg_t imethod;
[a35b458]196
[8820544]197 /** Notification handler */
198 async_notification_handler_t handler;
[a35b458]199
[3b1cc8d]200 /** Notification handler argument */
201 void *arg;
202
[8dab988]203 /** List of arrived notifications. */
204 list_t msg_list;
[8820544]205} notification_t;
206
[bc1f1c2]207/** Identifier of the incoming connection handled by the current fibril. */
[79ae36dd]208static fibril_local connection_t *fibril_connection;
[e70bfa5]209
[46eec3b]210static void *default_client_data_constructor(void)
211{
212 return NULL;
213}
214
215static void default_client_data_destructor(void *data)
216{
217}
218
219static async_client_data_ctor_t async_client_data_create =
220 default_client_data_constructor;
221static async_client_data_dtor_t async_client_data_destroy =
222 default_client_data_destructor;
223
224void async_set_client_data_constructor(async_client_data_ctor_t ctor)
225{
[f302586]226 assert(async_client_data_create == default_client_data_constructor);
[46eec3b]227 async_client_data_create = ctor;
228}
229
230void async_set_client_data_destructor(async_client_data_dtor_t dtor)
231{
[f302586]232 assert(async_client_data_destroy == default_client_data_destructor);
[46eec3b]233 async_client_data_destroy = dtor;
234}
235
[3bd1d7d4]236static futex_t client_futex = FUTEX_INITIALIZER;
[c80fdd0]237static hash_table_t client_hash_table;
[3b1cc8d]238
239// TODO: lockfree notification_queue?
240static futex_t notification_futex = FUTEX_INITIALIZER;
[8820544]241static hash_table_t notification_hash_table;
[3b1cc8d]242static LIST_INITIALIZE(notification_queue);
243static FIBRIL_SEMAPHORE_INITIALIZE(notification_semaphore, 0);
244
[8dab988]245static LIST_INITIALIZE(notification_freelist);
246static long notification_freelist_total = 0;
247static long notification_freelist_used = 0;
248
[8820544]249static sysarg_t notification_avail = 0;
250
[3bd1d7d4]251/* The remaining structures are guarded by async_futex. */
252static hash_table_t conn_hash_table;
253static LIST_INITIALIZE(timeout_list);
254
[8820544]255static size_t client_key_hash(void *key)
[c80fdd0]256{
[8820544]257 task_id_t in_task_id = *(task_id_t *) key;
258 return in_task_id;
[c80fdd0]259}
260
[062d900]261static size_t client_hash(const ht_link_t *item)
[c80fdd0]262{
[062d900]263 client_t *client = hash_table_get_inst(item, client_t, link);
264 return client_key_hash(&client->in_task_id);
[c80fdd0]265}
266
[8820544]267static bool client_key_equal(void *key, const ht_link_t *item)
[c80fdd0]268{
[8820544]269 task_id_t in_task_id = *(task_id_t *) key;
[062d900]270 client_t *client = hash_table_get_inst(item, client_t, link);
[8820544]271 return in_task_id == client->in_task_id;
[c80fdd0]272}
273
274/** Operations for the client hash table. */
[062d900]275static hash_table_ops_t client_hash_table_ops = {
[c80fdd0]276 .hash = client_hash,
[062d900]277 .key_hash = client_key_hash,
278 .key_equal = client_key_equal,
[4e00f87]279 .equal = NULL,
280 .remove_callback = NULL
[c80fdd0]281};
[80649a91]282
[853802e]283typedef struct {
284 task_id_t task_id;
285 sysarg_t phone_hash;
286} conn_key_t;
287
288/** Compute hash into the connection hash table
[e70bfa5]289 *
[853802e]290 * The hash is based on the source task ID and the source phone hash. The task
291 * ID is included in the hash because a phone hash alone might not be unique
292 * while we still track connections for killed tasks due to kernel's recycling
293 * of phone structures.
294 *
295 * @param key Pointer to the connection key structure.
[c07544d3]296 *
297 * @return Index into the connection hash table.
[e70bfa5]298 *
299 */
[062d900]300static size_t conn_key_hash(void *key)
[450cd3a]301{
[853802e]302 conn_key_t *ck = (conn_key_t *) key;
303
304 size_t hash = 0;
305 hash = hash_combine(hash, LOWER32(ck->task_id));
306 hash = hash_combine(hash, UPPER32(ck->task_id));
307 hash = hash_combine(hash, ck->phone_hash);
308 return hash;
[450cd3a]309}
[06502f7d]310
[062d900]311static size_t conn_hash(const ht_link_t *item)
[450cd3a]312{
[062d900]313 connection_t *conn = hash_table_get_inst(item, connection_t, link);
[853802e]314 return conn_key_hash(&(conn_key_t){
315 .task_id = conn->in_task_id,
316 .phone_hash = conn->in_phone_hash
317 });
[450cd3a]318}
[06502f7d]319
[062d900]320static bool conn_key_equal(void *key, const ht_link_t *item)
[450cd3a]321{
[853802e]322 conn_key_t *ck = (conn_key_t *) key;
[062d900]323 connection_t *conn = hash_table_get_inst(item, connection_t, link);
[853802e]324 return ((ck->task_id == conn->in_task_id) &&
325 (ck->phone_hash == conn->in_phone_hash));
[450cd3a]326}
327
[e70bfa5]328/** Operations for the connection hash table. */
[062d900]329static hash_table_ops_t conn_hash_table_ops = {
[80649a91]330 .hash = conn_hash,
[062d900]331 .key_hash = conn_key_hash,
332 .key_equal = conn_key_equal,
[4e00f87]333 .equal = NULL,
334 .remove_callback = NULL
[80649a91]335};
336
[9ef495f]337static client_t *async_client_get(task_id_t client_id, bool create)
338{
339 client_t *client = NULL;
[a35b458]340
[3bd1d7d4]341 futex_lock(&client_futex);
[9ef495f]342 ht_link_t *link = hash_table_find(&client_hash_table, &client_id);
343 if (link) {
344 client = hash_table_get_inst(link, client_t, link);
345 atomic_inc(&client->refcnt);
346 } else if (create) {
[3bd1d7d4]347 // TODO: move the malloc out of critical section
[9ef495f]348 client = malloc(sizeof(client_t));
349 if (client) {
350 client->in_task_id = client_id;
351 client->data = async_client_data_create();
[a35b458]352
[9ef495f]353 atomic_set(&client->refcnt, 1);
354 hash_table_insert(&client_hash_table, &client->link);
355 }
356 }
[a35b458]357
[3bd1d7d4]358 futex_unlock(&client_futex);
[9ef495f]359 return client;
360}
361
362static void async_client_put(client_t *client)
363{
364 bool destroy;
[a35b458]365
[3bd1d7d4]366 futex_lock(&client_futex);
[a35b458]367
[9ef495f]368 if (atomic_predec(&client->refcnt) == 0) {
369 hash_table_remove(&client_hash_table, &client->in_task_id);
370 destroy = true;
371 } else
372 destroy = false;
[a35b458]373
[3bd1d7d4]374 futex_unlock(&client_futex);
[a35b458]375
[9ef495f]376 if (destroy) {
377 if (client->data)
378 async_client_data_destroy(client->data);
[a35b458]379
[9ef495f]380 free(client);
381 }
382}
383
384/** Wrapper for client connection fibril.
385 *
386 * When a new connection arrives, a fibril with this implementing
387 * function is created.
388 *
389 * @param arg Connection structure pointer.
390 *
391 * @return Always zero.
392 *
393 */
[b7fd2a0]394static errno_t connection_fibril(void *arg)
[9ef495f]395{
396 assert(arg);
[a35b458]397
[9ef495f]398 /*
399 * Setup fibril-local connection pointer.
400 */
401 fibril_connection = (connection_t *) arg;
[a35b458]402
[9ef495f]403 /*
404 * Add our reference for the current connection in the client task
405 * tracking structure. If this is the first reference, create and
406 * hash in a new tracking structure.
407 */
[a35b458]408
[9ef495f]409 client_t *client = async_client_get(fibril_connection->in_task_id, true);
410 if (!client) {
[061274f]411 ipc_answer_0(fibril_connection->call.cap_handle, ENOMEM);
[9ef495f]412 return 0;
413 }
[a35b458]414
[9ef495f]415 fibril_connection->client = client;
[a35b458]416
[9ef495f]417 /*
418 * Call the connection handler function.
419 */
[984a9ba]420 fibril_connection->handler(&fibril_connection->call,
421 fibril_connection->data);
[a35b458]422
[9ef495f]423 /*
424 * Remove the reference for this client task connection.
425 */
426 async_client_put(client);
[a35b458]427
[9ef495f]428 /*
429 * Remove myself from the connection hash table.
430 */
[95838f1]431 futex_lock(&async_futex);
[853802e]432 hash_table_remove(&conn_hash_table, &(conn_key_t){
433 .task_id = fibril_connection->in_task_id,
434 .phone_hash = fibril_connection->in_phone_hash
435 });
[95838f1]436 futex_unlock(&async_futex);
[a35b458]437
[9ef495f]438 /*
439 * Answer all remaining messages with EHANGUP.
440 */
441 while (!list_empty(&fibril_connection->msg_queue)) {
442 msg_t *msg =
443 list_get_instance(list_first(&fibril_connection->msg_queue),
444 msg_t, link);
[a35b458]445
[9ef495f]446 list_remove(&msg->link);
[061274f]447 ipc_answer_0(msg->call.cap_handle, EHANGUP);
[9ef495f]448 free(msg);
449 }
[a35b458]450
[9ef495f]451 /*
452 * If the connection was hung-up, answer the last call,
453 * i.e. IPC_M_PHONE_HUNGUP.
454 */
[01c3bb4]455 if (fibril_connection->close_chandle)
456 ipc_answer_0(fibril_connection->close_chandle, EOK);
[a35b458]457
[9ef495f]458 free(fibril_connection);
[d5c1051]459 return EOK;
[9ef495f]460}
461
462/** Create a new fibril for a new connection.
463 *
[01c3bb4]464 * Create new fibril for connection, fill in connection structures and insert it
465 * into the hash table, so that later we can easily do routing of messages to
466 * particular fibrils.
[9ef495f]467 *
[01c3bb4]468 * @param in_task_id Identification of the incoming connection.
469 * @param in_phone_hash Identification of the incoming connection.
[061274f]470 * @param call Call data of the opening call. If call is NULL,
471 * the connection was opened by accepting the
472 * IPC_M_CONNECT_TO_ME call and this function is
473 * called directly by the server.
[01c3bb4]474 * @param handler Connection handler.
475 * @param data Client argument to pass to the connection handler.
[9ef495f]476 *
[01c3bb4]477 * @return New fibril id or NULL on failure.
[9ef495f]478 *
479 */
480static fid_t async_new_connection(task_id_t in_task_id, sysarg_t in_phone_hash,
[061274f]481 ipc_call_t *call, async_port_handler_t handler, void *data)
[9ef495f]482{
483 connection_t *conn = malloc(sizeof(*conn));
484 if (!conn) {
[061274f]485 if (call)
486 ipc_answer_0(call->cap_handle, ENOMEM);
[a35b458]487
[9ef495f]488 return (uintptr_t) NULL;
489 }
[a35b458]490
[9ef495f]491 conn->in_task_id = in_task_id;
492 conn->in_phone_hash = in_phone_hash;
493 list_initialize(&conn->msg_queue);
[01c3bb4]494 conn->close_chandle = CAP_NIL;
[9ef495f]495 conn->handler = handler;
496 conn->data = data;
[a35b458]497
[9ef495f]498 if (call)
499 conn->call = *call;
[061274f]500 else
501 conn->call.cap_handle = CAP_NIL;
[a35b458]502
[9ef495f]503 /* We will activate the fibril ASAP */
504 conn->wdata.active = true;
505 conn->wdata.fid = fibril_create(connection_fibril, conn);
[a35b458]506
[9ef495f]507 if (conn->wdata.fid == 0) {
508 free(conn);
[a35b458]509
[061274f]510 if (call)
511 ipc_answer_0(call->cap_handle, ENOMEM);
[a35b458]512
[9ef495f]513 return (uintptr_t) NULL;
514 }
[a35b458]515
[9ef495f]516 /* Add connection to the connection hash table */
[a35b458]517
[95838f1]518 futex_lock(&async_futex);
[9ef495f]519 hash_table_insert(&conn_hash_table, &conn->link);
[95838f1]520 futex_unlock(&async_futex);
[a35b458]521
[9ef495f]522 fibril_add_ready(conn->wdata.fid);
[a35b458]523
[9ef495f]524 return conn->wdata.fid;
525}
526
[78bb04b]527/** Wrapper for making IPC_M_CONNECT_TO_ME calls using the async framework.
528 *
529 * Ask through phone for a new connection to some service.
530 *
531 * @param exch Exchange for sending the message.
532 * @param iface Callback interface.
533 * @param arg1 User defined argument.
534 * @param arg2 User defined argument.
535 * @param handler Callback handler.
536 * @param data Handler data.
537 * @param port_id ID of the newly created port.
538 *
[cde999a]539 * @return Zero on success or an error code.
[78bb04b]540 *
541 */
[b7fd2a0]542errno_t async_create_callback_port(async_exch_t *exch, iface_t iface, sysarg_t arg1,
[78bb04b]543 sysarg_t arg2, async_port_handler_t handler, void *data, port_id_t *port_id)
544{
545 if ((iface & IFACE_MOD_CALLBACK) != IFACE_MOD_CALLBACK)
546 return EINVAL;
[a35b458]547
[78bb04b]548 if (exch == NULL)
549 return ENOENT;
[a35b458]550
[78bb04b]551 ipc_call_t answer;
552 aid_t req = async_send_3(exch, IPC_M_CONNECT_TO_ME, iface, arg1, arg2,
553 &answer);
[a35b458]554
[fda19b8]555 errno_t rc;
556 async_wait_for(req, &rc);
557 if (rc != EOK)
558 return rc;
[a35b458]559
[fda19b8]560 rc = async_create_port_internal(iface, handler, data, port_id);
561 if (rc != EOK)
562 return rc;
[a35b458]563
[fda19b8]564 sysarg_t phone_hash = IPC_GET_ARG5(answer);
[78bb04b]565 fid_t fid = async_new_connection(answer.in_task_id, phone_hash,
[061274f]566 NULL, handler, data);
[78bb04b]567 if (fid == (uintptr_t) NULL)
568 return ENOMEM;
[a35b458]569
[78bb04b]570 return EOK;
571}
572
[8820544]573static size_t notification_key_hash(void *key)
574{
575 sysarg_t id = *(sysarg_t *) key;
576 return id;
577}
578
579static size_t notification_hash(const ht_link_t *item)
580{
581 notification_t *notification =
[3b1cc8d]582 hash_table_get_inst(item, notification_t, htlink);
[8820544]583 return notification_key_hash(&notification->imethod);
584}
585
586static bool notification_key_equal(void *key, const ht_link_t *item)
587{
588 sysarg_t id = *(sysarg_t *) key;
589 notification_t *notification =
[3b1cc8d]590 hash_table_get_inst(item, notification_t, htlink);
[8820544]591 return id == notification->imethod;
592}
593
594/** Operations for the notification hash table. */
595static hash_table_ops_t notification_hash_table_ops = {
596 .hash = notification_hash,
597 .key_hash = notification_key_hash,
598 .key_equal = notification_key_equal,
599 .equal = NULL,
600 .remove_callback = NULL
601};
602
[e70bfa5]603/** Sort in current fibril's timeout request.
[49d072e]604 *
[c07544d3]605 * @param wd Wait data of the current fibril.
606 *
[49d072e]607 */
[b6ee5b1]608void async_insert_timeout(awaiter_t *wd)
[49d072e]609{
[79ae36dd]610 assert(wd);
[a35b458]611
[f53cc81]612 wd->to_event.occurred = false;
613 wd->to_event.inlist = true;
[a35b458]614
[b72efe8]615 link_t *tmp = timeout_list.head.next;
616 while (tmp != &timeout_list.head) {
[3bacee1]617 awaiter_t *cur =
618 list_get_instance(tmp, awaiter_t, to_event.link);
[a35b458]619
[f53cc81]620 if (tv_gteq(&cur->to_event.expires, &wd->to_event.expires))
[49d072e]621 break;
[a35b458]622
[49d072e]623 tmp = tmp->next;
624 }
[a35b458]625
[b72efe8]626 list_insert_before(&wd->to_event.link, tmp);
[49d072e]627}
628
[e70bfa5]629/** Try to route a call to an appropriate connection fibril.
[80649a91]630 *
[36c9234]631 * If the proper connection fibril is found, a message with the call is added to
632 * its message queue. If the fibril was not active, it is activated and all
633 * timeouts are unregistered.
634 *
[061274f]635 * @param call Data of the incoming call.
[c07544d3]636 *
637 * @return False if the call doesn't match any connection.
[47b7006]638 * @return True if the call was passed to the respective connection fibril.
[36c9234]639 *
[80649a91]640 */
[061274f]641static bool route_call(ipc_call_t *call)
[450cd3a]642{
[79ae36dd]643 assert(call);
[a35b458]644
[95838f1]645 futex_lock(&async_futex);
[a35b458]646
[853802e]647 ht_link_t *link = hash_table_find(&conn_hash_table, &(conn_key_t){
648 .task_id = call->in_task_id,
649 .phone_hash = call->in_phone_hash
650 });
[8820544]651 if (!link) {
[95838f1]652 futex_unlock(&async_futex);
[c07544d3]653 return false;
[450cd3a]654 }
[a35b458]655
[8820544]656 connection_t *conn = hash_table_get_inst(link, connection_t, link);
[a35b458]657
[c07544d3]658 msg_t *msg = malloc(sizeof(*msg));
659 if (!msg) {
[95838f1]660 futex_unlock(&async_futex);
[c07544d3]661 return false;
662 }
[a35b458]663
[80649a91]664 msg->call = *call;
665 list_append(&msg->link, &conn->msg_queue);
[a35b458]666
[228e490]667 if (IPC_GET_IMETHOD(*call) == IPC_M_PHONE_HUNGUP)
[061274f]668 conn->close_chandle = call->cap_handle;
[a35b458]669
[36c9234]670 /* If the connection fibril is waiting for an event, activate it */
[49d072e]671 if (!conn->wdata.active) {
[a35b458]672
[49d072e]673 /* If in timeout list, remove it */
[f53cc81]674 if (conn->wdata.to_event.inlist) {
675 conn->wdata.to_event.inlist = false;
676 list_remove(&conn->wdata.to_event.link);
[49d072e]677 }
[a35b458]678
[c07544d3]679 conn->wdata.active = true;
[bc1f1c2]680 fibril_add_ready(conn->wdata.fid);
[80649a91]681 }
[a35b458]682
[95838f1]683 futex_unlock(&async_futex);
[c07544d3]684 return true;
685}
[80649a91]686
[3b1cc8d]687/** Function implementing the notification handler fibril. Never returns. */
688static errno_t notification_fibril_func(void *arg)
689{
690 (void) arg;
691
692 while (true) {
693 fibril_semaphore_down(&notification_semaphore);
694
695 futex_lock(&notification_futex);
696
697 /*
698 * The semaphore ensures that if we get this far,
699 * the queue must be non-empty.
700 */
701 assert(!list_empty(&notification_queue));
702
703 notification_t *notification = list_get_instance(
704 list_first(&notification_queue), notification_t, qlink);
705
706 async_notification_handler_t handler = notification->handler;
707 void *arg = notification->arg;
708
[8dab988]709 notification_msg_t *m = list_pop(&notification->msg_list,
710 notification_msg_t, link);
711 assert(m);
712 ipc_call_t calldata = m->calldata;
713
714 notification_freelist_used--;
715
716 if (notification_freelist_total > 64 &&
717 notification_freelist_total > 2 * notification_freelist_used) {
718 /* Going to free the structure if we have too much. */
719 notification_freelist_total--;
720 } else {
721 /* Otherwise add to freelist. */
722 list_append(&m->link, &notification_freelist);
723 m = NULL;
724 }
[3b1cc8d]725
[8dab988]726 if (list_empty(&notification->msg_list))
727 list_remove(&notification->qlink);
[3b1cc8d]728
[8dab988]729 futex_unlock(&notification_futex);
[3b1cc8d]730
731 if (handler)
732 handler(&calldata, arg);
[8dab988]733
734 free(m);
[3b1cc8d]735 }
736
737 /* Not reached. */
738 return EOK;
739}
740
741/**
742 * Creates a new dedicated fibril for handling notifications.
743 * By default, there is one such fibril. This function can be used to
744 * create more in order to increase the number of notification that can
745 * be processed concurrently.
746 *
747 * Currently, there is no way to destroy those fibrils after they are created.
748 */
749errno_t async_spawn_notification_handler(void)
750{
751 fid_t f = fibril_create(notification_fibril_func, NULL);
752 if (f == 0)
753 return ENOMEM;
754
755 fibril_add_ready(f);
756 return EOK;
757}
758
759/** Queue notification.
[c07544d3]760 *
[c170438]761 * @param call Data of the incoming call.
[58563585]762 *
[c07544d3]763 */
[3b1cc8d]764static void queue_notification(ipc_call_t *call)
[c07544d3]765{
[c170438]766 assert(call);
[a35b458]767
[3b1cc8d]768 futex_lock(&notification_futex);
[a35b458]769
[8dab988]770 notification_msg_t *m = list_pop(&notification_freelist,
771 notification_msg_t, link);
772
773 if (!m) {
774 futex_unlock(&notification_futex);
775 m = malloc(sizeof(notification_msg_t));
776 if (!m) {
777 DPRINTF("Out of memory.\n");
778 abort();
779 }
780
781 futex_lock(&notification_futex);
782 notification_freelist_total++;
783 }
784
[8820544]785 ht_link_t *link = hash_table_find(&notification_hash_table,
[c170438]786 &IPC_GET_IMETHOD(*call));
[3b1cc8d]787 if (!link) {
788 /* Invalid notification. */
789 // TODO: Make sure this can't happen and turn it into assert.
[8dab988]790 notification_freelist_total--;
[3b1cc8d]791 futex_unlock(&notification_futex);
[8dab988]792 free(m);
[3b1cc8d]793 return;
[8820544]794 }
[a35b458]795
[3b1cc8d]796 notification_t *notification =
797 hash_table_get_inst(link, notification_t, htlink);
798
[8dab988]799 notification_freelist_used++;
800 m->calldata = *call;
801 list_append(&m->link, &notification->msg_list);
[3b1cc8d]802
[8dab988]803 if (!link_in_use(&notification->qlink))
804 list_append(&notification->qlink, &notification_queue);
[3b1cc8d]805
806 futex_unlock(&notification_futex);
807
808 fibril_semaphore_up(&notification_semaphore);
809}
810
811/**
812 * Creates a new notification structure and inserts it into the hash table.
813 *
814 * @param handler Function to call when notification is received.
815 * @param arg Argument for the handler function.
816 * @return The newly created notification structure.
817 */
818static notification_t *notification_create(async_notification_handler_t handler, void *arg)
819{
820 notification_t *notification = calloc(1, sizeof(notification_t));
821 if (!notification)
822 return NULL;
823
824 notification->handler = handler;
825 notification->arg = arg;
826
[8dab988]827 list_initialize(&notification->msg_list);
828
[3b1cc8d]829 fid_t fib = 0;
830
831 futex_lock(&notification_futex);
832
833 if (notification_avail == 0) {
834 /* Attempt to create the first handler fibril. */
835 fib = fibril_create(notification_fibril_func, NULL);
836 if (fib == 0) {
837 futex_unlock(&notification_futex);
838 free(notification);
839 return NULL;
840 }
841 }
842
843 sysarg_t imethod = notification_avail;
844 notification_avail++;
845
846 notification->imethod = imethod;
847 hash_table_insert(&notification_hash_table, &notification->htlink);
848
849 futex_unlock(&notification_futex);
850
851 if (imethod == 0) {
852 assert(fib);
853 fibril_add_ready(fib);
854 }
[a35b458]855
[3b1cc8d]856 return notification;
[80649a91]857}
858
[8820544]859/** Subscribe to IRQ notification.
860 *
861 * @param inr IRQ number.
862 * @param handler Notification handler.
863 * @param data Notification handler client data.
864 * @param ucode Top-half pseudocode handler.
865 *
[071a1ddb]866 * @param[out] handle IRQ capability handle on success.
867 *
[cde999a]868 * @return An error code.
[8820544]869 *
870 */
[b7fd2a0]871errno_t async_irq_subscribe(int inr, async_notification_handler_t handler,
[eadaeae8]872 void *data, const irq_code_t *ucode, cap_irq_handle_t *handle)
[8820544]873{
[3b1cc8d]874 notification_t *notification = notification_create(handler, data);
[8820544]875 if (!notification)
876 return ENOMEM;
[a35b458]877
[eadaeae8]878 cap_irq_handle_t ihandle;
[3b1cc8d]879 errno_t rc = ipc_irq_subscribe(inr, notification->imethod, ucode,
880 &ihandle);
[071a1ddb]881 if (rc == EOK && handle != NULL) {
[eadaeae8]882 *handle = ihandle;
[9233e9d]883 }
[071a1ddb]884 return rc;
[8820544]885}
886
887/** Unsubscribe from IRQ notification.
888 *
[eadaeae8]889 * @param handle IRQ capability handle.
[8820544]890 *
[cde999a]891 * @return Zero on success or an error code.
[8820544]892 *
893 */
[eadaeae8]894errno_t async_irq_unsubscribe(cap_irq_handle_t ihandle)
[8820544]895{
896 // TODO: Remove entry from hash table
897 // to avoid memory leak
[a35b458]898
[eadaeae8]899 return ipc_irq_unsubscribe(ihandle);
[8820544]900}
901
902/** Subscribe to event notifications.
903 *
904 * @param evno Event type to subscribe.
905 * @param handler Notification handler.
906 * @param data Notification handler client data.
907 *
[cde999a]908 * @return Zero on success or an error code.
[8820544]909 *
910 */
[b7fd2a0]911errno_t async_event_subscribe(event_type_t evno,
[8820544]912 async_notification_handler_t handler, void *data)
913{
[3b1cc8d]914 notification_t *notification = notification_create(handler, data);
[8820544]915 if (!notification)
916 return ENOMEM;
[a35b458]917
[3b1cc8d]918 return ipc_event_subscribe(evno, notification->imethod);
[8820544]919}
920
921/** Subscribe to task event notifications.
922 *
923 * @param evno Event type to subscribe.
924 * @param handler Notification handler.
925 * @param data Notification handler client data.
926 *
[cde999a]927 * @return Zero on success or an error code.
[8820544]928 *
929 */
[b7fd2a0]930errno_t async_event_task_subscribe(event_task_type_t evno,
[8820544]931 async_notification_handler_t handler, void *data)
932{
[3b1cc8d]933 notification_t *notification = notification_create(handler, data);
[8820544]934 if (!notification)
935 return ENOMEM;
[a35b458]936
[3b1cc8d]937 return ipc_event_task_subscribe(evno, notification->imethod);
[8820544]938}
939
940/** Unmask event notifications.
941 *
942 * @param evno Event type to unmask.
943 *
944 * @return Value returned by the kernel.
945 *
946 */
[b7fd2a0]947errno_t async_event_unmask(event_type_t evno)
[8820544]948{
949 return ipc_event_unmask(evno);
950}
951
952/** Unmask task event notifications.
953 *
954 * @param evno Event type to unmask.
955 *
956 * @return Value returned by the kernel.
957 *
958 */
[b7fd2a0]959errno_t async_event_task_unmask(event_task_type_t evno)
[8820544]960{
961 return ipc_event_task_unmask(evno);
962}
963
[e70bfa5]964/** Return new incoming message for the current (fibril-local) connection.
965 *
[984a9ba]966 * @param call Storage where the incoming call data will be stored.
967 * @param usecs Timeout in microseconds. Zero denotes no timeout.
968 *
969 * @return If no timeout was specified, then true is returned.
970 * @return If a timeout is specified, then true is returned unless
971 * the timeout expires prior to receiving a message.
[e70bfa5]972 *
973 */
[984a9ba]974bool async_get_call_timeout(ipc_call_t *call, suseconds_t usecs)
[80649a91]975{
[79ae36dd]976 assert(call);
977 assert(fibril_connection);
[a35b458]978
[7c3fb9b]979 /*
980 * Why doing this?
[79ae36dd]981 * GCC 4.1.0 coughs on fibril_connection-> dereference.
[6c46350]982 * GCC 4.1.1 happilly puts the rdhwr instruction in delay slot.
[c07544d3]983 * I would never expect to find so many errors in
984 * a compiler.
[6c46350]985 */
[79ae36dd]986 connection_t *conn = fibril_connection;
[a35b458]987
[95838f1]988 futex_lock(&async_futex);
[a35b458]989
[49d072e]990 if (usecs) {
[45cbcaf4]991 getuptime(&conn->wdata.to_event.expires);
[7f9d97f3]992 tv_add_diff(&conn->wdata.to_event.expires, usecs);
[c07544d3]993 } else
[f53cc81]994 conn->wdata.to_event.inlist = false;
[a35b458]995
[e70bfa5]996 /* If nothing in queue, wait until something arrives */
[6c46350]997 while (list_empty(&conn->msg_queue)) {
[01c3bb4]998 if (conn->close_chandle) {
[8c8f8d6]999 /*
1000 * Handle the case when the connection was already
1001 * closed by the client but the server did not notice
1002 * the first IPC_M_PHONE_HUNGUP call and continues to
1003 * call async_get_call_timeout(). Repeat
[47b7006]1004 * IPC_M_PHONE_HUNGUP until the caller notices.
[8c8f8d6]1005 */
1006 memset(call, 0, sizeof(ipc_call_t));
[228e490]1007 IPC_SET_IMETHOD(*call, IPC_M_PHONE_HUNGUP);
[95838f1]1008 futex_unlock(&async_futex);
[984a9ba]1009 return true;
[8c8f8d6]1010 }
[a35b458]1011
[085bd54]1012 if (usecs)
[b6ee5b1]1013 async_insert_timeout(&conn->wdata);
[a35b458]1014
[c07544d3]1015 conn->wdata.active = false;
[a35b458]1016
[c7509e5]1017 /*
1018 * Note: the current fibril will be rescheduled either due to a
1019 * timeout or due to an arriving message destined to it. In the
1020 * former case, handle_expired_timeouts() and, in the latter
1021 * case, route_call() will perform the wakeup.
1022 */
[ab6edb6]1023 fibril_switch(FIBRIL_FROM_BLOCKED);
[a35b458]1024
[3bacee1]1025 if ((usecs) && (conn->wdata.to_event.occurred) &&
1026 (list_empty(&conn->msg_queue))) {
[e70bfa5]1027 /* If we timed out -> exit */
[95838f1]1028 futex_unlock(&async_futex);
[984a9ba]1029 return false;
[49d072e]1030 }
[450cd3a]1031 }
[a35b458]1032
[57dea62]1033 msg_t *msg = list_get_instance(list_first(&conn->msg_queue),
1034 msg_t, link);
[80649a91]1035 list_remove(&msg->link);
[a35b458]1036
[80649a91]1037 *call = msg->call;
1038 free(msg);
[a35b458]1039
[95838f1]1040 futex_unlock(&async_futex);
[984a9ba]1041 return true;
[80649a91]1042}
1043
[455f190]1044void *async_get_client_data(void)
1045{
1046 assert(fibril_connection);
1047 return fibril_connection->client->data;
1048}
1049
[e2ab36f1]1050void *async_get_client_data_by_id(task_id_t client_id)
[455f190]1051{
[e2ab36f1]1052 client_t *client = async_client_get(client_id, false);
[455f190]1053 if (!client)
1054 return NULL;
[a35b458]1055
[455f190]1056 if (!client->data) {
1057 async_client_put(client);
1058 return NULL;
1059 }
[a35b458]1060
[455f190]1061 return client->data;
1062}
1063
[e2ab36f1]1064void async_put_client_data_by_id(task_id_t client_id)
[455f190]1065{
[e2ab36f1]1066 client_t *client = async_client_get(client_id, false);
[a35b458]1067
[455f190]1068 assert(client);
1069 assert(client->data);
[a35b458]1070
[cdc8ee2d]1071 /* Drop the reference we got in async_get_client_data_by_hash(). */
1072 async_client_put(client);
[a35b458]1073
[cdc8ee2d]1074 /* Drop our own reference we got at the beginning of this function. */
[455f190]1075 async_client_put(client);
1076}
1077
[36c9234]1078/** Handle a call that was received.
1079 *
1080 * If the call has the IPC_M_CONNECT_ME_TO method, a new connection is created.
1081 * Otherwise the call is routed to its connection fibril.
1082 *
[061274f]1083 * @param call Data of the incoming call.
[6b21292]1084 *
[36c9234]1085 */
[061274f]1086static void handle_call(ipc_call_t *call)
[80649a91]1087{
[79ae36dd]1088 assert(call);
[a35b458]1089
[e768aea]1090 if (call->flags & IPC_CALL_ANSWERED)
1091 return;
1092
[061274f]1093 if (call->cap_handle == CAP_NIL) {
[e768aea]1094 if (call->flags & IPC_CALL_NOTIF) {
1095 /* Kernel notification */
1096 queue_notification(call);
1097 }
[47b7006]1098 return;
[6b21292]1099 }
[a35b458]1100
[566992e1]1101 /* New connection */
1102 if (IPC_GET_IMETHOD(*call) == IPC_M_CONNECT_ME_TO) {
1103 iface_t iface = (iface_t) IPC_GET_ARG1(*call);
1104 sysarg_t in_phone_hash = IPC_GET_ARG5(*call);
[a35b458]1105
[49a796f1]1106 // TODO: Currently ignores all ports but the first one.
1107 void *data;
1108 async_port_handler_t handler =
1109 async_get_port_handler(iface, 0, &data);
[a35b458]1110
[061274f]1111 async_new_connection(call->in_task_id, in_phone_hash, call,
1112 handler, data);
[566992e1]1113 return;
1114 }
[a35b458]1115
[36c9234]1116 /* Try to route the call through the connection hash table */
[061274f]1117 if (route_call(call))
[47b7006]1118 return;
[a35b458]1119
[44c6d88d]1120 /* Unknown call from unknown phone - hang it up */
[061274f]1121 ipc_answer_0(call->cap_handle, EHANGUP);
[450cd3a]1122}
1123
[f2f0392]1124/** Fire all timeouts that expired. */
[e768aea]1125static suseconds_t handle_expired_timeouts(unsigned int *flags)
[c042bdd]1126{
[e768aea]1127 /* Make sure the async_futex is held. */
1128 futex_assert_is_locked(&async_futex);
1129
[c042bdd]1130 struct timeval tv;
[45cbcaf4]1131 getuptime(&tv);
[a35b458]1132
[e768aea]1133 bool fired = false;
[a35b458]1134
[b72efe8]1135 link_t *cur = list_first(&timeout_list);
1136 while (cur != NULL) {
[47b7006]1137 awaiter_t *waiter =
1138 list_get_instance(cur, awaiter_t, to_event.link);
[a35b458]1139
[e768aea]1140 if (tv_gt(&waiter->to_event.expires, &tv)) {
1141 if (fired) {
1142 *flags = SYNCH_FLAGS_NON_BLOCKING;
1143 return 0;
1144 }
1145 *flags = 0;
1146 return tv_sub_diff(&waiter->to_event.expires, &tv);
1147 }
[a35b458]1148
[f53cc81]1149 list_remove(&waiter->to_event.link);
1150 waiter->to_event.inlist = false;
1151 waiter->to_event.occurred = true;
[a35b458]1152
[36c9234]1153 /*
[c07544d3]1154 * Redundant condition?
1155 * The fibril should not be active when it gets here.
[c042bdd]1156 */
[49d072e]1157 if (!waiter->active) {
[c07544d3]1158 waiter->active = true;
[bc1f1c2]1159 fibril_add_ready(waiter->fid);
[e768aea]1160 fired = true;
[c042bdd]1161 }
[a35b458]1162
[b72efe8]1163 cur = list_first(&timeout_list);
[c042bdd]1164 }
[a35b458]1165
[e768aea]1166 if (fired) {
1167 *flags = SYNCH_FLAGS_NON_BLOCKING;
1168 return 0;
1169 }
1170
1171 return SYNCH_NO_TIMEOUT;
[c042bdd]1172}
1173
[36c9234]1174/** Endless loop dispatching incoming calls and answers.
1175 *
[c07544d3]1176 * @return Never returns.
1177 *
[36c9234]1178 */
[b7fd2a0]1179static errno_t async_manager_worker(void)
[80649a91]1180{
[c07544d3]1181 while (true) {
[95838f1]1182 futex_lock(&async_futex);
[ab6edb6]1183 fibril_switch(FIBRIL_FROM_MANAGER);
1184
1185 /*
1186 * The switch only returns when there is no non-manager fibril
1187 * it can run.
1188 */
[a35b458]1189
[1db6dfd]1190 unsigned int flags = SYNCH_FLAGS_NONE;
[e768aea]1191 suseconds_t next_timeout = handle_expired_timeouts(&flags);
1192 futex_unlock(&async_futex);
[a35b458]1193
[8619f25]1194 atomic_inc(&threads_in_ipc_wait);
[a35b458]1195
[c07544d3]1196 ipc_call_t call;
[e768aea]1197 errno_t rc = ipc_wait_cycle(&call, next_timeout, flags);
[a35b458]1198
[8619f25]1199 atomic_dec(&threads_in_ipc_wait);
[a35b458]1200
[6deb2cd]1201 assert(rc == EOK);
[061274f]1202 handle_call(&call);
[80649a91]1203 }
[01c3bb4]1204
[a46da63]1205 return 0;
[80649a91]1206}
1207
[36c9234]1208/** Function to start async_manager as a standalone fibril.
[c07544d3]1209 *
[36c9234]1210 * When more kernel threads are used, one async manager should exist per thread.
1211 *
[c07544d3]1212 * @param arg Unused.
1213 * @return Never returns.
[36c9234]1214 *
[a2cd194]1215 */
[b7fd2a0]1216static errno_t async_manager_fibril(void *arg)
[80649a91]1217{
[085bd54]1218 async_manager_worker();
[a46da63]1219 return 0;
[80649a91]1220}
[450cd3a]1221
[36c9234]1222/** Add one manager to manager list. */
[80649a91]1223void async_create_manager(void)
[450cd3a]1224{
[c170438]1225 fid_t fid = fibril_create_generic(async_manager_fibril, NULL, PAGE_SIZE);
[86d7bfa]1226 if (fid != 0)
1227 fibril_add_manager(fid);
[80649a91]1228}
1229
1230/** Remove one manager from manager list */
1231void async_destroy_manager(void)
1232{
[bc1f1c2]1233 fibril_remove_manager();
[80649a91]1234}
1235
[36c9234]1236/** Initialize the async framework.
1237 *
1238 */
[49a796f1]1239void __async_server_init(void)
[80649a91]1240{
[062d900]1241 if (!hash_table_create(&client_hash_table, 0, 0, &client_hash_table_ops))
[47b7006]1242 abort();
[a35b458]1243
[062d900]1244 if (!hash_table_create(&conn_hash_table, 0, 0, &conn_hash_table_ops))
[47b7006]1245 abort();
[a35b458]1246
[8820544]1247 if (!hash_table_create(&notification_hash_table, 0, 0,
1248 &notification_hash_table_ops))
1249 abort();
[49a796f1]1250}
[a35b458]1251
[984a9ba]1252errno_t async_answer_0(ipc_call_t *call, errno_t retval)
[49a796f1]1253{
[984a9ba]1254 return ipc_answer_0(call->cap_handle, retval);
[450cd3a]1255}
[01ff41c]1256
[984a9ba]1257errno_t async_answer_1(ipc_call_t *call, errno_t retval, sysarg_t arg1)
[01ff41c]1258{
[984a9ba]1259 return ipc_answer_1(call->cap_handle, retval, arg1);
[49a796f1]1260}
[a35b458]1261
[984a9ba]1262errno_t async_answer_2(ipc_call_t *call, errno_t retval, sysarg_t arg1,
[49a796f1]1263 sysarg_t arg2)
1264{
[984a9ba]1265 return ipc_answer_2(call->cap_handle, retval, arg1, arg2);
[49a796f1]1266}
[a35b458]1267
[984a9ba]1268errno_t async_answer_3(ipc_call_t *call, errno_t retval, sysarg_t arg1,
[49a796f1]1269 sysarg_t arg2, sysarg_t arg3)
1270{
[984a9ba]1271 return ipc_answer_3(call->cap_handle, retval, arg1, arg2, arg3);
[49a796f1]1272}
[a35b458]1273
[984a9ba]1274errno_t async_answer_4(ipc_call_t *call, errno_t retval, sysarg_t arg1,
[49a796f1]1275 sysarg_t arg2, sysarg_t arg3, sysarg_t arg4)
1276{
[984a9ba]1277 return ipc_answer_4(call->cap_handle, retval, arg1, arg2, arg3, arg4);
[49a796f1]1278}
[a35b458]1279
[984a9ba]1280errno_t async_answer_5(ipc_call_t *call, errno_t retval, sysarg_t arg1,
[49a796f1]1281 sysarg_t arg2, sysarg_t arg3, sysarg_t arg4, sysarg_t arg5)
1282{
[984a9ba]1283 return ipc_answer_5(call->cap_handle, retval, arg1, arg2, arg3, arg4,
1284 arg5);
[49a796f1]1285}
[a35b458]1286
[984a9ba]1287errno_t async_forward_fast(ipc_call_t *call, async_exch_t *exch,
[49a796f1]1288 sysarg_t imethod, sysarg_t arg1, sysarg_t arg2, unsigned int mode)
1289{
[984a9ba]1290 assert(call);
1291
[49a796f1]1292 if (exch == NULL)
1293 return ENOENT;
[a35b458]1294
[984a9ba]1295 return ipc_forward_fast(call->cap_handle, exch->phone, imethod, arg1,
1296 arg2, mode);
[49a796f1]1297}
[a35b458]1298
[984a9ba]1299errno_t async_forward_slow(ipc_call_t *call, async_exch_t *exch,
[49a796f1]1300 sysarg_t imethod, sysarg_t arg1, sysarg_t arg2, sysarg_t arg3,
1301 sysarg_t arg4, sysarg_t arg5, unsigned int mode)
1302{
[984a9ba]1303 assert(call);
1304
[49a796f1]1305 if (exch == NULL)
1306 return ENOENT;
[a35b458]1307
[984a9ba]1308 return ipc_forward_slow(call->cap_handle, exch->phone, imethod, arg1,
1309 arg2, arg3, arg4, arg5, mode);
[01ff41c]1310}
1311
[49a796f1]1312/** Wrapper for making IPC_M_CONNECT_TO_ME calls using the async framework.
[36c9234]1313 *
[49a796f1]1314 * Ask through phone for a new connection to some service.
[01ff41c]1315 *
[984a9ba]1316 * @param exch Exchange for sending the message.
1317 * @param arg1 User defined argument.
1318 * @param arg2 User defined argument.
1319 * @param arg3 User defined argument.
[c07544d3]1320 *
[49a796f1]1321 * @return Zero on success or an error code.
[36c9234]1322 *
[01ff41c]1323 */
[49a796f1]1324errno_t async_connect_to_me(async_exch_t *exch, sysarg_t arg1, sysarg_t arg2,
1325 sysarg_t arg3)
[01ff41c]1326{
[79ae36dd]1327 if (exch == NULL)
[49a796f1]1328 return ENOENT;
[a35b458]1329
[49a796f1]1330 ipc_call_t answer;
1331 aid_t req = async_send_3(exch, IPC_M_CONNECT_TO_ME, arg1, arg2, arg3,
1332 &answer);
[a35b458]1333
[49a796f1]1334 errno_t rc;
1335 async_wait_for(req, &rc);
1336 if (rc != EOK)
1337 return (errno_t) rc;
[a35b458]1338
[49a796f1]1339 return EOK;
1340}
[a35b458]1341
[49a796f1]1342/** Interrupt one thread of this task from waiting for IPC. */
1343void async_poke(void)
1344{
[c8afd5a]1345 if (atomic_get(&threads_in_ipc_wait) > 0)
1346 ipc_poke();
[01ff41c]1347}
1348
[49a796f1]1349/** Wrapper for receiving the IPC_M_SHARE_IN calls using the async framework.
[0da4e41]1350 *
[47b7006]1351 * This wrapper only makes it more comfortable to receive IPC_M_SHARE_IN
1352 * calls so that the user doesn't have to remember the meaning of each IPC
1353 * argument.
[0da4e41]1354 *
1355 * So far, this wrapper is to be used from within a connection fibril.
1356 *
[984a9ba]1357 * @param call Storage for the data of the IPC_M_SHARE_IN call.
1358 * @param size Destination address space area size.
[47b7006]1359 *
1360 * @return True on success, false on failure.
[0da4e41]1361 *
1362 */
[984a9ba]1363bool async_share_in_receive(ipc_call_t *call, size_t *size)
[0da4e41]1364{
[984a9ba]1365 assert(call);
[0da4e41]1366 assert(size);
[a35b458]1367
[984a9ba]1368 async_get_call(call);
[a35b458]1369
[984a9ba]1370 if (IPC_GET_IMETHOD(*call) != IPC_M_SHARE_IN)
[47b7006]1371 return false;
[a35b458]1372
[984a9ba]1373 *size = (size_t) IPC_GET_ARG1(*call);
[47b7006]1374 return true;
[0da4e41]1375}
1376
1377/** Wrapper for answering the IPC_M_SHARE_IN calls using the async framework.
1378 *
[fbcdeb8]1379 * This wrapper only makes it more comfortable to answer IPC_M_SHARE_IN
[47b7006]1380 * calls so that the user doesn't have to remember the meaning of each IPC
1381 * argument.
[0da4e41]1382 *
[984a9ba]1383 * @param call IPC_M_DATA_READ call to answer.
1384 * @param src Source address space base.
1385 * @param flags Flags to be used for sharing. Bits can be only cleared.
[47b7006]1386 *
1387 * @return Zero on success or a value from @ref errno.h on failure.
[0da4e41]1388 *
1389 */
[984a9ba]1390errno_t async_share_in_finalize(ipc_call_t *call, void *src, unsigned int flags)
[0da4e41]1391{
[984a9ba]1392 assert(call);
1393
[a69d42e]1394 // FIXME: The source has no business deciding destination address.
[984a9ba]1395 return ipc_answer_3(call->cap_handle, EOK, (sysarg_t) src, (sysarg_t) flags,
[a69d42e]1396 (sysarg_t) _end);
[0da4e41]1397}
1398
1399/** Wrapper for receiving the IPC_M_SHARE_OUT calls using the async framework.
1400 *
[47b7006]1401 * This wrapper only makes it more comfortable to receive IPC_M_SHARE_OUT
1402 * calls so that the user doesn't have to remember the meaning of each IPC
1403 * argument.
[0da4e41]1404 *
1405 * So far, this wrapper is to be used from within a connection fibril.
1406 *
[984a9ba]1407 * @param call Storage for the data of the IPC_M_SHARE_OUT call.
1408 * @param size Storage for the source address space area size.
1409 * @param flags Storage for the sharing flags.
[47b7006]1410 *
1411 * @return True on success, false on failure.
[0da4e41]1412 *
1413 */
[984a9ba]1414bool async_share_out_receive(ipc_call_t *call, size_t *size,
[01c3bb4]1415 unsigned int *flags)
[0da4e41]1416{
[984a9ba]1417 assert(call);
[0da4e41]1418 assert(size);
1419 assert(flags);
[a35b458]1420
[984a9ba]1421 async_get_call(call);
[a35b458]1422
[984a9ba]1423 if (IPC_GET_IMETHOD(*call) != IPC_M_SHARE_OUT)
[47b7006]1424 return false;
[a35b458]1425
[984a9ba]1426 *size = (size_t) IPC_GET_ARG2(*call);
1427 *flags = (unsigned int) IPC_GET_ARG3(*call);
[47b7006]1428 return true;
[0da4e41]1429}
1430
1431/** Wrapper for answering the IPC_M_SHARE_OUT calls using the async framework.
1432 *
[47b7006]1433 * This wrapper only makes it more comfortable to answer IPC_M_SHARE_OUT
1434 * calls so that the user doesn't have to remember the meaning of each IPC
1435 * argument.
[0da4e41]1436 *
[984a9ba]1437 * @param call IPC_M_DATA_WRITE call to answer.
1438 * @param dst Address of the storage for the destination address space area
1439 * base address.
[47b7006]1440 *
[01c3bb4]1441 * @return Zero on success or a value from @ref errno.h on failure.
[0da4e41]1442 *
1443 */
[984a9ba]1444errno_t async_share_out_finalize(ipc_call_t *call, void **dst)
[0da4e41]1445{
[984a9ba]1446 assert(call);
[0da4e41]1447
[984a9ba]1448 return ipc_answer_2(call->cap_handle, EOK, (sysarg_t) _end,
1449 (sysarg_t) dst);
[d768d4c8]1450}
1451
1452/** Wrapper for receiving the IPC_M_DATA_READ calls using the async framework.
1453 *
1454 * This wrapper only makes it more comfortable to receive IPC_M_DATA_READ
1455 * calls so that the user doesn't have to remember the meaning of each IPC
1456 * argument.
1457 *
1458 * So far, this wrapper is to be used from within a connection fibril.
1459 *
[984a9ba]1460 * @param call Storage for the data of the IPC_M_DATA_READ.
1461 * @param size Storage for the maximum size. Can be NULL.
[d768d4c8]1462 *
1463 * @return True on success, false on failure.
1464 *
1465 */
[984a9ba]1466bool async_data_read_receive(ipc_call_t *call, size_t *size)
[0da4e41]1467{
[984a9ba]1468 assert(call);
[a35b458]1469
[984a9ba]1470 async_get_call(call);
[a35b458]1471
[984a9ba]1472 if (IPC_GET_IMETHOD(*call) != IPC_M_DATA_READ)
[47b7006]1473 return false;
[a35b458]1474
[0da4e41]1475 if (size)
[984a9ba]1476 *size = (size_t) IPC_GET_ARG2(*call);
[a35b458]1477
[47b7006]1478 return true;
[0da4e41]1479}
1480
1481/** Wrapper for answering the IPC_M_DATA_READ calls using the async framework.
1482 *
[47b7006]1483 * This wrapper only makes it more comfortable to answer IPC_M_DATA_READ
1484 * calls so that the user doesn't have to remember the meaning of each IPC
1485 * argument.
[0da4e41]1486 *
[984a9ba]1487 * @param call IPC_M_DATA_READ call to answer.
1488 * @param src Source address for the IPC_M_DATA_READ call.
1489 * @param size Size for the IPC_M_DATA_READ call. Can be smaller than
1490 * the maximum size announced by the sender.
[47b7006]1491 *
[01c3bb4]1492 * @return Zero on success or a value from @ref errno.h on failure.
[0da4e41]1493 *
1494 */
[984a9ba]1495errno_t async_data_read_finalize(ipc_call_t *call, const void *src, size_t size)
[0da4e41]1496{
[984a9ba]1497 assert(call);
1498
1499 return ipc_answer_2(call->cap_handle, EOK, (sysarg_t) src,
1500 (sysarg_t) size);
[0da4e41]1501}
1502
[b4cbef1]1503/** Wrapper for forwarding any read request
1504 *
1505 */
[b7fd2a0]1506errno_t async_data_read_forward_fast(async_exch_t *exch, sysarg_t imethod,
[79ae36dd]1507 sysarg_t arg1, sysarg_t arg2, sysarg_t arg3, sysarg_t arg4,
1508 ipc_call_t *dataptr)
[b4cbef1]1509{
[79ae36dd]1510 if (exch == NULL)
1511 return ENOENT;
[a35b458]1512
[984a9ba]1513 ipc_call_t call;
1514 if (!async_data_read_receive(&call, NULL)) {
1515 async_answer_0(&call, EINVAL);
[b4cbef1]1516 return EINVAL;
1517 }
[a35b458]1518
[79ae36dd]1519 aid_t msg = async_send_fast(exch, imethod, arg1, arg2, arg3, arg4,
[b4cbef1]1520 dataptr);
1521 if (msg == 0) {
[984a9ba]1522 async_answer_0(&call, EINVAL);
[b4cbef1]1523 return EINVAL;
1524 }
[a35b458]1525
[984a9ba]1526 errno_t retval = ipc_forward_fast(call.cap_handle, exch->phone, 0, 0, 0,
[b4cbef1]1527 IPC_FF_ROUTE_FROM_ME);
1528 if (retval != EOK) {
[ab9f443]1529 async_forget(msg);
[984a9ba]1530 async_answer_0(&call, retval);
[b4cbef1]1531 return retval;
1532 }
[a35b458]1533
[b7fd2a0]1534 errno_t rc;
[b4cbef1]1535 async_wait_for(msg, &rc);
[a35b458]1536
[b7fd2a0]1537 return (errno_t) rc;
[b4cbef1]1538}
1539
[0da4e41]1540/** Wrapper for receiving the IPC_M_DATA_WRITE calls using the async framework.
1541 *
[47b7006]1542 * This wrapper only makes it more comfortable to receive IPC_M_DATA_WRITE
1543 * calls so that the user doesn't have to remember the meaning of each IPC
1544 * argument.
[0da4e41]1545 *
1546 * So far, this wrapper is to be used from within a connection fibril.
1547 *
[984a9ba]1548 * @param call Storage for the data of the IPC_M_DATA_WRITE.
1549 * @param size Storage for the suggested size. May be NULL.
[5ae1c51]1550 *
1551 * @return True on success, false on failure.
1552 *
1553 */
[984a9ba]1554bool async_data_write_receive(ipc_call_t *call, size_t *size)
[0da4e41]1555{
[984a9ba]1556 assert(call);
[a35b458]1557
[984a9ba]1558 async_get_call(call);
[a35b458]1559
[984a9ba]1560 if (IPC_GET_IMETHOD(*call) != IPC_M_DATA_WRITE)
[47b7006]1561 return false;
[a35b458]1562
[0da4e41]1563 if (size)
[984a9ba]1564 *size = (size_t) IPC_GET_ARG2(*call);
[a35b458]1565
[47b7006]1566 return true;
[0da4e41]1567}
1568
1569/** Wrapper for answering the IPC_M_DATA_WRITE calls using the async framework.
1570 *
[47b7006]1571 * This wrapper only makes it more comfortable to answer IPC_M_DATA_WRITE
1572 * calls so that the user doesn't have to remember the meaning of each IPC
1573 * argument.
[0da4e41]1574 *
[984a9ba]1575 * @param call IPC_M_DATA_WRITE call to answer.
1576 * @param dst Final destination address for the IPC_M_DATA_WRITE call.
1577 * @param size Final size for the IPC_M_DATA_WRITE call.
[b4cbef1]1578 *
[01c3bb4]1579 * @return Zero on success or a value from @ref errno.h on failure.
[0da4e41]1580 *
1581 */
[984a9ba]1582errno_t async_data_write_finalize(ipc_call_t *call, void *dst, size_t size)
[0da4e41]1583{
[984a9ba]1584 assert(call);
1585
1586 return async_answer_2(call, EOK, (sysarg_t) dst, (sysarg_t) size);
[0da4e41]1587}
1588
[eda925a]1589/** Wrapper for receiving binary data or strings
[8aa42e3]1590 *
1591 * This wrapper only makes it more comfortable to use async_data_write_*
[eda925a]1592 * functions to receive binary data or strings.
[8aa42e3]1593 *
[472c09d]1594 * @param data Pointer to data pointer (which should be later disposed
1595 * by free()). If the operation fails, the pointer is not
1596 * touched.
[eda925a]1597 * @param nullterm If true then the received data is always zero terminated.
1598 * This also causes to allocate one extra byte beyond the
1599 * raw transmitted data.
[b4cbef1]1600 * @param min_size Minimum size (in bytes) of the data to receive.
[472c09d]1601 * @param max_size Maximum size (in bytes) of the data to receive. 0 means
1602 * no limit.
[eda925a]1603 * @param granulariy If non-zero then the size of the received data has to
[472c09d]1604 * be divisible by this value.
1605 * @param received If not NULL, the size of the received data is stored here.
[8aa42e3]1606 *
1607 * @return Zero on success or a value from @ref errno.h on failure.
1608 *
1609 */
[b7fd2a0]1610errno_t async_data_write_accept(void **data, const bool nullterm,
[eda925a]1611 const size_t min_size, const size_t max_size, const size_t granularity,
1612 size_t *received)
[8aa42e3]1613{
[79ae36dd]1614 assert(data);
[a35b458]1615
[984a9ba]1616 ipc_call_t call;
[8aa42e3]1617 size_t size;
[984a9ba]1618 if (!async_data_write_receive(&call, &size)) {
1619 async_answer_0(&call, EINVAL);
[8aa42e3]1620 return EINVAL;
1621 }
[a35b458]1622
[b4cbef1]1623 if (size < min_size) {
[984a9ba]1624 async_answer_0(&call, EINVAL);
[b4cbef1]1625 return EINVAL;
1626 }
[a35b458]1627
[8aa42e3]1628 if ((max_size > 0) && (size > max_size)) {
[984a9ba]1629 async_answer_0(&call, EINVAL);
[8aa42e3]1630 return EINVAL;
1631 }
[a35b458]1632
[472c09d]1633 if ((granularity > 0) && ((size % granularity) != 0)) {
[984a9ba]1634 async_answer_0(&call, EINVAL);
[472c09d]1635 return EINVAL;
1636 }
[a35b458]1637
[57dea62]1638 void *arg_data;
[a35b458]1639
[eda925a]1640 if (nullterm)
[57dea62]1641 arg_data = malloc(size + 1);
[eda925a]1642 else
[57dea62]1643 arg_data = malloc(size);
[a35b458]1644
[57dea62]1645 if (arg_data == NULL) {
[984a9ba]1646 async_answer_0(&call, ENOMEM);
[8aa42e3]1647 return ENOMEM;
1648 }
[a35b458]1649
[984a9ba]1650 errno_t rc = async_data_write_finalize(&call, arg_data, size);
[8aa42e3]1651 if (rc != EOK) {
[57dea62]1652 free(arg_data);
[8aa42e3]1653 return rc;
1654 }
[a35b458]1655
[eda925a]1656 if (nullterm)
[57dea62]1657 ((char *) arg_data)[size] = 0;
[a35b458]1658
[57dea62]1659 *data = arg_data;
[472c09d]1660 if (received != NULL)
1661 *received = size;
[a35b458]1662
[8aa42e3]1663 return EOK;
1664}
1665
[b4cbef1]1666/** Wrapper for voiding any data that is about to be received
1667 *
1668 * This wrapper can be used to void any pending data
1669 *
1670 * @param retval Error value from @ref errno.h to be returned to the caller.
1671 *
1672 */
[b7fd2a0]1673void async_data_write_void(errno_t retval)
[b4cbef1]1674{
[984a9ba]1675 ipc_call_t call;
1676 async_data_write_receive(&call, NULL);
1677 async_answer_0(&call, retval);
[b4cbef1]1678}
1679
1680/** Wrapper for forwarding any data that is about to be received
1681 *
1682 */
[b7fd2a0]1683errno_t async_data_write_forward_fast(async_exch_t *exch, sysarg_t imethod,
[79ae36dd]1684 sysarg_t arg1, sysarg_t arg2, sysarg_t arg3, sysarg_t arg4,
1685 ipc_call_t *dataptr)
[b4cbef1]1686{
[79ae36dd]1687 if (exch == NULL)
1688 return ENOENT;
[a35b458]1689
[984a9ba]1690 ipc_call_t call;
1691 if (!async_data_write_receive(&call, NULL)) {
1692 async_answer_0(&call, EINVAL);
[b4cbef1]1693 return EINVAL;
1694 }
[a35b458]1695
[79ae36dd]1696 aid_t msg = async_send_fast(exch, imethod, arg1, arg2, arg3, arg4,
[b4cbef1]1697 dataptr);
1698 if (msg == 0) {
[984a9ba]1699 async_answer_0(&call, EINVAL);
[b4cbef1]1700 return EINVAL;
1701 }
[a35b458]1702
[984a9ba]1703 errno_t retval = ipc_forward_fast(call.cap_handle, exch->phone, 0, 0, 0,
[b4cbef1]1704 IPC_FF_ROUTE_FROM_ME);
1705 if (retval != EOK) {
[ab9f443]1706 async_forget(msg);
[984a9ba]1707 async_answer_0(&call, retval);
[b4cbef1]1708 return retval;
1709 }
[a35b458]1710
[b7fd2a0]1711 errno_t rc;
[b4cbef1]1712 async_wait_for(msg, &rc);
[a35b458]1713
[b7fd2a0]1714 return (errno_t) rc;
[b4cbef1]1715}
1716
[79ae36dd]1717/** Wrapper for receiving the IPC_M_CONNECT_TO_ME calls.
1718 *
1719 * If the current call is IPC_M_CONNECT_TO_ME then a new
1720 * async session is created for the accepted phone.
1721 *
1722 * @param mgmt Exchange management style.
1723 *
[8869f7b]1724 * @return New async session.
1725 * @return NULL on failure.
[79ae36dd]1726 *
1727 */
1728async_sess_t *async_callback_receive(exch_mgmt_t mgmt)
1729{
1730 /* Accept the phone */
1731 ipc_call_t call;
[984a9ba]1732 async_get_call(&call);
1733
[eadaeae8]1734 cap_phone_handle_t phandle = (cap_handle_t) IPC_GET_ARG5(call);
[a35b458]1735
[eadaeae8]1736 if ((IPC_GET_IMETHOD(call) != IPC_M_CONNECT_TO_ME) ||
1737 !CAP_HANDLE_VALID((phandle))) {
[984a9ba]1738 async_answer_0(&call, EINVAL);
[79ae36dd]1739 return NULL;
1740 }
[a35b458]1741
[79ae36dd]1742 async_sess_t *sess = (async_sess_t *) malloc(sizeof(async_sess_t));
1743 if (sess == NULL) {
[984a9ba]1744 async_answer_0(&call, ENOMEM);
[79ae36dd]1745 return NULL;
1746 }
[a35b458]1747
[566992e1]1748 sess->iface = 0;
[79ae36dd]1749 sess->mgmt = mgmt;
[01c3bb4]1750 sess->phone = phandle;
[79ae36dd]1751 sess->arg1 = 0;
1752 sess->arg2 = 0;
1753 sess->arg3 = 0;
[a35b458]1754
[58cbf8d5]1755 fibril_mutex_initialize(&sess->remote_state_mtx);
1756 sess->remote_state_data = NULL;
[a35b458]1757
[79ae36dd]1758 list_initialize(&sess->exch_list);
1759 fibril_mutex_initialize(&sess->mutex);
1760 atomic_set(&sess->refcnt, 0);
[a35b458]1761
[79ae36dd]1762 /* Acknowledge the connected phone */
[984a9ba]1763 async_answer_0(&call, EOK);
[a35b458]1764
[79ae36dd]1765 return sess;
1766}
1767
[8869f7b]1768/** Wrapper for receiving the IPC_M_CONNECT_TO_ME calls.
1769 *
1770 * If the call is IPC_M_CONNECT_TO_ME then a new
1771 * async session is created. However, the phone is
1772 * not accepted automatically.
1773 *
1774 * @param mgmt Exchange management style.
1775 * @param call Call data.
1776 *
1777 * @return New async session.
1778 * @return NULL on failure.
1779 * @return NULL if the call is not IPC_M_CONNECT_TO_ME.
1780 *
1781 */
1782async_sess_t *async_callback_receive_start(exch_mgmt_t mgmt, ipc_call_t *call)
1783{
[eadaeae8]1784 cap_phone_handle_t phandle = (cap_handle_t) IPC_GET_ARG5(*call);
[a35b458]1785
[eadaeae8]1786 if ((IPC_GET_IMETHOD(*call) != IPC_M_CONNECT_TO_ME) ||
1787 !CAP_HANDLE_VALID((phandle)))
[8869f7b]1788 return NULL;
[a35b458]1789
[8869f7b]1790 async_sess_t *sess = (async_sess_t *) malloc(sizeof(async_sess_t));
1791 if (sess == NULL)
1792 return NULL;
[a35b458]1793
[566992e1]1794 sess->iface = 0;
[8869f7b]1795 sess->mgmt = mgmt;
[01c3bb4]1796 sess->phone = phandle;
[8869f7b]1797 sess->arg1 = 0;
1798 sess->arg2 = 0;
1799 sess->arg3 = 0;
[a35b458]1800
[58cbf8d5]1801 fibril_mutex_initialize(&sess->remote_state_mtx);
1802 sess->remote_state_data = NULL;
[a35b458]1803
[8869f7b]1804 list_initialize(&sess->exch_list);
1805 fibril_mutex_initialize(&sess->mutex);
1806 atomic_set(&sess->refcnt, 0);
[a35b458]1807
[8869f7b]1808 return sess;
1809}
1810
[984a9ba]1811bool async_state_change_receive(ipc_call_t *call)
[2c4aa39]1812{
[984a9ba]1813 assert(call);
[a35b458]1814
[984a9ba]1815 async_get_call(call);
[a35b458]1816
[984a9ba]1817 if (IPC_GET_IMETHOD(*call) != IPC_M_STATE_CHANGE_AUTHORIZE)
[2c4aa39]1818 return false;
[a35b458]1819
[2c4aa39]1820 return true;
1821}
1822
[984a9ba]1823errno_t async_state_change_finalize(ipc_call_t *call, async_exch_t *other_exch)
[2c4aa39]1824{
[984a9ba]1825 assert(call);
1826
1827 return async_answer_1(call, EOK, CAP_HANDLE_RAW(other_exch->phone));
[2c4aa39]1828}
1829
[6b96dc06]1830__noreturn void async_manager(void)
[d73d992]1831{
[95838f1]1832 futex_lock(&async_futex);
[d73d992]1833 fibril_switch(FIBRIL_FROM_DEAD);
1834 __builtin_unreachable();
1835}
1836
[a46da63]1837/** @}
[b2951e2]1838 */
Note: See TracBrowser for help on using the repository browser.