From Hello World to Real World - Part 6/6

From EDM2
Jump to: navigation, search

Written by Alger Pike

Part 1

Part 2

Part 3

Part 4

Part 5

Part 6

Interrupt Processing


Although setting code up to use interrupts is an extra step in the coding process for a device driver, it is often worth the effort. By using interrupts, the application can do other useful things while waiting for the device to signal. The time scale of the wait can often be several milliseconds (ms). This is enough time to do some data processing or update the screen. There are several functions that are in the devhlp library, that make adding interrupt support relatively simple.

The first step is to set up the function that will handle the interrupt and associate it with a particular interrupt. This function, the Interrupt Service Routine (ISR), is called whenever an interrupt is generated by the device. Once the device has issued the interrupt, the processor stops what it is doing and executes the ISR. The only mandatory action of this function is to issue an end of interrupt statement. Besides this mandatory action, the ISR is free do whatever it needs to do when the interrupt occurs. Keep in mind however, that an interrupt routine that is too resource intensive can reduce system performance. For this reason, it is advisable to keep your ISR as short as possible. At this point we add a new file to our collection of device driver code. It is called ints.c, and this is where the interrupt handler is located. A typical interrupt handler looks like the following:

extern "C" VOID int_hand(void);

extern HCONTEXT Hook1;

#pragma aux (STRATEGY) int_hand;

VOID int_hand(void)
  //Tell OS we can reset interrupt

  //Good way to hear if you are calling your routine at first
  //DevBeep(500, 100);

  //Call our hook which will trigger back to app
  DevCtxArm(Hook1, (WORD32) 0); 

Figure 1: Interrupt Handler: Notice the special calling convention. This function will be called anytime the system receives an interrupt on level 9.

The calling convention for the function is important. The operating system is expecting the function to have a certain calling convention. We must make our interrupt handler comply with the preset standard. For details on the definition of the STRATEGY calling convention see devhelp.h.

The next step is to associate the above ISR with a particular interrupt level. My board will generate an interrupt on level 9 in response to a user programmed event. To set the board for the appropriate event I set up another IOCTL call. This IOCTL will also tell the operating system to call int_hand when an interrupt 9 occurs. This is done using the DevSetIRQ library function. The code to do all of this is as follows:

//Set Interrupt
case 0x61:
  unsigned int B;

  //Set board to use interrupt processing
  //Anytime the counter Finishes it will trigger interrupt 9
  //Get Interrupt control status of D/A card
  B = inpw(0x280 + 8);

  //Enable interrupt Processing
  // Set B3
  B = B | 0x0008;

  //Interrupt on counter done
  //Clear B1
  B = B & 0xFFF8;

  //Set B2,  and set B0
  B = B + 5;

  outpw(0x280 + 8, B);

  //Register for an interrupt
  DevIRQSet(9, int_hand, 0);

Figure 2: This IOCTL call setups up the D/A card to use interrupts when its timer is done. It also associates int_hand with interrupt level 9.

After associating int_hand with interrupt 9, the operating system will call int_hand any time the board generates an interrupt.

The first task of this interrupt handler is to issue the DevEOI call. This call tells the 8259 PIC that it can send another interrupt on this same channel. The rest of the handler contains the device specific code. With this handler, I need to be able to post an event semaphore back to the 32-bit application that is waiting on this semaphore. There are devhlp library functions that allow a device driver to use a 32-bit shared event semaphore. The only condition placed on these functions, is that they must be called from a task or strategy time thread. This means we cannot call these function directly from our interrupt handler. There is, however, a way for the interrupt handler to call a second thread which runs at task level. This is done using a context hook. The context hook runs as a separate thread and runs in the task context so it can call all the task devhlp functions.

The first step is to create the hook. This is done using the devhlp function DevCtxAllocate. The init section is a good place to create the context hook and associate it with the proper function. Like with interrupts this function must have a predefined calling convention. The following code sets up a context hook for use by the interrupt routine:

//Allocate Context Hook for Interrupt processing
//Note this call has been changed from the original DevHelp file
//You may have to change the code to get it to work with your library.

Hook1 = DevCtxAllocate((VOID FAR*) ctx_hand);
if (!Hook1)

  // Signal that we will not install without a context hook
  rp->Out.FinalCS = 0;
  rp->Out.FinalDS = 0;

  // Tell User no hook could be allocated

  return RPDONE;

Figure 3: Excerpt from init. This code allocates the context hook to be used by the interrupt handler.

The context hook will be executed in response to a DevCtxArm. This is done in the interrupt handler since we want the hook to execute when an interrupt occurs. Since this thread will run in a task time context it can use the devhlp functions that deal with event semaphores. The context function posts an event semaphore back to the 32-bit application is waiting on it.

This semaphore must first be opened by the device driver so that the driver can use it. This is done by passing the handle of a shared event semaphore, created by the 32-bit application, to the driver by using an IOCTL. The semaphore is then opened by the driver using the DevEventOpen devhlp library function:

//Send driver a shared event semaphore handle
case 0x63:

  WORD16 result;

  //Get handle of semaphore from app
  *((WORD32 *) &motorSemaphore) = *((WORD32 FAR*)rp->ParmPacket);

  //Open Event Semaphore and return error to app
  result = DevEventOpen(motorSemaphore);
  *((WORD16 FAR*)rp->DataPacket) = *((WORD16 *) &result);

Figure 9: Before you can use the event semaphore you must first register it with the device driver. This must be done before your device generates any interrupts.

This handle can now be used as a normal event semaphore, to trigger back to the 32-bit application. In order to post the event and tell the caller to continue, issue a DevEventPost. This function requires a task time thread to execute properly. Therefore it belongs in the context hook routine; notice the calling convention required for the context hook:

extern "C" VOID __far __saveregs __loadds ctx_hand(void);

HCONTEXT Hook1 = 0;

VOID  __far __saveregs __loadds ctx_hand(void)

  //Easy way to tell if you are calling the hook
  //DevBeep(500, 20); //Comment out because hook works
  //Tell Application DA counter has finished

Figure 10: Code required for the context hook. The hook is able to call task time devhlp functions and can post the opened event semaphore to the 32-bit application.

Now whenever an interrupt occurs, the ISR will be triggered. The ISR then triggers the context hook, which in turn signals the main application.

At this point we are now finished writing our device driver. It has all of the code that it needs to manage the device. You have now seen many of the things that are required for a data acquisition device driver. The driver is responsible for three major areas: 1) It must be an application manager for the device, 2) It handles the devices I/O calls, and 3) handles the devices interrupt needs. With these three goals met the user is now free to start interfacing with the ring 3 application and start taking data for his particular application. Now you should have all the tools you need to write a device driver for your own data acquisition card. For additional information I refer you to "Writing OS/2 2.1 Device Drivers in C, 2nd Edition" by Steve Mastrianni and also IBM's DDK which is now free and online at