/*
* Copyright 2007 The Android Open Source Project
*
* Fake device support.
*/
#include "Common.h"
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <linux/fb.h>
typedef struct FbState {
/* refcount for dup() */
int refCount;
/* index into gWrapSim.display[] */
int displayIdx;
/* VRAM address, set by mmap() call */
void* vramAddr;
/* kernel data structures */
struct fb_var_screeninfo vinfo;
struct fb_fix_screeninfo finfo;
} FbState;
/*
* Set up the initial values of the structs.
*
* The FbState struct is zeroed out initially, so we only need to set the
* fields that don't default to zero.
*/
static void configureInitialState(int displayIdx, FbState* fbState)
{
int width, height;
assert(displayIdx >= 0 && displayIdx < gWrapSim.numDisplays);
width = gWrapSim.display[displayIdx].width;
height = gWrapSim.display[displayIdx].height;
wsLog("Configuring FbState for display %d (%dx%x key=0x%08x)\n",
displayIdx, width, height, gWrapSim.display[displayIdx].shmemKey);
/* fb_fix_screeninfo */
strcpy(fbState->finfo.id, "omapfb");
fbState->finfo.smem_len = (width * 2) * height * 2;
fbState->finfo.line_length = width * 2;
/* fb_var_screeninfo */
fbState->vinfo.xres = width;
fbState->vinfo.yres = height;
fbState->vinfo.xres_virtual = width;
fbState->vinfo.yres_virtual = height * 2;
fbState->vinfo.bits_per_pixel = 16;
fbState->vinfo.red.offset = 11;
fbState->vinfo.red.length = 5;
fbState->vinfo.green.offset = 5;
fbState->vinfo.green.length = 6;
fbState->vinfo.blue.offset = 0;
fbState->vinfo.blue.length = 5;
fbState->vinfo.width = 51; // physical dimension, used for dpi
fbState->vinfo.height = 76;
fbState->vinfo.pixclock = 103092;
fbState->vinfo.upper_margin = 3;
fbState->vinfo.lower_margin = 227;
fbState->vinfo.left_margin = 12;
fbState->vinfo.right_margin = 8;
}
/*
* Free allocated state.
*/
static void freeState(FbState* fbState)
{
int oldcount;
oldcount = wsAtomicAdd(&fbState->refCount, -1);
if (oldcount == 0) {
free(fbState);
}
}
/*
* Wait for our synthetic vsync to happen.
*/
static void waitForVsync(FbState* state)
{
/* TODO: simulate a real interval */
usleep(1000000/60);
}
/*
* Forward pixels to the simulator.
*/
static void sendPixelsToSim(FbState* state)
{
if (state->vramAddr == 0) {
wsLog("## not sending pixels (no addr yet)\n");
return;
}
//wsLog("+++ sending pixels to sim (disp=%d yoff=%d)\n",
// state->displayIdx, state->vinfo.yoffset);
wsLockDisplay(state->displayIdx);
uint8_t* dst = gWrapSim.display[state->displayIdx].addr;
int l,t,r,b,w,h;
w = gWrapSim.display[state->displayIdx].width;
h = gWrapSim.display[state->displayIdx].height;
#if 0
/*
* TODO: surfaceflinger encodes the dirty region in vinfo.reserved[]. We
* can use that to perform a partial update.
*/
const Rect dirty(dirtyReg.bounds());
l = dirty.left >=0 ? dirty.left : 0;
t = dirty.top >=0 ? dirty.top : 0;
r = dirty.right <=w ? dirty.right : w;
b = dirty.bottom<=h ? dirty.bottom : h;
#else
l = t = 0;
r = w;
b = h;
#endif
/* find the right page */
int ypage = state->vinfo.yoffset;
int x, y;
for (y = t ; y < b ; y++) {
// no "stride" issues with this display
uint8_t* outPtr = dst + (y*w+l)*3;
const uint16_t* ptr16 = (uint16_t*)state->vramAddr + ((y+ypage)*w+l);
for (x = l; x < r; x++) {
uint16_t in = *ptr16++;
uint32_t R,G,B;
R = ((in>>8)&0xF8) | (in>>(8+5));
G = (in & 0x7E0)>>3;
G |= G>>6;
B = (in & 0x1F)<<3;
B |= B>>5;
*outPtr++ = R;
*outPtr++ = G;
*outPtr++ = B;
}
}
wsUnlockDisplay(state->displayIdx);
/* notify the simulator */
wsPostDisplayUpdate(state->displayIdx);
}
/*
* Provide a memory-mapped region for framebuffer data. We want to use a
* real mmap() call, not fake it with a malloc, so that related calls
* (munmap, madvise) will just work.
*/
static void* mmapFb(FakeDev* dev, void* start, size_t length, int prot,
int flags, int fd, __off_t offset)
{
FbState* state = (FbState*) dev->state;
void* map;
/* be reasonable */
if (length > (640*480*2)*4) {
errno = EINVAL;
return MAP_FAILED;
}
/* this is supposed to be VRAM, so just map a chunk */
map = mmap(start, length, prot, MAP_PRIVATE | MAP_ANON, -1, 0);
/* update our "VRAM address"; this feels a bit fragile */
if (state->vramAddr != NULL) {
wsLog("%s: NOTE: changing vram address from %p\n",
dev->debugName, state->vramAddr);
}
state->vramAddr = map;
wsLog("%s: mmap %u bytes --> %p\n", dev->debugName, length, map);
return map;
}
/*
* Handle framebuffer ioctls.
*/
static int ioctlFb(FakeDev* dev, int fd, int request, void* argp)
{
FbState* state = (FbState*) dev->state;
wsLog("%s: ioctl(0x%x, %p)\n", dev->debugName, request, argp);
switch (request) {
case FBIOGET_FSCREENINFO: // struct fb_fix_screeninfo*
memcpy(argp, &state->finfo, sizeof(struct fb_fix_screeninfo));
break;
case FBIOGET_VSCREENINFO: // struct fb_var_screeninfo*
memcpy(argp, &state->vinfo, sizeof(struct fb_var_screeninfo));
break;
case FBIOPUT_VSCREENINFO: // struct fb_var_screeninfo*
memcpy(&state->vinfo, argp, sizeof(struct fb_var_screeninfo));
if (state->vinfo.activate == FB_ACTIVATE_NOW) {
//wsLog("%s: activate now\n", dev->debugName);
sendPixelsToSim(state);
} else if (state->vinfo.activate == FB_ACTIVATE_VBL) {
//wsLog("%s: activate on VBL\n", dev->debugName);
sendPixelsToSim(state);
/* we wait *after* so other process gets scheduled to draw */
waitForVsync(state);
} else {
wsLog("%s: activate value is %d\n",
dev->debugName, state->vinfo.activate);
}
break;
case FBIOGET_VBLANK: // struct fb_vblank*
/* the device doesn't actually implement this */
//memset(argp, 0, sizeof(struct fb_vblank));
errno = EINVAL;
return -1;
default:
/*case FBIO_WAITFORVSYNC:*/
wsLog("GLITCH: UNKNOWN ioctl request 0x%x on %s\n",
request, dev->debugName);
return -1;
}
return 0;
}
/*
* Free up our state before closing down the fake descriptor.
*/
static int closeFb(FakeDev* dev, int fd)
{
freeState((FbState*)dev->state);
dev->state = NULL;
return 0;
}
/*
* dup() an existing fake descriptor
*/
static FakeDev* dupFb(FakeDev* dev, int fd)
{
FakeDev* newDev = wsCreateFakeDev(dev->debugName);
if (newDev != NULL) {
newDev->mmap = mmapFb;
newDev->ioctl = ioctlFb;
newDev->close = closeFb;
newDev->dup = dupFb;
/* use state from existing FakeDev */
FbState* fbState = dev->state;
wsAtomicAdd(&fbState->refCount, 1);
newDev->state = fbState;
}
return newDev;
}
/*
* Open the console TTY device, which responds to a collection of ioctl()s.
*/
FakeDev* wsOpenDevFb(const char* pathName, int flags)
{
FakeDev* newDev = wsCreateFakeDev(pathName);
if (newDev != NULL) {
newDev->mmap = mmapFb;
newDev->ioctl = ioctlFb;
newDev->close = closeFb;
newDev->dup = dupFb;
FbState* fbState = calloc(1, sizeof(FbState));
/* establish a connection to the front-end if necessary */
/* (also gets display configuration) */
wsSimConnect();
configureInitialState(0, fbState); // always use display 0 for now
newDev->state = fbState;
}
return newDev;
}