Linux USB
 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: 24+ 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-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-25  5:18   ` [PATCH 2/8] virtio-usb: add host role (USB Host Controller) support 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

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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox