virtio: add vhost-user sound device support
Add a vhost-user sound device to cloud-hypervisor, enabling audio passthrough via an external vhost-user sound backend daemon. The implementation mirrors the existing vhost-user GPU device. The sound device uses 4 virtqueues (control, event, tx, rx) per the virtio sound specification, and sets SCHED_FIFO priority 88 on its epoll thread for low-latency audio processing. Changes span the full stack: - vm-virtio: Sound = 25 device type registration - virtio-devices: Sound device implementation, seccomp filters, ActivateError variant - vmm: SoundConfig, CLI --sound argument, OpenAPI spec, device manager wiring, seccomp filters updated for sched_setscheduler Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
480bfb7095
commit
6ce557ced8
13 changed files with 492 additions and 4 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -8,3 +8,4 @@
|
|||
/.vscode
|
||||
/vendor
|
||||
__pycache__
|
||||
/.worktrees
|
||||
|
|
|
|||
|
|
@ -33,8 +33,8 @@ use vmm::vm_config::FwCfgConfig;
|
|||
use vmm::vm_config::IvshmemConfig;
|
||||
use vmm::vm_config::{
|
||||
BalloonConfig, DeviceConfig, DiskConfig, FsConfig, LandlockConfig, NetConfig, NumaConfig,
|
||||
PciSegmentConfig, PmemConfig, RateLimiterGroupConfig, TpmConfig, UserDeviceConfig, VdpaConfig,
|
||||
VmConfig, VsockConfig,
|
||||
PciSegmentConfig, PmemConfig, RateLimiterGroupConfig, SoundConfig, TpmConfig, UserDeviceConfig,
|
||||
VdpaConfig, VmConfig, VsockConfig,
|
||||
};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
use vmm_sys_util::signal::block_signal;
|
||||
|
|
@ -424,6 +424,11 @@ fn get_cli_options_sorted(
|
|||
.help("Control serial port: off|null|pty|tty|file=</path/to/a/file>|socket=</path/to/a/file>")
|
||||
.default_value("null")
|
||||
.group("vm-config"),
|
||||
Arg::new("sound")
|
||||
.long("sound")
|
||||
.help(SoundConfig::SYNTAX)
|
||||
.num_args(1..)
|
||||
.group("vm-config"),
|
||||
Arg::new("tpm")
|
||||
.long("tpm")
|
||||
.num_args(1)
|
||||
|
|
@ -999,6 +1004,7 @@ mod unit_tests {
|
|||
balloon: None,
|
||||
fs: None,
|
||||
gpu: None,
|
||||
sound: None,
|
||||
pmem: None,
|
||||
serial: ConsoleConfig {
|
||||
file: None,
|
||||
|
|
|
|||
|
|
@ -93,6 +93,8 @@ pub enum ActivateError {
|
|||
VhostUserSetup(#[source] vhost_user::Error),
|
||||
#[error("Failed to setup vhost-user-gpu daemon")]
|
||||
VhostUserGpuSetup(#[source] vhost_user::Error),
|
||||
#[error("Failed to setup vhost-user-sound daemon")]
|
||||
VhostUserSoundSetup(#[source] vhost_user::Error),
|
||||
#[error("Failed to create seccomp filter")]
|
||||
CreateSeccompFilter(#[source] seccompiler::Error),
|
||||
#[error("Failed to create rate limiter")]
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ pub enum Thread {
|
|||
VirtioVhostBlock,
|
||||
VirtioVhostFs,
|
||||
VirtioVhostGpu,
|
||||
VirtioVhostSound,
|
||||
VirtioVhostNet,
|
||||
VirtioVhostNetCtl,
|
||||
VirtioVsock,
|
||||
|
|
@ -204,6 +205,18 @@ fn virtio_vhost_gpu_thread_rules() -> Vec<(i64, Vec<SeccompRule>)> {
|
|||
]
|
||||
}
|
||||
|
||||
fn virtio_vhost_sound_thread_rules() -> Vec<(i64, Vec<SeccompRule>)> {
|
||||
vec![
|
||||
(libc::SYS_clock_nanosleep, vec![]),
|
||||
(libc::SYS_connect, vec![]),
|
||||
(libc::SYS_nanosleep, vec![]),
|
||||
(libc::SYS_recvmsg, vec![]),
|
||||
(libc::SYS_sched_setscheduler, vec![]),
|
||||
(libc::SYS_sendmsg, vec![]),
|
||||
(libc::SYS_socket, vec![]),
|
||||
]
|
||||
}
|
||||
|
||||
fn virtio_vhost_net_ctl_thread_rules() -> Vec<(i64, Vec<SeccompRule>)> {
|
||||
vec![]
|
||||
}
|
||||
|
|
@ -284,6 +297,7 @@ fn get_seccomp_rules(thread_type: Thread) -> Vec<(i64, Vec<SeccompRule>)> {
|
|||
Thread::VirtioVhostBlock => virtio_vhost_block_thread_rules(),
|
||||
Thread::VirtioVhostFs => virtio_vhost_fs_thread_rules(),
|
||||
Thread::VirtioVhostGpu => virtio_vhost_gpu_thread_rules(),
|
||||
Thread::VirtioVhostSound => virtio_vhost_sound_thread_rules(),
|
||||
Thread::VirtioVhostNet => virtio_vhost_net_thread_rules(),
|
||||
Thread::VirtioVhostNetCtl => virtio_vhost_net_ctl_thread_rules(),
|
||||
Thread::VirtioVsock => virtio_vsock_thread_rules(),
|
||||
|
|
|
|||
|
|
@ -37,12 +37,14 @@ pub mod blk;
|
|||
pub mod fs;
|
||||
pub mod gpu;
|
||||
pub mod net;
|
||||
pub mod sound;
|
||||
pub mod vu_common_ctrl;
|
||||
|
||||
pub use self::blk::Blk;
|
||||
pub use self::fs::*;
|
||||
pub use self::gpu::Gpu;
|
||||
pub use self::net::Net;
|
||||
pub use self::sound::Sound;
|
||||
pub use self::vu_common_ctrl::VhostUserConfig;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
|
|
|
|||
267
virtio-devices/src/vhost_user/sound.rs
Normal file
267
virtio-devices/src/vhost_user/sound.rs
Normal file
|
|
@ -0,0 +1,267 @@
|
|||
// Copyright © 2024 Intel Corporation
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::io;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::{Arc, Barrier, Mutex};
|
||||
use std::{result, thread};
|
||||
|
||||
use event_monitor::event;
|
||||
use log::{error, warn};
|
||||
use seccompiler::SeccompAction;
|
||||
use vhost::vhost_user::message::{
|
||||
VhostUserConfigFlags, VhostUserProtocolFeatures, VhostUserVirtioFeatures,
|
||||
};
|
||||
use vhost::vhost_user::{FrontendReqHandler, VhostUserFrontend, VhostUserFrontendReqHandler};
|
||||
use virtio_queue::Queue;
|
||||
use vm_memory::GuestMemoryAtomic;
|
||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshottable, Transportable};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use super::vu_common_ctrl::VhostUserHandle;
|
||||
use super::{DEFAULT_VIRTIO_FEATURES, Result};
|
||||
use crate::seccomp_filters::Thread;
|
||||
use crate::thread_helper::spawn_virtio_thread;
|
||||
use crate::vhost_user::VhostUserCommon;
|
||||
use crate::{
|
||||
ActivateResult, GuestMemoryMmap, GuestRegionMmap, VIRTIO_F_IOMMU_PLATFORM, VirtioCommon,
|
||||
VirtioDevice, VirtioDeviceType, VirtioInterrupt,
|
||||
};
|
||||
|
||||
const DEFAULT_QUEUE_NUMBER: usize = 4;
|
||||
const DEFAULT_QUEUE_SIZE: u16 = 256;
|
||||
|
||||
/// Minimal backend request handler — Sound has no shared memory requirements.
|
||||
struct SoundBackendReqHandler;
|
||||
impl VhostUserFrontendReqHandler for SoundBackendReqHandler {}
|
||||
|
||||
pub struct Sound {
|
||||
common: VirtioCommon,
|
||||
vu_common: VhostUserCommon,
|
||||
id: String,
|
||||
seccomp_action: SeccompAction,
|
||||
guest_memory: Option<GuestMemoryAtomic<GuestMemoryMmap>>,
|
||||
epoll_thread: Option<thread::JoinHandle<()>>,
|
||||
exit_evt: EventFd,
|
||||
iommu: bool,
|
||||
}
|
||||
|
||||
impl Sound {
|
||||
/// Create a new vhost-user sound device.
|
||||
pub fn new(
|
||||
id: String,
|
||||
path: &str,
|
||||
seccomp_action: SeccompAction,
|
||||
exit_evt: EventFd,
|
||||
iommu: bool,
|
||||
) -> Result<Sound> {
|
||||
let num_queues = DEFAULT_QUEUE_NUMBER;
|
||||
|
||||
let mut vu =
|
||||
VhostUserHandle::connect_vhost_user(false, path, num_queues as u64, false)?;
|
||||
|
||||
let avail_features = DEFAULT_VIRTIO_FEATURES;
|
||||
|
||||
let avail_protocol_features = VhostUserProtocolFeatures::CONFIG
|
||||
| VhostUserProtocolFeatures::MQ
|
||||
| VhostUserProtocolFeatures::REPLY_ACK
|
||||
| VhostUserProtocolFeatures::CONFIGURE_MEM_SLOTS;
|
||||
|
||||
let (acked_features, acked_protocol_features) =
|
||||
vu.negotiate_features_vhost_user(avail_features, avail_protocol_features)?;
|
||||
|
||||
Ok(Sound {
|
||||
common: VirtioCommon {
|
||||
device_type: VirtioDeviceType::Sound as u32,
|
||||
avail_features: acked_features,
|
||||
acked_features: acked_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits(),
|
||||
queue_sizes: vec![DEFAULT_QUEUE_SIZE; num_queues],
|
||||
paused_sync: Some(Arc::new(Barrier::new(2))),
|
||||
min_queues: DEFAULT_QUEUE_NUMBER as u16,
|
||||
paused: Arc::new(AtomicBool::new(false)),
|
||||
..Default::default()
|
||||
},
|
||||
vu_common: VhostUserCommon {
|
||||
vu: Some(Arc::new(Mutex::new(vu))),
|
||||
acked_protocol_features,
|
||||
socket_path: path.to_string(),
|
||||
vu_num_queues: num_queues,
|
||||
..Default::default()
|
||||
},
|
||||
id,
|
||||
seccomp_action,
|
||||
guest_memory: None,
|
||||
epoll_thread: None,
|
||||
exit_evt,
|
||||
iommu,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Sound {
|
||||
fn drop(&mut self) {
|
||||
if let Some(kill_evt) = self.common.kill_evt.take() {
|
||||
let _ = kill_evt.write(1);
|
||||
}
|
||||
self.common.wait_for_epoll_threads();
|
||||
if let Some(thread) = self.epoll_thread.take()
|
||||
&& let Err(e) = thread.join()
|
||||
{
|
||||
error!("Error joining thread: {e:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VirtioDevice for Sound {
|
||||
fn device_type(&self) -> u32 {
|
||||
self.common.device_type
|
||||
}
|
||||
|
||||
fn queue_max_sizes(&self) -> &[u16] {
|
||||
&self.common.queue_sizes
|
||||
}
|
||||
|
||||
fn features(&self) -> u64 {
|
||||
let mut features = self.common.avail_features;
|
||||
if self.iommu {
|
||||
features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM;
|
||||
}
|
||||
features
|
||||
}
|
||||
|
||||
fn ack_features(&mut self, value: u64) {
|
||||
self.common.ack_features(value);
|
||||
}
|
||||
|
||||
fn read_config(&self, offset: u64, mut data: &mut [u8]) {
|
||||
if let Some(vu) = &self.vu_common.vu
|
||||
&& let Err(e) = vu
|
||||
.lock()
|
||||
.unwrap()
|
||||
.socket_handle()
|
||||
.get_config(
|
||||
offset as u32,
|
||||
data.len() as u32,
|
||||
VhostUserConfigFlags::WRITABLE,
|
||||
data,
|
||||
)
|
||||
.map_err(|e| format!("{e:?}"))
|
||||
.and_then(|(_, config)| {
|
||||
use std::io::Write;
|
||||
data.write_all(&config).map_err(|e| format!("{e:?}"))
|
||||
})
|
||||
{
|
||||
error!("Failed getting vhost-user-sound configuration: {e:?}");
|
||||
}
|
||||
}
|
||||
|
||||
fn activate(
|
||||
&mut self,
|
||||
mem: GuestMemoryAtomic<GuestMemoryMmap>,
|
||||
interrupt_cb: Arc<dyn VirtioInterrupt>,
|
||||
queues: Vec<(usize, Queue, EventFd)>,
|
||||
) -> ActivateResult {
|
||||
self.common.activate(&queues, interrupt_cb.clone())?;
|
||||
self.guest_memory = Some(mem.clone());
|
||||
|
||||
let (kill_evt, pause_evt) = self.common.dup_eventfds();
|
||||
|
||||
let mut handler = self.vu_common.activate(
|
||||
mem,
|
||||
&queues,
|
||||
interrupt_cb,
|
||||
self.common.acked_features,
|
||||
None::<FrontendReqHandler<SoundBackendReqHandler>>,
|
||||
kill_evt,
|
||||
pause_evt,
|
||||
)?;
|
||||
|
||||
let paused = self.common.paused.clone();
|
||||
let paused_sync = self.common.paused_sync.clone();
|
||||
|
||||
let mut epoll_threads = Vec::new();
|
||||
spawn_virtio_thread(
|
||||
&self.id,
|
||||
&self.seccomp_action,
|
||||
Thread::VirtioVhostSound,
|
||||
&mut epoll_threads,
|
||||
&self.exit_evt,
|
||||
move || {
|
||||
let param = libc::sched_param { sched_priority: 88 };
|
||||
// SAFETY: pthread_self() always returns a valid handle for the
|
||||
// calling thread, and param is initialized above.
|
||||
let ret = unsafe {
|
||||
libc::pthread_setschedparam(libc::pthread_self(), libc::SCHED_FIFO, ¶m)
|
||||
};
|
||||
if ret != 0 {
|
||||
warn!(
|
||||
"Failed to set SCHED_FIFO for sound thread: {}",
|
||||
io::Error::from_raw_os_error(ret)
|
||||
);
|
||||
}
|
||||
handler.run(&paused, paused_sync.as_ref().unwrap())
|
||||
},
|
||||
)?;
|
||||
self.epoll_thread = Some(epoll_threads.remove(0));
|
||||
|
||||
event!("virtio-device", "activated", "id", &self.id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reset(&mut self) -> Option<Arc<dyn VirtioInterrupt>> {
|
||||
if self.common.pause_evt.take().is_some() {
|
||||
self.common.resume().ok()?;
|
||||
}
|
||||
|
||||
if let Some(vu) = &self.vu_common.vu
|
||||
&& let Err(e) = vu.lock().unwrap().reset_vhost_user()
|
||||
{
|
||||
error!("Failed to reset vhost-user daemon: {e:?}");
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some(kill_evt) = self.common.kill_evt.take() {
|
||||
let _ = kill_evt.write(1);
|
||||
}
|
||||
|
||||
event!("virtio-device", "reset", "id", &self.id);
|
||||
|
||||
Some(self.common.interrupt_cb.take().unwrap())
|
||||
}
|
||||
|
||||
fn shutdown(&mut self) {
|
||||
self.vu_common.shutdown();
|
||||
}
|
||||
|
||||
fn add_memory_region(
|
||||
&mut self,
|
||||
region: &Arc<GuestRegionMmap>,
|
||||
) -> std::result::Result<(), crate::Error> {
|
||||
self.vu_common.add_memory_region(&self.guest_memory, region)
|
||||
}
|
||||
}
|
||||
|
||||
impl Pausable for Sound {
|
||||
fn pause(&mut self) -> result::Result<(), MigratableError> {
|
||||
self.vu_common.pause()?;
|
||||
self.common.pause()
|
||||
}
|
||||
|
||||
fn resume(&mut self) -> result::Result<(), MigratableError> {
|
||||
self.common.resume()?;
|
||||
|
||||
if let Some(epoll_thread) = &self.epoll_thread {
|
||||
epoll_thread.thread().unpark();
|
||||
}
|
||||
|
||||
self.vu_common.resume()
|
||||
}
|
||||
}
|
||||
|
||||
impl Snapshottable for Sound {
|
||||
fn id(&self) -> String {
|
||||
self.id.clone()
|
||||
}
|
||||
}
|
||||
impl Transportable for Sound {}
|
||||
impl Migratable for Sound {}
|
||||
|
|
@ -37,6 +37,7 @@ pub enum VirtioDeviceType {
|
|||
Vsock = 19,
|
||||
Iommu = 23,
|
||||
Mem = 24,
|
||||
Sound = 25,
|
||||
Fs = 26,
|
||||
Pmem = 27,
|
||||
Watchdog = 35, // Temporary until official number allocated
|
||||
|
|
@ -57,6 +58,7 @@ impl From<u32> for VirtioDeviceType {
|
|||
19 => VirtioDeviceType::Vsock,
|
||||
23 => VirtioDeviceType::Iommu,
|
||||
24 => VirtioDeviceType::Mem,
|
||||
25 => VirtioDeviceType::Sound,
|
||||
26 => VirtioDeviceType::Fs,
|
||||
27 => VirtioDeviceType::Pmem,
|
||||
35 => VirtioDeviceType::Watchdog,
|
||||
|
|
@ -82,6 +84,7 @@ impl fmt::Display for VirtioDeviceType {
|
|||
VirtioDeviceType::Vsock => "vsock",
|
||||
VirtioDeviceType::Iommu => "iommu",
|
||||
VirtioDeviceType::Mem => "mem",
|
||||
VirtioDeviceType::Sound => "sound",
|
||||
VirtioDeviceType::Fs => "fs",
|
||||
VirtioDeviceType::Pmem => "pmem",
|
||||
VirtioDeviceType::Watchdog => "watchdog",
|
||||
|
|
|
|||
|
|
@ -619,6 +619,10 @@ components:
|
|||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/FsConfig"
|
||||
sound:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/SoundConfig"
|
||||
pmem:
|
||||
type: array
|
||||
items:
|
||||
|
|
@ -1135,6 +1139,19 @@ components:
|
|||
id:
|
||||
type: string
|
||||
|
||||
SoundConfig:
|
||||
required:
|
||||
- socket
|
||||
type: object
|
||||
properties:
|
||||
socket:
|
||||
type: string
|
||||
pci_segment:
|
||||
type: integer
|
||||
format: int16
|
||||
id:
|
||||
type: string
|
||||
|
||||
PmemConfig:
|
||||
required:
|
||||
- file
|
||||
|
|
|
|||
|
|
@ -96,6 +96,12 @@ pub enum Error {
|
|||
/// Error parsing GPU parameters
|
||||
#[error("Error parsing --gpu")]
|
||||
ParseGpu(#[source] OptionParserError),
|
||||
/// Sound socket is missing
|
||||
#[error("Error parsing --sound: socket missing")]
|
||||
ParseSoundSockMissing,
|
||||
/// Error parsing Sound parameters
|
||||
#[error("Error parsing --sound")]
|
||||
ParseSound(#[source] OptionParserError),
|
||||
/// Error parsing persistent memory parameters
|
||||
#[error("Error parsing --pmem")]
|
||||
ParsePersistentMemory(#[source] OptionParserError),
|
||||
|
|
@ -400,6 +406,7 @@ pub struct VmParams<'a> {
|
|||
pub balloon: Option<&'a str>,
|
||||
pub fs: Option<Vec<&'a str>>,
|
||||
pub gpu: Option<Vec<&'a str>>,
|
||||
pub sound: Option<Vec<&'a str>>,
|
||||
pub pmem: Option<Vec<&'a str>>,
|
||||
pub serial: &'a str,
|
||||
pub console: &'a str,
|
||||
|
|
@ -464,6 +471,9 @@ impl<'a> VmParams<'a> {
|
|||
let gpu: Option<Vec<&str>> = args
|
||||
.get_many::<String>("gpu")
|
||||
.map(|x| x.map(|y| y as &str).collect());
|
||||
let sound: Option<Vec<&str>> = args
|
||||
.get_many::<String>("sound")
|
||||
.map(|x| x.map(|y| y as &str).collect());
|
||||
let pmem: Option<Vec<&str>> = args
|
||||
.get_many::<String>("pmem")
|
||||
.map(|x| x.map(|y| y as &str).collect());
|
||||
|
|
@ -519,6 +529,7 @@ impl<'a> VmParams<'a> {
|
|||
balloon,
|
||||
fs,
|
||||
gpu,
|
||||
sound,
|
||||
pmem,
|
||||
serial,
|
||||
console,
|
||||
|
|
@ -1755,6 +1766,49 @@ impl GpuConfig {
|
|||
}
|
||||
}
|
||||
|
||||
impl SoundConfig {
|
||||
pub const SYNTAX: &'static str = "virtio-sound parameters \
|
||||
\"socket=<socket_path>,id=<device_id>,pci_segment=<segment_id>\"";
|
||||
|
||||
pub fn parse(sound: &str) -> Result<Self> {
|
||||
let mut parser = OptionParser::new();
|
||||
parser.add("socket").add("id").add("pci_segment");
|
||||
parser.parse(sound).map_err(Error::ParseSound)?;
|
||||
|
||||
let socket = PathBuf::from(parser.get("socket").ok_or(Error::ParseSoundSockMissing)?);
|
||||
let id = parser.get("id");
|
||||
|
||||
let pci_segment = parser
|
||||
.convert("pci_segment")
|
||||
.map_err(Error::ParseSound)?
|
||||
.unwrap_or_default();
|
||||
|
||||
Ok(SoundConfig {
|
||||
socket,
|
||||
id,
|
||||
pci_segment,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> {
|
||||
if let Some(platform_config) = vm_config.platform.as_ref() {
|
||||
if self.pci_segment >= platform_config.num_pci_segments {
|
||||
return Err(ValidationError::InvalidPciSegment(self.pci_segment));
|
||||
}
|
||||
|
||||
if let Some(iommu_segments) = platform_config.iommu_segments.as_ref()
|
||||
&& iommu_segments.contains(&self.pci_segment)
|
||||
{
|
||||
return Err(ValidationError::IommuNotSupportedOnSegment(
|
||||
self.pci_segment,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "fw_cfg")]
|
||||
impl FwCfgConfig {
|
||||
pub const SYNTAX: &'static str = "Boot params to pass to FW CFG device \
|
||||
|
|
@ -2784,6 +2838,13 @@ impl VmConfig {
|
|||
}
|
||||
}
|
||||
|
||||
if let Some(sounds) = &self.sound {
|
||||
for sound in sounds {
|
||||
sound.validate(self)?;
|
||||
Self::validate_identifier(&mut id_list, &sound.id)?;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(pmems) = &self.pmem {
|
||||
for pmem in pmems {
|
||||
pmem.validate(self)?;
|
||||
|
|
@ -3046,6 +3107,15 @@ impl VmConfig {
|
|||
gpu = Some(gpu_config_list);
|
||||
}
|
||||
|
||||
let mut sound: Option<Vec<SoundConfig>> = None;
|
||||
if let Some(sound_list) = &vm_params.sound {
|
||||
let mut sound_config_list = Vec::new();
|
||||
for item in sound_list.iter() {
|
||||
sound_config_list.push(SoundConfig::parse(item)?);
|
||||
}
|
||||
sound = Some(sound_config_list);
|
||||
}
|
||||
|
||||
let mut pmem: Option<Vec<PmemConfig>> = None;
|
||||
if let Some(pmem_list) = &vm_params.pmem {
|
||||
let mut pmem_config_list = Vec::new();
|
||||
|
|
@ -3183,6 +3253,7 @@ impl VmConfig {
|
|||
balloon,
|
||||
fs,
|
||||
gpu,
|
||||
sound,
|
||||
pmem,
|
||||
serial,
|
||||
console,
|
||||
|
|
@ -3251,6 +3322,13 @@ impl VmConfig {
|
|||
removed |= gpu_list.len() != len;
|
||||
}
|
||||
|
||||
// Remove if sound device
|
||||
if let Some(sound_list) = self.sound.as_mut() {
|
||||
let len = sound_list.len();
|
||||
sound_list.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id));
|
||||
removed |= sound_list.len() != len;
|
||||
}
|
||||
|
||||
// Remove if net device
|
||||
if let Some(net) = self.net.as_mut() {
|
||||
let len = net.len();
|
||||
|
|
@ -3324,6 +3402,7 @@ impl Clone for VmConfig {
|
|||
pvmemcontrol: self.pvmemcontrol.clone(),
|
||||
fs: self.fs.clone(),
|
||||
gpu: self.gpu.clone(),
|
||||
sound: self.sound.clone(),
|
||||
pmem: self.pmem.clone(),
|
||||
serial: self.serial.clone(),
|
||||
console: self.console.clone(),
|
||||
|
|
@ -4233,6 +4312,7 @@ mod unit_tests {
|
|||
balloon: None,
|
||||
fs: None,
|
||||
gpu: None,
|
||||
sound: None,
|
||||
pmem: None,
|
||||
serial: default_serial(),
|
||||
console: default_console(),
|
||||
|
|
@ -4437,6 +4517,7 @@ mod unit_tests {
|
|||
balloon: None,
|
||||
fs: None,
|
||||
gpu: None,
|
||||
sound: None,
|
||||
pmem: None,
|
||||
serial: ConsoleConfig {
|
||||
file: None,
|
||||
|
|
|
|||
|
|
@ -128,8 +128,8 @@ use crate::serial_manager::{Error as SerialManagerError, SerialManager};
|
|||
use crate::vm_config::IvshmemConfig;
|
||||
use crate::vm_config::{
|
||||
ConsoleOutputMode, DEFAULT_IOMMU_ADDRESS_WIDTH_BITS, DEFAULT_PCI_SEGMENT_APERTURE_WEIGHT,
|
||||
DeviceConfig, DiskConfig, FsConfig, GpuConfig, NetConfig, PmemConfig, UserDeviceConfig,
|
||||
VdpaConfig, VhostMode, VmConfig, VsockConfig,
|
||||
DeviceConfig, DiskConfig, FsConfig, GpuConfig, NetConfig, PmemConfig, SoundConfig,
|
||||
UserDeviceConfig, VdpaConfig, VhostMode, VmConfig, VsockConfig,
|
||||
};
|
||||
use crate::{DEVICE_MANAGER_SNAPSHOT_ID, GuestRegionMmap, PciDeviceInfo, device_node};
|
||||
|
||||
|
|
@ -159,6 +159,7 @@ const IVSHMEM_DEVICE_NAME: &str = "__ivshmem";
|
|||
const DISK_DEVICE_NAME_PREFIX: &str = "_disk";
|
||||
const FS_DEVICE_NAME_PREFIX: &str = "_fs";
|
||||
const GPU_DEVICE_NAME_PREFIX: &str = "_gpu";
|
||||
const SOUND_DEVICE_NAME_PREFIX: &str = "_sound";
|
||||
const NET_DEVICE_NAME_PREFIX: &str = "_net";
|
||||
const PMEM_DEVICE_NAME_PREFIX: &str = "_pmem";
|
||||
const VDPA_DEVICE_NAME_PREFIX: &str = "_vdpa";
|
||||
|
|
@ -219,6 +220,14 @@ pub enum DeviceManagerError {
|
|||
#[error("Cannot find a memory range for virtio-gpu")]
|
||||
GpuRangeAllocation,
|
||||
|
||||
/// Cannot create virtio-sound device
|
||||
#[error("Cannot create virtio-sound device")]
|
||||
CreateVirtioSound(#[source] virtio_devices::vhost_user::Error),
|
||||
|
||||
/// Virtio-sound device was created without a socket.
|
||||
#[error("Virtio-sound device was created without a socket")]
|
||||
NoVirtioSoundSock,
|
||||
|
||||
/// Cannot create vhost-user-blk device
|
||||
#[error("Cannot create vhost-user-blk device")]
|
||||
CreateVhostUserBlk(#[source] virtio_devices::vhost_user::Error),
|
||||
|
|
@ -2570,6 +2579,9 @@ impl DeviceManager {
|
|||
// Add virtio-gpu if required
|
||||
self.make_virtio_gpu_devices()?;
|
||||
|
||||
// Add virtio-sound if required
|
||||
self.make_virtio_sound_devices()?;
|
||||
|
||||
// Add virtio-pmem if required
|
||||
self.make_virtio_pmem_devices()?;
|
||||
|
||||
|
|
@ -3280,6 +3292,64 @@ impl DeviceManager {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn make_virtio_sound_device(
|
||||
&mut self,
|
||||
sound_cfg: &mut SoundConfig,
|
||||
) -> DeviceManagerResult<MetaVirtioDevice> {
|
||||
let id = if let Some(id) = &sound_cfg.id {
|
||||
id.clone()
|
||||
} else {
|
||||
let id = self.next_device_name(SOUND_DEVICE_NAME_PREFIX)?;
|
||||
sound_cfg.id = Some(id.clone());
|
||||
id
|
||||
};
|
||||
|
||||
info!("Creating virtio-sound device: {sound_cfg:?}");
|
||||
|
||||
let node = device_node!(id);
|
||||
|
||||
if let Some(sound_socket) = sound_cfg.socket.to_str() {
|
||||
let virtio_sound_device = virtio_devices::vhost_user::Sound::new(
|
||||
id.clone(),
|
||||
sound_socket,
|
||||
self.seccomp_action.clone(),
|
||||
self.exit_evt
|
||||
.try_clone()
|
||||
.map_err(DeviceManagerError::EventFd)?,
|
||||
self.force_iommu,
|
||||
)
|
||||
.map_err(DeviceManagerError::CreateVirtioSound)?;
|
||||
|
||||
let virtio_sound_device = Arc::new(Mutex::new(virtio_sound_device));
|
||||
|
||||
self.device_tree.lock().unwrap().insert(id.clone(), node);
|
||||
|
||||
Ok(MetaVirtioDevice {
|
||||
virtio_device: Arc::clone(&virtio_sound_device)
|
||||
as Arc<Mutex<dyn virtio_devices::VirtioDevice>>,
|
||||
iommu: false,
|
||||
id,
|
||||
pci_segment: sound_cfg.pci_segment,
|
||||
dma_handler: None,
|
||||
})
|
||||
} else {
|
||||
Err(DeviceManagerError::NoVirtioSoundSock)
|
||||
}
|
||||
}
|
||||
|
||||
fn make_virtio_sound_devices(&mut self) -> DeviceManagerResult<()> {
|
||||
let mut sound_devices = self.config.lock().unwrap().sound.take();
|
||||
if let Some(sound_list_cfg) = &mut sound_devices {
|
||||
for sound_cfg in sound_list_cfg.iter_mut() {
|
||||
let device = self.make_virtio_sound_device(sound_cfg)?;
|
||||
self.virtio_devices.push(device);
|
||||
}
|
||||
}
|
||||
self.config.lock().unwrap().sound = sound_devices;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn make_virtio_pmem_device(
|
||||
&mut self,
|
||||
pmem_cfg: &mut PmemConfig,
|
||||
|
|
|
|||
|
|
@ -2480,6 +2480,7 @@ mod unit_tests {
|
|||
balloon: None,
|
||||
fs: None,
|
||||
gpu: None,
|
||||
sound: None,
|
||||
pmem: None,
|
||||
serial: ConsoleConfig {
|
||||
file: None,
|
||||
|
|
|
|||
|
|
@ -657,6 +657,7 @@ fn vmm_thread_rules(
|
|||
(libc::SYS_rt_sigreturn, vec![]),
|
||||
(libc::SYS_sched_getaffinity, vec![]),
|
||||
(libc::SYS_sched_setaffinity, vec![]),
|
||||
(libc::SYS_sched_setscheduler, vec![]),
|
||||
(libc::SYS_sched_yield, vec![]),
|
||||
(libc::SYS_seccomp, vec![]),
|
||||
(libc::SYS_sendmsg, vec![]),
|
||||
|
|
|
|||
|
|
@ -485,6 +485,22 @@ impl ApplyLandlock for GpuConfig {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub struct SoundConfig {
|
||||
pub socket: PathBuf,
|
||||
#[serde(default)]
|
||||
pub id: Option<String>,
|
||||
#[serde(default)]
|
||||
pub pci_segment: u16,
|
||||
}
|
||||
|
||||
impl ApplyLandlock for SoundConfig {
|
||||
fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> {
|
||||
landlock.add_rule_with_access(&self.socket, "rw")?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub struct PmemConfig {
|
||||
pub file: PathBuf,
|
||||
|
|
@ -939,6 +955,7 @@ pub struct VmConfig {
|
|||
pub balloon: Option<BalloonConfig>,
|
||||
pub fs: Option<Vec<FsConfig>>,
|
||||
pub gpu: Option<Vec<GpuConfig>>,
|
||||
pub sound: Option<Vec<SoundConfig>>,
|
||||
pub pmem: Option<Vec<PmemConfig>>,
|
||||
#[serde(default = "default_serial")]
|
||||
pub serial: ConsoleConfig,
|
||||
|
|
@ -1020,6 +1037,12 @@ impl VmConfig {
|
|||
}
|
||||
}
|
||||
|
||||
if let Some(sound_configs) = &self.sound {
|
||||
for sound_config in sound_configs.iter() {
|
||||
sound_config.apply_landlock(&mut landlock)?;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(pmem_configs) = &self.pmem {
|
||||
for pmem_config in pmem_configs.iter() {
|
||||
pmem_config.apply_landlock(&mut landlock)?;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue