Read Me About TradDriverLoaderLib

1.0b6

The TradDriverLoaderLib provides a bunch of routines helpful for installing traditional Mac OS device drivers ('DRVR's). While there have been many samples of how to do this in the past, this sample has a number of advantages:

TradDriverLoaderLib has been used by many developers (and quite a few system software projects) in the 2 years since it was first distributed.

Packing List

The distribution contains the following files:

TradDriverLoaderLib also depends on the following MoreIsBetter libraries:

Using the Sample

Running the Test Program

The distribution includes a copy of both the 68K and PPC builds of the test application (TestTradDriverLoader-68K and TestTradDriverLoader-PPC). To run the test application, just double click it in the Finder which, amongst other things, installs and removes 494 copies of the test driver!

Using the API from C

Installing and opening a 'DRVR' using TradDriverLoaderLib is very simple. You need to take the following steps:

  1. Add "TradDriverLoaderLib.c" to your project.
  2. If you are building a PPC client, add "MoreInterfaceLib.c" to your project.
  3. Include "TradDriverLoaderLib.h" in your source.
  4. When you want to install a driver, execute the following:

err = TradInstallDriverFromResource(0, "\p.MyDriverName",
              48,
              TradHighestUnitNumber() + 1,
              &gDriverRefNum);
if (err == noErr || err == dupFNErr) {
    err = TradOpenInstalledDriver(gDriverRefNum, fsRdWrPerm);
}

Using the API from Pascal

Installing and opening a 'DRVR' using TradDriverLoaderLib is very simple. You need to take the following steps:

  1. Add "TradDriverLoaderLib.c" to your project.
  2. If you are building a PPC client, add "MoreInterfaceLib.c" to your project.
  3. Use "TradDriverLoaderLib.p" in your source.
  4. When you want to install a driver, execute the following:

driverName := '.MyDriverName';
err := TradInstallDriverFromResource(0, @driverName,
              48,
              TradHighestUnitNumber + 1,
              gDriverRefNum);
if (err = noErr) or (err = dupFNErr) then begin
    err := TradOpenInstalledDriver(gDriverRefNum, fsRdWrPerm);
end; (* if *)

Building the Sample

The sample was built using the standard MoreIsBetter build environment (CodeWarrior Pro 2 compiler with Universal Interfaces 3.2). You should be able to build the project in CodeWarrior Pro 4 without difficulty. To build the test application, open the "TestTradDriverLoader.mcp" project and choose a target (either 68K or PPC), and choose Make from the Project menu. This will build either TestTradDriverLoader-68K or TestTradDriverLoader-PPC.

Advanced Topics

API Reference

All of the routines in the API are described in detail in the comments in the "TradDriverLoaderLib.h" file. I suggest you look there for more in-depth information about the services provided by the library.

Internal Implementation Details

The most important routine in TradDriverLoaderLib is TradInstallDriverFromPtr. Both TradInstallDriverFromHandle and TradInstallDriverFromResource call through to this routine. This routine takes a pointer to an 'DRVR' that has been loaded in the system heap and creates a Device Control Entry (DCE) in the system's unit table for that driver. The driver uses an interesting variant of DriverInstall (namely DriverInstallReserveMem) to ensure that the driver's DCE is loaded as low in the system heap as possible.

When you call TradInstallDriverFromHandle, it creates an appropriately sized pointer block in the system heap and copies the 'DRVR' you supply into that block. It then calls TradInstallDriverFromPtr on that block. This ensures that the driver code is loaded as low in the system heap as possible.

TradInstallDriverFromResource simply calls through to TradInstallDriverFromHandle.

The source code contains many comments on the specific details of installing a driver in the unit table.

Sharing Driver Code Between Multiple Instances

Because TradInstallDriverFromPtr takes a pointer to the driver and just jams it into the dCtlDriver field of the DCE without interpretation, you can use this routine to share code between device drivers. This is useful for things like serial drivers, which traditionally install two different drivers with different names. You can create one big chunk of code that has multiple driver headers in it, and then install that code into two different DCEs using TradInstallDriverFromPtr.

Obviously, this is not for the faint of heart (&endash;:

dRAMBased and dNeedLock

The dctlFlags field of the DCE has a bit known as dRAMBased. Most people interpret this bit as:

1 ==> device driver is in RAM

0 ==> driver is in ROM

This interpretation is wrong! The name dRAMBased is a historical artifact of the time when the system had ROM based drivers for hardware and RAM based driver for desk accessories.

The correct interpretation of this bit is:

1 ==> the dCtlDriver field of the DCE is a handle

0 ==> the dCtlDriver field of the DCE is a pointer

So you don't have to set this bit to load your driver into RAM. In additon, pointer-based driver are also more efficient because the Device Manager does not have to dereference the handle each time.

dNeedLock controls whether the Device Manager locks the DCE for your driver. If you set dNeedLock, the Device Manager will lock your driver's DCE whenever your driver is active.

Devices that operate at interrupt time (either accepting asynchronous requests or completing requests) must never have dRAMBased set and must always have dNeedLock set. Failing to do this will result in the Device Manager calling Memory Manager routines at interrupt time, with unpredictable and possibly catastrophic results.

TradDriverLoaderLib implements this recomendation and always loads drivers into RAM, making sure that dRAMBased is clear and dNeedLock is set.

Setting dNeedLock

This sample always sets dNeedLock. This is necessary to prevent the Device Manager from locking and unlocking the DCE while the driver operates. Instead the Device Manager notices that dNeedLock is set and locks the DCE once when the driver is opened. This is good for much the same reasons :

  1. It saves a few CPU cycles.
  2. It avoids the Device Manager calling the Memory Manager HLock and HUnlock routines at interrupt time. Devices that operate at interrupt time (either accepting asynchronous requests or completing requests) must always have dNeedLock set.

Caveats

There are no known caveats at this time.

Credits and Version History

Thanks for François Grieu for his invaluable contribution to this effort.

If you find any problems with this sample, mail <DTS@apple.com> with "Attn: Quinn" as the first line of your mail and I'll try to fix them up.

1.0b1 (Jan 1997) was distributed to a couple of developers.

1.0b2 (Feb 1997) is the first official release version.

1.0b3 (Feb 1997) incorporates changes from the original DTS reviewers. In addition, TradDriverLoaderLib now calls CloseDriver instead of FSClose.

1.0b4 (Mar 1997) incorporates changes and suggestions from François Grieu. Tidied up the description of dNeedLock and dRAMBased in the documentation, and made TradGetDriverInformation more paranoid about bogus drivers.

1.0b5 (Jul 1997) can now build PPC native TradDriverLoaderLib using snazzy MixedMode glue for DriverInstallReserveMem (which is not in InterfaceLib). Also increased kMaximumNumberOfUnitTableEntries to 1024 to reflect the new limit used by the PCI DriverLoaderLib included with Mac OS 8. Cosmetic documentation and code changes.

1.0b6 (Feb 1999) Integrated the library into MoreIsBetter. TradRemoveDriver and TradRenameDriver now check that the driver is a 'DRVR' (as opposed to an 'ndrv') and refuse to remove it otherwise. Recast the documention in HTML. Minor stylistic changes.

Share and Enjoy.

Quinn "The Eskimo!"
Apple Developer Technical Support
Networking, Communications, Hardware

26 Feb 1999