Commit c6561082 authored by Michał Mirosław's avatar Michał Mirosław Committed by Felipe Balbi

usb: gadget: u_serial: use mutex for serialising open()s

Remove home-made waiting mechanism from gs_open() and rely on
portmaster's mutex to do the job.

Note: This releases thread waiting on close() when another thread
open()s simultaneously.
Signed-off-by: default avatarMichał Mirosław <mirq-linux@rere.qmqm.pl>
Signed-off-by: default avatarFelipe Balbi <felipe.balbi@linux.intel.com>
parent ef9b457d
...@@ -104,7 +104,6 @@ struct gs_port { ...@@ -104,7 +104,6 @@ struct gs_port {
struct gs_console *console; struct gs_console *console;
#endif #endif
bool openclose; /* open/close in progress */
u8 port_num; u8 port_num;
struct list_head read_pool; struct list_head read_pool;
...@@ -588,82 +587,45 @@ static int gs_open(struct tty_struct *tty, struct file *file) ...@@ -588,82 +587,45 @@ static int gs_open(struct tty_struct *tty, struct file *file)
{ {
int port_num = tty->index; int port_num = tty->index;
struct gs_port *port; struct gs_port *port;
int status; int status = 0;
do {
mutex_lock(&ports[port_num].lock); mutex_lock(&ports[port_num].lock);
port = ports[port_num].port; port = ports[port_num].port;
if (!port) if (!port) {
status = -ENODEV; status = -ENODEV;
else { goto out;
spin_lock_irq(&port->port_lock);
/* already open? Great. */
if (port->port.count) {
status = 0;
port->port.count++;
/* currently opening/closing? wait ... */
} else if (port->openclose) {
status = -EBUSY;
/* ... else we do the work */
} else {
status = -EAGAIN;
port->openclose = true;
}
spin_unlock_irq(&port->port_lock);
}
mutex_unlock(&ports[port_num].lock);
switch (status) {
default:
/* fully handled */
return status;
case -EAGAIN:
/* must do the work */
break;
case -EBUSY:
/* wait for EAGAIN task to finish */
msleep(1);
/* REVISIT could have a waitchannel here, if
* concurrent open performance is important
*/
break;
} }
} while (status != -EAGAIN);
/* Do the "real open" */
spin_lock_irq(&port->port_lock); spin_lock_irq(&port->port_lock);
/* allocate circular buffer on first open */ /* allocate circular buffer on first open */
if (!kfifo_initialized(&port->port_write_buf)) { if (!kfifo_initialized(&port->port_write_buf)) {
spin_unlock_irq(&port->port_lock); spin_unlock_irq(&port->port_lock);
/*
* portmaster's mutex still protects from simultaneous open(),
* and close() can't happen, yet.
*/
status = kfifo_alloc(&port->port_write_buf, status = kfifo_alloc(&port->port_write_buf,
WRITE_BUF_SIZE, GFP_KERNEL); WRITE_BUF_SIZE, GFP_KERNEL);
spin_lock_irq(&port->port_lock);
if (status) { if (status) {
pr_debug("gs_open: ttyGS%d (%p,%p) no buffer\n", pr_debug("gs_open: ttyGS%d (%p,%p) no buffer\n",
port->port_num, tty, file); port_num, tty, file);
port->openclose = false; goto out;
goto exit_unlock_port;
}
} }
/* REVISIT if REMOVED (ports[].port NULL), abort the open spin_lock_irq(&port->port_lock);
* to let rmmod work faster (but this way isn't wrong). }
*/
/* REVISIT maybe wait for "carrier detect" */ /* already open? Great. */
if (port->port.count++)
goto exit_unlock_port;
tty->driver_data = port; tty->driver_data = port;
port->port.tty = tty; port->port.tty = tty;
port->port.count = 1;
port->openclose = false;
/* if connected, start the I/O stream */ /* if connected, start the I/O stream */
if (port->port_usb) { if (port->port_usb) {
struct gserial *gser = port->port_usb; struct gserial *gser = port->port_usb;
...@@ -677,20 +639,21 @@ static int gs_open(struct tty_struct *tty, struct file *file) ...@@ -677,20 +639,21 @@ static int gs_open(struct tty_struct *tty, struct file *file)
pr_debug("gs_open: ttyGS%d (%p,%p)\n", port->port_num, tty, file); pr_debug("gs_open: ttyGS%d (%p,%p)\n", port->port_num, tty, file);
status = 0;
exit_unlock_port: exit_unlock_port:
spin_unlock_irq(&port->port_lock); spin_unlock_irq(&port->port_lock);
out:
mutex_unlock(&ports[port_num].lock);
return status; return status;
} }
static int gs_writes_finished(struct gs_port *p) static int gs_close_flush_done(struct gs_port *p)
{ {
int cond; int cond;
/* return true on disconnect or empty buffer */ /* return true on disconnect or empty buffer or if raced with open() */
spin_lock_irq(&p->port_lock); spin_lock_irq(&p->port_lock);
cond = (p->port_usb == NULL) || !kfifo_len(&p->port_write_buf); cond = p->port_usb == NULL || !kfifo_len(&p->port_write_buf) ||
p->port.count > 1;
spin_unlock_irq(&p->port_lock); spin_unlock_irq(&p->port_lock);
return cond; return cond;
...@@ -704,6 +667,7 @@ static void gs_close(struct tty_struct *tty, struct file *file) ...@@ -704,6 +667,7 @@ static void gs_close(struct tty_struct *tty, struct file *file)
spin_lock_irq(&port->port_lock); spin_lock_irq(&port->port_lock);
if (port->port.count != 1) { if (port->port.count != 1) {
raced_with_open:
if (port->port.count == 0) if (port->port.count == 0)
WARN_ON(1); WARN_ON(1);
else else
...@@ -713,12 +677,6 @@ static void gs_close(struct tty_struct *tty, struct file *file) ...@@ -713,12 +677,6 @@ static void gs_close(struct tty_struct *tty, struct file *file)
pr_debug("gs_close: ttyGS%d (%p,%p) ...\n", port->port_num, tty, file); pr_debug("gs_close: ttyGS%d (%p,%p) ...\n", port->port_num, tty, file);
/* mark port as closing but in use; we can drop port lock
* and sleep if necessary
*/
port->openclose = true;
port->port.count = 0;
gser = port->port_usb; gser = port->port_usb;
if (gser && gser->disconnect) if (gser && gser->disconnect)
gser->disconnect(gser); gser->disconnect(gser);
...@@ -729,9 +687,13 @@ static void gs_close(struct tty_struct *tty, struct file *file) ...@@ -729,9 +687,13 @@ static void gs_close(struct tty_struct *tty, struct file *file)
if (kfifo_len(&port->port_write_buf) > 0 && gser) { if (kfifo_len(&port->port_write_buf) > 0 && gser) {
spin_unlock_irq(&port->port_lock); spin_unlock_irq(&port->port_lock);
wait_event_interruptible_timeout(port->drain_wait, wait_event_interruptible_timeout(port->drain_wait,
gs_writes_finished(port), gs_close_flush_done(port),
GS_CLOSE_TIMEOUT * HZ); GS_CLOSE_TIMEOUT * HZ);
spin_lock_irq(&port->port_lock); spin_lock_irq(&port->port_lock);
if (port->port.count != 1)
goto raced_with_open;
gser = port->port_usb; gser = port->port_usb;
} }
...@@ -744,10 +706,9 @@ static void gs_close(struct tty_struct *tty, struct file *file) ...@@ -744,10 +706,9 @@ static void gs_close(struct tty_struct *tty, struct file *file)
else else
kfifo_reset(&port->port_write_buf); kfifo_reset(&port->port_write_buf);
port->port.count = 0;
port->port.tty = NULL; port->port.tty = NULL;
port->openclose = false;
pr_debug("gs_close: ttyGS%d (%p,%p) done!\n", pr_debug("gs_close: ttyGS%d (%p,%p) done!\n",
port->port_num, tty, file); port->port_num, tty, file);
...@@ -1207,8 +1168,9 @@ static int gs_closed(struct gs_port *port) ...@@ -1207,8 +1168,9 @@ static int gs_closed(struct gs_port *port)
int cond; int cond;
spin_lock_irq(&port->port_lock); spin_lock_irq(&port->port_lock);
cond = (port->port.count == 0) && !port->openclose; cond = port->port.count == 0;
spin_unlock_irq(&port->port_lock); spin_unlock_irq(&port->port_lock);
return cond; return cond;
} }
...@@ -1413,7 +1375,7 @@ void gserial_disconnect(struct gserial *gser) ...@@ -1413,7 +1375,7 @@ void gserial_disconnect(struct gserial *gser)
port->port_usb = NULL; port->port_usb = NULL;
gser->ioport = NULL; gser->ioport = NULL;
if (port->port.count > 0 || port->openclose) { if (port->port.count > 0) {
wake_up_interruptible(&port->drain_wait); wake_up_interruptible(&port->drain_wait);
if (port->port.tty) if (port->port.tty)
tty_hangup(port->port.tty); tty_hangup(port->port.tty);
...@@ -1426,7 +1388,7 @@ void gserial_disconnect(struct gserial *gser) ...@@ -1426,7 +1388,7 @@ void gserial_disconnect(struct gserial *gser)
/* finally, free any unused/unusable I/O buffers */ /* finally, free any unused/unusable I/O buffers */
spin_lock_irqsave(&port->port_lock, flags); spin_lock_irqsave(&port->port_lock, flags);
if (port->port.count == 0 && !port->openclose) if (port->port.count == 0)
kfifo_free(&port->port_write_buf); kfifo_free(&port->port_write_buf);
gs_free_requests(gser->out, &port->read_pool, NULL); gs_free_requests(gser->out, &port->read_pool, NULL);
gs_free_requests(gser->out, &port->read_queue, NULL); gs_free_requests(gser->out, &port->read_queue, NULL);
......
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