/*
 * Test egl driver for fb_dri.so
 */
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h> 
#include <errno.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/fb.h>

#include "utils.h"
#include "buffers.h"
#include "extensions.h"
#include "framebuffer.h"
#include "renderbuffer.h"
#include "array_cache/acache.h"
#include "swrast/swrast.h"
#include "swrast_setup/swrast_setup.h"
#include "tnl/tnl.h"
#include "tnl/t_context.h"
#include "tnl/t_pipeline.h"
#include "drivers/common/driverfuncs.h"
#include "drirenderbuffer.h"

#include "eglconfig.h"
#include "eglcontext.h"
#include "egldisplay.h"
#include "egldriver.h"
#include "eglglobals.h"
#include "eglmode.h"
#include "eglscreen.h"
#include "eglsurface.h"

extern void
fbSetSpanFunctions(driRenderbuffer *drb, const GLvisual *vis);

/**
 * fb driver-specific driver class derived from _EGLDriver
 */
typedef struct fb_driver
{
   _EGLDriver Base;  /* base class/object */
   GLuint fbStuff;
} fbDriver;

/**
 * fb display-specific driver class derived from _EGLDisplay
 */
typedef struct fb_display
{
   _EGLDisplay Base;  /* base class/object */
   void *pFB;
} fbDisplay;

/**
 * fb driver-specific screen class derived from _EGLScreen
 */
typedef struct fb_screen
{
   _EGLScreen Base;
   char fb[NAME_MAX];
} fbScreen;


/**
 * fb driver-specific surface class derived from _EGLSurface
 */
typedef struct fb_surface
{
   _EGLSurface Base;  /* base class/object */
   struct gl_framebuffer *mesa_framebuffer;
} fbSurface;


/**
 * fb driver-specific context class derived from _EGLContext
 */
typedef struct fb_context
{
   _EGLContext Base;  /* base class/object */
   GLcontext *glCtx;
   struct {
      __DRIcontextPrivate *context;	
      __DRIscreenPrivate *screen;	
      __DRIdrawablePrivate *drawable; /* drawable bound to this ctx */
   } dri;
} fbContext, *fbContextPtr;

#define FB_CONTEXT(ctx)		((fbContextPtr)(ctx->DriverCtx))


static EGLBoolean
fbFillInConfigs(_EGLDisplay *disp, unsigned pixel_bits, unsigned depth_bits,
               unsigned stencil_bits, GLboolean have_back_buffer) {
   _EGLConfig *configs;
   _EGLConfig *c;
   unsigned int i, num_configs;
   unsigned int depth_buffer_factor;
   unsigned int back_buffer_factor;
   GLenum fb_format;
   GLenum fb_type;

   /* Right now GLX_SWAP_COPY_OML isn't supported, but it would be easy
   * enough to add support.  Basically, if a context is created with an
   * fbconfig where the swap method is GLX_SWAP_COPY_OML, pageflipping
   * will never be used.
   */
   static const GLenum back_buffer_modes[] = {
            GLX_NONE, GLX_SWAP_UNDEFINED_OML /*, GLX_SWAP_COPY_OML */
         };

   u_int8_t depth_bits_array[2];
   u_int8_t stencil_bits_array[2];

   depth_bits_array[0] = 0;
   depth_bits_array[1] = depth_bits;

   /* Just like with the accumulation buffer, always provide some modes
   * with a stencil buffer.  It will be a sw fallback, but some apps won't
   * care about that.
   */
   stencil_bits_array[0] = 0;
   stencil_bits_array[1] = (stencil_bits == 0) ? 8 : stencil_bits;

   depth_buffer_factor = ((depth_bits != 0) || (stencil_bits != 0)) ? 2 : 1;
   back_buffer_factor = (have_back_buffer) ? 2 : 1;

   num_configs = depth_buffer_factor * back_buffer_factor * 2;

   if (pixel_bits == 16) {
      fb_format = GL_RGB;
      fb_type = GL_UNSIGNED_SHORT_5_6_5;
   } else {
      fb_format = GL_RGBA;
      fb_type = GL_UNSIGNED_INT_8_8_8_8_REV;
   }

   configs = calloc(sizeof(*configs), num_configs);
   c = configs;
   if (!_eglFillInConfigs(c, fb_format, fb_type,
                          depth_bits_array, stencil_bits_array, depth_buffer_factor,
                          back_buffer_modes, back_buffer_factor,
                          GLX_TRUE_COLOR)) {
      fprintf(stderr, "[%s:%u] Error creating FBConfig!\n",
               __func__, __LINE__);
      return EGL_FALSE;
   }

   /* Mark the visual as slow if there are "fake" stencil bits.
   */
   for (i = 0, c = configs; i < num_configs; i++, c++) {
      int stencil = GET_CONFIG_ATTRIB(c, EGL_STENCIL_SIZE);
      if ((stencil != 0)  && (stencil != stencil_bits)) {
         SET_CONFIG_ATTRIB(c, EGL_CONFIG_CAVEAT, EGL_SLOW_CONFIG);
      }
   }

   for (i = 0, c = configs; i < num_configs; i++, c++)
      _eglAddConfig(disp, c);
      
   free(configs);
   
   return EGL_TRUE;
}

