编写设备驱动程序

第 16 章 块设备驱动程序

本章介绍块设备驱动程序的结构。内核将块设备视为一组可随机访问的逻辑块。文件系统使用一系列 buf(9S) 结构来缓存块设备和用户空间之间的数据块。只有块设备可以支持文件系统。

本章介绍有关以下主题的信息:

块驱动程序结构概述

图 16–1 显示了用来定义块设备驱动程序结构的数据结构和例程。设备驱动程序通常包括以下元素:

下图中涂有阴影的设备访问部分列出了块驱动程序入口点。

图 16–1 块驱动程序结构图

图显示了块设备驱动程序的结构和入口点。

对于每个设备驱动程序,都有一个 dev_ops(9S) 结构与之关联,该结构进而又引用 cb_ops(9S) 结构。有关驱动程序数据结构的详细信息,请参见第 6 章

块设备驱动程序提供下列入口点:


注 –

可根据需要将其中一些入口点替换为 nodev(9F)nulldev(9F)


文件 I/O

文件系统是由目录和文件组成的树状分层结构。诸如 UNIX 文件系统 (UNIX File System, UFS) 之类的一些文件系统驻留在面向块的设备上。文件系统由 format(1M)newfs(1M) 创建。

当应用程序向 UFS 文件系统上的普通文件发出 read(2)write(2) 系统调用时,该文件系统可为所驻留在的块设备调用设备驱动程序 strategy(9E) 入口点。对于一个 read(2)write(2) 系统调用,文件系统代码可多次调用 strategy(9E)

文件系统代码为每个普通文件块确定逻辑设备地址,即逻辑块编号。然后采用指向块设备的 buf(9S) 结构的形式建立块 I/O 请求。驱动程序 strategy(9E) 入口点随后解释 buf(9S) 结构并完成请求。

块设备自动配置

attach(9E) 会为每个设备实例执行常见初始化任务:

块设备驱动程序创建类型为 S_IFBLK 的次要节点。因此,代表节点的块特殊文件会出现在 /devices 分层结构中。

块设备的逻辑设备名称位于 /dev/dsk 目录中,该名称由控制器编号、总线地址编号、磁盘编号和片编号组成。如果节点类型设置为 DDI_NT_BLOCKDDI_NT_BLOCK_CHAN,则这些名称由 devfsadm(1M) 程序创建。如果设备通过通道(即有附加寻址能力级别的总线)进行通信,则应该指定 DDI_NT_BLOCK_CHAN。SCSI 磁盘就是一个典型示例。DDI_NT_BLOCK_CHAN 可使总线地址字段 (tN) 出现在逻辑名称中。其他大多数设备则应该使用 DDI_NT_BLOCK

次要设备指磁盘上的分区。对于每个次要设备,驱动程序必须创建 nblocksNblocks 属性。此整数属性给出了次要设备所支持的块数,以 DEV_BSIZE(即 512 字节)为单位。文件系统使用 nblocksNblocks 属性来确定设备限制。Nblocks 是 64 位版本的 nblocks。应该将 Nblocks 用于每个磁盘的存储容量超过 1 TB 的存储设备。有关更多信息,请参见设备属性

示例 16–1 说明了一个典型的 attach(9E) 入口点,重点说明如何创建设备的次要节点和 Nblocks 属性。请注意,由于此示例使用 Nblocks 而非 nblocks,因此将调用 ddi_prop_update_int64(9F) 而非 ddi_prop_update_int(9F)

作为附带说明,本示例还说明了如何使用 makedevice(9F)ddi_prop_update_int64() 创建设备编号。makedevice 函数利用 ddi_driver_major(9F),后者基于指向 dev_info_t 结构的指针生成主设备号。使用 ddi_driver_major() 与使用 getmajor(9F) 类似,后者用于获取 dev_t 结构指针。


示例 16–1 块驱动程序 attach() 例程

