From 2bcf4e9ff9a3f29b21e6d487d89264d191df5822 Mon Sep 17 00:00:00 2001 From: Jason Andryuk Date: Mon, 16 Mar 2020 13:46:07 -0400 Subject: [PATCH 1/4] usb-serial: Move USB_TOKEN_IN into a helper function We'll be adding a loop, so move the code into a helper function. breaks are replaced with returns. While making this change, add braces to single line if statements to comply with coding style and keep checkpatch happy. Signed-off-by: Jason Andryuk Message-id: 20200316174610.115820-2-jandryuk@gmail.com Signed-off-by: Gerd Hoffmann --- hw/usb/dev-serial.c | 80 ++++++++++++++++++++++++++------------------- 1 file changed, 46 insertions(+), 34 deletions(-) diff --git a/hw/usb/dev-serial.c b/hw/usb/dev-serial.c index daac75b7ae..d807ce5771 100644 --- a/hw/usb/dev-serial.c +++ b/hw/usb/dev-serial.c @@ -358,13 +358,56 @@ static void usb_serial_handle_control(USBDevice *dev, USBPacket *p, } } +static void usb_serial_token_in(USBSerialState *s, USBPacket *p) +{ + int first_len, len; + uint8_t header[2]; + + first_len = RECV_BUF - s->recv_ptr; + len = p->iov.size; + if (len <= 2) { + p->status = USB_RET_NAK; + return; + } + header[0] = usb_get_modem_lines(s) | 1; + /* We do not have the uart details */ + /* handle serial break */ + if (s->event_trigger && s->event_trigger & FTDI_BI) { + s->event_trigger &= ~FTDI_BI; + header[1] = FTDI_BI; + usb_packet_copy(p, header, 2); + return; + } else { + header[1] = 0; + } + len -= 2; + if (len > s->recv_used) { + len = s->recv_used; + } + if (!len) { + p->status = USB_RET_NAK; + return; + } + if (first_len > len) { + first_len = len; + } + usb_packet_copy(p, header, 2); + usb_packet_copy(p, s->recv_buf + s->recv_ptr, first_len); + if (len > first_len) { + usb_packet_copy(p, s->recv_buf, len - first_len); + } + s->recv_used -= len; + s->recv_ptr = (s->recv_ptr + len) % RECV_BUF; + + return; +} + static void usb_serial_handle_data(USBDevice *dev, USBPacket *p) { USBSerialState *s = (USBSerialState *)dev; uint8_t devep = p->ep->nr; struct iovec *iov; - uint8_t header[2]; - int i, first_len, len; + int i; switch (p->pid) { case USB_TOKEN_OUT: @@ -382,38 +425,7 @@ static void usb_serial_handle_data(USBDevice *dev, USBPacket *p) case USB_TOKEN_IN: if (devep != 1) goto fail; - first_len = RECV_BUF - s->recv_ptr; - len = p->iov.size; - if (len <= 2) { - p->status = USB_RET_NAK; - break; - } - header[0] = usb_get_modem_lines(s) | 1; - /* We do not have the uart details */ - /* handle serial break */ - if (s->event_trigger && s->event_trigger & FTDI_BI) { - s->event_trigger &= ~FTDI_BI; - header[1] = FTDI_BI; - usb_packet_copy(p, header, 2); - break; - } else { - header[1] = 0; - } - len -= 2; - if (len > s->recv_used) - len = s->recv_used; - if (!len) { - p->status = USB_RET_NAK; - break; - } - if (first_len > len) - first_len = len; - usb_packet_copy(p, header, 2); - usb_packet_copy(p, s->recv_buf + s->recv_ptr, first_len); - if (len > first_len) - usb_packet_copy(p, s->recv_buf, len - first_len); - s->recv_used -= len; - s->recv_ptr = (s->recv_ptr + len) % RECV_BUF; + usb_serial_token_in(s, p); break; default: From 87db78f743668224ae9a5346229faed56ef7ff66 Mon Sep 17 00:00:00 2001 From: Jason Andryuk Date: Mon, 16 Mar 2020 13:46:08 -0400 Subject: [PATCH 2/4] usb-serial: chunk data to wMaxPacketSize usb-serial has issues with xHCI controllers where data is lost in the VM. Inspecting the URBs in the guest, EHCI starts every 64 byte boundary (wMaxPacketSize) with a header. EHCI hands packets into usb_serial_token_in() with size 64, so these cannot cross the 64 byte boundary. The xHCI controller has packets of 512 bytes and the usb-serial will just write through the 64 byte boundary. In the guest, this means data bytes are interpreted as header, so data bytes don't make it out the serial interface. Re-work usb_serial_token_in to chunk data into 64 byte units - 2 byte header and 62 bytes data. The Linux driver reads wMaxPacketSize to find the chunk size, so we match that. Real hardware was observed to pass in 512 byte URBs (496 bytes data + 8 * 2 byte headers). Since usb-serial only buffers 384 bytes of data, usb-serial will pass in 6 64 byte blocks and 1 12 byte partial block for 462 bytes max. Signed-off-by: Jason Andryuk Message-id: 20200316174610.115820-3-jandryuk@gmail.com Signed-off-by: Gerd Hoffmann --- hw/usb/dev-serial.c | 47 ++++++++++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/hw/usb/dev-serial.c b/hw/usb/dev-serial.c index d807ce5771..ec091b6a36 100644 --- a/hw/usb/dev-serial.c +++ b/hw/usb/dev-serial.c @@ -360,15 +360,16 @@ static void usb_serial_handle_control(USBDevice *dev, USBPacket *p, static void usb_serial_token_in(USBSerialState *s, USBPacket *p) { - int first_len, len; + const int max_packet_size = desc_iface0.eps[0].wMaxPacketSize; + int packet_len; uint8_t header[2]; - first_len = RECV_BUF - s->recv_ptr; - len = p->iov.size; - if (len <= 2) { + packet_len = p->iov.size; + if (packet_len <= 2) { p->status = USB_RET_NAK; return; } + header[0] = usb_get_modem_lines(s) | 1; /* We do not have the uart details */ /* handle serial break */ @@ -380,24 +381,34 @@ static void usb_serial_token_in(USBSerialState *s, USBPacket *p) } else { header[1] = 0; } - len -= 2; - if (len > s->recv_used) { - len = s->recv_used; - } - if (!len) { + + if (!s->recv_used) { p->status = USB_RET_NAK; return; } - if (first_len > len) { - first_len = len; + + while (s->recv_used && packet_len > 2) { + int first_len, len; + + len = MIN(packet_len, max_packet_size); + len -= 2; + if (len > s->recv_used) { + len = s->recv_used; + } + + first_len = RECV_BUF - s->recv_ptr; + if (first_len > len) { + first_len = len; + } + usb_packet_copy(p, header, 2); + usb_packet_copy(p, s->recv_buf + s->recv_ptr, first_len); + if (len > first_len) { + usb_packet_copy(p, s->recv_buf, len - first_len); + } + s->recv_used -= len; + s->recv_ptr = (s->recv_ptr + len) % RECV_BUF; + packet_len -= len + 2; } - usb_packet_copy(p, header, 2); - usb_packet_copy(p, s->recv_buf + s->recv_ptr, first_len); - if (len > first_len) { - usb_packet_copy(p, s->recv_buf, len - first_len); - } - s->recv_used -= len; - s->recv_ptr = (s->recv_ptr + len) % RECV_BUF; return; } From 30ad5fdd34d9cfc9f16e73e0229c133d7317a42a Mon Sep 17 00:00:00 2001 From: Jason Andryuk Date: Mon, 16 Mar 2020 13:46:09 -0400 Subject: [PATCH 3/4] usb-serial: Increase receive buffer to 496 A FTDI USB adapter on an xHCI controller can send 512 byte USB packets. These are 8 * ( 2 bytes header + 62 bytes data). A 384 byte receive buffer is insufficient to fill a 512 byte packet, so bump the receive size to 496 ( 512 - 2 * 8 ). Signed-off-by: Jason Andryuk Reviewed-by: Samuel Thibault Message-id: 20200316174610.115820-4-jandryuk@gmail.com Signed-off-by: Gerd Hoffmann --- hw/usb/dev-serial.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hw/usb/dev-serial.c b/hw/usb/dev-serial.c index ec091b6a36..77b964588b 100644 --- a/hw/usb/dev-serial.c +++ b/hw/usb/dev-serial.c @@ -29,7 +29,7 @@ do { printf("usb-serial: " fmt , ## __VA_ARGS__); } while (0) #define DPRINTF(fmt, ...) do {} while(0) #endif -#define RECV_BUF 384 +#define RECV_BUF (512 - (2 * 8)) /* Commands */ #define FTDI_RESET 0 From 647ee9877272d4359659e2595262db0e062c8ffc Mon Sep 17 00:00:00 2001 From: Jason Andryuk Date: Mon, 16 Mar 2020 13:46:10 -0400 Subject: [PATCH 4/4] usb-serial: Fix timeout closing the device Linux guests wait ~30 seconds when closing the emulated /dev/ttyUSB0. During that time, the kernel driver is sending many control URBs requesting GetModemStat (5). Real hardware returns a status with FTDI_THRE (Transmitter Holding Register) and FTDI_TEMT (Transmitter Empty) set. QEMU leaves them clear, and it seems Linux is waiting for FTDI_TEMT to be set to indicate the tx queue is empty before closing. Set the bits when responding to a GetModemStat query and avoid the shutdown delay. Signed-off-by: Jason Andryuk Reviewed-by: Samuel Thibault Message-id: 20200316174610.115820-5-jandryuk@gmail.com Signed-off-by: Gerd Hoffmann --- hw/usb/dev-serial.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hw/usb/dev-serial.c b/hw/usb/dev-serial.c index 77b964588b..d2c03681b7 100644 --- a/hw/usb/dev-serial.c +++ b/hw/usb/dev-serial.c @@ -332,7 +332,7 @@ static void usb_serial_handle_control(USBDevice *dev, USBPacket *p, break; case DeviceInVendor | FTDI_GET_MDM_ST: data[0] = usb_get_modem_lines(s) | 1; - data[1] = 0; + data[1] = FTDI_THRE | FTDI_TEMT; p->actual_length = 2; break; case DeviceOutVendor | FTDI_SET_EVENT_CHR: