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
22struct 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
31struct 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
39static 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
47static 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
74static 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
100void 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
115static 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
124RAMFBState *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