Handling Errors

All programming tasks carry the potential for error. Aside from standard errors of syntax or logic, however, applications that access hardware may encounter specific types of errors that are outside the experience of many application developers.

This chapter covers some of the errors you might see while developing an application that uses the I/O Kit to access hardware on an OS X system. It describes how to “read” the I/O Kit’s error codes and then provides some information on the exclusive-access error.

Interpreting I/O Kit Error Return Values

Because you’ll be using I/O Kit functions extensively while looking up devices in the I/O Registry and examining the objects that represent them, you should be familiar with the structure of the I/O Kit’s error return values. This section describes how your application can interpret error values returned by I/O Kit functions.

The I/O Kit uses an error return mechanism, defined by the kernel framework, in which a 32-bit, unsigned return value supplies information in three separate bit fields, as shown in Figure 5-1. That is:

To work with these error return values, the header file IOReturn.h defines the following type:

typedef kern_return_t IOReturn;    //kern_return_t is an int

You can use kern_return_t and IOReturn to obtain return values from I/O Kit functions that use either type.

Figure 5-1  Bit layout for kernel and I/O Kit error return values
Bit layout for kernel and I/O Kit error return values

Listing 5-1 shows some common system error values that are defined in the header Kernel.framework/Headers/mach/error.h.

Listing 5-1  Error.h system error values

#define     err_kern    err_system(0x0)     /* kernel */
#define     err_us      err_system(0x1)     /* user space library */
#define     err_server  err_system(0x2)     /* user space servers */
#define     err_ipc     err_system(0x3)     /* old ipc errors */
#define     err_mach_ipc err_system(0x4)    /* mach-ipc errors */
#define     err_dipc    err_system(0x7)     /* distributed ipc */
#define     err_local   err_system(0x3e)    /* user defined errors */
#define     err_ipc_compat err_system(0x3f) /* (compatibility) mach-ipc errors */
#define     err_max_system 0x3f

This header also defines macros for defining system and subsystem error values and for extracting system, subsystem, and code values from an error return value, as shown in Listing 5-2.

Listing 5-2  Error.h macros for working with error return values

#define     err_system(x)       (((x)&0x3f)<<26)
#define     err_sub(x)          (((x)&0xfff)<<14)
 
#define     err_get_system(err) (((err)>>26)&0x3f)
#define     err_get_sub(err)    (((err)>>14)&0xfff)
#define     err_get_code(err)   ((err)&0x3fff)

Additional system values, as well as subsystem and code values, are defined in header files for particular systems. For example, the header file Kernel.framework/Headers/IOKit/IOReturn.h defines the values shown in Listing 5-3 for the I/O Kit.

Listing 5-3  IOReturn.h error return values

#ifndef sys_iokit
#define sys_iokit                   err_system(0x38)
#endif /* sys_iokit */
#define sub_iokit_common            err_sub(0)
#define sub_iokit_usb               err_sub(1)
#define sub_iokit_firewire          err_sub(2)
#define sub_iokit_reserved          err_sub(-1)
#define iokit_common_err(return)    (sys_iokit|sub_iokit_common|return)
 
#define kIOReturnSuccess         KERN_SUCCESS            // OK
#define kIOReturnError           iokit_common_err(0x2bc) // general error
#define kIOReturnNoMemory        iokit_common_err(0x2bd) // can't allocate memory
#define kIOReturnNoResources     iokit_common_err(0x2be) // resource shortage
#define kIOReturnIPCError        iokit_common_err(0x2bf) // error during IPC
#define kIOReturnNoDevice        iokit_common_err(0x2c0) // no such device
// ... (many more individual error codes)

Your application may be able to use these error values directly, without having to extract system, subsystem, or code values. For example, you could use code like the following to check for a no device error:

IOReturn returnVal;
 
returnVal = IOKitSomeFunction(...);
 
if (returnVal == kIOReturnNoDevice)
{
    // "No device returned" error in I/O Kit system, common subsystem.
}

However, in some cases you may want to isolate an error code value or determine which system or subsystem an error value came from. From the definitions shown in Listing 5-3, and a little bit of calculation, you can see that the error return value for a no device error (kIOReturnNoDevice) in the I/O Kit system and the I/O Kit common subsystem would have the following bit-field values:

The fully assembled bit representation is 1110 0000 0000 0000 0000 0010 1011 1100, resulting in a hex value of 0xe00002c0. To extract the system, subsystem, or code value from such an error return value, you use the macros shown in Listing 5-2 along with the constants defined in Listing 5-3. For example:

IOReturn returnVal;
 
returnVal = IOKitSomeFunction(...);
 
if (err_get_system(returnVal) == err_get_system(sys_iokit))
{
    // The error was in the I/O Kit system
    UInt32 codeValue = err_get_code(returnVal);
    // Can now perform test on error code, display it to user, or whatever.
}

Handling Exclusive-Access Errors

Many types of devices are designed to allow only one process at a time to access the device. A scanner, for example, supports access by only one application at a time. Accordingly, I/O Kit families enforce the access policy for their devices, whether exclusive or shared. In addition, Classic (the Mac OS 9 environment that you can run in OS X) may expect its drivers to have exclusive access to some devices, such as USB devices. In the course of developing an application that accesses hardware, you may receive an exclusive-access error, even when you believe yours is the only process trying to open the device. When this happens, if there is no higher-level arbitration you can employ, you may need to present a dialog to the user and either try accessing another device of the same class or try to access the same device later or under different circumstances.

Defined in IOReturn.h (in the I/O Kit framework), the kIOReturnExclusiveAccess error tells your application that the device it is attempting to access has already been opened by another entity. In most cases, the user client enforces exclusive access in its open method. When an application uses a device interface’s open function, the device interface issues an open command to the user client. The user client responds by trying to open its provider (the device nub) and, if it fails, it returns the kIOReturnExclusiveAccess error. (If the user client finds that the provider is terminated, it will probably return the kIOReturnNotAttached error.)

Different I/O Kit device families handle the exclusive-access issue in different ways. The FireWire family, for example, enforces exclusive access to its devices, but also allows multiple device interfaces to open different objects in the FireWire driver stack. It does this by employing the concept of a session reference to refer to existing user space–kernel connections. Consider an application that uses a FireWire family device interface to open an AV/C unit on a FireWire device. If that application also wants to access the FireWire unit object that supports the AV/C unit, it can get a session reference from the AV/C device interface and use it to get the FireWire unit device interface. For more information on this process, see FireWire Device Interface Guide.

The SCSI Architecture Model family, on the other hand, does not allow multiple device interfaces to simultaneously open different objects in the driver stack, but it does allow applications to get information about devices that in-kernel drivers currently have open. It also allows an application to gain exclusive access to an authoring or media-mastering device by tearing down the upper layers of the stack and requiring the in-kernel logical unit driver to yield control to the application. For more information about this process, see SCSI Architecture Model Device Interface Guide.

For serial and storage devices you can access through device files, your application may fail to open the device for the following reasons: