From a67216b9fe06609cd570d5e40bfbf11616437450 Mon Sep 17 00:00:00 2001 From: Cuda-Chen Date: Fri, 9 Aug 2024 19:55:42 +0800 Subject: [PATCH] Enable ALSA debug in kernel Now gets "virtio_snd: probe of virtio2 failed with error -2" in dmesg. --- .gitmodules | 3 + Makefile | 21 +- cnfa | 1 + configs/buildroot.config | 3 + configs/linux.config | 98 ++++++- device.h | 50 ++++ feature.h | 5 + main.c | 35 +++ minimal.dts | 8 + virtio-snd.c | 566 +++++++++++++++++++++++++++++++++++++++ 10 files changed, 776 insertions(+), 14 deletions(-) create mode 100644 .gitmodules create mode 160000 cnfa create mode 100644 virtio-snd.c diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..9b0a9b3 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "cnfa"] + path = cnfa + url = https://github.com/cntools/cnfa diff --git a/Makefile b/Makefile index ceb397f..1a547bb 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,8 @@ OBJS_EXTRA := # command line option OPTS := +LDFLAGS := + # virtio-blk ENABLE_VIRTIOBLK ?= 1 $(call set-feature, VIRTIOBLK) @@ -41,8 +43,20 @@ ifeq ($(call has, VIRTIONET), 1) OBJS_EXTRA += virtio-net.o endif +# virtio-snd +ENABLE_VIRTIOSND ?= 1 +ifneq ($(UNAME_S),Linux) + ENABLE_VIRTIOSND := 0 +endif +$(call set-feature, VIRTIOSND) +ifeq ($(call has, VIRTIOSND), 1) + OBJS_EXTRA += virtio-snd.o + LDFLAGS += -lasound + CFLAGS += -Icnfa +endif + BIN = semu -all: $(BIN) minimal.dtb +all: cnfa_dep $(BIN) minimal.dtb OBJS := \ riscv.o \ @@ -56,6 +70,11 @@ OBJS := \ deps := $(OBJS:%.o=.%.o.d) +cnfa_dep: + rm cnfa/CNFA_sf.h + make -C cnfa os_generic.h + make -C cnfa CNFA_sf.h + $(BIN): $(OBJS) $(VECHO) " LD\t$@\n" $(Q)$(CC) -o $@ $^ $(LDFLAGS) 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..3e09da9 100644 --- a/configs/linux.config +++ b/configs/linux.config @@ -1,11 +1,15 @@ -CONFIG_CC_VERSION_TEXT="riscv32-buildroot-linux-gnu-gcc.br_real (Buildroot 2023.05.1) 12.3.0" +# +# Automatically generated file; DO NOT EDIT. +# Linux/riscv 6.1.107 Kernel Configuration +# +CONFIG_CC_VERSION_TEXT="riscv32-buildroot-linux-gnu-gcc.br_real (Buildroot 2024.05.2) 14.1.0" CONFIG_CC_IS_GCC=y -CONFIG_GCC_VERSION=120300 +CONFIG_GCC_VERSION=140100 CONFIG_CLANG_VERSION=0 CONFIG_AS_IS_GNU=y -CONFIG_AS_VERSION=23900 +CONFIG_AS_VERSION=24200 CONFIG_LD_IS_BFD=y -CONFIG_LD_VERSION=23900 +CONFIG_LD_VERSION=24200 CONFIG_LLD_VERSION=0 CONFIG_CC_CAN_LINK=y CONFIG_CC_CAN_LINK_STATIC=y @@ -21,7 +25,6 @@ CONFIG_THREAD_INFO_IN_TASK=y # # General setup # -CONFIG_BROKEN_ON_SMP=y CONFIG_INIT_ENV_ARG_LIMIT=32 # CONFIG_COMPILE_TEST is not set # CONFIG_WERROR is not set @@ -43,6 +46,7 @@ CONFIG_HAVE_ARCH_AUDITSYSCALL=y # CONFIG_GENERIC_IRQ_SHOW=y CONFIG_GENERIC_IRQ_SHOW_LEVEL=y +CONFIG_GENERIC_IRQ_EFFECTIVE_AFF_MASK=y CONFIG_HARDIRQS_SW_RESEND=y CONFIG_IRQ_DOMAIN=y CONFIG_IRQ_DOMAIN_HIERARCHY=y @@ -54,8 +58,12 @@ CONFIG_SPARSE_IRQ=y CONFIG_GENERIC_IRQ_MULTI_HANDLER=y CONFIG_ARCH_CLOCKSOURCE_INIT=y CONFIG_GENERIC_CLOCKEVENTS=y +CONFIG_ARCH_HAS_TICK_BROADCAST=y +CONFIG_GENERIC_CLOCKEVENTS_BROADCAST=y CONFIG_HAVE_POSIX_CPU_TIMERS_TASK_WORK=y CONFIG_POSIX_CPU_TIMERS_TASK_WORK=y +CONFIG_CONTEXT_TRACKING=y +CONFIG_CONTEXT_TRACKING_IDLE=y # # Timers subsystem @@ -92,18 +100,23 @@ CONFIG_TICK_CPU_ACCOUNTING=y # CONFIG_PSI is not set # end of CPU/Task time and stats accounting +CONFIG_CPU_ISOLATION=y + # # RCU Subsystem # -CONFIG_TINY_RCU=y +CONFIG_TREE_RCU=y # CONFIG_RCU_EXPERT is not set CONFIG_SRCU=y -CONFIG_TINY_SRCU=y +CONFIG_TREE_SRCU=y +CONFIG_RCU_STALL_COMMON=y +CONFIG_RCU_NEED_SEGCBLIST=y # end of RCU Subsystem # CONFIG_IKCONFIG is not set # CONFIG_IKHEADERS is not set CONFIG_LOG_BUF_SHIFT=16 +CONFIG_LOG_CPU_MAX_BUF_SHIFT=12 CONFIG_PRINTK_SAFE_LOG_BUF_SHIFT=12 # CONFIG_PRINTK_INDEX is not set CONFIG_GENERIC_SCHED_CLOCK=y @@ -114,7 +127,7 @@ CONFIG_GENERIC_SCHED_CLOCK=y # end of Scheduler features CONFIG_CC_IMPLICIT_FALLTHROUGH="-Wimplicit-fallthrough=5" -CONFIG_GCC11_NO_ARRAY_BOUNDS=y +CONFIG_GCC10_NO_ARRAY_BOUNDS=y CONFIG_CC_NO_ARRAY_BOUNDS=y # CONFIG_CGROUPS is not set # CONFIG_NAMESPACES is not set @@ -224,7 +237,10 @@ CONFIG_ARCH_RV32I=y CONFIG_CMODEL_MEDANY=y CONFIG_MODULE_SECTIONS=y CONFIG_SMP=y +CONFIG_NR_CPUS=32 +# CONFIG_HOTPLUG_CPU is not set CONFIG_TUNE_GENERIC=y +# CONFIG_NUMA is not set # CONFIG_RISCV_ISA_C is not set CONFIG_TOOLCHAIN_HAS_ZICBOM=y # CONFIG_RISCV_ISA_ZICBOM is not set @@ -243,6 +259,7 @@ CONFIG_HZ_250=y CONFIG_HZ=250 CONFIG_SCHED_HRTICK=y # CONFIG_RISCV_SBI_V01 is not set +# CONFIG_RISCV_BOOT_SPINWAIT is not set # CONFIG_KEXEC is not set # CONFIG_CRASH_DUMP is not set # end of Kernel features @@ -274,6 +291,7 @@ CONFIG_CC_HAVE_STACKPROTECTOR_TLS=y # end of CPU Power Management # CONFIG_VIRTUALIZATION is not set +CONFIG_CPU_MITIGATIONS=y # # General architecture-dependent options @@ -325,7 +343,7 @@ CONFIG_COMPAT_32BIT_TIME=y CONFIG_ARCH_OPTIONAL_KERNEL_RWX=y CONFIG_ARCH_OPTIONAL_KERNEL_RWX_DEFAULT=y CONFIG_ARCH_HAS_STRICT_KERNEL_RWX=y -CONFIG_STRICT_KERNEL_RWX=n +# CONFIG_STRICT_KERNEL_RWX is not set CONFIG_ARCH_HAS_STRICT_MODULE_RWX=y CONFIG_STRICT_MODULE_RWX=y # CONFIG_LOCK_EVENT_COUNTS is not set @@ -394,8 +412,13 @@ CONFIG_INLINE_READ_UNLOCK_IRQ=y CONFIG_INLINE_WRITE_UNLOCK=y CONFIG_INLINE_WRITE_UNLOCK_IRQ=y CONFIG_ARCH_SUPPORTS_ATOMIC_RMW=y +CONFIG_MUTEX_SPIN_ON_OWNER=y +CONFIG_RWSEM_SPIN_ON_OWNER=y +CONFIG_LOCK_SPIN_ON_OWNER=y CONFIG_ARCH_USE_QUEUED_RWLOCKS=y +CONFIG_QUEUED_RWLOCKS=y CONFIG_ARCH_HAS_MMIOWB=y +CONFIG_MMIOWB=y CONFIG_ARCH_HAS_NON_OVERLAPPING_ADDRESS_SPACE=y # @@ -425,6 +448,7 @@ CONFIG_SLAB_MERGE_DEFAULT=y # CONFIG_SLAB_FREELIST_RANDOM is not set # CONFIG_SLAB_FREELIST_HARDENED is not set # CONFIG_SLUB_STATS is not set +CONFIG_SLUB_CPU_PARTIAL=y # end of SLAB allocator options # CONFIG_SHUFFLE_PAGE_ALLOCATOR is not set @@ -438,10 +462,10 @@ CONFIG_COMPACTION=y CONFIG_COMPACT_UNEVICTABLE_DEFAULT=1 # CONFIG_PAGE_REPORTING is not set CONFIG_MIGRATION=y +CONFIG_PCP_BATCH_SCALE_MAX=5 # CONFIG_KSM is not set CONFIG_DEFAULT_MMAP_MIN_ADDR=4096 CONFIG_ARCH_WANT_GENERAL_HUGETLB=y -CONFIG_NEED_PER_CPU_KM=y # CONFIG_CMA is not set CONFIG_GENERIC_EARLY_IOREMAP=y # CONFIG_IDLE_PAGE_TRACKING is not set @@ -531,8 +555,14 @@ CONFIG_DEFAULT_TCP_CONG="cubic" # CONFIG_NET_L3_MASTER_DEV is not set # CONFIG_QRTR is not set # CONFIG_NET_NCSI is not set +CONFIG_PCPU_DEV_REFCNT=y +CONFIG_RPS=y +CONFIG_RFS_ACCEL=y +CONFIG_SOCK_RX_QUEUE_MAPPING=y +CONFIG_XPS=y CONFIG_NET_RX_BUSY_POLL=y CONFIG_BQL=y +CONFIG_NET_FLOW_LIMIT=y # # Network testing @@ -887,7 +917,6 @@ CONFIG_BCMA_POSSIBLE=y # CONFIG_MFD_MT6397 is not set # CONFIG_MFD_SM501 is not set CONFIG_MFD_SYSCON=y -# CONFIG_MFD_TI_AM335X_TSCADC is not set # CONFIG_MFD_TQMX86 is not set # end of Multifunction device drivers @@ -936,7 +965,43 @@ 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_TIMER=y +CONFIG_SND_PCM=y +CONFIG_SND_JACK=y +CONFIG_SND_JACK_INPUT_DEV=y +# CONFIG_SND_OSSEMUL is not set +CONFIG_SND_PCM_TIMER=y +# CONFIG_SND_HRTIMER is not set +# CONFIG_SND_DYNAMIC_MINORS is not set +CONFIG_SND_SUPPORT_OLD_API=y +CONFIG_SND_PROC_FS=y +CONFIG_SND_VERBOSE_PROCFS=y +# CONFIG_SND_VERBOSE_PRINTK is not set +CONFIG_SND_CTL_FAST_LOOKUP=y +CONFIG_SND_DEBUG=y +CONFIG_SND_DEBUG_VERBOSE=y +CONFIG_SND_PCM_XRUN_DEBUG=y +# CONFIG_SND_CTL_INPUT_VALIDATION is not set +# CONFIG_SND_CTL_DEBUG is not set +# CONFIG_SND_JACK_INJECTION_DEBUG is not set +# CONFIG_SND_SEQUENCER is not set +CONFIG_SND_DRIVERS=y +CONFIG_SND_DUMMY=y +CONFIG_SND_ALOOP=y +# CONFIG_SND_MTPAV is not set +# CONFIG_SND_SERIAL_U16550 is not set +# CONFIG_SND_MPU401 is not set + +# +# HD-Audio +# +# end of HD-Audio + +CONFIG_SND_HDA_PREALLOC_SIZE=64 +# CONFIG_SND_SOC is not set +CONFIG_SND_VIRTIO=y # # HID support @@ -1400,6 +1465,7 @@ CONFIG_CRYPTO_HASH2=y # CONFIG_CRYPTO_USER is not set CONFIG_CRYPTO_MANAGER_DISABLE_TESTS=y # CONFIG_CRYPTO_NULL is not set +# CONFIG_CRYPTO_PCRYPT is not set # CONFIG_CRYPTO_CRYPTD is not set # CONFIG_CRYPTO_AUTHENC is not set # CONFIG_CRYPTO_TEST is not set @@ -1579,12 +1645,15 @@ CONFIG_ZSTD_COMMON=y CONFIG_ZSTD_DECOMPRESS=y # CONFIG_XZ_DEC is not set CONFIG_DECOMPRESS_ZSTD=y +CONFIG_XARRAY_MULTI=y CONFIG_HAS_IOMEM=y CONFIG_HAS_IOPORT_MAP=y CONFIG_HAS_DMA=y CONFIG_DMA_DECLARE_COHERENT=y # CONFIG_DMA_API_DEBUG is not set # CONFIG_DMA_MAP_BENCHMARK is not set +# CONFIG_FORCE_NR_CPUS is not set +CONFIG_CPU_RMAP=y CONFIG_DQL=y CONFIG_NLATTR=y CONFIG_GENERIC_ATOMIC64=y @@ -1629,6 +1698,7 @@ CONFIG_DEBUG_KERNEL=y # Compile-time checks and compiler options # CONFIG_DEBUG_INFO=y +CONFIG_AS_HAS_NON_CONST_LEB128=y # CONFIG_DEBUG_INFO_NONE is not set # CONFIG_DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT is not set # CONFIG_DEBUG_INFO_DWARF4 is not set @@ -1681,7 +1751,6 @@ CONFIG_HAVE_KCSAN_COMPILER=y # CONFIG_SLUB_DEBUG is not set # CONFIG_PAGE_OWNER is not set # CONFIG_PAGE_POISONING is not set -# CONFIG_DEBUG_RODATA_TEST is not set CONFIG_ARCH_HAS_DEBUG_WX=y # CONFIG_DEBUG_WX is not set CONFIG_GENERIC_PTDUMP=y @@ -1698,6 +1767,7 @@ CONFIG_ARCH_HAS_DEBUG_VM_PGTABLE=y CONFIG_ARCH_HAS_DEBUG_VIRTUAL=y # CONFIG_DEBUG_VIRTUAL is not set # CONFIG_DEBUG_MEMORY_INIT is not set +# CONFIG_DEBUG_PER_CPU_MAPS is not set CONFIG_CC_HAS_KASAN_GENERIC=y CONFIG_CC_HAS_WORKING_NOSANITIZE_ADDRESS=y # end of Memory Debugging @@ -1768,6 +1838,8 @@ CONFIG_LOCK_DEBUGGING_SUPPORT=y # CONFIG_RCU_SCALE_TEST is not set # CONFIG_RCU_TORTURE_TEST is not set # CONFIG_RCU_REF_SCALE_TEST is not set +CONFIG_RCU_CPU_STALL_TIMEOUT=21 +CONFIG_RCU_EXP_CPU_STALL_TIMEOUT=0 # CONFIG_RCU_TRACE is not set # CONFIG_RCU_EQS_DEBUG is not set # end of RCU Debugging diff --git a/device.h b/device.h index 1bb7900..0707fa5 100644 --- a/device.h +++ b/device.h @@ -190,6 +190,52 @@ void clint_write(hart_t *vm, uint8_t width, uint32_t value); +#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[2]; + /* 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 + /* memory mapping */ typedef struct { @@ -205,4 +251,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 e17718a..8b201f7 100644 --- a/feature.h +++ b/feature.h @@ -12,5 +12,10 @@ #define SEMU_FEATUREVIRTIONET 1 #endif +/* virtio-snd */ +#ifndef SEMU_FEATUREVIRTIOSND +#define SEMU_FEATUREVIRTIOSND 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/virtio-snd.c b/virtio-snd.c new file mode 100644 index 0000000..cb56a40 --- /dev/null +++ b/virtio-snd.c @@ -0,0 +1,566 @@ +#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 VSND_QUEUE (vsnd->queues[vsnd->QueueSel]) + +#define PRIV(x) ((struct virtio_snd_config *) x->priv) + +enum { + VSND_FEATURES_0 = 0, + VSND_FEATURES_1, +}; + +enum { + VIRTIO_SND_R_JACK_INFO = 1, + VIRTIO_SND_R_PCM_INFO = 0x0100, + VIRTIO_SND_R_CHMAP_INFO = 0x0200, + VIRTIO_SND_S_OK = 0x8000, + VIRTIO_SND_S_BAD_MSG, + VIRTIO_SND_S_NOT_SUPP, + VIRTIO_SND_S_IO_ERR, +}; + +enum { + VIRTIO_SND_PCM_RATE_5512 = 0, + VIRTIO_SND_PCM_RATE_8000, + VIRTIO_SND_PCM_RATE_11025, + VIRTIO_SND_PCM_RATE_16000, + VIRTIO_SND_PCM_RATE_22050, + VIRTIO_SND_PCM_RATE_32000, + VIRTIO_SND_PCM_RATE_44100, + VIRTIO_SND_PCM_RATE_48000, + VIRTIO_SND_PCM_RATE_64000, + VIRTIO_SND_PCM_RATE_88200, + VIRTIO_SND_PCM_RATE_96000, + VIRTIO_SND_PCM_RATE_176400, + VIRTIO_SND_PCM_RATE_192000, + VIRTIO_SND_PCM_RATE_384000 +}; + + +/* 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 */ +}; + +enum { VIRTIO_SND_D_OUTPUT = 0, VIRTIO_SND_D_INPUT }; + + +typedef struct { + uint32_t jacks; + uint32_t streams; + uint32_t chmaps; +} virtio_snd_config; + +typedef struct { + uint32_t code; +} virtio_snd_hdr; + +typedef struct { + uint32_t hda_fn_nid; +} virtio_snd_info; + +typedef struct { + virtio_snd_hdr hdr; + uint32_t start_id; + uint32_t count; + uint32_t size; // size of a singly reply +} virtio_snd_query_info; + +typedef struct { + virtio_snd_info 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; + +typedef struct { + virtio_snd_info 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; + +#define VIRTIO_SND_CHMAP_MAX_SIZE 18 + +typedef struct { + virtio_snd_info hdr; + uint8_t direction; + uint8_t channels; + uint8_t positions[VIRTIO_SND_CHMAP_MAX_SIZE]; +} virtio_snd_chmap_info; + +static virtio_snd_config 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; +} + +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; + + 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 cnfa_audio_callback(struct CNFADriver *dev, + short *out, + short *in, + int framesp, + int framesr) +{ + if (framesp > 0) { // playback + if (guest_playing) { + } else { + memset(out, 0, framesp * 2); + } + } else { + printf("cnfa_audio_callback(%p, %p, %p, %d, %d)\n", dev, out, in, + framesp, framesr); + } +} + +static int virtio_snd_desc_handler(virtio_snd_state_t *vsnd, + const virtio_snd_queue_t *queue, + uint32_t desc_idx, + uint32_t *plen) +{ + struct virtq_desc vq_desc[3]; + + /* Collect the descriptors */ + for (int i = 0; i < 3; 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 */ + } + + /* The next flag for the first and second descriptors should be set, + * whereas for the third descriptor is should not be set + */ + if (!(vq_desc[0].flags & VIRTIO_DESC_F_NEXT) || + !(vq_desc[1].flags & VIRTIO_DESC_F_NEXT) || + (vq_desc[2].flags & VIRTIO_DESC_F_NEXT)) { + /* since the descriptor list is abnormal, we don't write the status + * back here */ + virtio_snd_set_fail(vsnd); + return -1; + } + + /* Process the header */ + const virtio_snd_query_info *query = + (virtio_snd_query_info *) ((uintptr_t) vsnd->ram + vq_desc[0].addr); + uint32_t type = query->hdr.code; + uint8_t *status = (uint8_t *) ((uintptr_t) vsnd->ram + vq_desc[2].addr); + + /* Process the data */ + switch (type) { + case VIRTIO_SND_R_JACK_INFO: { + virtio_snd_jack_info *info = (virtio_snd_jack_info *) (vq_desc[2].addr); + for (int 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)); + } + break; + } + case VIRTIO_SND_R_PCM_INFO: { + virtio_snd_pcm_info *info = (virtio_snd_pcm_info *) (vq_desc[2].addr); + for (int 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)); + } + break; + } + case VIRTIO_SND_R_CHMAP_INFO: { + virtio_snd_chmap_info *info = + (virtio_snd_chmap_info *) (vq_desc[2].addr); + for (int 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; + } + break; + } + default: + fprintf(stderr, "unsupported virtio-snd operation!\n"); + *status = VIRTIO_SND_S_NOT_SUPP; + return -1; + } + + /* Return the device status */ + *status = VIRTIO_SND_S_OK; + *plen = vq_desc[1].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 = VSND_QUEUE.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))) + 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) + VSND_QUEUE.QueueNum = value; + else + virtio_snd_set_fail(vsnd); + return true; + case _(QueueReady): + VSND_QUEUE.ready = value & 1; + if (value & 1) + VSND_QUEUE.last_avail = vsnd->ram[VSND_QUEUE.QueueAvail] >> 16; + return true; + case _(QueueDescLow): + VSND_QUEUE.QueueDesc = vsnd_preprocess(vsnd, value); + return true; + case _(QueueDescHigh): + if (value) + virtio_snd_set_fail(vsnd); + return true; + case _(QueueDriverLow): + VSND_QUEUE.QueueAvail = vsnd_preprocess(vsnd, value); + return true; + case _(QueueDriverHigh): + if (value) + virtio_snd_set_fail(vsnd); + return true; + case _(QueueDeviceLow): + VSND_QUEUE.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))) + 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", cnfa_audio_callback, 44100, + 0, 1, 0, 1024, NULL, NULL, NULL); + + return true; +}