Writing Device Drivers

Recommended Coding Practices

This section provides information to make drivers easier to debug. Because the driver is operating much closer to the hardware and without the protection of the operating system, debugging kernel code is more difficult than debugging user-level code. For example, a stray pointer access can crash the entire system. It is important to build debugging support into your driver, both for development and maintenance.

Use cmn_err(9F) to Log Driver Activity

cmn_err(9F) is used to print messages to the console from within the device driver. It provides additional format characters (such as %b) to print device register bits. See cmn_err(9F) and "Printing Messages" for more information.


Note -

Though printf() and uprintf() currently exist, they should not be used if the driver is to be Solaris DDI-compliant.


Use ASSERT(9F) to Catch Invalid Assumptions

void ASSERT(EXPRESSION)

ASSERT(9F) is a macro 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.

ASSERT is defined only when the compilation symbol DEBUG is defined; ASSERT is compiled out of the code if DEBUG is not defined and therefore has no effect.

For example, if a driver pointer should be non-NULL and is not, the following assertion could 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 be based on DEBUG.


Assertions are an extremely valuable form of active documentation.

Use mutex_owned(9F) to Validate and Document Locking Requirements

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(9F) 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(9F) is only valid within ASSERT(9F) 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.