该系列文章总目录链接与各部分简介: Android Qcom Display学习(零)
Linux DRM(二)基本概念和特性
libdrm:对底层定义在drm_ioctl.c 中各种IOCTL接口进行封装,向上层提供通用的API接口
GEM:Graphic Execution Manager,主要负责显示buffer的分配和释放,也是GPU唯一用到DRM的地方。
KMS:Kernel Mode Setting,负责显示buffer的切换,多图层的合成方式,显示位置。设置分辨率、刷新率等等。
KMS和DRM两者都是通过libdrm来进行调用的,但两者是具有相对独立性的,DPU对应的KMS对应于系统侧的HWC Composer,其重点是在于送显,而GPU对应DRM则侧重应用侧相关的渲染绘制。最初的版本DRM中没有KMS的,Mode-setting(包含更新画面、屏幕分辨、刷新率等等)都是在Userspace中实现的,这样会与Rendering发现竞争,并且两个硬件之间工作不统一的问题,因此就引入了KMS。
FrameBuffer,单个图层的显示内容(包含图像格式和大小),唯一一个和硬件无关的基本元素
我们知道在SurfaceFlinger中处理多个Layers的时候,这些Layers都会通过libdrm调用到GPU模块完成渲染,渲染后的buffer会通过dma_buf共享内存的方式放入GraphciBuffer。不同的Layers的合成方式不同,分为Device和Client,Client的类型还会通过GPU来进行图层的合成,HWC在收集所有的GraphicBuffer后,通过libdrm交给KMS的FrameBuffer,Plane再从drm_framebuffer中获取数据。
SurfaceFlinger传入HWC的过程,都会调用beginCommand,最终都会通过QtiComposerClient::CommandReader::parseCommonCmd来解析,如果没有Client合成的话,会尝试parsePresentOrValidateDisplay(PRESENT_OR_VALIDATE_DISPLAY)送显,如果无法接受显示则会调用valide,后续还会处理Client的合成,最终再通过parsePresentDisplay(PRESENT_DISPLAY)送显,两者都是调用PresentDisplay
HWCSession::PresentDisplay
HWCDisplayBuiltIn::Present
HWCDisplay::CommitLayerStack // DumpInputBuffers() 高通dump inputbuffer的地方
DisplayBuiltIn::Commit
DisplayBase::Commit
HWPeripheralDRM::Commit
HWDeviceDRM::AtomicCommit
HWDeviceDRM::SetupAtomic // fb id用于对应framebuffer
DRMAtomicReq::Commit
drmModeAtomicCommit
drm_atomic_commit
PLANE代表显示图层,每个 CRTC 需要定义一个 primary plane 以及可选的 overlay plane、cursor plane
enum drm_plane_type {
DRM_PLANE_TYPE_OVERLAY,
DRM_PLANE_TYPE_PRIMARY,
DRM_PLANE_TYPE_CURSOR,
}
PLANE 存在的意义主要有两点:增强系统灵活性、提高系统性能
(1)对于背景和光标等变化不频繁的基本图显输出,可用通用 plane 来实现。而那些频繁变化则可由专用的 plane 来实现。
(2)plane 具备图像缩放、剪裁、多图层叠加等基本的图像处理功能,因此,可以让 GPU 来将更多的精力放在图形渲染上。
PLANE对应的SSPP(Source Surface Processor Pipes)是DPU的硬件模块,功能如上就是支持缩放,格式转换,锐化质量改进等。
plane所支持的Format定义在drm_fourcc.h
#define fourcc_code(a, b, c, d) ((__u32)(a) | ((__u32)(b) << 8) | \
((__u32)(c) << 16) | ((__u32)(d) << 24))
#define DRM_FORMAT_RGBA8888 fourcc_code('R', 'A', '2', '4') /* [31:0] R:G:B:A 8:8:8:8 little endian */
'R', 'A', '2', '4' = 0x85 | 0x65 << 8 | 0x32 << 16 | 0x34 << 24 = 0x34326582 = 875718018
需要注意的是调用的驱动接口基本都使用atmoic的方式,例如atomic_set_property对应的应用接口drmModeAtomicCommit,之前的legacy的方式,例如drm_atomic_helper_update_plane对应的应用接口drmModeSetPlane已被弃用,新接口代替老接口的使用方式(atomic-plane)应用
static const struct drm_plane_funcs sde_plane_funcs = {
.update_plane = drm_atomic_helper_update_plane,
.disable_plane = drm_atomic_helper_disable_plane,
.destroy = sde_plane_destroy,
.atomic_set_property = sde_plane_atomic_set_property,
static const struct drm_plane_helper_funcs sde_plane_helper_funcs = {
.prepare_fb = sde_plane_prepare_fb, //预先对图层进行处理,为扫描准备帧缓冲区
.cleanup_fb = sde_plane_cleanup_fb, // 图层显示完成后的处理,清楚prepare_fb中分配的资源
.atomic_check = sde_plane_atomic_check,// 检查参数是否支持当前的format、区域大小越界等
.atomic_update = sde_plane_atomic_update,// 更新plane状态(注意FrameBuffer和crtc长宽区别)
atmoic: drmModeAtomicCommit + drmModeAtomicAddProperty = legacy: drmModeSetPlane
drmModeAtomicCommit
DRM_IOCTL_MODE_ATOMIC
drm_mode_atomic_ioctl
drm_atomic_set_property
case DRM_MODE_OBJECT_PLANE:
drm_atomic_plane_set_property
.atomic_set_property = sde_plane_atomic_set_property
drmModeSetPlane
DRM_IOCTL_MODE_SETPLANE
drm_mode_setplane
.update_plane = drm_atomic_helper_update_plane,
CRTC从drm_plane 接收RGB像素数据并将其混合到一起,传输给下级显示设备drm_encoder。
CRTC 模块存在的意义主要有以下三点:
(1)统一协调 FB、DRM、用户空间代码之间对图显输出的控制
(2)图显模式的配置权回收到 kernel 空间,避免用户空间代码直接控制图显控制器引起 kernel panic
(3)kernel 空间实现的图显输出暂停与恢复相关代码,可以方便的调用图显控制器的配置函数
CRTC对应的Layer Mixer以及DSPP(Destination Surface Processor Pipes)同样是DPU的一部分,Layer Mixer很好理解,就是将上述PANEL中提到的图层进行融合,DSPP则是基于面板特性进行Gamma校正和亮度,对比度,饱和度等调整。所以Android中护眼模式,色彩模式不应用于每个Layers而是应用于屏幕上就是指生成的颜色矩阵最终都是通过DSPP来实现的对应的模式效果吧,部分不开源封装在libsdm-color.so
static const struct drm_crtc_funcs sde_crtc_funcs = {
.set_config = drm_atomic_helper_set_config,
.page_flip = drm_atomic_helper_page_flip,
.atomic_set_property = sde_crtc_atomic_set_property,
.atomic_duplicate_state = sde_crtc_duplicate_state,
static const struct drm_crtc_helper_funcs sde_crtc_helper_funcs = {
.mode_fixup = sde_crtc_mode_fixup, // 在一帧显示前验证模式,是否支持seamless
.atomic_check = sde_crtc_atomic_check,// 当mix没有分配,需刷新一个关闭状态的crtc
.atomic_flush = sde_crtc_atomic_flush, //完成crtc上多个平面的更新
DRM Encoder 和 Connector 模块由图显外设抽象而来,Encoder 属于控制器部分,将内存的像素编码转换为显示器所需要的信号例如VGA、MIPI;Virutal Encoder管理一个逻辑显示,包含一个或多个物理encoder,每个物理encoder对应一个INTF硬件模块。
static const struct drm_encoder_funcs sde_encoder_funcs = {
.destroy = sde_encoder_destroy,
.late_register = sde_encoder_late_register,
.early_unregister = sde_encoder_early_unregister,
static const struct drm_encoder_helper_funcs sde_encoder_helper_funcs = {
.mode_set = sde_encoder_virt_mode_set, //ModeSetting
.disable = sde_encoder_virt_disable,
.enable = sde_encoder_virt_enable,
.atomic_check = sde_encoder_virt_atomic_check,
};
Connector 包含外设 PHY 或者显示器参数,代表连接的显示设备连接状态,支持的视频模式等。
static const struct drm_connector_funcs sde_connector_ops = {
.reset = sde_connector_atomic_reset,
.detect = sde_connector_detect,
.destroy = sde_connector_destroy,
.fill_modes = sde_connector_fill_modes,
static const struct drm_connector_helper_funcs sde_connector_helper_ops_v2 = {
.get_modes = sde_connector_get_modes, //调用到dsi connector的get_modes
.mode_valid = sde_connector_mode_valid,
.best_encoder = sde_connector_best_encoder,
.atomic_best_encoder = sde_connector_atomic_best_encoder,
.atomic_check = sde_connector_atomic_check,
};
_sde_kms_setup_displays中创建encoders和connectors时,赋予了sde_connector_ops,使得connector能对应到dsi
static const struct sde_connector_ops dsi_ops = {
.get_modes = dsi_connector_get_modes,//标准话的EDID交换LCD和DSI的数据
.get_info = dsi_display_get_info,//获取primary或者secondary的宽、高、Video模式等
.set_backlight = dsi_display_set_backlight,//调用背光支持WLED、PWD、DCS等
.get_mode_info = dsi_conn_get_mode_info,//获取timings、刷新率、时钟速率等
.get_dst_format = dsi_display_get_dst_format,//获取format,通过bpp来确定是RGB8888或其他
.cmd_transfer = dsi_display_cmd_transfer,//传输panel的cmd参数
kernel图显系统里DRM模块的注册与绑定
内核加载每个模块时间不定,component框架能保证在最后初始化的设备加载前,所需设备全部加载完毕。注册主要分为两类设备:
(1)master即超级设备,执行probe使用component_master_add_with_match函数注册自己到component框架中。
(2)component即普通设备,执行probe使用component_add函数注册自己到component框架中。
component子系统component_master_add_with_match/component_add,每个设备加入到框架中,框架就尝试进行匹配,当master匹配上所有component后,会调用master的bind回调,开始按顺序进行初始化,保证当所有子设备全部probe成功后再执行初始化操作
kernel/msm-4.19/techpack/display/msm/msm_drv.c DRM top level control
kernel/msm-4.19/techpack/display/msm/sde/sde_kms.c Adreno DPU 显示图像处理器
msm_pdev_probe
add_display_components(&pdev->dev, &match); /*添加组件用于匹配 master = sde-kms commponent = sde_dsi*/
component_match_add(dev, matchptr, compare_of, node);/*qcom,sde-kms connectors = <&sde_dsi>;*/
component_master_add_with_match(&pdev->dev, &msm_drm_ops, match);
/* master在component框架中component_list链表找到适配于自己的component组件,注册自己为master到component框架
dsi_display调用component_add 把自己添加到component_list, crtc+plane+encoder+connector 都是在DSI Controller驱动中 */
static const struct component_master_ops msm_drm_ops = {
.bind = msm_drm_bind,
.unbind = msm_drm_unbind,
};
msm_drm_bind /* 当该master下的所有支持的设备(dsi wb dp等)都初始化完成后,调用该回调的bind */
msm_drm_init(dev, &msm_driver);
msm_mdss_init(ddev);
msm_component_bind_all(dev, ddev);/* Bind all our sub-components: */
component_bind_all(dev, drm_dev);/* 调用所有从设备的bind 调用dsi_display_bind*/
_msm_drm_init_helper(priv, ddev, dev, pdev);
sde_kms_init(ddev);/* .compatible = "qcom,sde-kms" */
msm_kms_init(&sde_kms->base, &kms_funcs);/*.hw_init .prepare_commit kms_func函数注册
(kms)->funcs->hw_init(kms); /* 上述init中注册*/
_sde_kms_hw_init_blocks(sde_kms, dev, priv);
_sde_kms_drm_obj_init(sde_kms); /* create the DRM related objects */
_sde_kms_get_displays(sde_kms) /* 这里对应到Interface了 */
dsi_display_get_active_displays(sde_kms->dsi_displays,sde_kms->dsi_display_count);/* dsi */
wb_display_get_displays(sde_kms->wb_displays,sde_kms->wb_display_count);/* wb */
dp_display_get_displays(sde_kms->dp_displays,sde_kms->dp_display_count);/* dp */
_sde_kms_setup_displays(dev, priv, sde_kms)
sde_encoder_init(dev, &info);
drm_encoder_init(dev, drm_enc, &sde_encoder_funcs, drm_enc_mode, NULL); /*encoder初始化*/
sde_connector_init(dev,encoder,dsi_display_get_drm_panel(display),display,
&dsi_ops,DRM_CONNECTOR_POLL_HPD, DRM_MODE_CONNECTOR_DSI);
drm_connector_init(dev,&c_conn->base, &sde_connector_ops, connector_type);/*connector初始化*/
sde_plane_init(dev, catalog->sspp[i].id, primary,(1UL << max_crtc_count) - 1, 0);
drm_universal_plane_init(dev, plane, 0xff, &sde_plane_funcs, psde->formats,/* plane初始化 */
psde->nformats,NULL, type, NULL);
sde_crtc_init(dev, primary_planes[i]); /* Create one CRTC per encoder ctrc初始化*/
drm_crtc_init_with_planes(dev, crtc, plane, NULL, &sde_crtc_funcs, NULL);
drm_crtc_helper_add(crtc, &sde_crtc_helper_funcs);
priv->planes[i]->possible_crtcs =(1 << priv->num_crtcs) - 1; //plane to crtcs
priv->encoders[i]->possible_crtcs = (1 << priv->num_crtcs) - 1;//crtcs to encoder
/* 一个CRTC 可以连接多个 Encoder 用于实现多屏显示 */
kernel/msm-4.19/techpack/display/msm/dsi/dsi_display.c
kernel/msm-4.19/techpack/display/msm/dsi/dsi_ctrl.c
kernel/msm-4.19/techpack/display/msm/dsi/dsi_phy.c
kernel/msm-4.19/techpack/display/msm/dsi/dsi_panel.c
module_param_string(dsi_display0, dsi_display_primary, MAX_CMDLINE_PARAM_LEN, 0600);
module_param_string(name, string, len, perm); 内核把字符串直接复制到程序中的字符数组
name是外部的参数名,string是内部的变量名,len是以string命名的buffer大小,perm表示sysfs的访问权限
MODULE_PARM_DESC(dsi_display0, 参数格式的说明
"msm_drm.dsi_display0=<display node>:<configX> where <display node> is 'primary dsi display node name' and
<configX> where x represents index in the topology list");
cat /proc/cmdline: msm_drm.dsi_display0=qcom,mdss_dsi_hx8399_1080_video
dsi_display_parse_boot_display_selection(); /* 解析得到 boot_display.name boot_disp_en = true */
通过XBL-> ABL -> Kernel的Command Line来引导LCD驱动的加载或者使用default的panel型号
dsi_display_dev_probe
boot_disp_en = true /* 当前dsi控制器支持的panel列表中查找uefi传递的panel */
of_find_node_by_name(mdp_node, boot_disp->name);
else
display_panel = getDisplayPanel();
display_panel_name = strstr ( saved_command_line, "hx8399");
panel_node = of_parse_phandle(node,"qcom,dsi-default-panel1", 0);
dtsi:
qcom,dsi-default-panel1 = <&dsi_hx8399_1080_video>;
dsi_display_init(display);
dsi_display_dev_init(display);
dsi_display_parse_dt(display);/* 解析dtsi包括TE,初始化一些handel 包括dsi_ctrl dsi_phy */
dsi_display_res_init(display);
dsi_panel_get(&display->pdev->dev, display->panel_node, display->parser_node,
display->display_type, display->cmdline_topology); ↓↓↓
dsi_panel_parse_power_cfg(panel);
dsi_panel_parse_bl_config(panel); /* parse dtsi */
drm_panel_add(&panel->drm_panel); /* Let DRM can get panel name */
component_add(&pdev->dev, &dsi_display_comp_ops);
/* component框架中通过component_add添加管理多个组件的加载,保证都正常工作
其中注册了两个函数 .bind = dsi_display_bind, .unbind = dsi_display_unbind */
Linux内核中各个子系统相互依赖,当其中某个子系统状态发生改变时,就必须使用一定的机制告知使用其服务的其他子系统,以便其他子系统采取相应的措施。为满足这样的需求,内核实现了事件通知链机制(notification chain),而对应Display来说紧密相关就是TP,通过内核通知链通知TP进行Resume和Suspend等操作。
在msm_kms_init初始化的时候注册的函数,通过内核通知链去通知TP resume和suspend流程
.prepare_commit = sde_kms_prepare_commit,
_sde_kms_drm_check_dpms(state, DRM_PANEL_EARLY_EVENT_BLANK);
.complete_commit = sde_kms_complete_commit,
_sde_kms_drm_check_dpms(old_state, DRM_PANEL_EVENT_BLANK);
_sde_kms_drm_check_dpms
sde_kms_get_blank(crtc->state, connector->state);
case SDE_MODE_DPMS_ON:
blank = DRM_PANEL_BLANK_UNBLANK; //new mode:亮屏操作
break;
case SDE_MODE_DPMS_LP1:
case SDE_MODE_DPMS_LP2:
blank = DRM_PANEL_BLANK_LP;
break;
case SDE_MODE_DPMS_OFF:
default:
blank = DRM_PANEL_BLANK_POWERDOWN;//new mode:灭屏操作
break;
notifier_data.data = &new_mode;
drm_panel_notifier_call_chain(connector->panel,event, ¬ifier_data);
tp driver:
data->fb_notif.notifier_call = fb_notifier_callback;
drm_panel_notifier_register(data->active_panel,&data->fb_notif);
fb_notifier_callback
if (action == DRM_PANEL_EARLY_EVENT_BLANK && *transition == DRM_PANEL_BLANK_POWERDOWN)
mxt_secure_touch_stop(mxt_dev_data, 0);
else if (action == DRM_PANEL_EVENT_BLANK) {
if (*transition == DRM_PANEL_BLANK_UNBLANK){
mxt_resume(&mxt_dev_data->client->dev); /* resume */
}
else if (*transition == DRM_PANEL_BLANK_POWERDOWN){
mxt_suspend(&mxt_dev_data->client->dev);/* suspend */
}
}