Legacy Documentclose button

Important: The information in this document is obsolete and should not be used for new development.

Previous Book Contents Book Index Next

Inside Macintosh: Devices /
Chapter 1 - Device Manager


Writing a Device Driver

This section shows you how to write a basic device driver--one that can respond to Device Manager requests. Although you will need to write some assembly-language interface code, you can write your device driver routines in a high-level language.

Before you decide to write your own device driver, you should consider whether your task can be more easily accomplished using one of the standard Macintosh drivers described in this book or other Inside Macintosh volumes. In general, you should consider writing a device driver only if your hardware device or system service needs to be accessed at unpredictable times or by more than one application.

For example, if you develop a new output device that you want to make available to any application, you might need to write a custom driver. On the other hand, if your product is a specialized device that can only be used by your application, it may be easier to control the device using private code within your application.

This section describes how to

Creating a Driver Resource

You will probably want to store your device driver in a driver resource, although if you are writing a driver for a slot device, you might want to store your driver in an sResource data structure in the declaration ROM of the expansion card. See the chapter "Slot Manager" in this book for information about sResource data structures.

Storing your driver in a driver resource allows the Device Manager to load your driver code into memory and install a device control entry for your driver in the unit table. Like all resources, your driver resource has a resource type, a resource ID, a resource name, and resource attributes.

A driver resource has two parts:

The driver header contains a few words of flags and other data, offsets to the driver's routines, and an optional driver name. Figure 1-9 shows the format of a driver header.

Figure 1-9 The driver header

The elements of the driver header are:

Element Description
drvrFlags
Flags in the high-order byte of this field specify certain characteristics of the driver. These flags are copied to the high-order byte of the dCtlFlags field of the device control entry when the driver is opened. You can use the constants shown in Listing 1-6 to set or test the flags in this field.
NameBitMeaning
 dReadEnable8Set if the driver can respond to read requests.
 dWritEnable9Set if the driver can respond to write requests.
 dCtlEnable10Set if the driver can respond to control requests.
 dStatEnable11Set if the driver can respond to status requests.
 dNeedGoodbye12Set if the driver needs to be called before the application heap is reinitialized.
 dNeedTime13Set if the driver needs time for performing periodic tasks.
 dNeedLock14Set if the driver needs to be locked in memory as soon as it is opened.
drvrDelay
If the dNeedTime flag is set, this field contains the requested number of ticks between periodic actions. This value is approximate and should not be used as a timing reference.
drvrEMask
Used only by desk accessories, this field contains an event mask. See "Writing a Desk Accessory" on page 1-49 for information about this field.
drvrMenu
Used only by desk accessories, this field contains a menu ID. See "Writing a Desk Accessory" on page 1-49 for more information.
drvrOpen
The offset of the driver's open routine, relative to offset 0 of the driver header.
drvrPrime
The offset of the driver's prime routine.
drvrCtl
The offset of the driver's control routine.
drvrStatus
The offset of the driver's status routine.
drvrClose
The offset of the driver's close routine.
drvrName
A Pascal string containing the driver's name, up to 255 characters.
See the section "Entering and Exiting From Driver Routines" on page 1-29 for more information about the routine offsets.

Note
Your driver routines, which follow the driver header, must be aligned on a word boundary.
Listing 1-6 Driver flag constants

enum {
   /* flags used in the driver header and device control entry */
   dNeedLockMask    = 0x4000, /* set if driver must be locked in memory as
                                 soon as it's opened */ 
   dNeedTimeMask    = 0x2000, /* set if driver needs time for performing
                                 periodic tasks */ 
   dNeedGoodByeMask = 0x1000, /* set if driver needs to be called before the
                                 application heap is initialized */ 
   dStatEnableMask  = 0x0800, /* set if driver responds to status requests */ 
   dCtlEnableMask   = 0x0400, /* set if driver responds to control requests */ 
   dWritEnableMask  = 0x0200, /* set if driver responds to write requests */ 
   dReadEnableMask  = 0x0100, /* set if driver responds to read requests */ 
};
The dReadEnable, dWritEnable, dCtlEnable, and dStatEnable flags indicate which Device Manager requests the device driver can respond to. The next section, "Responding to the Device Manager," describes these routines in detail.

Drivers in the application heap are lost when the heap is reinitialized. If you set the dNeedGoodbye flag, the Device Manager calls your driver before the heap is reinitialized so that you can perform any clean-up actions. See "Writing Control and Status Routines," beginning on page 1-34, for information about using this flag.

