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” 指示此卡锁定的内容。如果卡被拔出,则 card_ID 将变为 “invalid”,并且在目标设置为新卡之前,任何命令都会返回 -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” 以返回到系统默认卡(TODO:尚未实现)。目前,仅支持 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 资源。

int vga_get_uninterruptible(struct pci_dev *pdev, unsigned int rsrc)

vga_get() 的快捷方式

参数

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 资源,并将这些资源标记为已锁定。如果请求的资源是“正常”资源(而不是旧式资源),则仲裁器将首先检查该卡是否正在为该类型的资源进行旧式解码。如果可以,则将锁“转换”为旧式资源锁。

仲裁器将首先查找所有可能冲突的 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 资源,并将这些资源标记为已锁定。如果请求的资源是“正常”资源(而不是传统资源),则仲裁器将首先检查该卡是否正在为该类型的资源进行传统解码。如果是,则锁定将“转换为”传统资源锁定。仲裁器将首先查找所有可能冲突的 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 树中。

  1. https://cgit.freedesktop.org/xorg/xserver/commit/?id=4b42448a2388d40f257774fbffdccaea87bd0347

  2. https://lists.freedesktop.org/archives/xorg/2005-March/006663.html

  3. https://lists.freedesktop.org/archives/xorg/2005-March/006745.html

  4. https://lists.freedesktop.org/archives/xorg/2007-October/029507.html