/*********************************************************************
 *                
 * Filename:      irvtd_driver.c
 * Version:       
 * Description:   Virtual tty driver (the "port emulation entity" of IrCOMM)
 * Status:        Experimental.
 * Author:        Takahide Higuchi <thiguchi@pluto.dti.ne.jp>
 * Source:        serial.c by Linus Torvalds
 *                isdn_tty.c by Fritz Elfert
 *
 *     Copyright (c) 1998-1999, Takahide Higuchi, <thiguchi@pluto.dti.ne.jp>,
 *     All Rights Reserved.
 *
 *     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.
 *
 *     I, Takahide Higuchi, provide no warranty for any of this software.
 *     This material is provided "AS-IS" and at no charge.
 *
 ********************************************************************/

#include <linux/module.h>
#include <linux/init.h>

#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/termios.h>
#include <linux/tty.h>
#include <asm/segment.h>
#include <asm/uaccess.h>

#include <net/irda/irda.h>
#include <net/irda/irttp.h>
#include <net/irda/irias_object.h>

#include <net/irda/irvtd.h>

#ifndef MIN
#define MIN(a,b)        ((a) < (b) ? (a) : (b))
#endif

#define DO_RESTART
#define RELEVANT_IFLAG(iflag) (iflag & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK))

struct tty_driver irvtd_drv;
struct tty_struct *irvtd_table[COMM_MAX_TTY];
struct termios *irvtd_termios[COMM_MAX_TTY];
struct termios *irvtd_termios_locked[COMM_MAX_TTY];
static int irvtd_refcount;
struct irvtd_cb **irvtd = NULL;

static char *revision_date = "Wed May 26 00:49:11 1999";


/*
 * prototypes
 */

int irvtd_open(struct tty_struct *tty, struct file *filp);
void irvtd_close(struct tty_struct * tty, struct file * filp);
int irvtd_write(struct tty_struct * tty, int from_user,
			const unsigned char *buf, int count);
void irvtd_put_char(struct tty_struct *tty, unsigned char ch);
int irvtd_write_room(struct tty_struct *tty);
int irvtd_chars_in_buffer(struct tty_struct *tty);
int irvtd_ioctl(struct tty_struct *tty, struct file * file,
	     unsigned int cmd, unsigned long arg);
void irvtd_set_termios(struct tty_struct *tty, struct termios * old);
void irvtd_throttle(struct tty_struct *tty);
void irvtd_unthrottle(struct tty_struct *tty);
void irvtd_stop(struct tty_struct *tty);
void irvtd_start(struct tty_struct *tty);
void irvtd_hangup(struct tty_struct *tty);
void irvtd_flush_buffer(struct tty_struct *tty);
void irvtd_flush_chars(struct tty_struct *tty);

static void change_speed(struct irvtd_cb *driver);
static void irvtd_write_to_tty( struct irvtd_cb *);
static void irvtd_send_data_request( struct irvtd_cb *);
static void irvtd_break(struct tty_struct *tty, int break_state);
static void irvtd_send_xchar(struct tty_struct *tty, char ch);
static void irvtd_wait_until_sent(struct tty_struct *tty, int timeout);

static void irvtd_start_tx_timer( struct irvtd_cb *driver, int timeout);
static void irvtd_tx_timer_expired(unsigned long data);
static void irvtd_start_rx_timer( struct irvtd_cb *driver, int timeout);
static void irvtd_rx_timer_expired(unsigned long data);

static int line_info(char *buf, struct irvtd_cb *driver);
static int irvtd_read_proc(char *buf, char **start, off_t offset, int len,
			   int *eof, void *unused);


/*
 **********************************************************************
 *
 * ircomm_receive_data() and friends
 *
 * like interrupt handler in the serial.c,we receive data when 
 * ircomm_data_indication comes
 *
 **********************************************************************
 */



/* 
 * irvtd_write_to_tty
 * send incoming/queued data to tty
 */

static void irvtd_write_to_tty( struct irvtd_cb *driver)
{	
	int status, c, flag;
	struct sk_buff *skb;
	struct tty_struct *tty = driver->tty;
	
	if(driver->rx_disable)
		return;

 	skb = skb_dequeue(&driver->rxbuff);
	if(skb == NULL)
		return; /* there's nothing */

	/* 
	 * we should parse controlchannel field here. 
	 * (see process_data() in ircomm.c)
	 */
	ircomm_parse_tuples(driver->comm, skb, CONTROL_CHANNEL);
	
#ifdef IRVTD_DEBUG_RX
	printk("received data:");
	{
		int i;
		for ( i=0;i<skb->len;i++)
			printk("%02x ", skb->data[i]);
		printk("\n");
	}
#endif
	
	status = driver->comm->peer_line_status & driver->read_status_mask;
	
	/* 
	 * if there are too many errors which make a character ignored,
	 * drop characters
	 */

	if(status & driver->ignore_status_mask){
		DEBUG(0,__FUNCTION__":some error:ignore characters.\n");
		dev_kfree_skb(skb);
		return;
	}
	
	c = MIN(skb->len, (TTY_FLIPBUF_SIZE - tty->flip.count));
	DEBUG(4, __FUNCTION__"skb_len=%d, tty->flip.count=%d \n"
	      ,(int)skb->len, tty->flip.count);
	
	if (driver->comm->peer_break_signal ) {
		driver->comm->peer_break_signal = 0;
		DEBUG(0,"handling break....\n");
		
		flag = TTY_BREAK;
		if (driver->flags & ASYNC_SAK)
			/*
			 * do_SAK() seems to be an implementation of the 
			 * idea called "Secure Attention Key",
			 * which seems to be discribed in "Orange book".
			 * (which is published by U.S.military!!?? ,
			 * see source of do_SAK())
			 *
			 * but what kind of security do we need 
			 * when we use infrared communication??? :p)
			 */
			do_SAK(tty);
	}else if (status & LSR_PE)
		flag = TTY_PARITY;
	else if (status & LSR_FE)
		flag = TTY_FRAME;
	else if (status & LSR_OE)
		flag = TTY_OVERRUN;
	else 
		flag = TTY_NORMAL;
	
	if(c){
		DEBUG(4,"writing %d chars to tty\n",c);
		driver->icount.rx += c;
		memset(tty->flip.flag_buf_ptr, flag, c);
		memcpy(tty->flip.char_buf_ptr, skb->data, c);
		tty->flip.flag_buf_ptr += c;
		tty->flip.char_buf_ptr += c;
		tty->flip.count += c;
		skb_pull(skb,c);
	}

	if(skb->len == 0)
		dev_kfree_skb(skb);
	else
	{
		/* queue rest of data again */
		DEBUG(4,__FUNCTION__":retrying frame!\n");

		/* build a dummy control channel */
		skb_push(skb,1);
		*skb->data = 0;  /* clen is 0 */
		skb_queue_head( &driver->rxbuff, skb );
	}
	
	if(c)
	/* let the process read its buffer! */
		tty_flip_buffer_push(tty);
	
	if(skb_queue_len(&driver->rxbuff)< IRVTD_RX_QUEUE_LOW &&
	   driver->ttp_stoprx){
		DEBUG(1, __FUNCTION__"():FLOW_START\n");
		/* 
		 * next 2 lines must follow this order since irttp_flow_request()
		 * will run its rx queue
		 */
		driver->ttp_stoprx = 0;
		irttp_flow_request(driver->comm->tsap, FLOW_START);
	}

	if(skb_queue_empty(&driver->rxbuff) && driver->disconnect_pend){
		/* disconnect */
		driver->disconnect_pend = 0;
		driver->rx_disable = 1;
		tty_hangup(driver->tty);
	}
}

