x86, hvm: Improve standard VGA performance
authorKeir Fraser <keir@xensource.com>
Fri, 26 Oct 2007 09:32:20 +0000 (10:32 +0100)
committerKeir Fraser <keir@xensource.com>
Fri, 26 Oct 2007 09:32:20 +0000 (10:32 +0100)
This patch improves the performance of Standard VGA,
the mode used during Windows boot and by the Linux
splash screen.

It does so by buffering all the stdvga programmed output ops
and memory mapped ops (both reads and writes) that are sent to QEMU.

We maintain locally essential VGA state so we can respond
immediately to input and read ops without waiting for
QEMU.  We snoop output and write ops to keep our state
up-to-date.

PIO input ops are satisfied from cached state without
bothering QEMU.

PIO output and mmio ops are passed through to QEMU, including
mmio read ops.  This is necessary because mmio reads
can have side effects.

I have changed the format of the buffered_iopage.
It used to contain 80 elements of type ioreq_t (48 bytes each).
Now it contains 672 elements of type buf_ioreq_t (6 bytes each).
Being able to pipeline 8 times as many ops improves
VGA performance by a factor of 8.

I changed hvm_buffered_io_intercept to use the same
registration and callback mechanism as hvm_portio_intercept
rather than the hacky hardcoding it used before.

In platform.c, I fixed send_timeoffset_req() to sets its
ioreq size to 8 (rather than 4), and its count to 1 (which
was missing).

Signed-off-by: Ben Guthro <bguthro@virtualron.com>
Signed-off-by: Robert Phillips <rphillips@virtualiron.com>
tools/ioemu/target-i386-dm/helper2.c
tools/ioemu/xenstore.c
xen/arch/x86/hvm/Makefile
xen/arch/x86/hvm/hvm.c
xen/arch/x86/hvm/intercept.c
xen/arch/x86/hvm/platform.c
xen/arch/x86/hvm/stdvga.c [new file with mode: 0644]
xen/include/asm-x86/hvm/domain.h
xen/include/asm-x86/hvm/io.h
xen/include/public/hvm/ioreq.h

index 013efb8e26696dbff4a9b7a26a2d911f30ffde28..45eb0f216469dfdeec78263a0783a1980e223e0d 100644 (file)
@@ -478,6 +478,7 @@ void cpu_ioreq_timeoffset(CPUState *env, ioreq_t *req)
 
     time_offset += (ulong)req->data;
 
+    fprintf(logfile, "Time offset set %ld, added offset %ld\n", time_offset, req->data);
     sprintf(b, "%ld", time_offset);
     xenstore_vm_write(domid, "rtc/timeoffset", b);
 }
@@ -538,20 +539,39 @@ void __handle_ioreq(CPUState *env, ioreq_t *req)
 
 void __handle_buffered_iopage(CPUState *env)
 {
-    ioreq_t *req = NULL;
+    buf_ioreq_t *buf_req = NULL;
+    ioreq_t req;
+    int qw = 0;
 
     if (!buffered_io_page)
         return;
 
     while (buffered_io_page->read_pointer !=
            buffered_io_page->write_pointer) {
-        req = &buffered_io_page->ioreq[buffered_io_page->read_pointer %
+        memset(&req, 0, sizeof(req));
+        buf_req = &buffered_io_page->buf_ioreq[buffered_io_page->read_pointer %
                                       IOREQ_BUFFER_SLOT_NUM];
+        req.size = 1UL << buf_req->size;
+        req.count = 1;
+        req.data = buf_req->data;
+        req.state = STATE_IOREQ_READY;
+        req.dir  = buf_req->dir;
+        req.type = buf_req->type;
+        qw = req.size == 8;
+        if (qw) {
+            req.data |= ((uint64_t)buf_req->addr) << 16;
+            buf_req = &buffered_io_page->buf_ioreq[(buffered_io_page->read_pointer+1) %
+                                               IOREQ_BUFFER_SLOT_NUM];
+            req.data |= ((uint64_t)buf_req->data) << 32;
+            req.data |= ((uint64_t)buf_req->addr) << 48;
+        }
+        else
+            req.addr = buf_req->addr;
 
-        __handle_ioreq(env, req);
+        __handle_ioreq(env, &req);
 
         mb();
-        buffered_io_page->read_pointer++;
+        buffered_io_page->read_pointer += qw ? 2 : 1;
     }
 }
 
index 5710466bac3571d2b549ccacf4237cb459275e8c..253f49fe5c90c5721ddd7ae1883ce1f00159408d 100644 (file)
@@ -734,7 +734,7 @@ int xenstore_vm_write(int domid, char *key, char *value)
 
     pasprintf(&buf, "%s/%s", path, key);
     rc = xs_write(xsh, XBT_NULL, buf, value, strlen(value));
-    if (rc) {
+    if (rc == 0) {
         fprintf(logfile, "xs_write(%s, %s): write error\n", buf, key);
         goto out;
     }
index 68bb62fc94553b6abe1de9e9ec943a2620f97a04..bb2e0e42c18a673caa5fa7ab22924a7683ec295c 100644 (file)
@@ -17,3 +17,4 @@ obj-y += vioapic.o
 obj-y += vlapic.o
 obj-y += vpic.o
 obj-y += save.o
+obj-y += stdvga.o
index 84ede3fe82ccc4d9411c42fa9801a736514a4465..05fc461ca79efeb54012f147b2e4060f2441fc92 100644 (file)
@@ -241,6 +241,8 @@ int hvm_domain_initialise(struct domain *d)
     if ( rc != 0 )
         goto fail1;
 
+    stdvga_init(d);
+
     hvm_init_ioreq_page(d, &d->arch.hvm_domain.ioreq);
     hvm_init_ioreq_page(d, &d->arch.hvm_domain.buf_ioreq);
 
@@ -266,6 +268,7 @@ void hvm_domain_relinquish_resources(struct domain *d)
     rtc_deinit(d);
     pmtimer_deinit(d);
     hpet_deinit(d);
+    stdvga_deinit(d);
 }
 
 void hvm_domain_destroy(struct domain *d)
index 7dadea6d4716591fbdb8229d866244948758125a..28eb7c7d671f903fadee9151dafbe58e1a34aa8f 100644 (file)
@@ -45,20 +45,6 @@ static struct hvm_mmio_handler *hvm_mmio_handlers[HVM_MMIO_HANDLER_NR] =
     &vioapic_mmio_handler
 };
 
