From bf05d5dca5cf588b9b551a9479db3230a23750f6 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. --- CNFA_sf.h | 2130 ++++++++++++++++++++++++++++++++++++++ Makefile | 14 + configs/buildroot.config | 3 + configs/linux.config | 98 +- device.h | 50 + feature.h | 5 + main.c | 38 +- minimal.dts | 8 + os_generic.h | 514 +++++++++ virtio-snd.c | 564 ++++++++++ 10 files changed, 3409 insertions(+), 15 deletions(-) create mode 100644 CNFA_sf.h create mode 100644 os_generic.h create mode 100644 virtio-snd.c diff --git a/CNFA_sf.h b/CNFA_sf.h new file mode 100644 index 0000000..94ef5ab --- /dev/null +++ b/CNFA_sf.h @@ -0,0 +1,2130 @@ +// This file was automatically generated by Makefile at +// https://github.com/cnlohr/cnfa Generated from files git hash +// a24b0f84ff7bf634217b21347c299bade7578d05 on Fri May 17 12:13:03 AM PDT 2024 +// (This is not the git hash of this file) Copyright <>< 2010-2020 Charles Lohr +// (And other authors as cited) CNFA is licensed under the MIT/x11, ColorChord +// or NewBSD Licenses. You choose. +// +// CN's Platform-agnostic, foundational sound driver subsystem. +// Easily output and input sound on a variety of platforms. +// +// Options: +// * #define CNFA_IMPLEMENTATION before this header and it will build all +// definitions in. +// + + +#ifndef _CNFA_H +#define _CNFA_H + + + +// this #define is per-platform. For instance on Linux, you have ALSA, Pulse +// and null +#define MAX_CNFA_DRIVERS 4 + +struct CNFADriver; + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef BUILD_DLL +#ifdef WINDOWS +#define DllExport __declspec(dllexport) +#else +#define DllExport extern +#endif +#else +#define DllExport +#endif + +// NOTE: Some drivers have synchronous duplex mode, other drivers will use two +// different callbacks. If ether is unavailable, it will be NULL. I.e. if `out` +// is null, only use in to read. If in is null, only place samples in out. +typedef void (*CNFACBType)(struct CNFADriver *sd, + short *out, + short *in, + int framesp, + int framesr); + +typedef void *(CNFAInitFn) (CNFACBType cb, + const char *your_name, + int reqSPSPlay, + int reqSPSRec, + int reqChannelsPlay, + int reqChannelsRec, + int sugBufferSize, + const char *outputSelect, + const char *inputSelect, + void *opaque); + +struct CNFADriver { + void (*CloseFn)(void *object); + int (*StateFn)(void *object); + CNFACBType callback; + short channelsPlay; + short channelsRec; + int spsPlay; + int spsRec; + void *opaque; + + // More fields may exist on a per-sound-driver basis +}; + +// Accepts: +// If DriverName = 0 or empty, will try to find best driver. +// +// our_source_name is an optional argument, but on some platforms controls the +// name of your endpoint. reqSPSPlay = 44100 is guaranteed on many platforms. +// reqSPSRec = 44100 is guaranteed on many platforms. +// NOTE: Some platforms do not allow SPS play and REC to deviate from each +// other. +// reqChannelsRec = 1 or 2 guaranteed on many platforms. +// reqChannelsPlay = 1 or 2 guaranteedon many platforms. NOTE: Some systems +// require ChannelsPlay == ChannelsRec! sugBufferSize = No promises. +// outputSelect = No standardization, NULL is OK for default. +// inputSelect = No standardization, NULL is OK for default. + +DllExport struct CNFADriver *CNFAInit(const char *driver_name, + const char *your_name, + CNFACBType cb, + int reqSPSPlay, + int reqSPSRec, + int reqChannelsPlay, + int reqChannelsRec, + int sugBufferSize, + const char *outputSelect, + const char *inputSelect, + void *opaque); + +DllExport int CNFAState( + struct CNFADriver *cnfaobject); // returns bitmask. 1 if mic recording, 2 + // if play back running, 3 if both running. +DllExport void CNFAClose(struct CNFADriver *cnfaobject); + + +// Called by various sound drivers. Notice priority must be greater than 0. +// Priority of 0 or less will not register. This is an internal function. +// Applications shouldnot call it. +void RegCNFADriver(int priority, const char *name, CNFAInitFn *fn); + +#if defined(_MSC_VER) && !defined(__clang__) +#define REGISTER_CNFA(cnfadriver, priority, name, function) \ + void REGISTER##cnfadriver() \ + { \ + RegCNFADriver(priority, name, function); \ + } +#else +#define REGISTER_CNFA(cnfadriver, priority, name, function) \ + void __attribute__((constructor)) REGISTER##cnfadriver() \ + { \ + RegCNFADriver(priority, name, function); \ + } +#endif + +#if defined(WINDOWS) || defined(__WINDOWS__) || defined(_WINDOWS) || \ + defined(_WIN32) || defined(_WIN64) || defined(WIN32) || defined(WIN64) || \ + defined(__WIN32__) || defined(__CYGWIN__) || defined(__MINGW32__) || \ + defined(__MINGW64__) || defined(__TOS_WIN__) +#define CNFA_WINDOWS 1 +#elif defined(ANDROID) || defined(__android__) || defined(ANDROID) +#define CNFA_ANDROID 1 +#elif defined(__NetBSD__) || defined(__NetBSD) || defined(__sun) || defined(sun) +#define CNFA_SUN 1 +#elif defined(__linux) || defined(__linux__) || defined(linux) || \ + defined(__LINUX__) +#define CNFA_LINUX 1 +#endif + +#if defined(PULSEAUDIO) +#define CNFA_PULSE 1 +#endif + +#ifdef __TINYC__ +#ifndef TCC +#define TCC +#endif +#endif + +#ifdef CNFA_IMPLEMENTATION +// Copyright <>< 2010-2020 Charles Lohr (And other authors as cited) +// CNFA is licensed under the MIT/x11, ColorChord or NewBSD Licenses. You +// choose. + +#ifndef _CNFA_C +#define _CNFA_C + +#include +#include +#include + +#if defined(_MSC_VER) +#if CNFA_WINDOWS +#ifndef strdup +#define strdup _strdup +#endif +#endif +#endif + +static CNFAInitFn *CNFADrivers[MAX_CNFA_DRIVERS]; +static char *CNFADriverNames[MAX_CNFA_DRIVERS]; +static int CNFADriverPriorities[MAX_CNFA_DRIVERS]; + +void RegCNFADriver(int priority, const char *name, CNFAInitFn *fn) +{ + int j; + + if (priority <= 0) { + return; + } + + printf("[CNFA] Registering Driver: %s\n", name); + + for (j = MAX_CNFA_DRIVERS - 1; j >= 0; j--) { + // Cruise along, find location to insert + if (j > 0 && + (!CNFADrivers[j - 1] || CNFADriverPriorities[j - 1] < priority)) { + CNFADrivers[j] = CNFADrivers[j - 1]; + CNFADriverNames[j] = CNFADriverNames[j - 1]; + CNFADriverPriorities[j] = CNFADriverPriorities[j - 1]; + } else { + CNFADrivers[j] = fn; + CNFADriverNames[j] = strdup(name); + CNFADriverPriorities[j] = priority; + break; + } + } +} + +struct CNFADriver *CNFAInit(const char *driver_name, + const char *your_name, + CNFACBType cb, + int reqSPSPlay, + int reqSPSRec, + int reqChannelsPlay, + int reqChannelsRec, + int sugBufferSize, + const char *outputSelect, + const char *inputSelect, + void *opaque) +{ +#if CNFA_ANDROID + // Android can't run static-time code. + void REGISTERAndroidCNFA(); + REGISTERAndroidCNFA(); +#endif + + int i; + struct CNFADriver *ret = 0; + int minprio = 0; + CNFAInitFn *bestinit = 0; + if (driver_name == 0 || strlen(driver_name) == 0) { + // Search for a driver. + for (i = 0; i < MAX_CNFA_DRIVERS; i++) { + if (CNFADrivers[i] == 0) { + break; + } + if (CNFADriverPriorities[i] > minprio) { + minprio = CNFADriverPriorities[i]; + bestinit = CNFADrivers[i]; + } + } + if (bestinit) { + ret = (struct CNFADriver *) bestinit( + cb, your_name, reqSPSPlay, reqSPSRec, reqChannelsPlay, + reqChannelsRec, sugBufferSize, outputSelect, inputSelect, + opaque); + } + if (ret) { + return ret; + } + } else { + for (i = 0; i < MAX_CNFA_DRIVERS; i++) { + if (CNFADrivers[i] == 0) { + break; + } + if (strcmp(CNFADriverNames[i], driver_name) == 0) { + return (struct CNFADriver *) CNFADrivers[i]( + cb, your_name, reqSPSPlay, reqSPSRec, reqChannelsPlay, + reqChannelsRec, sugBufferSize, outputSelect, inputSelect, + opaque); + } + } + } + printf("CNFA Driver not found.\n"); + return 0; +} + +int CNFAState(struct CNFADriver *cnfaobject) +{ + if (cnfaobject) { + return cnfaobject->StateFn(cnfaobject); + } + return -1; +} + +void CNFAClose(struct CNFADriver *cnfaobject) +{ + if (cnfaobject) { + cnfaobject->CloseFn(cnfaobject); + } +} + +#endif + + + +// Copyright 2015-2020 <>< Charles Lohr under the ColorChord License. + +#include +#include "os_generic.h" + +struct CNFADriverNull { + void (*CloseFn)(void *object); + int (*StateFn)(void *object); + CNFACBType callback; + short channelsPlay; + short channelsRec; + int spsPlay; + int spsRec; + void *opaque; +}; + +void CloseCNFANull(void *object) +{ + free(object); +} + +int CNFAStateNull(void *object) +{ + return 0; +} + + +void *InitCNFANull(CNFACBType cb, + const char *your_name, + int reqSPSPlay, + int reqSPSRec, + int reqChannelsPlay, + int reqChannelsRec, + int sugBufferSize, + const char *outputSelect, + const char *inputSelect, + void *opaque) +{ + struct CNFADriverNull *r = + (struct CNFADriverNull *) malloc(sizeof(struct CNFADriverNull)); + r->CloseFn = CloseCNFANull; + r->StateFn = CNFAStateNull; + r->callback = cb; + r->spsPlay = reqSPSPlay; + r->spsRec = reqSPSRec; + r->opaque = opaque; + r->channelsPlay = reqChannelsPlay; + r->channelsRec = reqChannelsRec; + return r; +} + + +REGISTER_CNFA(NullCNFA, 1, "NULL", InitCNFANull); + + +#if CNFA_WINDOWS +#include // This probably won't work on pre-NT systems +#include "CNFA_winmm.c" +#if VER_PRODUCTBUILD >= 7601 +#include "CNFA_wasapi.c" +#endif +#elif CNFA_ANDROID +// Copyright 2019-2020 <>< Charles Lohr under the ColorChord License, MIT/x11 +// license or NewBSD Licenses. +// This was originally to be used with rawdrawandroid + +#include +#include //Using android threads not os_generic threads. +#include +#include +#include +#include "os_generic.h" + +// based on +// https://github.com/android/ndk-samples/blob/master/native-audio/app/src/main/cpp/native-audio-jni.c + +// for native audio +#include +#include + +#include +#include +#include + +struct CNFADriverAndroid { + // Standard header - must remain. + void (*CloseFn)(void *object); + int (*StateFn)(void *object); + CNFACBType callback; + short channelsPlay; + short channelsRec; + int spsPlay; + int spsRec; + void *opaque; + + int buffsz; + + SLObjectItf engineObject; + SLEngineItf engineEngine; + SLRecordItf recorderRecord; + SLObjectItf recorderObject; + + SLPlayItf playerPlay; + SLObjectItf playerObject; + SLObjectItf outputMixObject; + + SLAndroidSimpleBufferQueueItf recorderBufferQueue; + SLAndroidSimpleBufferQueueItf playerBufferQueue; + // unsigned recorderSize; + + int recorderBufferSizeBytes; + int playerBufferSizeBytes; + short *recorderBuffer; + short *playerBuffer; +}; + + +void bqRecorderCallback(SLAndroidSimpleBufferQueueItf bq, void *context) +{ + struct CNFADriverAndroid *r = (struct CNFADriverAndroid *) context; + r->callback((struct CNFADriver *) r, 0, r->recorderBuffer, 0, + r->buffsz / (sizeof(short) * r->channelsRec)); + (*r->recorderBufferQueue) + ->Enqueue( + r->recorderBufferQueue, r->recorderBuffer, + r->recorderBufferSizeBytes / (r->channelsRec * sizeof(short))); +} + +void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context) +{ + struct CNFADriverAndroid *r = (struct CNFADriverAndroid *) context; + r->callback((struct CNFADriver *) r, r->playerBuffer, 0, + r->buffsz / (sizeof(short) * r->channelsPlay), 0); + (*r->playerBufferQueue) + ->Enqueue(r->playerBufferQueue, r->playerBuffer, + r->playerBufferSizeBytes / (r->channelsPlay * sizeof(short))); +} + +static struct CNFADriverAndroid *InitAndroidDriver(struct CNFADriverAndroid *r) +{ + SLresult result; + printf("Starting InitAndroidDriver\n"); + + // create engine + result = slCreateEngine(&r->engineObject, 0, NULL, 0, NULL, NULL); + assert(SL_RESULT_SUCCESS == result); + (void) result; + + // realize the engine + result = (*r->engineObject)->Realize(r->engineObject, SL_BOOLEAN_FALSE); + assert(SL_RESULT_SUCCESS == result); + (void) result; + + // get the engine interface, which is needed in order to create other + // objects + result = + (*r->engineObject) + ->GetInterface(r->engineObject, SL_IID_ENGINE, &r->engineEngine); + assert(SL_RESULT_SUCCESS == result); + (void) result; + + + /////////////////////////////////////////////////////////////////////////////////////////////////////// + if (r->channelsPlay) { + printf("create output mix"); + + SLDataFormat_PCM format_pcm = { + SL_DATAFORMAT_PCM, + r->channelsPlay, + r->spsPlay * 1000, + SL_PCMSAMPLEFORMAT_FIXED_16, + SL_PCMSAMPLEFORMAT_FIXED_16, + (r->channelsPlay == 1) ? SL_SPEAKER_FRONT_CENTER : 3, + SL_BYTEORDER_LITTLEENDIAN, + }; + SLDataLocator_AndroidSimpleBufferQueue loc_bq_play = { + SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2}; + SLDataSource source = {&loc_bq_play, &format_pcm}; + const SLInterfaceID ids[1] = {SL_IID_VOLUME}; + const SLboolean req[1] = {SL_BOOLEAN_TRUE}; + const SLInterfaceID id[1] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE}; + + result = (*r->engineEngine) + ->CreateOutputMix(r->engineEngine, &r->outputMixObject, 0, + ids, req); + result = (*r->outputMixObject) + ->Realize(r->outputMixObject, SL_BOOLEAN_FALSE); + + SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, + r->outputMixObject}; + SLDataSink sink; + sink.pFormat = &format_pcm; + sink.pLocator = &loc_outmix; + + // create audio player + result = (*r->engineEngine) + ->CreateAudioPlayer(r->engineEngine, &r->playerObject, + &source, &sink, 1, id, req); + if (SL_RESULT_SUCCESS != result) { + printf("CreateAudioPlayer failed\n"); + return JNI_FALSE; + } + + + // realize the audio player + result = (*r->playerObject)->Realize(r->playerObject, SL_BOOLEAN_FALSE); + if (SL_RESULT_SUCCESS != result) { + printf("AudioPlayer Realize failed: %d\n", result); + return JNI_FALSE; + } + + // get the player interface + result = + (*r->playerObject) + ->GetInterface(r->playerObject, SL_IID_PLAY, &r->playerPlay); + assert(SL_RESULT_SUCCESS == result); + (void) result; + + // get the buffer queue interface + result = + (*r->playerObject) + ->GetInterface(r->playerObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, + &r->playerBufferQueue); + assert(SL_RESULT_SUCCESS == result); + (void) result; + + // register callback on the buffer queue + result = + (*r->playerBufferQueue) + ->RegisterCallback(r->playerBufferQueue, bqPlayerCallback, r); + assert(SL_RESULT_SUCCESS == result); + (void) result; + + printf("===================== Player init ok.\n"); + } + + if (r->channelsRec) { + // configure audio source + SLDataLocator_IODevice loc_devI = {SL_DATALOCATOR_IODEVICE, + SL_IODEVICE_AUDIOINPUT, + SL_DEFAULTDEVICEID_AUDIOINPUT, NULL}; + SLDataSource audioSrc = {&loc_devI, NULL}; + + // configure audio sink + SLDataFormat_PCM format_pcm = { + SL_DATAFORMAT_PCM, + r->channelsRec, + r->spsRec * 1000, + SL_PCMSAMPLEFORMAT_FIXED_16, + SL_PCMSAMPLEFORMAT_FIXED_16, + (r->channelsRec == 1) ? SL_SPEAKER_FRONT_CENTER : 3, + SL_BYTEORDER_LITTLEENDIAN, + }; + SLDataLocator_AndroidSimpleBufferQueue loc_bq = { + SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2}; + SLDataSink audioSnk = {&loc_bq, &format_pcm}; + + + const SLInterfaceID id[1] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE}; + const SLboolean req[1] = {SL_BOOLEAN_TRUE}; + + result = (*r->engineEngine) + ->CreateAudioRecorder(r->engineEngine, &r->recorderObject, + &audioSrc, &audioSnk, 1, id, req); + if (SL_RESULT_SUCCESS != result) { + printf("CreateAudioRecorder failed\n"); + return JNI_FALSE; + } + + // realize the audio recorder + result = + (*r->recorderObject)->Realize(r->recorderObject, SL_BOOLEAN_FALSE); + if (SL_RESULT_SUCCESS != result) { + printf("AudioRecorder Realize failed: %d\n", result); + return JNI_FALSE; + } + + // get the record interface + result = (*r->recorderObject) + ->GetInterface(r->recorderObject, SL_IID_RECORD, + &r->recorderRecord); + assert(SL_RESULT_SUCCESS == result); + (void) result; + + // get the buffer queue interface + result = (*r->recorderObject) + ->GetInterface(r->recorderObject, + SL_IID_ANDROIDSIMPLEBUFFERQUEUE, + &r->recorderBufferQueue); + assert(SL_RESULT_SUCCESS == result); + (void) result; + + // register callback on the buffer queue + result = (*r->recorderBufferQueue) + ->RegisterCallback(r->recorderBufferQueue, + bqRecorderCallback, r); + assert(SL_RESULT_SUCCESS == result); + (void) result; + } + + + if (r->playerPlay) { + result = + (*r->playerPlay)->SetPlayState(r->playerPlay, SL_PLAYSTATE_STOPPED); + assert(SL_RESULT_SUCCESS == result); + (void) result; + result = (*r->playerBufferQueue)->Clear(r->playerBufferQueue); + assert(SL_RESULT_SUCCESS == result); + (void) result; + r->playerBuffer = malloc(r->playerBufferSizeBytes); + memset(r->playerBuffer, 0, r->playerBufferSizeBytes); + result = (*r->playerBufferQueue) + ->Enqueue(r->playerBufferQueue, r->playerBuffer, + r->playerBufferSizeBytes); + assert(SL_RESULT_SUCCESS == result); + (void) result; + result = + (*r->playerPlay)->SetPlayState(r->playerPlay, SL_PLAYSTATE_PLAYING); + assert(SL_RESULT_SUCCESS == result); + (void) result; + } + + + if (r->recorderRecord) { + result = + (*r->recorderRecord) + ->SetRecordState(r->recorderRecord, SL_RECORDSTATE_STOPPED); + assert(SL_RESULT_SUCCESS == result); + (void) result; + result = (*r->recorderBufferQueue)->Clear(r->recorderBufferQueue); + assert(SL_RESULT_SUCCESS == result); + (void) result; + // the buffer is not valid for playback yet + + r->recorderBuffer = malloc(r->recorderBufferSizeBytes); + + // enqueue an empty buffer to be filled by the recorder + // (for streaming recording, we would enqueue at least 2 empty buffers + // to start things off) + result = (*r->recorderBufferQueue) + ->Enqueue(r->recorderBufferQueue, r->recorderBuffer, + r->recorderBufferSizeBytes); + // the most likely other result is SL_RESULT_BUFFER_INSUFFICIENT, + // which for this code example would indicate a programming error + assert(SL_RESULT_SUCCESS == result); + (void) result; + + // start recording + result = + (*r->recorderRecord) + ->SetRecordState(r->recorderRecord, SL_RECORDSTATE_RECORDING); + assert(SL_RESULT_SUCCESS == result); + (void) result; + } + + + printf("Complete Init Sound Android\n"); + return r; +} + +int CNFAStateAndroid(void *v) +{ + struct CNFADriverAndroid *soundobject = (struct CNFADriverAndroid *) v; + return ((soundobject->recorderObject) ? 1 : 0) | + ((soundobject->playerObject) ? 2 : 0); +} + +void CloseCNFAAndroid(void *v) +{ + struct CNFADriverAndroid *r = (struct CNFADriverAndroid *) v; + // destroy audio recorder object, and invalidate all associated interfaces + if (r->recorderObject != NULL) { + (*r->recorderObject)->Destroy(r->recorderObject); + r->recorderObject = NULL; + r->recorderRecord = NULL; + r->recorderBufferQueue = NULL; + if (r->recorderBuffer) + free(r->recorderBuffer); + } + + + if (r->playerObject != NULL) { + (*r->playerObject)->Destroy(r->playerObject); + r->playerObject = NULL; + r->playerPlay = NULL; + r->playerBufferQueue = NULL; + if (r->playerBuffer) + free(r->playerBuffer); + } + + + // destroy engine object, and invalidate all associated interfaces + if (r->engineObject != NULL) { + (*r->engineObject)->Destroy(r->engineObject); + r->engineObject = NULL; + r->engineEngine = NULL; + } +} + + +int AndroidHasPermissions(const char *perm_name); +void AndroidRequestAppPermissions(const char *perm); + + +void *InitCNFAAndroid(CNFACBType cb, + const char *your_name, + int reqSPSPlay, + int reqSPSRec, + int reqChannelsPlay, + int reqChannelsRec, + int sugBufferSize, + const char *outputSelect, + const char *inputSelect, + void *opaque) +{ + struct CNFADriverAndroid *r = + (struct CNFADriverAndroid *) malloc(sizeof(struct CNFADriverAndroid)); + memset(r, 0, sizeof(*r)); + r->CloseFn = CloseCNFAAndroid; + r->StateFn = CNFAStateAndroid; + r->callback = cb; + r->opaque = opaque; + r->channelsPlay = reqChannelsPlay; + r->channelsRec = reqChannelsRec; + r->spsRec = reqSPSRec; + r->spsPlay = reqSPSPlay; + + r->recorderBufferSizeBytes = sugBufferSize * 2 * r->channelsRec; + r->playerBufferSizeBytes = sugBufferSize * 2 * r->channelsPlay; + + int hasperm = AndroidHasPermissions("RECORD_AUDIO"); + if (!hasperm) { + AndroidRequestAppPermissions("RECORD_AUDIO"); + } + + r->buffsz = sugBufferSize; + + return InitAndroidDriver(r); +} + +// Tricky: On Android, this can't actually run before main. Have to manually +// execute it. + +REGISTER_CNFA(AndroidCNFA, 10, "ANDROID", InitCNFAAndroid); + + +#elif CNFA_SUN +// Copyright 2015-2020 <>< Charles Lohr under the MIT/x11, NewBSD or ColorChord +// License. You choose. + +#include +#include +#include +#include +#include +#include "os_generic.h" + +struct CNFADriverSun { + void (*CloseFn)(void *object); + int (*StateFn)(void *object); + CNFACBType callback; + short channelsPlay; + short channelsRec; + int spsPlay; + int spsRec; + void *opaque; + + char *devRec; + char *devPlay; + + short *samplesRec; + short *samplesPlay; + + og_thread_t threadPlay; + og_thread_t threadRec; + int bufsize; + int playback_handle; + int record_handle; + + char playing; + char recording; +}; + +int CNFAStateSun(void *v) +{ + struct CNFADriverSun *r = (struct CNFADriverSun *) v; + return ((r->playing) ? 2 : 0) | ((r->recording) ? 1 : 0); +} + +void CloseCNFASun(void *v) +{ + struct CNFADriverSun *r = (struct CNFADriverSun *) v; + if (r) { + if (r->playback_handle != -1) + close(r->playback_handle); + if (r->record_handle != -1) + close(r->record_handle); + + if (r->threadPlay) + OGJoinThread(r->threadPlay); + if (r->threadRec) + OGJoinThread(r->threadRec); + + OGUSleep(2000); + + free(r->devRec); + free(r->devPlay); + free(r->samplesRec); + free(r->samplesPlay); + free(r); + } +} + + +void *RecThread(void *v) +{ + struct CNFADriverSun *r = (struct CNFADriverSun *) v; + size_t nbytes = r->bufsize * (2 * r->channelsRec); + do { + int nread = read(r->record_handle, r->samplesRec, nbytes); + if (nread < 0) { + fprintf(stderr, "Warning: Sun Recording Failed\n"); + break; + } + r->recording = 1; + r->callback((struct CNFADriver *) r, NULL, r->samplesRec, 0, + (nread / 2) / r->channelsRec); + } while (1); + r->recording = 0; + fprintf(stderr, "Sun Recording Stopped\n"); + return 0; +} + +void *PlayThread(void *v) +{ + struct CNFADriverSun *r = (struct CNFADriverSun *) v; + size_t nbytes = r->bufsize * (2 * r->channelsPlay); + int err; + + r->callback((struct CNFADriver *) r, r->samplesPlay, NULL, r->bufsize, 0); + err = write(r->playback_handle, r->samplesPlay, nbytes); + + while (err >= 0) { + r->callback((struct CNFADriver *) r, r->samplesPlay, NULL, r->bufsize, + 0); + err = write(r->playback_handle, r->samplesPlay, nbytes); + r->playing = 1; + } + r->playing = 0; + fprintf(stderr, "Sun Playback Stopped\n"); + return 0; +} + +static struct CNFADriverSun *InitSun(struct CNFADriverSun *r) +{ + const char *devPlay = r->devPlay; + const char *devRec = r->devRec; + struct audio_info rinfo, pinfo; + + if (devRec == NULL || strcmp(devRec, "default") == 0) { + devRec = "/dev/audio"; + } + + if (devPlay == NULL || strcmp(devPlay, "default") == 0) { + devPlay = "/dev/audio"; + } + + printf( + "CNFA Sun Init -> devPlay: %s, channelsPlay: %d, spsPlay: %d, devRec: " + "%s, channelsRec: %d, spsRec: %d\n", + devPlay, r->channelsPlay, r->spsPlay, devRec, r->channelsRec, + r->spsRec); + + if (r->channelsPlay && r->channelsRec && strcmp(devPlay, devRec) == 0) { + if ((r->playback_handle = r->record_handle = open(devPlay, O_RDWR)) < + 0) { + fprintf(stderr, "cannot open audio device (%s)\n", strerror(errno)); + goto fail; + } + } else { + if (r->channelsPlay) { + if ((r->playback_handle = open(devPlay, O_WRONLY)) < 0) { + fprintf(stderr, "cannot open output audio device %s (%s)\n", + r->devPlay, strerror(errno)); + goto fail; + } + } + + if (r->channelsRec) { + if ((r->record_handle = open(devRec, O_RDONLY)) < 0) { + fprintf(stderr, "cannot open input audio device %s (%s)\n", + r->devRec, strerror(errno)); + goto fail; + } + } + } + + if (r->playback_handle) { + AUDIO_INITINFO(&pinfo); + + pinfo.play.precision = 16; + pinfo.play.encoding = AUDIO_ENCODING_LINEAR; + pinfo.play.sample_rate = r->spsPlay; + pinfo.play.channels = r->channelsPlay; + + if (ioctl(r->playback_handle, AUDIO_SETINFO, &pinfo) < 0) { + fprintf(stderr, "cannot set audio playback format (%s)\n", + strerror(errno)); + goto fail; + } + + if (ioctl(r->playback_handle, AUDIO_GETINFO, &pinfo) < 0) { + fprintf(stderr, "cannot get audio record format (%s)\n", + strerror(errno)); + goto fail; + } + + r->spsPlay = pinfo.play.sample_rate; + r->channelsPlay = pinfo.play.channels; + + if ((r->samplesPlay = calloc(2 * r->channelsPlay, r->bufsize)) == + NULL) { + goto fail; + } + } + + if (r->record_handle) { + AUDIO_INITINFO(&rinfo); + + rinfo.record.precision = 16; + rinfo.record.encoding = AUDIO_ENCODING_LINEAR; + rinfo.record.sample_rate = r->spsRec; + rinfo.record.channels = r->channelsRec; + + if (ioctl(r->record_handle, AUDIO_SETINFO, &rinfo) < 0) { + fprintf(stderr, "cannot set audio record format (%s)\n", + strerror(errno)); + goto fail; + } + + if (ioctl(r->record_handle, AUDIO_GETINFO, &rinfo) < 0) { + fprintf(stderr, "cannot get audio record format (%s)\n", + strerror(errno)); + goto fail; + } + + r->spsRec = rinfo.record.sample_rate; + r->channelsRec = rinfo.record.channels; + + if ((r->samplesRec = calloc(2 * r->channelsRec, r->bufsize)) == NULL) { + goto fail; + } + } + + if (r->playback_handle) { + r->threadPlay = OGCreateThread(PlayThread, r); + } + + if (r->record_handle) { + r->threadRec = OGCreateThread(RecThread, r); + } + + printf( + "CNFA Sun Init Out -> channelsPlay: %d, spsPlay: %d, channelsRec: %d, " + "spsRec: %d\n", + r->channelsPlay, r->spsPlay, r->channelsRec, r->spsRec); + + return r; + +fail: + if (r) { + if (r->playback_handle != -1) + close(r->playback_handle); + if (r->record_handle != -1) + close(r->record_handle); + free(r->samplesPlay); + free(r->samplesRec); + free(r); + } + return 0; +} + + + +void *InitSunDriver(CNFACBType cb, + const char *your_name, + int reqSPSPlay, + int reqSPSRec, + int reqChannelsPlay, + int reqChannelsRec, + int sugBufferSize, + const char *outputSelect, + const char *inputSelect, + void *opaque) +{ + struct CNFADriverSun *r = + (struct CNFADriverSun *) malloc(sizeof(struct CNFADriverSun)); + + r->CloseFn = CloseCNFASun; + r->StateFn = CNFAStateSun; + r->callback = cb; + r->opaque = opaque; + r->spsPlay = reqSPSPlay; + r->spsRec = reqSPSRec; + r->channelsPlay = reqChannelsPlay; + r->channelsRec = reqChannelsRec; + + r->devRec = (inputSelect) ? strdup(inputSelect) : 0; + r->devPlay = (outputSelect) ? strdup(outputSelect) : 0; + + r->samplesPlay = NULL; + r->samplesRec = NULL; + + r->playback_handle = -1; + r->record_handle = -1; + r->bufsize = sugBufferSize; + + return InitSun(r); +} + +REGISTER_CNFA(SUN, 10, "Sun", InitSunDriver); + + +#elif CNFA_LINUX +// Copyright 2015-2020 <>< Charles Lohr under the MIT/x11, NewBSD or ColorChord +// License. You choose. + +#include +#include +#include "os_generic.h" + +struct CNFADriverAlsa { + void (*CloseFn)(void *object); + int (*StateFn)(void *object); + CNFACBType callback; + short channelsPlay; + short channelsRec; + int spsPlay; + int spsRec; + void *opaque; + + char *devRec; + char *devPlay; + + snd_pcm_uframes_t bufsize; + og_thread_t threadPlay; + og_thread_t threadRec; + snd_pcm_t *playback_handle; + snd_pcm_t *record_handle; + + char playing; + char recording; +}; + +int CNFAStateAlsa(void *v) +{ + struct CNFADriverAlsa *r = (struct CNFADriverAlsa *) v; + return ((r->playing) ? 2 : 0) | ((r->recording) ? 1 : 0); +} + +void CloseCNFAAlsa(void *v) +{ + struct CNFADriverAlsa *r = (struct CNFADriverAlsa *) v; + if (r) { + if (r->playback_handle) + snd_pcm_close(r->playback_handle); + if (r->record_handle) + snd_pcm_close(r->record_handle); + + if (r->threadPlay) + OGJoinThread(r->threadPlay); + if (r->threadRec) + OGJoinThread(r->threadRec); + + OGUSleep(2000); + + if (r->devRec) + free(r->devRec); + if (r->devPlay) + free(r->devPlay); + free(r); + } +} + + +static int SetHWParams(snd_pcm_t *handle, + int *samplerate, + short *channels, + snd_pcm_uframes_t *bufsize, + struct CNFADriverAlsa *a) +{ + int err; + int bufs; + int dir; + snd_pcm_hw_params_t *hw_params; + if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) { + fprintf(stderr, "cannot allocate hardware parameter structure (%s)\n", + snd_strerror(err)); + return -1; + } + + if ((err = snd_pcm_hw_params_any(handle, hw_params)) < 0) { + fprintf(stderr, "cannot initialize hardware parameter structure (%s)\n", + snd_strerror(err)); + goto fail; + } + + if ((err = snd_pcm_hw_params_set_access( + handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { + fprintf(stderr, "cannot set access type (%s)\n", snd_strerror(err)); + goto fail; + } + + if ((err = snd_pcm_hw_params_set_format(handle, hw_params, + SND_PCM_FORMAT_S16_LE)) < 0) { + fprintf(stderr, "cannot set sample format (%s)\n", snd_strerror(err)); + goto fail; + } + + if ((err = snd_pcm_hw_params_set_rate_near( + handle, hw_params, (unsigned int *) samplerate, 0)) < 0) { + fprintf(stderr, "cannot set sample rate (%s)\n", snd_strerror(err)); + goto fail; + } + + if ((err = snd_pcm_hw_params_set_channels(handle, hw_params, *channels)) < + 0) { + fprintf(stderr, "cannot set channel count (%s)\n", snd_strerror(err)); + goto fail; + } + + dir = 0; + if ((err = snd_pcm_hw_params_set_period_size_near(handle, hw_params, + bufsize, &dir)) < 0) { + fprintf(stderr, "cannot set period size. (%s)\n", snd_strerror(err)); + goto fail; + } + + // NOTE: This step is critical for low-latency sound. + bufs = *bufsize * 3; + if ((err = snd_pcm_hw_params_set_buffer_size(handle, hw_params, bufs)) < + 0) { + fprintf(stderr, + "cannot set snd_pcm_hw_params_set_buffer_size size. (%s)\n", + snd_strerror(err)); + goto fail; + } + + + if ((err = snd_pcm_hw_params(handle, hw_params)) < 0) { + fprintf(stderr, "cannot set parameters (%s)\n", snd_strerror(err)); + goto fail; + } + + snd_pcm_hw_params_free(hw_params); + return 0; +fail: + snd_pcm_hw_params_free(hw_params); + return -2; +} + + +static int SetSWParams(struct CNFADriverAlsa *d, snd_pcm_t *handle, int isrec) +{ + snd_pcm_sw_params_t *sw_params; + int err; + // Time for software parameters: + + if (!isrec) { + if ((err = snd_pcm_sw_params_malloc(&sw_params)) < 0) { + fprintf(stderr, + "cannot allocate software parameters structure (%s)\n", + snd_strerror(err)); + goto failhard; + } + if ((err = snd_pcm_sw_params_current(handle, sw_params)) < 0) { + fprintf( + stderr, + "cannot initialize software parameters structure (%s) (%p)\n", + snd_strerror(err), handle); + goto fail; + } + + int buffer_size = d->bufsize * 3; + int period_size = d->bufsize; + printf("PERIOD: %d BUFFER: %d\n", period_size, buffer_size); + + if ((err = snd_pcm_sw_params_set_avail_min(handle, sw_params, + period_size)) < 0) { + fprintf(stderr, "cannot set minimum available count (%s)\n", + snd_strerror(err)); + goto fail; + } + // if ((err = snd_pcm_sw_params_set_stop_threshold(handle, sw_params, + // 512 )) < 0) { fprintf (stderr, "cannot set minimum available count + //(%s)\n", snd_strerror (err)); goto fail; + // } + if ((err = snd_pcm_sw_params_set_start_threshold( + handle, sw_params, buffer_size - period_size)) < 0) { + fprintf(stderr, "cannot set minimum available count (%s)\n", + snd_strerror(err)); + goto fail; + } + if ((err = snd_pcm_sw_params(handle, sw_params)) < 0) { + fprintf(stderr, "cannot set software parameters (%s)\n", + snd_strerror(err)); + goto fail; + } + } + + if ((err = snd_pcm_prepare(handle)) < 0) { + fprintf(stderr, "cannot prepare audio interface for use (%s)\n", + snd_strerror(err)); + goto fail; + } + + return 0; +fail: + if (!isrec) { + snd_pcm_sw_params_free(sw_params); + } +failhard: + return -1; +} + +void *RecThread(void *v) +{ + struct CNFADriverAlsa *r = (struct CNFADriverAlsa *) v; + short samples[r->bufsize * r->channelsRec]; + snd_pcm_start(r->record_handle); + do { + int err = snd_pcm_readi(r->record_handle, samples, r->bufsize); + if (err < 0) { + fprintf(stderr, "Warning: ALSA Recording Failed\n"); + break; + } + if (err != r->bufsize) { + fprintf(stderr, "Warning: ALSA Recording Underflow\n"); + } + r->recording = 1; + r->callback((struct CNFADriver *) r, 0, samples, 0, err); + } while (1); + r->recording = 0; + fprintf(stderr, "ALSA Recording Stopped\n"); + return 0; +} + +void *PlayThread(void *v) +{ + struct CNFADriverAlsa *r = (struct CNFADriverAlsa *) v; + short samples[r->bufsize * r->channelsPlay]; + int err; + // int total_avail = snd_pcm_avail(r->playback_handle); + + snd_pcm_start(r->playback_handle); + r->callback((struct CNFADriver *) r, samples, 0, r->bufsize, 0); + err = snd_pcm_writei(r->playback_handle, samples, r->bufsize); + + while (err >= 0) { + // int avail = snd_pcm_avail(r->playback_handle); + // printf( "avail: %d\n", avail ); + r->callback((struct CNFADriver *) r, samples, 0, r->bufsize, 0); + err = snd_pcm_writei(r->playback_handle, samples, r->bufsize); + if (err != r->bufsize) { + fprintf(stderr, "Warning: ALSA Playback Overflow\n"); + } + r->playing = 1; + } + r->playing = 0; + fprintf(stderr, "ALSA Playback Stopped\n"); + return 0; +} + +static struct CNFADriverAlsa *InitALSA(struct CNFADriverAlsa *r) +{ + printf("CNFA Alsa Init %p %p (%d %d) %d %d\n", r->playback_handle, + r->record_handle, r->spsPlay, r->spsRec, r->channelsPlay, + r->channelsRec); + + int err; + if (r->channelsPlay) { + if ((err = snd_pcm_open(&r->playback_handle, + r->devPlay ? r->devPlay : "default", + SND_PCM_STREAM_PLAYBACK, 0)) < 0) { + fprintf(stderr, "cannot open output audio device (%s)\n", + snd_strerror(err)); + goto fail; + } + } + + if (r->channelsRec) { + if ((err = snd_pcm_open(&r->record_handle, + r->devRec ? r->devRec : "default", + SND_PCM_STREAM_CAPTURE, 0)) < 0) { + fprintf(stderr, "cannot open input audio device (%s)\n", + snd_strerror(err)); + goto fail; + } + } + + printf("%p %p\n", r->playback_handle, r->record_handle); + + if (r->playback_handle) { + if (SetHWParams(r->playback_handle, &r->spsPlay, &r->channelsPlay, + &r->bufsize, r) < 0) + goto fail; + if (SetSWParams(r, r->playback_handle, 0) < 0) + goto fail; + } + + if (r->record_handle) { + if (SetHWParams(r->record_handle, &r->spsRec, &r->channelsRec, + &r->bufsize, r) < 0) + goto fail; + if (SetSWParams(r, r->record_handle, 1) < 0) + goto fail; + } + +#if 0 + if( r->playback_handle ) + { + snd_async_handler_t *pcm_callback; + //Handle automatically cleaned up when stream closed. + err = snd_async_add_pcm_handler(&pcm_callback, r->playback_handle, playback_callback, r); + if(err < 0) + { + printf("Playback callback handler error: %s\n", snd_strerror(err)); + } + } + + if( r->record_handle ) + { + snd_async_handler_t *pcm_callback; + //Handle automatically cleaned up when stream closed. + err = snd_async_add_pcm_handler(&pcm_callback, r->record_handle, record_callback, r); + if(err < 0) + { + printf("Record callback handler error: %s\n", snd_strerror(err)); + } + } +#endif + + if (r->playback_handle && r->record_handle) { + err = snd_pcm_link(r->playback_handle, r->record_handle); + if (err < 0) { + printf("snd_pcm_link error: %s\n", snd_strerror(err)); + } + } + + if (r->playback_handle) { + r->threadPlay = OGCreateThread(PlayThread, r); + } + + if (r->record_handle) { + r->threadRec = OGCreateThread(RecThread, r); + } + + printf("CNFA Alsa Init Out -> %p %p (%d %d) %d %d\n", r->playback_handle, + r->record_handle, r->spsPlay, r->spsRec, r->channelsPlay, + r->channelsRec); + + return r; + +fail: + if (r) { + if (r->playback_handle) + snd_pcm_close(r->playback_handle); + if (r->record_handle) + snd_pcm_close(r->record_handle); + free(r); + } + fprintf(stderr, "Error: ALSA failed to start.\n"); + return 0; +} + + + +void *InitALSADriver(CNFACBType cb, + const char *your_name, + int reqSPSPlay, + int reqSPSRec, + int reqChannelsPlay, + int reqChannelsRec, + int sugBufferSize, + const char *outputSelect, + const char *inputSelect, + void *opaque) +{ + struct CNFADriverAlsa *r = + (struct CNFADriverAlsa *) malloc(sizeof(struct CNFADriverAlsa)); + + r->CloseFn = CloseCNFAAlsa; + r->StateFn = CNFAStateAlsa; + r->callback = cb; + r->opaque = opaque; + r->spsPlay = reqSPSPlay; + r->spsRec = reqSPSRec; + r->channelsPlay = reqChannelsPlay; + r->channelsRec = reqChannelsRec; + + r->devRec = (inputSelect) ? strdup(inputSelect) : 0; + r->devPlay = (outputSelect) ? strdup(outputSelect) : 0; + + r->playback_handle = 0; + r->record_handle = 0; + r->bufsize = sugBufferSize; + + return InitALSA(r); +} + +REGISTER_CNFA(ALSA, 10, "ALSA", InitALSADriver); + + +#if CNFA_PULSE +// Copyright 2015-2020 <>< Charles Lohr under the MIT/x11, NewBSD or ColorChord +// License. You choose. + +// This file is really rough. Full duplex doesn't seem to work hardly at all. + + +#include +#include "os_generic.h" + +#include +#include +#include +#include +#include + +#define BUFFERSETS 3 + + +// from +// http://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/Developer/Clients/Samples/AsyncPlayback/ +// also http://maemo.org/api_refs/5.0/5.0-final/pulseaudio/pacat_8c-example.html + + +struct CNFADriverPulse { + void (*CloseFn)(void *object); + int (*StateFn)(void *object); + CNFACBType callback; + short channelsPlay; + short channelsRec; + int spsPlay; + int spsRec; + void *opaque; + + char *sourceNamePlay; + char *sourceNameRec; + + og_thread_t thread; + pa_stream *play; + pa_stream *rec; + pa_context *pa_ctx; + pa_mainloop *pa_ml; + int pa_ready; + int buffer; + // More fields may exist on a per-sound-driver basis +}; + + + +int CNFAStatePulse(void *v) +{ + struct CNFADriverPulse *soundobject = (struct CNFADriverPulse *) v; + return ((soundobject->play) ? 2 : 0) | ((soundobject->rec) ? 1 : 0); +} + +void CloseCNFAPulse(void *v) +{ + struct CNFADriverPulse *r = (struct CNFADriverPulse *) v; + if (r) { + if (r->play) { + pa_stream_unref(r->play); + r->play = 0; + } + + if (r->rec) { + pa_stream_unref(r->rec); + r->rec = 0; + } + OGUSleep(2000); + OGCancelThread(r->thread); + + + if (r->sourceNamePlay) + free(r->sourceNamePlay); + if (r->sourceNameRec) + free(r->sourceNameRec); + free(r); + } +} + +static void *CNFAThread(void *v) +{ + struct CNFADriverPulse *r = (struct CNFADriverPulse *) v; + while (1) { + pa_mainloop_iterate(r->pa_ml, 1, NULL); + } + return 0; +} + +static void stream_request_cb(pa_stream *s, size_t length, void *userdata) +{ + struct CNFADriverPulse *r = (struct CNFADriverPulse *) userdata; + if (!r->play) { + return; + } + short bufp[length * r->channelsPlay / sizeof(short)]; + r->callback((struct CNFADriver *) r, bufp, 0, + length / (sizeof(short) * r->channelsPlay), 0); + pa_stream_write(r->play, &bufp[0], length, NULL, 0LL, PA_SEEK_RELATIVE); +} + + +static void stream_record_cb(pa_stream *s, size_t length, void *userdata) +{ + struct CNFADriverPulse *r = (struct CNFADriverPulse *) userdata; + + uint16_t *bufr; + + if (pa_stream_peek(r->rec, (const void **) &bufr, &length) < 0) { + fprintf(stderr, ("pa_stream_peek() failed: %s\n"), + pa_strerror(pa_context_errno(r->pa_ctx))); + return; + } + + short *buffer; + buffer = (short *) pa_xmalloc(length); + memcpy(buffer, bufr, length); + pa_stream_drop(r->rec); + r->callback((struct CNFADriver *) r, 0, buffer, 0, + length / (sizeof(short) * r->channelsRec)); + pa_xfree(buffer); +} + + + +static void stream_underflow_cb(pa_stream *s, void *userdata) +{ + printf("underflow\n"); +} + + +void pa_state_cb(pa_context *c, void *userdata) +{ + pa_context_state_t state; + int *pa_ready = (int *) userdata; + state = pa_context_get_state(c); + switch (state) { + // These are just here for reference + case PA_CONTEXT_UNCONNECTED: + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + default: + break; + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + *pa_ready = 2; + break; + case PA_CONTEXT_READY: + *pa_ready = 1; + break; + } +} + + +void *InitCNFAPulse(CNFACBType cb, + const char *your_name, + int reqSPSPlay, + int reqSPSRec, + int reqChannelsPlay, + int reqChannelsRec, + int sugBufferSize, + const char *outputSelect, + const char *inputSelect, + void *opaque) +{ + static pa_buffer_attr bufattr; + static pa_sample_spec ss; + int error; + pa_mainloop_api *pa_mlapi; + const char *title = your_name; + + struct CNFADriverPulse *r = + (struct CNFADriverPulse *) malloc(sizeof(struct CNFADriverPulse)); + + r->pa_ml = pa_mainloop_new(); + if (!r->pa_ml) { + fprintf(stderr, "Failed to initialize pa_mainloop_new()\n"); + goto fail; + } + + pa_mlapi = pa_mainloop_get_api(r->pa_ml); + if (!pa_mlapi) { + fprintf(stderr, "Failed to initialize pa_mainloop_get_api()\n"); + goto fail; + } + + r->pa_ctx = pa_context_new(pa_mlapi, title); + pa_context_connect(r->pa_ctx, NULL, PA_CONTEXT_NOFLAGS, NULL); + + // TODO: pa_context_set_state_callback + + r->CloseFn = CloseCNFAPulse; + r->StateFn = CNFAStatePulse; + r->callback = cb; + r->opaque = opaque; + r->spsPlay = reqSPSPlay; + r->spsRec = reqSPSRec; + r->channelsPlay = reqChannelsPlay; + r->channelsRec = reqChannelsRec; + r->sourceNamePlay = outputSelect ? strdup(outputSelect) : 0; + r->sourceNameRec = inputSelect ? strdup(inputSelect) : 0; + + r->play = 0; + r->rec = 0; + r->buffer = sugBufferSize; + + printf("Pulse: from: [O/I] %s/%s (%s) / (%d,%d)x(%d,%d) (%d)\n", + r->sourceNamePlay, r->sourceNameRec, title, r->spsPlay, r->spsRec, + r->channelsPlay, r->channelsRec, r->buffer); + + memset(&ss, 0, sizeof(ss)); + + ss.format = PA_SAMPLE_S16NE; + + r->pa_ready = 0; + pa_context_set_state_callback(r->pa_ctx, pa_state_cb, &r->pa_ready); + + while (r->pa_ready == 0) { + pa_mainloop_iterate(r->pa_ml, 1, NULL); + } + + int bufBytesPlay = r->buffer * sizeof(short) * r->channelsPlay; + int bufBytesRec = r->buffer * sizeof(short) * r->channelsRec; + + if (r->channelsPlay) { + ss.channels = r->channelsPlay; + ss.rate = r->spsPlay; + + if (!(r->play = pa_stream_new(r->pa_ctx, "Play", &ss, NULL))) { + error = -3; // XXX ??? TODO + fprintf(stderr, __FILE__ ": pa_simple_new() failed: %s\n", + pa_strerror(error)); + goto fail; + } + + pa_stream_set_underflow_callback(r->play, stream_underflow_cb, NULL); + pa_stream_set_write_callback(r->play, stream_request_cb, r); + + /* The absolute maximum number of bytes that can be stored in the + * buffer. If this value is exceeded then data will be lost. It is + * recommended to pass (uint32_t) -1 here which will cause the server to + * fill in the maximum possible value.*/ + bufattr.maxlength = bufBytesPlay * 3; + + /* The target fill level of the playback buffer. The server will only + * send requests for more data as long as the buffer has less than this + * number of bytes of data. If you pass (uint32_t) -1 (which is + * recommended) here the server will choose the longest target buffer + * fill level possible to minimize the number of necessary wakeups and + * maximize drop-out safety. This can exceed 2s of buffering. For + * low-latency applications or applications where latency matters you + * should pass a proper value here. */ + bufattr.tlength = bufBytesPlay * 3; + + /* Number of bytes that need to be in the buffer before playback will + * commence. Start of playback can be forced using pa_stream_trigger() + * even though the prebuffer size hasn't been reached. If a buffer + * underrun occurs, this prebuffering will be again enabled. If the + * playback shall never stop in case of a buffer underrun, this value + * should be set to 0. In that case the read index of the output buffer + * overtakes the write index, and hence the fill level of the buffer is + * negative. If you pass (uint32_t) -1 here (which is recommended) the + * server will choose the same value as tlength here. */ + bufattr.prebuf = (uint32_t) -1; + + /* Minimum free number of the bytes in the playback buffer before the + * server will request more data. It is recommended to fill in + * (uint32_t) -1 here. This value influences how much time the sound + * server has to move data from the per-stream server-side playback + * buffer to the hardware playback buffer. */ + bufattr.minreq = (uint32_t) -1; + + /* Maximum number of bytes that the server will push in one chunk for + * record streams. If you pass (uint32_t) -1 (which is recommended) + * here, the server will choose the longest fragment setting possible to + * minimize the number of necessary wakeups and maximize drop-out + * safety. This can exceed 2s of buffering. For low-latency applications + * or applications where latency matters you should pass a proper value + * here. */ + bufattr.fragsize = (uint32_t) -1; + + int ret = pa_stream_connect_playback( + r->play, r->sourceNamePlay, &bufattr, + // PA_STREAM_INTERPOLATE_TIMING + // |PA_STREAM_ADJUST_LATENCY //Some servers don't like the + // adjust_latency flag. |PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL); + PA_STREAM_NOFLAGS, NULL, NULL); + if (ret < 0) { + fprintf(stderr, + __FILE__ + ": (PLAY) pa_stream_connect_playback() failed: %s\n", + pa_strerror(ret)); + goto fail; + } + } + + if (r->channelsRec) { + ss.channels = r->channelsRec; + ss.rate = r->spsRec; + + if (!(r->rec = pa_stream_new(r->pa_ctx, "Record", &ss, NULL))) { + error = -3; // XXX ??? TODO + fprintf(stderr, __FILE__ ": pa_simple_new() failed: %s\n", + pa_strerror(error)); + goto fail; + } + + pa_stream_set_read_callback(r->rec, stream_record_cb, r); + + bufattr.fragsize = bufBytesRec; + bufattr.maxlength = + (uint32_t) -1; //(uint32_t)-1; //XXX: Todo, should this be low? + bufattr.minreq = bufBytesRec; + bufattr.prebuf = (uint32_t) -1; + bufattr.tlength = bufBytesRec * 3; + int ret = pa_stream_connect_record( + r->rec, r->sourceNameRec, &bufattr, + // PA_STREAM_INTERPOLATE_TIMING + PA_STREAM_ADJUST_LATENCY // Some servers don't like the + // adjust_latency flag. + // PA_STREAM_AUTO_TIMING_UPDATE + // PA_STREAM_NOFLAGS + ); + + printf("PA REC RES: %d\n", ret); + + if (ret < 0) { + fprintf(stderr, + __FILE__ + ": (REC) pa_stream_connect_playback() failed: %s\n", + pa_strerror(ret)); + goto fail; + } + } + + printf("Pulse initialized.\n"); + + + r->thread = OGCreateThread(CNFAThread, r); + + + if (r->play) { + stream_request_cb(r->play, bufBytesPlay, r); + } + + return r; + +fail: + if (r) { + if (r->play) + pa_xfree(r->play); + if (r->rec) + pa_xfree(r->rec); + free(r); + } + return 0; +} + + + +REGISTER_CNFA(PulseCNFA, 11, "PULSE", InitCNFAPulse); + +#endif +#elif defined(__APPLE__) +#if defined(PULSEAUDIO) +// Copyright 2015-2020 <>< Charles Lohr under the MIT/x11, NewBSD or ColorChord +// License. You choose. + +// This file is really rough. Full duplex doesn't seem to work hardly at all. + + +#include +#include "os_generic.h" + +#include +#include +#include +#include +#include + +#define BUFFERSETS 3 + + +// from +// http://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/Developer/Clients/Samples/AsyncPlayback/ +// also http://maemo.org/api_refs/5.0/5.0-final/pulseaudio/pacat_8c-example.html + + +struct CNFADriverPulse { + void (*CloseFn)(void *object); + int (*StateFn)(void *object); + CNFACBType callback; + short channelsPlay; + short channelsRec; + int spsPlay; + int spsRec; + void *opaque; + + char *sourceNamePlay; + char *sourceNameRec; + + og_thread_t thread; + pa_stream *play; + pa_stream *rec; + pa_context *pa_ctx; + pa_mainloop *pa_ml; + int pa_ready; + int buffer; + // More fields may exist on a per-sound-driver basis +}; + + + +int CNFAStatePulse(void *v) +{ + struct CNFADriverPulse *soundobject = (struct CNFADriverPulse *) v; + return ((soundobject->play) ? 2 : 0) | ((soundobject->rec) ? 1 : 0); +} + +void CloseCNFAPulse(void *v) +{ + struct CNFADriverPulse *r = (struct CNFADriverPulse *) v; + if (r) { + if (r->play) { + pa_stream_unref(r->play); + r->play = 0; + } + + if (r->rec) { + pa_stream_unref(r->rec); + r->rec = 0; + } + OGUSleep(2000); + OGCancelThread(r->thread); + + + if (r->sourceNamePlay) + free(r->sourceNamePlay); + if (r->sourceNameRec) + free(r->sourceNameRec); + free(r); + } +} + +static void *CNFAThread(void *v) +{ + struct CNFADriverPulse *r = (struct CNFADriverPulse *) v; + while (1) { + pa_mainloop_iterate(r->pa_ml, 1, NULL); + } + return 0; +} + +static void stream_request_cb(pa_stream *s, size_t length, void *userdata) +{ + struct CNFADriverPulse *r = (struct CNFADriverPulse *) userdata; + if (!r->play) { + return; + } + short bufp[length * r->channelsPlay / sizeof(short)]; + r->callback((struct CNFADriver *) r, bufp, 0, + length / (sizeof(short) * r->channelsPlay), 0); + pa_stream_write(r->play, &bufp[0], length, NULL, 0LL, PA_SEEK_RELATIVE); +} + + +static void stream_record_cb(pa_stream *s, size_t length, void *userdata) +{ + struct CNFADriverPulse *r = (struct CNFADriverPulse *) userdata; + + uint16_t *bufr; + + if (pa_stream_peek(r->rec, (const void **) &bufr, &length) < 0) { + fprintf(stderr, ("pa_stream_peek() failed: %s\n"), + pa_strerror(pa_context_errno(r->pa_ctx))); + return; + } + + short *buffer; + buffer = (short *) pa_xmalloc(length); + memcpy(buffer, bufr, length); + pa_stream_drop(r->rec); + r->callback((struct CNFADriver *) r, 0, buffer, 0, + length / (sizeof(short) * r->channelsRec)); + pa_xfree(buffer); +} + + + +static void stream_underflow_cb(pa_stream *s, void *userdata) +{ + printf("underflow\n"); +} + + +void pa_state_cb(pa_context *c, void *userdata) +{ + pa_context_state_t state; + int *pa_ready = (int *) userdata; + state = pa_context_get_state(c); + switch (state) { + // These are just here for reference + case PA_CONTEXT_UNCONNECTED: + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + default: + break; + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + *pa_ready = 2; + break; + case PA_CONTEXT_READY: + *pa_ready = 1; + break; + } +} + + +void *InitCNFAPulse(CNFACBType cb, + const char *your_name, + int reqSPSPlay, + int reqSPSRec, + int reqChannelsPlay, + int reqChannelsRec, + int sugBufferSize, + const char *outputSelect, + const char *inputSelect, + void *opaque) +{ + static pa_buffer_attr bufattr; + static pa_sample_spec ss; + int error; + pa_mainloop_api *pa_mlapi; + const char *title = your_name; + + struct CNFADriverPulse *r = + (struct CNFADriverPulse *) malloc(sizeof(struct CNFADriverPulse)); + + r->pa_ml = pa_mainloop_new(); + if (!r->pa_ml) { + fprintf(stderr, "Failed to initialize pa_mainloop_new()\n"); + goto fail; + } + + pa_mlapi = pa_mainloop_get_api(r->pa_ml); + if (!pa_mlapi) { + fprintf(stderr, "Failed to initialize pa_mainloop_get_api()\n"); + goto fail; + } + + r->pa_ctx = pa_context_new(pa_mlapi, title); + pa_context_connect(r->pa_ctx, NULL, PA_CONTEXT_NOFLAGS, NULL); + + // TODO: pa_context_set_state_callback + + r->CloseFn = CloseCNFAPulse; + r->StateFn = CNFAStatePulse; + r->callback = cb; + r->opaque = opaque; + r->spsPlay = reqSPSPlay; + r->spsRec = reqSPSRec; + r->channelsPlay = reqChannelsPlay; + r->channelsRec = reqChannelsRec; + r->sourceNamePlay = outputSelect ? strdup(outputSelect) : 0; + r->sourceNameRec = inputSelect ? strdup(inputSelect) : 0; + + r->play = 0; + r->rec = 0; + r->buffer = sugBufferSize; + + printf("Pulse: from: [O/I] %s/%s (%s) / (%d,%d)x(%d,%d) (%d)\n", + r->sourceNamePlay, r->sourceNameRec, title, r->spsPlay, r->spsRec, + r->channelsPlay, r->channelsRec, r->buffer); + + memset(&ss, 0, sizeof(ss)); + + ss.format = PA_SAMPLE_S16NE; + + r->pa_ready = 0; + pa_context_set_state_callback(r->pa_ctx, pa_state_cb, &r->pa_ready); + + while (r->pa_ready == 0) { + pa_mainloop_iterate(r->pa_ml, 1, NULL); + } + + int bufBytesPlay = r->buffer * sizeof(short) * r->channelsPlay; + int bufBytesRec = r->buffer * sizeof(short) * r->channelsRec; + + if (r->channelsPlay) { + ss.channels = r->channelsPlay; + ss.rate = r->spsPlay; + + if (!(r->play = pa_stream_new(r->pa_ctx, "Play", &ss, NULL))) { + error = -3; // XXX ??? TODO + fprintf(stderr, __FILE__ ": pa_simple_new() failed: %s\n", + pa_strerror(error)); + goto fail; + } + + pa_stream_set_underflow_callback(r->play, stream_underflow_cb, NULL); + pa_stream_set_write_callback(r->play, stream_request_cb, r); + + /* The absolute maximum number of bytes that can be stored in the + * buffer. If this value is exceeded then data will be lost. It is + * recommended to pass (uint32_t) -1 here which will cause the server to + * fill in the maximum possible value.*/ + bufattr.maxlength = bufBytesPlay * 3; + + /* The target fill level of the playback buffer. The server will only + * send requests for more data as long as the buffer has less than this + * number of bytes of data. If you pass (uint32_t) -1 (which is + * recommended) here the server will choose the longest target buffer + * fill level possible to minimize the number of necessary wakeups and + * maximize drop-out safety. This can exceed 2s of buffering. For + * low-latency applications or applications where latency matters you + * should pass a proper value here. */ + bufattr.tlength = bufBytesPlay * 3; + + /* Number of bytes that need to be in the buffer before playback will + * commence. Start of playback can be forced using pa_stream_trigger() + * even though the prebuffer size hasn't been reached. If a buffer + * underrun occurs, this prebuffering will be again enabled. If the + * playback shall never stop in case of a buffer underrun, this value + * should be set to 0. In that case the read index of the output buffer + * overtakes the write index, and hence the fill level of the buffer is + * negative. If you pass (uint32_t) -1 here (which is recommended) the + * server will choose the same value as tlength here. */ + bufattr.prebuf = (uint32_t) -1; + + /* Minimum free number of the bytes in the playback buffer before the + * server will request more data. It is recommended to fill in + * (uint32_t) -1 here. This value influences how much time the sound + * server has to move data from the per-stream server-side playback + * buffer to the hardware playback buffer. */ + bufattr.minreq = (uint32_t) -1; + + /* Maximum number of bytes that the server will push in one chunk for + * record streams. If you pass (uint32_t) -1 (which is recommended) + * here, the server will choose the longest fragment setting possible to + * minimize the number of necessary wakeups and maximize drop-out + * safety. This can exceed 2s of buffering. For low-latency applications + * or applications where latency matters you should pass a proper value + * here. */ + bufattr.fragsize = (uint32_t) -1; + + int ret = pa_stream_connect_playback( + r->play, r->sourceNamePlay, &bufattr, + // PA_STREAM_INTERPOLATE_TIMING + // |PA_STREAM_ADJUST_LATENCY //Some servers don't like the + // adjust_latency flag. |PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL); + PA_STREAM_NOFLAGS, NULL, NULL); + if (ret < 0) { + fprintf(stderr, + __FILE__ + ": (PLAY) pa_stream_connect_playback() failed: %s\n", + pa_strerror(ret)); + goto fail; + } + } + + if (r->channelsRec) { + ss.channels = r->channelsRec; + ss.rate = r->spsRec; + + if (!(r->rec = pa_stream_new(r->pa_ctx, "Record", &ss, NULL))) { + error = -3; // XXX ??? TODO + fprintf(stderr, __FILE__ ": pa_simple_new() failed: %s\n", + pa_strerror(error)); + goto fail; + } + + pa_stream_set_read_callback(r->rec, stream_record_cb, r); + + bufattr.fragsize = bufBytesRec; + bufattr.maxlength = + (uint32_t) -1; //(uint32_t)-1; //XXX: Todo, should this be low? + bufattr.minreq = bufBytesRec; + bufattr.prebuf = (uint32_t) -1; + bufattr.tlength = bufBytesRec * 3; + int ret = pa_stream_connect_record( + r->rec, r->sourceNameRec, &bufattr, + // PA_STREAM_INTERPOLATE_TIMING + PA_STREAM_ADJUST_LATENCY // Some servers don't like the + // adjust_latency flag. + // PA_STREAM_AUTO_TIMING_UPDATE + // PA_STREAM_NOFLAGS + ); + + printf("PA REC RES: %d\n", ret); + + if (ret < 0) { + fprintf(stderr, + __FILE__ + ": (REC) pa_stream_connect_playback() failed: %s\n", + pa_strerror(ret)); + goto fail; + } + } + + printf("Pulse initialized.\n"); + + + r->thread = OGCreateThread(CNFAThread, r); + + + if (r->play) { + stream_request_cb(r->play, bufBytesPlay, r); + } + + return r; + +fail: + if (r) { + if (r->play) + pa_xfree(r->play); + if (r->rec) + pa_xfree(r->rec); + free(r); + } + return 0; +} + + + +REGISTER_CNFA(PulseCNFA, 11, "PULSE", InitCNFAPulse); + +#endif +#endif +#endif + + +#ifdef __cplusplus +}; +#endif + + + +#endif diff --git a/Makefile b/Makefile index ceb397f..79244ac 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,6 +43,18 @@ 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 += -I/usr/local/include +endif + BIN = semu all: $(BIN) minimal.dtb 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..348ee56 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.106 Kernel Configuration +# +CONFIG_CC_VERSION_TEXT="riscv32-buildroot-linux-gnu-gcc.br_real (Buildroot -gd0bd1549) 12.4.0" CONFIG_CC_IS_GCC=y -CONFIG_GCC_VERSION=120300 +CONFIG_GCC_VERSION=120400 CONFIG_CLANG_VERSION=0 CONFIG_AS_IS_GNU=y -CONFIG_AS_VERSION=23900 +CONFIG_AS_VERSION=24100 CONFIG_LD_IS_BFD=y -CONFIG_LD_VERSION=23900 +CONFIG_LD_VERSION=24100 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=y +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 is not set +# CONFIG_SND_ALOOP is not set +# 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..bb1d339 100644 --- a/main.c +++ b/main.c @@ -72,8 +72,7 @@ static void emu_update_vblk_interrupts(vm_t *vm) } #endif -static void emu_update_timer_interrupt(hart_t *hart) -{ +static void emu_update_timer_interrupt(hart_t *hart) { emu_state_t *data = PRIV(hart); /* Sync global timer with local timer */ @@ -81,6 +80,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 +132,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 +184,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 +605,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 +632,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/os_generic.h b/os_generic.h new file mode 100644 index 0000000..33b0a7b --- /dev/null +++ b/os_generic.h @@ -0,0 +1,514 @@ +#ifndef _OS_GENERIC_H +#define _OS_GENERIC_H +/* + "osgeneric" Generic, platform independent tool for threads and time. + Geared around Windows and Linux. Designed for operation on MSVC, + TCC, GCC and clang. Others may work. + + It offers the following operations: + + Delay functions: + void OGSleep( int is ); + void OGUSleep( int ius ); + + Getting current time (may be time from program start, boot, or epoc) + double OGGetAbsoluteTime(); + double OGGetFileTime( const char * file ); + + Thread functions + og_thread_t OGCreateThread( void * (routine)( void * ), void * parameter + ); void * OGJoinThread( og_thread_t ot ); void OGCancelThread( og_thread_t ot + ); + + Mutex functions, used for protecting data structures. + (recursive on platforms where available.) + og_mutex_t OGCreateMutex(); + void OGLockMutex( og_mutex_t om ); + void OGUnlockMutex( og_mutex_t om ); + void OGDeleteMutex( og_mutex_t om ); + + Always a semaphore (not recursive) + og_sema_t OGCreateSema(); //Create a semaphore, comes locked initially. + NOTE: For platform compatibility, max count is 32767 + void OGLockSema( og_sema_t os ); + int OGGetSema( og_sema_t os ); //if <0 there was a failure. + void OGUnlockSema( og_sema_t os ); + void OGDeleteSema( og_sema_t os ); + + TLS (Thread-Local Storage) + og_tls_t OGCreateTLS(); + void OGDeleteTLS( og_tls_t tls ); + void OGSetTLS( og_tls_t tls, void * data ); + void * OGGetTLS( og_tls_t tls ); + + You can permute the operations of this file by the following means: + OSG_NO_IMPLEMENTATION + OSG_PREFIX + OSG_NOSTATIC + + The default behavior is to do static inline. + + Copyright (c) 2011-2012,2013,2016,2018,2019,2020 <>< Charles Lohr + + This file may be licensed under the MIT/x11 license, NewBSD or CC0 licenses + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of this file. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + Date Stamp: 2019-09-05 CNL: Allow for noninstantiation and added TLS. + Date Stamp: 2018-03-25 CNL: Switched to header-only format. +*/ + + +#if defined(OSG_NOSTATIC) && OSG_NOSTATIC != 0 +#ifndef OSG_PREFIX +#define OSG_PREFIX +#endif +#ifndef OSG_NO_IMPLEMENTATION +#define OSG_NO_IMPLEMENTATION +#endif +#endif + +#ifndef OSG_PREFIX +#ifdef __wasm__ +#define OSG_PREFIX +#else +#define OSG_PREFIX static inline +#endif +#endif + +// In case you want to hook the closure of a thread, i.e. if your system has +// thread-local storage. +#ifndef OSG_TERM_THREAD_CODE +#define OSG_TERM_THREAD_CODE +#endif + +typedef void *og_thread_t; +typedef void *og_mutex_t; +typedef void *og_sema_t; +typedef void *og_tls_t; + +#ifdef __cplusplus +extern "C" { +#endif + +OSG_PREFIX void OGSleep(int is); +OSG_PREFIX void OGUSleep(int ius); +OSG_PREFIX double OGGetAbsoluteTime(); +OSG_PREFIX double OGGetFileTime(const char *file); +OSG_PREFIX og_thread_t OGCreateThread(void *(routine) (void *), + void *parameter); +OSG_PREFIX void *OGJoinThread(og_thread_t ot); +OSG_PREFIX void OGCancelThread(og_thread_t ot); +OSG_PREFIX og_mutex_t OGCreateMutex(); +OSG_PREFIX void OGLockMutex(og_mutex_t om); +OSG_PREFIX void OGUnlockMutex(og_mutex_t om); +OSG_PREFIX void OGDeleteMutex(og_mutex_t om); +OSG_PREFIX og_sema_t OGCreateSema(); +OSG_PREFIX int OGGetSema(og_sema_t os); +OSG_PREFIX void OGLockSema(og_sema_t os); +OSG_PREFIX void OGUnlockSema(og_sema_t os); +OSG_PREFIX void OGDeleteSema(og_sema_t os); +OSG_PREFIX og_tls_t OGCreateTLS(); +OSG_PREFIX void OGDeleteTLS(og_tls_t key); +OSG_PREFIX void *OGGetTLS(og_tls_t key); +OSG_PREFIX void OGSetTLS(og_tls_t key, void *data); + +#ifdef __cplusplus +}; +#endif + +#ifndef OSG_NO_IMPLEMENTATION + +#if defined(WIN32) || defined(WINDOWS) || defined(_WIN32) +#define USE_WINDOWS +#endif + + +#ifdef __cplusplus +extern "C" { +#endif + + +#ifdef USE_WINDOWS + +#include +#include + +OSG_PREFIX void OGSleep(int is) +{ + Sleep(is * 1000); +} + +OSG_PREFIX void OGUSleep(int ius) +{ + Sleep(ius / 1000); +} + +OSG_PREFIX double OGGetAbsoluteTime() +{ + static LARGE_INTEGER lpf; + LARGE_INTEGER li; + + if (!lpf.QuadPart) { + QueryPerformanceFrequency(&lpf); + } + + QueryPerformanceCounter(&li); + return (double) li.QuadPart / (double) lpf.QuadPart; +} + + +OSG_PREFIX double OGGetFileTime(const char *file) +{ + FILETIME ft; + + HANDLE h = CreateFile(file, GENERIC_READ, FILE_SHARE_READ, NULL, + OPEN_EXISTING, 0, NULL); + + if (h == INVALID_HANDLE_VALUE) + return -1; + + GetFileTime(h, 0, 0, &ft); + + CloseHandle(h); + + return ft.dwHighDateTime + ft.dwLowDateTime; +} + + +OSG_PREFIX og_thread_t OGCreateThread(void *(routine) (void *), void *parameter) +{ + return (og_thread_t) CreateThread(0, 0, (LPTHREAD_START_ROUTINE) routine, + parameter, 0, 0); +} + +OSG_PREFIX void *OGJoinThread(og_thread_t ot) +{ + WaitForSingleObject(ot, INFINITE); + OSG_TERM_THREAD_CODE + CloseHandle(ot); + return 0; +} + +OSG_PREFIX void OGCancelThread(og_thread_t ot) +{ + OSG_TERM_THREAD_CODE + TerminateThread(ot, 0); + CloseHandle(ot); +} + +OSG_PREFIX og_mutex_t OGCreateMutex() +{ + return CreateMutex(0, 0, 0); +} + +OSG_PREFIX void OGLockMutex(og_mutex_t om) +{ + WaitForSingleObject(om, INFINITE); +} + +OSG_PREFIX void OGUnlockMutex(og_mutex_t om) +{ + ReleaseMutex(om); +} + +OSG_PREFIX void OGDeleteMutex(og_mutex_t om) +{ + CloseHandle(om); +} + + + +OSG_PREFIX og_sema_t OGCreateSema() +{ + HANDLE sem = CreateSemaphore(0, 0, 32767, 0); + return (og_sema_t) sem; +} + +OSG_PREFIX int OGGetSema(og_sema_t os) +{ + typedef LONG NTSTATUS; + HANDLE sem = (HANDLE) os; + typedef NTSTATUS(NTAPI * _NtQuerySemaphore)( + HANDLE SemaphoreHandle, + DWORD SemaphoreInformationClass, /* Would be SEMAPHORE_INFORMATION_CLASS + */ + PVOID SemaphoreInformation, /* but this is to much to dump here */ + ULONG SemaphoreInformationLength, PULONG ReturnLength OPTIONAL); + + typedef struct _SEMAPHORE_BASIC_INFORMATION { + ULONG CurrentCount; + ULONG MaximumCount; + } SEMAPHORE_BASIC_INFORMATION; + + + static _NtQuerySemaphore NtQuerySemaphore; + SEMAPHORE_BASIC_INFORMATION BasicInfo; + NTSTATUS Status; + + if (!NtQuerySemaphore) { + NtQuerySemaphore = (_NtQuerySemaphore) GetProcAddress( + GetModuleHandle("ntdll.dll"), "NtQuerySemaphore"); + if (!NtQuerySemaphore) { + return -1; + } + } + + + Status = NtQuerySemaphore(sem, 0 /*SemaphoreBasicInformation*/, &BasicInfo, + sizeof(SEMAPHORE_BASIC_INFORMATION), NULL); + + if (Status == ERROR_SUCCESS) { + return BasicInfo.CurrentCount; + } + + return -2; +} + +OSG_PREFIX void OGLockSema(og_sema_t os) +{ + WaitForSingleObject((HANDLE) os, INFINITE); +} + +OSG_PREFIX void OGUnlockSema(og_sema_t os) +{ + ReleaseSemaphore((HANDLE) os, 1, 0); +} + +OSG_PREFIX void OGDeleteSema(og_sema_t os) +{ + CloseHandle(os); +} + +OSG_PREFIX og_tls_t OGCreateTLS() +{ + return (og_tls_t) (intptr_t) TlsAlloc(); +} + +OSG_PREFIX void OGDeleteTLS(og_tls_t key) +{ + TlsFree((DWORD) (intptr_t) key); +} + +OSG_PREFIX void *OGGetTLS(og_tls_t key) +{ + return TlsGetValue((DWORD) (intptr_t) key); +} + +OSG_PREFIX void OGSetTLS(og_tls_t key, void *data) +{ + TlsSetValue((DWORD) (intptr_t) key, data); +} + +#elif defined(__wasm__) + +// We don't actually have any function defintions here. +// The outside system will handle it. + +#else + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include +#include + +OSG_PREFIX void OGSleep(int is) +{ + sleep(is); +} + +OSG_PREFIX void OGUSleep(int ius) +{ + usleep(ius); +} + +OSG_PREFIX double OGGetAbsoluteTime() +{ + struct timeval tv; + gettimeofday(&tv, 0); + return ((double) tv.tv_usec) / 1000000. + (tv.tv_sec); +} + +OSG_PREFIX double OGGetFileTime(const char *file) +{ + struct stat buff; + + int r = stat(file, &buff); + + if (r < 0) { + return -1; + } + + return buff.st_mtime; +} + + + +OSG_PREFIX og_thread_t OGCreateThread(void *(routine) (void *), void *parameter) +{ + pthread_t *ret = (pthread_t *) malloc(sizeof(pthread_t)); + if (!ret) + return 0; + int r = pthread_create(ret, 0, routine, parameter); + if (r) { + free(ret); + return 0; + } + return (og_thread_t) ret; +} + +OSG_PREFIX void *OGJoinThread(og_thread_t ot) +{ + void *retval; + if (!ot) { + return 0; + } + pthread_join(*(pthread_t *) ot, &retval); + OSG_TERM_THREAD_CODE + free(ot); + return retval; +} + +OSG_PREFIX void OGCancelThread(og_thread_t ot) +{ + if (!ot) { + return; + } +#ifdef ANDROID + pthread_kill(*(pthread_t *) ot, SIGTERM); +#else + pthread_cancel(*(pthread_t *) ot); +#endif + OSG_TERM_THREAD_CODE + free(ot); +} + +OSG_PREFIX og_mutex_t OGCreateMutex() +{ + pthread_mutexattr_t mta; + og_mutex_t r = malloc(sizeof(pthread_mutex_t)); + if (!r) + return 0; + + pthread_mutexattr_init(&mta); + pthread_mutexattr_settype(&mta, PTHREAD_MUTEX_RECURSIVE); + + pthread_mutex_init((pthread_mutex_t *) r, &mta); + + return r; +} + +OSG_PREFIX void OGLockMutex(og_mutex_t om) +{ + if (!om) { + return; + } + pthread_mutex_lock((pthread_mutex_t *) om); +} + +OSG_PREFIX void OGUnlockMutex(og_mutex_t om) +{ + if (!om) { + return; + } + pthread_mutex_unlock((pthread_mutex_t *) om); +} + +OSG_PREFIX void OGDeleteMutex(og_mutex_t om) +{ + if (!om) { + return; + } + + pthread_mutex_destroy((pthread_mutex_t *) om); + free(om); +} + + + +OSG_PREFIX og_sema_t OGCreateSema() +{ + sem_t *sem = (sem_t *) malloc(sizeof(sem_t)); + if (!sem) + return 0; + sem_init(sem, 0, 0); + return (og_sema_t) sem; +} + +OSG_PREFIX int OGGetSema(og_sema_t os) +{ + int valp; + sem_getvalue((sem_t *) os, &valp); + return valp; +} + + +OSG_PREFIX void OGLockSema(og_sema_t os) +{ + sem_wait((sem_t *) os); +} + +OSG_PREFIX void OGUnlockSema(og_sema_t os) +{ + sem_post((sem_t *) os); +} + +OSG_PREFIX void OGDeleteSema(og_sema_t os) +{ + sem_destroy((sem_t *) os); + free(os); +} + +OSG_PREFIX og_tls_t OGCreateTLS() +{ + pthread_key_t ret = 0; + pthread_key_create(&ret, 0); + return (og_tls_t) (intptr_t) ret; +} + +OSG_PREFIX void OGDeleteTLS(og_tls_t key) +{ + pthread_key_delete((pthread_key_t) (intptr_t) key); +} + +OSG_PREFIX void *OGGetTLS(og_tls_t key) +{ + return pthread_getspecific((pthread_key_t) (intptr_t) key); +} + +OSG_PREFIX void OGSetTLS(og_tls_t key, void *data) +{ + pthread_setspecific((pthread_key_t) (intptr_t) key, data); +} + +#endif + +#ifdef __cplusplus +}; +#endif + +#endif // OSG_NO_IMPLEMENTATION + +#endif //_OS_GENERIC_H diff --git a/virtio-snd.c b/virtio-snd.c new file mode 100644 index 0000000..ec8dafb --- /dev/null +++ b/virtio-snd.c @@ -0,0 +1,564 @@ +#include +#include +#include + +#define CNFA_IMPLEMENTATION +#include "CNFA_sf.h" + +#include "common.h" +#include "device.h" +#include "riscv.h" +#include "riscv_private.h" +#include "virtio.h" + +#define VSND_DEV_CNT_MAX 1 + +#define VSND_FEATURES_0 0 +#define VSND_FEATURES_1 1 +#define VSND_QUEUE_NUM_MAX 1024 +#define VSND_QUEUE (vsnd->queues[vsnd->QueueSel]) + +#define PRIV(x) ((struct virtio_snd_config *) x->priv) + +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; +}