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