DRM驱动(四)之ADD_FB

书接上回,使用drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create)接口创建了一块内存放在了gem cma的对象里。

gem obj创建之后需要与一块drm_framebuffer进行绑定使用,因为最终刷图使用的是fb。使用下面接口会创建drm_framebuffer并与上节创建的gem obj进行绑定。

int drmModeAddFB(int fd, uint32_t width, uint32_t height, uint8_t depth,uint8_t bpp, uint32_t pitch, uint32_t bo_handle,uint32_t *buf_id) //老接口

目前用的比较多的是下面两个

int drmModeAddFB2(int fd, uint32_t width, uint32_t height,  uint32_t pixel_format, const uint32_t bo_handles[4],  const uint32_t pitches[4], const uint32_t offsets[4], uint32_t *buf_id, uint32_t flags) 

int drmModeAddFB2WithModifiers(int fd, uint32_t width,  uint32_t height, uint32_t pixel_format, const uint32_t bo_handles[4],  const uint32_t pitches[4], const uint32_t offsets[4], const uint64_t modifier[4], uint32_t *buf_id, uint32_t flags)

重点介绍drmModeAddFB,因为drmModeAddFB会调用add_fb2

上面接口完成了如下的功能

user space的接口就不多说,比较简单,重点看下kernel中的实现

kernel:

后面的介绍全是围绕drm_framebuffer,先看下drm_framebuffer是何方神圣

struct drm_framebuffer {
	struct drm_device *dev;
	struct list_head head;
	struct drm_mode_object base;
	const struct drm_format_info *format;
	const struct drm_framebuffer_funcs *funcs;
	unsigned int pitches[4];
	unsigned int offsets[4];
	uint64_t modifier;
	unsigned int width;
	unsigned int height;
	int flags;
	int hot_x;
	int hot_y;
	struct list_head filp_head;
	struct drm_gem_object *obj[4];
};

其中比较重要的字段format, pitches,offsets,width,height,obj等。

通过应用层ioctl会调到drm的drm_mode_addfb

int drm_mode_addfb(struct drm_device *dev,
 void *data, struct drm_file *file_priv)
{
	struct drm_mode_fb_cmd *or = data;
	struct drm_mode_fb_cmd2 r = {};
	int ret;

	/* convert to new format and call new ioctl */
	r.fb_id = or->fb_id;
	r.width = or->width;
	r.height = or->height;
	r.pitches[0] = or->pitch;
	r.pixel_format = drm_mode_legacy_fb_format(or->bpp, or->depth);
	r.handles[0] = or->handle;

	if (r.pixel_format == DRM_FORMAT_XRGB2101010 &&
	    dev->driver->driver_features & DRIVER_PREFER_XBGR_30BPP)
		r.pixel_format = DRM_FORMAT_XBGR2101010;

	ret = drm_mode_addfb2(dev, &r, file_priv);
	if (ret)
		return ret;

	or->fb_id = r.fb_id;
	return 0;
}

可以看到bpp和depth的作用是找到对应的format。

大致看下drm_mode_legacy_fb_format的实现,不过多介绍,因为大多已经不使用drm_mode_addfb,而是使用drm_mode_addfb2。

uint32_t drm_mode_legacy_fb_format(uint32_t bpp, uint32_t depth)
{
	uint32_t fmt;

	switch (bpp) {
	case 8:
		fmt = DRM_FORMAT_C8;
		break;
	case 16:
		if (depth == 15)
			fmt = DRM_FORMAT_XRGB1555;
		else
			fmt = DRM_FORMAT_RGB565;
		break;
	case 24:
		fmt = DRM_FORMAT_RGB888;
		break;
	case 32:
		if (depth == 24)
			fmt = DRM_FORMAT_XRGB8888;
		else if (depth == 30)
			fmt = DRM_FORMAT_XRGB2101010;
		else
			fmt = DRM_FORMAT_ARGB8888;
		break;
	default:
		DRM_ERROR("bad bpp, assuming x8r8g8b8 pixel format\n");
		fmt = DRM_FORMAT_XRGB8888;
		break;
	}

	return fmt;
}

drm_mode_addfb会调用drm_mode_addfb2。从代码可以看到addfb2里检查了一下权限,就直接调用的drm_internal_framebuffer_create。(下面代码有进行删减)

