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