All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/2] hw/usb/hcd-ehci: fix the abort after a failed descriptor read
@ 2026-11-03 12:33 Junjie Cao
  2026-11-03 12:33 ` [PATCH 1/2] hw/usb/hcd-ehci: idle the schedule " Junjie Cao
  2026-11-03 12:33 ` [PATCH 2/2] tests/qtest/usb-hcd-ehci: test descriptor reads that fail Junjie Cao
  0 siblings, 2 replies; 5+ messages in thread
From: Junjie Cao @ 2026-11-03 12:33 UTC (permalink / raw)
  To: qemu-devel
  Cc: thuth, jamin_lin, philmd, clg, peter.maydell, kraxel,
	marcandre.lureau, farosas, lvivier, pbonzini, junjie.cao

2b2a0e4f465a ("hw/usb/hcd-ehci: Check for DMA errors in
get_dwords()/put_dwords()") lets a guest abort QEMU by pointing a
schedule descriptor at memory that is not there.  It is not in a
release yet; 11.2 would be the first.

Patch 2 adds the qtest: it aborts QEMU on current master and passes
with patch 1.

Junjie Cao (2):
  hw/usb/hcd-ehci: idle the schedule after a failed descriptor read
  tests/qtest/usb-hcd-ehci: test descriptor reads that fail

 hw/usb/hcd-ehci.c               |  14 ++++
 tests/qtest/usb-hcd-ehci-test.c | 119 ++++++++++++++++++++++++++++++++
 2 files changed, 133 insertions(+)

-- 
2.43.0



^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH 1/2] hw/usb/hcd-ehci: idle the schedule after a failed descriptor read
  2026-11-03 12:33 [PATCH 0/2] hw/usb/hcd-ehci: fix the abort after a failed descriptor read Junjie Cao
@ 2026-11-03 12:33 ` Junjie Cao
  2026-11-03 15:19   ` Tommaso Califano
  2026-11-03 12:33 ` [PATCH 2/2] tests/qtest/usb-hcd-ehci: test descriptor reads that fail Junjie Cao
  1 sibling, 1 reply; 5+ messages in thread
From: Junjie Cao @ 2026-11-03 12:33 UTC (permalink / raw)
  To: qemu-devel
  Cc: thuth, jamin_lin, philmd, clg, peter.maydell, kraxel,
	marcandre.lureau, farosas, lvivier, pbonzini, junjie.cao

Since get_dwords() started checking the DMA result, a failed descriptor
read raises HSE, clears Run/Stop and makes the state function return
without advancing.  That leaves the schedule in WAITLISTHEAD, FETCHQH,
FETCHSITD or FETCHQTD, none of which ehci_advance_async_state() or
ehci_advance_periodic_state() accept: the next frame hits their
g_assert_not_reached().

A guest gets there by pointing ASYNCLISTADDR, a QH's next qTD or a
frame list entry at an address outside guest memory; QEMU aborts on the
second frame.

Return the schedule to EST_ACTIVE at those four sites, so the next pass
sees Run/Stop clear and tears it down the way a stopped schedule is.
The iTD and fill-queue reads already take the existing controller-reset
path on error and are left alone.

Fixes: 2b2a0e4f465a ("hw/usb/hcd-ehci: Check for DMA errors in get_dwords()/put_dwords()")
Signed-off-by: Junjie Cao <junjie.cao@linux.dev>
---
 hw/usb/hcd-ehci.c | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/hw/usb/hcd-ehci.c b/hw/usb/hcd-ehci.c
index f371e567f3..938707043f 100644
--- a/hw/usb/hcd-ehci.c
+++ b/hw/usb/hcd-ehci.c
@@ -330,6 +330,15 @@ static int ehci_get_state(EHCIState *s, int async)
     return async ? s->astate : s->pstate;
 }
 
+/*
+ * get_dwords() has raised HSE and cleared Run/Stop.  Park the schedule
+ * in EST_ACTIVE so the next advance pass idles it instead of asserting.
+ */
+static void ehci_abandon_pass(EHCIState *ehci, int async)
+{
+    ehci_set_state(ehci, async, EST_ACTIVE);
+}
+
 static void ehci_set_fetch_addr(EHCIState *s, int async, uint64_t addr)
 {
     if (async) {
@@ -1600,6 +1609,7 @@ static int ehci_state_waitlisthead(EHCIState *ehci,  int async)
     for (i = 0; i < MAX_QH; i++) {
         if (!get_dwords(ehci, NLPTR_GET(entry), (uint32_t *) &qh,
                         ehci_qh_dwords(ehci))) {
+            ehci_abandon_pass(ehci, async);
             return 0;
         }
         ehci_trace_qh(NULL, NLPTR_GET(entry), &qh);
@@ -1703,6 +1713,7 @@ static EHCIQueue *ehci_state_fetchqh(EHCIState *ehci, int async)
     memset(&qh, 0, sizeof(qh));
     if (!get_dwords(ehci, NLPTR_GET(q->qhaddr),
                     (uint32_t *) &qh, ehci_qh_dwords(ehci))) {
+        ehci_abandon_pass(ehci, async);
         q = NULL;
         goto out;
     }
@@ -1807,6 +1818,7 @@ static int ehci_state_fetchsitd(EHCIState *ehci, int async)
 
     if (!get_dwords(ehci, NLPTR_GET(entry), (uint32_t *)&sitd,
                     sizeof(EHCIsitd) >> 2)) {
+        ehci_abandon_pass(ehci, async);
         return 0;
     }
     ehci_trace_sitd(ehci, entry, &sitd);
@@ -1867,6 +1879,7 @@ static int ehci_state_fetchqtd(EHCIQueue *q)
 
     addr = NLPTR_GET(q->qtdaddr);
     if (!get_dwords(q->ehci, addr +  8, &qtd.token,   1)) {
+        ehci_abandon_pass(q->ehci, q->async);
         return 0;
     }
     barrier();
@@ -1878,6 +1891,7 @@ static int ehci_state_fetchqtd(EHCIQueue *q)
         (q->ehci->caps_64bit_addr &&
          !get_dwords(q->ehci, addr + offsetof(EHCIqtd, bufptr_hi),
                      qtd.bufptr_hi, ARRAY_SIZE(qtd.bufptr_hi)))) {
+        ehci_abandon_pass(q->ehci, q->async);
         return 0;
     }
     ehci_trace_qtd(q, NLPTR_GET(q->qtdaddr), &qtd);
-- 
2.43.0



^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH 2/2] tests/qtest/usb-hcd-ehci: test descriptor reads that fail
  2026-11-03 12:33 [PATCH 0/2] hw/usb/hcd-ehci: fix the abort after a failed descriptor read Junjie Cao
  2026-11-03 12:33 ` [PATCH 1/2] hw/usb/hcd-ehci: idle the schedule " Junjie Cao
