Skip to content

Commit f473e91

Browse files
committed
Implement VirtIO sound device playback
The limitations are listed as follows: 1. The emulator will hang if PulseAudio is enabled on host. The reason is that the host OS cannot close CNFA driver because CNFA driver reference count is not zero (PulseAudio holds one reference count). What's worse, CNFA cannot initialize if PulseAudio is disabled, As it needs a dedicated threading model (CNFA uses different threading models between pure ALSA and PulseAudio environment), we suggest users need mitigations (for instance, restart the emulator after playing sound) or just wait for the future release. 2. The playback may play with repeating artifact (for example, A "front center" ALSA example sound will sound like "front front center"). The root cause is the Linux Kernel and it hasn't got fixed even in mainline version. See https://lore.kernel.org/all/[email protected]/T/ for more information.
1 parent 553152f commit f473e91

14 files changed

+1467
-4
lines changed

.github/workflows/main.yml

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ jobs:
1010
- name: install-dependencies
1111
run: |
1212
sudo apt-get install build-essential device-tree-compiler expect
13+
sudo apt-get install libasound2-dev libudev-dev
1314
- name: default build
1415
run: make
1516
shell: bash

.gitmodules

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[submodule "cnfa"]
2+
path = cnfa
3+
url = https://github.com/cntools/cnfa
4+
shallow = true

Makefile

+40
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
include mk/common.mk
2+
include mk/check-libs.mk
23

34
CC ?= gcc
45
CFLAGS := -O2 -g -Wall -Wextra
@@ -13,6 +14,8 @@ OBJS_EXTRA :=
1314
# command line option
1415
OPTS :=
1516

17+
LDFLAGS := -lm
18+
1619
# virtio-blk
1720
ENABLE_VIRTIOBLK ?= 1
1821
$(call set-feature, VIRTIOBLK)
@@ -50,6 +53,43 @@ ifeq ($(call has, VIRTIONET), 1)
5053
OBJS_EXTRA += netdev.o
5154
endif
5255

56+
# virtio-snd
57+
ENABLE_VIRTIOSND ?= 1
58+
ifneq ($(UNAME_S),$(filter $(UNAME_S),Linux))
59+
ENABLE_VIRTIOSND := 0
60+
endif
61+
62+
# Check ALSA installation
63+
ifeq ($(UNAME_S),Linux)
64+
ifeq (0, $(call check-alsa))
65+
$(warning No libasound installed. Check libasound in advance.)
66+
ENABLE_VIRTIOSND := 0
67+
endif
68+
endif
69+
$(call set-feature, VIRTIOSND)
70+
ifeq ($(call has, VIRTIOSND), 1)
71+
OBJS_EXTRA += virtio-snd.o
72+
73+
LDFLAGS += -lasound -lpthread
74+
CFLAGS += -Icnfa
75+
76+
cnfa/Makefile:
77+
git submodule update --init cnfa
78+
cnfa/os_generic: cnfa/Makefile
79+
$(MAKE) -C $(dir $<) os_generic.h
80+
CNFA_LIB := cnfa/CNFA_sf.h
81+
$(CNFA_LIB): cnfa/Makefile cnfa/os_generic
82+
$(MAKE) -C $(dir $<) CNFA_sf.h
83+
main.o: $(CNFA_LIB)
84+
85+
# suppress warning when compiling CNFA
86+
virtio-snd.o: CFLAGS += -Wno-unused-parameter -Wno-sign-compare
87+
endif
88+
89+
# .DEFAULT_GOAL should be set to all since the very first target is not all
90+
# after git submodule.
91+
.DEFAULT_GOAL := all
92+
5393
BIN = semu
5494
all: $(BIN) minimal.dtb
5595

README.md

+6-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@ A minimalist RISC-V system emulator capable of running Linux the kernel and corr
99
- UART: 8250/16550
1010
- PLIC (platform-level interrupt controller): 32 interrupts, no priority
1111
- Standard SBI, with the timer extension
12-
- VirtIO: virtio-blk acquires disk image from the host, and virtio-net is mapped as TAP interface
12+
- Three types of I/O support using VirtIO standard:
13+
- virtio-blk acquires disk image from the host.
14+
- virtio-net is mapped as TAP interface.
15+
- virtio-snd uses ALSA for sound operation with the following limitations:
16+
- The emulator will hang if PulseAudio is enabled on host.
17+
- The playback may plays with repeating artifact.
1318

1419
## Prerequisites
1520

cnfa

Submodule cnfa added at 60bcddd

configs/buildroot.config

