nuttx/drivers/virtio/virtio-snd.c
shipei f99f67378d audio:add state sync for virtio snd device
qemu host virtio device don't support recvice the same cmd like start/stop twice
Signed-off-by: shipei <shipei@xiaomi.com>
2023-09-25 21:04:59 +08:00

1242 lines
35 KiB
C

/****************************************************************************
* 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 <assert.h>
#include <debug.h>
#include <errno.h>
#include <stdio.h>
#include <sys/param.h>
#include <nuttx/audio/audio.h>
#include <nuttx/kmalloc.h>
#include <nuttx/virtio/virtio.h>
#include <nuttx/semaphore.h>
#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;
bool running;
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:0x%"PRIu32"\n", cmd, resp->code);
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:0x%"PRIu32"\n",
VIRTIO_SND_R_PCM_SET_PARAMS, resp->code);
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:0x%"PRIu32"\n", cmd, resp->code);
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;
int ret = OK;
if (!sdev->running)
{
ret = virtio_snd_send_cmd(sdev, VIRTIO_SND_R_PCM_START);
if (ret < 0)
{
return ret;
}
sdev->running = true;
}
return ret;
}
/****************************************************************************
* 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;
int ret = OK;
if (sdev->running)
{
ret = virtio_snd_send_cmd(sdev, VIRTIO_SND_R_PCM_STOP);
if (ret < 0)
{
return ret;
}
sdev->running = false;
}
ret = virtio_snd_send_cmd(sdev, VIRTIO_SND_R_PCM_RELEASE);
if (ret < 0)
{
return ret;
}
#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 ret;
}
#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;
int ret = OK;
if (sdev->running)
{
ret = virtio_snd_send_cmd(sdev, VIRTIO_SND_R_PCM_STOP);
if (ret < 0)
{
return ret;
}
sdev->running = false;
}
return ret;
}
#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;
int ret = OK;
if (!sdev->running)
{
ret = virtio_snd_send_cmd(sdev, VIRTIO_SND_R_PCM_START);
if (ret < 0)
{
return ret;
}
sdev->running = true;
}
return ret;
}
#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].running = false;
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);
}