JavaScript is required to for searching.
Skip Navigation Links
Exit Print View
Writing Device Drivers     Oracle Solaris 11.1 Information Library
search filter icon
search icon

Document Information

Preface

Part I Designing Device Drivers for the Oracle Solaris Platform

1.  Overview of Oracle Solaris Device Drivers

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

open() Entry Point (Block Drivers)

close() Entry Point (Block Drivers)

strategy() Entry Point

buf Structure

bp_mapin Structure

Synchronous Data Transfers (Block Drivers)

Asynchronous Data Transfers (Block Drivers)

Checking for Invalid buf Requests

Enqueuing the Request

Starting the First Transfer

Handling the Interrupting Device

dump() and print() Entry Points

dump() Entry Point (Block Drivers)

print() Entry Point (Block Drivers)

Disk Device Drivers

Disk ioctls

Disk Performance

17.  SCSI Target Drivers

18.  SCSI Host Bus Adapter Drivers

19.  Drivers for Network Devices

20.  USB Drivers

21.  SR-IOV Drivers

Part III Building a Device Driver

22.  Compiling, Loading, Packaging, and Testing Drivers

23.  Debugging, Testing, and Tuning Device Drivers

24.  Recommended Coding Practices

Part IV Appendixes

A.  Hardware Overview

B.  Summary of Oracle Solaris DDI/DKI Services

C.  Making a Device Driver 64-Bit Ready

D.  Console Frame Buffer Drivers

E.  pci.conf File

Index

Controlling Device Access

This section describes the entry points for open() and close() functions in block device drivers. See Chapter 15, Drivers for Character Devices for more information on open(9E) and close(9E).

open() Entry Point (Block Drivers)

The open(9E) entry point is used to gain access to a given device. The open(9E) routine of a block driver is called when a user thread issues an open(2) or mount(2) system call on a block special file associated with the minor device, or when a layered driver calls open(9E). See File I/O for more information.

The open() entry point should check for the following conditions:

The following example demonstrates a block driver open(9E) entry point.

Example 16-2 Block Driver open(9E) Routine

static int
xxopen(dev_t *devp, int flags, int otyp, cred_t *credp)
{
    minor_t         instance;
    struct xxstate        *xsp;

    instance = getminor(*devp);
    xsp = ddi_get_soft_state(statep, instance);
    if (xsp == NULL)
        return (ENXIO);
    mutex_enter(&xsp->mu);
    /*
     * only honor FEXCL. If a regular open or a layered open
     * is still outstanding on the device, the exclusive open
     * must fail.
     */
    if ((flags & FEXCL) && (xsp->open || xsp->nlayered)) {
        mutex_exit(&xsp->mu);
        return (EAGAIN);
    }
    switch (otyp) {
      case OTYP_LYR:
          xsp->nlayered++;
          break;
      case OTYP_BLK:
          xsp->open = 1;
          break;
      default:
          mutex_exit(&xsp->mu);
          return (EINVAL);
    }
    mutex_exit(&xsp->mu);
    return (0);
}

The otyp argument is used to specify the type of open on the device. OTYP_BLK is the typical open type for a block device. A device can be opened several times with otyp set to OTYP_BLK. close(9E) is called only once when the final close of type OTYP_BLK has occurred for the device. otyp is set to OTYP_LYR if the device is being used as a layered device. For every open of type OTYP_LYR, the layering driver issues a corresponding close of type OTYP_LYR. The example keeps track of each type of open so the driver can determine when the device is not being used in close(9E).

close() Entry Point (Block Drivers)

The close(9E) entry point uses the same arguments as open(9E) with one exception. dev is the device number rather than a pointer to the device number.

The close() routine should verify otyp in the same way as was described for the open(9E) entry point. In the following example, close() must determine when the device can really be closed. Closing is affected by the number of block opens and layered opens.

Example 16-3 Block Device close(9E) Routine

static int
xxclose(dev_t dev, int flag, int otyp, cred_t *credp)
{
    minor_t instance;
    struct xxstate *xsp;

    instance = getminor(dev);
    xsp = ddi_get_soft_state(statep, instance);
    if (xsp == NULL)
        return (ENXIO);
    mutex_enter(&xsp->mu);
    switch (otyp) {
      case OTYP_LYR:
          xsp->nlayered--;
          break;
      case OTYP_BLK:
          xsp->open = 0;
          break;
      default:
          mutex_exit(&xsp->mu);
         return (EINVAL);
    }

    if (xsp->open || xsp->nlayered) {
        /* not done yet */
        mutex_exit(&xsp->mu);
        return (0);
    }
    /* cleanup (rewind tape, free memory, etc.) */
    /* wait for I/O to drain */
    mutex_exit(&xsp->mu);

    return (0);
}

strategy() Entry Point

The strategy(9E) entry point is used to read and write data buffers to and from a block device. The name strategy refers to the fact that this entry point might implement some optimal strategy for ordering requests to the device.

strategy(9E) can be written to process one request at a time, that is, a synchronous transfer. strategy() can also be written to queue multiple requests to the device, as in an asynchronous transfer. When choosing a method, the abilities and limitations of the device should be taken into account.

The strategy(9E) routine is passed a pointer to a buf(9S) structure. This structure describes the transfer request, and contains status information on return. buf(9S) and strategy(9E) are the focus of block device operations.

buf Structure

The following buf structure members are important to block drivers:

int           b_flags;       /* Buffer status */
struct buf    *av_forw;      /* Driver work list link */
struct buf    *av_back;      /* Driver work list link */
size_t        b_bcount;      /* # of bytes to transfer */
union {
    caddr_t   b_addr;        /* Buffer's virtual address */
} b_un;
daddr_t       b_blkno;       /* Block number on device */
diskaddr_t    b_lblkno;      /* Expanded block number on device */
size_t        b_resid;       /* # of bytes not transferred after error */
int           b_error;       /* Expanded error field */
void          *b_private;    /* "opaque" driver private area */
dev_t         b_edev;        /* expanded dev field */

where:

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, which is expressed in 512-byte DEV_BSIZE 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, which is expressed in 512-byte DEV_BSIZE 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 16-7 for an example of setting b_resid. The b_resid member is overloaded. b_resid is also used by disksort(9F).

b_error

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

b_flags

Flags with status and transfer attributes of the buf structure. If B_READ is set, the buf structure indicates a transfer from the device to memory. Otherwise, this structure indicates a transfer from memory to the device. If the driver encounters an error during data transfer, the driver should set the B_ERROR field in the b_flags member. In addition, the driver should provide a more specific error value in b_error. Drivers should use bioerror(9F) rather than setting B_ERROR.


Caution

Caution - Drivers should never clear b_flags.


b_private

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

b_edev

Contains the device number of the device that was used in the transfer.

bp_mapin Structure

A buf structure pointer can be passed into the device driver's strategy(9E) routine. However, the data buffer referred to by b_un.b_addr is not necessarily mapped in the kernel's address space. Therefore, 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, these devices use the DMA mapping routines to enable the device's DMA engine to do the data transfer. For details about using DMA, see Chapter 9, Direct Memory Access (DMA).

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


Caution

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