static int irvtd_receive_data(void *instance, void *sap, struct sk_buff *skb)
{	
	struct irvtd_cb *driver = (struct irvtd_cb *)instance;

	ASSERT(driver != NULL, return -1;);
	ASSERT(driver->magic == IRVTD_MAGIC, return -1;);
	DEBUG(4, __FUNCTION__"(): queue frame\n");

	/* queue incoming data and make bottom half handler ready */

	skb_queue_tail( &driver->rxbuff, skb );

	if(skb_queue_len(&driver->rxbuff) > IRVTD_RX_QUEUE_HIGH){
		DEBUG(1, __FUNCTION__"():FLOW_STOP\n");
		irttp_flow_request(driver->comm->tsap, FLOW_STOP);
		driver->ttp_stoprx = 1;
	}
	irvtd_write_to_tty(driver);

	if(!skb_queue_empty(&driver->rxbuff))
		irvtd_start_rx_timer(driver,0);
	return 0;
}

/*
 ***********************************************************************
 *
 * irvtd_send_data() and friends
 *
 * like interrupt handler in the serial.c,we send data when 
 * a timer is expired
 *
 ***********************************************************************
 */


static void irvtd_start_tx_timer( struct irvtd_cb *driver, int timeout)
{
	ASSERT( driver != NULL, return;);
	ASSERT( driver->magic == IRVTD_MAGIC, return;);

	del_timer( &driver->tx_timer);
	
	driver->tx_timer.data     = (unsigned long) driver;
	driver->tx_timer.function = &irvtd_tx_timer_expired;
	driver->tx_timer.expires  = jiffies + timeout;
	
	add_timer( &driver->tx_timer);
}

static void irvtd_start_rx_timer( struct irvtd_cb *driver, int timeout)
{
	ASSERT( driver != NULL, return;);
	ASSERT( driver->magic == IRVTD_MAGIC, return;);

	del_timer( &driver->rx_timer);
	
	driver->rx_timer.data     = (unsigned long) driver;
	driver->rx_timer.function = &irvtd_rx_timer_expired;
	driver->rx_timer.expires  = jiffies + timeout;
	
	add_timer( &driver->rx_timer);
}


static void irvtd_tx_timer_expired(unsigned long data)
{
	struct irvtd_cb *driver = (struct irvtd_cb *)data;

	ASSERT(driver != NULL,return;);
	ASSERT(driver->magic == IRVTD_MAGIC,return;);
	DEBUG(4, __FUNCTION__"()\n");

	irvtd_send_data_request(driver);
}

static void irvtd_rx_timer_expired(unsigned long data)
{
	struct irvtd_cb *driver = (struct irvtd_cb *)data;

	ASSERT(driver != NULL,return;);
	ASSERT(driver->magic == IRVTD_MAGIC,return;);
	DEBUG(4, __FUNCTION__"()\n");

	while(TTY_FLIPBUF_SIZE - driver->tty->flip.count
	      && !skb_queue_empty(&driver->rxbuff))
		irvtd_write_to_tty(driver);

	DEBUG(1, __FUNCTION__"(): room in flip_buffer = %d\n",
	      TTY_FLIPBUF_SIZE - driver->tty->flip.count);
	
	if(!skb_queue_empty(&driver->rxbuff))
		/* handle it later */
		irvtd_start_rx_timer(driver, 1);
}


static void irvtd_send_data_request(struct irvtd_cb *driver)
{
	int err;
	struct sk_buff *skb = driver->txbuff;

	ASSERT(skb != NULL,return;);
	DEBUG(4, __FUNCTION__"()\n");

	if(driver->tty->hw_stopped || driver->tx_disable)
		return;
	if(!skb->len)
		return;   /* no data to send */

#ifdef IRVTD_DEBUG_TX
	DEBUG(4, "flush_txbuff:count(%d)\n",(int)skb->len);
	{
		int i;
		for ( i=0;i<skb->len;i++)
			printk("%02x", skb->data[i]);
		printk("\n");
	}
#endif

	DEBUG(1, __FUNCTION__"():len = %d, room = %d\n",(int)skb->len,
	      skb_tailroom(skb));
	driver->icount.tx += skb->len;
	err = ircomm_data_request(driver->comm, driver->txbuff);
	if (err){
		ASSERT(err == 0,;);
		DEBUG(1,"%d chars are lost\n",(int)skb->len);
		skb_trim(skb, 0);
	}

	/* allocate a new frame */
	skb = driver->txbuff 
		= dev_alloc_skb(driver->tx_max_sdu_size + driver->max_header_size);
	if (skb == NULL){
		printk(__FUNCTION__"():alloc_skb failed!\n");
	} else {
		skb_reserve(skb, driver->max_header_size);
	}

	wake_up_interruptible(&driver->tty->write_wait);
}


/*
 ***********************************************************************
 *
 * indication/confirmation handlers:
 *
 * these routines are handlers for IrCOMM protocol stack
 *
 ***********************************************************************
 */

/*
 * Function irvtd_connect_confirm (instance, sap, qos, max_sdu_size, skb)
 *
 *    ircomm_connect_request which we have send, has succeeded!
 *
 */
void irvtd_connect_confirm(void *instance, void *sap, struct qos_info *qos,
			   __u32 max_sdu_size, __u8 max_header_size, 
			   struct sk_buff *skb)
{
	struct irvtd_cb *driver = (struct irvtd_cb *)instance;
	ASSERT(driver != NULL, return;);
	ASSERT(driver->magic == IRVTD_MAGIC, return;);


	driver->tx_max_sdu_size = max_sdu_size;
	driver->max_header_size = max_header_size;
	/*
	 * set default value
	 */
	
	driver->msr |= (MSR_DCD|MSR_RI|MSR_DSR|MSR_CTS);

	/*
	 * sending initial control parameters here
	 */
	if (driver->comm->servicetype ==	THREE_WIRE_RAW)
		return;                /* do nothing */

	driver->comm->dte |= (MCR_DTR | MCR_RTS | DELTA_DTR | DELTA_RTS);

	ircomm_control_request(driver->comm, SERVICETYPE);
	ircomm_control_request(driver->comm, DATA_RATE);
	ircomm_control_request(driver->comm, DATA_FORMAT);
	ircomm_control_request(driver->comm, FLOW_CONTROL);
	ircomm_control_request(driver->comm, XON_XOFF_CHAR);
	/* ircomm_control_request(driver->comm, ENQ_ACK_CHAR); */

	switch (driver->comm->servicetype) {
	case CENTRONICS:
		break;

	case NINE_WIRE:
		ircomm_control_request(driver->comm, DTELINE_STATE);
		break;
	default:
	}
	
	driver->tx_disable = 0;
	wake_up_interruptible(&driver->open_wait);
}

/*
 * Function irvtd_connect_indication (instance, sap, qos, max_sdu_size, skb)
 *
 *    we are discovered and being requested to connect by remote device !
 *
 */
void irvtd_connect_indication(void *instance, void *sap, struct qos_info *qos,
			      __u32 max_sdu_size, __u8 max_header_size, 
			      struct sk_buff *skb)
{
	struct irvtd_cb *driver = (struct irvtd_cb *)instance;
	struct ircomm_cb *comm = (struct ircomm_cb *)sap;

	ASSERT(driver != NULL, return;);
	ASSERT(driver->magic == IRVTD_MAGIC, return;);
	ASSERT(comm != NULL, return;);
	ASSERT(comm->magic == IRCOMM_MAGIC, return;);

	driver->tx_max_sdu_size = max_sdu_size;
	driver->max_header_size = max_header_size;
	DEBUG(4, __FUNCTION__ "():sending connect_response...\n");

	ircomm_connect_response(comm, NULL, SAR_DISABLE );

	driver->tx_disable = 0;

	/*
	 * send initial control parameters
	 */
	if (driver->comm->servicetype == THREE_WIRE_RAW)
		return;                /* do nothing */

	driver->comm->dte |= (MCR_DTR | MCR_RTS | DELTA_DTR | DELTA_RTS);

	switch(driver->comm->servicetype){
	case NINE_WIRE:
		ircomm_control_request(driver->comm, DTELINE_STATE);
		break;
	default:
		DEBUG(0, __FUNCTION__ "(), not implemented!\n");
	}


