Some tty patches

Theodore Y. Ts'o (tytso@MIT.EDU)
Sat, 21 Jun 1997 02:11:42 -0400


Here are some tty patches against version 2.1.43; it contains Bill Hawes
N_TTY line discpline fix which prevents a deadlock situation in telnetd,
as well as his fix to prevent race conditions in the tty init_dev and
release_dev code. It also has Aaron's fast console patches, although
I've rewritten the opost_block to be a bit more efficient. And finally,
it has Jaakko's proposed enhancement to the serial driver to support
BSD-style break ioctl's.

Note that Bill Hawes's changes to tty_io.c fixes are important, and
probably should be back-ported to the 2.0 kernel. I haven't had a
chance to do that yet, though.

- Ted

P.S. I will be going on vacation for a week starting tomorrow, and will
be only sporadically read e-mail (if that).

Patch generated: on Sat Jun 21 01:10:52 EDT 1997 by tytso@rsts-11
against Linux version 2.1.43

===================================================================
RCS file: drivers/char/RCS/ChangeLog,v
retrieving revision 1.1
diff -u -r1.1 drivers/char/ChangeLog
--- drivers/char/ChangeLog 1997/06/18 19:25:45 1.1
+++ drivers/char/ChangeLog 1997/06/21 05:10:15
@@ -1,3 +1,27 @@
+Thu Jun 19 20:05:58 1997 Theodore Ts'o <tytso@rsts-11.mit.edu>
+
+ * serial.c (begin_break, end_break, rs_ioctl): Applied patch
+ to support BSD ioctls to set and clear the break
+ condition explicitly.
+
+ * console.c (scrup, scrdown, insert_line, delete_line): Applied
+ fix suggested by Aaron Tiensivu to speed up block scrolls
+ up and down.
+
+ * n_tty.c (opost_block, write_chan): Added a modified "fast
+ console" patch which processes a block of text via
+ "cooking" efficiently.
+
+Wed Jun 18 15:25:50 1997 Theodore Ts'o <tytso@rsts-11.mit.edu>
+
+ * tty_io.c (init_dev, release_dev): Applied fix suggested by Bill
+ Hawes to prevent race conditions in the tty code.
+
+ * n_tty.c (n_tty_chars_in_buffer): Applied fix suggested by Bill
+ Hawes so that n_tty_chars_in_buffer returns the correct
+ value in the case when the tty is in cannonical mode. (To
+ avoid a pty deadlock with telnetd.)
+
Thu Feb 27 01:53:08 1997 Theodore Ts'o <tytso@rsts-11.mit.edu>

* serial.c (change_speed): Add support for the termios flag
===================================================================
RCS file: drivers/char/RCS/n_tty.c,v
retrieving revision 1.1
diff -u -r1.1 drivers/char/n_tty.c
--- drivers/char/n_tty.c 1997/06/18 19:22:36 1.1
+++ drivers/char/n_tty.c 1997/06/19 20:29:36
@@ -88,9 +88,17 @@

/*
* Return number of characters buffered to be delivered to user
+ *
*/
int n_tty_chars_in_buffer(struct tty_struct *tty)
{
+ if (tty->icanon) {
+ if (!tty->canon_data) return 0;
+
+ return (tty->canon_head > tty->read_tail) ?
+ tty->canon_head - tty->read_tail :
+ tty->canon_head + (N_TTY_BUF_SIZE - tty->read_tail);
+ }
return tty->read_cnt;
}

@@ -157,6 +165,72 @@
return 0;
}

