|
|
@@ -0,0 +1,412 @@
|
|
|
+/*
|
|
|
+ * 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 <rtthread.h>
|
|
|
+#include <rtdevice.h>
|
|
|
+
|
|
|
+#define DBG_TAG "rpmsg.char"
|
|
|
+#define DBG_LVL DBG_INFO
|
|
|
+#include <rtdbg.h>
|
|
|
+
|
|
|
+struct rpmsg_char_ctrl;
|
|
|
+
|
|
|
+struct rpmsg_char
|
|
|
+{
|
|
|
+ struct rt_device parent;
|
|
|
+ rt_list_t list;
|
|
|
+
|
|
|
+ struct rpmsg_char_ctrl *rchc;
|
|
|
+ struct rt_rpmsg_endpoint *ept;
|
|
|
+
|
|
|
+ rt_bool_t is_overwrite;
|
|
|
+ struct rt_ringbuffer msg_ring;
|
|
|
+ rt_uint8_t msg_pool[RT_RPMSG_CHAR_MSG_SIZE_MAX * RT_RPMSG_CHAR_MSG_MAX];
|
|
|
+};
|
|
|
+
|
|
|
+struct rpmsg_char_ctrl
|
|
|
+{
|
|
|
+ struct rt_device parent;
|
|
|
+
|
|
|
+ struct rt_rpmsg_device *rdev;
|
|
|
+
|
|
|
+ rt_list_t ept_nodes;
|
|
|
+ rt_list_t del_ept_nodes;
|
|
|
+ struct rt_spinlock lock;
|
|
|
+ struct rt_work del_ept_work;
|
|
|
+};
|
|
|
+
|
|
|
+#define raw_to_rpmsg_char(raw) rt_container_of(raw, struct rpmsg_char, parent)
|
|
|
+#define raw_to_rpmsg_char_ctrl(raw) rt_container_of(raw, struct rpmsg_char_ctrl, parent)
|
|
|
+
|
|
|
+static struct rt_dm_ida rpmsg_ept_ida = RT_DM_IDA_INIT(RPMSG_EPT);
|
|
|
+static struct rt_dm_ida rpmsg_char_ida = RT_DM_IDA_INIT(RPMSG_CHAR);
|
|
|
+
|
|
|
+static rt_err_t rpmsg_char_open(rt_device_t dev, rt_uint16_t oflag)
|
|
|
+{
|
|
|
+ rt_ubase_t level;
|
|
|
+ rt_err_t err = RT_EOK;
|
|
|
+ struct rpmsg_char_ctrl *rchc;
|
|
|
+ struct rpmsg_char *this_rch = raw_to_rpmsg_char(dev), *rch, *rch_next;
|
|
|
+
|
|
|
+ rchc = this_rch->rchc;
|
|
|
+
|
|
|
+ level = rt_spin_lock_irqsave(&rchc->lock);
|
|
|
+
|
|
|
+ rt_list_for_each_entry_safe(rch, rch_next, &rchc->del_ept_nodes, list)
|
|
|
+ {
|
|
|
+ if (rch == this_rch)
|
|
|
+ {
|
|
|
+ /* It's been cleaned. Don't open it. */
|
|
|
+ err = -RT_EIO;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ rt_spin_unlock_irqrestore(&rchc->lock, level);
|
|
|
+
|
|
|
+ return err;
|
|
|
+}
|
|
|
+
|
|
|
+static rt_ssize_t rpmsg_char_read(rt_device_t dev,
|
|
|
+ rt_off_t pos, void *buffer, rt_size_t size)
|
|
|
+{
|
|
|
+ struct rpmsg_char *rch = raw_to_rpmsg_char(dev);
|
|
|
+
|
|
|
+ return rt_ringbuffer_get(&rch->msg_ring, buffer, size);
|
|
|
+}
|
|
|
+
|
|
|
+static rt_ssize_t rpmsg_char_write(rt_device_t dev,
|
|
|
+ rt_off_t pos, const void *buffer, rt_size_t size)
|
|
|
+{
|
|
|
+ struct rpmsg_char *rch = raw_to_rpmsg_char(dev);
|
|
|
+
|
|
|
+ return rt_rpmsg_send(rch->ept, buffer, size) ? : size;
|
|
|
+}
|
|
|
+
|
|
|
+static rt_err_t rpmsg_char_control(rt_device_t dev, int cmd, void *args)
|
|
|
+{
|
|
|
+ struct rpmsg_char *rch = raw_to_rpmsg_char(dev);
|
|
|
+
|
|
|
+ if (cmd == RT_DEVICE_CTRL_RPMSG_DESTROY_EPT)
|
|
|
+ {
|
|
|
+ if (dev->ref_count == 1)
|
|
|
+ {
|
|
|
+ rt_ubase_t level;
|
|
|
+
|
|
|
+ level = rt_spin_lock_irqsave(&rch->rchc->lock);
|
|
|
+ rt_list_remove(&rch->list);
|
|
|
+ rt_list_insert_before(&rch->rchc->del_ept_nodes, &rch->list);
|
|
|
+ rt_spin_unlock_irqrestore(&rch->rchc->lock, level);
|
|
|
+
|
|
|
+ rt_work_submit(&rch->rchc->del_ept_work,
|
|
|
+ RT_SCHED_PRIV(rt_thread_self()).remaining_tick);
|
|
|
+ }
|
|
|
+
|
|
|
+ return RT_EOK;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (cmd == RT_DEVICE_CTRL_RPMSG_DATA_OVERWRITE)
|
|
|
+ {
|
|
|
+ rch->is_overwrite = !!args;
|
|
|
+
|
|
|
+ return RT_EOK;
|
|
|
+ }
|
|
|
+
|
|
|
+ return -RT_EINVAL;
|
|
|
+}
|
|
|
+
|
|
|
+#ifdef RT_USING_DEVICE_OPS
|
|
|
+const static struct rt_device_ops rpmsg_char_ops =
|
|
|
+{
|
|
|
+ .open = rpmsg_char_open,
|
|
|
+ .read = rpmsg_char_read,
|
|
|
+ .write = rpmsg_char_write,
|
|
|
+ .control = rpmsg_char_control,
|
|
|
+};
|
|
|
+#endif
|
|
|
+
|
|
|
+static rt_err_t rpmsg_char_rx_callback(struct rt_rpmsg_device *rdev,
|
|
|
+ rt_uint32_t src, void *data, rt_size_t len)
|
|
|
+{
|
|
|
+ rt_size_t res_size;
|
|
|
+ struct rpmsg_char *rch;
|
|
|
+ struct rt_rpmsg_endpoint *ept;
|
|
|
+ struct rt_rpmsg_endpoint_info info;
|
|
|
+
|
|
|
+ RT_ASSERT(len <= RT_RPMSG_CHAR_MSG_SIZE_MAX);
|
|
|
+
|
|
|
+ info.src = RT_RPMSG_ADDR_ANY;
|
|
|
+ info.dst = src;
|
|
|
+ info.name[0] = '\0';
|
|
|
+ ept = rt_rpmsg_find_endpoint(rdev, &info);
|
|
|
+
|
|
|
+ if (ept)
|
|
|
+ {
|
|
|
+ rch = ept->priv;
|
|
|
+
|
|
|
+ if (rch->is_overwrite)
|
|
|
+ {
|
|
|
+ res_size = rt_ringbuffer_put_force(&rch->msg_ring, data, len);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ res_size = rt_ringbuffer_put(&rch->msg_ring, data, len);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ return -RT_EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ return res_size ? RT_EOK : -RT_ENOMEM;
|
|
|
+}
|
|
|
+
|
|
|
+static void rpmsg_char_ctrl_del_ept_work(struct rt_work *work, void *work_data)
|
|
|
+{
|
|
|
+ rt_ubase_t level;
|
|
|
+ rt_size_t clean_count = 0;
|
|
|
+ struct rpmsg_char *rch, *rch_next;
|
|
|
+ struct rpmsg_char_ctrl *rchc = work_data;
|
|
|
+
|
|
|
+ level = rt_spin_lock_irqsave(&rchc->lock);
|
|
|
+
|
|
|
+ rt_list_for_each_entry_safe(rch, rch_next, &rchc->del_ept_nodes, list)
|
|
|
+ {
|
|
|
+ if (rch->parent.open_flag == RT_DEVICE_OFLAG_CLOSE)
|
|
|
+ {
|
|
|
+ rt_list_remove(&rch->list);
|
|
|
+
|
|
|
+ rt_spin_unlock_irqrestore(&rchc->lock, level);
|
|
|
+
|
|
|
+ rt_rpmsg_destroy_endpoint(rchc->rdev, rch->ept);
|
|
|
+
|
|
|
+ rt_dm_ida_free(&rpmsg_ept_ida, rch->parent.device_id);
|
|
|
+
|
|
|
+ rt_device_unregister(&rch->parent);
|
|
|
+ rt_free(rch);
|
|
|
+
|
|
|
+ level = rt_spin_lock_irqsave(&rchc->lock);
|
|
|
+
|
|
|
+ ++clean_count;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ rt_spin_unlock_irqrestore(&rchc->lock, level);
|
|
|
+
|
|
|
+ if (!clean_count)
|
|
|
+ {
|
|
|
+ /* Try again */
|
|
|
+ rt_work_submit(&rchc->del_ept_work, RT_TICK_PER_SECOND);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static rt_err_t rpmsg_char_ctrl_control(rt_device_t dev, int cmd, void *args)
|
|
|
+{
|
|
|
+ struct rpmsg_char_ctrl *rchc = raw_to_rpmsg_char_ctrl(dev);
|
|
|
+
|
|
|
+ if (cmd == RT_DEVICE_CTRL_RPMSG_CREATE_EPT && args)
|
|
|
+ {
|
|
|
+ int device_id;
|
|
|
+ rt_ubase_t level;
|
|
|
+ struct rpmsg_char *rch;
|
|
|
+ struct rt_rpmsg_endpoint *ept;
|
|
|
+ struct rt_rpmsg_endpoint_info *info = args;
|
|
|
+
|
|
|
+ if (!info->name[0])
|
|
|
+ {
|
|
|
+ rt_strncpy(info->name, "rpmsg-raw", RT_RPMSG_NAME_SIZE);
|
|
|
+ }
|
|
|
+
|
|
|
+ ept = rt_rpmsg_create_endpoint(rchc->rdev, info, &rpmsg_char_rx_callback);
|
|
|
+
|
|
|
+ if (rt_is_err(ept))
|
|
|
+ {
|
|
|
+ return rt_ptr_err(ept);
|
|
|
+ }
|
|
|
+
|
|
|
+ rch = rt_calloc(1, sizeof(*rch));
|
|
|
+
|
|
|
+ if (!rch)
|
|
|
+ {
|
|
|
+ rt_rpmsg_destroy_endpoint(rchc->rdev, ept);
|
|
|
+ return -RT_ENOMEM;
|
|
|
+ }
|
|
|
+
|
|
|
+ if ((device_id = rt_dm_ida_alloc(&rpmsg_ept_ida)) < 0)
|
|
|
+ {
|
|
|
+ rt_free(rch);
|
|
|
+ rt_rpmsg_destroy_endpoint(rchc->rdev, ept);
|
|
|
+ return -RT_EFULL;
|
|
|
+ }
|
|
|
+
|
|
|
+ ept->priv = rch;
|
|
|
+ rch->ept = ept;
|
|
|
+ rch->rchc = rchc;
|
|
|
+
|
|
|
+ rch->parent.type = RT_Device_Class_Char;
|
|
|
+ #ifdef RT_USING_DEVICE_OPS
|
|
|
+ rch->parent.ops = &rpmsg_char_ops;
|
|
|
+ #else
|
|
|
+ rch->parent.read = rpmsg_char_read;
|
|
|
+ rch->parent.write = rpmsg_char_write;
|
|
|
+ rch->parent.control = rpmsg_char_control;
|
|
|
+ #endif
|
|
|
+ rch->parent.master_id = rpmsg_ept_ida.master_id;
|
|
|
+ rch->parent.device_id = device_id;
|
|
|
+
|
|
|
+ rt_ringbuffer_init(&rch->msg_ring, rch->msg_pool, sizeof(rch->msg_pool));
|
|
|
+ rt_dm_dev_set_name(&rch->parent, "rpmsg_%ux%u", ept->info.src, ept->info.dst);
|
|
|
+
|
|
|
+ rt_list_init(&rch->list);
|
|
|
+
|
|
|
+ level = rt_spin_lock_irqsave(&rchc->lock);
|
|
|
+ rt_list_insert_before(&rchc->ept_nodes, &rch->list);
|
|
|
+ rt_spin_unlock_irqrestore(&rchc->lock, level);
|
|
|
+
|
|
|
+ rt_device_register(&rch->parent, rt_dm_dev_get_name(&rch->parent),
|
|
|
+ RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_REMOVABLE);
|
|
|
+
|
|
|
+ return RT_EOK;
|
|
|
+ }
|
|
|
+
|
|
|
+ return -RT_EINVAL;
|
|
|
+}
|
|
|
+
|
|
|
+#ifdef RT_USING_DEVICE_OPS
|
|
|
+const static struct rt_device_ops rpmsg_char_ctrl_ops =
|
|
|
+{
|
|
|
+ .control = rpmsg_char_ctrl_control,
|
|
|
+};
|
|
|
+#endif
|
|
|
+
|
|
|
+static rt_err_t rpmsg_char_probe(struct rt_rpmsg_device *rdev)
|
|
|
+{
|
|
|
+ rt_err_t err;
|
|
|
+ int device_id;
|
|
|
+ struct rpmsg_char_ctrl *rchc = rt_calloc(1, sizeof(*rchc));
|
|
|
+
|
|
|
+ if (!rchc)
|
|
|
+ {
|
|
|
+ return -RT_ENOMEM;
|
|
|
+ }
|
|
|
+
|
|
|
+ if ((device_id = rt_dm_ida_alloc(&rpmsg_char_ida)) < 0)
|
|
|
+ {
|
|
|
+ err = -RT_EFULL;
|
|
|
+ goto _free_dev;
|
|
|
+ }
|
|
|
+
|
|
|
+ rchc->rdev = rdev;
|
|
|
+ rdev->parent.user_data = rchc;
|
|
|
+
|
|
|
+ rt_list_init(&rchc->ept_nodes);
|
|
|
+ rt_list_init(&rchc->del_ept_nodes);
|
|
|
+ rt_spin_lock_init(&rchc->lock);
|
|
|
+ rt_work_init(&rchc->del_ept_work, rpmsg_char_ctrl_del_ept_work, rchc);
|
|
|
+
|
|
|
+ rt_dm_dev_set_name(&rchc->parent, "rpmsg_char%u", device_id);
|
|
|
+
|
|
|
+ rchc->parent.type = RT_Device_Class_Char;
|
|
|
+#ifdef RT_USING_DEVICE_OPS
|
|
|
+ rchc->parent.ops = &rpmsg_char_ctrl_ops;
|
|
|
+#else
|
|
|
+ rchc->parent.control = rpmsg_char_ctrl_control;
|
|
|
+#endif
|
|
|
+ rchc->parent.master_id = rpmsg_char_ida.master_id;
|
|
|
+ rchc->parent.device_id = device_id;
|
|
|
+
|
|
|
+ if ((err = rt_device_register(&rchc->parent, rt_dm_dev_get_name(&rchc->parent),
|
|
|
+ RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_REMOVABLE)))
|
|
|
+ {
|
|
|
+ goto _fail;
|
|
|
+ }
|
|
|
+
|
|
|
+ return RT_EOK;
|
|
|
+
|
|
|
+_fail:
|
|
|
+ rt_dm_ida_free(&rpmsg_char_ida, device_id);
|
|
|
+_free_dev:
|
|
|
+ rt_free(rchc);
|
|
|
+
|
|
|
+ return err;
|
|
|
+}
|
|
|
+
|
|
|
+static rt_err_t rpmsg_char_remove(struct rt_rpmsg_device *rdev)
|
|
|
+{
|
|
|
+ rt_ubase_t level;
|
|
|
+ struct rpmsg_char *rch, *rch_next;
|
|
|
+ struct rpmsg_char_ctrl *rchc = rdev->parent.user_data;
|
|
|
+
|
|
|
+ rt_work_cancel(&rchc->del_ept_work);
|
|
|
+
|
|
|
+ level = rt_spin_lock_irqsave(&rchc->lock);
|
|
|
+
|
|
|
+ rt_list_for_each_entry_safe(rch, rch_next, &rchc->ept_nodes, list)
|
|
|
+ {
|
|
|
+ rt_list_remove(&rch->list);
|
|
|
+
|
|
|
+ rt_spin_unlock_irqrestore(&rchc->lock, level);
|
|
|
+
|
|
|
+ rt_rpmsg_destroy_endpoint(rchc->rdev, rch->ept);
|
|
|
+
|
|
|
+ rt_dm_ida_free(&rpmsg_ept_ida, rch->parent.device_id);
|
|
|
+
|
|
|
+ rt_device_unregister(&rch->parent);
|
|
|
+ rt_free(rch);
|
|
|
+
|
|
|
+ level = rt_spin_lock_irqsave(&rchc->lock);
|
|
|
+ }
|
|
|
+
|
|
|
+ rt_list_for_each_entry_safe(rch, rch_next, &rchc->del_ept_nodes, list)
|
|
|
+ {
|
|
|
+ rt_list_remove(&rch->list);
|
|
|
+
|
|
|
+ rt_spin_unlock_irqrestore(&rchc->lock, level);
|
|
|
+
|
|
|
+ rt_rpmsg_destroy_endpoint(rchc->rdev, rch->ept);
|
|
|
+
|
|
|
+ rt_dm_ida_free(&rpmsg_ept_ida, rch->parent.device_id);
|
|
|
+
|
|
|
+ rt_device_unregister(&rch->parent);
|
|
|
+ rt_free(rch);
|
|
|
+
|
|
|
+ level = rt_spin_lock_irqsave(&rchc->lock);
|
|
|
+ }
|
|
|
+
|
|
|
+ rt_spin_unlock_irqrestore(&rchc->lock, level);
|
|
|
+
|
|
|
+ rt_dm_ida_free(&rpmsg_char_ida, rchc->parent.device_id);
|
|
|
+
|
|
|
+ rt_device_unregister(&rchc->parent);
|
|
|
+
|
|
|
+ rt_free(rchc);
|
|
|
+
|
|
|
+ return RT_EOK;
|
|
|
+}
|
|
|
+
|
|
|
+static struct rt_rpmsg_device_id rpmsg_char_ids[] =
|
|
|
+{
|
|
|
+ { .name = "rpmsg-raw" },
|
|
|
+ { .name = "rpmsg-char" },
|
|
|
+ { /* sentinel */ }
|
|
|
+};
|
|
|
+
|
|
|
+static struct rt_rpmsg_driver rpmsg_char_driver =
|
|
|
+{
|
|
|
+ .parent.parent =
|
|
|
+ {
|
|
|
+ .name = "rpmsg-char",
|
|
|
+ },
|
|
|
+ .ids = rpmsg_char_ids,
|
|
|
+
|
|
|
+ .probe = rpmsg_char_probe,
|
|
|
+ .remove = rpmsg_char_remove,
|
|
|
+};
|
|
|
+RT_RPMSG_DRIVER_EXPORT(rpmsg_char_driver);
|