source: mainline/uspace/srv/devman/main.c@ 0b5a4131

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

Rename device_handle_t to devman_handle_t and make it explicitly clear that
device_handle_t is a handle understood by devman.

  • Property mode set to 100644
File size: 15.6 KB
RevLine 
[e2b9a993]1/*
2 * Copyright (c) 2010 Lenka Trochtova
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/**
30 * @defgroup devman Device manager.
31 * @brief HelenOS device manager.
32 * @{
33 */
34
35/** @file
36 */
37
38#include <assert.h>
39#include <ipc/services.h>
40#include <ipc/ns.h>
41#include <async.h>
42#include <stdio.h>
43#include <errno.h>
44#include <bool.h>
45#include <fibril_synch.h>
46#include <stdlib.h>
[c47e1a8]47#include <str.h>
[e2b9a993]48#include <dirent.h>
49#include <fcntl.h>
50#include <sys/stat.h>
51#include <ctype.h>
52#include <ipc/devman.h>
[5cd136ab]53#include <ipc/driver.h>
[d347b53]54#include <thread.h>
[ce89036b]55#include <devmap.h>
[e2b9a993]56
57#include "devman.h"
58
[a79d88d]59#define DRIVER_DEFAULT_STORE "/drv"
[e2b9a993]60
[0c3666d]61static driver_list_t drivers_list;
[e2b9a993]62static dev_tree_t device_tree;
[692c40cb]63static class_list_t class_list;
[e2b9a993]64
[38b3baf]65/** Register running driver. */
66static driver_t *devman_driver_register(void)
67{
[729fa2d6]68 ipc_call_t icall;
[c6c389ed]69 ipc_callid_t iid;
[729fa2d6]70 driver_t *driver = NULL;
[c6c389ed]71
72 printf(NAME ": devman_driver_register \n");
[729fa2d6]73
[c6c389ed]74 iid = async_get_call(&icall);
[729fa2d6]75 if (IPC_GET_METHOD(icall) != DEVMAN_DRIVER_REGISTER) {
76 ipc_answer_0(iid, EREFUSED);
77 return NULL;
78 }
79
[92413de]80 char *drv_name = NULL;
[729fa2d6]81
[38b3baf]82 /* Get driver name. */
83 int rc = async_data_write_accept((void **) &drv_name, true, 0, 0, 0, 0);
[729fa2d6]84 if (rc != EOK) {
85 ipc_answer_0(iid, rc);
86 return NULL;
87 }
[c6c389ed]88
[38b3baf]89 printf(NAME ": the %s driver is trying to register by the service.\n",
90 drv_name);
[729fa2d6]91
[38b3baf]92 /* Find driver structure. */
[729fa2d6]93 driver = find_driver(&drivers_list, drv_name);
[92413de]94
[c6c389ed]95 if (driver == NULL) {
[729fa2d6]96 printf(NAME ": no driver named %s was found.\n", drv_name);
[04c7003f]97 free(drv_name);
98 drv_name = NULL;
[729fa2d6]99 ipc_answer_0(iid, ENOENT);
100 return NULL;
101 }
102
[04c7003f]103 free(drv_name);
104 drv_name = NULL;
105
[38b3baf]106 /* Create connection to the driver. */
107 printf(NAME ": creating connection to the %s driver.\n", driver->name);
[729fa2d6]108 ipc_call_t call;
[38b3baf]109 ipc_callid_t callid = async_get_call(&call);
[729fa2d6]110 if (IPC_GET_METHOD(call) != IPC_M_CONNECT_TO_ME) {
111 ipc_answer_0(callid, ENOTSUP);
112 ipc_answer_0(iid, ENOTSUP);
113 return NULL;
114 }
115
[38b3baf]116 /* Remember driver's phone. */
[c16cf62]117 set_driver_phone(driver, IPC_GET_ARG5(call));
[729fa2d6]118
[38b3baf]119 printf(NAME ": the %s driver was successfully registered as running.\n",
120 driver->name);
[729fa2d6]121
[38b3baf]122 ipc_answer_0(callid, EOK);
[729fa2d6]123 ipc_answer_0(iid, EOK);
124
125 return driver;
126}
[e2b9a993]127
[38b3baf]128/** Receive device match ID from the device's parent driver and add it to the
129 * list of devices match ids.
130 *
131 * @param match_ids The list of the device's match ids.
132 * @return Zero on success, negative error code otherwise.
[3843ecb]133 */
[38b3baf]134static int devman_receive_match_id(match_id_list_t *match_ids)
135{
[66babbd]136 match_id_t *match_id = create_match_id();
137 ipc_callid_t callid;
138 ipc_call_t call;
139 int rc = 0;
140
141 callid = async_get_call(&call);
142 if (DEVMAN_ADD_MATCH_ID != IPC_GET_METHOD(call)) {
[38b3baf]143 printf(NAME ": ERROR: devman_receive_match_id - invalid "
144 "protocol.\n");
[66babbd]145 ipc_answer_0(callid, EINVAL);
146 delete_match_id(match_id);
147 return EINVAL;
148 }
149
[c6c389ed]150 if (match_id == NULL) {
[38b3baf]151 printf(NAME ": ERROR: devman_receive_match_id - failed to "
152 "allocate match id.\n");
[66babbd]153 ipc_answer_0(callid, ENOMEM);
154 return ENOMEM;
155 }
156
[2480e19]157 ipc_answer_0(callid, EOK);
158
[66babbd]159 match_id->score = IPC_GET_ARG1(call);
160
[c47e1a8]161 char *match_id_str;
[38b3baf]162 rc = async_data_write_accept((void **) &match_id_str, true, 0, 0, 0, 0);
[c47e1a8]163 match_id->id = match_id_str;
[c6c389ed]164 if (rc != EOK) {
[66babbd]165 delete_match_id(match_id);
[38b3baf]166 printf(NAME ": devman_receive_match_id - failed to receive "
167 "match id string.\n");
[66babbd]168 return rc;
169 }
170
171 list_append(&match_id->link, &match_ids->ids);
[a087f2e]172
[38b3baf]173 printf(NAME ": received match id '%s', score = %d \n",
174 match_id->id, match_id->score);
[66babbd]175 return rc;
176}
177
[38b3baf]178/** Receive device match IDs from the device's parent driver and add them to the
179 * list of devices match ids.
180 *
181 * @param match_count The number of device's match ids to be received.
182 * @param match_ids The list of the device's match ids.
183 * @return Zero on success, negative error code otherwise.
[3843ecb]184 */
[c6c389ed]185static int devman_receive_match_ids(ipcarg_t match_count,
186 match_id_list_t *match_ids)
[38b3baf]187{
[66babbd]188 int ret = EOK;
[5cd136ab]189 size_t i;
[38b3baf]190
[66babbd]191 for (i = 0; i < match_count; i++) {
[38b3baf]192 if (EOK != (ret = devman_receive_match_id(match_ids)))
[66babbd]193 return ret;
194 }
195 return ret;
196}
197
[38b3baf]198/** Handle child device registration.
199 *
[3843ecb]200 * Child devices are registered by their parent's device driver.
201 */
202static void devman_add_child(ipc_callid_t callid, ipc_call_t *call)
[bda60d9]203{
[0b5a4131]204 devman_handle_t parent_handle = IPC_GET_ARG1(*call);
[66babbd]205 ipcarg_t match_count = IPC_GET_ARG2(*call);
[957cfa58]206 dev_tree_t *tree = &device_tree;
[66babbd]207
[957cfa58]208 fibril_rwlock_write_lock(&tree->rwlock);
[c6c389ed]209 node_t *parent = find_dev_node_no_lock(&device_tree, parent_handle);
[d347b53]210
[c6c389ed]211 if (parent == NULL) {
[957cfa58]212 fibril_rwlock_write_unlock(&tree->rwlock);
[d347b53]213 ipc_answer_0(callid, ENOENT);
214 return;
[c6c389ed]215 }
[d347b53]216
217 char *dev_name = NULL;
[38b3baf]218 int rc = async_data_write_accept((void **)&dev_name, true, 0, 0, 0, 0);
[c6c389ed]219 if (rc != EOK) {
[957cfa58]220 fibril_rwlock_write_unlock(&tree->rwlock);
[d347b53]221 ipc_answer_0(callid, rc);
222 return;
223 }
224
225 node_t *node = create_dev_node();
226 if (!insert_dev_node(&device_tree, node, dev_name, parent)) {
[957cfa58]227 fibril_rwlock_write_unlock(&tree->rwlock);
[d347b53]228 delete_dev_node(node);
229 ipc_answer_0(callid, ENOMEM);
230 return;
[c6c389ed]231 }
232
[957cfa58]233 fibril_rwlock_write_unlock(&tree->rwlock);
[d347b53]234
[2480e19]235 printf(NAME ": devman_add_child %s\n", node->pathname);
236
[66babbd]237 devman_receive_match_ids(match_count, &node->match_ids);
[bda60d9]238
[38b3baf]239 /* Return device handle to parent's driver. */
[d347b53]240 ipc_answer_1(callid, EOK, node->handle);
[bda60d9]241
[38b3baf]242 /* Try to find suitable driver and assign it to the device. */
243 assign_driver(node, &drivers_list, &device_tree);
[d347b53]244}
245
[ce89036b]246static void devmap_register_class_dev(dev_class_info_t *cli)
247{
[38b3baf]248 /* Create devmap path and name for the device. */
[ce89036b]249 char *devmap_pathname = NULL;
[c6c389ed]250
[38b3baf]251 asprintf(&devmap_pathname, "%s/%s%c%s", DEVMAP_CLASS_NAMESPACE,
252 cli->dev_class->name, DEVMAP_SEPARATOR, cli->dev_name);
[c6c389ed]253 if (devmap_pathname == NULL)
[ce89036b]254 return;
255
[38b3baf]256 /*
257 * Register the device by the device mapper and remember its devmap
258 * handle.
259 */
260 devmap_device_register(devmap_pathname, &cli->devmap_handle);
[ce89036b]261
[38b3baf]262 /*
263 * Add device to the hash map of class devices registered by device
264 * mapper.
265 */
[a32defa]266 class_add_devmap_device(&class_list, cli);
267
[38b3baf]268 free(devmap_pathname);
[ce89036b]269}
270
[692c40cb]271static void devman_add_device_to_class(ipc_callid_t callid, ipc_call_t *call)
[ce89036b]272{
[0b5a4131]273 devman_handle_t handle = IPC_GET_ARG1(*call);
[692c40cb]274
[38b3baf]275 /* Get class name. */
[692c40cb]276 char *class_name;
[38b3baf]277 int rc = async_data_write_accept((void **) &class_name, true,
278 0, 0, 0, 0);
[692c40cb]279 if (rc != EOK) {
280 ipc_answer_0(callid, rc);
281 return;
282 }
283
284 node_t *dev = find_dev_node(&device_tree, handle);
[c6c389ed]285 if (dev == NULL) {
[692c40cb]286 ipc_answer_0(callid, ENOENT);
287 return;
288 }
289
290 dev_class_t *cl = get_dev_class(&class_list, class_name);
291 dev_class_info_t *class_info = add_device_to_class(dev, cl, NULL);
292
[38b3baf]293 /* Register the device's class alias by devmapper. */
[ce89036b]294 devmap_register_class_dev(class_info);
[692c40cb]295
[38b3baf]296 printf(NAME ": device '%s' added to class '%s', class name '%s' was "
297 "asigned to it\n", dev->pathname, class_name, class_info->dev_name);
[692c40cb]298
[38b3baf]299 ipc_answer_0(callid, EOK);
[692c40cb]300}
301
[38b3baf]302/** Initialize driver which has registered itself as running and ready.
303 *
304 * The initialization is done in a separate fibril to avoid deadlocks (if the
305 * driver needed to be served by devman during the driver's initialization).
[3843ecb]306 */
[d347b53]307static int init_running_drv(void *drv)
308{
[38b3baf]309 driver_t *driver = (driver_t *) drv;
310
311 initialize_running_driver(driver, &device_tree);
312 printf(NAME ": the %s driver was successfully initialized. \n",
313 driver->name);
[d347b53]314 return 0;
[bda60d9]315}
316
[38b3baf]317/** Function for handling connections from a driver to the device manager. */
[924c75e1]318static void devman_connection_driver(ipc_callid_t iid, ipc_call_t *icall)
[38b3baf]319{
320 /* Accept the connection. */
[e2b9a993]321 ipc_answer_0(iid, EOK);
322
[729fa2d6]323 driver_t *driver = devman_driver_register();
[c6c389ed]324 if (driver == NULL)
[729fa2d6]325 return;
[d347b53]326
[38b3baf]327 /*
328 * Initialize the driver as running (e.g. pass assigned devices to it)
329 * in a separate fibril; the separate fibril is used to enable the
330 * driver to use devman service during the driver's initialization.
331 */
[d347b53]332 fid_t fid = fibril_create(init_running_drv, driver);
333 if (fid == 0) {
[38b3baf]334 printf(NAME ": Error creating fibril for the initialization of "
335 "the newly registered running driver.\n");
[3843ecb]336 return;
[d347b53]337 }
338 fibril_add_ready(fid);
339
[729fa2d6]340 ipc_callid_t callid;
341 ipc_call_t call;
342 bool cont = true;
343 while (cont) {
[e2b9a993]344 callid = async_get_call(&call);
345
346 switch (IPC_GET_METHOD(call)) {
[729fa2d6]347 case IPC_M_PHONE_HUNGUP:
348 cont = false;
349 continue;
[e2b9a993]350 case DEVMAN_ADD_CHILD_DEVICE:
[3843ecb]351 devman_add_child(callid, &call);
[e2b9a993]352 break;
[692c40cb]353 case DEVMAN_ADD_DEVICE_TO_CLASS:
354 devman_add_device_to_class(callid, &call);
355 break;
[e2b9a993]356 default:
357 ipc_answer_0(callid, EINVAL);
358 break;
359 }
360 }
361}
362
[38b3baf]363/** Find handle for the device instance identified by the device's path in the
364 * device tree. */
[f658458]365static void devman_device_get_handle(ipc_callid_t iid, ipc_call_t *icall)
366{
[38b3baf]367 char *pathname;
368
369 int rc = async_data_write_accept((void **) &pathname, true, 0, 0, 0, 0);
[f658458]370 if (rc != EOK) {
371 ipc_answer_0(iid, rc);
372 return;
373 }
374
[38b3baf]375 node_t * dev = find_dev_node_by_path(&device_tree, pathname);
[f658458]376
377 free(pathname);
378
[c6c389ed]379 if (dev == NULL) {
[f658458]380 ipc_answer_0(iid, ENOENT);
381 return;
382 }
383
384 ipc_answer_1(iid, EOK, dev->handle);
385}
386
387
[38b3baf]388/** Function for handling connections from a client to the device manager. */
[f658458]389static void devman_connection_client(ipc_callid_t iid, ipc_call_t *icall)
390{
[38b3baf]391 /* Accept connection. */
[f658458]392 ipc_answer_0(iid, EOK);
393
394 bool cont = true;
395 while (cont) {
396 ipc_call_t call;
397 ipc_callid_t callid = async_get_call(&call);
398
399 switch (IPC_GET_METHOD(call)) {
400 case IPC_M_PHONE_HUNGUP:
401 cont = false;
402 continue;
403 case DEVMAN_DEVICE_GET_HANDLE:
404 devman_device_get_handle(callid, &call);
405 break;
406 default:
407 if (!(callid & IPC_CALLID_NOTIFICATION))
408 ipc_answer_0(callid, ENOENT);
409 }
[c6c389ed]410 }
[f658458]411}
412
[c6c389ed]413static void devman_forward(ipc_callid_t iid, ipc_call_t *icall,
414 bool drv_to_parent)
[38b3baf]415{
[0b5a4131]416 devman_handle_t handle = IPC_GET_ARG2(*icall);
[9a66bc2e]417
[5cd136ab]418 node_t *dev = find_dev_node(&device_tree, handle);
[c6c389ed]419 if (dev == NULL) {
[38b3baf]420 printf(NAME ": devman_forward error - no device with handle %x "
421 "was found.\n", handle);
[5cd136ab]422 ipc_answer_0(iid, ENOENT);
423 return;
424 }
425
426 driver_t *driver = NULL;
427
428 if (drv_to_parent) {
[c6c389ed]429 if (dev->parent != NULL)
[38b3baf]430 driver = dev->parent->drv;
[c6c389ed]431 } else if (dev->state == DEVICE_USABLE) {
[38b3baf]432 driver = dev->drv;
[c6c389ed]433 assert(driver != NULL);
[5cd136ab]434 }
435
[c6c389ed]436 if (driver == NULL) {
[38b3baf]437 printf(NAME ": devman_forward error - the device is not in "
438 "usable state.\n", handle);
[5cd136ab]439 ipc_answer_0(iid, ENOENT);
[38b3baf]440 return;
[5cd136ab]441 }
442
[38b3baf]443 int method;
444 if (drv_to_parent)
[5cd136ab]445 method = DRIVER_DRIVER;
[38b3baf]446 else
[5cd136ab]447 method = DRIVER_CLIENT;
448
[9a66bc2e]449 if (driver->phone <= 0) {
[38b3baf]450 printf(NAME ": devman_forward: cound not forward to driver %s ",
451 driver->name);
[3843ecb]452 printf("the driver's phone is %x).\n", driver->phone);
[ce89036b]453 ipc_answer_0(iid, EINVAL);
[9a66bc2e]454 return;
455 }
[c6c389ed]456
[38b3baf]457 printf(NAME ": devman_forward: forward connection to device %s to "
458 "driver %s.\n", dev->pathname, driver->name);
459 ipc_forward_fast(iid, driver->phone, method, dev->handle, 0, IPC_FF_NONE);
[5cd136ab]460}
461
[38b3baf]462/** Function for handling connections from a client forwarded by the device
463 * mapper to the device manager. */
[ce89036b]464static void devman_connection_devmapper(ipc_callid_t iid, ipc_call_t *icall)
465{
[991f645]466 devmap_handle_t devmap_handle = IPC_GET_METHOD(*icall);
[c6c389ed]467 node_t *dev;
468
469 dev = find_devmap_tree_device(&device_tree, devmap_handle);
470 if (dev == NULL)
[ce89036b]471 dev = find_devmap_class_device(&class_list, devmap_handle);
472
[c6c389ed]473 if (dev == NULL || dev->drv == NULL) {
[ce89036b]474 ipc_answer_0(iid, ENOENT);
475 return;
476 }
477
[c6c389ed]478 if (dev->state != DEVICE_USABLE || dev->drv->phone <= 0) {
[ce89036b]479 ipc_answer_0(iid, EINVAL);
480 return;
481 }
482
[38b3baf]483 printf(NAME ": devman_connection_devmapper: forward connection to "
484 "device %s to driver %s.\n", dev->pathname, dev->drv->name);
485 ipc_forward_fast(iid, dev->drv->phone, DRIVER_CLIENT, dev->handle, 0,
486 IPC_FF_NONE);
[ce89036b]487}
488
[38b3baf]489/** Function for handling connections to device manager. */
[924c75e1]490static void devman_connection(ipc_callid_t iid, ipc_call_t *icall)
[38b3baf]491{
492 /*
493 * Silly hack to enable the device manager to register as a driver by
494 * the device mapper. If the ipc method is not IPC_M_CONNECT_ME_TO, this
495 * is not the forwarded connection from naming service, so it must be a
496 * connection from the devmapper which thinks this is a devmapper-style
497 * driver. So pretend this is a devmapper-style driver. (This does not
498 * work for device with handle == IPC_M_CONNECT_ME_TO, because devmapper
499 * passes device handle to the driver as an ipc method.)
500 */
[c6c389ed]501 if (IPC_GET_METHOD(*icall) != IPC_M_CONNECT_ME_TO)
[ce89036b]502 devman_connection_devmapper(iid, icall);
503
[38b3baf]504 /*
505 * ipc method is IPC_M_CONNECT_ME_TO, so this is forwarded connection
506 * from naming service by which we registered as device manager, so be
507 * device manager.
508 */
[ce89036b]509
[38b3baf]510 /* Select interface. */
[924c75e1]511 switch ((ipcarg_t) (IPC_GET_ARG1(*icall))) {
512 case DEVMAN_DRIVER:
513 devman_connection_driver(iid, icall);
514 break;
[f658458]515 case DEVMAN_CLIENT:
516 devman_connection_client(iid, icall);
517 break;
[924c75e1]518 case DEVMAN_CONNECT_TO_DEVICE:
[38b3baf]519 /* Connect client to selected device. */
[5cd136ab]520 devman_forward(iid, icall, false);
521 break;
522 case DEVMAN_CONNECT_TO_PARENTS_DEVICE:
[38b3baf]523 /* Connect client to selected device. */
[5cd136ab]524 devman_forward(iid, icall, true);
[38b3baf]525 break;
[924c75e1]526 default:
527 /* No such interface */
528 ipc_answer_0(iid, ENOENT);
529 }
530}
531
[38b3baf]532/** Initialize device manager internal structures. */
533static bool devman_init(void)
[e2b9a993]534{
[38b3baf]535 printf(NAME ": devman_init - looking for available drivers.\n");
[08d9c4e6]536
[38b3baf]537 /* Initialize list of available drivers. */
[0c3666d]538 init_driver_list(&drivers_list);
[c6c389ed]539 if (lookup_available_drivers(&drivers_list,
540 DRIVER_DEFAULT_STORE) == 0) {
[e2b9a993]541 printf(NAME " no drivers found.");
542 return false;
543 }
[c6c389ed]544
[38b3baf]545 printf(NAME ": devman_init - list of drivers has been initialized.\n");
[e2b9a993]546
[38b3baf]547 /* Create root device node. */
[e2b9a993]548 if (!init_device_tree(&device_tree, &drivers_list)) {
549 printf(NAME " failed to initialize device tree.");
[38b3baf]550 return false;
[e2b9a993]551 }
552
[692c40cb]553 init_class_list(&class_list);
554
[38b3baf]555 /*
556 * !!! devman_connection ... as the device manager is not a real devmap
557 * driver (it uses a completely different ipc protocol than an ordinary
558 * devmap driver) forwarding a connection from client to the devman by
559 * devmapper would not work.
560 */
561 devmap_driver_register(NAME, devman_connection);
[ce89036b]562
[e2b9a993]563 return true;
564}
565
566int main(int argc, char *argv[])
567{
568 printf(NAME ": HelenOS Device Manager\n");
569
570 if (!devman_init()) {
571 printf(NAME ": Error while initializing service\n");
572 return -1;
573 }
574
[38b3baf]575 /* Set a handler of incomming connections. */
[e2b9a993]576 async_set_client_connection(devman_connection);
577
[38b3baf]578 /* Register device manager at naming service. */
[e2b9a993]579 ipcarg_t phonead;
580 if (ipc_connect_to_me(PHONE_NS, SERVICE_DEVMAN, 0, 0, &phonead) != 0)
581 return -1;
582
583 printf(NAME ": Accepting connections\n");
584 async_manager();
585
[38b3baf]586 /* Never reached. */
[e2b9a993]587 return 0;
588}
[c16cf62]589
590/** @}
[38b3baf]591 */
Note: See TracBrowser for help on using the repository browser.