[RFC PATCH 09/22] rpmsg: Introduce configfs entry for configuring rpmsg

From: Kishon Vijay Abraham I
Date: Thu Jul 02 2020 - 04:23:00 EST


Create a configfs entry for each "struct rpmsg_device_id" populated
in rpmsg client driver and create a configfs entry for each rpmsg
device. This will be used to bind a rpmsg client driver to a rpmsg
device in order to create a new rpmsg channel.

This is used for creating channel for VHOST based rpmsg bus (channels
are created in VIRTIO based bus during namespace announcement).

Signed-off-by: Kishon Vijay Abraham I <kishon@xxxxxx>
---
drivers/rpmsg/Makefile | 2 +-
drivers/rpmsg/rpmsg_cfs.c | 394 +++++++++++++++++++++++++++++++++
drivers/rpmsg/rpmsg_core.c | 7 +
drivers/rpmsg/rpmsg_internal.h | 16 ++
include/linux/rpmsg.h | 5 +
5 files changed, 423 insertions(+), 1 deletion(-)
create mode 100644 drivers/rpmsg/rpmsg_cfs.c

diff --git a/drivers/rpmsg/Makefile b/drivers/rpmsg/Makefile
index ae92a7fb08f6..047acfda518a 100644
--- a/drivers/rpmsg/Makefile
+++ b/drivers/rpmsg/Makefile
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: GPL-2.0
-obj-$(CONFIG_RPMSG) += rpmsg_core.o
+obj-$(CONFIG_RPMSG) += rpmsg_core.o rpmsg_cfs.o
obj-$(CONFIG_RPMSG_CHAR) += rpmsg_char.o
obj-$(CONFIG_RPMSG_MTK_SCP) += mtk_rpmsg.o
obj-$(CONFIG_RPMSG_QCOM_GLINK_RPM) += qcom_glink_rpm.o
diff --git a/drivers/rpmsg/rpmsg_cfs.c b/drivers/rpmsg/rpmsg_cfs.c
new file mode 100644
index 000000000000..a5c77aba00ee
--- /dev/null
+++ b/drivers/rpmsg/rpmsg_cfs.c
@@ -0,0 +1,394 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ * configfs to configure RPMSG
+ *
+ * Copyright (C) 2020 Texas Instruments
+ * Author: Kishon Vijay Abraham I <kishon@xxxxxx>
+ */
+
+#include <linux/configfs.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/idr.h>
+#include <linux/slab.h>
+
+#include "rpmsg_internal.h"
+
+static struct config_group *channel_group;
+static struct config_group *virtproc_group;
+
+enum rpmsg_channel_status {
+ STATUS_FREE,
+ STATUS_BUSY,
+};
+
+struct rpmsg_channel {
+ struct config_item item;
+ struct device *dev;
+ enum rpmsg_channel_status status;
+};
+
+struct rpmsg_channel_group {
+ struct config_group group;
+};
+
+struct rpmsg_virtproc_group {
+ struct config_group group;
+ struct device *dev;
+ const struct rpmsg_virtproc_ops *ops;
+};
+
+static inline
+struct rpmsg_channel *to_rpmsg_channel(struct config_item *channel_item)
+{
+ return container_of(channel_item, struct rpmsg_channel, item);
+}
+
+static inline struct rpmsg_channel_group
+*to_rpmsg_channel_group(struct config_group *channel_group)
+{
+ return container_of(channel_group, struct rpmsg_channel_group, group);
+}
+
+static inline
+struct rpmsg_virtproc_group *to_rpmsg_virtproc_group(struct config_item *item)
+{
+ return container_of(to_config_group(item), struct rpmsg_virtproc_group,
+ group);
+}
+
+/**
+ * rpmsg_virtproc_channel_link() - Create softlink of rpmsg client device
+ * directory to virtproc configfs directory
+ * @virtproc_item: Config item representing configfs entry of virtual remote
+ * processor
+ * @channel_item: Config item representing configfs entry of rpmsg client
+ * driver
+ *
+ * Bind rpmsg client device to virtual remote processor by creating softlink
+ * between rpmsg client device directory to virtproc configfs directory
+ * in order to create a new rpmsg channel.
+ */
+static int rpmsg_virtproc_channel_link(struct config_item *virtproc_item,
+ struct config_item *channel_item)
+{
+ struct rpmsg_virtproc_group *vgroup;
+ struct rpmsg_channel *channel;
+ struct config_group *cgroup;
+ struct device *dev;
+
+ vgroup = to_rpmsg_virtproc_group(virtproc_item);
+ channel = to_rpmsg_channel(channel_item);
+
+ if (channel->status == STATUS_BUSY)
+ return -EBUSY;
+
+ cgroup = channel_item->ci_group;
+
+ if (vgroup->ops && vgroup->ops->create_channel) {
+ dev = vgroup->ops->create_channel(vgroup->dev,
+ cgroup->cg_item.ci_name);
+ if (IS_ERR_OR_NULL(dev))
+ return PTR_ERR(dev);
+ }
+
+ channel->dev = dev;
+ channel->status = STATUS_BUSY;
+
+ return 0;
+}
+
+/**
+ * rpmsg_virtproc_channel_unlink() - Remove softlink of rpmsg client device
+ * directory from virtproc configfs directory
+ * @virtproc_item: Config item representing configfs entry of virtual remote
+ * processor
+ * @channel_item: Config item representing configfs entry of rpmsg client
+ * driver
+ *
+ * Unbind rpmsg client device from virtual remote processor by removing softlink
+ * of rpmsg client device directory from virtproc configfs directory which
+ * deletes the rpmsg channel.
+ */
+static void rpmsg_virtproc_channel_unlink(struct config_item *virtproc_item,
+ struct config_item *channel_item)
+{
+ struct rpmsg_virtproc_group *vgroup;
+ struct rpmsg_channel *channel;
+
+ channel = to_rpmsg_channel(channel_item);
+ vgroup = to_rpmsg_virtproc_group(virtproc_item);
+
+ if (vgroup->ops && vgroup->ops->delete_channel)
+ vgroup->ops->delete_channel(channel->dev);
+
+ channel->status = STATUS_FREE;
+}
+
+static struct configfs_item_operations rpmsg_virtproc_item_ops = {
+ .allow_link = rpmsg_virtproc_channel_link,
+ .drop_link = rpmsg_virtproc_channel_unlink,
+};
+
+static const struct config_item_type rpmsg_virtproc_item_type = {
+ .ct_item_ops = &rpmsg_virtproc_item_ops,
+ .ct_owner = THIS_MODULE,
+};
+
+/**
+ * rpmsg_cfs_add_virtproc_group() - Add new configfs directory for virtproc
+ * device
+ * @dev: Device representing the virtual remote processor
+ * @ops: rpmsg_virtproc_ops to create or delete rpmsg channel
+ *
+ * Add new configfs directory for virtproc device. The rpmsg client driver's
+ * configfs entry can be linked with this directory for creating a new
+ * rpmsg channel and the link can be removed for deleting the rpmsg channel.
+ */
+struct config_group *
+rpmsg_cfs_add_virtproc_group(struct device *dev,
+ const struct rpmsg_virtproc_ops *ops)
+{
+ struct rpmsg_virtproc_group *vgroup;
+ struct config_group *group;
+ struct device *vdev;
+ int ret;
+
+ vgroup = kzalloc(sizeof(*vgroup), GFP_KERNEL);
+ if (!vgroup)
+ return ERR_PTR(-ENOMEM);
+
+ group = &vgroup->group;
+ config_group_init_type_name(group, dev_name(dev),
+ &rpmsg_virtproc_item_type);
+ ret = configfs_register_group(virtproc_group, group);
+ if (ret)
+ goto err_register_group;
+
+ if (!try_module_get(ops->owner)) {
+ ret = -EPROBE_DEFER;
+ goto err_module_get;
+ }
+
+ vdev = get_device(dev);
+ vgroup->dev = vdev;
+ vgroup->ops = ops;
+
+ return group;
+
+err_module_get:
+ configfs_unregister_group(group);
+
+err_register_group:
+ kfree(vgroup);
+
+ return ERR_PTR(ret);
+}
+EXPORT_SYMBOL(rpmsg_cfs_add_virtproc_group);
+
+/**
+ * rpmsg_cfs_remove_virtproc_group() - Remove the configfs directory for
+ * virtproc device
+ * @group: config_group of the virtproc device
+ *
+ * Remove the configfs directory for virtproc device.
+ */
+void rpmsg_cfs_remove_virtproc_group(struct config_group *group)
+{
+ struct rpmsg_virtproc_group *vgroup;
+
+ if (!group)
+ return;
+
+ vgroup = container_of(group, struct rpmsg_virtproc_group, group);
+ put_device(vgroup->dev);
+ module_put(vgroup->ops->owner);
+ configfs_unregister_group(&vgroup->group);
+ kfree(vgroup);
+}
+EXPORT_SYMBOL(rpmsg_cfs_remove_virtproc_group);
+
+static const struct config_item_type rpmsg_channel_item_type = {
+ .ct_owner = THIS_MODULE,
+};
+
+/**
+ * rpmsg_channel_make() - Allow user to create sub-directory of rpmsg client
+ * driver
+ * @name: Name of the sub-directory created by the user.
+ *
+ * Invoked when user creates a sub-directory to the configfs directory
+ * representing the rpmsg client driver. This can be linked with the virtproc
+ * directory for creating a new rpmsg channel.
+ */
+static struct config_item *
+rpmsg_channel_make(struct config_group *group, const char *name)
+{
+ struct rpmsg_channel *channel;
+
+ channel = kzalloc(sizeof(*channel), GFP_KERNEL);
+ if (!channel)
+ return ERR_PTR(-ENOMEM);
+
+ channel->status = STATUS_FREE;
+
+ config_item_init_type_name(&channel->item, name, &rpmsg_channel_item_type);
+ return &channel->item;
+}
+
+/**
+ * rpmsg_channel_drop() - Allow user to delete sub-directory of rpmsg client
+ * driver
+ * @item: Config item representing the sub-directory the user created returned
+ * by rpmsg_channel_make()
+ *
+ * Invoked when user creates a sub-directory to the configfs directory
+ * representing the rpmsg client driver. This can be linked with the virtproc
+ * directory for creating a new rpmsg channel.
+ */
+static void rpmsg_channel_drop(struct config_group *group, struct config_item *item)
+{
+ struct rpmsg_channel *channel;
+
+ channel = to_rpmsg_channel(item);
+ kfree(channel);
+}
+
+static struct configfs_group_operations rpmsg_channel_group_ops = {
+ .make_item = &rpmsg_channel_make,
+ .drop_item = &rpmsg_channel_drop,
+};
+
+static const struct config_item_type rpmsg_channel_group_type = {
+ .ct_group_ops = &rpmsg_channel_group_ops,
+ .ct_owner = THIS_MODULE,
+};
+
+/**
+ * rpmsg_cfs_add_channel_group() - Create a configfs directory for each
+ * registered rpmsg client driver
+ * @name: The name of the rpmsg client driver
+ *
+ * Create a configfs directory for each registered rpmsg client driver. The
+ * user can create sub-directory within this directory for creating
+ * rpmsg channels to be used by the rpmsg client driver.
+ */
+struct config_group *rpmsg_cfs_add_channel_group(const char *name)
+{
+ struct rpmsg_channel_group *cgroup;
+ struct config_group *group;
+ int ret;
+
+ cgroup = kzalloc(sizeof(*cgroup), GFP_KERNEL);
+ if (!cgroup)
+ return ERR_PTR(-ENOMEM);
+
+ group = &cgroup->group;
+ config_group_init_type_name(group, name, &rpmsg_channel_group_type);
+ ret = configfs_register_group(channel_group, group);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return group;
+}
+EXPORT_SYMBOL(rpmsg_cfs_add_channel_group);
+
+/**
+ * rpmsg_cfs_remove_channel_group() - Remove the configfs directory associated
+ * with the rpmsg client driver
+ * @group: Config group representing the rpmsg client driver
+ *
+ * Remove the configfs directory associated with the rpmsg client driver.
+ */
+void rpmsg_cfs_remove_channel_group(struct config_group *group)
+{
+ struct rpmsg_channel_group *cgroup;
+
+ if (IS_ERR_OR_NULL(group))
+ return;
+
+ cgroup = to_rpmsg_channel_group(group);
+ configfs_unregister_default_group(group);
+ kfree(cgroup);
+}
+EXPORT_SYMBOL(rpmsg_cfs_remove_channel_group);
+
+static const struct config_item_type rpmsg_channel_type = {
+ .ct_owner = THIS_MODULE,
+};
+
+static const struct config_item_type rpmsg_virtproc_type = {
+ .ct_owner = THIS_MODULE,
+};
+
+static const struct config_item_type rpmsg_type = {
+ .ct_owner = THIS_MODULE,
+};
+
+static struct configfs_subsystem rpmsg_cfs_subsys = {
+ .su_group = {
+ .cg_item = {
+ .ci_namebuf = "rpmsg",
+ .ci_type = &rpmsg_type,
+ },
+ },
+ .su_mutex = __MUTEX_INITIALIZER(rpmsg_cfs_subsys.su_mutex),
+};
+
+static int __init rpmsg_cfs_init(void)
+{
+ int ret;
+ struct config_group *root = &rpmsg_cfs_subsys.su_group;
+
+ config_group_init(root);
+
+ ret = configfs_register_subsystem(&rpmsg_cfs_subsys);
+ if (ret) {
+ pr_err("Error %d while registering subsystem %s\n",
+ ret, root->cg_item.ci_namebuf);
+ goto err;
+ }
+
+ channel_group = configfs_register_default_group(root, "channel",
+ &rpmsg_channel_type);
+ if (IS_ERR(channel_group)) {
+ ret = PTR_ERR(channel_group);
+ pr_err("Error %d while registering channel group\n",
+ ret);
+ goto err_channel_group;
+ }
+
+ virtproc_group =
+ configfs_register_default_group(root, "virtproc",
+ &rpmsg_virtproc_type);
+ if (IS_ERR(virtproc_group)) {
+ ret = PTR_ERR(virtproc_group);
+ pr_err("Error %d while registering virtproc group\n",
+ ret);
+ goto err_virtproc_group;
+ }
+
+ return 0;
+
+err_virtproc_group:
+ configfs_unregister_default_group(channel_group);
+
+err_channel_group:
+ configfs_unregister_subsystem(&rpmsg_cfs_subsys);
+
+err:
+ return ret;
+}
+module_init(rpmsg_cfs_init);
+
+static void __exit rpmsg_cfs_exit(void)
+{
+ configfs_unregister_default_group(virtproc_group);
+ configfs_unregister_default_group(channel_group);
+ configfs_unregister_subsystem(&rpmsg_cfs_subsys);
+}
+module_exit(rpmsg_cfs_exit);
+
+MODULE_DESCRIPTION("PCI RPMSG CONFIGFS");
+MODULE_AUTHOR("Kishon Vijay Abraham I <kishon@xxxxxx>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/rpmsg/rpmsg_core.c b/drivers/rpmsg/rpmsg_core.c
index e330ec4dfc33..68569fec03e2 100644
--- a/drivers/rpmsg/rpmsg_core.c
+++ b/drivers/rpmsg/rpmsg_core.c
@@ -563,8 +563,15 @@ EXPORT_SYMBOL(rpmsg_unregister_device);
*/
int __register_rpmsg_driver(struct rpmsg_driver *rpdrv, struct module *owner)
{
+ const struct rpmsg_device_id *ids = rpdrv->id_table;
rpdrv->drv.bus = &rpmsg_bus;
rpdrv->drv.owner = owner;
+
+ while (ids && ids->name[0]) {
+ rpmsg_cfs_add_channel_group(ids->name);
+ ids++;
+ }
+
return driver_register(&rpdrv->drv);
}
EXPORT_SYMBOL(__register_rpmsg_driver);
diff --git a/drivers/rpmsg/rpmsg_internal.h b/drivers/rpmsg/rpmsg_internal.h
index 3fc83cd50e98..39b3a5caf242 100644
--- a/drivers/rpmsg/rpmsg_internal.h
+++ b/drivers/rpmsg/rpmsg_internal.h
@@ -68,6 +68,18 @@ struct rpmsg_endpoint_ops {
poll_table *wait);
};

