From ecb2614c4e9d9b56e50d2eb64273bb0d4d5520b3 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. For ALSA, the debug is enabled and will be removed once this commit is ready. Add description of descriptor chaining, yet need refactoring the description as well as code (exists uncertainty of the query struct). Succeed to initialize virtio-snd. Handle requests in control and TX queue, and print the address and length of each virtq element to check the validness of self-implement queue. For macOS, an experimental core audio library check is implemented. As the driver sends the PCM frames asynchronously, use a dedicated thread for receiving frames from driver. Need to let the thread become a thread pool so that we don't need to create the thread everytime when we try to receive frames from driver. As such, the pcm_release state is sent asynchronously, need to address this later. --- .github/workflows/main.yml | 1 + .gitmodules | 4 + Makefile | 48 ++ README.md | 5 +- cnfa | 1 + configs/buildroot.config | 3 + configs/linux.config | 10 +- device.h | 51 ++ feature.h | 5 + main.c | 35 ++ minimal.dts | 8 + mk/check-libs.mk | 35 ++ queue.h | 98 ++++ virtio-snd.c | 1106 ++++++++++++++++++++++++++++++++++++ 14 files changed, 1407 insertions(+), 3 deletions(-) create mode 100644 .gitmodules create mode 160000 cnfa create mode 100644 mk/check-libs.mk create mode 100644 queue.h create mode 100644 virtio-snd.c diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 673d1a6..d431134 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 a21e781..81bc00c 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 := -lm + # virtio-blk ENABLE_VIRTIOBLK ?= 1 $(call set-feature, VIRTIOBLK) @@ -43,6 +46,51 @@ ifeq ($(call has, VIRTIONET), 1) OBJS_EXTRA += netdev.o endif +# virtio-snd +ENABLE_VIRTIOSND ?= 1 +ifneq ($(UNAME_S),$(filter $(UNAME_S),Linux Darwin)) + ENABLE_VIRTIOSND := 0 +endif + +# Check ALSA installation +ifeq ($(UNAME_S),Linux) + ifeq (0, $(call check-alsa)) + $(warning No libasound installed. Check libasound in advance.) + ENABLE_VIRTIOSND := 0 + endif +endif +# Check core audio installation +ifeq ($(UNAME_S),Darwin) + ifeq (0, $(call check-coreaudio)) + $(warning No CoreAudio framework installed.) + ENABLE_VIRTIOSND := 0 + endif +endif +$(call set-feature, VIRTIOSND) +ifeq ($(call has, VIRTIOSND), 1) + OBJS_EXTRA += virtio-snd.o + + ifeq ($(UNAME_S),Linux) + LDFLAGS += -lasound -lpthread + else ifeq ($(UNAME_S),Darwin) + LDFLAGS += -framework AudioToolbox -lpthread + endif + 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..0e6c685 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,12 @@ 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_VIRTIO=y +CONFIG_SND_VERBOSE_PRINTK=y +CONFIG_SND_DEBUG=y +CONFIG_SND_DEBUG_VERBOSE=y # # HID support diff --git a/device.h b/device.h index 489cae6..5d2c65c 100644 --- a/device.h +++ b/device.h @@ -191,6 +191,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 { @@ -206,4 +253,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 39935f8..aa40a7d 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); @@ -591,6 +616,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; @@ -613,6 +643,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..4a46cb4 --- /dev/null +++ b/mk/check-libs.mk @@ -0,0 +1,35 @@ +# 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 + +# Create a mininal Core Audio program +define create-coreaudio-prog +echo '\ +#include \n\ +int main(){\n\ + AudioComponent comp;\n\ + comp = AudioComponentFindNext(NULL, &desc);\n\ + if (comp == NULL) exit (-1);\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 + +# Check Core Audio installation +define check-coreaudio +$(shell $(call create-coreaudio-prog) | $(CC) -x c -framework CoreAudio -o /dev/null > /dev/null 2> /dev/null - + && echo $$?) +endef diff --git a/queue.h b/queue.h new file mode 100644 index 0000000..8cd9b71 --- /dev/null +++ b/queue.h @@ -0,0 +1,98 @@ +#ifndef QUEUE_H +#define QUEUE_H + +#include + +#if defined(__GNUC__) +#define __QUEUE_HAVE_TYPEOF 1 +#endif + +/* Merely a doubly-linked list */ +struct queue_head { + struct queue_head *prev, *next; +}; + +#define QUEUE_HEAD(head) struct queue_head head = {&(head), &(head)} + +static inline void INIT_QUEUE_HEAD(struct queue_head *head) +{ + head->next = head; + head->prev = head; +} + +/* Push the node to the end of queue */ +static inline void queue_push(struct queue_head *node, struct queue_head *head) +{ + struct queue_head *prev = head->prev; + + prev->next = node; + node->next = head; + node->prev = prev; + head->prev = node; +} + +static inline int queue_empty(const struct queue_head *head) +{ + return (head->next == head); +} + +static inline void queue_del(struct queue_head *node) +{ + struct queue_head *next = node->next; + struct queue_head *prev = node->prev; + + next->prev = prev; + prev->next = next; + + node->prev = (struct queue_head *) (0x00100100); + node->next = (struct queue_head *) (0x00200200); +} + +static inline void queue_del_init(struct queue_head *node) +{ + queue_del(node); + INIT_QUEUE_HEAD(node); +} + +#ifndef container_of +#ifdef __QUEUE_HAVE_TYPEOF +#define container_of(ptr, type, member) \ + __extension__({ \ + const __typeof__(((type *) 0)->member) *__pmember = (ptr); \ + (type *) ((char *) __pmember - offsetof(type, member)); \ + }) +#else +#define container_of(ptr, type, member) \ + ((type *) ((char *) (ptr) -offsetof(type, member))) +#endif +#endif + +#define queue_entry(node, type, member) container_of(node, type, member) + +#define queue_first_entry(head, type, member) \ + queue_entry((head)->next, type, member) + +#define queue_last_entry(head, type, member) \ + queue_entry((head)->prev, type, member) + +#define queue_for_each(node, head) \ + for (node = (head)->next; node != (head); node = node->next) + +#ifdef __QUEUE_HAVE_TYPEOF +#define queue_for_each_entry(entry, head, member) \ + for (entry = queue_entry((head)->next, __typeof__(*entry), member); \ + &entry->member != (head); \ + entry = queue_entry(entry->member.next, __typeof__(*entry), member)) +#endif + +#define queue_for_each_safe(node, safe, head) \ + for (node = (head)->next, safe = node->next; node != (head); \ + node = safe, safe = node->next) + +#define queue_for_each_entry_safe(entry, safe, head, member) \ + for (entry = queue_entry((head)->next, __typeof__(*entry), member), \ + safe = queue_entry(entry->member.next, __typeof__(*entry), member); \ + &entry->member != (head); entry = safe, \ + safe = queue_entry(safe->member.next, __typeof__(*entry), member)) + +#endif diff --git a/virtio-snd.c b/virtio-snd.c new file mode 100644 index 0000000..ec05d4e --- /dev/null +++ b/virtio-snd.c @@ -0,0 +1,1106 @@ +#include +#include +#include +#include +#include + +#define CNFA_IMPLEMENTATION +#include "CNFA_sf.h" + +#include "device.h" +#include "queue.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) ((virtio_snd_config_t *) x->priv) + +enum { + VSND_QUEUE_CTRL = 0, + VSND_QUEUE_EVT = 1, + VSND_QUEUE_TX = 2, + VSND_QUEUE_RX = 3, +}; + +/* 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, + + /* jack event types */ + VIRTIO_SND_EVT_JACK_CONNECTED = 0x1000, + VIRTIO_SND_EVT_JACK_DISCONNECTED, + + /* PCM event types */ + VIRTIO_SND_EVT_PCM_PERIOD_ELAPSED = 0x1100, + VIRTIO_SND_EVT_PCM_XRUN, + + /* 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 stream features */ +enum { + VIRTIO_SND_PCM_F_SHMEM_HOST = 0, +}; + +/* 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; + uint32_t controls; +} 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; + +/* PCM I/O message header */ +typedef struct { + uint32_t stream_id; +} virtio_snd_pcm_xfer_t; + +/* PCM I/O message status */ +typedef struct { + uint32_t status; + uint32_t latency_bytes; +} virtio_snd_pcm_status_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; + +/* A queue to hold arbitrary number of PCM frames. */ +typedef struct { + void *buf; + struct queue_head q; +} virtio_snd_pcm_frame_t; +/* virtio-snd to hold the settings of each stream */ +typedef struct { + virtio_snd_jack_info_t j; + virtio_snd_pcm_info_t p; + virtio_snd_chmap_info_t c; + virtio_snd_pcm_set_params_t pp; + struct CNFADriver *audio_host; + bool is_guest_playing; + + // PCM frame buffer + virtio_snd_pcm_frame_t *pcm_frames; + struct queue_head pcm_frames_q; +} virtio_snd_prop_t; + +static virtio_snd_config_t vsnd_configs[VSND_DEV_CNT_MAX]; +static virtio_snd_prop_t vsnd_props[VSND_DEV_CNT_MAX] = { + [0 ... VSND_DEV_CNT_MAX - 1].pp.hdr.hdr.code = VIRTIO_SND_R_PCM_SET_PARAMS, +}; +static int vsnd_dev_cnt = 0; + +typedef struct { + int32_t guest_playing; + uint32_t stream_id; +} vsnd_stream_sel_t; +static vsnd_stream_sel_t v; + +static pthread_mutex_t virtio_snd_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t virtio_snd_ctrl_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t virtio_snd_tx_cond = PTHREAD_COND_INITIALIZER; +static pthread_cond_t virtio_snd_ctrl_cond = PTHREAD_COND_INITIALIZER; +static int tx_ev_notify; + +/* Forward declaration */ +static void virtio_snd_cb(struct CNFADriver *dev, + short *out, + short *in, + int framesp, + int framesr); + + +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; + uint32_t jacks = PRIV(vsnd)->jacks; + uint32_t streams = PRIV(vsnd)->streams; + uint32_t chmaps = PRIV(vsnd)->chmaps; + uint32_t controls = PRIV(vsnd)->controls; + memset(vsnd, 0, sizeof(*vsnd)); + vsnd->ram = ram; + vsnd->priv = priv; + PRIV(vsnd)->jacks = jacks; + PRIV(vsnd)->streams = streams; + PRIV(vsnd)->chmaps = chmaps; + PRIV(vsnd)->controls = controls; +} + +static void virtio_snd_read_jack_info_handler( + virtio_snd_jack_info_t *info, + const virtio_snd_query_info_t *query, + uint32_t *plen) +{ + uint32_t cnt = query->count; + for (uint32_t i = 0; i < cnt; 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)); + + vsnd_props[i].j.hdr.hda_fn_nid = 0; + vsnd_props[i].j.features = 0; + vsnd_props[i].j.hda_reg_defconf = 0; + vsnd_props[i].j.hda_reg_caps = 0; + vsnd_props[i].j.connected = 1; + memset(&vsnd_props[i].j.padding, 0, sizeof(vsnd_props[i].j.padding)); + } + + *plen = cnt * sizeof(*info); +} + +static void virtio_snd_read_pcm_info_handler( + virtio_snd_pcm_info_t *info, + const virtio_snd_query_info_t *query, + uint32_t *plen) +{ + uint32_t cnt = query->count; + for (uint32_t i = 0; i < cnt; i++) { + info[i].hdr.hda_fn_nid = 0; + info[i].features = 0; + info[i].formats = (1 << VIRTIO_SND_PCM_FMT_S16); + info[i].rates = (1 << VIRTIO_SND_PCM_RATE_44100); + info[i].direction = VIRTIO_SND_D_OUTPUT; + info[i].channels_min = 1; + info[i].channels_max = 1; + memset(&info[i].padding, 0, sizeof(info[i].padding)); + + vsnd_props[i].p.hdr.hda_fn_nid = 0; + vsnd_props[i].p.features = 0; + vsnd_props[i].p.formats = (1 << VIRTIO_SND_PCM_FMT_S16); + vsnd_props[i].p.rates = (1 << VIRTIO_SND_PCM_RATE_44100); + vsnd_props[i].p.direction = VIRTIO_SND_D_OUTPUT; + vsnd_props[i].p.channels_min = 1; + vsnd_props[i].p.channels_max = 1; + memset(&vsnd_props[i].p.padding, 0, sizeof(vsnd_props[i].p.padding)); + } + *plen = cnt * sizeof(*info); +} + +static void virtio_snd_read_chmap_info_handler( + virtio_snd_chmap_info_t *info, + const virtio_snd_query_info_t *query, + uint32_t *plen) +{ + uint32_t cnt = query->count; + for (uint32_t i = 0; i < cnt; i++) { + info[i].hdr.hda_fn_nid = 0; + info[i].direction = VIRTIO_SND_D_OUTPUT; + info[i].channels = 1; + info[i].positions[0] = VIRTIO_SND_CHMAP_MONO; + + vsnd_props[i].c.hdr.hda_fn_nid = 0; + vsnd_props[i].c.direction = VIRTIO_SND_D_OUTPUT; + vsnd_props[i].c.channels = 1; + vsnd_props[i].c.positions[0] = VIRTIO_SND_CHMAP_MONO; + } + *plen = cnt * sizeof(info); +} + +static void virtio_snd_read_pcm_set_params( + const virtio_snd_pcm_set_params_t *query, + uint32_t *plen) +{ + const virtio_snd_pcm_set_params_t *request = query; + uint32_t id = request->hdr.stream_id; + /* TODO: detect current state of stream */ + uint32_t code = vsnd_props[id].pp.hdr.hdr.code; + if (code != VIRTIO_SND_R_PCM_RELEASE && + code != VIRTIO_SND_R_PCM_SET_PARAMS && + code != VIRTIO_SND_R_PCM_PREPARE) { + fprintf(stderr, + "virtio_snd_pcm_set_params with invalid previous state %#08x\n", + code); + return; + } + /* TODO: check the valiability of buffer_bytes, period_bytes, channel_min, + * and channel_max */ + vsnd_props[id].pp.hdr.hdr.code = VIRTIO_SND_R_PCM_SET_PARAMS; + vsnd_props[id].pp.buffer_bytes = request->buffer_bytes; + vsnd_props[id].pp.period_bytes = request->period_bytes; + vsnd_props[id].pp.features = request->features; + vsnd_props[id].pp.channels = request->channels; + vsnd_props[id].pp.format = request->format; + vsnd_props[id].pp.rate = request->rate; + vsnd_props[id].pp.padding = request->padding; + + *plen = 0; + fprintf(stderr, "virtio_snd_read_pcm_set_params\n"); +} + +static void virtio_snd_read_pcm_prepare(const virtio_snd_pcm_hdr_t *query, + uint32_t *plen) +{ + const virtio_snd_pcm_hdr_t *request = query; + uint32_t stream_id = request->stream_id; + uint32_t code = vsnd_props[stream_id].pp.hdr.hdr.code; + if (code != VIRTIO_SND_R_PCM_RELEASE && + code != VIRTIO_SND_R_PCM_SET_PARAMS && + code != VIRTIO_SND_R_PCM_PREPARE) { + fprintf( + stderr, + "virtio_snd_read_pcm_prepare with invalid previous state %#08x\n", + code); + return; + } + + /* Control the callback to prepare the buffer */ + /* TODO: add lock to avoid race condition */ + // pthread_mutex_lock(&virtio_snd_mutex); + v.guest_playing = 0; + v.stream_id = stream_id; + // pthread_mutex_unlock(&virtio_snd_mutex); + vsnd_props[stream_id].pp.hdr.hdr.code = VIRTIO_SND_R_PCM_PREPARE; + vsnd_props[stream_id].audio_host = + CNFAInit(NULL, "semu-virtio-snd", virtio_snd_cb, 44100, 0, 1, 0, + vsnd_props[stream_id].pp.buffer_bytes, NULL, NULL, &v); + INIT_QUEUE_HEAD(&(vsnd_props[stream_id].pcm_frames_q)); + + *plen = 0; + fprintf(stderr, "virtio_snd_read_pcm_prepare\n"); +} + +static void virtio_snd_read_pcm_start(const virtio_snd_pcm_hdr_t *query, + uint32_t *plen) +{ + const virtio_snd_pcm_hdr_t *request = query; + uint32_t stream_id = request->stream_id; + uint32_t code = vsnd_props[stream_id].pp.hdr.hdr.code; + if (code != VIRTIO_SND_R_PCM_PREPARE && code != VIRTIO_SND_R_PCM_STOP) { + fprintf( + stderr, + "virtio_snd_read_pcm_start with previous invalide state %#08x\n", + code); + return; + } + + /* Control the callback to start playing */ + /* TODO: add lock to avoid race condition */ + vsnd_props[stream_id].pp.hdr.hdr.code = VIRTIO_SND_R_PCM_START; + v.guest_playing++; + pthread_cond_signal(&virtio_snd_ctrl_cond); + + *plen = 0; + fprintf(stderr, "virtio_snd_read_pcm_start\n"); +} + +static void virtio_snd_read_pcm_stop(const virtio_snd_pcm_hdr_t *query, + uint32_t *plen) +{ + const virtio_snd_pcm_hdr_t *request = query; + uint32_t stream_id = request->stream_id; + uint32_t code = vsnd_props[stream_id].pp.hdr.hdr.code; + if (code != VIRTIO_SND_R_PCM_START) { + fprintf(stderr, + "virtio_snd_read_pcm_stop with previous invalide state %#08x\n", + code); + return; + } + + /* Control the callback to stop playing */ + /* TODO: add lock to avoid race condition */ + vsnd_props[stream_id].pp.hdr.hdr.code = VIRTIO_SND_R_PCM_STOP; + v.guest_playing--; + pthread_cond_signal(&virtio_snd_ctrl_cond); + + *plen = 0; + fprintf(stderr, "virtio_snd_read_pcm_stop\n"); +} + +static void virtio_snd_read_pcm_release(const virtio_snd_pcm_hdr_t *query, + uint32_t *plen) +{ + fprintf(stderr, "virtio_snd_read_pcm_release start\n"); + const virtio_snd_pcm_hdr_t *request = query; + uint32_t stream_id = request->stream_id; + uint32_t code = vsnd_props[stream_id].pp.hdr.hdr.code; + if (code != VIRTIO_SND_R_PCM_PREPARE && code != VIRTIO_SND_R_PCM_STOP) { + fprintf( + stderr, + "virtio_snd_read_pcm_release with previous invalide state %#08x\n", + code); + return; + } + + vsnd_props[stream_id].pp.hdr.hdr.code = VIRTIO_SND_R_PCM_RELEASE; + CNFAClose(vsnd_props[stream_id].audio_host); + fprintf(stderr, "pass CNFAclose\n"); + + /* Tear down the PCM frames. */ + virtio_snd_pcm_frame_t *t = NULL; + struct queue_head *frame_q = &(vsnd_props[stream_id].pcm_frames_q); + virtio_snd_pcm_frame_t *frame = vsnd_props[stream_id].pcm_frames; + int idx = 0; + queue_for_each_entry_safe(frame, t, frame_q, q) + { + free(frame->buf); + queue_del(&frame->q); + free(frame); + + fprintf(stderr, "tear down frame %d\n", idx); + idx++; + } + assert(queue_empty(frame_q)); + + *plen = 0; + fprintf(stderr, "virtio_snd_read_pcm_release\n"); +} + +double omega = 0.0; +int totalframesp = 0; +int totalframesr = 0; +static void virtio_snd_cb(struct CNFADriver *dev, + short *out, + short *in, + int framesp, + int framesr) +{ + (void) in; /* reversed for future recording use */ +#if 0 + /* TODO: apply lock on guest_playing */ + vsnd_stream_sel_t *v_ptr = (vsnd_stream_sel_t *) dev->opaque; + bool playing = v_ptr->guest_playing; + uint32_t id = v_ptr->stream_id; + int output_channels = dev->channelsPlay; + int output_buf_sz = framesp * output_channels; + + if (!(playing)) { + memset(out, 0, sizeof(short) * output_buf_sz); + return; + } + + memcpy(out, vsnd_props[id].buf, output_buf_sz); +#endif + vsnd_stream_sel_t *v_ptr = (vsnd_stream_sel_t *) dev->opaque; + + pthread_mutex_lock(&virtio_snd_ctrl_mutex); + + while (v_ptr->guest_playing <= 0) { + fprintf(stderr, "wait ctrl cond\n"); + pthread_cond_wait(&virtio_snd_ctrl_cond, &virtio_snd_ctrl_mutex); + } + + v_ptr->guest_playing--; + + totalframesr += framesr; + totalframesp += framesp; + + fprintf(stderr, "start to play\n"); + int channels = dev->channelsPlay; + for (int i = 0; i < framesp; i++) { + // Shift phase, so we run at 440 Hz (A4) + omega += (3.14159 * 2 * 440.) / dev->spsPlay; + + // Make the 440 Hz tone at 10% volume and convert to short. + short value = sin(omega) * 0.1 * 32767; + + int c; + for (c = 0; c < channels; c++) { + *(out++) = value; + } + } + fprintf(stderr, "totalframesp %d totalframesr %d\n", totalframesp, + totalframesr); + + pthread_mutex_unlock(&virtio_snd_ctrl_mutex); +} + +#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: clarify the use of the third descriptor */ + /* virtio-snd command uses at most 3 virtqueue descriptors, where + * the first descriptor contains: + * struct virtio_snd_hdr hdr (for request) + * the second descriptors contains: + * struct virtio_snd_hdr hdr (for response) + * if needed, the third descriptors contains: + * (response payload structure) + */ + 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[i].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 void *query = (void *) ((uintptr_t) vsnd->ram + vq_desc[0].addr); + + /* TODO: let the program use this variable selectively according to + * the type. + */ + /* As there are plenty of structures for response payload, + * use a void pointer for generic type support. + */ + void *info = (void *) (uintptr_t) vsnd->ram + vq_desc[2].addr; + + /* Process the data */ + switch (type) { + case VIRTIO_SND_R_JACK_INFO: + virtio_snd_read_jack_info_handler(info, query, plen); + break; + case VIRTIO_SND_R_PCM_INFO: + virtio_snd_read_pcm_info_handler(info, query, plen); + break; + case VIRTIO_SND_R_CHMAP_INFO: + virtio_snd_read_chmap_info_handler(info, query, plen); + break; + case VIRTIO_SND_R_PCM_SET_PARAMS: + virtio_snd_read_pcm_set_params(query, plen); + break; + case VIRTIO_SND_R_PCM_PREPARE: + virtio_snd_read_pcm_prepare(query, plen); + break; + case VIRTIO_SND_R_PCM_RELEASE: + virtio_snd_read_pcm_release(query, plen); + break; + case VIRTIO_SND_R_PCM_START: + virtio_snd_read_pcm_start(query, plen); + break; + case VIRTIO_SND_R_PCM_STOP: + virtio_snd_read_pcm_stop(query, plen); + break; + default: + fprintf(stderr, "%d: unsupported virtio-snd operation!\n", type); + response->code = VIRTIO_SND_S_NOT_SUPP; + *plen = 0; + return -1; + } + + /* Return the device status */ + response->code = VIRTIO_SND_S_OK; + + return 0; +} + + +typedef struct { + struct virtq_desc vq_desc; + struct queue_head q; +} virtq_desc_queue_node_t; +static int virtio_snd_tx_desc_handler(virtio_snd_state_t *vsnd, + const virtio_snd_queue_t *queue, + uint32_t desc_idx, + uint32_t *plen) +{ + /* TODO: clarify the usage of the last descriptor. */ + /* virtio-snd TX uses arbitrary number of virtqueue descriptors. + * The first descritor descriptor contains: + * struct virtio_snd_hdr hdr (for request) + * Next, the payload consists of one or more descriptors + * representing PCM frames. + * Finally, the last descriptors contains: + * (response payload structure) + */ + virtq_desc_queue_node_t *node; + struct queue_head q; + INIT_QUEUE_HEAD(&q); + assert(queue_empty(&q)); + + /* Collect the descriptors */ + int cnt = 0; + for (;;) { + /* 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 */ + node = (virtq_desc_queue_node_t *) malloc(sizeof(*node)); + node->vq_desc.addr = desc[0]; + node->vq_desc.len = desc[2]; + node->vq_desc.flags = desc[3]; + queue_push(&node->q, &q); + desc_idx = desc[3] >> 16; /* vq_desc[desc_cnt].next */ + + cnt++; + + /* Leave the loop if next-flag is not set */ + if (!(desc[3] & VIRTIO_DESC_F_NEXT)) { + // fprintf(stderr, "index %" PRIu32 " no next flag\n", cnt); + break; + } + } + + int idx = 0; + uint32_t stream_id; + uintptr_t base = (uintptr_t) vsnd->ram; + struct queue_head *frame_q = &(vsnd_props[stream_id].pcm_frames_q); + + virtio_snd_pcm_frame_t *frame = vsnd_props[stream_id].pcm_frames; + uint32_t ret_len = 0; + queue_for_each_entry(node, &q, q) + { + uint32_t addr = node->vq_desc.addr; + uint32_t len = node->vq_desc.len; + // fprintf(stderr, "idx %d addr %" PRIu32 " len %" PRIu32 "\n", idx, + // addr, len); + if (idx == 0) { // the first descriptor + const virtio_snd_pcm_xfer_t *request = + (virtio_snd_pcm_xfer_t *) (base + addr); + stream_id = request->stream_id; + // fprintf(stderr, "stream_id %" PRIu32 "\n", stream_id); + goto early_continue; + } else if (idx == cnt - 1) { // the last descriptor + virtio_snd_pcm_status_t *response = + (virtio_snd_pcm_status_t *) (base + addr); + response->status = VIRTIO_SND_S_OK; + response->latency_bytes = ret_len; + *plen = sizeof(*response); + // fprintf(stderr, "TX response\n"); + goto early_continue; + } + + frame = (virtio_snd_pcm_frame_t *) malloc(sizeof(*frame)); + void *buf = (void *) (base + addr); + frame->buf = (void *) malloc(sizeof(frame->buf) * len); + memcpy(frame->buf, buf, len); + queue_push(&frame->q, frame_q); + ret_len += len; + + early_continue: + idx++; + } + + /* Tear down the descriptor list and free space. */ + idx = 0; + virtq_desc_queue_node_t *tmp = NULL; + queue_for_each_entry_safe(node, tmp, &q, q) + { + queue_del(&node->q); + free(node); + idx++; + } + + return 0; +} + +static void virtio_queue_notify_handler( + virtio_snd_state_t *vsnd, + int index, + int (*handler)(virtio_snd_state_t *, + const virtio_snd_queue_t *, + uint32_t, + uint32_t *)) +{ + 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 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 = handler(vsnd, queue, buffer_idx, &len); + // fprintf(stderr, "len: %" PRIu32 "\n", 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++; + + // fprintf(stderr, "last_avail %d new_avail %d\n", queue->last_avail, + // new_avail); + } + + /* 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)) { + // fprintf(stderr, "send interrupt\n"); + vsnd->InterruptStatus |= VIRTIO_INT__USED_RING; + } +} + +/* TX thread context + * TODO: need to let this thread become a daemon. + */ +static void *func(void *args) +{ + virtio_snd_state_t *vsnd = (virtio_snd_state_t *) args; + for (;;) { + // fprintf(stderr, "*** tx desc handler is called ***\n"); + pthread_mutex_lock(&virtio_snd_mutex); + while (tx_ev_notify <= 0) { + pthread_cond_wait(&virtio_snd_tx_cond, &virtio_snd_mutex); + // fprintf(stderr, "wait for cond\n"); + } + // fprintf(stderr, "*** start tx critical section ***\n"); + tx_ev_notify--; + // fprintf(stderr, "*** tx desc handler ***\n"); + virtio_queue_notify_handler(vsnd, 2, virtio_snd_tx_desc_handler); + // fprintf(stderr, "*** end tx cirtical section ***\n"); + pthread_mutex_unlock(&virtio_snd_mutex); + } + pthread_exit(NULL); +} + +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)) { + switch (value) { + case VSND_QUEUE_CTRL: + virtio_queue_notify_handler(vsnd, value, + virtio_snd_desc_handler); + break; + case VSND_QUEUE_TX: + tx_ev_notify++; + pthread_cond_signal(&virtio_snd_tx_cond); + break; + default: + fprintf(stderr, "value %d not supported\n", value); + return false; + } + } 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); + break; + + default: + vm_set_exception(vm, RV_EXC_ILLEGAL_INSN, 0); + break; + } +} +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); + break; + default: + vm_set_exception(vm, RV_EXC_ILLEGAL_INSN, 0); + break; + } +} + +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++]; + + PRIV(vsnd)->jacks = 1; + PRIV(vsnd)->streams = 1; + PRIV(vsnd)->chmaps = 1; + PRIV(vsnd)->controls = + 0; /* virtio-snd device does not support control elements */ + + tx_ev_notify = 0; + pthread_t tid; + if (pthread_create(&tid, NULL, func, vsnd) != 0) { + fprintf(stderr, "cannot create TX thread\n"); + return false; + } + + return true; +}