Logo Search packages:      
Sourcecode: affix-kernel version File versions

bty.c

/* 
   Affix - Bluetooth Protocol Stack for Linux
   Copyright (C) 2001 Nokia Corporation
   Original Author: Dmitry Kasatkin <dmitry.kasatkin@nokia.com>

   This program is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by the
   Free Software Foundation; either version 2 of the License, or (at your
   option) any later version.

   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License along
   with this program; if not, write to the Free Software Foundation, Inc.,
   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/

/* 
   $Id: bty.c,v 1.78 2003/12/10 15:02:11 kds Exp $

   BTY - RFCOMM terminal driver

   Fixes:   Dmitry Kasatkin <dmitry.kasatkin@nokia.com>
            Imre Deak <ext-imre.deak@nokia.com>
*/          

/* The following prevents "kernel_version" from being set in this file. */
#define __NO_VERSION__

#include <linux/config.h>

/* Module related headers, non-module drivers should not include */
#include <linux/module.h>
#include <linux/init.h>

/* Standard driver includes */
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/string.h>

#include <linux/interrupt.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/ioctl.h>
#include <linux/proc_fs.h>
#include <linux/devfs_fs_kernel.h>
#include <linux/tty.h>
#include <linux/serial.h>

#include <asm/uaccess.h>
#include <asm/bitops.h>

#include <linux/skbuff.h>
#include <net/sock.h>

#define FILEBIT   DBBTY

/* Local Includes */
#include <affix/bluetooth.h>
#include <affix/btdebug.h>

#include <affix/rfcomm.h>
#include "bty.h"


/*================================================================*/
/* "Driver global" definitions */

int                     sysctl_bty_wmem = BTY_SNDBUF_SIZE;
int                     sysctl_bty_rmem = BTY_RCVBUF_SIZE;
int                     bty_maxdev = BTY_MAX_TTY;
struct bty_table        *bty_table = NULL;
rwlock_t                bty_lock;

struct tty_driver       bty_driver; /* our driver structure */
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) 
static int              tty_refcount;
#endif
static struct tty_struct      **tty_table;
static struct termios         **tty_termios;
static struct termios         **tty_termios_locked;

/*
   BTY internal functions
   */
bty_struct *bty_alloc(void)
{
      bty_struct  *bty;

      DBFENTER;

      bty = kmalloc(sizeof(bty_struct), GFP_ATOMIC);
      if (!bty)
            return NULL;
      memset(bty, 0, sizeof(bty_struct));

      bty->magic = TTY_DRIVER_MAGIC;

      init_waitqueue_head(&bty->open_wait);     /* should be waken on ready */
      init_waitqueue_head(&bty->close_wait);
      init_waitqueue_head(&bty->delta_msr_wait);
      init_waitqueue_head(&bty->tx_wait);

      tasklet_init(&bty->rx_task, (void*)bty_rx_task, (unsigned long) bty);
      tasklet_init(&bty->tx_wakeup_task, (void*)bty_tx_wakeup_task, (unsigned long) bty);

      bty->closing_wait = 10*HZ;
      bty->close_delay = 5*HZ/10;

      DBFEXIT;
      return bty;
}

int bty_create(int line, bty_struct **ret_bty)
{
      bty_struct  *bty;

      DBFENTER;
      write_lock_bh(&bty_lock);
      bty = __bty_get(line);
      if (bty) {
            *ret_bty = bty;
            bty_hold(bty);
            write_unlock_bh(&bty_lock);
            return 0;
      }
      bty = bty_alloc();
      if (!bty) {
            write_unlock_bh(&bty_lock);
            return -ENOMEM;
      }
      atomic_set(&bty->refcnt, 1);  // one user
      bty->line = line;
      *ret_bty = bty;
      bty_table[line].bty = bty;
      write_unlock_bh(&bty_lock);
      DBFEXIT;
      return 0;
}

void __bty_destroy(bty_struct *bty)
{
      DBFENTER;
      bty_table[bty->line].bty = NULL;
      kfree(bty);
      DBFEXIT;
}


int bty_baud_rate(int baud)
{
      switch (baud) {
            case B2400:
                  return RFCOMM_2400;
            case B4800:
                  return RFCOMM_4800;
            case B9600:
                  return RFCOMM_9600;
            case B19200:
                  return RFCOMM_19200;
            case B38400:
                  return RFCOMM_38400;
            case B57600:
                  return RFCOMM_57600;
            case B115200:
                  return RFCOMM_115200;
            case B230400:
                  return RFCOMM_230400;
            default:
                  return RFCOMM_9600;
      }
}


/*
   For Remote Port
   */
void bty_change_param(bty_struct *bty)
{
      unsigned int                  cflag;
      struct rfcomm_port_param      param;

      DBFENTER;

      memset(&param, 0, sizeof(param));

      cflag = bty->tty->termios->c_cflag;

      param.bit_rate = bty_baud_rate(cflag & CBAUDEX);

      switch (cflag & CSIZE) {
            case CS5:
                  param.format |= RFCOMM_CS5;
                  break;
            case CS6:
                  param.format |= RFCOMM_CS6;
                  break;
            case CS7:
                  param.format |= RFCOMM_CS7;
                  break;
            case CS8:
                  param.format |= RFCOMM_CS8;
                  break;
            default:
                  param.format |= RFCOMM_CS8;
      }
      if (cflag & CSTOPB)
            param.format |= RFCOMM_15STOP;
      if (cflag & PARENB)
            param.format |= RFCOMM_PARENB;
      if (!(cflag & PARODD))
            param.format |= RFCOMM_PAREVEN;
      if (cflag & CRTSCTS) {
            bty->flags |= ASYNC_CTS_FLOW;
            param.fc |= RFCOMM_RTR_INPUT;
            param.fc |= RFCOMM_RTR_OUTPUT;
      } else {
            bty->flags &= ~ASYNC_CTS_FLOW;
            param.fc &= ~RFCOMM_RTR_INPUT;
            param.fc &= ~RFCOMM_RTR_OUTPUT;
      }
      if (cflag & CLOCAL)
            bty->flags &= ~ASYNC_CHECK_CD;
      else
            bty->flags |= ASYNC_CHECK_CD;
      /*
       * Set up parity check flag
       */
      bty->read_status_mask = BTY_LSR_OE;
      if (I_INPCK(bty->tty))
            bty->read_status_mask |= BTY_LSR_FE | BTY_LSR_PE;
      if (I_BRKINT(bty->tty) || I_PARMRK(bty->tty))
            bty->read_status_mask |= BTY_LSR_BI;
      /*
       * Characters to ignore
       */
      bty->ignore_status_mask = 0;
      if (I_IGNPAR(bty->tty))
            bty->ignore_status_mask |= BTY_LSR_PE | BTY_LSR_FE;
      if (I_IGNBRK(bty->tty)) {
            bty->ignore_status_mask |= BTY_LSR_BI;
            /*
             * If we're ignore parity and break indicators, ignore 
             * overruns too.  (For real raw supcon).
             */
            if (I_IGNPAR(bty->tty)) 
                  bty->ignore_status_mask |= BTY_LSR_OE;
      }
      rfcon_set_param(bty->con, &param);
      DBFEXIT;
}

