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 singlecyg_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 ") } |
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
, andCYGHWR_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
orcyg_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 inCYGPKG_IO_FRAMEBUF
ensures that application code can just #includecyg/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 |
#define FRAMEBUF 320x240x16 cyg_ucount16 width = CYG_FB_WIDTH(FRAMEBUF); |
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_) … |
#include |
#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 |
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 r90, r180and 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_) … |
_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 |
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.
Driver-Specific Source Code
Exporting parameters and macros in a header file is not enough. It is also necessary to actually define thecyg_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 ); |
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; } |
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 inFLAGS1, 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.For more info follow this link:
http://labdasar.ee.itb.ac.id/lab/installer/embedded/ecos-3.0.cygwin/ecos-3.0/doc/html/ref/framebuf-porting.html
1 comment:
Nice post. Very good write-up.
Post a Comment