patch-2.1.132 linux/net/irda/ircomm/irvtd_driver.c

Next file: linux/net/irda/irda_device.c
Previous file: linux/net/irda/ircomm/irvtd.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.1.131/linux/net/irda/ircomm/irvtd_driver.c linux/net/irda/ircomm/irvtd_driver.c
@@ -0,0 +1,1869 @@
+/*********************************************************************
+ *                
+ * Filename:      irvtd_driver.c
+ * Version:       
+ * Description:   An implementation of "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, 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/fs.h>
+#include <linux/sched.h>
+#include <linux/termios.h>
+#include <asm/segment.h>
+#include <asm/uaccess.h>
+
+#include <net/irda/irda.h>
+#include <net/irda/irttp.h>
+
+#include <net/irda/irvtd.h>
+#include <net/irda/irvtd_driver.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))
+
+static char *irvtd_ttyname = "irnine";
+struct tty_driver irvtd_drv, irvtd_callout_driver;
+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 ircomm_vsd_refcount;
+extern struct ircomm_cb **ircomm;
+extern struct irvtd_cb **irvtd;
+
+/*
+ * 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);
+
+static void flush_txbuff(struct irvtd_cb *info);
+static void change_speed(struct irvtd_cb *driver);
+static void irvtd_write_to_tty( void *instance );
+
+static void irvtd_break(struct tty_struct *tty, int break_state);
+static void irvtd_send_xchar(struct tty_struct *tty, char ch);
+
+#if 0
+static char *rcsid = "$Id: irvtd_driver.c,v 1.13 1998/12/06 10:09:07 takahide Exp $";
+#endif
+
+
+
+
+/*
+ * Function ircomm_register_device(void) 
+ *   we register "port emulation entity"(see IrCOMM specification) here
+ *   as a tty device.
+ *   it will be called when you insmod.
+ *   ( This function derives from linux/drivers/char/serial.c )
+ */
+
+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.name = irvtd_ttyname;
+	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 */
+	irvtd_drv.subtype = IRVTD_TYPE_NORMAL;      /* private type */
+
+	/*
+	 * 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 = &ircomm_vsd_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 = NULL;
+	irvtd_drv.wait_until_sent = NULL;
+
+        /*
+         * The callout device is just like normal device except for
+         * minor number and the subtype.
+         */
+
+	/* What is difference between callout device and normal device? */
+	/* My system dosen't have /dev/cua??, so we don't need it? :{| */
+	irvtd_callout_driver = irvtd_drv;
+	irvtd_callout_driver.name = "irninecua";
+	irvtd_callout_driver.minor_start = IRVTD_CALLOUT_MINOR; 
+	irvtd_callout_driver.subtype = IRVTD_TYPE_CALLOUT;
+
+
+	if (tty_register_driver(&irvtd_drv)){
+		DEBUG(0,"IrCOMM:Couldn't register tty driver\n");
+		return(1);
+	}
+	if (tty_register_driver(&irvtd_callout_driver))
+		DEBUG(0,"IrCOMM:Couldn't register callout tty driver\n");
+
+	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);
+	err = tty_unregister_driver(&irvtd_callout_driver);
+        if (err)
+                printk("IrCOMM: failed to unregister vtd_callout driver(%d)\n", err);
+
+        DEBUG( 4, "irvtd_unregister_device -->\n");
+	return;
+}
+
+
+
+/*
+ * ----------------------------------------------------------------------
+ * Routines for Virtual tty driver
+ *
+ *   most of infomation is descrived in linux/tty_driver.h, but
+ *   a function ircomm_receive() derives from receive_chars() which is
+ *   in 2.0.30 kernel (driver/char/serial.c).
+ *   if you want to understand them, please see related kernel source 
+ *   (and my comments :).
+ * ----------------------------------------------------------------------
+ */
+
+/*
+ * ----------------------------------------------------------------------
+ * ircomm_receive_data()
+ *
+ * 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( void *instance ){
+	
+	int status, c, flag;
+	
+	struct sk_buff *skb;
+	struct irvtd_cb *driver = (struct irvtd_cb *)instance;
+	struct tty_struct *tty = driver->tty;
+	
+	/* does instance still exist ? should be checked */
+	ASSERT(driver->magic == IRVTD_MAGIC, return;);
+	
+	if(driver->rx_disable ){
+		DEBUG(0,__FUNCTION__"rx_disable is true:do_nothing..\n");
+		return;
+	}
+	
+	skb = skb_dequeue(&driver->rxbuff);
+	ASSERT(skb != NULL, return;); /* there's nothing */
+	IS_SKB(skb, return;);
+	
+#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;
+	
+	/* 
+	 * FIXME: we must do ircomm_parse_ctrl() here, instead of 
+	 * ircomm_common.c!! 
+	 */
+	
+
+	/* 
+	 * 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 & IRVTD_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(0,"writing %d chars to tty\n",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(0,__FUNCTION__":retrying frame!\n");
+		skb_queue_head( &driver->rxbuff, skb );
+	}
+	
+	/*
+	 * in order to optimize this routine, these two tasks should be
+	 * queued in following order
+	 * ( see run_task_queue() and queue_task() in tqueue.h
+	 */
+	if(skb_queue_len(&driver->rxbuff))
+		/* let me try again! */
+		queue_task(&driver->rx_tqueue, &tq_timer);
+	if(c)
+		/* read your buffer! */
+		queue_task(&tty->flip.tqueue, &tq_timer);
+	
+
+	if(skb_queue_len(&driver->rxbuff)< IRVTD_RX_QUEUE_LOW
+	   &&  driver->ttp_stoprx){
+		irttp_flow_request(driver->comm->tsap, FLOW_START);
+		driver->ttp_stoprx = 0;
+	}
+}
+
+void irvtd_receive_data(void *instance, void *sap, struct sk_buff *skb){
+	
+	struct irvtd_cb *driver = (struct irvtd_cb *)instance;
+
+	ASSERT(driver != NULL, return;);
+	ASSERT(driver->magic == IRVTD_MAGIC, return;);
+
+	/* queue incoming data and make bottom half handler ready */
+
+	skb_queue_tail( &driver->rxbuff, skb );
+	if(skb_queue_len(&driver->rxbuff) == 1)
+		irvtd_write_to_tty(driver);
+	if(skb_queue_len(&driver->rxbuff) > IRVTD_RX_QUEUE_HIGH){
+		irttp_flow_request(driver->comm->tsap, FLOW_STOP);
+		driver->ttp_stoprx = 1;
+	}
+	return;
+}
+
+#if 0
+void irvtd_receive_data(void *instance, void *sap, struct sk_buff *skb){
+	
+	int flag,status;
+	__u8 c;
+	struct tty_struct *tty;
+	struct irvtd_cb *driver = (struct irvtd_cb *)instance;
+
+	ASSERT(driver != NULL, return;);
+	ASSERT(driver->magic == IRVTD_MAGIC, return;);
+
+	if(driver->rx_disable ){
+		DEBUG(0,__FUNCTION__"rx_disable is true:do nothing\n");
+		return;
+	}
+
+	tty = driver->tty;
+	status = driver->comm->peer_line_status & driver->read_status_mask;
+
+	c = MIN(skb->len, (TTY_FLIPBUF_SIZE - tty->flip.count));
+	DEBUG(0, __FUNCTION__"skb_len=%d, tty->flip.count=%d \n"
+	      ,(int)skb->len, tty->flip.count);
+
+#ifdef IRVTD_DEBUG_RX
+	printk("received data:");
+	{
+		int i;
+		for ( i=0;i<skb->len;i++)
+			printk("%02x ", skb->data[i]);
+		printk("\n");
+	}
+#endif
+
+	/* 
+	 * if there are too many errors which make a character ignored,
+	 * drop characters
+	 */
+
+	if(status & driver->ignore_status_mask){
+		DEBUG(0,__FUNCTION__"I/O error:ignore characters.\n");
+		dev_kfree_skb(skb, FREE_READ);
+		return;
+	} 
+	
+	if (driver->comm->peer_break_signal ) {
+		driver->comm->peer_break_signal = 0;
+		DEBUG(0,"handling break....\n");
+		
+		flag = TTY_BREAK;
+		if (driver->flags & IRVTD_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 is "Orange book"!?
+			 */
+			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(0,"writing %d chars to tty\n",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);
+		queue_task_irq_off(&tty->flip.tqueue, &tq_timer);
+	}
+	if(skb->len >0)
+		DEBUG(0,__FUNCTION__":dropping frame!\n");
+	dev_kfree_skb(skb, FREE_READ);
+	DEBUG(4,__FUNCTION__":done\n");
+}
+#endif
+
+/*
+ * ----------------------------------------------------------------------
+ * indication/confirmation handlers:
+ * they will be registerd in irvtd_startup() to know that we
+ * discovered (or we are discovered by) remote device.
+ * ----------------------------------------------------------------------
+ */
+
+/* this function is called whed ircomm_attach_cable succeed */
+
+void irvtd_attached(struct ircomm_cb *comm){
+
+	ASSERT(comm != NULL, return;);
+	ASSERT(comm->magic == IRCOMM_MAGIC, return;);
+
+	DEBUG(0,"irvtd_attached:sending connect_request"
+	      " for servicetype(%d)..\n",comm->servicetype);
+	ircomm_connect_request(comm, SAR_DISABLE );
+}
+
+
+/*
+ * irvtd_connect_confirm()
+ *  ircomm_connect_request which we have send have succeed!
+ */
+
+void irvtd_connect_confirm(void *instance, void *sap, struct qos_info *qos,
+			   int max_sdu_size, struct sk_buff *skb){
+
+	struct irvtd_cb *driver = (struct irvtd_cb *)instance;
+	ASSERT(driver != NULL, return;);
+	ASSERT(driver->magic == IRVTD_MAGIC, return;);
+
+	/*
+	 * sending initial control parameters here
+	 *
+	 * TODO: it must be done in ircomm_connect_request()
+	 */
+#if 1
+	if(driver->comm->servicetype ==	THREE_WIRE_RAW)
+		return;                /* do nothing */
+
+	ircomm_append_ctrl(driver->comm, SERVICETYPE);
+	/* ircomm_append_ctrl(self, DATA_RATE); */
+	ircomm_append_ctrl(driver->comm, DATA_FORMAT);
+	ircomm_append_ctrl(driver->comm, FLOW_CONTROL);
+	ircomm_append_ctrl(driver->comm, XON_XOFF_CHAR);
+	/* ircomm_append_ctrl(driver->comm, ENQ_ACK_CHAR); */
+
+	switch(driver->comm->servicetype){
+	case CENTRONICS:
+		break;
+
+	case NINE_WIRE:
+		ircomm_append_ctrl(driver->comm, DTELINE_STATE);
+		break;
+	default:
+	}
+	ircomm_control_request(driver->comm);
+#endif
+	
+
+	wake_up_interruptible(&driver->open_wait);
+}
+
+/*
+ * irvtd_connect_indication()
+ *  we are discovered and being requested to connect by remote device !
+ */
+
+void irvtd_connect_indication(void *instance, void *sap, struct qos_info *qos,
+			      int max_sdu_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;);
+
+	DEBUG(4,"irvtd_connect_indication:sending connect_response...\n");
+
+	/*TODO: connect_response should send initialcontrolparameters! TH*/
+
+	ircomm_connect_response(comm, NULL, SAR_DISABLE );
+
+	wake_up_interruptible(&driver->open_wait);
+}
+
+
+
+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");
+	tty_hangup(driver->tty);
+}
+
+/*
+ * irvtd_control_indication
+ *
+ */
+
+
+void irvtd_control_indication(void *instance, void *sap, LOCAL_FLOW flow){
+
+	struct irvtd_cb *driver = (struct irvtd_cb *)instance;
+	__u8 pi; /* instruction of control channel */
+
+	ASSERT(driver != NULL, return;);
+	ASSERT(driver->magic == IRVTD_MAGIC, return;);
+
+	DEBUG(0,"irvtd_control_indication:\n");
+
+	pi = driver->comm->pi;
+
+	switch(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 & IRVTD_ASYNC_CHECK_CD) && (driver->msr & DELTA_DCD)) {
+
+		DEBUG(0,"CD now %s...\n",
+		      (driver->msr & MSR_DCD) ? "on" : "off");
+
+		if (driver->msr & DELTA_DCD)
+			wake_up_interruptible(&driver->open_wait);
+		else if (!((driver->flags & IRVTD_ASYNC_CALLOUT_ACTIVE) &&
+			   (driver->flags & IRVTD_ASYNC_CALLOUT_NOHUP))) {
+
+                        DEBUG(0,"irvtd_control_indication: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;
+/* 				driver->IER &= ~UART_IER_THRI; */
+/* 				serial_out(info, UART_IER, info->IER); */
+                        }
+                }
+        }
+
+
+	break;
+
+	case TX_READY:
+		driver->ttp_stoptx = 0;
+		driver->tty->hw_stopped = driver->cts_stoptx;
+
+		/* 
+		 * driver->tty->write_wait will keep asleep if
+		 * our txbuff is not empty.
+		 * so if we can really send a packet now,
+		 * send it and then wake it up.
+		 */
+
+		if(driver->cts_stoptx)
+			break;
+
+		flush_txbuff(driver);
+		if ((driver->tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && 
+		     driver->tty->ldisc.write_wakeup)
+			(driver->tty->ldisc.write_wakeup)(driver->tty);
+		break;
+
+	case TX_BUSY:
+		driver->ttp_stoptx = driver->tty->hw_stopped = 1;
+		break;
+	default:
+		DEBUG(0,"irvtd:unknown control..\n");
+	
+	}
+}
+
+
+/*
+ * ----------------------------------------------------------------------
+ * irvtd_open() and friends
+ *
+ * 
+ * ----------------------------------------------------------------------
+ */
+
+
+static int irvtd_block_til_ready(struct tty_struct *tty, struct file * filp,
+				 struct irvtd_cb *driver)
+{
+
+ 	struct wait_queue wait = { current, NULL };
+	int		retval;
+	int		do_clocal = 0;
+
+	/*
+	 * If the device is in the middle of being closed, then block
+	 * (sleep) until it's done, and (when being woke up)then try again.
+	 */
+
+	if (tty_hung_up_p(filp) ||
+	    (driver->flags & IRVTD_ASYNC_CLOSING)) {
+		if (driver->flags & IRVTD_ASYNC_CLOSING)
+			interruptible_sleep_on(&driver->close_wait);
+#ifdef DO_RESTART
+		if (driver->flags & IRVTD_ASYNC_HUP_NOTIFY)
+			return -EAGAIN;
+		else
+			return -ERESTARTSYS;
+#else
+		return -EAGAIN;
+#endif
+	}
+
+	/*
+	 * If this is a callout device, then just make sure the normal
+	 * device isn't being used.
+	 */
+
+	if (tty->driver.subtype == IRVTD_TYPE_CALLOUT) {
+		if (driver->flags & IRVTD_ASYNC_NORMAL_ACTIVE)
+			return -EBUSY;
+		if ((driver->flags & IRVTD_ASYNC_CALLOUT_ACTIVE) &&
+		    (driver->flags & IRVTD_ASYNC_SESSION_LOCKOUT) &&
+		    (driver->session != current->session))
+			return -EBUSY;
+		if ((driver->flags & IRVTD_ASYNC_CALLOUT_ACTIVE) &&
+		    (driver->flags & IRVTD_ASYNC_PGRP_LOCKOUT) &&
+		    (driver->pgrp != current->pgrp))
+			return -EBUSY;
+
+		driver->flags |= IRVTD_ASYNC_CALLOUT_ACTIVE;
+		return 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))) {
+		if (driver->flags & IRVTD_ASYNC_CALLOUT_ACTIVE)
+			return -EBUSY;
+
+		driver->flags |= IRVTD_ASYNC_NORMAL_ACTIVE;
+		return 0;
+	}
+
+	if (driver->flags & IRVTD_ASYNC_CALLOUT_ACTIVE) {
+		if (driver->normal_termios.c_cflag & CLOCAL)
+			do_clocal = 1;
+	} else {
+		if (tty->termios->c_cflag & CLOCAL)
+			do_clocal = 1;
+	}
+	
+	/*
+	 * We wait until ircomm_connect_request() succeed or
+	 *   ircomm_connect_indication comes
+	 *
+	 * This is what is written in serial.c:
+	 * "Block waiting for the carrier detect and the line to become
+	 * free (i.e., not in use by the callout).  While we are in
+	 * this loop, driver->count is dropped by one, so that
+	 * rs_close() knows when to free things.  We restore it upon
+	 * exit, either normal or abnormal."
+	 */
+
+	retval = 0;
+	add_wait_queue(&driver->open_wait, &wait);
+
+	DEBUG(0,"block_til_ready before block: line%d, count = %d\n",
+	       driver->line, driver->count);
+
+	cli();
+	if (!tty_hung_up_p(filp)) 
+		driver->count--;
+	sti();
+	driver->blocked_open++;
+
+
+	while (1) {
+		current->state = TASK_INTERRUPTIBLE;
+
+	if (!(driver->flags & IRVTD_ASYNC_CALLOUT_ACTIVE) &&
+	    (driver->comm->state == COMM_CONN)){
+		/* 
+		 * signal DTR and RTS
+		 */
+		driver->comm->dte = driver->mcr |= (MCR_DTR | MCR_RTS |DELTA_DTR|DELTA_RTS);
+
+		ircomm_append_ctrl(driver->comm, DTELINE_STATE);
+		ircomm_control_request(driver->comm);
+	}
+
+		if (tty_hung_up_p(filp) ||
+		    !(driver->flags & IRVTD_ASYNC_INITIALIZED)) {
+#ifdef DO_RESTART
+			if (driver->flags & IRVTD_ASYNC_HUP_NOTIFY)
+				retval = -EAGAIN;
+			else
+				retval = -ERESTARTSYS;	
+#else
+			retval = -EAGAIN;
+#endif
+			break;
+		}
+
+		/*
+		 * if clocal == 0 or received DCD or state become CONN,then break
+		 */
+
+		if (!(driver->flags & IRVTD_ASYNC_CALLOUT_ACTIVE) &&
+		    !(driver->flags & IRVTD_ASYNC_CLOSING) &&
+		    (driver->comm->state == COMM_CONN) && 
+		    ( do_clocal || (driver->msr & MSR_DCD) )
+		    )
+			break;
+
+		if(signal_pending(current)){
+			retval = -ERESTARTSYS;
+			break;
+		}
+
+#ifdef IRVTD_DEBUG_OPEN
+		printk(KERN_INFO"block_til_ready blocking:"
+		       " ttys%d, count = %d\n", driver->line, driver->count);
+#endif
+		schedule();
+	}
+
+	current->state = TASK_RUNNING;
+	remove_wait_queue(&driver->open_wait, &wait);
+
+	if (!tty_hung_up_p(filp))
+		driver->count++;
+	driver->blocked_open--;
+#ifdef IRVTD_DEBUG_OPEN
+	printk("block_til_ready after blocking: ttys%d, count = %d\n",
+	       driver->line, driver->count);
+#endif
+	if (retval)
+		return retval;
+	driver->flags |= IRVTD_ASYNC_NORMAL_ACTIVE;
+	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;
+
+
+
+	/*
+	 * change baud rate here. but not implemented now
+	 */
+
+
+
+
+	/* 
+	 * byte size and parity
+	 */
+	switch (cflag & CSIZE) {
+	      case CS5: cval = 0x00; break;
+	      case CS6: cval = 0x01; break;
+	      case CS7: cval = 0x02; break;
+	      case CS8: cval = 0x03; break;
+	      default:  cval = 0x00; break;	/* too keep GCC shut... */
+	}
+	if (cflag & CSTOPB) {      /* use 2 stop bit mode */
+		cval |= 0x04;
+	}
+	if (cflag & PARENB)
+		cval |= 0x08;
+	if (!(cflag & PARODD))
+		cval |= 0x10;
+	
+	/* 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 &= ~IRVTD_ASYNC_CHECK_CD;
+	else
+		driver->flags |= IRVTD_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;
+	
+	driver->ignore_status_mask = 0;
+
+	if (I_IGNBRK(driver->tty)) {
+		driver->ignore_status_mask |= LSR_BI;
+		driver->read_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 | \
+				LSR_PE | LSR_FE;
+			driver->read_status_mask |= LSR_OE | \
+				LSR_PE | LSR_FE;
+		}
+	}
+	driver->comm->data_format = cval;
+	ircomm_append_ctrl(driver->comm, DATA_FORMAT);
+ 	ircomm_append_ctrl(driver->comm, FLOW_CONTROL);
+	ircomm_control_request(driver->comm);
+
+	/* output to IrCOMM here*/
+}
+
+
+
+
+static int irvtd_startup(struct irvtd_cb *driver){
+
+	struct notify_t irvtd_notify;
+
+
+	DEBUG(4,"irvtd_startup:\n" );
+
+	/*
+	 * initialize our tx/rx buffer
+	 */
+
+	if(driver->flags & IRVTD_ASYNC_INITIALIZED)
+		return(0);
+
+	skb_queue_head_init(&driver->rxbuff);
+	driver->rx_tqueue.data = driver;
+	driver->rx_tqueue.routine = irvtd_write_to_tty;
+
+	if(!driver->txbuff){
+		driver->txbuff = dev_alloc_skb(COMM_DEFAULT_DATA_SIZE); 
+		if (!driver->txbuff){
+			DEBUG(0,"irvtd_open():alloc_skb failed!\n");
+			return -ENOMEM;
+		}
+
+		skb_reserve(driver->txbuff, COMM_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;
+	irvtd_notify.instance = driver;
+	strncpy( irvtd_notify.name, "irvtd", NOTIFY_MAX_NAME);
+
+	/*
+	 * register ourself as a service user of IrCOMM
+	 *	   TODO: other servicetype(i.e. 3wire,3wireraw) 
+	 */
+
+	driver->comm = ircomm_attach_cable(NINE_WIRE, irvtd_notify,
+					   irvtd_attached);
+	if(driver->comm == NULL)
+		return -ENODEV;
+	
+	/*
+	 * TODO:we have to initialize control-channel here!
+	 *   i.e.set something into RTS,CTS and so on....
+	 */
+
+	if (driver->tty)
+		clear_bit(TTY_IO_ERROR, &driver->tty->flags);
+
+	change_speed(driver);
+
+	driver->flags |= IRVTD_ASYNC_INITIALIZED;
+	return 0;
+}
+
+
+int irvtd_open(struct tty_struct * tty, struct file * filp){
+	
+	struct irvtd_cb *driver;
+	int retval;
+	int line;
+
+	DEBUG(4, "irvtd_open():\n");
+
+	line = MINOR(tty->device) - tty->driver.minor_start;
+	if ((line <0) || (line >= COMM_MAX_TTY))
+		return -ENODEV;
+	driver = irvtd[line];
+	driver->line = line;
+	driver->count++;
+
+	DEBUG(0, "irvtd_open : %s%d count %d\n", tty->driver.name, line, 
+	      driver->count);
+	
+	tty->driver_data = driver;
+	driver->tty = tty;
+
+	
+	/* 
+	 * start up discovering process and ircomm_layer 
+	 */
+	
+	retval = irvtd_startup(driver);
+	if (retval)
+		return retval;
+	MOD_INC_USE_COUNT;
+
+	retval = irvtd_block_til_ready(tty, filp, driver);
+	if (retval){
+		DEBUG(0,"irvtd_open returning after block_til_ready with %d\n",
+		      retval);
+                return retval;
+	}
+
+	if ((driver->count == 1) && driver->flags & IRVTD_ASYNC_SPLIT_TERMIOS){
+		if(tty->driver.subtype == IRVTD_TYPE_NORMAL)
+			*tty->termios = driver->normal_termios;
+		else
+			*tty->termios = driver->callout_termios;
+
+ 		change_speed(driver);
+	}
+	
+	driver->session = current->session;
+	driver->pgrp = current->pgrp;
+	driver->rx_disable = 0;
+	return (0);
+}
+
+
+
+
+
+/*
+ * ----------------------------------------------------------------------
+ * irvtd_close() and friends
+ *
+ * most of this function is stolen from serial.c
+ * ----------------------------------------------------------------------
+ */
+
+
+static void irvtd_shutdown(struct irvtd_cb * driver)
+{
+	unsigned long	flags;
+
+	if (!(driver->flags & IRVTD_ASYNC_INITIALIZED))
+		return;
+
+	DEBUG(4,"irvtd_shutdown:\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_append_ctrl(driver->comm, DTELINE_STATE );
+	ircomm_control_request(driver->comm);
+
+
+	save_flags(flags); cli(); /* Disable interrupts */
+
+	if (driver->tty)
+		set_bit(TTY_IO_ERROR, &driver->tty->flags);
+	
+	ircomm_detach_cable(driver->comm);
+
+	/*
+	 * Free the transmit buffer here 
+	 */
+	if(driver->txbuff){
+		dev_kfree_skb(driver->txbuff);     /* is it OK?*/
+		driver->txbuff = NULL;
+	}
+
+	driver->flags &= ~IRVTD_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(0, "irvtd_close:refc(%d)\n",ircomm_vsd_refcount);
+
+	ASSERT(driver != NULL, return;);
+	ASSERT(driver->magic == IRVTD_MAGIC, return;);
+
+	save_flags(flags);cli();
+
+	/* 
+	 * tty_hung_up_p() is defined as 
+	 *   " return(filp->f_op == &hung_up_tty_fops); "
+	 *	 see driver/char/tty_io.c
+	 */
+
+	if(tty_hung_up_p(filp)){
+		MOD_DEC_USE_COUNT;
+		restore_flags(flags);
+		return;
+	}
+	
+
+	line = MINOR(tty->device) - tty->driver.minor_start;
+	DEBUG(0, "irvtd_close : %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("irvtd_close: bad count for line%d: %d\n",
+		       line, driver->count);
+		driver->count = 0;
+	}
+
+	if (driver->count) { 	/* do nothing */
+		MOD_DEC_USE_COUNT;
+		restore_flags(flags);
+		return; 
+	}
+
+	driver->flags |= IRVTD_ASYNC_CLOSING;
+	
+	/*
+	 * Save the termios structure, since this port may have
+	 * separate termios for callout and dialin.
+	 */
+
+	if (driver->flags & IRVTD_ASYNC_NORMAL_ACTIVE)
+		driver->normal_termios = *tty->termios;
+	if (driver->flags & IRVTD_ASYNC_CALLOUT_ACTIVE)
+		driver->callout_termios = *tty->termios;
+	
+	/*
+	 * 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 != IRVTD_ASYNC_CLOSING_WAIT_NONE)
+		tty_wait_until_sent(tty, driver->closing_wait);
+	
+	/* 
+	 * Now we stop accepting input.
+	 */
+
+	driver->rx_disable = TRUE;
+
+	/* 
+	 * Now we flush our buffer.., and shutdown ircomm service layer
+	 */
+
+	/* drop our tx/rx buffer */
+ 	if (tty->driver.flush_buffer) 
+ 		tty->driver.flush_buffer(tty);  
+
+	while(skb_queue_len(&driver->rxbuff)){
+		struct sk_buff *skb;
+		skb = skb_dequeue( &driver->rxbuff);
+		dev_kfree_skb(skb);
+	}
+
+	/* drop users buffer? */
+	if (tty->ldisc.flush_buffer)
+		tty->ldisc.flush_buffer(tty);
+
+
+
+	tty->closing = 0;
+	driver->tty = NULL;
+
+	/*
+	 * ad-hoc coding:
+	 * we wait 2 sec before ircomm_detach_cable so that 
+	 * irttp will send all contents of its queue
+	 */
+
+#if 0
+	if (driver->blocked_open) {
+		if (driver->close_delay) {
+#endif
+
+			/* kill time */
+			current->state = TASK_INTERRUPTIBLE;
+			schedule_timeout(driver->close_delay + 2*HZ);
+#if 0
+		}
+		wake_up_interruptible(&driver->open_wait);
+	}
+#endif
+	
+	driver->flags &= ~(IRVTD_ASYNC_NORMAL_ACTIVE|
+			   IRVTD_ASYNC_CALLOUT_ACTIVE|
+			   IRVTD_ASYNC_CLOSING);
+        wake_up_interruptible(&driver->close_wait); 
+
+	irvtd_shutdown(driver);
+	MOD_DEC_USE_COUNT;
+        restore_flags(flags);
+	DEBUG(4,"irvtd_close:done:refc(%d)\n",ircomm_vsd_refcount);
+}
+
+
+
+/*
+ * ----------------------------------------------------------------------
+ * irvtd_write() and friends
+ * This routine will be called when something data are passed from
+ * kernel or user.
+ *
+ * NOTE:I have stolen copy_from_user() from 2.0.30 kernel(linux/isdnif.h)
+ * to access user space of memory carefully. Thanks a lot!:)
+ * ----------------------------------------------------------------------
+ */
+
+int irvtd_write(struct tty_struct * tty, int from_user,
+			const unsigned char *buf, int count){
+
+	struct irvtd_cb *driver = (struct irvtd_cb *)tty->driver_data;
+	int c = 0;
+	int wrote = 0;
+	struct sk_buff *skb = NULL;
+	__u8 *frame;
+
+	DEBUG(4, "irvtd_write():\n");
+
+	if (!tty || !driver->txbuff)
+                return 0;
+
+
+	
+	while(1){
+		skb = driver->txbuff;
+		
+		c = MIN(count, (skb_tailroom(skb) - COMM_HEADER_SIZE));
+		if (c <= 0) 
+			break;
+
+		/* write to the frame */
+
+
+		frame = skb_put(skb,c);
+		if(from_user){
+			copy_from_user(frame,buf,c);
+		} else
+			memcpy(frame, buf, c);
+
+		/* flush the frame */
+		irvtd_flush_chars(tty);
+		wrote += c;
+		count -= c;
+	}
+	return (wrote);
+}
+
+/*
+ * ----------------------------------------------------------------------
+ * irvtd_put_char()
+ * 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 = driver->txbuff;
+
+	ASSERT(tty->driver_data != NULL, return;);
+
+	DEBUG(4, "irvtd_put_char:\n");
+	if(!driver->txbuff)
+		return;
+
+	DEBUG(4, "irvtd_put_char(0x%02x) skb_len(%d) MAX(%d):\n",
+	      (int)ch ,(int)skb->len,
+	      driver->comm->maxsdusize - COMM_HEADER_SIZE);
+
+	/* append a character  */
+
+	frame = skb_put(skb,1);
+	frame[0] = ch;
+	return;
+}
+
+/*
+ * ----------------------------------------------------------------------
+ * irvtd_flush_chars() and friend
+ * This routine will be called after a series of characters was written using 
+ * irvtd_put_char().We have to send them down to IrCOMM.
+ * ----------------------------------------------------------------------
+ */
+
+static void flush_txbuff(struct irvtd_cb *driver){
+	
+	struct sk_buff *skb = driver->txbuff;
+	struct tty_struct *tty = driver->tty;
+	ASSERT(tty != NULL, return;);
+
+#ifdef IRVTD_DEBUG_TX
+	printk("flush_txbuff:");
+	{
+		int i;
+		for ( i=0;i<skb->len;i++)
+			printk("%02x", skb->data[i]);
+		printk("\n");
+	}
+#else
+	DEBUG(4, "flush_txbuff:count(%d)\n",(int)skb->len);
+#endif
+
+	/* add "clen" field */
+	skb_push(skb,1);
+	skb->data[0]=0;          /* without control channel */
+
+	ircomm_data_request(driver->comm, driver->txbuff);
+
+	/* allocate new frame */
+	skb = driver->txbuff = dev_alloc_skb(driver->comm->max_txbuff_size);
+	if (skb == NULL){
+		printk(KERN_ERR"flush_txbuff():alloc_skb failed!\n");
+	} else {
+		skb_reserve(skb, COMM_HEADER_SIZE);
+	}
+	wake_up_interruptible(&driver->tty->write_wait);
+}
+
+void irvtd_flush_chars(struct tty_struct *tty){
+
+	struct irvtd_cb *driver = (struct irvtd_cb *)tty->driver_data;
+	if(!driver || driver->magic != IRVTD_MAGIC || !driver->txbuff){
+		DEBUG(0,"irvtd_flush_chars:null structure:ignore\n");
+		return;
+	}
+	DEBUG(4, "irvtd_flush_chars():\n");
+
+	while(tty->hw_stopped){
+		DEBUG(4,"irvtd_flush_chars:hw_stopped:sleep..\n");
+		tty_wait_until_sent(tty,0);
+		DEBUG(4,"irvtd_flush_chars:waken up!\n");
+		if(!driver->txbuff->len)
+			return;
+	}
+
+	flush_txbuff(driver);
+}
+
+
+
+
+/*
+ * ----------------------------------------------------------------------
+ * irvtd_write_room()
+ * This routine returns the room that our buffer has now.
+ *
+ * NOTE: 
+ * driver/char/n_tty.c drops a character(s) when this routine returns 0,
+ * and then linux will be frozen after a few minutes :(    why? bug?
+ * ( I found this on linux-2.0.33 )
+ * So this routine flushes a buffer if there is few room,     TH
+ * ----------------------------------------------------------------------
+ */
+
+int irvtd_write_room(struct tty_struct *tty){
+
+	int ret;
+	struct sk_buff *skb = (struct sk_buff *)((struct irvtd_cb *) tty->driver_data)->txbuff;
+
+	if(!skb){
+		DEBUG(0,"irvtd_write_room:NULL skb\n");
+		return(0);
+	}
+
+	ret = skb_tailroom(skb) - COMM_HEADER_SIZE;
+
+	if(ret < 0){
+		DEBUG(0,"irvtd_write_room:error:room is %d!",ret);
+		ret = 0;
+	}
+	DEBUG(4, "irvtd_write_room:\n");
+	DEBUG(4, "retval(%d)\n",ret);
+
+
+	/* flush buffer automatically to avoid kernel freeze :< */
+	if(ret < 8)    /* why 8? there's no reason :) */
+		irvtd_flush_chars(tty);
+
+	return(ret);
+}
+
+/*
+ * ----------------------------------------------------------------------
+ * irvtd_chars_in_buffer()
+ * 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 = 
+		(struct sk_buff *) ((struct irvtd_cb *)tty->driver_data) ->txbuff;
+	DEBUG(4, "irvtd_chars_in_buffer()\n");
+
+	if(!skb){
+		printk(KERN_ERR"irvtd_chars_in_buffer:NULL skb\n");
+		return(0);
+	}
+	return (skb->len );
+}
+
+/*
+ * ----------------------------------------------------------------------
+ * irvtd_break()
+ * 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_append_ctrl(driver->comm, BREAK_SIGNAL);
+		ircomm_control_request(driver->comm);
+	}
+	else
+	{
+		driver->comm->break_signal = 0x00;
+		ircomm_append_ctrl(driver->comm, BREAK_SIGNAL);
+		ircomm_control_request(driver->comm);
+	}
+
+	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);
+	put_user(result,value);
+	return 0;
+}
+
+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_append_ctrl(driver->comm, DTELINE_STATE );
+	ircomm_control_request(driver->comm);
+	return 0;
+}
+
+int irvtd_ioctl(struct tty_struct *tty, struct file * file,
+		unsigned int cmd, unsigned long arg){
+
+	int error;
+ 	struct irvtd_cb *driver = (struct irvtd_cb *)tty->driver_data;
+
+	struct icounter_struct cnow;
+	struct 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  */
+		unsigned long flags;
+		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)){
+			DEBUG(0,"irvtd_ioctl:I/O error...\n");
+			return -EIO;
+		}
+	}
+	
+	switch (cmd) {
+
+	case TIOCMGET:
+		error = verify_area(VERIFY_WRITE, (void *) arg,
+				    sizeof(unsigned int));
+		if (error)
+			return error;
+		return get_modem_info(driver, (unsigned int *) arg);
+
+	case TIOCMBIS:
+	case TIOCMBIC:
+	case TIOCMSET:
+		return set_modem_info(driver, cmd, (unsigned int *) arg);
+#if 0
+	/*
+	 * we wouldn't implement them since we don't use serial_struct
+	 */
+	case TIOCGSERIAL:
+		error = verify_area(VERIFY_WRITE, (void *) arg,
+				    sizeof(struct serial_struct));
+		if (error)
+			return error;
+		return irvtd_get_serial_info(driver,
+				       (struct serial_struct *) arg);
+	case TIOCSSERIAL:
+		error = verify_area(VERIFY_READ, (void *) arg,
+				    sizeof(struct serial_struct));
+		if (error)
+			return error;
+		return irvtd_set_serial_info(driver,
+				       (struct serial_struct *) arg);
+
+
+	case TIOCSERGETLSR: /* Get line status register */
+		error = verify_area(VERIFY_WRITE, (void *) arg,
+				    sizeof(unsigned int));
+		if (error)
+			return error;
+		else
+			return get_lsr_info(driver, (unsigned int *) arg);
+#endif		
+
+/*
+ *  I think we don't need them
+ */
+/* 	case TIOCSERCONFIG: */
+		
+
+/*
+ * They cannot be implemented because we don't use async_struct 
+ * which is defined in serial.h
+ */
+
+/* 	case TIOCSERGSTRUCT: */
+/* 		error = verify_area(VERIFY_WRITE, (void *) arg, */
+/* 				    sizeof(struct async_struct)); */
+/* 		if (error) */
+/* 			return error; */
+/* 		memcpy_tofs((struct async_struct *) arg, */
+/* 			    driver, sizeof(struct async_struct)); */
+/* 		return 0; */
+		
+/* 	case TIOCSERGETMULTI: */
+/* 		error = verify_area(VERIFY_WRITE, (void *) arg, */
+/* 				    sizeof(struct serial_multiport_struct)); */
+/* 		if (error) */
+/* 			return error; */
+/* 		return get_multiport_struct(driver, */
+/* 					    (struct serial_multiport_struct *) arg); */
+/* 	case TIOCSERSETMULTI: */
+/* 		error = verify_area(VERIFY_READ, (void *) arg, */
+/* 				    sizeof(struct serial_multiport_struct)); */
+/* 		if (error) */
+/* 			return error; */
+/* 		return set_multiport_struct(driver, */
+/* 					    (struct serial_multiport_struct *) arg); */
+		/*
+		 * Wait for any of the 4 modem inputs (DCD,RI,DSR,CTS)
+		 * to change
+		 * - mask passed in arg for lines of interest
+ 		 *   (use |'ed TIOCM_RNG/DSR/CD/CTS for masking)
+		 * Caller should use TIOCGICOUNT to see which one it was
+		 */
+
+	case TIOCMIWAIT:
+		while (1) {
+			interruptible_sleep_on(&driver->delta_msr_wait);
+			/* see if a signal did it */
+/* 			if (current->signal & ~current->blocked) */
+/* 				return -ERESTARTSYS; */
+
+			if ( ((arg & TIOCM_RNG) && (driver->msr & DELTA_RI))  ||
+			     ((arg & TIOCM_DSR) && (driver->msr & DELTA_DSR)) ||
+			     ((arg & TIOCM_CD)  && (driver->msr & DELTA_DCD)) ||
+			     ((arg & TIOCM_CTS) && (driver->msr & DELTA_CTS))) {
+				return 0;
+			}
+		}
+		/* NOTREACHED */
+
+
+	case TIOCGICOUNT:
+		/* 
+		 * Get counter of input serial line interrupts (DCD,RI,DSR,CTS)
+		 * Return: write counters to the user passed counter struct
+		 * NB: both 1->0 and 0->1 transitions are counted except for
+		 *     RI where only 0->1 is counted.
+		 */
+		error = verify_area(VERIFY_WRITE, (void *) arg,
+				    sizeof(struct icounter_struct));
+		if (error)
+			return error;
+		cli();
+		cnow = driver->icount;
+		sti();
+		p_cuser = (struct icounter_struct *) arg;
+		put_user(cnow.cts, &p_cuser->cts);
+		put_user(cnow.dsr, &p_cuser->dsr);
+		put_user(cnow.rng, &p_cuser->rng);
+		put_user(cnow.dcd, &p_cuser->dcd);
+		return 0;
+
+
+	case TIOCGSERIAL:
+	case TIOCSSERIAL:
+	case TIOCSERGETLSR:
+ 	case TIOCSERCONFIG:
+	case TIOCSERGWILD:
+	case TIOCSERSWILD:
+	case TIOCSERGSTRUCT:
+	case TIOCSERGETMULTI:
+	case TIOCSERSETMULTI:
+		DEBUG(0,"irvtd_ioctl:sorry, ioctl(0x%08x)is not implemented\n",cmd);
+		return -ENOIOCTLCMD;  /* ioctls which are imcompatible with serial.c */
+
+	case TCSETS:
+	case TCGETS:
+	case TCFLSH:
+	default:
+		return -ENOIOCTLCMD;  /* ioctls which we must not touch */
+	}
+	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(0, __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(0, "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_append_ctrl(driver->comm, DTELINE_STATE );
+	ircomm_control_request(driver->comm);
+        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(0, "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_append_ctrl(driver->comm, DTELINE_STATE );
+	ircomm_control_request(driver->comm);
+        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
+ * Is this routine right ? :{|
+ * ------------------------------------------------------------
+ */
+
+void irvtd_hangup(struct tty_struct *tty){
+
+	struct irvtd_cb *info = (struct irvtd_cb *)tty->driver_data;
+	DEBUG(0, "irvtd_hangup()\n");
+
+	irvtd_flush_buffer(tty);
+	irvtd_shutdown(info);
+	info->count = 0;
+	info->flags &= ~(IRVTD_ASYNC_NORMAL_ACTIVE|IRVTD_ASYNC_CALLOUT_ACTIVE);
+	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 = (struct sk_buff *)driver->txbuff;
+
+	DEBUG(4, "irvtd_flush_buffer:%d chars are gone..\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);
+}
+
+

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov