LINUX DRM 入门一条龙

#使用nanopiM3(s5p6818), LINUX 内核4.4.172. 使用HDMI接口显示图像.

#源码:usb2hdmi: 使用usb传输FB数据,hdmi接口输出.

#情景分析法, 使用modetest命令作为应用层的测试例程:modetest -M nexell -s 41@30:1280x720
#但我并不想阅读modetest的源码,这里使用大体相同的另一个libdrm例程.
 
#以下为仅保留框架的libdrm例程, 下面就基于此例程进行驱动代码分析.

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

    ioctl(dri_fd, DRM_IOCTL_SET_MASTER, 0);
 
    res.fb_id_ptr=(uint64_t)res_fb_buf;
    res.crtc_id_ptr=(uint64_t)res_crtc_buf;
    res.connector_id_ptr=(uint64_t)res_conn_buf;
    res.encoder_id_ptr=(uint64_t)res_enc_buf;
    ioctl(dri_fd, DRM_IOCTL_MODE_GETRESOURCES, &res);
 
    conn.connector_id=res_conn_buf[i];
    conn.modes_ptr=(uint64_t)conn_mode_buf;
    ioctl(dri_fd, DRM_IOCTL_MODE_GETCONNECTOR, &conn);

    create_dumb.width = conn_mode_buf[0].hdisplay;
    create_dumb.height = conn_mode_buf[0].vdisplay;
    create_dumb.bpp = 32;
    ioctl(dri_fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_dumb);

    ioctl(dri_fd,DRM_IOCTL_MODE_ADDFB,&cmd_dumb);

    map_dumb.handle=create_dumb.handle;
    ioctl(dri_fd,DRM_IOCTL_MODE_MAP_DUMB,&map_dumb);

    fb_base[i] = mmap(0, create_dumb.size, PROT_READ | PROT_WRITE, MAP_SHARED, dri_fd, map_dumb.offset);
    fb_w[i]=create_dumb.width;
    fb_h[i]=create_dumb.height;

    enc.encoder_id=conn.encoder_id;
    ioctl(dri_fd, DRM_IOCTL_MODE_GETENCODER, &enc);

    crtc.crtc_id=enc.crtc_id;
    ioctl(dri_fd, DRM_IOCTL_MODE_GETCRTC, &crtc);

    crtc.fb_id=cmd_dumb.fb_id;
    crtc.set_connectors_ptr=(uint64_t)&res_conn_buf[i];
    crtc.mode=conn_mode_buf[0];
    ioctl(dri_fd, DRM_IOCTL_MODE_SETCRTC, &crtc);
 
    ioctl(dri_fd, DRM_IOCTL_DROP_MASTER, 0);
 
    /* 写FB */
    int x,y;
    for (i=0;i<100;i++)
    {
        int j;
        for (j=0;j

#1
int dri_fd  = open("/dev/dri/card0",O_RDWR | O_CLOEXEC);
#应用层open() => DRM  drm_stub_open() =>  nx_drm_driver->nx_drm_fops->drm_open
#此步骤只是初始化一些链表而已.

#2
ioctl(dri_fd, DRM_IOCTL_MODE_GETRESOURCES, &res);
res.connector_id_ptr=(uint64_t)res_conn_buf;
#对应 drm_mode_getresources()
#遍历drm->mode_config.connector_list和drm->mode_config.crtc_list链表,
#找到所有的connector和crtc的ID,返回给应用程序.

#3    
#conn 为输入参数, 传给ioctl
conn.connector_id=res_conn_buf[i];
#这里保存#2步骤获得的connector_id, 即确定最终要显示图像的显示器(屏幕).
conn.modes_ptr=(uint64_t)conn_mode_buf;
ioctl(dri_fd, DRM_IOCTL_MODE_GETCONNECTOR, &conn);
#此ioctl步骤, 最终调用drm_mode_getresources(), 获取该显示器所支持的显示模式,
#即是显示分辨率,刷新率等信息.
#驱动将根据传入的connector_id, 确定connector.
#当前情景是使用HDMI接口进行显示,驱动使用i2c总线读取显示器的EDID,并进行解析,见panel_hdmi_ops_get_modes().
#将得到的个个显示模式, 加入connector->modes队列. 见drm_mode_connector_list_update().

#3    
create_dumb.width = conn_mode_buf[0].hdisplay;
create_dumb.height = conn_mode_buf[0].vdisplay;
create_dumb.bpp = 32;
ioctl(dri_fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_dumb);
#此ioctl最终调用 drm_mode_create_dumb_ioctl()
#应用层输入必须的参数: 长,宽,每个像素大小(bpp), 长x宽xBPP为期望申请的FB的大小.
#再进行一些对齐工作,即为最终要申请的FB的大小. 见nx_drm_gem_dma_alloc().

#4
cmd_dumb.handle=create_dumb.handle;
ioctl(dri_fd,DRM_IOCTL_MODE_ADDFB,&cmd_dumb);
#对应drm_mode_addfb2()
#通过handle索引到对于的fb, 将此fb加入drm->mode_config.fb_list链表.

# 将此fb->base加入 drm->mode_config.crtc_idr 链表..., 并返回对应的ID号, 将此ID号返回应用程序.
# 见drm_framebuffer_init()

#5
ioctl(dri_fd,DRM_IOCTL_MODE_MAP_DUMB,&map_dumb);
#对应drm_mode_mmap_dumb_ioctl()
#与mmap相关.

#6
fb_base[i] = mmap(0, create_dumb.size, PROT_READ | PROT_WRITE, MAP_SHARED, dri_fd, map_dumb.offset);
#对应nx_drm_gem_fops_mmap(). 内存映射, 使得应用程序可以直接写FB, 无需任何write()和ioctrl().

#7
enc.encoder_id=conn.encoder_id;
ioctl(dri_fd, DRM_IOCTL_MODE_GETENCODER, &enc);
#根据connector所连接的encoder的ID,得到对应encoder的结构体.
crtc.crtc_id=enc.crtc_id;
ioctl(dri_fd, DRM_IOCTL_MODE_GETCRTC, &crtc);
#根据encoder结构体内的ctrc_id信息,获得对应的CRTC结构体.

#8
crtc.fb_id=cmd_dumb.fb_id;
crtc.set_connectors_ptr=(uint64_t)&res_conn_buf[i];
crtc.mode=conn_mode_buf[0];
ioctl(dri_fd, drm_ioctl_mode_setcrtc, &crtc);
#对应drm_mode_setcrtc()
#应用程序传入必须参数:FB_ID, connector_id(包含于set_connectors_ptr), 欲设置的显示模式(conn_mode_buf)
#1. 使用crtc_id 遍历drm->mode_config.crtc_list 找到对应的crtc.
#    使用connector_id 遍历 drm->mode_config.connector_list 找到对应 connector.
#   使用fb_id 遍历 drm->mode_config.crtc_idr 找到对应的 FB.
#2. 使用crtc,connector 找到对应的硬件寄存器, 使用conn_mode_buf的参数设置寄存器.
#3. 将FB的物理地址写入硬件寄存器, 以此确定要显示的内容. nx_mlc_set_rgblayer_address()
#4. 正常地进行剩余的寄存器设置,这些基本是固定设置.无需使用使用应用程序的传来的参数.

#总体概括:


#总的而已, 在set_crtc之前的所以操作, 是在收集crtc,encoder,connector.
#先确定connector_id, 根据connector_id找到对应的encoder_id, 根据encoder_id找到对应的crtc_id.
#这样,完整的图像信号传输路径就完成了.
#根据这些id找到对应的结构体.
#借助crtc,encoder结构体,可以找到对应的硬件的寄存器地址(这些地址读取自dts).
#而根据connector结构体, 可以获得connector支持的显示模式,
#应用程序便可以选择其中一种显示模式, 创建对应大小的FB, 并传给set_crtc(), 确定要显示的内容.
#补充:
#当前例程仅使用到一个plane,即一个FB. 当要使用第二个plane时, 需要使用到DRM框架的 drm_mode_setplane()函数.
#形成FB => plane => crtc的关系.

#DRM术语说明


#PLANE
#图层.可以多图层,分辨率不同,位置不同,相互叠加显示于屏幕上.
#譬如显示linux桌面, 会使用两个plane, 一个用于显示鼠标, 一个用于显示其他东西,桌面,图标,应用..
#当前情景仅使用一个plane.
#FB
#一个plane对应于一个fb.
#GEM
#分配FB.

#CONNECTOR
#屏幕. 譬如, HDMI屏幕, lvds屏幕. 即各种接口的屏幕.
#ENCODER
#图像信号转换器. 连接各种屏幕(connector), 将其他图像信号转为对应屏幕的信号.

#CRTC
#目前我认为crtc 在DRM框架上有以下两个作用:
#1. 保存显示模式的参数.分辨率,刷新率等.
#2. 作为图像信号传输的起点, 而connector为终点. 因为只是作为起点,可以对应于任何在图像信号传输路径
#   上处于起点的硬件.
#   图像信号传输路径上, 可以有多个crtc, encoder 和 connector.
#   譬如我使用的nanopiM3(s5p6818), 其硬件上的图像信号传输路径为一下:

    mlc(multi layer controler0) -> dpc(displayer controler0)--|
                                                              |-->hdmi encoder -> "hdmi 屏幕"
                                                              |-->lvds encoder -> "lvds 屏幕"
    mlc(multi layer controler1) -> dpc(displayer controler1)--|

#其中 mlc和dpc对应 crtc, 则这里会有两个crtc.
#mlc 是多图层控制器, 6818的mlc提供3个图层(plane)的支持, 图层按需开启和使用.
#mlc将图层混合后,传给dpc(显示控制器).

#关于DRM框架模块 与硬件的对应关系,这里举s3c2440为例子进行说明


#例子: s3c2440 仅拥有一个显示控制器 和一块RGB的屏幕.
        FB0,FB1 => plane => crtc("2440显示控制器") => encoder ("空")  => connector("RGB屏幕")
#2440 没有mlc多图层控制器, 所以只支持一个plane, 可能多图层混合的工作由软件完成吧?
#2440 没有encoder, 2440显示控制器 输出的是RGB信号, 只能接入RGB屏幕. 若添加上RGB转HDMI的HDMI_encoder,
#就可以接入HDMI屏幕了. 所以这里的encoder可以为空.

#DRM 框架示意
 

             FB1 -> PLANE1 ->
GEM -> FB0 -> PLANE0("图层") -> CRTC("多图层混合","显示控制,包含显示参数") -> ENCODER -> CONNECTOR
      

###########################################
#DRM驱动的初始化流程分析


#1. drm_core_init()
#   申请DRM_MAJOR(226) 为主设备号.

#2. panel_hdmi_probe()
#   是HDMI encoder设备的初始化流程.
#panel_hdmi_get_display()  获取dts上的HDMI的寄存器地址.
#hdmi_ops_open()  读状态寄存器, 开启HDMI电缆插入和拔出中断
#panel_hdmi_parse_dt() 获得与该HDMI encoder配合的i2c 控制器,将在以后用于读取EDID.

#3. nx_drm_bind()
# 是crtc(mlc, dpc)设备的初始化流程
# 创建  struct drm_device *drm_dev; 结构体, 此结构体相当于 一般设备驱动中的 struct device *dev...
# 初始化 drm_dev->mode_config 上的队列.
#  nx_drm_crtc_create() 创建crtc 结构体, 解析dts,获得该设备的寄存器地址.
# crtc结构体 加入drm_dev->mode_config->crtc_list.
# drm_minor_alloc() 获得次设备号, 设置设备类型为 legacy, 设备节点名为 "cardx"
# drm_minor_register() 注册字符设备.

#4 nx_drm_connector_attach()
# 创建    struct drm_connector *connector; 结构体, 加入drm_dev->mode_config->connector_list 链表.
# 创建    struct drm_encoder *encoder; 结构体, 加入drm_dev->mode_config->encoder_list 链表.
#  读HDMI状态寄存器, 判断HDMI电缆是否插入. hdmi_ops_is_connected()

# 详细的各个结构体的作用,及其包含的信息, 见drm_read.c · suiren/usb2hdmi - Gitee.com

你可能感兴趣的:(驱动开发,linux)