Skip to content

Commit c3668f2

Browse files
author
Dorinda Bassey
committed
libkrun: Add vhost-user sound device support
Add support for vhost-user sound devices following QEMU's vhost-user-snd implementation. This allows libkrun VMs to use external vhost-user sound backends (e.g., vhost-device-sound) for audio playback and capture. The virtio-snd configuration space is read-only and contains device capabilities (jacks, streams, chmaps, controls). The implementation uses 4 queues as per the virtio-snd specification: control, event, TX/playback, and RX/capture. Note: The ALSA default device in the guest may not work without additional configuration. Use explicit device specification (e.g., 'aplay -D hw:0,0 <file>') or create /etc/asound.conf in the guest to set the default device. Tested with vhost-device-sound backend using PipeWire. Signed-off-by: Dorinda Bassey <dbassey@redhat.com>
1 parent 4e2342b commit c3668f2

3 files changed

Lines changed: 90 additions & 11 deletions

File tree

examples/chroot_vm.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ static void print_help(char *const name)
4040
" --net=NET_MODE Set network mode\n"
4141
" --passt-socket=PATH Instead of starting passt, connect to passt socket at PATH\n"
4242
" --vhost-user-rng=PATH Use vhost-user RNG backend at socket PATH\n"
43+
" --vhost-user-snd=PATH Use vhost-user sound backend at socket PATH\n"
4344
"NET_MODE can be either TSI (default) or PASST\n"
4445
"\n"
4546
"NEWROOT: the root directory of the vm\n"
@@ -56,6 +57,7 @@ static const struct option long_options[] = {
5657
{ "net_mode", required_argument, NULL, 'N' },
5758
{ "passt-socket", required_argument, NULL, 'P' },
5859
{ "vhost-user-rng", required_argument, NULL, 'V' },
60+
{ "vhost-user-snd", required_argument, NULL, 'S' },
5961
{ NULL, 0, NULL, 0 }
6062
};
6163

@@ -66,6 +68,7 @@ struct cmdline {
6668
enum net_mode net_mode;
6769
char const *passt_socket_path;
6870
char const *vhost_user_rng_socket;
71+
char const *vhost_user_snd_socket;
6972
char const *new_root;
7073
char *const *guest_argv;
7174
};
@@ -93,6 +96,7 @@ bool parse_cmdline(int argc, char *const argv[], struct cmdline *cmdline)
9396
.net_mode = NET_MODE_TSI,
9497
.passt_socket_path = NULL,
9598
.vhost_user_rng_socket = NULL,
99+
.vhost_user_snd_socket = NULL,
96100
.new_root = NULL,
97101
.guest_argv = NULL,
98102
.log_target = KRUN_LOG_TARGET_DEFAULT,
@@ -131,6 +135,9 @@ bool parse_cmdline(int argc, char *const argv[], struct cmdline *cmdline)
131135
case 'V':
132136
cmdline->vhost_user_rng_socket = optarg;
133137
break;
138+
case 'S':
139+
cmdline->vhost_user_snd_socket = optarg;
140+
break;
134141
case '?':
135142
return false;
136143
default:
@@ -270,6 +277,19 @@ int main(int argc, char *const argv[])
270277
printf("Using vhost-user RNG backend at %s (custom queue size: 512)\n", cmdline.vhost_user_rng_socket);
271278
}
272279

280+
// Configure vhost-user sound if requested
281+
if (cmdline.vhost_user_snd_socket != NULL) {
282+
if (err = krun_add_vhost_user_device(ctx_id, KRUN_VIRTIO_DEVICE_SND,
283+
cmdline.vhost_user_snd_socket, NULL,
284+
KRUN_VHOST_USER_SND_NUM_QUEUES,
285+
KRUN_VHOST_USER_SND_QUEUE_SIZES)) {
286+
errno = -err;
287+
perror("Error adding vhost-user sound device");
288+
return -1;
289+
}
290+
printf("Using vhost-user sound backend at %s\n", cmdline.vhost_user_snd_socket);
291+
}
292+
273293
// Raise RLIMIT_NOFILE to the maximum allowed to create some room for virtio-fs
274294
getrlimit(RLIMIT_NOFILE, &rlim);
275295
rlim.rlim_cur = rlim.rlim_max;

include/libkrun.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -732,6 +732,13 @@ int32_t krun_set_snd_device(uint32_t ctx_id, bool enable);
732732
#define KRUN_VHOST_USER_RNG_NUM_QUEUES 1
733733
#define KRUN_VHOST_USER_RNG_QUEUE_SIZES ((uint16_t[]){256})
734734