int drm_mode_addfb2(struct drm_device *dev,
		    void *data, struct drm_file *file_priv)
{
	struct drm_mode_fb_cmd2 *r = data;
	struct drm_framebuffer *fb;

	fb = drm_internal_framebuffer_create(dev, r, file_priv);

	r->fb_id = fb->base.id;

	list_add(&fb->filp_head, &file_priv->fbs);
}

drm_internal_framebuffer_create检查传入参数是否合法,然后调用

dev->mode_config.funcs->fb_create(dev, file_priv, r)

此回调是在drm驱动注册时初始化的,以imx的为例使用的drm提供的创建函数;此函数可以自己实现,而很多芯片厂商都是根据自己需求实现。我们学习,就简单分析下drm提供默认创建fb的函数

static const struct drm_mode_config_funcs mxsfb_mode_config_funcs = {
	.fb_create		= drm_fb_cma_create,
	.atomic_check		= drm_atomic_helper_check,
	.atomic_commit		= drm_atomic_helper_commit,
};

而drm_fb_cma_create->drm_gem_fb_create->drm_gem_fb_create_with_funcs

drm_gem_fb_create_with_funcs通过应用提供的handle,找到上次创建的gem obj。然后调用drm_gem_fb_alloc,分配一个drm_framebuffer,将gem obj赋值给framebuffer中的obj。

struct drm_framebuffer *
drm_gem_fb_create_with_funcs(struct drm_device *dev, struct drm_file *file,
			     const struct drm_mode_fb_cmd2 *mode_cmd,
			     const struct drm_framebuffer_funcs *funcs)
{
	const struct drm_format_info *info;
	struct drm_gem_object *objs[4];
	struct drm_framebuffer *fb;
	int ret, i;

	for (i = 0; i < info->num_planes; i++) {
		unsigned int width = mode_cmd->width / (i ? info->hsub : 1);
		unsigned int height = mode_cmd->height / (i ? info->vsub : 1);
		unsigned int min_size;

		objs[i] = drm_gem_object_lookup(file, mode_cmd->handles[i]);

		min_size = (height - 1) * mode_cmd->pitches[i]
			 + width * info->cpp[i]
			 + mode_cmd->offsets[i];

		if (objs[i]->size < min_size) {
			drm_gem_object_put_unlocked(objs[i]);
			ret = -EINVAL;
			goto err_gem_object_put;
		}
	}

	fb = drm_gem_fb_alloc(dev, mode_cmd, objs, i, funcs);
	return fb;
}
static struct drm_framebuffer *
drm_gem_fb_alloc(struct drm_device *dev,
		 const struct drm_mode_fb_cmd2 *mode_cmd,
		 struct drm_gem_object **obj, unsigned int num_planes,
		 const struct drm_framebuffer_funcs *funcs)
{
	struct drm_framebuffer *fb;
	int ret, i;

	fb = kzalloc(sizeof(*fb), GFP_KERNEL);

	drm_helper_mode_fill_fb_struct(dev, fb, mode_cmd);

	for (i = 0; i < num_planes; i++)
		fb->obj[i] = obj[i];

	ret = drm_framebuffer_init(dev, fb, funcs);

	return fb;
}

将应用传入的长,宽,format,pitch,flag等赋值给drm_framebuffer。

void drm_helper_mode_fill_fb_struct(struct drm_device *dev,
				    struct drm_framebuffer *fb,
				    const struct drm_mode_fb_cmd2 *mode_cmd)
{
	int i;

	fb->dev = dev;
	fb->format = drm_get_format_info(dev, mode_cmd);
	fb->width = mode_cmd->width;
	fb->height = mode_cmd->height;
	for (i = 0; i < 4; i++) {
		fb->pitches[i] = mode_cmd->pitches[i];
		fb->offsets[i] = mode_cmd->offsets[i];
	}
	fb->modifier = mode_cmd->modifier[0];
	fb->flags = mode_cmd->flags;
}

相关字段填充完成后会将fb添加进dev->mode_config.fb_list便于后面使用的时候通过id进行查找,

int drm_framebuffer_init(struct drm_device *dev, struct drm_framebuffer *fb,
			 const struct drm_framebuffer_funcs *funcs)
{
	int ret;


	INIT_LIST_HEAD(&fb->filp_head);

	fb->funcs = funcs;
	ret = __drm_mode_object_add(dev, &fb->base, DRM_MODE_OBJECT_FB,
				    false, drm_framebuffer_free);

	dev->mode_config.num_fb++;
	list_add(&fb->head, &dev->mode_config.fb_list);

	drm_mode_object_register(dev, &fb->base);
	return ret;
}

