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 3/8] virtio-usb: add device role (USB Device Controller) support
Date: Thu, 24 Sep 2026 18:09:02 +0200 [thread overview]
Message-ID: <20260924160907.145405-4-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 device (UDC) role: implement the usb_gadget_ops and
usb_ep_ops callback sets, handle BIND/UNBIND/DISCONNECTED events
from the host backend and forward setup/suspend/resume/reset
notifications to the UDC core, and extend the protocol header with
the endpoint and command messages the device role needs.
Each device-role virtual port gets its own struct virtio_usb_dc and
UDC registration, one per vports[] entry, instead of a single global
device controller - mirroring the per-VP model host role already
uses, since a device-role port is just as independent as a host-role
one. Each gadget gets a name derived from its port index, and its
endpoint name buffer is allocated dynamically instead of using a
stack variable that would outlive the calling function.
This driver virtualizes an entire UDC per device-role port: each port
maps 1:1 to a single struct usb_gadget/UDC instance owned by exactly
one guest. A separate approach - USB function-level virtualization,
where a single physical UDC hosts a composite gadget whose
individual functions are each routed to a different guest - is
currently being explored and is out of scope for this driver.
Hardware endpoint names are used directly as the virtual endpoint
names, since the endpoint name has a relation with the endpoint
address.
Keep the device lifecycle separate from link-state notifications
from the start: BIND registers the UDC and UNBIND tears it down,
while DISCONNECTED is only a transient link-state notification (a
disconnect may be followed by SETUP without a new bind, so it must
not free UDC resources). This aligns the guest-side lifecycle with
the host-side callbacks: BIND -> (SETUP / DISCONNECT / RESET /
SUSPEND / RESUME)* -> UNBIND.
Each port also registers its UDC under its own intermediate
platform_device instead of the shared virtio_device, created on BIND
and torn down on the matching UNBIND. usb_add_gadget_udc() derives
the UDC's class name from its parent kobject's name, and every
device-role port registering under the same shared parent would
collide.
The UDC driver is marked nonatomic, since some gadget API calls
performed under a spinlock cannot be completed atomically due to the
round trip over virtio to the backend.
With both host_role and device_role negotiated, a port's actual role
is ambiguous until a later commit adds an OTG-based per-port query -
every port defaults to DEVICE for now as a placeholder.
Signed-off-by: Aiswarya Cyriac <aiswarya.cyriac@oss.qualcomm.com>
Co-developed-by: Anton Yakovlev <anton.yakovlev@oss.qualcomm.com>
Signed-off-by: Anton Yakovlev <anton.yakovlev@oss.qualcomm.com>
Co-developed-by: Igor Skalkin <igor.skalkin@oss.qualcomm.com>
Signed-off-by: Igor Skalkin <igor.skalkin@oss.qualcomm.com>
---
drivers/usb/virtio_usb/Makefile | 3
drivers/usb/virtio_usb/controller.c | 75 +
drivers/usb/virtio_usb/controller.h | 17
drivers/usb/virtio_usb/device.c | 1356 ++++++++++++++++++++++++++++++++++++
drivers/usb/virtio_usb/device.h | 91 ++
include/uapi/linux/virtio_usb.h | 37
6 files changed, 1566 insertions(+), 13 deletions(-)
create mode 100644 drivers/usb/virtio_usb/device.c
create mode 100644 drivers/usb/virtio_usb/device.h
diff --git a/drivers/usb/virtio_usb/controller.c b/drivers/usb/virtio_usb/controller.c
index 216edfc..0646807 100644
--- a/drivers/usb/virtio_usb/controller.c
+++ b/drivers/usb/virtio_usb/controller.c
@@ -11,6 +11,7 @@
#include "controller.h"
#include "host.h"
+#include "device.h"
#include "vq_common.h"
u32 virtio_usb_cmd_timeout_ms = MSEC_PER_SEC;
@@ -77,7 +78,8 @@ static int virtio_usb_validate(struct virtio_device *vdev)
return -EINVAL;
}
- if (!virtio_has_feature(vdev, VIRTIO_USB_F_HOST)) {
+ if (!virtio_has_feature(vdev, VIRTIO_USB_F_HOST) &&
+ !virtio_has_feature(vdev, VIRTIO_USB_F_DEVICE)) {
dev_err(&vdev->dev,
"device should support at least one usb role\n");
return -EINVAL;
@@ -123,17 +125,40 @@ static int virtio_usb_probe(struct virtio_device *vdev)
if (virtio_has_feature(vdev, VIRTIO_USB_F_HOST))
vusb->host_role = 1;
- /* Only host_role exists so far, so every port is unambiguously a
- * host-role port. Later commits (device role, OTG) will replace
- * this with real per-port role resolution.
+ if (virtio_has_feature(vdev, VIRTIO_USB_F_DEVICE))
+ vusb->device_role = 1;
+
+ /* Only allocate/negotiate the virtqueue triplets this instance
+ * actually needs: HOST_* only exists when host_role is negotiated,
+ * DEV_* only when device_role is negotiated. A pure single-role
+ * instance therefore has exactly VIRTIO_USB_VQ_HOST_MAX (3) or
+ * VIRTIO_USB_VQ_DEV_MAX (3) virtqueues, not a fixed layout - queues
+ * that don't exist on the wire must not be created, since a peer
+ * with no host-role VP has no host command/event/data queues to
+ * negotiate at all.
*/
vusb->host_vq_base = -1;
+ vusb->dev_vq_base = -1;
+
if (vusb->host_role) {
vusb->host_vq_base = nvqs;
nvqs += VIRTIO_USB_VQ_HOST_MAX;
+ }
+ if (vusb->device_role) {
+ vusb->dev_vq_base = nvqs;
+ nvqs += VIRTIO_USB_VQ_DEV_MAX;
+ }
- for (i = 0; i < vusb->nports; i++)
+ /* Resolve every port's role. With only one role negotiated, every
+ * port unambiguously has that role. With both negotiated, a port's
+ * own role is ambiguous until a later commit adds an OTG-based
+ * per-port query - default to DEVICE for now as a placeholder.
+ */
+ for (i = 0; i < vusb->nports; i++) {
+ if (vusb->host_role && !vusb->device_role)
vusb->vports[i].role = VIRTIO_USB_ROLE_HOST;
+ else if (vusb->device_role)
+ vusb->vports[i].role = VIRTIO_USB_ROLE_DEVICE;
}
vusb->vqueues = devm_kcalloc(&vdev->dev, nvqs, sizeof(*vusb->vqueues),
@@ -155,6 +180,18 @@ static int virtio_usb_probe(struct virtio_device *vdev)
host_vqueues[i].stop;
}
+ if (vusb->dev_vq_base >= 0)
+ for (i = 0; i < VIRTIO_USB_VQ_DEV_MAX; i++) {
+ vusb->vqueues[vusb->dev_vq_base + i].name =
+ dev_vqueues[i].name;
+ vusb->vqueues[vusb->dev_vq_base + i].callback =
+ dev_vqueues[i].callback;
+ vusb->vqueues[vusb->dev_vq_base + i].process =
+ dev_vqueues[i].process;
+ vusb->vqueues[vusb->dev_vq_base + i].stop =
+ dev_vqueues[i].stop;
+ }
+
rc = virtio_usb_find_vqs(vusb);
if (rc) {
dev_err(&vdev->dev, "%s virtio_usb_find_vqs() error(%d)\n",
@@ -190,6 +227,30 @@ static int virtio_usb_probe(struct virtio_device *vdev)
}
}
+ if (vusb->device_role) {
+ INIT_WORK(&vusb->vq_dev_data_rx_work, virtio_usb_dc_data_work);
+ INIT_WORK(&vusb->vq_dev_event_work, virtio_usb_dc_event_work);
+
+ for (i = 0; i < vusb->nports; i++) {
+ if (vusb->vports[i].role != VIRTIO_USB_ROLE_DEVICE)
+ continue;
+ rc = virtio_usb_dc_init(vusb, i);
+ if (rc) {
+ dev_err(&vdev->dev,
+ "%s virtio_usb_dc_init() port=%d error(%d)\n",
+ __func__, i, rc);
+ goto on_error;
+ }
+ }
+ rc = virtio_usb_dc_event_populate(vusb);
+ if (rc) {
+ dev_err(&vdev->dev,
+ "%s virtio_usb_dc_event_populate() error(%d)\n",
+ __func__, rc);
+ goto on_error;
+ }
+ }
+
virtio_device_ready(vdev);
return rc;
@@ -219,6 +280,9 @@ static void virtio_usb_remove(struct virtio_device *vdev)
virtio_usb_hc_vp_deinit(vusb, i);
}
+ if (vusb->device_role && vusb->vports)
+ virtio_usb_dc_deinit(vusb);
+
virtio_reset_device(vdev);
vdev->config->del_vqs(vdev);
@@ -226,6 +290,7 @@ static void virtio_usb_remove(struct virtio_device *vdev)
static const unsigned int virtio_usb_features[] = {
VIRTIO_USB_F_HOST,
+ VIRTIO_USB_F_DEVICE,
};
static const struct virtio_device_id id_table[] = {
diff --git a/drivers/usb/virtio_usb/controller.h b/drivers/usb/virtio_usb/controller.h
index af68a77..ec59922 100644
--- a/drivers/usb/virtio_usb/controller.h
+++ b/drivers/usb/virtio_usb/controller.h
@@ -16,21 +16,26 @@
/* Forward declaration - full definition in host.h */
struct virtio_usb_hc_vp;
+/* Forward declaration - full definition in device.h */
+struct virtio_usb_dc;
#define VIRTIO_USB_VQ_COMMAND_IDX 0
#define VIRTIO_USB_VQ_EVENT_IDX 1
#define VIRTIO_USB_VQ_DATA_IDX 2
#define VIRTIO_USB_VQ_HOST_MAX 3
+#define VIRTIO_USB_VQ_DEV_MAX 3
/**
* struct virtio_usb_port - Per-virtual-port state.
* @role: Role of this port (VIRTIO_USB_ROLE_HOST or _DEVICE).
* @vhc: Host controller - non-NULL when role is HOST.
+ * @vudc: Device controller - non-NULL when role is DEVICE.
*/
struct virtio_usb_port {
unsigned int role;
struct virtio_usb_hc_vp *vhc;
+ struct virtio_usb_dc *vudc;
};
/**
@@ -41,14 +46,22 @@ struct virtio_usb_port {
* @nports: number of supported ports
* @nvqs: number of virtqueues for the device
* @host_role: flag indicating support for host role
+ * @device_role: flag indicating support for device role
* @host_vq_base: index into vqueues[] where the HOST_COMMAND/EVENT/DATA
* triplet starts, or -1 if this instance has no host-role
* VP (in which case those queues do not exist on the wire
* and must not be negotiated).
+ * @dev_vq_base: index into vqueues[] where the DEV_COMMAND/EVENT/DATA
+ * triplet starts, or -1 if this instance has no
+ * device-role VP.
* @vq_host_data_rx_work: Kernel work draining the host data queue, shared
* across every host-role VP.
* @vq_host_evt_work: Kernel work draining the host event queue, shared
* across every host-role VP.
+ * @vq_dev_data_rx_work: Kernel work draining the device data queue, shared
+ * across every device-role port.
+ * @vq_dev_event_work: Kernel work draining the device event queue, shared
+ * across every device-role port.
*/
struct virtio_usb {
struct virtio_device *vdev;
@@ -57,9 +70,13 @@ struct virtio_usb {
unsigned int nports;
u32 nvqs;
bool host_role;
+ bool device_role;
int host_vq_base;
+ int dev_vq_base;
struct work_struct vq_host_data_rx_work;
struct work_struct vq_host_evt_work;
+ struct work_struct vq_dev_data_rx_work;
+ struct work_struct vq_dev_event_work;
};
/**
diff --git a/drivers/usb/virtio_usb/Makefile b/drivers/usb/virtio_usb/Makefile
index 1111111..2222222 100644
--- a/drivers/usb/virtio_usb/Makefile
+++ b/drivers/usb/virtio_usb/Makefile
@@ -1,7 +1,8 @@
# SPDX-License-Identifier: GPL-2.0-or-later
virtio-usb-y := controller.o \
vq_common.o \
- host.o
+ host.o \
+ device.o
obj-$(CONFIG_USB_VIRTIO) += virtio-usb.o
diff --git a/drivers/usb/virtio_usb/device.c b/drivers/usb/virtio_usb/device.c
new file mode 100644
index 0000000..798c265
--- /dev/null
+++ b/drivers/usb/virtio_usb/device.c
@@ -0,0 +1,1356 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * virtio_usb: VirtIO USB device
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <uapi/linux/virtio_usb.h>
+
+#include "controller.h"
+#include "device.h"
+
+#define GADGET_NAME "virtio_usb_dc"
+
+/**
+ * struct virtio_usb_dc_priv - Device controller data priv
+ * Structure for UDC related data in data messages
+ * @req: usb_request structure
+ * @vep: virtio usb endpoint
+ * @vreq: virtio-usb request
+ */
+struct virtio_usb_dc_priv {
+ struct usb_request req;
+ struct virtio_usb_ep *vep;
+ struct virtio_usb_data *vreq;
+};
+
+/**
+ * usb_ep_to_virtio_ep() - Get the virtio usb endpoint from usb endpoint
+ * @ep: usb endpoint
+ *
+ * Context: Any context.
+ * Return: Pointer to virtio_usb_ep
+ */
+static struct virtio_usb_ep *usb_ep_to_virtio_ep(struct usb_ep *ep)
+{
+ return container_of(ep, struct virtio_usb_ep, ep);
+}
+
+/**
+ * usb_ep_dir_in() - check if the endpoint has IN direction
+ * @ep: usb endpoint
+ *
+ * Context: Any context.
+ * Return: 1 if direction is USB_DIR_IN else 0
+ */
+static unsigned int usb_ep_dir_in(struct usb_ep *ep)
+{
+ struct virtio_usb_ep *vep = usb_ep_to_virtio_ep(ep);
+ unsigned int direction = 0;
+
+ if (ep->address == 0)
+ direction = vep->setup.bRequestType & USB_DIR_IN ? 1 : 0;
+ else if (ep->desc && ep->desc->bEndpointAddress)
+ direction = usb_endpoint_dir_in(ep->desc) ? 1 : 0;
+
+ return direction;
+}
+
+/**
+ * usb_req_to_virtio_data() - Get the virtio usb data message from usb_request
+ * @req: usb_request structure
+ *
+ * Context: Any context.
+ * Return: virtio usb data message
+ */
+static struct virtio_usb_data *usb_req_to_virtio_data(struct usb_request *req)
+{
+ struct virtio_usb_dc_priv *priv =
+ container_of(req, struct virtio_usb_dc_priv, req);
+
+ return priv->vreq;
+}
+
+/**
+ * virtio_usb_dc_complete_req() - Completes a usb request
+ * @vreq: virtio usb data message.
+ *
+ * Context: Process context.
+ */
+static void virtio_usb_dc_complete_req(struct virtio_usb_data *vreq)
+{
+ struct virtio_usb_response *response = virtio_usb_data_response(vreq);
+ struct virtio_usb_dc_priv *priv = virtio_usb_data_priv(vreq);
+ unsigned int status = le32_to_cpu(response->status);
+ struct usb_request *req = &priv->req;
+ struct virtio_usb_ep *vep = priv->vep;
+ struct virtio_usb_dc *vudc = vep->vudc;
+ struct virtio_usb_data *vreq_iter;
+ unsigned int found = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&vudc->lock, flags);
+
+ list_for_each_entry(vreq_iter, &vep->req_queue, list) {
+ if (vreq_iter == vreq) {
+ found = 1;
+ break;
+ }
+ }
+ if (!found) {
+ spin_unlock_irqrestore(&vudc->lock, flags);
+ return;
+ }
+ if (req->status != -ECONNRESET && req->status != -ESHUTDOWN) {
+ req->status = virtio_error_to_usb(status);
+ if (!req->status)
+ req->actual = le32_to_cpu(response->actual_length);
+ }
+
+ list_del_init(&vreq->list);
+ spin_unlock_irqrestore(&vudc->lock, flags);
+ virtio_usb_data_unref(vreq);
+ usb_gadget_giveback_request(&vep->ep, req);
+}
+
+/**
+ * virtio_usb_dc_data_work() - Worker to get all completed data message
+ * from the virtqueue and call the completion handler.
+ * @work: kernel work to handle data message completion.
+ *
+ * Context: Process context.
+ */
+void virtio_usb_dc_data_work(struct work_struct *work)
+{
+ struct virtio_usb *vusb =
+ container_of(work, struct virtio_usb, vq_dev_data_rx_work);
+ struct virtio_usb_queue *dataq =
+ &vusb->vqueues[vusb->dev_vq_base + VIRTIO_USB_VQ_DATA_IDX];
+ struct virtio_usb_data *vreq;
+ unsigned int length;
+
+ spin_lock_irq(&dataq->lock);
+ do {
+ virtqueue_disable_cb(dataq->vqueue);
+ while ((vreq = virtqueue_get_buf(dataq->vqueue, &length))) {
+ spin_unlock_irq(&dataq->lock);
+ virtio_usb_dc_complete_req(vreq);
+ spin_lock_irq(&dataq->lock);
+ }
+ if (unlikely(virtqueue_is_broken(dataq->vqueue)))
+ break;
+ } while (!virtqueue_enable_cb(dataq->vqueue));
+ spin_unlock_irq(&dataq->lock);
+}
+
+/**
+ * virtio_usb_dc_data_alloc() - Allocate and initialize a device controller
+ * data message.
+ * @ep: VirtIO usb device
+ * @gfp: Kernel flags for memory allocation.
+ *
+ * The message will be automatically freed when the ref_count value is 0.
+ *
+ * Context: Any context. May sleep if @gfp flags permit.
+ * Return: Allocated data message on success, NULL on failure.
+ */
+static struct virtio_usb_data *virtio_usb_dc_data_alloc(struct usb_ep *ep,
+ gfp_t gfp)
+{
+ struct virtio_usb_ep *vep = usb_ep_to_virtio_ep(ep);
+ struct virtio_usb_request *request;
+ struct virtio_usb_response *response;
+ size_t request_size = sizeof(*request);
+ size_t response_size = sizeof(*response);
+ struct virtio_usb_dc_priv *priv;
+ struct virtio_usb_data *vreq;
+
+ vreq = virtio_usb_data_alloc(request_size, response_size,
+ sizeof(struct virtio_usb_dc_priv), gfp);
+ if (!vreq)
+ return NULL;
+
+ priv = virtio_usb_data_priv(vreq);
+ priv->vep = vep;
+ priv->vreq = vreq;
+ vreq->msg.queue =
+ &vep->vudc->vusb->vqueues[vep->vudc->vusb->dev_vq_base +
+ VIRTIO_USB_VQ_DATA_IDX];
+
+ request = virtio_usb_data_request(vreq);
+
+ request->tag = cpu_to_le64((uintptr_t)vreq);
+
+ return vreq;
+}
+
+/**
+ * virtio_usb_dc_cmd_alloc() - Allocate and initialize the device controller
+ * command message.
+ * @vusb: VirtIO usb device
+ * @command: command message
+ * @gfp: Kernel flags for memory allocation.
+ *
+ * The message will be automatically freed when the ref_count value is 0.
+ *
+ * Context: Any context. May sleep if @gfp flags permit.
+ * Return: Allocated command message on success, NULL on failure.
+ */
+static struct virtio_usb_cmd *
+virtio_usb_dc_cmd_alloc(struct virtio_usb_dc *vudc, unsigned int command,
+ gfp_t gfp)
+{
+ size_t request_size = sizeof(struct virtio_usb_dev_cmd_hdr);
+ size_t response_size = sizeof(struct virtio_usb_cmd_status);
+ struct virtio_usb_cmd_status *status;
+ struct virtio_usb_dev_cmd_hdr *hdr;
+ struct virtio_usb_cmd *cmd;
+
+ switch (command) {
+ case VIRTIO_USB_CMD_DEV_GET_ENDPOINT_COUNT:
+ response_size = sizeof(struct virtio_usb_dev_ep_count);
+ break;
+ case VIRTIO_USB_CMD_DEV_GET_FRAME_NUMBER:
+ response_size = sizeof(struct virtio_usb_dev_frame_number);
+ break;
+ case VIRTIO_USB_CMD_DEV_CANCEL:
+ request_size = sizeof(struct virtio_usb_dev_cmd_cancel);
+ break;
+ case VIRTIO_USB_CMD_DEV_VBUS_DRAW:
+ case VIRTIO_USB_CMD_DEV_PULLUP:
+ case VIRTIO_USB_CMD_DEV_EP_SET_HALT:
+ case VIRTIO_USB_CMD_DEV_SET_SELF_POWERED:
+ request_size = sizeof(struct virtio_usb_dev_cmd_set_value);
+ break;
+ }
+
+ cmd = virtio_usb_cmd_alloc(request_size, response_size, gfp);
+ if (!cmd)
+ return NULL;
+
+ hdr = virtio_usb_cmd_request(cmd);
+ status = virtio_usb_cmd_response(cmd);
+
+ hdr->code = cpu_to_le32(command);
+ hdr->port = cpu_to_le16(vudc->port);
+ cmd->msg.queue = &vudc->vusb->vqueues[vudc->vusb->dev_vq_base +
+ VIRTIO_USB_VQ_COMMAND_IDX];
+ status->code = cpu_to_le32(VIRTIO_USB_S_ERR_CANCELLED);
+ return cmd;
+}
+
+/* Endpoint callbacks */
+
+/**
+ * virtio_ep_enable() - Enable endpoint
+ * This callback is called to configure endpoint and make it usable.
+ * It is called to enable all endpoints except ep0
+ * @ep: endpoint object
+ * @desc: endpoint descriptor
+ *
+ * Context: Any context.
+ */
+static int virtio_ep_enable(struct usb_ep *ep,
+ const struct usb_endpoint_descriptor *desc)
+{
+ struct virtio_usb_ep *vep = usb_ep_to_virtio_ep(ep);
+ struct virtio_usb_dc *vudc = vep->vudc;
+ struct scatterlist sg;
+ struct scatterlist *psg_data = &sg;
+ struct virtio_usb_dev_cmd_hdr *hdr;
+ struct virtio_usb_cmd *cmd;
+ unsigned long flags;
+ u16 endpoint;
+ int rc;
+
+ if (!vudc->driver)
+ return -ESHUTDOWN;
+
+ spin_lock_irqsave(&vudc->lock, flags);
+
+ if (!ep || !desc || ep->caps.type_control ||
+ desc->bDescriptorType != USB_DT_ENDPOINT) {
+ spin_unlock_irqrestore(&vudc->lock, flags);
+ return -EINVAL;
+ }
+
+ ep->desc = desc;
+ ep->maxpacket = usb_endpoint_maxp(desc);
+ endpoint = vep->ep_id | (usb_ep_dir_in(ep) ? VIRTIO_USB_EP_DIR_IN :
+ VIRTIO_USB_EP_DIR_OUT);
+
+ spin_unlock_irqrestore(&vudc->lock, flags);
+
+ cmd = virtio_usb_dc_cmd_alloc(vudc, VIRTIO_USB_CMD_DEV_EP_ENABLE,
+ GFP_ATOMIC);
+ if (!cmd)
+ return -ENOMEM;
+
+ sg_init_one(psg_data, desc, sizeof(*desc));
+ hdr = virtio_usb_cmd_request(cmd);
+ hdr->endpoint = cpu_to_le16(endpoint);
+
+ rc = virtio_usb_cmd_send_sync(vudc->vusb, psg_data, NULL, cmd);
+
+ return rc;
+}
+
+/**
+ * virtio_ep_disable() - Disable endpoint
+ * This callback is called to disable endpoint which was
+ * enabled using ep_enable callback
+ * @ep: endpoint object
+ *
+ * Context: Any context.
+ * Return: 0 on success, -errno on failure.
+ */
+static int virtio_ep_disable(struct usb_ep *ep)
+{
+ struct virtio_usb_ep *vep = usb_ep_to_virtio_ep(ep);
+ struct virtio_usb_data *vreq = NULL, *vreq_tmp;
+ struct virtio_usb_dc *vudc = vep->vudc;
+ struct virtio_usb_dc_priv *priv = NULL;
+ struct virtio_usb_dev_cmd_hdr *hdr;
+ struct virtio_usb_cmd *cmd;
+ unsigned long flags;
+ u16 endpoint;
+ int rc;
+
+ if (!ep || ep->caps.type_control)
+ return -EINVAL;
+
+ spin_lock_irqsave(&vudc->lock, flags);
+
+ list_for_each_entry_safe(vreq, vreq_tmp, &vep->req_queue, list) {
+ priv = virtio_usb_data_priv(vreq);
+ /**
+ * When endpoint is disabled, completion handler for all pending
+ * requests will be called. Make the request status to -ESHUTDOWN
+ * to prevent requests completes even before the endpoint disable
+ * is send to the controller.
+ */
+ priv->req.status = -ESHUTDOWN;
+ }
+ endpoint = vep->ep_id | (usb_ep_dir_in(ep) ? VIRTIO_USB_EP_DIR_IN :
+ VIRTIO_USB_EP_DIR_OUT);
+ spin_unlock_irqrestore(&vudc->lock, flags);
+
+ cmd = virtio_usb_dc_cmd_alloc(vudc, VIRTIO_USB_CMD_DEV_EP_DISABLE,
+ GFP_ATOMIC);
+ if (!cmd)
+ return -ENOMEM;
+
+ hdr = virtio_usb_cmd_request(cmd);
+ hdr->endpoint = cpu_to_le16(endpoint);
+
+ rc = virtio_usb_cmd_send_sync(vudc->vusb, NULL, NULL, cmd);
+
+ return rc;
+}
+
+/**
+ * virtio_ep_alloc_request() - Allocates request
+ * @ep: Endpoint object associated with request
+ * @mem_flags: mem flags
+ *
+ * Return: allocated request address, NULL on allocation error
+ */
+static struct usb_request *virtio_ep_alloc_request(struct usb_ep *ep,
+ gfp_t mem_flags)
+{
+ struct virtio_usb_dc_priv *priv;
+ struct virtio_usb_data *vreq;
+
+ vreq = virtio_usb_dc_data_alloc(ep, mem_flags);
+ if (!vreq)
+ return NULL;
+
+ priv = virtio_usb_data_priv(vreq);
+
+ return &priv->req;
+}
+
+/**
+ * virtio_ep_free_request() - Free memory occupied by request
+ * @ep: Endpoint object associated with request
+ * @req: Request to be freed
+ */
+static void virtio_ep_free_request(struct usb_ep *ep, struct usb_request *req)
+{
+ struct virtio_usb_data *vreq = usb_req_to_virtio_data(req);
+
+ virtio_usb_data_unref(vreq);
+}
+
+/**
+ * virtio_ep_queue() - Transfer data on and endpoint
+ * @ep: Pointer to endpoint object
+ * @req: Pointer to request object
+ * @mem_flags: gfp flags
+ *
+ * Context: Any context.
+ * Return: 0 on success, -errno on failure.
+ */
+static int virtio_ep_queue(struct usb_ep *ep, struct usb_request *req,
+ gfp_t mem_flags)
+{
+ struct scatterlist *out_sgs = NULL, *in_sgs = NULL, *psg_data;
+ struct virtio_usb_data *vreq = usb_req_to_virtio_data(req);
+ struct virtio_usb_ep *vep = usb_ep_to_virtio_ep(ep);
+ struct virtio_usb_dc *vudc = vep->vudc;
+ struct virtio_usb_response *response;
+ struct virtio_usb_request *request;
+ u16 transfer_flags = 0;
+ struct scatterlist sg;
+ unsigned long flags;
+ u16 endpoint;
+ int rc;
+
+ virtio_usb_data_ref(vreq);
+
+ spin_lock_irqsave(&vudc->lock, flags);
+ req->actual = 0;
+ req->status = -EINPROGRESS;
+
+ list_add_tail(&vreq->list, &vep->req_queue);
+ endpoint = vep->ep_id | (usb_ep_dir_in(ep) ? VIRTIO_USB_EP_DIR_IN :
+ VIRTIO_USB_EP_DIR_OUT);
+ spin_unlock_irqrestore(&vudc->lock, flags);
+
+ request = virtio_usb_data_request(vreq);
+ response = virtio_usb_data_response(vreq);
+
+ response->actual_length = cpu_to_le32(0);
+ response->status = cpu_to_le32(VIRTIO_USB_S_ERR_INTERNAL);
+
+ if (req->short_not_ok)
+ transfer_flags |= VIRTIO_USB_FLAG_SHORT_NOT_OK;
+ else if (req->zero)
+ transfer_flags |= VIRTIO_USB_FLAG_ZERO_PACKET;
+
+ request->transfer_flags = cpu_to_le16(transfer_flags);
+ request->endpoint = cpu_to_le16(endpoint);
+ request->port = cpu_to_le16(vudc->port);
+
+ if (req->length && req->buf) {
+ psg_data = &sg;
+ sg_init_one(psg_data, req->buf, req->length);
+ } else if (req->length && req->num_sgs > 0) {
+ psg_data = req->sg;
+ } else if (req->sg) {
+ psg_data = &sg;
+ sg_init_one(psg_data, sg_virt(req->sg), req->length);
+ } else {
+ psg_data = NULL;
+ }
+
+ if (usb_ep_dir_in(ep))
+ out_sgs = psg_data;
+ else
+ in_sgs = psg_data;
+
+ rc = virtio_usb_data_send(vudc->vusb, vreq, out_sgs, in_sgs);
+ if (rc)
+ goto on_error_vq;
+
+ return rc;
+
+on_error_vq:
+ spin_lock_irqsave(&vudc->lock, flags);
+ list_del_init(&vreq->list);
+ spin_unlock_irqrestore(&vudc->lock, flags);
+
+ virtio_usb_data_unref(vreq);
+
+ return rc;
+}
+
+/**
+ * virtio_ep_dequeue() - Remove request from transfer queue
+ * @ep: Endpoint object associated with request
+ * @req: Request object
+ *
+ * Context: Any context.
+ * Return: 0 on success, -errno on failure.
+ */
+static int virtio_ep_dequeue(struct usb_ep *ep, struct usb_request *req)
+{
+ struct virtio_usb_ep *vep = usb_ep_to_virtio_ep(ep);
+ struct virtio_usb_dev_cmd_cancel *cancel;
+ struct virtio_usb_dc *vudc = vep->vudc;
+ struct virtio_usb_dc_priv *priv;
+ struct virtio_usb_data *vreq;
+ struct virtio_usb_cmd *cmd;
+ unsigned long flags;
+ int rc = -EINVAL;
+ u16 endpoint;
+
+ if (!vudc->driver)
+ return -ESHUTDOWN;
+
+ spin_lock_irqsave(&vudc->lock, flags);
+
+ list_for_each_entry(vreq, &vep->req_queue, list) {
+ priv = virtio_usb_data_priv(vreq);
+ if (req == &priv->req) {
+ pr_debug("dequeue for vreq = %p, tag %llx\n", vreq,
+ (u64)(uintptr_t)vreq);
+ // request will be completed from the completion handler
+ priv->req.status = -ECONNRESET;
+ rc = 0;
+ break;
+ }
+ }
+ endpoint = vep->ep_id | (usb_ep_dir_in(ep) ? VIRTIO_USB_EP_DIR_IN :
+ VIRTIO_USB_EP_DIR_OUT);
+ spin_unlock_irqrestore(&vudc->lock, flags);
+
+ if (rc)
+ return rc;
+
+ vreq = priv->vreq;
+
+ cmd = virtio_usb_dc_cmd_alloc(vudc, VIRTIO_USB_CMD_DEV_CANCEL,
+ GFP_KERNEL);
+ if (!cmd)
+ return -ENOMEM;
+
+ cancel = virtio_usb_cmd_request(cmd);
+ cancel->hdr.endpoint = cpu_to_le16(endpoint);
+ cancel->hdr.port = cpu_to_le16(vudc->port);
+ cancel->tag = cpu_to_le64((uintptr_t)vreq);
+
+ rc = virtio_usb_cmd_send_sync(vudc->vusb, NULL, NULL, cmd);
+
+ return rc;
+}
+
+/**
+ * virtio_ep_set_halt() - Sets/clears stall on selected endpoint
+ * @ep: Endpoint object to set/clear stall on
+ * @value: 1 for set stall, 0 for clear stall
+ *
+ * Context: Any context.
+ * Return: 0 on success, -errno on failure.
+ */
+static int virtio_ep_set_halt(struct usb_ep *ep, int value)
+{
+ struct virtio_usb_ep *vep = usb_ep_to_virtio_ep(ep);
+ struct virtio_usb_dev_cmd_set_value *req;
+ struct virtio_usb_dc *vudc = vep->vudc;
+ struct virtio_usb_cmd *cmd;
+ unsigned long flags;
+ u16 endpoint;
+ int rc;
+
+ spin_lock_irqsave(&vudc->lock, flags);
+
+ /*
+ * EP0 MUST NOT STALL if a control request is still pending.
+ * Composite expects -EAGAIN instead of a forced STALL, otherwise
+ * status stage collapses and the host sees EPROTO/EPIPE.
+ */
+ if (&vep->ep == vudc->gadget.ep0) {
+ if (value && !list_empty(&vep->req_queue)) {
+ spin_unlock_irqrestore(&vudc->lock, flags);
+ return -EAGAIN;
+ }
+ }
+
+ if (value && ep->desc && usb_ep_dir_in(ep) &&
+ !list_empty(&vep->req_queue)) {
+ spin_unlock_irqrestore(&vudc->lock, flags);
+ return -EAGAIN;
+ }
+ endpoint = vep->ep_id | (usb_ep_dir_in(ep) ? VIRTIO_USB_EP_DIR_IN :
+ VIRTIO_USB_EP_DIR_OUT);
+ spin_unlock_irqrestore(&vudc->lock, flags);
+
+ cmd = virtio_usb_dc_cmd_alloc(vudc, VIRTIO_USB_CMD_DEV_EP_SET_HALT,
+ GFP_ATOMIC);
+ if (!cmd)
+ return -ENOMEM;
+
+ req = virtio_usb_cmd_request(cmd);
+ req->value = cpu_to_le32(value);
+ req->hdr.endpoint = cpu_to_le16(endpoint);
+
+ rc = virtio_usb_cmd_send_sync(vudc->vusb, NULL, NULL, cmd);
+
+ return rc;
+}
+
+/**
+ * virtio_ep_set_wedge() - Set wedge on selected endpoint
+ * @ep: Endpoint object
+ *
+ * Context: Any context.
+ * Return: 0 on success, -errno on failure.
+ */
+static int virtio_ep_set_wedge(struct usb_ep *ep)
+{
+ struct virtio_usb_ep *vep = usb_ep_to_virtio_ep(ep);
+ struct virtio_usb_dc *vudc = vep->vudc;
+ struct virtio_usb_dev_cmd_hdr *hdr;
+ struct virtio_usb_cmd *cmd;
+ unsigned long flags;
+ u16 endpoint;
+ int rc;
+
+ spin_lock_irqsave(&vudc->lock, flags);
+ endpoint = vep->ep_id | (usb_ep_dir_in(ep) ? VIRTIO_USB_EP_DIR_IN :
+ VIRTIO_USB_EP_DIR_OUT);
+ spin_unlock_irqrestore(&vudc->lock, flags);
+
+ cmd = virtio_usb_dc_cmd_alloc(vudc, VIRTIO_USB_CMD_DEV_EP_SET_WEDGE,
+ GFP_ATOMIC);
+ if (!cmd)
+ return -ENOMEM;
+
+ hdr = virtio_usb_cmd_request(cmd);
+ hdr->endpoint = cpu_to_le16(endpoint);
+
+ rc = virtio_usb_cmd_send_sync(vudc->vusb, NULL, NULL, cmd);
+
+ return rc;
+}
+
+static const struct usb_ep_ops virtio_usb_ep_ops = {
+ .enable = virtio_ep_enable,
+ .disable = virtio_ep_disable,
+
+ .alloc_request = virtio_ep_alloc_request,
+ .free_request = virtio_ep_free_request,
+
+ .queue = virtio_ep_queue,
+ .dequeue = virtio_ep_dequeue,
+
+ .set_halt = virtio_ep_set_halt,
+ .set_wedge = virtio_ep_set_wedge,
+};
+
+/*-------------------------------------------------------------------------*/
+/* UDC callbacks */
+
+/**
+ * virtio_usb_dc_set_selfpowered() - Sets the device selfpowered feature.
+ * @gadget: The device being declared as self-powered
+ * @is_selfpowered: Flag indicates if gadget is selfpowered
+ *
+ * Return: 0 on success, -errno on failure.
+ */
+static int virtio_usb_dc_set_selfpowered(struct usb_gadget *gadget,
+ int is_selfpowered)
+{
+ struct virtio_usb_dc *vudc =
+ container_of(gadget, struct virtio_usb_dc, gadget);
+ unsigned int code = VIRTIO_USB_CMD_DEV_SET_SELF_POWERED;
+ struct virtio_usb_dev_cmd_set_value *req;
+ struct virtio_usb_cmd *cmd;
+
+ cmd = virtio_usb_dc_cmd_alloc(vudc, code, GFP_ATOMIC);
+ if (!cmd)
+ return -ENOMEM;
+
+ req = virtio_usb_cmd_request(cmd);
+ req->value = cpu_to_le32(!!is_selfpowered);
+
+ return virtio_usb_cmd_send_sync(vudc->vusb, NULL, NULL, cmd);
+}
+
+/**
+ * virtio_usb_dc_pullup() - Software-controlled connect/disconnect to USB host
+ * @gadget: Pointer to the usb gadget structure.
+ * @is_on: flag to start or stop
+ *
+ * Return: 0 on success, -errno on failure.
+ */
+static int virtio_usb_dc_pullup(struct usb_gadget *gadget, int is_on)
+{
+ struct virtio_usb_dc *vudc =
+ container_of(gadget, struct virtio_usb_dc, gadget);
+ unsigned int code = VIRTIO_USB_CMD_DEV_PULLUP;
+ struct virtio_usb_dev_cmd_set_value *req;
+ struct virtio_usb_cmd *cmd;
+ unsigned long flags;
+ int rc;
+
+ spin_lock_irqsave(&vudc->lock, flags);
+ is_on = !!is_on;
+ if (is_on == vudc->pullup) {
+ rc = -EALREADY;
+ goto on_unlock;
+ }
+
+ vudc->pullup = is_on;
+ spin_unlock_irqrestore(&vudc->lock, flags);
+
+ cmd = virtio_usb_dc_cmd_alloc(vudc, code, GFP_ATOMIC);
+ if (!cmd)
+ return -ENOMEM;
+
+ req = virtio_usb_cmd_request(cmd);
+ req->value = cpu_to_le32(is_on);
+
+ rc = virtio_usb_cmd_send_sync(vudc->vusb, NULL, NULL, cmd);
+
+ return rc;
+
+on_unlock:
+ spin_unlock_irqrestore(&vudc->lock, flags);
+
+ return rc;
+}
+
+/**
+ * virtio_usb_dc_set_speed() - Sets maximum speed supported by gadget
+ * @gadget: Pointer to the usb gadget structure.
+ * @speed: The maximum speed to allowed to run
+ */
+static void virtio_usb_dc_set_speed(struct usb_gadget *gadget,
+ enum usb_device_speed speed)
+{
+ struct virtio_usb_dc *vudc =
+ container_of(gadget, struct virtio_usb_dc, gadget);
+
+ vudc->gadget.speed = min_t(u8, USB_SPEED_HIGH, speed);
+
+ switch (speed) {
+ case USB_SPEED_HIGH:
+ case USB_SPEED_FULL:
+ vudc->veps[0].ep.maxpacket = 64;
+ break;
+ case USB_SPEED_LOW:
+ vudc->veps[0].ep.maxpacket = 8;
+ break;
+ default:
+ break;
+ }
+}
+
+/**
+ * virtio_usb_dc_start() - Starts the device controller.
+ * @gadget: Pointer to the usb gadget structure
+ * @driver: Pointer to gadget driver structure
+ *
+ * Return: zero always
+ */
+static int virtio_usb_dc_start(struct usb_gadget *gadget,
+ struct usb_gadget_driver *driver)
+{
+ struct virtio_usb_dc *vudc =
+ container_of(gadget, struct virtio_usb_dc, gadget);
+ unsigned long flags;
+
+ spin_lock_irqsave(&vudc->lock, flags);
+ vudc->driver = driver;
+ vudc->pullup = 0;
+ spin_unlock_irqrestore(&vudc->lock, flags);
+
+ return 0;
+}
+
+/**
+ * virtio_usb_dc_stop() - Stops the device controller.
+ * @gadget: Pointer to the usb gadget structure
+ *
+ * Return: zero always
+ */
+static int virtio_usb_dc_stop(struct usb_gadget *gadget)
+{
+ struct virtio_usb_dc *vudc =
+ container_of(gadget, struct virtio_usb_dc, gadget);
+ unsigned long flags;
+
+ spin_lock_irqsave(&vudc->lock, flags);
+ vudc->driver = NULL;
+ vudc->pullup = 0;
+ spin_unlock_irqrestore(&vudc->lock, flags);
+
+ return 0;
+}
+
+/**
+ * virtio_usb_dc_vbus_draw() - Constrain controller's VBUS power usage
+ * @gadget: The device whose VBUS usage is being described
+ * @mA: How much current to draw, in milliAmperes.
+ *
+ * Return: 0 on success, -errno on failure.
+ */
+static int virtio_usb_dc_vbus_draw(struct usb_gadget *gadget, unsigned int mA)
+{
+ struct virtio_usb_dc *vudc =
+ container_of(gadget, struct virtio_usb_dc, gadget);
+ unsigned int code = VIRTIO_USB_CMD_DEV_VBUS_DRAW;
+ struct virtio_usb_dev_cmd_set_value *req;
+ struct virtio_usb_cmd *cmd;
+ int rc;
+
+ cmd = virtio_usb_dc_cmd_alloc(vudc, code, GFP_ATOMIC);
+ if (!cmd)
+ return -ENOMEM;
+
+ req = virtio_usb_cmd_request(cmd);
+ req->value = cpu_to_le32(mA);
+
+ rc = virtio_usb_cmd_send_sync(vudc->vusb, NULL, NULL, cmd);
+
+ return rc;
+}
+
+/**
+ * virtio_usb_dc_get_frame() - returns the current frame number
+ * @gadget: controller that reports the frame number
+ *
+ * Return: Returns the usb frame number, normally eleven bits from
+ * a SOF packet, or -errno on failure.
+ */
+static int virtio_usb_dc_get_frame(struct usb_gadget *gadget)
+{
+ struct virtio_usb_dc *vudc =
+ container_of(gadget, struct virtio_usb_dc, gadget);
+ unsigned int code = VIRTIO_USB_CMD_DEV_GET_FRAME_NUMBER;
+ struct virtio_usb_dev_frame_number *resp;
+ struct virtio_usb_cmd *cmd;
+ int rc;
+
+ cmd = virtio_usb_dc_cmd_alloc(vudc, code, GFP_ATOMIC);
+ if (!cmd)
+ return -ENOMEM;
+
+ rc = virtio_usb_cmd_send_sync(vudc->vusb, NULL, NULL, cmd);
+ if (rc)
+ return rc;
+
+ resp = virtio_usb_cmd_response(cmd);
+ rc = le32_to_cpu(resp->frame_number);
+
+ return rc;
+}
+
+static const struct usb_gadget_ops virtio_gadget_ops = {
+ .set_selfpowered = virtio_usb_dc_set_selfpowered,
+ .pullup = virtio_usb_dc_pullup,
+ .udc_start = virtio_usb_dc_start,
+ .udc_stop = virtio_usb_dc_stop,
+ .vbus_draw = virtio_usb_dc_vbus_draw,
+ .udc_set_speed = virtio_usb_dc_set_speed,
+ .get_frame = virtio_usb_dc_get_frame,
+};
+
+/*-------------------------------------------------------------------------*/
+
+/**
+ * virtio_usb_dc_get_endpoint_count() - Function to get the number of
+ * endpoints from the virtio-usb device
+ * @vusb: Virtio usb device
+ *
+ * Context: Process context.
+ * Return: 0 on success, -errno on failure.
+ */
+static int virtio_usb_dc_get_endpoint_count(struct virtio_usb_dc *vudc)
+{
+ unsigned int code = VIRTIO_USB_CMD_DEV_GET_ENDPOINT_COUNT;
+ struct virtio_usb_dev_ep_count *ep_count;
+ struct virtio_usb_cmd *cmd;
+ int rc;
+
+ cmd = virtio_usb_dc_cmd_alloc(vudc, code, GFP_KERNEL);
+ if (!cmd)
+ return -ENOMEM;
+
+ virtio_usb_cmd_ref(cmd);
+ rc = virtio_usb_cmd_send_sync(vudc->vusb, NULL, NULL, cmd);
+ if (rc)
+ goto on_exit;
+
+ ep_count = virtio_usb_cmd_response(cmd);
+ vudc->neps = le32_to_cpu(ep_count->count);
+ if (!vudc->neps)
+ rc = -EINVAL;
+
+on_exit:
+ virtio_usb_cmd_unref(cmd);
+ return rc;
+}
+
+/**
+ * virtio_usb_dc_get_endpoint_info() - Function to get the endpoint
+ * info from virtio-usb device.
+ * @vusb: Virtio usb device
+ *
+ * Context: Process context.
+ * Return: 0 on success, -errno on failure.
+ */
+static int virtio_usb_dc_get_endpoint_info(struct virtio_usb_dc *vudc)
+{
+ unsigned int code = VIRTIO_USB_CMD_DEV_GET_ENDPOINT_INFO;
+ struct virtio_usb_dev_ep_info *epinfo;
+ struct virtio_usb_cmd *cmd;
+ struct scatterlist sg;
+ struct scatterlist *psg_data = &sg;
+ int rc, i;
+
+ vudc->veps = kcalloc(vudc->neps, sizeof(*vudc->veps) + sizeof(*epinfo),
+ GFP_KERNEL);
+ if (!vudc->veps)
+ return -ENOMEM;
+
+ epinfo = (void *)vudc->veps + (sizeof(*vudc->veps) * vudc->neps);
+
+ cmd = virtio_usb_dc_cmd_alloc(vudc, code, GFP_KERNEL);
+ if (!cmd) {
+ rc = -ENOMEM;
+ goto on_error;
+ }
+ virtio_usb_cmd_ref(cmd);
+
+ sg_init_one(psg_data, epinfo, sizeof(*epinfo) * vudc->neps);
+
+ rc = virtio_usb_cmd_send_sync(vudc->vusb, NULL, psg_data, cmd);
+ if (rc) {
+ virtio_usb_cmd_unref(cmd);
+ goto on_error;
+ }
+
+ INIT_LIST_HEAD(&vudc->gadget.ep_list);
+
+ for (i = 0; i < vudc->neps; i++) {
+ struct virtio_usb_ep *vep = &vudc->veps[i];
+ u16 types = le16_to_cpu(epinfo[i].types);
+ u16 directions = le16_to_cpu(epinfo[i].directions);
+ struct usb_ep_caps caps = VIRTIO_USB_EP_CAPS(types, directions);
+ unsigned int maxpacket_limit =
+ le16_to_cpu(epinfo[i].maxpacket_limit);
+
+ strscpy(vep->name, epinfo[i].name, sizeof(vep->name));
+ vep->ep_id = i;
+ vep->ep.caps = caps;
+ vep->ep.name = vep->name;
+ vep->ep.ops = &virtio_usb_ep_ops;
+ INIT_LIST_HEAD(&vep->req_queue);
+ list_add_tail(&vep->ep.ep_list, &vudc->gadget.ep_list);
+ usb_ep_set_maxpacket_limit(&vep->ep, maxpacket_limit);
+ vep->ep.max_streams = le16_to_cpu(epinfo[i].max_streams);
+ vep->vudc = vudc;
+ }
+ virtio_usb_cmd_unref(cmd);
+ vudc->gadget.ep0 = &vudc->veps[0].ep;
+ list_del_init(&vudc->veps[0].ep.ep_list);
+
+ return rc;
+
+on_error:
+ kfree(vudc->veps);
+ vudc->veps = NULL;
+ return rc;
+}
+
+static int virtio_usb_dc_parent_create(struct virtio_usb_dc *vudc)
+{
+ int rc;
+
+ vudc->pdev = platform_device_alloc(GADGET_NAME, vudc->port);
+ if (!vudc->pdev)
+ return -ENOMEM;
+
+ vudc->pdev->dev.parent = &vudc->vusb->vdev->dev;
+ rc = platform_device_add(vudc->pdev);
+ if (rc) {
+ platform_device_put(vudc->pdev);
+ vudc->pdev = NULL;
+ return rc;
+ }
+
+ return 0;
+}
+
+/**
+ * virtio_usb_dc_event_process() - Event process function
+ * @event: virtio_usb_event
+ *
+ * Context: Process context.
+ * Return: 0 on success, -errno on failure.
+ */
+static void virtio_usb_dc_event_process(struct virtio_usb_event *event)
+{
+ struct virtio_usb_dev_event *evt = virtio_usb_event_buf(event);
+ struct virtio_usb_dev_setup_event *setup_evt;
+ struct virtio_usb *vusb = event->vusb;
+ struct virtio_usb_dc *vudc;
+ struct usb_gadget_driver *driver;
+ struct virtio_usb_ep *vep;
+ unsigned long flags;
+ int rc = 0;
+ unsigned short port_id = le16_to_cpu(evt->port);
+
+ if (!vusb->nports || port_id >= vusb->nports) {
+ dev_err(&vusb->vdev->dev, "%s port[%d] index out of range\n",
+ __func__, port_id);
+ return;
+ }
+
+ vudc = vusb->vports[port_id].vudc;
+ driver = vudc->driver;
+
+ switch (le32_to_cpu(evt->code)) {
+ case VIRTIO_USB_EVT_DEV_BIND: {
+ if (vudc->registered) {
+ dev_err(&vusb->vdev->dev,
+ "port %d: BIND while still registered, ignoring\n",
+ port_id);
+ rc = -EBUSY;
+ break;
+ }
+
+ memzero_explicit(&vudc->gadget, sizeof(struct usb_gadget));
+ vudc->gadget.sg_supported = 1;
+ rc = virtio_usb_dc_get_endpoint_count(vudc);
+ if (rc) {
+ dev_err(&vusb->vdev->dev,
+ "Failed to get endpoint count\n");
+ break;
+ }
+ rc = virtio_usb_dc_get_endpoint_info(vudc);
+ if (rc) {
+ dev_err(&vusb->vdev->dev,
+ "Failed to get endpoint info\n");
+ break;
+ }
+ if (!vudc->pdev) {
+ rc = virtio_usb_dc_parent_create(vudc);
+ if (rc) {
+ dev_err(&vusb->vdev->dev,
+ "Failed to create UDC parent device\n");
+ kfree(vudc->veps);
+ vudc->veps = NULL;
+ break;
+ }
+ }
+ vudc->gadget.name =
+ kasprintf(GFP_KERNEL, "%s_%d", GADGET_NAME, port_id);
+ if (!vudc->gadget.name) {
+ rc = -ENOMEM;
+ kfree(vudc->veps);
+ vudc->veps = NULL;
+ break;
+ }
+ vudc->gadget.ops = &virtio_gadget_ops;
+ vudc->gadget.max_speed = USB_SPEED_HIGH;
+ vudc->gadget.nonatomic = 1;
+
+ //vudc->gadget.dev.init_name = gadget_name;
+ vudc->gadget.dev.parent = &vudc->pdev->dev;
+ rc = usb_add_gadget_udc(&vudc->pdev->dev, &vudc->gadget);
+ if (rc) {
+ dev_err(&vudc->pdev->dev, "Failed to add udc\n");
+ kfree(vudc->veps);
+ vudc->veps = NULL;
+ kfree(vudc->gadget.name);
+ break;
+ }
+ spin_lock_irqsave(&vudc->lock, flags);
+ vudc->registered = 1;
+ spin_unlock_irqrestore(&vudc->lock, flags);
+ break;
+ }
+ case VIRTIO_USB_EVT_DEV_DISCONNECTED: {
+ if (vudc->driver && vudc->driver->disconnect)
+ vudc->driver->disconnect(&vudc->gadget);
+ if (vudc->registered)
+ usb_gadget_set_state(&vudc->gadget,
+ USB_STATE_NOTATTACHED);
+ break;
+ }
+ case VIRTIO_USB_EVT_DEV_SETUP: {
+ if (!driver)
+ break;
+
+ vep = usb_ep_to_virtio_ep(vudc->gadget.ep0);
+ setup_evt = virtio_usb_event_buf(event);
+
+ memcpy(&vep->setup, setup_evt->setup,
+ sizeof(struct usb_ctrlrequest));
+
+ rc = driver->setup(&vudc->gadget,
+ (struct usb_ctrlrequest *)setup_evt->setup);
+ if (rc < 0 && rc != -ESHUTDOWN)
+ virtio_ep_set_halt(vudc->gadget.ep0, 1);
+ break;
+ }
+ case VIRTIO_USB_EVT_DEV_RESET: {
+ if (driver)
+ usb_gadget_udc_reset(&vudc->gadget, driver);
+ break;
+ }
+ case VIRTIO_USB_EVT_DEV_SUSPEND: {
+ if (driver && driver->suspend)
+ driver->suspend(&vudc->gadget);
+ break;
+ }
+ case VIRTIO_USB_EVT_DEV_RESUME: {
+ if (driver && driver->resume)
+ driver->resume(&vudc->gadget);
+ break;
+ }
+ case VIRTIO_USB_EVT_DEV_UNBIND: {
+ unsigned int registered;
+
+ spin_lock_irqsave(&vudc->lock, flags);
+ registered = vudc->registered;
+ vudc->registered = 0;
+ spin_unlock_irqrestore(&vudc->lock, flags);
+
+ if (registered) {
+ usb_del_gadget_udc(&vudc->gadget);
+ kfree(vudc->gadget.name);
+ vudc->gadget.name = NULL;
+ }
+
+ kfree(vudc->veps);
+ vudc->veps = NULL;
+
+ if (vudc->pdev) {
+ platform_device_unregister(vudc->pdev);
+ vudc->pdev = NULL;
+ }
+ break;
+ }
+ default:
+ rc = -EINVAL;
+ break;
+ }
+}
+
+/**
+ * virtio_usb_dc_event_work() - Worker to get all events
+ * from the virtqueue and process them.
+ * @work: Kernel work to handle event completion.
+ *
+ * Context: Process context.
+ */
+void virtio_usb_dc_event_work(struct work_struct *work)
+{
+ struct virtio_usb *vusb =
+ container_of(work, struct virtio_usb, vq_dev_event_work);
+ struct virtio_usb_queue *evtq =
+ &vusb->vqueues[vusb->dev_vq_base + VIRTIO_USB_VQ_EVENT_IDX];
+
+ virtio_usb_evt_work(evtq, virtio_usb_dc_event_process);
+}
+
+/**
+ * virtio_usb_dc_event_populate() - Add events to the device event queue.
+ * @vusb: VirtIO USB device
+ *
+ * Context: Any context.
+ * Return: 0 on success -errno on failure
+ */
+int virtio_usb_dc_event_populate(struct virtio_usb *vusb)
+{
+ struct virtio_usb_queue *evt_queue =
+ &vusb->vqueues[vusb->dev_vq_base + VIRTIO_USB_VQ_EVENT_IDX];
+ struct virtio_usb_event *events;
+ int rc;
+
+ events = virtio_usb_events_alloc(vusb, evt_queue,
+ sizeof(struct virtio_usb_dev_event));
+
+ if (!events)
+ return -ENOMEM;
+
+ rc = virtio_usb_events_populate(events);
+
+ return rc;
+}
+
+/**
+ * virtio_usb_dc_init() - Initializes the device role.
+ * @vusb: VirtIO USB device
+ *
+ * Context: Any context.
+ * Return: 0 on success -errno on failure
+ */
+int virtio_usb_dc_init(struct virtio_usb *vusb, int port_id)
+{
+ struct virtio_usb_dc *vudc;
+ unsigned int i;
+
+ vudc = devm_kzalloc(&vusb->vdev->dev, sizeof(*vudc), GFP_KERNEL);
+ if (!vudc)
+ return -ENOMEM;
+
+ vusb->vports[port_id].vudc = vudc;
+ vudc->vusb = vusb;
+ vudc->port = port_id;
+ for (i = 0; i < VIRTIO_USB_VQ_DEV_MAX; i++)
+ vudc->dcqs[i] = &vusb->vqueues[vusb->dev_vq_base + i];
+
+ spin_lock_init(&vudc->lock);
+
+ vudc->registered = 0;
+
+ return 0;
+}
+
+/**
+ * virtio_usb_dc_deinit() - Deinitialize the device role.
+ * @vusb: VirtIO USB device
+ *
+ * Context: Any context.
+ * Return: 0 on success -errno on failure
+ */
+int virtio_usb_dc_deinit(struct virtio_usb *vusb)
+{
+ int port_id;
+
+ if (!vusb->device_role)
+ return -ENODEV;
+
+ for (port_id = 0; port_id < vusb->nports; port_id++) {
+ struct virtio_usb_dc *vudc = vusb->vports[port_id].vudc;
+ unsigned int registered;
+ unsigned long flags;
+
+ if (!vudc)
+ continue;
+
+ spin_lock_irqsave(&vudc->lock, flags);
+ registered = vudc->registered;
+ spin_unlock_irqrestore(&vudc->lock, flags);
+
+ if (registered)
+ usb_del_gadget_udc(&vudc->gadget);
+
+ kfree(vudc->veps);
+ vudc->veps = NULL;
+
+ if (vudc->pdev) {
+ platform_device_unregister(vudc->pdev);
+ vudc->pdev = NULL;
+ }
+
+ vusb->vports[port_id].vudc = NULL;
+ }
+
+ return 0;
+}
+
+/**
+ * virtio_usb_dc_dataq_stop_cb() - Stops the data virtqueue
+ * @vusb: VirtIO usb device.
+ * @dataq: data virtqueue wrapper
+ *
+ * Context: Any context.
+ */
+static void virtio_usb_dc_dataq_stop_cb(struct virtio_usb *vusb,
+ struct virtio_usb_queue *dataq)
+{
+ int port_id;
+
+ if (!vusb->device_role)
+ return;
+
+ virtio_usb_dataq_stop_cb(vusb, dataq);
+ cancel_work_sync(&vusb->vq_dev_data_rx_work);
+
+ for (port_id = 0; port_id < vusb->nports; port_id++) {
+ struct virtio_usb_dc *vudc = vusb->vports[port_id].vudc;
+ struct virtio_usb_data *vreq, *vreq_tmp;
+ struct virtio_usb_dc_priv *priv;
+ unsigned int i;
+
+ if (!vudc)
+ continue;
+
+ for (i = 0; i < vudc->neps; i++) {
+ list_for_each_entry_safe(vreq, vreq_tmp,
+ &vudc->veps[i].req_queue,
+ list) {
+ priv = virtio_usb_data_priv(vreq);
+ priv->req.status = -ESHUTDOWN;
+ list_del_init(&vreq->list);
+ virtio_usb_data_unref(vreq);
+ usb_gadget_giveback_request(&priv->vep->ep,
+ &priv->req);
+ }
+ }
+ }
+}
+
+/**
+ * virtio_usb_dc_data_notify_cb() - Data virtqueue notification callback
+ * @vqueue: Underlying event virtqueue.
+ *
+ * This callback function is called upon a vring interrupt request from the
+ * device.
+ *
+ * Context: Interrupt context.
+ */
+static void virtio_usb_dc_data_notify_cb(struct virtqueue *vqueue)
+{
+ struct virtio_usb *vusb = vqueue->vdev->priv;
+
+ schedule_work(&vusb->vq_dev_data_rx_work);
+}
+
+/**
+ * virtio_usb_dc_evt_notify_cb() - Event virtqueue notification callback
+ * @vqueue: Underlying event virtqueue.
+ *
+ * This callback function is called upon a vring interrupt request from the
+ * device.
+ *
+ * Context: Interrupt context.
+ */
+static void virtio_usb_dc_evt_notify_cb(struct virtqueue *vqueue)
+{
+ struct virtio_usb *vusb = vqueue->vdev->priv;
+
+ schedule_work(&vusb->vq_dev_event_work);
+}
+
+/**
+ * virtio_usb_dc_evtq_stop_cb() - Stops the event virtqueue.
+ * @vusb: VirtIO usb device.
+ * @vq: virtio usb vq wrapper
+ *
+ * Context: Any context.
+ */
+static void virtio_usb_dc_evtq_stop_cb(struct virtio_usb *vusb,
+ struct virtio_usb_queue *vq)
+{
+ /*
+ * Unlike the host evtq stop path (which safely updates in-memory
+ * port status bits), dc event processing drives the UDC state
+ * machine and calls back into gadget drivers and UDC core. Doing
+ * so here, when the UDC may be only partially initialized (probe
+ * failure) or already torn down (remove path), risks
+ * use-after-free and crashes - virtio_usb_evt_drain_stop_cb()
+ * drains without processing for exactly this reason.
+ */
+ cancel_work_sync((struct work_struct *)&vusb->vq_dev_event_work);
+ virtio_usb_evt_drain_stop_cb(vq, NULL);
+}
+
+const struct virtio_usb_vq_desc dev_vqueues[VIRTIO_USB_VQ_DEV_MAX] = {
+ [VIRTIO_USB_VQ_COMMAND_IDX] = {
+ .callback = virtio_usb_cmd_notify_cb,
+ .name = "virtusb-dev-cmd",
+ .process = virtio_usb_cmd_process_cb,
+ .stop = virtio_usb_cmdq_stop_cb,
+ },
+ [VIRTIO_USB_VQ_EVENT_IDX] = {
+ .callback = virtio_usb_dc_evt_notify_cb,
+ .name = "virtusb-dev-evt",
+ .process = NULL,
+ .stop = virtio_usb_dc_evtq_stop_cb,
+ },
+ [VIRTIO_USB_VQ_DATA_IDX] = {
+ .callback = virtio_usb_dc_data_notify_cb,
+ .name = "virtusb-dev-data",
+ .process = NULL,
+ .stop = virtio_usb_dc_dataq_stop_cb,
+ },
+};
diff --git a/drivers/usb/virtio_usb/device.h b/drivers/usb/virtio_usb/device.h
new file mode 100644
index 0000000..9dacc89
--- /dev/null
+++ b/drivers/usb/virtio_usb/device.h
@@ -0,0 +1,91 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * virtio-usb: Virtio usb device
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef VIRTIO_USB_DC_H
+#define VIRTIO_USB_DC_H
+
+#include <linux/usb.h>
+#include <linux/list.h>
+#include <linux/platform_device.h>
+#include <uapi/linux/usb/ch11.h>
+#include <uapi/linux/usb/ch9.h>
+#include <linux/usb/gadget.h>
+
+#include "vq_common.h"
+
+#include <uapi/linux/virtio_usb.h>
+
+#define VIRTIO_USB_EP_CAPS_TYPE_CONTROL 0x01
+#define VIRTIO_USB_EP_CAPS_TYPE_INTERRUPT 0x02
+#define VIRTIO_USB_EP_CAPS_TYPE_BULK 0x04
+#define VIRTIO_USB_EP_CAPS_TYPE_ISOCHRONOUS 0x08
+
+#define VIRTIO_USB_EP_CAPS_DIR_IN 0x02
+#define VIRTIO_USB_EP_CAPS_DIR_OUT 0x01
+
+#define VIRTIO_USB_EP_CAPS(_type, _dir) \
+ { \
+ .type_control = !!((_type) & VIRTIO_USB_EP_CAPS_TYPE_CONTROL), \
+ .type_iso = !!((_type) & VIRTIO_USB_EP_CAPS_TYPE_ISOCHRONOUS), \
+ .type_bulk = !!((_type) & VIRTIO_USB_EP_CAPS_TYPE_BULK), \
+ .type_int = !!((_type) & VIRTIO_USB_EP_CAPS_TYPE_INTERRUPT), \
+ .dir_in = !!((_dir) & VIRTIO_USB_EP_CAPS_DIR_IN), \
+ .dir_out = !!((_dir) & VIRTIO_USB_EP_CAPS_DIR_OUT), \
+ }
+
+/**
+ * struct virtio_usb_ep - virtio usb device endpoint
+ * @ep: usb ep
+ * @vusb: VirtIO usb device
+ * @setup: setup packet for control endpoint
+ * @req_queue: list of usb requests submitted to ep awaiting response
+ * @ep_id: Id of the endpoint
+ * @name: Endpoint name
+ */
+struct virtio_usb_ep {
+ struct usb_ep ep;
+ struct virtio_usb_dc *vudc;
+ struct usb_ctrlrequest setup;
+ struct list_head req_queue;
+ u16 ep_id;
+ char name[16];
+};
+
+/**
+ * struct virtio_usb_dc - VirtIO USB Device controller (USBDC) device.
+ * @gadget: Pointer to the usb gadget structure
+ * @driver: Pointer to the usb gadget driver structure
+ * @veps: Virtual endpoints
+ * @neps: Number of virtual endpoints
+ * @dcqs: Device virtqueue wrappers, indexed by VIRTIO_USB_VQ_*_IDX.
+ * @vusb: VirtIO usb device
+ * @registered: Flag indicating registration status to the UDC core.
+ * @pullup: Software-controlled connect/disconnect status USB host.
+ * @lock: Spinlock that protects device state
+ */
+struct virtio_usb_dc {
+ struct platform_device *pdev;
+ u16 port; // Device id
+ struct usb_gadget gadget;
+ struct usb_gadget_driver *driver;
+ struct virtio_usb_ep *veps;
+ u32 neps;
+ struct virtio_usb_queue *dcqs[VIRTIO_USB_VQ_DEV_MAX];
+ struct virtio_usb *vusb;
+ unsigned registered : 1;
+ unsigned pullup : 1;
+ spinlock_t lock;
+};
+
+extern const struct virtio_usb_vq_desc dev_vqueues[VIRTIO_USB_VQ_DEV_MAX];
+
+int virtio_usb_dc_init(struct virtio_usb *vusb, int port_id);
+int virtio_usb_dc_deinit(struct virtio_usb *vusb);
+void virtio_usb_dc_event_work(struct work_struct *work);
+void virtio_usb_dc_data_work(struct work_struct *work);
+int virtio_usb_dc_event_populate(struct virtio_usb *vusb);
+#endif /* VIRTIO_USB_DC_H */
diff --git a/include/uapi/linux/virtio_usb.h b/include/uapi/linux/virtio_usb.h
index 459edc1..4cbfb3f 100644
--- a/include/uapi/linux/virtio_usb.h
+++ b/include/uapi/linux/virtio_usb.h
@@ -64,12 +64,11 @@ enum {
VIRTIO_USB_S_ERR_STALL,
VIRTIO_USB_S_ERR_SHORT_PKT,
VIRTIO_USB_S_ERR_CANCELLED,
- VIRTIO_USB_S_ERR_HOST,
};
struct virtio_usb_cmd_status {
__le32 code; /* VIRTIO_USB_S_XXX */
-};
+} __packed;
/*****************************************************************************
* HOST COMMAND MESSAGES
@@ -145,6 +144,8 @@ enum {
VIRTIO_USB_CMD_DEV_EP_SET_HALT,
VIRTIO_USB_CMD_DEV_EP_SET_WEDGE,
VIRTIO_USB_CMD_DEV_CANCEL,
+ VIRTIO_USB_CMD_DEV_GET_FRAME_NUMBER,
+ VIRTIO_USB_CMD_DEV_SET_SELF_POWERED,
};
struct virtio_usb_dev_cmd_hdr {
@@ -153,12 +154,30 @@ struct virtio_usb_dev_cmd_hdr {
__le16 endpoint; /* Endpoint ID */
};
+/* VIRTIO_USB_CMD_DEV_CANCEL */
+struct virtio_usb_dev_cmd_cancel {
+ struct virtio_usb_dev_cmd_hdr hdr;
+ __le64 tag;
+};
+
/* VIRTIO_USB_CMD_DEV_GET_ENDPOINT_COUNT */
-struct virtio_usb_dev_cmd_ep_count {
+struct virtio_usb_dev_ep_count {
struct virtio_usb_cmd_status status;
__le32 count; /* # of supported endpoints */
};
+struct virtio_usb_dev_cmd_set_value {
+ struct virtio_usb_dev_cmd_hdr hdr;
+ __le32 value;
+ __le32 padding;
+};
+
+/* VIRTIO_USB_CMD_DEV_GET_FRAME_NUMBER */
+struct virtio_usb_dev_frame_number {
+ struct virtio_usb_cmd_status status;
+ __le32 frame_number;
+};
+
enum {
VIRTIO_USB_DIR_OUT = 0,
VIRTIO_USB_DIR_IN,
@@ -176,35 +195,39 @@ enum {
* struct virtio_usb_cmd_status
* struct virtio_usb_dev_cmd_ep_info [count]
*/
-struct virtio_usb_dev_cmd_ep_info {
+struct virtio_usb_dev_ep_info {
__le16 types; /* supported type bit map (1 << VIRTIO_USB_EP_XXX) */
__le16 directions /* supported direction bit map (1 << VIRTIO_USB_DIR_XXX) */;
__le16 maxpacket_limit;
__le16 max_streams;
+ __u8 name[16];
};
/*******************************************************************************
* DEVICE EVENT MESSAGES
*/
enum {
- VIRTIO_USB_EVT_DEV_CONNECTED = 0,
+ VIRTIO_USB_EVT_DEV_BIND = 0,
VIRTIO_USB_EVT_DEV_DISCONNECTED,
VIRTIO_USB_EVT_DEV_SETUP,
VIRTIO_USB_EVT_DEV_RESET,
VIRTIO_USB_EVT_DEV_SUSPEND,
VIRTIO_USB_EVT_DEV_RESUME,
+ VIRTIO_USB_EVT_DEV_UNBIND,
};
struct virtio_usb_dev_event {
__le32 code; /* VIRTIO_USB_EVT_DEV_XXX */
- __u8 padding[12];
+ __le16 port; /* Device ID */
+ __u8 padding[10];
};
/* VIRTIO_USB_EVT_DEV_SETUP */
struct virtio_usb_dev_setup_event {
__le32 code; /* VIRTIO_USB_EVT_DEV_SETUP */
+ __le16 port; /* Device ID */
__u8 setup[8]; /* setup packet contents */
- __u8 padding[4];
+ __u8 padding[2];
};
/*******************************************************************************
next prev parent reply other threads:[~2026-09-24 16:09 UTC|newest]
Thread overview: 32+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-24 16:08 [PATCH 0/8] virtio-usb: add dual-role virtio USB driver Igor Skalkin
2026-09-24 16:09 ` [PATCH 1/8] virtio-usb: add protocol header and skeleton dual-role driver Igor Skalkin
2026-09-24 16:23 ` sashiko-bot
2026-09-25 5:14 ` Greg Kroah-Hartman
2026-09-28 14:19 ` Igor Skalkin
2026-09-25 5:21 ` Greg Kroah-Hartman
2026-09-28 14:14 ` Igor Skalkin
2026-09-24 16:09 ` [PATCH 2/8] virtio-usb: add host role (USB Host Controller) support Igor Skalkin
2026-09-24 16:25 ` sashiko-bot
2026-09-25 5:18 ` Greg Kroah-Hartman
2026-09-28 14:01 ` Igor Skalkin
2026-09-24 16:09 ` Igor Skalkin [this message]
2026-09-24 16:28 ` [PATCH 3/8] virtio-usb: add device role (USB Device " sashiko-bot
2026-09-24 16:09 ` [PATCH 4/8] virtio-usb: add OTG role query support Igor Skalkin
2026-09-24 16:19 ` sashiko-bot
2026-09-24 16:09 ` [PATCH 5/8] virtio-usb: add USB On-The-Go role-switching support Igor Skalkin
2026-09-24 16:24 ` sashiko-bot
2026-09-24 16:09 ` [PATCH 6/8] virtio-usb: rework endpoint lifecycle to an async split-phase state machine Igor Skalkin
2026-09-24 16:30 ` sashiko-bot
2026-09-24 16:09 ` [PATCH 7/8] virtio-usb: add SuperSpeed device-role support Igor Skalkin
2026-09-24 16:35 ` sashiko-bot
2026-09-24 16:09 ` [PATCH 8/8] virtio-usb: support a guest UDC name prefix from the bind event Igor Skalkin
2026-09-24 16:35 ` sashiko-bot
2026-09-25 5:17 ` [PATCH 0/8] virtio-usb: add dual-role virtio USB driver Greg Kroah-Hartman
2026-09-28 13:55 ` Igor Skalkin
2026-09-28 14:44 ` Greg Kroah-Hartman
2026-09-28 15:56 ` Igor Skalkin
2026-09-28 16:10 ` Greg Kroah-Hartman
2026-09-29 9:47 ` Michael S. Tsirkin
2026-09-29 16:01 ` Greg Kroah-Hartman
2026-09-29 19:02 ` Vasilii Ianikeev
2026-09-29 19:58 ` Vasilii Ianikeev
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260924160907.145405-4-igor.skalkin@oss.qualcomm.com \
--to=igor.skalkin@oss.qualcomm.com \
--cc=aiswarya.cyriac@oss.qualcomm.com \
--cc=anton.yakovlev@oss.qualcomm.com \
--cc=gregkh@linuxfoundation.org \
--cc=jasowangio@gmail.com \
--cc=linux-usb@vger.kernel.org \
--cc=mst@redhat.com \
--cc=trilok.soni@oss.qualcomm.com \
--cc=vasilii.ianikeev@oss.qualcomm.com \
--cc=virtualization@lists.linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.