	driver->msr |= (MSR_DCD|MSR_RI|MSR_DSR|MSR_CTS);
	wake_up_interruptible(&driver->open_wait);
}

/*
 * Function irvtd_disconnect_indication (instance, sap, reason, skb)
 *
 *    This is a handler for ircomm_disconnect_indication. since this
 *    function is called in the context of interrupt handler,
 *    interruptible_sleep_on() MUST not be used.
 */
void irvtd_disconnect_indication(void *instance, void *sap , LM_REASON reason,
				 struct sk_buff *skb)
{
	struct irvtd_cb *driver = (struct irvtd_cb *)instance;

	ASSERT(driver != NULL, return;);
	ASSERT(driver->tty != NULL, return;);
	ASSERT(driver->magic == IRVTD_MAGIC, return;);

	DEBUG(4,"irvtd_disconnect_indication:\n");

	driver->tx_disable = 1;
	if(skb_queue_empty(&driver->rxbuff)){
		/* disconnect */
		driver->rx_disable = 1;
		tty_hangup(driver->tty);
	} else {
		driver->disconnect_pend = 1;
	}
}

/*
 * Function irvtd_control_indication (instance, sap, cmd)
 *
 *    
 *
 */
void irvtd_control_indication(void *instance, void *sap, IRCOMM_CMD cmd)
{
	struct irvtd_cb *driver = (struct irvtd_cb *)instance;

	ASSERT(driver != NULL, return;);
	ASSERT(driver->magic == IRVTD_MAGIC, return;);

	DEBUG(4,__FUNCTION__"()\n");

	if(cmd == TX_READY){
		driver->ttp_stoptx = 0;
		driver->tty->hw_stopped = driver->cts_stoptx;

		if(driver->cts_stoptx)
			return;

		/* push tx queue so that client can send at least 1 octet */
		irvtd_send_data_request(driver);
		/* 
		 * driver->tty->write_wait will keep asleep if
		 * our txbuff is full.
		 * now that we can send packets to IrTTP layer,
		 * we kick it here.
		 */
		if ((driver->tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && 
		    driver->tty->ldisc.write_wakeup)
			(driver->tty->ldisc.write_wakeup)(driver->tty);
		return;
	}

	if(cmd == TX_BUSY){
		driver->ttp_stoptx = driver->tty->hw_stopped = 1;
		del_timer( &driver->tx_timer);
		return;
	}


	ASSERT(cmd == CONTROL_CHANNEL,return;);

	switch(driver->comm->pi){

	case DCELINE_STATE:
	driver->msr = driver->comm->peer_dce;

	if(driver->msr & (DELTA_CTS|DELTA_DSR|DELTA_RI|DELTA_DCD)){
		if(driver->msr & DELTA_CTS)
			driver->icount.cts++;
		if(driver->msr & DELTA_DSR)
			driver->icount.dsr++;
		if(driver->msr & DELTA_RI)
			driver->icount.rng++;
		if(driver->msr & DELTA_DCD)
			driver->icount.dcd++;
		wake_up_interruptible(&driver->delta_msr_wait);
	}

	if ((driver->flags & ASYNC_CHECK_CD) && (driver->msr & DELTA_DCD)) {

		DEBUG(0,"CD now %s...\n",
		      (driver->msr & MSR_DCD) ? "on" : "off");

		if (driver->msr & MSR_DCD)
		{
			/* DCD raised! */
			wake_up_interruptible(&driver->open_wait);
		}
		else
		{
			/* DCD falled */
                        DEBUG(0,__FUNCTION__"():hangup..\n");
			tty_hangup(driver->tty);
		}

	}

	if (driver->comm->flow_ctrl & USE_CTS) {
		if (driver->tty->hw_stopped) {
			if (driver->msr & MSR_CTS) {
				DEBUG(0,"CTS tx start...\n");

				driver->cts_stoptx = 0;
				driver->tty->hw_stopped = driver->ttp_stoptx;
				/*  
				 * replacement of 
				 * rs_sched_event(info, RS_EVENT_WRITE_WAKEUP)
				 * in serial.c
				 */

				if ((driver->tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
				    driver->tty->ldisc.write_wakeup)
					(driver->tty->ldisc.write_wakeup)(driver->tty);

				wake_up_interruptible(&driver->tty->write_wait);
				return;
			}
		} else {
			if (!(driver->msr & MSR_CTS)) {
				DEBUG(0,"CTS tx stop...");

				driver->cts_stoptx = 1;
				driver->tty->hw_stopped = 1;
                        }
                }
        }
	break;

	case FLOW_CONTROL:
	case DATA_RATE:
	case XON_XOFF_CHAR:
	case DTELINE_STATE:
	case ENQ_ACK_CHAR:	/* got this from win95 */
		/* (maybe) nothing to do  */
		break;
	default:
		DEBUG(0,__FUNCTION__"():PI = 0x%02x is not implemented\n",
		      (int)driver->comm->pi);
	}
}

/*
 ***********************************************************************
 *
 * driver kernel interfaces
 * these functions work as an interface between the kernel and this driver
 *
 ***********************************************************************
 */



/*
 * ----------------------------------------------------------------------
 * irvtd_open() and friends
 *
 * ----------------------------------------------------------------------
 */


static int irvtd_block_til_ready(struct tty_struct *tty, struct file * filp,
				 struct irvtd_cb *driver)
{

 	DECLARE_WAITQUEUE(wait,current);
	int		retval = 0;
	int		do_clocal = 0;

	/*
	 * If non-blocking mode is set, or the port is not enabled,
	 * then make the check up front and then exit.
	 */

	if ((filp->f_flags & O_NONBLOCK) ||
	    (tty->flags & (1 << TTY_IO_ERROR))) {

		return 0;
	}

	if (tty->termios->c_cflag & CLOCAL)
		do_clocal = 1;


	/*
	 * We wait until ircomm_connect_request() succeed or
	 *   ircomm_connect_indication comes
	 */


	add_wait_queue(&driver->open_wait, &wait);

	DEBUG(0,__FUNCTION__"():before block( line = %d, count = %d )\n",
	      driver->line, driver->count);

	driver->blocked_open++;

	/* wait for a connection established */
	while (1) {
		current->state = TASK_INTERRUPTIBLE;
		
		if (tty_hung_up_p(filp) ||
		    !(driver->flags & ASYNC_INITIALIZED)) {
#ifdef DO_RESTART
			if (driver->flags & ASYNC_HUP_NOTIFY)
				retval = -EAGAIN;
			else
				retval = -ERESTARTSYS;	
#else
			retval = -EAGAIN;
#endif
			break;
		}

		if (!(driver->flags & ASYNC_CLOSING) &&
		    (driver->comm->state == COMM_CONN) && 
		    ( do_clocal || (driver->msr & MSR_DCD)))
			break;

		if(signal_pending(current)){
			retval = -ERESTARTSYS;
			break;
		}


		DEBUG(4,__FUNCTION__"():blocking( %s%d, count = %d )\n",
		      tty->driver.name, driver->line, driver->count);

		schedule();
	}

	current->state = TASK_RUNNING;
	remove_wait_queue(&driver->open_wait, &wait);

	driver->blocked_open--;

	DEBUG(1, __FUNCTION__"():after blocking\n");

	if (retval)
		return retval;
	return 0;
}	

static void change_speed(struct irvtd_cb *driver)
{
	unsigned cflag,cval;

	if (!driver->tty || !driver->tty->termios || !driver->comm)
		return;
	cflag = driver->tty->termios->c_cflag;

	/* 
	 * byte size and parity
	 */
	switch (cflag & CSIZE) 
	{
	case CS5: cval = IRCOMM_WLEN5; break;
	case CS6: cval = IRCOMM_WLEN6; break;
	case CS7: cval = IRCOMM_WLEN7; break;
	case CS8: cval = IRCOMM_WLEN8; break;
	default:  cval = IRCOMM_WLEN5; break;	/* too keep GCC shut... */
	}
	if (cflag & CSTOPB) {      /* use 2 stop bit mode */
		cval |= IRCOMM_STOP2;
	}
	if (cflag & PARENB)
		cval |= IRCOMM_PARENB;    /* enable parity check */
	if (!(cflag & PARODD))
		cval |= IRCOMM_PAREVEN;         /* even parity */
	
	/* CTS flow control flag and modem status interrupts */

	if (cflag & CRTSCTS)
		driver->comm->flow_ctrl |= USE_CTS;
	else
		driver->comm->flow_ctrl |= ~USE_CTS;
	
	if (cflag & CLOCAL)
		driver->flags &= ~ASYNC_CHECK_CD;
	else
		driver->flags |= ASYNC_CHECK_CD;
	
	/*
	 * Set up parity check flag
	 */

	driver->read_status_mask = LSR_OE;
	if (I_INPCK(driver->tty))
		driver->read_status_mask |= LSR_FE | LSR_PE;
	if (I_BRKINT(driver->tty) || I_PARMRK(driver->tty))
		driver->read_status_mask |= LSR_BI;
	
	/*
	 * Characters to ignore
	 */
	driver->ignore_status_mask = 0;
	if (I_IGNPAR(driver->tty))
		driver->ignore_status_mask |= LSR_PE | LSR_FE;

	if (I_IGNBRK(driver->tty)) {
		driver->ignore_status_mask |= LSR_BI;
		/*
		 * If we're ignore parity and break indicators, ignore 
		 * overruns too.  (For real raw support).
		 */
		if (I_IGNPAR(driver->tty)) 
			driver->ignore_status_mask |= LSR_OE;
	}

	driver->comm->data_format = cval;
	ircomm_control_request(driver->comm, DATA_FORMAT);
 	ircomm_control_request(driver->comm, FLOW_CONTROL);
}




static int irvtd_startup(struct irvtd_cb *driver)
{
	struct ias_object* obj;
	struct notify_t irvtd_notify;

	/* FIXME: it should not be hard coded */
	__u8 oct_seq[6] = { 0,1,6,1,1,1 }; 

	DEBUG(4,__FUNCTION__"()\n" );
	if(driver->flags & ASYNC_INITIALIZED)
		return 0;

	/*
	 * initialize our tx/rx buffer
	 */

	skb_queue_head_init(&driver->rxbuff);
	driver->txbuff = dev_alloc_skb(COMM_DEFAULT_SDU_SIZE + COMM_MAX_HEADER_SIZE); 
	if (!driver->txbuff){
		DEBUG(0,__FUNCTION__"(), alloc_skb failed!\n");
		return -ENOMEM;
	}
	skb_reserve(driver->txbuff, COMM_MAX_HEADER_SIZE);

	irda_notify_init(&irvtd_notify);
	irvtd_notify.data_indication = irvtd_receive_data;
	irvtd_notify.connect_confirm = irvtd_connect_confirm;
	irvtd_notify.connect_indication = irvtd_connect_indication;
	irvtd_notify.disconnect_indication = irvtd_disconnect_indication;
	irvtd_notify.flow_indication = irvtd_control_indication;
	strncpy( irvtd_notify.name, "ircomm_tty", NOTIFY_MAX_NAME);
	irvtd_notify.instance = driver;

	driver->comm = ircomm_open_instance(irvtd_notify);
	if (!driver->comm)
		return -ENODEV;


	/* 
         *  Register with LM-IAS as a server
         */

	obj = irias_new_object( "IrDA:IrCOMM", IAS_IRCOMM_ID);
	irias_add_integer_attrib( obj, "IrDA:TinyTP:LsapSel", 
				  driver->comm->tsap->stsap_sel );

	irias_add_octseq_attrib( obj, "Parameters", &oct_seq[0], 6);
	irias_insert_object( obj);

	driver->flags |= ASYNC_INITIALIZED;

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

	change_speed(driver);

	/*
	 * discover a peer device
	 */
	if(driver->tty->termios->c_cflag & CRTSCTS)
	  ircomm_connect_request(driver->comm, NINE_WIRE);
	else
	  ircomm_connect_request(driver->comm, THREE_WIRE);

	/* irvtd_start_timer( driver); */

	driver->rx_disable = 0;
	driver->tx_disable = 1;
	driver->disconnect_pend = 0;
	return 0;
}


int irvtd_open(struct tty_struct * tty, struct file * filp)
{
	struct irvtd_cb *driver;
	int retval;
	int line;

	DEBUG(4, __FUNCTION__"():\n");
	MOD_INC_USE_COUNT;

	line = MINOR(tty->device) - tty->driver.minor_start;
	if ((line <0) || (line >= COMM_MAX_TTY)){
		MOD_DEC_USE_COUNT;
		return -ENODEV;
	}

	driver = irvtd[line];
	ASSERT(driver != NULL, MOD_DEC_USE_COUNT;return -ENOMEM;);
	ASSERT(driver->magic == IRVTD_MAGIC, MOD_DEC_USE_COUNT;return -EINVAL;);

	driver->count++;

	DEBUG(0, __FUNCTION__"():%s%d count %d\n", tty->driver.name, line, 
	      driver->count);
	
	tty->driver_data = driver;
	driver->tty = tty;

	driver->tty->low_latency = (driver->flags & ASYNC_LOW_LATENCY) ? 1 : 0;

	/*
	 * If the device is in the middle of being closed, then block
	 * (sleep) until it's done, then exit.
	 */

	if (tty_hung_up_p(filp) ||
	    (driver->flags & ASYNC_CLOSING)) {
		if (driver->flags & ASYNC_CLOSING)
			interruptible_sleep_on(&driver->close_wait);
#ifdef DO_RESTART
		if (driver->flags & ASYNC_HUP_NOTIFY)
			return -EAGAIN;
		else
			return -ERESTARTSYS;
#else
		return -EAGAIN;
#endif
	}

	/* 
	 * start up discovering process and ircomm_layer 
	 */
	
	retval = irvtd_startup(driver);
	if (retval){
		DEBUG(0, __FUNCTION__"():irvtd_startup returns %d\n",retval);
		return retval;
	}

	retval = irvtd_block_til_ready(tty, filp, driver);
	if (retval){
		DEBUG(0,__FUNCTION__
		      "():returning after block_til_ready (errno = %d)\n", retval);
                return retval;
	}

	driver->session = current->session;
	driver->pgrp = current->pgrp;
	return 0;
}





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

/*
 * Function irvtd_wait_until_sent (tty, timeout)
 *
 *    wait until Tx queue of IrTTP is empty 
 *
 */
static void irvtd_wait_until_sent(struct tty_struct *tty, int timeout)
{
	struct irvtd_cb *driver = (struct irvtd_cb *)tty->driver_data;
	unsigned long orig_jiffies;

	ASSERT(driver != NULL, return;);
	ASSERT(driver->magic == IRVTD_MAGIC, return;);
	ASSERT(driver->comm != NULL, return;);

	DEBUG(1, __FUNCTION__"():\n");	
	if(!tty->closing)
		return;   /* nothing to do */
	
	/* 
	 * at disconnection, we should wait until Tx queue of IrTTP is
	 * flushed
	 */

 	ircomm_disconnect_request(driver->comm, NULL, P_NORMAL);
	orig_jiffies = jiffies;

	while (driver->comm->tsap->disconnect_pend) {
		DEBUG(1, __FUNCTION__"():wait..\n");
		current->state = TASK_INTERRUPTIBLE;
		current->counter = 0;	/* make us low-priority */
		schedule_timeout(HZ);    /* 1sec */
		if (signal_pending(current))
			break;
		if (timeout && time_after(jiffies, orig_jiffies + timeout))
			break;
	}
	current->state = TASK_RUNNING;
}


static void irvtd_shutdown(struct irvtd_cb * driver)
{
	unsigned long	flags;

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

	DEBUG(1,__FUNCTION__"()\n");

	/*
	 * This comment is written in serial.c:
	 *
	 * clear delta_msr_wait queue to avoid mem leaks: we may free the irq
	 * here so the queue might never be waken up
	 */
 	wake_up_interruptible(&driver->delta_msr_wait);
	
	/* clear DTR and RTS */
	if (!driver->tty || (driver->tty->termios->c_cflag & HUPCL))
 		driver->mcr &= ~(MCR_DTR|MCR_RTS);

	driver->comm->dte = driver->mcr;
	ircomm_control_request(driver->comm, DTELINE_STATE );



	save_flags(flags); cli(); /* Disable interrupts */

	if (driver->tty)
		set_bit(TTY_IO_ERROR, &driver->tty->flags);
	
	del_timer( &driver->tx_timer);
	del_timer( &driver->rx_timer);

	irias_delete_object("IrDA:IrCOMM");

	/*
	 * Free the transmit buffer here 
	 */

	while(skb_queue_len(&driver->rxbuff)){
		struct sk_buff *skb;
		skb = skb_dequeue( &driver->rxbuff);
		dev_kfree_skb(skb);
	}
	if(driver->txbuff){
		dev_kfree_skb(driver->txbuff);
		driver->txbuff = NULL;
	}
	ircomm_close_instance(driver->comm);
	driver->comm = NULL;
	driver->flags &= ~ASYNC_INITIALIZED;
	restore_flags(flags);
}

void irvtd_close(struct tty_struct * tty, struct file * filp)
{
	struct irvtd_cb *driver = (struct irvtd_cb *)tty->driver_data;
	int line;
	unsigned long flags;

	DEBUG(1, __FUNCTION__"():refcount= %d\n",irvtd_refcount);

	ASSERT(driver != NULL, return;);
	ASSERT(driver->magic == IRVTD_MAGIC, return;);

	save_flags(flags);cli();


	if(tty_hung_up_p(filp)){
		/*
		 * upper tty layer caught a HUP signal and called irvtd_hangup()
		 * before. so we do nothing here.
		 */
		DEBUG(1, __FUNCTION__"():tty_hung_up_p.\n");
		MOD_DEC_USE_COUNT;
		restore_flags(flags);
		return;
	}
	

	line = MINOR(tty->device) - tty->driver.minor_start;
	DEBUG(0, __FUNCTION__"():%s%d count %d\n", tty->driver.name, line, 
	      driver->count);

	if ((tty->count == 1) && (driver->count != 1)) {
		/*
		 * Uh, oh.  tty->count is 1, which means that the tty
		 * structure will be freed.  Driver->count should always
		 * be one in these conditions.  If it's greater than
		 * one, we've got real problems, since it means the
		 * ircomm service layer won't be shutdown.
		 */
		printk(KERN_ERR"irvtd_close: bad serial port count;" 
		       "tty->count is 1, but driver->count is %d\n", driver->count);
		driver->count = 1;
	}
	if (--driver->count < 0) {
		printk(KERN_ERR"irvtd_close: bad count for line%d: %d\n",
		       line, driver->count);
		driver->count = 0;
	}

	if (driver->count) { 	/* do nothing */
		DEBUG(0, __FUNCTION__"():driver->count is not 0\n");
		MOD_DEC_USE_COUNT;
		restore_flags(flags);
		return; 
	}

	driver->flags |= ASYNC_CLOSING;
	
	/*
	 * 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 (driver->closing_wait != ASYNC_CLOSING_WAIT_NONE){
		DEBUG(4, __FUNCTION__"():calling tty_wait_until_sent()\n");
		tty_wait_until_sent(tty, driver->closing_wait);
	}
	/* 
	 * we can send disconnect_request with P_HIGH since
	 * tty_wait_until_sent() and irvtd_wait_until_sent() should
	 * have disconnected the link
	 */
	ircomm_disconnect_request(driver->comm, NULL, P_HIGH);
	
	/* 
	 * Now we stop accepting input.
	 */

	driver->rx_disable = TRUE;
	/* drop ldisc's buffer */
	if (tty->ldisc.flush_buffer)
		tty->ldisc.flush_buffer(tty);

 	if (tty->driver.flush_buffer) 
 		tty->driver.flush_buffer(driver->tty);  

	tty->closing = 0;
	driver->tty = NULL;

	if (driver->blocked_open)
	{
		if (driver->close_delay) {
			/* kill time */
			current->state = TASK_INTERRUPTIBLE;
			schedule_timeout(driver->close_delay);
		}
		wake_up_interruptible(&driver->open_wait);
	}
	irvtd_shutdown(driver);
	driver->flags &= ~ASYNC_CLOSING;
        wake_up_interruptible(&driver->close_wait); 

	MOD_DEC_USE_COUNT;
        restore_flags(flags);
	DEBUG(4, __FUNCTION__"():done\n");
}

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

int irvtd_write(struct tty_struct * tty, int from_user,
		const unsigned char *buf, int count)
{
	struct irvtd_cb *driver;
	int c = 0;
	int wrote = 0;
 	unsigned long flags;
	struct sk_buff *skb;
	__u8 *frame;

	ASSERT(tty != NULL, return -EFAULT;);
	driver = (struct irvtd_cb *)tty->driver_data;
	ASSERT(driver != NULL, return -EFAULT;);
	ASSERT(driver->magic == IRVTD_MAGIC, return -EFAULT;);

	DEBUG(4, __FUNCTION__"()\n");

	save_flags(flags);
	while(count > 0){
		cli();
		skb = driver->txbuff;
		ASSERT(skb != NULL, break;);
		c = MIN(count, (skb_tailroom(skb)));
		if (c <= 0)
		{
			if(!driver->ttp_stoptx)
			{
				irvtd_send_data_request(driver);
				continue;
			}
			else
				break;
		}

		/* write to the frame */

		frame = skb_put(skb,c);
		if(from_user){
			copy_from_user(frame,buf,c);
		} else
			memcpy(frame, buf, c);

		restore_flags(flags);
		wrote += c;
		count -= c;
		buf += c;
	}
	restore_flags(flags);
	irvtd_send_data_request(driver);
	return (wrote);
}

void irvtd_flush_chars(struct tty_struct *tty)
{
	struct irvtd_cb *driver = (struct irvtd_cb *)tty->driver_data;
	ASSERT( driver != NULL, return;);
	ASSERT( driver->magic == IRVTD_MAGIC, return;);

	DEBUG(4, __FUNCTION__"()\n");
	irvtd_send_data_request(driver);
}


/*
 * Function irvtd_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 irvtd_put_char(struct tty_struct *tty, unsigned char ch)
{
	__u8 *frame ;
	struct irvtd_cb *driver = (struct irvtd_cb *)tty->driver_data;
	struct sk_buff *skb;
	unsigned long flags;

	ASSERT(driver != NULL, return;);
	DEBUG(4, __FUNCTION__"()\n");


 again:
	save_flags(flags);cli();
	skb = driver->txbuff;
	ASSERT(skb != NULL,return;);
	if(!skb_tailroom(skb))
	{
		restore_flags(flags);
		irvtd_send_data_request(driver);
		goto again;
	}
	ASSERT(skb_tailroom(skb) > 0, return;);
	DEBUG(4, "irvtd_put_char(0x%02x) skb_len(%d) room(%d):\n",
	      (int)ch ,(int)skb->len,
	      skb_tailroom(skb));

	/* append a character  */
	frame = skb_put(skb,1);
	frame[0] = ch;

	restore_flags(flags);
	irvtd_start_tx_timer(driver,20);
	return;
}

/*
 * Function irvtd_write_room (tty)
 *
 *    This routine returns the room that our buffer has now.
 *
 */
int irvtd_write_room(struct tty_struct *tty){

	int ret;
	struct sk_buff *skb = ((struct irvtd_cb *) tty->driver_data)->txbuff;

	ASSERT(skb !=NULL, return 0;);

	ret = skb_tailroom(skb);

	DEBUG(4, __FUNCTION__"(): room is %d bytes\n",ret);

	return(ret);
}

/*
 * Function irvtd_chars_in_buffer (tty)
 *
 *    This function returns how many characters which have not been sent yet 
 *    are still in buffer.
 *
 */
int irvtd_chars_in_buffer(struct tty_struct *tty){

	struct sk_buff *skb;
	unsigned long flags;

	DEBUG(4, __FUNCTION__"()\n");

	save_flags(flags);cli();
	skb = ((struct irvtd_cb *) tty->driver_data)->txbuff;
	if(skb == NULL) goto err;

	restore_flags(flags);
	return (skb->len );
err:	
	ASSERT(skb != NULL, ;);
	restore_flags(flags);
	return 0;   /* why not -EFAULT or such? see driver/char/serial.c */
}

/*
 * Function irvtd_break (tty, break_state)
 *
 *    Routine which turns the break handling on or off
 *
 */
static void irvtd_break(struct tty_struct *tty, int break_state){
	
 	struct irvtd_cb *driver = (struct irvtd_cb *)tty->driver_data;
	unsigned long flags;

	DEBUG(0, __FUNCTION__"()\n");
	ASSERT(tty->driver_data != NULL, return;);
	ASSERT(driver->magic == IRVTD_MAGIC, return;);
	
	save_flags(flags);cli();
	if (break_state == -1)
	{
		driver->comm->break_signal = 0x01;
		ircomm_control_request(driver->comm, BREAK_SIGNAL);

	}
	else
	{
		driver->comm->break_signal = 0x00;
		ircomm_control_request(driver->comm, BREAK_SIGNAL);

	}

	restore_flags(flags);
}

/*
 * ----------------------------------------------------------------------
 * irvtd_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 IrCOMM because some ioctls are not implemented.
 * we should add some ioctls and make some tool which is resemble to setserial.
 * ----------------------------------------------------------------------
 */

static int get_modem_info(struct irvtd_cb * driver, unsigned int *value)
{
	unsigned int result;
	result =  ((driver->mcr & MCR_RTS) ? TIOCM_RTS : 0)
		| ((driver->mcr & MCR_DTR) ? TIOCM_DTR : 0)
		| ((driver->msr & DELTA_DCD) ? TIOCM_CAR : 0)
		| ((driver->msr & DELTA_RI) ? TIOCM_RNG : 0)
		| ((driver->msr & DELTA_DSR) ? TIOCM_DSR : 0)
		| ((driver->msr & DELTA_CTS) ? TIOCM_CTS : 0);
	return put_user(result,value);
}

static int set_modem_info(struct irvtd_cb * driver, unsigned int cmd,
			  unsigned int *value)
{ 
	int error;
	unsigned int arg;

	error = get_user(arg, value);
	if(error)
		return error;

	switch (cmd) {
	case TIOCMBIS: 
		if (arg & TIOCM_RTS) 
			driver->mcr |= MCR_RTS;
		if (arg & TIOCM_DTR)
			driver->mcr |= MCR_DTR;
		break;
		
	case TIOCMBIC:
		if (arg & TIOCM_RTS)
			driver->mcr &= ~MCR_RTS;
		if (arg & TIOCM_DTR)
 			driver->mcr &= ~MCR_DTR;
 		break;
		
	case TIOCMSET:
 		driver->mcr = ((driver->mcr & ~(MCR_RTS | MCR_DTR))
			       | ((arg & TIOCM_RTS) ? MCR_RTS : 0)
			       | ((arg & TIOCM_DTR) ? MCR_DTR : 0));
		break;
		
	default:
		return -EINVAL;
	}
	
	driver->comm->dte = driver->mcr;
	ircomm_control_request(driver->comm, DTELINE_STATE );

	return 0;
}

static int get_serial_info(struct irvtd_cb * driver,
			   struct serial_struct * retinfo)
{
	struct serial_struct tmp;
   
	if (!retinfo)
		return -EFAULT;
	memset(&tmp, 0, sizeof(tmp));
	tmp.line = driver->line;
	tmp.flags = driver->flags;
	tmp.baud_base = driver->comm->data_rate;
	tmp.close_delay = driver->close_delay;
	tmp.closing_wait = driver->closing_wait;

	/* for compatibility  */

 	tmp.type = PORT_16550A;
 	tmp.port = 0;
 	tmp.irq = 0;
	tmp.xmit_fifo_size = 0;
	tmp.hub6 = 0;   
	tmp.custom_divisor = driver->custom_divisor;

	if (copy_to_user(retinfo,&tmp,sizeof(*retinfo)))
		return -EFAULT;
	return 0;
}

static int set_serial_info(struct irvtd_cb * driver,
			   struct serial_struct * new_info)
{
	struct serial_struct new_serial;
	struct irvtd_cb old_driver;

	if (copy_from_user(&new_serial,new_info,sizeof(new_serial)))
		return -EFAULT;

	old_driver = *driver;
  
	if (!capable(CAP_SYS_ADMIN)) {
		if ((new_serial.baud_base != driver->comm->data_rate) ||
		    (new_serial.close_delay != driver->close_delay) ||
		    ((new_serial.flags & ~ASYNC_USR_MASK) !=
		     (driver->flags & ~ASYNC_USR_MASK)))
			return -EPERM;
		driver->flags = ((driver->flags & ~ASYNC_USR_MASK) |
				 (new_serial.flags & ASYNC_USR_MASK));
		driver->custom_divisor = new_serial.custom_divisor;
		goto check_and_exit;
	}

	/*
	 * OK, past this point, all the error checking has been done.
	 * At this point, we start making changes.....
	 */

	if(driver->comm->data_rate != new_serial.baud_base){
		driver->comm->data_rate = new_serial.baud_base;
		if(driver->comm->state == COMM_CONN)
			ircomm_control_request(driver->comm,DATA_RATE);
	}
	driver->close_delay = new_serial.close_delay * HZ/100;
	driver->closing_wait = new_serial.closing_wait * HZ/100;
	driver->custom_divisor = new_serial.custom_divisor;

	driver->flags = ((driver->flags & ~ASYNC_FLAGS) |
			 (new_serial.flags & ASYNC_FLAGS));
	driver->tty->low_latency = (driver->flags & ASYNC_LOW_LATENCY) ? 1 : 0;

 check_and_exit:
	if (driver->flags & ASYNC_INITIALIZED) {
		if (((old_driver.flags & ASYNC_SPD_MASK) !=
		     (driver->flags & ASYNC_SPD_MASK)) ||
		    (old_driver.custom_divisor != driver->custom_divisor)) {
			if ((driver->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI)
				driver->tty->alt_speed = 57600;
			if ((driver->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI)
				driver->tty->alt_speed = 115200;
			if ((driver->flags & ASYNC_SPD_MASK) == ASYNC_SPD_SHI)
				driver->tty->alt_speed = 230400;
			if ((driver->flags & ASYNC_SPD_MASK) == ASYNC_SPD_WARP)
				driver->tty->alt_speed = 460800;
			change_speed(driver);
		}
	}
	return 0;
}




int irvtd_ioctl(struct tty_struct *tty, struct file * file,
		unsigned int cmd, unsigned long arg)
{
	int error;
	unsigned long flags;
 	struct irvtd_cb *driver = (struct irvtd_cb *)tty->driver_data;

	struct serial_icounter_struct cnow,cprev;
	struct serial_icounter_struct *p_cuser;	/* user space */


	DEBUG(4,"irvtd_ioctl:requested ioctl(0x%08x)\n",cmd);

#ifdef IRVTD_DEBUG_IOCTL
	{
		/* kill time so that debug messages will come slowly  */
		save_flags(flags);cli();
		current->state = TASK_INTERRUPTIBLE;
		current->timeout = jiffies + HZ/4; /*0.25sec*/
		schedule();
		restore_flags(flags);
	}
#endif



	if ((cmd != TIOCGSERIAL) && (cmd != TIOCSSERIAL) &&
	    (cmd != TIOCSERCONFIG) && (cmd != TIOCSERGSTRUCT) &&
	    (cmd != TIOCMIWAIT) && (cmd != TIOCGICOUNT)) {
		if (tty->flags & (1 << TTY_IO_ERROR)){
			return -EIO;
		}
	}
	
	switch (cmd) {

	case TIOCMGET:
		return get_modem_info(driver, (unsigned int *) arg);

	case TIOCMBIS:
	case TIOCMBIC:
	case TIOCMSET:
		return set_modem_info(driver, cmd, (unsigned int *) arg);
	case TIOCGSERIAL:
		return get_serial_info(driver, (struct serial_struct *) arg);
	case TIOCSSERIAL:
		return set_serial_info(driver, (struct serial_struct *) arg);


	case TIOCMIWAIT:
		save_flags(flags); cli();
		/* note the counters on entry */
		cprev = driver->icount;
		restore_flags(flags);
		while (1) {
			interruptible_sleep_on(&driver->delta_msr_wait);
				/* see if a signal did it */
			if (signal_pending(current))
				return -ERESTARTSYS;
			save_flags(flags); cli();
			cnow = driver->icount; /* atomic copy */
			restore_flags(flags);
			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:
		save_flags(flags); cli();
		cnow = driver->icount;
		restore_flags(flags);
		p_cuser = (struct serial_icounter_struct *) arg;
		error = put_user(cnow.cts, &p_cuser->cts);
		if (error) return error;
		error = put_user(cnow.dsr, &p_cuser->dsr);
		if (error) return error;
		error = put_user(cnow.rng, &p_cuser->rng);
		if (error) return error;
		error = put_user(cnow.dcd, &p_cuser->dcd);
		if (error) return error;
		error = put_user(cnow.rx, &p_cuser->rx);
		if (error) return error;
		error = put_user(cnow.tx, &p_cuser->tx);
		if (error) return error;
		error = put_user(cnow.frame, &p_cuser->frame);
		if (error) return error;
		error = put_user(cnow.overrun, &p_cuser->overrun);
		if (error) return error;
		error = put_user(cnow.parity, &p_cuser->parity);
		if (error) return error;
		error = put_user(cnow.brk, &p_cuser->brk);
		if (error) return error;
		error = put_user(cnow.buf_overrun, &p_cuser->buf_overrun);
		if (error) return error;			
		return 0;
		
		
		/* ioctls which are imcompatible with serial.c */

	case TIOCSERGSTRUCT:
		DEBUG(0,__FUNCTION__"():sorry, TIOCSERGSTRUCT is not supported\n");
		return -ENOIOCTLCMD;  
	case TIOCSERGETLSR:
		DEBUG(0,__FUNCTION__"():sorry, TIOCSERGETLSR is not supported\n");
		return -ENOIOCTLCMD;  
 	case TIOCSERCONFIG:
		DEBUG(0,__FUNCTION__"():sorry, TIOCSERCONFIG is not supported\n");
		return -ENOIOCTLCMD;  


	default:
		return -ENOIOCTLCMD;  /* ioctls which we must ignore */
	}
	return 0;
}



/*
 * ----------------------------------------------------------------------
 * irvtd_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 irvtd_set_termios(struct tty_struct *tty, struct termios * old_termios){

	struct irvtd_cb *driver = (struct irvtd_cb *)tty->driver_data;

	ASSERT(driver != NULL,return;);
	ASSERT(driver->magic == IRVTD_MAGIC ,return;);

	DEBUG(0, "irvtd_set_termios:\n");
	return;

	if((tty->termios->c_cflag == old_termios->c_cflag) &&
	   (RELEVANT_IFLAG(tty->termios->c_iflag) 
	    == RELEVANT_IFLAG(old_termios->c_iflag)))
		return;

	change_speed(driver);

	if((old_termios->c_cflag & CRTSCTS) &&
	   !(tty->termios->c_cflag & CRTSCTS)) {
		tty->hw_stopped = driver->ttp_stoptx;
		/* irvtd_start(tty); */       /* we don't need this */
	}
}

/*
 * ----------------------------------------------------------------------
 * irvtd_throttle,irvtd_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
 * ----------------------------------------------------------------------
 */

static void irvtd_send_xchar(struct tty_struct *tty, char ch){

	DEBUG(1, __FUNCTION__"():\n");
	irvtd_put_char(tty, ch);
}

void irvtd_throttle(struct tty_struct *tty){
	struct irvtd_cb *driver = (struct irvtd_cb *)tty->driver_data;

	DEBUG(1, "irvtd_throttle:\n");

	if (I_IXOFF(tty))
		irvtd_put_char(tty, STOP_CHAR(tty));

	driver->mcr &= ~MCR_RTS; 
	driver->mcr |= DELTA_RTS; 
	driver->comm->dte = driver->mcr;
	ircomm_control_request(driver->comm, DTELINE_STATE );

	DEBUG(1, __FUNCTION__"():FLOW_STOP\n");
        irttp_flow_request(driver->comm->tsap, FLOW_STOP);
}

void irvtd_unthrottle(struct tty_struct *tty){
	struct irvtd_cb *driver = (struct irvtd_cb *)tty->driver_data;
	DEBUG(1, "irvtd_unthrottle:\n");

	if (I_IXOFF(tty))
		irvtd_put_char(tty, START_CHAR(tty));

	driver->mcr |= (MCR_RTS|DELTA_RTS);
	driver->comm->dte = driver->mcr;
	ircomm_control_request(driver->comm, DTELINE_STATE );

	DEBUG(1, __FUNCTION__"():FLOW_START\n");
        irttp_flow_request(driver->comm->tsap, FLOW_START);
}


/*
 * ------------------------------------------------------------
 * irvtd_stop() and irvtd_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
irvtd_stop(struct tty_struct *tty){
 	DEBUG(0, "irvtd_stop()\n");

	struct irvtd_cb *info = (struct irvtd_cb *)tty->driver_data; 
	DEBUG(0, "irvtd_start():not implemented!\n");
}
irvtd_start(struct tty_struct *tty){

	struct irvtd_cb *info = (struct irvtd_cb *)tty->driver_data;
	DEBUG(0, "irvtd_start():not_implemented!\n");
}
#endif

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

void irvtd_hangup(struct tty_struct *tty){

	struct irvtd_cb *info = (struct irvtd_cb *)tty->driver_data;
	DEBUG(0, __FUNCTION__"()\n");

	irvtd_flush_buffer(tty);
	irvtd_shutdown(info);
	info->count = 0;
	info->tty = NULL;
	wake_up_interruptible(&info->open_wait);
}

void irvtd_flush_buffer(struct tty_struct *tty){

	struct irvtd_cb *driver = (struct irvtd_cb *)tty->driver_data;
	struct sk_buff *skb;

	skb = driver->txbuff;
	ASSERT(skb != NULL, return;); 

	if(skb->len){
		DEBUG(0, __FUNCTION__"():%d chars in txbuff are lost..\n",(int)skb->len);
		skb_trim(skb,0); 
	}
	
	/* write_wait is a wait queue of tty_wait_until_sent().
	 * see tty_io.c of kernel 
	 */
	wake_up_interruptible(&tty->write_wait); 

	if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
	    tty->ldisc.write_wakeup)
		(tty->ldisc.write_wakeup)(tty);
}



