Part I Designing Device Drivers for the Solaris Platform
1. Overview of Solaris Device Drivers
2. Solaris Kernel and Device Tree
Storage Classes of Driver Data
Condition Variables in Thread Synchronization
Initializing Condition Variables
cv_wait() and cv_timedwait() Functions
5. Managing Events and Queueing Tasks
7. Device Access: Programmed I/O
10. Mapping Device and Kernel Memory
14. Layered Driver Interface (LDI)
Part II Designing Specific Kinds of Device Drivers
15. Drivers for Character Devices
18. SCSI Host Bus Adapter Drivers
19. Drivers for Network Devices
Part III Building a Device Driver
21. Compiling, Loading, Packaging, and Testing Drivers
22. Debugging, Testing, and Tuning Device Drivers
23. Recommended Coding Practices
B. Summary of Solaris DDI/DKI Services
C. Making a Device Driver 64-Bit Ready
The locking scheme for most device drivers should be kept straightforward. Using additional locks allows more concurrency but increases overhead. Using fewer locks is less time consuming but allows less concurrency. Generally, use one mutex per data structure, a condition variable for each event or condition the driver must wait for, and a mutex for each major set of data global to the driver. Avoid holding mutexes for long periods of time. Use the following guidelines when choosing a locking scheme:
Use the multithreading semantics of the entry point to your advantage.
Make all entry points re-entrant. You can reduce the amount of shared data by changing a static variable to automatic.
If your driver acquires multiple mutexes, acquire and release the mutexes in the same order in all code paths.
Hold and release locks within the same functional space.
Avoid holding driver mutexes when calling DDI interfaces that can block, for example, kmem_alloc(9F) with KM_SLEEP.
To look at lock usage, use lockstat(1M). lockstat(1M) monitors all kernel lock events, gathers frequency and timing data about the events, and displays the data.
See the Multithreaded Programming Guide for more details on multithreaded operations.
Mutexes are not re-entrant by the same thread. If you already own the mutex, attempting to claim this mutex a second time leads to the following panic:
panic: recursive mutex_enter. mutex %x caller %x
Releasing a mutex that the current thread does not hold causes this panic:
panic: mutex_adaptive_exit: mutex not held by thread
The following panic occurs only on uniprocessors:
panic: lock_set: lock held and only one CPU
The lock_set panic indicates that a spin mutex is held and will spin forever, because no other CPU can release this mutex. This situation can happen if the driver forgets to release the mutex on one code path or becomes blocked while holding the mutex.
A common cause of the lock_set panic occurs when a device with a high-level interrupt calls a routine that blocks, such as cv_wait(9F). Another typical cause is a high-level handler grabbing an adaptive mutex by calling mutex_enter(9F).
The sema_p_sig(), cv_wait_sig(), and cv_timedwait_sig() functions can be awakened when the thread receives a signal. A problem can arise because some threads are unable to receive signals. For example, when close(9E) is called as a result of the application calling close(2), signals can be received. However, when close(9E) is called from within the exit(2) processing that closes all open file descriptors, the thread cannot receive signals. When the thread cannot receive signals, sema_p_sig() behaves as sema_p(), cv_wait_sig() behaves as cv_wait(), and cv_timedwait_sig() behaves as cv_timedwait().
Use caution to avoid sleeping forever on events that might never occur. Events that never occur create unkillable (defunct) threads and make the device unusable until the system is rebooted. Signals cannot be received by defunct processes.
To detect whether the current thread is able to receive a signal, use the ddi_can_receive_sig(9F) function. If the ddi_can_receive_sig()function returns B_TRUE, then the above functions can wake up on a received signal. If the ddi_can_receive_sig()function returns B_FALSE, then the above functions cannot wake up on a received signal. If the ddi_can_receive_sig()function returns B_FALSE, then the driver should use an alternate means, such as the timeout(9F) function, to reawaken.
One important case where this problem occurs is with serial ports. If the remote system asserts flow control and the close(9E) function blocks while attempting to drain the output data, a port can be stuck until the flow control condition is resolved or the system is rebooted. Such drivers should detect this case and set up a timer to abort the drain operation when the flow control condition persists for an excessive period of time.
This issue also affects the qwait_sig(9F) function, which is described in Chapter 7, STREAMS Framework – Kernel Level, in STREAMS Programming Guide.