fondo de menuRastersoft

Cómo programar el framebuffer en Linux

En este documento intentare explicar como crear drivers de FrameBuffer para Linux. No soy un autentico experto, puesto que lo unico que he hecho hasta ahora es modificar un driver existente para que soporte mi tarjeta. Asi pues, cualquier mejora, correccion de errores y demas seran bienvenidas con gran jubilo y algarabia por mi parte :-)


Indice
  1. Cambios
    1. de la version 1.0 a la version 1.1
  2. Introduccion.
  3. Manejo de un dispositivo FrameBuffer
  4. Escribiendo un driver de FrameBuffer
    1. Añadiendo nuestro codigo
    2. INCLUDEs necesarios
    3. Estructuras de datos de un driver FrameBuffer
    4. Empezando a escribir el driver.
      1. mitarjetafb_setup
      2. mitarjetafb_init
      3. mitarjetafb_open
      4. mitarjetafb_close
      5. mitarjetafb_get_fix
      6. mitarjetafb_get_var
      7. mitarjetafb_set_var
      8. mitarjetafb_get_cmap
      9. mitarjetafb_set_cmap
      10. mitarjetafb_pan_display
      11. changevar
      12. switch_con
      13. updatevar
      14. blank
      15. las otras funciones
  5. Probando el driver de Framebuffer
  6. Referencias.

0. Cambios

0.1. de la version 1.0 a la version 1.1

Se ha añadido todo lo referente a la paleta de colores ('colormap') de la consola. Revisar los apartados 3.4.2, 3.4.7, 3.4.8 y 3.4.9.

1. Introduccion

Linux es un sistema operativo que fue creado originalmente para funcionar sobre maquinas de tipo PC. Debido a eso, la interfaz de consola que incorpora el kernel esta fuertemente orientada al modo texto de las tarjetas de PC.

Sin embargo, cuando se empezo a portar Linux a otras maquinas, como los Macintosh, las estaciones Sparc, etc, los desarrolladores se encontraron con que el modo texto originario de los PCs no existia en esas maquinas, sino que estas siempre trabajaban en modo grafico.

De esta forma, se decidio implementar un metodo distinto de acceso a la consola, el cual simulase un modo texto sobre una pantalla grafica, pero permitiendo un acceso flexible y, sobre todo, generalizado, para simplificar el porte del kernel a nuevas arquitecturas, asi como para permitir una mayor facilidad a la hora de programar aplicaciones graficas en consola, o un servidor X generico que trabajase sobre esta nueva interfaz. El FrameBuffer habia nacido.

2. Manejo de un dispositivo FrameBuffer

Como cualquier otro dispositivo, el FrameBuffer tiene una serie de ficheros asociados en el directorio /dev. Estos son fb, fb0,..., fb31. Normalmente, /dev/fb es un enlace simbolico que apunta a fb0.

Cada tarjeta grafica tiene asociado un fichero fb. Esto permite acceder a cada una por separado de forma sencilla.

La forma de trabajar con un FrameBuffer es en extremo simple:

3. Escribiendo un driver de FrameBuffer

3.1. Añadiendo nuestro codigo

Para añadir nuestro fichero con el codigo del driver, hemos de modificar varios ficheros en el kernel, en el directorio drivers/video. Estos ficheros son:

Por ultimo, decir que el texto que va en el #ifdef (en este caso lo he llamado "mitarjetafb_id") es un identificador que permite saber a que driver van destinados una serie de parametros dados al kernel. ¿Que es esto? Simple: en el fichero lilo.conf podemos añadir una o varias lineas con opciones para el kernel. Una (o mas) de estas lineas pueden estar destinadas al driver de Framebuffer, y para que el nucleo pueda identificarlas, usara esa cadena. Asi pues, si tenemos dos drivers de framebuffer compilados en el kernel y en el lilo.conf ponemos las lineas:

append="video=sis6326fb:640x480@70"
append="video=matroxfb:800x600@65"

la primera opcion se le pasara al driver de framebuffer que vaya definido como "sis6326fb", mientras que la segunda se le pasara unicamente al driver que haya sido definido como "matroxfb".

3.2. INCLUDEs necesarios

Para poder compilar un driver de FrameBuffer son necesarios varios #include. En concreto, los absolutamente imprescindibles son:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/tty.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/fb.h>
#include <linux/console.h>
#include <linux/selection.h>
#include <linux/ioport.h>
#include <linux/init.h>

#include <asm/io.h>
#include <asm/mtrr.h>

#include <video/fbcon.h>
#include <video/fbcon-cfb8.h>
#include <video/fbcon-cfb16.h>
#include <video/fbcon-cfb24.h>
#include <video/fbcon-cfb32.h>
#include <video/fbcon-mac.h>

Y, ademas, pueden ser necesarios otros, como por ejemplo:

#include <linux/string.h> // para operaciones con cadenas
#include <linux/pci.h> // para inicializar tarjetas PCI

3.3. Estructuras de datos de un driver FrameBuffer

Son ocho: fb_fix_screeninfo, fb_var_screeninfo, display, fb_info, fb_bitfield, fb_cmap, fb_ops y fb_monspecs.

fb_fix_screeninfo contiene informacion diversa que no es modificable directamente por el usuario. Sus variables son:

struct fb_fix_screeninfo {
  char id[16];
  unsigned long smem_start;
  __u32 smem_len;
  __u32 type;
  __u32 type_aux;
  __u32 visual;
  __u16 xpanstep;
  __u16 ypanstep;
  __u16 ywrapstep;
  __u32 line_length;
  unsigned long mmio_start;
  __u32 mmio_len;
  __u32 accel;
  __u16 reserved[3];
};

id:string de identificacion del driver. (por ejemplo "TT Builtin")
smem_start:direccion fisica del framebuffer. Suele ser devuelta por las funciones PCI, o las correspondientes al bus usado.
smem_len:longitud en bytes del framebuffer.
type:tipo de framebuffer. Puede ser:
FB_TYPE_PACKED_PIXELS : el formato clasico (1,2,3 o 4 bytes por cada pixel, uno detras de otro).
FB_TYPE_PLANES : distribucion por planos (cada plano contiene un byte del pixel)
FB_TYPE_INTERLEAVED_PLANES : lo mismo, pero con entrelazado.
FB_TYPE_TEXT : FrameBuffer en modo texto
FB_TYPE_VGA_PLANES : planos de tipo EGA/VGA (un bit en cada plano).
type_aux:entrelazado para planos entrelazados.
visual:organizacion de los bytes de cada pixel. Puede ser:
FB_VISUAL_MONO01 : monocromo (0-> blanco, 1->negro)
FB_VISUAL_MONO10 : monocromo (1-> blanco, 0->negro)
FB_VISUAL_TRUECOLOR : Color verdadero (15/16/24/32 bits)
FB_VISUAL_PSEUDOCOLOR : Color por paleta (normalmente 8 bits)
FB_VISUAL_DIRECTCOLOR : DirectColor (como en Atari)
FB_VISUAL_STATIC_PSEUDOCOLOR : Color por paleta, pero de solo lectura.
xpanstep:cero si no se soporta scroll horizontal por hardware
ypanstep:cero si no se soporta scroll vertical por hardware
ywrapstep:cero si no se soporta recorte de pantalla por hardware.
line_length:longitud en bytes de una linea completa (resx * bytesporpixel)
mmio_start:direccion fisica de la zona de mapeo en memoria de I/O
mmio_len:longitud de la zona de mapeo en memoria de I/O
accel:tipo de aceleracion soportada. Identifica al driver e indica, por tanto, que funciones de aceleracion por hardware se soportan.
reserved:reservado para futuras ampliaciones.

fb_var_screeninfo contiene toda la informacion que el usuario puede modificar. Se usa para pasar al driver informacion sobre el modo grafico que se quiere establecer. La informacion de fb_fix_screeninfo es obtenida por el driver en base a la que contiene una estructura fb_var_screeninfo.

struct fb_var_screeninfo {
  __u32 xres;
  __u32 yres;
  __u32 xres_virtual;
  __u32 yres_virtual;
  __u32 xoffset;
  __u32 yoffset;
  __u32 bits_per_pixel;
  __u32 grayscale;
  struct fb_bitfield red;
  struct fb_bitfield green;
  struct fb_bitfield blue;
  struct fb_bitfield transp;
  __u32 nonstd;
  __u32 activate;
  __u32 height;
  __u32 width;
  __u32 accel_flags;
  __u32 pixclock;
  __u32 left_margin;
  __u32 right_margin;
  __u32 upper_margin;
  __u32 lower_margin;
  __u32 hsync_len;
  __u32 vsync_len;
  __u32 sync;
  __u32 vmode;
  __u32 reserved[6];
};

xres:resolucion horizontal fisica del modo grafico, en pixels.
yres:resolucion vertical fisica del modo grafico, en pixels.
xres_virtual:resolucion horizontal virtual del modo grafico, en pixels. Suele ser igual que xres.
yres_virtual:resolucion vertical virtual del modo grafico, en pixels. Suele ser igual que yres.
xoffset:offset desde el borde virtual hasta el borde fisico.
yoffset:lo mismo, pero en vertical.
bits_per_pixel:numero de bits ocupados por cada pixel.
grayscale:si es distinto de cero, el modo es en grises, en vez de color.
red:
green:
blue:
transp:
contiene informacion sobre el numero de bits y la posicion de cada color y la transparencia. Si el modo es PSEUDOCOLOR, solo la longitud es importante (ver estructura fb_bitfield).
nonstd:si es distinto de cero, es que el formato de pixel no es estandar.
activate:indica la accion a realizar:
FB_ACTIVATE_NOW : activa los cambios ahora.
FB_ACTIVATE_NXTOPEN : activa los cambios la proxima vez que se abra el dispositivo.
FB_ACTIVATE_TEST : no actives. Tan solo indica si los valores son o no validos.
height:ancho de la pantalla en milimetros.
width:alto de la pantalla en milimetros.
accel_flags:flags de aceleracion hardware.
pixclock:periodo de reloj (en picosegundos).
left_margin:pixclocks desde el sincronismo horizontal hasta el inicio de la imagen.
right_margin:pixclocks desde el fin de la imagen hasta el sincronismo horizontal.
upper_margin:pixclocks desde el sincronismo vertical hasta el inicio de la imagen.
lower_margin:pixclocks desde el fin de la imagen hasta el sincronismo vertical.
hsync_len:duracion del sincronismo horizontal (en pixclocks).
vsync_len:duracion del sincronismo vertical (en pixclocks).
sync:modo de sincronismos:
FB_SYNC_HOR_HIGH_ACT : sincronismo horizontal activo en estado alto.
FB_SYNC_VERT_HIGH_ACT : sincronismo vertical activo en estado alto.
FB_SYNC_EXT : usar sincronismo externo
FB_SYNC_COMP_HIGH_ACT : mezclar sincronismos en una sola señal.
FB_SYNC_BROADCAST : tiempos de television:
  vtotal = 144d/288n/576i => PAL
  vtotal = 121d/242n/484i => NTSC
