diff --git a/Cargo.lock b/Cargo.lock index 454edf66728df6a508fe193e2dc5a073ffef72c0..45af85f5fe861c58f1d3216760302d7e7518fdcd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "acpi" @@ -227,9 +227,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.73" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +checksum = "e9e8aabfac534be767c909e0690571677d49f41bd8465ae876fe043d52ba5292" [[package]] name = "cexpr" @@ -1917,6 +1917,7 @@ version = "2.4.0" dependencies = [ "acpi", "address_space", + "alsa", "anyhow", "block_backend", "byteorder", diff --git a/machine/src/lib.rs b/machine/src/lib.rs index f066cdc80afeff5cf7a54a17d56e74589317f95b..8c3cfa9524e41437c619ca9ce8466eed7d46f372 100644 --- a/machine/src/lib.rs +++ b/machine/src/lib.rs @@ -130,8 +130,9 @@ use virtio::VirtioDeviceQuirk; use virtio::{ balloon_allow_list, find_port_by_nr, get_max_nr, vhost, virtio_register_pcidevops_type, virtio_register_sysbusdevops_type, Balloon, BalloonConfig, Block, BlockState, Input, - InputConfig, Serial, SerialPort, VirtioBlkDevConfig, VirtioDevice, VirtioMmioDevice, - VirtioMmioState, VirtioNetState, VirtioPciDevice, VirtioSerialState, VIRTIO_TYPE_CONSOLE, + InputConfig, Serial, SerialPort, Sound, SoundConfig, VirtioBlkDevConfig, VirtioDevice, + VirtioMmioDevice, VirtioMmioState, VirtioNetState, VirtioPciDevice, VirtioSerialState, + VIRTIO_TYPE_CONSOLE, }; #[cfg(feature = "virtio_gpu")] use virtio::{Gpu, GpuDevConfig}; @@ -789,6 +790,36 @@ pub trait MachineOps: MachineLifecycle { Ok(()) } + fn add_virtio_sound(&mut self, vm_config: &mut VmConfig, cfg_args: &str) -> Result<()> { + if vm_config.dev_name.contains_key("sound") { + bail!("Only one sound device is supported for each vm."); + } + let config = SoundConfig::try_parse_from(str_slip_to_clap(cfg_args, true, false))?; + vm_config.dev_name.insert("sound".to_string(), 1); + + let sys_mem = self.get_sys_mem(); + let sound = Arc::new(Mutex::new(Sound::new(config.clone(), sys_mem.clone()))); + match config.classtype.as_str() { + "virtio-sound-device" => { + check_arg_nonexist!( + ("bus", config.bus), + ("addr", config.addr), + ("multifunction", config.multifunction) + ); + self.add_virtio_mmio_device(config.id.clone(), sound)?; + } + _ => { + check_arg_exist!(("bus", config.bus), ("addr", config.addr)); + let bdf = PciBdf::new(config.bus.unwrap(), config.addr.unwrap()); + let multi_func = config.multifunction.unwrap_or_default(); + self.add_virtio_pci_device(&config.id, &bdf, sound, multi_func, false) + .with_context(|| "Failed to add virtio pci balloon device")?; + } + } + + Ok(()) + } + /// Add virtio serial device. /// /// # Arguments @@ -2069,6 +2100,7 @@ pub trait MachineOps: MachineLifecycle { ("pcie-root-port", add_pci_root_port, cfg_args), ("virtio-balloon-device" | "virtio-balloon-pci", add_virtio_balloon, vm_config, cfg_args), ("virtio-mem-device" | "virtio-mem-pci", add_virtio_mem, vm_config, cfg_args), + ("virtio-sound-device" | "virtio-sound-pci", add_virtio_sound, vm_config, cfg_args), ("virtio-input-device" | "virtio-input-pci", add_virtio_input, cfg_args), ("virtio-serial-device" | "virtio-serial-pci", add_virtio_serial, vm_config, cfg_args), ("virtconsole" | "virtserialport", add_virtio_serial_port, vm_config, cfg_args), diff --git a/virtio/Cargo.toml b/virtio/Cargo.toml index 3d173235eafa8fcceb0c4d389ee568ae046f981e..3f2407f655ad6146613e9b7c1c28895e5218987e 100644 --- a/virtio/Cargo.toml +++ b/virtio/Cargo.toml @@ -28,6 +28,7 @@ chardev_backend = {path = "../chardev_backend" } ui = { path = "../ui", features = ["console"], optional = true } trace = {path = "../trace"} clap = { version = "=4.1.4", default-features = false, features = ["std", "derive"] } +alsa = "0.7" [features] default = [] diff --git a/virtio/src/device/mod.rs b/virtio/src/device/mod.rs index cc3a7ab66b701617f3cb49eda47f9a34337d816a..0a9f5373737765db052450c6fb7034d765d50948 100644 --- a/virtio/src/device/mod.rs +++ b/virtio/src/device/mod.rs @@ -22,3 +22,4 @@ pub mod rng; #[cfg(feature = "virtio_scsi")] pub mod scsi_cntlr; pub mod serial; +pub mod sound; diff --git a/virtio/src/device/sound.rs b/virtio/src/device/sound.rs new file mode 100644 index 0000000000000000000000000000000000000000..9ea825856042da17124eefc5c52d81912c3d91d9 --- /dev/null +++ b/virtio/src/device/sound.rs @@ -0,0 +1,1043 @@ +use crate::{ + error::*, iov_discard_front, iov_from_buf, iov_size, iov_to_buf, read_config_default, Element, + Queue, VirtioBase, VirtioDevice, VirtioInterrupt, VirtioInterruptType, VIRTIO_F_VERSION_1, + VIRTIO_TYPE_SOUND, +}; +use address_space::{AddressAttr, AddressSpace}; +use anyhow::{Context, Result}; +use clap::{ArgAction, Parser}; +use log::error; +use machine_manager::{ + config::valid_id, + config::{get_pci_df, parse_bool, DEFAULT_VIRTQUEUE_SIZE}, + event_loop::register_event_helper, + event_loop::unregister_event_helper, +}; +use std::os::unix::io::{AsRawFd, RawFd}; +use std::rc::Rc; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, Mutex}; +use vmm_sys_util::{epoll::EventSet, eventfd::EventFd}; + +use util::byte_code::ByteCode; +use util::gen_base_func; +use util::loop_context::{read_fd, EventNotifier, NotifierCallback, NotifierOperation}; + +use alsa::{ + pcm::{Format, State, PCM}, + Direction, +}; +use std::error::Error; +use std::mem::size_of; +use std::mem::size_of_val; + +const VIRTIO_SND_JACK_DEFAULT: u32 = 2; +const VIRTIO_SND_STREAM_DEFAULT: u32 = 2; +const VIRTIO_SND_CHMAP_DEFAULT: u32 = 2; + +const VIRTIO_SND_R_PCM_INFO: u32 = 256; +const VIRTIO_SND_R_PCM_SET_PARAMS: u32 = 257; +const VIRTIO_SND_R_PCM_PREPARE: u32 = 258; +const VIRTIO_SND_R_PCM_RELEASE: u32 = 259; +const VIRTIO_SND_R_PCM_START: u32 = 260; +const VIRTIO_SND_R_PCM_STOP: u32 = 261; + +const VIRTIO_SND_S_OK: u32 = 0x8000; +const VIRTIO_SND_S_BAD_MSG: u32 = 0x8001; +const VIRTIO_SND_S_NOT_SUPP: u32 = 0x8002; +const VIRTIO_SND_S_IO_ERR: u32 = 0x8003; + +const VIRTIO_SND_D_OUTPUT: u8 = 0; +const VIRTIO_SND_D_INPUT: u8 = 1; + +const VIRTIO_SND_PCM_FMT_S8: u8 = 3; +const VIRTIO_SND_PCM_FMT_U8: u8 = 4; +const VIRTIO_SND_PCM_FMT_S16: u8 = 5; +const VIRTIO_SND_PCM_FMT_U16: u8 = 6; +const VIRTIO_SND_PCM_FMT_S32: u8 = 17; +const VIRTIO_SND_PCM_FMT_U32: u8 = 18; +const VIRTIO_SND_PCM_FMT_FLOAT: u8 = 19; + +const VIRTIO_SND_PCM_RATE_5512: u8 = 0; +const VIRTIO_SND_PCM_RATE_8000: u8 = 1; +const VIRTIO_SND_PCM_RATE_11025: u8 = 2; +const VIRTIO_SND_PCM_RATE_16000: u8 = 3; +const VIRTIO_SND_PCM_RATE_22050: u8 = 4; +const VIRTIO_SND_PCM_RATE_32000: u8 = 5; +const VIRTIO_SND_PCM_RATE_44100: u8 = 6; +const VIRTIO_SND_PCM_RATE_48000: u8 = 7; +const VIRTIO_SND_PCM_RATE_64000: u8 = 8; +const VIRTIO_SND_PCM_RATE_88200: u8 = 9; +const VIRTIO_SND_PCM_RATE_96000: u8 = 10; +const VIRTIO_SND_PCM_RATE_176400: u8 = 11; +const VIRTIO_SND_PCM_RATE_192000: u8 = 12; +const VIRTIO_SND_PCM_RATE_384000: u8 = 13; + +const SUPPORTED_FORMATS: u32 = 1 << VIRTIO_SND_PCM_FMT_S8 + | 1 << VIRTIO_SND_PCM_FMT_U8 + | 1 << VIRTIO_SND_PCM_FMT_S16 + | 1 << VIRTIO_SND_PCM_FMT_U16 + | 1 << VIRTIO_SND_PCM_FMT_S32 + | 1 << VIRTIO_SND_PCM_FMT_U32 + | 1 << VIRTIO_SND_PCM_FMT_FLOAT; + +const SUPPORTED_RATES: u32 = 1 << VIRTIO_SND_PCM_RATE_5512 + | 1 << VIRTIO_SND_PCM_RATE_8000 + | 1 << VIRTIO_SND_PCM_RATE_11025 + | 1 << VIRTIO_SND_PCM_RATE_16000 + | 1 << VIRTIO_SND_PCM_RATE_22050 + | 1 << VIRTIO_SND_PCM_RATE_32000 + | 1 << VIRTIO_SND_PCM_RATE_44100 + | 1 << VIRTIO_SND_PCM_RATE_48000 + | 1 << VIRTIO_SND_PCM_RATE_64000 + | 1 << VIRTIO_SND_PCM_RATE_88200 + | 1 << VIRTIO_SND_PCM_RATE_96000 + | 1 << VIRTIO_SND_PCM_RATE_176400 + | 1 << VIRTIO_SND_PCM_RATE_192000 + | 1 << VIRTIO_SND_PCM_RATE_384000; + +#[derive(Default, Clone)] +struct CtrlHdr { + code: u32, +} + +#[derive(Default, Clone)] +struct QueryInfo { + hdr: CtrlHdr, + start_id: u32, + count: u32, + size: u32, +} + +impl ByteCode for QueryInfo {} + +#[repr(C)] +#[derive(Debug, Default, Clone)] +struct SoundInfo { + hda_fn_nid: u32, +} + +#[repr(C)] +#[derive(Debug, Default, Clone)] +struct PCMInfo { + hdr: SoundInfo, + features: u32, + formats: u64, + rates: u64, + direction: u8, + channels_min: u8, + channels_max: u8, + padding: [u8; 5], +} + +impl ByteCode for PCMInfo {} + +#[derive(Copy, Clone, Debug, Default)] +struct SndHdr { + code: u32, +} + +impl ByteCode for SndHdr {} + +#[repr(C)] +#[derive(Copy, Clone, Debug, Default)] +struct PCMHdr { + hdr: SndHdr, + stream_id: u32, +} + +impl ByteCode for PCMHdr {} + +#[repr(C)] +#[derive(Copy, Clone, Debug, Default)] +struct PCMSetParams { + hdr: PCMHdr, + buffer_bytes: u32, + period_bytes: u32, + features: u32, + channels: u8, + format: u8, + rate: u8, + padding: u8, +} + +impl ByteCode for PCMSetParams {} + +#[derive(Copy, Clone, Debug, Default)] +struct PCMXfer { + stream_id: u32, +} +impl ByteCode for PCMXfer {} + +#[repr(C, packed)] +#[derive(Copy, Clone, Debug, Default)] +struct VirtioSndConfig { + jacks: u32, + streams: u32, + chmaps: u32, +} + +impl ByteCode for VirtioSndConfig {} + +#[repr(C)] +#[derive(Copy, Clone, Debug, Default)] +struct SndPcmStatus { + status: u32, + latency_bytes: u32, +} +impl ByteCode for SndPcmStatus {} + +struct PCMStream { + info: PCMInfo, + params: PCMSetParams, + id: u32, + audio_out: Mutex>, + queue: Arc>, +} + +impl PCMStream { + fn new(queue: Arc>, direction: u8) -> Self { + Self { + info: PCMInfo { + hdr: SoundInfo::default(), + direction, + features: 0, + channels_min: 1, + channels_max: 2, + formats: SUPPORTED_FORMATS as u64, + rates: SUPPORTED_RATES as u64, + padding: [0; 5], + }, + params: PCMSetParams::default(), + id: 0, + audio_out: Mutex::new(None), + queue: queue.clone(), + } + } +} + +pub struct SndPCM { + streams: Vec, + driver_features: u64, +} + +impl SndPCM { + fn new( + streams: u32, + tx_queue: Arc>, + rx_queue: Arc>, + driver_features: u64, + ) -> SndPCM { + let mut pcm = SndPCM { + streams: Vec::new(), + driver_features, + }; + for i in 0..streams { + let (queue, direction) = if i % 2 == 0 { + (tx_queue.clone(), VIRTIO_SND_D_OUTPUT) + } else { + (rx_queue.clone(), VIRTIO_SND_D_INPUT) + }; + pcm.streams.push(PCMStream::new(queue, direction)); + } + pcm + } + + fn handle_pcm_info(&mut self, mem_space: &AddressSpace, elem: &mut Element) -> (u32, usize) { + let mut req: QueryInfo = QueryInfo::default(); + + let read_size = match iov_to_buf(mem_space, &None, &elem.out_iovec, req.as_mut_bytes()) { + Ok(s) => s, + Err(e) => { + error!("Failed to read stream ID: {}", e); + return (VIRTIO_SND_S_BAD_MSG, 0); + } + }; + + if read_size != size_of::() { + error!(""); + return (VIRTIO_SND_S_BAD_MSG, 0); + } + + let start_id = req.start_id; + let count = req.count; + + let mut pcm_info = Vec::with_capacity(count as usize); + + for i in 0..count { + let stream = self.pcm_get_stream(i + start_id); + pcm_info.push(stream.info.clone()); + } + + let data: Vec = pcm_info + .iter() + .flat_map(|info| info.as_bytes().iter().copied()) + .collect(); + + iov_from_buf(mem_space, &None, &mut elem.in_iovec[1..], &data[..]).unwrap(); + + (VIRTIO_SND_S_OK, size_of::() * count as usize) + } + + fn handle_pcm_set_params(&mut self, mem_space: &AddressSpace, elem: &Element) -> u32 { + let mut req = PCMSetParams::default(); + + let read_size = match iov_to_buf(mem_space, &None, &elem.out_iovec, req.as_mut_bytes()) { + Ok(s) => s, + Err(e) => { + error!("Failed to read stream ID: {}", e); + return VIRTIO_SND_S_BAD_MSG; + } + }; + + if read_size != size_of::() { + error!(""); + return VIRTIO_SND_S_BAD_MSG; + } + + let supported_rates = SUPPORTED_RATES; + if (supported_rates & (1 << req.rate)) == 0 { + return VIRTIO_SND_S_NOT_SUPP; + } + + let supported_formats = SUPPORTED_FORMATS; + if (supported_formats & (1 << req.format)) == 0 { + return VIRTIO_SND_S_NOT_SUPP; + } + + if req.channels < 1 || req.channels > 2 { + return VIRTIO_SND_S_NOT_SUPP; + } + + let stream_id = req.hdr.stream_id; + let st_params = self.pcm_get_params(stream_id); + + st_params.buffer_bytes = req.buffer_bytes; + st_params.period_bytes = req.period_bytes; + st_params.features = req.features; + st_params.channels = req.channels; + st_params.format = req.format; + st_params.rate = req.rate; + + VIRTIO_SND_S_OK + } + + fn pcm_get_params(&mut self, stream_id: u32) -> &mut PCMSetParams { + &mut self.streams[stream_id as usize].params + } + + fn pcm_get_stream(&mut self, stream_id: u32) -> &mut PCMStream { + &mut self.streams[stream_id as usize] + } + + fn handle_pcm_prepare(&mut self, mem_space: &AddressSpace, elem: &mut Element) -> u32 { + let mut stream_id = 0; + let data_iovec = + iov_discard_front(&mut elem.out_iovec, size_of::() as u64).unwrap(); + + let read_size = match iov_to_buf(mem_space, &None, &data_iovec, stream_id.as_mut_bytes()) { + Ok(s) => s, + Err(e) => { + error!("Failed to read stream ID: {}", e); + return VIRTIO_SND_S_BAD_MSG; + } + }; + + if read_size != size_of::() { + error!(""); + return VIRTIO_SND_S_BAD_MSG; + } + + let stream = self.pcm_get_stream(stream_id); + stream.id = stream_id; + stream.info.direction = stream_id as u8; + stream.info.hdr.hda_fn_nid = 0; + stream.info.features = 0; + stream.info.channels_min = 1; + stream.info.channels_max = 2; + stream.info.formats = SUPPORTED_FORMATS as u64; + stream.info.rates = SUPPORTED_RATES as u64; + + let mut audio_out = stream.audio_out.lock().unwrap(); + + let params = stream.params; + + match AudioStream::new( + params.rate, + params.format, + params.channels, + stream.info.direction, + ) { + Ok(audio) => { + if audio.pcm.prepare().is_ok() { + *audio_out = Some(audio); + VIRTIO_SND_S_OK + } else { + VIRTIO_SND_S_BAD_MSG + } + } + Err(_) => VIRTIO_SND_S_BAD_MSG, + } + } + + fn handle_pcm_start(&mut self, mem_space: &AddressSpace, elem: &Element) -> u32 { + let mut req: PCMHdr = PCMHdr::default(); + let read_size = match iov_to_buf(mem_space, &None, &elem.out_iovec, req.as_mut_bytes()) { + Ok(s) => s, + Err(e) => { + error!("Failed to read stream ID: {}", e); + return VIRTIO_SND_S_BAD_MSG; + } + }; + + if read_size != size_of::() { + error!(""); + return VIRTIO_SND_S_BAD_MSG; + } + + let stream = self.pcm_get_stream(req.stream_id); + + let mut audio_out = stream.audio_out.lock().unwrap(); + + if let Some(audio) = &mut *audio_out { + if audio.pcm.start().is_ok() { + return VIRTIO_SND_S_OK; + } + } + + VIRTIO_SND_S_OK + } + + fn handle_pcm_stop(&mut self, mem_space: &AddressSpace, elem: &Element) -> u32 { + let mut req: PCMHdr = PCMHdr::default(); + let read_size = match iov_to_buf(mem_space, &None, &elem.out_iovec, req.as_mut_bytes()) { + Ok(s) => s, + Err(e) => { + error!("Failed to read stream ID: {}", e); + return VIRTIO_SND_S_BAD_MSG; + } + }; + + if read_size != size_of::() { + error!(""); + return VIRTIO_SND_S_BAD_MSG; + } + + let stream = self.pcm_get_stream(req.stream_id); + let mut audio_out = stream.audio_out.lock().unwrap(); + + if let Some(audio) = &mut *audio_out { + if audio.pcm.drop().is_ok() { + return VIRTIO_SND_S_OK; + } + } + VIRTIO_SND_S_OK + } + + fn handle_pcm_release( + &mut self, + mem_space: &Arc, + elem: &mut Element, + interrupt_cb: &Arc, + ) -> u32 { + let driver_features = self.driver_features; + let mut stream_id: u32 = 0; + + let data_iovec = match iov_discard_front(&mut elem.out_iovec, size_of::() as u64) { + Some(iovec) => iovec, + None => return VIRTIO_SND_S_BAD_MSG, + }; + + let read_size = match iov_to_buf(mem_space, &None, &data_iovec, stream_id.as_mut_bytes()) { + Ok(s) => s, + Err(e) => { + error!("Failed to read stream ID: {}", e); + return VIRTIO_SND_S_BAD_MSG; + } + }; + + if read_size != size_of::() { + error!(""); + return VIRTIO_SND_S_BAD_MSG; + } + + if stream_id as usize >= self.streams.len() { + error!("Invalid stream ID: {}", stream_id); + return VIRTIO_SND_S_BAD_MSG; + } + + let stream = self.pcm_get_stream(stream_id); + let mut queue = stream.queue.lock().unwrap(); + + let unhandled_count = match queue.vring.avail_ring_len() { + Ok(len) => len, + Err(e) => { + error!("Failed to get unhandled descriptors: {}", e); + return VIRTIO_SND_S_IO_ERR; + } + }; + + if unhandled_count > 0 { + for _ in 0..unhandled_count { + if let Ok(elem) = queue.vring.pop_avail(mem_space, driver_features) { + let _ = queue.vring.add_used(elem.index, 0); + } + } + } + (interrupt_cb)(&VirtioInterruptType::Vring, Some(&queue), false) + .with_context(|| VirtioError::InterruptTrigger("ctrl", VirtioInterruptType::Vring)) + .unwrap(); + *stream.audio_out.lock().unwrap() = None; + + VIRTIO_SND_S_OK + } +} + +struct SoundIoHandler { + /// The features of driver. + driver_features: u64, + /// Address space. + mem_space: Arc, + /// Control queue. + control_queue: Arc>, + /// Control EventFd. + control_evt: Arc, + /// Tx queue. + tx_queue: Arc>, + /// Tx EventFd. + tx_evt: Arc, + /// Rx queue. + rx_queue: Arc>, + /// Rx EventFd. + rx_evt: Arc, + /// Device is broken or not. + device_broken: Arc, + /// The interrupt call back function. + interrupt_cb: Arc, + pcm: Arc>, +} + +impl SoundIoHandler { + fn handle_ctrl(&mut self) -> Result<()> { + let mut resp = SndHdr::default(); + + let mut pcm = self.pcm.lock().unwrap(); + let mut locked_queue = self.control_queue.lock().unwrap(); + loop { + let mut elem = locked_queue + .vring + .pop_avail(&self.mem_space, self.driver_features) + .with_context(|| "Failed to pop avail ring for sound control queue")?; + if elem.desc_num == 0 { + break; + } + + let mut ctrl_hdr = CtrlHdr::default(); + let read_size = iov_to_buf( + &self.mem_space, + &None, + &mut elem.out_iovec, + ctrl_hdr.code.as_mut_bytes(), + ) + .with_context(|| "Failed to get control header")?; + + if read_size != size_of::() { + error!( + "Control header size mismatch: got {}, expected {}", + read_size, + size_of::() + ); + continue; + } + + let mut payload_size = 0; + match ctrl_hdr.code { + VIRTIO_SND_R_PCM_INFO => { + (resp.code, payload_size) = pcm.handle_pcm_info(&self.mem_space, &mut elem); + } + + VIRTIO_SND_R_PCM_SET_PARAMS => { + resp.code = pcm.handle_pcm_set_params(&self.mem_space, &mut elem); + } + + VIRTIO_SND_R_PCM_PREPARE => { + resp.code = pcm.handle_pcm_prepare(&self.mem_space, &mut elem); + } + + VIRTIO_SND_R_PCM_RELEASE => { + resp.code = + pcm.handle_pcm_release(&self.mem_space, &mut elem, &self.interrupt_cb); + } + + VIRTIO_SND_R_PCM_START => { + resp.code = pcm.handle_pcm_start(&self.mem_space, &mut elem); + } + + VIRTIO_SND_R_PCM_STOP => { + resp.code = pcm.handle_pcm_stop(&self.mem_space, &mut elem); + } + + _ => { + error!("Control queue header class {} not supported", ctrl_hdr.code); + resp.code = VIRTIO_SND_S_OK; + } + } + + let status = elem + .in_iovec + .first() + .with_context(|| "Failed to get device writable iovec")?; + self.mem_space + .write_object::(&resp, status.addr, AddressAttr::Ram)?; + + locked_queue + .vring + .add_used(elem.index, (size_of_val(&resp) + payload_size) as u32) + .with_context(|| format!("Failed to add used ring {}", elem.index))?; + + if locked_queue.vring.should_notify(self.driver_features) { + (self.interrupt_cb)(&VirtioInterruptType::Vring, Some(&locked_queue), false) + .with_context(|| { + VirtioError::InterruptTrigger("ctrl", VirtioInterruptType::Vring) + })?; + } + } + + Ok(()) + } + + fn handle_tx(&mut self) -> Result<()> { + let mut hdr: PCMXfer = PCMXfer::default(); + let mut locked_queue = self.tx_queue.lock().unwrap(); + let mut elem: Element = locked_queue + .vring + .pop_avail(&self.mem_space, self.driver_features) + .with_context(|| "Failed to pop avail ring for sound tx queue")?; + + let read_size = iov_to_buf( + &self.mem_space, + &None, + &mut elem.out_iovec, + hdr.as_mut_bytes(), + ) + .with_context(|| "Failed to get control header")?; + + if read_size != size_of::() { + error!( + "Control header size mismatch: got {}, expected {}", + read_size, + size_of::() + ); + return Ok(()); + } + + let size = iov_size(&elem.out_iovec).unwrap() - size_of_val(&hdr); + let mut pcm = self.pcm.lock().unwrap(); + let stream_id = hdr.stream_id; + let stream = pcm.pcm_get_stream(stream_id); + + let mut data: Vec = vec![0; size]; + + iov_to_buf(&self.mem_space, &None, &elem.out_iovec[1..], &mut data[..]) + .with_context(|| "[virtio-snd] Failed to get data") + .unwrap(); + + let mut audio_out = stream.audio_out.lock().unwrap(); + + if let Some(audio) = &mut *audio_out { + audio + .write(&mut data[..]) + .expect("Failed to write audio data"); + } + + let mut resp = SndPcmStatus::default(); + resp.status = VIRTIO_SND_S_OK; + resp.latency_bytes = size as u32; + + iov_from_buf( + &self.mem_space, + &None, + &mut elem.in_iovec, + resp.as_mut_bytes(), + )?; + + locked_queue + .vring + .add_used(elem.index, std::mem::size_of_val(&resp) as u32) + .with_context(|| format!("Failed to add used ring {}", elem.index))?; + + if locked_queue.vring.should_notify(self.driver_features) { + (self.interrupt_cb)(&VirtioInterruptType::Vring, Some(&locked_queue), false) + .with_context(|| { + VirtioError::InterruptTrigger("ctrl", VirtioInterruptType::Vring) + })?; + trace::virtqueue_send_interrupt("sound", &*locked_queue as *const _ as u64); + } + + Ok(()) + } + + fn handle_rx(&mut self) -> Result<()> { + let mut hdr: PCMXfer = PCMXfer::default(); + let mut locked_queue = self.rx_queue.lock().unwrap(); + let mut elem = locked_queue + .vring + .pop_avail(&self.mem_space, self.driver_features) + .with_context(|| "Failed to pop avail ring for sound rx queue")?; + + let read_size = iov_to_buf( + &self.mem_space, + &None, + &mut elem.out_iovec, + hdr.as_mut_bytes(), + ) + .with_context(|| "Failed to get control header")?; + + if read_size != size_of::() { + error!( + "Control header size mismatch: got {}, expected {}", + read_size, + size_of::() + ); + return Ok(()); + } + + let mut pcm = self.pcm.lock().unwrap(); + let stream_id = hdr.stream_id; + let stream = pcm.pcm_get_stream(stream_id); + + let size = iov_size(&mut elem.in_iovec).unwrap() - size_of::(); + + let mut data: Vec = vec![0; size]; + + let mut audio_out = stream.audio_out.lock().unwrap(); + + if let Some(audio) = &mut *audio_out { + audio + .read(&mut data[..]) + .expect("Failed to read audio data"); + } + + let mut resp = SndPcmStatus::default(); + resp.status = VIRTIO_SND_S_OK; + resp.latency_bytes = 0; + + iov_from_buf(&self.mem_space, &None, &mut elem.in_iovec, &mut data[..])?; + + let last_idx = elem.in_iovec.len() - 1; + let last_iovec = &mut elem.in_iovec[last_idx]; + + iov_from_buf( + &self.mem_space, + &None, + std::slice::from_mut(last_iovec), + resp.as_mut_bytes(), + )?; + + locked_queue + .vring + .add_used( + elem.index, + std::mem::size_of_val(&resp) as u32 + size as u32, + ) + .with_context(|| format!("Failed to add used ring {}", elem.index))?; + + (self.interrupt_cb)(&VirtioInterruptType::Vring, Some(&locked_queue), false) + .with_context(|| VirtioError::InterruptTrigger("sound", VirtioInterruptType::Vring))?; + Ok(()) + } + + fn ctrl_notifier(&self, sound_io: Arc>) -> Vec { + let device_broken = self.device_broken.clone(); + let handler: Rc = Rc::new(move |_, fd: RawFd| { + read_fd(fd); + + if device_broken.load(Ordering::SeqCst) { + return None; + } + sound_io.lock().unwrap().handle_ctrl().unwrap_or_else(|e| { + error!("Failed to handle ctrl queue, error is {:?}.", e); + }); + None + }); + let notifiers = vec![build_event_notifier( + self.control_evt.as_raw_fd(), + Some(handler), + NotifierOperation::AddShared, + EventSet::IN, + )]; + notifiers + } + + fn tx_notifier(&self, sound_io: Arc>) -> Vec { + let device_broken = self.device_broken.clone(); + let handler: Rc = Rc::new(move |_, fd: RawFd| { + read_fd(fd); + + if device_broken.load(Ordering::SeqCst) { + return None; + } + + sound_io.lock().unwrap().handle_tx().unwrap_or_else(|e| { + error!("Failed to handle tx queue, error is {:?}.", e); + }); + None + }); + let notifiers = vec![build_event_notifier( + self.tx_evt.as_raw_fd(), + Some(handler), + NotifierOperation::AddShared, + EventSet::IN, + )]; + notifiers + } + + fn rx_notifier(&self, sound_io: Arc>) -> Vec { + let device_broken = self.device_broken.clone(); + let handler: Rc = Rc::new(move |_, fd: RawFd| { + read_fd(fd); + + if device_broken.load(Ordering::SeqCst) { + return None; + } + + sound_io.lock().unwrap().handle_rx().unwrap_or_else(|e| { + error!("Failed to handle rx queue, error is {:?}.", e); + }); + None + }); + let notifiers = vec![build_event_notifier( + self.rx_evt.as_raw_fd(), + Some(handler), + NotifierOperation::AddShared, + EventSet::IN, + )]; + notifiers + } +} + +fn build_event_notifier( + fd: RawFd, + handler: Option>, + op: NotifierOperation, + event: EventSet, +) -> EventNotifier { + let mut handlers = Vec::new(); + if let Some(h) = handler { + handlers.push(h); + } + EventNotifier::new(op, fd, None, event, handlers) +} + +#[derive(Parser, Debug, Clone, Default)] +#[command(no_binary_name(true))] +pub struct SoundConfig { + #[arg(long)] + pub classtype: String, + #[arg(long, value_parser = valid_id)] + pub id: String, + #[arg(long)] + pub bus: Option, + #[arg(long, value_parser = get_pci_df)] + pub addr: Option<(u8, u8)>, + #[arg(long, value_parser = parse_bool, action = ArgAction::Append)] + pub multifunction: Option, +} + +pub struct AudioStream { + pcm: PCM, +} + +impl AudioStream { + fn new(sample_rate: u8, bits: u8, channels: u8, direction: u8) -> Result> { + let dir = match direction { + VIRTIO_SND_D_OUTPUT => Direction::Playback, + VIRTIO_SND_D_INPUT => Direction::Capture, + _ => Direction::Playback, + }; + let pcm = PCM::new("default", dir, false) + .with_context(|| format!("Failed to open ALSA PCM device (direction: {:?})", dir))?; + let buffer_time = 500000; + let period_time = buffer_time / 4; + { + let hwp = alsa::pcm::HwParams::any(&pcm).unwrap(); + let sample_rate = match sample_rate { + VIRTIO_SND_PCM_RATE_8000 => 8000, + VIRTIO_SND_PCM_RATE_11025 => 11025, + VIRTIO_SND_PCM_RATE_16000 => 16000, + VIRTIO_SND_PCM_RATE_22050 => 22050, + VIRTIO_SND_PCM_RATE_32000 => 32000, + VIRTIO_SND_PCM_RATE_44100 => 44100, + VIRTIO_SND_PCM_RATE_48000 => 48000, + VIRTIO_SND_PCM_RATE_96000 => 96000, + _ => return Err("Unsupported sample rate".into()), + }; + hwp.set_rate(sample_rate, alsa::ValueOr::Nearest)?; + hwp.set_format(match bits { + VIRTIO_SND_PCM_FMT_S8 => Format::S8, + VIRTIO_SND_PCM_FMT_U8 => Format::U8, + VIRTIO_SND_PCM_FMT_S16 => Format::S16LE, + VIRTIO_SND_PCM_FMT_U16 => Format::U16LE, + VIRTIO_SND_PCM_FMT_S32 => Format::S32LE, + VIRTIO_SND_PCM_FMT_FLOAT => Format::FloatLE, + _ => Format::Unknown, + })?; + hwp.set_channels(channels as u32)?; + hwp.set_buffer_time_near(buffer_time, alsa::ValueOr::Nearest)?; + hwp.set_period_time_near(period_time, alsa::ValueOr::Nearest)?; + pcm.hw_params(&hwp)?; + let swp = pcm.sw_params_current()?; + let period_size = hwp.get_period_size()?; + swp.set_start_threshold(period_size)?; + swp.set_avail_min(period_size)?; + } + Ok(Self { pcm }) + } + + fn write(&mut self, data: &[u8]) -> Result<(), Box> { + if data.is_empty() { + return Ok(()); + } + + if self.pcm.state() != State::Running { + self.pcm.prepare()?; + self.pcm.start()?; + } + + let io = self.pcm.io_bytes(); + if let Err(e) = io.writei(&data[..]) { + if self.pcm.state() == State::XRun { + self.pcm.prepare()?; + self.pcm.start()?; + } + } + + Ok(()) + } + + fn read(&mut self, data: &mut [u8]) -> Result<(), Box> { + if data.is_empty() { + return Ok(()); + } + + if self.pcm.state() != State::Running { + self.pcm.prepare()?; + self.pcm.start()?; + } + + let io = self.pcm.io_bytes(); + if let Err(e) = io.readi(data) { + if self.pcm.state() == State::XRun { + self.pcm.prepare()?; + self.pcm.start()?; + } + } + + Ok(()) + } +} + +pub struct Sound { + base: VirtioBase, + snd_cfg: SoundConfig, + mem_space: Arc, +} + +impl Sound { + /// Create a sound device. + /// + /// # Arguments + /// + /// * `snd_cfg` - sound configuration. + pub fn new(snd_cfg: SoundConfig, mem_space: Arc) -> Sound { + let queue_num = 4; + Sound { + base: VirtioBase::new(VIRTIO_TYPE_SOUND, queue_num, DEFAULT_VIRTQUEUE_SIZE), + snd_cfg, + mem_space, + } + } +} + +impl VirtioDevice for Sound { + gen_base_func!(virtio_base, virtio_base_mut, VirtioBase, base); + + fn realize(&mut self) -> Result<()> { + self.init_config_features()?; + Ok(()) + } + + fn init_config_features(&mut self) -> Result<()> { + self.base.device_features = 1u64 << VIRTIO_F_VERSION_1; + Ok(()) + } + + fn read_config(&self, offset: u64, data: &mut [u8]) -> Result<()> { + let config = VirtioSndConfig { + jacks: VIRTIO_SND_JACK_DEFAULT, + streams: VIRTIO_SND_STREAM_DEFAULT, + chmaps: VIRTIO_SND_CHMAP_DEFAULT, + }; + read_config_default(config.as_bytes(), offset, data) + } + + fn write_config(&mut self, _offset: u64, _data: &[u8]) -> Result<()> { + Ok(()) + } + + fn activate( + &mut self, + mem_space: Arc, + interrupt_cb: Arc, + queue_evts: Vec>, + ) -> Result<()> { + let queues = &self.base.queues.clone(); + let control_queue = queues[0].clone(); + let control_evt = queue_evts[0].clone(); + let tx_queue = queues[2].clone(); + let tx_evt = queue_evts[2].clone(); + let rx_queue = queues[3].clone(); + let rx_evt = queue_evts[3].clone(); + let cloned_tx_queue = tx_queue.clone(); + let cloned_rx_queue = rx_queue.clone(); + + let sound_io = Arc::new(Mutex::new(SoundIoHandler { + driver_features: self.base.driver_features, + mem_space, + control_queue, + control_evt, + tx_queue, + tx_evt, + rx_queue, + rx_evt, + device_broken: self.base.broken.clone(), + interrupt_cb, + pcm: Arc::new(Mutex::new(SndPCM::new( + 2, + cloned_tx_queue, + cloned_rx_queue, + self.base.driver_features, + ))), + })); + + let cloned_sound_io = sound_io.clone(); + let locked_sound_io = sound_io.lock().unwrap(); + let ctrl_notifiers = locked_sound_io.ctrl_notifier(cloned_sound_io); + let tx_notifiers = locked_sound_io.tx_notifier(sound_io.clone()); + let rx_notifiers = locked_sound_io.rx_notifier(sound_io.clone()); + drop(locked_sound_io); + register_event_helper(ctrl_notifiers, None, &mut self.base.deactivate_evts) + .with_context(|| "Failed to register sound event notifier to MainLoop")?; + register_event_helper(tx_notifiers, None, &mut self.base.deactivate_evts) + .with_context(|| "Failed to register sound event notifier to MainLoop")?; + register_event_helper(rx_notifiers, None, &mut self.base.deactivate_evts) + .with_context(|| "Failed to register sound event notifier to MainLoop")?; + self.base.broken.store(false, Ordering::SeqCst); + + Ok(()) + } + + fn deactivate(&mut self) -> Result<()> { + unregister_event_helper(None, &mut self.base.deactivate_evts) + } +} diff --git a/virtio/src/lib.rs b/virtio/src/lib.rs index bfa0ef5db08e847178b8bb100fe63aa758f34fb9..ac40708ef7056d61b58a0df8865c9f51ecfdade4 100644 --- a/virtio/src/lib.rs +++ b/virtio/src/lib.rs @@ -44,6 +44,7 @@ pub use device::rng::{Rng, RngConfig, RngState}; #[cfg(feature = "virtio_scsi")] pub use device::scsi_cntlr as ScsiCntlr; pub use device::serial::{find_port_by_nr, get_max_nr, Serial, SerialPort, VirtioSerialState}; +pub use device::sound::*; pub use error::VirtioError; pub use queue::*; pub use transport::virtio_mmio::{VirtioMmioDevice, VirtioMmioState}; @@ -67,7 +68,7 @@ use devices::pci::register_pcidevops_type; use devices::sysbus::register_sysbusdevops_type; use machine_manager::config::ConfigCheck; use migration_derive::ByteCode; -use util::aio::{iov_from_buf_direct, mem_to_buf, Iovec}; +use util::aio::{iov_from_buf_direct, mem_from_buf, mem_to_buf, Iovec}; use util::byte_code::ByteCode; use util::num_ops::{read_u32, write_u32}; use util::AsAny; @@ -88,6 +89,7 @@ pub const VIRTIO_TYPE_GPU: u32 = 16; pub const VIRTIO_TYPE_INPUT: u32 = 18; pub const VIRTIO_TYPE_VSOCK: u32 = 19; pub const VIRTIO_TYPE_MEM: u32 = 24; +pub const VIRTIO_TYPE_SOUND: u32 = 25; pub const VIRTIO_TYPE_FS: u32 = 26; // The Status of Virtio Device. @@ -844,6 +846,53 @@ pub fn iov_write_object( Ok(()) } +pub fn iov_from_buf( + mem_space: &AddressSpace, + cache: &Option, + iovec: &mut [ElemIovec], + buf: &[u8], +) -> Result { + let mut start: usize = 0; + let mut end: usize = 0; + + // Note: iovec is part of elem.in_iovec/out_iovec which has been checked + // in pop_avail(). The sum of iov_len is not greater than u32::MAX. + for iov in iovec { + let mut addr_map = Vec::new(); + mem_space.get_address_map(cache, iov.addr, u64::from(iov.len), &mut addr_map)?; + for addr in addr_map.into_iter() { + let write_len = cmp::min(iov.len as usize, buf.len() - start); + if write_len == 0 { + continue; + } + + end = start + write_len; + // SAFETY: addr_map is generated by address_space and len is not less than buf's. + unsafe { + mem_from_buf(&buf[start..end], addr.iov_base)?; + } + + start = end; + if start >= buf.len() { + break; + } + } + if start >= buf.len() { + break; + } + } + Ok(end) +} + +pub fn iov_size(iovec: &[ElemIovec]) -> Result { + let mut len: usize = 0; + for iov in iovec { + len += iov.len as usize; + } + + Ok(len) +} + /// Read iovec to buf and return the read number of bytes. pub fn iov_to_buf( mem_space: &AddressSpace,