Commit f278a2f7 authored by Dave Young's avatar Dave Young Committed by Linus Torvalds

tty: Fix regressions caused by commit b50989dc

The following commit made console open fails while booting:

	commit b50989dc
	Author: Alan Cox <alan@linux.intel.com>
	Date:   Sat Sep 19 13:13:22 2009 -0700

	tty: make the kref destructor occur asynchronously

Due to tty release routines run in a workqueue now, error like the
following will be reported while booting:

INIT open /dev/console Input/output error

It also causes hibernation regression to appear as reported at
http://bugzilla.kernel.org/show_bug.cgi?id=14229

The reason is that now there's latency issue with closing, but when
we open a "closing not finished" tty, -EIO will be returned.

Fix it as per the following Alan's suggestion:

  Fun but it's actually not a bug and the fix is wrong in itself as
  the port may be closing but not yet being destructed, in which case
  it seems to do the wrong thing.  Opening a tty that is closing (and
  could be closing for long periods) is supposed to return -EIO.

  I suspect a better way to deal with this and keep the old console
  timing is to split tty->shutdown into two functions.

  tty->shutdown() - called synchronously just before we dump the tty
  onto the waitqueue for destruction

  tty->cleanup() - called when the destructor runs.

  We would then do the shutdown part which can occur in IRQ context
  fine, before queueing the rest of the release (from tty->magic = 0
  ...  the end) to occur asynchronously

  The USB update in -next would then need a call like

       if (tty->cleanup)
               tty->cleanup(tty);

  at the top of the async function and the USB shutdown to be split
  between shutdown and cleanup as the USB resource cleanup and final
  tidy cannot occur synchronously as it needs to sleep.

  In other words the logic becomes

       final kref put
               make object unfindable

       async
               clean it up
