Sun Microsystems, Inc.
spacerspacer
spacer www.sun.com docs.sun.com |
spacer
black dot
 
 
11.  Drivers for Block Devices Controlling Device Access buf Structure  Previous   Contents   Next 
   
 
av_forw and av_back

Pointers that the driver can use to manage a list of buffers by the driver. See "Asynchronous Data Transfers (Block Drivers)" for a discussion of the av_forw and av_back pointers.

b_bcount

Specifies the number of bytes to be transferred by the device.

b_un.b_addr

The kernel virtual address of the data buffer. Only valid after bp_mapin(9F) call.

b_blkno

The starting 32-bit logical block number on the device for the data transfer, expressed in DEV_BSIZE (512 bytes) units. The driver should use either b_blkno or b_lblkno, but not both.

b_lblkno

The starting 64-bit logical block number on the device for the data transfer, expressed in DEV_BSIZE (512 bytes) units. The driver should use either b_blkno or b_lblkno, but not both.

b_resid

Set by the driver to indicate the number of bytes that were not transferred because of an error. See Example 11-8 for an example of setting b_resid. The b_resid member is overloaded: it is also used by disksort(9F).

b_error

Set to an error number by the driver when a transfer error occurs. It is set in conjunction with the b_flags B_ERROR bit. See Intro(9E) for details regarding error values. Drivers should use bioerror(9F) rather than setting b_error directly.

b_private

For exclusive use by the driver to store driver-private data.

b_edev

Contains the device number of the device involved in the transfer.

bp_mapin Structure

When a buf structure pointer is passed into the device driver's strategy(9E) routine, the data buffer referred to by b_un.b_addr is not necessarily mapped in the kernel's address space. This means that the driver cannot directly access the data. Most block-oriented devices have DMA capability, and therefore do not need to access the data buffer directly. Instead, they use the DMA mapping routines to allow the device's DMA engine to do the data transfer. For details about using DMA, see Chapter 8, Direct Memory Access (DMA).

If a driver needs to directly access the data buffer (as opposed to having the device access the data), it must first map the buffer into the kernel's address space using bp_mapin(9F). bp_mapout(9F) should be used when the driver no longer needs to access the data directly.


Caution - bp_mapout(9F) should only be called on buffers that have been allocated and are owned by the device driver. It must not be called on buffers passed to the driver through the strategy(9E) entry point (for example a file system). Because bp_mapin(9F) does not keep a reference count, bp_mapout(9F) will remove any kernel mapping that a layer above the device driver might rely on.


Synchronous Data Transfers (Block Drivers)

This section presents a simple method for performing synchronous I/O transfers. It assumes that the hardware is a simple disk device that can transfer only one data buffer at a time using DMA, and 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 one. The device interrupts when the transfer is complete or when an error occurs.

  1. Check for invalid buf(9S) requests.

    Check the buf(9S) structure passed to strategy(9E) for validity. All drivers should check that:

    • The request begins at a valid block. The driver converts the b_blkno field to the correct device offset and then determines if 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) and complete the request by calling biodone(9F). biodone(9F) notifies the caller of strategy(9E) that the transfer is complete (in this case, because of an error).

  2. Check whether the device is busy.

    Synchronous data transfers allow single-threaded access to the device. The device driver enforces this by maintaining a busy flag (guarded by a mutex), and by waiting on a condition variable with cv_wait(9F) when the device is busy.

    If the device is busy, the thread waits until a cv_broadcast(9F) or cv_signal(9F) from the interrupt handler indicates that the device is no longer busy. See Chapter 3, Multithreading for details on condition variables.

    When the device is no longer busy, the strategy(9E) routine marks it as busy and prepares the buffer and the device for the transfer.

  3. Set up the buffer for DMA.

    Prepare the data buffer for a DMA transfer by allocating a DMA handle using ddi_dma_alloc_handle(9F) and binding the data buffer to the handle using ddi_dma_buf_bind_handle(9F). See Chapter 8, Direct Memory Access (DMA) for information on setting up DMA resources and related data structures.

  4. 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.)

    Once the executing thread has started the device's DMA engine, the driver can return execution control to the calling routine, as shown in Example 11-4:


    Example 11-4 Synchronous Block Driver strategy(9E) Routine

    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 (see Chapter 9, Power Management),
        mark the device busy with pm_busy_components(9F),
    and then ensure that the device 
        is powered up by calling ddi_dev_is_needed(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, &regp->dma_addr,
                cookie.dmac_address);
        ddi_put32(xsp->data_access_handle, &regp->dma_size,
                 (uint32_t)cookie.dmac_size);
        ddi_put8(xsp->data_access_handle, &regp->csr,
                 ENABLE_INTERRUPTS | START_TRANSFER);
        return (0);
    }

  5. Handle the interrupting device.

    When the device finishes the data transfer it 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 available from the state structure.

    The interrupt handler should check the device's status register to determine if 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 and calls cv_signal(9F) or cv_broadcast(9F) on the condition variable, signaling that the device is no longer busy. This allows other threads waiting for the device (in strategy(9E)) to proceed with the next data transfer.


    Example 11-5 Synchronous Block Driver Interrupt Routine

    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);
    }

 
 
 
  Previous   Contents   Next