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
|
/.vscode
|
||||||
/vendor
|
/vendor
|
||||||
__pycache__
|
__pycache__
|
||||||
|
/.worktrees
|
||||||
|
|
|
||||||
|
|
@ -33,8 +33,8 @@ use vmm::vm_config::FwCfgConfig;
|
||||||
use vmm::vm_config::IvshmemConfig;
|
use vmm::vm_config::IvshmemConfig;
|
||||||
use vmm::vm_config::{
|
use vmm::vm_config::{
|
||||||
BalloonConfig, DeviceConfig, DiskConfig, FsConfig, LandlockConfig, NetConfig, NumaConfig,
|
BalloonConfig, DeviceConfig, DiskConfig, FsConfig, LandlockConfig, NetConfig, NumaConfig,
|
||||||
PciSegmentConfig, PmemConfig, RateLimiterGroupConfig, TpmConfig, UserDeviceConfig, VdpaConfig,
|
PciSegmentConfig, PmemConfig, RateLimiterGroupConfig, SoundConfig, TpmConfig, UserDeviceConfig,
|
||||||
VmConfig, VsockConfig,
|
VdpaConfig, VmConfig, VsockConfig,
|
||||||
};
|
};
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
use vmm_sys_util::signal::block_signal;
|
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>")
|
.help("Control serial port: off|null|pty|tty|file=</path/to/a/file>|socket=</path/to/a/file>")
|
||||||
.default_value("null")
|
.default_value("null")
|
||||||
.group("vm-config"),
|
.group("vm-config"),
|
||||||
|
Arg::new("sound")
|
||||||
|
.long("sound")
|
||||||
|
.help(SoundConfig::SYNTAX)
|
||||||
|
.num_args(1..)
|
||||||
|
.group("vm-config"),
|
||||||
Arg::new("tpm")
|
Arg::new("tpm")
|
||||||
.long("tpm")
|
.long("tpm")
|
||||||
.num_args(1)
|
.num_args(1)
|
||||||
|
|
@ -999,6 +1004,7 @@ mod unit_tests {
|
||||||
balloon: None,
|
balloon: None,
|
||||||
fs: None,
|
fs: None,
|
||||||
gpu: None,
|
gpu: None,
|
||||||
|
sound: None,
|
||||||
pmem: None,
|
pmem: None,
|
||||||
serial: ConsoleConfig {
|
serial: ConsoleConfig {
|
||||||
file: None,
|
file: None,
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,8 @@ pub enum ActivateError {
|
||||||
VhostUserSetup(#[source] vhost_user::Error),
|
VhostUserSetup(#[source] vhost_user::Error),
|
||||||
#[error("Failed to setup vhost-user-gpu daemon")]
|
#[error("Failed to setup vhost-user-gpu daemon")]
|
||||||
VhostUserGpuSetup(#[source] vhost_user::Error),
|
VhostUserGpuSetup(#[source] vhost_user::Error),
|
||||||
|
#[error("Failed to setup vhost-user-sound daemon")]
|
||||||
|
VhostUserSoundSetup(#[source] vhost_user::Error),
|
||||||
#[error("Failed to create seccomp filter")]
|
#[error("Failed to create seccomp filter")]
|
||||||
CreateSeccompFilter(#[source] seccompiler::Error),
|
CreateSeccompFilter(#[source] seccompiler::Error),
|
||||||
#[error("Failed to create rate limiter")]
|
#[error("Failed to create rate limiter")]
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ pub enum Thread {
|
||||||
VirtioVhostBlock,
|
VirtioVhostBlock,
|
||||||
VirtioVhostFs,
|
VirtioVhostFs,
|
||||||
VirtioVhostGpu,
|
VirtioVhostGpu,
|
||||||
|
VirtioVhostSound,
|
||||||
VirtioVhostNet,
|
VirtioVhostNet,
|
||||||
VirtioVhostNetCtl,
|
VirtioVhostNetCtl,
|
||||||
VirtioVsock,
|
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>)> {
|
fn virtio_vhost_net_ctl_thread_rules() -> Vec<(i64, Vec<SeccompRule>)> {
|
||||||
vec![]
|
vec![]
|
||||||
}
|
}
|
||||||
|
|
@ -284,6 +297,7 @@ fn get_seccomp_rules(thread_type: Thread) -> Vec<(i64, Vec<SeccompRule>)> {
|
||||||
Thread::VirtioVhostBlock => virtio_vhost_block_thread_rules(),
|
Thread::VirtioVhostBlock => virtio_vhost_block_thread_rules(),
|
||||||
Thread::VirtioVhostFs => virtio_vhost_fs_thread_rules(),
|
Thread::VirtioVhostFs => virtio_vhost_fs_thread_rules(),
|
||||||
Thread::VirtioVhostGpu => virtio_vhost_gpu_thread_rules(),
|
Thread::VirtioVhostGpu => virtio_vhost_gpu_thread_rules(),
|
||||||
|
Thread::VirtioVhostSound => virtio_vhost_sound_thread_rules(),
|
||||||
Thread::VirtioVhostNet => virtio_vhost_net_thread_rules(),
|
Thread::VirtioVhostNet => virtio_vhost_net_thread_rules(),
|
||||||
Thread::VirtioVhostNetCtl => virtio_vhost_net_ctl_thread_rules(),
|
Thread::VirtioVhostNetCtl => virtio_vhost_net_ctl_thread_rules(),
|
||||||
Thread::VirtioVsock => virtio_vsock_thread_rules(),
|
Thread::VirtioVsock => virtio_vsock_thread_rules(),
|
||||||
|
|
|
||||||
|
|
@ -37,12 +37,14 @@ pub mod blk;
|
||||||
pub mod fs;
|
pub mod fs;
|
||||||
pub mod gpu;
|
pub mod gpu;
|
||||||
pub mod net;
|
pub mod net;
|
||||||
|
pub mod sound;
|
||||||
pub mod vu_common_ctrl;
|
pub mod vu_common_ctrl;
|
||||||
|
|
||||||
pub use self::blk::Blk;
|
pub use self::blk::Blk;
|
||||||
pub use self::fs::*;
|
pub use self::fs::*;
|
||||||
pub use self::gpu::Gpu;
|
pub use self::gpu::Gpu;
|
||||||
pub use self::net::Net;
|
pub use self::net::Net;
|
||||||
|
pub use self::sound::Sound;
|
||||||
pub use self::vu_common_ctrl::VhostUserConfig;
|
pub use self::vu_common_ctrl::VhostUserConfig;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[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,
|
Vsock = 19,
|
||||||
Iommu = 23,
|
Iommu = 23,
|
||||||
Mem = 24,
|
Mem = 24,
|
||||||
|
Sound = 25,
|
||||||
Fs = 26,
|
Fs = 26,
|
||||||
Pmem = 27,
|
Pmem = 27,
|
||||||
Watchdog = 35, // Temporary until official number allocated
|
Watchdog = 35, // Temporary until official number allocated
|
||||||
|
|
@ -57,6 +58,7 @@ impl From<u32> for VirtioDeviceType {
|
||||||
19 => VirtioDeviceType::Vsock,
|
19 => VirtioDeviceType::Vsock,
|
||||||
23 => VirtioDeviceType::Iommu,
|
23 => VirtioDeviceType::Iommu,
|
||||||
24 => VirtioDeviceType::Mem,
|
24 => VirtioDeviceType::Mem,
|
||||||
|
25 => VirtioDeviceType::Sound,
|
||||||
26 => VirtioDeviceType::Fs,
|
26 => VirtioDeviceType::Fs,
|
||||||
27 => VirtioDeviceType::Pmem,
|
27 => VirtioDeviceType::Pmem,
|
||||||
35 => VirtioDeviceType::Watchdog,
|
35 => VirtioDeviceType::Watchdog,
|
||||||
|
|
@ -82,6 +84,7 @@ impl fmt::Display for VirtioDeviceType {
|
||||||
VirtioDeviceType::Vsock => "vsock",
|
VirtioDeviceType::Vsock => "vsock",
|
||||||
VirtioDeviceType::Iommu => "iommu",
|
VirtioDeviceType::Iommu => "iommu",
|
||||||
VirtioDeviceType::Mem => "mem",
|
VirtioDeviceType::Mem => "mem",
|
||||||
|
VirtioDeviceType::Sound => "sound",
|
||||||
VirtioDeviceType::Fs => "fs",
|
VirtioDeviceType::Fs => "fs",
|
||||||
VirtioDeviceType::Pmem => "pmem",
|
VirtioDeviceType::Pmem => "pmem",
|
||||||
VirtioDeviceType::Watchdog => "watchdog",
|
VirtioDeviceType::Watchdog => "watchdog",
|
||||||
|
|
|
||||||
|
|
@ -619,6 +619,10 @@ components:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: "#/components/schemas/FsConfig"
|
$ref: "#/components/schemas/FsConfig"
|
||||||
|
sound:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/SoundConfig"
|
||||||
pmem:
|
pmem:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
|
|
@ -1135,6 +1139,19 @@ components:
|
||||||
id:
|
id:
|
||||||
type: string
|
type: string
|
||||||
|
|
||||||
|
SoundConfig:
|
||||||
|
required:
|
||||||
|
- socket
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
socket:
|
||||||
|
type: string
|
||||||
|
pci_segment:
|
||||||
|
type: integer
|
||||||
|
format: int16
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
|
||||||
PmemConfig:
|
PmemConfig:
|
||||||
required:
|
required:
|
||||||
- file
|
- file
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,12 @@ pub enum Error {
|
||||||
/// Error parsing GPU parameters
|
/// Error parsing GPU parameters
|
||||||
#[error("Error parsing --gpu")]
|
#[error("Error parsing --gpu")]
|
||||||
ParseGpu(#[source] OptionParserError),
|
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 parsing persistent memory parameters
|
||||||
#[error("Error parsing --pmem")]
|
#[error("Error parsing --pmem")]
|
||||||
ParsePersistentMemory(#[source] OptionParserError),
|
ParsePersistentMemory(#[source] OptionParserError),
|
||||||
|
|
@ -400,6 +406,7 @@ pub struct VmParams<'a> {
|
||||||
pub balloon: Option<&'a str>,
|
pub balloon: Option<&'a str>,
|
||||||
pub fs: Option<Vec<&'a str>>,
|
pub fs: Option<Vec<&'a str>>,
|
||||||
pub gpu: Option<Vec<&'a str>>,
|
pub gpu: Option<Vec<&'a str>>,
|
||||||
|
pub sound: Option<Vec<&'a str>>,
|
||||||
pub pmem: Option<Vec<&'a str>>,
|
pub pmem: Option<Vec<&'a str>>,
|
||||||
pub serial: &'a str,
|
pub serial: &'a str,
|
||||||
pub console: &'a str,
|
pub console: &'a str,
|
||||||
|
|
@ -464,6 +471,9 @@ impl<'a> VmParams<'a> {
|
||||||
let gpu: Option<Vec<&str>> = args
|
let gpu: Option<Vec<&str>> = args
|
||||||
.get_many::<String>("gpu")
|
.get_many::<String>("gpu")
|
||||||
.map(|x| x.map(|y| y as &str).collect());
|
.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
|
let pmem: Option<Vec<&str>> = args
|
||||||
.get_many::<String>("pmem")
|
.get_many::<String>("pmem")
|
||||||
.map(|x| x.map(|y| y as &str).collect());
|
.map(|x| x.map(|y| y as &str).collect());
|
||||||
|
|
@ -519,6 +529,7 @@ impl<'a> VmParams<'a> {
|
||||||
balloon,
|
balloon,
|
||||||
fs,
|
fs,
|
||||||
gpu,
|
gpu,
|
||||||
|
sound,
|
||||||
pmem,
|
pmem,
|
||||||
serial,
|
serial,
|
||||||
console,
|
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")]
|
#[cfg(feature = "fw_cfg")]
|
||||||
impl FwCfgConfig {
|
impl FwCfgConfig {
|
||||||
pub const SYNTAX: &'static str = "Boot params to pass to FW CFG device \
|
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 {
|
if let Some(pmems) = &self.pmem {
|
||||||
for pmem in pmems {
|
for pmem in pmems {
|
||||||
pmem.validate(self)?;
|
pmem.validate(self)?;
|
||||||
|
|
@ -3046,6 +3107,15 @@ impl VmConfig {
|
||||||
gpu = Some(gpu_config_list);
|
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;
|
let mut pmem: Option<Vec<PmemConfig>> = None;
|
||||||
if let Some(pmem_list) = &vm_params.pmem {
|
if let Some(pmem_list) = &vm_params.pmem {
|
||||||
let mut pmem_config_list = Vec::new();
|
let mut pmem_config_list = Vec::new();
|
||||||
|
|
@ -3183,6 +3253,7 @@ impl VmConfig {
|
||||||
balloon,
|
balloon,
|
||||||
fs,
|
fs,
|
||||||
gpu,
|
gpu,
|
||||||
|
sound,
|
||||||
pmem,
|
pmem,
|
||||||
serial,
|
serial,
|
||||||
console,
|
console,
|
||||||
|
|
@ -3251,6 +3322,13 @@ impl VmConfig {
|
||||||
removed |= gpu_list.len() != len;
|
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
|
// Remove if net device
|
||||||
if let Some(net) = self.net.as_mut() {
|
if let Some(net) = self.net.as_mut() {
|
||||||
let len = net.len();
|
let len = net.len();
|
||||||
|
|
@ -3324,6 +3402,7 @@ impl Clone for VmConfig {
|
||||||
pvmemcontrol: self.pvmemcontrol.clone(),
|
pvmemcontrol: self.pvmemcontrol.clone(),
|
||||||
fs: self.fs.clone(),
|
fs: self.fs.clone(),
|
||||||
gpu: self.gpu.clone(),
|
gpu: self.gpu.clone(),
|
||||||
|
sound: self.sound.clone(),
|
||||||
pmem: self.pmem.clone(),
|
pmem: self.pmem.clone(),
|
||||||
serial: self.serial.clone(),
|
serial: self.serial.clone(),
|
||||||
console: self.console.clone(),
|
console: self.console.clone(),
|
||||||
|
|
@ -4233,6 +4312,7 @@ mod unit_tests {
|
||||||
balloon: None,
|
balloon: None,
|
||||||
fs: None,
|
fs: None,
|
||||||
gpu: None,
|
gpu: None,
|
||||||
|
sound: None,
|
||||||
pmem: None,
|
pmem: None,
|
||||||
serial: default_serial(),
|
serial: default_serial(),
|
||||||
console: default_console(),
|
console: default_console(),
|
||||||
|
|
@ -4437,6 +4517,7 @@ mod unit_tests {
|
||||||
balloon: None,
|
balloon: None,
|
||||||
fs: None,
|
fs: None,
|
||||||
gpu: None,
|
gpu: None,
|
||||||
|
sound: None,
|
||||||
pmem: None,
|
pmem: None,
|
||||||
serial: ConsoleConfig {
|
serial: ConsoleConfig {
|
||||||
file: None,
|
file: None,
|
||||||
|
|
|
||||||
|
|
@ -128,8 +128,8 @@ use crate::serial_manager::{Error as SerialManagerError, SerialManager};
|
||||||
use crate::vm_config::IvshmemConfig;
|
use crate::vm_config::IvshmemConfig;
|
||||||
use crate::vm_config::{
|
use crate::vm_config::{
|
||||||
ConsoleOutputMode, DEFAULT_IOMMU_ADDRESS_WIDTH_BITS, DEFAULT_PCI_SEGMENT_APERTURE_WEIGHT,
|
ConsoleOutputMode, DEFAULT_IOMMU_ADDRESS_WIDTH_BITS, DEFAULT_PCI_SEGMENT_APERTURE_WEIGHT,
|
||||||
DeviceConfig, DiskConfig, FsConfig, GpuConfig, NetConfig, PmemConfig, UserDeviceConfig,
|
DeviceConfig, DiskConfig, FsConfig, GpuConfig, NetConfig, PmemConfig, SoundConfig,
|
||||||
VdpaConfig, VhostMode, VmConfig, VsockConfig,
|
UserDeviceConfig, VdpaConfig, VhostMode, VmConfig, VsockConfig,
|
||||||
};
|
};
|
||||||
use crate::{DEVICE_MANAGER_SNAPSHOT_ID, GuestRegionMmap, PciDeviceInfo, device_node};
|
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 DISK_DEVICE_NAME_PREFIX: &str = "_disk";
|
||||||
const FS_DEVICE_NAME_PREFIX: &str = "_fs";
|
const FS_DEVICE_NAME_PREFIX: &str = "_fs";
|
||||||
const GPU_DEVICE_NAME_PREFIX: &str = "_gpu";
|
const GPU_DEVICE_NAME_PREFIX: &str = "_gpu";
|
||||||
|
const SOUND_DEVICE_NAME_PREFIX: &str = "_sound";
|
||||||
const NET_DEVICE_NAME_PREFIX: &str = "_net";
|
const NET_DEVICE_NAME_PREFIX: &str = "_net";
|
||||||
const PMEM_DEVICE_NAME_PREFIX: &str = "_pmem";
|
const PMEM_DEVICE_NAME_PREFIX: &str = "_pmem";
|
||||||
const VDPA_DEVICE_NAME_PREFIX: &str = "_vdpa";
|
const VDPA_DEVICE_NAME_PREFIX: &str = "_vdpa";
|
||||||
|
|
@ -219,6 +220,14 @@ pub enum DeviceManagerError {
|
||||||
#[error("Cannot find a memory range for virtio-gpu")]
|
#[error("Cannot find a memory range for virtio-gpu")]
|
||||||
GpuRangeAllocation,
|
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
|
/// Cannot create vhost-user-blk device
|
||||||
#[error("Cannot create vhost-user-blk device")]
|
#[error("Cannot create vhost-user-blk device")]
|
||||||
CreateVhostUserBlk(#[source] virtio_devices::vhost_user::Error),
|
CreateVhostUserBlk(#[source] virtio_devices::vhost_user::Error),
|
||||||
|
|
@ -2570,6 +2579,9 @@ impl DeviceManager {
|
||||||
// Add virtio-gpu if required
|
// Add virtio-gpu if required
|
||||||
self.make_virtio_gpu_devices()?;
|
self.make_virtio_gpu_devices()?;
|
||||||
|
|
||||||
|
// Add virtio-sound if required
|
||||||
|
self.make_virtio_sound_devices()?;
|
||||||
|
|
||||||
// Add virtio-pmem if required
|
// Add virtio-pmem if required
|
||||||
self.make_virtio_pmem_devices()?;
|
self.make_virtio_pmem_devices()?;
|
||||||
|
|
||||||
|
|
@ -3280,6 +3292,64 @@ impl DeviceManager {
|
||||||
Ok(())
|
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(
|
fn make_virtio_pmem_device(
|
||||||
&mut self,
|
&mut self,
|
||||||
pmem_cfg: &mut PmemConfig,
|
pmem_cfg: &mut PmemConfig,
|
||||||
|
|
|
||||||
|
|
@ -2480,6 +2480,7 @@ mod unit_tests {
|
||||||
balloon: None,
|
balloon: None,
|
||||||
fs: None,
|
fs: None,
|
||||||
gpu: None,
|
gpu: None,
|
||||||
|
sound: None,
|
||||||
pmem: None,
|
pmem: None,
|
||||||
serial: ConsoleConfig {
|
serial: ConsoleConfig {
|
||||||
file: None,
|
file: None,
|
||||||
|
|
|
||||||
|
|
@ -657,6 +657,7 @@ fn vmm_thread_rules(
|
||||||
(libc::SYS_rt_sigreturn, vec![]),
|
(libc::SYS_rt_sigreturn, vec![]),
|
||||||
(libc::SYS_sched_getaffinity, vec![]),
|
(libc::SYS_sched_getaffinity, vec![]),
|
||||||
(libc::SYS_sched_setaffinity, vec![]),
|
(libc::SYS_sched_setaffinity, vec![]),
|
||||||
|
(libc::SYS_sched_setscheduler, vec![]),
|
||||||
(libc::SYS_sched_yield, vec![]),
|
(libc::SYS_sched_yield, vec![]),
|
||||||
(libc::SYS_seccomp, vec![]),
|
(libc::SYS_seccomp, vec![]),
|
||||||
(libc::SYS_sendmsg, 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)]
|
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||||
pub struct PmemConfig {
|
pub struct PmemConfig {
|
||||||
pub file: PathBuf,
|
pub file: PathBuf,
|
||||||
|
|
@ -939,6 +955,7 @@ pub struct VmConfig {
|
||||||
pub balloon: Option<BalloonConfig>,
|
pub balloon: Option<BalloonConfig>,
|
||||||
pub fs: Option<Vec<FsConfig>>,
|
pub fs: Option<Vec<FsConfig>>,
|
||||||
pub gpu: Option<Vec<GpuConfig>>,
|
pub gpu: Option<Vec<GpuConfig>>,
|
||||||
|
pub sound: Option<Vec<SoundConfig>>,
|
||||||
pub pmem: Option<Vec<PmemConfig>>,
|
pub pmem: Option<Vec<PmemConfig>>,
|
||||||
#[serde(default = "default_serial")]
|
#[serde(default = "default_serial")]
|
||||||
pub serial: ConsoleConfig,
|
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 {
|
if let Some(pmem_configs) = &self.pmem {
|
||||||
for pmem_config in pmem_configs.iter() {
|
for pmem_config in pmem_configs.iter() {
|
||||||
pmem_config.apply_landlock(&mut landlock)?;
|
pmem_config.apply_landlock(&mut landlock)?;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue