Use a prefix based on the name of your driver to give global variables and functions unique names.
The name of each function, data element, and driver preprocessor definition must be unique for each driver.
A driver module is linked into the kernel. The name of each symbol unique to a particular driver must not collide with other kernel symbols. To avoid such collisions, each function and data element for a particular driver must be named with a prefix common to that driver. The prefix must be sufficient to uniquely name each driver symbol. Typically, this prefix is the name of the driver or an abbreviation for the name of the driver. For example, xx_open() would be the name of the open(9E) routine of driver xx.
When building a driver, a driver must necessarily include a number of system header files. The globally-visible names within these header files cannot be predicted. To avoid collisions with these names, each driver preprocessor definition must be given a unique name by using an identifying prefix.
A distinguishing driver symbol prefix also is an aid to deciphering system logs and panics when troubleshooting. Instead of seeing an error related to an ambiguous attach() function, you see an error message about xx_attach().
If you are basing your design on an existing driver, modify the configuration file before adding the driver.
The -n option in the add_drv(1M) command enables you to update the system configuration files for a driver without loading or attaching the driver.
Use the cmn_err() function to log driver activity.
You can use the cmn_err(9F) function to display information from your driver similar to the way you might use print statements to display information from a user program. The cmn_err(9F) function writes low priority messages to /dev/log. The syslogd(1M) daemon reads messages from /dev/log and writes low priority messages to /var/adm/messages. Use the following command to monitor the output from your cmn_err(9F) messages:
% tail -f /var/adm/messages
Be sure to remove cmn_err() calls that are used for development or debugging before you compile your production version driver. You might want to use cmn_err() calls in a production driver to write error messages that would be useful to a system administrator.
Clean up allocations and other initialization activities when the driver exits.
When the driver exits, whether intentionally or prematurely, you need to perform such tasks as closing opened files, freeing allocated memory, releasing mutex locks, and destroying any mutexes that have been created. In addition, the system must be able to close all minor devices and detach driver instances even after the hardware fails. An orderly approach is to reverse _init() actions in the _fini() routine, reverse open() operations in the close() routine, and reverse attach() operations in the detach() routine.
Use ASSERT(9F) to catch unexpected error returns.
ASSERT() is a macro that halts the kernel execution if a condition that was expected to be true turns out to be false. To activate ASSERT(), you need to include the sys/debug.h header file and specify the DEBUG preprocessor symbol during compilation.
Use mutex_owned() to validate and document locking requirements.
The mutex_owned(9F) function helps determine whether the current thread owns a specified mutex. To determine whether a mutex is held by a thread, use mutex_owned() within ASSERT().
Use conditional compilation to toggle “costly” debugging features.
The Solaris OS provides various debugging functions, such as ASSERT() and mutex-owned(), that can be turned on by specifying the DEBUG preprocessor symbol when the driver is compiled. With conditional compilation, unnecessary code can be removed from the production driver. This approach can also be accomplished by using a global variable.
Use a separate instance of the driver for each device to be controlled.
Use DDI functions as much as possible in your device drivers.
These interfaces shield the driver from platform-specific dependencies such as mismatches between processor and device endianness and any other data order dependencies. With these interfaces, a single-source driver can run on the SPARC platform, x86 platform, and related processor architectures.
Anticipate corrupted data.
Always check that the integrity of data before that data is used. The driver must avoid releasing bad data to the rest of the system.
A device should only write to DMA buffers that are controlled solely by the driver.
This technique prevents a DMA fault from corrupting an arbitrary part of the system's main memory.
Use the ddi_umem_alloc(9F) function when you need to make DMA transfers.
This function guarantees that only whole, aligned pages are transferred.
Set a fixed number of attempts before taking alternate action to deal with a stuck interrupt.
The device driver must not be an unlimited drain on system resources if the device locks up. The driver 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.
Use care when setting the sequence for mutex acquisitions and releases so as to avoid unwanted thread interactions if a device fails.
See Thread Interaction in Writing Device Drivers for more information.
Check for malformed ioctl() requests from user applications.
User requests can be destructive. The design of the driver should take into consideration the construction of each type of potential ioctl() request.
Try to avoid situations where a driver continues to function without detecting a device failure.
A driver should switch to an alternative device rather than try to work around a device failure.
All device drivers in the Solaris OS must support hotplugging.
All devices need to be able to be installed or removed without requiring a reboot of the system.
All device drivers should support power management.
Power management provides the ability to control and manage the electrical power usage of a computer system or device. Power management enables systems to conserve energy by using less power when idle and by shutting down completely when not in use.
Apply the volatile keyword to any variable that references a device register.
Without the volatile keyword, the compile-time optimizer can delete important accesses to a register.
Perform periodic health checks to detect and report faulty devices.
A periodic health check should include the following activities:
Check any register or memory location on the device whose value might have been altered since the last poll.
Timestamp outgoing requests such as transmit blocks or commands that are issued by the driver.
Initiate a test action on the device that should be completed before the next scheduled check.