/* 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 "android/utils/debug.h" #include "android/utils/duff.h" #include "goldfish_device.h" #include "console.h" /* These values *must* match the platform definitions found under * hardware/libhardware/include/hardware/hardware.h */ enum { HAL_PIXEL_FORMAT_RGBA_8888 = 1, HAL_PIXEL_FORMAT_RGBX_8888 = 2, HAL_PIXEL_FORMAT_RGB_888 = 3, HAL_PIXEL_FORMAT_RGB_565 = 4, HAL_PIXEL_FORMAT_BGRA_8888 = 5, HAL_PIXEL_FORMAT_RGBA_5551 = 6, HAL_PIXEL_FORMAT_RGBA_4444 = 7, }; 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_GET_FORMAT = 0x24, FB_INT_VSYNC = 1U << 0, FB_INT_BASE_UPDATE_DONE = 1U << 1 }; struct goldfish_fb_state { struct goldfish_device dev; DisplayState* ds; int pixel_format; int bytes_per_pixel; 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; DisplayState* ds = s->ds; qemu_put_be32(f, ds->surface->width); qemu_put_be32(f, ds->surface->height); qemu_put_be32(f, ds->surface->linesize); 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); DisplayState* ds = s->ds; if (ds->surface->width != ds_w || ds->surface->height != ds_h || ds->surface->linesize != 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; } /* Type used to record a mapping from display surface pixel format to * HAL pixel format */ typedef struct { int pixel_format; /* HAL pixel format */ uint8_t bits; uint8_t bytes; uint32_t rmask, gmask, bmask, amask; } FbConfig; /* Return the pixel format of the current framebuffer, based on * the current display surface's pixel format. * * Note that you should not call this function from the device initialization * function, because the display surface will change format before the kernel * start. */ static int goldfish_fb_get_pixel_format(struct goldfish_fb_state *s) { if (s->pixel_format >= 0) { return s->pixel_format; } static const FbConfig fb_configs[] = { { HAL_PIXEL_FORMAT_RGB_565, 16, 2, 0xf800, 0x7e0, 0x1f, 0x0 }, { HAL_PIXEL_FORMAT_RGBX_8888, 32, 4, 0xff0000, 0xff00, 0xff, 0x0 }, { HAL_PIXEL_FORMAT_RGBA_8888, 32, 4, 0xff0000, 0xff00, 0xff, 0xff000000 }, { -1, } }; /* Determine HAL pixel format value based on s->ds */ struct PixelFormat* pf = &s->ds->surface->pf; if (VERBOSE_CHECK(init)) { printf("%s:%d: display surface,pixel format:\n", __FUNCTION__, __LINE__); printf(" bits/pixel: %d\n", pf->bits_per_pixel); printf(" bytes/pixel: %d\n", pf->bytes_per_pixel); printf(" depth: %d\n", pf->depth); printf(" red: bits=%d mask=0x%x shift=%d max=0x%x\n", pf->rbits, pf->rmask, pf->rshift, pf->rmax); printf(" green: bits=%d mask=0x%x shift=%d max=0x%x\n", pf->gbits, pf->gmask, pf->gshift, pf->gmax); printf(" blue: bits=%d mask=0x%x shift=%d max=0x%x\n", pf->bbits, pf->bmask, pf->bshift, pf->bmax); printf(" alpha: bits=%d mask=0x%x shift=%d max=0x%x\n", pf->abits, pf->amask, pf->ashift, pf->amax); } s->bytes_per_pixel = pf->bytes_per_pixel; int nn; for (nn = 0; fb_configs[nn].pixel_format >= 0; nn++) { const FbConfig* fbc = &fb_configs[nn]; if (pf->bits_per_pixel == fbc->bits && pf->bytes_per_pixel == fbc->bytes && pf->rmask == fbc->rmask && pf->gmask == fbc->gmask && pf->bmask == fbc->bmask && pf->amask == fbc->amask) { /* We found it */ s->pixel_format = fbc->pixel_format; return s->pixel_format; } } fprintf(stderr, "%s:%d: Unsupported display pixel format (depth=%d, bytespp=%d, bitspp=%d)\n", __FUNCTION__, __LINE__, pf->depth, pf->bytes_per_pixel, pf->bits_per_pixel); exit(1); return -1; } static int goldfish_fb_get_bytes_per_pixel(struct goldfish_fb_state *s) { if (s->pixel_format < 0) { (void) goldfish_fb_get_pixel_format(s); } return s->bytes_per_pixel; } 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 /* This structure is used to hold the inputs for * compute_fb_update_rect_linear below. * This corresponds to the source framebuffer and destination * surface pixel buffers. */ typedef struct { int width; int height; int bytes_per_pixel; const uint8_t* src_pixels; int src_pitch; uint8_t* dst_pixels; int dst_pitch; } FbUpdateState; /* This structure is used to hold the outputs for * compute_fb_update_rect_linear below. * This corresponds to the smalled bounding rectangle of the * latest framebuffer update. */ typedef struct { int xmin, ymin, xmax, ymax; } FbUpdateRect; /* Determine the smallest bounding rectangle of pixels which changed * between the source (framebuffer) and destination (surface) pixel * buffers. * * Return 0 if there was no change, otherwise, populate '*rect' * and return 1. * * If 'dirty_base' is not 0, it is a physical address that will be * used to speed-up the check using the VGA dirty bits. In practice * this is only used if your kernel driver does not implement. * * This function assumes that the framebuffers are in linear memory. * This may change later when we want to support larger framebuffers * that exceed the max DMA aperture size though. */ static int compute_fb_update_rect_linear(FbUpdateState* fbs, uint32_t dirty_base, FbUpdateRect* rect) { int yy; int width = fbs->width; const uint8_t* src_line = fbs->src_pixels; uint8_t* dst_line = fbs->dst_pixels; uint32_t dirty_addr = dirty_base; rect->xmin = rect->ymin = INT_MAX; rect->xmax = rect->ymax = INT_MIN; for (yy = 0; yy < fbs->height; yy++) { int xx1, xx2; /* If dirty_addr is != 0, then use it as a physical address to * use the VGA dirty bits table to speed up the detection of * changed pixels. */ if (dirty_addr != 0) { int dirty = 0; int len = fbs->src_pitch; while (len > 0) { int len2 = TARGET_PAGE_SIZE - (dirty_addr & (TARGET_PAGE_SIZE-1)); if (len2 > len) len2 = len; dirty |= cpu_physical_memory_get_dirty(dirty_addr, VGA_DIRTY_FLAG); dirty_addr += len2; len -= len2; } if (!dirty) { /* this line was not modified, skip to next one */ goto NEXT_LINE; } } /* Then compute actual bounds of the changed pixels, while * copying them from 'src' to 'dst'. This depends on the pixel depth. */ switch (fbs->bytes_per_pixel) { case 2: { const uint16_t* src = (const uint16_t*) src_line; uint16_t* dst = (uint16_t*) dst_line; xx1 = 0; DUFF4(width, { uint16_t spix = src[xx1]; #if defined(HOST_WORDS_BIGENDIAN) != defined(TARGET_WORDS_BIGENDIAN) spix = (uint16_t)((spix << 8) | (spix >> 8)); #endif if (spix != dst[xx1]) break; xx1++; }); if (xx1 == width) { break; } xx2 = width-1; DUFF4(xx2-xx1, { if (src[xx2] != dst[xx2]) break; xx2--; }); #if defined(HOST_WORDS_BIGENDIAN) != defined(TARGET_WORDS_BIGENDIAN) /* Convert the guest pixels into host ones */ int xx = xx1; DUFF4(xx2-xx1+1,{ unsigned spix = src[xx]; dst[xx] = (uint16_t)((spix << 8) | (spix >> 8)); xx++; }); #else memcpy( dst+xx1, src+xx1, (xx2-xx1+1)*2 ); #endif break; } case 3: { xx1 = 0; DUFF4(width, { int xx = xx1*3; if (src_line[xx+0] != dst_line[xx+0] || src_line[xx+1] != dst_line[xx+1] || src_line[xx+2] != dst_line[xx+2]) { break; } xx1 ++; }); if (xx1 == width) { break; } xx2 = width-1; DUFF4(xx2-xx1,{ int xx = xx2*3; if (src_line[xx+0] != dst_line[xx+0] || src_line[xx+1] != dst_line[xx+1] || src_line[xx+2] != dst_line[xx+2]) { break; } xx2--; }); memcpy( dst_line+xx1*3, src_line+xx1*3, (xx2-xx1+1)*3 ); break; } case 4: { const uint32_t* src = (const uint32_t*) src_line; uint32_t* dst = (uint32_t*) dst_line; xx1 = 0; DUFF4(width, { uint32_t spix = src[xx1]; #if defined(HOST_WORDS_BIGENDIAN) != defined(TARGET_WORDS_BIGENDIAN) spix = (spix << 16) | (spix >> 16); spix = ((spix << 8) & 0xff00ff00) | ((spix >> 8) & 0x00ff00ff); #endif if (spix != dst[xx1]) { break; } xx1++; }); if (xx1 == width) { break; } xx2 = width-1; DUFF4(xx2-xx1,{ if (src[xx2] != dst[xx2]) { break; } xx2--; }); #if defined(HOST_WORDS_BIGENDIAN) != defined(TARGET_WORDS_BIGENDIAN) /* Convert the guest pixels into host ones */ int xx = xx1; DUFF4(xx2-xx1+1,{ uint32_t spix = src[xx]; spix = (spix << 16) | (spix >> 16); spix = ((spix << 8) & 0xff00ff00) | ((spix >> 8) & 0x00ff00ff); dst[xx] = spix; xx++; }) #else memcpy( dst+xx1, src+xx1, (xx2-xx1+1)*4 ); #endif break; } default: return 0; } /* Update bounds if pixels on this line were modified */ if (xx1 < width) { if (xx1 < rect->xmin) rect->xmin = xx1; if (xx2 > rect->xmax) rect->xmax = xx2; if (yy < rect->ymin) rect->ymin = yy; if (yy > rect->ymax) rect->ymax = yy; } NEXT_LINE: src_line += fbs->src_pitch; dst_line += fbs->dst_pitch; } if (rect->ymin > rect->ymax) { /* nothing changed */ return 0; } /* Always clear the dirty VGA bits */ cpu_physical_memory_reset_dirty(dirty_base + rect->ymin * fbs->src_pitch, dirty_base + (rect->ymax+1)* fbs->src_pitch, VGA_DIRTY_FLAG); return 1; } static void goldfish_fb_update_display(void *opaque) { struct goldfish_fb_state *s = (struct goldfish_fb_state *)opaque; uint32_t base; uint8_t* dst_line; uint8_t* src_line; 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); } 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 = qemu_get_ram_ptr( base ); dst_line = s->ds->surface->data; pitch = s->ds->surface->linesize; width = s->ds->surface->width; height = s->ds->surface->height; FbUpdateState fbs; FbUpdateRect rect; fbs.width = width; fbs.height = height; fbs.dst_pixels = dst_line; fbs.dst_pitch = pitch; fbs.bytes_per_pixel = goldfish_fb_get_bytes_per_pixel(s); fbs.src_pixels = src_line; fbs.src_pitch = width*s->ds->surface->pf.bytes_per_pixel; #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 ); rect.xmin = 0; rect.ymin = 0; rect.xmax = width-1; rect.ymax = height-1; } else { if (full_update) { /* don't use dirty-bits optimization */ base = 0; } if (compute_fb_update_rect_linear(&fbs, base, &rect) == 0) { return; } } rect.xmax += 1; rect.ymax += 1; #if 0 printf("goldfish_fb_update_display (y:%d,h:%d,x=%d,w=%d)\n", rect.ymin, rect.ymax-rect.ymin, rect.xmin, rect.xmax-rect.xmin); #endif dpy_update(s->ds, rect.xmin, rect.ymin, rect.xmax-rect.xmin, rect.ymax-rect.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 uint32_t goldfish_fb_read(void *opaque, target_phys_addr_t offset) { uint32_t ret; struct goldfish_fb_state *s = opaque; switch(offset) { case FB_GET_WIDTH: ret = ds_get_width(s->ds); //printf("FB_GET_WIDTH => %d\n", ret); return ret; case FB_GET_HEIGHT: ret = ds_get_height(s->ds); //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 = pixels_to_mm( ds_get_width(s->ds), s->dpi ); //printf( "FB_GET_PHYS_WIDTH => %d\n", ret ); return ret; case FB_GET_PHYS_HEIGHT: ret = pixels_to_mm( ds_get_height(s->ds), s->dpi ); //printf( "FB_GET_PHYS_HEIGHT => %d\n", ret ); return ret; case FB_GET_FORMAT: return goldfish_fb_get_pixel_format(s); 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; 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 ); dpy_resize(s->ds); } } 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(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->ds = graphic_console_init(goldfish_fb_update_display, goldfish_fb_invalidate_display, NULL, NULL, s); s->dpi = 165; /* XXX: Find better way to get actual value ! */ /* IMPORTANT: DO NOT COMPUTE s->pixel_format and s->bytes_per_pixel * here because the display surface is going to change later. */ s->bytes_per_pixel = 0; s->pixel_format = -1; 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); }