Linux-2.6.12-rc2
[cascardo/linux.git] / 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         devfs_remove(DEVNAME);
149         unregister_chrdev(major, DEVNAME);
150 }
151
152 static int DIVA_INIT_FUNCTION divas_idi_register_chrdev(void)
153 {
154         if ((major = register_chrdev(0, DEVNAME, &divas_idi_fops)) < 0)
155         {
156                 printk(KERN_ERR "%s: failed to create /dev entry.\n",
157                        DRIVERLNAME);
158                 return (0);
159         }
160         devfs_mk_cdev(MKDEV(major, 0), S_IFCHR|S_IRUSR|S_IWUSR, DEVNAME);
161
162         return (1);
163 }
164
165 /*
166 ** Driver Load
167 */
168 static int DIVA_INIT_FUNCTION divasi_init(void)
169 {
170         char tmprev[50];
171         int ret = 0;
172
173         printk(KERN_INFO "%s\n", DRIVERNAME);
174         printk(KERN_INFO "%s: Rel:%s  Rev:", DRIVERLNAME, DRIVERRELEASE_IDI);
175         strcpy(tmprev, main_revision);
176         printk("%s  Build: %s\n", getrev(tmprev), DIVA_BUILD);
177
178         if (!divas_idi_register_chrdev()) {
179                 ret = -EIO;
180                 goto out;
181         }
182
183         if (!create_um_idi_proc()) {
184                 divas_idi_unregister_chrdev();
185                 printk(KERN_ERR "%s: failed to create proc entry.\n",
186                        DRIVERLNAME);
187                 ret = -EIO;
188                 goto out;
189         }
190
191         if (!(idifunc_init())) {
192                 remove_um_idi_proc();
193                 divas_idi_unregister_chrdev();
194                 printk(KERN_ERR "%s: failed to connect to DIDD.\n",
195                        DRIVERLNAME);
196                 ret = -EIO;
197                 goto out;
198         }
199         printk(KERN_INFO "%s: started with major %d\n", DRIVERLNAME, major);
200
201       out:
202         return (ret);
203 }
204
205
206 /*
207 ** Driver Unload
208 */
209 static void DIVA_EXIT_FUNCTION divasi_exit(void)
210 {
211         idifunc_finit();
212         remove_um_idi_proc();
213         divas_idi_unregister_chrdev();
214
215         printk(KERN_INFO "%s: module unloaded.\n", DRIVERLNAME);
216 }
217
218 module_init(divasi_init);
219 module_exit(divasi_exit);
220
221
222 /*
223  *  FILE OPERATIONS
224  */
225
226 static int
227 divas_um_idi_copy_to_user(void *os_handle, void *dst, const void *src,
228                           int length)
229 {
230         memcpy(dst, src, length);
231         return (length);
232 }
233
234 static ssize_t
235 um_idi_read(struct file *file, char __user *buf, size_t count, loff_t * offset)
236 {
237         diva_um_idi_os_context_t *p_os;
238         int ret = -EINVAL;
239         void *data;
240
241         if (!file->private_data) {
242                 return (-ENODEV);
243         }
244
245         if (!
246             (p_os =
247              (diva_um_idi_os_context_t *) diva_um_id_get_os_context(file->
248                                                                     private_data)))
249         {
250                 return (-ENODEV);
251         }
252         if (p_os->aborted) {
253                 return (-ENODEV);
254         }
255
256         if (!(data = diva_os_malloc(0, count))) {
257                 return (-ENOMEM);
258         }
259
260         ret = diva_um_idi_read(file->private_data,
261                                file, data, count,
262                                divas_um_idi_copy_to_user);
263         switch (ret) {
264         case 0:         /* no message available */
265                 ret = (-EAGAIN);
266                 break;
267         case (-1):              /* adapter was removed */
268                 ret = (-ENODEV);
269                 break;
270         case (-2):              /* message_length > length of user buffer */
271                 ret = (-EFAULT);
272                 break;
273         }
274
275         if (ret > 0) {
276                 if (copy_to_user(buf, data, ret)) {
277                         ret = (-EFAULT);
278                 }
279         }
280
281         diva_os_free(0, data);
282         DBG_TRC(("read: ret %d", ret));
283         return (ret);
284 }
285
286
287 static int
288 divas_um_idi_copy_from_user(void *os_handle, void *dst, const void *src,
289                             int length)
290 {
291         memcpy(dst, src, length);
292         return (length);
293 }
294
295 static int um_idi_open_adapter(struct file *file, int adapter_nr)
296 {
297         diva_um_idi_os_context_t *p_os;
298         void *e =
299             divas_um_idi_create_entity((dword) adapter_nr, (void *) file);
300
301         if (!(file->private_data = e)) {
302                 return (0);
303         }
304         p_os = (diva_um_idi_os_context_t *) diva_um_id_get_os_context(e);
305         init_waitqueue_head(&p_os->read_wait);
306         init_waitqueue_head(&p_os->close_wait);
307         init_timer(&p_os->diva_timer_id);
308         p_os->diva_timer_id.function = (void *) diva_um_timer_function;
309         p_os->diva_timer_id.data = (unsigned long) p_os;
310         p_os->aborted = 0;
311         p_os->adapter_nr = adapter_nr;
312         return (1);
313 }
314
315 static ssize_t
316 um_idi_write(struct file *file, const char __user *buf, size_t count,
317              loff_t * offset)
318 {
319         diva_um_idi_os_context_t *p_os;
320         int ret = -EINVAL;
321         void *data;
322         int adapter_nr = 0;
323
324         if (!file->private_data) {
325                 /* the first write() selects the adapter_nr */
326                 if (count == sizeof(int)) {
327                         if (copy_from_user
328                             ((void *) &adapter_nr, buf,
329                              count)) return (-EFAULT);
330                         if (!(um_idi_open_adapter(file, adapter_nr)))
331                                 return (-ENODEV);
332                         return (count);
333                 } else
334                         return (-ENODEV);
335         }
336
337         if (!(p_os =
338              (diva_um_idi_os_context_t *) diva_um_id_get_os_context(file->
339                                                                     private_data)))
340         {
341                 return (-ENODEV);
342         }
343         if (p_os->aborted) {
344                 return (-ENODEV);
345         }
346
347         if (!(data = diva_os_malloc(0, count))) {
348                 return (-ENOMEM);
349         }
350
351         if (copy_from_user(data, buf, count)) {
352                 ret = -EFAULT;
353         } else {
354                 ret = diva_um_idi_write(file->private_data,
355                                         file, data, count,
356                                         divas_um_idi_copy_from_user);
357                 switch (ret) {
358                 case 0: /* no space available */
359                         ret = (-EAGAIN);
360                         break;
361                 case (-1):      /* adapter was removed */
362                         ret = (-ENODEV);
363                         break;
364                 case (-2):      /* length of user buffer > max message_length */
365                         ret = (-EFAULT);
366                         break;
367                 }
368         }
369         diva_os_free(0, data);
370         DBG_TRC(("write: ret %d", ret));
371         return (ret);
372 }
373
374 static unsigned int um_idi_poll(struct file *file, poll_table * wait)
375 {
376         diva_um_idi_os_context_t *p_os;
377
378         if (!file->private_data) {
379                 return (POLLERR);
380         }
381
382         if ((!(p_os =
383                (diva_um_idi_os_context_t *)
384                diva_um_id_get_os_context(file->private_data)))
385             || p_os->aborted) {
386                 return (POLLERR);
387         }
388
389         poll_wait(file, &p_os->read_wait, wait);
390
391         if (p_os->aborted) {
392                 return (POLLERR);
393         }
394
395         switch (diva_user_mode_idi_ind_ready(file->private_data, file)) {
396         case (-1):
397                 return (POLLERR);
398
399         case 0:
400                 return (0);
401         }
402
403         return (POLLIN | POLLRDNORM);
404 }
405
406 static int um_idi_open(struct inode *inode, struct file *file)
407 {
408         return (0);
409 }
410
411
412 static int um_idi_release(struct inode *inode, struct file *file)
413 {
414         diva_um_idi_os_context_t *p_os;
415         unsigned int adapter_nr;
416         int ret = 0;
417
418         if (!(file->private_data)) {
419                 ret = -ENODEV;
420                 goto out;
421         }
422
423         if (!(p_os =
424                 (diva_um_idi_os_context_t *) diva_um_id_get_os_context(file->private_data))) {
425                 ret = -ENODEV;
426                 goto out;
427         }
428
429         adapter_nr = p_os->adapter_nr;
430
431         if ((ret = remove_entity(file->private_data))) {
432                 goto out;
433         }
434
435         if (divas_um_idi_delete_entity
436             ((int) adapter_nr, file->private_data)) {
437                 ret = -ENODEV;
438                 goto out;
439         }
440
441       out:
442         return (ret);
443 }
444
445 int diva_os_get_context_size(void)
446 {
447         return (sizeof(diva_um_idi_os_context_t));
448 }
449
450 void diva_os_wakeup_read(void *os_context)
451 {
452         diva_um_idi_os_context_t *p_os =
453             (diva_um_idi_os_context_t *) os_context;
454         wake_up_interruptible(&p_os->read_wait);
455 }
456
457 void diva_os_wakeup_close(void *os_context)
458 {
459         diva_um_idi_os_context_t *p_os =
460             (diva_um_idi_os_context_t *) os_context;
461         wake_up_interruptible(&p_os->close_wait);
462 }
463
464 static
465 void diva_um_timer_function(unsigned long data)
466 {
467         diva_um_idi_os_context_t *p_os = (diva_um_idi_os_context_t *) data;
468
469         p_os->aborted = 1;
470         wake_up_interruptible(&p_os->read_wait);
471         wake_up_interruptible(&p_os->close_wait);
472         DBG_ERR(("entity removal watchdog"))
473 }
474
475 /*
476 **  If application exits without entity removal this function will remove
477 **  entity and block until removal is complete
478 */
479 static int remove_entity(void *entity)
480 {
481         struct task_struct *curtask = current;
482         diva_um_idi_os_context_t *p_os;
483
484         diva_um_idi_stop_wdog(entity);
485
486         if (!entity) {
487                 DBG_FTL(("Zero entity on remove"))
488                 return (0);
489         }
490
491         if (!(p_os =
492              (diva_um_idi_os_context_t *)
493              diva_um_id_get_os_context(entity))) {
494                 DBG_FTL(("Zero entity os context on remove"))
495                 return (0);
496         }
497
498         if (!divas_um_idi_entity_assigned(entity) || p_os->aborted) {
499                 /*
500                    Entity is not assigned, also can be removed
501                  */
502                 return (0);
503         }
504
505         DBG_TRC(("E(%08x) check remove", entity))
506
507         /*
508            If adapter not answers on remove request inside of
509            10 Sec, then adapter is dead
510          */
511         diva_um_idi_start_wdog(entity);
512
513         {
514                 DECLARE_WAITQUEUE(wait, curtask);
515
516                 add_wait_queue(&p_os->close_wait, &wait);
517                 for (;;) {
518                         set_current_state(TASK_INTERRUPTIBLE);
519                         if (!divas_um_idi_entity_start_remove(entity)
520                             || p_os->aborted) {
521                                 break;
522                         }
523                         schedule();
524                 }
525                 set_current_state(TASK_RUNNING);
526                 remove_wait_queue(&p_os->close_wait, &wait);
527         }
528
529         DBG_TRC(("E(%08x) start remove", entity))
530         {
531                 DECLARE_WAITQUEUE(wait, curtask);
532
533                 add_wait_queue(&p_os->close_wait, &wait);
534                 for (;;) {
535                         set_current_state(TASK_INTERRUPTIBLE);
536                         if (!divas_um_idi_entity_assigned(entity)
537                             || p_os->aborted) {
538                                 break;
539                         }
540                         schedule();
541                 }
542                 set_current_state(TASK_RUNNING);
543                 remove_wait_queue(&p_os->close_wait, &wait);
544         }
545
546         DBG_TRC(("E(%08x) remove complete, aborted:%d", entity,
547                  p_os->aborted))
548
549         diva_um_idi_stop_wdog(entity);
550
551         p_os->aborted = 0;
552
553         return (0);
554 }
555
556 /*
557  * timer watchdog
558  */
559 void diva_um_idi_start_wdog(void *entity)
560 {
561         diva_um_idi_os_context_t *p_os;
562
563         if (entity &&
564             ((p_os =
565               (diva_um_idi_os_context_t *)
566               diva_um_id_get_os_context(entity)))) {
567                 mod_timer(&p_os->diva_timer_id, jiffies + 10 * HZ);
568         }
569 }
570
571 void diva_um_idi_stop_wdog(void *entity)
572 {
573         diva_um_idi_os_context_t *p_os;
574
575         if (entity &&
576             ((p_os =
577               (diva_um_idi_os_context_t *)
578               diva_um_id_get_os_context(entity)))) {
579                 del_timer(&p_os->diva_timer_id);
580         }
581 }