static int
xxattach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
     int instance = ddi_get_instance(dip);
     switch (cmd) {
       case DDI_ATTACH:
       /*
        * allocate a state structure and initialize it
        * map the devices registers
        * add the device driver's interrupt handler(s)
        * initialize any mutexes and condition variables
        * read label information if the device is a disk
        * create power manageable components
        *
        * Create the device minor node. Note that the node_type
        * argument is set to DDI_NT_BLOCK.
        */
       if (ddi_create_minor_node(dip, "minor_name", S_IFBLK,
          instance, DDI_NT_BLOCK, 0) == DDI_FAILURE) {
          /* free resources allocated so far */
          /* Remove any previously allocated minor nodes */
          ddi_remove_minor_node(dip, NULL);
          return (DDI_FAILURE);
        }
       /*
        * Create driver properties like "Nblocks". If the device
        * is a disk, the Nblocks property is usually calculated from
        * information in the disk label.  Use "Nblocks" instead of
        * "nblocks" to ensure the property works for large disks.
        */
       xsp->Nblocks = size;
       /* size is the size of the device in 512 byte blocks */
       maj_number = ddi_driver_major(dip);
       if (ddi_prop_update_int64(makedevice(maj_number, instance), dip, 
          "Nblocks", xsp->Nblocks) != DDI_PROP_SUCCESS) {
          cmn_err(CE_CONT, "%s: cannot create Nblocks property\n",
               ddi_get_name(dip));
         /* free resources allocated so far */
         return (DDI_FAILURE);
       }
       xsp->open = 0;
       xsp->nlayered = 0;
       /* ... */
       return (DDI_SUCCESS);

    case DDI_RESUME:
       /* For information, see Chapter 12, "Power Management," in this book. */
       default:
          return (DDI_FAILURE);
     }
}

控制设备访问

本节介绍块设备驱动程序中 open()close() 函数的入口点。有关 open(9E)close(9E) 的更多信息,请参见第 15 章

open() 入口点(块驱动程序)

open(9E) 入口点用于访问给定的设备。当用户线程对与次要设备相关的块特殊文件发出 open(2)mount(2) 系统调用时,或者当分层驱动程序调用 open(9E) 时,均会调用块驱动程序的 open(9E) 例程。有关更多信息,请参见文件 I/O

open() 入口点会检查下列条件:

以下示例说明了块驱动程序 open(9E) 入口点。


示例 16–2 块驱动程序 open(9E) 例程

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

otyp 参数用于指定设备的打开类型。OTYP_BLK 是块设备的典型打开类型。将 otyp 设置为 OTYP_BLK 后,可以多次打开设备。最后关闭设备的 OTYP_BLK 类型时,仅调用一次 close(9E)。如果将设备用作分层设备,则 otyp 设置为 OTYP_LYR。每次打开 OTYP_LYR 类型,分层驱动程序都会发出类型为 OTYP_LYR 的对应关闭。此示例跟踪每种类型的打开,因此驱动程序可以确定何时设备不用于 close(9E)

close() 入口点(块驱动程序)

close(9E) 入口点使用与 open(9E) 相同的参数,但有一个例外。dev 是设备编号,而不是指向设备编号的指针。

close() 例程应采用与前面所述的 open(9E) 入口点所采用的相同的方式来检验 otyp。在以下示例中,close() 必须确定何时可以真正关闭设备。关闭受块打开数和分层打开数的影响。


示例 16–3 块设备 close(9E) 例程

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() 入口点

strategy(9E) 入口点用于从块设备读取数据缓冲区以及向块设备写入数据缓冲区。名称 strategy 指的是该入口点可以实现一些优化策略以对设备请求进行排序。

可以将 strategy(9E) 编写为一次处理一个请求,即同步传输。也可以将 strategy() 编写为对发送给设备的多个请求进行排队,即异步传输。选择方法时,应当考虑设备的能力和限制。

将向 strategy(9E) 例程传递一个指向 buf(9S) 结构的指针。此结构描述传输请求,并包含有关返回的状态信息。buf(9S)strategy(9E) 是块设备操作的焦点。

buf 结构

以下 buf 结构成员对块驱动程序很重要:

     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 */

其中:

av_forwav_back

