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