Ignore:
File:
1 edited

Legend:

Unmodified
Added
Removed
  • uspace/lib/drv/generic/remote_usb.c

    r9d58539 r58563585  
    3535
    3636#include <async.h>
     37#include <macros.h>
    3738#include <errno.h>
     39#include <devman.h>
    3840
    3941#include "usb_iface.h"
    4042#include "ddf/driver.h"
    4143
     44
     45usb_dev_session_t *usb_dev_connect(devman_handle_t handle)
     46{
     47        return devman_device_connect(handle, IPC_FLAG_BLOCKING);
     48}
     49
     50usb_dev_session_t *usb_dev_connect_to_self(ddf_dev_t *dev)
     51{
     52        return devman_parent_device_connect(ddf_dev_get_handle(dev), IPC_FLAG_BLOCKING);
     53}
     54
     55void usb_dev_disconnect(usb_dev_session_t *sess)
     56{
     57        if (sess)
     58                async_hangup(sess);
     59}
     60
    4261typedef enum {
    43         IPC_M_USB_GET_MY_ADDRESS,
    4462        IPC_M_USB_GET_MY_INTERFACE,
    45         IPC_M_USB_GET_HOST_CONTROLLER_HANDLE,
     63        IPC_M_USB_GET_MY_DEVICE_HANDLE,
     64        IPC_M_USB_RESERVE_DEFAULT_ADDRESS,
     65        IPC_M_USB_RELEASE_DEFAULT_ADDRESS,
     66        IPC_M_USB_DEVICE_ENUMERATE,
     67        IPC_M_USB_DEVICE_REMOVE,
     68        IPC_M_USB_REGISTER_ENDPOINT,
     69        IPC_M_USB_UNREGISTER_ENDPOINT,
     70        IPC_M_USB_READ,
     71        IPC_M_USB_WRITE,
    4672} usb_iface_funcs_t;
    4773
    48 /** Tell USB address assigned to device.
    49  * @param exch Vaid IPC exchange
    50  * @param address Pointer to address storage place.
    51  * @return Error code.
    52  *
    53  * Exch param is an open communication to device implementing usb_iface.
    54  */
    55 int usb_get_my_address(async_exch_t *exch, usb_address_t *address)
    56 {
    57         if (!exch)
    58                 return EINVAL;
    59         sysarg_t addr;
    60         const int ret = async_req_1_1(exch, DEV_IFACE_ID(USB_DEV_IFACE),
    61             IPC_M_USB_GET_MY_ADDRESS, &addr);
    62 
    63         if (ret == EOK && address != NULL)
    64                 *address = (usb_address_t) addr;
    65         return ret;
    66 }
    67 /*----------------------------------------------------------------------------*/
    6874/** Tell interface number given device can use.
    6975 * @param[in] exch IPC communication exchange
     
    7581{
    7682        if (!exch)
    77                 return EINVAL;
     83                return EBADMEM;
    7884        sysarg_t iface_no;
    7985        const int ret = async_req_1_1(exch, DEV_IFACE_ID(USB_DEV_IFACE),
     
    8389        return ret;
    8490}
    85 /*----------------------------------------------------------------------------*/
    86 /** Tell devman handle of device host controller.
     91
     92/** Tell devman handle of the usb device function.
     93 *
     94 * @param[in]  exch   IPC communication exchange
     95 * @param[out] handle devman handle of the HC used by the target device.
     96 *
     97 * @return Error code.
     98 *
     99 */
     100int usb_get_my_device_handle(async_exch_t *exch, devman_handle_t *handle)
     101{
     102        devman_handle_t h = 0;
     103        const int ret = async_req_1_1(exch, DEV_IFACE_ID(USB_DEV_IFACE),
     104            IPC_M_USB_GET_MY_DEVICE_HANDLE, &h);
     105        if (ret == EOK && handle)
     106                *handle = (devman_handle_t)h;
     107        return ret;
     108}
     109
     110/** Reserve default USB address.
    87111 * @param[in] exch IPC communication exchange
    88  * @param[out] hc_handle devman handle of the HC used by the target device.
     112 * @param[in] speed Communication speed of the newly attached device
    89113 * @return Error code.
    90114 */
    91 int usb_get_hc_handle(async_exch_t *exch, devman_handle_t *hc_handle)
    92 {
    93         if (!exch)
    94                 return EINVAL;
    95         devman_handle_t h;
    96         const int ret = async_req_1_1(exch, DEV_IFACE_ID(USB_DEV_IFACE),
    97             IPC_M_USB_GET_HOST_CONTROLLER_HANDLE, &h);
    98         if (ret == EOK && hc_handle)
    99                 *hc_handle = (devman_handle_t)h;
     115int usb_reserve_default_address(async_exch_t *exch, usb_speed_t speed)
     116{
     117        if (!exch)
     118                return EBADMEM;
     119        return async_req_2_0(exch, DEV_IFACE_ID(USB_DEV_IFACE),
     120            IPC_M_USB_RESERVE_DEFAULT_ADDRESS, speed);
     121}
     122
     123/** Release default USB address.
     124 *
     125 * @param[in] exch IPC communication exchange
     126 *
     127 * @return Error code.
     128 *
     129 */
     130int usb_release_default_address(async_exch_t *exch)
     131{
     132        if (!exch)
     133                return EBADMEM;
     134        return async_req_1_0(exch, DEV_IFACE_ID(USB_DEV_IFACE),
     135            IPC_M_USB_RELEASE_DEFAULT_ADDRESS);
     136}
     137
     138/** Trigger USB device enumeration
     139 *
     140 * @param[in]  exch   IPC communication exchange
     141 * @param[out] handle Identifier of the newly added device (if successful)
     142 *
     143 * @return Error code.
     144 *
     145 */
     146int usb_device_enumerate(async_exch_t *exch, unsigned port)
     147{
     148        if (!exch)
     149                return EBADMEM;
     150        const int ret = async_req_2_0(exch, DEV_IFACE_ID(USB_DEV_IFACE),
     151            IPC_M_USB_DEVICE_ENUMERATE, port);
    100152        return ret;
    101153}
    102154
    103 
    104 static void remote_usb_get_my_address(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
     155/** Trigger USB device enumeration
     156 *
     157 * @param[in] exch   IPC communication exchange
     158 * @param[in] handle Identifier of the device
     159 *
     160 * @return Error code.
     161 *
     162 */
     163int usb_device_remove(async_exch_t *exch, unsigned port)
     164{
     165        if (!exch)
     166                return EBADMEM;
     167        return async_req_2_0(exch, DEV_IFACE_ID(USB_DEV_IFACE),
     168            IPC_M_USB_DEVICE_REMOVE, port);
     169}
     170
     171int static_assert[sizeof(sysarg_t) >= 4 ? 1 : -1];
     172typedef union {
     173        uint8_t arr[sizeof(sysarg_t)];
     174        sysarg_t arg;
     175} pack8_t;
     176
     177int usb_register_endpoint(async_exch_t *exch, usb_endpoint_t endpoint,
     178    usb_transfer_type_t type, usb_direction_t direction,
     179    size_t mps, unsigned packets, unsigned interval)
     180{
     181        if (!exch)
     182                return EBADMEM;
     183        pack8_t pack;
     184        pack.arr[0] = type;
     185        pack.arr[1] = direction;
     186        pack.arr[2] = interval;
     187        pack.arr[3] = packets;
     188
     189        return async_req_4_0(exch, DEV_IFACE_ID(USB_DEV_IFACE),
     190            IPC_M_USB_REGISTER_ENDPOINT, endpoint, pack.arg, mps);
     191
     192}
     193
     194int usb_unregister_endpoint(async_exch_t *exch, usb_endpoint_t endpoint,
     195    usb_direction_t direction)
     196{
     197        if (!exch)
     198                return EBADMEM;
     199        return async_req_3_0(exch, DEV_IFACE_ID(USB_DEV_IFACE),
     200            IPC_M_USB_UNREGISTER_ENDPOINT, endpoint, direction);
     201}
     202
     203int usb_read(async_exch_t *exch, usb_endpoint_t endpoint, uint64_t setup,
     204    void *data, size_t size, size_t *rec_size)
     205{
     206        if (!exch)
     207                return EBADMEM;
     208
     209        if (size == 0 && setup == 0)
     210                return EOK;
     211
     212        /* Make call identifying target USB device and type of transfer. */
     213        aid_t opening_request = async_send_4(exch,
     214            DEV_IFACE_ID(USB_DEV_IFACE), IPC_M_USB_READ, endpoint,
     215            (setup & UINT32_MAX), (setup >> 32), NULL);
     216
     217        if (opening_request == 0) {
     218                return ENOMEM;
     219        }
     220
     221        /* Retrieve the data. */
     222        ipc_call_t data_request_call;
     223        aid_t data_request =
     224            async_data_read(exch, data, size, &data_request_call);
     225
     226        if (data_request == 0) {
     227                // FIXME: How to let the other side know that we want to abort?
     228                async_forget(opening_request);
     229                return ENOMEM;
     230        }
     231
     232        /* Wait for the answer. */
     233        sysarg_t data_request_rc;
     234        sysarg_t opening_request_rc;
     235        async_wait_for(data_request, &data_request_rc);
     236        async_wait_for(opening_request, &opening_request_rc);
     237
     238        if (data_request_rc != EOK) {
     239                /* Prefer the return code of the opening request. */
     240                if (opening_request_rc != EOK) {
     241                        return (int) opening_request_rc;
     242                } else {
     243                        return (int) data_request_rc;
     244                }
     245        }
     246        if (opening_request_rc != EOK) {
     247                return (int) opening_request_rc;
     248        }
     249
     250        *rec_size = IPC_GET_ARG2(data_request_call);
     251        return EOK;
     252}
     253
     254int usb_write(async_exch_t *exch, usb_endpoint_t endpoint, uint64_t setup,
     255    const void *data, size_t size)
     256{
     257        if (!exch)
     258                return EBADMEM;
     259
     260        if (size == 0 && setup == 0)
     261                return EOK;
     262
     263        aid_t opening_request = async_send_5(exch,
     264            DEV_IFACE_ID(USB_DEV_IFACE), IPC_M_USB_WRITE, endpoint, size,
     265            (setup & UINT32_MAX), (setup >> 32), NULL);
     266
     267        if (opening_request == 0) {
     268                return ENOMEM;
     269        }
     270
     271        /* Send the data if any. */
     272        if (size > 0) {
     273                const int ret = async_data_write_start(exch, data, size);
     274                if (ret != EOK) {
     275                        async_forget(opening_request);
     276                        return ret;
     277                }
     278        }
     279
     280        /* Wait for the answer. */
     281        sysarg_t opening_request_rc;
     282        async_wait_for(opening_request, &opening_request_rc);
     283
     284        return (int) opening_request_rc;
     285}
     286
    105287static void remote_usb_get_my_interface(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
    106 static void remote_usb_get_hc_handle(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
     288static void remote_usb_get_my_device_handle(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
     289static void remote_usb_reserve_default_address(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
     290static void remote_usb_release_default_address(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
     291static void remote_usb_device_enumerate(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
     292static void remote_usb_device_remove(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
     293static void remote_usb_register_endpoint(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
     294static void remote_usb_unregister_endpoint(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
     295static void remote_usb_read(ddf_fun_t *fun, void *iface, ipc_callid_t callid, ipc_call_t *call);
     296static void remote_usb_write(ddf_fun_t *fun, void *iface, ipc_callid_t callid, ipc_call_t *call);
    107297
    108298/** Remote USB interface operations. */
    109 static remote_iface_func_ptr_t remote_usb_iface_ops [] = {
    110         [IPC_M_USB_GET_MY_ADDRESS] = remote_usb_get_my_address,
     299static const remote_iface_func_ptr_t remote_usb_iface_ops [] = {
    111300        [IPC_M_USB_GET_MY_INTERFACE] = remote_usb_get_my_interface,
    112         [IPC_M_USB_GET_HOST_CONTROLLER_HANDLE] = remote_usb_get_hc_handle,
     301        [IPC_M_USB_GET_MY_DEVICE_HANDLE] = remote_usb_get_my_device_handle,
     302        [IPC_M_USB_RESERVE_DEFAULT_ADDRESS] = remote_usb_reserve_default_address,
     303        [IPC_M_USB_RELEASE_DEFAULT_ADDRESS] = remote_usb_release_default_address,
     304        [IPC_M_USB_DEVICE_ENUMERATE] = remote_usb_device_enumerate,
     305        [IPC_M_USB_DEVICE_REMOVE] = remote_usb_device_remove,
     306        [IPC_M_USB_REGISTER_ENDPOINT] = remote_usb_register_endpoint,
     307        [IPC_M_USB_UNREGISTER_ENDPOINT] = remote_usb_unregister_endpoint,
     308        [IPC_M_USB_READ] = remote_usb_read,
     309        [IPC_M_USB_WRITE] = remote_usb_write,
    113310};
    114311
    115312/** Remote USB interface structure.
    116313 */
    117 remote_iface_t remote_usb_iface = {
    118         .method_count = sizeof(remote_usb_iface_ops) /
    119             sizeof(remote_usb_iface_ops[0]),
    120         .methods = remote_usb_iface_ops
     314const remote_iface_t remote_usb_iface = {
     315        .method_count = ARRAY_SIZE(remote_usb_iface_ops),
     316        .methods = remote_usb_iface_ops,
    121317};
    122318
    123 /*----------------------------------------------------------------------------*/
    124 void remote_usb_get_my_address(ddf_fun_t *fun, void *iface,
    125     ipc_callid_t callid, ipc_call_t *call)
    126 {
    127         const usb_iface_t *usb_iface = (usb_iface_t *) iface;
    128 
    129         if (usb_iface->get_my_address == NULL) {
    130                 async_answer_0(callid, ENOTSUP);
    131                 return;
    132         }
    133 
    134         usb_address_t address;
    135         const int ret = usb_iface->get_my_address(fun, &address);
    136         if (ret != EOK) {
    137                 async_answer_0(callid, ret);
    138         } else {
    139                 async_answer_1(callid, EOK, address);
    140         }
    141 }
    142 /*----------------------------------------------------------------------------*/
    143319void remote_usb_get_my_interface(ddf_fun_t *fun, void *iface,
    144320    ipc_callid_t callid, ipc_call_t *call)
     
    159335        }
    160336}
    161 /*----------------------------------------------------------------------------*/
    162 void remote_usb_get_hc_handle(ddf_fun_t *fun, void *iface,
     337
     338void remote_usb_get_my_device_handle(ddf_fun_t *fun, void *iface,
    163339    ipc_callid_t callid, ipc_call_t *call)
    164340{
    165341        const usb_iface_t *usb_iface = (usb_iface_t *) iface;
    166342
    167         if (usb_iface->get_hc_handle == NULL) {
     343        if (usb_iface->get_my_device_handle == NULL) {
    168344                async_answer_0(callid, ENOTSUP);
    169345                return;
     
    171347
    172348        devman_handle_t handle;
    173         const int ret = usb_iface->get_hc_handle(fun, &handle);
     349        const int ret = usb_iface->get_my_device_handle(fun, &handle);
    174350        if (ret != EOK) {
    175351                async_answer_0(callid, ret);
     
    178354        async_answer_1(callid, EOK, (sysarg_t) handle);
    179355}
     356
     357void remote_usb_reserve_default_address(ddf_fun_t *fun, void *iface,
     358    ipc_callid_t callid, ipc_call_t *call)
     359{
     360        const usb_iface_t *usb_iface = (usb_iface_t *) iface;
     361
     362        if (usb_iface->reserve_default_address == NULL) {
     363                async_answer_0(callid, ENOTSUP);
     364                return;
     365        }
     366
     367        usb_speed_t speed = DEV_IPC_GET_ARG1(*call);
     368        const int ret = usb_iface->reserve_default_address(fun, speed);
     369        async_answer_0(callid, ret);
     370}
     371
     372void remote_usb_release_default_address(ddf_fun_t *fun, void *iface,
     373    ipc_callid_t callid, ipc_call_t *call)
     374{
     375        const usb_iface_t *usb_iface = (usb_iface_t *) iface;
     376
     377        if (usb_iface->release_default_address == NULL) {
     378                async_answer_0(callid, ENOTSUP);
     379                return;
     380        }
     381
     382        const int ret = usb_iface->release_default_address(fun);
     383        async_answer_0(callid, ret);
     384}
     385
     386static void remote_usb_device_enumerate(ddf_fun_t *fun, void *iface,
     387    ipc_callid_t callid, ipc_call_t *call)
     388{
     389        const usb_iface_t *usb_iface = (usb_iface_t *) iface;
     390
     391        if (usb_iface->device_enumerate == NULL) {
     392                async_answer_0(callid, ENOTSUP);
     393                return;
     394        }
     395
     396        const unsigned port = DEV_IPC_GET_ARG1(*call);
     397        const int ret = usb_iface->device_enumerate(fun, port);
     398        async_answer_0(callid, ret);
     399}
     400
     401static void remote_usb_device_remove(ddf_fun_t *fun, void *iface,
     402    ipc_callid_t callid, ipc_call_t *call)
     403{
     404        const usb_iface_t *usb_iface = (usb_iface_t *) iface;
     405
     406        if (usb_iface->device_remove == NULL) {
     407                async_answer_0(callid, ENOTSUP);
     408                return;
     409        }
     410
     411        const unsigned port = DEV_IPC_GET_ARG1(*call);
     412        const int ret = usb_iface->device_remove(fun, port);
     413        async_answer_0(callid, ret);
     414}
     415
     416static void remote_usb_register_endpoint(ddf_fun_t *fun, void *iface,
     417    ipc_callid_t callid, ipc_call_t *call)
     418{
     419        usb_iface_t *usb_iface = (usb_iface_t *) iface;
     420
     421        if (!usb_iface->register_endpoint) {
     422                async_answer_0(callid, ENOTSUP);
     423                return;
     424        }
     425
     426        const usb_endpoint_t endpoint = DEV_IPC_GET_ARG1(*call);
     427        const pack8_t pack = { .arg = DEV_IPC_GET_ARG2(*call)};
     428        const size_t max_packet_size = DEV_IPC_GET_ARG3(*call);
     429
     430        const usb_transfer_type_t transfer_type = pack.arr[0];
     431        const usb_direction_t direction = pack.arr[1];
     432        unsigned packets = pack.arr[2];
     433        unsigned interval = pack.arr[3];
     434
     435        const int ret = usb_iface->register_endpoint(fun, endpoint,
     436            transfer_type, direction, max_packet_size, packets, interval);
     437
     438        async_answer_0(callid, ret);
     439}
     440
     441static void remote_usb_unregister_endpoint(ddf_fun_t *fun, void *iface,
     442    ipc_callid_t callid, ipc_call_t *call)
     443{
     444        usb_iface_t *usb_iface = (usb_iface_t *) iface;
     445
     446        if (!usb_iface->unregister_endpoint) {
     447                async_answer_0(callid, ENOTSUP);
     448                return;
     449        }
     450
     451        usb_endpoint_t endpoint = (usb_endpoint_t) DEV_IPC_GET_ARG1(*call);
     452        usb_direction_t direction = (usb_direction_t) DEV_IPC_GET_ARG2(*call);
     453
     454        int rc = usb_iface->unregister_endpoint(fun, endpoint, direction);
     455
     456        async_answer_0(callid, rc);
     457}
     458
     459typedef struct {
     460        ipc_callid_t caller;
     461        ipc_callid_t data_caller;
     462        void *buffer;
     463} async_transaction_t;
     464
     465static void async_transaction_destroy(async_transaction_t *trans)
     466{
     467        if (trans == NULL) {
     468                return;
     469        }
     470        if (trans->buffer != NULL) {
     471                free(trans->buffer);
     472        }
     473
     474        free(trans);
     475}
     476
     477static async_transaction_t *async_transaction_create(ipc_callid_t caller)
     478{
     479        async_transaction_t *trans = malloc(sizeof(async_transaction_t));
     480        if (trans == NULL) {
     481                return NULL;
     482        }
     483
     484        trans->caller = caller;
     485        trans->data_caller = 0;
     486        trans->buffer = NULL;
     487
     488        return trans;
     489}
     490
     491static void callback_out(int outcome, void *arg)
     492{
     493        async_transaction_t *trans = arg;
     494
     495        async_answer_0(trans->caller, outcome);
     496
     497        async_transaction_destroy(trans);
     498}
     499
     500static void callback_in(int outcome, size_t actual_size, void *arg)
     501{
     502        async_transaction_t *trans = (async_transaction_t *)arg;
     503
     504        if (outcome != EOK) {
     505                async_answer_0(trans->caller, outcome);
     506                if (trans->data_caller) {
     507                        async_answer_0(trans->data_caller, EINTR);
     508                }
     509                async_transaction_destroy(trans);
     510                return;
     511        }
     512
     513        if (trans->data_caller) {
     514                async_data_read_finalize(trans->data_caller,
     515                    trans->buffer, actual_size);
     516        }
     517
     518        async_answer_0(trans->caller, EOK);
     519
     520        async_transaction_destroy(trans);
     521}
     522
     523void remote_usb_read(
     524    ddf_fun_t *fun, void *iface, ipc_callid_t callid, ipc_call_t *call)
     525{
     526        assert(fun);
     527        assert(iface);
     528        assert(call);
     529
     530        const usb_iface_t *usb_iface = iface;
     531
     532        if (!usb_iface->read) {
     533                async_answer_0(callid, ENOTSUP);
     534                return;
     535        }
     536
     537        const usb_endpoint_t ep = DEV_IPC_GET_ARG1(*call);
     538        const uint64_t setup =
     539            ((uint64_t)DEV_IPC_GET_ARG2(*call)) |
     540            (((uint64_t)DEV_IPC_GET_ARG3(*call)) << 32);
     541
     542        async_transaction_t *trans = async_transaction_create(callid);
     543        if (trans == NULL) {
     544                async_answer_0(callid, ENOMEM);
     545                return;
     546        }
     547
     548        size_t size = 0;
     549        if (!async_data_read_receive(&trans->data_caller, &size)) {
     550                async_answer_0(callid, EPARTY);
     551                async_transaction_destroy(trans);
     552                return;
     553        }
     554
     555        trans->buffer = malloc(size);
     556        if (trans->buffer == NULL) {
     557                async_answer_0(trans->data_caller, ENOMEM);
     558                async_answer_0(callid, ENOMEM);
     559                async_transaction_destroy(trans);
     560                return;
     561        }
     562
     563        const int rc = usb_iface->read(
     564            fun, ep, setup, trans->buffer, size, callback_in, trans);
     565
     566        if (rc != EOK) {
     567                async_answer_0(trans->data_caller, rc);
     568                async_answer_0(callid, rc);
     569                async_transaction_destroy(trans);
     570        }
     571}
     572
     573void remote_usb_write(
     574    ddf_fun_t *fun, void *iface, ipc_callid_t callid, ipc_call_t *call)
     575{
     576        assert(fun);
     577        assert(iface);
     578        assert(call);
     579
     580        const usb_iface_t *usb_iface = iface;
     581
     582        if (!usb_iface->write) {
     583                async_answer_0(callid, ENOTSUP);
     584                return;
     585        }
     586
     587        const usb_endpoint_t ep = DEV_IPC_GET_ARG1(*call);
     588        const size_t data_buffer_len = DEV_IPC_GET_ARG2(*call);
     589        const uint64_t setup =
     590            ((uint64_t)DEV_IPC_GET_ARG3(*call)) |
     591            (((uint64_t)DEV_IPC_GET_ARG4(*call)) << 32);
     592
     593        async_transaction_t *trans = async_transaction_create(callid);
     594        if (trans == NULL) {
     595                async_answer_0(callid, ENOMEM);
     596                return;
     597        }
     598
     599        size_t size = 0;
     600        if (data_buffer_len > 0) {
     601                const int rc = async_data_write_accept(&trans->buffer, false,
     602                    1, data_buffer_len, 0, &size);
     603
     604                if (rc != EOK) {
     605                        async_answer_0(callid, rc);
     606                        async_transaction_destroy(trans);
     607                        return;
     608                }
     609        }
     610
     611        const int rc = usb_iface->write(
     612            fun, ep, setup, trans->buffer, size, callback_out, trans);
     613
     614        if (rc != EOK) {
     615                async_answer_0(callid, rc);
     616                async_transaction_destroy(trans);
     617        }
     618}
    180619/**
    181620 * @}
Note: See TracChangeset for help on using the changeset viewer.