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