int bty_startup(bty_struct *bty)
{
      rfcomm_con  *con;

      DBFENTER;

      if (bty->flags & ASYNC_INITIALIZED)
            return 0;

      write_lock_bh(&bty_lock);
      con = __bty_getcon(bty->line);
      if (!con) {
            write_unlock_bh(&bty_lock);
            return -ENODEV;
      }
      bty->con = con;
      rfcon_hold(con);
      write_unlock_bh(&bty_lock);

      bty->sk = (struct sock*)con->priv;
      sock_hold(bty->sk);
      bty->sk->sk_sndbuf = sysctl_bty_wmem;
      bty->sk->sk_rcvbuf = sysctl_bty_rmem;

      /* MCR & MSR */
      rfcon_set_rxfc(bty->con, AFFIX_FLOW_ON);
      bty->msr = rfcon_get_msr(bty->con);
      bty->mcr = rfcon_get_mcr(bty->con);

      if (bty->tty)
            clear_bit(TTY_IO_ERROR, &bty->tty->flags);

      bty->flags |= ASYNC_INITIALIZED;

      bty_change_param(bty);

      DBFEXIT;
      return 0;
}

void bty_shutdown(bty_struct *bty)
{
      unsigned long     flags;

      DBFENTER;

      if (!(bty->flags & ASYNC_INITIALIZED))
            return;

      bty->flags &= ~ASYNC_INITIALIZED;

      /* disable tasklets */
      tasklet_kill(&bty->rx_task);
      tasklet_kill(&bty->tx_wakeup_task);

      /* wake up ioctl.. */
      wake_up_interruptible(&bty->delta_msr_wait);

      /* clear DTR and RTS */
      if (!bty->tty || (bty->tty->termios->c_cflag & HUPCL))
            bty->mcr &= ~(BTY_MCR_DTR|BTY_MCR_RTS|BTY_MCR_DCD);

      if (bty->con) {
            rfcon_set_rxfc(bty->con, AFFIX_FLOW_OFF);
            rfcon_put(xchg(&bty->con, NULL));
      }
      if (bty->sk)
            sock_put(xchg(&bty->sk, NULL));

      local_irq_save(flags);
      if (bty->tty)
            set_bit(TTY_IO_ERROR, &bty->tty->flags);
      local_irq_restore(flags);
      DBFEXIT;
}

void bty_write_to_tty(bty_struct *bty)
{
      rfcomm_con        *con;
      struct tty_struct *tty;
      struct sk_buff          *skb;
      int               room, flag;
      int               status;

      DBFENTER;

      if (!bty || !(bty->flags & ASYNC_INITIALIZED) || 
                  !(con = bty->con) || !(tty = bty->tty))
            return;
#if 0 
      if ((tty->termios->c_cflag & CRTSCTS) && !(bty->mcr & BTY_MCR_RTS))
            return;
#endif

      DBPRT("Queue length: %d\n", skb_queue_len(&bty->sk->sk_receive_queue));

      if (skb_queue_len(&bty->sk->sk_receive_queue) == 0)
            goto exit;
#if 0
      if (tty->flip.count >= TTY_FLIPBUF_SIZE)
            tty->flip.tqueue.routine((void *) tty);
#endif
      for(;;) {
            if (test_bit(TTY_THROTTLED, &tty->flags))
                  goto exit;
            if (tty->flip.count >= TTY_FLIPBUF_SIZE)
                  goto exit;
            skb = skb_dequeue(&bty->sk->sk_receive_queue);
            if (!skb)
                  break;
            DBDUMP(skb->data, skb->len);
            DBDUMPCHAR(skb->data, skb->len);
            status = rfcon_get_line_status(con) & bty->read_status_mask;
            if (status & bty->ignore_status_mask) {
                  BTERROR("line status error: ignore characters.\n");
                  dev_kfree_skb(skb);
                  continue;
            }
            if (con->peer_break_signal.b & RFCOMM_BREAK) {
                  con->peer_break_signal.b &= ~RFCOMM_BREAK;
                  DBPRT("handling break....\n");
                  flag = TTY_BREAK;
                  if (bty->flags & ASYNC_SAK)
                        do_SAK(tty);

            } else if (status & BTY_LSR_PE) flag = TTY_PARITY;
            else if (status & BTY_LSR_FE) flag = TTY_FRAME;
            else if (status & BTY_LSR_OE) flag = TTY_OVERRUN;
            else flag = TTY_NORMAL;

            room = btmin(TTY_FLIPBUF_SIZE-tty->flip.count, skb->len);
            DBPRT("RECV, room: %d, flag: %#02x\n", room, flag);
            memset(tty->flip.flag_buf_ptr, flag, room);
            memcpy(tty->flip.char_buf_ptr, skb->data, room);
            tty->flip.char_buf_ptr += room;
            tty->flip.flag_buf_ptr += room;
            tty->flip.count += room;
            skb_pull(skb, room);

            if (skb->len)
                  skb_queue_head(&bty->sk->sk_receive_queue, skb);
            else
                  dev_kfree_skb(skb);
            if (tty->flip.count >= TTY_FLIPBUF_SIZE)
                  tty_flip_buffer_push(tty);
      }
      tty_flip_buffer_push(tty);
exit:
      if (!skb_queue_empty(&bty->sk->sk_receive_queue))
            tasklet_schedule(&bty->rx_task);
      else if (rfcon_disconnect_pend(con))
            tty_hangup(tty);
      DBFEXIT;
}

