ehci: iovec support, remove buffer
Map guest memory and pass on a direct pointer instead of copying the bits to a indirect buffer. EHCI transfer descriptors can reference multiple (physical guest) pages so we'll actually start seeing usb packets wich carry iovec with more than one element. Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
This commit is contained in:
parent
df5e66eefb
commit
0ce668bc52
151
hw/usb-ehci.c
151
hw/usb-ehci.c
@ -28,6 +28,7 @@
|
||||
#include "pci.h"
|
||||
#include "monitor.h"
|
||||
#include "trace.h"
|
||||
#include "dma.h"
|
||||
|
||||
#define EHCI_DEBUG 0
|
||||
|
||||
@ -269,6 +270,7 @@ typedef struct EHCIqtd {
|
||||
|
||||
uint32_t bufptr[5]; // Standard buffer pointer
|
||||
#define QTD_BUFPTR_MASK 0xfffff000
|
||||
#define QTD_BUFPTR_SH 12
|
||||
} EHCIqtd;
|
||||
|
||||
/* EHCI spec version 1.0 Section 3.6
|
||||
@ -357,7 +359,7 @@ struct EHCIQueue {
|
||||
uint32_t qtdaddr; // address QTD read from
|
||||
|
||||
USBPacket packet;
|
||||
uint8_t buffer[BUFF_SIZE];
|
||||
QEMUSGList sgl;
|
||||
int pid;
|
||||
uint32_t tbytes;
|
||||
enum async_state async;
|
||||
@ -414,7 +416,7 @@ struct EHCIState {
|
||||
uint32_t p_fetch_addr; // which address to look at next
|
||||
|
||||
USBPacket ipacket;
|
||||
uint8_t ibuffer[BUFF_SIZE];
|
||||
QEMUSGList isgl;
|
||||
int isoch_pause;
|
||||
|
||||
uint64_t last_run_ns;
|
||||
@ -1165,60 +1167,58 @@ static int ehci_qh_do_overlay(EHCIQueue *q)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ehci_buffer_rw(EHCIQueue *q, int bytes, int rw)
|
||||
static int ehci_init_transfer(EHCIQueue *q)
|
||||
{
|
||||
int bufpos = 0;
|
||||
int cpage, offset;
|
||||
uint32_t head;
|
||||
uint32_t tail;
|
||||
|
||||
|
||||
if (!bytes) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
cpage = get_field(q->qh.token, QTD_TOKEN_CPAGE);
|
||||
if (cpage > 4) {
|
||||
fprintf(stderr, "cpage out of range (%d)\n", cpage);
|
||||
return USB_RET_PROCERR;
|
||||
}
|
||||
uint32_t cpage, offset, bytes, plen;
|
||||
target_phys_addr_t page;
|
||||
|
||||
cpage = get_field(q->qh.token, QTD_TOKEN_CPAGE);
|
||||
bytes = get_field(q->qh.token, QTD_TOKEN_TBYTES);
|
||||
offset = q->qh.bufptr[0] & ~QTD_BUFPTR_MASK;
|
||||
qemu_sglist_init(&q->sgl, 5);
|
||||
|
||||
do {
|
||||
/* start and end of this page */
|
||||
head = q->qh.bufptr[cpage] & QTD_BUFPTR_MASK;
|
||||
tail = head + ~QTD_BUFPTR_MASK + 1;
|
||||
/* add offset into page */
|
||||
head |= offset;
|
||||
|
||||
if (bytes <= (tail - head)) {
|
||||
tail = head + bytes;
|
||||
while (bytes > 0) {
|
||||
if (cpage > 4) {
|
||||
fprintf(stderr, "cpage out of range (%d)\n", cpage);
|
||||
return USB_RET_PROCERR;
|
||||
}
|
||||
|
||||
trace_usb_ehci_data(rw, cpage, offset, head, tail-head, bufpos);
|
||||
cpu_physical_memory_rw(head, q->buffer + bufpos, tail - head, rw);
|
||||
|
||||
bufpos += (tail - head);
|
||||
offset += (tail - head);
|
||||
bytes -= (tail - head);
|
||||
|
||||
if (bytes > 0) {
|
||||
cpage++;
|
||||
page = q->qh.bufptr[cpage] & QTD_BUFPTR_MASK;
|
||||
page += offset;
|
||||
plen = bytes;
|
||||
if (plen > 4096 - offset) {
|
||||
plen = 4096 - offset;
|
||||
offset = 0;
|
||||
cpage++;
|
||||
}
|
||||
} while (bytes > 0);
|
||||
|
||||
/* save cpage */
|
||||
set_field(&q->qh.token, cpage, QTD_TOKEN_CPAGE);
|
||||
|
||||
/* save offset into cpage */
|
||||
q->qh.bufptr[0] &= QTD_BUFPTR_MASK;
|
||||
q->qh.bufptr[0] |= offset;
|
||||
|
||||
qemu_sglist_add(&q->sgl, page, plen);
|
||||
bytes -= plen;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ehci_finish_transfer(EHCIQueue *q, int status)
|
||||
{
|
||||
uint32_t cpage, offset;
|
||||
|
||||
qemu_sglist_destroy(&q->sgl);
|
||||
|
||||
if (status > 0) {
|
||||
/* update cpage & offset */
|
||||
cpage = get_field(q->qh.token, QTD_TOKEN_CPAGE);
|
||||
offset = q->qh.bufptr[0] & ~QTD_BUFPTR_MASK;
|
||||
|
||||
offset += status;
|
||||
cpage += offset >> QTD_BUFPTR_SH;
|
||||
offset &= ~QTD_BUFPTR_MASK;
|
||||
|
||||
set_field(&q->qh.token, cpage, QTD_TOKEN_CPAGE);
|
||||
q->qh.bufptr[0] &= QTD_BUFPTR_MASK;
|
||||
q->qh.bufptr[0] |= offset;
|
||||
}
|
||||
}
|
||||
|
||||
static void ehci_async_complete_packet(USBPort *port, USBPacket *packet)
|
||||
{
|
||||
EHCIQueue *q;
|
||||
@ -1295,10 +1295,6 @@ err:
|
||||
}
|
||||
|
||||
if (q->tbytes && q->pid == USB_TOKEN_IN) {
|
||||
if (ehci_buffer_rw(q, q->usb_status, 1) != 0) {
|
||||
q->usb_status = USB_RET_PROCERR;
|
||||
return;
|
||||
}
|
||||
q->tbytes -= q->usb_status;
|
||||
} else {
|
||||
q->tbytes = 0;
|
||||
@ -1307,6 +1303,8 @@ err:
|
||||
DPRINTF("updating tbytes to %d\n", q->tbytes);
|
||||
set_field(&q->qh.token, q->tbytes, QTD_TOKEN_TBYTES);
|
||||
}
|
||||
ehci_finish_transfer(q, q->usb_status);
|
||||
usb_packet_unmap(&q->packet);
|
||||
|
||||
q->qh.token ^= QTD_TOKEN_DTOGGLE;
|
||||
q->qh.token &= ~QTD_TOKEN_ACTIVE;
|
||||
@ -1346,8 +1344,7 @@ static int ehci_execute(EHCIQueue *q)
|
||||
default: fprintf(stderr, "bad token\n"); break;
|
||||
}
|
||||
|
||||
if ((q->tbytes && q->pid != USB_TOKEN_IN) &&
|
||||
(ehci_buffer_rw(q, q->tbytes, 0) != 0)) {
|
||||
if (ehci_init_transfer(q) != 0) {
|
||||
return USB_RET_PROCERR;
|
||||
}
|
||||
|
||||
@ -1356,6 +1353,9 @@ static int ehci_execute(EHCIQueue *q)
|
||||
|
||||
ret = USB_RET_NODEV;
|
||||
|
||||
usb_packet_setup(&q->packet, q->pid, devadr, endp);
|
||||
usb_packet_map(&q->packet, &q->sgl);
|
||||
|
||||
// TO-DO: associating device with ehci port
|
||||
for(i = 0; i < NB_PORTS; i++) {
|
||||
port = &q->ehci->ports[i];
|
||||
@ -1367,9 +1367,6 @@ static int ehci_execute(EHCIQueue *q)
|
||||
continue;
|
||||
}
|
||||
|
||||
usb_packet_setup(&q->packet, q->pid, devadr, endp);
|
||||
usb_packet_addbuf(&q->packet, q->buffer, q->tbytes);
|
||||
|
||||
ret = usb_handle_packet(dev, &q->packet);
|
||||
|
||||
DPRINTF("submit: qh %x next %x qtd %x pid %x len %zd "
|
||||
@ -1399,7 +1396,7 @@ static int ehci_process_itd(EHCIState *ehci,
|
||||
USBPort *port;
|
||||
USBDevice *dev;
|
||||
int ret;
|
||||
uint32_t i, j, len, len1, len2, pid, dir, devaddr, endp;
|
||||
uint32_t i, j, len, pid, dir, devaddr, endp;
|
||||
uint32_t pg, off, ptr1, ptr2, max, mult;
|
||||
|
||||
dir =(itd->bufptr[1] & ITD_BUFPTR_DIRECTION);
|
||||
@ -1424,29 +1421,23 @@ static int ehci_process_itd(EHCIState *ehci,
|
||||
return USB_RET_PROCERR;
|
||||
}
|
||||
|
||||
qemu_sglist_init(&ehci->isgl, 2);
|
||||
if (off + len > 4096) {
|
||||
/* transfer crosses page border */
|
||||
len2 = off + len - 4096;
|
||||
len1 = len - len2;
|
||||
uint32_t len2 = off + len - 4096;
|
||||
uint32_t len1 = len - len2;
|
||||
qemu_sglist_add(&ehci->isgl, ptr1 + off, len1);
|
||||
qemu_sglist_add(&ehci->isgl, ptr2, len2);
|
||||
} else {
|
||||
len1 = len;
|
||||
len2 = 0;
|
||||
qemu_sglist_add(&ehci->isgl, ptr1 + off, len);
|
||||
}
|
||||
|
||||
if (!dir) {
|
||||
pid = USB_TOKEN_OUT;
|
||||
trace_usb_ehci_data(0, pg, off, ptr1 + off, len1, 0);
|
||||
cpu_physical_memory_rw(ptr1 + off, &ehci->ibuffer[0], len1, 0);
|
||||
if (len2) {
|
||||
trace_usb_ehci_data(0, pg+1, 0, ptr2, len2, len1);
|
||||
cpu_physical_memory_rw(ptr2, &ehci->ibuffer[len1], len2, 0);
|
||||
}
|
||||
} else {
|
||||
pid = USB_TOKEN_IN;
|
||||
}
|
||||
pid = dir ? USB_TOKEN_IN : USB_TOKEN_OUT;
|
||||
|
||||
usb_packet_setup(&ehci->ipacket, pid, devaddr, endp);
|
||||
usb_packet_map(&ehci->ipacket, &ehci->isgl);
|
||||
|
||||
ret = USB_RET_NODEV;
|
||||
|
||||
for (j = 0; j < NB_PORTS; j++) {
|
||||
port = &ehci->ports[j];
|
||||
dev = port->dev;
|
||||
@ -1455,9 +1446,6 @@ static int ehci_process_itd(EHCIState *ehci,
|
||||
continue;
|
||||
}
|
||||
|
||||
usb_packet_setup(&ehci->ipacket, pid, devaddr, endp);
|
||||
usb_packet_addbuf(&ehci->ipacket, ehci->ibuffer, len);
|
||||
|
||||
ret = usb_handle_packet(dev, &ehci->ipacket);
|
||||
|
||||
if (ret != USB_RET_NODEV) {
|
||||
@ -1465,6 +1453,9 @@ static int ehci_process_itd(EHCIState *ehci,
|
||||
}
|
||||
}
|
||||
|
||||
usb_packet_unmap(&ehci->ipacket);
|
||||
qemu_sglist_destroy(&ehci->isgl);
|
||||
|
||||
#if 0
|
||||
/* In isoch, there is no facility to indicate a NAK so let's
|
||||
* instead just complete a zero-byte transaction. Setting
|
||||
@ -1502,20 +1493,6 @@ static int ehci_process_itd(EHCIState *ehci,
|
||||
set_field(&itd->transact[i], len - ret, ITD_XACT_LENGTH);
|
||||
} else {
|
||||
/* IN */
|
||||
if (len1 > ret) {
|
||||
len1 = ret;
|
||||
}
|
||||
if (len2 > ret - len1) {
|
||||
len2 = ret - len1;
|
||||
}
|
||||
if (len1) {
|
||||
trace_usb_ehci_data(1, pg, off, ptr1 + off, len1, 0);
|
||||
cpu_physical_memory_rw(ptr1 + off, &ehci->ibuffer[0], len1, 1);
|
||||
}
|
||||
if (len2) {
|
||||
trace_usb_ehci_data(1, pg+1, 0, ptr2, len2, len1);
|
||||
cpu_physical_memory_rw(ptr2, &ehci->ibuffer[len1], len2, 1);
|
||||
}
|
||||
set_field(&itd->transact[i], ret, ITD_XACT_LENGTH);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user