aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Maydell <peter.maydell@linaro.org>2014-09-17 00:56:59 +0100
committerPeter Maydell <peter.maydell@linaro.org>2014-09-30 12:03:04 +0100
commit7fcaa6f0a807d8f615188ceb3b77f3e38e0a66c5 (patch)
treec775591b376fda2b51ce05f411b81b3df5caee0c
parent29429c7244c73eefada3d0ec6dd30c5698782d08 (diff)
downloadqemu-arm-7fcaa6f0a807d8f615188ceb3b77f3e38e0a66c5.tar.gz
Initial goldfish_fb display device
import goldfish_fb from android HACK: goldfish_fb: remove FB_GET_FORMAT register FB_GET_FORMAT and associated code poke at a bunch of display internals, but are never referenced by the kernel. Revisit on top of the new qemu display API if/when it's needed by the kernel. HACK: goldfish_fb: disable UI rotation events It's not clear how this worked or how to port it to the new display API, so disable it for now goldfish_fb: port to modern qemu apis The device/object APIs and display APIs have been significantly refactored. qemu also has a tracing API which can replace the debugging printfs. Signed-off-by: Greg Hackmann <ghackmann@google.com> Although the milkymist display device is indeed RGB565, it is bigendian, so we can't actually borrow its drawfn helpers. Implement our own instead. After an FB_SET_BASE write, the guest is waiting for us to complete an update cycle and notify it (via the FB_INT_BASE_UPDATE_DONE interrupt status bit). Force the graphics backend to actually do a redraw immediately; this avoids the guest timing out and producing periodic "goldfish_fb_pan_display: timeout waiting for base update" warnings on displays like VNC which make an effort to avoid redraw. goldfish_fb: Don't use uninitialized ymin if screen is blank Initialize ymin in the code path taken if the screen is blank, to avoid use of an uninitialized variable. Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
-rw-r--r--default-configs/arm-softmmu.mak1
-rw-r--r--hw/display/Makefile.objs1
-rw-r--r--hw/display/goldfish_fb.c401
-rw-r--r--hw/display/goldfish_fb_template.h78
-rw-r--r--trace-events8
5 files changed, 489 insertions, 0 deletions
diff --git a/default-configs/arm-softmmu.mak b/default-configs/arm-softmmu.mak
index f3513fa124..b1dc85fb1d 100644
--- a/default-configs/arm-softmmu.mak
+++ b/default-configs/arm-softmmu.mak
@@ -78,6 +78,7 @@ CONFIG_NSERIES=y
CONFIG_REALVIEW=y
CONFIG_ZAURUS=y
CONFIG_ZYNQ=y
+CONFIG_GOLDFISH=y
CONFIG_VERSATILE_PCI=y
CONFIG_VERSATILE_I2C=y
diff --git a/hw/display/Makefile.objs b/hw/display/Makefile.objs
index 7ed76a9c24..2456da6c66 100644
--- a/hw/display/Makefile.objs
+++ b/hw/display/Makefile.objs
@@ -15,6 +15,7 @@ common-obj-$(CONFIG_VMWARE_VGA) += vmware_vga.o
common-obj-$(CONFIG_BLIZZARD) += blizzard.o
common-obj-$(CONFIG_EXYNOS4) += exynos4210_fimd.o
common-obj-$(CONFIG_FRAMEBUFFER) += framebuffer.o
+common-obj-$(CONFIG_GOLDFISH) += goldfish_fb.o
common-obj-$(CONFIG_MILKYMIST) += milkymist-vgafb.o
common-obj-$(CONFIG_ZAURUS) += tc6393xb.o
diff --git a/hw/display/goldfish_fb.c b/hw/display/goldfish_fb.c
new file mode 100644
index 0000000000..bdd8987714
--- /dev/null
+++ b/hw/display/goldfish_fb.c
@@ -0,0 +1,401 @@
+/* Copyright (C) 2007-2013 The Android Open Source Project
+**
+** This software is licensed under the terms of the GNU General Public
+** License version 2, as published by the Free Software Foundation, and
+** may be copied, distributed, and modified under those terms.
+**
+** This program is distributed in the hope that it will be useful,
+** but WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+** GNU General Public License for more details.
+*/
+#include "framebuffer.h"
+#include "hw/hw.h"
+#include "hw/sysbus.h"
+#include "ui/console.h"
+#include "ui/pixel_ops.h"
+#include "trace.h"
+
+#define BITS 8
+#include "goldfish_fb_template.h"
+#define BITS 15
+#include "goldfish_fb_template.h"
+#define BITS 16
+#include "goldfish_fb_template.h"
+#define BITS 24
+#include "goldfish_fb_template.h"
+#define BITS 32
+#include "goldfish_fb_template.h"
+
+#define TYPE_GOLDFISH_FB "goldfish_fb"
+#define GOLDFISH_FB(obj) OBJECT_CHECK(struct goldfish_fb_state, (obj), TYPE_GOLDFISH_FB)
+
+enum {
+ FB_GET_WIDTH = 0x00,
+ FB_GET_HEIGHT = 0x04,
+ FB_INT_STATUS = 0x08,
+ FB_INT_ENABLE = 0x0c,
+ FB_SET_BASE = 0x10,
+ FB_SET_ROTATION = 0x14,
+ FB_SET_BLANK = 0x18,
+ FB_GET_PHYS_WIDTH = 0x1c,
+ FB_GET_PHYS_HEIGHT = 0x20,
+
+ FB_INT_VSYNC = 1U << 0,
+ FB_INT_BASE_UPDATE_DONE = 1U << 1
+};
+
+struct goldfish_fb_state {
+ SysBusDevice parent;
+
+ QemuConsole *con;
+ MemoryRegion iomem;
+ qemu_irq irq;
+
+ uint32_t fb_base;
+ uint32_t base_valid : 1;
+ uint32_t need_update : 1;
+ uint32_t need_int : 1;
+ uint32_t set_rotation : 2;
+ uint32_t blank : 1;
+ uint32_t int_status;
+ uint32_t int_enable;
+ int rotation; /* 0, 1, 2 or 3 */
+ int dpi;
+};
+
+#define GOLDFISH_FB_SAVE_VERSION 2
+
+static void goldfish_fb_save(QEMUFile* f, void* opaque)
+{
+ struct goldfish_fb_state* s = opaque;
+
+ DisplaySurface *ds = qemu_console_surface(s->con);
+
+ qemu_put_be32(f, surface_width(ds));
+ qemu_put_be32(f, surface_height(ds));
+ qemu_put_be32(f, surface_stride(ds));
+ qemu_put_byte(f, 0);
+
+ qemu_put_be32(f, s->fb_base);
+ qemu_put_byte(f, s->base_valid);
+ qemu_put_byte(f, s->need_update);
+ qemu_put_byte(f, s->need_int);
+ qemu_put_byte(f, s->set_rotation);
+ qemu_put_byte(f, s->blank);
+ qemu_put_be32(f, s->int_status);
+ qemu_put_be32(f, s->int_enable);
+ qemu_put_be32(f, s->rotation);
+ qemu_put_be32(f, s->dpi);
+}
+
+static int goldfish_fb_load(QEMUFile* f, void* opaque, int version_id)
+{
+ struct goldfish_fb_state* s = opaque;
+ int ret = -1;
+ int ds_w, ds_h, ds_pitch, ds_rot;
+
+ if (version_id != GOLDFISH_FB_SAVE_VERSION)
+ goto Exit;
+
+ ds_w = qemu_get_be32(f);
+ ds_h = qemu_get_be32(f);
+ ds_pitch = qemu_get_be32(f);
+ ds_rot = qemu_get_byte(f);
+
+ DisplaySurface *ds = qemu_console_surface(s->con);
+
+ if (surface_width(ds) != ds_w ||
+ surface_height(ds) != ds_h ||
+ surface_stride(ds) != ds_pitch ||
+ ds_rot != 0)
+ {
+ /* XXX: We should be able to force a resize/rotation from here ? */
+ fprintf(stderr, "%s: framebuffer dimensions mismatch\n", __FUNCTION__);
+ goto Exit;
+ }
+
+ s->fb_base = qemu_get_be32(f);
+ s->base_valid = qemu_get_byte(f);
+ s->need_update = qemu_get_byte(f);
+ s->need_int = qemu_get_byte(f);
+ s->set_rotation = qemu_get_byte(f);
+ s->blank = qemu_get_byte(f);
+ s->int_status = qemu_get_be32(f);
+ s->int_enable = qemu_get_be32(f);
+ s->rotation = qemu_get_be32(f);
+ s->dpi = qemu_get_be32(f);
+
+ /* force a refresh */
+ s->need_update = 1;
+
+ ret = 0;
+Exit:
+ return ret;
+}
+
+static int
+pixels_to_mm(int pixels, int dpi)
+{
+ /* dpi = dots / inch
+ ** inch = dots / dpi
+ ** mm / 25.4 = dots / dpi
+ ** mm = (dots * 25.4)/dpi
+ */
+ return (int)(0.5 + 25.4 * pixels / dpi);
+}
+
+
+#define STATS 0
+
+#if STATS
+static int stats_counter;
+static long stats_total;
+static int stats_full_updates;
+static long stats_total_full_updates;
+#endif
+
+static void goldfish_fb_update_display(void *opaque)
+{
+ struct goldfish_fb_state *s = (struct goldfish_fb_state *)opaque;
+ DisplaySurface *ds = qemu_console_surface(s->con);
+ int full_update = 0;
+ int width, height, pitch;
+
+ if (!s || !s->con || surface_bits_per_pixel(ds) == 0 || !s->fb_base)
+ return;
+
+ if((s->int_enable & FB_INT_VSYNC) && !(s->int_status & FB_INT_VSYNC)) {
+ s->int_status |= FB_INT_VSYNC;
+ qemu_irq_raise(s->irq);
+ }
+
+ if(s->need_update) {
+ full_update = 1;
+ if(s->need_int) {
+ s->int_status |= FB_INT_BASE_UPDATE_DONE;
+ if(s->int_enable & FB_INT_BASE_UPDATE_DONE)
+ qemu_irq_raise(s->irq);
+ }
+ s->need_int = 0;
+ s->need_update = 0;
+ }
+
+ pitch = surface_stride(ds);
+ width = surface_width(ds);
+ height = surface_height(ds);
+
+ int ymin, ymax;
+
+#if STATS
+ if (full_update)
+ stats_full_updates += 1;
+ if (++stats_counter == 120) {
+ stats_total += stats_counter;
+ stats_total_full_updates += stats_full_updates;
+
+ trace_goldfish_fb_update_stats(stats_full_updates*100.0/stats_counter,
+ stats_total_full_updates*100.0/stats_total );
+
+ stats_counter = 0;
+ stats_full_updates = 0;
+ }
+#endif /* STATS */
+
+ if (s->blank)
+ {
+ void *dst_line = surface_data(ds);
+ memset( dst_line, 0, height*pitch );
+ ymin = 0;
+ ymax = height-1;
+ }
+ else
+ {
+ SysBusDevice *dev = SYS_BUS_DEVICE(opaque);
+ MemoryRegion *address_space = sysbus_address_space(dev);
+ int src_width = width * 2;
+ int dest_col_pitch = surface_bytes_per_pixel(ds);
+ int dest_row_pitch = surface_stride(ds);
+ drawfn fn;
+
+ switch (surface_bits_per_pixel(ds)) {
+ case 0:
+ return;
+ case 8:
+ fn = draw_line_8;
+ break;
+ case 15:
+ fn = draw_line_15;
+ break;
+ case 16:
+ fn = draw_line_16;
+ break;
+ case 24:
+ fn = draw_line_24;
+ break;
+ case 32:
+ fn = draw_line_32;
+ break;
+ default:
+ hw_error("goldfish_fb: bad color depth\n");
+ return;
+ }
+
+ ymin = 0;
+ framebuffer_update_display(ds, address_space, s->fb_base, width, height,
+ src_width, dest_row_pitch, dest_col_pitch, full_update,
+ fn, ds, &ymin, &ymax);
+ }
+
+ ymax += 1;
+ if (ymin >= 0) {
+ trace_goldfish_fb_update_display(ymin, ymax-ymin, 0, width);
+ dpy_gfx_update(s->con, 0, ymin, width, ymax-ymin);
+ }
+}
+
+static void goldfish_fb_invalidate_display(void * opaque)
+{
+ // is this called?
+ struct goldfish_fb_state *s = (struct goldfish_fb_state *)opaque;
+ s->need_update = 1;
+}
+
+static uint64_t goldfish_fb_read(void *opaque, hwaddr offset, unsigned size)
+{
+ uint64_t ret = 0;
+ struct goldfish_fb_state *s = opaque;
+ DisplaySurface *ds = qemu_console_surface(s->con);
+
+ switch(offset) {
+ case FB_GET_WIDTH:
+ ret = surface_width(ds);
+ break;
+
+ case FB_GET_HEIGHT:
+ ret = surface_height(ds);
+ break;
+
+ case FB_INT_STATUS:
+ ret = s->int_status & s->int_enable;
+ if(ret) {
+ s->int_status &= ~ret;
+ qemu_irq_lower(s->irq);
+ }
+ break;
+
+ case FB_GET_PHYS_WIDTH:
+ ret = pixels_to_mm( surface_width(ds), s->dpi );
+ break;
+
+ case FB_GET_PHYS_HEIGHT:
+ ret = pixels_to_mm( surface_height(ds), s->dpi );
+ break;
+
+ default:
+ error_report("goldfish_fb_read: Bad offset 0x" TARGET_FMT_plx,
+ offset);
+ break;
+ }
+
+ trace_goldfish_fb_memory_read(offset, ret);
+ return ret;
+}
+
+static void goldfish_fb_write(void *opaque, hwaddr offset, uint64_t val,
+ unsigned size)
+{
+ struct goldfish_fb_state *s = opaque;
+
+ trace_goldfish_fb_memory_write(offset, val);
+
+ switch(offset) {
+ case FB_INT_ENABLE:
+ s->int_enable = val;
+ qemu_set_irq(s->irq, s->int_status & s->int_enable);
+ break;
+ case FB_SET_BASE:
+ s->fb_base = val;
+ s->int_status &= ~FB_INT_BASE_UPDATE_DONE;
+ s->need_update = 1;
+ s->need_int = 1;
+ s->base_valid = 1;
+ if(s->set_rotation != s->rotation) {
+ //printf("FB_SET_BASE: rotation : %d => %d\n", s->rotation, s->set_rotation);
+ s->rotation = s->set_rotation;
+ }
+ /* The guest is waiting for us to complete an update cycle
+ * and notify it, so make sure we do a redraw immediately.
+ */
+ graphic_hw_update(s->con);
+ qemu_set_irq(s->irq, s->int_status & s->int_enable);
+ break;
+ case FB_SET_ROTATION:
+ s->set_rotation = val;
+ break;
+ case FB_SET_BLANK:
+ s->blank = val;
+ s->need_update = 1;
+ break;
+ default:
+ error_report("goldfish_fb_write: Bad offset 0x" TARGET_FMT_plx,
+ offset);
+ }
+}
+
+static const MemoryRegionOps goldfish_fb_iomem_ops = {
+ .read = goldfish_fb_read,
+ .write = goldfish_fb_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .impl.min_access_size = 4,
+ .impl.max_access_size = 4,
+};
+
+static const GraphicHwOps goldfish_fb_ops = {
+ .invalidate = goldfish_fb_invalidate_display,
+ .gfx_update = goldfish_fb_update_display,
+};
+
+static int goldfish_fb_init(SysBusDevice *sbdev)
+{
+ DeviceState *dev = DEVICE(sbdev);
+ struct goldfish_fb_state *s = GOLDFISH_FB(dev);
+
+ sysbus_init_irq(sbdev, &s->irq);
+
+ s->con = graphic_console_init(dev, 0, &goldfish_fb_ops, s);
+
+ s->dpi = 165; /* XXX: Find better way to get actual value ! */
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &goldfish_fb_iomem_ops, s,
+ "goldfish_fb", 0x100);
+ sysbus_init_mmio(sbdev, &s->iomem);
+
+ register_savevm(dev, "goldfish_fb", 0, GOLDFISH_FB_SAVE_VERSION,
+ goldfish_fb_save, goldfish_fb_load, s);
+
+ return 0;
+}
+
+static void goldfish_fb_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = goldfish_fb_init;
+ dc->desc = "goldfish framebuffer";
+}
+
+static const TypeInfo goldfish_fb_info = {
+ .name = TYPE_GOLDFISH_FB,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(struct goldfish_fb_state),
+ .class_init = goldfish_fb_class_init,
+};
+
+static void goldfish_fb_register(void)
+{
+ type_register_static(&goldfish_fb_info);
+}
+
+type_init(goldfish_fb_register);
diff --git a/hw/display/goldfish_fb_template.h b/hw/display/goldfish_fb_template.h
new file mode 100644
index 0000000000..8579e68e4f
--- /dev/null
+++ b/hw/display/goldfish_fb_template.h
@@ -0,0 +1,78 @@
+/*
+ * QEMU model of the Goldfish framebuffer: drawing templates.
+ *
+ * Copyright (c) 2014 Linaro Limited
+ *
+ * Based heavily on the milkymist drawing templates, which are:
+ *
+ * Copyright (c) 2010 Michael Walle <michael@walle.cc>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#if BITS == 8
+#define COPY_PIXEL(to, r, g, b) \
+ do { \
+ *to = rgb_to_pixel8(r, g, b); \
+ to += 1; \
+ } while (0)
+#elif BITS == 15
+#define COPY_PIXEL(to, r, g, b) \
+ do { \
+ *(uint16_t *)to = rgb_to_pixel15(r, g, b); \
+ to += 2; \
+ } while (0)
+#elif BITS == 16
+#define COPY_PIXEL(to, r, g, b) \
+ do { \
+ *(uint16_t *)to = rgb_to_pixel16(r, g, b); \
+ to += 2; \
+ } while (0)
+#elif BITS == 24
+#define COPY_PIXEL(to, r, g, b) \
+ do { \
+ uint32_t tmp = rgb_to_pixel24(r, g, b); \
+ *(to++) = tmp & 0xff; \
+ *(to++) = (tmp >> 8) & 0xff; \
+ *(to++) = (tmp >> 16) & 0xff; \
+ } while (0)
+#elif BITS == 32
+#define COPY_PIXEL(to, r, g, b) \
+ do { \
+ *(uint32_t *)to = rgb_to_pixel32(r, g, b); \
+ to += 4; \
+ } while (0)
+#else
+#error unknown bit depth
+#endif
+
+static void glue(draw_line_, BITS)(void *opaque, uint8_t *d, const uint8_t *s,
+ int width, int deststep)
+{
+ uint16_t rgb565;
+ uint8_t r, g, b;
+
+ while (width--) {
+ rgb565 = lduw_le_p(s);
+ r = ((rgb565 >> 11) & 0x1f) << 3;
+ g = ((rgb565 >> 5) & 0x3f) << 2;
+ b = ((rgb565 >> 0) & 0x1f) << 3;
+ COPY_PIXEL(d, r, g, b);
+ s += 2;
+ }
+}
+
+#undef BITS
+#undef COPY_PIXEL
diff --git a/trace-events b/trace-events
index 011d1059f8..96ee8b51d6 100644
--- a/trace-events
+++ b/trace-events
@@ -1370,5 +1370,13 @@ mhp_pc_dimm_assigned_address(uint64_t addr) "0x%"PRIx64
kvm_enable_cmma(int rc) "CMMA: enabling with result code %d"
kvm_clear_cmma(int rc) "CMMA: clearing with result code %d"
+
# hw/dma/i8257.c
i8257_unregistered_dma(int nchan, int dma_pos, int dma_len) "unregistered DMA channel used nchan=%d dma_pos=%d dma_len=%d"
+
+# hw/display/goldfish_fb.c
+goldfish_fb_memory_read(uint32_t addr, uint32_t value) "addr %08x value %08x"
+goldfish_fb_memory_write(uint32_t addr, uint32_t value) "addr %08x value %08x"
+goldfish_fb_update_display(int y, int h, int x, int w) "y:%d,h:%d,x=%d,w=%d"
+goldfish_fb_update_stats(float peak, float total) "peak %.2f %% total %.2f %%"
+