void bty_rx_task(void *data)
{
      DBFENTER;
      bty_write_to_tty(data);
      DBFEXIT;
}

void bty_data_ind(rfcomm_con *con)
{
      bty_struct  *bty = bty_get(con->line);
      DBFENTER;
      if (!bty)
            return;
      if (!(bty->flags & ASYNC_INITIALIZED))
            goto exit;
      if (tasklet_trylock(&bty->rx_task)) {
            bty_write_to_tty(bty);
            tasklet_unlock(&bty->rx_task);
      } else
            tasklet_schedule(&bty->rx_task);
exit:
      bty_put(bty);
      DBFEXIT;

}

void bty_control_ind(rfcomm_con *con, int event)
{
      bty_struct        *bty = bty_get(con->line);
      struct tty_struct *tty;
      __u8              delta_msr;

      DBFENTER;

      if (!bty)
            return;
      if (!(bty->flags & ASYNC_INITIALIZED) || !(tty = bty->tty))
            goto exit;

      switch (event) {
            case RFCOMM_TX_WAKEUP:
                  tty->hw_stopped = bty_disabled(bty);
                  if (tty->hw_stopped)
                        goto exit;
                  /* fall through */
            case RFCOMM_WRITE_SPACE:
                  tasklet_schedule(&bty->tx_wakeup_task);
                  wake_up_interruptible(&tty->write_wait);
                  break;
            case RFCOMM_MODEM_STATUS:
            case RFCOMM_MODEM_STATUS_BRK:
                  delta_msr = bty->msr;
                  bty->msr = rfcon_get_msr(bty->con);
                  delta_msr ^= bty->msr;

                  if (delta_msr) {
                        if (bty->msr & BTY_MSR_CTS) bty->icount.cts++;
                        if (bty->msr & BTY_MSR_DSR) bty->icount.dsr++;
                        if (bty->msr & BTY_MSR_RI)  bty->icount.rng++;
                        if (bty->msr & BTY_MSR_DCD) bty->icount.dcd++;
                        wake_up_interruptible(&bty->delta_msr_wait);
                  }
                  if ((bty->flags & ASYNC_CHECK_CD) && (delta_msr & BTY_MSR_DCD)) {
                        DBPRT("CD now %s...\n", (bty->msr & BTY_MSR_DCD) ? "on" : "off");
                        if (bty->msr & BTY_MSR_DCD) {
                              /* DCD raised! */
                              wake_up_interruptible(&bty->open_wait);
                        } else {
                              /* DCD falled */
                              DBPRT("hangup..\n");
                              tty_hangup(tty);
                        }
                  }
                  /* like ASYNC_CTS_FLOW */
                  if (bty->flags & ASYNC_CTS_FLOW) {
                        if (tty->hw_stopped) {
                              if (bty->msr & BTY_MSR_CTS) {
                                    DBPRT("CTS tx start...\n");
                                    tty->hw_stopped = bty_disabled(bty);
                                    if (tty->hw_stopped)
                                          goto exit;
                                    tasklet_schedule(&bty->tx_wakeup_task);
                                    wake_up_interruptible(&tty->write_wait);
                              }
                        } else {
                              if (!(bty->msr & BTY_MSR_CTS)) {
                                    DBPRT("CTS tx stop...\n");
                                    tty->hw_stopped = 1;
                              }
                        }
                  }
                  break;
            case RFCOMM_PORT_PARAM:
                  BTDEBUG("Port Parameters (RPN) is not handled!!!\n");
                  break;
      }
exit:
      bty_put(bty);
      DBFEXIT;
}

void bty_tx_wakeup_task(void *data)
{
      bty_struct        *bty = data;
      struct tty_struct *tty = bty->tty;

      DBFENTER;
      if (!tty)
            return;
      if (test_bit(TTY_DO_WRITE_WAKEUP, &tty->flags) && tty->ldisc.write_wakeup)
            tty->ldisc.write_wakeup(tty);
      DBFEXIT;
}

/*
   it seems it is called from BH
   */
int bty_register_con(rfcomm_con *con, int line)
{
      DBFENTER;
      DBPRT("Register line: %d\n", line);
      write_lock_bh(&bty_lock);
      if (line == RFCOMM_BTY_ANY) {
            for (line = 0; line < bty_maxdev; line++)
                  if (!bty_table[line].con)
                        break;
            if (line >= bty_maxdev) {
                  line = -EBUSY;
                  goto exit;
            }
      } else {
            if (line < 0 || line >= bty_maxdev) {
                  line = -EINVAL;
                  goto exit;
            } else if (bty_table[line].con) {
                  line = -EBUSY;
                  goto exit;
            }
      }
      bty_table[line].con = con;
      con->line = line;
      rfcon_hold(con);  /* hold it */
      DBPRT("Line registered: %d\n", line);
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0)
        MOD_INC_USE_COUNT;
#endif
exit:
      write_unlock_bh(&bty_lock);
      DBFEXIT;
      return line;
}

int bty_unregister_con(int line)
{
      rfcomm_con  *con;
      bty_struct  *bty;

      DBFENTER;
      write_lock_bh(&bty_lock);
      con = __bty_getcon(line);
      if (!con) {
            write_unlock_bh(&bty_lock);
            return -ENODEV;
      }
      bty_table[line].con = NULL;
      con->line = -1;
      bty = __bty_get(line);
      if (bty && bty->tty)
            tty_hangup(bty->tty);
      rfcon_put(con);
      write_unlock_bh(&bty_lock);
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0)
      MOD_DEC_USE_COUNT;
#endif
      DBFEXIT;
      return 0;
}


/*********************   CALLBACKS   *******************/

/*
   open and friends
   */
int bty_open(struct tty_struct * tty, struct file * filp)
{
      bty_struct  *bty;
      __u32             line;
      int         err;

      DBFENTER;
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0)
        MOD_INC_USE_COUNT;
#endif
      line = __tty_to_line(tty);
      if (line >= bty_maxdev) {
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0)
            MOD_DEC_USE_COUNT;
#endif
            return -ENODEV;
      }
      err = bty_create(line, &bty);
      if (err) {
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0)
            MOD_DEC_USE_COUNT;
