From c9f93c48d9aacbf124935ddbe27ed71f73c2d235 Mon Sep 17 00:00:00 2001 From: Cuda-Chen Date: Fri, 9 Aug 2024 19:55:42 +0800 Subject: [PATCH] WIP Enable ALSA driver and System V IPC in Linux Kernel. Implement VIRTIO_SND_R_PCM_* prototypes. Set CNFA opaque for sound playing control. Set condition of start, stop, and prepeare playing. Add description of descriptor chaining, yet need refactoring the description as well as code (exists uncertainty of the query struct). --- .github/workflows/main.yml | 1 + .gitmodules | 4 + Makefile | 33 ++ README.md | 5 +- cnfa | 1 + configs/buildroot.config | 3 + configs/linux.config | 8 +- device.h | 51 +++ feature.h | 5 + main.c | 35 ++ minimal.dts | 8 + mk/check-libs.mk | 17 + virtio-snd.c | 731 +++++++++++++++++++++++++++++++++++++ 13 files changed, 899 insertions(+), 3 deletions(-) create mode 100644 .gitmodules create mode 160000 cnfa create mode 100644 mk/check-libs.mk create mode 100644 virtio-snd.c diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7fbdcb4..51904af 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,6 +10,7 @@ jobs: - name: install-dependencies run: | sudo apt-get install build-essential device-tree-compiler expect + sudo apt-get install libasound2-dev libudev-dev - name: default build run: make shell: bash diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..6d955f1 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "cnfa"] + path = cnfa + url = https://github.com/cntools/cnfa + shallow = true diff --git a/Makefile b/Makefile index ceb397f..8527680 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ include mk/common.mk +include mk/check-libs.mk CC ?= gcc CFLAGS := -O2 -g -Wall -Wextra @@ -13,6 +14,8 @@ OBJS_EXTRA := # command line option OPTS := +LDFLAGS := + # virtio-blk ENABLE_VIRTIOBLK ?= 1 $(call set-feature, VIRTIOBLK) @@ -41,6 +44,36 @@ ifeq ($(call has, VIRTIONET), 1) OBJS_EXTRA += virtio-net.o endif +# virtio-snd +ENABLE_VIRTIOSND ?= 1 +ifneq ($(UNAME_S),Linux) + ENABLE_VIRTIOSND := 0 +endif + +ifeq (0, $(call check-alsa)) +$(warning No libasound installed. Check libasound in advance.) + ENABLE_VIRTIOSND := 0 +endif +$(call set-feature, VIRTIOSND) +ifeq ($(call has, VIRTIOSND), 1) + OBJS_EXTRA += virtio-snd.o + LDFLAGS += -lasound -lpthread + CFLAGS += -Icnfa + +cnfa/Makefile: + git submodule update --init cnfa +cnfa/os_generic: cnfa/Makefile + $(MAKE) -C $(dir $<) os_generic.h +CNFA_LIB := cnfa/CNFA_sf.h +$(CNFA_LIB): cnfa/Makefile cnfa/os_generic + $(MAKE) -C $(dir $<) CNFA_sf.h +main.o: $(CNFA_LIB) +endif + +# .DEFAULT_GOAL should be set to all since the very first target is not all +# after git submodule. +.DEFAULT_GOAL := all + BIN = semu all: $(BIN) minimal.dtb diff --git a/README.md b/README.md index 314881e..bd69c47 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,10 @@ A minimalist RISC-V system emulator capable of running Linux the kernel and corr - UART: 8250/16550 - PLIC (platform-level interrupt controller): 32 interrupts, no priority - Standard SBI, with the timer extension -- VirtIO: virtio-blk acquires disk image from the host, and virtio-net is mapped as TAP interface +- Three types of I/O support using VirtIO standard: + - virtio-blk acquires disk image from the host. + - virtio-net is mapped as TAP interface. + - virtio-snd uses ALSA for sound operation. ## Prerequisites diff --git a/cnfa b/cnfa new file mode 160000 index 0000000..60bcddd --- /dev/null +++ b/cnfa @@ -0,0 +1 @@ +Subproject commit 60bcddd1e22c727be8f967027a7efb4b6c58e167 diff --git a/configs/buildroot.config b/configs/buildroot.config index 6f51a68..315fccf 100644 --- a/configs/buildroot.config +++ b/configs/buildroot.config @@ -36,6 +36,9 @@ BR2_RELRO_NONE=y # BR2_RELRO_PARTIAL is not set # BR2_RELRO_FULL is not set BR2_FORTIFY_SOURCE_1=y +BR2_PACKAGE_ALSA_UTILS=y +BR2_PACKAGE_ALSA_UTILS_APLAY=y +BR2_PACKAGE_ALSA_UTILS_SPEAKER_TEST=y # BR2_PACKAGE_URANDOM_SCRIPTS is not set BR2_TARGET_ROOTFS_CPIO=y BR2_TARGET_ROOTFS_CPIO_FULL=y diff --git a/configs/linux.config b/configs/linux.config index 99078d4..e026179 100644 --- a/configs/linux.config +++ b/configs/linux.config @@ -30,7 +30,8 @@ CONFIG_LOCALVERSION_AUTO=y CONFIG_BUILD_SALT="" CONFIG_DEFAULT_INIT="" CONFIG_DEFAULT_HOSTNAME="(none)" -# CONFIG_SYSVIPC is not set +CONFIG_SYSVIPC=y +CONFIG_SYSVIPC_SYSCTL=y # CONFIG_POSIX_MQUEUE is not set # CONFIG_WATCH_QUEUE is not set # CONFIG_CROSS_MEMORY_ATTACH is not set @@ -936,7 +937,10 @@ CONFIG_DUMMY_CONSOLE_ROWS=25 # end of Console display driver support # end of Graphics support -# CONFIG_SOUND is not set +CONFIG_SOUND=y +CONFIG_SND=y +CONFIG_SND_ALOOP=y +CONFIG_SND_VIRTIO=y # # HID support diff --git a/device.h b/device.h index 1bb7900..c1cb381 100644 --- a/device.h +++ b/device.h @@ -190,6 +190,53 @@ void clint_write(hart_t *vm, uint8_t width, uint32_t value); +/* VirtIO-Sound */ + +#if SEMU_HAS(VIRTIOSND) +#define IRQ_VSND 4 +#define IRQ_VSND_BIT (1 << IRQ_VSND) + +typedef struct { + uint32_t QueueNum; + uint32_t QueueDesc; + uint32_t QueueAvail; + uint32_t QueueUsed; + uint16_t last_avail; + bool ready; +} virtio_snd_queue_t; + +typedef struct { + /* feature negotiation */ + uint32_t DeviceFeaturesSel; + uint32_t DriverFeatures; + uint32_t DriverFeaturesSel; + /* queue config */ + uint32_t QueueSel; + virtio_snd_queue_t queues[4]; + /* status */ + uint32_t Status; + uint32_t InterruptStatus; + /* supplied by environment */ + uint32_t *ram; + /* implementation-specific */ + void *priv; +} virtio_snd_state_t; + +void virtio_snd_read(hart_t *core, + virtio_snd_state_t *vsnd, + uint32_t addr, + uint8_t width, + uint32_t *value); + +void virtio_snd_write(hart_t *core, + virtio_snd_state_t *vsnd, + uint32_t addr, + uint8_t width, + uint32_t value); + +bool virtio_snd_init(virtio_snd_state_t *vsnd); +#endif /* SEMU_HAS(VIRTIOSND) */ + /* memory mapping */ typedef struct { @@ -205,4 +252,8 @@ typedef struct { virtio_blk_state_t vblk; #endif clint_state_t clint; +#if SEMU_HAS(VIRTIOSND) + virtio_snd_state_t vsnd; +#endif + uint64_t timer; } emu_state_t; diff --git a/feature.h b/feature.h index 1dee984..d0adcdd 100644 --- a/feature.h +++ b/feature.h @@ -12,5 +12,10 @@ #define SEMU_FEATURE_VIRTIONET 1 #endif +/* virtio-snd */ +#ifndef SEMU_FEATURE_VIRTIOSND +#define SEMU_FEATURE_VIRTIOSND 1 +#endif + /* Feature test macro */ #define SEMU_HAS(x) SEMU_FEATURE_##x diff --git a/main.c b/main.c index 4a92113..4aefae6 100644 --- a/main.c +++ b/main.c @@ -81,6 +81,18 @@ static void emu_update_timer_interrupt(hart_t *hart) clint_update_interrupts(hart, &data->clint); } +#if SEMU_HAS(VIRTIOSND) +static void emu_update_vsnd_interrupts(vm_t *vm) +{ + emu_state_t *data = PRIV(vm->hart[0]); + if (data->vsnd.InterruptStatus) + data->plic.active |= IRQ_VSND_BIT; + else + data->plic.active &= ~IRQ_VSND_BIT; + plic_update_interrupts(vm, &data->plic); +} +#endif + static void mem_load(hart_t *hart, uint32_t addr, uint8_t width, @@ -121,6 +133,13 @@ static void mem_load(hart_t *hart, clint_read(hart, &data->clint, addr & 0xFFFFF, width, value); clint_update_interrupts(hart, &data->clint); return; + +#if SEMU_HAS(VIRTIOSND) + case 0x44: /* virtio-snd */ + virtio_snd_read(hart, &data->vsnd, addr & 0xFFFFF, width, value); + emu_update_vsnd_interrupts(hart->vm); + return; +#endif } } vm_set_exception(hart, RV_EXC_LOAD_FAULT, hart->exc_val); @@ -166,6 +185,12 @@ static void mem_store(hart_t *hart, clint_write(hart, &data->clint, addr & 0xFFFFF, width, value); clint_update_interrupts(hart, &data->clint); return; +#if SEMU_HAS(VIRTIOSND) + case 0x44: /* virtio-snd */ + virtio_snd_write(hart, &data->vsnd, addr & 0xFFFFF, width, value); + emu_update_vsnd_interrupts(hart->vm); + return; +#endif } } vm_set_exception(hart, RV_EXC_STORE_FAULT, hart->exc_val); @@ -581,6 +606,11 @@ static int semu_start(int argc, char **argv) emu.vblk.ram = emu.ram; emu.disk = virtio_blk_init(&(emu.vblk), disk_file); #endif +#if SEMU_HAS(VIRTIOSND) + if (!virtio_snd_init(&(emu.vsnd))) + fprintf(stderr, "No virtio-snd functioned\n"); + emu.vsnd.ram = emu.ram; +#endif /* Emulate */ uint32_t peripheral_update_ctr = 0; @@ -603,6 +633,11 @@ static int semu_start(int argc, char **argv) if (emu.vblk.InterruptStatus) emu_update_vblk_interrupts(&vm); #endif + +#if SEMU_HAS(VIRTIOSND) + if (emu.vsnd.InterruptStatus) + emu_update_vsnd_interrupts(&vm); +#endif } emu_update_timer_interrupt(vm.hart[i]); diff --git a/minimal.dts b/minimal.dts index d83bcfc..a0133b9 100644 --- a/minimal.dts +++ b/minimal.dts @@ -63,5 +63,13 @@ interrupts = <3>; }; #endif + +#if SEMU_FEATURE_VIRTIOSND + snd0: virtio@4400000 { + compatible = "virtio,mmio"; + reg = <0x4400000 0x200>; + interrupts = <4>; + }; +#endif }; }; diff --git a/mk/check-libs.mk b/mk/check-libs.mk new file mode 100644 index 0000000..888fc18 --- /dev/null +++ b/mk/check-libs.mk @@ -0,0 +1,17 @@ +# Create a mininal ALSA program +define create-alsa-prog +echo '\ +#include \n\ +int main(){\n\ + snd_pcm_t *pcm;\n\ + snd_pcm_open(&pcm, "default", SND_PCM_STREAM_PLAYBACK, 0);\n\ + snd_pcm_close(pcm);\n\ + return 0;\n\ +}\n' +endef + +# Check ALSA installation +define check-alsa +$(shell $(call create-alsa-prog) | $(CC) -x c -lasound -o /dev/null > /dev/null 2> /dev/null - + && echo $$?) +endef diff --git a/virtio-snd.c b/virtio-snd.c new file mode 100644 index 0000000..d0c5448 --- /dev/null +++ b/virtio-snd.c @@ -0,0 +1,731 @@ +#include +#include + +#define CNFA_IMPLEMENTATION +#include "CNFA_sf.h" + +#include "device.h" +#include "riscv.h" +#include "riscv_private.h" +#include "virtio.h" + +#define VSND_DEV_CNT_MAX 1 + +#define VSND_QUEUE_NUM_MAX 1024 +#define vsndq (vsnd->queues[vsnd->QueueSel]) + +#define PRIV(x) ((struct virtio_snd_config_t *) x->priv) + +/* supported virtio sound version */ +enum { + VSND_FEATURES_0 = 0, + VSND_FEATURES_1, +}; + +enum { + /* jack control requests types */ + VIRTIO_SND_R_JACK_INFO = 1, + + /* PCM control requests types */ + VIRTIO_SND_R_PCM_INFO = 0x0100, + VIRTIO_SND_R_PCM_SET_PARAMS, + VIRTIO_SND_R_PCM_PREPARE, + VIRTIO_SND_R_PCM_RELEASE, + VIRTIO_SND_R_PCM_START, + VIRTIO_SND_R_PCM_STOP, + + /* channel map control requests types */ + VIRTIO_SND_R_CHMAP_INFO = 0x0200, + + /* common status codes */ + VIRTIO_SND_S_OK = 0x8000, + VIRTIO_SND_S_BAD_MSG, + VIRTIO_SND_S_NOT_SUPP, + VIRTIO_SND_S_IO_ERR, +}; + +/* supported PCM frame rates */ +enum { + VIRTIO_SND_PCM_RATE_5512 = 0, /* 5512 Hz */ + VIRTIO_SND_PCM_RATE_8000, /* 8000 Hz */ + VIRTIO_SND_PCM_RATE_11025, /* 11025 Hz */ + VIRTIO_SND_PCM_RATE_16000, /* 16000 Hz */ + VIRTIO_SND_PCM_RATE_22050, /* 22050 Hz */ + VIRTIO_SND_PCM_RATE_32000, /* 32000 Hz */ + VIRTIO_SND_PCM_RATE_44100, /* 44100 Hz */ + VIRTIO_SND_PCM_RATE_48000, /* 48000 Hz */ + VIRTIO_SND_PCM_RATE_64000, /* 64000 Hz */ + VIRTIO_SND_PCM_RATE_88200, /* 88200 Hz */ + VIRTIO_SND_PCM_RATE_96000, /* 96000 Hz */ + VIRTIO_SND_PCM_RATE_176400, /* 176400 Hz */ + VIRTIO_SND_PCM_RATE_192000, /* 192000 Hz */ + VIRTIO_SND_PCM_RATE_384000, /* 384000 Hz */ +}; + +/* supported PCM sample formats */ +enum { + /* analog formats (width / physical width) */ + VIRTIO_SND_PCM_FMT_IMA_ADPCM = 0, /* 4 / 4 bits */ + VIRTIO_SND_PCM_FMT_MU_LAW, /* 8 / 8 bits */ + VIRTIO_SND_PCM_FMT_A_LAW, /* 8 / 8 bits */ + VIRTIO_SND_PCM_FMT_S8, /* 8 / 8 bits */ + VIRTIO_SND_PCM_FMT_U8, /* 8 / 8 bits */ + VIRTIO_SND_PCM_FMT_S16, /* 16 / 16 bits */ + VIRTIO_SND_PCM_FMT_U16, /* 16 / 16 bits */ + VIRTIO_SND_PCM_FMT_S18_3, /* 18 / 24 bits */ + VIRTIO_SND_PCM_FMT_U18_3, /* 18 / 24 bits */ + VIRTIO_SND_PCM_FMT_S20_3, /* 20 / 24 bits */ + VIRTIO_SND_PCM_FMT_U20_3, /* 20 / 24 bits */ + VIRTIO_SND_PCM_FMT_S24_3, /* 24 / 24 bits */ + VIRTIO_SND_PCM_FMT_U24_3, /* 24 / 24 bits */ + VIRTIO_SND_PCM_FMT_S20, /* 20 / 32 bits */ + VIRTIO_SND_PCM_FMT_U20, /* 20 / 32 bits */ + VIRTIO_SND_PCM_FMT_S24, /* 24 / 32 bits */ + VIRTIO_SND_PCM_FMT_U24, /* 24 / 32 bits */ + VIRTIO_SND_PCM_FMT_S32, /* 32 / 32 bits */ + VIRTIO_SND_PCM_FMT_U32, /* 32 / 32 bits */ + VIRTIO_SND_PCM_FMT_FLOAT, /* 32 / 32 bits */ + VIRTIO_SND_PCM_FMT_FLOAT64, /* 64 / 64 bits */ + /* digital formats (width / physical width) */ + VIRTIO_SND_PCM_FMT_DSD_U8, /* 8 / 8 bits */ + VIRTIO_SND_PCM_FMT_DSD_U16, /* 16 / 16 bits */ + VIRTIO_SND_PCM_FMT_DSD_U32, /* 32 / 32 bits */ + VIRTIO_SND_PCM_FMT_IEC958_SUBFRAME, /* 32 / 32 bits */ +}; + +/* standard channel position definition */ +enum { + VIRTIO_SND_CHMAP_NONE = 0, /* undefined */ + VIRTIO_SND_CHMAP_NA, /* silent */ + VIRTIO_SND_CHMAP_MONO, /* mono stream */ + VIRTIO_SND_CHMAP_FL, /* front left */ + VIRTIO_SND_CHMAP_FR, /* front right */ + VIRTIO_SND_CHMAP_RL, /* rear left */ + VIRTIO_SND_CHMAP_RR, /* rear right */ + VIRTIO_SND_CHMAP_FC, /* front center */ + VIRTIO_SND_CHMAP_LFE, /* low frequency (LFE) */ + VIRTIO_SND_CHMAP_SL, /* side left */ + VIRTIO_SND_CHMAP_SR, /* side right */ + VIRTIO_SND_CHMAP_RC, /* rear center */ + VIRTIO_SND_CHMAP_FLC, /* front left center */ + VIRTIO_SND_CHMAP_FRC, /* front right center */ + VIRTIO_SND_CHMAP_RLC, /* rear left center */ + VIRTIO_SND_CHMAP_RRC, /* rear right center */ + VIRTIO_SND_CHMAP_FLW, /* front left wide */ + VIRTIO_SND_CHMAP_FRW, /* front right wide */ + VIRTIO_SND_CHMAP_FLH, /* front left high */ + VIRTIO_SND_CHMAP_FCH, /* front center high */ + VIRTIO_SND_CHMAP_FRH, /* front right high */ + VIRTIO_SND_CHMAP_TC, /* top center */ + VIRTIO_SND_CHMAP_TFL, /* top front left */ + VIRTIO_SND_CHMAP_TFR, /* top front right */ + VIRTIO_SND_CHMAP_TFC, /* top front center */ + VIRTIO_SND_CHMAP_TRL, /* top rear left */ + VIRTIO_SND_CHMAP_TRR, /* top rear right */ + VIRTIO_SND_CHMAP_TRC, /* top rear center */ + VIRTIO_SND_CHMAP_TFLC, /* top front left center */ + VIRTIO_SND_CHMAP_TFRC, /* top front right center */ + VIRTIO_SND_CHMAP_TSL, /* top side left */ + VIRTIO_SND_CHMAP_TSR, /* top side right */ + VIRTIO_SND_CHMAP_LLFE, /* left LFE */ + VIRTIO_SND_CHMAP_RLFE, /* right LFE */ + VIRTIO_SND_CHMAP_BC, /* bottom center */ + VIRTIO_SND_CHMAP_BLC, /* bottom left center */ + VIRTIO_SND_CHMAP_BRC, /* bottom right center */ +}; + +/* audio data flow direction */ +enum { + VIRTIO_SND_D_OUTPUT = 0, + VIRTIO_SND_D_INPUT, +}; + +typedef struct { + uint32_t jacks; + uint32_t streams; + uint32_t chmaps; +} virtio_snd_config_t; + +/* VirtIO sound common header */ +typedef struct { + uint32_t code; +} virtio_snd_hdr_t; + +typedef struct { + uint32_t hda_fn_nid; +} virtio_snd_info_t; + +typedef struct { + virtio_snd_hdr_t hdr; + uint32_t start_id; + uint32_t count; + uint32_t size; +} virtio_snd_query_info_t; + +typedef struct { + virtio_snd_info_t hdr; + uint32_t features; + uint32_t hda_reg_defconf; + uint32_t hda_reg_caps; + uint8_t connected; + uint8_t padding[7]; +} virtio_snd_jack_info_t; + +typedef struct { + virtio_snd_info_t hdr; + uint32_t features; + uint64_t formats; + uint64_t rates; + uint8_t direction; + uint8_t channels_min; + uint8_t channels_max; + uint8_t padding[5]; +} virtio_snd_pcm_info_t; + +typedef struct { + virtio_snd_hdr_t hdr; + uint32_t stream_id; +} virtio_snd_pcm_hdr_t; + +typedef struct { + virtio_snd_pcm_hdr_t hdr; /* .code = VIRTIO_SND_R_PCM_SET_PARAMS */ + uint32_t buffer_bytes; + uint32_t period_bytes; + uint32_t features; /* 1 << VIRTIO_SND_PCM_F_XXX */ + uint8_t channels; + uint8_t format; + uint8_t rate; + uint8_t padding; +} virtio_snd_pcm_set_params_t; + +#define VIRTIO_SND_CHMAP_MAX_SIZE 18 + +typedef struct { + virtio_snd_info_t hdr; + uint8_t direction; + uint8_t channels; + uint8_t positions[VIRTIO_SND_CHMAP_MAX_SIZE]; +} virtio_snd_chmap_info_t; + +static virtio_snd_config_t vsnd_configs[VSND_DEV_CNT_MAX]; +static int vsnd_dev_cnt = 0; + +static struct CNFADriver *audio_host = NULL; + +static bool guest_playing = false; + +static void virtio_snd_set_fail(virtio_snd_state_t *vsnd) +{ + vsnd->Status |= VIRTIO_STATUS__DEVICE_NEEDS_RESET; + if (vsnd->Status & VIRTIO_STATUS__DRIVER_OK) + vsnd->InterruptStatus |= VIRTIO_INT__CONF_CHANGE; +} + +/* Check whether the address is valid or not */ +static inline uint32_t vsnd_preprocess(virtio_snd_state_t *vsnd, uint32_t addr) +{ + if ((addr >= RAM_SIZE) || (addr & 0b11)) + return virtio_snd_set_fail(vsnd), 0; + + /* shift right as we have checked in the above */ + return addr >> 2; +} + +static void virtio_snd_update_status(virtio_snd_state_t *vsnd, uint32_t status) +{ + vsnd->Status |= status; + if (status) + return; + + /* Reset */ + uint32_t *ram = vsnd->ram; + void *priv = vsnd->priv; + memset(vsnd, 0, sizeof(*vsnd)); + vsnd->ram = ram; + vsnd->priv = priv; +} + +static void virtio_snd_read_jack_info_handler( + struct virtq_desc *vq_desc, + const virtio_snd_query_info_t *query) +{ + virtio_snd_jack_info_t *info = + (virtio_snd_jack_info_t *) ((uintptr_t) vq_desc[2].addr); + for (uint32_t i = 0; i < query->count; i++) { + info[i].hdr.hda_fn_nid = 0; + info[i].features = 0; + info[i].hda_reg_defconf = 0; + info[i].hda_reg_caps = 0; + info[i].connected = 1; + memset(&info[i].padding, 0, sizeof(info[i].padding)); + } +} + +static void virtio_snd_read_pcm_info_handler( + struct virtq_desc *vq_desc, + const virtio_snd_query_info_t *query) +{ + /* TODO: should read the config */ + virtio_snd_pcm_info_t *info = + (virtio_snd_pcm_info_t *) ((uintptr_t) vq_desc[2].addr); + for (uint32_t i = 0; i < query->count; i++) { + info[0].features = 0; + info[0].formats = (1 << VIRTIO_SND_PCM_FMT_S16); + info[0].rates = (1 << VIRTIO_SND_PCM_RATE_44100); + info[0].direction = VIRTIO_SND_D_OUTPUT; + info[0].channels_min = 1; + info[0].channels_max = 1; + memset(&info[i].padding, 0, sizeof(info[i].padding)); + } +} + +static void virtio_snd_read_chmap_info_handler( + struct virtq_desc *vq_desc, + const virtio_snd_query_info_t *query) +{ + virtio_snd_chmap_info_t *info = + (virtio_snd_chmap_info_t *) ((uintptr_t) vq_desc[2].addr); + for (uint32_t i = 0; i < query->count; i++) { + info[i].direction = VIRTIO_SND_D_OUTPUT; + info[i].channels = 1; + info[i].positions[0] = VIRTIO_SND_CHMAP_MONO; + } +} + +static void virtio_snd_read_pcm_set_params(struct virtq_desc *vq_desc, + const virtio_snd_query_info_t *query) +{ + /* TODO: detect current state of stream */ + virtio_snd_pcm_set_params_t *info = + (virtio_snd_pcm_set_params_t *) ((uintptr_t) vq_desc[2].addr); + + /* TODO: let application to set each value at will */ + /* TODO: check the valiability of buffer_bytes, period_bytes, channel_min, + * and channel_max */ + info->hdr.hdr.code = VIRTIO_SND_R_PCM_SET_PARAMS; + info->hdr.stream_id = 0; + info->buffer_bytes = 2048; + info->period_bytes = 1024; + info->features = 0; + info->channels = 1; + info->format = (1 << VIRTIO_SND_PCM_FMT_S16); + info->rate = (1 << VIRTIO_SND_PCM_RATE_44100); + info->padding = 0; +} + +static void virtio_snd_read_pcm_prepare(struct virtq_desc *vq_desc, + const virtio_snd_query_info_t *query) +{ + virtio_snd_pcm_hdr_t *hdr = + (virtio_snd_pcm_hdr_t *) ((uintptr_t) vq_desc[2].addr); + + /* TODO: let application to set stream_id at will */ + hdr->hdr.code = VIRTIO_SND_R_PCM_PREPARE; + hdr->stream_id = 0; + + /* Control the callback to prepare the buffer */ + /* TODO: add lock to avoid race condition */ + guest_playing = false; +} + +static void virtio_snd_read_pcm_start(struct virtq_desc *vq_desc, + const virtio_snd_query_info_t *query) +{ + virtio_snd_pcm_hdr_t *hdr = + (virtio_snd_pcm_hdr_t *) ((uintptr_t) vq_desc[2].addr); + + /* TODO: let application to set stream_id at will */ + hdr->hdr.code = VIRTIO_SND_R_PCM_START; + hdr->stream_id = 0; + + /* Control the callback to start playing */ + /* TODO: add lock to avoid race condition */ + guest_playing = true; +} + +static void virtio_snd_read_pcm_stop(struct virtq_desc *vq_desc, + const virtio_snd_query_info_t *query) +{ + virtio_snd_pcm_hdr_t *hdr = + (virtio_snd_pcm_hdr_t *) ((uintptr_t) vq_desc[2].addr); + + /* TODO: let application to set stream_id at will */ + hdr->hdr.code = VIRTIO_SND_R_PCM_STOP; + hdr->stream_id = 0; + + /* Control the callback to stop playing */ + /* TODO: add lock to avoid race condition */ + guest_playing = false; +} + +static void virtio_snd_read_pcm_release(struct virtq_desc *vq_desc, + const virtio_snd_query_info_t *query) +{ + virtio_snd_pcm_hdr_t *hdr = + (virtio_snd_pcm_hdr_t *) ((uintptr_t) vq_desc[2].addr); + + /* TODO: let application to set stream_id at will */ + hdr->hdr.code = VIRTIO_SND_R_PCM_RELEASE; + hdr->stream_id = 0; +} + +static void virtio_snd_cb(struct CNFADriver *dev, + short *out, + short *in, + int framesp, + int framesr) +{ + /* TODO: apply lock on guest_playing and guest_playing_ptr */ + int *guest_playing_ptr = (int *) dev->opaque; + int output_channels = dev->channelsPlay; + int output_buf_sz = framesp * output_channels; + + if (framesp > 0) { // playback + if (!(*guest_playing_ptr)) { + memset(out, 0, sizeof(short) * output_buf_sz); + return; + } + } else { + fprintf(stderr, "virtio_snd_cb(%p, %p, %p, %d, %d)\n", dev, out, in, + framesp, framesr); + } +} + +#define VSND_DESC_CNT 3 +static int virtio_snd_desc_handler(virtio_snd_state_t *vsnd, + const virtio_snd_queue_t *queue, + uint32_t desc_idx, + uint32_t *plen) +{ + /* TODO: check the virtio_snd_query_info as the reference implementation + * maybe wrong. */ + /* A full virtio_snd_query_info is composed of at most 3 descriptors, where + * the first descriptor contains: + * struct virtio_snd_hdr hdr (for request) + * the second descriptors contains: + * struct virtio_snd_hdr hdr (for response) + * the third descriptors contains: + * (response payload) + */ + struct virtq_desc vq_desc[VSND_DESC_CNT]; + + /* Collect the descriptors */ + for (int i = 0; i < VSND_DESC_CNT; i++) { + /* The size of the `struct virtq_desc` is 4 words */ + const uint32_t *desc = &vsnd->ram[queue->QueueDesc + desc_idx * 4]; + + /* Retrieve the fields of current descriptor */ + vq_desc[i].addr = desc[0]; + vq_desc[i].len = desc[2]; + vq_desc[i].flags = desc[3]; + desc_idx = desc[3] >> 16; /* vq_desc[desc_cnt].next */ + + /* Leave the loop if next-flag is not set */ + if (!(vq_desc[0].flags & VIRTIO_DESC_F_NEXT)) + break; + } + + /* Process the header */ + const virtio_snd_hdr_t *request = + (virtio_snd_hdr_t *) ((uintptr_t) vsnd->ram + vq_desc[0].addr); + uint32_t type = request->code; + virtio_snd_hdr_t *response = + (virtio_snd_hdr_t *) ((uintptr_t) vsnd->ram + vq_desc[1].addr); + const virtio_snd_query_info_t *query = + (virtio_snd_query_info_t *) ((uintptr_t) vsnd->ram + vq_desc[0].addr); + + /* Process the data */ + switch (type) { + case VIRTIO_SND_R_JACK_INFO: + virtio_snd_read_jack_info_handler(vq_desc, query); + break; + case VIRTIO_SND_R_PCM_INFO: + virtio_snd_read_pcm_info_handler(vq_desc, query); + break; + case VIRTIO_SND_R_CHMAP_INFO: + virtio_snd_read_chmap_info_handler(vq_desc, query); + break; + case VIRTIO_SND_R_PCM_SET_PARAMS: + virtio_snd_read_pcm_set_params(vq_desc, query); + break; + case VIRTIO_SND_R_PCM_PREPARE: + virtio_snd_read_pcm_prepare(vq_desc, query); + break; + case VIRTIO_SND_R_PCM_RELEASE: + virtio_snd_read_pcm_release(vq_desc, query); + break; + case VIRTIO_SND_R_PCM_START: + virtio_snd_read_pcm_start(vq_desc, query); + break; + case VIRTIO_SND_R_PCM_STOP: + virtio_snd_read_pcm_stop(vq_desc, query); + break; + default: + fprintf(stderr, "unsupported virtio-snd operation!\n"); + response->code = VIRTIO_SND_S_NOT_SUPP; + *plen = 0; + return -1; + } + + /* Return the device status */ + response->code = VIRTIO_SND_S_OK; + *plen = vq_desc[2].len; + + return 0; +} + +static void virtio_queue_notify_handler(virtio_snd_state_t *vsnd, int index) +{ + uint32_t *ram = vsnd->ram; + virtio_snd_queue_t *queue = &vsnd->queues[index]; + if (vsnd->Status & VIRTIO_STATUS__DEVICE_NEEDS_RESET) + return; + + if (!((vsnd->Status & VIRTIO_STATUS__DRIVER_OK) && queue->ready)) + return virtio_snd_set_fail(vsnd); + + /* Check for new buffers */ + uint16_t new_avail = ram[queue->QueueAvail] >> 16; + if (new_avail - queue->last_avail > (uint16_t) queue->QueueNum) + return (fprintf(stderr, "size check fail\n"), + virtio_snd_set_fail(vsnd)); + + if (queue->last_avail == new_avail) + return; + + /* Process them */ + uint16_t new_used = ram[queue->QueueUsed] >> 16; /* virtq_used.idx (le16) */ + while (queue->last_avail != new_avail) { + /* Obtain the index in the ring buffer */ + uint16_t queue_idx = queue->last_avail % queue->QueueNum; + + /* Since each buffer index occupies 2 bytes but the memory is aligned + * with 4 bytes, and the first element of the available queue is stored + * at ram[queue->QueueAvail + 1], to acquire the buffer index, it + * requires the following array index calculation and bit shifting. + * Check also the `struct virtq_avail` on the spec. + */ + uint16_t buffer_idx = ram[queue->QueueAvail + 1 + queue_idx / 2] >> + (16 * (queue_idx % 2)); + + /* Consume request from the available queue and process the data in the + * descriptor list. + */ + uint32_t len = 0; + int result = virtio_snd_desc_handler(vsnd, queue, buffer_idx, &len); + if (result != 0) + return virtio_snd_set_fail(vsnd); + + /* Write used element information (`struct virtq_used_elem`) to the used + * queue */ + uint32_t vq_used_addr = + queue->QueueUsed + 1 + (new_used % queue->QueueNum) * 2; + ram[vq_used_addr] = buffer_idx; /* virtq_used_elem.id (le32) */ + ram[vq_used_addr + 1] = len; /* virtq_used_elem.len (le32) */ + queue->last_avail++; + new_used++; + } + + /* Check le32 len field of struct virtq_used_elem on the spec */ + vsnd->ram[queue->QueueUsed] &= MASK(16); /* Reset low 16 bits to zero */ + vsnd->ram[queue->QueueUsed] |= ((uint32_t) new_used) << 16; /* len */ + + /* Send interrupt, unless VIRTQ_AVAIL_F_NO_INTERRUPT is set */ + if (!(ram[queue->QueueAvail] & 1)) + vsnd->InterruptStatus |= VIRTIO_INT__USED_RING; +} + +static bool virtio_snd_reg_read(virtio_snd_state_t *vsnd, + uint32_t addr, + uint32_t *value) +{ +#define _(reg) VIRTIO_##reg + switch (addr) { + case _(MagicValue): + *value = 0x74726976; + return true; + case _(Version): + *value = 2; + return true; + case _(DeviceID): + *value = 25; + return true; + case _(VendorID): + *value = VIRTIO_VENDOR_ID; + return true; + case _(DeviceFeatures): + *value = vsnd->DeviceFeaturesSel == 0 + ? VSND_FEATURES_0 + : (vsnd->DeviceFeaturesSel == 1 ? VSND_FEATURES_1 : 0); + return true; + case _(QueueNumMax): + *value = VSND_QUEUE_NUM_MAX; + return true; + case _(QueueReady): + *value = vsndq.ready ? 1 : 0; + return true; + case _(InterruptStatus): + *value = vsnd->InterruptStatus; + return true; + case _(Status): + *value = vsnd->Status; + return true; + case _(ConfigGeneration): + *value = 0; + return true; + default: + /* Invalid address which exceeded the range */ + if (!RANGE_CHECK(addr, _(Config), sizeof(virtio_snd_config_t))) + return false; + + /* Read configuration from the corresponding register */ + *value = ((uint32_t *) PRIV(vsnd))[addr - _(Config)]; + + return true; + } +#undef _ +} +static bool virtio_snd_reg_write(virtio_snd_state_t *vsnd, + uint32_t addr, + uint32_t value) +{ +#define _(reg) VIRTIO_##reg + switch (addr) { + case _(DeviceFeaturesSel): + vsnd->DeviceFeaturesSel = value; + return true; + case _(DriverFeatures): + vsnd->DriverFeaturesSel == 0 ? (vsnd->DriverFeatures = value) : 0; + return true; + case _(DriverFeaturesSel): + vsnd->DriverFeaturesSel = value; + return true; + case _(QueueSel): + if (value < ARRAY_SIZE(vsnd->queues)) + vsnd->QueueSel = value; + else + virtio_snd_set_fail(vsnd); + return true; + case _(QueueNum): + if (value > 0 && value <= VSND_QUEUE_NUM_MAX) + vsndq.QueueNum = value; + else + virtio_snd_set_fail(vsnd); + return true; + case _(QueueReady): + vsndq.ready = value & 1; + if (value & 1) + vsndq.last_avail = vsnd->ram[vsndq.QueueAvail] >> 16; + return true; + case _(QueueDescLow): + vsndq.QueueDesc = vsnd_preprocess(vsnd, value); + return true; + case _(QueueDescHigh): + if (value) + virtio_snd_set_fail(vsnd); + return true; + case _(QueueDriverLow): + vsndq.QueueAvail = vsnd_preprocess(vsnd, value); + return true; + case _(QueueDriverHigh): + if (value) + virtio_snd_set_fail(vsnd); + return true; + case _(QueueDeviceLow): + vsndq.QueueUsed = vsnd_preprocess(vsnd, value); + return true; + case _(QueueDeviceHigh): + if (value) + virtio_snd_set_fail(vsnd); + return true; + case _(QueueNotify): + if (value < ARRAY_SIZE(vsnd->queues)) { + virtio_queue_notify_handler(vsnd, value); + } else { + virtio_snd_set_fail(vsnd); + } + return true; + case _(InterruptACK): + vsnd->InterruptStatus &= ~value; + return true; + case _(Status): + virtio_snd_update_status(vsnd, value); + return true; + default: + /* Invalid address which exceeded the range */ + if (!RANGE_CHECK(addr, _(Config), sizeof(virtio_snd_config_t))) + return false; + + /* Write configuration to the corresponding register */ + ((uint32_t *) PRIV(vsnd))[addr - _(Config)] = value; + + return true; + } +#undef _ +} +void virtio_snd_read(hart_t *vm, + virtio_snd_state_t *vsnd, + uint32_t addr, + uint8_t width, + uint32_t *value) +{ + switch (width) { + case RV_MEM_LW: + if (!virtio_snd_reg_read(vsnd, addr >> 2, value)) + vm_set_exception(vm, RV_EXC_LOAD_FAULT, vm->exc_val); + break; + case RV_MEM_LBU: + case RV_MEM_LB: + case RV_MEM_LHU: + case RV_MEM_LH: + vm_set_exception(vm, RV_EXC_LOAD_MISALIGN, vm->exc_val); + return; + + default: + vm_set_exception(vm, RV_EXC_ILLEGAL_INSN, 0); + return; + } +} +void virtio_snd_write(hart_t *vm, + virtio_snd_state_t *vsnd, + uint32_t addr, + uint8_t width, + uint32_t value) +{ + switch (width) { + case RV_MEM_SW: + if (!virtio_snd_reg_write(vsnd, addr >> 2, value)) + vm_set_exception(vm, RV_EXC_STORE_FAULT, vm->exc_val); + break; + case RV_MEM_SB: + case RV_MEM_SH: + vm_set_exception(vm, RV_EXC_STORE_MISALIGN, vm->exc_val); + return; + default: + vm_set_exception(vm, RV_EXC_ILLEGAL_INSN, 0); + return; + } +} + +bool virtio_snd_init(virtio_snd_state_t *vsnd) +{ + if (vsnd_dev_cnt >= VSND_DEV_CNT_MAX) { + fprintf(stderr, + "Exceeded the number of virtio-snd devices that can be " + "allocated.\n"); + return false; + } + + /* Allocate the memory of private member. */ + vsnd->priv = &vsnd_configs[vsnd_dev_cnt++]; + + audio_host = CNFAInit(NULL, "semu-virtio-snd", virtio_snd_cb, 44100, 0, 1, + 0, 1024, NULL, NULL, &guest_playing); + + if (!audio_host) { + fprintf(stderr, "virtio-snd driver initialization failed.\n"); + return false; + } + + return true; +}