You set the dNeedTime flag if your device driver needs to perform some action periodically. For example, a network driver may want to poll its input buffer every 5 seconds to see if it has received any messages. The value of the drvrDelay field indicates how many ticks should pass between periodic actions. For example, a value of 0 in the drvrDelay field indicates that the action should happen as often as possible, a value of 1 means it should happen every sixtieth of a second, a value of 2 means at most every thirtieth of a second, and so on. Whether the action actually occurs this frequently depends on how often an application calls WaitNextEvent or SystemTask. See "Writing Control and Status Routines," beginning on page 1-34, for information about using this flag.

Note
If you do not want your driver to depend on applications to call WaitNextEvent or SystemTask, you can perform actions periodically by installing a VBL task, a Deferred Task Manager task, a Time Manager task, or a Notification Manager task. For more information, see Inside Macintosh: Processes.
You need to set the dNeedLock flag if your device driver's code must be locked in memory. In particular, you need to set this flag in these two cases:

You can create your driver header in these ways:

Listing 1-7 An assembly-language driver header

DHeader
DFlags   DC.W  0                  ;set by MyDriverOpen
DDelay   DC.W  0                  ;none
DEMask   DC.W  0                  ;DA event mask
DMenu    DC.W  0                  ;no menu
         DC.W  DOpen - DHeader    ;offset to Open
         DC.W  DPrime - DHeader   ;offset to Prime
         DC.W  DControl - DHeader ;offset to Control
         DC.W  DStatus - DHeader  ;offset to Status
         DC.W  DClose - DHeader   ;offset to Close
Name     DC.B  '.MYDRIVER'        ;driver name
         ALIGN 2                  ;word alignment
In this example, the drvrFlags word is cleared to 0 because the flags are set by the MyDriverOpen function, shown in Listing 1-9 on page 1-32. This is an implementation decision--you can set the flags in the driver header or in your driver's open routine. The drvrDelay field is set to 0 because this driver does not perform any periodic actions using the SystemTask function. The drvrEMask and drvrMenu fields are set to 0, as this driver is not a desk accessory. The next five fields contain offsets to the driver routines, defined in the next section, "Responding to the Device Manager." The header ends with the driver name and the word alignment directive.

Responding to the Device Manager

The Device Manager calls a driver routine by setting up registers and jumping to the address indicated by the routine's offset in the driver header.

This interface requires you to use some assembly language when writing a driver. However, you can write your driver routines in a high-level language if you provide an assembly-language dispatching mechanism that acts as an interface between the Device Manager and your driver routines.

The next few sections discuss how you can provide a dispatching routine and how you can implement your driver routines in a high-level language.

Entering and Exiting From Driver Routines

Listing 1-8 shows an assembly-language dispatching routine that you can use as an interface between the Device Manager and your high-level language driver routines. This example properly handles synchronous, asynchronous, and immediate requests, as well as the special cases of open, close, and KillIO.

Listing 1-8 An assembly-language dispatching routine

DOpen
      MOVEM.L  A0-A1,-(SP)    ;save ParmBlkPtr, DCtlPtr across function call
      MOVEM.L  A0-A1,-(SP)    ;push ParmBlkPtr, DCtlPtr for C
      BSR      MyDriverOpen   ;call linked C function
      ADDQ     #8,SP          ;clean up the stack
      MOVEM.L  (SP)+,A0-A1    ;restore ParmBlkPtr, DCtlPtr
      RTS                     ;open is always immediate, must return via RTS

DPrime
      MOVEM.L  A0-A1,-(SP)    ;save ParmBlkPtr, DCtlPtr across function call
      MOVEM.L  A0-A1,-(SP)    ;push ParmBlkPtr, DCtlPtr for C
      BSR      MyDriverPrime  ;call linked C function
      ADDQ     #8,SP          ;clean up the stack
      MOVEM.L  (SP)+,A0-A1    ;restore ParmBlkPtr, DCtlPtr
      BRA.B    IOReturn

