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:
Davíð Steinn Geirsson 2026-03-22 10:05:30 +00:00
parent 480bfb7095
commit 6ce557ced8
13 changed files with 492 additions and 4 deletions

1
.gitignore vendored
View file

@ -8,3 +8,4 @@
/.vscode
/vendor
__pycache__
/.worktrees

View file

@ -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,

View file

@ -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")]

View file

@ -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(),

View file

@ -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)]

View 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, &param)
};
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 {}

View file

@ -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",

View file

@ -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

View 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,

View file

@ -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,

View file

@ -2480,6 +2480,7 @@ mod unit_tests {
balloon: None,
fs: None,
gpu: None,
sound: None,
pmem: None,
serial: ConsoleConfig {
file: None,

View file

@ -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![]),

View file

@ -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)?;