A Hello World Device Driver - Part 2/6

From EDM2
Jump to: navigation, search

Written by Alger Pike

Part 1

Part 2

Part 3

Part 4

Part 5

Part 6

Putting your driver to work

Introduction

[NOTE: You can download the source code here.]

In the first part of this series, we examined how a driver communicates with the operating system and the code required to do this. Up to this point the device driver did not do anything fancy. If you tried loading it, you have seen it load, giving you the initial Hello World message. You may also want to try to open the file with DosOpen and send it a DosDevIOCtl. With this driver however, you do not know whether it is responding correctly or not because the driver does not do anything. Let's put the driver to work and make it do something. This way we can test our interface code and see if our driver is responding.

Because everyone probably wants to control a different type of device, this makes it hard to pick what the driver should do. However, I want this driver to be as generic as possible, so to achieve this goal, I will make the driver beep when we send it an IOCTL call. This way the driver should work on all machines without any problems.

Ready for Action

The first step in getting the driver to do something is to add to its IOCTL section. If you remember back in part one of the series, we mentioned that all of the user code of a device driver goes into the IOCTL section. The IOCTL section for this driver package is contained in the file iotcl.c. There are two ways you can follow this example: 1) follow along this article, or 2) the better way, make the changes to hello.sys in the iotcl.c as I discuss them. This way you can get down to doing some device driver writing yourself.

Since each user function is resolved with a number, our first job is to pick a unique number for our function. Since this driver does not have any numbers yet, let's start with the number one. In the switch statement of iotcl.c, we need to put in a case statement which will handle requests for a function 0x01. Your IOCTL function should now look like this:

switch (rp->Function)
  {
  case 0x01:
    break;
  default:
    break;
  }

Figure 10.) Framework for IOCtl requests

Making it Beep

The next step is to add the functionality to the 0x01 request. Since we are going to make the computer beep this is very easy to do. As it turns out, DosBeep is one of the API calls that device drivers are allowed to call. We add the Dos Beep Statement in the following way:

case 0x01:
  DosBeep(2000, 500);
  break;

Figure 11.) Functionality for IOCtl request

Now, we need to remake the driver code; we should then be able to load this driver. To test it, we will write a short program that will help us determine if the driver is working or not. As a note, if you made the changes yourself to ioctl.c, you may also want to change the name of the driver to beep in the header.asm and also change the target name to beep in the makefile. These changes will make it easier to follow the example for testing the driver. After you make all the required changes remake the driver by typing wmake from the directory where the files are located. Now you are ready to load the driver; add DEVICE= YOURDIRECTORY\ beep.sys to your config.sys, reboot and then you will be ready to continue.

Calling the Device Driver

At this point you should have the beep.sys successfully loaded. We are now ready to write a simple program to test the driver to make sure that it works correctly. The first step is to open the driver and make it available to take user requests. Do this with the following code:

DosOpen((PSZ) "beep$   ",
        &driver_handle,
        &ActionTaken,
        0,
        0,
        FILE_OPEN,
        OPEN_SHARE_DENYNONE
          | OPEN_ACCESS_READWRITE,
        NULL);

Figure 12.) Open beep.sys

For the proper variables to declare, and include files to set up, see test.c that comes with beep.zip. Now, add some error handling code, so that if you have problems you can look in the OS/2 reference manuals to find out what the code means. Here is the code that I used:

if(rc)
  {
  printf("\nDosOpen failed, error = %d",rc);
  getchar();
  DosExit(EXIT_PROCESS,0);
  }

Figure 13.) Debug routine for DosOpen

Now that the driver is open, we can send it a request for the 0x01 function that we just wrote to see if the driver responds. Of course, if it does we will hear a half second beep at 2000 Hz. This step, along with the error handling code, are as follows:

rc = DosDevIOCtl(driver_handle, 0x91,
                 0x01,0,0,0,0,0,0);

if (rc)
  printf ("DevIOCtlfailed, error code = %ld\n", rc);
else
  //No errors encountered driver should be working
  printf ("You should have heard a beep.\n");

Figure 14.) Send the driver a request

Closing Up

The last step in using the driver is to close it when we are done with it. Note that this step is critical for drivers which limit the number of application instances that use the driver to one. The following API code is used to close the driver:

DosClose(driver_handle);

Figure 15.) Close the driver

Summary

You should now have a functioning driver that beeps when you make a category 0x91, function 0x01 IOCTL request to it. Although this driver is not terribly useful in a real world situation, it shows you the basics of how to add your own code to a driver. The driver is still limited in what it does; what it does do is hard-coded into the driver. In a real world situation you will want to be able to pass the driver your own data, and also get a result back from your code. In the next section we will examine how we can pass our own data to the user functions and how we get return values back from it.