diff --git a/components/drivers/virtio/Kconfig b/components/drivers/virtio/Kconfig index 5d974c886d..afa92546f3 100644 --- a/components/drivers/virtio/Kconfig +++ b/components/drivers/virtio/Kconfig @@ -45,6 +45,12 @@ config RT_VIRTIO_RNG depends on RT_HWCRYPTO_USING_RNG default y +config RT_VIRTIO_RPMSG + bool "VirtIO Rpmsg" + depends on RT_USING_VIRTIO + depends on RT_USING_RPMSG + default y + config RT_VIRTIO_SCSI bool "VirtIO SCSI" depends on RT_USING_VIRTIO @@ -92,6 +98,12 @@ config RT_VIRTIO_CRYPTO depends on RT_HWCRYPTO_USING_RC4 default y +config RT_VIRTIO_SOUND + bool "VirtIO Sound" + depends on RT_USING_VIRTIO + depends on RT_USING_AUDIO + default y + config RT_VIRTIO_SCMI bool "VirtIO SCMI" depends on RT_USING_VIRTIO diff --git a/components/drivers/virtio/SConscript b/components/drivers/virtio/SConscript index a5d3817f0d..6d8da8eb5e 100644 --- a/components/drivers/virtio/SConscript +++ b/components/drivers/virtio/SConscript @@ -28,6 +28,9 @@ if GetDepend(['RT_VIRTIO_CONSOLE']) or GetDepend(['RT_VIRTIO_RPROC_SERIAL']): if GetDepend(['RT_VIRTIO_RNG']): src += ['virtio-rng.c'] +if GetDepend(['RT_VIRTIO_RPMSG']): + src += ['virtio-rpmsg.c'] + if GetDepend(['RT_VIRTIO_SCSI']): src += ['virtio-scsi.c'] @@ -43,6 +46,9 @@ if GetDepend(['RT_VIRTIO_INPUT']): if GetDepend(['RT_VIRTIO_CRYPTO']): src += ['virtio-crypto.c'] +if GetDepend(['RT_VIRTIO_SOUND']): + src += ['virtio-sound.c'] + if GetDepend(['RT_VIRTIO_SCMI']): src += ['virtio-scmi.c'] diff --git a/components/drivers/virtio/virtio-blk.c b/components/drivers/virtio/virtio-blk.c index 32267ad0e0..0bdeb89c98 100644 --- a/components/drivers/virtio/virtio-blk.c +++ b/components/drivers/virtio/virtio-blk.c @@ -257,6 +257,7 @@ static rt_err_t virtio_blk_probe(struct rt_virtio_device *vdev) if ((vblk->vblk_id = rt_dm_ida_alloc(&virtio_blk_ida)) < 0) { + rt_free(vblk); return -RT_EFULL; } diff --git a/components/drivers/virtio/virtio-rpmsg.c b/components/drivers/virtio/virtio-rpmsg.c new file mode 100755 index 0000000000..bc7cbc21f2 --- /dev/null +++ b/components/drivers/virtio/virtio-rpmsg.c @@ -0,0 +1,460 @@ +/* + * Copyright (c) 2006-2023, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2023-02-25 GuEe-GUI the first version + */ + +#include +#include +#include + +#define DBG_TAG "virtio.dev.rpmsg" +#define DBG_LVL DBG_INFO +#include + +#include "virtio_config/virtio-rpmsg.h" + +#define QUEUE_RX _queue_rx +#define QUEUE_TX _queue_tx + +struct virtio_rpmsg_tx_buf +{ + rt_uint8_t data[VIRTIO_RPMSG_MAX_BUF_SIZE]; + rt_uint32_t idx; +}; + +struct virtio_rpmsg +{ + struct rt_rpmsg_device parent; + struct rt_rpmsg_device *ns; + struct rt_virtio_device *vdev; + + struct rt_virtqueue *vqs[2]; + struct rt_spinlock tx_lock; + + rt_bool_t supp_ns; + + rt_uint8_t *tx_buffer; + struct rt_completion *tx_done; +}; + +#define raw_to_virtio_rpmsg(raw) (raw)->priv + +static int _queue_rx, _queue_tx; + +static rt_err_t virtio_rpmsg_create_endpoint(struct rt_rpmsg_device *rdev, + struct rt_rpmsg_endpoint *ept, struct rt_rpmsg_endpoint_info *info) +{ + rt_err_t err = RT_EOK; + rt_bool_t is_remote = info->src == RT_RPMSG_ADDR_ANY; + struct rt_rpmsg_endpoint *target_ept; + struct virtio_rpmsg *vrpmsg = raw_to_virtio_rpmsg(rdev); + + ept->sysdata = (void *)(rt_ubase_t)is_remote; + + if (is_remote) + { + target_ept = rt_rpmsg_find_endpoint(rdev, info); + + if (target_ept) + { + LOG_E("%s: endpoint (name: %s, src: %u, dst: %u) already exist", + rt_dm_dev_get_name(&vrpmsg->vdev->parent), + info->name, info->src, info->dst); + + return -RT_EINVAL; + } + } + + if (!is_remote && vrpmsg->supp_ns) + { + struct rt_rpmsg_ns_msg ns_msg; + + rt_memset(&ns_msg, 0, sizeof(ns_msg)); + + if (info && info->name[0]) + { + rt_strncpy(ns_msg.name, info->name, RT_RPMSG_NAME_SIZE); + } + else + { + rt_strncpy(ns_msg.name, rdev->id.name, RT_RPMSG_NAME_SIZE); + } + + ns_msg.addr = rt_cpu_to_le32(ept->info.src); + ns_msg.flags = rt_cpu_to_le32(RT_RPMSG_NS_CREATE); + + err = rt_rpmsg_sendto(ept, RT_RPMSG_NS_ADDR, &ns_msg, sizeof(ns_msg)); + + if (err) + { + LOG_E("%s: Notify remote error = %s", + rt_dm_dev_get_name(&vrpmsg->vdev->parent), rt_strerror(err)); + } + } + + return err; +} + +static rt_err_t virtio_rpmsg_destroy_endpoint(struct rt_rpmsg_device *rdev, + struct rt_rpmsg_endpoint *ept) +{ + rt_err_t err = RT_EOK; + rt_bool_t is_remote = !!ept->sysdata; + struct virtio_rpmsg *vrpmsg = raw_to_virtio_rpmsg(rdev); + + if (!is_remote && vrpmsg->supp_ns) + { + struct rt_rpmsg_ns_msg ns_msg; + + rt_memset(&ns_msg, 0, sizeof(ns_msg)); + + if (ept->info.name[0]) + { + rt_strncpy(ns_msg.name, ept->info.name, RT_RPMSG_NAME_SIZE); + } + else + { + rt_strncpy(ns_msg.name, rdev->id.name, RT_RPMSG_NAME_SIZE); + } + + ns_msg.addr = rt_cpu_to_le32(ept->info.src); + ns_msg.flags = rt_cpu_to_le32(RT_RPMSG_NS_DESTROY); + + err = rt_rpmsg_sendto(ept, RT_RPMSG_NS_ADDR, &ns_msg, sizeof(ns_msg)); + + if (err) + { + LOG_E("%s: Notify remote error = %s", + rt_dm_dev_get_name(&vrpmsg->vdev->parent), rt_strerror(err)); + } + } + + return err; +} + +static rt_err_t virtio_rpmsg_send(struct rt_rpmsg_device *rdev, + rt_uint32_t src, rt_uint32_t dst, + const void *data, rt_size_t len, rt_int32_t timeout) +{ + rt_base_t level; + int idx; + struct rt_virtqueue *vq; + struct virtio_rpmsg_hdr *msg; + struct virtio_rpmsg_tx_buf *tx_buf; + struct virtio_rpmsg *vrpmsg = raw_to_virtio_rpmsg(rdev); + + if (src == RT_RPMSG_ADDR_ANY || dst == RT_RPMSG_ADDR_ANY) + { + LOG_E("%s: Invalid addr(src: %u, dst: %u)", + rt_dm_dev_get_name(&vrpmsg->vdev->parent), src, dst); + + return -RT_EINVAL; + } + + if (len > VIRTIO_RPMSG_MAX_BUF_SIZE - sizeof(*msg)) + { + LOG_E("%s: Data's len = %u > %u is too big", + rt_dm_dev_get_name(&vrpmsg->vdev->parent), + len, VIRTIO_RPMSG_MAX_BUF_SIZE - sizeof(*msg)); + + return -RT_EFULL; + } + + vq = vrpmsg->vqs[QUEUE_TX]; + rt_virtqueue_wait_prepare(vq, 1); + + level = rt_spin_lock_irqsave(&vrpmsg->tx_lock); + + idx = rt_virtqueue_next_buf_index(vq); + tx_buf = (void *)vrpmsg->tx_buffer + idx * sizeof(struct virtio_rpmsg_tx_buf); + tx_buf->idx = idx; + msg = (void *)tx_buf->data; + + msg->src = rt_cpu_to_le32(src); + msg->dst = rt_cpu_to_le32(dst); + msg->reserved = 0; + msg->len = rt_cpu_to_le16(len); + msg->flags = 0; + LOG_D("%s: %cX-HDR(src: %u, dst: %u, len: %u, flags: %u, reserved: %u)", + rt_dm_dev_get_name(&vrpmsg->vdev->parent), 'T', src, dst, len, 0, 0); + rt_memcpy(msg->data, data, len); + + rt_virtqueue_add_outbuf(vq, msg, sizeof(*msg) + len); + + rt_virtqueue_kick(vq); + + rt_spin_unlock_irqrestore(&vrpmsg->tx_lock, level); + + if (timeout) + { + rt_completion_wait(&vrpmsg->tx_done[idx], timeout); + } + + return RT_EOK; +} + +static const struct rt_rpmsg_ops virtio_rpmsg_ops = +{ + .create_endpoint = virtio_rpmsg_create_endpoint, + .destroy_endpoint = virtio_rpmsg_destroy_endpoint, + .send = virtio_rpmsg_send, +}; + +static void virtio_rpmsg_rx_done(struct rt_virtqueue *vq) +{ + rt_size_t len, msg_len; + struct rt_rpmsg_device *rdev; + struct rt_rpmsg_endpoint *ept; + struct rt_rpmsg_endpoint_info info; + struct virtio_rpmsg_hdr *msg; + struct virtio_rpmsg *vrpmsg = vq->vdev->priv; + + rdev = &vrpmsg->parent; + msg = rt_virtqueue_read_buf(vq, &len); + + if (!msg) + { + LOG_D("%s: not used buffer", rt_dm_dev_get_name(&vrpmsg->vdev->parent)); + return; + } + + do { + msg_len = rt_le16_to_cpu(msg->len); + + LOG_D("%s: %cX-HDR(src: %u, dst: %u, len: %u, flags: %u, reserved: %u)", + rt_dm_dev_get_name(&vrpmsg->vdev->parent), 'R', + rt_le32_to_cpu(msg->src), + rt_le32_to_cpu(msg->dst), + msg_len, + rt_le16_to_cpu(msg->flags), + rt_le32_to_cpu(msg->reserved)); + + info.src = rt_le32_to_cpu(msg->dst); + info.dst = RT_RPMSG_ADDR_ANY; + info.name[0] = '\0'; + + if (rt_le32_to_cpu(msg->dst) == RT_RPMSG_NS_ADDR && vrpmsg->ns) + { + ept = rt_rpmsg_find_endpoint(vrpmsg->ns, &info); + } + else + { + ept = rt_rpmsg_find_endpoint(rdev, &info); + } + + if (ept && ept->rx_callback) + { + ept->rx_callback(rdev, rt_le32_to_cpu(msg->src), msg->data, msg_len); + } + + rt_virtqueue_add_inbuf(vq, msg, VIRTIO_RPMSG_MAX_BUF_SIZE); + + rt_virtqueue_kick(vq); + + msg = rt_virtqueue_read_buf(vq, &len); + } while (msg); +} + +static void virtio_rpmsg_tx_done(struct rt_virtqueue *vq) +{ + rt_ubase_t level; + struct virtio_rpmsg_tx_buf *tx_buf; + struct virtio_rpmsg *vrpmsg = vq->vdev->priv; + + level = rt_spin_lock_irqsave(&vrpmsg->tx_lock); + + while ((tx_buf = rt_virtqueue_read_buf(vq, RT_NULL))) + { + rt_completion_done(&vrpmsg->tx_done[tx_buf->idx]); + } + + rt_spin_unlock_irqrestore(&vrpmsg->tx_lock, level); +} + +static rt_err_t virtio_rpmsg_vq_init(struct virtio_rpmsg *vrpmsg) +{ + rt_err_t err; + rt_size_t vqs_nr = 2, qsize; + void *rx_buffer; + const char *names[RT_ARRAY_SIZE(vrpmsg->vqs)]; + rt_virtqueue_callback cbs[RT_ARRAY_SIZE(vrpmsg->vqs)]; + struct rt_virtio_device *vdev = vrpmsg->vdev; + struct rt_virtqueue *vq; + + names[QUEUE_RX] = "rx"; + names[QUEUE_TX] = "tx"; + + cbs[QUEUE_RX] = &virtio_rpmsg_rx_done; + cbs[QUEUE_TX] = &virtio_rpmsg_tx_done; + + err = rt_virtio_virtqueue_install(vdev, vqs_nr, vrpmsg->vqs, names, cbs); + + if (err) + { + return err; + } + + qsize = rt_virtqueue_get_virtq_size(vrpmsg->vqs[QUEUE_TX]); + if (!(vrpmsg->tx_done = rt_malloc(sizeof(vrpmsg->tx_done[0]) * qsize))) + { + return -RT_ENOMEM; + } + + for (int idx = 0; idx < qsize; ++idx) + { + rt_completion_init(&vrpmsg->tx_done[idx]); + } + + vrpmsg->tx_buffer = rt_malloc(qsize * sizeof(struct virtio_rpmsg_tx_buf) + + rt_virtqueue_get_virtq_size(vrpmsg->vqs[QUEUE_RX]) * VIRTIO_RPMSG_MAX_BUF_SIZE); + + if (!vrpmsg->tx_buffer) + { + return -RT_ENOMEM; + } + + vq = vrpmsg->vqs[QUEUE_RX]; + rx_buffer = vrpmsg->tx_buffer + qsize * sizeof(struct virtio_rpmsg_tx_buf); + + for (int idx = 0; idx < qsize; ++idx, rx_buffer += VIRTIO_RPMSG_MAX_BUF_SIZE) + { + rt_virtqueue_add_inbuf(vq, rx_buffer, VIRTIO_RPMSG_MAX_BUF_SIZE); + + rt_virtqueue_submit(vq); + } + + rt_virtqueue_notify(vq); + + return err; +} + +static void virtio_rpmsg_vq_finit(struct virtio_rpmsg *vrpmsg) +{ + if (vrpmsg->vqs[0]) + { + rt_virtio_virtqueue_release(vrpmsg->vdev); + } + if (vrpmsg->tx_done) + { + rt_free(vrpmsg->tx_done); + } + if (vrpmsg->tx_buffer) + { + rt_free(vrpmsg->tx_buffer); + } +} + +static rt_err_t virtio_rpmsg_probe(struct rt_virtio_device *vdev) +{ + rt_err_t err = RT_EOK; + struct virtio_rpmsg *vrpmsg; + + if (rt_rpmsg_mode() == RT_RPMSG_MODE_SLAVE) + { + _queue_rx = 0; + _queue_tx = 1; + } + else + { + _queue_tx = 0; + _queue_rx = 1; + } + + vrpmsg = rt_calloc(1, sizeof(*vrpmsg)); + + if (!vrpmsg) + { + return -RT_ENOMEM; + } + + vdev->priv = vrpmsg; + vrpmsg->vdev = vdev; + vdev->parent.user_data = vrpmsg; + + if ((err = virtio_rpmsg_vq_init(vrpmsg))) + { + goto _fail; + } + + rt_spin_lock_init(&vrpmsg->tx_lock); + + rt_strncpy(vrpmsg->parent.id.name, "rpmsg-char", sizeof(vrpmsg->parent.id.name)); + vrpmsg->parent.ops = &virtio_rpmsg_ops; + vrpmsg->parent.priv = vrpmsg; + + if ((err = rt_rpmsg_device_register(&vrpmsg->parent))) + { + goto _fail; + } + + /* Enable the name service */ + if ((vrpmsg->supp_ns = rt_virtio_has_feature(vdev, VIRTIO_RPMSG_F_NS))) + { + vrpmsg->ns = rt_calloc(1, sizeof(*vrpmsg->ns)); + + if (!vrpmsg->ns) + { + goto _fail_ns; + } + + rt_strncpy(vrpmsg->ns->id.name, "rpmsg-name-service", RT_RPMSG_NAME_SIZE); + vrpmsg->ns->ops = &virtio_rpmsg_ops; + vrpmsg->ns->priv = vrpmsg; + + if ((err = rt_rpmsg_device_register(vrpmsg->ns))) + { + goto _fail_ns; + } + } + + return RT_EOK; + +_fail_ns: + rt_bus_remove_device(&vrpmsg->parent.parent); + +_fail: + virtio_rpmsg_vq_finit(vrpmsg); + rt_free(vrpmsg); + + return err; +} + +static rt_err_t virtio_rpmsg_remove(struct rt_virtio_device *vdev) +{ + struct virtio_rpmsg *vrpmsg = vdev->parent.user_data; + + rt_bus_remove_device(&vrpmsg->parent.parent); + + if (vrpmsg->ns) + { + rt_bus_remove_device(&vrpmsg->ns->parent); + } + + virtio_rpmsg_vq_finit(vrpmsg); + rt_free(vrpmsg); + + return RT_EOK; +} + +static const struct rt_virtio_device_id virtio_rpmsg_ids[] = +{ + { VIRTIO_DEVICE_ID_RPMSG, VIRTIO_DEVICE_ANY_ID }, + { /* sentinel */ } +}; + +static struct rt_virtio_driver virtio_rpmsg_driver = +{ + .ids = virtio_rpmsg_ids, + .features = + RT_BIT(VIRTIO_RPMSG_F_NS) + | RT_BIT(VIRTIO_F_ANY_LAYOUT), + + .probe = virtio_rpmsg_probe, + .remove = virtio_rpmsg_remove, +}; +RT_VIRTIO_DRIVER_EXPORT(virtio_rpmsg_driver); diff --git a/components/drivers/virtio/virtio-sound.c b/components/drivers/virtio/virtio-sound.c new file mode 100755 index 0000000000..a2e264d8cb --- /dev/null +++ b/components/drivers/virtio/virtio-sound.c @@ -0,0 +1,1847 @@ +/* + * Copyright (c) 2006-2023, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2023-02-25 GuEe-GUI the first version + * 2026-06-10 GuEe-GUI complete driver per VirtIO sound 1.2 + */ + +#include +#include +#include + +#define DBG_TAG "virtio.dev.sound" +#define DBG_LVL DBG_INFO +#include + +#include "virtio_config/virtio-sound.h" +#include "virtio_internal.h" + +#define QUEUE_CONTROL 0 +#define QUEUE_EVENT 1 +#define QUEUE_TX 2 +#define QUEUE_RX 3 + +#ifndef VSOUND_PERIOD_BYTES +#define VSOUND_PERIOD_BYTES RT_AUDIO_REPLAY_MP_BLOCK_SIZE +#endif + +#ifndef VSOUND_PERIOD_COUNT +#define VSOUND_PERIOD_COUNT 2 +#endif + +#ifndef VSOUND_RECYCLE_MAX +#define VSOUND_RECYCLE_MAX 32 +#endif + +#ifndef VSOUND_REPLAY_POLL_MS +#define VSOUND_REPLAY_POLL_MS 2 +#endif + + +static struct virtio_sound *virtio_sound_replay_notify_dev; + +struct virtio_sound_jack +{ + rt_uint32_t nid; + rt_uint32_t features; + rt_uint32_t defconf; + rt_uint32_t caps; + rt_bool_t connected; + int type; +}; + +struct virtio_sound_pcm_stream +{ + rt_uint32_t nid; + rt_uint32_t sid; + rt_uint32_t features; + rt_uint64_t formats; + rt_uint64_t rates; + rt_uint8_t direction; + rt_uint8_t channels_min; + rt_uint8_t channels_max; +}; + +struct virtio_sound_ctl_elem +{ + rt_uint32_t id; + rt_uint32_t role; + rt_uint32_t type; + rt_uint32_t count; + rt_uint32_t min; + rt_uint32_t max; +}; + +struct virtio_sound_io_msg +{ + struct virtio_snd_pcm_xfer xfer; + struct virtio_snd_pcm_status status; + rt_uint32_t period; + rt_bool_t pending; + rt_uint8_t *mp_block; +}; + +struct virtio_sound_pcm_runtime +{ + struct virtio_sound_pcm_stream *stream; + rt_uint8_t *buffer; + rt_uint32_t buffer_bytes; + rt_uint32_t period_bytes; + rt_uint32_t period_count; + struct virtio_sound_io_msg *msgs; + rt_uint8_t format; + rt_uint8_t rate; + rt_uint8_t channels; + rt_bool_t active; + rt_bool_t params_set; +}; + +struct virtio_sound +{ + struct rt_audio_device parent; + struct rt_audio_configure audio_config; + + struct rt_virtio_device *vdev; + + struct rt_virtqueue *vqs[4]; + + struct virtio_snd_event *event_msgs; + rt_uint32_t event_msgs_nr; + + struct rt_completion ctl_done; + struct rt_spinlock ctl_lock; + + rt_bool_t playback_mute; + rt_bool_t capture_mute; + rt_int32_t playback_volume; + rt_int32_t capture_volume; + + rt_uint32_t jacks_nr; + rt_uint32_t chmaps_nr; + rt_uint32_t substreams_nr; + rt_uint32_t controls_nr; + struct virtio_sound_jack *jacks; + struct virtio_sound_pcm_stream *substreams; + struct virtio_snd_chmap_info *chmaps; + + struct virtio_sound_pcm_runtime playback; + struct virtio_sound_pcm_runtime capture; + + struct rt_spinlock tx_lock; + struct rt_spinlock rx_lock; + + struct rt_semaphore replay_sem; + struct rt_thread *replay_thread; + rt_bool_t replay_thread_exit; + struct rt_mutex replay_push_lock; +}; + +#define raw_to_virtio_sound(raw) rt_container_of(raw, struct virtio_sound, parent) + +static rt_uint32_t virtio_sound_pcm_pending(struct virtio_sound_pcm_runtime *rt); +static rt_err_t virtio_sound_pcm_submit_tx(struct virtio_sound *vsound, + struct virtio_sound_pcm_runtime *rt, rt_uint32_t period); +static rt_err_t virtio_sound_pcm_submit_rx(struct virtio_sound *vsound, + struct virtio_sound_pcm_runtime *rt, rt_uint32_t period); + +static void virtio_sound_schedule_tx_complete(struct virtio_sound *vsound); +static rt_uint32_t virtio_sound_vq_recycle(struct rt_virtqueue *vq, + struct virtio_sound *vsound, + struct virtio_sound_pcm_runtime *rt, + struct rt_spinlock *lock, + rt_bool_t is_playback); +static rt_uint32_t virtio_sound_vq_drain_used(struct rt_virtqueue *vq, + struct virtio_sound *vsound, + struct virtio_sound_pcm_runtime *rt, + struct rt_spinlock *lock, + rt_bool_t is_playback); + +static rt_int32_t virtio_sound_find_free_period(struct virtio_sound_pcm_runtime *rt) +{ + if (!rt->msgs) + { + return -1; + } + + for (rt_uint32_t i = 0; i < rt->period_count; ++i) + { + if (!rt->msgs[i].pending) + { + return (rt_int32_t)i; + } + } + + return -1; +} + +static void virtio_sound_replay_reset_msgs(struct virtio_sound_pcm_runtime *rt) +{ + if (!rt || !rt->msgs) + { + return; + } + + for (rt_uint32_t i = 0; i < rt->period_count; ++i) + { + rt->msgs[i].pending = RT_FALSE; + rt->msgs[i].mp_block = RT_NULL; + } +} + +/* + * dev_audio _audio_send_replay_frame() loops while (index < dst_size) with + * remain_bytes = MIN(..., src_size - read_index). When read_index is stale + * and equals src_size, remain_bytes stays 0 and the loop never advances. + */ +static void virtio_sound_replay_queue_reset(struct virtio_sound *vsound) +{ + struct rt_audio_replay *replay = vsound->parent.replay; + + if (!replay) + { + return; + } + + replay->read_index = 0; + replay->pos = 0; + replay->write_index = 0; +} + +static void virtio_sound_replay_drain_tx(struct virtio_sound *vsound) +{ + struct rt_virtqueue *vq = vsound->vqs[QUEUE_TX]; + + if (vq) + { + virtio_sound_vq_drain_used(vq, vsound, &vsound->playback, + &vsound->tx_lock, RT_TRUE); + } +} + +static rt_bool_t virtio_sound_push_one_period(struct virtio_sound *vsound) +{ + struct rt_audio_device *audio = &vsound->parent; + struct virtio_sound_pcm_runtime *rt = &vsound->playback; + struct virtio_sound_io_msg *msg; + rt_int32_t period; + rt_uint8_t *data; + rt_size_t src_size; + rt_err_t err; + + if (!audio->replay || !rt->active || !rt->buffer || !rt->msgs) + { + return RT_FALSE; + } + + period = virtio_sound_find_free_period(rt); + if (period < 0) + { + return RT_FALSE; + } + + err = rt_data_queue_peek(&audio->replay->queue, (const void **)&data, &src_size); + if (err != RT_EOK || src_size == 0 || src_size < rt->period_bytes) + { + return RT_FALSE; + } + + rt_memcpy(rt->buffer + period * rt->period_bytes, data, rt->period_bytes); + + err = rt_data_queue_pop(&audio->replay->queue, (const void **)&data, &src_size, + RT_WAITING_NO); + if (err != RT_EOK) + { + return RT_FALSE; + } + + msg = &rt->msgs[period]; + msg->mp_block = data; + + + if (virtio_sound_pcm_submit_tx(vsound, rt, (rt_uint32_t)period) != RT_EOK) + { + msg->mp_block = RT_NULL; + rt_mp_free(data); + return RT_FALSE; + } + + return RT_TRUE; +} + +static void virtio_sound_replay_push(struct virtio_sound *vsound) +{ + struct rt_audio_device *audio = &vsound->parent; + struct virtio_sound_pcm_runtime *rt = &vsound->playback; + rt_bool_t rescheduled = RT_FALSE; + + if (!rt->active || !audio->replay || !rt->buffer || !rt->msgs) + { + return; + } + + if (rt_interrupt_get_nest() != 0) + { + virtio_sound_schedule_tx_complete(vsound); + return; + } + + rt_mutex_take(&vsound->replay_push_lock, RT_WAITING_FOREVER); + + virtio_sound_replay_drain_tx(vsound); + + while (rt->active) + { + if (rt_data_queue_len(&audio->replay->queue) == 0) + { + break; + } + + if (virtio_sound_find_free_period(rt) < 0) + { + break; + } + + + if (!virtio_sound_push_one_period(vsound)) + { + break; + } + + virtio_sound_replay_drain_tx(vsound); + } + + if (rt->active && audio->replay && + rt_data_queue_len(&audio->replay->queue) > 0 && + virtio_sound_find_free_period(rt) >= 0) + { + rescheduled = RT_TRUE; + } + + rt_mutex_release(&vsound->replay_push_lock); + + + if (rescheduled) + { + virtio_sound_schedule_tx_complete(vsound); + } +} + +static void virtio_sound_replay_thread_entry(void *parameter) +{ + struct virtio_sound *vsound = parameter; + + while (!vsound->replay_thread_exit) + { + (void)rt_sem_take(&vsound->replay_sem, + rt_tick_from_millisecond(VSOUND_REPLAY_POLL_MS)); + + if (vsound->replay_thread_exit) + { + break; + } + + virtio_sound_replay_push(vsound); + } +} + +static void virtio_sound_schedule_tx_complete(struct virtio_sound *vsound) +{ + rt_sem_release(&vsound->replay_sem); +} + +static void virtio_sound_replay_queue_notify(struct rt_data_queue *queue, rt_uint32_t event) +{ + struct virtio_sound *vsound = virtio_sound_replay_notify_dev; + + RT_UNUSED(queue); + + if (event != RT_DATAQUEUE_EVENT_PUSH || vsound == RT_NULL || + !vsound->playback.active) + { + return; + } + + virtio_sound_replay_push(vsound); +} + +static void virtio_sound_replay_thread_stop(struct virtio_sound *vsound) +{ + if (!vsound->replay_thread) + { + return; + } + + vsound->replay_thread_exit = RT_TRUE; + rt_sem_release(&vsound->replay_sem); + rt_thread_mdelay(10); + rt_thread_delete(vsound->replay_thread); + vsound->replay_thread = RT_NULL; +} + +static rt_err_t virtio_sound_ctl_status(struct virtio_sound *vsound, rt_uint32_t code) +{ + switch (code) + { + case VIRTIO_SND_S_OK: + return RT_EOK; + + case VIRTIO_SND_S_NOT_SUPP: + return -RT_ENOSYS; + + case VIRTIO_SND_S_BAD_MSG: + return -RT_EINVAL; + + case VIRTIO_SND_S_IO_ERR: + default: + return -RT_EIO; + } +} + +static rt_err_t virtio_sound_ctl_send(struct virtio_sound *vsound, + void *request, rt_size_t request_size, + void *response, rt_size_t response_size, + void *in_extra, rt_size_t in_extra_size) +{ + rt_err_t err; + rt_ubase_t level; + rt_uint32_t bufs = in_extra ? 3 : 2; + struct rt_virtqueue *vq = vsound->vqs[QUEUE_CONTROL]; + + rt_virtqueue_wait_prepare(vq, bufs); + + level = rt_spin_lock_irqsave(&vsound->ctl_lock); + + rt_virtqueue_add_outbuf(vq, request, request_size); + rt_virtqueue_add_inbuf(vq, response, response_size); + + if (in_extra) + { + rt_virtqueue_add_inbuf(vq, in_extra, in_extra_size); + } + + rt_spin_unlock_irqrestore(&vsound->ctl_lock, level); + rt_virtqueue_kick(vq); + + + rt_completion_wait(&vsound->ctl_done, RT_WAITING_FOREVER); + + err = virtio_sound_ctl_status(vsound, + virtio32_to_cpu(vsound->vdev, ((struct virtio_snd_hdr *)response)->code)); + + + return err; +} + +static rt_err_t virtio_sound_ctl_query_info(struct virtio_sound *vsound, + int command, int start_id, int count, rt_size_t size, void *info) +{ + struct virtio_snd_query_info query; + struct virtio_snd_hdr response; + + query.hdr.code = cpu_to_virtio32(vsound->vdev, command); + query.start_id = cpu_to_virtio32(vsound->vdev, start_id); + query.count = cpu_to_virtio32(vsound->vdev, count); + query.size = cpu_to_virtio32(vsound->vdev, size); + + return virtio_sound_ctl_send(vsound, &query, sizeof(query), + &response, sizeof(response), info, count * size); +} + +static rt_err_t virtio_sound_pcm_ctl(struct virtio_sound *vsound, + struct virtio_sound_pcm_stream *stream, int command, + void *request, rt_size_t request_size) +{ + struct virtio_snd_pcm_hdr simple; + struct virtio_snd_hdr response; + + if (!request || request_size < sizeof(struct virtio_snd_pcm_hdr)) + { + rt_memset(&simple, 0, sizeof(simple)); + simple.hdr.code = cpu_to_virtio32(vsound->vdev, command); + simple.stream_id = cpu_to_virtio32(vsound->vdev, stream->sid); + request = &simple; + request_size = sizeof(simple); + } + + return virtio_sound_ctl_send(vsound, request, request_size, + &response, sizeof(response), RT_NULL, 0); +} + +static int virtio_sound_pick_format_bits(rt_uint64_t formats) +{ + static const rt_uint32_t format_map[] = + { + [VIRTIO_SND_PCM_FMT_IMA_ADPCM] = 4, + [VIRTIO_SND_PCM_FMT_MU_LAW] = 8, + [VIRTIO_SND_PCM_FMT_A_LAW] = 8, + [VIRTIO_SND_PCM_FMT_S8] = 8, + [VIRTIO_SND_PCM_FMT_U8] = 8, + [VIRTIO_SND_PCM_FMT_S16] = 16, + [VIRTIO_SND_PCM_FMT_U16] = 16, + [VIRTIO_SND_PCM_FMT_S18_3] = 18, + [VIRTIO_SND_PCM_FMT_U18_3] = 18, + [VIRTIO_SND_PCM_FMT_S20_3] = 20, + [VIRTIO_SND_PCM_FMT_U20_3] = 20, + [VIRTIO_SND_PCM_FMT_S24_3] = 24, + [VIRTIO_SND_PCM_FMT_U24_3] = 24, + [VIRTIO_SND_PCM_FMT_S20] = 20, + [VIRTIO_SND_PCM_FMT_U20] = 20, + [VIRTIO_SND_PCM_FMT_S24] = 24, + [VIRTIO_SND_PCM_FMT_U24] = 24, + [VIRTIO_SND_PCM_FMT_S32] = 32, + [VIRTIO_SND_PCM_FMT_U32] = 32, + [VIRTIO_SND_PCM_FMT_FLOAT] = 32, + [VIRTIO_SND_PCM_FMT_FLOAT64] = 64, + [VIRTIO_SND_PCM_FMT_DSD_U8] = 8, + [VIRTIO_SND_PCM_FMT_DSD_U16] = 16, + [VIRTIO_SND_PCM_FMT_DSD_U32] = 32, + [VIRTIO_SND_PCM_FMT_IEC958_SUBFRAME] = 32 + }; + + for (int i = 0; i < RT_ARRAY_SIZE(format_map); ++i) + { + if (formats & RT_BIT_ULL(i)) + { + if (format_map[i]) + { + return format_map[i]; + } + } + } + + return 16; +} + +static int virtio_sound_pick_format(rt_uint64_t formats, int samplebits) +{ + static const struct + { + int bits; + int fmt; + } prefer[] = + { + { 16, VIRTIO_SND_PCM_FMT_S16 }, + { 32, VIRTIO_SND_PCM_FMT_S32 }, + { 24, VIRTIO_SND_PCM_FMT_S24 }, + { 8, VIRTIO_SND_PCM_FMT_S8 }, + }; + + if (samplebits > 0) + { + for (rt_size_t i = 0; i < RT_ARRAY_SIZE(prefer); ++i) + { + if (prefer[i].bits == samplebits && + (formats & RT_BIT_ULL(prefer[i].fmt))) + { + return prefer[i].fmt; + } + } + } + + for (int i = 0; i < 64; ++i) + { + if (formats & RT_BIT_ULL(i)) + { + return i; + } + } + + return VIRTIO_SND_PCM_FMT_S16; +} + +static int virtio_sound_pick_rate(rt_uint64_t rates) +{ + static const rt_uint32_t rate_map[] = + { + [VIRTIO_SND_PCM_RATE_5512] = 5512, + [VIRTIO_SND_PCM_RATE_8000] = 8000, + [VIRTIO_SND_PCM_RATE_11025] = 11025, + [VIRTIO_SND_PCM_RATE_16000] = 16000, + [VIRTIO_SND_PCM_RATE_22050] = 22050, + [VIRTIO_SND_PCM_RATE_32000] = 32000, + [VIRTIO_SND_PCM_RATE_44100] = 44100, + [VIRTIO_SND_PCM_RATE_48000] = 48000, + [VIRTIO_SND_PCM_RATE_64000] = 64000, + [VIRTIO_SND_PCM_RATE_88200] = 88200, + [VIRTIO_SND_PCM_RATE_96000] = 96000, + [VIRTIO_SND_PCM_RATE_176400] = 176400, + [VIRTIO_SND_PCM_RATE_192000] = 192000, + [VIRTIO_SND_PCM_RATE_384000] = 384000, + }; + + for (int i = 0; i < RT_ARRAY_SIZE(rate_map); ++i) + { + if (rates & RT_BIT_ULL(i)) + { + if (rate_map[i]) + { + return rate_map[i]; + } + } + } + + return 44100; +} + +static int virtio_sound_pick_rate_index(rt_uint64_t rates, int samplerate) +{ + static const struct + { + int hz; + int idx; + } prefer[] = + { + { 48000, VIRTIO_SND_PCM_RATE_48000 }, + { 44100, VIRTIO_SND_PCM_RATE_44100 }, + { 96000, VIRTIO_SND_PCM_RATE_96000 }, + { 192000, VIRTIO_SND_PCM_RATE_192000 }, + { 8000, VIRTIO_SND_PCM_RATE_8000 }, + { 16000, VIRTIO_SND_PCM_RATE_16000 }, + }; + + if (samplerate > 0) + { + for (rt_size_t i = 0; i < RT_ARRAY_SIZE(prefer); ++i) + { + if (prefer[i].hz == samplerate && + (rates & RT_BIT_ULL(prefer[i].idx))) + { + return prefer[i].idx; + } + } + } + + for (int i = 0; i < 64; ++i) + { + if (rates & RT_BIT_ULL(i)) + { + return i; + } + } + + return VIRTIO_SND_PCM_RATE_44100; +} + +static struct virtio_sound_pcm_stream *virtio_sound_get_stream(struct virtio_sound *vsound, + rt_bool_t is_playback) +{ + rt_uint8_t dir = is_playback ? VIRTIO_SND_D_OUTPUT : VIRTIO_SND_D_INPUT; + + for (rt_uint32_t i = 0; i < vsound->substreams_nr; ++i) + { + if (vsound->substreams[i].direction == dir) + { + return &vsound->substreams[i]; + } + } + + return RT_NULL; +} + +static rt_err_t virtio_sound_pcm_runtime_alloc(struct virtio_sound *vsound, + struct virtio_sound_pcm_runtime *rt) +{ + if (rt->buffer) + { + return RT_EOK; + } + + rt->period_bytes = VSOUND_PERIOD_BYTES; + rt->period_count = VSOUND_PERIOD_COUNT; + rt->buffer_bytes = rt->period_bytes * rt->period_count; + + rt->buffer = rt_malloc(rt->buffer_bytes); + if (!rt->buffer) + { + return -RT_ENOMEM; + } + + rt_memset(rt->buffer, 0, rt->buffer_bytes); + + rt->msgs = rt_calloc(rt->period_count, sizeof(*rt->msgs)); + if (!rt->msgs) + { + rt_free(rt->buffer); + rt->buffer = RT_NULL; + return -RT_ENOMEM; + } + + for (rt_uint32_t i = 0; i < rt->period_count; ++i) + { + rt->msgs[i].period = i; + } + + return RT_EOK; +} + +static void virtio_sound_pcm_runtime_free(struct virtio_sound_pcm_runtime *rt) +{ + rt_free(rt->msgs); + rt_free(rt->buffer); + rt->msgs = RT_NULL; + rt->buffer = RT_NULL; + rt->buffer_bytes = 0; + rt->params_set = RT_FALSE; + rt->active = RT_FALSE; +} + +static rt_err_t virtio_sound_pcm_set_params(struct virtio_sound *vsound, + struct virtio_sound_pcm_runtime *rt) +{ + struct virtio_snd_pcm_set_params request; + rt_err_t err; + + RT_ASSERT(rt->stream); + + request.hdr.hdr.code = cpu_to_virtio32(vsound->vdev, VIRTIO_SND_R_PCM_SET_PARAMS); + request.hdr.stream_id = cpu_to_virtio32(vsound->vdev, rt->stream->sid); + request.buffer_bytes = cpu_to_virtio32(vsound->vdev, rt->buffer_bytes); + request.period_bytes = cpu_to_virtio32(vsound->vdev, rt->period_bytes); + request.features = 0; + + if (rt->stream->features & RT_BIT(VIRTIO_SND_PCM_F_MSG_POLLING)) + { + request.features = cpu_to_virtio32(vsound->vdev, + RT_BIT(VIRTIO_SND_PCM_F_MSG_POLLING)); + } + + rt->channels = (rt_uint8_t)vsound->audio_config.channels; + if (rt->channels < rt->stream->channels_min) + { + rt->channels = rt->stream->channels_min; + } + if (rt->channels > rt->stream->channels_max) + { + rt->channels = rt->stream->channels_max; + } + + rt->format = (rt_uint8_t)virtio_sound_pick_format(rt->stream->formats, + vsound->audio_config.samplebits); + rt->rate = (rt_uint8_t)virtio_sound_pick_rate_index(rt->stream->rates, + vsound->audio_config.samplerate); + + request.channels = rt->channels; + request.format = rt->format; + request.rate = rt->rate; + request.padding = 0; + + err = virtio_sound_pcm_ctl(vsound, rt->stream, VIRTIO_SND_R_PCM_SET_PARAMS, + &request, sizeof(request)); + if (err == RT_EOK) + rt->params_set = RT_TRUE; + + return err; +} + +static rt_uint32_t virtio_sound_pcm_pending(struct virtio_sound_pcm_runtime *rt) +{ + rt_uint32_t pending = 0; + + if (!rt->msgs) + { + return 0; + } + + for (rt_uint32_t i = 0; i < rt->period_count; ++i) + { + if (rt->msgs[i].pending) + { + pending++; + } + } + + return pending; +} + +static rt_uint32_t virtio_sound_pcm_process_used(struct rt_virtqueue *vq, + struct virtio_sound *vsound, + struct virtio_sound_pcm_runtime *rt, + struct rt_spinlock *lock, + rt_bool_t is_playback, + rt_bool_t kick_pipeline) +{ + rt_uint32_t processed = 0; + rt_ubase_t level; + + if (!vq) + { + return 0; + } + + level = rt_spin_lock_irqsave(lock); + + while (processed < VSOUND_RECYCLE_MAX) + { + rt_size_t len; + void *buf = rt_virtqueue_read_buf(vq, &len); + struct virtio_sound_io_msg *msg; + + if (!buf) + { + break; + } + + msg = rt_container_of(buf, struct virtio_sound_io_msg, xfer); + msg->pending = RT_FALSE; + processed++; + + if (virtio32_to_cpu(vsound->vdev, msg->status.status) != VIRTIO_SND_S_OK) + { + LOG_W("PCM I/O period %u status 0x%x", + msg->period, + virtio32_to_cpu(vsound->vdev, msg->status.status)); + } + + rt_spin_unlock_irqrestore(lock, level); + + if (is_playback && msg->mp_block) + { + rt_mp_free(msg->mp_block); + msg->mp_block = RT_NULL; + } + + if (rt->active && kick_pipeline) + { + if (is_playback) + { + virtio_sound_schedule_tx_complete(vsound); + } + else + { + rt_audio_rx_done(&vsound->parent, + rt->buffer + msg->period * rt->period_bytes, + rt->period_bytes); + virtio_sound_pcm_submit_rx(vsound, rt, msg->period); + } + } + + level = rt_spin_lock_irqsave(lock); + } + + rt_spin_unlock_irqrestore(lock, level); + + return processed; +} + +static rt_uint32_t virtio_sound_vq_recycle(struct rt_virtqueue *vq, + struct virtio_sound *vsound, + struct virtio_sound_pcm_runtime *rt, + struct rt_spinlock *lock, + rt_bool_t is_playback) +{ + return virtio_sound_pcm_process_used(vq, vsound, rt, lock, is_playback, RT_FALSE); +} + +static rt_uint32_t virtio_sound_vq_drain_used(struct rt_virtqueue *vq, + struct virtio_sound *vsound, + struct virtio_sound_pcm_runtime *rt, + struct rt_spinlock *lock, + rt_bool_t is_playback) +{ + rt_uint32_t total = 0; + + for (rt_uint32_t n = 0; n < VSOUND_RECYCLE_MAX; n++) + { + rt_uint32_t batch = virtio_sound_vq_recycle(vq, vsound, rt, lock, is_playback); + + if (batch == 0) + { + break; + } + + total += batch; + } + + return total; +} + +static rt_bool_t virtio_sound_vq_wait_prepare(struct rt_virtqueue *vq, + struct virtio_sound *vsound, + struct virtio_sound_pcm_runtime *rt, + struct rt_spinlock *lock, + rt_bool_t is_playback, + rt_uint32_t nr) +{ + rt_uint32_t retries = 0; + + while (!rt_virtqueue_prepare(vq, nr)) + { + virtio_sound_vq_recycle(vq, vsound, rt, lock, is_playback); + + if (rt_virtqueue_prepare(vq, nr)) + { + break; + } + + rt_thread_yield(); + rt_hw_cpu_relax(); + + /* + * In thread context, keep polling until descriptors are recycled + * (QEMU virtio-sound may complete TX synchronously on kick). + */ + if (rt_interrupt_get_nest() != 0) + { + if (++retries > 1000) + { + LOG_E("virtqueue %s wait timeout", vq->name ? vq->name : "pcm"); + return RT_FALSE; + } + } + else if (++retries > 50000) + { + LOG_E("virtqueue %s thread wait timeout", vq->name ? vq->name : "pcm"); + return RT_FALSE; + } + } + + return RT_TRUE; +} + +static rt_err_t virtio_sound_pcm_submit_tx(struct virtio_sound *vsound, + struct virtio_sound_pcm_runtime *rt, rt_uint32_t period) +{ + struct virtio_sound_io_msg *msg; + struct rt_virtqueue *vq; + rt_ubase_t level; + + if (!rt->active || !rt->msgs || period >= rt->period_count) + { + return -RT_EINVAL; + } + + msg = &rt->msgs[period]; + if (msg->pending) + { + return -RT_EBUSY; + } + + msg->xfer.stream_id = cpu_to_virtio32(vsound->vdev, rt->stream->sid); + msg->status.status = 0; + msg->status.latency_bytes = 0; + + vq = vsound->vqs[QUEUE_TX]; + + if (!virtio_sound_vq_wait_prepare(vq, vsound, rt, &vsound->tx_lock, RT_TRUE, 3)) + { + return -RT_EBUSY; + } + + level = rt_spin_lock_irqsave(&vsound->tx_lock); + + if (rt_virtqueue_add_outbuf(vq, &msg->xfer, sizeof(msg->xfer)) != RT_EOK || + rt_virtqueue_add_outbuf(vq, rt->buffer + period * rt->period_bytes, + rt->period_bytes) != RT_EOK || + rt_virtqueue_add_inbuf(vq, &msg->status, sizeof(msg->status)) != RT_EOK) + { + rt_spin_unlock_irqrestore(&vsound->tx_lock, level); + return -RT_EIO; + } + + msg->pending = RT_TRUE; + rt_spin_unlock_irqrestore(&vsound->tx_lock, level); + + /* + * Kick must run without tx_lock: QEMU may invoke tx_done synchronously, + * which also takes tx_lock and would deadlock. + */ + rt_virtqueue_kick(vq); + + /* + * Host may complete the period before the TX ISR runs (QEMU trace shows + * virtio_snd_handle_tx_xfer immediately). Poll the used ring from thread + * context so the pipeline can submit the next period without waiting. + */ + if (rt_interrupt_get_nest() == 0) + { + virtio_sound_pcm_process_used(vq, vsound, rt, &vsound->tx_lock, RT_TRUE, RT_FALSE); + } + else if (virtio_sound_vq_recycle(vq, vsound, rt, &vsound->tx_lock, RT_TRUE) > 0) + { + virtio_sound_schedule_tx_complete(vsound); + } + + return RT_EOK; +} + +static rt_err_t virtio_sound_pcm_submit_rx(struct virtio_sound *vsound, + struct virtio_sound_pcm_runtime *rt, rt_uint32_t period) +{ + struct virtio_sound_io_msg *msg; + struct rt_virtqueue *vq; + rt_ubase_t level; + + if (!rt->active || !rt->msgs || period >= rt->period_count) + { + return -RT_EINVAL; + } + + msg = &rt->msgs[period]; + if (msg->pending) + { + return RT_EOK; + } + + msg->xfer.stream_id = cpu_to_virtio32(vsound->vdev, rt->stream->sid); + msg->status.status = 0; + msg->status.latency_bytes = 0; + + vq = vsound->vqs[QUEUE_RX]; + + if (!virtio_sound_vq_wait_prepare(vq, vsound, rt, &vsound->rx_lock, RT_FALSE, 3)) + { + return -RT_EBUSY; + } + + level = rt_spin_lock_irqsave(&vsound->rx_lock); + + if (rt_virtqueue_add_outbuf(vq, &msg->xfer, sizeof(msg->xfer)) != RT_EOK || + rt_virtqueue_add_inbuf(vq, rt->buffer + period * rt->period_bytes, + rt->period_bytes) != RT_EOK || + rt_virtqueue_add_inbuf(vq, &msg->status, sizeof(msg->status)) != RT_EOK) + { + rt_spin_unlock_irqrestore(&vsound->rx_lock, level); + return -RT_EIO; + } + + msg->pending = RT_TRUE; + rt_spin_unlock_irqrestore(&vsound->rx_lock, level); + + rt_virtqueue_kick(vq); + + virtio_sound_vq_recycle(vq, vsound, rt, &vsound->rx_lock, RT_FALSE); + + return RT_EOK; +} + +static rt_err_t virtio_sound_audio_getcaps(struct rt_audio_device *audio, + struct rt_audio_caps *caps) +{ + struct virtio_sound_pcm_stream *stream; + struct virtio_sound *vsound = raw_to_virtio_sound(audio); + + switch (caps->main_type) + { + case AUDIO_TYPE_QUERY: + switch (caps->sub_type) + { + case AUDIO_TYPE_QUERY: + caps->udata.mask = AUDIO_TYPE_INPUT | AUDIO_TYPE_OUTPUT | AUDIO_TYPE_MIXER; + break; + + default: + return -RT_ENOSYS; + } + break; + + case AUDIO_TYPE_OUTPUT: + case AUDIO_TYPE_INPUT: + stream = virtio_sound_get_stream(vsound, caps->main_type == AUDIO_TYPE_OUTPUT); + + if (!stream) + { + return -RT_ENOSYS; + } + + switch (caps->sub_type) + { + case AUDIO_DSP_PARAM: + caps->udata.config.channels = vsound->audio_config.channels; + caps->udata.config.samplebits = virtio_sound_pick_format_bits(stream->formats); + caps->udata.config.samplerate = virtio_sound_pick_rate(stream->rates); + break; + + case AUDIO_DSP_SAMPLERATE: + caps->udata.config.samplerate = virtio_sound_pick_rate(stream->rates); + break; + + case AUDIO_DSP_CHANNELS: + caps->udata.config.channels = vsound->audio_config.channels; + break; + + case AUDIO_DSP_SAMPLEBITS: + caps->udata.config.samplebits = virtio_sound_pick_format_bits(stream->formats); + break; + + default: + return -RT_ENOSYS; + } + break; + + case AUDIO_TYPE_MIXER: + switch (caps->sub_type) + { + case AUDIO_MIXER_QUERY: + caps->udata.mask = 0; + caps->udata.mask |= AUDIO_MIXER_VOLUME; + caps->udata.mask |= AUDIO_MIXER_MUTE; + break; + + case AUDIO_MIXER_VOLUME: + caps->udata.value = vsound->playback_volume; + break; + + case AUDIO_MIXER_MUTE: + caps->udata.value = vsound->playback_mute ? 1 : 0; + break; + + default: + return -RT_ENOSYS; + } + break; + + default: + return -RT_ENOSYS; + } + + return RT_EOK; +} + +static rt_err_t virtio_sound_audio_configure(struct rt_audio_device *audio, + struct rt_audio_caps *caps) +{ + struct virtio_sound *vsound = raw_to_virtio_sound(audio); + + switch (caps->main_type) + { + case AUDIO_TYPE_OUTPUT: + switch (caps->sub_type) + { + case AUDIO_DSP_SAMPLERATE: + vsound->audio_config.samplerate = caps->udata.config.samplerate; + vsound->playback.params_set = RT_FALSE; + break; + + case AUDIO_DSP_CHANNELS: + vsound->audio_config.channels = caps->udata.config.channels; + vsound->playback.params_set = RT_FALSE; + break; + + case AUDIO_DSP_SAMPLEBITS: + vsound->audio_config.samplebits = caps->udata.config.samplebits; + vsound->playback.params_set = RT_FALSE; + break; + + case AUDIO_DSP_PARAM: + vsound->audio_config.samplerate = caps->udata.config.samplerate; + vsound->audio_config.channels = caps->udata.config.channels; + vsound->audio_config.samplebits = caps->udata.config.samplebits; + vsound->playback.params_set = RT_FALSE; + break; + + default: + return -RT_ENOSYS; + } + break; + + case AUDIO_TYPE_INPUT: + switch (caps->sub_type) + { + case AUDIO_DSP_SAMPLERATE: + vsound->audio_config.samplerate = caps->udata.config.samplerate; + vsound->capture.params_set = RT_FALSE; + break; + + case AUDIO_DSP_CHANNELS: + vsound->audio_config.channels = caps->udata.config.channels; + vsound->capture.params_set = RT_FALSE; + break; + + case AUDIO_DSP_SAMPLEBITS: + vsound->audio_config.samplebits = caps->udata.config.samplebits; + vsound->capture.params_set = RT_FALSE; + break; + + case AUDIO_DSP_PARAM: + vsound->audio_config.samplerate = caps->udata.config.samplerate; + vsound->audio_config.channels = caps->udata.config.channels; + vsound->audio_config.samplebits = caps->udata.config.samplebits; + vsound->capture.params_set = RT_FALSE; + break; + + default: + return -RT_ENOSYS; + } + break; + + case AUDIO_TYPE_MIXER: + switch (caps->sub_type) + { + case AUDIO_MIXER_VOLUME: + vsound->playback_volume = caps->udata.value; + break; + + case AUDIO_MIXER_MUTE: + vsound->playback_mute = caps->udata.value ? RT_TRUE : RT_FALSE; + break; + + default: + return -RT_ENOSYS; + } + break; + + default: + return -RT_ENOSYS; + } + + return RT_EOK; +} + +static rt_err_t virtio_sound_pcm_start(struct virtio_sound *vsound, + struct virtio_sound_pcm_runtime *rt, rt_bool_t is_playback) +{ + rt_err_t err; + + if (!rt->stream) + { + LOG_W("start: no %s stream", is_playback ? "playback" : "capture"); + return -RT_ENOSYS; + } + + + if (is_playback) + { + virtio_sound_replay_queue_reset(vsound); + } + + err = virtio_sound_pcm_runtime_alloc(vsound, rt); + if (err) + { + return err; + } + + if (!rt->params_set) + { + err = virtio_sound_pcm_set_params(vsound, rt); + if (err) + { + return err; + } + } + + err = virtio_sound_pcm_ctl(vsound, rt->stream, VIRTIO_SND_R_PCM_PREPARE, RT_NULL, 0); + if (err) + { + return err; + } + + err = virtio_sound_pcm_ctl(vsound, rt->stream, VIRTIO_SND_R_PCM_START, RT_NULL, 0); + if (err) + { + return err; + } + + rt->active = RT_TRUE; + virtio_sound_replay_reset_msgs(rt); + + if (is_playback && vsound->parent.replay && + rt_data_queue_len(&vsound->parent.replay->queue) > 0) + { + virtio_sound_replay_push(vsound); + } + else if (!is_playback) + { + for (rt_uint32_t i = 0; i < rt->period_count; ++i) + { + err = virtio_sound_pcm_submit_rx(vsound, rt, i); + if (err) + { + break; + } + } + } + + return err; +} + +static rt_err_t virtio_sound_pcm_stop(struct virtio_sound *vsound, + struct virtio_sound_pcm_runtime *rt) +{ + rt_err_t err; + int timeout = 1000; + + if (!rt->stream || !rt->active) + { + return RT_EOK; + } + + + err = virtio_sound_pcm_ctl(vsound, rt->stream, VIRTIO_SND_R_PCM_STOP, RT_NULL, 0); + if (err) + { + LOG_W("PCM STOP failed: %d", err); + } + + while (virtio_sound_pcm_pending(rt) > 0 && timeout-- > 0) + { + if (rt == &vsound->playback) + { + virtio_sound_vq_recycle(vsound->vqs[QUEUE_TX], vsound, rt, + &vsound->tx_lock, RT_TRUE); + } + else + { + virtio_sound_vq_recycle(vsound->vqs[QUEUE_RX], vsound, rt, + &vsound->rx_lock, RT_FALSE); + } + + rt_thread_mdelay(1); + } + + if (rt == &vsound->playback) + { + virtio_sound_replay_reset_msgs(rt); + virtio_sound_replay_queue_reset(vsound); + } + + rt->active = RT_FALSE; + + err = virtio_sound_pcm_ctl(vsound, rt->stream, VIRTIO_SND_R_PCM_RELEASE, RT_NULL, 0); + if (err) + { + LOG_W("PCM RELEASE failed: %d", err); + } + + rt->params_set = RT_FALSE; + + + return RT_EOK; +} + +static rt_err_t virtio_sound_audio_init(struct rt_audio_device *audio) +{ + struct virtio_sound *vsound = raw_to_virtio_sound(audio); + + vsound->audio_config.samplerate = 48000; + vsound->audio_config.channels = 2; + vsound->audio_config.samplebits = 16; + vsound->playback_volume = AUDIO_VOLUME_MAX; + vsound->capture_volume = AUDIO_VOLUME_MAX; + + vsound->playback.stream = virtio_sound_get_stream(vsound, RT_TRUE); + vsound->capture.stream = virtio_sound_get_stream(vsound, RT_FALSE); + + return RT_EOK; +} + +static rt_err_t virtio_sound_audio_start(struct rt_audio_device *audio, int stream) +{ + struct virtio_sound *vsound = raw_to_virtio_sound(audio); + + if (stream == AUDIO_STREAM_REPLAY) + { + return virtio_sound_pcm_start(vsound, &vsound->playback, RT_TRUE); + } + + if (stream == AUDIO_STREAM_RECORD) + { + return virtio_sound_pcm_start(vsound, &vsound->capture, RT_FALSE); + } + + return -RT_EINVAL; +} + +static rt_err_t virtio_sound_audio_stop(struct rt_audio_device *audio, int stream) +{ + struct virtio_sound *vsound = raw_to_virtio_sound(audio); + + if (stream == AUDIO_STREAM_REPLAY) + { + return virtio_sound_pcm_stop(vsound, &vsound->playback); + } + + if (stream == AUDIO_STREAM_RECORD) + { + return virtio_sound_pcm_stop(vsound, &vsound->capture); + } + + return -RT_EINVAL; +} + +static rt_ssize_t virtio_sound_audio_transmit(struct rt_audio_device *audio, + const void *write_buf, void *read_buf, rt_size_t size) +{ + struct virtio_sound *vsound = raw_to_virtio_sound(audio); + struct virtio_sound_pcm_runtime *rt = &vsound->playback; + const rt_uint8_t *pcm = write_buf; + rt_uint32_t period; + + RT_UNUSED(read_buf); + + if (!rt->active || !rt->buffer || !pcm || size == 0) + { + return 0; + } + + if (pcm < rt->buffer || pcm >= rt->buffer + rt->buffer_bytes) + { + LOG_E("transmit: bad buffer %p", pcm); + return 0; + } + + period = (rt_uint32_t)(pcm - rt->buffer) / rt->period_bytes; + if (period >= rt->period_count || size != rt->period_bytes) + { + LOG_E("transmit: bad period=%u size=%u", period, (rt_uint32_t)size); + return 0; + } + + if (virtio_sound_pcm_submit_tx(vsound, rt, period) != RT_EOK) + { + return 0; + } + + return (rt_ssize_t)size; +} + +static void virtio_sound_audio_buffer_info(struct rt_audio_device *audio, + struct rt_audio_buf_info *info) +{ + struct virtio_sound *vsound = raw_to_virtio_sound(audio); + struct virtio_sound_pcm_runtime *rt = &vsound->playback; + + if (virtio_sound_pcm_runtime_alloc(vsound, rt) != RT_EOK) + { + info->buffer = RT_NULL; + info->total_size = 0; + info->block_size = 0; + info->block_count = 0; + return; + } + + info->buffer = rt->buffer; + info->total_size = rt->buffer_bytes; + info->block_size = (rt_uint16_t)rt->period_bytes; + info->block_count = (rt_uint16_t)rt->period_count; +} + +const static struct rt_audio_ops virtio_sound_audio_ops = +{ + .getcaps = virtio_sound_audio_getcaps, + .configure = virtio_sound_audio_configure, + .init = virtio_sound_audio_init, + .start = virtio_sound_audio_start, + .stop = virtio_sound_audio_stop, + .transmit = virtio_sound_audio_transmit, + .buffer_info = virtio_sound_audio_buffer_info, +}; + +static void virtio_sound_control_done(struct rt_virtqueue *vq) +{ + rt_ubase_t level; + struct virtio_sound *vsound = vq->vdev->priv; + + level = rt_spin_lock_irqsave(&vsound->ctl_lock); + + rt_virtqueue_read_buf(vq, RT_NULL); + rt_completion_done(&vsound->ctl_done); + + rt_spin_unlock_irqrestore(&vsound->ctl_lock, level); +} + +static void virtio_sound_event_done(struct rt_virtqueue *vq) +{ + rt_ubase_t level; + struct virtio_sound *vsound = vq->vdev->priv; + struct rt_virtqueue *event_vq = vsound->vqs[QUEUE_EVENT]; + + level = rt_spin_lock_irqsave(&vsound->ctl_lock); + + while (1) + { + rt_size_t len; + struct virtio_snd_event *event = rt_virtqueue_read_buf(vq, &len); + + if (!event) + { + break; + } + + switch (virtio32_to_cpu(vsound->vdev, event->hdr.code)) + { + case VIRTIO_SND_EVT_JACK_CONNECTED: + case VIRTIO_SND_EVT_JACK_DISCONNECTED: + LOG_I("jack event 0x%x data %u", + virtio32_to_cpu(vsound->vdev, event->hdr.code), + virtio32_to_cpu(vsound->vdev, event->data)); + break; + + case VIRTIO_SND_EVT_PCM_PERIOD_ELAPSED: + break; + + case VIRTIO_SND_EVT_PCM_XRUN: + LOG_W("PCM xrun on stream %u", + virtio32_to_cpu(vsound->vdev, event->data)); + break; + + default: + break; + } + + rt_virtqueue_add_inbuf(event_vq, event, sizeof(*event)); + } + + rt_virtqueue_kick(event_vq); + + rt_spin_unlock_irqrestore(&vsound->ctl_lock, level); +} + +static void virtio_sound_tx_done(struct rt_virtqueue *vq) +{ + struct virtio_sound *vsound = vq->vdev->priv; + + virtio_sound_pcm_process_used(vq, vsound, &vsound->playback, + &vsound->tx_lock, RT_TRUE, RT_TRUE); +} + +static void virtio_sound_rx_done(struct rt_virtqueue *vq) +{ + struct virtio_sound *vsound = vq->vdev->priv; + + virtio_sound_pcm_process_used(vq, vsound, &vsound->capture, + &vsound->rx_lock, RT_FALSE, RT_TRUE); +} + +static rt_err_t virtio_sound_vq_init(struct virtio_sound *vsound) +{ + const char *names[] = + { + "control", + "event", + "tx", + "rx", + }; + rt_virtqueue_callback cbs[] = + { + &virtio_sound_control_done, + &virtio_sound_event_done, + &virtio_sound_tx_done, + &virtio_sound_rx_done, + }; + + return rt_virtio_virtqueue_install(vsound->vdev, + RT_ARRAY_SIZE(names), vsound->vqs, names, cbs); +} + +static rt_err_t virtio_sound_event_init(struct virtio_sound *vsound) +{ + struct rt_virtqueue *vq = vsound->vqs[QUEUE_EVENT]; + rt_uint32_t i; + + if (!vq) + { + return RT_EOK; + } + + vsound->event_msgs_nr = rt_virtqueue_get_virtq_size(vq); + if (!vsound->event_msgs_nr) + { + return RT_EOK; + } + + vsound->event_msgs = rt_calloc(vsound->event_msgs_nr, sizeof(*vsound->event_msgs)); + if (!vsound->event_msgs) + { + return -RT_ENOMEM; + } + + for (i = 0; i < vsound->event_msgs_nr; ++i) + { + rt_virtqueue_add_inbuf(vq, &vsound->event_msgs[i], sizeof(vsound->event_msgs[i])); + } + + rt_virtqueue_kick(vq); + + return RT_EOK; +} + +static rt_err_t virtio_sound_jack_init(struct virtio_sound *vsound) +{ + rt_err_t err; + struct virtio_snd_jack_info *info; + + rt_virtio_read_config(vsound->vdev, struct virtio_snd_config, jacks, &vsound->jacks_nr); + if (!vsound->jacks_nr) + { + return RT_EOK; + } + + vsound->jacks = rt_calloc(vsound->jacks_nr, sizeof(*vsound->jacks)); + if (!vsound->jacks) + { + return -RT_ENOMEM; + } + + info = rt_calloc(vsound->jacks_nr, sizeof(*info)); + if (!info) + { + err = -RT_ENOMEM; + goto _fail; + } + + err = virtio_sound_ctl_query_info(vsound, + VIRTIO_SND_R_JACK_INFO, 0, vsound->jacks_nr, sizeof(*info), info); + if (err) + { + goto _fail; + } + + for (rt_uint32_t i = 0; i < vsound->jacks_nr; ++i) + { + vsound->jacks[i].nid = virtio32_to_cpu(vsound->vdev, info[i].hdr.hda_fn_nid); + vsound->jacks[i].features = virtio32_to_cpu(vsound->vdev, info[i].features); + vsound->jacks[i].defconf = virtio32_to_cpu(vsound->vdev, info[i].hda_reg_defconf); + vsound->jacks[i].caps = virtio32_to_cpu(vsound->vdev, info[i].hda_reg_caps); + vsound->jacks[i].connected = info[i].connected ? RT_TRUE : RT_FALSE; + } + +_fail: + rt_free(info); + + if (err) + { + rt_free(vsound->jacks); + vsound->jacks = RT_NULL; + } + + return err; +} + +static rt_err_t virtio_sound_chmap_init(struct virtio_sound *vsound) +{ + rt_err_t err; + + rt_virtio_read_config(vsound->vdev, struct virtio_snd_config, chmaps, &vsound->chmaps_nr); + if (!vsound->chmaps_nr) + { + return RT_EOK; + } + + vsound->chmaps = rt_calloc(vsound->chmaps_nr, sizeof(*vsound->chmaps)); + if (!vsound->chmaps) + { + return -RT_ENOMEM; + } + + err = virtio_sound_ctl_query_info(vsound, + VIRTIO_SND_R_CHMAP_INFO, 0, vsound->chmaps_nr, + sizeof(*vsound->chmaps), vsound->chmaps); + if (err) + { + rt_free(vsound->chmaps); + vsound->chmaps = RT_NULL; + } + + return err; +} + +static rt_err_t virtio_sound_pcm_init(struct virtio_sound *vsound) +{ + rt_err_t err; + struct virtio_snd_pcm_info *info; + + rt_virtio_read_config(vsound->vdev, struct virtio_snd_config, streams, &vsound->substreams_nr); + + if (!vsound->substreams_nr) + { + return RT_EOK; + } + + vsound->substreams = rt_calloc(vsound->substreams_nr, sizeof(*vsound->substreams)); + + if (!vsound->substreams) + { + return -RT_ENOMEM; + } + + info = rt_calloc(vsound->substreams_nr, sizeof(*info)); + + if (!info) + { + err = -RT_ENOMEM; + goto _end; + } + + err = virtio_sound_ctl_query_info(vsound, + VIRTIO_SND_R_PCM_INFO, 0, vsound->substreams_nr, sizeof(*info), info); + if (err) + { + goto _end; + } + + for (int i = 0; i < vsound->substreams_nr; ++i) + { + struct virtio_sound_pcm_stream *vss = &vsound->substreams[i]; + + vss->nid = virtio32_to_cpu(vsound->vdev, info[i].hdr.hda_fn_nid); + vss->sid = i; + vss->features = virtio32_to_cpu(vsound->vdev, info[i].features); + vss->formats = virtio64_to_cpu(vsound->vdev, info[i].formats); + vss->rates = virtio64_to_cpu(vsound->vdev, info[i].rates); + vss->direction = info[i].direction; + vss->channels_min = info[i].channels_min; + vss->channels_max = info[i].channels_max; + } + + vsound->playback.stream = virtio_sound_get_stream(vsound, RT_TRUE); + vsound->capture.stream = virtio_sound_get_stream(vsound, RT_FALSE); + +_end: + rt_free(info); + + if (err) + { + rt_free(vsound->substreams); + vsound->substreams = RT_NULL; + } + + return err; +} + +static void virtio_sound_vq_finit(struct virtio_sound *vsound) +{ + if (vsound->vqs[0]) + { + rt_virtio_virtqueue_release(vsound->vdev); + } +} + +static void virtio_sound_free(struct virtio_sound *vsound) +{ + virtio_sound_replay_thread_stop(vsound); + virtio_sound_pcm_runtime_free(&vsound->playback); + virtio_sound_pcm_runtime_free(&vsound->capture); + rt_free(vsound->event_msgs); + rt_free(vsound->jacks); + rt_free(vsound->chmaps); + rt_free(vsound->substreams); + rt_free(vsound); +} + +static rt_err_t virtio_sound_probe(struct rt_virtio_device *vdev) +{ + rt_err_t err; + const char *audio_name; + struct virtio_sound *vsound = rt_calloc(1, sizeof(*vsound)); + + if (!vsound) + { + return -RT_ENOMEM; + } + + vdev->priv = vsound; + vsound->vdev = vdev; + vdev->parent.user_data = vsound; + + rt_completion_init(&vsound->ctl_done); + rt_spin_lock_init(&vsound->ctl_lock); + rt_spin_lock_init(&vsound->tx_lock); + rt_spin_lock_init(&vsound->rx_lock); + rt_mutex_init(&vsound->replay_push_lock, "vs_plk", RT_IPC_FLAG_PRIO); + rt_sem_init(&vsound->replay_sem, "vs_rpl", 0, RT_IPC_FLAG_FIFO); + vsound->replay_thread = rt_thread_create("vs_rpl", + virtio_sound_replay_thread_entry, vsound, 16384, 12, 10); + if (!vsound->replay_thread) + { + err = -RT_ENOMEM; + goto _fail; + } + rt_thread_startup(vsound->replay_thread); + + if ((err = virtio_sound_vq_init(vsound)) < 0) + { + goto _fail; + } + + if ((err = virtio_sound_event_init(vsound))) + { + goto _fail; + } + + if ((err = virtio_sound_jack_init(vsound))) + { + goto _fail; + } + + if ((err = virtio_sound_pcm_init(vsound))) + { + goto _fail; + } + + if ((err = virtio_sound_chmap_init(vsound))) + { + goto _fail; + } + + rt_virtio_read_config(vsound->vdev, struct virtio_snd_config, controls, &vsound->controls_nr); + + vsound->audio_config.channels = 2; + vsound->audio_config.samplerate = 48000; + vsound->audio_config.samplebits = 16; + vsound->playback_volume = AUDIO_VOLUME_MAX; + + rt_dm_dev_set_name_auto(&vsound->parent.parent, "sound"); + audio_name = rt_dm_dev_get_name(&vsound->parent.parent); + + vsound->parent.ops = (struct rt_audio_ops *)&virtio_sound_audio_ops; + + if ((err = rt_audio_register(&vsound->parent, audio_name, RT_DEVICE_FLAG_RDWR, vsound))) + { + goto _fail; + } + + if (vsound->parent.replay) + { + virtio_sound_replay_notify_dev = vsound; + vsound->parent.replay->queue.evt_notify = virtio_sound_replay_queue_notify; + } + + LOG_I("virtio sound: %u streams, %u jacks, %u chmaps", + vsound->substreams_nr, vsound->jacks_nr, vsound->chmaps_nr); + + return RT_EOK; + +_fail: + virtio_sound_vq_finit(vsound); + virtio_sound_free(vsound); + + return err; +} + +static rt_err_t virtio_sound_remove(struct rt_virtio_device *vdev) +{ + struct virtio_sound *vsound = vdev->parent.user_data; + + if (virtio_sound_replay_notify_dev == vsound) + { + virtio_sound_replay_notify_dev = RT_NULL; + } + + rt_device_unregister(&vsound->parent.parent); + virtio_sound_vq_finit(vsound); + virtio_sound_free(vsound); + + return RT_EOK; +} + +static const struct rt_virtio_device_id virtio_sound_ids[] = +{ + { VIRTIO_DEVICE_ID_AUDIO, VIRTIO_DEVICE_ANY_ID }, + { /* sentinel */ } +}; + +static struct rt_virtio_driver virtio_sound_driver = +{ + .ids = virtio_sound_ids, + .features = + RT_BIT(VIRTIO_SOUND_F_CTLS) + | RT_BIT(VIRTIO_F_ANY_LAYOUT), + + .probe = virtio_sound_probe, + .remove = virtio_sound_remove, +}; +RT_VIRTIO_DRIVER_EXPORT(virtio_sound_driver); diff --git a/components/drivers/virtio/virtio_config/virtio-rpmsg.h b/components/drivers/virtio/virtio_config/virtio-rpmsg.h new file mode 100755 index 0000000000..018d4d3f4e --- /dev/null +++ b/components/drivers/virtio/virtio_config/virtio-rpmsg.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2006-2023, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2023-02-25 GuEe-GUI the first version + */ + +#ifndef __VIRTIO_RPMSG_H__ +#define __VIRTIO_RPMSG_H__ + +#include + +#define VIRTIO_RPMSG_F_NS 0 /* RP supports name service notifications */ + +#define VIRTIO_RPMSG_MAX_BUF_SIZE 512 +#define VIRTIO_RPMSG_RESERVED_ADDRESSES 1024 + +rt_packed(struct virtio_rpmsg_hdr +{ + rt_le32_t src; + rt_le32_t dst; + rt_le32_t reserved; + rt_le16_t len; + rt_le16_t flags; + rt_uint8_t data[]; +}); + +#endif /* __VIRTIO_RPMSG_H__ */ diff --git a/components/drivers/virtio/virtio_config/virtio-sound.h b/components/drivers/virtio/virtio_config/virtio-sound.h new file mode 100755 index 0000000000..933535f9aa --- /dev/null +++ b/components/drivers/virtio/virtio_config/virtio-sound.h @@ -0,0 +1,441 @@ +/* + * Copyright (c) 2006-2023, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2023-02-25 GuEe-GUI the first version + */ + +#ifndef __VIRTIO_SOUND_H__ +#define __VIRTIO_SOUND_H__ + +#include + +#define VIRTIO_SOUND_F_CTLS 1 + +struct virtio_snd_config +{ + /* available physical jacks */ + rt_le32_t jacks; + /* available PCM streams */ + rt_le32_t streams; + /* available channel maps */ + rt_le32_t chmaps; + /* available control elements if VIRTIO_SND_F_CTLS has been negotiated */ + rt_le32_t controls; +}; + +/* supported dataflow directions */ +enum +{ + VIRTIO_SND_D_OUTPUT = 0, + VIRTIO_SND_D_INPUT +}; + +enum +{ + /* jack control request types */ + VIRTIO_SND_R_JACK_INFO = 1, + VIRTIO_SND_R_JACK_REMAP, + + /* PCM control request types */ + VIRTIO_SND_R_PCM_INFO = 0x0100, + VIRTIO_SND_R_PCM_SET_PARAMS, + VIRTIO_SND_R_PCM_PREPARE, + VIRTIO_SND_R_PCM_RELEASE, + VIRTIO_SND_R_PCM_START, + VIRTIO_SND_R_PCM_STOP, + + /* channel map control request types */ + VIRTIO_SND_R_CHMAP_INFO = 0x0200, + + /* jack event types */ + VIRTIO_SND_EVT_JACK_CONNECTED = 0x1000, + VIRTIO_SND_EVT_JACK_DISCONNECTED, + + /* PCM event types */ + VIRTIO_SND_EVT_PCM_PERIOD_ELAPSED = 0x1100, + VIRTIO_SND_EVT_PCM_XRUN, + + /* common status codes */ + VIRTIO_SND_S_OK = 0x8000, + VIRTIO_SND_S_BAD_MSG, + VIRTIO_SND_S_NOT_SUPP, + VIRTIO_SND_S_IO_ERR +}; + +/* common header */ +struct virtio_snd_hdr +{ + rt_le32_t code; +}; + +/* event notification */ +struct virtio_snd_event +{ + /* VIRTIO_SND_EVT_XXX */ + struct virtio_snd_hdr hdr; + /* optional event data */ + rt_le32_t data; +}; + +/* common control request to query an item information */ +struct virtio_snd_query_info +{ + /* VIRTIO_SND_R_XXX_INFO */ + struct virtio_snd_hdr hdr; + /* item start identifier */ + rt_le32_t start_id; + /* item count to query */ + rt_le32_t count; + /* item information size in bytes */ + rt_le32_t size; +}; + +/* common item information header */ +struct virtio_snd_info +{ + /* function group node id (High Definition Audio Specification 7.1.2) */ + rt_le32_t hda_fn_nid; +}; + +struct virtio_snd_jack_hdr +{ + /* VIRTIO_SND_R_JACK_XXX */ + struct virtio_snd_hdr hdr; + /* 0 ... virtio_snd_config::jacks - 1 */ + rt_le32_t jack_id; +}; + +/* supported jack features */ +enum +{ + VIRTIO_SND_JACK_F_REMAP = 0 +}; + +struct virtio_snd_jack_info +{ + /* common header */ + struct virtio_snd_info hdr; + /* supported feature bit map (1 << VIRTIO_SND_JACK_F_XXX) */ + rt_le32_t features; + /* pin configuration (High Definition Audio Specification 7.3.3.31) */ + rt_le32_t hda_reg_defconf; + /* pin capabilities (High Definition Audio Specification 7.3.4.9) */ + rt_le32_t hda_reg_caps; + /* current jack connection status (0: disconnected, 1: connected) */ + rt_uint8_t connected; + + rt_uint8_t padding[7]; +}; + +/* jack remapping control request */ +struct virtio_snd_jack_remap +{ + /* .code = VIRTIO_SND_R_JACK_REMAP */ + struct virtio_snd_jack_hdr hdr; + /* selected association number */ + rt_le32_t association; + /* selected sequence number */ + rt_le32_t sequence; +}; + +struct virtio_snd_pcm_hdr +{ + /* VIRTIO_SND_R_PCM_XXX */ + struct virtio_snd_hdr hdr; + /* 0 ... virtio_snd_config::streams - 1 */ + rt_le32_t stream_id; +}; + +/* supported PCM stream features */ +enum +{ + VIRTIO_SND_PCM_F_SHMEM_HOST = 0, + VIRTIO_SND_PCM_F_SHMEM_GUEST, + VIRTIO_SND_PCM_F_MSG_POLLING, + VIRTIO_SND_PCM_F_EVT_SHMEM_PERIODS, + VIRTIO_SND_PCM_F_EVT_XRUNS +}; + +/* 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 */ +}; + +/* supported PCM frame rates */ +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 +}; + +struct virtio_snd_pcm_info +{ + /* common header */ + struct virtio_snd_info hdr; + /* supported feature bit map (1 << VIRTIO_SND_PCM_F_XXX) */ + rt_le32_t features; + /* supported sample format bit map (1 << VIRTIO_SND_PCM_FMT_XXX) */ + rt_le64_t formats; + /* supported frame rate bit map (1 << VIRTIO_SND_PCM_RATE_XXX) */ + rt_le64_t rates; + /* dataflow direction (VIRTIO_SND_D_XXX) */ + rt_uint8_t direction; + /* minimum # of supported channels */ + rt_uint8_t channels_min; + /* maximum # of supported channels */ + rt_uint8_t channels_max; + + rt_uint8_t padding[5]; +}; + +/* set PCM stream format */ +struct virtio_snd_pcm_set_params +{ + /* .code = VIRTIO_SND_R_PCM_SET_PARAMS */ + struct virtio_snd_pcm_hdr hdr; + /* size of the hardware buffer */ + rt_le32_t buffer_bytes; + /* size of the hardware period */ + rt_le32_t period_bytes; + /* selected feature bit map (1 << VIRTIO_SND_PCM_F_XXX) */ + rt_le32_t features; + /* selected # of channels */ + rt_uint8_t channels; + /* selected sample format (VIRTIO_SND_PCM_FMT_XXX) */ + rt_uint8_t format; + /* selected frame rate (VIRTIO_SND_PCM_RATE_XXX) */ + rt_uint8_t rate; + + rt_uint8_t padding; +}; + +/* I/O request header */ +struct virtio_snd_pcm_xfer +{ + /* 0 ... virtio_snd_config::streams - 1 */ + rt_le32_t stream_id; +}; + +/* I/O request status */ +struct virtio_snd_pcm_status +{ + /* VIRTIO_SND_S_XXX */ + rt_le32_t status; + /* current device latency */ + rt_le32_t latency_bytes; +}; + +struct virtio_snd_chmap_hdr +{ + /* VIRTIO_SND_R_CHMAP_XXX */ + struct virtio_snd_hdr hdr; + /* 0 ... virtio_snd_config::chmaps - 1 */ + rt_le32_t chmap_id; +}; + +/* 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 */ +}; + +/* maximum possible number of channels */ +#define VIRTIO_SND_CHMAP_MAX_SIZE 18 + +struct virtio_snd_chmap_info +{ + /* common header */ + struct virtio_snd_info hdr; + /* dataflow direction (VIRTIO_SND_D_XXX) */ + rt_uint8_t direction; + /* # of valid channel position values */ + rt_uint8_t channels; + /* channel position values (VIRTIO_SND_CHMAP_XXX) */ + rt_uint8_t positions[VIRTIO_SND_CHMAP_MAX_SIZE]; +}; + +enum +{ + VIRTIO_SND_CTL_ROLE_UNDEFINED = 0, + VIRTIO_SND_CTL_ROLE_VOLUME, + VIRTIO_SND_CTL_ROLE_MUTE, + VIRTIO_SND_CTL_ROLE_GAIN +}; + +enum +{ + VIRTIO_SND_CTL_TYPE_BOOLEAN = 0, + VIRTIO_SND_CTL_TYPE_INTEGER, + VIRTIO_SND_CTL_TYPE_INTEGER64, + VIRTIO_SND_CTL_TYPE_ENUMERATED, + VIRTIO_SND_CTL_TYPE_BYTES, + VIRTIO_SND_CTL_TYPE_IEC958 +}; + +enum +{ + VIRTIO_SND_CTL_ACCESS_READ = 0, + VIRTIO_SND_CTL_ACCESS_WRITE, + VIRTIO_SND_CTL_ACCESS_VOLATILE, + VIRTIO_SND_CTL_ACCESS_INACTIVE, + VIRTIO_SND_CTL_ACCESS_TLV_READ, + VIRTIO_SND_CTL_ACCESS_TLV_WRITE, + VIRTIO_SND_CTL_ACCESS_TLV_COMMAND +}; + +struct virtio_snd_ctl_info +{ + struct virtio_snd_info hdr; + rt_le32_t role; + rt_le32_t type; + rt_le32_t access; /* 1 << VIRTIO_SND_CTL_ACCESS_XXX */ + rt_le32_t count; + rt_le32_t index; + rt_uint8_t name[44]; + union + { + struct + { + rt_le32_t min; + rt_le32_t max; + rt_le32_t step; + } integer; + struct + { + rt_le64_t min; + rt_le64_t max; + rt_le64_t step; + } integer64; + struct + { + rt_le32_t items; + } enumerated; + } value; +}; + +struct virtio_snd_ctl_enum_item +{ + rt_uint8_t item[64]; +}; + +struct virtio_snd_ctl_iec958 +{ + rt_uint8_t status[24]; /* AES/IEC958 channel status bits */ + rt_uint8_t subcode[147]; /* AES/IEC958 subcode bits */ + rt_uint8_t pad; /* nothing */ + rt_uint8_t dig_subframe[4]; /* AES/IEC958 subframe bits */ +}; + +struct virtio_snd_ctl_value +{ + union + { + rt_le32_t integer[128]; + rt_le64_t integer64[64]; + rt_le32_t enumerated[128]; + rt_uint8_t bytes[512]; + struct virtio_snd_ctl_iec958 iec958; + } value; +}; + +struct virtio_snd_ctl_tlv +{ + rt_le32_t type; + rt_le32_t length; + rt_le32_t value[]; +}; + +enum +{ + VIRTIO_SND_CTL_EVT_MASK_VALUE = 0, + VIRTIO_SND_CTL_EVT_MASK_INFO, + VIRTIO_SND_CTL_EVT_MASK_TLV +}; + +struct virtio_snd_ctl_event +{ + struct virtio_snd_hdr hdr; /* .code = VIRTIO_SND_EVT_CTL_NOTIFY */ + rt_le16_t control_id; + rt_le16_t mask; /* 1 << VIRTIO_SND_CTL_EVT_MASK_XXX */ +}; + +#endif /* __VIRTIO_SOUND_H__ */