DControl
      MOVEM.L  A0-A1,-(SP)    ;save ParmBlkPtr, DCtlPtr across function call
      MOVEM.L  A0-A1,-(SP)    ;push ParmBlkPtr, DCtlPtr for C
      BSR      MyDriverControl;call linked C function
      ADDQ     #8,SP          ;clean up the stack
      MOVEM.L  (SP)+,A0-A1    ;restore ParmBlkPtr, DCtlPtr
      CMPI.W   #killCode,csCode(A0) ;test for KillIO call (special case)
      BNE.B    IOReturn
      RTS                     ;KillIO must always return via RTS

DStatus
      MOVEM.L  A0-A1,-(SP)    ;save ParmBlkPtr, DCtlPtr across function call
      MOVEM.L  A0-A1,-(SP)    ;push ParmBlkPtr, DCtlPtr for C
      BSR      MyDriverStatus ;call linked C function
      ADDQ     #8,SP          ;clean up the stack
      MOVEM.L  (SP)+,A0-A1    ;restore ParmBlkPtr, DCtlPtr

IOReturn
      MOVE.W   ioTrap(A0),D1
      BTST     #noQueueBit,D1 ;immediate calls are not queued, and must RTS
      BEQ.B    @Queued        ;branch if queued

@NotQueued
      TST.W    D0             ;test asynchronous return result
      BLE.B    @ImmedRTS      ;result must be \xBE0
      CLR.W    D0             ;"in progress" result (> 0) not passed back

@ImmedRTS
      MOVE.W   D0,ioResult(A0)   ;for immediate calls you must explicitly 
                                 ; place the result in the ioResult field
      RTS

@Queued
      TST.W    D0             ;test asynchronous return result
      BLE.B    @MyIODone      ;I/O is complete if result \xBE 0
      CLR.W    D0             ;"in progress" result (> 0) not passed back
      RTS

@MyIODone
      MOVE.L   JIODone,-(SP)  ;push IODone jump vector onto stack
      RTS

DClose
      MOVEM.L  A0-A1,-(SP)    ;save ParmBlkPtr, DCtlPtr across function call
      MOVEM.L  A0-A1,-(SP)    ;push ParmBlkPtr, DCtlPtr for C
      BSR      MyDriverClose  ;call linked C function
      ADDQ     #8,SP          ;clean up the stack
      MOVEM.L  (SP)+,A0-A1    ;restore ParmBlkPtr, DCtlPtr
      RTS                     ;close is always immediate, must return via RTS
In this example, DOpen, DPrime, DControl, DStatus, and DClose are the five entry points that the Device Manager locates using the offsets defined in the driver header. These in turn call the actual driver routines, which are written in C. The C functions return a result code if the I/O completed, or a positive value (usually 1) if the I/O is being handled asynchronously.

When the driver routine returns, the dispatching routine removes the parameters from the stack, restores the A0 and A1 registers, and then returns control to the Device Manager in one of two ways:

To use this dispatching routine you would place it after the driver header in your assembly-language source file, and link it to your C-language driver routines. Listing 1-7 on page 1-28 shows the driver header. Sample driver routines are presented in the following sections.

Writing Open and Close Routines

You must provide both an open routine and a close routine for your device driver. The open routine should allocate any private storage your driver requires and place a handle to this storage in the dCtlStorage field of the device control entry. After allocating memory, the open routine should perform any other preparation required by your driver.

If your open routine installs an interrupt handler, you may want to store a pointer to the device control entry in private storage where it will be available for the interrupt handler. The section "Handling Asynchronous I/O" on page 1-37 discusses interrupt handling in more detail.

Listing 1-9 shows a sample open routine, MyDriverOpen. This function begins by checking whether the driver is already open (by examining the contents of the dCtlStorage field of the device control entry). If the driver is not already open, the MyDriverOpen function sets the appropriate flags in the device control entry and allocates memory in the system heap for private storage. The private storage of the driver in this example contains two fields, byteCount and lastErr, which store information about the last I/O function. The prime, control, and status routines described in the following sections use these fields.

If the MyDriverOpen function fails to allocate memory for private storage, it returns the openErr result code, which notifies the Device Manager that the driver did not open.

Listing 1-9 Example driver open routine

struct MyDriverGlobals {
   short    byteCount;
   short    lastErr;
};
typedef struct MyDriverGlobals MyDriverGlobals;
typedef struct MyDriverGlobals *MyDriverGlobalsPtr, **MyDriverGlobalsHdl;

