Sun Microsystems, Inc.
spacerspacer
spacer www.sun.com docs.sun.com |
spacer
black dot
 
 
  Previous   Contents   Next 
   
 
Chapter 19

Recommended Coding Practices

This chapter describes how to write drivers that are robust. Drivers that are written in accordance with the guidelines discussed in this chapter:

  • Are easier to debug

  • Protect the system from hardware and software faults

This chapter provides information on the following subjects:

Debugging

Driver code is more difficult to debug than user programs because:

  • The driver interacts directly with the hardware

  • The driver operates without the protection of the operating system available to user processes

Be sure to build debugging support into your driver. Debugging support facilitates future development and maintenance work.

Use cmn_err() to Log Driver Activity

Use the cmn_err() function to print messages to the console from within the device driver. cmn_err() is similar to printf(3C), but provides additional format characters (such as %b) to print device register bits. See the cmn_err(9F) man page for more information.


Note - To ensure that the driver is DDI-compliant, use cmn_err() instead of printf() and uprintf().


Use ASSERT() to Catch Invalid Assumptions

The syntax for ASSERT(9F) is as follows:

void ASSERT(EXPRESSION)

ASSERT() is a macro that is used to halt the execution of the kernel if a condition expected to be true is actually false. ASSERT provides a way for the programmer to validate the assumptions made by a piece of code.

The ASSERT() macro is defined only when the DEBUG compilation symbol is defined. However, when DEBUG is not defined, theASSERT() macro has no effect.

For example, if a driver pointer should be non-NULL and is not, the following assertion can be used to check the code:

ASSERT(ptr != NULL);

If the driver is compiled with DEBUG defined and the assertion fails, a message is printed to the console and the system panics:

panic: assertion failed: ptr != NULL, file: driver.c, line: 56

Note - Because ASSERT(9F) uses the DEBUG compilation symbol, any conditional debugging code should also use DEBUG.


Assertions are an extremely valuable form of active documentation.

Use mutex_owned() to Validate and Document Locking Requirements

The syntax for mutex_owned(9F) is as follows:

int mutex_owned(kmutex_t *mp);

A significant portion of driver development involves properly handling multiple threads. Comments should always be used when a mutex is acquired; they are even more useful when an apparently necessary mutex is not acquired. To determine if a mutex is held by a thread, use mutex_owned() within ASSERT(9F):

void helper(void)
{
        /* this routine should always be called with xsp's mutex held */
        ASSERT(mutex_owned(&xsp->mu));
        ...
}

Caution - mutex_owned() is only valid within ASSERT() macros. Under no circumstances should you use it to control the behavior of a driver.


Use Conditional Compilation to Toggle Costly Debugging Features

Debugging code can be placed in a driver by conditionally compiling code based on a preprocessor symbol such as DEBUG or by using a global variable. Conditional compilation has the advantage that unnecessary code can be removed in the production driver. Using a variable allows the amount of debugging output to be chosen at runtime. This can be accomplished by setting a debugging level at runtime with an ioctl or through a debugger. Commonly, these two methods are combined.

The following example relies on the compiler to remove unreachable code (the code following the always-false test of zero), and also provides a local variable that can be set in /etc/system or patched by a debugger.

#ifdef DEBUG
comments on values of xxdebug and what they do
static int xxdebug;
#define dcmn_err if (xxdebug) cmn_err
#else
#define dcmn_err if (0) cmn_err
#endif
...
    dcmn_err(CE_NOTE, "Error!\n");

This method handles the fact that cmn_err(9F) has a variable number of arguments. Another method relies on the macro having one argument, a parenthesized argument list for cmn_err(9F), which the macro removes. It also removes the reliance on the optimizer by expanding the macro to nothing if DEBUG is not defined.

#ifdef DEBUG
comments on values of xxdebug and what they do
static int xxdebug;
#define dcmn_err(X) if (xxdebug) cmn_err X
#else
#define dcmn_err(X) /* nothing */
#endif
    ...
/* Note:double parentheses are required when using dcmn_err. */
    dcmn_err((CE_NOTE, "Error!")); 

You can extend this in many ways, such as by having different messages from cmn_err(9F), depending on the value of xxdebug, but be careful not to obscure the code with too much debugging information.

Another common scheme is to write an xxlog() function, which uses vsprintf(9F) or vcmn_err(9F) to handle variable argument lists.

Defensive Programming

Use the following defensive programming techniques to prevent your code from causing system panics or hangs, draining system resources, or allowing the spread of corrupted data.

All Solaris drivers should abide by these coding practices:

  • Each piece of hardware should be controlled by a separate instance of the device driver. (See "Device Configuration Concepts".)

  • Programmed I/O (PIO) must be performed only through the DDI access functions, using the appropriate data access handle. (See Chapter 6, Device Access -- Programmed I/O.)

  • The device driver must assume that data it receives from the device could be corrupted. The driver must check the integrity of the data before using it.

  • The driver must avoid releasing bad data to the rest of the system.

  • Use only documented DDI functions and interfaces in your driver.

  • The driver must ensure that all writes by the device into DMA buffers (DDI_DMA_READ) are contained within pages of memory controlled entirely by the driver. This prevents a DMA fault from corrupting an arbitrary part of the system's main memory.

  • The device driver must not be an unlimited drain on system resources if the device locks up. It should time-out if a device claims to be continuously busy. The driver should also detect a pathological (stuck) interrupt request and take appropriate action.

  • The device driver must support Solaris Hot-Plug.

  • The device driver must use callbacks instead of waiting on resources.

  • The driver must free up resources after a fault. For example, the system must be able to close all minor devices and detach driver instances even after the hardware fails.

 
 
 
  Previous   Contents   Next