Sample Minidriver

This chapter includes:

The minidriver program in this example is a simple implementation that you can use for debugging purposes. It counts the number of times it's called for each phase of the boot process and stores that information in its data area. Once the system is booted, a program can read the data area and retrieve this information. No hardware access is required for this minidriver.

In this example, the size of the data area is 64 KB. If you decrease the value of mdriver_max (the amount of data that's copied from flash to RAM between calls of your minidriver) from its default 16 KB, then you may need to increase the size of the data area because the handler function will be called more times.

The minidriver handler function

For this sample driver, the source code for the handler function looks like this:

struct mini_data
{
   uint16_t	nstartup;
   uint16_t	nstartupp;
   uint16_t	nstartupf;
   uint16_t	nkernel;
   uint16_t	nprocess;
   uint16_t	data_len;
};

/*
 * Sample minidriver handler function for debug purposes
 * 
 * Counts the number of calls for each state and
 * fills the data area with the current handler state
 */
int 
mini_data(int state, void *data)
{
   uint8_t		*dptr;
   struct mini_data	*mdata;
		
   mdata = (struct mini_data *) data;
   dptr = (uint8_t *) (mdata + 1);
	
   /* on MDRIVER_INIT, set up the data area */
   if (state == MDRIVER_INIT)
   {
      mdata->nstartup = 0;
      mdata->nstartupf = 0;
      mdata->nstartupp = 0;
      mdata->nkernel = 0;
      mdata->nprocess = 0;
      mdata->data_len = 0;
   }
	
   /* count the number of calls we get for each type */	
   if (state == MDRIVER_STARTUP)
      mdata->nstartup = mdata->nstartup + 1;
   else if (state == MDRIVER_STARTUP_PREPARE)
      mdata->nstartupp = mdata->nstartupp + 1;
   else if (state == MDRIVER_STARTUP_FINI)
      mdata->nstartupf = mdata->nstartupf + 1;
   else if (state == MDRIVER_KERNEL)
      mdata->nkernel = mdata->nkernel + 1;
   else if (state == MDRIVER_PROCESS)
      mdata->nprocess = mdata->nprocess + 1;
   else if (state == MDRIVER_INTR_ATTACH)
   {
      /* normally disable my interrupt */
      return (1);
   }
	
   /* put the state information in the data area 
   after the structure if we have room */
		
   if (mdata->data_len < 60000 ) {
      dptr[mdata->data_len] = (uint8_t) state;
      mdata->data_len = mdata->data_len + 1;
   }
		
   return (0);
}

A few things to note:

Adding your minidriver to the system

The main() function of startup main.c looks like this:

...
paddr_t		mdrvr_addr;
...

/*
* Collect information on all free RAM in the system.
*/
init_raminfo();

/* In a virtual system we need to initialize the page tables */

if(shdr->flags1 & STARTUP_HDR_FLAGS1_VIRTUAL)
{
   init_mmu();
}

/* The following routines have hardware or system dependencies that
   may need to be changed. */
init_intrinfo();

/* Allocate a 64 KB data area. */
mdrvr_addr = alloc_ram(~0L, 65535, 1);

/* Register the minidriver and its handler function. */
mdriver_add("mini-data",  0,  mini_data, mdrvr_addr, 65535);
...

The name stored in the system page for our minidriver is mini-data.

Test application: mini-peeker.c

Here's the source code for a test application called mini-peeker.c that maps in the minidriver data area and prints the contents:

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/neutrino.h>
#include <sys/syspage.h>
#include <hw/inout.h>
#include <inttypes.h>

struct mini_data
{
   uint16_t     nstartup;
   uint16_t     nstartupp;
   uint16_t     nstartupf;
   uint16_t     nkernel;
   uint16_t     nprocess;
   uint16_t     data_len;
};

int main(int argc, char *argv[]) 
{
   int               i, count;
   int               dump_data = 0;
   uint8_t           *dptr;
   struct mini_data  *mdata;
        
   if (argv[1])
       dump_data = 1;
                
   ThreadCtl(_NTO_TCTL_IO, 0);

   /* map in minidriver data area */
   if ((dptr = mmap_device_memory(0, 65535, PROT_READ | 
                  PROT_WRITE | PROT_NOCACHE, 0, 
                  SYSPAGE_ENTRY(mdriver)->data_paddr)) == NULL)
   {
        fprintf(stderr, "Unable to get data pointer\n");
        return (-1);
   }

   mdata = (struct mini_data *) dptr;
   dptr = dptr + sizeof(struct mini_data);

   /* dump mini-driver data */  
   printf("---------------- MDRIVER DATA -------------------\n");
   printf("\tMDRIVER_STARTUP         calls = %d\n", mdata->nstartup);
   printf("\tMDRIVER_STARTUP_PREPARE calls = %d\n", mdata->nstartupp);
   printf("\tMDRIVER_STARTUP_FINI    calls = %d\n", mdata->nstartupf);
   printf("\tMDRIVER_KERNEL          calls = %d\n", mdata->nkernel);
   printf("\tMDRIVER_PROCESS         calls = %d\n", mdata->nprocess);
   printf("\tData Length             calls = %d\n", mdata->data_len);
   count = mdata->data_len;

   if (dump_data)
   {    
       printf("State information:\n");
       for (i = 0; i < count; i++)
            printf("%d\n", dptr[i]);
    }
    printf("\n---------------------------------\n");

   return EXIT_SUCCESS;
}

Transition from minidriver to full driver

Here's an example of the code in the full driver that arranges the transition from the minidriver:

if ((id == InterruptAttachEvent(intr, event,
              _NTO_INTR_FLAGS_TRK_MSK)) == -1)
{
   perror("InterruptAttachEvent\n");
   return (-1);
}


if ((dptr = mmap_device_memory(0, data_size,
               PROT_READ | PROT_WRITE | PROT_NOCACHE, 
               0, SYSPAGE_ENTRY(mdriver)->data_paddr)) == NULL)
{
   fprintf(stderr, "Unable to get data pointer\n");
   return (-1);
}

/* Your minidriver should now be stopped and you should 
   have access to the interrupt and the data area */

/* Enable device interrupt (intr) */

Once the full driver is attached to the interrupt, it can process any buffered data and continue to provide hardware access.