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