适用于 S/390 和 zSeries 的 Linux¶
通用设备支持 (CDS) 设备驱动程序 I/O 支持例程
- 作者
Ingo Adlung
Cornelia Huck
版权所有,IBM Corp. 1999-2002
简介¶
本文档介绍了 Linux/390 的通用设备支持例程。与其他硬件架构不同,ESA/390 定义了一种统一的 I/O 访问方法。这减轻了设备驱动程序的负担,因为他们不必处理不同的总线类型、轮询与中断处理、共享与非共享中断处理、DMA 与端口 I/O (PIO) 以及其他更多硬件功能。但是,这意味着要么每个设备驱动程序都需要自己实现硬件 I/O 连接功能,要么操作系统提供一种统一的方法来访问硬件,从而提供每个设备驱动程序都需要提供的所有功能。
本文档并不打算详细解释 ESA/390 硬件架构的每个细节。这些信息可以从 ESA/390 操作原理手册(IBM 表单编号 SA22-7201)中获得。
为了为 ESA/390 I/O 接口构建通用设备支持,引入了一个功能层,该层为硬件提供通用 I/O 访问方法。
通用设备支持层包括下面定义的 I/O 支持例程。 其中一些实现了通用的 Linux 设备驱动程序接口,而另一些是 ESA/390 平台特有的。
- 注意
为了为 S/390 编写驱动程序,您还需要查看 S/390 驱动程序模型接口 中描述的接口。
从 2.4 移植驱动程序的注意事项
主要变化是
这些函数使用 ccw_device 而不是 irq(子通道)。
所有驱动程序都必须定义一个 ccw_driver(参见 S/390 驱动程序模型接口)和关联的函数。
request_irq()
和free_irq()
不再由驱动程序完成。oper_handler 被 ccw_driver 的 probe() 和 set_online() 函数(某种程度上)取代。
not_oper_handler 被 ccw_driver 的 remove() 和 set_offline() 函数(某种程度上)取代。
通道设备层已消失。
必须调整中断处理程序以使用 ccw_device 作为参数。 此外,它们不返回 devstat,而是返回 irb。
在启动 io 之前,必须通过
ccw_device_set_options()
设置选项。驱动程序发出通道程序并处理中断本身,而不是调用 read_dev_chars()/read_conf_data()。
- ccw_device_get_ciw()
从扩展感知数据获取命令。
- ccw_device_start()、ccw_device_start_timeout()、ccw_device_start_key()、ccw_device_start_key_timeout()
启动 I/O 请求。
- ccw_device_resume()
恢复通道程序执行。
- ccw_device_halt()
终止设备上处理的当前 I/O 请求。
- do_IRQ()
通用中断例程。 每当向系统呈现 I/O 中断时,中断入口例程都会调用此函数。 do_IRQ() 例程确定中断状态,并根据使用 do_IO() 启动 I/O 请求期间定义的规则(标志)调用特定于设备的中断处理程序。
接下来的章节将更详细地描述 do_IRQ() 以外的函数。 由于 do_IRQ() 仅从 Linux/390 第一级中断处理程序调用,并且不包括设备驱动程序可调用接口,因此未对其进行描述。 相反,do_IO() 的功能描述也描述了设备特定中断处理程序的输入。
- 注意
所有解释也适用于 64 位架构 s390x。
Linux/390 设备驱动程序的通用设备支持 (CDS)¶
常规信息¶
以下章节描述了 Linux/390 通用设备支持 (CDS) 提供的 I/O 相关接口例程,以便在 IBM ESA/390 硬件平台上实现特定于设备的驱动程序实现。 这些接口旨在提供每个设备驱动程序实现所需的功能,以允许在 ESA/390 平台上驱动特定的硬件设备。 一些接口例程是 Linux/390 特有的,而另一些接口例程也可以在其他 Linux 平台实现中找到。 各种函数原型、数据声明和宏定义可以在架构特定的 C 头文件 linux/arch/s390/include/asm/irq.h 中找到。
CDS 接口概念概述¶
与其他硬件平台不同,ESA/390 架构没有定义由特定中断控制器管理的中断线路和可能允许或不允许共享中断、DMA 处理等的总线系统。相反,ESA/390 架构实现了一个所谓的通道子系统,该子系统提供了物理连接到系统的设备的统一视图。尽管 ESA/390 硬件平台知道各种不同的外围设备连接,例如磁盘设备(又名 DASD)、磁带、通信控制器等,但所有这些设备都可以通过定义明确的访问方法访问,并且它们以统一的方式呈现 I/O 完成:I/O 中断。每个设备都通过所谓的子通道向系统唯一标识,其中 ESA/390 架构允许连接 64k 个设备。
然而,Linux 最初是在 Intel PC 架构上构建的,它具有两个级联的 8259 可编程中断控制器 (PIC),最多允许 15 条不同的中断线路。 连接到此类系统的所有设备共享这 15 个中断级别。 连接到 ISA 总线系统的设备不得共享中断级别(又名 IRQ),因为 ISA 总线基于边沿触发中断。 MCA、EISA、PCI 和其他总线系统基于电平触发中断,因此允许共享 IRQ。 但是,如果多个设备通过相同的(共享)IRQ 呈现其硬件状态,则操作系统必须调用在此 IRQ 上注册的每个设备驱动程序,以确定拥有引发中断的设备的设备驱动程序。
在内核 2.4 之前,Linux/390 过去通过 IRQ(子通道)提供接口。 对于通用 I/O 层的内部使用,这些仍然存在。 但是,设备驱动程序应该仅通过 ccw_device 使用新的调用接口。
在其启动期间,Linux/390 系统会检查外围设备。 每个设备都由 ESA/390 通道子系统通过所谓的子通道唯一定义。 虽然子通道号是系统生成的,但每个子通道也采用用户定义的属性,即所谓的设备号。 子通道号和设备号都不能超过 65535。在 sysfs 初始化期间,会收集有关控制单元类型和设备类型的信息,这些类型意味着特定的 I/O 命令(通道命令字 - CCW)以操作设备。 设备驱动程序可以在其初始化步骤中检索此组硬件信息,以使用保存在 struct ccw_device
中的信息识别它们支持的设备。 此方法意味着 Linux/390 不需要探测空闲(未启用)的中断请求线 (IRQ) 来驱动其设备。 在适用的情况下,设备驱动程序可以使用发出 READ DEVICE CHARACTERISTICS ccw 来检索其在线例程中的设备特性。
为了便于 I/O 启动,CDS 层提供了一个 ccw_device_start()
接口,该接口采用设备特定的通道程序(一个或多个 CCW)作为输入,设置所需的特定于架构的控制块,并代表设备驱动程序启动 I/O 请求。 ccw_device_start()
例程允许指定它是否期望 CDS 层在每次观察到中断时通知设备驱动程序,或者仅使用最终状态通知。 有关更多详细信息,请参见 ccw_device_start()
。 设备驱动程序绝不能自己发出 ESA/390 I/O 命令,而必须使用 Linux/390 CDS 接口。
为了取消长时间运行的 I/O 请求,CDS 层提供了 ccw_device_halt()
函数。 有些设备需要最初发出 HALT SUBCHANNEL (HSCH) 命令,而没有挂起的 I/O 请求。 此函数也由 ccw_device_halt()
涵盖。
get_ciw() - 获取命令信息字
此调用使设备驱动程序能够从扩展的 SenseID 数据中获取有关支持的命令的信息。
struct ciw *
ccw_device_get_ciw(struct ccw_device *cdev, __u32 cmd);
cdev |
要检索命令的 ccw_device。 |
cmd |
要检索的命令类型。 |
NULL |
没有可用的扩展数据、无效设备或未找到命令。 |
!NULL |
请求的命令。 |
ccw_device_start() - Initiate I/O Request
ccw_device_start()
例程是 I/O 请求前端处理器。 所有设备驱动程序 I/O 请求都必须使用此例程发出。 设备驱动程序绝不能自己发出 ESA/390 I/O 命令。 相反,ccw_device_start()
例程提供了驱动任意设备所需的所有接口。
此描述还涵盖了传递给设备驱动程序中断处理程序的状态信息,因为它与调用 ccw_device_start()
时与关联的 I/O 请求一起定义的规则(标志)相关。
int ccw_device_start(struct ccw_device *cdev,
struct ccw1 *cpa,
unsigned long intparm,
__u8 lpm,
unsigned long flags);
int ccw_device_start_timeout(struct ccw_device *cdev,
struct ccw1 *cpa,
unsigned long intparm,
__u8 lpm,
unsigned long flags,
int expires);
int ccw_device_start_key(struct ccw_device *cdev,
struct ccw1 *cpa,
unsigned long intparm,
__u8 lpm,
__u8 key,
unsigned long flags);
int ccw_device_start_key_timeout(struct ccw_device *cdev,
struct ccw1 *cpa,
unsigned long intparm,
__u8 lpm,
__u8 key,
unsigned long flags,
int expires);
cdev |
ccw_device I/O 的目标 |
cpa |
通道程序的逻辑起始地址 |
user_intparm |
用户特定的中断信息; 将返回到设备驱动程序的中断处理程序。 允许设备驱动程序将中断与特定的 I/O 请求关联。 |
lpm |
定义用于特定 I/O 请求的通道路径。 值为 0 将使 cio 使用 opm。 |
key |
用于 I/O 的存储密钥(对于使用存储密钥 != 默认密钥的存储进行操作非常有用) |
flag |
定义要对 I/O 处理执行的操作 |
expires |
jiffies 中的超时值。 在此之后,通用 I/O 层将终止正在运行的程序,并以 ERR_PTR(-ETIMEDOUT) 作为 irb 调用中断处理程序。 |
可能的标志值是
DOIO_ALLOW_SUSPEND |
通道程序可能会被挂起 |
DOIO_DENY_PREFETCH |
不允许 CCW 预取; 通常,这意味着通道程序可能会被修改 |
DOIO_SUPPRESS_INTER |
不要在中间状态下调用处理程序 |
cpa 参数指向通道程序的第一个格式 1 CCW
struct ccw1 {
__u8 cmd_code;/* command code */
__u8 flags; /* flags, like IDA addressing, etc. */
__u16 count; /* byte count */
__u32 cda; /* data address */
} __attribute__ ((packed,aligned(8)));
定义了以下 CCW 标志值
CCW_FLAG_DC |
数据链接 |
CCW_FLAG_CC |
命令链接 |
CCW_FLAG_SLI |
禁止不正确的长度 |
CCW_FLAG_SKIP |
跳过 |
CCW_FLAG_PCI |
PCI |
CCW_FLAG_IDA |
间接寻址 |
CCW_FLAG_SUSPEND |
挂起 |
通过 ccw_device_set_options()
,设备驱动程序可以为设备指定以下选项
DOIO_EARLY_NOTIFICATION |
允许提前中断通知 |
DOIO_REPORT_ALL |
报告所有中断条件 |
ccw_device_start()
函数返回
0 |
成功完成或请求成功启动 |
-EBUSY |
该设备当前正在处理之前的 I/O 请求,或者该设备上存在挂起的状态。 |
-ENODEV |
cdev 无效,该设备未运行或 ccw_device 未联机。 |
当 I/O 请求完成时,CDS 第一级中断处理程序会将状态累积到 struct irb
中,然后调用设备中断处理程序。 intparm 字段将包含设备驱动程序已与特定 I/O 请求关联的值。 如果识别出挂起的设备状态,则 intparm 将设置为 0(零)。 这可能在 I/O 启动期间发生,或者由于警报状态通知而延迟发生。 在任何情况下,此状态都与当前(最后一个)I/O 请求无关。 在延迟状态通知的情况下,不会显示特殊中断来指示 I/O 完成,因为 I/O 请求从未启动,即使 ccw_device_start()
成功完成并返回。
irb 可能包含一个错误值,设备驱动程序应该首先检查此错误值
-ETIMEDOUT |
通用 I/O 层在指定的超时值后终止了请求 |
-EIO |
通用 I/O 层由于错误状态而终止了请求 |
如果 irb 中扩展状态字 (esw) 中的并发感知标志已设置,则 esw 中 erw.scnt 字段描述了扩展控制字 irb->scsw.ecw[] 中可用的特定于设备的感知字节数。 设备驱动程序本身不需要设备感知。
设备中断处理程序可以使用以下定义来调查感知字节 0 中编码的主要单元检查源
SNS0_CMD_REJECT |
0x80 |
SNS0_INTERVENTION_REQ |
0x40 |
SNS0_BUS_OUT_CHECK |
0x20 |
SNS0_EQUIPMENT_CHECK |
0x10 |
SNS0_DATA_CHECK |
0x08 |
SNS0_OVERRUN |
0x04 |
SNS0_INCOMPL_DOMAIN |
0x01 |
根据设备状态,可以将这些值中的多个一起设置。 有关详细信息,请参阅设备特定的文档。
irb->scsw.cstat 字段提供(累积的)子通道状态
SCHN_STAT_PCI |
程序控制的中断 |
SCHN_STAT_INCORR_LEN |
长度不正确 |
SCHN_STAT_PROG_CHECK |
程序检查 |
SCHN_STAT_PROT_CHECK |
保护检查 |
SCHN_STAT_CHN_DATA_CHK |
通道数据检查 |
SCHN_STAT_CHN_CTRL_CHK |
通道控制检查 |
SCHN_STAT_INTF_CTRL_CHK |
接口控制检查 |
SCHN_STAT_CHAIN_CHECK |
链接检查 |
irb->scsw.dstat 字段提供(累积的)设备状态
DEV_STAT_ATTENTION |
注意 |
DEV_STAT_STAT_MOD |
状态修改器 |
DEV_STAT_CU_END |
控制单元结束 |
DEV_STAT_BUSY |
忙 |
DEV_STAT_CHN_END |
通道结束 |
DEV_STAT_DEV_END |
设备结束 |
DEV_STAT_UNIT_CHECK |
单元检查 |
DEV_STAT_UNIT_EXCEP |
单元异常 |
有关单个标志含义的详细信息,请参阅 ESA/390 操作原理手册。
使用说明
ccw_device_start()
必须在禁用状态下调用,并保持 ccw 设备锁。
设备驱动程序可以从其中断处理程序中发出下一个 ccw_device_start()
调用。 不需要安排一个 bottom-half,除非需要安排一个非确定性地长时间运行的错误恢复过程或类似过程。 在 I/O 处理期间,Linux/390 通用 I/O 设备驱动程序已经获得了 IRQ 锁,即处理程序在调用 ccw_device_start()
时不得尝试再次获取它,否则我们将陷入死锁状态!
如果设备驱动程序依赖于在启动下一个 I/O 请求之前完成的 I/O 请求,则可以通过将 NoOp I/O 命令 CCW_CMD_NOOP 链接到提交的 CCW 链的末尾来减少 I/O 处理开销。 这将强制通道结束和设备结束状态一起呈现,并伴随单个中断。 但是,应该谨慎使用此方法,因为它意味着通道将保持忙碌,无法处理同一通道上其他设备的 I/O 请求。 因此,例如,读取命令绝不应使用此技术,因为结果无论如何都会通过单个中断呈现。
为了最大限度地减少 I/O 开销,设备驱动程序仅应在设备可以报告在设备结束之前设备驱动程序迫切依赖的中间中断信息的情况下使用 DOIO_REPORT_ALL。 在这种情况下,所有 I/O 中断都会呈现给设备驱动程序,直到识别出最终状态。
如果设备能够从异步呈现的 I/O 错误中恢复,则可以使用 DOIO_EARLY_NOTIFICATION 标志执行重叠 I/O。 虽然某些设备始终将通道结束和设备结束一起报告,并伴随单个中断,但其他设备在通道准备好进行下一个 I/O 请求时呈现主要状态(通道结束),并在设备上完成数据传输时呈现次要状态(设备结束)。
上面的标志允许利用此功能,例如,对于可以处理网络上丢失的数据的通信设备,以允许增强的 I/O 处理。
除非通道子系统随时呈现辅助状态中断,否则利用此功能将导致仅将主要状态中断呈现给设备驱动程序,同时执行重叠 I/O。 当呈现没有错误的辅助状态(警报状态)时,这表示自上次辅助(最终)状态以来发出的所有重叠 ccw_device_start()
请求已成功完成。
打算在通道命令字 (CCW) 上设置挂起标志的通道程序必须使用 DOIO_ALLOW_SUSPEND 选项启动 I/O 操作,否则挂起标志将导致通道程序检查。 在通道程序被挂起时,通道子系统将生成一个中间中断。
ccw_device_resume()
- 恢复通道程序执行
如果设备驱动程序选择通过在特定的 CCW 上设置 CCW 挂起标志来挂起当前通道程序的执行,则通道程序执行将被挂起。为了恢复通道程序执行,CIO 层提供了 ccw_device_resume()
例程。
int ccw_device_resume(struct ccw_device *cdev);
cdev |
请求恢复操作的 ccw_device |
ccw_device_resume()
函数返回
0 |
挂起的通道程序已恢复 |
-EBUSY |
状态挂起 |
-ENODEV |
cdev 无效或非操作子通道 |
-EINVAL |
恢复函数不适用 |
-ENOTCONN |
没有 I/O 请求等待完成 |
使用说明
请查看 ccw_device_start()
用法说明,了解有关挂起通道程序的更多详细信息。
ccw_device_halt()
- 停止 I/O 请求处理
有时,设备驱动程序可能需要停止长时间运行的通道程序处理,或者设备可能需要最初发出停止子通道 (HSCH) I/O 命令。为此,提供了 ccw_device_halt()
命令。
必须在禁用状态下调用 ccw_device_halt()
,并且持有 ccw 设备锁。
int ccw_device_halt(struct ccw_device *cdev,
unsigned long intparm);
cdev |
请求停止操作的 ccw_device |
intparm |
中断参数;该值仅在没有未完成的 I/O 时使用,否则返回与 I/O 请求关联的 intparm |
ccw_device_halt()
函数返回
0 |
请求已成功启动 |
-EBUSY |
设备当前正忙,或状态挂起。 |
-ENODEV |
cdev 无效。 |
-EINVAL |
设备未运行或 ccw 设备未联机。 |
使用说明
设备驱动程序可以通过编写在其末尾通过通道内转移 (TIC) 命令 (CCW_CMD_TIC) 循环回到其开头的通道程序来编写永无止境的通道程序。通常,这是由网络设备驱动程序通过设置 PCI CCW 标志 (CCW_FLAG_PCI) 来执行的。一旦执行此 CCW,就会生成程序控制中断 (PCI)。然后,设备驱动程序可以执行适当的操作。在中断对网络设备的未完成读取(无论是否带有 PCI 标志)之前,需要 ccw_device_halt()
结束未完成的操作。
ccw_device_clear() - Terminage I/O Request Processing
为了终止子通道上的所有 I/O 处理,使用了清除子通道 (CSCH) 命令。可以通过 ccw_device_clear()
发出。
必须在禁用状态下调用 ccw_device_clear()
,并且持有 ccw 设备锁。
int ccw_device_clear(struct ccw_device *cdev, unsigned long intparm);
cdev |
请求清除操作的 ccw_device |
intparm |
中断参数(请参阅 |
ccw_device_clear()
函数返回
0 |
请求已成功启动 |
-ENODEV |
cdev 无效 |
-EINVAL |
设备未运行或 ccw 设备未联机。 |
其他支持例程¶
本章介绍 Linux/390 设备驱动程序编程环境中要使用的各种例程。
get_ccwdev_lock()
获取设备特定锁的地址。然后将其用于 spin_lock() / spin_unlock() 调用中。
__u8 ccw_device_get_path_mask(struct ccw_device *cdev);
获取当前可用于 cdev 的路径的掩码。