Writing Device Drivers

Debugging

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

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 – 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.