diff --git a/devices/src/pci/ac97_bus_master.rs b/devices/src/pci/ac97_bus_master.rs index 1334125e23..68c7912837 100644 --- a/devices/src/pci/ac97_bus_master.rs +++ b/devices/src/pci/ac97_bus_master.rs @@ -25,7 +25,7 @@ use crate::pci::ac97_mixer::Ac97Mixer; use crate::pci::ac97_regs::*; const DEVICE_SAMPLE_RATE: usize = 48000; -const DEVICE_CHANNEL_COUNT: usize = 2; +const DEVICE_INPUT_CHANNEL_COUNT: usize = 2; // Bus Master registers. Keeps the state of the bus master register values. Used to share the state // between the main and audio threads. @@ -69,6 +69,26 @@ impl Ac97BusMasterRegs { Ac97Function::Microphone => &mut self.mc_regs, } } + + fn channel_count(&self, func: Ac97Function) -> usize { + fn output_channel_count(glob_cnt: u32) -> usize { + let val = (glob_cnt & GLOB_CNT_PCM_246_MASK) >> 20; + match val { + 0 => 2, + 1 => 4, + 2 => 6, + _ => { + warn!("unknown channel_count: 0x{:x}", val); + return 2; + } + } + } + + match func { + Ac97Function::Output => output_channel_count(self.glob_cnt), + _ => DEVICE_INPUT_CHANNEL_COUNT, + } + } } // Internal error type used for reporting errors from guest memory reading. @@ -295,7 +315,7 @@ impl Ac97BusMaster { regs.po_regs.picb } else { // Estimate how many samples have been played since the last audio callback. - let num_channels = 2; + let num_channels = regs.channel_count(Ac97Function::Output) as u64; 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. @@ -499,7 +519,8 @@ impl Ac97BusMaster { }; let buffer_samples = current_buffer_size(self.regs.lock().func_regs(func), &self.mem)?; - let buffer_frames = buffer_samples / DEVICE_CHANNEL_COUNT; + let num_channels = self.regs.lock().channel_count(func); + let buffer_frames = buffer_samples / num_channels; thread_info.thread_run.store(true, Ordering::Relaxed); let thread_run = thread_info.thread_run.clone(); let thread_semaphore = thread_info.thread_semaphore.clone(); @@ -525,7 +546,7 @@ impl Ac97BusMaster { .audio_server .new_stream( direction, - DEVICE_CHANNEL_COUNT, + num_channels, SampleFormat::S16LE, DEVICE_SAMPLE_RATE, buffer_frames, @@ -678,7 +699,7 @@ fn next_guest_buffer<'a>( let offset = get_buffer_offset(func_regs, mem, index)? .try_into() .map_err(|_| AudioError::InvalidBufferOffset)?; - let frames = get_buffer_samples(func_regs, mem, index)? / DEVICE_CHANNEL_COUNT; + let frames = get_buffer_samples(func_regs, mem, index)? / regs.channel_count(func); Ok(Some(GuestBuffer { index, @@ -959,8 +980,13 @@ mod test { } #[test] - #[ignore] // flaky - see crbug.com/1058881 - fn start_playback() { + fn run_playback() { + start_playback(2); + start_playback(4); + start_playback(6); + } + + fn start_playback(num_channels: usize) { const TIMEOUT: Duration = Duration::from_millis(500); const LVI_MASK: u8 = 0x1f; // Five bits for 32 total entries. const IOC_MASK: u32 = 0x8000_0000; // Interrupt on completion. @@ -997,15 +1023,28 @@ mod test { bm.writeb(PO_LVI_15, LVI_MASK, &mixer); assert_eq!(bm.readb(PO_CIV_14), 0); + // Set channel count. + let mut cnt = bm.readl(GLOB_CNT_2C); + cnt &= !GLOB_CNT_PCM_246_MASK; + if num_channels == 4 { + cnt |= GLOB_CNT_PCM_4; + } else if num_channels == 6 { + cnt |= GLOB_CNT_PCM_6; + } + bm.writel(GLOB_CNT_2C, cnt); + // Start. bm.writeb(PO_CR_1B, CR_IOCE | CR_RPBM, &mixer); - assert_eq!(bm.readw(PO_PICB_18), 0); + // TODO(crbug.com/1058881): The test is flaky in builder. + // assert_eq!(bm.readw(PO_PICB_18), 0); let mut stream = stream_source.get_last_stream(); // Trigger callback and see that CIV has not changed, since only 1 // buffer has been sent. assert!(stream.trigger_callback_with_timeout(TIMEOUT)); + assert_eq!(stream.num_channels(), num_channels); + let mut civ = bm.readb(PO_CIV_14); assert_eq!(civ, 0); diff --git a/devices/src/pci/ac97_mixer.rs b/devices/src/pci/ac97_mixer.rs index 8c8e192c7c..045d964375 100644 --- a/devices/src/pci/ac97_mixer.rs +++ b/devices/src/pci/ac97_mixer.rs @@ -7,6 +7,8 @@ use crate::pci::ac97_regs::*; // AC97 Vendor ID const AC97_VENDOR_ID1: u16 = 0x8086; const AC97_VENDOR_ID2: u16 = 0x8086; +// Extented Audio ID +const AC97_EXTENDED_ID: u16 = MIXER_EI_CDAC | MIXER_EI_SDAC | MIXER_EI_LDAC; // Master volume register is specified in 1.5dB steps. const MASTER_VOLUME_STEP_DB: f64 = 1.5; @@ -59,6 +61,7 @@ impl Ac97Mixer { 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_EXTENDED_AUDIO_ID_28 => AC97_EXTENDED_ID, MIXER_VENDOR_ID1_7C => AC97_VENDOR_ID1, MIXER_VENDOR_ID2_7E => AC97_VENDOR_ID2, _ => 0, diff --git a/devices/src/pci/ac97_regs.rs b/devices/src/pci/ac97_regs.rs index 20b35ec09b..fc27dd0f61 100644 --- a/devices/src/pci/ac97_regs.rs +++ b/devices/src/pci/ac97_regs.rs @@ -37,9 +37,15 @@ 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_EXTENDED_AUDIO_ID_28: u64 = 0x28; pub const MIXER_VENDOR_ID1_7C: u64 = 0x7c; pub const MIXER_VENDOR_ID2_7E: u64 = 0x7e; +// Extended Audio ID Bits. +pub const MIXER_EI_CDAC: u16 = 0x0040; // PCM Center DAC is available. +pub const MIXER_EI_SDAC: u16 = 0x0080; // PCM Surround DAC is available. +pub const MIXER_EI_LDAC: u16 = 0x0100; // PCM LFE DAC is available. + // 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 @@ -72,10 +78,18 @@ 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. +// PCM 4/6 Enable bits +pub const GLOB_CNT_PCM_2: u32 = 0x0000_0000; // 2 channels +pub const GLOB_CNT_PCM_4: u32 = 0x0010_0000; // 4 channels +pub const GLOB_CNT_PCM_6: u32 = 0x0020_0000; // 6 channels +pub const GLOB_CNT_PCM_246_MASK: u32 = GLOB_CNT_PCM_4 | GLOB_CNT_PCM_6; // channel mask + // 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 +// Primary codec ready set and turn on D20:21 to support 4 and 6 channels on PCM out. +pub const GLOB_STA_RESET_VAL: u32 = 0x0030_0100; + +// glob_sta bits pub const GS_MD3: u32 = 1 << 17; pub const GS_AD3: u32 = 1 << 16; pub const GS_RCS: u32 = 1 << 15;