/* 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 "qemu_file.h" #include "android/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_set_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); }