DRM 从app到libdrm,到/dev/dri/card0

`libdrm` 是用户空间与 Linux 内核 DRM(Direct Rendering Manager)子系统之间的桥梁。它提供了一组 API,允许用户空间程序(如显示服务器、图形库)与内核 DRM 驱动进行交互,从而管理图形硬件资源(如显存、显示模式、渲染等)。

以下是 `libdrm` 和内核 DRM 驱动对接的详细说明:

## 1. **DRM 子系统概述**
DRM 是 Linux 内核中用于管理图形硬件的子系统,主要负责:
- 显存管理(如分配、释放)。
- 显示模式设置(如分辨率、刷新率)。
- 渲染和合成(如 2D/3D 加速)。
- 电源管理(如 GPU 频率调节)。

`libdrm` 是用户空间与 DRM 子系统交互的库,封装了与 DRM 驱动的通信细节。

## 2. **libdrm 和内核 DRM 驱动的对接流程**

### 2.1 **打开 DRM 设备**
用户空间程序通过 `libdrm` 打开 DRM 设备文件(如 `/dev/dri/card0`),获取文件描述符。#### 示例代码

#include <xf86drm.h>

int fd = drmOpen("card0", NULL);
if (fd < 0) {
    fprintf(stderr, "Failed to open DRM device\n");
    return -1;
}

- `drmOpen` 是 `libdrm` 提供的函数,用于打开 DRM 设备。
- 返回的文件描述符 `fd` 用于后续的 DRM API 调用。

### 2.2 **获取 DRM 资源**
通过 `libdrm` 获取 DRM 设备的资源信息,如 CRTC、Connector、Encoder 和 Framebuffer。#### 示例代码

