e8c858944e
* Hyper-V PV TLB flush (Vitaly) * git archive detection (Daniel) * host serial passthrough fix (David) * NPT support for SVM emulation (Jan) * x86 "info mem" and "info tlb" fix (Doug) -----BEGIN PGP SIGNATURE----- Version: GnuPG v2.0.22 (GNU/Linux) iQEcBAABAgAGBQJbOkI9AAoJEL/70l94x66DaA0IAIzJD+3hUdwDCqitlW65x/yX D+KAoX4Ytpz7+QOtcXC7BBUW3JwvHTS5sfuvaAqKWnqEXSDrQs4/gG2iEB1UJ3Ko hC2LHGKygdcD9k3vuQ2q2USOu08jEUYRvvjgHmD6lsyaAQ+cb2heAYz/SxQqbkkt qun6TFaWuTGBQF1qy0xjJitdPokGwFZgprlZyVmMId/yLlsbsFlwmGIJh/l1+zqw I4DBzRzuhAg/nLH9qVZ3LWOjH1H0MLPGBUG59w4GbIDpwRh1VZu+GTyAmAYaquHl dSHYweXywNTvhi0WLroP8SD0Nqf/ZObuSRtop60gqJuP3YAbPrBMeRTlsqoZIRE= =Xzc8 -----END PGP SIGNATURE----- Merge remote-tracking branch 'remotes/bonzini/tags/for-upstream' into staging * IEC units series (Philippe) * Hyper-V PV TLB flush (Vitaly) * git archive detection (Daniel) * host serial passthrough fix (David) * NPT support for SVM emulation (Jan) * x86 "info mem" and "info tlb" fix (Doug) # gpg: Signature made Mon 02 Jul 2018 16:18:21 BST # gpg: using RSA key BFFBD25F78C7AE83 # gpg: Good signature from "Paolo Bonzini <bonzini@gnu.org>" # gpg: aka "Paolo Bonzini <pbonzini@redhat.com>" # Primary key fingerprint: 46F5 9FBD 57D6 12E7 BFD4 E2F7 7E15 100C CD36 69B1 # Subkey fingerprint: F133 3857 4B66 2389 866C 7682 BFFB D25F 78C7 AE83 * remotes/bonzini/tags/for-upstream: (50 commits) tcg: simplify !CONFIG_TCG handling of tb_invalidate_* i386/monitor.c: make addresses canonical for "info mem" and "info tlb" target-i386: Add NPT support serial: Open non-block bsd-user: Use the IEC binary prefix definitions linux-user: Use the IEC binary prefix definitions tests/crypto: Use the IEC binary prefix definitions vl: Use the IEC binary prefix definitions monitor: Use the IEC binary prefix definitions cutils: Do not include "qemu/units.h" directly hw/rdma: Use the IEC binary prefix definitions hw/virtio: Use the IEC binary prefix definitions hw/vfio: Use the IEC binary prefix definitions hw/sd: Use the IEC binary prefix definitions hw/usb: Use the IEC binary prefix definitions hw/net: Use the IEC binary prefix definitions hw/i386: Use the IEC binary prefix definitions hw/ppc: Use the IEC binary prefix definitions hw/mips: Use the IEC binary prefix definitions hw/mips/r4k: Constify params_size ... Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
367 lines
10 KiB
C
367 lines
10 KiB
C
/*
|
|
* QEMU PCI bochs display adapter.
|
|
*
|
|
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
|
* See the COPYING file in the top-level directory.
|
|
*/
|
|
#include "qemu/osdep.h"
|
|
#include "qemu/units.h"
|
|
#include "hw/hw.h"
|
|
#include "hw/pci/pci.h"
|
|
#include "hw/display/bochs-vbe.h"
|
|
|
|
#include "qapi/error.h"
|
|
|
|
#include "ui/console.h"
|
|
#include "ui/qemu-pixman.h"
|
|
|
|
typedef struct BochsDisplayMode {
|
|
pixman_format_code_t format;
|
|
uint32_t bytepp;
|
|
uint32_t width;
|
|
uint32_t height;
|
|
uint32_t stride;
|
|
uint64_t offset;
|
|
uint64_t size;
|
|
} BochsDisplayMode;
|
|
|
|
typedef struct BochsDisplayState {
|
|
/* parent */
|
|
PCIDevice pci;
|
|
|
|
/* device elements */
|
|
QemuConsole *con;
|
|
MemoryRegion vram;
|
|
MemoryRegion mmio;
|
|
MemoryRegion vbe;
|
|
MemoryRegion qext;
|
|
|
|
/* device config */
|
|
uint64_t vgamem;
|
|
|
|
/* device registers */
|
|
uint16_t vbe_regs[VBE_DISPI_INDEX_NB];
|
|
bool big_endian_fb;
|
|
|
|
/* device state */
|
|
BochsDisplayMode mode;
|
|
} BochsDisplayState;
|
|
|
|
#define TYPE_BOCHS_DISPLAY "bochs-display"
|
|
#define BOCHS_DISPLAY(obj) OBJECT_CHECK(BochsDisplayState, (obj), \
|
|
TYPE_BOCHS_DISPLAY)
|
|
|
|
static const VMStateDescription vmstate_bochs_display = {
|
|
.name = "bochs-display",
|
|
.fields = (VMStateField[]) {
|
|
VMSTATE_PCI_DEVICE(pci, BochsDisplayState),
|
|
VMSTATE_UINT16_ARRAY(vbe_regs, BochsDisplayState, VBE_DISPI_INDEX_NB),
|
|
VMSTATE_BOOL(big_endian_fb, BochsDisplayState),
|
|
VMSTATE_END_OF_LIST()
|
|
}
|
|
};
|
|
|
|
static uint64_t bochs_display_vbe_read(void *ptr, hwaddr addr,
|
|
unsigned size)
|
|
{
|
|
BochsDisplayState *s = ptr;
|
|
unsigned int index = addr >> 1;
|
|
|
|
switch (index) {
|
|
case VBE_DISPI_INDEX_ID:
|
|
return VBE_DISPI_ID5;
|
|
case VBE_DISPI_INDEX_VIDEO_MEMORY_64K:
|
|
return s->vgamem / (64 * KiB);
|
|
}
|
|
|
|
if (index >= ARRAY_SIZE(s->vbe_regs)) {
|
|
return -1;
|
|
}
|
|
return s->vbe_regs[index];
|
|
}
|
|
|
|
static void bochs_display_vbe_write(void *ptr, hwaddr addr,
|
|
uint64_t val, unsigned size)
|
|
{
|
|
BochsDisplayState *s = ptr;
|
|
unsigned int index = addr >> 1;
|
|
|
|
if (index >= ARRAY_SIZE(s->vbe_regs)) {
|
|
return;
|
|
}
|
|
s->vbe_regs[index] = val;
|
|
}
|
|
|
|
static const MemoryRegionOps bochs_display_vbe_ops = {
|
|
.read = bochs_display_vbe_read,
|
|
.write = bochs_display_vbe_write,
|
|
.valid.min_access_size = 1,
|
|
.valid.max_access_size = 4,
|
|
.impl.min_access_size = 2,
|
|
.impl.max_access_size = 2,
|
|
.endianness = DEVICE_LITTLE_ENDIAN,
|
|
};
|
|
|
|
static uint64_t bochs_display_qext_read(void *ptr, hwaddr addr,
|
|
unsigned size)
|
|
{
|
|
BochsDisplayState *s = ptr;
|
|
|
|
switch (addr) {
|
|
case PCI_VGA_QEXT_REG_SIZE:
|
|
return PCI_VGA_QEXT_SIZE;
|
|
case PCI_VGA_QEXT_REG_BYTEORDER:
|
|
return s->big_endian_fb ?
|
|
PCI_VGA_QEXT_BIG_ENDIAN : PCI_VGA_QEXT_LITTLE_ENDIAN;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static void bochs_display_qext_write(void *ptr, hwaddr addr,
|
|
uint64_t val, unsigned size)
|
|
{
|
|
BochsDisplayState *s = ptr;
|
|
|
|
switch (addr) {
|
|
case PCI_VGA_QEXT_REG_BYTEORDER:
|
|
if (val == PCI_VGA_QEXT_BIG_ENDIAN) {
|
|
s->big_endian_fb = true;
|
|
}
|
|
if (val == PCI_VGA_QEXT_LITTLE_ENDIAN) {
|
|
s->big_endian_fb = false;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
static const MemoryRegionOps bochs_display_qext_ops = {
|
|
.read = bochs_display_qext_read,
|
|
.write = bochs_display_qext_write,
|
|
.valid.min_access_size = 4,
|
|
.valid.max_access_size = 4,
|
|
.endianness = DEVICE_LITTLE_ENDIAN,
|
|
};
|
|
|
|
static int bochs_display_get_mode(BochsDisplayState *s,
|
|
BochsDisplayMode *mode)
|
|
{
|
|
uint16_t *vbe = s->vbe_regs;
|
|
uint32_t virt_width;
|
|
|
|
if (!(vbe[VBE_DISPI_INDEX_ENABLE] & VBE_DISPI_ENABLED)) {
|
|
return -1;
|
|
}
|
|
|
|
memset(mode, 0, sizeof(*mode));
|
|
switch (vbe[VBE_DISPI_INDEX_BPP]) {
|
|
case 16:
|
|
/* best effort: support native endianess only */
|
|
mode->format = PIXMAN_r5g6b5;
|
|
mode->bytepp = 2;
|
|
break;
|
|
case 32:
|
|
mode->format = s->big_endian_fb
|
|
? PIXMAN_BE_x8r8g8b8
|
|
: PIXMAN_LE_x8r8g8b8;
|
|
mode->bytepp = 4;
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
|
|
mode->width = vbe[VBE_DISPI_INDEX_XRES];
|
|
mode->height = vbe[VBE_DISPI_INDEX_YRES];
|
|
virt_width = vbe[VBE_DISPI_INDEX_VIRT_WIDTH];
|
|
if (virt_width < mode->width) {
|
|
virt_width = mode->width;
|
|
}
|
|
mode->stride = virt_width * mode->bytepp;
|
|
mode->size = (uint64_t)mode->stride * mode->height;
|
|
mode->offset = ((uint64_t)vbe[VBE_DISPI_INDEX_X_OFFSET] * mode->bytepp +
|
|
(uint64_t)vbe[VBE_DISPI_INDEX_Y_OFFSET] * mode->stride);
|
|
|
|
if (mode->width < 64 || mode->height < 64) {
|
|
return -1;
|
|
}
|
|
if (mode->offset + mode->size > s->vgamem) {
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void bochs_display_update(void *opaque)
|
|
{
|
|
BochsDisplayState *s = opaque;
|
|
DirtyBitmapSnapshot *snap = NULL;
|
|
bool full_update = false;
|
|
BochsDisplayMode mode;
|
|
DisplaySurface *ds;
|
|
uint8_t *ptr;
|
|
bool dirty;
|
|
int y, ys, ret;
|
|
|
|
ret = bochs_display_get_mode(s, &mode);
|
|
if (ret < 0) {
|
|
/* no (valid) video mode */
|
|
return;
|
|
}
|
|
|
|
if (memcmp(&s->mode, &mode, sizeof(mode)) != 0) {
|
|
/* video mode switch */
|
|
s->mode = mode;
|
|
ptr = memory_region_get_ram_ptr(&s->vram);
|
|
ds = qemu_create_displaysurface_from(mode.width,
|
|
mode.height,
|
|
mode.format,
|
|
mode.stride,
|
|
ptr + mode.offset);
|
|
dpy_gfx_replace_surface(s->con, ds);
|
|
full_update = true;
|
|
}
|
|
|
|
if (full_update) {
|
|
dpy_gfx_update_full(s->con);
|
|
} else {
|
|
snap = memory_region_snapshot_and_clear_dirty(&s->vram,
|
|
mode.offset, mode.size,
|
|
DIRTY_MEMORY_VGA);
|
|
ys = -1;
|
|
for (y = 0; y < mode.height; y++) {
|
|
dirty = memory_region_snapshot_get_dirty(&s->vram, snap,
|
|
mode.offset + mode.stride * y,
|
|
mode.stride);
|
|
if (dirty && ys < 0) {
|
|
ys = y;
|
|
}
|
|
if (!dirty && ys >= 0) {
|
|
dpy_gfx_update(s->con, 0, ys,
|
|
mode.width, y - ys);
|
|
ys = -1;
|
|
}
|
|
}
|
|
if (ys >= 0) {
|
|
dpy_gfx_update(s->con, 0, ys,
|
|
mode.width, y - ys);
|
|
}
|
|
}
|
|
}
|
|
|
|
static const GraphicHwOps bochs_display_gfx_ops = {
|
|
.gfx_update = bochs_display_update,
|
|
};
|
|
|
|
static void bochs_display_realize(PCIDevice *dev, Error **errp)
|
|
{
|
|
BochsDisplayState *s = BOCHS_DISPLAY(dev);
|
|
Object *obj = OBJECT(dev);
|
|
int ret;
|
|
|
|
s->con = graphic_console_init(DEVICE(dev), 0, &bochs_display_gfx_ops, s);
|
|
|
|
if (s->vgamem < 4 * MiB) {
|
|
error_setg(errp, "bochs-display: video memory too small");
|
|
}
|
|
if (s->vgamem > 256 * MiB) {
|
|
error_setg(errp, "bochs-display: video memory too big");
|
|
}
|
|
s->vgamem = pow2ceil(s->vgamem);
|
|
|
|
memory_region_init_ram(&s->vram, obj, "bochs-display-vram", s->vgamem,
|
|
&error_fatal);
|
|
memory_region_init_io(&s->vbe, obj, &bochs_display_vbe_ops, s,
|
|
"bochs dispi interface", PCI_VGA_BOCHS_SIZE);
|
|
memory_region_init_io(&s->qext, obj, &bochs_display_qext_ops, s,
|
|
"qemu extended regs", PCI_VGA_QEXT_SIZE);
|
|
|
|
memory_region_init(&s->mmio, obj, "bochs-display-mmio",
|
|
PCI_VGA_MMIO_SIZE);
|
|
memory_region_add_subregion(&s->mmio, PCI_VGA_BOCHS_OFFSET, &s->vbe);
|
|
memory_region_add_subregion(&s->mmio, PCI_VGA_QEXT_OFFSET, &s->qext);
|
|
|
|
pci_set_byte(&s->pci.config[PCI_REVISION_ID], 2);
|
|
pci_register_bar(&s->pci, 0, PCI_BASE_ADDRESS_MEM_PREFETCH, &s->vram);
|
|
pci_register_bar(&s->pci, 2, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->mmio);
|
|
|
|
if (pci_bus_is_express(pci_get_bus(dev))) {
|
|
dev->cap_present |= QEMU_PCI_CAP_EXPRESS;
|
|
ret = pcie_endpoint_cap_init(dev, 0x80);
|
|
assert(ret > 0);
|
|
}
|
|
|
|
memory_region_set_log(&s->vram, true, DIRTY_MEMORY_VGA);
|
|
}
|
|
|
|
static bool bochs_display_get_big_endian_fb(Object *obj, Error **errp)
|
|
{
|
|
BochsDisplayState *s = BOCHS_DISPLAY(obj);
|
|
|
|
return s->big_endian_fb;
|
|
}
|
|
|
|
static void bochs_display_set_big_endian_fb(Object *obj, bool value,
|
|
Error **errp)
|
|
{
|
|
BochsDisplayState *s = BOCHS_DISPLAY(obj);
|
|
|
|
s->big_endian_fb = value;
|
|
}
|
|
|
|
static void bochs_display_init(Object *obj)
|
|
{
|
|
/* Expose framebuffer byteorder via QOM */
|
|
object_property_add_bool(obj, "big-endian-framebuffer",
|
|
bochs_display_get_big_endian_fb,
|
|
bochs_display_set_big_endian_fb,
|
|
NULL);
|
|
}
|
|
|
|
static void bochs_display_exit(PCIDevice *dev)
|
|
{
|
|
BochsDisplayState *s = BOCHS_DISPLAY(dev);
|
|
|
|
graphic_console_close(s->con);
|
|
}
|
|
|
|
static Property bochs_display_properties[] = {
|
|
DEFINE_PROP_SIZE("vgamem", BochsDisplayState, vgamem, 16 * MiB),
|
|
DEFINE_PROP_END_OF_LIST(),
|
|
};
|
|
|
|
static void bochs_display_class_init(ObjectClass *klass, void *data)
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
|
PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
|
|
|
|
k->class_id = PCI_CLASS_DISPLAY_OTHER;
|
|
k->vendor_id = PCI_VENDOR_ID_QEMU;
|
|
k->device_id = PCI_DEVICE_ID_QEMU_VGA;
|
|
|
|
k->realize = bochs_display_realize;
|
|
k->romfile = "vgabios-bochs-display.bin";
|
|
k->exit = bochs_display_exit;
|
|
dc->vmsd = &vmstate_bochs_display;
|
|
dc->props = bochs_display_properties;
|
|
set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories);
|
|
}
|
|
|
|
static const TypeInfo bochs_display_type_info = {
|
|
.name = TYPE_BOCHS_DISPLAY,
|
|
.parent = TYPE_PCI_DEVICE,
|
|
.instance_size = sizeof(BochsDisplayState),
|
|
.instance_init = bochs_display_init,
|
|
.class_init = bochs_display_class_init,
|
|
.interfaces = (InterfaceInfo[]) {
|
|
{ INTERFACE_PCIE_DEVICE },
|
|
{ INTERFACE_CONVENTIONAL_PCI_DEVICE },
|
|
{ },
|
|
},
|
|
};
|
|
|
|
static void bochs_display_register_types(void)
|
|
{
|
|
type_register_static(&bochs_display_type_info);
|
|
}
|
|
|
|
type_init(bochs_display_register_types)
|