编写设备驱动程序

第 4 部分 附录

附录提供了以下背景材料:

附录 A 硬件概述

本附录介绍有关可以支持 Solaris OS 的硬件的一般问题。其中包括 Solaris OS 支持的处理器、总线体系结构以及内存模型。另外,还介绍了各种设备问题以及 Sun 平台中使用的 PROM。


注 –

本附录中的材料仅用于提供信息。此信息在调试驱动程序的过程中可能会有用。但是,Solaris DDI/DKI 接口会对设备驱动程序隐藏其中的许多实现详细信息。


本附录提供有关以下主题的信息:

SPARC 处理器问题

本节介绍了许多特定于 SPARC 处理器的主题,如数据对齐、字节排序、寄存器窗口以及浮点指令的可用性 。有关特定于 x86 处理器主题的信息,请参见x86 处理器问题


注 –

驱动程序决不能执行浮点操作,因为内核中不支持这些操作。


SPARC 数据对齐

所有数量均必须使用标准 C 数据类型与其自然边界对齐:

通常,编译器会处理所有对齐问题。但是,驱动程序编写者可能更关心对齐,因为只有使用正确的数据类型才能访问设备。由于设备寄存器通常是通过指针引用来访问的,因此驱动程序必须确保在访问设备时正确对齐指针。

SPARC 结构中的成员对齐

由于 SPARC 处理器强加的数据对齐限制,因此,C 结构也具有对齐要求。结构对齐要求是由最严格对齐的结构组件强加的。例如,仅包含字符的结构没有对齐限制,而对于包含 long long 成员的结构,则必须对其结构进行设置,保证此成员在 64 位边界上对齐。

SPARC 字节排序

SPARC 处理器使用大端字节序进行字节排序。整数的最高有效字节 (most significant byte, MSB) 存储在该整数的最低地址上。最低有效字节存储在此处理器中字的最高地址上。例如,字节 63 是 64 位处理器的最低有效字节。

图中显示如何在大端字节序体系结构中对字节进行排序,即字节 0 是最高有效字节。

SPARC 寄存器窗口

SPARC 处理器使用寄存器窗口。每个寄存器窗口包含八个输入寄存器、八个局部寄存器、八个输出寄存器以及八个全局寄存器。输出寄存器是下一个窗口的输入寄存器。寄存器窗口的数量范围从 2 到 32,具体取决于处理器实现。

由于驱动程序通常是使用 C 语言编写的,因此编译器通常不会指明使用了寄存器窗口。但是,当调试驱动程序时,可能必须使用寄存器窗口。

SPARC 乘法和除法指令

版本 7 SPARC 处理器没有乘法或除法指令。 乘法和除法指令是在软件中模拟实现的。由于驱动程序可能在版本 7、版本 8 或者版本 9 处理器中运行,因此请避免进行大量整数乘除。相反,请使用按位向左和向右移位来以 2 的幂进行相乘和相除。

SPARC Architecture Manual, Version 9》介绍了有关 SPARC CPU 的更具体信息。《SPARC Compliance Definition》(版本 2.4)介绍了 SPARC V9 的应用程序二进制接口 (application binary interface, ABI) 的详细信息。本手册介绍了 32 位 SPARC V8 ABI 和 64 位 SPARC V9 ABI。可以从 SPARC International 的网站 http://www.sparc.com 上获取本文档。

x86 处理器问题

数据类型没有对齐限制。但是,x86 处理器可能需要额外的存储周期来正确处理未对齐的数据传送。


注 –

驱动程序不应执行浮点操作,因为内核中不支持这些操作。


x86 字节排序

x86 处理器使用小端字节序进行字节排序。整数的最低有效字节 (least significant byte, LSB) 存储在该整数的最低地址上。最高有效字节存储在此处理器中数据项的最高地址上。例如,字节 7 是 64 位处理器的最高有效字节。

图中显示如何在小端字节序体系结构中对字节进行排序,即字节 0 是最低有效字节。

x86 体系结构手册

Intel Corporation 和 AMD 都发布了大量有关 x86 系列处理器的书籍。请参见 http://www.intel.comhttp://www.amd.com

字节存储顺序

为了实现多平台、多指令集体系结构可移植性的目标,驱动程序中删除了主机总线的相关组件。要解决的第一个相关性问题是处理器的字节存储顺序,即字节排序。例如, x86 处理器系列采用小端字节序,而 SPARC 体系结构采用大端字节序。

总线体系结构显示了与处理器相同类型的字节存储顺序。例如,PCI 局部总线采用小端字节序,S 总线采用大端字节序,ISA 总线采用小端字节序等。

