用于 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()
调用。不需要调度下半部,除非需要调度不确定时长运行的错误恢复过程或类似过程。在 I/O 处理期间,Linux/390 通用 I/O 设备驱动程序支持已获得 IRQ 锁,即,处理程序在调用 ccw_device_start()
时不得尝试再次获取它,否则我们将陷入死锁!
如果设备驱动程序依赖于在启动下一个操作之前完成的 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 的路径的掩码。