+/*
+ * opost_block --- to speed up block console writes, among other
+ * things.
+ */
+static int opost_block(struct tty_struct * tty,
+ const unsigned char * inbuf, unsigned int nr)
+{
+ char buf[80];
+ int space;
+ int i;
+ char *cp;
+
+ space = tty->driver.write_room(tty);
+ if (!space)
+ return 0;
+ if (nr > space)
+ nr = space;
+ if (nr > sizeof(buf))
+ nr = sizeof(buf);
+ nr -= copy_from_user(buf, inbuf, nr);
+ if (!nr)
+ return 0;
+
+ for (i = 0, cp = buf; i < nr; i++, cp++) {
+ switch (*cp) {
+ case '\n':
+ if (O_ONLRET(tty))
+ tty->column = 0;
+ if (O_ONLCR(tty))
+ goto break_out;
+ tty->canon_column = tty->column;
+ break;
+ case '\r':
+ if (O_ONOCR(tty) && tty->column == 0)
+ goto break_out;
+ if (O_OCRNL(tty)) {
+ *cp = '\n';
+ if (O_ONLRET(tty))
+ tty->canon_column = tty->column = 0;
+ break;
+ }
+ tty->canon_column = tty->column = 0;
+ break;
+ case '\t':
+ goto break_out;
+ case '\b':
+ if (tty->column > 0)
+ tty->column--;
+ break;
+ default:
+ if (O_OLCUC(tty))
+ *cp = toupper(*cp);
+ if (!iscntrl(*cp))
+ tty->column++;
+ break;
+ }
+ }
+break_out:
+ if (tty->driver.flush_chars)
+ tty->driver.flush_chars(tty);
+ i = tty->driver.write(tty, 0, buf, i);
+ return i;
+}
+
+
+
static inline void put_char(unsigned char c, struct tty_struct *tty)
{
tty->driver.put_char(tty, c);
@@ -934,7 +1008,7 @@
const unsigned char * buf, unsigned int nr)
{
struct wait_queue wait = { current, NULL };
- int c;
+ int c, num;
const unsigned char *b = buf;
int retval = 0;

@@ -958,6 +1032,11 @@
}
if (O_OPOST(tty) && !(tty->flags & (1<<TTY_HW_COOK_OUT))) {
while (nr > 0) {
+ num = opost_block(tty, b, nr);
+ b += num;
+ nr -= num;
+ if (nr == 0)
+ break;
get_user(c, b);
if (opost(c, tty) < 0)
break;
===================================================================
RCS file: drivers/char/RCS/tty_io.c,v
retrieving revision 1.1
diff -u -r1.1 drivers/char/tty_io.c
--- drivers/char/tty_io.c 1997/06/18 19:22:41 1.1
+++ drivers/char/tty_io.c 1997/06/18 19:53:24
@@ -45,9 +45,12 @@
* Restrict vt switching via ioctl()
* -- grif@cs.ucr.edu, 5-Dec-95
*
- * Move console and virtual terminal code to more apropriate files,
+ * Move console and virtual terminal code to more appropriate files,
* implement CONFIG_VT and generalize console device interface.
* -- Marko Kohtala <Marko.Kohtala@hut.fi>, March 97
+ *
+ * Rewrote init_dev and release_dev to eliminate races.
+ * -- Bill Hawes <whawes@star.net>, June 97
*/

#include <linux/config.h>
@@ -90,8 +93,8 @@

#undef TTY_DEBUG_HANGUP

-#define TTY_PARANOIA_CHECK
-#define CHECK_TTY_COUNT
+#define TTY_PARANOIA_CHECK 1
+#define CHECK_TTY_COUNT 1

struct termios tty_std_termios; /* for the benefit of tty drivers */
struct tty_driver *tty_drivers = NULL; /* linked list of tty drivers */
@@ -651,18 +654,31 @@
(unsigned int)count);
}

