|
@@ -39,8 +39,11 @@
|
|
|
* port is opened, as this have a huge impact on the network port
|
|
|
* throughput.
|
|
|
*
|
|
|
- * Interface 2: Standard modem interface - circuit switched interface, should
|
|
|
- * not be used.
|
|
|
+ * Interface 2: Standard modem interface - circuit switched interface, this
|
|
|
+ * can be used to make a standard ppp connection however it
|
|
|
+ * should not be used in conjunction with the IP network interface
|
|
|
+ * enabled for USB performance reasons i.e. if using this set
|
|
|
+ * ideally disable_net=1.
|
|
|
*
|
|
|
*****************************************************************************/
|
|
|
|
|
@@ -63,6 +66,8 @@
|
|
|
#include <linux/usb/cdc.h>
|
|
|
#include <net/arp.h>
|
|
|
#include <asm/byteorder.h>
|
|
|
+#include <linux/serial_core.h>
|
|
|
+#include <linux/serial.h>
|
|
|
|
|
|
|
|
|
#define DRIVER_VERSION "1.2"
|
|
@@ -182,6 +187,41 @@ enum rx_ctrl_state{
|
|
|
RX_PENDING
|
|
|
};
|
|
|
|
|
|
+#define BM_REQUEST_TYPE (0xa1)
|
|
|
+#define B_NOTIFICATION (0x20)
|
|
|
+#define W_VALUE (0x0)
|
|
|
+#define W_INDEX (0x2)
|
|
|
+#define W_LENGTH (0x2)
|
|
|
+
|
|
|
+#define B_OVERRUN (0x1<<6)
|
|
|
+#define B_PARITY (0x1<<5)
|
|
|
+#define B_FRAMING (0x1<<4)
|
|
|
+#define B_RING_SIGNAL (0x1<<3)
|
|
|
+#define B_BREAK (0x1<<2)
|
|
|
+#define B_TX_CARRIER (0x1<<1)
|
|
|
+#define B_RX_CARRIER (0x1<<0)
|
|
|
+
|
|
|
+struct hso_serial_state_notification {
|
|
|
+ u8 bmRequestType;
|
|
|
+ u8 bNotification;
|
|
|
+ u16 wValue;
|
|
|
+ u16 wIndex;
|
|
|
+ u16 wLength;
|
|
|
+ u16 UART_state_bitmap;
|
|
|
+} __attribute__((packed));
|
|
|
+
|
|
|
+struct hso_tiocmget {
|
|
|
+ struct mutex mutex;
|
|
|
+ wait_queue_head_t waitq;
|
|
|
+ int intr_completed;
|
|
|
+ struct usb_endpoint_descriptor *endp;
|
|
|
+ struct urb *urb;
|
|
|
+ struct hso_serial_state_notification serial_state_notification;
|
|
|
+ u16 prev_UART_state_bitmap;
|
|
|
+ struct uart_icount icount;
|
|
|
+};
|
|
|
+
|
|
|
+
|
|
|
struct hso_serial {
|
|
|
struct hso_device *parent;
|
|
|
int magic;
|
|
@@ -219,6 +259,7 @@ struct hso_serial {
|
|
|
spinlock_t serial_lock;
|
|
|
|
|
|
int (*write_data) (struct hso_serial *serial);
|
|
|
+ struct hso_tiocmget *tiocmget;
|
|
|
/* Hacks required to get flow control
|
|
|
* working on the serial receive buffers
|
|
|
* so as not to drop characters on the floor.
|
|
@@ -310,7 +351,7 @@ static void async_get_intf(struct work_struct *data);
|
|
|
static void async_put_intf(struct work_struct *data);
|
|
|
static int hso_put_activity(struct hso_device *hso_dev);
|
|
|
static int hso_get_activity(struct hso_device *hso_dev);
|
|
|
-
|
|
|
+static void tiocmget_intr_callback(struct urb *urb);
|
|
|
/*****************************************************************************/
|
|
|
/* Helping functions */
|
|
|
/*****************************************************************************/
|
|
@@ -1460,25 +1501,217 @@ static int hso_serial_chars_in_buffer(struct tty_struct *tty)
|
|
|
|
|
|
return chars;
|
|
|
}
|
|
|
+int tiocmget_submit_urb(struct hso_serial *serial,
|
|
|
+ struct hso_tiocmget *tiocmget,
|
|
|
+ struct usb_device *usb)
|
|
|
+{
|
|
|
+ int result;
|
|
|
+
|
|
|
+ if (serial->parent->usb_gone)
|
|
|
+ return -ENODEV;
|
|
|
+ usb_fill_int_urb(tiocmget->urb, usb,
|
|
|
+ usb_rcvintpipe(usb,
|
|
|
+ tiocmget->endp->
|
|
|
+ bEndpointAddress & 0x7F),
|
|
|
+ &tiocmget->serial_state_notification,
|
|
|
+ sizeof(struct hso_serial_state_notification),
|
|
|
+ tiocmget_intr_callback, serial,
|
|
|
+ tiocmget->endp->bInterval);
|
|
|
+ result = usb_submit_urb(tiocmget->urb, GFP_ATOMIC);
|
|
|
+ if (result) {
|
|
|
+ dev_warn(&usb->dev, "%s usb_submit_urb failed %d\n", __func__,
|
|
|
+ result);
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+static void tiocmget_intr_callback(struct urb *urb)
|
|
|
+{
|
|
|
+ struct hso_serial *serial = urb->context;
|
|
|
+ struct hso_tiocmget *tiocmget;
|
|
|
+ int status = urb->status;
|
|
|
+ u16 UART_state_bitmap, prev_UART_state_bitmap;
|
|
|
+ struct uart_icount *icount;
|
|
|
+ struct hso_serial_state_notification *serial_state_notification;
|
|
|
+ struct usb_device *usb;
|
|
|
+
|
|
|
+ /* Sanity checks */
|
|
|
+ if (!serial)
|
|
|
+ return;
|
|
|
+ if (status) {
|
|
|
+ log_usb_status(status, __func__);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ tiocmget = serial->tiocmget;
|
|
|
+ if (!tiocmget)
|
|
|
+ return;
|
|
|
+ usb = serial->parent->usb;
|
|
|
+ serial_state_notification = &tiocmget->serial_state_notification;
|
|
|
+ if (serial_state_notification->bmRequestType != BM_REQUEST_TYPE ||
|
|
|
+ serial_state_notification->bNotification != B_NOTIFICATION ||
|
|
|
+ le16_to_cpu(serial_state_notification->wValue) != W_VALUE ||
|
|
|
+ le16_to_cpu(serial_state_notification->wIndex) != W_INDEX ||
|
|
|
+ le16_to_cpu(serial_state_notification->wLength) != W_LENGTH) {
|
|
|
+ dev_warn(&usb->dev,
|
|
|
+ "hso received invalid serial state notification\n");
|
|
|
+ DUMP(serial_state_notification,
|
|
|
+ sizeof(hso_serial_state_notifation))
|
|
|
+ } else {
|
|
|
+
|
|
|
+ UART_state_bitmap = le16_to_cpu(serial_state_notification->
|
|
|
+ UART_state_bitmap);
|
|
|
+ prev_UART_state_bitmap = tiocmget->prev_UART_state_bitmap;
|
|
|
+ icount = &tiocmget->icount;
|
|
|
+ spin_lock(&serial->serial_lock);
|
|
|
+ if ((UART_state_bitmap & B_OVERRUN) !=
|
|
|
+ (prev_UART_state_bitmap & B_OVERRUN))
|
|
|
+ icount->parity++;
|
|
|
+ if ((UART_state_bitmap & B_PARITY) !=
|
|
|
+ (prev_UART_state_bitmap & B_PARITY))
|
|
|
+ icount->parity++;
|
|
|
+ if ((UART_state_bitmap & B_FRAMING) !=
|
|
|
+ (prev_UART_state_bitmap & B_FRAMING))
|
|
|
+ icount->frame++;
|
|
|
+ if ((UART_state_bitmap & B_RING_SIGNAL) &&
|
|
|
+ !(prev_UART_state_bitmap & B_RING_SIGNAL))
|
|
|
+ icount->rng++;
|
|
|
+ if ((UART_state_bitmap & B_BREAK) !=
|
|
|
+ (prev_UART_state_bitmap & B_BREAK))
|
|
|
+ icount->brk++;
|
|
|
+ if ((UART_state_bitmap & B_TX_CARRIER) !=
|
|
|
+ (prev_UART_state_bitmap & B_TX_CARRIER))
|
|
|
+ icount->dsr++;
|
|
|
+ if ((UART_state_bitmap & B_RX_CARRIER) !=
|
|
|
+ (prev_UART_state_bitmap & B_RX_CARRIER))
|
|
|
+ icount->dcd++;
|
|
|
+ tiocmget->prev_UART_state_bitmap = UART_state_bitmap;
|
|
|
+ spin_unlock(&serial->serial_lock);
|
|
|
+ tiocmget->intr_completed = 1;
|
|
|
+ wake_up_interruptible(&tiocmget->waitq);
|
|
|
+ }
|
|
|
+ memset(serial_state_notification, 0,
|
|
|
+ sizeof(struct hso_serial_state_notification));
|
|
|
+ tiocmget_submit_urb(serial,
|
|
|
+ tiocmget,
|
|
|
+ serial->parent->usb);
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * next few functions largely stolen from drivers/serial/serial_core.c
|
|
|
+ */
|
|
|
+/* Wait for any of the 4 modem inputs (DCD,RI,DSR,CTS) to change
|
|
|
+ * - mask passed in arg for lines of interest
|
|
|
+ * (use |'ed TIOCM_RNG/DSR/CD/CTS for masking)
|
|
|
+ * Caller should use TIOCGICOUNT to see which one it was
|
|
|
+ */
|
|
|
+static int
|
|
|
+hso_wait_modem_status(struct hso_serial *serial, unsigned long arg)
|
|
|
+{
|
|
|
+ DECLARE_WAITQUEUE(wait, current);
|
|
|
+ struct uart_icount cprev, cnow;
|
|
|
+ struct hso_tiocmget *tiocmget;
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ tiocmget = serial->tiocmget;
|
|
|
+ if (!tiocmget)
|
|
|
+ return -ENOENT;
|
|
|
+ /*
|
|
|
+ * note the counters on entry
|
|
|
+ */
|
|
|
+ spin_lock_irq(&serial->serial_lock);
|
|
|
+ memcpy(&cprev, &tiocmget->icount, sizeof(struct uart_icount));
|
|
|
+ spin_unlock_irq(&serial->serial_lock);
|
|
|
+ add_wait_queue(&tiocmget->waitq, &wait);
|
|
|
+ for (;;) {
|
|
|
+ spin_lock_irq(&serial->serial_lock);
|
|
|
+ memcpy(&cnow, &tiocmget->icount, sizeof(struct uart_icount));
|
|
|
+ spin_unlock_irq(&serial->serial_lock);
|
|
|
+ set_current_state(TASK_INTERRUPTIBLE);
|
|
|
+ if (((arg & TIOCM_RNG) && (cnow.rng != cprev.rng)) ||
|
|
|
+ ((arg & TIOCM_DSR) && (cnow.dsr != cprev.dsr)) ||
|
|
|
+ ((arg & TIOCM_CD) && (cnow.dcd != cprev.dcd))) {
|
|
|
+ ret = 0;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ schedule();
|
|
|
+ /* see if a signal did it */
|
|
|
+ if (signal_pending(current)) {
|
|
|
+ ret = -ERESTARTSYS;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ cprev = cnow;
|
|
|
+ }
|
|
|
+ current->state = TASK_RUNNING;
|
|
|
+ remove_wait_queue(&tiocmget->waitq, &wait);
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Get counter of input serial line interrupts (DCD,RI,DSR,CTS)
|
|
|
+ * Return: write counters to the user passed counter struct
|
|
|
+ * NB: both 1->0 and 0->1 transitions are counted except for
|
|
|
+ * RI where only 0->1 is counted.
|
|
|
+ */
|
|
|
+static int hso_get_count(struct hso_serial *serial,
|
|
|
+ struct serial_icounter_struct __user *icnt)
|
|
|
+{
|
|
|
+ struct serial_icounter_struct icount;
|
|
|
+ struct uart_icount cnow;
|
|
|
+ struct hso_tiocmget *tiocmget = serial->tiocmget;
|
|
|
+
|
|
|
+ if (!tiocmget)
|
|
|
+ return -ENOENT;
|
|
|
+ spin_lock_irq(&serial->serial_lock);
|
|
|
+ memcpy(&cnow, &tiocmget->icount, sizeof(struct uart_icount));
|
|
|
+ spin_unlock_irq(&serial->serial_lock);
|
|
|
+
|
|
|
+ icount.cts = cnow.cts;
|
|
|
+ icount.dsr = cnow.dsr;
|
|
|
+ icount.rng = cnow.rng;
|
|
|
+ icount.dcd = cnow.dcd;
|
|
|
+ icount.rx = cnow.rx;
|
|
|
+ icount.tx = cnow.tx;
|
|
|
+ icount.frame = cnow.frame;
|
|
|
+ icount.overrun = cnow.overrun;
|
|
|
+ icount.parity = cnow.parity;
|
|
|
+ icount.brk = cnow.brk;
|
|
|
+ icount.buf_overrun = cnow.buf_overrun;
|
|
|
+
|
|
|
+ return copy_to_user(icnt, &icount, sizeof(icount)) ? -EFAULT : 0;
|
|
|
+}
|
|
|
+
|
|
|
|
|
|
static int hso_serial_tiocmget(struct tty_struct *tty, struct file *file)
|
|
|
{
|
|
|
- unsigned int value;
|
|
|
+ int retval;
|
|
|
struct hso_serial *serial = get_serial_by_tty(tty);
|
|
|
- unsigned long flags;
|
|
|
+ struct hso_tiocmget *tiocmget;
|
|
|
+ u16 UART_state_bitmap;
|
|
|
|
|
|
/* sanity check */
|
|
|
if (!serial) {
|
|
|
D1("no tty structures");
|
|
|
return -EINVAL;
|
|
|
}
|
|
|
-
|
|
|
- spin_lock_irqsave(&serial->serial_lock, flags);
|
|
|
- value = ((serial->rts_state) ? TIOCM_RTS : 0) |
|
|
|
+ spin_lock_irq(&serial->serial_lock);
|
|
|
+ retval = ((serial->rts_state) ? TIOCM_RTS : 0) |
|
|
|
((serial->dtr_state) ? TIOCM_DTR : 0);
|
|
|
- spin_unlock_irqrestore(&serial->serial_lock, flags);
|
|
|
+ tiocmget = serial->tiocmget;
|
|
|
+ if (tiocmget) {
|
|
|
|
|
|
- return value;
|
|
|
+ UART_state_bitmap = le16_to_cpu(
|
|
|
+ tiocmget->prev_UART_state_bitmap);
|
|
|
+ if (UART_state_bitmap & B_RING_SIGNAL)
|
|
|
+ retval |= TIOCM_RNG;
|
|
|
+ if (UART_state_bitmap & B_RX_CARRIER)
|
|
|
+ retval |= TIOCM_CD;
|
|
|
+ if (UART_state_bitmap & B_TX_CARRIER)
|
|
|
+ retval |= TIOCM_DSR;
|
|
|
+ }
|
|
|
+ spin_unlock_irq(&serial->serial_lock);
|
|
|
+ return retval;
|
|
|
}
|
|
|
|
|
|
static int hso_serial_tiocmset(struct tty_struct *tty, struct file *file,
|
|
@@ -1520,6 +1753,32 @@ static int hso_serial_tiocmset(struct tty_struct *tty, struct file *file,
|
|
|
USB_CTRL_SET_TIMEOUT);
|
|
|
}
|
|
|
|
|
|
+static int hso_serial_ioctl(struct tty_struct *tty, struct file *file,
|
|
|
+ unsigned int cmd, unsigned long arg)
|
|
|
+{
|
|
|
+ struct hso_serial *serial = get_serial_by_tty(tty);
|
|
|
+ void __user *uarg = (void __user *)arg;
|
|
|
+ int ret = 0;
|
|
|
+ D4("IOCTL cmd: %d, arg: %ld", cmd, arg);
|
|
|
+
|
|
|
+ if (!serial)
|
|
|
+ return -ENODEV;
|
|
|
+ switch (cmd) {
|
|
|
+ case TIOCMIWAIT:
|
|
|
+ ret = hso_wait_modem_status(serial, arg);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case TIOCGICOUNT:
|
|
|
+ ret = hso_get_count(serial, uarg);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ ret = -ENOIOCTLCMD;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
/* starts a transmit */
|
|
|
static void hso_kick_transmit(struct hso_serial *serial)
|
|
|
{
|
|
@@ -1982,7 +2241,10 @@ static int hso_start_serial_device(struct hso_device *hso_dev, gfp_t flags)
|
|
|
serial->shared_int->use_count++;
|
|
|
mutex_unlock(&serial->shared_int->shared_int_lock);
|
|
|
}
|
|
|
-
|
|
|
+ if (serial->tiocmget)
|
|
|
+ tiocmget_submit_urb(serial,
|
|
|
+ serial->tiocmget,
|
|
|
+ serial->parent->usb);
|
|
|
return result;
|
|
|
}
|
|
|
|
|
@@ -1990,6 +2252,7 @@ static int hso_stop_serial_device(struct hso_device *hso_dev)
|
|
|
{
|
|
|
int i;
|
|
|
struct hso_serial *serial = dev2ser(hso_dev);
|
|
|
+ struct hso_tiocmget *tiocmget;
|
|
|
|
|
|
if (!serial)
|
|
|
return -ENODEV;
|
|
@@ -2018,6 +2281,11 @@ static int hso_stop_serial_device(struct hso_device *hso_dev)
|
|
|
}
|
|
|
mutex_unlock(&serial->shared_int->shared_int_lock);
|
|
|
}
|
|
|
+ tiocmget = serial->tiocmget;
|
|
|
+ if (tiocmget) {
|
|
|
+ wake_up_interruptible(&tiocmget->waitq);
|
|
|
+ usb_kill_urb(tiocmget->urb);
|
|
|
+ }
|
|
|
|
|
|
return 0;
|
|
|
}
|
|
@@ -2368,6 +2636,20 @@ exit:
|
|
|
return NULL;
|
|
|
}
|
|
|
|
|
|
+static void hso_free_tiomget(struct hso_serial *serial)
|
|
|
+{
|
|
|
+ struct hso_tiocmget *tiocmget = serial->tiocmget;
|
|
|
+ if (tiocmget) {
|
|
|
+ kfree(tiocmget);
|
|
|
+ if (tiocmget->urb) {
|
|
|
+ usb_free_urb(tiocmget->urb);
|
|
|
+ tiocmget->urb = NULL;
|
|
|
+ }
|
|
|
+ serial->tiocmget = NULL;
|
|
|
+
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
/* Frees an AT channel ( goes for both mux and non-mux ) */
|
|
|
static void hso_free_serial_device(struct hso_device *hso_dev)
|
|
|
{
|
|
@@ -2386,6 +2668,7 @@ static void hso_free_serial_device(struct hso_device *hso_dev)
|
|
|
else
|
|
|
mutex_unlock(&serial->shared_int->shared_int_lock);
|
|
|
}
|
|
|
+ hso_free_tiomget(serial);
|
|
|
kfree(serial);
|
|
|
hso_free_device(hso_dev);
|
|
|
}
|
|
@@ -2397,6 +2680,7 @@ static struct hso_device *hso_create_bulk_serial_device(
|
|
|
struct hso_device *hso_dev;
|
|
|
struct hso_serial *serial;
|
|
|
int num_urbs;
|
|
|
+ struct hso_tiocmget *tiocmget;
|
|
|
|
|
|
hso_dev = hso_create_device(interface, port);
|
|
|
if (!hso_dev)
|
|
@@ -2409,8 +2693,27 @@ static struct hso_device *hso_create_bulk_serial_device(
|
|
|
serial->parent = hso_dev;
|
|
|
hso_dev->port_data.dev_serial = serial;
|
|
|
|
|
|
- if (port & HSO_PORT_MODEM)
|
|
|
+ if (port & HSO_PORT_MODEM) {
|
|
|
num_urbs = 2;
|
|
|
+ serial->tiocmget = kzalloc(sizeof(struct hso_tiocmget),
|
|
|
+ GFP_KERNEL);
|
|
|
+ /* it isn't going to break our heart if serial->tiocmget
|
|
|
+ * allocation fails don't bother checking this.
|
|
|
+ */
|
|
|
+ if (serial->tiocmget) {
|
|
|
+ tiocmget = serial->tiocmget;
|
|
|
+ tiocmget->urb = usb_alloc_urb(0, GFP_KERNEL);
|
|
|
+ if (tiocmget->urb) {
|
|
|
+ mutex_init(&tiocmget->mutex);
|
|
|
+ init_waitqueue_head(&tiocmget->waitq);
|
|
|
+ tiocmget->endp = hso_get_ep(
|
|
|
+ interface,
|
|
|
+ USB_ENDPOINT_XFER_INT,
|
|
|
+ USB_DIR_IN);
|
|
|
+ } else
|
|
|
+ hso_free_tiomget(serial);
|
|
|
+ }
|
|
|
+ }
|
|
|
else
|
|
|
num_urbs = 1;
|
|
|
|
|
@@ -2446,6 +2749,7 @@ static struct hso_device *hso_create_bulk_serial_device(
|
|
|
exit2:
|
|
|
hso_serial_common_free(serial);
|
|
|
exit:
|
|
|
+ hso_free_tiomget(serial);
|
|
|
kfree(serial);
|
|
|
hso_free_device(hso_dev);
|
|
|
return NULL;
|
|
@@ -2958,6 +3262,7 @@ static const struct tty_operations hso_serial_ops = {
|
|
|
.close = hso_serial_close,
|
|
|
.write = hso_serial_write,
|
|
|
.write_room = hso_serial_write_room,
|
|
|
+ .ioctl = hso_serial_ioctl,
|
|
|
.set_termios = hso_serial_set_termios,
|
|
|
.chars_in_buffer = hso_serial_chars_in_buffer,
|
|
|
.tiocmget = hso_serial_tiocmget,
|