Linux亮度调节分析-从顶层到底层
亮度调节分析
在Ubuntu下,我们是看不到亮度调节的按钮的(可能大部分用户调节屏幕亮度都是通过显示器按钮直接调节),但是新的银河麒麟和openkylin中,在display设置中出现了亮度控制的按钮可以拖动进行亮度的调节。
其实在ubuntu中,我们还可以通过ddcutil
工具来进行屏幕亮度的控制
1 | sudo ddcutil setvcp 10 80 |
通过上述命令就可以将屏幕的亮度调整至80%,如果失败了,可能需要通过apt install
来安装ddcutil
工具。当然ddcutil
不只是控制亮度这一个功能,对于显示器几乎所有的设置都可以使用ddcutil
,具体参数使用ddcutil vcpinfo
查看。
ddc协议
DDC(Display Data Channel):
- DDC是一种用于显示器与计算机之间进行通信的协议。它允许计算机通过连接的显示器获取关于显示器的信息,如分辨率、亮度、对比度、色彩设置等。
- DDC是I2C总线协议的一种应用,通过I2C总线进行数据传输。
- 最常用的版本是DDC/CI,它允许计算机通过I2C总线向显示器发送控制指令,实现调节显示器参数的功能,如调节亮度、对比度等。
- DDC通信通常是双向的,计算机可以查询显示器的信息,同时也可以发送控制指令给显示器。
VCP(Virtual Control Panel):
- VCP是DDC协议中定义的一组控制寄存器和对应的命令,用于对显示器进行远程控制。
- 通过VCP,计算机可以向支持DDC的显示器发送控制命令,从而调整显示器的参数,如亮度、对比度、色彩设置等。这样,计算机可以实现通过软件远程控制显示器的各种功能。
- VCP指令的具体定义由VESA制定,是DDC协议的一部分。
EDID(Extended Display Identification Data):
扩展显示器识别数据,一般存储在显示器的
EEPROM
中。在
EDID1.0->EDID1.3
版本中,EDID
的容量是 128Byte,从EDID1.3
版本以后,数据容量扩展到 256Byte。一般通过
DDC
(或I2C
)通道读取EDID
,从设备地址是0x50
。
EDID是一种由显示器提供的信息,用于描述显示器的特性和功能。每个连接到计算机的显示器都会提供一个EDID数据块,计算机可以通过DDC协议从显示器读取该信息。EDID数据包含了显示器的分辨率、刷新率、尺寸、支持的色彩深度以及其他一些显示器属性。计算机使用EDID来了解显示器的能力和特性,以便正确配置和优化图形输出。
ddcutil通信环境配置
- 确保显示器已启用DDC通信
- 视频驱动程序
视频驱动程序必须支持i2c总线通信,所有主要的开源驱动程序(noveau,radeno,admdgpu,i915)都支持i2c总线通信,Nvidia专有驱动程序也是如此。
- 确保
/dev/i2c-N
设备存在
通常,i2c总线公开名为
/dev/i2c-N
的设备,必须加载内核模块i2c-dev
才能创建/dev/i2c-N设备
- 为代表显示器的/dev/i2c-N设备授予读写权限
ddcutil安装成功但是执行失败,使用ddcutil environment
可以探测i2c环境
1 | *** Primary Check 1: Identify video card and driver *** |
ddcutil相关源码
在invoke_i2c_writer
函数中,亮度调节的过程主要是将表示亮度值的数据以特定格式写入到I2C总线上,以便通过DDC协议向显示器发送亮度设置命令。具体来说,在调节亮度的过程中,涉及以下几个关键步骤:
- 将亮度值80%转换为特定的数据格式:调节亮度的数据需要遵循DDC协议,并根据显示器支持的格式进行编码。这个转换过程主要发生在代码中的
set_table_vcp_value()
函数中,将亮度值80%转换为特定的表格数据,以便通过DDC协议发送给显示器。 - 调用
invoke_i2c_writer()
执行I2C写入操作:一旦数据格式转换完成,代码中会调用invoke_i2c_writer()
函数执行实际的I2C写入操作。该函数会根据I2C总线控制器的配置,通过相应的I2C写入函数向I2C总线发送写入命令。 - I2C总线发送命令到显示器:I2C总线会将经过转换的数据发送给显示器。这个过程是通过底层的硬件和I2C通信协议实现的,代码中的
invoke_i2c_writer()
函数负责调用相应的I2C写入函数来完成此操作。 - 显示器接收命令并调节亮度:显示器接收到I2C总线发送的命令,并根据命令中包含的亮度值(80%对应的表格数据)来进行亮度调节。这个过程是在显示器的硬件电路中实现的,通过DDC协议进行交互。
ddcutil读写用户空间/dev-i2c设备,该设备提供了类似于I2C总线的文件系统抽象。它将比特操作留给视频设备驱动程序,例如amdgpu、nouveau。
ddcutil
通过读写用户空间的/dev/i2c
设备与I2C总线进行通信,而不直接进行底层的I2C位操作。相反,ddcutil
将底层的I2C通信任务交给显卡设备驱动程序,例如amdgpu
和nouveau
等。
EDID 获取
应用程序调用libdrm
代码中的drmModeGetConnector()
函数,通过ioctl
传递宏DRM_IOCTL_MODE_GETCONNECTOR
通知 Linux 内核要获取相关数据(包括EDID
)。
在 Linux 内核中,如果定义了宏CONFIG_DRM_LOAD_EDID_FIRMWARE
,内核会先调用drm_load_edid_firmware()
函数,从/lib/firmware
路径下的对应文件中读取EDID
数据;若读取失败或未定义宏CONFIG_DRM_LOAD_EDID_FIRMWARE
,内核通过DDC
通道和显示器进行通信,获取EDID
数据。
EDID 获取流程
DDC 通信流程
x11中xserver-master\hw\xfree86\ddc\ddc.c
gpu相关代码
amdgpu_ddc_probe
函数的主要作用是通过I2C总线与连接到AMD GPU上的显示器进行通信,并读取显示器的EDID数据。具体的探测过程如下:
- 遍历AMD GPU的所有DDC端口:AMD GPU上通常有多个DDC端口,每个端口对应一个显示器连接口。该函数会遍历所有的DDC端口,尝试与连接的显示器进行通信。
- 使用DDC协议进行通信:对于每个DDC端口,函数会尝试通过I2C总线与显示器进行通信,使用DDC协议读取显示器的EDID数据。
- 解析EDID数据:一旦成功读取到显示器的EDID数据,函数会对EDID数据进行解析,提取其中的信息,例如显示器的分辨率、刷新率、色彩能力等。
- 填充驱动数据结构:根据解析得到的EDID信息,函数会将显示器的相关信息填充到GPU驱动的数据结构中,以供后续使用。
通过amdgpu_ddc_probe
函数,AMD GPU驱动可以在系统启动时自动探测并识别连接的显示器,并获取其EDID信息。这样,GPU驱动可以根据显示器的特性来正确配置显示输出,以实现最佳的图形显示效果。