Ignore:
File:
1 edited

Legend:

Unmodified
Added
Removed
  • uspace/srv/devman/main.c

    r3ad7b1c r655cc56  
    3939#include <assert.h>
    4040#include <ipc/services.h>
    41 #include <ipc/ns.h>
     41#include <ns.h>
    4242#include <async.h>
    4343#include <stdio.h>
     
    5656#include <ipc/driver.h>
    5757#include <thread.h>
    58 #include <devmap.h>
     58#include <loc.h>
    5959
    6060#include "devman.h"
     
    6464static driver_list_t drivers_list;
    6565static dev_tree_t device_tree;
    66 static class_list_t class_list;
     66
     67static int init_running_drv(void *drv);
    6768
    6869/** Register running driver. */
    69 static driver_t *devman_driver_register(void)
    70 {
    71         ipc_call_t icall;
    72         ipc_callid_t iid;
     70static driver_t *devman_driver_register(ipc_callid_t callid, ipc_call_t *call)
     71{
    7372        driver_t *driver = NULL;
     73        char *drv_name = NULL;
    7474
    7575        log_msg(LVL_DEBUG, "devman_driver_register");
    76        
    77         iid = async_get_call(&icall);
    78         if (IPC_GET_IMETHOD(icall) != DEVMAN_DRIVER_REGISTER) {
    79                 async_answer_0(iid, EREFUSED);
    80                 return NULL;
    81         }
    82        
    83         char *drv_name = NULL;
    8476       
    8577        /* Get driver name. */
    8678        int rc = async_data_write_accept((void **) &drv_name, true, 0, 0, 0, 0);
    8779        if (rc != EOK) {
    88                 async_answer_0(iid, rc);
     80                async_answer_0(callid, rc);
    8981                return NULL;
    9082        }
     
    9991                free(drv_name);
    10092                drv_name = NULL;
    101                 async_answer_0(iid, ENOENT);
     93                async_answer_0(callid, ENOENT);
    10294                return NULL;
    10395        }
     
    108100        fibril_mutex_lock(&driver->driver_mutex);
    109101       
    110         if (driver->phone >= 0) {
     102        if (driver->sess) {
    111103                /* We already have a connection to the driver. */
    112104                log_msg(LVL_ERROR, "Driver '%s' already started.\n",
    113105                    driver->name);
    114106                fibril_mutex_unlock(&driver->driver_mutex);
    115                 async_answer_0(iid, EEXISTS);
     107                async_answer_0(callid, EEXISTS);
    116108                return NULL;
    117109        }
     
    128120                break;
    129121        case DRIVER_RUNNING:
    130                 /* Should not happen since we do not have a connected phone */
     122                /* Should not happen since we do not have a connected session */
    131123                assert(false);
    132124        }
     
    135127        log_msg(LVL_DEBUG, "Creating connection to the `%s' driver.",
    136128            driver->name);
    137         ipc_call_t call;
    138         ipc_callid_t callid = async_get_call(&call);
    139         if (IPC_GET_IMETHOD(call) != IPC_M_CONNECT_TO_ME) {
     129        driver->sess = async_callback_receive(EXCHANGE_PARALLEL);
     130        if (!driver->sess) {
    140131                fibril_mutex_unlock(&driver->driver_mutex);
    141132                async_answer_0(callid, ENOTSUP);
    142                 async_answer_0(iid, ENOTSUP);
    143133                return NULL;
    144134        }
    145        
    146         /* Remember driver's phone. */
    147         driver->phone = IPC_GET_ARG5(call);
    148        
    149         fibril_mutex_unlock(&driver->driver_mutex);
    150        
    151         log_msg(LVL_NOTE,
     135        /* FIXME: Work around problem with callback sessions */
     136        async_sess_args_set(driver->sess, DRIVER_DEVMAN, 0, 0);
     137       
     138        log_msg(LVL_NOTE,
    152139            "The `%s' driver was successfully registered as running.",
    153140            driver->name);
    154141       
     142        /*
     143         * Initialize the driver as running (e.g. pass assigned devices to it)
     144         * in a separate fibril; the separate fibril is used to enable the
     145         * driver to use devman service during the driver's initialization.
     146         */
     147        fid_t fid = fibril_create(init_running_drv, driver);
     148        if (fid == 0) {
     149                log_msg(LVL_ERROR, "Failed to create initialization fibril " \
     150                    "for driver `%s'.", driver->name);
     151                fibril_mutex_unlock(&driver->driver_mutex);
     152                async_answer_0(callid, ENOMEM);
     153                return NULL;
     154        }
     155       
     156        fibril_add_ready(fid);
     157        fibril_mutex_unlock(&driver->driver_mutex);
     158       
    155159        async_answer_0(callid, EOK);
    156         async_answer_0(iid, EOK);
    157        
    158160        return driver;
    159161}
     
    232234        dev_node_t *dev_node = (dev_node_t *) arg;
    233235        assign_driver(dev_node, &drivers_list, &device_tree);
     236
     237        /* Delete one reference we got from the caller. */
     238        dev_del_ref(dev_node);
    234239        return EOK;
    235240}
    236241
    237 /** Handle function registration.
    238  *
    239  * Child devices are registered by their parent's device driver.
    240  */
    241 static void devman_add_function(ipc_callid_t callid, ipc_call_t *call)
    242 {
    243         fun_type_t ftype = (fun_type_t) IPC_GET_ARG1(*call);
    244         devman_handle_t dev_handle = IPC_GET_ARG2(*call);
    245         sysarg_t match_count = IPC_GET_ARG3(*call);
    246         dev_tree_t *tree = &device_tree;
    247        
    248         fibril_rwlock_write_lock(&tree->rwlock);
    249 
    250         dev_node_t *dev = NULL;
    251         dev_node_t *pdev = find_dev_node_no_lock(&device_tree, dev_handle);
    252        
    253         if (pdev == NULL) {
    254                 fibril_rwlock_write_unlock(&tree->rwlock);
    255                 async_answer_0(callid, ENOENT);
    256                 return;
    257         }
    258        
    259         if (ftype != fun_inner && ftype != fun_exposed) {
    260                 /* Unknown function type */
    261                 log_msg(LVL_ERROR,
    262                     "Unknown function type %d provided by driver.",
    263                     (int) ftype);
    264 
    265                 fibril_rwlock_write_unlock(&tree->rwlock);
    266                 async_answer_0(callid, EINVAL);
    267                 return;
    268         }
    269        
    270         char *fun_name = NULL;
    271         int rc = async_data_write_accept((void **)&fun_name, true, 0, 0, 0, 0);
    272         if (rc != EOK) {
    273                 fibril_rwlock_write_unlock(&tree->rwlock);
    274                 async_answer_0(callid, rc);
    275                 return;
    276         }
    277        
    278         /* Check that function with same name is not there already. */
    279         if (find_fun_node_in_device(pdev, fun_name) != NULL) {
    280                 fibril_rwlock_write_unlock(&tree->rwlock);
    281                 async_answer_0(callid, EEXISTS);
    282                 printf(NAME ": Warning, driver tried to register `%s' twice.\n",
    283                     fun_name);
    284                 free(fun_name);
    285                 return;
    286         }
    287 
    288         fun_node_t *fun = create_fun_node();
    289         if (!insert_fun_node(&device_tree, fun, fun_name, pdev)) {
    290                 fibril_rwlock_write_unlock(&tree->rwlock);
    291                 delete_fun_node(fun);
    292                 async_answer_0(callid, ENOMEM);
    293                 return;
    294         }
    295 
    296         if (ftype == fun_inner) {
     242static int online_function(fun_node_t *fun)
     243{
     244        dev_node_t *dev;
     245       
     246        fibril_rwlock_write_lock(&device_tree.rwlock);
     247
     248        if (fun->state == FUN_ON_LINE) {
     249                fibril_rwlock_write_unlock(&device_tree.rwlock);
     250                log_msg(LVL_WARN, "Function %s is already on line.",
     251                    fun->pathname);
     252                return EOK;
     253        }
     254       
     255        if (fun->ftype == fun_inner) {
    297256                dev = create_dev_node();
    298257                if (dev == NULL) {
    299                         fibril_rwlock_write_unlock(&tree->rwlock);
    300                         delete_fun_node(fun);
    301                         async_answer_0(callid, ENOMEM);
    302                         return;
     258                        fibril_rwlock_write_unlock(&device_tree.rwlock);
     259                        return ENOMEM;
    303260                }
    304261
    305                 insert_dev_node(tree, dev, fun);
    306         }
    307 
    308         fibril_rwlock_write_unlock(&tree->rwlock);
     262                insert_dev_node(&device_tree, dev, fun);
     263                dev_add_ref(dev);
     264        }
    309265       
    310266        log_msg(LVL_DEBUG, "devman_add_function(fun=\"%s\")", fun->pathname);
    311267       
    312         devman_receive_match_ids(match_count, &fun->match_ids);
    313 
    314         if (ftype == fun_inner) {
     268        if (fun->ftype == fun_inner) {
     269                dev = fun->child;
    315270                assert(dev != NULL);
     271               
     272                /* Give one reference over to assign_driver_fibril(). */
     273                dev_add_ref(dev);
    316274                /*
    317275                 * Try to find a suitable driver and assign it to the device.  We do
     
    323281                fid_t assign_fibril = fibril_create(assign_driver_fibril, dev);
    324282                if (assign_fibril == 0) {
    325                         /*
    326                          * Fallback in case we are out of memory.
    327                          * Probably not needed as we will die soon anyway ;-).
    328                          */
    329                         (void) assign_driver_fibril(fun);
    330                 } else {
    331                         fibril_add_ready(assign_fibril);
     283                        log_msg(LVL_ERROR, "Failed to create fibril for "
     284                            "assigning driver.");
     285                        /* XXX Cleanup */
     286                        fibril_rwlock_write_unlock(&device_tree.rwlock);
     287                        return ENOMEM;
     288                }
     289                fibril_add_ready(assign_fibril);
     290        } else {
     291                loc_register_tree_function(fun, &device_tree);
     292        }
     293       
     294        fibril_rwlock_write_unlock(&device_tree.rwlock);
     295       
     296        return EOK;
     297}
     298
     299static int offline_function(fun_node_t *fun)
     300{
     301        int rc;
     302       
     303        fibril_rwlock_write_lock(&device_tree.rwlock);
     304       
     305        if (fun->state == FUN_OFF_LINE) {
     306                fibril_rwlock_write_unlock(&device_tree.rwlock);
     307                log_msg(LVL_WARN, "Function %s is already off line.",
     308                    fun->pathname);
     309                return EOK;
     310        }
     311       
     312        if (fun->ftype == fun_inner) {
     313                log_msg(LVL_DEBUG, "Offlining inner function %s.",
     314                    fun->pathname);
     315               
     316                if (fun->child != NULL) {
     317                        dev_node_t *dev = fun->child;
     318                        device_state_t dev_state;
     319                       
     320                        dev_add_ref(dev);
     321                        dev_state = dev->state;
     322                       
     323                        fibril_rwlock_write_unlock(&device_tree.rwlock);
     324
     325                        /* If device is owned by driver, ask driver to give it up. */
     326                        if (dev_state == DEVICE_USABLE) {
     327                                rc = driver_dev_remove(&device_tree, dev);
     328                                if (rc != EOK) {
     329                                        dev_del_ref(dev);
     330                                        return ENOTSUP;
     331                                }
     332                        }
     333                       
     334                        /* Verify that driver removed all functions */
     335                        fibril_rwlock_read_lock(&device_tree.rwlock);
     336                        if (!list_empty(&dev->functions)) {
     337                                fibril_rwlock_read_unlock(&device_tree.rwlock);
     338                                dev_del_ref(dev);
     339                                return EIO;
     340                        }
     341                       
     342                        driver_t *driver = dev->drv;
     343                        fibril_rwlock_read_unlock(&device_tree.rwlock);
     344                       
     345                        if (driver)
     346                                detach_driver(&device_tree, dev);
     347                       
     348                        fibril_rwlock_write_lock(&device_tree.rwlock);
     349                        remove_dev_node(&device_tree, dev);
     350                       
     351                        /* Delete ref created when node was inserted */
     352                        dev_del_ref(dev);
     353                        /* Delete ref created by dev_add_ref(dev) above */
     354                        dev_del_ref(dev);
    332355                }
    333356        } else {
    334                 devmap_register_tree_function(fun, tree);
     357                /* Unregister from location service */
     358                rc = loc_service_unregister(fun->service_id);
     359                if (rc != EOK) {
     360                        fibril_rwlock_write_unlock(&device_tree.rwlock);
     361                        log_msg(LVL_ERROR, "Failed unregistering tree service.");
     362                        return EIO;
     363                }
     364               
     365                fun->service_id = 0;
     366        }
     367       
     368        fun->state = FUN_OFF_LINE;
     369        fibril_rwlock_write_unlock(&device_tree.rwlock);
     370       
     371        return EOK;
     372}
     373
     374/** Handle function registration.
     375 *
     376 * Child devices are registered by their parent's device driver.
     377 */
     378static void devman_add_function(ipc_callid_t callid, ipc_call_t *call)
     379{
     380        fun_type_t ftype = (fun_type_t) IPC_GET_ARG1(*call);
     381        devman_handle_t dev_handle = IPC_GET_ARG2(*call);
     382        sysarg_t match_count = IPC_GET_ARG3(*call);
     383        dev_tree_t *tree = &device_tree;
     384       
     385        dev_node_t *pdev = find_dev_node(&device_tree, dev_handle);
     386        if (pdev == NULL) {
     387                async_answer_0(callid, ENOENT);
     388                return;
     389        }
     390       
     391        if (ftype != fun_inner && ftype != fun_exposed) {
     392                /* Unknown function type */
     393                log_msg(LVL_ERROR,
     394                    "Unknown function type %d provided by driver.",
     395                    (int) ftype);
     396
     397                dev_del_ref(pdev);
     398                async_answer_0(callid, EINVAL);
     399                return;
     400        }
     401       
     402        char *fun_name = NULL;
     403        int rc = async_data_write_accept((void **)&fun_name, true, 0, 0, 0, 0);
     404        if (rc != EOK) {
     405                dev_del_ref(pdev);
     406                async_answer_0(callid, rc);
     407                return;
     408        }
     409       
     410        fibril_rwlock_write_lock(&tree->rwlock);
     411       
     412        /* Check device state */
     413        if (pdev->state == DEVICE_REMOVED) {
     414                fibril_rwlock_write_unlock(&tree->rwlock);
     415                dev_del_ref(pdev);
     416                async_answer_0(callid, ENOENT);
     417                return;
     418        }
     419       
     420        /* Check that function with same name is not there already. */
     421        if (find_fun_node_in_device(tree, pdev, fun_name) != NULL) {
     422                fibril_rwlock_write_unlock(&tree->rwlock);
     423                dev_del_ref(pdev);
     424                async_answer_0(callid, EEXISTS);
     425                printf(NAME ": Warning, driver tried to register `%s' twice.\n",
     426                    fun_name);
     427                free(fun_name);
     428                return;
     429        }
     430       
     431        fun_node_t *fun = create_fun_node();
     432        fun_add_ref(fun);
     433        fun->ftype = ftype;
     434       
     435        if (!insert_fun_node(&device_tree, fun, fun_name, pdev)) {
     436                fibril_rwlock_write_unlock(&tree->rwlock);
     437                dev_del_ref(pdev);
     438                delete_fun_node(fun);
     439                async_answer_0(callid, ENOMEM);
     440                return;
     441        }
     442       
     443        fibril_rwlock_write_unlock(&tree->rwlock);
     444        dev_del_ref(pdev);
     445       
     446        devman_receive_match_ids(match_count, &fun->match_ids);
     447       
     448        rc = online_function(fun);
     449        if (rc != EOK) {
     450                /* XXX clean up */
     451                async_answer_0(callid, rc);
     452                return;
    335453        }
    336454       
     
    339457}
    340458
    341 static void devmap_register_class_dev(dev_class_info_t *cli)
    342 {
    343         /* Create devmap path and name for the device. */
    344         char *devmap_pathname = NULL;
    345 
    346         asprintf(&devmap_pathname, "%s/%s%c%s", DEVMAP_CLASS_NAMESPACE,
    347             cli->dev_class->name, DEVMAP_SEPARATOR, cli->dev_name);
    348         if (devmap_pathname == NULL)
    349                 return;
    350        
    351         /*
    352          * Register the device by the device mapper and remember its devmap
    353          * handle.
    354          */
    355         devmap_device_register_with_iface(devmap_pathname,
    356             &cli->devmap_handle, DEVMAN_CONNECT_FROM_DEVMAP);
    357        
    358         /*
    359          * Add device to the hash map of class devices registered by device
    360          * mapper.
    361          */
    362         class_add_devmap_function(&class_list, cli);
    363        
    364         free(devmap_pathname);
    365 }
    366 
    367 static void devman_add_function_to_class(ipc_callid_t callid, ipc_call_t *call)
     459static void devman_add_function_to_cat(ipc_callid_t callid, ipc_call_t *call)
    368460{
    369461        devman_handle_t handle = IPC_GET_ARG1(*call);
    370        
    371         /* Get class name. */
    372         char *class_name;
    373         int rc = async_data_write_accept((void **) &class_name, true,
     462        category_id_t cat_id;
     463        int rc;
     464       
     465        /* Get category name. */
     466        char *cat_name;
     467        rc = async_data_write_accept((void **) &cat_name, true,
    374468            0, 0, 0, 0);
    375469        if (rc != EOK) {
    376470                async_answer_0(callid, rc);
    377471                return;
    378         }       
     472        }
    379473       
    380474        fun_node_t *fun = find_fun_node(&device_tree, handle);
     
    384478        }
    385479       
    386         dev_class_t *cl = get_dev_class(&class_list, class_name);
    387         dev_class_info_t *class_info = add_function_to_class(fun, cl, NULL);
    388        
    389         /* Register the device's class alias by devmapper. */
    390         devmap_register_class_dev(class_info);
    391        
    392         log_msg(LVL_NOTE, "Function `%s' added to class `%s' as `%s'.",
    393             fun->pathname, class_name, class_info->dev_name);
    394 
     480        fibril_rwlock_read_lock(&device_tree.rwlock);
     481       
     482        /* Check function state */
     483        if (fun->state == FUN_REMOVED) {
     484                fibril_rwlock_read_unlock(&device_tree.rwlock);
     485                async_answer_0(callid, ENOENT);
     486                return;
     487        }
     488       
     489        rc = loc_category_get_id(cat_name, &cat_id, IPC_FLAG_BLOCKING);
     490        if (rc == EOK) {
     491                loc_service_add_to_cat(fun->service_id, cat_id);
     492                log_msg(LVL_NOTE, "Function `%s' added to category `%s'.",
     493                    fun->pathname, cat_name);
     494        } else {
     495                log_msg(LVL_ERROR, "Failed adding function `%s' to category "
     496                    "`%s'.", fun->pathname, cat_name);
     497        }
     498       
     499        fibril_rwlock_read_unlock(&device_tree.rwlock);
     500        fun_del_ref(fun);
     501       
     502        async_answer_0(callid, rc);
     503}
     504
     505/** Online function by driver request.
     506 *
     507 */
     508static void devman_drv_fun_online(ipc_callid_t iid, ipc_call_t *icall,
     509    driver_t *drv)
     510{
     511        fun_node_t *fun;
     512        int rc;
     513       
     514        log_msg(LVL_DEBUG, "devman_drv_fun_online()");
     515       
     516        fun = find_fun_node(&device_tree, IPC_GET_ARG1(*icall));
     517        if (fun == NULL) {
     518                async_answer_0(iid, ENOENT);
     519                return;
     520        }
     521       
     522        fibril_rwlock_read_lock(&device_tree.rwlock);
     523        if (fun->dev == NULL || fun->dev->drv != drv) {
     524                fibril_rwlock_read_unlock(&device_tree.rwlock);
     525                fun_del_ref(fun);
     526                async_answer_0(iid, ENOENT);
     527                return;
     528        }
     529        fibril_rwlock_read_unlock(&device_tree.rwlock);
     530       
     531        rc = online_function(fun);
     532        if (rc != EOK) {
     533                fun_del_ref(fun);
     534                async_answer_0(iid, (sysarg_t) rc);
     535                return;
     536        }
     537       
     538        fun_del_ref(fun);
     539       
     540        async_answer_0(iid, (sysarg_t) EOK);
     541}
     542
     543
     544/** Offline function by driver request.
     545 *
     546 */
     547static void devman_drv_fun_offline(ipc_callid_t iid, ipc_call_t *icall,
     548    driver_t *drv)
     549{
     550        fun_node_t *fun;
     551        int rc;
     552
     553        fun = find_fun_node(&device_tree, IPC_GET_ARG1(*icall));
     554        if (fun == NULL) {
     555                async_answer_0(iid, ENOENT);
     556                return;
     557        }
     558       
     559        fibril_rwlock_write_lock(&device_tree.rwlock);
     560        if (fun->dev == NULL || fun->dev->drv != drv) {
     561                fun_del_ref(fun);
     562                async_answer_0(iid, ENOENT);
     563                return;
     564        }
     565        fibril_rwlock_write_unlock(&device_tree.rwlock);
     566       
     567        rc = offline_function(fun);
     568        if (rc != EOK) {
     569                fun_del_ref(fun);
     570                async_answer_0(iid, (sysarg_t) rc);
     571                return;
     572        }
     573       
     574        fun_del_ref(fun);
     575        async_answer_0(iid, (sysarg_t) EOK);
     576}
     577
     578/** Remove function. */
     579static void devman_remove_function(ipc_callid_t callid, ipc_call_t *call)
     580{
     581        devman_handle_t fun_handle = IPC_GET_ARG1(*call);
     582        dev_tree_t *tree = &device_tree;
     583        int rc;
     584       
     585        fun_node_t *fun = find_fun_node(&device_tree, fun_handle);
     586        if (fun == NULL) {
     587                async_answer_0(callid, ENOENT);
     588                return;
     589        }
     590       
     591        fibril_rwlock_write_lock(&tree->rwlock);
     592       
     593        log_msg(LVL_DEBUG, "devman_remove_function(fun='%s')", fun->pathname);
     594       
     595        /* Check function state */
     596        if (fun->state == FUN_REMOVED) {
     597                fibril_rwlock_write_unlock(&tree->rwlock);
     598                async_answer_0(callid, ENOENT);
     599                return;
     600        }
     601       
     602        if (fun->ftype == fun_inner) {
     603                /* This is a surprise removal. Handle possible descendants */
     604                if (fun->child != NULL) {
     605                        dev_node_t *dev = fun->child;
     606                        device_state_t dev_state;
     607                        int gone_rc;
     608                       
     609                        dev_add_ref(dev);
     610                        dev_state = dev->state;
     611                       
     612                        fibril_rwlock_write_unlock(&device_tree.rwlock);
     613                       
     614                        /* If device is owned by driver, inform driver it is gone. */
     615                        if (dev_state == DEVICE_USABLE)
     616                                gone_rc = driver_dev_gone(&device_tree, dev);
     617                        else
     618                                gone_rc = EOK;
     619                       
     620                        fibril_rwlock_read_lock(&device_tree.rwlock);
     621                       
     622                        /* Verify that driver succeeded and removed all functions */
     623                        if (gone_rc != EOK || !list_empty(&dev->functions)) {
     624                                log_msg(LVL_ERROR, "Driver did not remove "
     625                                    "functions for device that is gone. "
     626                                    "Device node is now defunct.");
     627                               
     628                                /*
     629                                 * Not much we can do but mark the device
     630                                 * node as having invalid state. This
     631                                 * is a driver bug.
     632                                 */
     633                                dev->state = DEVICE_INVALID;
     634                                fibril_rwlock_read_unlock(&device_tree.rwlock);
     635                                dev_del_ref(dev);
     636                                if (gone_rc == EOK)
     637                                        gone_rc = ENOTSUP;
     638                                async_answer_0(callid, gone_rc);
     639                                return;
     640                        }
     641                       
     642                        driver_t *driver = dev->drv;
     643                        fibril_rwlock_read_unlock(&device_tree.rwlock);
     644                       
     645                        if (driver)
     646                                detach_driver(&device_tree, dev);
     647                       
     648                        fibril_rwlock_write_lock(&device_tree.rwlock);
     649                        remove_dev_node(&device_tree, dev);
     650                       
     651                        /* Delete ref created when node was inserted */
     652                        dev_del_ref(dev);
     653                        /* Delete ref created by dev_add_ref(dev) above */
     654                        dev_del_ref(dev);
     655                }
     656        } else {
     657                if (fun->service_id != 0) {
     658                        /* Unregister from location service */
     659                        rc = loc_service_unregister(fun->service_id);
     660                        if (rc != EOK) {
     661                                log_msg(LVL_ERROR, "Failed unregistering tree "
     662                                    "service.");
     663                                fibril_rwlock_write_unlock(&tree->rwlock);
     664                                fun_del_ref(fun);
     665                                async_answer_0(callid, EIO);
     666                                return;
     667                        }
     668                }
     669        }
     670       
     671        remove_fun_node(&device_tree, fun);
     672        fibril_rwlock_write_unlock(&tree->rwlock);
     673       
     674        /* Delete ref added when inserting function into tree */
     675        fun_del_ref(fun);
     676        /* Delete ref added above when looking up function */
     677        fun_del_ref(fun);
     678       
     679        log_msg(LVL_DEBUG, "devman_remove_function() succeeded.");
    395680        async_answer_0(callid, EOK);
    396681}
     
    414699static void devman_connection_driver(ipc_callid_t iid, ipc_call_t *icall)
    415700{
     701        client_t *client;
     702        driver_t *driver;
     703       
    416704        /* Accept the connection. */
    417705        async_answer_0(iid, EOK);
    418706       
    419         driver_t *driver = devman_driver_register();
    420         if (driver == NULL)
    421                 return;
    422        
    423         /*
    424          * Initialize the driver as running (e.g. pass assigned devices to it)
    425          * in a separate fibril; the separate fibril is used to enable the
    426          * driver to use devman service during the driver's initialization.
    427          */
    428         fid_t fid = fibril_create(init_running_drv, driver);
    429         if (fid == 0) {
    430                 log_msg(LVL_ERROR, "Failed to create initialization fibril " \
    431                     "for driver `%s'.", driver->name);
    432                 return;
    433         }
    434         fibril_add_ready(fid);
    435        
    436         ipc_callid_t callid;
    437         ipc_call_t call;
    438         bool cont = true;
    439         while (cont) {
    440                 callid = async_get_call(&call);
     707        client = async_get_client_data();
     708        if (client == NULL) {
     709                log_msg(LVL_ERROR, "Failed to allocate client data.");
     710                return;
     711        }
     712       
     713        while (true) {
     714                ipc_call_t call;
     715                ipc_callid_t callid = async_get_call(&call);
     716               
     717                if (!IPC_GET_IMETHOD(call))
     718                        break;
     719               
     720                if (IPC_GET_IMETHOD(call) != DEVMAN_DRIVER_REGISTER) {
     721                        fibril_mutex_lock(&client->mutex);
     722                        driver = client->driver;
     723                        fibril_mutex_unlock(&client->mutex);
     724                        if (driver == NULL) {
     725                                /* First call must be to DEVMAN_DRIVER_REGISTER */
     726                                async_answer_0(callid, ENOTSUP);
     727                                continue;
     728                        }
     729                }
    441730               
    442731                switch (IPC_GET_IMETHOD(call)) {
    443                 case IPC_M_PHONE_HUNGUP:
    444                         cont = false;
    445                         continue;
     732                case DEVMAN_DRIVER_REGISTER:
     733                        fibril_mutex_lock(&client->mutex);
     734                        if (client->driver != NULL) {
     735                                fibril_mutex_unlock(&client->mutex);
     736                                async_answer_0(callid, EINVAL);
     737                                continue;
     738                        }
     739                        client->driver = devman_driver_register(callid, &call);
     740                        fibril_mutex_unlock(&client->mutex);
     741                        break;
    446742                case DEVMAN_ADD_FUNCTION:
    447743                        devman_add_function(callid, &call);
    448744                        break;
    449                 case DEVMAN_ADD_DEVICE_TO_CLASS:
    450                         devman_add_function_to_class(callid, &call);
     745                case DEVMAN_ADD_DEVICE_TO_CATEGORY:
     746                        devman_add_function_to_cat(callid, &call);
     747                        break;
     748                case DEVMAN_DRV_FUN_ONLINE:
     749                        devman_drv_fun_online(callid, &call, driver);
     750                        break;
     751                case DEVMAN_DRV_FUN_OFFLINE:
     752                        devman_drv_fun_offline(callid, &call, driver);
     753                        break;
     754                case DEVMAN_REMOVE_FUNCTION:
     755                        devman_remove_function(callid, &call);
    451756                        break;
    452757                default:
    453                         async_answer_0(callid, EINVAL); 
     758                        async_answer_0(callid, EINVAL);
    454759                        break;
    455760                }
     
    462767{
    463768        char *pathname;
     769        devman_handle_t handle;
    464770       
    465771        int rc = async_data_write_accept((void **) &pathname, true, 0, 0, 0, 0);
     
    478784        }
    479785
     786        fibril_rwlock_read_lock(&device_tree.rwlock);
     787
     788        /* Check function state */
     789        if (fun->state == FUN_REMOVED) {
     790                fibril_rwlock_read_unlock(&device_tree.rwlock);
     791                async_answer_0(iid, ENOENT);
     792                return;
     793        }
     794        handle = fun->handle;
     795
     796        fibril_rwlock_read_unlock(&device_tree.rwlock);
     797
     798        /* Delete reference created above by find_fun_node_by_path() */
     799        fun_del_ref(fun);
     800
     801        async_answer_1(iid, EOK, handle);
     802}
     803
     804/** Get device name. */
     805static void devman_fun_get_name(ipc_callid_t iid, ipc_call_t *icall)
     806{
     807        devman_handle_t handle = IPC_GET_ARG1(*icall);
     808
     809        fun_node_t *fun = find_fun_node(&device_tree, handle);
     810        if (fun == NULL) {
     811                async_answer_0(iid, ENOMEM);
     812                return;
     813        }
     814
     815        ipc_callid_t data_callid;
     816        size_t data_len;
     817        if (!async_data_read_receive(&data_callid, &data_len)) {
     818                async_answer_0(iid, EINVAL);
     819                fun_del_ref(fun);
     820                return;
     821        }
     822
     823        void *buffer = malloc(data_len);
     824        if (buffer == NULL) {
     825                async_answer_0(data_callid, ENOMEM);
     826                async_answer_0(iid, ENOMEM);
     827                fun_del_ref(fun);
     828                return;
     829        }
     830
     831        fibril_rwlock_read_lock(&device_tree.rwlock);
     832
     833        /* Check function state */
     834        if (fun->state == FUN_REMOVED) {
     835                fibril_rwlock_read_unlock(&device_tree.rwlock);
     836                free(buffer);
     837
     838                async_answer_0(data_callid, ENOENT);
     839                async_answer_0(iid, ENOENT);
     840                fun_del_ref(fun);
     841                return;
     842        }
     843
     844        size_t sent_length = str_size(fun->name);
     845        if (sent_length > data_len) {
     846                sent_length = data_len;
     847        }
     848
     849        async_data_read_finalize(data_callid, fun->name, sent_length);
     850        async_answer_0(iid, EOK);
     851
     852        fibril_rwlock_read_unlock(&device_tree.rwlock);
     853        fun_del_ref(fun);
     854        free(buffer);
     855}
     856
     857
     858/** Get device path. */
     859static void devman_fun_get_path(ipc_callid_t iid, ipc_call_t *icall)
     860{
     861        devman_handle_t handle = IPC_GET_ARG1(*icall);
     862
     863        fun_node_t *fun = find_fun_node(&device_tree, handle);
     864        if (fun == NULL) {
     865                async_answer_0(iid, ENOMEM);
     866                return;
     867        }
     868
     869        ipc_callid_t data_callid;
     870        size_t data_len;
     871        if (!async_data_read_receive(&data_callid, &data_len)) {
     872                async_answer_0(iid, EINVAL);
     873                fun_del_ref(fun);
     874                return;
     875        }
     876
     877        void *buffer = malloc(data_len);
     878        if (buffer == NULL) {
     879                async_answer_0(data_callid, ENOMEM);
     880                async_answer_0(iid, ENOMEM);
     881                fun_del_ref(fun);
     882                return;
     883        }
     884       
     885        fibril_rwlock_read_lock(&device_tree.rwlock);
     886       
     887        /* Check function state */
     888        if (fun->state == FUN_REMOVED) {
     889                fibril_rwlock_read_unlock(&device_tree.rwlock);
     890                free(buffer);
     891
     892                async_answer_0(data_callid, ENOENT);
     893                async_answer_0(iid, ENOENT);
     894                fun_del_ref(fun);
     895                return;
     896        }
     897       
     898        size_t sent_length = str_size(fun->pathname);
     899        if (sent_length > data_len) {
     900                sent_length = data_len;
     901        }
     902
     903        async_data_read_finalize(data_callid, fun->pathname, sent_length);
     904        async_answer_0(iid, EOK);
     905
     906        fibril_rwlock_read_unlock(&device_tree.rwlock);
     907        fun_del_ref(fun);
     908        free(buffer);
     909}
     910
     911static void devman_dev_get_functions(ipc_callid_t iid, ipc_call_t *icall)
     912{
     913        ipc_callid_t callid;
     914        size_t size;
     915        size_t act_size;
     916        int rc;
     917       
     918        if (!async_data_read_receive(&callid, &size)) {
     919                async_answer_0(callid, EREFUSED);
     920                async_answer_0(iid, EREFUSED);
     921                return;
     922        }
     923       
     924        fibril_rwlock_read_lock(&device_tree.rwlock);
     925       
     926        dev_node_t *dev = find_dev_node_no_lock(&device_tree,
     927            IPC_GET_ARG1(*icall));
     928        if (dev == NULL || dev->state == DEVICE_REMOVED) {
     929                fibril_rwlock_read_unlock(&device_tree.rwlock);
     930                async_answer_0(callid, ENOENT);
     931                async_answer_0(iid, ENOENT);
     932                return;
     933        }
     934       
     935        devman_handle_t *hdl_buf = (devman_handle_t *) malloc(size);
     936        if (hdl_buf == NULL) {
     937                fibril_rwlock_read_unlock(&device_tree.rwlock);
     938                async_answer_0(callid, ENOMEM);
     939                async_answer_0(iid, ENOMEM);
     940                return;
     941        }
     942       
     943        rc = dev_get_functions(&device_tree, dev, hdl_buf, size, &act_size);
     944        if (rc != EOK) {
     945                fibril_rwlock_read_unlock(&device_tree.rwlock);
     946                async_answer_0(callid, rc);
     947                async_answer_0(iid, rc);
     948                return;
     949        }
     950       
     951        fibril_rwlock_read_unlock(&device_tree.rwlock);
     952       
     953        sysarg_t retval = async_data_read_finalize(callid, hdl_buf, size);
     954        free(hdl_buf);
     955       
     956        async_answer_1(iid, retval, act_size);
     957}
     958
     959
     960/** Get handle for child device of a function. */
     961static void devman_fun_get_child(ipc_callid_t iid, ipc_call_t *icall)
     962{
     963        fun_node_t *fun;
     964       
     965        fibril_rwlock_read_lock(&device_tree.rwlock);
     966       
     967        fun = find_fun_node_no_lock(&device_tree, IPC_GET_ARG1(*icall));
     968        if (fun == NULL || fun->state == FUN_REMOVED) {
     969                fibril_rwlock_read_unlock(&device_tree.rwlock);
     970                async_answer_0(iid, ENOENT);
     971                return;
     972        }
     973       
     974        if (fun->child == NULL) {
     975                fibril_rwlock_read_unlock(&device_tree.rwlock);
     976                async_answer_0(iid, ENOENT);
     977                return;
     978        }
     979       
     980        async_answer_1(iid, EOK, fun->child->handle);
     981       
     982        fibril_rwlock_read_unlock(&device_tree.rwlock);
     983}
     984
     985/** Online function.
     986 *
     987 * Send a request to online a function to the responsible driver.
     988 * The driver may offline other functions if necessary (i.e. if the state
     989 * of this function is linked to state of another function somehow).
     990 */
     991static void devman_fun_online(ipc_callid_t iid, ipc_call_t *icall)
     992{
     993        fun_node_t *fun;
     994        int rc;
     995
     996        fun = find_fun_node(&device_tree, IPC_GET_ARG1(*icall));
     997        if (fun == NULL) {
     998                async_answer_0(iid, ENOENT);
     999                return;
     1000        }
     1001       
     1002        rc = driver_fun_online(&device_tree, fun);
     1003        fun_del_ref(fun);
     1004       
     1005        async_answer_0(iid, (sysarg_t) rc);
     1006}
     1007
     1008/** Offline function.
     1009 *
     1010 * Send a request to offline a function to the responsible driver. As
     1011 * a result the subtree rooted at that function should be cleanly
     1012 * detatched. The driver may offline other functions if necessary
     1013 * (i.e. if the state of this function is linked to state of another
     1014 * function somehow).
     1015 */
     1016static void devman_fun_offline(ipc_callid_t iid, ipc_call_t *icall)
     1017{
     1018        fun_node_t *fun;
     1019        int rc;
     1020
     1021        fun = find_fun_node(&device_tree, IPC_GET_ARG1(*icall));
     1022        if (fun == NULL) {
     1023                async_answer_0(iid, ENOENT);
     1024                return;
     1025        }
     1026       
     1027        rc = driver_fun_offline(&device_tree, fun);
     1028        fun_del_ref(fun);
     1029       
     1030        async_answer_0(iid, (sysarg_t) rc);
     1031}
     1032
     1033/** Find handle for the function instance identified by its service ID. */
     1034static void devman_fun_sid_to_handle(ipc_callid_t iid, ipc_call_t *icall)
     1035{
     1036        fun_node_t *fun;
     1037
     1038        fun = find_loc_tree_function(&device_tree, IPC_GET_ARG1(*icall));
     1039       
     1040        if (fun == NULL) {
     1041                async_answer_0(iid, ENOENT);
     1042                return;
     1043        }
     1044
     1045        fibril_rwlock_read_lock(&device_tree.rwlock);
     1046
     1047        /* Check function state */
     1048        if (fun->state == FUN_REMOVED) {
     1049                fibril_rwlock_read_unlock(&device_tree.rwlock);
     1050                async_answer_0(iid, ENOENT);
     1051                return;
     1052        }
     1053
    4801054        async_answer_1(iid, EOK, fun->handle);
    481 }
    482 
    483 /** Find handle for the device instance identified by device class name. */
    484 static void devman_function_get_handle_by_class(ipc_callid_t iid,
    485     ipc_call_t *icall)
    486 {
    487         char *classname;
    488         char *devname;
    489 
    490         int rc = async_data_write_accept((void **) &classname, true, 0, 0, 0, 0);
    491         if (rc != EOK) {
    492                 async_answer_0(iid, rc);
    493                 return;
    494         }
    495         rc = async_data_write_accept((void **) &devname, true, 0, 0, 0, 0);
    496         if (rc != EOK) {
    497                 free(classname);
    498                 async_answer_0(iid, rc);
    499                 return;
    500         }
    501 
    502 
    503         fun_node_t *fun = find_fun_node_by_class(&class_list,
    504             classname, devname);
    505 
    506         free(classname);
    507         free(devname);
    508 
    509         if (fun == NULL) {
    510                 async_answer_0(iid, ENOENT);
    511                 return;
    512         }
    513 
    514         async_answer_1(iid, EOK, fun->handle);
    515 }
    516 
     1055        fibril_rwlock_read_unlock(&device_tree.rwlock);
     1056        fun_del_ref(fun);
     1057}
    5171058
    5181059/** Function for handling connections from a client to the device manager. */
     
    5221063        async_answer_0(iid, EOK);
    5231064       
    524         bool cont = true;
    525         while (cont) {
     1065        while (true) {
    5261066                ipc_call_t call;
    5271067                ipc_callid_t callid = async_get_call(&call);
    5281068               
     1069                if (!IPC_GET_IMETHOD(call))
     1070                        break;
     1071               
    5291072                switch (IPC_GET_IMETHOD(call)) {
    530                 case IPC_M_PHONE_HUNGUP:
    531                         cont = false;
    532                         continue;
    5331073                case DEVMAN_DEVICE_GET_HANDLE:
    5341074                        devman_function_get_handle(callid, &call);
    5351075                        break;
    536                 case DEVMAN_DEVICE_GET_HANDLE_BY_CLASS:
    537                         devman_function_get_handle_by_class(callid, &call);
     1076                case DEVMAN_DEV_GET_FUNCTIONS:
     1077                        devman_dev_get_functions(callid, &call);
     1078                        break;
     1079                case DEVMAN_FUN_GET_CHILD:
     1080                        devman_fun_get_child(callid, &call);
     1081                        break;
     1082                case DEVMAN_FUN_GET_NAME:
     1083                        devman_fun_get_name(callid, &call);
     1084                        break;
     1085                case DEVMAN_FUN_GET_PATH:
     1086                        devman_fun_get_path(callid, &call);
     1087                        break;
     1088                case DEVMAN_FUN_ONLINE:
     1089                        devman_fun_online(callid, &call);
     1090                        break;
     1091                case DEVMAN_FUN_OFFLINE:
     1092                        devman_fun_offline(callid, &call);
     1093                        break;
     1094                case DEVMAN_FUN_SID_TO_HANDLE:
     1095                        devman_fun_sid_to_handle(callid, &call);
    5381096                        break;
    5391097                default:
     
    5541112        if (fun == NULL)
    5551113                dev = find_dev_node(&device_tree, handle);
    556         else
     1114        else {
     1115                fibril_rwlock_read_lock(&device_tree.rwlock);
    5571116                dev = fun->dev;
     1117                if (dev != NULL)
     1118                        dev_add_ref(dev);
     1119                fibril_rwlock_read_unlock(&device_tree.rwlock);
     1120        }
    5581121
    5591122        /*
     
    5671130                    "function with handle %" PRIun " was found.", handle);
    5681131                async_answer_0(iid, ENOENT);
    569                 return;
     1132                goto cleanup;
    5701133        }
    5711134
     
    5751138                    handle);
    5761139                async_answer_0(iid, ENOENT);
    577                 return;
     1140                goto cleanup;
    5781141        }
    5791142       
    5801143        driver_t *driver = NULL;
     1144       
     1145        fibril_rwlock_read_lock(&device_tree.rwlock);
    5811146       
    5821147        if (drv_to_parent) {
     
    5931158        }
    5941159       
     1160        fibril_rwlock_read_unlock(&device_tree.rwlock);
     1161       
    5951162        if (driver == NULL) {
    5961163                log_msg(LVL_ERROR, "IPC forwarding refused - " \
    5971164                    "the device %" PRIun " is not in usable state.", handle);
    5981165                async_answer_0(iid, ENOENT);
    599                 return;
     1166                goto cleanup;
    6001167        }
    6011168       
     
    6061173                method = DRIVER_CLIENT;
    6071174       
    608         if (driver->phone < 0) {
    609                 log_msg(LVL_ERROR,
    610                     "Could not forward to driver `%s' (phone is %d).",
    611                     driver->name, (int) driver->phone);
     1175        if (!driver->sess) {
     1176                log_msg(LVL_ERROR,
     1177                    "Could not forward to driver `%s'.", driver->name);
    6121178                async_answer_0(iid, EINVAL);
    613                 return;
     1179                goto cleanup;
    6141180        }
    6151181
     
    6231189                    dev->pfun->pathname, driver->name);
    6241190        }
    625 
    626         async_forward_fast(iid, driver->phone, method, fwd_h, 0, IPC_FF_NONE);
    627 }
    628 
    629 /** Function for handling connections from a client forwarded by the device
    630  * mapper to the device manager. */
    631 static void devman_connection_devmapper(ipc_callid_t iid, ipc_call_t *icall)
    632 {
    633         devmap_handle_t devmap_handle = IPC_GET_ARG2(*icall);
     1191       
     1192        async_exch_t *exch = async_exchange_begin(driver->sess);
     1193        async_forward_fast(iid, exch, method, fwd_h, 0, IPC_FF_NONE);
     1194        async_exchange_end(exch);
     1195
     1196cleanup:
     1197        if (dev != NULL)
     1198                dev_del_ref(dev);
     1199        if (fun != NULL)
     1200                fun_del_ref(fun);
     1201}
     1202
     1203/** Function for handling connections from a client forwarded by the location
     1204 * service to the device manager. */
     1205static void devman_connection_loc(ipc_callid_t iid, ipc_call_t *icall)
     1206{
     1207        service_id_t service_id = IPC_GET_ARG2(*icall);
    6341208        fun_node_t *fun;
    6351209        dev_node_t *dev;
    636 
    637         fun = find_devmap_tree_function(&device_tree, devmap_handle);
    638         if (fun == NULL)
    639                 fun = find_devmap_class_function(&class_list, devmap_handle);
    640        
    641         if (fun == NULL || fun->dev->drv == NULL) {
     1210        devman_handle_t handle;
     1211        driver_t *driver;
     1212
     1213        fun = find_loc_tree_function(&device_tree, service_id);
     1214       
     1215        fibril_rwlock_read_lock(&device_tree.rwlock);
     1216       
     1217        if (fun == NULL || fun->dev == NULL || fun->dev->drv == NULL) {
     1218                log_msg(LVL_WARN, "devman_connection_loc(): function "
     1219                    "not found.\n");
     1220                fibril_rwlock_read_unlock(&device_tree.rwlock);
    6421221                async_answer_0(iid, ENOENT);
    6431222                return;
     
    6451224       
    6461225        dev = fun->dev;
    647        
    648         if (dev->state != DEVICE_USABLE || dev->drv->phone < 0) {
    649                 async_answer_0(iid, EINVAL);
    650                 return;
    651         }
    652        
    653         async_forward_fast(iid, dev->drv->phone, DRIVER_CLIENT, fun->handle, 0,
     1226        driver = dev->drv;
     1227        handle = fun->handle;
     1228       
     1229        fibril_rwlock_read_unlock(&device_tree.rwlock);
     1230       
     1231        async_exch_t *exch = async_exchange_begin(driver->sess);
     1232        async_forward_fast(iid, exch, DRIVER_CLIENT, handle, 0,
    6541233            IPC_FF_NONE);
    655         log_msg(LVL_DEBUG,
    656             "Forwarding devmapper request for `%s' function to driver `%s'.",
    657             fun->pathname, dev->drv->name);
     1234        async_exchange_end(exch);
     1235       
     1236        log_msg(LVL_DEBUG,
     1237            "Forwarding loc service request for `%s' function to driver `%s'.",
     1238            fun->pathname, driver->name);
     1239
     1240        fun_del_ref(fun);
    6581241}
    6591242
    6601243/** Function for handling connections to device manager. */
    661 static void devman_connection(ipc_callid_t iid, ipc_call_t *icall)
    662 {
    663         /* Select interface. */
     1244static void devman_connection(ipc_callid_t iid, ipc_call_t *icall, void *arg)
     1245{
     1246        /* Select port. */
    6641247        switch ((sysarg_t) (IPC_GET_ARG1(*icall))) {
    6651248        case DEVMAN_DRIVER:
     
    6731256                devman_forward(iid, icall, false);
    6741257                break;
    675         case DEVMAN_CONNECT_FROM_DEVMAP:
    676                 /* Someone connected through devmap node. */
    677                 devman_connection_devmapper(iid, icall);
     1258        case DEVMAN_CONNECT_FROM_LOC:
     1259                /* Someone connected through loc node. */
     1260                devman_connection_loc(iid, icall);
    6781261                break;
    6791262        case DEVMAN_CONNECT_TO_PARENTS_DEVICE:
     
    6871270}
    6881271
     1272static void *devman_client_data_create(void)
     1273{
     1274        client_t *client;
     1275       
     1276        client = calloc(1, sizeof(client_t));
     1277        if (client == NULL)
     1278                return NULL;
     1279       
     1280        fibril_mutex_initialize(&client->mutex);
     1281        return client;
     1282}
     1283
     1284static void devman_client_data_destroy(void *data)
     1285{
     1286        free(data);
     1287}
     1288
    6891289/** Initialize device manager internal structures. */
    6901290static bool devman_init(void)
     
    7081308        }
    7091309
    710         init_class_list(&class_list);
    711        
    7121310        /*
    713          * !!! devman_connection ... as the device manager is not a real devmap
     1311         * !!! devman_connection ... as the device manager is not a real loc
    7141312         * driver (it uses a completely different ipc protocol than an ordinary
    715          * devmap driver) forwarding a connection from client to the devman by
    716          * devmapper would not work.
     1313         * loc driver) forwarding a connection from client to the devman by
     1314         * location service would not work.
    7171315         */
    718         devmap_driver_register(NAME, devman_connection);
     1316        loc_server_register(NAME, devman_connection);
    7191317       
    7201318        return true;
     
    7251323        printf(NAME ": HelenOS Device Manager\n");
    7261324
    727         if (log_init(NAME, LVL_ERROR) != EOK) {
     1325        if (log_init(NAME, LVL_WARN) != EOK) {
    7281326                printf(NAME ": Error initializing logging subsystem.\n");
    7291327                return -1;
     
    7351333        }
    7361334       
    737         /* Set a handler of incomming connections. */
     1335        /* Set handlers for incoming connections. */
     1336        async_set_client_data_constructor(devman_client_data_create);
     1337        async_set_client_data_destructor(devman_client_data_destroy);
    7381338        async_set_client_connection(devman_connection);
    7391339
     
    7451345
    7461346        printf(NAME ": Accepting connections.\n");
     1347        task_retval(0);
    7471348        async_manager();
    7481349
Note: See TracChangeset for help on using the changeset viewer.