Writing Device Drivers

Coding Hints

During development, debugging the driver should be a constant consideration. 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. This section provides some information that may be used to make the driver easier to debug.

Process Layout for Sun4m, Sun4c, Sun4d, and x86 Platforms

On the Sun4m, Sun4c, Sun4d, and x86 platforms, a Solaris 7 process looks like this:

Graphic

On these platforms, the kernel and user programs share the same context. The system portion of a process's virtual address space occupies the high end of memory, and the user portion occupies the lower end of memory.

The Solaris 7 system defines a KERNELBASE for each platform. KERNELBASE can be used when debugging drivers to determine the address space. Addresses below KERNELBASE probably refer to user addresses, while addresses above refer to kernel addresses. Table 16-1 lists the values of KERNELBASE for each of these platforms.

Table 16-1 KERNELBASE Values

Platform 

Value 

Sun4c 

OxF0000000 

Sun4m 

OxF0000000 

Sun4d 

OxE0000000 

x86 

OxE0000000 

Process Layout for Sun4u Platforms

On Sun4u platforms, there are separate kernel and user contexts. The process layout looks like this:

Graphic

On this platform, KERNELBASE is set to Ox10000000. USERLIMIT defines the upper limit of the user context; it is set to OxF0000000. Addresses below KERNELBASE probably refer to user addresses, while addresses above USERLIMIT probably refer to kernel addresses.

System Support

The system provides a number of routines that can aid in debugging; these are documented in Section 9 of the Solaris 2.7 Reference Manual.

cmn_err()()

cmn_err(9F) is used to print messages to the console from within the device driver. cmn_err(9F) 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.


ASSERT()()

void ASSERT(int expression)

ASSERT(9F) can be used to ensure that a condition is true at some point in the program. It is a macro whose use varies depending upon whether the compilation symbol DEBUG is defined. If DEBUG is not defined, the macro expands to nothing and the expression is not evaluated. If DEBUG is defined, the expression is evaluated and, if the value is zero, a message is printed to the system console and the system panics.

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 DEBUG, it is suggested that any conditional debugging code also be based on DEBUG, rather than on a driver symbol (such as MYDEBUG). Otherwise, for ASSERT(9F) to function properly, DEBUG must be defined whenever MYDEBUG is defined.


Assertions are an extremely valuable form of active documentation.

mutex_owned()()

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 the mutex held */
 	ASSERT(mutex_owned(&xsp->mu));
 	...
 }

Future releases of the Solaris operating system may only support the use of mutex_owned(9F) within ASSERT(9F) by not defining mutex_owned(9F) unless the preprocessor symbol DEBUG is defined.

Conditional Compilation and Variables

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 I/O control 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!")); 

This can be extended 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.

volatile and _depends_on

volatile is a keyword that must be used when declaring any variable that will reference a device register. If this is not done, the optimizer might optimize important accesses away. This is important; neglecting to use volatile can result in bugs that are difficult to track down. See "volatile" for more information.


Note -

_depends_on must not be declared a static variable; if it is, the compiler might optimize it out of the device driver code.