VirtualBox

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

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

Copyright year updates by scm.

  • 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 106061 2024-09-16 14:03:52Z vboxsync $ */
2/** @file
3 * DevSB16 - VBox SB16 Audio Controller.
4 */
5
6/*
7 * Copyright (C) 2015-2024 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 pThis The SB16 state.
1935 * @param pStream The SB16 stream to read DMA output data from.
1936 * @param uDmaChan DMA channel to read from.
1937 * @param offDma DMA offset (in bytes) to start reading from.
1938 * @param cbDma DMA area size in bytes.
1939 * @param cbToRead How much bytes to read in total.
1940 * @param pcbRead Where to return the DMA bytes read.
1941 *
1942 * @thread EMT
1943 */
1944static int sb16StreamDoDmaOutput(PSB16STATE pThis, PSB16STREAM pStream, int uDmaChan, uint32_t offDma, uint32_t cbDma,
1945 uint32_t cbToRead, uint32_t *pcbRead)
1946{
1947 uint32_t cbFree = (uint32_t)RTCircBufFree(pStream->State.pCircBuf);
1948 //Assert(cbToRead <= cbFree); /** @todo Add statistics for overflows. */
1949 cbToRead = RT_MIN(cbToRead, cbFree);
1950
1951 sb16StreamLock(pStream);
1952
1953 uint32_t cbReadTotal = 0;
1954 while (cbToRead)
1955 {
1956 void *pv = NULL;
1957 size_t cb = 0;
1958 RTCircBufAcquireWriteBlock(pStream->State.pCircBuf, RT_MIN(cbDma - offDma, cbToRead), &pv, &cb);
1959
1960 uint32_t cbRead = 0;
1961 int rc = PDMDevHlpDMAReadMemory(pThis->pDevInsR3, uDmaChan, pv, offDma, (uint32_t)cb, &cbRead);
1962 if (RT_SUCCESS(rc))
1963 Assert(cbRead == cb);
1964 else
1965 {
1966 AssertMsgFailed(("Reading from DMA failed: %Rrc (cbReadTotal=%#x)\n", rc, cbReadTotal));
1967 RTCircBufReleaseWriteBlock(pStream->State.pCircBuf, 0);
1968 if (cbReadTotal > 0)
1969 break;
1970 *pcbRead = 0;
1971
1972 sb16StreamUnlock(pStream);
1973 return rc;
1974 }
1975
1976 if (RT_LIKELY(!pStream->Dbg.Runtime.pFileDMA))
1977 { /* likely */ }
1978 else
1979 AudioHlpFileWrite(pStream->Dbg.Runtime.pFileDMA, pv, cbRead);
1980
1981 Log3Func(("Writing %#RX32 bytes to DMA buffer\n", cbRead));
1982
1983 RTCircBufReleaseWriteBlock(pStream->State.pCircBuf, cbRead);
1984
1985 Log3Func(("Now %#RX32 bytes in buffer\n", RTCircBufUsed(pStream->State.pCircBuf)));
1986
1987 Assert(cbToRead >= cbRead);
1988 pStream->State.offWrite += cbRead;
1989 offDma = (offDma + cbRead) % cbDma;
1990 cbReadTotal += cbRead;
1991 cbToRead -= cbRead;
1992 }
1993
1994 *pcbRead = cbReadTotal;
1995
1996 /* Update buffer stats. */
1997 pStream->State.StatDmaBufUsed = (uint32_t)RTCircBufUsed(pStream->State.pCircBuf);
1998
1999 Log3Func(("[SD%RU8] cbCircBufUsed=%#RX64\n", pStream->uIdx, RTCircBufUsed(pStream->State.pCircBuf)));
2000
2001 sb16StreamUnlock(pStream);
2002 return VINF_SUCCESS;
2003}
2004
2005/**
2006 * Enables or disables a SB16 audio stream.
2007 *
2008 * @returns VBox status code.
2009 * @param pThis The SB16 state.
2010 * @param pStream The SB16 stream to enable or disable.
2011 * @param fEnable Whether to enable or disable the stream.
2012 * @param fForce Whether to force re-opening the stream or not.
2013 * Otherwise re-opening only will happen if the PCM properties have changed.
2014 */
2015static int sb16StreamEnable(PSB16STATE pThis, PSB16STREAM pStream, bool fEnable, bool fForce)
2016{
2017 if ( !fForce
2018 && fEnable == pStream->State.fEnabled)
2019 return VINF_SUCCESS;
2020
2021 LogFlowFunc(("fEnable=%RTbool, fForce=%RTbool, fStreamEnabled=%RTbool\n", fEnable, fForce, pStream->State.fEnabled));
2022
2023 PAUDMIXSINK pSink = sb16StreamIndexToSink(pThis, pStream->uIdx);
2024 AssertPtrReturn(pSink, VERR_INTERNAL_ERROR_2);
2025
2026 /* We only need to register the AIO update job the first time around as the requence doesn't change. */
2027 int rc;
2028 if (fEnable && !pStream->State.fRegisteredAsyncUpdateJob)
2029 {
2030 rc = AudioMixerSinkAddUpdateJob(pSink, sb16StreamUpdateAsyncIoJob, pStream, RT_MS_1SEC / pStream->uTimerHz);
2031 AssertRC(rc);
2032 pStream->State.fRegisteredAsyncUpdateJob = RT_SUCCESS(rc) || rc == VERR_ALREADY_EXISTS;
2033 }
2034
2035 /* Tell the mixer. */
2036 if (fEnable)
2037 {
2038 rc = AudioMixerSinkStart(pSink);
2039 AssertRCReturn(rc, rc);
2040 }
2041 else
2042 {
2043 /* We need to lock the stream here in order to synchronize the amount of bytes we have to drain
2044 * here with the mixer's async I/O thread. See @bugref{10354}. */
2045 sb16StreamLock(pStream);
2046
2047 uint32_t const cbToDrain = pStream->State.pCircBuf ? (uint32_t)RTCircBufUsed(pStream->State.pCircBuf) : 0;
2048 Log3Func(("cbToDrain=%#RX32\n", cbToDrain));
2049
2050 sb16StreamUnlock(pStream);
2051
2052 rc = AudioMixerSinkDrainAndStop(pSink, cbToDrain);
2053 AssertRCReturn(rc, rc);
2054
2055
2056 }
2057
2058 pStream->State.fEnabled = fEnable;
2059
2060 return rc;
2061}
2062
2063/**
2064 * Retrieves the audio mixer sink of a corresponding SB16 stream.
2065 *
2066 * @returns Pointer to audio mixer sink if found, or NULL if not found / invalid.
2067 * @param pThis The SB16 state.
2068 * @param uIdx Stream index to get audio mixer sink for.
2069 */
2070DECLINLINE(PAUDMIXSINK) sb16StreamIndexToSink(PSB16STATE pThis, uint8_t uIdx)
2071{
2072 AssertReturn(uIdx <= SB16_MAX_STREAMS, NULL);
2073
2074 /* Dead simple for now; make this more sophisticated if we have more stuff to cover. */
2075 if (uIdx == SB16_IDX_OUT)
2076 return pThis->pSinkOut; /* Can be NULL if not configured / set up yet. */
2077
2078 AssertMsgFailed(("No sink attached (yet) for index %RU8\n", uIdx));
2079 return NULL;
2080}
2081
2082/**
2083 * Returns the audio direction of a specified stream descriptor.
2084 *
2085 * @returns Audio direction.
2086 * @param uIdx Stream index to get audio direction for.
2087 */
2088DECLINLINE(PDMAUDIODIR) sb16GetDirFromIndex(uint8_t uIdx)
2089{
2090 AssertReturn(uIdx <= SB16_MAX_STREAMS, PDMAUDIODIR_INVALID);
2091
2092 /* Dead simple for now; make this more sophisticated if we have more stuff to cover. */
2093 if (uIdx == SB16_IDX_OUT)
2094 return PDMAUDIODIR_OUT;
2095
2096 return PDMAUDIODIR_INVALID;
2097}
2098
2099/**
2100 * Creates a SB16 audio stream.
2101 *
2102 * @returns VBox status code.
2103 * @param pThis The SB16 state.
2104 * @param pStream The SB16 stream to create.
2105 * @param uIdx Stream index to assign.
2106 */
2107static int sb16StreamCreate(PSB16STATE pThis, PSB16STREAM pStream, uint8_t uIdx)
2108{
2109 LogFlowFuncEnter();
2110
2111 int rc = RTCritSectInit(&pStream->State.CritSect);
2112 AssertRCReturn(rc, rc);
2113
2114 pStream->Dbg.Runtime.fEnabled = pThis->Dbg.fEnabled;
2115
2116 if (RT_LIKELY(!pStream->Dbg.Runtime.fEnabled))
2117 { /* likely */ }
2118 else
2119 {
2120 int rc2 = AudioHlpFileCreateF(&pStream->Dbg.Runtime.pFileDMA, AUDIOHLPFILE_FLAGS_NONE, AUDIOHLPFILETYPE_WAV,
2121 pThis->Dbg.pszOutPath, AUDIOHLPFILENAME_FLAGS_NONE, 0 /*uInstance*/,
2122 sb16GetDirFromIndex(pStream->uIdx) == PDMAUDIODIR_IN
2123 ? "sb16StreamWriteSD%RU8" : "sb16StreamReadSD%RU8", pStream->uIdx);
2124 AssertRC(rc2);
2125
2126 /* Delete stale debugging files from a former run. */
2127 AudioHlpFileDelete(pStream->Dbg.Runtime.pFileDMA);
2128 }
2129
2130 pStream->uIdx = uIdx;
2131
2132 return VINF_SUCCESS;
2133}
2134
2135/**
2136 * Destroys a SB16 audio stream.
2137 *
2138 * @returns VBox status code.
2139 * @param pDevIns The device instance.
2140 * @param pThis The SB16 state.
2141 * @param pStream The SB16 stream to destroy.
2142 */
2143static int sb16StreamDestroy(PPDMDEVINS pDevIns, PSB16STATE pThis, PSB16STREAM pStream)
2144{
2145 LogFlowFuncEnter();
2146
2147 sb16StreamClose(pDevIns, pThis, pStream);
2148
2149 if (pStream->State.fRegisteredAsyncUpdateJob)
2150 {
2151 PAUDMIXSINK pSink = sb16StreamIndexToSink(pThis, pStream->uIdx);
2152 if (pSink)
2153 AudioMixerSinkRemoveUpdateJob(pSink, sb16StreamUpdateAsyncIoJob, pStream);
2154 pStream->State.fRegisteredAsyncUpdateJob = false;
2155 }
2156
2157 int rc2 = RTCritSectDelete(&pStream->State.CritSect);
2158 AssertRC(rc2);
2159
2160 if (pStream->State.pCircBuf)
2161 {
2162 RTCircBufDestroy(pStream->State.pCircBuf);
2163 pStream->State.pCircBuf = NULL;
2164 }
2165
2166 if (RT_LIKELY(!pStream->Dbg.Runtime.fEnabled))
2167 { /* likely */ }
2168 else
2169 {
2170 AudioHlpFileDestroy(pStream->Dbg.Runtime.pFileDMA);
2171 pStream->Dbg.Runtime.pFileDMA = NULL;
2172 }
2173
2174 pStream->uIdx = UINT8_MAX;
2175
2176 return VINF_SUCCESS;
2177}
2178
2179/**
2180 * Resets a SB16 stream.
2181 *
2182 * @param pThis The SB16 state.
2183 * @param pStream The SB16 stream to reset.
2184 */
2185static void sb16StreamReset(PSB16STATE pThis, PSB16STREAM pStream)
2186{
2187 LogFlowFuncEnter();
2188
2189 PDMDevHlpISASetIrq(pThis->pDevInsR3, pStream->HwCfgRuntime.uIrq, 0);
2190 if (pStream->dma_auto)
2191 {
2192 PDMDevHlpISASetIrq(pThis->pDevInsR3, pStream->HwCfgRuntime.uIrq, 1);
2193 PDMDevHlpISASetIrq(pThis->pDevInsR3, pStream->HwCfgRuntime.uIrq, 0);
2194
2195 pStream->dma_auto = 0;
2196 }
2197
2198 sb16StreamControl(pThis->pDevInsR3, pThis, pStream, false /* fRun */);
2199 sb16StreamEnable(pThis, pStream, false /* fEnable */, false /* fForce */);
2200
2201 switch (pStream->uIdx)
2202 {
2203 case SB16_IDX_OUT:
2204 {
2205 pStream->Cfg.enmDir = PDMAUDIODIR_OUT;
2206 pStream->Cfg.enmPath = PDMAUDIOPATH_OUT_FRONT;
2207
2208 PDMAudioPropsInit(&pStream->Cfg.Props, 1 /* 8-bit */, false /* fSigned */, 1 /* Mono */, 11025 /* uHz */);
2209 RTStrCopy(pStream->Cfg.szName, sizeof(pStream->Cfg.szName), "Output");
2210 break;
2211 }
2212
2213 default:
2214 AssertFailed();
2215 break;
2216 }
2217
2218 pStream->cbDmaLeft = 0;
2219 pStream->cbDmaBlockSize = 0;
2220 pStream->can_write = 1; /** @ŧodo r=andy BUGBUG Figure out why we (still) need this. */
2221
2222 /** @todo Also reset corresponding DSP values here? */
2223}
2224
2225/**
2226 * Locks a SB16 stream for serialized access.
2227 *
2228 * @param pStream The SB16 stream to lock.
2229 */
2230DECLINLINE(void) sb16StreamLock(PSB16STREAM pStream)
2231{
2232 int rc2 = RTCritSectEnter(&pStream->State.CritSect);
2233 AssertRC(rc2);
2234}
2235
2236/**
2237 * Unlocks a formerly locked SB16 stream.
2238 *
2239 * @param pStream The SB16 stream to unlock.
2240 */
2241DECLINLINE(void) sb16StreamUnlock(PSB16STREAM pStream)
2242{
2243 int rc2 = RTCritSectLeave(&pStream->State.CritSect);
2244 AssertRC(rc2);
2245}
2246
2247/**
2248 * Opens a SB16 stream with its current mixer settings.
2249 *
2250 * @returns VBox status code.
2251 * @param pDevIns The device instance.
2252 * @param pThis The SB16 device state.
2253 * @param pStream The SB16 stream to open.
2254 *
2255 * @note This currently only supports the one and only output stream.
2256 */
2257static int sb16StreamOpen(PPDMDEVINS pDevIns, PSB16STATE pThis, PSB16STREAM pStream)
2258{
2259 LogFlowFuncEnter();
2260 AssertLogRelReturn(PDMAudioPropsAreValid(&pStream->Cfg.Props), VERR_INTERNAL_ERROR_5);
2261
2262 switch (pStream->uIdx)
2263 {
2264 case SB16_IDX_OUT:
2265 pStream->Cfg.enmDir = PDMAUDIODIR_OUT;
2266 pStream->Cfg.enmPath = PDMAUDIOPATH_OUT_FRONT;
2267
2268 RTStrCopy(pStream->Cfg.szName, sizeof(pStream->Cfg.szName), "Output");
2269 break;
2270
2271 default:
2272 AssertFailed();
2273 break;
2274 }
2275
2276 LogRel2(("SB16: (Re-)Opening stream '%s' (%RU32Hz, %RU8 channels, %s%RU8)\n", pStream->Cfg.szName, pStream->Cfg.Props.uHz,
2277 PDMAudioPropsChannels(&pStream->Cfg.Props), pStream->Cfg.Props.fSigned ? "S" : "U",
2278 PDMAudioPropsSampleBits(&pStream->Cfg.Props)));
2279
2280 /** @todo r=bird: two DMA periods is probably too little. */
2281 const uint32_t cbCircBuf = PDMAudioPropsMilliToBytes(&pStream->Cfg.Props,
2282 (RT_MS_1SEC / pStream->uTimerHz) * 2 /* Use double buffering here */);
2283 int rc = VINF_SUCCESS;
2284
2285 /**
2286 * Note: Only re-create the DMA buffer if the size actually has changed.
2287 *
2288 * Otherwise do *not* reset the stream's circular buffer here, as the audio mixer still relies on
2289 * previously announced DMA data (via AudioMixerSinkDrainAndStop()) and processes it asynchronously.
2290 * Resetting the buffer here will cause a race condition. See @bugref{10354}. */
2291 if (pStream->State.StatDmaBufSize != cbCircBuf)
2292 {
2293 Log3Func(("Re-creating ring buffer (%#RX32 -> %#RX32)\n", pStream->State.StatDmaBufSize, cbCircBuf));
2294
2295 /* (Re-)create the stream's internal ring buffer. */
2296 if (pStream->State.pCircBuf)
2297 {
2298 RTCircBufDestroy(pStream->State.pCircBuf);
2299 pStream->State.pCircBuf = NULL;
2300 }
2301
2302 rc = RTCircBufCreate(&pStream->State.pCircBuf, cbCircBuf);
2303 AssertRCReturn(rc, rc);
2304 pStream->State.StatDmaBufSize = (uint32_t)RTCircBufSize(pStream->State.pCircBuf);
2305
2306 /* Set scheduling hint. */
2307 pStream->Cfg.Device.cMsSchedulingHint = RT_MS_1SEC / RT_MIN(pStream->uTimerHz, 1);
2308
2309 PAUDMIXSINK pMixerSink = sb16StreamIndexToSink(pThis, pStream->uIdx);
2310 AssertPtrReturn(pMixerSink, VERR_INVALID_POINTER);
2311
2312 sb16RemoveDrvStreams(pDevIns, pThis,
2313 sb16StreamIndexToSink(pThis, pStream->uIdx), pStream->Cfg.enmDir, pStream->Cfg.enmPath);
2314
2315 rc = sb16AddDrvStreams(pDevIns, pThis, pMixerSink, &pStream->Cfg);
2316 if (RT_SUCCESS(rc))
2317 {
2318 if (RT_LIKELY(!pStream->Dbg.Runtime.fEnabled))
2319 { /* likely */ }
2320 else
2321 {
2322 /* Make sure to close + delete a former debug file, as the PCM format has changed (e.g. U8 -> S16). */
2323 if (AudioHlpFileIsOpen(pStream->Dbg.Runtime.pFileDMA))
2324 {
2325 AudioHlpFileClose(pStream->Dbg.Runtime.pFileDMA);
2326 AudioHlpFileDelete(pStream->Dbg.Runtime.pFileDMA);
2327 }
2328
2329 int rc2 = AudioHlpFileOpen(pStream->Dbg.Runtime.pFileDMA, AUDIOHLPFILE_DEFAULT_OPEN_FLAGS,
2330 &pStream->Cfg.Props);
2331 AssertRC(rc2);
2332 }
2333 }
2334 }
2335
2336 LogFlowFuncLeaveRC(rc);
2337 return rc;
2338}
2339
2340/**
2341 * Closes a SB16 stream.
2342 *
2343 * @param pDevIns The device instance.
2344 * @param pThis SB16 state.
2345 * @param pStream The SB16 stream to close.
2346 */
2347static void sb16StreamClose(PPDMDEVINS pDevIns, PSB16STATE pThis, PSB16STREAM pStream)
2348{
2349 RT_NOREF(pDevIns, pThis, pStream);
2350
2351 LogFlowFuncEnter();
2352
2353 /* Nothing to do in here right now. */
2354}
2355
2356static void sb16StreamTransferScheduleNext(PSB16STATE pThis, PSB16STREAM pStream, uint32_t cbBytes)
2357{
2358 RT_NOREF(pStream);
2359
2360 uint64_t const uTimerHz = PDMDevHlpTimerGetFreq(pThis->pDevInsR3, pThis->hTimerIRQ);
2361
2362 const uint64_t usBytes = PDMAudioPropsBytesToMicro(&pStream->Cfg.Props, cbBytes);
2363 const uint64_t cTransferTicks = PDMDevHlpTimerFromMicro(pThis->pDevInsR3, pThis->hTimerIRQ, usBytes);
2364
2365 LogFlowFunc(("%RU32 bytes -> %RU64 ticks\n", cbBytes, cTransferTicks));
2366
2367 if (cTransferTicks < uTimerHz / 1024) /** @todo Explain this. */
2368 {
2369 LogFlowFunc(("IRQ\n"));
2370 PDMDevHlpISASetIrq(pThis->pDevInsR3, pStream->HwCfgRuntime.uIrq, 1);
2371 }
2372 else
2373 {
2374 LogFlowFunc(("Scheduled\n"));
2375 PDMDevHlpTimerSetRelative(pThis->pDevInsR3, pThis->hTimerIRQ, cTransferTicks, NULL);
2376 }
2377}
2378
2379
2380/**
2381 * Output streams: Pushes data to the mixer.
2382 *
2383 * @param pStream The SB16 stream.
2384 * @param pSink The mixer sink to push to.
2385 *
2386 * @thread MixAIO-n
2387 *
2388 * @note Takes the stream's lock.
2389 */
2390static void sb16StreamPushToMixer(PSB16STREAM pStream, PAUDMIXSINK pSink)
2391{
2392 sb16StreamLock(pStream);
2393
2394#ifdef LOG_ENABLED
2395 uint64_t const offReadOld = pStream->State.offRead;
2396 Log3Func(("[SD%RU8] cbCircBufUsed=%#RX64\n", pStream->uIdx, RTCircBufUsed(pStream->State.pCircBuf)));
2397#endif
2398 pStream->State.offRead = AudioMixerSinkTransferFromCircBuf(pSink,
2399 pStream->State.pCircBuf,
2400 pStream->State.offRead,
2401 pStream->uIdx,
2402 /** @todo pStream->Dbg.Runtime.fEnabled
2403 ? pStream->Dbg.Runtime.pFileStream :*/ NULL);
2404
2405 Log3Func(("[SD%RU8] transferred=%#RX64 bytes -> @%#RX64 (cbCircBufUsed=%#RX64)\n", pStream->uIdx,
2406 pStream->State.offRead - offReadOld, pStream->State.offRead, RTCircBufUsed(pStream->State.pCircBuf)));
2407
2408 /* Update buffer stats. */
2409 pStream->State.StatDmaBufUsed = (uint32_t)RTCircBufUsed(pStream->State.pCircBuf);
2410
2411 sb16StreamUnlock(pStream);
2412}
2413
2414
2415/**
2416 * @callback_method_impl{FNAUDMIXSINKUPDATE}
2417 *
2418 * For output streams this moves data from the internal DMA buffer (in which
2419 * sb16StreamDoDmaOutput put it), thru the mixer and to the various backend
2420 * audio devices.
2421 *
2422 * @thread MixAIO-n
2423 */
2424static DECLCALLBACK(void) sb16StreamUpdateAsyncIoJob(PPDMDEVINS pDevIns, PAUDMIXSINK pSink, void *pvUser)
2425{
2426 PSB16STATE const pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE);
2427 PSB16STREAM const pStream = (PSB16STREAM)pvUser;
2428 Assert(pStream->uIdx == (uintptr_t)(pStream - &pThis->aStreams[0]));
2429 Assert(pSink == sb16StreamIndexToSink(pThis, pStream->uIdx));
2430 RT_NOREF(pThis);
2431
2432 /*
2433 * Output.
2434 */
2435 if (sb16GetDirFromIndex(pStream->uIdx) == PDMAUDIODIR_OUT)
2436 sb16StreamPushToMixer(pStream, pSink);
2437 /*
2438 * No input streams at present.
2439 */
2440 else
2441 AssertFailed();
2442}
2443
2444
2445/*********************************************************************************************************************************
2446* Saved state handling *
2447*********************************************************************************************************************************/
2448
2449/**
2450 * @callback_method_impl{FNSSMDEVLIVEEXEC}
2451 */
2452static DECLCALLBACK(int) sb16LiveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uPass)
2453{
2454 RT_NOREF(uPass);
2455
2456 PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE);
2457 PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
2458
2459 /** Currently the saved state only contains the one-and-only output stream. */
2460 PSB16STREAM pStream = &pThis->aStreams[SB16_IDX_OUT];
2461
2462 pHlp->pfnSSMPutS32(pSSM, pStream->HwCfgDefault.uIrq);
2463 pHlp->pfnSSMPutS32(pSSM, pStream->HwCfgDefault.uDmaChanLow);
2464 pHlp->pfnSSMPutS32(pSSM, pStream->HwCfgDefault.uDmaChanHigh);
2465 pHlp->pfnSSMPutS32(pSSM, pStream->HwCfgDefault.uPort);
2466 pHlp->pfnSSMPutS32(pSSM, pStream->HwCfgDefault.uVer);
2467 return VINF_SSM_DONT_CALL_AGAIN;
2468}
2469
2470/**
2471 * Worker for sb16SaveExec.
2472 */
2473static int sb16Save(PCPDMDEVHLPR3 pHlp, PSSMHANDLE pSSM, PSB16STATE pThis)
2474{
2475 /* The saved state only contains the one-and-only output stream. */
2476 PSB16STREAM pStream = &pThis->aStreams[SB16_IDX_OUT];
2477
2478 pHlp->pfnSSMPutS32(pSSM, pStream->HwCfgRuntime.uIrq);
2479 pHlp->pfnSSMPutS32(pSSM, pStream->HwCfgRuntime.uDmaChanLow);
2480 pHlp->pfnSSMPutS32(pSSM, pStream->HwCfgRuntime.uDmaChanHigh);
2481 pHlp->pfnSSMPutS32(pSSM, pStream->HwCfgRuntime.uPort);
2482 pHlp->pfnSSMPutS32(pSSM, pStream->HwCfgRuntime.uVer);
2483 pHlp->pfnSSMPutS32(pSSM, pThis->dsp_in_idx);
2484 pHlp->pfnSSMPutS32(pSSM, pThis->dsp_out_data_len);
2485
2486 pHlp->pfnSSMPutS32(pSSM, PDMAudioPropsChannels(&pStream->Cfg.Props) >= 2 ? 1 : 0);
2487 pHlp->pfnSSMPutS32(pSSM, PDMAudioPropsIsSigned(&pStream->Cfg.Props) ? 1 : 0);
2488 pHlp->pfnSSMPutS32(pSSM, PDMAudioPropsSampleBits(&pStream->Cfg.Props));
2489 pHlp->pfnSSMPutU32(pSSM, 0); /* Legacy; was PDMAUDIOFMT, unused now. */
2490
2491 pHlp->pfnSSMPutS32(pSSM, pStream->dma_auto);
2492 pHlp->pfnSSMPutS32(pSSM, pStream->cbDmaBlockSize);
2493 pHlp->pfnSSMPutS32(pSSM, pStream->fifo);
2494 pHlp->pfnSSMPutS32(pSSM, PDMAudioPropsHz(&pStream->Cfg.Props));
2495 pHlp->pfnSSMPutS32(pSSM, pStream->time_const);
2496 pHlp->pfnSSMPutS32(pSSM, 0); /* Legacy; was speaker control (on/off) for output stream. */
2497 pHlp->pfnSSMPutS32(pSSM, pThis->dsp_in_needed_bytes);
2498 pHlp->pfnSSMPutS32(pSSM, pThis->cmd);
2499 pHlp->pfnSSMPutS32(pSSM, pStream->fDmaUseHigh);
2500 pHlp->pfnSSMPutS32(pSSM, pThis->highspeed);
2501 pHlp->pfnSSMPutS32(pSSM, pStream->can_write);
2502 pHlp->pfnSSMPutS32(pSSM, pThis->v2x6);
2503
2504 pHlp->pfnSSMPutU8 (pSSM, pThis->csp_param);
2505 pHlp->pfnSSMPutU8 (pSSM, pThis->csp_value);
2506 pHlp->pfnSSMPutU8 (pSSM, pThis->csp_mode);
2507 pHlp->pfnSSMPutU8 (pSSM, pThis->csp_param); /* Bug compatible! */
2508 pHlp->pfnSSMPutMem(pSSM, pThis->csp_regs, 256);
2509 pHlp->pfnSSMPutU8 (pSSM, pThis->csp_index);
2510 pHlp->pfnSSMPutMem(pSSM, pThis->csp_reg83, 4);
2511 pHlp->pfnSSMPutS32(pSSM, pThis->csp_reg83r);
2512 pHlp->pfnSSMPutS32(pSSM, pThis->csp_reg83w);
2513
2514 pHlp->pfnSSMPutMem(pSSM, pThis->dsp_in_data, sizeof(pThis->dsp_in_data));
2515 pHlp->pfnSSMPutMem(pSSM, pThis->dsp_out_data, sizeof(pThis->dsp_out_data));
2516 pHlp->pfnSSMPutU8 (pSSM, pThis->test_reg);
2517 pHlp->pfnSSMPutU8 (pSSM, pThis->last_read_byte);
2518
2519 pHlp->pfnSSMPutS32(pSSM, pThis->nzero);
2520 pHlp->pfnSSMPutS32(pSSM, pStream->cbDmaLeft);
2521 pHlp->pfnSSMPutS32(pSSM, pStream->State.fEnabled ? 1 : 0);
2522 /* The stream's bitrate. Needed for backwards (legacy) compatibility. */
2523 pHlp->pfnSSMPutS32(pSSM, AudioHlpCalcBitrate(PDMAudioPropsSampleBits(&pThis->aStreams[SB16_IDX_OUT].Cfg.Props),
2524 PDMAudioPropsHz(&pThis->aStreams[SB16_IDX_OUT].Cfg.Props),
2525 PDMAudioPropsChannels(&pThis->aStreams[SB16_IDX_OUT].Cfg.Props)));
2526 /* Block size alignment, superfluous and thus not saved anymore. Needed for backwards (legacy) compatibility. */
2527 pHlp->pfnSSMPutS32(pSSM, 0);
2528
2529 pHlp->pfnSSMPutS32(pSSM, pThis->mixer_nreg);
2530 return pHlp->pfnSSMPutMem(pSSM, pThis->mixer_regs, 256);
2531}
2532
2533/**
2534 * @callback_method_impl{FNSSMDEVSAVEEXEC}
2535 */
2536static DECLCALLBACK(int) sb16SaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
2537{
2538 PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE);
2539 PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
2540
2541 sb16LiveExec(pDevIns, pSSM, 0);
2542 return sb16Save(pHlp, pSSM, pThis);
2543}
2544
2545/**
2546 * Worker for sb16LoadExec.
2547 */
2548static int sb16Load(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, PSB16STATE pThis)
2549{
2550 PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
2551 PSB16STREAM pStream = &pThis->aStreams[SB16_IDX_OUT]; /* The saved state only contains the one-and-only output stream. */
2552 int rc;
2553
2554 int32_t i32Tmp;
2555 pHlp->pfnSSMGetS32(pSSM, &i32Tmp);
2556 pStream->HwCfgRuntime.uIrq = i32Tmp; /* IRQ. */
2557 pHlp->pfnSSMGetS32(pSSM, &i32Tmp);
2558 pStream->HwCfgRuntime.uDmaChanLow = i32Tmp; /* Low (8-bit) DMA channel. */
2559 pHlp->pfnSSMGetS32(pSSM, &i32Tmp);
2560 pStream->HwCfgRuntime.uDmaChanHigh = i32Tmp; /* High (16-bit) DMA channel. */
2561 pHlp->pfnSSMGetS32(pSSM, &i32Tmp); /* Used I/O port. */
2562 pStream->HwCfgRuntime.uPort = i32Tmp;
2563 pHlp->pfnSSMGetS32(pSSM, &i32Tmp); /* DSP version running. */
2564 pStream->HwCfgRuntime.uVer = i32Tmp;
2565 pHlp->pfnSSMGetS32(pSSM, &pThis->dsp_in_idx);
2566 pHlp->pfnSSMGetS32(pSSM, &pThis->dsp_out_data_len);
2567
2568 rc = pHlp->pfnSSMGetS32(pSSM, &i32Tmp); /* Output stream: Number of channels. */
2569 AssertRCReturn(rc, rc);
2570 AssertReturn((uint32_t)i32Tmp <= 1, VERR_INVALID_PARAMETER); /* Paranoia. */
2571 if (i32Tmp) /* PDMAudioPropsSetChannels() will assert if channels are 0 (will be re-set on DMA run command). */
2572 PDMAudioPropsSetChannels(&pStream->Cfg.Props, (uint8_t)i32Tmp);
2573 pHlp->pfnSSMGetS32(pSSM, &i32Tmp); /* Output stream: Signed format bit. */
2574 pStream->Cfg.Props.fSigned = i32Tmp != 0;
2575 rc = pHlp->pfnSSMGetS32(pSSM, &i32Tmp); /* Output stream: Sample size in bits. */
2576 AssertRCReturn(rc, rc);
2577 if (i32Tmp) /* PDMAudioPropsSetSampleSize() will assert if sample size is 0 (will be re-set on DMA run command). */
2578 PDMAudioPropsSetSampleSize(&pStream->Cfg.Props, (uint8_t)(i32Tmp / 8));
2579
2580 pHlp->pfnSSMSkip (pSSM, sizeof(int32_t)); /* Legacy; was PDMAUDIOFMT, unused now. */
2581 pHlp->pfnSSMGetS32(pSSM, &pStream->dma_auto);
2582 pHlp->pfnSSMGetS32(pSSM, &pStream->cbDmaBlockSize);
2583 pHlp->pfnSSMGetS32(pSSM, &pStream->fifo);
2584 pHlp->pfnSSMGetS32(pSSM, &i32Tmp); pStream->Cfg.Props.uHz = i32Tmp;
2585 pHlp->pfnSSMGetS32(pSSM, &pStream->time_const);
2586 pHlp->pfnSSMSkip (pSSM, sizeof(int32_t)); /* Legacy; was speaker (on / off) for output stream. */
2587 pHlp->pfnSSMGetS32(pSSM, &pThis->dsp_in_needed_bytes);
2588 pHlp->pfnSSMGetS32(pSSM, &pThis->cmd);
2589 pHlp->pfnSSMGetS32(pSSM, &pStream->fDmaUseHigh); /* Output stream: Whether to use the high or low DMA channel. */
2590 pHlp->pfnSSMGetS32(pSSM, &pThis->highspeed);
2591 pHlp->pfnSSMGetS32(pSSM, &pStream->can_write);
2592 pHlp->pfnSSMGetS32(pSSM, &pThis->v2x6);
2593
2594 pHlp->pfnSSMGetU8 (pSSM, &pThis->csp_param);
2595 pHlp->pfnSSMGetU8 (pSSM, &pThis->csp_value);
2596 pHlp->pfnSSMGetU8 (pSSM, &pThis->csp_mode);
2597 pHlp->pfnSSMGetU8 (pSSM, &pThis->csp_param); /* Bug compatible! */
2598 pHlp->pfnSSMGetMem(pSSM, pThis->csp_regs, 256);
2599 pHlp->pfnSSMGetU8 (pSSM, &pThis->csp_index);
2600 pHlp->pfnSSMGetMem(pSSM, pThis->csp_reg83, 4);
2601 pHlp->pfnSSMGetS32(pSSM, &pThis->csp_reg83r);
2602 pHlp->pfnSSMGetS32(pSSM, &pThis->csp_reg83w);
2603
2604 pHlp->pfnSSMGetMem(pSSM, pThis->dsp_in_data, sizeof(pThis->dsp_in_data));
2605 pHlp->pfnSSMGetMem(pSSM, pThis->dsp_out_data, sizeof(pThis->dsp_out_data));
2606 pHlp->pfnSSMGetU8 (pSSM, &pThis->test_reg);
2607 pHlp->pfnSSMGetU8 (pSSM, &pThis->last_read_byte);
2608
2609 pHlp->pfnSSMGetS32(pSSM, &pThis->nzero);
2610 pHlp->pfnSSMGetS32(pSSM, &pStream->cbDmaLeft);
2611 pHlp->pfnSSMGetS32(pSSM, &i32Tmp); /* Output stream: DMA currently running bit. */
2612 const bool fStreamEnabled = i32Tmp != 0;
2613 pHlp->pfnSSMSkip (pSSM, sizeof(int32_t)); /* Legacy; was the output stream's current bitrate (in bytes). */
2614 pHlp->pfnSSMSkip (pSSM, sizeof(int32_t)); /* Legacy; was the output stream's DMA block alignment. */
2615
2616 int32_t mixer_nreg = 0;
2617 rc = pHlp->pfnSSMGetS32(pSSM, &mixer_nreg);
2618 AssertRCReturn(rc, rc);
2619 pThis->mixer_nreg = (uint8_t)mixer_nreg;
2620 rc = pHlp->pfnSSMGetMem(pSSM, pThis->mixer_regs, 256);
2621 AssertRCReturn(rc, rc);
2622
2623 if (fStreamEnabled)
2624 {
2625 /* Sanity: If stream is going be enabled, PCM props must be valid. Otherwise the saved state is borked somehow. */
2626 AssertMsgReturn(AudioHlpPcmPropsAreValidAndSupported(&pStream->Cfg.Props),
2627 ("PCM properties for stream #%RU8 are invalid\n", pStream->uIdx), VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
2628 sb16StreamControl(pDevIns, pThis, pStream, true /* fRun */);
2629 }
2630
2631 /* Update the master (mixer) and PCM out volumes. */
2632 sb16UpdateVolume(pThis);
2633
2634 return VINF_SUCCESS;
2635}
2636
2637/**
2638 * @callback_method_impl{FNSSMDEVLOADEXEC}
2639 */
2640static DECLCALLBACK(int) sb16LoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass)
2641{
2642 PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE);
2643 PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
2644
2645 AssertMsgReturn( uVersion == SB16_SAVE_STATE_VERSION
2646 || uVersion == SB16_SAVE_STATE_VERSION_VBOX_30,
2647 ("%u\n", uVersion),
2648 VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION);
2649 if (uVersion > SB16_SAVE_STATE_VERSION_VBOX_30)
2650 {
2651 /** Currently the saved state only contains the one-and-only output stream. */
2652 PSB16STREAM pStream = &pThis->aStreams[SB16_IDX_OUT];
2653
2654 int32_t irq;
2655 pHlp->pfnSSMGetS32(pSSM, &irq);
2656 int32_t dma;
2657 pHlp->pfnSSMGetS32(pSSM, &dma);
2658 int32_t hdma;
2659 pHlp->pfnSSMGetS32(pSSM, &hdma);
2660 int32_t port;
2661 pHlp->pfnSSMGetS32(pSSM, &port);
2662 int32_t ver;
2663 int rc = pHlp->pfnSSMGetS32(pSSM, &ver);
2664 AssertRCReturn (rc, rc);
2665
2666 if ( irq != pStream->HwCfgDefault.uIrq
2667 || dma != pStream->HwCfgDefault.uDmaChanLow
2668 || hdma != pStream->HwCfgDefault.uDmaChanHigh
2669 || port != pStream->HwCfgDefault.uPort
2670 || ver != pStream->HwCfgDefault.uVer)
2671 {
2672 return pHlp->pfnSSMSetCfgError(pSSM, RT_SRC_POS,
2673 N_("config changed: irq=%x/%x dma=%x/%x hdma=%x/%x port=%x/%x ver=%x/%x (saved/config)"),
2674 irq, pStream->HwCfgDefault.uIrq,
2675 dma, pStream->HwCfgDefault.uDmaChanLow,
2676 hdma, pStream->HwCfgDefault.uDmaChanHigh,
2677 port, pStream->HwCfgDefault.uPort,
2678 ver, pStream->HwCfgDefault.uVer);
2679 }
2680 }
2681
2682 if (uPass != SSM_PASS_FINAL)
2683 return VINF_SUCCESS;
2684
2685 return sb16Load(pDevIns, pSSM, pThis);
2686}
2687
2688
2689/*********************************************************************************************************************************
2690* Debug Info Items *
2691*********************************************************************************************************************************/
2692
2693/**
2694 * @callback_method_impl{FNDBGFHANDLERDEV, sb16mixer}
2695 */
2696static DECLCALLBACK(void) sb16DbgInfoMixer(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs)
2697{
2698 PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE);
2699 if (pThis->pMixer)
2700 AudioMixerDebug(pThis->pMixer, pHlp, pszArgs);
2701 else
2702 pHlp->pfnPrintf(pHlp, "Mixer not available\n");
2703}
2704
2705
2706/*********************************************************************************************************************************
2707* IBase implementation *
2708*********************************************************************************************************************************/
2709
2710/**
2711 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
2712 */
2713static DECLCALLBACK(void *) sb16QueryInterface(struct PDMIBASE *pInterface, const char *pszIID)
2714{
2715 PSB16STATE pThis = RT_FROM_MEMBER(pInterface, SB16STATE, IBase);
2716 Assert(&pThis->IBase == pInterface);
2717
2718 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThis->IBase);
2719 return NULL;
2720}
2721
2722
2723/*********************************************************************************************************************************
2724* Device (PDM) handling *
2725*********************************************************************************************************************************/
2726
2727/**
2728 * Worker for sb16Construct() and sb16Attach().
2729 *
2730 * @returns VBox status code.
2731 * @param pThis SB16 state.
2732 * @param uLUN The logical unit which is being detached.
2733 * @param ppDrv Attached driver instance on success. Optional.
2734 */
2735static int sb16AttachInternal(PSB16STATE pThis, unsigned uLUN, PSB16DRIVER *ppDrv)
2736{
2737 /*
2738 * Allocate a new driver structure and try attach the driver.
2739 */
2740 PSB16DRIVER pDrv = (PSB16DRIVER)RTMemAllocZ(sizeof(SB16DRIVER));
2741 AssertPtrReturn(pDrv, VERR_NO_MEMORY);
2742 RTStrPrintf(pDrv->szDesc, sizeof(pDrv->szDesc), "Audio driver port (SB16) for LUN #%u", uLUN);
2743
2744 PPDMIBASE pDrvBase;
2745 int rc = PDMDevHlpDriverAttach(pThis->pDevInsR3, uLUN, &pThis->IBase, &pDrvBase, pDrv->szDesc);
2746 if (RT_SUCCESS(rc))
2747 {
2748 pDrv->pConnector = PDMIBASE_QUERY_INTERFACE(pDrvBase, PDMIAUDIOCONNECTOR);
2749 AssertPtr(pDrv->pConnector);
2750 if (RT_VALID_PTR(pDrv->pConnector))
2751 {
2752 pDrv->pDrvBase = pDrvBase;
2753 pDrv->pSB16State = pThis;
2754 pDrv->uLUN = uLUN;
2755
2756 /* Attach to driver list if not attached yet. */
2757 if (!pDrv->fAttached)
2758 {
2759 RTListAppend(&pThis->lstDrv, &pDrv->Node);
2760 pDrv->fAttached = true;
2761 }
2762
2763 if (ppDrv)
2764 *ppDrv = pDrv;
2765 LogFunc(("LUN#%u: returns VINF_SUCCESS (pCon=%p)\n", uLUN, pDrv->pConnector));
2766 return VINF_SUCCESS;
2767 }
2768 rc = VERR_PDM_MISSING_INTERFACE_BELOW;
2769 }
2770 else if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
2771 LogFunc(("No attached driver for LUN #%u\n", uLUN));
2772 else
2773 LogFunc(("Failed to attached driver for LUN #%u: %Rrc\n", uLUN, rc));
2774 RTMemFree(pDrv);
2775
2776 LogFunc(("LUN#%u: rc=%Rrc\n", uLUN, rc));
2777 return rc;
2778}
2779
2780/**
2781 * @interface_method_impl{PDMDEVREG,pfnAttach}
2782 */
2783static DECLCALLBACK(int) sb16Attach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags)
2784{
2785 PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE);
2786 RT_NOREF(fFlags);
2787 LogFunc(("iLUN=%u, fFlags=%#x\n", iLUN, fFlags));
2788
2789 /** @todo r=andy Any locking required here? */
2790
2791 PSB16DRIVER pDrv;
2792 int rc = sb16AttachInternal(pThis, iLUN, &pDrv);
2793 if (RT_SUCCESS(rc))
2794 {
2795 int rc2 = sb16AddDrv(pDevIns, pThis, pDrv);
2796 if (RT_FAILURE(rc2))
2797 LogFunc(("sb16AddDrv failed with %Rrc (ignored)\n", rc2));
2798 }
2799
2800 return rc;
2801}
2802
2803/**
2804 * @interface_method_impl{PDMDEVREG,pfnDetach}
2805 */
2806static DECLCALLBACK(void) sb16Detach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags)
2807{
2808 PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE);
2809 RT_NOREF(fFlags);
2810
2811 LogFunc(("iLUN=%u, fFlags=0x%x\n", iLUN, fFlags));
2812
2813 PSB16DRIVER pDrv;
2814 RTListForEach(&pThis->lstDrv, pDrv, SB16DRIVER, Node)
2815 {
2816 if (pDrv->uLUN == iLUN)
2817 {
2818 sb16RemoveDrv(pDevIns, pThis, pDrv);
2819 RTMemFree(pDrv);
2820 return;
2821 }
2822 }
2823 LogFunc(("LUN#%u was not found\n", iLUN));
2824}
2825
2826
2827/**
2828 * @interface_method_impl{PDMDEVREG,pfnReset}
2829 */
2830static DECLCALLBACK(void) sb16DevReset(PPDMDEVINS pDevIns)
2831{
2832 PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE);
2833
2834 LogRel2(("SB16: Reset\n"));
2835
2836 pThis->mixer_regs[0x82] = 0;
2837 pThis->csp_regs[5] = 1;
2838 pThis->csp_regs[9] = 0xf8;
2839
2840 pThis->dsp_in_idx = 0;
2841 pThis->dsp_out_data_len = 0;
2842 pThis->dsp_in_needed_bytes = 0;
2843 pThis->nzero = 0;
2844 pThis->highspeed = 0;
2845 pThis->v2x6 = 0;
2846 pThis->cmd = -1;
2847
2848 sb16MixerReset(pThis);
2849 sb16SpeakerControl(pThis, false /* fOn */);
2850 sb16DspCmdResetLegacy(pThis);
2851}
2852
2853/**
2854 * Powers off the device.
2855 *
2856 * @param pDevIns Device instance to power off.
2857 */
2858static DECLCALLBACK(void) sb16PowerOff(PPDMDEVINS pDevIns)
2859{
2860 PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE);
2861
2862 LogRel2(("SB16: Powering off ...\n"));
2863
2864 /*
2865 * Destroy all streams.
2866 */
2867 for (unsigned i = 0; i < SB16_MAX_STREAMS; i++)
2868 sb16StreamDestroy(pDevIns, pThis, &pThis->aStreams[i]);
2869
2870 /*
2871 * Destroy all sinks.
2872 */
2873 if (pThis->pSinkOut)
2874 {
2875 AudioMixerSinkDestroy(pThis->pSinkOut, pDevIns);
2876 pThis->pSinkOut = NULL;
2877 }
2878 /** @todo Ditto for sinks. */
2879
2880 /*
2881 * Note: Destroy the mixer while powering off and *not* in sb16Destruct,
2882 * giving the mixer the chance to release any references held to
2883 * PDM audio streams it maintains.
2884 */
2885 if (pThis->pMixer)
2886 {
2887 AudioMixerDestroy(pThis->pMixer, pDevIns);
2888 pThis->pMixer = NULL;
2889 }
2890}
2891
2892/**
2893 * @interface_method_impl{PDMDEVREG,pfnDestruct}
2894 */
2895static DECLCALLBACK(int) sb16Destruct(PPDMDEVINS pDevIns)
2896{
2897 PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns); /* this shall come first */
2898 PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE);
2899
2900 LogFlowFuncEnter();
2901
2902 PSB16DRIVER pDrv;
2903 while (!RTListIsEmpty(&pThis->lstDrv))
2904 {
2905 pDrv = RTListGetFirst(&pThis->lstDrv, SB16DRIVER, Node);
2906
2907 RTListNodeRemove(&pDrv->Node);
2908 RTMemFree(pDrv);
2909 }
2910
2911 /* We don't always go via PowerOff, so make sure the mixer is destroyed. */
2912 if (pThis->pMixer)
2913 {
2914 AudioMixerDestroy(pThis->pMixer, pDevIns);
2915 pThis->pMixer = NULL;
2916 }
2917
2918 return VINF_SUCCESS;
2919}
2920
2921/**
2922 * @interface_method_impl{PDMDEVREGR3,pfnConstruct}
2923 */
2924static DECLCALLBACK(int) sb16Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg)
2925{
2926 PDMDEV_CHECK_VERSIONS_RETURN(pDevIns); /* this shall come first */
2927 PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE);
2928 PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
2929 RT_NOREF(iInstance);
2930
2931 Assert(iInstance == 0);
2932
2933 /*
2934 * Initialize the data so sb16Destruct runs without a hitch if we return early.
2935 */
2936 pThis->pDevInsR3 = pDevIns;
2937 pThis->IBase.pfnQueryInterface = sb16QueryInterface;
2938 pThis->cmd = -1;
2939
2940 pThis->csp_regs[5] = 1;
2941 pThis->csp_regs[9] = 0xf8;
2942
2943 RTListInit(&pThis->lstDrv);
2944
2945 /*
2946 * Validate and read config data.
2947 */
2948 /* Note: For now we only support the one-and-only output stream. */
2949 PSB16STREAM pStream = &pThis->aStreams[SB16_IDX_OUT];
2950
2951 PDMDEV_VALIDATE_CONFIG_RETURN(pDevIns, "IRQ|DMA|DMA16|Port|Version|TimerHz|DebugEnabled|DebugPathOut", "");
2952 int rc = pHlp->pfnCFGMQueryU8Def(pCfg, "IRQ", &pStream->HwCfgDefault.uIrq, 5);
2953 if (RT_FAILURE(rc))
2954 return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: Failed to get the \"IRQ\" value"));
2955 /* Sanity-check supported SB16 IRQs. */
2956 if ( 2 != pStream->HwCfgDefault.uIrq
2957 && 5 != pStream->HwCfgDefault.uIrq
2958 && 7 != pStream->HwCfgDefault.uIrq
2959 && 10 != pStream->HwCfgDefault.uIrq)
2960 return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: Invalid \"IRQ\" value."));
2961 pStream->HwCfgRuntime.uIrq = pStream->HwCfgDefault.uIrq;
2962
2963 rc = pHlp->pfnCFGMQueryU8Def(pCfg, "DMA", &pStream->HwCfgDefault.uDmaChanLow, 1);
2964 if (RT_FAILURE(rc))
2965 return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: Failed to get the \"DMA\" value"));
2966 if ( 0 != pStream->HwCfgDefault.uDmaChanLow
2967 && 1 != pStream->HwCfgDefault.uDmaChanLow
2968 && 3 != pStream->HwCfgDefault.uDmaChanLow)
2969 return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: Invalid \"DMA\" value."));
2970 pStream->HwCfgRuntime.uDmaChanLow = pStream->HwCfgDefault.uDmaChanLow;
2971
2972 rc = pHlp->pfnCFGMQueryU8Def(pCfg, "DMA16", &pStream->HwCfgDefault.uDmaChanHigh, 5);
2973 if (RT_FAILURE(rc))
2974 return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: Failed to get the \"DMA16\" value"));
2975 if ( 5 != pStream->HwCfgDefault.uDmaChanHigh
2976 && 6 != pStream->HwCfgDefault.uDmaChanHigh
2977 && 7 != pStream->HwCfgDefault.uDmaChanHigh)
2978 return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: Invalid \"DMA16\" value."));
2979 pStream->HwCfgRuntime.uDmaChanHigh = pStream->HwCfgDefault.uDmaChanHigh;
2980
2981 rc = pHlp->pfnCFGMQueryPortDef(pCfg, "Port", &pStream->HwCfgDefault.uPort, 0x220);
2982 if (RT_FAILURE(rc))
2983 return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: Failed to get the \"Port\" value"));
2984 /* Sanity-check supported SB16 ports. */
2985 if ( 0x220 != pStream->HwCfgDefault.uPort
2986 && 0x240 != pStream->HwCfgDefault.uPort
2987 && 0x260 != pStream->HwCfgDefault.uPort
2988 && 0x280 != pStream->HwCfgDefault.uPort)
2989 return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: Invalid \"Port\" value. Did you specify it as a hex value (e.g. 0x220)?"));
2990 pStream->HwCfgRuntime.uPort = pStream->HwCfgDefault.uPort;
2991
2992 rc = pHlp->pfnCFGMQueryU16Def(pCfg, "Version", &pStream->HwCfgDefault.uVer, 0x0405);
2993 if (RT_FAILURE(rc))
2994 return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: Failed to get the \"Version\" value"));
2995 pStream->HwCfgRuntime.uVer = pStream->HwCfgDefault.uVer;
2996
2997 rc = pHlp->pfnCFGMQueryU16Def(pCfg, "TimerHz", &pStream->uTimerHz, SB16_TIMER_HZ_DEFAULT);
2998 if (RT_FAILURE(rc))
2999 return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: failed to read Hertz rate as unsigned integer"));
3000 if (pStream->uTimerHz == 0)
3001 return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: Hertz rate is invalid"));
3002 if (pStream->uTimerHz > 2048)
3003 return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: Maximum Hertz rate is 2048"));
3004
3005 rc = pHlp->pfnCFGMQueryBoolDef(pCfg, "DebugEnabled", &pThis->Dbg.fEnabled, false);
3006 if (RT_FAILURE(rc))
3007 return PDMDEV_SET_ERROR(pDevIns, rc,
3008 N_("SB16 configuration error: failed to read debugging enabled flag as boolean"));
3009
3010 rc = pHlp->pfnCFGMQueryStringAllocDef(pCfg, "DebugPathOut", &pThis->Dbg.pszOutPath, NULL);
3011 if (RT_FAILURE(rc))
3012 return PDMDEV_SET_ERROR(pDevIns, rc,
3013 N_("SB16 configuration error: failed to read debugging output path flag as string"));
3014
3015 if (pThis->Dbg.fEnabled)
3016 LogRel2(("SB16: Debug output will be saved to '%s'\n", pThis->Dbg.pszOutPath));
3017
3018 /*
3019 * Create internal software mixer.
3020 * Must come before we do the device's mixer reset.
3021 */
3022 rc = AudioMixerCreate("SB16 Mixer", 0 /* uFlags */, &pThis->pMixer);
3023 AssertRCReturn(rc, rc);
3024
3025 AssertRCReturn(rc, rc);
3026 rc = AudioMixerCreateSink(pThis->pMixer, "PCM Output",
3027 PDMAUDIODIR_OUT, pDevIns, &pThis->pSinkOut);
3028 AssertRCReturn(rc, rc);
3029
3030 /*
3031 * Create all hardware streams.
3032 * For now we have one stream only, namely the output (playback) stream.
3033 */
3034 AssertCompile(RT_ELEMENTS(pThis->aStreams) == SB16_MAX_STREAMS);
3035 for (unsigned i = 0; i < SB16_MAX_STREAMS; i++)
3036 {
3037 rc = sb16StreamCreate(pThis, &pThis->aStreams[i], i /* uIdx */);
3038 AssertRCReturn(rc, rc);
3039 }
3040
3041 /*
3042 * Setup the mixer now that we've got the irq and dma channel numbers.
3043 */
3044 pThis->mixer_regs[0x80] = magic_of_irq(pStream->HwCfgRuntime.uIrq);
3045 pThis->mixer_regs[0x81] = (1 << pStream->HwCfgRuntime.uDmaChanLow) | (1 << pStream->HwCfgRuntime.uDmaChanHigh);
3046 pThis->mixer_regs[0x82] = 2 << 5;
3047
3048 /*
3049 * Perform a device reset before we set up the mixer below,
3050 * to have a defined state. This includes the mixer reset + legacy reset.
3051 */
3052 sb16DevReset(pThis->pDevInsR3);
3053
3054 /*
3055 * Make sure that the mixer sink(s) have a valid format set.
3056 *
3057 * This is needed in order to make the driver attaching logic working done by Main
3058 * for machine construction. Must come after sb16DevReset().
3059 */
3060 PSB16STREAM const pStreamOut = &pThis->aStreams[SB16_IDX_OUT];
3061 AudioMixerSinkSetFormat(pThis->pSinkOut, &pStreamOut->Cfg.Props, pStreamOut->Cfg.Device.cMsSchedulingHint);
3062
3063 /*
3064 * Create timers.
3065 */
3066 rc = PDMDevHlpTimerCreate(pDevIns, TMCLOCK_VIRTUAL, sb16TimerIRQ, pThis,
3067 TMTIMER_FLAGS_DEFAULT_CRIT_SECT | TMTIMER_FLAGS_NO_RING0, "SB16 IRQ", &pThis->hTimerIRQ);
3068 AssertRCReturn(rc, rc);
3069
3070 static const char * const s_apszNames[] = { "SB16 OUT" };
3071 AssertCompile(RT_ELEMENTS(s_apszNames) == SB16_MAX_STREAMS);
3072 for (unsigned i = 0; i < SB16_MAX_STREAMS; i++)
3073 {
3074 rc = PDMDevHlpTimerCreate(pDevIns, TMCLOCK_VIRTUAL, sb16TimerIO, &pThis->aStreams[i],
3075 TMTIMER_FLAGS_DEFAULT_CRIT_SECT | TMTIMER_FLAGS_NO_RING0, s_apszNames[i], &pThis->aStreams[i].hTimerIO);
3076 AssertRCReturn(rc, rc);
3077
3078 pThis->aStreams[i].cTicksTimerIOInterval = PDMDevHlpTimerGetFreq(pDevIns, pThis->aStreams[i].hTimerIO)
3079 / pThis->aStreams[i].uTimerHz;
3080 pThis->aStreams[i].tsTimerIO = PDMDevHlpTimerGet(pDevIns, pThis->aStreams[i].hTimerIO);
3081 }
3082
3083 /*
3084 * Register I/O and DMA.
3085 */
3086 static const IOMIOPORTDESC s_aAllDescs[] =
3087 {
3088 { "FM Music Status Port", "FM Music Register Address Port", NULL, NULL }, // 00h
3089 { NULL, "FM Music Data Port", NULL, NULL }, // 01h
3090 { "Advanced FM Music Status Port", "Advanced FM Music Register Address Port", NULL, NULL }, // 02h
3091 { NULL, "Advanced FM Music Data Port", NULL, NULL }, // 03h
3092 { NULL, "Mixer chip Register Address Port", NULL, NULL }, // 04h
3093 { "Mixer chip Data Port", NULL, NULL, NULL }, // 05h
3094 { NULL, "DSP Reset", NULL, NULL }, // 06h
3095 { "Unused7", "Unused7", NULL, NULL }, // 07h
3096 { "FM Music Status Port", "FM Music Register Port", NULL, NULL }, // 08h
3097 { NULL, "FM Music Data Port", NULL, NULL }, // 09h
3098 { "DSP Read Data Port", NULL, NULL, NULL }, // 0Ah
3099 { "UnusedB", "UnusedB", NULL, NULL }, // 0Bh
3100 { "DSP Write-Buffer Status", "DSP Write Command/Data", NULL, NULL }, // 0Ch
3101 { "UnusedD", "UnusedD", NULL, NULL }, // 0Dh
3102 { "DSP Read-Buffer Status", NULL, NULL, NULL }, // 0Eh
3103 { "IRQ16ACK", NULL, NULL, NULL }, // 0Fh
3104 { "CD-ROM Data Register", "CD-ROM Command Register", NULL, NULL }, // 10h
3105 { "CD-ROM Status Register", NULL, NULL, NULL }, // 11h
3106 { NULL, "CD-ROM Reset Register", NULL, NULL }, // 12h
3107 { NULL, "CD-ROM Enable Register", NULL, NULL }, // 13h
3108 { NULL, NULL, NULL, NULL },
3109 };
3110
3111 rc = PDMDevHlpIoPortCreateAndMap(pDevIns, pStream->HwCfgRuntime.uPort + 0x04 /*uPort*/, 2 /*cPorts*/,
3112 sb16IoPortMixerWrite, sb16IoPortMixerRead,
3113 "SB16 - Mixer", &s_aAllDescs[4], &pThis->hIoPortsMixer);
3114 AssertRCReturn(rc, rc);
3115 rc = PDMDevHlpIoPortCreateAndMap(pDevIns, pStream->HwCfgRuntime.uPort + 0x06 /*uPort*/, 10 /*cPorts*/,
3116 sb16IoPortDspWrite, sb16IoPortDspRead,
3117 "SB16 - DSP", &s_aAllDescs[6], &pThis->hIoPortsDsp);
3118 AssertRCReturn(rc, rc);
3119
3120 rc = PDMDevHlpDMARegister(pDevIns, pStream->HwCfgRuntime.uDmaChanHigh, sb16DMARead, &pThis->aStreams[SB16_IDX_OUT] /* pvUser */);
3121 AssertRCReturn(rc, rc);
3122 rc = PDMDevHlpDMARegister(pDevIns, pStream->HwCfgRuntime.uDmaChanLow, sb16DMARead, &pThis->aStreams[SB16_IDX_OUT] /* pvUser */);
3123 AssertRCReturn(rc, rc);
3124
3125 /*
3126 * Register Saved state.
3127 */
3128 rc = PDMDevHlpSSMRegister3(pDevIns, SB16_SAVE_STATE_VERSION, sizeof(SB16STATE), sb16LiveExec, sb16SaveExec, sb16LoadExec);
3129 AssertRCReturn(rc, rc);
3130
3131 LogRel2(("SB16: Using port %#x, DMA%RU8, IRQ%RU8\n",
3132 pStream->HwCfgRuntime.uPort, pStream->HwCfgRuntime.uDmaChanLow, pStream->HwCfgRuntime.uIrq));
3133
3134 /*
3135 * Attach drivers. We ASSUME they are configured consecutively without any
3136 * gaps, so we stop when we hit the first LUN w/o a driver configured.
3137 */
3138 for (unsigned iLun = 0; ; iLun++)
3139 {
3140 AssertBreak(iLun < UINT8_MAX);
3141 LogFunc(("Trying to attach driver for LUN#%u ...\n", iLun));
3142 rc = sb16AttachInternal(pThis, iLun, NULL /* ppDrv */);
3143 if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
3144 {
3145 LogFunc(("cLUNs=%u\n", iLun));
3146 break;
3147 }
3148 AssertLogRelMsgReturn(RT_SUCCESS(rc), ("LUN#%u: rc=%Rrc\n", iLun, rc), rc);
3149 }
3150
3151 /*
3152 * Register statistics.
3153 */
3154# ifdef VBOX_WITH_STATISTICS
3155 PDMDevHlpSTAMRegister(pDevIns, &pThis->StatTimerIO, STAMTYPE_PROFILE, "Timer", STAMUNIT_TICKS_PER_CALL, "Profiling sb16TimerIO.");
3156 PDMDevHlpSTAMRegister(pDevIns, &pThis->StatBytesRead, STAMTYPE_COUNTER, "BytesRead", STAMUNIT_BYTES, "Bytes read from SB16 emulation.");
3157# endif
3158 for (unsigned idxStream = 0; idxStream < RT_ELEMENTS(pThis->aStreams); idxStream++)
3159 {
3160 PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].State.offRead, STAMTYPE_U64, STAMVISIBILITY_USED, STAMUNIT_BYTES,
3161 "Virtual internal buffer read position.", "Stream%u/offRead", idxStream);
3162 PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].State.offWrite, STAMTYPE_U64, STAMVISIBILITY_USED, STAMUNIT_BYTES,
3163 "Virtual internal buffer write position.", "Stream%u/offWrite", idxStream);
3164 PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].State.StatDmaBufSize, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_BYTES,
3165 "Size of the internal DMA buffer.", "Stream%u/DMABufSize", idxStream);
3166 PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].State.StatDmaBufUsed, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_BYTES,
3167 "Number of bytes used in the internal DMA buffer.", "Stream%u/DMABufUsed", idxStream);
3168 }
3169
3170 /*
3171 * Debug info items.
3172 */
3173 //PDMDevHlpDBGFInfoRegister(pDevIns, "sb16", "SB16 registers. (sb16 [register case-insensitive])", sb16DbgInfo);
3174 //PDMDevHlpDBGFInfoRegister(pDevIns, "sb16stream", "SB16 stream info. (sb16stream [stream number])", sb16DbgInfoStream);
3175 PDMDevHlpDBGFInfoRegister(pDevIns, "sb16mixer", "SB16 mixer state.", sb16DbgInfoMixer);
3176
3177 return VINF_SUCCESS;
3178}
3179
3180const PDMDEVREG g_DeviceSB16 =
3181{
3182 /* .u32Version = */ PDM_DEVREG_VERSION,
3183 /* .uReserved0 = */ 0,
3184 /* .szName = */ "sb16",
3185 /* .fFlags = */ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_NEW_STYLE
3186 | PDM_DEVREG_FLAGS_FIRST_POWEROFF_NOTIFICATION /* stream clearnup with working drivers */,
3187 /* .fClass = */ PDM_DEVREG_CLASS_AUDIO,
3188 /* .cMaxInstances = */ 1,
3189 /* .uSharedVersion = */ 42,
3190 /* .cbInstanceShared = */ sizeof(SB16STATE),
3191 /* .cbInstanceCC = */ 0,
3192 /* .cbInstanceRC = */ 0,
3193 /* .cMaxPciDevices = */ 0,
3194 /* .cMaxMsixVectors = */ 0,
3195 /* .pszDescription = */ "Sound Blaster 16 Controller",
3196#if defined(IN_RING3)
3197 /* .pszRCMod = */ "",
3198 /* .pszR0Mod = */ "",
3199 /* .pfnConstruct = */ sb16Construct,
3200 /* .pfnDestruct = */ sb16Destruct,
3201 /* .pfnRelocate = */ NULL,
3202 /* .pfnMemSetup = */ NULL,
3203 /* .pfnPowerOn = */ NULL,
3204 /* .pfnReset = */ sb16DevReset,
3205 /* .pfnSuspend = */ NULL,
3206 /* .pfnResume = */ NULL,
3207 /* .pfnAttach = */ sb16Attach,
3208 /* .pfnDetach = */ sb16Detach,
3209 /* .pfnQueryInterface = */ NULL,
3210 /* .pfnInitComplete = */ NULL,
3211 /* .pfnPowerOff = */ sb16PowerOff,
3212 /* .pfnSoftReset = */ NULL,
3213 /* .pfnReserved0 = */ NULL,
3214 /* .pfnReserved1 = */ NULL,
3215 /* .pfnReserved2 = */ NULL,
3216 /* .pfnReserved3 = */ NULL,
3217 /* .pfnReserved4 = */ NULL,
3218 /* .pfnReserved5 = */ NULL,
3219 /* .pfnReserved6 = */ NULL,
3220 /* .pfnReserved7 = */ NULL,
3221#elif defined(IN_RING0)
3222 /* .pfnEarlyConstruct = */ NULL,
3223 /* .pfnConstruct = */ NULL,
3224 /* .pfnDestruct = */ NULL,
3225 /* .pfnFinalDestruct = */ NULL,
3226 /* .pfnRequest = */ NULL,
3227 /* .pfnReserved0 = */ NULL,
3228 /* .pfnReserved1 = */ NULL,
3229 /* .pfnReserved2 = */ NULL,
3230 /* .pfnReserved3 = */ NULL,
3231 /* .pfnReserved4 = */ NULL,
3232 /* .pfnReserved5 = */ NULL,
3233 /* .pfnReserved6 = */ NULL,
3234 /* .pfnReserved7 = */ NULL,
3235#elif defined(IN_RC)
3236 /* .pfnConstruct = */ NULL,
3237 /* .pfnReserved0 = */ NULL,
3238 /* .pfnReserved1 = */ NULL,
3239 /* .pfnReserved2 = */ NULL,
3240 /* .pfnReserved3 = */ NULL,
3241 /* .pfnReserved4 = */ NULL,
3242 /* .pfnReserved5 = */ NULL,
3243 /* .pfnReserved6 = */ NULL,
3244 /* .pfnReserved7 = */ NULL,
3245#else
3246# error "Not in IN_RING3, IN_RING0 or IN_RC!"
3247#endif
3248 /* .u32VersionEnd = */ PDM_DEVREG_VERSION
3249};
3250
Note: See TracBrowser for help on using the repository browser.

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