文章目录
- 前言
- 屏幕探测部分
-
-
- 1) 打开 DRM 设备节点 打开 DRM 设备节点)
- 2) 读取 DRM 全局资源表 读取 DRM 全局资源表)
- 3) 遍历每个 connector(输出口),打印其状态和模式列表 遍历每个 connector(输出口),打印其状态和模式列表)
-
- [`drmModeGetConnector()` 返回的关键字段含义](#
drmModeGetConnector()返回的关键字段含义) - [`drmModeModeInfo` 里打印了哪些关键字段?](#
drmModeModeInfo里打印了哪些关键字段?) - 刷新率怎么算出来的?
- [`drmModeGetConnector()` 返回的关键字段含义](#
- 4) "自动选择最合适的模式" “自动选择最合适的模式”)
- 5) 找到 encoder / crtc(把"输出口"落到"实际显示 pipeline") 找到 encoder / crtc(把“输出口”落到“实际显示 pipeline”))
-
前言
昨天完成了基础的屏幕测试后,今天决定对drm操作屏幕这块,做一点细化的学习和测试
但要做到:每天结束时我清楚知道"我做了什么 + 打通了什么链路"
并且能快速看到结果,于是今天决定完成:
** 屏幕上出现可控画面(纯色/渐变/色块动画)**
于是我决定做三部分:
- 屏幕信息探测
- 最小显示封装:
mini_drm - 验证 demo:
drm_color_demo
屏幕探测部分
首先创建一个ui_drm_probe的文件夹,并编写一个探测代码
c
// drm_probe.c
// 用途:探测 DRM/KMS 资源,列出 connector/mode,并自动选择最合适的显示输出。
// 编译:见 Makefile
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
static const char* conn_type_name(uint32_t type) {
switch (type) {
case DRM_MODE_CONNECTOR_Unknown: return "Unknown";
case DRM_MODE_CONNECTOR_VGA: return "VGA";
case DRM_MODE_CONNECTOR_DVII: return "DVI-I";
case DRM_MODE_CONNECTOR_DVID: return "DVI-D";
case DRM_MODE_CONNECTOR_DVIA: return "DVI-A";
case DRM_MODE_CONNECTOR_Composite: return "Composite";
case DRM_MODE_CONNECTOR_SVIDEO: return "SVIDEO";
case DRM_MODE_CONNECTOR_LVDS: return "LVDS";
case DRM_MODE_CONNECTOR_Component: return "Component";
case DRM_MODE_CONNECTOR_9PinDIN: return "DIN";
case DRM_MODE_CONNECTOR_DisplayPort: return "DP";
case DRM_MODE_CONNECTOR_HDMIA: return "HDMI-A";
case DRM_MODE_CONNECTOR_HDMIB: return "HDMI-B";
case DRM_MODE_CONNECTOR_TV: return "TV";
case DRM_MODE_CONNECTOR_eDP: return "eDP";
case DRM_MODE_CONNECTOR_VIRTUAL: return "VIRTUAL";
case DRM_MODE_CONNECTOR_DSI: return "DSI";
default: return "Other";
}
}
static int mode_score(const drmModeModeInfo* m) {
// 评分策略:
// 1) 优先 1920x1080
// 2) 否则按分辨率面积最大优先
int w = m->hdisplay;
int h = m->vdisplay;
int area = w * h;
if (w == 1920 && h == 1080) return 100000000 + area;
return area;
}
int main(int argc, char** argv) {
const char* card = "/dev/dri/card0";
if (argc >= 2) card = argv[1];
printf("[DRM探测] 打开设备:%s\n", card);
int fd = open(card, O_RDWR | O_CLOEXEC);
if (fd < 0) {
printf("[DRM探测][错误] open 失败:%s\n", strerror(errno));
return 1;
}
drmModeRes* res = drmModeGetResources(fd);
if (!res) {
printf("[DRM探测][错误] drmModeGetResources 失败:%s\n", strerror(errno));
close(fd);
return 1;
}
printf("[DRM探测] connectors=%d encoders=%d crtcs=%d fbs=%d\n",
res->count_connectors, res->count_encoders, res->count_crtcs, res->count_fbs);
uint32_t best_conn_id = 0;
drmModeModeInfo best_mode;
memset(&best_mode, 0, sizeof(best_mode));
int best_score = -1;
uint32_t best_enc_id = 0;
for (int i = 0; i < res->count_connectors; i++) {
uint32_t cid = res->connectors[i];
drmModeConnector* conn = drmModeGetConnector(fd, cid);
if (!conn) continue;
printf("\n[Connector] id=%u 类型=%s(%u) 状态=%s 模式数=%d\n",
conn->connector_id,
conn_type_name(conn->connector_type),
conn->connector_type,
(conn->connection == DRM_MODE_CONNECTED) ? "已连接" :
(conn->connection == DRM_MODE_DISCONNECTED) ? "未连接" : "未知",
conn->count_modes);
for (int m = 0; m < conn->count_modes; m++) {
drmModeModeInfo* mode = &conn->modes[m];
int vrefresh = 0;
if (mode->htotal && mode->vtotal)
vrefresh = (int)((mode->clock * 1000LL) / (mode->htotal * mode->vtotal));
printf(" - mode[%d]: %s %dx%d refresh≈%dHz flags=0x%x type=0x%x\n",
m, mode->name, mode->hdisplay, mode->vdisplay, vrefresh, mode->flags, mode->type);
}
if (conn->connection == DRM_MODE_CONNECTED && conn->count_modes > 0) {
// 选择此 connector 下的最优 mode
int local_best = -1;
int local_best_score = -1;
for (int m = 0; m < conn->count_modes; m++) {
int sc = mode_score(&conn->modes[m]);
if (sc > local_best_score) {
local_best_score = sc;
local_best = m;
}
}
// encoder_id 可能为 0,需要通过 encoders 关联再选(这里先打印出来)
printf("[Connector] 当前 encoder_id=%u\n", conn->encoder_id);
if (local_best >= 0 && local_best_score > best_score) {
best_score = local_best_score;
best_conn_id = conn->connector_id;
best_mode = conn->modes[local_best];
best_enc_id = conn->encoder_id;
}
}
drmModeFreeConnector(conn);
}
if (!best_conn_id) {
printf("\n[DRM探测][错误] 没有找到"已连接且有模式"的 connector。\n");
drmModeFreeResources(res);
close(fd);
return 2;
}
// 尝试找到与 connector 对应的 encoder/crtc
drmModeConnector* best_conn = drmModeGetConnector(fd, best_conn_id);
if (!best_conn) {
printf("[DRM探测][错误] 读取 best connector 失败\n");
drmModeFreeResources(res);
close(fd);
return 3;
}
uint32_t crtc_id = 0;
uint32_t enc_id = best_conn->encoder_id;
// 如果 connector->encoder_id 为 0,就尝试从 connector->encoders 列表选一个
if (enc_id == 0 && best_conn->count_encoders > 0) {
enc_id = best_conn->encoders[0];
}
drmModeEncoder* enc = NULL;
if (enc_id) enc = drmModeGetEncoder(fd, enc_id);
if (enc) {
crtc_id = enc->crtc_id;
}
printf("\n[DRM探测][选择结果]\n");
printf(" - connector_id = %u\n", best_conn_id);
printf(" - mode = %s %dx%d\n", best_mode.name, best_mode.hdisplay, best_mode.vdisplay);
printf(" - encoder_id = %u\n", enc_id);
printf(" - crtc_id = %u\n", crtc_id);
if (!crtc_id) {
printf(" - 提示:crtc_id 为 0,可能需要更复杂的 encoder/crtc 匹配逻辑。\n");
}
if (enc) drmModeFreeEncoder(enc);
drmModeFreeConnector(best_conn);
drmModeFreeResources(res);
close(fd);
printf("\n[DRM探测] 完成。\n");
return 0;
}
运行结果如下:
sh
[root@canaan ~ ]$ ./drm_probe
[DRM探测] 打开设备:/dev/dri/card0
[DRM探测] connectors=1 encoders=1 crtcs=1 fbs=0
[Connector] id=49 类型=DSI(16) 状态=已连接 模式数=1
- mode[0]: 1080x1920 1080x1920 refresh≈30Hz flags=0xa type=0x48
[Connector] 当前 encoder_id=48
[DRM探测][选择结果]
- connector_id = 49
- mode = 1080x1920 1080x1920
- encoder_id = 48
- crtc_id = 57
[DRM探测] 完成。
这部分代码比较简单,我只说几个关键的函数接口和理解
1) 打开 DRM 设备节点
日志:
sh
[DRM探测] 打开设备:/dev/dri/card0
代码:
c
open(card, O_RDWR | O_CLOEXEC)
解释:
DRM 显示设备在 Linux 下通常是 /dev/dri/cardX
板子上是 card0
2) 读取 DRM 全局资源表
sh
[DRM探测] connectors=1 encoders=1 crtcs=1 fbs=0
代码是这个:
c
drmModeRes* res = drmModeGetResources(fd);
if (!res) {
printf("[DRM探测][错误] drmModeGetResources 失败:%s\n", strerror(errno));
close(fd);
return 1;
}
printf("[DRM探测] connectors=%d encoders=%d crtcs=%d fbs=%d\n",
res->count_connectors, res->count_encoders, res->count_crtcs, res->count_fbs);
解释一下:
-
res->count_connectors / res->connectors[]
机器上有哪些"输出口"(DSI/HDMI/eDP...)
-
res->count_encoders / res->encoders[]
把"像素数据流"编码/绑定到输出口的部件(抽象层)
-
res->count_crtcs / res->crtcs[]
真正负责"扫描输出"的显示控制器(CRTC 常被理解成一个显示 pipeline)
-
res->count_fbs / res->fbs[]
当前内核里注册的 framebuffer 对象数量
3) 遍历每个 connector(输出口),打印其状态和模式列表
这部分日志如下
sh
[Connector] id=49 类型=DSI(16) 状态=已连接 模式数=1
- mode[0]: 1080x1920 1080x1920 refresh≈30Hz flags=0xa type=0x48
[Connector] 当前 encoder_id=48
主要代码如下
c
uint32_t best_conn_id = 0;
drmModeModeInfo best_mode;
memset(&best_mode, 0, sizeof(best_mode));
int best_score = -1;
uint32_t best_enc_id = 0;
for (int i = 0; i < res->count_connectors; i++) {
uint32_t cid = res->connectors[i];
drmModeConnector* conn = drmModeGetConnector(fd, cid);
if (!conn) continue;
printf("\n[Connector] id=%u 类型=%s(%u) 状态=%s 模式数=%d\n",
conn->connector_id,
conn_type_name(conn->connector_type),
conn->connector_type,
(conn->connection == DRM_MODE_CONNECTED) ? "已连接" :
(conn->connection == DRM_MODE_DISCONNECTED) ? "未连接" : "未知",
conn->count_modes);
for (int m = 0; m < conn->count_modes; m++) {
drmModeModeInfo* mode = &conn->modes[m];
int vrefresh = 0;
if (mode->htotal && mode->vtotal)
vrefresh = (int)((mode->clock * 1000LL) / (mode->htotal * mode->vtotal));
printf(" - mode[%d]: %s %dx%d refresh≈%dHz flags=0x%x type=0x%x\n",
m, mode->name, mode->hdisplay, mode->vdisplay, vrefresh, mode->flags, mode->type);
}
if (conn->connection == DRM_MODE_CONNECTED && conn->count_modes > 0) {
// 选择此 connector 下的最优 mode
int local_best = -1;
int local_best_score = -1;
for (int m = 0; m < conn->count_modes; m++) {
int sc = mode_score(&conn->modes[m]);
if (sc > local_best_score) {
local_best_score = sc;
local_best = m;
}
}
// encoder_id 可能为 0,需要通过 encoders 关联再选(这里先打印出来)
printf("[Connector] 当前 encoder_id=%u\n", conn->encoder_id);
if (local_best >= 0 && local_best_score > best_score) {
best_score = local_best_score;
best_conn_id = conn->connector_id;
best_mode = conn->modes[local_best];
best_enc_id = conn->encoder_id;
}
}
drmModeFreeConnector(conn);
}
-
cid = res->connectors[i] -
drmModeConnector* conn = drmModeGetConnector(fd, cid); -
打印:
conn->connector_idconn->connector_type(用conn_type_name()翻译成人话)conn->connection(CONNECTED / DISCONNECTED / UNKNOWN)conn->count_modes
-
遍历
conn->modes[m](每个是drmModeModeInfo)
drmModeGetConnector() 返回的关键字段含义
connector_id:connector 的唯一 ID(这里是 49)connector_type:类型(这里是 DSI(16),典型就是 MIPI-DSI 屏)connection:是否连接(这里是 已连接)count_modes+modes[]:这个输出口支持的显示模式列表
这里只有 1 个模式:1080x1920
drmModeModeInfo 里打印了哪些关键字段?
mode->name:通常类似"1080x1920"(驱动给的名字)hdisplay / vdisplay:分辨率(宽/高)clock / htotal / vtotal:用来推算刷新率flags / type:驱动标记(比如 preferred、driver 等)
刷新率怎么算出来的?
代码:
c
if (mode->htotal && mode->vtotal)
vrefresh = (int)((mode->clock * 1000LL) / (mode->htotal * mode->vtotal));
解释:
mode->clock单位通常是 kHz(clock * 1000) / (htotal * vtotal)≈ Hz- 所以这里看到
refresh≈30Hz(大约 30Hz)
4) "自动选择最合适的模式"
代码的策略在 mode_score():
c
// 1) 优先 1920x1080
// 2) 否则按分辨率面积最大优先
if (w == 1920 && h == 1080) return 100000000 + area;
return area;
但实际 connector 只有一个模式:1080x1920
所以最终就选它(面积也是最大,因为只有它)。
5) 找到 encoder / crtc(把"输出口"落到"实际显示 pipeline")
日志:
sh
[DRM探测][选择结果]
- connector_id = 49
- mode = 1080x1920 1080x1920
- encoder_id = 48
- crtc_id = 57
代码:
-
重新拿一次 connector(为了稳妥)
best_conn = drmModeGetConnector(fd, best_conn_id);
-
取 encoder id
enc_id = best_conn->encoder_id;- 如果
encoder_id == 0,就从best_conn->encoders[0]猜一个
-
读取 encoder
enc = drmModeGetEncoder(fd, enc_id);
-
从 encoder 里拿到 CRTC
crtc_id = enc->crtc_id;
这几个对象之间的关系
- Connector(输出口):DSI/HDMI... "屏插在哪"
- Encoder(编码器/绑定器):把 CRTC 输出"接到"某个 connector
- CRTC(显示管线):负责扫描输出(决定时序、从 framebuffer 取像素输出)
- Mode(分辨率/时序):告诉 CRTC 要怎么扫(1080x1920@30Hz)
现在确定了:
- 输出口:DSI connector_id=49
- 对应 encoder:48
- 最终走的 CRTC:57
- 模式:1080x1920@~30Hz
到这一步,屏幕信息探测的部分就完成了