root@debian11:/home/rpdzkj/pcie_bt1120/build# sudo ./app --card=/dev/dri/card0 --w=1920 --h=1080 --fps=50 --addr=0xB1000000 --src-fmt=RGB888 --src-stride=5760 找不到可用的 connector/CRTC(请确认显示子系统/VOP2 已上线) #include <cstdio> #include <cstdlib> #include <cstdint> #include <cstring> #include <csignal> #include <string> #include <vector> #include <poll.h> #include <fcntl.h> #include <unistd.h> #include <sys/ioctl.h> #include <sys/mman.h> #include <errno.h> #include <cmath> // std::abs #include <climits> // INT_MAX // libdrm #include <drm.h> #include <drm_mode.h> #include <xf86drm.h> #include <xf86drmMode.h> #include <drm_fourcc.h> // =========== 选择 VOP 帧缓冲格式 =========== // 大多数 Rockchip VOP2 支持 UYVY;如遇到 plane 不支持,可以切到 XRGB8888 #ifndef FB_USE_UYVY #define FB_USE_UYVY 1 #endif #if FB_USE_UYVY #define FB_FMT DRM_FORMAT_UYVY #define FB_BPP 16 #define FB_BYTES_PER_PIXEL 2 #else #define FB_FMT DRM_FORMAT_XRGB8888 #define FB_BPP 32 #define FB_BYTES_PER_PIXEL 4 #endif // XDMA C 接口 extern "C" { int pcie_init(void); void pcie_deinit(void); void c2h_transfer(unsigned int addr, unsigned int len, unsigned char *dst); } static volatile int running = 1; static void on_sig(int){ running = 0; } static bool sw(const char* s,const char* p){ return std::strncmp(s,p,std::strlen(p))==0; } struct Args { std::string card = "/dev/dri/card0"; int w=1920, h=1080, fps=50; uint64_t xdma_addr = 0xB1000000ULL; std::string src_fmt = "UYVY"; // UYVY | RGB888 int src_stride = 0; // 0 => 自动 w*Bpp int conn_id = 0; // 手动指定连接器(可选) bool list=false; }; static Args parse(int ac,char**av){ Args a; for(int i=1;i<ac;i++){ if(!std::strcmp(av[i],"--list")) a.list=true; else if(sw(av[i],"--card=")) a.card=av[i]+7; else if(sw(av[i],"--w=")) a.w=std::atoi(av[i]+4); else if(sw(av[i],"--h=")) a.h=std::atoi(av[i]+4); else if(sw(av[i],"--fps=")) a.fps=std::atoi(av[i]+6); else if(sw(av[i],"--addr=")) a.xdma_addr=strtoull(av[i]+7,nullptr,0); else if(sw(av[i],"--src-fmt=")) a.src_fmt=av[i]+10; else if(sw(av[i],"--src-stride=")) a.src_stride=std::atoi(av[i]+13); else if(sw(av[i],"--conn-id=")) a.conn_id=std::atoi(av[i]+10); } if(a.fps<=0) a.fps=50; return a; } // clamp static inline uint8_t clamp_u8(int v){ if(v<0) return 0; if(v>255) return 255; return (uint8_t)v; } // 高效 RGB888 -> UYVY(2 像素一组,平均 U/V) static void rgb888_to_uyvy(const uint8_t* src, uint8_t* dst, int w, int h, int src_stride_bytes, int dst_pitch){ const int bpp = 3; for(int y=0;y<h;y++){ const uint8_t* sline = src + y*src_stride_bytes; uint8_t* dline = dst + y*dst_pitch; for(int x=0;x<w; x+=2){ const uint8_t *p0 = sline + x*bpp; const uint8_t *p1 = p0 + bpp; int r0=p0[0], g0=p0[1], b0=p0[2]; int r1=p1[0], g1=p1[1], b1=p1[2]; int Y0 = ( 77*r0 + 150*g0 + 29*b0 + 128) >> 8; // 0.299 0.587 0.114 int Y1 = ( 77*r1 + 150*g1 + 29*b1 + 128) >> 8; // BT.601 近似 int U0 = (-43*r0 - 85*g0 + 128*b0 + 32768) >> 8; int V0 = (128*r0 -107*g0 - 21*b0 + 32768) >> 8; int U1 = (-43*r1 - 85*g1 + 128*b1 + 32768) >> 8; int V1 = (128*r1 -107*g1 - 21*b1 + 32768) >> 8; int U = (U0 + U1) >> 1; int V = (V0 + V1) >> 1; dline[0] = clamp_u8(U); dline[1] = clamp_u8(Y0); dline[2] = clamp_u8(V); dline[3] = clamp_u8(Y1); dline += 4; } } } #if !FB_USE_UYVY // UYVY -> XRGB8888(如改用 XRGB8888) static void uyvy_to_xrgb8888(const uint8_t* src, uint8_t* dst, int w, int h, int src_stride, int dst_pitch){ for(int y=0;y<h;y++){ const uint8_t* s = src + y*src_stride; uint32_t* d = (uint32_t*)(dst + y*dst_pitch); for(int x=0;x<w; x+=2){ uint8_t U = s[0], Y0 = s[1], V = s[2], Y1 = s[3]; s += 4; auto YUV2RGB = [](int Y,int U,int V){ int C = Y - 16, D = U - 128, E = V - 128; int R = (298*C + 409*E + 128) >> 8; int G = (298*C - 100*D - 208*E + 128) >> 8; int B = (298*C + 516*D + 128) >> 8; return (0xFFu<<24) | ((uint8_t)clamp_u8(R)<<16) | ((uint8_t)clamp_u8(G)<<8) | ((uint8_t)clamp_u8(B)<<0); }; d[0] = YUV2RGB(Y0,U,V); d[1] = YUV2RGB(Y1,U,V); d += 2; } } } #endif struct DumbFB { uint32_t fb_id=0, handle=0, pitch=0; uint64_t size=0; uint8_t* map=nullptr; }; static int create_fb(int fd, int w, int h, DumbFB& out){ drm_mode_create_dumb creq{}; creq.width=w; creq.height=h; creq.bpp=FB_BPP; if(ioctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq)<0){ perror("CREATE_DUMB"); return -1; } uint32_t fb_id=0; uint32_t handles[4]={creq.handle,0,0,0}; uint32_t pitches[4]={creq.pitch,0,0,0}; // ★ 使用内核返回 pitch uint32_t offsets[4]={0,0,0,0}; if(drmModeAddFB2(fd, w, h, FB_FMT, handles, pitches, offsets, &fb_id, 0)!=0){ perror("drmModeAddFB2"); drm_mode_destroy_dumb d{}; d.handle=creq.handle; ioctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &d); return -2; } drm_mode_map_dumb mreq{}; mreq.handle=creq.handle; if(ioctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq)<0){ perror("MAP_DUMB"); drmModeRmFB(fd, fb_id); drm_mode_destroy_dumb d{}; d.handle=creq.handle; ioctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &d); return -3; } void* map = mmap(nullptr, creq.size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, mreq.offset); if(map==MAP_FAILED){ perror("mmap"); drmModeRmFB(fd, fb_id); drm_mode_destroy_dumb d{}; d.handle=creq.handle; ioctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &d); return -4; } out.fb_id=fb_id; out.handle=creq.handle; out.pitch=creq.pitch; out.size=creq.size; out.map=(uint8_t*)map; return 0; } static void destroy_fb(int fd, DumbFB& fb){ if(fb.map && fb.size) munmap(fb.map, fb.size); if(fb.fb_id) drmModeRmFB(fd, fb.fb_id); if(fb.handle){ drm_mode_destroy_dumb d{}; d.handle=fb.handle; ioctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &d); } fb = DumbFB{}; } static void list_res(int fd){ drmModeRes* r = drmModeGetResources(fd); if(!r){ printf("no resources\n"); return; } printf("CRTCs:"); for(int i=0;i<r->count_crtcs;i++) printf(" %d", r->crtcs[i]); printf("\n"); for(int i=0;i<r->count_connectors;i++){ auto*c = drmModeGetConnector(fd, r->connectors[i]); if(!c) continue; const char* st = (c->connection==DRM_MODE_CONNECTED)?"connected":(c->connection==DRM_MODE_DISCONNECTED)?"disconnected":"unknown"; printf("conn-id=%d type=%d status=%s modes=%d\n", c->connector_id, c->connector_type, st, c->count_modes); for(int m=0;m<c->count_modes;m++){ auto& mm=c->modes[m]; printf(" %s %dx%d@%d\n", mm.name, mm.hdisplay, mm.vdisplay, mm.vrefresh); } drmModeFreeConnector(c); } drmModeFreeResources(r); } // 选择连接器 & 匹配 CRTC(根据 encoder->possible_crtcs) static bool pick_conn_enc_crtc(int fd, int prefer_conn_id, uint32_t& conn_id, uint32_t& enc_id, uint32_t& crtc_id){ drmModeRes* res = drmModeGetResources(fd); if(!res) return false; // 选 connector:优先用户指定,其次已连接,再否则随便一个 drmModeConnector* chosen_conn = nullptr; for(int i=0;i<res->count_connectors;i++){ drmModeConnector* c = drmModeGetConnector(fd, res->connectors[i]); if(!c) continue; if(prefer_conn_id>0 && (int)c->connector_id==prefer_conn_id){ chosen_conn=c; break; } if(!chosen_conn || c->connection==DRM_MODE_CONNECTED) { if(chosen_conn) drmModeFreeConnector(chosen_conn); chosen_conn=c; if(c->connection==DRM_MODE_CONNECTED) break; } else drmModeFreeConnector(c); } if(!chosen_conn){ drmModeFreeResources(res); return false; } // 选 encoder drmModeEncoder* chosen_enc = nullptr; if(chosen_conn->encoder_id){ chosen_enc = drmModeGetEncoder(fd, chosen_conn->encoder_id); } else if (chosen_conn->count_encoders>0){ chosen_enc = drmModeGetEncoder(fd, chosen_conn->encoders[0]); } if(!chosen_enc){ drmModeFreeConnector(chosen_conn); drmModeFreeResources(res); return false; } // 选 crtc:从 possible_crtcs bitmask 中挑第一个 int crtc_index = -1; for(int i=0;i<res->count_crtcs;i++){ if(chosen_enc->possible_crtcs & (1<<i)){ crtc_index = i; break; } } if(crtc_index<0){ // 兜底:用第一个 crtc_index = 0; } conn_id = chosen_conn->connector_id; enc_id = chosen_enc->encoder_id; crtc_id = res->crtcs[crtc_index]; drmModeFreeEncoder(chosen_enc); drmModeFreeConnector(chosen_conn); drmModeFreeResources(res); return conn_id && crtc_id; } // 在连接器 modes 里找一个最合适的(优先首选,其次匹配分辨率,最后第一个) static bool choose_mode(int fd, uint32_t conn_id, int want_w, int want_h, int want_hz, drmModeModeInfo& out){ drmModeConnector* c = drmModeGetConnector(fd, conn_id); if(!c) return false; if(c->count_modes<=0){ drmModeFreeConnector(c); return false; } // 1) Preferred for(int i=0;i<c->count_modes;i++){ auto&m = c->modes[i]; if(m.type & DRM_MODE_TYPE_PREFERRED){ out = m; drmModeFreeConnector(c); return true; } } // 2) 分辨率匹配 && 刷新率最接近 int best = -1, best_delta = INT_MAX; for(int i=0;i<c->count_modes;i++){ auto&m = c->modes[i]; if(m.hdisplay==want_w && m.vdisplay==want_h){ int d = std::abs((int)m.vrefresh - (int)want_hz); // 显式转 int,避免重载歧义 if(d < best_delta){ best = i; best_delta = d; } } } if(best>=0){ out = c->modes[best]; drmModeFreeConnector(c); return true; } // 3) 退而求其次:第一个可用 out = c->modes[0]; drmModeFreeConnector(c); return true; } int main(int ac, char** av){ signal(SIGINT,on_sig); signal(SIGTERM,on_sig); Args args = parse(ac,av); int fd = open(args.card.c_str(), O_RDWR | O_CLOEXEC); if(fd<0){ perror("open card"); return 1; } if(args.list){ list_res(fd); close(fd); return 0; } uint32_t conn_id=0, enc_id=0, crtc_id=0; if(!pick_conn_enc_crtc(fd, args.conn_id, conn_id, enc_id, crtc_id)){ fprintf(stderr,"找不到可用的 connector/CRTC(请确认显示子系统/VOP2 已上线)\n"); close(fd); return 1; } drmModeModeInfo mode{}; if(!choose_mode(fd, conn_id, args.w, args.h, args.fps, mode)){ fprintf(stderr,"未获取到可用显示模式\n"); close(fd); return 1; } fprintf(stderr, "Use mode: %s %dx%d@%d\n", mode.name, mode.hdisplay, mode.vdisplay, mode.vrefresh); // 创建双缓冲 FB DumbFB fb[2]; if(create_fb(fd, mode.hdisplay, mode.vdisplay, fb[0]) || create_fb(fd, mode.hdisplay, mode.vdisplay, fb[1])){ fprintf(stderr,"create dumb fb failed\n"); destroy_fb(fd, fb[0]); destroy_fb(fd, fb[1]); close(fd); return 1; } // 绑定首帧到 CRTC(legacy 模式设置) if(drmModeSetCrtc(fd, crtc_id, fb[0].fb_id, 0,0, &conn_id, 1, &mode)!=0){ perror("drmModeSetCrtc"); destroy_fb(fd, fb[0]); destroy_fb(fd, fb[1]); close(fd); return 1; } // XDMA init if(pcie_init()<0){ fprintf(stderr,"pcie_init failed\n"); destroy_fb(fd, fb[0]); destroy_fb(fd, fb[1]); close(fd); return 1; } const bool src_is_rgb = (args.src_fmt=="RGB888"); const int src_bpp = src_is_rgb? 3 : 2; const int src_stride = (args.src_stride>0)? args.src_stride : (args.w*src_bpp); const size_t pull_bytes = (size_t)src_stride * args.h; std::vector<uint8_t> srcbuf(pull_bytes); const int interval_us = 1000000 / (args.fps?args.fps:50); // page-flip 事件 drmEventContext ev{}; ev.version = DRM_EVENT_CONTEXT_VERSION; auto handler = [](int, unsigned, unsigned, unsigned, void* u){ *(int*)u = 1; }; ev.page_flip_handler = handler; int front = 0; fprintf(stderr, "Start: %dx%d %s -> FB fmt=0x%08x fps=%d addr=0x%llx stride=%d\n", args.w, args.h, src_is_rgb?"RGB888":"UYVY", FB_FMT, args.fps, (unsigned long long)args.xdma_addr, src_stride); while(running){ // 从 FPGA 拉一帧 c2h_transfer((unsigned)args.xdma_addr, (unsigned)pull_bytes, srcbuf.data()); // 写到后备 FB uint8_t* dst = fb[front^1].map; #if FB_USE_UYVY if(src_is_rgb){ rgb888_to_uyvy(srcbuf.data(), dst, args.w, args.h, src_stride, fb[front^1].pitch); }else{ // UYVY -> UYVY:逐行拷贝,照顾 pitch 差异 int copy_bytes_per_line = args.w * 2; for(int y=0;y<args.h;y++){ std::memcpy(dst + y*fb[front^1].pitch, srcbuf.data()+ y*src_stride, copy_bytes_per_line); } } #else if(src_is_rgb){ // RGB888 -> XRGB8888(简单逐像素填充;更快可用 RGA/NEON) for(int y=0;y<args.h;y++){ const uint8_t* s = srcbuf.data() + y*src_stride; uint32_t* d = (uint32_t*)(dst + y*fb[front^1].pitch); for(int x=0;x<args.w;x++){ uint8_t r=s[0], g=s[1], b=s[2]; s+=3; d[x] = 0xFF000000u | (r<<16) | (g<<8) | (b<<0); } } }else{ // UYVY -> XRGB8888 uyvy_to_xrgb8888(srcbuf.data(), dst, args.w, args.h, src_stride, fb[front^1].pitch); } #endif // 提交翻转 int done=0; if(drmModePageFlip(fd, crtc_id, fb[front^1].fb_id, DRM_MODE_PAGE_FLIP_EVENT, &done)!=0){ // 某些驱动可能第一次不支持 flip,退化为直接 setCrtc perror("drmModePageFlip"); drmModeSetCrtc(fd, crtc_id, fb[front^1].fb_id, 0,0, &conn_id, 1, &mode); usleep(interval_us); front ^= 1; }else{ struct pollfd pfd{ .fd=fd, .events=POLLIN, .revents=0 }; int r = poll(&pfd, 1, interval_us/1000); if(r>0 && (pfd.revents & POLLIN)) drmHandleEvent(fd, &ev); front ^= 1; } } pcie_deinit(); destroy_fb(fd, fb[0]); destroy_fb(fd, fb[1]); close(fd); return 0; }
最新发布
10-21
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值