版权所有 (C) 1993 Rickard E. Faith (faith@cs.unc.edu)。
于 1993 年在北卡罗来纳大学为 COMP-291 编写。本文档所含信息绝不提供任何担保。
保留所有权利。在所有副本上保留版权声明和本许可声明的前提下,允许制作和分发本文档的完整副本。
这是(经作者明确许可)原始文档的修改副本。如果您希望复制本文档,建议您通过 ftp 从 ftp://ftp.cs.unc.edu/pub/users/faith/papers/scsi.paper.tar.gz 获取原始版本
[请注意,本文档自 1993 年的版权日期以来未进行修订。大多数内容仍然适用,但某些事实,例如当前支持的 SCSI 主机适配器列表,现在已经相当过时。]
目前,Linux 内核包含以下 SCSI 主机适配器的驱动程序:Adaptec 1542、Adaptec 1740、Future Domain TMC-1660/TMC-1680、Seagate ST-01/ST-02、UltraStor 14F 和 Western Digital WD-7000。您可能想要为您不受支持的主机适配器编写自己的驱动程序。您也可能想要重写或更新现有驱动程序之一。
SCSI 协议旨在提供高效的对等 I/O 总线,最多可连接 8 个设备,包括一个或多个主机。数据可以异步传输,速率仅取决于设备实现和电缆长度。同步数据传输支持高达每秒 10 兆传输的速率。借助 32 位宽数据传输选项,数据速率最高可达每秒 40 兆字节。SCSI-2 包括用于磁盘和光盘、磁带、打印机、处理器、CD-ROM、扫描仪、介质更换器和通信设备的命令集。
1985 年,当第一个 SCSI 标准最终确定为美国国家标准时,多家制造商与 X3T9.2 任务组联系。他们希望提高 SCSI 的强制性要求,并为直接访问设备定义更多功能。为了不延误 SCSI 标准,X3T9.2 成立了一个特设小组,负责制定一份最终被称为通用命令集 (CCS) 的工作文件。许多磁盘产品在设计时都将此工作文件与 SCSI 标准结合使用。
在开发 CCS 工作文件的同时,X3T9.2 开始着手制定增强型 SCSI 标准,该标准被命名为 SCSI-2。SCSI-2 包括 CCS 工作文件的成果,并将其扩展到所有设备类型。它还添加了缓存命令、性能增强功能以及 X3T9.2 认为有价值的其他功能。虽然 SCSI-2 已经远远超出了原始 SCSI 标准(现在称为 SCSI-1),但它与 SCSI-1 设备保持了高度的兼容性。
-SEL | -BSY | -MSG | -C/D | -I/O | 阶段 |
高电平 | 高电平 | ? | ? | ? | 总线空闲 |
高电平 | 低电平 | ? | ? | ? | 仲裁 |
I | I&T | ? | ? | ? | 选择 |
T | I&T | ? | ? | ? | 重选 |
高电平 | 低电平 | 高电平 | 高电平 | 高电平 | 数据输出 |
高电平 | 低电平 | 高电平 | 高电平 | 低电平 | 数据输入 |
高电平 | 低电平 | 高电平 | 低电平 | 高电平 | 命令 |
高电平 | 低电平 | 高电平 | 低电平 | 低电平 | 状态 |
高电平 | 低电平 | 低电平 | 低电平 | 高电平 | 消息输出 |
高电平 | 低电平 | 低电平 | 低电平 | 低电平 | 消息输入 |
某些控制器(特别是廉价的 Seagate 控制器)需要直接操作 SCSI 总线——其他控制器则自动处理这些底层细节。八个阶段中的每一个阶段都将详细描述。
值* | 状态 |
---|---|
0x00 | 良好 |
0x02 | 检查条件 |
0x04 | 条件满足 |
0x08 | 忙 |
0x10 | 中间 |
0x14 | 中间-条件满足 |
0x18 | 保留冲突 |
0x22 | 命令终止 |
0x28 | 队列已满 |
以下概述了三个最重要的状态代码的含义
每个 SCSI 命令的长度为 6、10 或 12 字节。SCSI 驱动程序开发人员必须充分理解以下命令。
Sense Key | 描述 |
---|---|
0x00 | NO SENSE |
0x01 | RECOVERED ERROR |
0x02 | NOT READY |
0x03 | MEDIUM ERROR |
0x04 | HARDWARE ERROR |
0x05 | ILLEGAL REQUEST |
0x06 | UNIT ATTENTION |
0x07 | DATA PROTECT |
0x08 | BLANK CHECK |
0x09 | (供应商特定错误) |
0x0a | COPY ABORTED |
0x0b | ABORTED COMMAND |
0x0c | EQUAL |
0x0d | VOLUME OVERFLOW |
0x0e | MISCOMPARE |
0x0f | 保留 |
低级设备驱动程序的作者需要了解内核如何处理中断。至少,应理解禁用 (cli()) 和启用 (sti()) 中断的内核函数。某些驱动程序可能还需要调度函数(例如,schedule(), sleepon()和wakeup())。有关这些函数的详细说明,请参见 支持函数。
在您开始为 Linux 编写 SCSI 驱动程序之前,您需要获取一些资源。
最重要的是可启动的 Linux 系统——最好是从 IDE、RLL 或 MFM 硬盘启动的系统。在开发新的 SCSI 驱动程序期间,您将多次重建内核并重新启动系统。编程错误可能会导致 SCSI 驱动器和非 SCSI 驱动器上的数据损坏。在开始之前备份您的系统。
已安装的 Linux 系统可以非常精简:GCC 编译器发行版(包括库和二进制实用程序)、编辑器和内核源代码是您需要的全部。其他工具,如od, hexdump和less将非常有用。所有这些工具都可以安装在廉价的 20-30~MB 硬盘上。(使用过的 20 MB MFM 硬盘和控制器的价格应低于 100 美元。)
文档至关重要。至少,您需要主机适配器的技术手册。由于 Linux 是可自由分发的,并且由于您(理想情况下)希望自由分发您的源代码,请避免保密协议 (NDA)。大多数 NDA 会禁止您发布源代码——您可能被允许发布包含驱动程序的目标文件,但这在目前的 Linux 社区中是完全不可接受的。
解释 SCSI 标准的手册将很有帮助。通常,您的磁盘驱动器的技术手册就足够了,但 SCSI 标准的副本通常也会很有帮助。(1991 年 10 月 17 日 SCSI-2 标准文档草案可通过匿名 ftp 从sunsite.unc.edu在/pub/Linux/development/scsi-2.tar.Z获取,也可以从 Global Engineering Documents (2805 McGaw, Irvine, CA 92714) 购买,电话 (800)-854-7179 或 (714)-261-1455。请参考文档 X3.131-199X。在 1993 年初,该手册的价格为 60-70 美元。)
在开始之前,请打印hosts.h, scsi.h以及 Linux 内核中的现有驱动程序之一的硬拷贝。在您编写驱动程序时,这些将证明是有用的参考资料。
Linux 内核中的高级 SCSI 接口管理内核和低级 SCSI 设备驱动程序之间的所有交互。由于这种分层设计,低级 SCSI 驱动程序只需要向高级代码提供一些基本服务。低级驱动程序的作者不需要了解内核 I/O 系统的复杂性,因此可以在相对较短的时间内编写低级驱动程序。
两个主要结构 (Scsi_Host和Scsi_Cmnd) 用于在高级代码和低级代码之间进行通信。接下来的两节提供了有关这些结构和低级驱动程序要求的详细信息。
#define FDOMAIN_16X0 { "Future Domain TMC-16x0", \ fdomain_16x0_detect, \ fdomain_16x0_info, \ fdomain_16x0_command, \ fdomain_16x0_queue, \ fdomain_16x0_abort, \ fdomain_16x0_reset, \ NULL, \ fdomain_16x0_biosparam, \ 1, 6, 64, 1 ,0, 0} #endif
所述Scsi_Host结构在下面给出。本节稍后将详细解释每个字段。
typedef struct { char *name; int (* detect)(int); const char *(* info)(void); int (* queuecommand)(Scsi_Cmnd *, void (*done)(Scsi_Cmnd *)); int (* command)(Scsi_Cmnd *); int (* abort)(Scsi_Cmnd *, int); int (* reset)(void); int (* slave_attach)(int, int); int (* bios_param)(int, int, int []); int can_queue; int this_id; short unsigned int sg_tablesize; short cmd_per_lun; unsigned present:1; unsigned unchecked_isa_dma:1; } Scsi_Host;
一般来说,变量在Scsi_Host结构直到detect()函数(请参阅 detect() 部分)被调用后才使用。因此,任何在主机适配器检测之前无法赋值的变量都应在检测期间赋值。例如,如果单个驱动程序为具有非常相似特征的多个主机适配器提供支持,则可能会发生这种情况。Scsi_Host结构中的某些参数可能会取决于检测到的特定主机适配器。
name保存指向 SCSI 主机适配器的简短描述的指针。
can_queue保存主机适配器可以处理的未完成命令的数量。除非驱动程序支持重选并且驱动程序是中断驱动的(一些早期的 Linux 驱动程序不是中断驱动的,因此性能非常差),否则此变量应设置为 1。
大多数主机适配器都有分配给它们的特定 SCSI ID。此 SCSI ID(通常为 6 或 7)用于重选。this_id变量保存主机适配器的 SCSI ID。如果主机适配器没有分配的 SCSI ID,则此变量应设置为 -1(在这种情况下,不支持重选)。
高级代码支持“分散-收集”,这是一种通过将许多小的 SCSI 请求合并为几个大的 SCSI 请求来提高 SCSI 吞吐量的方法。由于大多数 SCSI 磁盘驱动器都采用 1:1 交错格式(“1:1 交错”意味着单个磁道中的所有扇区都连续出现在磁盘表面上),因此执行 SCSI 仲裁和选择阶段所需的时间比扇区之间的旋转延迟时间更长。(这可能是一种过度简化。在较旧的设备上,实际的命令处理可能很重要。此外,内核中存在大量的分层开销:高级 SCSI 代码、缓冲代码和文件系统代码都会导致 SCSI 性能下降。)因此,每个磁盘旋转周期只能处理一个 SCSI 请求,从而导致吞吐量约为每秒 50 千字节。但是,当支持分散-收集时,平均吞吐量通常超过每秒 500 千字节。
所述sg_tablesize变量保存分散-收集列表中允许的最大请求数。如果驱动程序不支持分散-收集,则此变量应设置为SG_NONE。如果驱动程序可以支持无限数量的分组请求,则此变量应设置为SG_ALL。某些驱动程序将使用主机适配器来管理分散-收集列表,并且可能需要限制sg_tablesize为主机适配器硬件支持的数量。例如,某些 Adaptec 主机适配器需要限制为 16。
SCSI 标准支持“链接命令”的概念。链接命令允许将多个命令连续排队到单个 SCSI 设备。cmd_per_lun变量指定允许的链接命令的数量。如果不支持命令链接,则此变量应设置为 1。但是,目前,高级 SCSI 代码不会利用此功能。
链接命令与多个未完成命令(由can_queue变量描述)从根本上不同。链接命令始终发送到同一 SCSI 目标,并且不一定涉及重选阶段。此外,链接命令消除了集合中第一个命令之后的所有命令的仲裁、选择和消息输出阶段。相比之下,多个未完成命令可以发送到任意 SCSI 目标,并且需要仲裁、选择、消息输出和重选阶段。
所述present位(由高级代码)在检测到主机适配器时设置。
某些主机适配器使用直接内存访问 (DMA) 来直接从计算机主内存读取和写入数据块。Linux 是一个虚拟内存操作系统,可以使用超过 16 MB 的物理内存。不幸的是,在使用 ISA 总线(所谓的“工业标准架构”总线是随着 IBM PC/XT 和 IBM PC/AT 计算机引入的)的机器上,DMA 仅限于低 16 MB 的物理内存。
如果unchecked_isa_dma位已设置,则高级代码将提供数据缓冲区,保证这些缓冲区位于物理地址空间的低 16 MB 中。为不使用 DMA 的主机适配器编写的驱动程序应将此位设置为零。专用于 EISA 总线(“扩展工业标准架构”总线是用于 386 和 i486 机器的非专有 32 位总线)机器的驱动程序也应将此位设置为零,因为 EISA 总线机器允许不受限制的 DMA 访问。
所述detect()函数的唯一参数是“主机号”,它是scsi_hosts变量(类型数组struct Scsi_Host)的索引。detect()函数应在检测到主机适配器时返回非零值,否则应返回零。
主机适配器检测必须谨慎进行。通常,该过程首先在 ROM 区域中查找主机适配器的“BIOS 签名”。在 PC/AT 兼容计算机上,地址空间在0xc0000和和0xfffff0xc0000之间的使用已得到很好的定义。例如,大多数机器上的视频 BIOS 从开始,硬盘 BIOS(如果存在)从0xc80000xc0000开始。当 PC/AT 兼容计算机启动时,从到0xf8000的每个 2 千字节块都会被检查 2 字节签名 (0x55aa
),这表示存在有效的 BIOS 扩展 [Nor85]。
FUTURE DOMAIN CORP. (C) 1986-1990 1800-V2.07/28/89BIOS 签名通常由一系列字节组成,这些字节唯一地标识 BIOS。例如,Future Domain BIOS 签名之一是字符串
,正好在 BIOS 块开头的第五个字节处找到。
找到 BIOS 签名后,可以更具体地测试是否存在功能正常的主机适配器。由于 BIOS 签名是硬编码在内核中的,因此新 BIOS 的发布可能会导致驱动程序莫名其妙地失败。此外,专门为 Linux 使用 SCSI 适配器的人可能希望禁用 BIOS 以加快启动时间。因此,如果可以在不检查 BIOS 的情况下安全地检测到适配器,则应使用该替代方法。
通常,每个主机适配器都有一系列 I/O 端口地址,用于通信。有时,这些地址会硬编码到驱动程序中,迫使所有拥有此主机适配器的 Linux 用户使用一组特定的 I/O 端口地址。其他驱动程序更灵活,并通过扫描所有可能的端口地址来查找当前的 I/O 端口地址。通常,每个主机适配器将允许 3 或 4 组地址,这些地址可以通过主机适配器卡上的硬件跳线选择。
请求 IRQdetect()检测后,例程必须从内核请求任何所需的中断或 DMA 通道。有 16 个中断通道,标记为 IRQ 0 到 IRQ 15。内核提供了两种设置 IRQ 处理程序的方法和irqaction().
所述irqaction()request_irq()函数采用两个参数,即 IRQ 号和指向处理程序例程的指针。然后,它设置一个默认的sigaction例程必须从内核请求任何所需的中断或 DMA 通道。有 16 个中断通道,标记为 IRQ 0 到 IRQ 15。内核提供了两种设置 IRQ 处理程序的方法结构并调用。函数(Linux 0.99.7 内核源代码,irqaction()linux/kernel/irq.c例程必须从内核请求任何所需的中断或 DMA 通道。有 16 个中断通道,标记为 IRQ 0 到 IRQ 15。内核提供了两种设置 IRQ 处理程序的方法)的代码如下所示。我将我的讨论限制在更通用的
int request_irq( unsigned int irq, void (*handler)( int ) ) { struct sigaction sa; sa.sa_handler = handler; sa.sa_flags = 0; sa.sa_mask = 0; sa.sa_restorer = NULL; return irqaction( irq, &sa ); }
函数上。。函数(Linux 0.99.7 内核源代码,例程必须从内核请求任何所需的中断或 DMA 通道。有 16 个中断通道,标记为 IRQ 0 到 IRQ 15。内核提供了两种设置 IRQ 处理程序的方法声明(Linux 0.99.5 内核源代码,
int irqaction( unsigned int irq, struct sigaction *new )函数是其中第一个参数irq是要请求的 IRQ 的编号,第二个参数new是一个具有定义(Linux 0.99.5 内核源代码,linux/include/linux/signal.h
struct sigaction { __sighandler_t sa_handler; sigset_t sa_mask; int sa_flags; void (*sa_restorer)(void); };
)的结构,如下所示在此结构中,sa_handler
void fdomain_16x0_intr( int irq )应指向您的中断处理程序例程,该例程应具有类似于以下内容的定义其中第一个参数其中
所述将是导致调用中断处理程序例程的 IRQ 的编号。sa_mask例程必须从内核请求任何所需的中断或 DMA 通道。有 16 个中断通道,标记为 IRQ 0 到 IRQ 15。内核提供了两种设置 IRQ 处理程序的方法变量用作例程必须从内核请求任何所需的中断或 DMA 通道。有 16 个中断通道,标记为 IRQ 0 到 IRQ 15。内核提供了两种设置 IRQ 处理程序的方法.
所述例程的内部标志。传统上,此变量在调用之前设置为零sa_flags变量可以设置为零或SA_INTERRUPT变量可以设置为零或。如果选择零,则中断处理程序将在启用其他中断的情况下运行,并将通过信号处理返回函数返回。对于相对较慢的 IRQ,例如与键盘和定时器中断相关的 IRQ,建议使用此选项。如果变量可以设置为零或被选中,则将在禁用中断的情况下调用处理程序,并且返回将避免信号处理返回函数。
所述选择“快速”IRQ 处理程序调用例程,建议用于中断驱动的硬盘例程。但是,中断处理程序应尽快打开中断,以便可以处理其他中断。sa_restorer变量当前未使用,传统上设置为.
所述irqaction()和例程必须从内核请求任何所需的中断或 DMA 通道。有 16 个中断通道,标记为 IRQ 0 到 IRQ 15。内核提供了两种设置 IRQ 处理程序的方法NULL
panic()例程必须从内核请求任何所需的中断或 DMA 通道。有 16 个中断通道,标记为 IRQ 0 到 IRQ 15。内核提供了两种设置 IRQ 处理程序的方法的合理理由。
内核使用 Intel “中断门”来设置通过函数请求的 IRQ 处理程序例程。Intel i486 手册 [Int90, p. 9-11] 将中断门解释如下
... 使用中断门的中断清除 IF 标志 [中断使能标志],这可以防止其他中断干扰当前中断处理程序。随后的 IRET 指令将 IF 标志恢复为堆栈上 EFLAGS 寄存器的已保存内容中的值。
请求 DMA 通道detect()某些 SCSI 主机适配器使用 DMA 来访问内存中的大型数据块。由于 CPU 不必处理单独的 DMA 请求,因此数据传输比 CPU 介导的传输更快,并允许 CPU 在块传输期间执行其他有用的工作(假设启用了中断)。主机适配器将使用特定的 DMA 通道。此 DMA 通道将由函数确定,并使用
所述请求的 DMA 通道号大于 7。请求的 DMA 通道已分配。这是一个非常严重的情况,可能会导致任何 SCSI 请求失败。它值得调用nameinfo()
所述函数仅返回指向静态区域的指针,该区域包含低级驱动程序的简要说明。此描述类似于变量指向的描述,将在启动时打印。queuecommand()函数设置主机适配器以处理 SCSI 命令,然后返回。当命令完成时,将调用Scsi_Cmnddone()函数仅返回指向静态区域的指针,该区域包含低级驱动程序的简要说明。此描述类似于函数,并将
所述函数仅返回指向静态区域的指针,该区域包含低级驱动程序的简要说明。此描述类似于变量。有关can_queue的详细信息,请参阅 Scsi_Cmnd 结构。启动 SCSI 命令。对于高级主机适配器,这可能就像将命令发送到主机适配器“邮箱”一样简单。对于不太高级的主机适配器,仲裁阶段是手动启动的。函数仅在函数仅返回指向静态区域的指针,该区域包含低级驱动程序的简要说明。此描述类似于变量(请参阅 can_queue 部分)为非零时调用。否则,
所述queuecommand()command()Scsi_Cmnd函数用于所有 SCSI 请求。函数仅返回指向静态区域的指针,该区域包含低级驱动程序的简要说明。此描述类似于函数应在成功时返回零(当前高级 SCSI 代码目前忽略返回值)。queuecommand()函数在 SCSI 命令完成后调用。此命令需要的唯一参数是指向先前传递给函数的同一结构的指针。在调用函数的同一函数之前,必须正确设置
当前的低级驱动程序没有统一(或正确地)实现错误报告,因此最好查阅 scsi.c 以确定应该如何准确地报告错误,而不是研究现有的驱动程序。
所述启动 SCSI 命令。对于高级主机适配器,这可能就像将命令发送到主机适配器“邮箱”一样简单。对于不太高级的主机适配器,仲裁阶段是手动启动的。函数处理 SCSI 命令并在命令完成时返回。当最初编写 SCSI 代码时,不支持中断驱动的驱动程序。旧的驱动程序比当前的中断驱动的驱动程序效率低得多(在响应时间和延迟方面),但也更容易编写。对于新的驱动程序,此命令可以替换为对函数仅返回指向静态区域的指针,该区域包含低级驱动程序的简要说明。此描述类似于函数的调用,如此处所示。(Linux 0.99.5 内核,linux/kernel/blk_drv/scsi/aha1542.c,由 Tommy Thorn 编写。)
static volatile int internal_done_flag = 0; static volatile int internal_done_errcode = 0; static void internal_done( Scsi_Cmnd *SCpnt ) { internal_done_errcode = SCpnt->result; ++internal_done_flag; } int aha1542_command( Scsi_Cmnd *SCpnt ) { aha1542_queuecommand( SCpnt, internal_done ); while (!internal_done_flag); internal_done_flag = 0; return internal_done_errcode; }
返回值与函数的同一结构中的Scsi_Cmnd变量相同。请参阅 done() 部分和 Scsi_Cmnd 结构 部分以获取更多详细信息。
高级 SCSI 代码处理所有超时。这使低级驱动程序无需进行计时,并允许对不同的设备使用不同的超时时间段(例如,SCSI 磁带驱动器的超时时间几乎是无限的,而 SCSI 磁盘驱动器的超时时间相对较短)。
所述abort()函数用于请求中止当前正在执行的 SCSI 命令,该命令由Scsi_Cmnd指针指示。设置函数的同一结构中的Scsi_Cmnd结构后,abort()函数返回零。如果代码,即abort()函数的第二个参数为零,则函数的同一应设置为DID_ABORT。否则,函数的同一应设置为等于代码。如果代码不为零,则通常为SCSI 选择失败,因为在指定的地址没有设备。或DID_RESET.
。目前,没有一个低级驱动程序能够正确中止 SCSI 命令。发起者应请求(通过断言-ATN线路)目标进入 MESSAGE OUT 阶段。然后,发起者应向目标发送 ABORT 消息。
所述reset()函数用于重置 SCSI 总线。在 SCSI 总线重置后,任何正在执行的命令都应失败,并返回DID_RESET结果代码(参见 done() 部分)。
目前,没有一个低级驱动程序能正确处理重置。要正确重置 SCSI 命令,发起者应请求(通过断言-ATN线路)目标进入 MESSAGE OUT 阶段。然后,发起者应向目标发送 BUS DEVICE RESET 消息。也可能需要通过断言-RST线路来启动 SCSI RESET,这将导致所有目标设备被重置。重置后,可能需要与目标重新协商同步通信协议。
所述slave_attach()函数目前尚未实现。此函数将用于协商主机适配器和目标驱动器之间的同步通信。此协商需要在发起者和目标之间交换一对 SYNCHRONOUS DATA TRANSFER REQUEST 消息。此交换应在以下条件下发生 [LXT91]:
支持同步数据传输的 SCSI 设备识别到自上次接收到“硬”RESET 以来,它尚未与其他 SCSI 设备通信。支持同步数据传输的 SCSI 设备识别到自接收到 BUS DEVICE RESET 消息以来,它尚未与其他 SCSI 设备通信。
Linux 支持 MS-DOS(MS-DOS 是微软公司的注册商标)硬盘分区系统。每个磁盘都包含一个“分区表”,该表定义了磁盘如何划分为逻辑扇区。解释此分区表需要有关磁盘大小的信息,以柱面、磁头和每柱面扇区为单位。然而,SCSI 磁盘隐藏了它们的物理几何结构,并以逻辑方式作为连续的扇区列表访问。因此,为了与 MS-DOS 兼容,SCSI 主机适配器将“谎报”其几何结构。SCSI 磁盘的物理几何结构虽然可用,但很少用作“逻辑几何结构”。(其原因涉及 MS-DOS 施加的古老且任意的限制。)
Linux 需要确定“逻辑几何结构”,以便它可以正确地修改和解释分区表。不幸的是,没有标准的物理几何结构和逻辑几何结构之间的转换方法。因此,引入了bios_param()函数,试图提供对主机适配器几何结构信息的访问。
所述size参数是以扇区为单位的磁盘大小。一些主机适配器使用基于此数字的确定性公式来计算驱动器的逻辑几何结构。其他主机适配器将几何结构信息存储在驱动程序可以访问的表中。为了方便访问,dev参数包含驱动器的设备号。在linux/fs.h中定义了两个宏,这将有助于解释此值MAJOR(dev)是设备的主设备号,而MINOR(dev)是设备的次设备号。这些是标准 Linux mknod 命令用来在 /dev 目录中创建设备的相同主设备号和次设备号。info参数指向一个包含三个整数的数组,bios_param()函数将在返回之前填充这些整数
中的信息info不是驱动器的物理几何结构,而只是与 MS-DOS 用来访问驱动器的逻辑几何结构相同的逻辑几何结构。物理几何结构和逻辑几何结构之间的区别怎么强调都不过分。
所述Scsi_Cmnd结构,(Linux 0.99.7 内核,linux/kernel/blk_drv/scsi/scsi.h),如下所示,由高级代码使用,以指定要由低级代码执行的 SCSI 命令。低级设备驱动程序可以忽略Scsi_Cmnd结构中的许多变量——但是,其他变量非常重要。
typedef struct scsi_cmnd { int host; unsigned char target, lun, index; struct scsi_cmnd *next, *prev; unsigned char cmnd[10]; unsigned request_bufflen; void *request_buffer; unsigned char data_cmnd[10]; unsigned short use_sg; unsigned short sglist_len; unsigned bufflen; void *buffer; struct request request; unsigned char sense_buffer[16]; int retries; int allowed; int timeout_per_command, timeout_total, timeout; unsigned char internal_timeout; unsigned flags; void (*scsi_done)(struct scsi_cmnd *); void (*done)(struct scsi_cmnd *); Scsi_Pointer SCp; unsigned char *host_scribble; int result; } Scsi_Cmnd;
host是scsi_hosts数组的索引。
target存储 SCSI 命令的目标的 SCSI ID。如果支持每个目标的多个未完成命令或多个命令,则此信息非常重要。
cmnd是一个字节数组,其中包含实际的 SCSI 命令。这些字节应在 COMMAND 阶段发送到 SCSI 目标。cmnd[0]是 SCSI 命令代码。COMMAND_SIZE宏,在scsi.h中定义,可用于确定当前 SCSI 命令的长度。
函数的同一用于存储来自 SCSI 请求的结果代码。有关此变量的更多信息,请参见 done() 部分。必须在低级例程返回之前正确设置此变量。
use_sg包含分散-聚集链中片段的数量计数。如果use_sg为零,则request_buffer指向 SCSI 命令的数据缓冲区,并且request_bufflen是以字节为单位的此缓冲区的长度。否则,request_buffer指向一个scatterlist结构的数组,并且use_sg将指示数组中有多少个这样的结构。request_buffer的用法是非直观且令人困惑的。
数组的每个元素都包含一个scatterlist数组address和一个length组件。如果unchecked_isa_dma结构中的Scsi_Host标志设置为 1(有关 DMA 传输的更多信息,请参见 unchecked_isa_dma 部分),则保证地址在物理内存的前 16 MB 内。大量数据将由单个 SCSI 命令处理。这些数据的长度将等于scatterlist数组的索引。
暂存区
指针queuecommand()函数中的函数仅返回指向静态区域的指针,该区域包含低级驱动程序的简要说明。此描述类似于此指针应设置为
host_scribble高级代码提供了一对内存分配函数,和scsi_malloc()scsi_free()高级代码提供了一对内存分配函数,,保证返回物理内存前 16 MB 中的内存。因此,此内存适用于 DMA。每个请求分配的内存量必须是 512 字节的倍数,并且必须小于或等于 4096 字节。通过Scsi_Host可用的内存总量是sg_tablesize, cmd_per_lun和unchecked_isa_dma.
所述函数(有关更多信息,请参见 queuecommand() 部分)。此指针没有其他用途。结构变量的复杂函数。高级代码提供了一对内存分配函数,指针可用于指向使用
typedef struct scsi_pointer { char *ptr; /* data pointer */ int this_residual; /* left in this buffer */ struct scatterlist *buffer; /* which buffer */ int buffers_residual; /* how many buffers left */ volatile int Status; volatile int Message; volatile int have_data_in; volatile int sent_command; volatile int phase; } Scsi_Pointer;的结构,在此处描述此结构中的变量可以以低级驱动程序中任何必要的方式使用。通常,bufferscatterlist, 指向中的当前条目scatterlist, buffers_residual计数中剩余的条目数ptr
用作缓冲区的指针,并且
计数传输中剩余的字符数。一些主机适配器需要支持这种交互的细节——其他主机适配器可以完全忽略此结构。