@ 2026-11-03 12:33 ` Junjie Cao
  2026-11-03 15:24   ` Tommaso Califano
  1 sibling, 1 reply; 5+ messages in thread
From: Junjie Cao @ 2026-11-03 12:33 UTC (permalink / raw)
  To: qemu-devel
  Cc: thuth, jamin_lin, philmd, clg, peter.maydell, kraxel,
	marcandre.lureau, farosas, lvivier, pbonzini, junjie.cao

Run the async schedule into an unreadable list head, into a head QH
whose horizontal link is unreadable and into a QH whose next qTD is
unreadable, and the periodic schedule into an unreadable siTD.  Each
must raise HSE, clear Run/Stop and leave the controller halted with
both schedules idle on the following frames; before the previous patch
QEMU aborted there.  A reset afterwards must bring the controller back.

Signed-off-by: Junjie Cao <junjie.cao@linux.dev>
---
 tests/qtest/usb-hcd-ehci-test.c | 119 ++++++++++++++++++++++++++++++++
 1 file changed, 119 insertions(+)

diff --git a/tests/qtest/usb-hcd-ehci-test.c b/tests/qtest/usb-hcd-ehci-test.c
index 87e37cdd7c..b0a0b6df9b 100644
--- a/tests/qtest/usb-hcd-ehci-test.c
+++ b/tests/qtest/usb-hcd-ehci-test.c
@@ -12,6 +12,7 @@
 #include "libqos/pci-pc.h"
 #include "hw/usb/uhci-regs.h"
 #include "hw/usb/ehci-regs.h"
+#include "hw/usb/hcd-ehci.h"
 #include "libqos/usb.h"
 
 static QPCIBus *pcibus;
@@ -142,6 +143,123 @@ static void pci_ehci_port_hotplug(void)
     usb_test_hotplug(global_qtest, "ich9-ehci-1", "3", pci_ehci_port_3_hotplug);
 }
 