+3
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ BR2_RELRO_NONE=y
3636
# BR2_RELRO_PARTIAL is not set
3737
# BR2_RELRO_FULL is not set
3838
BR2_FORTIFY_SOURCE_1=y
39+
BR2_PACKAGE_ALSA_UTILS=y
40+
BR2_PACKAGE_ALSA_UTILS_APLAY=y
41+
BR2_PACKAGE_ALSA_UTILS_SPEAKER_TEST=y
3942
# BR2_PACKAGE_URANDOM_SCRIPTS is not set
4043
BR2_TARGET_ROOTFS_CPIO=y
4144
BR2_TARGET_ROOTFS_CPIO_FULL=y

configs/linux.config

+5-2
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ CONFIG_LOCALVERSION_AUTO=y
3030
CONFIG_BUILD_SALT=""
3131
CONFIG_DEFAULT_INIT=""
3232
CONFIG_DEFAULT_HOSTNAME="(none)"
33-
# CONFIG_SYSVIPC is not set
33+
CONFIG_SYSVIPC=y
34+
CONFIG_SYSVIPC_SYSCTL=y
3435
# CONFIG_POSIX_MQUEUE is not set
3536
# CONFIG_WATCH_QUEUE is not set
3637
# CONFIG_CROSS_MEMORY_ATTACH is not set
@@ -937,7 +938,9 @@ CONFIG_DUMMY_CONSOLE_ROWS=25
937938
# end of Console display driver support
938939
# end of Graphics support
939940

940-
# CONFIG_SOUND is not set
941+
CONFIG_SOUND=y
942+
CONFIG_SND=y
943+
CONFIG_SND_VIRTIO=y
941944

942945
#
943946
# HID support

device.h

+50
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,53 @@ void aclint_sswi_write(hart_t *hart,
306306
uint8_t width,
307307
uint32_t value);
308308

309+
/* VirtIO-Sound */
310+
311+
#if SEMU_HAS(VIRTIOSND)
312+
#define IRQ_VSND 5
313+
#define IRQ_VSND_BIT (1 << IRQ_VSND)
314+
315+
typedef struct {
316+
uint32_t QueueNum;
317+
uint32_t QueueDesc;
318+
uint32_t QueueAvail;
319+
uint32_t QueueUsed;
320+
uint16_t last_avail;
321+
bool ready;
322+
} virtio_snd_queue_t;
323+
324+
typedef struct {
325+
/* feature negotiation */
326+
uint32_t DeviceFeaturesSel;
327+
uint32_t DriverFeatures;
328+
uint32_t DriverFeaturesSel;
329+
/* queue config */
330+
uint32_t QueueSel;
331+
virtio_snd_queue_t queues[4];
332+
/* status */
333+
uint32_t Status;
334+
uint32_t InterruptStatus;
335+
/* supplied by environment */
336+
uint32_t *ram;
337+
/* implementation-specific */
338+
void *priv;
339+
} virtio_snd_state_t;
340+
341+
void virtio_snd_read(hart_t *core,
342+
virtio_snd_state_t *vsnd,
343+
uint32_t addr,
344+
uint8_t width,
345+
uint32_t *value);
346+
347+
void virtio_snd_write(hart_t *core,
348+
virtio_snd_state_t *vsnd,
349+
uint32_t addr,
350+
uint8_t width,
351+
uint32_t value);
352+
353+
bool virtio_snd_init(virtio_snd_state_t *vsnd);
354+
#endif /* SEMU_HAS(VIRTIOSND) */
355+
309356
/* memory mapping */
310357
typedef struct {
311358
bool stopped;
@@ -326,4 +373,7 @@ typedef struct {
326373
mtimer_state_t mtimer;
327374
mswi_state_t mswi;
328375
sswi_state_t sswi;
376+
#if SEMU_HAS(VIRTIOSND)
377+
virtio_snd_state_t vsnd;
378+
#endif
329379
} emu_state_t;

feature.h

+5
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,10 @@
1212
#define SEMU_FEATURE_VIRTIONET 1
1313
#endif
1414

15+
/* virtio-snd */
16+
#ifndef SEMU_FEATURE_VIRTIOSND
17+
#define SEMU_FEATURE_VIRTIOSND 1
18+
#endif
19+
1520
/* Feature test macro */
1621
#define SEMU_HAS(x) SEMU_FEATURE_##x

main.c

+37
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,18 @@ static void emu_update_swi_interrupt(hart_t *hart)
100100
aclint_sswi_update_interrupts(hart, &data->sswi);
101101
}
102102

