dcd5b650a0008c93e9c02a82cd288f2ed1506a8f
[safe/jmp/linux-2.6] / drivers / isdn / hardware / eicon / divasi.c
1 /* $Id: divasi.c,v 1.25.6.2 2005/01/31 12:22:20 armin Exp $
2  *
3  * Driver for Eicon DIVA Server ISDN cards.
4  * User Mode IDI Interface 
5  *
6  * Copyright 2000-2003 by Armin Schindler (mac@melware.de)
7  * Copyright 2000-2003 Cytronics & Melware (info@melware.de)
8  *
9  * This software may be used and distributed according to the terms
10  * of the GNU General Public License, incorporated herein by reference.
11  */
12
13 #include <linux/config.h>
14 #include <linux/module.h>
15 #include <linux/init.h>
16 #include <linux/kernel.h>
17 #include <linux/sched.h>
18 #include <linux/smp_lock.h>
19 #include <linux/poll.h>
20 #include <linux/proc_fs.h>
21 #include <linux/skbuff.h>
22 #include <linux/devfs_fs_kernel.h>
23 #include <asm/uaccess.h>
24
25 #include "platform.h"
26 #include "di_defs.h"
27 #include "divasync.h"
28 #include "um_xdi.h"
29 #include "um_idi.h"
30
31 static char *main_revision = "$Revision: 1.25.6.2 $";
32
33 static int major;
34
35 MODULE_DESCRIPTION("User IDI Interface for Eicon ISDN cards");
36 MODULE_AUTHOR("Cytronics & Melware, Eicon Networks");
37 MODULE_SUPPORTED_DEVICE("DIVA card driver");
38 MODULE_LICENSE("GPL");
39
40 typedef struct _diva_um_idi_os_context {
41         wait_queue_head_t read_wait;
42         wait_queue_head_t close_wait;
43         struct timer_list diva_timer_id;
44         int aborted;
45         int adapter_nr;
46 } diva_um_idi_os_context_t;
47
48 static char *DRIVERNAME = "Eicon DIVA - User IDI (http://www.melware.net)";
49 static char *DRIVERLNAME = "diva_idi";
50 static char *DEVNAME = "DivasIDI";
51 char *DRIVERRELEASE_IDI = "2.0";
52
53 extern int idifunc_init(void);
54 extern void idifunc_finit(void);
55
56 /*
57  *  helper functions
58  */
59 static char *getrev(const char *revision)
60 {
61         char *rev;
62         char *p;
63         if ((p = strchr(revision, ':'))) {
64                 rev = p + 2;
65                 p = strchr(rev, '$');
66                 *--p = 0;
67         } else
68                 rev = "1.0";
69         return rev;
70 }
71
72 /*
73  *  LOCALS
74  */
75 static ssize_t um_idi_read(struct file *file, char __user *buf, size_t count,
76                            loff_t * offset);
77 static ssize_t um_idi_write(struct file *file, const char __user *buf,
78                             size_t count, loff_t * offset);
79 static unsigned int um_idi_poll(struct file *file, poll_table * wait);
80 static int um_idi_open(struct inode *inode, struct file *file);
81 static int um_idi_release(struct inode *inode, struct file *file);
82 static int remove_entity(void *entity);
83 static void diva_um_timer_function(unsigned long data);
84
85 /*
86  * proc entry
87  */
88 extern struct proc_dir_entry *proc_net_eicon;
89 static struct proc_dir_entry *um_idi_proc_entry = NULL;
90
91 static int
92 um_idi_proc_read(char *page, char **start, off_t off, int count, int *eof,
93                  void *data)
94 {
95         int len = 0;
96         char tmprev[32];
97
98         len += sprintf(page + len, "%s\n", DRIVERNAME);
99         len += sprintf(page + len, "name     : %s\n", DRIVERLNAME);
100         len += sprintf(page + len, "release  : %s\n", DRIVERRELEASE_IDI);
101         strcpy(tmprev, main_revision);
102         len += sprintf(page + len, "revision : %s\n", getrev(tmprev));
103         len += sprintf(page + len, "build    : %s\n", DIVA_BUILD);
104         len += sprintf(page + len, "major    : %d\n", major);
105
106         if (off + count >= len)
107                 *eof = 1;
108         if (len < off)
109                 return 0;
110         *start = page + off;
111         return ((count < len - off) ? count : len - off);
112 }
113
114 static int DIVA_INIT_FUNCTION create_um_idi_proc(void)
115 {
116         um_idi_proc_entry = create_proc_entry(DRIVERLNAME,
117                                               S_IFREG | S_IRUGO | S_IWUSR,
118                                               proc_net_eicon);
119         if (!um_idi_proc_entry)
120                 return (0);
121
122         um_idi_proc_entry->read_proc = um_idi_proc_read;
123         um_idi_proc_entry->owner = THIS_MODULE;
124
125         return (1);
126 }
127
128 static void remove_um_idi_proc(void)
129 {
130         if (um_idi_proc_entry) {
131                 remove_proc_entry(DRIVERLNAME, proc_net_eicon);
132                 um_idi_proc_entry = NULL;
133         }
134 }
135
136 static struct file_operations divas_idi_fops = {
137         .owner   = THIS_MODULE,
138         .llseek  = no_llseek,
139         .read    = um_idi_read,
140         .write   = um_idi_write,
141         .poll    = um_idi_poll,
142         .open    = um_idi_open,
143         .release = um_idi_release
144 };
145
146 static void divas_idi_unregister_chrdev(void)
147 {
148         unregister_chrdev(major, DEVNAME);
149 }
150
151 static int DIVA_INIT_FUNCTION divas_idi_register_chrdev(void)
152 {
153         if ((major = register_chrdev(0, DEVNAME, &divas_idi_fops)) < 0)
154         {
155                 printk(KERN_ERR "%s: failed to create /dev entry.\n",
156                        DRIVERLNAME);
157                 return (0);
158         }
159
160         return (1);
161 }
162
163 /*
164 ** Driver Load
165 */
166 static int DIVA_INIT_FUNCTION divasi_init(void)
167 {
168         char tmprev[50];
169         int ret = 0;
170
171         printk(KERN_INFO "%s\n", DRIVERNAME);
172         printk(KERN_INFO "%s: Rel:%s  Rev:", DRIVERLNAME, DRIVERRELEASE_IDI);
173         strcpy(tmprev, main_revision);
174         printk("%s  Build: %s\n", getrev(tmprev), DIVA_BUILD);
175
176         if (!divas_idi_register_chrdev()) {
177                 ret = -EIO;
178                 goto out;
179         }
180
181         if (!create_um_idi_proc()) {
182                 divas_idi_unregister_chrdev();
183                 printk(KERN_ERR "%s: failed to create proc entry.\n",
184                        DRIVERLNAME);
185                 ret = -EIO;
186                 goto out;
187         }
188
189         if (!(idifunc_init())) {
190                 remove_um_idi_proc();
191                 divas_idi_unregister_chrdev();
192                 printk(KERN_ERR "%s: failed to connect to DIDD.\n",
193                        DRIVERLNAME);
194                 ret = -EIO;
195                 goto out;
196         }
197         printk(KERN_INFO "%s: started with major %d\n", DRIVERLNAME, major);
198
199       out:
200         return (ret);
201 }
202
203
204 /*
205 ** Driver Unload
206 */
207 static void DIVA_EXIT_FUNCTION divasi_exit(void)
208 {
209         idifunc_finit();
210         remove_um_idi_proc();
211         divas_idi_unregister_chrdev();
212
213         printk(KERN_INFO "%s: module unloaded.\n", DRIVERLNAME);
214 }
215
216 module_init(divasi_init);
217 module_exit(divasi_exit);
218
219
220 /*
221  *  FILE OPERATIONS
222  */
223
224 static int
225 divas_um_idi_copy_to_user(void *os_handle, void *dst, const void *src,
226                           int length)
227 {
228         memcpy(dst, src, length);
229         return (length);
230 }
231
232 static ssize_t
233 um_idi_read(struct file *file, char __user *buf, size_t count, loff_t * offset)
234 {
235         diva_um_idi_os_context_t *p_os;
236         int ret = -EINVAL;
237         void *data;
238
239         if (!file->private_data) {
240                 return (-ENODEV);
241         }
242
243         if (!
244             (p_os =
245              (diva_um_idi_os_context_t *) diva_um_id_get_os_context(file->
246                                                                     private_data)))
247         {
248                 return (-ENODEV);
249         }
250         if (p_os->aborted) {
251                 return (-ENODEV);
252         }
253
254         if (!(data = diva_os_malloc(0, count))) {
255                 return (-ENOMEM);
256         }
257
258         ret = diva_um_idi_read(file->private_data,
259                                file, data, count,
260                                divas_um_idi_copy_to_user);
261         switch (ret) {
262         case 0:         /* no message available */
263                 ret = (-EAGAIN);
264                 break;
265         case (-1):              /* adapter was removed */
266                 ret = (-ENODEV);
267                 break;
268         case (-2):              /* message_length > length of user buffer */
269                 ret = (-EFAULT);
270                 break;
271         }
272
273         if (ret > 0) {
274                 if (copy_to_user(buf, data, ret)) {
275                         ret = (-EFAULT);
276                 }
277         }
278
279         diva_os_free(0, data);
280         DBG_TRC(("read: ret %d", ret));
281         return (ret);
282 }
283
284
285 static int
286 divas_um_idi_copy_from_user(void *os_handle, void *dst, const void *src,
287                             int length)
288 {
289         memcpy(dst, src, length);
290         return (length);
291 }
292
293 static int um_idi_open_adapter(struct file *file, int adapter_nr)
294 {
295         diva_um_idi_os_context_t *p_os;
296         void *e =
297             divas_um_idi_create_entity((dword) adapter_nr, (void *) file);
298
299         if (!(file->private_data = e)) {
300                 return (0);
301         }
302         p_os = (diva_um_idi_os_context_t *) diva_um_id_get_os_context(e);
303         init_waitqueue_head(&p_os->read_wait);
304         init_waitqueue_head(&p_os->close_wait);
305         init_timer(&p_os->diva_timer_id);
306         p_os->diva_timer_id.function = (void *) diva_um_timer_function;
307         p_os->diva_timer_id.data = (unsigned long) p_os;
308         p_os->aborted = 0;
309         p_os->adapter_nr = adapter_nr;
310         return (1);
311 }
312
313 static ssize_t
314 um_idi_write(struct file *file, const char __user *buf, size_t count,
315              loff_t * offset)
316 {
317         diva_um_idi_os_context_t *p_os;
318         int ret = -EINVAL;
319         void *data;
320         int adapter_nr = 0;
321
322         if (!file->private_data) {
323                 /* the first write() selects the adapter_nr */
324                 if (count == sizeof(int)) {
325                         if (copy_from_user
326                             ((void *) &adapter_nr, buf,
327                              count)) return (-EFAULT);
328                         if (!(um_idi_open_adapter(file, adapter_nr)))
329                                 return (-ENODEV);
330                         return (count);
331                 } else
332                         return (-ENODEV);
333         }
334
335         if (!(p_os =
336              (diva_um_idi_os_context_t *) diva_um_id_get_os_context(file->
337                                                                     private_data)))
338         {
339                 return (-ENODEV);
340         }
341         if (p_os->aborted) {
342                 return (-ENODEV);
343         }
344
345         if (!(data = diva_os_malloc(0, count))) {
346                 return (-ENOMEM);
347         }
348
349         if (copy_from_user(data, buf, count)) {
350                 ret = -EFAULT;
351         } else {
352                 ret = diva_um_idi_write(file->private_data,
353                                         file, data, count,
354                                         divas_um_idi_copy_from_user);
355                 switch (ret) {
356                 case 0: /* no space available */
357                         ret = (-EAGAIN);
358                         break;
359                 case (-1):      /* adapter was removed */
360                         ret = (-ENODEV);
361                         break;
362                 case (-2):      /* length of user buffer > max message_length */
363                         ret = (-EFAULT);
364                         break;
365                 }
366         }
367         diva_os_free(0, data);
368         DBG_TRC(("write: ret %d", ret));
369         return (ret);
370 }
371
372 static unsigned int um_idi_poll(struct file *file, poll_table * wait)
373 {
374         diva_um_idi_os_context_t *p_os;
375
376         if (!file->private_data) {
377                 return (POLLERR);
378         }
379
380         if ((!(p_os =
381                (diva_um_idi_os_context_t *)
382                diva_um_id_get_os_context(file->private_data)))
383             || p_os->aborted) {
384                 return (POLLERR);
385         }
386
387         poll_wait(file, &p_os->read_wait, wait);
388
389         if (p_os->aborted) {
390                 return (POLLERR);
391         }
392
393         switch (diva_user_mode_idi_ind_ready(file->private_data, file)) {
394         case (-1):
395                 return (POLLERR);
396
397         case 0:
398                 return (0);
399         }
400
401         return (POLLIN | POLLRDNORM);
402 }
403
404 static int um_idi_open(struct inode *inode, struct file *file)
405 {
406         return (0);
407 }
408
409
410 static int um_idi_release(struct inode *inode, struct file *file)
411 {
412         diva_um_idi_os_context_t *p_os;
413         unsigned int adapter_nr;
414         int ret = 0;
415
416         if (!(file->private_data)) {
417                 ret = -ENODEV;
418                 goto out;
419         }
420
421         if (!(p_os =
422                 (diva_um_idi_os_context_t *) diva_um_id_get_os_context(file->private_data))) {
423                 ret = -ENODEV;
424                 goto out;
425         }
426
427         adapter_nr = p_os->adapter_nr;
428
429         if ((ret = remove_entity(file->private_data))) {
430                 goto out;
431         }
432
433         if (divas_um_idi_delete_entity
434             ((int) adapter_nr, file->private_data)) {
435                 ret = -ENODEV;
436                 goto out;
437         }
438
439       out:
440         return (ret);
441 }
442
443 int diva_os_get_context_size(void)
444 {
445         return (sizeof(diva_um_idi_os_context_t));
446 }
447
448 void diva_os_wakeup_read(void *os_context)
449 {
450         diva_um_idi_os_context_t *p_os =
451             (diva_um_idi_os_context_t *) os_context;
452         wake_up_interruptible(&p_os->read_wait);
453 }
454
455 void diva_os_wakeup_close(void *os_context)
456 {
457         diva_um_idi_os_context_t *p_os =
458             (diva_um_idi_os_context_t *) os_context;
459         wake_up_interruptible(&p_os->close_wait);
460 }
461
462 static
463 void diva_um_timer_function(unsigned long data)
464 {
465         diva_um_idi_os_context_t *p_os = (diva_um_idi_os_context_t *) data;
466
467         p_os->aborted = 1;
468         wake_up_interruptible(&p_os->read_wait);
469         wake_up_interruptible(&p_os->close_wait);
470         DBG_ERR(("entity removal watchdog"))
471 }
472
473 /*
474 **  If application exits without entity removal this function will remove
475 **  entity and block until removal is complete
476 */
477 static int remove_entity(void *entity)
478 {
479         struct task_struct *curtask = current;
480         diva_um_idi_os_context_t *p_os;
481
482         diva_um_idi_stop_wdog(entity);
483
484         if (!entity) {
485                 DBG_FTL(("Zero entity on remove"))
486                 return (0);
487         }
488
489         if (!(p_os =
490              (diva_um_idi_os_context_t *)
491              diva_um_id_get_os_context(entity))) {
492                 DBG_FTL(("Zero entity os context on remove"))
493                 return (0);
494         }
495
496         if (!divas_um_idi_entity_assigned(entity) || p_os->aborted) {
497                 /*
498                    Entity is not assigned, also can be removed
499                  */
500                 return (0);
501         }
502
503         DBG_TRC(("E(%08x) check remove", entity))
504
505         /*
506            If adapter not answers on remove request inside of
507            10 Sec, then adapter is dead
508          */
509         diva_um_idi_start_wdog(entity);
510
511         {
512                 DECLARE_WAITQUEUE(wait, curtask);
513
514                 add_wait_queue(&p_os->close_wait, &wait);
515                 for (;;) {
516                         set_current_state(TASK_INTERRUPTIBLE);
517                         if (!divas_um_idi_entity_start_remove(entity)
518                             || p_os->aborted) {
519                                 break;
520                         }
521                         schedule();
522                 }
523                 set_current_state(TASK_RUNNING);
524                 remove_wait_queue(&p_os->close_wait, &wait);
525         }
526
527         DBG_TRC(("E(%08x) start remove", entity))
528         {
529                 DECLARE_WAITQUEUE(wait, curtask);
530
531                 add_wait_queue(&p_os->close_wait, &wait);
532                 for (;;) {
533                         set_current_state(TASK_INTERRUPTIBLE);
534                         if (!divas_um_idi_entity_assigned(entity)
535                             || p_os->aborted) {
536                                 break;
537                         }
538                         schedule();
539                 }
540                 set_current_state(TASK_RUNNING);
541                 remove_wait_queue(&p_os->close_wait, &wait);
542         }
543
544         DBG_TRC(("E(%08x) remove complete, aborted:%d", entity,
545                  p_os->aborted))
546
547         diva_um_idi_stop_wdog(entity);
548
549         p_os->aborted = 0;
550
551         return (0);
552 }
553
554 /*
555  * timer watchdog
556  */
557 void diva_um_idi_start_wdog(void *entity)
558 {
559         diva_um_idi_os_context_t *p_os;
560
561         if (entity &&
562             ((p_os =
563               (diva_um_idi_os_context_t *)
564               diva_um_id_get_os_context(entity)))) {
565                 mod_timer(&p_os->diva_timer_id, jiffies + 10 * HZ);
566         }
567 }
568
569 void diva_um_idi_stop_wdog(void *entity)
570 {
571         diva_um_idi_os_context_t *p_os;
572
573         if (entity &&
574             ((p_os =
575               (diva_um_idi_os_context_t *)
576               diva_um_id_get_os_context(entity)))) {
577                 del_timer(&p_os->diva_timer_id);
578         }
579 }