summary refs log tree commit diff
path: root/devices/src/pci/ac97_bus_master.rs
diff options
context:
space:
mode:
authorDylan Reid <dgreid@chromium.org>2018-12-06 19:47:18 +0000
committerchrome-bot <chrome-bot@chromium.org>2019-01-26 10:43:20 -0800
commitb58b5529403c1d86a74cd973abf5796a9da4c348 (patch)
treef0584ba5b3725e81f9cf750196948fa526e983da /devices/src/pci/ac97_bus_master.rs
parent1f909a21ba90197500ccfc17608acdd6ea2c799a (diff)
downloadcrosvm-b58b5529403c1d86a74cd973abf5796a9da4c348.tar
crosvm-b58b5529403c1d86a74cd973abf5796a9da4c348.tar.gz
crosvm-b58b5529403c1d86a74cd973abf5796a9da4c348.tar.bz2
crosvm-b58b5529403c1d86a74cd973abf5796a9da4c348.tar.lz
crosvm-b58b5529403c1d86a74cd973abf5796a9da4c348.tar.xz
crosvm-b58b5529403c1d86a74cd973abf5796a9da4c348.tar.zst
crosvm-b58b5529403c1d86a74cd973abf5796a9da4c348.zip
devices: Add an Ac97 PCI device
The Ac97 device provides the guest with an audio playback device. All
input devices are stubbed out. Only playback at 48kHz is supported.

The device is emulated by `Ac97Dev` which interfaces with the PCI bus.
`Ac97Dev` uses `Ac97` to drive audio functions and emulate the device
registers. Physical Ac97 devices consist of two parts, the bus master
and a mixer. These two sets of registers are emulated by the
`Ac97BusMaster` and `Ac97Mixer` structures.

`Ac97BusMaster` handles audio samples and uses `Ac97Mixer` to determine
the configuration of the audio backend.

BUG=chromium:781398
TEST=crosvm run --disable-sandbox --null-audio --rwdisk gentoo.ext4 -c2
-m2048 -p 'root=/dev/vda snd_intel8x0.inside_vm=1
snd_intel8x0.ac97_clock=48000' vmlinux.bin
and play audio with aplay -d2 -Dhw:0,0 -f dat /dev/urandom
CQ-DEPEND=CL:1402264
CQ-DEPEND=CL:1421588
CQ-DEPEND=CL:1433794
CQ-DEPEND=CL:1432835

