diff options
-rw-r--r-- | Cargo.lock | 5 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | devices/Cargo.toml | 1 | ||||
-rw-r--r-- | devices/src/lib.rs | 3 | ||||
-rw-r--r-- | devices/src/pci/ac97.rs | 203 | ||||
-rw-r--r-- | devices/src/pci/ac97_bus_master.rs | 814 | ||||
-rw-r--r-- | devices/src/pci/ac97_mixer.rs | 164 | ||||
-rw-r--r-- | devices/src/pci/ac97_regs.rs | 247 | ||||
-rw-r--r-- | devices/src/pci/mod.rs | 5 |
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(®s.pi_regs, offset - PI_BASE_00), + PO_BASE_10...PO_CR_1B => readb_func_regs(®s.po_regs, offset - PO_BASE_10), + MC_BASE_20...MC_CR_2B => readb_func_regs(®s.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, |