1 | /* |
2 | * early boot framebuffer in guest ram |
3 | * configured using fw_cfg |
4 | * |
5 | * Copyright Red Hat, Inc. 2017 |
6 | * |
7 | * Author: |
8 | * Gerd Hoffmann <kraxel@redhat.com> |
9 | * |
10 | * This work is licensed under the terms of the GNU GPL, version 2 or later. |
11 | * See the COPYING file in the top-level directory. |
12 | */ |
13 | |
14 | #include "qemu/osdep.h" |
15 | #include "qapi/error.h" |
16 | #include "qemu/option.h" |
17 | #include "hw/loader.h" |
18 | #include "hw/display/ramfb.h" |
19 | #include "ui/console.h" |
20 | #include "sysemu/reset.h" |
21 | |
22 | struct QEMU_PACKED RAMFBCfg { |
23 | uint64_t addr; |
24 | uint32_t fourcc; |
25 | uint32_t flags; |
26 | uint32_t width; |
27 | uint32_t height; |
28 | uint32_t stride; |
29 | }; |
30 | |
31 | struct RAMFBState { |
32 | DisplaySurface *ds; |
33 | uint32_t width, height; |
34 | uint32_t starting_width, starting_height; |
35 | struct RAMFBCfg cfg; |
36 | bool locked; |
37 | }; |
38 | |
39 | static void ramfb_unmap_display_surface(pixman_image_t *image, void *unused) |
40 | { |
41 | void *data = pixman_image_get_data(image); |
42 | uint32_t size = pixman_image_get_stride(image) * |
43 | pixman_image_get_height(image); |
44 | cpu_physical_memory_unmap(data, size, 0, 0); |
45 | } |
46 | |
47 | static DisplaySurface *ramfb_create_display_surface(int width, int height, |
48 | pixman_format_code_t format, |
49 | int linesize, uint64_t addr) |
50 | { |
51 | DisplaySurface *surface; |
52 | hwaddr size; |
53 | void *data; |
54 | |
55 | if (linesize == 0) { |
56 | linesize = width * PIXMAN_FORMAT_BPP(format) / 8; |
57 | } |
58 | |
59 | size = (hwaddr)linesize * height; |
60 | data = cpu_physical_memory_map(addr, &size, 0); |
61 | if (size != (hwaddr)linesize * height) { |
62 | cpu_physical_memory_unmap(data, size, 0, 0); |
63 | return NULL; |
64 | } |
65 | |
66 | surface = qemu_create_displaysurface_from(width, height, |
67 | format, linesize, data); |
68 | pixman_image_set_destroy_function(surface->image, |
69 | ramfb_unmap_display_surface, NULL); |
70 | |
71 | return surface; |
72 | } |
73 | |
74 | static void ramfb_fw_cfg_write(void *dev, off_t offset, size_t len) |
75 | { |
76 | RAMFBState *s = dev; |
77 | uint32_t fourcc, format, width, height; |
78 | hwaddr stride, addr; |
79 | |
80 | width = be32_to_cpu(s->cfg.width); |
81 | height = be32_to_cpu(s->cfg.height); |
82 | stride = be32_to_cpu(s->cfg.stride); |
83 | fourcc = be32_to_cpu(s->cfg.fourcc); |
84 | addr = be64_to_cpu(s->cfg.addr); |
85 | format = qemu_drm_format_to_pixman(fourcc); |
86 | |
87 | fprintf(stderr, "%s: %dx%d @ 0x%" PRIx64 "\n" , __func__, |
88 | width, height, addr); |
89 | if (s->locked) { |
90 | fprintf(stderr, "%s: resolution locked, change rejected\n" , __func__); |
91 | return; |
92 | } |
93 | s->locked = true; |
94 | s->width = width; |
95 | s->height = height; |
96 | s->ds = ramfb_create_display_surface(s->width, s->height, |
97 | format, stride, addr); |
98 | } |
99 | |
100 | void ramfb_display_update(QemuConsole *con, RAMFBState *s) |
101 | { |
102 | if (!s->width || !s->height) { |
103 | return; |
104 | } |
105 | |
106 | if (s->ds) { |
107 | dpy_gfx_replace_surface(con, s->ds); |
108 | s->ds = NULL; |
109 | } |
110 | |
111 | /* simple full screen update */ |
112 | dpy_gfx_update_full(con); |
113 | } |
114 | |
115 | static void ramfb_reset(void *opaque) |
116 | { |
117 | RAMFBState *s = (RAMFBState *)opaque; |
118 | s->locked = false; |
119 | memset(&s->cfg, 0, sizeof(s->cfg)); |
120 | s->cfg.width = s->starting_width; |
121 | s->cfg.height = s->starting_height; |
122 | } |
123 | |
124 | RAMFBState *ramfb_setup(DeviceState* dev, Error **errp) |
125 | { |
126 | FWCfgState *fw_cfg = fw_cfg_find(); |
127 | RAMFBState *s; |
128 | |
129 | if (!fw_cfg || !fw_cfg->dma_enabled) { |
130 | error_setg(errp, "ramfb device requires fw_cfg with DMA" ); |
131 | return NULL; |
132 | } |
133 | |
134 | s = g_new0(RAMFBState, 1); |
135 | |
136 | const char *s_fb_width = qemu_opt_get(dev->opts, "xres" ); |
137 | const char *s_fb_height = qemu_opt_get(dev->opts, "yres" ); |
138 | if (s_fb_width) { |
139 | s->cfg.width = atoi(s_fb_width); |
140 | s->starting_width = s->cfg.width; |
141 | } |
142 | if (s_fb_height) { |
143 | s->cfg.height = atoi(s_fb_height); |
144 | s->starting_height = s->cfg.height; |
145 | } |
146 | s->locked = false; |
147 | |
148 | rom_add_vga("vgabios-ramfb.bin" ); |
149 | fw_cfg_add_file_callback(fw_cfg, "etc/ramfb" , |
150 | NULL, ramfb_fw_cfg_write, s, |
151 | &s->cfg, sizeof(s->cfg), false); |
152 | qemu_register_reset(ramfb_reset, s); |
153 | return s; |
154 | } |
155 | |