VGA 仲裁器¶
图形设备通过 I/O 或内存空间中的范围进行访问。虽然大多数现代设备允许重新定位这些范围,但一些在 PCI 上实现的“传统” VGA 设备通常具有与其在 ISA 上相同的“硬解码”地址。有关更多详细信息,请参阅“PCI 总线绑定到 IEEE Std 1275-1994 标准启动(初始化配置)固件修订版 2.1”第 7 节,传统设备。
当同一台机器上存在多个传统设备时,X 服务器 [0] 中的资源访问控制 (RAC) 模块用于传统的 VGA 仲裁任务(以及其他总线管理任务)。但是,当这些设备尝试被不同的用户空间客户端访问时(例如,并行运行两个服务器),就会出现问题。它们的地址分配冲突。此外,理想情况下,作为用户空间应用程序,控制总线资源不是 X 服务器的角色。因此,需要 X 服务器之外的仲裁方案来控制这些资源的共享。本文档介绍了为 Linux 内核实现的 VGA 仲裁器的操作。
vgaarb 内核/用户空间 ABI¶
vgaarb 是 Linux 内核的一个模块。首次加载时,它会扫描所有 PCI 设备,并将 VGA 设备添加到仲裁中。然后,仲裁器启用/禁用 VGA 传统指令在不同设备上的解码。不想/不需要使用仲裁器的设备可以通过调用 vga_set_legacy_decoding()
显式地告诉它。
内核向客户端导出一个字符设备接口 (/dev/vga_arbiter),它具有以下语义
- 打开
打开仲裁器的用户实例。默认情况下,它连接到系统的默认 VGA 设备。
- 关闭
关闭用户实例。释放用户进行的锁定。
- 读取
返回一个字符串,指示目标的状态,例如
“<card_ID>,decodes=<io_state>,owns=<io_state>,locks=<io_state> (ic,mc)”
IO 状态字符串的形式为 {io,mem,io+mem,none},mc 和 ic 分别是内存和 io 锁定计数(仅用于调试/诊断)。“decodes”表示卡当前解码的内容,“owns”表示当前在其上启用的内容,“locks”表示此卡锁定的内容。如果卡被拔出,我们会得到 “invalid” 作为 card_ID,并且对于任何命令都会返回 -ENODEV 错误,直到目标设置为新卡。
- 写入
向仲裁器写入命令。命令列表
- target <card_ID>
切换目标到卡 <card_ID> (见下文)
- lock <io_state>
获取目标上的锁 (“none” 是一个无效的 io_state)
- trylock <io_state>
非阻塞地获取目标上的锁 (如果不成功,则返回 EBUSY)
- unlock <io_state>
释放目标上的锁
- unlock all
释放此用户持有的目标上的所有锁(尚未实现)
- decodes <io_state>
设置卡的传统解码属性
- poll
如果任何卡上发生变化(不仅仅是目标),则触发事件
card_ID 的形式为 “PCI:domain:bus:dev.fn”。它可以设置为 “default” 以返回到系统默认卡(待办事项:尚未实现)。目前,仅支持 PCI 作为前缀,但用户空间 API 在将来可能会支持其他总线类型,即使当前的内核实现不支持。
关于锁的说明
驱动程序跟踪哪个用户在哪个卡上拥有哪些锁。它支持像内核一样的堆叠。这使实现变得有点复杂,但使仲裁器对用户空间问题更具容忍性,并能够在进程死亡的所有情况下正确清理。目前,对于仲裁器的给定用户(文件描述符实例),最多可以同时从用户空间为 16 张卡发出锁。
对于热插拔设备,有一个钩子 - pci_notify() - 用于通知它们在系统中被添加/删除,并自动在仲裁器中添加/删除。
DRM、vgacon 或其他驱动程序想要使用它时,仲裁器还有一个内核 API。
内核接口¶
-
int vga_get_interruptible(struct pci_dev *pdev, unsigned int rsrc)¶
参数
struct pci_dev *pdev
VGA 卡的 pci 设备,如果为 NULL,则表示系统默认设备
unsigned int rsrc
要获取和锁定的资源的位掩码
描述
vga_get 的快捷方式,interruptible 设置为 true。
成功后,再次使用 vga_put()
释放 VGA 资源。
参数
struct pci_dev *pdev
VGA 卡的 pci 设备,如果为 NULL,则表示系统默认设备
unsigned int rsrc
要获取和锁定的资源的位掩码
描述
vga_get 的快捷方式,interruptible 设置为 false。
成功后,再次使用 vga_put()
释放 VGA 资源。
-
struct pci_dev *vga_default_device(void)¶
返回默认的 VGA 设备,用于 vgacon
参数
void
无参数
描述
这可以由平台定义。默认实现相当愚蠢,可能只能在单个 VGA 卡设置和/或 x86 平台上正常工作。
如果您的 VGA 默认设备不是 PCI,则必须在此处返回 NULL。在这种情况下,我假设它不会与任何 PCI 卡冲突。如果这不是真的,我将必须定义两个 arch 钩子来启用/禁用 VGA 默认设备(如果可能)。这对于真正的 _ISA_ VGA 卡,以及 PCI 卡来说可能是一个问题。我现在不知道如何处理该卡。它们的 IO 可以完全禁用吗?如果不能,那么我认为这是一个具有适当的 arch 钩子告诉我们它的问题,所以我们基本上不允许任何人成功调用 vga_get()
。
-
int vga_remove_vgacon(struct pci_dev *pdev)¶
停用 VGA 控制台
参数
struct pci_dev *pdev
PCI 设备。
描述
如果 pdev 是默认的 VGA 设备,则取消绑定和注销 vgacon。GPU 驱动程序可以在初始化时调用它,以确保 vgacon 完成的 VGA 寄存器访问不会干扰设备。
-
int vga_get(struct pci_dev *pdev, unsigned int rsrc, int interruptible)¶
获取并锁定 VGA 资源
参数
struct pci_dev *pdev
VGA 卡的 PCI 设备,如果为 NULL,则表示系统默认设备
unsigned int rsrc
要获取和锁定的资源的位掩码
int interruptible
阻塞应该被信号中断吗?
描述
获取给定卡的 VGA 资源,并将这些资源标记为已锁定。如果请求的资源是“normal”(而不是传统)资源,则仲裁器将首先检查该卡是否正在为该类型的资源进行传统解码。如果是,则锁被“转换”为传统资源锁。
仲裁器将首先查找可能冲突的所有 VGA 卡,并禁用它们的 IO 和/或内存访问,包括在 P2P 网桥上的 VGA 转发(如果需要),以便可以使用请求的资源。然后,该卡被标记为锁定这些资源,并且在该卡上启用 IO 和/或内存访问(包括在父 P2P 网桥上的 VGA 转发(如果有))。
如果某些冲突卡已经锁定了一个或多个所需资源(或者在不同的总线段上的任何资源,因为 P2P 网桥不区分 VGA 内存和 IO),则此函数将阻塞。您可以指示此阻塞是否应被信号(对于用户空间接口)中断。
不得在中断时间或原子上下文中调用。如果卡已经拥有资源,则该函数成功。支持嵌套调用(维护每个资源的计数器)
成功后,再次使用 vga_put()
释放 VGA 资源。
成功返回 0,失败返回负错误代码。
-
void vga_put(struct pci_dev *pdev, unsigned int rsrc)¶
释放传统 VGA 资源的锁
参数
struct pci_dev *pdev
VGA 卡的 PCI 设备,如果为 NULL,则表示系统默认设备
unsigned int rsrc
要释放的资源的位掩码
描述
释放先前由 vga_get()
或 vga_tryget() 锁定的资源。资源不会立即禁用,因此随后对同一卡上的 vga_get()
将立即成功。资源有一个计数器,因此只有当计数器达到 0 时才会释放锁。
-
void vga_set_legacy_decoding(struct pci_dev *pdev, unsigned int decodes)¶
参数
struct pci_dev *pdev
VGA 卡的 PCI 设备
unsigned int decodes
卡解码的传统区域的位掩码
描述
向仲裁器指示该卡是否解码传统 VGA IO、传统 VGA 内存、两者或都不解码。所有卡默认为两者,卡驱动程序(例如 fbdev)应告知仲裁器它是否已禁用传统解码,因此可以将该卡排除在仲裁过程之外(并且可以随时安全地接收中断)。
-
int vga_client_register(struct pci_dev *pdev, unsigned int (*set_decode)(struct pci_dev *pdev, bool decode))¶
注册或注销 VGA 仲裁客户端
参数
struct pci_dev *pdev
VGA 客户端的 PCI 设备
unsigned int (*set_decode)(struct pci_dev *pdev, bool decode)
VGA 解码更改回调
描述
客户端有两种回调机制可以使用。
set_decode 回调:如果客户端可以禁用其 GPU VGA 资源,它将收到此回调以设置编码/解码状态。
理由:我们不能无条件地禁用 VGA 解码资源,因为某些单 GPU 笔记本电脑似乎需要 ACPI 或 BIOS 访问 VGA 寄存器来控制背光灯等。希望较新的多 GPU 笔记本电脑可以执行更合理的操作,并且台式机不会有任何特殊的 ACPI。当用户空间首次使用 VGA 仲裁时,驱动程序将收到回调,因为一些较旧的 X 服务器存在问题。
不检查是否已为 pdev 注册了客户端。
要注销,请调用 vga_client_unregister()。
返回值
成功返回 0,失败返回 -ENODEV
libpciaccess¶
为了使用 vga 仲裁器字符设备,在 libpciaccess 库中实现了一个 API。一个字段已添加到 struct pci_device(系统上的每个设备)
/* the type of resource decoded by the device */
int vgaarb_rsrc;
除此之外,在 pci_system 中添加了
int vgaarb_fd;
int vga_count;
struct pci_device *vga_target;
struct pci_device *vga_default_dev;
vga_count 用于跟踪有多少卡正在仲裁,因此,例如,如果只有一张卡,那么它可以完全避开仲裁。
以下函数获取给定卡的 VGA 资源,并将这些资源标记为已锁定。如果请求的资源是“normal”(而不是传统)资源,则仲裁器将首先检查该卡是否正在为该类型的资源进行传统解码。如果是,则锁被“转换”为传统资源锁。仲裁器将首先查找可能冲突的所有 VGA 卡,并禁用它们的 IO 和/或内存访问,包括在 P2P 网桥上的 VGA 转发(如果需要),以便可以使用请求的资源。然后,该卡被标记为锁定这些资源,并且在该卡上启用 IO 和/或内存访问(包括在父 P2P 网桥上的 VGA 转发(如果有))。在 vga_arb_lock() 的情况下,如果某些冲突卡已经锁定了一个或多个所需资源(或者在不同的总线段上的任何资源,因为 P2P 网桥不区分 VGA 内存和 IO),则该函数将阻塞。如果卡已经拥有资源,则该函数成功。vga_arb_trylock() 将返回 (-EBUSY) 而不是阻塞。支持嵌套调用(维护每个资源的计数器)。
设置此客户端的目标设备。
int pci_device_vgaarb_set_target (struct pci_device *dev);
例如,在 x86 中,如果同一总线上的两个设备想要锁定不同的资源,则两者都会成功(锁定)。如果设备位于不同的总线上,并且尝试锁定不同的资源,则只有第一个尝试者才会成功。
int pci_device_vgaarb_lock (void);
int pci_device_vgaarb_trylock (void);
解锁设备的资源。
int pci_device_vgaarb_unlock (void);
向仲裁器指示该卡是否解码传统 VGA IO、传统 VGA 内存、两者或都不解码。所有卡默认为两者,卡驱动程序(例如 fbdev)应告知仲裁器它是否已禁用传统解码,因此可以将该卡排除在仲裁过程之外(并且可以随时安全地接收中断)。
int pci_device_vgaarb_decodes (int new_vgaarb_rsrc);
连接到仲裁器设备,分配 struct
int pci_device_vgaarb_init (void);
关闭连接
void pci_device_vgaarb_fini (void);
xf86VGAArbiter (X 服务器实现)¶
X 服务器基本上包装了以某种方式触摸 VGA 寄存器的所有函数。
参考文献¶
Benjamin Herrenschmidt (IBM?) 在 2005 年与 Xorg 社区讨论此类设计时开始了这项工作 [1, 2]。在 2007 年底,Paulo Zanoni 和 Tiago Vignatti(均来自 C3SL/巴拉那联邦大学)继续了他的工作,增强了内核代码以适应作为内核模块,并且还完成了用户空间端的实现 [3]。现在(2009 年),Tiago Vignatti 和 Dave Airlie 最终将这项工作成形,并排队到 Jesse Barnes 的 PCI 树。