summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/rpmsg/Kconfig8
-rw-r--r--drivers/rpmsg/Makefile1
-rw-r--r--drivers/rpmsg/qcom_glink_native.c4
-rw-r--r--drivers/rpmsg/qcom_smd.c13
-rw-r--r--drivers/rpmsg/rpmsg_char.c238
-rw-r--r--drivers/rpmsg/rpmsg_char.h46
-rw-r--r--drivers/rpmsg/rpmsg_core.c15
-rw-r--r--drivers/rpmsg/rpmsg_ctrl.c243
-rw-r--r--drivers/rpmsg/rpmsg_internal.h10
-rw-r--r--drivers/rpmsg/virtio_rpmsg_bus.c2
-rw-r--r--include/uapi/linux/rpmsg.h10
11 files changed, 428 insertions, 162 deletions
diff --git a/drivers/rpmsg/Kconfig b/drivers/rpmsg/Kconfig
index 0b4407abdf13..d3795860f5c0 100644
--- a/drivers/rpmsg/Kconfig
+++ b/drivers/rpmsg/Kconfig
@@ -15,6 +15,14 @@ config RPMSG_CHAR
in /dev. They make it possible for user-space programs to send and
receive rpmsg packets.
+config RPMSG_CTRL
+ tristate "RPMSG control interface"
+ depends on RPMSG && ( RPMSG_CHAR || RPMSG_CHAR=n )
+ help
+ Say Y here to enable the support of the /dev/rpmsg_ctrlX API. This API
+ allows user-space programs to create endpoints with specific service name,
+ source and destination addresses.
+
config RPMSG_NS
tristate "RPMSG name service announcement"
depends on RPMSG
diff --git a/drivers/rpmsg/Makefile b/drivers/rpmsg/Makefile
index 8d452656f0ee..58e3b382e316 100644
--- a/drivers/rpmsg/Makefile
+++ b/drivers/rpmsg/Makefile
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_RPMSG) += rpmsg_core.o
obj-$(CONFIG_RPMSG_CHAR) += rpmsg_char.o
+obj-$(CONFIG_RPMSG_CTRL) += rpmsg_ctrl.o
obj-$(CONFIG_RPMSG_NS) += rpmsg_ns.o
obj-$(CONFIG_RPMSG_MTK_SCP) += mtk_rpmsg.o
qcom_glink-objs := qcom_glink_native.o qcom_glink_ssr.o
diff --git a/drivers/rpmsg/qcom_glink_native.c b/drivers/rpmsg/qcom_glink_native.c
index 1030cfa80e04..07586514991f 100644
--- a/drivers/rpmsg/qcom_glink_native.c
+++ b/drivers/rpmsg/qcom_glink_native.c
@@ -792,7 +792,7 @@ static int qcom_glink_rx_defer(struct qcom_glink *glink, size_t extra)
return -ENXIO;
}
- dcmd = kzalloc(sizeof(*dcmd) + extra, GFP_ATOMIC);
+ dcmd = kzalloc(struct_size(dcmd, data, extra), GFP_ATOMIC);
if (!dcmd)
return -ENOMEM;
@@ -1715,7 +1715,7 @@ static int qcom_glink_create_chrdev(struct qcom_glink *glink)
rpdev->dev.parent = glink->dev;
rpdev->dev.release = qcom_glink_device_release;
- return rpmsg_chrdev_register_device(rpdev);
+ return rpmsg_ctrldev_register_device(rpdev);
}
struct qcom_glink *qcom_glink_native_probe(struct device *dev,
diff --git a/drivers/rpmsg/qcom_smd.c b/drivers/rpmsg/qcom_smd.c
index 540e027f08c4..764c980507be 100644
--- a/drivers/rpmsg/qcom_smd.c
+++ b/drivers/rpmsg/qcom_smd.c
@@ -1113,7 +1113,7 @@ static int qcom_smd_create_chrdev(struct qcom_smd_edge *edge)
qsdev->rpdev.dev.parent = &edge->dev;
qsdev->rpdev.dev.release = qcom_smd_release_device;
- return rpmsg_chrdev_register_device(&qsdev->rpdev);
+ return rpmsg_ctrldev_register_device(&qsdev->rpdev);
}
/*
@@ -1288,9 +1288,14 @@ static void qcom_channel_state_worker(struct work_struct *work)
if (channel->state != SMD_CHANNEL_CLOSED)
continue;
+ /*
+ * Always open rpm_requests, even when already opened which is
+ * required on some SoCs like msm8953.
+ */
remote_state = GET_RX_CHANNEL_INFO(channel, state);
if (remote_state != SMD_CHANNEL_OPENING &&
- remote_state != SMD_CHANNEL_OPENED)
+ remote_state != SMD_CHANNEL_OPENED &&
+ strcmp(channel->name, "rpm_requests"))
continue;
if (channel->registered)
@@ -1298,9 +1303,7 @@ static void qcom_channel_state_worker(struct work_struct *work)
spin_unlock_irqrestore(&edge->channels_lock, flags);
qcom_smd_create_device(channel);
- channel->registered = true;
spin_lock_irqsave(&edge->channels_lock, flags);
-
channel->registered = true;
}
@@ -1605,7 +1608,7 @@ static int __init qcom_smd_init(void)
{
return platform_driver_register(&qcom_smd_driver);
}
-subsys_initcall(qcom_smd_init);
+arch_initcall(qcom_smd_init);
static void __exit qcom_smd_exit(void)
{
diff --git a/drivers/rpmsg/rpmsg_char.c b/drivers/rpmsg/rpmsg_char.c
index 5663cf799c95..b6183d4f62a2 100644
--- a/drivers/rpmsg/rpmsg_char.c
+++ b/drivers/rpmsg/rpmsg_char.c
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
/*
+ * Copyright (C) 2022, STMicroelectronics
* Copyright (c) 2016, Linaro Ltd.
* Copyright (c) 2012, Michal Simek <monstr@monstr.eu>
* Copyright (c) 2012, PetaLogix
@@ -25,33 +26,19 @@
#include <linux/uaccess.h>
#include <uapi/linux/rpmsg.h>
+#include "rpmsg_char.h"
+#include "rpmsg_internal.h"
+
#define RPMSG_DEV_MAX (MINORMASK + 1)
static dev_t rpmsg_major;
-static struct class *rpmsg_class;
-static DEFINE_IDA(rpmsg_ctrl_ida);
static DEFINE_IDA(rpmsg_ept_ida);
static DEFINE_IDA(rpmsg_minor_ida);
#define dev_to_eptdev(dev) container_of(dev, struct rpmsg_eptdev, dev)
#define cdev_to_eptdev(i_cdev) container_of(i_cdev, struct rpmsg_eptdev, cdev)
-#define dev_to_ctrldev(dev) container_of(dev, struct rpmsg_ctrldev, dev)
-#define cdev_to_ctrldev(i_cdev) container_of(i_cdev, struct rpmsg_ctrldev, cdev)
-
-/**
- * struct rpmsg_ctrldev - control device for instantiating endpoint devices
- * @rpdev: underlaying rpmsg device
- * @cdev: cdev for the ctrl device
- * @dev: device for the ctrl device
- */
-struct rpmsg_ctrldev {
- struct rpmsg_device *rpdev;
- struct cdev cdev;
- struct device dev;
-};
-
/**
* struct rpmsg_eptdev - endpoint device context
* @dev: endpoint device
@@ -63,6 +50,8 @@ struct rpmsg_ctrldev {
* @queue_lock: synchronization of @queue operations
* @queue: incoming message queue
* @readq: wait object for incoming queue
+ * @default_ept: set to channel default endpoint if the default endpoint should be re-used
+ * on device open to prevent endpoint address update.
*/
struct rpmsg_eptdev {
struct device dev;
@@ -73,13 +62,15 @@ struct rpmsg_eptdev {
struct mutex ept_lock;
struct rpmsg_endpoint *ept;
+ struct rpmsg_endpoint *default_ept;
spinlock_t queue_lock;
struct sk_buff_head queue;
wait_queue_head_t readq;
+
};
-static int rpmsg_eptdev_destroy(struct device *dev, void *data)
+int rpmsg_chrdev_eptdev_destroy(struct device *dev, void *data)
{
struct rpmsg_eptdev *eptdev = dev_to_eptdev(dev);
@@ -98,6 +89,7 @@ static int rpmsg_eptdev_destroy(struct device *dev, void *data)
return 0;
}
+EXPORT_SYMBOL(rpmsg_chrdev_eptdev_destroy);
static int rpmsg_ept_cb(struct rpmsg_device *rpdev, void *buf, int len,
void *priv, u32 addr)
@@ -133,7 +125,15 @@ static int rpmsg_eptdev_open(struct inode *inode, struct file *filp)
get_device(dev);
- ept = rpmsg_create_ept(rpdev, rpmsg_ept_cb, eptdev, eptdev->chinfo);
+ /*
+ * If the default_ept is set, the rpmsg device default endpoint is used.
+ * Else a new endpoint is created on open that will be destroyed on release.
+ */
+ if (eptdev->default_ept)
+ ept = eptdev->default_ept;
+ else
+ ept = rpmsg_create_ept(rpdev, rpmsg_ept_cb, eptdev, eptdev->chinfo);
+
if (!ept) {
dev_err(dev, "failed to open %s\n", eptdev->chinfo.name);
put_device(dev);
@@ -154,7 +154,8 @@ static int rpmsg_eptdev_release(struct inode *inode, struct file *filp)
/* Close the endpoint, if it's not already destroyed by the parent */
mutex_lock(&eptdev->ept_lock);
if (eptdev->ept) {
- rpmsg_destroy_ept(eptdev->ept);
+ if (!eptdev->default_ept)
+ rpmsg_destroy_ept(eptdev->ept);
eptdev->ept = NULL;
}
mutex_unlock(&eptdev->ept_lock);
@@ -242,10 +243,13 @@ static ssize_t rpmsg_eptdev_write_iter(struct kiocb *iocb,
goto unlock_eptdev;
}
- if (filp->f_flags & O_NONBLOCK)
+ if (filp->f_flags & O_NONBLOCK) {
ret = rpmsg_trysendto(eptdev->ept, kbuf, len, eptdev->chinfo.dst);
- else
+ if (ret == -ENOMEM)
+ ret = -EAGAIN;
+ } else {
ret = rpmsg_sendto(eptdev->ept, kbuf, len, eptdev->chinfo.dst);
+ }
unlock_eptdev:
mutex_unlock(&eptdev->ept_lock);
@@ -281,7 +285,11 @@ static long rpmsg_eptdev_ioctl(struct file *fp, unsigned int cmd,
if (cmd != RPMSG_DESTROY_EPT_IOCTL)
return -EINVAL;
- return rpmsg_eptdev_destroy(&eptdev->dev, NULL);
+ /* Don't allow to destroy a default endpoint. */
+ if (eptdev->default_ept)
+ return -EINVAL;
+
+ return rpmsg_chrdev_eptdev_destroy(&eptdev->dev, NULL);
}
static const struct file_operations rpmsg_eptdev_fops = {
@@ -339,21 +347,18 @@ static void rpmsg_eptdev_release_device(struct device *dev)
kfree(eptdev);
}
-static int rpmsg_eptdev_create(struct rpmsg_ctrldev *ctrldev,
- struct rpmsg_channel_info chinfo)
+static struct rpmsg_eptdev *rpmsg_chrdev_eptdev_alloc(struct rpmsg_device *rpdev,
+ struct device *parent)
{
- struct rpmsg_device *rpdev = ctrldev->rpdev;
struct rpmsg_eptdev *eptdev;
struct device *dev;
- int ret;
eptdev = kzalloc(sizeof(*eptdev), GFP_KERNEL);
if (!eptdev)
- return -ENOMEM;
+ return ERR_PTR(-ENOMEM);
dev = &eptdev->dev;
eptdev->rpdev = rpdev;
- eptdev->chinfo = chinfo;
mutex_init(&eptdev->ept_lock);
spin_lock_init(&eptdev->queue_lock);
@@ -362,13 +367,23 @@ static int rpmsg_eptdev_create(struct rpmsg_ctrldev *ctrldev,
device_initialize(dev);
dev->class = rpmsg_class;
- dev->parent = &ctrldev->dev;
+ dev->parent = parent;
dev->groups = rpmsg_eptdev_groups;
dev_set_drvdata(dev, eptdev);
cdev_init(&eptdev->cdev, &rpmsg_eptdev_fops);
eptdev->cdev.owner = THIS_MODULE;
+ return eptdev;
+}
+
+static int rpmsg_chrdev_eptdev_add(struct rpmsg_eptdev *eptdev, struct rpmsg_channel_info chinfo)
+{
+ struct device *dev = &eptdev->dev;
+ int ret;
+
+ eptdev->chinfo = chinfo;
+
ret = ida_simple_get(&rpmsg_minor_ida, 0, RPMSG_DEV_MAX, GFP_KERNEL);
if (ret < 0)
goto free_eptdev;
@@ -400,163 +415,91 @@ free_eptdev:
return ret;
}
-static int rpmsg_ctrldev_open(struct inode *inode, struct file *filp)
-{
- struct rpmsg_ctrldev *ctrldev = cdev_to_ctrldev(inode->i_cdev);
-
- get_device(&ctrldev->dev);
- filp->private_data = ctrldev;
-
- return 0;
-}
-
-static int rpmsg_ctrldev_release(struct inode *inode, struct file *filp)
-{
- struct rpmsg_ctrldev *ctrldev = cdev_to_ctrldev(inode->i_cdev);
-
- put_device(&ctrldev->dev);
-
- return 0;
-}
-
-static long rpmsg_ctrldev_ioctl(struct file *fp, unsigned int cmd,
- unsigned long arg)
+int rpmsg_chrdev_eptdev_create(struct rpmsg_device *rpdev, struct device *parent,
+ struct rpmsg_channel_info chinfo)
{
- struct rpmsg_ctrldev *ctrldev = fp->private_data;
- void __user *argp = (void __user *)arg;
- struct rpmsg_endpoint_info eptinfo;
- struct rpmsg_channel_info chinfo;
-
- if (cmd != RPMSG_CREATE_EPT_IOCTL)
- return -EINVAL;
-
- if (copy_from_user(&eptinfo, argp, sizeof(eptinfo)))
- return -EFAULT;
-
- memcpy(chinfo.name, eptinfo.name, RPMSG_NAME_SIZE);
- chinfo.name[RPMSG_NAME_SIZE-1] = '\0';
- chinfo.src = eptinfo.src;
- chinfo.dst = eptinfo.dst;
-
- return rpmsg_eptdev_create(ctrldev, chinfo);
-};
+ struct rpmsg_eptdev *eptdev;
+ int ret;
-static const struct file_operations rpmsg_ctrldev_fops = {
- .owner = THIS_MODULE,
- .open = rpmsg_ctrldev_open,
- .release = rpmsg_ctrldev_release,
- .unlocked_ioctl = rpmsg_ctrldev_ioctl,
- .compat_ioctl = compat_ptr_ioctl,
-};
+ eptdev = rpmsg_chrdev_eptdev_alloc(rpdev, parent);
+ if (IS_ERR(eptdev))
+ return PTR_ERR(eptdev);
-static void rpmsg_ctrldev_release_device(struct device *dev)
-{
- struct rpmsg_ctrldev *ctrldev = dev_to_ctrldev(dev);
+ ret = rpmsg_chrdev_eptdev_add(eptdev, chinfo);
- ida_simple_remove(&rpmsg_ctrl_ida, dev->id);
- ida_simple_remove(&rpmsg_minor_ida, MINOR(dev->devt));
- kfree(ctrldev);
+ return ret;
}
+EXPORT_SYMBOL(rpmsg_chrdev_eptdev_create);
static int rpmsg_chrdev_probe(struct rpmsg_device *rpdev)
{
- struct rpmsg_ctrldev *ctrldev;
- struct device *dev;
- int ret;
-
- ctrldev = kzalloc(sizeof(*ctrldev), GFP_KERNEL);
- if (!ctrldev)
- return -ENOMEM;
-
- ctrldev->rpdev = rpdev;
-
- dev = &ctrldev->dev;
- device_initialize(dev);
- dev->parent = &rpdev->dev;
- dev->class = rpmsg_class;
-
- cdev_init(&ctrldev->cdev, &rpmsg_ctrldev_fops);
- ctrldev->cdev.owner = THIS_MODULE;
-
- ret = ida_simple_get(&rpmsg_minor_ida, 0, RPMSG_DEV_MAX, GFP_KERNEL);
- if (ret < 0)
- goto free_ctrldev;
- dev->devt = MKDEV(MAJOR(rpmsg_major), ret);
-
- ret = ida_simple_get(&rpmsg_ctrl_ida, 0, 0, GFP_KERNEL);
- if (ret < 0)
- goto free_minor_ida;
- dev->id = ret;
- dev_set_name(&ctrldev->dev, "rpmsg_ctrl%d", ret);
+ struct rpmsg_channel_info chinfo;
+ struct rpmsg_eptdev *eptdev;
+ struct device *dev = &rpdev->dev;
- ret = cdev_device_add(&ctrldev->cdev, &ctrldev->dev);
- if (ret)
- goto free_ctrl_ida;
+ memcpy(chinfo.name, rpdev->id.name, RPMSG_NAME_SIZE);
+ chinfo.src = rpdev->src;
+ chinfo.dst = rpdev->dst;
- /* We can now rely on the release function for cleanup */
- dev->release = rpmsg_ctrldev_release_device;
+ eptdev = rpmsg_chrdev_eptdev_alloc(rpdev, dev);
+ if (IS_ERR(eptdev))
+ return PTR_ERR(eptdev);
- dev_set_drvdata(&rpdev->dev, ctrldev);
+ /* Set the default_ept to the rpmsg device endpoint */
+ eptdev->default_ept = rpdev->ept;
- return ret;
+ /*
+ * The rpmsg_ept_cb uses *priv parameter to get its rpmsg_eptdev context.
+ * Storedit in default_ept *priv field.
+ */
+ eptdev->default_ept->priv = eptdev;
-free_ctrl_ida:
- ida_simple_remove(&rpmsg_ctrl_ida, dev->id);
-free_minor_ida:
- ida_simple_remove(&rpmsg_minor_ida, MINOR(dev->devt));
-free_ctrldev:
- put_device(dev);
- kfree(ctrldev);
-
- return ret;
+ return rpmsg_chrdev_eptdev_add(eptdev, chinfo);
}
static void rpmsg_chrdev_remove(struct rpmsg_device *rpdev)
{
- struct rpmsg_ctrldev *ctrldev = dev_get_drvdata(&rpdev->dev);
int ret;
- /* Destroy all endpoints */
- ret = device_for_each_child(&ctrldev->dev, NULL, rpmsg_eptdev_destroy);
+ ret = device_for_each_child(&rpdev->dev, NULL, rpmsg_chrdev_eptdev_destroy);
if (ret)
- dev_warn(&rpdev->dev, "failed to nuke endpoints: %d\n", ret);
-
- cdev_device_del(&ctrldev->cdev, &ctrldev->dev);
- put_device(&ctrldev->dev);
+ dev_warn(&rpdev->dev, "failed to destroy endpoints: %d\n", ret);
}
+static struct rpmsg_device_id rpmsg_chrdev_id_table[] = {
+ { .name = "rpmsg-raw" },
+ { },
+};
+
static struct rpmsg_driver rpmsg_chrdev_driver = {
.probe = rpmsg_chrdev_probe,
.remove = rpmsg_chrdev_remove,
- .drv = {
- .name = "rpmsg_chrdev",
- },
+ .callback = rpmsg_ept_cb,
+ .id_table = rpmsg_chrdev_id_table,
+ .drv.name = "rpmsg_chrdev",
};
static int rpmsg_chrdev_init(void)
{
int ret;
- ret = alloc_chrdev_region(&rpmsg_major, 0, RPMSG_DEV_MAX, "rpmsg");
+ ret = alloc_chrdev_region(&rpmsg_major, 0, RPMSG_DEV_MAX, "rpmsg_char");
if (ret < 0) {
pr_err("failed to allocate char dev region\n");
return ret;
}
- rpmsg_class = class_create(THIS_MODULE, "rpmsg");
- if (IS_ERR(rpmsg_class)) {
- pr_err("failed to create rpmsg class\n");
- unregister_chrdev_region(rpmsg_major, RPMSG_DEV_MAX);
- return PTR_ERR(rpmsg_class);
- }
-
ret = register_rpmsg_driver(&rpmsg_chrdev_driver);
if (ret < 0) {
- pr_err("failed to register rpmsg driver\n");
- class_destroy(rpmsg_class);
- unregister_chrdev_region(rpmsg_major, RPMSG_DEV_MAX);
+ pr_err("rpmsg: failed to register rpmsg raw driver\n");
+ goto free_region;
}
+ return 0;
+
+free_region:
+ unregister_chrdev_region(rpmsg_major, RPMSG_DEV_MAX);
+
return ret;
}
postcore_initcall(rpmsg_chrdev_init);
@@ -564,7 +507,6 @@ postcore_initcall(rpmsg_chrdev_init);
static void rpmsg_chrdev_exit(void)
{
unregister_rpmsg_driver(&rpmsg_chrdev_driver);
- class_destroy(rpmsg_class);
unregister_chrdev_region(rpmsg_major, RPMSG_DEV_MAX);
}
module_exit(rpmsg_chrdev_exit);
diff --git a/drivers/rpmsg/rpmsg_char.h b/drivers/rpmsg/rpmsg_char.h
new file mode 100644
index 000000000000..117d9cbc52f0
--- /dev/null
+++ b/drivers/rpmsg/rpmsg_char.h
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2022, STMicroelectronics
+ */
+
+#ifndef __RPMSG_CHRDEV_H__
+#define __RPMSG_CHRDEV_H__
+
+#if IS_ENABLED(CONFIG_RPMSG_CHAR)
+/**
+ * rpmsg_chrdev_eptdev_create() - register char device based on an endpoint
+ * @rpdev: prepared rpdev to be used for creating endpoints
+ * @parent: parent device
+ * @chinfo: associated endpoint channel information.
+ *
+ * This function create a new rpmsg char endpoint device to instantiate a new
+ * endpoint based on chinfo information.
+ */
+int rpmsg_chrdev_eptdev_create(struct rpmsg_device *rpdev, struct device *parent,
+ struct rpmsg_channel_info chinfo);
+
+/**
+ * rpmsg_chrdev_eptdev_destroy() - destroy created char device endpoint.
+ * @data: private data associated to the endpoint device
+ *
+ * This function destroys a rpmsg char endpoint device created by the RPMSG_DESTROY_EPT_IOCTL
+ * control.
+ */
+int rpmsg_chrdev_eptdev_destroy(struct device *dev, void *data);
+
+#else /*IS_ENABLED(CONFIG_RPMSG_CHAR) */
+
+static inline int rpmsg_chrdev_eptdev_create(struct rpmsg_device *rpdev, struct device *parent,
+ struct rpmsg_channel_info chinfo)
+{
+ return -ENXIO;
+}
+
+static inline int rpmsg_chrdev_eptdev_destroy(struct device *dev, void *data)
+{
+ return -ENXIO;
+}
+
+#endif /*IS_ENABLED(CONFIG_RPMSG_CHAR) */
+
+#endif /*__RPMSG_CHRDEV_H__ */
diff --git a/drivers/rpmsg/rpmsg_core.c b/drivers/rpmsg/rpmsg_core.c
index d9e612f4f0f2..79368a957d89 100644
--- a/drivers/rpmsg/rpmsg_core.c
+++ b/drivers/rpmsg/rpmsg_core.c
@@ -20,6 +20,9 @@
#include "rpmsg_internal.h"
+struct class *rpmsg_class;
+EXPORT_SYMBOL(rpmsg_class);
+
/**
* rpmsg_create_channel() - create a new rpmsg channel
* using its name and address info.
@@ -662,10 +665,17 @@ static int __init rpmsg_init(void)
{
int ret;
+ rpmsg_class = class_create(THIS_MODULE, "rpmsg");
+ if (IS_ERR(rpmsg_class)) {
+ pr_err("failed to create rpmsg class\n");
+ return PTR_ERR(rpmsg_class);
+ }
+
ret = bus_register(&rpmsg_bus);
- if (ret)
+ if (ret) {
pr_err("failed to register rpmsg bus: %d\n", ret);
-
+ class_destroy(rpmsg_class);
+ }
return ret;
}
postcore_initcall(rpmsg_init);
@@ -673,6 +683,7 @@ postcore_initcall(rpmsg_init);
static void __exit rpmsg_fini(void)
{
bus_unregister(&rpmsg_bus);
+ class_destroy(rpmsg_class);
}
module_exit(rpmsg_fini);
diff --git a/drivers/rpmsg/rpmsg_ctrl.c b/drivers/rpmsg/rpmsg_ctrl.c
new file mode 100644
index 000000000000..107da70fdbaa
--- /dev/null
+++ b/drivers/rpmsg/rpmsg_ctrl.c
@@ -0,0 +1,243 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2022, STMicroelectronics
+ * Copyright (c) 2016, Linaro Ltd.
+ * Copyright (c) 2012, Michal Simek <monstr@monstr.eu>
+ * Copyright (c) 2012, PetaLogix
+ * Copyright (c) 2011, Texas Instruments, Inc.
+ * Copyright (c) 2011, Google, Inc.
+ *
+ * Based on rpmsg performance statistics driver by Michal Simek, which in turn
+ * was based on TI & Google OMX rpmsg driver.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/cdev.h>
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/idr.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/rpmsg.h>
+#include <linux/skbuff.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <uapi/linux/rpmsg.h>
+
+#include "rpmsg_char.h"
+#include "rpmsg_internal.h"
+
+#define RPMSG_DEV_MAX (MINORMASK + 1)
+
+static dev_t rpmsg_major;
+
+static DEFINE_IDA(rpmsg_ctrl_ida);
+static DEFINE_IDA(rpmsg_minor_ida);
+
+#define dev_to_ctrldev(dev) container_of(dev, struct rpmsg_ctrldev, dev)
+#define cdev_to_ctrldev(i_cdev) container_of(i_cdev, struct rpmsg_ctrldev, cdev)
+
+/**
+ * struct rpmsg_ctrldev - control device for instantiating endpoint devices
+ * @rpdev: underlaying rpmsg device
+ * @cdev: cdev for the ctrl device
+ * @dev: device for the ctrl device
+ * @ctrl_lock: serialize the ioctrls.
+ */
+struct rpmsg_ctrldev {
+ struct rpmsg_device *rpdev;
+ struct cdev cdev;
+ struct device dev;
+ struct mutex ctrl_lock;
+};
+
+static int rpmsg_ctrldev_open(struct inode *inode, struct file *filp)
+{
+ struct rpmsg_ctrldev *ctrldev = cdev_to_ctrldev(inode->i_cdev);
+
+ get_device(&ctrldev->dev);
+ filp->private_data = ctrldev;
+
+ return 0;
+}
+
+static int rpmsg_ctrldev_release(struct inode *inode, struct file *filp)
+{
+ struct rpmsg_ctrldev *ctrldev = cdev_to_ctrldev(inode->i_cdev);
+
+ put_device(&ctrldev->dev);
+
+ return 0;
+}
+
+static long rpmsg_ctrldev_ioctl(struct file *fp, unsigned int cmd,
+ unsigned long arg)
+{
+ struct rpmsg_ctrldev *ctrldev = fp->private_data;
+ void __user *argp = (void __user *)arg;
+ struct rpmsg_endpoint_info eptinfo;
+ struct rpmsg_channel_info chinfo;
+ struct rpmsg_device *rpdev;
+ int ret = 0;
+
+ if (copy_from_user(&eptinfo, argp, sizeof(eptinfo)))
+ return -EFAULT;
+
+ memcpy(chinfo.name, eptinfo.name, RPMSG_NAME_SIZE);
+ chinfo.name[RPMSG_NAME_SIZE - 1] = '\0';
+ chinfo.src = eptinfo.src;
+ chinfo.dst = eptinfo.dst;
+
+ mutex_lock(&ctrldev->ctrl_lock);
+ switch (cmd) {
+ case RPMSG_CREATE_EPT_IOCTL:
+ ret = rpmsg_chrdev_eptdev_create(ctrldev->rpdev, &ctrldev->dev, chinfo);
+ break;
+
+ case RPMSG_CREATE_DEV_IOCTL:
+ rpdev = rpmsg_create_channel(ctrldev->rpdev, &chinfo);
+ if (!rpdev) {
+ dev_err(&ctrldev->dev, "failed to create %s channel\n", chinfo.name);
+ ret = -ENXIO;
+ }
+ break;
+
+ case RPMSG_RELEASE_DEV_IOCTL:
+ ret = rpmsg_release_channel(ctrldev->rpdev, &chinfo);
+ if (ret)
+ dev_err(&ctrldev->dev, "failed to release %s channel (%d)\n",
+ chinfo.name, ret);
+ break;
+
+ default:
+ ret = -EINVAL;
+ }
+ mutex_unlock(&ctrldev->ctrl_lock);
+
+ return ret;
+};
+
+static const struct file_operations rpmsg_ctrldev_fops = {
+ .owner = THIS_MODULE,
+ .open = rpmsg_ctrldev_open,
+ .release = rpmsg_ctrldev_release,
+ .unlocked_ioctl = rpmsg_ctrldev_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+};
+
+static void rpmsg_ctrldev_release_device(struct device *dev)
+{
+ struct rpmsg_ctrldev *ctrldev = dev_to_ctrldev(dev);
+
+ ida_simple_remove(&rpmsg_ctrl_ida, dev->id);
+ ida_simple_remove(&rpmsg_minor_ida, MINOR(dev->devt));
+ kfree(ctrldev);
+}
+
+static int rpmsg_ctrldev_probe(struct rpmsg_device *rpdev)
+{
+ struct rpmsg_ctrldev *ctrldev;
+ struct device *dev;
+ int ret;
+
+ ctrldev = kzalloc(sizeof(*ctrldev), GFP_KERNEL);
+ if (!ctrldev)
+ return -ENOMEM;
+
+ ctrldev->rpdev = rpdev;
+
+ dev = &ctrldev->dev;
+ device_initialize(dev);
+ dev->parent = &rpdev->dev;
+ dev->class = rpmsg_class;
+
+ mutex_init(&ctrldev->ctrl_lock);
+ cdev_init(&ctrldev->cdev, &rpmsg_ctrldev_fops);
+ ctrldev->cdev.owner = THIS_MODULE;
+
+ ret = ida_simple_get(&rpmsg_minor_ida, 0, RPMSG_DEV_MAX, GFP_KERNEL);
+ if (ret < 0)
+ goto free_ctrldev;
+ dev->devt = MKDEV(MAJOR(rpmsg_major), ret);
+
+ ret = ida_simple_get(&rpmsg_ctrl_ida, 0, 0, GFP_KERNEL);
+ if (ret < 0)
+ goto free_minor_ida;
+ dev->id = ret;
+ dev_set_name(&ctrldev->dev, "rpmsg_ctrl%d", ret);
+
+ ret = cdev_device_add(&ctrldev->cdev, &ctrldev->dev);
+ if (ret)
+ goto free_ctrl_ida;
+
+ /* We can now rely on the release function for cleanup */
+ dev->release = rpmsg_ctrldev_release_device;
+
+ dev_set_drvdata(&rpdev->dev, ctrldev);
+
+ return ret;
+
+free_ctrl_ida:
+ ida_simple_remove(&rpmsg_ctrl_ida, dev->id);
+free_minor_ida:
+ ida_simple_remove(&rpmsg_minor_ida, MINOR(dev->devt));
+free_ctrldev:
+ put_device(dev);
+ kfree(ctrldev);
+
+ return ret;
+}
+
+static void rpmsg_ctrldev_remove(struct rpmsg_device *rpdev)
+{
+ struct rpmsg_ctrldev *ctrldev = dev_get_drvdata(&rpdev->dev);
+ int ret;
+
+ /* Destroy all endpoints */
+ ret = device_for_each_child(&ctrldev->dev, NULL, rpmsg_chrdev_eptdev_destroy);
+ if (ret)
+ dev_warn(&rpdev->dev, "failed to nuke endpoints: %d\n", ret);
+
+ cdev_device_del(&ctrldev->cdev, &ctrldev->dev);
+ put_device(&ctrldev->dev);
+}
+
+static struct rpmsg_driver rpmsg_ctrldev_driver = {
+ .probe = rpmsg_ctrldev_probe,
+ .remove = rpmsg_ctrldev_remove,
+ .drv = {
+ .name = "rpmsg_ctrl",
+ },
+};
+
+static int rpmsg_ctrldev_init(void)
+{
+ int ret;
+
+ ret = alloc_chrdev_region(&rpmsg_major, 0, RPMSG_DEV_MAX, "rpmsg_ctrl");
+ if (ret < 0) {
+ pr_err("failed to allocate char dev region\n");
+ return ret;
+ }
+
+ ret = register_rpmsg_driver(&rpmsg_ctrldev_driver);
+ if (ret < 0) {
+ pr_err("failed to register rpmsg driver\n");
+ unregister_chrdev_region(rpmsg_major, RPMSG_DEV_MAX);
+ }
+
+ return ret;
+}
+postcore_initcall(rpmsg_ctrldev_init);
+
+static void rpmsg_ctrldev_exit(void)
+{
+ unregister_rpmsg_driver(&rpmsg_ctrldev_driver);
+ unregister_chrdev_region(rpmsg_major, RPMSG_DEV_MAX);
+}
+module_exit(rpmsg_ctrldev_exit);
+
+MODULE_DESCRIPTION("rpmsg control interface");
+MODULE_ALIAS("rpmsg:" KBUILD_MODNAME);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/rpmsg/rpmsg_internal.h b/drivers/rpmsg/rpmsg_internal.h
index b1245d3ed7c6..d4b23fd019a8 100644
--- a/drivers/rpmsg/rpmsg_internal.h
+++ b/drivers/rpmsg/rpmsg_internal.h
@@ -18,6 +18,8 @@
#define to_rpmsg_device(d) container_of(d, struct rpmsg_device, dev)
#define to_rpmsg_driver(d) container_of(d, struct rpmsg_driver, drv)
+extern struct class *rpmsg_class;
+
/**
* struct rpmsg_device_ops - indirection table for the rpmsg_device operations
* @create_channel: create backend-specific channel, optional
@@ -84,16 +86,16 @@ struct rpmsg_device *rpmsg_create_channel(struct rpmsg_device *rpdev,
int rpmsg_release_channel(struct rpmsg_device *rpdev,
struct rpmsg_channel_info *chinfo);
/**
- * rpmsg_chrdev_register_device() - register chrdev device based on rpdev
+ * rpmsg_ctrldev_register_device() - register a char device for control based on rpdev
* @rpdev: prepared rpdev to be used for creating endpoints
*
* This function wraps rpmsg_register_device() preparing the rpdev for use as
* basis for the rpmsg chrdev.
*/
-static inline int rpmsg_chrdev_register_device(struct rpmsg_device *rpdev)
+static inline int rpmsg_ctrldev_register_device(struct rpmsg_device *rpdev)
{
- strcpy(rpdev->id.name, "rpmsg_chrdev");
- rpdev->driver_override = "rpmsg_chrdev";
+ strcpy(rpdev->id.name, "rpmsg_ctrl");
+ rpdev->driver_override = "rpmsg_ctrl";
return rpmsg_register_device(rpdev);
}
diff --git a/drivers/rpmsg/virtio_rpmsg_bus.c b/drivers/rpmsg/virtio_rpmsg_bus.c
index ac764e04c898..3ede25b1f2e4 100644
--- a/drivers/rpmsg/virtio_rpmsg_bus.c
+++ b/drivers/rpmsg/virtio_rpmsg_bus.c
@@ -849,7 +849,7 @@ static struct rpmsg_device *rpmsg_virtio_add_ctrl_dev(struct virtio_device *vdev
rpdev_ctrl->dev.release = virtio_rpmsg_release_device;
rpdev_ctrl->little_endian = virtio_is_little_endian(vrp->vdev);
- err = rpmsg_chrdev_register_device(rpdev_ctrl);
+ err = rpmsg_ctrldev_register_device(rpdev_ctrl);
if (err) {
kfree(vch);
return ERR_PTR(err);
diff --git a/include/uapi/linux/rpmsg.h b/include/uapi/linux/rpmsg.h
index f5ca8740f3fb..1637e68177d9 100644
--- a/include/uapi/linux/rpmsg.h
+++ b/include/uapi/linux/rpmsg.h
@@ -33,4 +33,14 @@ struct rpmsg_endpoint_info {
*/
#define RPMSG_DESTROY_EPT_IOCTL _IO(0xb5, 0x2)
+/**
+ * Instantiate a new local rpmsg service device.
+ */
+#define RPMSG_CREATE_DEV_IOCTL _IOW(0xb5, 0x3, struct rpmsg_endpoint_info)
+
+/**
+ * Release a local rpmsg device.
+ */
+#define RPMSG_RELEASE_DEV_IOCTL _IOW(0xb5, 0x4, struct rpmsg_endpoint_info)
+
#endif