source: mainline/uspace/lib/drv/generic/remote_usbhc.c@ b00849e

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since b00849e was 9753220, checked in by Vojtech Horky <vojtechhorky@…>, 15 years ago

Add atomic control transfers to remote USBHC

Only the remote part is implemented. This commit might have serious BUGS.

  • Property mode set to 100644
File size: 16.2 KB
RevLine 
[91db50ac]1/*
[9753220]2 * Copyright (c) 2010-2011 Vojtech Horky
[91db50ac]3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/** @addtogroup libdrv
30 * @{
31 */
32/** @file
33 */
34
35#include <ipc/ipc.h>
36#include <async.h>
37#include <errno.h>
38
[cb59f787]39#include "usbhc_iface.h"
[91db50ac]40#include "driver.h"
41
[1b22bd4]42#define USB_MAX_PAYLOAD_SIZE 1020
43
[aae339e9]44static void remote_usbhc_get_address(device_t *, void *, ipc_callid_t, ipc_call_t *);
[cb59f787]45static void remote_usbhc_get_buffer(device_t *, void *, ipc_callid_t, ipc_call_t *);
46static void remote_usbhc_interrupt_out(device_t *, void *, ipc_callid_t, ipc_call_t *);
47static void remote_usbhc_interrupt_in(device_t *, void *, ipc_callid_t, ipc_call_t *);
[a3dfb2e]48static void remote_usbhc_control_write_setup(device_t *, void *, ipc_callid_t, ipc_call_t *);
49static void remote_usbhc_control_write_data(device_t *, void *, ipc_callid_t, ipc_call_t *);
50static void remote_usbhc_control_write_status(device_t *, void *, ipc_callid_t, ipc_call_t *);
51static void remote_usbhc_control_read_setup(device_t *, void *, ipc_callid_t, ipc_call_t *);
52static void remote_usbhc_control_read_data(device_t *, void *, ipc_callid_t, ipc_call_t *);
53static void remote_usbhc_control_read_status(device_t *, void *, ipc_callid_t, ipc_call_t *);
[9753220]54static void remote_usbhc_control_write(device_t *, void *, ipc_callid_t, ipc_call_t *);
55static void remote_usbhc_control_read(device_t *, void *, ipc_callid_t, ipc_call_t *);
[6f04905]56static void remote_usbhc_reserve_default_address(device_t *, void *, ipc_callid_t, ipc_call_t *);
57static void remote_usbhc_release_default_address(device_t *, void *, ipc_callid_t, ipc_call_t *);
58static void remote_usbhc_request_address(device_t *, void *, ipc_callid_t, ipc_call_t *);
[4689d40]59static void remote_usbhc_bind_address(device_t *, void *, ipc_callid_t, ipc_call_t *);
[6f04905]60static void remote_usbhc_release_address(device_t *, void *, ipc_callid_t, ipc_call_t *);
[a3dfb2e]61//static void remote_usbhc(device_t *, void *, ipc_callid_t, ipc_call_t *);
[91db50ac]62
[6edd494]63/** Remote USB host controller interface operations. */
[cb59f787]64static remote_iface_func_ptr_t remote_usbhc_iface_ops [] = {
[aae339e9]65 remote_usbhc_get_address,
[6f04905]66
[aae339e9]67 remote_usbhc_get_buffer,
[6f04905]68
69 remote_usbhc_reserve_default_address,
70 remote_usbhc_release_default_address,
71
72 remote_usbhc_request_address,
[4689d40]73 remote_usbhc_bind_address,
[6f04905]74 remote_usbhc_release_address,
75
[aae339e9]76 remote_usbhc_interrupt_out,
[a3dfb2e]77 remote_usbhc_interrupt_in,
[6f04905]78
[a3dfb2e]79 remote_usbhc_control_write_setup,
80 remote_usbhc_control_write_data,
81 remote_usbhc_control_write_status,
[6f04905]82
[a3dfb2e]83 remote_usbhc_control_read_setup,
84 remote_usbhc_control_read_data,
[9753220]85 remote_usbhc_control_read_status,
86
87 remote_usbhc_control_write,
88 remote_usbhc_control_read
[91db50ac]89};
90
[6edd494]91/** Remote USB host controller interface structure.
[91db50ac]92 */
[cb59f787]93remote_iface_t remote_usbhc_iface = {
94 .method_count = sizeof(remote_usbhc_iface_ops) /
95 sizeof(remote_usbhc_iface_ops[0]),
96 .methods = remote_usbhc_iface_ops
[91db50ac]97};
98
[1b22bd4]99typedef struct {
100 ipc_callid_t caller;
101 void *buffer;
[9753220]102 void *setup_packet;
[1b22bd4]103 size_t size;
104} async_transaction_t;
[91db50ac]105
[aae339e9]106void remote_usbhc_get_address(device_t *device, void *iface,
107 ipc_callid_t callid, ipc_call_t *call)
108{
109 usbhc_iface_t *usb_iface = (usbhc_iface_t *) iface;
110
111 if (!usb_iface->tell_address) {
112 ipc_answer_0(callid, ENOTSUP);
113 return;
114 }
115
[eac610e]116 devman_handle_t handle = DEV_IPC_GET_ARG1(*call);
[aae339e9]117
118 usb_address_t address;
119 int rc = usb_iface->tell_address(device, handle, &address);
120 if (rc != EOK) {
121 ipc_answer_0(callid, rc);
122 } else {
123 ipc_answer_1(callid, EOK, address);
124 }
125}
[91db50ac]126
[cb59f787]127void remote_usbhc_get_buffer(device_t *device, void *iface,
[91db50ac]128 ipc_callid_t callid, ipc_call_t *call)
129{
[a9b6bec]130 sysarg_t buffer_hash = DEV_IPC_GET_ARG1(*call);
[1b22bd4]131 async_transaction_t * trans = (async_transaction_t *)buffer_hash;
132 if (trans == NULL) {
133 ipc_answer_0(callid, ENOENT);
134 return;
135 }
136 if (trans->buffer == NULL) {
137 ipc_answer_0(callid, EINVAL);
138 free(trans);
139 return;
140 }
141
142 ipc_callid_t cid;
143 size_t accepted_size;
144 if (!async_data_read_receive(&cid, &accepted_size)) {
145 ipc_answer_0(callid, EINVAL);
146 return;
147 }
148
149 if (accepted_size > trans->size) {
150 accepted_size = trans->size;
151 }
[eac610e]152 async_data_read_finalize(cid, trans->buffer, accepted_size);
[1b22bd4]153
154 ipc_answer_1(callid, EOK, accepted_size);
155
156 free(trans->buffer);
157 free(trans);
158}
159
[6f04905]160void remote_usbhc_reserve_default_address(device_t *device, void *iface,
161 ipc_callid_t callid, ipc_call_t *call)
162{
163 usbhc_iface_t *usb_iface = (usbhc_iface_t *) iface;
164
165 if (!usb_iface->reserve_default_address) {
166 ipc_answer_0(callid, ENOTSUP);
167 return;
168 }
169
170 int rc = usb_iface->reserve_default_address(device);
171
172 ipc_answer_0(callid, rc);
173}
174
175void remote_usbhc_release_default_address(device_t *device, void *iface,
176 ipc_callid_t callid, ipc_call_t *call)
177{
178 usbhc_iface_t *usb_iface = (usbhc_iface_t *) iface;
179
180 if (!usb_iface->release_default_address) {
181 ipc_answer_0(callid, ENOTSUP);
182 return;
183 }
184
185 int rc = usb_iface->release_default_address(device);
186
187 ipc_answer_0(callid, rc);
188}
189
190void remote_usbhc_request_address(device_t *device, void *iface,
191 ipc_callid_t callid, ipc_call_t *call)
192{
193 usbhc_iface_t *usb_iface = (usbhc_iface_t *) iface;
194
195 if (!usb_iface->request_address) {
196 ipc_answer_0(callid, ENOTSUP);
197 return;
198 }
199
200 usb_address_t address;
201 int rc = usb_iface->request_address(device, &address);
202 if (rc != EOK) {
203 ipc_answer_0(callid, rc);
204 } else {
[a9b6bec]205 ipc_answer_1(callid, EOK, (sysarg_t) address);
[6f04905]206 }
207}
208
[4689d40]209void remote_usbhc_bind_address(device_t *device, void *iface,
210 ipc_callid_t callid, ipc_call_t *call)
211{
212 usbhc_iface_t *usb_iface = (usbhc_iface_t *) iface;
213
214 if (!usb_iface->bind_address) {
215 ipc_answer_0(callid, ENOTSUP);
216 return;
217 }
218
[eac610e]219 usb_address_t address = (usb_address_t) DEV_IPC_GET_ARG1(*call);
220 devman_handle_t handle = (devman_handle_t) DEV_IPC_GET_ARG2(*call);
[4689d40]221
222 int rc = usb_iface->bind_address(device, address, handle);
223
224 ipc_answer_0(callid, rc);
225}
226
[6f04905]227void remote_usbhc_release_address(device_t *device, void *iface,
228 ipc_callid_t callid, ipc_call_t *call)
229{
230 usbhc_iface_t *usb_iface = (usbhc_iface_t *) iface;
231
232 if (!usb_iface->release_address) {
233 ipc_answer_0(callid, ENOTSUP);
234 return;
235 }
236
[eac610e]237 usb_address_t address = (usb_address_t) DEV_IPC_GET_ARG1(*call);
[6f04905]238
239 int rc = usb_iface->release_address(device, address);
240
241 ipc_answer_0(callid, rc);
242}
243
[1b22bd4]244
245static void callback_out(device_t *device,
246 usb_transaction_outcome_t outcome, void *arg)
247{
248 async_transaction_t *trans = (async_transaction_t *)arg;
249
250 // FIXME - answer according to outcome
[f841608]251 ipc_answer_0(trans->caller, outcome);
[1b22bd4]252
253 free(trans);
254}
255
256static void callback_in(device_t *device,
257 usb_transaction_outcome_t outcome, size_t actual_size, void *arg)
258{
259 async_transaction_t *trans = (async_transaction_t *)arg;
260
261 // FIXME - answer according to outcome
[f841608]262 ipc_answer_1(trans->caller, outcome, (sysarg_t)trans);
[1b22bd4]263
264 trans->size = actual_size;
[91db50ac]265}
266
[fb1dca09]267/** Process an outgoing transfer (both OUT and SETUP).
268 *
269 * @param device Target device.
270 * @param callid Initiating caller.
271 * @param call Initiating call.
272 * @param transfer_func Transfer function (might be NULL).
273 */
274static void remote_usbhc_out_transfer(device_t *device,
275 ipc_callid_t callid, ipc_call_t *call,
276 usbhc_iface_transfer_out_t transfer_func)
[91db50ac]277{
[fb1dca09]278 if (!transfer_func) {
279 ipc_answer_0(callid, ENOTSUP);
280 return;
281 }
[1b22bd4]282
[eac610e]283 size_t expected_len = DEV_IPC_GET_ARG3(*call);
[1b22bd4]284 usb_target_t target = {
[eac610e]285 .address = DEV_IPC_GET_ARG1(*call),
286 .endpoint = DEV_IPC_GET_ARG2(*call)
[1b22bd4]287 };
288
289 size_t len = 0;
290 void *buffer = NULL;
291 if (expected_len > 0) {
292 int rc = async_data_write_accept(&buffer, false,
293 1, USB_MAX_PAYLOAD_SIZE,
294 0, &len);
295
296 if (rc != EOK) {
297 ipc_answer_0(callid, rc);
298 return;
299 }
300 }
301
302 async_transaction_t *trans = malloc(sizeof(async_transaction_t));
303 trans->caller = callid;
[fb1dca09]304 trans->buffer = buffer;
[9753220]305 trans->setup_packet = NULL;
[fb1dca09]306 trans->size = len;
[1b22bd4]307
[fb1dca09]308 int rc = transfer_func(device, target, buffer, len,
[1b22bd4]309 callback_out, trans);
310
311 if (rc != EOK) {
312 ipc_answer_0(callid, rc);
[fb1dca09]313 if (buffer != NULL) {
314 free(buffer);
315 }
[1b22bd4]316 free(trans);
317 }
[91db50ac]318}
319
[fb1dca09]320/** Process an incoming transfer.
321 *
322 * @param device Target device.
323 * @param callid Initiating caller.
324 * @param call Initiating call.
325 * @param transfer_func Transfer function (might be NULL).
326 */
327static void remote_usbhc_in_transfer(device_t *device,
328 ipc_callid_t callid, ipc_call_t *call,
329 usbhc_iface_transfer_in_t transfer_func)
[91db50ac]330{
[fb1dca09]331 if (!transfer_func) {
332 ipc_answer_0(callid, ENOTSUP);
333 return;
334 }
[1b22bd4]335
[eac610e]336 size_t len = DEV_IPC_GET_ARG3(*call);
[1b22bd4]337 usb_target_t target = {
[eac610e]338 .address = DEV_IPC_GET_ARG1(*call),
339 .endpoint = DEV_IPC_GET_ARG2(*call)
[1b22bd4]340 };
341
342 async_transaction_t *trans = malloc(sizeof(async_transaction_t));
343 trans->caller = callid;
344 trans->buffer = malloc(len);
[9753220]345 trans->setup_packet = NULL;
[1b22bd4]346 trans->size = len;
347
[fb1dca09]348 int rc = transfer_func(device, target, trans->buffer, len,
[1b22bd4]349 callback_in, trans);
350
351 if (rc != EOK) {
352 ipc_answer_0(callid, rc);
353 free(trans->buffer);
354 free(trans);
355 }
[91db50ac]356}
357
[fb1dca09]358/** Process status part of control transfer.
359 *
360 * @param device Target device.
361 * @param callid Initiating caller.
362 * @param call Initiating call.
363 * @param direction Transfer direction (read ~ in, write ~ out).
364 * @param transfer_in_func Transfer function for control read (might be NULL).
365 * @param transfer_out_func Transfer function for control write (might be NULL).
366 */
367static void remote_usbhc_status_transfer(device_t *device,
368 ipc_callid_t callid, ipc_call_t *call,
369 usb_direction_t direction,
370 int (*transfer_in_func)(device_t *, usb_target_t,
371 usbhc_iface_transfer_in_callback_t, void *),
372 int (*transfer_out_func)(device_t *, usb_target_t,
373 usbhc_iface_transfer_out_callback_t, void *))
[a3dfb2e]374{
[fb1dca09]375 switch (direction) {
376 case USB_DIRECTION_IN:
377 if (!transfer_in_func) {
378 ipc_answer_0(callid, ENOTSUP);
379 return;
380 }
381 break;
382 case USB_DIRECTION_OUT:
383 if (!transfer_out_func) {
384 ipc_answer_0(callid, ENOTSUP);
385 return;
386 }
387 break;
388 default:
389 assert(false && "unreachable code");
390 break;
391 }
[a3dfb2e]392
393 usb_target_t target = {
[eac610e]394 .address = DEV_IPC_GET_ARG1(*call),
395 .endpoint = DEV_IPC_GET_ARG2(*call)
[a3dfb2e]396 };
397
398 async_transaction_t *trans = malloc(sizeof(async_transaction_t));
399 trans->caller = callid;
400 trans->buffer = NULL;
[9753220]401 trans->setup_packet = NULL;
[a3dfb2e]402 trans->size = 0;
403
[fb1dca09]404 int rc;
405 switch (direction) {
406 case USB_DIRECTION_IN:
407 rc = transfer_in_func(device, target,
408 callback_in, trans);
409 break;
410 case USB_DIRECTION_OUT:
411 rc = transfer_out_func(device, target,
412 callback_out, trans);
413 break;
414 default:
415 assert(false && "unreachable code");
416 break;
417 }
[a3dfb2e]418
419 if (rc != EOK) {
420 ipc_answer_0(callid, rc);
421 free(trans);
422 }
[fb1dca09]423 return;
[a3dfb2e]424}
425
[fb1dca09]426
427void remote_usbhc_interrupt_out(device_t *device, void *iface,
428 ipc_callid_t callid, ipc_call_t *call)
[a3dfb2e]429{
430 usbhc_iface_t *usb_iface = (usbhc_iface_t *) iface;
[fb1dca09]431 assert(usb_iface != NULL);
[a3dfb2e]432
[fb1dca09]433 return remote_usbhc_out_transfer(device, callid, call,
434 usb_iface->interrupt_out);
435}
[a3dfb2e]436
[fb1dca09]437void remote_usbhc_interrupt_in(device_t *device, void *iface,
438 ipc_callid_t callid, ipc_call_t *call)
439{
440 usbhc_iface_t *usb_iface = (usbhc_iface_t *) iface;
441 assert(usb_iface != NULL);
[a3dfb2e]442
[fb1dca09]443 return remote_usbhc_in_transfer(device, callid, call,
444 usb_iface->interrupt_in);
445}
[a3dfb2e]446
[fb1dca09]447void remote_usbhc_control_write_setup(device_t *device, void *iface,
448 ipc_callid_t callid, ipc_call_t *call)
449{
450 usbhc_iface_t *usb_iface = (usbhc_iface_t *) iface;
451 assert(usb_iface != NULL);
[a3dfb2e]452
[fb1dca09]453 return remote_usbhc_out_transfer(device, callid, call,
454 usb_iface->control_write_setup);
455}
[a3dfb2e]456
[fb1dca09]457void remote_usbhc_control_write_data(device_t *device, void *iface,
458 ipc_callid_t callid, ipc_call_t *call)
459{
460 usbhc_iface_t *usb_iface = (usbhc_iface_t *) iface;
461 assert(usb_iface != NULL);
[a3dfb2e]462
[fb1dca09]463 return remote_usbhc_out_transfer(device, callid, call,
464 usb_iface->control_write_data);
[a3dfb2e]465}
466
467void remote_usbhc_control_write_status(device_t *device, void *iface,
[fb1dca09]468 ipc_callid_t callid, ipc_call_t *call)
[a3dfb2e]469{
470 usbhc_iface_t *usb_iface = (usbhc_iface_t *) iface;
[fb1dca09]471 assert(usb_iface != NULL);
[a3dfb2e]472
[fb1dca09]473 return remote_usbhc_status_transfer(device, callid, call,
474 USB_DIRECTION_IN, usb_iface->control_write_status, NULL);
[a3dfb2e]475}
476
477void remote_usbhc_control_read_setup(device_t *device, void *iface,
[fb1dca09]478 ipc_callid_t callid, ipc_call_t *call)
[a3dfb2e]479{
480 usbhc_iface_t *usb_iface = (usbhc_iface_t *) iface;
[fb1dca09]481 assert(usb_iface != NULL);
[a3dfb2e]482
[fb1dca09]483 return remote_usbhc_out_transfer(device, callid, call,
484 usb_iface->control_read_setup);
[a3dfb2e]485}
486
487void remote_usbhc_control_read_data(device_t *device, void *iface,
488 ipc_callid_t callid, ipc_call_t *call)
489{
490 usbhc_iface_t *usb_iface = (usbhc_iface_t *) iface;
[fb1dca09]491 assert(usb_iface != NULL);
[a3dfb2e]492
[fb1dca09]493 return remote_usbhc_in_transfer(device, callid, call,
494 usb_iface->control_read_data);
[a3dfb2e]495}
496
497void remote_usbhc_control_read_status(device_t *device, void *iface,
498 ipc_callid_t callid, ipc_call_t *call)
499{
500 usbhc_iface_t *usb_iface = (usbhc_iface_t *) iface;
[fb1dca09]501 assert(usb_iface != NULL);
[a3dfb2e]502
[fb1dca09]503 return remote_usbhc_status_transfer(device, callid, call,
504 USB_DIRECTION_OUT, NULL, usb_iface->control_read_status);
[a3dfb2e]505}
506
[9753220]507void remote_usbhc_control_write(device_t *device, void *iface,
508ipc_callid_t callid, ipc_call_t *call)
509{
510 usbhc_iface_t *usb_iface = (usbhc_iface_t *) iface;
511 assert(usb_iface != NULL);
512
513 if (!usb_iface->control_write) {
514 ipc_answer_0(callid, ENOTSUP);
515 return;
516 }
517
518 usb_target_t target = {
519 .address = DEV_IPC_GET_ARG1(*call),
520 .endpoint = DEV_IPC_GET_ARG2(*call)
521 };
522
523 int rc;
524
525 void *setup_packet = NULL;
526 void *data_buffer = NULL;
527 size_t setup_packet_len = 0;
528 size_t data_buffer_len = 0;
529
530 rc = async_data_write_accept(&setup_packet, false,
531 1, USB_MAX_PAYLOAD_SIZE, 0, &setup_packet_len);
532 if (rc != EOK) {
533 ipc_answer_0(callid, rc);
534 return;
535 }
536 rc = async_data_write_accept(&data_buffer, false,
537 1, USB_MAX_PAYLOAD_SIZE, 0, &data_buffer_len);
538 if (rc != EOK) {
539 free(setup_packet);
540 ipc_answer_0(callid, rc);
541 return;
542 }
543
544 async_transaction_t *trans = malloc(sizeof(async_transaction_t));
545 trans->caller = callid;
546 trans->setup_packet = setup_packet;
547 trans->buffer = data_buffer;
548 trans->size = data_buffer_len;
549
550 rc = usb_iface->control_write(device, target,
551 setup_packet, setup_packet_len,
552 data_buffer, data_buffer_len,
553 callback_out, trans);
554
555 if (rc != EOK) {
556 ipc_answer_0(callid, rc);
557 free(setup_packet);
558 free(data_buffer);
559 free(trans);
560 }
561}
562
563
564void remote_usbhc_control_read(device_t *device, void *iface,
565ipc_callid_t callid, ipc_call_t *call)
566{
567 usbhc_iface_t *usb_iface = (usbhc_iface_t *) iface;
568 assert(usb_iface != NULL);
569
570 if (!usb_iface->control_read) {
571 ipc_answer_0(callid, ENOTSUP);
572 return;
573 }
574
575 size_t data_len = DEV_IPC_GET_ARG3(*call);
576 usb_target_t target = {
577 .address = DEV_IPC_GET_ARG1(*call),
578 .endpoint = DEV_IPC_GET_ARG2(*call)
579 };
580
581 int rc;
582
583 void *setup_packet = NULL;
584 size_t setup_packet_len = 0;
585
586 rc = async_data_write_accept(&setup_packet, false,
587 1, USB_MAX_PAYLOAD_SIZE, 0, &setup_packet_len);
588 if (rc != EOK) {
589 ipc_answer_0(callid, rc);
590 return;
591 }
592
593 async_transaction_t *trans = malloc(sizeof(async_transaction_t));
594 trans->caller = callid;
595 trans->setup_packet = setup_packet;
596 trans->buffer = malloc(data_len);
597 trans->size = data_len;
598
599 rc = usb_iface->control_read(device, target,
600 setup_packet, setup_packet_len,
601 trans->buffer, trans->size,
602 callback_in, trans);
603
604 if (rc != EOK) {
605 ipc_answer_0(callid, rc);
606 free(setup_packet);
607 free(trans->buffer);
608 free(trans);
609 }
610}
611
[a3dfb2e]612
[1b22bd4]613
[91db50ac]614/**
615 * @}
616 */
Note: See TracBrowser for help on using the repository browser.