Advanced Search
Apple Developer Connection
Member Login Log In | Not a Member? Contact ADC

Technical Note TN1192
ATA Interface Modules

CONTENTS

This Technote describes how to write a device driver for an ATA host bus controller, known as an ATA Interface Module, or AIM. An AIM is the ATA equivalent of the SCSI Interface Module (SIM). It does not control a device on the ATA bus, but implements a standard hardware abstraction for the bus itself.

AIMs operate at the very lowest level of the traditional Mac OS I/O subsystem, which makes them hard to write and hard to debug. Only experienced Mac OS device driver writers should consider developing an AIM.

This Note is directed at developers of ATA host bus controller cards (typically PCI or CardBus).

 Updated: [Dec 06 1999]






Introduction

This technote describes how to write a Mac OS device driver for ATA host bus controller hardware. Such a driver is known as an ATA Interface Module, or AIM. In some respects it is the ATA equivalent to the more commonly known SCSI Interface Module (SIM).

This technote is divided into two sections. This section, and the Core Concepts sections which follows it, represent the introductory material. They describe high-level issues with writing AIMs; understanding these sections is critical to writing a reliable AIM. The remaining sections are reference material. They describe how you can register an ATA bus with ATA Manager, how you should package your AIM so that it is recognized by ATA Manager, what entry points ATA Manager expects you AIM to implement, the list of function codes your AIM's action routine must support, and some support routines that ATA Manager exports to support AIM implementation.

IMPORTANT:
AIMs are only supported by ATA Manager 4.0 and above. Development of third-party ATA buses is not supported on older versions of ATA Manager. Device 0/1 support is not available on some early ATA Manager 4.0 systems; however, your AIM should always support device 0/1 operations.


Before You Begin

Before you consider developing an AIM, you should be familiar with the following concepts:

You will also need the following:

  • "ATA.h" from the latest version of Universal Headers.
  • If that version of "ATA.h" does not include the AIM-related declarations described in this technote, you will need the "ATAExtras.h" header file that is included with this technote. [2404935]
  • Similarly, if the libraries included with Universal Headers does not include ATAManager stub library, you will need the one included here.
  • Information about your bus controller from your hardware vendor.

Back to top

Core Concepts

Before starting your AIM, you should be familiar with some fundamental concepts, as described in the following sections.

Theory of Operation

AIMs represent an abstraction of an ATA bus. Your AIM isolates the ATA Manager and its clients from the specific details of your ATA bus controller hardware. For instance, an ATA bus may be part of a PC Card slot, or perhaps reside on a PCI card, or may even be integrated as part of a system chipset. Each physical ATA controller will have specific requirements for how it is addressed, what cycle times are supported and how they are programmed, how interrupts from the controller are routed to the system, and so on. By bundling the hardware-specific details of the ATA controller in a plug-in software module, the ATA Manager presents a consistent interface for drivers and applications to communicate with ATA and ATAPI devices.

All of the operations which directly touch the hardware in any manner are handled by your AIM. This includes:

  • describing the features of the bus controller, primarily the supported I/O modes and cycle times, and controlling those features
  • preparing physical DMA transfer buffers and building DMA programs for those buffers
  • alignment issues
  • reading and writing the device's task file registers
  • writing ATAPI command packets
  • handling ATA and ATAPI transfer operations
  • servicing hardware interrupts
  • timing out failed bus transactions
  • resetting the bus

Current versions of ATA Manager will only dispatch a single request for a single device on the bus controlled by your AIM at any given time. Overlapped ATA/ATAPI features are not currently supported. AIMs written to the specification in this document will never be expected to handle concurrent requests.

In general, your AIM should be interrupt-driven. It should do as much work as it can on a request, then return to the ATA Manager pending an interrupt from the hardware. For instance, when you receive a request to read data, you would set your AIM's internal flags as needed, write the task file to the device, and then return to ATA Manager. When the device is ready to transfer data, it will assert an interrupt which will call your AIM's hardware interrupt handler. Your AIM would then call ATAFamBusEventForAIM to queue a secondary handler and return from the hardware interrupt handler. ATA Manager will then call your AIM's MyHandleBusEvent function where you would complete the data transfer, clear your AIM's internal flags, and call ATAFamIODone. It is important that all your cleanup be done before calling ATAFamIODone, because ATA Manager may call you with another request during ATAFamIODone. Also, your AIM's MyHandleBusEvent function may be called immediately when calling ATAFamBusEventForAIM. Other than these two cases, the AIM will not be called reentrantly.

There are some limits on the controller hardware that can be supported by an AIM:

  • Each ATA bus must be capable of simultaneous operation, independent of any other ATA bus. If an ATA controller chip implements two bus controllers, but they share common wiring or a DMA engine such that only one bus can be active at a time, then only one bus may be supported under ATA Manager. ATA Manager has no synchronization mechanism between buses.
  • ATA controllers incapable of generating interrupts are not supported.
  • PCI ATA controllers which operate only in x86 "legacy mode" (that is, they hard-decode only the lower-address bits and cannot be relocated in PCI space) are not supported. However, it is possible to support addressing the controller in either memory or PCI I/O space.

Global Variables

Your AIM should allocate its per-bus global variables in a memory block (typically allocated use PoolAllocateResident) and use ATA Manager's per-AIM refCon facility to track its globals.