#endif
            BTERROR("bty structure create err: %d\n", err);
            return err;
      }
      /* set pointer to driver structures */
      DBPRT("tty: %p, data: %p\n", tty, bty->tty);
      tty->driver_data = bty;
      bty->tty = tty;
      bty->count++;
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
      DBPRT("%s[%d] opened, count %d\n", tty->driver.name, line, bty->count);
#else
      DBPRT("%s[%d] opened, count %d\n", tty->driver->name, line, bty->count);
#endif
#if 0
      tty->low_latency = (bty->flags & ASYNC_LOW_LATENCY) ? 1 : 0;
#else
      tty->low_latency = 1; /* force it */
#endif
      if (tty_hung_up_p(filp) || (bty->flags & ASYNC_CLOSING)) {
            DBPRT("Hang up...\n");
            if (bty->flags & ASYNC_CLOSING)
                  interruptible_sleep_on(&bty->close_wait);
#ifdef DO_RESTART
            if (!(bty->flags & ASYNC_HUP_NOTIFY))
                  err = -ERESTARTSYS;
            else
#endif
                  err = -EAGAIN;
            bty_put(bty);
            return err;
      }
      err = bty_startup(bty);
      if (err) {
            bty_put(bty);
            return err;
      }
      // block till ready XXX
      tty->hw_stopped = bty_disabled(bty);
      DBFEXIT;
      return 0;
}


/*
 * ----------------------------------------------------------------------
 * bty_close() and friends
 *
 * most of this function is stolen from serial.c
 * ----------------------------------------------------------------------
 */

void bty_close(struct tty_struct * tty, struct file * filp)
{
      bty_struct  *bty = (bty_struct*)tty->driver_data;

      DBFENTER;

      DBPRT("tty: %p, data: %p\n", tty, tty->driver_data);

      if (!bty) {
            BTERROR("bty == NULL\n");
            return;
      }
      write_lock_bh(&bty_lock);
      if (tty_hung_up_p(filp)) {
            /*
             * upper tty layer caught a HUP signal and called bty_hangup()
             * before. so we do nothing here.
             */
            write_unlock_bh(&bty_lock);
            DBPRT("Hang up...\n");
            goto exit;
      }
      if ((tty->count == 1) && (bty->count != 1)) {
            BTERROR("uart_close: bad serial port count; tty->count is 1, "
                   "state->count is %d\n", bty->count);
            bty->count = 1;
      }
      if (--bty->count < 0) {
            BTERROR("driver count is negative: %d\n", bty->count);
            bty->count = 0;
      }           
      if (bty->count) {       /* do nothing */
            write_unlock_bh(&bty_lock);
            DBPRT("driver still in use: %d\n", bty->count);
            goto exit;
      }
      bty->flags |= ASYNC_CLOSING;

      write_unlock_bh(&bty_lock);
      /*
       * Now we wait for the transmit buffer to clear; and we notify 
       * the line discipline to only process XON/XOFF characters.
       */
      tty->closing = 1;
      if (bty->closing_wait != ASYNC_CLOSING_WAIT_NONE){
            DBPRT("calling tty_wait_until_sent()\n");
            tty_wait_until_sent(tty, bty->closing_wait);
      }
      
      bty_shutdown(bty);

      if (tty->ldisc.flush_buffer)
            tty->ldisc.flush_buffer(tty);
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
      if (tty->driver.flush_buffer)
            tty->driver.flush_buffer(tty);
#else
      if (tty->driver->flush_buffer) 
            tty->driver->flush_buffer(tty);  
#endif
      tty->closing = 0;
      bty->tty = NULL;
      if (bty->blocked_open) {/* used to block till ready */
            if (bty->close_delay) {
                  /* kill time */
                  set_current_state(TASK_INTERRUPTIBLE);
                  schedule_timeout(bty->close_delay);
            }
            wake_up_interruptible(&bty->open_wait);
      }
      bty->flags &= ~ASYNC_CLOSING;
      wake_up_interruptible(&bty->close_wait); 
exit:
      bty_put(bty);
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0)
      MOD_DEC_USE_COUNT;
#endif
      DBFEXIT;
}

/*
 * ------------------------------------------------------------
 * bty_hangup()
 * This routine notifies that tty layer have got HUP signal
 * ------------------------------------------------------------
 */

void bty_hangup(struct tty_struct *tty)
{
      bty_struct  *bty = tty->driver_data;

      DBFENTER;
      bty_flush_buffer(tty);
      if (bty->flags & ASYNC_CLOSING)
            return;
      bty_shutdown(bty);
      bty->tty = NULL;
      bty->count = 0;
      wake_up_interruptible(&bty->open_wait);
      DBPRT("bty->count: %d, refcnt: %d\n", bty->count, atomic_read(&bty->refcnt));
      DBFEXIT;
}



/*
 * ----------------------------------------------------------------------
 * bty_write() and friends
 * This routine will be called when something data are passed from
 * kernel or user.
 * ----------------------------------------------------------------------
 */

int bty_write(struct tty_struct * tty, int from_user, const unsigned char *buf, int count)
{
      bty_struct  *bty = (bty_struct*)tty->driver_data;
      int         room, wrote = 0;
      __u8        *data;
      struct sk_buff    *txbuff;

      DBFENTER;
      DBPRT("Transmit: %d bytes\n", count);
      DBDUMP((void*)buf, count);
      DBDUMPCHAR(buf, count);
      if (!bty || !(bty->flags & ASYNC_INITIALIZED))
            return -ENODEV;
      if (!count)
            return 0;
      txbuff = skb_dequeue_tail(&bty->sk->sk_write_queue);
      if (txbuff && rfcomm_skb_tailroom(txbuff) == 0) {
            /* no space */
            skb_queue_tail(&bty->sk->sk_write_queue, txbuff);
            txbuff = NULL;
      }
      while (count) {
            if (!txbuff) {
                  /* allocate new one */
                  txbuff = rpf_wmalloc(bty->sk, rfcon_getmtu(bty->con), 0, GFP_ATOMIC);
                  if (!txbuff)
                        goto exit;
            }
            room = btmin(count, rfcomm_skb_tailroom(txbuff));
            DBPRT("room: %d\n", room);
            data = skb_put(txbuff, room);
            if (from_user)
                  copy_from_user(data, buf, room);
            else
                  memcpy(data, buf, room);
            buf += room;
            wrote += room;
            count -= room;
            skb_queue_tail(&bty->sk->sk_write_queue, txbuff);
            txbuff = NULL;
            rpf_send_data(bty->sk);
      }
exit:
      DBPRT("wrote: %d\n", wrote);
      DBFEXIT;
      return wrote;
}