驱动程序可用以管理其使用的一组缓冲区的指针。有关 av_forwav_back 指针的讨论,请参见异步数据传输(块驱动程序)

b_bcount

指定要由设备传输的字节数。

b_un.b_addr

数据缓冲区的内核虚拟地址。仅在进行 bp_mapin(9F) 调用后有效。

b_blkno

设备上用于数据传输的起始 32 位逻辑块编号,以 DEV_BSIZE(512 字节)为单位。驱动程序应使用 b_blknob_lblkno,但不能同时使用两者。

b_lblkno

设备上用于数据传输的起始 64 位逻辑块编号,以 DEV_BSIZE(512 字节)为单位。驱动程序应使用 b_blknob_lblkno,但不能同时使用两者。

b_resid

由驱动程序设置的用于表明由于发生错误而未传输的字节数。有关设置 b_resid 的示例,请参见示例 16–7b_resid 成员会过载。此外,disksort(9F) 也会使用 b_resid

b_error

当发生传输错误时,由驱动程序设置为错误编号。b_error 应与 b_flags B_ERROR 位一起设置。有关错误值的详细信息,请参见 Intro(9E) 手册页。驱动程序应使用 bioerror(9F),而不是直接设置 b_error

b_flags

表示 buf 结构的状态属性和传输属性的标志。如果设置了 B_READ,则 buf 结构指明从设备到内存的传输。否则,此结构指明从内存到设备的传输。如果在数据传输期间驱动程序遇到错误,则该驱动程序应设置 b_flags 成员中的 B_ERROR 字段。此外,该驱动程序还应在 b_error 中提供一个更明确的错误值。驱动程序应使用 bioerror(9F),而不是设置 B_ERROR


注意 – 注意 –

驱动程序绝不能清除 b_flags


b_private

专供驱动程序存储驱动程序专用数据。

b_edev

包含用于传输的设备的设备编号。

bp_mapin 结构

可以将 buf 结构指针传递到设备驱动程序的 strategy(9E) 例程。但是,b_un.b_addr 引用的数据缓冲区不一定映射到内核地址空间中。因此,驱动程序无法直接访问数据。大多数面向块的设备具有 DMA 功能,因此不需要直接访问数据缓冲区。这些设备改为使用 DMA 映射例程以使设备的 DMA 引擎进行数据传输。有关使用 DMA 的详细信息,请参见第 9 章

如果驱动程序需要直接访问数据缓冲区,则该驱动程序必须首先使用 bp_mapin(9F) 将缓冲区映射到内核地址空间。当驱动程序不再需要直接访问数据时,应使用 bp_mapout(9F)


注意 – 注意 –

只应对已分配且由设备驱动程序拥有的缓冲区调用 bp_mapout(9F)。不得对通过 strategy(9E) 入口点传递到驱动程序的缓冲区(如文件系统)调用 bp_mapout()bp_mapin(9F) 不保留引用计数。bp_mapout(9F) 将删除设备驱动程序之上的层所依赖的任何内核映射。


同步数据传输(块驱动程序)

本节介绍一种执行同步 I/O 传输的简单方法。此方法假设硬件是使用 DMA 一次只能传输一个数据缓冲区的简单磁盘设备。另一个假设是磁盘可以通过软件命令启动和停止。设备驱动程序的 strategy(9E) 例程等待当前请求完成,然后再接受新的请求。当传输完成时,设备中断。如果发生错误,设备也中断。

