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); | |
9b28e148 | 133 | return -ENODEV; |
fa96b8ed CG |
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 | { | |
5adf5dc5 | 186 | int ret; |
fa96b8ed CG |
187 | size_t actual_len; |
188 | size_t max_len; | |
fa96b8ed | 189 | struct mbo *mbo = NULL; |
d8b082e6 | 190 | struct aim_channel *c = filp->private_data; |
9bc79bbc | 191 | |
d8b082e6 | 192 | mutex_lock(&c->io_mutex); |
fa96b8ed | 193 | while (c->dev && !ch_get_mbo(c, &mbo)) { |
d8b082e6 | 194 | mutex_unlock(&c->io_mutex); |
9bc79bbc | 195 | |
9bc79bbc CG |
196 | if ((filp->f_flags & O_NONBLOCK)) |
197 | return -EAGAIN; | |
fa96b8ed | 198 | if (wait_event_interruptible(c->wq, ch_has_mbo(c) || !c->dev)) |
9bc79bbc | 199 | return -ERESTARTSYS; |
fa96b8ed | 200 | mutex_lock(&c->io_mutex); |
9bc79bbc CG |
201 | } |
202 | ||
d8b082e6 | 203 | if (unlikely(!c->dev)) { |
9b28e148 | 204 | ret = -ENODEV; |
5adf5dc5 | 205 | goto unlock; |
9bc79bbc | 206 | } |
9bc79bbc | 207 | |
d8b082e6 | 208 | max_len = c->cfg->buffer_size; |
9bc79bbc CG |
209 | actual_len = min(count, max_len); |
210 | mbo->buffer_length = actual_len; | |
211 | ||
5adf5dc5 CG |
212 | if (copy_from_user(mbo->virt_address, buf, mbo->buffer_length)) { |
213 | ret = -EFAULT; | |
214 | goto put_mbo; | |
9bc79bbc CG |
215 | } |
216 | ||
217 | ret = most_submit_mbo(mbo); | |
5adf5dc5 CG |
218 | if (ret) |
219 | goto put_mbo; | |
220 | ||
fa96b8ed | 221 | mutex_unlock(&c->io_mutex); |
5adf5dc5 CG |
222 | return actual_len; |
223 | put_mbo: | |
224 | most_put_mbo(mbo); | |
225 | unlock: | |
fa96b8ed | 226 | mutex_unlock(&c->io_mutex); |
5adf5dc5 | 227 | return ret; |
9bc79bbc CG |
228 | } |
229 | ||
230 | /** | |
231 | * aim_read - implements the syscall to read from the device | |
232 | * @filp: file pointer | |
233 | * @buf: pointer to user buffer | |
234 | * @count: number of bytes to read | |
235 | * @offset: offset from where to start reading | |
236 | */ | |
237 | static ssize_t | |
238 | aim_read(struct file *filp, char __user *buf, size_t count, loff_t *offset) | |
239 | { | |
06e7ecf2 | 240 | size_t to_copy, not_copied, copied; |
9bc79bbc | 241 | struct mbo *mbo; |
d8b082e6 | 242 | struct aim_channel *c = filp->private_data; |
9bc79bbc | 243 | |
fa96b8ed | 244 | mutex_lock(&c->io_mutex); |
f45b0fba | 245 | while (c->dev && !kfifo_peek(&c->fifo, &mbo)) { |
fa96b8ed | 246 | mutex_unlock(&c->io_mutex); |
9bc79bbc CG |
247 | if (filp->f_flags & O_NONBLOCK) |
248 | return -EAGAIN; | |
d8b082e6 CG |
249 | if (wait_event_interruptible(c->wq, |
250 | (!kfifo_is_empty(&c->fifo) || | |
251 | (!c->dev)))) | |
9bc79bbc | 252 | return -ERESTARTSYS; |
fa96b8ed | 253 | mutex_lock(&c->io_mutex); |
9bc79bbc CG |
254 | } |
255 | ||
9bc79bbc | 256 | /* make sure we don't submit to gone devices */ |
d8b082e6 CG |
257 | if (unlikely(!c->dev)) { |
258 | mutex_unlock(&c->io_mutex); | |
9b28e148 | 259 | return -ENODEV; |
9bc79bbc CG |
260 | } |
261 | ||
e6d6cbe3 CG |
262 | to_copy = min_t(size_t, |
263 | count, | |
d8b082e6 | 264 | mbo->processed_length - c->mbo_offs); |
9bc79bbc CG |
265 | |
266 | not_copied = copy_to_user(buf, | |
d8b082e6 | 267 | mbo->virt_address + c->mbo_offs, |
f9f24870 | 268 | to_copy); |
9bc79bbc | 269 | |
4aa575a9 | 270 | copied = to_copy - not_copied; |
9bc79bbc | 271 | |
d8b082e6 CG |
272 | c->mbo_offs += copied; |
273 | if (c->mbo_offs >= mbo->processed_length) { | |
f45b0fba | 274 | kfifo_skip(&c->fifo); |
9bc79bbc | 275 | most_put_mbo(mbo); |
d8b082e6 | 276 | c->mbo_offs = 0; |
9bc79bbc | 277 | } |
d8b082e6 | 278 | mutex_unlock(&c->io_mutex); |
f9f24870 | 279 | return copied; |
9bc79bbc CG |
280 | } |
281 | ||
aac997df CG |
282 | static unsigned int aim_poll(struct file *filp, poll_table *wait) |
283 | { | |
284 | struct aim_channel *c = filp->private_data; | |
285 | unsigned int mask = 0; | |
286 | ||
c27fc351 | 287 | poll_wait(filp, &c->wq, wait); |
aac997df CG |
288 | |
289 | if (c->cfg->direction == MOST_CH_RX) { | |
290 | if (!kfifo_is_empty(&c->fifo)) | |
291 | mask |= POLLIN | POLLRDNORM; | |
292 | } else { | |
cdc293d5 | 293 | if (ch_has_mbo(c)) |
aac997df CG |
294 | mask |= POLLOUT | POLLWRNORM; |
295 | } | |
296 | return mask; | |
297 | } | |
298 | ||
9bc79bbc CG |
299 | /** |
300 | * Initialization of struct file_operations | |
301 | */ | |
302 | static const struct file_operations channel_fops = { | |
303 | .owner = THIS_MODULE, | |
304 | .read = aim_read, | |
305 | .write = aim_write, | |
306 | .open = aim_open, | |
307 | .release = aim_close, | |
aac997df | 308 | .poll = aim_poll, |
9bc79bbc CG |
309 | }; |
310 | ||
311 | /** | |
312 | * aim_disconnect_channel - disconnect a channel | |
313 | * @iface: pointer to interface instance | |
314 | * @channel_id: channel index | |
315 | * | |
316 | * This frees allocated memory and removes the cdev that represents this | |
317 | * channel in user space. | |
318 | */ | |
5132fcd1 | 319 | static int aim_disconnect_channel(struct most_interface *iface, int channel_id) |
9bc79bbc | 320 | { |
d8b082e6 | 321 | struct aim_channel *c; |
9bc79bbc CG |
322 | |
323 | if (!iface) { | |
324 | pr_info("Bad interface pointer\n"); | |
325 | return -EINVAL; | |
326 | } | |
327 | ||
d8b082e6 CG |
328 | c = get_channel(iface, channel_id); |
329 | if (!c) | |
9bc79bbc CG |
330 | return -ENXIO; |
331 | ||
d8b082e6 | 332 | mutex_lock(&c->io_mutex); |
fa96b8ed | 333 | spin_lock(&c->unlink); |
d8b082e6 | 334 | c->dev = NULL; |
fa96b8ed | 335 | spin_unlock(&c->unlink); |
b3c9f3c5 | 336 | if (c->access_ref) { |
fa96b8ed CG |
337 | stop_channel(c); |
338 | wake_up_interruptible(&c->wq); | |
339 | mutex_unlock(&c->io_mutex); | |
340 | } else { | |
d8b082e6 | 341 | destroy_cdev(c); |
fa96b8ed | 342 | mutex_unlock(&c->io_mutex); |
d8b082e6 | 343 | kfree(c); |
9bc79bbc CG |
344 | } |
345 | return 0; | |
346 | } | |
347 | ||
348 | /** | |
349 | * aim_rx_completion - completion handler for rx channels | |
350 | * @mbo: pointer to buffer object that has completed | |
351 | * | |
352 | * This searches for the channel linked to this MBO and stores it in the local | |
353 | * fifo buffer. | |
354 | */ | |
5132fcd1 | 355 | static int aim_rx_completion(struct mbo *mbo) |
9bc79bbc | 356 | { |
d8b082e6 | 357 | struct aim_channel *c; |
9bc79bbc CG |
358 | |
359 | if (!mbo) | |
360 | return -EINVAL; | |
361 | ||
d8b082e6 CG |
362 | c = get_channel(mbo->ifp, mbo->hdm_channel_id); |
363 | if (!c) | |
9bc79bbc CG |
364 | return -ENXIO; |
365 | ||
fa96b8ed | 366 | spin_lock(&c->unlink); |
b3c9f3c5 | 367 | if (!c->access_ref || !c->dev) { |
fa96b8ed | 368 | spin_unlock(&c->unlink); |
9b28e148 | 369 | return -ENODEV; |
fa96b8ed | 370 | } |
d8b082e6 | 371 | kfifo_in(&c->fifo, &mbo, 1); |
fa96b8ed | 372 | spin_unlock(&c->unlink); |
9bc79bbc | 373 | #ifdef DEBUG_MESG |
d8b082e6 | 374 | if (kfifo_is_full(&c->fifo)) |
9bc79bbc CG |
375 | pr_info("WARN: Fifo is full\n"); |
376 | #endif | |
d8b082e6 | 377 | wake_up_interruptible(&c->wq); |
9bc79bbc CG |
378 | return 0; |
379 | } | |
380 | ||
381 | /** | |
382 | * aim_tx_completion - completion handler for tx channels | |
383 | * @iface: pointer to interface instance | |
384 | * @channel_id: channel index/ID | |
385 | * | |
386 | * This wakes sleeping processes in the wait-queue. | |
387 | */ | |
5132fcd1 | 388 | static int aim_tx_completion(struct most_interface *iface, int channel_id) |
9bc79bbc | 389 | { |
d8b082e6 | 390 | struct aim_channel *c; |
9bc79bbc CG |
391 | |
392 | if (!iface) { | |
393 | pr_info("Bad interface pointer\n"); | |
394 | return -EINVAL; | |
395 | } | |
396 | if ((channel_id < 0) || (channel_id >= iface->num_channels)) { | |
397 | pr_info("Channel ID out of range\n"); | |
398 | return -EINVAL; | |
399 | } | |
400 | ||
d8b082e6 CG |
401 | c = get_channel(iface, channel_id); |
402 | if (!c) | |
9bc79bbc | 403 | return -ENXIO; |
d8b082e6 | 404 | wake_up_interruptible(&c->wq); |
9bc79bbc CG |
405 | return 0; |
406 | } | |
407 | ||
9bc79bbc CG |
408 | /** |
409 | * aim_probe - probe function of the driver module | |
410 | * @iface: pointer to interface instance | |
411 | * @channel_id: channel index/ID | |
412 | * @cfg: pointer to actual channel configuration | |
413 | * @parent: pointer to kobject (needed for sysfs hook-up) | |
414 | * @name: name of the device to be created | |
415 | * | |
416 | * This allocates achannel object and creates the device node in /dev | |
417 | * | |
418 | * Returns 0 on success or error code otherwise. | |
419 | */ | |
5132fcd1 AR |
420 | static int aim_probe(struct most_interface *iface, int channel_id, |
421 | struct most_channel_config *cfg, | |
422 | struct kobject *parent, char *name) | |
9bc79bbc | 423 | { |
d8b082e6 | 424 | struct aim_channel *c; |
9bc79bbc CG |
425 | unsigned long cl_flags; |
426 | int retval; | |
427 | int current_minor; | |
428 | ||
429 | if ((!iface) || (!cfg) || (!parent) || (!name)) { | |
430 | pr_info("Probing AIM with bad arguments"); | |
431 | return -EINVAL; | |
432 | } | |
d8b082e6 CG |
433 | c = get_channel(iface, channel_id); |
434 | if (c) | |
9bc79bbc CG |
435 | return -EEXIST; |
436 | ||
437 | current_minor = ida_simple_get(&minor_id, 0, 0, GFP_KERNEL); | |
438 | if (current_minor < 0) | |
439 | return current_minor; | |
440 | ||
d8b082e6 CG |
441 | c = kzalloc(sizeof(*c), GFP_KERNEL); |
442 | if (!c) { | |
9bc79bbc CG |
443 | retval = -ENOMEM; |
444 | goto error_alloc_channel; | |
445 | } | |
446 | ||
d8b082e6 CG |
447 | c->devno = MKDEV(major, current_minor); |
448 | cdev_init(&c->cdev, &channel_fops); | |
449 | c->cdev.owner = THIS_MODULE; | |
450 | cdev_add(&c->cdev, c->devno, 1); | |
451 | c->iface = iface; | |
452 | c->cfg = cfg; | |
453 | c->channel_id = channel_id; | |
b3c9f3c5 | 454 | c->access_ref = 0; |
fa96b8ed | 455 | spin_lock_init(&c->unlink); |
d8b082e6 CG |
456 | INIT_KFIFO(c->fifo); |
457 | retval = kfifo_alloc(&c->fifo, cfg->num_buffers, GFP_KERNEL); | |
9bc79bbc CG |
458 | if (retval) { |
459 | pr_info("failed to alloc channel kfifo"); | |
460 | goto error_alloc_kfifo; | |
461 | } | |
d8b082e6 CG |
462 | init_waitqueue_head(&c->wq); |
463 | mutex_init(&c->io_mutex); | |
9bc79bbc | 464 | spin_lock_irqsave(&ch_list_lock, cl_flags); |
d8b082e6 | 465 | list_add_tail(&c->list, &channel_list); |
9bc79bbc | 466 | spin_unlock_irqrestore(&ch_list_lock, cl_flags); |
d8b082e6 | 467 | c->dev = device_create(aim_class, |
9bc79bbc | 468 | NULL, |
d8b082e6 | 469 | c->devno, |
9bc79bbc CG |
470 | NULL, |
471 | "%s", name); | |
472 | ||
ea398547 SM |
473 | if (IS_ERR(c->dev)) { |
474 | retval = PTR_ERR(c->dev); | |
9bc79bbc CG |
475 | pr_info("failed to create new device node %s\n", name); |
476 | goto error_create_device; | |
477 | } | |
d8b082e6 | 478 | kobject_uevent(&c->dev->kobj, KOBJ_ADD); |
9bc79bbc CG |
479 | return 0; |
480 | ||
481 | error_create_device: | |
d8b082e6 CG |
482 | kfifo_free(&c->fifo); |
483 | list_del(&c->list); | |
9bc79bbc | 484 | error_alloc_kfifo: |
d8b082e6 CG |
485 | cdev_del(&c->cdev); |
486 | kfree(c); | |
9bc79bbc CG |
487 | error_alloc_channel: |
488 | ida_simple_remove(&minor_id, current_minor); | |
489 | return retval; | |
490 | } | |
491 | ||
5132fcd1 | 492 | static struct most_aim cdev_aim = { |
9bc79bbc CG |
493 | .name = "cdev", |
494 | .probe_channel = aim_probe, | |
495 | .disconnect_channel = aim_disconnect_channel, | |
496 | .rx_completion = aim_rx_completion, | |
497 | .tx_completion = aim_tx_completion, | |
498 | }; | |
499 | ||
500 | static int __init mod_init(void) | |
501 | { | |
9b28e148 CG |
502 | int err; |
503 | ||
9bc79bbc CG |
504 | pr_info("init()\n"); |
505 | ||
506 | INIT_LIST_HEAD(&channel_list); | |
507 | spin_lock_init(&ch_list_lock); | |
508 | ida_init(&minor_id); | |
509 | ||
324e87b7 CG |
510 | err = alloc_chrdev_region(&aim_devno, 0, 50, "cdev"); |
511 | if (err < 0) | |
1d9e3a07 | 512 | goto dest_ida; |
9bc79bbc CG |
513 | major = MAJOR(aim_devno); |
514 | ||
515 | aim_class = class_create(THIS_MODULE, "most_cdev_aim"); | |
516 | if (IS_ERR(aim_class)) { | |
517 | pr_err("no udev support\n"); | |
9b28e148 | 518 | err = PTR_ERR(aim_class); |
9bc79bbc CG |
519 | goto free_cdev; |
520 | } | |
9b28e148 CG |
521 | err = most_register_aim(&cdev_aim); |
522 | if (err) | |
9bc79bbc CG |
523 | goto dest_class; |
524 | return 0; | |
525 | ||
526 | dest_class: | |
527 | class_destroy(aim_class); | |
528 | free_cdev: | |
529 | unregister_chrdev_region(aim_devno, 1); | |
1d9e3a07 CG |
530 | dest_ida: |
531 | ida_destroy(&minor_id); | |
9b28e148 | 532 | return err; |
9bc79bbc CG |
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"); |