+/**
+ * struct rpmsg_virtproc_ops - indirection table for rpmsg_virtproc operations
+ * @create_channel: Create a new rpdev channel
+ * @delete_channel: Delete the rpdev channel
+ * @owner: Owner of the module holding the ops
+ */
+struct rpmsg_virtproc_ops {
+ struct device *(*create_channel)(struct device *dev, const char *name);
+ void (*delete_channel)(struct device *dev);
+ struct module *owner;
+};
+
int rpmsg_register_device(struct rpmsg_device *rpdev);
int rpmsg_unregister_device(struct device *parent,
struct rpmsg_channel_info *chinfo);
@@ -75,6 +87,10 @@ int rpmsg_unregister_device(struct device *parent,
struct device *rpmsg_find_device(struct device *parent,
struct rpmsg_channel_info *chinfo);

+struct config_group *
+rpmsg_cfs_add_virtproc_group(struct device *dev,
+ const struct rpmsg_virtproc_ops *ops);
+
/**
* rpmsg_chrdev_register_device() - register chrdev device based on rpdev
* @rpdev: prepared rpdev to be used for creating endpoints
diff --git a/include/linux/rpmsg.h b/include/linux/rpmsg.h
index 9fe156d1c018..b9d9283b46ac 100644
--- a/include/linux/rpmsg.h
+++ b/include/linux/rpmsg.h
@@ -135,6 +135,7 @@ int rpmsg_trysend_offchannel(struct rpmsg_endpoint *ept, u32 src, u32 dst,
__poll_t rpmsg_poll(struct rpmsg_endpoint *ept, struct file *filp,
poll_table *wait);

+struct config_group *rpmsg_cfs_add_channel_group(const char *name);
#else

static inline int register_rpmsg_device(struct rpmsg_device *dev)
@@ -242,6 +243,10 @@ static inline __poll_t rpmsg_poll(struct rpmsg_endpoint *ept,
return 0;
}

+static inline struct config_group *rpmsg_cfs_add_channel_group(const char *name)
+{
+ return NULL;
+}
#endif /* IS_ENABLED(CONFIG_RPMSG) */

/* use a macro to avoid include chaining to get THIS_MODULE */
--
2.17.1