diff options
Diffstat (limited to 'drivers/usb/serial/usb_wwan.c')
| -rw-r--r-- | drivers/usb/serial/usb_wwan.c | 665 | 
1 files changed, 665 insertions, 0 deletions
diff --git a/drivers/usb/serial/usb_wwan.c b/drivers/usb/serial/usb_wwan.c new file mode 100644 index 00000000000..0c70b4a621b --- /dev/null +++ b/drivers/usb/serial/usb_wwan.c @@ -0,0 +1,665 @@ +/* +  USB Driver layer for GSM modems + +  Copyright (C) 2005  Matthias Urlichs <smurf@smurf.noris.de> + +  This driver is free software; you can redistribute it and/or modify +  it under the terms of Version 2 of the GNU General Public License as +  published by the Free Software Foundation. + +  Portions copied from the Keyspan driver by Hugh Blemings <hugh@blemings.org> + +  History: see the git log. + +  Work sponsored by: Sigos GmbH, Germany <info@sigos.de> + +  This driver exists because the "normal" serial driver doesn't work too well +  with GSM modems. Issues: +  - data loss -- one single Receive URB is not nearly enough +  - controlling the baud rate doesn't make sense +*/ + +#define DRIVER_VERSION "v0.7.2" +#define DRIVER_AUTHOR "Matthias Urlichs <smurf@smurf.noris.de>" +#define DRIVER_DESC "USB Driver for GSM modems" + +#include <linux/kernel.h> +#include <linux/jiffies.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/tty.h> +#include <linux/tty_flip.h> +#include <linux/module.h> +#include <linux/bitops.h> +#include <linux/usb.h> +#include <linux/usb/serial.h> +#include "usb-wwan.h" + +static int debug; + +void usb_wwan_dtr_rts(struct usb_serial_port *port, int on) +{ +	struct usb_serial *serial = port->serial; +	struct usb_wwan_port_private *portdata; + +	struct usb_wwan_intf_private *intfdata; + +	dbg("%s", __func__); + +	intfdata = port->serial->private; + +	if (!intfdata->send_setup) +		return; + +	portdata = usb_get_serial_port_data(port); +	mutex_lock(&serial->disc_mutex); +	portdata->rts_state = on; +	portdata->dtr_state = on; +	if (serial->dev) +		intfdata->send_setup(port); +	mutex_unlock(&serial->disc_mutex); +} +EXPORT_SYMBOL(usb_wwan_dtr_rts); + +void usb_wwan_set_termios(struct tty_struct *tty, +			  struct usb_serial_port *port, +			  struct ktermios *old_termios) +{ +	struct usb_wwan_intf_private *intfdata = port->serial->private; + +	dbg("%s", __func__); + +	/* Doesn't support option setting */ +	tty_termios_copy_hw(tty->termios, old_termios); + +	if (intfdata->send_setup) +		intfdata->send_setup(port); +} +EXPORT_SYMBOL(usb_wwan_set_termios); + +int usb_wwan_tiocmget(struct tty_struct *tty, struct file *file) +{ +	struct usb_serial_port *port = tty->driver_data; +	unsigned int value; +	struct usb_wwan_port_private *portdata; + +	portdata = usb_get_serial_port_data(port); + +	value = ((portdata->rts_state) ? TIOCM_RTS : 0) | +	    ((portdata->dtr_state) ? TIOCM_DTR : 0) | +	    ((portdata->cts_state) ? TIOCM_CTS : 0) | +	    ((portdata->dsr_state) ? TIOCM_DSR : 0) | +	    ((portdata->dcd_state) ? TIOCM_CAR : 0) | +	    ((portdata->ri_state) ? TIOCM_RNG : 0); + +	return value; +} +EXPORT_SYMBOL(usb_wwan_tiocmget); + +int usb_wwan_tiocmset(struct tty_struct *tty, struct file *file, +		      unsigned int set, unsigned int clear) +{ +	struct usb_serial_port *port = tty->driver_data; +	struct usb_wwan_port_private *portdata; +	struct usb_wwan_intf_private *intfdata; + +	portdata = usb_get_serial_port_data(port); +	intfdata = port->serial->private; + +	if (!intfdata->send_setup) +		return -EINVAL; + +	/* FIXME: what locks portdata fields ? */ +	if (set & TIOCM_RTS) +		portdata->rts_state = 1; +	if (set & TIOCM_DTR) +		portdata->dtr_state = 1; + +	if (clear & TIOCM_RTS) +		portdata->rts_state = 0; +	if (clear & TIOCM_DTR) +		portdata->dtr_state = 0; +	return intfdata->send_setup(port); +} +EXPORT_SYMBOL(usb_wwan_tiocmset); + +/* Write */ +int usb_wwan_write(struct tty_struct *tty, struct usb_serial_port *port, +		   const unsigned char *buf, int count) +{ +	struct usb_wwan_port_private *portdata; +	struct usb_wwan_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); + +	i = 0; +	left = count; +	for (i = 0; left > 0 && i < N_OUT_URB; i++) { +		todo = left; +		if (todo > OUT_BUFLEN) +			todo = OUT_BUFLEN; + +		this_urb = portdata->out_urbs[i]; +		if (test_and_set_bit(i, &portdata->out_busy)) { +			if (time_before(jiffies, +					portdata->tx_start_time[i] + 10 * HZ)) +				continue; +			usb_unlink_urb(this_urb); +			continue; +		} +		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; +	} + +	count -= left; +	dbg("%s: wrote (did %d)", __func__, count); +	return count; +} +EXPORT_SYMBOL(usb_wwan_write); + +static void usb_wwan_indat_callback(struct urb *urb) +{ +	int err; +	int endpoint; +	struct usb_serial_port *port; +	struct tty_struct *tty; +	unsigned char *data = urb->transfer_buffer; +	int status = urb->status; + +	dbg("%s: %p", __func__, urb); + +	endpoint = usb_pipeendpoint(urb->pipe); +	port = urb->context; + +	if (status) { +		dbg("%s: nonzero status: %d on endpoint %02x.", +		    __func__, status, endpoint); +	} else { +		tty = tty_port_tty_get(&port->port); +		if (urb->actual_length) { +			tty_insert_flip_string(tty, data, urb->actual_length); +			tty_flip_buffer_push(tty); +		} else +			dbg("%s: empty read urb received", __func__); +		tty_kref_put(tty); + +		/* Resubmit urb so we continue receiving */ +		if (status != -ESHUTDOWN) { +			err = usb_submit_urb(urb, GFP_ATOMIC); +			if (err && err != -EPERM) +				printk(KERN_ERR "%s: resubmit read urb failed. " +				       "(%d)", __func__, err); +			else +				usb_mark_last_busy(port->serial->dev); +		} + +	} +	return; +} + +static void usb_wwan_outdat_callback(struct urb *urb) +{ +	struct usb_serial_port *port; +	struct usb_wwan_port_private *portdata; +	struct usb_wwan_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(); +			clear_bit(i, &portdata->out_busy); +			break; +		} +	} +} + +int usb_wwan_write_room(struct tty_struct *tty) +{ +	struct usb_serial_port *port = tty->driver_data; +	struct usb_wwan_port_private *portdata; +	int i; +	int data_len = 0; +	struct urb *this_urb; + +	portdata = usb_get_serial_port_data(port); + +	for (i = 0; i < N_OUT_URB; i++) { +		this_urb = portdata->out_urbs[i]; +		if (this_urb && !test_bit(i, &portdata->out_busy)) +			data_len += OUT_BUFLEN; +	} + +	dbg("%s: %d", __func__, data_len); +	return data_len; +} +EXPORT_SYMBOL(usb_wwan_write_room); + +int usb_wwan_chars_in_buffer(struct tty_struct *tty) +{ +	struct usb_serial_port *port = tty->driver_data; +	struct usb_wwan_port_private *portdata; +	int i; +	int data_len = 0; +	struct urb *this_urb; + +	portdata = usb_get_serial_port_data(port); + +	for (i = 0; i < N_OUT_URB; i++) { +		this_urb = portdata->out_urbs[i]; +		/* FIXME: This locking is insufficient as this_urb may +		   go unused during the test */ +		if (this_urb && test_bit(i, &portdata->out_busy)) +			data_len += this_urb->transfer_buffer_length; +	} +	dbg("%s: %d", __func__, data_len); +	return data_len; +} +EXPORT_SYMBOL(usb_wwan_chars_in_buffer); + +int usb_wwan_open(struct tty_struct *tty, struct usb_serial_port *port) +{ +	struct usb_wwan_port_private *portdata; +	struct usb_wwan_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__); + +	/* Start reading from the IN endpoint */ +	for (i = 0; i < N_IN_URB; i++) { +		urb = portdata->in_urbs[i]; +		if (!urb) +			continue; +		err = usb_submit_urb(urb, GFP_KERNEL); +		if (err) { +			dbg("%s: submit urb %d failed (%d) %d", +			    __func__, i, err, urb->transfer_buffer_length); +		} +	} + +	if (intfdata->send_setup) +		intfdata->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; +} +EXPORT_SYMBOL(usb_wwan_open); + +void usb_wwan_close(struct usb_serial_port *port) +{ +	int i; +	struct usb_serial *serial = port->serial; +	struct usb_wwan_port_private *portdata; +	struct usb_wwan_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; +	} +} +EXPORT_SYMBOL(usb_wwan_close); + +/* Helper functions used by usb_wwan_setup_urbs */ +static struct urb *usb_wwan_setup_urb(struct usb_serial *serial, int endpoint, +				      int dir, void *ctx, char *buf, int len, +				      void (*callback) (struct urb *)) +{ +	struct urb *urb; + +	if (endpoint == -1) +		return NULL;	/* endpoint not needed */ + +	urb = usb_alloc_urb(0, GFP_KERNEL);	/* No ISO */ +	if (urb == NULL) { +		dbg("%s: alloc for endpoint %d failed.", __func__, endpoint); +		return NULL; +	} + +	/* Fill URB using supplied data. */ +	usb_fill_bulk_urb(urb, serial->dev, +			  usb_sndbulkpipe(serial->dev, endpoint) | dir, +			  buf, len, callback, ctx); + +	return urb; +} + +/* Setup urbs */ +static void usb_wwan_setup_urbs(struct usb_serial *serial) +{ +	int i, j; +	struct usb_serial_port *port; +	struct usb_wwan_port_private *portdata; + +	dbg("%s", __func__); + +	for (i = 0; i < serial->num_ports; i++) { +		port = serial->port[i]; +		portdata = usb_get_serial_port_data(port); + +		/* Do indat endpoints first */ +		for (j = 0; j < N_IN_URB; ++j) { +			portdata->in_urbs[j] = usb_wwan_setup_urb(serial, +								  port-> +								  bulk_in_endpointAddress, +								  USB_DIR_IN, +								  port, +								  portdata-> +								  in_buffer[j], +								  IN_BUFLEN, +								  usb_wwan_indat_callback); +		} + +		/* outdat endpoints */ +		for (j = 0; j < N_OUT_URB; ++j) { +			portdata->out_urbs[j] = usb_wwan_setup_urb(serial, +								   port-> +								   bulk_out_endpointAddress, +								   USB_DIR_OUT, +								   port, +								   portdata-> +								   out_buffer +								   [j], +								   OUT_BUFLEN, +								   usb_wwan_outdat_callback); +		} +	} +} + +int usb_wwan_startup(struct usb_serial *serial) +{ +	int i, j, err; +	struct usb_serial_port *port; +	struct usb_wwan_port_private *portdata; +	u8 *buffer; + +	dbg("%s", __func__); + +	/* Now setup per port private data */ +	for (i = 0; i < serial->num_ports; i++) { +		port = serial->port[i]; +		portdata = kzalloc(sizeof(*portdata), GFP_KERNEL); +		if (!portdata) { +			dbg("%s: kmalloc for usb_wwan_port_private (%d) failed!.", +			    __func__, i); +			return 1; +		} +		init_usb_anchor(&portdata->delayed); + +		for (j = 0; j < N_IN_URB; j++) { +			buffer = (u8 *) __get_free_page(GFP_KERNEL); +			if (!buffer) +				goto bail_out_error; +			portdata->in_buffer[j] = buffer; +		} + +		for (j = 0; j < N_OUT_URB; j++) { +			buffer = kmalloc(OUT_BUFLEN, GFP_KERNEL); +			if (!buffer) +				goto bail_out_error2; +			portdata->out_buffer[j] = buffer; +		} + +		usb_set_serial_port_data(port, portdata); + +		if (!port->interrupt_in_urb) +			continue; +		err = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); +		if (err) +			dbg("%s: submit irq_in urb failed %d", __func__, err); +	} +	usb_wwan_setup_urbs(serial); +	return 0; + +bail_out_error2: +	for (j = 0; j < N_OUT_URB; j++) +		kfree(portdata->out_buffer[j]); +bail_out_error: +	for (j = 0; j < N_IN_URB; j++) +		if (portdata->in_buffer[j]) +			free_page((unsigned long)portdata->in_buffer[j]); +	kfree(portdata); +	return 1; +} +EXPORT_SYMBOL(usb_wwan_startup); + +static void stop_read_write_urbs(struct usb_serial *serial) +{ +	int i, j; +	struct usb_serial_port *port; +	struct usb_wwan_port_private *portdata; + +	/* Stop reading/writing urbs */ +	for (i = 0; i < serial->num_ports; ++i) { +		port = serial->port[i]; +		portdata = usb_get_serial_port_data(port); +		for (j = 0; j < N_IN_URB; j++) +			usb_kill_urb(portdata->in_urbs[j]); +		for (j = 0; j < N_OUT_URB; j++) +			usb_kill_urb(portdata->out_urbs[j]); +	} +} + +void usb_wwan_disconnect(struct usb_serial *serial) +{ +	dbg("%s", __func__); + +	stop_read_write_urbs(serial); +} +EXPORT_SYMBOL(usb_wwan_disconnect); + +void usb_wwan_release(struct usb_serial *serial) +{ +	int i, j; +	struct usb_serial_port *port; +	struct usb_wwan_port_private *portdata; + +	dbg("%s", __func__); + +	/* Now free them */ +	for (i = 0; i < serial->num_ports; ++i) { +		port = serial->port[i]; +		portdata = usb_get_serial_port_data(port); + +		for (j = 0; j < N_IN_URB; j++) { +			usb_free_urb(portdata->in_urbs[j]); +			free_page((unsigned long) +				  portdata->in_buffer[j]); +			portdata->in_urbs[j] = NULL; +		} +		for (j = 0; j < N_OUT_URB; j++) { +			usb_free_urb(portdata->out_urbs[j]); +			kfree(portdata->out_buffer[j]); +			portdata->out_urbs[j] = NULL; +		} +	} + +	/* Now free per port private data */ +	for (i = 0; i < serial->num_ports; i++) { +		port = serial->port[i]; +		kfree(usb_get_serial_port_data(port)); +	} +} +EXPORT_SYMBOL(usb_wwan_release); + +#ifdef CONFIG_PM +int usb_wwan_suspend(struct usb_serial *serial, pm_message_t message) +{ +	struct usb_wwan_intf_private *intfdata = serial->private; +	int b; + +	dbg("%s entered", __func__); + +	if (message.event & PM_EVENT_AUTO) { +		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; +} +EXPORT_SYMBOL(usb_wwan_suspend); + +static void play_delayed(struct usb_serial_port *port) +{ +	struct usb_wwan_intf_private *data; +	struct usb_wwan_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++; +	} +} + +int usb_wwan_resume(struct usb_serial *serial) +{ +	int i, j; +	struct usb_serial_port *port; +	struct usb_wwan_intf_private *intfdata = serial->private; +	struct usb_wwan_port_private *portdata; +	struct urb *urb; +	int err = 0; + +	dbg("%s entered", __func__); +	/* get the interrupt URBs resubmitted unconditionally */ +	for (i = 0; i < serial->num_ports; i++) { +		port = serial->port[i]; +		if (!port->interrupt_in_urb) { +			dbg("%s: No interrupt URB for port %d", __func__, i); +			continue; +		} +		err = usb_submit_urb(port->interrupt_in_urb, GFP_NOIO); +		dbg("Submitted interrupt URB for port %d (result %d)", i, err); +		if (err < 0) { +			err("%s: Error %d for interrupt URB of port%d", +			    __func__, err, i); +			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); + +		/* skip closed ports */ +		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_ATOMIC); +			if (err < 0) { +				err("%s: Error %d for bulk URB %d", +				    __func__, err, i); +				spin_unlock_irq(&intfdata->susp_lock); +				goto err_out; +			} +		} +		play_delayed(port); +		spin_unlock_irq(&intfdata->susp_lock); +	} +	spin_lock_irq(&intfdata->susp_lock); +	intfdata->suspended = 0; +	spin_unlock_irq(&intfdata->susp_lock); +err_out: +	return err; +} +EXPORT_SYMBOL(usb_wwan_resume); +#endif + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_VERSION(DRIVER_VERSION); +MODULE_LICENSE("GPL"); + +module_param(debug, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "Debug messages");  |