执行块驱动程序的同步数据传输的步骤如下:

  1. 检查是否有无效的 buf(9S) 请求。

    检查传递到 strategy(9E)buf(9S) 结构是否有效。所有驱动程序应检查以下条件:

    • 请求起始于有效的块。驱动程序将 b_blkno 字段转换为正确的设备偏移,然后确定该偏移对设备而言是否有效。

    • 请求不能超出设备上的最后一个块。

    • 满足特定于设备的要求。

    如果遇到错误,驱动程序应使用 bioerror(9F) 指示相应的错误。然后,驱动程序通过调用 biodone(9F) 来完成请求。biodone() 会通知 strategy(9E) 的调用方传输已完成。在本例中,传输因错误而停止。

  2. 检查此设备是否忙。

    同步数据传输允许对设备进行单线程访问。设备驱动程序通过两种方式执行这种访问:

    • 驱动程序保持由互斥锁保护的忙标志。

    • 当设备忙时,驱动程序等待 cv_wait(9F) 的条件变量。

    如果设备忙,线程会一直等待,直到中断处理程序指示设备不再忙。cv_broadcast(9F)cv_signal(9F) 函数可以指示可用的状态。有关条件变量的详细信息,请参见第 3 章

    当设备不再忙时,strategy(9E) 例程将设备标记为可用。然后,strategy() 为传输准备缓冲区和设备。

  3. 为 DMA 设置缓冲区。

    通过使用 ddi_dma_alloc_handle(9F) 为 DMA 传送准备数据缓冲区,以便分配 DMA 句柄。使用 ddi_dma_buf_bind_handle(9F) 将数据缓冲区绑定到该句柄。有关设置 DMA 资源及相关数据结构的信息,请参见第 9 章

  4. 开始传输。

    此时,指向 buf(9S) 结构的指针将保存在设备的状态结构中。然后中断例程通过调用 biodone(9F) 来完成传输。

    设备驱动程序随后访问设备寄存器以启动数据传输。在大多数情况下,驱动程序应通过使用互斥锁来保护设备寄存器免受其他线程干扰。在本例中,由于 strategy(9E) 是单线程的,因此没有必要保护设备寄存器。有关数据锁定的详细信息,请参见第 3 章

    当执行线程已经启动设备的 DMA 引擎时,驱动程序可以将执行控制权返回到正在调用的例程,如下所示:

    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, &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. 处理中断的设备。

    当设备完成数据传输时,设备会生成中断,最终导致驱动程序的中断例程被调用。注册中断时,大多数驱动程序将设备的状态结构指定为中断例程的参数。请参见 ddi_add_intr(9F) 手册页和注册中断。随后,中断例程可以访问正在被传输的 buf(9S) 结构,以及状态结构提供的任何其他信息。

    中断处理程序会检查设备的状态寄存器,来确定是否在未发生任何错误的情况下完成传输。如果发生错误,处理程序应该使用 bioerror(9F) 指示相应的错误。处理程序还应该清除设备的挂起中断,然后通过调用 biodone(9F) 来完成传输。

    最后一项任务是处理程序清除忙标志。处理程序随后对条件变量调用 cv_signal(9F)cv_broadcast(9F),发出设备不再忙的信号。此通知会使在 strategy(9E) 中等待设备的其他线程继续进行下一个数据传输。

    以下示例说明了一个同步中断例程。


示例 16–4 块驱动程序的同步中断例程

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

异步数据传输(块驱动程序)

本节介绍一种执行异步 I/O 传输的方法。驱动程序将对 I/O 请求进行排队,然后将控制权返回到调用方。还是假设硬件是一次可以传输一个缓冲区的简单磁盘设备。当数据传输完成时,设备中断。如果发生错误,也会产生中断。执行异步数据传输的基本步骤如下:

  1. 检查是否有无效的 buf(9S) 请求。

  2. 对请求进行排队。

  3. 开始第一个传输。

  4. 处理中断的设备。

检查是否有无效的 buf 请求

正如同步传输示例中所示,设备驱动程序会检查传递到 strategy(9E)buf(9S) 结构是否有效。有关更多详细信息,请参见同步数据传输(块驱动程序)

对请求进行排队

与同步数据传输不同,驱动程序不等待异步请求完成,而是向队列中添加请求。队列的开头可以是当前传输,也可以是保留活动请求的状态结构中的独立字段,如示例 16–5 中所示。

如果队列开始是空的,那么硬件不忙,并且 strategy(9E) 在返回之前开始传输。否则,如果在队列非空的情况下完成一个传输,则中断例程会开始一个新的传输。为方便起见,示例 16–5 仅在一个单独的例程中决定是否开始新的传输。

