|
@@ -12,14 +12,25 @@
|
|
|
History:
|
|
|
|
|
|
2005-05-19 v0.1 Initial version, based on incomplete docs
|
|
|
- and analysis of misbehavior of the standard driver
|
|
|
+ and analysis of misbehavior with the standard driver
|
|
|
2005-05-20 v0.2 Extended the input buffer to avoid losing
|
|
|
random 64-byte chunks of data
|
|
|
2005-05-21 v0.3 implemented chars_in_buffer()
|
|
|
turned on low_latency
|
|
|
simplified the code somewhat
|
|
|
+ 2005-05-24 v0.4 option_write() sometimes deadlocked under heavy load
|
|
|
+ removed some dead code
|
|
|
+ added sponsor notice
|
|
|
+ coding style clean-up
|
|
|
+ 2005-06-20 v0.4.1 add missing braces :-/
|
|
|
+ killed end-of-line whitespace
|
|
|
+ 2005-07-15 v0.4.2 rename WLAN product to FUSION, add FUSION2
|
|
|
+
|
|
|
+ Work sponsored by: Sigos GmbH, Germany <info@sigos.de>
|
|
|
+
|
|
|
*/
|
|
|
-#define DRIVER_VERSION "v0.3"
|
|
|
+
|
|
|
+#define DRIVER_VERSION "v0.4"
|
|
|
#define DRIVER_AUTHOR "Matthias Urlichs <smurf@smurf.noris.de>"
|
|
|
#define DRIVER_DESC "Option Card (PC-Card to) USB to Serial Driver"
|
|
|
|
|
@@ -44,7 +55,6 @@ static int option_write_room (struct usb_serial_port *port);
|
|
|
|
|
|
static void option_instat_callback(struct urb *urb, struct pt_regs *regs);
|
|
|
|
|
|
-
|
|
|
static int option_write (struct usb_serial_port *port,
|
|
|
const unsigned char *buf, int count);
|
|
|
|
|
@@ -60,14 +70,17 @@ static int option_tiocmset (struct usb_serial_port *port, struct file *file,
|
|
|
static int option_send_setup (struct usb_serial_port *port);
|
|
|
|
|
|
/* Vendor and product IDs */
|
|
|
-#define OPTION_VENDOR_ID 0x0AF0
|
|
|
+#define OPTION_VENDOR_ID 0x0AF0
|
|
|
+
|
|
|
+#define OPTION_PRODUCT_OLD 0x5000
|
|
|
+#define OPTION_PRODUCT_FUSION 0x6000
|
|
|
+#define OPTION_PRODUCT_FUSION2 0x6300
|
|
|
|
|
|
-#define OPTION_PRODUCT_OLD 0x5000
|
|
|
-#define OPTION_PRODUCT_WLAN 0x6000
|
|
|
|
|
|
static struct usb_device_id option_ids[] = {
|
|
|
{ USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_OLD) },
|
|
|
- { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_WLAN) },
|
|
|
+ { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_FUSION) },
|
|
|
+ { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_FUSION2) },
|
|
|
{ } /* Terminating entry */
|
|
|
};
|
|
|
|
|
@@ -85,58 +98,62 @@ static struct usb_driver option_driver = {
|
|
|
* recognizes separately, thus num_port=1.
|
|
|
*/
|
|
|
static struct usb_serial_device_type option_3port_device = {
|
|
|
- .owner = THIS_MODULE,
|
|
|
- .name = "Option 3-port card",
|
|
|
- .short_name = "option",
|
|
|
- .id_table = option_ids,
|
|
|
- .num_interrupt_in = NUM_DONT_CARE,
|
|
|
- .num_bulk_in = NUM_DONT_CARE,
|
|
|
- .num_bulk_out = NUM_DONT_CARE,
|
|
|
- .num_ports = 1, /* 3 */
|
|
|
- .open = option_open,
|
|
|
- .close = option_close,
|
|
|
- .write = option_write,
|
|
|
- .write_room = option_write_room,
|
|
|
- .chars_in_buffer = option_chars_in_buffer,
|
|
|
- .throttle = option_rx_throttle,
|
|
|
- .unthrottle = option_rx_unthrottle,
|
|
|
- .ioctl = option_ioctl,
|
|
|
- .set_termios = option_set_termios,
|
|
|
- .break_ctl = option_break_ctl,
|
|
|
- .tiocmget = option_tiocmget,
|
|
|
- .tiocmset = option_tiocmset,
|
|
|
- .attach = option_startup,
|
|
|
- .shutdown = option_shutdown,
|
|
|
- .read_int_callback = option_instat_callback,
|
|
|
+ .owner = THIS_MODULE,
|
|
|
+ .name = "Option 3G data card",
|
|
|
+ .short_name = "option",
|
|
|
+ .id_table = option_ids,
|
|
|
+ .num_interrupt_in = NUM_DONT_CARE,
|
|
|
+ .num_bulk_in = NUM_DONT_CARE,
|
|
|
+ .num_bulk_out = NUM_DONT_CARE,
|
|
|
+ .num_ports = 1, /* 3, but the card reports its ports separately */
|
|
|
+ .open = option_open,
|
|
|
+ .close = option_close,
|
|
|
+ .write = option_write,
|
|
|
+ .write_room = option_write_room,
|
|
|
+ .chars_in_buffer = option_chars_in_buffer,
|
|
|
+ .throttle = option_rx_throttle,
|
|
|
+ .unthrottle = option_rx_unthrottle,
|
|
|
+ .ioctl = option_ioctl,
|
|
|
+ .set_termios = option_set_termios,
|
|
|
+ .break_ctl = option_break_ctl,
|
|
|
+ .tiocmget = option_tiocmget,
|
|
|
+ .tiocmset = option_tiocmset,
|
|
|
+ .attach = option_startup,
|
|
|
+ .shutdown = option_shutdown,
|
|
|
+ .read_int_callback = option_instat_callback,
|
|
|
};
|
|
|
|
|
|
+#ifdef CONFIG_USB_DEBUG
|
|
|
static int debug;
|
|
|
+#else
|
|
|
+#define debug 0
|
|
|
+#endif
|
|
|
+
|
|
|
|
|
|
/* per port private data */
|
|
|
|
|
|
-#define N_IN_URB 4
|
|
|
-#define N_OUT_URB 1
|
|
|
-#define IN_BUFLEN 1024
|
|
|
-#define OUT_BUFLEN 1024
|
|
|
+#define N_IN_URB 4
|
|
|
+#define N_OUT_URB 1
|
|
|
+#define IN_BUFLEN 1024
|
|
|
+#define OUT_BUFLEN 128
|
|
|
|
|
|
struct option_port_private {
|
|
|
/* Input endpoints and buffer for this port */
|
|
|
- struct urb *in_urbs[N_IN_URB];
|
|
|
- char in_buffer[N_IN_URB][IN_BUFLEN];
|
|
|
+ struct urb *in_urbs[N_IN_URB];
|
|
|
+ char in_buffer[N_IN_URB][IN_BUFLEN];
|
|
|
/* Output endpoints and buffer for this port */
|
|
|
- struct urb *out_urbs[N_OUT_URB];
|
|
|
- char out_buffer[N_OUT_URB][OUT_BUFLEN];
|
|
|
+ struct urb *out_urbs[N_OUT_URB];
|
|
|
+ char out_buffer[N_OUT_URB][OUT_BUFLEN];
|
|
|
|
|
|
/* Settings for the port */
|
|
|
- int rts_state; /* Handshaking pins (outputs) */
|
|
|
- int dtr_state;
|
|
|
- int cts_state; /* Handshaking pins (inputs) */
|
|
|
- int dsr_state;
|
|
|
- int dcd_state;
|
|
|
- int ri_state;
|
|
|
- // int break_on;
|
|
|
-
|
|
|
- unsigned long tx_start_time[N_OUT_URB];
|
|
|
+ int rts_state; /* Handshaking pins (outputs) */
|
|
|
+ int dtr_state;
|
|
|
+ int cts_state; /* Handshaking pins (inputs) */
|
|
|
+ int dsr_state;
|
|
|
+ int dcd_state;
|
|
|
+ int ri_state;
|
|
|
+
|
|
|
+ unsigned long tx_start_time[N_OUT_URB];
|
|
|
};
|
|
|
|
|
|
|
|
@@ -190,13 +207,13 @@ static void
|
|
|
option_break_ctl (struct usb_serial_port *port, int break_state)
|
|
|
{
|
|
|
/* Unfortunately, I don't know how to send a break */
|
|
|
- dbg("%s", __FUNCTION__);
|
|
|
+ dbg("%s", __FUNCTION__);
|
|
|
}
|
|
|
|
|
|
|
|
|
static void
|
|
|
option_set_termios (struct usb_serial_port *port,
|
|
|
- struct termios *old_termios)
|
|
|
+ struct termios *old_termios)
|
|
|
{
|
|
|
dbg("%s", __FUNCTION__);
|
|
|
|
|
@@ -204,10 +221,10 @@ option_set_termios (struct usb_serial_port *port,
|
|
|
}
|
|
|
|
|
|
static int
|
|
|
-option_tiocmget(struct usb_serial_port *port, struct file *file)
|
|
|
+option_tiocmget (struct usb_serial_port *port, struct file *file)
|
|
|
{
|
|
|
- unsigned int value;
|
|
|
- struct option_port_private *portdata;
|
|
|
+ unsigned int value;
|
|
|
+ struct option_port_private *portdata;
|
|
|
|
|
|
portdata = usb_get_serial_port_data(port);
|
|
|
|
|
@@ -225,7 +242,7 @@ static int
|
|
|
option_tiocmset (struct usb_serial_port *port, struct file *file,
|
|
|
unsigned int set, unsigned int clear)
|
|
|
{
|
|
|
- struct option_port_private *portdata;
|
|
|
+ struct option_port_private *portdata;
|
|
|
|
|
|
portdata = usb_get_serial_port_data(port);
|
|
|
|
|
@@ -250,71 +267,50 @@ option_ioctl (struct usb_serial_port *port, struct file *file,
|
|
|
|
|
|
/* Write */
|
|
|
static int
|
|
|
-option_write(struct usb_serial_port *port,
|
|
|
- const unsigned char *buf, int count)
|
|
|
+option_write (struct usb_serial_port *port,
|
|
|
+ const unsigned char *buf, int count)
|
|
|
{
|
|
|
- struct option_port_private *portdata;
|
|
|
- int i;
|
|
|
- int left, todo;
|
|
|
- struct urb *this_urb = NULL; /* spurious */
|
|
|
- int err;
|
|
|
+ struct option_port_private *portdata;
|
|
|
+ int i;
|
|
|
+ int left, todo;
|
|
|
+ struct urb *this_urb = NULL; /* spurious */
|
|
|
+ int err;
|
|
|
|
|
|
portdata = usb_get_serial_port_data(port);
|
|
|
|
|
|
dbg("%s: write (%d chars)", __FUNCTION__, count);
|
|
|
|
|
|
-#if 0
|
|
|
- spin_lock(&port->lock);
|
|
|
- if (port->write_urb_busy) {
|
|
|
- spin_unlock(&port->lock);
|
|
|
- dbg("%s: already writing", __FUNCTION__);
|
|
|
- return 0;
|
|
|
- }
|
|
|
- port->write_urb_busy = 1;
|
|
|
- spin_unlock(&port->lock);
|
|
|
-#endif
|
|
|
-
|
|
|
i = 0;
|
|
|
left = count;
|
|
|
- while (left>0) {
|
|
|
+ for (i=0; left > 0 && i < N_OUT_URB; i++) {
|
|
|
todo = left;
|
|
|
if (todo > OUT_BUFLEN)
|
|
|
todo = OUT_BUFLEN;
|
|
|
|
|
|
- for (;i < N_OUT_URB; i++) {
|
|
|
- /* Check we have a valid urb/endpoint before we use it... */
|
|
|
- this_urb = portdata->out_urbs[i];
|
|
|
- if (this_urb->status != -EINPROGRESS)
|
|
|
- break;
|
|
|
+ this_urb = portdata->out_urbs[i];
|
|
|
+ if (this_urb->status == -EINPROGRESS) {
|
|
|
if (this_urb->transfer_flags & URB_ASYNC_UNLINK)
|
|
|
continue;
|
|
|
if (time_before(jiffies, portdata->tx_start_time[i] + 10 * HZ))
|
|
|
continue;
|
|
|
this_urb->transfer_flags |= URB_ASYNC_UNLINK;
|
|
|
usb_unlink_urb(this_urb);
|
|
|
+ continue;
|
|
|
}
|
|
|
-
|
|
|
- if (i == N_OUT_URB) {
|
|
|
- /* no bulk out free! */
|
|
|
- dbg("%s: no output urb -- left %d", __FUNCTION__,count-left);
|
|
|
-#if 0
|
|
|
- port->write_urb_busy = 0;
|
|
|
-#endif
|
|
|
- return count-left;
|
|
|
- }
|
|
|
+ if (this_urb->status != 0)
|
|
|
+ dbg("usb_write %p failed (err=%d)", this_urb, this_urb->status);
|
|
|
|
|
|
dbg("%s: endpoint %d buf %d", __FUNCTION__, usb_pipeendpoint(this_urb->pipe), i);
|
|
|
|
|
|
+ /* send the data */
|
|
|
memcpy (this_urb->transfer_buffer, buf, todo);
|
|
|
-
|
|
|
- /* send the data out the bulk port */
|
|
|
this_urb->transfer_buffer_length = todo;
|
|
|
|
|
|
this_urb->transfer_flags &= ~URB_ASYNC_UNLINK;
|
|
|
this_urb->dev = port->serial->dev;
|
|
|
err = usb_submit_urb(this_urb, GFP_ATOMIC);
|
|
|
if (err) {
|
|
|
- dbg("usb_submit_urb %p (write bulk) failed (%d,, has %d)", this_urb, err, this_urb->status);
|
|
|
+ dbg("usb_submit_urb %p (write bulk) failed (%d, has %d)", this_urb, err, this_urb->status);
|
|
|
continue;
|
|
|
}
|
|
|
portdata->tx_start_time[i] = jiffies;
|
|
@@ -323,9 +319,6 @@ option_write(struct usb_serial_port *port,
|
|
|
}
|
|
|
|
|
|
count -= left;
|
|
|
-#if 0
|
|
|
- port->write_urb_busy = 0;
|
|
|
-#endif
|
|
|
dbg("%s: wrote (did %d)", __FUNCTION__, count);
|
|
|
return count;
|
|
|
}
|
|
@@ -333,7 +326,7 @@ option_write(struct usb_serial_port *port,
|
|
|
static void
|
|
|
option_indat_callback (struct urb *urb, struct pt_regs *regs)
|
|
|
{
|
|
|
- int i, err;
|
|
|
+ int i, err;
|
|
|
int endpoint;
|
|
|
struct usb_serial_port *port;
|
|
|
struct tty_struct *tty;
|
|
@@ -444,10 +437,11 @@ option_write_room (struct usb_serial_port *port)
|
|
|
|
|
|
portdata = usb_get_serial_port_data(port);
|
|
|
|
|
|
- for (i=0; i < N_OUT_URB; i++)
|
|
|
+ for (i=0; i < N_OUT_URB; i++) {
|
|
|
this_urb = portdata->out_urbs[i];
|
|
|
if (this_urb && this_urb->status != -EINPROGRESS)
|
|
|
data_len += OUT_BUFLEN;
|
|
|
+ }
|
|
|
|
|
|
dbg("%s: %d", __FUNCTION__, data_len);
|
|
|
return data_len;
|
|
@@ -464,11 +458,11 @@ option_chars_in_buffer (struct usb_serial_port *port)
|
|
|
|
|
|
portdata = usb_get_serial_port_data(port);
|
|
|
|
|
|
- for (i=0; i < N_OUT_URB; i++)
|
|
|
+ for (i=0; i < N_OUT_URB; i++) {
|
|
|
this_urb = portdata->out_urbs[i];
|
|
|
if (this_urb && this_urb->status == -EINPROGRESS)
|
|
|
data_len += this_urb->transfer_buffer_length;
|
|
|
-
|
|
|
+ }
|
|
|
dbg("%s: %d", __FUNCTION__, data_len);
|
|
|
return data_len;
|
|
|
}
|
|
@@ -477,10 +471,10 @@ option_chars_in_buffer (struct usb_serial_port *port)
|
|
|
static int
|
|
|
option_open (struct usb_serial_port *port, struct file *filp)
|
|
|
{
|
|
|
- struct option_port_private *portdata;
|
|
|
- struct usb_serial *serial = port->serial;
|
|
|
- int i, err;
|
|
|
- struct urb *urb;
|
|
|
+ struct option_port_private *portdata;
|
|
|
+ struct usb_serial *serial = port->serial;
|
|
|
+ int i, err;
|
|
|
+ struct urb *urb;
|
|
|
|
|
|
portdata = usb_get_serial_port_data(port);
|
|
|
|
|
@@ -528,7 +522,7 @@ option_open (struct usb_serial_port *port, struct file *filp)
|
|
|
}
|
|
|
|
|
|
static inline void
|
|
|
-stop_urb(struct urb *urb)
|
|
|
+stop_urb (struct urb *urb)
|
|
|
{
|
|
|
if (urb && urb->status == -EINPROGRESS) {
|
|
|
urb->transfer_flags &= ~URB_ASYNC_UNLINK;
|
|
@@ -537,11 +531,11 @@ stop_urb(struct urb *urb)
|
|
|
}
|
|
|
|
|
|
static void
|
|
|
-option_close(struct usb_serial_port *port, struct file *filp)
|
|
|
+option_close (struct usb_serial_port *port, struct file *filp)
|
|
|
{
|
|
|
- int i;
|
|
|
- struct usb_serial *serial = port->serial;
|
|
|
- struct option_port_private *portdata;
|
|
|
+ int i;
|
|
|
+ struct usb_serial *serial = port->serial;
|
|
|
+ struct option_port_private *portdata;
|
|
|
|
|
|
dbg("%s", __FUNCTION__);
|
|
|
portdata = usb_get_serial_port_data(port);
|
|
@@ -589,11 +583,11 @@ option_setup_urb (struct usb_serial *serial, int endpoint,
|
|
|
|
|
|
/* Setup urbs */
|
|
|
static void
|
|
|
-option_setup_urbs(struct usb_serial *serial)
|
|
|
+option_setup_urbs (struct usb_serial *serial)
|
|
|
{
|
|
|
- int j;
|
|
|
- struct usb_serial_port *port;
|
|
|
- struct option_port_private *portdata;
|
|
|
+ int j;
|
|
|
+ struct usb_serial_port *port;
|
|
|
+ struct option_port_private *portdata;
|
|
|
|
|
|
dbg("%s", __FUNCTION__);
|
|
|
|
|
@@ -617,7 +611,7 @@ option_setup_urbs(struct usb_serial *serial)
|
|
|
|
|
|
|
|
|
static int
|
|
|
-option_send_setup(struct usb_serial_port *port)
|
|
|
+option_send_setup (struct usb_serial_port *port)
|
|
|
{
|
|
|
struct usb_serial *serial = port->serial;
|
|
|
struct option_port_private *portdata;
|
|
@@ -644,9 +638,9 @@ option_send_setup(struct usb_serial_port *port)
|
|
|
static int
|
|
|
option_startup (struct usb_serial *serial)
|
|
|
{
|
|
|
- int i, err;
|
|
|
- struct usb_serial_port *port;
|
|
|
- struct option_port_private *portdata;
|
|
|
+ int i, err;
|
|
|
+ struct usb_serial_port *port;
|
|
|
+ struct option_port_private *portdata;
|
|
|
|
|
|
dbg("%s", __FUNCTION__);
|
|
|
|
|
@@ -677,9 +671,9 @@ option_startup (struct usb_serial *serial)
|
|
|
static void
|
|
|
option_shutdown (struct usb_serial *serial)
|
|
|
{
|
|
|
- int i, j;
|
|
|
- struct usb_serial_port *port;
|
|
|
- struct option_port_private *portdata;
|
|
|
+ int i, j;
|
|
|
+ struct usb_serial_port *port;
|
|
|
+ struct option_port_private *portdata;
|
|
|
|
|
|
dbg("%s", __FUNCTION__);
|
|
|
|
|
@@ -724,6 +718,8 @@ MODULE_DESCRIPTION(DRIVER_DESC);
|
|
|
MODULE_VERSION(DRIVER_VERSION);
|
|
|
MODULE_LICENSE("GPL");
|
|
|
|
|
|
+#ifdef CONFIG_USB_DEBUG
|
|
|
module_param(debug, bool, S_IRUGO | S_IWUSR);
|
|
|
MODULE_PARM_DESC(debug, "Debug messages");
|
|
|
+#endif
|
|
|
|