static EGLBoolean
fbSetupFramebuffer(fbDisplay *disp, char *fbdev) 
{
   int fd;
   char dev[20];
   struct fb_var_screeninfo varInfo;
   struct fb_fix_screeninfo fixedInfo;
   
   snprintf(dev, sizeof(dev), "/dev/%s", fbdev);

   /* open the framebuffer device */
   fd = open(dev, O_RDWR);
   if (fd < 0) {
      fprintf(stderr, "Error opening %s: %s\n", fbdev, strerror(errno));
      return EGL_FALSE;
   }

   /* get the original variable screen info */
   if (ioctl(fd, FBIOGET_VSCREENINFO, &varInfo)) {
      fprintf(stderr, "error: ioctl(FBIOGET_VSCREENINFO) failed: %s\n",
               strerror(errno));
      return EGL_FALSE;
   }

   /* Turn off hw accels (otherwise mmap of mmio region will be
    * refused)
    */
   if (varInfo.accel_flags) {
      varInfo.accel_flags = 0;
      if (ioctl(fd, FBIOPUT_VSCREENINFO, &varInfo)) {
         fprintf(stderr, "error: ioctl(FBIOPUT_VSCREENINFO) failed: %s\n",
                  strerror(errno));
         return EGL_FALSE;
      }
   }

   /* Get the fixed screen info */
   if (ioctl(fd, FBIOGET_FSCREENINFO, &fixedInfo)) {
      fprintf(stderr, "error: ioctl(FBIOGET_FSCREENINFO) failed: %s\n",
               strerror(errno));
      return EGL_FALSE;
   }
   
   if (fixedInfo.visual == FB_VISUAL_DIRECTCOLOR) {
      struct fb_cmap cmap;
      unsigned short red[256], green[256], blue[256];
      int rcols = 1 << varInfo.red.length;
      int gcols = 1 << varInfo.green.length;
      int bcols = 1 << varInfo.blue.length;
      int i;

      cmap.start = 0;      
      cmap.len = gcols;
      cmap.red   = red;
      cmap.green = green;
      cmap.blue  = blue;
      cmap.transp = NULL;

      for (i = 0; i < rcols ; i++) 
         red[i] = (65536/(rcols-1)) * i;

      for (i = 0; i < gcols ; i++) 
         green[i] = (65536/(gcols-1)) * i;

      for (i = 0; i < bcols ; i++) 
         blue[i] = (65536/(bcols-1)) * i;
      
      if (ioctl(fd, FBIOPUTCMAP, (void *) &cmap) < 0) {
         fprintf(stderr, "ioctl(FBIOPUTCMAP) failed [%d]\n", i);
         exit(1);
      }
   }

   /* mmap the framebuffer into our address space */
   if (!disp->pFB)
      disp->pFB = (caddr_t)mmap(0,  /* start */
                      fixedInfo.smem_len,  /* bytes */
                      PROT_READ | PROT_WRITE,  /* prot */
                      MAP_SHARED,  /* flags */
                      fd,  /* fd */
                      0); /* offset */ 
   if (disp->pFB == (caddr_t)-1) {
      fprintf(stderr, "error: unable to mmap framebuffer: %s\n",
               strerror(errno));
      return EGL_FALSE;
   }
   
   return EGL_TRUE;
}
   
