Contained Within
Find More Documentation
Featured Support Resources
|
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 Drivers
static 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);
}
|