DRM,全称Direct Rending Manger。是目前Linux主流的图形显示框架。相比较传统的Framebuffer,DRM更能适应现代硬件。支持GPU、3D渲染显示等。DRM可以统一管理GPU、Display驱动,使得软件架构更统一、方便开发和维护。本文只介绍Display相关内容,GPU相关的,博主也不懂,无能为力,等以后学到相关的再来更新。
从模块上划分,DRM可以分为三个部分:libdrm、KMS、GEM。l

图1 DRM框架
1. lbdrm是DRM框架提供的、位于用户空间、操作DRM的库。应用程序调用内核里面的KMS和GEM,访问显示相关的资源。
2. KMS(Kernel Mode Setting)
KMS是DRM框架的一个大模块,主要功能是:显示参数及显示控制。
3. GEM(Graphics Execution Manager)
GEM负责DRM下的内存管理和释放。
本文只涉及KMS和GEM相关部分。使用的开发板是stm32mp157(正点原子)进行测试。系统是ubuntu18.04。
如图1,可以看到KMS主要负责显示相关功能。在DRM中,将其进行抽象,包括:
Framebuffer、CRTC,ENCODER,CONNECTOR,PLANE,VBLANK,property。他们之间的关系如图2
图2 KMS之间模块之间的功能关系
单个图层的显示内容,唯一一个与硬件无关的基本元素。
从framebuffer中读取待显示的图像,并按照响应的格式输出给encoder。其承担的主要作用为:
1).配置适合显示器的分辨率,并输出响应的时序。
2).扫描framebuffer送到一个或多个显示器
3).更新framebuffer
概括下就是,对显示器进行扫描,产生时序信号的模块、负责帧切换、电源控制、色彩调整等等。
图层,实际输出的图像是多个图层叠加而成的,比如主图层、光标图层。其中有些图层由硬件加速模块生成,每个crtc至少一个plane。plane一共有三种,分别是:DRM_PLANE_TYPE_PRIMARY、DRM_PLANE_TYPE_OVERLAY、DRM_PLANE_TYPE_CURSOR。这是配置plane的三个枚举,标注主图层、覆盖图层、光标图层(自己翻译的,跟标准翻译可能有出入)。
将一定格式的图像信号(如RGB、YUV等)编码成connector需要的输出信号。以HDMI为例,数据都是通过TMDS data的串行总线输出,编码的任务就是encoder的任务。
连接显示器的物理接口,负责硬件设备的接入、屏参获取等,如DP、HDMI等。
软、硬件同步机制,RGB时序中垂直消影区,软件通常使用硬件VSYNC实现。
任何想设置的参数都可以做成property,是DRM驱动中最灵活的部分
以HDMI接口为例说明:Soc内部一般包含一个Display模块,通过总线连接到HDMI接口上。则Display模块对应CRTC、HDMI接口对应Connector,Framebuffer对应的是显存部分。Plane是对Framebuffer进行描述的部分。Encoder是将像素转化为HDMI接口所需要的信号。一般Encoder和Connector放到一块初始化。
主要负责显示buffer的分配和释放,包括dumb、prime、fence
只支持连续物理内存,基于kernel中通用CMA API实现,多用于小分辨率简单场景。主要负责一些简单的buffer显示,可以直接使用CPU渲染,GPU不会使用dumb。
连续、非连续物理内存都支持,基于DMA-BUF机制,可以实现buffer共享,多用于大内存复杂场景。
buffer同步机制,基于内核dma_fence机制实现,用于防止显示内容出现异步问题。
st公司已经写好了DRM框架代码,位于路径:drivers\gpu\drm\stm。本次进行测试的时候,将该目录下的代码删除,参考厂家的代码重新写。设备树部分,修改compatible等代码,适合本次测试代码,仅作为学习使用。
struct drm_driver是DRM框架的核心结构体。

图3 struct drm_driver结构体
如图3,driver_features描述的是DRM支持的相关操作。
1)、DRIVER_MODESET:表示支持modesetting 操作
2)、DRIVER_GEM:表示支持GEM 操作,用于操作对内存的分配、释放
3)、DRIVER_ATOMIC:支持 Atomic 操作,用于操作各种属性
dumb_create成员是创建dumb内存。本例中对其进行重写,其他的回调函数,使用cma api。
在probe函数中,申请struct drm_device *ddev=drm_dev_alloc(&drv_driver, dev)结构体,在里面传入struct drm_driver结构体、配置KMS、注册DRM。部分代码如下:
//配置KMS信息,
static int my_modeset_init(struct drm_device *ddev)
{
struct platform_device *pdev = to_platform_device(ddev->dev);
struct ltdc_device *ldev;
int ret;
DRM_DEBUG("%s\n", __func__);
ldev = devm_kzalloc(ddev->dev, sizeof(*ldev), GFP_KERNEL);
if (!ldev)
return -ENOMEM;
ddev->dev_private = (void *)ldev;
drm_mode_config_init(ddev); //初始化drm_device
/*
* 设置最大/小宽、高值
* 绑定framebuffer结构体,drm可以模拟fb
*/
ddev->mode_config.min_width = 0;
ddev->mode_config.min_height = 0;
ddev->mode_config.max_width = MY_MAX_FB_WIDTH;
ddev->mode_config.max_height = MY_MAX_FB_HEIGHT;
ddev->mode_config.funcs = &drv_mode_config_funcs; //设置framebuffer的回调函数结构体
ret = ltdc_load(ddev); //初始化ltdc接口,包括初始化connector和encoder一起初始化。
//connector初始化的时候会调用drm_panel结构体离的获取屏幕参数函数
if (ret)
goto err;
drm_mode_config_reset(ddev);
drm_kms_helper_poll_init(ddev);
platform_set_drvdata(pdev, ddev);
return 0;
err:
drm_mode_config_cleanup(ddev);
return ret;
}
//驱动的probe函数
static int my_drm_platform_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct drm_device *ddev;
int ret;
DRM_DEBUG("%s\n", __func__);
dma_set_coherent_mask(dev, DMA_BIT_MASK(32)); //设置DMA
ddev = drm_dev_alloc(&drv_driver, dev); //分配一个drm_device结构体
if (IS_ERR(ddev))
return PTR_ERR(ddev);
ret = my_modeset_init(ddev); //初始化KMS
if (ret)
goto err_put;
ret = drm_dev_register(ddev, 0); //注册drm
if (ret)
goto err_put;
drm_fbdev_generic_setup(ddev, 16);
return 0;
err_put:
drm_dev_put(ddev);
return ret;
}
ddev->mode_config.funcs = &drv_mode_config_funcs; //设置framebuffer的回调函数结构体
这个部分,是DRM用于模拟Framebuffer框架的代码,结构体如下:
static const struct drm_mode_config_funcs drv_mode_config_funcs = {
.fb_create = drm_gem_fb_create,
.atomic_check = drm_atomic_helper_check,
.atomic_commit = drm_atomic_helper_commit,
};
ret = ltdc_load(ddev);代码中,对KMS中的相关功能进行完善,包括connector、encoder、plane、crtc等。代码太多了,具体的可以看后面连接放的代码。这里简单的说下大致内容。
KMS中的基本元素(CRTC,ENCODER,CONNECTOR,PLANE,Framebuffer已经在GEM中实现)均需要在DRM驱动中实现,没有硬件对应时,需要模拟出来。在DRM中,每一个部分都是使用一个结构体进行描述,需要使用对应的函数进行初始化。
注:各个Soc厂家的DRM部分设计的很多都比(互)较(相)相(抄)似(袭)。DRM框架将它们共同的代码使用xxx_funcs描述、xxx_init进行初始化,不同的部分使用xxx_helper_funcs描述、
drm_xxx_helper_add()添加。
例如:
static int ltdc_plane_atomic_check(struct drm_plane *plane,
struct drm_plane_state *state)
{
struct drm_framebuffer *fb = state->fb;
struct drm_crtc_state *crtc_state;
struct drm_rect *src = &state->src;
struct drm_rect *dst = &state->dst;
DRM_DEBUG_DRIVER("\n");
if (!fb)
return 0;
/* convert src from 16:16 format */
src->x1 = state->src_x >> 16;
src->y1 = state->src_y >> 16;
src->x2 = (state->src_w >> 16) + src->x1 - 1;
src->y2 = (state->src_h >> 16) + src->y1 - 1;
dst->x1 = state->crtc_x;
dst->y1 = state->crtc_y;
dst->x2 = state->crtc_w + dst->x1 - 1;
dst->y2 = state->crtc_h + dst->y1 - 1;
DRM_DEBUG_DRIVER("plane:%d fb:%d (%dx%d)@(%d,%d) -> (%dx%d)@(%d,%d)\n",
plane->base.id, fb->base.id,
src->x2 - src->x1 + 1, src->y2 - src->y1 + 1,
src->x1, src->y1,
dst->x2 - dst->x1 + 1, dst->y2 - dst->y1 + 1,
dst->x1, dst->y1);
crtc_state = drm_atomic_get_existing_crtc_state(state->state,
state->crtc);
/* destination coordinates do not have to exceed display sizes */
if (crtc_state && (crtc_state->mode.hdisplay <= dst->x2 ||
crtc_state->mode.vdisplay <= dst->y2))
return -EINVAL;
/* source sizes do not have to exceed destination sizes */
if (dst->x2 - dst->x1 < src->x2 - src->x1 ||
dst->y2 - dst->y1 < src->y2 - src->y1)
return -EINVAL;
return 0;
}
static void ltdc_plane_atomic_update(struct drm_plane *plane,
struct drm_plane_state *oldstate)
{
struct ltdc_device *ldev = plane_to_ltdc(plane);
struct drm_plane_state *state = plane->state;
struct drm_rect *src = &state->src;
struct drm_rect *dst = &state->dst;
struct drm_framebuffer *fb = state->fb;
u32 lofs = plane->index * LAY_OFS;
u32 val, pitch_in_bytes, line_length, paddr, ahbp, avbp, bpcr;
enum ltdc_pix_fmt pf;
struct drm_rect dr;
if (!state->crtc || !fb) {
DRM_DEBUG_DRIVER("fb or crtc NULL");
return;
}
/* compute final coordinates of frame buffer */
dr.x1 = src->x1 + dst->x1;
dr.y1 = src->y1 + dst->y1;
dr.x2 = src->x2 + dst->x1;
dr.y2 = src->y2 + dst->y1;
bpcr = my_reg_read(ldev->regs, LTDC_BPCR);
ahbp = (bpcr & BPCR_AHBP) >> 16;
avbp = bpcr & BPCR_AVBP;
/* Configures the horizontal start and stop position */
val = ((dr.x2 + 1 + ahbp) << 16) + (dr.x1 + 1 + ahbp);
my_reg_update_bits(ldev->regs, LTDC_L1WHPCR + lofs,
LXWHPCR_WHSTPOS | LXWHPCR_WHSPPOS, val);
/* Configures the vertical start and stop position */
val = ((dr.y2 + 1 + avbp) << 16) + (dr.y1 + 1 + avbp);
my_reg_update_bits(ldev->regs, LTDC_L1WVPCR + lofs,
LXWVPCR_WVSTPOS | LXWVPCR_WVSPPOS, val);
/* Specifies the pixel format */
pf = to_ltdc_pixelformat(fb->format->format);
for (val = 0; val < NB_PF; val++)
if (ldev->caps.pix_fmt_hw[val] == pf)
break;
if (val == NB_PF) {
DRM_ERROR("Pixel format %.4s not supported\n",
(char *)&fb->format->format);
val = 0; /* set by default ARGB 32 bits */
}
my_reg_update_bits(ldev->regs, LTDC_L1PFCR + lofs, LXPFCR_PF, val);
/* Configures the color frame buffer pitch in bytes & line length */
pitch_in_bytes = fb->pitches[0];
line_length = fb->format->cpp[0] * (dr.x2 - dr.x1 + 1) +
(ldev->caps.bus_width >> 3) - 1;
val = ((pitch_in_bytes << 16) | line_length);
my_reg_update_bits(ldev->regs, LTDC_L1CFBLR + lofs,
LXCFBLR_CFBLL | LXCFBLR_CFBP, val);
/* Specifies the constant alpha value */
val = CONSTA_MAX;
my_reg_update_bits(ldev->regs, LTDC_L1CACR + lofs, LXCACR_CONSTA, val);
/* Specifies the blending factors */
val = BF1_PAXCA | BF2_1PAXCA;
if (!fb->format->has_alpha)
val = BF1_CA | BF2_1CA;
/* Manage hw-specific capabilities */
if (ldev->caps.non_alpha_only_l1 &&
plane->type != DRM_PLANE_TYPE_PRIMARY)
val = BF1_PAXCA | BF2_1PAXCA;
my_reg_update_bits(ldev->regs, LTDC_L1BFCR + lofs,
LXBFCR_BF2 | LXBFCR_BF1, val);
/* Configures the frame buffer line number */
val = dr.y2 - dr.y1 + 1;
my_reg_update_bits(ldev->regs, LTDC_L1CFBLNR + lofs, LXCFBLNR_CFBLN, val);
/* Sets the FB address */
paddr = (u32)drm_fb_cma_get_gem_addr(fb, state, 0);
DRM_DEBUG_DRIVER("fb: phys 0x%08x", paddr);
my_reg_write(ldev->regs, LTDC_L1CFBAR + lofs, paddr);
/* Enable layer and CLUT if needed */
val = fb->format->format == DRM_FORMAT_C8 ? LXCR_CLUTEN : 0;
val |= LXCR_LEN;
my_reg_update_bits(ldev->regs, LTDC_L1CR + lofs,
LXCR_LEN | LXCR_CLUTEN, val);
ldev->plane_fpsi[plane->index].counter++;
mutex_lock(&ldev->err_lock);
if (ldev->error_status & ISR_FUIF) {
DRM_WARN("ltdc fifo underrun: please verify display mode\n");
ldev->error_status &= ~ISR_FUIF;
}
if (ldev->error_status & ISR_TERRIF) {
DRM_WARN("ltdc transfer error\n");
ldev->error_status &= ~ISR_TERRIF;
}
mutex_unlock(&ldev->err_lock);
}
static void ltdc_plane_atomic_disable(struct drm_plane *plane,
struct drm_plane_state *oldstate)
{
struct ltdc_device *ldev = plane_to_ltdc(plane);
u32 lofs = plane->index * LAY_OFS;
/* disable layer */
my_reg_clear(ldev->regs, LTDC_L1CR + lofs, LXCR_LEN);
DRM_DEBUG_DRIVER("CRTC:%d plane:%d\n",
oldstate->crtc->base.id, plane->base.id);
}
static void ltdc_plane_atomic_print_state(struct drm_printer *p,
const struct drm_plane_state *state)
{
struct drm_plane *plane = state->plane;
struct ltdc_device *ldev = plane_to_ltdc(plane);
struct fps_info *fpsi = &ldev->plane_fpsi[plane->index];
int ms_since_last;
ktime_t now;
now = ktime_get();
ms_since_last = ktime_to_ms(ktime_sub(now, fpsi->last_timestamp));
drm_printf(p, "\tuser_updates=%dfps\n",
DIV_ROUND_CLOSEST(fpsi->counter * 1000, ms_since_last));
fpsi->last_timestamp = now;
fpsi->counter = 0;
}
static bool ltdc_plane_format_mod_supported(struct drm_plane *plane,
u32 format,
u64 modifier)
{
if (modifier == DRM_FORMAT_MOD_LINEAR)
return true;
return false;
}
static const struct drm_plane_funcs ltdc_plane_funcs = {
.update_plane = drm_atomic_helper_update_plane,
.disable_plane = drm_atomic_helper_disable_plane,
.destroy = drm_plane_cleanup,
.reset = drm_atomic_helper_plane_reset,
.atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
.atomic_print_state = ltdc_plane_atomic_print_state,
.format_mod_supported = ltdc_plane_format_mod_supported,
};
static const struct drm_plane_helper_funcs ltdc_plane_helper_funcs = {
.prepare_fb = drm_gem_fb_prepare_fb,
.atomic_check = ltdc_plane_atomic_check,
.atomic_update = ltdc_plane_atomic_update,
.atomic_disable = ltdc_plane_atomic_disable,
};
//创建图层
static struct drm_plane *ltdc_plane_create(struct drm_device *ddev,
enum drm_plane_type type)
{
unsigned long possible_crtcs = CRTC_MASK;
struct ltdc_device *ldev = ddev->dev_private;
struct device *dev = ddev->dev;
struct drm_plane *plane;
unsigned int i, nb_fmt = 0;
u32 formats[NB_PF * 2];
u32 drm_fmt, drm_fmt_no_alpha;
const u64 *modifiers = ltdc_format_modifiers;
int ret;
/* Get supported pixel formats NB_PF个*/
for (i = 0; i < NB_PF; i++) { //添加支持的图层格式
drm_fmt = to_drm_pixelformat(ldev->caps.pix_fmt_hw[i]);
if (!drm_fmt)
continue;
formats[nb_fmt++] = drm_fmt;
/* Add the no-alpha related format if any & supported */
drm_fmt_no_alpha = get_pixelformat_without_alpha(drm_fmt);
if (!drm_fmt_no_alpha)
continue;
/* Manage hw-specific capabilities */
if (ldev->caps.non_alpha_only_l1 &&
type != DRM_PLANE_TYPE_PRIMARY)
continue;
formats[nb_fmt++] = drm_fmt_no_alpha;
}
ltdc_plane_create函数用于创建plane相关部分,
drm_universal_plane_init:初始化plane结构体
drm_plane_helper_add:添加helper函数
将代码放到drivers\gpu\drm\stm目录下进行覆盖(省心),重新编译内核(经过测试,修改设备树和stm目录下的代码之后,屏幕在uboot启动阶段能正常使用,在Image启动阶段无法使用,说明修改之后,内核中的DRM框架已经无法正常工作),下载Image和设备树。重新启动之后,可以在内核启动中看到这种输出字样。
[ 1.358037] panel-simple panel-rgb: panel-rgb supply power not found, using dummy regulator
有这个部分说明DRM能正常使用。
/dev/dri下是DRM生成的节点,也可以看到/dev/fb0,这个节点是DRM框架兼容FB框架产生的节点。
modetest是libdrm库编译出来产生的一个DRM测试程序,输出结果如图,可以看到KSM相关的输出。libdrm使用版本为libdrm-2.4.109。
root@ATK-MP157:~# modetest
trying to open device 'i915'...failed
trying to open device 'amdgpu'...failed
trying to open device 'radeon'...failed
trying to open device 'nouveau'...failed
trying to open device 'vmwgfx'...failed
trying to open device 'omapdrm'...failed
trying to open device 'exynos'...failed
trying to open device 'tilcdc'...failed
trying to open device 'msm'...failed
trying to open device 'sti'...failed
trying to open device 'tegra'...failed
trying to open device 'imx-drm'...failed
trying to open device 'rockchip'...failed
trying to open device 'atmel-hlcdc'...failed
trying to open device 'fsl-dcu-drm'...failed
trying to open device 'vc4'...failed
trying to open device 'virtio_gpu'...failed
trying to open device 'mediatek'...failed
trying to open device 'meson'...failed
trying to open device 'pl111'...failed
trying to open device 'stm'...done
Encoders:
id crtc type possible crtcs possible clones
31 35 DPI 0x00000001 0x00000000
Connectors:
id encoder status name size (mm) modes encoders
32 31 connected DPI-1 0x0 1 31
modes:
index name refresh (Hz) hdisp hss hse htot vdisp vss vse vtot)
#0 1024x600 59.99 1024 1164 1184 1344 600 620 623 635 51200 flags: phsync, pvsync; type: preferred, driver
props:
1 EDID:
flags: immutable blob
blobs:
value:
2 DPMS:
flags: enum
enums: On=0 Standby=1 Suspend=2 Off=3
value: 0
5 link-status:
flags: enum
enums: Good=0 Bad=1
value: 0
6 non-desktop:
flags: immutable range
values: 0 1
value: 0
4 TILE:
flags: immutable blob
blobs:
value:
20 CRTC_ID:
flags: object
value: 35
CRTCs:
id fb pos size
35 38 (0,0) (1024x600)
#0 1024x600 59.99 1024 1164 1184 1344 600 620 623 635 51200 flags: phsync, pvsync; type: preferred, driver
props:
22 ACTIVE:
flags: range
values: 0 1
value: 1
23 MODE_ID:
flags: blob
blobs:
value:
00c8000000048c04a004400500005802
6c026f027b0200003c00000005000000
48000000313032347836303000000000
00000000000000000000000000000000
00000000
19 OUT_FENCE_PTR:
flags: range
values: 0 18446744073709551615
value: 0
24 VRR_ENABLED:
flags: range
values: 0 1
value: 0
28 GAMMA_LUT:
flags: blob
blobs:
value:
29 GAMMA_LUT_SIZE:
flags: immutable range
values: 0 4294967295
value: 256
Planes:
id crtc fb CRTC x,y x,y gamma size possible crtcs
33 35 38 0,0 0,0 0 0x00000001
formats: AR24 XR24 RG24 RG16 AR15 XR15 AR12 XR12 C8
props:
8 type:
flags: immutable enum
enums: Overlay=0 Primary=1 Cursor=2
value: 1
17 FB_ID:
flags: object
value: 38
18 IN_FENCE_FD:
flags: signed range
values: -1 2147483647
value: -1
20 CRTC_ID:
flags: object
value: 35
13 CRTC_X:
flags: signed range
values: -2147483648 2147483647
value: 0
14 CRTC_Y:
flags: signed range
values: -2147483648 2147483647
value: 0
15 CRTC_W:
flags: range
values: 0 2147483647
value: 1024
16 CRTC_H:
flags: range
values: 0 2147483647
value: 600
9 SRC_X:
flags: range
values: 0 4294967295
value: 0
10 SRC_Y:
flags: range
values: 0 4294967295
value: 0
11 SRC_W:
flags: range
values: 0 4294967295
value: 67108864
12 SRC_H:
flags: range
values: 0 4294967295
value: 39321600
30 IN_FORMATS:
flags: immutable blob
blobs:
value:
01000000000000000900000018000000
01000000400000004152323458523234
52473234524731364152313558523135
41523132585231324338202000000000
ff010000000000000000000000000000
0000000000000000
in_formats blob decoded:
AR24: LINEAR
XR24: LINEAR
RG24: LINEAR
RG16: LINEAR
AR15: LINEAR
XR15: LINEAR
AR12: LINEAR
XR12: LINEAR
C8 : LINEAR
36 0 0 0,0 0,0 0 0x00000001
formats: AR24 RG24 RG16 AR15 AR12 C8
props:
8 type:
flags: immutable enum
enums: Overlay=0 Primary=1 Cursor=2
value: 0
17 FB_ID:
flags: object
value: 0
18 IN_FENCE_FD:
flags: signed range
values: -1 2147483647
value: -1
20 CRTC_ID:
flags: object
value: 0
13 CRTC_X:
flags: signed range
values: -2147483648 2147483647
value: 0
14 CRTC_Y:
flags: signed range
values: -2147483648 2147483647
value: 0
15 CRTC_W:
flags: range
values: 0 2147483647
value: 0
16 CRTC_H:
flags: range
values: 0 2147483647
value: 0
9 SRC_X:
flags: range
values: 0 4294967295
value: 0
10 SRC_Y:
flags: range
values: 0 4294967295
value: 0
11 SRC_W:
flags: range
values: 0 4294967295
value: 0
12 SRC_H:
flags: range
values: 0 4294967295
value: 0
30 IN_FORMATS:
flags: immutable blob
blobs:
value:
01000000000000000600000018000000
01000000300000004152323452473234
52473136415231354152313243382020
3f000000000000000000000000000000
0000000000000000
in_formats blob decoded:
AR24: LINEAR
RG24: LINEAR
RG16: LINEAR
AR15: LINEAR
AR12: LINEAR
C8 : LINEAR
Frame buffers:
id size pitch
输入modetest -M stm -s 32@35:1024x600进行测试,可以看到输出为

