Linux for S/390 and zSeries¶
通用设备支持 (CDS) 设备驱动程序 I/O 支持例程
- 作者
Ingo Adlung
Cornelia Huck
Copyright, 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。
在发起 I/O 之前,必须通过
ccw_device_set_options()设置选项。驱动程序不再调用
read_dev_chars()/read_conf_data(),而是下发通道程序并自行处理中断。
- ccw_device_get_ciw()
从扩展 sense 数据中获取命令。
- 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()例程确定中断状态,并根据在发起 I/O 请求时用do_IO()定义的规则(标志)调用设备特定的中断处理程序。
接下来的章节将更详细地描述除 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)来驱动其设备。在适用的情况下,设备驱动程序可以在其 online 例程中下发 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() 函数。某些设备需要在没有挂起的 I/O 请求的情况下首先下发 HALT SUBCHANNEL (HSCH) 命令。此功能也由 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 |
I/O 发往的 ccw_device |
cpa |
通道程序的逻辑起始地址 |
user_intparm |
用户特定的中断信息;将返回给设备驱动程序的中断处理程序。允许设备驱动程序将中断与特定的 I/O 请求关联起来。 |
lpm |
定义用于特定 I/O 请求的通道路径。值为 0 将使 cio 使用 opm。 |
key |
用于 I/O 的存储键(对于操作存储键 != 默认键的存储很有用) |
标志 |
定义为 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 |
数据链 (data chaining) |
CCW_FLAG_CC |
命令链 (command chaining) |
CCW_FLAG_SLI |
抑制不正确长度 (suppress incorrect length) |
CCW_FLAG_SKIP |
跳过 (skip) |
CCW_FLAG_PCI |
PCI |
CCW_FLAG_IDA |
间接寻址 (indirect addressing) |
CCW_FLAG_SUSPEND |
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 |
程序控制中断 (program controlled interrupt) |
SCHN_STAT_INCORR_LEN |
不正确长度 (incorrect length) |
SCHN_STAT_PROG_CHECK |
程序检查 (program check) |
SCHN_STAT_PROT_CHECK |
保护检查 (protection check) |
SCHN_STAT_CHN_DATA_CHK |
通道数据检查 (channel data check) |
SCHN_STAT_CHN_CTRL_CHK |
通道控制检查 (channel control check) |
SCHN_STAT_INTF_CTRL_CHK |
接口控制检查 (interface control check) |
SCHN_STAT_CHAIN_CHECK |
链检查 (chaining check) |
irb->scsw.dstat 字段提供(累积的)设备状态
DEV_STAT_ATTENTION |
注意 (attention) |
DEV_STAT_STAT_MOD |
状态修改器 (status modifier) |
DEV_STAT_CU_END |
控制单元结束 (control unit end) |
DEV_STAT_BUSY |
busy |
DEV_STAT_CHN_END |
通道结束 (channel end) |
DEV_STAT_DEV_END |
设备结束 (device end) |
DEV_STAT_UNIT_CHECK |
单元检查 (unit check) |
DEV_STAT_UNIT_EXCEP |
单元异常 (unit exception) |
有关各个标志含义的详细信息,请参阅《ESA/390 操作原理》手册。
使用说明
必须在禁用中断且持有 ccw 设备锁的情况下调用 ccw_device_start()。
允许设备驱动程序在其中断程序内部直接下发下一个 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 设备锁的情况下调用 ccw_device_halt()。
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 设备锁的情况下调用 ccw_device_clear()。
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 的路径掩码。