Add a usb-storage device to xhci tests, enable USB Mass Storage Bulk endpoints, and run some MSD commands through it.
Signed-off-by: Nicholas Piggin <npig...@gmail.com> --- tests/qtest/usb-hcd-xhci-test.c | 359 +++++++++++++++++++++++++++++++- 1 file changed, 351 insertions(+), 8 deletions(-) diff --git a/tests/qtest/usb-hcd-xhci-test.c b/tests/qtest/usb-hcd-xhci-test.c index 291d1dfc36e..39c5c36e940 100644 --- a/tests/qtest/usb-hcd-xhci-test.c +++ b/tests/qtest/usb-hcd-xhci-test.c @@ -373,6 +373,20 @@ static uint64_t submit_cr_trb(XHCIQState *s, const XHCITRB *trb) return ret; } +static uint64_t submit_tr_trb(XHCIQState *s, int slot, int ep, + const XHCITRB *trb) +{ + XHCIQSlotState *sl = &s->slots[slot]; + XHCIQTRState *tr = &sl->transfer_ring[ep]; + uint64_t ret; + + ret = queue_trb(s, tr, trb); + + xhci_db_writel(s, slot, 1 + ep); /* doorbell slot, EP<ep> target */ + + return ret; +} + static void xhci_enable_device(XHCIQState *s) { XHCIQTRState *tr; @@ -451,14 +465,165 @@ static void xhci_enable_device(XHCIQState *s) for (i = 0; i < s->maxports; i++) { value = xhci_port_readl(s, i, 0); /* PORTSC */ - /* All ports should be disabled */ - g_assert(!(value & XHCI_PORTSC_CCS)); - g_assert(!(value & XHCI_PORTSC_PED)); - g_assert(((value >> XHCI_PORTSC_PLS_SHIFT) & - XHCI_PORTSC_PLS_MASK) == 5); + /* First port should be attached and enabled if we have usb-storage */ + if (qtest_has_device("usb-storage") && i == 0) { + g_assert(value & XHCI_PORTSC_CCS); + g_assert(value & XHCI_PORTSC_PED); + /* Port Speed must be identified (non-zero) */ + g_assert(((value >> XHCI_PORTSC_SPEED_SHIFT) & + XHCI_PORTSC_SPEED_MASK) != 0); + } else { + g_assert(!(value & XHCI_PORTSC_CCS)); + g_assert(!(value & XHCI_PORTSC_PED)); + g_assert(((value >> XHCI_PORTSC_PLS_SHIFT) & + XHCI_PORTSC_PLS_MASK) == 5); + } } } +/* XXX: what should these values be? */ +#define TRB_MAX_PACKET_SIZE 0x200 +#define TRB_AVERAGE_LENGTH 0x200 + +static void xhci_enable_slot(XHCIQState *s) +{ + XHCIQTRState *tr; + uint64_t input_context; + XHCITRB trb; + uint64_t tag; + g_autofree void *mem = g_malloc0(0x1000); /* buffer for writing to guest */ + uint32_t *dc; /* device context */ + + /* Issue a command ring enable slot */ + memset(&trb, 0, TRB_SIZE); + trb.control |= CR_ENABLE_SLOT << TRB_TYPE_SHIFT; + trb.control |= TRB_TR_IOC; + tag = submit_cr_trb(s, &trb); + wait_event_trb(s, &trb); + g_assert_cmphex(trb.parameter , ==, tag); + g_assert_cmpint(TRB_TYPE(trb), ==, ER_COMMAND_COMPLETE); + s->slotid = (trb.control >> TRB_CR_SLOTID_SHIFT) & 0xff; + + /* 32-byte input context size, should check HCCPARAMS1 for 64-byte size */ + input_context = xhci_guest_zalloc(s, 0x420); + + /* Set input control context */ + memset(mem, 0, 0x420); + ((uint32_t *)mem)[1] = cpu_to_le32(0x3); /* Add device contexts 0 and 1 */ + + /* Slot context */ + dc = mem + 1 * 0x20; + dc[0] = cpu_to_le32(1 << 27); /* 1 context entry */ + dc[1] = cpu_to_le32(1 << 16); /* 1 port number */ + + /* Endpoint 0 context */ + tr = &s->slots[s->slotid].transfer_ring[0]; + tr->addr = xhci_guest_zalloc(s, 0x1000); + tr->trb_entries = 0x10; + tr->trb_c = 1; + + dc = mem + 2 * 0x20; + dc[0] = 0; + dc[1] = cpu_to_le32((ET_CONTROL << EP_TYPE_SHIFT) | + (TRB_MAX_PACKET_SIZE << 16)); + dc[2] = cpu_to_le32((tr->addr & 0xffffffff) | 1); /* DCS=1 */ + dc[3] = cpu_to_le32(tr->addr >> 32); + dc[4] = cpu_to_le32(TRB_AVERAGE_LENGTH); + qtest_memwrite(s->parent->qts, input_context, mem, 0x420); + + s->slots[s->slotid].device_context = xhci_guest_zalloc(s, 0x400); + + ((uint64_t *)mem)[0] = cpu_to_le64(s->slots[s->slotid].device_context); + qtest_memwrite(s->parent->qts, s->dc_base_array + 8 * s->slotid, mem, 8); + + /* Issue a command ring address device */ + memset(&trb, 0, TRB_SIZE); + trb.parameter = input_context; + trb.control |= CR_ADDRESS_DEVICE << TRB_TYPE_SHIFT; + trb.control |= s->slotid << TRB_CR_SLOTID_SHIFT; + tag = submit_cr_trb(s, &trb); + wait_event_trb(s, &trb); + g_assert_cmphex(trb.parameter , ==, tag); + g_assert_cmpint(TRB_TYPE(trb), ==, ER_COMMAND_COMPLETE); + + guest_free(&s->parent->alloc, input_context); + + /* Check EP0 is running */ + qtest_memread(s->parent->qts, s->slots[s->slotid].device_context, mem, 0x400); + g_assert((((uint32_t *)mem)[8] & 0x3) == EP_RUNNING); +} + +static void xhci_enable_msd_bulk_endpoints(XHCIQState *s) +{ + XHCIQTRState *tr; + uint64_t input_context; + XHCITRB trb; + uint64_t tag; + g_autofree void *mem = g_malloc0(0x1000); /* buffer for writing to guest */ + uint32_t *dc; /* device context */ + + /* Configure 2 more endpoints */ + + /* 32-byte input context size, should check HCCPARAMS1 for 64-byte size */ + input_context = xhci_guest_zalloc(s, 0x420); + + /* Set input control context */ + memset(mem, 0, 0x420); + ((uint32_t *)mem)[1] = cpu_to_le32(0x19); /* Add device contexts 0, 3, 4 */ + + /* Slot context */ + dc = mem + 1 * 0x20; + dc[0] = cpu_to_le32(1 << 27); /* 1 context entry */ + dc[1] = cpu_to_le32(1 << 16); /* 1 port number */ + + /* Endpoint 1 (IN) context */ + tr = &s->slots[s->slotid].transfer_ring[2]; + tr->addr = xhci_guest_zalloc(s, 0x1000); + tr->trb_entries = 0x10; + tr->trb_c = 1; + + dc = mem + 4 * 0x20; + dc[0] = 0; + dc[1] = cpu_to_le32((ET_BULK_IN << EP_TYPE_SHIFT) | + (TRB_MAX_PACKET_SIZE << 16)); + dc[2] = cpu_to_le32((tr->addr & 0xffffffff) | 1); /* DCS=1 */ + dc[3] = cpu_to_le32(tr->addr >> 32); + dc[4] = cpu_to_le32(TRB_AVERAGE_LENGTH); + + /* Endpoint 2 (OUT) context */ + tr = &s->slots[s->slotid].transfer_ring[3]; + tr->addr = xhci_guest_zalloc(s, 0x1000); + tr->trb_entries = 0x10; + tr->trb_c = 1; + + dc = mem + 5 * 0x20; + dc[0] = 0; + dc[1] = cpu_to_le32((ET_BULK_OUT << EP_TYPE_SHIFT) | + (TRB_MAX_PACKET_SIZE << 16)); + dc[2] = cpu_to_le32((tr->addr & 0xffffffff) | 1); /* DCS=1 */ + dc[3] = cpu_to_le32(tr->addr >> 32); + dc[4] = cpu_to_le32(TRB_AVERAGE_LENGTH); + qtest_memwrite(s->parent->qts, input_context, mem, 0x420); + + /* Issue a command ring configure endpoint */ + memset(&trb, 0, TRB_SIZE); + trb.parameter = input_context; + trb.control |= CR_CONFIGURE_ENDPOINT << TRB_TYPE_SHIFT; + trb.control |= s->slotid << TRB_CR_SLOTID_SHIFT; + tag = submit_cr_trb(s, &trb); + wait_event_trb(s, &trb); + g_assert_cmphex(trb.parameter , ==, tag); + g_assert_cmpint(TRB_TYPE(trb), ==, ER_COMMAND_COMPLETE); + + guest_free(&s->parent->alloc, input_context); + + /* Check EPs are running */ + qtest_memread(s->parent->qts, s->slots[s->slotid].device_context, mem, 0x400); + g_assert((((uint32_t *)mem)[1*8] & 0x3) == EP_RUNNING); + g_assert((((uint32_t *)mem)[3*8] & 0x3) == EP_RUNNING); + g_assert((((uint32_t *)mem)[4*8] & 0x3) == EP_RUNNING); +} + static void xhci_disable_device(XHCIQState *s) { int i; @@ -477,6 +642,144 @@ static void xhci_disable_device(XHCIQState *s) guest_free(&s->parent->alloc, s->dc_base_array); } +struct QEMU_PACKED usb_msd_cbw { + uint32_t sig; + uint32_t tag; + uint32_t data_len; + uint8_t flags; + uint8_t lun; + uint8_t cmd_len; + uint8_t cmd[16]; +}; + +struct QEMU_PACKED usb_msd_csw { + uint32_t sig; + uint32_t tag; + uint32_t residue; + uint8_t status; +}; + +static ssize_t xhci_submit_scsi_cmd(XHCIQState *s, + const uint8_t *cmd, uint8_t cmd_len, + void *data, uint32_t data_len, + bool data_in) +{ + struct usb_msd_cbw cbw; + struct usb_msd_csw csw; + uint64_t trb_data; + XHCITRB trb; + uint64_t tag; + + /* TRB data payload */ + trb_data = xhci_guest_zalloc(s, data_len > sizeof(cbw) ? data_len : sizeof(cbw)); + + memset(&cbw, 0, sizeof(cbw)); + cbw.sig = cpu_to_le32(0x43425355); + cbw.tag = cpu_to_le32(0); + cbw.data_len = cpu_to_le32(data_len); + cbw.flags = data_in ? 0x80 : 0x00; + cbw.lun = 0; + cbw.cmd_len = cmd_len; /* cmd len */ + memcpy(cbw.cmd, cmd, cmd_len); + qtest_memwrite(s->parent->qts, trb_data, &cbw, sizeof(cbw)); + + /* Issue a transfer ring ep 3 data (out) */ + memset(&trb, 0, TRB_SIZE); + trb.parameter = trb_data; + trb.status = sizeof(cbw); + trb.control |= TR_NORMAL << TRB_TYPE_SHIFT; + trb.control |= TRB_TR_IOC; + tag = submit_tr_trb(s, s->slotid, 3, &trb); + wait_event_trb(s, &trb); + g_assert_cmphex(trb.parameter, ==, tag); + g_assert_cmpint(TRB_TYPE(trb), ==, ER_TRANSFER); + + if (data_in) { + g_assert(data_len); + + /* Issue a transfer ring ep 2 data (in) */ + memset(&trb, 0, TRB_SIZE); + trb.parameter = trb_data; + trb.status = data_len; /* data_len bytes, no more packets */ + trb.control |= TR_NORMAL << TRB_TYPE_SHIFT; + trb.control |= TRB_TR_IOC; + tag = submit_tr_trb(s, s->slotid, 2, &trb); + wait_event_trb(s, &trb); + g_assert_cmphex(trb.parameter, ==, tag); + g_assert_cmpint(TRB_TYPE(trb), ==, ER_TRANSFER); + + qtest_memread(s->parent->qts, trb_data, data, data_len); + } else if (data_len) { + qtest_memwrite(s->parent->qts, trb_data, data, data_len); + + /* Issue a transfer ring ep 3 data (out) */ + memset(&trb, 0, TRB_SIZE); + trb.parameter = trb_data; + trb.status = data_len; /* data_len bytes, no more packets */ + trb.control |= TR_NORMAL << TRB_TYPE_SHIFT; + trb.control |= TRB_TR_IOC; + tag = submit_tr_trb(s, s->slotid, 3, &trb); + wait_event_trb(s, &trb); + g_assert_cmphex(trb.parameter, ==, tag); + g_assert_cmpint(TRB_TYPE(trb), ==, ER_TRANSFER); + } else { + /* No data */ + } + + /* Issue a transfer ring ep 2 data (in) */ + memset(&trb, 0, TRB_SIZE); + trb.parameter = trb_data; + trb.status = sizeof(csw); + trb.control |= TR_NORMAL << TRB_TYPE_SHIFT; + trb.control |= TRB_TR_IOC; + tag = submit_tr_trb(s, s->slotid, 2, &trb); + wait_event_trb(s, &trb); + g_assert_cmphex(trb.parameter, ==, tag); + g_assert_cmpint(TRB_TYPE(trb), ==, ER_TRANSFER); + + qtest_memread(s->parent->qts, trb_data, &csw, sizeof(csw)); + + guest_free(&s->parent->alloc, trb_data); + + g_assert(csw.sig == cpu_to_le32(0x53425355)); + g_assert(csw.tag == cpu_to_le32(0)); + if (csw.status) { + return -1; + } + return data_len - le32_to_cpu(csw.residue); /* bytes copied */ +} + +#include "scsi/constants.h" + +static void xhci_test_msd(XHCIQState *s) +{ + uint8_t scsi_cmd[16]; + g_autofree void *mem = g_malloc0(0x1000); /* buffer for writing to guest */ + + /* Clear SENSE data */ + memset(scsi_cmd, 0, sizeof(scsi_cmd)); + scsi_cmd[0] = INQUIRY; + if (xhci_submit_scsi_cmd(s, scsi_cmd, 6, mem, 0, false) < 0) { + g_assert_not_reached(); + } + + /* Report LUNs */ + memset(scsi_cmd, 0, sizeof(scsi_cmd)); + scsi_cmd[0] = REPORT_LUNS; + /* length in big-endian */ + scsi_cmd[6] = 0x00; + scsi_cmd[7] = 0x00; + scsi_cmd[8] = 0x01; + scsi_cmd[9] = 0x00; + + if (xhci_submit_scsi_cmd(s, scsi_cmd, 16, mem, 0x100, true) < 0) { + g_assert_not_reached(); + } + + /* Check REPORT_LUNS data found 1 LUN */ + g_assert(((uint32_t *)mem)[0] == cpu_to_be32(8)); /* LUN List Length */ +} + /* * This test brings up an endpoint and runs some noops through its command * ring and gets responses back on the event ring, then brings up a device @@ -490,9 +793,18 @@ static void test_xhci_stress_rings(const void *arg) uint64_t tag; int i; - s = xhci_boot("-M q35 " - "-device %s,id=xhci,bus=pcie.0,addr=1d.0 ", - td->device); + if (qtest_has_device("usb-storage")) { + s = xhci_boot("-M q35 " + "-device %s,id=xhci,bus=pcie.0,addr=1d.0 " + "-device usb-storage,bus=xhci.0,drive=drive0 " + "-drive id=drive0,if=none,file=null-co://," + "file.read-zeroes=on,format=raw ", + td->device); + } else { + s = xhci_boot("-M q35 " + "-device %s,id=xhci,bus=pcie.0,addr=1d.0 ", + td->device); + } g_assert_cmphex(s->fingerprint, ==, td->fingerprint); xhci_enable_device(s); @@ -513,6 +825,34 @@ static void test_xhci_stress_rings(const void *arg) xhci_shutdown(s); } +/* + * This test brings up a USB MSD endpoint and runs MSD (SCSI) commands. + */ +static void test_usb_msd(const void *arg) +{ + const TestData *td = arg; + XHCIQState *s; + + s = xhci_boot("-M q35 " + "-device %s,id=xhci,bus=pcie.0,addr=1d.0 " + "-device usb-storage,bus=xhci.0,drive=drive0 " + "-drive id=drive0,if=none,file=null-co://," + "file.read-zeroes=on,format=raw ", + td->device); + g_assert_cmphex(s->fingerprint, ==, td->fingerprint); + + xhci_enable_device(s); + + xhci_enable_slot(s); + + xhci_enable_msd_bulk_endpoints(s); + + xhci_test_msd(s); + + xhci_disable_device(s); + xhci_shutdown(s); +} + static void add_test(const char *name, TestData *td, void (*fn)(const void *)) { g_autofree char *full_name = g_strdup_printf( @@ -530,6 +870,9 @@ static void add_tests(TestData *td) add_test("usb-ccid", td, test_usb_ccid_hotplug); } add_test("xhci-stress-rings", td, test_xhci_stress_rings); + if (qtest_has_device("usb-storage")) { + add_test("usb-msd", td, test_usb_msd); + } } /* tests */ -- 2.47.1