/*
 * Function bty_put_char (tty, ch)
 *
 *    This routine is called by the kernel to pass a single character.
 *    If we exausted our buffer,we can ignore the character!
 *
 */
void bty_put_char(struct tty_struct *tty, unsigned char ch)
{
      bty_struct  *bty = tty->driver_data;
      struct sk_buff    *txbuff;
      __u8        *ptr;

      DBFENTER;
      if (!bty || !(bty->flags & ASYNC_INITIALIZED))
            return;
      txbuff = skb_dequeue_tail(&bty->sk->sk_write_queue);
      if (txbuff && rfcomm_skb_tailroom(txbuff) == 0) {
            /* no space */
            skb_queue_tail(&bty->sk->sk_write_queue, txbuff);
            txbuff = NULL;
      }
      if (!txbuff) {
            /* rs_put_char does not do it but we do */
            /* allocate new one */
            txbuff = rpf_wmalloc(bty->sk, rfcon_getmtu(bty->con), 1, GFP_ATOMIC);
            if (!txbuff)
                  return;
      }
      ptr = skb_put(txbuff, 1);
      *ptr = ch;
      skb_queue_tail(&bty->sk->sk_write_queue, txbuff);
      DBFEXIT;
}


void bty_flush_chars(struct tty_struct *tty)
{
      bty_struct  *bty = tty->driver_data;
      DBFENTER;
      if (!bty || !(bty->flags & ASYNC_INITIALIZED))
            return;
      rpf_send_data(bty->sk);
      DBFEXIT;
}


/*
 * Function bty_write_room (tty)
 *
 *    This routine returns the room that our buffer has now.
 *
 */
int bty_write_room(struct tty_struct *tty)
{
      bty_struct  *bty = tty->driver_data;
      DBFENTER;
      if (!bty || !(bty->flags & ASYNC_INITIALIZED))
            return 0;
      return sock_wspace(bty->sk);
}


/*
 * Function bty_chars_in_buffer (tty)
 *
 *    This function returns how many characters which have not been sent yet 
 *    are still in buffer.
 *
 */
int bty_chars_in_buffer(struct tty_struct *tty)
{
      bty_struct  *bty = tty->driver_data;
      if (!bty || !(bty->flags & ASYNC_INITIALIZED))
            return 0;
      return atomic_read(&bty->sk->sk_wmem_alloc);
}

void bty_flush_buffer(struct tty_struct *tty)
{
      bty_struct  *bty = tty->driver_data;

      DBFENTER;
      if (bty->sk)
            skb_queue_purge(&bty->sk->sk_write_queue);
      wake_up_interruptible(&tty->write_wait);
      if (test_bit(TTY_DO_WRITE_WAKEUP, &tty->flags) && tty->ldisc.write_wakeup)
            tty->ldisc.write_wakeup(tty);
      DBFEXIT;
}

int bty_tiocmset(struct tty_struct *tty, struct file *file, 
            unsigned int set, unsigned int clear)
{
      bty_struct  *bty = tty->driver_data;

      DBFENTER;
      if (set && clear) {
            bty->mcr = bty->mcr & ~(BTY_MCR_RTS | BTY_MCR_DTR | BTY_MCR_DCD | BTY_MCR_RI);
            goto doset;
      } else if (set && !clear) {
doset:
            if (set & TIOCM_RTS) bty->mcr |= BTY_MCR_RTS;
            if (set & TIOCM_DTR) bty->mcr |= BTY_MCR_DTR;
            if (set & TIOCM_CAR) bty->mcr |= BTY_MCR_DCD;
            if (set & TIOCM_RNG) bty->mcr |= BTY_MCR_RI;
      } else if (!set && clear) {
            if (clear & TIOCM_RTS) bty->mcr &= ~BTY_MCR_RTS;
            if (clear & TIOCM_DTR) bty->mcr &= ~BTY_MCR_DTR;
            if (clear & TIOCM_CAR) bty->mcr &= ~BTY_MCR_DCD;
            if (clear & TIOCM_RNG) bty->mcr &= ~BTY_MCR_RI;
      }
      rfcon_set_mcr(bty->con, bty->mcr);
      DBFEXIT;
      return 0;
}

int set_modem_info(struct tty_struct *tty, struct file *file, 
            unsigned int cmd, unsigned int *arg)
{ 
      int         err;
      unsigned int      set, clear, val;

      DBFENTER;
      err = get_user(val, arg);
      if(err)
            return err;

      set = clear = 0;
      switch (cmd) {
            case TIOCMBIS:
                  set = val;
                  break;
            case TIOCMBIC:
                  clear = val;
                  break;
            case TIOCMSET:
                  set = val;
                  clear = ~val;
                  break;
            default:
                  return -EINVAL;
      }
      DBFEXIT;
      return bty_tiocmset(tty, file, set, clear);
}

int bty_tiocmget(struct tty_struct *tty, struct file *file)
{
      bty_struct  *bty = tty->driver_data;
      int         result = 0;

      DBFENTER;
      if (bty->mcr & BTY_MCR_RTS) result |= TIOCM_RTS;
      if (bty->mcr & BTY_MCR_DTR) result |= TIOCM_DTR;

      if (bty->msr & BTY_MSR_DCD) result |= TIOCM_CAR;
      if (bty->msr & BTY_MSR_RI) result |= TIOCM_RNG;
      if (bty->msr & BTY_MSR_DSR) result |= TIOCM_DSR;
      if (bty->msr & BTY_MSR_CTS) result |= TIOCM_CTS;
      DBFEXIT;
      return result;
}

int get_modem_info(struct tty_struct *tty, struct file *file, unsigned int *value)
{
      int   result = bty_tiocmget(tty, file);

      return put_user(result, value);
}


