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