-struct hvm_buffered_io_range {
-    unsigned long start_addr;
-    unsigned long length;
-};
-
-#define HVM_BUFFERED_IO_RANGE_NR 1
-
-static struct hvm_buffered_io_range buffered_stdvga_range = {0xA0000, 0x20000};
-static struct hvm_buffered_io_range
-*hvm_buffered_io_ranges[HVM_BUFFERED_IO_RANGE_NR] =
-{
-    &buffered_stdvga_range
-};
-
 static inline void hvm_mmio_access(struct vcpu *v,
                                    ioreq_t *p,
                                    hvm_mmio_read_t read_handler,
@@ -170,49 +156,70 @@ int hvm_buffered_io_send(ioreq_t *p)
     struct vcpu *v = current;
     struct hvm_ioreq_page *iorp = &v->domain->arch.hvm_domain.buf_ioreq;
     buffered_iopage_t *pg = iorp->va;
+    buf_ioreq_t bp;
+    /* Timeoffset sends 64b data, but no address.  Use two consecutive slots. */
+    int qw = 0;
+
+    /* Ensure buffered_iopage fits in a page */
+    BUILD_BUG_ON(sizeof(buffered_iopage_t) > PAGE_SIZE);
+
+    /* Return 0 for the cases we can't deal with. */
+    if (p->addr > 0xffffful || p->data_is_ptr || p->df || p->count != 1)
+        return 0;
 
+    bp.type = p->type;
+    bp.dir  = p->dir;
+    switch (p->size) {
+    case 1:
+        bp.size = 0;
+        break;
+    case 2:
+        bp.size = 1;
+        break;
+    case 4:
+        bp.size = 2;
+        break;
+    case 8:
+        bp.size = 3;
+        qw = 1;
+        gdprintk(XENLOG_INFO, "quadword ioreq type:%d data:%ld\n", p->type, p->data);
+        break;
+    default:
+        gdprintk(XENLOG_WARNING, "unexpected ioreq size:%ld\n", p->size);
+        return 0;
+    }
+    
+    bp.data = p->data;
+    bp.addr = qw ? ((p->data >> 16) & 0xfffful) : (p->addr & 0xffffful);
+    
     spin_lock(&iorp->lock);
 
-    if ( (pg->write_pointer - pg->read_pointer) == IOREQ_BUFFER_SLOT_NUM )
+    if ( (pg->write_pointer - pg->read_pointer) >= IOREQ_BUFFER_SLOT_NUM - (qw ? 1 : 0))
     {
         /* The queue is full: send the iopacket through the normal path. */
         spin_unlock(&iorp->lock);
         return 0;
     }
-
-    memcpy(&pg->ioreq[pg->write_pointer % IOREQ_BUFFER_SLOT_NUM],
-           p, sizeof(ioreq_t));
+    
+    memcpy(&pg->buf_ioreq[pg->write_pointer % IOREQ_BUFFER_SLOT_NUM],
+           &bp, sizeof(bp));
+    
+    if (qw) {
+        bp.data = p->data >> 32;
+        bp.addr = (p->data >> 48) & 0xfffful;
+        memcpy(&pg->buf_ioreq[(pg->write_pointer+1) % IOREQ_BUFFER_SLOT_NUM],
+               &bp, sizeof(bp));
+    }
 
     /* Make the ioreq_t visible /before/ write_pointer. */
     wmb();
-    pg->write_pointer++;
-
+    pg->write_pointer += qw ? 2 : 1;
+    
     spin_unlock(&iorp->lock);
-
+    
     return 1;
 }
 
-int hvm_buffered_io_intercept(ioreq_t *p)
-{
-    int i;
-
-    /* ignore READ ioreq_t! */
-    if ( p->dir == IOREQ_READ )
-        return 0;
-
-    for ( i = 0; i < HVM_BUFFERED_IO_RANGE_NR; i++ ) {
-        if ( p->addr >= hvm_buffered_io_ranges[i]->start_addr &&
-             p->addr + p->size - 1 < hvm_buffered_io_ranges[i]->start_addr +
-                                     hvm_buffered_io_ranges[i]->length )
-            break;
-    }
-
-    if ( i == HVM_BUFFERED_IO_RANGE_NR )
-        return 0;
-
-    return hvm_buffered_io_send(p);
-}
-
 int hvm_mmio_intercept(ioreq_t *p)
 {
     struct vcpu *v = current;
@@ -253,7 +260,7 @@ int hvm_io_intercept(ioreq_t *p, int type)
         addr = handler->hdl_list[i].addr;
         size = handler->hdl_list[i].size;
         if (p->addr >= addr &&
-            p->addr <  addr + size)
+            p->addr + p->size <=  addr + size)
             return handler->hdl_list[i].action(p);
     }
     return 0;