驱动程序可以使用 buf(9S) 结构中的 av_forwav_back 成员来管理传输请求列表。可以使用单个指针管理单链接表,也可以同时使用两个指针建立双链接表。设备硬件规格指定哪种类型的列表管理(如插入策略)用于优化设备的性能。传输列表是按设备提供的列表,因此列表的头和尾都存储在状态结构中。

以下示例提供了对驱动程序共享数据(如传输列表)有访问权限的多个线程。您必须标识共享数据,并且必须用互斥锁保护这些数据。有关互斥锁的更多详细信息,请参见第 3 章


示例 16–5 对块驱动程序的数据传输请求进行排队

static int
xxstrategy(struct buf *bp)
{
    struct xxstate *xsp;
    minor_t instance;
    instance = getminor(bp->b_edev);
    xsp = ddi_get_soft_state(statep, instance);
    /* ... */
    /* validate transfer request */
    /* ... */
    /*
     * Add the request to the end of the queue. Depending on the device, a sorting
     * algorithm, such as disksort(9F) can be used if it improves the
     * performance of the device.
     */
    mutex_enter(&xsp->mu);
    bp->av_forw = NULL;
    if (xsp->list_head) {
       /* Non-empty transfer list */
       xsp->list_tail->av_forw = bp;
       xsp->list_tail = bp;
    } else {
       /* Empty Transfer list */
       xsp->list_head = bp;
       xsp->list_tail = bp;
    }
    mutex_exit(&xsp->mu);
    /* Start the transfer if possible */
    (void) xxstart((caddr_t)xsp);
    return (0);
}

开始第一个传输

可实现排队的设备驱动程序通常具有 start() 例程。start() 可将下一个请求从队列中删除,并开始出入设备的数据传输。在以下示例中,不管设备状态是忙还是空闲,start() 将处理所有请求。


注 –

必须写入 start() 以便从任何上下文都可以对其进行调用。内核上下文中的策略例程与中断上下文中的中断例程都可以调用 start()


每次 strategy() 对请求排队时,将由 strategy(9E) 调用 start(),以便可以启动空闲设备。如果设备忙,则 start() 立即返回。

在处理程序从声明的中断返回之前,也可以由中断处理程序调用 start(),以便可以为非空队列提供服务。如果队列是空的,则 start() 立即返回。

由于 start() 是一个专用驱动程序例程,因此 start() 可以采用任何参数并返回任何类型。将写入以下代码样例用作 DMA 回调,尽管没有显示那一部分。相应地,此示例必须采用 caddr_t 作为参数并返回 int。有关 DMA 回调例程的更多信息,请参见处理资源分配故障


示例 16–6 开始块驱动程序的第一个数据请求

static int
xxstart(caddr_t arg)
{
    struct xxstate *xsp = (struct xxstate *)arg;
    struct buf *bp;

    mutex_enter(&xsp->mu);
    /*
     * If there is nothing more to do, or the device is
     * busy, return.
     */
    if (xsp->list_head == NULL || xsp->busy) {
       mutex_exit(&xsp->mu);
       return (0);
    }
    xsp->busy = 1;
    /* Get the first buffer off the transfer list */
    bp = xsp->list_head;
    /* Update the head and tail pointer */
    xsp->list_head = xsp->list_head->av_forw;
    if (xsp->list_head == NULL)
       xsp->list_tail = NULL;
    bp->av_forw = NULL;
    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;
    ddi_put32(xsp->data_access_handle, &xsp->regp->dma_addr,
        cookie.dmac_address);
    ddi_put32(xsp->data_access_handle, &xsp->regp->dma_size,
         (uint32_t)cookie.dmac_size);
    ddi_put8(xsp->data_access_handle, &xsp->regp->csr,
         ENABLE_INTERRUPTS | START_TRANSFER);
    return (0);
}

处理中断的设备

中断例程与异步版本类似,只是增加了对 start() 的调用并删除了对 cv_signal(9F) 的调用。


示例 16–7 异步中断的块驱动程序例程

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;
    mutex_exit(&xsp->mu);
    (void) xxstart((caddr_t)xsp);
    return (DDI_INTR_CLAIMED);
}

