xref: /linux-6.15/drivers/most/most_cdev.c (revision ceea9344)
1*ceea9344SChristian Gromm // SPDX-License-Identifier: GPL-2.0
2*ceea9344SChristian Gromm /*
3*ceea9344SChristian Gromm  * cdev.c - Character device component for Mostcore
4*ceea9344SChristian Gromm  *
5*ceea9344SChristian Gromm  * Copyright (C) 2013-2015 Microchip Technology Germany II GmbH & Co. KG
6*ceea9344SChristian Gromm  */
7*ceea9344SChristian Gromm 
8*ceea9344SChristian Gromm #include <linux/module.h>
9*ceea9344SChristian Gromm #include <linux/sched.h>
10*ceea9344SChristian Gromm #include <linux/fs.h>
11*ceea9344SChristian Gromm #include <linux/slab.h>
12*ceea9344SChristian Gromm #include <linux/device.h>
13*ceea9344SChristian Gromm #include <linux/cdev.h>
14*ceea9344SChristian Gromm #include <linux/poll.h>
15*ceea9344SChristian Gromm #include <linux/kfifo.h>
16*ceea9344SChristian Gromm #include <linux/uaccess.h>
17*ceea9344SChristian Gromm #include <linux/idr.h>
18*ceea9344SChristian Gromm #include <linux/most.h>
19*ceea9344SChristian Gromm 
20*ceea9344SChristian Gromm #define CHRDEV_REGION_SIZE 50
21*ceea9344SChristian Gromm 
22*ceea9344SChristian Gromm static struct cdev_component {
23*ceea9344SChristian Gromm 	dev_t devno;
24*ceea9344SChristian Gromm 	struct ida minor_id;
25*ceea9344SChristian Gromm 	unsigned int major;
26*ceea9344SChristian Gromm 	struct class *class;
27*ceea9344SChristian Gromm 	struct most_component cc;
28*ceea9344SChristian Gromm } comp;
29*ceea9344SChristian Gromm 
30*ceea9344SChristian Gromm struct comp_channel {
31*ceea9344SChristian Gromm 	wait_queue_head_t wq;
32*ceea9344SChristian Gromm 	spinlock_t unlink;	/* synchronization lock to unlink channels */
33*ceea9344SChristian Gromm 	struct cdev cdev;
34*ceea9344SChristian Gromm 	struct device *dev;
35*ceea9344SChristian Gromm 	struct mutex io_mutex;
36*ceea9344SChristian Gromm 	struct most_interface *iface;
37*ceea9344SChristian Gromm 	struct most_channel_config *cfg;
38*ceea9344SChristian Gromm 	unsigned int channel_id;
39*ceea9344SChristian Gromm 	dev_t devno;
40*ceea9344SChristian Gromm 	size_t mbo_offs;
41*ceea9344SChristian Gromm 	DECLARE_KFIFO_PTR(fifo, typeof(struct mbo *));
42*ceea9344SChristian Gromm 	int access_ref;
43*ceea9344SChristian Gromm 	struct list_head list;
44*ceea9344SChristian Gromm };
45*ceea9344SChristian Gromm 
46*ceea9344SChristian Gromm #define to_channel(d) container_of(d, struct comp_channel, cdev)
47*ceea9344SChristian Gromm static struct list_head channel_list;
48*ceea9344SChristian Gromm static spinlock_t ch_list_lock;
49*ceea9344SChristian Gromm 
50*ceea9344SChristian Gromm static inline bool ch_has_mbo(struct comp_channel *c)
51*ceea9344SChristian Gromm {
52*ceea9344SChristian Gromm 	return channel_has_mbo(c->iface, c->channel_id, &comp.cc) > 0;
53*ceea9344SChristian Gromm }
54*ceea9344SChristian Gromm 
55*ceea9344SChristian Gromm static inline struct mbo *ch_get_mbo(struct comp_channel *c, struct mbo **mbo)
56*ceea9344SChristian Gromm {
57*ceea9344SChristian Gromm 	if (!kfifo_peek(&c->fifo, mbo)) {
58*ceea9344SChristian Gromm 		*mbo = most_get_mbo(c->iface, c->channel_id, &comp.cc);
59*ceea9344SChristian Gromm 		if (*mbo)
60*ceea9344SChristian Gromm 			kfifo_in(&c->fifo, mbo, 1);
61*ceea9344SChristian Gromm 	}
62*ceea9344SChristian Gromm 	return *mbo;
63*ceea9344SChristian Gromm }
64*ceea9344SChristian Gromm 
65*ceea9344SChristian Gromm static struct comp_channel *get_channel(struct most_interface *iface, int id)
66*ceea9344SChristian Gromm {
67*ceea9344SChristian Gromm 	struct comp_channel *c, *tmp;
68*ceea9344SChristian Gromm 	unsigned long flags;
69*ceea9344SChristian Gromm 
70*ceea9344SChristian Gromm 	spin_lock_irqsave(&ch_list_lock, flags);
71*ceea9344SChristian Gromm 	list_for_each_entry_safe(c, tmp, &channel_list, list) {
72*ceea9344SChristian Gromm 		if ((c->iface == iface) && (c->channel_id == id)) {
73*ceea9344SChristian Gromm 			spin_unlock_irqrestore(&ch_list_lock, flags);
74*ceea9344SChristian Gromm 			return c;
75*ceea9344SChristian Gromm 		}
76*ceea9344SChristian Gromm 	}
77*ceea9344SChristian Gromm 	spin_unlock_irqrestore(&ch_list_lock, flags);
78*ceea9344SChristian Gromm 	return NULL;
79*ceea9344SChristian Gromm }
80*ceea9344SChristian Gromm 
81*ceea9344SChristian Gromm static void stop_channel(struct comp_channel *c)
82*ceea9344SChristian Gromm {
83*ceea9344SChristian Gromm 	struct mbo *mbo;
84*ceea9344SChristian Gromm 
85*ceea9344SChristian Gromm 	while (kfifo_out((struct kfifo *)&c->fifo, &mbo, 1))
86*ceea9344SChristian Gromm 		most_put_mbo(mbo);
87*ceea9344SChristian Gromm 	most_stop_channel(c->iface, c->channel_id, &comp.cc);
88*ceea9344SChristian Gromm }
89*ceea9344SChristian Gromm 
90*ceea9344SChristian Gromm static void destroy_cdev(struct comp_channel *c)
91*ceea9344SChristian Gromm {
92*ceea9344SChristian Gromm 	unsigned long flags;
93*ceea9344SChristian Gromm 
94*ceea9344SChristian Gromm 	device_destroy(comp.class, c->devno);
95*ceea9344SChristian Gromm 	cdev_del(&c->cdev);
96*ceea9344SChristian Gromm 	spin_lock_irqsave(&ch_list_lock, flags);
97*ceea9344SChristian Gromm 	list_del(&c->list);
98*ceea9344SChristian Gromm 	spin_unlock_irqrestore(&ch_list_lock, flags);
99*ceea9344SChristian Gromm }
100*ceea9344SChristian Gromm 
101*ceea9344SChristian Gromm static void destroy_channel(struct comp_channel *c)
102*ceea9344SChristian Gromm {
103*ceea9344SChristian Gromm 	ida_simple_remove(&comp.minor_id, MINOR(c->devno));
104*ceea9344SChristian Gromm 	kfifo_free(&c->fifo);
105*ceea9344SChristian Gromm 	kfree(c);
106*ceea9344SChristian Gromm }
107*ceea9344SChristian Gromm 
108*ceea9344SChristian Gromm /**
109*ceea9344SChristian Gromm  * comp_open - implements the syscall to open the device
110*ceea9344SChristian Gromm  * @inode: inode pointer
111*ceea9344SChristian Gromm  * @filp: file pointer
112*ceea9344SChristian Gromm  *
113*ceea9344SChristian Gromm  * This stores the channel pointer in the private data field of
114*ceea9344SChristian Gromm  * the file structure and activates the channel within the core.
115*ceea9344SChristian Gromm  */
116*ceea9344SChristian Gromm static int comp_open(struct inode *inode, struct file *filp)
117*ceea9344SChristian Gromm {
118*ceea9344SChristian Gromm 	struct comp_channel *c;
119*ceea9344SChristian Gromm 	int ret;
120*ceea9344SChristian Gromm 
121*ceea9344SChristian Gromm 	c = to_channel(inode->i_cdev);
122*ceea9344SChristian Gromm 	filp->private_data = c;
123*ceea9344SChristian Gromm 
124*ceea9344SChristian Gromm 	if (((c->cfg->direction == MOST_CH_RX) &&
125*ceea9344SChristian Gromm 	     ((filp->f_flags & O_ACCMODE) != O_RDONLY)) ||
126*ceea9344SChristian Gromm 	     ((c->cfg->direction == MOST_CH_TX) &&
127*ceea9344SChristian Gromm 		((filp->f_flags & O_ACCMODE) != O_WRONLY))) {
128*ceea9344SChristian Gromm 		return -EACCES;
129*ceea9344SChristian Gromm 	}
130*ceea9344SChristian Gromm 
131*ceea9344SChristian Gromm 	mutex_lock(&c->io_mutex);
132*ceea9344SChristian Gromm 	if (!c->dev) {
133*ceea9344SChristian Gromm 		mutex_unlock(&c->io_mutex);
134*ceea9344SChristian Gromm 		return -ENODEV;
135*ceea9344SChristian Gromm 	}
136*ceea9344SChristian Gromm 
137*ceea9344SChristian Gromm 	if (c->access_ref) {
138*ceea9344SChristian Gromm 		mutex_unlock(&c->io_mutex);
139*ceea9344SChristian Gromm 		return -EBUSY;
140*ceea9344SChristian Gromm 	}
141*ceea9344SChristian Gromm 
142*ceea9344SChristian Gromm 	c->mbo_offs = 0;
143*ceea9344SChristian Gromm 	ret = most_start_channel(c->iface, c->channel_id, &comp.cc);
144*ceea9344SChristian Gromm 	if (!ret)
145*ceea9344SChristian Gromm 		c->access_ref = 1;
146*ceea9344SChristian Gromm 	mutex_unlock(&c->io_mutex);
147*ceea9344SChristian Gromm 	return ret;
148*ceea9344SChristian Gromm }
149*ceea9344SChristian Gromm 
150*ceea9344SChristian Gromm /**
151*ceea9344SChristian Gromm  * comp_close - implements the syscall to close the device
152*ceea9344SChristian Gromm  * @inode: inode pointer
153*ceea9344SChristian Gromm  * @filp: file pointer
154*ceea9344SChristian Gromm  *
155*ceea9344SChristian Gromm  * This stops the channel within the core.
156*ceea9344SChristian Gromm  */
157*ceea9344SChristian Gromm static int comp_close(struct inode *inode, struct file *filp)
158*ceea9344SChristian Gromm {
159*ceea9344SChristian Gromm 	struct comp_channel *c = to_channel(inode->i_cdev);
160*ceea9344SChristian Gromm 
161*ceea9344SChristian Gromm 	mutex_lock(&c->io_mutex);
162*ceea9344SChristian Gromm 	spin_lock(&c->unlink);
163*ceea9344SChristian Gromm 	c->access_ref = 0;
164*ceea9344SChristian Gromm 	spin_unlock(&c->unlink);
165*ceea9344SChristian Gromm 	if (c->dev) {
166*ceea9344SChristian Gromm 		stop_channel(c);
167*ceea9344SChristian Gromm 		mutex_unlock(&c->io_mutex);
168*ceea9344SChristian Gromm 	} else {
169*ceea9344SChristian Gromm 		mutex_unlock(&c->io_mutex);
170*ceea9344SChristian Gromm 		destroy_channel(c);
171*ceea9344SChristian Gromm 	}
172*ceea9344SChristian Gromm 	return 0;
173*ceea9344SChristian Gromm }
174*ceea9344SChristian Gromm 
175*ceea9344SChristian Gromm /**
176*ceea9344SChristian Gromm  * comp_write - implements the syscall to write to the device
177*ceea9344SChristian Gromm  * @filp: file pointer
178*ceea9344SChristian Gromm  * @buf: pointer to user buffer
179*ceea9344SChristian Gromm  * @count: number of bytes to write
180*ceea9344SChristian Gromm  * @offset: offset from where to start writing
181*ceea9344SChristian Gromm  */
182*ceea9344SChristian Gromm static ssize_t comp_write(struct file *filp, const char __user *buf,
183*ceea9344SChristian Gromm 			  size_t count, loff_t *offset)
184*ceea9344SChristian Gromm {
185*ceea9344SChristian Gromm 	int ret;
186*ceea9344SChristian Gromm 	size_t to_copy, left;
187*ceea9344SChristian Gromm 	struct mbo *mbo = NULL;
188*ceea9344SChristian Gromm 	struct comp_channel *c = filp->private_data;
189*ceea9344SChristian Gromm 
190*ceea9344SChristian Gromm 	mutex_lock(&c->io_mutex);
191*ceea9344SChristian Gromm 	while (c->dev && !ch_get_mbo(c, &mbo)) {
192*ceea9344SChristian Gromm 		mutex_unlock(&c->io_mutex);
193*ceea9344SChristian Gromm 
194*ceea9344SChristian Gromm 		if ((filp->f_flags & O_NONBLOCK))
195*ceea9344SChristian Gromm 			return -EAGAIN;
196*ceea9344SChristian Gromm 		if (wait_event_interruptible(c->wq, ch_has_mbo(c) || !c->dev))
197*ceea9344SChristian Gromm 			return -ERESTARTSYS;
198*ceea9344SChristian Gromm 		mutex_lock(&c->io_mutex);
199*ceea9344SChristian Gromm 	}
200*ceea9344SChristian Gromm 
201*ceea9344SChristian Gromm 	if (unlikely(!c->dev)) {
202*ceea9344SChristian Gromm 		ret = -ENODEV;
203*ceea9344SChristian Gromm 		goto unlock;
204*ceea9344SChristian Gromm 	}
205*ceea9344SChristian Gromm 
206*ceea9344SChristian Gromm 	to_copy = min(count, c->cfg->buffer_size - c->mbo_offs);
207*ceea9344SChristian Gromm 	left = copy_from_user(mbo->virt_address + c->mbo_offs, buf, to_copy);
208*ceea9344SChristian Gromm 	if (left == to_copy) {
209*ceea9344SChristian Gromm 		ret = -EFAULT;
210*ceea9344SChristian Gromm 		goto unlock;
211*ceea9344SChristian Gromm 	}
212*ceea9344SChristian Gromm 
213*ceea9344SChristian Gromm 	c->mbo_offs += to_copy - left;
214*ceea9344SChristian Gromm 	if (c->mbo_offs >= c->cfg->buffer_size ||
215*ceea9344SChristian Gromm 	    c->cfg->data_type == MOST_CH_CONTROL ||
216*ceea9344SChristian Gromm 	    c->cfg->data_type == MOST_CH_ASYNC) {
217*ceea9344SChristian Gromm 		kfifo_skip(&c->fifo);
218*ceea9344SChristian Gromm 		mbo->buffer_length = c->mbo_offs;
219*ceea9344SChristian Gromm 		c->mbo_offs = 0;
220*ceea9344SChristian Gromm 		most_submit_mbo(mbo);
221*ceea9344SChristian Gromm 	}
222*ceea9344SChristian Gromm 
223*ceea9344SChristian Gromm 	ret = to_copy - left;
224*ceea9344SChristian Gromm unlock:
225*ceea9344SChristian Gromm 	mutex_unlock(&c->io_mutex);
226*ceea9344SChristian Gromm 	return ret;
227*ceea9344SChristian Gromm }
228*ceea9344SChristian Gromm 
229*ceea9344SChristian Gromm /**
230*ceea9344SChristian Gromm  * comp_read - implements the syscall to read from the device
231*ceea9344SChristian Gromm  * @filp: file pointer
232*ceea9344SChristian Gromm  * @buf: pointer to user buffer
233*ceea9344SChristian Gromm  * @count: number of bytes to read
234*ceea9344SChristian Gromm  * @offset: offset from where to start reading
235*ceea9344SChristian Gromm  */
236*ceea9344SChristian Gromm static ssize_t
237*ceea9344SChristian Gromm comp_read(struct file *filp, char __user *buf, size_t count, loff_t *offset)
238*ceea9344SChristian Gromm {
239*ceea9344SChristian Gromm 	size_t to_copy, not_copied, copied;
240*ceea9344SChristian Gromm 	struct mbo *mbo = NULL;
241*ceea9344SChristian Gromm 	struct comp_channel *c = filp->private_data;
242*ceea9344SChristian Gromm 
243*ceea9344SChristian Gromm 	mutex_lock(&c->io_mutex);
244*ceea9344SChristian Gromm 	while (c->dev && !kfifo_peek(&c->fifo, &mbo)) {
245*ceea9344SChristian Gromm 		mutex_unlock(&c->io_mutex);
246*ceea9344SChristian Gromm 		if (filp->f_flags & O_NONBLOCK)
247*ceea9344SChristian Gromm 			return -EAGAIN;
248*ceea9344SChristian Gromm 		if (wait_event_interruptible(c->wq,
249*ceea9344SChristian Gromm 					     (!kfifo_is_empty(&c->fifo) ||
250*ceea9344SChristian Gromm 					      (!c->dev))))
251*ceea9344SChristian Gromm 			return -ERESTARTSYS;
252*ceea9344SChristian Gromm 		mutex_lock(&c->io_mutex);
253*ceea9344SChristian Gromm 	}
254*ceea9344SChristian Gromm 
255*ceea9344SChristian Gromm 	/* make sure we don't submit to gone devices */
256*ceea9344SChristian Gromm 	if (unlikely(!c->dev)) {
257*ceea9344SChristian Gromm 		mutex_unlock(&c->io_mutex);
258*ceea9344SChristian Gromm 		return -ENODEV;
259*ceea9344SChristian Gromm 	}
260*ceea9344SChristian Gromm 
261*ceea9344SChristian Gromm 	to_copy = min_t(size_t,
262*ceea9344SChristian Gromm 			count,
263*ceea9344SChristian Gromm 			mbo->processed_length - c->mbo_offs);
264*ceea9344SChristian Gromm 
265*ceea9344SChristian Gromm 	not_copied = copy_to_user(buf,
266*ceea9344SChristian Gromm 				  mbo->virt_address + c->mbo_offs,
267*ceea9344SChristian Gromm 				  to_copy);
268*ceea9344SChristian Gromm 
269*ceea9344SChristian Gromm 	copied = to_copy - not_copied;
270*ceea9344SChristian Gromm 
271*ceea9344SChristian Gromm 	c->mbo_offs += copied;
272*ceea9344SChristian Gromm 	if (c->mbo_offs >= mbo->processed_length) {
273*ceea9344SChristian Gromm 		kfifo_skip(&c->fifo);
274*ceea9344SChristian Gromm 		most_put_mbo(mbo);
275*ceea9344SChristian Gromm 		c->mbo_offs = 0;
276*ceea9344SChristian Gromm 	}
277*ceea9344SChristian Gromm 	mutex_unlock(&c->io_mutex);
278*ceea9344SChristian Gromm 	return copied;
279*ceea9344SChristian Gromm }
280*ceea9344SChristian Gromm 
281*ceea9344SChristian Gromm static __poll_t comp_poll(struct file *filp, poll_table *wait)
282*ceea9344SChristian Gromm {
283*ceea9344SChristian Gromm 	struct comp_channel *c = filp->private_data;
284*ceea9344SChristian Gromm 	__poll_t mask = 0;
285*ceea9344SChristian Gromm 
286*ceea9344SChristian Gromm 	poll_wait(filp, &c->wq, wait);
287*ceea9344SChristian Gromm 
288*ceea9344SChristian Gromm 	mutex_lock(&c->io_mutex);
289*ceea9344SChristian Gromm 	if (c->cfg->direction == MOST_CH_RX) {
290*ceea9344SChristian Gromm 		if (!c->dev || !kfifo_is_empty(&c->fifo))
291*ceea9344SChristian Gromm 			mask |= EPOLLIN | EPOLLRDNORM;
292*ceea9344SChristian Gromm 	} else {
293*ceea9344SChristian Gromm 		if (!c->dev || !kfifo_is_empty(&c->fifo) || ch_has_mbo(c))
294*ceea9344SChristian Gromm 			mask |= EPOLLOUT | EPOLLWRNORM;
295*ceea9344SChristian Gromm 	}
296*ceea9344SChristian Gromm 	mutex_unlock(&c->io_mutex);
297*ceea9344SChristian Gromm 	return mask;
298*ceea9344SChristian Gromm }
299*ceea9344SChristian Gromm 
300*ceea9344SChristian Gromm /**
301*ceea9344SChristian Gromm  * Initialization of struct file_operations
302*ceea9344SChristian Gromm  */
303*ceea9344SChristian Gromm static const struct file_operations channel_fops = {
304*ceea9344SChristian Gromm 	.owner = THIS_MODULE,
305*ceea9344SChristian Gromm 	.read = comp_read,
306*ceea9344SChristian Gromm 	.write = comp_write,
307*ceea9344SChristian Gromm 	.open = comp_open,
308*ceea9344SChristian Gromm 	.release = comp_close,
309*ceea9344SChristian Gromm 	.poll = comp_poll,
310*ceea9344SChristian Gromm };
311*ceea9344SChristian Gromm 
312*ceea9344SChristian Gromm /**
313*ceea9344SChristian Gromm  * comp_disconnect_channel - disconnect a channel
314*ceea9344SChristian Gromm  * @iface: pointer to interface instance
315*ceea9344SChristian Gromm  * @channel_id: channel index
316*ceea9344SChristian Gromm  *
317*ceea9344SChristian Gromm  * This frees allocated memory and removes the cdev that represents this
318*ceea9344SChristian Gromm  * channel in user space.
319*ceea9344SChristian Gromm  */
320*ceea9344SChristian Gromm static int comp_disconnect_channel(struct most_interface *iface, int channel_id)
321*ceea9344SChristian Gromm {
322*ceea9344SChristian Gromm 	struct comp_channel *c;
323*ceea9344SChristian Gromm 
324*ceea9344SChristian Gromm 	c = get_channel(iface, channel_id);
325*ceea9344SChristian Gromm 	if (!c)
326*ceea9344SChristian Gromm 		return -EINVAL;
327*ceea9344SChristian Gromm 
328*ceea9344SChristian Gromm 	mutex_lock(&c->io_mutex);
329*ceea9344SChristian Gromm 	spin_lock(&c->unlink);
330*ceea9344SChristian Gromm 	c->dev = NULL;
331*ceea9344SChristian Gromm 	spin_unlock(&c->unlink);
332*ceea9344SChristian Gromm 	destroy_cdev(c);
333*ceea9344SChristian Gromm 	if (c->access_ref) {
334*ceea9344SChristian Gromm 		stop_channel(c);
335*ceea9344SChristian Gromm 		wake_up_interruptible(&c->wq);
336*ceea9344SChristian Gromm 		mutex_unlock(&c->io_mutex);
337*ceea9344SChristian Gromm 	} else {
338*ceea9344SChristian Gromm 		mutex_unlock(&c->io_mutex);
339*ceea9344SChristian Gromm 		destroy_channel(c);
340*ceea9344SChristian Gromm 	}
341*ceea9344SChristian Gromm 	return 0;
342*ceea9344SChristian Gromm }
343*ceea9344SChristian Gromm 
344*ceea9344SChristian Gromm /**
345*ceea9344SChristian Gromm  * comp_rx_completion - completion handler for rx channels
346*ceea9344SChristian Gromm  * @mbo: pointer to buffer object that has completed
347*ceea9344SChristian Gromm  *
348*ceea9344SChristian Gromm  * This searches for the channel linked to this MBO and stores it in the local
349*ceea9344SChristian Gromm  * fifo buffer.
350*ceea9344SChristian Gromm  */
351*ceea9344SChristian Gromm static int comp_rx_completion(struct mbo *mbo)
352*ceea9344SChristian Gromm {
353*ceea9344SChristian Gromm 	struct comp_channel *c;
354*ceea9344SChristian Gromm 
355*ceea9344SChristian Gromm 	if (!mbo)
356*ceea9344SChristian Gromm 		return -EINVAL;
357*ceea9344SChristian Gromm 
358*ceea9344SChristian Gromm 	c = get_channel(mbo->ifp, mbo->hdm_channel_id);
359*ceea9344SChristian Gromm 	if (!c)
360*ceea9344SChristian Gromm 		return -EINVAL;
361*ceea9344SChristian Gromm 
362*ceea9344SChristian Gromm 	spin_lock(&c->unlink);
363*ceea9344SChristian Gromm 	if (!c->access_ref || !c->dev) {
364*ceea9344SChristian Gromm 		spin_unlock(&c->unlink);
365*ceea9344SChristian Gromm 		return -ENODEV;
366*ceea9344SChristian Gromm 	}
367*ceea9344SChristian Gromm 	kfifo_in(&c->fifo, &mbo, 1);
368*ceea9344SChristian Gromm 	spin_unlock(&c->unlink);
369*ceea9344SChristian Gromm #ifdef DEBUG_MESG
370*ceea9344SChristian Gromm 	if (kfifo_is_full(&c->fifo))
371*ceea9344SChristian Gromm 		dev_warn(c->dev, "Fifo is full\n");
372*ceea9344SChristian Gromm #endif
373*ceea9344SChristian Gromm 	wake_up_interruptible(&c->wq);
374*ceea9344SChristian Gromm 	return 0;
375*ceea9344SChristian Gromm }
376*ceea9344SChristian Gromm 
377*ceea9344SChristian Gromm /**
378*ceea9344SChristian Gromm  * comp_tx_completion - completion handler for tx channels
379*ceea9344SChristian Gromm  * @iface: pointer to interface instance
380*ceea9344SChristian Gromm  * @channel_id: channel index/ID
381*ceea9344SChristian Gromm  *
382*ceea9344SChristian Gromm  * This wakes sleeping processes in the wait-queue.
383*ceea9344SChristian Gromm  */
384*ceea9344SChristian Gromm static int comp_tx_completion(struct most_interface *iface, int channel_id)
385*ceea9344SChristian Gromm {
386*ceea9344SChristian Gromm 	struct comp_channel *c;
387*ceea9344SChristian Gromm 
388*ceea9344SChristian Gromm 	c = get_channel(iface, channel_id);
389*ceea9344SChristian Gromm 	if (!c)
390*ceea9344SChristian Gromm 		return -EINVAL;
391*ceea9344SChristian Gromm 
392*ceea9344SChristian Gromm 	if ((channel_id < 0) || (channel_id >= iface->num_channels)) {
393*ceea9344SChristian Gromm 		dev_warn(c->dev, "Channel ID out of range\n");
394*ceea9344SChristian Gromm 		return -EINVAL;
395*ceea9344SChristian Gromm 	}
396*ceea9344SChristian Gromm 
397*ceea9344SChristian Gromm 	wake_up_interruptible(&c->wq);
398*ceea9344SChristian Gromm 	return 0;
399*ceea9344SChristian Gromm }
400*ceea9344SChristian Gromm 
401*ceea9344SChristian Gromm /**
402*ceea9344SChristian Gromm  * comp_probe - probe function of the driver module
403*ceea9344SChristian Gromm  * @iface: pointer to interface instance
404*ceea9344SChristian Gromm  * @channel_id: channel index/ID
405*ceea9344SChristian Gromm  * @cfg: pointer to actual channel configuration
406*ceea9344SChristian Gromm  * @name: name of the device to be created
407*ceea9344SChristian Gromm  *
408*ceea9344SChristian Gromm  * This allocates achannel object and creates the device node in /dev
409*ceea9344SChristian Gromm  *
410*ceea9344SChristian Gromm  * Returns 0 on success or error code otherwise.
411*ceea9344SChristian Gromm  */
412*ceea9344SChristian Gromm static int comp_probe(struct most_interface *iface, int channel_id,
413*ceea9344SChristian Gromm 		      struct most_channel_config *cfg, char *name, char *args)
414*ceea9344SChristian Gromm {
415*ceea9344SChristian Gromm 	struct comp_channel *c;
416*ceea9344SChristian Gromm 	unsigned long cl_flags;
417*ceea9344SChristian Gromm 	int retval;
418*ceea9344SChristian Gromm 	int current_minor;
419*ceea9344SChristian Gromm 
420*ceea9344SChristian Gromm 	if (!cfg || !name)
421*ceea9344SChristian Gromm 		return -EINVAL;
422*ceea9344SChristian Gromm 
423*ceea9344SChristian Gromm 	c = get_channel(iface, channel_id);
424*ceea9344SChristian Gromm 	if (c)
425*ceea9344SChristian Gromm 		return -EEXIST;
426*ceea9344SChristian Gromm 
427*ceea9344SChristian Gromm 	current_minor = ida_simple_get(&comp.minor_id, 0, 0, GFP_KERNEL);
428*ceea9344SChristian Gromm 	if (current_minor < 0)
429*ceea9344SChristian Gromm 		return current_minor;
430*ceea9344SChristian Gromm 
431*ceea9344SChristian Gromm 	c = kzalloc(sizeof(*c), GFP_KERNEL);
432*ceea9344SChristian Gromm 	if (!c) {
433*ceea9344SChristian Gromm 		retval = -ENOMEM;
434*ceea9344SChristian Gromm 		goto err_remove_ida;
435*ceea9344SChristian Gromm 	}
436*ceea9344SChristian Gromm 
437*ceea9344SChristian Gromm 	c->devno = MKDEV(comp.major, current_minor);
438*ceea9344SChristian Gromm 	cdev_init(&c->cdev, &channel_fops);
439*ceea9344SChristian Gromm 	c->cdev.owner = THIS_MODULE;
440*ceea9344SChristian Gromm 	retval = cdev_add(&c->cdev, c->devno, 1);
441*ceea9344SChristian Gromm 	if (retval < 0)
442*ceea9344SChristian Gromm 		goto err_free_c;
443*ceea9344SChristian Gromm 	c->iface = iface;
444*ceea9344SChristian Gromm 	c->cfg = cfg;
445*ceea9344SChristian Gromm 	c->channel_id = channel_id;
446*ceea9344SChristian Gromm 	c->access_ref = 0;
447*ceea9344SChristian Gromm 	spin_lock_init(&c->unlink);
448*ceea9344SChristian Gromm 	INIT_KFIFO(c->fifo);
449*ceea9344SChristian Gromm 	retval = kfifo_alloc(&c->fifo, cfg->num_buffers, GFP_KERNEL);
450*ceea9344SChristian Gromm 	if (retval)
451*ceea9344SChristian Gromm 		goto err_del_cdev_and_free_channel;
452*ceea9344SChristian Gromm 	init_waitqueue_head(&c->wq);
453*ceea9344SChristian Gromm 	mutex_init(&c->io_mutex);
454*ceea9344SChristian Gromm 	spin_lock_irqsave(&ch_list_lock, cl_flags);
455*ceea9344SChristian Gromm 	list_add_tail(&c->list, &channel_list);
456*ceea9344SChristian Gromm 	spin_unlock_irqrestore(&ch_list_lock, cl_flags);
457*ceea9344SChristian Gromm 	c->dev = device_create(comp.class, NULL, c->devno, NULL, "%s", name);
458*ceea9344SChristian Gromm 
459*ceea9344SChristian Gromm 	if (IS_ERR(c->dev)) {
460*ceea9344SChristian Gromm 		retval = PTR_ERR(c->dev);
461*ceea9344SChristian Gromm 		goto err_free_kfifo_and_del_list;
462*ceea9344SChristian Gromm 	}
463*ceea9344SChristian Gromm 	kobject_uevent(&c->dev->kobj, KOBJ_ADD);
464*ceea9344SChristian Gromm 	return 0;
465*ceea9344SChristian Gromm 
466*ceea9344SChristian Gromm err_free_kfifo_and_del_list:
467*ceea9344SChristian Gromm 	kfifo_free(&c->fifo);
468*ceea9344SChristian Gromm 	list_del(&c->list);
469*ceea9344SChristian Gromm err_del_cdev_and_free_channel:
470*ceea9344SChristian Gromm 	cdev_del(&c->cdev);
471*ceea9344SChristian Gromm err_free_c:
472*ceea9344SChristian Gromm 	kfree(c);
473*ceea9344SChristian Gromm err_remove_ida:
474*ceea9344SChristian Gromm 	ida_simple_remove(&comp.minor_id, current_minor);
475*ceea9344SChristian Gromm 	return retval;
476*ceea9344SChristian Gromm }
477*ceea9344SChristian Gromm 
478*ceea9344SChristian Gromm static struct cdev_component comp = {
479*ceea9344SChristian Gromm 	.cc = {
480*ceea9344SChristian Gromm 		.mod = THIS_MODULE,
481*ceea9344SChristian Gromm 		.name = "cdev",
482*ceea9344SChristian Gromm 		.probe_channel = comp_probe,
483*ceea9344SChristian Gromm 		.disconnect_channel = comp_disconnect_channel,
484*ceea9344SChristian Gromm 		.rx_completion = comp_rx_completion,
485*ceea9344SChristian Gromm 		.tx_completion = comp_tx_completion,
486*ceea9344SChristian Gromm 	},
487*ceea9344SChristian Gromm };
488*ceea9344SChristian Gromm 
489*ceea9344SChristian Gromm static int __init mod_init(void)
490*ceea9344SChristian Gromm {
491*ceea9344SChristian Gromm 	int err;
492*ceea9344SChristian Gromm 
493*ceea9344SChristian Gromm 	comp.class = class_create(THIS_MODULE, "most_cdev");
494*ceea9344SChristian Gromm 	if (IS_ERR(comp.class))
495*ceea9344SChristian Gromm 		return PTR_ERR(comp.class);
496*ceea9344SChristian Gromm 
497*ceea9344SChristian Gromm 	INIT_LIST_HEAD(&channel_list);
498*ceea9344SChristian Gromm 	spin_lock_init(&ch_list_lock);
499*ceea9344SChristian Gromm 	ida_init(&comp.minor_id);
500*ceea9344SChristian Gromm 
501*ceea9344SChristian Gromm 	err = alloc_chrdev_region(&comp.devno, 0, CHRDEV_REGION_SIZE, "cdev");
502*ceea9344SChristian Gromm 	if (err < 0)
503*ceea9344SChristian Gromm 		goto dest_ida;
504*ceea9344SChristian Gromm 	comp.major = MAJOR(comp.devno);
505*ceea9344SChristian Gromm 	err = most_register_component(&comp.cc);
506*ceea9344SChristian Gromm 	if (err)
507*ceea9344SChristian Gromm 		goto free_cdev;
508*ceea9344SChristian Gromm 	err = most_register_configfs_subsys(&comp.cc);
509*ceea9344SChristian Gromm 	if (err)
510*ceea9344SChristian Gromm 		goto deregister_comp;
511*ceea9344SChristian Gromm 	return 0;
512*ceea9344SChristian Gromm 
513*ceea9344SChristian Gromm deregister_comp:
514*ceea9344SChristian Gromm 	most_deregister_component(&comp.cc);
515*ceea9344SChristian Gromm free_cdev:
516*ceea9344SChristian Gromm 	unregister_chrdev_region(comp.devno, CHRDEV_REGION_SIZE);
517*ceea9344SChristian Gromm dest_ida:
518*ceea9344SChristian Gromm 	ida_destroy(&comp.minor_id);
519*ceea9344SChristian Gromm 	class_destroy(comp.class);
520*ceea9344SChristian Gromm 	return err;
521*ceea9344SChristian Gromm }
522*ceea9344SChristian Gromm 
523*ceea9344SChristian Gromm static void __exit mod_exit(void)
524*ceea9344SChristian Gromm {
525*ceea9344SChristian Gromm 	struct comp_channel *c, *tmp;
526*ceea9344SChristian Gromm 
527*ceea9344SChristian Gromm 	most_deregister_configfs_subsys(&comp.cc);
528*ceea9344SChristian Gromm 	most_deregister_component(&comp.cc);
529*ceea9344SChristian Gromm 
530*ceea9344SChristian Gromm 	list_for_each_entry_safe(c, tmp, &channel_list, list) {
531*ceea9344SChristian Gromm 		destroy_cdev(c);
532*ceea9344SChristian Gromm 		destroy_channel(c);
533*ceea9344SChristian Gromm 	}
534*ceea9344SChristian Gromm 	unregister_chrdev_region(comp.devno, CHRDEV_REGION_SIZE);
535*ceea9344SChristian Gromm 	ida_destroy(&comp.minor_id);
536*ceea9344SChristian Gromm 	class_destroy(comp.class);
537*ceea9344SChristian Gromm }
538*ceea9344SChristian Gromm 
539*ceea9344SChristian Gromm module_init(mod_init);
540*ceea9344SChristian Gromm module_exit(mod_exit);
541*ceea9344SChristian Gromm MODULE_AUTHOR("Christian Gromm <[email protected]>");
542*ceea9344SChristian Gromm MODULE_LICENSE("GPL");
543*ceea9344SChristian Gromm MODULE_DESCRIPTION("character device component for mostcore");
544