All of lore.kernel.org
 help / color / mirror / Atom feed
From: Igor Skalkin <igor.skalkin@oss.qualcomm.com>
To: "Michael S . Tsirkin" <mst@redhat.com>,
	Jason Wang <jasowangio@gmail.com>,
	Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: virtualization@lists.linux.dev, linux-usb@vger.kernel.org,
	Vasilii Ianikeev <vasilii.ianikeev@oss.qualcomm.com>,
	Aiswarya Cyriac <aiswarya.cyriac@oss.qualcomm.com>,
	Anton Yakovlev <anton.yakovlev@oss.qualcomm.com>,
	Trilok Soni <trilok.soni@oss.qualcomm.com>,
	Igor Skalkin <igor.skalkin@oss.qualcomm.com>
Subject: [PATCH 2/8] virtio-usb: add host role (USB Host Controller) support
Date: Thu, 24 Sep 2026 18:09:01 +0200	[thread overview]
Message-ID: <20260924160907.145405-3-igor.skalkin@oss.qualcomm.com> (raw)
In-Reply-To: <20260924160907.145405-1-igor.skalkin@oss.qualcomm.com>

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 */

  parent reply	other threads:[~2026-09-24 16:09 UTC|newest]

Thread overview: 32+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
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:23   ` sashiko-bot
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 ` Igor Skalkin [this message]
2026-09-24 16:25   ` [PATCH 2/8] virtio-usb: add host role (USB Host Controller) support sashiko-bot
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:28   ` sashiko-bot
2026-09-24 16:09 ` [PATCH 4/8] virtio-usb: add OTG role query support Igor Skalkin
2026-09-24 16:19   ` sashiko-bot
2026-09-24 16:09 ` [PATCH 5/8] virtio-usb: add USB On-The-Go role-switching support Igor Skalkin
2026-09-24 16:24   ` sashiko-bot
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:30   ` sashiko-bot
2026-09-24 16:09 ` [PATCH 7/8] virtio-usb: add SuperSpeed device-role support Igor Skalkin
2026-09-24 16:35   ` sashiko-bot
2026-09-24 16:09 ` [PATCH 8/8] virtio-usb: support a guest UDC name prefix from the bind event Igor Skalkin
2026-09-24 16:35   ` sashiko-bot
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

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260924160907.145405-3-igor.skalkin@oss.qualcomm.com \
    --to=igor.skalkin@oss.qualcomm.com \
    --cc=aiswarya.cyriac@oss.qualcomm.com \
    --cc=anton.yakovlev@oss.qualcomm.com \
    --cc=gregkh@linuxfoundation.org \
    --cc=jasowangio@gmail.com \
    --cc=linux-usb@vger.kernel.org \
    --cc=mst@redhat.com \
    --cc=trilok.soni@oss.qualcomm.com \
    --cc=vasilii.ianikeev@oss.qualcomm.com \
    --cc=virtualization@lists.linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.