summary refs log tree commit diff
diff options
context:
space:
mode:
-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,