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; +}