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