mirror of
https://github.com/torvalds/linux.git
synced 2024-11-07 12:41:55 +00:00
USB: serial: full autosuspend support for the option driver
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>
This commit is contained in:
parent
ab26d20f3e
commit
383cedc3bb
@ -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,
|
||||
@ -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];
|
||||
@ -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) */
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
@ -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;
|
||||
}
|
||||
@ -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();
|
||||
@ -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__);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
@ -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 */
|
||||
@ -1255,7 +1348,7 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1263,27 +1356,32 @@ static int option_resume(struct usb_serial *serial)
|
||||
/* 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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user