735+
/**
736+
* Vhost-user sound device default queue configuration.
737+
* Sound device uses 4 queues: control (idx 0), event (idx 1), TX/playback (idx 2), RX/capture (idx 3).
738+
*/
739+
#define KRUN_VHOST_USER_SND_NUM_QUEUES 4
740+
#define KRUN_VHOST_USER_SND_QUEUE_SIZES ((uint16_t[]){64, 64, 64, 64})
741+
735742
/**
736743
* Add a vhost-user device to the VM.
737744
*

src/devices/src/virtio/vhost_user/device.rs

Lines changed: 63 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use std::thread;
1313

1414
use log::{debug, error};
1515
use utils::eventfd::EventFd;
16+
use vhost::vhost_user::message::VhostUserConfigFlags;
1617
use vhost::vhost_user::{Frontend, VhostUserFrontend, VhostUserProtocolFeatures};
1718
use vhost::{VhostBackend, VhostUserMemoryRegionInfo, VringConfigData};
1819
use vm_memory::{Address, GuestMemory, GuestMemoryMmap, GuestMemoryRegion};
@@ -54,6 +55,12 @@ pub struct VhostUserDevice {
5455

5556
/// Device state
5657
device_state: DeviceState,
58+
59+
/// Cached device configuration space
60+
config_space: Arc<Mutex<Vec<u8>>>,
61+
62+
/// Device-specific configuration space size
63+
config_size: u32,
5764
}
5865

5966
impl VhostUserDevice {
@@ -138,14 +145,6 @@ impl VhostUserDevice {
138145
num_queues as usize
139146
};
140147

141-
debug!(
142-
"{}: using {} queues (requested: {}, sizes provided: {})",
143-
device_name,
144-
actual_num_queues,
145-
num_queues,
146-
queue_sizes.len()
147-
);
148-
149148
let default_size = queue_sizes.last().copied().unwrap_or(256);
150149
let queue_configs: Vec<_> = (0..actual_num_queues)
151150
.map(|i| {
@@ -154,6 +153,12 @@ impl VhostUserDevice {
154153
})
155154
.collect();
156155

156+
const VIRTIO_ID_SOUND: u32 = 25;
157+
let config_size = match device_type {
158+
VIRTIO_ID_SOUND => 16, // sizeof(struct virtio_snd_config)
159+
_ => 256,
160+
};
161+
157162
Ok(VhostUserDevice {
158163
frontend: Arc::new(Mutex::new(frontend)),
159164
device_type,
@@ -163,6 +168,8 @@ impl VhostUserDevice {
163168
has_protocol_features,
164169
acked_features: 0,
165170
device_state: DeviceState::Inactive,
171+
config_space: Arc::new(Mutex::new(Vec::new())),
172+
config_size,
166173
})
167174
}
168175

@@ -390,10 +397,55 @@ impl VirtioDevice for VhostUserDevice {
390397
}
391398

392399
fn read_config(&self, offset: u64, data: &mut [u8]) {
393-
// For now, configuration space reads are not supported
394-
// This can be extended using VHOST_USER_GET_CONFIG
400+
let offset_usize = offset as usize;
401+
let end = offset_usize + data.len();
402+
403+
{
404+
let config_cache = self.config_space.lock().unwrap();
405+
if !config_cache.is_empty() && end <= config_cache.len() {
406+
data.copy_from_slice(&config_cache[offset_usize..end]);
407+
return;
408+
}
409+
}
410+
411+
// Fetch from backend if not cached
412+
if self.has_protocol_features && self.config_size > 0 {
413+
if let Ok(mut frontend) = self.frontend.lock() {
414+
let mut config_buf = vec![0u8; self.config_size as usize];
415+
416+
match frontend.get_config(
417+
0,
418+
self.config_size,
419+
VhostUserConfigFlags::empty(),
420+
&mut config_buf,
421+
) {
422+
Ok((_, returned_buf)) => {
423+
let mut config_cache = self.config_space.lock().unwrap();
424+
*config_cache = returned_buf;
425+
426+
if end <= config_cache.len() {
427+
data.copy_from_slice(&config_cache[offset_usize..end]);
428+
debug!(
429+
"{}: fetched config on-demand, read {} bytes at offset {}",
430+
self.device_name,
431+
data.len(),
432+
offset
433+
);
434+
return;
435+
}
436+
}
437+
Err(e) => {
438+
debug!(
439+
"{}: failed to fetch config from backend: {:?}",
440+
self.device_name, e
441+
);
442+
}
443+
}
444+
}
445+
}
446+
395447
debug!(
396-
"{}: config read at offset {} (not yet implemented)",
448+
"{}: config read at offset {} returning zeros (cache empty or read failed)",
397449
self.device_name, offset
398450
);
399451
data.fill(0);

0 commit comments

Comments
 (0)