From: Alan Cox <alan@redhat.com>

Ok this should fix the bug

"We apologise for the inconvenience"

Signed-off-by: Andrew Morton <akpm@osdl.org>
---

 25-akpm/Documentation/tty.txt             |    6 
 25-akpm/drivers/char/n_tty.c              |  378 +++++++++++++++++++++++++-----
 25-akpm/drivers/char/pcmcia/synclink_cs.c |   59 +++-
 25-akpm/drivers/char/synclink.c           |   64 +++--
 25-akpm/drivers/char/synclinkmp.c         |   60 +++-
 25-akpm/drivers/char/tty_io.c             |   92 +++++--
 25-akpm/drivers/char/tty_ioctl.c          |   40 ++-
 25-akpm/drivers/usb/serial/ir-usb.c       |    4 
 25-akpm/include/linux/tty.h               |    1 
 25-akpm/include/linux/tty_ldisc.h         |    8 
 10 files changed, 563 insertions(+), 149 deletions(-)

diff -puN Documentation/tty.txt~tty-driver-take-4-try-2 Documentation/tty.txt
--- 25/Documentation/tty.txt~tty-driver-take-4-try-2	Thu Sep 23 17:16:31 2004
+++ 25-akpm/Documentation/tty.txt	Thu Sep 23 17:16:31 2004
@@ -58,8 +58,10 @@ flush_buffer()	-	May be called at any po
 
 chars_in_buffer() -	Report the number of bytes in the buffer.
 
-set_termios()	-	Called on termios structure changes. Semantics
-			deeply mysterious right now.
+set_termios()	-	Called on termios structure changes. The caller
+			passes the old termios data and the current data
+			is in the tty. Currently can be parallel entered
+			and ordering isn't predictable - FIXME
 
 read()		-	Move data from the line discipline to the user.
 			Multiple read calls may occur in parallel and the
diff -puN drivers/char/n_tty.c~tty-driver-take-4-try-2 drivers/char/n_tty.c
--- 25/drivers/char/n_tty.c~tty-driver-take-4-try-2	Thu Sep 23 17:16:31 2004
+++ 25-akpm/drivers/char/n_tty.c	Thu Sep 23 17:16:31 2004
@@ -99,11 +99,18 @@ static inline void put_tty_queue(unsigne
 	spin_unlock_irqrestore(&tty->read_lock, flags);
 }
 
-/* 
- * Check whether to call the driver.unthrottle function.
- * We test the TTY_THROTTLED bit first so that it always
- * indicates the current state.
+/**
+ *	check_unthrottle	-	allow new receive data
+ *	@tty; tty device
+ *
+ *	Check whether to call the driver.unthrottle function.
+ *	We test the TTY_THROTTLED bit first so that it always
+ *	indicates the current state. The decision about whether
+ *	it is worth allowing more input has been taken by the caller.
+ *	Can sleep, may be called under the atomic_read semaphore but
+ *	this is not guaranteed.
  */