要维持处理器与总线之间的可移植性, DDI 兼容驱动程序必须不采用任何端字节序。虽然驱动程序可以通过运行时检查或源代码中的预处理程序指令(如 #ifdef _LITTLE_ENDIAN) 管理其字节存储顺序,但是长期维护可能会很麻烦。在某些情况下,DDI 框架会使用软件方法来执行字节交换。在另外一些情况下,可以像内存管理单元 (memory management unit, MMU) 中那样通过硬件页级交换来执行字节交换,也可通过特殊计算机指令来执行字节交换。DDI 框架可以利用这些硬件功能来提高性能。

图 A–1 主机总线相关性所需的字节排序

图中显示反向字节存储顺序的字节交换。

除了不采用任何端字节排序之外,可移植驱动程序还必须独立于处理器的数据排序。在大多数情况下,必须按照驱动程序指示的顺序进行数据传送。但是,有时可以通过合并、批处理或者重新排列数据来简化数据传送,如下图中所示。例如,可以将数据合并应用于加速帧缓存器上的图形显示。驱动程序可以选择建议 DDI 框架在数据传送过程中使用其他最优传送机制。

图 A–2 数据排序主机总线相关性

图中显示通过 CPU 重新排列字节。

存储缓冲区

为提高性能,CPU 会使用内部存储缓冲区临时存储数据。使用内部缓冲区可能会对设备 I/O 操作的同步造成影响。因此,驱动程序需要执行明确的步骤来确保在适当的时间完成对寄存器的写入。

例如,假设通过锁来同步对设备空间(如寄存器或帧缓存器)的访问。驱动程序需要检查在释放锁之前是否实际完成了向设备空间中的数据存储。释放锁时并不一定会刷新 I/O 缓冲区。

另一个示例是,确认中断时,驱动程序通常会在设备控制寄存器中设置或清除一位。驱动程序必须确保在中断程序返回之前,已开始在设备上对控制寄存器进行写入。同样,在向控制寄存器写入了某一命令之后,设备可能要求延迟,即驱动程序繁忙,需要等待。在这种情况下,驱动程序必须确保在设备延迟之前已开始在该设备上进行写入。

当读取设备寄存器不会产生不良负面影响时,只需在写入之后立即读取就可以对写入进行验证了。如果无法在不产生不良负面影响的情况下读取该特定寄存器,则可以使用同一寄存器集中的其他设备寄存器。

系统内存模型

系统内存模型用于定义内存操作(如装入存储)的语义,并指定处理器执行这些操作的顺序与操作到达内存的顺序之间的关系。内存模型可同时适用于单处理器和共享内存多处理器。支持两种内存模型: 全存储排序 (total store ordering, TSO) 和部分存储排序 (partial store ordering, PSO)。

全存储排序 (Total Store Ordering, TSO)

TSO 可保证存储、FLUSH 以及原子装入存储指令出现在给定处理器的内存中的顺序与该处理器发出这些指令的顺序相同。

x86 和 SPARC 处理器均支持 TSO。

部分存储排序 (Partial Store Ordering, PSO)

PSO 无法保证存储、FLUSH 以及原子装入存储指令出现在给定处理器的内存中的顺序与该处理器发出这些指令的顺序相同。处理器可以对存储的指令重新排序,以使内存的存储指令顺序与 CPU 发出的存储指令顺序不同。

SPARC 处理器支持 PSO;x86 处理器则不支持。

对于 SPARC 处理器,指令的发出顺序和内存顺序之间的一致性是由系统框架使用 STBAR 指令实现的。如果以上指令中的两条指令按处理器的发出顺序由 STBAR 指令分隔,或者指令引用同一位置,则这两条指令的内存存储顺序与发出顺序相同。使用 ddi_regs_map_setup(9F) 接口可强制执行兼容 DDI 的驱动程序中的强数据排序。兼容的驱动程序不能直接使用 STBAR 指令。

有关 SPARC 内存模型的更多详细信息,请参见《SPARC Architecture Manual, Version 9》。

总线体系结构

本节介绍了设备标识、设备寻址和中断。

设备标识

设备标识是确定系统中存在哪些设备的过程。某些设备是自标识设备,意味着设备本身向系统提供信息,以便系统可以标识需要使用的设备驱动程序。S 总线和 PCI 局部总线设备是自标识设备的示例。在 S 总线上,信息通常是从设备上 FCode PROM 中存储的小 Forth 程序派生而来。大多数 PCI 设备都会提供包含设备配置信息的配置空间。有关更多信息,请参见 sbus(4)pci(4) 手册页。

所有现代总线体系结构都要求设备进行自标识。

支持的中断类型

Solaris 平台支持轮询中断和向量化中断。对于这两种中断类型,Solaris DDI/DKI 中断模型均相同。有关中断处理的更多信息,请参见第 8 章

总线特定信息

本节介绍特定于 Solaris 平台支持的总线的寻址问题和设备配置问题。

PCI 局部总线

PCI 局部总线是旨在实现高速数据传送的高性能总线。PCI 总线驻留在系统板上。此总线通常用作高度集成的外围组件、外围附件板以及主机处理器或内存系统之间的互连机制。主机处理器、主内存和 PCI 总线本身都通过 PCI 主桥 (host bridge) 连接,如图 A–3 中所示。

互连的 I/O 总线树结构通过一系列 PCI 总线网桥进行支持。可以在 PCI 主桥 (host bridge) 下扩展从属 PCI 总线网桥,以使单总线系统扩展为带有多条辅助总线的复杂系统。PCI 设备可以连接到其中的一条或多条辅助总线。此外,还可以连接其他总线网桥,如 SCSI 或 USB。

每个 PCI 设备都具有唯一的供应商 ID 和设备 ID。相同种类的多台设备会通过其驻留的总线上的唯一设备号进一步标识。

图 A–3 计算机结构图

图中显示 PCI 主桥 (host bridge) 如何将 CPU 和主内存连接到 PCI 总线。

PCI 主桥 (host bridge) 用于提供处理器和外围组件之间的互连。通过 PCI 主桥 (host bridge),处理器可以直接访问独立于其他 PCI 总线主控器的主内存。例如,当 CPU 正在从主桥 (host bridge) 中的高速缓存控制器中提取数据时,其他 PCI 设备也可以通过该主桥 (host bridge) 访问系统内存。这种体系结构的优点在于其分隔了 I/O 总线与处理器的主机总线。

PCI 主桥 (host bridge) 还可提供 CPU 和外围 I/O 设备之间的数据访问映射。该桥会将每个外围设备映射到主机地址域,以便处理器可以通过程控 I/O 访问此设备。在局部总线端,PCI 主桥 (host bridge) 会将系统内存映射到 PCI 地址域,以便 PCI 设备可以作为总线主控器访问主机内存。图 A–3 显示了两种地址域。

PCI 地址域

PCI 地址域包含三种不同的地址空间: 配置、内存以及 I/O 空间。

PCI 配置地址空间

配置空间按地理位置定义。外围设备的位置通过它在互连的 PCI 总线网桥树中的物理位置确定。设备按其总线编号设备插槽编号进行定位。每个外围设备在其 PCI 配置空间中都包含一组明确定义的配置寄存器。这些寄存器不仅用于标识设备,还用于为配置框架提供设备配置信息。例如,必须首先映射设备配置空间中的基址寄存器,然后设备才能响应数据访问。

生成配置周期的方法取决于主机。x86 计算机中使用的是特殊的 I/O 端口。在其他平台上,可以将 PCI 配置空间内存映射到对应于主机地址域中 PCI 主桥 (host bridge) 的某些地址位置。处理器访问设备配置寄存器时,会将请求路由到 PCI 主桥 (host bridge)。然后,该桥会在总线上将访问转换为正确的配置周期。

PCI 配置基址寄存器

PCI 配置空间针对每台设备最多包含六个 32 位基址寄存器。这些寄存器可同时提供大小和数据类型信息。系统固件会将 PCI 地址域中的基本地址分配给这些寄存器。

每个可寻址区域既可以是内存空间,也可以是 I/O 空间。基址寄存器的位 0 包含的值用于标识类型。位 0 中的值为 0 表示内存空间,值为 1 表示 I/O 空间。下图显示了两种基址寄存器: 一种表示内存类型,另一种表示 I/O 类型。

图 A–4 内存和 I/O 的基址寄存器

图中显示了基本地址中的位 0 如何表示内存或 I/O 空间。

PCI 内存地址空间

PCI 同时支持 32 位和 64 位的内存空间地址。系统固件会将 PCI 地址域中的内存空间区域分配给 PCI 外围设备。区域的基本地址存储在设备的 PCI 配置空间的基址寄存器中。每个区域的大小必须是 2 的幂,并且所分配的基本地址必须在与区域大小相等的边界上对齐。内存空间中的设备地址会内存映射到主机地址域中,以便处理器的本机装入指令或存储指令可以对任何设备执行数据访问。

PCI I/O 地址空间

PCI 支持 32 位 I/O 空间。可以在不同的平台上以不同方式访问 I/O 空间。带有特殊 I/O 指令的处理器(如 Intel 处理器系列)使用 inout 指令访问 I/O 空间。没有特殊 I/O 指令的计算机将映射到对应于主机地址域中 PCI 主桥 (host bridge) 的地址位置。处理器访问内存映射的地址时,会向 PCI 主桥 (host bridge) 发送一个 I/O 请求,该主桥 (host bridge) 随后会将地址转换为 I/O 周期并将其放置在 PCI 总线上。内存映射的 I/O 通过处理器的本机装入/存储指令执行。

PCI 硬件配置文件

对于 PCI 局部总线设备,硬件配置文件应是不必要的。但是在某些情况下,PCI 设备的驱动程序需要使用硬件配置文件来增加驱动程序的专用信息。有关更多详细信息,请参见 driver.conf(4)pci(4) 手册页。

PCI Express

标准 PCI 总线已发展为 PCI Express。PCI Express 是下一代高性能 I/O 总线,用于连接桌面、移动设备、工作站、服务器以及嵌入式计算和通信平台之类的应用程序中的外围设备。

PCI Express 可提高总线性能,减少整体系统支出,并可利用计算机设计中新的发展成果。PCI Express 使用串行的点对点类型互连在两台设备之间实现通信。通过交换机,用户可以在某个系统中将大量设备连接在一起。串行互连意味着每台设备软件包的管脚更少,这可降低成本并使性能具有高度可伸缩性。

PCI Express 总线具有内置功能,可以适应以下技术:

将两台设备连接在一起的 PCI Express 互连称为链路。链路可以是 x1、x2、x4、x8、x12、x16 或 x32 双向的信号对。这些信号称为。双工模式中每条道的带宽 (x1) 为 500 MB/秒。虽然 PCI-X 和 PCI Express 具有不同的硬件连接,但是对驱动程序编写者来说,两种总线是相同的。PCI-X 是共享总线。例如,总线上的所有设备都共享单独的一组数据线和信号线。PCI-Express 是交换总线,通过它可以更有效地使用设备和系统总线之间的带宽。

有关 PCI Express 的更多信息,请参阅以下 Web 站点:http://www.pcisig.com/home

S 总线

典型的 S 总线系统由主板(包含 CPU 和 S 总线接口逻辑)、主板本身上的大量 S 总线设备以及大量 S 总线扩展插槽组成。另外,还可以通过相应的总线网桥将 S 总线连接到其他类型的总线。

S 总线按地理位置进行寻址。每个 S 总线插槽位于系统中固定的物理地址上。S 总线卡具有不同的地址,具体取决于其插入的插槽。将 S 总线设备移动到新插槽会导致系统将此设备视为新设备。

S 总线使用轮询中断。S 总线设备中断时,系统仅知道若干设备中的哪些设备可能发出该中断。系统中断处理程序必须询问每台设备的驱动程序此设备是否负责中断。

S 总线物理地址空间

下表显示了 Sun UltraSPARC 2 计算机的物理地址空间布局。UltraSPARC 2 模型上的物理地址包含 41 位。该 41 位的物理地址空间会进一步分为多个通过 PA(40:33) 标识的 33 位地址空间。

表 A–1 Ultra 2 中的设备物理空间

PA(40:33) 

33 位空间 

使用情况 

0x0 

0x000000000 - 0x07FFFFFFF

2 GB 主内存 

0x80 – 0xDF 

Reserved on Ultra 2

在 Ultra 2 上保留 

0xE0 

Processor 0

处理器 0 

0xE1 

Processor 1

处理器 1 

0xE2 – 0xFD 

Reserved on Ultra 2

在 Ultra 2 上保留 

0xFE 

0x000000000 - 0x1FFFFFFFF

从属 UPA (FFB) 

0xFF 

0x000000000 - 0x0FFFFFFFF

系统 I/O 空间 

 

0x100000000 - 0x10FFFFFFF

S 总线插槽 0 

 

0x110000000 - 0x11FFFFFFF

S 总线插槽 1 

 

0x120000000 - 0x12FFFFFFF

S 总线插槽 2 

 

0x130000000 - 0x13FFFFFFF

S 总线插槽 3 

 

0x1D0000000 - 0x1DFFFFFFF

S 总线插槽 D 

 

0x1E0000000 - 0x1EFFFFFFF

S 总线插槽 E 

 

0x1F0000000 - 0x1FFFFFFFF

S 总线插槽 F 

物理 S 总线地址

S 总线具有 32 个地址位,如 SBus Specification(S 总线规范)中所述。下表介绍 Ultra 2 如何使用地址位。

表 A–2 Ultra 2 S 总线地址位

位 

说明 

0 - 27 

这些位是 S 总线卡用于寻址该卡的内容的 S 总线地址行。 

28 - 31 

供 CPU 用于选择其中一个 S 总线插槽。这些位会生成 SlaveSelect 行。 

此寻址方案将生成表 A–1 中显示的 Ultra 2 地址。其他实现可能会使用不同数量的地址位。

Ultra 2 具有七个 S 总线插槽,其中四个是物理插槽。插槽 0 到 3 可供 S 总线卡使用。插槽 4-12 为保留插槽。插槽的使用情况如下:

S 总线硬件配置文件

通常,S 总线设备不需要硬件配置文件。但是在某些情况下,S 总线设备的驱动程序需要使用硬件配置文件来增加 S 总线卡所提供的信息。有关更多详细信息,请参见 driver.conf(4)sbus(4) 手册页。

设备问题

本节介绍特殊设备的问题。

时间关键型部分

虽然在有了锁定原语所提供的同步和保护机制的情况下可以执行大多数驱动程序操作,但是对于某些设备而言,必须在没有中断的情况下按顺序发生一系列事件。函数 ddi_enter_critical(9F) 与锁定原语一起将请求系统尽可能保证不会抢占或中断当前线程。在进行对 ddi_exit_critical(9F) 的关闭调用之前,此保证将一直有效。有关详细信息,请参见 ddi_enter_critical(9F) 手册页。

延迟

许多芯片指定只能在指定间隔对其进行访问。例如, Zilog Z8530 SCC 具有 1.6 微秒的“写入恢复时间”。此规范意味着通过 8530 写入字符时,必须使用 drv_usecwait(9F) 强制延迟。在某些情况下,规范不会明确指示所需的延迟,因此必须根据经验来确定延迟。

请注意不要组合可能大量存在的设备部件的延迟,例如数以千计的 SCSI 磁盘驱动器。

内部顺序逻辑

具有内部顺序逻辑的设备会将多个内部寄存器映射到同一外部地址。各种内部顺序逻辑包括以下类型:

中断问题

请注意以下常见的与中断相关的问题:

SPARC 计算机上的 PROM

某些平台具有PROM 监视器,支持在没有操作系统的情况下调试设备。本节介绍如何使用 SPARC 计算机上的 PROM 来映射设备寄存器,以便可对其进行访问。通常,可以使用 PROM 命令对设备进行充分测试,以确定设备是否正常工作。

有关 x86 引导子系统的说明,请参见 boot(1M) 手册页。

PROM 具有多种用途,包括:

Open Boot PROM 3

有关 Open Boot PROM 的完整文档,请参见《Open Boot PROM Toolkit User's Guide》和 monitor(1M) 手册页。本节中的示例引用的是 Sun4U 体系结构。其他体系结构可能要求不同的命令来执行操作。


注 –

Open Boot PROM 当前在具有 S 总线或 UPA/PCI 的 Sun 计算机上使用。Open Boot PROM 使用 "ok" 提示符。在早期计算机上,可能必须键入 `n' 才能获取 "ok" 提示符。


如果 PROM 处于安全模式security-mode 参数并未设置为),则可能需要 PROM 口令(在 security-password 参数中设置)。

printenv 命令用于显示所有参数及其值。

使用 help 命令可以获取帮助信息。

可以使用 EMACS 样式的命令行历史记录。使用 Ctrl-N (下一步)和 Ctrl-P (上一步)可以遍历历史记录列表。

Forth 命令

Open Boot PROM 使用 Forth 编程语言。Forth 是一种基于栈的语言。必须将参数推送到栈上,然后再运行正确的命令(称为),并且结果将留在栈上。

要对栈进行编号,请键入其值。


ok 57
ok 68

要在栈上添加两个顶部值,请使用 + 运算符。


ok +

结果会保留在栈上。栈使用 .s 来进行显示。


ok .s
bf

缺省基值为十六进制。可以使用 hexdecimal 来切换基值。


ok decimal
ok .s
191

有关更多信息,请参见《Forth User's Guide》。

遍历 PROM 设备树

pwdcdls 命令将遍历 PROM 设备树以查找设备。必须首先使用 cd 命令在树中建立一个位置,然后pwd 才能运行。本示例为在 S 总线上带有 cgsix 帧缓存器的 Ultra 1 工作站。


ok cd /

要在树中查看连接到当前节点的设备,请使用 ls 命令。


ok ls
f006a064 SUNW,UltraSPARC@0,0
f00598b0 sbus@1f,0
f00592dc counter-timer@1f,3c00
f004eec8 virtual-memory
f004e8e8 memory@0,0
f002ca28 aliases
f002c9b8 options
f002c880 openprom
f002c814 chosen
f002c7a4 packages

可以使用全节点名称:


ok cd sbus@1f,0
ok ls
f006a4e4 cgsix@2,0
f0068194 SUNW,bpp@e,c800000
f0065370 ledma@e,8400010
f006120c espdma@e,8400000
f005a448 SUNW,pll@f,1304000
f005a394 sc@f,1300000
f005a24c zs@f,1000000
f005a174 zs@f,1100000
f005a0c0 eeprom@f,1200000
f0059f8c SUNW,fdtwo@f,1400000
f0059ec4 flashprom@f,0
f0059e34 auxio@f,1900000
f0059d28 SUNW,CS4231@d,c000000

如果不使用前一个示例中的全节点名称,则还可以使用缩写。缩写命令行项类似于以下示例:


ok cd sbus

对于 S 总线设备,名称实际为 device@slot,offsetcgsix 设备位于插槽 2 中,并在偏移 0 处开始。如果该树中显示了 S 总线设备,则表明 PROM 已经识别了此设备。

.properties 命令用于显示设备的 PROM 属性。通过检查这些属性可以确定设备导出的属性。以后可以使用此信息来确保驱动程序查找的是正确的硬件属性。这些属性与可以使用 ddi_getprop(9F) 检索的属性相同。


ok cd cgsix
ok .properties
character-set            ISO8859-1
intr                     00000005 00000000
interrupts               00000005
reg                      00000002 00000000 01000000
dblbuf                   00 00 00 00
vmsize                   00 00 00 01
...

reg 属性用于定义包含以下字段的寄存器说明结构的数组:


uint_t        bustype;       /* cookie for related bus type*/
uint_t        addr;          /* address of reg relative to bus */
uint_t        size;          /* size of this register set */

对于 cgsix 示例,地址为 0。

映射设备

必须将设备映射到内存中才能进行测试。然后,可以使用 PROM 来验证设备是否正确操作,方法是使用数据传送命令来传送字节、字以及长字。如果可以通过 PROM 操作设备(即使使用受限的方法),则驱动程序也应该可以操作设备。

要设置设备以进行初始测试,请执行以下步骤:

  1. 确定设备所在的 S 总线插槽编号。

    在本示例中,cgsix 设备位于插槽 2 中。

  2. 确定设备使用的物理地址空间中的偏移。

    所使用的偏移特定于设备。在 cgsix 示例中,视频内存恰好在偏移 0x800000 开始。

  3. 使用 select-dev 可选择 S 总线设备以及在其中映射此设备的 map-in

    select-dev 采用设备路径的字符串作为其参数。map-in 采用偏移插槽编号以及大小作为映射的参数。与偏移一样,字节传送的大小也特定于设备。在 cgsix 示例中,大小设置为 0x100000 字节。

    在以下代码示例中,S 总线路径显示为字 select-dev 的参数,帧缓存器的偏移、插槽编号以及大小值显示为字 map-in 的参数。请注意 select-dev 参数中起始引号和 / 之间的空格。要使用的虚拟地址保留在栈的顶部。栈通过使用字 .s 进行显示。通过 constant 操作可为该栈分配一个名称。


    ok " sbus@1f,0" select-dev
    ok 800000 2 100000 map-in
    ok .s
    ffe98000
    ok constant fb
    

读取和写入

PROM 提供了许多 8 位、16 位以及 32 位操作。通常,c(字符)前缀表示 8 位(一字节)操作;w(字)前缀表示 16 位(二字节)操作;L(长字)前缀表示 32 位(四字节)操作。

后缀 ! 表示写入操作。写入操作用于从栈中取出前两项。第一项是地址,第二项是值。


ok 55 ffe98000 c!

后缀 @ 表示读取操作。读取操作用于从栈中取出地址。


ok ffe98000 c@
ok .s
55

后缀 ? 用于显示值,并且不会影响栈。


ok ffe98000 c?
55

尝试查询设备时,请务必谨慎。如果未正确设置映射,则尝试读取或写入可能会导致错误。为处理这些情况,提供了特殊字。例如,cprobewprobelprobe 会从给定地址进行读取,但是如果此位置不响应,则会返回零;如果此位置响应,则返回非零值。


ok fffa4000 c@
Data Access Error

ok fffa4000 cprobe
ok .s0

ok ffe98000 cprobe
ok .s
0 ffffffffffffffff

使用字 dump 可以显示内存的区域。这会采用 addresslength,并以字节为单位显示内存区域的内容。

在以下示例中,字 fill 用于使用某种模式填充视频内存。fill 会采用地址、要填充的字节数以及要使用的字节。对于字和长字,请分别使用 wfillLfill。此填充示例会导致 cgsix 基于传递的字节显示简单模式。


ok " /sbus" select-dev
ok 800000 2 100000 map-in
ok constant fb
ok fb 10000 ff fill
ok fb 20000 0 fill
ok fb 18000 55 fill
ok fb 15000 3 fill
ok fb 10000 5 fillok fb 5000 f9 fill

附录 B Solaris DDI/DKI 服务汇总

本附录介绍了 Solaris DDI/DKI 所提供的接口。这些说明并不具有完整性和确定性,也未提供详细的使用指导,而是旨在使用一般术语介绍函数功能。有关更多详细信息,请参见 physio(9F)。介绍的类别如下:

模块函数

模块函数包括:

mod_info

查询可装入模块

mod_install

添加可装入模块

mod_remove

删除可装入模块

设备信息树节点 (dev_info_t) 函数

设备信息树节点函数包括:

ddi_binding_name()

返回驱动程序绑定名称

ddi_dev_is_sid()

指示设备是否能自我识别

ddi_driver_major()

返回驱动程序主设备号

ddi_driver_name()

返回标准化驱动程序名称

ddi_node_name()

返回 devinfo 节点名称

ddi_get_devstate()

检查设备状态

ddi_get_instance()

获取设备实例编号

ddi_get_name()

返回驱动程序绑定名称

ddi_get_parent()

查找设备信息结构的父节点

ddi_root_node()

获取 dev_info 树的根

设备 (dev_t) 函数

设备函数包括:

ddi_create_minor_node()

为设备创建次要节点

ddi_getiminor()

从外部 dev_t 中获取内核内部次要设备号

ddi_remove_minor_node()

删除设备的次要节点

getmajor()

获取主设备号

getminor()

获取次要设备号

makedevice()

根据主设备号和次要设备号生成设备编号

属性函数

属性函数包括:

ddi_prop_exists()

检查属性是否存在

ddi_prop_free()

释放属性查找使用的资源

ddi_prop_get_int()

查找整数属性

ddi_prop_get_int64()

查找 64 位整数属性

ddi_prop_lookup_byte_array()

查找字节数组属性

ddi_prop_lookup_int_array()

查找整数数组属性

ddi_prop_lookup_int64_array()

查找 64 位整数数组属性

ddi_prop_lookup_string()

查找字符串属性

ddi_prop_lookup_string_array()

查找字符串数组属性

ddi_prop_remove()

删除设备的一个属性

ddi_prop_remove_all()

删除设备的所有属性

ddi_prop_undefine()

隐藏设备的一个属性

ddi_prop_update_byte_array()

创建或更新字节数组属性

ddi_prop_update_int()

创建或更新整数属性

ddi_prop_update_int64()

创建或更新 64 位整数属性

ddi_prop_update_int_array()

创建或更新整数数组属性

ddi_prop_update_int64_array()

创建或更新 64 位整数数组属性

ddi_prop_update_string()

创建或更新字符串属性

ddi_prop_update_string_array()

创建或更新字符串数组属性

表 B–1 过时的属性函数

过时的函数 

替代函数 

ddi_getlongprop()

请参见 ddi_prop_lookup()

ddi_getlongprop_buf()

ddi_prop_lookup()

ddi_getprop()

ddi_prop_get_int()

ddi_getproplen()

ddi_prop_lookup()

ddi_prop_create()

ddi_prop_lookup()

ddi_prop_modify()

ddi_prop_lookup()

ddi_prop_op()

ddi_prop_lookup()

设备软件状态函数

设备软件状态函数包括:

ddi_get_driver_private()

获取设备的专用数据区的地址

ddi_get_soft_state()

获取指向实例软状态结构的指针

ddi_set_driver_private()

设置设备的专用数据区的地址

ddi_soft_state_fini()

销毁驱动程序软状态结构

ddi_soft_state_free()

释放实例软状态结构

ddi_soft_state_init()

初始化驱动程序软状态结构

ddi_soft_state_zalloc()

分配实例软状态结构

内存分配和取消分配函数

内存分配和取消分配函数包括:

kmem_alloc()

分配内核内存

kmem_free()

释放内核内存

kmem_zalloc()

分配零填充的内核内存

以下函数可以分配和释放用于 DMA 的内存。请参见直接内存访问 (Direct Memory Access, DMA) 函数

ddi_dma_mem_alloc()

为 DMA 传送操作分配内存

ddi_dma_mem_free()

释放以前分配的 DMA 内存

以下函数可以分配和释放用于导出到用户空间的内存。请参见用户空间访问函数

ddi_umem_alloc()

分配按页对齐的内核内存

ddi_umem_free()

释放按页对齐的内核内存

表 B–2 过时的内存分配和取消分配函数

过时的函数 

替代函数 

ddi_iopb_alloc()

ddi_dma_mem_alloc()

ddi_iopb_free()

ddi_dma_mem_free()

ddi_mem_alloc()

ddi_dma_mem_alloc()

ddi_mem_free()

ddi_dma_mem_free()

内核线程控制和同步函数

内核线程控制和同步函数包括:

cv_broadcast()

唤醒所有等待线程

cv_destroy()

释放已分配的条件变量

cv_init()

分配条件变量

cv_signal()

唤醒一个等待线程

cv_timedwait()

等待事件,具有超时设置

cv_timedwait_sig()

等待事件或信号,具有超时设置

cv_wait()

等待事件

cv_wait_sig()

等待事件或信号

ddi_can_receive_sig()

确定当前线程是否可以接收信号

ddi_enter_critical()

进入关键控制区

ddi_exit_critical()

退出关键控制区

mutex_destroy()

销毁互斥锁

mutex_enter()

获取互斥锁

mutex_exit()

释放互斥锁

mutex_init()

初始化互斥锁

mutex_owned()

确定当前线程是否持有互斥锁

mutex_tryenter()

尝试获取互斥锁,但不等待

rw_destroy()

销毁读取器/写入器锁

rw_downgrade()

将持有的读取器/写入器锁从写入器降级为读取器

rw_enter()

获取读取器/写入器锁

rw_exit()

释放读取器/写入器锁

rw_init()

初始化读取器/写入器锁

rw_read_locked()

确定持有的读取器/写入器锁是用于读取还是用于写入

rw_tryenter()

尝试获取读取器/写入器锁,但不等待

rw_tryupgrade()

尝试将持有的读取器/写入器锁从读取器升级为写入器

sema_destroy()

销毁信号

sema_init()

初始化信号

sema_p()

递减信号并可能阻塞

sema_p_sig()

递减信号,但信号待处理时不阻塞

sema_tryp()

尝试递减信号,但不阻塞

sema_v()

递增信号并可能解除阻塞等待程序

任务队列管理函数

下面列出了任务队列管理函数。有关这些接口的更多信息,请参见 taskq(9F) 手册页。

ddi_taskq_create()

创建任务队列

ddi_taskq_destroy()

销毁任务队列

ddi_taskq_dispatch()

在任务队列中添加任务

ddi_taskq_wait()

等待暂挂的任务完成

ddi_taskq_suspend()

暂挂任务队列

ddi_taskq_suspended()

检查任务队列是否已暂挂

ddi_taskq_resume()

恢复暂挂的任务队列

中断函数

中断函数包括:

ddi_intr_add_handler(9F)

添加中断处理程序。

ddi_intr_add_softint(9F)

添加软中断处理程序。

ddi_intr_alloc(9F)

为指定类型的中断分配系统资源和中断向量。

ddi_intr_block_disable(9F)

禁用指定范围的中断。仅适用于 MSI。

ddi_intr_block_enable(9F)

启用指定范围的中断。仅适用于 MSI。

ddi_intr_clr_mask(9F)

如果已启用指定的中断,则清除中断屏蔽码。

ddi_intr_disable(9F)

禁用指定的中断。

ddi_intr_dup_handler(9F)

仅适用于 MSI-X。将分配的中断向量的地址和数据对复制到同一设备上未使用的中断向量。

ddi_intr_enable(9F)

启用指定的中断。

ddi_intr_free(9F)

针对指定的中断句柄释放系统资源和中断向量。

ddi_intr_get_cap(9F)

针对指定的中断返回中断功能标志。

ddi_intr_get_hilevel_pri(9F)

返回高级别中断的最低优先级别。

ddi_intr_get_navail(9F)

返回可用于特定硬件设备和给定中断类型的中断的数量。

ddi_intr_get_nintrs(9F)

针对给定的中断类型获取设备支持的中断数。

ddi_intr_get_pending(9F)

读取中断待处理位(如果主桥 (host bridge) 或设备支持)。

ddi_intr_get_pri(9F)

返回指定中断的当前软件优先级设置。

ddi_intr_get_softint_pri(9F)

返回指定中断的软中断优先级。

ddi_intr_get_supported_types(9F)

返回设备和主机均支持的硬件中断类型。

ddi_intr_remove_handler(9F)

删除指定的中断处理程序。

ddi_intr_remove_softint(9F)

删除指定的软中断处理程序。

ddi_intr_set_cap(9F)

为指定的中断设置 DDI_INTR_FLAG_LEVEL 或 DDI_INTR_FLAG_EDGE 标志。

ddi_intr_set_mask(9F)

如果已启用指定的中断,则设置中断屏蔽码。

ddi_intr_set_pri(9F)

设置指定中断的中断优先级别。

ddi_intr_set_softint_pri(9F)

更改指定软中断的相对软中断优先级。

ddi_intr_trigger_softint(9F)

触发指定的软中断。

要利用新框架的功能,请使用上述接口。请勿使用下表中列出的过时接口。保留这些过时接口只是出于兼容性目的。

表 B–3 过时的中断函数

过时的中断函数 

替代函数 

ddi_add_intr(9F)

包含三个步骤的过程: 

  1. ddi_intr_alloc(9F)

  2. ddi_intr_add_handler(9F)

  3. ddi_intr_enable(9F)

ddi_add_softintr(9F)

ddi_intr_add_softint(9F)

ddi_dev_nintrs(9F)

ddi_intr_get_nintrs(9F)

ddi_get_iblock_cookie(9F)

包含三个步骤的过程: 

  1. ddi_intr_alloc(9F)

  2. ddi_intr_get_pri(9F)

  3. ddi_intr_free(9F)

ddi_get_soft_iblock_cookie(9F)

包含三个步骤的过程: 

  1. ddi_intr_add_softint(9F)

  2. ddi_intr_get_softint_pri(9F)

  3. ddi_intr_remove_softint(9F)

ddi_intr_hilevel(9F)

包含三个步骤的过程: 

  1. ddi_intr_alloc(9F)

  2. ddi_intr_get_hilevel_pri(9F)

  3. ddi_intr_free(9F)

ddi_remove_intr(9F)

包含三个步骤的过程: 

  1. ddi_intr_disable(9F)

  2. ddi_intr_remove_handler(9F)

  3. ddi_intr_free(9F)

ddi_remove_softintr(9F)

ddi_intr_remove_softint(9F)

ddi_trigger_softintr(9F)

ddi_intr_trigger_softint(9F)

程控 I/O 函数

程控 I/O 函数包括:

ddi_dev_nregs()

返回设备的寄存器集数

ddi_dev_regsize()

返回设备寄存器的大小

ddi_regs_map_setup()

为寄存器地址空间设置映射

ddi_regs_map_free()

释放以前映射的寄存器地址空间

ddi_device_copy()

在设备寄存器之间复制数据

ddi_device_zero()

零填充设备

ddi_check_acc_handle()

检查数据访问句柄

ddi_get8()

从映射的内存、设备寄存器或 DMA 内存中读取一个 8 位数据

ddi_get16()

从映射的内存、设备寄存器或 DMA 内存中读取一个 16 位数据

ddi_get32()

从映射的内存、设备寄存器或 DMA 内存中读取一个 32 位数据

ddi_get64()

从映射的内存、设备寄存器或 DMA 内存中读取一个 64 位数据

ddi_put8()

向映射的内存、设备寄存器或 DMA 内存中写入一个 8 位数据

ddi_put16()

向映射的内存、设备寄存器或 DMA 内存中写入一个 16 位数据

ddi_put32()

向映射的内存、设备寄存器或 DMA 内存中写入一个 32 位数据

ddi_put64()

向映射的内存、设备寄存器或 DMA 内存中写入一个 64 位数据

ddi_rep_get8()

从映射的内存、设备寄存器或 DMA 内存中读取多个 8 位数据

ddi_rep_get16()

从映射的内存、设备寄存器或 DMA 内存中读取多个 16 位数据

ddi_rep_get32()

从映射的内存、设备寄存器或 DMA 内存中读取多个 32 位数据

ddi_rep_get64()

从映射的内存、设备寄存器或 DMA 内存中读取多个 64 位数据

ddi_rep_put8()

向映射的内存、设备寄存器或 DMA 内存中写入多个 8 位数据

ddi_rep_put16()

向映射的内存、设备寄存器或 DMA 内存中写入多个 16 位数据

ddi_rep_put32()

向映射的内存、设备寄存器或 DMA 内存中写入多个 32 位数据

ddi_rep_put64()

向映射的内存、设备寄存器或 DMA 内存中写入多个 64 位数据

ddi_peek8()

从某一位置慎重读取一个 8 位的值

ddi_peek16()

从某一位置慎重读取一个 16 位的值

ddi_peek32()

从某一位置慎重读取一个 32 位的值

ddi_peek64()

从某一位置慎重读取一个 64 位的值

ddi_poke8()

向某一位置慎重写入一个 8 位的值

ddi_poke16()

向某一位置慎重写入一个 16 位的值

ddi_poke32()

向某一位置慎重写入一个 32 位的值

ddi_poke64()

向某一位置慎重写入一个 64 位的值

可以始终使用上面列出的一般程控 I/O 函数,而不必使用下面的 memiopci_config 函数。但如果编译时已知访问类型,以下函数可作为备用函数。

ddi_io_get8()

从 I/O 空间的映射设备寄存器中读取一个 8 位数据

ddi_io_get16()

从 I/O 空间的映射设备寄存器中读取一个 16 位数据

ddi_io_get32()

从 I/O 空间的映射设备寄存器中读取一个 32 位数据

ddi_io_put8()

向 I/O 空间的映射设备寄存器中写入一个 8 位数据

ddi_io_put16()

向 I/O 空间的映射设备寄存器中写入一个 16 位数据

ddi_io_put32()

向 I/O 空间的映射设备寄存器中写入一个 32 位数据

ddi_io_rep_get8()

从 I/O 空间的映射设备寄存器中读取多个 8 位数据

ddi_io_rep_get16()

从 I/O 空间的映射设备寄存器中读取多个 16 位数据

ddi_io_rep_get32()

从 I/O 空间的映射设备寄存器中读取多个 32 位数据

ddi_io_rep_put8()

向 I/O 空间的映射设备寄存器中写入多个 8 位数据

ddi_io_rep_put16()

向 I/O 空间的映射设备寄存器中写入多个 16 位数据

ddi_io_rep_put32()

向 I/O 空间的映射设备寄存器中写入多个 32 位数据

ddi_mem_get8()

从内存空间的映射设备或 DMA 内存中读取一个 8 位数据

ddi_mem_get16()

从内存空间的映射设备或 DMA 内存中读取一个 16 位数据

ddi_mem_get32()

从内存空间的映射设备或 DMA 内存中读取一个 32 位数据

ddi_mem_get64()

从内存空间的映射设备或 DMA 内存中读取一个 64 位数据

ddi_mem_put8()

向内存空间的映射设备或 DMA 内存中写入一个 8 位数据

ddi_mem_put16()

向内存空间的映射设备或 DMA 内存中写入一个 16 位数据

ddi_mem_put32()

向内存空间的映射设备或 DMA 内存中写入一个 32 位数据

ddi_mem_put64()

向内存空间的映射设备或 DMA 内存中写入一个 64 位数据

ddi_mem_rep_get8()

从内存空间的映射设备或 DMA 内存中读取多个 8 位数据

ddi_mem_rep_get16()

从内存空间的映射设备或 DMA 内存中读取多个 16 位数据

ddi_mem_rep_get32()

从内存空间的映射设备或 DMA 内存中读取多个 32 位数据

ddi_mem_rep_get64()

从内存空间的映射设备或 DMA 内存中读取多个 64 位数据

ddi_mem_rep_put8()

向内存空间的映射设备或 DMA 内存中写入多个 8 位数据

ddi_mem_rep_put16()

向内存空间的映射设备或 DMA 内存中写入多个 16 位数据

ddi_mem_rep_put32()

向内存空间的映射设备或 DMA 内存中写入多个 32 位数据

ddi_mem_rep_put64()

向内存空间的映射设备或 DMA 内存中写入多个 64 位数据

pci_config_setup()

设置对 PCI 本地总线配置空间的访问

pci_config_teardown()

销毁对 PCI 本地总线配置空间的访问

pci_config_get8()

从 PCI 本地总线配置空间中读取一个 8 位数据

pci_config_get16()

从 PCI 本地总线配置空间中读取一个 16 位数据

pci_config_get32()

从 PCI 本地总线配置空间中读取一个 32 位数据

pci_config_get64()

从 PCI 本地总线配置空间中读取一个 64 位数据

pci_config_put8()

向 PCI 本地总线配置空间中写入一个 8 位数据

pci_config_put16()

向 PCI 本地总线配置空间中写入一个 16 位数据

pci_config_put32()

向 PCI 本地总线配置空间中写入一个 32 位数据

pci_config_put64()

向 PCI 本地总线配置空间中写入一个 64 位数据

表 B–4 过时的程控 I/O 函数

过时的函数 

替代函数 

ddi_getb()

ddi_get8()

ddi_getl()

ddi_get32()

ddi_getll()

ddi_get64()

ddi_getw()

ddi_get16()

ddi_io_getb()

ddi_io_get8()

ddi_io_getl()

ddi_io_get32()

ddi_io_getw()

ddi_io_get16()

ddi_io_putb()

ddi_io_put8()

ddi_io_putl()

ddi_io_put32()

ddi_io_putw()

ddi_io_put16()

ddi_io_rep_getb()

ddi_io_rep_get8()

ddi_io_rep_getl()

ddi_io_rep_get32()

ddi_io_rep_getw()

ddi_io_rep_get16()

ddi_io_rep_putb()

ddi_io_rep_put8()

ddi_io_rep_putl()

ddi_io_rep_put32()

ddi_io_rep_putw()

ddi_io_rep_put16()

ddi_map_regs()

ddi_regs_map_setup()

ddi_mem_getb()

ddi_mem_get8()

ddi_mem_getl()

ddi_mem_get32()

ddi_mem_getll()

ddi_mem_get64()

ddi_mem_getw()

ddi_mem_get16()

ddi_mem_putb()

ddi_mem_put8()

ddi_mem_putl()

ddi_mem_put32()

ddi_mem_putll()

ddi_mem_put64()

ddi_mem_putw()

ddi_mem_put16()

ddi_mem_rep_getb()

ddi_mem_rep_get8()

ddi_mem_rep_getl()

ddi_mem_rep_get32()

ddi_mem_rep_getll()

ddi_mem_rep_get64()

ddi_mem_rep_getw()

ddi_mem_rep_get16()

ddi_mem_rep_putb()

ddi_mem_rep_put8()

ddi_mem_rep_putl()

ddi_mem_rep_put32()

ddi_mem_rep_putll()

ddi_mem_rep_put64()

ddi_mem_rep_putw()

ddi_mem_rep_put16()

ddi_peekc()

ddi_peek8()

ddi_peekd()

ddi_peek64()

ddi_peekl()

ddi_peek32()

ddi_peeks()

ddi_peek16()

ddi_pokec()

ddi_poke8()

ddi_poked()

ddi_poke64()

ddi_pokel()

ddi_poke32()

ddi_pokes()

ddi_poke16()

ddi_putb()

ddi_put8()

ddi_putl()

ddi_put32()

ddi_putll()

ddi_put64()

ddi_putw()

ddi_put16()

ddi_rep_getb()

ddi_rep_get8()

ddi_rep_getl()

ddi_rep_get32()

ddi_rep_getll()

ddi_rep_get64()

ddi_rep_getw()

ddi_rep_get16()

ddi_rep_putb()

ddi_rep_put8()

ddi_rep_putl()

ddi_rep_put32()

ddi_rep_putll()

ddi_rep_put64()

ddi_rep_putw()

ddi_rep_put16()

ddi_unmap_regs()

ddi_regs_map_free()

inb()

ddi_io_get8()

inl()

ddi_io_get32()

inw()

ddi_io_get16()

outb()

ddi_io_put8()

outl()

ddi_io_put32()

outw()

ddi_io_put16()

pci_config_getb()

pci_config_get8()

pci_config_getl()

pci_config_get32()

pci_config_getll()

pci_config_get64()

pci_config_getw()

pci_config_get16()

pci_config_putb()

pci_config_put8()

pci_config_putl()

pci_config_put32()

pci_config_putll()

pci_config_put64()

pci_config_putw()

pci_config_put16()

repinsb()

ddi_io_rep_get8()

repinsd()

ddi_io_rep_get32()

repinsw()

ddi_io_rep_get16()

repoutsb()

ddi_io_rep_put8()

repoutsd()

ddi_io_rep_put32()

repoutsw()

ddi_io_rep_put16()

直接内存访问 (Direct Memory Access, DMA) 函数

DMA 函数包括:

ddi_dma_alloc_handle()

分配 DMA 句柄

ddi_dma_free_handle()

释放 DMA 句柄

ddi_dma_mem_alloc()

为 DMA 传送操作分配内存

ddi_dma_mem_free()

释放以前分配的 DMA 内存

ddi_dma_addr_bind_handle()

将地址绑定到 DMA 句柄

ddi_dma_buf_bind_handle()

将系统缓冲区绑定到 DMA 句柄

ddi_dma_unbind_handle()

取消绑定 DMA 句柄中的地址

ddi_dma_nextcookie()

检索后续的 DMA cookie

ddi_dma_getwin()

激活新 DMA 窗口

ddi_dma_numwin()

检索 DMA 窗口数

ddi_dma_sync()

同步 CPU 和 I/O 内存视图

ddi_check_dma_handle()

检查 DMA 句柄

ddi_dma_set_sbus64()

允许在 S 总线上进行 64 位传送

ddi_slaveonly()

报告设备是否安装在只允许从属访问的位置

ddi_iomin()

查找 DMA 的最小对齐和传送大小

ddi_dma_burstsizes()

查找 DMA 映射的允许突发大小

ddi_dma_devalign()

查找 DMA 映射对齐和最小传送大小

ddi_dmae_alloc()

获取 DMA 通道

ddi_dmae_release()

释放 DMA 通道

ddi_dmae_getattr()

获取 DMA 引擎属性

ddi_dmae_prog()

对 DMA 通道编程

ddi_dmae_stop()

终止 DMA 引擎操作

ddi_dmae_disable()

禁用 DMA 通道

ddi_dmae_enable()

启用 DMA 通道

ddi_dmae_getcnt()

获取剩余的 DMA 引擎计数

ddi_dmae_1stparty()

配置 DMA 通道层叠模式

ddi_dma_coff()

将 DMA cookie 转换为 DMA 句柄内的偏移

表 B–5 过时的直接内存访问 (Direct Memory Access, DMA) 函数

过时的函数 

替代函数 

ddi_dma_addr_setup()

ddi_dma_alloc_handle()ddi_dma_addr_bind_handle()

ddi_dma_buf_setup()

ddi_dma_alloc_handle()ddi_dma_buf_bind_handle()

ddi_dma_curwin()

ddi_dma_getwin()

ddi_dma_free()

ddi_dma_free_handle()

ddi_dma_htoc()

ddi_dma_addr_bind_handle()ddi_dma_buf_bind_handle()

ddi_dma_movwin()

ddi_dma_getwin()

ddi_dma_nextseg()

ddi_dma_nextcookie()

ddi_dma_segtocookie()

ddi_dma_nextcookie()

ddi_dma_setup()

ddi_dma_alloc_handle()ddi_dma_addr_bind_handle()ddi_dma_buf_bind_handle()

ddi_dmae_getlim()

ddi_dmae_getattr()

ddi_iopb_alloc()

ddi_dma_mem_alloc()

ddi_iopb_free()

ddi_dma_mem_free()

ddi_mem_alloc()

ddi_dma_mem_alloc()

ddi_mem_free()

ddi_dma_mem_free()

hat_getkpfnum()

ddi_dma_addr_bind_handle()ddi_dma_buf_bind_handle()ddi_dma_nextcookie()

用户空间访问函数

用户空间访问函数包括:

ddi_copyin()

将数据复制到驱动程序缓冲区

ddi_copyout()

从驱动程序中复制数据

uiomove()

使用 uio 结构复制内核数据

ureadc()

uio 结构中添加字符

uwritec()

uio 结构中删除字符

getminor()

获取次要设备号

ddi_model_convert_from()

确定数据模型类型是否不匹配

IOC_CONVERT_FROM()

确定是否需要转换 M_IOCTL 内容

STRUCT_DECL()

声明并初始化指向本机形式结构实例的结构句柄

STRUCT_HANDLE()

声明并初始化指向本机形式结构实例的结构句柄

STRUCT_INIT()

声明并初始化指向本机形式结构实例的结构句柄

STRUCT_SET_HANDLE()

声明并初始化指向本机形式结构实例的结构句柄

SIZEOF_PTR()

返回指定数据模型中指针的大小

SIZEOF_STRUCT()

返回指定数据模型中结构的大小

STRUCT_SIZE()

返回应用程序数据模型中结构的大小

STRUCT_BUF()

返回指向结构的本机模式实例的指针

STRUCT_FADDR()

返回指向结构的指定字段的指针

STRUCT_FGET()

返回应用程序数据模型中结构的指定字段

STRUCT_FGETP()

返回应用程序数据模型中结构的指定指针字段

STRUCT_FSET()

设置应用程序数据模型中结构的指定字段

STRUCT_FSETP()

设置应用程序数据模型中结构的指定指针字段

表 B–6 过时的用户空间访问函数

过时的函数 

替代函数 

copyin()

ddi_copyin()

copyout()

ddi_copyout()

ddi_getminor()

getminor()

用户进程事件函数

用户进程事件函数包括:

pollwakeup()

通知进程事件已发生

proc_ref()

获取进程中指向信号的句柄

proc_unref()

释放进程中指向信号的句柄

proc_signal()

向进程发送信号

用户进程信息函数

用户进程信息函数包括:

ddi_get_cred()

返回指向呼叫者的凭证结构的指针

drv_priv()

确定进程凭证权限

ddi_get_pid()

返回进程 ID

表 B–7 过时的用户进程信息函数

过时的函数 

替代函数 

drv_getparm()

ddi_get_pid()ddi_get_cred()

用户应用程序内核和设备访问函数

用户应用程序内核和设备访问函数包括:

ddi_dev_nregs()

返回设备的寄存器集数

ddi_dev_regsize()

返回设备寄存器的大小

ddi_devmap_segmap()devmap_setup()

使用 devmap 框架设置用户与设备内存之间的映射

devmap_devmem_setup()

将设备内存导出到用户空间

devmap_load()

验证内存地址转换

devmap_unload()

使内存地址转换无效

devmap_do_ctxmgt()

对映射执行设备上下文切换

devmap_set_ctx_timeout()

为上下文管理回叫设置超时值

devmap_default_access()

缺省驱动程序内存访问函数

ddi_umem_alloc()

分配按页对齐的内核内存

ddi_umem_free()

释放按页对齐的内核内存

ddi_umem_lock()

锁定内存页

ddi_umem_unlock()

解除锁定内存页

ddi_umem_iosetup()

设置对应用程序内存的 I/O 请求

devmap_umem_setup()

将内核内存导出到用户空间

ddi_model_convert_from()

确定数据模型类型是否不匹配

表 B–8 过时的用户应用程序内核和设备访问函数

过时的函数 

替代函数 

ddi_mapdev()

devmap_setup()

ddi_mapdev_intercept()

devmap_load()

ddi_mapdev_nointercept()

devmap_unload()

ddi_mapdev_set_device_acc_attr()

devmap()

ddi_segmap()

devmap()

ddi_segmap_setup()

devmap_setup()

hat_getkpfnum()

devmap()

ddi_mmap_get_model()

devmap()

与时间有关的函数

与时间有关的函数包括:

ddi_get_lbolt()

返回自重新引导以来的时钟周期数

ddi_get_time()

返回当前时间(以秒为单位)

ddi_periodic_add()

以纳秒为周期发出超时请求

ddi_periodic_delete()

取消以纳秒为周期发出超时请求

delay()

使执行延迟指定的时钟周期数

drv_hztousec()

将时钟周期转换为微秒

drv_usectohz()

将微秒转换为时钟周期

drv_usecwait()

繁忙-等待指定的时间间隔

gethrtime()

获取高分辨率时间

gethrvtime()

获取高分辨率 LWP 虚拟时间

timeout()

在指定的时间长度后执行函数

untimeout()

取消以前的超时函数调用

drv_getparm()

ddi_get_lbolt()ddi_get_time()

表 B–9 过时的与时间有关的函数

过时的函数 

替代函数 

drv_getparm()

ddi_get_lbolt()ddi_get_time()

电源管理函数

电源管理函数包括:

ddi_removing_power()

使用 DDI_SUSPEND 检查设备是否断电

pci_report_pmcap()

报告 PCI 设备的电源管理功能

pm_busy_component()

将组件标记为繁忙

pm_idle_component()

将组件标记为空闲

pm_raise_power()

提高组件的电源级别

pm_lower_power()

降低组件的电源级别

pm_power_has_changed()

向电源管理框架通知有关自治电源级别的更改信息

pm_trans_check()

设备电源开关建议检查

表 B–10 过时的电源管理函数

函数名 

说明 

ddi_dev_is_needed()

通知系统需要某一设备组件 

pm_create_components()

创建可管理电源的组件 

pm_destroy_components()

销毁可管理电源的组件 

pm_get_normal_power()

获取设备组件的正常电源级别 

pm_set_normal_power()

设置设备组件的正常电源级别 

故障管理函数

故障管理函数包括:

ddi_fm_init()

根据声明的故障管理功能分配和初始化资源

ddi_fm_fini()

清除为该设备实例分配的资源,以支持声明为 ddi_fm_init() 的故障管理功能

ddi_fm_capable()

返回当前为该设备实例设置的功能位掩码

ddi_fm_handler_register()

在 IO 故障管理框架中注册错误处理程序回调例程

ddi_fm_handler_unregister()

删除使用 ddi_fm_handler_register() 注册的错误处理程序回调例程

ddi_fm_acc_err_get()

返回访问句柄的错误状态

ddi_fm_dma_err_get()

返回 DMA 句柄的错误状态

ddi_fm_acc_err_clear()

清除访问句柄的错误状态

ddi_fm_dma_err_clear()

清除 DMA 句柄的错误状态

ddi_fm_ereport_post()

将编码的故障管理错误报告名称-值对列表排入队列,以传送到 Fault Manager 守护进程 fmd(1M)

ddi_fm_service_impact()

报告错误的影响

pci_ereport_setup()

初始化错误报告生成支持,并设置对 PCI、PCI/X 或 PCI Express 配置空间进行后续访问所用的资源

pci_ereport_teardown()

释放 pci_ereport_setup() 为该设备实例分配和设置的所有资源

pci_ereport_post()

扫描和发布任何 PCI、PCI/X 或 PCI Express 总线错误

内核统计信息函数

内核统计信息 (kstat) 函数包括:

kstat_create()

创建并初始化新的 kstat

kstat_delete()

从系统中删除 kstat

kstat_install()

向系统中添加完全初始化的 kstat

kstat_named_init()

初始化已命名的 kstat

kstat_runq_back_to_waitq()

记录从运行队列到等待队列的事务迁移

kstat_runq_enter()

记录向运行队列中添加的事务

kstat_runq_exit()

记录从运行队列中移除的事务

kstat_waitq_enter()

记录向等待队列中添加的事务

kstat_waitq_exit()

记录从等待队列中移除的事务

kstat_waitq_to_runq()

记录从等待队列到运行队列的事务迁移

内核日志记录和列显函数

内核日志记录和列显函数包括:

cmn_err()vcmn_err()

显示错误消息

ddi_report_dev()

通知设备

strlog()

将消息提交至日志驱动程序

ddi_dev_report_fault()

报告硬件故障

scsi_errmsg()

显示 SCSI 请求检测消息

scsi_log()

显示与 SCSI 设备有关的消息

scsi_vu_errmsg()

显示 SCSI 请求检测消息

缓存 I/O 函数

缓存 I/O 函数包括:

physio()

执行物理 I/O

aphysio()

执行异步物理 I/O

anocancel()

禁止取消异步 I/O 请求

minphys()

限制 physio() 缓冲区大小

biowait()

暂停以待处理方式完成块 I/O 的进程

biodone()

在完成缓冲区 I/O 传送后释放缓冲区并通知阻塞的线程

bioerror()

指示缓冲区头中的错误

geterror()

返回 I/O 错误

bp_mapin()

分配虚拟地址空间

bp_mapout()

取消分配虚拟地址空间

disksort()

使用单向电梯查找策略对缓冲区排序

getrbuf()

获取原始缓冲区头

freerbuf()

释放原始缓冲区头

biosize()

返回缓冲区结构的大小

bioinit()

初始化缓冲区结构

biofini()

取消初始化缓冲区结构

bioreset()

在 I/O 完成后重用专用的缓冲区头

bioclone()

克隆另一个缓冲区

biomodified()

检查缓冲区是否已修改

clrbuf()

删除缓冲区的内容

虚拟内存函数

虚拟内存函数包括:

ddi_btop()

将设备字节转换为页(向下舍入)

ddi_btopr()

将设备字节转换为页(向上舍入)

ddi_ptob()

将设备页转换为字节

btop()

将以字节表示的大小转换为以页表示的大小(向下舍入)

btopr()

将以字节表示的大小转换为以页表示的大小(向上舍入)

ptob()

将以页表示的大小转换为以字节表示的大小

表 B–11 过时的虚拟内存函数

过时的函数 

替代函数 

hat_getkpfnum()

devmap()ddi_dma_*_bind_handle()ddi_dma_nextcookie()

设备 ID 函数

设备 ID 函数包括:

ddi_devid_init()

分配设备 ID 结构

ddi_devid_free()

释放设备 ID 结构

ddi_devid_register()

注册设备 ID

ddi_devid_unregister()

注销设备 ID

ddi_devid_compare()

比较两个设备 ID

ddi_devid_sizeof()

返回设备 ID 的大小

ddi_devid_valid()

验证设备 ID

ddi_devid_str_encode()

将设备 ID 和 minor_name 编码为以 null 结尾的 ASCII 字符串,返回指向该字符串的指针

ddi_devid_str_decode()

从以前编码的字符串中解码设备 ID 和 minor_name,分配并返回指向提取部分的指针

ddi_devid_str_free()

释放 ddi_devid_* 函数返回的所有字符串

SCSI 函数

SCSI 函数包括:

scsi_probe()

探测 SCSI 设备

scsi_unprobe()

释放在初始探测期间分配的资源

scsi_alloc_consistent_buf()

为 SCSI DMA 分配 I/O 缓冲区

scsi_free_consistent_buf()

释放以前分配的 SCSI DMA I/O 缓冲区

scsi_init_pkt()

准备完整的 SCSI 包

scsi_destroy_pkt()

释放已分配的 SCSI 包及其 DMA 资源

scsi_setup_cdb()

设置 SCSI 命令描述符块 (command descriptor block, CDB)

scsi_transport()

启动 SCSI 命令

scsi_poll()

运行轮询 SCSI 命令

scsi_ifgetcap()

获取 SCSI 传输功能

scsi_ifsetcap()

设置 SCSI 传输功能

scsi_sync_pkt()

同步 CPU 和 I/O 内存视图

scsi_abort()

异常中止 SCSI 命令

scsi_reset()

重置 SCSI 总线或目标

scsi_reset_notify()

向目标驱动程序通知总线重置

scsi_cname()

解码 SCSI 命令

scsi_dname()

解码 SCSI 外围设备类型

scsi_mname()

解码 SCSI 消息

scsi_rname()

解码 SCSI 包完成原因

scsi_sname()

解码 SCSI 感知密钥

scsi_errmsg()

显示 SCSI 请求检测消息

scsi_log()

显示与 SCSI 设备有关的消息

scsi_vu_errmsg()

显示 SCSI 请求检测消息

scsi_hba_init()

SCSI HBA 系统初始化例程

scsi_hba_fini()

SCSI HBA 系统完成例程

scsi_hba_attach_setup()

SCSI HBA 连接例程

scsi_hba_detach()

SCSI HBA 分离例程

scsi_hba_probe()

缺省 SCSI HBA 探测函数

scsi_hba_tran_alloc()

分配传输结构

scsi_hba_tran_free()

释放传输结构

scsi_hba_pkt_alloc()

分配 scsi_pkt 结构

scsi_hba_pkt_free()

释放 scsi_pkt 结构

scsi_hba_lookup_capstr()

返回索引匹配功能字符串

表 B–12 过时的 SCSI 函数

过时的函数 

替代函数 

free_pktiopb()

scsi_free_consistent_buf()

get_pktiopb()

scsi_alloc_consistent_buf()

makecom_g0()

scsi_setup_cdb()

makecom_g0_s()

scsi_setup_cdb()

makecom_g1()

scsi_setup_cdb()

makecom_g5()

scsi_setup_cdb()

scsi_dmafree()

scsi_destroy_pkt()

scsi_dmaget()

scsi_init_pkt()

scsi_hba_attach()

scsi_hba_attach_setup()

scsi_pktalloc()

scsi_init_pkt()

scsi_pktfree()

scsi_destroy_pkt()

scsi_resalloc()

scsi_init_pkt()

scsi_resfree()

scsi_destroy_pkt()

scsi_slave()

scsi_probe()

scsi_unslave()

scsi_unprobe()

资源映射管理函数

资源映射管理函数包括:

rmallocmap()

分配资源映射

rmallocmap_wait()

分配资源映射,必要时等待

rmfreemap()

释放资源映射

rmalloc()

从资源映射中分配空间

rmalloc_wait()

从资源映射中分配空间,必要时等待

rmfree()

将空间重新释放到资源映射中

系统全局状态

ddi_in_panic()

确定系统是否处于紧急状态

实用程序函数

实用程序函数包括:

nulldev()

零返回函数

nodev()

错误返回函数

nochpoll()

不可轮询设备的错误返回函数

ASSERT()

表达式验证

bcopy()

在内核的地址位置之间复制数据

bzero()

清除给定字节数的内存

bcmp()

比较两个字节数组

ddi_ffs()

查找长整数中设置的第一位

ddi_fls()

查找长整数中设置的最后一位

swab()

以 16 位半字交换字节

strcmp()

比较两个以 null 结尾的字符串

strncmp()

比较两个以 null 结尾的字符串,长度有限制

strlen()

确定字符串中的非空字节数

strcpy()

将字符串从一个位置复制到另一个位置

strncpy()

将字符串从一个位置复制到另一个位置,长度有限制

strchr()

在字符串中查找字符

sprintf()vsprintf()

格式化内存中的字符

numtos()

将整数转换为十进制字符串

stoi()

将十进制字符串转换为整数

max()

返回两个整数中的较大值

min()

返回两个整数中的较小值

va_arg()

查找变量参数列表中的下一个值

va_copy()

复制变量参数列表的状态

va_end()

删除指向变量参数列表的指针

va_start()

查找指向变量参数列表开头的指针

附录 C 使设备驱动程序支持 64 位

本附录为要将设备驱动程序转换为支持 64 位内核的设备驱动程序编写人员提供信息。本附录还介绍了 32 位设备驱动程序和 64 位设备驱动程序之间的区别,并说明了将 32 位设备驱动程序转换为 64 位设备驱动程序的步骤。这些信息仅适用于常规字符设备驱动程序和块设备驱动程序。

本附录提供有关以下主题的信息:

64 位驱动程序设计简介

对于仅需支持 32 位内核的驱动程序,现有 32 位设备驱动程序将仍然有效,无需重新编译。但是,大多数设备驱动程序需要进行一些更改才能在 64 位内核中正确运行,且所有设备驱动程序都需要重新编译以创建 64 位驱动程序模块。本附录中的信息旨在指导您利用通用源代码来生成 32 位和 64 位环境的驱动程序,从而提高代码可移植性并降低维护工作量。

开始修改设备驱动程序以便使用 64 位环境之前,应了解 32 位环境与 64 位环境之间的区别。特别是必须熟悉 C 语言数据类型模型 ILP32 和 LP64。请参见下表。

表 C–1 ILP32 与 LP64 数据类型对比

C 类型 

ILP32 

LP64 

char

short

16 

16 

int

32 

32 

long

32 

64 

long long

64 

64 

float

32 

32 

double

64 

64 

long double

96 

128 

pointer

32 

64 

因 ILP32 与 LP64 之间的差异而导致的特定于驱动程序的问题是本附录的主题。更多常规主题将在《Solaris(64 位)开发者指南》中进行介绍。

除了清理常规代码以支持 LP64 的数据模型更改,驱动程序编写人员还必须提供对 32 位和 64 位应用程序的支持。

ioctl(9E)、devmap(9E) 和 mmap(9E) 入口点使应用程序和设备驱动程序之间可直接共享数据结构。如果这些数据结构在 32 位环境与 64 位环境中的大小不同,则必须修改入口点,以便驱动程序可确定应用程序的数据模型与内核的数据模型是否相同。如果数据模型不同,则可对数据结构进行调整。请参见对有 64 位处理能力的设备驱动程序的 I/O 控制支持32 位和 64 位数据结构宏将内核内存与用户映射相关联

在许多驱动程序中,只有少量 ioctl 需要这种处理。其他 ioctl 无需更改即可应用,只要这些 ioctl 传递的数据结构大小不变。

常规转换步骤

以下各节提供了有关转换驱动程序以在 64 位环境中运行的信息。驱动程序编写人员可能需要执行以下一项或多项任务:

  1. 使用硬件寄存器的固定宽度类型。

  2. 使用固定宽度的公共访问函数。

  3. 检查并扩展派生类型的用法。

  4. 检查 DDI 数据结构中更改的字段。

  5. 检查 DDI 函数中更改的参数。

  6. 根据需要修改用于处理用户数据的驱动程序入口点。

  7. 检查 x86 平台上使用 64 位 long 类型的结构。

下面详细说明这些步骤。

完成每一步骤后,请修复所有编译器警告,然后使用 lint 查找其他问题。对于 SC5.0(或更高)版本的 lint,要想找出 64 位问题,必须在使用该命令时指定 -Xarch=v9-errchk=longptr64 选项。请参见《Solaris(64 位)开发者指南》中有关使用和解释 lint 输出的说明。


注 –

请勿忽略 LP64 转换期间出现的编译警告。以前在 ILP32 环境中可安全忽略的警告现在可能表示比较严重的问题。


完成所有步骤后,同时将驱动程序作为 32 位和 64 位模块进行编译和测试。

使用硬件寄存器的固定宽度类型

许多处理硬件设备的设备驱动程序使用 C 数据结构说明硬件的布局。在 LP64 数据模型中,使用 long 或 unsigned long 类型定义硬件寄存器的数据结构几乎肯定不正确,因为 long 类型现在是 64 位。首先包括 <sys/inttypes.h>,然后将此类数据结构更新为使用 int32_tuint32_t,而不是 32 位设备数据的 long。 此方法可保留 32 位数据结构的二进制布局。例如,将以下代码:

struct device_regs {
    ulong_t        addr;
    uint_t         count;
};      /* Only works for ILP32 compilation */

更改为:

struct device_regs {
    uint32_t        addr;
    uint32_t        count;
};      /* Works for any data model */

使用固定宽度的公共访问函数

Solaris DDI 允许通过访问函数访问设备寄存器,以便可在多个平台间移植。DDI 公共访问函数以前以字节和字等单位指定数据大小。例如,ddi_getl( 9F) 用于访问 32 位。此函数不存在于 64 位 DDI 环境中,且已被替换为可指定要处理的位数的函数版本。

这些例程已添加到 Solaris 2.6 操作环境的 32 位内核中,以允许驱动程序编写人员采用其早期版本。例如,要在 32 位和 64 位内核间进行移植,驱动程序必须使用 ddi_get32(9F) 而不是 ddi_getl(9F) 来访问 32 位数据。

所有公共访问例程都被替换为其固定宽度的对等例程。有关详细信息,请参见 ddi_get8(9F)、ddi_put8(9F)、 ddi_rep_get8(9F) 和 ddi_rep_put8(9F) 手册页。

检查并扩展派生类型的用法

应尽可能使用系统派生的类型(如 size_t),以使产生的变量在各种函数间传递时都有效。而新的派生类型 uintptr_tintptr_t 是整数类型,应该用于指针。

固定宽度的整数类型用于表示二进制数据结构或硬件寄存器的显式大小,而基础 C 语言数据类型(如 int)仍然可用于循环计数器或文件描述符。

一些系统派生类型在 32 位系统上表示 32 位,但是在 64 位系统上表示 64 位。以此方式更改大小的派生类型包括: clock_tdaddr_tdev_tino_tintptr_toff_t size_tssize_ttime_tuintptr_ttimeout_id_t

设计使用这些派生类型的驱动程序时,请特别注意这些类型的用法,尤其是驱动程序将这些值指定给其他类型(如固定宽度类型)的变量时。

检查 DDI 数据结构中更改的字段

DDI 数据结构中某些字段的数据类型(如 buf(9S))已被更改。使用这些数据结构的驱动程序应确保正确使用这些字段。下面列出了变动很大的数据结构及字段。

buf 结构更改

以下列出的字段与传输大小(现在可超过 4 GB)有关。

size_t        b_bcount;          /* was type unsigned int */
size_t        b_resid;           /* was type unsigned int */
size_t        b_bufsize;         /* was type long */

ddi_dma_attr

ddi_dma_attr(9S) 结构定义 DMA 引擎和设备的属性。因为这些属性指定寄存器大小,所以使用了固定宽度的数据类型而不是基本类型。

ddi_dma_cookie 结构更改

uint32_t     dmac_address;    /* was type unsigned long */
size_t       dmac_size;       /* was type u_int */

ddi_dma_cookie(9S) 结构包含 32 位 DMA 地址,因此使用了固定宽度的数据类型来定义该地址。其大小已重新定义为 size_t

csi_arq_status 结构更改

uint_t    sts_rqpkt_state;         /* was type u_long */
uint_t    sts_rqpkt_statistics;    /* was type u_long */

此结构中的这些字段无需增大,已重新定义为 32 位。

scsi_pkt 结构更改

uint_t      pkt_flags;           /* was type u_long */
int     pkt_time;        /* was type long */
ssize_t     pkt_resid;           /* was type long */
uint_t      pkt_state;           /* was type u_long */
uint_t      pkt_statistics;      /* was type u_long */

由于 scsi_pkt(9S) 结构中的 pkt_flagspkt_statepkt_statistics 字段无需增大,因此这些字段已重新定义为 32 位整数。数据传输大小 pkt_resid 字段需要增大,已重新定义为 ssize_t

检查 DDI 函数中更改的参数

本节介绍已更改的 DDI 函数参数数据类型。

getrbuf() 参数更改

struct buf *getrbuf(int sleepflag);

在以前的发行版中,sleepflag 被定义为 long 类型。

drv_getparm() 参数更改

int drv_getparm(unsigned int parm, void *value_p);

在以前的发行版中,value_p 被定义为 unsigned long 类型。在 64 位内核中,drv_getparm(9F) 可提取 32 位和 64 位。此接口未定义这些量的数据类型,可能会发生简单的编程错误。

以下新例程提供更安全的替代方法:

clock_t       ddi_get_lbolt(void);
time_t        ddi_get_time(void);
cred_t        *ddi_get_cred(void);
pid_t         ddi_get_pid(void);

强烈要求驱动程序编写人员使用这些例程而不要使用 drv_getparm(9F)

delay()timeout() 参数更改

void delay(clock_t ticks);
timeout_id_t timeout(void (*func)(void *), void *arg, clock_t ticks);

delay(9F)timeout(9F) 例程的 ticks 参数已从 long 更改为 clock_t

rmallocmap()rmallocmap_wait() 参数更改

struct map *rmallocmap(size_t mapsize);
struct map *rmallocmap_wait(size_t mapsize);

rmallocmap(9F)rmallocmap_wait(9F) 例程的 mapsize 参数已从 ulong_t 更改为 size_t

scsi_alloc_consistent_buf() 参数更改

struct buf *scsi_alloc_consistent_buf(struct scsi_address *ap,
    struct buf *bp, size_t datalen, uint_t bflags,
    int (*callback )(caddr_t), caddr_t arg);

在以前的发行版中,datalen 被定义为 int 类型,bflags 被定义为 ulong 类型。

uiomove() 参数更改

int uiomove(caddr_t address, size_t nbytes,
    enum uio_rw rwflag, uio_t *uio_p);

nbytes 参数被定义为 long 类型,但是由于 nbytes 以字节为单位表示大小,因此 size_t 更适合。

cv_timedwait()cv_timedwait_sig() 参数更改

int cv_timedwait(kcondvar_t *cvp, kmutex_t *mp, clock_t timeout);
int cv_timedwait_sig(kcondvar_t *cvp, kmutex_t *mp,    clock_t timeout);

在以前的发行版中,cv_timedwait(9F)cv_timedwait_sig(9F) 例程的 timeout 参数被定义为 long 类型。由于这些例程表示时间周期,因此 clock_t 更适合。

ddi_device_copy() 参数更改

int ddi_device_copy(ddi_acc_handle_t src_handle,
    caddr_t src_addr, ssize_t src_advcnt,
    ddi_acc_handle_t dest_handle, caddr_t dest_addr,
    ssize_t dest_advcnt, size_t bytecount, uint_t dev_datasz);

src_advcnt dest_advcntdev_datasz 参数的类型已更改。这些参数以前分别被定义为 longlongulong_t 类型。

ddi_device_zero() 参数更改

int ddi_device_zero(ddi_acc_handle_t handle,
    caddr_t dev_addr, size_t bytecount, ssize_t dev_advcnt,
    uint_t dev_datasz):

在以前的发行版中,dev_advcnt 被定义为 long 类型,dev_datasz 被定义为 ulong_t 类型。

ddi_dma_mem_alloc() 参数更改

int ddi_dma_mem_alloc(ddi_dma_handle_t handle,
    size_t length, ddi_device_acc_attr_t *accattrp,
    uint_t flags, int (*waitfp)(caddr_t), caddr_t arg,
    caddr_t *kaddrp, size_t *real_length,
    ddi_acc_handle_t *handlep);

在以前的发行版中,lengthflagsreal_length 分别被定义为 uint_tulong_tuint_t * 类型。

修改处理数据共享的例程

如果设备驱动程序使用 ioctl(9E)devmap(9E)mmap(9E) 与 32 位应用程序共享包含 long 或指针类型的数据结构,而驱动程序已针对 64 位内核进行了重新编译,则数据结构的二进制布局将不兼容。 如果当前已按 long 类型定义了字段,并且未使用 64 位数据项,请更改数据结构,以使用仍为 32 位的数据类型(intunsigned int)。否则,驱动程序需要识别 ILP32 和 LP64 的不同结构形式,并确定应用程序与内核之间是否出现模型不匹配。

要处理潜在的数据模型差异,需要编写可直接与用户应用程序交互的 ioctl()devmap()mmap() 驱动程序入口点,以确定参数是否来自使用与内核具有相同数据模型的应用程序。

ioctl() 中的数据共享

要确定应用程序与驱动程序之间是否存在模型不匹配,驱动程序可使用 FMODELS 掩码确定 ioctl () mode 参数的模型类型。在 mode 中采用以下值之一来标识应用程序的数据模型:

对有 64 位处理能力的设备驱动程序的 I/O 控制支持中的代码示例说明如 何使用 ddi_model_convert_from(9F) 处理此情况。

devmap() 中的数据共享

要使 64 位驱动程序和 32 位应用程序共享内存,64 位驱动程序生成的二进制布局必须与 32 位应用程序使用的布局相同。要导出到应用程序的映射内存可能需要包含与数据模型有关的数据结构。

很少内存映射设备会面临此问题,因为在内核数据模型发生变化时设备寄存器不会改变大小。但是,一些将映射导出到用户地址空间的伪设备可能要将不同数据结构导出到 ILP32 或 LP64 应用程序。要确定是否出现了数据模型不匹配,devmap(9E) 可使用 model 参数说明应用程序期望的数据模型。model 参数被设置为以下值之一:

可将未经转换的模型参数传递到 ddi_model_convert_from(9F) 例程或 STRUCT_INIT()。请参见32 位和 64 位数据结构宏

mmap() 中的数据共享

由于 mmap(9E) 没有可用于传递数据模型信息的参数,因此可编写驱动程序的 mmap(9E) 入口点,以使用新的 DDI 函数 ddi_model_convert_from(9F)。此函数返回以下值之一,以指示应用程序的数据类型模型:

ioctl()devmap() 一样,可将模型位传递到 ddi_model_convert_from(9F) 以确定是否需要进行数据转换,或可将模型传递到 STRUCT_INIT()

或者,迁移设备驱动程序以支持 devmap(9E) 入口点。

检查 x86 平台上 64 位 Long 数据类型的结构

您应认真检查 x86 平台上使用 64 位 long 类型(如 uint64_t)的结构。32 位模式编译与 64 位模式编译的对齐方式和大小可能不同。请参考以下示例。

#include &lt;studio>
#include &ltsys>

struct myTestStructure {
        uint32_t        my1stInteger;
        uint64_t        my2ndInteger;
};

main()
{
        struct myTestStructure a;

        printf("sizeof myTestStructure is: %d\n", sizeof(a));
        printf("offset to my2ndInteger is: %d\n", (uintptr_t)&a.bar - (uintptr_t)&a);
}

在 32 位系统中,该示例显示以下结果:


sizeof myTestStructure is: 12
offset to my2ndInteger is: 4

而在 64 位系统中,该示例显示以下结果:


sizeof myTestStructure is: 16
offset to my2ndInteger is: 8

因此,32 位应用程序与 64 位应用程序对结构的理解不同。这样,尝试在 32 位和 64 位两种环境中使用同一结构可能会导致问题。这种情况经常发生,尤其是在通过 ioctl() 调用将结构传入或传出内核的情况下。

已知的 ioctl 接口

许多 ioctl(9E) 操作对一类设备驱动程序通用。例如,大多数磁盘驱动程序实现 dkio(7I) 系列的众多 ioctls。这些接口中有许多将数据结构复制到内核中,或从内核中复制出数据结构,在 LP64 数据模型中这些数据结构的一部分已更改了大小。以下部分列出了对于 dkiofdio(7I)fbio(7I)cdio(7I)mtio(7I) 系列的 ioctls,现在需要在 64 位驱动程序 ioctl 例程中显式进行转换的 ioctls

ioctl 命令

受影响的数据结构 

参考 

DKIOCGAPART

DKIOCSAPART

dk_map

dk_allmap

dkio(7I)

DKIOGVTOC

DKIOSVTOC

partition

vtoc

dkio(7I)

FBIOPUTCMAP

FBIOGETCMAP

fbcmap

fbio(7I)

FBIOPUTCMAPI

FBIOGETCMAPI

fbcmap_i

fbio(7I)

FBIOCCURSOR

FBIOSCURSOR

fbcursor

fbio(7I)

CDROMREADMODE1

CDROMREADMODE2

cdrom_read

cdio(7I)

CDROMCDDA

cdrom_cdda

cdio(7I)

CDROMCDXA

cdrom_cdxa

cdio(7I)

CDROMSUBCODE

cdrom_subcode

cdio(7I)

FDIOCMD

fd_cmd

fdio(7I)

FDRAW

fd_raw

fdio(7I)

MTIOCTOP

mtop

mtio(7I)

MTIOCGET

mtget

mtio(7I)

MTIOCGETDRIVETYPE

mtdrivetype_request

mtio(7I)

USCSICMD

uscsi_cmd

scsi_free_consistent_buf(9F)

设备大小

nblocks 属性按块设备驱动程序的每一片导出。此属性包含 512 字节块的数量,设备的每一片都支持这些块。nblocks 属性被定义为带符号的 32 位量,这就将片的最大大小限制为 1 TB。

每个磁盘提供 1 TB 以上存储空间的磁盘设备必须定义 Nblocks 属性,该属性仍应包含设备可支持的 512 字节块的数量。但是,Nblocks 是带符号的 64 位量,它除去了对磁盘空间的任何实际限制。

nblocks 属性现在已过时。所有磁盘设备都应提供 Nblocks 属性。

附录 D 控制台帧缓存器驱动程序

用于系统控制台的帧缓存器驱动程序必须提供相应的接口,以使系统能够在控制台上显示文本。Solaris OS 可提供增强的可视化 I/O 接口,以使内核终端仿真器能够直接在控制台帧缓存器上显示文本。本附录介绍如何向帧缓存器驱动程序添加必要的接口,以使该驱动程序能够与 Solaris 内核终端仿真器进行交互。

Solaris 控制台和内核终端仿真器

内核终端仿真器的作用是按照帧缓存器的屏幕高度、宽度和像素深度模式确定的正确位置和表示法在控制台帧缓存器中呈现文本。终端仿真器还可以驱动滚动、控制软件光标,以及解释 ANSI 终端转义序列。终端仿真器以 VGA 文本模式或像素模式访问控制台帧缓存器,具体取决于图形卡。要将您的帧缓存器驱动程序用作 Solaris 控制台帧缓存器驱动程序,它必须与 Solaris 内核终端仿真器兼容。目标平台是最重要的因素,它决定了您是否需要修改帧缓存器驱动程序,以使您的驱动程序与 Solaris 内核终端仿真器兼容。

x86 平台控制台通信

在 x86 平台上,Solaris 内核终端仿真器模块 (terminal emulator module, tem) 以独占方式使用 VGA 文本模式来与 vgatext 模块进行交互。vgatext 模块使用行业标准 VGA 文本模式与 x86 兼容的帧缓存器设备进行交互。由于 vgatext 模块已经支持控制台帧缓存器接口,因此 x86 帧缓存器驱动程序与内核 tem 模块兼容。不需要向 x86 帧缓存器驱动程序添加任何特殊的接口。

本附录的其余部分仅适用于 SPARC 平台。

SPARC 平台控制台通信

SPARC 帧缓存器驱动程序通常不在 VGA 文本模式下运行。SPARC 帧缓存器驱动程序通常需要发送像素图案,以描述显示的文本和图像。内核 tem 要求 SPARC 驱动程序支持特定的接口,以便在屏幕上呈现数据、执行滚动和显示文本光标。驱动程序实际上如何在屏幕上呈现 tem 发出的数据取决于具体的设备。驱动程序通常根据硬件和视频模式在视频内存中绘制数据。

Solaris OS 提供的一些接口使内核终端仿真器能够直接驱动兼容的控制台帧缓存器。将驱动程序转换为与内核终端仿真器兼容的好处在于:

SPARC 控制台帧缓存器驱动程序不需要与内核终端仿真器兼容。如果控制台帧缓存器驱动程序不与内核终端仿真器兼容,系统将使用 OpenBoot PROM 中的 FCode 终端仿真器。

控制台帧缓存器通过 EEPROM screen 环境变量进行识别。系统通过检查帧缓存器驱动程序是否导出 tem-support DDI 属性,来确定控制台帧缓存器是否与内核终端仿真器模块兼容。如果导出了 tem-support 属性,则系统将在系统引导过程中配置控制台时对帧缓存器驱动程序发出 VIS_DEVINIT I/O 控制 (ioctl) 命令。如果导出了 tem-support DDI 属性,同时 VIS_DEVINIT ioctl 命令成功并向 tem 返回了兼容版本号,那么,系统会将系统控制台配置为通过内核终端仿真器利用该帧缓存器驱动程序。有关 I/O 控制驱动程序入口点的信息,请参见 ioctl(9E) 手册页。

支持内核终端仿真器的 SPARC 驱动程序应导出 tem-support DDI 属性。该属性表示驱动程序支持内核终端仿真器。如果帧缓存器驱动程序导出了 tem-support DDI 属性,则早在引导过程中配置控制台时就将会处理该驱动程序。如果帧缓存器驱动程序未导出 tem-support 属性,则在引导过程中,可能不会那么早就处理该驱动程序。

tem-support

设置为 1 时,此 DDI 属性表示此驱动程序与控制台内核帧缓存器接口兼容。

内核终端仿真器模块通过两种主要接口与控制台帧缓存器驱动程序进行交互:

下节将提供详细信息。

控制台可视化 I/O 接口

内核终端仿真器通过两种接口与控制台帧缓存器驱动程序进行交互。在正常的系统活动期间(系统成功引导后),内核终端仿真器与控制台帧缓存器驱动程序之间的通信通过 ioctl 接口进行。在独立模式期间(系统引导之前或调试期间),内核终端仿真器与控制台帧缓存器驱动程序之间的通信通过轮询式 I/O 接口进行。内核终端仿真器与控制台帧缓存器驱动程序之间的所有活动都由内核终端仿真器启动,但控制台帧缓存器驱动程序用来通知内核终端仿真器有关视频模式方面的变化的回调函数除外。

visual_io(7I) 手册页中详细说明了控制台可视化 I/O 接口。有关视频模式更改回调函数的更多信息,请参见视频模式更改回调接口

I/O 控制接口

在正常的系统活动期间,内核终端仿真器通过下表中列出的 ioctl 接口与控制台帧缓存器驱动程序进行通信:

ioctl 名称

对应的数据结构 

说明 

VIS_DEVINIT

vis_devinit

初始化终端仿真器模块与帧缓存器之间的会话。请参见VIS_DEVINIT

VIS_DEVFINI

不适用 

终止终端仿真器模块与帧缓存器之间的会话。请参见VIS_DEFINI

VIS_CONSDISPLAY

vis_consdisplay

以矩形显示像素。请参见VIS_CONSDISPLAY

VIS_CONSCOPY

vis_conscopy

复制像素的矩形区(滚动)。请参见VIS_CONSCOPY

VIS_CONSCURSOR

vis_conscursor

显示或隐藏文本光标。请参见VIS_CONSCURSOR

VIS_PUTCMAP

vis_cmap

将终端仿真器模块色彩表发送到帧缓存器驱动程序。请参见VIS_PUTCMAP

VIS_GETCMAP

vis_cmap

从帧缓存器读取终端仿真器模块色彩表。请参见VIS_GETCMAP

轮询式 I/O 接口

轮询式 I/O 接口提供的功能与 VIS_CONSDISPLAYVIS_CONSCOPY VIS_CONSCURSOR ioctl 接口的功能相同。仅当操作系统处于静止状态并处于独立模式时,才调用轮询式 I/O 接口。有关更多信息,请参见在控制台帧缓存器驱动程序中实现轮询式 I/O

处于独立模式时,内核终端仿真器通过下表中列出的轮询式 I/O 接口与控制台帧缓存器驱动程序进行通信:

轮询式 I/O 函数 

对应的数据结构 

说明 

(*display)()

vis_consdisplay

以矩形显示像素。 

(*copy)()

vis_conscopy

复制像素的矩形区(滚动)。 

(*cursor)()

vis_conscursor

显示或隐藏文本光标。 

视频模式更改回调接口

在任何时候,控制台帧缓存器驱动程序与内核终端仿真器都必须就视频模式取得一致。视频模式涉及到控制台屏幕高度、宽度和深度(以像素为单位)。视频模式还涉及到内核终端仿真器与控制台帧缓存器之间的通信是在 VGA 文本模式还是在像素模式下进行。

为了让控制台帧缓存器驱动程序通知内核终端仿真器有关视频模式方面的变化,将使用下表中所述的 (*modechg_cb)() 内核终端仿真器回调函数的地址初始化控制台帧缓存器驱动程序:

回调函数 

对应的数据结构 

说明 

(*modechg_cb)()

vis_modechg_arg

vis_devinit

使终端仿真器模块与驱动程序视频模式(屏幕高度、宽度和像素深度)保持同步。 

在控制台帧缓存器驱动程序中实现可视化 I/O 接口

除了视频模式更改回调外,驱动程序与内核终端仿真器之间的所有活动都由 tem(terminal emulator module,终端仿真器模块)启动。这意味着,tem 将发出本文档中介绍的所有 ioctl 命令。以下各节提供了有关每个 ioctl 命令的实现详细信息。有关更多信息,请参见 visual_io(7I) 手册页和 /usr/include/sys/visual_io.h 头文件。有关视频模式更改回调函数的详细信息,请参见视频模式更改回调接口


注 –

每个 ioctl 命令都应确定是否已在 ioctl 标志参数中设置 FKIOCTL,如果未设置该位,则返回 EPERM


VIS_DEVINIT

VIS_DEVINIT ioctl 命令将帧缓存器驱动程序初始化为系统控制台设备。该 ioctl 将传递 vis_devinit 结构的地址。

tem 首先将其视频模式更改回调函数的地址装入 vis_devinit 结构的 modechg_cb 字段,再将其软状态装入 modechg_arg 字段。然后,tem 发出 VIS_DEVINIT ioctl 命令。接下来,帧缓存器驱动程序初始化自身,并通过设置 vis_devinit 结构中的 version widthheightlinebytesdepthmodepolledio 字段将自身的配置摘要返回到 tem。以下代码中显示了 vis_devinit 结构。

struct vis_devinit {
      /*
       * This set of fields are used as parameters passed from the
       * layered frame buffer driver to the terminal emulator.
       */
      int             version;        /* Console IO interface rev */
      screen_size_t   width;          /* Width of the device */
      screen_size_t   height;         /* Height of the device */
      screen_size_t   linebytes;      /* Bytes per scan line */
      int             depth;          /* Device depth */
      short           mode;           /* Display mode Mode */
      struct vis_polledio *polledio;  /* Polled output routines */
      /*
       * The following fields are used as parameters passed from the
       * terminal emulator to the underlying frame buffer driver.
       */
      vis_modechg_cb_t modechg_cb;   /* Video mode change callback */
      struct vis_modechg_arg *modechg_arg;  /* Mode change cb arg */
};

要在控制台帧缓存器驱动程序中实现 VIS_DEVINIT ioctl 命令,请按照以下通用步骤操作:

  1. 定义一个 struct 以包含特定于控制台的状态。该结构由控制台帧缓存器驱动程序专用。在本附录中,该结构称为 consinfoconsinfo 结构包含诸如以下的信息:

    • 位块传输 (blit) 缓冲区的当前大小

    • 指向位块传输 (blit) 缓冲区的指针

    • 色彩表信息

    • 呈现模式信息(如行间距)的驱动程序

    • 背景色

    • 视频内存地址

    • 终端仿真器回调地址

  2. 分配内存:

    1. 分配足够大的位块传输 (blit) 缓冲区,以便以最高的视频深度存储像素的合理的、缺省大小的矩形区。如果传入的请求超出缓冲区的大小,可以分配额外的内存。帧缓存器驱动程序的最大字体大小为 12×22。假设 DEFAULT_HEIGHT 为 12,DEFAULT_WIDTH 为 22,最大视频深度为 32,那么,缓冲区大小应为 8448 个字节 (DEFAULT_HEIGHT × DEFAULT_WIDTH × 32)。

    2. 分配 vis_polledio 结构。

    3. 分配缓冲区以用于保持光标。该缓冲区的大小应相当于最大字符的大小。该缓冲区的大小将不会发生变化。

  3. modechg_cbmodechg_ctx 获取 tem 的视频更改回调地址和回调上下文,并将这些信息存储在 consinfo 结构中。

  4. 使用轮询式显示、副本和光标函数的入口点地址填充 vis_polledio 结构。

  5. tem 传递给驱动程序的 vis_devinit 结构的字段中提供相应信息:

    1. version 字段设置为 VIS_CONS_REV,这是 /usr/include/sys/visual_io.h 头文件中定义的一个常量。

    2. mode 字段设置为 VIS_PIXEL

    3. polledio 字段设置为 vis_polledio 结构的地址。

    4. height 字段设置为视频模式高度(以像素为单位)。

    5. width 字段设置为视频模式宽度(以像素为单位)。

    6. depth 字段设置为帧缓存器像素深度,单位为字节(例如,32 位像素深度将为 4 个字节)。

    7. linebytes 字段设置为 height × width × depth 的值。

      将会使用 vis_devinit 结构将这些信息从驱动程序发送到 tem。通过这些信息,终端仿真器可知道如何呈现信息以及如何将信息传递给图形驱动程序。

    每当控制台帧缓存器驱动程序更改视频模式(具体而言,heightwidthdepth)时,它都必须调用 tem 的视频模式更改回调函数,以更新 vis_devinit 结构,并将此结构传递回给终端仿真器。终端仿真器将其模式更改回调函数地址传入 vis_devinit 结构的 modechg_cb 字段。模式更改回调函数具有以下函数签名:

    typedef void (*vis_modechg_cb_t)
          (struct vis_modechg_arg *, struct vis_devinit *);

    如前面的 typedef 中所述,模式更改回调函数使用两个参数。第一个参数为 modechg_arg,第二个参数为 vis_devinit 结构。modechg_arg 会在 VIS_DEVINIT ioctl 命令初始化期间从 tem 发送到驱动程序。驱动程序必须通过每个视频模式更改回调将 modechg_arg 发送回给 tem

  6. 初始化内核控制台的上下文。具体的要求会随图形设备的功能而异。例如,该初始化的步骤可能包括:设置绘制引擎状态、初始化调色板,或者定位和映射视频内存或呈现引擎,以便数据能够以位块传输到屏幕。

  7. vis_devinit 结构返回给调用方。

VIS_DEFINI

VIS_DEFINI ioctl 命令可释放驱动程序的控制台资源,并完成会话。

要在控制台帧缓存器驱动程序中实现 VIS_DEVFINI ioctl 命令,请按照以下通用步骤操作:

  1. 重置控制台帧缓存器驱动程序状态。

  2. 清除轮询式 I/O 入口点和内核终端仿真器视频更改函数回调地址。

  3. 释放内存。

VIS_CONSDISPLAY

VIS_CONSDISPLAY ioctl 命令可在指定的位置显示像素矩形区。这种显示方式又称为以位块传输 (blitting) 矩形。vis_consdisplay 结构包含以驱动程序和 tem 使用的视频深度呈现矩形所必需的信息。以下代码中显示了 vis_consdisplay 结构。

struct vis_consdisplay {
      screen_pos_t    row;      /* Row (in pixels) to display data at */
      screen_pos_t    col;      /* Col (in pixels) to display data at */
      screen_size_t   width;    /* Width of data (in pixels) */
      screen_size_t   height;   /* Height of data (in pixels) */
      unsigned char   *data;    /* Address of pixels to display */
      unsigned char   fg_color; /* Foreground color */
      unsigned char   bg_color; /* Background color */
};

要在控制台帧缓存器驱动程序中实现 VIS_CONSDISPLAY ioctl 命令,请按照以下通用步骤操作:

  1. 复制 vis_consdisplay 结构。

  2. 验证显示参数。如果任一显示参数超出范围,则会返回错误。

  3. 计算要以位块传输到视频内存的矩形的大小。根据执行 VIS_DEVINIT 期间创建的位块传输 (blit) 缓冲区大小验证此大小。如果需要,为位块传输 (blit) 缓冲区分配额外的内存。

  4. 检索位块传输 (blit) 数据。内核终端仿真器已在议定的像素深度准备了此数据。该深度与执行 VIS_DEVINIT 期间 tem 传递的像素深度相同。每当设备驱动程序通过 tem 的回调更改视频模式时,都会更新像素深度。典型的像素深度为 8 位索引色彩表和 32 位真彩 (TrueColor)。

  5. 使所有用户上下文无效,以使用户应用程序不能通过用户内存映射同时访问帧缓存器硬件。在轮询式 I/O 模式下,既不允许也没有必要执行此步骤,因为用户应用程序并没有运行。请务必持有锁,以便在完成 VIS_CONSDISPLAY ioctl 之前,用户无法通过缺页恢复映射。

  6. 建立特定于驱动程序的控制台呈现上下文。

  7. 如果帧缓存器在 8 位索引色彩模式下运行,请恢复 tem 以前通过 VIS_PUTCMAP ioctl 设置的内核控制台色彩表。建议使用延迟 ( lazy) 色彩表装入方案,以优化性能。在延迟 (lazy) 方案中,控制台帧缓存器只恢复自发出 VIS_DEVINIT ioctl 以来实际使用的色彩。

  8. tem 发送的像素坐标上显示 tem 传出的数据。您可能需要转换 RGB 像素数据字节顺序。

VIS_CONSCOPY

VIS_CONSCOPY ioctl 命令可将像素矩形区从一个位置复制到另一个位置。该 ioctl 的用途之一就是执行滚动。

要在控制台帧缓存器驱动程序中实现 VIS_CONSCOPY ioctl 命令,请按照下面的通用步骤操作:

  1. 复制 vis_conscopy 结构。vis_conscopy 结构描述源和目标矩形大小与位置。

  2. 验证显示参数。如果任一显示参数超出范围,则会返回错误。

  3. 使所有用户上下文无效,以使用户应用程序不能通过用户内存映射同时访问帧缓存器硬件。在轮询式 I/O 模式下,既不允许也没有必要执行此步骤,因为用户应用程序并没有运行。请务必持有锁,以便在完成 VIS_CONSDISPLAY ioctl 之前,用户无法通过缺页恢复映射。

  4. 调用函数以复制矩形。


    注 –

    为实现最佳性能,请使用图形设备的呈现引擎来实现复制功能。您需要确定如何执行驱动程序内的上下文管理以设置呈现引擎,从而实现最佳性能。


VIS_CONSCURSOR

VIS_CONSCURSOR ioctl 命令可显示或隐藏光标。以下代码中显示了 vis_conscursor 结构。

struct vis_conscursor {
      screen_pos_t    row;      /* Row to display cursor (in pixels) */
      screen_pos_t    col;      /* Col to display cursor (in pixels) */
      screen_size_t   width;    /* Width of cursor (in pixels) */
      screen_size_t   height;   /* Height of cursor (in pixels) */
      color_t         fg_color; /* Foreground color */
      color_t         bg_color; /* Background color */
      short           action;   /* Show or Hide cursor */
};

要在控制台帧缓存器驱动程序中实现 VIS_CONSCOPY ioctl 命令,请按照下面的通用步骤操作:

  1. 从内核终端仿真器复制 vis_conscursor 结构。

  2. 验证显示参数。如果任一显示参数超出范围,则会返回错误。

  3. 使所有用户上下文无效,以使用户应用程序不能通过用户内存映射同时访问帧缓存器硬件。在轮询式 I/O 模式下,既不允许也没有必要执行此步骤,因为用户应用程序并没有运行。请务必持有锁,以便在完成 VIS_CONSDISPLAY ioctl 之前,用户无法通过缺页恢复映射。

  4. 终端仿真器可通过以下两个操作之一调用 VIS_CONSCOPY ioctlSHOW_CURSORHIDE_CURSOR。以下步骤介绍如何通过读取和写入视频内存实现此功能。您可能也可使用呈现引擎来完成此工作。是否能够使用呈现引擎取决于帧缓存器硬件。

    执行以下步骤可实现 SHOW_CURSOR 功能:

    1. 将像素保存到要在其中绘制光标的矩形内。隐藏光标时将需要使用这些保存的像素。

    2. 扫描要在其中绘制光标的矩形界定的屏幕上的所有像素。在此矩形中,将与指定光标前景色 (fg_color) 匹配的像素替换为白色像素。将与指定光标背景色 (bg_color) 匹配的像素替换为黑色像素。视觉效果为黑色光标悬停在白色文本上。此方法适用于文本的任何前景色和背景色。尝试根据色彩表位置进行反色是不切实际的。也没有必要使用更复杂的策略,例如使用 HSB(Hue, Saturation, Brightness,色调、饱和度和亮度)色彩模式进行反色。

    要实现 HIDE_CURSOR 功能,请将光标矩形下方的像素替换为通过前面的 SHOW_CURSOR 操作保存的像素。

VIS_PUTCMAP

VIS_PUTCMAP ioctl 命令可建立控制台色彩表。终端仿真器调用此函数以设置内核的色彩表。以下代码中显示了 vis_cmap 结构。该结构只适用于 8 位索引色彩模式。

struct vis_cmap {
      int             index;  /* Index into colormap to start updating */
      int             count;  /* Number of entries to update */
      unsigned char   *red;   /* List of red values */
      unsigned char   *green; /* List of green values */
      unsigned char   *blue;  /* List of blue values */
};

VIS_PUTCMAP ioctl 命令与 FBIOPUTCMAP 命令类似。VIS_PUTCMAP 命令特定于与帧缓存器终端仿真器兼容的控制台代码。

VIS_GETCMAP

终端仿真器可调用 VIS_GETCMAP ioctl 命令来检索控制台色彩表。

在控制台帧缓存器驱动程序中实现轮询式 I/O

轮询式 I/O 接口在驱动程序中作为函数实现,并由内核终端仿真器直接调用。在执行 VIS_DEVINIT ioctl 命令期间,驱动程序会将其轮询式 I/O 入口点的地址传递给终端仿真器。VIS_DEVINIT 命令由终端仿真器启动。

以下代码中显示了 vis_polledio 结构。

typedef void * vis_opaque_arg_t;

struct vis_polledio {
      struct vis_polledio_arg *arg;
      void    (*display)(vis_opaque_arg_t, struct vis_consdisplay *);
      void    (*copy)(vis_opaque_arg_t, struct vis_conscopy *);
      void    (*cursor)(vis_opaque_arg_t, struct vis_conscursor *);
};

轮询式 I/O 接口提供的功能与 VIS_CONSDISPLAYVIS_CONSCOPY VIS_CONSCURSOR ioctl 接口的功能相同。要实现轮询式 I/O 接口,应该按照上面针对相应 ioctl 命令所述的相同步骤操作。轮询式 I/O 接口必须严格遵循本节其余部分所述的其他限制。

仅当操作系统处于静止状态并处于独立模式时,才调用轮询式 I/O 接口。每当用户进入 OpenBoot PROM 或 kmdb 调试器时,或者系统出现紧急情况时,系统就会进入独立模式。此时,只有一个 CPU 和一个线程处于活动状态。其他所有 CPU 和线程均会停止。分时、DDI 中断和系统服务都将关闭。

独立模式会严重限制驱动程序的功能,但会简化驱动程序同步要求。例如,用户应用程序无法通过在轮询式 I/O 例程中对控制台帧缓存器驱动程序进行内存映射这样的方式来访问该驱动程序。

在独立模式下,控制台帧缓存器驱动程序不得执行下列任一操作:

遵守这些限制并不困难,因为操作轮询式 I/O 函数相对较为简单。例如,在使用呈现引擎时,控制台帧缓存器驱动程序可以轮询设备中的某个位,而不是等待中断。驱动程序可以使用预先分配的内存来呈现位块传输 (blit) 数据。DDI 或 LDI 接口应该是不需要的。

特定于帧缓存器的配置模块

当特定于驱动程序的 fbconfig() 模块导致分辨率或颜色深度发生变化时,该 fbconfig() 模块必须向帧缓存器驱动程序发送 ioctl。此 ioctl 将触发帧缓存器驱动程序,使其使用新的屏幕大小和深度调用终端仿真器的模式更改回调函数。在任何时候,帧缓存器驱动程序与终端仿真器必须就视频模式取得一致。如果帧缓存器驱动程序与终端仿真器未就视频模式取得一致,屏幕上的信息将难以辩认,从而没有意义。

特定于 X 窗口系统帧缓存器的 DDX 模块

当 X 窗口系统退出命令行时,帧缓存器的 DDX 模块必须向帧缓存器驱动程序发送 ioctl。此 ioctl 将触发帧缓存器驱动程序,使其调用终端仿真器的模式更改回调函数。如果 X 窗口系统在启动之后,退出之前更改了视频分辨率,这种通信将使帧缓存器驱动程序与终端仿真器就视频模式取得一致。在任何时候,帧缓存器驱动程序与终端仿真器必须就视频模式取得一致。如果帧缓存器驱动程序与终端仿真器未就视频模式取得一致,屏幕上的信息将难以辩认,从而没有意义。

开发、测试和调试控制台帧缓存器驱动程序

在活动的系统上调试控制台帧缓存器驱动程序可能会遇到问题。

本节提供了一些建议,可帮助您开发、测试和调试控制台帧缓存器驱动程序。

测试 I/O 控制接口

要测试 ioctl 命令,请额外创建一些可通过用户应用程序调用的 ioctl 入口点。确保正确地复制参数。使用 ddi_copyin(9F)ddi_copyout(9F) 例程在用户地址空间来回传输数据。然后编写一个应用程序,以验证呈现、滚动和光标行为。这样,在您开发和测试这些 ioctl 命令时,它们就不会影响您的控制台。

为确保 ioctl 命令正常工作,请引导系统,然后登录。检查在执行 prstat(1M)、ls(1)、 vi(1) 和 man(1) 等命令时,是否能够得到预期的行为。

执行以下脚本以验证 ANSI 颜色是否正常工作:

#!/bin/bash
printf "\n\n\n\e[37;40m             Color List       \e[m\n\n"
printf "\e[30m Color 30 black\e[m\n"
printf "\e[31m Color 31 red\e[m\n"
printf "\e[32m Color 32 green\e[m\n"
printf "\e[33m Color 33 yellow\e[m\n"
printf "\e[34m Color 34 blue\e[m\n"
printf "\e[35m Color 35 purple\e[m\n"
printf "\e[36m Color 36 cyan\e[m\n"
printf "\e[37m Color 37 white\e[m\n\n"
printf "\e[40m Backlight 40 black \e[m\n"
printf "\e[41m Backlight 41 red   \e[m\n"
printf "\e[34;42m Backlight 42 green \e[m\n"
printf "\e[43m Backlight 43 yellow\e[m\n"
printf "\e[37;44m Backlight 44 blue  \e[m\n"
printf "\e[45m Backlight 45 purple\e[m\n"
printf "\e[30;46m Backlight 46 cyan  \e[m\n"
printf "\e[30;47m Backlight 47 white \e[m\n\n"

测试轮询式 I/O 接口

轮询式 I/O 接口仅在以下情况下可用:

轮询式 I/O 接口仅在引导过程的特定点可用。运行系统之前从 OpenBoot PROM 发出的轮询式 I/O 请求不会呈现。同样,配置控制台之前发出的 kmdb 提示也不会呈现。

要测试轮询式 I/O 接口,请使用 L1+A 击键序列进入 OpenBoot PROM。要验证是否正在使用轮询式 I/O 接口,请在 OpenBoot PROM ok 提示符下键入以下命令:


ok 1b emit ." [32m This is a test" 1b emit ." [m"

如果以下叙述属实,则表明轮询式 I/O 接口工作正常:

测试视频模式更改回调函数

要确定视频模式更改回调函数是否正常工作,请登录系统,然后使用 fbconfig(1M) 多次更改帧缓存器的分辨率和深度。如果控制台能够继续正常显示文本,则表明视频模式更改回调函数工作正常。内核终端仿真器可能会调整字体大小以适合不同的屏幕大小,但这并不会对控制台帧缓存器驱动程序有重大影响。

要确定 X 窗口系统和控制台帧缓存器驱动程序是否正常交互,请在 X 窗口系统与命令行之间进行多次切换,同时,以不同的方式修改 X 窗口系统的视频分辨率和命令行分辨率。如果 X 窗口系统退出,并且控制台字符不能正常显示,则要么是 X 窗口系统未将视频模式已更改的情况通知给驱动程序控制台代码,要么是驱动程序未调用内核终端仿真器的视频模式更改回调函数。

有关测试控制台帧缓存器驱动程序的其他建议

在引导过程中,如果系统找不到或者无法成功装入与内核终端仿真器兼容的帧缓存器驱动程序,系统将向 /var/adm/messages 发送消息。要监视这些消息,请在单独的窗口中键入以下命令:


% tail -f /var/adm/messages

为避免调试驱动程序时 USB 发生问题,请更改 EEPROM input-device NVRAM 配置参数,以使用串行端口来代替键盘。有关此参数的更多信息,请参见 eeprom(1M) 手册页。