/*
 * Function ircomm_register_device(void), init_module() and friends
 *
 *   we register "port emulation entity"(see IrCOMM specification) here
 *   as a tty device.
 */

int irvtd_register_ttydriver(void){

        DEBUG( 4, "-->irvtd_register_ttydriver\n");

	/* setup virtual serial port device */

        /* Initialize the tty_driver structure ,which is defined in 
	   tty_driver.h */
        
        memset(&irvtd_drv, 0, sizeof(struct tty_driver));
	irvtd_drv.magic = IRVTD_MAGIC;
	irvtd_drv.driver_name = "IrCOMM_tty";
	irvtd_drv.name = "irnine";
	irvtd_drv.major = IRCOMM_MAJOR;
	irvtd_drv.minor_start = IRVTD_MINOR;
	irvtd_drv.num = COMM_MAX_TTY;
	irvtd_drv.type = TTY_DRIVER_TYPE_SERIAL;  /* see tty_driver.h */


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

        irvtd_drv.init_termios = tty_std_termios;
        irvtd_drv.init_termios.c_cflag =
		B9600 | CS8 | CREAD | HUPCL | CLOCAL;
        irvtd_drv.flags = TTY_DRIVER_REAL_RAW;   /* see tty_driver.h */
        irvtd_drv.refcount = &irvtd_refcount;

	/* pointer to the tty data structures */

        irvtd_drv.table = irvtd_table;  
        irvtd_drv.termios = irvtd_termios;
        irvtd_drv.termios_locked = irvtd_termios_locked;

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

        irvtd_drv.open = irvtd_open;
        irvtd_drv.close = irvtd_close;
        irvtd_drv.write = irvtd_write;
        irvtd_drv.put_char = irvtd_put_char;
	irvtd_drv.flush_chars = irvtd_flush_chars;
        irvtd_drv.write_room = irvtd_write_room;
	irvtd_drv.chars_in_buffer = irvtd_chars_in_buffer; 
        irvtd_drv.flush_buffer = irvtd_flush_buffer;
 	irvtd_drv.ioctl = irvtd_ioctl; 
	irvtd_drv.throttle = irvtd_throttle;
	irvtd_drv.unthrottle = irvtd_unthrottle;
 	irvtd_drv.set_termios = irvtd_set_termios;
	irvtd_drv.stop = NULL;          /*  irvtd_stop; */
	irvtd_drv.start = NULL;         /* irvtd_start; */
        irvtd_drv.hangup = irvtd_hangup;

        irvtd_drv.send_xchar = irvtd_send_xchar;
	irvtd_drv.break_ctl = irvtd_break;
	irvtd_drv.read_proc = irvtd_read_proc;
 	irvtd_drv.wait_until_sent = irvtd_wait_until_sent;



	if (tty_register_driver(&irvtd_drv)){
		DEBUG(0,"IrCOMM:Couldn't register tty driver\n");
		return(1);
	}

	DEBUG( 4, "irvtd_register_ttydriver: done.\n");
	return(0);
}


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

