diff --git a/README.md b/README.md index e0ede423..60aa2722 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ A minimalist RISC-V system emulator capable of running Linux the kernel and corr the program cannot write the PCM frames into guest OS ALSA stack. - For instance, the following buffer/period size settings on `aplay` has been tested with broken and stutter effects yet complete with no any errors: `aplay --buffer-size=32768 --period-size=4096 /usr/share/sounds/alsa/Front_Center.wav`. + - For capture, ALSA usually gets stuck in XRUN state, so you may need to try multiple times. - virtio-input exposes SDL-backed keyboard and mouse devices to the guest. - virtio-gpu exposes a minimal 2D DRM/KMS device to the guest. Linux can bind the `virtio_gpu` driver and create `/dev/dri/card0`. diff --git a/device.h b/device.h index 02198be4..ef9e084a 100644 --- a/device.h +++ b/device.h @@ -466,6 +466,7 @@ typedef struct { uint32_t InterruptStatus; /* supplied by environment */ uint32_t *ram; + int wake_fd; /* implementation-specific */ void *priv; } virtio_snd_state_t; diff --git a/main.c b/main.c index a0b53334..46a6df33 100644 --- a/main.c +++ b/main.c @@ -1010,6 +1010,7 @@ static int semu_init(emu_state_t *emu, int argc, char **argv) emu->sswi.ssip = calloc(vm->n_hart, sizeof(uint32_t)); emu->sswi.n_hart = vm->n_hart; #if SEMU_HAS(VIRTIOSND) + emu->vsnd.wake_fd = -1; if (!virtio_snd_init(&(emu->vsnd))) fprintf(stderr, "No virtio-snd functioned\n"); emu->vsnd.ram = emu->ram; @@ -1064,6 +1065,9 @@ static int semu_init(emu_state_t *emu, int argc, char **argv) return EXIT_FAILURE; } } +#if SEMU_HAS(VIRTIOSND) + emu->vsnd.wake_fd = emu->wake_fd[1]; +#endif #endif emu->peripheral_update_ctr = 0; diff --git a/virtio-snd.c b/virtio-snd.c index 8527cb83..19b3eeca 100644 --- a/virtio-snd.c +++ b/virtio-snd.c @@ -13,7 +13,7 @@ #include "utils.h" #include "virtio.h" -#define VSND_DEV_CNT_MAX 1 +#define VSND_DEV_CNT_MAX 2 #define VSND_QUEUE_NUM_MAX 1024 #define vsndq (vsnd->queues[vsnd->QueueSel]) @@ -314,10 +314,12 @@ static virtio_snd_prop_t vsnd_props[VSND_DEV_CNT_MAX] = { }, }; static int vsnd_dev_cnt = 0; - -static pthread_mutex_t virtio_snd_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t virtio_snd_tx_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t virtio_snd_rx_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t virtio_snd_tx_cond = PTHREAD_COND_INITIALIZER; +static pthread_cond_t virtio_snd_rx_cond = PTHREAD_COND_INITIALIZER; static int tx_ev_notify; +static int rx_ev_notify; /* vsnd virtq callback type */ typedef int (*vsnd_virtq_cb)(virtio_snd_state_t *, /* vsnd state */ @@ -326,135 +328,156 @@ typedef int (*vsnd_virtq_cb)(virtio_snd_state_t *, /* vsnd state */ uint32_t * /* response length */); /* Forward declaration */ -static int virtio_snd_stream_cb(const void *input, - void *output, - unsigned long frame_cnt, - const PaStreamCallbackTimeInfo *time_info, - PaStreamCallbackFlags status_flags, - void *user_data); +static int virtio_snd_tx_stream_cb(const void *input, + void *output, + unsigned long frame_cnt, + const PaStreamCallbackTimeInfo *time_info, + PaStreamCallbackFlags status_flags, + void *user_data); +static int virtio_snd_rx_stream_cb(const void *input, + void *output, + unsigned long frame_cnt, + const PaStreamCallbackTimeInfo *time_info, + PaStreamCallbackFlags status_flags, + void *user_data); +static void __virtio_snd_tx_frame_enqueue(void *payload, + uint32_t n, + uint32_t stream_id); +static void __virtio_snd_tx_frame_dequeue(void *payload, + uint32_t n, + uint32_t stream_id); +static void __virtio_snd_rx_frame_enqueue(const void *payload, + uint32_t n, + uint32_t stream_id); +static void __virtio_snd_rx_frame_dequeue(void *out, + uint32_t n, + uint32_t stream_id); static void virtio_queue_notify_handler(virtio_snd_state_t *vsnd, int index); -static void __virtio_snd_frame_enqueue(void *payload, - uint32_t n, - uint32_t stream_id); + typedef struct { struct virtq_desc vq_desc; struct list_head q; } virtq_desc_queue_node_t; -/* Flush only stream_id 0. - * FIXME: let TX queue flushing can select arbitrary stream_id. +/* Flush only default streams (TX: stream_id 0, RX: stream_id 1). + * FIXME: let TX and RX queue to flush can select arbitrary stream_id. */ -static uint32_t flush_stream_id = 0; - -#define VSND_TX_QUEUE_BODY(WRITE) \ - do { \ - /* A PCM I/O message uses at least 3 virtqueue descriptors to \ - * represent a PCM data of a period size. \ - * The first part contains one descriptor as follows: \ - * struct virtio_snd_pcm_xfer \ - * The second part contains one or more descriptors \ - * representing PCM frames. \ - * the last part contains one descriptor as follows: \ - * struct virtio_snd_pcm_status \ - */ \ - virtq_desc_queue_node_t *node; \ - struct list_head q; \ - INIT_LIST_HEAD(&q); \ - \ - /* Collect the descriptors */ \ - int cnt = 0; \ - for (;;) { \ - /* The size of the `struct virtq_desc` is 4 words */ \ - const uint32_t *desc = \ - &vsnd->ram[queue->QueueDesc + desc_idx * 4]; \ - \ - /* Retrieve the fields of current descriptor */ \ - node = (virtq_desc_queue_node_t *) malloc(sizeof(*node)); \ - node->vq_desc.addr = desc[0]; \ - node->vq_desc.len = desc[2]; \ - node->vq_desc.flags = desc[3]; \ - list_push(&node->q, &q); \ - desc_idx = desc[3] >> 16; /* vq_desc[desc_cnt].next */ \ - \ - cnt++; \ - \ - /* Leave the loop if next-flag is not set */ \ - if (!(desc[3] & VIRTIO_DESC_F_NEXT)) \ - break; \ - } \ - \ - int idx = 0; \ - uint32_t stream_id = 0; /* Explicitly set the stream_id */ \ - uintptr_t base = (uintptr_t) vsnd->ram; \ - uint32_t ret_len = 0; \ - uint8_t bad_msg_err = 0; \ - list_for_each_entry (node, &q, q) { \ - uint32_t addr = node->vq_desc.addr; \ - uint32_t len = node->vq_desc.len; \ - if (idx == 0) { /* the first descriptor */ \ - const virtio_snd_pcm_xfer_t *request = \ - (virtio_snd_pcm_xfer_t *) (base + addr); \ - stream_id = request->stream_id; \ - IIF(WRITE)(/* enqueue frames */ \ - bad_msg_err = \ - stream_id >= VSND_DEV_CNT_MAX ? 1 : 0; \ - , /* flush queue */ \ - bad_msg_err = \ - stream_id != flush_stream_id \ - ? 1 \ - : 0; /* select only stream_id 0 */ \ - ) goto early_continue; \ - } else if (idx == cnt - 1) { /* the last descriptor */ \ - IIF(WRITE)( /* enqueue frames */ \ - , /* flush queue */ \ - if (bad_msg_err == 1) { \ - fprintf(stderr, \ - "ignore flush stream_id %" PRIu32 "\n", \ - stream_id); \ - goto early_continue; \ - } fprintf(stderr, "flush stream_id %" PRIu32 "\n", \ - stream_id);) \ - virtio_snd_pcm_status_t *response = \ - (virtio_snd_pcm_status_t *) (base + addr); \ - response->status = \ - bad_msg_err ? VIRTIO_SND_S_IO_ERR : VIRTIO_SND_S_OK; \ - response->latency_bytes = ret_len; \ - *plen = sizeof(*response); \ - goto early_continue; \ - } \ - \ - IIF(WRITE)(/* enqueue frames */ \ - void *payload = (void *) (base + addr); \ - if (bad_msg_err == 0) __virtio_snd_frame_enqueue( \ - payload, len, stream_id); \ - , /* flush queue */ \ - (void) stream_id; \ - /* Suppress unused variable warning. */) ret_len += \ - len; \ - \ - early_continue: \ - idx++; \ - } \ - \ - if (bad_msg_err != 0) \ - goto finally; \ - IIF(WRITE)(/* enque frames */ \ - virtio_snd_prop_t *props = &vsnd_props[stream_id]; \ - pthread_mutex_lock(&props->lock.lock); \ +static uint32_t flush_tx_stream_id = 0; +static uint32_t flush_rx_stream_id = 1; + +#define VSND_TX_QUEUE_BODY(WRITE, TR, VERB) \ + do { \ + /* A PCM I/O message uses at least 3 virtqueue descriptors to \ + * represent a PCM data of a period size. \ + * The first part contains one descriptor as follows: \ + * struct virtio_snd_pcm_xfer \ + * The second part contains one or more descriptors \ + * representing PCM frames. \ + * the last part contains one descriptor as follows: \ + * struct virtio_snd_pcm_status \ + */ \ + virtq_desc_queue_node_t *node; \ + struct list_head q; \ + INIT_LIST_HEAD(&q); \ + \ + /* Collect the descriptors */ \ + int cnt = 0; \ + for (;;) { \ + /* The size of the `struct virtq_desc` is 4 words */ \ + const uint32_t *desc = \ + &vsnd->ram[queue->QueueDesc + desc_idx * 4]; \ + \ + /* Retrieve the fields of current descriptor */ \ + node = (virtq_desc_queue_node_t *) malloc(sizeof(*node)); \ + node->vq_desc.addr = desc[0]; \ + node->vq_desc.len = desc[2]; \ + node->vq_desc.flags = desc[3]; \ + list_push(&node->q, &q); \ + desc_idx = desc[3] >> 16; /* vq_desc[desc_cnt].next */ \ + \ + cnt++; \ + \ + /* Leave the loop if next-flag is not set */ \ + if (!(desc[3] & VIRTIO_DESC_F_NEXT)) \ + break; \ + } \ + \ + int idx = 0; \ + uint32_t stream_id = 0; /* Explicitly set the stream_id */ \ + uintptr_t base = (uintptr_t) vsnd->ram; \ + uint32_t ret_len = 0; \ + uint8_t bad_msg_err = 0; \ + list_for_each_entry (node, &q, q) { \ + uint32_t addr = node->vq_desc.addr; \ + uint32_t len = node->vq_desc.len; \ + if (idx == 0) { /* the first descriptor */ \ + const virtio_snd_pcm_xfer_t *request = \ + (virtio_snd_pcm_xfer_t *) (base + addr); \ + stream_id = request->stream_id; \ + IIF(WRITE) \ + (/* enqueue frames */ \ + bad_msg_err = stream_id >= VSND_DEV_CNT_MAX ? 1 : 0; \ + , /* flush queue */ \ + bad_msg_err = stream_id != flush_##VERB##_stream_id \ + ? 1 \ + : 0; /* select only stream_id 0 */ \ + ) goto early_continue; \ + } else if (idx == cnt - 1) { /* the last descriptor */ \ + IIF(WRITE) \ + ( /* enqueue frames */ \ + , /* flush queue */ \ + if (bad_msg_err == 1) { \ + fprintf(stderr, "ignore flush stream_id %" PRIu32 "\n", \ + stream_id); \ + goto early_continue; \ + } fprintf(stderr, "flush stream_id %" PRIu32 "\n", \ + stream_id);) virtio_snd_pcm_status_t *response = \ + (virtio_snd_pcm_status_t *) (base + addr); \ + response->status = \ + bad_msg_err ? VIRTIO_SND_S_IO_ERR : VIRTIO_SND_S_OK; \ + response->latency_bytes = ret_len; \ + *plen = sizeof(*response); \ + goto early_continue; \ + } \ + \ + IIF(WRITE) \ + (/* enqueue frames */ \ + void *payload = (void *) (base + addr); \ + if (bad_msg_err == 0) IIF(TR)( \ + __virtio_snd_tx_frame_enqueue(payload, len, stream_id); \ + , __virtio_snd_rx_frame_dequeue( \ + payload, len, stream_id);), /* flush queue */ \ + (void) stream_id; \ + /* Suppress unused variable warning. */) ret_len += len; \ + \ + early_continue: \ + idx++; \ + } \ + \ + if (bad_msg_err != 0) \ + goto finally; \ + IIF(WRITE) \ + (/* enque frames */ \ + virtio_snd_prop_t *props = &vsnd_props[stream_id]; \ + IIF(TR)(/* TX */ pthread_mutex_lock(&props->lock.lock); \ props->lock.buf_ev_notify++; \ pthread_cond_signal(&props->lock.readable); \ - pthread_mutex_unlock(&props->lock.lock);, /* flush queue */ \ - ) \ - \ - /* Tear down the descriptor list and free space. */ \ - virtq_desc_queue_node_t *tmp = NULL; \ - list_for_each_entry_safe (node, tmp, &q, q) { \ - list_del(&node->q); \ - free(node); \ - } \ - \ - finally: \ - return 0; \ + pthread_mutex_unlock(&props->lock.lock);, \ + /* RX */ pthread_mutex_lock(&props->lock.lock); \ + props->lock.buf_ev_notify--; \ + pthread_cond_signal(&props->lock.writable); \ + pthread_mutex_unlock(&props->lock.lock); \ + ), /* flush queue */ \ + ) \ + \ + finally : /* Tear down the descriptor list and free space. */ \ + virtq_desc_queue_node_t *tmp = NULL; \ + list_for_each_entry_safe (node, tmp, &q, q) { \ + list_del(&node->q); \ + free(node); \ + } \ + \ + return 0; \ } while (0) static int virtio_snd_tx_desc_handler(virtio_snd_state_t *vsnd, @@ -462,15 +485,31 @@ static int virtio_snd_tx_desc_handler(virtio_snd_state_t *vsnd, uint32_t desc_idx, uint32_t *plen) { - VSND_TX_QUEUE_BODY(1); + VSND_TX_QUEUE_BODY(1, 1, tx); } -static int virtio_snd_io_desc_flush_handler(virtio_snd_state_t *vsnd, +static int virtio_snd_rx_desc_handler(virtio_snd_state_t *vsnd, + const virtio_snd_queue_t *queue, + uint32_t desc_idx, + uint32_t *plen) +{ + VSND_TX_QUEUE_BODY(1, 0, rx); +} + +static int virtio_snd_io_desc_tx_flush_handler(virtio_snd_state_t *vsnd, const virtio_snd_queue_t *queue, uint32_t desc_idx, uint32_t *plen) { - VSND_TX_QUEUE_BODY(0); + VSND_TX_QUEUE_BODY(0, 1, tx); +} + +static int virtio_snd_io_desc_rx_flush_handler(virtio_snd_state_t *vsnd, + const virtio_snd_queue_t *queue, + uint32_t desc_idx, + uint32_t *plen) +{ + VSND_TX_QUEUE_BODY(0, 1, rx); } static void virtio_snd_set_fail(virtio_snd_state_t *vsnd) @@ -665,25 +704,46 @@ static void virtio_snd_read_pcm_prepare(const virtio_snd_pcm_hdr_t *query, uint32_t cnfa_period_frames = cnfa_period_bytes / VSND_CNFA_FRAME_SZ; INIT_LIST_HEAD(&props->buf_queue_head); + uint32_t dir = props->p.direction; + PaError err = paNoError; props->lock.releasing = 0; props->lock.buf_ev_notify = 0; - PaStreamParameters params = { - .device = Pa_GetDefaultOutputDevice(), - .channelCount = props->pp.channels, - .sampleFormat = paInt16, - .suggestedLatency = 0.1, /* 100 ms */ - .hostApiSpecificStreamInfo = NULL, - }; - PaError err = Pa_OpenStream(&props->pa_stream, NULL, /* no input */ - ¶ms, rate, cnfa_period_frames, paClipOff, - virtio_snd_stream_cb, &props->v); - if (err != paNoError) { - fprintf(stderr, "Cannot create PortAudio\n"); - printf("PortAudio error: %s\n", Pa_GetErrorText(err)); - return; + if (dir == VIRTIO_SND_D_OUTPUT) { + PaStreamParameters params = { + .device = Pa_GetDefaultOutputDevice(), + .channelCount = props->pp.channels, + .sampleFormat = paInt16, /* FIXME: set mapping of format */ + .suggestedLatency = 0.1, /* 100 ms */ + .hostApiSpecificStreamInfo = NULL, + }; + err = Pa_OpenStream(&props->pa_stream, NULL, /* no input */ + ¶ms, rate, cnfa_period_frames, paClipOff, + virtio_snd_tx_stream_cb, &props->v); + if (err != paNoError) + goto pa_err; + } else if (dir == VIRTIO_SND_D_INPUT) { + PaStreamParameters params = { + .device = Pa_GetDefaultInputDevice(), + .channelCount = props->pp.channels, + .sampleFormat = paInt16, /* FIXME: set mapping of format */ + .hostApiSpecificStreamInfo = NULL, + }; + /* Change 'period_bytes' and 'sz' to the suggestion mentioned by the + * driver. */ + cnfa_period_frames = props->pp.period_bytes / VSND_CNFA_FRAME_SZ; + err = Pa_OpenStream(&props->pa_stream, ¶ms, NULL /* no output */, + rate, cnfa_period_frames, paClipOff, + virtio_snd_rx_stream_cb, &props->v); + if (err != paNoError) + goto pa_err; } *plen = 0; + return; + +pa_err: + fprintf(stderr, "Cannot create PortAudio\n"); + printf("PortAudio error: %s\n", Pa_GetErrorText(err)); } static void virtio_snd_read_pcm_start(const virtio_snd_pcm_hdr_t *query, @@ -796,7 +856,7 @@ static void virtio_snd_read_pcm_release(const virtio_snd_pcm_hdr_t *query, *plen = 0; } -static void __virtio_snd_frame_dequeue(void *out, +static void __virtio_snd_tx_frame_dequeue(void *out, uint32_t n, uint32_t stream_id) { @@ -838,7 +898,43 @@ static void __virtio_snd_frame_dequeue(void *out, pthread_mutex_unlock(&props->lock.lock); } -static int virtio_snd_stream_cb(const void *input, +static void __virtio_snd_rx_frame_dequeue(void *out, + uint32_t n, + uint32_t stream_id) +{ + virtio_snd_prop_t *props = &vsnd_props[stream_id]; + + pthread_mutex_lock(&props->lock.lock); + while (!(props->lock.buf_ev_notify > 0 && !Pa_IsStreamStopped(props->pa_stream))) + pthread_cond_wait(&props->lock.readable, &props->lock.lock); + + fprintf(stderr, "deque start\n"); + /* Get the PCM frames from queue */ + uint32_t written_bytes = 0; + while (!list_empty(&props->buf_queue_head) && written_bytes < n) { + vsnd_buf_queue_node_t *node = + list_first_entry(&props->buf_queue_head, vsnd_buf_queue_node_t, q); + uint32_t left = n - written_bytes; + uint32_t actual = node->len - node->pos; + uint32_t len = + left < actual ? left : actual; /* Naive min implementation */ + + memcpy(out + written_bytes, node->addr + node->pos, len); + fprintf(stderr, "write %" PRIu32 " bytes\n", len); + + written_bytes += len; + node->pos += len; + if (node->pos >= node->len) { + list_del(&node->q); + free(node->addr); + free(node); + } + } + fprintf(stderr, "deque end\n"); + pthread_mutex_unlock(&props->lock.lock); +} + +static int virtio_snd_tx_stream_cb(const void *input, void *output, unsigned long frame_cnt, const PaStreamCallbackTimeInfo *time_info, @@ -850,7 +946,31 @@ static int virtio_snd_stream_cb(const void *input, int channels = vsnd_props[id].pp.channels; uint32_t out_buf_sz = frame_cnt * channels; uint32_t out_buf_bytes = out_buf_sz * VSND_CNFA_FRAME_SZ; - __virtio_snd_frame_dequeue(output, out_buf_bytes, id); + __virtio_snd_tx_frame_dequeue(output, out_buf_bytes, id); + + return paContinue; +} + +static int virtio_snd_rx_stream_cb(const void *input, + void *output, + unsigned long frame_cnt, + const PaStreamCallbackTimeInfo *time_info, + PaStreamCallbackFlags status_flags, + void *user_data) +{ + /* suppress unused variable warning */ + (void) output; + (void) time_info; + (void) status_flags; + + vsnd_stream_sel_t *v_ptr = (vsnd_stream_sel_t *) user_data; + uint32_t id = v_ptr->stream_id; + virtio_snd_prop_t *props = &vsnd_props[id]; + int channels = props->pp.channels; + uint32_t out_buf_sz = frame_cnt * channels; + uint32_t out_buf_bytes = out_buf_sz * VSND_CNFA_FRAME_SZ; + + __virtio_snd_rx_frame_enqueue(input, out_buf_bytes, id); return paContinue; } @@ -947,14 +1067,14 @@ static vsnd_virtq_cb __virtio_snd_queue_op_tbl[8] = { virtio_snd_ctrl_desc_handler, /* control queue */ NULL, /* event queue */ virtio_snd_tx_desc_handler, /* TX queue */ - NULL, /* RX queue */ + virtio_snd_rx_desc_handler, /* RX queue */ NULL, /* no need to flush control queue, so trigger a failed null check */ NULL, /* no need to flush event queue, so trigger a failed null check */ - virtio_snd_io_desc_flush_handler, /* flush TX queue */ - NULL, /* flush RX queue */ + virtio_snd_io_desc_tx_flush_handler, /* flush TX queue */ + virtio_snd_io_desc_rx_flush_handler, /* flush RX queue */ }; -static void __virtio_snd_frame_enqueue(void *payload, +static void __virtio_snd_tx_frame_enqueue(void *payload, uint32_t n, uint32_t stream_id) { @@ -995,6 +1115,40 @@ static void __virtio_snd_frame_enqueue(void *payload, pthread_mutex_unlock(&props->lock.lock); } +static void __virtio_snd_rx_frame_enqueue(const void *payload, + uint32_t n, + uint32_t stream_id) +{ + virtio_snd_prop_t *props = &vsnd_props[stream_id]; + + /*pthread_mutex_lock(&props->lock.lock); + while (props->lock.buf_ev_notify > 0) + pthread_cond_wait(&props->lock.writable, &props->lock.lock);*/ + if (pthread_mutex_trylock(&props->lock.lock) == 0) { + fprintf(stderr, "enque start \n"); + /* Add a PCM frame to queue */ + vsnd_buf_queue_node_t *node = malloc(sizeof(*node)); + if (!node) + goto rx_frame_enqueue_final; + node->addr = malloc(sizeof(*node->addr) * n); + if (!node->addr) { + free(node); + goto rx_frame_enqueue_final; + } + memcpy(node->addr, payload, n); + node->len = n; + node->pos = 0; + list_push(&node->q, &props->buf_queue_head); + + props->lock.buf_ev_notify++; + pthread_cond_signal(&props->lock.readable); + + rx_frame_enqueue_final: + fprintf(stderr, "enque end\n"); + pthread_mutex_unlock(&props->lock.lock); + } +} + static void virtio_queue_notify_handler(virtio_snd_state_t *vsnd, int index) { uint32_t *ram = vsnd->ram; @@ -1049,35 +1203,43 @@ static void virtio_queue_notify_handler(virtio_snd_state_t *vsnd, int index) 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 */ + + /* Publish used-ring writes before making the IRQ visible to the guest. */ + if (!(ram[queue->QueueAvail] & 1)) { + __atomic_fetch_or(&vsnd->InterruptStatus, VIRTIO_INT__USED_RING, + __ATOMIC_RELEASE); + if (vsnd->wake_fd >= 0) { + char wake_byte = 1; + if (write(vsnd->wake_fd, &wake_byte, 1) < 0) { + /* ignore error */ + } + } + } } - - /* 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 */ - - /* Publish used-ring writes before making the IRQ visible to the guest. */ - if (!(ram[queue->QueueAvail] & 1)) - __atomic_fetch_or(&vsnd->InterruptStatus, VIRTIO_INT__USED_RING, - __ATOMIC_RELEASE); } -/* TX thread context */ -/* Receive PCM frames from driver. */ -static void *func(void *args) -{ - virtio_snd_state_t *vsnd = (virtio_snd_state_t *) args; - for (;;) { - pthread_mutex_lock(&virtio_snd_mutex); - while (tx_ev_notify <= 0) - pthread_cond_wait(&virtio_snd_tx_cond, &virtio_snd_mutex); - - tx_ev_notify--; - pthread_mutex_unlock(&virtio_snd_mutex); - - virtio_queue_notify_handler(vsnd, 2); +#define VSND_GEN_IO_FUNC(VERB, queue_idx) \ + static void *VERB##_func(void *args) \ + { \ + virtio_snd_state_t *vsnd = (virtio_snd_state_t *) args; \ + for (;;) { \ + pthread_mutex_lock(&virtio_snd_##VERB##_mutex); \ + while (VERB##_ev_notify <= 0) \ + pthread_cond_wait(&virtio_snd_##VERB##_cond, \ + &virtio_snd_##VERB##_mutex); \ + VERB##_ev_notify--; \ + pthread_mutex_unlock(&virtio_snd_##VERB##_mutex); \ + virtio_queue_notify_handler( \ + vsnd, queue_idx); \ + } \ + pthread_exit(NULL); \ } - pthread_exit(NULL); -} + +VSND_GEN_IO_FUNC(tx, 2) +VSND_GEN_IO_FUNC(rx, 3) static bool virtio_snd_reg_read(virtio_snd_state_t *vsnd, uint32_t addr, @@ -1189,10 +1351,16 @@ static bool virtio_snd_reg_write(virtio_snd_state_t *vsnd, virtio_queue_notify_handler(vsnd, value); break; case VSND_QUEUE_TX: - pthread_mutex_lock(&virtio_snd_mutex); + pthread_mutex_lock(&virtio_snd_tx_mutex); tx_ev_notify++; pthread_cond_signal(&virtio_snd_tx_cond); - pthread_mutex_unlock(&virtio_snd_mutex); + pthread_mutex_unlock(&virtio_snd_tx_mutex); + break; + case VSND_QUEUE_RX: + //pthread_mutex_lock(&virtio_snd_rx_mutex); + rx_ev_notify++; + pthread_cond_signal(&virtio_snd_rx_cond); + //pthread_mutex_unlock(&virtio_snd_rx_mutex); break; default: fprintf(stderr, "value %d not supported\n", value); @@ -1285,11 +1453,17 @@ bool virtio_snd_init(virtio_snd_state_t *vsnd) 0; /* virtio-snd device does not support control elements */ tx_ev_notify = 0; - pthread_t tid; - if (pthread_create(&tid, NULL, func, vsnd) != 0) { + rx_ev_notify = 0; + pthread_t tx_tid, rx_tid; + if (pthread_create(&tx_tid, NULL, tx_func, vsnd) != 0) { fprintf(stderr, "cannot create TX thread\n"); return false; } + if (pthread_create(&rx_tid, NULL, rx_func, vsnd) != 0) { + fprintf(stderr, "cannot create RX thread\n"); + return false; + } + /* Initialize PortAudio */ PaError err = Pa_Initialize(); if (err != paNoError) {