summary refs log tree commit diff
path: root/devices/src/pci/ac97.rs
diff options
context:
space:
mode:
Diffstat (limited to 'devices/src/pci/ac97.rs')
-rw-r--r--devices/src/pci/ac97.rs203
1 files changed, 203 insertions, 0 deletions
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)
+            }
+            _ => (),
+        }
+    }
+}