{"id":2233325,"url":"http://patchwork.ozlabs.org/api/1.1/patches/2233325/?format=json","web_url":"http://patchwork.ozlabs.org/project/qemu-devel/patch/20260505-ohci-one-packet-per-endpoint-v1-1-295bd298c206@gmail.com/","project":{"id":14,"url":"http://patchwork.ozlabs.org/api/1.1/projects/14/?format=json","name":"QEMU Development","link_name":"qemu-devel","list_id":"qemu-devel.nongnu.org","list_email":"qemu-devel@nongnu.org","web_url":"","scm_url":"","webscm_url":""},"msgid":"<20260505-ohci-one-packet-per-endpoint-v1-1-295bd298c206@gmail.com>","date":"2026-05-06T01:57:50","name":"Changed hcd-ohci to allow up to one active packet per endpoint rather than up to one active packet per host controller","commit_ref":null,"pull_url":null,"state":"new","archived":false,"hash":"06b23c8bef2f4550c0ba66fd4c50da0f1082f776","submitter":{"id":93337,"url":"http://patchwork.ozlabs.org/api/1.1/people/93337/?format=json","name":null,"email":"specialfred453@gmail.com"},"delegate":null,"mbox":"http://patchwork.ozlabs.org/project/qemu-devel/patch/20260505-ohci-one-packet-per-endpoint-v1-1-295bd298c206@gmail.com/mbox/","series":[{"id":502936,"url":"http://patchwork.ozlabs.org/api/1.1/series/502936/?format=json","web_url":"http://patchwork.ozlabs.org/project/qemu-devel/list/?series=502936","date":"2026-05-06T01:57:50","name":"Changed hcd-ohci to allow up to one active packet per endpoint rather than up to one active packet per host controller","version":1,"mbox":"http://patchwork.ozlabs.org/series/502936/mbox/"}],"comments":"http://patchwork.ozlabs.org/api/patches/2233325/comments/","check":"pending","checks":"http://patchwork.ozlabs.org/api/patches/2233325/checks/","tags":{},"headers":{"Return-Path":"<qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org>","X-Original-To":"incoming@patchwork.ozlabs.org","Delivered-To":"patchwork-incoming@legolas.ozlabs.org","Authentication-Results":["legolas.ozlabs.org;\n\tdkim=pass (2048-bit key;\n unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256\n header.s=20251104 header.b=CihFZMdd;\n\tdkim-atps=neutral","legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=nongnu.org\n (client-ip=209.51.188.17; helo=lists1p.gnu.org;\n envelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org;\n receiver=patchwork.ozlabs.org)"],"Received":["from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17])\n\t(using TLSv1.2 with cipher ECDHE-ECDSA-AES256-GCM-SHA384 (256/256 bits))\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4g9Spt3C0bz1yKj\n\tfor <incoming@patchwork.ozlabs.org>; Wed, 06 May 2026 18:14:44 +1000 (AEST)","from localhost ([::1] helo=lists1p.gnu.org)\n\tby lists1p.gnu.org with esmtp (Exim 4.90_1)\n\t(envelope-from <qemu-devel-bounces@nongnu.org>)\n\tid 1wKXOa-0001F8-G6; Wed, 06 May 2026 04:14:16 -0400","from eggs.gnu.org ([2001:470:142:3::10])\n by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)\n (Exim 4.90_1) (envelope-from <specialfred453@gmail.com>)\n id 1wKRWR-0003bP-HW\n for qemu-devel@nongnu.org; Tue, 05 May 2026 21:57:59 -0400","from mail-yw1-x1131.google.com ([2607:f8b0:4864:20::1131])\n by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128)\n (Exim 4.90_1) (envelope-from <specialfred453@gmail.com>)\n id 1wKRWP-0001YZ-Dm\n for qemu-devel@nongnu.org; Tue, 05 May 2026 21:57:59 -0400","by mail-yw1-x1131.google.com with SMTP id\n 00721157ae682-7bd5dde63dbso60615797b3.3\n for <qemu-devel@nongnu.org>; Tue, 05 May 2026 18:57:56 -0700 (PDT)","from [192.168.4.136] (1533255-static.lxtnkya2.metronetinc.net.\n [162.221.217.211]) by smtp.gmail.com with ESMTPSA id\n 00721157ae682-7bd6683794dsm73829167b3.27.2026.05.05.18.57.54\n (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n Tue, 05 May 2026 18:57:54 -0700 (PDT)"],"DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=gmail.com; s=20251104; t=1778032675; x=1778637475; darn=nongnu.org;\n h=cc:to:message-id:content-transfer-encoding:mime-version:subject\n :date:from:from:to:cc:subject:date:message-id:reply-to;\n bh=/DYJgHssiV4kmweEJO9mPM/DVD+M0iqCY7xFajGbVUg=;\n b=CihFZMddW6QCHAiK4N3iqVuKkayuXZArbF/qo8ct5mIh52+xHj2Rk6wVtlDi6W74Nl\n OIFRTXgoE+YCes1gLOLeztrq/2wdNJ0v/nbxdHOceBaYTNCCaFetrooKdCxwo1ZNEgg1\n jLVuzjTtSK40mouwUiosZan9kiqN5oK4CeWNn4fG+Va7riVZJiaSTAA9qn95Oi+Kl/Xl\n FZ5pCFis6g7QhgcQ2LNL63UHjgVmuW9CQSRF5GlxjatAfD+oDk6ourwls5oxxrI231ke\n U/Uq77SZGHeFXgSNlPd2zsL2QJtZdsg5sKVs/VdhFDgTTUkMzGLhmud7lS4uuQAHAAjN\n rNcw==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=1e100.net; s=20251104; t=1778032675; x=1778637475;\n h=cc:to:message-id:content-transfer-encoding:mime-version:subject\n :date:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date\n :message-id:reply-to;\n bh=/DYJgHssiV4kmweEJO9mPM/DVD+M0iqCY7xFajGbVUg=;\n b=mL7kEDgjlBYM/j/XG1uLTRc/tl8wqGI/HzYLkSXlF1j8kEcJv7tzuT29YA7Pe8YTBz\n N3/bexbv0ObhcuOwS18w651WJrEYKNu3ew0G3zwNTmdtAuBITed8er1JzJY61cYHsRDy\n q6To40R5yZxKlGQ9wlyaATG33++8/xhkLO/YDJQ29DXwh4/oA+ie4X/1k7z3Qt1JOYF+\n hq6rNMAhorrK9cTMWnEPKInib4q5o5qjAvhkPjIy1uYq7MBPE1Az2F2tSn111JtCUpQ3\n 0apDJxngox3DbO+oezHHZ/wjln6GCUYXruWyi996/iW/y+8kYNyi6sHMO0IEoerM7r/I\n t9cg==","X-Gm-Message-State":"AOJu0Yxa+npQj11V+CvLyCykaKDhx5GrwVWFWYKLsfnBZu1CCegG86BT\n maL8Tqqlv8rAu6BWWjpE/bfuBSkSpwwi7vfu3xrMhgTzISxW43QERd4R81IEpg==","X-Gm-Gg":"AeBDieuts/lt6m23dxSTqdBFEhUdNGYLbQhImv45yDwfvpJFT4CsznTu0JIHzUc+Um2\n 3thU+/I1pIZDDZdQWMDl+BgbAD35p4AZ1/r54vDxmXhqLSZNRbBdKwtTy69COSbtEoWjtrzXq27\n iAWyl/HdRcgJ1kYVM/yvmoUgOH9abN612aGciFvU7nGYOJ7sFrBdxKfk7TxEtMsRU/bAlY/9EKl\n zAoUnt04UZSYC8CaqI7fuMlWQsvlS8btb2PJrLso41pjMW32DV2DXHxqGJU07yEhM3sx6GTgVEE\n 7mRndS+UybgB7PwC3uu69AKRjogwP5i748HKzD0meuTNKQI5CwfgW9PSYCzgXHq8t4nDS4xU5zS\n WXj1pzapENXduiPbKjU5LRSyGjMHX8PtGvfWJVDZYZXXDDvr8V6jwaogdhCj+Ez1T6ZdvLqvKL6\n nJ4Z9L9Up1ST3GQ/OtcSWtJr1niEv1JJ6hrL6hLNG6AWHl8R+4cp7czTVplwRYKlXG2f7vlQyb6\n V6rTtcGUbEkOc50x04=","X-Received":"by 2002:a05:690c:38b:b0:7bd:8cb2:4f9f with SMTP id\n 00721157ae682-7bdf5df4820mr20400017b3.21.1778032674962;\n Tue, 05 May 2026 18:57:54 -0700 (PDT)","From":"specialfred453@gmail.com","Date":"Tue, 05 May 2026 21:57:50 -0400","Subject":"[PATCH] Changed hcd-ohci to allow up to one active packet per\n endpoint rather than up to one active packet per host controller","MIME-Version":"1.0","Content-Type":"text/plain; charset=\"utf-8\"","Content-Transfer-Encoding":"7bit","Message-Id":"\n <20260505-ohci-one-packet-per-endpoint-v1-1-295bd298c206@gmail.com>","X-B4-Tracking":"v=1; b=H4sIAAAAAAAC/yXMQQrCMBAF0KuUWTsQYyPoVcRFOvm1o5CEJIpQe\n nejLt/mrVRRFJXOw0oFL62aYsd+N5AsPt7AGrrJGns0zoycFlFOEZy9PNA4ozBiyEljY7HO+Xk\n 8HcIUqBe5YNb3r79c/67P6Q5p35O27QNPjVWYgAAAAA==","X-Change-ID":"20260504-ohci-one-packet-per-endpoint-c255af493dbd","To":"qemu-devel@nongnu.org","Cc":"specialfred453 <specialfred453@gmail.com>","X-Mailer":"b4 0.15.2","X-Developer-Signature":"v=1; a=openpgp-sha256; l=11765;\n i=specialfred453@gmail.com; h=from:subject:message-id;\n bh=BLfoIFN7sIcLcFocFmHTJPVu/N4uqXV1WLrtHtdePiU=;\n b=owGbwMvMwCUWkf1S/muK2CvG02pJDJm/Figadi69UBfozJY57+HcfTN8Vxi7z7I89MQ50TIj8\n 8Ne8yeqHaUsDGJcDLJiiiy97dmtX/TD7HX2/6yCmcPKBDKEgYtTACYy14WRoYtbZI53T98ZZ54M\n vfmCm+/tvNo42y42UGonj7tvwDaxA4wMU36K5M8vYuQ/fvhoTPin0Aml/yZsv174u1FD+8bapef\n eMgIA","X-Developer-Key":"i=specialfred453@gmail.com; a=openpgp;\n fpr=8D876B85F42F563F2CBFF97A586BE91FF56416EA","Received-SPF":"pass client-ip=2607:f8b0:4864:20::1131;\n envelope-from=specialfred453@gmail.com; helo=mail-yw1-x1131.google.com","X-Spam_score_int":"-17","X-Spam_score":"-1.8","X-Spam_bar":"-","X-Spam_report":"(-1.8 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1,\n DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1,\n FREEMAIL_ENVFROM_END_DIGIT=0.25, FREEMAIL_FROM=0.001,\n RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001,\n SPF_PASS=-0.001 autolearn=ham autolearn_force=no","X-Spam_action":"no action","X-Mailman-Approved-At":"Wed, 06 May 2026 04:14:14 -0400","X-BeenThere":"qemu-devel@nongnu.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"qemu development <qemu-devel.nongnu.org>","List-Unsubscribe":"<https://lists.nongnu.org/mailman/options/qemu-devel>,\n <mailto:qemu-devel-request@nongnu.org?subject=unsubscribe>","List-Archive":"<https://lists.nongnu.org/archive/html/qemu-devel>","List-Post":"<mailto:qemu-devel@nongnu.org>","List-Help":"<mailto:qemu-devel-request@nongnu.org?subject=help>","List-Subscribe":"<https://lists.nongnu.org/mailman/listinfo/qemu-devel>,\n <mailto:qemu-devel-request@nongnu.org?subject=subscribe>","Errors-To":"qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org","Sender":"qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org"},"content":"---\nThis patch addresses an issue with the OHCI controller that is documented on \nline 999 of hw/usb/hcd-ohci.c where it states that the hardware should\nallow one active packet per endpoint and that the current implementation\nonly allows one active packet per controller. This limitation causes\nproblems with some input devices, particularly when using USB\npassthrough of original Xbox controllers. This doesn't appear to be an\nissue when using the uhci or xhci implementations, but Xemu uses\npci-ohci for its USB implementation. \n\nI'm not completely sure of the implementation I've done here, but it\ndoes appear to address the problem.\n\nI was able to reproduce the issue using a Steel Battalion controller\npassed into a VM using the Archlinux VM image, and running a small\nutility I wrote that interacts with the Steel Battalion controller via\nlibusb-1.0. With this patch, I am no longer able to reproduce the issue.\n\nSigned-off-by: specialfred453 <specialfred453@gmail.com>\n---\n hw/usb/hcd-ohci-pci.c |   4 --\n hw/usb/hcd-ohci.c     | 127 ++++++++++++++++++++++++++++++++------------------\n hw/usb/hcd-ohci.h     |  14 ++++++\n 3 files changed, 95 insertions(+), 50 deletions(-)\n\n\n---\nbase-commit: a85c588d07f8d3345ccad38b22026569a04571d1\nchange-id: 20260504-ohci-one-packet-per-endpoint-c255af493dbd\n\nBest regards,\n--  \nspecialfred453 <specialfred453@gmail.com>","diff":"diff --git a/hw/usb/hcd-ohci-pci.c b/hw/usb/hcd-ohci-pci.c\nindex 18b58f5fcb..5ca204da28 100644\n--- a/hw/usb/hcd-ohci-pci.c\n+++ b/hw/usb/hcd-ohci-pci.c\n@@ -87,10 +87,6 @@ static void usb_ohci_exit(PCIDevice *dev)\n     trace_usb_ohci_exit(s->name);\n     ohci_bus_stop(s);\n \n-    if (s->async_td) {\n-        usb_cancel_packet(&s->usb_packet);\n-        s->async_td = 0;\n-    }\n     ohci_stop_endpoints(s);\n \n     if (!ohci->masterbus) {\ndiff --git a/hw/usb/hcd-ohci.c b/hw/usb/hcd-ohci.c\nindex 6ed8046fc2..c8b098448a 100644\n--- a/hw/usb/hcd-ohci.c\n+++ b/hw/usb/hcd-ohci.c\n@@ -299,10 +299,7 @@ void ohci_stop_endpoints(OHCIState *ohci)\n     USBDevice *dev;\n     int i, j;\n \n-    if (ohci->async_td) {\n-        usb_cancel_packet(&ohci->usb_packet);\n-        ohci->async_td = 0;\n-    }\n+    ohci_clear_active_packets(ohci);\n     for (i = 0; i < ohci->num_ports; i++) {\n         dev = ohci->rhport[i].port.dev;\n         if (dev && dev->attached) {\n@@ -885,6 +882,8 @@ static int ohci_service_td(OHCIState *ohci, struct ohci_ed *ed)\n     uint32_t addr;\n     int flag_r;\n     int completion;\n+    USBActivePacket *packet = NULL;\n+    USBActivePacket *iter;\n \n     addr = ed->head & OHCI_DPTR_MASK;\n     if (addr == 0) {\n@@ -904,6 +903,12 @@ static int ohci_service_td(OHCIState *ohci, struct ohci_ed *ed)\n         return 1;\n     }\n \n+    dev = ohci_find_device(ohci, OHCI_BM(ed->flags, ED_FA));\n+    if (dev == NULL) {\n+        trace_usb_ohci_td_dev_error();\n+        return 1;\n+    }\n+\n     dir = OHCI_BM(ed->flags, ED_D);\n     switch (dir) {\n     case OHCI_TD_DIR_OUT:\n@@ -937,6 +942,31 @@ static int ohci_service_td(OHCIState *ohci, struct ohci_ed *ed)\n         trace_usb_ohci_td_bad_direction(dir);\n         return 1;\n     }\n+\n+    ep = usb_ep_get(dev, pid, OHCI_BM(ed->flags, ED_EN));\n+    QTAILQ_FOREACH(iter, &ohci->active_packets, next) {\n+        if (iter->ep == ep) {\n+            packet = iter;\n+            break;\n+        }\n+    }\n+\n+    if (packet == NULL) {\n+        packet = g_malloc(sizeof(USBActivePacket));\n+        usb_packet_init(&packet->usb_packet);\n+        packet->ep = ep;\n+        packet->async_complete = false;\n+        packet->async_td = 0;\n+        packet->ohci = ohci;\n+        QTAILQ_INSERT_TAIL(&ohci->active_packets, packet, next);\n+    }\n+\n+    completion = (addr == packet->async_td);\n+    if (completion && !packet->async_complete) {\n+        trace_usb_ohci_td_skip_async();\n+        return 1;\n+    }\n+\n     if (td.cbp && td.be) {\n         if ((td.cbp & 0xfffff000) != (td.be & 0xfffff000)) {\n             len = (td.be & 0xfff) + 0x1001 - (td.cbp & 0xfff);\n@@ -948,8 +978,8 @@ static int ohci_service_td(OHCIState *ohci, struct ohci_ed *ed)\n             }\n             len = (td.be - td.cbp) + 1;\n         }\n-        if (len > sizeof(ohci->usb_buf)) {\n-            len = sizeof(ohci->usb_buf);\n+        if (len > sizeof(packet->usb_buf)) {\n+            len = sizeof(packet->usb_buf);\n         }\n \n         pktlen = len;\n@@ -971,7 +1001,7 @@ static int ohci_service_td(OHCIState *ohci, struct ohci_ed *ed)\n                 pktlen = len;\n             }\n             if (!completion) {\n-                if (ohci_copy_td(ohci, &td, ohci->usb_buf, pktlen,\n+                if (ohci_copy_td(ohci, &td, packet->usb_buf, pktlen,\n                                  DMA_DIRECTION_TO_DEVICE)) {\n                     ohci_die(ohci);\n                 }\n@@ -985,50 +1015,39 @@ static int ohci_service_td(OHCIState *ohci, struct ohci_ed *ed)\n     ohci_td_pkt(\"OUT\", ohci->usb_buf, pktlen);\n \n     if (completion) {\n-        ohci->async_td = 0;\n-        ohci->async_complete = false;\n+        packet->async_td = 0;\n+        packet->async_complete = false;\n     } else {\n-        dev = ohci_find_device(ohci, OHCI_BM(ed->flags, ED_FA));\n-        if (dev == NULL) {\n-            trace_usb_ohci_td_dev_error();\n-            return 1;\n-        }\n-        ep = usb_ep_get(dev, pid, OHCI_BM(ed->flags, ED_EN));\n-        if (ohci->async_td) {\n-            /*\n-             * ??? The hardware should allow one active packet per\n-             * endpoint.  We only allow one active packet per controller.\n-             * This should be sufficient as long as devices respond in a\n-             * timely manner.\n-             */\n+        if (packet->async_td) {\n+            /* Only allow one active packet per endpoint */\n             trace_usb_ohci_td_too_many_pending(ep->nr);\n             return 1;\n         }\n-        usb_packet_setup(&ohci->usb_packet, pid, ep, 0, addr, !flag_r,\n+        usb_packet_setup(&packet->usb_packet, pid, ep, 0, addr, !flag_r,\n                          OHCI_BM(td.flags, TD_DI) == 0);\n-        usb_packet_addbuf(&ohci->usb_packet, ohci->usb_buf, pktlen);\n-        usb_handle_packet(dev, &ohci->usb_packet);\n-        trace_usb_ohci_td_packet_status(ohci->usb_packet.status);\n+        usb_packet_addbuf(&packet->usb_packet, packet->usb_buf, pktlen);\n+        usb_handle_packet(dev, &packet->usb_packet);\n+        trace_usb_ohci_td_packet_status(packet->usb_packet.status);\n \n-        if (ohci->usb_packet.status == USB_RET_ASYNC) {\n+        if (packet->usb_packet.status == USB_RET_ASYNC) {\n             usb_device_flush_ep_queue(dev, ep);\n-            ohci->async_td = addr;\n+            packet->async_td = addr;\n             return 1;\n         }\n     }\n-    if (ohci->usb_packet.status == USB_RET_SUCCESS) {\n-        ret = ohci->usb_packet.actual_length;\n+    if (packet->usb_packet.status == USB_RET_SUCCESS) {\n+        ret = packet->usb_packet.actual_length;\n     } else {\n-        ret = ohci->usb_packet.status;\n+        ret = packet->usb_packet.status;\n     }\n \n     if (ret >= 0) {\n         if (dir == OHCI_TD_DIR_IN) {\n-            if (ohci_copy_td(ohci, &td, ohci->usb_buf, ret,\n+            if (ohci_copy_td(ohci, &td, packet->usb_buf, ret,\n                              DMA_DIRECTION_FROM_DEVICE)) {\n                 ohci_die(ohci);\n             }\n-            ohci_td_pkt(\"IN\", ohci->usb_buf, pktlen);\n+            ohci_td_pkt(\"IN\", packet->usb_buf, pktlen);\n         } else {\n             ret = pktlen;\n         }\n@@ -1124,6 +1143,7 @@ static int ohci_service_ed_list(OHCIState *ohci, uint32_t head)\n     int active;\n     uint32_t link_cnt = 0;\n     active = 0;\n+    USBActivePacket *iter;\n \n     if (head == 0) {\n         return 0;\n@@ -1141,11 +1161,13 @@ static int ohci_service_ed_list(OHCIState *ohci, uint32_t head)\n             uint32_t addr;\n             /* Cancel pending packets for ED that have been paused. */\n             addr = ed.head & OHCI_DPTR_MASK;\n-            if (ohci->async_td && addr == ohci->async_td) {\n-                usb_cancel_packet(&ohci->usb_packet);\n-                ohci->async_td = 0;\n-                usb_device_ep_stopped(ohci->usb_packet.ep->dev,\n-                                      ohci->usb_packet.ep);\n+            QTAILQ_FOREACH(iter, &ohci->active_packets, next) {\n+                if (iter->async_td && addr == iter->async_td) {\n+                    usb_cancel_packet(&iter->usb_packet);\n+                    iter->async_td = 0;\n+                    usb_device_ep_stopped(iter->usb_packet.ep->dev,\n+                                        iter->usb_packet.ep);\n+                }\n             }\n             continue;\n         }\n@@ -1820,12 +1842,7 @@ static void ohci_child_detach(USBPort *port1, USBDevice *dev)\n {\n     OHCIState *ohci = port1->opaque;\n \n-    if (ohci->async_td &&\n-        usb_packet_is_inflight(&ohci->usb_packet) &&\n-        ohci->usb_packet.ep->dev == dev) {\n-        usb_cancel_packet(&ohci->usb_packet);\n-        ohci->async_td = 0;\n-    }\n+    ohci_clear_active_packets(ohci);\n }\n \n static void ohci_detach(USBPort *port1)\n@@ -1955,9 +1972,8 @@ void usb_ohci_init(OHCIState *ohci, DeviceState *dev, uint32_t num_ports,\n     ohci->localmem_base = localmem_base;\n \n     ohci->name = object_get_typename(OBJECT(dev));\n-    usb_packet_init(&ohci->usb_packet);\n \n-    ohci->async_td = 0;\n+    QTAILQ_INIT(&ohci->active_packets);\n \n     ohci->eof_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,\n                                    ohci_frame_boundary, ohci);\n@@ -1971,10 +1987,29 @@ void ohci_sysbus_die(struct OHCIState *ohci)\n {\n     trace_usb_ohci_die();\n \n+    ohci_clear_active_packets(ohci);\n     ohci_set_interrupt(ohci, OHCI_INTR_UE);\n     ohci_bus_stop(ohci);\n }\n \n+void ohci_clear_active_packets(struct OHCIState *ohci)\n+{\n+    while (!QTAILQ_EMPTY(&ohci->active_packets)) {\n+        USBActivePacket *packet = QTAILQ_FIRST(&ohci->active_packets);\n+        if (packet->async_td) {\n+            usb_cancel_packet(&packet->usb_packet);\n+            packet->async_td = 0;\n+            if (packet->usb_packet.ep) {\n+                usb_device_ep_stopped(packet->usb_packet.ep->dev,\n+                    packet->usb_packet.ep);\n+            }\n+        }\n+        QTAILQ_REMOVE(&ohci->active_packets, packet, next);\n+        usb_packet_cleanup(&packet->usb_packet);\n+        g_free(packet);\n+    }\n+}\n+\n static const VMStateDescription vmstate_ohci_state_port = {\n     .name = \"ohci-core/port\",\n     .version_id = 1,\ndiff --git a/hw/usb/hcd-ohci.h b/hw/usb/hcd-ohci.h\nindex 243cd1f46a..5ab4fa9acf 100644\n--- a/hw/usb/hcd-ohci.h\n+++ b/hw/usb/hcd-ohci.h\n@@ -34,8 +34,20 @@ typedef struct OHCIPort {\n     uint32_t ctrl;\n } OHCIPort;\n \n+typedef struct USBActivePacket USBActivePacket;\n typedef struct OHCIState OHCIState;\n \n+struct USBActivePacket {\n+    USBEndpoint *ep;\n+    USBPacket usb_packet;\n+    uint8_t usb_buf[8192];\n+    uint32_t async_td;\n+    bool async_complete;\n+    OHCIState *ohci;\n+\n+    QTAILQ_ENTRY(USBActivePacket) next;\n+};\n+\n struct OHCIState {\n     USBBus bus;\n     qemu_irq irq;\n@@ -91,6 +103,7 @@ struct OHCIState {\n     uint8_t usb_buf[8192];\n     uint32_t async_td;\n     bool async_complete;\n+    QTAILQ_HEAD(, USBActivePacket) active_packets;\n \n     void (*ohci_die)(OHCIState *ohci);\n };\n@@ -120,5 +133,6 @@ void ohci_bus_stop(OHCIState *ohci);\n void ohci_stop_endpoints(OHCIState *ohci);\n void ohci_hard_reset(OHCIState *ohci);\n void ohci_sysbus_die(struct OHCIState *ohci);\n+void ohci_clear_active_packets(struct OHCIState *ohci);\n \n #endif\n","prefixes":[]}