/* * early boot framebuffer in guest ram * configured using fw_cfg * * Copyright Red Hat, Inc. 2017 * * Author: * Gerd Hoffmann * * This work is licensed under the terms of the GNU GPL, version 2 or later. * See the COPYING file in the top-level directory. */ #include "qemu/osdep.h" #include "qapi/error.h" #include "qemu/option.h" #include "hw/loader.h" #include "hw/display/ramfb.h" #include "ui/console.h" #include "sysemu/reset.h" struct QEMU_PACKED RAMFBCfg { uint64_t addr; uint32_t fourcc; uint32_t flags; uint32_t width; uint32_t height; uint32_t stride; }; struct RAMFBState { DisplaySurface *ds; uint32_t width, height; uint32_t starting_width, starting_height; struct RAMFBCfg cfg; bool locked; }; static void ramfb_unmap_display_surface(pixman_image_t *image, void *unused) { void *data = pixman_image_get_data(image); uint32_t size = pixman_image_get_stride(image) * pixman_image_get_height(image); cpu_physical_memory_unmap(data, size, 0, 0); } static DisplaySurface *ramfb_create_display_surface(int width, int height, pixman_format_code_t format, int linesize, uint64_t addr) { DisplaySurface *surface; hwaddr size; void *data; if (linesize == 0) { linesize = width * PIXMAN_FORMAT_BPP(format) / 8; } size = (hwaddr)linesize * height; data = cpu_physical_memory_map(addr, &size, false); if (size != (hwaddr)linesize * height) { cpu_physical_memory_unmap(data, size, 0, 0); return NULL; } surface = qemu_create_displaysurface_from(width, height, format, linesize, data); pixman_image_set_destroy_function(surface->image, ramfb_unmap_display_surface, NULL); return surface; } static void ramfb_fw_cfg_write(void *dev, off_t offset, size_t len) { RAMFBState *s = dev; uint32_t fourcc, format, width, height; hwaddr stride, addr; width = be32_to_cpu(s->cfg.width); height = be32_to_cpu(s->cfg.height); stride = be32_to_cpu(s->cfg.stride); fourcc = be32_to_cpu(s->cfg.fourcc); addr = be64_to_cpu(s->cfg.addr); format = qemu_drm_format_to_pixman(fourcc); fprintf(stderr, "%s: %dx%d @ 0x%" PRIx64 "\n", __func__, width, height, addr); if (s->locked) { fprintf(stderr, "%s: resolution locked, change rejected\n", __func__); return; } s->locked = true; s->width = width; s->height = height; s->ds = ramfb_create_display_surface(s->width, s->height, format, stride, addr); } void ramfb_display_update(QemuConsole *con, RAMFBState *s) { if (!s->width || !s->height) { return; } if (s->ds) { dpy_gfx_replace_surface(con, s->ds); s->ds = NULL; } /* simple full screen update */ dpy_gfx_update_full(con); } static void ramfb_reset(void *opaque) { RAMFBState *s = (RAMFBState *)opaque; s->locked = false; memset(&s->cfg, 0, sizeof(s->cfg)); s->cfg.width = s->starting_width; s->cfg.height = s->starting_height; } RAMFBState *ramfb_setup(DeviceState* dev, Error **errp) { FWCfgState *fw_cfg = fw_cfg_find(); RAMFBState *s; if (!fw_cfg || !fw_cfg->dma_enabled) { error_setg(errp, "ramfb device requires fw_cfg with DMA"); return NULL; } s = g_new0(RAMFBState, 1); const char *s_fb_width = qemu_opt_get(dev->opts, "xres"); const char *s_fb_height = qemu_opt_get(dev->opts, "yres"); if (s_fb_width) { s->cfg.width = atoi(s_fb_width); s->starting_width = s->cfg.width; } if (s_fb_height) { s->cfg.height = atoi(s_fb_height); s->starting_height = s->cfg.height; } s->locked = false; rom_add_vga("vgabios-ramfb.bin"); fw_cfg_add_file_callback(fw_cfg, "etc/ramfb", NULL, ramfb_fw_cfg_write, s, &s->cfg, sizeof(s->cfg), false); qemu_register_reset(ramfb_reset, s); return s; }