Home | History | Annotate | Download | only in goldfish
      1 /* Copyright (C) 2007-2008 The Android Open Source Project
      2 **
      3 ** This software is licensed under the terms of the GNU General Public
      4 ** License version 2, as published by the Free Software Foundation, and
      5 ** may be copied, distributed, and modified under those terms.
      6 **
      7 ** This program is distributed in the hope that it will be useful,
      8 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
      9 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     10 ** GNU General Public License for more details.
     11 */
     12 #include "cpu.h"
     13 #include "migration/qemu-file.h"
     14 #include "android/android.h"
     15 #include "android/utils/debug.h"
     16 #include "android/utils/duff.h"
     17 #include "hw/android/goldfish/device.h"
     18 #include "hw/hw.h"
     19 #include "ui/console.h"
     20 
     21 /* These values *must* match the platform definitions found under
     22  * hardware/libhardware/include/hardware/hardware.h
     23  */
     24 enum {
     25     HAL_PIXEL_FORMAT_RGBA_8888          = 1,
     26     HAL_PIXEL_FORMAT_RGBX_8888          = 2,
     27     HAL_PIXEL_FORMAT_RGB_888            = 3,
     28     HAL_PIXEL_FORMAT_RGB_565            = 4,
     29     HAL_PIXEL_FORMAT_BGRA_8888          = 5,
     30     HAL_PIXEL_FORMAT_RGBA_5551          = 6,
     31     HAL_PIXEL_FORMAT_RGBA_4444          = 7,
     32 };
     33 
     34 enum {
     35     FB_GET_WIDTH        = 0x00,
     36     FB_GET_HEIGHT       = 0x04,
     37     FB_INT_STATUS       = 0x08,
     38     FB_INT_ENABLE       = 0x0c,
     39     FB_SET_BASE         = 0x10,
     40     FB_SET_ROTATION     = 0x14,
     41     FB_SET_BLANK        = 0x18,
     42     FB_GET_PHYS_WIDTH   = 0x1c,
     43     FB_GET_PHYS_HEIGHT  = 0x20,
     44     FB_GET_FORMAT       = 0x24,
     45 
     46     FB_INT_VSYNC             = 1U << 0,
     47     FB_INT_BASE_UPDATE_DONE  = 1U << 1
     48 };
     49 
     50 struct goldfish_fb_state {
     51     struct goldfish_device dev;
     52     DisplayState*  ds;
     53     int      pixel_format;
     54     int      bytes_per_pixel;
     55     uint32_t fb_base;
     56     uint32_t base_valid : 1;
     57     uint32_t need_update : 1;
     58     uint32_t need_int : 1;
     59     uint32_t set_rotation : 2;
     60     uint32_t blank : 1;
     61     uint32_t int_status;
     62     uint32_t int_enable;
     63     int      rotation;   /* 0, 1, 2 or 3 */
     64     int      dpi;
     65 };
     66 
     67 #define  GOLDFISH_FB_SAVE_VERSION  2
     68 
     69 static void goldfish_fb_save(QEMUFile*  f, void*  opaque)
     70 {
     71     struct goldfish_fb_state*  s = opaque;
     72 
     73     DisplayState*  ds = s->ds;
     74 
     75     qemu_put_be32(f, ds->surface->width);
     76     qemu_put_be32(f, ds->surface->height);
     77     qemu_put_be32(f, ds->surface->linesize);
     78     qemu_put_byte(f, 0);
     79 
     80     qemu_put_be32(f, s->fb_base);
     81     qemu_put_byte(f, s->base_valid);
     82     qemu_put_byte(f, s->need_update);
     83     qemu_put_byte(f, s->need_int);
     84     qemu_put_byte(f, s->set_rotation);
     85     qemu_put_byte(f, s->blank);
     86     qemu_put_be32(f, s->int_status);
     87     qemu_put_be32(f, s->int_enable);
     88     qemu_put_be32(f, s->rotation);
     89     qemu_put_be32(f, s->dpi);
     90 }
     91 
     92 static int  goldfish_fb_load(QEMUFile*  f, void*  opaque, int  version_id)
     93 {
     94     struct goldfish_fb_state*  s   = opaque;
     95     int                        ret = -1;
     96     int                        ds_w, ds_h, ds_pitch, ds_rot;
     97 
     98     if (version_id != GOLDFISH_FB_SAVE_VERSION)
     99         goto Exit;
    100 
    101     ds_w     = qemu_get_be32(f);
    102     ds_h     = qemu_get_be32(f);
    103     ds_pitch = qemu_get_be32(f);
    104     ds_rot   = qemu_get_byte(f);
    105 
    106     DisplayState*  ds = s->ds;
    107 
    108     if (ds->surface->width != ds_w ||
    109         ds->surface->height != ds_h ||
    110         ds->surface->linesize != ds_pitch ||
    111         ds_rot != 0)
    112     {
    113         /* XXX: We should be able to force a resize/rotation from here ? */
    114         fprintf(stderr, "%s: framebuffer dimensions mismatch\n", __FUNCTION__);
    115         goto Exit;
    116     }
    117 
    118     s->fb_base      = qemu_get_be32(f);
    119     s->base_valid   = qemu_get_byte(f);
    120     s->need_update  = qemu_get_byte(f);
    121     s->need_int     = qemu_get_byte(f);
    122     s->set_rotation = qemu_get_byte(f);
    123     s->blank        = qemu_get_byte(f);
    124     s->int_status   = qemu_get_be32(f);
    125     s->int_enable   = qemu_get_be32(f);
    126     s->rotation     = qemu_get_be32(f);
    127     s->dpi          = qemu_get_be32(f);
    128 
    129     /* force a refresh */
    130     s->need_update = 1;
    131 
    132     ret = 0;
    133 Exit:
    134     return ret;
    135 }
    136 
    137 /* Type used to record a mapping from display surface pixel format to
    138  * HAL pixel format */
    139 typedef struct {
    140     int    pixel_format; /* HAL pixel format */
    141     uint8_t bits;
    142     uint8_t bytes;
    143     uint32_t rmask, gmask, bmask, amask;
    144 } FbConfig;
    145 
    146 
    147 /* Return the pixel format of the current framebuffer, based on
    148  * the current display surface's pixel format.
    149  *
    150  * Note that you should not call this function from the device initialization
    151  * function, because the display surface will change format before the kernel
    152  * start.
    153  */
    154 static int goldfish_fb_get_pixel_format(struct goldfish_fb_state *s)
    155 {
    156     if (s->pixel_format >= 0) {
    157         return s->pixel_format;
    158     }
    159     static const FbConfig fb_configs[] = {
    160         { HAL_PIXEL_FORMAT_RGB_565, 16, 2, 0xf800, 0x7e0, 0x1f, 0x0 },
    161         { HAL_PIXEL_FORMAT_RGBX_8888, 32, 4, 0xff0000, 0xff00, 0xff, 0x0 },
    162         { HAL_PIXEL_FORMAT_RGBA_8888, 32, 4, 0xff0000, 0xff00, 0xff, 0xff000000 },
    163         { -1, }
    164     };
    165 
    166     /* Determine HAL pixel format value based on s->ds */
    167     struct PixelFormat* pf = &s->ds->surface->pf;
    168     if (VERBOSE_CHECK(init)) {
    169         printf("%s:%d: display surface,pixel format:\n", __FUNCTION__, __LINE__);
    170         printf("  bits/pixel:  %d\n", pf->bits_per_pixel);
    171         printf("  bytes/pixel: %d\n", pf->bytes_per_pixel);
    172         printf("  depth:       %d\n", pf->depth);
    173         printf("  red:         bits=%d mask=0x%x shift=%d max=0x%x\n",
    174             pf->rbits, pf->rmask, pf->rshift, pf->rmax);
    175         printf("  green:       bits=%d mask=0x%x shift=%d max=0x%x\n",
    176             pf->gbits, pf->gmask, pf->gshift, pf->gmax);
    177         printf("  blue:        bits=%d mask=0x%x shift=%d max=0x%x\n",
    178             pf->bbits, pf->bmask, pf->bshift, pf->bmax);
    179         printf("  alpha:       bits=%d mask=0x%x shift=%d max=0x%x\n",
    180             pf->abits, pf->amask, pf->ashift, pf->amax);
    181     }
    182 
    183     s->bytes_per_pixel = pf->bytes_per_pixel;
    184     int nn;
    185     for (nn = 0; fb_configs[nn].pixel_format >= 0; nn++) {
    186         const FbConfig* fbc = &fb_configs[nn];
    187         if (pf->bits_per_pixel == fbc->bits &&
    188             pf->bytes_per_pixel == fbc->bytes &&
    189             pf->rmask == fbc->rmask &&
    190             pf->gmask == fbc->gmask &&
    191             pf->bmask == fbc->bmask &&
    192             pf->amask == fbc->amask) {
    193             /* We found it */
    194             s->pixel_format = fbc->pixel_format;
    195             return s->pixel_format;
    196         }
    197     }
    198     fprintf(stderr, "%s:%d: Unsupported display pixel format (depth=%d, bytespp=%d, bitspp=%d)\n",
    199                 __FUNCTION__, __LINE__,
    200                 pf->depth,
    201                 pf->bytes_per_pixel,
    202                 pf->bits_per_pixel);
    203     exit(1);
    204     return -1;
    205 }
    206 
    207 static int goldfish_fb_get_bytes_per_pixel(struct goldfish_fb_state *s)
    208 {
    209     if (s->pixel_format < 0) {
    210         (void) goldfish_fb_get_pixel_format(s);
    211     }
    212     return s->bytes_per_pixel;
    213 }
    214 
    215 static int
    216 pixels_to_mm(int  pixels, int dpi)
    217 {
    218     /* dpi = dots / inch
    219     ** inch = dots / dpi
    220     ** mm / 25.4 = dots / dpi
    221     ** mm = (dots * 25.4)/dpi
    222     */
    223     return (int)(0.5 + 25.4 * pixels  / dpi);
    224 }
    225 
    226 
    227 #define  STATS  0
    228 
    229 #if STATS
    230 static int   stats_counter;
    231 static long  stats_total;
    232 static int   stats_full_updates;
    233 static long  stats_total_full_updates;
    234 #endif
    235 
    236 /* This structure is used to hold the inputs for
    237  * compute_fb_update_rect_linear below.
    238  * This corresponds to the source framebuffer and destination
    239  * surface pixel buffers.
    240  */
    241 typedef struct {
    242     int            width;
    243     int            height;
    244     int            bytes_per_pixel;
    245     const uint8_t* src_pixels;
    246     int            src_pitch;
    247     uint8_t*       dst_pixels;
    248     int            dst_pitch;
    249 } FbUpdateState;
    250 
    251 /* This structure is used to hold the outputs for
    252  * compute_fb_update_rect_linear below.
    253  * This corresponds to the smalled bounding rectangle of the
    254  * latest framebuffer update.
    255  */
    256 typedef struct {
    257     int xmin, ymin, xmax, ymax;
    258 } FbUpdateRect;
    259 
    260 /* Determine the smallest bounding rectangle of pixels which changed
    261  * between the source (framebuffer) and destination (surface) pixel
    262  * buffers.
    263  *
    264  * Return 0 if there was no change, otherwise, populate '*rect'
    265  * and return 1.
    266  *
    267  * If 'dirty_base' is not 0, it is a physical address that will be
    268  * used to speed-up the check using the VGA dirty bits. In practice
    269  * this is only used if your kernel driver does not implement.
    270  *
    271  * This function assumes that the framebuffers are in linear memory.
    272  * This may change later when we want to support larger framebuffers
    273  * that exceed the max DMA aperture size though.
    274  */
    275 static int
    276 compute_fb_update_rect_linear(FbUpdateState*  fbs,
    277                               uint32_t        dirty_base,
    278                               FbUpdateRect*   rect)
    279 {
    280     int  yy;
    281     int  width = fbs->width;
    282     const uint8_t* src_line = fbs->src_pixels;
    283     uint8_t*       dst_line = fbs->dst_pixels;
    284     uint32_t       dirty_addr = dirty_base;
    285     rect->xmin = rect->ymin = INT_MAX;
    286     rect->xmax = rect->ymax = INT_MIN;
    287     for (yy = 0; yy < fbs->height; yy++) {
    288         int xx1, xx2;
    289         /* If dirty_addr is != 0, then use it as a physical address to
    290          * use the VGA dirty bits table to speed up the detection of
    291          * changed pixels.
    292          */
    293         if (dirty_addr != 0) {
    294             int  dirty = 0;
    295             int  len   = fbs->src_pitch;
    296 
    297             while (len > 0) {
    298                 int  len2 = TARGET_PAGE_SIZE - (dirty_addr & (TARGET_PAGE_SIZE-1));
    299 
    300                 if (len2 > len)
    301                     len2 = len;
    302 
    303                 dirty |= cpu_physical_memory_get_dirty(dirty_addr, VGA_DIRTY_FLAG);
    304                 dirty_addr  += len2;
    305                 len         -= len2;
    306             }
    307 
    308             if (!dirty) { /* this line was not modified, skip to next one */
    309                 goto NEXT_LINE;
    310             }
    311         }
    312 
    313         /* Then compute actual bounds of the changed pixels, while
    314          * copying them from 'src' to 'dst'. This depends on the pixel depth.
    315          */
    316         switch (fbs->bytes_per_pixel) {
    317         case 2:
    318         {
    319             const uint16_t* src = (const uint16_t*) src_line;
    320             uint16_t*       dst = (uint16_t*) dst_line;
    321 
    322             xx1 = 0;
    323             DUFF4(width, {
    324                 uint16_t spix = src[xx1];
    325 #if defined(HOST_WORDS_BIGENDIAN) != defined(TARGET_WORDS_BIGENDIAN)
    326                 spix = (uint16_t)((spix << 8) | (spix >> 8));
    327 #endif
    328                 if (spix != dst[xx1])
    329                     break;
    330                 xx1++;
    331             });
    332             if (xx1 == width) {
    333                 break;
    334             }
    335             xx2 = width-1;
    336             DUFF4(xx2-xx1, {
    337                 if (src[xx2] != dst[xx2])
    338                     break;
    339                 xx2--;
    340             });
    341 #if defined(HOST_WORDS_BIGENDIAN) != defined(TARGET_WORDS_BIGENDIAN)
    342             /* Convert the guest pixels into host ones */
    343             int xx = xx1;
    344             DUFF4(xx2-xx1+1,{
    345                 unsigned   spix = src[xx];
    346                 dst[xx] = (uint16_t)((spix << 8) | (spix >> 8));
    347                 xx++;
    348             });
    349 #else
    350             memcpy( dst+xx1, src+xx1, (xx2-xx1+1)*2 );
    351 #endif
    352             break;
    353         }
    354 
    355         case 3:
    356         {
    357             xx1 = 0;
    358             DUFF4(width, {
    359                 int xx = xx1*3;
    360                 if (src_line[xx+0] != dst_line[xx+0] ||
    361                     src_line[xx+1] != dst_line[xx+1] ||
    362                     src_line[xx+2] != dst_line[xx+2]) {
    363                     break;
    364                 }
    365                 xx1 ++;
    366             });
    367             if (xx1 == width) {
    368                 break;
    369             }
    370             xx2 = width-1;
    371             DUFF4(xx2-xx1,{
    372                 int xx = xx2*3;
    373                 if (src_line[xx+0] != dst_line[xx+0] ||
    374                     src_line[xx+1] != dst_line[xx+1] ||
    375                     src_line[xx+2] != dst_line[xx+2]) {
    376                     break;
    377                 }
    378                 xx2--;
    379             });
    380             memcpy( dst_line+xx1*3, src_line+xx1*3, (xx2-xx1+1)*3 );
    381             break;
    382         }
    383 
    384         case 4:
    385         {
    386             const uint32_t* src = (const uint32_t*) src_line;
    387             uint32_t*       dst = (uint32_t*) dst_line;
    388 
    389             xx1 = 0;
    390             DUFF4(width, {
    391                 uint32_t spix = src[xx1];
    392 #if defined(HOST_WORDS_BIGENDIAN) != defined(TARGET_WORDS_BIGENDIAN)
    393                 spix = (spix << 16) | (spix >> 16);
    394                 spix = ((spix << 8) & 0xff00ff00) | ((spix >> 8) & 0x00ff00ff);
    395 #endif
    396                 if (spix != dst[xx1]) {
    397                     break;
    398                 }
    399                 xx1++;
    400             });
    401             if (xx1 == width) {
    402                 break;
    403             }
    404             xx2 = width-1;
    405             DUFF4(xx2-xx1,{
    406                 if (src[xx2] != dst[xx2]) {
    407                     break;
    408                 }
    409                 xx2--;
    410             });
    411 #if defined(HOST_WORDS_BIGENDIAN) != defined(TARGET_WORDS_BIGENDIAN)
    412             /* Convert the guest pixels into host ones */
    413             int xx = xx1;
    414             DUFF4(xx2-xx1+1,{
    415                 uint32_t   spix = src[xx];
    416                 spix = (spix << 16) | (spix >> 16);
    417                 spix = ((spix << 8) & 0xff00ff00) | ((spix >> 8) & 0x00ff00ff);
    418                 dst[xx] = spix;
    419                 xx++;
    420             })
    421 #else
    422             memcpy( dst+xx1, src+xx1, (xx2-xx1+1)*4 );
    423 #endif
    424             break;
    425         }
    426         default:
    427             return 0;
    428         }
    429         /* Update bounds if pixels on this line were modified */
    430         if (xx1 < width) {
    431             if (xx1 < rect->xmin) rect->xmin = xx1;
    432             if (xx2 > rect->xmax) rect->xmax = xx2;
    433             if (yy < rect->ymin) rect->ymin = yy;
    434             if (yy > rect->ymax) rect->ymax = yy;
    435         }
    436     NEXT_LINE:
    437         src_line += fbs->src_pitch;
    438         dst_line += fbs->dst_pitch;
    439     }
    440 
    441     if (rect->ymin > rect->ymax) { /* nothing changed */
    442         return 0;
    443     }
    444 
    445     /* Always clear the dirty VGA bits */
    446     cpu_physical_memory_reset_dirty(dirty_base + rect->ymin * fbs->src_pitch,
    447                                     dirty_base + (rect->ymax+1)* fbs->src_pitch,
    448                                     VGA_DIRTY_FLAG);
    449     return 1;
    450 }
    451 
    452 
    453 static void goldfish_fb_update_display(void *opaque)
    454 {
    455     struct goldfish_fb_state *s = (struct goldfish_fb_state *)opaque;
    456     uint32_t base;
    457     uint8_t*  dst_line;
    458     uint8_t*  src_line;
    459     int full_update = 0;
    460     int  width, height, pitch;
    461 
    462     base = s->fb_base;
    463     if(base == 0)
    464         return;
    465 
    466     if((s->int_enable & FB_INT_VSYNC) && !(s->int_status & FB_INT_VSYNC)) {
    467         s->int_status |= FB_INT_VSYNC;
    468         goldfish_device_set_irq(&s->dev, 0, 1);
    469     }
    470 
    471     if(s->need_update) {
    472         full_update = 1;
    473         if(s->need_int) {
    474             s->int_status |= FB_INT_BASE_UPDATE_DONE;
    475             if(s->int_enable & FB_INT_BASE_UPDATE_DONE)
    476                 goldfish_device_set_irq(&s->dev, 0, 1);
    477         }
    478         s->need_int = 0;
    479         s->need_update = 0;
    480     }
    481 
    482     src_line  = qemu_get_ram_ptr( base );
    483 
    484     dst_line  = s->ds->surface->data;
    485     pitch     = s->ds->surface->linesize;
    486     width     = s->ds->surface->width;
    487     height    = s->ds->surface->height;
    488 
    489     FbUpdateState  fbs;
    490     FbUpdateRect   rect;
    491 
    492     fbs.width      = width;
    493     fbs.height     = height;
    494     fbs.dst_pixels = dst_line;
    495     fbs.dst_pitch  = pitch;
    496     fbs.bytes_per_pixel = goldfish_fb_get_bytes_per_pixel(s);
    497 
    498     fbs.src_pixels = src_line;
    499     fbs.src_pitch  = width*s->ds->surface->pf.bytes_per_pixel;
    500 
    501 
    502 #if STATS
    503     if (full_update)
    504         stats_full_updates += 1;
    505     if (++stats_counter == 120) {
    506         stats_total               += stats_counter;
    507         stats_total_full_updates  += stats_full_updates;
    508 
    509         printf( "full update stats:  peak %.2f %%  total %.2f %%\n",
    510                 stats_full_updates*100.0/stats_counter,
    511                 stats_total_full_updates*100.0/stats_total );
    512 
    513         stats_counter      = 0;
    514         stats_full_updates = 0;
    515     }
    516 #endif /* STATS */
    517 
    518     if (s->blank)
    519     {
    520         memset( dst_line, 0, height*pitch );
    521         rect.xmin = 0;
    522         rect.ymin = 0;
    523         rect.xmax = width-1;
    524         rect.ymax = height-1;
    525     }
    526     else
    527     {
    528         if (full_update) { /* don't use dirty-bits optimization */
    529             base = 0;
    530         }
    531         if (compute_fb_update_rect_linear(&fbs, base, &rect) == 0) {
    532             return;
    533         }
    534     }
    535 
    536     rect.xmax += 1;
    537     rect.ymax += 1;
    538 #if 0
    539     printf("goldfish_fb_update_display (y:%d,h:%d,x=%d,w=%d)\n",
    540            rect.ymin, rect.ymax-rect.ymin, rect.xmin, rect.xmax-rect.xmin);
    541 #endif
    542 
    543     dpy_update(s->ds, rect.xmin, rect.ymin, rect.xmax-rect.xmin, rect.ymax-rect.ymin);
    544 }
    545 
    546 static void goldfish_fb_invalidate_display(void * opaque)
    547 {
    548     // is this called?
    549     struct goldfish_fb_state *s = (struct goldfish_fb_state *)opaque;
    550     s->need_update = 1;
    551 }
    552 
    553 static uint32_t goldfish_fb_read(void *opaque, hwaddr offset)
    554 {
    555     uint32_t ret;
    556     struct goldfish_fb_state *s = opaque;
    557 
    558     switch(offset) {
    559         case FB_GET_WIDTH:
    560             ret = ds_get_width(s->ds);
    561             //printf("FB_GET_WIDTH => %d\n", ret);
    562             return ret;
    563 
    564         case FB_GET_HEIGHT:
    565             ret = ds_get_height(s->ds);
    566             //printf( "FB_GET_HEIGHT = %d\n", ret);
    567             return ret;
    568 
    569         case FB_INT_STATUS:
    570             ret = s->int_status & s->int_enable;
    571             if(ret) {
    572                 s->int_status &= ~ret;
    573                 goldfish_device_set_irq(&s->dev, 0, 0);
    574             }
    575             return ret;
    576 
    577         case FB_GET_PHYS_WIDTH:
    578             ret = pixels_to_mm( ds_get_width(s->ds), s->dpi );
    579             //printf( "FB_GET_PHYS_WIDTH => %d\n", ret );
    580             return ret;
    581 
    582         case FB_GET_PHYS_HEIGHT:
    583             ret = pixels_to_mm( ds_get_height(s->ds), s->dpi );
    584             //printf( "FB_GET_PHYS_HEIGHT => %d\n", ret );
    585             return ret;
    586 
    587         case FB_GET_FORMAT:
    588             return goldfish_fb_get_pixel_format(s);
    589 
    590         default:
    591             cpu_abort (cpu_single_env, "goldfish_fb_read: Bad offset %x\n", offset);
    592             return 0;
    593     }
    594 }
    595 
    596 static void goldfish_fb_write(void *opaque, hwaddr offset,
    597                         uint32_t val)
    598 {
    599     struct goldfish_fb_state *s = opaque;
    600 
    601     switch(offset) {
    602         case FB_INT_ENABLE:
    603             s->int_enable = val;
    604             goldfish_device_set_irq(&s->dev, 0, (s->int_status & s->int_enable));
    605             break;
    606         case FB_SET_BASE: {
    607             int need_resize = !s->base_valid;
    608             s->fb_base = val;
    609             s->int_status &= ~FB_INT_BASE_UPDATE_DONE;
    610             s->need_update = 1;
    611             s->need_int = 1;
    612             s->base_valid = 1;
    613             if(s->set_rotation != s->rotation) {
    614                 //printf("FB_SET_BASE: rotation : %d => %d\n", s->rotation, s->set_rotation);
    615                 s->rotation = s->set_rotation;
    616                 need_resize = 1;
    617             }
    618             goldfish_device_set_irq(&s->dev, 0, (s->int_status & s->int_enable));
    619             if (need_resize) {
    620                 //printf("FB_SET_BASE: need resize (rotation=%d)\n", s->rotation );
    621                 dpy_resize(s->ds);
    622             }
    623             } break;
    624         case FB_SET_ROTATION:
    625             //printf( "FB_SET_ROTATION %d\n", val);
    626             s->set_rotation = val;
    627             break;
    628         case FB_SET_BLANK:
    629             s->blank = val;
    630             s->need_update = 1;
    631             break;
    632         default:
    633             cpu_abort (cpu_single_env, "goldfish_fb_write: Bad offset %x\n", offset);
    634     }
    635 }
    636 
    637 static CPUReadMemoryFunc *goldfish_fb_readfn[] = {
    638    goldfish_fb_read,
    639    goldfish_fb_read,
    640    goldfish_fb_read
    641 };
    642 
    643 static CPUWriteMemoryFunc *goldfish_fb_writefn[] = {
    644    goldfish_fb_write,
    645    goldfish_fb_write,
    646    goldfish_fb_write
    647 };
    648 
    649 void goldfish_fb_init(int id)
    650 {
    651     struct goldfish_fb_state *s;
    652 
    653     s = (struct goldfish_fb_state *)g_malloc0(sizeof(*s));
    654     s->dev.name = "goldfish_fb";
    655     s->dev.id = id;
    656     s->dev.size = 0x1000;
    657     s->dev.irq_count = 1;
    658 
    659     s->ds = graphic_console_init(goldfish_fb_update_display,
    660                                  goldfish_fb_invalidate_display,
    661                                  NULL,
    662                                  NULL,
    663                                  s);
    664 
    665     s->dpi = 165;  /* XXX: Find better way to get actual value ! */
    666 
    667     /* IMPORTANT: DO NOT COMPUTE s->pixel_format and s->bytes_per_pixel
    668      * here because the display surface is going to change later.
    669      */
    670     s->bytes_per_pixel = 0;
    671     s->pixel_format    = -1;
    672 
    673     goldfish_device_add(&s->dev, goldfish_fb_readfn, goldfish_fb_writefn, s);
    674 
    675     register_savevm(NULL,
    676                     "goldfish_fb",
    677                     0,
    678                     GOLDFISH_FB_SAVE_VERSION,
    679                     goldfish_fb_save,
    680                     goldfish_fb_load,
    681                     s);
    682 }
    683