From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from PA4PR04CU001.outbound.protection.outlook.com (mail-francecentralazon11023079.outbound.protection.outlook.com [40.107.162.79]) by lore.virtuozzo.com (Postfix) with ESMTPS id 6F25980682 for ; Fri, 25 Sep 2026 14:11:33 +0000 (UTC) ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=UjQcVscvoTW24py/w9vvJ7A5op/nkILGiOPRdW92uif1dGzCUCVVJG/wr3wcU1GyH43dtBtU54y/rkE0kSh0U0tkIluHHDJUAUgdO2Idk/Tvwo3ycwOfelpmjPzsE9KTGLSHLFq8xYR+el/ud5+u+DHGAvN6TXuDL0AdOmPWKQd5nE8WP7P2l6anqjkRfGVnVzCIMb2BMfRxF4Hs43c9mmgTJHZ8rwLuAsKbFNS8L4qLrf7vX84qaV+hQQXDBdujFeOpkQ9vXpdWSPqnCvOmenr08etqyWa3LAb1tUQLBxftcDBjqU39s51ih/saLxONLBsTbv0332GqipR2B3dOuw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector10001; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=ZkWkIwPiaBH9EL7N+r/VOnatLVVNEc7O3GHH/QFGaJQ=; b=KupdKHlZZA1JyQQB1HwQagl4dcO2/7hFs+33bH+Gy+o9+bF8C5g0JTzx4aHW6X3AyJaQu+wEwPtnbePCwZj6XigLAqxUyDvORlvh0x42pH9KqRi7IkXshYnvULjBFuDenUoxAEdYk87cE61MpHareaoD1DBpNrYZ7qvJBAUB2UKDbqBe2QtHdqseCQ8yYQW/z+5EoU/T75PajwnnguEpqr9BSvaelVvLpoZgr3UaRE+xkTdEOsrv1Sj9OhJTlvwrL8cnghbU4ASiEhP2O1t9aXn3q1rcm8bA72+4TMTNnQ5JHc/MUqLTMP8yTU0hkErFZpT5was+3II7JVuMzmM4VQ== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=virtuozzo.com; dmarc=pass action=none header.from=virtuozzo.com; dkim=pass header.d=virtuozzo.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=virtuozzo.com; s=selector2; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=ZkWkIwPiaBH9EL7N+r/VOnatLVVNEc7O3GHH/QFGaJQ=; b=fysNuSFPOS8KkD3zpzqyuqDDSofdoqZQ0t1Mc/eKIJRDCi7dt0WaAjHSfxebbWLWExEp9Lh6PPMWhDk2iWM9Vr8w9OLFXCYKe6YonsXvfSnXQ3ZlOlkDNwG9Q24n0QIG0smAjQHDQBFMpyRMwfCfrrjrj4XBfZi4tR7udi7xtUtDKJsODjY9Tt7mj5nLcCXgDyZUfnFiCh6UypJ4PlUic79a0bC6T78HBLIuVGfrND2NmTjo+dB0GIMmoAGEdfJwp2caIePzLjKDl6hkPlDCRbbRGE+XgazEmTnde/ql0IR6e9ya0Rr81hyYfOvkyVIhf8YlwQJb3/O8NN7HMc2Zsw== Authentication-Results: mx.microsoft.com 1; dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=virtuozzo.com; Received: from VI0PR08MB10656.eurprd08.prod.outlook.com (2603:10a6:800:20a::12) by GVXPR08MB11398.eurprd08.prod.outlook.com (2603:10a6:150:2e5::8) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.21.451.18; Fri, 25 Sep 2026 14:11:29 +0000 Received: from VI0PR08MB10656.eurprd08.prod.outlook.com ([fe80::4e37:b189:ddcd:3dd8]) by VI0PR08MB10656.eurprd08.prod.outlook.com ([fe80::4e37:b189:ddcd:3dd8%7]) with mapi id 15.21.0451.014; Fri, 25 Sep 2026 14:11:29 +0000 From: Andrey Drobyshev Date: Fri, 25 Sep 2026 17:11:16 +0300 Subject: [PATCH QEMU HCI-8.0 v5 07/13] usb-host: re-issue drained URBs if CPR is aborted #VSTOR-137800 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit Message-Id: <20260925-usb-host_v3-v5-7-e5b21724db4d@virtuozzo.com> References: <20260925-usb-host_v3-v5-0-e5b21724db4d@virtuozzo.com> In-Reply-To: <20260925-usb-host_v3-v5-0-e5b21724db4d@virtuozzo.com> To: svt-core@virtuozzo.com Cc: andrey.drobyshev@virtuozzo.com, den@openvz.org X-Mailer: b4 0.16.0 X-ClientProxiedBy: VI1PR06CA0178.eurprd06.prod.outlook.com (2603:10a6:803:c8::35) To VI0PR08MB10656.eurprd08.prod.outlook.com (2603:10a6:800:20a::12) MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: VI0PR08MB10656:EE_|GVXPR08MB11398:EE_ X-MS-Office365-Filtering-Correlation-Id: ed4cdd6f-536d-4b13-cfb0-08df1b0ee4eb X-LD-Processed: 0bc7f26d-0264-416e-a6fc-8352af79c58f,ExtAddr List-Id: svt-core@virtuozzo.com X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0;ARA:13230040|23010399003|366016|376014|52116014|1800799024|38350700014|5113699003|6133799003|10067099003|5023799004|56012099006|18002099003|22082099003; X-Microsoft-Antispam-Message-Info: koxtT7GGcK7YW1qYMk3ZSA+FCviga1vD9dG39BEJGgXAnCynLUHW2NIHd1GKMtq/+EngsL3KZJoH8tA7pzcgLw64OTaiY0kcDk+XcHGk04/oH5DSOiFRDVWkc0+J7FeGRRTY7mR7OOczFe+d6jMtufugjlaodp+3Wpyja5iiWH19HfRat1ekewx9gYQ9EEemKvWowoMdId9CYB8mbsmjc1PVsPKKPc3Nk1CeiIo/BRwpsRufmFEj06G70udXpTfjFZncgvvzXP/a1X2AYYXLqHRPkg3kBRZKmmUauB29gQdWRLOO1PStNR9pSTG7wB/C0EswtkFCdXwTZDBAcdwFPjFcL+RXabSuJaQevCPzrrDEO884p/nM/VMTRAzLq4lkJnvIlT/m+xYEGko9Fd9fyYLyKNRNl+uHLtZMYi/joRbRc44uLh2Chu6N2RX5Zqu4Ao7ruMW9gnkQLq6tHSSwmmiCDU1kaGELz0/HYhwpRvSMZbb9TJdtOaouBkAMf8Ig0BSrvufsHw4Kzf3dyGZlol9iCnnBWsMbg1/QFxDafSTr0A6d6/acWKniENZpeF/xFOFmH1IRZffV6dMqQAhnnhkHvOdGC2q4CuprMBEE8Wo3O2JAEMJ9jn2Loukg18Wga0bUmuQ9+QLL9cjhISez0cu4Femeew5sGCMDIuf0FOCwTle/9mZIpLUrGddVcTn2n9niaHta446SUNjUE12IYV9RmV/Rvlt0lISFdXlhhyg= X-Forefront-Antispam-Report: CIP:255.255.255.255;CTRY:;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:VI0PR08MB10656.eurprd08.prod.outlook.com;PTR:;CAT:NONE;SFS:(13230040)(23010399003)(366016)(376014)(52116014)(1800799024)(38350700014)(5113699003)(6133799003)(10067099003)(5023799004)(56012099006)(18002099003)(22082099003);DIR:OUT;SFP:1102; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: =?utf-8?B?d3Fvcnh3SWw3WGxzZEMwaGxhN2dIS2hMTHBhZ2prZ1NRREZUdmQ1MnBwTjlR?= =?utf-8?B?MklPMGs1NWlTR3ZPS0REa1pkWWNkWnBzM09tZld4b1VrbHA3MFFnQVRjY3hp?= =?utf-8?B?MEJCN3dsNjg2dEVDY1JyRm0zRUc5djFqbVhHamJaVEd5Rm91VmN0SlRLS1Fz?= =?utf-8?B?aHduUnJORVhNaHVNNUFNaTQ5R1JEQktTMFlsdUliVHk3QlEwMldhODNDSlJk?= =?utf-8?B?S3RGcGI5Z3JxSEtBUXUyRkJyTU1KNVlRVXFRdmg0M0Z6bmtXNXlIMlVVT2g5?= =?utf-8?B?NjMvSUF0ZGJ0bzJnWnZ0dXhkZ01oOEhYQS8rWFJHaWlGOFJCalhZYWNVdWVw?= =?utf-8?B?VUtyTjlUUklISHdYTkFKYVB4SE4rTThxQlFVNVhKM044NTBBanNzcmFVTEFM?= =?utf-8?B?aGdtYjV5bVN2R0NZSy9PTHM4YmZwLy9uNzk4RzhOMmhuZDY0Sm96am9VWUhm?= =?utf-8?B?Vmh3NG04YkZsblVBeTRTVExTOHFIbFgvSzROODJkV1N1V0dQaEh6VjhGWGRV?= =?utf-8?B?ZUZva05wY2M5eDBwRkphTldpR25ya1RXNFFObkZxSjZxQ0N3bFBFWEg3NWNl?= =?utf-8?B?VitLclF5TG1JazdONTVDRE5TTkRYdnh4R1VjRWNMMkhkTWtrN3M4dENyaUxH?= =?utf-8?B?dDJGa0ovOHpVTXdaV1RMTng2dnlFMjZXQ1VhcDNUbWZKZTZBT1U5NEduTGJE?= =?utf-8?B?R001U1VPd1hHa251WEpSanJ0WXZQTGI0R3AxR2FFT29ZZG03OFlGR1BPeWUy?= =?utf-8?B?K2thaThjVTRpNkFEK2RscmRrWTErT1o4ZkE5dTBPcHJ0WnlTelFDWFMrOFRS?= =?utf-8?B?VXFPVmRDNmF5VkRsUmF1djczSzBRNnVMSEZ5QVJPWG9lOGVqcjUycGFhU3Fx?= =?utf-8?B?d3NBUkYvcmhHVExuRlV1QTRrcnVoNklFaWxVQmdrYmpzN2RKYjE0ZkEzQWlm?= =?utf-8?B?UEFwNlVEMXZ0R1RNSU04OVQyemU1NVJxVThsVG93emJHNGVBYmRCeG02NHFs?= =?utf-8?B?WHVGUHkwRUtncUJKT3dtL1dKUnBWYlkwUnFHbHMraHBIbXJjUDFVRFloSXhZ?= =?utf-8?B?YTcwS1VINThwOWF2cFdQRTVPMXdjRmRTV1RYcjhvRHdteVVxeisrbm1rVEdm?= =?utf-8?B?aExPdDBDVjRjbFBDOFQ3ZnlEVVNLc2ZNY1kzSmZoejlINFhjanZybzNYRjZl?= =?utf-8?B?cFFmdklXTWp6ZEdTMno3OGhmNDlweHNVM1pqR1hEb1BuZU9saXc0RHNMN3h2?= =?utf-8?B?QWVRbGZmTUNlRHpSMDZlK1pKS2xUVVRRMjRGZUZPd1UvSkhlakVsYkFucGZX?= =?utf-8?B?NGlRZ2NPb1pXNVI4a3BFMmJ0dXYyYkg1bzYyV0J2aEh6eis1QnY4d3FZcWNl?= =?utf-8?B?eVIyY0U2VTAybjBDeVdDQWRNWEdQTTJFYmxkcWd0Qy9HUVRFMEorM3JKaTZV?= =?utf-8?B?Wi9JYU9HWFIyWHdubE5yNmpPN05BNHYvUk5rMTVHQUExQjF0N08vUGVEdEVl?= =?utf-8?B?RytsRWZ4WXhLM0VmZS9DQ1VSTHI5VnZ1NlErVTA5c3RZR3BkZWJvT3BxajI5?= =?utf-8?B?TUJlNTNMa0hrNlpwTE5YTGgxNDhzMmJFQ3JQU1JOa2xTeHZ0UVVVMkNEd3E2?= =?utf-8?B?UUpFbktTbERkSFZ3a2ptQkR3VzljQytPaUVKSDloOW9nNkh5RkFJQ3ZFTjVY?= =?utf-8?B?ZFpEZEFibFNEUnU2Umt6QmZ4NEVyb3hEeFRzWWdDRnFUV29scTZsc1V0SXdX?= =?utf-8?B?ak93ZGZUUnd5bjMvYTRUcUxrSmFOTTluV1E3SHNOSFNNT0pqWDRVRTNwNEdI?= =?utf-8?B?aEZnSkNuZ1pZd3QwRUtxUEI0S1VYNXAxK1hneDJuQTdvdlB2NVJPbjZYRDRv?= =?utf-8?B?KzltSmlrbUdqQ1l1UWFEVy9rVWNJRGJYeS9iMnlWbjV4TVhUQzByQzB0Z2wx?= =?utf-8?B?LzR6NUJFVzlsamFYMytCaWZoQzl6RzFRQzMxdGpTZ0VhWHdjaXBHM0Vzdjdm?= =?utf-8?B?VWlWMmVhczE1WWZFcnI2VlFXUEVTcGtIKzdaY1I1UW5vM0hYRjczZE1YVEJ1?= =?utf-8?B?MGFaU3F2Slo0OVlESnlQbWJsa3U0NlYvYmxmOE5FSDFORzNINitzTVZmZGpP?= =?utf-8?B?Q0NMVVYxTWxRd0NDb2l4Sk1OSFVyZ1FmRnpQUEdnVG5kY0ZTQkYvSWRubFBD?= =?utf-8?B?NFA2ZDRyanI1TlA2Tml3Y1hYSnVWYmZEMlNwRFdKZjdlVTErRjRXbnhZQm1V?= =?utf-8?B?ZTFIL3RObUlFcnN4SXNEYjRhWEY0SFFBNWtURjAyaTJaZHBIS05QeUwxQ0s1?= =?utf-8?B?NUdFa3ErMFNrTWNLOGlRL1VqMEloZm0wOWZRNTByM29QR0IrU1VKdkhOSDBP?= =?utf-8?Q?qqtAg5/De0pOAVMY=3D?= X-Auto-Response-Suppress: DR, OOF, AutoReply X-OriginatorOrg: virtuozzo.com X-MS-Exchange-CrossTenant-Network-Message-Id: ed4cdd6f-536d-4b13-cfb0-08df1b0ee4eb X-MS-Exchange-CrossTenant-AuthSource: VI0PR08MB10656.eurprd08.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 25 Sep 2026 14:11:29.5621 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-CrossTenant-Id: 0bc7f26d-0264-416e-a6fc-8352af79c58f X-MS-Exchange-CrossTenant-MailboxType: HOSTED X-MS-Exchange-CrossTenant-UserPrincipalName: nDxtAKyCrxQi24q8twDcfFwLLNpeEl17UShwLrqqAx135OJuMJItd6obaS/KXkgkNGUDjluhNXvVDtLWerj1jtMRKNulKMngzMeKc1vE8g8= X-MS-Exchange-Transport-CrossTenantHeadersStamped: GVXPR08MB11398 The previous commit drains the in-flight URBs in .pre_save() and leaves their packets ASYNC for the target to replay. If migration fails, the source resumes instead, with those packets still owned by the controller and nothing left to complete them. Remember the drained packets in a list, and re-issue them on the preserved FD from a MIG_EVENT_PRECOPY_FAILED notifier. Control transfers can't be reconstructed from the packet alone, so complete those as errors and let the guest driver retry. Signed-off-by: Andrey Drobyshev --- hw/usb/host-libusb.c | 96 +++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 92 insertions(+), 4 deletions(-) diff --git a/hw/usb/host-libusb.c b/hw/usb/host-libusb.c index 9e9ee7d305f..a14a3748838 100644 --- a/hw/usb/host-libusb.c +++ b/hw/usb/host-libusb.c @@ -107,6 +107,9 @@ struct USBHostDevice { int hostfd; Error *cpr_blocker; + NotifierWithReturn cpr_notifier; + GSList *cpr_inflight; + bool cpr_drained; libusb_device *dev; libusb_device_handle *dh; struct libusb_device_descriptor ddesc; @@ -425,6 +428,23 @@ static USBHostRequest *usb_host_req_find(USBHostDevice *s, USBPacket *p) return NULL; } +/* + * A CANCELLED reap with r->p still set -> the CPR drain cancelled it (every + * other path clears r->p first). Don't complete it, let the target replay it. + * Returns true if the caller should just free the request. + */ +static bool usb_host_req_cpr_replay(USBHostRequest *r, + struct libusb_transfer *xfer) +{ + if (xfer->status != LIBUSB_TRANSFER_CANCELLED) { + return false; + } + if (r->p->state == USB_PACKET_ASYNC) { + r->host->cpr_inflight = g_slist_append(r->host->cpr_inflight, r->p); + } + return true; +} + static void LIBUSB_CALL usb_host_req_complete_ctrl(struct libusb_transfer *xfer) { USBHostRequest *r = xfer->user_data; @@ -434,7 +454,7 @@ static void LIBUSB_CALL usb_host_req_complete_ctrl(struct libusb_transfer *xfer) if (r->p == NULL) { goto out; /* request was canceled */ } - if (xfer->status == LIBUSB_TRANSFER_CANCELLED) { + if (usb_host_req_cpr_replay(r, xfer)) { goto out; /* CPR drain: the target will replay this packet */ } @@ -487,7 +507,7 @@ static void LIBUSB_CALL usb_host_req_complete_data(struct libusb_transfer *xfer) if (r->p == NULL) { goto out; /* request was canceled */ } - if (xfer->status == LIBUSB_TRANSFER_CANCELLED) { + if (usb_host_req_cpr_replay(r, xfer)) { goto out; /* CPR drain: the target will replay this packet */ } @@ -1166,8 +1186,13 @@ static bool usb_host_iso_inflight(USBHostDevice *s) } /* - * Quiesce the device for a CPR switchover. Cancel and reap all URBs, - * so that no URB submitted by this process can outlive it. + * Quiesce the device for a CPR switchover. Cancel and reap all URBs + * without completing their packets, so that no URB submitted by this + * process can outlive it. CPR target re-executes them in + * usb_xhci_post_load() kicking the running endpoints. + * + * Keep the list of packets so that they can be re-issued on this side + * instead if MIG_EVENT_PRECOPY_FAILED fires. */ static int usb_host_cpr_drain_xfers(USBHostDevice *s) { @@ -1176,6 +1201,8 @@ static int usb_host_cpr_drain_xfers(USBHostDevice *s) USBHostIsoXfer *xfer; int limit = 100; + s->cpr_drained = true; + /* * Cancel and reap every URB. Completing a reaped URB can make xHCI queue * the next TD, so re-cancel whatever reappears until the queue stays empty. @@ -1308,6 +1335,53 @@ static libusb_device *usb_host_find_ref(int bus, int addr) return ret; } +static void usb_host_handle_data(USBDevice *udev, USBPacket *p); + +/* + * A failed CPR migration resumes the source VM with the drained + * packets still owned by the host controller as in-flight. Re-issue + * them on the preserved fd. Control transfers cannot be reconstructed + * from the packet alone; complete them as errors and let the guest + * driver retry. + */ +static int usb_host_cpr_notifier(NotifierWithReturn *notifier, + MigrationEvent *e, Error **errp) +{ + USBHostDevice *s = container_of(notifier, USBHostDevice, cpr_notifier); + USBDevice *udev = USB_DEVICE(s); + GSList *it; + USBPacket *p; + + if (e->type != MIG_EVENT_PRECOPY_FAILED || !s->cpr_drained) { + return 0; + } + + for (it = s->cpr_inflight; it; it = it->next) { + p = it->data; + /* Replay from the start of the TD */ + p->actual_length = 0; + if (p->ep->nr == 0) { + /* + * p->ep[0] is control endpoint. Control transfers can't + * be reconstructed reliably, so complete them as errors + * and let the guest retry. + */ + p->status = USB_RET_IOERROR; + usb_generic_async_ctrl_complete(udev, p); + } else { + usb_host_handle_data(udev, p); + if (p->status != USB_RET_ASYNC) { + /* Completed synchronously with error */ + usb_packet_complete(udev, p); + } + } + } + g_slist_free(s->cpr_inflight); + s->cpr_inflight = NULL; + s->cpr_drained = false; + return 0; +} + static void usb_host_realize(USBDevice *udev, Error **errp) { USBHostDevice *s = USB_HOST_DEVICE(udev); @@ -1384,6 +1458,14 @@ static void usb_host_realize(USBDevice *udev, Error **errp) s->exit.notify = usb_host_exit_notifier; qemu_add_exit_notifier(&s->exit); +#if LIBUSB_API_VERSION >= 0x01000107 && !defined(CONFIG_WIN32) + if (s->hostdevice && DEVICE(s)->id) { + migration_add_notifier_modes(&s->cpr_notifier, usb_host_cpr_notifier, + MIG_MODE_CPR_TRANSFER, + MIG_MODE_CPR_EXEC, -1); + } +#endif + error_setg(&s->cpr_blocker, "usb-host device %s does not support CPR", DEVICE(s)->id ?: "(anonymous)"); if (migrate_add_blocker_modes(&s->cpr_blocker, errp, @@ -1417,6 +1499,9 @@ static void usb_host_unrealize(USBDevice *udev) } usb_host_close(s); migrate_del_blocker(&s->cpr_blocker); + migration_remove_notifier(&s->cpr_notifier); + g_slist_free(s->cpr_inflight); + s->cpr_inflight = NULL; } static void usb_host_cancel_packet(USBDevice *udev, USBPacket *p) @@ -1424,6 +1509,9 @@ static void usb_host_cancel_packet(USBDevice *udev, USBPacket *p) USBHostDevice *s = USB_HOST_DEVICE(udev); USBHostRequest *r; + /* Packet is being cancelled -> it mustn't be re-issued on failed CPR */ + s->cpr_inflight = g_slist_remove(s->cpr_inflight, p); + if (p->combined) { usb_combined_packet_cancel(udev, p); return; -- 2.47.1