Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 383cedc3 authored by Oliver Neukum's avatar Oliver Neukum Committed by Greg Kroah-Hartman
Browse files

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: default avatarOliver Neukum <oliver@neukum.org>
Tested-off-by: default avatarZhao Ming <zhao.ming9@zte.com.cn>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent ab26d20f
Loading
Loading
Loading
Loading
+116 −18
Original line number Diff line number Diff line
@@ -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;

		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