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 case 32: 162 mode->format = s->big_endian_fb 163 ? PIXMAN_BE_x8r8g8b8 164 : PIXMAN_LE_x8r8g8b8; 165 mode->bytepp = 4; 166 break; 167 default: 168 return -1; 169 } 170 171 mode->width = vbe[VBE_DISPI_INDEX_XRES]; 172 mode->height = vbe[VBE_DISPI_INDEX_YRES]; 173 virt_width = vbe[VBE_DISPI_INDEX_VIRT_WIDTH]; 174 if (virt_width < mode->width) { 175 virt_width = mode->width; 176 } 177 mode->stride = virt_width * mode->bytepp; 178 mode->size = (uint64_t)mode->stride * mode->height; 179 mode->offset = ((uint64_t)vbe[VBE_DISPI_INDEX_X_OFFSET] * mode->bytepp + 180 (uint64_t)vbe[VBE_DISPI_INDEX_Y_OFFSET] * mode->stride); 181 182 if (mode->width < 64 || mode->height < 64) { 183 return -1; 184 } 185 if (mode->offset + mode->size > s->vgamem) { 186 return -1; 187 } 188 return 0; 189 } 190 191 static void bochs_display_update(void *opaque) 192 { 193 BochsDisplayState *s = opaque; 194 BochsDisplayMode mode; 195 DisplaySurface *ds; 196 uint8_t *ptr; 197 int ret; 198 199 ret = bochs_display_get_mode(s, &mode); 200 if (ret < 0) { 201 /* no (valid) video mode */ 202 return; 203 } 204 205 if (memcmp(&s->mode, &mode, sizeof(mode)) != 0) { 206 /* video mode switch */ 207 s->mode = mode; 208 ptr = memory_region_get_ram_ptr(&s->vram); 209 ds = qemu_create_displaysurface_from(mode.width, 210 mode.height, 211 mode.format, 212 mode.stride, 213 ptr + mode.offset); 214 dpy_gfx_replace_surface(s->con, ds); 215 } 216 217 dpy_gfx_update_full(s->con); 218 } 219 220 static const GraphicHwOps bochs_display_gfx_ops = { 221 .gfx_update = bochs_display_update, 222 }; 223 224 static void bochs_display_realize(PCIDevice *dev, Error **errp) 225 { 226 BochsDisplayState *s = BOCHS_DISPLAY(dev); 227 Object *obj = OBJECT(dev); 228 229 s->con = graphic_console_init(DEVICE(dev), 0, &bochs_display_gfx_ops, s); 230 231 if (s->vgamem < (4 * 1024 * 1024)) { 232 error_setg(errp, "bochs-display: video memory too small"); 233 } 234 if (s->vgamem > (256 * 1024 * 1024)) { 235 error_setg(errp, "bochs-display: video memory too big"); 236 } 237 s->vgamem = pow2ceil(s->vgamem); 238 239 memory_region_init_ram(&s->vram, obj, "bochs-display-vram", s->vgamem, 240 &error_fatal); 241 memory_region_init_io(&s->vbe, obj, &bochs_display_vbe_ops, s, 242 "bochs dispi interface", PCI_VGA_BOCHS_SIZE); 243 memory_region_init_io(&s->qext, obj, &bochs_display_qext_ops, s, 244 "qemu extended regs", PCI_VGA_QEXT_SIZE); 245 246 memory_region_init(&s->mmio, obj, "bochs-display-mmio", 247 PCI_VGA_MMIO_SIZE); 248 memory_region_add_subregion(&s->mmio, PCI_VGA_BOCHS_OFFSET, &s->vbe); 249 memory_region_add_subregion(&s->mmio, PCI_VGA_QEXT_OFFSET, &s->qext); 250 251 pci_set_byte(&s->pci.config[PCI_REVISION_ID], 2); 252 pci_register_bar(&s->pci, 0, PCI_BASE_ADDRESS_MEM_PREFETCH, &s->vram); 253 pci_register_bar(&s->pci, 2, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->mmio); 254 } 255 256 static bool bochs_display_get_big_endian_fb(Object *obj, Error **errp) 257 { 258 BochsDisplayState *s = BOCHS_DISPLAY(obj); 259 260 return s->big_endian_fb; 261 } 262 263 static void bochs_display_set_big_endian_fb(Object *obj, bool value, 264 Error **errp) 265 { 266 BochsDisplayState *s = BOCHS_DISPLAY(obj); 267 268 s->big_endian_fb = value; 269 } 270 271 static void bochs_display_init(Object *obj) 272 { 273 /* Expose framebuffer byteorder via QOM */ 274 object_property_add_bool(obj, "big-endian-framebuffer", 275 bochs_display_get_big_endian_fb, 276 bochs_display_set_big_endian_fb, 277 NULL); 278 } 279 280 static void bochs_display_exit(PCIDevice *dev) 281 { 282 BochsDisplayState *s = BOCHS_DISPLAY(dev); 283 284 graphic_console_close(s->con); 285 } 286 287 static Property bochs_display_properties[] = { 288 DEFINE_PROP_SIZE("vgamem", BochsDisplayState, vgamem, 16 * 1024 * 1024), 289 DEFINE_PROP_END_OF_LIST(), 290 }; 291 292 static void bochs_display_class_init(ObjectClass *klass, void *data) 293 { 294 DeviceClass *dc = DEVICE_CLASS(klass); 295 PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); 296 297 k->class_id = PCI_CLASS_DISPLAY_OTHER; 298 k->vendor_id = PCI_VENDOR_ID_QEMU; 299 k->device_id = PCI_DEVICE_ID_QEMU_VGA; 300 301 k->realize = bochs_display_realize; 302 k->exit = bochs_display_exit; 303 dc->vmsd = &vmstate_bochs_display; 304 dc->props = bochs_display_properties; 305 set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories); 306 } 307 308 static const TypeInfo bochs_display_type_info = { 309 .name = TYPE_BOCHS_DISPLAY, 310 .parent = TYPE_PCI_DEVICE, 311 .instance_size = sizeof(BochsDisplayState), 312 .instance_init = bochs_display_init, 313 .class_init = bochs_display_class_init, 314 .interfaces = (InterfaceInfo[]) { 315 { INTERFACE_CONVENTIONAL_PCI_DEVICE }, 316 { }, 317 }, 318 }; 319 320 static void bochs_display_register_types(void) 321 { 322 type_register_static(&bochs_display_type_info); 323 } 324 325 type_init(bochs_display_register_types) 326