xref: /openbmc/linux/drivers/rpmsg/rpmsg_char.c (revision c900529f3d9161bfde5cca0754f83b4d3c3e0220)
1136200f4SSuman Anna // SPDX-License-Identifier: GPL-2.0
2c0cdc19fSBjorn Andersson /*
369265bc1SArnaud Pouliquen  * Copyright (C) 2022, STMicroelectronics
4c0cdc19fSBjorn Andersson  * Copyright (c) 2016, Linaro Ltd.
5c0cdc19fSBjorn Andersson  * Copyright (c) 2012, Michal Simek <monstr@monstr.eu>
6c0cdc19fSBjorn Andersson  * Copyright (c) 2012, PetaLogix
7c0cdc19fSBjorn Andersson  * Copyright (c) 2011, Texas Instruments, Inc.
8c0cdc19fSBjorn Andersson  * Copyright (c) 2011, Google, Inc.
9c0cdc19fSBjorn Andersson  *
10c0cdc19fSBjorn Andersson  * Based on rpmsg performance statistics driver by Michal Simek, which in turn
11c0cdc19fSBjorn Andersson  * was based on TI & Google OMX rpmsg driver.
12c0cdc19fSBjorn Andersson  */
13c5727244SArnaud Pouliquen 
14c5727244SArnaud Pouliquen #define pr_fmt(fmt)	KBUILD_MODNAME ": " fmt
15c5727244SArnaud Pouliquen 
16c0cdc19fSBjorn Andersson #include <linux/cdev.h>
17c0cdc19fSBjorn Andersson #include <linux/device.h>
18c0cdc19fSBjorn Andersson #include <linux/fs.h>
19c0cdc19fSBjorn Andersson #include <linux/idr.h>
20c0cdc19fSBjorn Andersson #include <linux/kernel.h>
21c0cdc19fSBjorn Andersson #include <linux/module.h>
22c0cdc19fSBjorn Andersson #include <linux/poll.h>
23c0cdc19fSBjorn Andersson #include <linux/rpmsg.h>
24c0cdc19fSBjorn Andersson #include <linux/skbuff.h>
25c0cdc19fSBjorn Andersson #include <linux/slab.h>
26c0cdc19fSBjorn Andersson #include <linux/uaccess.h>
27c0cdc19fSBjorn Andersson #include <uapi/linux/rpmsg.h>
28c0cdc19fSBjorn Andersson 
2969265bc1SArnaud Pouliquen #include "rpmsg_char.h"
30608edd96SArnaud Pouliquen #include "rpmsg_internal.h"
3169265bc1SArnaud Pouliquen 
32c0cdc19fSBjorn Andersson #define RPMSG_DEV_MAX	(MINORMASK + 1)
33c0cdc19fSBjorn Andersson 
34c0cdc19fSBjorn Andersson static dev_t rpmsg_major;
35c0cdc19fSBjorn Andersson 
36c0cdc19fSBjorn Andersson static DEFINE_IDA(rpmsg_ept_ida);
37c0cdc19fSBjorn Andersson static DEFINE_IDA(rpmsg_minor_ida);
38c0cdc19fSBjorn Andersson 
39c0cdc19fSBjorn Andersson #define dev_to_eptdev(dev) container_of(dev, struct rpmsg_eptdev, dev)
40c0cdc19fSBjorn Andersson #define cdev_to_eptdev(i_cdev) container_of(i_cdev, struct rpmsg_eptdev, cdev)
41c0cdc19fSBjorn Andersson 
42c0cdc19fSBjorn Andersson /**
43c0cdc19fSBjorn Andersson  * struct rpmsg_eptdev - endpoint device context
44c0cdc19fSBjorn Andersson  * @dev:	endpoint device
45c0cdc19fSBjorn Andersson  * @cdev:	cdev for the endpoint device
46c0cdc19fSBjorn Andersson  * @rpdev:	underlaying rpmsg device
47c0cdc19fSBjorn Andersson  * @chinfo:	info used to open the endpoint
48c0cdc19fSBjorn Andersson  * @ept_lock:	synchronization of @ept modifications
49c0cdc19fSBjorn Andersson  * @ept:	rpmsg endpoint reference, when open
50c0cdc19fSBjorn Andersson  * @queue_lock:	synchronization of @queue operations
51c0cdc19fSBjorn Andersson  * @queue:	incoming message queue
52c0cdc19fSBjorn Andersson  * @readq:	wait object for incoming queue
53bea9b79cSArnaud Pouliquen  * @default_ept: set to channel default endpoint if the default endpoint should be re-used
54bea9b79cSArnaud Pouliquen  *              on device open to prevent endpoint address update.
55*5550201cSChris Lew  * remote_flow_restricted: to indicate if the remote has requested for flow to be limited
56*5550201cSChris Lew  * remote_flow_updated: to indicate if the flow control has been requested
57c0cdc19fSBjorn Andersson  */
58c0cdc19fSBjorn Andersson struct rpmsg_eptdev {
59c0cdc19fSBjorn Andersson 	struct device dev;
60c0cdc19fSBjorn Andersson 	struct cdev cdev;
61c0cdc19fSBjorn Andersson 
62c0cdc19fSBjorn Andersson 	struct rpmsg_device *rpdev;
63c0cdc19fSBjorn Andersson 	struct rpmsg_channel_info chinfo;
64c0cdc19fSBjorn Andersson 
65c0cdc19fSBjorn Andersson 	struct mutex ept_lock;
66c0cdc19fSBjorn Andersson 	struct rpmsg_endpoint *ept;
67bea9b79cSArnaud Pouliquen 	struct rpmsg_endpoint *default_ept;
68c0cdc19fSBjorn Andersson 
69c0cdc19fSBjorn Andersson 	spinlock_t queue_lock;
70c0cdc19fSBjorn Andersson 	struct sk_buff_head queue;
71c0cdc19fSBjorn Andersson 	wait_queue_head_t readq;
72bea9b79cSArnaud Pouliquen 
73*5550201cSChris Lew 	bool remote_flow_restricted;
74*5550201cSChris Lew 	bool remote_flow_updated;
75c0cdc19fSBjorn Andersson };
76c0cdc19fSBjorn Andersson 
rpmsg_chrdev_eptdev_destroy(struct device * dev,void * data)7769265bc1SArnaud Pouliquen int rpmsg_chrdev_eptdev_destroy(struct device *dev, void *data)
78c0cdc19fSBjorn Andersson {
79c0cdc19fSBjorn Andersson 	struct rpmsg_eptdev *eptdev = dev_to_eptdev(dev);
80c0cdc19fSBjorn Andersson 
81c0cdc19fSBjorn Andersson 	mutex_lock(&eptdev->ept_lock);
8217b88a20SDeepak Kumar Singh 	eptdev->rpdev = NULL;
83c0cdc19fSBjorn Andersson 	if (eptdev->ept) {
84467233a4SShengjiu Wang 		/* The default endpoint is released by the rpmsg core */
85467233a4SShengjiu Wang 		if (!eptdev->default_ept)
86c0cdc19fSBjorn Andersson 			rpmsg_destroy_ept(eptdev->ept);
87c0cdc19fSBjorn Andersson 		eptdev->ept = NULL;
88c0cdc19fSBjorn Andersson 	}
89c0cdc19fSBjorn Andersson 	mutex_unlock(&eptdev->ept_lock);
90c0cdc19fSBjorn Andersson 
91c0cdc19fSBjorn Andersson 	/* wake up any blocked readers */
92c0cdc19fSBjorn Andersson 	wake_up_interruptible(&eptdev->readq);
93c0cdc19fSBjorn Andersson 
947a534ae8SMatthias Kaehlcke 	cdev_device_del(&eptdev->cdev, &eptdev->dev);
95c0cdc19fSBjorn Andersson 	put_device(&eptdev->dev);
96c0cdc19fSBjorn Andersson 
97c0cdc19fSBjorn Andersson 	return 0;
98c0cdc19fSBjorn Andersson }
9969265bc1SArnaud Pouliquen EXPORT_SYMBOL(rpmsg_chrdev_eptdev_destroy);
100c0cdc19fSBjorn Andersson 
rpmsg_ept_cb(struct rpmsg_device * rpdev,void * buf,int len,void * priv,u32 addr)101c0cdc19fSBjorn Andersson static int rpmsg_ept_cb(struct rpmsg_device *rpdev, void *buf, int len,
102c0cdc19fSBjorn Andersson 			void *priv, u32 addr)
103c0cdc19fSBjorn Andersson {
104c0cdc19fSBjorn Andersson 	struct rpmsg_eptdev *eptdev = priv;
105c0cdc19fSBjorn Andersson 	struct sk_buff *skb;
106c0cdc19fSBjorn Andersson 
107c0cdc19fSBjorn Andersson 	skb = alloc_skb(len, GFP_ATOMIC);
108c0cdc19fSBjorn Andersson 	if (!skb)
109c0cdc19fSBjorn Andersson 		return -ENOMEM;
110c0cdc19fSBjorn Andersson 
11159ae1d12SJohannes Berg 	skb_put_data(skb, buf, len);
112c0cdc19fSBjorn Andersson 
113c0cdc19fSBjorn Andersson 	spin_lock(&eptdev->queue_lock);
114c0cdc19fSBjorn Andersson 	skb_queue_tail(&eptdev->queue, skb);
115c0cdc19fSBjorn Andersson 	spin_unlock(&eptdev->queue_lock);
116c0cdc19fSBjorn Andersson 
117c0cdc19fSBjorn Andersson 	/* wake up any blocking processes, waiting for new data */
118c0cdc19fSBjorn Andersson 	wake_up_interruptible(&eptdev->readq);
119c0cdc19fSBjorn Andersson 
120c0cdc19fSBjorn Andersson 	return 0;
121c0cdc19fSBjorn Andersson }
122c0cdc19fSBjorn Andersson 
rpmsg_ept_flow_cb(struct rpmsg_device * rpdev,void * priv,bool enable)123*5550201cSChris Lew static int rpmsg_ept_flow_cb(struct rpmsg_device *rpdev, void *priv, bool enable)
124*5550201cSChris Lew {
125*5550201cSChris Lew 	struct rpmsg_eptdev *eptdev = priv;
126*5550201cSChris Lew 
127*5550201cSChris Lew 	eptdev->remote_flow_restricted = enable;
128*5550201cSChris Lew 	eptdev->remote_flow_updated = true;
129*5550201cSChris Lew 
130*5550201cSChris Lew 	wake_up_interruptible(&eptdev->readq);
131*5550201cSChris Lew 
132*5550201cSChris Lew 	return 0;
133*5550201cSChris Lew }
134*5550201cSChris Lew 
rpmsg_eptdev_open(struct inode * inode,struct file * filp)135c0cdc19fSBjorn Andersson static int rpmsg_eptdev_open(struct inode *inode, struct file *filp)
136c0cdc19fSBjorn Andersson {
137c0cdc19fSBjorn Andersson 	struct rpmsg_eptdev *eptdev = cdev_to_eptdev(inode->i_cdev);
138c0cdc19fSBjorn Andersson 	struct rpmsg_endpoint *ept;
139c0cdc19fSBjorn Andersson 	struct rpmsg_device *rpdev = eptdev->rpdev;
140c0cdc19fSBjorn Andersson 	struct device *dev = &eptdev->dev;
141c0cdc19fSBjorn Andersson 
142abe13e9aSShengjiu Wang 	mutex_lock(&eptdev->ept_lock);
143abe13e9aSShengjiu Wang 	if (eptdev->ept) {
144abe13e9aSShengjiu Wang 		mutex_unlock(&eptdev->ept_lock);
145964e8bedSArnaud Pouliquen 		return -EBUSY;
146abe13e9aSShengjiu Wang 	}
147964e8bedSArnaud Pouliquen 
14817b88a20SDeepak Kumar Singh 	if (!eptdev->rpdev) {
14917b88a20SDeepak Kumar Singh 		mutex_unlock(&eptdev->ept_lock);
15017b88a20SDeepak Kumar Singh 		return -ENETRESET;
15117b88a20SDeepak Kumar Singh 	}
15217b88a20SDeepak Kumar Singh 
153c0cdc19fSBjorn Andersson 	get_device(dev);
154c0cdc19fSBjorn Andersson 
155bea9b79cSArnaud Pouliquen 	/*
156bea9b79cSArnaud Pouliquen 	 * If the default_ept is set, the rpmsg device default endpoint is used.
157bea9b79cSArnaud Pouliquen 	 * Else a new endpoint is created on open that will be destroyed on release.
158bea9b79cSArnaud Pouliquen 	 */
159bea9b79cSArnaud Pouliquen 	if (eptdev->default_ept)
160bea9b79cSArnaud Pouliquen 		ept = eptdev->default_ept;
161bea9b79cSArnaud Pouliquen 	else
162c0cdc19fSBjorn Andersson 		ept = rpmsg_create_ept(rpdev, rpmsg_ept_cb, eptdev, eptdev->chinfo);
163bea9b79cSArnaud Pouliquen 
164c0cdc19fSBjorn Andersson 	if (!ept) {
165c0cdc19fSBjorn Andersson 		dev_err(dev, "failed to open %s\n", eptdev->chinfo.name);
166c0cdc19fSBjorn Andersson 		put_device(dev);
167abe13e9aSShengjiu Wang 		mutex_unlock(&eptdev->ept_lock);
168c0cdc19fSBjorn Andersson 		return -EINVAL;
169c0cdc19fSBjorn Andersson 	}
170c0cdc19fSBjorn Andersson 
171*5550201cSChris Lew 	ept->flow_cb = rpmsg_ept_flow_cb;
172c0cdc19fSBjorn Andersson 	eptdev->ept = ept;
173c0cdc19fSBjorn Andersson 	filp->private_data = eptdev;
174abe13e9aSShengjiu Wang 	mutex_unlock(&eptdev->ept_lock);
175c0cdc19fSBjorn Andersson 
176c0cdc19fSBjorn Andersson 	return 0;
177c0cdc19fSBjorn Andersson }
178c0cdc19fSBjorn Andersson 
rpmsg_eptdev_release(struct inode * inode,struct file * filp)179c0cdc19fSBjorn Andersson static int rpmsg_eptdev_release(struct inode *inode, struct file *filp)
180c0cdc19fSBjorn Andersson {
181c0cdc19fSBjorn Andersson 	struct rpmsg_eptdev *eptdev = cdev_to_eptdev(inode->i_cdev);
182c0cdc19fSBjorn Andersson 	struct device *dev = &eptdev->dev;
183c0cdc19fSBjorn Andersson 
184c0cdc19fSBjorn Andersson 	/* Close the endpoint, if it's not already destroyed by the parent */
185c0cdc19fSBjorn Andersson 	mutex_lock(&eptdev->ept_lock);
186c0cdc19fSBjorn Andersson 	if (eptdev->ept) {
187bea9b79cSArnaud Pouliquen 		if (!eptdev->default_ept)
188c0cdc19fSBjorn Andersson 			rpmsg_destroy_ept(eptdev->ept);
189c0cdc19fSBjorn Andersson 		eptdev->ept = NULL;
190c0cdc19fSBjorn Andersson 	}
191c0cdc19fSBjorn Andersson 	mutex_unlock(&eptdev->ept_lock);
192*5550201cSChris Lew 	eptdev->remote_flow_updated = false;
193c0cdc19fSBjorn Andersson 
194c0cdc19fSBjorn Andersson 	/* Discard all SKBs */
195bb06a5ceSChristophe JAILLET 	skb_queue_purge(&eptdev->queue);
196c0cdc19fSBjorn Andersson 
197c0cdc19fSBjorn Andersson 	put_device(dev);
198c0cdc19fSBjorn Andersson 
199c0cdc19fSBjorn Andersson 	return 0;
200c0cdc19fSBjorn Andersson }
201c0cdc19fSBjorn Andersson 
rpmsg_eptdev_read_iter(struct kiocb * iocb,struct iov_iter * to)202ccf45b18SBjorn Andersson static ssize_t rpmsg_eptdev_read_iter(struct kiocb *iocb, struct iov_iter *to)
203c0cdc19fSBjorn Andersson {
204ccf45b18SBjorn Andersson 	struct file *filp = iocb->ki_filp;
205c0cdc19fSBjorn Andersson 	struct rpmsg_eptdev *eptdev = filp->private_data;
206c0cdc19fSBjorn Andersson 	unsigned long flags;
207c0cdc19fSBjorn Andersson 	struct sk_buff *skb;
208c0cdc19fSBjorn Andersson 	int use;
209c0cdc19fSBjorn Andersson 
210c0cdc19fSBjorn Andersson 	if (!eptdev->ept)
211c0cdc19fSBjorn Andersson 		return -EPIPE;
212c0cdc19fSBjorn Andersson 
213c0cdc19fSBjorn Andersson 	spin_lock_irqsave(&eptdev->queue_lock, flags);
214c0cdc19fSBjorn Andersson 
215c0cdc19fSBjorn Andersson 	/* Wait for data in the queue */
216c0cdc19fSBjorn Andersson 	if (skb_queue_empty(&eptdev->queue)) {
217c0cdc19fSBjorn Andersson 		spin_unlock_irqrestore(&eptdev->queue_lock, flags);
218c0cdc19fSBjorn Andersson 
219c0cdc19fSBjorn Andersson 		if (filp->f_flags & O_NONBLOCK)
220c0cdc19fSBjorn Andersson 			return -EAGAIN;
221c0cdc19fSBjorn Andersson 
222c0cdc19fSBjorn Andersson 		/* Wait until we get data or the endpoint goes away */
223c0cdc19fSBjorn Andersson 		if (wait_event_interruptible(eptdev->readq,
224c0cdc19fSBjorn Andersson 					     !skb_queue_empty(&eptdev->queue) ||
225c0cdc19fSBjorn Andersson 					     !eptdev->ept))
226c0cdc19fSBjorn Andersson 			return -ERESTARTSYS;
227c0cdc19fSBjorn Andersson 
228c0cdc19fSBjorn Andersson 		/* We lost the endpoint while waiting */
229c0cdc19fSBjorn Andersson 		if (!eptdev->ept)
230c0cdc19fSBjorn Andersson 			return -EPIPE;
231c0cdc19fSBjorn Andersson 
232c0cdc19fSBjorn Andersson 		spin_lock_irqsave(&eptdev->queue_lock, flags);
233c0cdc19fSBjorn Andersson 	}
234c0cdc19fSBjorn Andersson 
235c0cdc19fSBjorn Andersson 	skb = skb_dequeue(&eptdev->queue);
2360abd6bddSDan Carpenter 	spin_unlock_irqrestore(&eptdev->queue_lock, flags);
237c0cdc19fSBjorn Andersson 	if (!skb)
238c0cdc19fSBjorn Andersson 		return -EFAULT;
239c0cdc19fSBjorn Andersson 
240ccf45b18SBjorn Andersson 	use = min_t(size_t, iov_iter_count(to), skb->len);
241ccf45b18SBjorn Andersson 	if (copy_to_iter(skb->data, use, to) != use)
242c0cdc19fSBjorn Andersson 		use = -EFAULT;
243c0cdc19fSBjorn Andersson 
244c0cdc19fSBjorn Andersson 	kfree_skb(skb);
245c0cdc19fSBjorn Andersson 
246c0cdc19fSBjorn Andersson 	return use;
247c0cdc19fSBjorn Andersson }
248c0cdc19fSBjorn Andersson 
rpmsg_eptdev_write_iter(struct kiocb * iocb,struct iov_iter * from)249ccf45b18SBjorn Andersson static ssize_t rpmsg_eptdev_write_iter(struct kiocb *iocb,
250ccf45b18SBjorn Andersson 				       struct iov_iter *from)
251c0cdc19fSBjorn Andersson {
252ccf45b18SBjorn Andersson 	struct file *filp = iocb->ki_filp;
253c0cdc19fSBjorn Andersson 	struct rpmsg_eptdev *eptdev = filp->private_data;
254ccf45b18SBjorn Andersson 	size_t len = iov_iter_count(from);
255c0cdc19fSBjorn Andersson 	void *kbuf;
256c0cdc19fSBjorn Andersson 	int ret;
257c0cdc19fSBjorn Andersson 
258ccf45b18SBjorn Andersson 	kbuf = kzalloc(len, GFP_KERNEL);
259ccf45b18SBjorn Andersson 	if (!kbuf)
260ccf45b18SBjorn Andersson 		return -ENOMEM;
261ccf45b18SBjorn Andersson 
262bbe692e3SNavid Emamdoost 	if (!copy_from_iter_full(kbuf, len, from)) {
263bbe692e3SNavid Emamdoost 		ret = -EFAULT;
264bbe692e3SNavid Emamdoost 		goto free_kbuf;
265bbe692e3SNavid Emamdoost 	}
266c0cdc19fSBjorn Andersson 
267c0cdc19fSBjorn Andersson 	if (mutex_lock_interruptible(&eptdev->ept_lock)) {
268c0cdc19fSBjorn Andersson 		ret = -ERESTARTSYS;
269c0cdc19fSBjorn Andersson 		goto free_kbuf;
270c0cdc19fSBjorn Andersson 	}
271c0cdc19fSBjorn Andersson 
272c0cdc19fSBjorn Andersson 	if (!eptdev->ept) {
273c0cdc19fSBjorn Andersson 		ret = -EPIPE;
274c0cdc19fSBjorn Andersson 		goto unlock_eptdev;
275c0cdc19fSBjorn Andersson 	}
276c0cdc19fSBjorn Andersson 
277cbf58250STim Blechmann 	if (filp->f_flags & O_NONBLOCK) {
278b4ce7e2eSArnaud Pouliquen 		ret = rpmsg_trysendto(eptdev->ept, kbuf, len, eptdev->chinfo.dst);
279cbf58250STim Blechmann 		if (ret == -ENOMEM)
280cbf58250STim Blechmann 			ret = -EAGAIN;
281cbf58250STim Blechmann 	} else {
282b4ce7e2eSArnaud Pouliquen 		ret = rpmsg_sendto(eptdev->ept, kbuf, len, eptdev->chinfo.dst);
283cbf58250STim Blechmann 	}
284c0cdc19fSBjorn Andersson 
285c0cdc19fSBjorn Andersson unlock_eptdev:
286c0cdc19fSBjorn Andersson 	mutex_unlock(&eptdev->ept_lock);
287c0cdc19fSBjorn Andersson 
288c0cdc19fSBjorn Andersson free_kbuf:
289c0cdc19fSBjorn Andersson 	kfree(kbuf);
290c0cdc19fSBjorn Andersson 	return ret < 0 ? ret : len;
291c0cdc19fSBjorn Andersson }
292c0cdc19fSBjorn Andersson 
rpmsg_eptdev_poll(struct file * filp,poll_table * wait)293afc9a42bSAl Viro static __poll_t rpmsg_eptdev_poll(struct file *filp, poll_table *wait)
294c0cdc19fSBjorn Andersson {
295c0cdc19fSBjorn Andersson 	struct rpmsg_eptdev *eptdev = filp->private_data;
296afc9a42bSAl Viro 	__poll_t mask = 0;
297c0cdc19fSBjorn Andersson 
298c0cdc19fSBjorn Andersson 	if (!eptdev->ept)
299a9a08845SLinus Torvalds 		return EPOLLERR;
300c0cdc19fSBjorn Andersson 
301c0cdc19fSBjorn Andersson 	poll_wait(filp, &eptdev->readq, wait);
302c0cdc19fSBjorn Andersson 
303c0cdc19fSBjorn Andersson 	if (!skb_queue_empty(&eptdev->queue))
304a9a08845SLinus Torvalds 		mask |= EPOLLIN | EPOLLRDNORM;
305c0cdc19fSBjorn Andersson 
306*5550201cSChris Lew 	if (eptdev->remote_flow_updated)
307*5550201cSChris Lew 		mask |= EPOLLPRI;
308*5550201cSChris Lew 
30917b88a20SDeepak Kumar Singh 	mutex_lock(&eptdev->ept_lock);
310c0cdc19fSBjorn Andersson 	mask |= rpmsg_poll(eptdev->ept, filp, wait);
31117b88a20SDeepak Kumar Singh 	mutex_unlock(&eptdev->ept_lock);
312c0cdc19fSBjorn Andersson 
313c0cdc19fSBjorn Andersson 	return mask;
314c0cdc19fSBjorn Andersson }
315c0cdc19fSBjorn Andersson 
rpmsg_eptdev_ioctl(struct file * fp,unsigned int cmd,unsigned long arg)316c0cdc19fSBjorn Andersson static long rpmsg_eptdev_ioctl(struct file *fp, unsigned int cmd,
317c0cdc19fSBjorn Andersson 			       unsigned long arg)
318c0cdc19fSBjorn Andersson {
319c0cdc19fSBjorn Andersson 	struct rpmsg_eptdev *eptdev = fp->private_data;
320c0cdc19fSBjorn Andersson 
321*5550201cSChris Lew 	bool set;
322*5550201cSChris Lew 	int ret;
323c0cdc19fSBjorn Andersson 
324*5550201cSChris Lew 	switch (cmd) {
325*5550201cSChris Lew 	case RPMSG_GET_OUTGOING_FLOWCONTROL:
326*5550201cSChris Lew 		eptdev->remote_flow_updated = false;
327*5550201cSChris Lew 		ret = put_user(eptdev->remote_flow_restricted, (int __user *)arg);
328*5550201cSChris Lew 		break;
329*5550201cSChris Lew 	case RPMSG_SET_INCOMING_FLOWCONTROL:
330*5550201cSChris Lew 		if (arg > 1) {
331*5550201cSChris Lew 			ret = -EINVAL;
332*5550201cSChris Lew 			break;
333*5550201cSChris Lew 		}
334*5550201cSChris Lew 		set = !!arg;
335*5550201cSChris Lew 		ret = rpmsg_set_flow_control(eptdev->ept, set, eptdev->chinfo.dst);
336*5550201cSChris Lew 		break;
337*5550201cSChris Lew 	case RPMSG_DESTROY_EPT_IOCTL:
338bea9b79cSArnaud Pouliquen 		/* Don't allow to destroy a default endpoint. */
339*5550201cSChris Lew 		if (eptdev->default_ept) {
340*5550201cSChris Lew 			ret = -EINVAL;
341*5550201cSChris Lew 			break;
342*5550201cSChris Lew 		}
343*5550201cSChris Lew 		ret = rpmsg_chrdev_eptdev_destroy(&eptdev->dev, NULL);
344*5550201cSChris Lew 		break;
345*5550201cSChris Lew 	default:
346*5550201cSChris Lew 		ret = -EINVAL;
347*5550201cSChris Lew 	}
348bea9b79cSArnaud Pouliquen 
349*5550201cSChris Lew 	return ret;
350c0cdc19fSBjorn Andersson }
351c0cdc19fSBjorn Andersson 
352c0cdc19fSBjorn Andersson static const struct file_operations rpmsg_eptdev_fops = {
353c0cdc19fSBjorn Andersson 	.owner = THIS_MODULE,
354c0cdc19fSBjorn Andersson 	.open = rpmsg_eptdev_open,
355c0cdc19fSBjorn Andersson 	.release = rpmsg_eptdev_release,
356ccf45b18SBjorn Andersson 	.read_iter = rpmsg_eptdev_read_iter,
357ccf45b18SBjorn Andersson 	.write_iter = rpmsg_eptdev_write_iter,
358c0cdc19fSBjorn Andersson 	.poll = rpmsg_eptdev_poll,
359c0cdc19fSBjorn Andersson 	.unlocked_ioctl = rpmsg_eptdev_ioctl,
3601832f2d8SArnd Bergmann 	.compat_ioctl = compat_ptr_ioctl,
361c0cdc19fSBjorn Andersson };
362c0cdc19fSBjorn Andersson 
name_show(struct device * dev,struct device_attribute * attr,char * buf)363c0cdc19fSBjorn Andersson static ssize_t name_show(struct device *dev, struct device_attribute *attr,
364c0cdc19fSBjorn Andersson 			 char *buf)
365c0cdc19fSBjorn Andersson {
366c0cdc19fSBjorn Andersson 	struct rpmsg_eptdev *eptdev = dev_get_drvdata(dev);
367c0cdc19fSBjorn Andersson 
368c0cdc19fSBjorn Andersson 	return sprintf(buf, "%s\n", eptdev->chinfo.name);
369c0cdc19fSBjorn Andersson }
370c0cdc19fSBjorn Andersson static DEVICE_ATTR_RO(name);
371c0cdc19fSBjorn Andersson 
src_show(struct device * dev,struct device_attribute * attr,char * buf)372c0cdc19fSBjorn Andersson static ssize_t src_show(struct device *dev, struct device_attribute *attr,
373c0cdc19fSBjorn Andersson 			 char *buf)
374c0cdc19fSBjorn Andersson {
375c0cdc19fSBjorn Andersson 	struct rpmsg_eptdev *eptdev = dev_get_drvdata(dev);
376c0cdc19fSBjorn Andersson 
377c0cdc19fSBjorn Andersson 	return sprintf(buf, "%d\n", eptdev->chinfo.src);
378c0cdc19fSBjorn Andersson }
379c0cdc19fSBjorn Andersson static DEVICE_ATTR_RO(src);
380c0cdc19fSBjorn Andersson 
dst_show(struct device * dev,struct device_attribute * attr,char * buf)381c0cdc19fSBjorn Andersson static ssize_t dst_show(struct device *dev, struct device_attribute *attr,
382c0cdc19fSBjorn Andersson 			 char *buf)
383c0cdc19fSBjorn Andersson {
384c0cdc19fSBjorn Andersson 	struct rpmsg_eptdev *eptdev = dev_get_drvdata(dev);
385c0cdc19fSBjorn Andersson 
386c0cdc19fSBjorn Andersson 	return sprintf(buf, "%d\n", eptdev->chinfo.dst);
387c0cdc19fSBjorn Andersson }
388c0cdc19fSBjorn Andersson static DEVICE_ATTR_RO(dst);
389c0cdc19fSBjorn Andersson 
390c0cdc19fSBjorn Andersson static struct attribute *rpmsg_eptdev_attrs[] = {
391c0cdc19fSBjorn Andersson 	&dev_attr_name.attr,
392c0cdc19fSBjorn Andersson 	&dev_attr_src.attr,
393c0cdc19fSBjorn Andersson 	&dev_attr_dst.attr,
394c0cdc19fSBjorn Andersson 	NULL
395c0cdc19fSBjorn Andersson };
396c0cdc19fSBjorn Andersson ATTRIBUTE_GROUPS(rpmsg_eptdev);
397c0cdc19fSBjorn Andersson 
rpmsg_eptdev_release_device(struct device * dev)398c0cdc19fSBjorn Andersson static void rpmsg_eptdev_release_device(struct device *dev)
399c0cdc19fSBjorn Andersson {
400c0cdc19fSBjorn Andersson 	struct rpmsg_eptdev *eptdev = dev_to_eptdev(dev);
401c0cdc19fSBjorn Andersson 
402c0cdc19fSBjorn Andersson 	ida_simple_remove(&rpmsg_ept_ida, dev->id);
403c0cdc19fSBjorn Andersson 	ida_simple_remove(&rpmsg_minor_ida, MINOR(eptdev->dev.devt));
404c0cdc19fSBjorn Andersson 	kfree(eptdev);
405c0cdc19fSBjorn Andersson }
406c0cdc19fSBjorn Andersson 
rpmsg_chrdev_eptdev_alloc(struct rpmsg_device * rpdev,struct device * parent)407cc9da7deSArnaud Pouliquen static struct rpmsg_eptdev *rpmsg_chrdev_eptdev_alloc(struct rpmsg_device *rpdev,
408cc9da7deSArnaud Pouliquen 						      struct device *parent)
409c0cdc19fSBjorn Andersson {
410c0cdc19fSBjorn Andersson 	struct rpmsg_eptdev *eptdev;
411c0cdc19fSBjorn Andersson 	struct device *dev;
412c0cdc19fSBjorn Andersson 
413c0cdc19fSBjorn Andersson 	eptdev = kzalloc(sizeof(*eptdev), GFP_KERNEL);
414c0cdc19fSBjorn Andersson 	if (!eptdev)
415cc9da7deSArnaud Pouliquen 		return ERR_PTR(-ENOMEM);
416c0cdc19fSBjorn Andersson 
417c0cdc19fSBjorn Andersson 	dev = &eptdev->dev;
418c0cdc19fSBjorn Andersson 	eptdev->rpdev = rpdev;
419c0cdc19fSBjorn Andersson 
420c0cdc19fSBjorn Andersson 	mutex_init(&eptdev->ept_lock);
421c0cdc19fSBjorn Andersson 	spin_lock_init(&eptdev->queue_lock);
422c0cdc19fSBjorn Andersson 	skb_queue_head_init(&eptdev->queue);
423c0cdc19fSBjorn Andersson 	init_waitqueue_head(&eptdev->readq);
424c0cdc19fSBjorn Andersson 
425c0cdc19fSBjorn Andersson 	device_initialize(dev);
426c0cdc19fSBjorn Andersson 	dev->class = rpmsg_class;
42769265bc1SArnaud Pouliquen 	dev->parent = parent;
428c0cdc19fSBjorn Andersson 	dev->groups = rpmsg_eptdev_groups;
429c0cdc19fSBjorn Andersson 	dev_set_drvdata(dev, eptdev);
430c0cdc19fSBjorn Andersson 
431c0cdc19fSBjorn Andersson 	cdev_init(&eptdev->cdev, &rpmsg_eptdev_fops);
432c0cdc19fSBjorn Andersson 	eptdev->cdev.owner = THIS_MODULE;
433c0cdc19fSBjorn Andersson 
434cc9da7deSArnaud Pouliquen 	return eptdev;
435cc9da7deSArnaud Pouliquen }
436cc9da7deSArnaud Pouliquen 
rpmsg_chrdev_eptdev_add(struct rpmsg_eptdev * eptdev,struct rpmsg_channel_info chinfo)437cc9da7deSArnaud Pouliquen static int rpmsg_chrdev_eptdev_add(struct rpmsg_eptdev *eptdev, struct rpmsg_channel_info chinfo)
438cc9da7deSArnaud Pouliquen {
439cc9da7deSArnaud Pouliquen 	struct device *dev = &eptdev->dev;
440cc9da7deSArnaud Pouliquen 	int ret;
441cc9da7deSArnaud Pouliquen 
442cc9da7deSArnaud Pouliquen 	eptdev->chinfo = chinfo;
443cc9da7deSArnaud Pouliquen 
444c0cdc19fSBjorn Andersson 	ret = ida_simple_get(&rpmsg_minor_ida, 0, RPMSG_DEV_MAX, GFP_KERNEL);
445c0cdc19fSBjorn Andersson 	if (ret < 0)
446c0cdc19fSBjorn Andersson 		goto free_eptdev;
447c0cdc19fSBjorn Andersson 	dev->devt = MKDEV(MAJOR(rpmsg_major), ret);
448c0cdc19fSBjorn Andersson 
449c0cdc19fSBjorn Andersson 	ret = ida_simple_get(&rpmsg_ept_ida, 0, 0, GFP_KERNEL);
450c0cdc19fSBjorn Andersson 	if (ret < 0)
451c0cdc19fSBjorn Andersson 		goto free_minor_ida;
452c0cdc19fSBjorn Andersson 	dev->id = ret;
453c0cdc19fSBjorn Andersson 	dev_set_name(dev, "rpmsg%d", ret);
454c0cdc19fSBjorn Andersson 
4557a534ae8SMatthias Kaehlcke 	ret = cdev_device_add(&eptdev->cdev, &eptdev->dev);
456c0cdc19fSBjorn Andersson 	if (ret)
457c0cdc19fSBjorn Andersson 		goto free_ept_ida;
458c0cdc19fSBjorn Andersson 
459c0cdc19fSBjorn Andersson 	/* We can now rely on the release function for cleanup */
460c0cdc19fSBjorn Andersson 	dev->release = rpmsg_eptdev_release_device;
461c0cdc19fSBjorn Andersson 
462c0cdc19fSBjorn Andersson 	return ret;
463c0cdc19fSBjorn Andersson 
464c0cdc19fSBjorn Andersson free_ept_ida:
465c0cdc19fSBjorn Andersson 	ida_simple_remove(&rpmsg_ept_ida, dev->id);
466c0cdc19fSBjorn Andersson free_minor_ida:
467c0cdc19fSBjorn Andersson 	ida_simple_remove(&rpmsg_minor_ida, MINOR(dev->devt));
468c0cdc19fSBjorn Andersson free_eptdev:
469c0cdc19fSBjorn Andersson 	put_device(dev);
470c0cdc19fSBjorn Andersson 	kfree(eptdev);
471c0cdc19fSBjorn Andersson 
472c0cdc19fSBjorn Andersson 	return ret;
473c0cdc19fSBjorn Andersson }
474cc9da7deSArnaud Pouliquen 
rpmsg_chrdev_eptdev_create(struct rpmsg_device * rpdev,struct device * parent,struct rpmsg_channel_info chinfo)475cc9da7deSArnaud Pouliquen int rpmsg_chrdev_eptdev_create(struct rpmsg_device *rpdev, struct device *parent,
476cc9da7deSArnaud Pouliquen 			       struct rpmsg_channel_info chinfo)
477cc9da7deSArnaud Pouliquen {
478cc9da7deSArnaud Pouliquen 	struct rpmsg_eptdev *eptdev;
479cc9da7deSArnaud Pouliquen 
480cc9da7deSArnaud Pouliquen 	eptdev = rpmsg_chrdev_eptdev_alloc(rpdev, parent);
481cc9da7deSArnaud Pouliquen 	if (IS_ERR(eptdev))
482cc9da7deSArnaud Pouliquen 		return PTR_ERR(eptdev);
483cc9da7deSArnaud Pouliquen 
48406564be4Sye xingchen 	return rpmsg_chrdev_eptdev_add(eptdev, chinfo);
485cc9da7deSArnaud Pouliquen }
48669265bc1SArnaud Pouliquen EXPORT_SYMBOL(rpmsg_chrdev_eptdev_create);
487c0cdc19fSBjorn Andersson 
rpmsg_chrdev_probe(struct rpmsg_device * rpdev)488bc69d106SArnaud Pouliquen static int rpmsg_chrdev_probe(struct rpmsg_device *rpdev)
489bc69d106SArnaud Pouliquen {
490bc69d106SArnaud Pouliquen 	struct rpmsg_channel_info chinfo;
491bc69d106SArnaud Pouliquen 	struct rpmsg_eptdev *eptdev;
492bc69d106SArnaud Pouliquen 	struct device *dev = &rpdev->dev;
493bc69d106SArnaud Pouliquen 
494bc69d106SArnaud Pouliquen 	memcpy(chinfo.name, rpdev->id.name, RPMSG_NAME_SIZE);
495bc69d106SArnaud Pouliquen 	chinfo.src = rpdev->src;
496bc69d106SArnaud Pouliquen 	chinfo.dst = rpdev->dst;
497bc69d106SArnaud Pouliquen 
498bc69d106SArnaud Pouliquen 	eptdev = rpmsg_chrdev_eptdev_alloc(rpdev, dev);
499bc69d106SArnaud Pouliquen 	if (IS_ERR(eptdev))
500bc69d106SArnaud Pouliquen 		return PTR_ERR(eptdev);
501bc69d106SArnaud Pouliquen 
502bc69d106SArnaud Pouliquen 	/* Set the default_ept to the rpmsg device endpoint */
503bc69d106SArnaud Pouliquen 	eptdev->default_ept = rpdev->ept;
504bc69d106SArnaud Pouliquen 
505bc69d106SArnaud Pouliquen 	/*
506bc69d106SArnaud Pouliquen 	 * The rpmsg_ept_cb uses *priv parameter to get its rpmsg_eptdev context.
507bc69d106SArnaud Pouliquen 	 * Storedit in default_ept *priv field.
508bc69d106SArnaud Pouliquen 	 */
509bc69d106SArnaud Pouliquen 	eptdev->default_ept->priv = eptdev;
510bc69d106SArnaud Pouliquen 
511bc69d106SArnaud Pouliquen 	return rpmsg_chrdev_eptdev_add(eptdev, chinfo);
512bc69d106SArnaud Pouliquen }
513bc69d106SArnaud Pouliquen 
rpmsg_chrdev_remove(struct rpmsg_device * rpdev)514bc69d106SArnaud Pouliquen static void rpmsg_chrdev_remove(struct rpmsg_device *rpdev)
515bc69d106SArnaud Pouliquen {
516bc69d106SArnaud Pouliquen 	int ret;
517bc69d106SArnaud Pouliquen 
518bc69d106SArnaud Pouliquen 	ret = device_for_each_child(&rpdev->dev, NULL, rpmsg_chrdev_eptdev_destroy);
519bc69d106SArnaud Pouliquen 	if (ret)
520bc69d106SArnaud Pouliquen 		dev_warn(&rpdev->dev, "failed to destroy endpoints: %d\n", ret);
521bc69d106SArnaud Pouliquen }
522bc69d106SArnaud Pouliquen 
523bc69d106SArnaud Pouliquen static struct rpmsg_device_id rpmsg_chrdev_id_table[] = {
524bc69d106SArnaud Pouliquen 	{ .name	= "rpmsg-raw" },
525bc69d106SArnaud Pouliquen 	{ },
526bc69d106SArnaud Pouliquen };
527bc69d106SArnaud Pouliquen 
528bc69d106SArnaud Pouliquen static struct rpmsg_driver rpmsg_chrdev_driver = {
529bc69d106SArnaud Pouliquen 	.probe = rpmsg_chrdev_probe,
530bc69d106SArnaud Pouliquen 	.remove = rpmsg_chrdev_remove,
531bc69d106SArnaud Pouliquen 	.callback = rpmsg_ept_cb,
532bc69d106SArnaud Pouliquen 	.id_table = rpmsg_chrdev_id_table,
533bc69d106SArnaud Pouliquen 	.drv.name = "rpmsg_chrdev",
534bc69d106SArnaud Pouliquen };
535bc69d106SArnaud Pouliquen 
rpmsg_chrdev_init(void)53660d7b22dSArnaud Pouliquen static int rpmsg_chrdev_init(void)
537c0cdc19fSBjorn Andersson {
538c0cdc19fSBjorn Andersson 	int ret;
539c0cdc19fSBjorn Andersson 
540617d3293SArnaud Pouliquen 	ret = alloc_chrdev_region(&rpmsg_major, 0, RPMSG_DEV_MAX, "rpmsg_char");
541c0cdc19fSBjorn Andersson 	if (ret < 0) {
542c5727244SArnaud Pouliquen 		pr_err("failed to allocate char dev region\n");
543c0cdc19fSBjorn Andersson 		return ret;
544c0cdc19fSBjorn Andersson 	}
545c0cdc19fSBjorn Andersson 
546bc69d106SArnaud Pouliquen 	ret = register_rpmsg_driver(&rpmsg_chrdev_driver);
547bc69d106SArnaud Pouliquen 	if (ret < 0) {
548bc69d106SArnaud Pouliquen 		pr_err("rpmsg: failed to register rpmsg raw driver\n");
549bc69d106SArnaud Pouliquen 		goto free_region;
550bc69d106SArnaud Pouliquen 	}
551bc69d106SArnaud Pouliquen 
552617d3293SArnaud Pouliquen 	return 0;
553bc69d106SArnaud Pouliquen 
554bc69d106SArnaud Pouliquen free_region:
555bc69d106SArnaud Pouliquen 	unregister_chrdev_region(rpmsg_major, RPMSG_DEV_MAX);
556bc69d106SArnaud Pouliquen 
557bc69d106SArnaud Pouliquen 	return ret;
558c0cdc19fSBjorn Andersson }
55960d7b22dSArnaud Pouliquen postcore_initcall(rpmsg_chrdev_init);
560c0cdc19fSBjorn Andersson 
rpmsg_chrdev_exit(void)561c0cdc19fSBjorn Andersson static void rpmsg_chrdev_exit(void)
562c0cdc19fSBjorn Andersson {
563bc69d106SArnaud Pouliquen 	unregister_rpmsg_driver(&rpmsg_chrdev_driver);
564c0cdc19fSBjorn Andersson 	unregister_chrdev_region(rpmsg_major, RPMSG_DEV_MAX);
565c0cdc19fSBjorn Andersson }
566c0cdc19fSBjorn Andersson module_exit(rpmsg_chrdev_exit);
56793dd4e73SRamon Fried 
56893dd4e73SRamon Fried MODULE_ALIAS("rpmsg:rpmsg_chrdev");
569c0cdc19fSBjorn Andersson MODULE_LICENSE("GPL v2");
570