+
 static void check_unthrottle(struct tty_struct * tty)
 {
 	if (tty->count &&
@@ -112,10 +119,13 @@ static void check_unthrottle(struct tty_
 		tty->driver->unthrottle(tty);
 }
 
-/*
- * Reset the read buffer counters, clear the flags, 
- * and make sure the driver is unthrottled. Called
- * from n_tty_open() and n_tty_flush_buffer().
+/**
+ *	reset_buffer_flags	-	reset buffer state
+ *	@tty: terminal to reset
+ *
+ *	Reset the read buffer counters, clear the flags,
+ *	and make sure the driver is unthrottled. Called
+ *	from n_tty_open() and n_tty_flush_buffer().
  */
 static void reset_buffer_flags(struct tty_struct *tty)
 {
@@ -129,9 +139,19 @@ static void reset_buffer_flags(struct tt
 	check_unthrottle(tty);
 }
 
-/*
- * Flush the input buffer
+/**
+ *	n_tty_flush_buffer	-	clean input queue
+ *	@tty:	terminal device
+ *
+ *	Flush the input buffer. Called when the line discipline is
+ *	being closed, when the tty layer wants the buffer flushed (eg
+ *	at hangup) or when the N_TTY line discipline internally has to
+ *	clean the pending queue (for example some signals).
+ *
+ *	FIXME: tty->ctrl_status is not spinlocked and relies on
+ *	lock_kernel() still.
  */
+
 void n_tty_flush_buffer(struct tty_struct * tty)
 {
 	/* clear everything and unthrottle the driver */
@@ -146,9 +166,14 @@ void n_tty_flush_buffer(struct tty_struc
 	}
 }
 
-/*
- * Return number of characters buffered to be delivered to user
+/**
+ *	n_tty_chars_in_buffer	-	report available bytes
+ *	@tty: tty device
+ *
+ *	Report the number of characters buffered to be delivered to user
+ *	at this instant in time.
  */
+
 ssize_t n_tty_chars_in_buffer(struct tty_struct *tty)
 {
 	unsigned long flags;
@@ -166,20 +191,47 @@ ssize_t n_tty_chars_in_buffer(struct tty
 	return n;
 }
 
+/**
+ *	is_utf8_continuation	-	utf8 multibyte check
+ *	@c: byte to check
+ *
+ *	Returns true if the utf8 character 'c' is a multibyte continuation
+ *	character. We use this to correctly compute the on screen size
+ *	of the character when printing
+ */
+
 static inline int is_utf8_continuation(unsigned char c)
 {
 	return (c & 0xc0) == 0x80;
 }
 
+/**
+ *	is_continuation		-	multibyte check
+ *	@c: byte to check
+ *
+ *	Returns true if the utf8 character 'c' is a multibyte continuation
+ *	character and the terminal is in unicode mode.
+ */
+
 static inline int is_continuation(unsigned char c, struct tty_struct *tty)
 {
 	return I_IUTF8(tty) && is_utf8_continuation(c);
 }
 
-/*
- * Perform OPOST processing.  Returns -1 when the output device is
- * full and the character must be retried.
+/**
+ *	opost			-	output post processor
+ *	@c: character (or partial unicode symbol)
+ *	@tty: terminal device
+ *
+ *	Perform OPOST processing.  Returns -1 when the output device is
+ *	full and the character must be retried. Note that Linux currently
+ *	ignores TABDLY, CRDLY, VTDLY, FFDLY and NLDLY. They simply aren't
+ *	relevant in the world today. If you ever need them, add them here.
+ *
+ *	Called from both the receive and transmit sides and can be called
+ *	re-entrantly. Relies on lock_kernel() still.
  */
+
 static int opost(unsigned char c, struct tty_struct *tty)
 {
 	int	space, spaces;
@@ -239,10 +291,20 @@ static int opost(unsigned char c, struct
 	return 0;
 }
 
-/*
- * opost_block --- to speed up block console writes, among other
- * things.
+/**
+ *	opost_block		-	block postprocess
+ *	@tty: terminal device
+ *	@inbuf: user buffer
+ *	@nr: number of bytes
+ *
+ *	This path is used to speed up block console writes, among other
+ *	things when processing blocks of output data. It handles only
+ *	the simple cases normally found and helps to generate blocks of
+ *	symbols for the console driver and thus improve performance.
+ *
+ *	Called from write_chan under the tty layer write lock.
  */
+
 static ssize_t opost_block(struct tty_struct * tty,
 		       const unsigned char __user * inbuf, unsigned int nr)
 {
@@ -304,13 +366,27 @@ break_out:
 }
 
 
+/**
+ *	put_char	-	write character to driver
+ *	@c: character (or part of unicode symbol)
+ *	@tty: terminal device
+ *
+ *	Queue a byte to the driver layer for output
+ */
 
 static inline void put_char(unsigned char c, struct tty_struct *tty)
 {
 	tty->driver->put_char(tty, c);
 }
 
-/* Must be called only when L_ECHO(tty) is true. */
+/**
+ *	echo_char	-	echo characters
+ *	@c: unicode byte to echo
+ *	@tty: terminal device
+ *
+ *	Echo user input back onto the screen. This must be called only when
+ *	L_ECHO(tty) is true. Called from the driver receive_buf path.
+ */
 
 static void echo_char(unsigned char c, struct tty_struct *tty)
 {
@@ -331,6 +407,16 @@ static inline void finish_erasing(struct
 	}
 }
 
+/**
+ *	eraser		-	handle erase function
+ *	@c: character input
+ *	@tty: terminal device
+ *
+ *	Perform erase and neccessary output when an erase character is
+ *	present in the stream from the driver layer. Handles the complexities
+ *	of UTF-8 multibyte symbols.
+ */
+
 static void eraser(unsigned char c, struct tty_struct *tty)
 {
 	enum { ERASE, WERASE, KILL } kill_type;
@@ -463,6 +549,18 @@ static void eraser(unsigned char c, stru
 		finish_erasing(tty);
 }
 
+/**
+ *	isig		-	handle the ISIG optio
+ *	@sig: signal
+ *	@tty: terminal
+ *	@flush: force flush
+ *
+ *	Called when a signal is being sent due to terminal input. This
+ *	may caus terminal flushing to take place according to the termios
+ *	settings and character used. Called from the driver receive_buf
+ *	path so serialized.
+ */
+
 static inline void isig(int sig, struct tty_struct *tty, int flush)
 {
 	if (tty->pgrp > 0)
@@ -474,6 +572,16 @@ static inline void isig(int sig, struct 
 	}
 }
 
+/**
+ *	n_tty_receive_break	-	handle break
+ *	@tty: terminal
+ *
+ *	An RS232 break event has been hit in the incoming bitstream. This
+ *	can cause a variety of events depending upon the termios settings.
+ *
+ *	Called from the receive_buf path so single threaded.
+ */
+
 static inline void n_tty_receive_break(struct tty_struct *tty)
 {
 	if (I_IGNBRK(tty))
@@ -490,19 +598,40 @@ static inline void n_tty_receive_break(s
 	wake_up_interruptible(&tty->read_wait);
 }
 
+/**
+ *	n_tty_receive_overrun	-	handle overrun reporting
+ *	@tty: terminal
+ *
+ *	Data arrived faster than we could process it. While the tty
+ *	driver has flagged this the bits that were missed are gone
+ *	forever.
+ *
+ *	Called from the receive_buf path so single threaded. Does not
+ *	need locking as num_overrun and overrun_time are function
+ *	private.
+ */
+
 static inline void n_tty_receive_overrun(struct tty_struct *tty)
 {
 	char buf[64];
 
 	tty->num_overrun++;
 	if (time_before(tty->overrun_time, jiffies - HZ)) {
-		printk("%s: %d input overrun(s)\n", tty_name(tty, buf),
+		printk(KERN_WARNING "%s: %d input overrun(s)\n", tty_name(tty, buf),
 		       tty->num_overrun);
 		tty->overrun_time = jiffies;
 		tty->num_overrun = 0;
 	}
 }
 
+/**
+ *	n_tty_receive_parity_error	-	error notifier
+ *	@tty: terminal device
+ *	@c: character
+ *
+ *	Process a parity error and queue the right data to indicate
+ *	the error case if neccessary. Locking as per n_tty_receive_buf.
+ */
 static inline void n_tty_receive_parity_error(struct tty_struct *tty,
 					      unsigned char c)
 {
@@ -520,6 +649,16 @@ static inline void n_tty_receive_parity_
 	wake_up_interruptible(&tty->read_wait);
 }
 
+/**
+ *	n_tty_receive_char	-	perform processing
+ *	@tty: terminal device
+ *	@c: character
+ *
+ *	Process an individual character of input received from the driver.
+ *	This is serialized with respect to itself by the rules for the
+ *	driver above.
+ */
+
 static inline void n_tty_receive_char(struct tty_struct *tty, unsigned char c)
 {
 	unsigned long flags;
@@ -711,6 +850,16 @@ send_signal:
 	put_tty_queue(c, tty);
 }	
 
+/**
+ *	n_tty_receive_room	-	receive space
+ *	@tty: terminal
+ *
+ *	Called by the driver to find out how much data it is
+ *	permitted to feed to the line discipline without any being lost
+ *	and thus to manage flow control. Not serialized. Answers for the
+ *	"instant".
+ */
+
 static int n_tty_receive_room(struct tty_struct *tty)
 {
 	int	left = N_TTY_BUF_SIZE - tty->read_cnt - 1;
@@ -729,10 +878,13 @@ static int n_tty_receive_room(struct tty
 	return 0;
 }
 
-/*
- * Required for the ptys, serial driver etc. since processes
- * that attach themselves to the master and rely on ASYNC
- * IO must be woken up
+/**
+ *	n_tty_write_wakeup	-	asynchronous I/O notifier
+ *	@tty: tty device
+ *
+ *	Required for the ptys, serial driver etc. since processes
+ *	that attach themselves to the master and rely on ASYNC
+ *	IO must be woken up
  */
 
 static void n_tty_write_wakeup(struct tty_struct *tty)
@@ -745,6 +897,19 @@ static void n_tty_write_wakeup(struct tt
 	return;
 }
 
+/**
+ *	n_tty_receive_buf	-	data receive
+ *	@tty: terminal device
+ *	@cp: buffer
+ *	@fp: flag buffer
+ *	@count: characters
+ *
+ *	Called by the terminal driver when a block of characters has
+ *	been received. This function must be called from soft contexts
+ *	not from interrupt context. The driver is responsible for making
+ *	calls one at a time and in order (or using flush_to_ldisc)
+ */
+
 static void n_tty_receive_buf(struct tty_struct *tty, const unsigned char *cp,
 			      char *fp, int count)
 {
@@ -828,6 +993,18 @@ int is_ignored(int sig)
 	        current->sighand->action[sig-1].sa.sa_handler == SIG_IGN);
 }
 
+/**
+ *	n_tty_set_termios	-	termios data changed
+ *	@tty: terminal
+ *	@old: previous data
+ *
+ *	Called by the tty layer when the user changes termios flags so
+ *	that the line discipline can plan ahead. This function cannot sleep
+ *	and is protected from re-entry by the tty layer. The user is
+ *	guaranteed that this function will not be re-entered or in progress
+ *	when the ldisc is closed.
+ */
+
 static void n_tty_set_termios(struct tty_struct *tty, struct termios * old)
 {
 	if (!tty)
@@ -843,7 +1020,6 @@ static void n_tty_set_termios(struct tty
 	    I_ICRNL(tty) || I_INLCR(tty) || L_ICANON(tty) ||
 	    I_IXON(tty) || L_ISIG(tty) || L_ECHO(tty) ||
 	    I_PARMRK(tty)) {
-		local_irq_disable(); // FIXME: is this safe?
 		memset(tty->process_char_map, 0, 256/8);
 
 		if (I_IGNCR(tty) || I_ICRNL(tty))
@@ -879,7 +1055,6 @@ static void n_tty_set_termios(struct tty
 			set_bit(SUSP_CHAR(tty), tty->process_char_map);
 		}
 		clear_bit(__DISABLED_CHAR, tty->process_char_map);
-		local_irq_enable(); // FIXME: is this safe?
 		tty->raw = 0;
 		tty->real_raw = 0;
 	} else {
@@ -893,6 +1068,16 @@ static void n_tty_set_termios(struct tty
 	}
 }
 
+/**
+ *	n_tty_close		-	close the ldisc for this tty
+ *	@tty: device
+ *
+ *	Called from the terminal layer when this line discipline is
+ *	being shut down, either because of a close or becsuse of a
+ *	discipline change. The function will not be called while other
+ *	ldisc methods are in progress.
+ */
+
 static void n_tty_close(struct tty_struct *tty)
 {
 	n_tty_flush_buffer(tty);
@@ -902,11 +1087,22 @@ static void n_tty_close(struct tty_struc
 	}
 }
 
+/**
+ *	n_tty_open		-	open an ldisc
+ *	@tty: terminal to open
+ *
+ *	Called when this line discipline is being attached to the
+ *	terminal device. Can sleep. Called serialized so that no
+ *	other events will occur in parallel. No further open will occur
+ *	until a close.
+ */
+
 static int n_tty_open(struct tty_struct *tty)
 {
 	if (!tty)
 		return -EINVAL;
 
+	/* This one is ugly. Currently a malloc failure here can panic */
 	if (!tty->read_buf) {
 		tty->read_buf = alloc_buf();
 		if (!tty->read_buf)
@@ -932,14 +1128,23 @@ static inline int input_available_p(stru
 	return 0;
 }
 
-/*
- * Helper function to speed up read_chan.  It is only called when
- * ICANON is off; it copies characters straight from the tty queue to
- * user space directly.  It can be profitably called twice; once to
- * drain the space from the tail pointer to the (physical) end of the
- * buffer, and once to drain the space from the (physical) beginning of
- * the buffer to head pointer.
+/**
+ * 	copy_from_read_buf	-	copy read data directly
+ *	@tty: terminal device
+ *	@b: user data
+ *	@nr: size of data
+ *
+ *	Helper function to speed up read_chan.  It is only called when
+ *	ICANON is off; it copies characters straight from the tty queue to
+ *	user space directly.  It can be profitably called twice; once to
+ *	drain the space from the tail pointer to the (physical) end of the
+ *	buffer, and once to drain the space from the (physical) beginning of
+ *	the buffer to head pointer.
+ *
+ *	Called under the tty->atomic_read sem and with TTY_DONT_FLIP set
+ *
  */
+
 static inline int copy_from_read_buf(struct tty_struct *tty,
 				      unsigned char __user **b,
 				      size_t *nr)
@@ -970,6 +1175,54 @@ static inline int copy_from_read_buf(str
 
 extern ssize_t redirected_tty_write(struct file *,const char *,size_t,loff_t *);
 
+/**
+ *	job_control		-	check job control
+ *	@tty: tty
+ *	@file: file handle
+ *
+ *	Perform job control management checks on this file/tty descriptor
+ *	and if appropriate send any needed signals and return a negative
+ *	error code if action should be taken.
+ */
+
+static int job_control(struct tty_struct *tty, struct file *file)
+{
+	/* Job control check -- must be done at start and after
+	   every sleep (POSIX.1 7.1.1.4). */
+	/* NOTE: not yet done after every sleep pending a thorough
+	   check of the logic of this change. -- jlc */
+	/* don't stop on /dev/console */
+	if (file->f_op->write != redirected_tty_write &&
+	    current->signal->tty == tty) {
+		if (tty->pgrp <= 0)
+			printk("read_chan: tty->pgrp <= 0!\n");
+		else if (process_group(current) != tty->pgrp) {
+			if (is_ignored(SIGTTIN) ||
+			    is_orphaned_pgrp(process_group(current)))
+				return -EIO;
+			kill_pg(process_group(current), SIGTTIN, 1);
+			return -ERESTARTSYS;
+		}
+	}
+	return 0;
+}
+
+
+/**
+ *	read_chan		-	read function for tty
+ *	@tty: tty device
+ *	@file: file object
+ *	@buf: userspace buffer pointer
+ *	@nr: size of I/O
+ *
+ *	Perform reads for the line discipline. We are guaranteed that the
+ *	line discipline will not be closed under us but we may get multiple
+ *	parallel readers and must handle this ourselves. We may also get
+ *	a hangup. Always called in user context, may sleep.
+ *
+ *	This code must be sure never to sleep through a hangup.
+ */
+
 static ssize_t read_chan(struct tty_struct *tty, struct file *file,
 			 unsigned char __user *buf, size_t nr)
 {
@@ -989,23 +1242,9 @@ do_it_again:
 		return -EIO;
 	}
 
-	/* Job control check -- must be done at start and after
-	   every sleep (POSIX.1 7.1.1.4). */
-	/* NOTE: not yet done after every sleep pending a thorough
-	   check of the logic of this change. -- jlc */
-	/* don't stop on /dev/console */
-	if (file->f_op->write != redirected_tty_write &&
-	    current->signal->tty == tty) {
-		if (tty->pgrp <= 0)
-			printk("read_chan: tty->pgrp <= 0!\n");
-		else if (process_group(current) != tty->pgrp) {
-			if (is_ignored(SIGTTIN) ||
-			    is_orphaned_pgrp(process_group(current)))
-				return -EIO;
-			kill_pg(process_group(current), SIGTTIN, 1);
-			return -ERESTARTSYS;
-		}
-	}
+	c = job_control(tty, file);
+	if(c < 0)
+		return c;
 
 	minimum = time = 0;
 	timeout = MAX_SCHEDULE_TIMEOUT;
@@ -1028,6 +1267,9 @@ do_it_again:
 		}
 	}
 
+	/*
+	 *	Internal serialization of reads.
+	 */
 	if (file->f_flags & O_NONBLOCK) {
 		if (down_trylock(&tty->atomic_read))
 			return -EAGAIN;
@@ -1177,6 +1419,21 @@ do_it_again:
 	return retval;
 }
 
+/**
+ *	write_chan		-	write function for tty
+ *	@tty: tty device
+ *	@file: file object
+ *	@buf: userspace buffer pointer
+ *	@nr: size of I/O
+ *
+ *	Write function of the terminal device. This is serialized with
+ *	respect to other write callers but not to termios changes, reads
+ *	and other such events. We must be careful with N_TTY as the receive
+ *	code will echo characters, thus calling driver write methods.
+ *
+ *	This code must be sure never to sleep through a hangup.
+ */
+
 static ssize_t write_chan(struct tty_struct * tty, struct file * file,
 			  const unsigned char __user * buf, size_t nr)
 {
@@ -1246,7 +1503,25 @@ break_out:
 	return (b - buf) ? b - buf : retval;
 }
 
-/* Called without the kernel lock held - fine */
+/**
+ *	normal_poll		-	poll method for N_TTY
+ *	@tty: terminal device
+ *	@file: file accessing it
+ *	@wait: poll table
+ *
+ *	Called when the line discipline is asked to poll() for data or
+ *	for special events. This code is not serialized with respect to
+ *	other events save open/close.
+ *
+ *	This code must be sure never to sleep through a hangup.
+ *	Called without the kernel lock held - fine
+ *
+ *	FIXME: if someone changes the VMIN or discipline settings for the
+ *	terminal while another process is in poll() the poll does not
+ *	recompute the new limits. Possibly set_termios should issue
+ *	a read wakeup to fix this bug.
+ */
+
 static unsigned int normal_poll(struct tty_struct * tty, struct file * file, poll_table *wait)
 {
 	unsigned int mask = 0;
@@ -1287,6 +1562,7 @@ struct tty_ldisc tty_ldisc_N_TTY = {
 	n_tty_ioctl,		/* ioctl */
 	n_tty_set_termios,	/* set_termios */
 	normal_poll,		/* poll */
+	NULL,			/* hangup */
 	n_tty_receive_buf,	/* receive_buf */
 	n_tty_receive_room,	/* receive_room */
 	n_tty_write_wakeup	/* write_wakeup */
diff -puN drivers/char/pcmcia/synclink_cs.c~tty-driver-take-4-try-2 drivers/char/pcmcia/synclink_cs.c
--- 25/drivers/char/pcmcia/synclink_cs.c~tty-driver-take-4-try-2	Thu Sep 23 17:16:31 2004
+++ 25-akpm/drivers/char/pcmcia/synclink_cs.c	Thu Sep 23 17:16:31 2004
@@ -516,6 +516,40 @@ static void* mgslpc_get_text_ptr(void)
 	return mgslpc_get_text_ptr;
 }
 
+/**
+ * line discipline callback wrappers
+ *
+ * The wrappers maintain line discipline references
+ * while calling into the line discipline.
+ *
+ * ldisc_flush_buffer - flush line discipline receive buffers
+ * ldisc_receive_buf  - pass receive data to line discipline
+ */
+
+static void ldisc_flush_buffer(struct tty_struct *tty)
+{
+	struct tty_ldisc *ld = tty_ldisc_ref(tty);
+	if (ld) {
+		if (ld->flush_buffer)
+			ld->flush_buffer(tty);
+		tty_ldisc_deref(ld);
+	}
+}
+
+static void ldisc_receive_buf(struct tty_struct *tty,
+			      const __u8 *data, char *flags, int count)
+{
+	struct tty_ldisc *ld;
+	if (!tty)
+		return;
+	ld = tty_ldisc_ref(tty);
+	if (ld) {
+		if (ld->receive_buf)
+			ld->receive_buf(tty, data, flags, count);
+		tty_ldisc_deref(ld);
+	}
+}
+
 static dev_link_t *mgslpc_attach(void)
 {
     MGSLPC_INFO *info;
@@ -969,13 +1003,7 @@ void bh_transmit(MGSLPC_INFO *info)
 		printk("bh_transmit() entry on %s\n", info->device_name);
 
 	if (tty) {
-		if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
-		    tty->ldisc.write_wakeup) {
-			if ( debug_level >= DEBUG_LEVEL_BH )
-				printk( "%s(%d):calling ldisc.write_wakeup on %s\n",
-					__FILE__,__LINE__,info->device_name);
-			(tty->ldisc.write_wakeup)(tty);
-		}
+		tty_wakeup(tty);
 		wake_up_interruptible(&tty->write_wait);
 	}
 }
@@ -1860,11 +1888,9 @@ static void mgslpc_flush_buffer(struct t
 	info->tx_count = info->tx_put = info->tx_get = 0;
 	del_timer(&info->tx_timer);	
 	spin_unlock_irqrestore(&info->lock,flags);
-	
+
 	wake_up_interruptible(&tty->write_wait);
-	if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
-	    tty->ldisc.write_wakeup)
-		(tty->ldisc.write_wakeup)(tty);
+	tty_wakeup(tty);
 }
 
 /* Send a high-priority XON/XOFF character
@@ -2573,9 +2599,8 @@ static void mgslpc_close(struct tty_stru
 
 	if (tty->driver->flush_buffer)
 		tty->driver->flush_buffer(tty);
-		
-	if (tty->ldisc.flush_buffer)
-		tty->ldisc.flush_buffer(tty);
+
+	ldisc_flush_buffer(tty);
 		
 	shutdown(info);
 	
@@ -4040,11 +4065,7 @@ int rx_get_frame(MGSLPC_INFO *info)
 				hdlcdev_rx(info, buf->data, framesize);
 			else
 #endif
-			{
-				/* Call the line discipline receive callback directly. */
-				if (tty && tty->ldisc.receive_buf)
-					tty->ldisc.receive_buf(tty, buf->data, info->flag_buf, framesize);
-			}
+				ldisc_receive_buf(tty, buf->data, info->flag_buf, framesize);
 		}
 	}
 
diff -puN drivers/char/synclink.c~tty-driver-take-4-try-2 drivers/char/synclink.c
--- 25/drivers/char/synclink.c~tty-driver-take-4-try-2	Thu Sep 23 17:16:31 2004
+++ 25-akpm/drivers/char/synclink.c	Thu Sep 23 17:16:31 2004
@@ -975,6 +975,40 @@ static inline int mgsl_paranoia_check(st
 	return 0;
 }
 
+/**
+ * line discipline callback wrappers
+ *
+ * The wrappers maintain line discipline references
+ * while calling into the line discipline.
+ *
+ * ldisc_flush_buffer - flush line discipline receive buffers
+ * ldisc_receive_buf  - pass receive data to line discipline
+ */
+
+static void ldisc_flush_buffer(struct tty_struct *tty)
+{
+	struct tty_ldisc *ld = tty_ldisc_ref(tty);
+	if (ld) {
+		if (ld->flush_buffer)
+			ld->flush_buffer(tty);
+		tty_ldisc_deref(ld);
+	}
+}
+
+static void ldisc_receive_buf(struct tty_struct *tty,
+			      const __u8 *data, char *flags, int count)
+{
+	struct tty_ldisc *ld;
+	if (!tty)
+		return;
+	ld = tty_ldisc_ref(tty);
+	if (ld) {
+		if (ld->receive_buf)
+			ld->receive_buf(tty, data, flags, count);
+		tty_ldisc_deref(ld);
+	}
+}
+
 /* mgsl_stop()		throttle (stop) transmitter
  * 	
  * Arguments:		tty	pointer to tty info structure
@@ -1135,13 +1169,7 @@ void mgsl_bh_transmit(struct mgsl_struct
 			__FILE__,__LINE__,info->device_name);
 
 	if (tty) {
-		if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
-		    tty->ldisc.write_wakeup) {
-			if ( debug_level >= DEBUG_LEVEL_BH )
-				printk( "%s(%d):calling ldisc.write_wakeup on %s\n",
-					__FILE__,__LINE__,info->device_name);
-			(tty->ldisc.write_wakeup)(tty);
-		}
+		tty_wakeup(tty);
 		wake_up_interruptible(&tty->write_wait);
 	}
 
@@ -2397,11 +2425,8 @@ static void mgsl_flush_buffer(struct tty
 	spin_unlock_irqrestore(&info->irq_spinlock,flags);
 	
 	wake_up_interruptible(&tty->write_wait);
-	if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
-	    tty->ldisc.write_wakeup)
-		(tty->ldisc.write_wakeup)(tty);
-		
-}	/* end of mgsl_flush_buffer() */
+	tty_wakeup(tty);
+}
 
 /* mgsl_send_xchar()
  *
@@ -3235,9 +3260,8 @@ static void mgsl_close(struct tty_struct
 
 	if (tty->driver->flush_buffer)
 		tty->driver->flush_buffer(tty);
-		
-	if (tty->ldisc.flush_buffer)
-		tty->ldisc.flush_buffer(tty);
+
+	ldisc_flush_buffer(tty);
 		
 	shutdown(info);
 	
@@ -6810,11 +6834,7 @@ int mgsl_get_rx_frame(struct mgsl_struct
 				hdlcdev_rx(info,info->intermediate_rxbuffer,framesize);
 			else
 #endif
-			{
-				/* Call the line discipline receive callback directly. */
-				if ( tty && tty->ldisc.receive_buf )
-				tty->ldisc.receive_buf(tty, info->intermediate_rxbuffer, info->flag_buf, framesize);
-			}
+				ldisc_receive_buf(tty, info->intermediate_rxbuffer, info->flag_buf, framesize);
 		}
 	}
 	/* Free the buffers used by this frame. */
@@ -6986,9 +7006,7 @@ int mgsl_get_raw_rx_frame(struct mgsl_st
 			memcpy( info->intermediate_rxbuffer, pBufEntry->virt_addr, framesize);
 			info->icount.rxok++;
 
-			/* Call the line discipline receive callback directly. */
-			if ( tty && tty->ldisc.receive_buf )
-				tty->ldisc.receive_buf(tty, info->intermediate_rxbuffer, info->flag_buf, framesize);
+			ldisc_receive_buf(tty, info->intermediate_rxbuffer, info->flag_buf, framesize);
 		}
 
 		/* Free the buffers used by this frame. */
diff -puN drivers/char/synclinkmp.c~tty-driver-take-4-try-2 drivers/char/synclinkmp.c
--- 25/drivers/char/synclinkmp.c~tty-driver-take-4-try-2	Thu Sep 23 17:16:31 2004
+++ 25-akpm/drivers/char/synclinkmp.c	Thu Sep 23 17:16:31 2004
@@ -699,6 +699,40 @@ static inline int sanity_check(SLMP_INFO
 	return 0;
 }
 
+/**
+ * line discipline callback wrappers
+ *
+ * The wrappers maintain line discipline references
+ * while calling into the line discipline.
+ *
+ * ldisc_flush_buffer - flush line discipline receive buffers
+ * ldisc_receive_buf  - pass receive data to line discipline
+ */
+
+static void ldisc_flush_buffer(struct tty_struct *tty)
+{
+	struct tty_ldisc *ld = tty_ldisc_ref(tty);
+	if (ld) {
+		if (ld->flush_buffer)
+			ld->flush_buffer(tty);
+		tty_ldisc_deref(ld);
+	}
+}
+
+static void ldisc_receive_buf(struct tty_struct *tty,
+			      const __u8 *data, char *flags, int count)
+{
+	struct tty_ldisc *ld;
+	if (!tty)
+		return;
+	ld = tty_ldisc_ref(tty);
+	if (ld) {
+		if (ld->receive_buf)
+			ld->receive_buf(tty, data, flags, count);
+		tty_ldisc_deref(ld);
+	}
+}
+
 /* tty callbacks */
 
 /* Called when a port is opened.  Init and enable port.
@@ -846,8 +880,7 @@ static void close(struct tty_struct *tty
 	if (tty->driver->flush_buffer)
 		tty->driver->flush_buffer(tty);
 
-	if (tty->ldisc.flush_buffer)
-		tty->ldisc.flush_buffer(tty);
+	ldisc_flush_buffer(tty);
 
 	shutdown(info);
 
@@ -1252,9 +1285,7 @@ static void flush_buffer(struct tty_stru
 	spin_unlock_irqrestore(&info->lock,flags);
 
 	wake_up_interruptible(&tty->write_wait);
-	if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
-	    tty->ldisc.write_wakeup)
-		(tty->ldisc.write_wakeup)(tty);
+	tty_wakeup(tty);
 }
 
 /* throttle (stop) transmitter
@@ -2123,13 +2154,7 @@ void bh_transmit(SLMP_INFO *info)
 			__FILE__,__LINE__,info->device_name);
 
 	if (tty) {
-		if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
-		    tty->ldisc.write_wakeup) {
-			if ( debug_level >= DEBUG_LEVEL_BH )
-				printk( "%s(%d):%s calling ldisc.write_wakeup\n",
-					__FILE__,__LINE__,info->device_name);
-			(tty->ldisc.write_wakeup)(tty);
-		}
+		tty_wakeup(tty);
 		wake_up_interruptible(&tty->write_wait);
 	}
 }
@@ -5051,15 +5076,8 @@ CheckAgain:
 				hdlcdev_rx(info,info->tmp_rx_buf,framesize);
 			else
 #endif
-			{
-				if ( tty && tty->ldisc.receive_buf ) {
-					/* Call the line discipline receive callback directly. */
-					tty->ldisc.receive_buf(tty,
-						info->tmp_rx_buf,
-						info->flag_buf,
-						framesize);
-				}
-			}
+				ldisc_receive_buf(tty,info->tmp_rx_buf,
+						  info->flag_buf, framesize);
 		}
 	}
 	/* Free the buffers used by this frame. */
