blob: 8300e7c85de0f03c173926546452300e14eb473a [file] [log] [blame]
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -08001/* 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 "qemu_file.h"
13#include "android/android.h"
14#include "goldfish_device.h"
David 'Digit' Turner055ae422010-07-27 11:34:16 -070015#include "console.h"
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -080016
17enum {
18 FB_GET_WIDTH = 0x00,
19 FB_GET_HEIGHT = 0x04,
20 FB_INT_STATUS = 0x08,
21 FB_INT_ENABLE = 0x0c,
22 FB_SET_BASE = 0x10,
23 FB_SET_ROTATION = 0x14,
24 FB_SET_BLANK = 0x18,
25 FB_GET_PHYS_WIDTH = 0x1c,
26 FB_GET_PHYS_HEIGHT = 0x20,
27
28 FB_INT_VSYNC = 1U << 0,
29 FB_INT_BASE_UPDATE_DONE = 1U << 1
30};
31
32struct goldfish_fb_state {
33 struct goldfish_device dev;
David 'Digit' Turner055ae422010-07-27 11:34:16 -070034 DisplayState* ds;
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -080035 uint32_t fb_base;
36 uint32_t base_valid : 1;
37 uint32_t need_update : 1;
38 uint32_t need_int : 1;
39 uint32_t set_rotation : 2;
40 uint32_t blank : 1;
41 uint32_t int_status;
42 uint32_t int_enable;
43 int rotation; /* 0, 1, 2 or 3 */
David 'Digit' Turner055ae422010-07-27 11:34:16 -070044 int dpi;
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -080045};
46
David 'Digit' Turner055ae422010-07-27 11:34:16 -070047#define GOLDFISH_FB_SAVE_VERSION 2
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -080048
49static void goldfish_fb_save(QEMUFile* f, void* opaque)
50{
51 struct goldfish_fb_state* s = opaque;
52
David 'Digit' Turner055ae422010-07-27 11:34:16 -070053 DisplayState* ds = s->ds;
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -080054
David 'Digit' Turner055ae422010-07-27 11:34:16 -070055 qemu_put_be32(f, ds->surface->width);
56 qemu_put_be32(f, ds->surface->height);
57 qemu_put_be32(f, ds->surface->linesize);
58 qemu_put_byte(f, 0);
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -080059
60 qemu_put_be32(f, s->fb_base);
61 qemu_put_byte(f, s->base_valid);
62 qemu_put_byte(f, s->need_update);
63 qemu_put_byte(f, s->need_int);
64 qemu_put_byte(f, s->set_rotation);
65 qemu_put_byte(f, s->blank);
66 qemu_put_be32(f, s->int_status);
67 qemu_put_be32(f, s->int_enable);
68 qemu_put_be32(f, s->rotation);
David 'Digit' Turner055ae422010-07-27 11:34:16 -070069 qemu_put_be32(f, s->dpi);
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -080070}
71
72static int goldfish_fb_load(QEMUFile* f, void* opaque, int version_id)
73{
74 struct goldfish_fb_state* s = opaque;
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -080075 int ret = -1;
76 int ds_w, ds_h, ds_pitch, ds_rot;
77
78 if (version_id != GOLDFISH_FB_SAVE_VERSION)
79 goto Exit;
80
81 ds_w = qemu_get_be32(f);
82 ds_h = qemu_get_be32(f);
83 ds_pitch = qemu_get_be32(f);
84 ds_rot = qemu_get_byte(f);
85
David 'Digit' Turner055ae422010-07-27 11:34:16 -070086 DisplayState* ds = s->ds;
87
88 if (ds->surface->width != ds_w ||
89 ds->surface->height != ds_h ||
90 ds->surface->linesize != ds_pitch ||
91 ds_rot != 0)
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -080092 {
93 /* XXX: We should be able to force a resize/rotation from here ? */
94 fprintf(stderr, "%s: framebuffer dimensions mismatch\n", __FUNCTION__);
95 goto Exit;
96 }
97
98 s->fb_base = qemu_get_be32(f);
99 s->base_valid = qemu_get_byte(f);
100 s->need_update = qemu_get_byte(f);
101 s->need_int = qemu_get_byte(f);
102 s->set_rotation = qemu_get_byte(f);
103 s->blank = qemu_get_byte(f);
104 s->int_status = qemu_get_be32(f);
105 s->int_enable = qemu_get_be32(f);
106 s->rotation = qemu_get_be32(f);
David 'Digit' Turner055ae422010-07-27 11:34:16 -0700107 s->dpi = qemu_get_be32(f);
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800108
109 /* force a refresh */
110 s->need_update = 1;
111
112 ret = 0;
113Exit:
114 return ret;
115}
116
David 'Digit' Turner055ae422010-07-27 11:34:16 -0700117static int
118pixels_to_mm(int pixels, int dpi)
119{
120 /* dpi = dots / inch
121 ** inch = dots / dpi
122 ** mm / 25.4 = dots / dpi
123 ** mm = (dots * 25.4)/dpi
124 */
125 return (int)(0.5 + 25.4 * pixels / dpi);
126}
127
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800128
129#define STATS 0
130
131#if STATS
132static int stats_counter;
133static long stats_total;
134static int stats_full_updates;
135static long stats_total_full_updates;
136#endif
137
138static void goldfish_fb_update_display(void *opaque)
139{
140 struct goldfish_fb_state *s = (struct goldfish_fb_state *)opaque;
141 uint32_t addr;
142 uint32_t base;
143
144 uint8_t* dst_line;
145 uint8_t* src_line;
146 int y_first, y_last = 0;
147 int full_update = 0;
148 int width, height, pitch;
149
150 base = s->fb_base;
151 if(base == 0)
152 return;
153
154 if((s->int_enable & FB_INT_VSYNC) && !(s->int_status & FB_INT_VSYNC)) {
155 s->int_status |= FB_INT_VSYNC;
156 goldfish_device_set_irq(&s->dev, 0, 1);
157 }
158
159 y_first = -1;
160 addr = base;
161 if(s->need_update) {
162 full_update = 1;
163 if(s->need_int) {
164 s->int_status |= FB_INT_BASE_UPDATE_DONE;
165 if(s->int_enable & FB_INT_BASE_UPDATE_DONE)
166 goldfish_device_set_irq(&s->dev, 0, 1);
167 }
168 s->need_int = 0;
169 s->need_update = 0;
170 }
171
David 'Digit' Turner5d8f37a2009-09-14 14:32:27 -0700172 src_line = qemu_get_ram_ptr( base );
David 'Digit' Turner055ae422010-07-27 11:34:16 -0700173
174 dst_line = s->ds->surface->data;
175 pitch = s->ds->surface->linesize;
176 width = s->ds->surface->width;
177 height = s->ds->surface->height;
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800178
179#if STATS
180 if (full_update)
181 stats_full_updates += 1;
182 if (++stats_counter == 120) {
183 stats_total += stats_counter;
184 stats_total_full_updates += stats_full_updates;
185
186 printf( "full update stats: peak %.2f %% total %.2f %%\n",
187 stats_full_updates*100.0/stats_counter,
188 stats_total_full_updates*100.0/stats_total );
189
190 stats_counter = 0;
191 stats_full_updates = 0;
192 }
193#endif /* STATS */
194
195 if (s->blank)
196 {
197 memset( dst_line, 0, height*pitch );
198 y_first = 0;
199 y_last = height-1;
200 }
201 else if (full_update)
202 {
203 int yy;
204
205 for (yy = 0; yy < height; yy++, dst_line += pitch, src_line += width*2)
206 {
207 uint16_t* src = (uint16_t*) src_line;
208 uint16_t* dst = (uint16_t*) dst_line;
209 int nn;
210
211 for (nn = 0; nn < width; nn++) {
212 unsigned spix = src[nn];
213 unsigned dpix = dst[nn];
David 'Digit' Turner20894ae2010-05-10 17:07:36 -0700214#if HOST_WORDS_BIGENDIAN
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800215 spix = ((spix << 8) | (spix >> 8)) & 0xffff;
216#else
217 if (spix != dpix)
218 break;
219#endif
220 }
221
222 if (nn == width)
223 continue;
224
David 'Digit' Turner20894ae2010-05-10 17:07:36 -0700225#if HOST_WORDS_BIGENDIAN
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800226 for ( ; nn < width; nn++ ) {
227 unsigned spix = src[nn];
228 dst[nn] = (uint16_t)((spix << 8) | (spix >> 8));
229 }
230#else
231 memcpy( dst+nn, src+nn, (width-nn)*2 );
232#endif
233
234 y_first = (y_first < 0) ? yy : y_first;
235 y_last = yy;
236 }
237 }
238 else /* not a full update, should not happen very often with Android */
239 {
240 int yy;
241
242 for (yy = 0; yy < height; yy++, dst_line += pitch, src_line += width*2)
243 {
244 uint16_t* src = (uint16_t*) src_line;
245 uint16_t* dst = (uint16_t*) dst_line;
246 int len = width*2;
David 'Digit' Turner20894ae2010-05-10 17:07:36 -0700247#if HOST_WORDS_BIGENDIAN
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800248 int nn;
249#endif
250 int dirty = 0;
251
252 while (len > 0) {
253 int len2 = TARGET_PAGE_SIZE - (addr & (TARGET_PAGE_SIZE-1));
254
255 if (len2 > len)
256 len2 = len;
257
258 dirty |= cpu_physical_memory_get_dirty(addr, VGA_DIRTY_FLAG);
259 addr += len2;
260 len -= len2;
261 }
262
263 if (!dirty)
264 continue;
265
David 'Digit' Turner20894ae2010-05-10 17:07:36 -0700266#if HOST_WORDS_BIGENDIAN
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800267 for (nn = 0; nn < width; nn++ ) {
268 unsigned spix = src[nn];
269 dst[nn] = (uint16_t)((spix << 8) | (spix >> 8));
270 }
271#else
272 memcpy( dst, src, width*2 );
273#endif
274
275 y_first = (y_first < 0) ? yy : y_first;
276 y_last = yy;
277 }
278 }
279
280 if (y_first < 0)
281 return;
282
283 y_last += 1;
284 //printf("goldfish_fb_update_display %d %d, base %x\n", first, last, base);
285
286 cpu_physical_memory_reset_dirty(base + y_first * width * 2,
287 base + y_last * width * 2,
288 VGA_DIRTY_FLAG);
289
David 'Digit' Turner055ae422010-07-27 11:34:16 -0700290 dpy_update(s->ds, 0, y_first, width, y_last-y_first);
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800291}
292
293static void goldfish_fb_invalidate_display(void * opaque)
294{
295 // is this called?
296 struct goldfish_fb_state *s = (struct goldfish_fb_state *)opaque;
297 s->need_update = 1;
298}
299
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800300static uint32_t goldfish_fb_read(void *opaque, target_phys_addr_t offset)
301{
302 uint32_t ret;
303 struct goldfish_fb_state *s = opaque;
David 'Digit' Turner5d8f37a2009-09-14 14:32:27 -0700304
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800305 switch(offset) {
306 case FB_GET_WIDTH:
David 'Digit' Turner055ae422010-07-27 11:34:16 -0700307 ret = ds_get_width(s->ds);
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800308 //printf("FB_GET_WIDTH => %d\n", ret);
309 return ret;
310
311 case FB_GET_HEIGHT:
David 'Digit' Turner055ae422010-07-27 11:34:16 -0700312 ret = ds_get_height(s->ds);
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800313 //printf( "FB_GET_HEIGHT = %d\n", ret);
314 return ret;
315
316 case FB_INT_STATUS:
317 ret = s->int_status & s->int_enable;
318 if(ret) {
319 s->int_status &= ~ret;
320 goldfish_device_set_irq(&s->dev, 0, 0);
321 }
322 return ret;
323
324 case FB_GET_PHYS_WIDTH:
David 'Digit' Turner055ae422010-07-27 11:34:16 -0700325 ret = pixels_to_mm( ds_get_width(s->ds), s->dpi );
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800326 //printf( "FB_GET_PHYS_WIDTH => %d\n", ret );
327 return ret;
328
329 case FB_GET_PHYS_HEIGHT:
David 'Digit' Turner055ae422010-07-27 11:34:16 -0700330 ret = pixels_to_mm( ds_get_height(s->ds), s->dpi );
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800331 //printf( "FB_GET_PHYS_HEIGHT => %d\n", ret );
332 return ret;
333
334 default:
335 cpu_abort (cpu_single_env, "goldfish_fb_read: Bad offset %x\n", offset);
336 return 0;
337 }
338}
339
340static void goldfish_fb_write(void *opaque, target_phys_addr_t offset,
341 uint32_t val)
342{
343 struct goldfish_fb_state *s = opaque;
David 'Digit' Turner5d8f37a2009-09-14 14:32:27 -0700344
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800345 switch(offset) {
346 case FB_INT_ENABLE:
347 s->int_enable = val;
348 goldfish_device_set_irq(&s->dev, 0, (s->int_status & s->int_enable));
349 break;
350 case FB_SET_BASE: {
351 int need_resize = !s->base_valid;
352 s->fb_base = val;
353 s->int_status &= ~FB_INT_BASE_UPDATE_DONE;
354 s->need_update = 1;
355 s->need_int = 1;
356 s->base_valid = 1;
357 if(s->set_rotation != s->rotation) {
358 //printf("FB_SET_BASE: rotation : %d => %d\n", s->rotation, s->set_rotation);
359 s->rotation = s->set_rotation;
360 need_resize = 1;
361 }
362 goldfish_device_set_irq(&s->dev, 0, (s->int_status & s->int_enable));
363 if (need_resize) {
364 //printf("FB_SET_BASE: need resize (rotation=%d)\n", s->rotation );
David 'Digit' Turner055ae422010-07-27 11:34:16 -0700365 dpy_resize(s->ds);
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800366 }
367 } break;
368 case FB_SET_ROTATION:
369 //printf( "FB_SET_ROTATION %d\n", val);
370 s->set_rotation = val;
371 break;
372 case FB_SET_BLANK:
373 s->blank = val;
374 s->need_update = 1;
375 break;
376 default:
377 cpu_abort (cpu_single_env, "goldfish_fb_write: Bad offset %x\n", offset);
378 }
379}
380
381static CPUReadMemoryFunc *goldfish_fb_readfn[] = {
382 goldfish_fb_read,
383 goldfish_fb_read,
384 goldfish_fb_read
385};
386
387static CPUWriteMemoryFunc *goldfish_fb_writefn[] = {
388 goldfish_fb_write,
389 goldfish_fb_write,
390 goldfish_fb_write
391};
392
David 'Digit' Turner055ae422010-07-27 11:34:16 -0700393void goldfish_fb_init(int id)
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800394{
395 struct goldfish_fb_state *s;
396
397 s = (struct goldfish_fb_state *)qemu_mallocz(sizeof(*s));
398 s->dev.name = "goldfish_fb";
399 s->dev.id = id;
400 s->dev.size = 0x1000;
401 s->dev.irq_count = 1;
402
David 'Digit' Turner055ae422010-07-27 11:34:16 -0700403 s->ds = graphic_console_init(goldfish_fb_update_display,
404 goldfish_fb_invalidate_display,
405 NULL,
406 NULL,
407 s);
408
409 s->dpi = 165; /* XXX: Find better way to get actual value ! */
The Android Open Source Project8b23a6c2009-03-03 19:30:32 -0800410
411 goldfish_device_add(&s->dev, goldfish_fb_readfn, goldfish_fb_writefn, s);
412
413 register_savevm( "goldfish_fb", 0, GOLDFISH_FB_SAVE_VERSION,
414 goldfish_fb_save, goldfish_fb_load, s);
415}