4. KMS 驱动核心数据结构:初始化与注册
在 DRM/KMS 驱动中,三个核心数据结构构成了驱动与内核框架之间的桥梁:struct drm_driver(驱动描述符)、struct drm_device(设备实例)和 struct drm_mode_config_funcs(模式配置回调)。理解它们的初始化顺序与注册时机,是从 FBDEV 平滑迁移到 KMS 的关键一步。
4.1 struct drm_driver:驱动描述符的填充
struct drm_driver 是 DRM 框架识别驱动的“身份证”,它定义了驱动名称、支持的 GPU 系列、文件操作接口、以及各种回调函数。在 KMS 驱动中,该结构体通常以 static const 形式定义,并在模块入口处注册。
| 字段 | 类型 | 说明 | KMS 驱动典型值 |
|---|---|---|---|
driver_features |
u32 | 驱动能力标志位 | DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC |
name |
const char * | 驱动名称,用于 sysfs 和调试 | "mykms" |
desc |
const char * | 驱动描述 | "My KMS Driver" |
date |
const char * | 驱动日期 | "20250301" |
major/minor/patchlevel |
int | 驱动版本号 | 1, 0, 0 |
fops |
const struct file_operations * | 用户空间文件操作接口 | &mykms_fops |
gem_create_object |
回调 | GEM 对象创建函数 | mykms_gem_create_object |
prime_handle_to_fd/prime_fd_to_handle |
回调 | DMA-BUF 导入导出 | drm_gem_prime_* 通用实现 |
DRIVER_MODESET:必须设置,表示驱动使用 KMS 而非 FBDEV。DRIVER_GEM:必须设置,启用 GEM 内存管理。DRIVER_ATOMIC:推荐设置,启用原子模式设置(Atomic Modeset)。DRIVER_RENDER:可选,若支持 GPU 渲染则设置。
以下是一个典型的 drm_driver 定义示例:
static const struct file_operations mykms_fops = {
.owner = THIS_MODULE,
.open = drm_open,
.release = drm_release,
.unlocked_ioctl = drm_ioctl,
.mmap = drm_gem_mmap,
.poll = drm_poll,
.read = drm_read,
};
static const struct drm_driver mykms_driver = {
.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
.name = "mykms",
.desc = "My KMS Driver",
.date = "20250301",
.major = 1,
.minor = 0,
.patchlevel = 0,
.fops = &mykms_fops,
.gem_create_object = mykms_gem_create_object,
.prime_handle_to_fd = drm_gem_prime_handle_to_fd,
.prime_fd_to_handle = drm_gem_prime_fd_to_handle,
.gem_prime_import = drm_gem_prime_import,
.gem_prime_export = drm_gem_prime_export,
.dumb_create = mykms_dumb_create,
.dumb_map_offset = mykms_dumb_map_offset,
.dumb_destroy = drm_gem_dumb_destroy,
};
4.2 struct drm_device:设备实例的创建与初始化
struct drm_device 是 DRM 框架中代表一个显示设备的实例。它通过 drm_dev_alloc() 或 drm_dev_init() 创建,并在 probe 函数中完成初始化。KMS 驱动通常使用 devm_drm_dev_alloc() 进行资源管理,简化错误处理。
初始化流程包含以下关键步骤:
- 分配设备实例:调用
devm_drm_dev_alloc(),传入父设备、驱动描述符和私有数据结构体大小。 - 设置 DRM 设备私有数据:通过
container_of或直接访问返回的指针,初始化硬件相关字段。 - 模式配置初始化:调用
drm_mode_config_init(),并填充drm_mode_config_funcs。 - 注册 DRM 设备:调用
drm_dev_register(),使设备对用户空间可见。
struct mykms_private {
struct drm_device drm;
void __iomem *regs;
int irq;
struct clk *clk;
};
static int mykms_probe(struct platform_device *pdev)
{
struct mykms_private *priv;
struct drm_device *drm;
int ret;
// 1. 分配设备实例,包含私有数据
priv = devm_drm_dev_alloc(&pdev->dev, &mykms_driver,
struct mykms_private, drm);
if (IS_ERR(priv))
return PTR_ERR(priv);
drm = &priv->drm;
// 2. 初始化硬件资源(寄存器、时钟、中断等)
priv->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(priv->regs))
return PTR_ERR(priv->regs);
// 3. 模式配置初始化
drm_mode_config_init(drm);
drm->mode_config.funcs = &mykms_mode_config_funcs;
drm->mode_config.min_width = 640;
drm->mode_config.max_width = 1920;
drm->mode_config.min_height = 480;
drm->mode_config.max_height = 1080;
// 4. 注册 DRM 设备
ret = drm_dev_register(drm, 0);
if (ret)
return ret;
platform_set_drvdata(pdev, drm);
return 0;
}
drm_dev_register() 之前,必须确保所有模式配置参数(如 min_width、max_width)和回调函数已经设置完毕。否则,用户空间在设备注册后立即查询能力时可能读到未初始化的值。
4.3 struct drm_mode_config_funcs:模式配置回调的实现
struct drm_mode_config_funcs 是 KMS 驱动的“模式设置引擎”,它定义了帧缓冲创建、原子提交、输出属性验证等核心操作。在支持原子模式设置的驱动中,atomic_check 和 atomic_commit 是最重要的两个回调。
| 回调函数 | 触发时机 | 典型实现 |
|---|---|---|
fb_create |
用户空间调用 ADDFB2 ioctl |
分配 GEM 对象并创建 drm_framebuffer |
output_poll_changed |
热插拔事件触发 | 调用 drm_kms_helper_hotplug_event() |
atomic_check |
原子提交前验证 | 检查时钟、带宽、扫描线限制 |
atomic_commit |
原子提交执行 | 编程硬件寄存器,触发 VBLANK |
atomic_state_alloc/free |
原子状态管理 | 分配/释放驱动私有状态结构体 |
以下是一个简化的 drm_mode_config_funcs 实现:
static int mykms_fb_create(struct drm_device *drm,
struct drm_framebuffer *fb,
struct drm_file *file_priv,
const struct drm_mode_fb_cmd2 *mode_cmd)
{
// 验证像素格式、宽度、高度
// 分配 GEM 对象作为 backing storage
// 调用 drm_gem_fb_create() 或自定义实现
return drm_gem_fb_create(drm, fb, file_priv, mode_cmd);
}
static int mykms_atomic_check(struct drm_device *drm,
struct drm_atomic_state *state)
{
// 遍历所有 CRTC、plane、connector 的状态
// 检查时钟频率是否在硬件支持范围内
// 检查扫描线带宽是否超过限制
// 返回 0 表示验证通过,负值表示拒绝
return 0;
}
static int mykms_atomic_commit(struct drm_device *drm,
struct drm_atomic_state *state,
bool nonblock)
{
// 非阻塞提交:将状态复制到硬件 shadow 寄存器
// 阻塞提交:等待 VBLANK 后切换寄存器
// 调用 drm_atomic_helper_commit() 或自定义实现
return drm_atomic_helper_commit(drm, state, nonblock);
}
static const struct drm_mode_config_funcs mykms_mode_config_funcs = {
.fb_create = mykms_fb_create,
.output_poll_changed = mykms_output_poll_changed,
.atomic_check = mykms_atomic_check,
.atomic_commit = mykms_atomic_commit,
.atomic_state_alloc = mykms_atomic_state_alloc,
.atomic_state_free = mykms_atomic_state_free,
};
4.4 数据结构关系与初始化流程
下图展示了三个核心数据结构在驱动初始化过程中的关系与调用顺序:
4.5 注册与注销的对称性
KMS 驱动必须保证初始化与注销的对称性。在 remove 回调中,需要按相反顺序释放资源:
static int mykms_remove(struct platform_device *pdev)
{
struct drm_device *drm = platform_get_drvdata(pdev);
drm_dev_unregister(drm); // 先注销设备
drm_atomic_helper_shutdown(drm); // 关闭所有 CRTC
// devm 管理的资源自动释放
return 0;
}
- 优先使用
devm_drm_dev_alloc()而非drm_dev_alloc(),前者在设备移除时自动释放。 - 在
remove中显式调用drm_atomic_helper_shutdown(),确保所有显示管道被正确关闭。 - 使用
drm_dev_unregister()而非drm_dev_put(),前者会同步移除 sysfs 节点并等待所有打开的文件句柄关闭。
4.6 从 FBDEV 迁移的注意事项
| FBDEV 做法 | KMS 做法 | 迁移要点 |
|---|---|---|
register_framebuffer() |
drm_dev_register() |
KMS 不再直接注册 fb_info,而是通过 DRM 框架管理 |
fb_ops->fb_setcoloreg |
drm_mode_config_funcs->atomic_commit |
颜色查找表通过 CRTC 的 gamma_lut 属性设置 |
fb_ops->fb_pan_display |
drm_plane_state->src_x/src_y |
平移操作通过 plane 的源坐标属性实现 |
struct fb_var_screeninfo |
struct drm_display_mode |
显示模式通过 connector 的 modes 列表管理 |
| 直接操作寄存器设置模式 | drm_crtc_helper_funcs->mode_set |
硬件编程逻辑封装在 CRTC 回调中 |
通过以上三个核心数据结构的正确初始化与注册,KMS 驱动能够与 DRM 框架无缝协作,为用户空间提供统一的显示控制接口。下一阶段将深入每个子对象的实现细节,包括 CRTC、Encoder、Connector 和 Plane 的创建与配置。