drm的组件都是通过dev->mode_config中的链表进行管理的,只要拿到mode_config就可以拿到drm相关信息

  • struct list_head fb_list

至此drm_framebuffer已经创建完毕。通过r->fb_id = fb->base.id;将fb_id返回给用户。

大致流程:

总结:

        整个流程就是创建一个drm_framebuffer并根据应用提供的参数进行填充对应字段(为了后面驱动根据对应字段配置显示的寄存器)。然后添加进mode_config的fb_list,返回给fb_id给用户。

<think>好的,我需要解释用户提出的关于Linux fbdev框架中`fb_edid_to_monspecs`函数的问题。用户之前已经询问过`fb_info`结构体中`monspecs.modedb_len`的意义和填写逻辑,现在进一步询问`fb_edid_to_monspecs`函数的作用。我需要确保回答结构清晰,符合用户提供的系统级指令和可见层要求。 首先,我需要回忆`fb_edid_to_monspecs`函数的作用。这个函数在Linux内核的帧缓冲(fbdev)子系统中用于解析显示器的EDID(Extended Display Identification Data)数据,并将解析后的信息填充到`fb_monspecs`结构体中。EDID数据包含了显示器支持的视频模式、制造商信息、分辨率等详细信息。`fb_edid_to_monspecs`的任务就是将这些数据转换为内核可以使用的格式,尤其是生成`modedb`数组和设置`modedb_len`。 接下来,我需要确定回答的结构。根据用户之前的提问和回答,用户可能希望了解这个函数的功能、工作流程、参数和返回值,以及它在系统中的角色。需要分步骤解释,确保逻辑清晰,同时遵循中文回答和格式要求,如行内公式使用`$...$`,独立公式使用`$$...$$`,虽然在这个问题中可能数学公式较少,但需要注意代码块的格式。 可能会涉及到的知识点包括EDID的结构、如何解析EDID数据、`fb_monspecs`结构体的成员,以及该函数如何与`fb_info`交互。需要注意技术细节的准确性,例如EDID的版本(如EDID 1.3或1.4)、解析的具体步骤(如校验和验证、时序描述符的提取)、支持的视频模式如何被转换为`fb_videomode`结构体数组等。 需要确认是否有代码示例或内核源码中的具体函数调用链可以作为例子,比如`fb_edid_to_monspecs`的调用位置,可能是在显示器热插拔时或初始化阶段被调用。此外,可能需要提到错误处理,例如当EDID数据损坏或无法解析时,函数如何回退到默认模式。 还需要考虑用户可能的后续问题,例如该函数与之前提到的`fb_find_mode`或`fb_add_videomode`的关系,或者在实际开发中如何调试EDID解析问题。不过根据当前问题,重点应放在`fb_edid_to_monspecs`本身。 需要检查是否有任何潜在的技术错误,例如函数参数的类型、返回值的含义,以及是否正确处理了不同EDID版本的情况。此外,是否该函数在内核的不同版本中有变化,是否需要注明版本差异?用户没有提到特定内核版本,因此可能不需要涉及,但可以说明一般情况。 最后,确保回答符合用户给出的格式要求:结构清晰,分步骤说明,使用中文,正确使用LaTeX格式(如果有数学表达式),代码块用反引号包裹,结构体成员用适当的方式突出显示等。同时,用户可能希望了解实际应用中的例子,例如在驱动开发中如何利用该函数,或者在调试EDID相关问题时如何查看解析结果。 总结来说,回答的结构应包括: 1. 函数的作用和目的 2. 输入参数和输出结果 3. 内部工作流程(解析EDID的步骤) 4. 与`fb_monspecs`结构体的关系 5. 错误处理和回退机制 6. 实际应用示例或代码片段 7. 相关调试方法 在组织内容时,要确保逐步展开,从基本概念到具体实现细节,帮助用户逐步理解函数的机制。同时,避免过于深入内核源码细节,除非对理解函数的行为有帮助。需要保持回答的准确性和易懂性,适合不同层次的技术读者。</think>### `fb_edid_to_monspecs` 函数详解 #### 一、功能与作用 `fb_edid_to_monspecs` 是 Linux 内核帧缓冲(fbdev)子系统中的关键函数,**用于解析显示器的 EDID 数据**,并填充到 `struct fb_monspecs` 结构体中。其核心任务是将显示器报告的原始二进制 EDID 数据转换为内核可识别的视频模式列表(`modedb`)和显示器规格信息。 --- #### 二、函数原型与参数 ```c int fb_edid_to_monspecs(unsigned char *edid, struct fb_monspecs *specs); ``` - **输入参数**: - `edid`:指向 EDID 原始数据的指针(128字节或更大,符合 EDID 标准)。 - `specs`:目标 `fb_monspecs` 结构体指针,用于存储解析结果。 - **返回值**: - `0`:解析成功。 - 非零值:解析失败(如 EDID 校验和错误)。 --- #### 三、工作流程解析 ##### 步骤1:EDID 基础校验 1. **版本检查** 验证 EDID 头部的版本标识,支持 EDID 1.3 或更高版本: ```c if (edid[0] != 0x00 || edid[1] != 0xff || ...) // 检查头标志 return -EINVAL; ``` 2. **校验和验证** 计算 EDID 前 128 字节的校验和,确保数据完整性: $$ \text{checksum} = \sum_{i=0}^{127} \text{edid}[i] \mod 256 = 0 $$ 若不满足,返回错误。 ##### 步骤2:基础信息提取 解析 EDID 的固定字段: - **制造商 ID**(如 `"DEL"` 表示 Dell) - **产品序列号** - **生产日期** - **显示器物理尺寸**(单位:厘米) ##### 步骤3:时序描述符解析 遍历 EDID 的时序描述符块(通常位于字节 54-125): 1. **详细时序描述符**(Detailed Timing Descriptors) 解析分辨率、刷新率等关键参数: ```c struct fb_videomode mode; mode.xres = (edid[offset+2] << 8) | edid[offset+1]; mode.yres = (edid[offset+5] << 8) | edid[offset+4]; mode.refresh = calc_refresh(edid+offset); ``` 2. **标准时序标识码**(Standard Timing Identifiers) 将标准代码(如 VESA 定义的 0x2025)转换为具体分辨率(如 1920x1080@60Hz)。 ##### 步骤4:模式列表生成 将解析出的所有有效模式存入 `specs->modedb` 数组,并更新 `specs->modedb_len`。 --- #### 、关键数据结构 解析结果存储在 `fb_monspecs` 中: ```c struct fb_monspecs { struct fb_videomode *modedb; // 模式数组 __u8 modedb_len; // 模式数量 __u8 manufacturer[4]; // 制造商代码(如 "ACI") __u16 max_x; // 最大水平分辨率 __u16 max_y; // 最大垂直分辨率 ... }; ``` --- #### 五、典型调用场景 1. **显示器热插拔** 当检测到新显示器连接时,通过 I2C/DDC 总线读取 EDID,调用此函数解析: ```c // 驱动示例代码片段 unsigned char edid[128]; if (read_edid_from_i2c(adapter, &edid) == 0) { fb_edid_to_monspecs(edid, &info->monspecs); } ``` 2. **初始化阶段** 显卡驱动加载时,若支持 EDID,优先使用此函数初始化显示模式。 --- #### 六、错误处理与回退 若解析失败(如 EDID 损坏): 1. 内核日志输出警告:`dmesg | grep "EDID invalid"` 2. 回退到预定义模式列表(如 `fb_default_modes`) 3. 使用通用分辨率(如 1024x768@60Hz) --- #### 七、调试技巧 1. **查看解析后的模式列表**: ```bash cat /sys/class/graphics/fb0/modes ``` 2. **导出原始 EDID**: ```bash cp /sys/class/drm/card0-HDMI-A-1/edid /tmp/edid.bin ``` 3. **使用 `edid-decode` 工具分析**: ```bash edid-decode edid.bin # 显示详细解析结果 ``` --- ### 总结 `fb_edid_to_monspecs` 是连接显示器硬件能力与软件配置的核心桥梁。其通过精准解析 EDID 数据,确保系统能够自动适配显示器的最佳分辨率,并为多屏配置提供关键信息支持。理解此函数的工作原理,对调试显示问题或开发定制化显示驱动至关重要。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值