当我们在谈论色温调节的时候,我们在谈论什么

总述

前段时间在一个项目里,做了一个色温调节的东西。发现这部分的资料有点少,所幸研究了一段时间,记录一下,免得后面又忘记了。
色温调节是上层发出,最终由显卡驱动处理的一个事件。桌面一般都是采用redshift 调节色温,到xserver,xserver进行一些自己的处理,转由libdrm用drm ioctl陷入内核drm模块,最终到实际的显卡驱动。redshift这边没怎么研究过,本文就先从xserver讲起吧。涉及到具体的显卡驱动,就用radeon驱动为例。

xserver

用户可以直接用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->xf86RandR12LoadPalettexf86RandR12LoadPalette遍历每个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

libdrm

令人迷惑的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_SETGAMMAflag的处理即可。

内核drm模块

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中实现写寄存器,这部分就和硬件相关了。不同的显卡芯片、不同给厂家的显卡,这部分实现都是不一样的。

Q & A

    1. 为什么有的设备能调节色温,有的设备不能调节色温呢?
      色温调节是要显卡支持的,如上分析,色温调节最终从drm 的gamma_set调用到显卡自身实现的gamma_set中去,真正执行调节功能的是显卡自身的gamma set函数。然而不是所有得到显卡都实现了这个功能,因此给用户看到的现象是:有的卡能调色温,有的卡不支持。

你可能感兴趣的:(驱动书写指南,内核玩起来,linux内核的一些事)