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:
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:
or:
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.
![]() |
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. |
The prototype of the minidriver handler function is:
int my_handler (int state, void *data);
The arguments are:
![]() |
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.
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:
For an example, see the Hardware Interaction Within the Minidriver appendix.
Use the following techniques to debug your minidriver:
kprintf("I am the minidriver!\n"); kprintf("Global variable mcounts=%d\n", mcounts);
For more information, see the Customizing Image Startup Programs chapter of Building Embedded Systems.
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:
extern int mini_data(int state, void *data);
/* Global variable: */ paddr_t mdriver_addr; /* Allocate 64 KB of memory for use by the minidriver */ mdriver_addr = alloc_ram(~0L, 65536, 1);
/* Add a minidriver function called "mini-data" for IRQ 81. */ mdriver_add("mini-data", 81, mini_data, mdrvr_addr, 65536);
For more information about the startup library functions, see the Customizing Image Startup Programs chapter of Building Embedded Systems.
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:
dptr = mmap_device_memory( 0, 65536, PROT_READ | PROT_WRITE | PROT_NOCACHE, 0, SYSPAGE_ENTRY(mdriver)->data_paddr );
Since the minidriver is still running at this point, it continues to run whenever the interrupt is triggered. Depending on the design, it may be necessary to do some processing of the existing data that has been stored by the minidriver before the full driver takes control.
For safety, the full driver should always disable the device interrupt before calling InterruptAttach() or InterruptAttachEvent(), and then enable the interrupt upon success.
After this, the minidriver is no longer called, and only the full driver receives the interrupt.
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:
[virtual=armle-v7,binary] .bootstrap = {
Note that the keyword +compress isn't included in this line. You should change the armle-v7 or binary entry to reflect your hardware and image format.
For example, if you compile your startup program as startup-my_board, you should copy it to the appropriate directory (e.g., ${QNX_TARGET}/armle-v7/boot/sys/startup-my_board-mdriver), and then change your buildfile to include startup-my_board-mdriver.
For more information and sample buildfiles, see the Sample Drivers for Instant Device Activation chapter of this guide.