/*
 * ----------------------------------------------------------------------
 * bty_ioctl() and friends
 * This routine allows us to implement device-specific ioctl's.
 * If passed ioctl number (i.e.cmd) is unknown one, we should return 
 * ENOIOCTLCMD.
 *
 * TODO: we can't use setserial on tty because some ioctls are not implemented.
 * we should add some ioctls and make some tool which is resemble to setserial.
 * ----------------------------------------------------------------------
 */

int bty_ioctl(struct tty_struct *tty, struct file * file, unsigned int cmd, unsigned long arg)
{
      bty_struct              *bty = tty->driver_data;
      rfcomm_con              *con;
      int                     err = 0;
      struct async_icount           cnow, cprev;
      struct serial_icounter_struct icount;     /* user space */

      //DBFENTER;

      con = bty->con;

      switch (cmd) {
            case TIOCMGET:
                  return get_modem_info(tty, file, (unsigned int *) arg);
            case TIOCMBIS:
            case TIOCMBIC:
            case TIOCMSET:
                  return set_modem_info(tty, file, cmd, (unsigned int *) arg);
            case TIOCMIWAIT:
                  write_lock_bh(&bty_lock);
                  /* note the counters on entry */
                  cprev = bty->icount;
                  write_unlock_bh(&bty_lock);
                  for (;;) {
                        interruptible_sleep_on(&bty->delta_msr_wait);
                        /* see if a signal did it */
                        if (signal_pending(current))
                              return -ERESTARTSYS;
                        write_lock_bh(&bty_lock);
                        cnow = bty->icount; /* atomic copy */
                        write_unlock_bh(&bty_lock);
                        if( cnow.rng == cprev.rng && cnow.dsr == cprev.dsr && 
                                    cnow.dcd == cprev.dcd && cnow.cts == cprev.cts )
                              return -EIO; /* no change => error */
                        if( ((arg & TIOCM_RNG) && (cnow.rng != cprev.rng)) ||
                                    ((arg & TIOCM_DSR) && (cnow.dsr != cprev.dsr)) ||
                                    ((arg & TIOCM_CD)  && (cnow.dcd != cprev.dcd)) ||
                                    ((arg & TIOCM_CTS) && (cnow.cts != cprev.cts)) ) {
                              return 0;
                        }
                        cprev = cnow;
                  }
                  /* NOTREACHED */
            case TIOCGICOUNT:
                  write_lock_bh(&bty_lock);
                  cnow = bty->icount;
                  write_unlock_bh(&bty_lock);

                  icount.cts = cnow.cts;
                  icount.dsr = cnow.dsr;
                  icount.rng = cnow.rng;
                  icount.dcd = cnow.dcd;
                  icount.rx = cnow.rx;
                  icount.tx = cnow.tx;
                  icount.frame = cnow.frame;
                  icount.overrun = cnow.overrun;
                  icount.parity = cnow.parity;
                  icount.brk = cnow.brk;
                  icount.buf_overrun = cnow.buf_overrun;

                  err =  copy_to_user((void*)arg, &icount, sizeof(icount));
                  if (err)
                        return -EFAULT;
                  return 0;
                  
                  /* ioctls which are imcompatible with serial.c */
            case TIOCGSERIAL:
                  DBPRT("TIOCGSERIAL is not supported\n");
                  return -ENOIOCTLCMD;  
                  //return get_serial_info(driver, (struct serial_struct *) arg);
            case TIOCSSERIAL:
                  DBPRT("TIOCSSERIAL is not supported\n");
                  return -ENOIOCTLCMD;  
                  //return set_serial_info(driver, (struct serial_struct *) arg);
            case TIOCSERGSTRUCT:
                  DBPRT("TIOCSERGSTRUCT is not supported\n");
                  return -ENOIOCTLCMD;  
            case TIOCSERGETLSR:
                  DBPRT("TIOCSERGETLSR is not supported\n");
                  return -ENOIOCTLCMD;  
            case TIOCSERCONFIG:
                  DBPRT("TIOCSERCONFIG is not supported\n");
                  return -ENOIOCTLCMD;  
            default:
                  //DBPRT("Command ignored: %#04x\n", cmd);
                  return -ENOIOCTLCMD;  /* ioctls which we must ignore */
      }
      //DBFEXIT;
      return err;
}

/*
 * ----------------------------------------------------------------------
 * bty_throttle,bty_unthrottle
 *   These routines will be called when we have to pause sending up data to tty.
 *   We use RTS virtual signal when servicetype is NINE_WIRE
 * ----------------------------------------------------------------------
 */

void bty_throttle(struct tty_struct *tty)
{
      bty_struct  *bty = tty->driver_data;
      rfcomm_con  *con = bty->con;

      DBFENTER;
      if (I_IXOFF(tty))
            bty_send_xchar(tty, STOP_CHAR(tty));
      if (tty->termios->c_cflag & CRTSCTS) {
            bty->mcr &= ~BTY_MCR_RTS; 
            rfcon_set_mcr(con, bty->mcr);
      }
      DBFEXIT;
}


void bty_unthrottle(struct tty_struct *tty)
{
      bty_struct  *bty = tty->driver_data;
      rfcomm_con  *con = bty->con;

      DBFENTER;
      if (I_IXOFF(tty))
            bty_send_xchar(tty, START_CHAR(tty));
      if (tty->termios->c_cflag & CRTSCTS) {
            bty->mcr |= BTY_MCR_RTS;
            rfcon_set_mcr(con, bty->mcr);
      }
      DBFEXIT;
}


/*
 * ----------------------------------------------------------------------
 * bty_set_termios()
 * This is called when termios is changed.
 * If things that changed is significant for us,(i.e. changing baud rate etc.)
 * send something to peer device.
 * ----------------------------------------------------------------------
 */

void bty_set_termios(struct tty_struct *tty, struct termios * old_termios)
{
      bty_struct  *bty = tty->driver_data;

      DBFENTER;
      if( (tty->termios->c_cflag == old_termios->c_cflag) &&
                  (RELEVANT_IFLAG(tty->termios->c_iflag) == RELEVANT_IFLAG(old_termios->c_iflag)) )
            return;
      bty_change_param(bty);
      /* handle turning off CRTSCTS */
      if ((old_termios->c_cflag & CRTSCTS) && !(tty->termios->c_cflag & CRTSCTS)) {
            tty->hw_stopped = bty_disabled(bty);
            /* bty_start(tty); */       /* we don't need this */
      }
      DBFEXIT;
}

