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