Signed-off-by: default avatarDave Young <hidave.darkstar@gmail.com>
[ rjw: Rebased on top of 2.6.31-git, reworked the changelog. ]
Signed-off-by: default avatar"Rafael J. Wysocki" <rjw@sisk.pl>
[ Changed serial naming to match new rules, dropped tty_shutdown as per
  comments from Alan Stern  - Linus ]
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent 569ec4cc
...@@ -1389,7 +1389,7 @@ EXPORT_SYMBOL(tty_shutdown); ...@@ -1389,7 +1389,7 @@ EXPORT_SYMBOL(tty_shutdown);
* of ttys that the driver keeps. * of ttys that the driver keeps.
* *
* This method gets called from a work queue so that the driver private * This method gets called from a work queue so that the driver private
* shutdown ops can sleep (needed for USB at least) * cleanup ops can sleep (needed for USB at least)
*/ */
static void release_one_tty(struct work_struct *work) static void release_one_tty(struct work_struct *work)
{ {
...@@ -1397,10 +1397,9 @@ static void release_one_tty(struct work_struct *work) ...@@ -1397,10 +1397,9 @@ static void release_one_tty(struct work_struct *work)
container_of(work, struct tty_struct, hangup_work); container_of(work, struct tty_struct, hangup_work);
struct tty_driver *driver = tty->driver; struct tty_driver *driver = tty->driver;
if (tty->ops->shutdown) if (tty->ops->cleanup)
tty->ops->shutdown(tty); tty->ops->cleanup(tty);
else
tty_shutdown(tty);
tty->magic = 0; tty->magic = 0;
tty_driver_kref_put(driver); tty_driver_kref_put(driver);
module_put(driver->owner); module_put(driver->owner);
...@@ -1415,6 +1414,12 @@ static void release_one_tty(struct work_struct *work) ...@@ -1415,6 +1414,12 @@ static void release_one_tty(struct work_struct *work)
static void queue_release_one_tty(struct kref *kref) static void queue_release_one_tty(struct kref *kref)
{ {
struct tty_struct *tty = container_of(kref, struct tty_struct, kref); struct tty_struct *tty = container_of(kref, struct tty_struct, kref);
if (tty->ops->shutdown)
tty->ops->shutdown(tty);
else
tty_shutdown(tty);
/* The hangup queue is now free so we can reuse it rather than /* The hangup queue is now free so we can reuse it rather than
waste a chunk of memory for each port */ waste a chunk of memory for each port */
INIT_WORK(&tty->hangup_work, release_one_tty); INIT_WORK(&tty->hangup_work, release_one_tty);
......
...@@ -192,7 +192,7 @@ void usb_serial_put(struct usb_serial *serial) ...@@ -192,7 +192,7 @@ void usb_serial_put(struct usb_serial *serial)
* This is the first place a new tty gets used. Hence this is where we * This is the first place a new tty gets used. Hence this is where we
* acquire references to the usb_serial structure and the driver module, * acquire references to the usb_serial structure and the driver module,
* where we store a pointer to the port, and where we do an autoresume. * where we store a pointer to the port, and where we do an autoresume.
* All these actions are reversed in serial_release(). * All these actions are reversed in serial_cleanup().
*/ */
static int serial_install(struct tty_driver *driver, struct tty_struct *tty) static int serial_install(struct tty_driver *driver, struct tty_struct *tty)
{ {
...@@ -339,15 +339,16 @@ static void serial_close(struct tty_struct *tty, struct file *filp) ...@@ -339,15 +339,16 @@ static void serial_close(struct tty_struct *tty, struct file *filp)
} }
/** /**
* serial_release - free resources post close/hangup * serial_cleanup - free resources post close/hangup
* @port: port to free up * @port: port to free up
* *
* Do the resource freeing and refcount dropping for the port. * Do the resource freeing and refcount dropping for the port.
* Avoid freeing the console. * Avoid freeing the console.
* *
* Called when the last tty kref is dropped. * Called asynchronously after the last tty kref is dropped,
* and the tty layer has already done the tty_shutdown(tty);
*/ */
static void serial_release(struct tty_struct *tty) static void serial_cleanup(struct tty_struct *tty)
{ {
struct usb_serial_port *port = tty->driver_data; struct usb_serial_port *port = tty->driver_data;
struct usb_serial *serial; struct usb_serial *serial;
...@@ -361,9 +362,6 @@ static void serial_release(struct tty_struct *tty) ...@@ -361,9 +362,6 @@ static void serial_release(struct tty_struct *tty)
dbg("%s - port %d", __func__, port->number); dbg("%s - port %d", __func__, port->number);
/* Standard shutdown processing */
tty_shutdown(tty);
tty->driver_data = NULL; tty->driver_data = NULL;
serial = port->serial; serial = port->serial;
...@@ -1210,7 +1208,7 @@ static const struct tty_operations serial_ops = { ...@@ -1210,7 +1208,7 @@ static const struct tty_operations serial_ops = {
.chars_in_buffer = serial_chars_in_buffer, .chars_in_buffer = serial_chars_in_buffer,
.tiocmget = serial_tiocmget, .tiocmget = serial_tiocmget,
.tiocmset = serial_tiocmset, .tiocmset = serial_tiocmset,
.shutdown = serial_release, .cleanup = serial_cleanup,
.install = serial_install, .install = serial_install,
.proc_fops = &serial_proc_fops, .proc_fops = &serial_proc_fops,
}; };
......
...@@ -45,8 +45,16 @@ ...@@ -45,8 +45,16 @@
* *
* void (*shutdown)(struct tty_struct * tty); * void (*shutdown)(struct tty_struct * tty);
* *
* This routine is called when a particular tty device is closed for * This routine is called synchronously when a particular tty device
* the last time freeing up the resources. * is closed for the last time freeing up the resources.
*
*
* void (*cleanup)(struct tty_struct * tty);
*
* This routine is called asynchronously when a particular tty device
* is closed for the last time freeing up the resources. This is
* actually the second part of shutdown for routines that might sleep.
*
* *
* int (*write)(struct tty_struct * tty, * int (*write)(struct tty_struct * tty,
* const unsigned char *buf, int count); * const unsigned char *buf, int count);
...@@ -233,6 +241,7 @@ struct tty_operations { ...@@ -233,6 +241,7 @@ struct tty_operations {
int (*open)(struct tty_struct * tty, struct file * filp); int (*open)(struct tty_struct * tty, struct file * filp);
void (*close)(struct tty_struct * tty, struct file * filp); void (*close)(struct tty_struct * tty, struct file * filp);
void (*shutdown)(struct tty_struct *tty); void (*shutdown)(struct tty_struct *tty);
void (*cleanup)(struct tty_struct *tty);
int (*write)(struct tty_struct * tty, int (*write)(struct tty_struct * tty,
const unsigned char *buf, int count); const unsigned char *buf, int count);
int (*put_char)(struct tty_struct *tty, unsigned char ch); int (*put_char)(struct tty_struct *tty, unsigned char ch);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment