qemu/hw/display/bochs-display.c
<<
>>
Prefs
   1/*
   2 * QEMU PCI bochs display adapter.
   3 *
   4 * This work is licensed under the terms of the GNU GPL, version 2 or later.
   5 * See the COPYING file in the top-level directory.
   6 */
   7
   8#include "qemu/osdep.h"
   9#include "qemu/module.h"
  10#include "qemu/units.h"
  11#include "hw/pci/pci.h"
  12#include "hw/qdev-properties.h"
  13#include "migration/vmstate.h"
  14#include "hw/display/bochs-vbe.h"
  15#include "hw/display/edid.h"
  16
  17#include "qapi/error.h"
  18
  19#include "ui/console.h"
  20#include "ui/qemu-pixman.h"
  21#include "qom/object.h"
  22
  23typedef struct BochsDisplayMode {
  24    pixman_format_code_t format;
  25    uint32_t             bytepp;
  26    uint32_t             width;
  27    uint32_t             height;
  28    uint32_t             stride;
  29    uint64_t             offset;
  30    uint64_t             size;
  31} BochsDisplayMode;
  32
  33struct BochsDisplayState {
  34    /* parent */
  35    PCIDevice        pci;
  36
  37    /* device elements */
  38    QemuConsole      *con;
  39    MemoryRegion     vram;
  40    MemoryRegion     mmio;
  41    MemoryRegion     vbe;
  42    MemoryRegion     qext;
  43    MemoryRegion     edid;
  44
  45    /* device config */
  46    uint64_t         vgamem;
  47    bool             enable_edid;
  48    qemu_edid_info   edid_info;
  49    uint8_t          edid_blob[256];
  50
  51    /* device registers */
  52    uint16_t         vbe_regs[VBE_DISPI_INDEX_NB];
  53    bool             big_endian_fb;
  54
  55    /* device state */
  56    BochsDisplayMode mode;
  57};
  58
  59#define TYPE_BOCHS_DISPLAY "bochs-display"
  60OBJECT_DECLARE_SIMPLE_TYPE(BochsDisplayState, BOCHS_DISPLAY)
  61
  62static const VMStateDescription vmstate_bochs_display = {
  63    .name = "bochs-display",
  64    .fields = (VMStateField[]) {
  65        VMSTATE_PCI_DEVICE(pci, BochsDisplayState),
  66        VMSTATE_UINT16_ARRAY(vbe_regs, BochsDisplayState, VBE_DISPI_INDEX_NB),
  67        VMSTATE_BOOL(big_endian_fb, BochsDisplayState),
  68        VMSTATE_END_OF_LIST()
  69    }
  70};
  71
  72static uint64_t bochs_display_vbe_read(void *ptr, hwaddr addr,
  73                                       unsigned size)
  74{
  75    BochsDisplayState *s = ptr;
  76    unsigned int index = addr >> 1;
  77
  78    switch (index) {
  79    case VBE_DISPI_INDEX_ID:
  80        return VBE_DISPI_ID5;
  81    case VBE_DISPI_INDEX_VIDEO_MEMORY_64K:
  82        return s->vgamem / (64 * KiB);
  83    }
  84
  85    if (index >= ARRAY_SIZE(s->vbe_regs)) {
  86        return -1;
  87    }
  88    return s->vbe_regs[index];
  89}
  90
  91static void bochs_display_vbe_write(void *ptr, hwaddr addr,
  92                                    uint64_t val, unsigned size)
  93{
  94    BochsDisplayState *s = ptr;
  95    unsigned int index = addr >> 1;
  96
  97    if (index >= ARRAY_SIZE(s->vbe_regs)) {
  98        return;
  99    }
 100    s->vbe_regs[index] = val;
 101}
 102
 103static const MemoryRegionOps bochs_display_vbe_ops = {
 104    .read = bochs_display_vbe_read,
 105    .write = bochs_display_vbe_write,
 106    .valid.min_access_size = 1,
 107    .valid.max_access_size = 4,
 108    .impl.min_access_size = 2,
 109    .impl.max_access_size = 2,
 110    .endianness = DEVICE_LITTLE_ENDIAN,
 111};
 112
 113static uint64_t bochs_display_qext_read(void *ptr, hwaddr addr,
 114                                        unsigned size)
 115{
 116    BochsDisplayState *s = ptr;
 117
 118    switch (addr) {
 119    case PCI_VGA_QEXT_REG_SIZE:
 120        return PCI_VGA_QEXT_SIZE;
 121    case PCI_VGA_QEXT_REG_BYTEORDER:
 122        return s->big_endian_fb ?
 123            PCI_VGA_QEXT_BIG_ENDIAN : PCI_VGA_QEXT_LITTLE_ENDIAN;
 124    default:
 125        return 0;
 126    }
 127}
 128
 129static void bochs_display_qext_write(void *ptr, hwaddr addr,
 130                                     uint64_t val, unsigned size)
 131{
 132    BochsDisplayState *s = ptr;
 133
 134    switch (addr) {
 135    case PCI_VGA_QEXT_REG_BYTEORDER:
 136        if (val == PCI_VGA_QEXT_BIG_ENDIAN) {
 137            s->big_endian_fb = true;
 138        }
 139        if (val == PCI_VGA_QEXT_LITTLE_ENDIAN) {
 140            s->big_endian_fb = false;
 141        }
 142        break;
 143    }
 144}
 145
 146static const MemoryRegionOps bochs_display_qext_ops = {
 147    .read = bochs_display_qext_read,
 148    .write = bochs_display_qext_write,
 149    .valid.min_access_size = 4,
 150    .valid.max_access_size = 4,
 151    .endianness = DEVICE_LITTLE_ENDIAN,
 152};
 153
 154static int bochs_display_get_mode(BochsDisplayState *s,
 155                                   BochsDisplayMode *mode)
 156{
 157    uint16_t *vbe = s->vbe_regs;
 158    uint32_t virt_width;
 159
 160    if (!(vbe[VBE_DISPI_INDEX_ENABLE] & VBE_DISPI_ENABLED)) {
 161        return -1;
 162    }
 163
 164    memset(mode, 0, sizeof(*mode));
 165    switch (vbe[VBE_DISPI_INDEX_BPP]) {
 166    case 16:
 167        /* best effort: support native endianess only */
 168        mode->format = PIXMAN_r5g6b5;
 169        mode->bytepp = 2;
 170        break;
 171    case 32:
 172        mode->format = s->big_endian_fb
 173            ? PIXMAN_BE_x8r8g8b8
 174            : PIXMAN_LE_x8r8g8b8;
 175        mode->bytepp = 4;
 176        break;
 177    default:
 178        return -1;
 179    }
 180
 181    mode->width  = vbe[VBE_DISPI_INDEX_XRES];
 182    mode->height = vbe[VBE_DISPI_INDEX_YRES];
 183    virt_width  = vbe[VBE_DISPI_INDEX_VIRT_WIDTH];
 184    if (virt_width < mode->width) {
 185        virt_width = mode->width;
 186    }
 187    mode->stride = virt_width * mode->bytepp;
 188    mode->size   = (uint64_t)mode->stride * mode->height;
 189    mode->offset = ((uint64_t)vbe[VBE_DISPI_INDEX_X_OFFSET] * mode->bytepp +
 190                    (uint64_t)vbe[VBE_DISPI_INDEX_Y_OFFSET] * mode->stride);
 191
 192    if (mode->width < 64 || mode->height < 64) {
 193        return -1;
 194    }
 195    if (mode->offset + mode->size > s->vgamem) {
 196        return -1;
 197    }
 198    return 0;
 199}
 200
 201static void bochs_display_update(void *opaque)
 202{
 203    BochsDisplayState *s = opaque;
 204    DirtyBitmapSnapshot *snap = NULL;
 205    bool full_update = false;
 206    BochsDisplayMode mode;
 207    DisplaySurface *ds;
 208    uint8_t *ptr;
 209    bool dirty;
 210    int y, ys, ret;
 211
 212    ret = bochs_display_get_mode(s, &mode);
 213    if (ret < 0) {
 214        /* no (valid) video mode */
 215        return;
 216    }
 217
 218    if (memcmp(&s->mode, &mode, sizeof(mode)) != 0) {
 219        /* video mode switch */
 220        s->mode = mode;
 221        ptr = memory_region_get_ram_ptr(&s->vram);
 222        ds = qemu_create_displaysurface_from(mode.width,
 223                                             mode.height,
 224                                             mode.format,
 225                                             mode.stride,
 226                                             ptr + mode.offset);
 227        dpy_gfx_replace_surface(s->con, ds);
 228        full_update = true;
 229    }
 230
 231    if (full_update) {
 232        dpy_gfx_update_full(s->con);
 233    } else {
 234        snap = memory_region_snapshot_and_clear_dirty(&s->vram,
 235                                                      mode.offset, mode.size,
 236                                                      DIRTY_MEMORY_VGA);
 237        ys = -1;
 238        for (y = 0; y < mode.height; y++) {
 239            dirty = memory_region_snapshot_get_dirty(&s->vram, snap,
 240                                                     mode.offset + mode.stride * y,
 241                                                     mode.stride);
 242            if (dirty && ys < 0) {
 243                ys = y;
 244            }
 245            if (!dirty && ys >= 0) {
 246                dpy_gfx_update(s->con, 0, ys,
 247                               mode.width, y - ys);
 248                ys = -1;
 249            }
 250        }
 251        if (ys >= 0) {
 252            dpy_gfx_update(s->con, 0, ys,
 253                           mode.width, y - ys);
 254        }
 255
 256        g_free(snap);
 257    }
 258}
 259
 260static const GraphicHwOps bochs_display_gfx_ops = {
 261    .gfx_update = bochs_display_update,
 262};
 263
 264static void bochs_display_realize(PCIDevice *dev, Error **errp)
 265{
 266    BochsDisplayState *s = BOCHS_DISPLAY(dev);
 267    Object *obj = OBJECT(dev);
 268    int ret;
 269
 270    if (s->vgamem < 4 * MiB) {
 271        error_setg(errp, "bochs-display: video memory too small");
 272        return;
 273    }
 274    if (s->vgamem > 256 * MiB) {
 275        error_setg(errp, "bochs-display: video memory too big");
 276        return;
 277    }
 278    s->vgamem = pow2ceil(s->vgamem);
 279
 280    s->con = graphic_console_init(DEVICE(dev), 0, &bochs_display_gfx_ops, s);
 281
 282    memory_region_init_ram(&s->vram, obj, "bochs-display-vram", s->vgamem,
 283                           &error_fatal);
 284    memory_region_init_io(&s->vbe, obj, &bochs_display_vbe_ops, s,
 285                          "bochs dispi interface", PCI_VGA_BOCHS_SIZE);
 286    memory_region_init_io(&s->qext, obj, &bochs_display_qext_ops, s,
 287                          "qemu extended regs", PCI_VGA_QEXT_SIZE);
 288
 289    memory_region_init_io(&s->mmio, obj, &unassigned_io_ops, NULL,
 290                          "bochs-display-mmio", PCI_VGA_MMIO_SIZE);
 291    memory_region_add_subregion(&s->mmio, PCI_VGA_BOCHS_OFFSET, &s->vbe);
 292    memory_region_add_subregion(&s->mmio, PCI_VGA_QEXT_OFFSET, &s->qext);
 293
 294    pci_set_byte(&s->pci.config[PCI_REVISION_ID], 2);
 295    pci_register_bar(&s->pci, 0, PCI_BASE_ADDRESS_MEM_PREFETCH, &s->vram);
 296    pci_register_bar(&s->pci, 2, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->mmio);
 297
 298    if (s->enable_edid) {
 299        qemu_edid_generate(s->edid_blob, sizeof(s->edid_blob), &s->edid_info);
 300        qemu_edid_region_io(&s->edid, obj, s->edid_blob, sizeof(s->edid_blob));
 301        memory_region_add_subregion(&s->mmio, 0, &s->edid);
 302    }
 303
 304    if (pci_bus_is_express(pci_get_bus(dev))) {
 305        ret = pcie_endpoint_cap_init(dev, 0x80);
 306        assert(ret > 0);
 307    } else {
 308        dev->cap_present &= ~QEMU_PCI_CAP_EXPRESS;
 309    }
 310
 311    memory_region_set_log(&s->vram, true, DIRTY_MEMORY_VGA);
 312}
 313
 314static bool bochs_display_get_big_endian_fb(Object *obj, Error **errp)
 315{
 316    BochsDisplayState *s = BOCHS_DISPLAY(obj);
 317
 318    return s->big_endian_fb;
 319}
 320
 321static void bochs_display_set_big_endian_fb(Object *obj, bool value,
 322                                            Error **errp)
 323{
 324    BochsDisplayState *s = BOCHS_DISPLAY(obj);
 325
 326    s->big_endian_fb = value;
 327}
 328
 329static void bochs_display_init(Object *obj)
 330{
 331    PCIDevice *dev = PCI_DEVICE(obj);
 332
 333    /* Expose framebuffer byteorder via QOM */
 334    object_property_add_bool(obj, "big-endian-framebuffer",
 335                             bochs_display_get_big_endian_fb,
 336                             bochs_display_set_big_endian_fb);
 337
 338    dev->cap_present |= QEMU_PCI_CAP_EXPRESS;
 339}
 340
 341static void bochs_display_exit(PCIDevice *dev)
 342{
 343    BochsDisplayState *s = BOCHS_DISPLAY(dev);
 344
 345    graphic_console_close(s->con);
 346}
 347
 348static Property bochs_display_properties[] = {
 349    DEFINE_PROP_SIZE("vgamem", BochsDisplayState, vgamem, 16 * MiB),
 350    DEFINE_PROP_BOOL("edid", BochsDisplayState, enable_edid, true),
 351    DEFINE_EDID_PROPERTIES(BochsDisplayState, edid_info),
 352    DEFINE_PROP_END_OF_LIST(),
 353};
 354
 355static void bochs_display_class_init(ObjectClass *klass, void *data)
 356{
 357    DeviceClass *dc = DEVICE_CLASS(klass);
 358    PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
 359
 360    k->class_id  = PCI_CLASS_DISPLAY_OTHER;
 361    k->vendor_id = PCI_VENDOR_ID_QEMU;
 362    k->device_id = PCI_DEVICE_ID_QEMU_VGA;
 363
 364    k->realize   = bochs_display_realize;
 365    k->romfile   = "vgabios-bochs-display.bin";
 366    k->exit      = bochs_display_exit;
 367    dc->vmsd     = &vmstate_bochs_display;
 368    device_class_set_props(dc, bochs_display_properties);
 369    set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories);
 370}
 371
 372static const TypeInfo bochs_display_type_info = {
 373    .name           = TYPE_BOCHS_DISPLAY,
 374    .parent         = TYPE_PCI_DEVICE,
 375    .instance_size  = sizeof(BochsDisplayState),
 376    .instance_init  = bochs_display_init,
 377    .class_init     = bochs_display_class_init,
 378    .interfaces     = (InterfaceInfo[]) {
 379        { INTERFACE_PCIE_DEVICE },
 380        { INTERFACE_CONVENTIONAL_PCI_DEVICE },
 381        { },
 382    },
 383};
 384
 385static void bochs_display_register_types(void)
 386{
 387    type_register_static(&bochs_display_type_info);
 388}
 389
 390type_init(bochs_display_register_types)
 391