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]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 r90, r180 and r270 variants to support rotation in software, and db variants to support double-buffered displays.

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.