其中 32为 Connectors ID、35为CRTCs ID,分辨率为1024×600。屏幕显示如图

博主自己参考libdrm库里面的例程写了一个简单的测试代码,编译之后显示如图。测试代码和DRM驱动一起放到百度云上面供学习参考。

声明:代码仅供学习参考,使用中出现的任何情况均与本人无关。
要实现一个 DRM KMS 驱动,通常需要实现如下代码:
fops、drm_driver
dumb_create、fb_create、atomic_commit
drm_xxx_funcs、drm_xxx_helper_funcs
drm_xxx_init()、drm_xxx_helper_add()
drm_dev_init()、drm_dev_register()
核心是7个 objects,一切都围绕着这几个 objects 展开:
为了创建 crtc/plane/encoder/connector objects,需要调用 drm_xxx_init()。
为了创建 framebuffer object,需要实现 fb_create() callback。
为了创建 gem object,需要实现 dumb_create() callback。
为了创建 property objects,需要调用 drm_mode_config_init()。
为了让这些 objects 动起来,需要实现各种 funcs 和 helper funcs。
为了支持 atomic 操作,需要实现 atomic_commit() callback。
DRM框架是Linux内核中一个比较复杂的框架,本文只是介绍其中的一部分,并未完全介绍完,如DMA-Buf部分并未介绍,以后有时间再来更新。
百度云链接:
链接:https://pan.baidu.com/s/1zuqqy_nryNTD6YqLjazJ1w 提取码:6hgv
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
这里是Ruby新手。完成一些练习后碰壁了。练习:计算一系列成绩的字母等级创建一个方法get_grade来接受测试分数数组。数组中的每个分数应介于0和100之间,其中100是最大分数。计算平均分并将字母等级作为字符串返回,即“A”、“B”、“C”、“D”、“E”或“F”。我一直返回错误:avg.rb:1:syntaxerror,unexpectedtLBRACK,expecting')'defget_grade([100,90,80])^avg.rb:1:syntaxerror,unexpected')',expecting$end这是我目前所拥有的。我想坚持使用下面的方法或.join,
按照目前的情况,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visitthehelpcenter指导。关闭10年前。问题1)我想知道rubyonrails是否有功能类似于primefaces的gem。我问的原因是如果您使用primefaces(http://www.primefaces.org/showcase-labs/ui/home.jsf),开发人员无需担心javascript或jquery的东西。据我所知,JSF是一个规范,基于规范的各种可用实现,prim
在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList()Obt
本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01 客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02 数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit
目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称
最近在学习CAN,记录一下,也供大家参考交流。推荐几个我觉得很好的CAN学习,本文也是在看了他们的好文之后做的笔记首先是瑞萨的CAN入门,真的通透;秀!靠这篇我竟然2天理解了CAN协议!实战STM32F4CAN!原文链接:https://blog.csdn.net/XiaoXiaoPengBo/article/details/116206252CAN详解(小白教程)原文链接:https://blog.csdn.net/xwwwj/article/details/105372234一篇易懂的CAN通讯协议指南1一篇易懂的CAN通讯协议指南1-知乎(zhihu.com)视频推荐CAN总线个人知识总
深度学习部署:Windows安装pycocotools报错解决方法1.pycocotools库的简介2.pycocotools安装的坑3.解决办法更多Ai资讯:公主号AiCharm本系列是作者在跑一些深度学习实例时,遇到的各种各样的问题及解决办法,希望能够帮助到大家。ERROR:Commanderroredoutwithexitstatus1:'D:\Anaconda3\python.exe'-u-c'importsys,setuptools,tokenize;sys.argv[0]='"'"'C:\\Users\\46653\\AppData\\Local\\Temp\\pip-instal
基础版云数据库RDS的产品系列包括基础版、高可用版、集群版、三节点企业版,本文介绍基础版实例的相关信息。RDS基础版实例也称为单机版实例,只有单个数据库节点,计算与存储分离,性价比超高。说明RDS基础版实例只有一个数据库节点,没有备节点作为热备份,因此当该节点意外宕机或者执行重启实例、变更配置、版本升级等任务时,会出现较长时间的不可用。如果业务对数据库的可用性要求较高,不建议使用基础版实例,可选择其他系列(如高可用版),部分基础版实例也支持升级为高可用版。基础版与高可用版的对比拓扑图如下所示。优势 性能由于不提供备节点,主节点不会因为实时的数据库复制而产生额外的性能开销,因此基础版的性能相对于
我完全不是程序员,正在学习使用Ruby和Rails框架进行编程。我目前正在使用Ruby1.8.7和Rails3.0.3,但我想知道我是否应该升级到Ruby1.9,因为我真的没有任何升级的“遗留”成本。缺点是什么?我是否会遇到与普通gem的兼容性问题,或者甚至其他我不太了解甚至无法预料的问题? 最佳答案 你应该升级。不要坚持从1.8.7开始。如果您发现不支持1.9.2的gem,请避免使用它们(因为它们很可能不被维护)。如果您对gem是否兼容1.9.2有任何疑问,您可以在以下位置查看:http://www.railsplugins.or