VirtualBox

source: vbox/trunk/src/VBox/Devices/USB/VUSBBufferedPipe.cpp@ 62958

Last change on this file since 62958 was 62958, checked in by vboxsync, 8 years ago

Devices: warnings

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 19.7 KB
Line 
1/* $Id: VUSBBufferedPipe.cpp 62958 2016-08-04 07:54:03Z vboxsync $ */
2/** @file
3 * Virtual USB - Buffering for isochronous in/outpipes.
4 */
5
6/*
7 * Copyright (C) 2006-2016 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
19/*********************************************************************************************************************************
20* Header Files *
21*********************************************************************************************************************************/
22#define LOG_GROUP LOG_GROUP_DRV_VUSB
23#include <VBox/err.h>
24#include <VBox/log.h>
25#include <iprt/alloc.h>
26#include <iprt/assert.h>
27#include <iprt/string.h>
28#include <iprt/path.h>
29#include <iprt/critsect.h>
30#include <iprt/circbuf.h>
31#include "VUSBInternal.h"
32
33
34/*********************************************************************************************************************************
35* Structures and Typedefs *
36*********************************************************************************************************************************/
37
38/**
39 * Copy of the isoc packet descriptors.
40 */
41typedef struct VUSBISOCDESC
42{
43 /** Total number of bytes described by the packets. */
44 size_t cbTotal;
45 /** The number of isochronous packets described in aIsocPkts. */
46 uint32_t cIsocPkts;
47 /** The iso packets. */
48 VUSBURBISOCPKT aIsocPkts[8];
49} VUSBISOCDESC;
50/** Pointer to a isoc packets descriptor. */
51typedef VUSBISOCDESC *PVUSBISOCDESC;
52
53/**
54 * Buffered pipe state.
55 */
56typedef enum VUSBBUFFEREDPIPESTATE
57{
58 /** Invalid state. */
59 VUSBBUFFEREDPIPESTATE_INVALID = 0,
60 /** The buffer is created. */
61 VUSBBUFFEREDPIPESTATE_CREATING,
62 /** The buffer is destroyed. */
63 VUSBBUFFEREDPIPESTATE_DESTROYING,
64 /** The buffer is filling with data. */
65 VUSBBUFFEREDPIPESTATE_FILLING,
66 /** The buffer is streaming data to the guest/device based on the direction. */
67 VUSBBUFFEREDPIPESTATE_STREAMING,
68 /** 32bit hack. */
69 VUSBBUFFEREDPIPESTATE_32BIT_HACK = 0x7fffffff
70} VUSBBUFFEREDPIPESTATE;
71/** Pointer to a buffered pipe state. */
72typedef VUSBBUFFEREDPIPESTATE *PVUSBBUFFEREDPIPESTATE;
73
74/**
75 * VUSB buffered pipe instance data.
76 */
77typedef struct VUSBBUFFEREDPIPEINT
78{
79 /** Pointer to the device which the thread is for. */
80 PVUSBDEV pDev;
81 /** Pointer to the pipe which the thread is servicing. */
82 PVUSBPIPE pPipe;
83 /** USB speed to serve. */
84 VUSBSPEED enmSpeed;
85 /** The buffered pipe state. */
86 VUSBBUFFEREDPIPESTATE enmState;
87 /** Maximum latency the buffer should cause. */
88 uint32_t cLatencyMs;
89 /** Interval of the endpoint in frames (Low/Full-speed 1ms per frame, High-speed 125us). */
90 unsigned uInterval;
91 /** Packet size. */
92 size_t cbPktSize;
93 /** Endpoint. */
94 unsigned uEndPt;
95 /** The direction of the buffering. */
96 VUSBDIRECTION enmDirection;
97 /** Size of the ring buffer to keep all data for buffering. */
98 size_t cbRingBufData;
99 /** The circular buffer keeping the data. */
100 PRTCIRCBUF pRingBufData;
101 /** Number of URBs in flight on the device. */
102 unsigned cUrbsInFlight;
103 /** Critical section protecting the ring buffer and. */
104 RTCRITSECT CritSectBuffer;
105 /** Number of isoc descriptors for buffering. */
106 unsigned cIsocDesc;
107 /** Current index into the isoc descriptor array for reading. */
108 unsigned idxIsocDescRead;
109 /** Current index of the isoc descriptor for writing. */
110 unsigned idxIsocDescWrite;
111 /** Array of isoc descriptors for pre buffering. */
112 PVUSBISOCDESC paIsocDesc;
113 /** Our own URB pool. */
114 VUSBURBPOOL UrbPool;
115#ifdef DEBUG
116 /** Lock contention counter. */
117 volatile uint32_t cLockContention;
118#endif
119#ifdef LOG_ENABLED
120 /** Serial number tag for logging. */
121 uint32_t iSerial;
122#endif
123} VUSBBUFFEREDPIPEINT, *PVUSBBUFFEREDPIPEINT;
124
125
126/*********************************************************************************************************************************
127* Implementation *
128*********************************************************************************************************************************/
129
130/**
131 * Callback for freeing an URB.
132 * @param pUrb The URB to free.
133 */
134static DECLCALLBACK(void) vusbBufferedPipeFreeUrb(PVUSBURB pUrb)
135{
136 /*
137 * Assert sanity.
138 */
139 vusbUrbAssert(pUrb);
140 PVUSBBUFFEREDPIPEINT pThis = (PVUSBBUFFEREDPIPEINT)pUrb->pVUsb->pvFreeCtx;
141 AssertPtr(pThis);
142
143 /*
144 * Free the URB description (logging builds only).
145 */
146 if (pUrb->pszDesc)
147 {
148 RTStrFree(pUrb->pszDesc);
149 pUrb->pszDesc = NULL;
150 }
151
152 vusbUrbPoolFree(&pThis->UrbPool, pUrb);
153}
154
155
156#ifdef DEBUG
157/**
158 * Locks the buffered pipe for exclusive access.
159 *
160 * @returns nothing.
161 * @param pThis The buffered pipe instance.
162 */
163DECLINLINE(void) vusbBufferedPipeLock(PVUSBBUFFEREDPIPEINT pThis)
164{
165 int rc = RTCritSectTryEnter(&pThis->CritSectBuffer);
166 if (rc == VERR_SEM_BUSY)
167 {
168 ASMAtomicIncU32(&pThis->cLockContention);
169 RTCritSectEnter(&pThis->CritSectBuffer);
170 }
171}
172#else
173# define vusbBufferedPipeLock(a_pThis) RTCritSectEnter(&(a_pThis)->CritSectBuffer)
174#endif
175
176
177/**
178 * Create a new isochronous URB.
179 *
180 * @returns Pointer to the new URB or NULL on failure.
181 * @param pThis The buffered pipe instance.
182 * @param pIsocDesc The isochronous packet descriptor saved from the HC submitted URB.
183 */
184static PVUSBURB vusbBufferedPipeNewIsocUrb(PVUSBBUFFEREDPIPEINT pThis, PVUSBISOCDESC pIsocDesc)
185{
186 PVUSBURB pUrb;
187
188 /*
189 * Allocate and initialize the URB.
190 */
191 Assert(pThis->pDev->u8Address != VUSB_INVALID_ADDRESS);
192
193 pUrb = vusbUrbPoolAlloc(&pThis->UrbPool, VUSBXFERTYPE_ISOC, pThis->enmDirection, pIsocDesc->cbTotal,
194 0, 0, 0);
195 if (!pUrb)
196 /* not much we can do here... */
197 return NULL;
198
199 pUrb->EndPt = pThis->uEndPt;
200 pUrb->fShortNotOk = false;
201 pUrb->enmStatus = VUSBSTATUS_OK;
202 pUrb->pVUsb->pvBuffered = pThis;
203 pUrb->pVUsb->pvFreeCtx = pThis;
204 pUrb->pVUsb->pfnFree = vusbBufferedPipeFreeUrb;
205 pUrb->DstAddress = pThis->pDev->u8Address;
206 pUrb->pVUsb->pDev = pThis->pDev;
207
208#ifdef LOG_ENABLED
209 pThis->iSerial = (pThis->iSerial + 1) % 10000;
210 RTStrAPrintf(&pUrb->pszDesc, "URB %p isoc%c%04d (buffered)", pUrb,
211 (pUrb->enmDir == VUSBDIRECTION_IN) ? '<' : '>',
212 pThis->iSerial);
213#endif
214
215 /* Copy data over. */
216 void *pv = NULL;
217 size_t cb = 0;
218 RTCircBufAcquireReadBlock(pThis->pRingBufData, pIsocDesc->cbTotal, &pv, &cb);
219 memcpy(&pUrb->abData[0], pv, cb);
220 RTCircBufReleaseReadBlock(pThis->pRingBufData, cb);
221 /* Take possible wraparound in the ring buffer into account. */
222 if (cb < pIsocDesc->cbTotal)
223 {
224 size_t cb2 = 0;
225 RTCircBufAcquireReadBlock(pThis->pRingBufData, pIsocDesc->cbTotal - cb, &pv, &cb2);
226 memcpy(&pUrb->abData[cb], pv, cb2);
227 RTCircBufReleaseReadBlock(pThis->pRingBufData, cb2);
228 Assert(pIsocDesc->cbTotal == cb + cb2);
229 }
230
231 /* Set up the individual packets. */
232 pUrb->cIsocPkts = pIsocDesc->cIsocPkts;
233 for (unsigned i = 0; i < pUrb->cIsocPkts; i++)
234 {
235 pUrb->aIsocPkts[i].enmStatus = VUSBSTATUS_NOT_ACCESSED;
236 pUrb->aIsocPkts[i].off = pIsocDesc->aIsocPkts[i].off;
237 pUrb->aIsocPkts[i].cb = pIsocDesc->aIsocPkts[i].cb;
238 }
239
240 return pUrb;
241}
242
243
244/**
245 * Stream waiting data to the device.
246 *
247 * @returns VBox status code.
248 * @param pThis The buffered pipe instance.
249 */
250static int vusbBufferedPipeStream(PVUSBBUFFEREDPIPEINT pThis)
251{
252 int rc = VINF_SUCCESS;
253
254 while ( pThis->idxIsocDescRead != pThis->idxIsocDescWrite
255 && RT_SUCCESS(rc))
256 {
257 PVUSBURB pUrb = vusbBufferedPipeNewIsocUrb(pThis, &pThis->paIsocDesc[pThis->idxIsocDescRead]);
258 if (pUrb)
259 {
260 pUrb->enmState = VUSBURBSTATE_IN_FLIGHT;
261 rc = vusbUrbQueueAsyncRh(pUrb);
262 if (RT_SUCCESS(rc))
263 pThis->cUrbsInFlight++;
264 else
265 pUrb->pVUsb->pfnFree(pUrb);
266 }
267 else
268 rc = VERR_NO_MEMORY;
269
270 pThis->idxIsocDescRead = (pThis->idxIsocDescRead + 1) % pThis->cIsocDesc;
271 }
272
273 return rc;
274}
275
276
277/**
278 * Set parameters for the buffered pipe like packet size from the given endpoint.
279 *
280 * @returns VBox status code.
281 * @param pThis The buffered pipe instance.
282 * @param pDesc The endpoint descriptor to take the data from.
283 */
284static int vusbBufferedPipeSetParamsFromDescriptor(PVUSBBUFFEREDPIPEINT pThis, PCVUSBDESCENDPOINT pDesc)
285{
286 unsigned cbPktMax;
287 unsigned uInterval;
288 unsigned uMult;
289
290 if (pThis->enmSpeed == VUSB_SPEED_HIGH)
291 {
292 /* High-speed endpoint */
293 Assert((pDesc->wMaxPacketSize & 0x1fff) == pDesc->wMaxPacketSize);
294 Assert(pDesc->bInterval <= 16);
295 uInterval = pDesc->bInterval ? 1 << (pDesc->bInterval - 1) : 1;
296 cbPktMax = pDesc->wMaxPacketSize & 0x7ff;
297 uMult = ((pDesc->wMaxPacketSize & 0x1800) >> 11) + 1;
298 }
299 else if (pThis->enmSpeed == VUSB_SPEED_FULL || pThis->enmSpeed == VUSB_SPEED_LOW)
300 {
301 /* Full- or low-speed endpoint */
302 Assert((pDesc->wMaxPacketSize & 0x7ff) == pDesc->wMaxPacketSize);
303 uInterval = pDesc->bInterval;
304 cbPktMax = pDesc->wMaxPacketSize;
305 uMult = 1;
306 }
307 else
308 {
309 /** @todo: Implement for super speed and up if it turns out to be required, at the moment it looks
310 * like we don't need it. */
311 return VERR_NOT_SUPPORTED;
312 }
313
314 pThis->uInterval = uInterval;
315 pThis->cbPktSize = cbPktMax * uMult;
316 pThis->uEndPt = pDesc->bEndpointAddress & 0xf;
317
318 unsigned cPackets = pThis->cLatencyMs / pThis->uInterval;
319 cPackets = RT_MAX(cPackets, 1); /* At least one packet. */
320 pThis->cbRingBufData = pThis->cbPktSize * cPackets;
321 pThis->cIsocDesc = cPackets / 8 + ((cPackets % 8) ? 1 : 0);
322
323 return VINF_SUCCESS;
324}
325
326
327/**
328 * Completes an URB issued by the pipe buffer.
329 *
330 * @returns nothing.
331 * @param pUrb The completed URB.
332 */
333DECLHIDDEN(void) vusbBufferedPipeCompleteUrb(PVUSBURB pUrb)
334{
335 Assert(pUrb);
336 Assert(pUrb->pVUsb->pvBuffered);
337 PVUSBBUFFEREDPIPEINT pThis = (PVUSBBUFFEREDPIPEINT)pUrb->pVUsb->pvBuffered;
338
339 vusbBufferedPipeLock(pThis);
340
341#if defined(LOG_ENABLED) || defined(RT_STRICT)
342 unsigned cbXfer = 0;
343 for (unsigned i = 0; i < pUrb->cIsocPkts; i++)
344 {
345 LogFlowFunc(("packet %u: cb=%u enmStatus=%u\n", i, pUrb->aIsocPkts[i].cb, pUrb->aIsocPkts[i].enmStatus));
346 cbXfer += pUrb->aIsocPkts[i].cb;
347 }
348 Assert(cbXfer == pUrb->cbData);
349#endif
350 pUrb->pVUsb->pfnFree(pUrb);
351 pThis->cUrbsInFlight--;
352
353 /* Stream more data if available.*/
354 if (pThis->enmState == VUSBBUFFEREDPIPESTATE_STREAMING)
355 vusbBufferedPipeStream(pThis);
356 RTCritSectLeave(&pThis->CritSectBuffer);
357}
358
359
360/**
361 * Submit and process the given URB, for outgoing endpoints we will buffer the content
362 * until we reached a threshold and start sending the data to the device.
363 * For incoming endpoints prefetched data is used to complete the URB immediately.
364 *
365 * @returns VBox status code.
366 * @param hBuffer The buffered pipe instance.
367 * @param pUrb The URB submitted by HC
368 */
369DECLHIDDEN(int) vusbBufferedPipeSubmitUrb(VUSBBUFFEREDPIPE hBuffer, PVUSBURB pUrb)
370{
371 int rc = VINF_SUCCESS;
372 PVUSBBUFFEREDPIPEINT pThis = hBuffer;
373
374 AssertReturn(pThis->enmDirection == pUrb->enmDir, VERR_INTERNAL_ERROR);
375 AssertReturn(pUrb->enmType == VUSBXFERTYPE_ISOC, VERR_INTERNAL_ERROR);
376
377 vusbBufferedPipeLock(pThis);
378
379 if (pThis->enmDirection == VUSBDIRECTION_OUT)
380 {
381 void *pv = NULL;
382 size_t cb = 0;
383
384 /* Copy the data of the URB into our internal ring buffer. */
385 RTCircBufAcquireWriteBlock(pThis->pRingBufData, pUrb->cbData, &pv, &cb);
386 memcpy(pv, &pUrb->abData[0], cb);
387 RTCircBufReleaseWriteBlock(pThis->pRingBufData, cb);
388 /* Take possible wraparound in the ring buffer into account. */
389 if (cb < pUrb->cbData)
390 {
391 size_t cb2 = 0;
392 RTCircBufAcquireWriteBlock(pThis->pRingBufData, pUrb->cbData - cb, &pv, &cb2);
393 memcpy(pv, &pUrb->abData[cb], cb2);
394 RTCircBufReleaseWriteBlock(pThis->pRingBufData, cb2);
395 Assert(pUrb->cbData == cb + cb2);
396 }
397
398 /*
399 * Copy the isoc packet descriptors over stuffing as much as possible into one.
400 * We recombine URBs into one if possible maximizing the number of frames
401 * one URB covers when we send it to the device.
402 */
403 unsigned idxIsocPkt = 0;
404 for (unsigned iTry = 0; iTry < 2; iTry++)
405 {
406 PVUSBISOCDESC pIsocDesc = &pThis->paIsocDesc[pThis->idxIsocDescWrite];
407 for (unsigned i = idxIsocPkt; i < pUrb->cIsocPkts && pIsocDesc->cIsocPkts < RT_ELEMENTS(pIsocDesc->aIsocPkts); i++)
408 {
409 pIsocDesc->aIsocPkts[pIsocDesc->cIsocPkts].enmStatus = VUSBSTATUS_NOT_ACCESSED;
410 pIsocDesc->aIsocPkts[pIsocDesc->cIsocPkts].off = (uint16_t)pIsocDesc->cbTotal;
411 Assert(pIsocDesc->aIsocPkts[pIsocDesc->cIsocPkts].off == pIsocDesc->cbTotal);
412 pIsocDesc->aIsocPkts[pIsocDesc->cIsocPkts].cb = pUrb->aIsocPkts[i].cb;
413 pIsocDesc->cbTotal += pUrb->aIsocPkts[i].cb;
414 pIsocDesc->cIsocPkts++;
415 idxIsocPkt++;
416 pUrb->aIsocPkts[i].enmStatus = VUSBSTATUS_OK;
417 }
418
419 if (pIsocDesc->cIsocPkts == RT_ELEMENTS(pIsocDesc->aIsocPkts))
420 {
421 /* Advance to the next isoc descriptor. */
422 pThis->idxIsocDescWrite = (pThis->idxIsocDescWrite + 1) % pThis->cIsocDesc;
423 pThis->paIsocDesc[pThis->idxIsocDescWrite].cbTotal = 0;
424 pThis->paIsocDesc[pThis->idxIsocDescWrite].cIsocPkts = 0;
425 /* On the first wraparound start streaming because our buffer is full. */
426 if ( pThis->enmState == VUSBBUFFEREDPIPESTATE_FILLING
427 && pThis->idxIsocDescWrite == 0)
428 pThis->enmState = VUSBBUFFEREDPIPESTATE_STREAMING;
429 }
430 }
431
432 if (pThis->enmState == VUSBBUFFEREDPIPESTATE_STREAMING)
433 {
434 /* Stream anything we have. */
435 rc = vusbBufferedPipeStream(pThis);
436 }
437
438 /* Complete the URB submitted by the HC. */
439 pUrb->enmState = VUSBURBSTATE_REAPED;
440 pUrb->enmStatus = VUSBSTATUS_OK;
441 vusbUrbCompletionRh(pUrb);
442 }
443 else
444 {
445 AssertMsgFailed(("TODO"));
446 }
447 RTCritSectLeave(&pThis->CritSectBuffer);
448 return rc;
449}
450
451
452DECLHIDDEN(int) vusbBufferedPipeCreate(PVUSBDEV pDev, PVUSBPIPE pPipe, VUSBDIRECTION enmDirection,
453 VUSBSPEED enmSpeed, uint32_t cLatencyMs,
454 PVUSBBUFFEREDPIPE phBuffer)
455{
456 int rc;
457 PVUSBBUFFEREDPIPEINT pThis = (PVUSBBUFFEREDPIPEINT)RTMemAllocZ(sizeof(VUSBBUFFEREDPIPEINT));
458
459 AssertReturn(enmDirection == VUSBDIRECTION_IN || enmDirection == VUSBDIRECTION_OUT,
460 VERR_INVALID_PARAMETER);
461 AssertPtrReturn(pDev, VERR_INVALID_POINTER);
462 AssertPtrReturn(pPipe, VERR_INVALID_POINTER);
463 AssertPtrReturn(phBuffer, VERR_INVALID_POINTER);
464
465 if (!cLatencyMs)
466 {
467 *phBuffer = NULL;
468 return VINF_SUCCESS;
469 }
470
471 if (pThis)
472 {
473 PCVUSBDESCENDPOINT pDesc;
474
475 pThis->pDev = pDev;
476 pThis->pPipe = pPipe;
477 pThis->enmSpeed = enmSpeed;
478 pThis->cLatencyMs = cLatencyMs;
479 pThis->enmDirection = enmDirection;
480 pThis->enmState = VUSBBUFFEREDPIPESTATE_CREATING;
481 pThis->cUrbsInFlight = 0;
482 pThis->idxIsocDescRead = 0;
483 pThis->idxIsocDescWrite = 0;
484#ifdef DEBUG
485 pThis->cLockContention = 0;
486#endif
487#ifdef LOG_ENABLED
488 pThis->iSerial = 0;
489#endif
490
491 if (enmDirection == VUSBDIRECTION_IN)
492 pDesc = &pPipe->in->Core;
493 else
494 pDesc = &pPipe->out->Core;
495 Assert(pDesc);
496
497 rc = vusbBufferedPipeSetParamsFromDescriptor(pThis, pDesc);
498 if (RT_SUCCESS(rc))
499 {
500 pThis->paIsocDesc = (PVUSBISOCDESC)RTMemAllocZ(pThis->cIsocDesc * sizeof(VUSBISOCDESC));
501 if (RT_LIKELY(pThis->paIsocDesc))
502 {
503 rc = vusbUrbPoolInit(&pThis->UrbPool);
504 if (RT_SUCCESS(rc))
505 {
506 rc = RTCritSectInit(&pThis->CritSectBuffer);
507 if (RT_SUCCESS(rc))
508 {
509 /*
510 * Create a ring buffer which can hold twice the amount of data
511 * for the required latency so we can fill the buffer with new data
512 * while the old one is still being used
513 */
514 rc = RTCircBufCreate(&pThis->pRingBufData, 2 * pThis->cbRingBufData);
515 if (RT_SUCCESS(rc))
516 {
517 /*
518 * For an input pipe start filling the buffer for an output endpoint
519 * we have to wait until the buffer is filled by the guest before
520 * starting to stream it to the device.
521 */
522 if (enmDirection == VUSBDIRECTION_IN)
523 {
524 /** @todo */
525 }
526 pThis->enmState = VUSBBUFFEREDPIPESTATE_FILLING;
527 *phBuffer = pThis;
528 return VINF_SUCCESS;
529 }
530
531 RTCritSectDelete(&pThis->CritSectBuffer);
532 }
533
534 RTMemFree(pThis->paIsocDesc);
535 }
536 }
537 else
538 rc = VERR_NO_MEMORY;
539 }
540
541 RTMemFree(pThis);
542 }
543 else
544 rc = VERR_NO_MEMORY;
545
546 return rc;
547}
548
549
550DECLHIDDEN(void) vusbBufferedPipeDestroy(VUSBBUFFEREDPIPE hBuffer)
551{
552 PVUSBBUFFEREDPIPEINT pThis = hBuffer;
553
554 pThis->enmState = VUSBBUFFEREDPIPESTATE_DESTROYING;
555
556 /* Cancel all outstanding URBs. */
557 vusbDevCancelAllUrbs(pThis->pDev, false /* fDetaching */);
558
559 vusbBufferedPipeLock(pThis);
560 pThis->cUrbsInFlight = 0;
561
562 /* Stream the last data. */
563 vusbBufferedPipeStream(pThis);
564
565 /* Wait for any in flight URBs to complete. */
566 while (pThis->cUrbsInFlight)
567 {
568 RTCritSectLeave(&pThis->CritSectBuffer);
569 RTThreadSleep(1);
570 RTCritSectEnter(&pThis->CritSectBuffer);
571 }
572
573 RTCircBufDestroy(pThis->pRingBufData);
574 vusbUrbPoolDestroy(&pThis->UrbPool);
575 RTCritSectLeave(&pThis->CritSectBuffer);
576#ifdef DEBUG
577 Log(("VUSB: Destroyed buffered pipe with lock contention counter %u\n", pThis->cLockContention));
578#endif
579 RTMemFree(pThis->paIsocDesc);
580 RTMemFree(pThis);
581}
582
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