前段时间在一个项目里,做了一个色温调节的东西。发现这部分的资料有点少,所幸研究了一段时间,记录一下,免得后面又忘记了。
色温调节是上层发出,最终由显卡驱动处理的一个事件。桌面一般都是采用redshift 调节色温,到xserver,xserver进行一些自己的处理,转由libdrm用drm ioctl陷入内核drm模块,最终到实际的显卡驱动。redshift这边没怎么研究过,本文就先从xserver讲起吧。涉及到具体的显卡驱动,就用radeon驱动为例。
用户可以直接用xrand命令调节色温:
xrandr --output XXXX --gamma 1:1:1
可以用这个命令看看色温调节是否正常。
gamma参数后面第一个1是红色,第二个1是绿色,第三个1是蓝色。通过调三原色达到调色温的目的。先拉一份xserver的代码来看看xserver是怎么处理参数的。从debian的仓库里直接拉下,非常方便。apt source xorg-server
(不要用sudo拉代码,不信你可以试试看)。经验之谈:代码拉下来之后,先打个包替换本地安装的xserver,验证代码的正确性。
dpkg-buildpackage -sa -nc -j40
,第一次打包会很慢,稍微等一会。完成之后会在父目录下生成很多个deb包,暂时没分辨分别是什么什么包,所以统统安装,之后重启xserver。重启之后x依然运行正常,那就没什么问题了。
对于xserver而言,上层可以通过两种方式调节色温:一种是redshift;另一种是xrandr提供的gamma参数。这两种方式一开始调用的接口不同,殊途同归,最终通过同一个接口进到libdrm中去。
redshift调节色温xserver函数调用链
ProcVidModeSetGamma
{
if (!pVidMode->SetGamma(pScreen, ((float) stuff->red) / 10000.,
((float) stuff->green) / 10000.,
((float) stuff->blue) / 10000.))
return BadValue;
return Success;
}
->xf86VidModeSetGamma
->xf86ChangeGamma
->CMapChangeGamma
->CMapReinstallMap
->CMapRefreshColors
->xf86RandR12LoadPalette
{
for (c = 0; c < config->num_crtc; c++) {
xf86CrtcPtr crtc = config->crtc[c];
RRCrtcPtr randr_crtc = crtc->randr_crtc;
if (randr_crtc) {
xf86RandR12CrtcComputeGamma(crtc, colors, reds, greens, blues,
randr_crtc->gammaRed,
randr_crtc->gammaGreen,
randr_crtc->gammaBlue,
randr_crtc->gammaSize);
} else {
xf86RandR12CrtcComputeGamma(crtc, colors, reds, greens, blues,
NULL, NULL, NULL,
xf86GetGammaRampSize(pScreen));
}
xf86RandR12CrtcReloadGamma(crtc);
}
}
->xf86RandR12CrtcReloadGamma
->drmmode_crtc_gamma_set
{
drmModeCrtcSetGamma(drmmode->fd, drmmode_crtc->mode_crtc->crtc_id,
size, red, green, blue);
}
xf86VidModeSetGammaProcVidModeSetGamma
接口实现在x扩展中,应该和x协议 xclient有关(细节待补充)。这个函数根据传入的参数,调用实现在xf86中的setgamma钩子函数,钩子上挂的是xf86VidModeSetGamma
,这个函数没什么实际功能是个框架:转换参数,调用xf86ChangeGamma
->CMapChangeGamma
->``CMapReinstallMap->CMapRefreshColors
->xf86RandR12LoadPalette
。xf86RandR12LoadPalette
遍历每个crtc,分别计算gamma和load gamma。 用户可能修改一个或者多个gamma参数,在这里全部扫过之后,可以节省调用时间。 load gamma直接调用crtc的gamma set接口——drmmode_crtc_gamma_set
,从这里正式进入libdrm里。
xrandr方式调节色温的调用链简单多了,从randr调用带xf86中,余下的和上面一样。
RRCrtcGammaSet
->xf86RandR12CrtcSetGamma
->xf86RandR12CrtcReloadGamma
->drmmode_crtc_gamma_set
令人迷惑的xserver终于大概理清楚了,主要是能力有限,分析不清楚。libdrm的代码一般都很简单,做一个基本的判非操作之后,用ioctl陷入内核中。比如刚刚找不到的 函数,他的实现是这样的:
drm_public int drmModeCrtcSetGamma(int fd, uint32_t crtc_id, uint32_t size,
uint16_t *red, uint16_t *green,
uint16_t *blue)
{
struct drm_mode_crtc_lut l;
memclear(l);
l.crtc_id = crtc_id;
l.gamma_size = size;
l.red = VOID2U64(red);
l.green = VOID2U64(green);
l.blue = VOID2U64(blue);
return DRM_IOCTL(fd, DRM_IOCTL_MODE_SETGAMMA, &l);
}
熟悉内核代码的童鞋都知道,从ioctl开始就陷入内核了。接下来需要去drm驱动里找到对DRM_IOCTL_MODE_SETGAMMA
flag的处理即可。
drm 的ioctl接口都是实现在文件drivers/gpu/drm/drm_ioctl.c
中,DRM_IOCTL_MODE_SETGAMMA
对应的接口是drm_mode_gamma_set_ioctl
:
int drm_mode_gamma_set_ioctl(struct drm_device *dev,
void *data, struct drm_file *file_priv)
{
struct drm_mode_crtc_lut *crtc_lut = data;
struct drm_crtc *crtc;
void *r_base, *g_base, *b_base;
int size;
struct drm_modeset_acquire_ctx ctx;
int ret = 0;
if (!drm_core_check_feature(dev, DRIVER_MODESET))
return -EINVAL;
crtc = drm_crtc_find(dev, file_priv, crtc_lut->crtc_id);
if (!crtc)
return -ENOENT;
if (crtc->funcs->gamma_set == NULL)
return -ENOSYS;
drm_modeset_acquire_init(&ctx, 0);
retry:
ret = drm_modeset_lock_all_ctx(dev, &ctx);
if (ret)
goto out;
size = crtc_lut->gamma_size * (sizeof(uint16_t));
r_base = crtc->gamma_store;
if (copy_from_user(r_base, (void __user *)(unsigned long)crtc_lut->red, size)) {
ret = -EFAULT;
goto out;
}
g_base = r_base + size;
if (copy_from_user(g_base, (void __user *)(unsigned long)crtc_lut->green, size)) {
ret = -EFAULT;
goto out;
}
b_base = g_base + size;
if (copy_from_user(b_base, (void __user *)(unsigned long)crtc_lut->blue, size)) {
ret = -EFAULT;
goto out;
}
ret = crtc->funcs->gamma_set(crtc, r_base, g_base, b_base,
crtc->gamma_size, &ctx);
......
return ret;
}
按照惯例,drm只实现框架代码并不care具体的功能,代码通常比较简单,没什么复杂的调用链。drm进来先判断操作的合法性,检查feature标志、根据用户参数获取crtc,判断显卡是否实现了gamma_set接口。全部正常之后,根据用户传入的参数计算红绿蓝三色的基准值,然后调用显卡自己实现的gamma_set
接口。以radeon为例,大致调用链如下所示:
radeon_crtc_gamma_set
->radeon_crtc_load_lut
{
#define ASIC_IS_DCE5(rdev) ((rdev->family >= CHIP_BARTS))
if (ASIC_IS_DCE5(rdev))
dce5_crtc_load_lut(crtc);
else if (ASIC_IS_DCE4(rdev))
dce4_crtc_load_lut(crtc);
else if (ASIC_IS_AVIVO(rdev))
avivo_crtc_load_lut(crtc);
else
legacy_crtc_load_lut(crtc);
}
->device dependence write registers
radeon gamma_set调用radeon_crtc_load_lut
,load lut根据显卡芯片类型确定调用哪个,在load lut中实现写寄存器,这部分就和硬件相关了。不同的显卡芯片、不同给厂家的显卡,这部分实现都是不一样的。