103+
#if SEMU_HAS(VIRTIOSND)
104+
static void emu_update_vsnd_interrupts(vm_t *vm)
105+
{
106+
emu_state_t *data = PRIV(vm->hart[0]);
107+
if (data->vsnd.InterruptStatus)
108+
data->plic.active |= IRQ_VSND_BIT;
109+
else
110+
data->plic.active &= ~IRQ_VSND_BIT;
111+
plic_update_interrupts(vm, &data->plic);
112+
}
113+
#endif
114+
103115
static void mem_load(hart_t *hart,
104116
uint32_t addr,
105117
uint8_t width,
@@ -155,6 +167,13 @@ static void mem_load(hart_t *hart,
155167
emu_update_vrng_interrupts(hart->vm);
156168
return;
157169
#endif
170+
171+
#if SEMU_HAS(VIRTIOSND)
172+
case 0x47: /* virtio-snd */
173+
virtio_snd_read(hart, &data->vsnd, addr & 0xFFFFF, width, value);
174+
emu_update_vsnd_interrupts(hart->vm);
175+
return;
176+
#endif
158177
}
159178
}
160179
vm_set_exception(hart, RV_EXC_LOAD_FAULT, hart->exc_val);
@@ -209,12 +228,20 @@ static void mem_store(hart_t *hart,
209228
aclint_sswi_write(hart, &data->sswi, addr & 0xFFFFF, width, value);
210229
aclint_sswi_update_interrupts(hart, &data->sswi);
211230
return;
231+
212232
#if SEMU_HAS(VIRTIORNG)
213233
case 0x46: /* virtio-rng */
214234
virtio_rng_write(hart, &data->vrng, addr & 0xFFFFF, width, value);
215235
emu_update_vrng_interrupts(hart->vm);
216236
return;
217237
#endif
238+
239+
#if SEMU_HAS(VIRTIOSND)
240+
case 0x47: /* virtio-snd */
241+
virtio_snd_write(hart, &data->vsnd, addr & 0xFFFFF, width, value);
242+
emu_update_vsnd_interrupts(hart->vm);
243+
return;
244+
#endif
218245
}
219246
}
220247
vm_set_exception(hart, RV_EXC_STORE_FAULT, hart->exc_val);
@@ -651,6 +678,11 @@ static int semu_start(int argc, char **argv)
651678
emu.mtimer.mtimecmp = calloc(vm.n_hart, sizeof(uint64_t));
652679
emu.mswi.msip = calloc(vm.n_hart, sizeof(uint32_t));
653680
emu.sswi.ssip = calloc(vm.n_hart, sizeof(uint32_t));
681+
#if SEMU_HAS(VIRTIOSND)
682+
if (!virtio_snd_init(&(emu.vsnd)))
683+
fprintf(stderr, "No virtio-snd functioned\n");
684+
emu.vsnd.ram = emu.ram;
685+
#endif
654686

655687
/* Emulate */
656688
uint32_t peripheral_update_ctr = 0;
@@ -676,6 +708,11 @@ static int semu_start(int argc, char **argv)
676708
if (emu.vblk.InterruptStatus)
677709
emu_update_vblk_interrupts(&vm);
678710
#endif
711+
712+
#if SEMU_HAS(VIRTIOSND)
713+
if (emu.vsnd.InterruptStatus)
714+
emu_update_vsnd_interrupts(&vm);
715+
#endif
679716
}
680717

681718
emu_update_timer_interrupt(vm.hart[i]);

minimal.dts

+8
Original file line numberDiff line numberDiff line change
@@ -71,5 +71,13 @@
7171
interrupts = <4>;
7272
};
7373
#endif
74+
75+
#if SEMU_FEATURE_VIRTIOSND
76+
snd0: virtio@4700000 {
77+
compatible = "virtio,mmio";
78+
reg = <0x4700000 0x200>;
79+
interrupts = <5>;
80+
};
81+
#endif
7482
};
7583
};

mk/check-libs.mk

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Create a mininal ALSA program
2+
define create-alsa-prog
3+
echo '\
4+
#include <alsa/asoundlib.h>\n\
5+
int main(){\n\
6+
snd_pcm_t *pcm;\n\
7+
snd_pcm_open(&pcm, "default", SND_PCM_STREAM_PLAYBACK, 0);\n\
8+
snd_pcm_close(pcm);\n\
9+
return 0;\n\
10+
}\n'
11+
endef
12+
13+
# Check ALSA installation
14+
define check-alsa
15+
$(shell $(call create-alsa-prog) | $(CC) -x c -lasound -o /dev/null > /dev/null 2> /dev/null -
16+
&& echo $$?)
17+
endef

0 commit comments

Comments
 (0)