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