JavaScript is required to for searching.
Skip Navigation Links
Exit Print View
Writing Device Drivers     Oracle Solaris 11.1 Information Library
search filter icon
search icon

Document Information

Preface

Part I Designing Device Drivers for the Oracle Solaris Platform

1.  Overview of Oracle Solaris Device Drivers

2.  Oracle Solaris Kernel and Device Tree

3.  Multithreading

Locking Primitives

Storage Classes of Driver Data

Mutual-Exclusion Locks

Setting Up Mutexes

Using Mutexes

Readers/Writer Locks

Semaphores

Thread Synchronization

Condition Variables in Thread Synchronization

Initializing Condition Variables

Waiting for the Condition

Signaling the Condition

cv_wait() and cv_timedwait() Functions

cv_wait_sig() Function

cv_timedwait_sig() Function

Choosing a Locking Scheme

Potential Locking Pitfalls

Threads Unable to Receive Signals

4.  Properties

5.  Managing Events and Queueing Tasks

6.  Driver Autoconfiguration

7.  Device Access: Programmed I/O

8.  Interrupt Handlers

9.  Direct Memory Access (DMA)

10.  Mapping Device and Kernel Memory

11.  Device Context Management

12.  Power Management

13.  Hardening Oracle Solaris Drivers

14.  Layered Driver Interface (LDI)

Part II Designing Specific Kinds of Device Drivers

15.  Drivers for Character Devices

16.  Drivers for Block Devices

17.  SCSI Target Drivers

18.  SCSI Host Bus Adapter Drivers

19.  Drivers for Network Devices

20.  USB Drivers

21.  SR-IOV Drivers

Part III Building a Device Driver

22.  Compiling, Loading, Packaging, and Testing Drivers

23.  Debugging, Testing, and Tuning Device Drivers

24.  Recommended Coding Practices

Part IV Appendixes

A.  Hardware Overview

B.  Summary of Oracle Solaris DDI/DKI Services

C.  Making a Device Driver 64-Bit Ready

D.  Console Frame Buffer Drivers

E.  pci.conf File

Index

Thread Synchronization

In addition to protecting shared data, drivers often need to synchronize execution among multiple threads.

Condition Variables in Thread Synchronization

Condition variables are a standard form of thread synchronization. They are designed to be used with mutexes. The associated mutex is used to ensure that a condition can be checked atomically, and that the thread can block on the associated condition variable without missing either a change to the condition or a signal that the condition has changed.

The condvar(9F) functions are:

cv_broadcast(9F)

Signals all threads waiting on the condition variable.

cv_destroy(9F)

Destroys a condition variable.

cv_init(9F)

Initializes a condition variable.

cv_signal(9F)

Signals one thread waiting on the condition variable.

cv_timedwait(9F)

Waits for condition, time-out, or signal. See Threads Unable to Receive Signals.

cv_timedwait_sig(9F)

Waits for condition or time-out.

cv_wait(9F)

Waits for condition.

cv_wait_sig(9F)

Waits for condition or return zero on receipt of a signal. See Threads Unable to Receive Signals.

Initializing Condition Variables

Declare a condition variable of type kcondvar_t for each condition. Usually, the condition variables are declared in the driver's soft-state structure. Use cv_init(9F) to initialize each condition variable. Similar to mutexes, condition variables are usually initialized at attach(9E) time. A typical example of initializing a condition variable is:

cv_init(&xsp->cv, NULL, CV_DRIVER, NULL);

For a more complete example of condition variable initialization, see Chapter 6, Driver Autoconfiguration.

Waiting for the Condition

To use condition variables, follow these steps in the code path waiting for the condition:

  1. Acquire the mutex guarding the condition.

  2. Test the condition.

  3. If the test results do not allow the thread to continue, use cv_wait(9F) to block the current thread on the condition. The cv_wait(9F) function releases the mutex before blocking the thread and reacquires the mutex before returning. On return from cv_wait(9F), repeat the test.

  4. After the test allows the thread to continue, set the condition to its new value. For example, set a device flag to busy.

  5. Release the mutex.

Signaling the Condition

