Writing a Minidriver

This chapter includes:

In order to write a minidriver, you must first decide on the following:

The BSP associated with your hardware platform includes the source code to the board's startup program. You must link your minidriver to this program. For more information, see the BSP documentation, as well as Building Embedded Systems.

You'll need to modify these files:

Don't modify the following files unless you're directed to do so by QNX Software Systems, but make sure they're included in your startup code directory:

Timing requirements

Since the minidriver code is polled during the startup and kernel-initialization phases of the boot process, you need to know the timing of your device in order to verify if the poll rate is fast enough. Most of the time in startup is spent copying the boot image from flash to RAM, and the minidriver is polled during this time period. The sequence might look like this:

The startup library contains a global variable mdriver_max, which is the amount of data (in bytes) that's copied from flash to RAM between calls to your minidriver. This variable is defined in mdriver_max.c, and its default value is 16 KB.

You might have to experiment to determine the best value, based on the timing requirements of your device, processor speed, and the flash.

In order to change this value, you can:

Data storage

The minidriver program usually requires a space to store the received hardware data and any other information that the full driver will later need at process time. You need to determine the amount of data you require and allocate the memory.

As we'll see, you allocate the data area in the startup's main() function and provide the area's physical address when you register the handler function. When the handler is invoked, it's passed the area's virtual address.


Caution: This area of memory isn't internally managed by the system; it's your driver's responsibility to avoid overwriting system memory. If your handler function writes outside of its data area, a system failure could occur, and the operating system might not boot.

Handler function

The prototype of the minidriver handler function is:

int my_handler (int state, void *data);

The arguments are:

state
Indicates when the handler is being called. It can have one of the following values, defined in <sys/syspage.h>, and presented here in chronological order:
data
The virtual address of the data area, converted from the physical address that you provided as the data_paddr parameter to mdriver_add().

Note: If you're working with an ARM platform, your minidriver handler function must be written as Position Independent Code (PIC). This means that when your handler is in the MDRIVER_KERNEL, MDRIVER_PROCESS, or MDRIVER_INTR_ATTACH state, you must not use global or static variables.

The handler function should return:

Don't assume that just because the handler has been called that the device actually needs servicing.

Hardware access

The minidriver program most likely requires hardware access, meaning it needs to read and write hardware registers. In order to help you access hardware registers, the startup library provides function calls to map and unmap physical memory. At different times in the boot process, some calls may or may not be available:

For more information, see the Customizing Image Startup Programs chapter of Building Embedded Systems.

Here's a summary of what you need to do at each state if your minidriver needs to access hardware:

MDRIVER_INIT
MDRIVER_STARTUP
Use ptr1 to do all hardware access. No memory map calls are needed.
MDRIVER_STARTUP_PREPARE
At this point, The minidriver should call callout_io_map() or callout_memory_map(), Store the returned pointer (which we'll call ptr2) in the minidriver data area or in a static variable, separate from the previously stored value. Don't use ptr2 yet; continue to use ptr1 to do all hardware access.
MDRIVER_STARTUP_FINI
This is the last call to your handler from within startup, so it's the last state in which you'll use ptr1 to do all hardware access. After this state, you'll use ptr2 instead.
MDRIVER_KERNEL, MDRIVER_PROCESS, MDRIVER_INTR_ATTACH
In these states, use ptr2 to do all hardware access.

For an example, see the Hardware Interaction Within the Minidriver appendix.

Debugging from within the minidriver

Use the following techniques to debug your minidriver:

Customizing the startup program to include your minidriver

You need to modify the startup code in the BSP's main.c file in order to set up the minidriver's data area, register the handler function, and so on. Depending on what your minidriver needs to do, you might have to do the following:

For more information about the startup library functions, see the Customizing Image Startup Programs chapter of Building Embedded Systems.

Making the transition to a full driver

Once the kernel is running and interrupts are enabled, the minidriver continues to be called when the interrupt that it's attached to is triggered. This action can continue for the lifetime of the system; in other words, the minidriver can behave like a tiny interrupt handler that's always active.

Usually the hardware needs more attention than the minidriver is set up to give it, so you'll want the minidriver to hand off to a full driver.

Here's the sequence of events for doing this:

Making a boot image that includes your minidriver

At this point, you have a startup program (including your minidriver code) that's been compiled. Now include this startup program in the QNX Neutrino boot image and try out the minidriver.

There are some basic rules to follow when building a boot image that includes a minidriver: