After you decide that the simple lock method is the appropriate method for locking specific resources, you use the simple lock interfaces to accomplish the locking. To use simple locks in a device driver, perform the following tasks:
To illustrate the use of these interfaces, the chapter uses code from an example device driver called /dev/xx that operates on some XX device. This example driver locks a softc structure resource called xx_softc.
Before using a simple lock, declare a simple lock structure for the resource you want to lock by using the decl_simple_lock_data interface. The following code fragment shows a call to decl_simple_lock_data in the /dev/xx device driver:
.
.
.
#include <kern/lock.h> [1]
.
.
.
struct xx_softc { int sc_openf; /* Open flag */ int sc_count; /* Count of characters written to device */ decl_simple_lock_data( , lk_xx_softc); /* SMP lock for xx_softc */ }xx_softc[NNONE]; [2]
.
.
.
The decl_simple_lock_data interface declares a simple lock structure, slock, of the specified name. You declare a simple lock structure for the purpose of protecting device driver data structures and device register access. You use decl_simple_lock_data to declare a simple lock structure and then pass it to the following simple lock-specific interfaces: simple_lock_init, simple_lock, simple_lock_try, simple_unlock, and simple_lock_terminate.
The decl_simple_lock_data interface can take two arguments:
You can also declare a simple lock structure by using the typedef simple_lock_data_t as in the following example:
.
.
.
struct xx_softc { int sc_openf; /* Open flag */ int sc_count; /* Count of characters written to device */ simple_lock_data_t lk_xx_softc; /* SMP lock for xx_softc */ }xx_softc[NNONE]; [1]
After declaring the simple lock structure, you initialize it by calling the simple_lock_init interface. The following code fragment shows a call to simple_lock_init by the /dev/xx device driver's xxcattach interface. The xxcattach interface performs the tasks necessary to establish communication with the actual device. One of these tasks is to initialize any global data structures. Thus, the xxcattach interface initializes the simple lock structure lk_xx_softc.
The code fragment also shows the declaration of the simple lock structure in the xx_softc structure.
.
.
.
#include <kern/lock.h> [1]
.
.
.
struct xx_softc { int sc_openf; /* Open flag */ int sc_count; /* Count of characters written to device */ simple_lock_data_t lk_xx_softc; /* SMP lock for xx_softc */ }xx_softc[NNONE]; [2]
.
.
.
xxcattach(ctlr) struct controller *ctlr; { register struct xx_softc *sc = &xx_softc[ctlr->ctlr_num]; /* Tasks to perform controller-specific initialization */
.
.
.
simple_lock_init(&sc->lk_xx_softc); [3]
.
.
.
/* Perform any other controller-specific initialization tasks */ }
The simple_lock_init interface takes one argument: a pointer to a simple lock structure. You can declare this simple lock structure by using the decl_simple_lock_data interface. In this call, the xxcattach interface passes the address of the lk_xx_softc member of the xx_softc structure pointer. You need to initialize the simple lock structure only once. [Return to example]
After declaring and initializing the simple lock structure, you can assert exclusive access by calling the simple_lock interface. The following code fragment shows a call to simple_lock by the /dev/xx device driver's xxopen interface. The xxopen interface is called as the result of an open system call.
The xxopen interface performs the following tasks:
The code fragment also shows the declaration of the simple lock structure in the xx_softc structure and the initialization of the simple lock structure by the driver's xxcattach interface. See Section 3.2 for explanations of these tasks.
.
.
.
#include <kern/lock.h>
.
.
.
struct xx_softc { int sc_openf; /* Open flag */ int sc_count; /* Count of characters written to device */ simple_lock_data_t lk_xx_softc; /* SMP lock for xx_softc */ }xx_softc[NXX];
.
.
.
xxcattach(ctlr) struct controller *ctlr; { register struct xx_softc *sc = &xx_softc[ctlr->ctlr_num]; /* Tasks to perform controller-specific initialization */
.
.
.
simple_lock_init(&sc->lk_xx_softc);
.
.
.
}
.
.
.
xxopen(dev, flag, format) dev_t dev; int flag; int format; register int unit = minor(dev); struct controller *ctlr = xxinfo[unit]; struct xx_softc *sc = &xx_softc[unit]; if(unit >= NXX) return ENODEV; [1] simple_lock(&sc->lk_xx_softc); [2] if (sc->sc_openf == DN_OPEN) {
.
.
.
}
The simple_lock interface takes one argument: a pointer to a simple lock structure. You can declare this simple lock structure by using the decl_simple_lock_data interface. In this call, the xxopen interface passes the address of the lk_xx_softc member of the xx_softc structure pointer.
Figure 3-1 shows what happens when two instances of the /dev/xx driver execute on two CPUs. As the figure shows, the kernel thread emanating from CPU1 obtains the simple lock on the code block that follows item 2 in the code fragment before the kernel thread emanating from CPU2. The reason for locking this code block is to prevent data corruption of any future writes to the xx_softc structure. The CPU2 kernel thread spins while waiting for the CPU1 kernel thread to free the simple lock. [Return to example]
After asserting a simple lock (with exclusive access), you must release the lock by calling the simple_unlock interface. The following code fragment shows calls to simple_unlock by the /dev/xx device driver's xxopen interface. The xxopen interface is called as the result of an open system call.
The xxopen interface performs the following tasks:
The code fragment also shows the declaration of the simple lock structure in the xx_softc structure and the initialization of the simple lock structure by the driver's xxcattach interface.
.
.
.
#include <kern/lock.h>
.
.
.
struct xx_softc { int sc_openf; /* Open flag */ int sc_count; /* Count of characters written to device */ simple_lock_data_t lk_xx_softc; /* SMP lock for xx_softc */ }xx_softc[NXX];
.
.
.
xxcattach(ctlr) struct controller *ctlr; { register struct xx_softc *sc = &xx_softc[ctlr->ctlr_num]; /* Tasks to perform controller-specific initialization */
.
.
.
simple_lock_init(&sc->lk_xx_softc);
.
.
.
}
.
.
.
xxopen(dev, flag, format) dev_t dev; int flag; int format; register int unit = minor(dev); struct controller *ctlr = xxinfo[unit]; struct xx_softc *sc = &xx_softc[unit]; if(unit >= NXX) return ENODEV; [1] simple_lock(&sc->lk_xx_softc); [2] if (sc->sc_openf == DN_OPEN) [3] { simple_unlock(&sc->lk_xx_softc); return (EBUSY); } if ((ctlr !=0) && (ctlr->alive & ALV_ALIVE)) [4] { sc->sc_openf = DN_OPEN; simple_unlock(&sc->lk_xx_softc); return(0); } else [5] { simple_unlock(&sc->lk_xx_softc); return(ENXIO); } }
.
.
.
The simple_lock interface takes one argument: a pointer to a simple lock structure. You can declare this simple lock structure by using the decl_simple_lock_data interface. In this call, the xxopen interface passes the address of the lk_xx_softc member of the xx_softc structure pointer. [Return to example]
The simple_unlock interface releases a simple lock for the resource associated with the specified simple lock structure pointer. This simple lock was previously asserted by calling the simple_lock or simple_lock_try interface. In this call, the locked resource is referenced in the code block beginning with item 3.
The simple_unlock interface takes one argument: a pointer to a simple lock structure. You can declare this simple lock structure by using the decl_simple_lock_data interface. In this call, the xxopen interface passes the address of the lk_xx_softc member of the xx_softc structure pointer. [Return to example]
Figure 3-2 shows what happens when one instance of the /dev/xx driver releases a previously asserted exclusive lock on the code block that opens the device. As the figure shows, the CPU1 kernel thread releases the simple lock on the code block that opens the device. The CPU2 kernel thread, which was spinning while waiting for the simple lock to be freed, now obtains the simple lock. Furthermore, the figure shows that the CPU1 kernel thread makes another attempt to lock the code block that opens the device. This time it spins while waiting for the CPU2 kernel thread to free the simple lock. [Return to example]
In addition to explicitly asserting a simple lock, you can also try to assert the simple lock by calling the simple_lock_try interface. The main difference between simple_lock and simple_lock_try is that simple_lock_try returns immediately if the resource is already locked while simple_lock spins until the lock has been obtained. Thus, call simple_lock_try when you need a simple lock but the code cannot spin until the lock is obtained.
The following code fragment shows a call to simple_lock_try by the /dev/xx device driver's xxopen interface. The xxopen interface is called as the result of an open system call.
The xxopen interface performs the following tasks:
The code fragment also shows the declaration of the simple lock structure in the xx_softc structure and the initialization of the simple lock structure by the driver's xxcattach interface.
.
.
.
#include <kern/lock.h>
.
.
.
struct xx_softc { int sc_openf; /* Open flag */ int sc_count; /* Count of characters written to device */ simple_lock_data_t lk_xx_softc; /* SMP lock for xx_softc */ }xx_softc[NXX];
.
.
.
xxcattach(ctlr) struct controller *ctlr; { register struct xx_softc *sc = &xx_softc[ctlr->ctlr_num]; /* Tasks to perform controller-specific initialization */
.
.
.
simple_lock_init(&sc->lk_xx_softc);
.
.
.
}
.
.
.
xxopen(dev, flag, format) dev_t dev; int flag; int format; register int unit = minor(dev); struct controller *ctlr = xxinfo[unit]; struct xx_softc *sc = &xx_softc[unit]; boolean_t try_ret_val; [1] if(unit >= NXX) return ENODEV; [2] try_ret_val = simple_lock_try(&sc->lk_xx_softc); [3] if (try_ret_val == TRUE) [4] { if (sc->sc_openf == DN_OPEN)
.
.
.
else /* Perform some other tasks if simple_lock_try fails * * to assert an exclusive access */
.
.
.
}
Value | Meaning |
TRUE | The simple_lock_try interface successfully asserted the simple lock. |
FALSE | The simple_lock_try interface failed to assert the simple lock. |
The simple_lock_try interface takes one argument: a pointer to a simple lock structure. You can declare this simple lock structure by using the decl_simple_lock_data interface. In this call, the xxopen interface passes the address of the lk_xx_softc member of the xx_softc structure pointer. [Return to example]
Figure 3-3 shows what happens when two instances of the /dev/xx driver try to assert an exclusive lock on the code block that opens the device. As the figure shows, the CPU1 and CPU2 kernel threads try to assert an exclusive lock on the code block that opens the device. In this case, the CPU1 kernel thread successfully obtains the lock. To indicate this success, simple_lock_try returns the value TRUE. At the same time, the CPU2 kernel thread fails to obtain the lock and simple_lock_try immediately returns the value FALSE to indicate this. [Return to example]
After unlocking a simple lock (with exclusive access) and knowing that you are finished using the lock for this resource, you can terminate the lock by calling the simple_lock_terminate interface. Typically, you terminate any locks in the driver's controller (or device) unattach interface. These interfaces are associated with loadable drivers. One task associated with a controller or device unattach interface is to terminate any locks initialized in the driver's unattach interface.
The following code fragment shows a call to simple_lock_terminate by the /dev/xx device driver's xx_ctlr_unattach interface. The code fragment also shows the declaration of the simple lock structure in the xx_softc structure, the initialization of the simple lock structure by the driver's xxcattach interface, and the unlocking of the simple lock structure by the driver's xxopen interface.
.
.
.
#include <kern/lock.h> [1]
.
.
.
struct xx_softc { int sc_openf; /* Open flag */ int sc_count; /* Count of characters written to device */ simple_lock_data_t lk_xx_softc; /* SMP lock for xx_softc */ }xx_softc[NXX]; [2]
.
.
.
xxcattach(ctlr) struct controller *ctlr; { register struct xx_softc *sc = &xx_softc[ctlr->ctlr_num]; /* Tasks to perform controller-specific initialization */
.
.
.
simple_lock_init(&sc->lk_xx_softc); [3]
.
.
.
}
.
.
.
xxopen(dev, flag, format) dev_t dev; int flag; int format; register int unit = minor(dev); struct controller *ctlr = xxinfo[unit]; struct xx_softc *sc = &xx_softc[unit]; if(unit >= NXX) return ENODEV; [4] simple_lock(&sc->lk_xx_softc); [5] if (sc->sc_openf == DN_OPEN) [6] { simple_unlock(&sc->lk_xx_softc); return (EBUSY); } if ((ctlr !=0) && (ctlr->alive & ALV_ALIVE)) [7] { sc->sc_openf = DN_OPEN; simple_unlock(&sc->lk_xx_softc); return(0); } else [8] { simple_unlock(&sc->lk_xx_softc); return(ENXIO); } }
.
.
.
xx_ctlr_unattach(bus, ctlr) struct bus *bus; struct controller *ctlr; { register int unit = ctlr->ctlr_num; if ((unit > num_xx) || (unit < 0) { return(1); } if (xx_is_dynamic == 0) { return(1); } /* Performs controller unattach tasks */
.
.
.
simple_lock_terminate(&sc->lk_xx_softc); [9]
The simple_lock_init interface takes one argument: a pointer to a simple lock structure. You can declare this simple lock structure by using the decl_simple_lock_data interface. In this call, the xxcattach interface passes the address of the lk_xx_softc member of the xx_softc structure pointer. You need to initialize the simple lock structure only once. After initializing a simple lock structure, device drivers can call simple_lock to assert exclusive access on the associated resource or simple_lock_try to attempt to assert exclusive access on the associated resource. [Return to example]
The simple_lock interface takes one argument: a pointer to a simple lock structure. You can declare this simple lock structure by using the decl_simple_lock_data interface. In this call, the xxopen interface passes the address of the lk_xx_softc member of the xx_softc structure pointer. [Return to example]
The simple_unlock interface releases a simple lock for the resource associated with the specified simple lock structure pointer. This simple lock was previously asserted by calling the simple_lock or simple_lock_try interface. In this call, the locked resource is referenced in the code block beginning with item 6.
The simple_unlock interface takes one argument: a pointer to a simple lock structure. You can declare this simple lock structure by using the decl_simple_lock_data interface. In this call, the xxopen interface passes the address of the lk_xx_softc member of the xx_softc structure pointer. [Return to example]
The simple_lock_terminate interface takes one argument: a pointer to a simple lock structure. You can declare this simple lock structure by using the decl_simple_lock_data interface. In this call, the xx_ctlr_unattach interface passes the address of the lk_xx_softc member of the xx_softc structure pointer. In calling simple_lock_terminate, the /dev/xx driver must not reference this simple lock again. [Return to example]
The spl interfaces block out asynchronous events on the CPU that the spl call is performed on. Simple locks block out other CPUs. You need to use both the spl interfaces and the simple lock interfaces when synchronizing with kernel threads and interrupt service interfaces. The following code fragment shows calls to the spl and simple lock interfaces:
.
.
.
#include <kern/lock.h> [1]
.
.
.
struct tty_softc {
.
.
.
decl_simple_lock_data( , lk_tty_softc); /* SMP lock for tty_softc */
.
.
.
}tty_softc[NSOMEDEVICE]; [2]
.
.
.
simple_lock_init(&sc->lk_tty_softc);
.
.
.
s = spltty(); [3] simple_lock(&lk_tty_softc); [4]
.
.
.
/* Manipulate resource */
.
.
.
simple_unlock(&lk_tty_softc); [5] splx(s); [6]
.
.
.
The splx interface takes one argument: a CPU priority level. This level must be a value returned by a previous call to one of the spl interfaces, in this example spltty. Calling splx releases the priority on this CPU. [Return to example]