Ignore:
File:
1 edited

Legend:

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

    r9d58539 r0918382f  
    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(EXCHANGE_PARALLEL, handle, IPC_FLAG_BLOCKING);
     48}
     49
     50usb_dev_session_t *usb_dev_connect_to_self(ddf_dev_t *dev)
     51{
     52        // TODO All usb requests are atomic so this is safe,
     53        // it will need to change once USING EXCHANGE PARALLEL is safe with
     54        // devman_parent_device_connect
     55        return devman_parent_device_connect(EXCHANGE_ATOMIC,
     56            ddf_dev_get_handle(dev), IPC_FLAG_BLOCKING);
     57}
     58
     59void usb_dev_disconnect(usb_dev_session_t *sess)
     60{
     61        if (sess)
     62                async_hangup(sess);
     63}
     64
    4265typedef enum {
    43         IPC_M_USB_GET_MY_ADDRESS,
    4466        IPC_M_USB_GET_MY_INTERFACE,
    45         IPC_M_USB_GET_HOST_CONTROLLER_HANDLE,
     67        IPC_M_USB_GET_MY_DEVICE_HANDLE,
     68        IPC_M_USB_RESERVE_DEFAULT_ADDRESS,
     69        IPC_M_USB_RELEASE_DEFAULT_ADDRESS,
     70        IPC_M_USB_DEVICE_ENUMERATE,
     71        IPC_M_USB_DEVICE_REMOVE,
     72        IPC_M_USB_REGISTER_ENDPOINT,
     73        IPC_M_USB_UNREGISTER_ENDPOINT,
     74        IPC_M_USB_READ,
     75        IPC_M_USB_WRITE,
    4676} usb_iface_funcs_t;
    4777
    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 /*----------------------------------------------------------------------------*/
    6878/** Tell interface number given device can use.
    6979 * @param[in] exch IPC communication exchange
     
    7585{
    7686        if (!exch)
    77                 return EINVAL;
     87                return EBADMEM;
    7888        sysarg_t iface_no;
    7989        const int ret = async_req_1_1(exch, DEV_IFACE_ID(USB_DEV_IFACE),
     
    8393        return ret;
    8494}
    85 /*----------------------------------------------------------------------------*/
    86 /** Tell devman handle of device host controller.
     95
     96/** Tell devman handle of the usb device function.
    8797 * @param[in] exch IPC communication exchange
    88  * @param[out] hc_handle devman handle of the HC used by the target device.
     98 * @param[out] handle devman handle of the HC used by the target device.
    8999 * @return Error code.
    90100 */
    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;
     101int usb_get_my_device_handle(async_exch_t *exch, devman_handle_t *handle)
     102{
     103        devman_handle_t h = 0;
    96104        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;
     105            IPC_M_USB_GET_MY_DEVICE_HANDLE, &h);
     106        if (ret == EOK && handle)
     107                *handle = (devman_handle_t)h;
    100108        return ret;
    101109}
    102110
    103 
    104 static void remote_usb_get_my_address(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
     111/** Reserve default USB address.
     112 * @param[in] exch IPC communication exchange
     113 * @param[in] speed Communication speed of the newly attached device
     114 * @return Error code.
     115 */
     116int usb_reserve_default_address(async_exch_t *exch, usb_speed_t speed)
     117{
     118        if (!exch)
     119                return EBADMEM;
     120        return async_req_2_0(exch, DEV_IFACE_ID(USB_DEV_IFACE),
     121            IPC_M_USB_RESERVE_DEFAULT_ADDRESS, speed);
     122}
     123
     124/** Release default USB address.
     125 * @param[in] exch IPC communication exchange
     126 * @return Error code.
     127 */
     128int usb_release_default_address(async_exch_t *exch)
     129{
     130        if (!exch)
     131                return EBADMEM;
     132        return async_req_1_0(exch, DEV_IFACE_ID(USB_DEV_IFACE),
     133            IPC_M_USB_RELEASE_DEFAULT_ADDRESS);
     134}
     135
     136/** Trigger USB device enumeration
     137 * @param[in] exch IPC communication exchange
     138 * @param[out] handle Identifier of the newly added device (if successful)
     139 * @return Error code.
     140 */
     141int usb_device_enumerate(async_exch_t *exch, unsigned port)
     142{
     143        if (!exch)
     144                return EBADMEM;
     145        const int ret = async_req_2_0(exch, DEV_IFACE_ID(USB_DEV_IFACE),
     146            IPC_M_USB_DEVICE_ENUMERATE, port);
     147        return ret;
     148}
     149
     150/** Trigger USB device enumeration
     151 * @param[in] exch IPC communication exchange
     152 * @param[in] handle Identifier of the device
     153 * @return Error code.
     154 */
     155int usb_device_remove(async_exch_t *exch, unsigned port)
     156{
     157        if (!exch)
     158                return EBADMEM;
     159        return async_req_2_0(exch, DEV_IFACE_ID(USB_DEV_IFACE),
     160            IPC_M_USB_DEVICE_REMOVE, port);
     161}
     162
     163int usb_register_endpoint(async_exch_t *exch, usb_endpoint_t endpoint,
     164    usb_transfer_type_t type, usb_direction_t direction,
     165    size_t mps, unsigned interval)
     166{
     167        if (!exch)
     168                return EBADMEM;
     169#define _PACK2(high, low) (((high & 0xffff) << 16) | (low & 0xffff))
     170
     171        return async_req_4_0(exch, DEV_IFACE_ID(USB_DEV_IFACE),
     172            IPC_M_USB_REGISTER_ENDPOINT, endpoint,
     173            _PACK2(type, direction), _PACK2(mps, interval));
     174
     175#undef _PACK2
     176}
     177
     178int usb_unregister_endpoint(async_exch_t *exch, usb_endpoint_t endpoint,
     179    usb_direction_t direction)
     180{
     181        if (!exch)
     182                return EBADMEM;
     183        return async_req_3_0(exch, DEV_IFACE_ID(USB_DEV_IFACE),
     184            IPC_M_USB_UNREGISTER_ENDPOINT, endpoint, direction);
     185}
     186
     187int usb_read(async_exch_t *exch, usb_endpoint_t endpoint, uint64_t setup,
     188    void *data, size_t size, size_t *rec_size)
     189{
     190        if (!exch)
     191                return EBADMEM;
     192
     193        if (size == 0 && setup == 0)
     194                return EOK;
     195
     196        /* Make call identifying target USB device and type of transfer. */
     197        aid_t opening_request = async_send_4(exch,
     198            DEV_IFACE_ID(USB_DEV_IFACE), IPC_M_USB_READ, endpoint,
     199            (setup & UINT32_MAX), (setup >> 32), NULL);
     200
     201        if (opening_request == 0) {
     202                return ENOMEM;
     203        }
     204
     205        /* Retrieve the data. */
     206        ipc_call_t data_request_call;
     207        aid_t data_request =
     208            async_data_read(exch, data, size, &data_request_call);
     209
     210        if (data_request == 0) {
     211                // FIXME: How to let the other side know that we want to abort?
     212                async_forget(opening_request);
     213                return ENOMEM;
     214        }
     215
     216        /* Wait for the answer. */
     217        sysarg_t data_request_rc;
     218        sysarg_t opening_request_rc;
     219        async_wait_for(data_request, &data_request_rc);
     220        async_wait_for(opening_request, &opening_request_rc);
     221
     222        if (data_request_rc != EOK) {
     223                /* Prefer the return code of the opening request. */
     224                if (opening_request_rc != EOK) {
     225                        return (int) opening_request_rc;
     226                } else {
     227                        return (int) data_request_rc;
     228                }
     229        }
     230        if (opening_request_rc != EOK) {
     231                return (int) opening_request_rc;
     232        }
     233
     234        *rec_size = IPC_GET_ARG2(data_request_call);
     235        return EOK;
     236}
     237
     238int usb_write(async_exch_t *exch, usb_endpoint_t endpoint, uint64_t setup,
     239    const void *data, size_t size)
     240{
     241        if (!exch)
     242                return EBADMEM;
     243
     244        if (size == 0 && setup == 0)
     245                return EOK;
     246
     247        aid_t opening_request = async_send_5(exch,
     248            DEV_IFACE_ID(USB_DEV_IFACE), IPC_M_USB_WRITE, endpoint, size,
     249            (setup & UINT32_MAX), (setup >> 32), NULL);
     250
     251        if (opening_request == 0) {
     252                return ENOMEM;
     253        }
     254
     255        /* Send the data if any. */
     256        if (size > 0) {
     257                const int ret = async_data_write_start(exch, data, size);
     258                if (ret != EOK) {
     259                        async_forget(opening_request);
     260                        return ret;
     261                }
     262        }
     263
     264        /* Wait for the answer. */
     265        sysarg_t opening_request_rc;
     266        async_wait_for(opening_request, &opening_request_rc);
     267
     268        return (int) opening_request_rc;
     269}
     270
    105271static 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 *);
     272static void remote_usb_get_my_device_handle(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
     273static void remote_usb_reserve_default_address(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
     274static void remote_usb_release_default_address(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
     275static void remote_usb_device_enumerate(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
     276static void remote_usb_device_remove(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
     277static void remote_usb_register_endpoint(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
     278static void remote_usb_unregister_endpoint(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
     279static void remote_usb_read(ddf_fun_t *fun, void *iface, ipc_callid_t callid, ipc_call_t *call);
     280static void remote_usb_write(ddf_fun_t *fun, void *iface, ipc_callid_t callid, ipc_call_t *call);
    107281
    108282/** Remote USB interface operations. */
    109283static remote_iface_func_ptr_t remote_usb_iface_ops [] = {
    110         [IPC_M_USB_GET_MY_ADDRESS] = remote_usb_get_my_address,
    111284        [IPC_M_USB_GET_MY_INTERFACE] = remote_usb_get_my_interface,
    112         [IPC_M_USB_GET_HOST_CONTROLLER_HANDLE] = remote_usb_get_hc_handle,
     285        [IPC_M_USB_GET_MY_DEVICE_HANDLE] = remote_usb_get_my_device_handle,
     286        [IPC_M_USB_RESERVE_DEFAULT_ADDRESS] = remote_usb_reserve_default_address,
     287        [IPC_M_USB_RELEASE_DEFAULT_ADDRESS] = remote_usb_release_default_address,
     288        [IPC_M_USB_DEVICE_ENUMERATE] = remote_usb_device_enumerate,
     289        [IPC_M_USB_DEVICE_REMOVE] = remote_usb_device_remove,
     290        [IPC_M_USB_REGISTER_ENDPOINT] = remote_usb_register_endpoint,
     291        [IPC_M_USB_UNREGISTER_ENDPOINT] = remote_usb_unregister_endpoint,
     292        [IPC_M_USB_READ] = remote_usb_read,
     293        [IPC_M_USB_WRITE] = remote_usb_write,
    113294};
    114295
     
    116297 */
    117298remote_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
     299        .method_count = ARRAY_SIZE(remote_usb_iface_ops),
     300        .methods = remote_usb_iface_ops,
    121301};
    122302
    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 /*----------------------------------------------------------------------------*/
    143303void remote_usb_get_my_interface(ddf_fun_t *fun, void *iface,
    144304    ipc_callid_t callid, ipc_call_t *call)
     
    159319        }
    160320}
    161 /*----------------------------------------------------------------------------*/
    162 void remote_usb_get_hc_handle(ddf_fun_t *fun, void *iface,
     321
     322void remote_usb_get_my_device_handle(ddf_fun_t *fun, void *iface,
    163323    ipc_callid_t callid, ipc_call_t *call)
    164324{
    165325        const usb_iface_t *usb_iface = (usb_iface_t *) iface;
    166326
    167         if (usb_iface->get_hc_handle == NULL) {
     327        if (usb_iface->get_my_device_handle == NULL) {
    168328                async_answer_0(callid, ENOTSUP);
    169329                return;
     
    171331
    172332        devman_handle_t handle;
    173         const int ret = usb_iface->get_hc_handle(fun, &handle);
     333        const int ret = usb_iface->get_my_device_handle(fun, &handle);
    174334        if (ret != EOK) {
    175335                async_answer_0(callid, ret);
     
    178338        async_answer_1(callid, EOK, (sysarg_t) handle);
    179339}
     340
     341void remote_usb_reserve_default_address(ddf_fun_t *fun, void *iface,
     342    ipc_callid_t callid, ipc_call_t *call)
     343{
     344        const usb_iface_t *usb_iface = (usb_iface_t *) iface;
     345
     346        if (usb_iface->reserve_default_address == NULL) {
     347                async_answer_0(callid, ENOTSUP);
     348                return;
     349        }
     350
     351        usb_speed_t speed = DEV_IPC_GET_ARG1(*call);
     352        const int ret = usb_iface->reserve_default_address(fun, speed);
     353        async_answer_0(callid, ret);
     354}
     355
     356void remote_usb_release_default_address(ddf_fun_t *fun, void *iface,
     357    ipc_callid_t callid, ipc_call_t *call)
     358{
     359        const usb_iface_t *usb_iface = (usb_iface_t *) iface;
     360
     361        if (usb_iface->release_default_address == NULL) {
     362                async_answer_0(callid, ENOTSUP);
     363                return;
     364        }
     365
     366        const int ret = usb_iface->release_default_address(fun);
     367        async_answer_0(callid, ret);
     368}
     369
     370static void remote_usb_device_enumerate(ddf_fun_t *fun, void *iface,
     371    ipc_callid_t callid, ipc_call_t *call)
     372{
     373        const usb_iface_t *usb_iface = (usb_iface_t *) iface;
     374
     375        if (usb_iface->device_enumerate == NULL) {
     376                async_answer_0(callid, ENOTSUP);
     377                return;
     378        }
     379
     380        const unsigned port = DEV_IPC_GET_ARG1(*call);
     381        const int ret = usb_iface->device_enumerate(fun, port);
     382        async_answer_0(callid, ret);
     383}
     384
     385static void remote_usb_device_remove(ddf_fun_t *fun, void *iface,
     386    ipc_callid_t callid, ipc_call_t *call)
     387{
     388        const usb_iface_t *usb_iface = (usb_iface_t *) iface;
     389
     390        if (usb_iface->device_remove == NULL) {
     391                async_answer_0(callid, ENOTSUP);
     392                return;
     393        }
     394
     395        const unsigned port = DEV_IPC_GET_ARG1(*call);
     396        const int ret = usb_iface->device_remove(fun, port);
     397        async_answer_0(callid, ret);
     398}
     399
     400static void remote_usb_register_endpoint(ddf_fun_t *fun, void *iface,
     401    ipc_callid_t callid, ipc_call_t *call)
     402{
     403        usb_iface_t *usb_iface = (usb_iface_t *) iface;
     404
     405        if (!usb_iface->register_endpoint) {
     406                async_answer_0(callid, ENOTSUP);
     407                return;
     408        }
     409
     410#define _INIT_FROM_HIGH_DATA2(type, var, arg_no) \
     411        type var = (type) (DEV_IPC_GET_ARG##arg_no(*call) >> 16)
     412#define _INIT_FROM_LOW_DATA2(type, var, arg_no) \
     413        type var = (type) (DEV_IPC_GET_ARG##arg_no(*call) & 0xffff)
     414
     415        const usb_endpoint_t endpoint = DEV_IPC_GET_ARG1(*call);
     416
     417        _INIT_FROM_HIGH_DATA2(usb_transfer_type_t, transfer_type, 2);
     418        _INIT_FROM_LOW_DATA2(usb_direction_t, direction, 2);
     419
     420        _INIT_FROM_HIGH_DATA2(size_t, max_packet_size, 3);
     421        _INIT_FROM_LOW_DATA2(unsigned int, interval, 3);
     422
     423#undef _INIT_FROM_HIGH_DATA2
     424#undef _INIT_FROM_LOW_DATA2
     425
     426        const int ret = usb_iface->register_endpoint(fun, endpoint,
     427            transfer_type, direction, max_packet_size, interval);
     428
     429        async_answer_0(callid, ret);
     430}
     431
     432static void remote_usb_unregister_endpoint(ddf_fun_t *fun, void *iface,
     433    ipc_callid_t callid, ipc_call_t *call)
     434{
     435        usb_iface_t *usb_iface = (usb_iface_t *) iface;
     436
     437        if (!usb_iface->unregister_endpoint) {
     438                async_answer_0(callid, ENOTSUP);
     439                return;
     440        }
     441
     442        usb_endpoint_t endpoint = (usb_endpoint_t) DEV_IPC_GET_ARG1(*call);
     443        usb_direction_t direction = (usb_direction_t) DEV_IPC_GET_ARG2(*call);
     444
     445        int rc = usb_iface->unregister_endpoint(fun, endpoint, direction);
     446
     447        async_answer_0(callid, rc);
     448}
     449
     450typedef struct {
     451        ipc_callid_t caller;
     452        ipc_callid_t data_caller;
     453        void *buffer;
     454} async_transaction_t;
     455
     456static void async_transaction_destroy(async_transaction_t *trans)
     457{
     458        if (trans == NULL) {
     459                return;
     460        }
     461        if (trans->buffer != NULL) {
     462                free(trans->buffer);
     463        }
     464
     465        free(trans);
     466}
     467
     468static async_transaction_t *async_transaction_create(ipc_callid_t caller)
     469{
     470        async_transaction_t *trans = malloc(sizeof(async_transaction_t));
     471        if (trans == NULL) {
     472                return NULL;
     473        }
     474
     475        trans->caller = caller;
     476        trans->data_caller = 0;
     477        trans->buffer = NULL;
     478
     479        return trans;
     480}
     481
     482static void callback_out(int outcome, void *arg)
     483{
     484        async_transaction_t *trans = arg;
     485
     486        async_answer_0(trans->caller, outcome);
     487
     488        async_transaction_destroy(trans);
     489}
     490
     491static void callback_in(int outcome, size_t actual_size, void *arg)
     492{
     493        async_transaction_t *trans = (async_transaction_t *)arg;
     494
     495        if (outcome != EOK) {
     496                async_answer_0(trans->caller, outcome);
     497                if (trans->data_caller) {
     498                        async_answer_0(trans->data_caller, EINTR);
     499                }
     500                async_transaction_destroy(trans);
     501                return;
     502        }
     503
     504        if (trans->data_caller) {
     505                async_data_read_finalize(trans->data_caller,
     506                    trans->buffer, actual_size);
     507        }
     508
     509        async_answer_0(trans->caller, EOK);
     510
     511        async_transaction_destroy(trans);
     512}
     513
     514void remote_usb_read(
     515    ddf_fun_t *fun, void *iface, ipc_callid_t callid, ipc_call_t *call)
     516{
     517        assert(fun);
     518        assert(iface);
     519        assert(call);
     520
     521        const usb_iface_t *usb_iface = iface;
     522
     523        if (!usb_iface->read) {
     524                async_answer_0(callid, ENOTSUP);
     525                return;
     526        }
     527
     528        const usb_endpoint_t ep = DEV_IPC_GET_ARG1(*call);
     529        const uint64_t setup =
     530            ((uint64_t)DEV_IPC_GET_ARG2(*call)) |
     531            (((uint64_t)DEV_IPC_GET_ARG3(*call)) << 32);
     532
     533        async_transaction_t *trans = async_transaction_create(callid);
     534        if (trans == NULL) {
     535                async_answer_0(callid, ENOMEM);
     536                return;
     537        }
     538
     539        size_t size = 0;
     540        if (!async_data_read_receive(&trans->data_caller, &size)) {
     541                async_answer_0(callid, EPARTY);
     542                return;
     543        }
     544
     545        trans->buffer = malloc(size);
     546        if (trans->buffer == NULL) {
     547                async_answer_0(trans->data_caller, ENOMEM);
     548                async_answer_0(callid, ENOMEM);
     549                async_transaction_destroy(trans);
     550        }
     551
     552        const int rc = usb_iface->read(
     553            fun, ep, setup, trans->buffer, size, callback_in, trans);
     554
     555        if (rc != EOK) {
     556                async_answer_0(trans->data_caller, rc);
     557                async_answer_0(callid, rc);
     558                async_transaction_destroy(trans);
     559        }
     560}
     561
     562void remote_usb_write(
     563    ddf_fun_t *fun, void *iface, ipc_callid_t callid, ipc_call_t *call)
     564{
     565        assert(fun);
     566        assert(iface);
     567        assert(call);
     568
     569        const usb_iface_t *usb_iface = iface;
     570
     571        if (!usb_iface->write) {
     572                async_answer_0(callid, ENOTSUP);
     573                return;
     574        }
     575
     576        const usb_endpoint_t ep = DEV_IPC_GET_ARG1(*call);
     577        const size_t data_buffer_len = DEV_IPC_GET_ARG2(*call);
     578        const uint64_t setup =
     579            ((uint64_t)DEV_IPC_GET_ARG3(*call)) |
     580            (((uint64_t)DEV_IPC_GET_ARG4(*call)) << 32);
     581
     582        async_transaction_t *trans = async_transaction_create(callid);
     583        if (trans == NULL) {
     584                async_answer_0(callid, ENOMEM);
     585                return;
     586        }
     587
     588        size_t size = 0;
     589        if (data_buffer_len > 0) {
     590                const int rc = async_data_write_accept(&trans->buffer, false,
     591                    1, data_buffer_len, 0, &size);
     592
     593                if (rc != EOK) {
     594                        async_answer_0(callid, rc);
     595                        async_transaction_destroy(trans);
     596                        return;
     597                }
     598        }
     599
     600        const int rc = usb_iface->write(
     601            fun, ep, setup, trans->buffer, size, callback_out, trans);
     602
     603        if (rc != EOK) {
     604                async_answer_0(callid, rc);
     605                async_transaction_destroy(trans);
     606        }
     607}
    180608/**
    181609 * @}
Note: See TracChangeset for help on using the changeset viewer.