void irvtd_unregister_ttydriver(void){

	int err;	
        DEBUG( 4, "--> irvtd_unregister_device\n");

	/* unregister tty device   */

	err = tty_unregister_driver(&irvtd_drv);
        if (err)
                printk("IrCOMM: failed to unregister vtd driver(%d)\n",err);
        DEBUG( 4, "irvtd_unregister_device -->\n");
	return;
}

/*
 **********************************************************************
 *    proc stuff
 *
 **********************************************************************
 */

static int line_info(char *buf, struct irvtd_cb *driver)
{
	int	ret=0;

	ASSERT(driver != NULL,goto exit;);
	ASSERT(driver->magic == IRVTD_MAGIC,goto exit;);

	ret += sprintf(buf, "tx: %d rx: %d"
		      ,driver->icount.tx, driver->icount.rx);

	if (driver->icount.frame)
                ret += sprintf(buf+ret, " fe:%d", driver->icount.frame);
        if (driver->icount.parity)
                ret += sprintf(buf+ret, " pe:%d", driver->icount.parity);
        if (driver->icount.brk)
                ret += sprintf(buf+ret, " brk:%d", driver->icount.brk);  
        if (driver->icount.overrun)
                ret += sprintf(buf+ret, " oe:%d", driver->icount.overrun);

	if (driver->mcr & MCR_RTS)
                ret += sprintf(buf+ret, "|RTS");
        if (driver->msr & MSR_CTS)
                ret += sprintf(buf+ret, "|CTS");
        if (driver->mcr & MCR_DTR)
                ret += sprintf(buf+ret, "|DTR");
        if (driver->msr & MSR_DSR)
                ret += sprintf(buf+ret, "|DSR");
        if (driver->msr & MSR_DCD)
                ret += sprintf(buf+ret, "|CD");
	if (driver->msr & MSR_RI) 
		ret += sprintf(buf+ret, "|RI");

	ret += sprintf(buf+ret, "\n");
	ret += sprintf(buf+ret, "rx queue:%d",
		       skb_queue_len( &driver->rxbuff));
	ret += sprintf(buf+ret, "ttp_stoprx:%s",
		       driver->ttp_stoprx?"TRUE":"FALSE");

 exit:
	ret += sprintf(buf+ret, "\n");
	return ret;
}



