162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2022, STMicroelectronics
462306a36Sopenharmony_ci * Copyright (c) 2016, Linaro Ltd.
562306a36Sopenharmony_ci * Copyright (c) 2012, Michal Simek <monstr@monstr.eu>
662306a36Sopenharmony_ci * Copyright (c) 2012, PetaLogix
762306a36Sopenharmony_ci * Copyright (c) 2011, Texas Instruments, Inc.
862306a36Sopenharmony_ci * Copyright (c) 2011, Google, Inc.
962306a36Sopenharmony_ci *
1062306a36Sopenharmony_ci * Based on rpmsg performance statistics driver by Michal Simek, which in turn
1162306a36Sopenharmony_ci * was based on TI & Google OMX rpmsg driver.
1262306a36Sopenharmony_ci */
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci#define pr_fmt(fmt)		KBUILD_MODNAME ": " fmt
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#include <linux/cdev.h>
1762306a36Sopenharmony_ci#include <linux/device.h>
1862306a36Sopenharmony_ci#include <linux/fs.h>
1962306a36Sopenharmony_ci#include <linux/idr.h>
2062306a36Sopenharmony_ci#include <linux/kernel.h>
2162306a36Sopenharmony_ci#include <linux/module.h>
2262306a36Sopenharmony_ci#include <linux/rpmsg.h>
2362306a36Sopenharmony_ci#include <linux/skbuff.h>
2462306a36Sopenharmony_ci#include <linux/slab.h>
2562306a36Sopenharmony_ci#include <linux/uaccess.h>
2662306a36Sopenharmony_ci#include <uapi/linux/rpmsg.h>
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci#include "rpmsg_char.h"
2962306a36Sopenharmony_ci#include "rpmsg_internal.h"
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci#define RPMSG_DEV_MAX	(MINORMASK + 1)
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_cistatic dev_t rpmsg_major;
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_cistatic DEFINE_IDA(rpmsg_ctrl_ida);
3662306a36Sopenharmony_cistatic DEFINE_IDA(rpmsg_minor_ida);
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci#define dev_to_ctrldev(dev) container_of(dev, struct rpmsg_ctrldev, dev)
3962306a36Sopenharmony_ci#define cdev_to_ctrldev(i_cdev) container_of(i_cdev, struct rpmsg_ctrldev, cdev)
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci/**
4262306a36Sopenharmony_ci * struct rpmsg_ctrldev - control device for instantiating endpoint devices
4362306a36Sopenharmony_ci * @rpdev:	underlaying rpmsg device
4462306a36Sopenharmony_ci * @cdev:	cdev for the ctrl device
4562306a36Sopenharmony_ci * @dev:	device for the ctrl device
4662306a36Sopenharmony_ci * @ctrl_lock:	serialize the ioctrls.
4762306a36Sopenharmony_ci */
4862306a36Sopenharmony_cistruct rpmsg_ctrldev {
4962306a36Sopenharmony_ci	struct rpmsg_device *rpdev;
5062306a36Sopenharmony_ci	struct cdev cdev;
5162306a36Sopenharmony_ci	struct device dev;
5262306a36Sopenharmony_ci	struct mutex ctrl_lock;
5362306a36Sopenharmony_ci};
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_cistatic int rpmsg_ctrldev_open(struct inode *inode, struct file *filp)
5662306a36Sopenharmony_ci{
5762306a36Sopenharmony_ci	struct rpmsg_ctrldev *ctrldev = cdev_to_ctrldev(inode->i_cdev);
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci	get_device(&ctrldev->dev);
6062306a36Sopenharmony_ci	filp->private_data = ctrldev;
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	return 0;
6362306a36Sopenharmony_ci}
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_cistatic int rpmsg_ctrldev_release(struct inode *inode, struct file *filp)
6662306a36Sopenharmony_ci{
6762306a36Sopenharmony_ci	struct rpmsg_ctrldev *ctrldev = cdev_to_ctrldev(inode->i_cdev);
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	put_device(&ctrldev->dev);
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	return 0;
7262306a36Sopenharmony_ci}
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_cistatic long rpmsg_ctrldev_ioctl(struct file *fp, unsigned int cmd,
7562306a36Sopenharmony_ci				unsigned long arg)
7662306a36Sopenharmony_ci{
7762306a36Sopenharmony_ci	struct rpmsg_ctrldev *ctrldev = fp->private_data;
7862306a36Sopenharmony_ci	void __user *argp = (void __user *)arg;
7962306a36Sopenharmony_ci	struct rpmsg_endpoint_info eptinfo;
8062306a36Sopenharmony_ci	struct rpmsg_channel_info chinfo;
8162306a36Sopenharmony_ci	struct rpmsg_device *rpdev;
8262306a36Sopenharmony_ci	int ret = 0;
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	if (copy_from_user(&eptinfo, argp, sizeof(eptinfo)))
8562306a36Sopenharmony_ci		return -EFAULT;
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	memcpy(chinfo.name, eptinfo.name, RPMSG_NAME_SIZE);
8862306a36Sopenharmony_ci	chinfo.name[RPMSG_NAME_SIZE - 1] = '\0';
8962306a36Sopenharmony_ci	chinfo.src = eptinfo.src;
9062306a36Sopenharmony_ci	chinfo.dst = eptinfo.dst;
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	mutex_lock(&ctrldev->ctrl_lock);
9362306a36Sopenharmony_ci	switch (cmd) {
9462306a36Sopenharmony_ci	case RPMSG_CREATE_EPT_IOCTL:
9562306a36Sopenharmony_ci		ret = rpmsg_chrdev_eptdev_create(ctrldev->rpdev, &ctrldev->dev, chinfo);
9662306a36Sopenharmony_ci		break;
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	case RPMSG_CREATE_DEV_IOCTL:
9962306a36Sopenharmony_ci		rpdev = rpmsg_create_channel(ctrldev->rpdev, &chinfo);
10062306a36Sopenharmony_ci		if (!rpdev) {
10162306a36Sopenharmony_ci			dev_err(&ctrldev->dev, "failed to create %s channel\n", chinfo.name);
10262306a36Sopenharmony_ci			ret = -ENXIO;
10362306a36Sopenharmony_ci		}
10462306a36Sopenharmony_ci		break;
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	case RPMSG_RELEASE_DEV_IOCTL:
10762306a36Sopenharmony_ci		ret = rpmsg_release_channel(ctrldev->rpdev, &chinfo);
10862306a36Sopenharmony_ci		if (ret)
10962306a36Sopenharmony_ci			dev_err(&ctrldev->dev, "failed to release %s channel (%d)\n",
11062306a36Sopenharmony_ci				chinfo.name, ret);
11162306a36Sopenharmony_ci		break;
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci	default:
11462306a36Sopenharmony_ci		ret = -EINVAL;
11562306a36Sopenharmony_ci	}
11662306a36Sopenharmony_ci	mutex_unlock(&ctrldev->ctrl_lock);
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	return ret;
11962306a36Sopenharmony_ci};
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_cistatic const struct file_operations rpmsg_ctrldev_fops = {
12262306a36Sopenharmony_ci	.owner = THIS_MODULE,
12362306a36Sopenharmony_ci	.open = rpmsg_ctrldev_open,
12462306a36Sopenharmony_ci	.release = rpmsg_ctrldev_release,
12562306a36Sopenharmony_ci	.unlocked_ioctl = rpmsg_ctrldev_ioctl,
12662306a36Sopenharmony_ci	.compat_ioctl = compat_ptr_ioctl,
12762306a36Sopenharmony_ci};
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_cistatic void rpmsg_ctrldev_release_device(struct device *dev)
13062306a36Sopenharmony_ci{
13162306a36Sopenharmony_ci	struct rpmsg_ctrldev *ctrldev = dev_to_ctrldev(dev);
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	ida_simple_remove(&rpmsg_ctrl_ida, dev->id);
13462306a36Sopenharmony_ci	ida_simple_remove(&rpmsg_minor_ida, MINOR(dev->devt));
13562306a36Sopenharmony_ci	kfree(ctrldev);
13662306a36Sopenharmony_ci}
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_cistatic int rpmsg_ctrldev_probe(struct rpmsg_device *rpdev)
13962306a36Sopenharmony_ci{
14062306a36Sopenharmony_ci	struct rpmsg_ctrldev *ctrldev;
14162306a36Sopenharmony_ci	struct device *dev;
14262306a36Sopenharmony_ci	int ret;
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci	ctrldev = kzalloc(sizeof(*ctrldev), GFP_KERNEL);
14562306a36Sopenharmony_ci	if (!ctrldev)
14662306a36Sopenharmony_ci		return -ENOMEM;
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	ctrldev->rpdev = rpdev;
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	dev = &ctrldev->dev;
15162306a36Sopenharmony_ci	device_initialize(dev);
15262306a36Sopenharmony_ci	dev->parent = &rpdev->dev;
15362306a36Sopenharmony_ci	dev->class = rpmsg_class;
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	mutex_init(&ctrldev->ctrl_lock);
15662306a36Sopenharmony_ci	cdev_init(&ctrldev->cdev, &rpmsg_ctrldev_fops);
15762306a36Sopenharmony_ci	ctrldev->cdev.owner = THIS_MODULE;
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci	ret = ida_simple_get(&rpmsg_minor_ida, 0, RPMSG_DEV_MAX, GFP_KERNEL);
16062306a36Sopenharmony_ci	if (ret < 0)
16162306a36Sopenharmony_ci		goto free_ctrldev;
16262306a36Sopenharmony_ci	dev->devt = MKDEV(MAJOR(rpmsg_major), ret);
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	ret = ida_simple_get(&rpmsg_ctrl_ida, 0, 0, GFP_KERNEL);
16562306a36Sopenharmony_ci	if (ret < 0)
16662306a36Sopenharmony_ci		goto free_minor_ida;
16762306a36Sopenharmony_ci	dev->id = ret;
16862306a36Sopenharmony_ci	dev_set_name(&ctrldev->dev, "rpmsg_ctrl%d", ret);
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci	ret = cdev_device_add(&ctrldev->cdev, &ctrldev->dev);
17162306a36Sopenharmony_ci	if (ret)
17262306a36Sopenharmony_ci		goto free_ctrl_ida;
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci	/* We can now rely on the release function for cleanup */
17562306a36Sopenharmony_ci	dev->release = rpmsg_ctrldev_release_device;
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci	dev_set_drvdata(&rpdev->dev, ctrldev);
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci	return ret;
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_cifree_ctrl_ida:
18262306a36Sopenharmony_ci	ida_simple_remove(&rpmsg_ctrl_ida, dev->id);
18362306a36Sopenharmony_cifree_minor_ida:
18462306a36Sopenharmony_ci	ida_simple_remove(&rpmsg_minor_ida, MINOR(dev->devt));
18562306a36Sopenharmony_cifree_ctrldev:
18662306a36Sopenharmony_ci	put_device(dev);
18762306a36Sopenharmony_ci	kfree(ctrldev);
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	return ret;
19062306a36Sopenharmony_ci}
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_cistatic void rpmsg_ctrldev_remove(struct rpmsg_device *rpdev)
19362306a36Sopenharmony_ci{
19462306a36Sopenharmony_ci	struct rpmsg_ctrldev *ctrldev = dev_get_drvdata(&rpdev->dev);
19562306a36Sopenharmony_ci	int ret;
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	mutex_lock(&ctrldev->ctrl_lock);
19862306a36Sopenharmony_ci	/* Destroy all endpoints */
19962306a36Sopenharmony_ci	ret = device_for_each_child(&ctrldev->dev, NULL, rpmsg_chrdev_eptdev_destroy);
20062306a36Sopenharmony_ci	if (ret)
20162306a36Sopenharmony_ci		dev_warn(&rpdev->dev, "failed to nuke endpoints: %d\n", ret);
20262306a36Sopenharmony_ci	mutex_unlock(&ctrldev->ctrl_lock);
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci	cdev_device_del(&ctrldev->cdev, &ctrldev->dev);
20562306a36Sopenharmony_ci	put_device(&ctrldev->dev);
20662306a36Sopenharmony_ci}
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_cistatic struct rpmsg_driver rpmsg_ctrldev_driver = {
20962306a36Sopenharmony_ci	.probe = rpmsg_ctrldev_probe,
21062306a36Sopenharmony_ci	.remove = rpmsg_ctrldev_remove,
21162306a36Sopenharmony_ci	.drv = {
21262306a36Sopenharmony_ci		.name = "rpmsg_ctrl",
21362306a36Sopenharmony_ci	},
21462306a36Sopenharmony_ci};
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_cistatic int rpmsg_ctrldev_init(void)
21762306a36Sopenharmony_ci{
21862306a36Sopenharmony_ci	int ret;
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci	ret = alloc_chrdev_region(&rpmsg_major, 0, RPMSG_DEV_MAX, "rpmsg_ctrl");
22162306a36Sopenharmony_ci	if (ret < 0) {
22262306a36Sopenharmony_ci		pr_err("failed to allocate char dev region\n");
22362306a36Sopenharmony_ci		return ret;
22462306a36Sopenharmony_ci	}
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_ci	ret = register_rpmsg_driver(&rpmsg_ctrldev_driver);
22762306a36Sopenharmony_ci	if (ret < 0) {
22862306a36Sopenharmony_ci		pr_err("failed to register rpmsg driver\n");
22962306a36Sopenharmony_ci		unregister_chrdev_region(rpmsg_major, RPMSG_DEV_MAX);
23062306a36Sopenharmony_ci	}
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_ci	return ret;
23362306a36Sopenharmony_ci}
23462306a36Sopenharmony_cipostcore_initcall(rpmsg_ctrldev_init);
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_cistatic void rpmsg_ctrldev_exit(void)
23762306a36Sopenharmony_ci{
23862306a36Sopenharmony_ci	unregister_rpmsg_driver(&rpmsg_ctrldev_driver);
23962306a36Sopenharmony_ci	unregister_chrdev_region(rpmsg_major, RPMSG_DEV_MAX);
24062306a36Sopenharmony_ci}
24162306a36Sopenharmony_cimodule_exit(rpmsg_ctrldev_exit);
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_ciMODULE_DESCRIPTION("rpmsg control interface");
24462306a36Sopenharmony_ciMODULE_ALIAS("rpmsg:" KBUILD_MODNAME);
24562306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
246