参考 https://github.com/dvdhrm/docs/tree/master/drm-howto 中的 modeset-atomic.c

Modeset prepare

首先 open drm 设备节点:

fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC);

设置 client 的 capability:

drmSetClientCap(fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
drmSetClientCap(fd, DRM_CLIENT_CAP_ATOMIC, 1);

第一行设置 drm file_priv->universal_planes=1。
第二行设置 file_priv->atomic=1, file_priv->universal_planes=1, file_priv->aspect_ratio_allowed = 1。


获取 client 的 capability:

drmGetCap(fd, DRM_CAP_DUMB_BUFFER, &cap);
drmGetCap(fd, DRM_CAP_CRTC_IN_VBLANK_EVENT, &cap);

第一行,如果 drm_driver 提供了 dumb_create 回调,则返回 1,一般都会提供。 第二行,必定返回 1,现在 driver 都会支持 Vblank 事件。


获取 resources:

drmModeGetResources();
drmIoctl(fd, DRM_IOCTL_MODE_GETRESOURCES, &res);

得到 fbs/crtcs/connectors/encoders 的数量,以及每个对应的 id,还有显示支持的最大最小长宽,填充结构体:

typedef struct _drmModeRes {
	int count_fbs;
	uint32_t *fbs;
	int count_crtcs;
	uint32_t *crtcs;
	int count_connectors;
	uint32_t *connectors;
	int count_encoders;
	uint32_t *encoders;
	uint32_t min_width, max_width;
	uint32_t min_height, max_height;
} drmModeRes, *drmModeResPtr;

获取 connector:

根据上面的 connectors id,获取 connector

drmModeGetConnector(fd, res->connectors[i]);
drmIoctl(fd, DRM_IOCTL_MODE_GETCONNECTOR, &conn);

填充 connector 结构体:

typedef struct _drmModeConnector {
	uint32_t connector_id; // 传入的connector id
	uint32_t encoder_id; // 对应的encoder id
	uint32_t connector_type; // 在connector_init中分配的DRM_MODE_CONNECTOR_XXX
	uint32_t connector_type_id; // 在connector_init中分配的一个unique id
	drmModeConnection connection;
	uint32_t mmWidth, mmHeight; //panel的物理长宽
	drmModeSubPixel subpixel;

	int count_modes;
	drmModeModeInfoPtr modes;

	int count_props;
	uint32_t *props; /**< List of property ids */
	uint64_t *prop_values; /**< List of property values */

	int count_encoders;
	uint32_t *encoders; /**< List of encoder ids */
} drmModeConnector, *drmModeConnectorPtr;

创建自定的 property blob:

drmModeCreatePropertyBlob(fd, &out->mode, sizeof(out->mode),
				      &out->mode_blob_id);
DRM_IOCTL(fd, DRM_IOCTL_MODE_CREATEPROPBLOB, &create);

根据 connector 对应的 encoder id,获取 encoder, 再根据 encoder id 找到对应的 crtc id 保存起来:

drmModeGetEncoder(fd, conn->encoder_id);
drmIoctl(fd, DRM_IOCTL_MODE_GETENCODER, &enc);

填充 encoder 结构体:

typedef struct _drmModeEncoder {
	uint32_t encoder_id;
	uint32_t encoder_type; // DRM_MODE_ENCODER_XXX
	uint32_t crtc_id;
	uint32_t possible_crtcs;
	uint32_t possible_clones;
} drmModeEncoder, *drmModeEncoderPtr;

获取 plane resource, 包括 plane id 数组和 plane count:

drmModeGetPlaneResources(fd);
drmIoctl(fd, DRM_IOCTL_MODE_GETPLANERESOURCES, &res);

根据 plane id 获取 plane:

drmModePlanePtr plane = drmModeGetPlane(fd, plane_id);
drmIoctl(fd, DRM_IOCTL_MODE_GETPLANE, &ovr);

获取 plane,connector,crtc 的 properties:

modeset_get_object_properties(fd, connector, DRM_MODE_OBJECT_CONNECTOR);
modeset_get_object_properties(fd, crtc, DRM_MODE_OBJECT_CRTC);
modeset_get_object_properties(fd, plane, DRM_MODE_OBJECT_PLANE);

drmIoctl(fd, DRM_IOCTL_MODE_OBJ_GETPROPERTIES, &properties); // 获取crtc/plane/connector所有properties的id
drmIoctl(fd, DRM_IOCTL_MODE_GETPROPERTY, &prop); // 传入某个prop id,返回value

创建 dumb buffer 以及 add framebuffer, 最后 mmap framebuffer:

drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq);
drmModeAddFB2(fd, buf->width, buf->height, DRM_FORMAT_XRGB8888,
			    handles, pitches, offsets, &buf->fb, 0);
DRM_IOCTL(fd, DRM_IOCTL_MODE_ADDFB2, &f)

buf->map = mmap(0, buf->size, PROT_READ | PROT_WRITE, MAP_SHARED,
		fd, mreq.offset);

Modeset draw

绘制好需要下一帧显示的 framebuffer:

modeset_paint_framebuffer(iter);
//...
*(uint32_t*)&buf->map[off] =
		(out->r << 16) | (out->g << 8) | out->b;

修改好各种 properties 后进行 atomic commit:

drmModeAtomicAlloc();
set_drm_object_property(req, &out->connector, "CRTC_ID", out->crtc.id);
set_drm_object_property(req, plane, "SRC_X", 0);
//...
flags = DRM_MODE_ATOMIC_ALLOW_MODESET | DRM_MODE_PAGE_FLIP_EVENT;
drmModeAtomicCommit(fd, req, flags, NULL);

接着在 main 中 polling drm event 事件(通过 read drm fd),等到 kernel 的 page flip done event 会进入 userspace 设置的回调函数中:

drmEventContext ev;
ev.version = 3;
ev.page_flip_handler2 = modeset_page_flip_event; // kernel返回flip完成event后,会进入该回调

drmHandleEvent(fd, &ev);

在 modeset_page_flip_event 函数中,继续绘制下一帧,double buffer 中的另一块 framebuffer 接着再 atomic commit:

modeset_page_flip_event();
	modeset_draw_out();

modeset_paint_framebuffer(out);
drmModeAtomicAlloc();
modeset_atomic_prepare_commit(fd, out, req);
flags = DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_NONBLOCK;
drmModeAtomicCommit(fd, req, flags, NULL);