OSErr MyDriverOpen(IOParamPtr pb, DCtlPtr dce)
{
   if (dce->dCtlStorage == nil) 
   {
      /* set up flags in the device control entry */
      dce->dCtlFlags |= (dCtlEnableMask | dStatEnableMask | dWritEnableMask |
                         dReadEnableMask | dNeedLockMask | dRAMBasedMask );
               
      /* initialize dCtlStorage */
      dce->dCtlStorage = NewHandleSysClear(sizeof(MyDriverGlobals));
      if (dce->dCtlStorage == nil)
         return(openErr);
      else
         return(noErr);
   }
   else
   {
      /* the driver is already open */
      return(noErr);
   }
}
The close routine must reverse the effects of the open routine by releasing any memory allocated by the driver, removing interrupt handlers, removing any VBL or Time Manager tasks, and replacing changed interrupt vectors. If the close routine cannot complete the close request, it should return the closErr result code and the driver should continue to operate normally.

The Device Manager does not dispose of the device control entry when a driver is closed. If you want to save any information about the operational state of the driver until the next time the driver is opened, you can store a handle to the information in the dCtlStorage field of the device control entry.

Listing 1-10 shows a sample close routine, MyDriverClose. Because this device
driver does not need to store any information until the next time it is opened, the MyDriverClose function disposes of the private storage allocated by MyDriverOpen.

Listing 1-10 Example driver close routine

OSErr MyDriverClose(IOParamPtr pb, DCtlPtr dce)
{
   if (dce->dCtlStorage != nil)
   {
      DisposeHandle(dce->dCtlStorage);
      dce->dCtlStorage = nil;
   }
   return(noErr);
}

Writing a Prime Routine

The prime routine implements I/O requests. You can write your prime routine to execute synchronously or asynchronously. While a synchronous prime routine completes an entire I/O request before returning to the Device Manager, an asynchronous prime routine can begin an I/O transaction but return to the Device Manager before the request is complete. In this case, the I/O request continues to be executed, typically when more data is available, by other routines such as interrupt handlers or completion routines. "Handling Asynchronous I/O" on page 1-37 discusses how to complete an asynchronous prime routine.

The Device Manager indicates whether it is requesting a read or a write operation by placing one of the following constants in the low-order byte of the ioTrap field of the parameter block:

enum {
   aRdCmd   = 2,  /* read operation requested */
   aWrCmd   = 3   /* write operation requested */
};
The Device Manager includes two routines, Fetch and Stash, that provide low-level support for reading and writing characters to and from data buffers. Use of these routines is optional. "Writing and Installing Device Drivers," beginning on page 1-82, describes these functions.

The Fetch and Stash routines update the ioActCount field of the parameter block. If you do not use these routines, you are responsible for updating this field.

If your driver serves a block device, you should update the dCtlPosition field of the device control entry.

Listing 1-11 shows a sample prime routine. This routine determines whether a read or write operation is being requested, then calls the appropriate function. The reading and writing functions, which are not shown here, would transfer the data to or from the hardware device.

Listing 1-11 Example driver prime routine

OSErr MyDriverPrime(IOParamPtr pb, DCtlPtr dce)
{
   MyDriverGlobalsHdl   dStore;
   short                callType;
   long                 numBytes;
   short                myErr;

   dStore = (MyDriverGlobalsHdl)dce->dCtlStorage;
   numBytes = pb->ioReqCount;
   callType = 0x00ff & pb->ioTrap; /* get the low byte */
   switch (callType)
   {
      case aRdCmd:
         myErr = MyReadBytes(pb->ioBuffer, numBytes);
         break;
      case aWrCmd:
         myErr = MyWriteBytes(pb->ioBuffer, numBytes);
         break;
   }
   (*dStore)->byteCount = numBytes; /* save in private storage */
   (*dStore)->lastErr = myErr;
   pb->ioActCount = numBytes; /* update parameter block field */
   return(myErr);
}
After obtaining a handle to the device driver's private storage from the dCtlStorage field of the device control entry, the MyDriverPrime function examines the low-order byte of the ioTrap field of the parameter block to determine whether the Device Manager is requesting a read operation or a write operation. MyDriverPrime then calls either the MyReadBytes or MyWriteBytes function to move the requested number of bytes to or from the buffer designated by the parameter block.

The MyDriverPrime function stores the result code and byte count in its private storage. These values will be used by the example control and status routines described in the next section. Finally, MyDriverPrime updates the ioActCount field of the parameter block and returns the result code.

Writing Control and Status Routines

