diff options
author | paulhsia <paulhsia@chromium.org> | 2019-04-18 22:27:52 +0800 |
---|---|---|
committer | chrome-bot <chrome-bot@chromium.org> | 2019-04-20 03:58:47 -0700 |
commit | 67d124ac53e4ba4d5f64f6b368de00e90afe4f8e (patch) | |
tree | 889c2fc352890b7ca66f12561bd63ec9df091806 /devices/src/pci/ac97_bus_master.rs | |
parent | d92f81a249cdeacdd1b37574b479d35c09dc5e55 (diff) | |
download | crosvm-67d124ac53e4ba4d5f64f6b368de00e90afe4f8e.tar crosvm-67d124ac53e4ba4d5f64f6b368de00e90afe4f8e.tar.gz crosvm-67d124ac53e4ba4d5f64f6b368de00e90afe4f8e.tar.bz2 crosvm-67d124ac53e4ba4d5f64f6b368de00e90afe4f8e.tar.lz crosvm-67d124ac53e4ba4d5f64f6b368de00e90afe4f8e.tar.xz crosvm-67d124ac53e4ba4d5f64f6b368de00e90afe4f8e.tar.zst crosvm-67d124ac53e4ba4d5f64f6b368de00e90afe4f8e.zip |
devices: Add capture path in AC'97 PCI device
Add capture support in AC'97 PCI device. Only capture at 48kHz is supported. BUG=chromium:932268 TEST=cargo test -p device start_capture TEST=Run crosvm with `--cras-audio` option to run a guest vm then test audio capture by command $ arecord -D hw:0,0 -r 48000 -f dat -c 2 /tmp/test.raw Change-Id: Ie3aab1004695f0df607fef8fc337fa58cb723b65 Reviewed-on: https://chromium-review.googlesource.com/1573600 Commit-Ready: Chih-Yang Hsia <paulhsia@chromium.org> Tested-by: kokoro <noreply+kokoro@google.com> Reviewed-by: Dylan Reid <dgreid@chromium.org>
Diffstat (limited to 'devices/src/pci/ac97_bus_master.rs')
-rw-r--r-- | devices/src/pci/ac97_bus_master.rs | 279 |
1 files changed, 256 insertions, 23 deletions
diff --git a/devices/src/pci/ac97_bus_master.rs b/devices/src/pci/ac97_bus_master.rs index b1245b9..47be8c4 100644 --- a/devices/src/pci/ac97_bus_master.rs +++ b/devices/src/pci/ac97_bus_master.rs @@ -12,7 +12,10 @@ use std::sync::Arc; use std::thread; use std::time::Instant; -use audio_streams::{PlaybackBuffer, PlaybackBufferStream, StreamControl, StreamSource}; +use audio_streams::{ + capture::{CaptureBuffer, CaptureBufferStream}, + PlaybackBuffer, PlaybackBufferStream, StreamControl, StreamSource, +}; use data_model::{VolatileMemory, VolatileSlice}; use sync::Mutex; use sys_util::{ @@ -51,7 +54,7 @@ impl Ac97BusMasterRegs { } } - fn func_regs(&mut self, func: Ac97Function) -> &Ac97FunctionRegs { + fn func_regs(&self, func: Ac97Function) -> &Ac97FunctionRegs { match func { Ac97Function::Input => &self.pi_regs, Ac97Function::Output => &self.po_regs, @@ -68,13 +71,49 @@ impl Ac97BusMasterRegs { } } -// Internal error type used for reporting errors from the audio playback thread. +// Internal error type used for reporting errors from guest memory reading. #[derive(Debug)] -enum PlaybackError { +enum GuestMemoryError { // Failure getting the address of the audio buffer. ReadingGuestBufferAddress(sys_util::GuestMemoryError), // Failure reading samples from guest memory. ReadingGuestSamples(data_model::VolatileMemoryError), +} + +impl std::error::Error for GuestMemoryError {} + +impl Display for GuestMemoryError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use self::GuestMemoryError::*; + + match self { + ReadingGuestBufferAddress(e) => { + write!(f, "Failed to get the address of the audio buffer: {}.", e) + } + ReadingGuestSamples(e) => write!(f, "Failed to read samples from guest memory: {}.", e), + } + } +} + +impl From<GuestMemoryError> for PlaybackError { + fn from(err: GuestMemoryError) -> Self { + PlaybackError::ReadingGuestError(err) + } +} + +impl From<GuestMemoryError> for CaptureError { + fn from(err: GuestMemoryError) -> Self { + CaptureError::ReadingGuestError(err) + } +} + +type GuestMemoryResult<T> = std::result::Result<T, GuestMemoryError>; + +// Internal error type used for reporting errors from the audio playback thread. +#[derive(Debug)] +enum PlaybackError { + // Failure to read guest memory. + ReadingGuestError(GuestMemoryError), // Failure to get an buffer from the stream. StreamError(Box<dyn Error>), // Failure writing to the audio output. @@ -88,10 +127,7 @@ impl Display for PlaybackError { use self::PlaybackError::*; match self { - ReadingGuestBufferAddress(e) => { - write!(f, "Failed to get the address of the audio buffer: {}.", e) - } - ReadingGuestSamples(e) => write!(f, "Failed to read samples from guest memory: {}.", e), + ReadingGuestError(e) => write!(f, "Failed to read guest memory: {}.", e), StreamError(e) => write!(f, "Failed to get a buffer from the stream: {}", e), WritingOutput(e) => write!(f, "Failed to write audio output: {}.", e), } @@ -100,6 +136,33 @@ impl Display for PlaybackError { type PlaybackResult<T> = std::result::Result<T, PlaybackError>; +// Internal error type used for reporting errors from the audio capture thread. +#[derive(Debug)] +enum CaptureError { + // Failure to read guest memory. + ReadingGuestError(GuestMemoryError), + // Failure to get an buffer from the stream. + StreamError(Box<dyn Error>), + // Failure reading to the audio input. + ReadingInput(std::io::Error), +} + +impl std::error::Error for CaptureError {} + +impl Display for CaptureError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use self::CaptureError::*; + + match self { + ReadingGuestError(e) => write!(f, "Failed to read guest memory: {}.", e), + StreamError(e) => write!(f, "Failed to get a buffer from the stream: {}", e), + ReadingInput(e) => write!(f, "Failed to read audio input: {}.", e), + } + } +} + +type CaptureResult<T> = std::result::Result<T, CaptureError>; + /// `Ac97BusMaster` emulates the bus master portion of AC97. It exposes a register read/write /// interface compliant with the ICH bus master. pub struct Ac97BusMaster { @@ -108,12 +171,17 @@ pub struct Ac97BusMaster { regs: Arc<Mutex<Ac97BusMasterRegs>>, acc_sema: u8, + // Audio thread for capture stream. + audio_thread_pi: Option<thread::JoinHandle<()>>, + audio_thread_pi_run: Arc<AtomicBool>, + pi_stream_control: Option<Box<dyn StreamControl>>, + // 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 used to create playback or capture streams. audio_server: Box<dyn StreamSource>, // Thread for hadlind IRQ resample events from the guest. @@ -129,6 +197,10 @@ impl Ac97BusMaster { regs: Arc::new(Mutex::new(Ac97BusMasterRegs::new())), acc_sema: 0, + audio_thread_pi: None, + audio_thread_pi_run: Arc::new(AtomicBool::new(false)), + pi_stream_control: None, + audio_thread_po: None, audio_thread_po_run: Arc::new(AtomicBool::new(false)), po_stream_control: None, @@ -159,9 +231,20 @@ impl Ac97BusMaster { } { // 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 { + let regs = thread_regs.lock(); + // Check output irq + let po_int_mask = regs.func_regs(Ac97Function::Output).int_mask(); + if regs.func_regs(Ac97Function::Output).sr & po_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; + } + } + } + // Check input irq + let pi_int_mask = regs.func_regs(Ac97Function::Input).int_mask(); + if regs.func_regs(Ac97Function::Input).sr & pi_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); @@ -272,6 +355,7 @@ impl Ac97BusMaster { match offset { PI_CIV_04 => (), // RO PI_LVI_05 => self.set_lvi(Ac97Function::Input, val), + PI_SR_06 => self.set_sr(Ac97Function::Input, u16::from(val)), PI_PIV_0A => (), // RO PI_CR_0B => self.set_cr(Ac97Function::Input, val, mixer), PO_CIV_14 => (), // RO @@ -412,7 +496,38 @@ impl Ac97BusMaster { const AUDIO_THREAD_RTPRIO: u16 = 12; // Matches other cros audio clients. match func { - Ac97Function::Input => (), + Ac97Function::Input => { + 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 (stream_control, input_stream) = self.audio_server.new_capture_stream( + num_channels, + DEVICE_SAMPLE_RATE, + buffer_frames, + )?; + self.pi_stream_control = Some(stream_control); + self.update_mixer_settings(mixer); + + self.audio_thread_pi_run.store(true, Ordering::Relaxed); + let thread_run = self.audio_thread_pi_run.clone(); + let thread_mem = self.mem.clone(); + let thread_regs = self.regs.clone(); + + self.audio_thread_pi = 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_in_thread(thread_regs, thread_mem, &thread_run, input_stream) + { + error!("Capture error: {}", e); + } + thread_run.store(false, Ordering::Relaxed); + })); + } Ac97Function::Output => { let num_channels = 2; @@ -455,7 +570,14 @@ impl Ac97BusMaster { fn stop_audio(&mut self, func: Ac97Function) { match func { - Ac97Function::Input => (), + Ac97Function::Input => { + self.audio_thread_pi_run.store(false, Ordering::Relaxed); + if let Some(thread) = self.audio_thread_pi.take() { + if let Err(e) = thread.join() { + error!("Failed to join the capture thread: {:?}.", e); + } + } + } Ac97Function::Output => { self.audio_thread_po_run.store(false, Ordering::Relaxed); if let Some(thread) = self.audio_thread_po.take() { @@ -488,7 +610,7 @@ impl Ac97BusMaster { fn next_guest_buffer<'a>( func_regs: &mut Ac97FunctionRegs, mem: &'a GuestMemory, -) -> PlaybackResult<Option<VolatileSlice<'a>>> { +) -> GuestMemoryResult<Option<VolatileSlice<'a>>> { let sample_size = 2; if func_regs.sr & SR_DCH != 0 { @@ -498,11 +620,11 @@ fn next_guest_buffer<'a>( 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)?; + .map_err(GuestMemoryError::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)?; + .map_err(GuestMemoryError::ReadingGuestBufferAddress)?; let buffer_samples: usize = control_reg as usize & 0x0000_ffff; func_regs.picb = buffer_samples as u16; @@ -514,7 +636,7 @@ fn next_guest_buffer<'a>( let read_pos = u64::from(buffer_addr); Ok(Some( mem.get_slice(read_pos, samples_remaining as u64 * sample_size) - .map_err(PlaybackError::ReadingGuestSamples)?, + .map_err(GuestMemoryError::ReadingGuestSamples)?, )) } @@ -548,13 +670,13 @@ fn buffer_completed( regs: &mut Ac97BusMasterRegs, mem: &GuestMemory, func: Ac97Function, -) -> PlaybackResult<()> { +) -> GuestMemoryResult<()> { // 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)?; + .map_err(GuestMemoryError::ReadingGuestBufferAddress)?; let mut new_sr = regs.func_regs(func).sr; @@ -598,6 +720,41 @@ fn audio_out_thread( Ok(()) } +// Reads samples from `in_buffer` and writes it to the next buffer from guest memory. +fn capture_buffer( + regs: &mut Ac97BusMasterRegs, + mem: &GuestMemory, + in_buffer: &mut CaptureBuffer, +) -> CaptureResult<()> { + // If the current buffer had any samples in it, mark it as done. + if regs.func_regs_mut(Ac97Function::Input).picb > 0 { + buffer_completed(regs, mem, Ac97Function::Input)? + } + let func_regs = regs.func_regs_mut(Ac97Function::Input); + if let Some(buffer) = next_guest_buffer(func_regs, mem)? { + buffer + .read_from(in_buffer) + .map_err(CaptureError::ReadingInput)?; + } + Ok(()) +} + +// Runs, capturing audio from `input_stream` to the guest until stopped or an error occurs. +fn audio_in_thread( + regs: Arc<Mutex<Ac97BusMasterRegs>>, + mem: GuestMemory, + thread_run: &AtomicBool, + mut input_stream: Box<dyn CaptureBufferStream>, +) -> CaptureResult<()> { + while thread_run.load(Ordering::Relaxed) { + input_stream + .next_capture_buffer() + .map_err(CaptureError::StreamError) + .and_then(|mut cp_buf| capture_buffer(&mut regs.lock(), &mem, &mut cp_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 { @@ -639,12 +796,15 @@ fn update_sr(regs: &mut Ac97BusMasterRegs, func: Ac97Function, val: u16) { } // 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> { +fn current_buffer_size( + func_regs: &Ac97FunctionRegs, + mem: &GuestMemory, +) -> GuestMemoryResult<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)?; + .map_err(GuestMemoryError::ReadingGuestBufferAddress)?; let buffer_len: usize = control_reg as usize & 0x0000_ffff; Ok(buffer_len) } @@ -774,7 +934,7 @@ mod test { // test only checks that some samples are consumed. assert!(pos > 1000); - assert!(bm.readw(PO_SR_16) & 0x01 == 0); // DMA is running. + assert!(bm.readw(PO_SR_16) & SR_DCH == 0); // DMA is running. // civ should move eventually. for _i in 0..30 { @@ -814,4 +974,77 @@ mod test { bm.writeb(PO_CR_1B, 0, &mixer); assert!(bm.readw(PO_SR_16) & 0x01 != 0); // DMA is not running. } + + #[test] + fn start_capture() { + 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. + bm.writel(PI_BDBAR_00, 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); + 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(PI_LVI_05, LVI_MASK, &mixer); + + // Start. + bm.writeb(PI_CR_0B, CR_RPBM, &mixer); + assert_eq!(bm.readw(PI_PICB_08), 0); + + std::thread::sleep(time::Duration::from_millis(50)); + let picb = bm.readw(PI_PICB_08); + assert!(picb > 1000); + assert!(bm.readw(PI_SR_06) & SR_DCH == 0); // DMA is running. + + // civ should move eventually. + for _i in 0..10 { + let civ = bm.readb(PI_CIV_04); + if civ != 0 { + break; + } + std::thread::sleep(time::Duration::from_millis(20)); + } + assert_ne!(bm.readb(PI_CIV_04), 0); + + let civ = bm.readb(PI_CIV_04); + // Sets LVI to CIV + 1 to trigger last buffer hit + bm.writeb(PI_LVI_05, civ + 1, &mixer); + std::thread::sleep(time::Duration::from_millis(5000)); + assert_ne!(bm.readw(PI_SR_06) & SR_LVBCI, 0); // Hit last buffer + assert_eq!(bm.readw(PI_SR_06) & SR_DCH, SR_DCH); // DMA stopped because of lack of buffers. + assert_eq!(bm.readb(PI_LVI_05), bm.readb(PI_CIV_04)); + + // Clear the LVB bit + bm.writeb(PI_SR_06, SR_LVBCI as u8, &mixer); + assert!(bm.readw(PI_SR_06) & SR_LVBCI == 0); + // Reset the LVI to the last buffer and check that playback resumes + bm.writeb(PI_LVI_05, LVI_MASK, &mixer); + assert!(bm.readw(PI_SR_06) & SR_DCH == 0); // DMA restarts. + + let restart_civ = bm.readb(PI_CIV_04); + std::thread::sleep(time::Duration::from_millis(200)); + assert_ne!(bm.readb(PI_CIV_04), restart_civ); + + // Stop. + bm.writeb(PI_CR_0B, 0, &mixer); + assert!(bm.readw(PI_SR_06) & 0x01 != 0); // DMA is not running. + } } |