]>
Commit | Line | Data |
---|---|---|
765c9429 GH |
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 | */ | |
0b8fa32f | 7 | |
765c9429 | 8 | #include "qemu/osdep.h" |
0b8fa32f | 9 | #include "qemu/module.h" |
f0353b0d | 10 | #include "qemu/units.h" |
765c9429 | 11 | #include "hw/pci/pci.h" |
a27bd6c7 | 12 | #include "hw/qdev-properties.h" |
d6454270 | 13 | #include "migration/vmstate.h" |
765c9429 | 14 | #include "hw/display/bochs-vbe.h" |
a0d098b7 | 15 | #include "hw/display/edid.h" |
765c9429 GH |
16 | |
17 | #include "qapi/error.h" | |
18 | ||
19 | #include "ui/console.h" | |
20 | #include "ui/qemu-pixman.h" | |
db1015e9 | 21 | #include "qom/object.h" |
765c9429 GH |
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 | ||
db1015e9 | 33 | struct BochsDisplayState { |
765c9429 GH |
34 | /* parent */ |
35 | PCIDevice pci; | |
36 | ||
37 | /* device elements */ | |
38 | QemuConsole *con; | |
39 | MemoryRegion vram; | |
40 | MemoryRegion mmio; | |
41 | MemoryRegion vbe; | |
42 | MemoryRegion qext; | |
a0d098b7 | 43 | MemoryRegion edid; |
765c9429 GH |
44 | |
45 | /* device config */ | |
46 | uint64_t vgamem; | |
a0d098b7 GH |
47 | bool enable_edid; |
48 | qemu_edid_info edid_info; | |
49 | uint8_t edid_blob[256]; | |
765c9429 GH |
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; | |
db1015e9 EH |
57 | }; |
58 | typedef struct BochsDisplayState BochsDisplayState; | |
765c9429 GH |
59 | |
60 | #define TYPE_BOCHS_DISPLAY "bochs-display" | |
8110fa1d EH |
61 | DECLARE_INSTANCE_CHECKER(BochsDisplayState, BOCHS_DISPLAY, |
62 | TYPE_BOCHS_DISPLAY) | |
765c9429 GH |
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: | |
f0353b0d | 84 | return s->vgamem / (64 * KiB); |
765c9429 GH |
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; | |
bc820db0 | 172 | break; |
765c9429 GH |
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; | |
33ebad54 GH |
206 | DirtyBitmapSnapshot *snap = NULL; |
207 | bool full_update = false; | |
765c9429 GH |
208 | BochsDisplayMode mode; |
209 | DisplaySurface *ds; | |
210 | uint8_t *ptr; | |
33ebad54 GH |
211 | bool dirty; |
212 | int y, ys, ret; | |
765c9429 GH |
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); | |
33ebad54 | 230 | full_update = true; |
765c9429 GH |
231 | } |
232 | ||
33ebad54 GH |
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 | } | |
0d82411d CE |
257 | |
258 | g_free(snap); | |
33ebad54 | 259 | } |
765c9429 GH |
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); | |
f2581064 | 270 | int ret; |
765c9429 | 271 | |
f0353b0d | 272 | if (s->vgamem < 4 * MiB) { |
765c9429 | 273 | error_setg(errp, "bochs-display: video memory too small"); |
ee29f6e9 | 274 | return; |
765c9429 | 275 | } |
f0353b0d | 276 | if (s->vgamem > 256 * MiB) { |
765c9429 | 277 | error_setg(errp, "bochs-display: video memory too big"); |
ee29f6e9 | 278 | return; |
765c9429 GH |
279 | } |
280 | s->vgamem = pow2ceil(s->vgamem); | |
281 | ||
ee29f6e9 MA |
282 | s->con = graphic_console_init(DEVICE(dev), 0, &bochs_display_gfx_ops, s); |
283 | ||
765c9429 GH |
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 | ||
f872c762 GH |
291 | memory_region_init_io(&s->mmio, obj, &unassigned_io_ops, NULL, |
292 | "bochs-display-mmio", PCI_VGA_MMIO_SIZE); | |
765c9429 GH |
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); | |
33ebad54 | 299 | |
a0d098b7 GH |
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 | ||
f2581064 | 306 | if (pci_bus_is_express(pci_get_bus(dev))) { |
f2581064 GH |
307 | ret = pcie_endpoint_cap_init(dev, 0x80); |
308 | assert(ret > 0); | |
5e7bcdcf GH |
309 | } else { |
310 | dev->cap_present &= ~QEMU_PCI_CAP_EXPRESS; | |
f2581064 GH |
311 | } |
312 | ||
33ebad54 | 313 | memory_region_set_log(&s->vram, true, DIRTY_MEMORY_VGA); |
765c9429 GH |
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 | { | |
5e7bcdcf GH |
333 | PCIDevice *dev = PCI_DEVICE(obj); |
334 | ||
765c9429 GH |
335 | /* Expose framebuffer byteorder via QOM */ |
336 | object_property_add_bool(obj, "big-endian-framebuffer", | |
337 | bochs_display_get_big_endian_fb, | |
d2623129 | 338 | bochs_display_set_big_endian_fb); |
5e7bcdcf GH |
339 | |
340 | dev->cap_present |= QEMU_PCI_CAP_EXPRESS; | |
765c9429 GH |
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[] = { | |
f0353b0d | 351 | DEFINE_PROP_SIZE("vgamem", BochsDisplayState, vgamem, 16 * MiB), |
0a719662 | 352 | DEFINE_PROP_BOOL("edid", BochsDisplayState, enable_edid, true), |
a0d098b7 | 353 | DEFINE_EDID_PROPERTIES(BochsDisplayState, edid_info), |
765c9429 GH |
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; | |
7c538789 | 367 | k->romfile = "vgabios-bochs-display.bin"; |
765c9429 GH |
368 | k->exit = bochs_display_exit; |
369 | dc->vmsd = &vmstate_bochs_display; | |
4f67d30b | 370 | device_class_set_props(dc, bochs_display_properties); |
765c9429 GH |
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[]) { | |
f2581064 | 381 | { INTERFACE_PCIE_DEVICE }, |
765c9429 GH |
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) |