Developing a Universal Binary

If you plan to create a universal binary version of your logical unit driver, protocol services driver, or filter scheme, first read Universal Binary Programming Guidelines, Second Edition. That document covers architectural differences and byte-ordering formats and provides comprehensive guidelines for code modification and building universal binaries. Then, to find out how to decide which compiler version and SDK you need, see “Developing a Device Driver to Run on an Intel-Based Macintosh” in IOKit Device Driver Design Guidelines.

This chapter briefly outlines a few of the mass storage–specific issues you should keep in mind as you create a universal binary version of your driver or filter scheme.

Creating a Logical Unit or Protocol Services Driver Universal Binary

As you create a universal binary version of your logical unit or protocol services driver, be aware of places in your code where you might make assumptions about the byte ordering of multibyte numerical values. Be sure to replace any hard-coded byte swaps (such as code that always swaps a multibyte value from big endian to little endian) with the appropriate conditional byte-swapping macros defined in libkern/OSByteOrder.h.

For example, the Apple-provided IOSCSIBlockCommandsDevice class contains code that uses a byte-swapping macro defined in OSByteOrder.h to swap two four-byte fields in a SCSI_Capacity_Data structure, as shown in Listing 4-1. (The SCSI_Capacity_Data structure is defined as the capacity return structure for the READ_CAPACITY 10 command in the SCSICmds_READ_CAPACITYDefinitions.h header file.)

Listing 4-1  Byte-swapping in IOSCSIBlockCommandsDevice code

bool IOSCSIBlockCommandsDevice::DetermineMediumCapacity (UInt64 * blockSize, UInt64 * blockCount) {
SCSI_Capacity_Data    capacityData = {0};
...
*blockSize = 0;
*blockCount = 0;
...
// Create and send READ_CAPACITY command.
// If the command completed successfully:
*blockSize = OSSwapBigToHostInt32 (capacityData.BLOCK_LENGTH_IN_BYTES);
*blockCount = ((UInt64) OSSwapBigToHostInt32 (capacityData.RETURNED_LOGICAL_BLOCK_ADDRESS)) + 1;
...
}

In general, data returned from devices that comply with the SCSI Architecture Model specifications is in the big-endian format. Fortunately, however, the SCSI command model specification defines the CDB (command descriptor block) as a byte array. This means that the bytes are stored in the defined order regardless of the native endian format of the computer the driver is running in.

Creating a Filter Scheme Universal Binary

As you create a universal binary version of your filter-scheme driver, be aware that filter schemes frequently handle data structures that are read from or written to disk. It's essential that the data structure on the disk remain in the correct endian format so the disk can be used with both PowerPC-based and Intel-based Macintosh computers. Depending on the native endian format of the computer in which your filter-scheme driver is running, therefore, your driver may need to byte swap the data structures it handles.

If you've determined that byte-swapping is necessary, you can implement it in either of the following two ways:

To avoid confusion, it's best to choose only one of these two alternatives and be consistent in its implementation. Whichever option you choose, however, be sure to use the conditional byte-swapping macros defined in libkern/OSByteOrder.h. When you use these macros, the compiler optimizes your code so the routines are executed only if they are necessary for the architecture in which your driver is running.

For example, the built-in Apple partition-scheme driver, IOApplePartitionScheme, uses a byte-swapping macro defined in OSByteOrder.h to swap a two-byte field in a dpme structure, as shown in Listing 4-2. (The dpme structure is defined as a disk partition map entry in the IOApplePartitionScheme.h header file.)

Listing 4-2  Byte-swapping in IOApplePartitionScheme code

OSSet * IOApplePartitionScheme::scan (SInt32 * score) {
...
dpme *     dpmeMap = 0;
...
// Read in a partition entry and assign to dpmeMap.
...
// Determine whether the partition entry signature is present.
if (OSSwapBigToHostInt16(dpmeMap->dpme_signature) != DPME_SIGNATURE)
    ...
}