FB_SYNC_ON_GREEN : enviar los sincronismos mezclados con la señal de verde.
vmode:informacion extra:
FB_VMODE_NONINTERLACED : modo no entrelazado
FB_VMODE_INTERLACED : modo entrelazado
FB_VMODE_DOUBLE : double scan
FB_VMODE_YWRAP : usar ywrap en vez de desplazar la pantalla
FB_VMODE_SMOOTH_XPAN : soportado desplazamiento suave
FB_VMODE_CONUPDATE : no actualizar x/yoffset

La estructura display es una parte extremadamente importante en el driver FrameBuffer, puesto que contiene todas las caracteristicas del modo grafico asociadas a cada una de las consolas virtuales de Linux. Existe una variable global (que veremos con calma luego) denominada fb_display[], que contiene un array de estructuras display, una para cada una de las consolas. Es esta estructura la que permite al sistema operativo conmutar entre los modos graficos adecuados al cambiar de una consola a otra.

De esta estructura solo nos interesa una parte, pues la otra es actualizada automaticamente por el driver de consola. De ahi los puntos suspensivos del final de la estructura.

struct display {
  struct fb_var_screeninfo var;
  struct fb_cmap cmap;
  char *screen_base;
  int visual;
  int type;
  int type_aux;
  u_short ypanstep;
  u_short ywrapstep;
  u_long line_length;
  u_short can_soft_blank;
  u_short inverse;
  struct display_switch *dispsw;
  void *dispsw_data;

 #if 0
   struct fb_fix_cursorinfo fcrsr;
   struct fb_var_cursorinfo *vcrsr;
   struct fb_cursorstate crsrstate;
 #endif

  u_long next_line;

  [...]

};

var:es una estructura de tipo fb_var_screeninfo, que contiene toda la informacion sobre el modo grafico actual. Ha de ser actualizada cada vez que se cambia el modo grafico, o cuando se conmuta de consola, a excepcion de los campos yoffset y vmode, que son actualizados por fbcon.c.
cmap:contiene la paleta de la consola, salvo que el modo sea TRUECOLOR, en cuyo caso su longitud es cero, para indicar que no hay paleta.
screen_base:puntero a char con la direccion virtual de la memoria FrameBuffer. Este puntero se obtiene como resultado de la funcion:

direccion_virtual = ioremap(direccion_fisica,longitud);

visual:ver FB_VISUAL_*
type:ver FB_TYPE_*
type_aux:entrelazado para planos entrelazados.
ypanstep:cero si no se soporta scroll vertical por hardware
ywrapstep:cero si no se soporta recorte de pantalla por hardware. Esto es, si la tarjeta no soporta que, al llegar al final de la memoria y seguir escribiendo, empiece a escribir de nuevo al principio.
line_length:longitud en bytes de una linea completa (resx * bytesporpixel)
can_soft_blank:cero si no se soporta poner la pantalla en negro por hardware.
inverse:si es !=0, se usara color negro sobre fondo blanco para el texto.
dispsw:funciones de bajo nivel para la consola. Se explica con mas detalle en la funcion mitarjetafb_set_var.
dispsw_data:informacion de ayuda opcional para dispsw. Se explica con mas detalle en la funcion mitarjetafb_set_var.
next_line: bytes a sumar a una direccion para pasar a la siguiente linea. Suele ser igual a line_length, salvo que xres_virtual sea distinto que xres. Pese a todo, no tengo claro si este campo es necesario actualizarlo, o si lo hace fbcon.c automaticamente.

La estructura fb_info contiene informacion necesaria para inicializar el sistema de FrameBuffer.

struct fb_info {
  char modename[40];
  kdev_t node;
  int flags;
  int open;
  struct fb_var_screeninfo var;
  struct fb_fix_screeninfo fix;
  struct fb_monspecs monspecs;
  struct fb_cmap cmap;
  struct fb_ops *fbops;
  char *screen_base;
  struct display *disp;
  struct vc_data *display_fg;
  char fontname[40];
  devfs_handle_t devfs_handle;
  devfs_handle_t devfs_lhandle;
  int (*changevar)(int);
  int (*switch_con)(int, struct fb_info*);
  int (*updatevar)(int, struct fb_info*);
  void (*blank)(int, struct fb_info*);
  void *pseudo_palette;
  void *par;
};

modename:nombre del modo actual.
open:Indica si ya ha sido abierto el driver o no.
var:var del modo actual
fix:fix del modo actual
monspecs:capacidades actuales del monitor.
cmap:paleta actual.
fbops: estructura conteniendo punteros a las diversas rutinas del driver.
screen_base: direccion virtual de la memoria FrameBuffer
disp:parametros iniciales para el FrameBuffer
display_fg:consola visible en este display (?)
fontname:tipo de letra a usar por defecto.
changevar:puntero a una rutina que se llama cada vez que cambia var (por ejemplo, cuando un programa cambia el modo grafico). Puede ser NULL).
switch_con:puntero a una rutina que es llamada cada vez que se cambia de consola.
updatevar:puntero a una rutina que es llamada para actualizar el modo (por ejemplo, al desplazar la pantalla).
blank:puntero a una rutina que es llamada para encender, poner negro o apagar el monitor (para los modos de ahorro de energia).

