Skip to content

Commit

Permalink
USB: serial: full autosuspend support for the option driver
Browse files Browse the repository at this point in the history
this adds autosupport usable even in an always online mode.

- enables remote wakeup on open
- autoresume for sending
- timeout based autosuspend if nothing is sent or recieved
- autosuspend without remote wakeup support on open/close

Signed-off-by: Oliver Neukum <oliver@neukum.org>
Tested-off-by: Zhao Ming <zhao.ming9@zte.com.cn>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
  • Loading branch information
Oliver Neukum authored and Greg Kroah-Hartman committed Sep 23, 2009
1 parent ab26d20 commit 383cedc
Showing 1 changed file with 116 additions and 18 deletions.
134 changes: 116 additions & 18 deletions drivers/usb/serial/option.c
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,7 @@ static struct usb_driver option_driver = {
#ifdef CONFIG_PM
.suspend = usb_serial_suspend,
.resume = usb_serial_resume,
.supports_autosuspend = 1,
#endif
.id_table = option_ids,
.no_dynamic_id = 1,
Expand Down Expand Up @@ -643,6 +644,12 @@ static int debug;
#define IN_BUFLEN 4096
#define OUT_BUFLEN 4096

struct option_intf_private {
spinlock_t susp_lock;
unsigned int suspended:1;
int in_flight;
};

struct option_port_private {
/* Input endpoints and buffer for this port */
struct urb *in_urbs[N_IN_URB];
Expand All @@ -651,6 +658,8 @@ struct option_port_private {
struct urb *out_urbs[N_OUT_URB];
u8 *out_buffer[N_OUT_URB];
unsigned long out_busy; /* Bit vector of URBs in use */
int opened;
struct usb_anchor delayed;

/* Settings for the port */
int rts_state; /* Handshaking pins (outputs) */
Expand Down Expand Up @@ -697,12 +706,17 @@ module_exit(option_exit);
static int option_probe(struct usb_serial *serial,
const struct usb_device_id *id)
{
struct option_intf_private *data;
/* D-Link DWM 652 still exposes CD-Rom emulation interface in modem mode */
if (serial->dev->descriptor.idVendor == DLINK_VENDOR_ID &&
serial->dev->descriptor.idProduct == DLINK_PRODUCT_DWM_652 &&
serial->interface->cur_altsetting->desc.bInterfaceClass == 0x8)
return -ENODEV;

data = serial->private = kzalloc(sizeof(struct option_intf_private), GFP_KERNEL);
if (!data)
return -ENOMEM;
spin_lock_init(&data->susp_lock);
return 0;
}

Expand Down Expand Up @@ -759,12 +773,15 @@ static int option_write(struct tty_struct *tty, struct usb_serial_port *port,
const unsigned char *buf, int count)
{
struct option_port_private *portdata;
struct option_intf_private *intfdata;
int i;
int left, todo;
struct urb *this_urb = NULL; /* spurious */
int err;
unsigned long flags;

portdata = usb_get_serial_port_data(port);
intfdata = port->serial->private;

dbg("%s: write (%d chars)", __func__, count);

Expand All @@ -786,17 +803,33 @@ static int option_write(struct tty_struct *tty, struct usb_serial_port *port,
dbg("%s: endpoint %d buf %d", __func__,
usb_pipeendpoint(this_urb->pipe), i);

err = usb_autopm_get_interface_async(port->serial->interface);
if (err < 0)
break;

/* send the data */
memcpy(this_urb->transfer_buffer, buf, todo);
this_urb->transfer_buffer_length = todo;

err = usb_submit_urb(this_urb, GFP_ATOMIC);
if (err) {
dbg("usb_submit_urb %p (write bulk) failed "
"(%d)", this_urb, err);
clear_bit(i, &portdata->out_busy);
continue;
spin_lock_irqsave(&intfdata->susp_lock, flags);
if (intfdata->suspended) {
usb_anchor_urb(this_urb, &portdata->delayed);
spin_unlock_irqrestore(&intfdata->susp_lock, flags);
} else {
intfdata->in_flight++;
spin_unlock_irqrestore(&intfdata->susp_lock, flags);
err = usb_submit_urb(this_urb, GFP_ATOMIC);
if (err) {
dbg("usb_submit_urb %p (write bulk) failed "
"(%d)", this_urb, err);
clear_bit(i, &portdata->out_busy);
spin_lock_irqsave(&intfdata->susp_lock, flags);
intfdata->in_flight--;
spin_unlock_irqrestore(&intfdata->susp_lock, flags);
continue;
}
}

portdata->tx_start_time[i] = jiffies;
buf += todo;
left -= todo;
Expand Down Expand Up @@ -840,7 +873,10 @@ static void option_indat_callback(struct urb *urb)
if (err)
printk(KERN_ERR "%s: resubmit read urb failed. "
"(%d)", __func__, err);
else
usb_mark_last_busy(port->serial->dev);
}

}
return;
}
Expand All @@ -849,15 +885,21 @@ static void option_outdat_callback(struct urb *urb)
{
struct usb_serial_port *port;
struct option_port_private *portdata;
struct option_intf_private *intfdata;
int i;

dbg("%s", __func__);

port = urb->context;
intfdata = port->serial->private;

usb_serial_port_softint(port);

usb_autopm_put_interface_async(port->serial->interface);
portdata = usb_get_serial_port_data(port);
spin_lock(&intfdata->susp_lock);
intfdata->in_flight--;
spin_unlock(&intfdata->susp_lock);

for (i = 0; i < N_OUT_URB; ++i) {
if (portdata->out_urbs[i] == urb) {
smp_mb__before_clear_bit();
Expand Down Expand Up @@ -967,10 +1009,13 @@ static int option_chars_in_buffer(struct tty_struct *tty)
static int option_open(struct tty_struct *tty, struct usb_serial_port *port)
{
struct option_port_private *portdata;
struct option_intf_private *intfdata;
struct usb_serial *serial = port->serial;
int i, err;
struct urb *urb;

portdata = usb_get_serial_port_data(port);
intfdata = serial->private;

dbg("%s", __func__);

Expand All @@ -989,6 +1034,12 @@ static int option_open(struct tty_struct *tty, struct usb_serial_port *port)

option_send_setup(port);

serial->interface->needs_remote_wakeup = 1;
spin_lock_irq(&intfdata->susp_lock);
portdata->opened = 1;
spin_unlock_irq(&intfdata->susp_lock);
usb_autopm_put_interface(serial->interface);

return 0;
}

Expand All @@ -1013,16 +1064,23 @@ static void option_close(struct usb_serial_port *port)
int i;
struct usb_serial *serial = port->serial;
struct option_port_private *portdata;
struct option_intf_private *intfdata = port->serial->private;

dbg("%s", __func__);
portdata = usb_get_serial_port_data(port);

if (serial->dev) {
/* Stop reading/writing urbs */
spin_lock_irq(&intfdata->susp_lock);
portdata->opened = 0;
spin_unlock_irq(&intfdata->susp_lock);

for (i = 0; i < N_IN_URB; i++)
usb_kill_urb(portdata->in_urbs[i]);
for (i = 0; i < N_OUT_URB; i++)
usb_kill_urb(portdata->out_urbs[i]);
usb_autopm_get_interface(serial->interface);
serial->interface->needs_remote_wakeup = 0;
}
}

Expand Down Expand Up @@ -1127,6 +1185,7 @@ static int option_startup(struct usb_serial *serial)
__func__, i);
return 1;
}
init_usb_anchor(&portdata->delayed);

for (j = 0; j < N_IN_URB; j++) {
buffer = (u8 *)__get_free_page(GFP_KERNEL);
Expand Down Expand Up @@ -1229,18 +1288,52 @@ static void option_release(struct usb_serial *serial)
#ifdef CONFIG_PM
static int option_suspend(struct usb_serial *serial, pm_message_t message)
{
struct option_intf_private *intfdata = serial->private;
int b;

dbg("%s entered", __func__);

if (serial->dev->auto_pm) {
spin_lock_irq(&intfdata->susp_lock);
b = intfdata->in_flight;
spin_unlock_irq(&intfdata->susp_lock);

if (b)
return -EBUSY;
}

spin_lock_irq(&intfdata->susp_lock);
intfdata->suspended = 1;
spin_unlock_irq(&intfdata->susp_lock);
stop_read_write_urbs(serial);

return 0;
}

static void play_delayed(struct usb_serial_port *port)
{
struct option_intf_private *data;
struct option_port_private *portdata;
struct urb *urb;
int err;

portdata = usb_get_serial_port_data(port);
data = port->serial->private;
while ((urb = usb_get_from_anchor(&portdata->delayed))) {
err = usb_submit_urb(urb, GFP_ATOMIC);
if (!err)
data->in_flight++;
}
}

static int option_resume(struct usb_serial *serial)
{
int err, i, j;
int i, j;
struct usb_serial_port *port;
struct urb *urb;
struct option_intf_private *intfdata = serial->private;
struct option_port_private *portdata;
struct urb *urb;
int err = 0;

dbg("%s entered", __func__);
/* get the interrupt URBs resubmitted unconditionally */
Expand All @@ -1255,35 +1348,40 @@ static int option_resume(struct usb_serial *serial)
if (err < 0) {
err("%s: Error %d for interrupt URB of port%d",
__func__, err, i);
return err;
goto err_out;
}
}

for (i = 0; i < serial->num_ports; i++) {
/* walk all ports */
port = serial->port[i];
portdata = usb_get_serial_port_data(port);
mutex_lock(&port->mutex);

/* skip closed ports */
if (!port->port.count) {
mutex_unlock(&port->mutex);
spin_lock_irq(&intfdata->susp_lock);
if (!portdata->opened) {
spin_unlock_irq(&intfdata->susp_lock);
continue;
}

for (j = 0; j < N_IN_URB; j++) {
urb = portdata->in_urbs[j];
err = usb_submit_urb(urb, GFP_NOIO);
err = usb_submit_urb(urb, GFP_ATOMIC);
if (err < 0) {
mutex_unlock(&port->mutex);
err("%s: Error %d for bulk URB %d",
__func__, err, i);
return err;
spin_unlock_irq(&intfdata->susp_lock);
goto err_out;
}
}
mutex_unlock(&port->mutex);
play_delayed(port);
spin_unlock_irq(&intfdata->susp_lock);
}
return 0;
spin_lock_irq(&intfdata->susp_lock);
intfdata->suspended = 0;
spin_unlock_irq(&intfdata->susp_lock);
err_out:
return err;
}
#endif

Expand Down

0 comments on commit 383cedc

Please sign in to comment.