12.2 通用基础结构

  CAM代表通用访问方法(Common Access Method)。它以类SCSI方式寻址 I/O总线。这就允许将通用设备驱动程序和控制I/O总线的驱动程序分离开来: 例如磁盘驱动程序能同时控制SCSI、IDE、且/或任何其他总线上的磁盘, 这样磁盘驱动程序部分不必为每种新的I/O总线而重写(或拷贝修改)。 这样,两个最重要的活动实体是:

  外围设备驱动程序从OS接收请求,将它们转换为SCSI命令序列并将 这些SCSI命令传递到SCSI接口模块。SCSI接口模块负责将这些命令传递给 实际硬件(或者如果实际硬件不是SCSI,而是例如IDE,则也要将这些SCSI 命令转换为硬件的native命令)。

  由于这儿我们感兴趣的是编写SCSI适配器驱动程序,从此处开始我们 将从SIM的角度考虑所有的事情。

  典型的SIM驱动程序需要包括如下的CAM相关的头文件:

#include <cam/cam.h>
#include <cam/cam_ccb.h>
#include <cam/cam_sim.h>
#include <cam/cam_xpt_sim.h>
#include <cam/cam_debug.h>
#include <cam/scsi/scsi_all.h>

  每个SIM驱动程序必须做的第一件事情是向CAM子系统注册它自己。 这在驱动程序的xxx_attach()函数(此处和以后的 xxx_用于指带唯一的驱动程序名字前缀)期间完成。 xxx_attach()函数自身由系统总线自动配置代码 调用,我们在此不描述这部分代码。

  这需要好几步来完成:首先需要分配与SIM关联的请求队列:

    struct cam_devq *devq;

    if(( devq = cam_simq_alloc(SIZE) )==NULL) {
        error; /* 一些处理错误的代码 */
    }

  此处 SIZE 为要分配的队列的大小, 它能包含的最大请求数目。 它是 SIM 驱动程序在 SCSI 卡上能够并行处理的请求的数目。一般可以如下估算:

SIZE = NUMBER_OF_SUPPORTED_TARGETS * MAX_SIMULTANEOUS_COMMANDS_PER_TARGET

  下一步为我们的SIM创建描述符:

    struct cam_sim *sim;

    if(( sim = cam_sim_alloc(action_func, poll_func, driver_name,
            softc, unit, max_dev_transactions,
            max_tagged_dev_transactions, devq) )==NULL) {
        cam_simq_free(devq);
        error; /* 一些错误处理代码 */
    }

  注意如果我们不能创建SIM描述符,我们也释放 devq,因为我们对其无法做任何其他事情, 而且我们想节约内存。

  如果SCSI卡上有多条SCSI总线,则每条总线需要它自己的 cam_sim 结构。

  一个有趣的问题是,如果SCSI卡有不只一条SCSI总线我们该怎么做, 每个卡需要一个devq结构还是每条SCSI总线? 在CAM代码的注释中给出的答案是:任一方式均可,由驱动程序的作者 选择。

  参量为:



  最后我们注册与我们的SCSI适配器关联的SCSI总线。

    if(xpt_bus_register(sim, bus_number) != CAM_SUCCESS) {
        cam_sim_free(sim, /*free_devq*/ TRUE);
        error; /* 一些错误处理代码 */
    }

  如果每条SCSI总线有一个devq结构(即, 我们将带有多条总线的卡看作多个卡,每个卡带有一条总线),则总线号 总是为0,否则SCSI卡上的每条总线应当有不同的号。每条总线需要 它自己单独的cam_sim结构。

  这之后我们的控制器完全挂接到CAM系统。现在 devq的值可以被丢弃:在所有以后从CAM发出的 调用中将以sim为参量,devq可以由它导出。

  CAM为这些异步事件提供了框架。有些事件来自底层(SIM驱动程序), 有些来自外围设备驱动程序,还有一些来自CAM子系统本身。任何驱动 程序都可以为某些类型的异步事件注册回调,这样那些事件发生时它就 会被通知。

  这种事件的一个典型例子就是设备复位。每个事务和事件以 “path”的方式区分它们所作用的设备。目标特定的事件 通常在与设备进行事务处理期间发生。因此那个事务的路径可以被重用 来报告此事件(这是安全的,因为事件路径的拷贝是在事件报告例程中进行的, 而且既不会被deallocate也不作进一步传递)。在任何时刻,包括中断例程中, 动态分配路径也是安全的,尽管那样会导致某些额外开销,并且这种方法 可能存在的一个问题是碰巧那时可能没有空闲内存。对于总线复位事件, 我们需要定义包括总线上所有设备在内的通配符路径。这样我们就能提前为 以后的总线复位事件创建路径,避免以后内存不足的问题:

    struct cam_path *path;

    if(xpt_create_path(&path, /*periph*/NULL,
                cam_sim_path(sim), CAM_TARGET_WILDCARD,
                CAM_LUN_WILDCARD) != CAM_REQ_CMP) {
        xpt_bus_deregister(cam_sim_path(sim));
        cam_sim_free(sim, /*free_devq*/TRUE);
        error; /* 一些错误处理代码 */
    }

    softc->wpath = path;
    softc->sim = sim;

  正如你所看到的,路径包括:

  如果驱动程序不能分配这个路径,它将不能正常工作,因此那样情况下 我们卸除(dismantle)那个SCSI总线。

  我们在softc结构中保存路径指针以便以后 使用。这之后我们保存sim的值(或者如果我们愿意,也可以在从 xxx_probe()退出时丢弃它)。

  这就是最低要求的初始化所需要做的一切。为了把事情做正确无误, 还剩下一个问题。

  对于SIM驱动程序,有一个特殊感兴趣的事件:何时目标设备被认为 找不到了。这种情况下复位与这个设备的SCSI协商可能是个好主意。因此我们 为这个事件向CAM注册一个回调。通过为这种类型的请求来请求CAM控制块上 的CAM动作,请求就被传递到CAM:(译注:参看下面示例代码和原文)

    struct ccb_setasync csa;

    xpt_setup_ccb(&csa.ccb_h, path, /*优先级*/5);
    csa.ccb_h.func_code = XPT_SASYNC_CB;
    csa.event_enable = AC_LOST_DEVICE;
    csa.callback = xxx_async;
    csa.callback_arg = sim;
    xpt_action((union ccb *)&csa);

  现在我们看一下xxx_action()xxx_poll()的驱动程序入口点。

  

