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 *simunion ccb *ccb); 
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>.