Name
Porting — writing a new framebuffer device driver
Description
As with most device drivers, the easiest way to write a new framebuffer package is to start with an existing one. Suitable ones include the PC VGA mode13 driver, an 8bpp paletted display, and the ARM iPAQ driver, a 16bpp true colour display. This document only outlines the process.
Before writing any code it is necessary to decide how many framebuffer devices should be provided by the device driver. Each such device requires a cyg_fb structure and appropriate functions, and an identifier for use with the macro API plus associated macros. There are no hard rules here. Some device drivers may support just a single device, others may support many devices which drive the hardware in different modes or orientations. Optional functionality such as viewports and page flipping may be supported by having different cyg_fb devices, or by a number of configuration options which affect a single cyg_fb device. Usually providing multiple cyg_fb structures is harmless because the unused ones will get eliminated at link-time.
Configuration
The CDL for a framebuffer package is usually straightforward. A
framebuffer package should be a hardware package and reside in the
devs/framebuf
hierarchy,
further organized by architecture. Generic framebuffer packages, if
any, can go into a generic
subdirectory, and will normally rely on the platform HAL to provide
some platform-specific information such as base addresses. The package
should be part of the target definition and hence loaded
automatically, but should be
active_if CYGPKG_IO_FRAMEBUF
so that the
driver only gets built if the generic framebuffer support is
explicitly added to the configuration.
The configuration option CYGDAT_IO_FRAMEBUF_DEVICES
should hold all the valid identifiers which can be used as the first
argument for the macro API. This helps application developers to
select the appropriate identifier, and allows higher-level graphics
library packages to check that they have been configured correctly.
This is achieved using something like the following, where
mode13_320x200x8
is a valid identifier for the PC
VGA driver:
requires { is_substr(CYGDAT_IO_FRAMEBUF_DEVICES, " mode13_320x200x8 ") }
The spaces ensure that the CDL inference engine keeps the identifiers separate.
CYGPKG_IO_FRAMEBUF
contains a number of interfaces
which should be implemented by individual device drivers when
appropriate. This is used to eliminate some code or data structure
fields at compile-time, keeping down memory requirements. The
interfaces are
CYGHWR_IO_FRAMEBUF_FUNCTIONALITY_32BPP
,
CYGHWR_IO_FRAMEBUF_FUNCTIONALITY_TRUE_COLOUR
,
CYGHWR_IO_FRAMEBUF_FUNCTIONALITY_PALETTE
,
CYGHWR_IO_FRAMEBUF_FUNCTIONALITY_WRITEABLE_PALETTE
,
CYGHWR_IO_FRAMEBUF_FUNCTIONALITY_DOUBLE_BUFFER
,
and CYGHWR_IO_FRAMEBUF_FUNCTIONALITY_VIEWPORT
.
For example if a device driver provides a true colour display but
fails to implement the relevant interface then functions like
cyg_fb_make_colour
will be no-ops.
Device drivers for paletted displays should observe the generic
configuration option
CYGFUN_IO_FRAMEBUF_INSTALL_DEFAULT_PALETTE
and
install either cyg_fb_palette_ega
or
cyg_fb_palette_vga
as part of their
cyg_fb_on
implementation.
Exported Header File(s)
Each framebuffer device driver should export one or more header files
to cyg/io/framebufs
. A custom
build step in CYGPKG_IO_FRAMEBUF
ensures that
application code can just #include
cyg/io/framebuf.h
and this will
automatically include the device-specific headers. Drivers may export
one header per cyg_fb device or a single
header for all devices, without affecting any code outside the device
driver.
Each exported header serves two purposes. First it defines the parameters, drawing primitive macros, and iteration macros for each device. Second it declares the cyg_fb structure.
Parameters
The parameter section should resemble the following:
#define CYG_FB_320x240x16_STRUCT cyg_ipaq_fb_320x240x16 #define CYG_FB_320x240x16_DEPTH 16 #define CYG_FB_320x240x16_FORMAT CYG_FB_FORMAT_16BPP_TRUE_565 #define CYG_FB_320x240x16_WIDTH 320 #define CYG_FB_320x240x16_HEIGHT 240 #define CYG_FB_320x240x16_VIEWPORT_WIDTH 320 #define CYG_FB_320x240x16_VIEWPORT_HEIGHT 240 #define CYG_FB_320x240x16_FLAGS0 (CYG_FB_FLAGS0_LINEAR_FRAMEBUFFER | \ CYG_FB_FLAGS0_TRUE_COLOUR | \ CYG_FB_FLAGS0_BLANK | \ CYG_FB_FLAGS0_BACKLIGHT) #define CYG_FB_320x240x16_FLAGS1 0 #define CYG_FB_320x240x16_FLAGS2 0 #define CYG_FB_320x240x16_FLAGS3 0 #define CYG_FB_320x240x16_BASE ((void*)0x01FC0020) #define CYG_FB_320x240x16_STRIDE 640
Here 320x240x16
is the framebuffer identifier for
use with the macro API. Application code like:
#define FRAMEBUF 320x240x16 cyg_ucount16 width = CYG_FB_WIDTH(FRAMEBUF);
will end up using the CYG_FB_320x240x16_WIDTH
definition. To allow for efficient portable code all parameters must
be compile-time constants. If the hardware may allow some of the
parameters to be varied, for example different resolutions, then this
should be handled either by defining separate devices for each
resolution or by configuration options.
The viewport width and height should always be defined. If the device driver does not support a viewport then these will be the same as the standard width and height.
To allow for future expansion there are FLAGS1
,
FLAGS2
and FLAGS3
parameters. No
flags are defined for these at present, but device drivers should
still define the parameters.
Drawing Primitives
For each device the exported header file should define macros for the drawing primitives, using the same naming convention as for parameters. In the case of true colour displays there should also be macros for the make-colour and break-colour primitives:
#define CYG_FB_320x240x16_WRITE_PIXEL(_x_, _y_, _colour_) … #define CYG_FB_320x240x16_READ_PIXEL(_x_, _y_) … #define CYG_FB_320x240x16_WRITE_HLINE(_x_, _y_, _len_, _colour_) … #define CYG_FB_320x240x16_WRITE_VLINE(_x_, _y_, _len_, _colour_) … #define CYG_FB_320x240x16_FILL_BLOCK(_x_, _y_, _w_, _h_, _colour_) … #define CYG_FB_320x240x16_WRITE_BLOCK(_x_, _y_, _w_, _h_, _data_, _off_, _s_) … #define CYG_FB_320x240x16_READ_BLOCK(_x_, _y_, _w_, _h_, _data_, _off_, _s_) … #define CYG_FB_320x240x16_MOVE_BLOCK(_x_, _y_, _w_, _h_, _new_x_, _new_y_) … #define CYG_FB_320x240x16_MAKE_COLOUR(_r_, _g_, _b_) … #define CYG_FB_320x240x16_BREAK_COLOUR(_colour_, _r_, _g_, _b_) …
For typical linear framebuffers there are default implementations of
all of these primitives in the generic framebuffer package, held in
the exported header cyg/io/framebuf.inl
. Hence the
definitions will typically look something like:
#include <cyg/io/framebuf.inl> … #define CYG_FB_320x240x16_WRITE_PIXEL(_x_, _y_, _colour_) \ CYG_MACRO_START \ cyg_fb_linear_write_pixel_16_inl(CYG_FB_320x240x16_BASE, \ CYG_FB_320x240x16_STRIDE, \ _x_, _y_, _colour_); \ CYG_MACRO_END #define CYG_FB_320x240x16_READ_PIXEL(_x_, _y_) \ ({ cyg_fb_linear_read_pixel_16_inl(CYG_FB_320x240x16_BASE, \ CYG_FB_320x240x16_STRIDE, \ _x_, _y_); }) …
All of the drawing primitives have variants for the common display
depths and layouts: 1le, 1be, 2le, 2be, 4le, 4be, 8, 16 and 32. The
inlines take the framebuffer memory base address as the first
argument, and the stride in bytes as the second. Similarly there are
default definitions of the true colour primitives for
8BPP_TRUE_332
, 16BPP_TRUE_565
,
16BPP_TRUE_555
, and 32BPP_TRUE_0888
:
#define CYG_FB_320x240x16_MAKE_COLOUR(_r_, _g_, _b_) \ ({ CYG_FB_MAKE_COLOUR_16BPP_TRUE_565(_r_, _g_, _b_); }) #define CYG_FB_320x240x16_BREAK_COLOUR(_colour_, _r_, _g_, _b_) \ CYG_MACRO_START \ CYG_FB_BREAK_COLOUR_16BPP_TRUE_565(_colour_, _r_, _g_, _b_); \ CYG_MACRO_END
These default definitions assume the most common layout of colours
within a pixel value, so for
example CYG_FB_MAKE_COLOUR_16BPP_TRUE_565
assumes
bits 0 to 4 hold the blue intensity, bits 5 to 10 the green, and bits
11 to 15 the red.
If the hardware does not implement a linear framebuffer then obviously writing the device driver will be significantly more work. The macros will have to perform the operations themselves instead of relying on generic implementations. The required functionality should be obvious, and the generic implementations can still be consulted as a reference. For complicated hardware it may be appropriate to map the macros onto function calls, rather than try to implement everything inline.
Note | |
---|---|
At the time of writing the support for linear
framebuffers is incomplete. Only 8bpp, 16bpp and 32bpp depths have
full support. There may also be future extensions, for example
|
Iteration Macros
In addition to the drawing primitives the exported header file should define iteration macros:
#define CYG_FB_320x240x16_PIXELx_VAR( _fb_, _id_) … #define CYG_FB_320x240x16_PIXELx_SET( _fb_, _id_, _x_, _y_) … #define CYG_FB_320x240x16_PIXELx_GET( _fb_, _id_, _x_, _y_) … #define CYG_FB_320x240x16_PIXELx_ADDX( _fb_, _id_, _incr_) … #define CYG_FB_320x240x16_PIXELx_ADDY( _fb_, _id_, _incr_) … #define CYG_FB_320x240x16_PIXELx_WRITE(_fb_, _id_, _colour_) … #define CYG_FB_320x240x16_PIXELx_READ( _fb_, _id_)… #define CYG_FB_320x240x16_PIXELx_FLUSHABS( _fb_, _id_, _x0_, _y0_, _w_, _h_) … #define CYG_FB_320x240x16_PIXELx_FLUSHREL( _fb_, _id_, _x0_, _y0_, _dx_, _dy_) …
The _fb_
argument will be the identifier, in
this case 320x240x16
, and the
_id_
will be a small number, 0 for a
PIXEL0
iteration, 1 for PIXEL1
,
and so on. Together these two should allow unique local variable names
to be constructed. Again there are default definitions of the macros
in cyg/io/framebuf.inl
for
linear framebuffers:
#define CYG_FB_320x240x16_PIXELx_VAR( _fb_, _id_) \ CYG_FB_PIXELx_VAR_16( _fb_, _id_) #define CYG_FB_320x240x16_PIXELx_SET( _fb_, _id_, _x_, _y_) \ CYG_MACRO_START \ CYG_FB_PIXELx_SET_16( _fb_, _id_, \ CYG_FB_320x240x16_BASE, \ 320, _x_, _y_); \ CYG_MACRO_END
The linear SET
and GET
macros
take base and stride information. The ADDX
and
ADDY
macros only need the stride. By convention
most of the macros are wrapped in
CYG_MACRO_START
/CYG_MACRO_END
or
({
/})
pairs, allowing debug code
to be inserted if necessary. However the _VAR
macro
must not be wrapped in this way: its purpose is to define one or more
local variables; wrapping the macro would declare the variables in a
new scope, inaccessible to the other macros.
Again for non-linear framebuffers it will be necessary to implement these macros fully rather than rely on generic implementations, but the generic versions can be consulted as a reference.
The cyg_fb declaration
Finally there should be an export of the
cyg_fb structure or structures. Typically
this uses the _STRUCT
parameter, reducing the
possibility of an accidental mismatch between the macro and function APIs:
extern cyg_fb CYG_FB_320x240x16_STRUCT;
Driver-Specific Source Code
Exporting parameters and macros in a header file is not enough. It is
also necessary to actually define the cyg_fb
structure or structures, and to provide hardware-specific versions of
the control operations. For non-linear framebuffers it will also be
necessary to provide the drawing functions. There is a utility macro
CYG_FB_FRAMEBUFFER
for instantiating a
cyg_fb structure. Drivers may ignore this
macro and do the work themselves, but at an increased risk of
compatibility problems with future versions of the generic code.
CYG_FB_FRAMEBUFFER(CYG_FB_320x240x16_STRUCT, CYG_FB_320x240x16_DEPTH, CYG_FB_320x240x16_FORMAT, CYG_FB_320x240x16_WIDTH, CYG_FB_320x240x16_HEIGHT, CYG_FB_320x240x16_VIEWPORT_WIDTH, CYG_FB_320x240x16_VIEWPORT_HEIGHT, CYG_FB_320x240x16_BASE, CYG_FB_320x240x16_STRIDE, CYG_FB_320x240x16_FLAGS0, CYG_FB_320x240x16_FLAGS1, CYG_FB_320x240x16_FLAGS2, CYG_FB_320x240x16_FLAGS3, 0, 0, 0, 0, // fb_driver0 -> fb_driver3 &cyg_ipaq_fb_on, &cyg_ipaq_fb_off, &cyg_ipaq_fb_ioctl, &cyg_fb_nop_synch, &cyg_fb_nop_read_palette, &cyg_fb_nop_write_palette, &cyg_fb_dev_make_colour_16bpp_true_565, &cyg_fb_dev_break_colour_16bpp_true_565, &cyg_fb_linear_write_pixel_16, &cyg_fb_linear_read_pixel_16, &cyg_fb_linear_write_hline_16, &cyg_fb_linear_write_vline_16, &cyg_fb_linear_fill_block_16, &cyg_fb_linear_write_block_16, &cyg_fb_linear_read_block_16, &cyg_fb_linear_move_block_16, 0, 0, 0, 0 // fb_spare0 -> fb_spare3 );
The first 13 arguments to the macro correspond to the device
parameters. The next four are arbitrary CYG_ADDRWORD
values for use by the device driver. Typically these are used to share
on/off/ioctl functions between multiple
cyg_fb structure. They are followed by
function pointers: on/off/ioctl control; double buffer synch; palette
management; true colour support; and the drawing primitives.
nop
versions of the on, off, ioctl, synch, palette
management and true colour functions are provided by the generic
framebuffer package, and often these arguments to the
CYG_FB_FRAMEBUFFER
macro will be discarded
at compile-time because the relevant CDL interface is not implemented.
The final four arguments are currently unused and should be 0. They
are intended for future expansion, with a value of 0 indicating that a
device driver does not implement non-core functionality.
As with the macros there are default implementations of the true
colour primitives for 8bpp_true_332
,
16bpp_true_565
, 16bpp_true_555
and 32bpp_true_0888
, assuming the most common
layout for these colour modes. There are also default
implementations of the drawing primitives for linear framebuffers,
with variants for the common display depths and layouts. Obviously
non-linear framebuffers will need rather more work.
Typically a true colour or grey scale framebuffer device driver will have to implement just three hardware-specific functions:
int cyg_ipaq_fb_on(cyg_fb* fb) { … } int cyg_ipaq_fb_off(cyg_fb* fb) { … } int cyg_ipaq_fb_ioctl(cyg_fb* fb, cyg_ucount16 key, void* data, size_t* len) { int result; switch(key) { case CYG_FB_IOCTL_BLANK_GET: … … default: result = ENOSYS; break; } return result; }
These control operations are entirely hardware-specific and cannot be implemented by generic code. Paletted displays will need two more functions, again hardware-specific:
void cyg_pcvga_fb_read_palette(cyg_fb* fb, cyg_ucount32 first, cyg_ucount32 len, void* data) { … } void cyg_pcvga_fb_write_palette(cyg_fb* fb, cyg_ucount32 first, cyg_ucount32 len, const void* data, cyg_ucount16 when) { … }
Future Expansion
As has been mentioned before framebuffer hardware varies widely. The design of a generic framebuffer API requires complicated trade-offs between efficiency, ease of use, ease of porting, and still supporting a very wide range of hardware. To some extent this requires a lowest common denominator approach, but the design allows for some future expansion and optional support for more advanced features like hardware acceleration.
The most obvious route for expansion is the ioctl
interface. Device drivers can define their own keys, values
0x8000
and higher, for any operation. Alternatively
a device driver does not have to implement just the interface provided
by the generic framebuffer package: additional functions and macros
can be exported as required.
Currently there are only a small number of ioctl
operations. Additional ones may get added in future, for example to
support a hardware mouse cursor, but only in cases where the
functionality is likely to be provided by a significant number of
framebuffer devices. Adding new generic functionality adds to the
maintenance overhead of both code and documentation. When a new
generic ioctl
operation is added there will
usually also be one or more new flags, so that device drivers can
indicate they support the functionality. At the time of writing only
12 of the 32 FLAGS0
flags are used, and a further
96 are available in FLAGS1
,
FLAGS2
and FLAGS3
.
Another route for future expansion is the four spare arguments to the
CYG_FB_FRAMEBUFFER
macro. As an example of how
these may get used in future, consider support for 3d hardware
acceleration. One of the spare fields would become another table of
function pointers to the various accelerators, or possibly a
structure. A FLAGS0
flag would indicate that the
device driver implements such functionality.
Other forms of expansion such as defining a new standard drawing
primitive would be more difficult, since this would normally involve
changing the CYG_FB_FRAMEBUFFER
macro. Such
expansion should not be necessary because the existing primitives
provide all reasonable core functionality. Instead other packages such
as graphics libraries can work on top of the existing primitives.
2024-03-18 | Open Publication License |