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/smp_lock.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
  90um_idi_proc_read(char *page, char **start, off_t off, int count, int *eof,
  91                 void *data)
  92{
  93        int len = 0;
  94        char tmprev[32];
  95
  96        len += sprintf(page + len, "%s\n", DRIVERNAME);
  97        len += sprintf(page + len, "name     : %s\n", DRIVERLNAME);
  98        len += sprintf(page + len, "release  : %s\n", DRIVERRELEASE_IDI);
  99        strcpy(tmprev, main_revision);
 100        len += sprintf(page + len, "revision : %s\n", getrev(tmprev));
 101        len += sprintf(page + len, "build    : %s\n", DIVA_BUILD);
 102        len += sprintf(page + len, "major    : %d\n", major);
 103
 104        if (off + count >= len)
 105                *eof = 1;
 106        if (len < off)
 107                return 0;
 108        *start = page + off;
 109        return ((count < len - off) ? count : len - off);
 110}
 111
 112static int DIVA_INIT_FUNCTION create_um_idi_proc(void)
 113{
 114        um_idi_proc_entry = create_proc_entry(DRIVERLNAME,
 115                                              S_IFREG | S_IRUGO | S_IWUSR,
 116                                              proc_net_eicon);
 117        if (!um_idi_proc_entry)
 118                return (0);
 119
 120        um_idi_proc_entry->read_proc = um_idi_proc_read;
 121
 122        return (1);
 123}
 124
 125static void remove_um_idi_proc(void)
 126{
 127        if (um_idi_proc_entry) {
 128                remove_proc_entry(DRIVERLNAME, proc_net_eicon);
 129                um_idi_proc_entry = NULL;
 130        }
 131}
 132
 133static const struct file_operations divas_idi_fops = {
 134        .owner   = THIS_MODULE,
 135        .llseek  = no_llseek,
 136        .read    = um_idi_read,
 137        .write   = um_idi_write,
 138        .poll    = um_idi_poll,
 139        .open    = um_idi_open,
 140        .release = um_idi_release
 141};
 142
 143static void divas_idi_unregister_chrdev(void)
 144{
 145        unregister_chrdev(major, DEVNAME);
 146}
 147
 148static int DIVA_INIT_FUNCTION divas_idi_register_chrdev(void)
 149{
 150        if ((major = register_chrdev(0, DEVNAME, &divas_idi_fops)) < 0)
 151        {
 152                printk(KERN_ERR "%s: failed to create /dev entry.\n",
 153                       DRIVERLNAME);
 154                return (0);
 155        }
 156
 157        return (1);
 158}
 159
 160/*
 161** Driver Load
 162*/
 163static int DIVA_INIT_FUNCTION divasi_init(void)
 164{
 165        char tmprev[50];
 166        int ret = 0;
 167
 168        printk(KERN_INFO "%s\n", DRIVERNAME);
 169        printk(KERN_INFO "%s: Rel:%s  Rev:", DRIVERLNAME, DRIVERRELEASE_IDI);
 170        strcpy(tmprev, main_revision);
 171        printk("%s  Build: %s\n", getrev(tmprev), DIVA_BUILD);
 172
 173        if (!divas_idi_register_chrdev()) {
 174                ret = -EIO;
 175                goto out;
 176        }
 177
 178        if (!create_um_idi_proc()) {
 179                divas_idi_unregister_chrdev();
 180                printk(KERN_ERR "%s: failed to create proc entry.\n",
 181                       DRIVERLNAME);
 182                ret = -EIO;
 183                goto out;
 184        }
 185
 186        if (!(idifunc_init())) {
 187                remove_um_idi_proc();
 188                divas_idi_unregister_chrdev();
 189                printk(KERN_ERR "%s: failed to connect to DIDD.\n",
 190                       DRIVERLNAME);
 191                ret = -EIO;
 192                goto out;
 193        }
 194        printk(KERN_INFO "%s: started with major %d\n", DRIVERLNAME, major);
 195
 196      out:
 197        return (ret);
 198}
 199
 200
 201/*
 202** Driver Unload
 203*/
 204static void DIVA_EXIT_FUNCTION divasi_exit(void)
 205{
 206        idifunc_finit();
 207        remove_um_idi_proc();
 208        divas_idi_unregister_chrdev();
 209
 210        printk(KERN_INFO "%s: module unloaded.\n", DRIVERLNAME);
 211}
 212
 213module_init(divasi_init);
 214module_exit(divasi_exit);
 215
 216
 217/*
 218 *  FILE OPERATIONS
 219 */
 220
 221static int
 222divas_um_idi_copy_to_user(void *os_handle, void *dst, const void *src,
 223                          int length)
 224{
 225        memcpy(dst, src, length);
 226        return (length);
 227}
 228
 229static ssize_t
 230um_idi_read(struct file *file, char __user *buf, size_t count, loff_t * offset)
 231{
 232        diva_um_idi_os_context_t *p_os;
 233        int ret = -EINVAL;
 234        void *data;
 235
 236        if (!file->private_data) {
 237                return (-ENODEV);
 238        }
 239
 240        if (!
 241            (p_os =
 242             (diva_um_idi_os_context_t *) diva_um_id_get_os_context(file->
 243                                                                    private_data)))
 244        {
 245                return (-ENODEV);
 246        }
 247        if (p_os->aborted) {
 248                return (-ENODEV);
 249        }
 250
 251        if (!(data = diva_os_malloc(0, count))) {
 252                return (-ENOMEM);
 253        }
 254
 255        ret = diva_um_idi_read(file->private_data,
 256                               file, data, count,
 257                               divas_um_idi_copy_to_user);
 258        switch (ret) {
 259        case 0:         /* no message available */
 260                ret = (-EAGAIN);
 261                break;
 262        case (-1):              /* adapter was removed */
 263                ret = (-ENODEV);
 264                break;
 265        case (-2):              /* message_length > length of user buffer */
 266                ret = (-EFAULT);
 267                break;
 268        }
 269
 270        if (ret > 0) {
 271                if (copy_to_user(buf, data, ret)) {
 272                        ret = (-EFAULT);
 273                }
 274        }
 275
 276        diva_os_free(0, data);
 277        DBG_TRC(("read: ret %d", ret));
 278        return (ret);
 279}
 280
 281
 282static int
 283divas_um_idi_copy_from_user(void *os_handle, void *dst, const void *src,
 284                            int length)
 285{
 286        memcpy(dst, src, length);
 287        return (length);
 288}
 289
 290static int um_idi_open_adapter(struct file *file, int adapter_nr)
 291{
 292        diva_um_idi_os_context_t *p_os;
 293        void *e =
 294            divas_um_idi_create_entity((dword) adapter_nr, (void *) file);
 295
 296        if (!(file->private_data = e)) {
 297                return (0);
 298        }
 299        p_os = (diva_um_idi_os_context_t *) diva_um_id_get_os_context(e);
 300        init_waitqueue_head(&p_os->read_wait);
 301        init_waitqueue_head(&p_os->close_wait);
 302        init_timer(&p_os->diva_timer_id);
 303        p_os->diva_timer_id.function = (void *) diva_um_timer_function;
 304        p_os->diva_timer_id.data = (unsigned long) p_os;
 305        p_os->aborted = 0;
 306        p_os->adapter_nr = adapter_nr;
 307        return (1);
 308}
 309
 310static ssize_t
 311um_idi_write(struct file *file, const char __user *buf, size_t count,
 312             loff_t * offset)
 313{
 314        diva_um_idi_os_context_t *p_os;
 315        int ret = -EINVAL;
 316        void *data;
 317        int adapter_nr = 0;
 318
 319        if (!file->private_data) {
 320                /* the first write() selects the adapter_nr */
 321                if (count == sizeof(int)) {
 322                        if (copy_from_user
 323                            ((void *) &adapter_nr, buf,
 324                             count)) return (-EFAULT);
 325                        if (!(um_idi_open_adapter(file, adapter_nr)))
 326                                return (-ENODEV);
 327                        return (count);
 328                } else
 329                        return (-ENODEV);
 330        }
 331
 332        if (!(p_os =
 333             (diva_um_idi_os_context_t *) diva_um_id_get_os_context(file->
 334                                                                    private_data)))
 335        {
 336                return (-ENODEV);
 337        }
 338        if (p_os->aborted) {
 339                return (-ENODEV);
 340        }
 341
 342        if (!(data = diva_os_malloc(0, count))) {
 343                return (-ENOMEM);
 344        }
 345
 346        if (copy_from_user(data, buf, count)) {
 347                ret = -EFAULT;
 348        } else {
 349                ret = diva_um_idi_write(file->private_data,
 350                                        file, data, count,
 351                                        divas_um_idi_copy_from_user);
 352                switch (ret) {
 353                case 0: /* no space available */
 354                        ret = (-EAGAIN);
 355                        break;
 356                case (-1):      /* adapter was removed */
 357                        ret = (-ENODEV);
 358                        break;
 359                case (-2):      /* length of user buffer > max message_length */
 360                        ret = (-EFAULT);
 361                        break;
 362                }
 363        }
 364        diva_os_free(0, data);
 365        DBG_TRC(("write: ret %d", ret));
 366        return (ret);
 367}
 368
 369static unsigned int um_idi_poll(struct file *file, poll_table * wait)
 370{
 371        diva_um_idi_os_context_t *p_os;
 372
 373        if (!file->private_data) {
 374                return (POLLERR);
 375        }
 376
 377        if ((!(p_os =
 378               (diva_um_idi_os_context_t *)
 379               diva_um_id_get_os_context(file->private_data)))
 380            || p_os->aborted) {
 381                return (POLLERR);
 382        }
 383
 384        poll_wait(file, &p_os->read_wait, wait);
 385
 386        if (p_os->aborted) {
 387                return (POLLERR);
 388        }
 389
 390        switch (diva_user_mode_idi_ind_ready(file->private_data, file)) {
 391        case (-1):
 392                return (POLLERR);
 393
 394        case 0:
 395                return (0);
 396        }
 397
 398        return (POLLIN | POLLRDNORM);
 399}
 400
 401static int um_idi_open(struct inode *inode, struct file *file)
 402{
 403        cycle_kernel_lock();
 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