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