The reason to use the refCon (rather than C extern and static variables, which are stored in your fragment's data section) is that the ATA Manager has a facility to update your AIM to a newer version "on the fly." The process, described below, involves closing the CFM connection to the older version of your AIM and replacing it with a connection to the newer version. If you had important data in the older AIM's data section, that data is lost when the data section is disposed as part of closing the CFM connection. In contrast, ATA Manager explicitly passes the refCon to the new version of your AIM.

Note:
An AIM is a native driver ('ndrv') and shares many of the properties of native drivers in general. One of these properties is that your AIM is instantiated once for each instance of your ATA hardware on the machine (technically, one per ATA node in the Name Registry). Therefore you can store per-bus global variables in your data section, and you will automatically get one copy of these global variables per bus.


However, you should avoid using this strategy for per-bus global variables for the reason described above.

It is acceptable to use your data section for global variables as long as you have a strategy for passing them between versions. You could, for example, store the data primarily in you data section, push it into the AIM's refCon memory block when your AIM is suspended, and restore it to your data section when your AIM is resumed. Or you might choose to pass data between versions of your AIM via Name Registry properties. However, both of these techniques are significantly more convoluted than using the AIM's refCon, hence the recommendation to just use the refCon approach.

Regardless of how you allocate your global variables, you must ensure that they are held resident in memory (in the virtual memory sense). All of the techniques described above guarantee this.

Finally, your AIM should use PowerPC structure alignment for its global variables. This has two advantages:

  1. If you do atomic operations on a global variable, PowerPC alignment ensures that the variable is aligned appropriate.
  2. PowerPC alignment yields better performance for native code than 68K alignment.

AIM Update Process

As mentioned above, the ATA Manager has a process for updating your AIM to a newer version "on the fly," without shutting the bus down. The ATA Manager accomplishes this in a 7 step process:

  1. Open a CFM connection to the new AIM
  2. Wait for all pending AIM requests to terminate
  3. Block any new AIM requests from starting
  4. Call the old AIM's suspend routine (MyAIMSuspend)
  5. Call the new AIM's resume routine (MyAIMResume), passing it the refCon from the old AIM
  6. Unblock the AIM to allow bus operations to continue
  7. Close the CFM connection to the old AIM

There are a number of important things about this process to keep in mind.

  • The ATA Manager finds replacement AIMs by matching the Name Registry node name to the nameInfoStr field of the DriverType structure of its driver description. In order for your AIM to be updated, these names much match exactly.
  • In addition, the ATA Manager will only update an AIM if the version field of the DriverType structure of its driver description is newer than the version of the existing AIM.
  • You must structure your per-bus globals such that they can be passed from one version of your AIM to the next. Some suggested techniques are:
  • Using your AIM's refCon to store a pointer to your per-bus storage simplifies the process of passing this information between versions. See the previous section for more details.
  • Put the version number of your AIM in your per-bus storage so that the newer AIM knows which older version it is taking over from (and can compensate for known bugs, oversights, and so on).
  • Include some "reserved" fields in your per-bus storage to give future versions of your AIM some room for expansion.
  • You can also use Name Registry properties to pass information between versions of your AIM.
  • AIM resume routines are not defined to return an error code. You should design your AIM so that its resume routine cannot fail.

AIM Synchronization Model

As long as you follow one simple rule, the ATA Manager takes care of most of the synchronization problems in writing an AIM. The rule is:

Note:
When your AIM is called by anyone other than ATA Manager, you must synchronize with ATA Manager by posting a bus event.


Posting a bus event (ATAFamBusEventForAIM) is a the way your AIM informs ATA Manager that its bus event handler (MyAIMHandleBusEvent) should be called. ATA Manager queues the bus event and eventually calls the bus event handler when it is safe to do so.

If you follow this rule, ATA Manager guarantees that it will never call your AIM on more than one thread of execution at a time. So as long as you're executing within the context of a routine that is called by ATA Manager, you can access global variables without worrying about synchronization issues.

An obvious example of where this is useful is for handling interrupts. As a rule, your AIM will service I/O requests as a state machine. When ATA Manager calls your AIM to start a request (MyAIMAction), your AIM will start an asynchronous I/O operation and then return to ATA Manager. When the asynchronous operation completes, your hardware will interrupt the processor, and system software will execute your interrupt service routine. This interrupt service routine executes at hardware interrupt time. If you access your global variables from it, you must worry about synchronizing that access with lower execution levels. The solution is for your AIM to post a bus event. ATA Manager will defer calling your bus event handler (MyAIMHandleBusEvent) until it can guarantee that it is the only thread of execution running inside your AIM. Your bus event handler can therefore safely access global variables without worrying about synchronization.

IMPORTANT:
Your interrupt service routine must perform the following tasks as quickly as possible:


  • Identify the source of the interrupt.
  • If the interrupt is from your device, clear the source of the interrupt and post a bus event.

The bulk of the work in handling an interrupt should be done in your bus event handler.

If your AIM's hardware is hosted on a PCI bus, you must be sure to handle hardware interrupts in an expansion chassis friendly fashion. See DTS Technote 1135 Dealing with PCI Expansion Chassis Problems for details.

Controllers which contain two ATA buses which share a single PCI interrupt must extend the interrupt source tree so that each ATA bus has a separate interrupt node. Typically this is done in your AIM's initialization routine.

A less obvious example of the use of bus events is to handle timeouts. If your AIM implements timeouts using the system timer service (SetInterruptTimer), the timer routine will be executed at hardware interrupt time. Your AIM can avoid synchronization problems by posting this event as a bus event as well.

Note:
ATA Manager uses the bus event mechanism to both guarantee synchronization and to defer the processing of bus events until interrupts are enabled (by way of a secondary interrupt). You should not rely on ATA Manager's use of secondary interrupts. Older versions of ATA Manager implemented this using a deferred task, and the implementation might change again in the future.


In addition to guaranteeing that only one thread of execution can be running inside your AIM at any point in time, the ATA Manager also guarantees to dispatch only a single request to your AIM at a time. Between the point when ATA Manager dispatches a request (by calling MyAIMAction) and the point when your AIM completes it (by calling ATAFamIODone), ATA Manager will not dispatch any further requests to your AIM. Instead, it will queue these requests on its internal queues. This "one request at a time" guarantee is in recognition of the fact that the ATA bus architecture does not support parallel overlapped requests and that, by guaranteeing this, ATA Manager simplifies your life.

Both of the synchronization guarantees described in this section are defined on a bus-by-bus basis. If your AIM is multiply instantiated on the system, and those instances share common data, you must be careful to synchronize access to that common data.

Finally, ATA Manager also handles enabling and disabling of user code (in the virtual memory sense) for you. Readers who are familiar with SIMs (the equivalent to AIMs for SCSI buses) know that they are required to call EnteringSIM and ExitingSIM whenever they enter or leave the SIM. AIM developers are not required to jump through that particular hoop because the ATA Manager knows when non-reentrant portions of the AIM are executing (because the non-reentrant parts of the AIM are always executed as a result of the ATA Manager calling the AIM) and so it can enable and disable user code appropriately.

ATA I/O Modes

This section explains one of the trickier aspects of the ATA Manager's API for setting transfer modes and timings. In addition to the discussion here, you should read the ATA Device Software for Macintosh Computers (and its errata, DTS Technote 1098 ATA Device Software Guide Additions and Corrections) carefully to fully understand how ATA client software expects your AIM to handle transfer modes and timings.

Prior to ATA Manager 3.0, which was the first version of the ATA Manager to support DMA transfers, transfer modes were specified as absolute numbers. Thus, a value of 2 for a transfer mode meant PIO mode 2. Starting with ATA Manager 3.0, transfer modes were specified as bitmaps. Thus a value of 1 meant transfer mode 0, a value of 2 meant transfer mode 1, and so on. The ataModeType field of the ATAReqBlock determines which mode this request is in.

This is important to remember when trying to establish the correct timing mode on the ATA bus. For example, the flag bit mATAFlagUseConfigSpeed has different meanings depending on the value of the ataModeType field. If the ataModeType field is set to kATAModeAbsolute (pre-ATA Manager 3.0) then the flag bit mATAFlagUseConfigSpeed indicates whether to use timing values for the mode last set with a Set Driver Configuration request, or to use timing values for the PIO mode specified in the field ataPBIOSpeed. If the ataModeType field is set to kATAModeBitmap (ATA Manager 3.0 and above) then the flag bit mATAFlagUseConfigSpeed should always be set. If so, your AIM must use the bus mode (PIO, singleword DMA, multiword DMA, UltraDMA) and timing values that were stored from the last Set Driver Configuration request. If mATAFlagUseConfigSpeed is not set, your AIM should execute the request at the slowest possible transfer speed.

AIMs versus SIMs

In many respects, AIMs are architecturally similar to SCSI Interface Modules (SIMs), the name given to host bus controller drivers in the Mas OS SCSI architecture. The following table compares various features of AIMs and SIMs.


Feature AIM SIM
Single Thread Yes No
Single Request Yes No
Interrupt Polling Yes Yes
Enable/Disable User Code No Yes
Per-Bus Storage Maintained by ATA Manager Maintained by SCSI Manager
Per-Request Storage Single request, therefore put per-request data in per-bus globals Allocated by client as part of SCSI parameter block
Update "on the fly" Yes Not provided by SCSI Manager
'ndrv' Required Possible, but not required

Back to top

ATA Manager Additions

ATA Manager 4.0 defines two new ATA Manager function codes to be used with the ataManager system call. The new functions are defined below. The codes are used to add and remove ATA buses, respectively.



enum {
    kATAMgrAddATABus            = 0x93,
    kATAMgrRemoveATABus         = 0x94
};


IMPORTANT:
If your AIM is loaded from an expansion ROM on a card, you do not need to register it manually with ATA Manager. At startup time, ATA Manager will search the Name Registry for ATA nodes and automatically register an ATA bus for any node with an available AIM. ATA Manager considers any node whose "device_type" property is kATADeviceType ("ata\15\0") to be an ATA node.


For compatibility reasons, ATA Manager also recognizes nodes of type "ide\0". New AIMs should use always kATADeviceType.

Adding an ATA Bus

To add an ATA bus to ATA Manager, you must call the ataManager system call, passing in a parameter block of type ataAddATABus.



struct ataAddATABus {
    ataPBHeader *                   ataPBLink;
    UInt16                          ataPBQType;
    UInt8                           ataPBVers;
    UInt8                           ataPBReserved;
    Ptr                             ataPBReserved2;
    ProcPtr                         ataPBCallbackPtr;
    OSErr                           ataPBResult;
    UInt8                           ataPBFunctionCode;
    UInt8                           ataPBIOSpeed;
    UInt16                          ataPBFlags;
    SInt16                          ataPBReserved3;
    UInt32                          ataPBDeviceID;
    UInt32                          ataPBTimeOut;
    Ptr                             ataPBClientPtr1;
    Ptr                             ataPBClientPtr2;
    UInt16                          ataPBState;
    UInt16                          ataPBSemaphores;
    SInt32                          ataPBReserved4;
    RegEntryIDPtr                   ataNameRegEntry;
    CFragConnectionID               connID;
    UInt32                          busID;
    UInt8                           flags;
    UInt8                           socketType;
    Ptr                             iconData;
    Ptr                             stringData;
};
typedef struct ataAddATABus ataAddATABus;


The fields have the following meaning:

ataPBLink
ataPBQType
ataPBVers
ataPBReserved
ataPBReserved2
ataPBCallbackPtr
ataPBResult
ataPBFunctionCode
ataPBIOSpeed
ataPBFlags
ataPBReserved3
ataPBDeviceID
ataPBTimeOut
ataPBClientPtr1
ataPBClientPtr2
ataPBState
ataPBSemaphores
ataPBReserved4
Standard ATA Manager parameter block header. See ATA Device Software for Macintosh Computers for details. You must initialize ataPBFunctionCode to kATAMgrAddATABus, ataPBVers to kATAPBVers2.
ataNameRegEntry
You must set this to the Name Registry node of the ATA bus which you wish to add. The ATA Manager will use Driver Loader Library to locate your native driver (AIM) for this bus. See AIM Packaging for more information about how your native driver must be structured.
connID
Reserved. You must set this field to zero and ignore any value returned.
busID
On successful completion of the request, the ATA Manager sets this field to the ATA bus ID of the newly created ATA bus.
flags
You must set this to the bus flags for this bus. The possible flags are defined below. The undefined flag bits are reserved; you must set them to zero.
socketType
You must set this to the ATA socket type of the bus using one of the constants defined in "ATA.h" (currently one of kATASocketInternal, kATASocketMB, or kATASocketPCMCIA).
iconData
You must set this to either a pointer to a black and white icon (256 bytes of data in 'ICN#' format) that represents the ATA bus, or to nil if there is no such icon. ATA Manager makes a copy of the data, so you can dispose of it when the call completes.
stringData
You must set this to either a pointer to a string that describes the location of the ATA bus, or to nil if you do not wish to supply a location. The string is a C string (zero terminated) of at most 31 characters in the system script encoding; longer strings will be truncated by ATA Manager. ATA Manager makes a copy of the data, so you can dispose of it when the call completes.

In response to this request, the ATA Manager opens your AIM and creates the internal data structures necessary for it to track the AIM and its attached devices. As part of processing this call, the ATA Manager calls your AIM's initialization routine (MyAIMInit), which must probe your bus for attached devices. If your AIM's initialization routine returns an error, the ATA Manager cleans up and completes the request with that error. If your AIM's initialization routine succeeds and indicates that devices are attached to the bus, ATA Manager will issue kATAUpdateEvent and kATAOnlineEvent events for each device.

The possible flags for the flags field of the ataAddATABus structure are defined below.



enum {
    mATANoDMAOnBus              = 0x80
};


The meaning of these flags is:

mATANoDMAOnBus
If this flag is set, the bus the ATA Manager's kATAMgrBusInquiry function will indicate that the bus does not support DMA, even if your AIM indicates that it does. This allows the bus expert which registers this bus to override AIM defaults for DMA support. The actual effect of this flag is that the ATA Manager clears the ataSingleDMAModes, ataMultiDMAModes and ataUltraDMAModes fields returned by your AIM in response to a kATAFnBusInquiry request before returning those fields as part of the client's kATAMgrBusInquiry request.

You must issue this request at system task time.

Removing an ATA Bus

To remove an ATA bus, you must call the ataManager system call, passing in a parameter block of type ataAddATABus.



struct ataRemoveATABus {
    ataPBHeader *                   ataPBLink;
    UInt16                          ataPBQType;
    UInt8                           ataPBVers;
    UInt8                           ataPBReserved;
    Ptr                             ataPBReserved2;
    ProcPtr                         ataPBCallbackPtr;
    OSErr                           ataPBResult;
    UInt8                           ataPBFunctionCode;
    UInt8                           ataPBIOSpeed;
    UInt16                          ataPBFlags;
    SInt16                          ataPBReserved3;
    UInt32                          ataPBDeviceID;
    UInt32                          ataPBTimeOut;
    Ptr                             ataPBClientPtr1;
    Ptr                             ataPBClientPtr2;
    UInt16                          ataPBState;
    UInt16                          ataPBSemaphores;
    SInt32                          ataPBReserved4;
    UInt32                          busID;
    RegEntryIDPtr                   ataNameRegEntry;
};
typedef struct ataRemoveATABus ataRemoveATABus;


The fields have the following meaning:

ataPBLink
ataPBQType
ataPBVers
ataPBReserved
ataPBReserved2
ataPBCallbackPtr
ataPBResult
ataPBFunctionCode
ataPBIOSpeed
ataPBFlags
ataPBReserved3
ataPBDeviceID
ataPBTimeOut
ataPBClientPtr1
ataPBClientPtr2
ataPBState
ataPBSemaphores
ataPBReserved4
Standard ATA Manager parameter block header. See ATA Device Software for Macintosh Computers for details. You must initialize ataPBFunctionCode to kATAMgrRemoveATABus, ataPBVers to kATAPBVers2.
busID
Reserved. You must set this field to zero and ignore any value returned.
ataNameRegEntry
You must set this to the Name Registry node of the ATA bus which you wish to add.

In response to this request the ATA Manager will remove the ATA bus associated with the Name Registry node specified in ataNameRegEntry. The ATA Manager executes the following steps:

  1. It calls your AIM's action routine (MyAIMAction) with the kATAFnKillIO function code, to indicate that your AIM should stop processing the current request.
  2. It completes any pending I/O requests (including the current one) for your AIM with the nsDrvErr error code.
  3. It calls your AIM's device light routine (MyAIMDeviceLight) to turn off the device's light.
  4. It issues the kATARemovedEvent event for each device on your bus.
  5. It calls your AIM's close routine (MyAIMClose), ignoring any error result.
  6. It disposes of the resources it used to track the bus and releases its CFM connection to your AIM.

You may issue this request at any execution level, although if you issue it at anything other than system task level the ATA Manager's connection to the AIM is not closed until a future system task time.

Back to top

AIM Packaging

An AIM is packaged as a native driver ('ndrv'). For ATA Manager to load your AIM, it must be available to the Driver Loader Library. See Designing PCI Cards and Drivers for Power Macintosh Computers for more information on how Driver Loader Library finds native drivers.

Your AIM must export two named entry global variables:

  • TheDriverDescription -- This is the standard native driver description.
  • ThePluginDispatchTable -- This contains information specific to ATA Manager.

The structure of these global variables is described in the following sections.

TheDriverDescription

An AIM must export the standard native driver description structure under the name "TheDriverDescription". An example of how your AIM should fill out this structure is given below.



01 extern DriverDescription TheDriverDescription = {
02
03     // Signature
04
05     kTheDescriptionSignature,
06     kInitialDriverDescriptor,
07
08     // Driver Type
09
10     {
11         "\pMyAIMName",
12         kmajorRev, kminorAndBugRev, kstage, knonRelRev,
13     },
14
15     // OS Runtime Requirements of Driver
16
17     {
18         kDriverIsUnderExpertControl,
19         "\pMyAIMName",
20     },
21
22     // Service Category List
23
24     // Only one service category required
25
26     1,
27
28     // First Service Category
29
30     kServiceCategoryATA,
31     0,
32     1, 0, 0, 0
33 };


Some parts of this definition deserve further explanation.

Line 11
You must set the nameInfoStr field of the DriverType structure to the name of the Name Registry node your driver controls. This allows the Driver Loader Library, and hence the ATA Manager, to associate your driver with the appropriate hardware.
Line 12
You must set the version field of the DriverType structure the version number of your driver. This allows the Driver Loader Library to load the latest version of your driver if more than one happens to be installed. It also allows the ATA Manager to replace an initial ROM-based AIM with a newer file-based AIM once the File Manager is available.
Line 18
AIMs are explicitly loaded by the ATA Manager (the expert), so you must set the driverRuntime flags of the DriverOSRuntime structure to kDriverIsUnderExpertControl.
Line 19
The driverName field of the DriverOSRuntime structure is typically used to hold the unit table name of the driver. As AIMs are not installed in unit table, this field is not significant. You should set to the same string you used in Line 11.
Lines 26 through 32
AIMs are required to export at least one service category, namely kServiceCategoryATA. This allows the ATA Manager to check that it correctly matched the AIM to the Name Registry node. The other fields of the DriverServiceInfo structure are reserved for future versions of the ATA Manager; you must initialize them as shown.

ThePluginDispatchTable

In addition to the standard drive description, you AIM must export a plugin dispatch table under the name "ThePluginDispatchTable". The ATAPluginDispatchTable type describes the format of this table.



struct ATAPluginDispatchTable {
    ATAPluginHeader                 header;
    ATAPluginInit                   init;
    ATAPluginClose                  close;
    ATAPluginAction                 action;
    ATAPluginHandleBusEvent         busEvent;
    ATAPluginPoll                   poll;
    ATAPluginEjectDevice            eject;
    ATAPluginDeviceLight            light;
    ATAPluginDeviceLock             lock;
    ATAPluginSuspend                suspend;
    ATAPluginResume                 resume;
};
typedef struct ATAPluginDispatchTable ATAPluginDispatchTable;
 
typedef OSStatus ATAPluginInit(ATAInitInfo *pb);
typedef OSStatus ATAPluginClose(UInt32 refCon, RegEntryIDPtr aimRegEntry);
typedef void ATAPluginAction(UInt32 refCon, ATAReqBlock *pb);
typedef void ATAPluginHandleBusEvent(UInt32 refCon, UInt32 aimData);
typedef Boolean ATAPluginPoll(UInt32 refCon, UInt32 interruptLevel, UInt32 *aimData);
typedef void ATAPluginEjectDevice(UInt32 refCon);
typedef void ATAPluginDeviceLight(UInt32 refCon, UInt32 whichDevice, UInt32 lightState);
typedef void ATAPluginDeviceLock(UInt32 refCon, UInt32 whichDevice, UInt32 lockState);
typedef void ATAPluginSuspend(UInt32 refCon);
typedef void ATAPluginResume(UInt32 refCon);


The fields have the following meaning:

header
A header which describes the version of the overall plugin dispatch table. See below.
init
You must set this to a pointer to your AIM's initialization routine (MyAIMInit).
close
You must set this to a pointer to your AIM's close routine (MyAIMClose).
action
You must set this to a pointer to your AIM's action routine (MyAIMAction).
busEvent
You must set this to a pointer to your AIM's bus event handler routine (MyAIMHandleBusEvent).
poll
You must set this to a pointer to your AIM's interrupt poll routine (MyAIMPoll).
eject
You must set this to a pointer to your AIM's eject routine (MyAIMEjectDevice) or nil if your AIM does not have an eject routine.
light
opt You must set this to a pointer to your AIM's device light routine (MyAIMDeviceLight) or nil if your AIM does not have an eject routine.
lock
You must set this to a pointer to your AIM's device lock routine (MyAIMDeviceLock) or nil if your AIM does not have an eject routine.
suspend
You must set this to a pointer to your AIM's suspend routine (MyAIMSuspend).
resume
You must set this to a pointer to your AIM's resume routine (MyAIMResume).

The ATAPluginHeader, as defined below, structure describes the version of the overall plugin dispatch table.



struct ATAPluginHeader {
    NumVersion                      headerVersion;
    NumVersion                      dispatchVersion;
    UInt32                          reservedA;
    UInt32                          reservedB;
};
typedef struct ATAPluginHeader ATAPluginHeader;
 
enum {
    kATAPluginVersion           = 0x00000001,
    kATAPluginCurrentVersion    = kATAPluginVersion
};


The fields have the following meaning:

headerVersion
You must set this to the version number of your AIM, as defined above.
dispatchVersion
If your AIM conforms to this version of the specification, you must set this field to kATAPluginVersion.
reservedA
Reserved. You must set this to zero.
reservedB
Reserved. You must set this to zero.

Note:
ATA Manager does not currently look at the dispatchVersion field. Moreover, Apple's AIMs do not currently set this field correctly. Because of this confusion, any future version of ATA Manager that implements an extended plug-in interface will not use this field to determine which version of the plug-in interface that your AIM conforms to.


Back to top

AIM Entry Points

Your AIM must implement a number of routines and export those routines to the ATA Manager via the plugin dispatch table. This section describes these routines in detail.

MyAIMInit



extern OSStatus MyAIMInit(ATAInitInfo *aimInit);


aimInit

A pointer to an ATAInitInfo parameter block, described below.

result An error code; see below for details.

The ATA Manager calls your AIM's initialization routine to commence operations on the ATA bus controlled by your AIM. This routine is called when an ATA bus is registered with ATA Manager. Your AIM must allocate any private resources it needs (typically per-bus storage), install its interrupt handler, initialize its hardware, probe the bus for attached devices (and determine whether they are ATA or ATAPI), and return information about those devices to the ATA Manager. Depending on your hardware, your AIM may need to reset the ATA bus to determine if any devices are attached.

Your AIM must also create child Name Registry nodes for the attached ATA devices. The nodes must have at least the following properties:

  • The "name" property must be set to either "ata-disk" or "atapi-disk".
  • The "device_type" property must be set to "block".
  • The "device_id" property must be set to a UInt32 that contains the ATA bus ID and device ID (in the same format as the ataPBDeviceID field of ATA Manager parameter blocks).

Your AIM initialization routine must not issue any commands to the ATA device.

Your AIM's initialization routine receives the address of an ATAInitInfo parameter block as a parameter. The parameter block contains both input and output fields.



struct ATAInitInfo {
    UInt32                          busID;
    ATADevInfo                      FirstDevice;
    ATADevInfo                      SecondDevice;
    RegEntryIDPtr                   aimRegEntry;
    UInt32                          refCon;
};
typedef struct ATAInitInfo ATAInitInfo;


The fields have the following meaning:

busID
ATA Manager sets this to the ATA bus ID it has assigned to the new bus that is being registered.
FirstDevice
Your AIM must initialize this structure to hold information about the first device on the ATA bus. See below for a description of the ATADevInfo structure. If the bus supports ATA devices 0 and 1, and device 1 is present but device 0 isn't, your AIM should set this structure to represent device 1 and set SecondDevice to indicate that no second device is attached.
SecondDevice
Your AIM must initialize this structure to hold information about the second device on the ATA bus.
aimRegEntry
The ATA Manager sets this to the Name Registry node of the ATA bus which is being registered.
refCon
Your AIM may set this field to any 32-bit value, typically a pointer to your per-bus storage. The ATA Manager will pass this value as a parameter (typically named refCon) whenever it calls your other AIM entry points.

IMPORTANT:
The AIMInitInfo data structure and the structures it points to are deallocated as soon as your AIM returns from MyAIMInit. If you wish to retain access to this data, you must copy it to your own storage. Specifically, you should make a copy of the RegEntryID pointed to by aimRegEntry. Copying the RegEntryIDPtr is not sufficient!


The ATADevInfo structure holds information about a specific ATA device on a bus. Your



struct ATADevInfo {
    UInt8                           devType;
    SInt8                           devID;
};
typedef struct ATADevInfo ATADevInfo;
 
enum {
    kATAInvalidDeviceID         = -1,
    kATADevice0DeviceID         =  0,
    kATADevice1DeviceID         =  1
};


The fields have the following meaning:

devType
Your AIM must set this field to indicate the type of the attached device. Possible ATA device types are listed in "ATA.h" (current kATADeviceUnknown, kATADeviceATA, kATADeviceATAPI, and kATADeviceReserved).
devID
Your AIM must set this field to indicate the ATA device ID of the attached device. Possible device IDs are kATAInvalidDeviceID, kATADevice0DeviceID and kATADevice1DeviceID.

If your AIM wants to indicate that no ATA device is attach, it must set devType to kATADeviceUnknown and devID to kATAInvalidDeviceID.

If your AIM initialization routine returns an error, or it indicates that there are no devices on the bus, ATA Manager fails the request to register the ATA bus, unloads your AIM, and dispose of all references to it.

IMPORTANT:
It is especially important to take note of the circumstances under which your AIM will be unloaded when extending the interrupt source tree for multiple bus controllers that share an interrupt. Remember, your buses may be initialized in any order. The first instance of an AIM that successfully initializes should extend the interrupt tree and store the child interrupt nodes for each bus in the Name Registry. When the second bus is initialized, it can look in the Name Registry to determine whether the interrupt tree has been extended or not.


Suggested result codes include:

  • noErr Initialization successful.
  • memFullErr Unable to allocate private data.
  • nsDrvErr No device detected on node
  • paramErr Bad parameter
  • nrInvalidNodeErr Invalid Name Registry node
  • nrNotFoundErr A required property was not found in the Name Registry node
  • ATAInitFail Initialization failed

This routine is always be called at system task time.

MyAIMClose



extern OSStatus MyAIMClose(UInt32 refCon, RegEntryIDPtr aimRegEntry);


refCon A pointer to your per-bus storage, as returned by MyAIMInit
aimRegEntry The Name Registry node of the ATA bus which is being deregistered.
result An error code; see below for details.

The ATA Manager calls your AIM's close routine to terminate operations on the ATA bus controlled by your AIM. This routine is called when an ATA bus is deregistered with ATA Manager. Your AIM must shut down its hardware, remove any interrupt handlers, and release any resources it owns. This will be the last request that a particular instance of your AIM will receive.

Any error code returned by your AIM is ignored. You should structure your AIM such that its close routine can not fail.

This routine will always be called at system task time.

MyAIMAction



extern void     MyAIMAction(UInt32 refCon, ATAReqBlock *pb);


refCon A pointer to your per-bus storage, as returned by MyAIMInit
pb A pointer to an ATAReqBlock parameter block, described below.

The ATA Manager calls your AIM's action routine to perform a transaction on the ATA bus. The ATAReqBlock parameter block specifies the action to perform and the place to store the results. The meaning of many of the fields is dependent on the ataFunctionCode field, which specifies exactly what operation is to be performed. Each function code is described in detail in AIM Action Function Codes.



struct ATAReqBlock {
    UInt32                          connectionID;
    UInt32                          MsgID;
    ATAResult *                     result;
    ATADiagResult *                 DiagResult;
    ATABusInfo *                    busInfo;
    ATADevConfig *                  devConfig;
    ATADataObject                   ioObject;
    ataTaskFile                     ataPBTaskFile;
    ATAPICmdPacket                  packetCBD;
    Duration                        Timeout;
    UInt32                          BusID;
    SInt8                           DevID;
    UInt8                           ataFunctionCode;
    UInt32                          AbortID;
    UInt32                          ataPBLogicalBlockSize;
    UInt32                          ataPBFlags;
    UInt32                          reserved;
    struct ATAReqBlock *            nextREQ;
    OSStatus                        ataPBResult;
    UInt8                           ataPBErrorRegister;
    UInt8                           ataPBStatusRegister;
    UInt32                          ataPBactualXferCount;
    UInt32                          ataPBState;
    UInt32                          ataPBSemaphores;
    UInt8                           XferType;
    UInt8                           ataModeType;
    UInt8                           ataPBIOSpeed;
    UInt8                           reserved2;
    UInt16                          reserved3;
};
typedef struct ATAReqBlock ATAReqBlock;


Unless otherwise stated, a field is has the same meaning for all function codes (except kATAFnKillIO). The fields have the following meaning:

connectionID
Reserved. You must not modify this field or depend on its contents.
MsgID
Reserved. You must not modify this field or depend on its contents.
result
This field is used to hold the result of an AIM action. This field is a structure (ATAResult), not a simple 'ioResult' value, but the fields of the structure are only relevant to the kATAFnExecIO function code. The only field relevant to the other function codes is ataResult field, but your AIM does not need to explicitly set this field because ATAFamIODone does it for you.
DiagResult
This field is significant only for the kATAFnRegAccess function code and is described along with that function code.
busInfo
This field is significant only for the kATAFnBusInquiry function code and is described along with that function code.
devConfig
This field is significant only for the kATAFnGetDriveConfig and kATAFnSetDriveConfig function codes and is described along with those function codes.
ioObject
This field is significant only for the kATAFnExecIO and kATAFnDriveIdentify function codes and is described along with those function codes.
ataPBTaskFile
This field is significant only for the kATAFnExecIO function code and is described along with that function code.
packetCBD
This field is significant only for the kATAFnExecIO function code and is described along with that function code.
Timeout
The ATA Manager sets this field to a timeout (in milliseconds) for the request. It derives the value from the ataPBTimeOut field of the client's request, or sets it to a default value (currently 31000) if ataPBTimeOut was zero.
BusID
ATA Manager sets up this field to the bus ID of the ATA bus on which to perform the action. It derives this value from ataPBDeviceID field of the client's request. Typically your AIM ignores this field because the bus is already uniquely identified by the per-bus storage pointed to by the refCon passed to AIMAction.
DevID
ATA Manager sets up this field to either kATADevice0DeviceID or kATADevice1DeviceID to describe which device on the ATA bus to act upon. It derives this value from the ataPBDeviceID field of the client's request.
ataFunctionCode
ATA Manager sets up this field to describe the action that the AIM should take. AIM Action Function Codes lists the defined function codes.
AbortID
ATA Manager does not use this field except insofar as to initialize it to zero. Your AIM can use this field as it sees fit.
ataPBLogicalBlockSize
This field is significant only for the kATAFnExecIO function code and is described along with that function code.
ataPBFlags
The ATA Managers sets this field to the value of the ataPBFlags field of the client's request. Your AIM is expected to read this field and act on the flags it contains.
reserved
Reserved. You must not modify this field or depend on its contents.
nextREQ
ATA Manager does not use this field except insofar as to initialize it to zero. Your AIM can use this field as it sees fit.
ataPBResult
ATA Manager does not use this field except insofar as to initialize it to zero. Your AIM can use this field as it sees fit, although typically it is used to hold a temporary result.
ataPBErrorRegister
ATA Manager does not use this field except insofar as to initialize it to zero. Your AIM can use this field as it sees fit, although typically it is used as temporary storage for the error register.
ataPBStatusRegister
ATA Manager does not use this field except insofar as to initialize it to zero. Your AIM can use this field as it sees fit, although typically it is used as temporary storage for the status register.
ataPBactualXferCount
ATA Manager does not use this field except insofar as to initialize it to zero. Your AIM can use this field as it sees fit, although typically it is used as temporary storage for the transfer count.
ataPBState
ATA Manager does not use this field except insofar as to initialize it to zero. Your AIM can use this field as it sees fit, although typically it is used to track the state of the request.
ataPBSemaphores
ATA Manager does not use this field except insofar as to initialize it to zero. Your AIM can use this field as it sees fit, although typically it is used to hold flags that indicate the status of the request.
XferType
ATA Manager does not use this field except insofar as to initialize it to zero. Your AIM can use this field as it sees fit, although typically it is used to hold the I/O transfer type (PIO, single-word DMA, multi-word DMA).
ataModeType
ATA Manager sets up this field to indicate whether the ataPBIOSpeed field contains an absolute value (kATAModeAbsolute) or a bitmap of possible values (kATAModeBitmap). It derives this value from the ataPBVers field of the client request; absolute mode is used only if the parameter block is less than version 3. See ATA I/O Modes for more information about how your AIM should interpret this and the ataPBIOSpeed field.
ataPBIOSpeed
ATA Manager sets this field to the ataPBIOSpeed field of the client's request.
reserved2
Reserved. You must not modify this field or depend on its contents.
reserved3
Reserved. You must not modify this field or depend on its contents.

Each field is described in more detail with the appropriate function code.

Your AIM typically processes an action request using a state machine. When it receives the action request, it initializes the state machine and starts the first (asynchronous) step of processing the request. It then returns control to the ATA Manager. When the first step is complete, the hardware generates an interrupt and the AIM's interrupt service routine is called. It notifies ATA Manager of the bus event by calling ATAFamBusEventForAIM. ATA Manager then calls the AIM's bus event handler routine (MyAIMHandleBusEvent), which starts the next (asynchronous) step of processing the request. When the last step is done, the AIM calls ATAFamIODone. ATA Manager completes the original client's request and then calls the AIM to start the next request.

This routine may be called at any execution level.

MyAIMHandleBusEvent



extern void     MyAIMHandleBusEvent(UInt32 refCon, UInt32 aimData);


refCon A pointer to your per-bus storage, as returned by MyAIMInit
aimData The bus event type, as passed in to ATAFamBusEventForAIM or returned from MyAIMPoll; AIM Synchronization Model for more details

The ATA Manager calls your AIM's bus event handler routine to handle events detected on your bus. Typically these events are interrupts from your bus's interrupt hardware. Your AIM informs ATA Manager of these events in one of two ways:

  • When an interrupt occurs (and is not masked), your interrupt service routine is called. Your interrupt service routine must inform ATA Manager of the bus event by calling ATAFamBusEventForAIM and then return.
  • When interrupts are masked (or otherwise deferred), ATA Manager calls your AIM's interrupt poll routine (MyAIMPoll) to poll for masked interrupts. If the interrupt poll routine detects an interrupt, it returns an indicative status to ATA Manager.

Regardless of how it informs ATA Manager of the bus event, your AIM can provide a bus event type which indicates what type of bus event occurred. The ATA Manager passes the same bus event type back to the bus event handler in the aimData parameter. Typically this bus event type is used to distinguish the type of bus event that has occurred. For example, your AIM might use a value of 1 to indicate that a DMA interrupt has occurred and a value of 2 to indicate that an I/O interrupt has occurred. ATA Manager does not interrupt this value.

Typically your AIM responds to a bus event by moving the current I/O request to the next state. For example, if the current I/O request is waiting for an I/O completion bus event, your AIM would respond to that bus event by calling ATAFamIODone to inform ATA Manager that the I/O request is complete. On the other hand, if the current I/O request is not complete, your AIM would respond by setting up the next asynchronous I/O operation.

This routine may be called at any execution level. It is typically executed with interrupts enabled (either from a deferred task or a secondary interrupt) but that is not guaranteed.

MyAIMPoll



extern Boolean  MyAIMPoll(UInt32 refCon,
                          UInt32 interruptLevel,
                          UInt32 *aimDataPtr);


refCon

A pointer to your per-bus storage, as returned by MyAIMInit.

interruptLevel

The current 68K interrupt mask: a value from 0 to 7.

aimDataPtr

If your AIM detected a bus event, it should set the value pointed to by this parameter to the type of bus event that occurred; see AIM Synchronization Model for more details.

result

True if the AIM detected a bus event, otherwise false.


The ATA Manager calls your AIM's interrupt poll routine when it detects that a synchronous I/O request is blocked because interrupts are masked (or otherwise deferred). Your AIM must look at its interrupt hardware to determine if there is an