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