patch-2.3.13 linux/drivers/char/generic_serial.c

Next file: linux/drivers/char/generic_serial.h
Previous file: linux/drivers/char/epca.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.3.12/linux/drivers/char/generic_serial.c linux/drivers/char/generic_serial.c
@@ -0,0 +1,1074 @@
+/*
+ *  generic_serial.c
+ *
+ *  Copyright (C) 1998/1999 R.E.Wolff@BitWizard.nl
+ *
+ *  written for the SX serial driver.
+ *     Contains the code that should be shared over all the serial drivers.
+ *
+ *  Credit for the idea to do it this way might go to Alan Cox. 
+ *
+ *
+ *  Version 0.1 -- December, 1998. Initial version.
+ *  Version 0.2 -- March, 1999.    Some more routines. Bugfixes. Etc.
+ *  Version 0.5 -- August, 1999.   Some more fixes. Reformat for Linus.
+ */
+
+#include <linux/tty.h>
+#include <linux/serial.h>
+#include <linux/mm.h>
+#include <asm/semaphore.h>
+#include <linux/version.h>
+
+
+#if LINUX_VERSION_CODE < 0x020100    /* Less than 2.1.0 */
+#define TWO_ZERO
+#else
+#if LINUX_VERSION_CODE < 0x020200   /* less than 2.2.x */
+#warning "Please use a 2.2.x kernel. "
+#else
+#if LINUX_VERSION_CODE < 0x020300   /* less than 2.2.x */
+#define TWO_TWO
+#else
+#define TWO_THREE
+#endif
+#endif
+#endif
+
+#ifdef TWO_ZERO
+
+/* Here is the section that makes the 2.2 compatible driver source 
+   work for 2.0 too! We mostly try to adopt the "new thingies" from 2.2, 
+   and provide for compatibility stuff here if possible. */
+
+/* Some 200 days (on intel) */
+#define MAX_SCHEDULE_TIMEOUT     ((long)(~0UL>>1))
+
+
+#ifndef MODULE
+
+#define copy_to_user(a,b,c)          memcpy_tofs(a,b,c)
+
+static inline int copy_from_user(void *to,const void *from, int c) 
+{
+	memcpy_fromfs(to, from, c);
+	return 0;
+}
+
+
+#define capable(x)                   suser()
+
+#define queue_task                   queue_task_irq_off
+#define tty_flip_buffer_push(tty)    queue_task(&tty->flip.tqueue, &tq_timer)
+#define signal_pending(current)      (current->signal & ~current->blocked)
+#define schedule_timeout(to)         do {current->timeout = jiffies + (to);schedule ();} while (0)
+#define time_after(t1,t2)            (((long)t1-t2) > 0)
+
+#define test_and_set_bit(nr, addr)   set_bit(nr, addr)
+#define test_and_clear_bit(nr, addr) clear_bit(nr, addr)
+
+/* Not yet implemented on 2.0 */
+#define ASYNC_SPD_SHI  -1
+#define ASYNC_SPD_WARP -1
+
+
+
+/* Ugly hack: the driver_name doesn't exist in 2.0.x . So we define it
+   to the "name" field that does exist. As long as the assignments are
+   done in the right order, there is nothing to worry about. */
+#define driver_name           name 
+
+
+/* Should be in a header somewhere. */
+#define TTY_HW_COOK_OUT       14 /* Flag to tell ntty what we can handle */
+#define TTY_HW_COOK_IN        15 /* in hardware - output and input       */
+#endif
+
+#endif
+
+#ifndef TWO_ZERO
+/* This include is new with 2.2  (and required!) */
+#include <asm/uaccess.h>
+#endif
+
+#ifndef TWO_THREE
+/* These are new in 2.3. The source now uses 2.3 syntax, and here is 
+   the compatibility define... */
+#define waitq_head_t struct wait_queue *
+#define DECLARE_MUTEX(name) struct semaphore name = MUTEX
+#define DECLARE_WAITQUEUE(wait, current) struct wait_queue wait = { current, NULL }
+
+#endif
+
+#include "generic_serial.h"
+
+
+#ifndef MODULE
+extern void my_hd (unsigned char *ptr, int n);
+#endif
+
+static char *                  tmp_buf; 
+static DECLARE_MUTEX(tmp_buf_sem);
+
+int gs_debug = 0;
+
+
+#ifdef DEBUG
+#define gs_dprintk(f, str...) if (gs_debug & f) printk (str)
+#else
+#define gs_dprintk(f, str...) /* nothing */
+#endif
+
+#define func_enter() gs_dprintk (SX_DEBUG_FLOW, "gs: enter " __FUNCTION__ "\n")
+#define func_exit()  gs_dprintk (SX_DEBUG_FLOW, "gs: exit  " __FUNCTION__ "\n")
+
+
+
+#if NEW_WRITE_LOCKING
+#define DECL      /* Nothing */
+#define LOCKIT    down (& port->port_write_sem);
+#define RELEASEIT up (&port->port_write_sem);
+#else
+#define DECL      unsigned long flags;
+#define LOCKIT    save_flags (flags);cli ()
+#define RELEASEIT restore_flags (flags)
+#endif
+
+
+
+void gs_put_char(struct tty_struct * tty, unsigned char ch)
+{
+	struct gs_port *port = tty->driver_data;
+	DECL
+
+	/*  func_enter (); */
+
+	/* Take a lock on the serial tranmit buffer! */
+	LOCKIT;
+
+	if (port->xmit_cnt >= SERIAL_XMIT_SIZE - 1) {
+		/* Sorry, buffer is full, drop character. Update statistics???? -- REW */
+		RELEASEIT;
+		return;
+	}
+
+	port->xmit_buf[port->xmit_head++] = ch;
+	port->xmit_head &= SERIAL_XMIT_SIZE - 1;
+	port->xmit_cnt++;  /* Characters in buffer */
+
+	RELEASEIT;
+	/* func_exit ();*/
+}
+
+
+#ifdef NEW_WRITE_LOCKING
+
+/*
+> Problems to take into account are:
+>       -1- Interrupts that empty part of the buffer.
+>       -2- page faults on the access to userspace. 
+>       -3- Other processes that are also trying to do a "write". 
+*/
+
+int gs_write(struct tty_struct * tty, int from_user, 
+                    const unsigned char *buf, int count)
+{
+	struct gs_port *port = tty->driver_data;
+	int c, total = 0;
+	int t;
+
+	/* func_enter (); */
+
+	if (! (port->flags & ASYNC_INITIALIZED))
+		return 0;
+
+	/* get exclusive "write" access to this port (problem 3) */
+	/* This is not a spinlock because we can have a disk access (page 
+		 fault) in copy_from_user */
+	down (& port->port_write_sem);
+
+	while (1) {
+
+		c = count;
+ 
+		/* This is safe because we "OWN" the "head". Noone else can 
+		   change the "head": we own the port_write_sem. */
+		/* Don't overrun the end of the buffer */
+		t = SERIAL_XMIT_SIZE - port->xmit_head;
+		if (t < c) c = t;
+ 
+		/* This is safe because the xmit_cnt can only decrease. This 
+		   would increase "t", so we might copy too little chars. */
+		/* Don't copy past the "head" of the buffer */
+		t = SERIAL_XMIT_SIZE - 1 - port->xmit_cnt;
+		if (t < c) c = t;
+ 
+		/* Can't copy more? break out! */
+		if (c <= 0) break;
+		if (from_user)
+			copy_from_user (port->xmit_buf + port->xmit_head, buf, c);
+		else
+			memcpy         (port->xmit_buf + port->xmit_head, buf, c);
+
+		port -> xmit_cnt += c;
+		port -> xmit_head = (port->xmit_head + c) & (SERIAL_XMIT_SIZE -1);
+		buf += c;
+		count -= c;
+		total += c;
+	}
+	up (& port->port_write_sem);
+
+	gs_dprintk (GS_DEBUG_WRITE, "write: interrupts are %s\n", 
+	            (port->flags & GS_TX_INTEN)?"enabled": "disabled"); 
+
+	if (port->xmit_cnt && 
+	    !tty->stopped && 
+	    !tty->hw_stopped &&
+	    !(port->flags & GS_TX_INTEN)) {
+		port->flags |= GS_TX_INTEN;
+		port->rd->enable_tx_interrupts (port);
+	}
+	/* func_exit (); */
+	return total;
+}
+#else
+/*
+> Problems to take into account are:
+>       -1- Interrupts that empty part of the buffer.
+>       -2- page faults on the access to userspace. 
+>       -3- Other processes that are also trying to do a "write". 
+*/
+
+int gs_write(struct tty_struct * tty, int from_user, 
+                    const unsigned char *buf, int count)
+{
+	struct gs_port *port;
+	int c, total = 0;
+	int t;
+	unsigned long flags;
+
+	func_enter ();
+
+	/* The standard serial driver returns 0 in this case. 
+	   That sounds to me as "No error, I just didn't get to writing any
+	   bytes. Feel free to try again." 
+	   The "official" way to write n bytes from buf is:
+
+		 for (nwritten = 0;nwritten < n;nwritten += rv) {
+			 rv = write (fd, buf+nwritten, n-nwritten);
+			 if (rv < 0) break; // Error: bail out. //
+		 } 
+
+	   which will loop endlessly in this case. The manual page for write
+	   agrees with me. In practise almost everybody writes 
+	   "write (fd, buf,n);" but some people might have had to deal with 
+	   incomplete writes in the past and correctly implemented it by now... 
+	 */
+
+	if (!tty) return -EIO;
+
+	port = tty->driver_data;
+	if (!port || !port->xmit_buf || !tmp_buf)
+		return -EIO;
+
+	/* printk ("from_user = %d.\n", from_user); */
+	save_flags(flags);
+	if (from_user) {
+		/* printk ("Going into the semaphore\n"); */
+		down(&tmp_buf_sem);
+		/* printk ("got out of the semaphore\n"); */
+		while (1) {
+			c = count;
+
+			/* This is safe because we "OWN" the "head". Noone else can 
+			   change the "head": we own the port_write_sem. */
+			/* Don't overrun the end of the buffer */
+			t = SERIAL_XMIT_SIZE - port->xmit_head;
+			if (t < c) c = t;
+ 
+			/* This is safe because the xmit_cnt can only decrease. This 
+			   would increase "t", so we might copy too little chars. */
+			/* Don't copy past the "head" of the buffer */
+			t = SERIAL_XMIT_SIZE - 1 - port->xmit_cnt;
+			if (t < c) c = t;	 
+
+			/* Can't copy more? break out! */
+			if (c <= 0) break;
+
+			c -= copy_from_user(tmp_buf, buf, c);
+			if (!c) {
+				if (!total)
+					total = -EFAULT;
+				break;
+			}
+			cli();
+			t = SERIAL_XMIT_SIZE - port->xmit_head;
+			if (t < c) c = t;
+			t = SERIAL_XMIT_SIZE - 1 - port->xmit_cnt;
+			if (t < c) c = t;
+
+			memcpy(port->xmit_buf + port->xmit_head, tmp_buf, c);
+			port->xmit_head = ((port->xmit_head + c) &
+			                   (SERIAL_XMIT_SIZE-1));
+			port->xmit_cnt += c;
+			restore_flags(flags);
+			buf += c;
+			count -= c;
+			total += c;
+		}
+		up(&tmp_buf_sem);
+	} else {
+		while (1) {
+			cli();
+			c = count;
+
+			/* This is safe because we "OWN" the "head". Noone else can 
+			   change the "head": we own the port_write_sem. */
+			/* Don't overrun the end of the buffer */
+			t = SERIAL_XMIT_SIZE - port->xmit_head;
+			if (t < c) c = t;
+ 
+			/* This is safe because the xmit_cnt can only decrease. This 
+			   would increase "t", so we might copy too little chars. */
+			/* Don't copy past the "head" of the buffer */
+			t = SERIAL_XMIT_SIZE - 1 - port->xmit_cnt;
+			if (t < c) c = t;
+ 
+			/* Can't copy more? break out! */
+			if (c <= 0) {
+				restore_flags(flags);
+				break;
+			}
+			memcpy(port->xmit_buf + port->xmit_head, buf, c);
+			port->xmit_head = ((port->xmit_head + c) &
+			                   (SERIAL_XMIT_SIZE-1));
+			port->xmit_cnt += c;
+			restore_flags(flags);
+			buf += c;
+			count -= c;
+			total += c;
+		}
+	}
+
+	if (port->xmit_cnt && 
+	    !tty->stopped && 
+	    !tty->hw_stopped &&
+	    !(port->flags & GS_TX_INTEN)) {
+		port->flags |= GS_TX_INTEN;
+		port->rd->enable_tx_interrupts (port);
+	}
+	func_exit ();
+	return total;
+}
+
+#endif
+
+
+
+int gs_write_room(struct tty_struct * tty)
+{
+	struct gs_port *port = tty->driver_data;
+	int ret;
+
+	/* func_enter (); */
+	ret = SERIAL_XMIT_SIZE - port->xmit_cnt - 1;
+	if (ret < 0)
+		ret = 0;
+	/* func_exit (); */
+	return ret;
+}
+
+
+int gs_chars_in_buffer(struct tty_struct *tty)
+{
+	struct gs_port *port = tty->driver_data;
+	func_enter ();
+
+	func_exit ();
+	return port->xmit_cnt;
+}
+
+
+int gs_real_chars_in_buffer(struct tty_struct *tty)
+{
+	struct gs_port *port = tty->driver_data;
+	func_enter ();
+
+	if (!tty) return 0;
+	port = tty->driver_data;
+
+	func_exit ();
+	return port->xmit_cnt + port->rd->chars_in_buffer (port);
+}
+
+
+static void gs_wait_tx_flushed (void * ptr, int timeout) 
+{
+	struct gs_port *port = ptr;
+	long end_jiffies;
+	int jiffies_to_transmit, charsleft;
+	int to, rcib;
+
+	func_enter();
+
+	gs_dprintk (GS_DEBUG_FLUSH, "port=%p.\n", port);
+	if (port) {
+		gs_dprintk (GS_DEBUG_FLUSH, "xmit_cnt=%x, xmit_buf=%p, tty=%p.\n", 
+		port->xmit_cnt, port->xmit_buf, port->tty);
+	}
+
+	if (!port || port->xmit_cnt < 0 || !port->xmit_buf) {
+		gs_dprintk (GS_DEBUG_FLUSH, "ERROR: !port, !port->xmit_buf or prot->xmit_cnt < 0.\n");
+	func_exit();
+		return;  /* This is an error which we don't know how to handle. */
+	}
+	gs_dprintk (GS_DEBUG_FLUSH, "checkpoint 1\n");
+
+	rcib = gs_real_chars_in_buffer(port->tty);
+
+	gs_dprintk (GS_DEBUG_FLUSH, "checkpoint 2\n");
+
+	if(rcib <= 0) {
+		gs_dprintk (GS_DEBUG_FLUSH, "nothing to wait for.\n");
+	func_exit();
+	return;
+	}
+	gs_dprintk (GS_DEBUG_FLUSH, "checkpoint 3\n");
+
+	/* stop trying: now + twice the time it would normally take +  seconds */
+	end_jiffies  = jiffies; 
+	if (timeout !=  MAX_SCHEDULE_TIMEOUT)
+		end_jiffies += port->baud?(2 * rcib * 10 * HZ / port->baud):0;
+	end_jiffies += timeout;
+
+	gs_dprintk (GS_DEBUG_FLUSH, "now=%lx, end=%lx (%ld).\n", 
+		    jiffies, end_jiffies, end_jiffies-jiffies); 
+
+	to = 100;
+	/* the expression is actually jiffies < end_jiffies, but that won't
+	   work around the wraparound. Tricky eh? */
+	while (to-- &&
+	       (charsleft = gs_real_chars_in_buffer (port->tty)) &&
+	        time_after (end_jiffies, jiffies)) {
+		/* Units check: 
+		   chars * (bits/char) * (jiffies /sec) / (bits/sec) = jiffies!
+		   check! */
+
+		charsleft += 16; /* Allow 16 chars more to be transmitted ... */
+		jiffies_to_transmit = port->baud?(1 + charsleft * 10 * HZ / port->baud):0;
+		/*                                ^^^ Round up.... */
+		if (jiffies_to_transmit <= 0) jiffies_to_transmit = 1;
+
+		gs_dprintk (GS_DEBUG_FLUSH, "Expect to finish in %d jiffies "
+			    "(%d chars).\n", jiffies_to_transmit, charsleft); 
+
+		current->state = TASK_INTERRUPTIBLE;
+		schedule_timeout(jiffies_to_transmit);
+		if (signal_pending (current))
+			break;
+	}
+
+	gs_dprintk (GS_DEBUG_FLUSH, "charsleft = %d.\n", charsleft); 
+	current->state = TASK_RUNNING;
+
+	func_exit();
+}
+
+
+
+void gs_flush_buffer(struct tty_struct *tty)
+{
+	struct gs_port *port = tty->driver_data;
+	unsigned long flags;
+
+	func_enter ();
+	/* XXX Would the write semaphore do? */
+	save_flags(flags); cli();
+	port->xmit_cnt = port->xmit_head = port->xmit_tail = 0;
+	restore_flags(flags);
+
+	wake_up_interruptible(&tty->write_wait);
+	if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
+	    tty->ldisc.write_wakeup)
+		(tty->ldisc.write_wakeup)(tty);
+	func_exit ();
+}
+
+
+void gs_flush_chars(struct tty_struct * tty)
+{
+	struct gs_port *port = tty->driver_data;
+
+	func_enter ();
+	if (port->xmit_cnt <= 0 || tty->stopped || tty->hw_stopped ||
+	    !port->xmit_buf) {
+		func_exit ();
+		return;
+	}
+
+	/* Beats me -- REW */
+	port->flags |= GS_TX_INTEN;
+	port->rd->enable_tx_interrupts (port);
+	func_exit ();
+}
+
+
+void gs_stop(struct tty_struct * tty)
+{
+	struct gs_port *port = tty->driver_data;
+
+	func_enter ();
+	if (port->xmit_cnt && 
+	    port->xmit_buf && 
+	    (port->flags & GS_TX_INTEN) ) {
+		port->flags &= ~GS_TX_INTEN;
+		port->rd->disable_tx_interrupts (port);
+	}
+	func_exit ();
+}
+
+
+void gs_start(struct tty_struct * tty)
+{
+	struct gs_port *port = tty->driver_data;
+
+	if (port->xmit_cnt && 
+	    port->xmit_buf && 
+	    !(port->flags & GS_TX_INTEN) ) {
+		port->flags |= GS_TX_INTEN;
+		port->rd->enable_tx_interrupts (port);
+	}
+	func_exit ();
+}
+
+
+void gs_shutdown_port (struct gs_port *port)
+{
+	long flags;
+
+	if (!(port->flags & ASYNC_INITIALIZED))
+		return;
+
+	save_flags (flags);
+	cli ();
+
+	if (port->xmit_buf) {
+		free_page((unsigned long) port->xmit_buf);
+		port->xmit_buf = 0;
+	}
+
+	if (port->tty)
+		set_bit(TTY_IO_ERROR, &port->tty->flags);
+
+	port->rd->shutdown_port (port);
+
+	port->flags &= ~ASYNC_INITIALIZED;
+	restore_flags (flags);
+}
+
+
+void gs_hangup(struct tty_struct *tty)
+{
+	struct gs_port   *port = tty->driver_data;
+
+	func_enter ();
+
+	tty = port->tty;
+	if (!tty) return;
+
+	gs_shutdown_port (port);
+
+	/* gs_flush_buffer (tty); */
+	port->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CALLOUT_ACTIVE |GS_ACTIVE);
+	port->tty = NULL;
+	port->count = 0;
+
+	wake_up_interruptible(&port->open_wait);
+	func_exit ();
+}
+
+
+void gs_do_softint(void *private_)
+{
+	struct gs_port *port = private_;
+	struct tty_struct *tty;
+
+	tty = port->tty;
+	if(!tty) return;
+
+	if (test_and_clear_bit(RS_EVENT_WRITE_WAKEUP, &port->event)) {
+		if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
+		    tty->ldisc.write_wakeup)
+			(tty->ldisc.write_wakeup)(tty);
+		wake_up_interruptible(&tty->write_wait);
+	}
+	func_exit ();
+}
+
+
+int block_til_ready(void *port_, struct file * filp)
+{
+	struct gs_port *port = port_;
+	DECLARE_WAITQUEUE(wait, current);
+	int    retval;
+	int    do_clocal = 0;
+	int    CD;
+	struct tty_struct *tty;
+
+	func_enter ();
+	tty = port->tty;
+
+	gs_dprintk (GS_DEBUG_BTR, "Entering block_till_ready.\n"); 
+	/*
+	 * If the device is in the middle of being closed, then block
+	 * until it's done, and then try again.
+	 */
+	if (tty_hung_up_p(filp) || port->flags & ASYNC_CLOSING) {
+	  interruptible_sleep_on(&port->close_wait);
+		if (port->flags & ASYNC_HUP_NOTIFY)
+			return -EAGAIN;
+		else
+			return -ERESTARTSYS;
+	}
+
+	gs_dprintk (GS_DEBUG_BTR, "after hung up\n"); 
+
+	/*
+	 * If this is a callout device, then just make sure the normal
+	 * device isn't being used.
+	 */
+	if (tty->driver.subtype == GS_TYPE_CALLOUT) {
+		if (port->flags & ASYNC_NORMAL_ACTIVE)
+			return -EBUSY;
+		if ((port->flags & ASYNC_CALLOUT_ACTIVE) &&
+		    (port->flags & ASYNC_SESSION_LOCKOUT) &&
+		    (port->session != current->session))
+			return -EBUSY;
+		if ((port->flags & ASYNC_CALLOUT_ACTIVE) &&
+		    (port->flags & ASYNC_PGRP_LOCKOUT) &&
+		    (port->pgrp != current->pgrp))
+			return -EBUSY;
+		port->flags |= ASYNC_CALLOUT_ACTIVE;
+		return 0;
+	}
+
+	gs_dprintk (GS_DEBUG_BTR, "after subtype\n");
+
+	/*
+	 * 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 (port->flags & ASYNC_CALLOUT_ACTIVE)
+			return -EBUSY;
+		port->flags |= ASYNC_NORMAL_ACTIVE;
+		return 0;
+	}
+
+	gs_dprintk (GS_DEBUG_BTR, "after nonblock\n"); 
+ 
+	if (port->flags & ASYNC_CALLOUT_ACTIVE) {
+		if (port->normal_termios.c_cflag & CLOCAL) 
+			do_clocal = 1;
+	} else {
+		if (C_CLOCAL(tty))
+			do_clocal = 1;
+	}
+
+	gs_dprintk (GS_DEBUG_BTR, "after clocal check.\n"); 
+
+	/*
+	 * 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, port->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(&port->open_wait, &wait);
+
+	gs_dprintk (GS_DEBUG_BTR, "after add waitq.\n"); 
+
+	cli();
+	if (!tty_hung_up_p(filp))
+		port->count--;
+	sti();
+	port->blocked_open++;
+	while (1) {
+		CD = port->rd->get_CD (port);
+		gs_dprintk (GS_DEBUG_BTR, "CD is now %d.\n", CD);
+		current->state = TASK_INTERRUPTIBLE;
+		if (tty_hung_up_p(filp) ||
+		    !(port->flags & ASYNC_INITIALIZED)) {
+			if (port->flags & ASYNC_HUP_NOTIFY)
+				retval = -EAGAIN;
+			else
+				retval = -ERESTARTSYS;
+			break;
+		}
+		if (!(port->flags & ASYNC_CALLOUT_ACTIVE) &&
+		    !(port->flags & ASYNC_CLOSING) &&
+		    (do_clocal || CD))
+			break;
+		gs_dprintk (GS_DEBUG_BTR, "signal_pending is now: %d (%lx)\n", 
+		(int)signal_pending (current), *(long*)(&current->blocked)); 
+		if (signal_pending(current)) {
+			retval = -ERESTARTSYS;
+			break;
+		}
+		schedule();
+	}
+	gs_dprintk (GS_DEBUG_BTR, "Got out of the loop. (%d)\n",
+		    port->blocked_open);
+	current->state = TASK_RUNNING;
+	remove_wait_queue(&port->open_wait, &wait);
+	if (!tty_hung_up_p(filp))
+		port->count++;
+	port->blocked_open--;
+	if (retval)
+		return retval;
+
+	port->flags |= ASYNC_NORMAL_ACTIVE;
+	func_exit ();
+	return 0;
+}			 
+
+
+void gs_close(struct tty_struct * tty, struct file * filp)
+{
+	unsigned long flags;
+	struct gs_port *port;
+
+	func_enter ();
+	port = (struct gs_port *) tty->driver_data;
+
+	gs_dprintk (GS_DEBUG_CLOSE, "tty=%p, port=%p port->tty=%p\n",
+	            tty, port, port->tty);
+
+	if(! port) {
+		func_exit();
+		return;
+	}
+	if (!port->tty) {
+		printk (KERN_WARNING "gs: Odd: port->tty is NULL\n");
+		port->tty = tty;
+	}
+
+
+	save_flags(flags); cli();
+
+	if (tty_hung_up_p(filp)) {
+		restore_flags(flags);
+		port->rd->hungup (port);
+		func_exit ();
+		return;
+	}
+
+	if ((tty->count == 1) && (port->count != 1)) {
+		printk(KERN_ERR "gs: gs_close: bad port count;"
+		       " tty->count is 1, port count is %d\n", port->count);
+		port->count = 1;
+	}
+	if (--port->count < 0) {
+		printk(KERN_ERR "gs: gs_close: bad port count: %d\n", port->count);
+		port->count = 0;
+	}
+	if (port->count) {
+		restore_flags(flags);
+		func_exit ();
+		return;
+	}
+	port->flags |= ASYNC_CLOSING;
+
+	/*
+	 * Save the termios structure, since this port may have
+	 * separate termios for callout and dialin.
+	 */
+	if (port->flags & ASYNC_NORMAL_ACTIVE)
+		port->normal_termios = *tty->termios;
+	if (port->flags & ASYNC_CALLOUT_ACTIVE)
+		port->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 (port->closing_wait != ASYNC_CLOSING_WAIT_NONE)
+	   tty_wait_until_sent(tty, port->closing_wait); */
+
+	/*
+	 * At this point we stop accepting input.  To do this, we
+	 * disable the receive line status interrupts, and tell the
+	 * interrupt driver to stop checking the data ready bit in the
+	 * line status register.
+	 */
+
+	port->rd->disable_rx_interrupts (port);
+
+	if (port->closing_wait != ASYNC_CLOSING_WAIT_NONE)
+		gs_wait_tx_flushed (port, port->closing_wait); 
+
+	port->flags &= ~GS_ACTIVE;
+
+	if (tty->driver.flush_buffer)
+		tty->driver.flush_buffer(tty);
+	if (tty->ldisc.flush_buffer)
+		tty->ldisc.flush_buffer(tty);
+	tty->closing = 0;
+	port->event = 0;
+	port->tty = 0;
+	if (port->blocked_open) {
+		if (port->close_delay) {
+			current->state = TASK_INTERRUPTIBLE;
+			schedule_timeout(port->close_delay);
+		}
+		wake_up_interruptible(&port->open_wait);
+	}
+	port->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CALLOUT_ACTIVE|
+	                 ASYNC_CLOSING | ASYNC_INITIALIZED);
+	wake_up_interruptible(&port->close_wait);
+
+	port->rd->close (port);
+	port->rd->shutdown_port (port);
+	restore_flags(flags);
+	func_exit ();
+}
+
+
+static unsigned int     gs_baudrates[] = {
+  0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
+  9600, 19200, 38400, 57600, 115200, 230400, 460800, 921600
+};
+
+
+void gs_set_termios (struct tty_struct * tty, 
+                     struct termios * old_termios)
+{
+	struct gs_port *port = tty->driver_data;
+	int baudrate, tmp;
+	struct termios *tiosp;
+
+	func_enter();
+
+	tiosp = tty->termios;
+
+
+	if (gs_debug & GS_DEBUG_TERMIOS) {
+		gs_dprintk (GS_DEBUG_TERMIOS, "termios structure (%p):\n", tiosp);
+		my_hd ((unsigned char *)tiosp, sizeof (struct termios));
+	}
+
+#if 0
+	/* This is an optimization that is only allowed for dumb cards */
+	/* Smart cards require knowledge of iflags and oflags too: that 
+	   might change hardware cooking mode.... */
+#endif
+	if (old_termios) {
+		if(   (tiosp->c_iflag == old_termios->c_iflag)
+		   && (tiosp->c_oflag == old_termios->c_oflag)
+		   && (tiosp->c_cflag == old_termios->c_cflag)
+		   && (tiosp->c_lflag == old_termios->c_lflag)
+		   && (tiosp->c_line  == old_termios->c_line)
+		   && (memcmp(tiosp->c_cc, old_termios->c_cc, NCC) == 0)) {
+			gs_dprintk(GS_DEBUG_TERMIOS, "gs_set_termios: optimized away\n");
+			return;
+		}
+	} else 
+		gs_dprintk(GS_DEBUG_TERMIOS, "gs_set_termios: no old_termios: "
+		           "no optimization\n");
+
+	if(old_termios && (gs_debug & GS_DEBUG_TERMIOS)) {
+		if(tiosp->c_iflag != old_termios->c_iflag)  printk("c_iflag changed\n");
+		if(tiosp->c_oflag != old_termios->c_oflag)  printk("c_oflag changed\n");
+		if(tiosp->c_cflag != old_termios->c_cflag)  printk("c_cflag changed\n");
+		if(tiosp->c_lflag != old_termios->c_lflag)  printk("c_lflag changed\n");
+		if(tiosp->c_line  != old_termios->c_line)   printk("c_line changed\n");
+		if(!memcmp(tiosp->c_cc, old_termios->c_cc, NCC)) printk("c_cc changed\n");
+	}
+
+	baudrate = tiosp->c_cflag & CBAUD;
+	if (baudrate & CBAUDEX) {
+		baudrate &= ~CBAUDEX;
+		if ((baudrate < 1) || (baudrate > 4))
+			tiosp->c_cflag &= ~CBAUDEX;
+		else
+			baudrate += 15;
+	}
+
+	baudrate = gs_baudrates[baudrate];
+	if ((tiosp->c_cflag & CBAUD) == B38400) {
+		if (     (port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI)
+			baudrate = 57600;
+		else if ((port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI)
+			baudrate = 115200;
+		else if ((port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_SHI)
+			baudrate = 230400;
+		else if ((port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_WARP)
+			baudrate = 460800;
+		else if ((port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST)
+			baudrate = (port->baud_base / port->custom_divisor);
+	}
+
+	/* I recommend using THIS instead of the mess in termios (and
+	   duplicating the above code). Next we should create a clean
+	   interface towards this variable. If your card supports arbitrary
+	   baud rates, (e.g. CD1400 or 16550 based cards) then everything
+	   will be very easy..... */
+	port->baud = baudrate;
+
+	/* Two timer ticks seems enough to wakeup something like SLIP driver */
+	/* Baudrate/10 is cps. Divide by HZ to get chars per tick. */
+	tmp = (baudrate / 10 / HZ) * 2;			 
+
+	if (tmp <                 0) tmp = 0;
+	if (tmp >= SERIAL_XMIT_SIZE) tmp = SERIAL_XMIT_SIZE-1;
+
+	port->wakeup_chars = tmp;
+
+	/* We should really wait for the characters to be all sent before
+	   changing the settings. -- CAL */
+	gs_wait_tx_flushed (port, MAX_SCHEDULE_TIMEOUT);
+
+	port->rd->set_real_termios(port);
+
+	if ((!old_termios || 
+	     (old_termios->c_cflag & CRTSCTS)) &&
+	    !(      tiosp->c_cflag & CRTSCTS)) {
+		tty->stopped = 0;
+		gs_start(tty);
+	}
+
+#ifdef tytso_patch_94Nov25_1726
+	/* This "makes sense", Why is it commented out? */
+
+	if (!(old_termios->c_cflag & CLOCAL) &&
+	    (tty->termios->c_cflag & CLOCAL))
+		wake_up_interruptible(&info->open_wait);
+#endif
+
+	func_exit();
+	return;
+}
+
+
+
+/* Must be called with interrupts enabled */
+int gs_init_port(struct gs_port *port)
+{
+	unsigned long flags;
+	unsigned long page;
+
+	save_flags (flags);
+	if (!tmp_buf) {
+		page = get_free_page(GFP_KERNEL);
+
+		cli (); /* Don't expect this to make a difference. */ 
+		if (tmp_buf)
+			free_page(page);
+		else
+			tmp_buf = (unsigned char *) page;
+		restore_flags (flags);
+
+		if (!tmp_buf) {
+			return -ENOMEM;
+		}
+	}
+
+	if (port->flags & ASYNC_INITIALIZED)
+		return 0;
+
+	if (!port->xmit_buf) {
+		/* We may sleep in get_free_page() */
+		unsigned long tmp;
+
+		tmp = get_free_page(GFP_KERNEL);
+
+		/* Spinlock? */
+		cli ();
+		if (port->xmit_buf) 
+			free_page (tmp);
+		else
+			port->xmit_buf = (unsigned char *) tmp;
+		restore_flags (flags);
+
+		if (!port->xmit_buf)
+			return -ENOMEM;
+	}
+
+	cli();
+
+	if (port->tty) 
+		clear_bit(TTY_IO_ERROR, &port->tty->flags);
+
+	port->xmit_cnt = port->xmit_head = port->xmit_tail = 0;
+
+	gs_set_termios(port->tty, NULL);
+
+	port->flags |= ASYNC_INITIALIZED;
+	port->flags &= ~GS_TX_INTEN;
+
+	restore_flags(flags);
+	return 0;
+}
+
+
+int gs_setserial(struct gs_port *port, struct serial_struct *sp)
+{
+	struct serial_struct sio;
+
+	copy_from_user(&sio, sp, sizeof(struct serial_struct));
+
+	if (!capable(CAP_SYS_ADMIN)) {
+		if ((sio.baud_base != port->baud_base) ||
+		    (sio.close_delay != port->close_delay) ||
+		    ((sio.flags & ~ASYNC_USR_MASK) !=
+		     (port->flags & ~ASYNC_USR_MASK)))
+			return(-EPERM);
+	} 
+
+	port->flags = (port->flags & ~ASYNC_USR_MASK) |
+		(sio.flags & ASYNC_USR_MASK);
+  
+	port->baud_base = sio.baud_base;
+	port->close_delay = sio.close_delay;
+	port->closing_wait = sio.closing_wait;
+	port->custom_divisor = sio.custom_divisor;
+
+	gs_set_termios (port->tty, NULL);
+
+	return 0;
+}
+
+
+/*****************************************************************************/
+
+/*
+ *      Generate the serial struct info.
+ */
+
+void gs_getserial(struct gs_port *port, struct serial_struct *sp)
+{
+	struct serial_struct    sio;
+
+	memset(&sio, 0, sizeof(struct serial_struct));
+	sio.flags = port->flags;
+	sio.baud_base = port->baud_base;
+	sio.close_delay = port->close_delay;
+	sio.closing_wait = port->closing_wait;
+	sio.custom_divisor = port->custom_divisor;
+	sio.hub6 = 0;
+
+	/* If you want you can override these. */
+	sio.type = PORT_UNKNOWN;
+	sio.xmit_fifo_size = -1;
+	sio.line = -1;
+	sio.port = -1;
+	sio.irq = -1;
+
+	if (port->rd->getserial)
+		port->rd->getserial (port, &sio);
+
+	copy_to_user(sp, &sio, sizeof(struct serial_struct));
+}
+

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)