summary refs log tree commit diff
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
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>
-rw-r--r--Cargo.lock5
-rw-r--r--Cargo.toml1
-rw-r--r--devices/Cargo.toml1
-rw-r--r--devices/src/lib.rs3
-rw-r--r--devices/src/pci/ac97.rs203
-rw-r--r--devices/src/pci/ac97_bus_master.rs814
-rw-r--r--devices/src/pci/ac97_mixer.rs164
-rw-r--r--devices/src/pci/ac97_regs.rs247
-rw-r--r--devices/src/pci/mod.rs5
9 files changed, 1442 insertions, 1 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 2217466..4b91e74 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -35,6 +35,10 @@ name = "assertions"
 version = "0.1.0"
 
 [[package]]
+name = "audio_streams"
+version = "0.1.0"
+
+[[package]]
 name = "bit_field"
 version = "0.1.0"
 dependencies = [
@@ -122,6 +126,7 @@ dependencies = [
 name = "devices"
 version = "0.1.0"
 dependencies = [
+ "audio_streams 0.1.0",
  "bit_field 0.1.0",
  "byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "data_model 0.1.0",
diff --git a/Cargo.toml b/Cargo.toml
index dd720fb..29ad554 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -68,6 +68,7 @@ sys_util = "*"
 
 [patch.crates-io]
 assertions = { path = "assertions" }
+audio_streams = { path = "../../third_party/adhd/audio_streams" } # ignored by ebuild
 data_model = { path = "data_model" }
 poll_token_derive = { path = "sys_util/poll_token_derive" }
 sync = { path = "sync" }
diff --git a/devices/Cargo.toml b/devices/Cargo.toml
index 58877d4..35574f2 100644
--- a/devices/Cargo.toml
+++ b/devices/Cargo.toml
@@ -9,6 +9,7 @@ gpu = ["gpu_buffer", "gpu_display", "gpu_renderer"]
 tpm = ["tpm2"]
 
 [dependencies]
+audio_streams = "*"
 bit_field = { path = "../bit_field" }
 byteorder = "*"
 data_model = { path = "../data_model" }
diff --git a/devices/src/lib.rs b/devices/src/lib.rs
index edc886a..d1d516d 100644
--- a/devices/src/lib.rs
+++ b/devices/src/lib.rs
@@ -4,6 +4,7 @@
 
 //! Emulates virtual and hardware devices.
 
+extern crate audio_streams;
 extern crate bit_field;
 extern crate byteorder;
 extern crate data_model;
@@ -39,7 +40,7 @@ pub use self::bus::{Bus, BusDevice, BusRange};
 pub use self::cmos::Cmos;
 pub use self::i8042::I8042Device;
 pub use self::pci::{
-    PciConfigIo, PciConfigMmio, PciDevice, PciDeviceError, PciInterruptPin, PciRoot,
+    Ac97Dev, PciConfigIo, PciConfigMmio, PciDevice, PciDeviceError, PciInterruptPin, PciRoot,
 };
 pub use self::pl030::Pl030;
 pub use self::proxy::Error as ProxyError;
diff --git a/devices/src/pci/ac97.rs b/devices/src/pci/ac97.rs
new file mode 100644
index 0000000..c8eb8ca
--- /dev/null
+++ b/devices/src/pci/ac97.rs
@@ -0,0 +1,203 @@
+// 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::os::unix::io::RawFd;
+
+use audio_streams::StreamSource;
+use pci::ac97_bus_master::Ac97BusMaster;
+use pci::ac97_mixer::Ac97Mixer;
+use pci::ac97_regs::*;
+use pci::pci_configuration::{
+    PciClassCode, PciConfiguration, PciHeaderType, PciMultimediaSubclass,
+};
+use pci::pci_device::{self, PciDevice, Result};
+use pci::PciInterruptPin;
+use resources::SystemAllocator;
+use sys_util::{EventFd, GuestMemory};
+
+// Use 82801AA because it's what qemu does.
+const PCI_DEVICE_ID_INTEL_82801AA_5: u16 = 0x2415;
+
+/// AC97 audio device emulation.
+/// Provides the PCI interface for the internal Ac97 emulation.
+/// Internally the `Ac97BusMaster` and `Ac97Mixer` structs are used to emulated the bus master and
+/// mixer registers respectively. `Ac97BusMaster` handles moving smaples between guest memory and
+/// the audio backend.
+pub struct Ac97Dev {
+    config_regs: PciConfiguration,
+    // The irq events are temporarily saved here. They need to be passed to the device after the
+    // jail forks. This happens when the bus is first written.
+    irq_evt: Option<EventFd>,
+    irq_resample_evt: Option<EventFd>,
+    bus_master: Ac97BusMaster,
+    mixer: Ac97Mixer,
+}
+
+impl Ac97Dev {
+    /// Creates an 'Ac97Dev' that uses the given `GuestMemory` and starts with all registers at
+    /// default values.
+    pub fn new(mem: GuestMemory, audio_server: Box<StreamSource>) -> Self {
+        let config_regs = PciConfiguration::new(
+            0x8086,
+            PCI_DEVICE_ID_INTEL_82801AA_5,
+            PciClassCode::MultimediaController,
+            &PciMultimediaSubclass::AudioDevice,
+            None, // No Programming interface.
+            PciHeaderType::Device,
+            0x8086, // Subsystem Vendor ID
+            0x1,    // Subsystem ID.
+        );
+
+        Ac97Dev {
+            config_regs,
+            irq_evt: None,
+            irq_resample_evt: None,
+            bus_master: Ac97BusMaster::new(mem, audio_server),
+            mixer: Ac97Mixer::new(),
+        }
+    }
+
+    fn read_mixer(&mut self, offset: u64, data: &mut [u8]) {
+        match data.len() {
+            // The mixer is only accessed with 16-bit words.
+            2 => {
+                let val: u16 = self.mixer.readw(offset);
+                data[0] = val as u8;
+                data[1] = (val >> 8) as u8;
+            }
+            l => error!("mixer read length of {}", l),
+        }
+    }
+
+    fn write_mixer(&mut self, offset: u64, data: &[u8]) {
+        match data.len() {
+            // The mixer is only accessed with 16-bit words.
+            2 => self
+                .mixer
+                .writew(offset, u16::from(data[0]) | u16::from(data[1]) << 8),
+            l => error!("mixer write length of {}", l),
+        }
+        // Apply the new mixer settings to the bus master.
+        self.bus_master.update_mixer_settings(&self.mixer);
+    }
+
+    fn read_bus_master(&mut self, offset: u64, data: &mut [u8]) {
+        match data.len() {
+            1 => data[0] = self.bus_master.readb(offset),
+            2 => {
+                let val: u16 = self.bus_master.readw(offset);
+                data[0] = val as u8;
+                data[1] = (val >> 8) as u8;
+            }
+            4 => {
+                let val: u32 = self.bus_master.readl(offset);
+                data[0] = val as u8;
+                data[1] = (val >> 8) as u8;
+                data[2] = (val >> 16) as u8;
+                data[3] = (val >> 24) as u8;
+            }
+            l => error!("read length of {}", l),
+        }
+    }
+
+    fn write_bus_master(&mut self, offset: u64, data: &[u8]) {
+        match data.len() {
+            1 => self.bus_master.writeb(offset, data[0], &self.mixer),
+            2 => self
+                .bus_master
+                .writew(offset, u16::from(data[0]) | u16::from(data[1]) << 8),
+            4 => self.bus_master.writel(
+                offset,
+                (u32::from(data[0]))
+                    | (u32::from(data[1]) << 8)
+                    | (u32::from(data[2]) << 16)
+                    | (u32::from(data[3]) << 24),
+            ),
+            l => error!("write length of {}", l),
+        }
+    }
+}
+
+impl PciDevice for Ac97Dev {
+    fn debug_label(&self) -> String {
+        "AC97".to_owned()
+    }
+
+    fn assign_irq(
+        &mut self,
+        irq_evt: EventFd,
+        irq_resample_evt: EventFd,
+        irq_num: u32,
+        irq_pin: PciInterruptPin,
+    ) {
+        self.config_regs.set_irq(irq_num as u8, irq_pin);
+        self.irq_evt = Some(irq_evt);
+        self.irq_resample_evt = Some(irq_resample_evt);
+    }
+
+    fn allocate_io_bars(&mut self, resources: &mut SystemAllocator) -> Result<Vec<(u64, u64)>> {
+        let mut ranges = Vec::new();
+        let mixer_regs_addr = resources
+            .allocate_mmio_addresses(MIXER_REGS_SIZE)
+            .ok_or(pci_device::Error::IoAllocationFailed(MIXER_REGS_SIZE))?;
+        self.config_regs
+            .add_memory_region(mixer_regs_addr, MIXER_REGS_SIZE)
+            .ok_or_else(|| pci_device::Error::IoRegistrationFailed(mixer_regs_addr))?;
+        ranges.push((mixer_regs_addr, MIXER_REGS_SIZE));
+        let master_regs_addr = resources
+            .allocate_mmio_addresses(MASTER_REGS_SIZE)
+            .ok_or_else(|| pci_device::Error::IoAllocationFailed(MASTER_REGS_SIZE))?;
+        self.config_regs
+            .add_memory_region(master_regs_addr, MASTER_REGS_SIZE)
+            .ok_or_else(|| pci_device::Error::IoRegistrationFailed(master_regs_addr))?;
+        ranges.push((master_regs_addr, MASTER_REGS_SIZE));
+        Ok(ranges)
+    }
+
+    fn config_registers(&self) -> &PciConfiguration {
+        &self.config_regs
+    }
+
+    fn config_registers_mut(&mut self) -> &mut PciConfiguration {
+        &mut self.config_regs
+    }
+
+    fn keep_fds(&self) -> Vec<RawFd> {
+        if let Some(server_fds) = self.bus_master.keep_fds() {
+            server_fds
+        } else {
+            Vec::new()
+        }
+    }
+
+    fn read_bar(&mut self, addr: u64, data: &mut [u8]) {
+        let bar0 = u64::from(self.config_regs.get_bar_addr(0));
+        let bar1 = u64::from(self.config_regs.get_bar_addr(1));
+        match addr {
+            a if a >= bar0 && a < bar0 + MIXER_REGS_SIZE => self.read_mixer(addr - bar0, data),
+            a if a >= bar1 && a < bar1 + MASTER_REGS_SIZE => {
+                self.read_bus_master(addr - bar1, data)
+            }
+            _ => (),
+        }
+    }
+
+    fn write_bar(&mut self, addr: u64, data: &[u8]) {
+        let bar0 = u64::from(self.config_regs.get_bar_addr(0));
+        let bar1 = u64::from(self.config_regs.get_bar_addr(1));
+        match addr {
+            a if a >= bar0 && a < bar0 + MIXER_REGS_SIZE => self.write_mixer(addr - bar0, data),
+            a if a >= bar1 && a < bar1 + MASTER_REGS_SIZE => {
+                // Check if the irq needs to be passed to the device.
+                if let (Some(irq_evt), Some(irq_resample_evt)) =
+                    (self.irq_evt.take(), self.irq_resample_evt.take())
+                {
+                    self.bus_master.set_irq_event_fd(irq_evt, irq_resample_evt);
+                }
+                self.write_bus_master(addr - bar1, data)
+            }
+            _ => (),
+        }
+    }
+}
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.
+    }
+}
diff --git a/devices/src/pci/ac97_mixer.rs b/devices/src/pci/ac97_mixer.rs
new file mode 100644
index 0000000..58db533
--- /dev/null
+++ b/devices/src/pci/ac97_mixer.rs
@@ -0,0 +1,164 @@
+// 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 pci::ac97_regs::*;
+
+// AC97 Vendor ID
+const AC97_VENDOR_ID1: u16 = 0x8086;
+const AC97_VENDOR_ID2: u16 = 0x8086;
+
+// Master volume register is specified in 1.5dB steps.
+const MASTER_VOLUME_STEP_DB: f64 = 1.5;
+
+/// `Ac97Mixer` holds the mixer state for the AC97 bus.
+/// The mixer is used by calling the `readb`/`readw`/`readl` functions to read register values and
+/// the `writeb`/`writew`/`writel` functions to set register values.
+pub struct Ac97Mixer {
+    // Mixer Registers
+    master_volume_l: u8,
+    master_volume_r: u8,
+    master_mute: bool,
+    mic_muted: bool,
+    mic_20db: bool,
+    mic_volume: u8,
+    record_gain_l: u8,
+    record_gain_r: u8,
+    record_gain_mute: bool,
+    pcm_out_vol_l: u16,
+    pcm_out_vol_r: u16,
+    pcm_out_mute: bool,
+    power_down_control: u16,
+}
+
+impl Ac97Mixer {
+    /// Creates an 'Ac97Mixer' with the standard default register values.
+    pub fn new() -> Self {
+        Ac97Mixer {
+            master_volume_l: 0,
+            master_volume_r: 0,
+            master_mute: true,
+            mic_muted: true,
+            mic_20db: false,
+            mic_volume: 0x8,
+            record_gain_l: 0,
+            record_gain_r: 0,
+            record_gain_mute: true,
+            pcm_out_vol_l: 0x8,
+            pcm_out_vol_r: 0x8,
+            pcm_out_mute: true,
+            power_down_control: PD_REG_STATUS_MASK, // Report everything is ready.
+        }
+    }
+
+    /// Reads a word from the register at `offset`.
+    pub fn readw(&self, offset: u64) -> u16 {
+        match offset {
+            MIXER_MASTER_VOL_MUTE_02 => self.get_master_reg(),
+            MIXER_MIC_VOL_MUTE_0E => self.get_mic_volume(),
+            MIXER_PCM_OUT_VOL_MUTE_18 => self.get_pcm_out_volume(),
+            MIXER_REC_VOL_MUTE_1C => self.get_record_gain_reg(),
+            MIXER_POWER_DOWN_CONTROL_26 => self.power_down_control,
+            MIXER_VENDOR_ID1_7C => AC97_VENDOR_ID1,
+            MIXER_VENDOR_ID2_7E => AC97_VENDOR_ID2,
+            _ => 0,
+        }
+    }
+
+    /// Writes a word `val` to the register `offset`.
+    pub fn writew(&mut self, offset: u64, val: u16) {
+        match offset {
+            MIXER_MASTER_VOL_MUTE_02 => self.set_master_reg(val),
+            MIXER_MIC_VOL_MUTE_0E => self.set_mic_volume(val),
+            MIXER_PCM_OUT_VOL_MUTE_18 => self.set_pcm_out_volume(val),
+            MIXER_REC_VOL_MUTE_1C => self.set_record_gain_reg(val),
+            MIXER_POWER_DOWN_CONTROL_26 => self.set_power_down_reg(val),
+            _ => (),
+        }
+    }
+
+    /// Returns the mute status and left and right attenuation from the master volume register.
+    pub fn get_master_volume(&self) -> (bool, f64, f64) {
+        (
+            self.master_mute,
+            f64::from(self.master_volume_l) * MASTER_VOLUME_STEP_DB,
+            f64::from(self.master_volume_r) * MASTER_VOLUME_STEP_DB,
+        )
+    }
+
+    // Returns the master mute and l/r volumes (reg 0x02).
+    fn get_master_reg(&self) -> u16 {
+        let reg = (u16::from(self.master_volume_l)) << 8 | u16::from(self.master_volume_r);
+        if self.master_mute {
+            reg | MUTE_REG_BIT
+        } else {
+            reg
+        }
+    }
+
+    // Handles writes to the master register (0x02).
+    fn set_master_reg(&mut self, val: u16) {
+        self.master_mute = val & MUTE_REG_BIT != 0;
+        self.master_volume_r = (val & VOL_REG_MASK) as u8;
+        self.master_volume_l = (val >> 8 & VOL_REG_MASK) as u8;
+    }
+
+    // Returns the value read in the Mic volume register (0x0e).
+    fn get_mic_volume(&self) -> u16 {
+        let mut reg = u16::from(self.mic_volume);
+        if self.mic_muted {
+            reg |= MUTE_REG_BIT;
+        }
+        if self.mic_20db {
+            reg |= MIXER_MIC_20DB;
+        }
+        reg
+    }
+
+    // Sets the mic input mute, boost, and volume settings (0x0e).
+    fn set_mic_volume(&mut self, val: u16) {
+        self.mic_volume = (val & MIXER_VOL_MASK) as u8;
+        self.mic_muted = val & MUTE_REG_BIT != 0;
+        self.mic_20db = val & MIXER_MIC_20DB != 0;
+    }
+
+    // Returns the value read in the Mic volume register (0x18).
+    fn get_pcm_out_volume(&self) -> u16 {
+        let reg = (self.pcm_out_vol_l as u16) << 8 | self.pcm_out_vol_r as u16;
+        if self.pcm_out_mute {
+            reg | MUTE_REG_BIT
+        } else {
+            reg
+        }
+    }
+
+    // Sets the pcm output mute and volume states (0x18).
+    fn set_pcm_out_volume(&mut self, val: u16) {
+        self.pcm_out_vol_r = val & MIXER_VOL_MASK;
+        self.pcm_out_vol_l = (val >> MIXER_VOL_LEFT_SHIFT) & MIXER_VOL_MASK;
+        self.pcm_out_mute = val & MUTE_REG_BIT != 0;
+    }
+
+    // Returns the record gain register (0x01c).
+    fn get_record_gain_reg(&self) -> u16 {
+        let reg = u16::from(self.record_gain_l) << 8 | u16::from(self.record_gain_r);
+        if self.record_gain_mute {
+            reg | MUTE_REG_BIT
+        } else {
+            reg
+        }
+    }
+
+    // Handles writes to the record_gain register (0x1c).
+    fn set_record_gain_reg(&mut self, val: u16) {
+        self.record_gain_mute = val & MUTE_REG_BIT != 0;
+        self.record_gain_r = (val & VOL_REG_MASK) as u8;
+        self.record_gain_l = (val >> 8 & VOL_REG_MASK) as u8;
+    }
+
+    // Handles writes to the powerdown ctrl/status register (0x26).
+    fn set_power_down_reg(&mut self, val: u16) {
+        self.power_down_control =
+            (val & !PD_REG_STATUS_MASK) | (self.power_down_control & PD_REG_STATUS_MASK);
+    }
+}
diff --git a/devices/src/pci/ac97_regs.rs b/devices/src/pci/ac97_regs.rs
new file mode 100644
index 0000000..bcca05b
--- /dev/null
+++ b/devices/src/pci/ac97_regs.rs
@@ -0,0 +1,247 @@
+// 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.
+
+#![allow(dead_code)]
+
+// Audio Mixer Registers
+// 00h Reset
+// 02h Master Volume Mute
+// 04h Headphone Volume Mute
+// 06h Master Volume Mono Mute
+// 08h Master Tone (R & L)
+// 0Ah PC_BEEP Volume Mute
+// 0Ch Phone Volume Mute
+// 0Eh Mic Volume Mute
+// 10h Line In Volume Mute
+// 12h CD Volume Mute
+// 14h Video Volume Mute
+// 16h Aux Volume Mute
+// 18h PCM Out Volume Mute
+// 1Ah Record Select
+// 1Ch Record Gain Mute
+// 1Eh Record Gain Mic Mute
+// 20h General Purpose
+// 22h 3D Control
+// 24h AC’97 RESERVED
+// 26h Powerdown Ctrl/Stat
+// 28h Extended Audio
+// 2Ah Extended Audio Ctrl/Stat
+
+// Size of IO register regions
+pub const MIXER_REGS_SIZE: u64 = 0x100;
+pub const MASTER_REGS_SIZE: u64 = 0x400;
+
+pub const MIXER_MASTER_VOL_MUTE_02: u64 = 0x02;
+pub const MIXER_MIC_VOL_MUTE_0E: u64 = 0x0e;
+pub const MIXER_PCM_OUT_VOL_MUTE_18: u64 = 0x18;
+pub const MIXER_REC_VOL_MUTE_1C: u64 = 0x1c;
+pub const MIXER_POWER_DOWN_CONTROL_26: u64 = 0x26;
+pub const MIXER_VENDOR_ID1_7C: u64 = 0x7c;
+pub const MIXER_VENDOR_ID2_7E: u64 = 0x7e;
+
+// Bus Master regs from ICH spec:
+// 00h PI_BDBAR PCM In Buffer Descriptor list Base Address Register
+// 04h PI_CIV PCM In Current Index Value
+// 05h PI_LVI PCM In Last Valid Index
+// 06h PI_SR PCM In Status Register
+// 08h PI_PICB PCM In Position In Current Buffer
+// 0Ah PI_PIV PCM In Prefetched Index Value
+// 0Bh PI_CR PCM In Control Register
+// 10h PO_BDBAR PCM Out Buffer Descriptor list Base Address Register
+// 14h PO_CIV PCM Out Current Index Value
+// 15h PO_LVI PCM Out Last Valid Index
+// 16h PO_SR PCM Out Status Register
+// 18h PO_PICB PCM Out Position In Current Buffer
+// 1Ah PO_PIV PCM Out Prefetched Index Value
+// 1Bh PO_CR PCM Out Control Register
+// 20h MC_BDBAR Mic. In Buffer Descriptor list Base Address Register
+// 24h PM_CIV Mic. In Current Index Value
+// 25h MC_LVI Mic. In Last Valid Index
+// 26h MC_SR Mic. In Status Register
+// 28h MC_PICB Mic In Position In Current Buffer
+// 2Ah MC_PIV Mic. In Prefetched Index Value
+// 2Bh MC_CR Mic. In Control Register
+// 2Ch GLOB_CNT Global Control
+// 30h GLOB_STA Global Status
+// 34h ACC_SEMA Codec Write Semaphore Register
+
+// Global Control
+pub const GLOB_CNT_2C: u64 = 0x2C;
+pub const GLOB_CNT_COLD_RESET: u32 = 0x0000_0002;
+pub const GLOB_CNT_WARM_RESET: u32 = 0x0000_0004;
+pub const GLOB_CNT_STABLE_BITS: u32 = 0x0000_007f; // Bits not affected by reset.
+
+// Global status
+pub const GLOB_STA_30: u64 = 0x30;
+pub const GLOB_STA_RESET_VAL: u32 = 0x0000_0100; // primary codec ready set.
+                                                 // glob_sta bits
+pub const GS_MD3: u32 = 1 << 17;
+pub const GS_AD3: u32 = 1 << 16;
+pub const GS_RCS: u32 = 1 << 15;
+pub const GS_B3S12: u32 = 1 << 14;
+pub const GS_B2S12: u32 = 1 << 13;
+pub const GS_B1S12: u32 = 1 << 12;
+pub const GS_S1R1: u32 = 1 << 11;
+pub const GS_S0R1: u32 = 1 << 10;
+pub const GS_S1CR: u32 = 1 << 9;
+pub const GS_S0CR: u32 = 1 << 8;
+pub const GS_MINT: u32 = 1 << 7;
+pub const GS_POINT: u32 = 1 << 6;
+pub const GS_PIINT: u32 = 1 << 5;
+pub const GS_RSRVD: u32 = 1 << 4 | 1 << 3;
+pub const GS_MOINT: u32 = 1 << 2;
+pub const GS_MIINT: u32 = 1 << 1;
+pub const GS_GSCI: u32 = 1;
+pub const GS_RO_MASK: u32 = GS_B3S12
+    | GS_B2S12
+    | GS_B1S12
+    | GS_S1CR
+    | GS_S0CR
+    | GS_MINT
+    | GS_POINT
+    | GS_PIINT
+    | GS_RSRVD
+    | GS_MOINT
+    | GS_MIINT;
+pub const GS_VALID_MASK: u32 = 0x0003_ffff;
+pub const GS_WCLEAR_MASK: u32 = GS_RCS | GS_S1R1 | GS_S0R1 | GS_GSCI;
+
+pub const ACC_SEMA_34: u64 = 0x34;
+
+// Audio funciton registers.
+pub const CIV_OFFSET: u64 = 0x04;
+pub const LVI_OFFSET: u64 = 0x05;
+pub const SR_OFFSET: u64 = 0x06;
+pub const PICB_OFFSET: u64 = 0x08;
+pub const PIV_OFFSET: u64 = 0x0a;
+pub const CR_OFFSET: u64 = 0x0b;
+
+// Capture
+pub const PI_BASE_00: u64 = 0x00;
+pub const PI_BDBAR_00: u64 = PI_BASE_00;
+pub const PI_CIV_04: u64 = PI_BASE_00 + CIV_OFFSET;
+pub const PI_LVI_05: u64 = PI_BASE_00 + LVI_OFFSET;
+pub const PI_SR_06: u64 = PI_BASE_00 + SR_OFFSET;
+pub const PI_PICB_08: u64 = PI_BASE_00 + PICB_OFFSET;
+pub const PI_PIV_0A: u64 = PI_BASE_00 + PIV_OFFSET;
+pub const PI_CR_0B: u64 = PI_BASE_00 + CR_OFFSET;
+
+// Play Out
+pub const PO_BASE_10: u64 = 0x10;
+pub const PO_BDBAR_10: u64 = PO_BASE_10;
+pub const PO_CIV_14: u64 = PO_BASE_10 + CIV_OFFSET;
+pub const PO_LVI_15: u64 = PO_BASE_10 + LVI_OFFSET;
+pub const PO_SR_16: u64 = PO_BASE_10 + SR_OFFSET;
+pub const PO_PICB_18: u64 = PO_BASE_10 + PICB_OFFSET;
+pub const PO_PIV_1A: u64 = PO_BASE_10 + PIV_OFFSET;
+pub const PO_CR_1B: u64 = PO_BASE_10 + CR_OFFSET;
+
+// Microphone
+pub const MC_BASE_20: u64 = 0x20;
+pub const MC_BDBAR_20: u64 = MC_BASE_20;
+pub const MC_CIV_24: u64 = MC_BASE_20 + CIV_OFFSET;
+pub const MC_LVI_25: u64 = MC_BASE_20 + LVI_OFFSET;
+pub const MC_SR_26: u64 = MC_BASE_20 + SR_OFFSET;
+pub const MC_PICB_28: u64 = MC_BASE_20 + PICB_OFFSET;
+pub const MC_PIV_2A: u64 = MC_BASE_20 + PIV_OFFSET;
+pub const MC_CR_2B: u64 = MC_BASE_20 + CR_OFFSET;
+
+// Status Register Bits.
+pub const SR_DCH: u16 = 0x01;
+pub const SR_CELV: u16 = 0x02;
+pub const SR_LVBCI: u16 = 0x04;
+pub const SR_BCIS: u16 = 0x08;
+pub const SR_FIFOE: u16 = 0x10;
+pub const SR_VALID_MASK: u16 = 0x1f;
+pub const SR_WCLEAR_MASK: u16 = SR_FIFOE | SR_BCIS | SR_LVBCI;
+pub const SR_RO_MASK: u16 = SR_DCH | SR_CELV;
+pub const SR_INT_MASK: u16 = SR_BCIS | SR_LVBCI;
+
+// Control Register Bits.
+pub const CR_RPBM: u8 = 0x01;
+pub const CR_RR: u8 = 0x02;
+pub const CR_LVBIE: u8 = 0x04;
+pub const CR_FEIE: u8 = 0x08;
+pub const CR_IOCE: u8 = 0x10;
+pub const CR_VALID_MASK: u8 = 0x1f;
+pub const CR_DONT_CLEAR_MASK: u8 = CR_IOCE | CR_FEIE | CR_LVBIE;
+
+// Mixer register bits
+pub const MUTE_REG_BIT: u16 = 0x8000;
+pub const VOL_REG_MASK: u16 = 0x003f;
+pub const MIXER_VOL_MASK: u16 = 0x001f;
+pub const MIXER_VOL_LEFT_SHIFT: usize = 8;
+pub const MIXER_MIC_20DB: u16 = 0x0040;
+// Powerdown reg
+pub const PD_REG_STATUS_MASK: u16 = 0x000f;
+pub const PD_REG_OUTPUT_MUTE_MASK: u16 = 0xb200;
+pub const PD_REG_INPUT_MUTE_MASK: u16 = 0x0d00;
+
+// Buffer descriptors are four bytes of pointer and 4 bytes of control/length.
+pub const DESCRIPTOR_LENGTH: usize = 8;
+pub const BD_IOC: u32 = 1 << 31;
+
+/// The functions that are supported by the Ac97 subsystem.
+#[derive(Copy, Clone)]
+pub enum Ac97Function {
+    Input,
+    Output,
+    Microphone,
+}
+
+/// Registers for individual audio functions.
+/// Each audio function in Ac97 gets a set of these registers.
+#[derive(Clone, Default)]
+pub struct Ac97FunctionRegs {
+    pub bdbar: u32,
+    pub civ: u8,
+    pub lvi: u8,
+    pub sr: u16,
+    pub picb: u16,
+    pub piv: u8,
+    pub cr: u8,
+}
+
+impl Ac97FunctionRegs {
+    /// Creates a new set of function registers, these can be used for the capture, playback, or
+    /// microphone functions.
+    pub fn new() -> Self {
+        let mut regs = Ac97FunctionRegs {
+            sr: SR_DCH,
+            ..Default::default()
+        };
+        regs.do_reset();
+        regs
+    }
+
+    /// Reset all the registers to the PoR defaults.
+    pub fn do_reset(&mut self) {
+        self.bdbar = 0;
+        self.civ = 0;
+        self.lvi = 0;
+        self.sr = SR_DCH;
+        self.picb = 0;
+        self.piv = 0;
+        self.cr &= CR_DONT_CLEAR_MASK;
+    }
+
+    /// Read register 4, 5, and 6 as one 32 bit word.
+    /// According to the ICH spec, reading these three with one 32 bit access is allowed.
+    pub fn atomic_status_regs(&self) -> u32 {
+        u32::from(self.civ) | u32::from(self.lvi) << 8 | u32::from(self.sr) << 16
+    }
+
+    /// Returns the mask for enabled interrupts. The returned mask represents the bits in the status
+    /// register that should trigger and interrupt.
+    pub fn int_mask(&self) -> u16 {
+        let mut int_mask = 0;
+        if self.cr & CR_LVBIE != 0 {
+            int_mask |= SR_LVBCI;
+        }
+        if self.cr & CR_IOCE != 0 {
+            int_mask |= SR_BCIS;
+        }
+        int_mask
+    }
+}
diff --git a/devices/src/pci/mod.rs b/devices/src/pci/mod.rs
index 2579ba7..07147b4 100644
--- a/devices/src/pci/mod.rs
+++ b/devices/src/pci/mod.rs
@@ -4,10 +4,15 @@
 
 //! Implements pci devices and busses.
 
+mod ac97;
+mod ac97_bus_master;
+mod ac97_mixer;
+mod ac97_regs;
 mod pci_configuration;
 mod pci_device;
 mod pci_root;
 
+pub use self::ac97::Ac97Dev;
 pub use self::pci_configuration::{
     PciCapability, PciCapabilityID, PciClassCode, PciConfiguration, PciHeaderType,
     PciProgrammingInterface, PciSubclass,