static int irvtd_read_proc(char *buf, char **start, off_t offset, int len,
		 int *eof, void *unused)
{
	int i, count = 0, l;
	off_t	begin = 0;

	count += sprintf(buf, "driver revision:%s\n", revision_date);
	for (i = 0; i < COMM_MAX_TTY && count < 4000; i++) {
		l = line_info(buf + count, irvtd[i]);
		count += l;
		if (count+begin > offset+len)
			goto done;
		if (count+begin < offset) {
			begin += count;
			count = 0;
		}
	}
	*eof = 1;
done:
	if (offset >= count+begin)
		return 0;
	*start = buf + (begin-offset);
	return ((len < begin+count-offset) ? len : begin+count-offset);
}




/************************************************************
 *    init & cleanup this module 
 ************************************************************/

__initfunc(int irvtd_init(void))
{
	int i;

	DEBUG( 4, __FUNCTION__"()\n");
	printk( KERN_INFO 
		"ircomm_tty: virtual tty driver for IrCOMM ( revision:%s )\n",
		revision_date);


	/* allocate a master array */

	irvtd = (struct irvtd_cb **) kmalloc( sizeof(void *) *
						COMM_MAX_TTY,GFP_KERNEL);
	if ( irvtd == NULL) {
		printk( KERN_WARNING __FUNCTION__"(): kmalloc failed!\n");
		return -ENOMEM;
	}

	memset( irvtd, 0, sizeof(void *) * COMM_MAX_TTY);

	for (i=0; i < COMM_MAX_TTY; i++){
		irvtd[i] = kmalloc( sizeof(struct irvtd_cb), GFP_KERNEL);	
		if(irvtd[i] == NULL){
			printk(KERN_ERR __FUNCTION__"(): kmalloc failed!\n");
			return -ENOMEM;
		}
		memset( irvtd[i], 0, sizeof(struct irvtd_cb));
		irvtd[i]->magic = IRVTD_MAGIC;
		irvtd[i]->line = i;
		irvtd[i]->closing_wait = 10*HZ ;
		irvtd[i]->close_delay = 5*HZ/10 ; 
		init_waitqueue_head(&irvtd[i]->open_wait);
		init_waitqueue_head(&irvtd[i]->close_wait);
		init_waitqueue_head(&irvtd[i]->tx_wait);
		init_waitqueue_head(&irvtd[i]->delta_msr_wait);
	}

	/* 
	 * initialize a "port emulation entity"
	 */

	if(irvtd_register_ttydriver()){
		printk( KERN_WARNING "IrCOMM: Error in ircomm_register_device\n");
		return -ENODEV;
	}

	return 0;
}

void irvtd_cleanup(void)
{
	int i;
	DEBUG( 4, __FUNCTION__"()\n");

	/*
	 * free some resources
	 */
	if (irvtd) {
		for (i=0; i<COMM_MAX_TTY; i++) {
			if (irvtd[i]) {
				if(irvtd[i]->comm)
					ircomm_close_instance(irvtd[i]->comm);
				if(irvtd[i]->txbuff)
					dev_kfree_skb(irvtd[i]->txbuff);
				DEBUG( 4, "freeing structures\n");
				kfree(irvtd[i]);
				irvtd[i] = NULL;
			}
		}
		DEBUG( 4, "freeing master array\n");
		kfree(irvtd);
		irvtd = NULL;
	}

 	irvtd_unregister_ttydriver();

}

#ifdef MODULE

int init_module(void) 
{
	irvtd_init();
	return 0;
}

/*
 * Function ircomm_cleanup (void)
 *   This is called when you rmmod.
 */

void cleanup_module(void)
{
	irvtd_cleanup();
}

#endif /* MODULE */