const char *sysfs = "/sys/class/graphics";

static EGLBoolean
fbInitialize(_EGLDriver *drv, EGLDisplay dpy, EGLint *major, EGLint *minor)
{
   _EGLDisplay *disp = _eglLookupDisplay(dpy);
   fbDisplay *display;
   fbScreen *s;
   _EGLScreen *scrn;
   char c;
   unsigned int x, y, r;
   DIR *dir;
   FILE *file;
   struct dirent *dirent;
   char path[NAME_MAX];
   
   /* Switch display structure to one with our private fields */
   display = calloc(1, sizeof(*display));
   display->Base = *disp;
   _eglHashInsert(_eglGlobal.Displays, disp->Handle, display);
   free(disp);
   
   *major = 1;
   *minor = 0;
   
   dir = opendir(sysfs);
   if (!dir) {
      printf("EGL - %s framebuffer device not found.", sysfs);
      return EGL_FALSE;
   }
   
   while ((dirent = readdir(dir))) {  /* assignment! */
      
      if (dirent->d_name[0] != 'f')
         continue;
      if (dirent->d_name[1] != 'b')
         continue;
   
      if (fbSetupFramebuffer(display, dirent->d_name) == EGL_FALSE)
         continue;
         
      /* Create a screen */
      s = (fbScreen *) calloc(1, sizeof(fbScreen));
      if (!s)
         return EGL_FALSE;

      strncpy(s->fb, dirent->d_name, NAME_MAX);
      scrn = &s->Base;
      _eglInitScreen(scrn);
      _eglAddScreen(&display->Base, scrn);
      
      snprintf(path, sizeof(path), "%s/%s/modes", sysfs, s->fb);
      file = fopen(path, "r");
      while (fgets(path, sizeof(path), file)) {
         sscanf(path, "%c:%ux%u-%u", &c, &x, &y, &r);
         _eglAddMode(scrn, x, y, r * 1000, path);
      }
      fclose(file);

      fbFillInConfigs(&display->Base, 32, 24, 8, 1);
      
   }
   closedir(dir);

   drv->Initialized = EGL_TRUE;
   return EGL_TRUE;
}


static fbDisplay *
Lookup_fbDisplay(EGLDisplay dpy)
{
   _EGLDisplay *d = _eglLookupDisplay(dpy);
   return (fbDisplay *) d;
}


static fbScreen *
Lookup_fbScreen(EGLDisplay dpy, EGLScreenMESA screen)
{
   _EGLScreen *s = _eglLookupScreen(dpy, screen);
   return (fbScreen *) s;
}


static fbContext *
Lookup_fbContext(EGLContext ctx)
{
   _EGLContext *c = _eglLookupContext(ctx);
   return (fbContext *) c;
}


static fbSurface *
Lookup_fbSurface(EGLSurface surf)
{
   _EGLSurface *s = _eglLookupSurface(surf);
   return (fbSurface *) s;
}


static EGLBoolean
fbTerminate(_EGLDriver *drv, EGLDisplay dpy)
{
   fbDisplay *display = Lookup_fbDisplay(dpy);
   _eglCleanupDisplay(&display->Base);
   free(display);
   free(drv);
   return EGL_TRUE;
}


static const GLubyte *
get_string(GLcontext *ctx, GLenum pname)
{
   (void) ctx;
   switch (pname) {
      case GL_RENDERER:
         return (const GLubyte *) "Mesa dumb framebuffer";
      default:
         return NULL;
   }
}


static void
update_state( GLcontext *ctx, GLuint new_state )
{
   /* not much to do here - pass it on */
   _swrast_InvalidateState( ctx, new_state );
   _swsetup_InvalidateState( ctx, new_state );
   _ac_InvalidateState( ctx, new_state );
   _tnl_InvalidateState( ctx, new_state );
}


/**
 * Called by ctx->Driver.GetBufferSize from in core Mesa to query the
 * current framebuffer size.
 */
static void
get_buffer_size( GLframebuffer *buffer, GLuint *width, GLuint *height )
{
   *width  = buffer->Width;
   *height = buffer->Height;
}