diff -puN drivers/char/tty_io.c~tty-driver-take-4-try-2 drivers/char/tty_io.c
--- 25/drivers/char/tty_io.c~tty-driver-take-4-try-2	Thu Sep 23 17:16:31 2004
+++ 25-akpm/drivers/char/tty_io.c	Thu Sep 23 17:16:31 2004
@@ -130,6 +130,8 @@ LIST_HEAD(tty_drivers);			/* linked list
 /* Semaphore to protect creating and releasing a tty. This is shared with
    vt.c for deeply disgusting hack reasons */
 DECLARE_MUTEX(tty_sem);
+/* Lock for tty_termios changes - private to tty_io/tty_ioctl */
+spinlock_t tty_termios_lock = SPIN_LOCK_UNLOCKED;
 
 #ifdef CONFIG_UNIX98_PTYS
 extern struct tty_driver *ptm_driver;	/* Unix98 pty masters; for /dev/ptmx */
@@ -230,6 +232,20 @@ static int check_tty_count(struct tty_st
 }
 
 /*
+ *	This is probably overkill for real world processors but
+ *	they are not on hot paths so a little discipline won't do
+ *	any harm.
+ */
+
+static void tty_set_termios_ldisc(struct tty_struct *tty, int num)
+{
+	unsigned long flags;
+	spin_lock_irqsave(&tty_termios_lock, flags);
+	tty->termios->c_line = num;
+	spin_unlock_irqrestore(&tty_termios_lock, flags);
+}
+
+/*
  *	This guards the refcounted line discipline lists. The lock
  *	must be taken with irqs off because there are hangup path
  *	callers who will do ldisc lookups and cannot sleep.
@@ -365,7 +381,7 @@ struct tty_ldisc *tty_ldisc_ref_wait(str
 	/* wait_event is a macro */
 	wait_event(tty_ldisc_wait, tty_ldisc_try(tty));
 	if(tty->ldisc.refcount == 0)
