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