用于 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

要检索的命令类型。

ccw_device_get_ciw() 返回

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_halt())。

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 的路径的掩码。