/*
 * ------------------------------------------------------------
 * bty_stop() and bty_start()
 *
 * This routines are called before setting or resetting tty->stopped.
 * They enable or disable an interrupt which means "transmitter-is-ready"
 * in serial.c, but  I think these routine are not necessary for us. 
 * ------------------------------------------------------------
 */

#if 0
void bty_stop(struct tty_struct *tty)
{
      bty_struct *bty = (bty_struct *)tty->driver_data; 

      DBFENTER;
      DBFEXIT;
}

void bty_start(struct tty_struct *tty)
{
      bty_struct *bty = (bty_struct *)tty->driver_data;

      DBFENTER;
      DBFEXIT;
}
#endif


void bty_send_xchar(struct tty_struct *tty, char ch)
{
      DBFENTER;
      bty_put_char(tty, ch);
      DBFEXIT;
}

/*
 * Function bty_break (tty, break_state)
 *
 *    Routine which turns the break handling on or off
 *
 */
void bty_break_ctl(struct tty_struct *tty, int break_state)
{
      bty_struct  *bty = tty->driver_data;
      rfcomm_con  *con = bty->con;

      DBFENTER;
      //write_lock_bh(&bty_lock);
      if (break_state == -1)
            rfcon_set_break(con, 1);
      else
            rfcon_set_break(con, 0);
      //write_unlock_bh(&bty_lock);
      DBFEXIT;
}


int bty_proc_read(char *buf, char **start, off_t offset, int len);

int bty_read_proc(char *buf, char **start, off_t offset, int len, int *eof, void *unused)
{
      return bty_proc_read(buf, start, offset, len);
}


/*
 * Function bty_wait_until_sent (tty, timeout)
 *
 *    wait until Tx queue of BTY is empty 
 *
 */
void bty_wait_until_sent(struct tty_struct *tty, int timeout)
{
      bty_struct  *bty = tty->driver_data;
      unsigned long     orig_jiffies;

      DBFENTER;
      DBPRT("chars in buffer: %d\n", bty_chars_in_buffer(tty));
      if (!tty->closing || !bty->con)
            return;   /* nothing to do */
      orig_jiffies = jiffies;
      while (rfcon_disconnect_pend(bty->con)) {
            DBPRT("wait..\n");
            set_current_state(TASK_INTERRUPTIBLE);
            schedule_timeout(HZ);
            if (signal_pending(current))
                  break;
            if (timeout && time_after(jiffies, orig_jiffies + timeout))
                  break;
      }
      set_current_state(TASK_RUNNING);
      DBFEXIT;
}

#if defined(CONFIG_DEVFS_FS)

#define __KERNEL_SYSCALLS__
#include <linux/unistd.h>
static inline _syscall2(int,chmod,char*,name,mode_t,mode);

static int  errno;

#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 4, 17)
#define devfs_get_handle      devfs_find_handle
#define devfs_put(a)
#endif

#define DEVPATHLEN      256

void bty_change_mode(struct tty_driver *driver)
{
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 71)
      char        path[DEVPATHLEN];
      char        *name;
      devfs_handle_t    handle;
      int         pos;
      mm_segment_t      old_fs;
#endif
        int             i;
      umode_t     mode = S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;

      for(i = 0; i < driver->num; i++) {
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 71)
            handle = devfs_get_handle (NULL, NULL, driver->major, driver->minor_start + i,
                        DEVFS_SPECIAL_CHR, 0);
            if (!handle) {
                  BTERROR("unable to get device handle\n");
                  break;
            }
            pos = devfs_generate_path(handle, path, DEVPATHLEN);
            devfs_put(handle);
            if (pos < 0) {
                  BTERROR("unable to generate a path\n");
                  break;
            }
            /* add /dev/ */
            name = path + pos - 5;
            memcpy(name, "/dev/", 5);

            old_fs = get_fs(); set_fs(KERNEL_DS);
            chmod(name, mode);
            set_fs(old_fs);
#else
                devfs_mk_cdev(MKDEV(driver->major, driver->minor_start + i), mode, "bty%d", i);
#endif
      }
}

#endif

/*
 * Function bty_register_ttydriver(void)
 *
 *   we register "port emulation entity"(see Bty specification) here
 *   as a tty device.
 */

int bty_register_ttydriver(void)
{
      int   err;
      DBFENTER;


      /* allocate memory for data structures */
      err = -ENOMEM;
      tty_table = (struct tty_struct**)kmalloc(sizeof(void*)*bty_maxdev, GFP_KERNEL);
      if (!tty_table) {
            BTERROR("Unable to allocate memory for data structures\n");
            goto err1;
      }
      memset(tty_table, 0, sizeof(void*)*bty_maxdev);

      tty_termios = (struct termios**)kmalloc(sizeof(void*)*bty_maxdev, GFP_KERNEL);
      if (!tty_termios) {
            BTERROR("Unable to allocate memory for data structures\n");
            goto err2;
      }
      memset(tty_termios, 0, sizeof(void*)*bty_maxdev);

      tty_termios_locked = (struct termios**)kmalloc(sizeof(void*)*bty_maxdev, GFP_KERNEL);
      if (!tty_termios_locked) {
            BTERROR("Unable to allocate memory for data structures\n");
            goto err3;
      }
      memset(tty_termios_locked, 0, sizeof(void*)*bty_maxdev);

      /* setup virtual serial port device */

      /* Initialize the tty_driver structure ,which is defined in 
         tty_driver.h */

      memset(&bty_driver, 0, sizeof(struct tty_driver));
      bty_driver.magic = TTY_DRIVER_MAGIC;
      bty_driver.driver_name = "affix_rfcomm";
#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 5, 0)
      bty_driver.devfs_name = "bty/";
      bty_driver.name = "bty";
#else
#ifdef CONFIG_DEVFS_FS
      bty_driver.name = "bty/%d";
#else
      bty_driver.name = "bty";
#endif
#endif
      bty_driver.major = BTY_MAJOR;
      bty_driver.minor_start = BTY_MINOR;
      bty_driver.num = bty_maxdev;
      bty_driver.type = TTY_DRIVER_TYPE_SERIAL;  /* see tty_driver.h */
      bty_driver.subtype = SERIAL_TYPE_NORMAL;  /* see tty_driver.h */


      /*
       * see drivers/char/tty_io.c and termios(3)
       */

      bty_driver.init_termios = tty_std_termios;
      bty_driver.init_termios.c_cflag = B115200 | CS8 | CREAD | HUPCL | CLOCAL;
      bty_driver.flags = TTY_DRIVER_REAL_RAW;   /* see tty_driver.h */
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) 
      bty_driver.refcount = &tty_refcount;
      /* pointer to the tty data structures */
      bty_driver.table = tty_table;  
