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 <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
  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 __poll_t 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(struct timer_list *t);
  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 __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
 113static 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
 121static 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
 131static void divas_idi_unregister_chrdev(void)
 132{
 133        unregister_chrdev(major, DEVNAME);
 134}
 135
 136static 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*/
 151static 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
 184out:
 185        return (ret);
 186}
 187
 188
 189/*
 190** Driver Unload
 191*/
 192static 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
 201module_init(divasi_init);
 202module_exit(divasi_exit);
 203
 204
 205/*
 206 *  FILE OPERATIONS
 207 */
 208
 209static int
 210divas_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
 217static ssize_t
 218um_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
 270static int
 271divas_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
 278static 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
 296static ssize_t
 297um_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
 355static __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
 387static int um_idi_open(struct inode *inode, struct file *file)
 388{
 389        return (0);
 390}
 391
 392
 393static 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
 422out:
 423        return (ret);
 424}
 425
 426int diva_os_get_context_size(void)
 427{
 428        return (sizeof(diva_um_idi_os_context_t));
 429}
 430
 431void 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
 438void 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
 445static
 446void 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*/
 460static 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 */
 540void 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
 552void 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