+/* Semaphore to protect creating and releasing a tty */
+static struct semaphore tty_sem = MUTEX;
+static void down_tty_sem(int index)
+{
+ down(&tty_sem);
+}
+static void up_tty_sem(int index)
+{
+ up(&tty_sem);
+}
+static void release_mem(struct tty_struct *tty, int idx);
+
/*
- * This is so ripe with races that you should *really* not touch this
- * unless you know exactly what you are doing. All the changes have to be
- * made atomically, or there may be incorrect pointers all over the place.
+ * WSH 06/09/97: Rewritten to remove races and properly clean up after a
+ * failed open. The new code protects the open with a semaphore, so it's
+ * really quite straightforward. The semaphore locking can probably be
+ * relaxed for the (most common) case of reopening a tty.
*/
static int init_dev(kdev_t device, struct tty_struct **ret_tty)
{
- struct tty_struct *tty, **tty_loc, *o_tty, **o_tty_loc;
+ struct tty_struct *tty, *o_tty;
struct termios *tp, **tp_loc, *o_tp, **o_tp_loc;
struct termios *ltp, **ltp_loc, *o_ltp, **o_ltp_loc;
struct tty_driver *driver;
- int retval;
+ int retval=0;
int idx;

driver = get_tty_driver(device);
@@ -670,175 +686,236 @@
return -ENODEV;

idx = MINOR(device) - driver->minor_start;
- tty = o_tty = NULL;
+ tty = driver->table[idx];
+
+ /*
+ * Check whether we need to acquire the tty semaphore to avoid
+ * race conditions. For now, play it safe.
+ */
+ down_tty_sem(idx);
+
+ /* check whether we're reopening an existing tty */
+ if(tty) goto fast_track;
+
+ /*
+ * First time open is complex, especially for PTY devices.
+ * This code guarantees that either everything succeeds and the
+ * TTY is ready for operation, or else the table slots are vacated
+ * and the allocated memory released. (Except that the termios
+ * and locked termios may be retained.)
+ */
+
+ o_tty = NULL;
tp = o_tp = NULL;
ltp = o_ltp = NULL;
- o_tty_loc = NULL;
- o_tp_loc = o_ltp_loc = NULL;

- tty_loc = &driver->table[idx];
- tp_loc = &driver->termios[idx];
- ltp_loc = &driver->termios_locked[idx];
+ tty = (struct tty_struct*) get_free_page(GFP_KERNEL);
+ if(!tty)
+ goto fail_no_mem;
+ initialize_tty_struct(tty);
+ tty->device = device;
+ tty->driver = *driver;

-repeat:
- retval = -EIO;
- if (driver->type == TTY_DRIVER_TYPE_PTY &&
- driver->subtype == PTY_TYPE_MASTER &&
- *tty_loc && (*tty_loc)->count)
- goto end_init;
- retval = -ENOMEM;
- if (!*tty_loc && !tty) {
- if (!(tty = (struct tty_struct*) get_free_page(GFP_KERNEL)))
- goto end_init;
- initialize_tty_struct(tty);
- tty->device = device;
- tty->driver = *driver;
- goto repeat;
- }
- if (!*tp_loc && !tp) {
+ tp_loc = &driver->termios[idx];
+ if (!*tp_loc) {
tp = (struct termios *) kmalloc(sizeof(struct termios),
GFP_KERNEL);
if (!tp)
- goto end_init;
+ goto free_mem_out;
*tp = driver->init_termios;
- goto repeat;
}
- if (!*ltp_loc && !ltp) {
+
+ ltp_loc = &driver->termios_locked[idx];
+ if (!*ltp_loc) {
ltp = (struct termios *) kmalloc(sizeof(struct termios),
GFP_KERNEL);
if (!ltp)
- goto end_init;
+ goto free_mem_out;
memset(ltp, 0, sizeof(struct termios));
- goto repeat;
}
+
if (driver->type == TTY_DRIVER_TYPE_PTY) {
- o_tty_loc = &driver->other->table[idx];
- o_tp_loc = &driver->other->termios[idx];
- o_ltp_loc = &driver->other->termios_locked[idx];
+ o_tty = (struct tty_struct *) get_free_page(GFP_KERNEL);
+ if (!o_tty)
+ goto free_mem_out;
+ initialize_tty_struct(o_tty);
+ o_tty->device = (kdev_t) MKDEV(driver->other->major,
+ driver->other->minor_start + idx);
+ o_tty->driver = *driver->other;

- if (!*o_tty_loc && !o_tty) {
- kdev_t o_device;
-
- o_tty = (struct tty_struct *)
- get_free_page(GFP_KERNEL);
- if (!o_tty)
- goto end_init;
- o_device = MKDEV(driver->other->major,
- driver->other->minor_start + idx);
- initialize_tty_struct(o_tty);
- o_tty->device = o_device;
- o_tty->driver = *driver->other;
- goto repeat;
- }
- if (!*o_tp_loc && !o_tp) {
+ o_tp_loc = &driver->other->termios[idx];
+ if (!*o_tp_loc) {
o_tp = (struct termios *)
kmalloc(sizeof(struct termios), GFP_KERNEL);
if (!o_tp)
- goto end_init;
+ goto free_mem_out;
*o_tp = driver->other->init_termios;
- goto repeat;
}
- if (!*o_ltp_loc && !o_ltp) {
+
+ o_ltp_loc = &driver->other->termios_locked[idx];
+ if (!*o_ltp_loc) {
o_ltp = (struct termios *)
kmalloc(sizeof(struct termios), GFP_KERNEL);
if (!o_ltp)
- goto end_init;
+ goto free_mem_out;
memset(o_ltp, 0, sizeof(struct termios));
- goto repeat;
}
-
+
+ /*
+ * Everything allocated ... set up the o_tty structure.
+ */
+ driver->other->table[idx] = o_tty;
+ if (!*o_tp_loc)
+ *o_tp_loc = o_tp;
+ if (!*o_ltp_loc)
+ *o_ltp_loc = o_ltp;
+ o_tty->termios = *o_tp_loc;
+ o_tty->termios_locked = *o_ltp_loc;
+ (*driver->other->refcount)++;
+ if (driver->subtype == PTY_TYPE_MASTER)
+ o_tty->count++;
+
+ /* Establish the links in both directions */
+ tty->link = o_tty;
+ o_tty->link = tty;
}
- /* Now we have allocated all the structures: update all the pointers.. */
- if (!*tp_loc) {
+
+ /*
+ * All structures have been allocated, so now we install them.
+ * Failures after this point use release_mem to clean up, so
+ * there's no need to null out the local pointers.
+ */
+ driver->table[idx] = tty;
+ if (!*tp_loc)
*tp_loc = tp;
- tp = NULL;
- }
- if (!*ltp_loc) {
+ if (!*ltp_loc)
*ltp_loc = ltp;
- ltp = NULL;
+ tty->termios = *tp_loc;
+ tty->termios_locked = *ltp_loc;
+ (*driver->refcount)++;
+ tty->count++;
+
+ /*
+ * Structures all installed ... call the ldisc open routines.
+ * If we fail here just call release_mem to clean up. No need
+ * to decrement the use counts, as release_mem doesn't care.
+ */
+ if (tty->ldisc.open) {
+ retval = (tty->ldisc.open)(tty);
+ if (retval)
+ goto release_mem_out;
}
- if (!*tty_loc) {
- tty->termios = *tp_loc;
- tty->termios_locked = *ltp_loc;
- *tty_loc = tty;
- (*driver->refcount)++;
- (*tty_loc)->count++;
- if (tty->ldisc.open) {
- retval = (tty->ldisc.open)(tty);
- if (retval < 0) {
- (*tty_loc)->count--;
- tty = NULL;
- goto end_init;
- }
- }
- tty = NULL;
- } else {
- if ((*tty_loc)->flags & (1 << TTY_CLOSING)) {
- printk("Attempt to open closing tty %s.\n",
- tty_name(*tty_loc));
- printk("Ack!!!! This should never happen!!\n");
- return -EINVAL;
+ if (o_tty && o_tty->ldisc.open) {
+ retval = (o_tty->ldisc.open)(o_tty);
+ if (retval) {
+ if (tty->ldisc.close)
+ (tty->ldisc.close)(tty);
+ goto release_mem_out;
}
- (*tty_loc)->count++;
}
- if (driver->type == TTY_DRIVER_TYPE_PTY) {
- if (!*o_tp_loc) {
- *o_tp_loc = o_tp;
- o_tp = NULL;
- }
- if (!*o_ltp_loc) {
- *o_ltp_loc = o_ltp;
- o_ltp = NULL;
- }
- if (!*o_tty_loc) {
- o_tty->termios = *o_tp_loc;
- o_tty->termios_locked = *o_ltp_loc;
- *o_tty_loc = o_tty;
- (*driver->other->refcount)++;
- if (o_tty->ldisc.open) {
- retval = (o_tty->ldisc.open)(o_tty);
- if (retval < 0) {
- (*tty_loc)->count--;
- o_tty = NULL;
- goto end_init;
- }
- }
- o_tty = NULL;
+ goto success;
+
+ /*
+ * This fast open can be used if the tty is already open.
+ * No memory is allocated, and the only failures are from
+ * attempting to open a closing tty or attempting multiple
+ * opens on a pty master.
+ */
+fast_track:
+ if (tty->flags & (1 << TTY_CLOSING)) {
+ retval = -EIO;
+ goto end_init;
+ }
+ if (driver->type == TTY_DRIVER_TYPE_PTY &&
+ driver->subtype == PTY_TYPE_MASTER) {
+ /*
+ * special case for PTY masters: only one open permitted,
+ * and the slave side open count is incremented as well.
+ */
+ if (tty->count) {
+ retval = -EIO;
+ goto end_init;
}
- (*tty_loc)->link = *o_tty_loc;
- (*o_tty_loc)->link = *tty_loc;
- if (driver->subtype == PTY_TYPE_MASTER)
- (*o_tty_loc)->count++;
+ tty->link->count++;
}
- (*tty_loc)->driver = *driver;
- *ret_tty = *tty_loc;
- retval = 0;
+ tty->count++;
+ tty->driver = *driver; /* N.B. why do this every time?? */
+
+success:
+ *ret_tty = tty;
+
+ /* All paths come through here to release the semaphore */
end_init:
- if (tty)
- free_page((unsigned long) tty);
- if (o_tty)
- free_page((unsigned long) o_tty);
- if (tp)
- kfree_s(tp, sizeof(struct termios));
+ up_tty_sem(idx);
+ return retval;
+
+ /* Release locally allocated memory ... nothing placed in slots */
+free_mem_out:
if (o_tp)
kfree_s(o_tp, sizeof(struct termios));
+ if (o_tty)
+ free_page((unsigned long) o_tty);
if (ltp)
kfree_s(ltp, sizeof(struct termios));
- if (o_ltp)
- kfree_s(o_ltp, sizeof(struct termios));
- return retval;
+ if (tp)
+ kfree_s(tp, sizeof(struct termios));
+ free_page((unsigned long) tty);
+
+fail_no_mem:
+ retval = -ENOMEM;
+ goto end_init;
+
+ /* call the tty release_mem routine to clean out this slot */
+release_mem_out:
+ printk("init_dev: ldisc open failed, clearing slot %d\n", idx);
+ release_mem(tty, idx);
+ goto end_init;
+}
+
+/*
+ * Releases memory associated with a tty structure, and clears out the
+ * driver table slots.
+ */
+static void release_mem(struct tty_struct *tty, int idx)
+{
+ struct tty_struct *o_tty;
+ struct termios *tp;
+
+ if ((o_tty = tty->link) != NULL) {
+ o_tty->driver.table[idx] = NULL;
+ if (o_tty->driver.flags & TTY_DRIVER_RESET_TERMIOS) {
+ tp = o_tty->driver.termios[idx];
+ o_tty->driver.termios[idx] = NULL;
+ kfree_s(tp, sizeof(struct termios));
+ }
+ o_tty->magic = 0;
+ (*o_tty->driver.refcount)--;
+ free_page((unsigned long) o_tty);
+ }
+
+ tty->driver.table[idx] = NULL;
+ if (tty->driver.flags & TTY_DRIVER_RESET_TERMIOS) {
+ tp = tty->driver.termios[idx];
+ tty->driver.termios[idx] = NULL;
+ kfree_s(tp, sizeof(struct termios));
+ }
+ tty->magic = 0;
+ (*tty->driver.refcount)--;
+ free_page((unsigned long) tty);
}

/*
* Even releasing the tty structures is a tricky business.. We have
* to be very careful that the structures are all released at the
* same time, as interrupts might otherwise get the wrong pointers.
+ *
+ * WSH 09/09/97: rewritten to avoid some nasty race conditions that could
+ * lead to double frees or releasing memory still in use.
*/
static void release_dev(struct file * filp)
{
struct tty_struct *tty, *o_tty;
- struct termios *tp, *o_tp, *ltp, *o_ltp;
- struct task_struct *p;
+ int pty_master, tty_closing, o_tty_closing, do_sleep;
int idx;

tty = (struct tty_struct *)filp->private_data;
@@ -849,10 +926,11 @@

tty_fasync(filp->f_inode, filp, 0);

- tp = tty->termios;
- ltp = tty->termios_locked;
-
idx = MINOR(tty->device) - tty->driver.minor_start;
+ pty_master = (tty->driver.type == TTY_DRIVER_TYPE_PTY &&
+ tty->driver.subtype == PTY_TYPE_MASTER);
+ o_tty = tty->link;
+
#ifdef TTY_PARANOIA_CHECK
if (idx < 0 || idx >= tty->driver.num) {
printk("release_dev: bad idx when trying to free (%s)\n",
@@ -864,15 +942,15 @@
idx, kdevname(tty->device));
return;
}
- if (tp != tty->driver.termios[idx]) {
- printk("release_dev: driver.termios[%d] not termios for ("
- "%s)\n",
+ if (tty->termios != tty->driver.termios[idx]) {
+ printk("release_dev: driver.termios[%d] not termios "
+ "for (%s)\n",
idx, kdevname(tty->device));
return;
}
- if (ltp != tty->driver.termios_locked[idx]) {
- printk("release_dev: driver.termios_locked[%d] not termios_locked for ("
- "%s)\n",
+ if (tty->termios_locked != tty->driver.termios_locked[idx]) {
+ printk("release_dev: driver.termios_locked[%d] not "
+ "termios_locked for (%s)\n",
idx, kdevname(tty->device));
return;
}
@@ -883,10 +961,6 @@
tty->count);
#endif

- o_tty = tty->link;
- o_tp = (o_tty) ? o_tty->termios : NULL;
- o_ltp = (o_tty) ? o_tty->termios_locked : NULL;
-
#ifdef TTY_PARANOIA_CHECK
if (tty->driver.other) {
if (o_tty != tty->driver.other->table[idx]) {
@@ -895,34 +969,90 @@
idx, kdevname(tty->device));
return;
}
- if (o_tp != tty->driver.other->termios[idx]) {
- printk("release_dev: other->termios[%d] not o_termios for ("
- "%s)\n",
+ if (o_tty->termios != tty->driver.other->termios[idx]) {
+ printk("release_dev: other->termios[%d] not o_termios "
+ "for (%s)\n",
idx, kdevname(tty->device));
return;
}
- if (o_ltp != tty->driver.other->termios_locked[idx]) {
- printk("release_dev: other->termios_locked[%d] not o_termios_locked for ("
- "%s)\n",
+ if (o_tty->termios_locked !=
+ tty->driver.other->termios_locked[idx]) {
+ printk("release_dev: other->termios_locked[%d] not "
+ "o_termios_locked for (%s)\n",
idx, kdevname(tty->device));
return;
}
-
if (o_tty->link != tty) {
printk("release_dev: bad pty pointers\n");
return;
}
}
#endif
-
+ /*
+ * Sanity check: if tty->count is going to zero, there shouldn't be
+ * any waiters on tty->read_wait or tty->write_wait. We test the
+ * wait queues and kick everyone out _before_ actually starting to
+ * close. This ensures that we won't block while releasing the tty
+ * structure.
+ *
+ * The test for the o_tty closing is necessary, since the master and
+ * slave sides may close in any order. If the slave side closes out
+ * first, its count will be one, since the master side holds an open.
+ * Thus this test wouldn't be triggered at the time the slave closes,
+ * so we do it now.
+ *
+ * Note that it's possible for the tty to be opened again while we're
+ * flushing out waiters. By recalculating the closing flags before
+ * each iteration we avoid any problems.
+ */
+ while (1) {
+ tty_closing = tty->count <= 1;
+ o_tty_closing = o_tty &&
+ (o_tty->count <= (pty_master ? 1 : 0));
+ do_sleep = 0;
+
+ if (tty_closing) {
+ if (waitqueue_active(&tty->read_wait)) {
+ wake_up(&tty->read_wait);
+ do_sleep++;
+ }
+ if (waitqueue_active(&tty->write_wait)) {
+ wake_up(&tty->write_wait);
+ do_sleep++;
+ }
+ }
+ if (o_tty_closing) {
+ if (waitqueue_active(&o_tty->read_wait)) {
+ wake_up(&o_tty->read_wait);
+ do_sleep++;
+ }
+ if (waitqueue_active(&o_tty->write_wait)) {
+ wake_up(&o_tty->write_wait);
+ do_sleep++;
+ }
+ }
+ if (!do_sleep)
+ break;
+
+ printk("release_dev: %s: read/write wait queue active!\n",
+ tty_name(tty));
+ schedule();
+ }
+
+ /*
+ * The closing flags are now consistent with the open counts on
+ * both sides, and we've completed the last operation that could
+ * block, so it's safe to proceed with closing.
+ */
+
if (tty->driver.close)
tty->driver.close(tty, filp);
- if (tty->driver.type == TTY_DRIVER_TYPE_PTY &&
- tty->driver.subtype == PTY_TYPE_MASTER) {
- if (--tty->link->count < 0) {
+
+ if (pty_master) {
+ if (--o_tty->count < 0) {
printk("release_dev: bad pty slave count (%d) for %s\n",
- tty->count, tty_name(tty));
- tty->link->count = 0;
+ o_tty->count, tty_name(o_tty));
+ o_tty->count = 0;
}
}
if (--tty->count < 0) {
@@ -930,60 +1060,54 @@
tty->count, tty_name(tty));
tty->count = 0;
}
- if (tty->count)
- return;

/*
- * Sanity check --- if tty->count is zero, there shouldn't be
- * any waiters on tty->read_wait or tty->write_wait. But just
- * in case....
+ * Perform some housekeeping before deciding whether to return.
+ *
+ * Set the TTY_CLOSING flag if this was the last open. In the
+ * case of a pty we may have to wait around for the other side
+ * to close, and TTY_CLOSING makes sure we can't be reopened.
*/
- while (1) {
- if (waitqueue_active(&tty->read_wait)) {
- printk("release_dev: %s: read_wait active?!?\n",
- tty_name(tty));
- wake_up(&tty->read_wait);
- } else if (waitqueue_active(&tty->write_wait)) {
- printk("release_dev: %s: write_wait active?!?\n",
- tty_name(tty));
- wake_up(&tty->write_wait);
- } else
- break;
- schedule();
- }
-
+ if(tty_closing)
+ tty->flags |= (1 << TTY_CLOSING);
+ if(o_tty_closing)
+ o_tty->flags |= (1 << TTY_CLOSING);
+
/*
- * We're committed; at this point, we must not block!
+ * If _either_ side is closing, make sure there aren't any
+ * processes that still think tty or o_tty is their controlling
+ * tty. Also, clear redirect if it points to either tty.
*/
- if (o_tty) {
- if (o_tty->count)
- return;
- tty->driver.other->table[idx] = NULL;
- tty->driver.other->termios[idx] = NULL;
- kfree_s(o_tp, sizeof(struct termios));
+ if (tty_closing || o_tty_closing) {
+ struct task_struct *p;
+
+ read_lock(&tasklist_lock);
+ for_each_task(p) {
+ if (p->tty == tty || (o_tty && p->tty == o_tty))
+ p->tty = NULL;
+ }
+ read_unlock(&tasklist_lock);
+
+ if (redirect == tty || (o_tty && redirect == o_tty))
+ redirect = NULL;
}
+
+ if (o_tty)
+ printk("release_dev: %s tty=%d o_tty=%d\n", tty_name(tty),
+ tty->count, o_tty->count);
+
+ /* check whether both sides are closing ... */
+ if (!tty_closing || (o_tty && !o_tty_closing))
+ return;
+ filp->private_data = 0;

#ifdef TTY_DEBUG_HANGUP
printk("freeing tty structure...");
#endif
- tty->flags |= (1 << TTY_CLOSING);
-
- /*
- * Make sure there aren't any processes that still think this
- * tty is their controlling tty.
- */
- read_lock(&tasklist_lock);
- for_each_task(p) {
- if (p->tty == tty)
- p->tty = NULL;
- if (o_tty && p->tty == o_tty)
- p->tty = NULL;
- }
- read_unlock(&tasklist_lock);

/*
- * Shutdown the current line discipline, and reset it to
- * N_TTY.
+ * Shutdown the current line discipline, and reset it to N_TTY.
+ * N.B. why reset ldisc when we're releasing the memory??
*/
if (tty->ldisc.close)
(tty->ldisc.close)(tty);
@@ -995,41 +1119,34 @@
o_tty->ldisc = ldiscs[N_TTY];
}

- tty->driver.table[idx] = NULL;
- if (tty->driver.flags & TTY_DRIVER_RESET_TERMIOS) {
- tty->driver.termios[idx] = NULL;
- kfree_s(tp, sizeof(struct termios));
- }
- if (tty == redirect || o_tty == redirect)
- redirect = NULL;
/*
* Make sure that the tty's task queue isn't activated. If it
- * is, take it out of the linked list.
+ * is, take it out of the linked list. The tqueue isn't used by
+ * pty's, so skip the test for them.
*/
- spin_lock_irq(&tqueue_lock);
- if (tty->flip.tqueue.sync) {
- struct tq_struct *tq, *prev;
-
- for (tq=tq_timer, prev=0; tq; prev=tq, tq=tq->next) {
- if (tq == &tty->flip.tqueue) {
- if (prev)
- prev->next = tq->next;
- else
- tq_timer = tq->next;
- break;
+ if (tty->driver.type != TTY_DRIVER_TYPE_PTY) {
+ spin_lock_irq(&tqueue_lock);
+ if (tty->flip.tqueue.sync) {
+ struct tq_struct *tq, *prev;
+
+ for (tq=tq_timer, prev=0; tq; prev=tq, tq=tq->next) {
+ if (tq == &tty->flip.tqueue) {
+ if (prev)
+ prev->next = tq->next;
+ else
+ tq_timer = tq->next;
+ break;
+ }
}
}
+ spin_unlock_irq(&tqueue_lock);
}
- spin_unlock_irq(&tqueue_lock);
- tty->magic = 0;
- (*tty->driver.refcount)--;
- free_page((unsigned long) tty);
- filp->private_data = 0;
- if (o_tty) {
- o_tty->magic = 0;
- (*o_tty->driver.refcount)--;
- free_page((unsigned long) o_tty);
- }
+
+ /*
+ * The release_mem function takes care of the details of clearing
+ * the slots and preserving the termios structure.
+ */
+ release_mem(tty, idx);
}

/*
@@ -1077,6 +1194,7 @@
retval = init_dev(device, &tty);
if (retval)
return retval;
+ /* N.B. this error exit may leave filp->f_flags with O_NONBLOCK set */
filp->private_data = tty;
check_tty_count(tty, "tty_open");
if (tty->driver.type == TTY_DRIVER_TYPE_PTY &&
@@ -1123,11 +1241,6 @@
return 0;
}

-/*
- * Note that releasing a pty master also releases the child, so
- * we have to make the redirection checks after that and on both
- * sides of a pty.
- */
static int tty_release(struct inode * inode, struct file * filp)
{
release_dev(filp);
===================================================================
RCS file: drivers/char/RCS/console.c,v
retrieving revision 1.1
diff -u -r1.1 drivers/char/console.c
--- drivers/char/console.c 1997/06/19 20:51:00 1.1
+++ drivers/char/console.c 1997/06/19 20:51:40
@@ -497,13 +497,15 @@
__set_origin(__real_origin);
}

-static void scrup(int currcons, unsigned int t, unsigned int b)
+static void scrup(int currcons, unsigned int t, unsigned int b, unsigned int nr)
{
int hardscroll = hardscroll_enabled;

- if (b > video_num_lines || t >= b)
+ if (t+nr >= b)
+ nr = b - t - 1;
+ if (b > video_num_lines || t >= b || nr < 1)
return;
- if (t || b != video_num_lines)
+ if (t || b != video_num_lines || nr > 1)
hardscroll = 0;
if (hardscroll) {
origin += video_size_row;
@@ -544,31 +546,35 @@
set_origin(currcons);
} else {
unsigned short * d = (unsigned short *) (origin+video_size_row*t);
- unsigned short * s = (unsigned short *) (origin+video_size_row*(t+1));
+ unsigned short * s = (unsigned short *) (origin+video_size_row*(t+nr));

- memcpyw(d, s, (b-t-1) * video_size_row);
- memsetw(d + (b-t-1) * video_num_columns, video_erase_char, video_size_row);
+ memcpyw(d, s, (b-t-nr) * video_size_row);
+ memsetw(d + (b-t-nr) * video_num_columns, video_erase_char, video_size_row*nr);
}
}

static void
-scrdown(int currcons, unsigned int t, unsigned int b)
+scrdown(int currcons, unsigned int t, unsigned int b, unsigned int nr)
{
unsigned short *s;
unsigned int count;
+ unsigned int step;

- if (b > video_num_lines || t >= b)
+ if (t+nr >= b)
+ nr = b - t - 1;
+ if (b > video_num_lines || t >= b || nr < 1)
return;
- s = (unsigned short *) (origin+video_size_row*(b-2));
- if (b >= t + 1) {
- count = b - t - 1;
- while (count) {
- count--;
- memcpyw(s + video_num_columns, s, video_size_row);
- s -= video_num_columns;
- }
+ s = (unsigned short *) (origin+video_size_row*(b-nr-1));
+ step = video_num_columns * nr;
+ count = b - t - nr;
+ while (count--) {
+ memcpyw(s + step, s, video_size_row);
+ s -= video_num_columns;
+ }
+ while (nr--) {
+ s += video_num_columns;
+ memsetw(s, video_erase_char, video_size_row);
}
- memsetw(s + video_num_columns, video_erase_char, video_size_row);
has_scrolled = 1;
}

@@ -578,7 +584,7 @@
* if below scrolling region
*/
if (y+1 == bottom)
- scrup(currcons,top,bottom);
+ scrup(currcons,top,bottom, 1);
else if (y < video_num_lines-1) {
y++;
pos += video_size_row;
@@ -592,7 +598,7 @@
* if above scrolling region
*/
if (y == top)
- scrdown(currcons,top,bottom);
+ scrdown(currcons,top,bottom,1);
else if (y > 0) {
y--;
pos -= video_size_row;
@@ -1117,9 +1123,9 @@
need_wrap = 0;
}

-static void insert_line(int currcons)
+static void insert_line(int currcons, unsigned int nr)
{
- scrdown(currcons,y,bottom);
+ scrdown(currcons,y,bottom,nr);
need_wrap = 0;
}

@@ -1136,9 +1142,9 @@
need_wrap = 0;
}

-static void delete_line(int currcons)
+static void delete_line(int currcons, unsigned int nr)
{
- scrup(currcons,y,bottom);
+ scrup(currcons,y,bottom,nr);
need_wrap = 0;
}

@@ -1158,8 +1164,7 @@
nr = video_num_lines;
else if (!nr)
nr = 1;
- while (nr--)
- insert_line(currcons);
+ insert_line(currcons, nr);
}

static void csi_P(int currcons, unsigned int nr)
@@ -1178,8 +1183,7 @@
nr = video_num_lines;
else if (!nr)
nr=1;
- while (nr--)
- delete_line(currcons);
+ delete_line(currcons, nr);
}

static void save_cur(int currcons)
===================================================================
RCS file: drivers/char/RCS/serial.c,v
retrieving revision 1.1
diff -u -r1.1 drivers/char/serial.c
--- drivers/char/serial.c 1997/06/19 21:00:33 1.1
+++ drivers/char/serial.c 1997/06/19 21:17:51
@@ -1328,6 +1328,9 @@
info->MCR |= UART_MCR_OUT1 | UART_MCR_OUT2;
#endif

+ /* disable break condition */
+ serial_out(info, UART_LCR, serial_inp(info, UART_LCR) & ~UART_LCR_SBC);
+
if (!info->tty || (info->tty->termios->c_cflag & HUPCL))
info->MCR &= ~(UART_MCR_DTR|UART_MCR_RTS);
serial_outp(info, UART_MCR, info->MCR);
@@ -1996,6 +1999,30 @@
}

/*
+ * This routine sets the break condition on the serial port.
+ */
+static void begin_break(struct async_struct * info)
+{
+ if (!info->port)
+ return;
+ cli();
+ serial_out(info, UART_LCR, serial_inp(info, UART_LCR) | UART_LCR_SBC);
+ sti();
+}
+
+/*
+ * This routine clears the break condition on the serial port.
+ */
+static void end_break(struct async_struct * info)
+{
+ if (!info->port)
+ return;
+ cli();
+ serial_out(info, UART_LCR, serial_inp(info, UART_LCR) & ~UART_LCR_SBC);
+ sti();
+}
+
+/*
* This routine returns a bitfield of "wild interrupts". Basically,
* any unclaimed interrupts which is flapping around.
*/
@@ -2198,6 +2225,19 @@
send_break(info, arg ? arg*(HZ/10) : HZ/4);
if (current->signal & ~current->blocked)
return -EINTR;
+ return 0;
+ case TIOCSBRK:
+ retval = tty_check_change(tty);
+ if (retval)
+ return retval;
+ tty_wait_until_sent(tty, 0);
+ begin_break(info);
+ return 0;
+ case TIOCCBRK:
+ retval = tty_check_change(tty);
+ if (retval)
+ return retval;
+ end_break(info);
return 0;
case TIOCGSOFTCAR:
return put_user(C_CLOCAL(tty) ? 1 : 0, (int *) arg);
===================================================================
RCS file: include/asm-i386/RCS/ioctls.h,v
retrieving revision 1.1
diff -u -r1.1 include/asm-i386/ioctls.h
--- include/asm-i386/ioctls.h 1997/06/19 21:11:48 1.1
+++ include/asm-i386/ioctls.h 1997/06/19 21:13:50
@@ -44,6 +44,9 @@
#define TIOCGETD 0x5424
#define TCSBRKP 0x5425 /* Needed for POSIX tcsendbreak() */
#define TIOCTTYGSTRUCT 0x5426 /* For debugging only */
+#define TIOCSBRK 0x5427 /* BSD compatibility */
+#define TIOCCBRK 0x5428 /* BSD compatibility */
+
#define FIONCLEX 0x5450 /* these numbers need to be adjusted. */
#define FIOCLEX 0x5451
#define FIOASYNC 0x5452
===================================================================
RCS file: include/asm-alpha/RCS/ioctls.h,v
retrieving revision 1.1
diff -u -r1.1 include/asm-alpha/ioctls.h
--- include/asm-alpha/ioctls.h 1997/06/19 21:11:48 1.1
+++ include/asm-alpha/ioctls.h 1997/06/19 21:14:03
@@ -83,6 +83,8 @@
#define TIOCGETD 0x5424
#define TCSBRKP 0x5425 /* Needed for POSIX tcsendbreak() */
#define TIOCTTYGSTRUCT 0x5426 /* For debugging only */
+#define TIOCSBRK 0x5427 /* BSD compatibility */
+#define TIOCCBRK 0x5428 /* BSD compatibility */

#define TIOCSERCONFIG 0x5453
#define TIOCSERGWILD 0x5454
===================================================================
RCS file: include/asm-m68k/RCS/ioctls.h,v
retrieving revision 1.1
diff -u -r1.1 include/asm-m68k/ioctls.h
--- include/asm-m68k/ioctls.h 1997/06/19 21:11:48 1.1
+++ include/asm-m68k/ioctls.h 1997/06/19 21:14:27
@@ -44,6 +44,9 @@
#define TIOCGETD 0x5424
#define TCSBRKP 0x5425 /* Needed for POSIX tcsendbreak() */
#define TIOCTTYGSTRUCT 0x5426 /* For debugging only */
+#define TIOCSBRK 0x5427 /* BSD compatibility */
+#define TIOCCBRK 0x5428 /* BSD compatibility */
+
#define FIONCLEX 0x5450 /* these numbers need to be adjusted. */
#define FIOCLEX 0x5451
#define FIOASYNC 0x5452
===================================================================
RCS file: include/asm-mips/RCS/ioctls.h,v
retrieving revision 1.1
diff -u -r1.1 include/asm-mips/ioctls.h
--- include/asm-mips/ioctls.h 1997/06/19 21:11:48 1.1
+++ include/asm-mips/ioctls.h 1997/06/19 21:14:56
@@ -84,6 +84,8 @@
#define TIOCSERGETLSR 0x548e /* Get line status register */
#define TIOCSERGETMULTI 0x548f /* Get multiport config */
#define TIOCSERSETMULTI 0x5490 /* Set multiport config */
+#define TIOCSBRK 0x5491 /* BSD compatibility */
+#define TIOCCBRK 0x5492 /* BSD compatibility */

/* ----------------------------------------------------------------------- */

===================================================================
RCS file: include/asm-ppc/RCS/ioctls.h,v
retrieving revision 1.1
diff -u -r1.1 include/asm-ppc/ioctls.h
--- include/asm-ppc/ioctls.h 1997/06/19 21:11:48 1.1
+++ include/asm-ppc/ioctls.h 1997/06/19 21:15:14
@@ -83,6 +83,8 @@
#define TIOCGETD 0x5424
#define TCSBRKP 0x5425 /* Needed for POSIX tcsendbreak() */
#define TIOCTTYGSTRUCT 0x5426 /* For debugging only */
+#define TIOCSBRK 0x5427 /* BSD compatibility */
+#define TIOCCBRK 0x5428 /* BSD compatibility */

#define TIOCSERCONFIG 0x5453
#define TIOCSERGWILD 0x5454