| /* |
| * 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/module.h" |
| #include "qemu/units.h" |
| #include "hw/pci/pci_device.h" |
| #include "hw/qdev-properties.h" |
| #include "migration/vmstate.h" |
| #include "hw/display/bochs-vbe.h" |
| #include "hw/display/edid.h" |
| |
| #include "qapi/error.h" |
| |
| #include "ui/console.h" |
| #include "ui/qemu-pixman.h" |
| #include "qom/object.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; |
| |
| struct BochsDisplayState { |
| /* parent */ |
| PCIDevice pci; |
| |
| /* device elements */ |
| QemuConsole *con; |
| MemoryRegion vram; |
| MemoryRegion mmio; |
| MemoryRegion vbe; |
| MemoryRegion qext; |
| MemoryRegion edid; |
| |
| /* device config */ |
| uint64_t vgamem; |
| bool enable_edid; |
| qemu_edid_info edid_info; |
| uint8_t edid_blob[256]; |
| |
| /* device registers */ |
| uint16_t vbe_regs[VBE_DISPI_INDEX_NB]; |
| bool big_endian_fb; |
| |
| /* device state */ |
| BochsDisplayMode mode; |
| }; |
| |
| #define TYPE_BOCHS_DISPLAY "bochs-display" |
| OBJECT_DECLARE_SIMPLE_TYPE(BochsDisplayState, 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 endianness 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); |
| } |
| |
| g_free(snap); |
| } |
| } |
| |
| 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; |
| |
| if (s->vgamem < 4 * MiB) { |
| error_setg(errp, "bochs-display: video memory too small"); |
| return; |
| } |
| if (s->vgamem > 256 * MiB) { |
| error_setg(errp, "bochs-display: video memory too big"); |
| return; |
| } |
| s->vgamem = pow2ceil(s->vgamem); |
| |
| s->con = graphic_console_init(DEVICE(dev), 0, &bochs_display_gfx_ops, s); |
| |
| 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_io(&s->mmio, obj, &unassigned_io_ops, NULL, |
| "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 (s->enable_edid) { |
| qemu_edid_generate(s->edid_blob, sizeof(s->edid_blob), &s->edid_info); |
| qemu_edid_region_io(&s->edid, obj, s->edid_blob, sizeof(s->edid_blob)); |
| memory_region_add_subregion(&s->mmio, 0, &s->edid); |
| } |
| |
| if (pci_bus_is_express(pci_get_bus(dev))) { |
| ret = pcie_endpoint_cap_init(dev, 0x80); |
| assert(ret > 0); |
| } else { |
| dev->cap_present &= ~QEMU_PCI_CAP_EXPRESS; |
| } |
| |
| 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) |
| { |
| PCIDevice *dev = PCI_DEVICE(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); |
| |
| dev->cap_present |= QEMU_PCI_CAP_EXPRESS; |
| } |
| |
| 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_BOOL("edid", BochsDisplayState, enable_edid, true), |
| DEFINE_EDID_PROPERTIES(BochsDisplayState, edid_info), |
| 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; |
| device_class_set_props(dc, 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) |