static void xxx_action ( struct cam_sim *sim, union ccb *ccb );



  响应CAM子系统的请求采取某些动作。Sim描述了请求的SIM,CCB为 请求本身。CCB代表“CAM Control Block”。它是很多特定 实例的联合,每个实例为某些类型的事务描述参量。所有这些实例共享 存储着参量公共部分的CCB头部。(译注:这一段不很准确,请自行参考原文)

  CAM既支持SCSI控制器工作于发起者(initiator)(“normal”) 模式,也支持SCSI控制器工作于目标(target)(模拟SCSI设备)模式。这儿 我们只考虑与发起者模式有关的部分。

  定义了几个函数和宏(换句话说,方法)来访问结构sim中公共数据:

  为了识别设备,xxx_action()可以使用这些 函数得到单元号和指向它的softc结构的指针。

  请求的类型被存储在 ccb->ccb_h.func_code。因此,通常 xxx_action()由一个大的switch组成:

    struct xxx_softc *softc = (struct xxx_softc *) cam_sim_softc(sim);
    struct ccb_hdr *ccb_h = &ccb->ccb_h;
    int unit = cam_sim_unit(sim);
    int bus = cam_sim_bus(sim);

    switch(ccb_h->func_code) {
    case ...:
        ...
    default:
        ccb_h->status = CAM_REQ_INVALID;
        xpt_done(ccb);
        break;
    }

  从default case语句部分可以看出(如果收到未知命令),命令的返回码 被设置到 ccb->ccb_h.status 中,并且通过 调用xpt_done(ccb)将整个CCB返回到CAM中。

  xpt_done()不必从 xxx_action()中调用:例如I/O请求可以在SIM驱动程序 和/或它的SCSI控制器中排队。(译注:它指I/O请求?) 然后,当设备传递(post)一个中断信号,指示对此请求的处理已结束时, xpt_done()可以从中断处理例程中被调用。

  实际上,CCB状态不是仅仅被赋值为一个返回码,而是始终有某种状态。 CCB被传递给xxx_action()例程前,其取得状态 CCB_REQ_INPROG,表示其正在进行中。/sys/cam/cam.h 中定义了数量惊人的状态值,它们应该能非常详尽地表示请求的状态。 更有趣的是,状态实际上是一个枚举状态值(低6位)和一些可能出现的附加 类(似)旗标位(高位)的“位或(bitwise or)”。枚举值会在以后 更详细地讨论。对它们的汇总可以在错误概览节(Errors Summary section) 找到。可能的状态旗标为:

  函数xxx_action()不允许睡眠,因此对资源 访问的所有同步必须通过冻结SIM或设备队列来完成。除了前述的旗标外, CAM子系统提供了函数xpt_release_simq()xpt_release_devq()来直接解冻队列,而不必将 CCB传递到CAM。

  CCB头部包含如下字段:

  使用CCB的SIM私有字段的建议方法是为它们定义一些有意义的名字, 并且在驱动程序中使用这些有意义的名字,就像下面这样:

#define ccb_some_meaningful_name    sim_priv.entries[0].bytes
#define ccb_hcb spriv_ptr1 /* 用于硬件控制块 */

  最常见的发起者模式的请求是:

本文档和其它文档可从这里下载:ftp://ftp.FreeBSD.org/pub/FreeBSD/doc/.

如果对于FreeBSD有问题,请先阅读文档,如不能解决再联系<questions@FreeBSD.org>.
关于本文档的问题请发信联系 <doc@FreeBSD.org>.