Follow these steps in the code path to signal the condition:

  1. Acquire the mutex guarding the condition.

  2. Set the condition.

  3. Signal the blocked thread with cv_broadcast(9F).

  4. Release the mutex.

The following example uses a busy flag along with mutex and condition variables to force the read(9E) routine to wait until the device is no longer busy before starting a transfer.

Example 3-1 Using Mutexes and Condition Variables

static int
xxread(dev_t dev, struct uio *uiop, cred_t *credp)
{
        struct xxstate *xsp;
        /* ... */
        mutex_enter(&xsp->mu);
        while (xsp->busy)
                cv_wait(&xsp->cv, &xsp->mu);
        xsp->busy = 1;
        mutex_exit(&xsp->mu);
        /* perform the data access */
}

static uint_t
xxintr(caddr_t arg)
{
        struct xxstate *xsp = (struct xxstate *)arg;
        mutex_enter(&xsp->mu);
        xsp->busy = 0;
        cv_broadcast(&xsp->cv);
        mutex_exit(&xsp->mu);
}

cv_wait() and cv_timedwait() Functions

If a thread is blocked on a condition with cv_wait(9F) and that condition does not occur, the thread would wait forever. To avoid that situation, use cv_timedwait(9F), which depends upon another thread to perform a wakeup. cv_timedwait() takes an absolute wait time as an argument. cv_timedwait() returns -1 if the time is reached and the event has not occurred. cv_timedwait() returns a positive value if the condition is met.

cv_timedwait(9F) requires an absolute wait time expressed in clock ticks since the system was last rebooted. The wait time can be determined by retrieving the current value with ddi_get_lbolt(9F). The driver usually has a maximum number of seconds or microseconds to wait, so this value is converted to clock ticks with drv_usectohz(9F) and added to the value from ddi_get_lbolt(9F).

The following example shows how to use cv_timedwait(9F) to wait up to five seconds to access the device before returning EIO to the caller.

Example 3-2 Using cv_timedwait()

clock_t            cur_ticks, to;
mutex_enter(&xsp->mu);
while (xsp->busy) {
        cur_ticks = ddi_get_lbolt();
        to = cur_ticks + drv_usectohz(5000000); /* 5 seconds from now */
        if (cv_timedwait(&xsp->cv, &xsp->mu, to) == -1) {
                /*
                 * The timeout time 'to' was reached without the
                 * condition being signaled.
                 */
                /* tidy up and exit */
                mutex_exit(&xsp->mu);
                return (EIO);
        }
}
xsp->busy = 1;
mutex_exit(&xsp->mu);

Although device driver writers generally prefer to use cv_timedwait(9F) over cv_wait(9F), sometimes cv_wait(9F) is a better choice. For example, cv_wait(9F) is better if a driver is waiting on the following conditions:

cv_wait_sig() Function

A driver might be waiting for a condition that cannot occur or will not happen for a long time. In such cases, the user can send a signal to abort the thread. Depending on the driver design, the signal might not cause the driver to wake up.

cv_wait_sig(9F) allows a signal to unblock the thread. This capability enables the user to break out of potentially long waits by sending a signal to the thread with kill(1) or by typing the interrupt character. cv_wait_sig(9F) returns zero if it is returning because of a signal, or nonzero if the condition occurred. However, see Threads Unable to Receive Signals for cases in which signals might not be received.

The following example shows how to use cv_wait_sig(9F) to allow a signal to unblock the thread.

Example 3-3 Using cv_wait_sig()

mutex_enter(&xsp->mu);
while (xsp->busy) {
        if (cv_wait_sig(&xsp->cv, &xsp->mu) == 0) {
        /* Signaled while waiting for the condition */
                /* tidy up and exit */
                mutex_exit(&xsp->mu);
                return (EINTR);
        }
}
xsp->busy = 1;
mutex_exit(&xsp->mu);

cv_timedwait_sig() Function

cv_timedwait_sig(9F) is similar to cv_timedwait(9F) and cv_wait_sig(9F), except that cv_timedwait_sig() returns -1 without the condition being signaled after a timeout has been reached, or 0 if a signal (for example, kill(2)) is sent to the thread.

For both cv_timedwait(9F) and cv_timedwait_sig(9F), time is measured in absolute clock ticks since the last system reboot.