diff options
Diffstat (limited to 'devices/src/pci/ac97_bus_master.rs')
-rw-r--r-- | devices/src/pci/ac97_bus_master.rs | 814 |
1 files changed, 814 insertions, 0 deletions
diff --git a/devices/src/pci/ac97_bus_master.rs b/devices/src/pci/ac97_bus_master.rs new file mode 100644 index 0000000..190fdce --- /dev/null +++ b/devices/src/pci/ac97_bus_master.rs @@ -0,0 +1,814 @@ +// Copyright 2018 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +use std; +use std::error::Error; +use std::fmt::{self, Display}; +use std::io::Write; +use std::os::unix::io::RawFd; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; +use std::thread; +use std::time::Instant; + +use audio_streams::{PlaybackBuffer, PlaybackBufferStream, StreamControl, StreamSource}; +use data_model::{VolatileMemory, VolatileSlice}; +use pci::ac97_mixer::Ac97Mixer; +use pci::ac97_regs::*; +use sync::Mutex; +use sys_util::{self, set_rt_prio_limit, set_rt_round_robin, EventFd, GuestAddress, GuestMemory}; + +const DEVICE_SAMPLE_RATE: usize = 48000; + +// Bus Master registers. Keeps the state of the bus master register values. Used to share the state +// between the main and audio threads. +struct Ac97BusMasterRegs { + pi_regs: Ac97FunctionRegs, // Input + po_regs: Ac97FunctionRegs, // Output + po_pointer_update_time: Instant, // Time the picb and civ regs were last updated. + mc_regs: Ac97FunctionRegs, // Microphone + glob_cnt: u32, + glob_sta: u32, + + // IRQ event - driven by the glob_sta register. + irq_evt: Option<EventFd>, +} + +impl Ac97BusMasterRegs { + fn new() -> Ac97BusMasterRegs { + Ac97BusMasterRegs { + pi_regs: Ac97FunctionRegs::new(), + po_regs: Ac97FunctionRegs::new(), + po_pointer_update_time: Instant::now(), + mc_regs: Ac97FunctionRegs::new(), + glob_cnt: 0, + glob_sta: GLOB_STA_RESET_VAL, + irq_evt: None, + } + } + + fn func_regs(&mut self, func: Ac97Function) -> &Ac97FunctionRegs { + match func { + Ac97Function::Input => &self.pi_regs, + Ac97Function::Output => &self.po_regs, + Ac97Function::Microphone => &self.mc_regs, + } + } + + fn func_regs_mut(&mut self, func: Ac97Function) -> &mut Ac97FunctionRegs { + match func { + Ac97Function::Input => &mut self.pi_regs, + Ac97Function::Output => &mut self.po_regs, + Ac97Function::Microphone => &mut self.mc_regs, + } + } +} + +// Internal error type used for reporting errors from the audio playback thread. +#[derive(Debug)] +enum PlaybackError { + // Failure getting the address of the audio buffer. + ReadingGuestBufferAddress(sys_util::GuestMemoryError), + // Failure reading samples from guest memory. + ReadingGuestSamples(data_model::VolatileMemoryError), + // Failure to get an buffer from the stream. + StreamError(Box<Error>), + // Failure writing to the audio output. + WritingOutput(std::io::Error), +} + +impl Error for PlaybackError {} + +impl Display for PlaybackError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + PlaybackError::ReadingGuestBufferAddress(e) => { + write!(f, "Failed to get the address of the audio buffer: {:?}.", e) + } + PlaybackError::ReadingGuestSamples(e) => { + write!(f, "Failed to read samples from guest memory: {:?}.", e) + } + PlaybackError::StreamError(e) => { + write!(f, "Failed to get a buffer from the stream: {:?}", e) + } + PlaybackError::WritingOutput(e) => write!(f, "Failed to write audio output: {:?}.", e), + } + } +} + +type PlaybackResult<T> = std::result::Result<T, PlaybackError>; + +/// `Ac97BusMaster` emulates the bus master portion of AC97. It exposes a register read/write +/// interface compliant with the ICH bus master. +pub struct Ac97BusMaster { + // Keep guest memory as each function will use it for buffer descriptors. + mem: GuestMemory, + regs: Arc<Mutex<Ac97BusMasterRegs>>, + acc_sema: u8, + + // Audio thread book keeping. + audio_thread_po: Option<thread::JoinHandle<()>>, + audio_thread_po_run: Arc<AtomicBool>, + po_stream_control: Option<Box<dyn StreamControl>>, + + // Audio server used to create playback streams. + audio_server: Box<dyn StreamSource>, + + // Thread for hadlind IRQ resample events from the guest. + irq_resample_thread: Option<thread::JoinHandle<()>>, +} + +impl Ac97BusMaster { + /// Creates an Ac97BusMaster` object that plays audio from `mem` to streams provided by + /// `audio_server`. + pub fn new(mem: GuestMemory, audio_server: Box<dyn StreamSource>) -> Self { + Ac97BusMaster { + mem, + regs: Arc::new(Mutex::new(Ac97BusMasterRegs::new())), + acc_sema: 0, + + audio_thread_po: None, + audio_thread_po_run: Arc::new(AtomicBool::new(false)), + po_stream_control: None, + + audio_server, + + irq_resample_thread: None, + } + } + + /// Returns any file descriptors that need to be kept open when entering a jail. + pub fn keep_fds(&self) -> Option<Vec<RawFd>> { + self.audio_server.keep_fds() + } + + /// Provides the events needed to raise interrupts in the guest. + pub fn set_irq_event_fd(&mut self, irq_evt: EventFd, irq_resample_evt: EventFd) { + let thread_regs = self.regs.clone(); + self.regs.lock().irq_evt = Some(irq_evt); + self.irq_resample_thread = Some(thread::spawn(move || { + loop { + if let Err(e) = irq_resample_evt.read() { + error!( + "Failed to read the irq event from the resample thread: {:?}.", + e + ); + break; + } + { + // Scope for the lock on thread_regs. + let mut regs = thread_regs.lock(); + let int_mask = regs.func_regs(Ac97Function::Output).int_mask(); + if regs.func_regs(Ac97Function::Output).sr & int_mask != 0 { + if let Some(irq_evt) = regs.irq_evt.as_ref() { + if let Err(e) = irq_evt.write(1) { + error!("Failed to set the irq from the resample thread: {:?}.", e); + break; + } + } + } + } + } + })); + } + + /// Called when `mixer` has been changed and the new values should be applied to currently + /// active streams. + pub fn update_mixer_settings(&mut self, mixer: &Ac97Mixer) { + if let Some(control) = self.po_stream_control.as_mut() { + // The audio server only supports one volume, not separate left and right. + let (muted, left_volume, _right_volume) = mixer.get_master_volume(); + control.set_volume(left_volume); + control.set_mute(muted); + } + } + + /// Checks if the bus master is in the cold reset state. + pub fn is_cold_reset(&self) -> bool { + self.regs.lock().glob_cnt & GLOB_CNT_COLD_RESET == 0 + } + + /// Reads a byte from the given `offset`. + pub fn readb(&mut self, offset: u64) -> u8 { + fn readb_func_regs(func_regs: &Ac97FunctionRegs, offset: u64) -> u8 { + match offset { + CIV_OFFSET => func_regs.civ, + LVI_OFFSET => func_regs.lvi, + SR_OFFSET => func_regs.sr as u8, + PIV_OFFSET => func_regs.piv, + CR_OFFSET => func_regs.cr, + _ => 0, + } + } + + let regs = self.regs.lock(); + match offset { + PI_BASE_00...PI_CR_0B => readb_func_regs(®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. + } +} |