dump()print() 入口点

本节讨论 dump(9E)print(9E) 入口点。

dump() 入口点(块驱动程序)

dump(9E) 入口点用于在系统发生故障时将虚拟地址空间的一部分直接复制到指定的设备。在检查点操作期间,还可以使用 dump() 将内核状态复制到磁盘。有关更多信息,请参见 cpr(7)dump(9E) 手册页。由于在检查点操作期间中断被禁用,因此该入口点必须能够在不使用中断的情况下执行此操作。

int dump(dev_t dev, caddr_t addr, daddr_t blkno, int nblk)

其中:

dev

接收转储的设备的设备编号。

addr

开始转储的基本内核虚拟地址。

blkno

开始转储的块。

nblk

转储的块的编号。

转储依赖于现有的驱动程序是否工作正常。

print() 入口点(块驱动程序)

int print(dev_t dev, char *str)

系统调用 print(9E) 入口点以显示有关已检测到的异常的消息。print(9E) 应调用 cmn_err(9F) 以代表系统将消息发布到控制台。以下示例说明了一个典型的 print() 入口点。

static int
 xxprint(dev_t dev, char *str)
 {
     cmn_err(CE_CONT, “xx: %s\n”, str);
     return (0);
 }

磁盘设备驱动程序

磁盘设备是一类重要的块设备驱动程序。

磁盘 ioctl

Solaris 磁盘驱动程序至少需要支持一组特定于 Solaris 磁盘驱动程序的 ioctl 命令。在 dkio(7I) 手册页中指定了这些 I/O 控制。磁盘 I/O 控制用于将磁盘信息传入/传出设备驱动程序。磁盘实用程序命令(如 format(1M)newfs(1M))支持 Solaris 磁盘设备。强制性的 Sun 磁盘 I/O 控制如下:

DKIOCINFO

返回描述磁盘控制器的信息

DKIOCGAPART

返回磁盘的分区映射

DKIOCSAPART

设置磁盘的分区映射

DKIOCGGEOM

返回磁盘的几何参数

DKIOCSGEOM

设置磁盘的几何参数

DKIOCGVTOC

返回磁盘的卷目录

DKIOCSVTOC

设置磁盘的卷目录

磁盘性能

Solaris DDI/DKI 提供了优化 I/O 传输以提高文件系统性能的工具。它是一种管理 I/O 请求列表以便优化文件系统磁盘访问的机制。有关对 I/O 请求进行排队的说明,请参见异步数据传输(块驱动程序)

diskhd 结构用于管理 I/O 请求链接表。

struct diskhd {
    long     b_flags;         /* not used, needed for consistency*/
    struct   buf *b_forw,    *b_back;       /* queue of unit queues */
    struct   buf *av_forw,    *av_back;    /* queue of bufs for this unit */
    long     b_bcount;            /* active flag */
};

diskhd 数据结构具有驱动程序可处理的两个 buf 指针。av_forw 指针指向第一个活动 I/O 请求。第二个指针 av_back 指向列表中的上一个活动请求。

一个指向此结构的指针以及一个指向要处理的当前 buf 结构的指针作为参数传递给 disksort(9F)disksort() 例程对 buf 请求排序以优化磁盘查找。然后此例程将 buf 指针插入 diskhd 列表。disksort() 程序将 buf 结构的 b_resid 中的值用作排序关键字。驱动程序负责设置此值。大多数 Sun 磁盘驱动程序使用柱面组作为排序关键字。此方法优化了文件系统读前访问。

将数据添加到 diskhd 列表后,设备需要传输这些数据。如果设备未忙于处理请求,则 xxstart() 例程会将第一个 buf 结构拉出 diskhd 列表并开始传输。

如果设备正忙,则驱动程序会从 xxstrategy() 入口点返回。当硬件执行完数据传输时,便会产生中断。随后会调用驱动程序的中断例程为设备提供服务。在提供中断服务后,驱动程序可以调用 start() 例程来处理 diskhd 列表中的下一个 buf 结构。