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