-		printk("Ref wait -> 0 ref\n");
+		printk(KERN_ERR "tty_ldisc_ref_wait\n");
 	return &tty->ldisc;
 }
 
@@ -406,7 +422,7 @@ void tty_ldisc_deref(struct tty_ldisc *l
 
 	spin_lock_irqsave(&tty_ldisc_lock, flags);
 	if(ld->refcount == 0)
-		printk(KERN_EMERG "tty_ldisc_deref: no references.\n");
+		printk(KERN_ERR "tty_ldisc_deref: no references.\n");
 	else
 		ld->refcount--;
 	if(ld->refcount == 0)
@@ -503,19 +519,19 @@ restart:
 
 	/* Now set up the new line discipline. */
 	tty_ldisc_assign(tty, ld);
-	tty->termios->c_line = ldisc;
+	tty_set_termios_ldisc(tty, ldisc);
 	if (tty->ldisc.open)
 		retval = (tty->ldisc.open)(tty);
 	if (retval < 0) {
 		tty_ldisc_put(ldisc);
 		/* There is an outstanding reference here so this is safe */
 		tty_ldisc_assign(tty, tty_ldisc_get(o_ldisc.num));
-		tty->termios->c_line = tty->ldisc.num;
+		tty_set_termios_ldisc(tty, tty->ldisc.num);
 		if (tty->ldisc.open && (tty->ldisc.open(tty) < 0)) {
 			tty_ldisc_put(o_ldisc.num);
 			/* This driver is always present */
 			tty_ldisc_assign(tty, tty_ldisc_get(N_TTY));
-			tty->termios->c_line = N_TTY;
+			tty_set_termios_ldisc(tty, N_TTY);
 			if (tty->ldisc.open) {
 				int r = tty->ldisc.open(tty);
 
@@ -711,6 +727,7 @@ void do_tty_hangup(void *data)
 	
 	check_tty_count(tty, "do_tty_hangup");
 	file_list_lock();
+	/* This breaks for file handles being sent over AF_UNIX sockets ? */
 	list_for_each_entry(filp, &tty->tty_files, f_list) {
 		if (filp->f_op->write == redirected_tty_write)
 			cons_filp = filp;
@@ -723,7 +740,7 @@ void do_tty_hangup(void *data)
 	file_list_unlock();
 	
 	/* FIXME! What are the locking issues here? This may me overdoing things..
-	* this question is especially important now that we've removed the irqlock. */
+	 * this question is especially important now that we've removed the irqlock. */
 
 	ld = tty_ldisc_ref(tty);
 	if(ld != NULL)	/* We may have no line discipline at this point */
@@ -735,8 +752,13 @@ void do_tty_hangup(void *data)
 		if ((test_bit(TTY_DO_WRITE_WAKEUP, &tty->flags)) &&
 		    ld->write_wakeup)
 			ld->write_wakeup(tty);
+		if (ld->hangup)
+			ld->hangup(tty);
 	}
 
+	/* FIXME: Once we trust the LDISC code better we can wait here for
+	   ldisc completion and fix the driver call race */
+
 	wake_up_interruptible(&tty->write_wait);
 	wake_up_interruptible(&tty->read_wait);
 
@@ -745,8 +767,13 @@ void do_tty_hangup(void *data)
 	 * N_TTY.
 	 */
 	if (tty->driver->flags & TTY_DRIVER_RESET_TERMIOS)
+	{
+		unsigned long flags;
+		spin_lock_irqsave(&tty_termios_lock, flags);
 		*tty->termios = tty->driver->init_termios;
-		
+		spin_unlock_irqrestore(&tty_termios_lock, flags);
+	}
+
 	/* Defer ldisc switch */
 	/* tty_deferred_ldisc_switch(N_TTY);
 
@@ -785,8 +812,13 @@ void do_tty_hangup(void *data)
 	} else if (tty->driver->hangup)
 		(tty->driver->hangup)(tty);
 
-	if(ld)
-	{
+	/* We don't want to have driver/ldisc interactions beyond
+	   the ones we did here. The driver layer expects no
+	   calls after ->hangup() from the ldisc side. However we
+	   can't yet guarantee all that */
+
+	set_bit(TTY_HUPPED, &tty->flags);
+	if (ld) {
 		set_bit(TTY_LDISC, &tty->flags);
 		tty_ldisc_deref(ld);
 	}
@@ -895,8 +927,6 @@ EXPORT_SYMBOL(stop_tty);
 
 void start_tty(struct tty_struct *tty)
 {
-	struct tty_ldisc *ld;
-
 	if (!tty->stopped || tty->flow_stopped)
 		return;
 	tty->stopped = 0;
@@ -909,11 +939,7 @@ void start_tty(struct tty_struct *tty)
 		(tty->driver->start)(tty);
 
 	/* If we have a running line discipline it may need kicking */
-	ld = tty_ldisc_ref(tty);
-	if (ld && (test_bit(TTY_DO_WRITE_WAKEUP, &tty->flags)) &&
-	    ld->write_wakeup)
-		(ld->write_wakeup)(tty);
-	tty_ldisc_deref(ld);
+	tty_wakeup(tty);
 	wake_up_interruptible(&tty->write_wait);
 }
 
@@ -1367,6 +1393,7 @@ static void release_dev(struct file * fi
 	int	devpts_master, devpts;
 	int	idx;
 	char	buf[64];
+	unsigned long flags;
 	
 	tty = (struct tty_struct *)filp->private_data;
 	if (tty_paranoia_check(tty, filp->f_dentry->d_inode, "release_dev"))
@@ -1582,6 +1609,19 @@ static void release_dev(struct file * fi
 	flush_scheduled_work();
 
 	/*
+	 * Wait for any short term users (we know they are just driver
+	 * side waiters as the file is closing so user count on the file
+	 * side is zero.
+	 */
+	spin_lock_irqsave(&tty_ldisc_lock, flags);
+	while(tty->ldisc.refcount)
+	{
+		spin_unlock_irqrestore(&tty_ldisc_lock, flags);
+		wait_event(tty_ldisc_wait, tty->ldisc.refcount == 0);
+		spin_lock_irqsave(&tty_ldisc_lock, flags);
+	}
+	spin_unlock_irqrestore(&tty_ldisc_lock, flags);
+	/*
 	 * Shutdown the current line discipline, and reset it to N_TTY.
 	 * N.B. why reset ldisc when we're releasing the memory??
 	 *
@@ -1595,7 +1635,7 @@ static void release_dev(struct file * fi
 	 *	Switch the line discipline back
 	 */
 	tty_ldisc_assign(tty, tty_ldisc_get(N_TTY));
-	tty->termios->c_line = N_TTY;
+	tty_set_termios_ldisc(tty,N_TTY);
 	if (o_tty) {
 		/* FIXME: could o_tty be in setldisc here ? */
 		clear_bit(TTY_LDISC, &o_tty->flags);
@@ -1603,8 +1643,8 @@ static void release_dev(struct file * fi
 			(o_tty->ldisc.close)(o_tty);
 		tty_ldisc_put(o_tty->ldisc.num);
 		tty_ldisc_assign(o_tty, tty_ldisc_get(N_TTY));
+		tty_set_termios_ldisc(o_tty,N_TTY);
 	}
-
 	/*
 	 * The release_mem function takes care of the details of clearing
 	 * the slots and preserving the termios structure.
@@ -1858,12 +1898,8 @@ static int tiocsti(struct tty_struct *tt
 	if (get_user(ch, p))
 		return -EFAULT;
 	ld = tty_ldisc_ref_wait(tty);
-	/* FIXME: suppose this is NOFLIP ?? */
-	if(ld)
-	{
-		ld->receive_buf(tty, &ch, &mbz, 1);
-		tty_ldisc_deref(ld);
-	}
+	ld->receive_buf(tty, &ch, &mbz, 1);
+	tty_ldisc_deref(ld);
 	return 0;
 }
 
@@ -2441,7 +2477,11 @@ static int n_baud_table = ARRAY_SIZE(bau
 
 int tty_termios_baud_rate(struct termios *termios)
 {
-	unsigned int cbaud = termios->c_cflag & CBAUD;
+	unsigned int cbaud;
+	unsigned long flags;
+
+	spin_lock_irqsave(&tty_termios_lock, flags);
+	cbaud = termios->c_cflag & CBAUD;
 
 	if (cbaud & CBAUDEX) {
 		cbaud &= ~CBAUDEX;
@@ -2451,7 +2491,7 @@ int tty_termios_baud_rate(struct termios
 		else
 			cbaud += 15;
 	}
-
+	spin_unlock_irqrestore(&tty_termios_lock, flags);
 	return baud_table[cbaud];
 }
 
diff -puN drivers/char/tty_ioctl.c~tty-driver-take-4-try-2 drivers/char/tty_ioctl.c
--- 25/drivers/char/tty_ioctl.c~tty-driver-take-4-try-2	Thu Sep 23 17:16:31 2004
+++ 25-akpm/drivers/char/tty_ioctl.c	Thu Sep 23 17:16:31 2004
@@ -29,6 +29,8 @@
 
 #undef	DEBUG
 
+extern spinlock_t tty_termios_lock;
+
 /*
  * Internal flag options for termios setting behavior
  */
@@ -99,8 +101,17 @@ static void change_termios(struct tty_st
 	int canon_change;
 	struct termios old_termios = *tty->termios;
 	struct tty_ldisc *ld;
+	unsigned long flags;
+
+	/*
+	 *	Perform the actual termios internal changes under lock.
+	 */
+
+
+	/* FIXME: we need to decide on some locking/ordering semantics
+	   for the set_termios notification eventually */
+	spin_lock_irqsave(&tty_termios_lock, flags);
 
-	local_irq_disable(); // FIXME: is this safe?
 	*tty->termios = *new_termios;
 	unset_locked_termios(tty->termios, &old_termios, tty->termios_locked);
 	canon_change = (old_termios.c_lflag ^ tty->termios->c_lflag) & ICANON;
@@ -110,12 +121,13 @@ static void change_termios(struct tty_st
 		tty->canon_data = 0;
 		tty->erasing = 0;
 	}
-	local_irq_enable(); // FIXME: is this safe?
+
+
 	if (canon_change && !L_ICANON(tty) && tty->read_cnt)
 		/* Get characters left over from canonical mode. */
 		wake_up_interruptible(&tty->read_wait);
 
-	/* see if packet mode change of state */
+	/* See if packet mode change of state. */
 
 	if (tty->link && tty->link->packet) {
 		int old_flow = ((old_termios.c_iflag & IXON) &&
@@ -138,9 +150,12 @@ static void change_termios(struct tty_st
 		(*tty->driver->set_termios)(tty, &old_termios);
 
 	ld = tty_ldisc_ref(tty);
-	if (ld->set_termios)
-		(ld->set_termios)(tty, &old_termios);
-	tty_ldisc_deref(ld);
+	if (ld != NULL) {
+		if (ld->set_termios)
+			(ld->set_termios)(tty, &old_termios);
+		tty_ldisc_deref(ld);
+	}
+	spin_unlock_irqrestore(&tty_termios_lock, flags);
 }
 
 static int set_termios(struct tty_struct * tty, void __user *arg, int opt)
@@ -234,12 +249,16 @@ static int get_sgflags(struct tty_struct
 static int get_sgttyb(struct tty_struct * tty, struct sgttyb __user * sgttyb)
 {
 	struct sgttyb tmp;
+	unsigned long flags;
 
+	spin_lock_irqsave(&tty_termios_lock, flags);
 	tmp.sg_ispeed = 0;
 	tmp.sg_ospeed = 0;
 	tmp.sg_erase = tty->termios->c_cc[VERASE];
 	tmp.sg_kill = tty->termios->c_cc[VKILL];
 	tmp.sg_flags = get_sgflags(tty);
+	spin_unlock_irqrestore(&tty_termios_lock, flags);
+
 	return copy_to_user(sgttyb, &tmp, sizeof(tmp)) ? -EFAULT : 0;
 }
 
@@ -278,12 +297,16 @@ static int set_sgttyb(struct tty_struct 
 	retval = tty_check_change(tty);
 	if (retval)
 		return retval;
-	termios =  *tty->termios;
+
 	if (copy_from_user(&tmp, sgttyb, sizeof(tmp)))
 		return -EFAULT;
+
+	spin_lock_irqsave(&tty_termios_lock, flags);
+	termios =  *tty->termios;
 	termios.c_cc[VERASE] = tmp.sg_erase;
 	termios.c_cc[VKILL] = tmp.sg_kill;
 	set_sgflags(&termios, tmp.sg_flags);
+	spin_unlock_irqrestore(&tty_termios_lock, flags);
 	change_termios(tty, &termios);
 	return 0;
 }
@@ -375,6 +398,7 @@ int n_tty_ioctl(struct tty_struct * tty,
 	void __user *p = (void __user *)arg;
 	int retval;
 	struct tty_ldisc *ld;
+	unsigned long flags;
 
 	if (tty->driver->type == TTY_DRIVER_TYPE_PTY &&
 	    tty->driver->subtype == PTY_TYPE_MASTER)
@@ -518,9 +542,11 @@ int n_tty_ioctl(struct tty_struct * tty,
 		case TIOCSSOFTCAR:
 			if (get_user(arg, (unsigned int __user *) arg))
 				return -EFAULT;
+			spin_lock_irqsave(&tty_termios_lock, flags);
 			tty->termios->c_cflag =
 				((tty->termios->c_cflag & ~CLOCAL) |
 				 (arg ? CLOCAL : 0));
+			spin_unlock_irqrestore(&tty_termios_lock, flags);
 			return 0;
 		default:
 			return -ENOIOCTLCMD;
diff -puN drivers/usb/serial/ir-usb.c~tty-driver-take-4-try-2 drivers/usb/serial/ir-usb.c
--- 25/drivers/usb/serial/ir-usb.c~tty-driver-take-4-try-2	Thu Sep 23 17:16:31 2004
+++ 25-akpm/drivers/usb/serial/ir-usb.c	Thu Sep 23 17:16:31 2004
@@ -449,6 +449,10 @@ static void ir_read_bulk_callback (struc
 			 */
 			tty = port->tty;
 
+			/*
+			 *	FIXME: must not do this in IRQ context,
+			 *	must honour TTY_DONT_FLIP
+			 */
 			tty->ldisc.receive_buf(
 				tty,
 				data+1,
diff -puN include/linux/tty.h~tty-driver-take-4-try-2 include/linux/tty.h
--- 25/include/linux/tty.h~tty-driver-take-4-try-2	Thu Sep 23 17:16:31 2004
+++ 25-akpm/include/linux/tty.h	Thu Sep 23 17:16:31 2004
@@ -320,6 +320,7 @@ struct tty_struct {
 #define TTY_HW_COOK_IN 		15	/* Hardware can do input cooking */
 #define TTY_PTY_LOCK 		16	/* pty private */
 #define TTY_NO_WRITE_SPLIT 	17	/* Preserve write boundaries to driver */
+#define TTY_HUPPED 		18	/* Post driver->hangup() */
 
 #define TTY_WRITE_FLUSH(tty) tty_write_flush((tty))
 
diff -puN include/linux/tty_ldisc.h~tty-driver-take-4-try-2 include/linux/tty_ldisc.h
--- 25/include/linux/tty_ldisc.h~tty-driver-take-4-try-2	Thu Sep 23 17:16:31 2004
+++ 25-akpm/include/linux/tty_ldisc.h	Thu Sep 23 17:16:31 2004
@@ -95,6 +95,13 @@
  * 	that line discpline should try to send more characters to the
  * 	low-level driver for transmission.  If the line discpline does
  * 	not have any more data to send, it can just return.
+ *
+ * int (*hangup)(struct tty_struct *)
+ *
+ *	Called on a hangup. Tells the discipline that it should
+ *	cease I/O to the tty driver. Can sleep. The driver should
+ *	seek to perform this action quickly but should wait until
+ *	any pending driver I/O is completed.
  */
 
 #include <linux/fs.h>
@@ -122,6 +129,7 @@ struct tty_ldisc {
 	void	(*set_termios)(struct tty_struct *tty, struct termios * old);
 	unsigned int (*poll)(struct tty_struct *, struct file *,
 			     struct poll_table_struct *);
+	int	(*hangup)(struct tty_struct *tty);
 	
 	/*
 	 * The following routines are called from below.
_