Follow Techotopia on Twitter

On-line Guides
All Guides
eBook Store
iOS / Android
Linux for Beginners
Office Productivity
Linux Installation
Linux Security
Linux Utilities
Linux Virtualization
Linux Kernel
System/Network Admin
Scripting Languages
Development Tools
Web Development
GUI Toolkits/Desktop
Mail Systems
Eclipse Documentation

How To Guides
General System Admin
Linux Security
Linux Filesystems
Web Servers
Graphics & Desktop
PC Hardware
Problem Solutions
Privacy Policy




Writing Device Drivers
Previous Next

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:

  1. 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.

  2. 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.

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

  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.

    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);
            return (0);
        /* validate the transfer request */
        if ((bp->b_blkno >= xsp->Nblocks) || (bp->b_blkno < 0)) {
            bioerror(bp, EINVAL);    
            return (0);
         * Hold off all threads until the device is not busy.
        while (xsp->busy) {
            cv_wait(&xsp->cv, &xsp->mu);
        xsp->busy = 1;
         * 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, &regp->dma_addr,
        ddi_put32(xsp->data_access_handle, &regp->dma_size,
        ddi_put8(xsp->data_access_handle, &regp->csr,
        return (0);
  5. 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;
    status = ddi_get8(xsp->data_access_handle, &xsp->regp->csr);
    if (!(status & INTERRUPTING)) {
        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,
    /* The transfer has finished, successfully or not */
     * 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;
    return (DDI_INTR_CLAIMED);
Previous Next

  Published under the terms fo the Public Documentation License Version 1.01. Design by Interspire