Linux USB
 help / color / mirror / Atom feed
* [PATCH 0/8] virtio-usb: add dual-role virtio USB driver
@ 2026-09-24 16:08 Igor Skalkin
  2026-09-24 16:09 ` [PATCH 1/8] virtio-usb: add protocol header and skeleton dual-role driver Igor Skalkin
                   ` (8 more replies)
  0 siblings, 9 replies; 24+ messages in thread
From: Igor Skalkin @ 2026-09-24 16:08 UTC (permalink / raw)
  To: Michael S . Tsirkin, Jason Wang, Greg Kroah-Hartman
  Cc: virtualization, linux-usb, Vasilii Ianikeev, Aiswarya Cyriac,
	Anton Yakovlev, Trilok Soni, Igor Skalkin

This series adds a new virtio-usb driver: a dual-role virtio device
capable of acting as a USB host controller, a USB device controller,
or both simultaneously with runtime role switching between the two
(USB OTG-style role switching) on ports that support it.

The corresponding virtio-usb device specification has been posted to
virtio-comment for review. This series matches the v2 revision of
that spec, which reconciles a small number of protocol details
(per-role virtqueue presentation, host-role vp_idx for hub/multi-VP
support, and the device-role BIND/UNBIND event split) that were
clarified while integrating and testing this driver against the
spec:

  [RFC PATCH v2] virtio-usb: Add initial virtio-usb specification
  Igor Skalkin <igor.skalkin@oss.qualcomm.com>
  virtio-comment@lists.linux.dev
  https://lore.kernel.org/virtio-comment/20260924154007.143927-1-igor.skalkin@oss.qualcomm.com/

This driver has been tested end-to-end against our own userspace
virtio-usb device implementation (host-side backend) in two setups:
  - arm64 hardware with a type-1 hypervisor, exercising a real dwc3
    USB controller on the host side.
  - x86 with a type-2 hypervisor (QEMU/KVM), using dummy_hcd/dummy_udc
    on the host side in place of real USB hardware.
Host role and device role were exercised in both setups: testusb on
the guest, over a virtualized host port, against g_zero running on
a real hardware UDC or on dummy UDC on the host side (host role);
and testusb on the host side, against g_zero running on a virtualized
UDC on the guest (device role); plus real USB mass-storage
hardware/gadgets and iperf3 over usbnet. Role switching was exercised
in both setups as well, but not with a real OTG cable/ID-pin connection:
on dwc3, role switching was driven through its "role-switch" sysfs
configuration; on dummy_hcd, it was emulated by toggling between two
simultaneously-configured dummy_hcd instances on the same port.
Testing with a real OTG connection is planned as a follow-up.

Known scope: this driver virtualizes an entire UDC per device-role
port, with each port owned by a single guest. USB function-level
virtualization - a single physical UDC exposing a composite gadget
whose individual functions are routed to different guests - is a
separate approach currently being explored, and is out of scope for
this series.

Patch summary:
  1:   protocol header and skeleton dual-role driver (probe/remove
       plumbing, no role support yet).
  2:   USB host controller (HCD) role support.
  3:   USB device controller (UDC) role support.
  4-5: USB OTG-style role query and role-switching support.
  6:   endpoint-lifecycle robustness rework (async split-phase state
       machine, replacing an earlier out-of-tree gadget.nonatomic
       patch that didn't pass upstream review).
  7:   SuperSpeed device-role support.
  8:   guest UDC naming fix (name prefix from the bind event).

Aiswarya Cyriac (3):
  virtio-usb: add protocol header and skeleton dual-role driver
  virtio-usb: add host role (USB Host Controller) support
  virtio-usb: add device role (USB Device Controller) support

Igor Skalkin (5):
  virtio-usb: add OTG role query support
  virtio-usb: add USB On-The-Go role-switching support
  virtio-usb: rework endpoint lifecycle to an async split-phase state
    machine
  virtio-usb: add SuperSpeed device-role support
  virtio-usb: support a guest UDC name prefix from the bind event

 MAINTAINERS                         |   10 +
 drivers/usb/Kconfig                 |    2 +
 drivers/usb/Makefile                |    1 +
 drivers/usb/virtio_usb/Kconfig      |   21 +
 drivers/usb/virtio_usb/Makefile     |    9 +
 drivers/usb/virtio_usb/controller.c |  422 ++++++++
 drivers/usb/virtio_usb/controller.h |  152 +++
 drivers/usb/virtio_usb/device.c     | 1893 +++++++++++++++++++++++++++++++++++
 drivers/usb/virtio_usb/device.h     |  115 +++
 drivers/usb/virtio_usb/host.c       | 1368 +++++++++++++++++++++++++
 drivers/usb/virtio_usb/host.h       |  203 ++++
 drivers/usb/virtio_usb/otg.c        |  403 ++++++++
 drivers/usb/virtio_usb/otg.h        |   38 +
 drivers/usb/virtio_usb/vq_common.c  |  740 ++++++++++++++
 drivers/usb/virtio_usb/vq_common.h  |  163 +++
 include/uapi/linux/virtio_usb.h     |  346 +++++++
 16 files changed, 5886 insertions(+)
 create mode 100644 drivers/usb/virtio_usb/Kconfig
 create mode 100644 drivers/usb/virtio_usb/Makefile
 create mode 100644 drivers/usb/virtio_usb/controller.c
 create mode 100644 drivers/usb/virtio_usb/controller.h
 create mode 100644 drivers/usb/virtio_usb/device.c
 create mode 100644 drivers/usb/virtio_usb/device.h
 create mode 100644 drivers/usb/virtio_usb/host.c
 create mode 100644 drivers/usb/virtio_usb/host.h
 create mode 100644 drivers/usb/virtio_usb/otg.c
 create mode 100644 drivers/usb/virtio_usb/otg.h
 create mode 100644 drivers/usb/virtio_usb/vq_common.c
 create mode 100644 drivers/usb/virtio_usb/vq_common.h
 create mode 100644 include/uapi/linux/virtio_usb.h

-- 
2.49.0

^ permalink raw reply	[flat|nested] 24+ messages in thread

* [PATCH 1/8] virtio-usb: add protocol header and skeleton dual-role driver
  2026-09-24 16:08 [PATCH 0/8] virtio-usb: add dual-role virtio USB driver Igor Skalkin
@ 2026-09-24 16:09 ` Igor Skalkin
  2026-09-25  5:14   ` Greg Kroah-Hartman
  2026-09-25  5:21   ` Greg Kroah-Hartman
  2026-09-24 16:09 ` [PATCH 2/8] virtio-usb: add host role (USB Host Controller) support Igor Skalkin
                   ` (7 subsequent siblings)
  8 siblings, 2 replies; 24+ messages in thread
From: Igor Skalkin @ 2026-09-24 16:09 UTC (permalink / raw)
  To: Michael S . Tsirkin, Jason Wang, Greg Kroah-Hartman
  Cc: virtualization, linux-usb, Vasilii Ianikeev, Aiswarya Cyriac,
	Anton Yakovlev, Trilok Soni, Igor Skalkin

From: Aiswarya Cyriac <aiswarya.cyriac@oss.qualcomm.com>

Add the virtio_usb device and virtqueue-wrapper skeleton, the
protocol header shared with the vhost gadget driver and userspace
backend, and probe()/remove() plumbing with no role support yet.

Introduce a per-port virtio_usb_port array (vports[]), sized to the
device's negotiated port count, ahead of any code that actually
populates or reads it. Every later commit that adds a role to a port
(host, device, OTG) builds on this same array from the start, instead
of the host, device, and OTG subsystems each growing their own
separate port-indexed storage that later has to be reconciled.

Wire the new drivers/usb/virtio_usb/ directory into the USB
subsystem's build (drivers/usb/Kconfig, drivers/usb/Makefile) as a
new CONFIG_USB_VIRTIO option, listed alongside the other USB
dual-mode controller drivers.

Signed-off-by: Igor Skalkin <igor.skalkin@oss.qualcomm.com>
---
 MAINTAINERS                         |   10 +
 drivers/usb/Kconfig                 |    2 
 drivers/usb/Makefile                |    1 
 drivers/usb/virtio_usb/Kconfig      |   21 ++
 drivers/usb/virtio_usb/Makefile     |    5 
 drivers/usb/virtio_usb/controller.c |  155 ++++++++++++++++++
 drivers/usb/virtio_usb/controller.h |   84 ++++++++++
 include/uapi/linux/virtio_usb.h     |  302 ++++++++++++++++++++++++++++++++++++
 8 files changed, 580 insertions(+)
 create mode 100644 drivers/usb/virtio_usb/Kconfig
 create mode 100644 drivers/usb/virtio_usb/Makefile
 create mode 100644 drivers/usb/virtio_usb/controller.c
 create mode 100644 drivers/usb/virtio_usb/controller.h
 create mode 100644 include/uapi/linux/virtio_usb.h

diff --git a/MAINTAINERS b/MAINTAINERS
index cc3cae2e378b..93869e5f4b51 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -28450,6 +28450,16 @@ F:	drivers/media/usb/uvc/
 F:	include/linux/usb/uvc.h
 F:	include/uapi/linux/uvcvideo.h
 
+USB VIRTIO DRIVER
+M:	Igor Skalkin <igor.skalkin@oss.qualcomm.com>
+R:	Aiswarya Cyriac <aiswarya.cyriac@oss.qualcomm.com>
+R:	Vasilii Ianikeev <vasilii.ianikeev@oss.qualcomm.com>
+L:	linux-usb@vger.kernel.org
+L:	virtualization@lists.linux.dev
+S:	Maintained
+F:	drivers/usb/virtio_usb/
+F:	include/uapi/linux/virtio_usb.h
+
 USB WEBCAM GADGET
 L:	linux-usb@vger.kernel.org
 S:	Orphan
diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig
index abf8c6c..68076db 100644
--- a/drivers/usb/Kconfig
+++ b/drivers/usb/Kconfig
@@ -139,6 +139,8 @@ source "drivers/usb/chipidea/Kconfig"
 
 source "drivers/usb/isp1760/Kconfig"
 
+source "drivers/usb/virtio_usb/Kconfig"
+
 comment "USB port drivers"
 
 if USB
diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile
index eecbd63..f28a4b7 100644
--- a/drivers/usb/Makefile
+++ b/drivers/usb/Makefile
@@ -12,6 +12,7 @@ obj-$(CONFIG_USB_SUPPORT)	+= phy/
 obj-$(CONFIG_USB_DWC3)		+= dwc3/
 obj-$(CONFIG_USB_DWC2)		+= dwc2/
 obj-$(CONFIG_USB_ISP1760)	+= isp1760/
+obj-$(CONFIG_USB_VIRTIO)	+= virtio_usb/
 
 obj-$(CONFIG_USB_CDNS_SUPPORT)	+= cdns3/
 
diff --git a/drivers/usb/virtio_usb/Kconfig b/drivers/usb/virtio_usb/Kconfig
new file mode 100644
index 0000000..bd377b9
--- /dev/null
+++ b/drivers/usb/virtio_usb/Kconfig
@@ -0,0 +1,21 @@
+config USB_VIRTIO
+	tristate "Virtio USB device support"
+	depends on USB_SUPPORT && VIRTIO
+	depends on USB || USB_GADGET
+	depends on USB if !USB_GADGET
+	depends on USB_GADGET if !USB
+	select USB_ROLE_SWITCH
+	help
+	  Say Y here if you want to support a virtio-usb device: a virtio
+	  transport for USB that can present itself to the guest as a USB
+	  host controller, a USB device controller (UDC), or both, with
+	  each port independently configurable and, on ports where the
+	  virtio-usb device advertises support for it, capable of
+	  switching between the host and device role at runtime (USB
+	  OTG-style role switching), backed by the standard USB Role
+	  Switch class.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called virtio-usb.
+
+	  If unsure, say N.
diff --git a/drivers/usb/virtio_usb/Makefile b/drivers/usb/virtio_usb/Makefile
new file mode 100644
index 0000000..b7ee9e8
--- /dev/null
+++ b/drivers/usb/virtio_usb/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+virtio-usb-y := controller.o
+
+obj-$(CONFIG_USB_VIRTIO) += virtio-usb.o
diff --git a/drivers/usb/virtio_usb/controller.c b/drivers/usb/virtio_usb/controller.c
new file mode 100644
index 0000000..2fc6f50
--- /dev/null
+++ b/drivers/usb/virtio_usb/controller.c
@@ -0,0 +1,155 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * virtio_usb: VirtIO USB device
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <linux/module.h>
+#include <uapi/linux/virtio_ids.h>
+
+#include "controller.h"
+
+/**
+ * virtio_usb_find_vqs() - Enumerate and initialize all virtqueues.
+ * @vusb: VirtIO usb device.
+ *
+ * Context: Any context.
+ * Return: 0 on success, -errno on failure.
+ */
+static int virtio_usb_find_vqs(struct virtio_usb *vusb)
+{
+	unsigned int nvqs = vusb->nvqs, i;
+	struct virtqueue **vqs = NULL;
+	int rc = -ENOMEM;
+
+	vqs = kcalloc(nvqs, sizeof(void *), GFP_KERNEL);
+	if (!vqs)
+		return -ENOMEM;
+
+	struct virtqueue_info *vqs_info =
+		kcalloc(nvqs, sizeof(*vqs_info), GFP_KERNEL);
+	if (!vqs_info) {
+		rc = -ENOMEM;
+		goto on_exit;
+	}
+	for (i = 0; i < nvqs; i++) {
+		vqs_info[i].name = vusb->vqueues[i].name;
+		vqs_info[i].callback = vusb->vqueues[i].callback;
+	}
+	rc = virtio_find_vqs(vusb->vdev, nvqs, vqs, vqs_info, NULL);
+
+	kfree(vqs_info);
+
+	for (i = 0; i < nvqs; i++) {
+		vusb->vqueues[i].vqueue = vqs[i];
+		spin_lock_init(&vusb->vqueues[i].lock);
+	}
+on_exit:
+	kfree(vqs);
+	return rc;
+}
+
+/**
+ * virtio_usb_validate() - Validate if the device can be started.
+ * @vdev: VirtIO parent device.
+ *
+ * Context: Any context.
+ * Return: 0 on success, -EINVAL on failure.
+ */
+static int virtio_usb_validate(struct virtio_device *vdev)
+{
+	if (!vdev->config->get) {
+		dev_err(&vdev->dev, "configuration access disabled\n");
+		return -EINVAL;
+	}
+
+	if (!virtio_has_feature(vdev, VIRTIO_F_VERSION_1)) {
+		dev_err(&vdev->dev,
+			"device does not comply with spec version 1.x\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/**
+ * virtio_usb_probe() - Probe VirtIO usb controller.
+ * @vdev: VirtIO parent device.
+ *
+ * Context: Any context that permits to sleep.
+ * Return: 0 on success, -errno on failure.
+ */
+static int virtio_usb_probe(struct virtio_device *vdev)
+{
+	struct virtio_usb *vusb;
+	unsigned int nvqs = 0;
+	int rc = 0;
+
+	vusb = devm_kzalloc(&vdev->dev, sizeof(*vusb), GFP_KERNEL);
+	if (!vusb)
+		return -ENOMEM;
+
+	vusb->vdev = vdev;
+	vdev->priv = vusb;
+
+	virtio_cread_le(vdev, struct virtio_usb_config, ports, &vusb->nports);
+
+	vusb->vports = devm_kcalloc(&vdev->dev, vusb->nports,
+				    sizeof(*vusb->vports), GFP_KERNEL);
+	if (!vusb->vports)
+		return -ENOMEM;
+
+	vusb->vqueues = devm_kcalloc(&vdev->dev, nvqs, sizeof(*vusb->vqueues),
+				     GFP_KERNEL);
+	if (!vusb->vqueues)
+		return -ENOMEM;
+
+	vusb->nvqs = nvqs;
+
+	rc = virtio_usb_find_vqs(vusb);
+	if (rc)
+		goto on_exit;
+
+	virtio_device_ready(vdev);
+
+on_exit:
+	return rc;
+}
+
+/**
+ * virtio_usb_remove() - Remove VirtIO usb device.
+ * @vdev: VirtIO parent device.
+ *
+ * Context: Any context that permits to sleep.
+ */
+static void virtio_usb_remove(struct virtio_device *vdev)
+{
+	struct virtio_usb *vusb = vdev->priv;
+	int i;
+
+	virtio_reset_device(vdev);
+	for (i = 0; i < vusb->nvqs; i++)
+		vusb->vqueues[i].stop(vusb, &vusb->vqueues[i]);
+
+	vdev->config->del_vqs(vdev);
+}
+
+static const struct virtio_device_id id_table[] = {
+	{ VIRTIO_ID_USB, VIRTIO_DEV_ANY_ID },
+	{ 0 },
+};
+
+static struct virtio_driver virtio_usb_driver = {
+	.driver.name = KBUILD_MODNAME,
+	.id_table = id_table,
+	.validate = virtio_usb_validate,
+	.probe = virtio_usb_probe,
+	.remove = virtio_usb_remove,
+};
+
+module_virtio_driver(virtio_usb_driver);
+
+MODULE_DEVICE_TABLE(virtio, id_table);
+MODULE_DESCRIPTION("Dual-role virtio-usb controller driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/virtio_usb/controller.h b/drivers/usb/virtio_usb/controller.h
new file mode 100644
index 0000000..eb07d0b
--- /dev/null
+++ b/drivers/usb/virtio_usb/controller.h
@@ -0,0 +1,84 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * virtio-usb: Virtio usb device
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef VIRTIO_USB_CONTROLLER_H
+#define VIRTIO_USB_CONTROLLER_H
+
+#include <linux/slab.h>
+#include <linux/virtio.h>
+#include <linux/virtio_config.h>
+
+#include <uapi/linux/virtio_usb.h>
+
+/**
+ * struct virtio_usb_port - Per-virtual-port state.
+ * @role: Role of this port (VIRTIO_USB_ROLE_HOST or _DEVICE), once a
+ *        role-providing commit has assigned it. Unused for now - this
+ *        struct is deliberately introduced ahead of any code that
+ *        populates or reads @role, so that every later commit that adds
+ *        a role (host, device, OTG) can build on this same per-port
+ *        array from the start instead of each reinventing its own
+ *        port-indexed storage.
+ */
+struct virtio_usb_port {
+	unsigned int role;
+};
+
+/**
+ * struct virtio_usb - VirtIO USB device.
+ * @vdev: Underlying virtio device
+ * @vqueues: Array of virtqueue wrappers.
+ * @vports: Array of per-port structures, one entry per virtual port.
+ * @nports: number of supported ports
+ * @nvqs: number of virtqueues for the device
+ */
+struct virtio_usb {
+	struct virtio_device *vdev;
+	struct virtio_usb_queue *vqueues;
+	struct virtio_usb_port *vports;
+	unsigned int nports;
+	u32 nvqs;
+};
+
+/**
+ * struct virtio_usb_queue - Virtqueue wrapper structure.
+ * @name: Name of the virtqueue.
+ * @callback: Used to synchronize access to a virtqueue.
+ * @vqueue: Underlying virtqueue.
+ * @lock: Used to synchronize access to a virtqueue.
+ * @process: queue process callback function
+ * @stop: queue stop callback function
+ */
+struct virtio_usb_queue {
+	const char *name;
+	vq_callback_t *callback;
+	struct virtqueue *vqueue;
+	spinlock_t lock;
+	void (*process)(struct virtio_usb *vusb, void *buf);
+	void (*stop)(struct virtio_usb *vusb, struct virtio_usb_queue *vq);
+};
+
+/**
+ * struct virtio_usb_vq_desc - Read-only virtqueue template.
+ * @name: Name of the virtqueue.
+ * @callback: Virtqueue notification callback function.
+ * @process: queue process callback function.
+ * @stop: queue stop callback function.
+ *
+ * Immutable per-role virtqueue description, copied into a fresh
+ * per-instance struct virtio_usb_queue during probe() instead of being
+ * shared directly - so that multiple probed virtio_usb instances never
+ * alias the same struct virtqueue.
+ */
+struct virtio_usb_vq_desc {
+	const char *name;
+	vq_callback_t *callback;
+	void (*process)(struct virtio_usb *vusb, void *buf);
+	void (*stop)(struct virtio_usb *vusb, struct virtio_usb_queue *vq);
+};
+
+#endif /* VIRTIO_USB_CONTROLLER_H */
diff --git a/include/uapi/linux/virtio_usb.h b/include/uapi/linux/virtio_usb.h
new file mode 100644
index 0000000..b9dc448
--- /dev/null
+++ b/include/uapi/linux/virtio_usb.h
@@ -0,0 +1,302 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef _UAPI_LINUX_VIRTIO_USB_H
+#define _UAPI_LINUX_VIRTIO_USB_H
+
+#include <linux/virtio_types.h>
+
+/*****************************************************************************
+ * FEATURE BITS
+ */
+enum {
+	/* Host controller is supported */
+	VIRTIO_USB_F_HOST = 0,
+	/* Device controller is supported */
+	VIRTIO_USB_F_DEVICE,
+	/* Role switching is supported */
+	VIRTIO_USB_F_SWITCH_ROLE,
+};
+
+/*****************************************************************************
+ * CONFIGURATION SPACE
+ */
+struct virtio_usb_config {
+	/* # of available ports */
+	__le32 ports;
+};
+
+/*****************************************************************************
+ * COMMON DEFINITIONS
+ */
+enum {
+	VIRTIO_USB_VQ_HOST_COMMAND = 0,
+	VIRTIO_USB_VQ_HOST_EVENT,
+	VIRTIO_USB_VQ_HOST_DATA,
+	VIRTIO_USB_VQ_DEV_COMMAND,
+	VIRTIO_USB_VQ_DEV_EVENT,
+	VIRTIO_USB_VQ_DEV_DATA,
+	VIRTIO_USB_VQ_OTG_COMMAND,
+	VIRTIO_USB_VQ_OTG_EVENT,
+	VIRTIO_USB_VQ_MAX,
+};
+
+enum {
+	VIRTIO_USB_EP_CONTROL = 0,
+	VIRTIO_USB_EP_INTERRUPT,
+	VIRTIO_USB_EP_BULK,
+	VIRTIO_USB_EP_ISOCHRONOUS,
+};
+
+enum {
+	VIRTIO_USB_S_OK = 0,
+	VIRTIO_USB_S_ERR_BAD_MSG,
+	VIRTIO_USB_S_ERR_INTERNAL,
+	VIRTIO_USB_S_ERR_NO_DEVICE,
+	VIRTIO_USB_S_ERR_ISO_XFER,
+	VIRTIO_USB_S_ERR_ISO_BIG,
+	VIRTIO_USB_S_ERR_DATA_IN,
+	VIRTIO_USB_S_ERR_DATA_OUT,
+	VIRTIO_USB_S_ERR_MSG_SIZE,
+	VIRTIO_USB_S_ERR_OVERFLOW,
+	VIRTIO_USB_S_ERR_STALL,
+	VIRTIO_USB_S_ERR_SHORT_PKT,
+	VIRTIO_USB_S_ERR_CANCELLED,
+	VIRTIO_USB_S_ERR_HOST,
+};
+
+struct virtio_usb_cmd_status {
+	__le32 code; /* VIRTIO_USB_S_XXX */
+};
+
+/*****************************************************************************
+ * HOST COMMAND MESSAGES
+ */
+enum {
+	VIRTIO_USB_CMD_HOST_CANCEL = 0,
+	VIRTIO_USB_CMD_HOST_STREAMS_ALLOC,
+	VIRTIO_USB_CMD_HOST_STREAMS_FREE,
+};
+
+struct virtio_usb_host_cmd_hdr {
+	__le32 code; /* VIRTIO_USB_CMD_HOST_XXX */
+	__le32 port; /* Port ID */
+};
+
+/* VIRTIO_USB_CMD_HOST_CANCEL */
+struct virtio_usb_host_cmd_cancel {
+	struct virtio_usb_host_cmd_hdr hdr;
+	__le64 tag;
+};
+
+/* VIRTIO_USB_CMD_HOST_STREAMS_ALLOC/FREE */
+struct virtio_usb_host_cmd_streams {
+	struct virtio_usb_host_cmd_hdr hdr;
+	__le32 number_of_streams;
+	__le32 number_of_eps;
+};
+
+/*****************************************************************************
+ * HOST EVENT MESSAGES
+ */
+enum {
+	VIRTIO_USB_EVT_HOST_PORT_CONNECTED = 0,
+	VIRTIO_USB_EVT_HOST_PORT_DISCONNECTED,
+};
+
+/* VIRTIO_USB_EVT_HOST_PORT_CONNECTED/DISCONNECTED */
+enum {
+	VIRTIO_USB_SPEED_UNKNOWN = 0,
+	VIRTIO_USB_SPEED_LOW,
+	VIRTIO_USB_SPEED_FULL, /* usb 1.1 */
+	VIRTIO_USB_SPEED_HIGH, /* usb 2.0 */
+	VIRTIO_USB_SPEED_WIRELESS, /* wireless (usb 2.5) */
+	VIRTIO_USB_USB_SPEED_SUPER, /* usb 3.0 */
+	VIRTIO_USB_SPEED_SUPER_PLUS, /* usb 3.1 */
+};
+
+struct virtio_usb_host_port_event {
+	__le32 code; /* VIRTIO_USB_EVT_HOST_PORT_XXX */
+	__le32 port_id;
+	__le32 speed; /* VIRTIO_USB_SPEED_XXX */
+	__le32 padding;
+};
+
+/*****************************************************************************
+ * DEVICE COMMAND MESSAGES
+ */
+enum {
+	VIRTIO_USB_CMD_DEV_GET_ENDPOINT_COUNT = 0,
+	VIRTIO_USB_CMD_DEV_GET_ENDPOINT_INFO,
+	VIRTIO_USB_CMD_DEV_PULLUP,
+	VIRTIO_USB_CMD_DEV_VBUS_DRAW,
+	VIRTIO_USB_CMD_DEV_EP_ENABLE,
+	VIRTIO_USB_CMD_DEV_EP_DISABLE,
+	VIRTIO_USB_CMD_DEV_EP_SET_HALT,
+	VIRTIO_USB_CMD_DEV_EP_SET_WEDGE,
+	VIRTIO_USB_CMD_DEV_CANCEL,
+};
+
+struct virtio_usb_dev_cmd_hdr {
+	__le32 code; /* VIRTIO_USB_CMD_DEV_XXX */
+	__le16 port; /* Port ID */
+	__le16 endpoint; /* Endpoint ID */
+};
+
+/* VIRTIO_USB_CMD_DEV_GET_ENDPOINT_COUNT */
+struct virtio_usb_dev_cmd_ep_count {
+	struct virtio_usb_cmd_status status;
+	__le32 count; /* # of supported endpoints */
+};
+
+enum {
+	VIRTIO_USB_DIR_OUT = 0,
+	VIRTIO_USB_DIR_IN,
+};
+
+enum {
+	VIRTIO_USB_EP_DIR_OUT = 0,
+	VIRTIO_USB_EP_DIR_IN = 0x80,
+};
+
+/**
+ * VIRTIO_USB_CMD_DEV_GET_ENDPOINT_INFO
+ *
+ * Response:
+ *     struct virtio_usb_cmd_status
+ *     struct virtio_usb_dev_cmd_ep_info [count]
+ */
+struct virtio_usb_dev_cmd_ep_info {
+	__le16 types; /* supported type bit map (1 << VIRTIO_USB_EP_XXX) */
+	__le16 directions /* supported direction bit map (1 << VIRTIO_USB_DIR_XXX) */;
+	__le16 maxpacket_limit;
+	__le16 max_streams;
+};
+
+/*******************************************************************************
+ * DEVICE EVENT MESSAGES
+ */
+enum {
+	VIRTIO_USB_EVT_DEV_CONNECTED = 0,
+	VIRTIO_USB_EVT_DEV_DISCONNECTED,
+	VIRTIO_USB_EVT_DEV_SETUP,
+	VIRTIO_USB_EVT_DEV_RESET,
+	VIRTIO_USB_EVT_DEV_SUSPEND,
+	VIRTIO_USB_EVT_DEV_RESUME,
+};
+
+struct virtio_usb_dev_event {
+	__le32 code; /* VIRTIO_USB_EVT_DEV_XXX */
+	__u8 padding[12];
+};
+
+/* VIRTIO_USB_EVT_DEV_SETUP */
+struct virtio_usb_dev_setup_event {
+	__le32 code; /* VIRTIO_USB_EVT_DEV_SETUP */
+	__u8 setup[8]; /* setup packet contents */
+	__u8 padding[4];
+};
+
+/*******************************************************************************
+ * HOST/DEVICE DATA MESSAGES
+ */
+enum {
+	VIRTIO_USB_FLAG_SHORT_NOT_OK = (1 << 0),
+	VIRTIO_USB_FLAG_ISO_ASAP = (1 << 1),
+	VIRTIO_USB_FLAG_ZERO_PACKET = (1 << 2),
+};
+
+struct virtio_usb_request {
+	__le64 tag;
+	__le16 port; /* Port ID */
+	__le16 endpoint; /* Endpoint ID */
+	__le16 transfer_type; /* VIRTIO_USB_EP_XXX */
+	__le16 transfer_flags; /* VIRTIO_USB_FLAG_XXX */
+	union {
+		/* transfer_type = VIRTIO_USB_EP_CONTROL */
+		struct {
+			__u8 setup[8];
+		} control;
+		/* transfer_type = VIRTIO_USB_EP_INTERRUPT */
+		struct {
+			__le32 interval;
+		} interrupt;
+		/* transfer_type = VIRTIO_USB_EP_BULK */
+		struct {
+			__le32 stream_id;
+		} bulk;
+		/* transfer_type = VIRTIO_USB_EP_ISOCHRONOUS */
+		struct {
+			__le32 interval;
+			__le32 start_frame;
+			__le32 number_of_packets;
+			__le32 padding;
+		} iso;
+	};
+};
+
+struct virtio_usb_response {
+	__le32 status; /* VIRTIO_USB_S_XXX */
+	__le32 actual_length;
+	__le32 interval; /* type = VIRTIO_USB_EP_INTERRUPT/ISOCHRONOUS */
+	__le32 start_frame; /* type = VIRTIO_USB_EP_ISOCHRONOUS */
+};
+
+/**
+ * ISO request:
+ *     struct virtio_usb_request
+ *     struct virtio_usb_iso_packet [number_of_packets]
+ * ISO response:
+ *     struct virtio_usb_response
+ *     struct virtio_usb_iso_status [number_of_packets]
+ */
+struct virtio_usb_iso_packet {
+	__le32 offset;
+	__le32 length;
+};
+
+struct virtio_usb_iso_status {
+	__le32 status; /* VIRTIO_USB_S_XXX */
+	__le32 actual_length;
+};
+
+/*****************************************************************************
+ * OTG COMMAND MESSAGES
+ */
+enum {
+	VIRTIO_USB_CMD_OTG_GET_ROLE = 0,
+	/* If VIRTIO_USB_F_SWITCH_ROLE is negotiated */
+	VIRTIO_USB_CMD_OTG_SWITCH_ROLE,
+};
+
+struct virtio_usb_otg_cmd_hdr {
+	__le32 code; /* VIRTIO_USB_CMD_OTG_XXX */
+	__le32 port; /* Port ID */
+};
+
+/* VIRTIO_USB_CMD_OTG_GET_ROLE */
+enum {
+	VIRTIO_USB_ROLE_HOST = 0,
+	VIRTIO_USB_ROLE_DEVICE,
+};
+
+struct virtio_usb_otg_cmd_role {
+	struct virtio_usb_cmd_status status;
+	__le32 role; /* VIRTIO_USB_ROLE_XXX */
+};
+
+/*****************************************************************************
+ * OTG EVENT MESSAGES
+ */
+enum {
+	VIRTIO_USB_EVT_OTG_CHANGE_ROLE = 0,
+};
+
+struct virtio_usb_otg_event {
+	__le32 code; /* VIRTIO_USB_EVT_OTG_XXX */
+	__le32 port; /* Port ID */
+};
+
+#endif /* _UAPI_LINUX_VIRTIO_USB_H */

^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH 2/8] virtio-usb: add host role (USB Host Controller) support
  2026-09-24 16:08 [PATCH 0/8] virtio-usb: add dual-role virtio USB driver Igor Skalkin
  2026-09-24 16:09 ` [PATCH 1/8] virtio-usb: add protocol header and skeleton dual-role driver Igor Skalkin
@ 2026-09-24 16:09 ` Igor Skalkin
  2026-09-25  5:18   ` Greg Kroah-Hartman
  2026-09-24 16:09 ` [PATCH 3/8] virtio-usb: add device role (USB Device " Igor Skalkin
                   ` (6 subsequent siblings)
  8 siblings, 1 reply; 24+ messages in thread
From: Igor Skalkin @ 2026-09-24 16:09 UTC (permalink / raw)
  To: Michael S . Tsirkin, Jason Wang, Greg Kroah-Hartman
  Cc: virtualization, linux-usb, Vasilii Ianikeev, Aiswarya Cyriac,
	Anton Yakovlev, Trilok Soni, Igor Skalkin

From: Aiswarya Cyriac <aiswarya.cyriac@oss.qualcomm.com>

Add the common virtqueue handling code (command, event and data
queues) shared by every role, and the virtio-usb host controller
(HCD) implementation, wiring it up as the host role of the dual-role
driver on top of that common code.

Each host-role virtual port gets its own HS+SS usb_hcd pair and its
own root hub (struct virtio_usb_hc_vp), with a fixed
VIRTIO_USB_VP_MAX_PORTS (8) leaf slots pre-allocated at VP init time
and reused across connect/disconnect - never dynamically alloc'd or
freed. This lets the backend forward more than one physical socket -
and, for host ports behind a physical hub, more than one leaf device
per socket - as independent virtual ports from the start, instead of
collapsing everything onto a single shared root hub and having to
revisit that decision once more than one host-role port needs to
exist at the same time.

virtio_usb_add_hcd() derives each VP's HCD bus_name from the parent
virtio_device with devm_kasprintf() rather than a stack buffer, since
usb_create_hcd()/usb_create_shared_hcd() store that pointer as-is in
hcd->self.bus_name without copying it - it must outlive the HCD
itself.

Every port is host-role for now, since no other role exists yet;
vports[].role is populated unconditionally until later commits add
device role and OTG-based role resolution.

Signed-off-by: Aiswarya Cyriac <aiswarya.cyriac@oss.qualcomm.com>
Co-developed-by: Anton Yakovlev <anton.yakovlev@oss.qualcomm.com>
Signed-off-by: Anton Yakovlev <anton.yakovlev@oss.qualcomm.com>
Signed-off-by: Vasilii Ianikeev <vasilii.ianikeev@oss.qualcomm.com>
Co-developed-by: Igor Skalkin <igor.skalkin@oss.qualcomm.com>
Signed-off-by: Igor Skalkin <igor.skalkin@oss.qualcomm.com>
---
 drivers/usb/virtio_usb/Makefile     |    4 
 drivers/usb/virtio_usb/controller.c |  105 ++
 drivers/usb/virtio_usb/controller.h |   35 
 drivers/usb/virtio_usb/host.c       | 1335 ++++++++++++++++++++++++++++++++++++
 drivers/usb/virtio_usb/host.h       |  203 +++++
 drivers/usb/virtio_usb/vq_common.c  |  740 +++++++++++++++++++
 drivers/usb/virtio_usb/vq_common.h  |  163 ++++
 include/uapi/linux/virtio_usb.h     |   16 
 8 files changed, 2585 insertions(+), 16 deletions(-)
 create mode 100644 drivers/usb/virtio_usb/host.c
 create mode 100644 drivers/usb/virtio_usb/host.h
 create mode 100644 drivers/usb/virtio_usb/vq_common.c
 create mode 100644 drivers/usb/virtio_usb/vq_common.h

diff --git a/drivers/usb/virtio_usb/controller.c b/drivers/usb/virtio_usb/controller.c
index 2fc6f50..216edfc 100644
--- a/drivers/usb/virtio_usb/controller.c
+++ b/drivers/usb/virtio_usb/controller.c
@@ -6,9 +6,16 @@
  */
 
 #include <linux/module.h>
+#include <linux/moduleparam.h>
 #include <uapi/linux/virtio_ids.h>
 
 #include "controller.h"
+#include "host.h"
+#include "vq_common.h"
+
+u32 virtio_usb_cmd_timeout_ms = MSEC_PER_SEC;
+module_param_named(cmd_timeout_ms, virtio_usb_cmd_timeout_ms, uint, 0644);
+MODULE_PARM_DESC(cmd_timeout_ms, "Command completion timeout in milliseconds");
 
 /**
  * virtio_usb_find_vqs() - Enumerate and initialize all virtqueues.
@@ -70,9 +77,22 @@ static int virtio_usb_validate(struct virtio_device *vdev)
 		return -EINVAL;
 	}
 
+	if (!virtio_has_feature(vdev, VIRTIO_USB_F_HOST)) {
+		dev_err(&vdev->dev,
+			"device should support at least one usb role\n");
+		return -EINVAL;
+	}
+
+	if (!virtio_usb_cmd_timeout_ms) {
+		dev_err(&vdev->dev, "msg_timeout_ms value cannot be zero\n");
+		return -EINVAL;
+	}
+
 	return 0;
 }
 
+static void virtio_usb_remove(struct virtio_device *vdev);
+
 /**
  * virtio_usb_probe() - Probe VirtIO usb controller.
  * @vdev: VirtIO parent device.
@@ -84,7 +104,7 @@ static int virtio_usb_probe(struct virtio_device *vdev)
 {
 	struct virtio_usb *vusb;
 	unsigned int nvqs = 0;
-	int rc = 0;
+	int rc = 0, i = 0;
 
 	vusb = devm_kzalloc(&vdev->dev, sizeof(*vusb), GFP_KERNEL);
 	if (!vusb)
@@ -100,6 +120,22 @@ static int virtio_usb_probe(struct virtio_device *vdev)
 	if (!vusb->vports)
 		return -ENOMEM;
 
+	if (virtio_has_feature(vdev, VIRTIO_USB_F_HOST))
+		vusb->host_role = 1;
+
+	/* Only host_role exists so far, so every port is unambiguously a
+	 * host-role port. Later commits (device role, OTG) will replace
+	 * this with real per-port role resolution.
+	 */
+	vusb->host_vq_base = -1;
+	if (vusb->host_role) {
+		vusb->host_vq_base = nvqs;
+		nvqs += VIRTIO_USB_VQ_HOST_MAX;
+
+		for (i = 0; i < vusb->nports; i++)
+			vusb->vports[i].role = VIRTIO_USB_ROLE_HOST;
+	}
+
 	vusb->vqueues = devm_kcalloc(&vdev->dev, nvqs, sizeof(*vusb->vqueues),
 				     GFP_KERNEL);
 	if (!vusb->vqueues)
@@ -107,13 +143,60 @@ static int virtio_usb_probe(struct virtio_device *vdev)
 
 	vusb->nvqs = nvqs;
 
+	if (vusb->host_vq_base >= 0)
+		for (i = 0; i < VIRTIO_USB_VQ_HOST_MAX; i++) {
+			vusb->vqueues[vusb->host_vq_base + i].name =
+				host_vqueues[i].name;
+			vusb->vqueues[vusb->host_vq_base + i].callback =
+				host_vqueues[i].callback;
+			vusb->vqueues[vusb->host_vq_base + i].process =
+				host_vqueues[i].process;
+			vusb->vqueues[vusb->host_vq_base + i].stop =
+				host_vqueues[i].stop;
+		}
+
 	rc = virtio_usb_find_vqs(vusb);
-	if (rc)
-		goto on_exit;
+	if (rc) {
+		dev_err(&vdev->dev, "%s virtio_usb_find_vqs() error(%d)\n",
+			__func__, rc);
+		goto on_error;
+	}
+
+	if (vusb->host_role) {
+		INIT_WORK(&vusb->vq_host_data_rx_work, virtio_usb_hc_rx_work);
+		INIT_WORK(&vusb->vq_host_evt_work, virtio_usb_hc_evt_work);
+
+		/* Initialize one HCD pair per host-role VP. */
+		for (i = 0; i < vusb->nports; i++) {
+			if (vusb->vports[i].role != VIRTIO_USB_ROLE_HOST)
+				continue;
+			rc = virtio_usb_hc_vp_init(vusb, i);
+			if (rc) {
+				dev_err(&vdev->dev,
+					"%s virtio_usb_hc_vp_init() port=%d error(%d)\n",
+					__func__, i, rc);
+				goto on_error;
+			}
+		}
+		/* Populate the shared host event queue once, after every
+		 * VP is initialized.
+		 */
+		rc = virtio_usb_hc_event_populate(vusb);
+		if (rc) {
+			dev_err(&vdev->dev,
+				"%s virtio_usb_hc_event_populate() error(%d)\n",
+				__func__, rc);
+			goto on_error;
+		}
+	}
 
 	virtio_device_ready(vdev);
 
-on_exit:
+	return rc;
+
+on_error:
+	dev_err(&vdev->dev, "%s failed(%d)\n", __func__, rc);
+	virtio_usb_remove(vdev);
 	return rc;
 }
 
@@ -128,13 +211,23 @@ static void virtio_usb_remove(struct virtio_device *vdev)
 	struct virtio_usb *vusb = vdev->priv;
 	int i;
 
-	virtio_reset_device(vdev);
 	for (i = 0; i < vusb->nvqs; i++)
 		vusb->vqueues[i].stop(vusb, &vusb->vqueues[i]);
 
+	if (vusb->host_role && vusb->vports) {
+		for (i = 0; i < (int)vusb->nports; i++)
+			virtio_usb_hc_vp_deinit(vusb, i);
+	}
+
+	virtio_reset_device(vdev);
+
 	vdev->config->del_vqs(vdev);
 }
 
+static const unsigned int virtio_usb_features[] = {
+	VIRTIO_USB_F_HOST,
+};
+
 static const struct virtio_device_id id_table[] = {
 	{ VIRTIO_ID_USB, VIRTIO_DEV_ANY_ID },
 	{ 0 },
@@ -142,6 +235,8 @@ static const struct virtio_device_id id_table[] = {
 
 static struct virtio_driver virtio_usb_driver = {
 	.driver.name = KBUILD_MODNAME,
+	.feature_table = virtio_usb_features,
+	.feature_table_size = ARRAY_SIZE(virtio_usb_features),
 	.id_table = id_table,
 	.validate = virtio_usb_validate,
 	.probe = virtio_usb_probe,
diff --git a/drivers/usb/virtio_usb/controller.h b/drivers/usb/virtio_usb/controller.h
index eb07d0b..af68a77 100644
--- a/drivers/usb/virtio_usb/controller.h
+++ b/drivers/usb/virtio_usb/controller.h
@@ -14,18 +14,23 @@
 
 #include <uapi/linux/virtio_usb.h>
 
+/* Forward declaration - full definition in host.h */
+struct virtio_usb_hc_vp;
+
+#define VIRTIO_USB_VQ_COMMAND_IDX 0
+#define VIRTIO_USB_VQ_EVENT_IDX 1
+#define VIRTIO_USB_VQ_DATA_IDX 2
+
+#define VIRTIO_USB_VQ_HOST_MAX 3
+
 /**
  * struct virtio_usb_port - Per-virtual-port state.
- * @role: Role of this port (VIRTIO_USB_ROLE_HOST or _DEVICE), once a
- *        role-providing commit has assigned it. Unused for now - this
- *        struct is deliberately introduced ahead of any code that
- *        populates or reads @role, so that every later commit that adds
- *        a role (host, device, OTG) can build on this same per-port
- *        array from the start instead of each reinventing its own
- *        port-indexed storage.
+ * @role: Role of this port (VIRTIO_USB_ROLE_HOST or _DEVICE).
+ * @vhc: Host controller - non-NULL when role is HOST.
  */
 struct virtio_usb_port {
 	unsigned int role;
+	struct virtio_usb_hc_vp *vhc;
 };
 
 /**
@@ -35,6 +40,15 @@ struct virtio_usb_port {
  * @vports: Array of per-port structures, one entry per virtual port.
  * @nports: number of supported ports
  * @nvqs: number of virtqueues for the device
+ * @host_role: flag indicating support for host role
+ * @host_vq_base: index into vqueues[] where the HOST_COMMAND/EVENT/DATA
+ *                triplet starts, or -1 if this instance has no host-role
+ *                VP (in which case those queues do not exist on the wire
+ *                and must not be negotiated).
+ * @vq_host_data_rx_work: Kernel work draining the host data queue, shared
+ *                        across every host-role VP.
+ * @vq_host_evt_work: Kernel work draining the host event queue, shared
+ *                     across every host-role VP.
  */
 struct virtio_usb {
 	struct virtio_device *vdev;
@@ -42,6 +56,10 @@ struct virtio_usb {
 	struct virtio_usb_port *vports;
 	unsigned int nports;
 	u32 nvqs;
+	bool host_role;
+	int host_vq_base;
+	struct work_struct vq_host_data_rx_work;
+	struct work_struct vq_host_evt_work;
 };
 
 /**
@@ -81,4 +99,7 @@ struct virtio_usb_vq_desc {
 	void (*stop)(struct virtio_usb *vusb, struct virtio_usb_queue *vq);
 };
 
+/* Command completion timeout in milliseconds (module parameter). */
+extern u32 virtio_usb_cmd_timeout_ms;
+
 #endif /* VIRTIO_USB_CONTROLLER_H */
diff --git a/drivers/usb/virtio_usb/Makefile b/drivers/usb/virtio_usb/Makefile
index b7ee9e8..1111111 100644
--- a/drivers/usb/virtio_usb/Makefile
+++ b/drivers/usb/virtio_usb/Makefile
@@ -1,5 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0-or-later
 
-virtio-usb-y := controller.o
+virtio-usb-y := controller.o \
+	vq_common.o \
+	host.o
 
 obj-$(CONFIG_USB_VIRTIO) += virtio-usb.o
diff --git a/drivers/usb/virtio_usb/host.c b/drivers/usb/virtio_usb/host.c
new file mode 100644
index 0000000..9926e66
--- /dev/null
+++ b/drivers/usb/virtio_usb/host.c
@@ -0,0 +1,1335 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * virtio_usb: VirtIO USB device
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include "controller.h"
+
+#include "host.h"
+#include "vq_common.h"
+
+/**
+ * virtio_usb_hc_reset() - Reset the host controller.
+ * @hcd: USB host controller device.
+ *
+ * Context: Any context
+ * Return: 0 on success, -errno on failure.
+ */
+static int virtio_usb_hc_reset(struct usb_hcd *hcd)
+{
+	struct virtio_usb_hc_vp *vhcd_vp = vhcd_get(hcd);
+
+	if (hcd == vhcd_vp->hs) {
+		hcd->speed = HCD_USB2;
+		hcd->self.root_hub->speed = USB_SPEED_HIGH;
+		hcd->has_tt = 1;
+	} else {
+		hcd->speed = HCD_USB3;
+		hcd->self.root_hub->speed = USB_SPEED_SUPER;
+	}
+
+	hcd->self.sg_tablesize = ~0;
+	return 0;
+}
+
+/**
+ * virtio_usb_hc_start() - Start the host controller.
+ * @hcd: USB host controller device
+ *
+ * Context: Any context
+ * Return: 0 on success, -errno on failure.
+ */
+static int virtio_usb_hc_start(struct usb_hcd *hcd)
+{
+	struct virtio_usb_hc_vp *vhcd_vp = vhcd_get(hcd);
+	unsigned long iflags;
+
+	hcd->uses_new_polling = 1;
+	clear_bit(HCD_FLAG_POLL_RH, &hcd->flags);
+
+	spin_lock_irqsave(&vhcd_vp->lock, iflags);
+	hcd->state = HC_STATE_RUNNING;
+	spin_unlock_irqrestore(&vhcd_vp->lock, iflags);
+	hcd->self.no_sg_constraint = 1;
+
+	return 0;
+}
+
+/**
+ * virtio_usb_hc_stop() - Stop the host controller.
+ * @hcd: USB host controller device
+ *
+ * Context: Any context
+ */
+static void virtio_usb_hc_stop(struct usb_hcd *hcd)
+{
+	struct virtio_usb_hc_vp *vhcd_vp = vhcd_get(hcd);
+	unsigned long iflags;
+
+	spin_lock_irqsave(&vhcd_vp->lock, iflags);
+	hcd->state = HC_STATE_HALT;
+	spin_unlock_irqrestore(&vhcd_vp->lock, iflags);
+}
+
+/**
+ * virtio_usb_hub_status_data() - Get the hub status data for the root hub.
+ * @vhcd_vp: per-VP VirtIO USB host controller
+ * @buffer: status buffer in which the hub status need to be updated
+ * @ss_mode: indicates if the root hub is a super speed hub.
+ *
+ * Context: Any context
+ * Return: 0 if the status hasn't changed, or the number of bytes in buffer.
+ */
+static int virtio_usb_hub_status_data(struct virtio_usb_hc_vp *vhcd_vp,
+				      char *buffer, bool ss_mode)
+{
+	unsigned int i;
+	unsigned int nbytes = DIV_ROUND_UP(VIRTIO_USB_VP_MAX_PORTS + 1, 8);
+	int changed = 0;
+	unsigned long iflags;
+	struct usb_hcd *hcd = ss_mode ? vhcd_vp->ss : vhcd_vp->hs;
+
+	memset(buffer, 0, nbytes);
+
+	for (i = 0; i < VIRTIO_USB_VP_MAX_PORTS; i++) {
+		struct virtio_usb_hc_port *port = &vhcd_vp->ports[i];
+		u16 value;
+
+		spin_lock_irqsave(&port->lock, iflags);
+		value = ss_mode ? port->ss.change.value : port->hs.change.value;
+		spin_unlock_irqrestore(&port->lock, iflags);
+
+		if (value) {
+			buffer[(i + 1) / 8] |= 1 << ((i + 1) % 8);
+			changed = 1;
+		}
+	}
+	if (changed) {
+		spin_lock_irqsave(&vhcd_vp->lock, iflags);
+		if (hcd->state == HC_STATE_SUSPENDED)
+			usb_hcd_resume_root_hub(hcd);
+		spin_unlock_irqrestore(&vhcd_vp->lock, iflags);
+	}
+
+	return changed ? nbytes : 0;
+}
+
+static int virtio_usb_hs_hub_status_data(struct usb_hcd *hcd, char *buffer)
+{
+	return virtio_usb_hub_status_data(vhcd_get(hcd), buffer, false);
+}
+
+static int virtio_usb_ss_hub_status_data(struct usb_hcd *hcd, char *buffer)
+{
+	return virtio_usb_hub_status_data(vhcd_get(hcd), buffer, true);
+}
+
+/**
+ * virtio_usb_windex_to_port - Map wIndex to a host controller port.
+ * @vhcd_vp: per-VP host controller
+ * @wIndex:  low byte contains the 1-based port number
+ *
+ * Return: pointer to port on success, NULL if out of range.
+ */
+static struct virtio_usb_hc_port *
+virtio_usb_windex_to_port(struct virtio_usb_hc_vp *vhcd_vp, u16 wIndex)
+{
+	u16 pIndex = wIndex & 0xFF;
+
+	if (pIndex == 0 || pIndex > VIRTIO_USB_VP_MAX_PORTS)
+		return NULL;
+	return &vhcd_vp->ports[pIndex - 1];
+}
+
+/**
+ * virtio_usb_hub_control() - Hub control request callback (shared HS/SS).
+ */
+static int virtio_usb_hub_control(struct usb_hcd *hcd, u16 type, u16 wValue,
+				  u16 wIndex, char *buffer, u16 wLength,
+				  bool ss_mode)
+{
+	struct virtio_usb_hc_port *port;
+	struct virtio_usb_hc_vp *vhcd_vp = vhcd_get(hcd);
+
+	switch (type) {
+	case GetHubDescriptor: {
+		struct usb_hub_descriptor *dsc =
+			(struct usb_hub_descriptor *)buffer;
+
+		memset(dsc, 0, sizeof(struct usb_hub_descriptor));
+
+		if (ss_mode) {
+			dsc->bDescLength = USB_DT_SS_HUB_SIZE;
+			dsc->bDescriptorType = USB_DT_SS_HUB;
+		} else {
+			dsc->bDescLength = 9;
+			dsc->bDescriptorType = USB_DT_HUB;
+		}
+
+		/* Fixed port count per VP - always within USB_MAXCHILDREN (31)
+		 * and USB_SS_MAXPORTS (15).
+		 */
+		dsc->bNbrPorts = VIRTIO_USB_VP_MAX_PORTS;
+
+		return 0;
+	}
+	case GetHubStatus: {
+		*((u32 *)buffer) = 0;
+		return 0;
+	}
+	case GetPortStatus: {
+		u16 *status = (u16 *)buffer;
+		unsigned long iflags;
+
+		memset(status, 0, wLength);
+
+		port = virtio_usb_windex_to_port(vhcd_vp, wIndex);
+		if (port) {
+			spin_lock_irqsave(&port->lock, iflags);
+			if (ss_mode) {
+				status[0] = port->ss.status.value;
+				status[1] = port->ss.change.value;
+			} else {
+				status[0] = port->hs.status.value;
+				status[1] = port->hs.change.value;
+			}
+			spin_unlock_irqrestore(&port->lock, iflags);
+		}
+
+		return 0;
+	}
+	}
+
+	return -EPIPE;
+}
+
+/**
+ * virtio_usb_hs_hub_control() - VirtIO USB High speed hub control request.
+ */
+static int virtio_usb_hs_hub_control(struct usb_hcd *hcd, u16 type, u16 wValue,
+				     u16 wIndex, char *buffer, u16 wLength)
+{
+	struct virtio_usb_hc_vp *vhcd_vp = vhcd_get(hcd);
+	struct virtio_usb_hc_port *port;
+	unsigned long iflags;
+	int rc = 0;
+
+	switch (type) {
+	case ClearPortFeature:
+		port = virtio_usb_windex_to_port(vhcd_vp, wIndex);
+		if (port == NULL)
+			return -EPIPE;
+
+		spin_lock_irqsave(&port->lock, iflags);
+		switch (wValue) {
+		case USB_PORT_FEAT_ENABLE:
+			if (port->hs.status.bits.enable) {
+				port->hs.status.bits.enable = 0;
+				port->hs.change.bits.enable = 1;
+			}
+			break;
+		case USB_PORT_FEAT_C_CONNECTION:
+			port->hs.change.bits.connect = 0;
+			break;
+		case USB_PORT_FEAT_C_ENABLE:
+			port->hs.change.bits.enable = 0;
+			break;
+		case USB_PORT_FEAT_C_RESET:
+			port->hs.change.bits.reset = 0;
+			break;
+		default:
+			rc = -EPIPE;
+			break;
+		}
+		spin_unlock_irqrestore(&port->lock, iflags);
+		break;
+	case SetPortFeature:
+		port = virtio_usb_windex_to_port(vhcd_vp, wIndex);
+		if (port == NULL)
+			return -EPIPE;
+
+		spin_lock_irqsave(&port->lock, iflags);
+		switch (wValue) {
+		case USB_PORT_FEAT_RESET:
+			if (!port->hs.status.bits.enable) {
+				port->hs.status.bits.enable = 1;
+				port->hs.change.bits.enable = 1;
+			}
+			port->hs.change.bits.reset = 1;
+			break;
+		case USB_PORT_FEAT_POWER:
+			port->hs.status.bits.power = 1;
+			break;
+		default:
+			rc = -EPIPE;
+			break;
+		}
+		spin_unlock_irqrestore(&port->lock, iflags);
+		break;
+	default:
+		rc = virtio_usb_hub_control(hcd, type, wValue, wIndex, buffer,
+					    wLength, false);
+		break;
+	}
+
+	return rc;
+}
+
+/* Super speed root hub device descriptor */
+static struct {
+	struct usb_bos_descriptor bos;
+	struct usb_ss_cap_descriptor ss_cap;
+} __packed ss_bos_desc = {
+	.bos = {
+		.bLength = USB_DT_BOS_SIZE,
+		.bDescriptorType = USB_DT_BOS,
+		.wTotalLength = cpu_to_le16(sizeof(ss_bos_desc)),
+		.bNumDeviceCaps = 1,
+		},
+	.ss_cap = {
+		.bLength = USB_DT_USB_SS_CAP_SIZE,
+		.bDescriptorType = USB_DT_DEVICE_CAPABILITY,
+		.bDevCapabilityType = USB_SS_CAP_TYPE,
+		.bmAttributes = 0x00,
+		.wSpeedSupported = cpu_to_le16(USB_5GBPS_OPERATION),
+		.bFunctionalitySupport = ilog2(USB_5GBPS_OPERATION),
+		.bU1devExitLat = 0x00,
+		.bU2DevExitLat = 0x00,
+		},
+};
+
+/**
+ * virtio_usb_ss_hub_control() - VirtIO USB Super speed hub control request.
+ */
+static int virtio_usb_ss_hub_control(struct usb_hcd *hcd, u16 type, u16 wValue,
+				     u16 wIndex, char *buffer, u16 wLength)
+{
+	struct virtio_usb_hc_vp *vhcd_vp = vhcd_get(hcd);
+	struct virtio_usb_hc_port *port;
+	unsigned long iflags;
+	int rc = 0;
+
+	switch (type) {
+	case ClearPortFeature:
+		port = virtio_usb_windex_to_port(vhcd_vp, wIndex);
+		if (port == NULL)
+			return -EPIPE;
+
+		spin_lock_irqsave(&port->lock, iflags);
+		switch (wValue) {
+		case USB_PORT_FEAT_C_CONNECTION:
+			port->ss.change.bits.connect = 0;
+			break;
+		case USB_PORT_FEAT_C_RESET:
+			port->ss.change.bits.reset = 0;
+			break;
+		case USB_PORT_FEAT_C_PORT_LINK_STATE:
+			port->ss.change.bits.link_state = 0;
+			break;
+		case USB_PORT_FEAT_C_BH_PORT_RESET:
+			port->ss.change.bits.bh_reset = 0;
+			break;
+		default:
+			rc = -EPIPE;
+			break;
+		}
+		spin_unlock_irqrestore(&port->lock, iflags);
+		break;
+	case DeviceRequest | USB_REQ_GET_DESCRIPTOR: {
+		u8 dtype = (wValue >> 8) & 0xff;
+
+		if (dtype == USB_DT_BOS) {
+			u16 bos_length = sizeof(ss_bos_desc);
+
+			if (bos_length > wLength)
+				bos_length = wLength;
+
+			memcpy(buffer, &ss_bos_desc, bos_length);
+
+			rc = bos_length;
+		} else {
+			rc = -EPIPE;
+		}
+
+		break;
+	}
+	case SetPortFeature: {
+		port = virtio_usb_windex_to_port(vhcd_vp, wIndex);
+		if (port == NULL)
+			return -EPIPE;
+
+		spin_lock_irqsave(&port->lock, iflags);
+		switch (wValue) {
+		case USB_PORT_FEAT_RESET:
+		case USB_PORT_FEAT_BH_PORT_RESET:
+			port->ss.status.bits.enable = 1;
+
+			if (port->ss.status.bits.link_state != 0x00) {
+				port->ss.status.bits.link_state = 0x00;
+				port->ss.change.bits.link_state = 1;
+			}
+
+			port->ss.status.bits.reset = 0;
+			port->ss.change.bits.reset = 1;
+
+			if (wValue == USB_PORT_FEAT_BH_PORT_RESET)
+				port->ss.change.bits.bh_reset = 1;
+
+			break;
+		case USB_PORT_FEAT_POWER:
+			port->ss.status.bits.power = 1;
+			break;
+		default:
+			rc = -EPIPE;
+			break;
+		}
+		spin_unlock_irqrestore(&port->lock, iflags);
+		break;
+	}
+	default:
+		rc = virtio_usb_hub_control(hcd, type, wValue, wIndex, buffer,
+					    wLength, true);
+		break;
+	}
+
+	return rc;
+}
+
+/* virtio usb host controller priv structure */
+struct virtio_usb_hc_priv {
+	struct virtio_usb_hc_port *port;
+	struct urb *urb;
+	struct scatterlist *sgs;
+};
+
+/**
+ * virtio_usb_hc_complete_urb() - Completes a URB
+ * @vurb: virtio_usb_data message.
+ *
+ * Context: Process context.
+ */
+static void virtio_usb_hc_complete_urb(struct virtio_usb_data *vurb)
+{
+	struct virtio_usb_hc_priv *priv =
+		(struct virtio_usb_hc_priv *)vurb->priv;
+	struct virtio_usb_response *response;
+	struct virtio_usb_iso_status *iso_urb_status;
+	struct virtio_usb_hc_port *port = priv->port;
+	struct urb *urb = priv->urb;
+	struct usb_hcd *hcd;
+	unsigned long flags;
+	int i;
+	unsigned int status;
+
+	if (unlikely(!urb || !urb->dev || !urb->dev->bus)) {
+		kfree(priv->sgs);
+		virtio_usb_data_unref(vurb);
+		return;
+	}
+
+	hcd = bus_to_hcd(urb->dev->bus);
+
+	response = virtio_usb_data_response(vurb);
+
+	status = le32_to_cpu(response->status);
+
+	spin_lock_irqsave(&port->lock, flags);
+
+	usb_hcd_unlink_urb_from_ep(hcd, urb);
+	urb->hcpriv = NULL;
+	list_del(&vurb->list);
+
+	spin_unlock_irqrestore(&port->lock, flags);
+
+	urb->status = virtio_error_to_usb(status);
+	urb->actual_length = le32_to_cpu(response->actual_length);
+	if (urb->status == -EINVAL)
+		dev_err(&urb->dev->dev,
+			"URB ep%02x status -EINVAL from virtio status %u actual=%d\n",
+			urb->ep->desc.bEndpointAddress, status,
+			urb->actual_length);
+
+	if (usb_pipeisoc(urb->pipe) || usb_pipeint(urb->pipe))
+		urb->interval = le32_to_cpu(response->interval);
+
+	if (usb_pipeisoc(urb->pipe)) {
+		iso_urb_status = (void *)response + sizeof(*response);
+
+		for (i = 0; i < urb->number_of_packets; i++) {
+			struct virtio_usb_iso_status *iso_status =
+				&iso_urb_status[i];
+			status = le32_to_cpu(iso_status->status);
+
+			urb->iso_frame_desc[i].actual_length =
+				le32_to_cpu(iso_status->actual_length);
+			urb->iso_frame_desc[i].status =
+				virtio_error_to_usb(status);
+
+			if (iso_status->status)
+				urb->error_count++;
+		}
+
+		urb->start_frame = le32_to_cpu(response->start_frame);
+	}
+	local_bh_disable();
+	usb_hcd_giveback_urb(hcd, urb, urb->status);
+	local_bh_enable();
+	kfree(priv->sgs);
+	virtio_usb_data_unref(vurb);
+}
+
+/**
+ * virtio_usb_hc_set_urb_sgs() - Set urb sgs when total sg elements > 1.
+ */
+static struct scatterlist *
+virtio_usb_hc_set_urb_sgs(struct virtio_usb_hc_priv *priv, gfp_t gfp)
+{
+	struct scatterlist *sg = NULL, *sgs = NULL;
+	struct urb *urb = priv->urb;
+	unsigned int nsgs, i = 0;
+	unsigned int buffer_length = urb->transfer_buffer_length;
+	unsigned int sg_length = 0;
+
+	nsgs = urb->num_sgs;
+
+	for_each_sg(urb->sg, sg, nsgs, i) {
+		sg_length += sg->length;
+	}
+	if (sg_length > buffer_length) {
+		sgs = kcalloc(nsgs, sizeof(*sgs), gfp);
+		if (!sgs)
+			return NULL;
+
+		sg_init_table(sgs, nsgs);
+
+		for_each_sg(urb->sg, sg, nsgs, i) {
+			sg_length = sg->length;
+
+			if (sg_length > buffer_length)
+				sg_length = buffer_length;
+
+			sg_set_page(&sgs[i], sg_page(sg), sg_length,
+				    sg->offset);
+
+			buffer_length -= sg_length;
+			if (!buffer_length)
+				break;
+		}
+		priv->sgs = sgs;
+		return sgs;
+	}
+	return urb->sg;
+}
+
+/**
+ * virtio_usb_hc_data_alloc() - Allocates a virtio usb data for the host
+ * @port: VirtIO USB HC port
+ * @urb: The urb request
+ * @gfp: Kernel flags for memory allocation.
+ */
+static struct virtio_usb_data *
+virtio_usb_hc_data_alloc(struct virtio_usb_hc_port *port, struct urb *urb,
+			 gfp_t gfp)
+{
+	struct virtio_usb_data *vurb;
+	struct virtio_usb_hc_priv *priv;
+	struct virtio_usb_request *request;
+	struct virtio_usb_response *response;
+	struct virtio_usb_iso_packet *iso_packet;
+	struct virtio_usb_iso_status *iso_status;
+	size_t request_size = sizeof(*request);
+	size_t response_size = sizeof(*response);
+	u16 ep, transfer_flags = 0;
+
+	if (usb_pipeisoc(urb->pipe)) {
+		request_size += sizeof(*iso_packet) * urb->number_of_packets;
+		response_size += sizeof(*iso_status) * urb->number_of_packets;
+	}
+
+	vurb = virtio_usb_data_alloc(request_size, response_size,
+				     sizeof(struct virtio_usb_hc_priv), gfp);
+
+	if (!vurb)
+		return NULL;
+
+	priv = vurb->priv;
+	priv->port = port;
+	priv->urb = urb;
+	vurb->msg.queue = port->vhcd_vp->hcqs[VIRTIO_USB_VQ_DATA_IDX];
+
+	INIT_LIST_HEAD(&vurb->list);
+
+	request = virtio_usb_data_request(vurb);
+	response = virtio_usb_data_response(vurb);
+
+	response->status = cpu_to_le32(VIRTIO_USB_S_ERR_CANCELLED);
+
+	if (usb_pipeisoc(urb->pipe)) {
+		iso_packet = (void *)request + sizeof(*request);
+		iso_status = (void *)response + sizeof(*response);
+	}
+	request->tag = cpu_to_le64((uintptr_t)vurb);
+	ep = usb_pipeendpoint(urb->pipe);
+	if (usb_pipein(urb->pipe))
+		ep |= VIRTIO_USB_EP_DIR_IN;
+
+	request->endpoint = cpu_to_le16(ep);
+	request->vp_idx = cpu_to_le16((u16)port->vhcd_vp->vp_idx);
+	request->port = cpu_to_le16((u16)port->port_id);
+
+	if (urb->transfer_flags & URB_SHORT_NOT_OK)
+		transfer_flags |= VIRTIO_USB_FLAG_SHORT_NOT_OK;
+	if (urb->transfer_flags & URB_ISO_ASAP)
+		transfer_flags |= VIRTIO_USB_FLAG_ISO_ASAP;
+	if (urb->transfer_flags & URB_ZERO_PACKET)
+		transfer_flags |= VIRTIO_USB_FLAG_ZERO_PACKET;
+	request->transfer_flags = cpu_to_le16(transfer_flags);
+
+	switch (usb_pipetype(urb->pipe)) {
+	case PIPE_ISOCHRONOUS: {
+		int i;
+
+		request->transfer_type = cpu_to_le16(VIRTIO_USB_EP_ISOCHRONOUS);
+		request->iso.start_frame = cpu_to_le32(urb->start_frame);
+		request->iso.interval = cpu_to_le32(urb->interval);
+		request->iso.number_of_packets =
+			cpu_to_le32(urb->number_of_packets);
+
+		for (i = 0; i < urb->number_of_packets; i++) {
+			struct virtio_usb_iso_packet *packet = &iso_packet[i];
+
+			packet->offset =
+				cpu_to_le32(urb->iso_frame_desc[i].offset);
+			packet->length =
+				cpu_to_le32(urb->iso_frame_desc[i].length);
+		}
+
+		break;
+	}
+	case PIPE_INTERRUPT:
+		request->transfer_type = cpu_to_le16(VIRTIO_USB_EP_INTERRUPT);
+		request->interrupt.interval = cpu_to_le32(urb->interval);
+		break;
+	case PIPE_CONTROL:
+		request->transfer_type = cpu_to_le16(VIRTIO_USB_EP_CONTROL);
+		memcpy(request->control.setup, urb->setup_packet, 8);
+		break;
+	case PIPE_BULK:
+		request->transfer_type = cpu_to_le16(VIRTIO_USB_EP_BULK);
+		break;
+	}
+	return vurb;
+}
+
+/**
+ * virtio_usb_hc_cmd_alloc() - Allocate and initialize a host command message.
+ */
+static struct virtio_usb_cmd *virtio_usb_hc_cmd_alloc(struct virtio_usb *vusb,
+						      unsigned int vp_idx,
+						      unsigned int command,
+						      gfp_t gfp)
+{
+	size_t request_size = sizeof(struct virtio_usb_host_cmd_hdr);
+	size_t response_size = sizeof(struct virtio_usb_cmd_status);
+	struct virtio_usb_cmd *cmd;
+
+	switch (command) {
+	case VIRTIO_USB_CMD_HOST_CANCEL:
+		request_size = sizeof(struct virtio_usb_host_cmd_cancel);
+		break;
+	case VIRTIO_USB_CMD_HOST_STREAMS_ALLOC:
+	case VIRTIO_USB_CMD_HOST_STREAMS_FREE:
+		request_size = sizeof(struct virtio_usb_host_cmd_streams);
+		break;
+	default:
+		break;
+	}
+
+	cmd = virtio_usb_cmd_alloc(request_size, response_size, gfp);
+	if (cmd) {
+		struct virtio_usb_host_cmd_hdr *hdr =
+			virtio_usb_cmd_request(cmd);
+		struct virtio_usb_cmd_status *status =
+			virtio_usb_cmd_response(cmd);
+
+		hdr->code = cpu_to_le32(command);
+		cmd->msg.queue = vusb->vports[vp_idx]
+					 .vhc->hcqs[VIRTIO_USB_VQ_COMMAND_IDX];
+		status->code = cpu_to_le32(VIRTIO_USB_S_ERR_CANCELLED);
+	}
+	return cmd;
+}
+
+/**
+ * virtio_usb_port_from_urb() - Look up the port for a URB.
+ *
+ * urb->dev->portnum is the 1-based port number on the root hub, which
+ * equals port_id + 1. The HCD identifies which VP.
+ *
+ * Returns NULL if the slot is out of range.
+ */
+static struct virtio_usb_hc_port *virtio_usb_port_from_urb(struct usb_hcd *hcd,
+							   struct urb *urb)
+{
+	struct virtio_usb_hc_vp *vhcd_vp = vhcd_get(hcd);
+	int slot = urb->dev->portnum - 1;
+
+	if (slot < 0 || slot >= VIRTIO_USB_VP_MAX_PORTS)
+		return NULL;
+	return &vhcd_vp->ports[slot];
+}
+
+/**
+ * virtio_usb_hc_enqueue() - Enqueue a URB.
+ * @hcd: USB host controller device
+ * @urb: URB
+ * @mem_flags: Kernel flags for memory allocation.
+ *
+ * Context: Any context
+ * Return: 0 on success -errno on failure
+ */
+static int virtio_usb_hc_enqueue(struct usb_hcd *hcd, struct urb *urb,
+				 gfp_t mem_flags)
+{
+	struct virtio_usb_hc_vp *vhcd_vp = vhcd_get(hcd);
+	struct virtio_usb *vusb = vhcd_vp->vusb;
+	struct virtio_usb_hc_port *port;
+	struct virtio_usb_data *vurb;
+	struct virtio_usb_hc_priv *priv;
+	struct scatterlist sg;
+	struct scatterlist *psg_data = &sg, *out_sgs = NULL, *in_sgs = NULL;
+	unsigned long flags;
+	int rc = 0;
+
+	port = virtio_usb_port_from_urb(hcd, urb);
+	if (!port)
+		return -ENODEV;
+
+	vurb = virtio_usb_hc_data_alloc(port, urb, mem_flags);
+	if (!vurb)
+		return -ENOMEM;
+	priv = vurb->priv;
+
+	if (urb->transfer_buffer) {
+		sg_init_one(psg_data, urb->transfer_buffer,
+			    urb->transfer_buffer_length);
+	} else if (urb->num_sgs > 1) {
+		psg_data = virtio_usb_hc_set_urb_sgs(priv, mem_flags);
+		if (!psg_data) {
+			rc = -ENOMEM;
+			goto on_exit;
+		}
+	} else if (urb->transfer_buffer_length && urb->sg) {
+		sg_init_one(psg_data, sg_virt(urb->sg),
+			    urb->transfer_buffer_length);
+	} else {
+		psg_data = NULL;
+	}
+
+	spin_lock_irqsave(&port->lock, flags);
+	rc = usb_hcd_link_urb_to_ep(port_vhcd_get(port), urb);
+	if (rc) {
+		spin_unlock_irqrestore(&port->lock, flags);
+		goto on_exit;
+	}
+	urb->hcpriv = vurb;
+	list_add_tail(&vurb->list, &port->pending_urb_list);
+	spin_unlock_irqrestore(&port->lock, flags);
+
+	if (usb_pipeout(urb->pipe))
+		out_sgs = psg_data;
+	else
+		in_sgs = psg_data;
+
+	rc = virtio_usb_data_send(vusb, vurb, out_sgs, in_sgs);
+	if (rc)
+		goto on_error_vq;
+
+	return rc;
+
+on_error_vq:
+	spin_lock_irqsave(&port->lock, flags);
+	usb_hcd_unlink_urb_from_ep(port_vhcd_get(port), urb);
+	urb->hcpriv = NULL;
+	list_del(&vurb->list);
+	spin_unlock_irqrestore(&port->lock, flags);
+
+on_exit:
+	kfree(priv->sgs);
+	virtio_usb_data_unref(vurb);
+	return rc;
+}
+
+/**
+ * virtio_usb_hc_dequeue() - Dequeue a URB.
+ * @hcd: USB host controller device
+ * @urb: URB
+ * @status: status of the URB.
+ *
+ * Context: Any context
+ * Return: 0 on success -errno on failure
+ */
+static int virtio_usb_hc_dequeue(struct usb_hcd *hcd, struct urb *urb,
+				 int status)
+{
+	struct virtio_usb_hc_vp *vhcd_vp = vhcd_get(hcd);
+	struct virtio_usb *vusb = vhcd_vp->vusb;
+	struct virtio_usb_hc_port *port;
+	struct virtio_usb_host_cmd_cancel *cancel;
+	struct virtio_usb_data *vurb;
+	struct virtio_usb_cmd *cmd;
+	int rc;
+	unsigned long flags;
+
+	port = virtio_usb_port_from_urb(hcd, urb);
+	if (!port)
+		return -ENODEV;
+
+	spin_lock_irqsave(&port->lock, flags);
+
+	vurb = urb->hcpriv;
+	if (!vurb) {
+		spin_unlock_irqrestore(&port->lock, flags);
+		return -EIDRM;
+	}
+
+	rc = usb_hcd_check_unlink_urb(port_vhcd_get(port), urb, status);
+	spin_unlock_irqrestore(&port->lock, flags);
+
+	if (rc)
+		return rc;
+
+	cmd = virtio_usb_hc_cmd_alloc(vusb, vhcd_vp->vp_idx,
+				      VIRTIO_USB_CMD_HOST_CANCEL, GFP_ATOMIC);
+	if (!cmd)
+		return -ENOMEM;
+
+	cancel = virtio_usb_cmd_request(cmd);
+	cancel->hdr.port = cpu_to_le32(port->port_id);
+	cancel->tag = cpu_to_le64((uintptr_t)vurb);
+
+	return virtio_usb_cmd_send_async(vusb, NULL, NULL, cmd);
+}
+
+/**
+ * virtio_usb_hc_get_frame() - Get the current hw frame number
+ */
+static int virtio_usb_hc_get_frame(struct usb_hcd *hcd)
+{
+	return 0;
+}
+
+/* Virtio USB high speed host controller driver */
+static struct hc_driver virtio_usb_hs_hc_driver = {
+	.description = "virtio-usb-hc",
+	.product_desc = "VirtIO USB2 Host Controller",
+	.hcd_priv_size = sizeof(void *),
+	.flags = HCD_USB2 | HCD_SHARED,
+
+	.reset = virtio_usb_hc_reset,
+	.start = virtio_usb_hc_start,
+	.stop = virtio_usb_hc_stop,
+
+	.hub_status_data = virtio_usb_hs_hub_status_data,
+	.hub_control = virtio_usb_hs_hub_control,
+
+	.urb_enqueue = virtio_usb_hc_enqueue,
+	.urb_dequeue = virtio_usb_hc_dequeue,
+
+	.get_frame_number = virtio_usb_hc_get_frame,
+};
+
+/* Virtio USB super speed host controller driver */
+static struct hc_driver virtio_usb_ss_hc_driver = {
+	.description = "virtio-usb-hc",
+	.product_desc = "VirtIO USB3 Host Controller",
+	.hcd_priv_size = sizeof(void *),
+	.flags = HCD_USB3 | HCD_SHARED,
+
+	.reset = virtio_usb_hc_reset,
+	.start = virtio_usb_hc_start,
+	.stop = virtio_usb_hc_stop,
+
+	.hub_status_data = virtio_usb_ss_hub_status_data,
+	.hub_control = virtio_usb_ss_hub_control,
+
+	.urb_enqueue = virtio_usb_hc_enqueue,
+	.urb_dequeue = virtio_usb_hc_dequeue,
+
+	.get_frame_number = virtio_usb_hc_get_frame,
+};
+
+/**
+ * virtio_usb_add_hcd() - Add HS and SS HCDs for one VP.
+ * @vusb:    VirtIO usb device.
+ * @vhcd_vp: per-VP host controller to populate.
+ */
+static int virtio_usb_add_hcd(struct virtio_usb *vusb,
+			      struct virtio_usb_hc_vp *vhcd_vp)
+{
+	struct virtio_device *vdev = vusb->vdev;
+	struct device *dev = &vdev->dev;
+	const char *name;
+	int rc;
+
+	/* usb_create_hcd()/usb_create_shared_hcd() store this pointer as-is
+	 * in hcd->self.bus_name (no copy is made) - it must stay valid for
+	 * the lifetime of the HCD, so it cannot be a stack buffer. Allocate
+	 * it from the parent virtio device, which outlives the HCDs.
+	 */
+	name = devm_kasprintf(&vusb->vdev->dev, GFP_KERNEL, "%s-vp%u",
+			      dev_name(dev), vhcd_vp->vp_idx);
+	if (!name)
+		return -ENOMEM;
+
+	vhcd_vp->hs = usb_create_hcd(&virtio_usb_hs_hc_driver, dev, name);
+	if (!vhcd_vp->hs)
+		return -ENOMEM;
+
+	vhcd_set(vhcd_vp->hs, vhcd_vp);
+
+	vhcd_vp->hs->skip_phy_initialization = 1;
+	rc = usb_add_hcd(vhcd_vp->hs, 0, 0);
+	if (rc)
+		goto on_put_hs;
+
+	vhcd_vp->ss = usb_create_shared_hcd(&virtio_usb_ss_hc_driver, dev, name,
+					    vhcd_vp->hs);
+	if (!vhcd_vp->ss) {
+		rc = -ENOMEM;
+		goto on_remove_hs;
+	}
+
+	vhcd_set(vhcd_vp->ss, vhcd_vp);
+
+	rc = usb_add_hcd(vhcd_vp->ss, 0, 0);
+	if (rc)
+		goto on_put_ss;
+
+	return 0;
+
+on_put_ss:
+	usb_put_hcd(vhcd_vp->ss);
+on_remove_hs:
+	usb_remove_hcd(vhcd_vp->hs);
+on_put_hs:
+	usb_put_hcd(vhcd_vp->hs);
+
+	return rc;
+}
+
+/**
+ * virtio_usb_hc_event_populate() - Add events to the host event queue.
+ * @vusb: VirtIO USB device
+ *
+ * Context: Any context.
+ * Return: 0 on success -errno on failure
+ */
+int virtio_usb_hc_event_populate(struct virtio_usb *vusb)
+{
+	/* The host event queue is shared across every host-role VP and
+	 * lives at the struct virtio_usb level - not owned by any single
+	 * VP - since a host-role VP may not exist yet (e.g. a dual-role
+	 * OTG instance where every port currently reports device role).
+	 */
+	struct virtio_usb_queue *evt_queue =
+		&vusb->vqueues[vusb->host_vq_base + VIRTIO_USB_VQ_EVENT_IDX];
+	struct virtio_usb_event *events;
+
+	events = virtio_usb_events_alloc(
+		vusb, evt_queue, sizeof(struct virtio_usb_host_port_event));
+
+	return virtio_usb_events_populate(events);
+}
+
+/**
+ * virtio_usb_hc_rx_work() - Worker to drain completed data messages.
+ * @work: kernel work item embedded in struct virtio_usb.
+ *
+ * The data virtqueue is shared across all host-role VPs, so the work
+ * item that drains it lives on struct virtio_usb itself instead of on
+ * any single VP (mirrors vq_dev_data_rx_work on the device-role side).
+ *
+ * Context: Process context.
+ */
+void virtio_usb_hc_rx_work(struct work_struct *work)
+{
+	struct virtio_usb *vusb =
+		container_of(work, struct virtio_usb, vq_host_data_rx_work);
+	struct virtio_usb_queue *dataq =
+		&vusb->vqueues[vusb->host_vq_base + VIRTIO_USB_VQ_DATA_IDX];
+	u32 length;
+	struct virtio_usb_data *vurb;
+
+	spin_lock_irq(&dataq->lock);
+	do {
+		virtqueue_disable_cb(dataq->vqueue);
+		while ((vurb = virtqueue_get_buf(dataq->vqueue, &length))) {
+			spin_unlock_irq(&dataq->lock);
+			virtio_usb_hc_complete_urb(vurb);
+			spin_lock_irq(&dataq->lock);
+		}
+		if (unlikely(virtqueue_is_broken(dataq->vqueue)))
+			break;
+	} while (!virtqueue_enable_cb(dataq->vqueue));
+	spin_unlock_irq(&dataq->lock);
+}
+
+/**
+ * virtio_usb_hc_vp_init() - Initialize the host controller for one VP.
+ * @vusb:    VirtIO USB device
+ * @vp_idx:  VP index (0..nports-1)
+ *
+ * Context: Any context.
+ * Return: 0 on success -errno on failure
+ */
+int virtio_usb_hc_vp_init(struct virtio_usb *vusb, unsigned int vp_idx)
+{
+	struct virtio_usb_hc_vp *vhcd_vp;
+	unsigned int i;
+	int rc;
+
+	vhcd_vp = devm_kzalloc(&vusb->vdev->dev, sizeof(*vhcd_vp), GFP_KERNEL);
+	if (!vhcd_vp)
+		return -ENOMEM;
+
+	vhcd_vp->vusb = vusb;
+	vhcd_vp->vp_idx = vp_idx;
+	spin_lock_init(&vhcd_vp->lock);
+
+	/* Pre-allocate all port structs. Reused across connect/disconnect. */
+	for (i = 0; i < VIRTIO_USB_VP_MAX_PORTS; i++) {
+		struct virtio_usb_hc_port *port = &vhcd_vp->ports[i];
+
+		port->vhcd_vp = vhcd_vp;
+		INIT_LIST_HEAD(&port->pending_urb_list);
+		spin_lock_init(&port->lock);
+		port->port_id = i;
+		port->ss.status.bits.link_state = 0x05; /* RX_DETECT */
+	}
+	for (i = 0; i < VIRTIO_USB_VQ_HOST_MAX; i++)
+		vhcd_vp->hcqs[i] = &vusb->vqueues[vusb->host_vq_base + i];
+
+	/* Install into the port before add_hcd so vhcd_vp->vusb is set */
+	vusb->vports[vp_idx].vhc = vhcd_vp;
+
+	/* Add HCDs first so hs/ss are valid before any PORT_CONNECTED event */
+	rc = virtio_usb_add_hcd(vusb, vhcd_vp);
+	if (rc) {
+		vusb->vports[vp_idx].vhc = NULL;
+		return rc;
+	}
+
+	return 0;
+}
+
+/**
+ * virtio_usb_hc_vp_deinit() - Deinitialize the host controller for one VP.
+ * @vusb:   VirtIO USB device
+ * @vp_idx: VP index
+ */
+int virtio_usb_hc_vp_deinit(struct virtio_usb *vusb, unsigned int vp_idx)
+{
+	struct virtio_usb_hc_vp *vhcd_vp = vusb->vports[vp_idx].vhc;
+
+	if (!vhcd_vp)
+		return 0;
+
+	usb_remove_hcd(vhcd_vp->ss);
+	usb_put_hcd(vhcd_vp->ss);
+	usb_remove_hcd(vhcd_vp->hs);
+	usb_put_hcd(vhcd_vp->hs);
+
+	vhcd_vp->ss = NULL;
+	vhcd_vp->hs = NULL;
+
+	vusb->vports[vp_idx].vhc = NULL;
+	return 0;
+}
+
+/**
+ * virtio_usb_hc_evt_process_one() - Process a single host port event.
+ * @uevent: VirtIO usb host controller event.
+ *
+ * Context: Process context (called from virtio_usb_hc_evt_work()).
+ */
+static void virtio_usb_hc_evt_process_one(struct virtio_usb_event *uevent)
+{
+	struct virtio_usb *vusb = uevent->vusb;
+	struct virtio_usb_host_port_event *evt;
+	unsigned int vp_idx, port_id;
+	struct virtio_usb_hc_vp *vhcd_vp;
+	struct virtio_usb_hc_port *port;
+	struct usb_hcd *hcd = NULL;
+	unsigned long iflags;
+
+	evt = (struct virtio_usb_host_port_event *)virtio_usb_event_buf(uevent);
+	vp_idx = le32_to_cpu(evt->vp_idx);
+	port_id = le32_to_cpu(evt->port_id);
+
+	if (vp_idx >= vusb->nports || !vusb->vports) {
+		dev_err_ratelimited(
+			&vusb->vdev->dev,
+			"virtio_usb: PORT event vp_idx %u invalid (nports=%u), ignoring\n",
+			vp_idx, vusb->nports);
+		return;
+	}
+
+	if (port_id >= VIRTIO_USB_VP_MAX_PORTS) {
+		dev_err_ratelimited(
+			&vusb->vdev->dev,
+			"virtio_usb: PORT event port_id %u >= VP_MAX_PORTS %u, ignoring\n",
+			port_id, VIRTIO_USB_VP_MAX_PORTS);
+		return;
+	}
+
+	vhcd_vp = vusb->vports[vp_idx].vhc;
+	if (!vhcd_vp) {
+		dev_err_ratelimited(
+			&vusb->vdev->dev,
+			"virtio_usb: PORT event vp_idx %u not host-role, ignoring\n",
+			vp_idx);
+		return;
+	}
+
+	port = &vhcd_vp->ports[port_id];
+
+	spin_lock_irqsave(&port->lock, iflags);
+	switch (le32_to_cpu(evt->code)) {
+	case VIRTIO_USB_EVT_HOST_PORT_CONNECTED:
+		/* Reinitialize the pre-allocated port struct in place */
+		memset(&port->hs, 0, sizeof(port->hs));
+		memset(&port->ss, 0, sizeof(port->ss));
+		port->ss.status.bits.link_state = 0x05; /* RX_DETECT */
+
+		switch (le32_to_cpu(evt->speed)) {
+		case USB_SPEED_SUPER_PLUS:
+		case USB_SPEED_SUPER:
+			port->ss.status.bits.connect = 1;
+			port->ss.status.bits.enable = 1;
+			port->ss.status.bits.link_state = 0x00; /* U0 */
+			port->ss.change.bits.connect = 1;
+			port->ss.change.bits.link_state = 1;
+			hcd = vhcd_vp->ss;
+			break;
+		case USB_SPEED_HIGH:
+		case USB_SPEED_FULL:
+		case USB_SPEED_LOW:
+			port->hs.status.bits.connect = 1;
+			port->hs.change.bits.connect = 1;
+			if (le32_to_cpu(evt->speed) == USB_SPEED_HIGH)
+				port->hs.status.bits.high_speed = 1;
+			if (le32_to_cpu(evt->speed) == USB_SPEED_LOW)
+				port->hs.status.bits.low_speed = 1;
+			hcd = vhcd_vp->hs;
+			break;
+		default:
+			break;
+		}
+		port->speed = le32_to_cpu(evt->speed);
+		break;
+
+	case VIRTIO_USB_EVT_HOST_PORT_DISCONNECTED:
+		switch (port->speed) {
+		case USB_SPEED_SUPER_PLUS:
+		case USB_SPEED_SUPER:
+			port->ss.status.bits.connect = 0;
+			port->ss.status.bits.enable = 0;
+			port->ss.status.bits.link_state = 0x05; /* RX_DETECT */
+			port->ss.change.bits.connect = 1;
+			port->ss.change.bits.link_state = 1;
+			hcd = vhcd_vp->ss;
+			break;
+		case USB_SPEED_HIGH:
+		case USB_SPEED_FULL:
+		case USB_SPEED_LOW:
+			port->hs.status.bits.connect = 0;
+			port->hs.status.bits.enable = 0;
+			port->hs.status.bits.low_speed = 0;
+			port->hs.status.bits.high_speed = 0;
+			port->hs.change.bits.connect = 1;
+			port->hs.change.bits.enable = 1;
+			if (port->hs.status.bits.suspend) {
+				port->hs.status.bits.suspend = 0;
+				port->hs.change.bits.suspend = 1;
+			}
+			hcd = vhcd_vp->hs;
+			break;
+		default:
+			break;
+		}
+		port->speed = USB_SPEED_UNKNOWN;
+		break;
+	}
+	spin_unlock_irqrestore(&port->lock, iflags);
+
+	if (hcd)
+		usb_hcd_poll_rh_status(hcd);
+}
+
+/**
+ * virtio_usb_hc_evt_work() - Host event queue receive worker.
+ * @work: kernel work item embedded in struct virtio_usb.
+ *
+ * The host event queue is shared across all host-role VPs and its VP
+ * may not even exist yet at probe time (e.g. a dual-role instance
+ * where every port currently reports device role), so events are
+ * drained and processed here, in process context, rather than
+ * directly inside the interrupt-context notify callback.
+ *
+ * Context: Process context.
+ */
+void virtio_usb_hc_evt_work(struct work_struct *work)
+{
+	struct virtio_usb *vusb =
+		container_of(work, struct virtio_usb, vq_host_evt_work);
+	struct virtio_usb_queue *evtq =
+		&vusb->vqueues[vusb->host_vq_base + VIRTIO_USB_VQ_EVENT_IDX];
+
+	virtio_usb_evt_work(evtq, virtio_usb_hc_evt_process_one);
+}
+
+/**
+ * virtio_usb_hc_dataq_stop_cb() - Stop data virtqueue, force-complete all
+ * pending URBs with -ESHUTDOWN.
+ */
+static void virtio_usb_hc_dataq_stop_cb(struct virtio_usb *vusb,
+					struct virtio_usb_queue *dataq)
+{
+	struct virtio_usb_data *vurb, *vurb_tmp;
+	struct virtio_usb_hc_priv *priv;
+	struct usb_hcd *hcd;
+	unsigned int vp_idx, slot;
+	unsigned long flags;
+
+	virtio_usb_dataq_stop_cb(vusb, dataq);
+
+	if (!vusb->vports)
+		return;
+
+	/* The data virtqueue is shared across all host-role VPs, so the
+	 * work item that drains it lives on struct virtio_usb itself.
+	 */
+	cancel_work_sync(&vusb->vq_host_data_rx_work);
+
+	for (vp_idx = 0; vp_idx < vusb->nports; vp_idx++) {
+		struct virtio_usb_hc_vp *vhcd_vp = vusb->vports[vp_idx].vhc;
+
+		if (!vhcd_vp)
+			continue;
+
+		for (slot = 0; slot < VIRTIO_USB_VP_MAX_PORTS; slot++) {
+			struct virtio_usb_hc_port *port = &vhcd_vp->ports[slot];
+			LIST_HEAD(giveback_list);
+
+			spin_lock_irqsave(&port->lock, flags);
+			list_for_each_entry_safe(
+				vurb, vurb_tmp, &port->pending_urb_list, list) {
+				priv = (struct virtio_usb_hc_priv *)vurb->priv;
+				if (!priv->urb) {
+					list_move_tail(&vurb->list,
+						       &giveback_list);
+					continue;
+				}
+				if (!priv->urb->dev) {
+					usb_hcd_unlink_urb_from_ep(
+						port_vhcd_get(port), priv->urb);
+					priv->urb->hcpriv = NULL;
+					list_move_tail(&vurb->list,
+						       &giveback_list);
+					continue;
+				}
+				hcd = bus_to_hcd(priv->urb->dev->bus);
+				usb_hcd_unlink_urb_from_ep(hcd, priv->urb);
+				priv->urb->hcpriv = NULL;
+				list_move_tail(&vurb->list, &giveback_list);
+			}
+			spin_unlock_irqrestore(&port->lock, flags);
+
+			list_for_each_entry_safe(vurb, vurb_tmp, &giveback_list,
+						 list) {
+				priv = (struct virtio_usb_hc_priv *)vurb->priv;
+				if (!priv->urb || !priv->urb->dev) {
+					list_del(&vurb->list);
+					kfree(priv->sgs);
+					virtio_usb_data_unref(vurb);
+					continue;
+				}
+				hcd = bus_to_hcd(priv->urb->dev->bus);
+				priv->urb->status = -ESHUTDOWN;
+				list_del(&vurb->list);
+				local_bh_disable();
+				usb_hcd_giveback_urb(hcd, priv->urb,
+						     priv->urb->status);
+				local_bh_enable();
+				kfree(priv->sgs);
+				virtio_usb_data_unref(vurb);
+			}
+		}
+	}
+}
+
+/**
+ * virtio_usb_hc_evt_notify_cb() - Event virtqueue notification callback.
+ *
+ * Just schedules virtio_usb_hc_evt_work() - the actual event processing
+ * needs process context, since it may end up reading vhc concurrently
+ * with an OTG-triggered virtio_usb_hc_vp_init()/_deinit(), which sleep.
+ *
+ * Context: Interrupt context.
+ */
+static void virtio_usb_hc_evt_notify_cb(struct virtqueue *vqueue)
+{
+	struct virtio_usb *vusb = vqueue->vdev->priv;
+
+	schedule_work(&vusb->vq_host_evt_work);
+}
+
+/**
+ * virtio_usb_hc_evtq_stop_cb() - Stop the host event virtqueue.
+ *
+ * Do not process host port events during teardown - the vhc they'd
+ * reference may already be gone. Just cancel the (idempotent)
+ * work item and drain the used ring so del_vqs() finds it empty.
+ */
+static void virtio_usb_hc_evtq_stop_cb(struct virtio_usb *vusb,
+				       struct virtio_usb_queue *vq)
+{
+	virtio_usb_evt_drain_stop_cb(vq, &vusb->vq_host_evt_work);
+}
+
+/**
+ * virtio_usb_host_data_notify_cb() - Data virtqueue notification callback.
+ *
+ * The data virtqueue is shared across all host-role VPs; the work item
+ * that drains it lives on struct virtio_usb, not on any specific VP.
+ */
+static void virtio_usb_host_data_notify_cb(struct virtqueue *vqueue)
+{
+	struct virtio_usb *vusb = vqueue->vdev->priv;
+
+	schedule_work(&vusb->vq_host_data_rx_work);
+}
+
+const struct virtio_usb_vq_desc host_vqueues[VIRTIO_USB_VQ_HOST_MAX] = {
+	[VIRTIO_USB_VQ_COMMAND_IDX] = {
+			.callback = virtio_usb_cmd_notify_cb,
+			.name = "virtusb-host-cmd",
+			.process = virtio_usb_cmd_process_cb,
+			.stop = virtio_usb_cmdq_stop_cb,
+			},
+	[VIRTIO_USB_VQ_EVENT_IDX] = {
+			.callback = virtio_usb_hc_evt_notify_cb,
+			.name = "virtusb-host-evt",
+			.process = NULL,
+			.stop = virtio_usb_hc_evtq_stop_cb,
+			},
+	[VIRTIO_USB_VQ_DATA_IDX] = {
+			.callback = virtio_usb_host_data_notify_cb,
+			.name = "virtusb-host-data",
+			.process = NULL,
+			.stop = virtio_usb_hc_dataq_stop_cb,
+			},
+};
diff --git a/drivers/usb/virtio_usb/host.h b/drivers/usb/virtio_usb/host.h
new file mode 100644
index 0000000..8c5a233
--- /dev/null
+++ b/drivers/usb/virtio_usb/host.h
@@ -0,0 +1,203 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * virtio_usb: VirtIO USB device
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef VIRTIO_USB_HOST_H
+#define VIRTIO_USB_HOST_H
+
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/usb/hcd.h>
+#include <linux/virtio.h>
+
+#include <uapi/linux/usb/ch11.h>
+#include <uapi/linux/usb/ch9.h>
+
+#include "controller.h"
+
+/**
+ * struct virtio_usb_port_hs_status - High speed port wPortStatus
+ * See USB 2.0 spec Table 11-21
+ */
+struct virtio_usb_port_hs_status {
+	u16 connect : 1;
+	u16 enable : 1;
+	u16 suspend : 1;
+	u16 over_current : 1;
+	u16 reset : 1;
+	u16 reserved0 : 3;
+	u16 power : 1;
+	u16 low_speed : 1;
+	u16 high_speed : 1;
+	u16 test_mode : 1;
+	u16 indicator_control : 1;
+	u16 reserved1 : 3;
+};
+
+/**
+ * virtio_usb_port_hs_change - High speed port wPortChange
+ * See USB 2.0 spec Table 11-22
+ */
+struct virtio_usb_port_hs_change {
+	u16 connect : 1;
+	u16 enable : 1;
+	u16 suspend : 1;
+	u16 over_current : 1;
+	u16 reset : 1;
+	u16 reserved : 11;
+};
+
+/**
+ * struct virtio_usb_port_ss_status - Super speed port wPortStatus
+ * See USB 3.1 spec Table 10-13.
+ */
+struct virtio_usb_port_ss_status {
+	u16 connect : 1;
+	u16 enable : 1;
+	u16 reserved0 : 1;
+	u16 over_current : 1;
+	u16 reset : 1;
+	u16 link_state : 4;
+	u16 power : 1;
+	u16 speed : 3;
+	u16 reserved1 : 3;
+};
+
+/**
+ * struct virtio_usb_port_ss_change - Super speed port wPortChange.
+ * See USB 3.1 spec Table 10-14.
+ */
+struct virtio_usb_port_ss_change {
+	u16 connect : 1;
+	u16 reserved0 : 2;
+	u16 over_current : 1;
+	u16 reset : 1;
+	u16 bh_reset : 1;
+	u16 link_state : 1;
+	u16 config_error : 1;
+	u16 reserved1 : 8;
+};
+
+/**
+ * struct virtio_usb_hc_port - VirtIO USB host controller port.
+ *
+ * One slot in a VP's root hub. Pre-allocated at VP init time and reused
+ * across connect/disconnect cycles - never dynamically freed.
+ *
+ * @vhcd_vp: Per-VP host controller this port belongs to
+ * @pending_urb_list: Pending URB list to the port
+ * @speed: Speed of current device connected to the port
+ * @port_id: Slot index within the VP (0..VIRTIO_USB_VP_MAX_PORTS-1)
+ * @lock: Spinlock that protects fields shared by interrupt handlers and
+ *        hcd operation callback
+ * @hs: High speed Port Status and Port Change structure
+ * @ss: Super speed Port Status and Port Change structure
+ */
+struct virtio_usb_hc_port {
+	struct virtio_usb_hc_vp *vhcd_vp;
+	struct list_head pending_urb_list;
+	enum usb_device_speed speed;
+	u32 port_id;
+	spinlock_t lock;
+	struct {
+		/* USB 2.0 port status bits */
+		union {
+			struct virtio_usb_port_hs_status bits;
+			u16 value;
+		} status;
+		/* USB 2.0 port status change bits */
+		union {
+			struct virtio_usb_port_hs_change bits;
+			u16 value;
+		} change;
+	} hs;
+	struct {
+		/* USB 3.0 port status bits */
+		union {
+			struct virtio_usb_port_ss_status bits;
+			u16 value;
+		} status;
+		/* USB 3.0 port status change bits */
+		union {
+			struct virtio_usb_port_ss_change bits;
+			u16 value;
+		} change;
+	} ss;
+};
+
+/**
+ * struct virtio_usb_hc_vp - Per-VP VirtIO USB Host controller.
+ *
+ * One instance per host-role virtual port (physical USB socket).
+ * Pointed to by virtio_usb_port.vhc - NULL for device-role VPs,
+ * mirroring how virtio_usb_port.vudc works for device-role VPs.
+ *
+ * All VIRTIO_USB_VP_MAX_PORTS port structs are pre-allocated at init
+ * time and reused across connect/disconnect cycles.
+ *
+ * @vusb:         VirtIO usb device
+ * @vp_idx:       Index of this VP in vusb->vports[]
+ * @hs:           High speed usb_hcd for this VP
+ * @ss:           Super speed usb_hcd for this VP
+ * @ports:        Pre-allocated port state array, one entry per slot
+ * @hcqs:         Host virtqueue wrappers, indexed by VIRTIO_USB_VQ_*_IDX
+ *                (shared across VPs)
+ * @lock:         Spinlock protecting HC state for this VP
+ */
+struct virtio_usb_hc_vp {
+	struct virtio_usb *vusb;
+	unsigned int vp_idx;
+	struct usb_hcd *hs;
+	struct usb_hcd *ss;
+	struct virtio_usb_hc_port ports[VIRTIO_USB_VP_MAX_PORTS];
+	struct virtio_usb_queue *hcqs[VIRTIO_USB_VQ_HOST_MAX];
+	spinlock_t lock;
+};
+
+extern const struct virtio_usb_vq_desc host_vqueues[VIRTIO_USB_VQ_HOST_MAX];
+
+/**
+ * vhcd_get() - Get pointer to per-VP host controller stored in hcd_priv.
+ * @hcd: usb_hcd
+ */
+static inline struct virtio_usb_hc_vp *vhcd_get(struct usb_hcd *hcd)
+{
+	return ((void **)hcd->hcd_priv)[0];
+}
+
+/**
+ * vhcd_set() - Store per-VP host controller pointer in hcd_priv.
+ * @hcd: usb_hcd
+ * @vhcd_vp: per-VP host controller
+ */
+static inline void vhcd_set(struct usb_hcd *hcd,
+			    struct virtio_usb_hc_vp *vhcd_vp)
+{
+	((void **)hcd->hcd_priv)[0] = vhcd_vp;
+}
+
+/**
+ * port_vhcd_get() - Get the usb_hcd that owns this port's speed.
+ * @port: VirtIO usb host controller port
+ *
+ * Returns the SS hcd for SuperSpeed and SuperSpeed+ devices,
+ * HS hcd for everything else.
+ */
+static inline struct usb_hcd *port_vhcd_get(struct virtio_usb_hc_port *port)
+{
+	return (port->speed == USB_SPEED_SUPER ||
+		port->speed == USB_SPEED_SUPER_PLUS) ?
+		       port->vhcd_vp->ss :
+		       port->vhcd_vp->hs;
+}
+
+int virtio_usb_hc_vp_init(struct virtio_usb *vusb, unsigned int vp_idx);
+int virtio_usb_hc_vp_deinit(struct virtio_usb *vusb, unsigned int vp_idx);
+int virtio_usb_hc_event_populate(struct virtio_usb *vusb);
+void virtio_usb_hc_rx_work(struct work_struct *work);
+void virtio_usb_hc_evt_work(struct work_struct *work);
+
+#endif
diff --git a/include/uapi/linux/virtio_usb.h b/include/uapi/linux/virtio_usb.h
index b9dc448..459edc1 100644
--- a/include/uapi/linux/virtio_usb.h
+++ b/include/uapi/linux/virtio_usb.h
@@ -106,6 +106,14 @@ enum {
 	VIRTIO_USB_EVT_HOST_PORT_DISCONNECTED,
 };
 
+/* Maximum number of leaf-device slots per virtual port (physical socket).
+ * Each VP's root hub advertises exactly this many ports to the guest hub
+ * driver. Leaf devices (direct or behind a physical hub) are flattened
+ * into slots 0..VIRTIO_USB_VP_MAX_PORTS-1 of their VP's root hub.
+ * Must be <= USB_MAXCHILDREN (31) and <= USB_SS_MAXPORTS (15).
+ */
+#define VIRTIO_USB_VP_MAX_PORTS 8
+
 /* VIRTIO_USB_EVT_HOST_PORT_CONNECTED/DISCONNECTED */
 enum {
 	VIRTIO_USB_SPEED_UNKNOWN = 0,
@@ -119,9 +127,9 @@ enum {
 
 struct virtio_usb_host_port_event {
 	__le32 code; /* VIRTIO_USB_EVT_HOST_PORT_XXX */
-	__le32 port_id;
+	__le32 vp_idx; /* virtual port (physical socket) index, 0..nports-1 */
+	__le32 port_id; /* leaf slot within VP, 0..VIRTIO_USB_VP_MAX_PORTS-1 */
 	__le32 speed; /* VIRTIO_USB_SPEED_XXX */
-	__le32 padding;
 };
 
 /*****************************************************************************
@@ -210,10 +218,12 @@ enum {
 
 struct virtio_usb_request {
 	__le64 tag;
-	__le16 port; /* Port ID */
+	__le16 vp_idx; /* virtual port (physical socket) index, host role only */
+	__le16 port; /* Port ID (leaf slot within vp_idx for host role) */
 	__le16 endpoint; /* Endpoint ID */
 	__le16 transfer_type; /* VIRTIO_USB_EP_XXX */
 	__le16 transfer_flags; /* VIRTIO_USB_FLAG_XXX */
+	__le16 padding;
 	union {
 		/* transfer_type = VIRTIO_USB_EP_CONTROL */
 		struct {
diff --git a/drivers/usb/virtio_usb/vq_common.c b/drivers/usb/virtio_usb/vq_common.c
new file mode 100644
index 0000000..baaf29d
--- /dev/null
+++ b/drivers/usb/virtio_usb/vq_common.c
@@ -0,0 +1,740 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * virtio-usb: Virtio usb device
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include "controller.h"
+#include "vq_common.h"
+
+/**
+ * struct virtio_usb_cmd_generic_hdr - Generic command header
+ * for the command
+ * @code: command code
+ * @value: value for the command
+ */
+struct virtio_usb_cmd_generic_hdr {
+	__le32 code;
+	__le32 value;
+};
+
+/**
+ * virtio_error_to_usb - Convert virtio error to usb error
+ * @error: virtio error code
+ *
+ * Context: Any context.
+ * Return: virtio error converted to usb error code
+ */
+int virtio_error_to_usb(unsigned int error)
+{
+	int status;
+
+	switch (error) {
+	case VIRTIO_USB_S_OK:
+		status = 0;
+		break;
+	case VIRTIO_USB_S_ERR_CANCELLED:
+		/* cancelled */
+		status = -ECONNRESET;
+		break;
+		/* device shutdown or removal*/
+	case VIRTIO_USB_S_ERR_NO_DEVICE:
+		status = -ESHUTDOWN;
+		break;
+	case VIRTIO_USB_S_ERR_STALL:
+		status = -EPIPE;
+		break;
+	case VIRTIO_USB_S_ERR_OVERFLOW:
+		status = -EOVERFLOW;
+		break;
+	case VIRTIO_USB_S_ERR_SHORT_PKT:
+		/* short packet */
+		status = -EREMOTEIO;
+		break;
+	case VIRTIO_USB_S_ERR_BAD_MSG:
+		status = -EINVAL;
+		break;
+	case VIRTIO_USB_S_ERR_DATA_IN:
+		status = -ECOMM;
+		break;
+	case VIRTIO_USB_S_ERR_DATA_OUT:
+		status = -ENOSR;
+		break;
+	case VIRTIO_USB_S_ERR_ISO_XFER:
+		status = -EXDEV;
+		break;
+	case VIRTIO_USB_S_ERR_ISO_BIG:
+		status = -EFBIG;
+		break;
+	case VIRTIO_USB_S_ERR_MSG_SIZE:
+		status = -EMSGSIZE;
+		break;
+	case VIRTIO_USB_S_ERR_INTERNAL:
+	default:
+		status = -EPROTO;
+		break;
+	}
+	return status;
+}
+
+/**
+ * virtio_usb_msg_ref() - Increment reference counter for the message.
+ * @msg: common message.
+ *
+ * Context: Any context.
+ */
+static void virtio_usb_msg_ref(struct virtio_usb_msg_common *msg)
+{
+	refcount_inc(&msg->ref_count);
+}
+
+/**
+ * virtio_usb_msg_unref() - Decrement reference counter for the message.
+ * @msg: common message.
+ *
+ * The message will be freed when the ref_count value is 0.
+ *
+ * Context: Any context.
+ */
+static void virtio_usb_msg_unref(struct virtio_usb_msg_common *msg)
+{
+	if (refcount_dec_and_test(&msg->ref_count))
+		kfree(msg);
+}
+
+/**
+ * virtio_usb_msg_request() - Get a pointer to the request header.
+ * @msg: common message.
+ *
+ * Context: Any context.
+ */
+static void *virtio_usb_msg_request(struct virtio_usb_msg_common *msg)
+{
+	return sg_virt(&msg->sg_request);
+}
+
+/**
+ * virtio_usb_msg_response() - Get a pointer to the response header.
+ * @msg: common message.
+ *
+ * Context: Any context.
+ */
+static void *virtio_usb_msg_response(struct virtio_usb_msg_common *msg)
+{
+	return sg_virt(&msg->sg_response);
+}
+
+/**
+ * virtio_usb_msg_alloc() - Allocate and initialize a message.
+ * @msg_size: Size of the requested message.
+ * @request_size: Size of request header.
+ * @response_size: Size of response header.
+ * @gfp: Kernel flags for memory allocation.
+ *
+ * The message will be automatically freed when the ref_count value is 0.
+ *
+ * Context: Any context. May sleep if @gfp flags permit.
+ * Return: Allocated message on success, NULL on failure.
+ */
+static void *virtio_usb_msg_alloc(size_t msg_size, size_t request_size,
+				  size_t response_size, gfp_t gfp)
+{
+	struct virtio_usb_msg_common *msg;
+
+	if (!msg_size || !request_size || !response_size)
+		return NULL;
+
+	msg = kzalloc(msg_size + request_size + response_size, gfp);
+	if (!msg)
+		return NULL;
+
+	sg_init_one(&msg->sg_request, (u8 *)msg + msg_size, request_size);
+	sg_init_one(&msg->sg_response, (u8 *)msg + msg_size + request_size,
+		    response_size);
+
+	refcount_set(&msg->ref_count, 1);
+
+	return msg;
+}
+
+/**
+ * virtio_usb_cmd_ref() - Increment reference counter for the command.
+ * @cmd: Command message.
+ *
+ * Context: Any context.
+ */
+void virtio_usb_cmd_ref(struct virtio_usb_cmd *cmd)
+{
+	virtio_usb_msg_ref((struct virtio_usb_msg_common *)cmd);
+}
+
+/**
+ * virtio_usb_cmd_unref() - Decrement reference counter for the command.
+ * @cmd: Command message.
+ *
+ * The message will be freed when the ref_count value is 0.
+ *
+ * Context: Any context.
+ */
+void virtio_usb_cmd_unref(struct virtio_usb_cmd *cmd)
+{
+	virtio_usb_msg_unref((struct virtio_usb_msg_common *)cmd);
+}
+
+/**
+ * virtio_usb_cmd_request() - Get a pointer to the request header.
+ * @cmd: Command msg.
+ *
+ * Context: Any context.
+ */
+void *virtio_usb_cmd_request(struct virtio_usb_cmd *cmd)
+{
+	return virtio_usb_msg_request((struct virtio_usb_msg_common *)cmd);
+}
+
+/**
+ * virtio_usb_cmd_response() - Get a pointer to the response header.
+ * @cmd: command message.
+ *
+ * Context: Any context.
+ */
+void *virtio_usb_cmd_response(struct virtio_usb_cmd *cmd)
+{
+	return virtio_usb_msg_response((struct virtio_usb_msg_common *)cmd);
+}
+
+/**
+ * virtio_usb_cmd_alloc() - Allocate and initialize a control message.
+ * @request_size: Size of request header.
+ * @response_size: Size of response header.
+ * @gfp: Kernel flags for memory allocation.
+ *
+ * The message will be automatically freed when the ref_count value is 0.
+ *
+ * Context: Any context. May sleep if @gfp flags permit.
+ * Return: Allocated message on success, NULL on failure.
+ */
+struct virtio_usb_cmd *virtio_usb_cmd_alloc(size_t request_size,
+					    size_t response_size, gfp_t gfp)
+{
+	struct virtio_usb_cmd *cmd;
+
+	if (!request_size || !response_size)
+		return NULL;
+
+	cmd = virtio_usb_msg_alloc(sizeof(*cmd), request_size, response_size,
+				   gfp);
+	if (!cmd)
+		return NULL;
+
+	init_completion(&cmd->notify);
+
+	return cmd;
+}
+
+/**
+ * virtio_usb_cmd_msg_send() - Function to send command message to the
+ * command virtqueue
+ * @vusb: VirtIO usb device.
+ * @msg: common message.
+ * @out_sgs: Additional sg-list to attach to the request header (may be NULL).
+ * @in_sgs: Additional sg-list to attach to the response header (may be NULL).
+ *
+ * Context: Any context. Takes and releases the command queue spinlock.
+ * Return: 0 on success, -errno on failure.
+ */
+static int virtio_usb_cmd_msg_send(struct virtio_usb *vusb,
+				   struct virtio_usb_msg_common *msg,
+				   struct scatterlist *out_sgs,
+				   struct scatterlist *in_sgs)
+{
+	struct virtio_usb_queue *queue = msg->queue;
+	unsigned int nouts = 0, nins = 0;
+	struct scatterlist *psgs[4];
+	bool notify = false;
+	unsigned long flags;
+	int rc = 0;
+
+	psgs[nouts++] = &msg->sg_request;
+	if (out_sgs)
+		psgs[nouts++] = out_sgs;
+
+	psgs[nouts + nins++] = &msg->sg_response;
+	if (in_sgs)
+		psgs[nouts + nins++] = in_sgs;
+
+	spin_lock_irqsave(&queue->lock, flags);
+	rc = virtqueue_add_sgs(queue->vqueue, psgs, nouts, nins, msg,
+			       GFP_ATOMIC);
+	if (!rc)
+		notify = virtqueue_kick_prepare(queue->vqueue);
+	spin_unlock_irqrestore(&queue->lock, flags);
+
+	if (rc)
+		goto on_exit;
+
+	if (notify)
+		virtqueue_notify(queue->vqueue);
+
+on_exit:
+	return rc;
+}
+
+/**
+ * virtio_usb_cmd_send() - Send a command to the command virtqueue
+ * @vusb: VirtIO usb device.
+ * @cmd: command message.
+ * @out_sgs: Additional sg-list to attach to the request header (may be NULL).
+ * @in_sgs: Additional sg-list to attach to the response header (may be NULL).
+ * @nowait: Flag indicating whether to wait for completion.
+ *
+ * Context: Any context. Takes and releases the command queue spinlock.
+ *          May sleep if @nowait is false.
+ * Return: The return value is a message status code (VIRTIO_USB_S_XXX) converted to an
+ * appropriate -errno value.
+ */
+int virtio_usb_cmd_send(struct virtio_usb *vusb, struct virtio_usb_cmd *cmd,
+			struct scatterlist *out_sgs, struct scatterlist *in_sgs,
+			bool nowait)
+{
+	unsigned int js = msecs_to_jiffies(virtio_usb_cmd_timeout_ms);
+	struct virtio_usb_cmd_generic_hdr *request =
+		virtio_usb_cmd_request(cmd);
+	struct virtio_usb_cmd_status *response = virtio_usb_cmd_response(cmd);
+	struct virtio_device *vdev = vusb->vdev;
+	int rc;
+	u32 code;
+
+	/* Set the default status in case the command was canceled. */
+	response->code = cpu_to_le32(VIRTIO_USB_S_ERR_CANCELLED);
+
+	virtio_usb_cmd_ref(cmd);
+
+	rc = virtio_usb_cmd_msg_send(vusb, (struct virtio_usb_msg_common *)cmd,
+				     out_sgs, in_sgs);
+	if (rc) {
+		dev_err(&vdev->dev, "failed to send control message (0x%08x)\n",
+			le32_to_cpu(request->code));
+
+		/*
+		 * Since in this case virtio_usb_cmd_process_cb() will not be
+		 * called, it is necessary to decrement the reference count.
+		 */
+		virtio_usb_cmd_unref(cmd);
+		goto on_exit;
+	}
+
+	if (nowait)
+		goto on_exit;
+
+	rc = wait_for_completion_interruptible_timeout(&cmd->notify, js);
+	if (rc <= 0) {
+		if (!rc) {
+			dev_err(&vdev->dev,
+				"control message (0x%08x) timeout\n",
+				le32_to_cpu(request->code));
+			rc = -ETIMEDOUT;
+		}
+
+		goto on_exit;
+	}
+
+	code = le32_to_cpu(response->code);
+
+	rc = virtio_error_to_usb(code);
+
+on_exit:
+	virtio_usb_cmd_unref(cmd);
+	return rc;
+}
+
+/**
+ * virtio_usb_events_alloc() - Allocates the events.
+ * @vusb: VirtIO USB device
+ * @vq: event virtqueue to which events need to be populated.
+ * @evt_size: Size of the event structure.
+ *
+ * Context: Any context.
+ * Return: 0 on success -errno on failure
+ */
+struct virtio_usb_event *virtio_usb_events_alloc(struct virtio_usb *vusb,
+						 struct virtio_usb_queue *vq,
+						 size_t evt_size)
+{
+	unsigned int n = virtqueue_get_vring_size(vq->vqueue);
+	struct virtio_device *vdev = vusb->vdev;
+	struct virtio_usb_event *events, *event;
+	unsigned int i;
+
+	events = devm_kcalloc(&vdev->dev, n, (sizeof(*events) + evt_size),
+			      GFP_KERNEL);
+	if (!events)
+		return NULL;
+
+	for (i = 0; i < n; i++) {
+		event = (void *)events + i * (sizeof(*event) + evt_size);
+		event->evt_size = evt_size;
+		event->queue = vq;
+		sg_init_one(&event->sg_event, (void *)event + sizeof(*event),
+			    evt_size);
+		event->vusb = vusb;
+	}
+	return events;
+}
+
+/**
+ * virtio_usb_events_populate() - Add preallocated events to the event queue.
+ * @events: Pointer to preallocated virtio usb events
+ *
+ * Context: Any context.
+ * Return: 0 on success -errno on failure
+ */
+int virtio_usb_events_populate(struct virtio_usb_event *events)
+{
+	unsigned int n = virtqueue_get_vring_size(events[0].queue->vqueue);
+	size_t evt_size = events[0].evt_size;
+	struct virtio_usb_event *event;
+	unsigned int i, rc = 0;
+
+	for (i = 0; i < n; i++) {
+		event = (void *)events + i * (sizeof(*event) + evt_size);
+		rc = virtqueue_add_inbuf(event->queue->vqueue, &event->sg_event,
+					 1, event, GFP_KERNEL);
+		if (rc)
+			return rc;
+	}
+	/* Notify the backend that event buffers are available so it can
+	 * deliver any pending PORT_CONNECTED events immediately.
+	 */
+	virtqueue_notify(events[0].queue->vqueue);
+	return rc;
+}
+
+/**
+ * virtio_usb_event_buf() - Get the event buffer.
+ * @event: The virtio_usb_event
+ *
+ * Context: Any context.
+ * Return: Pointer to the event buffer
+ */
+void *virtio_usb_event_buf(struct virtio_usb_event *event)
+{
+	return sg_virt(&event->sg_event);
+}
+
+/**
+ * virtio_usb_event_send() - Send an event to the specified event queue.
+ * @event: The event that needs to be send
+ *
+ *
+ * Context: Any context which expects the event queue spinlock to be held by
+ *          caller.
+ * Return: 0 on success -errno on failure
+ */
+int virtio_usb_event_send(struct virtio_usb_event *event)
+{
+	int rc = 0;
+	void *event_buf = virtio_usb_event_buf(event);
+
+	/* reset event content */
+	memset(event_buf, 0, event->evt_size);
+
+	rc = virtqueue_add_inbuf(event->queue->vqueue, &event->sg_event, 1,
+				 event, GFP_ATOMIC);
+	if (rc)
+		return rc;
+
+	if (virtqueue_kick_prepare(event->queue->vqueue))
+		virtqueue_notify(event->queue->vqueue);
+	return rc;
+}
+
+/**
+ * virtio_usb_data_ref() - Increment reference counter for the data.
+ * @data: Data message.
+ *
+ * Context: Any context.
+ */
+void virtio_usb_data_ref(struct virtio_usb_data *data)
+{
+	virtio_usb_msg_ref((struct virtio_usb_msg_common *)data);
+}
+
+/**
+ * virtio_usb_data_unref() - Decrement reference counter for the data.
+ * @data: Data message.
+ *
+ * The data will be freed when the ref_count value is 0.
+ *
+ * Context: Any context.
+ */
+void virtio_usb_data_unref(struct virtio_usb_data *data)
+{
+	virtio_usb_msg_unref((struct virtio_usb_msg_common *)data);
+}
+
+/**
+ * virtio_usb_data_request() - Get a pointer to the request header.
+ * @data: Data message.
+ *
+ * Context: Any context.
+ */
+void *virtio_usb_data_request(struct virtio_usb_data *data)
+{
+	return virtio_usb_msg_request((struct virtio_usb_msg_common *)data);
+}
+
+/**
+ * virtio_usb_data_priv() - Get a pointer to the data priv.
+ * @data: Data message.
+ *
+ * Context: Any context.
+ */
+void *virtio_usb_data_priv(struct virtio_usb_data *data)
+{
+	if (!data)
+		return NULL;
+
+	return data->priv;
+}
+
+/**
+ * virtio_usb_data_response() - Get a pointer to the response header.
+ * @data: Data message.
+ *
+ * Context: Any context.
+ */
+void *virtio_usb_data_response(struct virtio_usb_data *data)
+{
+	return virtio_usb_msg_response((struct virtio_usb_msg_common *)data);
+}
+
+/**
+ * virtio_usb_data_alloc() - Allocate and initialize a data message.
+ * @request_size: Size of request header.
+ * @response_size: Size of response header.
+ * @priv_size: Size of priv context of the data.
+ * @gfp: Kernel flags for memory allocation.
+ *
+ * The message will be automatically freed when the ref_count value is 0.
+ *
+ * Context: Any context. May sleep if @gfp flags permit.
+ * Return: Allocated message on success, NULL on failure.
+ */
+struct virtio_usb_data *virtio_usb_data_alloc(size_t request_size,
+					      size_t response_size,
+					      size_t priv_size, gfp_t gfp)
+{
+	struct virtio_usb_data *data;
+
+	if (!request_size || !response_size || !priv_size)
+		return NULL;
+
+	data = virtio_usb_msg_alloc(sizeof(*data) + priv_size, request_size,
+				    response_size, gfp);
+	if (!data)
+		return NULL;
+	data->priv = (u8 *)data + sizeof(*data);
+
+	return data;
+}
+
+/**
+ * virtio_usb_data_send() - Send a data message
+ * @vusb: VirtIO usb device.
+ * @data: Data message.
+ * @out_sgs: Additional sg-list to attach to the request header
+ * @in_sgs: Additional sg-list to attach to the response header
+ *
+ * Context: Any context. Takes and releases the data queue spinlock.
+ * Return: 0 on success, -errno on failure.
+ */
+int virtio_usb_data_send(struct virtio_usb *vusb, struct virtio_usb_data *data,
+			 struct scatterlist *out_sgs,
+			 struct scatterlist *in_sgs)
+{
+	struct virtio_usb_queue *queue = data->msg.queue;
+	struct scatterlist *psgs[4] = { NULL };
+	unsigned int nouts = 0, nins = 0;
+	bool notify = false;
+	int rc = 0;
+
+	psgs[nouts++] = &data->msg.sg_request;
+	if (out_sgs)
+		psgs[nouts++] = out_sgs;
+
+	psgs[nouts + nins++] = &data->msg.sg_response;
+	if (in_sgs)
+		psgs[nouts + nins++] = in_sgs;
+
+	spin_lock_irq(&queue->lock);
+	rc = virtqueue_add_sgs(queue->vqueue, psgs, nouts, nins, data,
+			       GFP_ATOMIC);
+	if (!rc)
+		notify = virtqueue_kick_prepare(queue->vqueue);
+	spin_unlock_irq(&queue->lock);
+
+	if (rc)
+		goto on_exit;
+
+	if (notify)
+		virtqueue_notify(queue->vqueue);
+
+on_exit:
+	return rc;
+}
+
+/**
+ * virtio_usb_cmd_notify_cb() - command virtqueue
+ * notification callback
+ * @vqueue: Underlying virtqueue.
+ *
+ * This callback function is called upon a vring interrupt request from the
+ * device.
+ *
+ * Context: Interrupt context.
+ */
+void virtio_usb_cmd_notify_cb(struct virtqueue *vqueue)
+{
+	struct virtio_usb *vusb = vqueue->vdev->priv;
+	struct virtio_usb_queue *vq = &vusb->vqueues[vqueue->index];
+	unsigned long flags;
+	u32 length;
+	void *buf;
+
+	spin_lock_irqsave(&vq->lock, flags);
+	do {
+		virtqueue_disable_cb(vqueue);
+		while ((buf = virtqueue_get_buf(vqueue, &length)))
+			vq->process(vusb, buf);
+		if (unlikely(virtqueue_is_broken(vqueue)))
+			break;
+	} while (!virtqueue_enable_cb(vqueue));
+	spin_unlock_irqrestore(&vq->lock, flags);
+}
+
+/**
+ * virtio_usb_cmd_process_cb() - process callback for commands.
+ * @vusb: VirtIO usb device.
+ * @buf: Pointer to the command message
+ *
+ * Context: Interrupt context.  Expects the command queue spinlock to be held by
+ *          caller.
+ */
+void virtio_usb_cmd_process_cb(struct virtio_usb *vusb, void *buf)
+{
+	struct virtio_usb_cmd *cmd = (struct virtio_usb_cmd *)buf;
+
+	complete(&cmd->notify);
+	virtio_usb_cmd_unref(cmd);
+}
+
+/**
+ * virtio_usb_cmdq_stop_cb() - Stops the command virtqueue
+ * @vusb: VirtIO usb device.
+ * @cmdq: The command virtqueue to be stopped
+ *
+ * Context: Any context.
+ */
+void virtio_usb_cmdq_stop_cb(struct virtio_usb *vusb,
+			     struct virtio_usb_queue *cmdq)
+{
+	struct virtio_usb_cmd *cmd;
+	unsigned long flags;
+
+	if (cmdq->vqueue) {
+		spin_lock_irqsave(&cmdq->lock, flags);
+		virtqueue_disable_cb(cmdq->vqueue);
+
+		while ((cmd = virtqueue_detach_unused_buf(cmdq->vqueue)))
+			cmdq->process(vusb, cmd);
+
+		spin_unlock_irqrestore(&cmdq->lock, flags);
+	}
+}
+
+/**
+ * virtio_usb_dataq_stop_cb() - Stops the data virtqueue
+ * @vusb: VirtIO usb device.
+ * @dataq: The data virtqueue to be stopped.
+ *
+ * Context: Any context.
+ */
+void virtio_usb_dataq_stop_cb(struct virtio_usb *vusb,
+			      struct virtio_usb_queue *dataq)
+{
+	if (dataq->vqueue) {
+		spin_lock_irq(&dataq->lock);
+		virtqueue_disable_cb(dataq->vqueue);
+		spin_unlock_irq(&dataq->lock);
+	}
+}
+
+/**
+ * virtio_usb_evt_work() - Generic event queue receive worker.
+ * @evtq: The event virtqueue to drain.
+ * @process_one: Callback invoked for each dequeued event, with the
+ *               queue's own lock released (the callback is free to
+ *               sleep / send further virtio commands).
+ *
+ * Common drain loop shared by every role's own event queue: dequeue
+ * completed event buffers, hand each one to @process_one, then
+ * immediately re-arm and resend it via virtio_usb_event_send() so the
+ * backend always has a full set of event buffers available.
+ *
+ * Context: Process context.
+ */
+void virtio_usb_evt_work(struct virtio_usb_queue *evtq,
+			 void (*process_one)(struct virtio_usb_event *event))
+{
+	u32 length;
+	struct virtio_usb_event *event;
+
+	spin_lock(&evtq->lock);
+	do {
+		while ((event = virtqueue_get_buf(evtq->vqueue, &length))) {
+			spin_unlock(&evtq->lock);
+			process_one(event);
+			spin_lock(&evtq->lock);
+			virtio_usb_event_send(event);
+		}
+		if (unlikely(virtqueue_is_broken(evtq->vqueue)))
+			break;
+	} while (!virtqueue_enable_cb(evtq->vqueue));
+	spin_unlock(&evtq->lock);
+}
+
+/**
+ * virtio_usb_evt_drain_stop_cb() - Generic event queue stop callback.
+ * @vq: The event virtqueue to stop.
+ * @work: The work item that drains @vq via virtio_usb_evt_work(), to
+ *        be cancelled before draining (may be NULL if the caller has
+ *        already cancelled it, or must defer cancellation itself).
+ *
+ * Do not process events during teardown - whatever state process_one()
+ * would touch may already be partially torn down (probe failure) or
+ * gone (remove path). Just drain the used ring without processing, so
+ * virtio core's del_vqs() finds it empty.
+ *
+ * Context: Any context that permits to sleep (if @work is non-NULL).
+ */
+void virtio_usb_evt_drain_stop_cb(struct virtio_usb_queue *vq,
+				  struct work_struct *work)
+{
+	unsigned long flags;
+	u32 length;
+	void *buf;
+
+	if (!vq->vqueue)
+		return;
+
+	if (work)
+		cancel_work_sync(work);
+
+	spin_lock_irqsave(&vq->lock, flags);
+	virtqueue_disable_cb(vq->vqueue);
+	while ((buf = virtqueue_get_buf(vq->vqueue, &length)))
+		;
+	spin_unlock_irqrestore(&vq->lock, flags);
+}
diff --git a/drivers/usb/virtio_usb/vq_common.h b/drivers/usb/virtio_usb/vq_common.h
new file mode 100644
index 0000000..28755f3
--- /dev/null
+++ b/drivers/usb/virtio_usb/vq_common.h
@@ -0,0 +1,163 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * virtio-usb: Virtio usb device
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef VIRTIO_USB_COMMON_H
+#define VIRTIO_USB_COMMON_H
+
+#include <linux/atomic.h>
+#include <linux/virtio.h>
+
+#include "controller.h"
+
+/**
+ * struct virtio_usb_msg_common - Common message structure
+ * for command and data message
+ * @sg_request: Scattergather list containing a device request (header).
+ * @sg_response: Scattergather list containing a device response (status).
+ * @queue: Virtqueue wrapper
+ * @ref_count: Reference count used to manage a message lifetime.
+ */
+struct virtio_usb_msg_common {
+	struct scatterlist sg_request;
+	struct scatterlist sg_response;
+	struct virtio_usb_queue *queue;
+	refcount_t ref_count;
+};
+
+/**
+ * struct virtio_usb_cmd - Command message
+ * @msg: Common message
+ * @notify: Request completed notification.
+ */
+struct virtio_usb_cmd {
+	struct virtio_usb_msg_common msg;
+	struct completion notify;
+};
+
+void virtio_usb_cmd_ref(struct virtio_usb_cmd *cmd);
+void virtio_usb_cmd_unref(struct virtio_usb_cmd *cmd);
+void *virtio_usb_cmd_request(struct virtio_usb_cmd *cmd);
+void *virtio_usb_cmd_response(struct virtio_usb_cmd *cmd);
+
+struct virtio_usb_cmd *virtio_usb_cmd_alloc(size_t request_size,
+					    size_t response_size, gfp_t gfp);
+int virtio_usb_cmd_send(struct virtio_usb *vusb, struct virtio_usb_cmd *cmd,
+			struct scatterlist *out_sgs, struct scatterlist *in_sgs,
+			bool nowait);
+
+/**
+ * virtio_usb_cmd_send_sync - Simplified sending of synchronous message.
+ * @vusb: VirtIO usb device.
+ * @out_sgs: Additional sg-list to attach to the request header (may be NULL).
+ * @in_sgs: Additional sg-list to attach to the response header (may be NULL).
+ * @cmd: Command message.
+ *
+ * After returning from this function, the message will be deleted. If message
+ * content is still needed, the caller must additionally to
+ * virtio_usb_cmd_ref/unref() it.
+ *
+ * The msg_timeout_ms module parameter defines the message completion timeout.
+ * If the message is not completed within this time, the function will return an
+ * error.
+ *
+ * Context: Any context that permits to sleep.
+ * Return: 0 on success, -errno on failure.
+ *
+ * The return value is a message status code (VIRTIO_USB_S_XXX) converted to an
+ * appropriate -errno value.
+ */
+static inline int virtio_usb_cmd_send_sync(struct virtio_usb *vusb,
+					   struct scatterlist *out_sgs,
+					   struct scatterlist *in_sgs,
+					   struct virtio_usb_cmd *cmd)
+{
+	return virtio_usb_cmd_send(vusb, cmd, out_sgs, in_sgs, false);
+}
+
+/**
+ * virtio_usb_cmd_send_async() - Simplified sending of asynchronous message.
+ * @vusb: VirtIO usb device.
+ * @out_sgs: Additional sg-list to attach to the request header (may be NULL).
+ * @in_sgs: Additional sg-list to attach to the response header (may be NULL).
+ * @cmd: Command message..
+ *
+ * Context: Any context.
+ * Return: 0 on success, -errno on failure.
+ */
+static inline int virtio_usb_cmd_send_async(struct virtio_usb *vusb,
+					    struct scatterlist *out_sgs,
+					    struct scatterlist *in_sgs,
+					    struct virtio_usb_cmd *cmd)
+{
+	return virtio_usb_cmd_send(vusb, cmd, out_sgs, in_sgs, true);
+}
+
+/**
+ * struct virtio_usb_data - Data message.
+ * @msg: Common message
+ * @list: VirtIO usb data list entry.
+ * @priv: Pointer to priv structure.
+ */
+struct virtio_usb_data {
+	struct virtio_usb_msg_common msg;
+	struct list_head list;
+	void *priv;
+};
+
+void *virtio_usb_data_request(struct virtio_usb_data *data);
+void *virtio_usb_data_response(struct virtio_usb_data *data);
+void *virtio_usb_data_priv(struct virtio_usb_data *data);
+void virtio_usb_data_ref(struct virtio_usb_data *data);
+void virtio_usb_data_unref(struct virtio_usb_data *data);
+
+struct virtio_usb_data *virtio_usb_data_alloc(size_t request_size,
+					      size_t response_size,
+					      size_t priv_size, gfp_t gfp);
+
+int virtio_usb_data_send(struct virtio_usb *vusb, struct virtio_usb_data *data,
+			 struct scatterlist *out_sgs,
+			 struct scatterlist *in_sgs);
+
+void virtio_usb_cmd_notify_cb(struct virtqueue *vqueue);
+
+void virtio_usb_cmd_process_cb(struct virtio_usb *vusb, void *value);
+
+void virtio_usb_cmdq_stop_cb(struct virtio_usb *vusb,
+			     struct virtio_usb_queue *vq);
+void virtio_usb_dataq_stop_cb(struct virtio_usb *vusb,
+			      struct virtio_usb_queue *vq);
+
+/**
+ * struct virtio_usb_event - Event message.
+ * @work: Optional Kernel work to handle the event.
+ * @sg_event: Scattergather list containing a event.
+ * @queue: Virtqueue wrqapper
+ * @vusb: Virtio usb device
+ * @evt_size: size of event buffer
+ */
+struct virtio_usb_event {
+	struct work_struct work;
+	struct scatterlist sg_event;
+	struct virtio_usb_queue *queue;
+	struct virtio_usb *vusb;
+	size_t evt_size;
+};
+
+struct virtio_usb_event *virtio_usb_events_alloc(struct virtio_usb *vusb,
+						 struct virtio_usb_queue *vq,
+						 size_t evt_size);
+int virtio_usb_events_populate(struct virtio_usb_event *events);
+int virtio_usb_event_send(struct virtio_usb_event *event);
+void *virtio_usb_event_buf(struct virtio_usb_event *event);
+int virtio_error_to_usb(unsigned int error);
+
+void virtio_usb_evt_work(struct virtio_usb_queue *evtq,
+			 void (*process_one)(struct virtio_usb_event *event));
+void virtio_usb_evt_drain_stop_cb(struct virtio_usb_queue *vq,
+				  struct work_struct *work);
+
+#endif /* VIRTIO_USB_COMMON_H */

^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH 3/8] virtio-usb: add device role (USB Device Controller) support
  2026-09-24 16:08 [PATCH 0/8] virtio-usb: add dual-role virtio USB driver Igor Skalkin
  2026-09-24 16:09 ` [PATCH 1/8] virtio-usb: add protocol header and skeleton dual-role driver Igor Skalkin
  2026-09-24 16:09 ` [PATCH 2/8] virtio-usb: add host role (USB Host Controller) support Igor Skalkin
@ 2026-09-24 16:09 ` Igor Skalkin
  2026-09-24 16:09 ` [PATCH 4/8] virtio-usb: add OTG role query support Igor Skalkin
                   ` (5 subsequent siblings)
  8 siblings, 0 replies; 24+ messages in thread
From: Igor Skalkin @ 2026-09-24 16:09 UTC (permalink / raw)
  To: Michael S . Tsirkin, Jason Wang, Greg Kroah-Hartman
  Cc: virtualization, linux-usb, Vasilii Ianikeev, Aiswarya Cyriac,
	Anton Yakovlev, Trilok Soni, Igor Skalkin

From: Aiswarya Cyriac <aiswarya.cyriac@oss.qualcomm.com>

Add the device (UDC) role: implement the usb_gadget_ops and
usb_ep_ops callback sets, handle BIND/UNBIND/DISCONNECTED events
from the host backend and forward setup/suspend/resume/reset
notifications to the UDC core, and extend the protocol header with
the endpoint and command messages the device role needs.

Each device-role virtual port gets its own struct virtio_usb_dc and
UDC registration, one per vports[] entry, instead of a single global
device controller - mirroring the per-VP model host role already
uses, since a device-role port is just as independent as a host-role
one. Each gadget gets a name derived from its port index, and its
endpoint name buffer is allocated dynamically instead of using a
stack variable that would outlive the calling function.

This driver virtualizes an entire UDC per device-role port: each port
maps 1:1 to a single struct usb_gadget/UDC instance owned by exactly
one guest. A separate approach - USB function-level virtualization,
where a single physical UDC hosts a composite gadget whose
individual functions are each routed to a different guest - is
currently being explored and is out of scope for this driver.

Hardware endpoint names are used directly as the virtual endpoint
names, since the endpoint name has a relation with the endpoint
address.

Keep the device lifecycle separate from link-state notifications
from the start: BIND registers the UDC and UNBIND tears it down,
while DISCONNECTED is only a transient link-state notification (a
disconnect may be followed by SETUP without a new bind, so it must
not free UDC resources). This aligns the guest-side lifecycle with
the host-side callbacks: BIND -> (SETUP / DISCONNECT / RESET /
SUSPEND / RESUME)* -> UNBIND.

Each port also registers its UDC under its own intermediate
platform_device instead of the shared virtio_device, created on BIND
and torn down on the matching UNBIND. usb_add_gadget_udc() derives
the UDC's class name from its parent kobject's name, and every
device-role port registering under the same shared parent would
collide.

The UDC driver is marked nonatomic, since some gadget API calls
performed under a spinlock cannot be completed atomically due to the
round trip over virtio to the backend.

With both host_role and device_role negotiated, a port's actual role
is ambiguous until a later commit adds an OTG-based per-port query -
every port defaults to DEVICE for now as a placeholder.

Signed-off-by: Aiswarya Cyriac <aiswarya.cyriac@oss.qualcomm.com>
Co-developed-by: Anton Yakovlev <anton.yakovlev@oss.qualcomm.com>
Signed-off-by: Anton Yakovlev <anton.yakovlev@oss.qualcomm.com>
Co-developed-by: Igor Skalkin <igor.skalkin@oss.qualcomm.com>
Signed-off-by: Igor Skalkin <igor.skalkin@oss.qualcomm.com>
---
 drivers/usb/virtio_usb/Makefile     |    3 
 drivers/usb/virtio_usb/controller.c |   75 +
 drivers/usb/virtio_usb/controller.h |   17 
 drivers/usb/virtio_usb/device.c     | 1356 ++++++++++++++++++++++++++++++++++++
 drivers/usb/virtio_usb/device.h     |   91 ++
 include/uapi/linux/virtio_usb.h     |   37 
 6 files changed, 1566 insertions(+), 13 deletions(-)
 create mode 100644 drivers/usb/virtio_usb/device.c
 create mode 100644 drivers/usb/virtio_usb/device.h

diff --git a/drivers/usb/virtio_usb/controller.c b/drivers/usb/virtio_usb/controller.c
index 216edfc..0646807 100644
--- a/drivers/usb/virtio_usb/controller.c
+++ b/drivers/usb/virtio_usb/controller.c
@@ -11,6 +11,7 @@
 
 #include "controller.h"
 #include "host.h"
+#include "device.h"
 #include "vq_common.h"
 
 u32 virtio_usb_cmd_timeout_ms = MSEC_PER_SEC;
@@ -77,7 +78,8 @@ static int virtio_usb_validate(struct virtio_device *vdev)
 		return -EINVAL;
 	}
 
-	if (!virtio_has_feature(vdev, VIRTIO_USB_F_HOST)) {
+	if (!virtio_has_feature(vdev, VIRTIO_USB_F_HOST) &&
+	    !virtio_has_feature(vdev, VIRTIO_USB_F_DEVICE)) {
 		dev_err(&vdev->dev,
 			"device should support at least one usb role\n");
 		return -EINVAL;
@@ -123,17 +125,40 @@ static int virtio_usb_probe(struct virtio_device *vdev)
 	if (virtio_has_feature(vdev, VIRTIO_USB_F_HOST))
 		vusb->host_role = 1;
 
-	/* Only host_role exists so far, so every port is unambiguously a
-	 * host-role port. Later commits (device role, OTG) will replace
-	 * this with real per-port role resolution.
+	if (virtio_has_feature(vdev, VIRTIO_USB_F_DEVICE))
+		vusb->device_role = 1;
+
+	/* Only allocate/negotiate the virtqueue triplets this instance
+	 * actually needs: HOST_* only exists when host_role is negotiated,
+	 * DEV_* only when device_role is negotiated. A pure single-role
+	 * instance therefore has exactly VIRTIO_USB_VQ_HOST_MAX (3) or
+	 * VIRTIO_USB_VQ_DEV_MAX (3) virtqueues, not a fixed layout - queues
+	 * that don't exist on the wire must not be created, since a peer
+	 * with no host-role VP has no host command/event/data queues to
+	 * negotiate at all.
 	 */
 	vusb->host_vq_base = -1;
+	vusb->dev_vq_base = -1;
+
 	if (vusb->host_role) {
 		vusb->host_vq_base = nvqs;
 		nvqs += VIRTIO_USB_VQ_HOST_MAX;
+	}
+	if (vusb->device_role) {
+		vusb->dev_vq_base = nvqs;
+		nvqs += VIRTIO_USB_VQ_DEV_MAX;
+	}
 
-		for (i = 0; i < vusb->nports; i++)
+	/* Resolve every port's role. With only one role negotiated, every
+	 * port unambiguously has that role. With both negotiated, a port's
+	 * own role is ambiguous until a later commit adds an OTG-based
+	 * per-port query - default to DEVICE for now as a placeholder.
+	 */
+	for (i = 0; i < vusb->nports; i++) {
+		if (vusb->host_role && !vusb->device_role)
 			vusb->vports[i].role = VIRTIO_USB_ROLE_HOST;
+		else if (vusb->device_role)
+			vusb->vports[i].role = VIRTIO_USB_ROLE_DEVICE;
 	}
 
 	vusb->vqueues = devm_kcalloc(&vdev->dev, nvqs, sizeof(*vusb->vqueues),
@@ -155,6 +180,18 @@ static int virtio_usb_probe(struct virtio_device *vdev)
 				host_vqueues[i].stop;
 		}
 
+	if (vusb->dev_vq_base >= 0)
+		for (i = 0; i < VIRTIO_USB_VQ_DEV_MAX; i++) {
+			vusb->vqueues[vusb->dev_vq_base + i].name =
+				dev_vqueues[i].name;
+			vusb->vqueues[vusb->dev_vq_base + i].callback =
+				dev_vqueues[i].callback;
+			vusb->vqueues[vusb->dev_vq_base + i].process =
+				dev_vqueues[i].process;
+			vusb->vqueues[vusb->dev_vq_base + i].stop =
+				dev_vqueues[i].stop;
+		}
+
 	rc = virtio_usb_find_vqs(vusb);
 	if (rc) {
 		dev_err(&vdev->dev, "%s virtio_usb_find_vqs() error(%d)\n",
@@ -190,6 +227,30 @@ static int virtio_usb_probe(struct virtio_device *vdev)
 		}
 	}
 
+	if (vusb->device_role) {
+		INIT_WORK(&vusb->vq_dev_data_rx_work, virtio_usb_dc_data_work);
+		INIT_WORK(&vusb->vq_dev_event_work, virtio_usb_dc_event_work);
+
+		for (i = 0; i < vusb->nports; i++) {
+			if (vusb->vports[i].role != VIRTIO_USB_ROLE_DEVICE)
+				continue;
+			rc = virtio_usb_dc_init(vusb, i);
+			if (rc) {
+				dev_err(&vdev->dev,
+					"%s virtio_usb_dc_init() port=%d error(%d)\n",
+					__func__, i, rc);
+				goto on_error;
+			}
+		}
+		rc = virtio_usb_dc_event_populate(vusb);
+		if (rc) {
+			dev_err(&vdev->dev,
+				"%s virtio_usb_dc_event_populate() error(%d)\n",
+				__func__, rc);
+			goto on_error;
+		}
+	}
+
 	virtio_device_ready(vdev);
 
 	return rc;
@@ -219,6 +280,9 @@ static void virtio_usb_remove(struct virtio_device *vdev)
 			virtio_usb_hc_vp_deinit(vusb, i);
 	}
 
+	if (vusb->device_role && vusb->vports)
+		virtio_usb_dc_deinit(vusb);
+
 	virtio_reset_device(vdev);
 
 	vdev->config->del_vqs(vdev);
@@ -226,6 +290,7 @@ static void virtio_usb_remove(struct virtio_device *vdev)
 
 static const unsigned int virtio_usb_features[] = {
 	VIRTIO_USB_F_HOST,
+	VIRTIO_USB_F_DEVICE,
 };
 
 static const struct virtio_device_id id_table[] = {
diff --git a/drivers/usb/virtio_usb/controller.h b/drivers/usb/virtio_usb/controller.h
index af68a77..ec59922 100644
--- a/drivers/usb/virtio_usb/controller.h
+++ b/drivers/usb/virtio_usb/controller.h
@@ -16,21 +16,26 @@
 
 /* Forward declaration - full definition in host.h */
 struct virtio_usb_hc_vp;
+/* Forward declaration - full definition in device.h */
+struct virtio_usb_dc;
 
 #define VIRTIO_USB_VQ_COMMAND_IDX 0
 #define VIRTIO_USB_VQ_EVENT_IDX 1
 #define VIRTIO_USB_VQ_DATA_IDX 2
 
 #define VIRTIO_USB_VQ_HOST_MAX 3
+#define VIRTIO_USB_VQ_DEV_MAX 3
 
 /**
  * struct virtio_usb_port - Per-virtual-port state.
  * @role: Role of this port (VIRTIO_USB_ROLE_HOST or _DEVICE).
  * @vhc: Host controller - non-NULL when role is HOST.
+ * @vudc: Device controller - non-NULL when role is DEVICE.
  */
 struct virtio_usb_port {
 	unsigned int role;
 	struct virtio_usb_hc_vp *vhc;
+	struct virtio_usb_dc *vudc;
 };
 
 /**
@@ -41,14 +46,22 @@ struct virtio_usb_port {
  * @nports: number of supported ports
  * @nvqs: number of virtqueues for the device
  * @host_role: flag indicating support for host role
+ * @device_role: flag indicating support for device role
  * @host_vq_base: index into vqueues[] where the HOST_COMMAND/EVENT/DATA
  *                triplet starts, or -1 if this instance has no host-role
  *                VP (in which case those queues do not exist on the wire
  *                and must not be negotiated).
+ * @dev_vq_base: index into vqueues[] where the DEV_COMMAND/EVENT/DATA
+ *               triplet starts, or -1 if this instance has no
+ *               device-role VP.
  * @vq_host_data_rx_work: Kernel work draining the host data queue, shared
  *                        across every host-role VP.
  * @vq_host_evt_work: Kernel work draining the host event queue, shared
  *                     across every host-role VP.
+ * @vq_dev_data_rx_work: Kernel work draining the device data queue, shared
+ *                       across every device-role port.
+ * @vq_dev_event_work: Kernel work draining the device event queue, shared
+ *                      across every device-role port.
  */
 struct virtio_usb {
 	struct virtio_device *vdev;
@@ -57,9 +70,13 @@ struct virtio_usb {
 	unsigned int nports;
 	u32 nvqs;
 	bool host_role;
+	bool device_role;
 	int host_vq_base;
+	int dev_vq_base;
 	struct work_struct vq_host_data_rx_work;
 	struct work_struct vq_host_evt_work;
+	struct work_struct vq_dev_data_rx_work;
+	struct work_struct vq_dev_event_work;
 };
 
 /**
diff --git a/drivers/usb/virtio_usb/Makefile b/drivers/usb/virtio_usb/Makefile
index 1111111..2222222 100644
--- a/drivers/usb/virtio_usb/Makefile
+++ b/drivers/usb/virtio_usb/Makefile
@@ -1,7 +1,8 @@
 # SPDX-License-Identifier: GPL-2.0-or-later
 
 virtio-usb-y := controller.o \
 	vq_common.o \
-	host.o
+	host.o \
+	device.o
 
 obj-$(CONFIG_USB_VIRTIO) += virtio-usb.o
diff --git a/drivers/usb/virtio_usb/device.c b/drivers/usb/virtio_usb/device.c
new file mode 100644
index 0000000..798c265
--- /dev/null
+++ b/drivers/usb/virtio_usb/device.c
@@ -0,0 +1,1356 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * virtio_usb: VirtIO USB device
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <uapi/linux/virtio_usb.h>
+
+#include "controller.h"
+#include "device.h"
+
+#define GADGET_NAME "virtio_usb_dc"
+
+/**
+ * struct virtio_usb_dc_priv - Device controller data priv
+ * Structure for UDC related data in data messages
+ * @req: usb_request structure
+ * @vep: virtio usb endpoint
+ * @vreq: virtio-usb request
+ */
+struct virtio_usb_dc_priv {
+	struct usb_request req;
+	struct virtio_usb_ep *vep;
+	struct virtio_usb_data *vreq;
+};
+
+/**
+ * usb_ep_to_virtio_ep() - Get the virtio usb endpoint from usb endpoint
+ * @ep: usb endpoint
+ *
+ * Context: Any context.
+ * Return: Pointer to virtio_usb_ep
+ */
+static struct virtio_usb_ep *usb_ep_to_virtio_ep(struct usb_ep *ep)
+{
+	return container_of(ep, struct virtio_usb_ep, ep);
+}
+
+/**
+ * usb_ep_dir_in() - check if the endpoint has IN direction
+ * @ep: usb endpoint
+ *
+ * Context: Any context.
+ * Return: 1 if direction is USB_DIR_IN else 0
+ */
+static unsigned int usb_ep_dir_in(struct usb_ep *ep)
+{
+	struct virtio_usb_ep *vep = usb_ep_to_virtio_ep(ep);
+	unsigned int direction = 0;
+
+	if (ep->address == 0)
+		direction = vep->setup.bRequestType & USB_DIR_IN ? 1 : 0;
+	else if (ep->desc && ep->desc->bEndpointAddress)
+		direction = usb_endpoint_dir_in(ep->desc) ? 1 : 0;
+
+	return direction;
+}
+
+/**
+ * usb_req_to_virtio_data() - Get the virtio usb data message from usb_request
+ * @req: usb_request structure
+ *
+ * Context: Any context.
+ * Return: virtio usb data message
+ */
+static struct virtio_usb_data *usb_req_to_virtio_data(struct usb_request *req)
+{
+	struct virtio_usb_dc_priv *priv =
+		container_of(req, struct virtio_usb_dc_priv, req);
+
+	return priv->vreq;
+}
+
+/**
+ * virtio_usb_dc_complete_req() - Completes a usb request
+ * @vreq: virtio usb data message.
+ *
+ * Context: Process context.
+ */
+static void virtio_usb_dc_complete_req(struct virtio_usb_data *vreq)
+{
+	struct virtio_usb_response *response = virtio_usb_data_response(vreq);
+	struct virtio_usb_dc_priv *priv = virtio_usb_data_priv(vreq);
+	unsigned int status = le32_to_cpu(response->status);
+	struct usb_request *req = &priv->req;
+	struct virtio_usb_ep *vep = priv->vep;
+	struct virtio_usb_dc *vudc = vep->vudc;
+	struct virtio_usb_data *vreq_iter;
+	unsigned int found = 0;
+	unsigned long flags;
+
+	spin_lock_irqsave(&vudc->lock, flags);
+
+	list_for_each_entry(vreq_iter, &vep->req_queue, list) {
+		if (vreq_iter == vreq) {
+			found = 1;
+			break;
+		}
+	}
+	if (!found) {
+		spin_unlock_irqrestore(&vudc->lock, flags);
+		return;
+	}
+	if (req->status != -ECONNRESET && req->status != -ESHUTDOWN) {
+		req->status = virtio_error_to_usb(status);
+		if (!req->status)
+			req->actual = le32_to_cpu(response->actual_length);
+	}
+
+	list_del_init(&vreq->list);
+	spin_unlock_irqrestore(&vudc->lock, flags);
+	virtio_usb_data_unref(vreq);
+	usb_gadget_giveback_request(&vep->ep, req);
+}
+
+/**
+ * virtio_usb_dc_data_work() - Worker to get all completed data message
+ * from the virtqueue and call the completion handler.
+ * @work: kernel work to handle data message completion.
+ *
+ * Context: Process context.
+ */
+void virtio_usb_dc_data_work(struct work_struct *work)
+{
+	struct virtio_usb *vusb =
+		container_of(work, struct virtio_usb, vq_dev_data_rx_work);
+	struct virtio_usb_queue *dataq =
+		&vusb->vqueues[vusb->dev_vq_base + VIRTIO_USB_VQ_DATA_IDX];
+	struct virtio_usb_data *vreq;
+	unsigned int length;
+
+	spin_lock_irq(&dataq->lock);
+	do {
+		virtqueue_disable_cb(dataq->vqueue);
+		while ((vreq = virtqueue_get_buf(dataq->vqueue, &length))) {
+			spin_unlock_irq(&dataq->lock);
+			virtio_usb_dc_complete_req(vreq);
+			spin_lock_irq(&dataq->lock);
+		}
+		if (unlikely(virtqueue_is_broken(dataq->vqueue)))
+			break;
+	} while (!virtqueue_enable_cb(dataq->vqueue));
+	spin_unlock_irq(&dataq->lock);
+}
+
+/**
+ * virtio_usb_dc_data_alloc() - Allocate and initialize a device controller
+ * data message.
+ * @ep: VirtIO usb device
+ * @gfp: Kernel flags for memory allocation.
+ *
+ * The message will be automatically freed when the ref_count value is 0.
+ *
+ * Context: Any context. May sleep if @gfp flags permit.
+ * Return: Allocated data message on success, NULL on failure.
+ */
+static struct virtio_usb_data *virtio_usb_dc_data_alloc(struct usb_ep *ep,
+							gfp_t gfp)
+{
+	struct virtio_usb_ep *vep = usb_ep_to_virtio_ep(ep);
+	struct virtio_usb_request *request;
+	struct virtio_usb_response *response;
+	size_t request_size = sizeof(*request);
+	size_t response_size = sizeof(*response);
+	struct virtio_usb_dc_priv *priv;
+	struct virtio_usb_data *vreq;
+
+	vreq = virtio_usb_data_alloc(request_size, response_size,
+				     sizeof(struct virtio_usb_dc_priv), gfp);
+	if (!vreq)
+		return NULL;
+
+	priv = virtio_usb_data_priv(vreq);
+	priv->vep = vep;
+	priv->vreq = vreq;
+	vreq->msg.queue =
+		&vep->vudc->vusb->vqueues[vep->vudc->vusb->dev_vq_base +
+					  VIRTIO_USB_VQ_DATA_IDX];
+
+	request = virtio_usb_data_request(vreq);
+
+	request->tag = cpu_to_le64((uintptr_t)vreq);
+
+	return vreq;
+}
+
+/**
+ * virtio_usb_dc_cmd_alloc() - Allocate and initialize the device controller
+ * command message.
+ * @vusb: VirtIO usb device
+ * @command: command message
+ * @gfp: Kernel flags for memory allocation.
+ *
+ * The message will be automatically freed when the ref_count value is 0.
+ *
+ * Context: Any context. May sleep if @gfp flags permit.
+ * Return: Allocated command message on success, NULL on failure.
+ */
+static struct virtio_usb_cmd *
+virtio_usb_dc_cmd_alloc(struct virtio_usb_dc *vudc, unsigned int command,
+			gfp_t gfp)
+{
+	size_t request_size = sizeof(struct virtio_usb_dev_cmd_hdr);
+	size_t response_size = sizeof(struct virtio_usb_cmd_status);
+	struct virtio_usb_cmd_status *status;
+	struct virtio_usb_dev_cmd_hdr *hdr;
+	struct virtio_usb_cmd *cmd;
+
+	switch (command) {
+	case VIRTIO_USB_CMD_DEV_GET_ENDPOINT_COUNT:
+		response_size = sizeof(struct virtio_usb_dev_ep_count);
+		break;
+	case VIRTIO_USB_CMD_DEV_GET_FRAME_NUMBER:
+		response_size = sizeof(struct virtio_usb_dev_frame_number);
+		break;
+	case VIRTIO_USB_CMD_DEV_CANCEL:
+		request_size = sizeof(struct virtio_usb_dev_cmd_cancel);
+		break;
+	case VIRTIO_USB_CMD_DEV_VBUS_DRAW:
+	case VIRTIO_USB_CMD_DEV_PULLUP:
+	case VIRTIO_USB_CMD_DEV_EP_SET_HALT:
+	case VIRTIO_USB_CMD_DEV_SET_SELF_POWERED:
+		request_size = sizeof(struct virtio_usb_dev_cmd_set_value);
+		break;
+	}
+
+	cmd = virtio_usb_cmd_alloc(request_size, response_size, gfp);
+	if (!cmd)
+		return NULL;
+
+	hdr = virtio_usb_cmd_request(cmd);
+	status = virtio_usb_cmd_response(cmd);
+
+	hdr->code = cpu_to_le32(command);
+	hdr->port = cpu_to_le16(vudc->port);
+	cmd->msg.queue = &vudc->vusb->vqueues[vudc->vusb->dev_vq_base +
+					      VIRTIO_USB_VQ_COMMAND_IDX];
+	status->code = cpu_to_le32(VIRTIO_USB_S_ERR_CANCELLED);
+	return cmd;
+}
+
+/* Endpoint callbacks */
+
+/**
+ * virtio_ep_enable() - Enable endpoint
+ * This callback is called to configure endpoint and make it usable.
+ * It is called to enable all endpoints except ep0
+ * @ep: endpoint object
+ * @desc: endpoint descriptor
+ *
+ * Context: Any context.
+ */
+static int virtio_ep_enable(struct usb_ep *ep,
+			    const struct usb_endpoint_descriptor *desc)
+{
+	struct virtio_usb_ep *vep = usb_ep_to_virtio_ep(ep);
+	struct virtio_usb_dc *vudc = vep->vudc;
+	struct scatterlist sg;
+	struct scatterlist *psg_data = &sg;
+	struct virtio_usb_dev_cmd_hdr *hdr;
+	struct virtio_usb_cmd *cmd;
+	unsigned long flags;
+	u16 endpoint;
+	int rc;
+
+	if (!vudc->driver)
+		return -ESHUTDOWN;
+
+	spin_lock_irqsave(&vudc->lock, flags);
+
+	if (!ep || !desc || ep->caps.type_control ||
+	    desc->bDescriptorType != USB_DT_ENDPOINT) {
+		spin_unlock_irqrestore(&vudc->lock, flags);
+		return -EINVAL;
+	}
+
+	ep->desc = desc;
+	ep->maxpacket = usb_endpoint_maxp(desc);
+	endpoint = vep->ep_id | (usb_ep_dir_in(ep) ? VIRTIO_USB_EP_DIR_IN :
+						     VIRTIO_USB_EP_DIR_OUT);
+
+	spin_unlock_irqrestore(&vudc->lock, flags);
+
+	cmd = virtio_usb_dc_cmd_alloc(vudc, VIRTIO_USB_CMD_DEV_EP_ENABLE,
+				      GFP_ATOMIC);
+	if (!cmd)
+		return -ENOMEM;
+
+	sg_init_one(psg_data, desc, sizeof(*desc));
+	hdr = virtio_usb_cmd_request(cmd);
+	hdr->endpoint = cpu_to_le16(endpoint);
+
+	rc = virtio_usb_cmd_send_sync(vudc->vusb, psg_data, NULL, cmd);
+
+	return rc;
+}
+
+/**
+ * virtio_ep_disable() - Disable endpoint
+ * This callback is called to disable endpoint which was
+ * enabled using ep_enable callback
+ * @ep: endpoint object
+ *
+ * Context: Any context.
+ * Return: 0 on success, -errno on failure.
+ */
+static int virtio_ep_disable(struct usb_ep *ep)
+{
+	struct virtio_usb_ep *vep = usb_ep_to_virtio_ep(ep);
+	struct virtio_usb_data *vreq = NULL, *vreq_tmp;
+	struct virtio_usb_dc *vudc = vep->vudc;
+	struct virtio_usb_dc_priv *priv = NULL;
+	struct virtio_usb_dev_cmd_hdr *hdr;
+	struct virtio_usb_cmd *cmd;
+	unsigned long flags;
+	u16 endpoint;
+	int rc;
+
+	if (!ep || ep->caps.type_control)
+		return -EINVAL;
+
+	spin_lock_irqsave(&vudc->lock, flags);
+
+	list_for_each_entry_safe(vreq, vreq_tmp, &vep->req_queue, list) {
+		priv = virtio_usb_data_priv(vreq);
+		/**
+		 * When endpoint is disabled, completion handler for all pending
+		 * requests will be called. Make the request status to -ESHUTDOWN
+		 * to prevent requests completes even before the endpoint disable
+		 * is send to the controller.
+		 */
+		priv->req.status = -ESHUTDOWN;
+	}
+	endpoint = vep->ep_id | (usb_ep_dir_in(ep) ? VIRTIO_USB_EP_DIR_IN :
+						     VIRTIO_USB_EP_DIR_OUT);
+	spin_unlock_irqrestore(&vudc->lock, flags);
+
+	cmd = virtio_usb_dc_cmd_alloc(vudc, VIRTIO_USB_CMD_DEV_EP_DISABLE,
+				      GFP_ATOMIC);
+	if (!cmd)
+		return -ENOMEM;
+
+	hdr = virtio_usb_cmd_request(cmd);
+	hdr->endpoint = cpu_to_le16(endpoint);
+
+	rc = virtio_usb_cmd_send_sync(vudc->vusb, NULL, NULL, cmd);
+
+	return rc;
+}
+
+/**
+ * virtio_ep_alloc_request() - Allocates request
+ * @ep: Endpoint object associated with request
+ * @mem_flags: mem flags
+ *
+ * Return: allocated request address, NULL on allocation error
+ */
+static struct usb_request *virtio_ep_alloc_request(struct usb_ep *ep,
+						   gfp_t mem_flags)
+{
+	struct virtio_usb_dc_priv *priv;
+	struct virtio_usb_data *vreq;
+
+	vreq = virtio_usb_dc_data_alloc(ep, mem_flags);
+	if (!vreq)
+		return NULL;
+
+	priv = virtio_usb_data_priv(vreq);
+
+	return &priv->req;
+}
+
+/**
+ * virtio_ep_free_request() - Free memory occupied by request
+ * @ep: Endpoint object associated with request
+ * @req: Request to be freed
+ */
+static void virtio_ep_free_request(struct usb_ep *ep, struct usb_request *req)
+{
+	struct virtio_usb_data *vreq = usb_req_to_virtio_data(req);
+
+	virtio_usb_data_unref(vreq);
+}
+
+/**
+ * virtio_ep_queue() - Transfer data on and endpoint
+ * @ep: Pointer to endpoint object
+ * @req: Pointer to request object
+ * @mem_flags: gfp flags
+ *
+ * Context: Any context.
+ * Return: 0 on success, -errno on failure.
+ */
+static int virtio_ep_queue(struct usb_ep *ep, struct usb_request *req,
+			   gfp_t mem_flags)
+{
+	struct scatterlist *out_sgs = NULL, *in_sgs = NULL, *psg_data;
+	struct virtio_usb_data *vreq = usb_req_to_virtio_data(req);
+	struct virtio_usb_ep *vep = usb_ep_to_virtio_ep(ep);
+	struct virtio_usb_dc *vudc = vep->vudc;
+	struct virtio_usb_response *response;
+	struct virtio_usb_request *request;
+	u16 transfer_flags = 0;
+	struct scatterlist sg;
+	unsigned long flags;
+	u16 endpoint;
+	int rc;
+
+	virtio_usb_data_ref(vreq);
+
+	spin_lock_irqsave(&vudc->lock, flags);
+	req->actual = 0;
+	req->status = -EINPROGRESS;
+
+	list_add_tail(&vreq->list, &vep->req_queue);
+	endpoint = vep->ep_id | (usb_ep_dir_in(ep) ? VIRTIO_USB_EP_DIR_IN :
+						     VIRTIO_USB_EP_DIR_OUT);
+	spin_unlock_irqrestore(&vudc->lock, flags);
+
+	request = virtio_usb_data_request(vreq);
+	response = virtio_usb_data_response(vreq);
+
+	response->actual_length = cpu_to_le32(0);
+	response->status = cpu_to_le32(VIRTIO_USB_S_ERR_INTERNAL);
+
+	if (req->short_not_ok)
+		transfer_flags |= VIRTIO_USB_FLAG_SHORT_NOT_OK;
+	else if (req->zero)
+		transfer_flags |= VIRTIO_USB_FLAG_ZERO_PACKET;
+
+	request->transfer_flags = cpu_to_le16(transfer_flags);
+	request->endpoint = cpu_to_le16(endpoint);
+	request->port = cpu_to_le16(vudc->port);
+
+	if (req->length && req->buf) {
+		psg_data = &sg;
+		sg_init_one(psg_data, req->buf, req->length);
+	} else if (req->length && req->num_sgs > 0) {
+		psg_data = req->sg;
+	} else if (req->sg) {
+		psg_data = &sg;
+		sg_init_one(psg_data, sg_virt(req->sg), req->length);
+	} else {
+		psg_data = NULL;
+	}
+
+	if (usb_ep_dir_in(ep))
+		out_sgs = psg_data;
+	else
+		in_sgs = psg_data;
+
+	rc = virtio_usb_data_send(vudc->vusb, vreq, out_sgs, in_sgs);
+	if (rc)
+		goto on_error_vq;
+
+	return rc;
+
+on_error_vq:
+	spin_lock_irqsave(&vudc->lock, flags);
+	list_del_init(&vreq->list);
+	spin_unlock_irqrestore(&vudc->lock, flags);
+
+	virtio_usb_data_unref(vreq);
+
+	return rc;
+}
+
+/**
+ * virtio_ep_dequeue() - Remove request from transfer queue
+ * @ep: Endpoint object associated with request
+ * @req: Request object
+ *
+ * Context: Any context.
+ * Return: 0 on success, -errno on failure.
+ */
+static int virtio_ep_dequeue(struct usb_ep *ep, struct usb_request *req)
+{
+	struct virtio_usb_ep *vep = usb_ep_to_virtio_ep(ep);
+	struct virtio_usb_dev_cmd_cancel *cancel;
+	struct virtio_usb_dc *vudc = vep->vudc;
+	struct virtio_usb_dc_priv *priv;
+	struct virtio_usb_data *vreq;
+	struct virtio_usb_cmd *cmd;
+	unsigned long flags;
+	int rc = -EINVAL;
+	u16 endpoint;
+
+	if (!vudc->driver)
+		return -ESHUTDOWN;
+
+	spin_lock_irqsave(&vudc->lock, flags);
+
+	list_for_each_entry(vreq, &vep->req_queue, list) {
+		priv = virtio_usb_data_priv(vreq);
+		if (req == &priv->req) {
+			pr_debug("dequeue for vreq = %p, tag %llx\n", vreq,
+				 (u64)(uintptr_t)vreq);
+			// request will be completed from the completion handler
+			priv->req.status = -ECONNRESET;
+			rc = 0;
+			break;
+		}
+	}
+	endpoint = vep->ep_id | (usb_ep_dir_in(ep) ? VIRTIO_USB_EP_DIR_IN :
+						     VIRTIO_USB_EP_DIR_OUT);
+	spin_unlock_irqrestore(&vudc->lock, flags);
+
+	if (rc)
+		return rc;
+
+	vreq = priv->vreq;
+
+	cmd = virtio_usb_dc_cmd_alloc(vudc, VIRTIO_USB_CMD_DEV_CANCEL,
+				      GFP_KERNEL);
+	if (!cmd)
+		return -ENOMEM;
+
+	cancel = virtio_usb_cmd_request(cmd);
+	cancel->hdr.endpoint = cpu_to_le16(endpoint);
+	cancel->hdr.port = cpu_to_le16(vudc->port);
+	cancel->tag = cpu_to_le64((uintptr_t)vreq);
+
+	rc = virtio_usb_cmd_send_sync(vudc->vusb, NULL, NULL, cmd);
+
+	return rc;
+}
+
+/**
+ * virtio_ep_set_halt() - Sets/clears stall on selected endpoint
+ * @ep: Endpoint object to set/clear stall on
+ * @value: 1 for set stall, 0 for clear stall
+ *
+ * Context: Any context.
+ * Return: 0 on success, -errno on failure.
+ */
+static int virtio_ep_set_halt(struct usb_ep *ep, int value)
+{
+	struct virtio_usb_ep *vep = usb_ep_to_virtio_ep(ep);
+	struct virtio_usb_dev_cmd_set_value *req;
+	struct virtio_usb_dc *vudc = vep->vudc;
+	struct virtio_usb_cmd *cmd;
+	unsigned long flags;
+	u16 endpoint;
+	int rc;
+
+	spin_lock_irqsave(&vudc->lock, flags);
+
+	/*
+	 * EP0 MUST NOT STALL if a control request is still pending.
+	 * Composite expects -EAGAIN instead of a forced STALL, otherwise
+	 * status stage collapses and the host sees EPROTO/EPIPE.
+	 */
+	if (&vep->ep == vudc->gadget.ep0) {
+		if (value && !list_empty(&vep->req_queue)) {
+			spin_unlock_irqrestore(&vudc->lock, flags);
+			return -EAGAIN;
+		}
+	}
+
+	if (value && ep->desc && usb_ep_dir_in(ep) &&
+	    !list_empty(&vep->req_queue)) {
+		spin_unlock_irqrestore(&vudc->lock, flags);
+		return -EAGAIN;
+	}
+	endpoint = vep->ep_id | (usb_ep_dir_in(ep) ? VIRTIO_USB_EP_DIR_IN :
+						     VIRTIO_USB_EP_DIR_OUT);
+	spin_unlock_irqrestore(&vudc->lock, flags);
+
+	cmd = virtio_usb_dc_cmd_alloc(vudc, VIRTIO_USB_CMD_DEV_EP_SET_HALT,
+				      GFP_ATOMIC);
+	if (!cmd)
+		return -ENOMEM;
+
+	req = virtio_usb_cmd_request(cmd);
+	req->value = cpu_to_le32(value);
+	req->hdr.endpoint = cpu_to_le16(endpoint);
+
+	rc = virtio_usb_cmd_send_sync(vudc->vusb, NULL, NULL, cmd);
+
+	return rc;
+}
+
+/**
+ * virtio_ep_set_wedge() - Set wedge on selected endpoint
+ * @ep: Endpoint object
+ *
+ * Context: Any context.
+ * Return: 0 on success, -errno on failure.
+ */
+static int virtio_ep_set_wedge(struct usb_ep *ep)
+{
+	struct virtio_usb_ep *vep = usb_ep_to_virtio_ep(ep);
+	struct virtio_usb_dc *vudc = vep->vudc;
+	struct virtio_usb_dev_cmd_hdr *hdr;
+	struct virtio_usb_cmd *cmd;
+	unsigned long flags;
+	u16 endpoint;
+	int rc;
+
+	spin_lock_irqsave(&vudc->lock, flags);
+	endpoint = vep->ep_id | (usb_ep_dir_in(ep) ? VIRTIO_USB_EP_DIR_IN :
+						     VIRTIO_USB_EP_DIR_OUT);
+	spin_unlock_irqrestore(&vudc->lock, flags);
+
+	cmd = virtio_usb_dc_cmd_alloc(vudc, VIRTIO_USB_CMD_DEV_EP_SET_WEDGE,
+				      GFP_ATOMIC);
+	if (!cmd)
+		return -ENOMEM;
+
+	hdr = virtio_usb_cmd_request(cmd);
+	hdr->endpoint = cpu_to_le16(endpoint);
+
+	rc = virtio_usb_cmd_send_sync(vudc->vusb, NULL, NULL, cmd);
+
+	return rc;
+}
+
+static const struct usb_ep_ops virtio_usb_ep_ops = {
+	.enable = virtio_ep_enable,
+	.disable = virtio_ep_disable,
+
+	.alloc_request = virtio_ep_alloc_request,
+	.free_request = virtio_ep_free_request,
+
+	.queue = virtio_ep_queue,
+	.dequeue = virtio_ep_dequeue,
+
+	.set_halt = virtio_ep_set_halt,
+	.set_wedge = virtio_ep_set_wedge,
+};
+
+/*-------------------------------------------------------------------------*/
+/* UDC callbacks */
+
+/**
+ * virtio_usb_dc_set_selfpowered() - Sets the device selfpowered feature.
+ * @gadget: The device being declared as self-powered
+ * @is_selfpowered: Flag indicates if gadget is selfpowered
+ *
+ * Return: 0 on success, -errno on failure.
+ */
+static int virtio_usb_dc_set_selfpowered(struct usb_gadget *gadget,
+					 int is_selfpowered)
+{
+	struct virtio_usb_dc *vudc =
+		container_of(gadget, struct virtio_usb_dc, gadget);
+	unsigned int code = VIRTIO_USB_CMD_DEV_SET_SELF_POWERED;
+	struct virtio_usb_dev_cmd_set_value *req;
+	struct virtio_usb_cmd *cmd;
+
+	cmd = virtio_usb_dc_cmd_alloc(vudc, code, GFP_ATOMIC);
+	if (!cmd)
+		return -ENOMEM;
+
+	req = virtio_usb_cmd_request(cmd);
+	req->value = cpu_to_le32(!!is_selfpowered);
+
+	return virtio_usb_cmd_send_sync(vudc->vusb, NULL, NULL, cmd);
+}
+
+/**
+ * virtio_usb_dc_pullup() - Software-controlled connect/disconnect to USB host
+ * @gadget: Pointer to the usb gadget structure.
+ * @is_on: flag to start or stop
+ *
+ * Return: 0 on success, -errno on failure.
+ */
+static int virtio_usb_dc_pullup(struct usb_gadget *gadget, int is_on)
+{
+	struct virtio_usb_dc *vudc =
+		container_of(gadget, struct virtio_usb_dc, gadget);
+	unsigned int code = VIRTIO_USB_CMD_DEV_PULLUP;
+	struct virtio_usb_dev_cmd_set_value *req;
+	struct virtio_usb_cmd *cmd;
+	unsigned long flags;
+	int rc;
+
+	spin_lock_irqsave(&vudc->lock, flags);
+	is_on = !!is_on;
+	if (is_on == vudc->pullup) {
+		rc = -EALREADY;
+		goto on_unlock;
+	}
+
+	vudc->pullup = is_on;
+	spin_unlock_irqrestore(&vudc->lock, flags);
+
+	cmd = virtio_usb_dc_cmd_alloc(vudc, code, GFP_ATOMIC);
+	if (!cmd)
+		return -ENOMEM;
+
+	req = virtio_usb_cmd_request(cmd);
+	req->value = cpu_to_le32(is_on);
+
+	rc = virtio_usb_cmd_send_sync(vudc->vusb, NULL, NULL, cmd);
+
+	return rc;
+
+on_unlock:
+	spin_unlock_irqrestore(&vudc->lock, flags);
+
+	return rc;
+}
+
+/**
+ * virtio_usb_dc_set_speed() - Sets maximum speed supported by gadget
+ * @gadget: Pointer to the usb gadget structure.
+ * @speed: The maximum speed to allowed to run
+ */
+static void virtio_usb_dc_set_speed(struct usb_gadget *gadget,
+				    enum usb_device_speed speed)
+{
+	struct virtio_usb_dc *vudc =
+		container_of(gadget, struct virtio_usb_dc, gadget);
+
+	vudc->gadget.speed = min_t(u8, USB_SPEED_HIGH, speed);
+
+	switch (speed) {
+	case USB_SPEED_HIGH:
+	case USB_SPEED_FULL:
+		vudc->veps[0].ep.maxpacket = 64;
+		break;
+	case USB_SPEED_LOW:
+		vudc->veps[0].ep.maxpacket = 8;
+		break;
+	default:
+		break;
+	}
+}
+
+/**
+ * virtio_usb_dc_start() - Starts the device controller.
+ * @gadget: Pointer to the usb gadget structure
+ * @driver: Pointer to gadget driver structure
+ *
+ * Return: zero always
+ */
+static int virtio_usb_dc_start(struct usb_gadget *gadget,
+			       struct usb_gadget_driver *driver)
+{
+	struct virtio_usb_dc *vudc =
+		container_of(gadget, struct virtio_usb_dc, gadget);
+	unsigned long flags;
+
+	spin_lock_irqsave(&vudc->lock, flags);
+	vudc->driver = driver;
+	vudc->pullup = 0;
+	spin_unlock_irqrestore(&vudc->lock, flags);
+
+	return 0;
+}
+
+/**
+ * virtio_usb_dc_stop() - Stops the device controller.
+ * @gadget: Pointer to the usb gadget structure
+ *
+ * Return: zero always
+ */
+static int virtio_usb_dc_stop(struct usb_gadget *gadget)
+{
+	struct virtio_usb_dc *vudc =
+		container_of(gadget, struct virtio_usb_dc, gadget);
+	unsigned long flags;
+
+	spin_lock_irqsave(&vudc->lock, flags);
+	vudc->driver = NULL;
+	vudc->pullup = 0;
+	spin_unlock_irqrestore(&vudc->lock, flags);
+
+	return 0;
+}
+
+/**
+ * virtio_usb_dc_vbus_draw() - Constrain controller's VBUS power usage
+ * @gadget: The device whose VBUS usage is being described
+ * @mA: How much current to draw, in milliAmperes.
+ *
+ * Return: 0 on success, -errno on failure.
+ */
+static int virtio_usb_dc_vbus_draw(struct usb_gadget *gadget, unsigned int mA)
+{
+	struct virtio_usb_dc *vudc =
+		container_of(gadget, struct virtio_usb_dc, gadget);
+	unsigned int code = VIRTIO_USB_CMD_DEV_VBUS_DRAW;
+	struct virtio_usb_dev_cmd_set_value *req;
+	struct virtio_usb_cmd *cmd;
+	int rc;
+
+	cmd = virtio_usb_dc_cmd_alloc(vudc, code, GFP_ATOMIC);
+	if (!cmd)
+		return -ENOMEM;
+
+	req = virtio_usb_cmd_request(cmd);
+	req->value = cpu_to_le32(mA);
+
+	rc = virtio_usb_cmd_send_sync(vudc->vusb, NULL, NULL, cmd);
+
+	return rc;
+}
+
+/**
+ * virtio_usb_dc_get_frame() - returns the current frame number
+ * @gadget: controller that reports the frame number
+ *
+ * Return: Returns the usb frame number, normally eleven bits from
+ * a SOF packet, or -errno on failure.
+ */
+static int virtio_usb_dc_get_frame(struct usb_gadget *gadget)
+{
+	struct virtio_usb_dc *vudc =
+		container_of(gadget, struct virtio_usb_dc, gadget);
+	unsigned int code = VIRTIO_USB_CMD_DEV_GET_FRAME_NUMBER;
+	struct virtio_usb_dev_frame_number *resp;
+	struct virtio_usb_cmd *cmd;
+	int rc;
+
+	cmd = virtio_usb_dc_cmd_alloc(vudc, code, GFP_ATOMIC);
+	if (!cmd)
+		return -ENOMEM;
+
+	rc = virtio_usb_cmd_send_sync(vudc->vusb, NULL, NULL, cmd);
+	if (rc)
+		return rc;
+
+	resp = virtio_usb_cmd_response(cmd);
+	rc = le32_to_cpu(resp->frame_number);
+
+	return rc;
+}
+
+static const struct usb_gadget_ops virtio_gadget_ops = {
+	.set_selfpowered = virtio_usb_dc_set_selfpowered,
+	.pullup = virtio_usb_dc_pullup,
+	.udc_start = virtio_usb_dc_start,
+	.udc_stop = virtio_usb_dc_stop,
+	.vbus_draw = virtio_usb_dc_vbus_draw,
+	.udc_set_speed = virtio_usb_dc_set_speed,
+	.get_frame = virtio_usb_dc_get_frame,
+};
+
+/*-------------------------------------------------------------------------*/
+
+/**
+ * virtio_usb_dc_get_endpoint_count() - Function to get the number of
+ * endpoints from the virtio-usb device
+ * @vusb: Virtio usb device
+ *
+ * Context: Process context.
+ * Return: 0 on success, -errno on failure.
+ */
+static int virtio_usb_dc_get_endpoint_count(struct virtio_usb_dc *vudc)
+{
+	unsigned int code = VIRTIO_USB_CMD_DEV_GET_ENDPOINT_COUNT;
+	struct virtio_usb_dev_ep_count *ep_count;
+	struct virtio_usb_cmd *cmd;
+	int rc;
+
+	cmd = virtio_usb_dc_cmd_alloc(vudc, code, GFP_KERNEL);
+	if (!cmd)
+		return -ENOMEM;
+
+	virtio_usb_cmd_ref(cmd);
+	rc = virtio_usb_cmd_send_sync(vudc->vusb, NULL, NULL, cmd);
+	if (rc)
+		goto on_exit;
+
+	ep_count = virtio_usb_cmd_response(cmd);
+	vudc->neps = le32_to_cpu(ep_count->count);
+	if (!vudc->neps)
+		rc = -EINVAL;
+
+on_exit:
+	virtio_usb_cmd_unref(cmd);
+	return rc;
+}
+
+/**
+ * virtio_usb_dc_get_endpoint_info() - Function to get the endpoint
+ * info from virtio-usb device.
+ * @vusb: Virtio usb device
+ *
+ * Context: Process context.
+ *  Return: 0 on success, -errno on failure.
+ */
+static int virtio_usb_dc_get_endpoint_info(struct virtio_usb_dc *vudc)
+{
+	unsigned int code = VIRTIO_USB_CMD_DEV_GET_ENDPOINT_INFO;
+	struct virtio_usb_dev_ep_info *epinfo;
+	struct virtio_usb_cmd *cmd;
+	struct scatterlist sg;
+	struct scatterlist *psg_data = &sg;
+	int rc, i;
+
+	vudc->veps = kcalloc(vudc->neps, sizeof(*vudc->veps) + sizeof(*epinfo),
+			     GFP_KERNEL);
+	if (!vudc->veps)
+		return -ENOMEM;
+
+	epinfo = (void *)vudc->veps + (sizeof(*vudc->veps) * vudc->neps);
+
+	cmd = virtio_usb_dc_cmd_alloc(vudc, code, GFP_KERNEL);
+	if (!cmd) {
+		rc = -ENOMEM;
+		goto on_error;
+	}
+	virtio_usb_cmd_ref(cmd);
+
+	sg_init_one(psg_data, epinfo, sizeof(*epinfo) * vudc->neps);
+
+	rc = virtio_usb_cmd_send_sync(vudc->vusb, NULL, psg_data, cmd);
+	if (rc) {
+		virtio_usb_cmd_unref(cmd);
+		goto on_error;
+	}
+
+	INIT_LIST_HEAD(&vudc->gadget.ep_list);
+
+	for (i = 0; i < vudc->neps; i++) {
+		struct virtio_usb_ep *vep = &vudc->veps[i];
+		u16 types = le16_to_cpu(epinfo[i].types);
+		u16 directions = le16_to_cpu(epinfo[i].directions);
+		struct usb_ep_caps caps = VIRTIO_USB_EP_CAPS(types, directions);
+		unsigned int maxpacket_limit =
+			le16_to_cpu(epinfo[i].maxpacket_limit);
+
+		strscpy(vep->name, epinfo[i].name, sizeof(vep->name));
+		vep->ep_id = i;
+		vep->ep.caps = caps;
+		vep->ep.name = vep->name;
+		vep->ep.ops = &virtio_usb_ep_ops;
+		INIT_LIST_HEAD(&vep->req_queue);
+		list_add_tail(&vep->ep.ep_list, &vudc->gadget.ep_list);
+		usb_ep_set_maxpacket_limit(&vep->ep, maxpacket_limit);
+		vep->ep.max_streams = le16_to_cpu(epinfo[i].max_streams);
+		vep->vudc = vudc;
+	}
+	virtio_usb_cmd_unref(cmd);
+	vudc->gadget.ep0 = &vudc->veps[0].ep;
+	list_del_init(&vudc->veps[0].ep.ep_list);
+
+	return rc;
+
+on_error:
+	kfree(vudc->veps);
+	vudc->veps = NULL;
+	return rc;
+}
+
+static int virtio_usb_dc_parent_create(struct virtio_usb_dc *vudc)
+{
+	int rc;
+
+	vudc->pdev = platform_device_alloc(GADGET_NAME, vudc->port);
+	if (!vudc->pdev)
+		return -ENOMEM;
+
+	vudc->pdev->dev.parent = &vudc->vusb->vdev->dev;
+	rc = platform_device_add(vudc->pdev);
+	if (rc) {
+		platform_device_put(vudc->pdev);
+		vudc->pdev = NULL;
+		return rc;
+	}
+
+	return 0;
+}
+
+/**
+ * virtio_usb_dc_event_process() - Event process function
+ * @event: virtio_usb_event
+ *
+ * Context: Process context.
+ * Return: 0 on success, -errno on failure.
+ */
+static void virtio_usb_dc_event_process(struct virtio_usb_event *event)
+{
+	struct virtio_usb_dev_event *evt = virtio_usb_event_buf(event);
+	struct virtio_usb_dev_setup_event *setup_evt;
+	struct virtio_usb *vusb = event->vusb;
+	struct virtio_usb_dc *vudc;
+	struct usb_gadget_driver *driver;
+	struct virtio_usb_ep *vep;
+	unsigned long flags;
+	int rc = 0;
+	unsigned short port_id = le16_to_cpu(evt->port);
+
+	if (!vusb->nports || port_id >= vusb->nports) {
+		dev_err(&vusb->vdev->dev, "%s port[%d] index out of range\n",
+			__func__, port_id);
+		return;
+	}
+
+	vudc = vusb->vports[port_id].vudc;
+	driver = vudc->driver;
+
+	switch (le32_to_cpu(evt->code)) {
+	case VIRTIO_USB_EVT_DEV_BIND: {
+		if (vudc->registered) {
+			dev_err(&vusb->vdev->dev,
+				"port %d: BIND while still registered, ignoring\n",
+				port_id);
+			rc = -EBUSY;
+			break;
+		}
+
+		memzero_explicit(&vudc->gadget, sizeof(struct usb_gadget));
+		vudc->gadget.sg_supported = 1;
+		rc = virtio_usb_dc_get_endpoint_count(vudc);
+		if (rc) {
+			dev_err(&vusb->vdev->dev,
+				"Failed to get endpoint count\n");
+			break;
+		}
+		rc = virtio_usb_dc_get_endpoint_info(vudc);
+		if (rc) {
+			dev_err(&vusb->vdev->dev,
+				"Failed to get endpoint info\n");
+			break;
+		}
+		if (!vudc->pdev) {
+			rc = virtio_usb_dc_parent_create(vudc);
+			if (rc) {
+				dev_err(&vusb->vdev->dev,
+					"Failed to create UDC parent device\n");
+				kfree(vudc->veps);
+				vudc->veps = NULL;
+				break;
+			}
+		}
+		vudc->gadget.name =
+			kasprintf(GFP_KERNEL, "%s_%d", GADGET_NAME, port_id);
+		if (!vudc->gadget.name) {
+			rc = -ENOMEM;
+			kfree(vudc->veps);
+			vudc->veps = NULL;
+			break;
+		}
+		vudc->gadget.ops = &virtio_gadget_ops;
+		vudc->gadget.max_speed = USB_SPEED_HIGH;
+		vudc->gadget.nonatomic = 1;
+
+		//vudc->gadget.dev.init_name = gadget_name;
+		vudc->gadget.dev.parent = &vudc->pdev->dev;
+		rc = usb_add_gadget_udc(&vudc->pdev->dev, &vudc->gadget);
+		if (rc) {
+			dev_err(&vudc->pdev->dev, "Failed to add udc\n");
+			kfree(vudc->veps);
+			vudc->veps = NULL;
+			kfree(vudc->gadget.name);
+			break;
+		}
+		spin_lock_irqsave(&vudc->lock, flags);
+		vudc->registered = 1;
+		spin_unlock_irqrestore(&vudc->lock, flags);
+		break;
+	}
+	case VIRTIO_USB_EVT_DEV_DISCONNECTED: {
+		if (vudc->driver && vudc->driver->disconnect)
+			vudc->driver->disconnect(&vudc->gadget);
+		if (vudc->registered)
+			usb_gadget_set_state(&vudc->gadget,
+					     USB_STATE_NOTATTACHED);
+		break;
+	}
+	case VIRTIO_USB_EVT_DEV_SETUP: {
+		if (!driver)
+			break;
+
+		vep = usb_ep_to_virtio_ep(vudc->gadget.ep0);
+		setup_evt = virtio_usb_event_buf(event);
+
+		memcpy(&vep->setup, setup_evt->setup,
+		       sizeof(struct usb_ctrlrequest));
+
+		rc = driver->setup(&vudc->gadget,
+				   (struct usb_ctrlrequest *)setup_evt->setup);
+		if (rc < 0 && rc != -ESHUTDOWN)
+			virtio_ep_set_halt(vudc->gadget.ep0, 1);
+		break;
+	}
+	case VIRTIO_USB_EVT_DEV_RESET: {
+		if (driver)
+			usb_gadget_udc_reset(&vudc->gadget, driver);
+		break;
+	}
+	case VIRTIO_USB_EVT_DEV_SUSPEND: {
+		if (driver && driver->suspend)
+			driver->suspend(&vudc->gadget);
+		break;
+	}
+	case VIRTIO_USB_EVT_DEV_RESUME: {
+		if (driver && driver->resume)
+			driver->resume(&vudc->gadget);
+		break;
+	}
+	case VIRTIO_USB_EVT_DEV_UNBIND: {
+		unsigned int registered;
+
+		spin_lock_irqsave(&vudc->lock, flags);
+		registered = vudc->registered;
+		vudc->registered = 0;
+		spin_unlock_irqrestore(&vudc->lock, flags);
+
+		if (registered) {
+			usb_del_gadget_udc(&vudc->gadget);
+			kfree(vudc->gadget.name);
+			vudc->gadget.name = NULL;
+		}
+
+		kfree(vudc->veps);
+		vudc->veps = NULL;
+
+		if (vudc->pdev) {
+			platform_device_unregister(vudc->pdev);
+			vudc->pdev = NULL;
+		}
+		break;
+	}
+	default:
+		rc = -EINVAL;
+		break;
+	}
+}
+
+/**
+ * virtio_usb_dc_event_work() - Worker to get all events
+ * from the virtqueue and process them.
+ * @work: Kernel work to handle event completion.
+ *
+ * Context: Process context.
+ */
+void virtio_usb_dc_event_work(struct work_struct *work)
+{
+	struct virtio_usb *vusb =
+		container_of(work, struct virtio_usb, vq_dev_event_work);
+	struct virtio_usb_queue *evtq =
+		&vusb->vqueues[vusb->dev_vq_base + VIRTIO_USB_VQ_EVENT_IDX];
+
+	virtio_usb_evt_work(evtq, virtio_usb_dc_event_process);
+}
+
+/**
+ * virtio_usb_dc_event_populate() - Add events to the device event queue.
+ * @vusb: VirtIO USB device
+ *
+ * Context: Any context.
+ * Return: 0 on success -errno on failure
+ */
+int virtio_usb_dc_event_populate(struct virtio_usb *vusb)
+{
+	struct virtio_usb_queue *evt_queue =
+		&vusb->vqueues[vusb->dev_vq_base + VIRTIO_USB_VQ_EVENT_IDX];
+	struct virtio_usb_event *events;
+	int rc;
+
+	events = virtio_usb_events_alloc(vusb, evt_queue,
+					 sizeof(struct virtio_usb_dev_event));
+
+	if (!events)
+		return -ENOMEM;
+
+	rc = virtio_usb_events_populate(events);
+
+	return rc;
+}
+
+/**
+ * virtio_usb_dc_init() - Initializes the device role.
+ * @vusb: VirtIO USB device
+ *
+ * Context: Any context.
+ * Return: 0 on success -errno on failure
+ */
+int virtio_usb_dc_init(struct virtio_usb *vusb, int port_id)
+{
+	struct virtio_usb_dc *vudc;
+	unsigned int i;
+
+	vudc = devm_kzalloc(&vusb->vdev->dev, sizeof(*vudc), GFP_KERNEL);
+	if (!vudc)
+		return -ENOMEM;
+
+	vusb->vports[port_id].vudc = vudc;
+	vudc->vusb = vusb;
+	vudc->port = port_id;
+	for (i = 0; i < VIRTIO_USB_VQ_DEV_MAX; i++)
+		vudc->dcqs[i] = &vusb->vqueues[vusb->dev_vq_base + i];
+
+	spin_lock_init(&vudc->lock);
+
+	vudc->registered = 0;
+
+	return 0;
+}
+
+/**
+ * virtio_usb_dc_deinit() - Deinitialize the device role.
+ * @vusb: VirtIO USB device
+ *
+ * Context: Any context.
+ * Return: 0 on success -errno on failure
+ */
+int virtio_usb_dc_deinit(struct virtio_usb *vusb)
+{
+	int port_id;
+
+	if (!vusb->device_role)
+		return -ENODEV;
+
+	for (port_id = 0; port_id < vusb->nports; port_id++) {
+		struct virtio_usb_dc *vudc = vusb->vports[port_id].vudc;
+		unsigned int registered;
+		unsigned long flags;
+
+		if (!vudc)
+			continue;
+
+		spin_lock_irqsave(&vudc->lock, flags);
+		registered = vudc->registered;
+		spin_unlock_irqrestore(&vudc->lock, flags);
+
+		if (registered)
+			usb_del_gadget_udc(&vudc->gadget);
+
+		kfree(vudc->veps);
+		vudc->veps = NULL;
+
+		if (vudc->pdev) {
+			platform_device_unregister(vudc->pdev);
+			vudc->pdev = NULL;
+		}
+
+		vusb->vports[port_id].vudc = NULL;
+	}
+
+	return 0;
+}
+
+/**
+ * virtio_usb_dc_dataq_stop_cb() - Stops the data virtqueue
+ * @vusb: VirtIO usb device.
+ * @dataq: data virtqueue wrapper
+ *
+ * Context: Any context.
+ */
+static void virtio_usb_dc_dataq_stop_cb(struct virtio_usb *vusb,
+					struct virtio_usb_queue *dataq)
+{
+	int port_id;
+
+	if (!vusb->device_role)
+		return;
+
+	virtio_usb_dataq_stop_cb(vusb, dataq);
+	cancel_work_sync(&vusb->vq_dev_data_rx_work);
+
+	for (port_id = 0; port_id < vusb->nports; port_id++) {
+		struct virtio_usb_dc *vudc = vusb->vports[port_id].vudc;
+		struct virtio_usb_data *vreq, *vreq_tmp;
+		struct virtio_usb_dc_priv *priv;
+		unsigned int i;
+
+		if (!vudc)
+			continue;
+
+		for (i = 0; i < vudc->neps; i++) {
+			list_for_each_entry_safe(vreq, vreq_tmp,
+						 &vudc->veps[i].req_queue,
+						 list) {
+				priv = virtio_usb_data_priv(vreq);
+				priv->req.status = -ESHUTDOWN;
+				list_del_init(&vreq->list);
+				virtio_usb_data_unref(vreq);
+				usb_gadget_giveback_request(&priv->vep->ep,
+							    &priv->req);
+			}
+		}
+	}
+}
+
+/**
+ * virtio_usb_dc_data_notify_cb() - Data virtqueue notification callback
+ * @vqueue: Underlying event virtqueue.
+ *
+ * This callback function is called upon a vring interrupt request from the
+ * device.
+ *
+ * Context: Interrupt context.
+ */
+static void virtio_usb_dc_data_notify_cb(struct virtqueue *vqueue)
+{
+	struct virtio_usb *vusb = vqueue->vdev->priv;
+
+	schedule_work(&vusb->vq_dev_data_rx_work);
+}
+
+/**
+ * virtio_usb_dc_evt_notify_cb() - Event virtqueue notification callback
+ * @vqueue: Underlying event virtqueue.
+ *
+ * This callback function is called upon a vring interrupt request from the
+ * device.
+ *
+ * Context: Interrupt context.
+ */
+static void virtio_usb_dc_evt_notify_cb(struct virtqueue *vqueue)
+{
+	struct virtio_usb *vusb = vqueue->vdev->priv;
+
+	schedule_work(&vusb->vq_dev_event_work);
+}
+
+/**
+ * virtio_usb_dc_evtq_stop_cb() - Stops the event virtqueue.
+ * @vusb: VirtIO usb device.
+ * @vq: virtio usb vq wrapper
+ *
+ * Context: Any context.
+ */
+static void virtio_usb_dc_evtq_stop_cb(struct virtio_usb *vusb,
+				       struct virtio_usb_queue *vq)
+{
+	/*
+	 * Unlike the host evtq stop path (which safely updates in-memory
+	 * port status bits), dc event processing drives the UDC state
+	 * machine and calls back into gadget drivers and UDC core. Doing
+	 * so here, when the UDC may be only partially initialized (probe
+	 * failure) or already torn down (remove path), risks
+	 * use-after-free and crashes - virtio_usb_evt_drain_stop_cb()
+	 * drains without processing for exactly this reason.
+	 */
+	cancel_work_sync((struct work_struct *)&vusb->vq_dev_event_work);
+	virtio_usb_evt_drain_stop_cb(vq, NULL);
+}
+
+const struct virtio_usb_vq_desc dev_vqueues[VIRTIO_USB_VQ_DEV_MAX] = {
+			[VIRTIO_USB_VQ_COMMAND_IDX] = {
+			.callback = virtio_usb_cmd_notify_cb,
+			.name = "virtusb-dev-cmd",
+			.process = virtio_usb_cmd_process_cb,
+			.stop = virtio_usb_cmdq_stop_cb,
+		},
+		[VIRTIO_USB_VQ_EVENT_IDX] = {
+			.callback = virtio_usb_dc_evt_notify_cb,
+			.name = "virtusb-dev-evt",
+			.process = NULL,
+			.stop = virtio_usb_dc_evtq_stop_cb,
+		},
+		[VIRTIO_USB_VQ_DATA_IDX] = {
+			.callback = virtio_usb_dc_data_notify_cb,
+			.name = "virtusb-dev-data",
+			.process = NULL,
+			.stop = virtio_usb_dc_dataq_stop_cb,
+		},
+};
diff --git a/drivers/usb/virtio_usb/device.h b/drivers/usb/virtio_usb/device.h
new file mode 100644
index 0000000..9dacc89
--- /dev/null
+++ b/drivers/usb/virtio_usb/device.h
@@ -0,0 +1,91 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * virtio-usb: Virtio usb device
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef VIRTIO_USB_DC_H
+#define VIRTIO_USB_DC_H
+
+#include <linux/usb.h>
+#include <linux/list.h>
+#include <linux/platform_device.h>
+#include <uapi/linux/usb/ch11.h>
+#include <uapi/linux/usb/ch9.h>
+#include <linux/usb/gadget.h>
+
+#include "vq_common.h"
+
+#include <uapi/linux/virtio_usb.h>
+
+#define VIRTIO_USB_EP_CAPS_TYPE_CONTROL 0x01
+#define VIRTIO_USB_EP_CAPS_TYPE_INTERRUPT 0x02
+#define VIRTIO_USB_EP_CAPS_TYPE_BULK 0x04
+#define VIRTIO_USB_EP_CAPS_TYPE_ISOCHRONOUS 0x08
+
+#define VIRTIO_USB_EP_CAPS_DIR_IN 0x02
+#define VIRTIO_USB_EP_CAPS_DIR_OUT 0x01
+
+#define VIRTIO_USB_EP_CAPS(_type, _dir)                                        \
+	{                                                                      \
+		.type_control = !!((_type) & VIRTIO_USB_EP_CAPS_TYPE_CONTROL), \
+		.type_iso = !!((_type) & VIRTIO_USB_EP_CAPS_TYPE_ISOCHRONOUS), \
+		.type_bulk = !!((_type) & VIRTIO_USB_EP_CAPS_TYPE_BULK),       \
+		.type_int = !!((_type) & VIRTIO_USB_EP_CAPS_TYPE_INTERRUPT),   \
+		.dir_in = !!((_dir) & VIRTIO_USB_EP_CAPS_DIR_IN),              \
+		.dir_out = !!((_dir) & VIRTIO_USB_EP_CAPS_DIR_OUT),            \
+	}
+
+/**
+ * struct virtio_usb_ep - virtio usb device endpoint
+ * @ep: usb ep
+ * @vusb: VirtIO usb device
+ * @setup: setup packet for control endpoint
+ * @req_queue: list of usb requests submitted to ep awaiting response
+ * @ep_id: Id of the endpoint
+ * @name: Endpoint name
+ */
+struct virtio_usb_ep {
+	struct usb_ep ep;
+	struct virtio_usb_dc *vudc;
+	struct usb_ctrlrequest setup;
+	struct list_head req_queue;
+	u16 ep_id;
+	char name[16];
+};
+
+/**
+ * struct virtio_usb_dc - VirtIO USB Device controller (USBDC) device.
+ * @gadget: Pointer to the usb gadget structure
+ * @driver: Pointer to the usb gadget  driver structure
+ * @veps: Virtual endpoints
+ * @neps: Number of virtual endpoints
+ * @dcqs: Device virtqueue wrappers, indexed by VIRTIO_USB_VQ_*_IDX.
+ * @vusb: VirtIO usb device
+ * @registered: Flag indicating registration status to the UDC core.
+ * @pullup:  Software-controlled connect/disconnect status USB host.
+ * @lock: Spinlock that protects device state
+ */
+struct virtio_usb_dc {
+	struct platform_device *pdev;
+	u16 port; // Device id
+	struct usb_gadget gadget;
+	struct usb_gadget_driver *driver;
+	struct virtio_usb_ep *veps;
+	u32 neps;
+	struct virtio_usb_queue *dcqs[VIRTIO_USB_VQ_DEV_MAX];
+	struct virtio_usb *vusb;
+	unsigned registered : 1;
+	unsigned pullup : 1;
+	spinlock_t lock;
+};
+
+extern const struct virtio_usb_vq_desc dev_vqueues[VIRTIO_USB_VQ_DEV_MAX];
+
+int virtio_usb_dc_init(struct virtio_usb *vusb, int port_id);
+int virtio_usb_dc_deinit(struct virtio_usb *vusb);
+void virtio_usb_dc_event_work(struct work_struct *work);
+void virtio_usb_dc_data_work(struct work_struct *work);
+int virtio_usb_dc_event_populate(struct virtio_usb *vusb);
+#endif /* VIRTIO_USB_DC_H */
diff --git a/include/uapi/linux/virtio_usb.h b/include/uapi/linux/virtio_usb.h
index 459edc1..4cbfb3f 100644
--- a/include/uapi/linux/virtio_usb.h
+++ b/include/uapi/linux/virtio_usb.h
@@ -64,12 +64,11 @@ enum {
 	VIRTIO_USB_S_ERR_STALL,
 	VIRTIO_USB_S_ERR_SHORT_PKT,
 	VIRTIO_USB_S_ERR_CANCELLED,
-	VIRTIO_USB_S_ERR_HOST,
 };
 
 struct virtio_usb_cmd_status {
 	__le32 code; /* VIRTIO_USB_S_XXX */
-};
+} __packed;
 
 /*****************************************************************************
  * HOST COMMAND MESSAGES
@@ -145,6 +144,8 @@ enum {
 	VIRTIO_USB_CMD_DEV_EP_SET_HALT,
 	VIRTIO_USB_CMD_DEV_EP_SET_WEDGE,
 	VIRTIO_USB_CMD_DEV_CANCEL,
+	VIRTIO_USB_CMD_DEV_GET_FRAME_NUMBER,
+	VIRTIO_USB_CMD_DEV_SET_SELF_POWERED,
 };
 
 struct virtio_usb_dev_cmd_hdr {
@@ -153,12 +154,30 @@ struct virtio_usb_dev_cmd_hdr {
 	__le16 endpoint; /* Endpoint ID */
 };
 
+/* VIRTIO_USB_CMD_DEV_CANCEL */
+struct virtio_usb_dev_cmd_cancel {
+	struct virtio_usb_dev_cmd_hdr hdr;
+	__le64 tag;
+};
+
 /* VIRTIO_USB_CMD_DEV_GET_ENDPOINT_COUNT */
-struct virtio_usb_dev_cmd_ep_count {
+struct virtio_usb_dev_ep_count {
 	struct virtio_usb_cmd_status status;
 	__le32 count; /* # of supported endpoints */
 };
 
+struct virtio_usb_dev_cmd_set_value {
+	struct virtio_usb_dev_cmd_hdr hdr;
+	__le32 value;
+	__le32 padding;
+};
+
+/* VIRTIO_USB_CMD_DEV_GET_FRAME_NUMBER */
+struct virtio_usb_dev_frame_number {
+	struct virtio_usb_cmd_status status;
+	__le32 frame_number;
+};
+
 enum {
 	VIRTIO_USB_DIR_OUT = 0,
 	VIRTIO_USB_DIR_IN,
@@ -176,35 +195,39 @@ enum {
  *     struct virtio_usb_cmd_status
  *     struct virtio_usb_dev_cmd_ep_info [count]
  */
-struct virtio_usb_dev_cmd_ep_info {
+struct virtio_usb_dev_ep_info {
 	__le16 types; /* supported type bit map (1 << VIRTIO_USB_EP_XXX) */
 	__le16 directions /* supported direction bit map (1 << VIRTIO_USB_DIR_XXX) */;
 	__le16 maxpacket_limit;
 	__le16 max_streams;
+	__u8 name[16];
 };
 
 /*******************************************************************************
  * DEVICE EVENT MESSAGES
  */
 enum {
-	VIRTIO_USB_EVT_DEV_CONNECTED = 0,
+	VIRTIO_USB_EVT_DEV_BIND = 0,
 	VIRTIO_USB_EVT_DEV_DISCONNECTED,
 	VIRTIO_USB_EVT_DEV_SETUP,
 	VIRTIO_USB_EVT_DEV_RESET,
 	VIRTIO_USB_EVT_DEV_SUSPEND,
 	VIRTIO_USB_EVT_DEV_RESUME,
+	VIRTIO_USB_EVT_DEV_UNBIND,
 };
 
 struct virtio_usb_dev_event {
 	__le32 code; /* VIRTIO_USB_EVT_DEV_XXX */
-	__u8 padding[12];
+	__le16 port; /* Device ID */
+	__u8 padding[10];
 };
 
 /* VIRTIO_USB_EVT_DEV_SETUP */
 struct virtio_usb_dev_setup_event {
 	__le32 code; /* VIRTIO_USB_EVT_DEV_SETUP */
+	__le16 port; /* Device ID */
 	__u8 setup[8]; /* setup packet contents */
-	__u8 padding[4];
+	__u8 padding[2];
 };
 
 /*******************************************************************************

^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH 4/8] virtio-usb: add OTG role query support
  2026-09-24 16:08 [PATCH 0/8] virtio-usb: add dual-role virtio USB driver Igor Skalkin
                   ` (2 preceding siblings ...)
  2026-09-24 16:09 ` [PATCH 3/8] virtio-usb: add device role (USB Device " Igor Skalkin
@ 2026-09-24 16:09 ` Igor Skalkin
  2026-09-24 16:09 ` [PATCH 5/8] virtio-usb: add USB On-The-Go role-switching support Igor Skalkin
                   ` (4 subsequent siblings)
  8 siblings, 0 replies; 24+ messages in thread
From: Igor Skalkin @ 2026-09-24 16:09 UTC (permalink / raw)
  To: Michael S . Tsirkin, Jason Wang, Greg Kroah-Hartman
  Cc: virtualization, linux-usb, Vasilii Ianikeev, Aiswarya Cyriac,
	Anton Yakovlev, Trilok Soni, Igor Skalkin

With both host_role and device_role negotiated, a port's own role is
ambiguous (host|device) - determining it requires more than reading
the two feature bits. Introduce a minimal OTG command/event queue
pair (otg_vq_base, otg_vqueues[], otg_init()/otg_deinit()) and
otg_get_role(), a synchronous VIRTIO_USB_CMD_OTG_GET_ROLE request/
response exchange, and query every port's role individually during
probe instead of the alternative of overloading the config space
ports field with role bits.

This replaces the previous commit's DEVICE-only placeholder for the
both-roles-negotiated case with the real per-port answer.

Signed-off-by: Igor Skalkin <igor.skalkin@oss.qualcomm.com>
---
 drivers/usb/virtio_usb/Makefile     |    3 
 drivers/usb/virtio_usb/controller.c |   87 +++++++++++++++---
 drivers/usb/virtio_usb/controller.h |   11 ++
 drivers/usb/virtio_usb/otg.c        |  168 ++++++++++++++++++++++++++++++++++++
 drivers/usb/virtio_usb/otg.h        |   26 +++++
 5 files changed, 277 insertions(+), 18 deletions(-)
 create mode 100644 drivers/usb/virtio_usb/otg.c
 create mode 100644 drivers/usb/virtio_usb/otg.h

diff --git a/drivers/usb/virtio_usb/controller.c b/drivers/usb/virtio_usb/controller.c
index 0646807..59af5cc 100644
--- a/drivers/usb/virtio_usb/controller.c
+++ b/drivers/usb/virtio_usb/controller.c
@@ -12,6 +12,7 @@
 #include "controller.h"
 #include "host.h"
 #include "device.h"
+#include "otg.h"
 #include "vq_common.h"
 
 u32 virtio_usb_cmd_timeout_ms = MSEC_PER_SEC;
@@ -130,12 +131,12 @@ static int virtio_usb_probe(struct virtio_device *vdev)
 
 	/* Only allocate/negotiate the virtqueue triplets this instance
 	 * actually needs: HOST_* only exists when host_role is negotiated,
-	 * DEV_* only when device_role is negotiated. A pure single-role
-	 * instance therefore has exactly VIRTIO_USB_VQ_HOST_MAX (3) or
-	 * VIRTIO_USB_VQ_DEV_MAX (3) virtqueues, not a fixed layout - queues
-	 * that don't exist on the wire must not be created, since a peer
-	 * with no host-role VP has no host command/event/data queues to
-	 * negotiate at all.
+	 * DEV_* only when device_role is negotiated, OTG_* only when both
+	 * roles are negotiated. A pure single-role instance therefore has
+	 * exactly VIRTIO_USB_VQ_HOST_MAX (3) or VIRTIO_USB_VQ_DEV_MAX (3)
+	 * virtqueues, not a fixed layout - queues that don't exist on the
+	 * wire must not be created, since a peer with no host-role VP has
+	 * no host command/event/data queues to negotiate at all.
 	 */
 	vusb->host_vq_base = -1;
 	vusb->dev_vq_base = -1;
@@ -148,17 +149,15 @@ static int virtio_usb_probe(struct virtio_device *vdev)
 		vusb->dev_vq_base = nvqs;
 		nvqs += VIRTIO_USB_VQ_DEV_MAX;
 	}
-
-	/* Resolve every port's role. With only one role negotiated, every
-	 * port unambiguously has that role. With both negotiated, a port's
-	 * own role is ambiguous until a later commit adds an OTG-based
-	 * per-port query - default to DEVICE for now as a placeholder.
-	 */
-	for (i = 0; i < vusb->nports; i++) {
-		if (vusb->host_role && !vusb->device_role)
-			vusb->vports[i].role = VIRTIO_USB_ROLE_HOST;
-		else if (vusb->device_role)
-			vusb->vports[i].role = VIRTIO_USB_ROLE_DEVICE;
+	if (vusb->host_role && vusb->device_role) {
+		/* The OTG command/event queue pair is needed whenever both
+		 * roles are negotiated - it is how the driver asks each
+		 * port for its actual role via otg_get_role() below, since
+		 * a port's own role is otherwise ambiguous (host|device)
+		 * until then.
+		 */
+		vusb->otg_vq_base = nvqs;
+		nvqs += VIRTIO_USB_VQ_OTG_MAX;
 	}
 
 	vusb->vqueues = devm_kcalloc(&vdev->dev, nvqs, sizeof(*vusb->vqueues),
@@ -192,6 +191,18 @@ static int virtio_usb_probe(struct virtio_device *vdev)
 				dev_vqueues[i].stop;
 		}
 
+	if (vusb->host_role && vusb->device_role)
+		for (i = 0; i < VIRTIO_USB_VQ_OTG_MAX; i++) {
+			vusb->vqueues[vusb->otg_vq_base + i].name =
+				otg_vqueues[i].name;
+			vusb->vqueues[vusb->otg_vq_base + i].callback =
+				otg_vqueues[i].callback;
+			vusb->vqueues[vusb->otg_vq_base + i].process =
+				otg_vqueues[i].process;
+			vusb->vqueues[vusb->otg_vq_base + i].stop =
+				otg_vqueues[i].stop;
+		}
+
 	rc = virtio_usb_find_vqs(vusb);
 	if (rc) {
 		dev_err(&vdev->dev, "%s virtio_usb_find_vqs() error(%d)\n",
@@ -199,6 +210,46 @@ static int virtio_usb_probe(struct virtio_device *vdev)
 		goto on_error;
 	}
 
+	if (vusb->host_role && vusb->device_role) {
+		rc = otg_init(vusb);
+		if (rc) {
+			dev_err(&vdev->dev, "%s otg_init() error(%d)\n",
+				__func__, rc);
+			goto on_error;
+		}
+	}
+
+	/* Resolve every port's role. With only one role negotiated, every
+	 * port unambiguously has that role. With both negotiated, query
+	 * each port's actual role individually via otg_get_role(), since
+	 * it is otherwise ambiguous (host|device).
+	 */
+	for (i = 0; i < vusb->nports; i++) {
+		if (vusb->host_role && !vusb->device_role) {
+			vusb->vports[i].role = VIRTIO_USB_ROLE_HOST;
+		} else if (vusb->device_role && !vusb->host_role) {
+			vusb->vports[i].role = VIRTIO_USB_ROLE_DEVICE;
+		} else {
+			u32 status, role;
+
+			status = otg_get_role(vusb, i, &role);
+			if (status != VIRTIO_USB_S_OK) {
+				dev_err(&vdev->dev, "%s status(%d)\n", __func__,
+					status);
+				rc = -EIO;
+				goto on_error;
+			}
+			if (role != VIRTIO_USB_ROLE_HOST &&
+			    role != VIRTIO_USB_ROLE_DEVICE) {
+				dev_err(&vdev->dev, "%s port%d wrong role %d\n",
+					__func__, i, role);
+				rc = -EIO;
+				goto on_error;
+			}
+			vusb->vports[i].role = role;
+		}
+	}
+
 	if (vusb->host_role) {
 		INIT_WORK(&vusb->vq_host_data_rx_work, virtio_usb_hc_rx_work);
 		INIT_WORK(&vusb->vq_host_evt_work, virtio_usb_hc_evt_work);
@@ -286,6 +337,8 @@ static void virtio_usb_remove(struct virtio_device *vdev)
 	virtio_reset_device(vdev);
 
 	vdev->config->del_vqs(vdev);
+
+	otg_deinit(vusb);
 }
 
 static const unsigned int virtio_usb_features[] = {
diff --git a/drivers/usb/virtio_usb/controller.h b/drivers/usb/virtio_usb/controller.h
index ec59922..4d9e0e2 100644
--- a/drivers/usb/virtio_usb/controller.h
+++ b/drivers/usb/virtio_usb/controller.h
@@ -18,6 +18,8 @@
 struct virtio_usb_hc_vp;
 /* Forward declaration - full definition in device.h */
 struct virtio_usb_dc;
+/* Forward declaration - full definition in otg.h */
+struct virtio_usb_otg;
 
 #define VIRTIO_USB_VQ_COMMAND_IDX 0
 #define VIRTIO_USB_VQ_EVENT_IDX 1
@@ -25,6 +27,7 @@ struct virtio_usb_dc;
 
 #define VIRTIO_USB_VQ_HOST_MAX 3
 #define VIRTIO_USB_VQ_DEV_MAX 3
+#define VIRTIO_USB_VQ_OTG_MAX 2
 
 /**
  * struct virtio_usb_port - Per-virtual-port state.
@@ -54,6 +57,12 @@ struct virtio_usb_port {
  * @dev_vq_base: index into vqueues[] where the DEV_COMMAND/EVENT/DATA
  *               triplet starts, or -1 if this instance has no
  *               device-role VP.
+ * @otg_vq_base: index into vqueues[] where the OTG_COMMAND/EVENT pair
+ *               starts, or -1 if this instance has neither a host-role
+ *               nor a device-role VP. Used to query each port's role via
+ *               otg_get_role() below, since with both host_role and
+ *               device_role negotiated a port's own role is otherwise
+ *               ambiguous.
  * @vq_host_data_rx_work: Kernel work draining the host data queue, shared
  *                        across every host-role VP.
  * @vq_host_evt_work: Kernel work draining the host event queue, shared
@@ -73,10 +82,12 @@ struct virtio_usb {
 	bool device_role;
 	int host_vq_base;
 	int dev_vq_base;
+	int otg_vq_base;
 	struct work_struct vq_host_data_rx_work;
 	struct work_struct vq_host_evt_work;
 	struct work_struct vq_dev_data_rx_work;
 	struct work_struct vq_dev_event_work;
+	struct virtio_usb_otg *otg;
 };
 
 /**
diff --git a/drivers/usb/virtio_usb/Makefile b/drivers/usb/virtio_usb/Makefile
index 2222222..b7ee9e8 100644
--- a/drivers/usb/virtio_usb/Makefile
+++ b/drivers/usb/virtio_usb/Makefile
@@ -1,8 +1,9 @@
 # SPDX-License-Identifier: GPL-2.0-or-later
 
 virtio-usb-y := controller.o \
 	vq_common.o \
 	host.o \
-	device.o
+	device.o \
+	otg.o
 
 obj-$(CONFIG_USB_VIRTIO) += virtio-usb.o
diff --git a/drivers/usb/virtio_usb/otg.c b/drivers/usb/virtio_usb/otg.c
new file mode 100644
index 0000000..557dfae
--- /dev/null
+++ b/drivers/usb/virtio_usb/otg.c
@@ -0,0 +1,168 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * virtio_usb: VirtIO USB device
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <linux/mutex.h>
+#include "controller.h"
+#include "otg.h"
+#include "vq_common.h"
+
+int otg_init(struct virtio_usb *vusb)
+{
+	struct virtio_usb_otg *otg =
+		devm_kzalloc(&vusb->vdev->dev, sizeof(*otg), GFP_KERNEL);
+	unsigned int i;
+
+	if (!otg)
+		return -ENOMEM;
+
+	otg->vusb = vusb;
+	vusb->otg = otg;
+	for (i = 0; i < VIRTIO_USB_VQ_OTG_MAX; i++)
+		otg->oqs[i] = &vusb->vqueues[vusb->otg_vq_base + i];
+
+	mutex_init(&otg->lock);
+	init_completion(&otg->completion);
+
+	return 0;
+}
+
+void otg_deinit(struct virtio_usb *vusb)
+{
+	struct virtio_usb_otg *otg = vusb->otg;
+
+	if (!otg)
+		return;
+
+	/* Wake potential OTG command waiters before releasing OTG objects. */
+	complete_all(&otg->completion);
+
+	vusb->otg = NULL;
+}
+
+/* Send an OTG command and get a response.
+ *
+ * The function is implemented as synchronous. Design pattern is
+ * virtio_can.c/virtio_can_send_ctrl_msg()
+ */
+u32 otg_get_role(struct virtio_usb *vusb, int port_id, u32 *role)
+{
+	struct scatterlist sg_out, sg_in, *sgs[2] = { &sg_out, &sg_in };
+	struct virtqueue *vq =
+		vusb->otg->oqs[VIRTIO_USB_VQ_COMMAND_IDX]->vqueue;
+	unsigned int len;
+	u32 status = VIRTIO_USB_S_ERR_INTERNAL;
+
+	struct otg_get_role {
+		struct virtio_usb_otg_cmd_hdr cmd_hdr;
+		struct virtio_usb_otg_cmd_role cmd_role;
+	} *msg = kzalloc(sizeof(struct otg_get_role), GFP_KERNEL);
+
+	if (!msg)
+		return status;
+
+	msg->cmd_hdr.code = cpu_to_le32(VIRTIO_USB_CMD_OTG_GET_ROLE);
+	msg->cmd_hdr.port = cpu_to_le32(port_id);
+	sg_init_one(&sg_out, &msg->cmd_hdr, sizeof(msg->cmd_hdr));
+	sg_init_one(&sg_in, &msg->cmd_role, sizeof(msg->cmd_role));
+
+	mutex_lock(&vusb->otg->lock);
+
+	if (virtqueue_add_sgs(vq, sgs, 1u, 1u, msg, GFP_ATOMIC)) {
+		pr_err("%s virtqueue_add_sgs error\n", __func__);
+		goto exit;
+	}
+
+	if (!virtqueue_kick(vq)) {
+		pr_err("%s virtqueue_kick error\n", __func__);
+		goto exit;
+	}
+
+	while (!virtqueue_get_buf(vq, &len) && !virtqueue_is_broken(vq))
+		wait_for_completion(&vusb->otg->completion);
+
+	status = le32_to_cpu(msg->cmd_role.status.code);
+	*role = le32_to_cpu(msg->cmd_role.role);
+
+	if (*role != VIRTIO_USB_ROLE_HOST && *role != VIRTIO_USB_ROLE_DEVICE)
+		pr_err("%s - wrong role (%d)\n", __func__, *role);
+	else {
+		pr_info("%s otg_role %s\n", __func__,
+			*role == VIRTIO_USB_ROLE_HOST ?
+				"VIRTIO_USB_ROLE_HOST" :
+				"VIRTIO_USB_ROLE_DEVICE");
+	}
+
+exit:
+	kfree(msg);
+	mutex_unlock(&vusb->otg->lock);
+	return status;
+}
+
+static void virtio_usb_otg_cmd_notify_cb(struct virtqueue *vqueue)
+{
+	struct virtio_usb *vusb = vqueue->vdev->priv;
+
+	if (!vusb->otg)
+		return;
+
+	complete(&vusb->otg->completion);
+}
+
+static void virtio_usb_otg_cmdq_stop_cb(struct virtio_usb *vusb,
+					struct virtio_usb_queue *vq)
+{
+	unsigned long flags;
+
+	if (!vusb->otg || !vq->vqueue)
+		return;
+
+	/*
+	 * Wake sleepers in OTG synchronous command paths so they can
+	 * observe started=false and exit.
+	 */
+	complete_all(&vusb->otg->completion);
+
+	spin_lock_irqsave(&vq->lock, flags);
+	virtqueue_disable_cb(vq->vqueue);
+	spin_unlock_irqrestore(&vq->lock, flags);
+}
+
+static void virtio_usb_otg_evtq_stop_cb(struct virtio_usb *vusb,
+					struct virtio_usb_queue *vq)
+{
+	unsigned long flags;
+	u32 length;
+	void *buf;
+
+	if (!vq->vqueue)
+		return;
+
+	/* The OTG event queue is not populated yet at this stage (no
+	 * VIRTIO_USB_F_SWITCH_ROLE negotiation, no CHANGE_ROLE events),
+	 * so this only has to make sure del_vqs() finds the ring empty.
+	 */
+	spin_lock_irqsave(&vq->lock, flags);
+	virtqueue_disable_cb(vq->vqueue);
+	while ((buf = virtqueue_get_buf(vq->vqueue, &length)))
+		;
+	spin_unlock_irqrestore(&vq->lock, flags);
+}
+
+const struct virtio_usb_vq_desc otg_vqueues[VIRTIO_USB_VQ_OTG_MAX] = {
+	[VIRTIO_USB_VQ_COMMAND_IDX] = {
+		.callback = virtio_usb_otg_cmd_notify_cb,
+		.name = "virtusb-otg-cmd",
+		.process = NULL,
+		.stop = virtio_usb_otg_cmdq_stop_cb,
+	},
+	[VIRTIO_USB_VQ_EVENT_IDX] = {
+		.callback = NULL,
+		.name = "virtusb-otg-evt",
+		.process = NULL,
+		.stop = virtio_usb_otg_evtq_stop_cb,
+	},
+};
diff --git a/drivers/usb/virtio_usb/otg.h b/drivers/usb/virtio_usb/otg.h
new file mode 100644
index 0000000..a34317c
--- /dev/null
+++ b/drivers/usb/virtio_usb/otg.h
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * virtio_usb: VirtIO USB device
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef VIRTIO_USB_OTG_H
+#define VIRTIO_USB_OTG_H
+
+#include "controller.h"
+
+extern int otg_init(struct virtio_usb *vusb);
+extern void otg_deinit(struct virtio_usb *vusb);
+extern u32 otg_get_role(struct virtio_usb *vusb, int port_id, u32 *role);
+
+struct virtio_usb_otg {
+	struct virtio_usb *vusb;
+	struct mutex lock;
+	struct completion completion;
+	struct virtio_usb_queue *oqs[VIRTIO_USB_VQ_OTG_MAX];
+};
+
+extern const struct virtio_usb_vq_desc otg_vqueues[VIRTIO_USB_VQ_OTG_MAX];
+
+#endif /* VIRTIO_USB_OTG_H */

^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH 5/8] virtio-usb: add USB On-The-Go role-switching support
  2026-09-24 16:08 [PATCH 0/8] virtio-usb: add dual-role virtio USB driver Igor Skalkin
                   ` (3 preceding siblings ...)
  2026-09-24 16:09 ` [PATCH 4/8] virtio-usb: add OTG role query support Igor Skalkin
@ 2026-09-24 16:09 ` Igor Skalkin
  2026-09-24 16:09 ` [PATCH 6/8] virtio-usb: rework endpoint lifecycle to an async split-phase state machine Igor Skalkin
                   ` (3 subsequent siblings)
  8 siblings, 0 replies; 24+ messages in thread
From: Igor Skalkin @ 2026-09-24 16:09 UTC (permalink / raw)
  To: Michael S . Tsirkin, Jason Wang, Greg Kroah-Hartman
  Cc: virtualization, linux-usb, Vasilii Ianikeev, Aiswarya Cyriac,
	Anton Yakovlev, Trilok Soni, Igor Skalkin

Add support for ports that can switch between the host and device
roles at runtime (USB OTG-style role switching): negotiate
VIRTIO_USB_F_SWITCH_ROLE support during probe, extend otg_get_role()
to also report each port's supported roles (is_otg), and register a
USB Role Switch class device so userspace can observe and control the
role and be notified of role changes via VIRTIO_USB_EVT_OTG_CHANGE_ROLE.

This is a proof-of-concept for internal demo purposes; the two
directions currently work quite differently and neither is fully
guest-initiated:

- Device-to-host: the guest writes the new role to its own USB Role
  Switch sysfs entry. The driver sends a switch command to the host
  device; the host device performs the switch and sends back a
  VIRTIO_USB_EVT_OTG_CHANGE_ROLE event, which is what actually updates
  the guest's role.
- Host-to-device: cannot be initiated from the guest at all. The host
  kernel driver switches role on its own, either from an OTG interrupt
  or a manual sysfs role write on the host side. The host virtio
  device notices the USB port disappearing, checks the role via
  sysfs, and if it changed, sends the guest a
  VIRTIO_USB_EVT_OTG_CHANGE_ROLE event; the guest driver just reacts
  to it.

Real USB OTG separates this into two signals: bus_req, asserted by a
B-device (peripheral) to request becoming host, and bus_drop,
asserted by the current host to grant permission for the role swap -
without bus_drop, the peripheral's request cannot succeed.
VIRTIO_USB_CMD_OTG_SWITCH_ROLE only implements the bus_req side;
there is no bus_drop equivalent, so the current host's permission is
implicitly always granted. Fine for a first version; can be
revisited if a real use case needs the host to refuse a switch.

Signed-off-by: Igor Skalkin <igor.skalkin@oss.qualcomm.com>
---
 drivers/usb/virtio_usb/controller.c |   94 ++++++--
 drivers/usb/virtio_usb/controller.h |   21 +
 drivers/usb/virtio_usb/device.c     |    8 
 drivers/usb/virtio_usb/host.c       |   45 +++-
 drivers/usb/virtio_usb/otg.c        |  401 ++++++++++++++++++++++++++++--------
 drivers/usb/virtio_usb/otg.h        |   16 +
 include/uapi/linux/virtio_usb.h     |    1 
 7 files changed, 474 insertions(+), 112 deletions(-)

diff --git a/drivers/usb/virtio_usb/controller.c b/drivers/usb/virtio_usb/controller.c
index 59af5cc..c018725 100644
--- a/drivers/usb/virtio_usb/controller.c
+++ b/drivers/usb/virtio_usb/controller.c
@@ -10,8 +10,8 @@
 #include <uapi/linux/virtio_ids.h>
 
 #include "controller.h"
-#include "host.h"
 #include "device.h"
+#include "host.h"
 #include "otg.h"
 #include "vq_common.h"
 
@@ -86,6 +86,14 @@ static int virtio_usb_validate(struct virtio_device *vdev)
 		return -EINVAL;
 	}
 
+	if ((!(virtio_has_feature(vdev, VIRTIO_USB_F_HOST)) ||
+	     !(virtio_has_feature(vdev, VIRTIO_USB_F_DEVICE))) &&
+	    virtio_has_feature(vdev, VIRTIO_USB_F_SWITCH_ROLE)) {
+		dev_err(&vdev->dev,
+			"OTG requires both (host and device) roles support\n");
+		return -EINVAL;
+	}
+
 	if (!virtio_usb_cmd_timeout_ms) {
 		dev_err(&vdev->dev, "msg_timeout_ms value cannot be zero\n");
 		return -EINVAL;
@@ -129,6 +137,12 @@ static int virtio_usb_probe(struct virtio_device *vdev)
 	if (virtio_has_feature(vdev, VIRTIO_USB_F_DEVICE))
 		vusb->device_role = 1;
 
+	if (virtio_has_feature(vdev, VIRTIO_USB_F_SWITCH_ROLE))
+		vusb->switch_role = 1;
+
+	dev_info(&vdev->dev, "%s nports %d (h_role %d dev_role %d)\n", __func__,
+		 vusb->nports, vusb->host_role, vusb->device_role);
+
 	/* Only allocate/negotiate the virtqueue triplets this instance
 	 * actually needs: HOST_* only exists when host_role is negotiated,
 	 * DEV_* only when device_role is negotiated, OTG_* only when both
@@ -219,20 +233,44 @@ static int virtio_usb_probe(struct virtio_device *vdev)
 		}
 	}
 
+	if (vusb->device_role) {
+		INIT_WORK(&vusb->vq_dev_event_work, virtio_usb_dc_event_work);
+		INIT_WORK(&vusb->vq_dev_data_rx_work, virtio_usb_dc_data_work);
+
+		/* Populate the shared device event queue before telling the
+		 * backend we are ready, so a DEV_CONNECTED event can never
+		 * race ahead of the driver having posted receive buffers.
+		 */
+		rc = virtio_usb_dc_event_populate(vusb);
+		if (rc) {
+			dev_err(&vdev->dev,
+				"%s virtio_usb_dc_event_populate() error(%d)\n",
+				__func__, rc);
+			goto on_error;
+		}
+	}
+
+	virtio_device_ready(vdev);
+
 	/* Resolve every port's role. With only one role negotiated, every
 	 * port unambiguously has that role. With both negotiated, query
-	 * each port's actual role individually via otg_get_role(), since
-	 * it is otherwise ambiguous (host|device).
+	 * each port's actual role - and, if switch_role is negotiated,
+	 * whether the port supports switching - individually via
+	 * otg_get_role(), since a port's own role is otherwise ambiguous
+	 * (host|device).
 	 */
 	for (i = 0; i < vusb->nports; i++) {
+		vusb->vports[i].vusb = vusb;
+		spin_lock_init(&vusb->vports[i].vhc_lock);
+
 		if (vusb->host_role && !vusb->device_role) {
 			vusb->vports[i].role = VIRTIO_USB_ROLE_HOST;
 		} else if (vusb->device_role && !vusb->host_role) {
 			vusb->vports[i].role = VIRTIO_USB_ROLE_DEVICE;
 		} else {
-			u32 status, role;
+			u32 status, role, supported_role;
 
-			status = otg_get_role(vusb, i, &role);
+			status = otg_get_role(vusb, i, &role, &supported_role);
 			if (status != VIRTIO_USB_S_OK) {
 				dev_err(&vdev->dev, "%s status(%d)\n", __func__,
 					status);
@@ -247,9 +285,25 @@ static int virtio_usb_probe(struct virtio_device *vdev)
 				goto on_error;
 			}
 			vusb->vports[i].role = role;
+			vusb->vports[i].is_otg =
+				supported_role &
+				(1UL << VIRTIO_USB_F_SWITCH_ROLE);
+
+			if (vusb->vports[i].is_otg && !vusb->switch_role) {
+				dev_err(&vdev->dev,
+					"%s port%d switch_role not supported\n",
+					__func__, i);
+				rc = -EIO;
+				goto on_error;
+			}
 		}
 	}
 
+	for (i = 0; i < vusb->nports; i++)
+		dev_info(&vdev->dev, "%s port[%d] VIRTIO_USB_F_%s is_otg %s\n",
+			 __func__, i, vusb->vports[i].role ? "DEVICE" : "HOST",
+			 vusb->vports[i].is_otg ? "TRUE" : "FALSE");
+
 	if (vusb->host_role) {
 		INIT_WORK(&vusb->vq_host_data_rx_work, virtio_usb_hc_rx_work);
 		INIT_WORK(&vusb->vq_host_evt_work, virtio_usb_hc_evt_work);
@@ -278,13 +332,9 @@ static int virtio_usb_probe(struct virtio_device *vdev)
 		}
 	}
 
-	if (vusb->device_role) {
-		INIT_WORK(&vusb->vq_dev_data_rx_work, virtio_usb_dc_data_work);
-		INIT_WORK(&vusb->vq_dev_event_work, virtio_usb_dc_event_work);
-
-		for (i = 0; i < vusb->nports; i++) {
-			if (vusb->vports[i].role != VIRTIO_USB_ROLE_DEVICE)
-				continue;
+	for (i = 0; i < vusb->nports; i++) {
+		if (vusb->vports[i].is_otg ||
+		    vusb->vports[i].role == VIRTIO_USB_ROLE_DEVICE) {
 			rc = virtio_usb_dc_init(vusb, i);
 			if (rc) {
 				dev_err(&vdev->dev,
@@ -293,17 +343,17 @@ static int virtio_usb_probe(struct virtio_device *vdev)
 				goto on_error;
 			}
 		}
-		rc = virtio_usb_dc_event_populate(vusb);
-		if (rc) {
-			dev_err(&vdev->dev,
-				"%s virtio_usb_dc_event_populate() error(%d)\n",
-				__func__, rc);
-			goto on_error;
-		}
 	}
 
-	virtio_device_ready(vdev);
+	if (vusb->host_role && vusb->device_role)
+		if (otg_sysfs_init(vusb))
+			goto on_error;
+
+	WRITE_ONCE(vusb->started, true);
+	if (vusb->device_role)
+		schedule_work(&vusb->vq_dev_event_work);
 
+	dev_info(&vdev->dev, "%s returns %d\n", __func__, rc);
 	return rc;
 
 on_error:
@@ -323,6 +373,9 @@ static void virtio_usb_remove(struct virtio_device *vdev)
 	struct virtio_usb *vusb = vdev->priv;
 	int i;
 
+	/* Stop queue-driven workers from scheduling new activity. */
+	WRITE_ONCE(vusb->started, false);
+
 	for (i = 0; i < vusb->nvqs; i++)
 		vusb->vqueues[i].stop(vusb, &vusb->vqueues[i]);
 
@@ -344,6 +397,7 @@ static void virtio_usb_remove(struct virtio_device *vdev)
 static const unsigned int virtio_usb_features[] = {
 	VIRTIO_USB_F_HOST,
 	VIRTIO_USB_F_DEVICE,
+	VIRTIO_USB_F_SWITCH_ROLE,
 };
 
 static const struct virtio_device_id id_table[] = {
diff --git a/drivers/usb/virtio_usb/controller.h b/drivers/usb/virtio_usb/controller.h
index 4d9e0e2..deee053 100644
--- a/drivers/usb/virtio_usb/controller.h
+++ b/drivers/usb/virtio_usb/controller.h
@@ -20,6 +20,7 @@ struct virtio_usb_hc_vp;
 struct virtio_usb_dc;
 /* Forward declaration - full definition in otg.h */
 struct virtio_usb_otg;
+struct virtio_usb_port_otg;
 
 #define VIRTIO_USB_VQ_COMMAND_IDX 0
 #define VIRTIO_USB_VQ_EVENT_IDX 1
@@ -31,14 +32,27 @@ struct virtio_usb_otg;
 
 /**
  * struct virtio_usb_port - Per-virtual-port state.
+ * @vusb: VirtIO usb device this port belongs to.
+ * @is_otg: True if this port supports dynamic role switching.
  * @role: Role of this port (VIRTIO_USB_ROLE_HOST or _DEVICE).
  * @vhc: Host controller - non-NULL when role is HOST.
  * @vudc: Device controller - non-NULL when role is DEVICE.
+ * @otg: Per-port OTG role-switching state - non-NULL when is_otg.
  */
 struct virtio_usb_port {
+	struct virtio_usb *vusb;
+	bool is_otg;
 	unsigned int role;
+	/*! Protects vhc against concurrent OTG-triggered init/deinit while
+	 * the host event-processing worker is reading it. Only needs to
+	 * guard the pointer itself: virtio_usb_hc_vp_init()/_deinit()'s
+	 * sleeping work (devm_kzalloc(), usb_add_hcd(), usb_remove_hcd())
+	 * happens outside this lock.
+	 */
+	spinlock_t vhc_lock;
 	struct virtio_usb_hc_vp *vhc;
 	struct virtio_usb_dc *vudc;
+	struct virtio_usb_port_otg *otg;
 };
 
 /**
@@ -63,6 +77,9 @@ struct virtio_usb_port {
  *               otg_get_role() below, since with both host_role and
  *               device_role negotiated a port's own role is otherwise
  *               ambiguous.
+ * @switch_role: flag indicating support for dynamically switching roles
+ * @started: True once probe() has finished bringing up every VP - guards
+ *           notify callbacks against running before setup is complete.
  * @vq_host_data_rx_work: Kernel work draining the host data queue, shared
  *                        across every host-role VP.
  * @vq_host_evt_work: Kernel work draining the host event queue, shared
@@ -76,13 +93,15 @@ struct virtio_usb {
 	struct virtio_device *vdev;
 	struct virtio_usb_queue *vqueues;
 	struct virtio_usb_port *vports;
-	unsigned int nports;
+	u32 nports;
 	u32 nvqs;
 	bool host_role;
 	bool device_role;
 	int host_vq_base;
 	int dev_vq_base;
 	int otg_vq_base;
+	bool switch_role;
+	bool started;
 	struct work_struct vq_host_data_rx_work;
 	struct work_struct vq_host_evt_work;
 	struct work_struct vq_dev_data_rx_work;
diff --git a/drivers/usb/virtio_usb/device.c b/drivers/usb/virtio_usb/device.c
index 798c265..5ae0fc8 100644
--- a/drivers/usb/virtio_usb/device.c
+++ b/drivers/usb/virtio_usb/device.c
@@ -1056,6 +1056,8 @@ static void virtio_usb_dc_event_process(struct virtio_usb_event *event)
 		break;
 	}
 	case VIRTIO_USB_EVT_DEV_DISCONNECTED: {
+		pr_info("%s port_id %d VIRTIO_USB_EVT_DEV_DISCONNECTED\n",
+			__func__, port_id);
 		if (vudc->driver && vudc->driver->disconnect)
 			vudc->driver->disconnect(&vudc->gadget);
 		if (vudc->registered)
@@ -1080,16 +1082,19 @@ static void virtio_usb_dc_event_process(struct virtio_usb_event *event)
 		break;
 	}
 	case VIRTIO_USB_EVT_DEV_RESET: {
+		pr_info("%s VIRTIO_USB_EVT_DEV_RESET\n", __func__);
 		if (driver)
 			usb_gadget_udc_reset(&vudc->gadget, driver);
 		break;
 	}
 	case VIRTIO_USB_EVT_DEV_SUSPEND: {
+		pr_info("%s VIRTIO_USB_EVT_DEV_SUSPEND\n", __func__);
 		if (driver && driver->suspend)
 			driver->suspend(&vudc->gadget);
 		break;
 	}
 	case VIRTIO_USB_EVT_DEV_RESUME: {
+		pr_info("%s VIRTIO_USB_EVT_DEV_RESUME\n", __func__);
 		if (driver && driver->resume)
 			driver->resume(&vudc->gadget);
 		break;
@@ -1308,6 +1313,9 @@ static void virtio_usb_dc_evt_notify_cb(struct virtqueue *vqueue)
 {
 	struct virtio_usb *vusb = vqueue->vdev->priv;
 
+	if (!READ_ONCE(vusb->started))
+		return;
+
 	schedule_work(&vusb->vq_dev_event_work);
 }
 
diff --git a/drivers/usb/virtio_usb/host.c b/drivers/usb/virtio_usb/host.c
index 9926e66..5dc47f3 100644
--- a/drivers/usb/virtio_usb/host.c
+++ b/drivers/usb/virtio_usb/host.c
@@ -1013,12 +1013,16 @@ int virtio_usb_hc_vp_init(struct virtio_usb *vusb, unsigned int vp_idx)
 		vhcd_vp->hcqs[i] = &vusb->vqueues[vusb->host_vq_base + i];
 
 	/* Install into the port before add_hcd so vhcd_vp->vusb is set */
+	spin_lock(&vusb->vports[vp_idx].vhc_lock);
 	vusb->vports[vp_idx].vhc = vhcd_vp;
+	spin_unlock(&vusb->vports[vp_idx].vhc_lock);
 
 	/* Add HCDs first so hs/ss are valid before any PORT_CONNECTED event */
 	rc = virtio_usb_add_hcd(vusb, vhcd_vp);
 	if (rc) {
+		spin_lock(&vusb->vports[vp_idx].vhc_lock);
 		vusb->vports[vp_idx].vhc = NULL;
+		spin_unlock(&vusb->vports[vp_idx].vhc_lock);
 		return rc;
 	}
 
@@ -1032,7 +1036,11 @@ int virtio_usb_hc_vp_init(struct virtio_usb *vusb, unsigned int vp_idx)
  */
 int virtio_usb_hc_vp_deinit(struct virtio_usb *vusb, unsigned int vp_idx)
 {
-	struct virtio_usb_hc_vp *vhcd_vp = vusb->vports[vp_idx].vhc;
+	struct virtio_usb_hc_vp *vhcd_vp;
+
+	spin_lock(&vusb->vports[vp_idx].vhc_lock);
+	vhcd_vp = vusb->vports[vp_idx].vhc;
+	spin_unlock(&vusb->vports[vp_idx].vhc_lock);
 
 	if (!vhcd_vp)
 		return 0;
@@ -1045,7 +1053,9 @@ int virtio_usb_hc_vp_deinit(struct virtio_usb *vusb, unsigned int vp_idx)
 	vhcd_vp->ss = NULL;
 	vhcd_vp->hs = NULL;
 
+	spin_lock(&vusb->vports[vp_idx].vhc_lock);
 	vusb->vports[vp_idx].vhc = NULL;
+	spin_unlock(&vusb->vports[vp_idx].vhc_lock);
 	return 0;
 }
 
@@ -1085,7 +1095,18 @@ static void virtio_usb_hc_evt_process_one(struct virtio_usb_event *uevent)
 		return;
 	}
 
+	/* vhc can be concurrently init/deinit'd by an OTG role switch
+	 * (virtio_usb_otg_event_process() runs on its own workqueue) -
+	 * vhc_lock protects only the pointer read itself; the vhcd_vp it
+	 * points to remains valid for as long as we hold a reference to
+	 * it here, since virtio_usb_hc_vp_deinit() only clears the
+	 * vports[vp_idx].vhc pointer under the same lock, it does not
+	 * free vhcd_vp itself (devm-managed, freed at device teardown).
+	 */
+	spin_lock(&vusb->vports[vp_idx].vhc_lock);
 	vhcd_vp = vusb->vports[vp_idx].vhc;
+	spin_unlock(&vusb->vports[vp_idx].vhc_lock);
+
 	if (!vhcd_vp) {
 		dev_err_ratelimited(
 			&vusb->vdev->dev,
@@ -1174,10 +1195,12 @@ static void virtio_usb_hc_evt_process_one(struct virtio_usb_event *uevent)
  * @work: kernel work item embedded in struct virtio_usb.
  *
  * The host event queue is shared across all host-role VPs and its VP
- * may not even exist yet at probe time (e.g. a dual-role instance
- * where every port currently reports device role), so events are
- * drained and processed here, in process context, rather than
- * directly inside the interrupt-context notify callback.
+ * may not even exist yet (or may be concurrently torn down by an OTG
+ * role switch), so events are drained and processed here, in process
+ * context, instead of directly inside the interrupt-context notify
+ * callback - this lets virtio_usb_hc_evt_process_one() safely take
+ * vhc_lock without needing an atomic-context-safe primitive on the
+ * writer side (virtio_usb_hc_vp_init()/_deinit() sleep).
  *
  * Context: Process context.
  */
@@ -1215,7 +1238,11 @@ static void virtio_usb_hc_dataq_stop_cb(struct virtio_usb *vusb,
 	cancel_work_sync(&vusb->vq_host_data_rx_work);
 
 	for (vp_idx = 0; vp_idx < vusb->nports; vp_idx++) {
-		struct virtio_usb_hc_vp *vhcd_vp = vusb->vports[vp_idx].vhc;
+		struct virtio_usb_hc_vp *vhcd_vp;
+
+		spin_lock(&vusb->vports[vp_idx].vhc_lock);
+		vhcd_vp = vusb->vports[vp_idx].vhc;
+		spin_unlock(&vusb->vports[vp_idx].vhc_lock);
 
 		if (!vhcd_vp)
 			continue;
@@ -1284,6 +1311,9 @@ static void virtio_usb_hc_evt_notify_cb(struct virtqueue *vqueue)
 {
 	struct virtio_usb *vusb = vqueue->vdev->priv;
 
+	if (!READ_ONCE(vusb->started))
+		return;
+
 	schedule_work(&vusb->vq_host_evt_work);
 }
 
@@ -1310,6 +1340,9 @@ static void virtio_usb_host_data_notify_cb(struct virtqueue *vqueue)
 {
 	struct virtio_usb *vusb = vqueue->vdev->priv;
 
+	if (!READ_ONCE(vusb->started))
+		return;
+
 	schedule_work(&vusb->vq_host_data_rx_work);
 }
 
diff --git a/include/uapi/linux/virtio_usb.h b/include/uapi/linux/virtio_usb.h
index 4cbfb3f..29cec5d 100644
--- a/include/uapi/linux/virtio_usb.h
+++ b/include/uapi/linux/virtio_usb.h
@@ -318,6 +318,7 @@ enum {
 struct virtio_usb_otg_cmd_role {
 	struct virtio_usb_cmd_status status;
 	__le32 role; /* VIRTIO_USB_ROLE_XXX */
+	__le32 supported_role; /* VIRTIO_USB_F_[HOST|DEVICE|SWITCH_ROLE] */
 };
 
 /*****************************************************************************
diff --git a/drivers/usb/virtio_usb/otg.c b/drivers/usb/virtio_usb/otg.c
index 557dfae..f0f390f 100644
--- a/drivers/usb/virtio_usb/otg.c
+++ b/drivers/usb/virtio_usb/otg.c
@@ -7,11 +7,238 @@
 
 #include <linux/mutex.h>
 #include "controller.h"
+#include "host.h"
 #include "otg.h"
 #include "vq_common.h"
 
+// for usb_role enum from include/linux/usb/role.h
+const char *role_switch_role_names[] = { "USB_ROLE_NONE", "USB_ROLE_HOST",
+					 "USB_ROLE_DEVICE" };
+
+static int vusb_set_role_cb(struct usb_role_switch *sw, enum usb_role role)
+{
+	struct virtio_usb_port *vport = usb_role_switch_get_drvdata(sw);
+
+	pr_info("%s set_role to %s\n", __func__, role_switch_role_names[role]);
+	mutex_lock(&vport->otg->lock);
+
+	if (role == USB_ROLE_HOST && vport->role == VIRTIO_USB_ROLE_DEVICE) {
+		pr_warn("%s we are in device role, switch to host\n", __func__);
+		schedule_work(&vport->otg->set_role_work);
+	}
+	mutex_unlock(&vport->otg->lock);
+	return 0;
+}
+
+static enum usb_role vusb_get_role_cb(struct usb_role_switch *sw)
+{
+	struct virtio_usb_port *vport = usb_role_switch_get_drvdata(sw);
+
+	switch (vport->role) {
+	case VIRTIO_USB_ROLE_HOST:
+		return USB_ROLE_HOST;
+	case VIRTIO_USB_ROLE_DEVICE:
+		return USB_ROLE_DEVICE;
+	default:
+		return USB_ROLE_NONE;
+	}
+}
+
+static void virtio_usb_otg_event_process(struct virtio_usb_event *event)
+{
+	struct virtio_usb *vusb = event->vusb;
+	struct virtio_usb_otg_event *evt = virtio_usb_event_buf(event);
+	int port_id = le32_to_cpu(evt->port);
+	struct virtio_usb_port *vport = &vusb->vports[port_id];
+	u32 status, role, supported_role, old_role;
+	int rc;
+
+	if (le32_to_cpu(evt->code) != VIRTIO_USB_EVT_OTG_CHANGE_ROLE) {
+		pr_err("%s wrong event code %d\n", __func__,
+		       le32_to_cpu(evt->code));
+		return;
+	}
+
+	old_role = vport->role;
+	pr_info("%s VIRTIO_USB_EVT_OTG_CHANGE_ROLE curr_role %s\n", __func__,
+		old_role == VIRTIO_USB_ROLE_DEVICE ? "VIRTIO_USB_ROLE_DEVICE" :
+						     "VIRTIO_USB_ROLE_HOST");
+
+	status = otg_get_role(vusb, port_id, &role, &supported_role);
+	if (status != VIRTIO_USB_S_OK) {
+		pr_err("%s otg_get_role status %d\n", __func__, status);
+		return;
+	}
+
+	/* A port's usb_hcd/root hub is only supposed to exist while that
+	 * port is actually in host role - it's immediately visible to the
+	 * rest of the kernel (lsusb, udev, ...) the moment usb_add_hcd()
+	 * runs, unlike the device-role side's vudc, which stays dormant
+	 * and kernel-invisible until a later BIND event. So, unlike vudc
+	 * (already unconditionally pre-allocated for every OTG-capable
+	 * port at probe time, regardless of its current role - see
+	 * virtio_usb_probe()), vhc must be created/destroyed dynamically,
+	 * exactly at the moment a port's role actually changes.
+	 */
+	if (role != old_role && role == VIRTIO_USB_ROLE_HOST) {
+		rc = virtio_usb_hc_vp_init(vusb, port_id);
+		if (rc) {
+			pr_err("%s virtio_usb_hc_vp_init() port=%d error(%d)\n",
+			       __func__, port_id, rc);
+			/* Leave vport->role at its old value: reporting a
+			 * host role with no working HCD behind it would be
+			 * worse than not switching at all.
+			 */
+			return;
+		}
+	} else if (role != old_role && old_role == VIRTIO_USB_ROLE_HOST) {
+		virtio_usb_hc_vp_deinit(vusb, port_id);
+	}
+
+	vport->role = role;
+	vport->is_otg = supported_role & (1UL << VIRTIO_USB_F_SWITCH_ROLE);
+	pr_info("%s The new role (%s) is set\n", __func__,
+		role == VIRTIO_USB_ROLE_DEVICE ? "VIRTIO_USB_ROLE_DEVICE" :
+						 "VIRTIO_USB_ROLE_HOST");
+}
+
+/**
+ * virtio_usb_otg_event_work() - OTG event queue receive worker.
+ * @work: Kernel work to handle event completion.
+ *
+ * Context: Process context.
+ */
+static void virtio_usb_otg_event_work(struct work_struct *work)
+{
+	struct virtio_usb_otg *otg =
+		container_of(work, struct virtio_usb_otg, event_process_work);
+	struct virtio_usb_queue *evtq = otg->oqs[VIRTIO_USB_VQ_EVENT_IDX];
+
+	virtio_usb_evt_work(evtq, virtio_usb_otg_event_process);
+}
+
+/**
+ * virtio_usb_otg_set_role_work() - OTG send command to device worker.
+ *
+ * Context: Process context.
+ */
+static void virtio_usb_otg_set_role_work(struct work_struct *work)
+{
+	struct virtio_usb_port_otg *vport_otg =
+		container_of(work, struct virtio_usb_port_otg, set_role_work);
+	struct virtio_usb_otg *otg = vport_otg->vusb->otg;
+	struct virtio_usb_otg_cmd_hdr *hdr;
+	struct virtio_usb_cmd *cmd;
+	int rc;
+
+	cmd = virtio_usb_cmd_alloc(
+		sizeof(*hdr), sizeof(struct virtio_usb_cmd_status), GFP_KERNEL);
+	if (!cmd)
+		return;
+
+	hdr = virtio_usb_cmd_request(cmd);
+	hdr->code = cpu_to_le32(VIRTIO_USB_CMD_OTG_SWITCH_ROLE);
+	hdr->port = cpu_to_le32(vport_otg->port_id);
+	cmd->msg.queue = otg->oqs[VIRTIO_USB_VQ_COMMAND_IDX];
+
+	mutex_lock(&otg->lock);
+	rc = virtio_usb_cmd_send_sync(vport_otg->vusb, NULL, NULL, cmd);
+	mutex_unlock(&otg->lock);
+
+	if (rc)
+		pr_err("%s virtio_usb_cmd_send_sync() error %d\n", __func__,
+		       rc);
+	else
+		pr_info("%s success\n", __func__);
+}
+
+/**
+ * virtio_usb_otg_event_populate() - Add events to the otg event queue.
+ * @vusb: VirtIO USB device.
+ *
+ * Context: Any context.
+ * Return: 0 on success -errno on failure.
+ */
+static int virtio_usb_otg_event_populate(struct virtio_usb *vusb)
+{
+	struct virtio_usb_queue *otg_evt_queue =
+		vusb->otg->oqs[VIRTIO_USB_VQ_EVENT_IDX];
+	struct virtio_usb_event *events;
+
+	events = virtio_usb_events_alloc(vusb, otg_evt_queue,
+					 sizeof(struct virtio_usb_otg_event));
+	if (!events)
+		return -ENOMEM;
+
+	return virtio_usb_events_populate(events);
+}
+
+static void otg_vports_deinit(struct virtio_usb *vusb)
+{
+	if (!vusb->vports)
+		return;
+
+	for (int i = 0; i < vusb->nports; ++i) {
+		struct virtio_usb_port *vport = &vusb->vports[i];
+		/* is_otg is set before otg_sysfs_init() runs (which is
+		 * where vport->otg actually gets allocated) - a probe
+		 * failure in between the two would otherwise leave
+		 * is_otg true but otg still NULL here.
+		 */
+		if (!vport->is_otg || !vport->otg)
+			continue;
+
+		cancel_work_sync(&vport->otg->set_role_work);
+		usb_role_switch_unregister(vport->otg->sw);
+		vport->otg->sw = NULL;
+		kfree(vport->otg->sysfs_name);
+		vport->otg->sysfs_name = NULL;
+	}
+}
+
+static int otg_vport_init(struct virtio_usb *vusb, int port_id)
+{
+	struct device *dev = &vusb->vdev->dev;
+	struct virtio_usb_port *vport = &vusb->vports[port_id];
+	struct virtio_usb_port_otg *vport_otg;
+	struct usb_role_switch_desc desc = { 0 };
+
+	vport_otg = devm_kzalloc(dev, sizeof(*vport_otg), GFP_KERNEL);
+	if (!vport_otg)
+		return -ENOMEM;
+
+	vport_otg->vusb = vusb;
+	vport_otg->port_id = port_id;
+	vport_otg->sysfs_name = kasprintf(GFP_KERNEL, "port%u", port_id);
+
+	mutex_init(&vport_otg->lock);
+	INIT_WORK(&vport_otg->set_role_work, virtio_usb_otg_set_role_work);
+
+	desc.name = vport_otg->sysfs_name;
+	desc.set = vusb_set_role_cb;
+	desc.get = vusb_get_role_cb;
+	desc.driver_data = vport;
+	desc.fwnode = NULL;
+	desc.allow_userspace_control = true;
+
+	vport_otg->sw = usb_role_switch_register(dev, &desc);
+	if (IS_ERR(vport_otg->sw)) {
+		int rc = PTR_ERR(vport_otg->sw);
+		dev_err(dev, "port%u: usb_role_switch_register() failed: %d\n",
+			port_id, rc);
+		vport_otg->sw = NULL;
+		kfree(vport_otg->sysfs_name);
+		vport_otg->sysfs_name = NULL;
+		return rc;
+	}
+
+	vport->otg = vport_otg;
+	return 0;
+}
+
 int otg_init(struct virtio_usb *vusb)
 {
+	int rc;
 	struct virtio_usb_otg *otg =
 		devm_kzalloc(&vusb->vdev->dev, sizeof(*otg), GFP_KERNEL);
 	unsigned int i;
@@ -24,12 +251,39 @@ int otg_init(struct virtio_usb *vusb)
 	for (i = 0; i < VIRTIO_USB_VQ_OTG_MAX; i++)
 		otg->oqs[i] = &vusb->vqueues[vusb->otg_vq_base + i];
 
+	INIT_WORK((struct work_struct *)&otg->event_process_work,
+		  virtio_usb_otg_event_work);
+
 	mutex_init(&otg->lock);
-	init_completion(&otg->completion);
+
+	rc = virtio_usb_otg_event_populate(vusb);
+	if (rc)
+		return rc;
 
 	return 0;
 }
 
+int otg_sysfs_init(struct virtio_usb *vusb)
+{
+	int i, rc;
+
+	for (i = 0; i < vusb->nports; i++)
+		if (vusb->vports[i].is_otg) {
+			/* Skip if already registered - probe may be retried
+			 * after a Virtio reset and the role switch device
+			 * persists across retries (devm allocation).
+			 * Calling usb_role_switch_register() again would
+			 * return -EEXIST from device_register().
+			 */
+			if (vusb->vports[i].otg)
+				continue;
+			rc = otg_vport_init(vusb, i);
+			if (rc)
+				return rc;
+		}
+	return 0;
+}
+
 void otg_deinit(struct virtio_usb *vusb)
 {
 	struct virtio_usb_otg *otg = vusb->otg;
@@ -37,55 +291,75 @@ void otg_deinit(struct virtio_usb *vusb)
 	if (!otg)
 		return;
 
-	/* Wake potential OTG command waiters before releasing OTG objects. */
-	complete_all(&otg->completion);
-
+	/*
+	 * event_process_work is already cancelled by
+	 * virtio_usb_otg_evtq_stop_cb(), called earlier in
+	 * virtio_usb_remove()'s vqueues[i].stop() loop.
+	 */
+	otg_vports_deinit(vusb);
 	vusb->otg = NULL;
 }
 
+/**
+ * virtio_usb_otg_evt_notify_cb() - OTG Event virtqueue notification callback
+ * @vqueue: Underlying event virtqueue.
+ *
+ * This callback function is called upon a vring interrupt request from the
+ * device.
+ *
+ * Context: Interrupt context.
+ */
+static void virtio_usb_otg_evt_notify_cb(struct virtqueue *vqueue)
+{
+	struct virtio_usb *vusb = vqueue->vdev->priv;
+
+	if (!READ_ONCE(vusb->started) || !vusb->otg)
+		return;
+
+	schedule_work(&vusb->otg->event_process_work);
+}
+
 /* Send an OTG command and get a response.
  *
  * The function is implemented as synchronous. Design pattern is
  * virtio_can.c/virtio_can_send_ctrl_msg()
  */
-u32 otg_get_role(struct virtio_usb *vusb, int port_id, u32 *role)
+u32 otg_get_role(struct virtio_usb *vusb, int port_id, u32 *role,
+		 u32 *supported_role)
 {
-	struct scatterlist sg_out, sg_in, *sgs[2] = { &sg_out, &sg_in };
-	struct virtqueue *vq =
-		vusb->otg->oqs[VIRTIO_USB_VQ_COMMAND_IDX]->vqueue;
-	unsigned int len;
+	struct virtio_usb_otg_cmd_hdr *hdr;
+	struct virtio_usb_otg_cmd_role *resp;
+	struct virtio_usb_cmd *cmd;
 	u32 status = VIRTIO_USB_S_ERR_INTERNAL;
+	int rc;
 
-	struct otg_get_role {
-		struct virtio_usb_otg_cmd_hdr cmd_hdr;
-		struct virtio_usb_otg_cmd_role cmd_role;
-	} *msg = kzalloc(sizeof(struct otg_get_role), GFP_KERNEL);
-
-	if (!msg)
+	cmd = virtio_usb_cmd_alloc(sizeof(*hdr), sizeof(*resp), GFP_KERNEL);
+	if (!cmd)
 		return status;
 
-	msg->cmd_hdr.code = cpu_to_le32(VIRTIO_USB_CMD_OTG_GET_ROLE);
-	msg->cmd_hdr.port = cpu_to_le32(port_id);
-	sg_init_one(&sg_out, &msg->cmd_hdr, sizeof(msg->cmd_hdr));
-	sg_init_one(&sg_in, &msg->cmd_role, sizeof(msg->cmd_role));
+	hdr = virtio_usb_cmd_request(cmd);
+	hdr->code = cpu_to_le32(VIRTIO_USB_CMD_OTG_GET_ROLE);
+	hdr->port = cpu_to_le32(port_id);
+	cmd->msg.queue = vusb->otg->oqs[VIRTIO_USB_VQ_COMMAND_IDX];
 
-	mutex_lock(&vusb->otg->lock);
+	virtio_usb_cmd_ref(cmd);
 
-	if (virtqueue_add_sgs(vq, sgs, 1u, 1u, msg, GFP_ATOMIC)) {
-		pr_err("%s virtqueue_add_sgs error\n", __func__);
-		goto exit;
-	}
+	mutex_lock(&vusb->otg->lock);
+	rc = virtio_usb_cmd_send_sync(vusb, NULL, NULL, cmd);
+	mutex_unlock(&vusb->otg->lock);
 
-	if (!virtqueue_kick(vq)) {
-		pr_err("%s virtqueue_kick error\n", __func__);
-		goto exit;
+	if (rc) {
+		pr_err("%s virtio_usb_cmd_send_sync() error %d\n", __func__,
+		       rc);
+		virtio_usb_cmd_unref(cmd);
+		return status;
 	}
 
-	while (!virtqueue_get_buf(vq, &len) && !virtqueue_is_broken(vq))
-		wait_for_completion(&vusb->otg->completion);
-
-	status = le32_to_cpu(msg->cmd_role.status.code);
-	*role = le32_to_cpu(msg->cmd_role.role);
+	resp = virtio_usb_cmd_response(cmd);
+	status = le32_to_cpu(resp->status.code);
+	*role = le32_to_cpu(resp->role);
+	*supported_role = le32_to_cpu(resp->supported_role);
+	virtio_usb_cmd_unref(cmd);
 
 	if (*role != VIRTIO_USB_ROLE_HOST && *role != VIRTIO_USB_ROLE_DEVICE)
 		pr_err("%s - wrong role (%d)\n", __func__, *role);
@@ -96,71 +370,32 @@ u32 otg_get_role(struct virtio_usb *vusb, int port_id, u32 *role)
 				"VIRTIO_USB_ROLE_DEVICE");
 	}
 
-exit:
-	kfree(msg);
-	mutex_unlock(&vusb->otg->lock);
 	return status;
 }
 
-static void virtio_usb_otg_cmd_notify_cb(struct virtqueue *vqueue)
-{
-	struct virtio_usb *vusb = vqueue->vdev->priv;
-
-	if (!vusb->otg)
-		return;
-
-	complete(&vusb->otg->completion);
-}
-
-static void virtio_usb_otg_cmdq_stop_cb(struct virtio_usb *vusb,
-					struct virtio_usb_queue *vq)
-{
-	unsigned long flags;
-
-	if (!vusb->otg || !vq->vqueue)
-		return;
-
-	/*
-	 * Wake sleepers in OTG synchronous command paths so they can
-	 * observe started=false and exit.
-	 */
-	complete_all(&vusb->otg->completion);
-
-	spin_lock_irqsave(&vq->lock, flags);
-	virtqueue_disable_cb(vq->vqueue);
-	spin_unlock_irqrestore(&vq->lock, flags);
-}
-
 static void virtio_usb_otg_evtq_stop_cb(struct virtio_usb *vusb,
 					struct virtio_usb_queue *vq)
 {
-	unsigned long flags;
-	u32 length;
-	void *buf;
-
-	if (!vq->vqueue)
-		return;
-
-	/* The OTG event queue is not populated yet at this stage (no
-	 * VIRTIO_USB_F_SWITCH_ROLE negotiation, no CHANGE_ROLE events),
-	 * so this only has to make sure del_vqs() finds the ring empty.
+	/*
+	 * Now that otg_get_role()/virtio_usb_otg_set_role_work() go
+	 * through the common, timeout-protected virtio_usb_cmd_send_sync()
+	 * instead of blocking indefinitely on virtqueue_is_broken(), it's
+	 * safe to cancel event_process_work directly here, same as every
+	 * other role's own evtq stop callback.
 	 */
-	spin_lock_irqsave(&vq->lock, flags);
-	virtqueue_disable_cb(vq->vqueue);
-	while ((buf = virtqueue_get_buf(vq->vqueue, &length)))
-		;
-	spin_unlock_irqrestore(&vq->lock, flags);
+	virtio_usb_evt_drain_stop_cb(
+		vq, vusb->otg ? &vusb->otg->event_process_work : NULL);
 }
 
 const struct virtio_usb_vq_desc otg_vqueues[VIRTIO_USB_VQ_OTG_MAX] = {
 	[VIRTIO_USB_VQ_COMMAND_IDX] = {
-		.callback = virtio_usb_otg_cmd_notify_cb,
+		.callback = virtio_usb_cmd_notify_cb,
 		.name = "virtusb-otg-cmd",
-		.process = NULL,
-		.stop = virtio_usb_otg_cmdq_stop_cb,
+		.process = virtio_usb_cmd_process_cb,
+		.stop = virtio_usb_cmdq_stop_cb,
 	},
 	[VIRTIO_USB_VQ_EVENT_IDX] = {
-		.callback = NULL,
+		.callback = virtio_usb_otg_evt_notify_cb,
 		.name = "virtusb-otg-evt",
 		.process = NULL,
 		.stop = virtio_usb_otg_evtq_stop_cb,
diff --git a/drivers/usb/virtio_usb/otg.h b/drivers/usb/virtio_usb/otg.h
index a34317c..85e9b9e 100644
--- a/drivers/usb/virtio_usb/otg.h
+++ b/drivers/usb/virtio_usb/otg.h
@@ -8,17 +8,29 @@
 #ifndef VIRTIO_USB_OTG_H
 #define VIRTIO_USB_OTG_H
 
+#include <linux/usb/role.h>
 #include "controller.h"
 
 extern int otg_init(struct virtio_usb *vusb);
+extern int otg_sysfs_init(struct virtio_usb *vusb);
 extern void otg_deinit(struct virtio_usb *vusb);
-extern u32 otg_get_role(struct virtio_usb *vusb, int port_id, u32 *role);
+extern u32 otg_get_role(struct virtio_usb *vusb, int port_id, u32 *role,
+			u32 *supported_role);
+
+struct virtio_usb_port_otg {
+	struct virtio_usb *vusb;
+	struct mutex lock;
+	struct usb_role_switch *sw;
+	char *sysfs_name;
+	struct work_struct set_role_work;
+	int port_id;
+};
 
 struct virtio_usb_otg {
 	struct virtio_usb *vusb;
 	struct mutex lock;
-	struct completion completion;
 	struct virtio_usb_queue *oqs[VIRTIO_USB_VQ_OTG_MAX];
+	struct work_struct event_process_work;
 };
 
 extern const struct virtio_usb_vq_desc otg_vqueues[VIRTIO_USB_VQ_OTG_MAX];

^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH 6/8] virtio-usb: rework endpoint lifecycle to an async split-phase state machine
  2026-09-24 16:08 [PATCH 0/8] virtio-usb: add dual-role virtio USB driver Igor Skalkin
                   ` (4 preceding siblings ...)
  2026-09-24 16:09 ` [PATCH 5/8] virtio-usb: add USB On-The-Go role-switching support Igor Skalkin
@ 2026-09-24 16:09 ` Igor Skalkin
  2026-09-24 16:09 ` [PATCH 7/8] virtio-usb: add SuperSpeed device-role support Igor Skalkin
                   ` (2 subsequent siblings)
  8 siblings, 0 replies; 24+ messages in thread
From: Igor Skalkin @ 2026-09-24 16:09 UTC (permalink / raw)
  To: Michael S . Tsirkin, Jason Wang, Greg Kroah-Hartman
  Cc: virtualization, linux-usb, Vasilii Ianikeev, Aiswarya Cyriac,
	Anton Yakovlev, Trilok Soni, Igor Skalkin

The USB gadget API's usb_ep_ops/usb_gadget_ops callbacks are documented
as atomic - they must not sleep. This driver's endpoint and command
handling needs to send a virtio command and wait for the backend's
response, which is exactly what those callbacks cannot do.

We previously worked around this with a small out-of-tree patch to
the gadget core itself: a gadget.nonatomic flag that, when set, swapped
the UDC core's spinlocks for mutexes so a driver could sleep in these
callbacks. That patch was never going to pass upstream review, so it
had to go - which meant this driver needed to actually honor the
atomic contract instead of relying on a modified core.

This commit is that fix: an explicit endpoint state machine
(EP_DISABLED, EP_ENABLING, EP_ENABLED, EP_HALTED) plus per-UDC/
per-request work items and a pending-request queue, so every
usb_ep_ops/usb_gadget_ops callback returns immediately and the actual
virtio round trip happens asynchronously on a workqueue.
ep_enable()/ep_disable()/set_selfpowered()/vbus_draw() all move to
this split-phase pattern; ep_queue()/ep_dequeue() are reworked on top
of the same state machine. gadget.nonatomic itself is removed, since
nothing needs it anymore.

Also fixes a handful of bugs found while doing this: preserve
-ECONNRESET/-ESHUTDOWN completion statuses instead of always
overwriting them, support early-completed requests without a double
giveback(), serialize SETUP handling against in-flight EP0 completions
and still-EP_ENABLING endpoints to avoid overlapping control transfers
exposed by dummy_hcd's tighter timing, and fix EP0's descriptor
initialization and a spinlock-unbalance bug in ep_enable().

Signed-off-by: Igor Skalkin <igor.skalkin@oss.qualcomm.com>
---
 drivers/usb/virtio_usb/device.c |  774 +++++++++++++++++++++++++++++++---------
 drivers/usb/virtio_usb/device.h |   24 +
 2 files changed, 639 insertions(+), 159 deletions(-)

diff --git a/drivers/usb/virtio_usb/device.c b/drivers/usb/virtio_usb/device.c
index 5ae0fc8..9ee1f05 100644
--- a/drivers/usb/virtio_usb/device.c
+++ b/drivers/usb/virtio_usb/device.c
@@ -11,6 +11,7 @@
 #include "device.h"
 
 #define GADGET_NAME "virtio_usb_dc"
+#define VIRTIO_USB_EP0_IDLE_TIMEOUT_MS 5000
 
 /**
  * struct virtio_usb_dc_priv - Device controller data priv
@@ -23,6 +24,8 @@ struct virtio_usb_dc_priv {
 	struct usb_request req;
 	struct virtio_usb_ep *vep;
 	struct virtio_usb_data *vreq;
+	struct work_struct cancel_work;
+	bool completed_early;
 };
 
 /**
@@ -72,6 +75,34 @@ static struct virtio_usb_data *usb_req_to_virtio_data(struct usb_request *req)
 	return priv->vreq;
 }
 
+static bool virtio_usb_dc_req_queue_empty(struct virtio_usb_ep *vep)
+{
+	struct virtio_usb_dc *vudc = vep->vudc;
+	unsigned long flags;
+	bool empty;
+
+	spin_lock_irqsave(&vudc->lock, flags);
+	empty = list_empty(&vep->req_queue);
+	spin_unlock_irqrestore(&vudc->lock, flags);
+
+	return empty;
+}
+
+static int virtio_usb_dc_wait_ep0_idle(struct virtio_usb_dc *vudc)
+{
+	struct virtio_usb_ep *vep = usb_ep_to_virtio_ep(vudc->gadget.ep0);
+	long ret;
+
+	if (virtio_usb_dc_req_queue_empty(vep))
+		return 0;
+
+	ret = wait_event_timeout(
+		vudc->ep0_idle, virtio_usb_dc_req_queue_empty(vep),
+		msecs_to_jiffies(VIRTIO_USB_EP0_IDLE_TIMEOUT_MS));
+
+	return ret ? 0 : -ETIMEDOUT;
+}
+
 /**
  * virtio_usb_dc_complete_req() - Completes a usb request
  * @vreq: virtio usb data message.
@@ -89,6 +120,8 @@ static void virtio_usb_dc_complete_req(struct virtio_usb_data *vreq)
 	struct virtio_usb_data *vreq_iter;
 	unsigned int found = 0;
 	unsigned long flags;
+	bool wake_ep0 = false;
+	bool early;
 
 	spin_lock_irqsave(&vudc->lock, flags);
 
@@ -98,10 +131,15 @@ static void virtio_usb_dc_complete_req(struct virtio_usb_data *vreq)
 			break;
 		}
 	}
+	early = priv->completed_early;
 	if (!found) {
 		spin_unlock_irqrestore(&vudc->lock, flags);
+		/* Arrived after ep_disable() already gave back */
+		if (early)
+			virtio_usb_data_unref(vreq);
 		return;
 	}
+	/* Common case - request still belongs to in-flight queue */
 	if (req->status != -ECONNRESET && req->status != -ESHUTDOWN) {
 		req->status = virtio_error_to_usb(status);
 		if (!req->status)
@@ -109,9 +147,17 @@ static void virtio_usb_dc_complete_req(struct virtio_usb_data *vreq)
 	}
 
 	list_del_init(&vreq->list);
+	wake_ep0 = !vep->ep_id && list_empty(&vep->req_queue);
 	spin_unlock_irqrestore(&vudc->lock, flags);
+	if (wake_ep0)
+		wake_up(&vudc->ep0_idle);
+
+	/* Normal completion: giveback now */
+	if (!early)
+		usb_gadget_giveback_request(&vep->ep, req);
+
+	/* Drop final reference */
 	virtio_usb_data_unref(vreq);
-	usb_gadget_giveback_request(&vep->ep, req);
 }
 
 /**
@@ -128,22 +174,24 @@ void virtio_usb_dc_data_work(struct work_struct *work)
 	struct virtio_usb_queue *dataq =
 		&vusb->vqueues[vusb->dev_vq_base + VIRTIO_USB_VQ_DATA_IDX];
 	struct virtio_usb_data *vreq;
+	unsigned long flags;
 	unsigned int length;
 
-	spin_lock_irq(&dataq->lock);
+	spin_lock_irqsave(&dataq->lock, flags);
 	do {
 		virtqueue_disable_cb(dataq->vqueue);
 		while ((vreq = virtqueue_get_buf(dataq->vqueue, &length))) {
-			spin_unlock_irq(&dataq->lock);
+			spin_unlock_irqrestore(&dataq->lock, flags);
 			virtio_usb_dc_complete_req(vreq);
-			spin_lock_irq(&dataq->lock);
+			spin_lock_irqsave(&dataq->lock, flags);
 		}
 		if (unlikely(virtqueue_is_broken(dataq->vqueue)))
 			break;
 	} while (!virtqueue_enable_cb(dataq->vqueue));
-	spin_unlock_irq(&dataq->lock);
+	spin_unlock_irqrestore(&dataq->lock, flags);
 }
 
+static void virtio_usb_dc_cancel_work(struct work_struct *work);
 /**
  * virtio_usb_dc_data_alloc() - Allocate and initialize a device controller
  * data message.
@@ -174,6 +222,8 @@ static struct virtio_usb_data *virtio_usb_dc_data_alloc(struct usb_ep *ep,
 	priv = virtio_usb_data_priv(vreq);
 	priv->vep = vep;
 	priv->vreq = vreq;
+	priv->completed_early = false;
+	INIT_WORK(&priv->cancel_work, virtio_usb_dc_cancel_work);
 	vreq->msg.queue =
 		&vep->vudc->vusb->vqueues[vep->vudc->vusb->dev_vq_base +
 					  VIRTIO_USB_VQ_DATA_IDX];
@@ -242,6 +292,264 @@ virtio_usb_dc_cmd_alloc(struct virtio_usb_dc *vudc, unsigned int command,
 
 /* Endpoint callbacks */
 
+static int do_real_queue(struct virtio_usb_ep *vep, struct usb_request *req)
+{
+	struct virtio_usb_dc *vudc = vep->vudc;
+	struct virtio_usb_data *vreq = usb_req_to_virtio_data(req);
+	struct virtio_usb_dc_priv *priv = virtio_usb_data_priv(vreq);
+	struct virtio_usb_request *request;
+	struct virtio_usb_response *response;
+	struct scatterlist *out_sgs = NULL, *in_sgs = NULL, *psg_data = NULL;
+	struct scatterlist sg;
+	unsigned long flags;
+	u16 endpoint;
+	u16 transfer_flags = 0;
+	u16 transfer_type = VIRTIO_USB_EP_CONTROL;
+	int rc;
+
+	virtio_usb_data_ref(vreq);
+
+	request = virtio_usb_data_request(vreq);
+	response = virtio_usb_data_response(vreq);
+	response->actual_length = cpu_to_le32(0);
+	response->status = cpu_to_le32(VIRTIO_USB_S_ERR_INTERNAL);
+
+	if (req->short_not_ok)
+		transfer_flags |= VIRTIO_USB_FLAG_SHORT_NOT_OK;
+	else if (req->zero)
+		transfer_flags |= VIRTIO_USB_FLAG_ZERO_PACKET;
+	request->transfer_flags = cpu_to_le16(transfer_flags);
+
+	endpoint = vep->ep_id |
+		   (usb_ep_dir_in(&vep->ep) ? VIRTIO_USB_EP_DIR_IN :
+					      VIRTIO_USB_EP_DIR_OUT);
+	request->endpoint = cpu_to_le16(endpoint);
+	request->port = cpu_to_le16(vudc->port);
+	if (vep->ep_id && vep->ep.desc) {
+		switch (usb_endpoint_type(vep->ep.desc)) {
+		case USB_ENDPOINT_XFER_ISOC:
+			transfer_type = VIRTIO_USB_EP_ISOCHRONOUS;
+			break;
+		case USB_ENDPOINT_XFER_BULK:
+			transfer_type = VIRTIO_USB_EP_BULK;
+			break;
+		case USB_ENDPOINT_XFER_INT:
+			transfer_type = VIRTIO_USB_EP_INTERRUPT;
+			break;
+		default:
+			transfer_type = VIRTIO_USB_EP_CONTROL;
+			break;
+		}
+	}
+	request->transfer_type = cpu_to_le16(transfer_type);
+
+	if (req->length && req->buf) {
+		psg_data = &sg;
+		sg_init_one(psg_data, req->buf, req->length);
+	} else if (req->length && req->num_sgs > 0) {
+		psg_data = req->sg;
+	} else if (req->length && req->sg) {
+		psg_data = &sg;
+		sg_init_one(psg_data, sg_virt(req->sg), req->length);
+	} else {
+		psg_data = NULL;
+	}
+
+	if (usb_ep_dir_in(&vep->ep))
+		out_sgs = psg_data;
+	else
+		in_sgs = psg_data;
+
+	spin_lock_irqsave(&vudc->lock, flags);
+	priv->completed_early = false;
+	req->actual = 0;
+	req->status = -EINPROGRESS;
+	list_add_tail(&vreq->list, &vep->req_queue);
+	spin_unlock_irqrestore(&vudc->lock, flags);
+
+	rc = virtio_usb_data_send(vudc->vusb, vreq, out_sgs, in_sgs);
+	if (rc) {
+		spin_lock_irqsave(&vudc->lock, flags);
+		list_del_init(&vreq->list);
+		spin_unlock_irqrestore(&vudc->lock, flags);
+
+		virtio_usb_data_unref(vreq);
+		return rc;
+	}
+
+	return 0;
+}
+
+static bool virtio_usb_dc_ep_enabling_locked(struct virtio_usb_dc *vudc)
+{
+	u32 i;
+
+	for (i = 1; i < vudc->neps; i++) {
+		if (vudc->veps[i].state == EP_ENABLING)
+			return true;
+	}
+
+	return false;
+}
+
+static void virtio_usb_dc_flush_ep0_pend(struct virtio_usb_dc *vudc)
+{
+	struct virtio_usb_ep *vep = &vudc->veps[0];
+	struct virtio_usb_data *vreq;
+	unsigned long flags;
+
+	spin_lock_irqsave(&vudc->lock, flags);
+	if (virtio_usb_dc_ep_enabling_locked(vudc)) {
+		spin_unlock_irqrestore(&vudc->lock, flags);
+		return;
+	}
+
+	while (!list_empty(&vep->pend_queue)) {
+		struct virtio_usb_dc_priv *priv;
+		int qrc;
+
+		vreq = list_first_entry(&vep->pend_queue,
+					struct virtio_usb_data, list);
+		priv = virtio_usb_data_priv(vreq);
+		list_del_init(&vreq->list);
+		spin_unlock_irqrestore(&vudc->lock, flags);
+
+		qrc = do_real_queue(vep, &priv->req);
+		virtio_usb_data_unref(vreq);
+		if (qrc) {
+			priv->req.status = qrc;
+			usb_gadget_giveback_request(&vep->ep, &priv->req);
+		}
+
+		spin_lock_irqsave(&vudc->lock, flags);
+	}
+	spin_unlock_irqrestore(&vudc->lock, flags);
+}
+
+static bool
+virtio_usb_ep_comp_valid(const struct usb_ss_ep_comp_descriptor *comp)
+{
+	return comp && comp->bLength == USB_DT_SS_EP_COMP_SIZE &&
+	       comp->bDescriptorType == USB_DT_SS_ENDPOINT_COMP;
+}
+
+static void vep_enable_work(struct work_struct *work)
+{
+	struct virtio_usb_ep *vep =
+		container_of(work, struct virtio_usb_ep, enable_work);
+	struct virtio_usb_dc *vudc = vep->vudc;
+	const struct usb_endpoint_descriptor *desc = vep->ep.desc;
+	struct scatterlist sg;
+	struct scatterlist *psg_data = &sg;
+	struct virtio_usb_dev_cmd_hdr *hdr;
+	struct virtio_usb_cmd *cmd;
+	unsigned long flags;
+	u16 endpoint;
+	int rc;
+
+	if (!desc)
+		return;
+
+	endpoint = vep->ep_id |
+		   (usb_ep_dir_in(&vep->ep) ? VIRTIO_USB_EP_DIR_IN :
+					      VIRTIO_USB_EP_DIR_OUT);
+
+	cmd = virtio_usb_dc_cmd_alloc(vudc, VIRTIO_USB_CMD_DEV_EP_ENABLE,
+				      GFP_KERNEL);
+	if (!cmd) {
+		rc = -ENOMEM;
+		goto done;
+	}
+
+	sg_init_one(psg_data, desc, sizeof(*desc));
+	hdr = virtio_usb_cmd_request(cmd);
+	hdr->endpoint = cpu_to_le16(endpoint);
+
+	rc = virtio_usb_cmd_send_sync(vudc->vusb, psg_data, NULL, cmd);
+
+done:
+	spin_lock_irqsave(&vudc->lock, flags);
+	if (!rc) {
+		vep->state = EP_ENABLED;
+		vep->last_err = 0;
+		/* Flush pend_queue */
+		while (!list_empty(&vep->pend_queue)) {
+			struct virtio_usb_data *vreq = list_first_entry(
+				&vep->pend_queue, struct virtio_usb_data, list);
+			struct virtio_usb_dc_priv *priv =
+				virtio_usb_data_priv(vreq);
+			int qrc;
+
+			list_del_init(&vreq->list);
+			spin_unlock_irqrestore(&vudc->lock, flags);
+			qrc = do_real_queue(vep, &priv->req);
+			/*
+			 * do_real_queue() takes its own ref for the
+			 * in-flight/vring lifetime; drop the ref that kept
+			 * vreq alive while it was parked in pend_queue.
+			 */
+			virtio_usb_data_unref(vreq);
+			if (qrc) {
+				/*
+				 * Request was accepted while EP was ENABLING.
+				 * If forwarding now fails, complete it with
+				 * error instead of silently dropping it.
+				 */
+				priv->req.status = qrc;
+				usb_gadget_giveback_request(&vep->ep,
+							    &priv->req);
+			}
+			spin_lock_irqsave(&vudc->lock, flags);
+		}
+	} else {
+		vep->state = EP_HALTED;
+		vep->last_err = rc;
+
+		while (!list_empty(&vep->pend_queue)) {
+			struct virtio_usb_data *vreq = list_first_entry(
+				&vep->pend_queue, struct virtio_usb_data, list);
+			struct virtio_usb_dc_priv *priv =
+				virtio_usb_data_priv(vreq);
+
+			list_del_init(&vreq->list);
+			priv->req.status = rc;
+			spin_unlock_irqrestore(&vudc->lock, flags);
+			virtio_usb_data_unref(vreq);
+			usb_gadget_giveback_request(&vep->ep, &priv->req);
+			spin_lock_irqsave(&vudc->lock, flags);
+		}
+	}
+	spin_unlock_irqrestore(&vudc->lock, flags);
+	virtio_usb_dc_flush_ep0_pend(vudc);
+}
+
+static void vep_disable_work(struct work_struct *work)
+{
+	struct virtio_usb_ep *vep =
+		container_of(work, struct virtio_usb_ep, disable_work);
+	struct virtio_usb_dc *vudc = vep->vudc;
+	struct virtio_usb_cmd *cmd;
+	struct virtio_usb_dev_cmd_hdr *hdr;
+	u16 endpoint;
+
+	if (!READ_ONCE(vudc->driver) || !vudc->registered)
+		return;
+
+	endpoint = vep->ep_id |
+		   (usb_ep_dir_in(&vep->ep) ? VIRTIO_USB_EP_DIR_IN :
+					      VIRTIO_USB_EP_DIR_OUT);
+
+	cmd = virtio_usb_dc_cmd_alloc(vudc, VIRTIO_USB_CMD_DEV_EP_DISABLE,
+				      GFP_KERNEL);
+	if (!cmd)
+		return;
+
+	hdr = virtio_usb_cmd_request(cmd);
+	hdr->endpoint = cpu_to_le16(endpoint);
+
+	(void)virtio_usb_cmd_send_sync(vudc->vusb, NULL, NULL, cmd);
+}
+
 /**
  * virtio_ep_enable() - Enable endpoint
  * This callback is called to configure endpoint and make it usable.
@@ -256,44 +564,30 @@ static int virtio_ep_enable(struct usb_ep *ep,
 {
 	struct virtio_usb_ep *vep = usb_ep_to_virtio_ep(ep);
 	struct virtio_usb_dc *vudc = vep->vudc;
-	struct scatterlist sg;
-	struct scatterlist *psg_data = &sg;
-	struct virtio_usb_dev_cmd_hdr *hdr;
-	struct virtio_usb_cmd *cmd;
 	unsigned long flags;
-	u16 endpoint;
-	int rc;
-
-	if (!vudc->driver)
-		return -ESHUTDOWN;
 
 	spin_lock_irqsave(&vudc->lock, flags);
 
-	if (!ep || !desc || ep->caps.type_control ||
+	if (!vudc->driver || !ep || !desc || ep->caps.type_control ||
 	    desc->bDescriptorType != USB_DT_ENDPOINT) {
 		spin_unlock_irqrestore(&vudc->lock, flags);
 		return -EINVAL;
 	}
 
+	if (vep->state != EP_DISABLED) {
+		spin_unlock_irqrestore(&vudc->lock, flags);
+		return -EBUSY;
+	}
+
 	ep->desc = desc;
 	ep->maxpacket = usb_endpoint_maxp(desc);
-	endpoint = vep->ep_id | (usb_ep_dir_in(ep) ? VIRTIO_USB_EP_DIR_IN :
-						     VIRTIO_USB_EP_DIR_OUT);
-
-	spin_unlock_irqrestore(&vudc->lock, flags);
-
-	cmd = virtio_usb_dc_cmd_alloc(vudc, VIRTIO_USB_CMD_DEV_EP_ENABLE,
-				      GFP_ATOMIC);
-	if (!cmd)
-		return -ENOMEM;
+	vep->state = EP_ENABLING;
 
-	sg_init_one(psg_data, desc, sizeof(*desc));
-	hdr = virtio_usb_cmd_request(cmd);
-	hdr->endpoint = cpu_to_le16(endpoint);
+	queue_work(vudc->ep_cmd_wq, &vep->enable_work);
 
-	rc = virtio_usb_cmd_send_sync(vudc->vusb, psg_data, NULL, cmd);
+	spin_unlock_irqrestore(&vudc->lock, flags);
 
-	return rc;
+	return 0;
 }
 
 /**
@@ -311,42 +605,82 @@ static int virtio_ep_disable(struct usb_ep *ep)
 	struct virtio_usb_data *vreq = NULL, *vreq_tmp;
 	struct virtio_usb_dc *vudc = vep->vudc;
 	struct virtio_usb_dc_priv *priv = NULL;
-	struct virtio_usb_dev_cmd_hdr *hdr;
-	struct virtio_usb_cmd *cmd;
+	LIST_HEAD(giveback_inflight);
+	LIST_HEAD(giveback_pended);
 	unsigned long flags;
-	u16 endpoint;
-	int rc;
 
-	if (!ep || ep->caps.type_control)
+	if (!ep || ep->caps.type_control) // EP0 not called disable
 		return -EINVAL;
 
 	spin_lock_irqsave(&vudc->lock, flags);
 
+	if (vep->state == EP_ENABLING) {
+		/*
+		 * cancel_work_sync() can sleep and must not be called while
+		 * holding vudc->lock: vep_enable_work() only re-acquires
+		 * this same lock after its (possibly blocking) virtio
+		 * command completes, so holding the lock here across
+		 * cancel_work_sync() would deadlock against it.
+		 */
+		spin_unlock_irqrestore(&vudc->lock, flags);
+		cancel_work_sync(&vep->enable_work);
+		spin_lock_irqsave(&vudc->lock, flags);
+	}
+
+	if (vep->state == EP_DISABLED) {
+		spin_unlock_irqrestore(&vudc->lock, flags);
+		return 0;
+	}
+
+	vep->state = EP_DISABLED;
+
+	/*
+	 * All pending (waiting for the ENABLING) - put to local list for
+	 * giveback(-ESHUTDOWN). These requests are not in the virtqueue,
+	 * can be unref now.
+	 */
+	list_for_each_entry_safe(vreq, vreq_tmp, &vep->pend_queue, list) {
+		list_del_init(&vreq->list);
+		list_add_tail(&vreq->list, &giveback_pended);
+	}
+
+	/*
+	 * in-flight requests - make early giveback, remove from req_queue,
+	 * mark completed_early, but do not unref: unref will be in complete
+	 * handler.
+	 */
 	list_for_each_entry_safe(vreq, vreq_tmp, &vep->req_queue, list) {
 		priv = virtio_usb_data_priv(vreq);
-		/**
-		 * When endpoint is disabled, completion handler for all pending
-		 * requests will be called. Make the request status to -ESHUTDOWN
-		 * to prevent requests completes even before the endpoint disable
-		 * is send to the controller.
-		 */
 		priv->req.status = -ESHUTDOWN;
+		priv->completed_early = true;
+		list_del_init(&vreq->list);
+		list_add_tail(&vreq->list, &giveback_inflight);
 	}
-	endpoint = vep->ep_id | (usb_ep_dir_in(ep) ? VIRTIO_USB_EP_DIR_IN :
-						     VIRTIO_USB_EP_DIR_OUT);
+
 	spin_unlock_irqrestore(&vudc->lock, flags);
 
-	cmd = virtio_usb_dc_cmd_alloc(vudc, VIRTIO_USB_CMD_DEV_EP_DISABLE,
-				      GFP_ATOMIC);
-	if (!cmd)
-		return -ENOMEM;
+	/* pending: just giveback and unref */
+	list_for_each_entry_safe(vreq, vreq_tmp, &giveback_pended, list) {
+		struct virtio_usb_dc_priv *p = virtio_usb_data_priv(vreq);
 
-	hdr = virtio_usb_cmd_request(cmd);
-	hdr->endpoint = cpu_to_le16(endpoint);
+		list_del_init(&vreq->list);
+		p->req.status = -ESHUTDOWN;
+		usb_gadget_giveback_request(&vep->ep, &p->req);
+		virtio_usb_data_unref(
+			vreq); /* drop the pend_queue ref taken in virtio_ep_queue() */
+	}
 
-	rc = virtio_usb_cmd_send_sync(vudc->vusb, NULL, NULL, cmd);
+	/* in-flight: giveback now, unref will be in complete_req */
+	list_for_each_entry_safe(vreq, vreq_tmp, &giveback_inflight, list) {
+		struct virtio_usb_dc_priv *p = virtio_usb_data_priv(vreq);
 
-	return rc;
+		list_del_init(&vreq->list);
+		usb_gadget_giveback_request(&vep->ep, &p->req);
+	}
+
+	queue_work(vudc->ep_cmd_wq, &vep->disable_work);
+
+	return 0;
 }
 
 /**
@@ -395,75 +729,52 @@ static void virtio_ep_free_request(struct usb_ep *ep, struct usb_request *req)
 static int virtio_ep_queue(struct usb_ep *ep, struct usb_request *req,
 			   gfp_t mem_flags)
 {
-	struct scatterlist *out_sgs = NULL, *in_sgs = NULL, *psg_data;
 	struct virtio_usb_data *vreq = usb_req_to_virtio_data(req);
 	struct virtio_usb_ep *vep = usb_ep_to_virtio_ep(ep);
 	struct virtio_usb_dc *vudc = vep->vudc;
-	struct virtio_usb_response *response;
-	struct virtio_usb_request *request;
-	u16 transfer_flags = 0;
-	struct scatterlist sg;
 	unsigned long flags;
-	u16 endpoint;
-	int rc;
-
-	virtio_usb_data_ref(vreq);
+	int rc = 0;
 
 	spin_lock_irqsave(&vudc->lock, flags);
-	req->actual = 0;
-	req->status = -EINPROGRESS;
-
-	list_add_tail(&vreq->list, &vep->req_queue);
-	endpoint = vep->ep_id | (usb_ep_dir_in(ep) ? VIRTIO_USB_EP_DIR_IN :
-						     VIRTIO_USB_EP_DIR_OUT);
-	spin_unlock_irqrestore(&vudc->lock, flags);
-
-	request = virtio_usb_data_request(vreq);
-	response = virtio_usb_data_response(vreq);
 
-	response->actual_length = cpu_to_le32(0);
-	response->status = cpu_to_le32(VIRTIO_USB_S_ERR_INTERNAL);
+	switch (vep->state) {
+	case EP_ENABLED:
+		if (!vep->ep_id && virtio_usb_dc_ep_enabling_locked(vudc)) {
+			virtio_usb_data_ref(vreq);
+			req->actual = 0;
+			req->status = -EINPROGRESS;
+			list_add_tail(&vreq->list, &vep->pend_queue);
+			spin_unlock_irqrestore(&vudc->lock, flags);
+			return 0;
+		}
+		spin_unlock_irqrestore(&vudc->lock, flags);
+		rc = do_real_queue(vep, req);
+		/* if rc==0 - unref will be in completion, on failure
+		 * do_real_queue() has already dropped its own ref
+		 */
+		return rc;
 
-	if (req->short_not_ok)
-		transfer_flags |= VIRTIO_USB_FLAG_SHORT_NOT_OK;
-	else if (req->zero)
-		transfer_flags |= VIRTIO_USB_FLAG_ZERO_PACKET;
+	case EP_ENABLING:
+		/* Waiting for ENABLED state */
+		virtio_usb_data_ref(vreq);
+		req->actual = 0;
+		req->status = -EINPROGRESS;
+		list_add_tail(&vreq->list, &vep->pend_queue);
+		spin_unlock_irqrestore(&vudc->lock, flags);
+		// Additional ref waited for the enable_work()
+		return 0;
 
-	request->transfer_flags = cpu_to_le16(transfer_flags);
-	request->endpoint = cpu_to_le16(endpoint);
-	request->port = cpu_to_le16(vudc->port);
+	case EP_HALTED:
+		spin_unlock_irqrestore(&vudc->lock, flags);
+		req->status = vep->last_err ? vep->last_err : -EPIPE;
+		usb_gadget_giveback_request(&vep->ep, req);
+		return req->status;
 
-	if (req->length && req->buf) {
-		psg_data = &sg;
-		sg_init_one(psg_data, req->buf, req->length);
-	} else if (req->length && req->num_sgs > 0) {
-		psg_data = req->sg;
-	} else if (req->sg) {
-		psg_data = &sg;
-		sg_init_one(psg_data, sg_virt(req->sg), req->length);
-	} else {
-		psg_data = NULL;
+	case EP_DISABLED:
+	default:
+		spin_unlock_irqrestore(&vudc->lock, flags);
+		return -ESHUTDOWN;
 	}
-
-	if (usb_ep_dir_in(ep))
-		out_sgs = psg_data;
-	else
-		in_sgs = psg_data;
-
-	rc = virtio_usb_data_send(vudc->vusb, vreq, out_sgs, in_sgs);
-	if (rc)
-		goto on_error_vq;
-
-	return rc;
-
-on_error_vq:
-	spin_lock_irqsave(&vudc->lock, flags);
-	list_del_init(&vreq->list);
-	spin_unlock_irqrestore(&vudc->lock, flags);
-
-	virtio_usb_data_unref(vreq);
-
-	return rc;
 }
 
 /**
@@ -477,20 +788,32 @@ on_error_vq:
 static int virtio_ep_dequeue(struct usb_ep *ep, struct usb_request *req)
 {
 	struct virtio_usb_ep *vep = usb_ep_to_virtio_ep(ep);
-	struct virtio_usb_dev_cmd_cancel *cancel;
 	struct virtio_usb_dc *vudc = vep->vudc;
 	struct virtio_usb_dc_priv *priv;
 	struct virtio_usb_data *vreq;
-	struct virtio_usb_cmd *cmd;
 	unsigned long flags;
 	int rc = -EINVAL;
-	u16 endpoint;
 
 	if (!vudc->driver)
 		return -ESHUTDOWN;
 
 	spin_lock_irqsave(&vudc->lock, flags);
 
+	/* If request is still pending (ENABLING) */
+	list_for_each_entry(vreq, &vep->pend_queue, list) {
+		priv = virtio_usb_data_priv(vreq);
+		if (&priv->req == req) {
+			list_del_init(&vreq->list);
+			req->status = -ECONNRESET;
+			spin_unlock_irqrestore(&vudc->lock, flags);
+
+			virtio_usb_data_unref(vreq);
+			usb_gadget_giveback_request(ep, req);
+			return 0;
+		}
+	}
+
+	/* in-flight - schedule async cancel */
 	list_for_each_entry(vreq, &vep->req_queue, list) {
 		priv = virtio_usb_data_priv(vreq);
 		if (req == &priv->req) {
@@ -502,28 +825,14 @@ static int virtio_ep_dequeue(struct usb_ep *ep, struct usb_request *req)
 			break;
 		}
 	}
-	endpoint = vep->ep_id | (usb_ep_dir_in(ep) ? VIRTIO_USB_EP_DIR_IN :
-						     VIRTIO_USB_EP_DIR_OUT);
 	spin_unlock_irqrestore(&vudc->lock, flags);
 
-	if (rc)
+	if (rc) // request not found in req_queue
 		return rc;
 
-	vreq = priv->vreq;
+	schedule_work(&priv->cancel_work);
 
-	cmd = virtio_usb_dc_cmd_alloc(vudc, VIRTIO_USB_CMD_DEV_CANCEL,
-				      GFP_KERNEL);
-	if (!cmd)
-		return -ENOMEM;
-
-	cancel = virtio_usb_cmd_request(cmd);
-	cancel->hdr.endpoint = cpu_to_le16(endpoint);
-	cancel->hdr.port = cpu_to_le16(vudc->port);
-	cancel->tag = cpu_to_le64((uintptr_t)vreq);
-
-	rc = virtio_usb_cmd_send_sync(vudc->vusb, NULL, NULL, cmd);
-
-	return rc;
+	return 0;
 }
 
 /**
@@ -633,6 +942,109 @@ static const struct usb_ep_ops virtio_usb_ep_ops = {
 /*-------------------------------------------------------------------------*/
 /* UDC callbacks */
 
+static void virtio_usb_dc_cancel_work(struct work_struct *work)
+{
+	struct virtio_usb_dc_priv *priv =
+		container_of(work, struct virtio_usb_dc_priv, cancel_work);
+	struct virtio_usb_ep *vep = priv->vep;
+	struct virtio_usb_dc *vudc = vep->vudc;
+	struct virtio_usb_cmd *cmd;
+	struct virtio_usb_dev_cmd_cancel *cancel;
+	struct virtio_usb_data *vreq = priv->vreq;
+	unsigned long flags;
+	u16 endpoint;
+	struct virtio_usb_data *iter;
+	bool found = false;
+
+	spin_lock_irqsave(&vudc->lock, flags);
+
+	list_for_each_entry(iter, &vep->req_queue, list) {
+		if (iter == vreq) {
+			found = true;
+			break;
+		}
+	}
+	if (!found) {
+		spin_unlock_irqrestore(&vudc->lock, flags);
+		return;
+	}
+	endpoint = vep->ep_id |
+		   (usb_ep_dir_in(&vep->ep) ? VIRTIO_USB_EP_DIR_IN :
+					      VIRTIO_USB_EP_DIR_OUT);
+	spin_unlock_irqrestore(&vudc->lock, flags);
+
+	cmd = virtio_usb_dc_cmd_alloc(vudc, VIRTIO_USB_CMD_DEV_CANCEL,
+				      GFP_KERNEL);
+	if (!cmd)
+		return;
+
+	cancel = virtio_usb_cmd_request(cmd);
+	cancel->hdr.endpoint = cpu_to_le16(endpoint);
+	cancel->hdr.port = cpu_to_le16(vudc->port);
+	cancel->tag = cpu_to_le64((uintptr_t)vreq);
+
+	(void)virtio_usb_cmd_send_sync(vudc->vusb, NULL, NULL, cmd);
+}
+
+static int virtio_usb_dc_send_cmd(struct virtio_usb_dc *vudc,
+				  unsigned int cmd_code, unsigned int val)
+{
+	struct virtio_usb_dev_cmd_set_value *req;
+	struct virtio_usb_cmd *cmd;
+
+	cmd = virtio_usb_dc_cmd_alloc(vudc, cmd_code, GFP_ATOMIC);
+	if (!cmd)
+		return -ENOMEM;
+
+	req = virtio_usb_cmd_request(cmd);
+	req->value = cpu_to_le32(val);
+
+	return virtio_usb_cmd_send_sync(vudc->vusb, NULL, NULL, cmd);
+}
+
+static void virtio_usb_dc_work(struct work_struct *work)
+{
+	struct virtio_usb_dc *vudc =
+		container_of(work, struct virtio_usb_dc, dc_cmd_work);
+	int sp, ma;
+
+	mutex_lock(&vudc->dc_cmd_work_lock);
+
+	sp = atomic_xchg(&vudc->pending_self_pwr, -1);
+	ma = atomic_xchg(&vudc->pending_vbus_ma, -1);
+
+	if (sp >= 0) {
+		bool new_sp = (sp != 0);
+
+		if (new_sp != vudc->cur_self_pwr) {
+			(void)virtio_usb_dc_send_cmd(
+				vudc, VIRTIO_USB_CMD_DEV_SET_SELF_POWERED,
+				new_sp);
+			vudc->cur_self_pwr = new_sp;
+		}
+	}
+
+	if (ma >= 0) {
+		unsigned int new_ma = (unsigned int)ma;
+
+		if (vudc->cur_self_pwr)
+			new_ma = 0;
+
+		if (new_ma != vudc->cur_vbus_ma) {
+			(void)virtio_usb_dc_send_cmd(
+				vudc, VIRTIO_USB_CMD_DEV_VBUS_DRAW, new_ma);
+			vudc->cur_vbus_ma = new_ma;
+		}
+	}
+
+	mutex_unlock(&vudc->dc_cmd_work_lock);
+
+	if (atomic_read(&vudc->pending_self_pwr) >= 0 ||
+	    atomic_read(&vudc->pending_vbus_ma) >= 0) {
+		queue_work(vudc->dc_cmd_wq, &vudc->dc_cmd_work);
+	}
+}
+
 /**
  * virtio_usb_dc_set_selfpowered() - Sets the device selfpowered feature.
  * @gadget: The device being declared as self-powered
@@ -645,18 +1057,11 @@ static int virtio_usb_dc_set_selfpowered(struct usb_gadget *gadget,
 {
 	struct virtio_usb_dc *vudc =
 		container_of(gadget, struct virtio_usb_dc, gadget);
-	unsigned int code = VIRTIO_USB_CMD_DEV_SET_SELF_POWERED;
-	struct virtio_usb_dev_cmd_set_value *req;
-	struct virtio_usb_cmd *cmd;
 
-	cmd = virtio_usb_dc_cmd_alloc(vudc, code, GFP_ATOMIC);
-	if (!cmd)
-		return -ENOMEM;
-
-	req = virtio_usb_cmd_request(cmd);
-	req->value = cpu_to_le32(!!is_selfpowered);
+	atomic_set(&vudc->pending_self_pwr, is_selfpowered ? 1 : 0);
+	queue_work(vudc->dc_cmd_wq, &vudc->dc_cmd_work);
 
-	return virtio_usb_cmd_send_sync(vudc->vusb, NULL, NULL, cmd);
+	return 0;
 }
 
 /**
@@ -774,29 +1179,18 @@ static int virtio_usb_dc_stop(struct usb_gadget *gadget)
 /**
  * virtio_usb_dc_vbus_draw() - Constrain controller's VBUS power usage
  * @gadget: The device whose VBUS usage is being described
- * @mA: How much current to draw, in milliAmperes.
+ * @ma: How much current to draw, in milliAmperes.
  *
  * Return: 0 on success, -errno on failure.
  */
-static int virtio_usb_dc_vbus_draw(struct usb_gadget *gadget, unsigned int mA)
+static int virtio_usb_dc_vbus_draw(struct usb_gadget *gadget, unsigned int ma)
 {
 	struct virtio_usb_dc *vudc =
 		container_of(gadget, struct virtio_usb_dc, gadget);
-	unsigned int code = VIRTIO_USB_CMD_DEV_VBUS_DRAW;
-	struct virtio_usb_dev_cmd_set_value *req;
-	struct virtio_usb_cmd *cmd;
-	int rc;
-
-	cmd = virtio_usb_dc_cmd_alloc(vudc, code, GFP_ATOMIC);
-	if (!cmd)
-		return -ENOMEM;
-
-	req = virtio_usb_cmd_request(cmd);
-	req->value = cpu_to_le32(mA);
-
-	rc = virtio_usb_cmd_send_sync(vudc->vusb, NULL, NULL, cmd);
 
-	return rc;
+	atomic_set(&vudc->pending_vbus_ma, ma);
+	queue_work(vudc->dc_cmd_wq, &vudc->dc_cmd_work);
+	return 0;
 }
 
 /**
@@ -930,6 +1324,11 @@ static int virtio_usb_dc_get_endpoint_info(struct virtio_usb_dc *vudc)
 		vep->ep.name = vep->name;
 		vep->ep.ops = &virtio_usb_ep_ops;
 		INIT_LIST_HEAD(&vep->req_queue);
+		INIT_LIST_HEAD(&vep->pend_queue);
+		vep->state = i ? EP_DISABLED : EP_ENABLED; // EP0 always enabled
+		INIT_WORK(&vep->enable_work, vep_enable_work);
+		INIT_WORK(&vep->disable_work, vep_disable_work);
+		vep->last_err = 0;
 		list_add_tail(&vep->ep.ep_list, &vudc->gadget.ep_list);
 		usb_ep_set_maxpacket_limit(&vep->ep, maxpacket_limit);
 		vep->ep.max_streams = le16_to_cpu(epinfo[i].max_streams);
@@ -1038,7 +1437,6 @@ static void virtio_usb_dc_event_process(struct virtio_usb_event *event)
 		}
 		vudc->gadget.ops = &virtio_gadget_ops;
 		vudc->gadget.max_speed = USB_SPEED_HIGH;
-		vudc->gadget.nonatomic = 1;
 
 		//vudc->gadget.dev.init_name = gadget_name;
 		vudc->gadget.dev.parent = &vudc->pdev->dev;
@@ -1071,6 +1469,14 @@ static void virtio_usb_dc_event_process(struct virtio_usb_event *event)
 
 		vep = usb_ep_to_virtio_ep(vudc->gadget.ep0);
 		setup_evt = virtio_usb_event_buf(event);
+		rc = virtio_usb_dc_wait_ep0_idle(vudc);
+		if (rc) {
+			dev_warn(
+				&vusb->vdev->dev,
+				"SETUP while ep0 request is still pending, timing out\n");
+			virtio_ep_set_halt(vudc->gadget.ep0, 1);
+			break;
+		}
 
 		memcpy(&vep->setup, setup_evt->setup,
 		       sizeof(struct usb_ctrlrequest));
@@ -1102,6 +1508,9 @@ static void virtio_usb_dc_event_process(struct virtio_usb_event *event)
 	case VIRTIO_USB_EVT_DEV_UNBIND: {
 		unsigned int registered;
 
+		pr_info("%s port_id %d VIRTIO_USB_EVT_DEV_UNBIND\n", __func__,
+			port_id);
+
 		spin_lock_irqsave(&vudc->lock, flags);
 		registered = vudc->registered;
 		vudc->registered = 0;
@@ -1113,8 +1522,14 @@ static void virtio_usb_dc_event_process(struct virtio_usb_event *event)
 			vudc->gadget.name = NULL;
 		}
 
-		kfree(vudc->veps);
-		vudc->veps = NULL;
+		if (vudc->veps) {
+			unsigned int i;
+
+			for (i = 0; i < vudc->neps; i++)
+				cancel_work_sync(&vudc->veps[i].enable_work);
+			kfree(vudc->veps);
+			vudc->veps = NULL;
+		}
 
 		if (vudc->pdev) {
 			platform_device_unregister(vudc->pdev);
@@ -1141,7 +1556,6 @@ void virtio_usb_dc_event_work(struct work_struct *work)
 		container_of(work, struct virtio_usb, vq_dev_event_work);
 	struct virtio_usb_queue *evtq =
 		&vusb->vqueues[vusb->dev_vq_base + VIRTIO_USB_VQ_EVENT_IDX];
-
 	virtio_usb_evt_work(evtq, virtio_usb_dc_event_process);
 }
 
@@ -1186,13 +1600,39 @@ int virtio_usb_dc_init(struct virtio_usb *vusb, int port_id)
 	if (!vudc)
 		return -ENOMEM;
 
-	vusb->vports[port_id].vudc = vudc;
 	vudc->vusb = vusb;
 	vudc->port = port_id;
 	for (i = 0; i < VIRTIO_USB_VQ_DEV_MAX; i++)
 		vudc->dcqs[i] = &vusb->vqueues[vusb->dev_vq_base + i];
 
+	vudc->ep_cmd_wq = alloc_ordered_workqueue(
+		"vudc_ep_work_%d", WQ_MEM_RECLAIM | WQ_UNBOUND, port_id);
+	vudc->dc_cmd_wq = alloc_ordered_workqueue(
+		"vudc_dc_work_%d", WQ_MEM_RECLAIM | WQ_UNBOUND, port_id);
+	if (!vudc->ep_cmd_wq || !vudc->dc_cmd_wq) {
+		if (vudc->ep_cmd_wq)
+			destroy_workqueue(vudc->ep_cmd_wq);
+		if (vudc->dc_cmd_wq)
+			destroy_workqueue(vudc->dc_cmd_wq);
+		devm_kfree(&vusb->vdev->dev, vudc);
+		return -ENOMEM;
+	}
+
+	/* Publish only once fully initialized - vports[port_id].vudc must
+	 * never point at a partially-constructed (or already-freed, on the
+	 * failure path above) struct virtio_usb_dc.
+	 */
+	vusb->vports[port_id].vudc = vudc;
+
+	INIT_WORK(&vudc->dc_cmd_work, virtio_usb_dc_work);
+	mutex_init(&vudc->dc_cmd_work_lock);
+	atomic_set(&vudc->pending_self_pwr, -1);
+	atomic_set(&vudc->pending_vbus_ma, -1);
+	vudc->cur_vbus_ma = 0;
+	vudc->cur_self_pwr = false;
+
 	spin_lock_init(&vudc->lock);
+	init_waitqueue_head(&vudc->ep0_idle);
 
 	vudc->registered = 0;
 
@@ -1228,6 +1668,22 @@ int virtio_usb_dc_deinit(struct virtio_usb *vusb)
 		if (registered)
 			usb_del_gadget_udc(&vudc->gadget);
 
+		if (vudc->dc_cmd_wq) {
+			cancel_work_sync(&vudc->dc_cmd_work);
+			destroy_workqueue(vudc->dc_cmd_wq);
+		}
+
+		if (vudc->ep_cmd_wq) {
+			flush_workqueue(vudc->ep_cmd_wq);
+			if (vudc->veps) {
+				unsigned int i;
+
+				for (i = 0; i < vudc->neps; i++)
+					cancel_work_sync(
+						&vudc->veps[i].enable_work);
+			}
+			destroy_workqueue(vudc->ep_cmd_wq);
+		}
 		kfree(vudc->veps);
 		vudc->veps = NULL;
 
diff --git a/drivers/usb/virtio_usb/device.h b/drivers/usb/virtio_usb/device.h
index 9dacc89..2f93f45 100644
--- a/drivers/usb/virtio_usb/device.h
+++ b/drivers/usb/virtio_usb/device.h
@@ -11,6 +11,7 @@
 #include <linux/usb.h>
 #include <linux/list.h>
 #include <linux/platform_device.h>
+#include <linux/wait.h>
 #include <uapi/linux/usb/ch11.h>
 #include <uapi/linux/usb/ch9.h>
 #include <linux/usb/gadget.h>
@@ -37,12 +38,19 @@
 		.dir_out = !!((_dir) & VIRTIO_USB_EP_CAPS_DIR_OUT),            \
 	}
 
+enum vep_state { EP_DISABLED, EP_ENABLING, EP_ENABLED, EP_HALTED };
+
 /**
  * struct virtio_usb_ep - virtio usb device endpoint
  * @ep: usb ep
  * @vusb: VirtIO usb device
  * @setup: setup packet for control endpoint
  * @req_queue: list of usb requests submitted to ep awaiting response
+ * @pend_queue: list of usb requests waiting for submit while EP is ENABLING
+ * @state: endpoint state (necessary to support split-phase async processing)
+ * @enable_work:  workstruct for the async ep_enable()
+ * @disable_work:  workstruct for the async ep_disable()
+ * @last_err: error code if async processeing brings us to HALTED state
  * @ep_id: Id of the endpoint
  * @name: Endpoint name
  */
@@ -51,6 +59,11 @@ struct virtio_usb_ep {
 	struct virtio_usb_dc *vudc;
 	struct usb_ctrlrequest setup;
 	struct list_head req_queue;
+	struct list_head pend_queue;
+	enum vep_state state;
+	struct work_struct enable_work;
+	struct work_struct disable_work;
+	int last_err;
 	u16 ep_id;
 	char name[16];
 };
@@ -66,6 +79,7 @@ struct virtio_usb_ep {
  * @registered: Flag indicating registration status to the UDC core.
  * @pullup:  Software-controlled connect/disconnect status USB host.
  * @lock: Spinlock that protects device state
+ * @ep0_idle: Wait queue for SETUP serialization while ep0 has an in-flight req
  */
 struct virtio_usb_dc {
 	struct platform_device *pdev;
@@ -79,6 +93,16 @@ struct virtio_usb_dc {
 	unsigned registered : 1;
 	unsigned pullup : 1;
 	spinlock_t lock;
+	wait_queue_head_t ep0_idle;
+	/* Workqueue for EP enable/disable/cancel commands */
+	struct workqueue_struct *ep_cmd_wq;
+	struct workqueue_struct *dc_cmd_wq;
+	struct work_struct dc_cmd_work;
+	struct mutex dc_cmd_work_lock;
+	atomic_t pending_self_pwr;
+	atomic_t pending_vbus_ma;
+	unsigned int cur_vbus_ma;
+	bool cur_self_pwr;
 };
 
 extern const struct virtio_usb_vq_desc dev_vqueues[VIRTIO_USB_VQ_DEV_MAX];

^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH 7/8] virtio-usb: add SuperSpeed device-role support
  2026-09-24 16:08 [PATCH 0/8] virtio-usb: add dual-role virtio USB driver Igor Skalkin
                   ` (5 preceding siblings ...)
  2026-09-24 16:09 ` [PATCH 6/8] virtio-usb: rework endpoint lifecycle to an async split-phase state machine Igor Skalkin
@ 2026-09-24 16:09 ` Igor Skalkin
  2026-09-24 16:09 ` [PATCH 8/8] virtio-usb: support a guest UDC name prefix from the bind event Igor Skalkin
  2026-09-25  5:17 ` [PATCH 0/8] virtio-usb: add dual-role virtio USB driver Greg Kroah-Hartman
  8 siblings, 0 replies; 24+ messages in thread
From: Igor Skalkin @ 2026-09-24 16:09 UTC (permalink / raw)
  To: Michael S . Tsirkin, Jason Wang, Greg Kroah-Hartman
  Cc: virtualization, linux-usb, Vasilii Ianikeev, Aiswarya Cyriac,
	Anton Yakovlev, Trilok Soni, Igor Skalkin

Stop hardcoding the gadget's max_speed to USB_SPEED_HIGH on DEV_BIND;
parse max_speed from the bind event sent by the host backend and
clamp it to a supported set (LOW/FULL/HIGH/SUPER/SUPER_PLUS),
defaulting to HIGH on invalid data. Update set_speed() to clamp the
negotiated speed against gadget.max_speed and configure the EP0 max
packet size for SuperSpeed and SuperSpeed Plus (512 bytes both, per
USB 3.1), while preserving existing HS/FS behavior.

Also send the SuperSpeed endpoint companion descriptor together with
the endpoint descriptor on EP_ENABLE, as an additional scatter-gather
payload, so the host backend can configure burst/mult parameters
instead of relying on the endpoint descriptor alone.

This aligns the guest-side speed configuration with the host's
capability and enables SuperSpeed path bring-up.

Signed-off-by: Igor Skalkin <igor.skalkin@oss.qualcomm.com>
---
 drivers/usb/virtio_usb/device.c |   54 +++++++++++++++++++++++++++++++++++-----
 include/uapi/linux/virtio_usb.h |    8 +++++
 2 files changed, 56 insertions(+), 6 deletions(-)

diff --git a/drivers/usb/virtio_usb/device.c b/drivers/usb/virtio_usb/device.c
index 9ee1f05..ee41ea9 100644
--- a/drivers/usb/virtio_usb/device.c
+++ b/drivers/usb/virtio_usb/device.c
@@ -439,12 +439,14 @@ static void vep_enable_work(struct work_struct *work)
 		container_of(work, struct virtio_usb_ep, enable_work);
 	struct virtio_usb_dc *vudc = vep->vudc;
 	const struct usb_endpoint_descriptor *desc = vep->ep.desc;
-	struct scatterlist sg;
-	struct scatterlist *psg_data = &sg;
+	const struct usb_ss_ep_comp_descriptor *comp = vep->ep.comp_desc;
+	struct scatterlist sgs[2];
+	struct scatterlist *psg_data = sgs;
 	struct virtio_usb_dev_cmd_hdr *hdr;
 	struct virtio_usb_cmd *cmd;
 	unsigned long flags;
 	u16 endpoint;
+	u8 n_sgs = 1;
 	int rc;
 
 	if (!desc)
@@ -461,7 +463,13 @@ static void vep_enable_work(struct work_struct *work)
 		goto done;
 	}
 
-	sg_init_one(psg_data, desc, sizeof(*desc));
+	if (virtio_usb_ep_comp_valid(comp))
+		n_sgs = 2;
+
+	sg_init_table(psg_data, n_sgs);
+	sg_set_buf(&psg_data[0], desc, sizeof(*desc));
+	if (n_sgs == 2)
+		sg_set_buf(&psg_data[1], comp, sizeof(*comp));
 	hdr = virtio_usb_cmd_request(cmd);
 	hdr->endpoint = cpu_to_le16(endpoint);
 
@@ -564,6 +572,7 @@ static int virtio_ep_enable(struct usb_ep *ep,
 {
 	struct virtio_usb_ep *vep = usb_ep_to_virtio_ep(ep);
 	struct virtio_usb_dc *vudc = vep->vudc;
+	const struct usb_ss_ep_comp_descriptor *comp = ep->comp_desc;
 	unsigned long flags;
 
 	spin_lock_irqsave(&vudc->lock, flags);
@@ -579,6 +588,16 @@ static int virtio_ep_enable(struct usb_ep *ep,
 		return -EBUSY;
 	}
 
+	if (comp) {
+		if (!virtio_usb_ep_comp_valid(comp) || comp->bMaxBurst > 15) {
+			spin_unlock_irqrestore(&vudc->lock, flags);
+			return -EINVAL;
+		}
+	} else if (vudc->gadget.speed >= USB_SPEED_SUPER) {
+		spin_unlock_irqrestore(&vudc->lock, flags);
+		return -EINVAL;
+	}
+
 	ep->desc = desc;
 	ep->maxpacket = usb_endpoint_maxp(desc);
 	vep->state = EP_ENABLING;
@@ -1119,9 +1138,13 @@ static void virtio_usb_dc_set_speed(struct usb_gadget *gadget,
 	struct virtio_usb_dc *vudc =
 		container_of(gadget, struct virtio_usb_dc, gadget);
 
-	vudc->gadget.speed = min_t(u8, USB_SPEED_HIGH, speed);
+	vudc->gadget.speed = min_t(u8, vudc->gadget.max_speed, speed);
 
-	switch (speed) {
+	switch (vudc->gadget.speed) {
+	case USB_SPEED_SUPER_PLUS:
+	case USB_SPEED_SUPER:
+		vudc->veps[0].ep.maxpacket = 512;
+		break;
 	case USB_SPEED_HIGH:
 	case USB_SPEED_FULL:
 		vudc->veps[0].ep.maxpacket = 64;
@@ -1375,6 +1398,8 @@ static int virtio_usb_dc_parent_create(struct virtio_usb_dc *vudc)
 static void virtio_usb_dc_event_process(struct virtio_usb_event *event)
 {
 	struct virtio_usb_dev_event *evt = virtio_usb_event_buf(event);
+	struct virtio_usb_dev_bind_event *bind_evt =
+		(struct virtio_usb_dev_bind_event *)evt;
 	struct virtio_usb_dev_setup_event *setup_evt;
 	struct virtio_usb *vusb = event->vusb;
 	struct virtio_usb_dc *vudc;
@@ -1395,6 +1420,8 @@ static void virtio_usb_dc_event_process(struct virtio_usb_event *event)
 
 	switch (le32_to_cpu(evt->code)) {
 	case VIRTIO_USB_EVT_DEV_BIND: {
+		u8 max_speed;
+
 		if (vudc->registered) {
 			dev_err(&vusb->vdev->dev,
 				"port %d: BIND while still registered, ignoring\n",
@@ -1405,6 +1432,8 @@ static void virtio_usb_dc_event_process(struct virtio_usb_event *event)
 
 		memzero_explicit(&vudc->gadget, sizeof(struct usb_gadget));
 		vudc->gadget.sg_supported = 1;
+		max_speed = bind_evt->max_speed;
+
 		rc = virtio_usb_dc_get_endpoint_count(vudc);
 		if (rc) {
 			dev_err(&vusb->vdev->dev,
@@ -1436,7 +1465,20 @@ static void virtio_usb_dc_event_process(struct virtio_usb_event *event)
 			break;
 		}
 		vudc->gadget.ops = &virtio_gadget_ops;
-		vudc->gadget.max_speed = USB_SPEED_HIGH;
+		switch (max_speed) {
+		case USB_SPEED_LOW:
+		case USB_SPEED_FULL:
+		case USB_SPEED_HIGH:
+		case USB_SPEED_SUPER:
+		case USB_SPEED_SUPER_PLUS:
+			vudc->gadget.max_speed = max_speed;
+			break;
+		default:
+			pr_info("virtio-usb: unrecognized max_speed %u from host, defaulting to HIGH\n",
+				max_speed);
+			vudc->gadget.max_speed = USB_SPEED_HIGH;
+			break;
+		}
 
 		//vudc->gadget.dev.init_name = gadget_name;
 		vudc->gadget.dev.parent = &vudc->pdev->dev;
diff --git a/include/uapi/linux/virtio_usb.h b/include/uapi/linux/virtio_usb.h
index 29cec5d..d89223d 100644
--- a/include/uapi/linux/virtio_usb.h
+++ b/include/uapi/linux/virtio_usb.h
@@ -222,6 +222,14 @@ struct virtio_usb_dev_event {
 	__u8 padding[10];
 };
 
+/* VIRTIO_USB_EVT_DEV_BIND */
+struct virtio_usb_dev_bind_event {
+	__le32 code; /* VIRTIO_USB_EVT_DEV_BIND */
+	__le16 port; /* Device ID */
+	__u8 max_speed; /* enum usb_device_speed */
+	__u8 padding[9];
+};
+
 /* VIRTIO_USB_EVT_DEV_SETUP */
 struct virtio_usb_dev_setup_event {
 	__le32 code; /* VIRTIO_USB_EVT_DEV_SETUP */

^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH 8/8] virtio-usb: support a guest UDC name prefix from the bind event
  2026-09-24 16:08 [PATCH 0/8] virtio-usb: add dual-role virtio USB driver Igor Skalkin
                   ` (6 preceding siblings ...)
  2026-09-24 16:09 ` [PATCH 7/8] virtio-usb: add SuperSpeed device-role support Igor Skalkin
@ 2026-09-24 16:09 ` Igor Skalkin
  2026-09-25  5:17 ` [PATCH 0/8] virtio-usb: add dual-role virtio USB driver Greg Kroah-Hartman
  8 siblings, 0 replies; 24+ messages in thread
From: Igor Skalkin @ 2026-09-24 16:09 UTC (permalink / raw)
  To: Michael S . Tsirkin, Jason Wang, Greg Kroah-Hartman
  Cc: virtualization, linux-usb, Vasilii Ianikeev, Aiswarya Cyriac,
	Anton Yakovlev, Trilok Soni, Igor Skalkin

Let the host backend supply a name prefix for a port's UDC parent
platform_device via the bind event, instead of always using the
generic GADGET_NAME. When present, the prefix is used verbatim as
the parent device's name and the gadget name becomes
"<prefix>_p<port>"; when empty, behavior is unchanged.

Signed-off-by: Igor Skalkin <igor.skalkin@oss.qualcomm.com>
---
 drivers/usb/virtio_usb/device.c |   47 +++++++++++++++++++++++++++++++++-------
 include/uapi/linux/virtio_usb.h |    4 ++-
 2 files changed, 42 insertions(+), 9 deletions(-)

diff --git a/drivers/usb/virtio_usb/device.c b/drivers/usb/virtio_usb/device.c
index ee41ea9..fedce74 100644
--- a/drivers/usb/virtio_usb/device.c
+++ b/drivers/usb/virtio_usb/device.c
@@ -1369,11 +1369,26 @@ on_error:
 	return rc;
 }
 
-static int virtio_usb_dc_parent_create(struct virtio_usb_dc *vudc)
+static int virtio_usb_dc_parent_create(struct virtio_usb_dc *vudc,
+				       const unsigned char *udc_prefix)
 {
+	size_t prefix_len;
+	char *parent_name;
 	int rc;
 
-	vudc->pdev = platform_device_alloc(GADGET_NAME, vudc->port);
+	prefix_len = strnlen((const char *)udc_prefix,
+			     VIRTIO_USB_UDC_NAME_PREFIX_MAX);
+	if (prefix_len) {
+		parent_name = kasprintf(GFP_KERNEL, "%.*s", (int)prefix_len,
+					(const char *)udc_prefix);
+	} else {
+		parent_name = kstrdup(GADGET_NAME, GFP_KERNEL);
+	}
+	if (!parent_name)
+		return -ENOMEM;
+
+	vudc->pdev = platform_device_alloc(parent_name, vudc->port);
+	kfree(parent_name);
 	if (!vudc->pdev)
 		return -ENOMEM;
 
@@ -1421,6 +1436,7 @@ static void virtio_usb_dc_event_process(struct virtio_usb_event *event)
 	switch (le32_to_cpu(evt->code)) {
 	case VIRTIO_USB_EVT_DEV_BIND: {
 		u8 max_speed;
+		size_t udc_prefix_len;
 
 		if (vudc->registered) {
 			dev_err(&vusb->vdev->dev,
@@ -1447,7 +1463,8 @@ static void virtio_usb_dc_event_process(struct virtio_usb_event *event)
 			break;
 		}
 		if (!vudc->pdev) {
-			rc = virtio_usb_dc_parent_create(vudc);
+			rc = virtio_usb_dc_parent_create(
+				vudc, bind_evt->udc_name_prefix);
 			if (rc) {
 				dev_err(&vusb->vdev->dev,
 					"Failed to create UDC parent device\n");
@@ -1456,8 +1473,18 @@ static void virtio_usb_dc_event_process(struct virtio_usb_event *event)
 				break;
 			}
 		}
-		vudc->gadget.name =
-			kasprintf(GFP_KERNEL, "%s_%d", GADGET_NAME, port_id);
+		udc_prefix_len =
+			strnlen((const char *)bind_evt->udc_name_prefix,
+				VIRTIO_USB_UDC_NAME_PREFIX_MAX);
+		if (udc_prefix_len) {
+			vudc->gadget.name = kasprintf(
+				GFP_KERNEL, "%.*s_p%d", (int)udc_prefix_len,
+				(const char *)bind_evt->udc_name_prefix,
+				port_id);
+		} else {
+			vudc->gadget.name = kasprintf(GFP_KERNEL, "%s_%d",
+						      GADGET_NAME, port_id);
+		}
 		if (!vudc->gadget.name) {
 			rc = -ENOMEM;
 			kfree(vudc->veps);
@@ -1480,7 +1507,6 @@ static void virtio_usb_dc_event_process(struct virtio_usb_event *event)
 			break;
 		}
 
-		//vudc->gadget.dev.init_name = gadget_name;
 		vudc->gadget.dev.parent = &vudc->pdev->dev;
 		rc = usb_add_gadget_udc(&vudc->pdev->dev, &vudc->gadget);
 		if (rc) {
@@ -1612,11 +1638,16 @@ int virtio_usb_dc_event_populate(struct virtio_usb *vusb)
 {
 	struct virtio_usb_queue *evt_queue =
 		&vusb->vqueues[vusb->dev_vq_base + VIRTIO_USB_VQ_EVENT_IDX];
+	size_t evt_size;
 	struct virtio_usb_event *events;
 	int rc;
 
-	events = virtio_usb_events_alloc(vusb, evt_queue,
-					 sizeof(struct virtio_usb_dev_event));
+	evt_size = max_t(size_t, sizeof(struct virtio_usb_dev_event),
+			 sizeof(struct virtio_usb_dev_setup_event));
+	evt_size = max_t(size_t, evt_size,
+			 sizeof(struct virtio_usb_dev_bind_event));
+
+	events = virtio_usb_events_alloc(vusb, evt_queue, evt_size);
 
 	if (!events)
 		return -ENOMEM;
diff --git a/include/uapi/linux/virtio_usb.h b/include/uapi/linux/virtio_usb.h
index d89223d..5fff248 100644
--- a/include/uapi/linux/virtio_usb.h
+++ b/include/uapi/linux/virtio_usb.h
@@ -216,6 +216,8 @@ enum {
 	VIRTIO_USB_EVT_DEV_UNBIND,
 };
 
+#define VIRTIO_USB_UDC_NAME_PREFIX_MAX 64
+
 struct virtio_usb_dev_event {
 	__le32 code; /* VIRTIO_USB_EVT_DEV_XXX */
 	__le16 port; /* Device ID */
@@ -227,7 +229,7 @@ struct virtio_usb_dev_bind_event {
 	__le32 code; /* VIRTIO_USB_EVT_DEV_BIND */
 	__le16 port; /* Device ID */
 	__u8 max_speed; /* enum usb_device_speed */
-	__u8 padding[9];
+	__u8 udc_name_prefix[VIRTIO_USB_UDC_NAME_PREFIX_MAX];
 };
 
 /* VIRTIO_USB_EVT_DEV_SETUP */

^ permalink raw reply related	[flat|nested] 24+ messages in thread

* Re: [PATCH 1/8] virtio-usb: add protocol header and skeleton dual-role driver
  2026-09-24 16:09 ` [PATCH 1/8] virtio-usb: add protocol header and skeleton dual-role driver Igor Skalkin
@ 2026-09-25  5:14   ` Greg Kroah-Hartman
  2026-09-28 14:19     ` Igor Skalkin
  2026-09-25  5:21   ` Greg Kroah-Hartman
  1 sibling, 1 reply; 24+ messages in thread
From: Greg Kroah-Hartman @ 2026-09-25  5:14 UTC (permalink / raw)
  To: Igor Skalkin
  Cc: Michael S . Tsirkin, Jason Wang, virtualization, linux-usb,
	Vasilii Ianikeev, Aiswarya Cyriac, Anton Yakovlev, Trilok Soni

On Thu, Sep 24, 2026 at 06:09:00PM +0200, Igor Skalkin wrote:
> From: Aiswarya Cyriac <aiswarya.cyriac@oss.qualcomm.com>
> 
> Add the virtio_usb device and virtqueue-wrapper skeleton, the
> protocol header shared with the vhost gadget driver and userspace
> backend, and probe()/remove() plumbing with no role support yet.
> 
> Introduce a per-port virtio_usb_port array (vports[]), sized to the
> device's negotiated port count, ahead of any code that actually
> populates or reads it. Every later commit that adds a role to a port
> (host, device, OTG) builds on this same array from the start, instead
> of the host, device, and OTG subsystems each growing their own
> separate port-indexed storage that later has to be reconciled.
> 
> Wire the new drivers/usb/virtio_usb/ directory into the USB
> subsystem's build (drivers/usb/Kconfig, drivers/usb/Makefile) as a
> new CONFIG_USB_VIRTIO option, listed alongside the other USB
> dual-mode controller drivers.
> 
> Signed-off-by: Igor Skalkin <igor.skalkin@oss.qualcomm.com>

Why does this not match the author name?

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH 0/8] virtio-usb: add dual-role virtio USB driver
  2026-09-24 16:08 [PATCH 0/8] virtio-usb: add dual-role virtio USB driver Igor Skalkin
                   ` (7 preceding siblings ...)
  2026-09-24 16:09 ` [PATCH 8/8] virtio-usb: support a guest UDC name prefix from the bind event Igor Skalkin
@ 2026-09-25  5:17 ` Greg Kroah-Hartman
  2026-09-28 13:55   ` Igor Skalkin
  8 siblings, 1 reply; 24+ messages in thread
From: Greg Kroah-Hartman @ 2026-09-25  5:17 UTC (permalink / raw)
  To: Igor Skalkin
  Cc: Michael S . Tsirkin, Jason Wang, virtualization, linux-usb,
	Vasilii Ianikeev, Aiswarya Cyriac, Anton Yakovlev, Trilok Soni

On Thu, Sep 24, 2026 at 06:08:59PM +0200, Igor Skalkin wrote:
> This series adds a new virtio-usb driver: a dual-role virtio device
> capable of acting as a USB host controller, a USB device controller,
> or both simultaneously with runtime role switching between the two
> (USB OTG-style role switching) on ports that support it.
> 
> The corresponding virtio-usb device specification has been posted to
> virtio-comment for review. This series matches the v2 revision of
> that spec, which reconciles a small number of protocol details
> (per-role virtqueue presentation, host-role vp_idx for hub/multi-VP
> support, and the device-role BIND/UNBIND event split) that were
> clarified while integrating and testing this driver against the
> spec:

Why do we need this at all when we have other ways of doing usb devices
through virtio?

Why is a USB virtio spec needed at all, who is going to use it?


> 
>   [RFC PATCH v2] virtio-usb: Add initial virtio-usb specification
>   Igor Skalkin <igor.skalkin@oss.qualcomm.com>
>   virtio-comment@lists.linux.dev
>   https://lore.kernel.org/virtio-comment/20260924154007.143927-1-igor.skalkin@oss.qualcomm.com/
> 
> This driver has been tested end-to-end against our own userspace
> virtio-usb device implementation (host-side backend) in two setups:

Where is that code and why isn't it part of this submission?

>   4-5: USB OTG-style role query and role-switching support.

There's a reason OTG isn't used anymore by devices, how have you
addressed those problems here?  And why duplicate the failures of the
past?

>   6:   endpoint-lifecycle robustness rework (async split-phase state
>        machine, replacing an earlier out-of-tree gadget.nonatomic
>        patch that didn't pass upstream review).
>   7:   SuperSpeed device-role support.

Why should speed settings matter to a virtual connection?

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH 2/8] virtio-usb: add host role (USB Host Controller) support
  2026-09-24 16:09 ` [PATCH 2/8] virtio-usb: add host role (USB Host Controller) support Igor Skalkin
@ 2026-09-25  5:18   ` Greg Kroah-Hartman
  2026-09-28 14:01     ` Igor Skalkin
  0 siblings, 1 reply; 24+ messages in thread
From: Greg Kroah-Hartman @ 2026-09-25  5:18 UTC (permalink / raw)
  To: Igor Skalkin
  Cc: Michael S . Tsirkin, Jason Wang, virtualization, linux-usb,
	Vasilii Ianikeev, Aiswarya Cyriac, Anton Yakovlev, Trilok Soni

On Thu, Sep 24, 2026 at 06:09:01PM +0200, Igor Skalkin wrote:
> From: Aiswarya Cyriac <aiswarya.cyriac@oss.qualcomm.com>
> 
> Add the common virtqueue handling code (command, event and data
> queues) shared by every role, and the virtio-usb host controller
> (HCD) implementation, wiring it up as the host role of the dual-role
> driver on top of that common code.
> 
> Each host-role virtual port gets its own HS+SS usb_hcd pair and its
> own root hub (struct virtio_usb_hc_vp), with a fixed
> VIRTIO_USB_VP_MAX_PORTS (8) leaf slots pre-allocated at VP init time
> and reused across connect/disconnect - never dynamically alloc'd or
> freed. This lets the backend forward more than one physical socket -
> and, for host ports behind a physical hub, more than one leaf device
> per socket - as independent virtual ports from the start, instead of
> collapsing everything onto a single shared root hub and having to
> revisit that decision once more than one host-role port needs to
> exist at the same time.
> 
> virtio_usb_add_hcd() derives each VP's HCD bus_name from the parent
> virtio_device with devm_kasprintf() rather than a stack buffer, since
> usb_create_hcd()/usb_create_shared_hcd() store that pointer as-is in
> hcd->self.bus_name without copying it - it must outlive the HCD
> itself.
> 
> Every port is host-role for now, since no other role exists yet;
> vports[].role is populated unconditionally until later commits add
> device role and OTG-based role resolution.
> 
> Signed-off-by: Aiswarya Cyriac <aiswarya.cyriac@oss.qualcomm.com>
> Co-developed-by: Anton Yakovlev <anton.yakovlev@oss.qualcomm.com>
> Signed-off-by: Anton Yakovlev <anton.yakovlev@oss.qualcomm.com>
> Signed-off-by: Vasilii Ianikeev <vasilii.ianikeev@oss.qualcomm.com>
> Co-developed-by: Igor Skalkin <igor.skalkin@oss.qualcomm.com>
> Signed-off-by: Igor Skalkin <igor.skalkin@oss.qualcomm.com>
> ---
>  drivers/usb/virtio_usb/Makefile     |    4 
>  drivers/usb/virtio_usb/controller.c |  105 ++
>  drivers/usb/virtio_usb/controller.h |   35 
>  drivers/usb/virtio_usb/host.c       | 1335 ++++++++++++++++++++++++++++++++++++
>  drivers/usb/virtio_usb/host.h       |  203 +++++
>  drivers/usb/virtio_usb/vq_common.c  |  740 +++++++++++++++++++
>  drivers/usb/virtio_usb/vq_common.h  |  163 ++++
>  include/uapi/linux/virtio_usb.h     |   16 
>  8 files changed, 2585 insertions(+), 16 deletions(-)
>  create mode 100644 drivers/usb/virtio_usb/host.c
>  create mode 100644 drivers/usb/virtio_usb/host.h
>  create mode 100644 drivers/usb/virtio_usb/vq_common.c
>  create mode 100644 drivers/usb/virtio_usb/vq_common.h
> 
> diff --git a/drivers/usb/virtio_usb/controller.c b/drivers/usb/virtio_usb/controller.c
> index 2fc6f50..216edfc 100644
> --- a/drivers/usb/virtio_usb/controller.c
> +++ b/drivers/usb/virtio_usb/controller.c
> @@ -6,9 +6,16 @@
>   */
>  
>  #include <linux/module.h>
> +#include <linux/moduleparam.h>
>  #include <uapi/linux/virtio_ids.h>
>  
>  #include "controller.h"
> +#include "host.h"
> +#include "vq_common.h"
> +
> +u32 virtio_usb_cmd_timeout_ms = MSEC_PER_SEC;
> +module_param_named(cmd_timeout_ms, virtio_usb_cmd_timeout_ms, uint, 0644);
> +MODULE_PARM_DESC(cmd_timeout_ms, "Command completion timeout in milliseconds");

This is not the 1990's, please do not add new module parameters.  Just
make it work without manual configuration at module load time.

If you really need a configuration option, make it per-device and use
the correct, modern, apis for it.

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH 1/8] virtio-usb: add protocol header and skeleton dual-role driver
  2026-09-24 16:09 ` [PATCH 1/8] virtio-usb: add protocol header and skeleton dual-role driver Igor Skalkin
  2026-09-25  5:14   ` Greg Kroah-Hartman
@ 2026-09-25  5:21   ` Greg Kroah-Hartman
  2026-09-28 14:14     ` Igor Skalkin
  1 sibling, 1 reply; 24+ messages in thread
From: Greg Kroah-Hartman @ 2026-09-25  5:21 UTC (permalink / raw)
  To: Igor Skalkin
  Cc: Michael S . Tsirkin, Jason Wang, virtualization, linux-usb,
	Vasilii Ianikeev, Aiswarya Cyriac, Anton Yakovlev, Trilok Soni

On Thu, Sep 24, 2026 at 06:09:00PM +0200, Igor Skalkin wrote:
> +/* VIRTIO_USB_EVT_HOST_PORT_CONNECTED/DISCONNECTED */
> +enum {
> +	VIRTIO_USB_SPEED_UNKNOWN = 0,
> +	VIRTIO_USB_SPEED_LOW,
> +	VIRTIO_USB_SPEED_FULL, /* usb 1.1 */
> +	VIRTIO_USB_SPEED_HIGH, /* usb 2.0 */
> +	VIRTIO_USB_SPEED_WIRELESS, /* wireless (usb 2.5) */
> +	VIRTIO_USB_USB_SPEED_SUPER, /* usb 3.0 */
> +	VIRTIO_USB_SPEED_SUPER_PLUS, /* usb 3.1 */

Please enumerate all of your enums with explicit values as these are
going to userspace.

> +};
> +
> +struct virtio_usb_host_port_event {
> +	__le32 code; /* VIRTIO_USB_EVT_HOST_PORT_XXX */
> +	__le32 port_id;
> +	__le32 speed; /* VIRTIO_USB_SPEED_XXX */
> +	__le32 padding;

All of your padding fields MUST be verified to only be set to 0.

These are basic "how to write a uapi" things, did you all not read the
in-kernel documentation for this?

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH 0/8] virtio-usb: add dual-role virtio USB driver
  2026-09-25  5:17 ` [PATCH 0/8] virtio-usb: add dual-role virtio USB driver Greg Kroah-Hartman
@ 2026-09-28 13:55   ` Igor Skalkin
  2026-09-28 14:44     ` Greg Kroah-Hartman
  2026-09-29  9:47     ` Michael S. Tsirkin
  0 siblings, 2 replies; 24+ messages in thread
From: Igor Skalkin @ 2026-09-28 13:55 UTC (permalink / raw)
  To: Greg Kroah-Hartman
  Cc: Michael S . Tsirkin, Jason Wang, virtualization, linux-usb,
	Vasilii Ianikeev, Aiswarya Cyriac, Anton Yakovlev, Trilok Soni



On 9/25/2026 7:17 AM, Greg Kroah-Hartman wrote:
> On Thu, Sep 24, 2026 at 06:08:59PM +0200, Igor Skalkin wrote:
>> This series adds a new virtio-usb driver: a dual-role virtio device
>> capable of acting as a USB host controller, a USB device controller,
>> or both simultaneously with runtime role switching between the two
>> (USB OTG-style role switching) on ports that support it.
>>
>> The corresponding virtio-usb device specification has been posted to
>> virtio-comment for review. This series matches the v2 revision of
>> that spec, which reconciles a small number of protocol details
>> (per-role virtqueue presentation, host-role vp_idx for hub/multi-VP
>> support, and the device-role BIND/UNBIND event split) that were
>> clarified while integrating and testing this driver against the
>> spec:
> 
> Why do we need this at all when we have other ways of doing usb devices
> through virtio?
> 
> Why is a USB virtio spec needed at all, who is going to use it?
> 
> 
For device classes that already have a virtio equivalent (storage, HID,
video, audio) there's no need for virtio-usb: we can use
virtio-blk/virtio-input/virtio-video/virtio-snd directly.
What virtio-usb actually addresses is different: protocols that are
about raw USB semantics itself, not about any particular device class.
Two concrete cases we care about. ADB (Android Debug Bridge), a specific
USB interface/vendor-class protocol used throughout Android development
and debugging, with no meaningful way to express it as a block or HID
device. And Android Auto (AOA) / Apple CarPlay, USB-level
control-transfer and vendor-negotiation protocols used when a phone is
plugged into an automotive head unit.
Our motivating use case is automotive cockpit virtualization: a physical
USB port where a phone is plugged in needs to be handed to a guest VM
running the head-unit stack, and that guest needs to run one of these
USB-native protocols. The existing software on both sides already speaks
raw USB and works unmodified if it sees a real-looking USB device.
virtio-usb lets that keep working, instead of needing a new bespoke
virtio spec for every such protocol as new USB-based ecosystems show up.

>>
>>   [RFC PATCH v2] virtio-usb: Add initial virtio-usb specification
>>   Igor Skalkin <igor.skalkin@oss.qualcomm.com>
>>   virtio-comment@lists.linux.dev
>>   https://lore.kernel.org/virtio-comment/20260924154007.143927-1-igor.skalkin@oss.qualcomm.com/
>>
>> This driver has been tested end-to-end against our own userspace
>> virtio-usb device implementation (host-side backend) in two setups:
> 
> Where is that code and why isn't it part of this submission?
> 
The backend we used for the testing described above is an internal
implementation that we're not releasing as part of this submission - it
integrates with some systems that aren't ready to be public. We
recognize that limits independent verification of our specific test
results, and we don't think that's an ideal situation.

>>   4-5: USB OTG-style role query and role-switching support.
> 
> There's a reason OTG isn't used anymore by devices, how have you
> addressed those problems here?  And why duplicate the failures of the
> past?
> 
We're not implementing ID-pin detection, HNP, or SRP - none of that.
"OTG" here is just a reused name for something much simpler: an explicit
role-switch command exposed to the guest via sysfs. Device-to-host is
driver-initiated (guest writes to that sysfs entry); host-to-device is
host-initiated (the host switches on its own and notifies the guest).
Either direction is always granted - no negotiation step to get wrong.
Happy to rename the OTG-tagged commands/constants if the naming is
causing confusion.

>>   6:   endpoint-lifecycle robustness rework (async split-phase state
>>        machine, replacing an earlier out-of-tree gadget.nonatomic
>>        patch that didn't pass upstream review).
>>   7:   SuperSpeed device-role support.
> 
> Why should speed settings matter to a virtual connection?
>
Because the kernel APIs we're implementing on top of are inherently
speed-typed, not because of any real electrical/physical constraint.
usb_hcd (host role) and the USB Gadget API (device role) both bake speed
into their core design - it drives enumeration, bandwidth scheduling,
and descriptor selection (e.g. SuperSpeed companion descriptors) in the
USB core and in gadget function drivers. dummy_hcd is a good precedent:
it's also a purely virtual host controller with no real signaling, and
it still has to declare and support different speed configurations,
because the framework requires it. We're in the same position -
unmodified guest USB class drivers and gadget function drivers depend on
accurate speed information regardless of what's actually behind the
interface.
> thanks,
> 
> greg k-h
Thanks,
Igor


^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH 2/8] virtio-usb: add host role (USB Host Controller) support
  2026-09-25  5:18   ` Greg Kroah-Hartman
@ 2026-09-28 14:01     ` Igor Skalkin
  0 siblings, 0 replies; 24+ messages in thread
From: Igor Skalkin @ 2026-09-28 14:01 UTC (permalink / raw)
  To: Greg Kroah-Hartman
  Cc: Michael S . Tsirkin, Jason Wang, virtualization, linux-usb,
	Vasilii Ianikeev, Aiswarya Cyriac, Anton Yakovlev, Trilok Soni



On 9/25/2026 7:18 AM, Greg Kroah-Hartman wrote:
> On Thu, Sep 24, 2026 at 06:09:01PM +0200, Igor Skalkin wrote:
>> From: Aiswarya Cyriac <aiswarya.cyriac@oss.qualcomm.com>
>>
>> Add the common virtqueue handling code (command, event and data
>> queues) shared by every role, and the virtio-usb host controller
>> (HCD) implementation, wiring it up as the host role of the dual-role
>> driver on top of that common code.
>>
>> Each host-role virtual port gets its own HS+SS usb_hcd pair and its
>> own root hub (struct virtio_usb_hc_vp), with a fixed
>> VIRTIO_USB_VP_MAX_PORTS (8) leaf slots pre-allocated at VP init time
>> and reused across connect/disconnect - never dynamically alloc'd or
>> freed. This lets the backend forward more than one physical socket -
>> and, for host ports behind a physical hub, more than one leaf device
>> per socket - as independent virtual ports from the start, instead of
>> collapsing everything onto a single shared root hub and having to
>> revisit that decision once more than one host-role port needs to
>> exist at the same time.
>>
>> virtio_usb_add_hcd() derives each VP's HCD bus_name from the parent
>> virtio_device with devm_kasprintf() rather than a stack buffer, since
>> usb_create_hcd()/usb_create_shared_hcd() store that pointer as-is in
>> hcd->self.bus_name without copying it - it must outlive the HCD
>> itself.
>>
>> Every port is host-role for now, since no other role exists yet;
>> vports[].role is populated unconditionally until later commits add
>> device role and OTG-based role resolution.
>>
>> Signed-off-by: Aiswarya Cyriac <aiswarya.cyriac@oss.qualcomm.com>
>> Co-developed-by: Anton Yakovlev <anton.yakovlev@oss.qualcomm.com>
>> Signed-off-by: Anton Yakovlev <anton.yakovlev@oss.qualcomm.com>
>> Signed-off-by: Vasilii Ianikeev <vasilii.ianikeev@oss.qualcomm.com>
>> Co-developed-by: Igor Skalkin <igor.skalkin@oss.qualcomm.com>
>> Signed-off-by: Igor Skalkin <igor.skalkin@oss.qualcomm.com>
>> ---
>>  drivers/usb/virtio_usb/Makefile     |    4 
>>  drivers/usb/virtio_usb/controller.c |  105 ++
>>  drivers/usb/virtio_usb/controller.h |   35 
>>  drivers/usb/virtio_usb/host.c       | 1335 ++++++++++++++++++++++++++++++++++++
>>  drivers/usb/virtio_usb/host.h       |  203 +++++
>>  drivers/usb/virtio_usb/vq_common.c  |  740 +++++++++++++++++++
>>  drivers/usb/virtio_usb/vq_common.h  |  163 ++++
>>  include/uapi/linux/virtio_usb.h     |   16 
>>  8 files changed, 2585 insertions(+), 16 deletions(-)
>>  create mode 100644 drivers/usb/virtio_usb/host.c
>>  create mode 100644 drivers/usb/virtio_usb/host.h
>>  create mode 100644 drivers/usb/virtio_usb/vq_common.c
>>  create mode 100644 drivers/usb/virtio_usb/vq_common.h
>>
>> diff --git a/drivers/usb/virtio_usb/controller.c b/drivers/usb/virtio_usb/controller.c
>> index 2fc6f50..216edfc 100644
>> --- a/drivers/usb/virtio_usb/controller.c
>> +++ b/drivers/usb/virtio_usb/controller.c
>> @@ -6,9 +6,16 @@
>>   */
>>  
>>  #include <linux/module.h>
>> +#include <linux/moduleparam.h>
>>  #include <uapi/linux/virtio_ids.h>
>>  
>>  #include "controller.h"
>> +#include "host.h"
>> +#include "vq_common.h"
>> +
>> +u32 virtio_usb_cmd_timeout_ms = MSEC_PER_SEC;
>> +module_param_named(cmd_timeout_ms, virtio_usb_cmd_timeout_ms, uint, 0644);
>> +MODULE_PARM_DESC(cmd_timeout_ms, "Command completion timeout in milliseconds");
> 
> This is not the 1990's, please do not add new module parameters.  Just
> make it work without manual configuration at module load time.
> 
> If you really need a configuration option, make it per-device and use
> the correct, modern, apis for it.
> 
Agreed, will remove it.

> thanks,
> 
> greg k-h


^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH 1/8] virtio-usb: add protocol header and skeleton dual-role driver
  2026-09-25  5:21   ` Greg Kroah-Hartman
@ 2026-09-28 14:14     ` Igor Skalkin
  0 siblings, 0 replies; 24+ messages in thread
From: Igor Skalkin @ 2026-09-28 14:14 UTC (permalink / raw)
  To: Greg Kroah-Hartman
  Cc: Michael S . Tsirkin, Jason Wang, virtualization, linux-usb,
	Vasilii Ianikeev, Aiswarya Cyriac, Anton Yakovlev, Trilok Soni



On 9/25/2026 7:21 AM, Greg Kroah-Hartman wrote:
> On Thu, Sep 24, 2026 at 06:09:00PM +0200, Igor Skalkin wrote:
>> +/* VIRTIO_USB_EVT_HOST_PORT_CONNECTED/DISCONNECTED */
>> +enum {
>> +	VIRTIO_USB_SPEED_UNKNOWN = 0,
>> +	VIRTIO_USB_SPEED_LOW,
>> +	VIRTIO_USB_SPEED_FULL, /* usb 1.1 */
>> +	VIRTIO_USB_SPEED_HIGH, /* usb 2.0 */
>> +	VIRTIO_USB_SPEED_WIRELESS, /* wireless (usb 2.5) */
>> +	VIRTIO_USB_USB_SPEED_SUPER, /* usb 3.0 */
>> +	VIRTIO_USB_SPEED_SUPER_PLUS, /* usb 3.1 */
> 
> Please enumerate all of your enums with explicit values as these are
> going to userspace.
> 
Will convert these to #define constants.

>> +};
>> +
>> +struct virtio_usb_host_port_event {
>> +	__le32 code; /* VIRTIO_USB_EVT_HOST_PORT_XXX */
>> +	__le32 port_id;
>> +	__le32 speed; /* VIRTIO_USB_SPEED_XXX */
>> +	__le32 padding;
> 
> All of your padding fields MUST be verified to only be set to 0.
> 
> These are basic "how to write a uapi" things, did you all not read the
> in-kernel documentation for this?
> 
Agreed, will be fixed.

> thanks,
> 
> greg k-h

Thanks,
Igor


^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH 1/8] virtio-usb: add protocol header and skeleton dual-role driver
  2026-09-25  5:14   ` Greg Kroah-Hartman
@ 2026-09-28 14:19     ` Igor Skalkin
  0 siblings, 0 replies; 24+ messages in thread
From: Igor Skalkin @ 2026-09-28 14:19 UTC (permalink / raw)
  To: Greg Kroah-Hartman
  Cc: Michael S . Tsirkin, Jason Wang, virtualization, linux-usb,
	Vasilii Ianikeev, Aiswarya Cyriac, Anton Yakovlev, Trilok Soni



On 9/25/2026 7:14 AM, Greg Kroah-Hartman wrote:
> On Thu, Sep 24, 2026 at 06:09:00PM +0200, Igor Skalkin wrote:
>> From: Aiswarya Cyriac <aiswarya.cyriac@oss.qualcomm.com>
>>
>> Add the virtio_usb device and virtqueue-wrapper skeleton, the
>> protocol header shared with the vhost gadget driver and userspace
>> backend, and probe()/remove() plumbing with no role support yet.
>>
>> Introduce a per-port virtio_usb_port array (vports[]), sized to the
>> device's negotiated port count, ahead of any code that actually
>> populates or reads it. Every later commit that adds a role to a port
>> (host, device, OTG) builds on this same array from the start, instead
>> of the host, device, and OTG subsystems each growing their own
>> separate port-indexed storage that later has to be reconciled.
>>
>> Wire the new drivers/usb/virtio_usb/ directory into the USB
>> subsystem's build (drivers/usb/Kconfig, drivers/usb/Makefile) as a
>> new CONFIG_USB_VIRTIO option, listed alongside the other USB
>> dual-mode controller drivers.
>>
>> Signed-off-by: Igor Skalkin <igor.skalkin@oss.qualcomm.com>
> 
> Why does this not match the author name?
> 
Sorry, this is an artifact of squashing a much larger internal history
(100+ commits) down to this 8-commit series. Will be fixed.

> thanks,
> 
> greg k-h
Thanks,
Igor

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH 0/8] virtio-usb: add dual-role virtio USB driver
  2026-09-28 13:55   ` Igor Skalkin
@ 2026-09-28 14:44     ` Greg Kroah-Hartman
  2026-09-28 15:56       ` Igor Skalkin
  2026-09-29  9:47     ` Michael S. Tsirkin
  1 sibling, 1 reply; 24+ messages in thread
From: Greg Kroah-Hartman @ 2026-09-28 14:44 UTC (permalink / raw)
  To: Igor Skalkin
  Cc: Michael S . Tsirkin, Jason Wang, virtualization, linux-usb,
	Vasilii Ianikeev, Aiswarya Cyriac, Anton Yakovlev, Trilok Soni

On Mon, Sep 28, 2026 at 03:55:38PM +0200, Igor Skalkin wrote:
> 
> 
> On 9/25/2026 7:17 AM, Greg Kroah-Hartman wrote:
> > On Thu, Sep 24, 2026 at 06:08:59PM +0200, Igor Skalkin wrote:
> >> This series adds a new virtio-usb driver: a dual-role virtio device
> >> capable of acting as a USB host controller, a USB device controller,
> >> or both simultaneously with runtime role switching between the two
> >> (USB OTG-style role switching) on ports that support it.
> >>
> >> The corresponding virtio-usb device specification has been posted to
> >> virtio-comment for review. This series matches the v2 revision of
> >> that spec, which reconciles a small number of protocol details
> >> (per-role virtqueue presentation, host-role vp_idx for hub/multi-VP
> >> support, and the device-role BIND/UNBIND event split) that were
> >> clarified while integrating and testing this driver against the
> >> spec:
> > 
> > Why do we need this at all when we have other ways of doing usb devices
> > through virtio?
> > 
> > Why is a USB virtio spec needed at all, who is going to use it?
> > 
> > 
> For device classes that already have a virtio equivalent (storage, HID,
> video, audio) there's no need for virtio-usb: we can use
> virtio-blk/virtio-input/virtio-video/virtio-snd directly.
> What virtio-usb actually addresses is different: protocols that are
> about raw USB semantics itself, not about any particular device class.
> Two concrete cases we care about. ADB (Android Debug Bridge), a specific
> USB interface/vendor-class protocol used throughout Android development
> and debugging, with no meaningful way to express it as a block or HID
> device. And Android Auto (AOA) / Apple CarPlay, USB-level
> control-transfer and vendor-negotiation protocols used when a phone is
> plugged into an automotive head unit.
> Our motivating use case is automotive cockpit virtualization: a physical
> USB port where a phone is plugged in needs to be handed to a guest VM
> running the head-unit stack, and that guest needs to run one of these
> USB-native protocols. The existing software on both sides already speaks
> raw USB and works unmodified if it sees a real-looking USB device.
> virtio-usb lets that keep working, instead of needing a new bespoke
> virtio spec for every such protocol as new USB-based ecosystems show up.

So you just want "raw" usb, then why not use usb-ip?  Isn't that what
it's there for?  Or just mount usbfs and expose that to the host as
that's what adb is using already, right?

> >>   [RFC PATCH v2] virtio-usb: Add initial virtio-usb specification
> >>   Igor Skalkin <igor.skalkin@oss.qualcomm.com>
> >>   virtio-comment@lists.linux.dev
> >>   https://lore.kernel.org/virtio-comment/20260924154007.143927-1-igor.skalkin@oss.qualcomm.com/
> >>
> >> This driver has been tested end-to-end against our own userspace
> >> virtio-usb device implementation (host-side backend) in two setups:
> > 
> > Where is that code and why isn't it part of this submission?
> > 
> The backend we used for the testing described above is an internal
> implementation that we're not releasing as part of this submission - it
> integrates with some systems that aren't ready to be public. We
> recognize that limits independent verification of our specific test
> results, and we don't think that's an ideal situation.

Then we can't even review this at all, sorry, you all know better than
that.

> >>   4-5: USB OTG-style role query and role-switching support.
> > 
> > There's a reason OTG isn't used anymore by devices, how have you
> > addressed those problems here?  And why duplicate the failures of the
> > past?
> > 
> We're not implementing ID-pin detection, HNP, or SRP - none of that.
> "OTG" here is just a reused name for something much simpler: an explicit
> role-switch command exposed to the guest via sysfs. Device-to-host is
> driver-initiated (guest writes to that sysfs entry); host-to-device is
> host-initiated (the host switches on its own and notifies the guest).
> Either direction is always granted - no negotiation step to get wrong.
> Happy to rename the OTG-tagged commands/constants if the naming is
> causing confusion.

"OTG" has a _VERY_ specific definition in the USB world, don't attempt
to re-define it please.  That way lies madness...

> >>   6:   endpoint-lifecycle robustness rework (async split-phase state
> >>        machine, replacing an earlier out-of-tree gadget.nonatomic
> >>        patch that didn't pass upstream review).
> >>   7:   SuperSpeed device-role support.
> > 
> > Why should speed settings matter to a virtual connection?
> >
> Because the kernel APIs we're implementing on top of are inherently
> speed-typed, not because of any real electrical/physical constraint.
> usb_hcd (host role) and the USB Gadget API (device role) both bake speed
> into their core design - it drives enumeration, bandwidth scheduling,
> and descriptor selection (e.g. SuperSpeed companion descriptors) in the
> USB core and in gadget function drivers. dummy_hcd is a good precedent:
> it's also a purely virtual host controller with no real signaling, and
> it still has to declare and support different speed configurations,
> because the framework requires it. We're in the same position -
> unmodified guest USB class drivers and gadget function drivers depend on
> accurate speed information regardless of what's actually behind the
> interface.

This is a virtual connection, speed means nothing here other than some
descriptor stuff in a few places.

Again, try using the existing code, usb-ip or usbfs, don't invent
something new, especially when it's not even visable to anyone.  Would
you want to attempt to review something like this in that situation?

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH 0/8] virtio-usb: add dual-role virtio USB driver
  2026-09-28 14:44     ` Greg Kroah-Hartman
@ 2026-09-28 15:56       ` Igor Skalkin
  2026-09-28 16:10         ` Greg Kroah-Hartman
  0 siblings, 1 reply; 24+ messages in thread
From: Igor Skalkin @ 2026-09-28 15:56 UTC (permalink / raw)
  To: Greg Kroah-Hartman
  Cc: Michael S . Tsirkin, Jason Wang, virtualization, linux-usb,
	Vasilii Ianikeev, Aiswarya Cyriac, Anton Yakovlev, Trilok Soni



On 9/28/2026 4:44 PM, Greg Kroah-Hartman wrote:
> On Mon, Sep 28, 2026 at 03:55:38PM +0200, Igor Skalkin wrote:
>>
>>
>> On 9/25/2026 7:17 AM, Greg Kroah-Hartman wrote:
>>> On Thu, Sep 24, 2026 at 06:08:59PM +0200, Igor Skalkin wrote:
>>>> This series adds a new virtio-usb driver: a dual-role virtio device
>>>> capable of acting as a USB host controller, a USB device controller,
>>>> or both simultaneously with runtime role switching between the two
>>>> (USB OTG-style role switching) on ports that support it.
>>>>
>>>> The corresponding virtio-usb device specification has been posted to
>>>> virtio-comment for review. This series matches the v2 revision of
>>>> that spec, which reconciles a small number of protocol details
>>>> (per-role virtqueue presentation, host-role vp_idx for hub/multi-VP
>>>> support, and the device-role BIND/UNBIND event split) that were
>>>> clarified while integrating and testing this driver against the
>>>> spec:
>>>
>>> Why do we need this at all when we have other ways of doing usb devices
>>> through virtio?
>>>
>>> Why is a USB virtio spec needed at all, who is going to use it?
>>>
>>>
>> For device classes that already have a virtio equivalent (storage, HID,
>> video, audio) there's no need for virtio-usb: we can use
>> virtio-blk/virtio-input/virtio-video/virtio-snd directly.
>> What virtio-usb actually addresses is different: protocols that are
>> about raw USB semantics itself, not about any particular device class.
>> Two concrete cases we care about. ADB (Android Debug Bridge), a specific
>> USB interface/vendor-class protocol used throughout Android development
>> and debugging, with no meaningful way to express it as a block or HID
>> device. And Android Auto (AOA) / Apple CarPlay, USB-level
>> control-transfer and vendor-negotiation protocols used when a phone is
>> plugged into an automotive head unit.
>> Our motivating use case is automotive cockpit virtualization: a physical
>> USB port where a phone is plugged in needs to be handed to a guest VM
>> running the head-unit stack, and that guest needs to run one of these
>> USB-native protocols. The existing software on both sides already speaks
>> raw USB and works unmodified if it sees a real-looking USB device.
>> virtio-usb lets that keep working, instead of needing a new bespoke
>> virtio spec for every such protocol as new USB-based ecosystems show up.
> 
> So you just want "raw" usb, then why not use usb-ip?  Isn't that what
> it's there for?  Or just mount usbfs and expose that to the host as
> that's what adb is using already, right?
> 
usb-ip's host and guest sides are both Linux-specific - in some of our
target deployments the host OS isn't Linux at all (e.g. QNX), so there's
no usb-ip host-side implementation to use in the first place.
virtio-usb's backend only needs to speak the virtio transport, which is
host-OS-agnostic.
Separately, usb-ip requires explicit manual configuration on both sides
for every device. Its own checklist also calls for disabling SELinux and
opening a TCP port. We're trying to avoid that operational overhead
(ideally - fully virtualized vanilla Android as a guest).
usbfs is a different layer - it lets a local process talk to a
locally-attached device (how the ADB host daemon works today), but
doesn't address getting the USB device into the guest in the first place.
And usbfs is Linux-specific too.
>>>>   [RFC PATCH v2] virtio-usb: Add initial virtio-usb specification
>>>>   Igor Skalkin <igor.skalkin@oss.qualcomm.com>
>>>>   virtio-comment@lists.linux.dev
>>>>   https://lore.kernel.org/virtio-comment/20260924154007.143927-1-igor.skalkin@oss.qualcomm.com/
>>>>
>>>> This driver has been tested end-to-end against our own userspace
>>>> virtio-usb device implementation (host-side backend) in two setups:
>>>
>>> Where is that code and why isn't it part of this submission?
>>>
>> The backend we used for the testing described above is an internal
>> implementation that we're not releasing as part of this submission - it
>> integrates with some systems that aren't ready to be public. We
>> recognize that limits independent verification of our specific test
>> results, and we don't think that's an ideal situation.
> 
> Then we can't even review this at all, sorry, you all know better than
> that.
We're considering publishing a virtio-usb test device for QEMU to make
independent testing easier.>
>>>>   4-5: USB OTG-style role query and role-switching support.
>>>
>>> There's a reason OTG isn't used anymore by devices, how have you
>>> addressed those problems here?  And why duplicate the failures of the
>>> past?
>>>
>> We're not implementing ID-pin detection, HNP, or SRP - none of that.
>> "OTG" here is just a reused name for something much simpler: an explicit
>> role-switch command exposed to the guest via sysfs. Device-to-host is
>> driver-initiated (guest writes to that sysfs entry); host-to-device is
>> host-initiated (the host switches on its own and notifies the guest).
>> Either direction is always granted - no negotiation step to get wrong.
>> Happy to rename the OTG-tagged commands/constants if the naming is
>> causing confusion.
> 
> "OTG" has a _VERY_ specific definition in the USB world, don't attempt
> to re-define it please.  That way lies madness...
> 
>>>>   6:   endpoint-lifecycle robustness rework (async split-phase state
>>>>        machine, replacing an earlier out-of-tree gadget.nonatomic
>>>>        patch that didn't pass upstream review).
>>>>   7:   SuperSpeed device-role support.
>>>
>>> Why should speed settings matter to a virtual connection?
>>>
>> Because the kernel APIs we're implementing on top of are inherently
>> speed-typed, not because of any real electrical/physical constraint.
>> usb_hcd (host role) and the USB Gadget API (device role) both bake speed
>> into their core design - it drives enumeration, bandwidth scheduling,
>> and descriptor selection (e.g. SuperSpeed companion descriptors) in the
>> USB core and in gadget function drivers. dummy_hcd is a good precedent:
>> it's also a purely virtual host controller with no real signaling, and
>> it still has to declare and support different speed configurations,
>> because the framework requires it. We're in the same position -
>> unmodified guest USB class drivers and gadget function drivers depend on
>> accurate speed information regardless of what's actually behind the
>> interface.
> 
> This is a virtual connection, speed means nothing here other than some
> descriptor stuff in a few places.
> 
OK, will change commit message. In this commit I just add some SS
specific details to EP0 processing and fix SS issues.

> Again, try using the existing code, usb-ip or usbfs, don't invent
> something new, especially when it's not even visable to anyone.  Would
> you want to attempt to review something like this in that situation?
> 
Answered both points above (usb-ip/usbfs, and the backend visibility
question) - happy to go deeper on either if those answers don't address
your concern.
> thanks,
> 
> greg k-h
Thanks,
Igor


^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH 0/8] virtio-usb: add dual-role virtio USB driver
  2026-09-28 15:56       ` Igor Skalkin
@ 2026-09-28 16:10         ` Greg Kroah-Hartman
  0 siblings, 0 replies; 24+ messages in thread
From: Greg Kroah-Hartman @ 2026-09-28 16:10 UTC (permalink / raw)
  To: Igor Skalkin
  Cc: Michael S . Tsirkin, Jason Wang, virtualization, linux-usb,
	Vasilii Ianikeev, Aiswarya Cyriac, Anton Yakovlev, Trilok Soni

On Mon, Sep 28, 2026 at 05:56:22PM +0200, Igor Skalkin wrote:
> 
> 
> On 9/28/2026 4:44 PM, Greg Kroah-Hartman wrote:
> > On Mon, Sep 28, 2026 at 03:55:38PM +0200, Igor Skalkin wrote:
> >>
> >>
> >> On 9/25/2026 7:17 AM, Greg Kroah-Hartman wrote:
> >>> On Thu, Sep 24, 2026 at 06:08:59PM +0200, Igor Skalkin wrote:
> >>>> This series adds a new virtio-usb driver: a dual-role virtio device
> >>>> capable of acting as a USB host controller, a USB device controller,
> >>>> or both simultaneously with runtime role switching between the two
> >>>> (USB OTG-style role switching) on ports that support it.
> >>>>
> >>>> The corresponding virtio-usb device specification has been posted to
> >>>> virtio-comment for review. This series matches the v2 revision of
> >>>> that spec, which reconciles a small number of protocol details
> >>>> (per-role virtqueue presentation, host-role vp_idx for hub/multi-VP
> >>>> support, and the device-role BIND/UNBIND event split) that were
> >>>> clarified while integrating and testing this driver against the
> >>>> spec:
> >>>
> >>> Why do we need this at all when we have other ways of doing usb devices
> >>> through virtio?
> >>>
> >>> Why is a USB virtio spec needed at all, who is going to use it?
> >>>
> >>>
> >> For device classes that already have a virtio equivalent (storage, HID,
> >> video, audio) there's no need for virtio-usb: we can use
> >> virtio-blk/virtio-input/virtio-video/virtio-snd directly.
> >> What virtio-usb actually addresses is different: protocols that are
> >> about raw USB semantics itself, not about any particular device class.
> >> Two concrete cases we care about. ADB (Android Debug Bridge), a specific
> >> USB interface/vendor-class protocol used throughout Android development
> >> and debugging, with no meaningful way to express it as a block or HID
> >> device. And Android Auto (AOA) / Apple CarPlay, USB-level
> >> control-transfer and vendor-negotiation protocols used when a phone is
> >> plugged into an automotive head unit.
> >> Our motivating use case is automotive cockpit virtualization: a physical
> >> USB port where a phone is plugged in needs to be handed to a guest VM
> >> running the head-unit stack, and that guest needs to run one of these
> >> USB-native protocols. The existing software on both sides already speaks
> >> raw USB and works unmodified if it sees a real-looking USB device.
> >> virtio-usb lets that keep working, instead of needing a new bespoke
> >> virtio spec for every such protocol as new USB-based ecosystems show up.
> > 
> > So you just want "raw" usb, then why not use usb-ip?  Isn't that what
> > it's there for?  Or just mount usbfs and expose that to the host as
> > that's what adb is using already, right?
> > 
> usb-ip's host and guest sides are both Linux-specific - in some of our
> target deployments the host OS isn't Linux at all (e.g. QNX), so there's
> no usb-ip host-side implementation to use in the first place.
> virtio-usb's backend only needs to speak the virtio transport, which is
> host-OS-agnostic.

But you are making a brand new virtio transport, which will have to be
written for all OSes.  usb-ip already works on other operating systems
today.  Same for usbfs through libusb.

> Separately, usb-ip requires explicit manual configuration on both sides
> for every device. Its own checklist also calls for disabling SELinux and
> opening a TCP port. We're trying to avoid that operational overhead
> (ideally - fully virtualized vanilla Android as a guest).
> usbfs is a different layer - it lets a local process talk to a
> locally-attached device (how the ADB host daemon works today), but
> doesn't address getting the USB device into the guest in the first place.
> And usbfs is Linux-specific too.

No, see libusb, it abstracts that away.

And really, why do you want to do anything other than Linux.  Or more
realisticly, why should _I_ care about anything other than Linux?  :)

Again, mount usbfs and away you go, libusb is your friend :)

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH 0/8] virtio-usb: add dual-role virtio USB driver
  2026-09-28 13:55   ` Igor Skalkin
  2026-09-28 14:44     ` Greg Kroah-Hartman
@ 2026-09-29  9:47     ` Michael S. Tsirkin
  2026-09-29 16:01       ` Greg Kroah-Hartman
  1 sibling, 1 reply; 24+ messages in thread
From: Michael S. Tsirkin @ 2026-09-29  9:47 UTC (permalink / raw)
  To: Igor Skalkin
  Cc: Greg Kroah-Hartman, Jason Wang, virtualization, linux-usb,
	Vasilii Ianikeev, Aiswarya Cyriac, Anton Yakovlev, Trilok Soni

On Mon, Sep 28, 2026 at 03:55:38PM +0200, Igor Skalkin wrote:
> >>
> >>   [RFC PATCH v2] virtio-usb: Add initial virtio-usb specification
> >>   Igor Skalkin <igor.skalkin@oss.qualcomm.com>
> >>   virtio-comment@lists.linux.dev
> >>   https://lore.kernel.org/virtio-comment/20260924154007.143927-1-igor.skalkin@oss.qualcomm.com/
> >>
> >> This driver has been tested end-to-end against our own userspace
> >> virtio-usb device implementation (host-side backend) in two setups:
> > 
> > Where is that code and why isn't it part of this submission?
> > 
> The backend we used for the testing described above is an internal
> implementation that we're not releasing as part of this submission - it
> integrates with some systems that aren't ready to be public. We
> recognize that limits independent verification of our specific test
> results, and we don't think that's an ideal situation.

Supporting vhost-user with a backend doing pass-through shouldn't be too hard.

-- 
MST


^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH 0/8] virtio-usb: add dual-role virtio USB driver
  2026-09-29  9:47     ` Michael S. Tsirkin
@ 2026-09-29 16:01       ` Greg Kroah-Hartman
  2026-09-29 19:02         ` Vasilii Ianikeev
  2026-09-29 19:58         ` Vasilii Ianikeev
  0 siblings, 2 replies; 24+ messages in thread
From: Greg Kroah-Hartman @ 2026-09-29 16:01 UTC (permalink / raw)
  To: Michael S. Tsirkin
  Cc: Igor Skalkin, Jason Wang, virtualization, linux-usb,
	Vasilii Ianikeev, Aiswarya Cyriac, Anton Yakovlev, Trilok Soni

On Tue, Sep 29, 2026 at 05:47:30AM -0400, Michael S. Tsirkin wrote:
> On Mon, Sep 28, 2026 at 03:55:38PM +0200, Igor Skalkin wrote:
> > >>
> > >>   [RFC PATCH v2] virtio-usb: Add initial virtio-usb specification
> > >>   Igor Skalkin <igor.skalkin@oss.qualcomm.com>
> > >>   virtio-comment@lists.linux.dev
> > >>   https://lore.kernel.org/virtio-comment/20260924154007.143927-1-igor.skalkin@oss.qualcomm.com/
> > >>
> > >> This driver has been tested end-to-end against our own userspace
> > >> virtio-usb device implementation (host-side backend) in two setups:
> > > 
> > > Where is that code and why isn't it part of this submission?
> > > 
> > The backend we used for the testing described above is an internal
> > implementation that we're not releasing as part of this submission - it
> > integrates with some systems that aren't ready to be public. We
> > recognize that limits independent verification of our specific test
> > results, and we don't think that's an ideal situation.
> 
> Supporting vhost-user with a backend doing pass-through shouldn't be too hard.

Wait, if all you want is adb to work, why not just use it in network
mode?  That should work find across a virtual machine, right?

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH 0/8] virtio-usb: add dual-role virtio USB driver
  2026-09-29 16:01       ` Greg Kroah-Hartman
@ 2026-09-29 19:02         ` Vasilii Ianikeev
  2026-09-29 19:58         ` Vasilii Ianikeev
  1 sibling, 0 replies; 24+ messages in thread
From: Vasilii Ianikeev @ 2026-09-29 19:02 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Michael S. Tsirkin
  Cc: Igor Skalkin, Jason Wang, virtualization, linux-usb,
	Aiswarya Cyriac, Anton Yakovlev, Trilok Soni

Hi all,

I'd like to join the discussion and share my perspective.

Greg's main concern is: "Why is this needed at all?" Let me explain.

First of all, our primary focus is embedded systems, including the
automotive
industry, particularly infotainment systems. In this area, the easiest
way to
provide USB access to a GVM is to pass the USB device directly through
to the
guest. This is even simpler than usb-ip, since we can rely on the
standard Linux
USB driver.

And it works well until we need more flexibility, such as:
- Sharing different USB devices connected to the same USB host among
multiple
  VMs (for instance, assigning one USB stick to the AGL GVM, another to the
  Android GVM, and a sound card to the PVM).
- Sharing multiple USB gadget functions from several VMs through the
same USB
  gadget port (for instance, providing ADB connectivity to all VMs,
including
  the PVM and both GVMs).

Once this level of flexibility is required, simple passthrough is no longer
sufficient, and we have to rely on USB sharing mechanisms.

And this brings us to your main concern: Why not simply use usb-ip to
share USB devices between the PVM and GVMs? Why introduce an entirely new
transport for USB virtualization instead of relying on existing solutions?

There are several reasons for this.

First, we aim to support not only Linux-based hosts. The host could be
running
QNX or even a bare-metal (no-OS) environment, where usb-ip and usbfs are not
available. Igor has already explained this point. However, as you rightly
pointed out:

> Or more realisticly, why should _I_ care about anything other than
Linux?  :)

It seems, you are right: why you should pay efforts reviewing our
patches, while
no one except us will be able to utilize this for non-Linux based
workspaces. In
reality, the opposite is true: if VirtIO USB becomes a standard part of the
open-source Linux kernel, USB virtualization will become
hypervisor-agnostic.
For end users who rely on open-source or commercial hypervisors, it will not
matter which hypervisor is running under the hood (for example, QNX or
QEMU).
They will be able to use the open-source Linux kernel out of the box,
without
any modifications. If they later decide to migrate to a different hypervisor
that supports the standard, the transition will require little to no effort.
Therefore, the Linux community should, in principle, be interested in
supporting
this effort.

Furthermore, this is not only about host OS, this also about guests.
Let's imagine
you want to run QNX or VxWorks image under qemu on Linux Host. Both GVM OS
supports OASIS Virtio Standard, but does not support usb-ip. Therefore,
we would
like to introduce a common approach that works across all operating systems.

And we also plan to add virtio-usb support to QEMU as well. This
approach will
be available to the broader community as well, not just as a proprietary
solution.

The second important reason is that one of the key virtio-usb features
we need,
and which partially motivated this work, is support for a dual-role USB
controller for Apple CarPlay. usb-ip cannot provide this because it
lacks OTG
support.

Third, performance is another reason for introducing a new solution.
Igor has
already partially explained this point. Existing solutions such as
usb-ip rely on sockets and therefore introduce additional overhead. This
becomes
particularly important in resource-constrained embedded systems. With
our own
implementation, we can avoid this overhead.

Fourth, as Igor has already mentioned, usb-ip and usbfs do not work out
of the
box. They still require additional configuration on the GVM side to function
properly. Yes, this is standard Linux administration, but it still requires
additional configuration and maintenance effort. In contrast, a vanilla
Linux
kernel with VirtIO USB support will boot and operate as is, requiring zero
additional effort.

And last but not least, let's be honest: usb-ip is not really about
virtualization.
Its primary goal is to share USB devices over a network. This is a perfectly
valid solution, but it serves a different purpose than virtio-usb. By
the same
reasoning, one could ask: "Why do we need virtio-gpu when we can simply
use RDP
over IP?". In other words, this is not about inventing a brand-new
transport. It
is about defining and standardizing an existing approach as an open industry
standard.

Thanks,
Vasilii Ianikeev

On 9/29/2026 6:01 PM, Greg Kroah-Hartman wrote:
> On Tue, Sep 29, 2026 at 05:47:30AM -0400, Michael S. Tsirkin wrote:
>> On Mon, Sep 28, 2026 at 03:55:38PM +0200, Igor Skalkin wrote:
>>>>>   [RFC PATCH v2] virtio-usb: Add initial virtio-usb specification
>>>>>   Igor Skalkin <igor.skalkin@oss.qualcomm.com>
>>>>>   virtio-comment@lists.linux.dev
>>>>>   https://lore.kernel.org/virtio-comment/20260924154007.143927-1-igor.skalkin@oss.qualcomm.com/
>>>>>
>>>>> This driver has been tested end-to-end against our own userspace
>>>>> virtio-usb device implementation (host-side backend) in two setups:
>>>> Where is that code and why isn't it part of this submission?
>>>>
>>> The backend we used for the testing described above is an internal
>>> implementation that we're not releasing as part of this submission - it
>>> integrates with some systems that aren't ready to be public. We
>>> recognize that limits independent verification of our specific test
>>> results, and we don't think that's an ideal situation.
>> Supporting vhost-user with a backend doing pass-through shouldn't be too hard.
> Wait, if all you want is adb to work, why not just use it in network
> mode?  That should work find across a virtual machine, right?
>
> thanks,
>
> greg k-h

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH 0/8] virtio-usb: add dual-role virtio USB driver
  2026-09-29 16:01       ` Greg Kroah-Hartman
  2026-09-29 19:02         ` Vasilii Ianikeev
@ 2026-09-29 19:58         ` Vasilii Ianikeev
  1 sibling, 0 replies; 24+ messages in thread
From: Vasilii Ianikeev @ 2026-09-29 19:58 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Michael S. Tsirkin
  Cc: Igor Skalkin, Jason Wang, virtualization, linux-usb,
	Aiswarya Cyriac, Anton Yakovlev, Trilok Soni

Hi all,

Sorry for the formatting issues.

I'd like to join the discussion and share my perspective.

Greg's main concern is: "Why is this needed at all?" Let me explain.

First of all, our primary focus is embedded systems, including the 
automotive
industry, particularly infotainment systems. In this area, the easiest 
way to
provide USB access to a GVM is to pass the USB device directly through 
to the
guest. This is even simpler than usb-ip, since we can rely on the 
standard Linux
USB driver.

And it works well until we need more flexibility, such as:
- Sharing different USB devices connected to the same USB host among 
multiple
   VMs (for instance, assigning one USB stick to the AGL GVM, another to the
   Android GVM, and a sound card to the PVM).
- Sharing multiple USB gadget functions from several VMs through the 
same USB
   gadget port (for instance, providing ADB connectivity to all VMs, 
including
   the PVM and both GVMs).

Once this level of flexibility is required, simple passthrough is no longer
sufficient, and we have to rely on USB sharing mechanisms.

And this brings us to Greg's main concern: Why not simply use usb-ip to 
share
USB devices between the PVM and GVMs? Why introduce an entirely new 
transport
for USB virtualization instead of relying on existing solutions?

There are several reasons for this.

First, we aim to support not only Linux-based hosts. The host could be 
running
QNX or even a bare-metal (no-OS) environment, where usb-ip and usbfs are not
available. Igor has already explained this point. However, as you rightly
pointed out:

 > Or more realisticly, why should _I_ care about anything other than 
Linux? :)

It seems, you are right: why you should pay efforts reviewing our 
patches, while
nonone excpet us will be able to utilize this for non-Linux based 
workpaces. In
reality, the opposite is true: if VirtIO USB becomes a standard part of the
open-source Linux kernel, USB virtualization will become 
hypervisor-agnostic.
For end users who rely on open-source or commercial hypervisors, it will not
matter which hypervisor is running under the hood (for example, QNX or 
QEMU).
They will be able to use the open-source Linux kernel out of the box, 
without
any modifications. If they later decide to migrate to a different hypervisor
that supports the standard, the transition will require little to no effort.
Therefore, the Linux community should, in principle, be interested in 
supporting
this effort.

Furthmore, this is not only about host OS, this also about guests. Let's 
imagine
you want to run QNX or VxWorks image under qemu on Linux Host. Both GVM OS
supports OASIS Virtio Standard, but does not support usb-ip. Therefore, 
we would
like to introduce a common approach that works across all operating systems.

And we also plan to add virtio-usb support to QEMU as well. This 
approach will
be available to the broader community as well, not just as a proprietary
solution.

The second important reason is that one of the key virtio-usb features 
we need,
and which partially motivated this work, is support for a dual-role USB
controller for Apple CarPlay. usb-ip cannot provide this because it 
lacks OTG
support.

Third, performance is another reason for introducing a new solution. 
Igor has
already partially explained this point. Existing solutions such as 
usb-ip rely
on sockets and therefore introduce additional overhead. This becomes
particularly important in resource-constrained embedded systems. With 
our own
implementation, we can avoid this overhead.

Fourth, as Igor has already mentioned, usb-ip and usbfs do not work out 
of the
box. They still require additional configuration on the GVM side to function
properly. Yes, this is standard Linux administration, but it still requires
additional configuration and maintenance effort.. In contrast, a vanilla 
Linux
kernel with VirtIO USB support will boot and operate as is, requiring zero
additional effort.

Last but not least, let's be honest: usb-ip is not really about 
virtualization.
Its primary goal is to share USB devices over a network. This is a perfectly
valid solution, but it serves a different purpose than virtio-usb. By 
the same
reasoning, one could ask: "Why do we need virtio-gpu when we can simply 
use RDP
over IP?". In other words, this is not about inventing a brand-new 
transport. It
is about defining and standardizing an existing approach as an open industry
standard.

Best Regards,
Vasilii

On 9/29/2026 6:01 PM, Greg Kroah-Hartman wrote:
> On Tue, Sep 29, 2026 at 05:47:30AM -0400, Michael S. Tsirkin wrote:
>> On Mon, Sep 28, 2026 at 03:55:38PM +0200, Igor Skalkin wrote:
>>>>>    [RFC PATCH v2] virtio-usb: Add initial virtio-usb specification
>>>>>    Igor Skalkin <igor.skalkin@oss.qualcomm.com>
>>>>>    virtio-comment@lists.linux.dev
>>>>>    https://lore.kernel.org/virtio-comment/20260924154007.143927-1-igor.skalkin@oss.qualcomm.com/
>>>>>
>>>>> This driver has been tested end-to-end against our own userspace
>>>>> virtio-usb device implementation (host-side backend) in two setups:
>>>> Where is that code and why isn't it part of this submission?
>>>>
>>> The backend we used for the testing described above is an internal
>>> implementation that we're not releasing as part of this submission - it
>>> integrates with some systems that aren't ready to be public. We
>>> recognize that limits independent verification of our specific test
>>> results, and we don't think that's an ideal situation.
>> Supporting vhost-user with a backend doing pass-through shouldn't be too hard.
> Wait, if all you want is adb to work, why not just use it in network
> mode?  That should work find across a virtual machine, right?
>
> thanks,
>
> greg k-h

^ permalink raw reply	[flat|nested] 24+ messages in thread

end of thread, other threads:[~2026-09-29 19:58 UTC | newest]

Thread overview: 24+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-24 16:08 [PATCH 0/8] virtio-usb: add dual-role virtio USB driver Igor Skalkin
2026-09-24 16:09 ` [PATCH 1/8] virtio-usb: add protocol header and skeleton dual-role driver Igor Skalkin
2026-09-25  5:14   ` Greg Kroah-Hartman
2026-09-28 14:19     ` Igor Skalkin
2026-09-25  5:21   ` Greg Kroah-Hartman
2026-09-28 14:14     ` Igor Skalkin
2026-09-24 16:09 ` [PATCH 2/8] virtio-usb: add host role (USB Host Controller) support Igor Skalkin
2026-09-25  5:18   ` Greg Kroah-Hartman
2026-09-28 14:01     ` Igor Skalkin
2026-09-24 16:09 ` [PATCH 3/8] virtio-usb: add device role (USB Device " Igor Skalkin
2026-09-24 16:09 ` [PATCH 4/8] virtio-usb: add OTG role query support Igor Skalkin
2026-09-24 16:09 ` [PATCH 5/8] virtio-usb: add USB On-The-Go role-switching support Igor Skalkin
2026-09-24 16:09 ` [PATCH 6/8] virtio-usb: rework endpoint lifecycle to an async split-phase state machine Igor Skalkin
2026-09-24 16:09 ` [PATCH 7/8] virtio-usb: add SuperSpeed device-role support Igor Skalkin
2026-09-24 16:09 ` [PATCH 8/8] virtio-usb: support a guest UDC name prefix from the bind event Igor Skalkin
2026-09-25  5:17 ` [PATCH 0/8] virtio-usb: add dual-role virtio USB driver Greg Kroah-Hartman
2026-09-28 13:55   ` Igor Skalkin
2026-09-28 14:44     ` Greg Kroah-Hartman
2026-09-28 15:56       ` Igor Skalkin
2026-09-28 16:10         ` Greg Kroah-Hartman
2026-09-29  9:47     ` Michael S. Tsirkin
2026-09-29 16:01       ` Greg Kroah-Hartman
2026-09-29 19:02         ` Vasilii Ianikeev
2026-09-29 19:58         ` Vasilii Ianikeev

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox