18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Copyright (c) 2016, Linaro Ltd.
48c2ecf20Sopenharmony_ci * Copyright (c) 2012, Michal Simek <monstr@monstr.eu>
58c2ecf20Sopenharmony_ci * Copyright (c) 2012, PetaLogix
68c2ecf20Sopenharmony_ci * Copyright (c) 2011, Texas Instruments, Inc.
78c2ecf20Sopenharmony_ci * Copyright (c) 2011, Google, Inc.
88c2ecf20Sopenharmony_ci *
98c2ecf20Sopenharmony_ci * Based on rpmsg performance statistics driver by Michal Simek, which in turn
108c2ecf20Sopenharmony_ci * was based on TI & Google OMX rpmsg driver.
118c2ecf20Sopenharmony_ci */
128c2ecf20Sopenharmony_ci#include <linux/cdev.h>
138c2ecf20Sopenharmony_ci#include <linux/device.h>
148c2ecf20Sopenharmony_ci#include <linux/fs.h>
158c2ecf20Sopenharmony_ci#include <linux/idr.h>
168c2ecf20Sopenharmony_ci#include <linux/kernel.h>
178c2ecf20Sopenharmony_ci#include <linux/module.h>
188c2ecf20Sopenharmony_ci#include <linux/poll.h>
198c2ecf20Sopenharmony_ci#include <linux/rpmsg.h>
208c2ecf20Sopenharmony_ci#include <linux/skbuff.h>
218c2ecf20Sopenharmony_ci#include <linux/slab.h>
228c2ecf20Sopenharmony_ci#include <linux/uaccess.h>
238c2ecf20Sopenharmony_ci#include <uapi/linux/rpmsg.h>
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci#include "rpmsg_internal.h"
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_ci#define RPMSG_DEV_MAX	(MINORMASK + 1)
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_cistatic dev_t rpmsg_major;
308c2ecf20Sopenharmony_cistatic struct class *rpmsg_class;
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_cistatic DEFINE_IDA(rpmsg_ctrl_ida);
338c2ecf20Sopenharmony_cistatic DEFINE_IDA(rpmsg_ept_ida);
348c2ecf20Sopenharmony_cistatic DEFINE_IDA(rpmsg_minor_ida);
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ci#define dev_to_eptdev(dev) container_of(dev, struct rpmsg_eptdev, dev)
378c2ecf20Sopenharmony_ci#define cdev_to_eptdev(i_cdev) container_of(i_cdev, struct rpmsg_eptdev, cdev)
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_ci#define dev_to_ctrldev(dev) container_of(dev, struct rpmsg_ctrldev, dev)
408c2ecf20Sopenharmony_ci#define cdev_to_ctrldev(i_cdev) container_of(i_cdev, struct rpmsg_ctrldev, cdev)
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci/**
438c2ecf20Sopenharmony_ci * struct rpmsg_ctrldev - control device for instantiating endpoint devices
448c2ecf20Sopenharmony_ci * @rpdev:	underlaying rpmsg device
458c2ecf20Sopenharmony_ci * @cdev:	cdev for the ctrl device
468c2ecf20Sopenharmony_ci * @dev:	device for the ctrl device
478c2ecf20Sopenharmony_ci */
488c2ecf20Sopenharmony_cistruct rpmsg_ctrldev {
498c2ecf20Sopenharmony_ci	struct rpmsg_device *rpdev;
508c2ecf20Sopenharmony_ci	struct cdev cdev;
518c2ecf20Sopenharmony_ci	struct device dev;
528c2ecf20Sopenharmony_ci};
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_ci/**
558c2ecf20Sopenharmony_ci * struct rpmsg_eptdev - endpoint device context
568c2ecf20Sopenharmony_ci * @dev:	endpoint device
578c2ecf20Sopenharmony_ci * @cdev:	cdev for the endpoint device
588c2ecf20Sopenharmony_ci * @rpdev:	underlaying rpmsg device
598c2ecf20Sopenharmony_ci * @chinfo:	info used to open the endpoint
608c2ecf20Sopenharmony_ci * @ept_lock:	synchronization of @ept modifications
618c2ecf20Sopenharmony_ci * @ept:	rpmsg endpoint reference, when open
628c2ecf20Sopenharmony_ci * @queue_lock:	synchronization of @queue operations
638c2ecf20Sopenharmony_ci * @queue:	incoming message queue
648c2ecf20Sopenharmony_ci * @readq:	wait object for incoming queue
658c2ecf20Sopenharmony_ci */
668c2ecf20Sopenharmony_cistruct rpmsg_eptdev {
678c2ecf20Sopenharmony_ci	struct device dev;
688c2ecf20Sopenharmony_ci	struct cdev cdev;
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ci	struct rpmsg_device *rpdev;
718c2ecf20Sopenharmony_ci	struct rpmsg_channel_info chinfo;
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_ci	struct mutex ept_lock;
748c2ecf20Sopenharmony_ci	struct rpmsg_endpoint *ept;
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_ci	spinlock_t queue_lock;
778c2ecf20Sopenharmony_ci	struct sk_buff_head queue;
788c2ecf20Sopenharmony_ci	wait_queue_head_t readq;
798c2ecf20Sopenharmony_ci};
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_cistatic int rpmsg_eptdev_destroy(struct device *dev, void *data)
828c2ecf20Sopenharmony_ci{
838c2ecf20Sopenharmony_ci	struct rpmsg_eptdev *eptdev = dev_to_eptdev(dev);
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci	mutex_lock(&eptdev->ept_lock);
868c2ecf20Sopenharmony_ci	if (eptdev->ept) {
878c2ecf20Sopenharmony_ci		rpmsg_destroy_ept(eptdev->ept);
888c2ecf20Sopenharmony_ci		eptdev->ept = NULL;
898c2ecf20Sopenharmony_ci	}
908c2ecf20Sopenharmony_ci	mutex_unlock(&eptdev->ept_lock);
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_ci	/* wake up any blocked readers */
938c2ecf20Sopenharmony_ci	wake_up_interruptible(&eptdev->readq);
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_ci	cdev_device_del(&eptdev->cdev, &eptdev->dev);
968c2ecf20Sopenharmony_ci	put_device(&eptdev->dev);
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_ci	return 0;
998c2ecf20Sopenharmony_ci}
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_cistatic int rpmsg_ept_cb(struct rpmsg_device *rpdev, void *buf, int len,
1028c2ecf20Sopenharmony_ci			void *priv, u32 addr)
1038c2ecf20Sopenharmony_ci{
1048c2ecf20Sopenharmony_ci	struct rpmsg_eptdev *eptdev = priv;
1058c2ecf20Sopenharmony_ci	struct sk_buff *skb;
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci	skb = alloc_skb(len, GFP_ATOMIC);
1088c2ecf20Sopenharmony_ci	if (!skb)
1098c2ecf20Sopenharmony_ci		return -ENOMEM;
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_ci	skb_put_data(skb, buf, len);
1128c2ecf20Sopenharmony_ci
1138c2ecf20Sopenharmony_ci	spin_lock(&eptdev->queue_lock);
1148c2ecf20Sopenharmony_ci	skb_queue_tail(&eptdev->queue, skb);
1158c2ecf20Sopenharmony_ci	spin_unlock(&eptdev->queue_lock);
1168c2ecf20Sopenharmony_ci
1178c2ecf20Sopenharmony_ci	/* wake up any blocking processes, waiting for new data */
1188c2ecf20Sopenharmony_ci	wake_up_interruptible(&eptdev->readq);
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_ci	return 0;
1218c2ecf20Sopenharmony_ci}
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_cistatic int rpmsg_eptdev_open(struct inode *inode, struct file *filp)
1248c2ecf20Sopenharmony_ci{
1258c2ecf20Sopenharmony_ci	struct rpmsg_eptdev *eptdev = cdev_to_eptdev(inode->i_cdev);
1268c2ecf20Sopenharmony_ci	struct rpmsg_endpoint *ept;
1278c2ecf20Sopenharmony_ci	struct rpmsg_device *rpdev = eptdev->rpdev;
1288c2ecf20Sopenharmony_ci	struct device *dev = &eptdev->dev;
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci	get_device(dev);
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_ci	ept = rpmsg_create_ept(rpdev, rpmsg_ept_cb, eptdev, eptdev->chinfo);
1338c2ecf20Sopenharmony_ci	if (!ept) {
1348c2ecf20Sopenharmony_ci		dev_err(dev, "failed to open %s\n", eptdev->chinfo.name);
1358c2ecf20Sopenharmony_ci		put_device(dev);
1368c2ecf20Sopenharmony_ci		return -EINVAL;
1378c2ecf20Sopenharmony_ci	}
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_ci	eptdev->ept = ept;
1408c2ecf20Sopenharmony_ci	filp->private_data = eptdev;
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci	return 0;
1438c2ecf20Sopenharmony_ci}
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_cistatic int rpmsg_eptdev_release(struct inode *inode, struct file *filp)
1468c2ecf20Sopenharmony_ci{
1478c2ecf20Sopenharmony_ci	struct rpmsg_eptdev *eptdev = cdev_to_eptdev(inode->i_cdev);
1488c2ecf20Sopenharmony_ci	struct device *dev = &eptdev->dev;
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_ci	/* Close the endpoint, if it's not already destroyed by the parent */
1518c2ecf20Sopenharmony_ci	mutex_lock(&eptdev->ept_lock);
1528c2ecf20Sopenharmony_ci	if (eptdev->ept) {
1538c2ecf20Sopenharmony_ci		rpmsg_destroy_ept(eptdev->ept);
1548c2ecf20Sopenharmony_ci		eptdev->ept = NULL;
1558c2ecf20Sopenharmony_ci	}
1568c2ecf20Sopenharmony_ci	mutex_unlock(&eptdev->ept_lock);
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_ci	/* Discard all SKBs */
1598c2ecf20Sopenharmony_ci	skb_queue_purge(&eptdev->queue);
1608c2ecf20Sopenharmony_ci
1618c2ecf20Sopenharmony_ci	put_device(dev);
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_ci	return 0;
1648c2ecf20Sopenharmony_ci}
1658c2ecf20Sopenharmony_ci
1668c2ecf20Sopenharmony_cistatic ssize_t rpmsg_eptdev_read_iter(struct kiocb *iocb, struct iov_iter *to)
1678c2ecf20Sopenharmony_ci{
1688c2ecf20Sopenharmony_ci	struct file *filp = iocb->ki_filp;
1698c2ecf20Sopenharmony_ci	struct rpmsg_eptdev *eptdev = filp->private_data;
1708c2ecf20Sopenharmony_ci	unsigned long flags;
1718c2ecf20Sopenharmony_ci	struct sk_buff *skb;
1728c2ecf20Sopenharmony_ci	int use;
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_ci	if (!eptdev->ept)
1758c2ecf20Sopenharmony_ci		return -EPIPE;
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_ci	spin_lock_irqsave(&eptdev->queue_lock, flags);
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_ci	/* Wait for data in the queue */
1808c2ecf20Sopenharmony_ci	if (skb_queue_empty(&eptdev->queue)) {
1818c2ecf20Sopenharmony_ci		spin_unlock_irqrestore(&eptdev->queue_lock, flags);
1828c2ecf20Sopenharmony_ci
1838c2ecf20Sopenharmony_ci		if (filp->f_flags & O_NONBLOCK)
1848c2ecf20Sopenharmony_ci			return -EAGAIN;
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_ci		/* Wait until we get data or the endpoint goes away */
1878c2ecf20Sopenharmony_ci		if (wait_event_interruptible(eptdev->readq,
1888c2ecf20Sopenharmony_ci					     !skb_queue_empty(&eptdev->queue) ||
1898c2ecf20Sopenharmony_ci					     !eptdev->ept))
1908c2ecf20Sopenharmony_ci			return -ERESTARTSYS;
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_ci		/* We lost the endpoint while waiting */
1938c2ecf20Sopenharmony_ci		if (!eptdev->ept)
1948c2ecf20Sopenharmony_ci			return -EPIPE;
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_ci		spin_lock_irqsave(&eptdev->queue_lock, flags);
1978c2ecf20Sopenharmony_ci	}
1988c2ecf20Sopenharmony_ci
1998c2ecf20Sopenharmony_ci	skb = skb_dequeue(&eptdev->queue);
2008c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&eptdev->queue_lock, flags);
2018c2ecf20Sopenharmony_ci	if (!skb)
2028c2ecf20Sopenharmony_ci		return -EFAULT;
2038c2ecf20Sopenharmony_ci
2048c2ecf20Sopenharmony_ci	use = min_t(size_t, iov_iter_count(to), skb->len);
2058c2ecf20Sopenharmony_ci	if (copy_to_iter(skb->data, use, to) != use)
2068c2ecf20Sopenharmony_ci		use = -EFAULT;
2078c2ecf20Sopenharmony_ci
2088c2ecf20Sopenharmony_ci	kfree_skb(skb);
2098c2ecf20Sopenharmony_ci
2108c2ecf20Sopenharmony_ci	return use;
2118c2ecf20Sopenharmony_ci}
2128c2ecf20Sopenharmony_ci
2138c2ecf20Sopenharmony_cistatic ssize_t rpmsg_eptdev_write_iter(struct kiocb *iocb,
2148c2ecf20Sopenharmony_ci				       struct iov_iter *from)
2158c2ecf20Sopenharmony_ci{
2168c2ecf20Sopenharmony_ci	struct file *filp = iocb->ki_filp;
2178c2ecf20Sopenharmony_ci	struct rpmsg_eptdev *eptdev = filp->private_data;
2188c2ecf20Sopenharmony_ci	size_t len = iov_iter_count(from);
2198c2ecf20Sopenharmony_ci	void *kbuf;
2208c2ecf20Sopenharmony_ci	int ret;
2218c2ecf20Sopenharmony_ci
2228c2ecf20Sopenharmony_ci	kbuf = kzalloc(len, GFP_KERNEL);
2238c2ecf20Sopenharmony_ci	if (!kbuf)
2248c2ecf20Sopenharmony_ci		return -ENOMEM;
2258c2ecf20Sopenharmony_ci
2268c2ecf20Sopenharmony_ci	if (!copy_from_iter_full(kbuf, len, from)) {
2278c2ecf20Sopenharmony_ci		ret = -EFAULT;
2288c2ecf20Sopenharmony_ci		goto free_kbuf;
2298c2ecf20Sopenharmony_ci	}
2308c2ecf20Sopenharmony_ci
2318c2ecf20Sopenharmony_ci	if (mutex_lock_interruptible(&eptdev->ept_lock)) {
2328c2ecf20Sopenharmony_ci		ret = -ERESTARTSYS;
2338c2ecf20Sopenharmony_ci		goto free_kbuf;
2348c2ecf20Sopenharmony_ci	}
2358c2ecf20Sopenharmony_ci
2368c2ecf20Sopenharmony_ci	if (!eptdev->ept) {
2378c2ecf20Sopenharmony_ci		ret = -EPIPE;
2388c2ecf20Sopenharmony_ci		goto unlock_eptdev;
2398c2ecf20Sopenharmony_ci	}
2408c2ecf20Sopenharmony_ci
2418c2ecf20Sopenharmony_ci	if (filp->f_flags & O_NONBLOCK)
2428c2ecf20Sopenharmony_ci		ret = rpmsg_trysend(eptdev->ept, kbuf, len);
2438c2ecf20Sopenharmony_ci	else
2448c2ecf20Sopenharmony_ci		ret = rpmsg_send(eptdev->ept, kbuf, len);
2458c2ecf20Sopenharmony_ci
2468c2ecf20Sopenharmony_ciunlock_eptdev:
2478c2ecf20Sopenharmony_ci	mutex_unlock(&eptdev->ept_lock);
2488c2ecf20Sopenharmony_ci
2498c2ecf20Sopenharmony_cifree_kbuf:
2508c2ecf20Sopenharmony_ci	kfree(kbuf);
2518c2ecf20Sopenharmony_ci	return ret < 0 ? ret : len;
2528c2ecf20Sopenharmony_ci}
2538c2ecf20Sopenharmony_ci
2548c2ecf20Sopenharmony_cistatic __poll_t rpmsg_eptdev_poll(struct file *filp, poll_table *wait)
2558c2ecf20Sopenharmony_ci{
2568c2ecf20Sopenharmony_ci	struct rpmsg_eptdev *eptdev = filp->private_data;
2578c2ecf20Sopenharmony_ci	__poll_t mask = 0;
2588c2ecf20Sopenharmony_ci
2598c2ecf20Sopenharmony_ci	if (!eptdev->ept)
2608c2ecf20Sopenharmony_ci		return EPOLLERR;
2618c2ecf20Sopenharmony_ci
2628c2ecf20Sopenharmony_ci	poll_wait(filp, &eptdev->readq, wait);
2638c2ecf20Sopenharmony_ci
2648c2ecf20Sopenharmony_ci	if (!skb_queue_empty(&eptdev->queue))
2658c2ecf20Sopenharmony_ci		mask |= EPOLLIN | EPOLLRDNORM;
2668c2ecf20Sopenharmony_ci
2678c2ecf20Sopenharmony_ci	mask |= rpmsg_poll(eptdev->ept, filp, wait);
2688c2ecf20Sopenharmony_ci
2698c2ecf20Sopenharmony_ci	return mask;
2708c2ecf20Sopenharmony_ci}
2718c2ecf20Sopenharmony_ci
2728c2ecf20Sopenharmony_cistatic long rpmsg_eptdev_ioctl(struct file *fp, unsigned int cmd,
2738c2ecf20Sopenharmony_ci			       unsigned long arg)
2748c2ecf20Sopenharmony_ci{
2758c2ecf20Sopenharmony_ci	struct rpmsg_eptdev *eptdev = fp->private_data;
2768c2ecf20Sopenharmony_ci
2778c2ecf20Sopenharmony_ci	if (cmd != RPMSG_DESTROY_EPT_IOCTL)
2788c2ecf20Sopenharmony_ci		return -EINVAL;
2798c2ecf20Sopenharmony_ci
2808c2ecf20Sopenharmony_ci	return rpmsg_eptdev_destroy(&eptdev->dev, NULL);
2818c2ecf20Sopenharmony_ci}
2828c2ecf20Sopenharmony_ci
2838c2ecf20Sopenharmony_cistatic const struct file_operations rpmsg_eptdev_fops = {
2848c2ecf20Sopenharmony_ci	.owner = THIS_MODULE,
2858c2ecf20Sopenharmony_ci	.open = rpmsg_eptdev_open,
2868c2ecf20Sopenharmony_ci	.release = rpmsg_eptdev_release,
2878c2ecf20Sopenharmony_ci	.read_iter = rpmsg_eptdev_read_iter,
2888c2ecf20Sopenharmony_ci	.write_iter = rpmsg_eptdev_write_iter,
2898c2ecf20Sopenharmony_ci	.poll = rpmsg_eptdev_poll,
2908c2ecf20Sopenharmony_ci	.unlocked_ioctl = rpmsg_eptdev_ioctl,
2918c2ecf20Sopenharmony_ci	.compat_ioctl = compat_ptr_ioctl,
2928c2ecf20Sopenharmony_ci};
2938c2ecf20Sopenharmony_ci
2948c2ecf20Sopenharmony_cistatic ssize_t name_show(struct device *dev, struct device_attribute *attr,
2958c2ecf20Sopenharmony_ci			 char *buf)
2968c2ecf20Sopenharmony_ci{
2978c2ecf20Sopenharmony_ci	struct rpmsg_eptdev *eptdev = dev_get_drvdata(dev);
2988c2ecf20Sopenharmony_ci
2998c2ecf20Sopenharmony_ci	return sprintf(buf, "%s\n", eptdev->chinfo.name);
3008c2ecf20Sopenharmony_ci}
3018c2ecf20Sopenharmony_cistatic DEVICE_ATTR_RO(name);
3028c2ecf20Sopenharmony_ci
3038c2ecf20Sopenharmony_cistatic ssize_t src_show(struct device *dev, struct device_attribute *attr,
3048c2ecf20Sopenharmony_ci			 char *buf)
3058c2ecf20Sopenharmony_ci{
3068c2ecf20Sopenharmony_ci	struct rpmsg_eptdev *eptdev = dev_get_drvdata(dev);
3078c2ecf20Sopenharmony_ci
3088c2ecf20Sopenharmony_ci	return sprintf(buf, "%d\n", eptdev->chinfo.src);
3098c2ecf20Sopenharmony_ci}
3108c2ecf20Sopenharmony_cistatic DEVICE_ATTR_RO(src);
3118c2ecf20Sopenharmony_ci
3128c2ecf20Sopenharmony_cistatic ssize_t dst_show(struct device *dev, struct device_attribute *attr,
3138c2ecf20Sopenharmony_ci			 char *buf)
3148c2ecf20Sopenharmony_ci{
3158c2ecf20Sopenharmony_ci	struct rpmsg_eptdev *eptdev = dev_get_drvdata(dev);
3168c2ecf20Sopenharmony_ci
3178c2ecf20Sopenharmony_ci	return sprintf(buf, "%d\n", eptdev->chinfo.dst);
3188c2ecf20Sopenharmony_ci}
3198c2ecf20Sopenharmony_cistatic DEVICE_ATTR_RO(dst);
3208c2ecf20Sopenharmony_ci
3218c2ecf20Sopenharmony_cistatic struct attribute *rpmsg_eptdev_attrs[] = {
3228c2ecf20Sopenharmony_ci	&dev_attr_name.attr,
3238c2ecf20Sopenharmony_ci	&dev_attr_src.attr,
3248c2ecf20Sopenharmony_ci	&dev_attr_dst.attr,
3258c2ecf20Sopenharmony_ci	NULL
3268c2ecf20Sopenharmony_ci};
3278c2ecf20Sopenharmony_ciATTRIBUTE_GROUPS(rpmsg_eptdev);
3288c2ecf20Sopenharmony_ci
3298c2ecf20Sopenharmony_cistatic void rpmsg_eptdev_release_device(struct device *dev)
3308c2ecf20Sopenharmony_ci{
3318c2ecf20Sopenharmony_ci	struct rpmsg_eptdev *eptdev = dev_to_eptdev(dev);
3328c2ecf20Sopenharmony_ci
3338c2ecf20Sopenharmony_ci	ida_simple_remove(&rpmsg_ept_ida, dev->id);
3348c2ecf20Sopenharmony_ci	ida_simple_remove(&rpmsg_minor_ida, MINOR(eptdev->dev.devt));
3358c2ecf20Sopenharmony_ci	kfree(eptdev);
3368c2ecf20Sopenharmony_ci}
3378c2ecf20Sopenharmony_ci
3388c2ecf20Sopenharmony_cistatic int rpmsg_eptdev_create(struct rpmsg_ctrldev *ctrldev,
3398c2ecf20Sopenharmony_ci			       struct rpmsg_channel_info chinfo)
3408c2ecf20Sopenharmony_ci{
3418c2ecf20Sopenharmony_ci	struct rpmsg_device *rpdev = ctrldev->rpdev;
3428c2ecf20Sopenharmony_ci	struct rpmsg_eptdev *eptdev;
3438c2ecf20Sopenharmony_ci	struct device *dev;
3448c2ecf20Sopenharmony_ci	int ret;
3458c2ecf20Sopenharmony_ci
3468c2ecf20Sopenharmony_ci	eptdev = kzalloc(sizeof(*eptdev), GFP_KERNEL);
3478c2ecf20Sopenharmony_ci	if (!eptdev)
3488c2ecf20Sopenharmony_ci		return -ENOMEM;
3498c2ecf20Sopenharmony_ci
3508c2ecf20Sopenharmony_ci	dev = &eptdev->dev;
3518c2ecf20Sopenharmony_ci	eptdev->rpdev = rpdev;
3528c2ecf20Sopenharmony_ci	eptdev->chinfo = chinfo;
3538c2ecf20Sopenharmony_ci
3548c2ecf20Sopenharmony_ci	mutex_init(&eptdev->ept_lock);
3558c2ecf20Sopenharmony_ci	spin_lock_init(&eptdev->queue_lock);
3568c2ecf20Sopenharmony_ci	skb_queue_head_init(&eptdev->queue);
3578c2ecf20Sopenharmony_ci	init_waitqueue_head(&eptdev->readq);
3588c2ecf20Sopenharmony_ci
3598c2ecf20Sopenharmony_ci	device_initialize(dev);
3608c2ecf20Sopenharmony_ci	dev->class = rpmsg_class;
3618c2ecf20Sopenharmony_ci	dev->parent = &ctrldev->dev;
3628c2ecf20Sopenharmony_ci	dev->groups = rpmsg_eptdev_groups;
3638c2ecf20Sopenharmony_ci	dev_set_drvdata(dev, eptdev);
3648c2ecf20Sopenharmony_ci
3658c2ecf20Sopenharmony_ci	cdev_init(&eptdev->cdev, &rpmsg_eptdev_fops);
3668c2ecf20Sopenharmony_ci	eptdev->cdev.owner = THIS_MODULE;
3678c2ecf20Sopenharmony_ci
3688c2ecf20Sopenharmony_ci	ret = ida_simple_get(&rpmsg_minor_ida, 0, RPMSG_DEV_MAX, GFP_KERNEL);
3698c2ecf20Sopenharmony_ci	if (ret < 0)
3708c2ecf20Sopenharmony_ci		goto free_eptdev;
3718c2ecf20Sopenharmony_ci	dev->devt = MKDEV(MAJOR(rpmsg_major), ret);
3728c2ecf20Sopenharmony_ci
3738c2ecf20Sopenharmony_ci	ret = ida_simple_get(&rpmsg_ept_ida, 0, 0, GFP_KERNEL);
3748c2ecf20Sopenharmony_ci	if (ret < 0)
3758c2ecf20Sopenharmony_ci		goto free_minor_ida;
3768c2ecf20Sopenharmony_ci	dev->id = ret;
3778c2ecf20Sopenharmony_ci	dev_set_name(dev, "rpmsg%d", ret);
3788c2ecf20Sopenharmony_ci
3798c2ecf20Sopenharmony_ci	ret = cdev_device_add(&eptdev->cdev, &eptdev->dev);
3808c2ecf20Sopenharmony_ci	if (ret)
3818c2ecf20Sopenharmony_ci		goto free_ept_ida;
3828c2ecf20Sopenharmony_ci
3838c2ecf20Sopenharmony_ci	/* We can now rely on the release function for cleanup */
3848c2ecf20Sopenharmony_ci	dev->release = rpmsg_eptdev_release_device;
3858c2ecf20Sopenharmony_ci
3868c2ecf20Sopenharmony_ci	return ret;
3878c2ecf20Sopenharmony_ci
3888c2ecf20Sopenharmony_cifree_ept_ida:
3898c2ecf20Sopenharmony_ci	ida_simple_remove(&rpmsg_ept_ida, dev->id);
3908c2ecf20Sopenharmony_cifree_minor_ida:
3918c2ecf20Sopenharmony_ci	ida_simple_remove(&rpmsg_minor_ida, MINOR(dev->devt));
3928c2ecf20Sopenharmony_cifree_eptdev:
3938c2ecf20Sopenharmony_ci	put_device(dev);
3948c2ecf20Sopenharmony_ci	kfree(eptdev);
3958c2ecf20Sopenharmony_ci
3968c2ecf20Sopenharmony_ci	return ret;
3978c2ecf20Sopenharmony_ci}
3988c2ecf20Sopenharmony_ci
3998c2ecf20Sopenharmony_cistatic int rpmsg_ctrldev_open(struct inode *inode, struct file *filp)
4008c2ecf20Sopenharmony_ci{
4018c2ecf20Sopenharmony_ci	struct rpmsg_ctrldev *ctrldev = cdev_to_ctrldev(inode->i_cdev);
4028c2ecf20Sopenharmony_ci
4038c2ecf20Sopenharmony_ci	get_device(&ctrldev->dev);
4048c2ecf20Sopenharmony_ci	filp->private_data = ctrldev;
4058c2ecf20Sopenharmony_ci
4068c2ecf20Sopenharmony_ci	return 0;
4078c2ecf20Sopenharmony_ci}
4088c2ecf20Sopenharmony_ci
4098c2ecf20Sopenharmony_cistatic int rpmsg_ctrldev_release(struct inode *inode, struct file *filp)
4108c2ecf20Sopenharmony_ci{
4118c2ecf20Sopenharmony_ci	struct rpmsg_ctrldev *ctrldev = cdev_to_ctrldev(inode->i_cdev);
4128c2ecf20Sopenharmony_ci
4138c2ecf20Sopenharmony_ci	put_device(&ctrldev->dev);
4148c2ecf20Sopenharmony_ci
4158c2ecf20Sopenharmony_ci	return 0;
4168c2ecf20Sopenharmony_ci}
4178c2ecf20Sopenharmony_ci
4188c2ecf20Sopenharmony_cistatic long rpmsg_ctrldev_ioctl(struct file *fp, unsigned int cmd,
4198c2ecf20Sopenharmony_ci				unsigned long arg)
4208c2ecf20Sopenharmony_ci{
4218c2ecf20Sopenharmony_ci	struct rpmsg_ctrldev *ctrldev = fp->private_data;
4228c2ecf20Sopenharmony_ci	void __user *argp = (void __user *)arg;
4238c2ecf20Sopenharmony_ci	struct rpmsg_endpoint_info eptinfo;
4248c2ecf20Sopenharmony_ci	struct rpmsg_channel_info chinfo;
4258c2ecf20Sopenharmony_ci
4268c2ecf20Sopenharmony_ci	if (cmd != RPMSG_CREATE_EPT_IOCTL)
4278c2ecf20Sopenharmony_ci		return -EINVAL;
4288c2ecf20Sopenharmony_ci
4298c2ecf20Sopenharmony_ci	if (copy_from_user(&eptinfo, argp, sizeof(eptinfo)))
4308c2ecf20Sopenharmony_ci		return -EFAULT;
4318c2ecf20Sopenharmony_ci
4328c2ecf20Sopenharmony_ci	memcpy(chinfo.name, eptinfo.name, RPMSG_NAME_SIZE);
4338c2ecf20Sopenharmony_ci	chinfo.name[RPMSG_NAME_SIZE-1] = '\0';
4348c2ecf20Sopenharmony_ci	chinfo.src = eptinfo.src;
4358c2ecf20Sopenharmony_ci	chinfo.dst = eptinfo.dst;
4368c2ecf20Sopenharmony_ci
4378c2ecf20Sopenharmony_ci	return rpmsg_eptdev_create(ctrldev, chinfo);
4388c2ecf20Sopenharmony_ci};
4398c2ecf20Sopenharmony_ci
4408c2ecf20Sopenharmony_cistatic const struct file_operations rpmsg_ctrldev_fops = {
4418c2ecf20Sopenharmony_ci	.owner = THIS_MODULE,
4428c2ecf20Sopenharmony_ci	.open = rpmsg_ctrldev_open,
4438c2ecf20Sopenharmony_ci	.release = rpmsg_ctrldev_release,
4448c2ecf20Sopenharmony_ci	.unlocked_ioctl = rpmsg_ctrldev_ioctl,
4458c2ecf20Sopenharmony_ci	.compat_ioctl = compat_ptr_ioctl,
4468c2ecf20Sopenharmony_ci};
4478c2ecf20Sopenharmony_ci
4488c2ecf20Sopenharmony_cistatic void rpmsg_ctrldev_release_device(struct device *dev)
4498c2ecf20Sopenharmony_ci{
4508c2ecf20Sopenharmony_ci	struct rpmsg_ctrldev *ctrldev = dev_to_ctrldev(dev);
4518c2ecf20Sopenharmony_ci
4528c2ecf20Sopenharmony_ci	ida_simple_remove(&rpmsg_ctrl_ida, dev->id);
4538c2ecf20Sopenharmony_ci	ida_simple_remove(&rpmsg_minor_ida, MINOR(dev->devt));
4548c2ecf20Sopenharmony_ci	kfree(ctrldev);
4558c2ecf20Sopenharmony_ci}
4568c2ecf20Sopenharmony_ci
4578c2ecf20Sopenharmony_cistatic int rpmsg_chrdev_probe(struct rpmsg_device *rpdev)
4588c2ecf20Sopenharmony_ci{
4598c2ecf20Sopenharmony_ci	struct rpmsg_ctrldev *ctrldev;
4608c2ecf20Sopenharmony_ci	struct device *dev;
4618c2ecf20Sopenharmony_ci	int ret;
4628c2ecf20Sopenharmony_ci
4638c2ecf20Sopenharmony_ci	ctrldev = kzalloc(sizeof(*ctrldev), GFP_KERNEL);
4648c2ecf20Sopenharmony_ci	if (!ctrldev)
4658c2ecf20Sopenharmony_ci		return -ENOMEM;
4668c2ecf20Sopenharmony_ci
4678c2ecf20Sopenharmony_ci	ctrldev->rpdev = rpdev;
4688c2ecf20Sopenharmony_ci
4698c2ecf20Sopenharmony_ci	dev = &ctrldev->dev;
4708c2ecf20Sopenharmony_ci	device_initialize(dev);
4718c2ecf20Sopenharmony_ci	dev->parent = &rpdev->dev;
4728c2ecf20Sopenharmony_ci	dev->class = rpmsg_class;
4738c2ecf20Sopenharmony_ci
4748c2ecf20Sopenharmony_ci	cdev_init(&ctrldev->cdev, &rpmsg_ctrldev_fops);
4758c2ecf20Sopenharmony_ci	ctrldev->cdev.owner = THIS_MODULE;
4768c2ecf20Sopenharmony_ci
4778c2ecf20Sopenharmony_ci	ret = ida_simple_get(&rpmsg_minor_ida, 0, RPMSG_DEV_MAX, GFP_KERNEL);
4788c2ecf20Sopenharmony_ci	if (ret < 0)
4798c2ecf20Sopenharmony_ci		goto free_ctrldev;
4808c2ecf20Sopenharmony_ci	dev->devt = MKDEV(MAJOR(rpmsg_major), ret);
4818c2ecf20Sopenharmony_ci
4828c2ecf20Sopenharmony_ci	ret = ida_simple_get(&rpmsg_ctrl_ida, 0, 0, GFP_KERNEL);
4838c2ecf20Sopenharmony_ci	if (ret < 0)
4848c2ecf20Sopenharmony_ci		goto free_minor_ida;
4858c2ecf20Sopenharmony_ci	dev->id = ret;
4868c2ecf20Sopenharmony_ci	dev_set_name(&ctrldev->dev, "rpmsg_ctrl%d", ret);
4878c2ecf20Sopenharmony_ci
4888c2ecf20Sopenharmony_ci	ret = cdev_device_add(&ctrldev->cdev, &ctrldev->dev);
4898c2ecf20Sopenharmony_ci	if (ret)
4908c2ecf20Sopenharmony_ci		goto free_ctrl_ida;
4918c2ecf20Sopenharmony_ci
4928c2ecf20Sopenharmony_ci	/* We can now rely on the release function for cleanup */
4938c2ecf20Sopenharmony_ci	dev->release = rpmsg_ctrldev_release_device;
4948c2ecf20Sopenharmony_ci
4958c2ecf20Sopenharmony_ci	dev_set_drvdata(&rpdev->dev, ctrldev);
4968c2ecf20Sopenharmony_ci
4978c2ecf20Sopenharmony_ci	return ret;
4988c2ecf20Sopenharmony_ci
4998c2ecf20Sopenharmony_cifree_ctrl_ida:
5008c2ecf20Sopenharmony_ci	ida_simple_remove(&rpmsg_ctrl_ida, dev->id);
5018c2ecf20Sopenharmony_cifree_minor_ida:
5028c2ecf20Sopenharmony_ci	ida_simple_remove(&rpmsg_minor_ida, MINOR(dev->devt));
5038c2ecf20Sopenharmony_cifree_ctrldev:
5048c2ecf20Sopenharmony_ci	put_device(dev);
5058c2ecf20Sopenharmony_ci	kfree(ctrldev);
5068c2ecf20Sopenharmony_ci
5078c2ecf20Sopenharmony_ci	return ret;
5088c2ecf20Sopenharmony_ci}
5098c2ecf20Sopenharmony_ci
5108c2ecf20Sopenharmony_cistatic void rpmsg_chrdev_remove(struct rpmsg_device *rpdev)
5118c2ecf20Sopenharmony_ci{
5128c2ecf20Sopenharmony_ci	struct rpmsg_ctrldev *ctrldev = dev_get_drvdata(&rpdev->dev);
5138c2ecf20Sopenharmony_ci	int ret;
5148c2ecf20Sopenharmony_ci
5158c2ecf20Sopenharmony_ci	/* Destroy all endpoints */
5168c2ecf20Sopenharmony_ci	ret = device_for_each_child(&ctrldev->dev, NULL, rpmsg_eptdev_destroy);
5178c2ecf20Sopenharmony_ci	if (ret)
5188c2ecf20Sopenharmony_ci		dev_warn(&rpdev->dev, "failed to nuke endpoints: %d\n", ret);
5198c2ecf20Sopenharmony_ci
5208c2ecf20Sopenharmony_ci	cdev_device_del(&ctrldev->cdev, &ctrldev->dev);
5218c2ecf20Sopenharmony_ci	put_device(&ctrldev->dev);
5228c2ecf20Sopenharmony_ci}
5238c2ecf20Sopenharmony_ci
5248c2ecf20Sopenharmony_cistatic struct rpmsg_driver rpmsg_chrdev_driver = {
5258c2ecf20Sopenharmony_ci	.probe = rpmsg_chrdev_probe,
5268c2ecf20Sopenharmony_ci	.remove = rpmsg_chrdev_remove,
5278c2ecf20Sopenharmony_ci	.drv = {
5288c2ecf20Sopenharmony_ci		.name = "rpmsg_chrdev",
5298c2ecf20Sopenharmony_ci	},
5308c2ecf20Sopenharmony_ci};
5318c2ecf20Sopenharmony_ci
5328c2ecf20Sopenharmony_cistatic int rpmsg_char_init(void)
5338c2ecf20Sopenharmony_ci{
5348c2ecf20Sopenharmony_ci	int ret;
5358c2ecf20Sopenharmony_ci
5368c2ecf20Sopenharmony_ci	ret = alloc_chrdev_region(&rpmsg_major, 0, RPMSG_DEV_MAX, "rpmsg");
5378c2ecf20Sopenharmony_ci	if (ret < 0) {
5388c2ecf20Sopenharmony_ci		pr_err("rpmsg: failed to allocate char dev region\n");
5398c2ecf20Sopenharmony_ci		return ret;
5408c2ecf20Sopenharmony_ci	}
5418c2ecf20Sopenharmony_ci
5428c2ecf20Sopenharmony_ci	rpmsg_class = class_create(THIS_MODULE, "rpmsg");
5438c2ecf20Sopenharmony_ci	if (IS_ERR(rpmsg_class)) {
5448c2ecf20Sopenharmony_ci		pr_err("failed to create rpmsg class\n");
5458c2ecf20Sopenharmony_ci		unregister_chrdev_region(rpmsg_major, RPMSG_DEV_MAX);
5468c2ecf20Sopenharmony_ci		return PTR_ERR(rpmsg_class);
5478c2ecf20Sopenharmony_ci	}
5488c2ecf20Sopenharmony_ci
5498c2ecf20Sopenharmony_ci	ret = register_rpmsg_driver(&rpmsg_chrdev_driver);
5508c2ecf20Sopenharmony_ci	if (ret < 0) {
5518c2ecf20Sopenharmony_ci		pr_err("rpmsgchr: failed to register rpmsg driver\n");
5528c2ecf20Sopenharmony_ci		class_destroy(rpmsg_class);
5538c2ecf20Sopenharmony_ci		unregister_chrdev_region(rpmsg_major, RPMSG_DEV_MAX);
5548c2ecf20Sopenharmony_ci	}
5558c2ecf20Sopenharmony_ci
5568c2ecf20Sopenharmony_ci	return ret;
5578c2ecf20Sopenharmony_ci}
5588c2ecf20Sopenharmony_cipostcore_initcall(rpmsg_char_init);
5598c2ecf20Sopenharmony_ci
5608c2ecf20Sopenharmony_cistatic void rpmsg_chrdev_exit(void)
5618c2ecf20Sopenharmony_ci{
5628c2ecf20Sopenharmony_ci	unregister_rpmsg_driver(&rpmsg_chrdev_driver);
5638c2ecf20Sopenharmony_ci	class_destroy(rpmsg_class);
5648c2ecf20Sopenharmony_ci	unregister_chrdev_region(rpmsg_major, RPMSG_DEV_MAX);
5658c2ecf20Sopenharmony_ci}
5668c2ecf20Sopenharmony_cimodule_exit(rpmsg_chrdev_exit);
5678c2ecf20Sopenharmony_ci
5688c2ecf20Sopenharmony_ciMODULE_ALIAS("rpmsg:rpmsg_chrdev");
5698c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
570