#else
      /* pointer to the tty data structures */
      bty_driver.ttys = tty_table;  
      bty_driver.owner = THIS_MODULE;
#endif
      bty_driver.termios = tty_termios;
      bty_driver.termios_locked = tty_termios_locked;

      /*
       * Interface table from the kernel(tty driver) to the bty
       * layer
       */

      bty_driver.open = bty_open;
      bty_driver.close = bty_close;
      bty_driver.write = bty_write;
      bty_driver.put_char = bty_put_char;
      bty_driver.flush_chars = bty_flush_chars;
      bty_driver.write_room = bty_write_room;
      bty_driver.chars_in_buffer = bty_chars_in_buffer; 
      bty_driver.flush_buffer = bty_flush_buffer;
      bty_driver.ioctl = bty_ioctl; 
      bty_driver.throttle = bty_throttle;
      bty_driver.unthrottle = bty_unthrottle;
      bty_driver.set_termios = bty_set_termios;
      bty_driver.stop = NULL;                   /* bty_stop */
      bty_driver.start = NULL;                        /* bty_start */
      bty_driver.hangup = bty_hangup;
      bty_driver.send_xchar = bty_send_xchar;
      bty_driver.break_ctl = bty_break_ctl;
      bty_driver.read_proc = bty_read_proc;
      bty_driver.wait_until_sent = bty_wait_until_sent;
#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 5, 0)
      bty_driver.tiocmget = bty_tiocmget;
      bty_driver.tiocmset = bty_tiocmset;
#endif
      if ((err = tty_register_driver(&bty_driver))) {
            goto err4;
      }
#if defined(CONFIG_DEVFS_FS)
      //bty_change_mode(&bty_driver);
#endif
      DBPRT("done.\n");

      DBFEXIT;

      return 0;
err4:
      kfree(tty_termios_locked);
err3:
      kfree(tty_termios);
err2:
      kfree(tty_table);
err1:
      return err;
}


/*
 * Function bty_unregister_ttydriver(void) 
 *   it will be called when you rmmod
 */

int bty_unregister_ttydriver(void)
{
      int err;    
      DBFENTER;

      /* unregister tty device   */
      err = tty_unregister_driver(&bty_driver);
      if (err)
            BTERROR("BTY: failed to unregister vtd driver(%d)\n", err);
      kfree(tty_termios_locked);
      kfree(tty_termios);
      kfree(tty_table);
      return err;
}

/* Proc file system services */

#define LV(v)     ((v) != 0)
int bty_proc_read(char *buf, char **start, off_t offset, int len)
{
      rfcomm_con  *con;
      bty_struct  *bty;
      int count = 0, i;

      DBFENTER;
      read_lock_bh(&bty_lock);
      for (i = 0; i < bty_maxdev; i++) {
            count += sprintf(buf+count, "line: %d\n", i);
            con = __bty_getcon(i);
            if (con) {
                  count += sprintf(buf+count, "\tMSR: credit: %d, fc: %d, use rtr: %d, "
                              "rtr: %d, rtc: %d, dv: %d, ic: %d\n", 
                              atomic_read(&con->tx_credit), con->peer_modem.fc, con->peer_param.fc & RFCOMM_RTR_OUTPUT,
                              LV(con->peer_modem.mr&RFCOMM_RTR), LV(con->peer_modem.mr&RFCOMM_RTC),
                              LV(con->peer_modem.mr&RFCOMM_DV), LV(con->peer_modem.mr&RFCOMM_IC));
                  count += sprintf(buf+count, "\tMCR: credit: %d, fc: %d, use rtr: %d, "
                              "rtr: %d, rtc: %d, dv: %d, ic: %d\n", 
                              atomic_read(&con->rx_credit), con->modem.fc, con->param.fc & RFCOMM_RTR_OUTPUT,
                              LV(con->modem.mr&RFCOMM_RTR), LV(con->modem.mr&RFCOMM_RTC),
                              LV(con->modem.mr&RFCOMM_DV), LV(con->modem.mr&RFCOMM_IC));
            }
            bty = __bty_get(i);
            if (bty) {
                  count += sprintf(buf+count, "\n\tbty count: %d, bty refcnt: %d\n\n", 
                              bty->count, atomic_read(&bty->refcnt));
            }
      }
      read_unlock_bh(&bty_lock);
      DBFEXIT;
      return count;
}

struct proc_dir_entry   *bty_proc;

int __init bty_init(void)
{
      int   err = -ENOMEM;

      DBFENTER;

      rwlock_init(&bty_lock);

      bty_table = (struct bty_table*)kmalloc(sizeof(struct bty_table) * bty_maxdev, GFP_KERNEL);
      if (!bty_table) {
            BTERROR("kmalloc failed!\n");
            goto err1;
      }
      memset(bty_table, 0, sizeof(struct bty_table) * bty_maxdev);

      bty_proc = create_proc_info_entry("bty", 0, proc_affix, bty_proc_read);
      if (!bty_proc) {
            BTERROR("Unable to register proc fs entry\n");
            goto err2;
      }
      err = bty_register_ttydriver();
      if (err){
            BTERROR("Error in rdvtd_register_device\n");
            goto err3;
      }
      DBFEXIT;
      return 0;
err3:
      remove_proc_entry("bty", proc_affix);
err2:
      kfree(bty_table);
err1:
      return err;
}

void __exit bty_exit(void)
{
      DBFENTER;
      bty_unregister_ttydriver();
      remove_proc_entry("bty", proc_affix);
      kfree(bty_table); // must be not NULL
      DBFEXIT;
}

/* 
 * - no reason to lock *con* if sock is locked
 *
 */

Generated by  Doxygen 1.6.0   Back to index