La estructura fb_bitfield contiene la descripcion del formato de un pixel. Permite saber que bits corresponden a uno de los tres componentes de un color (R, G o B).

struct fb_bitfield {
  __u32 offset;
  __u32 length;
  __u32 msb_right;
};

offset: bit de inicio de la componente. Por ejemplo, si es 0, la componente correspondiente empezará en el bit cero. Si es 3, empezará en el bit tres, y abrá que rotar el dato correspondiente a la componente 3 posiciones a la izquierda o a la derecha, en funcion del campo msb_right. Este campo no se usa en modos con paleta (PSEUDOCOLOR).
length:longitud en bits de la componente. Unido al bit de inicio, permite saber que bits del pixel corresponden a esta componente.
msb_right:si es distinto de cero, el bit de mayor peso esta a la derecha. Si no, esta a la izquierda.

La estructura fb_cmap contiene una paleta de colores. Cada componente se define con 16 bits en vez de 8. El driver, al asignar una paleta, debe recortar el numero de bits de forma que se ajuste al tamaño de las entradas en el DAC de la tarjeta.

Esta estructura se usa en los modos con paleta para almacenar la paleta que esta en el DAC de la tarjeta, y en los modos que no usan paleta para almacenar los colores que usara la consola (normalmente los 16 primeros).

struct fb_cmap {
  __u32 start; /* First entry */
  __u32 len; /* Number of entries */
  __u16 *red; /* Red values */
  __u16 *green;
  __u16 *blue;
  __u16 *transp; /* transparency, can be NULL */
};

start: indice inicial de la paleta almacenada en la estructura. Este campo existe porque esta estructura tambien se usa para asignar una paleta, y puede no interesarnos cambiar todos, sino solo unos cuantos.
len:numero de entradas en la estructura. Este campo existe por lo mismo que el anterior.
red:puntero a una zona de memoria con las componentes rojas.
green:puntero a una zona de memoria con las componentes verdes.
blue:puntero a una zona de memoria con las componentes azules.
transp:puntero a una zona de memoria con la transparencia del color. Puede ser NULL.

La estructura fb_ops contiene punteros a las distintas funciones que componen el driver, tales como las destinadas a cambiar el modo grafico, a cambiar la paleta, etc. Todas estas funciones seran definidas con mas detalle en los apartados posteriores.

struct fb_ops {
  struct module *owner;
  int (*fb_open)(struct fb_info *info, int user);
  int (*fb_release)(struct fb_info *info, int user);
  int (*fb_get_fix)(struct fb_fix_screeninfo *fix, int con, struct fb_info *info);
  int (*fb_get_var)(struct fb_var_screeninfo *var, int con, struct fb_info *info);
  int (*fb_set_var)(struct fb_var_screeninfo *var, int con, struct fb_info *info);
  int (*fb_get_cmap)(struct fb_cmap *cmap, int kspc, int con, struct fb_info *info);
  int (*fb_set_cmap)(struct fb_cmap *cmap, int kspc, int con, struct fb_info *info);
  int (*fb_pan_display)(struct fb_var_screeninfo *var, int con, struct fb_info *info);
  int (*fb_ioctl)(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg, int con, struct fb_info *info);
  int (*fb_mmap)(struct fb_info *info, struct file *file, struct vm_area_struct *vma);
  int (*fb_rasterimg)(struct fb_info *info, int start);
};

owner:contiene el nombre del driver de FrameBuffer. Lo normal es asignarlo con la macro THIS_MODULE (ver ejemplo en la seccion 3.4.1).
fb_open:funcion que se llama cuando se abre el dispositivo. Lo normal es llamar a MOD_INC_USE_COUNT, si bien no es necesario definirla.
fb_release:funcion que se llama cuando se cierra el dispositivo. Lo normal es llamar a MOD_DEC_USE_COUNT, si bien no es necesario definirla.
fb_get_fix:puntero a una funcion que devuelve la estructura fix.
fb_get_var:puntero a una funcion que devuelve la estructura var.
fb_set_var:puntero a una funcion que asigna una nueva estructura var y, por tanto, cambia el modo grafico actual.
fb_get_cmap:puntero a una funcion que obtiene la paleta actual.
fb_set_cmap:puntero a una funcion que asigna una nueva paleta.
fb_pan_display:puntero a una funcion que desplaza la pantalla (para hacer scroll). No es obligatorio definirla.
fb_ioctl:puntero a una funcion que realiza una funcion IOCTL especifica de la tarjeta. No es obligatorio definirla.
fb_mmap:puntero a una funcion que realiza el mapeado de la memoria FB. Si se define, al llamar a mmap() se ejecutara esta funcion en vez de la original del nucleo. No es obligatorio definirla.
fb_rasterimg:puntero a una funcion de BITBLT (puede llamar al acelerador hardware de la tarjeta) que copia una zona de la pantalla en otra. No es obligatorio definirla.

La estructura fb_monspecs contiene informacion sobre el monitor. En concreto, las frecuencias horizontal y vertical maximas que soporta. Esto se usa para evitar quemar un monitor si se asignan frecuencias de refresco demasiado elevadas. Estos parametros deberian ser obtenidos a traves de las funciones DDC del monitor; sin embargo, para obtener la especificacion de dicha norma hay que pagar (ANDA, NO ME DIGAS!!!!!), con lo que lo mejor es poner por defecto las frecuencias de los monitores clasicos de VGA y permitir que el usuario seleccione otras a traves de la linea de parametros en el LILO.

