/**************************************************************************** * drivers/virtio/virtio-snd.c * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. The * ASF licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include "virtio-snd.h" /**************************************************************************** * Private Types ****************************************************************************/ /* Map for converting VirtIO frame rate to nuttx frame rate */ struct virtio_snd_rate_map_s { unsigned int nrate; unsigned int sps; }; /* Map for nuttx bps to virtio pcm subformat */ struct virtio_snd_format_map_s { uint8_t nformat; unsigned int bps; }; /* Buffer for pcm data tx/rx transfer */ struct virtio_snd_buffer_s { struct ap_buffer_s apb; struct virtio_snd_pcm_xfer xfer; struct virtio_snd_pcm_status status; FAR struct audio_lowerhalf_s *dev; }; /* Include struct audio_lowerhalf_s, use to get struct virtio_snd_s */ struct virtio_snd_dev_s { struct audio_lowerhalf_s dev; uint32_t period_bytes; uint32_t index; FAR void *priv; }; /* Virtio snd card struct for virtio driver */ struct virtio_snd_s { FAR struct virtio_device *vdev; FAR struct virtio_snd_dev_s *dev; FAR struct virtio_snd_pcm_info *info; struct virtio_snd_config config; }; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ static void virtio_snd_pcm_notify_cb(FAR struct virtqueue *vqueue); static void virtio_snd_ctl_notify_cb(FAR struct virtqueue *vqueue); static void virtio_snd_event_notify_cb(FAR struct virtqueue *vqueue); static unsigned int virtio_snd_get_period_bytes(unsigned int rate, unsigned int ch, unsigned int bps, unsigned int period_time); static unsigned int virtio_snd_get_support_rates(FAR const struct virtio_snd_pcm_info *info); static void virtio_snd_get_support_formats(FAR const struct virtio_snd_pcm_info *info, FAR struct audio_caps_s *caps); static int virtio_snd_send_pcm(FAR struct virtio_snd_dev_s *sdev, FAR struct virtio_snd_buffer_s *buf); static int virtio_snd_send_ctl(FAR struct virtio_snd_s *priv, FAR struct virtqueue_buf *vb, int readable, int writable); static int virtio_snd_query_info(FAR struct virtio_snd_s *priv, int cmd, size_t size, size_t count, FAR struct virtio_snd_pcm_info *info); static int virtio_snd_set_params(FAR struct virtio_snd_dev_s *sdev, unsigned int ch, unsigned int rate, unsigned int bps); static int virtio_snd_send_cmd(FAR struct virtio_snd_dev_s *sdev, int cmd); static int virtio_snd_getcaps(FAR struct audio_lowerhalf_s *dev, int type, FAR struct audio_caps_s *caps); #ifdef CONFIG_AUDIO_MULTI_SESSION static int virtio_snd_configure(FAR struct audio_lowerhalf_s *dev, FAR void *session, FAR const struct audio_caps_s *caps); static int virtio_snd_start(FAR struct audio_lowerhalf_s *dev, FAR void *session); #ifndef CONFIG_AUDIO_EXCLUDE_STOP static int virtio_snd_stop(FAR struct audio_lowerhalf_s *dev, FAR void *session); #endif #ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME static int virtio_snd_pause(FAR struct audio_lowerhalf_s *dev, FAR void *session); static int virtio_snd_resume(FAR struct audio_lowerhalf_s *dev, FAR void *session); #endif static int virtio_snd_reserve(FAR struct audio_lowerhalf_s *dev, FAR void **session); static int virtio_snd_release(FAR struct audio_lowerhalf_s *dev, FAR void *session); #else static int virtio_snd_configure(FAR struct audio_lowerhalf_s *dev, FAR const struct audio_caps_s *caps); static int virtio_snd_start(FAR struct audio_lowerhalf_s *dev); #ifndef CONFIG_AUDIO_EXCLUDE_STOP static int virtio_snd_stop(FAR struct audio_lowerhalf_s *dev); #endif #ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME static int virtio_snd_pause(FAR struct audio_lowerhalf_s *dev); static int virtio_snd_resume(FAR struct audio_lowerhalf_s *dev); #endif static int virtio_snd_reserve(FAR struct audio_lowerhalf_s *dev); static int virtio_snd_release(FAR struct audio_lowerhalf_s *dev); #endif static int virtio_snd_allocbuffer(FAR struct audio_lowerhalf_s *dev, FAR struct audio_buf_desc_s *apb); static int virtio_snd_freebuffer(FAR struct audio_lowerhalf_s *dev, FAR struct audio_buf_desc_s *apb); static int virtio_snd_enqueuebuffer(FAR struct audio_lowerhalf_s *dev, FAR struct ap_buffer_s *apb); static int virtio_snd_ioctl(FAR struct audio_lowerhalf_s *dev, int cmd, unsigned long arg); static int virtio_snd_shutdown(FAR struct audio_lowerhalf_s *dev); static int virtio_snd_probe(FAR struct virtio_device *vdev); static void virtio_snd_remove(FAR struct virtio_device *vdev); /**************************************************************************** * Private Data ****************************************************************************/ static const struct virtio_snd_rate_map_s g_rate_map[] = { [VIRTIO_SND_PCM_RATE_8000] = { AUDIO_SAMP_RATE_8K, 8000 }, [VIRTIO_SND_PCM_RATE_11025] = { AUDIO_SAMP_RATE_11K, 11025 }, [VIRTIO_SND_PCM_RATE_16000] = { AUDIO_SAMP_RATE_16K, 16000 }, [VIRTIO_SND_PCM_RATE_22050] = { AUDIO_SAMP_RATE_22K, 22050 }, [VIRTIO_SND_PCM_RATE_32000] = { AUDIO_SAMP_RATE_32K, 32000 }, [VIRTIO_SND_PCM_RATE_44100] = { AUDIO_SAMP_RATE_44K, 44100 }, [VIRTIO_SND_PCM_RATE_48000] = { AUDIO_SAMP_RATE_48K, 48000 }, [VIRTIO_SND_PCM_RATE_96000] = { AUDIO_SAMP_RATE_96K, 96000 }, [VIRTIO_SND_PCM_RATE_192000] = { AUDIO_SAMP_RATE_192K, 192000 } }; static const struct virtio_snd_format_map_s g_format_map[] = { [VIRTIO_SND_PCM_FMT_S8] = { AUDIO_SUBFMT_PCM_S8, 8 }, [VIRTIO_SND_PCM_FMT_S16] = { AUDIO_SUBFMT_PCM_S16_LE, 16 }, [VIRTIO_SND_PCM_FMT_S32] = { AUDIO_SUBFMT_PCM_S32_LE, 32 } }; static struct virtio_driver g_virtio_snd_driver = { LIST_INITIAL_VALUE(g_virtio_snd_driver.node), /* node */ VIRTIO_ID_SOUND, /* device id */ virtio_snd_probe, /* probe */ virtio_snd_remove, /* remove */ }; static const struct audio_ops_s g_virtio_snd_ops = { virtio_snd_getcaps, /* getcaps */ virtio_snd_configure, /* configure */ virtio_snd_shutdown, /* shutdown */ virtio_snd_start, /* start */ #ifndef CONFIG_AUDIO_EXCLUDE_STOP virtio_snd_stop, /* stop */ #endif #ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME virtio_snd_pause, /* pause */ virtio_snd_resume, /* resume */ #endif virtio_snd_allocbuffer, /* allocbuffer */ virtio_snd_freebuffer, /* freebuffer */ virtio_snd_enqueuebuffer, /* enqueuebuffer */ NULL, /* cancelbuffer */ virtio_snd_ioctl, /* ioctl */ NULL, /* read */ NULL, /* write */ virtio_snd_reserve, /* reserve */ virtio_snd_release /* release */ }; /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: virtio_snd_get_period_bytes ****************************************************************************/ static unsigned int virtio_snd_get_period_bytes(unsigned int rate, unsigned int ch, unsigned int bps, unsigned int period_time) { return rate * ch * (bps / 8) * period_time / 1000; } /**************************************************************************** * Name: virtio_snd_get_support_rates ****************************************************************************/ static unsigned int virtio_snd_get_support_rates(FAR const struct virtio_snd_pcm_info *info) { unsigned int rates = 0; int i; for (i = VIRTIO_SND_PCM_RATE_5512; i < VIRTIO_SND_PCM_RATE_384000; i++) { if (info->rates & (1 << i)) { rates |= g_rate_map[i].nrate; } } return rates; } /**************************************************************************** * Name: virtio_snd_get_support_formats ****************************************************************************/ static void virtio_snd_get_support_formats(FAR const struct virtio_snd_pcm_info *info, FAR struct audio_caps_s *caps) { size_t subformats = 0; size_t i; for (i = 0; i < nitems(g_format_map); i++) { if (info->formats & (1 << i)) { caps->ac_controls.b[subformats++] = g_format_map[i].nformat; if (subformats >= nitems(caps->ac_controls.b)) break; } } } /**************************************************************************** * Name: virtio_snd_pcm_notify_cb ****************************************************************************/ static void virtio_snd_pcm_notify_cb(FAR struct virtqueue *vq) { for (; ; ) { FAR struct virtio_snd_buffer_s *buf; buf = virtqueue_get_buffer(vq, NULL, NULL); if (buf == NULL) { break; } #ifdef CONFIG_AUDIO_MULTI_SESSION buf->dev->upper(buf->dev->priv, AUDIO_CALLBACK_DEQUEUE, &buf->apb, OK, NULL); #else buf->dev->upper(buf->dev->priv, AUDIO_CALLBACK_DEQUEUE, &buf->apb, OK); #endif } } /**************************************************************************** * Name: virtio_snd_ctl_notify_cb ****************************************************************************/ static void virtio_snd_ctl_notify_cb(FAR struct virtqueue *vq) { FAR sem_t *ctl_sem; ctl_sem = virtqueue_get_buffer(vq, NULL, NULL); nxsem_post(ctl_sem); } /**************************************************************************** * Name: virtio_snd_event_notify_cb ****************************************************************************/ static void virtio_snd_event_notify_cb(FAR struct virtqueue *vqueue) { vrtinfo("recvive jack/pcm event\n"); } /**************************************************************************** * Name: virtio_snd_send_pcm ****************************************************************************/ static int virtio_snd_send_pcm(FAR struct virtio_snd_dev_s *sdev, FAR struct virtio_snd_buffer_s *buf) { FAR struct virtio_snd_s *priv = sdev->priv; FAR struct virtio_snd_pcm_info *info = &priv->info[sdev->index]; int idx = info->direction == VIRTIO_SND_D_INPUT ? VIRTIO_SND_VQ_RX : VIRTIO_SND_VQ_TX; FAR struct virtqueue *vq = priv->vdev->vrings_info[idx].vq; struct virtqueue_buf vb[3]; vb[0].buf = &buf->xfer; vb[0].len = sizeof(buf->xfer); vb[1].buf = buf->apb.samp; vb[1].len = sdev->period_bytes; vb[2].buf = &buf->status; vb[2].len = sizeof(buf->status); if (idx == VIRTIO_SND_VQ_RX) { virtqueue_add_buffer(vq, vb, 1, 2, buf); } else { virtqueue_add_buffer(vq, vb, 2, 1, buf); } virtqueue_kick(vq); return OK; } /**************************************************************************** * Name: virtio_snd_send_ctl ****************************************************************************/ static int virtio_snd_send_ctl(FAR struct virtio_snd_s *priv, FAR struct virtqueue_buf *vb, int readable, int writable) { FAR struct virtqueue *vq = priv->vdev->vrings_info[VIRTIO_SND_VQ_CONTROL].vq; sem_t ctl_sem; int ret; nxsem_init(&ctl_sem, 0, 0); virtqueue_add_buffer(vq, vb, readable, writable, &ctl_sem); virtqueue_kick(vq); ret = nxsem_wait_uninterruptible(&ctl_sem); nxsem_destroy(&ctl_sem); if (ret < 0) { vrterr("nxsem wait error:%d\n", ret); } return ret; } /**************************************************************************** * Name: virtio_snd_query_info ****************************************************************************/ static int virtio_snd_query_info(FAR struct virtio_snd_s *priv, int cmd, size_t size, size_t count, FAR struct virtio_snd_pcm_info *info) { FAR struct virtio_snd_query_info *req; FAR struct virtio_snd_hdr *resp; struct virtqueue_buf vb[3]; int ret; req = virtio_zalloc_buf(priv->vdev, sizeof(*req), 16); if (req == NULL) { vrterr("virtio audio driver cmd request alloc failed\n"); return -ENOMEM; } req->hdr.code = cmd; req->count = count; req->size = size; resp = virtio_alloc_buf(priv->vdev, sizeof(*resp), 16); if (resp == NULL) { vrterr("virtio audio driver cmd response alloc failed\n"); ret = -ENOMEM; goto out; } resp->code = VIRTIO_SND_S_IO_ERR; vb[0].buf = req; vb[0].len = sizeof(*req); vb[1].buf = resp; vb[1].len = sizeof(*resp); vb[2].buf = info; vb[2].len = count * size; ret = virtio_snd_send_ctl(priv, vb, 1, 2); if (ret < 0) { vrterr("send msg error:%d\n", ret); goto out; } ret = resp->code == VIRTIO_SND_S_OK ? OK : -EIO; vrtinfo("send cmd:0x%x and resp:%d\n", cmd, ret); out: virtio_free_buf(priv->vdev, req); virtio_free_buf(priv->vdev, resp); return ret; } /**************************************************************************** * Name: virtio_snd_dev_set_params ****************************************************************************/ static int virtio_snd_set_params(FAR struct virtio_snd_dev_s *sdev, unsigned int ch, unsigned int rate, unsigned int bps) { FAR struct virtio_snd_s *priv = sdev->priv; FAR struct virtio_snd_pcm_set_params *req; FAR struct virtio_snd_hdr *resp; struct virtqueue_buf vb[2]; size_t i; int ret; req = virtio_zalloc_buf(priv->vdev, sizeof(*req), 16); if (req == NULL) { vrterr("zalloc for request error\n"); return -ENOMEM; } req->hdr.hdr.code = VIRTIO_SND_R_PCM_SET_PARAMS; req->hdr.stream_id = sdev->index; req->channels = ch; req->rate = VIRTIO_SND_PCM_RATE_44100; for (i = 0; i < nitems(g_rate_map); i++) { if (rate == g_rate_map[i].sps) { req->rate = i; break; } } req->format = VIRTIO_SND_PCM_FMT_S16; for (i = 0; i < nitems(g_format_map); i++) { if (bps == g_format_map[i].bps) { req->format = i; break; } } req->period_bytes = sdev->period_bytes; req->buffer_bytes = req->period_bytes * CONFIG_DRIVERS_VIRTIO_SND_BUFFER_COUNT; resp = virtio_alloc_buf(priv->vdev, sizeof(*resp), 16); if (resp == NULL) { vrterr("zalloc for request error\n"); ret = -ENOMEM; goto out; } resp->code = VIRTIO_SND_S_IO_ERR; vb[0].buf = req; vb[0].len = sizeof(*req); vb[1].buf = resp; vb[1].len = sizeof(*resp); ret = virtio_snd_send_ctl(priv, vb, 1, 1); if (ret < 0) { vrterr("send msg error:%d\n", ret); goto out; } ret = resp->code == VIRTIO_SND_S_OK ? OK : -EIO; vrtinfo("send cmd:0x%x and resp:%d\n", VIRTIO_SND_R_PCM_SET_PARAMS, ret); out: virtio_free_buf(priv->vdev, req); virtio_free_buf(priv->vdev, resp); return ret; } /**************************************************************************** * Name: virtio_snd_send_cmd ****************************************************************************/ static int virtio_snd_send_cmd(FAR struct virtio_snd_dev_s *sdev, int cmd) { FAR struct virtio_snd_s *priv = sdev->priv; FAR struct virtio_device *vdev = priv->vdev; FAR struct virtio_snd_pcm_hdr *req; FAR struct virtio_snd_hdr *resp; struct virtqueue_buf vb[2]; int ret; req = virtio_alloc_buf(vdev, sizeof(*req), 16); if (req == NULL) { vrterr("zalloc for request error\n"); return -ENOMEM; } req->hdr.code = cmd; req->stream_id = sdev->index; resp = virtio_alloc_buf(vdev, sizeof(*resp), 16); if (resp == NULL) { vrterr("zalloc for request error\n"); ret = -ENOMEM; goto out; } resp->code = VIRTIO_SND_S_IO_ERR; vb[0].buf = req; vb[0].len = sizeof(*req); vb[1].buf = resp; vb[1].len = sizeof(*resp); ret = virtio_snd_send_ctl(priv, vb, 1, 1); if (ret < 0) { vrterr("send msg error:%d\n", ret); goto out; } ret = resp->code == VIRTIO_SND_S_OK ? OK : -EIO; if (ret < 0) { vrterr("check response error:%d\n", ret); } vrtinfo("send cmd:0x%x and resp:%d\n", cmd, ret); out: virtio_free_buf(vdev, req); virtio_free_buf(vdev, resp); return ret; } /**************************************************************************** * Name: virtio_snd_getcaps ****************************************************************************/ static int virtio_snd_getcaps(FAR struct audio_lowerhalf_s *dev, int type, FAR struct audio_caps_s *caps) { FAR struct virtio_snd_dev_s *sdev = (FAR struct virtio_snd_dev_s *)dev; FAR struct virtio_snd_s *priv = sdev->priv; FAR struct virtio_snd_pcm_info *info = &priv->info[sdev->index]; DEBUGASSERT(caps->ac_len >= sizeof(struct audio_caps_s)); caps->ac_format.hw = 0; caps->ac_controls.w = 0; switch (caps->ac_type) { case AUDIO_TYPE_QUERY: switch (caps->ac_subtype) { case AUDIO_TYPE_QUERY: caps->ac_controls.b[0] = info->direction == VIRTIO_SND_D_INPUT ? AUDIO_TYPE_INPUT : AUDIO_TYPE_OUTPUT; caps->ac_format.hw = 1 << (AUDIO_FMT_PCM - 1); break; case AUDIO_FMT_PCM: virtio_snd_get_support_formats(info, caps); break; default: caps->ac_controls.b[0] = AUDIO_SUBFMT_END; break; } break; case AUDIO_TYPE_OUTPUT: case AUDIO_TYPE_INPUT: { caps->ac_channels = (info->channels_min << 4) | (info->channels_max & 0x0f); switch (caps->ac_subtype) { case AUDIO_TYPE_QUERY: caps->ac_controls.b[0] = virtio_snd_get_support_rates(info); break; default: break; } break; } default: caps->ac_subtype = 0; caps->ac_channels = 0; break; } return caps->ac_len; } /**************************************************************************** * Name: virtio_snd_configure * * Description: * Configure the audio device for the specified mode of operation. * ****************************************************************************/ #ifdef CONFIG_AUDIO_MULTI_SESSION static int virtio_snd_configure(FAR struct audio_lowerhalf_s *dev, FAR void *session, FAR const struct audio_caps_s *caps) #else static int virtio_snd_configure(FAR struct audio_lowerhalf_s *dev, FAR const struct audio_caps_s *caps) #endif { FAR struct virtio_snd_dev_s *sdev = (FAR struct virtio_snd_dev_s *)dev; int ret = -ENOTTY; uint32_t rate; uint8_t bps; uint8_t ch; switch (caps->ac_type) { case AUDIO_TYPE_OUTPUT: case AUDIO_TYPE_INPUT: vrtinfo("Number of channels: %"PRIu8"\n", caps->ac_channels); vrtinfo("Sample rate: %"PRIu16"\n", caps->ac_controls.hw[0]); vrtinfo("Sample width: %"PRIu8"\n", caps->ac_controls.b[2]); vrtinfo("channel map: 0x%x\n", caps->ac_chmap); rate = caps->ac_controls.hw[0] | (caps->ac_controls.b[3] << 16); bps = caps->ac_controls.b[2]; ch = caps->ac_channels; sdev->period_bytes = virtio_snd_get_period_bytes(rate, ch, bps, CONFIG_DRIVERS_VIRTIO_SOUND_PERIOD_TIME); vrtinfo("period_bytes:%"PRIu32"\n", sdev->period_bytes); ret = virtio_snd_set_params(sdev, ch, rate, bps); if (ret < 0) { break; } ret = virtio_snd_send_cmd(sdev, VIRTIO_SND_R_PCM_PREPARE); break; } return ret; } /**************************************************************************** * Name: virtio_snd_start * * Description: * Start the configured operation (audio streaming, volume enabled, etc.). * ****************************************************************************/ #ifdef CONFIG_AUDIO_MULTI_SESSION static int virtio_snd_start(FAR struct audio_lowerhalf_s *dev, FAR void *session) #else static int virtio_snd_start(FAR struct audio_lowerhalf_s *dev) #endif { FAR struct virtio_snd_dev_s *sdev = (FAR struct virtio_snd_dev_s *)dev; return virtio_snd_send_cmd(sdev, VIRTIO_SND_R_PCM_START); } /**************************************************************************** * Name: virtio_snd_stop * * Description: Stop the configured operation (audio streaming, volume * disabled, etc.). * ****************************************************************************/ #ifndef CONFIG_AUDIO_EXCLUDE_STOP #ifdef CONFIG_AUDIO_MULTI_SESSION static int virtio_snd_stop(FAR struct audio_lowerhalf_s *dev, FAR void *session) #else static int virtio_snd_stop(FAR struct audio_lowerhalf_s *dev) #endif { FAR struct virtio_snd_dev_s *sdev = (FAR struct virtio_snd_dev_s *)dev; virtio_snd_send_cmd(sdev, VIRTIO_SND_R_PCM_STOP); virtio_snd_send_cmd(sdev, VIRTIO_SND_R_PCM_RELEASE); #ifdef CONFIG_AUDIO_MULTI_SESSION dev->upper(dev->priv, AUDIO_CALLBACK_COMPLETE, NULL, OK, NULL); #else dev->upper(dev->priv, AUDIO_CALLBACK_COMPLETE, NULL, OK); #endif return OK; } #endif /**************************************************************************** * Name: virtio_snd_pause * * Description: Pauses the playback. * ****************************************************************************/ #ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME #ifdef CONFIG_AUDIO_MULTI_SESSION static int virtio_snd_pause(FAR struct audio_lowerhalf_s *dev, FAR void *session) #else static int virtio_snd_pause(FAR struct audio_lowerhalf_s *dev) #endif { FAR struct virtio_snd_dev_s *sdev = (FAR struct virtio_snd_dev_s *)dev; return virtio_snd_send_cmd(sdev, VIRTIO_SND_R_PCM_STOP); } #endif /**************************************************************************** * Name: virtio_snd_resume * * Description: Resumes the playback. * ****************************************************************************/ #ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME #ifdef CONFIG_AUDIO_MULTI_SESSION static int virtio_snd_resume(FAR struct audio_lowerhalf_s *dev, FAR void *session) #else static int virtio_snd_resume(FAR struct audio_lowerhalf_s *dev) #endif { FAR struct virtio_snd_dev_s *sdev = (FAR struct virtio_snd_dev_s *)dev; return virtio_snd_send_cmd(sdev, VIRTIO_SND_R_PCM_START); } #endif static int virtio_snd_allocbuffer(FAR struct audio_lowerhalf_s *dev, FAR struct audio_buf_desc_s *desc) { FAR struct virtio_snd_dev_s *sdev = (FAR struct virtio_snd_dev_s *)dev; FAR struct virtio_snd_s *priv = sdev->priv; FAR struct virtio_snd_buffer_s *buf; DEBUGASSERT(desc->u.pbuffer != NULL); buf = virtio_zalloc_buf(priv->vdev, sizeof(*buf) + desc->numbytes, 16); if (buf == NULL) { vrterr("failed to allocate apb buffer\n"); return -ENOMEM; } *desc->u.pbuffer = &buf->apb; buf->apb.crefs = 1; buf->apb.nmaxbytes = desc->numbytes; buf->apb.samp = (FAR uint8_t *)(buf + 1); #ifdef CONFIG_AUDIO_MULTI_SESSION buf->apb.session = desc->session; #endif buf->xfer.stream_id = sdev->index; buf->status.status = VIRTIO_SND_S_IO_ERR; buf->dev = dev; nxmutex_init(&buf->apb.lock); return sizeof(struct audio_buf_desc_s); } static int virtio_snd_freebuffer(FAR struct audio_lowerhalf_s *dev, FAR struct audio_buf_desc_s *desc) { FAR struct virtio_snd_dev_s *sdev = (FAR struct virtio_snd_dev_s *)dev; FAR struct virtio_snd_s *priv = sdev->priv; FAR struct virtio_device *vdev = priv->vdev; FAR struct ap_buffer_s *apb = desc->u.buffer; int refcount; nxmutex_lock(&apb->lock); refcount = apb->crefs--; nxmutex_unlock(&apb->lock); if (refcount <= 1) { nxmutex_destroy(&apb->lock); virtio_free_buf(vdev, apb); } return sizeof(struct audio_buf_desc_s); } /**************************************************************************** * Name: virtio_snd_enqueuebuffer * * Description: Enqueue an Audio Pipeline Buffer for playback/ processing. * ****************************************************************************/ static int virtio_snd_enqueuebuffer(FAR struct audio_lowerhalf_s *dev, FAR struct ap_buffer_s *apb) { FAR struct virtio_snd_dev_s *sdev = (FAR struct virtio_snd_dev_s *)dev; FAR struct virtio_snd_buffer_s *buf = (FAR struct virtio_snd_buffer_s *)apb; return virtio_snd_send_pcm(sdev, buf); } /**************************************************************************** * Name: virtio_snd_ioctl * * Description: Perform a device ioctl * ****************************************************************************/ static int virtio_snd_ioctl(FAR struct audio_lowerhalf_s *dev, int cmd, unsigned long arg) { FAR struct virtio_snd_dev_s *sdev = (FAR struct virtio_snd_dev_s *)dev; FAR struct ap_buffer_info_s *bufinfo; switch (cmd) { case AUDIOIOC_GETBUFFERINFO: bufinfo = (FAR struct ap_buffer_info_s *)arg; bufinfo->nbuffers = CONFIG_DRIVERS_VIRTIO_SND_BUFFER_COUNT; bufinfo->buffer_size = sdev->period_bytes; break; default: return -ENOTTY; } return OK; } /**************************************************************************** * Name: virtio_snd_shutdown * * Description: * Shutdown the driver and put it in the lowest power state possible. * ****************************************************************************/ static int virtio_snd_shutdown(FAR struct audio_lowerhalf_s *dev) { vrtinfo("Return OK\n"); return OK; } /**************************************************************************** * Name: virtio_snd_reserve * * Description: Reserves a session (the only one we have). * ****************************************************************************/ #ifdef CONFIG_AUDIO_MULTI_SESSION static int virtio_snd_reserve(FAR struct audio_lowerhalf_s *dev, FAR void **session) #else static int virtio_snd_reserve(FAR struct audio_lowerhalf_s *dev) #endif { vrtinfo("Return OK\n"); return OK; } /**************************************************************************** * Name: virtio_snd_release * * Description: Releases the session (the only one we have). * ****************************************************************************/ #ifdef CONFIG_AUDIO_MULTI_SESSION static int virtio_snd_release(FAR struct audio_lowerhalf_s *dev, FAR void *session) #else static int virtio_snd_release(FAR struct audio_lowerhalf_s *dev) #endif { vrtinfo("Return OK\n"); return OK; } /**************************************************************************** * Name: virtio_snd_init ****************************************************************************/ static int virtio_snd_init(FAR struct virtio_snd_s *priv) { FAR const char *vqnames[VIRTIO_SND_VQ_MAX]; vq_callback callbacks[VIRTIO_SND_VQ_MAX]; int ret; int i; vqnames[VIRTIO_SND_VQ_CONTROL] = "virtsnd-ctl"; vqnames[VIRTIO_SND_VQ_EVENT] = "virtsnd-event"; vqnames[VIRTIO_SND_VQ_TX] = "virtsnd-tx"; vqnames[VIRTIO_SND_VQ_RX] = "virtsnd-rx"; callbacks[VIRTIO_SND_VQ_CONTROL] = virtio_snd_ctl_notify_cb; callbacks[VIRTIO_SND_VQ_EVENT] = virtio_snd_event_notify_cb; callbacks[VIRTIO_SND_VQ_TX] = virtio_snd_pcm_notify_cb; callbacks[VIRTIO_SND_VQ_RX] = virtio_snd_pcm_notify_cb; ret = virtio_create_virtqueues(priv->vdev, 0, VIRTIO_SND_VQ_MAX, vqnames, callbacks); if (ret < 0) { vrterr("virtio_device_create_virtqueue failed, ret=%d\n", ret); return ret; } for (i = 0; i < VIRTIO_SND_VQ_MAX; i++) { virtqueue_enable_cb(priv->vdev->vrings_info[i].vq); } virtio_set_status(priv->vdev, VIRTIO_CONFIG_STATUS_DRIVER_OK); virtio_read_config(priv->vdev, 0, &priv->config, sizeof(struct virtio_snd_config)); vrtinfo("jacks:%"PRIu32" streams:%"PRIu32" chmap:%"PRIu32"\n", priv->config.jacks, priv->config.streams, priv->config.chmaps); priv->info = virtio_alloc_buf(priv->vdev, priv->config.streams * sizeof(struct virtio_snd_pcm_info), 16); if (priv->info == NULL) { vrterr("virtio audio driver query pcm info alloc failed\n"); ret = -ENOMEM; goto err_with_vq; } /* Query pcm info */ ret = virtio_snd_query_info(priv, VIRTIO_SND_R_PCM_INFO, sizeof(struct virtio_snd_pcm_info), priv->config.streams, priv->info); if (ret < 0) { vrterr("virtio snd query pcm info failed,ret:%d\n", ret); goto err_with_info; } return ret; err_with_info: virtio_free_buf(priv->vdev, priv->info); err_with_vq: virtio_delete_virtqueues(priv->vdev); return ret; } static int virtio_snd_register_audio_driver(FAR struct virtio_snd_s *priv) { char devname[32]; int tx_minor = 0; int rx_minor = 0; int ret = -ENODEV; uint32_t i; priv->dev = kmm_zalloc(priv->config.streams * sizeof(struct virtio_snd_dev_s)); if (priv->dev == NULL) { vrterr("zalloc for virtio audio device failed\n"); return -ENOMEM; } for (i = 0; i < priv->config.streams; i++) { switch (priv->info[i].direction) { case VIRTIO_SND_D_OUTPUT: snprintf(devname, 32, "pcm%dp", tx_minor++); break; case VIRTIO_SND_D_INPUT: snprintf(devname, 32, "pcm%dc", rx_minor++); break; } priv->dev[i].index = i; priv->dev[i].priv = priv; priv->dev[i].dev.ops = &g_virtio_snd_ops; ret = audio_register(devname, &priv->dev[i].dev); if (ret < 0) { vrterr("failed to register /dev/%s device: %d\n", devname, ret); break; } } if (i == 0) { kmm_free(priv->dev); } return ret; } /**************************************************************************** * Name: virtio_snd_probe ****************************************************************************/ static int virtio_snd_probe(FAR struct virtio_device *vdev) { FAR struct virtio_snd_s *priv; int ret; priv = kmm_zalloc(sizeof(*priv)); if (priv == NULL) { vrterr("virtio net driver priv alloc failed\n"); return -ENOMEM; } priv->vdev = vdev; vdev->priv = priv; ret = virtio_snd_init(priv); if (ret < 0) { goto err_with_virtsnd; } ret = virtio_snd_register_audio_driver(priv); if (ret < 0) { goto err_with_vq; } return ret; err_with_vq: virtio_reset_device(vdev); virtio_delete_virtqueues(vdev); virtio_free_buf(vdev, priv->info); err_with_virtsnd: kmm_free(priv); return ret; } /**************************************************************************** * Name: virtio_snd_remove ****************************************************************************/ static void virtio_snd_remove(FAR struct virtio_device *vdev) { FAR struct virtio_snd_s *priv = vdev->priv; virtio_reset_device(vdev); virtio_delete_virtqueues(vdev); virtio_free_buf(vdev, priv->info); kmm_free(priv->dev); kmm_free(priv); } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: virtio_register_snd_driver ****************************************************************************/ int virtio_register_snd_driver(void) { return virtio_register_driver(&g_virtio_snd_driver); }