From 95c94f8968325390d63b3c407584e91e6d7fb010 Mon Sep 17 00:00:00 2001 From: npes87184 Date: Wed, 1 Aug 2018 20:24:10 +0800 Subject: [PATCH 1/7] docs/usb2.txt: ehci has six ports In commit 5cc194caeb019cf1dae7f74ccbdf0401a56c2ac6, the number of ehci ports is corrected to six. Fix docs related to it. Signed-off-by: npes87184 Message-id: 20180801122410.10343-1-npes87184@gmail.com Signed-off-by: Gerd Hoffmann --- docs/usb2.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/usb2.txt b/docs/usb2.txt index f63c8d9465..172614d3a7 100644 --- a/docs/usb2.txt +++ b/docs/usb2.txt @@ -94,8 +94,8 @@ physical port addressing First you can (for all USB devices) specify the physical port where the device will show up in the guest. This can be done using the -"port" property. UHCI has two root ports (1,2). EHCI has four root -ports (1-4), the emulated (1.1) USB hub has eight ports. +"port" property. UHCI has two root ports (1,2). EHCI has six root +ports (1-6), the emulated (1.1) USB hub has eight ports. Plugging a tablet into UHCI port 1 works like this: From 7c48b95df47458d10726d6dca0c4c33eb5eace40 Mon Sep 17 00:00:00 2001 From: Sebastian Bauer Date: Sun, 29 Jul 2018 21:19:28 +0200 Subject: [PATCH 2/7] ohci: Clear the interrupt counter for erroneous transfers This is mandated by the ohci specification. It tells at 6.4.4 on page 104 that for transfer descriptors that are retired with an error the done queue interrupt counter is cleared as if the interrupt delay field of the descriptions were zero. Before this change, error conditions were handled similarly to the successful condition which is especially troublesome for control transfers. Some drivers (e.g., the AmigaOS-one) as well as the example code in the spec, set the setup stage with an interrupt delay of seven (which means no interrupt). This is fine under normal conditions, because one usually doesn't want to be notified about the completion of this stage. However, if an error occurs in this stage, these drivers will not get notified with the current implementation. The fix addresses this by following the spec more closely. Also, otherwise, the ability to set interrupt delay to seven would be useless. Note that Linux drivers that I looked at don't seem to be affected as they set six as the interrupt delay presumably for the reason that they won't get notified otherwise. Signed-off-by: Sebastian Bauer Message-id: 20180729191928.11254-1-mail@sebastianbauer.info Signed-off-by: Gerd Hoffmann --- hw/usb/hcd-ohci.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hw/usb/hcd-ohci.c b/hw/usb/hcd-ohci.c index d4c0293db5..98da5f0f04 100644 --- a/hw/usb/hcd-ohci.c +++ b/hw/usb/hcd-ohci.c @@ -1156,6 +1156,9 @@ static int ohci_service_td(OHCIState *ohci, struct ohci_ed *ed) OHCI_SET_BM(td.flags, TD_EC, 3); break; } + /* An error occured so we have to clear the interrupt counter. See + * spec at 6.4.4 on page 104 */ + ohci->done_count = 0; } ed->head |= OHCI_ED_H; } From 47bff13cea8dd3e6ae869c3203723d93bd734637 Mon Sep 17 00:00:00 2001 From: Bandan Das Date: Fri, 20 Jul 2018 17:40:16 -0400 Subject: [PATCH 3/7] dev-mtp: add support for canceling transaction The initiator can choose to cancel an ongoing request which is specified by bRequest=0x64. If such a request arrives, free up any pending state Signed-off-by: Bandan Das Message-id: 20180720214020.22897-2-bsd@redhat.com Signed-off-by: Gerd Hoffmann --- hw/usb/dev-mtp.c | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/hw/usb/dev-mtp.c b/hw/usb/dev-mtp.c index 1ded7ac9a3..c40b0de0bb 100644 --- a/hw/usb/dev-mtp.c +++ b/hw/usb/dev-mtp.c @@ -82,6 +82,7 @@ enum mtp_code { FMT_ASSOCIATION = 0x3001, /* event codes */ + EVT_CANCEL_TRANSACTION = 0x4001, EVT_OBJ_ADDED = 0x4002, EVT_OBJ_REMOVED = 0x4003, EVT_OBJ_INFO_CHANGED = 0x4007, @@ -1551,14 +1552,35 @@ static void usb_mtp_handle_control(USBDevice *dev, USBPacket *p, int length, uint8_t *data) { int ret; + MTPState *s = USB_MTP(dev); + uint16_t *event = (uint16_t *)data; - ret = usb_desc_handle_control(dev, p, request, value, index, length, data); - if (ret >= 0) { - return; + switch (request) { + case ClassInterfaceOutRequest | 0x64: + if (*event == EVT_CANCEL_TRANSACTION) { + g_free(s->result); + s->result = NULL; + usb_mtp_data_free(s->data_in); + s->data_in = NULL; + if (s->write_pending) { + g_free(s->dataset.filename); + s->write_pending = false; + } + usb_mtp_data_free(s->data_out); + s->data_out = NULL; + } else { + p->status = USB_RET_STALL; + } + break; + default: + ret = usb_desc_handle_control(dev, p, request, + value, index, length, data); + if (ret >= 0) { + return; + } } trace_usb_mtp_stall(dev->addr, "unknown control request"); - p->status = USB_RET_STALL; } static void usb_mtp_cancel_packet(USBDevice *dev, USBPacket *p) From 406f35d7fcf5f029780d2e0cc9fa0cc37856d57c Mon Sep 17 00:00:00 2001 From: Bandan Das Date: Fri, 20 Jul 2018 17:40:17 -0400 Subject: [PATCH 4/7] dev-mtp: fix buffer allocation for writing file contents usb_mtp_realloc() was being incorrectly used when allocating buffer for incoming data. Set d->length only after resizing the buffer. Signed-off-by: Bandan Das Message-id: 20180720214020.22897-3-bsd@redhat.com Signed-off-by: Gerd Hoffmann --- hw/usb/dev-mtp.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/hw/usb/dev-mtp.c b/hw/usb/dev-mtp.c index c40b0de0bb..1b72603dc5 100644 --- a/hw/usb/dev-mtp.c +++ b/hw/usb/dev-mtp.c @@ -1721,6 +1721,7 @@ static void usb_mtp_get_data(MTPState *s, mtp_container *container, MTPData *d = s->data_out; uint64_t dlen; uint32_t data_len = p->iov.size; + uint64_t total_len; if (!d) { usb_mtp_queue_result(s, RES_INVALID_OBJECTINFO, 0, @@ -1729,10 +1730,11 @@ static void usb_mtp_get_data(MTPState *s, mtp_container *container, } if (d->first) { /* Total length of incoming data */ - d->length = cpu_to_le32(container->length) - sizeof(mtp_container); + total_len = cpu_to_le32(container->length) - sizeof(mtp_container); /* Length of data in this packet */ data_len -= sizeof(mtp_container); - usb_mtp_realloc(d, d->length); + usb_mtp_realloc(d, total_len); + d->length += total_len; d->offset = 0; d->first = false; } From d33e3e4bf8df7a7f1bd538bc19d17c8c21f14df2 Mon Sep 17 00:00:00 2001 From: Bandan Das Date: Fri, 20 Jul 2018 17:40:18 -0400 Subject: [PATCH 5/7] dev-mtp: retry write for incomplete transfers For large buffers, write may not copy the full buffer. For example, on Linux, write imposes a limit of 0x7ffff000. Note that this does not fix >4G transfers but ~>2G files will transfer successfully. Signed-off-by: Bandan Das Message-id: 20180720214020.22897-4-bsd@redhat.com Signed-off-by: Gerd Hoffmann --- hw/usb/dev-mtp.c | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/hw/usb/dev-mtp.c b/hw/usb/dev-mtp.c index 1b72603dc5..c8f6eb4e9e 100644 --- a/hw/usb/dev-mtp.c +++ b/hw/usb/dev-mtp.c @@ -1602,6 +1602,24 @@ static void utf16_to_str(uint8_t len, uint16_t *arr, char *name) g_free(wstr); } +/* Wrapper around write, returns 0 on failure */ +static uint64_t write_retry(int fd, void *buf, uint64_t size) +{ + uint64_t bytes_left = size, ret; + + while (bytes_left > 0) { + ret = write(fd, buf, bytes_left); + if ((ret == -1) && (errno != EINTR || errno != EAGAIN || + errno != EWOULDBLOCK)) { + break; + } + bytes_left -= ret; + buf += ret; + } + + return size - bytes_left; +} + static void usb_mtp_write_data(MTPState *s) { MTPData *d = s->data_out; @@ -1644,8 +1662,8 @@ static void usb_mtp_write_data(MTPState *s) goto success; } - rc = write(d->fd, d->data, s->dataset.size); - if (rc == -1) { + rc = write_retry(d->fd, d->data, s->dataset.size); + if (!rc) { usb_mtp_queue_result(s, RES_STORE_FULL, d->trans, 0, 0, 0, 0); goto done; From 3e096650a64daaac261c289b569362b06995e379 Mon Sep 17 00:00:00 2001 From: Bandan Das Date: Fri, 20 Jul 2018 17:40:19 -0400 Subject: [PATCH 6/7] dev-mtp: Add support for > 4GB file transfers To support larger file transfers, rely on a short packet to detect end of the data phase and rewrite d->length to the size received Signed-off-by: Bandan Das Message-id: 20180720214020.22897-5-bsd@redhat.com Signed-off-by: Gerd Hoffmann --- hw/usb/dev-mtp.c | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/hw/usb/dev-mtp.c b/hw/usb/dev-mtp.c index c8f6eb4e9e..2e3ea58da6 100644 --- a/hw/usb/dev-mtp.c +++ b/hw/usb/dev-mtp.c @@ -147,9 +147,12 @@ struct MTPData { uint32_t trans; uint64_t offset; uint64_t length; - uint32_t alloc; + uint64_t alloc; uint8_t *data; bool first; + /* Used for >4G file sizes */ + bool pending; + uint64_t cached_length; int fd; }; @@ -1626,7 +1629,7 @@ static void usb_mtp_write_data(MTPState *s) MTPObject *parent = usb_mtp_object_lookup(s, s->dataset.parent_handle); char *path = NULL; - int rc = -1; + uint64_t rc; mode_t mask = 0644; assert(d != NULL); @@ -1643,7 +1646,7 @@ static void usb_mtp_write_data(MTPState *s) d->fd = mkdir(path, mask); goto free; } - if (s->dataset.size < d->length) { + if ((s->dataset.size != 0xFFFFFFFF) && (s->dataset.size < d->length)) { usb_mtp_queue_result(s, RES_STORE_FULL, d->trans, 0, 0, 0, 0); goto done; @@ -1754,13 +1757,27 @@ static void usb_mtp_get_data(MTPState *s, mtp_container *container, usb_mtp_realloc(d, total_len); d->length += total_len; d->offset = 0; + d->cached_length = total_len; d->first = false; + d->pending = false; + } + + if (d->pending) { + usb_mtp_realloc(d, d->cached_length); + d->length += d->cached_length; + d->pending = false; } if (d->length - d->offset > data_len) { dlen = data_len; } else { dlen = d->length - d->offset; + /* Check for cached data for large files */ + if ((s->dataset.size == 0xFFFFFFFF) && (dlen < p->iov.size)) { + usb_mtp_realloc(d, p->iov.size - dlen); + d->length += p->iov.size - dlen; + dlen = p->iov.size; + } } switch (d->code) { @@ -1780,12 +1797,18 @@ static void usb_mtp_get_data(MTPState *s, mtp_container *container, case CMD_SEND_OBJECT: usb_packet_copy(p, d->data + d->offset, dlen); d->offset += dlen; - if (d->offset == d->length) { + if ((p->iov.size % 64) || !p->iov.size) { + assert((s->dataset.size == 0xFFFFFFFF) || + (s->dataset.size == d->length)); + usb_mtp_write_data(s); usb_mtp_data_free(s->data_out); s->data_out = NULL; return; } + if (d->offset == d->length) { + d->pending = true; + } break; default: p->status = USB_RET_STALL; From 15aa757d0523b9d6fb26ea1e5c967ba0619dce0a Mon Sep 17 00:00:00 2001 From: Bandan Das Date: Fri, 20 Jul 2018 17:40:20 -0400 Subject: [PATCH 7/7] dev-mtp: rename x-root to rootdir x-root was renamed as such owing to the experimental nature of the property; the underlying filesystem semantics were undecided Signed-off-by: Bandan Das Message-id: 20180720214020.22897-6-bsd@redhat.com Signed-off-by: Gerd Hoffmann --- hw/usb/dev-mtp.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hw/usb/dev-mtp.c b/hw/usb/dev-mtp.c index 2e3ea58da6..3fdc4b0da1 100644 --- a/hw/usb/dev-mtp.c +++ b/hw/usb/dev-mtp.c @@ -2018,7 +2018,7 @@ static void usb_mtp_realize(USBDevice *dev, Error **errp) QTAILQ_INIT(&s->objects); if (s->desc == NULL) { if (s->root == NULL) { - error_setg(errp, "usb-mtp: x-root property must be configured"); + error_setg(errp, "usb-mtp: rootdir property must be configured"); return; } s->desc = strrchr(s->root, '/'); @@ -2047,7 +2047,7 @@ static const VMStateDescription vmstate_usb_mtp = { }; static Property mtp_properties[] = { - DEFINE_PROP_STRING("x-root", MTPState, root), + DEFINE_PROP_STRING("rootdir", MTPState, root), DEFINE_PROP_STRING("desc", MTPState, desc), DEFINE_PROP_BOOL("readonly", MTPState, readonly, true), DEFINE_PROP_END_OF_LIST(),