Hardware Interaction within the Minidriver

The following example shows how to interact with hardware from within a minidriver.The important message is that the mapping of hardware registers is different, depending on where in the boot process that the minidriver is called. This transition is handled in the MDRIVER_STARTUP_PREPARE and the MDRIVER_STARTUP_FINI stages.

/------------------------------------------------------------------------/
Here is a more complex example of a mini driver for a fictional hardware
device called 'MYBUS'.
Characteristics of MYBUS are as follows:

- series of 8 bit registers (status and data) at address 0xFF000000  (MBAR)
- interrupt 56 is generated when a character arrives at the MYBUS port

There are registers at this address which will be read/written to.

/------------------------------------------------------------------------/

#include "startup.h"      // this is included with the BSP for your board

   typedef unsigned char  U8;
   typedef unsigned short U16;
   typedef unsigned int   U32;

/**************    MYBUS Registers   ********************/

typedef struct MYBUS_register_set {
   volatile U8  interrupt_status;
   volatile U8  data_register;
   volatile U8  control_register;
   volatile U8  extra1;
   volatile U8  extra2;
   volatile U8  extra3;
   volatile U8  extra4;
   volatile U8  extra5;
}  MYBUS_regs_t;

/**************    GPIO Registers   ********************/

typedef struct GPIO_register_set {
   volatile U32  gpio0;
   volatile U32  gpio1;
}  GPIO_regs_t;

/**************    Mini-driver data area   ************/

typedef struct
{
   MYBUS_regs    *MYBUS_REGS;                   //this value will either be 
                                                //same as PREKERNEL or POSTKERNEL
   MYBUS_regs    *MYBUS_REGS_PREKERNEL_START;   //register  mappings to 
                                                //use before kernel starts
   MYBUS_regs    *MYBUS_REGS_POSTKERNEL_START;  //register mappings to use 
                                                //after kernel starts
   U16           total_message_counter;         //total times mini-handler 
                                                //is called
   U16           process_counter;               //times called after kernel 
                                                //is running
   U16           kernel_counter;                //times called during 
                                                //kernel booting
   U16           data_len;                      // length of data portion 
                                                //stored in the data area
}MYBUS_data_t;


// Physical memory locations and offsets

#define MBAR_BASE 0xff000000
#define GPIO_OFFSET 0x0C00
#define MYBUS_OFFSET 0x2400

// control_register settings
#define CTRL_INTERRUPT_ON       0x01
#define CTRL_INTERRUPT_OFF      0x00

/*********************************************************
void MYBUS_Init(void)

Hardware initialization function for MYBUS.
This routine is only called once when the mini-driver is
started.

INPUTS   None

OUTPUTS  None

 *********************************************************/
 
 
 
static MYBUS_regs_t *
MYBUS_Init(void)
{
   GPIO_regs_t   *GPIO_REGS_P;
   MYBUS_regs_t    *MYBUS_REGS_P;
   U32 data_byte;

   if((GPIO_REGS_P 
      = (GPIO_regs_t *)startup_memory_map(0x40,MBAR_BASE + GPIO_OFFSET), 
        PROT_READ|PROT_WRITE|PROT_NOCACHE)) == 0 )
      {
          startup_memory_unmap((unsigned)GPIO_REGS_P);
          return (0);
      }

      // change GPIO as needed
      Data = GPIO_REGS_P->gpio0;
      Data = Data & 0xFFF0FFFF;
      GPIO_REGS_P->gpio0 = Data;

      // we are done with GPIO
      startup_memory_unmap((void *)PORT_REGS_P);

      if((MYBUS_REGS_P 
         = (MYBUS_regs_t *)startup_memory_map(0x10, 
           (MBAR_BASE + MYBUS_OFFSET), 
           PROT_READ|PROT_WRITE|PROT_NOCACHE)) == 0
         {
             startup_memory_unmap((unsigned)MYBUS_REGS_P);
             return (0);
         }

        // initialize MYBUS and turn on the interrupt

        // write any values to the MYBUS_REGS_P as needed .. 
	//then turn on the interrupt source

        MYBUS_REGS_P->control_register = CTRL_INTERRUPT_ON;

        kprintf("MYBUS is initialized\n" );
        return ( MYBUS_REGS_P );

}

/*********************************************************
   int mini_mybus_handler(void)


 *********************************************************/

int mini_mybus_handler(int state, void *data)
{
   U8              *dptr;
   U8 StatusReg;
   U8 notValid;
   MYBUS_data_t    *mdata;
   int             val;

   mdata = (MYBUS_data_t *) data;
   dptr = data + sizeof(MYBUS_data_t);

   if (state == MDRIVER_INTR_ATTACH)
      {
          kprintf("Real driver is attaching .. mini-driver 
                        was called %d times\n", mdata->total_message_counter);

          // disable MYBUS interrupt
          mdata->MYBUS_REGS_POSTKERNEL->control_register = CTRL_INTERRUPT_OFF;
                return (1);
      }

      else if (state == MDRIVER_INIT)
      {
         /* the first time called initialize the hardware and do data setup */
         mdata->MYBUS_REGS_PREKERNEL = MYBUS_Init();
         if (mdata->MYBUS_REGS_PREKERNEL == 0)
         return (1);


         // make our default register location reflect the fact that we are in PREKERNEL
         mdata->MYBUS_REGS = mdata->MYBUS_REGS_PREKERNEL;

         mdata->total_message_counter = 0;    // Initialize messages received
                mdata->process_counter = 0;
                mdata->kernel_counter = 0;
        }
        else if (state == MDRIVER_PROCESS)
        {
                mdata->process_counter++;
        }
        else if (state == MDRIVER_KERNEL)
        {
                mdata->kernel_counter++;
        }
        else if (state == MDRIVER_STARTUP_PREPARE)
        {
           /* once we are out of startup use */ 
	   /* callout_io_map or callout_memory_map */
           kprintf("I am in STARTUP PREPARE %x\n", mdata->total_message_counter);
           if ((mdata->MYBUS_REGS_POSTKERNEL = (MYBUS_regs_t
                *)(callout_memory_map(0x10,(MBAR_BASE + MYBUS_OFFSET), 
                PROT_READ|PROT_WRITE|PROT_NOCACHE)))$
           {
              /* something bad happened ..disable the interrupt */
	      /* and turn off the mini-driver */
              mdata->MYBUS_REGS_PREKERNEL->control_register = CTRL_INTERRUPT_OFF;
                 return (1);
            }

        }

        // at this point, we use MYBUS_REGS .. we could either be in startup, in 
        kernel loading or at process time

        // read the interrupt status register immediately upon entry to the handler
        StatusReg = mdata->MYBUS_REGS->interrupt_status;

        // increase message counter
        mdata->total_message_counter++;

        switch( StatusReg ) {
        // read my data and add to my data area (after data_len in MYBUS_data_t)

        // make sure that you clear the source of interrupt before you return

        // ...
        }

        if (state == MDRIVER_STARTUP_FINI)
        {
           val = mdata->total_message_counter;
           kprintf("I am in state STARTUP FINI. Total messages processed=%x \n", val);

           // startup has finished.. now I switch over to use the POSTKERNEL mapping
           mdata->MYBUS_REGS = mdata->MYBUS_REGS_POSTKERNEL;
        }
        return (0);
}