VirtualBox

source: vbox/trunk/src/VBox/Main/src-client/RemoteUSBBackend.cpp@ 42546

Last change on this file since 42546 was 38986, checked in by vboxsync, 13 years ago

VRDP-USB: Preserve order of completed URBs.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 39.0 KB
Line 
1/** @file
2 *
3 * VirtualBox Remote USB backend
4 */
5
6/*
7 * Copyright (C) 2006-2010 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18#define LOG_GROUP LOG_GROUP_DEV_USB
19#include "ConsoleImpl.h"
20#include "ConsoleVRDPServer.h"
21#include "RemoteUSBBackend.h"
22#include "RemoteUSBDeviceImpl.h"
23
24#include <VBox/RemoteDesktop/VRDE.h>
25#include <VBox/vrdpusb.h>
26#include <VBox/err.h>
27#include <VBox/log.h>
28
29#include <VBox/vusb.h>
30
31#include <iprt/time.h>
32
33/** @page pg_vrdb_usb Async Remote USB
34 *
35 *
36 * USB backend functions are called in EMT so care must be taken to prevent
37 * delays in the functions execution.
38 *
39 * Among 11 backend functions 10 just return a success indicator.
40 *
41 * Such a function usually will check pending error code and if everything is ok,
42 * submit asynchronous RDP request and return success immediately.
43 *
44 * On actual completion of each request, the status will be saved as
45 * pending, so in case of an error all further functions will fail with
46 * device disconnected condition.
47 * @todo May be a device disconnect notification for console is required?
48 *
49 * The only remaining function that needs special processing is
50 * the reap_urb. It has a timeout parameter.
51 * Normally, the timeout is 0, as result of polling from VUSB frame timer.
52 * It is ok for async processing, the backend will periodically reap urbs from client.
53 * And already reaped URBs from client will be returned for the call.
54 * Exceptions:
55 * 1) during device initialization, when obtaining device descriptions
56 * the timeout is -1, and the request is expected to be processed synchronously.
57 * It looks like only 3 URBs with some information are retrieved that way.
58 * Probably, one can return this information in DEVICE_LIST together with the
59 * device description and when such request are submitted, just return
60 * the prefetched data.
61 * 2) during suspend timeout is non zero (10 or less milliseconds),
62 * and URB's are reaped for about 1 second. But here network delays
63 * will not affect the timeout, so it is ok.
64 *
65 *
66 * @subsection sub_vrdb_usb_dad Device attaching/detaching
67 *
68 * Devices are attached when client is connected or when a new device is connected to client.
69 * Devices are detached when client is disconnected (all devices) or a device is disconnected
70 * the client side.
71 *
72 * The backend polls the client for list of attached USB devices from RemoteUSBThread.
73 *
74 */
75
76/* Queued URB submitted to VRDP client. */
77typedef struct _REMOTEUSBQURB
78{
79 struct _REMOTEUSBQURB *next;
80 struct _REMOTEUSBQURB *prev;
81
82 PREMOTEUSBDEVICE pDevice; /* Device, the URB is queued for. */
83
84 uint32_t u32Handle; /* The handle of the URB. Generated by the Remote USB backend. */
85
86 void *pvData; /* Pointer to URB data allocated by VUSB. */
87 void *pvURB; /* Pointer to URB known to VUSB. */
88
89 uint32_t u32Len; /* Data length returned by the VRDP client. */
90 uint32_t u32Err; /* URB error code returned by the VRDP client. */
91
92 bool fCompleted; /* The URB has been returned back by VRDP client. */
93 bool fInput; /* This URB receives data from the client. */
94
95 uint32_t u32TransferredLen; /* For VRDE_USB_DIRECTION_OUT URBs = bytes written.
96 * For VRDE_USB_DIRECTION_IN URBs = bytes received.
97 */
98} REMOTEUSBQURB;
99
100/* Remote USB device instance data. */
101typedef struct _REMOTEUSBDEVICE
102{
103 struct _REMOTEUSBDEVICE *prev;
104 struct _REMOTEUSBDEVICE *next;
105
106 RemoteUSBBackend *pOwner;
107
108 VRDEUSBDEVID id; /* The remote identifier, assigned by client. */
109
110 uint32_t u32ClientId; /* The identifier of the remote client. */
111
112 REMOTEUSBQURB *pHeadQURBs; /* List of URBs queued for the device. */
113 REMOTEUSBQURB *pTailQURBs;
114
115 volatile uint32_t hURB; /* Source for URB's handles. */
116 bool fFailed; /* True if an operation has failed for the device. */
117 RTCRITSECT critsect; /* Protects the queued urb list. */
118} REMOTEUSBDEVICE;
119
120
121
122static void requestDevice(REMOTEUSBDEVICE *pDevice)
123{
124 int rc = RTCritSectEnter(&pDevice->critsect);
125 AssertRC(rc);
126}
127
128static void releaseDevice(REMOTEUSBDEVICE *pDevice)
129{
130 RTCritSectLeave(&pDevice->critsect);
131}
132
133static REMOTEUSBQURB *qurbAlloc(PREMOTEUSBDEVICE pDevice)
134{
135 /* @todo reuse URBs. */
136 REMOTEUSBQURB *pQURB = (REMOTEUSBQURB *)RTMemAllocZ (sizeof (REMOTEUSBQURB));
137
138 if (pQURB)
139 {
140 pQURB->pDevice = pDevice;
141 }
142
143 return pQURB;
144}
145
146static void qurbFree (REMOTEUSBQURB *pQURB)
147{
148 RTMemFree (pQURB);
149 return;
150}
151
152
153/* Called by VRDP server when the client responds to a request on USB channel. */
154DECLCALLBACK(int) USBClientResponseCallback (void *pv, uint32_t u32ClientId, uint8_t code, const void *pvRet, uint32_t cbRet)
155{
156 int rc = VINF_SUCCESS;
157
158 LogFlow(("USBClientResponseCallback: id = %d, pv = %p, code = %d, pvRet = %p, cbRet = %d\n",
159 u32ClientId, pv, code, pvRet, cbRet));
160
161 RemoteUSBBackend *pThis = (RemoteUSBBackend *)pv;
162
163 switch (code)
164 {
165 case VRDE_USB_REQ_DEVICE_LIST:
166 {
167 rc = pThis->saveDeviceList (pvRet, cbRet);
168 } break;
169
170 case VRDE_USB_REQ_NEGOTIATE:
171 {
172 if (pvRet && cbRet >= sizeof (VRDEUSBREQNEGOTIATERET))
173 {
174 VRDEUSBREQNEGOTIATERET *pret = (VRDEUSBREQNEGOTIATERET *)pvRet;
175
176 rc = pThis->negotiateResponse (pret, cbRet);
177 }
178 else
179 {
180 Log(("USBClientResponseCallback: WARNING: not enough data in response: pv = %p, cb = %d, expected %d.\n",
181 pvRet, cbRet, sizeof (VRDEUSBREQNEGOTIATERET)));
182
183 rc = VERR_INVALID_PARAMETER;
184 }
185 } break;
186
187 case VRDE_USB_REQ_REAP_URB:
188 {
189 rc = pThis->reapURB (pvRet, cbRet);
190
191 LogFlow(("USBClientResponseCallback: reap URB, rc = %Rrc.\n", rc));
192 } break;
193
194 case VRDE_USB_REQ_QUEUE_URB:
195 case VRDE_USB_REQ_CLOSE:
196 case VRDE_USB_REQ_CANCEL_URB:
197 {
198 /* Do nothing, actually this should not happen. */
199 Log(("USBClientResponseCallback: WARNING: response to a request %d is not expected!!!\n", code));
200 } break;
201
202 case VRDE_USB_REQ_OPEN:
203 case VRDE_USB_REQ_RESET:
204 case VRDE_USB_REQ_SET_CONFIG:
205 case VRDE_USB_REQ_CLAIM_INTERFACE:
206 case VRDE_USB_REQ_RELEASE_INTERFACE:
207 case VRDE_USB_REQ_INTERFACE_SETTING:
208 case VRDE_USB_REQ_CLEAR_HALTED_EP:
209 {
210 /*
211 * Device specific responses with status codes.
212 */
213 if (pvRet && cbRet >= sizeof (VRDEUSBREQRETHDR))
214 {
215 VRDEUSBREQRETHDR *pret = (VRDEUSBREQRETHDR *)pvRet;
216
217 if (pret->status != VRDE_USB_STATUS_SUCCESS)
218 {
219 REMOTEUSBDEVICE *pDevice = pThis->deviceFromId (pret->id);
220
221 if (!pDevice)
222 {
223 Log(("USBClientResponseCallback: WARNING: invalid device id %08X.\n", pret->id));
224 rc = VERR_INVALID_PARAMETER;
225 }
226 else
227 {
228 Log(("USBClientResponseCallback: WARNING: the operation failed, status %d\n", pret->status));
229 pDevice->fFailed = true;
230 }
231 }
232 }
233 else
234 {
235 Log(("USBClientResponseCallback: WARNING: not enough data in response: pv = %p, cb = %d, expected %d.\n",
236 pvRet, cbRet, sizeof (VRDEUSBREQRETHDR)));
237 }
238 } break;
239
240 default:
241 {
242 Log(("USBClientResponseCallback: WARNING: invalid code %d\n", code));
243 } break;
244 }
245
246 return rc;
247}
248
249/*
250 * Backend entry points.
251 */
252static DECLCALLBACK(int) iface_Open (PREMOTEUSBBACKEND pInstance, const char *pszAddress, size_t cbAddress, PREMOTEUSBDEVICE *ppDevice)
253{
254 int rc = VINF_SUCCESS;
255
256 RemoteUSBBackend *pThis = (RemoteUSBBackend *)pInstance;
257
258 REMOTEUSBDEVICE *pDevice = (REMOTEUSBDEVICE *)RTMemAllocZ (sizeof (REMOTEUSBDEVICE));
259
260 if (!pDevice)
261 {
262 rc = VERR_NO_MEMORY;
263 }
264 else
265 {
266 /* Parse given address string to find the device identifier.
267 * The format is "REMOTEUSB0xAAAABBBB&0xCCCCDDDD", where AAAABBBB is hex device identifier
268 * and CCCCDDDD is hex client id.
269 */
270 if (strncmp (pszAddress, REMOTE_USB_BACKEND_PREFIX_S, REMOTE_USB_BACKEND_PREFIX_LEN) != 0)
271 {
272 AssertFailed();
273 rc = VERR_INVALID_PARAMETER;
274 }
275 else
276 {
277 /* Initialize the device structure. */
278 pDevice->pOwner = pThis;
279
280 rc = RTCritSectInit(&pDevice->critsect);
281 AssertRC(rc);
282
283 if (RT_SUCCESS(rc))
284 {
285 pDevice->id = RTStrToUInt32 (&pszAddress[REMOTE_USB_BACKEND_PREFIX_LEN]);
286
287 size_t l = strlen (pszAddress);
288
289 if (l >= REMOTE_USB_BACKEND_PREFIX_LEN + strlen ("0x12345678&0x87654321"))
290 {
291 const char *p = &pszAddress[REMOTE_USB_BACKEND_PREFIX_LEN + strlen ("0x12345678")];
292 if (*p == '&')
293 {
294 pDevice->u32ClientId = RTStrToUInt32 (p + 1);
295 }
296 else
297 {
298 AssertFailed ();
299 rc = VERR_INVALID_PARAMETER;
300 }
301 }
302 else
303 {
304 AssertFailed ();
305 rc = VERR_INVALID_PARAMETER;
306 }
307
308 if (RT_SUCCESS(rc))
309 {
310 VRDE_USB_REQ_OPEN_PARM parm;
311
312 parm.code = VRDE_USB_REQ_OPEN;
313 parm.id = pDevice->id;
314
315 pThis->VRDPServer()->SendUSBRequest (pDevice->u32ClientId, &parm, sizeof (parm));
316 }
317 }
318 }
319 }
320
321 if (RT_SUCCESS(rc))
322 {
323 *ppDevice = pDevice;
324
325 pThis->addDevice (pDevice);
326 }
327 else
328 {
329 RTMemFree (pDevice);
330 }
331
332 return rc;
333}
334
335static DECLCALLBACK(void) iface_Close (PREMOTEUSBDEVICE pDevice)
336{
337 RemoteUSBBackend *pThis = pDevice->pOwner;
338
339 VRDE_USB_REQ_CLOSE_PARM parm;
340
341 parm.code = VRDE_USB_REQ_CLOSE;
342 parm.id = pDevice->id;
343
344 pThis->VRDPServer()->SendUSBRequest (pDevice->u32ClientId, &parm, sizeof (parm));
345
346 pThis->removeDevice (pDevice);
347
348 if (RTCritSectIsInitialized (&pDevice->critsect))
349 {
350 RTCritSectDelete (&pDevice->critsect);
351 }
352
353 RTMemFree (pDevice);
354
355 return;
356}
357
358static DECLCALLBACK(int) iface_Reset (PREMOTEUSBDEVICE pDevice)
359{
360 RemoteUSBBackend *pThis = pDevice->pOwner;
361
362 if (pDevice->fFailed)
363 {
364 return VERR_VUSB_DEVICE_NOT_ATTACHED;
365 }
366
367 VRDE_USB_REQ_RESET_PARM parm;
368
369 parm.code = VRDE_USB_REQ_RESET;
370 parm.id = pDevice->id;
371
372 pThis->VRDPServer()->SendUSBRequest (pDevice->u32ClientId, &parm, sizeof (parm));
373
374 return VINF_SUCCESS;
375}
376
377static DECLCALLBACK(int) iface_SetConfig (PREMOTEUSBDEVICE pDevice, uint8_t u8Cfg)
378{
379 RemoteUSBBackend *pThis = pDevice->pOwner;
380
381 if (pDevice->fFailed)
382 {
383 return VERR_VUSB_DEVICE_NOT_ATTACHED;
384 }
385
386 VRDE_USB_REQ_SET_CONFIG_PARM parm;
387
388 parm.code = VRDE_USB_REQ_SET_CONFIG;
389 parm.id = pDevice->id;
390 parm.configuration = u8Cfg;
391
392 pThis->VRDPServer()->SendUSBRequest (pDevice->u32ClientId, &parm, sizeof (parm));
393
394 return VINF_SUCCESS;
395}
396
397static DECLCALLBACK(int) iface_ClaimInterface (PREMOTEUSBDEVICE pDevice, uint8_t u8Ifnum)
398{
399 RemoteUSBBackend *pThis = pDevice->pOwner;
400
401 if (pDevice->fFailed)
402 {
403 return VERR_VUSB_DEVICE_NOT_ATTACHED;
404 }
405
406 VRDE_USB_REQ_CLAIM_INTERFACE_PARM parm;
407
408 parm.code = VRDE_USB_REQ_CLAIM_INTERFACE;
409 parm.id = pDevice->id;
410 parm.iface = u8Ifnum;
411
412 pThis->VRDPServer()->SendUSBRequest (pDevice->u32ClientId, &parm, sizeof (parm));
413
414 return VINF_SUCCESS;
415}
416
417static DECLCALLBACK(int) iface_ReleaseInterface (PREMOTEUSBDEVICE pDevice, uint8_t u8Ifnum)
418{
419 RemoteUSBBackend *pThis = pDevice->pOwner;
420
421 if (pDevice->fFailed)
422 {
423 return VERR_VUSB_DEVICE_NOT_ATTACHED;
424 }
425
426 VRDE_USB_REQ_RELEASE_INTERFACE_PARM parm;
427
428 parm.code = VRDE_USB_REQ_RELEASE_INTERFACE;
429 parm.id = pDevice->id;
430 parm.iface = u8Ifnum;
431
432 pThis->VRDPServer()->SendUSBRequest (pDevice->u32ClientId, &parm, sizeof (parm));
433
434 return VINF_SUCCESS;
435}
436
437static DECLCALLBACK(int) iface_InterfaceSetting (PREMOTEUSBDEVICE pDevice, uint8_t u8Ifnum, uint8_t u8Setting)
438{
439 RemoteUSBBackend *pThis = pDevice->pOwner;
440
441 if (pDevice->fFailed)
442 {
443 return VERR_VUSB_DEVICE_NOT_ATTACHED;
444 }
445
446 VRDE_USB_REQ_INTERFACE_SETTING_PARM parm;
447
448 parm.code = VRDE_USB_REQ_INTERFACE_SETTING;
449 parm.id = pDevice->id;
450 parm.iface = u8Ifnum;
451 parm.setting = u8Setting;
452
453 pThis->VRDPServer()->SendUSBRequest (pDevice->u32ClientId, &parm, sizeof (parm));
454
455 return VINF_SUCCESS;
456}
457
458static DECLCALLBACK(int) iface_ClearHaltedEP (PREMOTEUSBDEVICE pDevice, uint8_t u8Ep)
459{
460 RemoteUSBBackend *pThis = pDevice->pOwner;
461
462 if (pDevice->fFailed)
463 {
464 return VERR_VUSB_DEVICE_NOT_ATTACHED;
465 }
466
467 VRDE_USB_REQ_CLEAR_HALTED_EP_PARM parm;
468
469 parm.code = VRDE_USB_REQ_CLEAR_HALTED_EP;
470 parm.id = pDevice->id;
471 parm.ep = u8Ep;
472
473 pThis->VRDPServer()->SendUSBRequest (pDevice->u32ClientId, &parm, sizeof (parm));
474
475 return VINF_SUCCESS;
476}
477
478static DECLCALLBACK(void) iface_CancelURB (PREMOTEUSBDEVICE pDevice, PREMOTEUSBQURB pRemoteURB)
479{
480 RemoteUSBBackend *pThis = pDevice->pOwner;
481
482 VRDE_USB_REQ_CANCEL_URB_PARM parm;
483
484 parm.code = VRDE_USB_REQ_CANCEL_URB;
485 parm.id = pDevice->id;
486 parm.handle = pRemoteURB->u32Handle;
487
488 pThis->VRDPServer()->SendUSBRequest (pDevice->u32ClientId, &parm, sizeof (parm));
489
490 requestDevice (pDevice);
491
492 /* Remove this urb from the queue. It is safe because if
493 * client will return the URB, it will be just ignored
494 * in reapURB.
495 */
496 if (pRemoteURB->prev)
497 {
498 pRemoteURB->prev->next = pRemoteURB->next;
499 }
500 else
501 {
502 pDevice->pHeadQURBs = pRemoteURB->next;
503 }
504
505 if (pRemoteURB->next)
506 {
507 pRemoteURB->next->prev = pRemoteURB->prev;
508 }
509 else
510 {
511 pDevice->pTailQURBs = pRemoteURB->prev;
512 }
513
514 qurbFree (pRemoteURB);
515
516 releaseDevice (pDevice);
517
518 return;
519}
520
521static DECLCALLBACK(int) iface_QueueURB (PREMOTEUSBDEVICE pDevice, uint8_t u8Type, uint8_t u8Ep, uint8_t u8Direction, uint32_t u32Len, void *pvData, void *pvURB, PREMOTEUSBQURB *ppRemoteURB)
522{
523 int rc = VINF_SUCCESS;
524
525#ifdef DEBUG_sunlover
526 LogFlow(("RemoteUSBBackend::iface_QueueURB: u8Type = %d, u8Ep = %d, u8Direction = %d, data\n%.*Rhxd\n", u8Type, u8Ep, u8Direction, u32Len, pvData));
527#endif /* DEBUG_sunlover */
528
529 if (pDevice->fFailed)
530 {
531 return VERR_VUSB_DEVICE_NOT_ATTACHED;
532 }
533
534 RemoteUSBBackend *pThis = pDevice->pOwner;
535
536 VRDE_USB_REQ_QUEUE_URB_PARM parm;
537 uint32_t u32Handle = 0;
538 uint32_t u32DataLen = 0;
539
540 REMOTEUSBQURB *qurb = qurbAlloc (pDevice);
541
542 if (qurb == NULL)
543 {
544 rc = VERR_NO_MEMORY;
545 goto l_leave;
546 }
547
548 /*
549 * Compute length of data which need to be transferred to the client.
550 */
551 switch(u8Direction)
552 {
553 case VUSB_DIRECTION_IN:
554 {
555 if (u8Type == VUSBXFERTYPE_MSG)
556 {
557 u32DataLen = 8; /* 8 byte header. */
558 // u32DataLen = u32Len; // @todo do messages need all information?
559 }
560 } break;
561
562 case VUSB_DIRECTION_OUT:
563 {
564 u32DataLen = u32Len;
565 } break;
566
567 default:
568 {
569 AssertFailed();
570 rc = VERR_INVALID_PARAMETER;
571 goto l_leave;
572 }
573 }
574
575 parm.code = VRDE_USB_REQ_QUEUE_URB;
576 parm.id = pDevice->id;
577
578 u32Handle = pDevice->hURB++;
579 if (u32Handle == 0)
580 {
581 u32Handle = pDevice->hURB++;
582 }
583
584 LogFlow(("RemoteUSBBackend::iface_QueueURB: handle = %d\n", u32Handle));
585
586 parm.handle = u32Handle;
587
588 switch(u8Type)
589 {
590 case VUSBXFERTYPE_CTRL: parm.type = VRDE_USB_TRANSFER_TYPE_CTRL; break;
591 case VUSBXFERTYPE_ISOC: parm.type = VRDE_USB_TRANSFER_TYPE_ISOC; break;
592 case VUSBXFERTYPE_BULK: parm.type = VRDE_USB_TRANSFER_TYPE_BULK; break;
593 case VUSBXFERTYPE_INTR: parm.type = VRDE_USB_TRANSFER_TYPE_INTR; break;
594 case VUSBXFERTYPE_MSG: parm.type = VRDE_USB_TRANSFER_TYPE_MSG; break;
595 default: AssertFailed(); rc = VERR_INVALID_PARAMETER; goto l_leave;
596 }
597
598 parm.ep = u8Ep;
599
600 switch(u8Direction)
601 {
602 case VUSB_DIRECTION_SETUP: AssertFailed(); parm.direction = VRDE_USB_DIRECTION_SETUP; break;
603 case VUSB_DIRECTION_IN: parm.direction = VRDE_USB_DIRECTION_IN; break;
604 case VUSB_DIRECTION_OUT: parm.direction = VRDE_USB_DIRECTION_OUT; break;
605 default: AssertFailed(); rc = VERR_INVALID_PARAMETER; goto l_leave;
606 }
607
608 parm.urblen = u32Len;
609 parm.datalen = u32DataLen;
610
611 if (u32DataLen)
612 {
613 parm.data = pvData;
614 }
615
616 requestDevice (pDevice);
617
618 /* Add at tail of queued urb list. */
619 qurb->next = NULL;
620 qurb->prev = pDevice->pTailQURBs;
621 qurb->u32Err = VRDE_USB_XFER_OK;
622 qurb->u32Len = u32Len;
623 qurb->pvData = pvData;
624 qurb->pvURB = pvURB;
625 qurb->u32Handle = u32Handle;
626 qurb->fCompleted = false;
627 qurb->fInput = (u8Direction == VUSB_DIRECTION_IN);
628 qurb->u32TransferredLen = 0;
629
630 if (pDevice->pTailQURBs)
631 {
632 Assert(pDevice->pTailQURBs->next == NULL);
633 pDevice->pTailQURBs->next = qurb;
634 }
635 else
636 {
637 /* This is the first URB to be added. */
638 Assert(pDevice->pHeadQURBs == NULL);
639 pDevice->pHeadQURBs = qurb;
640 }
641
642 pDevice->pTailQURBs = qurb;
643
644 releaseDevice (pDevice);
645
646 *ppRemoteURB = qurb;
647
648 pThis->VRDPServer()->SendUSBRequest (pDevice->u32ClientId, &parm, sizeof (parm));
649
650l_leave:
651 if (RT_FAILURE(rc))
652 {
653 qurbFree (qurb);
654 }
655
656 return rc;
657}
658
659/* The function checks the URB queue for completed URBs. Also if the client
660 * has requested URB polling, the function will send URB poll requests.
661 */
662static DECLCALLBACK(int) iface_ReapURB (PREMOTEUSBDEVICE pDevice, uint32_t u32Millies, void **ppvURB, uint32_t *pu32Len, uint32_t *pu32Err)
663{
664 int rc = VINF_SUCCESS;
665
666 LogFlow(("RemoteUSBBackend::iface_ReapURB %d ms\n", u32Millies));
667
668 if (pDevice->fFailed)
669 {
670 return VERR_VUSB_DEVICE_NOT_ATTACHED;
671 }
672
673 RemoteUSBBackend *pThis = pDevice->pOwner;
674
675 /* Wait for transaction completion. */
676 uint64_t u64StartTime = RTTimeMilliTS ();
677
678 if (pThis->pollingEnabledURB ())
679 {
680 VRDE_USB_REQ_REAP_URB_PARM parm;
681
682 parm.code = VRDE_USB_REQ_REAP_URB;
683
684 pThis->VRDPServer()->SendUSBRequest (pDevice->u32ClientId, &parm, sizeof (parm));
685 }
686
687 REMOTEUSBQURB *qurb = NULL;
688
689 for (;;)
690 {
691 uint32_t u32ClientId;
692
693 /* Scan queued URBs, look for completed. */
694 requestDevice (pDevice);
695
696 u32ClientId = pDevice->u32ClientId;
697
698 qurb = pDevice->pHeadQURBs;
699
700 while (qurb)
701 {
702 if (qurb->fCompleted)
703 {
704 /* Remove this completed urb from the queue. */
705 if (qurb->prev)
706 {
707 qurb->prev->next = qurb->next;
708 }
709 else
710 {
711 pDevice->pHeadQURBs = qurb->next;
712 }
713
714 if (qurb->next)
715 {
716 qurb->next->prev = qurb->prev;
717 }
718 else
719 {
720 pDevice->pTailQURBs = qurb->prev;
721 }
722
723 qurb->next = NULL;
724 qurb->prev = NULL;
725
726 break;
727 }
728
729 qurb = qurb->next;
730 }
731
732 releaseDevice (pDevice);
733
734 if ( qurb
735 || !pDevice->pHeadQURBs
736 || u32Millies == 0
737 || pDevice->fFailed
738 || (RTTimeMilliTS () - u64StartTime >= (uint64_t)u32Millies))
739 {
740 /* Got an URB or do not have to wait for an URB. */
741 break;
742 }
743
744 LogFlow(("RemoteUSBBackend::iface_ReapURB iteration.\n"));
745
746 RTThreadSleep (10);
747
748 if (pThis->pollingEnabledURB ())
749 {
750 VRDE_USB_REQ_REAP_URB_PARM parm;
751
752 parm.code = VRDE_USB_REQ_REAP_URB;
753
754 pThis->VRDPServer()->SendUSBRequest (u32ClientId, &parm, sizeof (parm));
755 }
756 }
757
758 LogFlow(("RemoteUSBBackend::iface_ReapURB completed in %lld ms, qurb = %p\n", RTTimeMilliTS () - u64StartTime, qurb));
759
760 if (!qurb)
761 {
762 *ppvURB = NULL;
763 *pu32Len = 0;
764 *pu32Err = VUSBSTATUS_OK;
765 }
766 else
767 {
768 *ppvURB = qurb->pvURB;
769 *pu32Len = qurb->u32Len;
770 *pu32Err = qurb->u32Err;
771
772#ifdef LOG_ENABLED
773 Log(("URB len = %d, data = %p\n", qurb->u32Len, qurb->pvURB));
774 if (qurb->u32Len)
775 {
776 Log(("Received URB content:\n%.*Rhxd\n", qurb->u32Len, qurb->pvData));
777 }
778#endif
779
780 qurbFree (qurb);
781 }
782
783 return rc;
784}
785
786void RemoteUSBBackend::AddRef (void)
787{
788 cRefs++;
789}
790
791void RemoteUSBBackend::Release (void)
792{
793 cRefs--;
794
795 if (cRefs <= 0)
796 {
797 delete this;
798 }
799}
800
801void RemoteUSBBackend::PollRemoteDevices (void)
802{
803 if ( mfWillBeDeleted
804 && menmPollRemoteDevicesStatus != PollRemoteDevicesStatus_Dereferenced)
805 {
806 /* Unmount all remote USB devices. */
807 mConsole->processRemoteUSBDevices (mu32ClientId, NULL, 0, false);
808
809 menmPollRemoteDevicesStatus = PollRemoteDevicesStatus_Dereferenced;
810
811 Release ();
812
813 return;
814 }
815
816 switch (menmPollRemoteDevicesStatus)
817 {
818 case PollRemoteDevicesStatus_Negotiate:
819 {
820 VRDEUSBREQNEGOTIATEPARM parm;
821
822 parm.code = VRDE_USB_REQ_NEGOTIATE;
823 parm.version = VRDE_USB_VERSION;
824 /* VRDE_USB_VERSION_3: support VRDE_USB_REQ_DEVICE_LIST_EXT_RET. */
825 parm.flags = VRDE_USB_SERVER_CAPS_PORT_VERSION;
826
827 mServer->SendUSBRequest (mu32ClientId, &parm, sizeof (parm));
828
829 /* Reference the object. When the client disconnects and
830 * the backend is about to be deleted, the method must be called
831 * to disconnect the USB devices (as stated above).
832 */
833 AddRef ();
834
835 /* Goto the disabled state. When a response will be received
836 * the state will be changed to the SendRequest.
837 */
838 menmPollRemoteDevicesStatus = PollRemoteDevicesStatus_WaitNegotiateResponse;
839 } break;
840
841 case PollRemoteDevicesStatus_WaitNegotiateResponse:
842 {
843 LogFlow(("USB::PollRemoteDevices: WaitNegotiateResponse\n"));
844 /* Do nothing. */
845 } break;
846
847 case PollRemoteDevicesStatus_SendRequest:
848 {
849 LogFlow(("USB::PollRemoteDevices: SendRequest\n"));
850
851 /* Send a request for device list. */
852 VRDE_USB_REQ_DEVICE_LIST_PARM parm;
853
854 parm.code = VRDE_USB_REQ_DEVICE_LIST;
855
856 mServer->SendUSBRequest (mu32ClientId, &parm, sizeof (parm));
857
858 menmPollRemoteDevicesStatus = PollRemoteDevicesStatus_WaitResponse;
859 } break;
860
861 case PollRemoteDevicesStatus_WaitResponse:
862 {
863 LogFlow(("USB::PollRemoteDevices: WaitResponse\n"));
864
865 if (mfHasDeviceList)
866 {
867 mConsole->processRemoteUSBDevices (mu32ClientId, (VRDEUSBDEVICEDESC *)mpvDeviceList, mcbDeviceList, mfDescExt);
868 LogFlow(("USB::PollRemoteDevices: WaitResponse after process\n"));
869
870 menmPollRemoteDevicesStatus = PollRemoteDevicesStatus_SendRequest;
871
872 mfHasDeviceList = false;
873 }
874 } break;
875
876 case PollRemoteDevicesStatus_Dereferenced:
877 {
878 LogFlow(("USB::PollRemoteDevices: Dereferenced\n"));
879 /* Do nothing. */
880 } break;
881
882 default:
883 {
884 AssertFailed ();
885 } break;
886 }
887}
888
889void RemoteUSBBackend::NotifyDelete (void)
890{
891 mfWillBeDeleted = true;
892}
893
894/*
895 * The backend maintains a list of UUIDs of devices
896 * which are managed by the backend.
897 */
898bool RemoteUSBBackend::addUUID (const Guid *pUuid)
899{
900 unsigned i;
901 for (i = 0; i < RT_ELEMENTS(aGuids); i++)
902 {
903 if (aGuids[i].isEmpty ())
904 {
905 aGuids[i] = *pUuid;
906 return true;
907 }
908 }
909
910 return false;
911}
912
913bool RemoteUSBBackend::findUUID (const Guid *pUuid)
914{
915 unsigned i;
916 for (i = 0; i < RT_ELEMENTS(aGuids); i++)
917 {
918 if (aGuids[i] == *pUuid)
919 {
920 return true;
921 }
922 }
923
924 return false;
925}
926
927void RemoteUSBBackend::removeUUID (const Guid *pUuid)
928{
929 unsigned i;
930 for (i = 0; i < RT_ELEMENTS(aGuids); i++)
931 {
932 if (aGuids[i] == *pUuid)
933 {
934 aGuids[i].clear ();
935 break;
936 }
937 }
938}
939
940RemoteUSBBackend::RemoteUSBBackend(Console *console, ConsoleVRDPServer *server, uint32_t u32ClientId)
941 :
942 mConsole (console),
943 mServer (server),
944 cRefs (0),
945 mu32ClientId (u32ClientId),
946 mfHasDeviceList (false),
947 mpvDeviceList (NULL),
948 mcbDeviceList (0),
949 menmPollRemoteDevicesStatus (PollRemoteDevicesStatus_Negotiate),
950 mfPollURB (true),
951 mpDevices (NULL),
952 mfWillBeDeleted (false),
953 mClientVersion (0), /* VRDE_USB_VERSION_2: the client version. */
954 mfDescExt (false) /* VRDE_USB_VERSION_3: VRDE_USB_REQ_DEVICE_LIST_EXT_RET. */
955{
956 Assert(console);
957 Assert(server);
958
959 int rc = RTCritSectInit (&mCritsect);
960
961 if (RT_FAILURE(rc))
962 {
963 AssertFailed ();
964 memset (&mCritsect, 0, sizeof (mCritsect));
965 }
966
967 mCallback.pInstance = (PREMOTEUSBBACKEND)this;
968 mCallback.pfnOpen = iface_Open;
969 mCallback.pfnClose = iface_Close;
970 mCallback.pfnReset = iface_Reset;
971 mCallback.pfnSetConfig = iface_SetConfig;
972 mCallback.pfnClaimInterface = iface_ClaimInterface;
973 mCallback.pfnReleaseInterface = iface_ReleaseInterface;
974 mCallback.pfnInterfaceSetting = iface_InterfaceSetting;
975 mCallback.pfnQueueURB = iface_QueueURB;
976 mCallback.pfnReapURB = iface_ReapURB;
977 mCallback.pfnClearHaltedEP = iface_ClearHaltedEP;
978 mCallback.pfnCancelURB = iface_CancelURB;
979}
980
981RemoteUSBBackend::~RemoteUSBBackend()
982{
983 Assert(cRefs == 0);
984
985 if (RTCritSectIsInitialized (&mCritsect))
986 {
987 RTCritSectDelete (&mCritsect);
988 }
989
990 RTMemFree (mpvDeviceList);
991
992 mServer->usbBackendRemoveFromList (this);
993}
994
995int RemoteUSBBackend::negotiateResponse (const VRDEUSBREQNEGOTIATERET *pret, uint32_t cbRet)
996{
997 int rc = VINF_SUCCESS;
998
999 Log(("RemoteUSBBackend::negotiateResponse: flags = %02X.\n", pret->flags));
1000
1001 LogRel(("Remote USB: Received negotiate response. Flags 0x%02X.\n",
1002 pret->flags));
1003
1004 if (pret->flags & VRDE_USB_CAPS_FLAG_POLL)
1005 {
1006 Log(("RemoteUSBBackend::negotiateResponse: client requested URB polling.\n"));
1007 mfPollURB = true;
1008 }
1009 else
1010 {
1011 mfPollURB = false;
1012 }
1013
1014 /* VRDE_USB_VERSION_2: check the client version. */
1015 if (pret->flags & VRDE_USB_CAPS2_FLAG_VERSION)
1016 {
1017 /* This could be a client version > 1. */
1018 if (cbRet >= sizeof (VRDEUSBREQNEGOTIATERET_2))
1019 {
1020 VRDEUSBREQNEGOTIATERET_2 *pret2 = (VRDEUSBREQNEGOTIATERET_2 *)pret;
1021
1022 if (pret2->u32Version <= VRDE_USB_VERSION)
1023 {
1024 /* This is OK. The client wants a version supported by the server. */
1025 mClientVersion = pret2->u32Version;
1026 }
1027 else
1028 {
1029 LogRel(("VRDP: ERROR: unsupported remote USB protocol client version %d.\n", pret2->u32Version));
1030 rc = VERR_NOT_SUPPORTED;
1031 }
1032 }
1033 else
1034 {
1035 LogRel(("VRDP: ERROR: invalid remote USB negotiate request packet size %d.\n", cbRet));
1036 rc = VERR_NOT_SUPPORTED;
1037 }
1038 }
1039 else
1040 {
1041 /* This is a client version 1. */
1042 mClientVersion = VRDE_USB_VERSION_1;
1043 }
1044
1045 if (RT_SUCCESS(rc))
1046 {
1047 LogRel(("VRDP: remote USB protocol version %d.\n", mClientVersion));
1048
1049 /* VRDE_USB_VERSION_3: check the client capabilities: VRDE_USB_CLIENT_CAPS_*. */
1050 if (mClientVersion == VRDE_USB_VERSION_3)
1051 {
1052 if (cbRet >= sizeof (VRDEUSBREQNEGOTIATERET_3))
1053 {
1054 VRDEUSBREQNEGOTIATERET_3 *pret3 = (VRDEUSBREQNEGOTIATERET_3 *)pret;
1055
1056 mfDescExt = (pret3->u32Flags & VRDE_USB_CLIENT_CAPS_PORT_VERSION) != 0;
1057 }
1058 else
1059 {
1060 LogRel(("VRDP: ERROR: invalid remote USB negotiate request packet size %d.\n", cbRet));
1061 rc = VERR_NOT_SUPPORTED;
1062 }
1063 }
1064
1065 menmPollRemoteDevicesStatus = PollRemoteDevicesStatus_SendRequest;
1066 }
1067
1068 return rc;
1069}
1070
1071int RemoteUSBBackend::saveDeviceList (const void *pvList, uint32_t cbList)
1072{
1073 Log(("RemoteUSBBackend::saveDeviceList: pvList = %p, cbList = %d\n", pvList, cbList));
1074
1075 if (!mfHasDeviceList)
1076 {
1077 RTMemFree (mpvDeviceList);
1078 mpvDeviceList = NULL;
1079
1080 mcbDeviceList = cbList;
1081
1082 if (cbList > 0)
1083 {
1084 mpvDeviceList = RTMemAlloc (cbList);
1085 memcpy (mpvDeviceList, pvList, cbList);
1086 }
1087
1088 mfHasDeviceList = true;
1089 }
1090
1091 return VINF_SUCCESS;
1092}
1093
1094void RemoteUSBBackend::request (void)
1095{
1096 int rc = RTCritSectEnter(&mCritsect);
1097 AssertRC(rc);
1098}
1099
1100void RemoteUSBBackend::release (void)
1101{
1102 RTCritSectLeave(&mCritsect);
1103}
1104
1105PREMOTEUSBDEVICE RemoteUSBBackend::deviceFromId (VRDEUSBDEVID id)
1106{
1107 request ();
1108
1109 REMOTEUSBDEVICE *pDevice = mpDevices;
1110
1111 while (pDevice && pDevice->id != id)
1112 {
1113 pDevice = pDevice->next;
1114 }
1115
1116 release ();
1117
1118 return pDevice;
1119}
1120
1121void RemoteUSBBackend::addDevice (PREMOTEUSBDEVICE pDevice)
1122{
1123 request ();
1124
1125 pDevice->next = mpDevices;
1126
1127 if (mpDevices)
1128 {
1129 mpDevices->prev = pDevice;
1130 }
1131
1132 mpDevices = pDevice;
1133
1134 release ();
1135}
1136
1137void RemoteUSBBackend::removeDevice (PREMOTEUSBDEVICE pDevice)
1138{
1139 request ();
1140
1141 if (pDevice->prev)
1142 {
1143 pDevice->prev->next = pDevice->next;
1144 }
1145 else
1146 {
1147 mpDevices = pDevice->next;
1148 }
1149
1150 if (pDevice->next)
1151 {
1152 pDevice->next->prev = pDevice->prev;
1153 }
1154
1155 release ();
1156}
1157
1158int RemoteUSBBackend::reapURB (const void *pvBody, uint32_t cbBody)
1159{
1160 int rc = VINF_SUCCESS;
1161
1162 LogFlow(("RemoteUSBBackend::reapURB: pvBody = %p, cbBody = %d\n", pvBody, cbBody));
1163
1164 VRDEUSBREQREAPURBBODY *pBody = (VRDEUSBREQREAPURBBODY *)pvBody;
1165
1166 while (cbBody >= sizeof (VRDEUSBREQREAPURBBODY))
1167 {
1168 Log(("RemoteUSBBackend::reapURB: id = %d, flags = %02X, error = %d, handle %d, len = %d.\n",
1169 pBody->id, pBody->flags, pBody->error, pBody->handle, pBody->len));
1170
1171 uint8_t fu8ReapValidFlags;
1172
1173 if (mClientVersion == VRDE_USB_VERSION_1 || mClientVersion == VRDE_USB_VERSION_2)
1174 {
1175 fu8ReapValidFlags = VRDE_USB_REAP_VALID_FLAGS;
1176 }
1177 else
1178 {
1179 fu8ReapValidFlags = VRDE_USB_REAP_VALID_FLAGS_3;
1180 }
1181
1182 /* Verify client's data. */
1183 if ( (pBody->flags & ~fu8ReapValidFlags) != 0
1184 || sizeof (VRDEUSBREQREAPURBBODY) > cbBody
1185 || pBody->handle == 0)
1186 {
1187 LogFlow(("RemoteUSBBackend::reapURB: WARNING: invalid reply data. Skipping the reply.\n"));
1188 rc = VERR_INVALID_PARAMETER;
1189 break;
1190 }
1191
1192 PREMOTEUSBDEVICE pDevice = deviceFromId (pBody->id);
1193
1194 if (!pDevice)
1195 {
1196 LogFlow(("RemoteUSBBackend::reapURB: WARNING: invalid device id. Skipping the reply.\n"));
1197 rc = VERR_INVALID_PARAMETER;
1198 break;
1199 }
1200
1201 uint32_t cbBodyData = 0; /* Data contained in the URB body structure for input URBs. */
1202
1203 requestDevice (pDevice);
1204
1205 /* Search the queued URB for given handle. */
1206 REMOTEUSBQURB *qurb = pDevice->pHeadQURBs;
1207
1208 while (qurb && qurb->u32Handle != pBody->handle)
1209 {
1210 LogFlow(("RemoteUSBBackend::reapURB: searching: %p handle = %d.\n", qurb, qurb->u32Handle));
1211 qurb = qurb->next;
1212 }
1213
1214 if (!qurb)
1215 {
1216 LogFlow(("RemoteUSBBackend::reapURB: Queued URB not found, probably already canceled. Skipping the URB.\n"));
1217 }
1218 else
1219 {
1220 LogFlow(("RemoteUSBBackend::reapURB: qurb = %p\n", qurb));
1221
1222 /* Update the URB error field. */
1223 if (mClientVersion == VRDE_USB_VERSION_1)
1224 {
1225 switch(pBody->error)
1226 {
1227 case VRDE_USB_XFER_OK: qurb->u32Err = VUSBSTATUS_OK; break;
1228 case VRDE_USB_XFER_STALL: qurb->u32Err = VUSBSTATUS_STALL; break;
1229 case VRDE_USB_XFER_DNR: qurb->u32Err = VUSBSTATUS_DNR; break;
1230 case VRDE_USB_XFER_CRC: qurb->u32Err = VUSBSTATUS_CRC; break;
1231 default: Log(("RemoteUSBBackend::reapURB: Invalid error %d\n", pBody->error));
1232 qurb->u32Err = VUSBSTATUS_DNR; break;
1233 }
1234 }
1235 else if ( mClientVersion == VRDE_USB_VERSION_2
1236 || mClientVersion == VRDE_USB_VERSION_3)
1237 {
1238 switch(pBody->error)
1239 {
1240 case VRDE_USB_XFER_OK: qurb->u32Err = VUSBSTATUS_OK; break;
1241 case VRDE_USB_XFER_STALL: qurb->u32Err = VUSBSTATUS_STALL; break;
1242 case VRDE_USB_XFER_DNR: qurb->u32Err = VUSBSTATUS_DNR; break;
1243 case VRDE_USB_XFER_CRC: qurb->u32Err = VUSBSTATUS_CRC; break;
1244 case VRDE_USB_XFER_DO: qurb->u32Err = VUSBSTATUS_DATA_OVERRUN; break;
1245 case VRDE_USB_XFER_DU: qurb->u32Err = VUSBSTATUS_DATA_UNDERRUN; break;
1246
1247 /* Unmapped errors. */
1248 case VRDE_USB_XFER_BS:
1249 case VRDE_USB_XFER_DTM:
1250 case VRDE_USB_XFER_PCF:
1251 case VRDE_USB_XFER_UPID:
1252 case VRDE_USB_XFER_BO:
1253 case VRDE_USB_XFER_BU:
1254 case VRDE_USB_XFER_ERR:
1255 default: Log(("RemoteUSBBackend::reapURB: Invalid error %d\n", pBody->error));
1256 qurb->u32Err = VUSBSTATUS_DNR; break;
1257 }
1258 }
1259 else
1260 {
1261 qurb->u32Err = VUSBSTATUS_DNR;
1262 }
1263
1264 /* Get the URB data. */
1265 bool fURBCompleted = true;
1266
1267 if (qurb->fInput)
1268 {
1269 cbBodyData = pBody->len; /* VRDE_USB_DIRECTION_IN URBs include some data. */
1270 }
1271
1272 if ( qurb->u32Err == VUSBSTATUS_OK
1273 && qurb->fInput)
1274 {
1275 LogFlow(("RemoteUSBBackend::reapURB: copying data %d bytes\n", pBody->len));
1276
1277 uint32_t u32DataLen = qurb->u32TransferredLen + pBody->len;
1278
1279 if (u32DataLen > qurb->u32Len)
1280 {
1281 /* Received more data than expected for this URB. If there more fragments follow,
1282 * they will be discarded because the URB handle will not be valid anymore.
1283 */
1284 qurb->u32Err = VUSBSTATUS_DNR;
1285 }
1286 else
1287 {
1288 memcpy ((uint8_t *)qurb->pvData + qurb->u32TransferredLen, &pBody[1], pBody->len);
1289 }
1290
1291 if ( qurb->u32Err == VUSBSTATUS_OK
1292 && (pBody->flags & VRDE_USB_REAP_FLAG_FRAGMENT) != 0)
1293 {
1294 /* If the client sends fragmented packets, accumulate the URB data. */
1295 fURBCompleted = false;
1296 }
1297 }
1298
1299 qurb->u32TransferredLen += pBody->len; /* Update the value for all URBs. */
1300
1301 if (fURBCompleted)
1302 {
1303 /* Move the URB near the head of URB list, so that iface_ReapURB can
1304 * find it faster. Note that the order of completion must be preserved!
1305 */
1306 if (qurb->prev)
1307 {
1308 /* The URB is not in the head. Unlink it from its current position. */
1309 qurb->prev->next = qurb->next;
1310
1311 if (qurb->next)
1312 {
1313 qurb->next->prev = qurb->prev;
1314 }
1315 else
1316 {
1317 pDevice->pTailQURBs = qurb->prev;
1318 }
1319
1320 /* And insert it to its new place. */
1321 if (pDevice->pHeadQURBs->fCompleted)
1322 {
1323 /* At least one other completed URB; insert after the
1324 * last completed URB.
1325 */
1326 REMOTEUSBQURB *prev_qurb = pDevice->pHeadQURBs;
1327 while (prev_qurb->next && prev_qurb->next->fCompleted)
1328 prev_qurb = prev_qurb->next;
1329
1330 qurb->next = prev_qurb->next;
1331 qurb->prev = prev_qurb;
1332
1333 if (prev_qurb->next)
1334 prev_qurb->next->prev = qurb;
1335 else
1336 pDevice->pTailQURBs = qurb;
1337 prev_qurb->next = qurb;
1338 }
1339 else
1340 {
1341 /* No other completed URBs; insert at head. */
1342 qurb->next = pDevice->pHeadQURBs;
1343 qurb->prev = NULL;
1344
1345 pDevice->pHeadQURBs->prev = qurb;
1346 pDevice->pHeadQURBs = qurb;
1347 }
1348 }
1349
1350 qurb->u32Len = qurb->u32TransferredLen; /* Update the final length. */
1351 qurb->fCompleted = true;
1352 }
1353 }
1354
1355 releaseDevice (pDevice);
1356
1357 if (pBody->flags & VRDE_USB_REAP_FLAG_LAST)
1358 {
1359 break;
1360 }
1361
1362 /* There is probably a further URB body. */
1363 uint32_t cbBodySize = sizeof (VRDEUSBREQREAPURBBODY) + cbBodyData;
1364
1365 if (cbBodySize > cbBody)
1366 {
1367 rc = VERR_INVALID_PARAMETER;
1368 break;
1369 }
1370
1371 pBody = (VRDEUSBREQREAPURBBODY *)((uint8_t *)pBody + cbBodySize);
1372 cbBody -= cbBodySize;
1373 }
1374
1375 LogFlow(("RemoteUSBBackend::reapURB: returns %Rrc\n", rc));
1376
1377 return rc;
1378}
1379/* vi: set tabstop=4 shiftwidth=4 expandtab: */
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette