VirtualBox

source: vbox/trunk/src/VBox/Devices/Audio/DevSB16.cpp@ 103134

Last change on this file since 103134 was 103134, checked in by vboxsync, 3 months ago

Audio: More locking needed to avoid debug assertions when draining a stream on stream disable. This debug assertion will happen because EMT and the mixer's async I/O thread work on the same circular buffer. There can be situations, if no locking is being used, that the async I/O thread just has consumed the remaining data just a tad before EMT looks for remaining data within the same buffer when disabling the stream. Also, don't immediately (hard) reset the circular buffer when a stream gets disabled, as this will wipe the already announced amount of remaining data when draining a stream. bugref:10354

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 116.0 KB
Line 
1/* $Id: DevSB16.cpp 103134 2024-01-31 09:21:06Z vboxsync $ */
2/** @file
3 * DevSB16 - VBox SB16 Audio Controller.
4 */
5
6/*
7 * Copyright (C) 2015-2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 * --------------------------------------------------------------------
27 *
28 * This code is based on: sb16.c from QEMU AUDIO subsystem (r3917).
29 * QEMU Soundblaster 16 emulation
30 *
31 * Copyright (c) 2003-2005 Vassili Karpov (malc)
32 *
33 * Permission is hereby granted, free of charge, to any person obtaining a copy
34 * of this software and associated documentation files (the "Software"), to deal
35 * in the Software without restriction, including without limitation the rights
36 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
37 * copies of the Software, and to permit persons to whom the Software is
38 * furnished to do so, subject to the following conditions:
39 *
40 * The above copyright notice and this permission notice shall be included in
41 * all copies or substantial portions of the Software.
42 *
43 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
44 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
45 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
46 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
47 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
48 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
49 * THE SOFTWARE.
50 */
51
52
53/*********************************************************************************************************************************
54* Header Files *
55*********************************************************************************************************************************/
56#define LOG_GROUP LOG_GROUP_DEV_SB16
57#include <VBox/log.h>
58#include <iprt/assert.h>
59#include <iprt/file.h>
60#ifdef IN_RING3
61# include <iprt/mem.h>
62# include <iprt/string.h>
63# include <iprt/uuid.h>
64#endif
65
66#include <VBox/vmm/pdmdev.h>
67#include <VBox/vmm/pdmaudioifs.h>
68#include <VBox/vmm/pdmaudioinline.h>
69#include <VBox/AssertGuest.h>
70
71#include "VBoxDD.h"
72
73#include "AudioMixBuffer.h"
74#include "AudioMixer.h"
75#include "AudioHlp.h"
76
77
78/*********************************************************************************************************************************
79* Defined Constants And Macros *
80*********************************************************************************************************************************/
81/** Default timer frequency (in Hz). */
82#define SB16_TIMER_HZ_DEFAULT 100
83/** The maximum number of separate streams we currently implement.
84 * Currently we only support one stream only, namely the output stream. */
85#define SB16_MAX_STREAMS 1
86/** The (zero-based) index of the output stream in \a aStreams. */
87#define SB16_IDX_OUT 0
88
89/** Current saved state version. */
90#define SB16_SAVE_STATE_VERSION 2
91/** The version used in VirtualBox version 3.0 and earlier. This didn't include the config dump. */
92#define SB16_SAVE_STATE_VERSION_VBOX_30 1
93
94
95/*********************************************************************************************************************************
96* Global Variables *
97*********************************************************************************************************************************/
98static const char e3[] = "COPYRIGHT (C) CREATIVE TECHNOLOGY LTD, 1992.";
99
100
101
102/*********************************************************************************************************************************
103* Structures and Typedefs *
104*********************************************************************************************************************************/
105/** Pointer to the SB16 state. */
106typedef struct SB16STATE *PSB16STATE;
107
108/**
109 * The internal state of a SB16 stream.
110 */
111typedef struct SB16STREAMSTATE
112{
113 /** Critical section for this stream. */
114 RTCRITSECT CritSect;
115 /** Flag indicating whether this stream is in enabled state or not. */
116 bool fEnabled;
117 /** Set if we've registered the asynchronous update job. */
118 bool fRegisteredAsyncUpdateJob;
119 /** DMA cache to read data from / write data to. */
120 PRTCIRCBUF pCircBuf;
121 /** Current circular buffer read offset (for tracing & logging). */
122 uint64_t offRead;
123 /** Current circular buffer write offset (for tracing & logging). */
124 uint64_t offWrite;
125
126 /** Size of the DMA buffer (pCircBuf) in bytes. */
127 uint32_t StatDmaBufSize;
128 /** Number of used bytes in the DMA buffer (pCircBuf). */
129 uint32_t StatDmaBufUsed;
130} SB16STREAMSTATE;
131/** Pointer to internal state of an SB16 stream. */
132typedef SB16STREAMSTATE *PSB16STREAMSTATE;
133
134/**
135 * Structure defining a (host backend) driver stream.
136 * Each driver has its own instances of audio mixer streams, which then
137 * can go into the same (or even different) audio mixer sinks.
138 */
139typedef struct SB16DRIVERSTREAM
140{
141 /** Associated mixer stream handle. */
142 R3PTRTYPE(PAUDMIXSTREAM) pMixStrm;
143 /** The stream's current configuration. */
144} SB16DRIVERSTREAM, *PSB16DRIVERSTREAM;
145
146/**
147 * Struct for tracking a host backend driver, i.e. our per-LUN data.
148 */
149typedef struct SB16DRIVER
150{
151 /** Node for storing this driver in our device driver list of SB16STATE. */
152 RTLISTNODER3 Node;
153 /** Pointer to SB16 controller (state). */
154 R3PTRTYPE(PSB16STATE) pSB16State;
155 /** Pointer to attached driver base interface. */
156 R3PTRTYPE(PPDMIBASE) pDrvBase;
157 /** Audio connector interface to the underlying host backend. */
158 R3PTRTYPE(PPDMIAUDIOCONNECTOR) pConnector;
159 /** Stream for output. */
160 SB16DRIVERSTREAM Out;
161 /** LUN # to which this driver has been assigned. */
162 uint8_t uLUN;
163 /** Whether this driver is in an attached state or not. */
164 bool fAttached;
165 /** The LUN description. */
166 char szDesc[48 - 2];
167} SB16DRIVER;
168/** Pointer to the per-LUN data. */
169typedef SB16DRIVER *PSB16DRIVER;
170
171/**
172 * Runtime configurable debug stuff for a SB16 stream.
173 */
174typedef struct SB16STREAMDEBUGRT
175{
176 /** Whether debugging is enabled or not. */
177 bool fEnabled;
178 uint8_t Padding[7];
179 /** File for dumping DMA reads / writes.
180 * For input streams, this dumps data being written to the device DMA,
181 * whereas for output streams this dumps data being read from the device DMA. */
182 R3PTRTYPE(PAUDIOHLPFILE) pFileDMA;
183} SB16STREAMDEBUGRT;
184
185/**
186 * Debug stuff for a SB16 stream.
187 */
188typedef struct SB16STREAMDEBUG
189{
190 /** Runtime debug stuff. */
191 SB16STREAMDEBUGRT Runtime;
192} SB16STREAMDEBUG;
193
194/**
195 * Structure for keeping a SB16 hardware stream configuration.
196 */
197typedef struct SB16STREAMHWCFG
198{
199 /** IRQ # to use. */
200 uint8_t uIrq;
201 /** Low DMA channel to use. */
202 uint8_t uDmaChanLow;
203 /** High DMA channel to use. */
204 uint8_t uDmaChanHigh;
205 /** IO port to use. */
206 RTIOPORT uPort;
207 /** DSP version to expose. */
208 uint16_t uVer;
209} SB16STREAMHWCFG;
210
211/**
212 * Structure for a SB16 stream.
213 */
214typedef struct SB16STREAM
215{
216 /** The stream's own index in \a aStreams of SB16STATE.
217 * Set to UINT8_MAX if not set (yet). */
218 uint8_t uIdx;
219 uint16_t uTimerHz;
220 /** The timer for pumping data thru the attached LUN drivers. */
221 TMTIMERHANDLE hTimerIO;
222 /** The timer interval for pumping data thru the LUN drivers in timer ticks. */
223 uint64_t cTicksTimerIOInterval;
224 /** Timestamp of the last timer callback (sb16TimerIO).
225 * Used to calculate thetime actually elapsed between two timer callbacks.
226 * This currently ASSMUMES that we only have one single (output) stream. */
227 uint64_t tsTimerIO; /** @todo Make this a per-stream value. */
228 /** The stream's currentconfiguration. */
229 PDMAUDIOSTREAMCFG Cfg;
230 /** The stream's defaulthardware configuration, mostly done by jumper settings back then. */
231 SB16STREAMHWCFG HwCfgDefault;
232 /** The stream's hardware configuration set at runtime.
233 * Might differ from the default configuration above and is needed for live migration. */
234 SB16STREAMHWCFG HwCfgRuntime;
235
236 int fifo;
237 int dma_auto;
238 /** Whether to use the high (\c true) or the low (\c false) DMA channel. */
239 int fDmaUseHigh;
240 int can_write; /** @todo r=andy BUGBUG Value never gets set to 0! */
241 int time_const;
242 /** The DMA transfer (block)size in bytes. */
243 int32_t cbDmaBlockSize;
244 int32_t cbDmaLeft; /** Note: Can be < 0. Needs to 32-bit for backwards compatibility. */
245 /** Internal state of this stream. */
246 SB16STREAMSTATE State;
247 /** Debug stuff. */
248 SB16STREAMDEBUG Dbg;
249} SB16STREAM;
250/** Pointer to a SB16 stream */
251typedef SB16STREAM *PSB16STREAM;
252
253/**
254 * SB16 debug settings.
255 */
256typedef struct SB16STATEDEBUG
257{
258 /** Whether debugging is enabled or not. */
259 bool fEnabled;
260 bool afAlignment[7];
261 /** Path where to dump the debug output to.
262 * Can be NULL, in which the system's temporary directory will be used then. */
263 R3PTRTYPE(char *) pszOutPath;
264} SB16STATEDEBUG;
265
266/**
267 * The SB16 state.
268 */
269typedef struct SB16STATE
270{
271 /** Pointer to the device instance. */
272 PPDMDEVINSR3 pDevInsR3;
273 /** Pointer to the connector of the attached audio driver. */
274 PPDMIAUDIOCONNECTOR pDrv;
275
276 int dsp_in_idx;
277 int dsp_out_data_len;
278 int dsp_in_needed_bytes;
279 int cmd;
280 int highspeed;
281
282 int v2x6;
283
284 uint8_t csp_param;
285 uint8_t csp_value;
286 uint8_t csp_mode;
287 uint8_t csp_index;
288 uint8_t csp_regs[256];
289 uint8_t csp_reg83[4];
290 int csp_reg83r;
291 int csp_reg83w;
292
293 uint8_t dsp_in_data[10];
294 uint8_t dsp_out_data[50];
295 uint8_t test_reg;
296 uint8_t last_read_byte;
297 int nzero;
298
299 RTLISTANCHOR lstDrv;
300 /** IRQ timer */
301 TMTIMERHANDLE hTimerIRQ;
302 /** The base interface for LUN\#0. */
303 PDMIBASE IBase;
304
305 /** Array of all SB16 hardware audio stream. */
306 SB16STREAM aStreams[SB16_MAX_STREAMS];
307 /** The device's software mixer. */
308 R3PTRTYPE(PAUDIOMIXER) pMixer;
309 /** Audio sink for PCM output. */
310 R3PTRTYPE(PAUDMIXSINK) pSinkOut;
311
312 /** The two mixer I/O ports (port + 4). */
313 IOMIOPORTHANDLE hIoPortsMixer;
314 /** The 10 DSP I/O ports (port + 6). */
315 IOMIOPORTHANDLE hIoPortsDsp;
316
317 /** Debug settings. */
318 SB16STATEDEBUG Dbg;
319
320 /* mixer state */
321 uint8_t mixer_nreg;
322 uint8_t mixer_regs[256];
323
324#ifdef VBOX_WITH_STATISTICS
325 STAMPROFILE StatTimerIO;
326 STAMCOUNTER StatBytesRead;
327#endif
328} SB16STATE;
329
330
331/*********************************************************************************************************************************
332* Internal Functions *
333*********************************************************************************************************************************/
334DECLINLINE(PDMAUDIODIR) sb16GetDirFromIndex(uint8_t uIdx);
335
336static int sb16StreamEnable(PSB16STATE pThis, PSB16STREAM pStream, bool fEnable, bool fForce);
337static void sb16StreamReset(PSB16STATE pThis, PSB16STREAM pStream);
338static int sb16StreamOpen(PPDMDEVINS pDevIns, PSB16STATE pThis, PSB16STREAM pStream);
339static void sb16StreamClose(PPDMDEVINS pDevIns, PSB16STATE pThis, PSB16STREAM pStream);
340DECLINLINE(void) sb16StreamLock(PSB16STREAM pStream);
341DECLINLINE(void) sb16StreamUnlock(PSB16STREAM pStream);
342DECLINLINE(PAUDMIXSINK) sb16StreamIndexToSink(PSB16STATE pThis, uint8_t uIdx);
343static void sb16StreamTransferScheduleNext(PSB16STATE pThis, PSB16STREAM pStream, uint32_t cSamples);
344static int sb16StreamDoDmaOutput(PSB16STATE pThis, PSB16STREAM pStream, int uDmaChan, uint32_t offDma, uint32_t cbDma, uint32_t cbToRead, uint32_t *pcbRead);
345static DECLCALLBACK(void) sb16StreamUpdateAsyncIoJob(PPDMDEVINS pDevIns, PAUDMIXSINK pSink, void *pvUser);
346
347static DECLCALLBACK(void) sb16TimerIO(PPDMDEVINS pDevIns, TMTIMERHANDLE hTimer, void *pvUser);
348static DECLCALLBACK(void) sb16TimerIRQ(PPDMDEVINS pDevIns, TMTIMERHANDLE hTimer, void *pvUser);
349DECLINLINE(void) sb16TimerSet(PPDMDEVINS pDevIns, PSB16STREAM pStream, uint64_t cTicksToDeadline);
350
351static void sb16SpeakerControl(PSB16STATE pThis, bool fOn);
352static void sb16UpdateVolume(PSB16STATE pThis);
353
354
355
356static void sb16SpeakerControl(PSB16STATE pThis, bool fOn)
357{
358 RT_NOREF(pThis, fOn);
359
360 /** @todo This currently does nothing. */
361}
362
363static void sb16StreamControl(PPDMDEVINS pDevIns, PSB16STATE pThis, PSB16STREAM pStream, bool fRun)
364{
365 unsigned uDmaChan = pStream->fDmaUseHigh ? pStream->HwCfgRuntime.uDmaChanHigh : pStream->HwCfgRuntime.uDmaChanLow;
366
367 LogFunc(("fRun=%RTbool, fDmaUseHigh=%RTbool, uDmaChan=%u\n", fRun, pStream->fDmaUseHigh, uDmaChan));
368
369 PDMDevHlpDMASetDREQ(pThis->pDevInsR3, uDmaChan, fRun ? 1 : 0);
370
371 if (fRun != pStream->State.fEnabled)
372 {
373 if (fRun)
374 {
375 int rc = VINF_SUCCESS;
376
377 if (pStream->Cfg.Props.uHz > 0)
378 {
379 rc = sb16StreamOpen(pDevIns, pThis, pStream);
380 if (RT_SUCCESS(rc))
381 sb16UpdateVolume(pThis);
382 }
383 else
384 AssertFailed(); /** @todo Buggy code? */
385
386 if (RT_SUCCESS(rc))
387 {
388 rc = sb16StreamEnable(pThis, pStream, true /* fEnable */, false /* fForce */);
389 if (RT_SUCCESS(rc))
390 {
391 sb16TimerSet(pDevIns, pStream, pStream->cTicksTimerIOInterval);
392
393 PDMDevHlpDMASchedule(pThis->pDevInsR3);
394 }
395 }
396 }
397 else
398 {
399 sb16StreamEnable(pThis, pStream, false /* fEnable */, false /* fForce */);
400 }
401 }
402}
403
404#define DMA8_AUTO 1
405#define DMA8_HIGH 2
406
407static void sb16DmaCmdContinue8(PPDMDEVINS pDevIns, PSB16STATE pThis, PSB16STREAM pStream)
408{
409 sb16StreamControl(pDevIns, pThis, pStream, true /* fRun */);
410}
411
412static void sb16DmaCmd8(PPDMDEVINS pDevIns, PSB16STATE pThis, PSB16STREAM pStream,
413 int mask, int dma_len)
414{
415 pStream->fDmaUseHigh = 0;
416
417 if (-1 == pStream->time_const)
418 {
419 if (pStream->Cfg.Props.uHz == 0)
420 pStream->Cfg.Props.uHz = 11025;
421 }
422 else
423 {
424 int tmp = (256 - pStream->time_const);
425 pStream->Cfg.Props.uHz = (1000000 + (tmp / 2)) / tmp;
426 }
427
428 /** @todo r=bird: Use '(pThis->mixer_regs[0x0e] & 2) == 0 ? 1 : 2' like below? */
429 unsigned cShiftChannels = PDMAudioPropsChannels(&pStream->Cfg.Props) >= 2 ? 1 : 0;
430
431 if (dma_len != -1)
432 {
433 pStream->cbDmaBlockSize = dma_len << cShiftChannels;
434 }
435 else
436 {
437 /* This is apparently the only way to make both Act1/PL
438 and SecondReality/FC work
439
440 r=andy Wow, actually someone who remembers Future Crew :-)
441
442 Act1 sets block size via command 0x48 and it's an odd number
443 SR does the same with even number
444 Both use stereo, and Creatives own documentation states that
445 0x48 sets block size in bytes less one.. go figure */
446 pStream->cbDmaBlockSize &= ~cShiftChannels;
447 }
448
449 pStream->Cfg.Props.uHz >>= cShiftChannels;
450 pStream->cbDmaLeft = pStream->cbDmaBlockSize;
451 /* pThis->highspeed = (mask & DMA8_HIGH) != 0; */
452 pStream->dma_auto = (mask & DMA8_AUTO) != 0;
453
454 PDMAudioPropsInit(&pStream->Cfg.Props, 1 /* 8-bit */,
455 false /* fSigned */,
456 (pThis->mixer_regs[0x0e] & 2) == 0 ? 1 : 2 /* Mono/Stereo */,
457 pStream->Cfg.Props.uHz);
458
459 /** @todo Check if stream's DMA block size is properly aligned to the set PCM props. */
460
461 sb16DmaCmdContinue8(pDevIns, pThis, pStream);
462 sb16SpeakerControl(pThis, true /* fOn */);
463}
464
465static void sb16DmaCmd(PPDMDEVINS pDevIns, PSB16STATE pThis, PSB16STREAM pStream,
466 uint8_t cmd, uint8_t d0, int dma_len)
467{
468 pStream->fDmaUseHigh = cmd < 0xc0;
469 pStream->fifo = (cmd >> 1) & 1;
470 pStream->dma_auto = (cmd >> 2) & 1;
471
472 pStream->Cfg.Props.fSigned = RT_BOOL(d0 & RT_BIT_32(4));
473 PDMAudioPropsSetChannels(&pStream->Cfg.Props, 1 + ((d0 >> 5) & 1));
474
475 switch (cmd >> 4)
476 {
477 case 11:
478 PDMAudioPropsSetSampleSize(&pStream->Cfg.Props, 2 /*16-bit*/);
479 break;
480
481 case 12:
482 PDMAudioPropsSetSampleSize(&pStream->Cfg.Props, 1 /*8-bit*/);
483 break;
484
485 default:
486 AssertFailed();
487 break;
488 }
489
490 if (-1 != pStream->time_const)
491 {
492#if 1
493 int tmp = 256 - pStream->time_const;
494 pStream->Cfg.Props.uHz = (1000000 + (tmp / 2)) / tmp;
495#else
496 /* pThis->freq = 1000000 / ((255 - pStream->time_const) << pThis->fmt_stereo); */
497 pThis->freq = 1000000 / ((255 - pStream->time_const));
498#endif
499 pStream->time_const = -1;
500 }
501
502 pStream->cbDmaBlockSize = dma_len + 1;
503 pStream->cbDmaBlockSize <<= PDMAudioPropsSampleSize(&pStream->Cfg.Props) == 2 ? 1 : 0;
504 if (!pStream->dma_auto)
505 {
506 /*
507 * It is clear that for DOOM and auto-init this value
508 * shouldn't take stereo into account, while Miles Sound Systems
509 * setsound.exe with single transfer mode wouldn't work without it
510 * wonders of SB16 yet again.
511 */
512 pStream->cbDmaBlockSize <<= PDMAudioPropsSampleSize(&pStream->Cfg.Props) == 2 ? 1 : 0;
513 }
514
515 pStream->cbDmaLeft = pStream->cbDmaBlockSize;
516
517 pThis->highspeed = 0;
518
519 /** @todo Check if stream's DMA block size is properly aligned to the set PCM props. */
520
521 sb16StreamControl(pDevIns, pThis, pStream, true /* fRun */);
522 sb16SpeakerControl(pThis, true /* fOn */);
523}
524
525static inline void sb16DspSeData(PSB16STATE pThis, uint8_t val)
526{
527 LogFlowFunc(("%#x\n", val));
528 if ((size_t) pThis->dsp_out_data_len < sizeof (pThis->dsp_out_data))
529 pThis->dsp_out_data[pThis->dsp_out_data_len++] = val;
530}
531
532static inline uint8_t sb16DspGetData(PSB16STATE pThis)
533{
534 if (pThis->dsp_in_idx)
535 return pThis->dsp_in_data[--pThis->dsp_in_idx];
536 AssertMsgFailed(("DSP input buffer underflow\n"));
537 return 0;
538}
539
540static void sb16DspCmdLookup(PPDMDEVINS pDevIns, PSB16STATE pThis, PSB16STREAM pStream, uint8_t cmd)
541{
542 LogFlowFunc(("command %#x\n", cmd));
543
544 if (cmd > 0xaf && cmd < 0xd0)
545 {
546 if (cmd & 8) /** @todo Handle recording. */
547 LogFlowFunc(("ADC not yet supported (command %#x)\n", cmd));
548
549 switch (cmd >> 4)
550 {
551 case 11:
552 case 12:
553 break;
554 default:
555 LogFlowFunc(("%#x wrong bits\n", cmd));
556 }
557
558 pThis->dsp_in_needed_bytes = 3;
559 }
560 else
561 {
562 pThis->dsp_in_needed_bytes = 0;
563
564 /** @todo Use a mapping table with
565 * - a command verb (binary search)
566 * - required bytes
567 * - function callback handler
568 */
569
570 switch (cmd)
571 {
572 case 0x03: /* ASP Status */
573 sb16DspSeData(pThis, 0x10); /* pThis->csp_param); */
574 goto warn;
575
576 case 0x04: /* DSP Status (Obsolete) / ASP ??? */
577 pThis->dsp_in_needed_bytes = 1;
578 goto warn;
579
580 case 0x05: /* ASP ??? */
581 pThis->dsp_in_needed_bytes = 2;
582 goto warn;
583
584 case 0x08: /* ??? */
585 /* __asm__ ("int3"); */
586 goto warn;
587
588 case 0x09: /* ??? */
589 sb16DspSeData(pThis, 0xf8);
590 goto warn;
591
592 case 0x0e: /* ??? */
593 pThis->dsp_in_needed_bytes = 2;
594 goto warn;
595
596 case 0x0f: /* ??? */
597 pThis->dsp_in_needed_bytes = 1;
598 goto warn;
599
600 case 0x10: /* Direct mode DAC */
601 pThis->dsp_in_needed_bytes = 1;
602 goto warn;
603
604 case 0x14: /* DAC DMA, 8-bit, uncompressed */
605 pThis->dsp_in_needed_bytes = 2;
606 pStream->cbDmaBlockSize = 0;
607 break;
608
609 case 0x1c: /* Auto-Initialize DMA DAC, 8-bit */
610 sb16DmaCmd8(pDevIns, pThis, pStream, DMA8_AUTO, -1);
611 break;
612
613 case 0x20: /* Direct ADC, Juice/PL */
614 sb16DspSeData(pThis, 0xff);
615 goto warn;
616
617 case 0x35: /* MIDI Read Interrupt + Write Poll (UART) */
618 LogRelMax2(32, ("SB16: MIDI support not implemented yet\n"));
619 break;
620
621 case 0x40: /* Set Time Constant */
622 pStream->time_const = -1;
623 pThis->dsp_in_needed_bytes = 1;
624 break;
625
626 case 0x41: /* Set sample rate for input */
627 pStream->Cfg.Props.uHz = 0; /** @todo r=andy Why do we reset output stuff here? */
628 pStream->time_const = -1;
629 pThis->dsp_in_needed_bytes = 2;
630 break;
631
632 case 0x42: /* Set sample rate for output */
633 pStream->Cfg.Props.uHz = 0;
634 pStream->time_const = -1;
635 pThis->dsp_in_needed_bytes = 2;
636 goto warn;
637
638 case 0x45: /* Continue Auto-Initialize DMA, 8-bit */
639 sb16DspSeData(pThis, 0xaa);
640 goto warn;
641
642 case 0x47: /* Continue Auto-Initialize DMA, 16-bit */
643 break;
644
645 case 0x48: /* Set DMA Block Size */
646 pThis->dsp_in_needed_bytes = 2;
647 break;
648
649 case 0x74: /* DMA DAC, 4-bit ADPCM */
650 pThis->dsp_in_needed_bytes = 2;
651 LogFlowFunc(("4-bit ADPCM not implemented yet\n"));
652 break;
653
654 case 0x75: /* DMA DAC, 4-bit ADPCM Reference */
655 pThis->dsp_in_needed_bytes = 2;
656 LogFlowFunc(("DMA DAC, 4-bit ADPCM Reference not implemented\n"));
657 break;
658
659 case 0x76: /* DMA DAC, 2.6-bit ADPCM */
660 pThis->dsp_in_needed_bytes = 2;
661 LogFlowFunc(("DMA DAC, 2.6-bit ADPCM not implemented yet\n"));
662 break;
663
664 case 0x77: /* DMA DAC, 2.6-bit ADPCM Reference */
665 pThis->dsp_in_needed_bytes = 2;
666 LogFlowFunc(("ADPCM reference not implemented yet\n"));
667 break;
668
669 case 0x7d: /* Auto-Initialize DMA DAC, 4-bit ADPCM Reference */
670 LogFlowFunc(("Autio-Initialize DMA DAC, 4-bit ADPCM reference not implemented yet\n"));
671 break;
672
673 case 0x7f: /* Auto-Initialize DMA DAC, 16-bit ADPCM Reference */
674 LogFlowFunc(("Autio-Initialize DMA DAC, 2.6-bit ADPCM Reference not implemented yet\n"));
675 break;
676
677 case 0x80: /* Silence DAC */
678 pThis->dsp_in_needed_bytes = 2;
679 break;
680
681 case 0x90: /* Auto-Initialize DMA DAC, 8-bit (High Speed) */
682 RT_FALL_THROUGH();
683 case 0x91: /* Normal DMA DAC, 8-bit (High Speed) */
684 sb16DmaCmd8(pDevIns, pThis, pStream, (((cmd & 1) == 0) ? 1 : 0) | DMA8_HIGH, -1);
685 break;
686
687 case 0xd0: /* Halt DMA operation. 8bit */
688 sb16StreamControl(pDevIns, pThis, pStream, false /* fRun */);
689 break;
690
691 case 0xd1: /* Speaker on */
692 sb16SpeakerControl(pThis, true /* fOn */);
693 break;
694
695 case 0xd3: /* Speaker off */
696 sb16SpeakerControl(pThis, false /* fOn */);
697 break;
698
699 case 0xd4: /* Continue DMA operation, 8-bit */
700 /* KQ6 (or maybe Sierras audblst.drv in general) resets
701 the frequency between halt/continue */
702 sb16DmaCmdContinue8(pDevIns, pThis, pStream);
703 break;
704
705 case 0xd5: /* Halt DMA operation, 16-bit */
706 sb16StreamControl(pDevIns, pThis, pStream, false /* fRun */);
707 break;
708
709 case 0xd6: /* Continue DMA operation, 16-bit */
710 sb16StreamControl(pDevIns, pThis, pStream, true /* fRun */);
711 break;
712
713 case 0xd9: /* Exit auto-init DMA after this block, 16-bit */
714 pStream->dma_auto = 0;
715 break;
716
717 case 0xda: /* Exit auto-init DMA after this block, 8-bit */
718 pStream->dma_auto = 0;
719 break;
720
721 case 0xe0: /* DSP identification */
722 pThis->dsp_in_needed_bytes = 1;
723 break;
724
725 case 0xe1: /* DSP version */
726 sb16DspSeData(pThis, RT_LO_U8(pStream->HwCfgRuntime.uVer));
727 sb16DspSeData(pThis, RT_HI_U8(pStream->HwCfgRuntime.uVer));
728 break;
729
730 case 0xe2: /* ??? */
731 pThis->dsp_in_needed_bytes = 1;
732 goto warn;
733
734 case 0xe3: /* DSP copyright */
735 {
736 for (int i = sizeof(e3) - 1; i >= 0; --i)
737 sb16DspSeData(pThis, e3[i]);
738 break;
739 }
740
741 case 0xe4: /* Write test register */
742 pThis->dsp_in_needed_bytes = 1;
743 break;
744
745 case 0xe7: /* ??? */
746 LogFlowFunc(("Attempt to probe for ESS (0xe7)?\n"));
747 break;
748
749 case 0xe8: /* Read test register */
750 sb16DspSeData(pThis, pThis->test_reg);
751 break;
752
753 case 0xf2: /* IRQ Request, 8-bit */
754 RT_FALL_THROUGH();
755 case 0xf3: /* IRQ Request, 16-bit */
756 {
757 sb16DspSeData(pThis, 0xaa);
758 pThis->mixer_regs[0x82] |= (cmd == 0xf2) ? 1 : 2;
759 PDMDevHlpISASetIrq(pThis->pDevInsR3, pStream->HwCfgRuntime.uIrq, 1);
760 break;
761 }
762
763 case 0xf8: /* Undocumented, used by old Creative diagnostic programs */
764 sb16DspSeData(pThis, 0);
765 goto warn;
766
767 case 0xf9: /* ??? */
768 pThis->dsp_in_needed_bytes = 1;
769 goto warn;
770
771 case 0xfa: /* ??? */
772 sb16DspSeData(pThis, 0);
773 goto warn;
774
775 case 0xfc: /* ??? */
776 sb16DspSeData(pThis, 0);
777 goto warn;
778
779 default:
780 LogFunc(("Unrecognized DSP command %#x, ignored\n", cmd));
781 break;
782 }
783 }
784
785exit:
786
787 if (!pThis->dsp_in_needed_bytes)
788 pThis->cmd = -1;
789 else
790 pThis->cmd = cmd;
791
792 return;
793
794warn:
795 LogFunc(("warning: command %#x,%d is not truly understood yet\n", cmd, pThis->dsp_in_needed_bytes));
796 goto exit;
797}
798
799DECLINLINE(uint16_t) sb16DspGetLoHi(PSB16STATE pThis)
800{
801 const uint8_t hi = sb16DspGetData(pThis);
802 const uint8_t lo = sb16DspGetData(pThis);
803 return RT_MAKE_U16(lo, hi);
804}
805
806DECLINLINE(uint16_t) sb16DspGetHiLo(PSB16STATE pThis)
807{
808 const uint8_t lo = sb16DspGetData(pThis);
809 const uint8_t hi = sb16DspGetData(pThis);
810 return RT_MAKE_U16(lo, hi);
811}
812
813static void sb16DspCmdComplete(PPDMDEVINS pDevIns, PSB16STATE pThis)
814{
815 LogFlowFunc(("Command %#x, in_index %d, needed_bytes %d\n", pThis->cmd, pThis->dsp_in_idx, pThis->dsp_in_needed_bytes));
816
817 int v0, v1, v2;
818
819 PSB16STREAM pStream = &pThis->aStreams[SB16_IDX_OUT]; /** @ŧodo Improve this. */
820
821 if (pThis->cmd > 0xaf && pThis->cmd < 0xd0)
822 {
823 v2 = sb16DspGetData(pThis);
824 v1 = sb16DspGetData(pThis);
825 v0 = sb16DspGetData(pThis);
826
827 if (pThis->cmd & 8)
828 LogFlowFunc(("ADC params cmd = %#x d0 = %d, d1 = %d, d2 = %d\n", pThis->cmd, v0, v1, v2));
829 else
830 {
831 LogFlowFunc(("cmd = %#x d0 = %d, d1 = %d, d2 = %d\n", pThis->cmd, v0, v1, v2));
832 sb16DmaCmd(pDevIns, pThis, pStream, pThis->cmd, v0, v1 + (v2 << 8));
833 }
834 }
835 else
836 {
837 switch (pThis->cmd)
838 {
839 case 0x04:
840 pThis->csp_mode = sb16DspGetData(pThis);
841 pThis->csp_reg83r = 0;
842 pThis->csp_reg83w = 0;
843 LogFlowFunc(("CSP command 0x04: mode=%#x\n", pThis->csp_mode));
844 break;
845
846 case 0x05:
847 pThis->csp_param = sb16DspGetData(pThis);
848 pThis->csp_value = sb16DspGetData(pThis);
849 LogFlowFunc(("CSP command 0x05: param=%#x value=%#x\n", pThis->csp_param, pThis->csp_value));
850 break;
851
852 case 0x0e:
853 v0 = sb16DspGetData(pThis);
854 v1 = sb16DspGetData(pThis);
855 LogFlowFunc(("write CSP register %d <- %#x\n", v1, v0));
856 if (v1 == 0x83)
857 {
858 LogFlowFunc(("0x83[%d] <- %#x\n", pThis->csp_reg83r, v0));
859 pThis->csp_reg83[pThis->csp_reg83r % 4] = v0;
860 pThis->csp_reg83r += 1;
861 }
862 else
863 pThis->csp_regs[v1] = v0;
864 break;
865
866 case 0x0f:
867 v0 = sb16DspGetData(pThis);
868 LogFlowFunc(("read CSP register %#x -> %#x, mode=%#x\n", v0, pThis->csp_regs[v0], pThis->csp_mode));
869 if (v0 == 0x83)
870 {
871 LogFlowFunc(("0x83[%d] -> %#x\n", pThis->csp_reg83w, pThis->csp_reg83[pThis->csp_reg83w % 4]));
872 sb16DspSeData(pThis, pThis->csp_reg83[pThis->csp_reg83w % 4]);
873 pThis->csp_reg83w += 1;
874 }
875 else
876 sb16DspSeData(pThis, pThis->csp_regs[v0]);
877 break;
878
879 case 0x10:
880 v0 = sb16DspGetData(pThis);
881 LogFlowFunc(("cmd 0x10 d0=%#x\n", v0));
882 break;
883
884 case 0x14:
885 sb16DmaCmd8(pDevIns, pThis, pStream, 0, sb16DspGetLoHi(pThis) + 1);
886 break;
887
888 case 0x22: /* Sets the master volume. */
889 /** @todo Setting the master volume is not implemented yet. */
890 break;
891
892 case 0x40: /* Sets the timer constant; SB16 is able to use sample rates via 0x41 instead. */
893 pStream->time_const = sb16DspGetData(pThis);
894 LogFlowFunc(("set time const %d\n", pStream->time_const));
895 break;
896
897 case 0x42: /* Sets the input rate (in Hz). */
898#if 0
899 LogFlowFunc(("cmd 0x42 might not do what it think it should\n"));
900#endif
901 RT_FALL_THROUGH(); /** @todo BUGBUG FT2 sets output freq with this, go figure. */
902
903 case 0x41: /* Sets the output rate (in Hz). */
904 pStream->Cfg.Props.uHz = sb16DspGetHiLo(pThis);
905 LogFlowFunc(("set freq to %RU16Hz\n", pStream->Cfg.Props.uHz));
906 break;
907
908 case 0x48:
909 pStream->cbDmaBlockSize = sb16DspGetLoHi(pThis) + 1;
910 LogFlowFunc(("set dma block len %d\n", pStream->cbDmaBlockSize));
911 break;
912
913 case 0x74:
914 case 0x75:
915 case 0x76:
916 case 0x77:
917 /* ADPCM stuff, ignore. */
918 break;
919
920 case 0x80: /* Sets the IRQ. */
921 sb16StreamTransferScheduleNext(pThis, pStream, sb16DspGetLoHi(pThis) + 1);
922 break;
923
924 case 0xe0:
925 v0 = sb16DspGetData(pThis);
926 pThis->dsp_out_data_len = 0;
927 LogFlowFunc(("E0=%#x\n", v0));
928 sb16DspSeData(pThis, ~v0);
929 break;
930
931 case 0xe2:
932 v0 = sb16DspGetData(pThis);
933 LogFlowFunc(("E2=%#x\n", v0));
934 break;
935
936 case 0xe4:
937 pThis->test_reg = sb16DspGetData(pThis);
938 break;
939
940 case 0xf9:
941 v0 = sb16DspGetData(pThis);
942 switch (v0)
943 {
944 case 0x0e:
945 sb16DspSeData(pThis, 0xff);
946 break;
947
948 case 0x0f:
949 sb16DspSeData(pThis, 0x07);
950 break;
951
952 case 0x37:
953 sb16DspSeData(pThis, 0x38);
954 break;
955
956 default:
957 sb16DspSeData(pThis, 0x00);
958 break;
959 }
960 break;
961
962 default:
963 LogRel2(("SB16: Unrecognized command %#x, skipping\n", pThis->cmd));
964 return;
965 }
966 }
967
968 pThis->cmd = -1;
969 return;
970}
971
972static void sb16DspCmdResetLegacy(PSB16STATE pThis)
973{
974 LogFlowFuncEnter();
975
976 /* Disable speaker(s). */
977 sb16SpeakerControl(pThis, false /* fOn */);
978
979 /*
980 * Reset all streams.
981 */
982 for (unsigned i = 0; i < SB16_MAX_STREAMS; i++)
983 sb16StreamReset(pThis, &pThis->aStreams[i]);
984}
985
986static void sb16DspCmdReset(PSB16STATE pThis)
987{
988 pThis->mixer_regs[0x82] = 0;
989 pThis->dsp_in_idx = 0;
990 pThis->dsp_out_data_len = 0;
991 pThis->dsp_in_needed_bytes = 0;
992 pThis->nzero = 0;
993 pThis->highspeed = 0;
994 pThis->v2x6 = 0;
995 pThis->cmd = -1;
996
997 sb16DspSeData(pThis, 0xaa);
998
999 sb16DspCmdResetLegacy(pThis);
1000}
1001
1002/**
1003 * @callback_method_impl{PFNIOMIOPORTNEWOUT}
1004 */
1005static DECLCALLBACK(VBOXSTRICTRC) sb16IoPortDspWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t u32, unsigned cb)
1006{
1007 PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE);
1008 RT_NOREF(pvUser, cb);
1009
1010 /** @todo Figure out how we can distinguish between streams. DSP port #, e.g. 0x220? */
1011 PSB16STREAM pStream = &pThis->aStreams[SB16_IDX_OUT];
1012
1013 LogFlowFunc(("write %#x <- %#x\n", offPort, u32));
1014 switch (offPort)
1015 {
1016 case 0:
1017 switch (u32)
1018 {
1019 case 0x00:
1020 {
1021 if (pThis->v2x6 == 1)
1022 {
1023 if (0 && pThis->highspeed)
1024 {
1025 pThis->highspeed = 0;
1026 PDMDevHlpISASetIrq(pThis->pDevInsR3, pStream->HwCfgRuntime.uIrq, 0);
1027 sb16StreamControl(pDevIns, pThis, pStream, false /* fRun */);
1028 }
1029 else
1030 sb16DspCmdReset(pThis);
1031 }
1032 pThis->v2x6 = 0;
1033 break;
1034 }
1035
1036 case 0x01:
1037 case 0x03: /* FreeBSD kludge */
1038 pThis->v2x6 = 1;
1039 break;
1040
1041 case 0xc6:
1042 pThis->v2x6 = 0; /* Prince of Persia, csp.sys, diagnose.exe */
1043 break;
1044
1045 case 0xb8: /* Panic */
1046 sb16DspCmdReset(pThis);
1047 break;
1048
1049 case 0x39:
1050 sb16DspSeData(pThis, 0x38);
1051 sb16DspCmdReset(pThis);
1052 pThis->v2x6 = 0x39;
1053 break;
1054
1055 default:
1056 pThis->v2x6 = u32;
1057 break;
1058 }
1059 break;
1060
1061 case 6: /* Write data or command | write status */
1062#if 0
1063 if (pThis->highspeed)
1064 break;
1065#endif
1066 if (0 == pThis->dsp_in_needed_bytes)
1067 {
1068 sb16DspCmdLookup(pDevIns, pThis, pStream, u32);
1069 }
1070 else
1071 {
1072 if (pThis->dsp_in_idx == sizeof (pThis->dsp_in_data))
1073 {
1074 AssertMsgFailed(("DSP input data overrun\n"));
1075 }
1076 else
1077 {
1078 pThis->dsp_in_data[pThis->dsp_in_idx++] = u32;
1079 if (pThis->dsp_in_idx == pThis->dsp_in_needed_bytes)
1080 {
1081 pThis->dsp_in_needed_bytes = 0;
1082 sb16DspCmdComplete(pDevIns, pThis);
1083 }
1084 }
1085 }
1086 break;
1087
1088 default:
1089 LogFlowFunc(("offPort=%#x, u32=%#x)\n", offPort, u32));
1090 break;
1091 }
1092
1093 return VINF_SUCCESS;
1094}
1095
1096
1097/**
1098 * @callback_method_impl{PFNIOMIOPORTNEWIN}
1099 */
1100static DECLCALLBACK(VBOXSTRICTRC) sb16IoPortDspRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t *pu32, unsigned cb)
1101{
1102 RT_NOREF(pvUser, cb);
1103
1104 PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE);
1105
1106 uint32_t retval;
1107 int ack = 0;
1108
1109 /** @todo Figure out how we can distinguish between streams. */
1110 PSB16STREAM pStream = &pThis->aStreams[SB16_IDX_OUT];
1111
1112 /** @todo reject non-byte access?
1113 * The spec does not mention a non-byte access so we should check how real hardware behaves. */
1114
1115 switch (offPort)
1116 {
1117 case 0: /* reset */
1118 retval = 0xff;
1119 break;
1120
1121 case 4: /* read data */
1122 if (pThis->dsp_out_data_len)
1123 {
1124 retval = pThis->dsp_out_data[--pThis->dsp_out_data_len];
1125 pThis->last_read_byte = retval;
1126 }
1127 else
1128 {
1129 if (pThis->cmd != -1)
1130 LogFlowFunc(("empty output buffer for command %#x\n", pThis->cmd));
1131 retval = pThis->last_read_byte;
1132 /* goto error; */
1133 }
1134 break;
1135
1136 case 6: /* 0 can write */
1137 retval = pStream->can_write ? 0 : 0x80;
1138 break;
1139
1140 case 7: /* timer interrupt clear */
1141 /* LogFlowFunc(("timer interrupt clear\n")); */
1142 retval = 0;
1143 break;
1144
1145 case 8: /* data available status | irq 8 ack */
1146 retval = (!pThis->dsp_out_data_len || pThis->highspeed) ? 0 : 0x80;
1147 if (pThis->mixer_regs[0x82] & 1)
1148 {
1149 ack = 1;
1150 pThis->mixer_regs[0x82] &= ~1;
1151 PDMDevHlpISASetIrq(pThis->pDevInsR3, pStream->HwCfgRuntime.uIrq, 0);
1152 }
1153 break;
1154
1155 case 9: /* irq 16 ack */
1156 retval = 0xff;
1157 if (pThis->mixer_regs[0x82] & 2)
1158 {
1159 ack = 1;
1160 pThis->mixer_regs[0x82] &= ~2;
1161 PDMDevHlpISASetIrq(pThis->pDevInsR3, pStream->HwCfgRuntime.uIrq, 0);
1162 }
1163 break;
1164
1165 default:
1166 LogFlowFunc(("warning: sb16IoPortDspRead %#x error\n", offPort));
1167 return VERR_IOM_IOPORT_UNUSED;
1168 }
1169
1170 if (!ack)
1171 LogFlowFunc(("read %#x -> %#x\n", offPort, retval));
1172
1173 *pu32 = retval;
1174 return VINF_SUCCESS;
1175}
1176
1177
1178/*********************************************************************************************************************************
1179* Mixer functions *
1180*********************************************************************************************************************************/
1181
1182static uint8_t sb16MixRegToVol(PSB16STATE pThis, int reg)
1183{
1184 /* The SB16 mixer has a 0 to -62dB range in 32 levels (2dB each step).
1185 * We use a 0 to -96dB range in 256 levels (0.375dB each step).
1186 * Only the top 5 bits of a mixer register are used.
1187 */
1188 uint8_t steps = 31 - (pThis->mixer_regs[reg] >> 3);
1189 uint8_t vol = 255 - steps * 16 / 3; /* (2dB*8) / (0.375dB*8) */
1190 return vol;
1191}
1192
1193/**
1194 * Returns the device's current master volume.
1195 *
1196 * @param pThis SB16 state.
1197 * @param pVol Where to store the master volume information.
1198 */
1199DECLINLINE(void) sb16GetMasterVolume(PSB16STATE pThis, PPDMAUDIOVOLUME pVol)
1200{
1201 /* There's no mute switch, only volume controls. */
1202 PDMAudioVolumeInitFromStereo(pVol, false /*fMuted*/, sb16MixRegToVol(pThis, 0x30), sb16MixRegToVol(pThis, 0x31));
1203}
1204
1205/**
1206 * Returns the device's current output stream volume.
1207 *
1208 * @param pThis SB16 state.
1209 * @param pVol Where to store the output stream volume information.
1210 */
1211DECLINLINE(void) sb16GetPcmOutVolume(PSB16STATE pThis, PPDMAUDIOVOLUME pVol)
1212{
1213 /* There's no mute switch, only volume controls. */
1214 PDMAudioVolumeInitFromStereo(pVol, false /*fMuted*/, sb16MixRegToVol(pThis, 0x32), sb16MixRegToVol(pThis, 0x33));
1215}
1216
1217static void sb16UpdateVolume(PSB16STATE pThis)
1218{
1219 PDMAUDIOVOLUME VolMaster;
1220 sb16GetMasterVolume(pThis, &VolMaster);
1221
1222 PDMAUDIOVOLUME VolOut;
1223 sb16GetPcmOutVolume(pThis, &VolOut);
1224
1225 /* Combine the master + output stream volume. */
1226 PDMAUDIOVOLUME VolCombined;
1227 PDMAudioVolumeCombine(&VolCombined, &VolMaster, &VolOut);
1228
1229 int rc2 = AudioMixerSinkSetVolume(pThis->pSinkOut, &VolCombined);
1230 AssertRC(rc2);
1231}
1232
1233static void sb16MixerReset(PSB16STATE pThis)
1234{
1235 memset(pThis->mixer_regs, 0xff, 0x7f);
1236 memset(pThis->mixer_regs + 0x83, 0xff, sizeof (pThis->mixer_regs) - 0x83);
1237
1238 pThis->mixer_regs[0x02] = 4; /* master volume 3bits */
1239 pThis->mixer_regs[0x06] = 4; /* MIDI volume 3bits */
1240 pThis->mixer_regs[0x08] = 0; /* CD volume 3bits */
1241 pThis->mixer_regs[0x0a] = 0; /* voice volume 2bits */
1242
1243 /* d5=input filt, d3=lowpass filt, d1,d2=input source */
1244 pThis->mixer_regs[0x0c] = 0;
1245
1246 /* d5=output filt, d1=stereo switch */
1247 pThis->mixer_regs[0x0e] = 0;
1248
1249 /* voice volume L d5,d7, R d1,d3 */
1250 pThis->mixer_regs[0x04] = (12 << 4) | 12;
1251 /* master ... */
1252 pThis->mixer_regs[0x22] = (12 << 4) | 12;
1253 /* MIDI ... */
1254 pThis->mixer_regs[0x26] = (12 << 4) | 12;
1255
1256 /* master/voice/MIDI L/R volume */
1257 for (int i = 0x30; i < 0x36; i++)
1258 pThis->mixer_regs[i] = 24 << 3; /* -14 dB */
1259
1260 /* treble/bass */
1261 for (int i = 0x44; i < 0x48; i++)
1262 pThis->mixer_regs[i] = 0x80;
1263
1264 /* Update the master (mixer) and PCM out volumes. */
1265 sb16UpdateVolume(pThis);
1266
1267 /*
1268 * Reset mixer sinks.
1269 *
1270 * Do the reset here instead of in sb16StreamReset();
1271 * the mixer sink(s) might still have data to be processed when an audio stream gets reset.
1272 */
1273 if (pThis->pSinkOut)
1274 AudioMixerSinkReset(pThis->pSinkOut);
1275}
1276
1277static int magic_of_irq(int irq)
1278{
1279 switch (irq)
1280 {
1281 case 5:
1282 return 2;
1283 case 7:
1284 return 4;
1285 case 9:
1286 return 1;
1287 case 10:
1288 return 8;
1289 default:
1290 break;
1291 }
1292
1293 LogFlowFunc(("bad irq %d\n", irq));
1294 return 2;
1295}
1296
1297static int irq_of_magic(int magic)
1298{
1299 switch (magic)
1300 {
1301 case 1:
1302 return 9;
1303 case 2:
1304 return 5;
1305 case 4:
1306 return 7;
1307 case 8:
1308 return 10;
1309 default:
1310 break;
1311 }
1312
1313 LogFlowFunc(("bad irq magic %d\n", magic));
1314 return -1;
1315}
1316
1317static int sb16MixerWriteIndex(PSB16STATE pThis, PSB16STREAM pStream, uint8_t val)
1318{
1319 RT_NOREF(pStream);
1320 pThis->mixer_nreg = val;
1321 return VINF_SUCCESS;
1322}
1323
1324#ifndef VBOX
1325static uint32_t popcount(uint32_t u)
1326{
1327 u = ((u&0x55555555) + ((u>>1)&0x55555555));
1328 u = ((u&0x33333333) + ((u>>2)&0x33333333));
1329 u = ((u&0x0f0f0f0f) + ((u>>4)&0x0f0f0f0f));
1330 u = ((u&0x00ff00ff) + ((u>>8)&0x00ff00ff));
1331 u = ( u&0x0000ffff) + (u>>16);
1332 return u;
1333}
1334#endif
1335
1336static uint32_t lsbindex(uint32_t u)
1337{
1338#ifdef VBOX
1339 return u ? ASMBitFirstSetU32(u) - 1 : 32;
1340#else
1341 return popcount((u & -(int32_t)u) - 1);
1342#endif
1343}
1344
1345/* Convert SB16 to SB Pro mixer volume (left). */
1346static inline void sb16ConvVolumeL(PSB16STATE pThis, unsigned reg, uint8_t val)
1347{
1348 /* High nibble in SBP mixer. */
1349 pThis->mixer_regs[reg] = (pThis->mixer_regs[reg] & 0x0f) | (val & 0xf0);
1350}
1351
1352/* Convert SB16 to SB Pro mixer volume (right). */
1353static inline void sb16ConvVolumeR(PSB16STATE pThis, unsigned reg, uint8_t val)
1354{
1355 /* Low nibble in SBP mixer. */
1356 pThis->mixer_regs[reg] = (pThis->mixer_regs[reg] & 0xf0) | (val >> 4);
1357}
1358
1359/* Convert SB Pro to SB16 mixer volume (left + right). */
1360static inline void sb16ConvVolumeOldToNew(PSB16STATE pThis, unsigned reg, uint8_t val)
1361{
1362 /* Left channel. */
1363 pThis->mixer_regs[reg + 0] = (val & 0xf0) | RT_BIT(3);
1364 /* Right channel (the register immediately following). */
1365 pThis->mixer_regs[reg + 1] = (val << 4) | RT_BIT(3);
1366}
1367
1368
1369static int sb16MixerWriteData(PSB16STATE pThis, PSB16STREAM pStream, uint8_t val)
1370{
1371 bool fUpdateMaster = false;
1372 bool fUpdateStream = false;
1373
1374 LogFlowFunc(("[%#x] <- %#x\n", pThis->mixer_nreg, val));
1375
1376 switch (pThis->mixer_nreg)
1377 {
1378 case 0x00:
1379 sb16MixerReset(pThis);
1380 /* And update the actual volume, too. */
1381 fUpdateMaster = true;
1382 fUpdateStream = true;
1383 break;
1384
1385 case 0x04: /* Translate from old style voice volume (L/R). */
1386 sb16ConvVolumeOldToNew(pThis, 0x32, val);
1387 fUpdateStream = true;
1388 break;
1389
1390 case 0x22: /* Translate from old style master volume (L/R). */
1391 sb16ConvVolumeOldToNew(pThis, 0x30, val);
1392 fUpdateMaster = true;
1393 break;
1394
1395 case 0x26: /* Translate from old style MIDI volume (L/R). */
1396 sb16ConvVolumeOldToNew(pThis, 0x34, val);
1397 break;
1398
1399 case 0x28: /* Translate from old style CD volume (L/R). */
1400 sb16ConvVolumeOldToNew(pThis, 0x36, val);
1401 break;
1402
1403 case 0x2E: /* Translate from old style line volume (L/R). */
1404 sb16ConvVolumeOldToNew(pThis, 0x38, val);
1405 break;
1406
1407 case 0x30: /* Translate to old style master volume (L). */
1408 sb16ConvVolumeL(pThis, 0x22, val);
1409 fUpdateMaster = true;
1410 break;
1411
1412 case 0x31: /* Translate to old style master volume (R). */
1413 sb16ConvVolumeR(pThis, 0x22, val);
1414 fUpdateMaster = true;
1415 break;
1416
1417 case 0x32: /* Translate to old style voice volume (L). */
1418 sb16ConvVolumeL(pThis, 0x04, val);
1419 fUpdateStream = true;
1420 break;
1421
1422 case 0x33: /* Translate to old style voice volume (R). */
1423 sb16ConvVolumeR(pThis, 0x04, val);
1424 fUpdateStream = true;
1425 break;
1426
1427 case 0x34: /* Translate to old style MIDI volume (L). */
1428 sb16ConvVolumeL(pThis, 0x26, val);
1429 break;
1430
1431 case 0x35: /* Translate to old style MIDI volume (R). */
1432 sb16ConvVolumeR(pThis, 0x26, val);
1433 break;
1434
1435 case 0x36: /* Translate to old style CD volume (L). */
1436 sb16ConvVolumeL(pThis, 0x28, val);
1437 break;
1438
1439 case 0x37: /* Translate to old style CD volume (R). */
1440 sb16ConvVolumeR(pThis, 0x28, val);
1441 break;
1442
1443 case 0x38: /* Translate to old style line volume (L). */
1444 sb16ConvVolumeL(pThis, 0x2E, val);
1445 break;
1446
1447 case 0x39: /* Translate to old style line volume (R). */
1448 sb16ConvVolumeR(pThis, 0x2E, val);
1449 break;
1450
1451 case 0x80:
1452 {
1453 int irq = irq_of_magic(val);
1454 LogRelMax2(64, ("SB16: Setting IRQ to %d\n", irq));
1455 if (irq > 0)
1456 pStream->HwCfgRuntime.uIrq = irq;
1457 break;
1458 }
1459
1460 case 0x81:
1461 {
1462 int dma = lsbindex(val & 0xf);
1463 int hdma = lsbindex(val & 0xf0);
1464 if ( dma != pStream->HwCfgRuntime.uDmaChanLow
1465 || hdma != pStream->HwCfgRuntime.uDmaChanHigh)
1466 {
1467 LogRelMax2(64, ("SB16: Attempt to change DMA 8bit %d(%d), 16bit %d(%d)\n",
1468 dma, pStream->HwCfgRuntime.uDmaChanLow, hdma, pStream->HwCfgRuntime.uDmaChanHigh));
1469 }
1470#if 0
1471 pStream->dma = dma;
1472 pStream->hdma = hdma;
1473#endif
1474 break;
1475 }
1476
1477 case 0x82:
1478 LogRelMax2(64, ("SB16: Attempt to write into IRQ status register to %#x\n", val));
1479 return VINF_SUCCESS;
1480
1481 default:
1482 if (pThis->mixer_nreg >= 0x80)
1483 LogFlowFunc(("attempt to write mixer[%#x] <- %#x\n", pThis->mixer_nreg, val));
1484 break;
1485 }
1486
1487 pThis->mixer_regs[pThis->mixer_nreg] = val;
1488
1489 /* Update the master (mixer) volume. */
1490 if ( fUpdateMaster
1491 || fUpdateStream)
1492 {
1493 sb16UpdateVolume(pThis);
1494 }
1495
1496 return VINF_SUCCESS;
1497}
1498
1499/**
1500 * @callback_method_impl{PFNIOMIOPORTNEWOUT}
1501 */
1502static DECLCALLBACK(VBOXSTRICTRC) sb16IoPortMixerWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t u32, unsigned cb)
1503{
1504 PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE);
1505 RT_NOREF(pvUser);
1506
1507 /** @todo Figure out how we can distinguish between streams. */
1508 PSB16STREAM pStream = &pThis->aStreams[SB16_IDX_OUT];
1509
1510 switch (cb)
1511 {
1512 case 1:
1513 switch (offPort)
1514 {
1515 case 0:
1516 sb16MixerWriteIndex(pThis, pStream, u32);
1517 break;
1518 case 1:
1519 sb16MixerWriteData(pThis, pStream, u32);
1520 break;
1521 default:
1522 AssertFailed();
1523 }
1524 break;
1525 case 2:
1526 sb16MixerWriteIndex(pThis, pStream, u32 & 0xff);
1527 sb16MixerWriteData(pThis, pStream, (u32 >> 8) & 0xff);
1528 break;
1529 default:
1530 ASSERT_GUEST_MSG_FAILED(("offPort=%#x cb=%d u32=%#x\n", offPort, cb, u32));
1531 break;
1532 }
1533 return VINF_SUCCESS;
1534}
1535
1536/**
1537 * @callback_method_impl{PFNIOMIOPORTNEWIN}
1538 */
1539static DECLCALLBACK(VBOXSTRICTRC) sb16IoPortMixerRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t *pu32, unsigned cb)
1540{
1541 PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE);
1542 RT_NOREF(pvUser, cb, offPort);
1543
1544#ifndef DEBUG_SB16_MOST
1545 if (pThis->mixer_nreg != 0x82)
1546 LogFlowFunc(("sb16IoPortMixerRead[%#x] -> %#x\n", pThis->mixer_nreg, pThis->mixer_regs[pThis->mixer_nreg]));
1547#else
1548 LogFlowFunc(("sb16IoPortMixerRead[%#x] -> %#x\n", pThis->mixer_nreg, pThis->mixer_regs[pThis->mixer_nreg]));
1549#endif
1550 *pu32 = pThis->mixer_regs[pThis->mixer_nreg];
1551 return VINF_SUCCESS;
1552}
1553
1554
1555/*********************************************************************************************************************************
1556* DMA handling *
1557*********************************************************************************************************************************/
1558
1559/**
1560 * Worker for sb16DMARead.
1561 */
1562
1563/**
1564 * @callback_method_impl{FNDMATRANSFERHANDLER,
1565 * Worker callback for both DMA channels.}
1566 */
1567static DECLCALLBACK(uint32_t) sb16DMARead(PPDMDEVINS pDevIns, void *pvUser, unsigned uChannel, uint32_t off, uint32_t cb)
1568
1569{
1570 PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE);
1571 AssertPtr(pThis);
1572 PSB16STREAM pStream = (PSB16STREAM)pvUser;
1573 AssertPtr(pStream);
1574
1575 int till, copy, free;
1576
1577 if (pStream->cbDmaBlockSize <= 0)
1578 {
1579 LogFlowFunc(("invalid block size=%d uChannel=%d off=%d cb=%d\n", pStream->cbDmaBlockSize, uChannel, off, cb));
1580 return off;
1581 }
1582
1583 if (pStream->cbDmaLeft < 0)
1584 pStream->cbDmaLeft = pStream->cbDmaBlockSize;
1585
1586 free = cb;
1587
1588 copy = free;
1589 till = pStream->cbDmaLeft;
1590
1591 Log4Func(("pos=%d %d, till=%d, len=%d\n", off, free, till, cb));
1592
1593 if (copy >= till)
1594 {
1595 if (0 == pStream->dma_auto)
1596 {
1597 copy = till;
1598 }
1599 else
1600 {
1601 if (copy >= till + pStream->cbDmaBlockSize)
1602 copy = till; /* Make sure we won't skip IRQs. */
1603 }
1604 }
1605
1606 STAM_COUNTER_ADD(&pThis->StatBytesRead, copy);
1607
1608 uint32_t written = 0; /* Shut up GCC. */
1609 int rc = sb16StreamDoDmaOutput(pThis, pStream, uChannel, off, cb, copy, &written);
1610 AssertRC(rc);
1611
1612 /** @todo Convert the rest to uin32_t / size_t. */
1613 off = (off + (int)written) % cb;
1614 pStream->cbDmaLeft -= (int)written; /** @todo r=andy left_till_irq can be < 0. Correct? Revisit this. */
1615
1616 Log3Func(("pos %d/%d, free=%d, till=%d, copy=%d, written=%RU32, block_size=%d\n",
1617 off, cb, free, pStream->cbDmaLeft, copy, copy, pStream->cbDmaBlockSize));
1618
1619 if (pStream->cbDmaLeft <= 0)
1620 {
1621 pThis->mixer_regs[0x82] |= (uChannel & 4) ? 2 : 1;
1622
1623 PDMDevHlpISASetIrq(pThis->pDevInsR3, pStream->HwCfgRuntime.uIrq, 1);
1624
1625 if (0 == pStream->dma_auto) /** @todo r=andy BUGBUG Why do we first assert the IRQ if dma_auto is 0? Revisit this. */
1626 {
1627 sb16StreamControl(pDevIns, pThis, pStream, false /* fRun */);
1628 sb16SpeakerControl(pThis, false /* fOn */);
1629 }
1630 }
1631
1632 while (pStream->cbDmaLeft <= 0)
1633 pStream->cbDmaLeft += pStream->cbDmaBlockSize;
1634
1635 return off;
1636}
1637
1638
1639/*********************************************************************************************************************************
1640* Timer-related code *
1641*********************************************************************************************************************************/
1642
1643/**
1644 * @callback_method_impl{PFNTMTIMERDEV}
1645 */
1646static DECLCALLBACK(void) sb16TimerIRQ(PPDMDEVINS pDevIns, TMTIMERHANDLE hTimer, void *pvUser)
1647{
1648 PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE);
1649 RT_NOREF(hTimer, pThis);
1650
1651 PSB16STREAM pStream = (PSB16STREAM)pvUser;
1652 AssertPtrReturnVoid(pStream);
1653
1654 LogFlowFuncEnter();
1655
1656 pStream->can_write = 1;
1657 PDMDevHlpISASetIrq(pDevIns, pStream->HwCfgRuntime.uIrq, 1);
1658}
1659
1660/**
1661 * Sets the stream's I/O timer to a new expiration time.
1662 *
1663 * @param pDevIns The device instance.
1664 * @param pStream SB16 stream to set timer for.
1665 * @param cTicksToDeadline The number of ticks to the new deadline.
1666 */
1667DECLINLINE(void) sb16TimerSet(PPDMDEVINS pDevIns, PSB16STREAM pStream, uint64_t cTicksToDeadline)
1668{
1669 int rc = PDMDevHlpTimerSetRelative(pDevIns, pStream->hTimerIO, cTicksToDeadline, NULL /*pu64Now*/);
1670 AssertRC(rc);
1671}
1672
1673/**
1674 * @callback_method_impl{FNTMTIMERDEV}
1675 */
1676static DECLCALLBACK(void) sb16TimerIO(PPDMDEVINS pDevIns, TMTIMERHANDLE hTimer, void *pvUser)
1677{
1678 PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE);
1679 STAM_PROFILE_START(&pThis->StatTimerIO, a);
1680
1681 PSB16STREAM pStream = (PSB16STREAM)pvUser;
1682 AssertPtrReturnVoid(pStream);
1683 AssertReturnVoid(hTimer == pStream->hTimerIO);
1684
1685 const uint64_t cTicksNow = PDMDevHlpTimerGet(pDevIns, pStream->hTimerIO);
1686
1687 pStream->tsTimerIO = cTicksNow;
1688
1689 PAUDMIXSINK pSink = sb16StreamIndexToSink(pThis, pStream->uIdx);
1690 AssertPtrReturnVoid(pSink);
1691
1692 const bool fSinkActive = AudioMixerSinkIsActive(pSink);
1693
1694 LogFlowFunc(("fSinkActive=%RTbool\n", fSinkActive));
1695
1696 /* Schedule the next transfer. */
1697 PDMDevHlpDMASchedule(pDevIns);
1698
1699 if (fSinkActive)
1700 {
1701 /** @todo adjust cTicks down by now much cbOutMin represents. */
1702 sb16TimerSet(pDevIns, pStream, pStream->cTicksTimerIOInterval);
1703 }
1704
1705 AudioMixerSinkSignalUpdateJob(pSink);
1706
1707 STAM_PROFILE_STOP(&pThis->StatTimerIO, a);
1708}
1709
1710
1711/*********************************************************************************************************************************
1712* LUN (driver) management *
1713*********************************************************************************************************************************/
1714
1715/**
1716 * Retrieves a specific driver stream of a SB16 driver.
1717 *
1718 * @returns Pointer to driver stream if found, or NULL if not found.
1719 * @param pDrv Driver to retrieve driver stream for.
1720 * @param enmDir Stream direction to retrieve.
1721 * @param enmPath Stream destination / source to retrieve.
1722 */
1723static PSB16DRIVERSTREAM sb16GetDrvStream(PSB16DRIVER pDrv, PDMAUDIODIR enmDir, PDMAUDIOPATH enmPath)
1724{
1725 PSB16DRIVERSTREAM pDrvStream = NULL;
1726
1727 if (enmDir == PDMAUDIODIR_OUT)
1728 {
1729 LogFunc(("enmPath=%d\n", enmPath));
1730
1731 switch (enmPath)
1732 {
1733 case PDMAUDIOPATH_OUT_FRONT:
1734 pDrvStream = &pDrv->Out;
1735 break;
1736 default:
1737 AssertFailed();
1738 break;
1739 }
1740 }
1741 else
1742 Assert(enmDir == PDMAUDIODIR_IN /** @todo Recording not implemented yet. */);
1743
1744 return pDrvStream;
1745}
1746
1747/**
1748 * Adds a driver stream to a specific mixer sink.
1749 *
1750 * @returns VBox status code.
1751 * @param pDevIns The device instance.
1752 * @param pMixSink Mixer sink to add driver stream to.
1753 * @param pCfg Stream configuration to use.
1754 * @param pDrv Driver stream to add.
1755 */
1756static int sb16AddDrvStream(PPDMDEVINS pDevIns, PAUDMIXSINK pMixSink, PCPDMAUDIOSTREAMCFG pCfg, PSB16DRIVER pDrv)
1757{
1758 AssertReturn(pCfg->enmDir == PDMAUDIODIR_OUT, VERR_NOT_IMPLEMENTED); /* We don't support recording for SB16 so far. */
1759 LogFunc(("[LUN#%RU8] %s\n", pDrv->uLUN, pCfg->szName));
1760
1761 int rc;
1762 PSB16DRIVERSTREAM pDrvStream = sb16GetDrvStream(pDrv, pCfg->enmDir, pCfg->enmPath);
1763 if (pDrvStream)
1764 {
1765 AssertMsg(pDrvStream->pMixStrm == NULL, ("[LUN#%RU8] Driver stream already present when it must not\n", pDrv->uLUN));
1766
1767 PAUDMIXSTREAM pMixStrm;
1768 rc = AudioMixerSinkCreateStream(pMixSink, pDrv->pConnector, pCfg, pDevIns, &pMixStrm);
1769 LogFlowFunc(("LUN#%RU8: Created stream \"%s\" for sink, rc=%Rrc\n", pDrv->uLUN, pCfg->szName, rc));
1770 if (RT_SUCCESS(rc))
1771 {
1772 rc = AudioMixerSinkAddStream(pMixSink, pMixStrm);
1773 LogFlowFunc(("LUN#%RU8: Added stream \"%s\" to sink, rc=%Rrc\n", pDrv->uLUN, pCfg->szName, rc));
1774 if (RT_SUCCESS(rc))
1775 pDrvStream->pMixStrm = pMixStrm;
1776 else
1777 AudioMixerStreamDestroy(pMixStrm, pDevIns, true /*fImmediate*/);
1778 }
1779 }
1780 else
1781 rc = VERR_INVALID_PARAMETER;
1782
1783 LogFlowFuncLeaveRC(rc);
1784 return rc;
1785}
1786
1787/**
1788 * Adds all current driver streams to a specific mixer sink.
1789 *
1790 * @returns VBox status code.
1791 * @param pDevIns The device instance.
1792 * @param pThis The SB16 state.
1793 * @param pMixSink Mixer sink to add stream to.
1794 * @param pCfg Stream configuration to use.
1795 */
1796static int sb16AddDrvStreams(PPDMDEVINS pDevIns, PSB16STATE pThis, PAUDMIXSINK pMixSink, PCPDMAUDIOSTREAMCFG pCfg)
1797{
1798 AssertPtrReturn(pMixSink, VERR_INVALID_POINTER);
1799
1800 int rc;
1801 if (AudioHlpStreamCfgIsValid(pCfg))
1802 {
1803 rc = AudioMixerSinkSetFormat(pMixSink, &pCfg->Props, pCfg->Device.cMsSchedulingHint);
1804 if (RT_SUCCESS(rc))
1805 {
1806 PSB16DRIVER pDrv;
1807 RTListForEach(&pThis->lstDrv, pDrv, SB16DRIVER, Node)
1808 {
1809 int rc2 = sb16AddDrvStream(pDevIns, pMixSink, pCfg, pDrv);
1810 if (RT_FAILURE(rc2))
1811 LogFunc(("Attaching stream failed with %Rrc\n", rc2));
1812
1813 /* Do not pass failure to rc here, as there might be drivers which aren't
1814 * configured / ready yet. */
1815 }
1816 }
1817 }
1818 else
1819 rc = VERR_INVALID_PARAMETER;
1820
1821 LogFlowFuncLeaveRC(rc);
1822 return rc;
1823}
1824
1825/**
1826 * Removes a driver stream from a specific mixer sink.
1827 *
1828 * @param pDevIns The device instance.
1829 * @param pMixSink Mixer sink to remove audio streams from.
1830 * @param enmDir Stream direction to remove.
1831 * @param enmPath Stream destination / source to remove.
1832 * @param pDrv Driver stream to remove.
1833 */
1834static void sb16RemoveDrvStream(PPDMDEVINS pDevIns, PAUDMIXSINK pMixSink, PDMAUDIODIR enmDir,
1835 PDMAUDIOPATH enmPath, PSB16DRIVER pDrv)
1836{
1837 PSB16DRIVERSTREAM pDrvStream = sb16GetDrvStream(pDrv, enmDir, enmPath);
1838 if (pDrvStream)
1839 {
1840 if (pDrvStream->pMixStrm)
1841 {
1842 LogFlowFunc(("[LUN#%RU8]\n", pDrv->uLUN));
1843
1844 AudioMixerSinkRemoveStream(pMixSink, pDrvStream->pMixStrm);
1845
1846 AudioMixerStreamDestroy(pDrvStream->pMixStrm, pDevIns, false /*fImmediate*/);
1847 pDrvStream->pMixStrm = NULL;
1848 }
1849 }
1850}
1851
1852/**
1853 * Removes all driver streams from a specific mixer sink.
1854 *
1855 * @param pDevIns The device instance.
1856 * @param pThis The SB16 state.
1857 * @param pMixSink Mixer sink to remove audio streams from.
1858 * @param enmDir Stream direction to remove.
1859 * @param enmPath Stream destination / source to remove.
1860 */
1861static void sb16RemoveDrvStreams(PPDMDEVINS pDevIns, PSB16STATE pThis, PAUDMIXSINK pMixSink,
1862 PDMAUDIODIR enmDir, PDMAUDIOPATH enmPath)
1863{
1864 AssertPtrReturnVoid(pMixSink);
1865
1866 PSB16DRIVER pDrv;
1867 RTListForEach(&pThis->lstDrv, pDrv, SB16DRIVER, Node)
1868 {
1869 sb16RemoveDrvStream(pDevIns, pMixSink, enmDir, enmPath, pDrv);
1870 }
1871}
1872
1873/**
1874 * Adds a specific SB16 driver to the driver chain.
1875 *
1876 * @returns VBox status code.
1877 * @param pDevIns The device instance.
1878 * @param pThis The SB16 device state.
1879 * @param pDrv The SB16 driver to add.
1880 */
1881static int sb16AddDrv(PPDMDEVINS pDevIns, PSB16STATE pThis, PSB16DRIVER pDrv)
1882{
1883 int rc = VINF_SUCCESS;
1884
1885 for (unsigned i = 0; i < SB16_MAX_STREAMS; i++)
1886 {
1887 if (AudioHlpStreamCfgIsValid(&pThis->aStreams[i].Cfg))
1888 {
1889 int rc2 = sb16AddDrvStream(pDevIns, sb16StreamIndexToSink(pThis, pThis->aStreams[i].uIdx),
1890 &pThis->aStreams[i].Cfg, pDrv);
1891 if (RT_SUCCESS(rc))
1892 rc = rc2;
1893 }
1894 }
1895
1896 return rc;
1897}
1898
1899/**
1900 * Removes a specific SB16 driver from the driver chain and destroys its
1901 * associated streams.
1902 *
1903 * This is only used by sb16Detach.
1904 *
1905 * @param pDevIns The device instance.
1906 * @param pThis The SB16 device state.
1907 * @param pDrv SB16 driver to remove.
1908 */
1909static void sb16RemoveDrv(PPDMDEVINS pDevIns, PSB16STATE pThis, PSB16DRIVER pDrv)
1910{
1911 RT_NOREF(pDevIns);
1912
1913 /** @todo We only implement one single output (playback) stream at the moment. */
1914
1915 if (pDrv->Out.pMixStrm)
1916 {
1917 AudioMixerSinkRemoveStream(pThis->pSinkOut, pDrv->Out.pMixStrm);
1918 AudioMixerStreamDestroy(pDrv->Out.pMixStrm, pDevIns, true /*fImmediate*/);
1919 pDrv->Out.pMixStrm = NULL;
1920 }
1921
1922 RTListNodeRemove(&pDrv->Node);
1923}
1924
1925
1926/*********************************************************************************************************************************
1927* Stream handling *
1928*********************************************************************************************************************************/
1929
1930/**
1931 * Reads DMA output data into the internal DMA buffer.
1932 *
1933 * @returns VBox status code.
1934 * @param pStream The SB16 stream to read DMA output data from.
1935 * @param uDmaChan DMA channel to read from.
1936 * @param offDma DMA offset (in bytes) to start reading from.
1937 * @param cbDma DMA area size in bytes.
1938 * @param cbToRead How much bytes to read in total.
1939 * @param pcbRead Where to return the DMA bytes read.
1940 *
1941 * @thread µEMT
1942 */
1943static int sb16StreamDoDmaOutput(PSB16STATE pThis, PSB16STREAM pStream, int uDmaChan, uint32_t offDma, uint32_t cbDma,
1944 uint32_t cbToRead, uint32_t *pcbRead)
1945{
1946 uint32_t cbFree = (uint32_t)RTCircBufFree(pStream->State.pCircBuf);
1947 //Assert(cbToRead <= cbFree); /** @todo Add statistics for overflows. */
1948 cbToRead = RT_MIN(cbToRead, cbFree);
1949
1950 sb16StreamLock(pStream);
1951
1952 uint32_t cbReadTotal = 0;
1953 while (cbToRead)
1954 {
1955 void *pv = NULL;
1956 size_t cb = 0;
1957 RTCircBufAcquireWriteBlock(pStream->State.pCircBuf, RT_MIN(cbDma - offDma, cbToRead), &pv, &cb);
1958
1959 uint32_t cbRead = 0;
1960 int rc = PDMDevHlpDMAReadMemory(pThis->pDevInsR3, uDmaChan, pv, offDma, (uint32_t)cb, &cbRead);
1961 if (RT_SUCCESS(rc))
1962 Assert(cbRead == cb);
1963 else
1964 {
1965 AssertMsgFailed(("Reading from DMA failed: %Rrc (cbReadTotal=%#x)\n", rc, cbReadTotal));
1966 RTCircBufReleaseWriteBlock(pStream->State.pCircBuf, 0);
1967 if (cbReadTotal > 0)
1968 break;
1969 *pcbRead = 0;
1970
1971 sb16StreamUnlock(pStream);
1972 return rc;
1973 }
1974
1975 if (RT_LIKELY(!pStream->Dbg.Runtime.pFileDMA))
1976 { /* likely */ }
1977 else
1978 AudioHlpFileWrite(pStream->Dbg.Runtime.pFileDMA, pv, cbRead);
1979
1980 Log3Func(("Writing %#RX32 bytes to DMA buffer\n", cbRead));
1981
1982 RTCircBufReleaseWriteBlock(pStream->State.pCircBuf, cbRead);
1983
1984 Log3Func(("Now %#RX32 bytes in buffer\n", RTCircBufUsed(pStream->State.pCircBuf)));
1985
1986 Assert(cbToRead >= cbRead);
1987 pStream->State.offWrite += cbRead;
1988 offDma = (offDma + cbRead) % cbDma;
1989 cbReadTotal += cbRead;
1990 cbToRead -= cbRead;
1991 }
1992
1993 *pcbRead = cbReadTotal;
1994
1995 /* Update buffer stats. */
1996 pStream->State.StatDmaBufUsed = (uint32_t)RTCircBufUsed(pStream->State.pCircBuf);
1997
1998 Log3Func(("[SD%RU8] cbCircBufUsed=%#RX64\n", pStream->uIdx, RTCircBufUsed(pStream->State.pCircBuf)));
1999
2000 sb16StreamUnlock(pStream);
2001 return VINF_SUCCESS;
2002}
2003
2004/**
2005 * Enables or disables a SB16 audio stream.
2006 *
2007 * @returns VBox status code.
2008 * @param pThis The SB16 state.
2009 * @param pStream The SB16 stream to enable or disable.
2010 * @param fEnable Whether to enable or disable the stream.
2011 * @param fForce Whether to force re-opening the stream or not.
2012 * Otherwise re-opening only will happen if the PCM properties have changed.
2013 */
2014static int sb16StreamEnable(PSB16STATE pThis, PSB16STREAM pStream, bool fEnable, bool fForce)
2015{
2016 if ( !fForce
2017 && fEnable == pStream->State.fEnabled)
2018 return VINF_SUCCESS;
2019
2020 LogFlowFunc(("fEnable=%RTbool, fForce=%RTbool, fStreamEnabled=%RTbool\n", fEnable, fForce, pStream->State.fEnabled));
2021
2022 PAUDMIXSINK pSink = sb16StreamIndexToSink(pThis, pStream->uIdx);
2023 AssertPtrReturn(pSink, VERR_INTERNAL_ERROR_2);
2024
2025 /* We only need to register the AIO update job the first time around as the requence doesn't change. */
2026 int rc;
2027 if (fEnable && !pStream->State.fRegisteredAsyncUpdateJob)
2028 {
2029 rc = AudioMixerSinkAddUpdateJob(pSink, sb16StreamUpdateAsyncIoJob, pStream, RT_MS_1SEC / pStream->uTimerHz);
2030 AssertRC(rc);
2031 pStream->State.fRegisteredAsyncUpdateJob = RT_SUCCESS(rc) || rc == VERR_ALREADY_EXISTS;
2032 }
2033
2034 /* Tell the mixer. */
2035 if (fEnable)
2036 {
2037 rc = AudioMixerSinkStart(pSink);
2038 AssertRCReturn(rc, rc);
2039 }
2040 else
2041 {
2042 /* We need to lock the stream here in order to synchronize the amount of bytes we have to drain
2043 * here with the mixer's async I/O thread. See @bugref{10354}. */
2044 sb16StreamLock(pStream);
2045
2046 uint32_t const cbToDrain = pStream->State.pCircBuf ? (uint32_t)RTCircBufUsed(pStream->State.pCircBuf) : 0;
2047 Log3Func(("cbToDrain=%#RX32\n", cbToDrain));
2048
2049 sb16StreamUnlock(pStream);
2050
2051 rc = AudioMixerSinkDrainAndStop(pSink, cbToDrain);
2052 AssertRCReturn(rc, rc);
2053
2054
2055 }
2056
2057 pStream->State.fEnabled = fEnable;
2058
2059 return rc;
2060}
2061
2062/**
2063 * Retrieves the audio mixer sink of a corresponding SB16 stream.
2064 *
2065 * @returns Pointer to audio mixer sink if found, or NULL if not found / invalid.
2066 * @param pThis The SB16 state.
2067 * @param uIdx Stream index to get audio mixer sink for.
2068 */
2069DECLINLINE(PAUDMIXSINK) sb16StreamIndexToSink(PSB16STATE pThis, uint8_t uIdx)
2070{
2071 AssertReturn(uIdx <= SB16_MAX_STREAMS, NULL);
2072
2073 /* Dead simple for now; make this more sophisticated if we have more stuff to cover. */
2074 if (uIdx == SB16_IDX_OUT)
2075 return pThis->pSinkOut; /* Can be NULL if not configured / set up yet. */
2076
2077 AssertMsgFailed(("No sink attached (yet) for index %RU8\n", uIdx));
2078 return NULL;
2079}
2080
2081/**
2082 * Returns the audio direction of a specified stream descriptor.
2083 *
2084 * @returns Audio direction.
2085 * @param uIdx Stream index to get audio direction for.
2086 */
2087DECLINLINE(PDMAUDIODIR) sb16GetDirFromIndex(uint8_t uIdx)
2088{
2089 AssertReturn(uIdx <= SB16_MAX_STREAMS, PDMAUDIODIR_INVALID);
2090
2091 /* Dead simple for now; make this more sophisticated if we have more stuff to cover. */
2092 if (uIdx == SB16_IDX_OUT)
2093 return PDMAUDIODIR_OUT;
2094
2095 return PDMAUDIODIR_INVALID;
2096}
2097
2098/**
2099 * Creates a SB16 audio stream.
2100 *
2101 * @returns VBox status code.
2102 * @param pThis The SB16 state.
2103 * @param pStream The SB16 stream to create.
2104 * @param uIdx Stream index to assign.
2105 */
2106static int sb16StreamCreate(PSB16STATE pThis, PSB16STREAM pStream, uint8_t uIdx)
2107{
2108 LogFlowFuncEnter();
2109
2110 int rc = RTCritSectInit(&pStream->State.CritSect);
2111 AssertRCReturn(rc, rc);
2112
2113 pStream->Dbg.Runtime.fEnabled = pThis->Dbg.fEnabled;
2114
2115 if (RT_LIKELY(!pStream->Dbg.Runtime.fEnabled))
2116 { /* likely */ }
2117 else
2118 {
2119 int rc2 = AudioHlpFileCreateF(&pStream->Dbg.Runtime.pFileDMA, AUDIOHLPFILE_FLAGS_NONE, AUDIOHLPFILETYPE_WAV,
2120 pThis->Dbg.pszOutPath, AUDIOHLPFILENAME_FLAGS_NONE, 0 /*uInstance*/,
2121 sb16GetDirFromIndex(pStream->uIdx) == PDMAUDIODIR_IN
2122 ? "sb16StreamWriteSD%RU8" : "sb16StreamReadSD%RU8", pStream->uIdx);
2123 AssertRC(rc2);
2124
2125 /* Delete stale debugging files from a former run. */
2126 AudioHlpFileDelete(pStream->Dbg.Runtime.pFileDMA);
2127 }
2128
2129 pStream->uIdx = uIdx;
2130
2131 return VINF_SUCCESS;
2132}
2133
2134/**
2135 * Destroys a SB16 audio stream.
2136 *
2137 * @returns VBox status code.
2138 * @param pDevIns The device instance.
2139 * @param pThis The SB16 state.
2140 * @param pStream The SB16 stream to destroy.
2141 */
2142static int sb16StreamDestroy(PPDMDEVINS pDevIns, PSB16STATE pThis, PSB16STREAM pStream)
2143{
2144 LogFlowFuncEnter();
2145
2146 sb16StreamClose(pDevIns, pThis, pStream);
2147
2148 if (pStream->State.fRegisteredAsyncUpdateJob)
2149 {
2150 PAUDMIXSINK pSink = sb16StreamIndexToSink(pThis, pStream->uIdx);
2151 if (pSink)
2152 AudioMixerSinkRemoveUpdateJob(pSink, sb16StreamUpdateAsyncIoJob, pStream);
2153 pStream->State.fRegisteredAsyncUpdateJob = false;
2154 }
2155
2156 int rc2 = RTCritSectDelete(&pStream->State.CritSect);
2157 AssertRC(rc2);
2158
2159 if (pStream->State.pCircBuf)
2160 {
2161 RTCircBufDestroy(pStream->State.pCircBuf);
2162 pStream->State.pCircBuf = NULL;
2163 }
2164
2165 if (RT_LIKELY(!pStream->Dbg.Runtime.fEnabled))
2166 { /* likely */ }
2167 else
2168 {
2169 AudioHlpFileDestroy(pStream->Dbg.Runtime.pFileDMA);
2170 pStream->Dbg.Runtime.pFileDMA = NULL;
2171 }
2172
2173 pStream->uIdx = UINT8_MAX;
2174
2175 return VINF_SUCCESS;
2176}
2177
2178/**
2179 * Resets a SB16 stream.
2180 *
2181 * @param pThis The SB16 state.
2182 * @param pStream The SB16 stream to reset.
2183 */
2184static void sb16StreamReset(PSB16STATE pThis, PSB16STREAM pStream)
2185{
2186 LogFlowFuncEnter();
2187
2188 PDMDevHlpISASetIrq(pThis->pDevInsR3, pStream->HwCfgRuntime.uIrq, 0);
2189 if (pStream->dma_auto)
2190 {
2191 PDMDevHlpISASetIrq(pThis->pDevInsR3, pStream->HwCfgRuntime.uIrq, 1);
2192 PDMDevHlpISASetIrq(pThis->pDevInsR3, pStream->HwCfgRuntime.uIrq, 0);
2193
2194 pStream->dma_auto = 0;
2195 }
2196
2197 sb16StreamControl(pThis->pDevInsR3, pThis, pStream, false /* fRun */);
2198 sb16StreamEnable(pThis, pStream, false /* fEnable */, false /* fForce */);
2199
2200 switch (pStream->uIdx)
2201 {
2202 case SB16_IDX_OUT:
2203 {
2204 pStream->Cfg.enmDir = PDMAUDIODIR_OUT;
2205 pStream->Cfg.enmPath = PDMAUDIOPATH_OUT_FRONT;
2206
2207 PDMAudioPropsInit(&pStream->Cfg.Props, 1 /* 8-bit */, false /* fSigned */, 1 /* Mono */, 11025 /* uHz */);
2208 RTStrCopy(pStream->Cfg.szName, sizeof(pStream->Cfg.szName), "Output");
2209 break;
2210 }
2211
2212 default:
2213 AssertFailed();
2214 break;
2215 }
2216
2217 pStream->cbDmaLeft = 0;
2218 pStream->cbDmaBlockSize = 0;
2219 pStream->can_write = 1; /** @ŧodo r=andy BUGBUG Figure out why we (still) need this. */
2220
2221 /** @todo Also reset corresponding DSP values here? */
2222}
2223
2224/**
2225 * Locks a SB16 stream for serialized access.
2226 *
2227 * @param pStream The SB16 stream to lock.
2228 */
2229DECLINLINE(void) sb16StreamLock(PSB16STREAM pStream)
2230{
2231 int rc2 = RTCritSectEnter(&pStream->State.CritSect);
2232 AssertRC(rc2);
2233}
2234
2235/**
2236 * Unlocks a formerly locked SB16 stream.
2237 *
2238 * @param pStream The SB16 stream to unlock.
2239 */
2240DECLINLINE(void) sb16StreamUnlock(PSB16STREAM pStream)
2241{
2242 int rc2 = RTCritSectLeave(&pStream->State.CritSect);
2243 AssertRC(rc2);
2244}
2245
2246/**
2247 * Opens a SB16 stream with its current mixer settings.
2248 *
2249 * @returns VBox status code.
2250 * @param pDevIns The device instance.
2251 * @param pThis The SB16 device state.
2252 * @param pStream The SB16 stream to open.
2253 *
2254 * @note This currently only supports the one and only output stream.
2255 */
2256static int sb16StreamOpen(PPDMDEVINS pDevIns, PSB16STATE pThis, PSB16STREAM pStream)
2257{
2258 LogFlowFuncEnter();
2259 AssertLogRelReturn(PDMAudioPropsAreValid(&pStream->Cfg.Props), VERR_INTERNAL_ERROR_5);
2260
2261 switch (pStream->uIdx)
2262 {
2263 case SB16_IDX_OUT:
2264 pStream->Cfg.enmDir = PDMAUDIODIR_OUT;
2265 pStream->Cfg.enmPath = PDMAUDIOPATH_OUT_FRONT;
2266
2267 RTStrCopy(pStream->Cfg.szName, sizeof(pStream->Cfg.szName), "Output");
2268 break;
2269
2270 default:
2271 AssertFailed();
2272 break;
2273 }
2274
2275 LogRel2(("SB16: (Re-)Opening stream '%s' (%RU32Hz, %RU8 channels, %s%RU8)\n", pStream->Cfg.szName, pStream->Cfg.Props.uHz,
2276 PDMAudioPropsChannels(&pStream->Cfg.Props), pStream->Cfg.Props.fSigned ? "S" : "U",
2277 PDMAudioPropsSampleBits(&pStream->Cfg.Props)));
2278
2279 /** @todo r=bird: two DMA periods is probably too little. */
2280 const uint32_t cbCircBuf = PDMAudioPropsMilliToBytes(&pStream->Cfg.Props,
2281 (RT_MS_1SEC / pStream->uTimerHz) * 2 /* Use double buffering here */);
2282 int rc = VINF_SUCCESS;
2283
2284 /**
2285 * Note: Only re-create the DMA buffer if the size actually has changed.
2286 *
2287 * Otherwise do *not* reset the stream's circular buffer here, as the audio mixer still relies on
2288 * previously announced DMA data (via AudioMixerSinkDrainAndStop()) and processes it asynchronously.
2289 * Resetting the buffer here will cause a race condition. See @bugref{10354}. */
2290 if (pStream->State.StatDmaBufSize != cbCircBuf)
2291 {
2292 Log3Func(("Re-creating ring buffer (%#RX32 -> %#RX32)\n", pStream->State.StatDmaBufSize, cbCircBuf));
2293
2294 /* (Re-)create the stream's internal ring buffer. */
2295 if (pStream->State.pCircBuf)
2296 {
2297 RTCircBufDestroy(pStream->State.pCircBuf);
2298 pStream->State.pCircBuf = NULL;
2299 }
2300
2301 rc = RTCircBufCreate(&pStream->State.pCircBuf, cbCircBuf);
2302 AssertRCReturn(rc, rc);
2303 pStream->State.StatDmaBufSize = (uint32_t)RTCircBufSize(pStream->State.pCircBuf);
2304
2305 /* Set scheduling hint. */
2306 pStream->Cfg.Device.cMsSchedulingHint = RT_MS_1SEC / RT_MIN(pStream->uTimerHz, 1);
2307
2308 PAUDMIXSINK pMixerSink = sb16StreamIndexToSink(pThis, pStream->uIdx);
2309 AssertPtrReturn(pMixerSink, VERR_INVALID_POINTER);
2310
2311 sb16RemoveDrvStreams(pDevIns, pThis,
2312 sb16StreamIndexToSink(pThis, pStream->uIdx), pStream->Cfg.enmDir, pStream->Cfg.enmPath);
2313
2314 rc = sb16AddDrvStreams(pDevIns, pThis, pMixerSink, &pStream->Cfg);
2315 if (RT_SUCCESS(rc))
2316 {
2317 if (RT_LIKELY(!pStream->Dbg.Runtime.fEnabled))
2318 { /* likely */ }
2319 else
2320 {
2321 /* Make sure to close + delete a former debug file, as the PCM format has changed (e.g. U8 -> S16). */
2322 if (AudioHlpFileIsOpen(pStream->Dbg.Runtime.pFileDMA))
2323 {
2324 AudioHlpFileClose(pStream->Dbg.Runtime.pFileDMA);
2325 AudioHlpFileDelete(pStream->Dbg.Runtime.pFileDMA);
2326 }
2327
2328 int rc2 = AudioHlpFileOpen(pStream->Dbg.Runtime.pFileDMA, AUDIOHLPFILE_DEFAULT_OPEN_FLAGS,
2329 &pStream->Cfg.Props);
2330 AssertRC(rc2);
2331 }
2332 }
2333 }
2334
2335 LogFlowFuncLeaveRC(rc);
2336 return rc;
2337}
2338
2339/**
2340 * Closes a SB16 stream.
2341 *
2342 * @param pDevIns The device instance.
2343 * @param pThis SB16 state.
2344 * @param pStream The SB16 stream to close.
2345 */
2346static void sb16StreamClose(PPDMDEVINS pDevIns, PSB16STATE pThis, PSB16STREAM pStream)
2347{
2348 RT_NOREF(pDevIns, pThis, pStream);
2349
2350 LogFlowFuncEnter();
2351
2352 /* Nothing to do in here right now. */
2353}
2354
2355static void sb16StreamTransferScheduleNext(PSB16STATE pThis, PSB16STREAM pStream, uint32_t cbBytes)
2356{
2357 RT_NOREF(pStream);
2358
2359 uint64_t const uTimerHz = PDMDevHlpTimerGetFreq(pThis->pDevInsR3, pThis->hTimerIRQ);
2360
2361 const uint64_t usBytes = PDMAudioPropsBytesToMicro(&pStream->Cfg.Props, cbBytes);
2362 const uint64_t cTransferTicks = PDMDevHlpTimerFromMicro(pThis->pDevInsR3, pThis->hTimerIRQ, usBytes);
2363
2364 LogFlowFunc(("%RU32 bytes -> %RU64 ticks\n", cbBytes, cTransferTicks));
2365
2366 if (cTransferTicks < uTimerHz / 1024) /** @todo Explain this. */
2367 {
2368 LogFlowFunc(("IRQ\n"));
2369 PDMDevHlpISASetIrq(pThis->pDevInsR3, pStream->HwCfgRuntime.uIrq, 1);
2370 }
2371 else
2372 {
2373 LogFlowFunc(("Scheduled\n"));
2374 PDMDevHlpTimerSetRelative(pThis->pDevInsR3, pThis->hTimerIRQ, cTransferTicks, NULL);
2375 }
2376}
2377
2378
2379/**
2380 * Output streams: Pushes data to the mixer.
2381 *
2382 * @param pStream The SB16 stream.
2383 * @param pSink The mixer sink to push to.
2384 *
2385 * @thread MixAIO-n
2386 *
2387 * @note Takes the stream's lock.
2388 */
2389static void sb16StreamPushToMixer(PSB16STREAM pStream, PAUDMIXSINK pSink)
2390{
2391 sb16StreamLock(pStream);
2392
2393#ifdef LOG_ENABLED
2394 uint64_t const offReadOld = pStream->State.offRead;
2395 Log3Func(("[SD%RU8] cbCircBufUsed=%#RX64\n", pStream->uIdx, RTCircBufUsed(pStream->State.pCircBuf)));
2396#endif
2397 pStream->State.offRead = AudioMixerSinkTransferFromCircBuf(pSink,
2398 pStream->State.pCircBuf,
2399 pStream->State.offRead,
2400 pStream->uIdx,
2401 /** @todo pStream->Dbg.Runtime.fEnabled
2402 ? pStream->Dbg.Runtime.pFileStream :*/ NULL);
2403
2404 Log3Func(("[SD%RU8] transferred=%#RX64 bytes -> @%#RX64 (cbCircBufUsed=%#RX64)\n", pStream->uIdx,
2405 pStream->State.offRead - offReadOld, pStream->State.offRead, RTCircBufUsed(pStream->State.pCircBuf)));
2406
2407 /* Update buffer stats. */
2408 pStream->State.StatDmaBufUsed = (uint32_t)RTCircBufUsed(pStream->State.pCircBuf);
2409
2410 sb16StreamUnlock(pStream);
2411}
2412
2413
2414/**
2415 * @callback_method_impl{FNAUDMIXSINKUPDATE}
2416 *
2417 * For output streams this moves data from the internal DMA buffer (in which
2418 * sb16StreamDoDmaOutput put it), thru the mixer and to the various backend
2419 * audio devices.
2420 *
2421 * @thread MixAIO-n
2422 */
2423static DECLCALLBACK(void) sb16StreamUpdateAsyncIoJob(PPDMDEVINS pDevIns, PAUDMIXSINK pSink, void *pvUser)
2424{
2425 PSB16STATE const pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE);
2426 PSB16STREAM const pStream = (PSB16STREAM)pvUser;
2427 Assert(pStream->uIdx == (uintptr_t)(pStream - &pThis->aStreams[0]));
2428 Assert(pSink == sb16StreamIndexToSink(pThis, pStream->uIdx));
2429 RT_NOREF(pThis);
2430
2431 /*
2432 * Output.
2433 */
2434 if (sb16GetDirFromIndex(pStream->uIdx) == PDMAUDIODIR_OUT)
2435 sb16StreamPushToMixer(pStream, pSink);
2436 /*
2437 * No input streams at present.
2438 */
2439 else
2440 AssertFailed();
2441}
2442
2443
2444/*********************************************************************************************************************************
2445* Saved state handling *
2446*********************************************************************************************************************************/
2447
2448/**
2449 * @callback_method_impl{FNSSMDEVLIVEEXEC}
2450 */
2451static DECLCALLBACK(int) sb16LiveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uPass)
2452{
2453 RT_NOREF(uPass);
2454
2455 PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE);
2456 PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
2457
2458 /** Currently the saved state only contains the one-and-only output stream. */
2459 PSB16STREAM pStream = &pThis->aStreams[SB16_IDX_OUT];
2460
2461 pHlp->pfnSSMPutS32(pSSM, pStream->HwCfgDefault.uIrq);
2462 pHlp->pfnSSMPutS32(pSSM, pStream->HwCfgDefault.uDmaChanLow);
2463 pHlp->pfnSSMPutS32(pSSM, pStream->HwCfgDefault.uDmaChanHigh);
2464 pHlp->pfnSSMPutS32(pSSM, pStream->HwCfgDefault.uPort);
2465 pHlp->pfnSSMPutS32(pSSM, pStream->HwCfgDefault.uVer);
2466 return VINF_SSM_DONT_CALL_AGAIN;
2467}
2468
2469/**
2470 * Worker for sb16SaveExec.
2471 */
2472static int sb16Save(PCPDMDEVHLPR3 pHlp, PSSMHANDLE pSSM, PSB16STATE pThis)
2473{
2474 /* The saved state only contains the one-and-only output stream. */
2475 PSB16STREAM pStream = &pThis->aStreams[SB16_IDX_OUT];
2476
2477 pHlp->pfnSSMPutS32(pSSM, pStream->HwCfgRuntime.uIrq);
2478 pHlp->pfnSSMPutS32(pSSM, pStream->HwCfgRuntime.uDmaChanLow);
2479 pHlp->pfnSSMPutS32(pSSM, pStream->HwCfgRuntime.uDmaChanHigh);
2480 pHlp->pfnSSMPutS32(pSSM, pStream->HwCfgRuntime.uPort);
2481 pHlp->pfnSSMPutS32(pSSM, pStream->HwCfgRuntime.uVer);
2482 pHlp->pfnSSMPutS32(pSSM, pThis->dsp_in_idx);
2483 pHlp->pfnSSMPutS32(pSSM, pThis->dsp_out_data_len);
2484
2485 pHlp->pfnSSMPutS32(pSSM, PDMAudioPropsChannels(&pStream->Cfg.Props) >= 2 ? 1 : 0);
2486 pHlp->pfnSSMPutS32(pSSM, PDMAudioPropsIsSigned(&pStream->Cfg.Props) ? 1 : 0);
2487 pHlp->pfnSSMPutS32(pSSM, PDMAudioPropsSampleBits(&pStream->Cfg.Props));
2488 pHlp->pfnSSMPutU32(pSSM, 0); /* Legacy; was PDMAUDIOFMT, unused now. */
2489
2490 pHlp->pfnSSMPutS32(pSSM, pStream->dma_auto);
2491 pHlp->pfnSSMPutS32(pSSM, pStream->cbDmaBlockSize);
2492 pHlp->pfnSSMPutS32(pSSM, pStream->fifo);
2493 pHlp->pfnSSMPutS32(pSSM, PDMAudioPropsHz(&pStream->Cfg.Props));
2494 pHlp->pfnSSMPutS32(pSSM, pStream->time_const);
2495 pHlp->pfnSSMPutS32(pSSM, 0); /* Legacy; was speaker control (on/off) for output stream. */
2496 pHlp->pfnSSMPutS32(pSSM, pThis->dsp_in_needed_bytes);
2497 pHlp->pfnSSMPutS32(pSSM, pThis->cmd);
2498 pHlp->pfnSSMPutS32(pSSM, pStream->fDmaUseHigh);
2499 pHlp->pfnSSMPutS32(pSSM, pThis->highspeed);
2500 pHlp->pfnSSMPutS32(pSSM, pStream->can_write);
2501 pHlp->pfnSSMPutS32(pSSM, pThis->v2x6);
2502
2503 pHlp->pfnSSMPutU8 (pSSM, pThis->csp_param);
2504 pHlp->pfnSSMPutU8 (pSSM, pThis->csp_value);
2505 pHlp->pfnSSMPutU8 (pSSM, pThis->csp_mode);
2506 pHlp->pfnSSMPutU8 (pSSM, pThis->csp_param); /* Bug compatible! */
2507 pHlp->pfnSSMPutMem(pSSM, pThis->csp_regs, 256);
2508 pHlp->pfnSSMPutU8 (pSSM, pThis->csp_index);
2509 pHlp->pfnSSMPutMem(pSSM, pThis->csp_reg83, 4);
2510 pHlp->pfnSSMPutS32(pSSM, pThis->csp_reg83r);
2511 pHlp->pfnSSMPutS32(pSSM, pThis->csp_reg83w);
2512
2513 pHlp->pfnSSMPutMem(pSSM, pThis->dsp_in_data, sizeof(pThis->dsp_in_data));
2514 pHlp->pfnSSMPutMem(pSSM, pThis->dsp_out_data, sizeof(pThis->dsp_out_data));
2515 pHlp->pfnSSMPutU8 (pSSM, pThis->test_reg);
2516 pHlp->pfnSSMPutU8 (pSSM, pThis->last_read_byte);
2517
2518 pHlp->pfnSSMPutS32(pSSM, pThis->nzero);
2519 pHlp->pfnSSMPutS32(pSSM, pStream->cbDmaLeft);
2520 pHlp->pfnSSMPutS32(pSSM, pStream->State.fEnabled ? 1 : 0);
2521 /* The stream's bitrate. Needed for backwards (legacy) compatibility. */
2522 pHlp->pfnSSMPutS32(pSSM, AudioHlpCalcBitrate(PDMAudioPropsSampleBits(&pThis->aStreams[SB16_IDX_OUT].Cfg.Props),
2523 PDMAudioPropsHz(&pThis->aStreams[SB16_IDX_OUT].Cfg.Props),
2524 PDMAudioPropsChannels(&pThis->aStreams[SB16_IDX_OUT].Cfg.Props)));
2525 /* Block size alignment, superfluous and thus not saved anymore. Needed for backwards (legacy) compatibility. */
2526 pHlp->pfnSSMPutS32(pSSM, 0);
2527
2528 pHlp->pfnSSMPutS32(pSSM, pThis->mixer_nreg);
2529 return pHlp->pfnSSMPutMem(pSSM, pThis->mixer_regs, 256);
2530}
2531
2532/**
2533 * @callback_method_impl{FNSSMDEVSAVEEXEC}
2534 */
2535static DECLCALLBACK(int) sb16SaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
2536{
2537 PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE);
2538 PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
2539
2540 sb16LiveExec(pDevIns, pSSM, 0);
2541 return sb16Save(pHlp, pSSM, pThis);
2542}
2543
2544/**
2545 * Worker for sb16LoadExec.
2546 */
2547static int sb16Load(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, PSB16STATE pThis)
2548{
2549 PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
2550 PSB16STREAM pStream = &pThis->aStreams[SB16_IDX_OUT]; /* The saved state only contains the one-and-only output stream. */
2551 int rc;
2552
2553 int32_t i32Tmp;
2554 pHlp->pfnSSMGetS32(pSSM, &i32Tmp);
2555 pStream->HwCfgRuntime.uIrq = i32Tmp; /* IRQ. */
2556 pHlp->pfnSSMGetS32(pSSM, &i32Tmp);
2557 pStream->HwCfgRuntime.uDmaChanLow = i32Tmp; /* Low (8-bit) DMA channel. */
2558 pHlp->pfnSSMGetS32(pSSM, &i32Tmp);
2559 pStream->HwCfgRuntime.uDmaChanHigh = i32Tmp; /* High (16-bit) DMA channel. */
2560 pHlp->pfnSSMGetS32(pSSM, &i32Tmp); /* Used I/O port. */
2561 pStream->HwCfgRuntime.uPort = i32Tmp;
2562 pHlp->pfnSSMGetS32(pSSM, &i32Tmp); /* DSP version running. */
2563 pStream->HwCfgRuntime.uVer = i32Tmp;
2564 pHlp->pfnSSMGetS32(pSSM, &pThis->dsp_in_idx);
2565 pHlp->pfnSSMGetS32(pSSM, &pThis->dsp_out_data_len);
2566
2567 rc = pHlp->pfnSSMGetS32(pSSM, &i32Tmp); /* Output stream: Number of channels. */
2568 AssertRCReturn(rc, rc);
2569 AssertReturn((uint32_t)i32Tmp <= 1, VERR_INVALID_PARAMETER); /* Paranoia. */
2570 if (i32Tmp) /* PDMAudioPropsSetChannels() will assert if channels are 0 (will be re-set on DMA run command). */
2571 PDMAudioPropsSetChannels(&pStream->Cfg.Props, (uint8_t)i32Tmp);
2572 pHlp->pfnSSMGetS32(pSSM, &i32Tmp); /* Output stream: Signed format bit. */
2573 pStream->Cfg.Props.fSigned = i32Tmp != 0;
2574 rc = pHlp->pfnSSMGetS32(pSSM, &i32Tmp); /* Output stream: Sample size in bits. */
2575 AssertRCReturn(rc, rc);
2576 if (i32Tmp) /* PDMAudioPropsSetSampleSize() will assert if sample size is 0 (will be re-set on DMA run command). */
2577 PDMAudioPropsSetSampleSize(&pStream->Cfg.Props, (uint8_t)(i32Tmp / 8));
2578
2579 pHlp->pfnSSMSkip (pSSM, sizeof(int32_t)); /* Legacy; was PDMAUDIOFMT, unused now. */
2580 pHlp->pfnSSMGetS32(pSSM, &pStream->dma_auto);
2581 pHlp->pfnSSMGetS32(pSSM, &pStream->cbDmaBlockSize);
2582 pHlp->pfnSSMGetS32(pSSM, &pStream->fifo);
2583 pHlp->pfnSSMGetS32(pSSM, &i32Tmp); pStream->Cfg.Props.uHz = i32Tmp;
2584 pHlp->pfnSSMGetS32(pSSM, &pStream->time_const);
2585 pHlp->pfnSSMSkip (pSSM, sizeof(int32_t)); /* Legacy; was speaker (on / off) for output stream. */
2586 pHlp->pfnSSMGetS32(pSSM, &pThis->dsp_in_needed_bytes);
2587 pHlp->pfnSSMGetS32(pSSM, &pThis->cmd);
2588 pHlp->pfnSSMGetS32(pSSM, &pStream->fDmaUseHigh); /* Output stream: Whether to use the high or low DMA channel. */
2589 pHlp->pfnSSMGetS32(pSSM, &pThis->highspeed);
2590 pHlp->pfnSSMGetS32(pSSM, &pStream->can_write);
2591 pHlp->pfnSSMGetS32(pSSM, &pThis->v2x6);
2592
2593 pHlp->pfnSSMGetU8 (pSSM, &pThis->csp_param);
2594 pHlp->pfnSSMGetU8 (pSSM, &pThis->csp_value);
2595 pHlp->pfnSSMGetU8 (pSSM, &pThis->csp_mode);
2596 pHlp->pfnSSMGetU8 (pSSM, &pThis->csp_param); /* Bug compatible! */
2597 pHlp->pfnSSMGetMem(pSSM, pThis->csp_regs, 256);
2598 pHlp->pfnSSMGetU8 (pSSM, &pThis->csp_index);
2599 pHlp->pfnSSMGetMem(pSSM, pThis->csp_reg83, 4);
2600 pHlp->pfnSSMGetS32(pSSM, &pThis->csp_reg83r);
2601 pHlp->pfnSSMGetS32(pSSM, &pThis->csp_reg83w);
2602
2603 pHlp->pfnSSMGetMem(pSSM, pThis->dsp_in_data, sizeof(pThis->dsp_in_data));
2604 pHlp->pfnSSMGetMem(pSSM, pThis->dsp_out_data, sizeof(pThis->dsp_out_data));
2605 pHlp->pfnSSMGetU8 (pSSM, &pThis->test_reg);
2606 pHlp->pfnSSMGetU8 (pSSM, &pThis->last_read_byte);
2607
2608 pHlp->pfnSSMGetS32(pSSM, &pThis->nzero);
2609 pHlp->pfnSSMGetS32(pSSM, &pStream->cbDmaLeft);
2610 pHlp->pfnSSMGetS32(pSSM, &i32Tmp); /* Output stream: DMA currently running bit. */
2611 const bool fStreamEnabled = i32Tmp != 0;
2612 pHlp->pfnSSMSkip (pSSM, sizeof(int32_t)); /* Legacy; was the output stream's current bitrate (in bytes). */
2613 pHlp->pfnSSMSkip (pSSM, sizeof(int32_t)); /* Legacy; was the output stream's DMA block alignment. */
2614
2615 int32_t mixer_nreg = 0;
2616 rc = pHlp->pfnSSMGetS32(pSSM, &mixer_nreg);
2617 AssertRCReturn(rc, rc);
2618 pThis->mixer_nreg = (uint8_t)mixer_nreg;
2619 rc = pHlp->pfnSSMGetMem(pSSM, pThis->mixer_regs, 256);
2620 AssertRCReturn(rc, rc);
2621
2622 if (fStreamEnabled)
2623 {
2624 /* Sanity: If stream is going be enabled, PCM props must be valid. Otherwise the saved state is borked somehow. */
2625 AssertMsgReturn(AudioHlpPcmPropsAreValidAndSupported(&pStream->Cfg.Props),
2626 ("PCM properties for stream #%RU8 are invalid\n", pStream->uIdx), VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
2627 sb16StreamControl(pDevIns, pThis, pStream, true /* fRun */);
2628 }
2629
2630 /* Update the master (mixer) and PCM out volumes. */
2631 sb16UpdateVolume(pThis);
2632
2633 return VINF_SUCCESS;
2634}
2635
2636/**
2637 * @callback_method_impl{FNSSMDEVLOADEXEC}
2638 */
2639static DECLCALLBACK(int) sb16LoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass)
2640{
2641 PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE);
2642 PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
2643
2644 AssertMsgReturn( uVersion == SB16_SAVE_STATE_VERSION
2645 || uVersion == SB16_SAVE_STATE_VERSION_VBOX_30,
2646 ("%u\n", uVersion),
2647 VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION);
2648 if (uVersion > SB16_SAVE_STATE_VERSION_VBOX_30)
2649 {
2650 /** Currently the saved state only contains the one-and-only output stream. */
2651 PSB16STREAM pStream = &pThis->aStreams[SB16_IDX_OUT];
2652
2653 int32_t irq;
2654 pHlp->pfnSSMGetS32(pSSM, &irq);
2655 int32_t dma;
2656 pHlp->pfnSSMGetS32(pSSM, &dma);
2657 int32_t hdma;
2658 pHlp->pfnSSMGetS32(pSSM, &hdma);
2659 int32_t port;
2660 pHlp->pfnSSMGetS32(pSSM, &port);
2661 int32_t ver;
2662 int rc = pHlp->pfnSSMGetS32(pSSM, &ver);
2663 AssertRCReturn (rc, rc);
2664
2665 if ( irq != pStream->HwCfgDefault.uIrq
2666 || dma != pStream->HwCfgDefault.uDmaChanLow
2667 || hdma != pStream->HwCfgDefault.uDmaChanHigh
2668 || port != pStream->HwCfgDefault.uPort
2669 || ver != pStream->HwCfgDefault.uVer)
2670 {
2671 return pHlp->pfnSSMSetCfgError(pSSM, RT_SRC_POS,
2672 N_("config changed: irq=%x/%x dma=%x/%x hdma=%x/%x port=%x/%x ver=%x/%x (saved/config)"),
2673 irq, pStream->HwCfgDefault.uIrq,
2674 dma, pStream->HwCfgDefault.uDmaChanLow,
2675 hdma, pStream->HwCfgDefault.uDmaChanHigh,
2676 port, pStream->HwCfgDefault.uPort,
2677 ver, pStream->HwCfgDefault.uVer);
2678 }
2679 }
2680
2681 if (uPass != SSM_PASS_FINAL)
2682 return VINF_SUCCESS;
2683
2684 return sb16Load(pDevIns, pSSM, pThis);
2685}
2686
2687
2688/*********************************************************************************************************************************
2689* Debug Info Items *
2690*********************************************************************************************************************************/
2691
2692/**
2693 * @callback_method_impl{FNDBGFHANDLERDEV, sb16mixer}
2694 */
2695static DECLCALLBACK(void) sb16DbgInfoMixer(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs)
2696{
2697 PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE);
2698 if (pThis->pMixer)
2699 AudioMixerDebug(pThis->pMixer, pHlp, pszArgs);
2700 else
2701 pHlp->pfnPrintf(pHlp, "Mixer not available\n");
2702}
2703
2704
2705/*********************************************************************************************************************************
2706* IBase implementation *
2707*********************************************************************************************************************************/
2708
2709/**
2710 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
2711 */
2712static DECLCALLBACK(void *) sb16QueryInterface(struct PDMIBASE *pInterface, const char *pszIID)
2713{
2714 PSB16STATE pThis = RT_FROM_MEMBER(pInterface, SB16STATE, IBase);
2715 Assert(&pThis->IBase == pInterface);
2716
2717 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThis->IBase);
2718 return NULL;
2719}
2720
2721
2722/*********************************************************************************************************************************
2723* Device (PDM) handling *
2724*********************************************************************************************************************************/
2725
2726/**
2727 * Worker for sb16Construct() and sb16Attach().
2728 *
2729 * @returns VBox status code.
2730 * @param pThis SB16 state.
2731 * @param uLUN The logical unit which is being detached.
2732 * @param ppDrv Attached driver instance on success. Optional.
2733 */
2734static int sb16AttachInternal(PSB16STATE pThis, unsigned uLUN, PSB16DRIVER *ppDrv)
2735{
2736 /*
2737 * Allocate a new driver structure and try attach the driver.
2738 */
2739 PSB16DRIVER pDrv = (PSB16DRIVER)RTMemAllocZ(sizeof(SB16DRIVER));
2740 AssertPtrReturn(pDrv, VERR_NO_MEMORY);
2741 RTStrPrintf(pDrv->szDesc, sizeof(pDrv->szDesc), "Audio driver port (SB16) for LUN #%u", uLUN);
2742
2743 PPDMIBASE pDrvBase;
2744 int rc = PDMDevHlpDriverAttach(pThis->pDevInsR3, uLUN, &pThis->IBase, &pDrvBase, pDrv->szDesc);
2745 if (RT_SUCCESS(rc))
2746 {
2747 pDrv->pConnector = PDMIBASE_QUERY_INTERFACE(pDrvBase, PDMIAUDIOCONNECTOR);
2748 AssertPtr(pDrv->pConnector);
2749 if (RT_VALID_PTR(pDrv->pConnector))
2750 {
2751 pDrv->pDrvBase = pDrvBase;
2752 pDrv->pSB16State = pThis;
2753 pDrv->uLUN = uLUN;
2754
2755 /* Attach to driver list if not attached yet. */
2756 if (!pDrv->fAttached)
2757 {
2758 RTListAppend(&pThis->lstDrv, &pDrv->Node);
2759 pDrv->fAttached = true;
2760 }
2761
2762 if (ppDrv)
2763 *ppDrv = pDrv;
2764 LogFunc(("LUN#%u: returns VINF_SUCCESS (pCon=%p)\n", uLUN, pDrv->pConnector));
2765 return VINF_SUCCESS;
2766 }
2767 rc = VERR_PDM_MISSING_INTERFACE_BELOW;
2768 }
2769 else if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
2770 LogFunc(("No attached driver for LUN #%u\n", uLUN));
2771 else
2772 LogFunc(("Failed to attached driver for LUN #%u: %Rrc\n", uLUN, rc));
2773 RTMemFree(pDrv);
2774
2775 LogFunc(("LUN#%u: rc=%Rrc\n", uLUN, rc));
2776 return rc;
2777}
2778
2779/**
2780 * @interface_method_impl{PDMDEVREG,pfnAttach}
2781 */
2782static DECLCALLBACK(int) sb16Attach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags)
2783{
2784 PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE);
2785 RT_NOREF(fFlags);
2786 LogFunc(("iLUN=%u, fFlags=%#x\n", iLUN, fFlags));
2787
2788 /** @todo r=andy Any locking required here? */
2789
2790 PSB16DRIVER pDrv;
2791 int rc = sb16AttachInternal(pThis, iLUN, &pDrv);
2792 if (RT_SUCCESS(rc))
2793 {
2794 int rc2 = sb16AddDrv(pDevIns, pThis, pDrv);
2795 if (RT_FAILURE(rc2))
2796 LogFunc(("sb16AddDrv failed with %Rrc (ignored)\n", rc2));
2797 }
2798
2799 return rc;
2800}
2801
2802/**
2803 * @interface_method_impl{PDMDEVREG,pfnDetach}
2804 */
2805static DECLCALLBACK(void) sb16Detach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags)
2806{
2807 PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE);
2808 RT_NOREF(fFlags);
2809
2810 LogFunc(("iLUN=%u, fFlags=0x%x\n", iLUN, fFlags));
2811
2812 PSB16DRIVER pDrv;
2813 RTListForEach(&pThis->lstDrv, pDrv, SB16DRIVER, Node)
2814 {
2815 if (pDrv->uLUN == iLUN)
2816 {
2817 sb16RemoveDrv(pDevIns, pThis, pDrv);
2818 RTMemFree(pDrv);
2819 return;
2820 }
2821 }
2822 LogFunc(("LUN#%u was not found\n", iLUN));
2823}
2824
2825
2826/**
2827 * @interface_method_impl{PDMDEVREG,pfnReset}
2828 */
2829static DECLCALLBACK(void) sb16DevReset(PPDMDEVINS pDevIns)
2830{
2831 PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE);
2832
2833 LogRel2(("SB16: Reset\n"));
2834
2835 pThis->mixer_regs[0x82] = 0;
2836 pThis->csp_regs[5] = 1;
2837 pThis->csp_regs[9] = 0xf8;
2838
2839 pThis->dsp_in_idx = 0;
2840 pThis->dsp_out_data_len = 0;
2841 pThis->dsp_in_needed_bytes = 0;
2842 pThis->nzero = 0;
2843 pThis->highspeed = 0;
2844 pThis->v2x6 = 0;
2845 pThis->cmd = -1;
2846
2847 sb16MixerReset(pThis);
2848 sb16SpeakerControl(pThis, false /* fOn */);
2849 sb16DspCmdResetLegacy(pThis);
2850}
2851
2852/**
2853 * Powers off the device.
2854 *
2855 * @param pDevIns Device instance to power off.
2856 */
2857static DECLCALLBACK(void) sb16PowerOff(PPDMDEVINS pDevIns)
2858{
2859 PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE);
2860
2861 LogRel2(("SB16: Powering off ...\n"));
2862
2863 /*
2864 * Destroy all streams.
2865 */
2866 for (unsigned i = 0; i < SB16_MAX_STREAMS; i++)
2867 sb16StreamDestroy(pDevIns, pThis, &pThis->aStreams[i]);
2868
2869 /*
2870 * Destroy all sinks.
2871 */
2872 if (pThis->pSinkOut)
2873 {
2874 AudioMixerSinkDestroy(pThis->pSinkOut, pDevIns);
2875 pThis->pSinkOut = NULL;
2876 }
2877 /** @todo Ditto for sinks. */
2878
2879 /*
2880 * Note: Destroy the mixer while powering off and *not* in sb16Destruct,
2881 * giving the mixer the chance to release any references held to
2882 * PDM audio streams it maintains.
2883 */
2884 if (pThis->pMixer)
2885 {
2886 AudioMixerDestroy(pThis->pMixer, pDevIns);
2887 pThis->pMixer = NULL;
2888 }
2889}
2890
2891/**
2892 * @interface_method_impl{PDMDEVREG,pfnDestruct}
2893 */
2894static DECLCALLBACK(int) sb16Destruct(PPDMDEVINS pDevIns)
2895{
2896 PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns); /* this shall come first */
2897 PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE);
2898
2899 LogFlowFuncEnter();
2900
2901 PSB16DRIVER pDrv;
2902 while (!RTListIsEmpty(&pThis->lstDrv))
2903 {
2904 pDrv = RTListGetFirst(&pThis->lstDrv, SB16DRIVER, Node);
2905
2906 RTListNodeRemove(&pDrv->Node);
2907 RTMemFree(pDrv);
2908 }
2909
2910 /* We don't always go via PowerOff, so make sure the mixer is destroyed. */
2911 if (pThis->pMixer)
2912 {
2913 AudioMixerDestroy(pThis->pMixer, pDevIns);
2914 pThis->pMixer = NULL;
2915 }
2916
2917 return VINF_SUCCESS;
2918}
2919
2920/**
2921 * @interface_method_impl{PDMDEVREGR3,pfnConstruct}
2922 */
2923static DECLCALLBACK(int) sb16Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg)
2924{
2925 PDMDEV_CHECK_VERSIONS_RETURN(pDevIns); /* this shall come first */
2926 PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE);
2927 PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
2928 RT_NOREF(iInstance);
2929
2930 Assert(iInstance == 0);
2931
2932 /*
2933 * Initialize the data so sb16Destruct runs without a hitch if we return early.
2934 */
2935 pThis->pDevInsR3 = pDevIns;
2936 pThis->IBase.pfnQueryInterface = sb16QueryInterface;
2937 pThis->cmd = -1;
2938
2939 pThis->csp_regs[5] = 1;
2940 pThis->csp_regs[9] = 0xf8;
2941
2942 RTListInit(&pThis->lstDrv);
2943
2944 /*
2945 * Validate and read config data.
2946 */
2947 /* Note: For now we only support the one-and-only output stream. */
2948 PSB16STREAM pStream = &pThis->aStreams[SB16_IDX_OUT];
2949
2950 PDMDEV_VALIDATE_CONFIG_RETURN(pDevIns, "IRQ|DMA|DMA16|Port|Version|TimerHz|DebugEnabled|DebugPathOut", "");
2951 int rc = pHlp->pfnCFGMQueryU8Def(pCfg, "IRQ", &pStream->HwCfgDefault.uIrq, 5);
2952 if (RT_FAILURE(rc))
2953 return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: Failed to get the \"IRQ\" value"));
2954 /* Sanity-check supported SB16 IRQs. */
2955 if ( 2 != pStream->HwCfgDefault.uIrq
2956 && 5 != pStream->HwCfgDefault.uIrq
2957 && 7 != pStream->HwCfgDefault.uIrq
2958 && 10 != pStream->HwCfgDefault.uIrq)
2959 return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: Invalid \"IRQ\" value."));
2960 pStream->HwCfgRuntime.uIrq = pStream->HwCfgDefault.uIrq;
2961
2962 rc = pHlp->pfnCFGMQueryU8Def(pCfg, "DMA", &pStream->HwCfgDefault.uDmaChanLow, 1);
2963 if (RT_FAILURE(rc))
2964 return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: Failed to get the \"DMA\" value"));
2965 if ( 0 != pStream->HwCfgDefault.uDmaChanLow
2966 && 1 != pStream->HwCfgDefault.uDmaChanLow
2967 && 3 != pStream->HwCfgDefault.uDmaChanLow)
2968 return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: Invalid \"DMA\" value."));
2969 pStream->HwCfgRuntime.uDmaChanLow = pStream->HwCfgDefault.uDmaChanLow;
2970
2971 rc = pHlp->pfnCFGMQueryU8Def(pCfg, "DMA16", &pStream->HwCfgDefault.uDmaChanHigh, 5);
2972 if (RT_FAILURE(rc))
2973 return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: Failed to get the \"DMA16\" value"));
2974 if ( 5 != pStream->HwCfgDefault.uDmaChanHigh
2975 && 6 != pStream->HwCfgDefault.uDmaChanHigh
2976 && 7 != pStream->HwCfgDefault.uDmaChanHigh)
2977 return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: Invalid \"DMA16\" value."));
2978 pStream->HwCfgRuntime.uDmaChanHigh = pStream->HwCfgDefault.uDmaChanHigh;
2979
2980 rc = pHlp->pfnCFGMQueryPortDef(pCfg, "Port", &pStream->HwCfgDefault.uPort, 0x220);
2981 if (RT_FAILURE(rc))
2982 return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: Failed to get the \"Port\" value"));
2983 /* Sanity-check supported SB16 ports. */
2984 if ( 0x220 != pStream->HwCfgDefault.uPort
2985 && 0x240 != pStream->HwCfgDefault.uPort
2986 && 0x260 != pStream->HwCfgDefault.uPort
2987 && 0x280 != pStream->HwCfgDefault.uPort)
2988 return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: Invalid \"Port\" value. Did you specify it as a hex value (e.g. 0x220)?"));
2989 pStream->HwCfgRuntime.uPort = pStream->HwCfgDefault.uPort;
2990
2991 rc = pHlp->pfnCFGMQueryU16Def(pCfg, "Version", &pStream->HwCfgDefault.uVer, 0x0405);
2992 if (RT_FAILURE(rc))
2993 return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: Failed to get the \"Version\" value"));
2994 pStream->HwCfgRuntime.uVer = pStream->HwCfgDefault.uVer;
2995
2996 rc = pHlp->pfnCFGMQueryU16Def(pCfg, "TimerHz", &pStream->uTimerHz, SB16_TIMER_HZ_DEFAULT);
2997 if (RT_FAILURE(rc))
2998 return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: failed to read Hertz rate as unsigned integer"));
2999 if (pStream->uTimerHz == 0)
3000 return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: Hertz rate is invalid"));
3001 if (pStream->uTimerHz > 2048)
3002 return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: Maximum Hertz rate is 2048"));
3003
3004 rc = pHlp->pfnCFGMQueryBoolDef(pCfg, "DebugEnabled", &pThis->Dbg.fEnabled, false);
3005 if (RT_FAILURE(rc))
3006 return PDMDEV_SET_ERROR(pDevIns, rc,
3007 N_("SB16 configuration error: failed to read debugging enabled flag as boolean"));
3008
3009 rc = pHlp->pfnCFGMQueryStringAllocDef(pCfg, "DebugPathOut", &pThis->Dbg.pszOutPath, NULL);
3010 if (RT_FAILURE(rc))
3011 return PDMDEV_SET_ERROR(pDevIns, rc,
3012 N_("SB16 configuration error: failed to read debugging output path flag as string"));
3013
3014 if (pThis->Dbg.fEnabled)
3015 LogRel2(("SB16: Debug output will be saved to '%s'\n", pThis->Dbg.pszOutPath));
3016
3017 /*
3018 * Create internal software mixer.
3019 * Must come before we do the device's mixer reset.
3020 */
3021 rc = AudioMixerCreate("SB16 Mixer", 0 /* uFlags */, &pThis->pMixer);
3022 AssertRCReturn(rc, rc);
3023
3024 AssertRCReturn(rc, rc);
3025 rc = AudioMixerCreateSink(pThis->pMixer, "PCM Output",
3026 PDMAUDIODIR_OUT, pDevIns, &pThis->pSinkOut);
3027 AssertRCReturn(rc, rc);
3028
3029 /*
3030 * Create all hardware streams.
3031 * For now we have one stream only, namely the output (playback) stream.
3032 */
3033 AssertCompile(RT_ELEMENTS(pThis->aStreams) == SB16_MAX_STREAMS);
3034 for (unsigned i = 0; i < SB16_MAX_STREAMS; i++)
3035 {
3036 rc = sb16StreamCreate(pThis, &pThis->aStreams[i], i /* uIdx */);
3037 AssertRCReturn(rc, rc);
3038 }
3039
3040 /*
3041 * Setup the mixer now that we've got the irq and dma channel numbers.
3042 */
3043 pThis->mixer_regs[0x80] = magic_of_irq(pStream->HwCfgRuntime.uIrq);
3044 pThis->mixer_regs[0x81] = (1 << pStream->HwCfgRuntime.uDmaChanLow) | (1 << pStream->HwCfgRuntime.uDmaChanHigh);
3045 pThis->mixer_regs[0x82] = 2 << 5;
3046
3047 /*
3048 * Perform a device reset before we set up the mixer below,
3049 * to have a defined state. This includes the mixer reset + legacy reset.
3050 */
3051 sb16DevReset(pThis->pDevInsR3);
3052
3053 /*
3054 * Make sure that the mixer sink(s) have a valid format set.
3055 *
3056 * This is needed in order to make the driver attaching logic working done by Main
3057 * for machine construction. Must come after sb16DevReset().
3058 */
3059 PSB16STREAM const pStreamOut = &pThis->aStreams[SB16_IDX_OUT];
3060 AudioMixerSinkSetFormat(pThis->pSinkOut, &pStreamOut->Cfg.Props, pStreamOut->Cfg.Device.cMsSchedulingHint);
3061
3062 /*
3063 * Create timers.
3064 */
3065 rc = PDMDevHlpTimerCreate(pDevIns, TMCLOCK_VIRTUAL, sb16TimerIRQ, pThis,
3066 TMTIMER_FLAGS_DEFAULT_CRIT_SECT | TMTIMER_FLAGS_NO_RING0, "SB16 IRQ", &pThis->hTimerIRQ);
3067 AssertRCReturn(rc, rc);
3068
3069 static const char * const s_apszNames[] = { "SB16 OUT" };
3070 AssertCompile(RT_ELEMENTS(s_apszNames) == SB16_MAX_STREAMS);
3071 for (unsigned i = 0; i < SB16_MAX_STREAMS; i++)
3072 {
3073 rc = PDMDevHlpTimerCreate(pDevIns, TMCLOCK_VIRTUAL, sb16TimerIO, &pThis->aStreams[i],
3074 TMTIMER_FLAGS_DEFAULT_CRIT_SECT | TMTIMER_FLAGS_NO_RING0, s_apszNames[i], &pThis->aStreams[i].hTimerIO);
3075 AssertRCReturn(rc, rc);
3076
3077 pThis->aStreams[i].cTicksTimerIOInterval = PDMDevHlpTimerGetFreq(pDevIns, pThis->aStreams[i].hTimerIO)
3078 / pThis->aStreams[i].uTimerHz;
3079 pThis->aStreams[i].tsTimerIO = PDMDevHlpTimerGet(pDevIns, pThis->aStreams[i].hTimerIO);
3080 }
3081
3082 /*
3083 * Register I/O and DMA.
3084 */
3085 static const IOMIOPORTDESC s_aAllDescs[] =
3086 {
3087 { "FM Music Status Port", "FM Music Register Address Port", NULL, NULL }, // 00h
3088 { NULL, "FM Music Data Port", NULL, NULL }, // 01h
3089 { "Advanced FM Music Status Port", "Advanced FM Music Register Address Port", NULL, NULL }, // 02h
3090 { NULL, "Advanced FM Music Data Port", NULL, NULL }, // 03h
3091 { NULL, "Mixer chip Register Address Port", NULL, NULL }, // 04h
3092 { "Mixer chip Data Port", NULL, NULL, NULL }, // 05h
3093 { NULL, "DSP Reset", NULL, NULL }, // 06h
3094 { "Unused7", "Unused7", NULL, NULL }, // 07h
3095 { "FM Music Status Port", "FM Music Register Port", NULL, NULL }, // 08h
3096 { NULL, "FM Music Data Port", NULL, NULL }, // 09h
3097 { "DSP Read Data Port", NULL, NULL, NULL }, // 0Ah
3098 { "UnusedB", "UnusedB", NULL, NULL }, // 0Bh
3099 { "DSP Write-Buffer Status", "DSP Write Command/Data", NULL, NULL }, // 0Ch
3100 { "UnusedD", "UnusedD", NULL, NULL }, // 0Dh
3101 { "DSP Read-Buffer Status", NULL, NULL, NULL }, // 0Eh
3102 { "IRQ16ACK", NULL, NULL, NULL }, // 0Fh
3103 { "CD-ROM Data Register", "CD-ROM Command Register", NULL, NULL }, // 10h
3104 { "CD-ROM Status Register", NULL, NULL, NULL }, // 11h
3105 { NULL, "CD-ROM Reset Register", NULL, NULL }, // 12h
3106 { NULL, "CD-ROM Enable Register", NULL, NULL }, // 13h
3107 { NULL, NULL, NULL, NULL },
3108 };
3109
3110 rc = PDMDevHlpIoPortCreateAndMap(pDevIns, pStream->HwCfgRuntime.uPort + 0x04 /*uPort*/, 2 /*cPorts*/,
3111 sb16IoPortMixerWrite, sb16IoPortMixerRead,
3112 "SB16 - Mixer", &s_aAllDescs[4], &pThis->hIoPortsMixer);
3113 AssertRCReturn(rc, rc);
3114 rc = PDMDevHlpIoPortCreateAndMap(pDevIns, pStream->HwCfgRuntime.uPort + 0x06 /*uPort*/, 10 /*cPorts*/,
3115 sb16IoPortDspWrite, sb16IoPortDspRead,
3116 "SB16 - DSP", &s_aAllDescs[6], &pThis->hIoPortsDsp);
3117 AssertRCReturn(rc, rc);
3118
3119 rc = PDMDevHlpDMARegister(pDevIns, pStream->HwCfgRuntime.uDmaChanHigh, sb16DMARead, &pThis->aStreams[SB16_IDX_OUT] /* pvUser */);
3120 AssertRCReturn(rc, rc);
3121 rc = PDMDevHlpDMARegister(pDevIns, pStream->HwCfgRuntime.uDmaChanLow, sb16DMARead, &pThis->aStreams[SB16_IDX_OUT] /* pvUser */);
3122 AssertRCReturn(rc, rc);
3123
3124 /*
3125 * Register Saved state.
3126 */
3127 rc = PDMDevHlpSSMRegister3(pDevIns, SB16_SAVE_STATE_VERSION, sizeof(SB16STATE), sb16LiveExec, sb16SaveExec, sb16LoadExec);
3128 AssertRCReturn(rc, rc);
3129
3130 LogRel2(("SB16: Using port %#x, DMA%RU8, IRQ%RU8\n",
3131 pStream->HwCfgRuntime.uPort, pStream->HwCfgRuntime.uDmaChanLow, pStream->HwCfgRuntime.uIrq));
3132
3133 /*
3134 * Attach drivers. We ASSUME they are configured consecutively without any
3135 * gaps, so we stop when we hit the first LUN w/o a driver configured.
3136 */
3137 for (unsigned iLun = 0; ; iLun++)
3138 {
3139 AssertBreak(iLun < UINT8_MAX);
3140 LogFunc(("Trying to attach driver for LUN#%u ...\n", iLun));
3141 rc = sb16AttachInternal(pThis, iLun, NULL /* ppDrv */);
3142 if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
3143 {
3144 LogFunc(("cLUNs=%u\n", iLun));
3145 break;
3146 }
3147 AssertLogRelMsgReturn(RT_SUCCESS(rc), ("LUN#%u: rc=%Rrc\n", iLun, rc), rc);
3148 }
3149
3150 /*
3151 * Register statistics.
3152 */
3153# ifdef VBOX_WITH_STATISTICS
3154 PDMDevHlpSTAMRegister(pDevIns, &pThis->StatTimerIO, STAMTYPE_PROFILE, "Timer", STAMUNIT_TICKS_PER_CALL, "Profiling sb16TimerIO.");
3155 PDMDevHlpSTAMRegister(pDevIns, &pThis->StatBytesRead, STAMTYPE_COUNTER, "BytesRead", STAMUNIT_BYTES, "Bytes read from SB16 emulation.");
3156# endif
3157 for (unsigned idxStream = 0; idxStream < RT_ELEMENTS(pThis->aStreams); idxStream++)
3158 {
3159 PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].State.offRead, STAMTYPE_U64, STAMVISIBILITY_USED, STAMUNIT_BYTES,
3160 "Virtual internal buffer read position.", "Stream%u/offRead", idxStream);
3161 PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].State.offWrite, STAMTYPE_U64, STAMVISIBILITY_USED, STAMUNIT_BYTES,
3162 "Virtual internal buffer write position.", "Stream%u/offWrite", idxStream);
3163 PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].State.StatDmaBufSize, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_BYTES,
3164 "Size of the internal DMA buffer.", "Stream%u/DMABufSize", idxStream);
3165 PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].State.StatDmaBufUsed, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_BYTES,
3166 "Number of bytes used in the internal DMA buffer.", "Stream%u/DMABufUsed", idxStream);
3167 }
3168
3169 /*
3170 * Debug info items.
3171 */
3172 //PDMDevHlpDBGFInfoRegister(pDevIns, "sb16", "SB16 registers. (sb16 [register case-insensitive])", sb16DbgInfo);
3173 //PDMDevHlpDBGFInfoRegister(pDevIns, "sb16stream", "SB16 stream info. (sb16stream [stream number])", sb16DbgInfoStream);
3174 PDMDevHlpDBGFInfoRegister(pDevIns, "sb16mixer", "SB16 mixer state.", sb16DbgInfoMixer);
3175
3176 return VINF_SUCCESS;
3177}
3178
3179const PDMDEVREG g_DeviceSB16 =
3180{
3181 /* .u32Version = */ PDM_DEVREG_VERSION,
3182 /* .uReserved0 = */ 0,
3183 /* .szName = */ "sb16",
3184 /* .fFlags = */ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_NEW_STYLE
3185 | PDM_DEVREG_FLAGS_FIRST_POWEROFF_NOTIFICATION /* stream clearnup with working drivers */,
3186 /* .fClass = */ PDM_DEVREG_CLASS_AUDIO,
3187 /* .cMaxInstances = */ 1,
3188 /* .uSharedVersion = */ 42,
3189 /* .cbInstanceShared = */ sizeof(SB16STATE),
3190 /* .cbInstanceCC = */ 0,
3191 /* .cbInstanceRC = */ 0,
3192 /* .cMaxPciDevices = */ 0,
3193 /* .cMaxMsixVectors = */ 0,
3194 /* .pszDescription = */ "Sound Blaster 16 Controller",
3195#if defined(IN_RING3)
3196 /* .pszRCMod = */ "",
3197 /* .pszR0Mod = */ "",
3198 /* .pfnConstruct = */ sb16Construct,
3199 /* .pfnDestruct = */ sb16Destruct,
3200 /* .pfnRelocate = */ NULL,
3201 /* .pfnMemSetup = */ NULL,
3202 /* .pfnPowerOn = */ NULL,
3203 /* .pfnReset = */ sb16DevReset,
3204 /* .pfnSuspend = */ NULL,
3205 /* .pfnResume = */ NULL,
3206 /* .pfnAttach = */ sb16Attach,
3207 /* .pfnDetach = */ sb16Detach,
3208 /* .pfnQueryInterface = */ NULL,
3209 /* .pfnInitComplete = */ NULL,
3210 /* .pfnPowerOff = */ sb16PowerOff,
3211 /* .pfnSoftReset = */ NULL,
3212 /* .pfnReserved0 = */ NULL,
3213 /* .pfnReserved1 = */ NULL,
3214 /* .pfnReserved2 = */ NULL,
3215 /* .pfnReserved3 = */ NULL,
3216 /* .pfnReserved4 = */ NULL,
3217 /* .pfnReserved5 = */ NULL,
3218 /* .pfnReserved6 = */ NULL,
3219 /* .pfnReserved7 = */ NULL,
3220#elif defined(IN_RING0)
3221 /* .pfnEarlyConstruct = */ NULL,
3222 /* .pfnConstruct = */ NULL,
3223 /* .pfnDestruct = */ NULL,
3224 /* .pfnFinalDestruct = */ NULL,
3225 /* .pfnRequest = */ NULL,
3226 /* .pfnReserved0 = */ NULL,
3227 /* .pfnReserved1 = */ NULL,
3228 /* .pfnReserved2 = */ NULL,
3229 /* .pfnReserved3 = */ NULL,
3230 /* .pfnReserved4 = */ NULL,
3231 /* .pfnReserved5 = */ NULL,
3232 /* .pfnReserved6 = */ NULL,
3233 /* .pfnReserved7 = */ NULL,
3234#elif defined(IN_RC)
3235 /* .pfnConstruct = */ NULL,
3236 /* .pfnReserved0 = */ NULL,
3237 /* .pfnReserved1 = */ NULL,
3238 /* .pfnReserved2 = */ NULL,
3239 /* .pfnReserved3 = */ NULL,
3240 /* .pfnReserved4 = */ NULL,
3241 /* .pfnReserved5 = */ NULL,
3242 /* .pfnReserved6 = */ NULL,
3243 /* .pfnReserved7 = */ NULL,
3244#else
3245# error "Not in IN_RING3, IN_RING0 or IN_RC!"
3246#endif
3247 /* .u32VersionEnd = */ PDM_DEVREG_VERSION
3248};
3249
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use