VirtualBox

source: vbox/trunk/src/VBox/Devices/USB/VUSBReadAhead.cpp@ 27411

Last change on this file since 27411 was 26970, checked in by vboxsync, 15 years ago

Export code for handling virtual USB devices to OSE, mainly for emulating USB mouse and USB keyboard. This does NOT include support for passing through USB host devices!

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 13.9 KB
Line 
1/* $Id: VUSBReadAhead.cpp 26970 2010-03-02 20:43:37Z vboxsync $ */
2/** @file
3 * Virtual USB - Read-ahead buffering for periodic endpoints.
4 */
5
6/*
7 * Copyright (C) 2006-2009 Sun Microsystems, Inc.
8 *
9 * Sun Microsystems, Inc. confidential
10 * All rights reserved
11 */
12
13
14/*******************************************************************************
15* Header Files *
16*******************************************************************************/
17#define LOG_GROUP LOG_GROUP_DRV_VUSB
18#include <VBox/pdm.h>
19#include <VBox/vmapi.h>
20#include <VBox/err.h>
21#include <VBox/log.h>
22#include <iprt/alloc.h>
23#include <iprt/time.h>
24#include <iprt/thread.h>
25#include <iprt/semaphore.h>
26#include <iprt/string.h>
27#include <iprt/assert.h>
28#include <iprt/asm.h>
29#include "VUSBInternal.h"
30
31
32/*******************************************************************************
33* Structures and Typedefs *
34*******************************************************************************/
35
36/**
37 * Argument package of vusbDevReadAheadThread().
38 */
39typedef struct vusb_read_ahead_args
40{
41 /** Pointer to the device which the thread is for. */
42 PVUSBDEV pDev;
43 /** Pointer to the pipe which the thread is servicing. */
44 PVUSBPIPE pPipe;
45 /** A flag indicating a high-speed (vs. low/full-speed) endpoint. */
46 bool fHighSpeed;
47 /** A flag telling the thread to terminate. */
48 bool fTerminate;
49} VUSBREADAHEADARGS, *PVUSBREADAHEADARGS;
50
51
52/*******************************************************************************
53* Implementation *
54*******************************************************************************/
55
56static PVUSBURB vusbDevNewIsocUrb(PVUSBDEV pDev, unsigned uEndPt, unsigned uInterval, unsigned uPktSize)
57{
58 PVUSBURB pUrb;
59 unsigned cPackets = 0;
60 uint32_t cbTotal = 0;
61 unsigned uNextIndex = 0;
62
63 Assert(pDev);
64 Assert(uEndPt);
65 Assert(uInterval);
66 Assert(uPktSize);
67
68 /* Calculate the amount of data needed, taking the endpoint's bInterval into account */
69 for (unsigned i = 0; i < 8; ++i)
70 {
71 if (i == uNextIndex)
72 {
73 cbTotal += uPktSize;
74 cPackets++;
75 uNextIndex += uInterval;
76 }
77 }
78 Assert(cbTotal <= 24576);
79
80 // @todo: What do we do if cPackets is 0?
81
82 /*
83 * Allocate and initialize the URB.
84 */
85 Assert(pDev->u8Address != VUSB_INVALID_ADDRESS);
86
87 PVUSBROOTHUB pRh = vusbDevGetRh(pDev);
88 if (!pRh)
89 /* can happen during disconnect */
90 return NULL;
91
92 pUrb = vusbRhNewUrb(pRh, pDev->u8Address, cbTotal, 1);
93 if (!pUrb)
94 /* not much we can do here... */
95 return NULL;
96
97 pUrb->enmType = VUSBXFERTYPE_ISOC;
98 pUrb->EndPt = uEndPt;
99 pUrb->enmDir = VUSBDIRECTION_IN;
100 pUrb->fShortNotOk = false;
101 pUrb->enmStatus = VUSBSTATUS_OK;
102 pUrb->Hci.EdAddr = 0;
103 pUrb->Hci.fUnlinked = false;
104// @todo: fill in the rest? The Hci member is not relevant
105#ifdef LOG_ENABLED
106 static unsigned s_iSerial = 0;
107 s_iSerial = (s_iSerial + 1) % 10000;
108 RTStrAPrintf(&pUrb->pszDesc, "URB %p prab<%04d", pUrb, s_iSerial); // prab = Periodic Read-Ahead Buffer
109#endif
110
111 /* Set up the individual packets, again with bInterval in mind */
112 pUrb->cIsocPkts = 8;
113 unsigned off = 0;
114 uNextIndex = 0;
115 for (unsigned i = 0; i < 8; i++)
116 {
117 pUrb->aIsocPkts[i].enmStatus = VUSBSTATUS_NOT_ACCESSED;
118 pUrb->aIsocPkts[i].off = off;
119 if (i == uNextIndex) // skip unused packets
120 {
121 pUrb->aIsocPkts[i].cb = uPktSize;
122 off += uPktSize;
123 uNextIndex += uInterval;
124 }
125 else
126 pUrb->aIsocPkts[i].cb = 0;
127 }
128 Assert(off == cbTotal);
129 return pUrb;
130}
131
132/**
133 * Thread function for performing read-ahead buffering of periodic input.
134 *
135 * This thread keeps a buffer (queue) filled with data read from a periodic
136 * input endpoint.
137 *
138 * The problem: In the EHCI emulation, there is a very short period between the
139 * time when the guest can schedule a request and the time when it expects the results.
140 * This leads to many dropped URBs because by the time we get the data from the host,
141 * the guest already gave up and moved on.
142 *
143 * The solution: For periodic transfers, we know the worst-case bandwidth. We can
144 * read ahead and buffer a few milliseconds worth of data. That way data is available
145 * by the time the guest asks for it and we can deliver it immediately.
146 *
147 * @returns success indicator.
148 * @param Thread This thread.
149 * @param pvUser Pointer to a VUSBREADAHEADARGS structure.
150 */
151static DECLCALLBACK(int) vusbDevReadAheadThread(RTTHREAD Thread, void *pvUser)
152{
153 PVUSBPIPE pPipe;
154 PVUSBREADAHEADARGS pArgs = (PVUSBREADAHEADARGS)pvUser;
155 PCVUSBDESCENDPOINT pDesc;
156 PVUSBURB pUrb;
157 int rc = VINF_SUCCESS;
158 unsigned max_pkt_size, mult, interval;
159
160 LogFlow(("vusb: periodic read-ahead buffer thread started\n"));
161 Assert(pArgs);
162 Assert(pArgs->pPipe && pArgs->pDev);
163
164 pPipe = pArgs->pPipe;
165 pDesc = &pPipe->in->Core;
166 Assert(pDesc);
167
168 /* The previous read-ahead thread could be still running (vusbReadAheadStop sets only
169 * fTerminate to true and returns immediately). Therefore we have to wait until the
170 * previous thread is done and all submitted URBs are completed. */
171 while (pPipe->cSubmitted > 0)
172 {
173 Log2(("vusbDevReadAheadThread: still %u packets submitted, waiting before starting...\n", pPipe->cSubmitted));
174 RTThreadSleep(1);
175 }
176 pPipe->pvReadAheadArgs = pArgs;
177 pPipe->cBuffered = 0;
178
179 /* Figure out the maximum bandwidth we might need */
180 if (pArgs->fHighSpeed)
181 {
182 /* High-speed endpoint */
183 Assert((pDesc->wMaxPacketSize & 0x1fff) == pDesc->wMaxPacketSize);
184 Assert(pDesc->bInterval <= 16);
185 interval = pDesc->bInterval ? 1 << (pDesc->bInterval - 1) : 1;
186 max_pkt_size = pDesc->wMaxPacketSize & 0x7ff;
187 mult = ((pDesc->wMaxPacketSize & 0x1800) >> 11) + 1;
188 }
189 else
190 {
191 /* Full- or low-speed endpoint */
192 Assert((pDesc->wMaxPacketSize & 0x7ff) == pDesc->wMaxPacketSize);
193 interval = pDesc->bInterval;
194 max_pkt_size = pDesc->wMaxPacketSize;
195 mult = 1;
196 }
197 Log(("vusb: interval=%u, max pkt size=%u, multiplier=%u\n", interval, max_pkt_size, mult));
198
199 /*
200 * Submit new URBs in a loop unless the buffer is too full (paused VM etc.). Note that we only
201 * queue the URBs here, they are reaped on a different thread.
202 */
203 while (pArgs->fTerminate == false)
204 {
205 while (pPipe->cSubmitted < 120 && pPipe->cBuffered < 120)
206 {
207 pUrb = vusbDevNewIsocUrb(pArgs->pDev, pDesc->bEndpointAddress & 0xF, interval, max_pkt_size * mult);
208 if (!pUrb) {
209 /* Happens if device was unplugged. */
210 Log(("vusb: read-ahead thread failed to allocate new URB; exiting\n"));
211 vusbReadAheadStop(pvUser);
212 break;
213 }
214
215 Assert(pUrb->enmState == VUSBURBSTATE_ALLOCATED);
216
217 // @todo: at the moment we abuse the Hci.pNext member (which is otherwise entirely unused!)
218 pUrb->Hci.pNext = (PVUSBURB)pvUser;
219
220 pUrb->enmState = VUSBURBSTATE_IN_FLIGHT;
221 rc = vusbUrbQueueAsyncRh(pUrb);
222 if (RT_FAILURE(rc))
223 {
224 /* Happens if device was unplugged. */
225 Log(("vusb: read-ahead thread failed to queue URB with %Rrc; exiting\n", rc));
226 vusbReadAheadStop(pvUser);
227 break;
228 }
229 ++pPipe->cSubmitted;
230 }
231 RTThreadSleep(1);
232 }
233 LogFlow(("vusb: periodic read-ahead buffer thread exiting\n"));
234 pPipe->pvReadAheadArgs = NULL;
235
236 /* wait until there are no more submitted packets */
237 while (pPipe->cSubmitted > 0)
238 {
239 Log2(("vusbDevReadAheadThread: still %u packets submitted, waiting before terminating...\n", pPipe->cSubmitted));
240 RTThreadSleep(1);
241 }
242
243 RTMemTmpFree(pArgs);
244
245 return rc;
246}
247
248/**
249 * Completes a read-ahead URB. This function does *not* free the URB but puts
250 * it on a queue instead. The URB is only freed when the guest asks for the data
251 * (by reading on the buffered pipe) or when the pipe/device is torn down.
252 */
253void vusbUrbCompletionReadAhead(PVUSBURB pUrb)
254{
255 Assert(pUrb);
256 Assert(pUrb->Hci.pNext);
257 PVUSBREADAHEADARGS pArgs = (PVUSBREADAHEADARGS)pUrb->Hci.pNext;
258 PVUSBPIPE pPipe = pArgs->pPipe;
259 Assert(pPipe);
260
261 pUrb->Hci.pNext = NULL; // @todo: use a more suitable field
262 if (pPipe->pBuffUrbHead == NULL)
263 {
264 // The queue is empty, this is easy
265 Assert(!pPipe->pBuffUrbTail);
266 pPipe->pBuffUrbTail = pPipe->pBuffUrbHead = pUrb;
267 }
268 else
269 {
270 // Some URBs are queued already
271 Assert(pPipe->pBuffUrbTail);
272 Assert(!pPipe->pBuffUrbTail->Hci.pNext);
273 pPipe->pBuffUrbTail = pPipe->pBuffUrbTail->Hci.pNext = pUrb;
274 }
275 --pPipe->cSubmitted;
276 ++pPipe->cBuffered;
277}
278
279/**
280 * Process a submit of an input URB on a pipe with read-ahead buffering. Instead
281 * of passing the URB to the proxy, we use previously read data stored in the
282 * read-ahead buffer, immediately complete the input URB and free the buffered URB.
283 *
284 * @param pUrb The URB submitted by HC
285 * @param pPipe The pipe with read-ahead buffering
286 *
287 * @return int Status code
288 */
289int vusbUrbSubmitBufferedRead(PVUSBURB pUrb, PVUSBPIPE pPipe)
290{
291 PVUSBURB pBufferedUrb;
292 Assert(pUrb && pPipe);
293
294 pBufferedUrb = pPipe->pBuffUrbHead;
295 if (pBufferedUrb)
296 {
297 unsigned cbTotal;
298
299 // There's a URB available in the read-ahead buffer; use it
300 pPipe->pBuffUrbHead = pBufferedUrb->Hci.pNext;
301 if (pPipe->pBuffUrbHead == NULL)
302 pPipe->pBuffUrbTail = NULL;
303
304 --pPipe->cBuffered;
305
306 // Make sure the buffered URB is what we expect
307 Assert(pUrb->enmType == pBufferedUrb->enmType);
308 Assert(pUrb->EndPt == pBufferedUrb->EndPt);
309 Assert(pUrb->enmDir == pBufferedUrb->enmDir);
310
311 pUrb->enmState = VUSBURBSTATE_REAPED;
312 pUrb->enmStatus = pBufferedUrb->enmStatus;
313 cbTotal = 0;
314 // Copy status and data received from the device
315 for (unsigned i = 0; i < pUrb->cIsocPkts; ++i)
316 {
317 unsigned off, len;
318
319 off = pBufferedUrb->aIsocPkts[i].off;
320 len = pBufferedUrb->aIsocPkts[i].cb;
321 pUrb->aIsocPkts[i].cb = len;
322 pUrb->aIsocPkts[i].off = off;
323 pUrb->aIsocPkts[i].enmStatus = pBufferedUrb->aIsocPkts[i].enmStatus;
324 cbTotal += len;
325 Assert(pUrb->VUsb.cbDataAllocated >= cbTotal);
326 memcpy(&pUrb->abData[off], &pBufferedUrb->abData[off], len);
327 }
328 // Give back the data to the HC right away and then free the buffered URB
329 vusbUrbCompletionRh(pUrb);
330 // This assertion is wrong as the URB could be re-allocated in the meantime by the EMT (race)
331 // Assert(pUrb->enmState == VUSBURBSTATE_FREE);
332 Assert(pBufferedUrb->enmState == VUSBURBSTATE_REAPED);
333 LogFlow(("%s: vusbUrbSubmitBufferedRead: Freeing buffered URB\n", pBufferedUrb->pszDesc));
334 pBufferedUrb->VUsb.pfnFree(pBufferedUrb);
335 // This assertion is wrong as the URB could be re-allocated in the meantime by the EMT (race)
336 // Assert(pBufferedUrb->enmState == VUSBURBSTATE_FREE);
337 }
338 else
339 {
340 // No URB on hand. Either we exhausted the buffer (shouldn't happen!) or the guest simply
341 // asked for data too soon. Pretend that the device didn't deliver any data.
342 pUrb->enmState = VUSBURBSTATE_REAPED;
343 pUrb->enmStatus = VUSBSTATUS_DATA_UNDERRUN;
344 for (unsigned i = 0; i < pUrb->cIsocPkts; ++i)
345 {
346 pUrb->aIsocPkts[i].cb = 0;
347 pUrb->aIsocPkts[i].enmStatus = VUSBSTATUS_NOT_ACCESSED;
348 }
349 vusbUrbCompletionRh(pUrb);
350 // This assertion is wrong as the URB could be re-allocated in the meantime by the EMT (race)
351 // Assert(pUrb->enmState == VUSBURBSTATE_FREE);
352 LogFlow(("%s: vusbUrbSubmitBufferedRead: No buffered URB available!\n", pBufferedUrb->pszDesc));
353 }
354 return VINF_SUCCESS;
355}
356
357/* Read-ahead start/stop functions, used primarily to keep the PVUSBREADAHEADARGS struct private to this module. */
358
359void vusbReadAheadStart(PVUSBDEV pDev, PVUSBPIPE pPipe)
360{
361 int rc;
362 PVUSBREADAHEADARGS pArgs = (PVUSBREADAHEADARGS)RTMemTmpAlloc(sizeof(*pArgs));
363
364 if (pArgs)
365 {
366 pArgs->pDev = pDev;
367 pArgs->pPipe = pPipe;
368 pArgs->fTerminate = false;
369 pArgs->fHighSpeed = ((vusbDevGetRh(pDev)->fHcVersions & VUSB_STDVER_20) != 0);
370 if (pArgs->fHighSpeed)
371 rc = RTThreadCreate(&pPipe->ReadAheadThread, vusbDevReadAheadThread, pArgs, 0, RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "USBISOC");
372 else
373 rc = VERR_VUSB_DEVICE_NOT_ATTACHED; // No buffering for low/full-speed devices at the moment, needs testing.
374 if (RT_SUCCESS(rc))
375 {
376 Log(("vusb: created isochronous read-ahead thread\n"));
377 }
378 else
379 {
380 Log(("vusb: isochronous read-ahead thread creation failed, rc=%d\n", rc));
381 pPipe->ReadAheadThread = NIL_RTTHREAD;
382 RTMemTmpFree(pArgs);
383 }
384 }
385 /* If thread creation failed for any reason, simply fall back to standard processing. */
386}
387
388void vusbReadAheadStop(void *pvReadAheadArgs)
389{
390 PVUSBREADAHEADARGS pArgs = (PVUSBREADAHEADARGS)pvReadAheadArgs;
391 Log(("vusb: terminating read-ahead thread for endpoint\n"));
392 pArgs->fTerminate = true;
393}
394
395/*
396 * Local Variables:
397 * mode: c
398 * c-file-style: "bsd"
399 * c-basic-offset: 4
400 * tab-width: 4
401 * indent-tabs-mode: s
402 * End:
403 */
404
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