mirror of https://gitee.com/openkylin/qemu.git
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 "pci.h"
|
||||||
#include "monitor.h"
|
#include "monitor.h"
|
||||||
#include "trace.h"
|
#include "trace.h"
|
||||||
|
#include "dma.h"
|
||||||
|
|
||||||
#define EHCI_DEBUG 0
|
#define EHCI_DEBUG 0
|
||||||
|
|
||||||
|
@ -269,6 +270,7 @@ typedef struct EHCIqtd {
|
||||||
|
|
||||||
uint32_t bufptr[5]; // Standard buffer pointer
|
uint32_t bufptr[5]; // Standard buffer pointer
|
||||||
#define QTD_BUFPTR_MASK 0xfffff000
|
#define QTD_BUFPTR_MASK 0xfffff000
|
||||||
|
#define QTD_BUFPTR_SH 12
|
||||||
} EHCIqtd;
|
} EHCIqtd;
|
||||||
|
|
||||||
/* EHCI spec version 1.0 Section 3.6
|
/* EHCI spec version 1.0 Section 3.6
|
||||||
|
@ -357,7 +359,7 @@ struct EHCIQueue {
|
||||||
uint32_t qtdaddr; // address QTD read from
|
uint32_t qtdaddr; // address QTD read from
|
||||||
|
|
||||||
USBPacket packet;
|
USBPacket packet;
|
||||||
uint8_t buffer[BUFF_SIZE];
|
QEMUSGList sgl;
|
||||||
int pid;
|
int pid;
|
||||||
uint32_t tbytes;
|
uint32_t tbytes;
|
||||||
enum async_state async;
|
enum async_state async;
|
||||||
|
@ -414,7 +416,7 @@ struct EHCIState {
|
||||||
uint32_t p_fetch_addr; // which address to look at next
|
uint32_t p_fetch_addr; // which address to look at next
|
||||||
|
|
||||||
USBPacket ipacket;
|
USBPacket ipacket;
|
||||||
uint8_t ibuffer[BUFF_SIZE];
|
QEMUSGList isgl;
|
||||||
int isoch_pause;
|
int isoch_pause;
|
||||||
|
|
||||||
uint64_t last_run_ns;
|
uint64_t last_run_ns;
|
||||||
|
@ -1165,60 +1167,58 @@ static int ehci_qh_do_overlay(EHCIQueue *q)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int ehci_buffer_rw(EHCIQueue *q, int bytes, int rw)
|
static int ehci_init_transfer(EHCIQueue *q)
|
||||||
{
|
{
|
||||||
int bufpos = 0;
|
uint32_t cpage, offset, bytes, plen;
|
||||||
int cpage, offset;
|
target_phys_addr_t page;
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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;
|
offset = q->qh.bufptr[0] & ~QTD_BUFPTR_MASK;
|
||||||
|
qemu_sglist_init(&q->sgl, 5);
|
||||||
|
|
||||||
do {
|
while (bytes > 0) {
|
||||||
/* start and end of this page */
|
if (cpage > 4) {
|
||||||
head = q->qh.bufptr[cpage] & QTD_BUFPTR_MASK;
|
fprintf(stderr, "cpage out of range (%d)\n", cpage);
|
||||||
tail = head + ~QTD_BUFPTR_MASK + 1;
|
return USB_RET_PROCERR;
|
||||||
/* add offset into page */
|
|
||||||
head |= offset;
|
|
||||||
|
|
||||||
if (bytes <= (tail - head)) {
|
|
||||||
tail = head + bytes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
trace_usb_ehci_data(rw, cpage, offset, head, tail-head, bufpos);
|
page = q->qh.bufptr[cpage] & QTD_BUFPTR_MASK;
|
||||||
cpu_physical_memory_rw(head, q->buffer + bufpos, tail - head, rw);
|
page += offset;
|
||||||
|
plen = bytes;
|
||||||
bufpos += (tail - head);
|
if (plen > 4096 - offset) {
|
||||||
offset += (tail - head);
|
plen = 4096 - offset;
|
||||||
bytes -= (tail - head);
|
|
||||||
|
|
||||||
if (bytes > 0) {
|
|
||||||
cpage++;
|
|
||||||
offset = 0;
|
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;
|
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)
|
static void ehci_async_complete_packet(USBPort *port, USBPacket *packet)
|
||||||
{
|
{
|
||||||
EHCIQueue *q;
|
EHCIQueue *q;
|
||||||
|
@ -1295,10 +1295,6 @@ err:
|
||||||
}
|
}
|
||||||
|
|
||||||
if (q->tbytes && q->pid == USB_TOKEN_IN) {
|
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;
|
q->tbytes -= q->usb_status;
|
||||||
} else {
|
} else {
|
||||||
q->tbytes = 0;
|
q->tbytes = 0;
|
||||||
|
@ -1307,6 +1303,8 @@ err:
|
||||||
DPRINTF("updating tbytes to %d\n", q->tbytes);
|
DPRINTF("updating tbytes to %d\n", q->tbytes);
|
||||||
set_field(&q->qh.token, q->tbytes, QTD_TOKEN_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_DTOGGLE;
|
||||||
q->qh.token &= ~QTD_TOKEN_ACTIVE;
|
q->qh.token &= ~QTD_TOKEN_ACTIVE;
|
||||||
|
@ -1346,8 +1344,7 @@ static int ehci_execute(EHCIQueue *q)
|
||||||
default: fprintf(stderr, "bad token\n"); break;
|
default: fprintf(stderr, "bad token\n"); break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((q->tbytes && q->pid != USB_TOKEN_IN) &&
|
if (ehci_init_transfer(q) != 0) {
|
||||||
(ehci_buffer_rw(q, q->tbytes, 0) != 0)) {
|
|
||||||
return USB_RET_PROCERR;
|
return USB_RET_PROCERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1356,6 +1353,9 @@ static int ehci_execute(EHCIQueue *q)
|
||||||
|
|
||||||
ret = USB_RET_NODEV;
|
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
|
// TO-DO: associating device with ehci port
|
||||||
for(i = 0; i < NB_PORTS; i++) {
|
for(i = 0; i < NB_PORTS; i++) {
|
||||||
port = &q->ehci->ports[i];
|
port = &q->ehci->ports[i];
|
||||||
|
@ -1367,9 +1367,6 @@ static int ehci_execute(EHCIQueue *q)
|
||||||
continue;
|
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);
|
ret = usb_handle_packet(dev, &q->packet);
|
||||||
|
|
||||||
DPRINTF("submit: qh %x next %x qtd %x pid %x len %zd "
|
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;
|
USBPort *port;
|
||||||
USBDevice *dev;
|
USBDevice *dev;
|
||||||
int ret;
|
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;
|
uint32_t pg, off, ptr1, ptr2, max, mult;
|
||||||
|
|
||||||
dir =(itd->bufptr[1] & ITD_BUFPTR_DIRECTION);
|
dir =(itd->bufptr[1] & ITD_BUFPTR_DIRECTION);
|
||||||
|
@ -1424,29 +1421,23 @@ static int ehci_process_itd(EHCIState *ehci,
|
||||||
return USB_RET_PROCERR;
|
return USB_RET_PROCERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
qemu_sglist_init(&ehci->isgl, 2);
|
||||||
if (off + len > 4096) {
|
if (off + len > 4096) {
|
||||||
/* transfer crosses page border */
|
/* transfer crosses page border */
|
||||||
len2 = off + len - 4096;
|
uint32_t len2 = off + len - 4096;
|
||||||
len1 = len - len2;
|
uint32_t len1 = len - len2;
|
||||||
|
qemu_sglist_add(&ehci->isgl, ptr1 + off, len1);
|
||||||
|
qemu_sglist_add(&ehci->isgl, ptr2, len2);
|
||||||
} else {
|
} else {
|
||||||
len1 = len;
|
qemu_sglist_add(&ehci->isgl, ptr1 + off, len);
|
||||||
len2 = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!dir) {
|
pid = dir ? USB_TOKEN_IN : USB_TOKEN_OUT;
|
||||||
pid = USB_TOKEN_OUT;
|
|
||||||
trace_usb_ehci_data(0, pg, off, ptr1 + off, len1, 0);
|
usb_packet_setup(&ehci->ipacket, pid, devaddr, endp);
|
||||||
cpu_physical_memory_rw(ptr1 + off, &ehci->ibuffer[0], len1, 0);
|
usb_packet_map(&ehci->ipacket, &ehci->isgl);
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = USB_RET_NODEV;
|
ret = USB_RET_NODEV;
|
||||||
|
|
||||||
for (j = 0; j < NB_PORTS; j++) {
|
for (j = 0; j < NB_PORTS; j++) {
|
||||||
port = &ehci->ports[j];
|
port = &ehci->ports[j];
|
||||||
dev = port->dev;
|
dev = port->dev;
|
||||||
|
@ -1455,9 +1446,6 @@ static int ehci_process_itd(EHCIState *ehci,
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
usb_packet_setup(&ehci->ipacket, pid, devaddr, endp);
|
|
||||||
usb_packet_addbuf(&ehci->ipacket, ehci->ibuffer, len);
|
|
||||||
|
|
||||||
ret = usb_handle_packet(dev, &ehci->ipacket);
|
ret = usb_handle_packet(dev, &ehci->ipacket);
|
||||||
|
|
||||||
if (ret != USB_RET_NODEV) {
|
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
|
#if 0
|
||||||
/* In isoch, there is no facility to indicate a NAK so let's
|
/* In isoch, there is no facility to indicate a NAK so let's
|
||||||
* instead just complete a zero-byte transaction. Setting
|
* 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);
|
set_field(&itd->transact[i], len - ret, ITD_XACT_LENGTH);
|
||||||
} else {
|
} else {
|
||||||
/* IN */
|
/* 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);
|
set_field(&itd->transact[i], ret, ITD_XACT_LENGTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue