Boot-up Graphics in Linux
Boot-up Graphics 指在 Linux 系统启动时涉及到一些和图形显示相关的问题。
当主板上同时有多张PCI显卡时, 哪一个会做为默认显示输出呢?
一个最简单的场景,主板上有一个 Intel 的集显,还有一个 AMD 的独显,这两个 GPU 各自通过 HDMI 接口连接到同一个显示器上,那么在Linux 系统启动时(未进入桌面环境),是哪个 GPU 在显示呢?
Linux VGAArbiter
Linux kernel vgaarbiter 的创建
1 | commit deb2d2ecd43dfc51efe71eed7128fda514da96c6 |
X Server的输出设备检测和驱动加载
Xorg抽象的输出设备
Linux下的显示输出设备一般要么是一个pci_device
,要么是一个xf86_platform_device
Xorg定义了一个全局数组xf86_platform_devices
, 这个数组的元素类型是xf86_platform_device
, 存储是动态申请的,Xorg探测到一个PCI显卡时,就申请一个存储。
1 | struct xf86_platform_device *xf86_platform_devices; |
OdevAttributes
定义了一个PCI输出设备的属性。
-
path
kernel device node,
/dev/dri/card0
-
syspath
system device path,
/sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0/drm/card1
-
busid
DRI 风格的Bus ID, 如 “pci:0000:04:00.0”
-
fd
文件描述符,
open(/dev/dri/card0)
返回值 -
major
主设备号
-
minor
次设备号
-
driver
kernel driver name, 如 “amdgpu”
PCI BusID
DRI-style
1 | pci:0000:04:00.0 |
0000
PCI domain04
PCI bus00
PCI device0
PCI function
Xorg 日志
- 来自 xorg-server-1.18.4 版本的 Xorg 日志
1 | [ 8.456] (--) PCI: (0:5:0:0) 1002:677b:174b:3000 rev 0, Mem @ 0x1040000000/268435456, 0x58600000/131072, I/O @ 0x00002000/256, BIOS @ 0x????????/131072 |
- 来自 xorg-server-1.20.0 版本的 Xorg 日志
1 | [ 40.891] (--) PCI: (10@0:0:0) 1a03:2000:1a03:2000 rev 65, Mem @ 0x60000000/16777216, 0x61000000/131072, I/O @ 0x00002000/128, BIOS @ 0x????????/65536 |
变更发生于 2017-06-19:
1 | commit e905b19a53f96013c4417bec993a1dea5a3b0a5f |
Xorg 配置文件 “Device” Section
上面两种格式都可以作为Device
段里BusID
的格式,X11规定的BusID
的格式是
“bus[@domain]:device[:func]”
Xserver依赖下面的用户空间库来填写该结构,这个过程也体现了Xserver检测显卡及加载驱动的过程
- 通过
libpciaccess
的接口发现系统PCI设备,获取syspath
,path
- 通过
open
系统调用打开侦测到的drm_device
,fd
有了 - 通过
libdrm
接口获取major
,minor
和driver
Device Detect Routines
Xserver提供了两种设备检测方法:
1 | void xf86PlatformDeviceProbe(struct OdevAttributes *attribs); |
默认是使用xf86PlatformDeviceProbe
, 当这个函数完成xf86_add_platform_device
后,除了attribs
, xf86_platform_device
的其它成员还没有被填写,剩下的任务交由libudev
, libpciaccess
和libdrm
的接口完成。
libudev
- udev_enumerate_add_match_subsys
- udev_enumerate_add_match_sysname
libpciaccess
- pci_device_probe
- pci_device_is_boot_vga
libdrm
- drmSetInterfaceVersion
- drmGetBusid
- drmGetVersion
Primary Bus
在多卡的情况下,Xserver启动后默认使用哪个显卡显示?
1 | typedef struct _bus { |
Xserver定义了一个全局的BusRec
类型变量primaryBus
1 | BusRec primaryBus = { BUS_NONE, {0} }; |
这个primaryBus
将是Xserver启动后默认使用的显卡设备的唯一候选者。
Who Is The Lucky Boy?
Xserver有两个规则去确定primaryBus:
How config_udev_odev_probe
Works?
config_udev_odev_probe
唯一的参数是一个callback函数xf86PlatformDeviceProbe
, config_udev_odev_probe
要做的就是调用libudev的接口枚举出/dev
文件系统里注册的drm
设备,将它的path
填到xf86_platform_device.attribs->path。所以这一步决定了xf86_platform_devices数组中platform device的顺序,按照此顺序最后那个pci_device_is_boot_vga
返回True
的显示设备将为primaryBus
, 但这是在没有配置PrimaryGPU
选项时的行为。下面的代码来自libudev
1 | _public_ int sd_device_enumerator_add_match_sysname(sd_device_enumerator *enumerator, const char *sysname) { |
这里参数sysname
是一个正则表达式card[0-9]*
How pci_device_is_boot_vga
Works?
pci_device_is_boot_vga
是一个虚接口,在Linux下的实现是pci_device_linux_sysfs_boot_vga
, 下面的代码来自libpciaccess
1 | static int pci_device_linux_sysfs_boot_vga(struct pci_device *dev) |
这里SYS_BUS_PCI
被定义为/sys/bus/pci/devices
, pci_device_is_boot_vga
的返回值取决于显示设备的kernel driver如何实现sysfs
文件系统中的/sys/bus/pci/devices/0000:05:00.0/boot_vga
节点。
以上两点规则说明,在多卡系统中,不使用PrimaryGPU
的情况下,当且仅当目标卡的drm
设备节点号最大,而且/sys/bus/pci/devices/XXXX:XX:XX.X/boot_vga
被实现为read它返回ASCII字符'1'
. 这样目标卡才能做为primaryBus
设备默认显示输出。