static void
updateFramebufferSize(GLcontext *ctx)
{
   fbContextPtr fbmesa = FB_CONTEXT(ctx);
   struct gl_framebuffer *fb = ctx->WinSysDrawBuffer;
   if (fbmesa->dri.drawable->w != fb->Width ||
       fbmesa->dri.drawable->h != fb->Height) {
      driUpdateFramebufferSize(ctx, fbmesa->dri.drawable);
   }
}

static void
viewport(GLcontext *ctx, GLint x, GLint y, GLsizei w, GLsizei h)
{
   /* XXX this should be called after we acquire the DRI lock, not here */
   updateFramebufferSize(ctx);
}


static void
init_core_functions( struct dd_function_table *functions )
{
   functions->GetString = get_string;
   functions->UpdateState = update_state;
   functions->ResizeBuffers = _mesa_resize_framebuffer;
   functions->GetBufferSize = get_buffer_size;
   functions->Viewport = viewport;

   functions->Clear = _swrast_Clear;  /* could accelerate with blits */
}


static EGLContext
fbCreateContext(_EGLDriver *drv, EGLDisplay dpy, EGLConfig config, EGLContext share_list, const EGLint *attrib_list)
{
   GLcontext *ctx;
   _EGLConfig *conf;
   fbContext *c;
   _EGLDisplay *disp = _eglLookupDisplay(dpy);
   struct dd_function_table functions;
   GLvisual vis;
   int i;

   conf = _eglLookupConfig(drv, dpy, config);
   if (!conf) {
      _eglError(EGL_BAD_CONFIG, "eglCreateContext");
      return EGL_NO_CONTEXT;
   }

   for (i = 0; attrib_list && attrib_list[i] != EGL_NONE; i++) {
      switch (attrib_list[i]) {
         /* no attribs defined for now */
      default:
         _eglError(EGL_BAD_ATTRIBUTE, "eglCreateContext");
         return EGL_NO_CONTEXT;
      }
   }

   c = (fbContext *) calloc(1, sizeof(fbContext));
   if (!c)
      return EGL_NO_CONTEXT;

   _eglInitContext(&c->Base);
   c->Base.Display = disp;
   c->Base.Config = conf;
   c->Base.DrawSurface = EGL_NO_SURFACE;
   c->Base.ReadSurface = EGL_NO_SURFACE;

   /* generate handle and insert into hash table */
   _eglSaveContext(&c->Base);
   assert(c->Base.Handle);

   /* Init default driver functions then plug in our FBdev-specific functions
    */
   _mesa_init_driver_functions(&functions);
   init_core_functions(&functions);

   _eglConfigToContextModesRec(conf, &vis);

   ctx = c->glCtx = _mesa_create_context(&vis, NULL, &functions, (void *)c);
   if (!c->glCtx) {
      _mesa_free(c);
      return GL_FALSE;
   }

   /* Create module contexts */
   _swrast_CreateContext( ctx );
   _ac_CreateContext( ctx );
   _tnl_CreateContext( ctx );
   _swsetup_CreateContext( ctx );
   _swsetup_Wakeup( ctx );


   /* use default TCL pipeline */
   {
      TNLcontext *tnl = TNL_CONTEXT(ctx);
      tnl->Driver.RunPipeline = _tnl_run_pipeline;
   }

   _mesa_enable_sw_extensions(ctx);

   return c->Base.Handle;
}


static EGLSurface
fbCreateWindowSurface(_EGLDriver *drv, EGLDisplay dpy, EGLConfig config, NativeWindowType window, const EGLint *attrib_list)
{
   int i;
   for (i = 0; attrib_list && attrib_list[i] != EGL_NONE; i++) {
      switch (attrib_list[i]) {
         /* no attribs at this time */
      default:
         _eglError(EGL_BAD_ATTRIBUTE, "eglCreateWindowSurface");
         return EGL_NO_SURFACE;
      }
   }
   printf("eglCreateWindowSurface()\n");
   /* XXX unfinished */

   return EGL_NO_SURFACE;
}