struct fb_monspecs {
  __u32 hfmin;
  __u32 hfmax;
  __u16 vfmin;
  __u16 vfmax;
  unsigned dpms : 1;
};

hfmin:frecuencia horizontal minima soportada por el monitor (en Hz).
hfmax:frecuencia horizontal maxima soportada por el monitor (en Hz).
vfmin:frecuencia vertical minima soportada por el monitor (en Hz).
vfmax:frecuencia vertical maxima soportada por el monitor (en Hz).
dpms:si vale 1, el monitor soporta DPMS (ahorro de energia).

3.4. Empezando a escribir el driver.

Una vez que sabemos todo lo anterior, nos queda por indicar una serie de detalles antes de empezar a escribir nuestras funciones:

A continuacion, pasare a describir el cometido de cada una de las funciones necesarias en el driver.

3.4.1. mitarjetafb_setup

El prototipo de esta funcion es

  int __init mitarjetafb_setup(char *);

Esta funcion recibe como parametro un puntero a una cadena ASCIIZ (ASCII terminada en ), la cual contiene la linea de parametros que se pasa en LILO. Esta linea es del estilo

  append="video=mitarjetafb_id:..."

siendo "mitarjetafb_id" la cadena que define la tarjeta, tal y como fue escrita en el fichero fbmem.c.

El nucleo entregara los parametros indicados al driver que se registre como mitarjeta (ver el nombre en fb_info, la estructura usada para registrar el driver).

Lo ideal es especificar el modo por defecto con

  video=mitarjeta: <resx>x<resy>[-<bpp>][@<refresh>]

Ademas de esto, se pueden indicar todo tipo de parametros que el programador desee. Lo unico importante es documentarlos bien en un fichero de texto ;-)

Para separar las distintas opciones, se puede usar la funcion strtok() y luego strcmp o strncmp.

Esta funcion inicializara las diversas estructuras, de modo que todo quede listo para poder llamar a la siguiente funcion: mitarjetafb_init.

Por ultimo, indicar que esta funcion NO sera llamada si no existe ninguna linea append para este driver, pero la siguiente funcion (mitarjetafb_init) SIEMPRE sera llamada.

3.4.2. mitarjetafb_init

El prototipo de esta funcion es

  int __init mitarjetafb_init(void);

Ha de inicializar la tarjeta grafica en el modo grafico por defecto, y su espacio de memoria y puertos (por ejemplo, usando las funciones de la biblioteca pci.h). Si no identifica la tarjeta, ha de retornar un 0.

A continuacion, debemos pedir acceso a la memoria y puertos. La memoria la pedimos con la funcion:

  int request_mem_region(video_base, video_size, "mitarjetafb_id");

usando como video_base la direccion FISICA de la memoria de la tarjeta. Si esta funcion nos devuelve 0, debemos imprimir un error (usando printk() de la misma forma que usariamos printf) y devolver un error (return -EBUSY).

A continuacion, obtendriamos la direccion VIRTUAL de la memoria de la tarjeta. Para ello, llamamos a la funcion:

  char * ioremap(video_base, video_size);

Esta funcion toma como parametros la direccion FISICA de la memoria y su tamaño, y nos devuelve un puntero a una zona de memoria direccionable desde el procesador (no olvidemos que, en modo protegido, trabajamos con una serie de direcciones virtuales que son convertidas a direcciones fisicas mediante las tablas de selectores y de descriptores de los procesadores 386 y superiores).

Si esta funcion devuelve NULL (o sea, 0), debemos devolver un error (return -EIO) y liberar la memoria reservada, usando:

  release_mem_region(video_base, video_size);

Ahora, hemos de reservar acceso a los registros de la tarjeta grafica, usando:

  request_region(primer_puerto, numero_puertos, "mitarjetafb_id");

En primer_puerto, pondremos la direccion del primer puerto al que queremos acceder, y en numero_puertos, la cantidad. Como los puertos no suelen ser consecutivos, es posible que tengamos que hacer varias llamadas. Asi, si queremos ganar acceso a los puertos 0x300 a 0x305, 0x3c0 a 0x3cf y 0x3f0, tendriamos que hacer:

  request_region(0x300, 6, "mitarjetafb_id");
  request_region(0x3c0, 16, "mitarjetafb_id");
  request_region(0x3f0, 1, "mitarjetafb_id");

A continuacion, se debe cambiar al modo grafico adecuado. Para el calculo de las frecuencias, recomiendo leer la pagina 'www.linux-fbdev.org', que incluye un pequeño tutorial sobre los framebuffers.

Por ultimo, se ha de rellenar una estructura de tipo fb_info, incluyendo al menos los campos modename, changevar (que puede ser NULL), node (que suele ponerse a -1), fb_ops (con los punteros al resto de las funciones), disp (que debe ser un puntero a una estructura disp global y estatica), switch_con, updatevar, blank y flags (que, normalmente, sera inicializado a FBINFO_FLAG_DEFAULT), y con ella llamar a la funcion

  int register_framebuffer(struct fb_info *);

Si esta funcion devuelve un valor negativo, es señal de que el driver no se ha podido registrar, por lo que se debe devolver un error (return -EINVAL). La carga continuara en modo texto, o se probara el siguiente driver de FrameBuffer de la lista.

