VirtualBox

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

Last change on this file since 60066 was 59776, checked in by vboxsync, 9 years ago

VUSB: build fix

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 19.6 KB
Line 
1/* $Id: VUSBBufferedPipe.cpp 59776 2016-02-22 14:06:02Z 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 int rc = VINF_SUCCESS;
287 unsigned cbPktMax, uInterval, uMult;
288
289 if (pThis->enmSpeed == VUSB_SPEED_HIGH)
290 {
291 /* High-speed endpoint */
292 Assert((pDesc->wMaxPacketSize & 0x1fff) == pDesc->wMaxPacketSize);
293 Assert(pDesc->bInterval <= 16);
294 uInterval = pDesc->bInterval ? 1 << (pDesc->bInterval - 1) : 1;
295 cbPktMax = pDesc->wMaxPacketSize & 0x7ff;
296 uMult = ((pDesc->wMaxPacketSize & 0x1800) >> 11) + 1;
297 }
298 else if (pThis->enmSpeed == VUSB_SPEED_FULL || pThis->enmSpeed == VUSB_SPEED_LOW)
299 {
300 /* Full- or low-speed endpoint */
301 Assert((pDesc->wMaxPacketSize & 0x7ff) == pDesc->wMaxPacketSize);
302 uInterval = pDesc->bInterval;
303 cbPktMax = pDesc->wMaxPacketSize;
304 uMult = 1;
305 }
306 else
307 {
308 /** @todo: Implement for super speed and up if it turns out to be required, at the moment it looks
309 * like we don't need it. */
310 rc = VERR_NOT_SUPPORTED;
311 }
312
313 if (RT_SUCCESS(rc))
314 {
315 pThis->uInterval = uInterval;
316 pThis->cbPktSize = cbPktMax * uMult;
317 pThis->uEndPt = pDesc->bEndpointAddress & 0xf;
318
319 unsigned cPackets = pThis->cLatencyMs / pThis->uInterval;
320 cPackets = RT_MAX(cPackets, 1); /* At least one packet. */
321 pThis->cbRingBufData = pThis->cbPktSize * cPackets;
322 pThis->cIsocDesc = cPackets / 8 + ((cPackets % 8) ? 1 : 0);
323 }
324
325 return rc;
326}
327
328
329/**
330 * Completes an URB issued by the pipe buffer.
331 *
332 * @returns nothing.
333 * @param pUrb The completed URB.
334 */
335DECLHIDDEN(void) vusbBufferedPipeCompleteUrb(PVUSBURB pUrb)
336{
337 Assert(pUrb);
338 Assert(pUrb->pVUsb->pvBuffered);
339 PVUSBBUFFEREDPIPEINT pThis = (PVUSBBUFFEREDPIPEINT)pUrb->pVUsb->pvBuffered;
340
341 vusbBufferedPipeLock(pThis);
342
343#if defined(LOG_ENABLED) || defined(RT_STRICT)
344 unsigned cbXfer = 0;
345 for (unsigned i = 0; i < pUrb->cIsocPkts; i++)
346 {
347 LogFlowFunc(("packet %u: cb=%u enmStatus=%u\n", i, pUrb->aIsocPkts[i].cb, pUrb->aIsocPkts[i].enmStatus));
348 cbXfer += pUrb->aIsocPkts[i].cb;
349 }
350 Assert(cbXfer == pUrb->cbData);
351#endif
352 pUrb->pVUsb->pfnFree(pUrb);
353 pThis->cUrbsInFlight--;
354
355 /* Stream more data if available.*/
356 if (pThis->enmState == VUSBBUFFEREDPIPESTATE_STREAMING)
357 vusbBufferedPipeStream(pThis);
358 RTCritSectLeave(&pThis->CritSectBuffer);
359}
360
361
362/**
363 * Submit and process the given URB, for outgoing endpoints we will buffer the content
364 * until we reached a threshold and start sending the data to the device.
365 * For incoming endpoints prefetched data is used to complete the URB immediately.
366 *
367 * @returns VBox status code.
368 * @param hBuffer The buffered pipe instance.
369 * @param pUrb The URB submitted by HC
370 */
371DECLHIDDEN(int) vusbBufferedPipeSubmitUrb(VUSBBUFFEREDPIPE hBuffer, PVUSBURB pUrb)
372{
373 int rc = VINF_SUCCESS;
374 PVUSBBUFFEREDPIPEINT pThis = hBuffer;
375
376 AssertReturn(pThis->enmDirection == pUrb->enmDir, VERR_INTERNAL_ERROR);
377 AssertReturn(pUrb->enmType == VUSBXFERTYPE_ISOC, VERR_INTERNAL_ERROR);
378
379 vusbBufferedPipeLock(pThis);
380
381 if (pThis->enmDirection == VUSBDIRECTION_OUT)
382 {
383 void *pv = NULL;
384 size_t cb = 0;
385
386 /* Copy the data of the URB into our internal ring buffer. */
387 RTCircBufAcquireWriteBlock(pThis->pRingBufData, pUrb->cbData, &pv, &cb);
388 memcpy(pv, &pUrb->abData[0], cb);
389 RTCircBufReleaseWriteBlock(pThis->pRingBufData, cb);
390 /* Take possible wraparound in the ring buffer into account. */
391 if (cb < pUrb->cbData)
392 {
393 size_t cb2 = 0;
394 RTCircBufAcquireWriteBlock(pThis->pRingBufData, pUrb->cbData - cb, &pv, &cb2);
395 memcpy(pv, &pUrb->abData[cb], cb2);
396 RTCircBufReleaseWriteBlock(pThis->pRingBufData, cb2);
397 Assert(pUrb->cbData == cb + cb2);
398 }
399
400 /*
401 * Copy the isoc packet descriptors over stuffing as much as possible into one.
402 * We recombine URBs into one if possible maximizing the number of frames
403 * one URB covers when we send it to the device.
404 */
405 unsigned idxIsocPkt = 0;
406 for (unsigned iTry = 0; iTry < 2; iTry++)
407 {
408 PVUSBISOCDESC pIsocDesc = &pThis->paIsocDesc[pThis->idxIsocDescWrite];
409 for (unsigned i = idxIsocPkt; i < pUrb->cIsocPkts && pIsocDesc->cIsocPkts < RT_ELEMENTS(pIsocDesc->aIsocPkts); i++)
410 {
411 pIsocDesc->aIsocPkts[pIsocDesc->cIsocPkts].enmStatus = VUSBSTATUS_NOT_ACCESSED;
412 pIsocDesc->aIsocPkts[pIsocDesc->cIsocPkts].off = pIsocDesc->cbTotal;
413 pIsocDesc->aIsocPkts[pIsocDesc->cIsocPkts].cb = pUrb->aIsocPkts[i].cb;
414 pIsocDesc->cbTotal += pUrb->aIsocPkts[i].cb;
415 pIsocDesc->cIsocPkts++;
416 idxIsocPkt++;
417 pUrb->aIsocPkts[i].enmStatus = VUSBSTATUS_OK;
418 }
419
420 if (pIsocDesc->cIsocPkts == RT_ELEMENTS(pIsocDesc->aIsocPkts))
421 {
422 /* Advance to the next isoc descriptor. */
423 pThis->idxIsocDescWrite = (pThis->idxIsocDescWrite + 1) % pThis->cIsocDesc;
424 pThis->paIsocDesc[pThis->idxIsocDescWrite].cbTotal = 0;
425 pThis->paIsocDesc[pThis->idxIsocDescWrite].cIsocPkts = 0;
426 /* On the first wraparound start streaming because our buffer is full. */
427 if ( pThis->enmState == VUSBBUFFEREDPIPESTATE_FILLING
428 && pThis->idxIsocDescWrite == 0)
429 pThis->enmState = VUSBBUFFEREDPIPESTATE_STREAMING;
430 }
431 }
432
433 if (pThis->enmState == VUSBBUFFEREDPIPESTATE_STREAMING)
434 {
435 /* Stream anything we have. */
436 rc = vusbBufferedPipeStream(pThis);
437 }
438
439 /* Complete the URB submitted by the HC. */
440 pUrb->enmState = VUSBURBSTATE_REAPED;
441 pUrb->enmStatus = VUSBSTATUS_OK;
442 vusbUrbCompletionRh(pUrb);
443 }
444 else
445 {
446 AssertMsgFailed(("TODO"));
447 }
448 RTCritSectLeave(&pThis->CritSectBuffer);
449 return rc;
450}
451
452
453DECLHIDDEN(int) vusbBufferedPipeCreate(PVUSBDEV pDev, PVUSBPIPE pPipe, VUSBDIRECTION enmDirection,
454 VUSBSPEED enmSpeed, uint32_t cLatencyMs,
455 PVUSBBUFFEREDPIPE phBuffer)
456{
457 int rc;
458 PVUSBBUFFEREDPIPEINT pThis = (PVUSBBUFFEREDPIPEINT)RTMemAllocZ(sizeof(VUSBBUFFEREDPIPEINT));
459
460 AssertReturn(enmDirection == VUSBDIRECTION_IN || enmDirection == VUSBDIRECTION_OUT,
461 VERR_INVALID_PARAMETER);
462 AssertPtrReturn(pDev, VERR_INVALID_POINTER);
463 AssertPtrReturn(pPipe, VERR_INVALID_POINTER);
464 AssertPtrReturn(phBuffer, VERR_INVALID_POINTER);
465
466 if (!cLatencyMs)
467 {
468 *phBuffer = NULL;
469 return VINF_SUCCESS;
470 }
471
472 if (pThis)
473 {
474 PCVUSBDESCENDPOINT pDesc;
475
476 pThis->pDev = pDev;
477 pThis->pPipe = pPipe;
478 pThis->enmSpeed = enmSpeed;
479 pThis->cLatencyMs = cLatencyMs;
480 pThis->enmDirection = enmDirection;
481 pThis->enmState = VUSBBUFFEREDPIPESTATE_CREATING;
482 pThis->cUrbsInFlight = 0;
483 pThis->idxIsocDescRead = 0;
484 pThis->idxIsocDescWrite = 0;
485#ifdef DEBUG
486 pThis->cLockContention = 0;
487#endif
488#ifdef LOG_ENABLED
489 pThis->iSerial = 0;
490#endif
491
492 if (enmDirection == VUSBDIRECTION_IN)
493 pDesc = &pPipe->in->Core;
494 else
495 pDesc = &pPipe->out->Core;
496 Assert(pDesc);
497
498 rc = vusbBufferedPipeSetParamsFromDescriptor(pThis, pDesc);
499 if (RT_SUCCESS(rc))
500 {
501 pThis->paIsocDesc = (PVUSBISOCDESC)RTMemAllocZ(pThis->cIsocDesc * sizeof(VUSBISOCDESC));
502 if (RT_LIKELY(pThis->paIsocDesc))
503 {
504 rc = vusbUrbPoolInit(&pThis->UrbPool);
505 if (RT_SUCCESS(rc))
506 {
507 rc = RTCritSectInit(&pThis->CritSectBuffer);
508 if (RT_SUCCESS(rc))
509 {
510 /*
511 * Create a ring buffer which can hold twice the amount of data
512 * for the required latency so we can fill the buffer with new data
513 * while the old one is still being used
514 */
515 rc = RTCircBufCreate(&pThis->pRingBufData, 2 * pThis->cbRingBufData);
516 if (RT_SUCCESS(rc))
517 {
518 /*
519 * For an input pipe start filling the buffer for an output endpoint
520 * we have to wait until the buffer is filled by the guest before
521 * starting to stream it to the device.
522 */
523 if (enmDirection == VUSBDIRECTION_IN)
524 {
525 /** @todo */
526 }
527 pThis->enmState = VUSBBUFFEREDPIPESTATE_FILLING;
528 *phBuffer = pThis;
529 return VINF_SUCCESS;
530 }
531
532 RTCritSectDelete(&pThis->CritSectBuffer);
533 }
534
535 RTMemFree(pThis->paIsocDesc);
536 }
537 }
538 else
539 rc = VERR_NO_MEMORY;
540 }
541
542 RTMemFree(pThis);
543 }
544 else
545 rc = VERR_NO_MEMORY;
546
547 return rc;
548}
549
550
551DECLHIDDEN(void) vusbBufferedPipeDestroy(VUSBBUFFEREDPIPE hBuffer)
552{
553 PVUSBBUFFEREDPIPEINT pThis = hBuffer;
554
555 pThis->enmState = VUSBBUFFEREDPIPESTATE_DESTROYING;
556
557 /* Cancel all outstanding URBs. */
558 vusbDevCancelAllUrbs(pThis->pDev, false /* fDetaching */);
559
560 vusbBufferedPipeLock(pThis);
561 pThis->cUrbsInFlight = 0;
562
563 /* Stream the last data. */
564 vusbBufferedPipeStream(pThis);
565
566 /* Wait for any in flight URBs to complete. */
567 while (pThis->cUrbsInFlight)
568 {
569 RTCritSectLeave(&pThis->CritSectBuffer);
570 RTThreadSleep(1);
571 RTCritSectEnter(&pThis->CritSectBuffer);
572 }
573
574 RTCircBufDestroy(pThis->pRingBufData);
575 vusbUrbPoolDestroy(&pThis->UrbPool);
576 RTCritSectLeave(&pThis->CritSectBuffer);
577#ifdef DEBUG
578 Log(("VUSB: Destroyed buffered pipe with lock contention counter %u\n", pThis->cLockContention));
579#endif
580 RTMemFree(pThis->paIsocDesc);
581 RTMemFree(pThis);
582}
583
584
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