static EGLSurface
fbCreatePixmapSurface(_EGLDriver *drv, EGLDisplay dpy, EGLConfig config, NativePixmapType pixmap, const EGLint *attrib_list)
{
   _EGLConfig *conf;
   EGLint i;

   conf = _eglLookupConfig(drv, dpy, config);
   if (!conf) {
      _eglError(EGL_BAD_CONFIG, "eglCreatePixmapSurface");
      return EGL_NO_SURFACE;
   }

   for (i = 0; attrib_list && attrib_list[i] != EGL_NONE; i++) {
      switch (attrib_list[i]) {
         /* no attribs at this time */
      default:
         _eglError(EGL_BAD_ATTRIBUTE, "eglCreatePixmapSurface");
         return EGL_NO_SURFACE;
      }
   }

   if (conf->Attrib[EGL_SURFACE_TYPE - FIRST_ATTRIB] == 0) {
      _eglError(EGL_BAD_MATCH, "eglCreatePixmapSurface");
      return EGL_NO_SURFACE;
   }

   printf("eglCreatePixmapSurface()\n");
   return EGL_NO_SURFACE;
}


static EGLSurface
fbCreatePbufferSurface(_EGLDriver *drv, EGLDisplay dpy, EGLConfig config, const EGLint *attrib_list)
{
   fbSurface *surf;

   surf = (fbSurface *) calloc(1, sizeof(fbSurface));
   if (!surf) {
      return EGL_NO_SURFACE;
   }

   if (_eglInitPbufferSurface(&surf->Base, drv, dpy, config, attrib_list) == EGL_NO_SURFACE) {
      free(surf);
      return EGL_NO_SURFACE;
   }

   /* create software-based pbuffer */
   {
      GLcontext *ctx = NULL; /* this _should_ be OK */
      GLvisual vis;
      _EGLConfig *conf = _eglLookupConfig(drv, dpy, config);
      assert(conf); /* bad config should be caught earlier */
      _eglConfigToContextModesRec(conf, &vis);

      surf->mesa_framebuffer = _mesa_create_framebuffer(&vis);
      _mesa_add_soft_renderbuffers(surf->mesa_framebuffer,
                                   GL_TRUE, /* color bufs */
                                   vis.haveDepthBuffer,
                                   vis.haveStencilBuffer,
                                   vis.haveAccumBuffer,
                                   GL_FALSE, /* alpha */
                                   GL_FALSE /* aux */ );

      /* set pbuffer/framebuffer size */
      _mesa_resize_framebuffer(ctx, surf->mesa_framebuffer,
                               surf->Base.Width, surf->Base.Height);
   }

   return surf->Base.Handle;
}


static EGLBoolean
fbDestroySurface(_EGLDriver *drv, EGLDisplay dpy, EGLSurface surface)
{
   fbSurface *fs = Lookup_fbSurface(surface);
   _eglRemoveSurface(&fs->Base);
   if (fs->Base.IsBound) {
      fs->Base.DeletePending = EGL_TRUE;
   }
   else {
      free(fs);
   }
   return EGL_TRUE;
}


static EGLBoolean
fbDestroyContext(_EGLDriver *drv, EGLDisplay dpy, EGLContext context)
{
   fbContext *fc = Lookup_fbContext(context);
   _eglRemoveContext(&fc->Base);
   if (fc->Base.IsBound) {
      fc->Base.DeletePending = EGL_TRUE;
   }
   else {
      free(fc);
   }
   return EGL_TRUE;
}


static EGLBoolean
fbMakeCurrent(_EGLDriver *drv, EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext context)
{
   fbSurface *readSurf = Lookup_fbSurface(read);
   fbSurface *drawSurf = Lookup_fbSurface(draw);
   fbContext *ctx = Lookup_fbContext(context);
   EGLBoolean b;

   b = _eglMakeCurrent(drv, dpy, draw, read, context);
   if (!b)
      return EGL_FALSE;

   if (ctx) {
      _mesa_make_current( ctx->glCtx, 
                           drawSurf->mesa_framebuffer,
                           readSurf->mesa_framebuffer);
   } else
      _mesa_make_current( NULL, NULL, NULL );

   return EGL_TRUE;
}


/**
 * Create a drawing surface which can be directly displayed on a screen.
 */