Si todo ha ido bien, retornaremos sin error (return 0).

Como comentamos antes, la funcion mitarjetafb_setup no siempre es llamada. Esto debemos tenerlo en cuenta en esta funcion. Mi recomendacion es colocar una variable estatica global que indique si la funcion setup es llamada, y si no lo ha sido, retornar directamente desde init y no registrar el framebuffer. De esta forma el usuario puede volver a arrancar en modo texto sin necesidad de recompilar el kernel. Tan solo ha de quitar la linea append del fichero lilo.conf.

Tambien hemos de inicializar una variable estatica global, que podemos llamar por ejemplo, VIDEO_CMAP_LEN, con el valor 16 si el modo inicial es sin paleta (normalmente los modos de 15, 16, 24 y 32 bpp no usan paleta), o bien con 256 si el modo inicial es con paleta (los modos de 8 bpp). Esta variable sera necesaria en las funciones para obtener y modificar la paleta, que veremos mas adelante.

Por ultimo, un breve ejemplo de como se ha de rellenar la estructura fb_ops. Puesto que algunas funciones pueden ir definidas y otras no, lo mejor es introducir los punteros indicando su lugar correspondiente. De esta forma se evitan los riesgos de meter un NULL donde no se deba. Por ejemplo, podemos hacerlo al definir la estructura de esta forma:

static struct fb_ops sis6326fb_ops = {
  owner: THIS_MODULE,
  fb_get_fix: mitarjetafb_get_fix,
  fb_get_var: mitarjetafb_get_var,
  fb_set_var: mitarjetafb_set_var,
  fb_get_cmap: mitarjetafb_get_cmap,
  fb_set_cmap: mitarjetafb_set_cmap,
  fb_pan_display: mitarjetafb_pan_display,
};

Por supuesto, la estructura tiene que ser definida como GLOBAL, nunca como local.

3.4.3. mitarjetafb_open

Esta funcion ha de ser definida en la estructura fb_ops.

Es llamada cada vez que un programa abre el dispositivo FrameBuffer (/dev/fb). Lo normal es hacer una simple llamada a MOD_INC_USE_COUNT, aunque ni siquiera eso es necesario, sino que se puede dejar sin definir.

3.4.4. mitarjetafb_close

Esta funcion ha de ser definida en la estructura fb_ops.

Es llamada cada vez que un programa cierra el dispositivo FrameBuffer (/dev/fb). Lo normal es hacer una simple llamada a MOD_DEC_USE_COUNT, aunque ni siquiera eso es necesario, sino que se puede dejar sin definir.

3.4.5. mitarjetafb_get_fix

Esta funcion ha de ser definida en la estructura fb_ops.

Su prototipo es:

  static int mitarjetafb_fb_get_fix(struct fb_fix_screeninfo *fix, int con,struct fb_info *info);

Esta funcion devuelve en la estructura 'fix' (cuyo puntero le es pasado como parametro) los valores correspondientes al modo grafico correspondiente a la consola virtual 'con' de la tarjeta 'info'. Los parametros se deben obtener de la matriz global 'fb_display[]' pasandole como indice el numero de consola que se recibe como parametro, a menos que 'con' valga -1, en cuyo caso se deben leer los parametros de la tarjeta grafica; esto es, debemos devolver los parametros correspondientes al modo grafico que tenga la tarjeta en este preciso instante.

Una forma de hacer esto consiste en mantener los valores actuales de la tarjeta grafica en una estructura global estatica, y si 'con' es -1, usarlos para rellenar la estructura 'fix'. Esto simplifica la programacion pues no hace falta leer los registros de la tarjeta grafica e interpretarlos, sino que cada vez que se cambia el modo grafico, basta con actualizar los valores en la estructura global.

3.4.6. mitarjetafb_get_var

Esta funcion ha de ser definida en la estructura fb_ops.

Su prototipo es:

   static int mitarjetafb_get_var(struct fb_var_screeninfo *var, int con, struct fb_info *info);

Esta funcion devuelve en la estructura 'var' (cuyo puntero le es pasado como parametro) los valores correspondientes al modo grafico correspondiente a la consola virtual 'con' de la tarjeta 'info'. Los parametros se deben obtener de 'fb_display[].var' pasandole como indice el numero de consola que se recibe como parametro, a menos que 'con' valga -1, en cuyo caso se deben leer los parametros de la tarjeta grafica; esto es, debemos devolver los parametros correspondientes al modo grafico que tenga la tarjeta en este preciso instante.

Una forma de hacer esto consiste en mantener los valores actuales de la tarjeta grafica en una estructura global estatica, y si 'con' es -1, usarlos para rellenar la estructura 'var'. Esto simplifica la programacion pues no hace falta leer los registros de la tarjeta grafica e interpretarlos, sino que cada vez que se cambia el modo grafico, basta con actualizar los valores en la estructura global.

3.4.7. mitarjetafb_set_var

Esta funcion ha de ser definida en la estructura fb_ops.

Su prototipo es:

   static int mitarjetafb_set_var(struct fb_var_screeninfo *var, int con, struct fb_info *info);

Esta funcion se encarga de cambiar el modo grafico actual de la tarjeta al modo indicado por el parametro 'var'. Para ello hay tres fases:

