This chapter describes the functions provided in the following libraries:
The disputil (display utilities) library provides miscellaneous functions that can be useful when writing graphics drivers:
It's not safe to call any of the disputil functions except from driver entry points that get called within the context of io-display (with the exception of disp_vm_alloc_surface_external() and disp_vm_free_surface_external(). |
The display driver utilities set includes:
This function registers your driver with the display utilities libraries. The prototype is:
int disp_register_adapter (disp_adapter_t *adapter)
Call this function before calling any other disp_* functions. If your driver uses the video BIOS functions, it must call vbios_register() before calling disp_register_adapter(). |
This function frees any resources allocated by disp_register_adapter(). The prototype is:
int disp_unregister_adapter ( disp_adapter_t *adapter)
Your driver should call this function when it no longer requires the services of the DISPUTIL lib. Typically, one of the driver's fini() routines calls disp_unregister_adapter().
This function calculates the CRT controller settings. The prototype is:
int disp_crtc_calc (disp_crtc_settings_t *display)
It uses the xres, yres, refresh, h_granularity, and v_granularity members of the disp_crtc_settings_t structure pointed to by display and computes the remaining members, according to the VESA GTF (Generalized Timing Formula).
This function opens the specified file, and populates the disp_crtc_settings_t structure with standard CRTC timings for the mode specified by xres, yres and refresh. The specified file name is normally /etc/system/config/crtc-settings.
The prototype is:
int disp_mode_get_entry( disp_adapter_t *adapter, disp_crtc_settings_t *settings, const char *fname, int xres, int yres, int refresh );
This function acquires access to the VGA registers. The prototype is:
int disp_acquire_vga_resources ( disp_adapter_t *adapter)
You must call this function before activating any of the VGA registers. |
Drivers that can't deactivate their legacy VGA registers shouldn't set the DISP_CAP_MULTI_MONITOR_SAFE flag in the caps member of the disp_adapter_t structure. At initialization time, the driver should ensure that the device doesn't respond to I/O or memory cycles to legacy VGA addresses (e.g. the memory aperture from 0xa0000 to 0xbffff, or VGA I/O ports within in the 0x3b4 to 0x3d5 range). If the driver needs to activate the VGA legacy registers, and it has set the DISP_CAP_MULTI_MONITOR_SAFE flags, it should call this function before activating the registers.
For non-x86 architectures, VGA register conflicts aren't a concern, and this function has no effect.
This function is the opposite of disp_acquire_vga_resources(); call it when you're done with the VGA registers. The prototype is:
int disp_release_vga_resources ( disp_adapter_t *adapter)
You must deactivate the card's response to legacy VGA cycles before calling this function. |
For non-x86 architectures, VGA register conflicts aren't a concern, and this function has no effect.
This function is similar to the standard C library's perror() function. The prototype is:
void disp_perror (disp_adapter_t *adapter, char *what)
It outputs the string given by what along with the string interpretation of the global errno to the system logger, slogger.
This function is similar to the standard C library's printf() and fprintf() functions. The prototype is:
void disp_printf (disp_adapter_t *adapter, const char *fmt, …)
It outputs the given string (starting with the fmt parameter and any additional parameters specified) to the system logger, slogger.
This function busy waits for at least usecs microseconds. The prototype is:
void disp_usecspin (unsigned usecs)
While polling is generally discouraged, sometimes the hardware demands that registers be accessed only after a certain (small) delay. If longer delays are necessary, use the disp_wait_condition function, which allows other processes to be scheduled, instead of wasting CPU cycles.
This function suspends the calling thread for a specified number of milliseconds.
The suspension time may be greater than the requested amount, due to the scheduling of other, higher-priority threads by the system. |
The prototype is:
void disp_delay(unsigned milliseconds);
This function gets the adapter's ROM image, if present. The prototype is:
char *disp_get_rom_image (disp_adapter_t *adapter, int code_type, int *size, int map_pci_base_index, int map_offset)
For PCI devices, this function first attempts to fetch the ROM from the PCI ROM aperture. Note that the ROM image in the PCI aperture may be different on x86 systems from the image that typically shows up at physical address 0xc0000. This is because the POST (Power On Self Test) process may modify and shrink the ROM image. See the PCI specification for more details. If the ROM can't be found in the PCI aperture, then, (x86 systems only) the function looks for a ROM image at physical address 0xc0000.
The code_type parameter specifies the type of ROM image to fetch from the ROM aperture. The PCI specification defines the ROM types, and allows multiple ROM images to be present in the PCI ROM aperture, in order to allow multiple platforms to be supported. A value of 0 for rom_type specifies an x86 ROM image.
On some systems, the BIOS doesn't set up the bridge chips correctly, such that it isn't possible to map the ROM aperture. To work around this, a trick is used, whereby the ROM is mapped inside of one of the device's memory apertures (usually the frame buffer). The map_pci_base_index argument specifies in which of the six pci apertures the ROM should be mapped. The map_offset argument specifies the offset into this aperture at which the ROM should be mapped.
If size is non-NULL, disp_get_rom_image() sets *size to the size of the ROM, in bytes.
This function allocates memory, copies the ROM image into it, and then returns a pointer to that memory. You need to free() the memory when you're finished using it. |
This function reads the values of the generic VGA registers and saves them in the disp_vga_state_t structure pointed to by state. The prototype is:
void disp_vga_save_state( disp_adapter_t *ctx, disp_vga_state_t *state);
This function restores the state of the VGA registers from the values saved in the disp_vga_state_t structure pointed to by state. The prototype is:
void disp_vga_restore_state( disp_adapter_t *ctx, disp_vga_state_t *state);
This structure is used to save the state of the VGA registers:
typedef struct { uint8_t misc_out; uint8_t seq[5]; uint8_t crtc[25]; uint8_t gc[9]; uint8_t attr[21]; uint8_t pal[64*3]; } disp_vga_state_t;
The PCI configuration access utilities set includes:
This function attaches the driver to a PCI device. The prototype is:
int disp_pci_init (disp_adapter_t *adapter, unsigned flags)
It attaches the driver to the PCI device specified by the pci_vendor_id, pci_device_id, and pci_index members of the adapter structure.
The flags argument may be one of:
If successful, disp_pci_init() sets adapter->bus_type to DISP_BUS_PCI, initializes the adapter->bus.pci.base, adapter->bus.pci.apsize, and adapter->irq members, and returns 0. Otherwise, this function returns -1.
Call this function before calling any other PCI-related functions in the disputil library. |
This function detaches the driver from the device and releases the resources from a previous disp_pci_init() call. The prototype is:
int disp_pci_shutdown (disp_adapter_t *adapter)
This function reads a PCI configuration register. The prototype is:
int disp_pci_read_config (disp_adapter_t *adapter, unsigned offset, unsigned count, size_t size, void *bufptr)
PCI configuration registers can be byte, word, or double-word. This function reads a PCI configuration register (or registers if count is greater than one), as given by offset and size, into the data area given by bufptr. For details on the return values, see pci_read_config() in the QNX Neutrino Library Reference.
This function writes a PCI configuration register. The prototype is:
int disp_pci_write_config (disp_adapter_t *adapter, unsigned offset, unsigned count, size_t size, void *bufptr)
It writes a PCI configuration register (or registers if count is greater than one), as given by offset and size, from the data area given by bufptr. For details on the return values, see pci_write_config() in the QNX Neutrino Library Reference.
This function is similar to pci_find_device(). The prototype is:
int disp_pci_dev_find (unsigned devid, unsigned venid, unsigned index, unsigned *bus, unsigned *devfunc)
It discovers a device's bus and devfunc values in order to let a driver talk to a PCI device other than the one specified in the disp_adapter_t structure.
This function reads a PCI configuration register. The prototype is:
int disp_pci_dev_read_config (unsigned bus, unsigned devfunc, unsigned offset, unsigned cnt, size_t size, void *bufptr)
This function reads a PCI configuration register (like disp_pci_read_config(), above), but from a specific bus and device (devfunc). Error return codes are documented in pci_read_config().
This function writes a PCI configuration register. The prototype is:
int disp_pci_dev_write_config (unsigned bus, unsigned devfunc, unsigned offset, unsigned cnt, size_t size, void *bufptr)
This function writes a PCI configuration register (like disp_pci_write_config(), above), but to a specific bus and device (devfunc). Error return codes are documented in pci_write_config().
This function is a cover function for pci_present(). The prototype is:
int disp_pci_info (unsigned *lastbus, unsigned *version, unsigned *hardware)
The memory-manager utilities set includes:
The prototype is:
void *disp_mmap_device_memory (paddr_t base, size_t len, int prot, int flags)
It creates a virtual address space pointer to the physical address given in base, which is len bytes in length. The prot parameter is selected from one or more of the following bitmapped flags:
The flags parameter is selected from one or more of the following bitmapped flags:
This function creates either a virtual address space pointer (like disp_mmap_device_memory(), above), or returns its argument base. The prototype is:
unsigned long disp_mmap_device_io (size_t len, paddr_t base)
On non-x86 architectures, this function returns a virtual address space pointer (because these don't have a separate I/O space), whereas for x86 architectures it returns the argument base unmodified. Regardless of the architecture, the return value can be used with functions such as in8() and out8().
This function releases any resources that were acquired by disp_mmap_device_memory(). The prototype is:
void disp_munmap_device_memory (void *addr, size_t len)
It invalidates (unmaps) the virtual address pointer in addr.
This function unmaps len bytes of device I/O memory at io (that was previously mapped with disp_mmap_device_io()). The prototype is:
int disp_munmap_device_io( uintptr_t io, size_t len);
This function returns the physical address corresponding to the virtual address passed in addr. The prototype is:
paddr_t disp_phys_addr (void *addr)
This call is useful with devices that use DMA (which must be programmed with the physical address of the transfer area), under architectures (e.g. x86) where device I/O and memory cycles aren't translated by the MMU.
Note that the paddr_t physical address is valid only for a maximum of __PAGESIZE bytes (i.e. from the physical address corresponding to the passed virtual address up to and including the end of the page boundary), unless the memory is physically contiguous.
For example, if __PAGESIZE is 4096 (0x1000), and the virtual address translated to a physical address of 0x7B000100, then only the physical address range 0x7B000100 through to 0x7B000FFF (inclusive) is valid.
This function allocates memory. The prototype is:
void *disp_getmem (int size, unsigned prot, unsigned flags)
It allocates size bytes of memory that conform to the prot and flags parameters.
Allocations are rounded up to be a multiple of __PAGESIZE. For example, even if size is 1, __PAGESIZE bytes of system memory are used up. |
The size, prot and flags arguments are the same as those passed to disp_mmap_device_memory() above, with the following additional bits for flags:
Note that you don't supply a base parameter as with the other mapping function; instead, disp_getmem() finds a free block of memory (called anonymous memory) and allocates it.
This function returns a pointer (virtual address) to the memory, or NULL if the memory couldn't be allocated.
This function invalidates the virtual address pointer in addr and deallocates the memory. The prototype is:
void disp_freemem (void *addr, int size)
The video memory management utilities set includes:
These functions are primarily intended to be called from your driver's Video Memory Manager module. A simple Video Memory Manager is little more than a wrapper for these functions.
This function creates a new memory pool for the memory manager. The prototype is:
disp_vm_pool_t *disp_vm_create_pool ( disp_adapter_t *adapter, disp_surface_t *surf, int bytealign)
Pass the adapter associated with this memory pool, a pointer to the surface in surf, and a byte alignment parameter, bytealign. The bytealign argument indicates the alignment for the memory manager — all chunks of memory returned by the memory manager for this pool are aligned to the number of bytes specified.
The surf argument describes the block of memory that the driver wants to have managed. The driver should set the vidptr, offset and paddr (if appropriate) members of the surface structure. When the libraries' memory management routines are used to allocate a surface, the library uses these initial values to calculate the corresponding values for the allocated surface.
The pixel_format member of surf is typically set to DISP_SURFACE_FORMAT_BYTES, since the unallocated memory doesn't have any particular pixel format. The stride and height members should be set such that stride×height is equal to the size of the memory block. For example, the height could be set to 1 and the stride set to the size of the memory block.
Set the flags member to describe the characteristics of the video RAM.
A driver can create multiple pools, for example, if there are multiple regions of memory that have different characteristics. This is the case if not all of the devices RAM are addressable by the display controller. In this case, the driver might create a pool of CRTC-addressable memory, and a pool of non-CRTC-addressable memory. When the driver is asked to allocate some memory, it tries to obtain the memory from the non-CRTC-addressable pool first (assuming the framework hasn't explicitly requested displayable memory via the DISP_SURFACE_DISPLAYABLE flag). If the allocation from the non-CRTC-addressable pool fails, the driver then tries to obtain the memory from the CRTC-addressable pool.
The return value is a pool_handle_t that's passed to subsequent disp_vm_*() function calls.
This function deallocates all surfaces that were allocated from the pool and releases the resources associated with tracking the pool allocation. The prototype is:
int disp_vm_destroy_pool (disp_adapter_t *adapter, disp_vm_pool_t *pool)
This function allocates a surface from the memory pool specified by pool. The prototype is:
disp_surface_t *disp_vm_alloc_surface ( pool_handle_t *pool, int width, int height, int stride, unsigned format, unsigned flags)
The flags parameter is selected from the set of manifest constants defined in the flags argument for the disp_surface_t data type. This specifies the characteristics of the memory that's being requested. If there's no memory available with the specified characteristics, the function should return NULL. Otherwise, this function returns a pointer to a disp_surface_t structure, which describes the allocated memory.
The width, height and stride parameters define the area and stride of the surface to be allocated.
The pixel_format member of the disp_surface_t structure is set to the value specified by the pixel_format argument.
The format parameter is selected from the set of manifest constants beginning with DISP_SURFACE_FORMAT_*. For more information, see “Pixel formats,” under the description for devg_get_corefuncs() in the Writing a Graphics Driver chapter.
This function releases the surface memory identified by surf back into the surface memory manager's pool. It also frees the surf structure. The prototype is:
int disp_vm_free_surface (disp_adapter_t *adapter, disp_surface_t surf)
This function returns 0, or -1 if an error occurred.
This function returns how much memory is available in the pool identified by pool, in bytes. The prototype is:
unsigned long disp_vm_mem_avail ( disp_vm_pool_t *pool, unsigned sflags)
It should report only memory that has at least the characteristics specified by sflags.
Sometimes in the course of rendering, your driver may wish to allocate a memory surface. Since most of the memory management routines in libdisputil use pointers to track memory allocations, it is not possible to call them from the address space of a rendering client.
This function allows your driver to allocate a memory surface when called within the context of a rendering client. When you call this function, it will result in your driver's alloc_surface() memory manager entry point being called, with the same width, height, and flags arguments. A surface will then be constructed and returned, satisfying the allocation requirement, which may then be used in the context of the client.
The prototype is:
disp_surface_t * disp_vm_alloc_surface_external( disp_adapter_t *adapter, int width, int height, unsigned flags)
This function returns a surface, or NULL upon failure.
This function frees a surface that was created with disp_vm_alloc_surface_external().
It has this prototype:
void disp_vm_free_surface_external( disp_adapter_t *adapter, disp_surface_t *surf)
The video BIOS services include:
The vbios_* routines provide drivers with the ability to make calls to the VGA / VESA BIOS. These calls are often referred to as Int 10 calls, since the Video BIOS is generally accessed via vector 0x10 in the Real Mode Interrupt Vector table.
These services are available only on x86 platforms; this section isn't relevant to non-x86 architectures or to drivers that are targeted toward machines that don't have a Video BIOS.
Typically, graphics drivers that use the Video BIOS use only the VESA / VGA Modeswitching services, performing other functions, such as drawing, by directly programming the hardware. Often, using the BIOS can be the most reliable method of writing a modeswitcher, since the BIOS on the video card is tailor-made for the device to which it is attached.
One of the main drawbacks of using the BIOS to switch modes is that the BIOS might not provide a method of selecting a video refresh rate. However, some vendors do provide a way to set a specific refresh rate using the BIOS. If the BIOS for a particular device doesn't have refresh-rate support, you need to write a direct modeswitcher to program the hardware directly, in order to provide refresh rate support.
Video BIOSs are generally implemented with real 8086 code. Hence special support is required to allow execution of the BIOS under a memory-protected operating system.
Under QNX Neutrino, there's special support in the kernel that allows the Video BIOS to be executed in the x86 processor's Virtual 8086 mode.
This function registers your driver with the VBIOS services. You should call this function before calling any of the other vbios_* routines. The prototype is:
int vbios_register( disp_adapter_t *adp, unsigned flags );
The adp argument is a pointer to your driver's disp_adapter_t structure. There are currently no flags defined; pass 0 for the flags argument.
If your driver uses the video BIOS functions as well as the disp_* functions, it must call vbios_register() before calling disp_register_adapter(). |
This function stores a pointer to a vbios_context_t structure in the vbios member of the structure pointed to by adp. This pointer is to be used in subsequent calls to vbios_* functions.
This function initializes the xfer_area_seg, xfer_area_off, and xfer_area_ptr members of the vbios_context_t structure. These members point to a communications area that's used to supply data to and retrieve data from the BIOS code. The xfer_area_ptr member points to the communications area in the driver's address space. Together, xfer_area_seg and xfer_area_off specify the real-mode address that the BIOS code can use to address the communications area.
You driver should call this function when it no longer requires VBIOS services. The prototype is:
void vbios_unregister( vbios_context_t *ctx );
The ctx argument is a pointer to the vbios_context_t structure that vbios_register() stored in the vbios member of the driver's disp_adapter_t structure.
This function makes a BIOS call. The prototype is:
int vbios_int( vbios_context_t *ctx, int inum, vbios_regs_t *regs, int xfer_size );
The arguments are:
The driver accesses the communications area data via the xfer_area_ptr member of ctx, whereas the BIOS code accesses it using the real-mode address specified by xfer_area_seg and xfer_area_off.
This function makes a real-mode call. The prototype is:
int vbios_call( vbios_context_t *ctx, int seg, int offs, vbios_regs_t *regs, int xfer_size );
The ctx argument is a pointer to the vbios_context_t structure that vbios_register() stored in the vbios member of the driver's disp_adapter_t structure.
The seg and offs arguments specify the real-mode address of the routine to call. This function is similar to vbios_int(), except that the address of the routine to execute is passed directly, rather than coming from the real-mode Interrupt Vector Table.
The prototype is:
int vbios_dowarmboot( vbios_context_t *ctx, unsigned ax_val, unsigned char *biosptr, int bios_size );
In the event that there's more than one video card in the system, it may be necessary to perform a warm boot of the non-primary cards. At boot time, the system BIOS selects one of the adapters to be the BIOS primary. The system then calls the BIOS of this card in order to initialize the card and put it into text mode. Meanwhile, other graphics devices stay dormant.
Many drivers don't duplicate the code that's present in the BIOS, and which performs this device initialization; they rely on the fact that the BIOS has already performed basic device initialization. In fact, it often isn't possible to perform this initialization, since the driver may lack certain knowledge that's necessary to initialize the device (e.g. what type of memory is present on the adapter).
However, on devices that are compliant to PCI 2.1 (or greater), it's possible to perform the BIOS warm boot sequence for one of the nonprimary cards. This involves disabling the active VGA device and performing an initialization sequence similar to the sequence performed by the POST (Power On Self Test) on the primary VGA device, at boot time.
If your driver can't initialize the graphics device from scratch, you should call this routine. It determines whether or not the device has already been warm booted, and performs the BIOS POST sequence, if necessary.
The arguments to vbios_dowarmboot() are:
The prototype is:
void *vbios_get_realptr( vbios_context_t *ctx, unsigned seg, unsigned off );
This function takes the real-mode address specified via the segment seg and the offset off, and returns a pointer to this memory within the driver's address space.
The ctx argument is a pointer to the vbios_context_t structure that vbios_register() stored in the vbios member of the driver's disp_adapter_t structure.
Since not all addresses within the real-mode address range (0x00 - 0x10ffff) are usable by the driver or by the BIOS, this function can return NULL. Generally, the address specified by seg:off should reside either within the first 4K of memory, or within the range 0xa0000 - 0x100fff (640K to 1Meg + 4K).
This structure stores the Video BIOS context information; vbios_register() stores a pointer to this structure in the vbios member of the driver's disp_adapter_t structure.
typedef struct vbios_context { disp_adapter_t *adp; unsigned xfer_area_seg; unsigned xfer_area_off; unsigned char *xfer_area_ptr; } vbios_context_t;
The FFB (Flat Frame Buffer) library includes:
Core functions that you can fall back on in disp_draw_corefuncs_t:
Context functions that you can fall back on in disp_draw_contextfuncs_t:
Draw state update notify functions:
Color space conversion utility:
Miscellaneous:
Draw function retrieval routines:
You can use these functions when you create your driver. For example, you may start out with a driver that doesn't actually do very much, and instead relies upon the functionality of these routines to perform the work. As you progress in your development cycle, you can take over more and more functionality from these functions and do them in a card-specific manner (e.g. using the hardware acceleration).
In general, this can be done quite simply by taking the function table pointer that's passed to you in your initialization function, and calling the appropriate function (ffb_get_corefuncs() or ffb_get_contextfuncs()) to populate your function table array with the defaults from this library. Note, however, that all functions in the library are exposed; you don't have to bind to them by way of the ffb_get_*() functions; you can just simply link against them.
The next step in the development cycle is to take over some of the functions, and follow the outlines discussed above for each of them. If you find that you're able to support a given operation in a card-specific manner, separate that case out of the function call and handle it, while relying on the library routines to perform functions that your hardware doesn't support or that you don't wish to write the code for right at that point. Since the supplied libraries are hardware-independent (i.e. everything is implemented in software), they're likely to be much slower than your hardware-accelerated versions.
Another advantage of the way that the library and graphics framework are structured is that in case your driver becomes out-of-date (i.e. a newer version of the graphics framework has been released that has more functions), the shared library that's supplied with the newer version will know how to handle the extra functions, without any additional intervention on your part. You may then release a new version of your driver that supports accelerated versions of the extra function(s) at your convenience.
The following functions don't actually draw anything; instead, they return an error code, which notifies higher-level software that it needs to break down the primitive into simpler operations:
There are four sets of functions for some of the core functions supplied,
optimized based on the pixel depth.
For example, instead of the expected single function
ffb_draw_span(), there are in fact four of them:
Use the pixel_format argument passed to ffb_get_corefuncs() to determine which function to bind to the draw_span entry in the disp_draw_corefuncs_t function table. The other functions (that aren't listed as having 8/16/24/32 bits-per-pixel variants) support all pixel depths. The impact on your driver is that you may choose to call the ffb_get_corefuncs() four times, (once for each color depth), and fill four separate arrays, or you may choose to call it whenever your get_corefuncs() call-in is called, so that you can dynamically bind the appropriate library routines. The get_corefuncs() call-in gets called very infrequently (only during initialization and modeswitch operations) so efficiency isn't of paramount importance in this case. |
This function populates the function table pointed to by funcs with the core functions from the Flat Frame Buffer library. The prototype is:
int ffb_get_corefuncs ( disp_adapter_t *context, unsigned pixel_format, disp_draw_corefuncs_t *funcs, int tabsize)
This function returns 0 on success, or -1 if an error occurred (e.g. the pixel format is invalid).
This function populates the function table pointed to by funcs with the context functions from the Flat Frame Buffer library. The prototype is:
int ffb_get_contextfuncs ( disp_adapter_t *context, disp_draw_contextfuncs_t *funcs, int tabsize)
This function returns 0 on success, or -1 if an error occurred.
This function takes the color that corresponds to the surface type specified by srcformat, and returns a disp_color_t that corresponds to the same (or closest available) color in the surface type specified by dstformat. The prototype is:
disp_color_t ffb_color_translate ( disp_draw_context_t *context, int srcformat, int dstformat, disp_color_t color)
For more information about the surface types, see “Pixel formats” in the Writing a Graphics Driver chapter.
Note that this function doesn't yet handle a palette-based format for dstformat.