From 55f4e4a5ec657a017e3bf75299ad71fd1c968dd3 Mon Sep 17 00:00:00 2001 From: The Android Open Source Project Date: Tue, 21 Oct 2008 07:00:00 -0700 Subject: Initial Contribution --- hw/goldfish_fb.c | 405 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 405 insertions(+) create mode 100644 hw/goldfish_fb.c (limited to 'hw/goldfish_fb.c') diff --git a/hw/goldfish_fb.c b/hw/goldfish_fb.c new file mode 100644 index 0000000..0924735 --- /dev/null +++ b/hw/goldfish_fb.c @@ -0,0 +1,405 @@ +/* Copyright (C) 2007-2008 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 "vl.h" +#include "android.h" +#include "goldfish_device.h" +#include "framebuffer.h" + +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 { + struct goldfish_device dev; + QFrameBuffer* qfbuff; + 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 */ +}; + +#define GOLDFISH_FB_SAVE_VERSION 1 + +static void goldfish_fb_save(QEMUFile* f, void* opaque) +{ + struct goldfish_fb_state* s = opaque; + + QFrameBuffer* q = s->qfbuff; + + qemu_put_be32(f, q->width); + qemu_put_be32(f, q->height); + qemu_put_be32(f, q->pitch); + qemu_put_byte(f, q->rotation); + + 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); +} + +static int goldfish_fb_load(QEMUFile* f, void* opaque, int version_id) +{ + struct goldfish_fb_state* s = opaque; + + QFrameBuffer* q = s->qfbuff; + 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); + + if (q->width != ds_w || + q->height != ds_h || + q->pitch != ds_pitch || + q->rotation != ds_rot ) + { + /* 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); + + /* force a refresh */ + s->need_update = 1; + + ret = 0; +Exit: + return ret; +} + + +#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; + uint32_t addr; + uint32_t base; + + uint8_t* dst_line; + uint8_t* src_line; + int y_first, y_last = 0; + int full_update = 0; + int width, height, pitch; + + base = s->fb_base; + if(base == 0) + return; + + if((s->int_enable & FB_INT_VSYNC) && !(s->int_status & FB_INT_VSYNC)) { + s->int_status |= FB_INT_VSYNC; + goldfish_device_set_irq(&s->dev, 0, 1); + } + + y_first = -1; + addr = base; + 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) + goldfish_device_set_irq(&s->dev, 0, 1); + } + s->need_int = 0; + s->need_update = 0; + } + + src_line = phys_ram_base + base; + dst_line = s->qfbuff->pixels; + pitch = s->qfbuff->pitch; + width = s->qfbuff->width; + height = s->qfbuff->height; + +#if STATS + if (full_update) + stats_full_updates += 1; + if (++stats_counter == 120) { + stats_total += stats_counter; + stats_total_full_updates += stats_full_updates; + + printf( "full update stats: peak %.2f %% total %.2f %%\n", + 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) + { + memset( dst_line, 0, height*pitch ); + y_first = 0; + y_last = height-1; + } + else if (full_update) + { + int yy; + + for (yy = 0; yy < height; yy++, dst_line += pitch, src_line += width*2) + { + uint16_t* src = (uint16_t*) src_line; + uint16_t* dst = (uint16_t*) dst_line; + int nn; + + for (nn = 0; nn < width; nn++) { + unsigned spix = src[nn]; + unsigned dpix = dst[nn]; +#if WORDS_BIGENDIAN + spix = ((spix << 8) | (spix >> 8)) & 0xffff; +#else + if (spix != dpix) + break; +#endif + } + + if (nn == width) + continue; + +#if WORDS_BIGENDIAN + for ( ; nn < width; nn++ ) { + unsigned spix = src[nn]; + dst[nn] = (uint16_t)((spix << 8) | (spix >> 8)); + } +#else + memcpy( dst+nn, src+nn, (width-nn)*2 ); +#endif + + y_first = (y_first < 0) ? yy : y_first; + y_last = yy; + } + } + else /* not a full update, should not happen very often with Android */ + { + int yy; + + for (yy = 0; yy < height; yy++, dst_line += pitch, src_line += width*2) + { + uint16_t* src = (uint16_t*) src_line; + uint16_t* dst = (uint16_t*) dst_line; + int len = width*2; +#if WORDS_BIGENDIAN + int nn; +#endif + int dirty = 0; + + while (len > 0) { + int len2 = TARGET_PAGE_SIZE - (addr & (TARGET_PAGE_SIZE-1)); + + if (len2 > len) + len2 = len; + + dirty |= cpu_physical_memory_get_dirty(addr, VGA_DIRTY_FLAG); + addr += len2; + len -= len2; + } + + if (!dirty) + continue; + +#if WORDS_BIGENDIAN + for (nn = 0; nn < width; nn++ ) { + unsigned spix = src[nn]; + dst[nn] = (uint16_t)((spix << 8) | (spix >> 8)); + } +#else + memcpy( dst, src, width*2 ); +#endif + + y_first = (y_first < 0) ? yy : y_first; + y_last = yy; + } + } + + if (y_first < 0) + return; + + y_last += 1; + //printf("goldfish_fb_update_display %d %d, base %x\n", first, last, base); + + cpu_physical_memory_reset_dirty(base + y_first * width * 2, + base + y_last * width * 2, + VGA_DIRTY_FLAG); + + qframebuffer_update( s->qfbuff, 0, y_first, width, y_last-y_first ); +} + +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 void goldfish_fb_detach_display(void* opaque) +{ + struct goldfish_fb_state *s = (struct goldfish_fb_state *)opaque; + s->qfbuff = NULL; +} + +static uint32_t goldfish_fb_read(void *opaque, target_phys_addr_t offset) +{ + uint32_t ret; + struct goldfish_fb_state *s = opaque; + offset -= s->dev.base; + switch(offset) { + case FB_GET_WIDTH: + ret = s->qfbuff->width; + //printf("FB_GET_WIDTH => %d\n", ret); + return ret; + + case FB_GET_HEIGHT: + ret = s->qfbuff->height; + //printf( "FB_GET_HEIGHT = %d\n", ret); + return ret; + + case FB_INT_STATUS: + ret = s->int_status & s->int_enable; + if(ret) { + s->int_status &= ~ret; + goldfish_device_set_irq(&s->dev, 0, 0); + } + return ret; + + case FB_GET_PHYS_WIDTH: + ret = s->qfbuff->phys_width_mm; + //printf( "FB_GET_PHYS_WIDTH => %d\n", ret ); + return ret; + + case FB_GET_PHYS_HEIGHT: + ret = s->qfbuff->phys_height_mm; + //printf( "FB_GET_PHYS_HEIGHT => %d\n", ret ); + return ret; + + default: + cpu_abort (cpu_single_env, "goldfish_fb_read: Bad offset %x\n", offset); + return 0; + } +} + +static void goldfish_fb_write(void *opaque, target_phys_addr_t offset, + uint32_t val) +{ + struct goldfish_fb_state *s = opaque; + offset -= s->dev.base; + switch(offset) { + case FB_INT_ENABLE: + s->int_enable = val; + goldfish_device_set_irq(&s->dev, 0, (s->int_status & s->int_enable)); + break; + case FB_SET_BASE: { + int need_resize = !s->base_valid; + 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; + need_resize = 1; + } + goldfish_device_set_irq(&s->dev, 0, (s->int_status & s->int_enable)); + if (need_resize) { + //printf("FB_SET_BASE: need resize (rotation=%d)\n", s->rotation ); + qframebuffer_rotate( s->qfbuff, s->rotation ); + } + } break; + case FB_SET_ROTATION: + //printf( "FB_SET_ROTATION %d\n", val); + s->set_rotation = val; + break; + case FB_SET_BLANK: + s->blank = val; + s->need_update = 1; + break; + default: + cpu_abort (cpu_single_env, "goldfish_fb_write: Bad offset %x\n", offset); + } +} + +static CPUReadMemoryFunc *goldfish_fb_readfn[] = { + goldfish_fb_read, + goldfish_fb_read, + goldfish_fb_read +}; + +static CPUWriteMemoryFunc *goldfish_fb_writefn[] = { + goldfish_fb_write, + goldfish_fb_write, + goldfish_fb_write +}; + +void goldfish_fb_init(DisplayState *ds, int id) +{ + struct goldfish_fb_state *s; + + s = (struct goldfish_fb_state *)qemu_mallocz(sizeof(*s)); + s->dev.name = "goldfish_fb"; + s->dev.id = id; + s->dev.size = 0x1000; + s->dev.irq_count = 1; + + s->qfbuff = qframebuffer_fifo_get(); + qframebuffer_add_producer( s->qfbuff, s, + goldfish_fb_update_display, + goldfish_fb_invalidate_display, + goldfish_fb_detach_display ); + + goldfish_device_add(&s->dev, goldfish_fb_readfn, goldfish_fb_writefn, s); + + register_savevm( "goldfish_fb", 0, GOLDFISH_FB_SAVE_VERSION, + goldfish_fb_save, goldfish_fb_load, s); +} + -- cgit v1.1