Un ultimo comentario: el campo cmap.len de la estructura fb_display[con] NO SE HA DE MODIFICAR. De hacerlo, se produciria un kernel panic cuando se cambiase de una consola con paleta a una sin paleta. Lo que si se ha de cambiar es la variable global VIDEO_CMAP_LEN (de la que ya hablamos en mitarjetafb_init) para que contenga 16 si el modo grafico actual no tiene paleta (esto es, si es un modo de 15, 16, 24 o 32 bpp), o 256 si es un modo con paleta.

3.4.8. mitarjetafb_get_cmap

Esta funcion ha de ser definida en la estructura fb_ops.

Su prototipo es:

  int mitarjetafb_get_cmap(struct fb_cmap *cmap, int kspc, int con,struct fb_info *info);

Esta funcion toma el 'colormap' de la consola 'con' y lo devuelve en la estructura 'cmap'. Lo normal, sin embargo, es implementarla de esta forma:

 if (con == currcon) /* current console? */
  return fb_get_cmap(cmap, kspc, mitarjetafb_getcolreg, info);
 else if (fb_display[con].cmap.len) /* non default colormap? */
  fb_copy_cmap(&fb_display[con].cmap, cmap, kspc ? 0 : 2);
 else
  fb_copy_cmap(fb_default_cmap(video_cmap_len),cmap, kspc ? 0 : 2);
 return 0;

Aqui vemos que usamos la variable VIDEO_CMAP_LEN, y que si la longitud indicada en fb_display[con].cmap.len es cero, asignaremos a la consola actual el 'colormap' por defecto.

Las funciones 'fb_get_cmap', 'fb_copy_cmap' y 'fb_default_cmap' son funciones genericas que vienen implementadas en el sistema Framebuffer, y no tenemos que preocuparnos por ellas.

Por otro lado, la funcion 'mitarjetafb_getcolreg' (que pasamos como parametro a 'fb_get_cmap') es una funcion que si hemos de implementar nosotros, y cuyo prototipo es:

 static int mitarjetafb_getcolreg(unsigned regno, unsigned *red, unsigned *green, unsigned *blue, unsigned *transp, struct fb_info *fb_info);

Esta funcion recibe un numero de registro de color en 'regno', y ha de devolver las cuatro componentes de color de dicho registro de la paleta (en formato de 16 bits) en 'red', 'green', 'blue' y 'transp'. Si la tarjeta no soporta transparencia, 'transp' ira a cero.

De esta forma, la funcion 'fb_get_cmap' llamara a 'mitarjetafb_getcolreg' las veces necesarias para leer la totalidad de los registros, y almacenarlos en la estructura 'cmap'.

3.4.9. mitarjetafb_set_cmap

Esta funcion ha de ser definida en la estructura fb_ops.

Su prototipo es:

  int mitarjetafb_set_cmap(struct fb_cmap *cmap, int kspc, int con, struct fb_info *info);

Esta funcion actualiza el 'colormap' de la consola 'con' con el que se le pasa en la estructura 'cmap'. Lo normal, sin embargo, es implementarla de esta forma:

 int err;

 if (!fb_display[con].cmap.len) { /* no colormap allocated? */
  err = fb_alloc_cmap(&fb_display[con].cmap,video_cmap_len,0);
 if (err)
  return err;
 }
 if (con == currcon) /* current console? */
  return fb_set_cmap(cmap, kspc, mitarjeta_setcolreg, info);
 else
  fb_copy_cmap(cmap, &fb_display[con].cmap, kspc ? 0 : 1);
 return 0;

Aqui vemos que tambien usamos la variable VIDEO_CMAP_LEN, para el supuesto de que la consola actual todavia no tenga un 'colormap' asignado.

Las funciones 'fb_set_cmap' y 'fb_copy_cmap' son funciones genericas que vienen implementadas en el sistema Framebuffer, y no tenemos que preocuparnos de ellas.

Por otro lado, la funcion 'mitarjetafb_setcolreg' (que pasamos como parametro a 'fb_set_cmap') es una funcion que si hemos de implementar nosotros, y cuyo prototipo es:

 static int mitarjetafb_setcolreg(unsigned regno, unsigned red, unsigned green, unsigned blue, unsigned transp, struct fb_info *fb_info);

Esta funcion recibe un numero de registro de color en 'regno', y las cuatro componentes de color de dicho registro de la paleta (en formato de 16 bits) en 'red', 'green', 'blue' y 'transp', y ha de meter dichos valores en el registro de paleta correspondiente (el indicado en 'regno').

De esta forma, la funcion 'fb_set_cmap' llamara a 'mitarjetafb_setcolreg' las veces necesarias para escribir la totalidad de los registros segun indique la estructura 'cmap'.

Esta funcion tiene un segundo cometido, que es asignar la paleta para la consola en un formato adecuado. Para esto se ha de rellenar la estructura fbcon_cmap con los valores adecuados. Veamos un ejemplo:

  switch (video_bpp) {
#ifdef FBCON_HAS_CFB8
  case 8:
   sis6326_setpalette(regno,red,green,blue);
   break;
#endif
#ifdef FBCON_HAS_CFB16
  case 15:
   fbcon_cmap.cfb16[regno] =
   ((red & 0xf800) >> 1) |
   ((green & 0xf800) >> 6) |
   ((blue & 0xf800) >> 11);
   break;
  case 16:
   fbcon_cmap.cfb16[regno] =
   ((red & 0xf800) ) |
   ((green & 0xfc00) >> 5) |
   ((blue & 0xf800) >> 11);
   break;
#endif
#ifdef FBCON_HAS_CFB24
  case 24:
   red >>= 8;
   green >>= 8;
   blue >>= 8;
   fbcon_cmap.cfb24[regno] =
   (red << 16) |
   (green << 8) |
   (blue);
   break;
#endif
#ifdef FBCON_HAS_CFB32
  case 32:
   red >>= 8;
   green >>= 8;
   blue >>= 8;
   fbcon_cmap.cfb32[regno] =
   (red << 24) |
   (green << 16) |
   (blue << 8);
   break;
#endif
}