static EGLSurface
fbCreateScreenSurfaceMESA(_EGLDriver *drv, EGLDisplay dpy, EGLConfig cfg,
                          const EGLint *attrib_list)
{
   _EGLConfig *config = _eglLookupConfig(drv, dpy, cfg);
   fbDisplay *display = Lookup_fbDisplay(dpy);
   fbSurface *surface;
   EGLSurface surf;
   GLvisual vis;
   GLcontext *ctx = NULL; /* this should be OK */
   int origin, bytesPerPixel;
   int width, height, stride;
   
   surface = (fbSurface *) malloc(sizeof(*surface));
   if (!surface) {
      return EGL_NO_SURFACE;
   }

   /* init base class, error check, etc. */
   surf = _eglInitScreenSurface(&surface->Base, drv, dpy, cfg, attrib_list);
   if (surf == EGL_NO_SURFACE) {
      free(surface);
      return EGL_NO_SURFACE;
   }

   /* convert EGLConfig to GLvisual */
   _eglConfigToContextModesRec(config, &vis);

   /* create Mesa framebuffer */
   surface->mesa_framebuffer = _mesa_create_framebuffer(&vis);
   if (!surface->mesa_framebuffer) {
      free(surface);
      _eglRemoveSurface(&surface->Base);
      return EGL_NO_SURFACE;
   }

   width = surface->Base.Width;
   height = surface->Base.Height;
   bytesPerPixel = vis.rgbBits / 8;
   stride = width * bytesPerPixel;
   origin = 0;

   /* front color renderbuffer */
   {
      driRenderbuffer *drb = driNewRenderbuffer(GL_RGBA, display->pFB,
                                                bytesPerPixel,
                                                origin, stride, NULL);
      fbSetSpanFunctions(drb, &vis);
      _mesa_add_renderbuffer(surface->mesa_framebuffer,
                             BUFFER_FRONT_LEFT, &drb->Base);
   }

   /* back color renderbuffer */
   if (vis.doubleBufferMode) {
      GLubyte *backBuf = _mesa_malloc(stride * height);
      driRenderbuffer *drb = driNewRenderbuffer(GL_RGBA, backBuf,
                                                bytesPerPixel,
                                                origin, stride, NULL);
      fbSetSpanFunctions(drb, &vis);
      _mesa_add_renderbuffer(surface->mesa_framebuffer,
                             BUFFER_BACK_LEFT, &drb->Base);
   }

   /* other renderbuffers- software based */
   _mesa_add_soft_renderbuffers(surface->mesa_framebuffer,
                                GL_FALSE, /* color */
                                vis.haveDepthBuffer,
                                vis.haveStencilBuffer,
                                vis.haveAccumBuffer,
                                GL_FALSE, /* alpha */
                                GL_FALSE /* aux */);
   
   _mesa_resize_framebuffer(ctx, surface->mesa_framebuffer, width, height);

   return surf;
}


/**
 * Show the given surface on the named screen.
 * If surface is EGL_NO_SURFACE, disable the screen's output.
 */
static EGLBoolean
fbShowSurfaceMESA(_EGLDriver *drv, EGLDisplay dpy, EGLScreenMESA screen,
                    EGLSurface surface, EGLModeMESA m)
{
   fbDisplay *display = Lookup_fbDisplay(dpy);
   fbScreen *scrn = Lookup_fbScreen(dpy, screen);
   fbSurface *surf = Lookup_fbSurface(surface);
   FILE *file;
   char buffer[NAME_MAX];
   _EGLMode *mode = _eglLookupMode(dpy, m);
   int bits;
   
   if (!_eglShowSurfaceMESA(drv, dpy, screen, surface, m))
      return EGL_FALSE;
      
   snprintf(buffer, sizeof(buffer), "%s/%s/blank", sysfs, scrn->fb);
   
   file = fopen(buffer, "r+");
   if (!file) {
err:
      printf("chown all fb sysfs attrib to allow write - %s\n", buffer);
      return EGL_FALSE;
   }
   snprintf(buffer, sizeof(buffer), "%d", (m == EGL_NO_MODE_MESA ? VESA_POWERDOWN : VESA_VSYNC_SUSPEND));
   fputs(buffer, file);
   fclose(file);
   
   if (m == EGL_NO_MODE_MESA)
      return EGL_TRUE;
   
   snprintf(buffer, sizeof(buffer), "%s/%s/mode", sysfs, scrn->fb);
   
   file = fopen(buffer, "r+");
   if (!file)
      goto err;
   fputs(mode->Name, file);
   fclose(file);
   
   snprintf(buffer, sizeof(buffer), "%s/%s/bits_per_pixel", sysfs, scrn->fb);
   
   file = fopen(buffer, "r+");
   if (!file)
      goto err;
   bits = GET_CONFIG_ATTRIB(surf->Base.Config, EGL_BUFFER_SIZE);
   snprintf(buffer, sizeof(buffer), "%d", bits);
   fputs(buffer, file);
   fclose(file);

   fbSetupFramebuffer(display, scrn->fb);
   
   snprintf(buffer, sizeof(buffer), "%s/%s/blank", sysfs, scrn->fb);
   
   file = fopen(buffer, "r+");
   if (!file)
      goto err;
      
   snprintf(buffer, sizeof(buffer), "%d", VESA_NO_BLANKING);
   fputs(buffer, file);
   fclose(file);
   
   return EGL_TRUE;
}