index 0b622d99ace085e2d653fa67dcc998d414baf64f..66228edc59e98a66c1a98e911d6dad84508ccb55 100644 (file)
@@ -944,7 +944,8 @@ void send_timeoffset_req(unsigned long timeoff)
     memset(p, 0, sizeof(*p));
 
     p->type = IOREQ_TYPE_TIMEOFFSET;
-    p->size = 4;
+    p->size = 8;
+    p->count = 1;
     p->dir = IOREQ_WRITE;
     p->data = timeoff;
 
diff --git a/xen/arch/x86/hvm/stdvga.c b/xen/arch/x86/hvm/stdvga.c
new file mode 100644 (file)
index 0000000..d0cc4de
--- /dev/null
@@ -0,0 +1,712 @@
+/*
+ *  Copyright (c) 2003-2007, Virtual Iron Software, Inc.
+ *
+ *  Portions have been modified by Virtual Iron Software, Inc.
+ *  (c) 2007. This file and the modifications can be redistributed and/or
+ *  modified under the terms and conditions of the GNU General Public
+ *  License, version 2.1 and not any later version of the GPL, as published
+ *  by the Free Software Foundation. 
+ *
+ *
+ *
+ *  This improves the performance of Standard VGA,
+ *  the mode used during Windows boot and by the Linux
+ *  splash screen.
+ *
+ *  It does so by buffering all the stdvga programmed output ops
+ *  and memory mapped ops (both reads and writes) that are sent to QEMU.
+ *
+ *  We maintain locally essential VGA state so we can respond
+ *  immediately to input and read ops without waiting for
+ *  QEMU.  We snoop output and write ops to keep our state
+ *  up-to-date.
+ *
+ *  PIO input ops are satisfied from cached state without
+ *  bothering QEMU.
+ *
+    PIO output and mmio ops are passed through to QEMU, including
+ *  mmio read ops.  This is necessary because mmio reads
+ *  can have side effects.
+ */
+
+#include <xen/config.h>
+#include <xen/types.h>
+#include <xen/sched.h>
+#include <asm/hvm/support.h>
+
+#define vram_b(_s, _a) (((uint8_t*) (_s)->vram_ptr[((_a)>>12)&0x3f])[(_a)&0xfff])
+#define vram_w(_s, _a) (((uint16_t*)(_s)->vram_ptr[((_a)>>11)&0x3f])[(_a)&0x7ff])
+#define vram_l(_s, _a) (((uint32_t*)(_s)->vram_ptr[((_a)>>10)&0x3f])[(_a)&0x3ff])
+
+#ifdef STDVGA_STATS
+#define UPDATE_STATS(x) x
+#else
+#define UPDATE_STATS(x)
+#endif
+
+#define PAT(x) (x)
+static const uint32_t mask16[16] = {
+    PAT(0x00000000),
+    PAT(0x000000ff),
+    PAT(0x0000ff00),
+    PAT(0x0000ffff),
+    PAT(0x00ff0000),
+    PAT(0x00ff00ff),
+    PAT(0x00ffff00),
+    PAT(0x00ffffff),
+    PAT(0xff000000),
+    PAT(0xff0000ff),
+    PAT(0xff00ff00),
+    PAT(0xff00ffff),
+    PAT(0xffff0000),
+    PAT(0xffff00ff),
+    PAT(0xffffff00),
+    PAT(0xffffffff),
+};
+
+/* force some bits to zero */
+const uint8_t sr_mask[8] = {
+    (uint8_t)~0xfc,
+    (uint8_t)~0xc2,
+    (uint8_t)~0xf0,
+    (uint8_t)~0xc0,
+    (uint8_t)~0xf1,
+    (uint8_t)~0xff,
+    (uint8_t)~0xff,
+    (uint8_t)~0x00,
+};
+
+const uint8_t gr_mask[16] = {
+    (uint8_t)~0xf0, /* 0x00 */
+    (uint8_t)~0xf0, /* 0x01 */
+    (uint8_t)~0xf0, /* 0x02 */
+    (uint8_t)~0xe0, /* 0x03 */
+    (uint8_t)~0xfc, /* 0x04 */
+    (uint8_t)~0x84, /* 0x05 */
+    (uint8_t)~0xf0, /* 0x06 */
+    (uint8_t)~0xf0, /* 0x07 */
+    (uint8_t)~0x00, /* 0x08 */
+};
+
+static uint64_t stdvga_inb(uint64_t addr)
+{
+    struct hvm_hw_stdvga *s = &current->domain->arch.hvm_domain.stdvga;
+    uint8_t val = 0;
+    switch (addr) {
+    case 0x3c4:                 /* sequencer address register */
+        val = s->sr_index;
+        break;
+
+    case 0x3c5:                 /* sequencer data register */
+        if (s->sr_index < sizeof(s->sr))
+            val = s->sr[s->sr_index];
+        break;
+
+    case 0x3ce:                 /* graphics address register */
+        val = s->gr_index;
+        break;
+
+    case 0x3cf:                 /* graphics data register */
+        val = s->gr[s->gr_index];
+        break;
+
+    default:
+        gdprintk(XENLOG_WARNING, "unexpected io addr 0x%04x\n", (int)addr);
+    }
+    return val;
+}
+
+static uint64_t stdvga_in(ioreq_t *p)
+{
+    /* Satisfy reads from sequence and graphics registers using local values */
+    uint64_t data = 0;
+    switch (p->size) {
+    case 1:
+        data = stdvga_inb(p->addr);
+        break;
+
+    case 2:
+        data = stdvga_inb(p->addr);
+        data |= stdvga_inb(p->addr + 1) << 8;
+        break;
+
+    case 4:
+        data = stdvga_inb(p->addr);
+        data |= stdvga_inb(p->addr + 1) << 8;
+        data |= stdvga_inb(p->addr + 2) << 16;
+        data |= stdvga_inb(p->addr + 3) << 24;
+        break;
+
+    case 8:
+        data = stdvga_inb(p->addr);
+        data |= stdvga_inb(p->addr + 1) << 8;
+        data |= stdvga_inb(p->addr + 2) << 16;
+        data |= stdvga_inb(p->addr + 3) << 24;
+        data |= stdvga_inb(p->addr + 4) << 32;
+        data |= stdvga_inb(p->addr + 5) << 40;
+        data |= stdvga_inb(p->addr + 6) << 48;
+        data |= stdvga_inb(p->addr + 7) << 56;
+        break;
+
+    default:
+        gdprintk(XENLOG_WARNING, "invalid io size:%d\n", (int)p->size);
+    }
+    return data;
+}
+
+static void stdvga_outb(uint64_t addr, uint8_t val)
+{
+    /* Bookkeep (via snooping) the sequencer and graphics registers */
+
+    struct hvm_hw_stdvga *s = &current->domain->arch.hvm_domain.stdvga;
+    int prev_stdvga = s->stdvga;
+
+    switch (addr) {
+    case 0x3c4:                 /* sequencer address register */
+        s->sr_index = val;
+        break;
+
+    case 0x3c5:                 /* sequencer data register */
+        switch (s->sr_index) {
+        case 0x00 ... 0x05:
+        case 0x07:
+            s->sr[s->sr_index] = val & sr_mask[s->sr_index];
+            break;
+        case 0x06:
+            s->sr[s->sr_index] = ((val & 0x17) == 0x12) ? 0x12 : 0x0f;
+            break;
+        default:
+            if (s->sr_index < sizeof(s->sr))
+                s->sr[s->sr_index] = val;
+            break;
+        }
+        break;
+
+    case 0x3ce:                 /* graphics address register */
+        s->gr_index = val;
+        break;
+
+    case 0x3cf:                 /* graphics data register */
+        if (s->gr_index < sizeof(gr_mask)) {
+            s->gr[s->gr_index] = val & gr_mask[s->gr_index];
+        }
+        else if (s->gr_index == 0xff && s->vram_ptr != NULL) {
+            uint32_t addr;
+            for (addr = 0xa0000; addr < 0xa4000; addr += 2)
+                vram_w(s, addr) = (val << 8) | s->gr[0xfe];
+        }
+        else
+            s->gr[s->gr_index] = val;
+        break;
+    }
+
+    /* When in standard vga mode, emulate here all writes to the vram buffer
+     * so we can immediately satisfy reads without waiting for qemu. */
+    s->stdvga =
+        s->sr[0x07] == 0 &&          /* standard vga mode */
+        s->gr[6] == 0x05;            /* misc graphics register w/ MemoryMapSelect=1  0xa0000-0xaffff (64K region) and AlphaDis=1 */
+
+    if (!prev_stdvga && s->stdvga) {
+        s->cache = 1;       /* (re)start caching video buffer */
+        gdprintk(XENLOG_INFO, "entering stdvga and caching modes\n");
+    }
+    else
+    if (prev_stdvga && !s->stdvga)
+        gdprintk(XENLOG_INFO, "leaving  stdvga\n");
+}
+
+static void stdvga_outv(uint64_t addr, uint64_t data, uint32_t size)
+{
+    switch (size) {
+    case 1:
+        stdvga_outb(addr, data);
+        break;
+
+    case 2:
+        stdvga_outb(addr+0, data >>  0);
+        stdvga_outb(addr+1, data >>  8);
+        break;
+
+    case 4:
+        stdvga_outb(addr+0, data >>  0);
+        stdvga_outb(addr+1, data >>  8);
+        stdvga_outb(addr+2, data >> 16);
+        stdvga_outb(addr+3, data >> 24);
+        break;
+
+    case 8:
+        stdvga_outb(addr+0, data >>  0);
+        stdvga_outb(addr+1, data >>  8);
+        stdvga_outb(addr+2, data >> 16);
+        stdvga_outb(addr+3, data >> 24);
+        stdvga_outb(addr+4, data >> 32);
+        stdvga_outb(addr+5, data >> 40);
+        stdvga_outb(addr+6, data >> 48);
+        stdvga_outb(addr+7, data >> 56);
+        break;
+
+    default:
+        gdprintk(XENLOG_WARNING, "invalid io size:%d\n", size);
+    }
+}
+
+static void stdvga_out(ioreq_t *p)
+{
+    if (p->data_is_ptr) {
+        int i, sign = p->df ? -1 : 1;
+        uint64_t addr = p->addr, data = p->data, tmp;
+        for (i = 0; i < p->count; i++) {
+            hvm_copy_from_guest_phys(&tmp, data, p->size);
+            stdvga_outv(addr, tmp, p->size);
+            data += sign * p->size;
+            addr += sign * p->size;
+        }
+    }
+    else
+        stdvga_outv(p->addr, p->data, p->size);
+}
+
+int stdvga_intercept_pio(ioreq_t *p)
+{
+    struct hvm_hw_stdvga *s = &current->domain->arch.hvm_domain.stdvga;
+    int buf = 0;
+
+    if (p->size > 8) {
+        gdprintk(XENLOG_WARNING, "stdvga bad access size %d\n", (int)p->size);
+        return 0;
+    }
+
+    spin_lock(&s->lock);
+    if ( p->dir == IOREQ_READ ) {
+        if (p->size != 1)
+            gdprintk(XENLOG_WARNING, "unexpected io size:%d\n", (int)p->size);
+        if (!(p->addr == 0x3c5 && s->sr_index >= sizeof(sr_mask)) &&
+            !(p->addr == 0x3cf && s->gr_index >= sizeof(gr_mask)))
+        {
+            p->data = stdvga_in(p);
+            buf = 1;
+        }
+    }
+    else {
+        stdvga_out(p);
+        buf = 1;
+    }
+
+    if (buf && hvm_buffered_io_send(p)) {
+        UPDATE_STATS(s->stats.nr_pio_buffered_wr++);
+        spin_unlock(&s->lock);
+        return 1;
+    }
+    else {
+        UPDATE_STATS(s->stats.nr_pio_unbuffered_wr++);
+        spin_unlock(&s->lock);
+        return 0;
+    }
+}
+
+#define GET_PLANE(data, p) (((data) >> ((p) * 8)) & 0xff)
+
+static uint8_t stdvga_mem_readb(uint64_t addr)
+{
+    struct hvm_hw_stdvga *s = &current->domain->arch.hvm_domain.stdvga;
+    int plane;
+    uint32_t ret;
+
+    addr &= 0x1ffff;
+    if (addr >= 0x10000)
+        return 0xff;
+
+    if (s->sr[4] & 0x08) {
+        /* chain 4 mode : simplest access */
+        ret = vram_b(s, addr);
+    } else if (s->gr[5] & 0x10) {
+        /* odd/even mode (aka text mode mapping) */
+        plane = (s->gr[4] & 2) | (addr & 1);
+        ret = vram_b(s, ((addr & ~1) << 1) | plane);
+    } else {
+        /* standard VGA latched access */
+        s->latch = vram_l(s, addr);
+
+        if (!(s->gr[5] & 0x08)) {
+            /* read mode 0 */
+            plane = s->gr[4];
+            ret = GET_PLANE(s->latch, plane);
+        } else {
+            /* read mode 1 */
+            ret = (s->latch ^ mask16[s->gr[2]]) & mask16[s->gr[7]];
+            ret |= ret >> 16;
+            ret |= ret >> 8;
+            ret = (~ret) & 0xff;
+        }
+    }
+    return ret;
+}
+
+static uint32_t stdvga_mem_read(uint32_t addr, uint32_t size)
+{
+    uint32_t data = 0;
+
+    switch (size) {
+    case 1:
+        data = stdvga_mem_readb(addr);
+        break;
+
+    case 2:
+        data = stdvga_mem_readb(addr);
+        data |= stdvga_mem_readb(addr + 1) << 8;
+        break;
+
+    case 4:
+        data = stdvga_mem_readb(addr);
+        data |= stdvga_mem_readb(addr + 1) << 8;
+        data |= stdvga_mem_readb(addr + 2) << 16;
+        data |= stdvga_mem_readb(addr + 3) << 24;
+        break;
+
+    default:
+        gdprintk(XENLOG_WARNING, "invalid io size:%d\n", size);
+    }
+    return data;
+}
+
+static void stdvga_mem_writeb(uint64_t addr, uint32_t val)
+{
+    struct hvm_hw_stdvga *s = &current->domain->arch.hvm_domain.stdvga;
+    int plane, write_mode, b, func_select, mask;
+    uint32_t write_mask, bit_mask, set_mask;
+
+    addr &= 0x1ffff;
+    if (addr >= 0x10000)
+        return;
+
+    if (s->sr[4] & 0x08) {
+        /* chain 4 mode : simplest access */
+        plane = addr & 3;
+        mask = (1 << plane);
+        if (s->sr[2] & mask) {
+            vram_b(s, addr) = val;
+        }
+    } else if (s->gr[5] & 0x10) {
+        /* odd/even mode (aka text mode mapping) */
+        plane = (s->gr[4] & 2) | (addr & 1);
+        mask = (1 << plane);
+        if (s->sr[2] & mask) {
+            addr = ((addr & ~1) << 1) | plane;
+            vram_b(s, addr) = val;
+        }
+    } else {
+        write_mode = s->gr[5] & 3;
+        switch(write_mode) {
+        default:
+        case 0:
+            /* rotate */
+            b = s->gr[3] & 7;
+            val = ((val >> b) | (val << (8 - b))) & 0xff;
+            val |= val << 8;
+            val |= val << 16;
+
+            /* apply set/reset mask */
+            set_mask = mask16[s->gr[1]];
+            val = (val & ~set_mask) | (mask16[s->gr[0]] & set_mask);
+            bit_mask = s->gr[8];
+            break;
+        case 1:
+            val = s->latch;
+            goto do_write;
+        case 2:
+            val = mask16[val & 0x0f];
+            bit_mask = s->gr[8];
+            break;
+        case 3:
+            /* rotate */
+            b = s->gr[3] & 7;
+            val = (val >> b) | (val << (8 - b));
+
+            bit_mask = s->gr[8] & val;
+            val = mask16[s->gr[0]];
+            break;
+        }
+
+        /* apply logical operation */
+        func_select = s->gr[3] >> 3;
+        switch(func_select) {
+        case 0:
+        default:
+            /* nothing to do */
+            break;
+        case 1:
+            /* and */
+            val &= s->latch;
+            break;
+        case 2:
+            /* or */
+            val |= s->latch;
+            break;
+        case 3:
+            /* xor */
+            val ^= s->latch;
+            break;
+        }
+
+        /* apply bit mask */
+        bit_mask |= bit_mask << 8;
+        bit_mask |= bit_mask << 16;
+        val = (val & bit_mask) | (s->latch & ~bit_mask);
+
+    do_write:
+        /* mask data according to sr[2] */
+        mask = s->sr[2];
+        write_mask = mask16[mask];
+        vram_l(s, addr) =
+            (vram_l(s, addr) & ~write_mask) |
+            (val & write_mask);
+    }
+}
+
+static void stdvga_mem_write(uint32_t addr, uint32_t data, uint32_t size)
+{
+    /* Intercept mmio write */
+    switch (size) {
+    case 1:
+        stdvga_mem_writeb(addr, (data >>  0) & 0xff);
+        break;
+
+    case 2:
+        stdvga_mem_writeb(addr+0, (data >>  0) & 0xff);
+        stdvga_mem_writeb(addr+1, (data >>  8) & 0xff);
+        break;
+
+    case 4:
+        stdvga_mem_writeb(addr+0, (data >>  0) & 0xff);
+        stdvga_mem_writeb(addr+1, (data >>  8) & 0xff);
+        stdvga_mem_writeb(addr+2, (data >> 16) & 0xff);
+        stdvga_mem_writeb(addr+3, (data >> 24) & 0xff);
+        break;
+
+    default:
+        gdprintk(XENLOG_WARNING, "invalid io size:%d\n", size);
+    }
+}
+
+static uint32_t read_data;
+
+static int mmio_move(struct hvm_hw_stdvga *s, ioreq_t *p)
+{
+    int i;
+    int sign = p->df ? -1 : 1;
+
+    if (p->data_is_ptr) {
+        if (p->dir == IOREQ_READ ) {
+            uint32_t addr = p->addr, data = p->data, tmp;
+            for (i = 0; i < p->count; i++) {
+                tmp = stdvga_mem_read(addr, p->size);
+                hvm_copy_to_guest_phys(data, &tmp, p->size);
+                data += sign * p->size;
+                addr += sign * p->size;
+            }
+        }
+        else {
+            uint32_t addr = p->addr, data = p->data, tmp;
+            for (i = 0; i < p->count; i++) {
+                hvm_copy_from_guest_phys(&tmp, data, p->size);
+                stdvga_mem_write(addr, tmp, p->size);
+                data += sign * p->size;
+                addr += sign * p->size;
+            }
+        }
+    }
+    else {
+        if (p->dir == IOREQ_READ ) {
+            uint32_t addr = p->addr;
+            for (i = 0; i < p->count; i++) {
+                p->data = stdvga_mem_read(addr, p->size);
+                addr += sign * p->size;
+            }
+        }
+        else {
+            uint32_t addr = p->addr;
+            for (i = 0; i < p->count; i++) {
+                stdvga_mem_write(addr, p->data, p->size);
+                addr += sign * p->size;
+            }
+        }
+    }
+
+    read_data = p->data;
+    return 1;
+}
+
+static uint32_t op_and(uint32_t a, uint32_t b) { return a & b; }
+static uint32_t op_or (uint32_t a, uint32_t b) { return a | b; }
+static uint32_t op_xor(uint32_t a, uint32_t b) { return a ^ b; }
+static uint32_t op_add(uint32_t a, uint32_t b) { return a + b; }
+static uint32_t op_sub(uint32_t a, uint32_t b) { return a - b; }
+static uint32_t (*op_array[])(uint32_t, uint32_t) = {
+    [IOREQ_TYPE_AND] = op_and,
+    [IOREQ_TYPE_OR ] = op_or,
+    [IOREQ_TYPE_XOR] = op_xor,
+    [IOREQ_TYPE_ADD] = op_add,
+    [IOREQ_TYPE_SUB] = op_sub
+};
+
+static int mmio_op(struct hvm_hw_stdvga *s, ioreq_t *p)
+{
+    uint32_t orig, mod = 0;
+    orig = stdvga_mem_read(p->addr, p->size);
+    if (p->dir == IOREQ_WRITE) {
+        mod = (op_array[p->type])(orig, p->data);
+        stdvga_mem_write(p->addr, mod, p->size);
+    }
+    // p->data = orig; // Can't modify p->data yet.  QEMU still needs to use it.  So return zero below.
+    return 0; /* Don't try to buffer these operations */
+}
+
+int stdvga_intercept_mmio(ioreq_t *p)
+{
+    struct domain *d = current->domain;
+    struct hvm_hw_stdvga *s = &d->arch.hvm_domain.stdvga;
+    int buf = 0;
+
+    if (p->size > 8) {
+        gdprintk(XENLOG_WARNING, "invalid mmio size %d\n", (int)p->size);
+        return 0;
+    }
+
+    spin_lock(&s->lock);
+
+    if (s->stdvga && s->cache) {
+        switch (p->type) {
+        case IOREQ_TYPE_COPY:
+            buf = mmio_move(s, p);
+            break;
+        case IOREQ_TYPE_AND:
+        case IOREQ_TYPE_OR:
+        case IOREQ_TYPE_XOR:
+        case IOREQ_TYPE_ADD:
+        case IOREQ_TYPE_SUB:
+            buf = mmio_op(s, p);
+            break;
+        default:
+            gdprintk(XENLOG_ERR, "unsupported mmio request type:%d "
+                     "addr:0x%04x data:0x%04x size:%d count:%d state:%d isptr:%d dir:%d df:%d\n",
+                     p->type,
+                     (int)p->addr, (int)p->data, (int)p->size, (int)p->count, p->state,
+                     p->data_is_ptr, p->dir, p->df);
+            s->cache = 0;
+        }
+    }
+    if (buf && hvm_buffered_io_send(p)) {
+        UPDATE_STATS(p->dir == IOREQ_READ ? s->stats.nr_mmio_buffered_rd++ : s->stats.nr_mmio_buffered_wr++);
+        spin_unlock(&s->lock);
+        return 1;
+    }
+    else {
+        UPDATE_STATS(p->dir == IOREQ_READ ? s->stats.nr_mmio_unbuffered_rd++ : s->stats.nr_mmio_unbuffered_wr++);
+        spin_unlock(&s->lock);
+        return 0;
+    }
+}
+
+void stdvga_init(struct domain *d)
+{
+    int i;
+    struct hvm_hw_stdvga *s = &d->arch.hvm_domain.stdvga;
+    memset(s, 0, sizeof(*s));
+    spin_lock_init(&s->lock);
+    
+    for (i = 0; i != ARRAY_SIZE(s->vram_ptr); i++) {
+        struct page_info *vram_page;
+        vram_page = alloc_domheap_page(NULL);
+        if (!vram_page)
+            break;
+        s->vram_ptr[i] = page_to_virt(vram_page);
+        memset(s->vram_ptr[i], 0, PAGE_SIZE);
+    }
+    if (i == ARRAY_SIZE(s->vram_ptr)) {
+        register_portio_handler(d, 0x3c4, 2, stdvga_intercept_pio); /* sequencer registers */
+        register_portio_handler(d, 0x3ce, 2, stdvga_intercept_pio); /* graphics registers */
+        register_buffered_io_handler(d, 0xa0000, 0x10000, stdvga_intercept_mmio); /* mmio */
+    }
+}
+
+void stdvga_deinit(struct domain *d)
+{
+    struct hvm_hw_stdvga *s = &d->arch.hvm_domain.stdvga;
+    int i;
+    for (i = 0; i != ARRAY_SIZE(s->vram_ptr); i++) {
+        struct page_info *vram_page;
+        if (s->vram_ptr[i] == NULL)
+            continue;
+        vram_page = virt_to_page(s->vram_ptr[i]);
+        free_domheap_page(vram_page);
+        s->vram_ptr[i] = NULL;
+    }
+}
+
+#ifdef STDVGA_STATS
+static void stdvga_stats_dump(unsigned char key)
+{
+    struct domain *d;
+
+    printk("%s: key '%c' pressed\n", __FUNCTION__, key);
+
+    rcu_read_lock(&domlist_read_lock);
+
+    for_each_domain ( d )
+    {
+        struct hvm_hw_stdvga *s;
+        int i;
+
+        if ( !is_hvm_domain(d) )
+            continue;
+
+        s = &d->arch.hvm_domain.stdvga;
+        spin_lock(&s->lock);
+        printk("\n>>> Domain %d <<<\n", d->domain_id);
+        printk("    modes: stdvga:%d caching:%d\n", s->stdvga, s->cache);
+        printk("                       %8s %8s\n", "read", "write");
+        printk("    nr_mmio_buffered:  %8u %8u\n", s->stats.nr_mmio_buffered_rd, s->stats.nr_mmio_buffered_wr);
+        printk("    nr_mmio_unbuffered:%8u %8u\n", s->stats.nr_mmio_unbuffered_rd, s->stats.nr_mmio_unbuffered_wr);
+        printk("    nr_pio_buffered:   %8u %8u\n", s->stats.nr_pio_buffered_rd, s->stats.nr_pio_buffered_wr);
+        printk("    nr_pio_unbuffered: %8u %8u\n", s->stats.nr_pio_unbuffered_rd, s->stats.nr_pio_unbuffered_wr);
+
+        for (i = 0; i != sizeof(s->sr); i++) {
+            if (i % 8 == 0)
+                printk("    sr[0x%02x] ", i);
+            printk("%02x ", s->sr[i]);
+            if (i % 8 == 7)
+                printk("\n");
+        }
+        if (i % 8 != 7)
+            printk("\n");
+
+        for (i = 0; i != sizeof(s->gr); i++) {
+            if (i % 8 == 0)
+                printk("    gr[0x%02x] ", i);
+            printk("%02x ", s->gr[i]);
+            if (i % 8 == 7)
+                printk("\n");
+        }
+        if (i % 8 != 7)
+            printk("\n");
+
+        memset(&s->stats, 0, sizeof(s->stats));
+
+        spin_unlock(&s->lock);
+    }
+
+    rcu_read_unlock(&domlist_read_lock);
+}
+
+#include <xen/keyhandler.h>
+
+static int __init setup_stdvga_stats_dump(void)
+{
+    register_keyhandler('<', stdvga_stats_dump, "dump stdvga stats");
+    return 0;
+}
+
+__initcall(setup_stdvga_stats_dump);
+
+#endif
+
index 5dfe14d863c0677ea3557aa0211bcfe7b108448c..269f33a7bd7cd49f91278ee257a45bf7f499b664 100644 (file)
@@ -51,6 +51,7 @@ struct hvm_domain {
     struct hvm_irq         irq;
     struct hvm_hw_vpic     vpic[2]; /* 0=master; 1=slave */
     struct hvm_vioapic    *vioapic;
+    struct hvm_hw_stdvga   stdvga;
 
     /* hvm_print_line() logging. */
     char                   pbuf[80];
index 2537ac3abd7c0a2d3d6bc904b7728b81d3ddd74b..feec0a23580c7ce3cb924b0dc2d702e29a765ca9 100644 (file)
@@ -80,10 +80,11 @@ struct hvm_io_op {
     struct cpu_user_regs    io_context; /* current context */
 };
 
-#define MAX_IO_HANDLER              9
+#define MAX_IO_HANDLER             12
 
 #define HVM_PORTIO                  0
 #define HVM_MMIO                    1
+#define HVM_BUFFERED_IO             2
 
 typedef int (*intercept_action_t)(ioreq_t *);
 typedef unsigned long (*hvm_mmio_read_t)(struct vcpu *v,
@@ -126,9 +127,13 @@ static inline int hvm_portio_intercept(ioreq_t *p)
     return hvm_io_intercept(p, HVM_PORTIO);
 }
 
+static inline int hvm_buffered_io_intercept(ioreq_t *p)
+{
+    return hvm_io_intercept(p, HVM_BUFFERED_IO);
+}
+
 extern int hvm_mmio_intercept(ioreq_t *p);
 extern int hvm_buffered_io_send(ioreq_t *p);
-extern int hvm_buffered_io_intercept(ioreq_t *p);
 
 static inline int register_portio_handler(
     struct domain *d, unsigned long addr,
@@ -137,6 +142,13 @@ static inline int register_portio_handler(
     return register_io_handler(d, addr, size, action, HVM_PORTIO);
 }
 
+static inline int register_buffered_io_handler(
+    struct domain *d, unsigned long addr,
+    unsigned long size, intercept_action_t action)
+{
+    return register_io_handler(d, addr, size, action, HVM_BUFFERED_IO);
+}
+
 #if defined(__i386__) || defined(__x86_64__)
 static inline int irq_masked(unsigned long eflags)
 {
@@ -154,5 +166,38 @@ extern void hvm_io_assist(void);
 extern void hvm_dpci_eoi(struct domain *d, unsigned int guest_irq,
                          union vioapic_redir_entry *ent);
 
+
+#undef  STDVGA_STATS /* #define to enable stdvga statistics */
+#undef  STDVGA_CHECK /* debug: ensure cached value matches qemu value */
+
+struct hvm_hw_stdvga {
+    uint8_t sr_index;
+    uint8_t sr[0x18];
+    uint8_t gr_index;
+    uint8_t gr[256];
+    uint32_t latch;
+    int stdvga;
+    int cache;
+    uint8_t *vram_ptr[64];  /* shadow of 0xa0000-0xaffff */
+    spinlock_t lock;
+    
+#ifdef STDVGA_STATS
+    struct {
+        uint32_t nr_mmio_buffered_rd;
+        uint32_t nr_mmio_buffered_wr;
+        uint32_t nr_mmio_unbuffered_rd;
+        uint32_t nr_mmio_unbuffered_wr;
+        uint32_t nr_pio_buffered_rd;
+        uint32_t nr_pio_buffered_wr;
+        uint32_t nr_pio_unbuffered_rd;
+        uint32_t nr_pio_unbuffered_wr;
+    } stats;
+#endif
+};
+
+extern void stdvga_init(struct domain *d);
+extern void stdvga_deinit(struct domain *d);
+extern void stdvga_check_cached_value(ioreq_t *p);
+
 #endif /* __ASM_X86_HVM_IO_H__ */
 
index 91a4cef924e3112c472a71aa1ed1fb54e992d736..6bad4f5a52134867e5b9208ca30d0bee0b01ccf3 100644 (file)
@@ -77,14 +77,27 @@ struct shared_iopage {
 };
 typedef struct shared_iopage shared_iopage_t;
 
-#define IOREQ_BUFFER_SLOT_NUM     80
+#pragma pack(push,2)
+
+struct buf_ioreq {
+    uint8_t  type;   /*  I/O type                    */
+    uint8_t  dir:1;  /*  1=read, 0=write             */
+    uint8_t  size:2; /*  0=>1, 1=>2, 3=>8. If 8 then use two contig buf_ioreqs */
+    uint32_t addr:20; /*  physical address or high-order data */
+    uint16_t data;   /*  (low order) data            */
+};
+typedef struct buf_ioreq buf_ioreq_t;
+
+#define IOREQ_BUFFER_SLOT_NUM     672
 struct buffered_iopage {
-    unsigned int    read_pointer;
-    unsigned int    write_pointer;
-    ioreq_t         ioreq[IOREQ_BUFFER_SLOT_NUM];
+    volatile unsigned int read_pointer;
+    volatile unsigned int write_pointer;
+    buf_ioreq_t buf_ioreq[IOREQ_BUFFER_SLOT_NUM];
 }; /* NB. Size of this structure must be no greater than one page. */
 typedef struct buffered_iopage buffered_iopage_t;
 
+#pragma pack(pop)
+
 #if defined(__ia64__)
 struct pio_buffer {
     uint32_t page_offset;