Vemos que si el modo actual es de 8 bpp, simplemente metemos el color indicado en el DAC de la tarjeta. Si es de 15 o 16 bpp, metemos el color en fbcon_cmap.cfb16[regno] (siendo regno el numero de entrada correspondiente), tras haber eliminado los bits que no interesaban y rotado adecuadamente cada componente. Lo mismo con 24 o 32 bpp, pero usando fbcon_cmap.cfb24 o fbcon_cmap.cfb32, respectivamente.

¿Por que hacemos esto? La consola usara estas estructuras cuando quiera pintar un punto de un color determinado. Gracias a esto, se gana en eficiencia, porque los 16 colores posibles de la consola ya estan adecuadamente preparados para el modo grafico actual, y solo hay que copiar 1, 2, 3 o 4 bytes en el punto concreto del framebuffer para obtener el color deseado. No hace falta rotar y enmascarar cada componente por separado.

Tambien vemos que usamos #defines para no meter codigo innecesario en el driver. Esto es opcional, por supuesto, pero siempre resulta bueno porque es muy importante que el kernel sea del menor tamaño posible.

3.4.10. changevar

Esta funcion es definida en la estructura fb_info que se usa para inicializar el driver de Framebuffer.

Su prototipo es:

  int changevar (int con);

Esta funcion, por lo que se, es llamada siempre que se cambia la informacion de 'var' en la consola 'con'. No es obligatorio definirla, sino que se puede poner NULL. Si alguien tiene mas informacion sobre ella...

3.4.11. mitarjetafb_pan_display

Esta funcion es definida en la estructura fb_info que se usa para inicializar el driver de Framebuffer.

Su prototipo es:

  int mitarjetafb_pan_display(struct fb_var_screeninfo *var, int con, struct fb_info *info);

Esta funcion se encarga de desplazar la pantalla al punto indicado por 'var->xoffset' y 'var->yoffset', dentro de la pantalla virtual. Si los valores son demasiado grandes, debe retornar -EINVAL. Este desplazamiento normalmente se hara cambiando la direccion base de inicio de la memoria de video, y no usando funciones de la aceleradora grafica.

3.4.12. switch_con

Esta funcion es definida en la estructura fb_info que se usa para inicializar el driver de Framebuffer.

Su prototipo es:

  int switch_con (int con, struct fb_info *info);

Esta funcion es llamada siempre que el usuario conmuta de una consola a otra. Sus cometidos son los siguientes:

Resulta interesante comparar antes 'con' con 'currcon', y retornar si son iguales. Esto es necesario porque, en algunos casos (en concreto al ejecutar programas que usan la biblioteca SDL), la funcion es llamada dos veces cuando se conmuta a la consola que soporta las X-Windows. El resultado es que las X se cuelgan. Sin embargo, si se hace esta comprobacion, la segunda vez que sea llamada se retornara sin tocar nada, y todo funcionara perfectamente.

3.4.13. updatevar

Esta funcion es definida en la estructura fb_info que se usa para inicializar el driver de Framebuffer.

Su prototipo es:

  int updatevar(int con, struct fb_info *info);

Esta funcion es llamada cada vez que se hace algun cambio en 'var' que requiera desplazar la pantalla. Se debe ver en 'fb_display[con].var.xoffset' y en 'fb_display[con].var.yoffset' el nuevo desplazamiento de la pantalla, y devolver -EINVAL si es un desplazamiento no permitido (demasiado grande).

Lo normal es llamar con 'fb_display[con].var' a la funcion 'mitarjetafb_pan_display' siempre y cuando 'con' sea igual a 'currcon'.

3.4.14. blank

Esta funcion es definida en la estructura fb_info que se usa para inicializar el driver de Framebuffer.

Su prototipo es:

  void blank(int blank, struct fb_info *info);

Esta funcion es llamada para activar o desactivar los modos de ahorro de energia del monitor.

3.4.15. las otras funciones

Las otras funciones de 'fb_ops' no resultan imprescindibles para escribir un driver de Framebuffer, por lo que a todo aquel que quiera usarlas le recomiendo que le eche un vistazo al codigo fuente de otros drivers Framebuffer.

4. Probando el driver de Framebuffer

Hay cuatro pruebas que considero fundamentales para comprobar si un driver de Framebuffer funciona como debe o no. Son las siguientes:

5. Referencias

*Codigo fuente de los drivers 'vesafb.c', 'matroxfb.c' y 'atyfb.c'.

*Documentacion del Framebuffer disponible en todos los kernels
  (linux-2.4/Documentation/fb/*).

*Documentacion sobre programacion de drivers de Framebuffer
  (http://www.linux-fbdev.org).

Licencia de Creative Commons
Esta obra está bajo una licencia de Creative Commons Reconocimiento-CompartirIgual 4.0 Internacional.