1 /*         ______   ___    ___
2  *        /\  _  \ /\_ \  /\_ \
3  *        \ \ \L\ \\//\ \ \//\ \      __     __   _ __   ___
4  *         \ \  __ \ \ \ \  \ \ \   /'__`\ /'_ `\/\`'__\/ __`\
5  *          \ \ \/\ \ \_\ \_ \_\ \_/\  __//\ \L\ \ \ \//\ \L\ \
6  *           \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/
7  *            \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/
8  *                                           /\____/
9  *                                           \_/__/
10  *
11  *      Video driver for the Linux framebuffer device.
12  *
13  *      By Shawn Hargreaves.
14  *
15  *      Proper mode setting support added by George Foot.
16  *
17  *      Modified by Grzegorz Adam Hankiewicz.
18  *
19  *      See readme.txt for copyright information.
20  */
21 
22 
23 #include "allegro.h"
24 #include "allegro/internal/aintern.h"
25 #include "allegro/platform/aintunix.h"
26 
27 
28 #if (defined ALLEGRO_LINUX_FBCON) && ((!defined ALLEGRO_WITH_MODULES) || (defined ALLEGRO_MODULE))
29 
30 #if !defined(_POSIX_MAPPED_FILES) || !defined(ALLEGRO_HAVE_MMAP)
31 #error "Sorry, mapped files are required for Linux console Allegro to work!"
32 #endif
33 
34 #include <stdio.h>
35 #include <string.h>
36 #include <sys/ioctl.h>
37 #include <linux/fb.h>
38 #include <sys/mman.h>
39 
40 /* 2.4 kernel doesn't seem to have these, so assume that they don't
41  * "have sticky," heheh. */
42 
43 #ifndef FB_VBLANK_HAVE_STICKY
44 #define FB_VBLANK_HAVE_STICKY 0
45 #endif
46 
47 #ifndef FB_VBLANK_STICKY
48 #define FB_VBLANK_STICKY 0
49 #endif
50 
51 
52 
53 #define PREFIX_I                "al-fbcon INFO: "
54 #define PREFIX_W                "al-fbcon WARNING: "
55 #define PREFIX_E                "al-fbcon ERROR: "
56 
57 
58 
59 static BITMAP *fb_init(int w, int h, int v_w, int v_h, int color_depth);
60 static int fb_open_device(void);
61 static void fb_exit(BITMAP *b);
62 static void fb_save(void);
63 static void fb_restore(void);
64 static int  fb_scroll(int x, int y);
65 static void fb_vsync(void);
66 static void fb_set_palette(AL_CONST RGB *p, int from, int to, int vsync);
67 static void fb_save_cmap(void);
68 static void fb_restore_cmap(void);
69 
70 
71 
72 GFX_DRIVER gfx_fbcon =
73 {
74    GFX_FBCON,
75    empty_string,
76    empty_string,
77    "fbcon",
78    fb_init,
79    fb_exit,
80    fb_scroll,
81    fb_vsync,
82    fb_set_palette,
83    NULL, NULL, NULL,             /* no triple buffering */
84    NULL, NULL, NULL, NULL,       /* no video bitmaps */
85    NULL, NULL,                   /* no system bitmaps */
86    NULL, NULL, NULL, NULL,       /* no hardware cursor */
87    NULL,                         /* no drawing mode hook */
88    fb_save,
89    fb_restore,
90    NULL,    // AL_METHOD(void, set_blender_mode, (int mode, int r, int g, int b, int a));
91    NULL,                         /* no fetch mode hook */
92    0, 0,
93    TRUE,
94    0, 0, 0, 0, FALSE
95 };
96 
97 
98 static char fb_desc[256] = EMPTY_STRING;     /* description string */
99 
100 static int fb_mode_read = FALSE;             /* has orig_mode been read? */
101 
102 static struct fb_fix_screeninfo fix_info;    /* read-only video mode info */
103 static struct fb_var_screeninfo orig_mode;   /* original video mode info */
104 static struct fb_var_screeninfo my_mode;     /* my video mode info */
105 
106 static void *fbaddr;                         /* frame buffer address */
107 
108 static int fbfd;                             /* file descriptor */
109 
110 static int fb_approx;                        /* emulate small resolution */
111 
112 #ifdef FBIOGET_VBLANK
113    static int vblank_flags;                  /* supports retrace detection? */
114 #endif
115 
116 static int in_fb_restore;
117 
118 
119 
120 static int update_timings(struct fb_var_screeninfo *mode);
121 
122 
123 
set_ramp_cmap(AL_CONST struct fb_fix_screeninfo * fix,AL_CONST struct fb_var_screeninfo * var)124 static void set_ramp_cmap(AL_CONST struct fb_fix_screeninfo *fix,
125                           AL_CONST struct fb_var_screeninfo *var)
126 {
127    struct fb_cmap cmap;
128    static unsigned short r[256], g[256], b[256]; /* 1.5 KB, so in .bss */
129    int rlen, glen, blen, rdiv, gdiv, bdiv;
130    unsigned int c;
131 
132    ASSERT(fix);
133    ASSERT(var);
134    ASSERT(fix->visual == FB_VISUAL_DIRECTCOLOR);
135 
136    /* initialize what is common */
137    cmap.start = 0;
138    cmap.red = r;
139    cmap.green = g;
140    cmap.blue = b;
141    cmap.transp = NULL;
142 
143    /* from the mode's color depth information */
144    rlen = 1<<var->red.length;
145    glen = 1<<var->green.length;
146    blen = 1<<var->blue.length;
147    cmap.len = MAX(rlen, MAX(glen, blen));
148    ASSERT(cmap.len <= 256);
149    if (cmap.len > 256)
150       cmap.len = 256; /* are there cards with more than 8 bits per color ? */
151 
152    rdiv = rlen<=1 ? 1 : rlen-1;
153    gdiv = glen<=1 ? 1 : glen-1;
154    bdiv = blen<=1 ? 1 : blen-1;
155 
156    /* the easy case (should not happen) */
157    if (cmap.len == 0)
158       return;
159 
160    /* build the colormap */
161    for (c=0; c<cmap.len; ++c) {
162       cmap.red[c] = c*65535/rdiv;
163       cmap.green[c] = c*65535/gdiv;
164       cmap.blue[c] = c*65535/bdiv;
165    }
166 
167    /* wait a little to set colors once the frame is drawn */
168    fb_vsync();
169 
170    /* ioctl to the FB driver with our colormap */
171    if (ioctl(fbfd, FBIOPUTCMAP, &cmap)) {
172       /* well, we can continue with potentially screwed up colors, I guess */
173       ASSERT(0);
174    }
175 }
176 
177 
178 
calculate_refresh_rate(AL_CONST struct fb_var_screeninfo * mode)179 static void calculate_refresh_rate(AL_CONST struct fb_var_screeninfo *mode)
180 {
181    int h, v, hz;
182 
183    ASSERT(mode);
184 
185    h = mode->left_margin+mode->xres+mode->left_margin+mode->hsync_len;
186    v = mode->upper_margin+mode->yres+mode->lower_margin+mode->vsync_len;
187    hz = (int)(0.5f+1.0e12f/((float)mode->pixclock*h*v));
188    _set_current_refresh_rate(hz);
189 }
190 
191 
192 
193 /* __al_linux_get_fb_color_depth:
194  *  Get the colour depth of the framebuffer
195  */
__al_linux_get_fb_color_depth(void)196 int __al_linux_get_fb_color_depth(void)
197 {
198    if ((!fb_mode_read) && (fb_open_device() != 0))
199       return 0;
200    return orig_mode.bits_per_pixel;
201 }
202 
203 
204 
205 /* __al_linux_get_fb_resolution:
206  *  Get the resolution of the framebuffer
207  */
__al_linux_get_fb_resolution(int * width,int * height)208 int __al_linux_get_fb_resolution(int *width, int *height)
209 {
210    if ((!fb_mode_read) && (fb_open_device() != 0))
211       return -1;
212 
213    *width = orig_mode.xres;
214    *height = orig_mode.yres;
215    return 0;
216 }
217 
218 
219 
220 /* fb_init:
221  *  Sets a graphics mode.
222  */
fb_init(int w,int h,int v_w,int v_h,int color_depth)223 static BITMAP *fb_init(int w, int h, int v_w, int v_h, int color_depth)
224 {
225    AL_CONST char *p;
226    int stride, tries, original_color_depth = _color_depth;
227    BITMAP *b;
228    char tmp[16];
229 
230    /* open framebuffer and store info in global variables */
231    if ((!fb_mode_read) && (fb_open_device() != 0))
232       return NULL;
233 
234    /* look for a nice graphics mode in several passes */
235    fb_approx = FALSE;
236 
237    ASSERT (w >= 0);
238    ASSERT (h >= 0);
239 
240    /* preset a resolution if the user didn't ask for one */
241    if (((!w) && (!h)) || _safe_gfx_mode_change) {
242       w = orig_mode.xres;
243       h = orig_mode.yres;
244       TRACE(PREFIX_I "User didn't ask for a resolution, and we are trying "
245 	    "to set a safe mode, so I will try resolution %dx%d\n", w, h);
246    }
247 
248    if (_safe_gfx_mode_change) tries = -1;
249    else tries = 0;
250 
251    if (__al_linux_console_graphics() != 0) {
252       ioctl(fbfd, FBIOPUT_VSCREENINFO, &orig_mode);
253       close(fbfd);
254       return NULL;
255    }
256 
257    for (; tries<3; tries++) {
258       TRACE(PREFIX_I "...try number %d...\n", tries);
259       my_mode = orig_mode;
260 
261       switch (tries) {
262 
263 	 case -1:
264 	    /* let's see if we can get the actual screen mode */
265 	    /* shouldn't we be keeping the previous color depth setting? */
266 	    switch (orig_mode.bits_per_pixel) {
267 	       case 8:
268 	       case 16:
269 	       case 24:
270 	       case 32:
271 		  color_depth = orig_mode.bits_per_pixel;
272 		  set_color_depth(color_depth);
273 	    }
274 	    break;
275 	 case 0:
276 	    /* try for largest possible virtual screen size */
277 	    my_mode.xres = w;
278 	    my_mode.yres = h;
279 	    my_mode.xres_virtual = MAX(w, v_w);
280 	    my_mode.yres_virtual = MAX(MAX(h, v_h), (int)(fix_info.smem_len / (my_mode.xres_virtual * BYTES_PER_PIXEL(color_depth))));
281 	    break;
282 
283 	 case 1:
284 	    /* try setting the exact size that was requested */
285 	    my_mode.xres = w;
286 	    my_mode.yres = h;
287 	    my_mode.xres_virtual = MAX(w, v_w);
288 	    my_mode.yres_virtual = MAX(h, v_h);
289 	    break;
290 
291 	 case 2:
292 	    /* see if we can fake a smaller mode (better than nothing) */
293 	    if (((int)my_mode.xres < w) || ((int)my_mode.yres < h) || (v_w > w) || (v_h > h))
294 	       continue;
295 	    fb_approx = TRUE;
296 	    break;
297       }
298 
299       my_mode.bits_per_pixel = color_depth;
300       my_mode.grayscale = 0;
301       my_mode.activate = FB_ACTIVATE_NOW;
302       my_mode.xoffset = 0;
303       my_mode.yoffset = 0;
304 
305       switch (color_depth) {
306 
307 	 #ifdef ALLEGRO_COLOR16
308 
309 	    case 15:
310 	       my_mode.red.offset = 10;
311 	       my_mode.red.length = 5;
312 	       my_mode.green.offset = 5;
313 	       my_mode.green.length = 5;
314 	       my_mode.blue.offset = 0;
315 	       my_mode.blue.length = 5;
316 	       break;
317 
318 	    case 16:
319 	       my_mode.red.offset = 11;
320 	       my_mode.red.length = 5;
321 	       my_mode.green.offset = 5;
322 	       my_mode.green.length = 6;
323 	       my_mode.blue.offset = 0;
324 	       my_mode.blue.length = 5;
325 	       break;
326 
327 	 #endif
328 
329 	 #if (defined ALLEGRO_COLOR24) || (defined ALLEGRO_COLOR32)
330 
331 	    case 24:
332 	    case 32:
333 	       my_mode.red.offset = 16;
334 	       my_mode.red.length = 8;
335 	       my_mode.green.offset = 8;
336 	       my_mode.green.length = 8;
337 	       my_mode.blue.offset = 0;
338 	       my_mode.blue.length = 8;
339 	       break;
340 
341 	 #endif
342 
343 	 case 8:
344 	 default:
345 	    my_mode.red.offset = 0;
346 	    my_mode.red.length = 0;
347 	    my_mode.green.offset = 0;
348 	    my_mode.green.length = 0;
349 	    my_mode.blue.offset = 0;
350 	    my_mode.blue.length = 0;
351 	    break;
352       }
353 
354       my_mode.red.msb_right = 0;
355       my_mode.green.msb_right = 0;
356       my_mode.blue.msb_right = 0;
357 
358       /* fill in the timings */
359       if (update_timings(&my_mode) != 0)
360 	 continue;
361 
362       /* try to set the mode */
363       if (ioctl(fbfd, FBIOPUT_VSCREENINFO, &my_mode) == 0) {
364 	 if (my_mode.bits_per_pixel == (unsigned)color_depth)
365 	    goto got_a_nice_mode;
366       }
367    }
368 
369    /* oops! */
370    if (_safe_gfx_mode_change) {
371       set_color_depth(original_color_depth);
372       TRACE(PREFIX_I "Restoring color depth %d\n", original_color_depth);
373    }
374 
375    ioctl(fbfd, FBIOPUT_VSCREENINFO, &orig_mode);
376    __al_linux_console_text();
377    close(fbfd);
378    ustrzcpy(allegro_error, ALLEGRO_ERROR_SIZE, get_config_text("Framebuffer resolution not available"));
379    TRACE(PREFIX_E "Resolution %dx%d not available...\n", w, h);
380    return NULL;
381 
382    got_a_nice_mode:
383 
384    /* get the fb_fix_screeninfo again, as it may have changed */
385    if (ioctl(fbfd, FBIOGET_FSCREENINFO, &fix_info) != 0) {
386       ioctl(fbfd, FBIOPUT_VSCREENINFO, &orig_mode);
387       __al_linux_console_text();
388       close(fbfd);
389       ustrzcpy(allegro_error, ALLEGRO_ERROR_SIZE, get_config_text("Framebuffer ioctl() failed"));
390       return NULL;
391    }
392 
393    /* map the framebuffer */
394    fbaddr = mmap(NULL, fix_info.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fbfd, 0);
395    if (fbaddr == MAP_FAILED) {
396       ioctl(fbfd, FBIOPUT_VSCREENINFO, &orig_mode);
397       __al_linux_console_text();
398       close(fbfd);
399       ustrzcpy(allegro_error, ALLEGRO_ERROR_SIZE, get_config_text("Can't map framebuffer"));
400       TRACE(PREFIX_E "Couldn't map framebuffer for %dx%d. Restored old "
401 	    "resolution.\n", w, h);
402       return NULL;
403    }
404 
405    /* set up the screen bitmap */
406    gfx_fbcon.w = w;
407    gfx_fbcon.h = h;
408 
409    gfx_fbcon.vid_mem = fix_info.smem_len;
410 
411    stride = fix_info.line_length;
412    v_w = my_mode.xres_virtual;
413    v_h = my_mode.yres_virtual;
414    p = fbaddr;
415 
416    if (fb_approx) {
417       v_w = w;
418       v_h = h;
419 
420       p += (my_mode.xres-w)/2 * BYTES_PER_PIXEL(color_depth) +
421 	   (my_mode.yres-h)/2 * stride;
422    }
423 
424    b = _make_bitmap(v_w, v_h, (unsigned long)p, &gfx_fbcon, color_depth, stride);
425    if (!b) {
426       ioctl(fbfd, FBIOPUT_VSCREENINFO, &orig_mode);
427       munmap(fbaddr, fix_info.smem_len);
428       __al_linux_console_text();
429       close(fbfd);
430       TRACE(PREFIX_E "Couldn't make bitmap `b', sorry.\n");
431       return NULL;
432    }
433 
434    b->vtable->acquire = __al_linux_acquire_bitmap;
435    b->vtable->release = __al_linux_release_bitmap;
436 
437    do_uconvert(fix_info.id, U_ASCII, fb_desc, U_CURRENT, sizeof(fb_desc));
438 
439    if (fb_approx) {
440       ustrzcat(fb_desc, sizeof(fb_desc), uconvert_ascii(", ", tmp));
441       ustrzcat(fb_desc, sizeof(fb_desc), get_config_text("approx."));
442    }
443 
444    gfx_fbcon.desc = fb_desc;
445 
446    /* set up the truecolor pixel format */
447    switch (color_depth) {
448 
449       #ifdef ALLEGRO_COLOR16
450 
451 	 case 15:
452 	    _rgb_r_shift_15 = my_mode.red.offset;
453 	    _rgb_g_shift_15 = my_mode.green.offset;
454 	    _rgb_b_shift_15 = my_mode.blue.offset;
455 	    break;
456 
457 	 case 16:
458 	    _rgb_r_shift_16 = my_mode.red.offset;
459 	    _rgb_g_shift_16 = my_mode.green.offset;
460 	    _rgb_b_shift_16 = my_mode.blue.offset;
461 	    break;
462 
463       #endif
464 
465       #ifdef ALLEGRO_COLOR24
466 
467 	 case 24:
468 	    _rgb_r_shift_24 = my_mode.red.offset;
469 	    _rgb_g_shift_24 = my_mode.green.offset;
470 	    _rgb_b_shift_24 = my_mode.blue.offset;
471 	    break;
472 
473       #endif
474 
475       #ifdef ALLEGRO_COLOR32
476 
477 	 case 32:
478 	    _rgb_r_shift_32 = my_mode.red.offset;
479 	    _rgb_g_shift_32 = my_mode.green.offset;
480 	    _rgb_b_shift_32 = my_mode.blue.offset;
481 	    break;
482 
483       #endif
484    }
485 
486    /* is retrace detection available? */
487  #ifdef FBIOGET_VBLANK
488    {
489       struct fb_vblank vblank;
490 
491       if (ioctl(fbfd, FBIOGET_VBLANK, &vblank) == 0)
492 	 vblank_flags = vblank.flags;
493       else
494 	 vblank_flags = 0;
495    }
496 
497    if (!(vblank_flags & (FB_VBLANK_HAVE_VBLANK | FB_VBLANK_HAVE_STICKY | FB_VBLANK_HAVE_VCOUNT)))
498  #endif
499    {
500       ustrzcat(fb_desc, sizeof(fb_desc), uconvert_ascii(", ", tmp));
501       ustrzcat(fb_desc, sizeof(fb_desc), get_config_text("no vsync"));
502    }
503 
504    /* is scrolling available? */
505    if ((my_mode.xres_virtual > my_mode.xres) ||
506        (my_mode.yres_virtual > my_mode.yres)) {
507 
508       fb_scroll(0, 0);
509 
510       if ((fb_approx) ||
511 	  ((my_mode.xres_virtual > my_mode.xres) && (!fix_info.xpanstep)) ||
512 	  ((my_mode.yres_virtual > my_mode.yres) && (!fix_info.ypanstep)))
513 	 gfx_fbcon.scroll = NULL;
514       else
515 	 gfx_fbcon.scroll = fb_scroll;
516    }
517    else
518       gfx_fbcon.scroll = NULL;
519 
520    if (fb_approx)
521       memset(fbaddr, 0, gfx_fbcon.vid_mem);
522 
523    fb_save_cmap();    /* Maybe we should fill in our default palette too... */
524 
525    /* for directcolor modes, set up a ramp colormap, so colors are mapped
526       onto themselves, so to speak */
527    if (fix_info.visual == FB_VISUAL_DIRECTCOLOR)
528       set_ramp_cmap(&fix_info, &my_mode);
529 
530    calculate_refresh_rate(&my_mode);
531 
532    /* Note: Locked in __al_linux_console_graphics(). */
533    __al_linux_display_switch_lock(FALSE, FALSE);
534 
535    TRACE(PREFIX_I "Got a bitmap %dx%dx%d\n", b->w, b->h, bitmap_color_depth(b));
536    return b;
537 }
538 
539 
540 
541 /* fb_open_device:
542  *  Opens the framebuffer device, first checking config values or
543  *  environment variables. Returns 0 on success.
544  */
fb_open_device(void)545 static int fb_open_device(void)
546 {
547    char fname[1024], tmp1[256], tmp2[256];
548    AL_CONST char *p;
549 
550    /* find the device filename */
551    p = get_config_string(uconvert_ascii("graphics", tmp1), uconvert_ascii("framebuffer", tmp2), NULL);
552 
553    if (p && ugetc(p))
554       do_uconvert(p, U_CURRENT, fname, U_ASCII, sizeof(fname));
555    else {
556       p = getenv("FRAMEBUFFER");
557 
558       if ((p) && (p[0])) {
559 	 _al_sane_strncpy(fname, p, sizeof(fname));
560       }
561       else
562 	 _al_sane_strncpy(fname, "/dev/fb0", 1024);
563    }
564 
565    /* open the framebuffer device */
566    if ((fbfd = open(fname, O_RDWR)) < 0) {
567       uszprintf(allegro_error, ALLEGRO_ERROR_SIZE, get_config_text("Can't open framebuffer %s"), uconvert_ascii(fname, tmp1));
568       TRACE(PREFIX_E "Couldn't open %s\n", fname);
569       return 1;
570    }
571 
572    /* read video mode information */
573    if ((ioctl(fbfd, FBIOGET_FSCREENINFO, &fix_info) != 0) ||
574        (ioctl(fbfd, FBIOGET_VSCREENINFO, &orig_mode) != 0)) {
575       ustrzcpy(allegro_error, ALLEGRO_ERROR_SIZE, get_config_text("Framebuffer ioctl() failed"));
576       TRACE(PREFIX_E "ioctl() failed\n");
577       return 2;
578    }
579 
580    TRACE(PREFIX_I "fb device %s opened successfully.\n", fname);
581    fb_mode_read = TRUE;
582    return 0;
583 }
584 
585 
586 
587 /* fb_exit:
588  *  Unsets the video mode.
589  */
fb_exit(BITMAP * b)590 static void fb_exit(BITMAP *b)
591 {
592    TRACE(PREFIX_I "Unsetting video mode.\n");
593 
594    /* Note: Unlocked in __al_linux_console_text(). */
595    __al_linux_display_switch_lock(TRUE, TRUE);
596 
597    ioctl(fbfd, FBIOPUT_VSCREENINFO, &orig_mode);
598    fb_restore_cmap();
599 
600    munmap(fbaddr, fix_info.smem_len);
601    close(fbfd);
602 
603    __al_linux_console_text();
604    fb_mode_read = FALSE;
605 }
606 
607 
608 
609 /* fb_save:
610  *  Saves the graphics state.
611  */
fb_save(void)612 static void fb_save(void)
613 {
614    ioctl(fbfd, FBIOPUT_VSCREENINFO, &orig_mode);
615 }
616 
617 
618 
619 /* fb_restore:
620  *  Restores the graphics state.
621  */
fb_restore(void)622 static void fb_restore(void)
623 {
624    ASSERT(!in_fb_restore);
625 
626    in_fb_restore = TRUE;
627 
628    ioctl(fbfd, FBIOPUT_VSCREENINFO, &my_mode);
629 
630    if (fb_approx)
631       memset(fbaddr, 0, gfx_fbcon.vid_mem);
632 
633    if (fix_info.visual == FB_VISUAL_DIRECTCOLOR)
634       set_ramp_cmap(&fix_info, &my_mode);
635 
636    in_fb_restore = FALSE;
637 }
638 
639 
640 
641 /* fb_scroll:
642  *  Hardware scrolling routine.
643  */
fb_scroll(int x,int y)644 static int fb_scroll(int x, int y)
645 {
646    int ret;
647 
648    my_mode.xoffset = x;
649    my_mode.yoffset = y;
650 
651    ret = ioctl(fbfd, FBIOPAN_DISPLAY, &my_mode);
652 
653    if (_wait_for_vsync) {
654       /* On a lot of graphics hardware the scroll registers don't
655        * take effect until the start of the next frame, so when you do a
656        * scroll you want to make sure you've waited at least until then.
657        * If you have reliable vsyncing, one call to vysnc is enough,
658        * but if you don't then the first call waits some time between 0
659        * and 1/60th of a second, and since it's not really synced to the
660        * retrace, this may or may not include an actual retrace. �However,
661        * by making a second call, we then wait for a full 1/60th of a second
662        * after the first call, and this is sure to include a retrace,
663        * assuming the refresh rate is at least 60Hz. �Sometimes overall
664        * we'll have waited past two retraces, or even more if the refresh
665        * rate is much higher than the fake vblank timer frequency, but
666        * it's still better than sometimes missing the retrace completely.
667        */
668       fb_vsync();
669 
670     #ifdef FBIOGET_VBLANK
671       if (!vblank_flags)
672     #endif
673          fb_vsync();
674    }
675 
676    return (ret) ? -1 : 0;
677 }
678 
679 
680 
681 /* fb_vsync:
682  *  Waits for a retrace.
683  */
fb_vsync(void)684 static void fb_vsync(void)
685 {
686    unsigned int prev;
687 
688  #ifdef FBIOGET_VBLANK
689 
690    struct fb_vblank vblank;
691 
692    if (vblank_flags & FB_VBLANK_HAVE_STICKY) {
693       /* it's really good if sticky bits are available */
694       if (ioctl(fbfd, FBIOGET_VBLANK, &vblank) != 0)
695 	 return;
696 
697       do {
698 	 if (ioctl(fbfd, FBIOGET_VBLANK, &vblank) != 0)
699 	    break;
700       } while (!(vblank.flags & FB_VBLANK_STICKY));
701    }
702    else if (vblank_flags & FB_VBLANK_HAVE_VCOUNT) {
703       /* we can read the exact scanline position, which avoids skipping */
704       if (ioctl(fbfd, FBIOGET_VBLANK, &vblank) != 0)
705 	 return;
706 
707       do {
708 	 prev = vblank.vcount;
709 	 if (ioctl(fbfd, FBIOGET_VBLANK, &vblank) != 0)
710 	    break;
711       } while (vblank.vcount >= prev);
712    }
713    else if (vblank_flags & FB_VBLANK_HAVE_VBLANK) {
714       /* boring, normal style poll operation */
715       do {
716 	 if (ioctl(fbfd, FBIOGET_VBLANK, &vblank) != 0)
717 	    break;
718       } while (vblank.flags & FB_VBLANK_VBLANKING);
719 
720       do {
721 	 if (ioctl(fbfd, FBIOGET_VBLANK, &vblank) != 0)
722 	    break;
723       } while (!(vblank.flags & FB_VBLANK_VBLANKING));
724    }
725    else
726 
727  #endif
728 
729    /* bodged implementation for when the framebuffer doesn't support it */
730    /* We must not to run this loop while returning from a VT switch as timer
731     * "interrupts" will not be running at that time so retrace_count will
732     * remain constant.
733     */
734    if (_timer_installed && !in_fb_restore) {
735       prev = retrace_count;
736 
737       do {
738       } while (retrace_count == (int)prev);
739    }
740 }
741 
742 
743 
744 /* fb_set_palette:
745  *  Sets the palette.
746  */
fb_set_palette(AL_CONST RGB * p,int from,int to,int vsync)747 static void fb_set_palette(AL_CONST RGB *p, int from, int to, int vsync)
748 {
749    unsigned short r[256], g[256], b[256];
750    struct fb_cmap cmap;
751    int i;
752    ASSERT(p);
753 
754    cmap.start = from;
755    cmap.len = to-from+1;
756    cmap.red = r;
757    cmap.green = g;
758    cmap.blue = b;
759    cmap.transp = NULL;
760 
761    for (i=0; i < (int)cmap.len; i++) {
762       r[i] = p[from+i].r << 10;
763       g[i] = p[from+i].g << 10;
764       b[i] = p[from+i].b << 10;
765    }
766 
767    fb_vsync();
768 
769    ioctl(fbfd, FBIOPUTCMAP, &cmap);
770 }
771 
772 
773 
774 static unsigned short *orig_cmap_data;      /* original palette data */
775 
776 /* fb_do_cmap:
777  *  Helper for fb_{save|restore}_cmap.
778  */
fb_do_cmap(int ioctlno)779 static void fb_do_cmap (int ioctlno)
780 {
781 	struct fb_cmap cmap;
782 	cmap.start = 0;
783 	cmap.len = 256;
784 	cmap.red = orig_cmap_data;
785 	cmap.green = orig_cmap_data+256;
786 	cmap.blue = orig_cmap_data+512;
787 	cmap.transp = NULL;
788 	ioctl(fbfd, ioctlno, &cmap);
789 }
790 
791 
792 
793 /* fb_{save|restore}_cmap:
794  *  Routines to save and restore the whole palette.
795  */
fb_save_cmap(void)796 static void fb_save_cmap (void)
797 {
798 	if (orig_cmap_data) _AL_FREE (orig_cmap_data);   /* can't happen */
799 	orig_cmap_data = _AL_MALLOC_ATOMIC (sizeof *orig_cmap_data* 768);
800 	if (orig_cmap_data)
801 		fb_do_cmap (FBIOGETCMAP);
802 }
803 
804 
805 
fb_restore_cmap(void)806 static void fb_restore_cmap (void)
807 {
808 	if (orig_cmap_data) {
809 		fb_do_cmap (FBIOPUTCMAP);
810 		_AL_FREE (orig_cmap_data);
811 		orig_cmap_data = NULL;
812 	}
813 }
814 
815 
816 
817 static struct timings {
818    char config_item[1024];
819    int pixclock;
820    int left_margin;
821    int right_margin;
822    int upper_margin;
823    int lower_margin;
824    int hsync_len;
825    int vsync_len;
826    int vmode;
827    int sync;
828    int xres;
829    int yres;
830 } _fb_current_timings;
831 
832 static struct timings temp_timings;
833 
834 
835 static int read_config_file (int w, int h);
836 static int read_fbmodes_file (int w, int h);
837 static int tweak_timings (int w, int h);
838 
839 
840 /* _fb_get_timings:
841  *  Returns a pointer to a struct as above containing timings for the given
842  *  resolution, or NULL on error.
843  */
_fb_get_timings(int w,int h)844 static struct timings *_fb_get_timings (int w, int h)
845 {
846    /* First try the config file */
847    if (read_config_file (w, h)) return &temp_timings;
848 
849    /* Failing that, try fb.modes */
850    if (read_fbmodes_file (w, h)) return &temp_timings;
851 
852    /* Still no luck, so tweak the current mode instead */
853    if (tweak_timings (w, h)) return &temp_timings;
854    return NULL;
855 }
856 
857 
858 /* read_config_file:
859  *  Assigns timing settings from the config file or returns 0.
860  */
read_config_file(int w,int h)861 static int read_config_file (int w, int h)
862 {
863    char tmp[128];
864    char **argv;
865    int argc;
866 
867    /* Let the setup program know what config string we read for this mode */
868    uszprintf(temp_timings.config_item, sizeof(temp_timings.config_item), uconvert_ascii("fb_mode_%dx%d", tmp), w, h);
869 
870    /* First try the config file */
871    argv = get_config_argv (NULL, temp_timings.config_item, &argc);
872    if (argv) {
873       #define get_info(info) if (*argv) temp_timings.info = ustrtol (*argv++, NULL, 10)
874       get_info(pixclock);
875       get_info(left_margin);
876       get_info(right_margin);
877       get_info(upper_margin);
878       get_info(lower_margin);
879       get_info(hsync_len);
880       get_info(vsync_len);
881 
882       if (*argv) {
883 	 if (!ustrcmp (*argv, uconvert_ascii("none", tmp)))
884 	    temp_timings.vmode = FB_VMODE_NONINTERLACED;
885 	 else if (!ustrcmp (*argv, uconvert_ascii("interlaced", tmp)))
886 	    temp_timings.vmode = FB_VMODE_INTERLACED;
887 	 else if (!ustrcmp (*argv, uconvert_ascii("doublescan", tmp)))
888 	    temp_timings.vmode = FB_VMODE_DOUBLE;
889 	 argv++;
890       } else
891 	 temp_timings.vmode = FB_VMODE_NONINTERLACED;
892 
893       get_info(sync);
894       #undef get_info
895 
896       temp_timings.xres = w;
897       temp_timings.yres = h;
898 
899       return 1;
900    }
901    return 0;
902 }
903 
904 
905 /* helper to read the relevant parts of a line from fb.modes */
get_line(FILE * file)906 static char *get_line (FILE *file)
907 {
908    static char buffer[1024];
909    char *ch;
910    ASSERT (file);
911 
912    if (!fgets (buffer, sizeof (buffer), file))
913       return 0;
914 
915    /* if there's no eol, get one before continuing */
916    if (!strchr (buffer, '\n') && strlen (buffer) == 1 + sizeof (buffer)) {
917       char waste[128], *ret;
918 
919       do {
920 	 ret = fgets (waste, sizeof (waste), file);
921       } while (ret && !strchr (waste, '\n'));
922       /* this doesn't actually exit because we still have buffer */
923    }
924 
925    if ((ch = strpbrk (buffer, "#\n")))
926       *ch = 0;
927 
928    ch = buffer;
929    while (uisspace(*ch)) ch++;
930    return ch;
931 }
932 
933 /* read_fbmodes_file:
934  *  Assigns timing settings from the fbmodes file or returns 0.
935  */
read_fbmodes_file(int w,int h)936 static int read_fbmodes_file (int w, int h)
937 {
938    char *mode_id = NULL;
939    char *geometry = NULL;
940    char *timings = NULL;
941    int sync = 0, vmode = 0;
942    char *s, *t;
943    FILE *fbmodes;
944    int ret = 0;
945 
946    fbmodes = fopen ("/etc/fb.modes", "r");
947    if (!fbmodes) return 0;
948 
949    do {
950       s = get_line (fbmodes);
951       if (!s) break;
952       t = strchr (s, ' ');
953       if (t) {
954 	 *t++ = '\0';
955 	 while (uisspace(*t)) t++;
956       } else {
957 	 t = strchr (s, '\0');
958       }
959 
960       if (!strcmp (s, "mode")) {
961 	 _AL_FREE (mode_id);
962 	 _AL_FREE (geometry);
963 	 _AL_FREE (timings);
964 	 mode_id = strdup (t);
965 	 geometry = timings = NULL;
966 	 sync = 0;
967 	 vmode = FB_VMODE_NONINTERLACED;
968       } else if (!strcmp (s, "endmode")) {
969 	 if (geometry && timings) {
970 	    int mw, mh;
971 	    sscanf (geometry, "%d %d", &mw, &mh);
972 	    if ((mw == w) && (mh == h)) {
973 	       sscanf (timings, "%d %d %d %d %d %d %d",
974 		  &temp_timings.pixclock,
975 		  &temp_timings.left_margin,
976 		  &temp_timings.right_margin,
977 		  &temp_timings.upper_margin,
978 		  &temp_timings.lower_margin,
979 		  &temp_timings.hsync_len,
980 		  &temp_timings.vsync_len
981 	       );
982 	       temp_timings.sync = sync;
983 	       temp_timings.vmode = vmode;
984 	       temp_timings.xres = w;
985 	       temp_timings.yres = h;
986 	       ret = 1;
987 	       s = NULL;
988 	    }
989 	 }
990 	 _AL_FREE (mode_id);
991 	 _AL_FREE (geometry);
992 	 _AL_FREE (timings);
993 	 mode_id = geometry = timings = NULL;
994       } else if (!strcmp (s, "geometry")) {
995 	 _AL_FREE (geometry);
996 	 geometry = strdup (t);
997       } else if (!strcmp (s, "timings")) {
998 	 _AL_FREE (timings);
999 	 timings = strdup (t);
1000       } else if (!strcmp (s, "hsync")) {
1001 	 #define set_bit(var,bit,on) var = ((var) &~ (bit)) | ((on) ? bit : 0)
1002 	 set_bit (sync, FB_SYNC_HOR_HIGH_ACT, t[0] == 'h');
1003       } else if (!strcmp (s, "vsync")) {
1004 	 set_bit (sync, FB_SYNC_VERT_HIGH_ACT, t[0] == 'h');
1005       } else if (!strcmp (s, "csync")) {
1006 	 set_bit (sync, FB_SYNC_COMP_HIGH_ACT, t[0] == 'h');
1007       } else if (!strcmp (s, "gsync")) {
1008 	 set_bit (sync, FB_SYNC_ON_GREEN, t[0] == 'h');
1009       } else if (!strcmp (s, "extsync")) {
1010 	 set_bit (sync, FB_SYNC_EXT, t[0] == 't');
1011       } else if (!strcmp (s, "bcast")) {
1012 	 set_bit (sync, FB_SYNC_BROADCAST, t[0] == 't');
1013       } else if (!strcmp (s, "laced")) {
1014 	 if (t[0] == 't') vmode = FB_VMODE_INTERLACED;
1015       } else if (!strcmp (s, "double")) {
1016 	 if (t[0] == 't') vmode = FB_VMODE_DOUBLE;
1017       }
1018    } while (s);
1019 
1020    _AL_FREE (mode_id);
1021    _AL_FREE (geometry);
1022    _AL_FREE (timings);
1023 
1024    fclose (fbmodes);
1025    return ret;
1026 }
1027 
1028 
1029 /* tweak_timings:
1030  *  Tweak the timings to match the mode we want to set.  Only works if
1031  *  the parent is a higher resolution.
1032  */
tweak_timings(int w,int h)1033 static int tweak_timings (int w, int h)
1034 {
1035    if ((w <= temp_timings.xres) && (h <= temp_timings.yres)) {
1036       int diff = temp_timings.xres - w;
1037       temp_timings.left_margin += diff/2;
1038       temp_timings.right_margin += diff/2 + diff%2;
1039       temp_timings.xres = w;
1040 
1041       diff = temp_timings.yres - h;
1042       temp_timings.upper_margin += diff/2;
1043       temp_timings.lower_margin += diff/2 + diff%2;
1044       temp_timings.yres = h;
1045 
1046       return 1;
1047    }
1048    return 0;
1049 }
1050 
1051 
set_default_timings(void)1052 static void set_default_timings (void)
1053 {
1054    char tmp[128];
1055 
1056    #define cp(x) temp_timings.x = orig_mode.x
1057    cp(pixclock);
1058    cp(left_margin);
1059    cp(right_margin);
1060    cp(upper_margin);
1061    cp(lower_margin);
1062    cp(hsync_len);
1063    cp(vsync_len);
1064    cp(vmode);
1065    cp(sync);
1066    cp(xres);
1067    cp(yres);
1068    #undef cp
1069    uszprintf(temp_timings.config_item, sizeof(temp_timings.config_item), uconvert_ascii("fb_mode_%dx%d", tmp),
1070 	     orig_mode.xres, orig_mode.yres);
1071 }
1072 
1073 
1074 /* update_timings:
1075  *  Updates the timing section of the mode info.  Maybe we can make
1076  *  this algorithmic, as a backup, at some point.  For now it searches
1077  *  the config file or /etc/fb.modes for the data.
1078  *
1079  *  We could make the init routine give up if the data isn't there, or
1080  *  use an algorithmic guesser.
1081  *
1082  *  If we go right ahead with this system, I think the setup program
1083  *  should offer quite a few options -- dotclock probing might be useful,
1084  *  along with the functionality of xvidtune.
1085  */
update_timings(struct fb_var_screeninfo * mode)1086 static int update_timings(struct fb_var_screeninfo *mode)
1087 {
1088    struct timings *t;
1089    ASSERT(mode);
1090 
1091    set_default_timings();
1092    t = _fb_get_timings (mode->xres, mode->yres);
1093    if (!t) return -1;
1094 
1095    /* for debugging, maybe for the setup program too */
1096    memcpy (&_fb_current_timings, t, sizeof(struct timings));
1097 
1098    /* update the mode struct */
1099    mode->pixclock = t->pixclock;
1100    mode->left_margin = t->left_margin;
1101    mode->right_margin = t->right_margin;
1102    mode->upper_margin = t->upper_margin;
1103    mode->lower_margin = t->lower_margin;
1104    mode->hsync_len = t->hsync_len;
1105    mode->vsync_len = t->vsync_len;
1106    mode->vmode = t->vmode;
1107    mode->sync = t->sync;
1108 
1109    return 0;
1110 }
1111 
1112 
1113 /* I'm not sure whether these work or not -- my Matrox seems capable
1114  * of setting whatever you ask it to. */
1115 
1116 #if 0
1117 
1118 static int _fb_get_pixclock(void)
1119 {
1120    struct fb_var_screeninfo mode;
1121    if (ioctl (fbfd, FBIOGET_VSCREENINFO, &mode)) return -1;
1122    return mode.pixclock;
1123 }
1124 
1125 static void _fb_set_pixclock(int new_val)
1126 {
1127    struct fb_var_screeninfo mode;
1128    if (ioctl (fbfd, FBIOGET_VSCREENINFO, &mode)) return;
1129    mode.pixclock = new_val;
1130    if (ioctl (fbfd, FBIOPUT_VSCREENINFO, &mode)) return;
1131 }
1132 
1133 #endif
1134 
1135 
1136 
1137 #ifdef ALLEGRO_MODULE
1138 
1139 /* _module_init:
1140  *  Called when loaded as a dynamically linked module.
1141  */
_module_init(int system_driver)1142 void _module_init(int system_driver)
1143 {
1144    if (system_driver == SYSTEM_LINUX) {
1145       _unix_register_gfx_driver(GFX_FBCON, &gfx_fbcon, TRUE, TRUE);
1146       /* Register resolution and colour depth getters.
1147        * This is a bit ugly because these are actually part of the
1148        * system driver rather than the graphics driver...
1149        */
1150       system_linux.desktop_color_depth = __al_linux_get_fb_color_depth;
1151       system_linux.get_desktop_resolution = __al_linux_get_fb_resolution;
1152    }
1153 }
1154 
1155 #endif      /* ifdef ALLEGRO_MODULE */
1156 
1157 
1158 
1159 #endif      /* if (defined ALLEGRO_LINUX_FBCON) ... */
1160