From patchwork Thu Nov 1 15:54:37 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Gerd Hoffmann X-Patchwork-Id: 196313 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id B6BEE2C034B for ; Fri, 2 Nov 2012 04:51:30 +1100 (EST) Received: from localhost ([::1]:53742 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1TTx8G-0007Ko-Eh for incoming@patchwork.ozlabs.org; Thu, 01 Nov 2012 11:56:36 -0400 Received: from eggs.gnu.org ([208.118.235.92]:35204) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1TTx6f-0004PR-RY for qemu-devel@nongnu.org; Thu, 01 Nov 2012 11:55:00 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1TTx6a-0008G4-CI for qemu-devel@nongnu.org; Thu, 01 Nov 2012 11:54:57 -0400 Received: from mx1.redhat.com ([209.132.183.28]:20656) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1TTx6a-0008Es-2M for qemu-devel@nongnu.org; Thu, 01 Nov 2012 11:54:52 -0400 Received: from int-mx11.intmail.prod.int.phx2.redhat.com (int-mx11.intmail.prod.int.phx2.redhat.com [10.5.11.24]) by mx1.redhat.com (8.14.4/8.14.4) with ESMTP id qA1FspnR001956 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK) for ; Thu, 1 Nov 2012 11:54:51 -0400 Received: from rincewind.home.kraxel.org (ovpn-116-42.ams2.redhat.com [10.36.116.42]) by int-mx11.intmail.prod.int.phx2.redhat.com (8.14.4/8.14.4) with ESMTP id qA1FsmLB028207; Thu, 1 Nov 2012 11:54:49 -0400 Received: by rincewind.home.kraxel.org (Postfix, from userid 500) id DACE445FF5; Thu, 1 Nov 2012 16:54:45 +0100 (CET) From: Gerd Hoffmann To: qemu-devel@nongnu.org Date: Thu, 1 Nov 2012 16:54:37 +0100 Message-Id: <1351785284-15384-25-git-send-email-kraxel@redhat.com> In-Reply-To: <1351785284-15384-1-git-send-email-kraxel@redhat.com> References: <1351785284-15384-1-git-send-email-kraxel@redhat.com> X-Scanned-By: MIMEDefang 2.68 on 10.5.11.24 X-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x X-Received-From: 209.132.183.28 Cc: Hans de Goede , Gerd Hoffmann Subject: [Qemu-devel] [PATCH 24/31] usb: Add packet combining functions X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org From: Hans de Goede Currently we only do pipelining for output endpoints, since to properly support short-not-ok semantics we can only have one outstanding input packet. Since the ehci and uhci controllers have a limited per td packet size guests will split large input transfers to into multiple packets, and since we don't pipeline these, this comes with a serious performance penalty. This patch adds helper functions to (re-)combine packets which belong to 1 transfer at the guest device-driver level into 1 large transger. This can be used by (redirection) usb-devices to enable pipelining for input endpoints. This patch will combine packets together until a transfer terminating packet is encountered. A terminating packet is a packet which meets one or more of the following conditions: 1) The packet size is *not* a multiple of the endpoint max packet size 2) The packet does *not* have its short-not-ok flag set 3) The packet has its interrupt-on-complete flag set The short-not-ok flag of the combined packet is that of the terminating packet. Multiple combined packets may be submitted to the device, if the combined packets do not have their short-not-ok flag set, enabling true pipelining. If a combined packet does have its short-not-ok flag set the queue will wait with submitting further packets to the device until that packet has completed. Once enabled in the usb-redir and ehci code, this improves the speed (MB/s) of a Linux guest reading from a USB mass storage device by a factor of 1.2 - 1.5. And the main reason why I started working on this, when reading from a pl2303 USB<->serial converter, it combines the previous 4 packets submitted per device-driver level read into 1 big read, reducing the number of packets / sec by a factor 4, and it allows to have multiple reads outstanding. This allows for much better latency tolerance without the pl2303's internal buffer overflowing (which was happening at 115200 bps, without serial flow control). Signed-off-by: Hans de Goede Signed-off-by: Gerd Hoffmann --- hw/usb.h | 13 ++++ hw/usb/Makefile.objs | 2 +- hw/usb/combined-packet.c | 179 ++++++++++++++++++++++++++++++++++++++++++++++ hw/usb/core.c | 1 + 4 files changed, 194 insertions(+), 1 deletions(-) create mode 100644 hw/usb/combined-packet.c diff --git a/hw/usb.h b/hw/usb.h index 1837bf7..aef07f4 100644 --- a/hw/usb.h +++ b/hw/usb.h @@ -160,6 +160,7 @@ typedef struct USBBusOps USBBusOps; typedef struct USBPort USBPort; typedef struct USBDevice USBDevice; typedef struct USBPacket USBPacket; +typedef struct USBCombinedPacket USBCombinedPacket; typedef struct USBEndpoint USBEndpoint; typedef struct USBDesc USBDesc; @@ -356,7 +357,15 @@ struct USBPacket { int result; /* transfer length or USB_RET_* status code */ /* Internal use by the USB layer. */ USBPacketState state; + USBCombinedPacket *combined; QTAILQ_ENTRY(USBPacket) queue; + QTAILQ_ENTRY(USBPacket) combined_entry; +}; + +struct USBCombinedPacket { + USBPacket *first; + QTAILQ_HEAD(packets_head, USBPacket) packets; + QEMUIOVector iov; }; void usb_packet_init(USBPacket *p); @@ -399,6 +408,10 @@ void usb_ep_set_pipeline(USBDevice *dev, int pid, int ep, bool enabled); USBPacket *usb_ep_find_packet_by_id(USBDevice *dev, int pid, int ep, uint64_t id); +void usb_ep_combine_input_packets(USBEndpoint *ep); +void usb_combined_input_packet_complete(USBDevice *dev, USBPacket *p); +void usb_combined_packet_cancel(USBDevice *dev, USBPacket *p); + void usb_attach(USBPort *port); void usb_detach(USBPort *port); void usb_port_reset(USBPort *port); diff --git a/hw/usb/Makefile.objs b/hw/usb/Makefile.objs index 7669938..5a4eeb6 100644 --- a/hw/usb/Makefile.objs +++ b/hw/usb/Makefile.objs @@ -7,7 +7,7 @@ common-obj-y += libhw.o common-obj-$(CONFIG_SMARTCARD) += dev-smartcard-reader.o common-obj-$(CONFIG_USB_REDIR) += redirect.o -common-obj-y += core.o bus.o desc.o dev-hub.o +common-obj-y += core.o combined-packet.o bus.o desc.o dev-hub.o common-obj-y += host-$(HOST_USB).o dev-bluetooth.o common-obj-y += dev-hid.o dev-storage.o dev-wacom.o common-obj-y += dev-serial.o dev-network.o dev-audio.o diff --git a/hw/usb/combined-packet.c b/hw/usb/combined-packet.c new file mode 100644 index 0000000..652bf02 --- /dev/null +++ b/hw/usb/combined-packet.c @@ -0,0 +1,179 @@ +/* + * QEMU USB packet combining code (for input pipelining) + * + * Copyright(c) 2012 Red Hat, Inc. + * + * Red Hat Authors: + * Hans de Goede + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or(at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ +#include "qemu-common.h" +#include "hw/usb.h" +#include "iov.h" +#include "trace.h" + +static void usb_combined_packet_add(USBCombinedPacket *combined, USBPacket *p) +{ + qemu_iovec_concat(&combined->iov, &p->iov, 0, p->iov.size); + QTAILQ_INSERT_TAIL(&combined->packets, p, combined_entry); + p->combined = combined; +} + +static void usb_combined_packet_remove(USBCombinedPacket *combined, + USBPacket *p) +{ + assert(p->combined == combined); + p->combined = NULL; + QTAILQ_REMOVE(&combined->packets, p, combined_entry); +} + +/* Also handles completion of non combined packets for pipelined input eps */ +void usb_combined_input_packet_complete(USBDevice *dev, USBPacket *p) +{ + USBCombinedPacket *combined = p->combined; + USBEndpoint *ep = p->ep; + USBPacket *next; + enum { completing, complete, leftover }; + int result, state = completing; + bool short_not_ok; + + if (combined == NULL) { + usb_packet_complete_one(dev, p); + goto leave; + } + + assert(combined->first == p && p == QTAILQ_FIRST(&combined->packets)); + + result = combined->first->result; + short_not_ok = QTAILQ_LAST(&combined->packets, packets_head)->short_not_ok; + + QTAILQ_FOREACH_SAFE(p, &combined->packets, combined_entry, next) { + if (state == completing) { + /* Distribute data over uncombined packets */ + if (result >= p->iov.size) { + p->result = p->iov.size; + } else { + /* Send short or error packet to complete the transfer */ + p->result = result; + state = complete; + } + p->short_not_ok = short_not_ok; + usb_combined_packet_remove(combined, p); + usb_packet_complete_one(dev, p); + result -= p->result; + } else { + /* Remove any leftover packets from the queue */ + state = leftover; + p->result = USB_RET_REMOVE_FROM_QUEUE; + dev->port->ops->complete(dev->port, p); + } + } + /* + * If we had leftover packets the hcd driver will have cancelled them + * and usb_combined_packet_cancel has already freed combined! + */ + if (state != leftover) { + g_free(combined); + } +leave: + /* Check if there are packets in the queue waiting for our completion */ + usb_ep_combine_input_packets(ep); +} + +/* May only be called for combined packets! */ +void usb_combined_packet_cancel(USBDevice *dev, USBPacket *p) +{ + USBCombinedPacket *combined = p->combined; + assert(combined != NULL); + + usb_combined_packet_remove(combined, p); + if (p == combined->first) { + usb_device_cancel_packet(dev, p); + } + if (QTAILQ_EMPTY(&combined->packets)) { + g_free(combined); + } +} + +/* + * Large input transfers can get split into multiple input packets, this + * function recombines them, removing the short_not_ok checks which all but + * the last packet of such splits transfers have, thereby allowing input + * transfer pipelining (which we cannot do on short_not_ok transfers) + */ +void usb_ep_combine_input_packets(USBEndpoint *ep) +{ + USBPacket *p, *u, *next, *prev = NULL, *first = NULL; + USBPort *port = ep->dev->port; + int ret; + + assert(ep->pipeline); + assert(ep->pid == USB_TOKEN_IN); + + QTAILQ_FOREACH_SAFE(p, &ep->queue, queue, next) { + /* Empty the queue on a halt */ + if (ep->halted) { + p->result = USB_RET_REMOVE_FROM_QUEUE; + port->ops->complete(port, p); + continue; + } + + /* Skip packets already submitted to the device */ + if (p->state == USB_PACKET_ASYNC) { + prev = p; + continue; + } + usb_packet_check_state(p, USB_PACKET_QUEUED); + + /* + * If the previous (combined) packet has the short_not_ok flag set + * stop, as we must not submit packets to the device after a transfer + * ending with short_not_ok packet. + */ + if (prev && prev->short_not_ok) { + break; + } + + if (first) { + if (first->combined == NULL) { + USBCombinedPacket *combined = g_new0(USBCombinedPacket, 1); + + combined->first = first; + QTAILQ_INIT(&combined->packets); + qemu_iovec_init(&combined->iov, 2); + usb_combined_packet_add(combined, first); + } + usb_combined_packet_add(first->combined, p); + } else { + first = p; + } + + /* Is this packet the last one of a (combined) transfer? */ + if ((p->iov.size % ep->max_packet_size) != 0 || !p->short_not_ok || + next == NULL) { + ret = usb_device_handle_data(ep->dev, first); + assert(ret == USB_RET_ASYNC); + if (first->combined) { + QTAILQ_FOREACH(u, &first->combined->packets, combined_entry) { + usb_packet_set_state(u, USB_PACKET_ASYNC); + } + } else { + usb_packet_set_state(first, USB_PACKET_ASYNC); + } + first = NULL; + prev = p; + } + } +} diff --git a/hw/usb/core.c b/hw/usb/core.c index 632a8ef..ab37f6f 100644 --- a/hw/usb/core.c +++ b/hw/usb/core.c @@ -545,6 +545,7 @@ void usb_packet_setup(USBPacket *p, int pid, USBEndpoint *ep, uint64_t id, p->parameter = 0; p->short_not_ok = short_not_ok; p->int_req = int_req; + p->combined = NULL; qemu_iovec_reset(&p->iov); usb_packet_set_state(p, USB_PACKET_SETUP); }