diff options
author | Richard Ash <richard@audacityteam.org> | 2009-08-17 10:32:53 +0100 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2009-09-15 12:01:35 -0700 |
commit | 713e5f69d07ce6ff4ac9c2637ece4829a8127606 (patch) | |
tree | 15aa643a19da42b083b7cb63f464d6dd48674466 /drivers/staging | |
parent | 72ba94ec61d1ceb9b80e317f72548b1e88e539f4 (diff) |
Staging: quatech_usb2: close, read, and some ioctl support
This patch implements close(), read(), write() and some ioctls, and
fixes some implementation issues in open(). Compared to the previous
patch it doesn't suffer an oops in the module code, however if you try
to open any of the serial devices a second time then an oops occurs in
tty_open(), presumably because my code isn't playing nicely on the
previous close():
Aug 13 11:44:01 [kernel] WARNING: at drivers/char/tty_io.c:1268 tty_open+0x3e5/0x46d()
Aug 13 11:44:01 [kernel] Hardware name: HP Compaq dc5100 MT(PW097ET)
Aug 13 11:44:01 [kernel] Modules linked in: quatech_usb2(C) usbserial snd_seq snd_seq_device snd_pcm_oss snd_mixer_oss nls_iso8859_1 cifs xt_limit xt_NFLOG nfnetlink_log nfnetlink xt_tcpudp nf_conntrack_ipv4 nf_defrag_ipv4 xt_state nf_conntrack iptable_filter ip_tables x_tables i915 fb drm i2c_algo_bit cfbcopyarea i2c_core video backlight output cfbimgblt cfbfillrect snd_intel8x0 snd_ac97_codec ehci_hcd ac97_bus uhci_hcd snd_pcm snd_timer usbcore snd ohci1394 psmouse ide_cd_mod cdrom tg3 soundcore snd_page_alloc ieee1394 libphy intel_agp agpgart floppy evdev [last unloaded: usbserial]
Aug 13 11:44:01 [kernel] Pid: 26765, comm: stty Tainted: G C 2.6.31-rc5-git3-gkh #7
Aug 13 11:44:01 [kernel] Call Trace:
Aug 13 11:44:01 [kernel] [<c11dbef8>] ? tty_open+0x3e5/0x46d
Aug 13 11:44:01 [kernel] [<c10264e2>] warn_slowpath_common+0x88/0xb0
Aug 13 11:44:01 [kernel] [<c11dbef8>] ? tty_open+0x3e5/0x46d
Aug 13 11:44:01 [kernel] [<c102652b>] warn_slowpath_null+0x21/0x3b
Aug 13 11:44:01 [kernel] [<c11dbef8>] tty_open+0x3e5/0x46d
Aug 13 11:44:01 [kernel] [<c10883d1>] chrdev_open+0x77/0x113
Aug 13 11:44:01 [kernel] [<c1083a5d>] __dentry_open+0xb8/0x230
Aug 13 11:44:01 [kernel] [<c108835a>] ? chrdev_open+0x0/0x113
Aug 13 11:44:01 [kernel] [<c1084993>] nameidata_to_filp+0x61/0x6a
Aug 13 11:44:01 [kernel] [<c1091144>] do_filp_open+0x248/0x7cd
Aug 13 11:44:01 [kernel] [<c106f14f>] ? __do_fault+0x2ba/0x3b2
Aug 13 11:44:01 [kernel] [<c109a337>] ? alloc_fd+0x6a/0xf1
Aug 13 11:44:01 [kernel] [<c1083804>] do_sys_open+0x5f/0x12b
Aug 13 11:44:01 [kernel] [<c1083947>] sys_open+0x2e/0x47
Aug 13 11:44:01 [kernel] [<c1002e4f>] sysenter_do_call+0x12/0x26
Aug 13 11:44:01 [kernel] ---[ end trace 1d6b9e2cd7636394 ]---
Aug 13 11:44:01 [kernel] BUG: unable to handle kernel NULL pointer dereference at 00000004
Aug 13 11:44:01 [kernel] IP: [<c1086538>] file_move+0x26/0x47
Aug 13 11:44:01 [kernel] *pde = 00000000
Aug 13 11:44:01 [kernel] Modules linked in: quatech_usb2(C) usbserial snd_seq snd_seq_device snd_pcm_oss snd_mixer_oss nls_iso8859_1 cifs xt_limit xt_NFLOG nfnetlink_log nfnetlink xt_tcpudp nf_conntrack_ipv4 nf_defrag_ipv4 xt_state nf_conntrack iptable_filter ip_tables x_tables i915 fb drm i2c_algo_bit cfbcopyarea i2c_core video backlight output cfbimgblt cfbfillrect snd_intel8x0 snd_ac97_codec ehci_hcd ac97_bus uhci_hcd snd_pcm snd_timer usbcore snd ohci1394 psmouse ide_cd_mod cdrom tg3 soundcore snd_page_alloc ieee1394 libphy intel_agp agpgart floppy evdev [last unloaded: usbserial]
Aug 13 11:44:01 [kernel] Pid: 26765, comm: stty Tainted: G WC (2.6.31-rc5-git3-gkh #7) HP Compaq dc5100 MT(PW097ET)
Aug 13 11:44:01 [kernel] EIP: 0060:[<c1086538>] EFLAGS: 00010282 CPU: 0
Aug 13 11:44:01 [kernel] EIP is at file_move+0x26/0x47
Aug 13 11:44:01 [kernel] EAX: 00000000 EBX: e593b508 ECX: ea7e9900 EDX: f734a888
Aug 13 11:44:01 [kernel] ESI: 00000000 EDI: 0bc00004 EBP: d8923e10 ESP: d8923e08
Aug 13 11:44:01 [kernel] DS: 007b ES: 007b FS: 0000 GS: 00e0 SS: 0068
Aug 13 11:44:01 [kernel] 2ba6efc8 c8c93180 d8923e48 c11dbca7 ea7e9900 eed37e6c 00000000 00008800
Aug 13 11:44:01 [kernel] <0> 00000000 e593b400 e593b400 00000004 2ba6efc8 c8c93188 00000000 eed37e6c
Aug 13 11:44:01 [kernel] <0> d8923e68 c10883d1 ea7e9900 2ba6efc8 2ba6efc8 ea7e9900 eed37e6c ffffffe9
Aug 13 11:44:01 [kernel] [<c11dbca7>] ? tty_open+0x194/0x46d
Aug 13 11:44:01 [kernel] [<c10883d1>] ? chrdev_open+0x77/0x113
Aug 13 11:44:01 [kernel] [<c1083a5d>] ? __dentry_open+0xb8/0x230
Aug 13 11:44:01 [kernel] [<c108835a>] ? chrdev_open+0x0/0x113
Aug 13 11:44:01 [kernel] [<c1084993>] ? nameidata_to_filp+0x61/0x6a
Aug 13 11:44:01 [kernel] [<c1091144>] ? do_filp_open+0x248/0x7cd
Aug 13 11:44:01 [kernel] [<c106f14f>] ? __do_fault+0x2ba/0x3b2
Aug 13 11:44:01 [kernel] [<c109a337>] ? alloc_fd+0x6a/0xf1
Aug 13 11:44:01 [kernel] [<c1083804>] ? do_sys_open+0x5f/0x12b
Aug 13 11:44:01 [kernel] [<c1083947>] ? sys_open+0x2e/0x47
Aug 13 11:44:01 [kernel] [<c1002e4f>] ? sysenter_do_call+0x12/0x26
Aug 13 11:44:01 [kernel] ---[ end trace 1d6b9e2cd7636395 ]---
Read and Write also do not work at the moment, and I'm fairly sure that
the URB completion callbacks are not running. Why this is I don't know,
and haven't tried to investigate.
Signed-off-by: Richard Ash <richard@audacityteam.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/staging')
-rw-r--r-- | drivers/staging/quatech_usb2/quatech_usb2.c | 694 |
1 files changed, 646 insertions, 48 deletions
diff --git a/drivers/staging/quatech_usb2/quatech_usb2.c b/drivers/staging/quatech_usb2/quatech_usb2.c index 0eaea2e7ce8..bbbf9b88fac 100644 --- a/drivers/staging/quatech_usb2/quatech_usb2.c +++ b/drivers/staging/quatech_usb2/quatech_usb2.c @@ -39,23 +39,33 @@ static int debug; #define QU2BOX232 0x40 /* RS232 mode on MEI devices */ #define QU2BOXSPD9600 0x60 /* set speed to 9600 baud */ #define FIFO_DEPTH 1024 /* size of hardware fifos */ +#define QT2_TX_HEADER_LENGTH 5 +/* length of the header sent to the box with each write URB */ + /* directions for USB transfers */ #define USBD_TRANSFER_DIRECTION_IN 0xc0 #define USBD_TRANSFER_DIRECTION_OUT 0x40 -/* special Quatech command IDs */ + +/* special Quatech command IDs. These are pushed down the + USB control pipe to get the box on the end to do things */ #define QT_SET_GET_DEVICE 0xc2 #define QT_OPEN_CLOSE_CHANNEL 0xca /*#define QT_GET_SET_PREBUF_TRIG_LVL 0xcc -#define QT_SET_ATF 0xcd -#define QT_GET_SET_REGISTER 0xc0*/ +#define QT_SET_ATF 0xcd*/ +#define QT2_GET_SET_REGISTER 0xc0 #define QT_GET_SET_UART 0xc1 /*#define QT_HW_FLOW_CONTROL_MASK 0xc5 #define QT_SW_FLOW_CONTROL_MASK 0xc6 #define QT_SW_FLOW_CONTROL_DISABLE 0xc7 #define QT_BREAK_CONTROL 0xc8 -#define QT_STOP_RECEIVE 0xe0 -#define QT_FLUSH_DEVICE 0xc4*/ +#define QT_STOP_RECEIVE 0xe0*/ +#define QT2_FLUSH_DEVICE 0xc4 #define QT_GET_SET_QMCR 0xe1 + +/* sorts of flush we can do on */ +#define QT2_FLUSH_RX 0x00 +#define QT2_FLUSH_TX 0x01 + /* port setting constants */ #define SERIAL_MCR_DTR 0x01 #define SERIAL_MCR_RTS 0x02 @@ -85,6 +95,19 @@ static int debug; #define SERIAL_LSR_FE 0x08 #define SERIAL_LSR_BI 0x10 +/* value of Line Status Register when UART has completed + * emptying data out on the line */ +#define QT2_LSR_TEMT 0x40 + +/* register numbers on each UART, for use with qt2_box_[get|set]_register*/ +#define QT2_XMT_HOLD_REGISTER 0x00 +#define QT2_XVR_BUFFER_REGISTER 0x00 +#define QT2_FIFO_CONTROL_REGISTER 0x02 +#define QT2_LINE_CONTROL_REGISTER 0x03 +#define QT2_MODEM_CONTROL_REGISTER 0x04 +#define QT2_LINE_STATUS_REGISTER 0x05 +#define QT2_MODEM_STATUS_REGISTER 0x06 + /* handy macros for doing escape sequence parsing on data reads */ #define THISCHAR ((unsigned char *)(urb->transfer_buffer))[i] #define NEXTCHAR ((unsigned char *)(urb->transfer_buffer))[i + 1] @@ -118,7 +141,7 @@ static struct usb_driver quausb2_usb_driver = { * alongside the usb_serial_port structure * @param read_urb_busy Flag indicating that port->read_urb is in use * @param close_pending flag indicating that this port is in the process of - * being closed. + * being closed (and so no new reads / writes should be started). * @param shadowLSR Last received state of the line status register, holds the * value of the line status flags from the port * @param shadowMSR Last received state of the modem status register, holds @@ -127,10 +150,14 @@ static struct usb_driver quausb2_usb_driver = { * the serial port * @param xmit_fifo_room_bytes free space available in the transmit fifo * for this port on the box - * @param rcv_flush Flag indicating that a receive flush has been requested by + * @param rcv_flush Flag indicating that a receive flush has occured on * the hardware. - * @param xmit_flush Flag indicating that a transmit flush has been requested by + * @param xmit_flush Flag indicating that a transmit flush has been processed by * the hardware. + * @param fifo_empty_flag Flag indicating that the (per-port) buffer on the + * device is empty. + * @param tx_fifo_room Number of bytes room left in the buffer + * @param tx_pending_bytes Number of bytes waiting to be sent */ struct quatech2_port { int magic; @@ -140,8 +167,11 @@ struct quatech2_port { __u8 shadowMSR; int xmit_pending_bytes; int xmit_fifo_room_bytes; - char rcv_flush; - char xmit_flush; + bool rcv_flush; + bool xmit_flush; +/* bool fifo_empty_flag; + int tx_fifo_room;*/ + int tx_pending_bytes; char active; /* someone has this device open */ unsigned char *xfer_to_tty_buffer; @@ -151,7 +181,6 @@ struct quatech2_port { __u8 shadowLCR; /* last LCR value received */ __u8 shadowMCR; /* last MCR value received */ char RxHolding; - char fifo_empty_flag; struct semaphore pend_xmit_sem; /* locks this structure */ spinlock_t lock; }; @@ -164,11 +193,16 @@ struct quatech2_port { * the read stream is currently directed to. Escape sequences in the read * stream will change this around as data arrives from different ports on the * box + * @buffer_size: The max size buffer each URB can take, used to set the size of + * the buffers allocated for writing to each port on the device (we need to + * store this because it is known only to the endpoint, but used each time a + * port is opened and a new buffer is allocated. */ struct quatech2_dev { bool ReadBulkStopped; char open_ports; struct usb_serial_port *current_port; + int buffer_size; }; /* structure which holds line and modem status flags */ @@ -200,6 +234,7 @@ static int qt2_closeboxchannel(struct usb_serial *serial, __u16 static int qt2_conf_uart(struct usb_serial *serial, unsigned short Uart_Number, unsigned short divisor, unsigned char LCR); static void qt2_read_bulk_callback(struct urb *urb); +static void qt2_write_bulk_callback(struct urb *urb); static void qt2_process_line_status(struct usb_serial_port *port, unsigned char LineStatus); static void qt2_process_modem_status(struct usb_serial_port *port, @@ -212,6 +247,19 @@ static void qt2_process_rcv_flush(struct usb_serial_port *port); static void qt2_process_xmit_flush(struct usb_serial_port *port); static void qt2_process_rx_char(struct usb_serial_port *port, unsigned char data); +static int qt2_box_get_register(struct usb_serial *serial, + unsigned char uart_number, unsigned short register_num, + __u8 *pValue); +static int qt2_box_set_register(struct usb_serial *serial, + unsigned short Uart_Number, unsigned short Register_Num, + unsigned short Value); +static int qt2_box_flush(struct usb_serial *serial, unsigned char uart_number, + unsigned short rcv_or_xmit); +static int qt2_write(struct tty_struct *tty, struct usb_serial_port *port, + const unsigned char *buf, int count); +static int qt2_tiocmget(struct tty_struct *tty, struct file *file); +static int qt2_tiocmset(struct tty_struct *tty, struct file *file, + unsigned int set, unsigned int clear); /* implementation functions, roughly in order of use, are here */ static int qt2_calc_num_ports(struct usb_serial *serial) @@ -291,7 +339,7 @@ static int qt2_attach(struct usb_serial *serial) return -ENOMEM; } spin_lock_init(&qt2_port->lock); - usb_set_serial_port_data(port, qt2_port); + qt2_set_port_private(port, qt2_port); } /* gain access to port[0]'s structure because we want to store @@ -307,15 +355,17 @@ static int qt2_attach(struct usb_serial *serial) if ((endpoint->bEndpointAddress & 0x80) && ((endpoint->bmAttributes & 3) == 0x02)) { /* we found a bulk in endpoint */ - dbg("found bulk in at 0x%x", + dbg("found bulk in at %#.2x", endpoint->bEndpointAddress); } if (((endpoint->bEndpointAddress & 0x80) == 0x00) && ((endpoint->bmAttributes & 3) == 0x02)) { /* we found a bulk out endpoint */ - dbg("found bulk out at 0x%x", + dbg("found bulk out at %#.2x", endpoint->bEndpointAddress); + qt2_dev->buffer_size = endpoint->wMaxPacketSize; + /* max size of URB needs recording for the device */ } } /* end printing endpoint addresses */ @@ -368,7 +418,8 @@ static void qt2_release(struct usb_serial *serial) usb_set_serial_port_data(port, NULL); } } -/* This function is called once per serial port on the device. +/* This function is called once per serial port on the device, when + * that port is opened by a userspace application. * The tty_struct and the usb_serial_port belong to this port, * i.e. there are multiple ones for a multi-port device. * However the usb_serial_port structure has a back-pointer @@ -379,12 +430,11 @@ static void qt2_release(struct usb_serial *serial) * This is most helpful if the device shares resources (e.g. end * points) between different ports */ -int qt2_open(struct tty_struct *tty, - struct usb_serial_port *port, struct file *filp) +int qt2_open(struct tty_struct *tty, struct usb_serial_port *port) { struct usb_serial *serial; /* device structure */ struct usb_serial_port *port0; /* first port structure on device */ - struct quatech2_port *port_extra; /* extra data for this port */ + struct quatech2_port *port_extra; /* extra data for this port */ struct quatech2_port *port0_extra; /* extra data for first port */ struct quatech2_dev *dev_extra; /* extra data for the device */ struct qt2_status_data ChannelData; @@ -405,22 +455,23 @@ int qt2_open(struct tty_struct *tty, } dev_extra = qt2_get_dev_private(serial); /* get the device private data */ + if (dev_extra == NULL) { + dbg("device extra data pointer is null"); + return -ENODEV; + } port0 = serial->port[0]; /* get the first port's device structure */ - if (port_paranoia_check(port, __func__)) { + if (port_paranoia_check(port0, __func__)) { dbg("port0 usb_serial_port struct failed sanity check"); return -ENODEV; } + port_extra = qt2_get_port_private(port); port0_extra = qt2_get_port_private(port0); - if (port_extra == NULL || port0_extra == NULL) { - dbg("failed to get private data for port and port0"); + dbg("failed to get private data for port or port0"); return -ENODEV; } - usb_clear_halt(serial->dev, port->write_urb->pipe); - usb_clear_halt(serial->dev, port->read_urb->pipe); - /* FIXME: are these needed? Does it even do anything useful? */ /* get the modem and line status values from the UART */ status = qt2_openboxchannel(serial, port->number, @@ -433,12 +484,11 @@ int qt2_open(struct tty_struct *tty, port_extra->shadowLSR = ChannelData.line_status & (SERIAL_LSR_OE | SERIAL_LSR_PE | SERIAL_LSR_FE | SERIAL_LSR_BI); - port_extra->shadowMSR = ChannelData.modem_status & (SERIAL_MSR_CTS | SERIAL_MSR_DSR | SERIAL_MSR_RI | SERIAL_MSR_CD); - port_extra->fifo_empty_flag = true; +/* port_extra->fifo_empty_flag = true;*/ dbg("qt2_openboxchannel on channel %d completed.", port->number); @@ -463,19 +513,57 @@ int qt2_open(struct tty_struct *tty, * when we do a write to a port, we will use the same endpoint * regardless of the port, with a 5-byte header added on to * tell the box which port it should eventually come out of, so we only - * need the one set of endpoints. + * need the one set of endpoints. We will have one URB per port for + * writing, so that multiple ports can be writing at once. * Finally we need a bulk in URB to use for background reads from the * device, which will deal with uplink data from the box to host. */ - dbg("port number is %d", port->number); dbg("serial number is %d", port->serial->minor); dbg("port0 bulk in endpoint is %#.2x", port0->bulk_in_endpointAddress); dbg("port0 bulk out endpoint is %#.2x", port0->bulk_out_endpointAddress); + /* set up write_urb for bulk out transfers on this port. The USB + * serial framework will have allocated a blank URB, buffer etc for + * port0 when it put the endpoints there, but not for any of the other + * ports on the device because there are no more endpoints. Thus we + * have to allocate our own URBs for ports 1-7 + */ + if (port->write_urb == NULL) { + dbg("port->write_urb == NULL, allocating one"); + port->write_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!port->write_urb) { + err("Allocating write URB failed"); + return -ENOMEM; + } + /* buffer same size as port0 */ + port->bulk_out_size = dev_extra->buffer_size; + port->bulk_out_buffer = kmalloc(port->bulk_out_size, + GFP_KERNEL); + if (!port->bulk_out_buffer) { + err("Couldn't allocate bulk_out_buffer"); + return -ENOMEM; + } + } + if (serial->dev == NULL) + dbg("serial->dev == NULL"); + dbg("port->bulk_out_size is %d", port->bulk_out_size); + + usb_fill_bulk_urb(port->write_urb, serial->dev, + usb_sndbulkpipe(serial->dev, + port0->bulk_out_endpointAddress), + port->bulk_out_buffer, + port->bulk_out_size, + qt2_write_bulk_callback, + port); + /*port_extra->fifo_empty_flag = true; + port_extra->tx_fifo_room = FIFO_DEPTH;*/ + port_extra->tx_pending_bytes = 0; + if (dev_extra->open_ports == 0) { - /* this is first port to be opened, so need some URBs */ - /* initialise read_urb for bulk in transfers */ + /* this is first port to be opened, so need the read URB + * initialised for bulk in transfers (this is shared amongst + * all the ports on the device) */ usb_fill_bulk_urb(port0->read_urb, serial->dev, usb_rcvbulkpipe(serial->dev, port0->bulk_in_endpointAddress), @@ -490,25 +578,444 @@ int qt2_open(struct tty_struct *tty, result = usb_submit_urb(port->read_urb, GFP_KERNEL); if (result) { dev_err(&port->dev, - "%s - Error %d submitting bulk in urb\n", + "%s(): Error %d submitting bulk in urb", __func__, result); port_extra->read_urb_busy = false; + dev_extra->ReadBulkStopped = true; } + + /* When the first port is opened, initialise the value of + * current_port in dev_extra to this port, so it is set + * to something. Once the box sends data it will send the + * relevant escape sequences to get it to the right port anyway + */ + dev_extra->current_port = port; } /* initialize our wait queues */ init_waitqueue_head(&port_extra->wait); - /* remember to store port_extra and port0 back again at end !*/ + /* remember to store dev_extra, port_extra and port0_extra back again at + * end !*/ qt2_set_port_private(port, port_extra); qt2_set_port_private(serial->port[0], port0_extra); qt2_set_dev_private(serial, dev_extra); - dev_extra->open_ports++; /* one more port opened */ + dev_extra->open_ports++; /* one more port opened */ return 0; } +/* called when a port is closed by userspace */ +/* Setting close_pending should keep new data from being written out, + * once all the data in the enpoint buffers is moved out we won't get + * any more. */ +/* BoxStopReceive would keep any more data from coming from a given + * port, but isn't called by the vendor driver, although their comments + * mention it. Should it be used here to stop the inbound data + * flow? + */ +static void qt2_close(struct usb_serial_port *port) +{ + /* time out value for flush loops */ + unsigned long jift; + struct quatech2_port *port_extra; /* extra data for this port */ + struct usb_serial *serial; /* device structure */ + struct quatech2_dev *dev_extra; /* extra data for the device */ + __u8 lsr_value = 0; /* value of Line Status Register */ + int status; /* result of last USB comms function */ + + dbg("%s(): port %d", __func__, port->number); + serial = port->serial; /* get the parent device structure */ + dev_extra = qt2_get_dev_private(serial); + /* get the device private data */ + port_extra = qt2_get_port_private(port); /* port private data */ + + /* to check we have successfully flushed the buffers on the hardware, + * we set the flags indicating flushes have occured to false, then ask + * for flushes to occur, then sit in a timed loop until either we + * get notified back that the flushes have happened (good) or we get + * tired of waiting for the flush to happen and give up (bad). + */ + port_extra->rcv_flush = false; + port_extra->xmit_flush = false; + qt2_box_flush(serial, port->number, QT2_FLUSH_TX); /* flush tx buffer */ + qt2_box_flush(serial, port->number, QT2_FLUSH_RX); /* flush rx buffer */ + /* now wait for the flags to magically go back to being true */ + jift = jiffies + (10 * HZ); + do { + if ((port_extra->rcv_flush == true) && + (port_extra->xmit_flush == true)) { + dbg("Flush completed"); + break; + } + schedule(); + } while (jiffies <= jift); + + /* we can now (and only now) stop reading data */ + port_extra->close_pending = true; + dbg("%s(): port_extra->close_pending = true", __func__); + /* although the USB side is now empty, the UART itself may + * still be pushing characters out over the line, so we have to + * wait testing the actual line status until the lines change + * indicating that the data is done transfering. */ + jift = jiffies + (10 * HZ); /* 10 sec timeout */ + do { + status = qt2_box_get_register(serial, port->number, + QT2_LINE_STATUS_REGISTER, &lsr_value); + if (status < 0) { + dbg("%s(): qt2_box_get_register failed", __func__); + break; + } + if ((lsr_value & QT2_LSR_TEMT)) { + dbg("UART done sending"); + break; + } + schedule(); + } while (jiffies <= jift); + + status = qt2_closeboxchannel(serial, port->number); + if (status < 0) + dbg("%s(): port %d qt2_box_open_close_channel failed", + __func__, port->number); + /* to avoid leaking URBs, we should now free the write_urb for this + * port and set the pointer to null so that next time the port is opened + * a new URB is allocated. This avoids leaking URBs when the device is + * removed */ + usb_free_urb(port->write_urb); + kfree(port->bulk_out_buffer); + port->bulk_out_buffer = NULL; + port->bulk_out_size = 0; + + dev_extra->open_ports--; + dbg("%s(): Exit, dev_extra->open_ports = %d", __func__, + dev_extra->open_ports); +} + +/* called to deliver writes from the next layer up to the device */ +static int qt2_write(struct tty_struct *tty, struct usb_serial_port *port, + const unsigned char *buf, int count) +{ + struct usb_serial *serial; /* parent device struct */ + __u8 header_array[5]; /* header used to direct writes to the correct + port on the device */ + struct quatech2_port *port_extra; /* extra data for this port */ + + int result; + + /* get the parent device of the port */ + serial = port->serial; + if (serial == NULL) + return -ENODEV; + dbg("%s(): port %d", __func__, port->number); + + if (count <= 0) { + dbg("%s(): write request of <= 0 bytes", __func__); + return 0; /* no bytes written */ + } + port_extra = qt2_get_port_private(port); + + /* check if the write urb is already in use, i.e. data already being + * sent to this port */ + if ((port->write_urb->status == -EINPROGRESS)) { + /* Fifo hasn't been emptied since last write to this port */ + dbg("%s(): already writing, port->write_urb->status == " + "-EINPROGRESS", __func__); + /* schedule_work(&port->work); commented in vendor driver */ + return 0; + } else if (port_extra->tx_pending_bytes >= FIFO_DEPTH) { + /* such a lot queued up that we will fill the buffer again as + * soon as it does empty? Overflowed buffer? */ + dbg("%s(): already writing, port_extra->tx_pending_bytes >=" + " FIFO_DEPTH", __func__); + /* schedule_work(&port->work); commented in vendor driver */ + return 0; + } + + /* We must fill the first 5 bytes of anything we sent with a transmit + * header which directes the data to the correct port. The maximum + * size we can send out in one URB is port->bulk_out_size, which caps + * the number of bytes of real data we can send in each write. As the + * semantics of write allow us to write less than we were give, we cap + * the maximum we will ever write to the device as 5 bytes less than + * one URB's worth, by reducing the value of the count argument + * appropriately*/ + if (count > port->bulk_out_size - QT2_TX_HEADER_LENGTH) + count = port->bulk_out_size - QT2_TX_HEADER_LENGTH; + /* we must also ensure that the FIFO at the other end can cope with the + * URB we send it, otherwise it will have problems. As above, we can + * restrict the write size by just shrinking count.*/ + if (count > (FIFO_DEPTH - port_extra->tx_pending_bytes)) + count = FIFO_DEPTH - port_extra->tx_pending_bytes; + /* now build the header for transmission */ + header_array[0] = 0x1b; + header_array[1] = 0x1b; + header_array[2] = (__u8)port->number; + header_array[3] = (__u8)count; + header_array[4] = (__u8)count >> 8; + /* copy header into URB */ + memcpy(port->write_urb->transfer_buffer, header_array, + QT2_TX_HEADER_LENGTH); + /* and actual data to write */ + memcpy(port->write_urb->transfer_buffer + 5, buf, count); + + dbg("%s(): first data byte to send = %#.2x", __func__, *buf); + + /* set up our urb */ + usb_fill_bulk_urb(port->write_urb, serial->dev, + usb_sndbulkpipe(serial->dev, + port->bulk_out_endpointAddress), + port->write_urb->transfer_buffer, count + 5, + (qt2_write_bulk_callback), port); + /* send the data out the bulk port */ + result = usb_submit_urb(port->write_urb, GFP_ATOMIC); + if (result) { + /* error couldn't submit urb */ + result = 0; + dbg("%s(): failed submitting write urb, error %d", + __func__, result); + } else { + port_extra->tx_pending_bytes += (count - QT2_TX_HEADER_LENGTH); + /*port->fifo_empty_flag = false; + port->xmit_fifo_room_bytes = FIFO_DEPTH - + port->xmit_pending_bytes;*/ + result = count; + dbg("%s(): submitted write urb, returning %d", __func__, +result); + } + return result; +} + +static int qt2_write_room(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + /* parent usb_serial_port pointer */ + struct quatech2_port *port_extra; /* extra data for this port */ + int room = 0; + port_extra = qt2_get_port_private(port); + + if (port_extra->close_pending == true) { + dbg("%s(): port_extra->close_pending == true", __func__); + return -ENODEV; + } + + dbg("%s(): port %d", __func__, port->number); + if ((port->write_urb->status != -EINPROGRESS) && + (port_extra->tx_pending_bytes == 0)) + room = port->bulk_out_size - QT2_TX_HEADER_LENGTH; + return room; +} + +static int qt2_chars_in_buffer(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + /* parent usb_serial_port pointer */ + int chars = 0; + struct quatech2_port *port_extra; /* extra data for this port */ + port_extra = qt2_get_port_private(port); + + dbg("%s(): port %d", __func__, port->number); + if ((port->write_urb->status == -EINPROGRESS) && + (port_extra->tx_pending_bytes != 0)) + chars = port->write_urb->transfer_buffer_length; + dbg("%s(): returns %d", __func__, chars); + return chars; +} + + +static int qt2_ioctl(struct tty_struct *tty, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct usb_serial_port *port = tty->driver_data; + struct usb_serial *serial = port->serial; + __u8 mcr_value; /* Modem Control Register value */ + __u8 msr_value; /* Modem Status Register value */ + unsigned short prev_msr_value; /* Previous value of Modem Status + * Register used to implement waiting for a line status change to + * occur */ + struct quatech2_port *port_extra; /* extra data for this port */ + DECLARE_WAITQUEUE(wait, current); + /* Declare a wait queue named "wait" */ + + unsigned int value; + int status; + unsigned int UartNumber; + + if (serial == NULL) + return -ENODEV; + UartNumber = tty->index - serial->minor; + port_extra = qt2_get_port_private(port); + + dbg("%s(): port %d, UartNumber %d, tty =0x%p", __func__, + port->number, UartNumber, tty); + + if (cmd == TIOCMGET) { + return qt2_tiocmget(tty, file); + /* same as tiocmget function */ + } else if (cmd == TIOCMSET) { + if (copy_from_user(&value, (unsigned int *)arg, + sizeof(unsigned int))) + return -EFAULT; + return qt2_tiocmset(tty, file, value, 0); + /* same as tiocmset function */ + } else if (cmd == TIOCMBIS || cmd == TIOCMBIC) { + status = qt2_box_get_register(port->serial, UartNumber, + QT2_MODEM_CONTROL_REGISTER, &mcr_value); + if (status < 0) + return -ESPIPE; + if (copy_from_user(&value, (unsigned int *)arg, + sizeof(unsigned int))) + return -EFAULT; + + switch (cmd) { + case TIOCMBIS: + if (value & TIOCM_RTS) + mcr_value |= SERIAL_MCR_RTS; + if (value & TIOCM_DTR) + mcr_value |= SERIAL_MCR_DTR; + if (value & TIOCM_LOOP) + mcr_value |= SERIAL_MCR_LOOP; + break; + case TIOCMBIC: + if (value & TIOCM_RTS) + mcr_value &= ~SERIAL_MCR_RTS; + if (value & TIOCM_DTR) + mcr_value &= ~SERIAL_MCR_DTR; + if (value & TIOCM_LOOP) + mcr_value &= ~SERIAL_MCR_LOOP; + break; + default: + break; + } /* end of local switch on cmd */ + status = qt2_box_set_register(port->serial, UartNumber, + QT2_MODEM_CONTROL_REGISTER, mcr_value); + if (status < 0) { + return -ESPIPE; + } else { + port_extra->shadowMCR = mcr_value; + return 0; + } + } else if (cmd == TIOCMIWAIT) { + dbg("%s() port %d, cmd == TIOCMIWAIT enter", + __func__, port->number); + prev_msr_value = port_extra->shadowMSR & SERIAL_MSR_MASK; + while (1) { + add_wait_queue(&port_extra->wait, &wait); + set_current_state(TASK_INTERRUPTIBLE); + schedule(); + dbg("%s(): port %d, cmd == TIOCMIWAIT here\n", + __func__, port->number); + remove_wait_queue(&port_extra->wait, &wait); + /* see if a signal woke us up */ + if (signal_pending(current)) + return -ERESTARTSYS; + msr_value = port_extra->shadowMSR & SERIAL_MSR_MASK; + if (msr_value == prev_msr_value) + return -EIO; /* no change - error */ + if ((arg & TIOCM_RNG && + ((prev_msr_value & SERIAL_MSR_RI) == + (msr_value & SERIAL_MSR_RI))) || + (arg & TIOCM_DSR && + ((prev_msr_value & SERIAL_MSR_DSR) == + (msr_value & SERIAL_MSR_DSR))) || + (arg & TIOCM_CD && + ((prev_msr_value & SERIAL_MSR_CD) == + (msr_value & SERIAL_MSR_CD))) || + (arg & TIOCM_CTS && + ((prev_msr_value & SERIAL_MSR_CTS) == + (msr_value & SERIAL_MSR_CTS)))) { + return 0; + } + } /* end inifinite while */ + } else { + /* any other ioctls we don't know about come here */ + dbg("%s(): No ioctl for that one. port = %d", __func__, + port->number); + return -ENOIOCTLCMD; + } +} + +static int qt2_tiocmget(struct tty_struct *tty, struct file *file) +{ + struct usb_serial_port *port = tty->driver_data; + struct usb_serial *serial = port->serial; + + __u8 mcr_value; /* Modem Control Register value */ + __u8 msr_value; /* Modem Status Register value */ + unsigned int result = 0; + int status; + unsigned int UartNumber; + + if (serial == NULL) + return -ENODEV; + + dbg("%s(): port %d, tty =0x%p", __func__, port->number, tty); + UartNumber = tty->index - serial->minor; + dbg("UartNumber is %d", UartNumber); + + status = qt2_box_get_register(port->serial, UartNumber, + QT2_MODEM_CONTROL_REGISTER, &mcr_value); + if (status >= 0) { + status = qt2_box_get_register(port->serial, UartNumber, + QT2_MODEM_STATUS_REGISTER, &msr_value); + } + if (status >= 0) { + result = ((mcr_value & SERIAL_MCR_DTR) ? TIOCM_DTR : 0) + /*DTR set */ + | ((mcr_value & SERIAL_MCR_RTS) ? TIOCM_RTS : 0) + /*RTS set */ + | ((msr_value & SERIAL_MSR_CTS) ? TIOCM_CTS : 0) + /* CTS set */ + | ((msr_value & SERIAL_MSR_CD) ? TIOCM_CAR : 0) + /*Carrier detect set */ + | ((msr_value & SERIAL_MSR_RI) ? TIOCM_RI : 0) + /* Ring indicator set */ + | ((msr_value & SERIAL_MSR_DSR) ? TIOCM_DSR : 0); + /* DSR set */ + return result; + } else { + return -ESPIPE; + } +} + +static int qt2_tiocmset(struct tty_struct *tty, struct file *file, + unsigned int set, unsigned int clear) +{ + struct usb_serial_port *port = tty->driver_data; + struct usb_serial *serial = port->serial; + __u8 mcr_value; /* Modem Control Register value */ + int status; + unsigned int UartNumber; + + if (serial == NULL) + return -ENODEV; + + UartNumber = tty->index - serial->minor; + dbg("%s(): port %d, UartNumber %d", __func__, port->number, UartNumber); + + status = qt2_box_get_register(port->serial, UartNumber, + QT2_MODEM_CONTROL_REGISTER, &mcr_value); + if (status < 0) + return -ESPIPE; + + /* Turn off RTS, DTR and loopback, then only turn on what was asked + * for */ + mcr_value &= ~(SERIAL_MCR_RTS | SERIAL_MCR_DTR | SERIAL_MCR_LOOP); + if (set & TIOCM_RTS) + mcr_value |= SERIAL_MCR_RTS; + if (set & TIOCM_DTR) + mcr_value |= SERIAL_MCR_DTR; + if (set & TIOCM_LOOP) + mcr_value |= SERIAL_MCR_LOOP; + + status = qt2_box_set_register(port->serial, UartNumber, + QT2_MODEM_CONTROL_REGISTER, mcr_value); + if (status < 0) + return -ESPIPE; + else + return 0; +} + /* internal, private helper functions for the driver */ /* Power up the FPGA in the box to get it working */ @@ -643,7 +1150,7 @@ static int qt2_conf_uart(struct usb_serial *serial, unsigned short Uart_Number, return result; } -/** @brief Callback for asynchronous submission of URBs on bulk in +/** @brief Callback for asynchronous submission of read URBs on bulk in * endpoints * * Registered in qt2_open_port(), used to deal with incomming data @@ -678,7 +1185,7 @@ static void qt2_read_bulk_callback(struct urb *urb) if (urb->status) { /* read didn't go well */ dev_extra->ReadBulkStopped = true; - dbg("%s(): nonzero write bulk status received: %d", + dbg("%s(): nonzero bulk read status received: %d", __func__, urb->status); return; } @@ -908,6 +1415,34 @@ __func__); return; } + +/** @brief Callback for asynchronous submission of write URBs on bulk in + * endpoints + * + * Registered in qt2_write(), used to deal with outgoing data + * to the box. + */ +static void qt2_write_bulk_callback(struct urb *urb) +{ + struct usb_serial_port *port = (struct usb_serial_port *)urb->context; + struct usb_serial *serial = port->serial; + dbg("%s(): port %d", __func__, port->number); + if (!serial) { + dbg("%s(): bad serial pointer, exiting", __func__); + return; + } + if (urb->status) { + dbg("%s(): nonzero write bulk status received: %d", + __func__, urb->status); + return; + } + + /*port_softint((void *) serial); commented in vendor driver */ + schedule_work(&port->work); + dbg("%s(): port %d exit", __func__, port->number); + return; +} + static void qt2_process_line_status(struct usb_serial_port *port, unsigned char LineStatus) { @@ -922,8 +1457,11 @@ static void qt2_process_modem_status(struct usb_serial_port *port, /* obtain the private structure for the port */ struct quatech2_port *port_extra = qt2_get_port_private(port); port_extra->shadowMSR = ModemStatus; - /* ?? */ wake_up_interruptible(&port_extra->wait); + /* this wakes up the otherwise indefinitely waiting code for + * the TIOCMIWAIT ioctl, so that it can notice that + * port_extra->shadowMSR has changed and the ioctl needs to return. + */ } static void qt2_process_xmit_empty(struct usb_serial_port *port, @@ -980,6 +1518,66 @@ static void qt2_process_rx_char(struct usb_serial_port *port, /*tty_flip_buffer_push(tty);*/ } } + +/** @brief Retreive the value of a register from the device + * + * Issues a GET_REGISTER vendor-spcific request over the USB control + * pipe to obtain a value back from a specific register on a specific + * UART + * @param serial Serial device handle to access the device through + * @param uart_number Which UART the value is wanted from + * @param register_num Which register to read the value from + * @param pValue Pointer to somewhere to put the retrieved value + */ +static int qt2_box_get_register(struct usb_serial *serial, + unsigned char uart_number, unsigned short register_num, + __u8 *pValue) +{ + int result; + result = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0), + QT2_GET_SET_REGISTER, 0xC0, register_num, + uart_number, (void *)pValue, sizeof(*pValue), 300); + return result; +} + +/** qt2_box_set_register + * Issue a SET_REGISTER vendor-specific request on the default control pipe + */ +static int qt2_box_set_register(struct usb_serial *serial, + unsigned short Uart_Number, unsigned short Register_Num, + unsigned short Value) +{ + int result; + unsigned short reg_and_byte; + + reg_and_byte = Value; + reg_and_byte = reg_and_byte << 8; + reg_and_byte = reg_and_byte + Register_Num; + + result = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), + QT2_GET_SET_REGISTER, 0x40, reg_and_byte, + Uart_Number, NULL, 0, 300); + return result; +} + + +/** @brief Request the Tx or Rx buffers on the USB side be flushed + * + * Tx flush: When all the currently buffered data has been sent, send an escape + * sequence back up the data stream to us + * Rx flush: add a flag in the data stream now so we know when it's made it's + * way up to us. + */ +static int qt2_box_flush(struct usb_serial *serial, unsigned char uart_number, + unsigned short rcv_or_xmit) +{ + int result; + result = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0), + QT2_FLUSH_DEVICE, 0x40, rcv_or_xmit, uart_number, NULL, 0, + 300); + return result; +} + /* * last things in file: stuff to register this driver into the generic * USB serial framework. @@ -995,20 +1593,22 @@ static struct usb_serial_driver quatech2_device = { .id_table = quausb2_id_table, .num_ports = 8, .open = qt2_open, - /*.close = qt_close, - .write = qt_write, - .write_room = qt_write_room, - .chars_in_buffer = qt_chars_in_buffer, - .throttle = qt_throttle, + .close = qt2_close, + .write = qt2_write, + .write_room = qt2_write_room, + .chars_in_buffer = qt2_chars_in_buffer, + /*.throttle = qt_throttle, .unthrottle = qt_unthrottle,*/ .calc_num_ports = qt2_calc_num_ports, - /*.ioctl = qt_ioctl, - .set_termios = qt_set_termios, - .break_ctl = qt_break, - .tiocmget = qt_tiocmget, - .tiocmset = qt_tiocmset,*/ + .ioctl = qt2_ioctl, + /*.set_termios = qt_set_termios, + .break_ctl = qt_break,*/ + .tiocmget = qt2_tiocmget, + .tiocmset = qt2_tiocmset, .attach = qt2_attach, .release = qt2_release, + .read_bulk_callback = qt2_read_bulk_callback, + .write_bulk_callback = qt2_write_bulk_callback, }; static int __init quausb2_usb_init(void) @@ -1038,8 +1638,6 @@ failed_usb_serial_register: return retval; } - - static void __exit quausb2_usb_exit(void) { usb_deregister(&quausb2_usb_driver); |