/* If the backbuffer is on a videocard, this is extraordinarily slow!
 */
static EGLBoolean
fbSwapBuffers(_EGLDriver *drv, EGLDisplay dpy, EGLSurface draw)
{
   fbContext *context = (fbContext *)_eglGetCurrentContext();
   fbSurface *fs =  Lookup_fbSurface(draw);
   struct gl_renderbuffer * front_renderbuffer = fs->mesa_framebuffer->Attachment[BUFFER_FRONT_LEFT].Renderbuffer;
   void *frontBuffer = front_renderbuffer->Data;
   int currentPitch = ((driRenderbuffer *)front_renderbuffer)->pitch;
   void *backBuffer = fs->mesa_framebuffer->Attachment[BUFFER_BACK_LEFT].Renderbuffer->Data;

   if (!_eglSwapBuffers(drv, dpy, draw))
      return EGL_FALSE;

   if (context) {
      GLcontext *ctx = context->glCtx;
      
      if (ctx->Visual.doubleBufferMode) {
	 int i;
	 int offset = 0;
         char *tmp = _mesa_malloc(currentPitch);

         _mesa_notifySwapBuffers( ctx );  /* flush pending rendering comands */

         ASSERT(frontBuffer);
         ASSERT(backBuffer);

	 for (i = 0; i < fs->Base.Height; i++) {
            _mesa_memcpy(tmp, (char *) backBuffer + offset,
                         currentPitch);
            _mesa_memcpy((char *) frontBuffer + offset, tmp,
                          currentPitch);
            offset += currentPitch;
	 }
	    
	 _mesa_free(tmp);
      }
   }
   else {
      /* XXX this shouldn't be an error but we can't handle it for now */
      _mesa_problem(NULL, "fbSwapBuffers: drawable has no context!\n");
      return EGL_FALSE;
   }
   return EGL_TRUE;
}


/**
 * The bootstrap function.  Return a new fbDriver object and
 * plug in API functions.
 */
_EGLDriver *
_eglMain(_EGLDisplay *dpy)
{
   fbDriver *fb;

   fb = (fbDriver *) calloc(1, sizeof(fbDriver));
   if (!fb) {
      return NULL;
   }

   /* First fill in the dispatch table with defaults */
   _eglInitDriverFallbacks(&fb->Base);
   
   /* then plug in our fb-specific functions */
   fb->Base.Initialize = fbInitialize;
   fb->Base.Terminate = fbTerminate;
   fb->Base.CreateContext = fbCreateContext;
   fb->Base.MakeCurrent = fbMakeCurrent;
   fb->Base.CreateWindowSurface = fbCreateWindowSurface;
   fb->Base.CreatePixmapSurface = fbCreatePixmapSurface;
   fb->Base.CreatePbufferSurface = fbCreatePbufferSurface;
   fb->Base.DestroySurface = fbDestroySurface;
   fb->Base.DestroyContext = fbDestroyContext;
   fb->Base.CreateScreenSurfaceMESA = fbCreateScreenSurfaceMESA;
   fb->Base.ShowSurfaceMESA = fbShowSurfaceMESA;
   fb->Base.SwapBuffers = fbSwapBuffers;
   
   /* enable supported extensions */
   fb->Base.MESA_screen_surface = EGL_TRUE;
   fb->Base.MESA_copy_context = EGL_TRUE;

   return &fb->Base;
}
