VirtualBox

source: vbox/trunk/src/VBox/Devices/Audio/DrvHostAudioValidationKit.cpp@ 89935

最後變更 在這個檔案從89935是 89890,由 vboxsync 提交於 4 年 前

Audio/ValKit: More code for audio verification. bugref:10008

  • 屬性 svn:eol-style 設為 native
  • 屬性 svn:keywords 設為 Author Date Id Revision
檔案大小: 37.6 KB
 
1/* $Id: DrvHostAudioValidationKit.cpp 89890 2021-06-24 15:56:05Z vboxsync $ */
2/** @file
3 * Host audio driver - ValidationKit - For dumping and injecting audio data from/to the device emulation.
4 */
5
6/*
7 * Copyright (C) 2016-2021 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.alldomusa.eu.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* Defined Constants And Macros *
21*********************************************************************************************************************************/
22#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO
23#include <iprt/dir.h>
24#include <iprt/env.h>
25#include <iprt/mem.h>
26#include <iprt/path.h>
27#include <iprt/semaphore.h>
28#include <iprt/stream.h>
29#include <iprt/uuid.h> /* For PDMIBASE_2_PDMDRV. */
30
31#include <VBox/log.h>
32#include <VBox/vmm/pdmaudioifs.h>
33#include <VBox/vmm/pdmaudioinline.h>
34
35#include "VBoxDD.h"
36#include "AudioHlp.h"
37#include "AudioTest.h"
38#include "AudioTestService.h"
39
40
41/*********************************************************************************************************************************
42* Structures and Typedefs *
43*********************************************************************************************************************************/
44/**
45 * Structure for keeping a Validation Kit input/output stream.
46 */
47typedef struct VALKITAUDIOSTREAM
48{
49 /** Common part. */
50 PDMAUDIOBACKENDSTREAM Core;
51 /** The stream's acquired configuration. */
52 PDMAUDIOSTREAMCFG Cfg;
53} VALKITAUDIOSTREAM;
54/** Pointer to a Validation Kit stream. */
55typedef VALKITAUDIOSTREAM *PVALKITAUDIOSTREAM;
56
57/**
58 * Test tone-specific instance data.
59 */
60typedef struct VALKITTESTTONEDATA
61{
62 union
63 {
64 struct
65 {
66 /** How many bytes to write. */
67 uint64_t cbToWrite;
68 /** How many bytes already written. */
69 uint64_t cbWritten;
70 } Rec;
71 struct
72 {
73 /** How many bytes to read. */
74 uint64_t cbToRead;
75 /** How many bytes already read. */
76 uint64_t cbRead;
77 } Play;
78 } u;
79 /** The test tone instance to use. */
80 AUDIOTESTTONE Tone;
81 /** The test tone parameters to use. */
82 AUDIOTESTTONEPARMS Parms;
83} VALKITTESTTONEDATA;
84
85/**
86 * Structure keeping a single Validation Kit test.
87 */
88typedef struct VALKITTESTDATA
89{
90 /** The list node. */
91 RTLISTNODE Node;
92 /** Index in test sequence (0-based). */
93 uint32_t idxTest;
94 /** Current test set entry to process. */
95 PAUDIOTESTENTRY pEntry;
96 /** Current test object to process. */
97 PAUDIOTESTOBJ pObj;
98 /** Stream configuration to use for this test. */
99 PDMAUDIOSTREAMCFG StreamCfg;
100 union
101 {
102 /** Test tone-specific data. */
103 VALKITTESTTONEDATA TestTone;
104 } t;
105 /** Time stamp (real, in ms) when test started. */
106 uint64_t msStartedTS;
107} VALKITTESTDATA;
108/** Pointer to Validation Kit test data. */
109typedef VALKITTESTDATA *PVALKITTESTDATA;
110
111/**
112 * Validation Kit audio driver instance data.
113 * @implements PDMIAUDIOCONNECTOR
114 */
115typedef struct DRVHOSTVALKITAUDIO
116{
117 /** Pointer to the driver instance structure. */
118 PPDMDRVINS pDrvIns;
119 /** Pointer to host audio interface. */
120 PDMIHOSTAUDIO IHostAudio;
121 /** Temporary path to use. */
122 char szPathTemp[RTPATH_MAX];
123 /** Output path to use. */
124 char szPathOut[RTPATH_MAX];
125 /** Current test set being handled. */
126 AUDIOTESTSET Set;
127 /** Number of total tests created. */
128 uint32_t cTestsTotal;
129 /** Number of tests in \a lstTestsRec. */
130 uint32_t cTestsRec;
131 /** List keeping the recording tests (FIFO). */
132 RTLISTANCHOR lstTestsRec;
133 /** Number of tests in \a lstTestsPlay. */
134 uint32_t cTestsPlay;
135 /** List keeping the recording tests (FIFO). */
136 RTLISTANCHOR lstTestsPlay;
137 /** Pointer to current test being processed. */
138 PVALKITTESTDATA pTestCur;
139 /** Critical section for serializing access across threads. */
140 RTCRITSECT CritSect;
141 bool fTestSetEnded;
142 RTSEMEVENT EventSemEnded;
143 /** The Audio Test Service (ATS) instance. */
144 ATSSERVER Srv;
145 /** Absolute path to the packed up test set archive.
146 * Keep it simple for now and only support one (open) archive at a time. */
147 char szTestSetArchive[RTPATH_MAX];
148 /** File handle to the (opened) test set archive for reading. */
149 RTFILE hTestSetArchive;
150
151} DRVHOSTVALKITAUDIO;
152/** Pointer to a Validation Kit host audio driver instance. */
153typedef DRVHOSTVALKITAUDIO *PDRVHOSTVALKITAUDIO;
154
155
156/*********************************************************************************************************************************
157* Internal test handling code *
158*********************************************************************************************************************************/
159
160/**
161 * Unregisters a ValKit test, common code.
162 *
163 * @param pTst Test to unregister.
164 * The pointer will be invalid afterwards.
165 */
166static void drvHostValKiUnregisterTest(PVALKITTESTDATA pTst)
167{
168 AssertPtrReturnVoid(pTst);
169
170 RTListNodeRemove(&pTst->Node);
171
172 AudioTestSetObjClose(pTst->pObj);
173 pTst->pObj = NULL;
174
175 if (pTst->pEntry) /* Set set entry assign? Mark as done. */
176 {
177 AssertPtrReturnVoid(pTst->pEntry);
178 pTst->pEntry = NULL;
179 }
180
181 RTMemFree(pTst);
182 pTst = NULL;
183}
184
185/**
186 * Unregisters a ValKit recording test.
187 *
188 * @param pThis ValKit audio driver instance.
189 * @param pTst Test to unregister.
190 * The pointer will be invalid afterwards.
191 */
192static void drvHostValKiUnregisterRecTest(PDRVHOSTVALKITAUDIO pThis, PVALKITTESTDATA pTst)
193{
194 drvHostValKiUnregisterTest(pTst);
195
196 Assert(pThis->cTestsRec);
197 pThis->cTestsRec--;
198}
199
200/**
201 * Unregisters a ValKit playback test.
202 *
203 * @param pThis ValKit audio driver instance.
204 * @param pTst Test to unregister.
205 * The pointer will be invalid afterwards.
206 */
207static void drvHostValKiUnregisterPlayTest(PDRVHOSTVALKITAUDIO pThis, PVALKITTESTDATA pTst)
208{
209 drvHostValKiUnregisterTest(pTst);
210
211 Assert(pThis->cTestsPlay);
212 pThis->cTestsPlay--;
213}
214
215/**
216 * Performs some internal cleanup / housekeeping of all registered tests.
217 *
218 * @param pThis ValKit audio driver instance.
219 */
220static void drvHostValKitCleanup(PDRVHOSTVALKITAUDIO pThis)
221{
222 LogRel(("ValKit: Cleaning up ...\n"));
223
224 if (pThis->cTestsRec)
225 LogRel(("ValKit: Warning: %RU32 guest recording tests still outstanding:\n", pThis->cTestsRec));
226
227 PVALKITTESTDATA pTst, pTstNext;
228 RTListForEachSafe(&pThis->lstTestsRec, pTst, pTstNext, VALKITTESTDATA, Node)
229 {
230 size_t const cbOutstanding = pTst->t.TestTone.u.Rec.cbToWrite - pTst->t.TestTone.u.Rec.cbWritten;
231 if (cbOutstanding)
232 LogRel(("ValKit: \tRecording test #%RU32 has %RU64 bytes (%RU32ms) outstanding\n",
233 pTst->idxTest, cbOutstanding, PDMAudioPropsBytesToMilli(&pTst->t.TestTone.Parms.Props, (uint32_t)cbOutstanding)));
234 drvHostValKiUnregisterRecTest(pThis, pTst);
235 }
236
237 if (pThis->cTestsPlay)
238 LogRel(("ValKit: Warning: %RU32 guest playback tests still outstanding:\n", pThis->cTestsPlay));
239
240 RTListForEachSafe(&pThis->lstTestsPlay, pTst, pTstNext, VALKITTESTDATA, Node)
241 {
242 size_t const cbOutstanding = pTst->t.TestTone.u.Play.cbToRead - pTst->t.TestTone.u.Play.cbRead;
243 if (cbOutstanding)
244 LogRel(("ValKit: \tPlayback test #%RU32 has %RU64 bytes (%RU32ms) outstanding\n",
245 pTst->idxTest, cbOutstanding, PDMAudioPropsBytesToMilli(&pTst->t.TestTone.Parms.Props, (uint32_t)cbOutstanding)));
246 drvHostValKiUnregisterPlayTest(pThis, pTst);
247 }
248
249 Assert(pThis->cTestsRec == 0);
250 Assert(pThis->cTestsPlay == 0);
251}
252
253
254/*********************************************************************************************************************************
255* ATS callback implementations *
256*********************************************************************************************************************************/
257
258/** @copydoc ATSCALLBACKS::pfnTestSetBegin */
259static DECLCALLBACK(int) drvHostValKitTestSetBegin(void const *pvUser, const char *pszTag)
260{
261 PDRVHOSTVALKITAUDIO pThis = (PDRVHOSTVALKITAUDIO)pvUser;
262
263 int rc = RTCritSectEnter(&pThis->CritSect);
264 if (RT_SUCCESS(rc))
265 {
266
267 LogRel(("ValKit: Beginning test set '%s'\n", pszTag));
268 rc = AudioTestSetCreate(&pThis->Set, pThis->szPathTemp, pszTag);
269
270 int rc2 = RTCritSectLeave(&pThis->CritSect);
271 if (RT_SUCCESS(rc))
272 rc = rc2;
273 }
274
275 if (RT_FAILURE(rc))
276 LogRel(("ValKit: Beginning test set failed with %Rrc\n", rc));
277
278 return rc;
279}
280
281/** @copydoc ATSCALLBACKS::pfnTestSetEnd */
282static DECLCALLBACK(int) drvHostValKitTestSetEnd(void const *pvUser, const char *pszTag)
283{
284 PDRVHOSTVALKITAUDIO pThis = (PDRVHOSTVALKITAUDIO)pvUser;
285
286 int rc = RTCritSectEnter(&pThis->CritSect);
287 if (RT_SUCCESS(rc))
288 {
289 const PAUDIOTESTSET pSet = &pThis->Set;
290
291 if (AudioTestSetIsRunning(pSet))
292 {
293 pThis->fTestSetEnded = true;
294
295 rc = RTCritSectLeave(&pThis->CritSect);
296 if (RT_SUCCESS(rc))
297 {
298 LogRel(("ValKit: Waiting for runnig test set '%s' to end ...\n", pszTag));
299 rc = RTSemEventWait(pThis->EventSemEnded, RT_MS_30SEC);
300
301 int rc2 = RTCritSectEnter(&pThis->CritSect);
302 if (RT_SUCCESS(rc))
303 rc = rc2;
304 }
305 }
306
307 if (RT_SUCCESS(rc))
308 {
309 LogRel(("ValKit: Ending test set '%s'\n", pszTag));
310
311 /* Close the test set first. */
312 rc = AudioTestSetClose(pSet);
313 if (RT_SUCCESS(rc))
314 {
315 /* Before destroying the test environment, pack up the test set so
316 * that it's ready for transmission. */
317 rc = AudioTestSetPack(pSet, pThis->szPathOut, pThis->szTestSetArchive, sizeof(pThis->szTestSetArchive));
318 if (RT_SUCCESS(rc))
319 LogRel(("ValKit: Packed up to '%s'\n", pThis->szTestSetArchive));
320
321 /* Do some internal housekeeping. */
322 drvHostValKitCleanup(pThis);
323
324#ifndef DEBUG_andy
325 int rc2 = AudioTestSetWipe(pSet);
326 if (RT_SUCCESS(rc))
327 rc = rc2;
328#endif
329 }
330
331 AudioTestSetDestroy(pSet);
332 }
333
334 int rc2 = RTCritSectLeave(&pThis->CritSect);
335 if (RT_SUCCESS(rc))
336 rc = rc2;
337 }
338
339 if (RT_FAILURE(rc))
340 LogRel(("ValKit: Ending test set failed with %Rrc\n", rc));
341
342 return rc;
343}
344
345/** @copydoc ATSCALLBACKS::pfnTonePlay
346 *
347 * Creates and registers a new test tone guest recording test.
348 * This backend will play (inject) input data to the guest.
349 */
350static DECLCALLBACK(int) drvHostValKitRegisterGuestRecTest(void const *pvUser, PAUDIOTESTTONEPARMS pToneParms)
351{
352 PDRVHOSTVALKITAUDIO pThis = (PDRVHOSTVALKITAUDIO)pvUser;
353
354 PVALKITTESTDATA pTestData = (PVALKITTESTDATA)RTMemAllocZ(sizeof(VALKITTESTDATA));
355 AssertPtrReturn(pTestData, VERR_NO_MEMORY);
356
357 memcpy(&pTestData->t.TestTone.Parms, pToneParms, sizeof(AUDIOTESTTONEPARMS));
358
359 AssertReturn(pTestData->t.TestTone.Parms.msDuration, VERR_INVALID_PARAMETER);
360 AssertReturn(PDMAudioPropsAreValid(&pTestData->t.TestTone.Parms.Props), VERR_INVALID_PARAMETER);
361
362 AudioTestToneInit(&pTestData->t.TestTone.Tone, &pTestData->t.TestTone.Parms.Props, pTestData->t.TestTone.Parms.dbFreqHz);
363
364 pTestData->t.TestTone.u.Rec.cbToWrite = PDMAudioPropsMilliToBytes(&pTestData->t.TestTone.Parms.Props,
365 pTestData->t.TestTone.Parms.msDuration);
366 int rc = RTCritSectEnter(&pThis->CritSect);
367 if (RT_SUCCESS(rc))
368 {
369 LogRel(("ValKit: Registered guest recording test #%RU32 (%RU32ms, %RU64 bytes)\n",
370 pThis->cTestsTotal, pTestData->t.TestTone.Parms.msDuration, pTestData->t.TestTone.u.Rec.cbToWrite));
371
372 RTListAppend(&pThis->lstTestsRec, &pTestData->Node);
373
374 pTestData->idxTest = pThis->cTestsTotal++;
375
376 pThis->cTestsRec++;
377
378 int rc2 = RTCritSectLeave(&pThis->CritSect);
379 AssertRC(rc2);
380 }
381
382 return VINF_SUCCESS;
383}
384
385/** @copydoc ATSCALLBACKS::pfnToneRecord
386 *
387 * Creates and registers a new test tone guest playback test.
388 * This backend will record the guest output data.
389 */
390static DECLCALLBACK(int) drvHostValKitRegisterGuestPlayTest(void const *pvUser, PAUDIOTESTTONEPARMS pToneParms)
391{
392 PDRVHOSTVALKITAUDIO pThis = (PDRVHOSTVALKITAUDIO)pvUser;
393
394 PVALKITTESTDATA pTestData = (PVALKITTESTDATA)RTMemAllocZ(sizeof(VALKITTESTDATA));
395 AssertPtrReturn(pTestData, VERR_NO_MEMORY);
396
397 memcpy(&pTestData->t.TestTone.Parms, pToneParms, sizeof(AUDIOTESTTONEPARMS));
398
399 AssertReturn(pTestData->t.TestTone.Parms.msDuration, VERR_INVALID_PARAMETER);
400 AssertReturn(PDMAudioPropsAreValid(&pTestData->t.TestTone.Parms.Props), VERR_INVALID_PARAMETER);
401
402 pTestData->t.TestTone.u.Play.cbToRead = PDMAudioPropsMilliToBytes(&pTestData->t.TestTone.Parms.Props,
403 pTestData->t.TestTone.Parms.msDuration);
404 int rc = RTCritSectEnter(&pThis->CritSect);
405 if (RT_SUCCESS(rc))
406 {
407 LogRel(("ValKit: Registered guest playback test #%RU32 (%RU32ms, %RU64 bytes)\n",
408 pThis->cTestsTotal, pTestData->t.TestTone.Parms.msDuration, pTestData->t.TestTone.u.Play.cbToRead));
409
410 RTListAppend(&pThis->lstTestsPlay, &pTestData->Node);
411
412 pTestData->idxTest = pThis->cTestsTotal++;
413
414 pThis->cTestsPlay++;
415
416 int rc2 = RTCritSectLeave(&pThis->CritSect);
417 AssertRC(rc2);
418 }
419
420 return VINF_SUCCESS;
421}
422
423/** @copydoc ATSCALLBACKS::pfnTestSetSendBegin */
424static DECLCALLBACK(int) drvHostValKitTestSetSendBeginCallback(void const *pvUser, const char *pszTag)
425{
426 RT_NOREF(pszTag);
427
428 PDRVHOSTVALKITAUDIO pThis = (PDRVHOSTVALKITAUDIO)pvUser;
429
430 int rc = RTCritSectEnter(&pThis->CritSect);
431 if (RT_SUCCESS(rc))
432 {
433 if (RTFileExists(pThis->szTestSetArchive)) /* Has the archive successfully been created yet? */
434 {
435 rc = RTFileOpen(&pThis->hTestSetArchive, pThis->szTestSetArchive, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
436 if (RT_SUCCESS(rc))
437 {
438 uint64_t uSize;
439 rc = RTFileQuerySize(pThis->hTestSetArchive, &uSize);
440 if (RT_SUCCESS(rc))
441 LogRel(("ValKit: Sending test set '%s' (%zu bytes)\n", pThis->szTestSetArchive, uSize));
442 }
443 }
444 else
445 rc = VERR_FILE_NOT_FOUND;
446
447 int rc2 = RTCritSectLeave(&pThis->CritSect);
448 if (RT_SUCCESS(rc))
449 rc = rc2;
450 }
451
452 if (RT_FAILURE(rc))
453 LogRel(("ValKit: Beginning to send test set failed with %Rrc\n", rc));
454
455 return rc;
456}
457
458/** @copydoc ATSCALLBACKS::pfnTestSetSendRead */
459static DECLCALLBACK(int) drvHostValKitTestSetSendReadCallback(void const *pvUser,
460 const char *pszTag, void *pvBuf, size_t cbBuf, size_t *pcbRead)
461{
462 RT_NOREF(pszTag);
463
464 PDRVHOSTVALKITAUDIO pThis = (PDRVHOSTVALKITAUDIO)pvUser;
465
466 int rc = RTCritSectEnter(&pThis->CritSect);
467 if (RT_SUCCESS(rc))
468 {
469 if (RTFileIsValid(pThis->hTestSetArchive))
470 {
471 rc = RTFileRead(pThis->hTestSetArchive, pvBuf, cbBuf, pcbRead);
472 }
473 else
474 rc = VERR_WRONG_ORDER;
475
476 int rc2 = RTCritSectLeave(&pThis->CritSect);
477 if (RT_SUCCESS(rc))
478 rc = rc2;
479 }
480
481 if (RT_FAILURE(rc))
482 LogRel(("ValKit: Reading from test set failed with %Rrc\n", rc));
483
484 return rc;
485}
486
487/** @copydoc ATSCALLBACKS::pfnTestSetSendEnd */
488static DECLCALLBACK(int) drvHostValKitTestSetSendEndCallback(void const *pvUser, const char *pszTag)
489{
490 RT_NOREF(pszTag);
491
492 PDRVHOSTVALKITAUDIO pThis = (PDRVHOSTVALKITAUDIO)pvUser;
493
494 int rc = RTCritSectEnter(&pThis->CritSect);
495 if (RT_SUCCESS(rc))
496 {
497 if (RTFileIsValid(pThis->hTestSetArchive))
498 {
499 rc = RTFileClose(pThis->hTestSetArchive);
500 if (RT_SUCCESS(rc))
501 pThis->hTestSetArchive = NIL_RTFILE;
502 }
503
504 int rc2 = RTCritSectLeave(&pThis->CritSect);
505 if (RT_SUCCESS(rc))
506 rc = rc2;
507 }
508
509 if (RT_FAILURE(rc))
510 LogRel(("ValKit: Ending to send test set failed with %Rrc\n", rc));
511
512 return rc;
513}
514
515
516/*********************************************************************************************************************************
517* PDMIHOSTAUDIO interface implementation *
518*********************************************************************************************************************************/
519
520/**
521 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
522 */
523static DECLCALLBACK(int) drvHostValKitAudioHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
524{
525 RT_NOREF(pInterface);
526 AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
527
528 /*
529 * Fill in the config structure.
530 */
531 RTStrCopy(pBackendCfg->szName, sizeof(pBackendCfg->szName), "Validation Kit");
532 pBackendCfg->cbStream = sizeof(VALKITAUDIOSTREAM);
533 pBackendCfg->fFlags = 0;
534 pBackendCfg->cMaxStreamsOut = 1; /* Output (Playback). */
535 pBackendCfg->cMaxStreamsIn = 1; /* Input (Recording). */
536
537 return VINF_SUCCESS;
538}
539
540
541/**
542 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
543 */
544static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostValKitAudioHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
545{
546 RT_NOREF(enmDir);
547 AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN);
548
549 return PDMAUDIOBACKENDSTS_RUNNING;
550}
551
552
553/**
554 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
555 */
556static DECLCALLBACK(int) drvHostValKitAudioHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
557 PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
558{
559 PDRVHOSTVALKITAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTVALKITAUDIO, IHostAudio);
560 PVALKITAUDIOSTREAM pStreamDbg = (PVALKITAUDIOSTREAM)pStream;
561 AssertPtrReturn(pStreamDbg, VERR_INVALID_POINTER);
562 AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
563 AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
564 RT_NOREF(pThis);
565
566 int rc = VINF_SUCCESS;
567 PDMAudioStrmCfgCopy(&pStreamDbg->Cfg, pCfgAcq);
568 return rc;
569}
570
571/**
572 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
573 */
574static DECLCALLBACK(int) drvHostValKitAudioHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
575 bool fImmediate)
576{
577 RT_NOREF(pInterface, fImmediate);
578 PVALKITAUDIOSTREAM pStreamDbg = (PVALKITAUDIOSTREAM)pStream;
579 AssertPtrReturn(pStreamDbg, VERR_INVALID_POINTER);
580
581 return VINF_SUCCESS;
582}
583
584
585/**
586 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamEnable}
587 */
588static DECLCALLBACK(int) drvHostValKitAudioHA_StreamEnable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
589{
590 RT_NOREF(pInterface, pStream);
591 return VINF_SUCCESS;
592}
593
594
595/**
596 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDisable}
597 */
598static DECLCALLBACK(int) drvHostValKitAudioHA_StreamDisable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
599{
600 RT_NOREF(pInterface, pStream);
601 return VINF_SUCCESS;
602}
603
604
605/**
606 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPause}
607 */
608static DECLCALLBACK(int) drvHostValKitAudioHA_StreamPause(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
609{
610 RT_NOREF(pInterface, pStream);
611 return VINF_SUCCESS;
612}
613
614
615/**
616 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamResume}
617 */
618static DECLCALLBACK(int) drvHostValKitAudioHA_StreamResume(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
619{
620 RT_NOREF(pInterface, pStream);
621 return VINF_SUCCESS;
622}
623
624
625/**
626 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDrain}
627 */
628static DECLCALLBACK(int) drvHostValKitAudioHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
629{
630 RT_NOREF(pStream);
631
632 PDRVHOSTVALKITAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTVALKITAUDIO, IHostAudio);
633
634 int rc = RTCritSectEnter(&pThis->CritSect);
635 if (RT_SUCCESS(rc))
636 {
637 PVALKITTESTDATA pTst = pThis->pTestCur;
638
639 if (pTst)
640 {
641 LogRel(("ValKit: Test #%RU32: Recording audio data ended (took %RU32ms)\n",
642 pTst->idxTest, RTTimeMilliTS() - pTst->msStartedTS));
643
644 if (pTst->t.TestTone.u.Play.cbRead > pTst->t.TestTone.u.Play.cbToRead)
645 LogRel(("ValKit: Warning: Test #%RU32 read %RU32 bytes more than announced\n",
646 pTst->idxTest, pTst->t.TestTone.u.Play.cbRead - pTst->t.TestTone.u.Play.cbToRead));
647
648 AudioTestSetTestDone(pTst->pEntry);
649
650 pThis->pTestCur = NULL;
651 pTst = NULL;
652
653 if (pThis->fTestSetEnded)
654 rc = RTSemEventSignal(pThis->EventSemEnded);
655 }
656
657 int rc2 = RTCritSectLeave(&pThis->CritSect);
658 AssertRC(rc2);
659 }
660
661 return VINF_SUCCESS;
662}
663
664
665/**
666 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
667 */
668static DECLCALLBACK(uint32_t) drvHostValKitAudioHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
669{
670 RT_NOREF(pStream);
671
672 PDRVHOSTVALKITAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTVALKITAUDIO, IHostAudio);
673
674 int rc = RTCritSectEnter(&pThis->CritSect);
675 if (RT_SUCCESS(rc))
676 {
677 pThis->pTestCur = RTListGetFirst(&pThis->lstTestsPlay, VALKITTESTDATA, Node);
678
679 int rc2 = RTCritSectLeave(&pThis->CritSect);
680 AssertRC(rc2);
681 }
682
683 if (pThis->pTestCur == NULL) /* Empty list? */
684 return 0;
685
686 PVALKITTESTDATA const pTst = pThis->pTestCur;
687
688 Assert(pTst->t.TestTone.u.Rec.cbToWrite >= pTst->t.TestTone.u.Rec.cbWritten);
689 return pTst->t.TestTone.u.Rec.cbToWrite - pTst->t.TestTone.u.Rec.cbWritten;
690}
691
692
693/**
694 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
695 */
696static DECLCALLBACK(uint32_t) drvHostValKitAudioHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
697{
698 RT_NOREF(pInterface, pStream);
699 return UINT32_MAX;
700}
701
702
703/**
704 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState}
705 */
706static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvHostValKitAudioHA_StreamGetState(PPDMIHOSTAUDIO pInterface,
707 PPDMAUDIOBACKENDSTREAM pStream)
708{
709 RT_NOREF(pInterface);
710 AssertPtrReturn(pStream, PDMHOSTAUDIOSTREAMSTATE_INVALID);
711 return PDMHOSTAUDIOSTREAMSTATE_OKAY;
712}
713
714
715/**
716 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
717 */
718static DECLCALLBACK(int) drvHostValKitAudioHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
719 const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
720{
721 RT_NOREF(pStream);
722
723 if (cbBuf == 0)
724 {
725 /* Fend off draining calls. */
726 *pcbWritten = 0;
727 return VINF_SUCCESS;
728 }
729
730 PDRVHOSTVALKITAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTVALKITAUDIO, IHostAudio);
731
732 PVALKITTESTDATA pTst = NULL;
733
734 int rc = RTCritSectEnter(&pThis->CritSect);
735 if (RT_SUCCESS(rc))
736 {
737 if (pThis->pTestCur == NULL)
738 pThis->pTestCur = RTListGetFirst(&pThis->lstTestsPlay, VALKITTESTDATA, Node);
739
740 pTst = pThis->pTestCur;
741
742 int rc2 = RTCritSectLeave(&pThis->CritSect);
743 AssertRC(rc2);
744 }
745
746 if (pTst == NULL) /* Empty list? */
747 {
748 LogRel(("ValKit: Warning: Guest is playing back data when no playback test is active\n"));
749
750 *pcbWritten = cbBuf;
751 return VINF_SUCCESS;
752 }
753
754#if 1
755 if (PDMAudioPropsIsBufferSilence(&pStream->pStream->Cfg.Props, pvBuf, cbBuf))
756 {
757 LogRel(("ValKit: Skipping %RU32 bytes of silence\n", cbBuf));
758
759 *pcbWritten = cbBuf;
760 return VINF_SUCCESS;
761 }
762#endif
763
764 if (pTst->t.TestTone.u.Play.cbRead == 0)
765 {
766 AUDIOTESTPARMS Parms;
767 RT_ZERO(Parms);
768 Parms.enmDir = PDMAUDIODIR_IN;
769 Parms.enmType = AUDIOTESTTYPE_TESTTONE_RECORD;
770 Parms.TestTone = pTst->t.TestTone.Parms;
771
772 Assert(pTst->pEntry == NULL);
773 rc = AudioTestSetTestBegin(&pThis->Set, "Recording audio data from guest",
774 &Parms, &pTst->pEntry);
775 if (RT_SUCCESS(rc))
776 rc = AudioTestSetObjCreateAndRegister(&pThis->Set, "host-tone-rec.pcm", &pTst->pObj);
777
778 if (RT_SUCCESS(rc))
779 {
780 pTst->msStartedTS = RTTimeMilliTS();
781 LogRel(("ValKit: Test #%RU32: Recording audio data (%RU16Hz, %RU32ms) started\n",
782 pTst->idxTest, (uint16_t)Parms.TestTone.dbFreqHz, Parms.TestTone.msDuration));
783 }
784 }
785
786 uint32_t cbWritten = 0;
787
788 if (RT_SUCCESS(rc))
789 {
790 rc = AudioTestSetObjWrite(pTst->pObj, pvBuf, cbBuf);
791 if (RT_SUCCESS(rc))
792 {
793 pTst->t.TestTone.u.Play.cbRead += cbBuf;
794
795 #if 0
796 const bool fComplete = pTst->t.TestTone.u.Play.cbRead >= pTst->t.TestTone.u.Play.cbToRead;
797 if (fComplete)
798 {
799 LogRel(("ValKit: Test #%RU32: Recording audio data ended (took %RU32ms)\n",
800 pTst->idxTest, RTTimeMilliTS() - pTst->msStartedTS));
801
802 if (pTst->t.TestTone.u.Play.cbRead > pTst->t.TestTone.u.Play.cbToRead)
803 LogRel(("ValKit: Warning: Test #%RU32 read %RU32 bytes more than announced\n",
804 pTst->idxTest, pTst->t.TestTone.u.Play.cbRead - pTst->t.TestTone.u.Play.cbToRead));
805
806 rc = RTCritSectEnter(&pThis->CritSect);
807 if (RT_SUCCESS(rc))
808 {
809 AudioTestSetTestDone(pTst->pEntry);
810
811 pThis->pTestCur = NULL;
812 pTst = NULL;
813
814 if (pThis->fTestSetEnded)
815 rc = RTSemEventSignal(pThis->EventSemEnded);
816
817 int rc2 = RTCritSectLeave(&pThis->CritSect);
818 if (RT_SUCCESS(rc))
819 rc = rc2;
820 }
821 }
822 #endif
823
824 cbWritten = cbBuf;
825 }
826 }
827
828 if (RT_FAILURE(rc))
829 {
830 if ( pTst
831 && pTst->pEntry)
832 AudioTestSetTestFailed(pTst->pEntry, rc, "Recording audio data failed");
833 LogRel(("ValKit: Recording audio data failed with %Rrc\n", rc));
834 }
835
836 *pcbWritten = cbWritten;
837
838 return VINF_SUCCESS; /** @todo Return rc here? */
839}
840
841
842/**
843 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
844 */
845static DECLCALLBACK(int) drvHostValKitAudioHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
846 void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
847{
848 RT_NOREF(pStream);
849
850 PDRVHOSTVALKITAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTVALKITAUDIO, IHostAudio);
851
852 int rc = RTCritSectEnter(&pThis->CritSect);
853 if (RT_SUCCESS(rc))
854 {
855 if (pThis->pTestCur == NULL)
856 pThis->pTestCur = RTListGetFirst(&pThis->lstTestsRec, VALKITTESTDATA, Node);
857
858 int rc2 = RTCritSectLeave(&pThis->CritSect);
859 AssertRC(rc2);
860 }
861
862 if (pThis->pTestCur == NULL) /* Empty list? */
863 {
864 LogRelMax(64, ("ValKit: Warning: Guest is recording audio data when no recording test is active\n"));
865
866 *pcbRead = 0;
867 return VINF_SUCCESS;
868 }
869
870 PVALKITTESTDATA pTst = pThis->pTestCur;
871
872 if (pTst->t.TestTone.u.Rec.cbWritten == 0)
873 {
874 AUDIOTESTPARMS Parms;
875 RT_ZERO(Parms);
876 Parms.enmDir = PDMAUDIODIR_OUT;
877 Parms.enmType = AUDIOTESTTYPE_TESTTONE_PLAY;
878 Parms.TestTone = pTst->t.TestTone.Parms;
879
880 Assert(pTst->pEntry == NULL);
881 rc = AudioTestSetTestBegin(&pThis->Set, "Injecting audio input data to guest",
882 &Parms, &pTst->pEntry);
883 if (RT_SUCCESS(rc))
884 rc = AudioTestSetObjCreateAndRegister(&pThis->Set, "host-tone-play.pcm", &pTst->pObj);
885
886 if (RT_SUCCESS(rc))
887 {
888 pTst->msStartedTS = RTTimeMilliTS();
889 LogRel(("ValKit: Injecting audio input data (%RU16Hz, %RU32ms) started\n",
890 (uint16_t)pTst->t.TestTone.Tone.rdFreqHz,
891 pTst->t.TestTone.Parms.msDuration));
892 }
893 }
894
895 uint32_t cbRead = 0;
896
897 if (RT_SUCCESS(rc))
898 {
899 uint32_t cbToWrite = pTst->t.TestTone.u.Rec.cbToWrite - pTst->t.TestTone.u.Rec.cbWritten;
900 if (cbToWrite)
901 rc = AudioTestToneGenerate(&pTst->t.TestTone.Tone, pvBuf, RT_MIN(cbToWrite, cbBuf), &cbRead);
902 if ( RT_SUCCESS(rc)
903 && cbRead)
904 {
905 rc = AudioTestSetObjWrite(pTst->pObj, pvBuf, cbRead);
906 if (RT_SUCCESS(rc))
907 {
908 pTst->t.TestTone.u.Rec.cbWritten += cbRead;
909 Assert(pTst->t.TestTone.u.Rec.cbWritten <= pTst->t.TestTone.u.Rec.cbToWrite);
910
911 const bool fComplete = pTst->t.TestTone.u.Rec.cbToWrite == pTst->t.TestTone.u.Rec.cbWritten;
912
913 if (fComplete)
914 {
915 LogRel(("ValKit: Injecting audio input data done (took %RU32ms)\n",
916 RTTimeMilliTS() - pTst->msStartedTS));
917
918 rc = RTCritSectEnter(&pThis->CritSect);
919 if (RT_SUCCESS(rc))
920 {
921 drvHostValKiUnregisterRecTest(pThis, pTst);
922
923 pThis->pTestCur = NULL;
924 pTst = NULL;
925
926 int rc2 = RTCritSectLeave(&pThis->CritSect);
927 AssertRC(rc2);
928 }
929 }
930 }
931 }
932 }
933
934 if (RT_FAILURE(rc))
935 {
936 if ( pTst
937 && pTst->pEntry)
938 AudioTestSetTestFailed(pTst->pEntry, rc, "Injecting audio input data failed");
939 LogRel(("ValKit: Injecting audio input data failed with %Rrc\n", rc));
940 }
941
942 *pcbRead = cbRead;
943
944 return VINF_SUCCESS; /** @todo Return rc here? */
945}
946
947
948/**
949 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
950 */
951static DECLCALLBACK(void *) drvHostValKitAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID)
952{
953 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
954 PDRVHOSTVALKITAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTVALKITAUDIO);
955
956 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
957 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
958 return NULL;
959}
960
961
962/**
963 * Constructs a VaKit audio driver instance.
964 *
965 * @copydoc FNPDMDRVCONSTRUCT
966 */
967static DECLCALLBACK(int) drvHostValKitAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
968{
969 RT_NOREF(pCfg, fFlags);
970 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
971 PDRVHOSTVALKITAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTVALKITAUDIO);
972 LogRel(("Audio: Initializing VALKIT driver\n"));
973
974 /*
975 * Init the static parts.
976 */
977 pThis->pDrvIns = pDrvIns;
978 /* IBase */
979 pDrvIns->IBase.pfnQueryInterface = drvHostValKitAudioQueryInterface;
980 /* IHostAudio */
981 pThis->IHostAudio.pfnGetConfig = drvHostValKitAudioHA_GetConfig;
982 pThis->IHostAudio.pfnGetDevices = NULL;
983 pThis->IHostAudio.pfnGetStatus = drvHostValKitAudioHA_GetStatus;
984 pThis->IHostAudio.pfnDoOnWorkerThread = NULL;
985 pThis->IHostAudio.pfnStreamConfigHint = NULL;
986 pThis->IHostAudio.pfnStreamCreate = drvHostValKitAudioHA_StreamCreate;
987 pThis->IHostAudio.pfnStreamInitAsync = NULL;
988 pThis->IHostAudio.pfnStreamDestroy = drvHostValKitAudioHA_StreamDestroy;
989 pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL;
990 pThis->IHostAudio.pfnStreamEnable = drvHostValKitAudioHA_StreamEnable;
991 pThis->IHostAudio.pfnStreamDisable = drvHostValKitAudioHA_StreamDisable;
992 pThis->IHostAudio.pfnStreamPause = drvHostValKitAudioHA_StreamPause;
993 pThis->IHostAudio.pfnStreamResume = drvHostValKitAudioHA_StreamResume;
994 pThis->IHostAudio.pfnStreamDrain = drvHostValKitAudioHA_StreamDrain;
995 pThis->IHostAudio.pfnStreamGetReadable = drvHostValKitAudioHA_StreamGetReadable;
996 pThis->IHostAudio.pfnStreamGetWritable = drvHostValKitAudioHA_StreamGetWritable;
997 pThis->IHostAudio.pfnStreamGetPending = NULL;
998 pThis->IHostAudio.pfnStreamGetState = drvHostValKitAudioHA_StreamGetState;
999 pThis->IHostAudio.pfnStreamPlay = drvHostValKitAudioHA_StreamPlay;
1000 pThis->IHostAudio.pfnStreamCapture = drvHostValKitAudioHA_StreamCapture;
1001
1002 int rc = RTCritSectInit(&pThis->CritSect);
1003 AssertRCReturn(rc, rc);
1004 rc = RTSemEventCreate(&pThis->EventSemEnded);
1005 AssertRCReturn(rc, rc);
1006
1007 pThis->fTestSetEnded = false;
1008
1009 RTListInit(&pThis->lstTestsRec);
1010 pThis->cTestsRec = 0;
1011 RTListInit(&pThis->lstTestsPlay);
1012 pThis->cTestsPlay = 0;
1013
1014 ATSCALLBACKS Callbacks;
1015 RT_ZERO(Callbacks);
1016 Callbacks.pfnTestSetBegin = drvHostValKitTestSetBegin;
1017 Callbacks.pfnTestSetEnd = drvHostValKitTestSetEnd;
1018 Callbacks.pfnTonePlay = drvHostValKitRegisterGuestRecTest;
1019 Callbacks.pfnToneRecord = drvHostValKitRegisterGuestPlayTest;
1020 Callbacks.pfnTestSetSendBegin = drvHostValKitTestSetSendBeginCallback;
1021 Callbacks.pfnTestSetSendRead = drvHostValKitTestSetSendReadCallback;
1022 Callbacks.pfnTestSetSendEnd = drvHostValKitTestSetSendEndCallback;
1023 Callbacks.pvUser = pThis;
1024
1025 /** @todo Make this configurable via CFGM. */
1026 const char *pszTcpAddr = ATS_TCP_HOST_DEFAULT_ADDR_STR;
1027 uint32_t uTcpPort = ATS_TCP_HOST_DEFAULT_PORT;
1028
1029 LogRel(("ValKit: Starting Audio Test Service (ATS) at %s:%RU32...\n",
1030 pszTcpAddr, uTcpPort));
1031
1032 rc = AudioTestSvcInit(&pThis->Srv,
1033 /* We only allow connections from localhost for now. */
1034 pszTcpAddr, uTcpPort, &Callbacks);
1035 if (RT_SUCCESS(rc))
1036 rc = AudioTestSvcStart(&pThis->Srv);
1037
1038 if (RT_SUCCESS(rc))
1039 {
1040 LogRel(("ValKit: Audio Test Service (ATS) running\n"));
1041
1042 /** @todo Let the following be customizable by CFGM later. */
1043 rc = AudioTestPathCreateTemp(pThis->szPathTemp, sizeof(pThis->szPathTemp), "ValKitAudio");
1044 if (RT_SUCCESS(rc))
1045 {
1046 LogRel(("ValKit: Using temp dir '%s'\n", pThis->szPathTemp));
1047 rc = AudioTestPathGetTemp(pThis->szPathOut, sizeof(pThis->szPathOut));
1048 if (RT_SUCCESS(rc))
1049 LogRel(("ValKit: Using output dir '%s'\n", pThis->szPathOut));
1050 }
1051 }
1052
1053 if (RT_FAILURE(rc))
1054 LogRel(("ValKit: Initialization failed, rc=%Rrc\n", rc));
1055
1056 return rc;
1057}
1058
1059static DECLCALLBACK(void) drvHostValKitAudioDestruct(PPDMDRVINS pDrvIns)
1060{
1061 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
1062 PDRVHOSTVALKITAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTVALKITAUDIO);
1063
1064 LogRel(("ValKit: Shutting down Audio Test Service (ATS) ...\n"));
1065
1066 int rc = AudioTestSvcShutdown(&pThis->Srv);
1067 if (RT_SUCCESS(rc))
1068 rc = AudioTestSvcDestroy(&pThis->Srv);
1069
1070 if (RT_SUCCESS(rc))
1071 {
1072 LogRel(("ValKit: Shutdown of Audio Test Service (ATS) complete\n"));
1073 drvHostValKitCleanup(pThis);
1074 }
1075 else
1076 LogRel(("ValKit: Shutdown of Audio Test Service (ATS) failed, rc=%Rrc\n", rc));
1077
1078 /* Try cleaning up a bit. */
1079 RTDirRemove(pThis->szPathTemp);
1080 RTDirRemove(pThis->szPathOut);
1081
1082 RTSemEventDestroy(pThis->EventSemEnded);
1083
1084 if (RTCritSectIsInitialized(&pThis->CritSect))
1085 {
1086 int rc2 = RTCritSectDelete(&pThis->CritSect);
1087 if (RT_SUCCESS(rc))
1088 rc = rc2;
1089 }
1090
1091 if (RT_FAILURE(rc))
1092 LogRel(("ValKit: Destruction failed, rc=%Rrc\n", rc));
1093}
1094
1095/**
1096 * Char driver registration record.
1097 */
1098const PDMDRVREG g_DrvHostValidationKitAudio =
1099{
1100 /* u32Version */
1101 PDM_DRVREG_VERSION,
1102 /* szName */
1103 "ValidationKitAudio",
1104 /* szRCMod */
1105 "",
1106 /* szR0Mod */
1107 "",
1108 /* pszDescription */
1109 "ValidationKitAudio audio host driver",
1110 /* fFlags */
1111 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
1112 /* fClass. */
1113 PDM_DRVREG_CLASS_AUDIO,
1114 /* cMaxInstances */
1115 ~0U,
1116 /* cbInstance */
1117 sizeof(DRVHOSTVALKITAUDIO),
1118 /* pfnConstruct */
1119 drvHostValKitAudioConstruct,
1120 /* pfnDestruct */
1121 drvHostValKitAudioDestruct,
1122 /* pfnRelocate */
1123 NULL,
1124 /* pfnIOCtl */
1125 NULL,
1126 /* pfnPowerOn */
1127 NULL,
1128 /* pfnReset */
1129 NULL,
1130 /* pfnSuspend */
1131 NULL,
1132 /* pfnResume */
1133 NULL,
1134 /* pfnAttach */
1135 NULL,
1136 /* pfnDetach */
1137 NULL,
1138 /* pfnPowerOff */
1139 NULL,
1140 /* pfnSoftReset */
1141 NULL,
1142 /* u32EndVersion */
1143 PDM_DRVREG_VERSION
1144};
1145
注意: 瀏覽 TracBrowser 來幫助您使用儲存庫瀏覽器

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