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

Commit 7ea3a9ad authored by Denis Joseph Barrow's avatar Denis Joseph Barrow Committed by David S. Miller
Browse files

hso: Add TIOCM ioctl handling.



Makes TIOCM ioctls for Data Carrier Detect & related functions
work like /drivers/serial/serial-core.c potentially needed 
for pppd & similar user programs.   

Signed-off-by: default avatarDenis Joseph Barrow <D.Barow@option.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 52429eb2
Loading
Loading
Loading
Loading
+317 −12
Original line number Diff line number Diff line
@@ -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,