Control and status routines are usually used to send and receive driver-specific information. However, you can use these routines for any kind of data transfer as long as you implement the minimum functionality described in this section. Like the prime routine, the control and status routines that you write can execute synchronously or asynchronously.

The Device Manager passes information to the control routine in the csCode and csParam fields of the parameter block. The csCode field specifies the type of control request and the csParam field contains any additional information. The csCode values
-32767 through 127 are reserved by Apple Computer, Inc. Within this range, the following constant values are defined for use by all device drivers:
Constant nameValueMeaning
killCode1KillIO requested
goodbye-1Heap being reinitialized
accRun65Time for periodic action

When the Device Manager receives a KillIO request, it removes every parameter block from the driver I/O queue. If your driver responds to any requests asynchronously, the part of your driver that completes asynchronous requests (for example, an interrupt handler) might expect the parameter block for the pending request to be at the head of the queue. The Device Manager notifies your driver of KillIO requests so that it can take the appropriate actions to stop work on the pending request. Your driver must return control to the Device Manager by means of an RTS instruction and not by jumping to the IODone routine.

If you set the dNeedGoodbye flag in the drvrFlags field of the driver header (or the dCtlFlags field of the device control entry), the Device Manager will call your control routine with the value goodbye in the csCode parameter before the heap is reinitialized. You driver can respond by performing any clean-up actions necessary before heap reinitialization.

If you set the dNeedTime flag in the drvrFlags field of the driver header (or the dCtlFlags field of the device control entry), the Event Manager will periodically call your control routine with the value accRun in the csCode parameter. Because these calls are immediate, your driver must be reentrant to handle them properly. For more information about the dNeedTime flag and periodic actions, see the description of the driver header, beginning on page 1-25.

Your control routine must return the controlErr result code for any csCode values that are not supported. You can define driver-specific csCode values if necessary, as long as they are outside the range reserved by Apple Computer, Inc.

Listing 1-12 shows a sample control routine, MyDriverControl. This function interprets the driver-specific csCode value of kClearAll as a command to clear the information saved in the driver's private storage by the MyDriverPrime routine.

Listing 1-12 Example driver control routine

OSErr MyDriverControl(CntrlParamPtr pb, DCtlPtr dce)
{
   MyDriverGlobalsHdl   dStore;

   dStore = (MyDriverGlobalsHdl)dce->dCtlStorage;
   switch (pb->csCode)
   {
      case kClearAll:
         (*dStore)->byteCount = 0;
         (*dStore)->lastErr = 0;
         return(noErr);
      default: /* always return controlErr for unknown csCode */
         return(controlErr);
   }
}
Your status routine should work in a similar manner. The Device Manager uses the csCode field to specify the type of status information requested. The status routine should respond to whatever requests are appropriate for your driver and return the error code statusErr for any unsupported csCode value.

The Device Manager interprets a status request with a csCode value of 1 as a special case. When the Device Manager receives such a status request, it returns a handle to the driver's device control entry. Your driver's status routine never sees this request.

Listing 1-13 shows a sample status routine, MyDriverStatus, that implements two driver-specific status requests, kByteCount and kLastErr. When MyDriverStatus receives one of these requests, it returns the byte count or error code values saved in private storage by the MyDriverPrime routine. MyDriverStatus returns this information in the csParam field.

Listing 1-13 Example driver status routine

OSErr MyDriverStatus(CntrlParamPtr pb, DCtlPtr dce)
{
   MyDriverGlobalsHdl   dStore;

   dStore = (MyDriverGlobalsHdl)dce->dCtlStorage;
   switch (pb->csCode)
   {
      case kByteCount:
         pb->csParam[0] = (*dStore)->byteCount;
         return(noErr);
      case kLastErr:
         pb->csParam[0] = (*dStore)->lastErr;
         return(noErr);
      default: /* always return statusErr for unknown csCode */
         return(statusErr);
   }
}

Handling Asynchronous I/O

If you design any of your driver routines to execute asynchronously, you must provide a mechanism for your driver to complete the requests. Some examples of routines that you might use are:

If your driver serves a device on a NuBus\x81 expansion card, you might want to use slot interrupts to signal your driver. When a NuBus card device signals a slot interrupt, the CPU can quickly detect which card requested the interrupt service, but not which device on the card. To determine which device caused the interrupt, the system uses a polling procedure. Your driver should provide a polling routine that checks if the device it serves caused the current interrupt, and if so, calls the proper driver routine to handle the interrupt. The Slot Manager maintains a queue of these polling routines for each slot. Your driver can install an element in this queue using the Slot Manager function SIntInstall. You can remove a queue element with the SIntRemove function. See the chapter "Slot Manager" in this book for information about these functions.

