mirror of
https://github.com/RT-Thread/rt-thread.git
synced 2026-08-26 21:49:45 +08:00
1. support RPMsg 2. support Sound 3. fixup virtio-blk free Signed-off-by: GuEe-GUI <2991707448@qq.com>
461 lines
11 KiB
C
Executable File
461 lines
11 KiB
C
Executable File
/*
|
|
* 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 <rthw.h>
|
|
#include <rtthread.h>
|
|
#include <rtdevice.h>
|
|
|
|
#define DBG_TAG "virtio.dev.rpmsg"
|
|
#define DBG_LVL DBG_INFO
|
|
#include <rtdbg.h>
|
|
|
|
#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);
|