Am Dienstag, 20. März 2007 00:56 schrieb John:
> This is a fix for race condition in usb-serial.c
> between serial_open and usb_serial_disconnect
> (kernel version 2.6.21-rc3)
> Prior to this patch instances of usb_serial were not
> ref-counted consistently, which caused serial_open
> to reuse already deleted instance.
> For more details search Web for
> "Possible race condition in usb-serial.c"
> discussion in Dec 2006.
Hi John,
this was so long ago that I made a patch based on the discussion
we had back then. Would you mind having a look at it? We should
merge those patches to get an optimum.
Regards
Oliver
--- a/include/linux/usb/serial.h 2007-03-16 01:32:25.000000000 +0100
+++ b/include/linux/usb/serial.h 2007-03-16 20:32:05.000000000 +0100
@@ -128,6 +128,7 @@
struct usb_device * dev;
struct usb_serial_driver * type;
struct usb_interface * interface;
+ spinlock_t lock;
unsigned char minor;
unsigned char num_ports;
unsigned char num_port_pointers;
@@ -135,6 +136,7 @@
char num_interrupt_out;
char num_bulk_in;
char num_bulk_out;
+ int busy;
struct usb_serial_port * port[MAX_NUM_PORTS];
struct kref kref;
void * private;
--- a/drivers/usb/serial/usb-serial.c 2007-03-16 01:32:38.000000000 +0100
+++ b/drivers/usb/serial/usb-serial.c 2007-03-16 21:24:47.000000000 +0100
@@ -59,8 +59,10 @@
static int debug;
static struct usb_serial *serial_table[SERIAL_TTY_MINORS]; /* initially
all NULL */
+static unsigned long deathrow[SERIAL_TTY_MINORS/BITS_PER_LONG];
/* disconnected but open*/
static spinlock_t table_lock;
static LIST_HEAD(usb_serial_driver_list);
+static DECLARE_WAIT_QUEUE_HEAD(usbserial_henchman);
struct usb_serial *usb_serial_get_by_index(unsigned index)
{
@@ -104,6 +106,7 @@
for (i = *minor; (i < (*minor + num_ports)) && (i <
SERIAL_TTY_MINORS); ++i) {
serial_table[i] = serial;
serial->port[j++]->number = i;
+ __clear_bit(i, deathrow);
}
spin_unlock(&table_lock);
return serial;
@@ -112,6 +115,53 @@
return NULL;
}
+/* requires locking from caller*/
+static int on_deathrow(struct usb_serial_port *port)
+{
+ int number = port->number;
+
+ return test_bit(number, deathrow);
+}
+
+/* requires locking from caller*/
+static void schedule_execution(struct usb_serial_port *port)
+{
+ int number = port->number;
+
+ __set_bit(number, deathrow);
+}
+
+static int lock_serial(struct usb_serial_port *port)
+{
+ struct usb_serial *serial = port->serial;
+ unsigned long flags;
+ int terminated;
+
+ spin_lock_irqsave(&serial->lock, flags);
+ terminated = on_deathrow(port);
+ if (!terminated)
+ serial->busy++;
+ spin_unlock_irqrestore(&serial->lock, flags);
+
+ if (terminated)
+ return -ENODEV;
+ return 0;
+}
+
+static void unlock_serial(struct usb_serial_port *port)
+{
+ struct usb_serial *serial = port->serial;
+ unsigned long flags;
+ int waiters;
+
+ spin_lock_irqsave(&serial->lock, flags);
+ waiters = --serial->busy;
+ spin_unlock_irqrestore(&serial->lock, flags);
+
+ if (!waiters)
+ wake_up(&usbserial_henchman);
+}
+
static void return_serial(struct usb_serial *serial)
{
int i;
@@ -148,9 +198,6 @@
serial->port[i] = NULL;
}
- if (serial->type->shutdown)
- serial->type->shutdown(serial);
-
/* return the minor range that this device had */
return_serial(serial);
@@ -203,9 +250,13 @@
goto bailout_kref_put;
}
+ retval = lock_serial(port);
+ if (retval)
+ goto bailout_kref_put;
+
if (mutex_lock_interruptible(&port->mutex)) {
retval = -ERESTARTSYS;
- goto bailout_kref_put;
+ goto bailout_kref_put_unlock;
}
++port->open_count;
@@ -233,6 +284,7 @@
}
mutex_unlock(&port->mutex);
+ unlock_serial(port);
return 0;
bailout_module_put:
@@ -242,6 +294,8 @@
tty->driver_data = NULL;
port->tty = NULL;
mutex_unlock(&port->mutex);
+bailout_kref_put_unlock:
+ unlock_serial(port);
bailout_kref_put:
usb_serial_put(serial);
return retval;
@@ -298,9 +352,15 @@
goto exit;
}
+ retval = lock_serial(port);
+ if (retval)
+ goto exit;
+
/* pass on to the driver specific version of this function */
retval = port->serial->type->write(port, buf, count);
+ unlock_serial(port);
+
exit:
return retval;
}
@@ -320,9 +380,14 @@
goto exit;
}
+ retval = lock_serial(port);
+ if (retval)
+ goto exit;
/* pass on to the driver specific version of this function */
retval = port->serial->type->write_room(port);
+ unlock_serial(port);
+
exit:
return retval;
}
@@ -342,9 +407,13 @@
goto exit;
}
+ retval = lock_serial(port);
+ if (retval)
+ goto exit;
/* pass on to the driver specific version of this function */
retval = port->serial->type->chars_in_buffer(port);
+ unlock_serial(port);
exit:
return retval;
}
@@ -364,8 +433,12 @@
}
/* pass on to the driver specific version of this function */
- if (port->serial->type->throttle)
+ if (port->serial->type->throttle) {
+ if (0 < lock_serial(port))
+ return;
port->serial->type->throttle(port);
+ unlock_serial(port);
+ }
}
static void serial_unthrottle (struct tty_struct * tty)
@@ -383,8 +456,12 @@
}
/* pass on to the driver specific version of this function */
- if (port->serial->type->unthrottle)
+ if (port->serial->type->unthrottle) {
+ if (0 < lock_serial(port))
+ return;
port->serial->type->unthrottle(port);
+ unlock_serial(port);
+ }
}
static int serial_ioctl (struct tty_struct *tty, struct file * file, unsigned
int cmd, unsigned long arg)
@@ -402,12 +479,16 @@
goto exit;
}
+ retval = lock_serial(port);
+ if (retval)
+ goto exit;
/* pass on to the driver specific version of this function if it is
available */
if (port->serial->type->ioctl)
retval = port->serial->type->ioctl(port, file, cmd, arg);
else
retval = -ENOIOCTLCMD;
-
+
+ unlock_serial(port);
exit:
return retval;
}
@@ -427,8 +508,12 @@
}
/* pass on to the driver specific version of this function if it is
available */
- if (port->serial->type->set_termios)
+ if (port->serial->type->set_termios) {
+ if (0 < lock_serial(port))
+ return;
port->serial->type->set_termios(port, old);
+ unlock_serial(port);
+ }
}
static void serial_break (struct tty_struct *tty, int break_state)
@@ -446,8 +531,12 @@
}
/* pass on to the driver specific version of this function if it is
available */
- if (port->serial->type->break_ctl)
+ if (port->serial->type->break_ctl) {
+ if (0 < lock_serial(port))
+ return;
port->serial->type->break_ctl(port, break_state);
+ unlock_serial(port);
+ }
}
static int serial_read_proc (char *page, char **start, off_t off, int count,
int *eof, void *data)
@@ -500,6 +589,7 @@
static int serial_tiocmget (struct tty_struct *tty, struct file *file)
{
struct usb_serial_port *port = tty->driver_data;
+ int retval;
if (!port)
return -ENODEV;
@@ -511,16 +601,26 @@
return -ENODEV;
}
+ retval = lock_serial(port);
+ if (retval)
+ goto exit;
+
if (port->serial->type->tiocmget)
- return port->serial->type->tiocmget(port, file);
+ retval = port->serial->type->tiocmget(port, file);
+ else
+ retval = -EINVAL;
+
+ unlock_serial(port);
+exit:
- return -EINVAL;
+ return retval;
}
static int serial_tiocmset (struct tty_struct *tty, struct file *file,
unsigned int set, unsigned int clear)
{
struct usb_serial_port *port = tty->driver_data;
+ int retval;
if (!port)
return -ENODEV;
@@ -532,10 +632,18 @@
return -ENODEV;
}
+ retval = lock_serial(port);
+ if (retval)
+ goto exit;
+
if (port->serial->type->tiocmset)
- return port->serial->type->tiocmset(port, file, set, clear);
+ retval = port->serial->type->tiocmset(port, file, set, clear);
+ else
+ retval = -EINVAL;
- return -EINVAL;
+ unlock_serial(port);
+exit:
+ return retval;
}
/*
@@ -612,6 +720,7 @@
serial->type = driver;
serial->interface = interface;
kref_init(&serial->kref);
+ spin_lock_init(&serial->lock);
return serial;
}
@@ -1059,11 +1168,19 @@
for (i = 0; i < serial->num_ports; ++i) {
port = serial->port[i];
if (port) {
+ spin_lock_irq(&serial->lock);
+ schedule_execution(port);
+ spin_unlock_irq(&serial->lock);
if (port->tty)
tty_hangup(port->tty);
kill_traffic(port);
}
}
+ wait_event(usbserial_henchman, serial->busy == 0);
+ /* allow the subdriver to cleanly kill
+ * private URBs */
+ if (serial->type->shutdown)
+ (serial->type->shutdown)(serial);
/* let the last holder of this object
* cause it to be cleaned up */
usb_serial_put(serial);
-------------------------------------------------------------------------
Take Surveys. Earn Cash. Influence the Future of IT
Join SourceForge.net's Techsay panel and you'll get the chance to share your
opinions on IT & business topics through brief surveys-and earn cash
http://www.techsay.com/default.php?page=join.php&p=sourceforge&CID=DEVDEV
_______________________________________________
[email protected]
To unsubscribe, use the last form field at:
https://lists.sourceforge.net/lists/listinfo/linux-usb-devel