You should observe these guidelines when writing or using asynchronous routines:

Installing a Device Driver

There are a variety of ways to install a device driver, depending on where the driver code is stored and how much control you want over the installation process.

If you store your driver in a resource of type 'DRVR' you can use the OpenDriver or PBOpen functions to install and open your driver. If you need more control over the installation process, you can use the DriverInstall function to create the device control entry and add it to the unit table, or you can create the device control entry yourself, install it in the unit table, and then use OpenDriver or PBOpen to open the driver. If the driver is already installed in the unit table, OpenDriver and PBOpen simply call the driver's open routine and return the driver reference number.

If you want to use the OpenDriver function to install your driver, you are responsible for examining the unit table and changing your driver resource ID so that the OpenDriver function installs your driver in an empty location in the unit table. If the handle at a given unit number is nil, there is no device control entry installed in that position. You can install your device control entry in any empty location in the unit table that is not listed as reserved by Apple Computer, Inc. Table 1-2 summarizes the unit numbers reserved for specific purposes.
Table 1-2 Reserved unit numbers
Unit number rangeReference number rangePurpose
0 through 11-1 through -12Reserved for serial, disk, AppleTalk, printer, and other drivers
12 through 31-13 through -32Available for desk accessories
32 through 38-33 through -39Available for SCSI devices
39 through 47-40 through -48Reserved
48 through 127-49 through -128Available for slot and other drivers

Listing 1-14 shows a method of searching the unit table for an appropriate location to install your driver. The MyOpenDriver function in Listing 1-1 on page 1-18 calls this function and then uses the OpenDriver function to install and open the device driver.

Listing 1-14 Finding space in the unit table

short MyFindSpaceInUnitTable(void);
{
   Ptr      curUTableBase, newUTableBase;
   short    curUTableEntries, newUTableEntries;
   short    refNum, unitNum;
   
   /* get current unit table values from low memory globals */
   curUTableEntries = *(short*)UnitNtryCnt;
   curUTableBase = *(Ptr*)UTableBase;
   
   /* search for empty space in the current unit table */
   for ( unitNum = curUTableEntries - 1; 
         unitNum >= 48; /* lowest available unit number */
         unitNum-- )
   {
      refNum = ~(unitNum);
      if (GetDCtlEntry(refNum) == nil)
         return(unitNum); /* found a space */
   }
   
   /* no space in the current table, so make a new one */
   
   /* increase the size of the table by 16 (an arbitrary value) */
   newUTableEntries = curUTableEntries + 16;
   
   /* allocate space for the new table */
   newUTableBase = 
      NewPtrSysClear((long)newUTableEntries * sizeof(Handle));
   if (newUTableBase == nil)
      return(memErr);
   
   /* copy the old table to the new table */
   BlockMove(curUTableBase, newUTableBase, 
             (long)curUTableEntries * sizeof(Handle));
   
   /* set the new unit table values in low memory */
   *(Ptr*)UTableBase = newUTableBase;
   *(short*)UnitNtryCnt = newUTableEntries;
   
   unitNum = newUTableEntries - 1;
   return(unitNum); 
}
Although rare, it is possible for the unit table to become completely full. If the MyFindSpaceInUnitTable function does not find an empty unit table entry, it creates a larger unit table and copies the contents of the old unit table into the new one. To avoid the need for every driver to create a larger table, this function increases the size of the table by 16 entries--a reasonable amount in most cases.

The MyFindSpaceInUnitTable function does not need to disable interrupts when changing the values of the UTableBase and UnitNtryCnt system global variables because both unit tables are valid and drivers are not opened or closed at interrupt time.

Note that this function does not check for empty locations in the space reserved for desk accessories or SCSI drivers. You may wish to modify the function if you are installing one of these.


Subtopics
Creating a Driver Resource
Responding to the Device Manager
Entering and Exiting From Driver Routines
Writing Open and Close Routines
Writing a Prime Routine
Writing Control and Status Routines
Handling Asynchronous I/O
Installing a Device Driver

Previous Book Contents Book Index Next

© Apple Computer, Inc.
3 JUL 1996