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