+/* operational registers, relative to the BAR */
+#define EHCI_USBCMD             0x20
+#define EHCI_USBSTS             0x24
+#define EHCI_PERIODICLISTBASE   0x34
+#define EHCI_ASYNCLISTADDR      0x38
+
+#define NLPTR_TYPE_QH           (1 << 1)
+#define NLPTR_TYPE_SITD         (2 << 1)
+#define NLPTR_T                 1
+
+#define QH_ADDR                 0x100000    /* in the default 128 MiB of RAM */
+#define FRAME_LIST_ADDR         0x101000
+#define BAD_ADDR                0x40000000  /* above RAM, nothing mapped */
+#define FRAME_NS                1000000
+
+static uint32_t ehci_readl(uint32_t off)
+{
+    return qpci_io_readl(ehci1.dev, ehci1.bar, off);
+}
+
+static void ehci_writel(uint32_t off, uint32_t val)
+{
+    qpci_io_writel(ehci1.dev, ehci1.bar, off, val);
+}
+
+static void ehci_hcreset(void)
+{
+    ehci_writel(EHCI_USBCMD, USBCMD_HCRESET);
+    g_assert_cmphex(ehci_readl(EHCI_USBCMD) & USBCMD_HCRESET, ==, 0);
+    g_assert_cmphex(ehci_readl(EHCI_USBSTS) & USBSTS_HALT, ==, USBSTS_HALT);
+}
+
+static void ehci_write_qh(uint32_t next, uint32_t next_qtd)
+{
+    uint32_t qh[16] = {
+        next,
+        QH_EPCHAR_H | (64 << QH_EPCHAR_MPLEN_SH) |
+            (EHCI_QH_EPS_HIGH << QH_EPCHAR_EPS_SH),
+        1 << QH_EPCAP_MULT_SH,
+        0,                                      /* current qTD */
+        next_qtd,
+        NLPTR_T,                                /* alternate next qTD */
+        0,                                      /* token: inactive */
+    };
+
+    memwrite(QH_ADDR, qh, sizeof(qh));
+}
+
+/* Run a schedule into a descriptor that cannot be read. */
+static void ehci_run_into_dma_error(uint32_t usbcmd)
+{
+    uint32_t sts;
+    int i;
+
+    ehci_writel(EHCI_USBCMD, usbcmd);
+    for (i = 0; i < 16; i++) {
+        clock_step(FRAME_NS);
+        if (ehci_readl(EHCI_USBSTS) & USBSTS_HSE) {
+            break;
+        }
+    }
+    g_assert_cmphex(ehci_readl(EHCI_USBSTS) & USBSTS_HSE, ==, USBSTS_HSE);
+    g_assert_cmphex(ehci_readl(EHCI_USBCMD) & USBCMD_RUNSTOP, ==, 0);
+
+    for (i = 0; i < 4; i++) {
+        clock_step(FRAME_NS);
+    }
+    sts = ehci_readl(EHCI_USBSTS);
+    g_assert_cmphex(sts & USBSTS_HALT, ==, USBSTS_HALT);
+    g_assert_cmphex(sts & (USBSTS_ASS | USBSTS_PSS), ==, 0);
+
+    /* a reset must bring the controller back */
+    ehci_hcreset();
+    ehci_write_qh(QH_ADDR | NLPTR_TYPE_QH, NLPTR_T);
+    ehci_writel(EHCI_ASYNCLISTADDR, QH_ADDR);
+    ehci_writel(EHCI_USBCMD, USBCMD_RUNSTOP | USBCMD_ASE);
+    for (i = 0; i < 4; i++) {
+        clock_step(FRAME_NS);
+    }
+    g_assert_cmphex(ehci_readl(EHCI_USBSTS) & (USBSTS_HSE | USBSTS_HALT),
+                    ==, 0);
+    g_assert_cmphex(ehci_readl(EHCI_USBCMD) & USBCMD_RUNSTOP, ==,
+                    USBCMD_RUNSTOP);
+    ehci_hcreset();
+}
+
+static void pci_ehci_dma_error(void)
+{
+    uint32_t entries[32];
+    int i;
+
+    g_assert(pcibus != NULL);
+    ehci_hcreset();
+
+    /* async schedule: the list head itself is unreadable */
+    ehci_writel(EHCI_ASYNCLISTADDR, BAD_ADDR);
+    ehci_run_into_dma_error(USBCMD_RUNSTOP | USBCMD_ASE);
+
+    /* async schedule: the head QH links to an unreadable QH */
+    ehci_write_qh(BAD_ADDR | NLPTR_TYPE_QH, NLPTR_T);
+    ehci_writel(EHCI_ASYNCLISTADDR, QH_ADDR);
+    ehci_run_into_dma_error(USBCMD_RUNSTOP | USBCMD_ASE);
+
+    /* async schedule: a readable QH points at an unreadable qTD */
+    ehci_write_qh(QH_ADDR | NLPTR_TYPE_QH, BAD_ADDR);
+    ehci_writel(EHCI_ASYNCLISTADDR, QH_ADDR);
+    ehci_run_into_dma_error(USBCMD_RUNSTOP | USBCMD_ASE);
+
+    /* periodic schedule: the frame list points at an unreadable siTD */
+    for (i = 0; i < ARRAY_SIZE(entries); i++) {
+        entries[i] = BAD_ADDR | NLPTR_TYPE_SITD;
+    }
+    memwrite(FRAME_LIST_ADDR, entries, sizeof(entries));
+    ehci_writel(EHCI_PERIODICLISTBASE, FRAME_LIST_ADDR);
+    ehci_run_into_dma_error(USBCMD_RUNSTOP | USBCMD_PSE);
+}
+
 
 int main(int argc, char **argv)
 {
@@ -160,6 +278,7 @@ int main(int argc, char **argv)
     qtest_add_func("/ehci/pci/uhci-port-2", pci_uhci_port_2);
     qtest_add_func("/ehci/pci/ehci-port-2", pci_ehci_port_2);
     qtest_add_func("/ehci/pci/ehci-port-3-hotplug", pci_ehci_port_hotplug);
+    qtest_add_func("/ehci/pci/ehci-dma-error", pci_ehci_dma_error);
 
     qtest_start("-machine q35 -device ich9-usb-ehci1,bus=pcie.0,addr=1d.7,"
                 "multifunction=on,id=ich9-ehci-1 "
-- 
2.43.0



^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [PATCH 1/2] hw/usb/hcd-ehci: idle the schedule after a failed descriptor read
  2026-11-03 12:33 ` [PATCH 1/2] hw/usb/hcd-ehci: idle the schedule " Junjie Cao
@ 2026-11-03 15:19   ` Tommaso Califano
  0 siblings, 0 replies; 5+ messages in thread
From: Tommaso Califano @ 2026-11-03 15:19 UTC (permalink / raw)
  To: Junjie Cao, qemu-devel
  Cc: thuth, jamin_lin, philmd, clg, peter.maydell, kraxel,
	marcandre.lureau, farosas, lvivier, pbonzini,
	qemu-devel-bounces+tcalifano=redhat.com

On Tue Nov 3, 2026 at 1:33 PM CET, Junjie Cao wrote:
> Since get_dwords() started checking the DMA result, a failed descriptor
> read raises HSE, clears Run/Stop and makes the state function return
> without advancing.  That leaves the schedule in WAITLISTHEAD, FETCHQH,
> FETCHSITD or FETCHQTD, none of which ehci_advance_async_state() or
> ehci_advance_periodic_state() accept: the next frame hits their
> g_assert_not_reached().
>
> A guest gets there by pointing ASYNCLISTADDR, a QH's next qTD or a
> frame list entry at an address outside guest memory; QEMU aborts on the
> second frame.
>
> Return the schedule to EST_ACTIVE at those four sites, so the next pass
> sees Run/Stop clear and tears it down the way a stopped schedule is.
> The iTD and fill-queue reads already take the existing controller-reset
> path on error and are left alone.
>
> Fixes: 2b2a0e4f465a ("hw/usb/hcd-ehci: Check for DMA errors in get_dwords()/put_dwords()")
> Signed-off-by: Junjie Cao <junjie.cao@linux.dev>
> ---
>  hw/usb/hcd-ehci.c | 14 ++++++++++++++
>  1 file changed, 14 insertions(+)
>
> diff --git a/hw/usb/hcd-ehci.c b/hw/usb/hcd-ehci.c
> index f371e567f3..938707043f 100644
> --- a/hw/usb/hcd-ehci.c
> +++ b/hw/usb/hcd-ehci.c
> @@ -330,6 +330,15 @@ static int ehci_get_state(EHCIState *s, int async)
>      return async ? s->astate : s->pstate;
>  }
>  
> +/*
> + * get_dwords() has raised HSE and cleared Run/Stop.  Park the schedule
> + * in EST_ACTIVE so the next advance pass idles it instead of asserting.
> + */
> +static void ehci_abandon_pass(EHCIState *ehci, int async)
> +{
> +    ehci_set_state(ehci, async, EST_ACTIVE);
> +}
> +

I'm not entirely convinced a wrapper is needed here, but I see the value
of keeping the comment in a single place, so I'm fine with it.

>  static void ehci_set_fetch_addr(EHCIState *s, int async, uint64_t addr)
>  {
>      if (async) {
> @@ -1600,6 +1609,7 @@ static int ehci_state_waitlisthead(EHCIState *ehci,  int async)
>      for (i = 0; i < MAX_QH; i++) {
>          if (!get_dwords(ehci, NLPTR_GET(entry), (uint32_t *) &qh,
>                          ehci_qh_dwords(ehci))) {
> +            ehci_abandon_pass(ehci, async);
>              return 0;
>          }
>          ehci_trace_qh(NULL, NLPTR_GET(entry), &qh);
> @@ -1703,6 +1713,7 @@ static EHCIQueue *ehci_state_fetchqh(EHCIState *ehci, int async)
>      memset(&qh, 0, sizeof(qh));
>      if (!get_dwords(ehci, NLPTR_GET(q->qhaddr),
>                      (uint32_t *) &qh, ehci_qh_dwords(ehci))) {
> +        ehci_abandon_pass(ehci, async);
>          q = NULL;
>          goto out;
>      }
> @@ -1807,6 +1818,7 @@ static int ehci_state_fetchsitd(EHCIState *ehci, int async)
>  
>      if (!get_dwords(ehci, NLPTR_GET(entry), (uint32_t *)&sitd,
>                      sizeof(EHCIsitd) >> 2)) {
> +        ehci_abandon_pass(ehci, async);
>          return 0;
>      }
>      ehci_trace_sitd(ehci, entry, &sitd);
> @@ -1867,6 +1879,7 @@ static int ehci_state_fetchqtd(EHCIQueue *q)
>  
>      addr = NLPTR_GET(q->qtdaddr);
>      if (!get_dwords(q->ehci, addr +  8, &qtd.token,   1)) {
> +        ehci_abandon_pass(q->ehci, q->async);
>          return 0;
>      }
>      barrier();
> @@ -1878,6 +1891,7 @@ static int ehci_state_fetchqtd(EHCIQueue *q)
>          (q->ehci->caps_64bit_addr &&
>           !get_dwords(q->ehci, addr + offsetof(EHCIqtd, bufptr_hi),
>                       qtd.bufptr_hi, ARRAY_SIZE(qtd.bufptr_hi)))) {
> +        ehci_abandon_pass(q->ehci, q->async);
>          return 0;
>      }
>      ehci_trace_qtd(q, NLPTR_GET(q->qtdaddr), &qtd);

Reviewed-by: Tommaso Califano <tcalifano@redhat.com>

Regards,
Tommaso Califano



^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH 2/2] tests/qtest/usb-hcd-ehci: test descriptor reads that fail
  2026-11-03 12:33 ` [PATCH 2/2] tests/qtest/usb-hcd-ehci: test descriptor reads that fail Junjie Cao
@ 2026-11-03 15:24   ` Tommaso Califano
  0 siblings, 0 replies; 5+ messages in thread
From: Tommaso Califano @ 2026-11-03 15:24 UTC (permalink / raw)
  To: Junjie Cao, qemu-devel
  Cc: thuth, jamin_lin, philmd, clg, peter.maydell, kraxel,
	marcandre.lureau, farosas, lvivier, pbonzini,
	qemu-devel-bounces+tcalifano=redhat.com

On Tue Nov 3, 2026 at 1:33 PM CET, Junjie Cao wrote:
> Run the async schedule into an unreadable list head, into a head QH
> whose horizontal link is unreadable and into a QH whose next qTD is
> unreadable, and the periodic schedule into an unreadable siTD.  Each
> must raise HSE, clear Run/Stop and leave the controller halted with
> both schedules idle on the following frames; before the previous patch
> QEMU aborted there.  A reset afterwards must bring the controller back.
>
> Signed-off-by: Junjie Cao <junjie.cao@linux.dev>

I'm not very familiar with qtest, but this LGTM.

Reviewed-by: Tommaso Califano <tcalifano@redhat.com>

Regards,
Tommaso

> ---
>  tests/qtest/usb-hcd-ehci-test.c | 119 ++++++++++++++++++++++++++++++++
>  1 file changed, 119 insertions(+)
>
> diff --git a/tests/qtest/usb-hcd-ehci-test.c b/tests/qtest/usb-hcd-ehci-test.c
> index 87e37cdd7c..b0a0b6df9b 100644
> --- a/tests/qtest/usb-hcd-ehci-test.c
> +++ b/tests/qtest/usb-hcd-ehci-test.c
> @@ -12,6 +12,7 @@
>  #include "libqos/pci-pc.h"
>  #include "hw/usb/uhci-regs.h"
>  #include "hw/usb/ehci-regs.h"
> +#include "hw/usb/hcd-ehci.h"
>  #include "libqos/usb.h"
>  
>  static QPCIBus *pcibus;
> @@ -142,6 +143,123 @@ static void pci_ehci_port_hotplug(void)
>      usb_test_hotplug(global_qtest, "ich9-ehci-1", "3", pci_ehci_port_3_hotplug);
>  }
>  
> +/* operational registers, relative to the BAR */
> +#define EHCI_USBCMD             0x20
> +#define EHCI_USBSTS             0x24
> +#define EHCI_PERIODICLISTBASE   0x34
> +#define EHCI_ASYNCLISTADDR      0x38
> +
> +#define NLPTR_TYPE_QH           (1 << 1)
> +#define NLPTR_TYPE_SITD         (2 << 1)
> +#define NLPTR_T                 1
> +
> +#define QH_ADDR                 0x100000    /* in the default 128 MiB of RAM */
> +#define FRAME_LIST_ADDR         0x101000
> +#define BAD_ADDR                0x40000000  /* above RAM, nothing mapped */
> +#define FRAME_NS                1000000
> +
> +static uint32_t ehci_readl(uint32_t off)
> +{
> +    return qpci_io_readl(ehci1.dev, ehci1.bar, off);
> +}
> +
> +static void ehci_writel(uint32_t off, uint32_t val)
> +{
> +    qpci_io_writel(ehci1.dev, ehci1.bar, off, val);
> +}
> +
> +static void ehci_hcreset(void)
> +{
> +    ehci_writel(EHCI_USBCMD, USBCMD_HCRESET);
> +    g_assert_cmphex(ehci_readl(EHCI_USBCMD) & USBCMD_HCRESET, ==, 0);
> +    g_assert_cmphex(ehci_readl(EHCI_USBSTS) & USBSTS_HALT, ==, USBSTS_HALT);
> +}
> +
> +static void ehci_write_qh(uint32_t next, uint32_t next_qtd)
> +{
> +    uint32_t qh[16] = {
> +        next,
> +        QH_EPCHAR_H | (64 << QH_EPCHAR_MPLEN_SH) |
> +            (EHCI_QH_EPS_HIGH << QH_EPCHAR_EPS_SH),
> +        1 << QH_EPCAP_MULT_SH,
> +        0,                                      /* current qTD */
> +        next_qtd,
> +        NLPTR_T,                                /* alternate next qTD */
> +        0,                                      /* token: inactive */
> +    };
> +
> +    memwrite(QH_ADDR, qh, sizeof(qh));
> +}
> +
> +/* Run a schedule into a descriptor that cannot be read. */
> +static void ehci_run_into_dma_error(uint32_t usbcmd)
> +{
> +    uint32_t sts;
> +    int i;
> +
> +    ehci_writel(EHCI_USBCMD, usbcmd);
> +    for (i = 0; i < 16; i++) {
> +        clock_step(FRAME_NS);
> +        if (ehci_readl(EHCI_USBSTS) & USBSTS_HSE) {
> +            break;
> +        }
> +    }
> +    g_assert_cmphex(ehci_readl(EHCI_USBSTS) & USBSTS_HSE, ==, USBSTS_HSE);
> +    g_assert_cmphex(ehci_readl(EHCI_USBCMD) & USBCMD_RUNSTOP, ==, 0);
> +
> +    for (i = 0; i < 4; i++) {
> +        clock_step(FRAME_NS);
> +    }
> +    sts = ehci_readl(EHCI_USBSTS);
> +    g_assert_cmphex(sts & USBSTS_HALT, ==, USBSTS_HALT);
> +    g_assert_cmphex(sts & (USBSTS_ASS | USBSTS_PSS), ==, 0);
> +
> +    /* a reset must bring the controller back */
> +    ehci_hcreset();
> +    ehci_write_qh(QH_ADDR | NLPTR_TYPE_QH, NLPTR_T);
> +    ehci_writel(EHCI_ASYNCLISTADDR, QH_ADDR);
> +    ehci_writel(EHCI_USBCMD, USBCMD_RUNSTOP | USBCMD_ASE);
> +    for (i = 0; i < 4; i++) {
> +        clock_step(FRAME_NS);
> +    }
> +    g_assert_cmphex(ehci_readl(EHCI_USBSTS) & (USBSTS_HSE | USBSTS_HALT),
> +                    ==, 0);
> +    g_assert_cmphex(ehci_readl(EHCI_USBCMD) & USBCMD_RUNSTOP, ==,
> +                    USBCMD_RUNSTOP);
> +    ehci_hcreset();
> +}
> +
> +static void pci_ehci_dma_error(void)
> +{
> +    uint32_t entries[32];
> +    int i;
> +
> +    g_assert(pcibus != NULL);
> +    ehci_hcreset();
> +
> +    /* async schedule: the list head itself is unreadable */
> +    ehci_writel(EHCI_ASYNCLISTADDR, BAD_ADDR);
> +    ehci_run_into_dma_error(USBCMD_RUNSTOP | USBCMD_ASE);
> +
> +    /* async schedule: the head QH links to an unreadable QH */
> +    ehci_write_qh(BAD_ADDR | NLPTR_TYPE_QH, NLPTR_T);
> +    ehci_writel(EHCI_ASYNCLISTADDR, QH_ADDR);
> +    ehci_run_into_dma_error(USBCMD_RUNSTOP | USBCMD_ASE);
> +
> +    /* async schedule: a readable QH points at an unreadable qTD */
> +    ehci_write_qh(QH_ADDR | NLPTR_TYPE_QH, BAD_ADDR);
> +    ehci_writel(EHCI_ASYNCLISTADDR, QH_ADDR);
> +    ehci_run_into_dma_error(USBCMD_RUNSTOP | USBCMD_ASE);
> +
> +    /* periodic schedule: the frame list points at an unreadable siTD */
> +    for (i = 0; i < ARRAY_SIZE(entries); i++) {
> +        entries[i] = BAD_ADDR | NLPTR_TYPE_SITD;
> +    }
> +    memwrite(FRAME_LIST_ADDR, entries, sizeof(entries));
> +    ehci_writel(EHCI_PERIODICLISTBASE, FRAME_LIST_ADDR);
> +    ehci_run_into_dma_error(USBCMD_RUNSTOP | USBCMD_PSE);
> +}
> +
>  
>  int main(int argc, char **argv)
>  {
> @@ -160,6 +278,7 @@ int main(int argc, char **argv)
>      qtest_add_func("/ehci/pci/uhci-port-2", pci_uhci_port_2);
>      qtest_add_func("/ehci/pci/ehci-port-2", pci_ehci_port_2);
>      qtest_add_func("/ehci/pci/ehci-port-3-hotplug", pci_ehci_port_hotplug);
> +    qtest_add_func("/ehci/pci/ehci-dma-error", pci_ehci_dma_error);
>  
>      qtest_start("-machine q35 -device ich9-usb-ehci1,bus=pcie.0,addr=1d.7,"
>                  "multifunction=on,id=ich9-ehci-1 "




^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-11-03 15:25 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-03 12:33 [PATCH 0/2] hw/usb/hcd-ehci: fix the abort after a failed descriptor read Junjie Cao
2026-11-03 12:33 ` [PATCH 1/2] hw/usb/hcd-ehci: idle the schedule " Junjie Cao
2026-11-03 15:19   ` Tommaso Califano
2026-11-03 12:33 ` [PATCH 2/2] tests/qtest/usb-hcd-ehci: test descriptor reads that fail Junjie Cao
2026-11-03 15:24   ` Tommaso Califano

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.