Document Information
Preface
Part I Designing Device Drivers for the Solaris Platform
1. Overview of Solaris Device Drivers
2. Solaris Kernel and Device Tree
3. Multithreading
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 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
Block Driver Structure Overview
File I/O
Block Device Autoconfiguration
Controlling Device Access
Asynchronous Data Transfers (Block Drivers)
dump() and print() Entry Points
Disk Device Drivers
17. SCSI Target Drivers
18. SCSI Host Bus Adapter Drivers
19. Drivers for Network Devices
20. USB Drivers
Part III Building a Device Driver
21. Compiling, Loading, Packaging, and Testing Drivers
22. Debugging, Testing, and Tuning Device Drivers
23. Recommended Coding Practices
Part IV Appendixes
A. Hardware Overview
B. Summary of Solaris DDI/DKI Services
C. Making a Device Driver 64-Bit Ready
D. Console Frame Buffer Drivers
Index
|
Synchronous Data Transfers (Block Drivers)
This section presents a simple method for performing synchronous I/O transfers. This method assumes
that the hardware is a simple disk device that can transfer only
one data buffer at a time by using DMA. Another assumption is that
the disk can be spun up and spun down by software command. The
device driver's strategy(9E) routine waits for the current request to be completed
before accepting a new request. The device interrupts when the transfer is complete.
The device also interrupts if an error occurs. The steps for performing a synchronous data transfer for a block driver are
as follows:
Check for invalid buf(9S) requests. Check the buf(9S) structure that is passed to strategy(9E) for validity. All drivers should check the following conditions:
The request begins at a valid block. The driver converts the b_blkno field to the correct device offset and then determines whether the offset is valid for the device.
The request does not go beyond the last block on the device.
Device-specific requirements are met.
If an error is encountered, the driver should indicate the appropriate error with bioerror(9F). The driver should then complete the request by calling biodone(9F). biodone() notifies the caller of strategy(9E) that the transfer is complete. In this case, the transfer has stopped because of an error.
Check whether the device is busy. Synchronous data transfers allow single-threaded access to the device. The device driver enforces this access in two ways:
The driver maintains a busy flag that is guarded by a mutex.
The driver waits on a condition variable with cv_wait(9F), when the device is busy.
If the device is busy, the thread waits until the interrupt handler indicates that the device is not longer busy. The available status can be indicated by either the cv_broadcast(9F) or the cv_signal(9F) function. See Chapter 3, Multithreading for details on condition variables. When the device is no longer busy, the strategy(9E) routine marks the device as available. strategy() then prepares the buffer and the device for the transfer.
Set up the buffer for DMA. Prepare the data buffer for a DMA transfer by using ddi_dma_alloc_handle(9F) to allocate a DMA handle. Use ddi_dma_buf_bind_handle(9F) to bind the data buffer to the handle. For information on setting up DMA resources and related data structures, see Chapter 9, Direct Memory Access (DMA).
Begin the transfer. At this point, a pointer to the buf(9S) structure is saved in the state structure of the device. The interrupt routine can then complete the transfer by calling biodone(9F). The device driver then accesses device registers to initiate a data transfer. In most cases, the driver should protect the device registers from other threads by using mutexes. In this case, because strategy(9E) is single-threaded, guarding the device registers is not necessary. See Chapter 3, Multithreading for details about data locks. When the executing thread has started the device's DMA engine, the driver can return execution control to the calling routine, as follows: static int
xxstrategy(struct buf *bp)
{
struct xxstate *xsp;
struct device_reg *regp;
minor_t instance;
ddi_dma_cookie_t cookie;
instance = getminor(bp->b_edev);
xsp = ddi_get_soft_state(statep, instance);
if (xsp == NULL) {
bioerror(bp, ENXIO);
biodone(bp);
return (0);
}
/* validate the transfer request */
if ((bp->b_blkno >= xsp->Nblocks) || (bp->b_blkno < 0)) {
bioerror(bp, EINVAL);
biodone(bp);
return (0);
}
/*
* Hold off all threads until the device is not busy.
*/
mutex_enter(&xsp->mu);
while (xsp->busy) {
cv_wait(&xsp->cv, &xsp->mu);
}
xsp->busy = 1;
mutex_exit(&xsp->mu);
/*
* If the device has power manageable components,
* mark the device busy with pm_busy_components(9F),
* and then ensure that the device
* is powered up by calling pm_raise_power(9F).
*
* Set up DMA resources with ddi_dma_alloc_handle(9F) and
* ddi_dma_buf_bind_handle(9F).
*/
xsp->bp = bp;
regp = xsp->regp;
ddi_put32(xsp->data_access_handle, ®p->dma_addr,
cookie.dmac_address);
ddi_put32(xsp->data_access_handle, ®p->dma_size,
(uint32_t)cookie.dmac_size);
ddi_put8(xsp->data_access_handle, ®p->csr,
ENABLE_INTERRUPTS | START_TRANSFER);
return (0);
}
Handle the interrupting device. When the device finishes the data transfer, the driver generates an interrupt, which eventually results in the driver's interrupt routine being called. Most drivers specify the state structure of the device as the argument to the interrupt routine when registering interrupts. See the ddi_add_intr(9F) man page and Registering Interrupts. The interrupt routine can then access the buf(9S) structure being transferred, plus any other information that is available from the state structure. The interrupt handler should check the device's status register to determine whether the transfer completed without error. If an error occurred, the handler should indicate the appropriate error with bioerror(9F). The handler should also clear the pending interrupt for the device and then complete the transfer by calling biodone(9F). As the final task, the handler clears the busy flag. The handler then calls cv_signal(9F) or cv_broadcast(9F) on the condition variable, signaling that the device is no longer busy. This notification enables other threads waiting for the device in strategy(9E) to proceed with the next data transfer. The following example shows a synchronous interrupt routine.
Example 16-4 Synchronous Interrupt Routine for Block Driversstatic u_int
xxintr(caddr_t arg)
{
struct xxstate *xsp = (struct xxstate *)arg;
struct buf *bp;
uint8_t status;
mutex_enter(&xsp->mu);
status = ddi_get8(xsp->data_access_handle, &xsp->regp->csr);
if (!(status & INTERRUPTING)) {
mutex_exit(&xsp->mu);
return (DDI_INTR_UNCLAIMED);
}
/* Get the buf responsible for this interrupt */
bp = xsp->bp;
xsp->bp = NULL;
/*
* This example is for a simple device which either
* succeeds or fails the data transfer, indicated in the
* command/status register.
*/
if (status & DEVICE_ERROR) {
/* failure */
bp->b_resid = bp->b_bcount;
bioerror(bp, EIO);
} else {
/* success */
bp->b_resid = 0;
}
ddi_put8(xsp->data_access_handle, &xsp->regp->csr,
CLEAR_INTERRUPT);
/* The transfer has finished, successfully or not */
biodone(bp);
/*
* If the device has power manageable components that were
* marked busy in strategy(9F), mark them idle now with
* pm_idle_component(9F)
* Release any resources used in the transfer, such as DMA
* resources ddi_dma_unbind_handle(9F) and
* ddi_dma_free_handle(9F).
*
* Let the next I/O thread have access to the device.
*/
xsp->busy = 0;
cv_signal(&xsp->cv);
mutex_exit(&xsp->mu);
return (DDI_INTR_CLAIMED);
}
|