Change-Id: I9985ffad753bccc1bf468ebbdacec0876560a5e0
Signed-off-by: Dylan Reid <dgreid@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/1366544
Commit-Ready: Chih-Yang Hsia <paulhsia@chromium.org>
Tested-by: kokoro <noreply+kokoro@google.com>
Tested-by: Chih-Yang Hsia <paulhsia@chromium.org>
Reviewed-by: Chih-Yang Hsia <paulhsia@chromium.org>
Diffstat (limited to 'devices/src/pci/ac97_bus_master.rs')
-rw-r--r--devices/src/pci/ac97_bus_master.rs814
1 files changed, 814 insertions, 0 deletions
diff --git a/devices/src/pci/ac97_bus_master.rs b/devices/src/pci/ac97_bus_master.rs
new file mode 100644
index 0000000..190fdce
--- /dev/null
+++ b/devices/src/pci/ac97_bus_master.rs
@@ -0,0 +1,814 @@
+// Copyright 2018 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std;
+use std::error::Error;
+use std::fmt::{self, Display};
+use std::io::Write;
+use std::os::unix::io::RawFd;
+use std::sync::atomic::{AtomicBool, Ordering};
+use std::sync::Arc;
+use std::thread;
+use std::time::Instant;
+
+use audio_streams::{PlaybackBuffer, PlaybackBufferStream, StreamControl, StreamSource};
+use data_model::{VolatileMemory, VolatileSlice};
+use pci::ac97_mixer::Ac97Mixer;
+use pci::ac97_regs::*;
+use sync::Mutex;
+use sys_util::{self, set_rt_prio_limit, set_rt_round_robin, EventFd, GuestAddress, GuestMemory};
+
+const DEVICE_SAMPLE_RATE: usize = 48000;
+
+// Bus Master registers. Keeps the state of the bus master register values. Used to share the state
+// between the main and audio threads.
+struct Ac97BusMasterRegs {
+    pi_regs: Ac97FunctionRegs,       // Input
+    po_regs: Ac97FunctionRegs,       // Output
+    po_pointer_update_time: Instant, // Time the picb and civ regs were last updated.
+    mc_regs: Ac97FunctionRegs,       // Microphone
+    glob_cnt: u32,
+    glob_sta: u32,
+
+    // IRQ event - driven by the glob_sta register.
+    irq_evt: Option<EventFd>,
+}
+
+impl Ac97BusMasterRegs {
+    fn new() -> Ac97BusMasterRegs {
+        Ac97BusMasterRegs {
+            pi_regs: Ac97FunctionRegs::new(),
+            po_regs: Ac97FunctionRegs::new(),
+            po_pointer_update_time: Instant::now(),
+            mc_regs: Ac97FunctionRegs::new(),
+            glob_cnt: 0,
+            glob_sta: GLOB_STA_RESET_VAL,
+            irq_evt: None,
+        }
+    }
+
+    fn func_regs(&mut self, func: Ac97Function) -> &Ac97FunctionRegs {
+        match func {
+            Ac97Function::Input => &self.pi_regs,
+            Ac97Function::Output => &self.po_regs,
+            Ac97Function::Microphone => &self.mc_regs,
+        }
+    }
+
+    fn func_regs_mut(&mut self, func: Ac97Function) -> &mut Ac97FunctionRegs {
+        match func {
+            Ac97Function::Input => &mut self.pi_regs,
+            Ac97Function::Output => &mut self.po_regs,
+            Ac97Function::Microphone => &mut self.mc_regs,
+        }
+    }
+}
+
+// Internal error type used for reporting errors from the audio playback thread.
+#[derive(Debug)]
+enum PlaybackError {
+    // Failure getting the address of the audio buffer.
+    ReadingGuestBufferAddress(sys_util::GuestMemoryError),
+    // Failure reading samples from guest memory.
+    ReadingGuestSamples(data_model::VolatileMemoryError),
+    // Failure to get an buffer from the stream.
+    StreamError(Box<Error>),
+    // Failure writing to the audio output.
+    WritingOutput(std::io::Error),
+}
+
+impl Error for PlaybackError {}
+
+impl Display for PlaybackError {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            PlaybackError::ReadingGuestBufferAddress(e) => {
+                write!(f, "Failed to get the address of the audio buffer: {:?}.", e)
+            }
+            PlaybackError::ReadingGuestSamples(e) => {
+                write!(f, "Failed to read samples from guest memory: {:?}.", e)
+            }
+            PlaybackError::StreamError(e) => {
+                write!(f, "Failed to get a buffer from the stream: {:?}", e)
+            }
+            PlaybackError::WritingOutput(e) => write!(f, "Failed to write audio output: {:?}.", e),
+        }
+    }
+}
+
+type PlaybackResult<T> = std::result::Result<T, PlaybackError>;
+
+/// `Ac97BusMaster` emulates the bus master portion of AC97. It exposes a register read/write
+/// interface compliant with the ICH bus master.
+pub struct Ac97BusMaster {
+    // Keep guest memory as each function will use it for buffer descriptors.
+    mem: GuestMemory,
+    regs: Arc<Mutex<Ac97BusMasterRegs>>,
+    acc_sema: u8,
+
+    // Audio thread book keeping.
+    audio_thread_po: Option<thread::JoinHandle<()>>,
+    audio_thread_po_run: Arc<AtomicBool>,
+    po_stream_control: Option<Box<dyn StreamControl>>,
+
+    // Audio server used to create playback streams.
+    audio_server: Box<dyn StreamSource>,
+
+    // Thread for hadlind IRQ resample events from the guest.
+    irq_resample_thread: Option<thread::JoinHandle<()>>,
+}
+
+impl Ac97BusMaster {
+    /// Creates an Ac97BusMaster` object that plays audio from `mem` to streams provided by
+    /// `audio_server`.
+    pub fn new(mem: GuestMemory, audio_server: Box<dyn StreamSource>) -> Self {
+        Ac97BusMaster {
+            mem,
+            regs: Arc::new(Mutex::new(Ac97BusMasterRegs::new())),
+            acc_sema: 0,
+
+            audio_thread_po: None,
+            audio_thread_po_run: Arc::new(AtomicBool::new(false)),
+            po_stream_control: None,
+
+            audio_server,
+
+            irq_resample_thread: None,
+        }
+    }
+
+    /// Returns any file descriptors that need to be kept open when entering a jail.
+    pub fn keep_fds(&self) -> Option<Vec<RawFd>> {
+        self.audio_server.keep_fds()
+    }
+
+    /// Provides the events needed to raise interrupts in the guest.
+    pub fn set_irq_event_fd(&mut self, irq_evt: EventFd, irq_resample_evt: EventFd) {
+        let thread_regs = self.regs.clone();
+        self.regs.lock().irq_evt = Some(irq_evt);
+        self.irq_resample_thread = Some(thread::spawn(move || {
+            loop {
+                if let Err(e) = irq_resample_evt.read() {
+                    error!(
+                        "Failed to read the irq event from the resample thread: {:?}.",
+                        e
+                    );
+                    break;
+                }
+                {
+                    // Scope for the lock on thread_regs.
+                    let mut regs = thread_regs.lock();
+                    let int_mask = regs.func_regs(Ac97Function::Output).int_mask();
+                    if regs.func_regs(Ac97Function::Output).sr & int_mask != 0 {
+                        if let Some(irq_evt) = regs.irq_evt.as_ref() {
+                            if let Err(e) = irq_evt.write(1) {
+                                error!("Failed to set the irq from the resample thread: {:?}.", e);
+                                break;
+                            }
+                        }
+                    }
+                }
+            }
+        }));
+    }
+
+    /// Called when `mixer` has been changed and the new values should be applied to currently
+    /// active streams.
+    pub fn update_mixer_settings(&mut self, mixer: &Ac97Mixer) {
+        if let Some(control) = self.po_stream_control.as_mut() {
+            // The audio server only supports one volume, not separate left and right.
+            let (muted, left_volume, _right_volume) = mixer.get_master_volume();
+            control.set_volume(left_volume);
+            control.set_mute(muted);
+        }
+    }
+
+    /// Checks if the bus master is in the cold reset state.
+    pub fn is_cold_reset(&self) -> bool {
+        self.regs.lock().glob_cnt & GLOB_CNT_COLD_RESET == 0
+    }
+
+    /// Reads a byte from the given `offset`.
+    pub fn readb(&mut self, offset: u64) -> u8 {
+        fn readb_func_regs(func_regs: &Ac97FunctionRegs, offset: u64) -> u8 {
+            match offset {
+                CIV_OFFSET => func_regs.civ,
+                LVI_OFFSET => func_regs.lvi,
+                SR_OFFSET => func_regs.sr as u8,
+                PIV_OFFSET => func_regs.piv,
+                CR_OFFSET => func_regs.cr,
+                _ => 0,
+            }
+        }
+
+        let regs = self.regs.lock();
+        match offset {
+            PI_BASE_00...PI_CR_0B => readb_func_regs(&regs.pi_regs, offset - PI_BASE_00),
+            PO_BASE_10...PO_CR_1B => readb_func_regs(&regs.po_regs, offset - PO_BASE_10),
+            MC_BASE_20...MC_CR_2B => readb_func_regs(&regs.mc_regs, offset - MC_BASE_20),
+            ACC_SEMA_34 => self.acc_sema,
+            _ => 0,
+        }
+    }
+
+    /// Reads a word from the given `offset`.
+    pub fn readw(&mut self, offset: u64) -> u16 {
+        let regs = self.regs.lock();
+        match offset {
+            PI_SR_06 => regs.pi_regs.sr,
+            PI_PICB_08 => regs.pi_regs.picb,
+            PO_SR_16 => regs.po_regs.sr,
+            PO_PICB_18 => {
+                // PO PICB
+                if !self.audio_thread_po_run.load(Ordering::Relaxed) {
+                    // Not running, no need to estimate what has been consumed.
+                    regs.po_regs.picb
+                } else {
+                    // Estimate how many samples have been played since the last audio callback.
+                    let num_channels = 2;
+                    let micros = regs.po_pointer_update_time.elapsed().subsec_micros();
+                    // Round down to the next 10 millisecond boundary. The linux driver often
+                    // assumes that two rapid reads from picb will return the same value.
+                    let millis = micros / 1000 / 10 * 10;
+
+                    let frames_consumed = DEVICE_SAMPLE_RATE as u64 * u64::from(millis) / 1000;
+
+                    regs.po_regs
+                        .picb
+                        .saturating_sub((num_channels * frames_consumed) as u16)
+                }
+            }
+            MC_SR_26 => regs.mc_regs.sr,
+            MC_PICB_28 => regs.mc_regs.picb,
+            _ => 0,
+        }
+    }
+
+    /// Reads a 32-bit word from the given `offset`.
+    pub fn readl(&mut self, offset: u64) -> u32 {
+        let regs = self.regs.lock();
+        match offset {
+            PI_BDBAR_00 => regs.pi_regs.bdbar,
+            PI_CIV_04 => regs.pi_regs.atomic_status_regs(),
+            PO_BDBAR_10 => regs.po_regs.bdbar,
+            PO_CIV_14 => regs.po_regs.atomic_status_regs(),
+            MC_BDBAR_20 => regs.mc_regs.bdbar,
+            MC_CIV_24 => regs.mc_regs.atomic_status_regs(),
+            GLOB_CNT_2C => regs.glob_cnt,
+            GLOB_STA_30 => regs.glob_sta,
+            _ => 0,
+        }
+    }
+
+    /// Writes the byte `val` to the register specified by `offset`.
+    pub fn writeb(&mut self, offset: u64, val: u8, mixer: &Ac97Mixer) {
+        // Only process writes to the control register when cold reset is set.
+        if self.is_cold_reset() {
+            return;
+        }
+
+        match offset {
+            PI_CIV_04 => (), // RO
+            PI_LVI_05 => self.set_lvi(Ac97Function::Input, val),
+            PI_PIV_0A => (), // RO
+            PI_CR_0B => self.set_cr(Ac97Function::Input, val, mixer),
+            PO_CIV_14 => (), // RO
+            PO_LVI_15 => self.set_lvi(Ac97Function::Output, val),
+            PO_SR_16 => self.set_sr(Ac97Function::Output, u16::from(val)),
+            PO_PIV_1A => (), // RO
+            PO_CR_1B => self.set_cr(Ac97Function::Output, val, mixer),
+            MC_CIV_24 => (), // RO
+            MC_LVI_25 => self.set_lvi(Ac97Function::Microphone, val),
+            MC_PIV_2A => (), // RO
+            MC_CR_2B => self.set_cr(Ac97Function::Microphone, val, mixer),
+            ACC_SEMA_34 => self.acc_sema = val,
+            o => warn!("write byte to 0x{:x}", o),
+        }
+    }
+
+    /// Writes the word `val` to the register specified by `offset`.
+    pub fn writew(&mut self, offset: u64, val: u16) {
+        // Only process writes to the control register when cold reset is set.
+        if self.is_cold_reset() {
+            return;
+        }
+        match offset {
+            PI_SR_06 => self.set_sr(Ac97Function::Input, val),
+            PI_PICB_08 => (), // RO
+            PO_SR_16 => self.set_sr(Ac97Function::Output, val),
+            PO_PICB_18 => (), // RO
+            MC_SR_26 => self.set_sr(Ac97Function::Microphone, val),
+            MC_PICB_28 => (), // RO
+            o => warn!("write word to 0x{:x}", o),
+        }
+    }
+
+    /// Writes the 32-bit `val` to the register specified by `offset`.
+    pub fn writel(&mut self, offset: u64, val: u32) {
+        // Only process writes to the control register when cold reset is set.
+        if self.is_cold_reset() && offset != 0x2c {
+            return;
+        }
+        match offset {
+            PI_BDBAR_00 => self.set_bdbar(Ac97Function::Input, val),
+            PO_BDBAR_10 => self.set_bdbar(Ac97Function::Output, val),
+            MC_BDBAR_20 => self.set_bdbar(Ac97Function::Microphone, val),
+            GLOB_CNT_2C => self.set_glob_cnt(val),
+            GLOB_STA_30 => (), // RO
+            o => warn!("write long to 0x{:x}", o),
+        }
+    }
+
+    fn set_bdbar(&mut self, func: Ac97Function, val: u32) {
+        self.regs.lock().func_regs_mut(func).bdbar = val & !0x07;
+    }
+
+    fn set_lvi(&mut self, func: Ac97Function, val: u8) {
+        let mut regs = self.regs.lock();
+        let func_regs = regs.func_regs_mut(func);
+        func_regs.lvi = val % 32; // LVI wraps at 32.
+
+        // If running and stalled waiting for more valid buffers, restart by clearing the "DMA
+        // stopped" bit.
+        if func_regs.cr & CR_RPBM == CR_RPBM
+            && func_regs.sr & SR_DCH == SR_DCH
+            && func_regs.civ != func_regs.lvi
+        {
+            func_regs.sr &= !SR_DCH;
+        }
+    }
+
+    fn set_sr(&mut self, func: Ac97Function, val: u16) {
+        let mut sr = self.regs.lock().func_regs(func).sr;
+        if val & SR_FIFOE != 0 {
+            sr &= !SR_FIFOE;
+        }
+        if val & SR_LVBCI != 0 {
+            sr &= !SR_LVBCI;
+        }
+        if val & SR_BCIS != 0 {
+            sr &= !SR_BCIS;
+        }
+        update_sr(&mut self.regs.lock(), func, sr);
+    }
+
+    fn set_cr(&mut self, func: Ac97Function, val: u8, mixer: &Ac97Mixer) {
+        if val & CR_RR != 0 {
+            self.stop_audio(func);
+            let mut regs = self.regs.lock();
+            regs.func_regs_mut(func).do_reset();
+        } else {
+            let cr = self.regs.lock().func_regs(func).cr;
+            if val & CR_RPBM == 0 {
+                // Run/Pause set to pause.
+                self.stop_audio(func);
+                let mut regs = self.regs.lock();
+                regs.func_regs_mut(func).sr |= SR_DCH;
+            } else if cr & CR_RPBM == 0 {
+                // Not already running.
+                // Run/Pause set to run.
+                {
+                    let mut regs = self.regs.lock();
+                    let func_regs = regs.func_regs_mut(func);
+                    func_regs.piv = 1;
+                    func_regs.civ = 0;
+                    func_regs.sr &= !SR_DCH;
+                }
+                if self.start_audio(func, mixer).is_err() {
+                    warn!("Failed to start audio");
+                }
+            }
+            let mut regs = self.regs.lock();
+            regs.func_regs_mut(func).cr = val & CR_VALID_MASK;
+        }
+    }
+
+    fn set_glob_cnt(&mut self, new_glob_cnt: u32) {
+        // Only the reset bits are emulated, the GPI and PCM formatting are not supported.
+        if new_glob_cnt & GLOB_CNT_COLD_RESET == 0 {
+            self.reset_audio_regs();
+
+            let mut regs = self.regs.lock();
+            regs.glob_cnt = new_glob_cnt & GLOB_CNT_STABLE_BITS;
+            self.acc_sema = 0;
+            return;
+        }
+        if new_glob_cnt & GLOB_CNT_WARM_RESET != 0 {
+            // Check if running and if so, ignore. Warm reset is specified to no-op when the device
+            // is playing or recording audio.
+            if !self.audio_thread_po_run.load(Ordering::Relaxed) {
+                self.stop_all_audio();
+                let mut regs = self.regs.lock();
+                regs.glob_cnt = new_glob_cnt & !GLOB_CNT_WARM_RESET; // Auto-cleared reset bit.
+                return;
+            }
+        }
+        self.regs.lock().glob_cnt = new_glob_cnt;
+    }
+
+    fn start_audio(&mut self, func: Ac97Function, mixer: &Ac97Mixer) -> Result<(), Box<Error>> {
+        const AUDIO_THREAD_RTPRIO: u16 = 12; // Matches other cros audio clients.
+
+        match func {
+            Ac97Function::Input => (),
+            Ac97Function::Output => {
+                let num_channels = 2;
+
+                let buffer_samples =
+                    current_buffer_size(self.regs.lock().func_regs(func), &self.mem)?;
+
+                let buffer_frames = buffer_samples / num_channels;
+                let (mut stream_control, mut output_stream) = self
+                    .audio_server
+                    .new_playback_stream(num_channels, DEVICE_SAMPLE_RATE, buffer_frames)?;
+                self.po_stream_control = Some(stream_control);
+
+                self.update_mixer_settings(mixer);
+
+                self.audio_thread_po_run.store(true, Ordering::Relaxed);
+                let thread_run = self.audio_thread_po_run.clone();
+                let thread_mem = self.mem.clone();
+                let thread_regs = self.regs.clone();
+
+                self.audio_thread_po = Some(thread::spawn(move || {
+                    if set_rt_prio_limit(u64::from(AUDIO_THREAD_RTPRIO)).is_err()
+                        || set_rt_round_robin(i32::from(AUDIO_THREAD_RTPRIO)).is_err()
+                    {
+                        warn!("Failed to set audio thread to real time.");
+                    }
+                    if let Err(e) =
+                        audio_out_thread(thread_regs, thread_mem, &thread_run, output_stream)
+                    {
+                        error!("Playback error: {:?}", e);
+                    }
+                    thread_run.store(false, Ordering::Relaxed);
+                }));
+            }
+            Ac97Function::Microphone => (),
+        }
+        Ok(())
+    }
+
+    fn stop_audio(&mut self, func: Ac97Function) {
+        match func {
+            Ac97Function::Input => (),
+            Ac97Function::Output => {
+                self.audio_thread_po_run.store(false, Ordering::Relaxed);
+                if let Some(thread) = self.audio_thread_po.take() {
+                    if let Err(e) = thread.join() {
+                        error!("Failed to join the playback thread: {:?}.", e);
+                    }
+                }
+            }
+            Ac97Function::Microphone => (),
+        };
+    }
+
+    fn stop_all_audio(&mut self) {
+        self.stop_audio(Ac97Function::Input);
+        self.stop_audio(Ac97Function::Output);
+        self.stop_audio(Ac97Function::Microphone);
+    }
+
+    fn reset_audio_regs(&mut self) {
+        self.stop_all_audio();
+        let mut regs = self.regs.lock();
+        regs.pi_regs.do_reset();
+        regs.po_regs.do_reset();
+        regs.mc_regs.do_reset();
+    }
+}
+
+// Gets the next buffer from the guest. This will return `None` if the DMA controlled stopped bit is
+// set, such as after an underrun where CIV hits LVI.
+fn next_guest_buffer<'a>(
+    func_regs: &mut Ac97FunctionRegs,
+    mem: &'a GuestMemory,
+) -> PlaybackResult<Option<VolatileSlice<'a>>> {
+    let sample_size = 2;
+
+    if func_regs.sr & SR_DCH != 0 {
+        return Ok(None);
+    }
+    let next_buffer = func_regs.civ;
+    let descriptor_addr = func_regs.bdbar + u32::from(next_buffer) * DESCRIPTOR_LENGTH as u32;
+    let buffer_addr_reg: u32 = mem
+        .read_obj_from_addr(GuestAddress(u64::from(descriptor_addr)))
+        .map_err(PlaybackError::ReadingGuestBufferAddress)?;
+    let buffer_addr = buffer_addr_reg & !0x03u32; // The address must be aligned to four bytes.
+    let control_reg: u32 = mem
+        .read_obj_from_addr(GuestAddress(u64::from(descriptor_addr) + 4))
+        .map_err(PlaybackError::ReadingGuestBufferAddress)?;
+    let buffer_samples: usize = control_reg as usize & 0x0000_ffff;
+
+    func_regs.picb = buffer_samples as u16;
+
+    let samples_remaining = func_regs.picb as usize;
+    if samples_remaining == 0 {
+        return Ok(None);
+    }
+    let read_pos = u64::from(buffer_addr);
+    Ok(Some(
+        mem.get_slice(read_pos, samples_remaining as u64 * sample_size)
+            .map_err(PlaybackError::ReadingGuestSamples)?,
+    ))
+}
+
+// Reads the next buffer from guest memory and writes it to `out_buffer`.
+fn play_buffer(
+    regs: &mut Ac97BusMasterRegs,
+    mem: &GuestMemory,
+    out_buffer: &mut PlaybackBuffer,
+) -> PlaybackResult<()> {
+    // If the current buffer had any samples in it, mark it as done.
+    if regs.func_regs_mut(Ac97Function::Output).picb > 0 {
+        buffer_completed(regs, mem, Ac97Function::Output)?
+    }
+    let func_regs = regs.func_regs_mut(Ac97Function::Output);
+    let buffer_len = func_regs.picb * 2;
+    if let Some(buffer) = next_guest_buffer(func_regs, mem)? {
+        buffer
+            .write_to(out_buffer)
+            .map_err(PlaybackError::WritingOutput)?;
+    } else {
+        let zeros = vec![0u8; buffer_len as usize];
+        out_buffer
+            .write(&zeros)
+            .map_err(PlaybackError::WritingOutput)?;
+    }
+    Ok(())
+}
+
+// Moves to the next buffer for the given function and registers.
+fn buffer_completed(
+    regs: &mut Ac97BusMasterRegs,
+    mem: &GuestMemory,
+    func: Ac97Function,
+) -> PlaybackResult<()> {
+    // check if the completed descriptor wanted an interrupt on completion.
+    let civ = regs.func_regs(func).civ;
+    let descriptor_addr = regs.func_regs(func).bdbar + u32::from(civ) * DESCRIPTOR_LENGTH as u32;
+    let control_reg: u32 = mem
+        .read_obj_from_addr(GuestAddress(u64::from(descriptor_addr) + 4))
+        .map_err(PlaybackError::ReadingGuestBufferAddress)?;
+
+    let mut new_sr = regs.func_regs(func).sr;
+
+    if control_reg & BD_IOC != 0 {
+        new_sr |= SR_BCIS;
+    }
+
+    let lvi = regs.func_regs(func).lvi;
+    // if the current buffer was the last valid buffer, then update the status register to
+    // indicate that the end of audio was hit and possibly raise an interrupt.
+    if civ == lvi {
+        new_sr |= SR_DCH | SR_CELV | SR_LVBCI;
+    } else {
+        let func_regs = regs.func_regs_mut(func);
+        func_regs.civ = func_regs.piv;
+        func_regs.piv = (func_regs.piv + 1) % 32; // move piv to the next buffer.
+    }
+
+    if new_sr != regs.func_regs(func).sr {
+        update_sr(regs, func, new_sr);
+    }
+
+    regs.po_pointer_update_time = Instant::now();
+
+    Ok(())
+}
+
+// Runs, playing back audio from the guest to `output_stream` until stopped or an error occurs.
+fn audio_out_thread(
+    regs: Arc<Mutex<Ac97BusMasterRegs>>,
+    mem: GuestMemory,
+    thread_run: &AtomicBool,
+    mut output_stream: Box<dyn PlaybackBufferStream>,
+) -> PlaybackResult<()> {
+    while thread_run.load(Ordering::Relaxed) {
+        output_stream
+            .next_playback_buffer()
+            .map_err(PlaybackError::StreamError)
+            .and_then(|mut pb_buf| play_buffer(&mut regs.lock(), &mem, &mut pb_buf))?;
+    }
+    Ok(())
+}
+
+// Update the status register and if any interrupts need to fire, raise them.
+fn update_sr(regs: &mut Ac97BusMasterRegs, func: Ac97Function, val: u16) {
+    let int_mask = match func {
+        Ac97Function::Input => GS_PIINT,
+        Ac97Function::Output => GS_POINT,
+        Ac97Function::Microphone => GS_MINT,
+    };
+
+    let mut interrupt_high = false;
+
+    {
+        let func_regs = regs.func_regs_mut(func);
+        func_regs.sr = val;
+        if val & SR_INT_MASK != 0 {
+            if (val & SR_LVBCI) != 0 && (func_regs.cr & CR_LVBIE) != 0 {
+                interrupt_high = true;
+            }
+            if (val & SR_BCIS) != 0 && (func_regs.cr & CR_IOCE) != 0 {
+                interrupt_high = true;
+            }
+        }
+    }
+
+    if interrupt_high {
+        regs.glob_sta |= int_mask;
+        if let Some(irq_evt) = regs.irq_evt.as_ref() {
+            // Ignore write failure, nothing can be done about it from here.
+            let _ = irq_evt.write(1);
+        }
+    } else {
+        regs.glob_sta &= !int_mask;
+        if regs.glob_sta & (GS_PIINT | GS_POINT | GS_MINT) == 0 {
+            if let Some(irq_evt) = regs.irq_evt.as_ref() {
+                // Ignore write failure, nothing can be done about it from here.
+                let _ = irq_evt.write(0);
+            }
+        }
+    }
+}
+
+// Returns the size in samples of the buffer pointed to by the CIV register.
+fn current_buffer_size(func_regs: &Ac97FunctionRegs, mem: &GuestMemory) -> PlaybackResult<usize> {
+    let civ = func_regs.civ;
+    let descriptor_addr = func_regs.bdbar + u32::from(civ) * DESCRIPTOR_LENGTH as u32;
+    let control_reg: u32 = mem
+        .read_obj_from_addr(GuestAddress(u64::from(descriptor_addr) + 4))
+        .map_err(PlaybackError::ReadingGuestBufferAddress)?;
+    let buffer_len: usize = control_reg as usize & 0x0000_ffff;
+    Ok(buffer_len)
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    use std::time;
+
+    use audio_streams::DummyStreamSource;
+
+    #[test]
+    fn bm_bdbar() {
+        let mut bm = Ac97BusMaster::new(
+            GuestMemory::new(&[]).expect("Creating guest memory failed."),
+            Box::new(DummyStreamSource::new()),
+        );
+
+        let bdbars = [0x00u64, 0x10, 0x20];
+
+        // Make sure writes have no affect during cold reset.
+        bm.writel(0x00, 0x5555_555f);
+        assert_eq!(bm.readl(0x00), 0x0000_0000);
+
+        // Relesase cold reset.
+        bm.writel(GLOB_CNT_2C, 0x0000_0002);
+
+        // Tests that the base address is writable and that the bottom three bits are read only.
+        for bdbar in &bdbars {
+            assert_eq!(bm.readl(*bdbar), 0x0000_0000);
+            bm.writel(*bdbar, 0x5555_555f);
+            assert_eq!(bm.readl(*bdbar), 0x5555_5558);
+        }
+    }
+
+    #[test]
+    fn bm_status_reg() {
+        let mut bm = Ac97BusMaster::new(
+            GuestMemory::new(&[]).expect("Creating guest memory failed."),
+            Box::new(DummyStreamSource::new()),
+        );
+
+        let sr_addrs = [0x06u64, 0x16, 0x26];
+
+        for sr in &sr_addrs {
+            assert_eq!(bm.readw(*sr), 0x0001);
+            bm.writew(*sr, 0xffff);
+            assert_eq!(bm.readw(*sr), 0x0001);
+        }
+    }
+
+    #[test]
+    fn bm_global_control() {
+        let mut bm = Ac97BusMaster::new(
+            GuestMemory::new(&[]).expect("Creating guest memory failed."),
+            Box::new(DummyStreamSource::new()),
+        );
+
+        assert_eq!(bm.readl(GLOB_CNT_2C), 0x0000_0000);
+
+        // Relesase cold reset.
+        bm.writel(GLOB_CNT_2C, 0x0000_0002);
+
+        // Check interrupt enable bits are writable.
+        bm.writel(GLOB_CNT_2C, 0x0000_0072);
+        assert_eq!(bm.readl(GLOB_CNT_2C), 0x0000_0072);
+
+        // A Warm reset should doesn't affect register state and is auto cleared.
+        bm.writel(0x00, 0x5555_5558);
+        bm.writel(GLOB_CNT_2C, 0x0000_0076);
+        assert_eq!(bm.readl(GLOB_CNT_2C), 0x0000_0072);
+        assert_eq!(bm.readl(0x00), 0x5555_5558);
+        // Check that a cold reset works, but setting bdbar and checking it is zeroed.
+        bm.writel(0x00, 0x5555_555f);
+        bm.writel(GLOB_CNT_2C, 0x000_0070);
+        assert_eq!(bm.readl(GLOB_CNT_2C), 0x0000_0070);
+        assert_eq!(bm.readl(0x00), 0x0000_0000);
+    }
+
+    #[test]
+    fn start_playback() {
+        const LVI_MASK: u8 = 0x1f; // Five bits for 32 total entries.
+        const IOC_MASK: u32 = 0x8000_0000; // Interrupt on completion.
+        let num_buffers = LVI_MASK as usize + 1;
+        const BUFFER_SIZE: usize = 32768;
+        const FRAGMENT_SIZE: usize = BUFFER_SIZE / 2;
+
+        const GUEST_ADDR_BASE: u32 = 0x100_0000;
+        let mem = GuestMemory::new(&[(GuestAddress(GUEST_ADDR_BASE as u64), 1024 * 1024 * 1024)])
+            .expect("Creating guest memory failed.");
+        let mut bm = Ac97BusMaster::new(mem.clone(), Box::new(DummyStreamSource::new()));
+        let mixer = Ac97Mixer::new();
+
+        // Release cold reset.
+        bm.writel(GLOB_CNT_2C, 0x0000_0002);
+
+        // Setup ping-pong buffers. A and B repeating for every possible index.
+        bm.writel(PO_BDBAR_10, GUEST_ADDR_BASE);
+        for i in 0..num_buffers {
+            let pointer_addr = GuestAddress(GUEST_ADDR_BASE as u64 + i as u64 * 8);
+            let control_addr = GuestAddress(GUEST_ADDR_BASE as u64 + i as u64 * 8 + 4);
+            if i % 2 == 0 {
+                mem.write_obj_at_addr(GUEST_ADDR_BASE, pointer_addr)
+                    .expect("Writing guest memory failed.");
+            } else {
+                mem.write_obj_at_addr(GUEST_ADDR_BASE + FRAGMENT_SIZE as u32, pointer_addr)
+                    .expect("Writing guest memory failed.");
+            };
+            mem.write_obj_at_addr(IOC_MASK | (FRAGMENT_SIZE as u32) / 2, control_addr)
+                .expect("Writing guest memory failed.");
+        }
+
+        bm.writeb(PO_LVI_15, LVI_MASK, &mixer);
+
+        // Start.
+        bm.writeb(PO_CR_1B, CR_RPBM, &mixer);
+
+        std::thread::sleep(time::Duration::from_millis(50));
+        let picb = bm.readw(PO_PICB_18);
+        let mut civ = bm.readb(PO_CIV_14);
+        assert_eq!(civ, 0);
+        let pos = (FRAGMENT_SIZE - (picb as usize * 2)) / 4;
+
+        // Check that frames are consumed at least at a reasonable rate.
+        // This wont be exact as during unit tests the thread scheduling is highly variable, so the
+        // test only checks that some samples are consumed.
+        assert!(pos > 1000);
+
+        assert!(bm.readw(PO_SR_16) & 0x01 == 0); // DMA is running.
+
+        // civ should move eventually.
+        for _i in 0..30 {
+            if civ != 0 {
+                break;
+            }
+            std::thread::sleep(time::Duration::from_millis(20));
+            civ = bm.readb(PO_CIV_14);
+        }
+
+        assert_ne!(0, civ);
+
+        // Buffer complete should be set as the IOC bit was set in the descriptor.
+        assert!(bm.readw(PO_SR_16) & SR_BCIS != 0);
+        // Clear the BCIS bit
+        bm.writew(PO_SR_16, SR_BCIS);
+        assert!(bm.readw(PO_SR_16) & SR_BCIS == 0);
+
+        // Set last valid to the next and wait until it is hit.
+        bm.writeb(PO_LVI_15, civ + 1, &mixer);
+        std::thread::sleep(time::Duration::from_millis(500));
+        assert!(bm.readw(PO_SR_16) & SR_LVBCI != 0); // Hit last buffer
+        assert!(bm.readw(PO_SR_16) & SR_DCH == SR_DCH); // DMA stopped because of lack of buffers.
+        assert_eq!(bm.readb(PO_LVI_15), bm.readb(PO_CIV_14));
+        // Clear the LVB bit
+        bm.writeb(PO_SR_16, SR_LVBCI as u8, &mixer);
+        assert!(bm.readw(PO_SR_16) & SR_LVBCI == 0);
+        // Reset the LVI to the last buffer and check that playback resumes
+        bm.writeb(PO_LVI_15, LVI_MASK, &mixer);
+        assert!(bm.readw(PO_SR_16) & SR_DCH == 0); // DMA restarts.
+
+        let (restart_civ, restart_picb) = (bm.readb(PO_CIV_14), bm.readw(PO_PICB_18));
+        std::thread::sleep(time::Duration::from_millis(20));
+        assert!(bm.readw(PO_PICB_18) != restart_picb || bm.readb(PO_CIV_14) != restart_civ);
+
+        // Stop.
+        bm.writeb(PO_CR_1B, 0, &mixer);
+        assert!(bm.readw(PO_SR_16) & 0x01 != 0); // DMA is not running.
+    }
+}