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