udl设备驱动学习(udl.c)

udl设备驱动学习(udl.c)文章详细解析了 UDL 驱动 它是 Linux 内核中管理通过 USB 连接的图形设备的组件 涉及 USB 设备的初始化 数据传输以及与 DRM 子系统的集成

大家好,欢迎来到IT知识分享网。

udl驱动学习笔记


一、udl设备
  • 来自gpt的解释

    DRM UDL驱动程序的主体是内核空间的驱动程序模块。这个模块负责与Linux内核中的DRM子系统集成,以便管理通过USB连接的图形设备。UDL代表”USB DisplayLink”,它是一种外部图形适配器,可以通过USB连接到计算机,用于将图像数据传输到外部显示器。

    主体驱动程序负责执行以下任务:

    1. 初始化和配置USB设备:驱动程序负责在系统启动时检测连接的UDL设备,并进行初始化和配置,以便正确地与设备通信和传输数据。
    2. 数据传输和管理:驱动程序负责处理从应用程序发送到显示设备的图像数据。这包括将图像数据传输到UDL设备,并确保数据按照正确的格式和协议传输。
    3. 与DRM子系统集成:UDL驱动程序需要与Linux内核中的DRM(Direct Rendering Manager)子系统集成,以便与图形硬件交互并在显示器上渲染图像。

    总之,UDL驱动程序的主体是内核空间的驱动程序模块,它负责管理USB连接的图形设备并实现图像数据的传输和显示。

  • 个人理解

    我问学长udl drm驱动是否是一个usb转hdmi等显示接口的转换器?学长答否,先保留这个问题。

    udl设备首先是USB子系统中,以从设备身份与主机进行通信的一个设备。它是一个图形设备,因此要实现DRM设备的相关功能。

    DRM设备是一个显示控制器,了解drm驱动框架时了解到,其组成部分包括FB、plane、CRTC、encoder、connector等。UDL设备上,也有对应的这些部分。在DRM驱动中,各个部分的功能已经了解过,FB上中存储有凸显数据,plane管理图层的叠加、位置、大小等,通过CRTC扫描转换为图像信号再经encoder转换为对应conector的信号,在显示器上显示。

    //这部分理解错误

    因此理解udl设备为两个主要部分,一个是与主机进行通信的usb从设备的部分。一个是drm设备的部分。udl设备驱动的drm功能部分注册到主机的drm子系统中,驱动中usb通信部分功能管理drm内核与该驱动中drm部分功能的数据传输。

    因此一个udl设备驱动需要实现哪些功能?

    • drm功能
    • usb数据传输功能
    • drm内核与drm设备驱动的交互变为drm内核与usb总线的交互和usb总线和drm设备驱动的交互。

    //

    以上一部分,我把udl设备驱动理解为从设备侧的gadget驱动。实际上,udl设备驱动位于host侧。它主 要完成两个功能:

    • 注册DRM驱动,可以在用户空间打开对应的设备,进行对图形和显示的操作。
    • 作为USB传输中的主机侧,将DRM框架内的图像数据传输至从设备。

    作为从设备,gadget驱动中,要实现哪些功能呢?

    • 接收host传来的图像数据,在自己一侧对这些数据进行处理,然后进行显示。

    到这里,解释学长说:“udl设备不是一个usb转hdmi”?

    ​ 在问学长前,我在网上检索usb显卡相关内容,找到一个产品的拆解分析(https://zhuanlan.zhihu.com/p/),如下:

    img

    • 25Q64JVSIQ:8MB容量的闪存芯片。
    • FL2000DX:USB转VGA视频输出芯片。
    • CM108AH:USB声卡。
    • FE8.1:USB2.0 Hub芯片。
    • IT66121FN:VGA转HDMI芯片,也支持音频输入输出。

    ​ 这是这个”USB显卡”的架构分析。可以发现,其确实为USB转VGA和HDMI的设备。看这篇文章时,还学到了一个小知识:

    ​ USB3.0接口保留了USB2.0的D+和D-这一套差分信号引脚,以兼容USB2.0设备。而USB3.0协议本身使用T+/T-、R+/R-这一套引脚进行数据传输。

    ​ 利用这一点,提到的“USB显卡”的工程师使出了一个大脑洞:USB接口的T+/T-、R+/R-这一套引脚直接连接FL2000DX,D+和D-这一套引脚连接FE8.1(USB2.0Hub)。这样成功的省下了一个USB3.0Hub(CM108AH不支持USB3.0)。

    ​ 现在回到原来的问题。这个“USB显卡”设备确实是一个USB转VGA和HDMI的设备,因此它实际上应该叫做一个“USB”显示适配器。而在我面临的问题中,我们是使用rk3588作为gadget设备,把接收到的图像数据通过rk3588上搭载的系统进行处理最终进行显示,并不是简单的将数据转换为hdmi信号发送至显示设备。

    ​ 那么UDL设备到底是指什么呢,还是回到GPT的解释:“UDL代表”USB DisplayLink”,它是一种外部图形适配器,可以通过USB连接到计算机,用于将图像数据传输到外部显示器。”外部图形适配器就是它的解释,显示器连接到UDL设备,UDL设备通过USB接口连接到主机,在主机侧通过UDL驱动把图像数据发送至UDL设备,UDL设备完成与显示器的交互。

二、代码分析

​ udl驱动的功能是通过USB实现把图像数据发送至UDL设备,UDL设备再完成与显示器的交互。下面从图像数据传输的角度来理解udl驱动。

首先来看入口函数:分配udl_device,调用udl_driver_create()

static int udl_usb_probe(struct usb_interface *interface, const struct usb_device_id *id) { 
    int r; struct udl_device *udl;//分配udl_device udl = udl_driver_create(interface); if (IS_ERR(udl)) return PTR_ERR(udl); r = drm_dev_register(&udl->drm, 0); //注册DRM设备 if (r) return r; DRM_INFO("Initialized udl on minor %d\n", udl->drm.primary->index); drm_fbdev_generic_setup(&udl->drm, 0);//用于创建并注册一个与 fbdev(Linux 帧缓冲设备)兼容的 DRM Framebuffer 设备。 return 0; } 

udl_driver_create():设置udl_device,调用udl_init()

static struct udl_device *udl_driver_create(struct usb_interface *interface) { 
    struct usb_device *udev = interface_to_usbdev(interface); struct udl_device *udl; int r; //设置udl_device udl = devm_drm_dev_alloc(&interface->dev, &driver, struct udl_device, drm); if (IS_ERR(udl)) return udl; udl->udev = udev; r = udl_init(udl); if (r) return ERR_PTR(r); usb_set_intfdata(interface, udl); return udl; } 

udl_init():调用udl_modeset_init()

int udl_init(struct udl_device *udl) { 
    struct drm_device *dev = &udl->drm; int ret = -ENOMEM; DRM_DEBUG("\n"); //首先获取 USB 设备的 DMA 设备,然后初始化 udl 设备的互斥锁 gem_lock。 udl->dmadev = usb_intf_get_dma_device(to_usb_interface(dev->dev)); if (!udl->dmadev) drm_warn(dev, "buffer sharing not supported"); /* not an error */ mutex_init(&udl->gem_lock); //解析 USB 设备的厂商描述符,如果解析失败则返回错误。 if (!udl_parse_vendor_descriptor(dev, udl->udev)) { 
    ret = -ENODEV; DRM_ERROR("firmware not recognized. Assume incompatible device\n"); goto err; } //函数选择标准通道,如果失败则打印错误信息。 if (udl_select_std_channel(udl)) DRM_ERROR("Selecting channel failed\n"); //分配 urb 列表,如果分配失败则返回错误。 if (!udl_alloc_urb_list(dev, WRITES_IN_FLIGHT, MAX_TRANSFER)) { 
    DRM_ERROR("udl_alloc_urb_list failed\n"); goto err; } DRM_DEBUG("\n"); ret = udl_modeset_init(dev); if (ret) goto err; //初始化 KMS 帮助器的轮询支持,并返回成功或失败的结果。 drm_kms_helper_poll_init(dev); return 0; err: if (udl->urbs.count) udl_free_urb_list(dev); put_device(udl->dmadev); DRM_ERROR("%d\n", ret); return ret; } 

udl_modeset_init:调用drm_simple_display_pipe_init()

int udl_modeset_init(struct drm_device *dev) { 
    size_t format_count = ARRAY_SIZE(udl_simple_display_pipe_formats); struct udl_device *udl = to_udl(dev); struct drm_connector *connector; int ret; ret = drmm_mode_config_init(dev); if (ret) return ret; dev->mode_config.min_width = 640; dev->mode_config.min_height = 480; dev->mode_config.max_width = 2048; dev->mode_config.max_height = 2048; dev->mode_config.prefer_shadow = 0; dev->mode_config.preferred_depth = 16; dev->mode_config.funcs = &udl_mode_funcs; connector = udl_connector_init(dev); if (IS_ERR(connector)) return PTR_ERR(connector); format_count = ARRAY_SIZE(udl_simple_display_pipe_formats); ret = drm_simple_display_pipe_init(dev, &udl->display_pipe, &udl_simple_display_pipe_funcs, udl_simple_display_pipe_formats, format_count, NULL, connector); if (ret) return ret; drm_mode_config_reset(dev); return 0; } 

drm_simple_display_pipe_init():主要是udl_simple_display_pipe_funcs->enable

struct drm_simple_display_pipe_funcs udl_simple_display_pipe_funcs = { 
    .mode_valid = udl_simple_display_pipe_mode_valid, .enable = udl_simple_display_pipe_enable, .disable = udl_simple_display_pipe_disable, .update = udl_simple_display_pipe_update, .prepare_fb = drm_gem_fb_simple_display_pipe_prepare_fb, }; 

udl_simple_display_pipe_enable:调用udl_handle_damage

static void udl_simple_display_pipe_enable(struct drm_simple_display_pipe *pipe, struct drm_crtc_state *crtc_state, struct drm_plane_state *plane_state) { 
    struct drm_crtc *crtc = &pipe->crtc; struct drm_device *dev = crtc->dev; struct drm_framebuffer *fb = plane_state->fb; //获取fb,plane_state在drm_simple_display_pipe_init()中由drm核心提供 struct udl_device *udl = to_udl(dev); struct drm_display_mode *mode = &crtc_state->mode; char *buf; char *wrptr; int color_depth = UDL_COLOR_DEPTH_16BPP; buf = (char *)udl->mode_buf; /* This first section has to do with setting the base address on the * controller associated with the display. There are 2 base * pointers, currently, we only use the 16 bpp segment. */ wrptr = udl_vidreg_lock(buf); wrptr = udl_set_color_depth(wrptr, color_depth); /* set base for 16bpp segment to 0 */ wrptr = udl_set_base16bpp(wrptr, 0); /* set base for 8bpp segment to end of fb */ wrptr = udl_set_base8bpp(wrptr, 2 * mode->vdisplay * mode->hdisplay); wrptr = udl_set_vid_cmds(wrptr, mode); wrptr = udl_set_blank_mode(wrptr, UDL_BLANK_MODE_ON); wrptr = udl_vidreg_unlock(wrptr); wrptr = udl_dummy_render(wrptr); udl->mode_buf_len = wrptr - buf; udl_handle_damage(fb, 0, 0, fb->width, fb->height); if (!crtc_state->mode_changed) return; /* enable display */ udl_crtc_write_mode_to_hw(crtc); } 

udl_handle_damage:调用udl_render_hline进行传输

static int udl_handle_damage(struct drm_framebuffer *fb, int x, int y, int width, int height) { 
    struct drm_device *dev = fb->dev; struct dma_buf_attachment *import_attach = fb->obj[0]->import_attach; int i, ret, tmp_ret; char *cmd; struct urb *urb; struct drm_rect clip; int log_bpp; void *vaddr; ret = udl_log_cpp(fb->format->cpp[0]); if (ret < 0) return ret; log_bpp = ret; ret = udl_aligned_damage_clip(&clip, x, y, width, height); if (ret) return ret; else if ((clip.x2 > fb->width) || (clip.y2 > fb->height)) return -EINVAL; if (import_attach) { 
    ret = dma_buf_begin_cpu_access(import_attach->dmabuf, DMA_FROM_DEVICE); if (ret) return ret; } vaddr = drm_gem_shmem_vmap(fb->obj[0]); //将帧缓冲区对象fb->obj[0]映射为vaddr if (IS_ERR(vaddr)) { 
    DRM_ERROR("failed to vmap fb\n"); goto out_dma_buf_end_cpu_access; } urb = udl_get_urb(dev); if (!urb) { 
    ret = -ENOMEM; goto out_drm_gem_shmem_vunmap; } cmd = urb->transfer_buffer; for (i = clip.y1; i < clip.y2; i++) { 
    const int line_offset = fb->pitches[0] * i; const int byte_offset = line_offset + (clip.x1 << log_bpp); const int dev_byte_offset = (fb->width * i + clip.x1) << log_bpp; const int byte_width = (clip.x2 - clip.x1) << log_bpp; ret = udl_render_hline(dev, log_bpp, &urb, (char *)vaddr, &cmd, byte_offset, dev_byte_offset, byte_width); //调用udl_render_hline进行传输,vaddr指向fb if (ret) goto out_drm_gem_shmem_vunmap; } if (cmd > (char *)urb->transfer_buffer) { 
    /* Send partial buffer remaining before exiting */ int len; if (cmd < (char *)urb->transfer_buffer + urb->transfer_buffer_length) *cmd++ = 0xAF; len = cmd - (char *)urb->transfer_buffer; ret = udl_submit_urb(dev, urb, len); } else { 
    udl_urb_completion(urb); } ret = 0; out_drm_gem_shmem_vunmap: drm_gem_shmem_vunmap(fb->obj[0], vaddr); out_dma_buf_end_cpu_access: if (import_attach) { 
    tmp_ret = dma_buf_end_cpu_access(import_attach->dmabuf, DMA_FROM_DEVICE); if (tmp_ret && !ret) ret = tmp_ret; /* only update ret if not set yet */ } return ret; } 

udl_render_hline()

int udl_render_hline(struct drm_device *dev, int log_bpp, struct urb **urb_ptr, const char *front, char **urb_buf_ptr, u32 byte_offset, u32 device_byte_offset, u32 byte_width) { 
    const u8 *line_start, *line_end, *next_pixel; u32 base16 = 0 + (device_byte_offset >> log_bpp) * 2; struct urb *urb = *urb_ptr; u8 *cmd = *urb_buf_ptr; u8 *cmd_end = (u8 *) urb->transfer_buffer + urb->transfer_buffer_length; BUG_ON(!(log_bpp == 1 || log_bpp == 2)); line_start = (u8 *) (front + byte_offset); //front存储了像素数据 next_pixel = line_start; line_end = next_pixel + byte_width; //进行传输 while (next_pixel < line_end) { 
    //读取指定长度像素数据并压缩,存储到USB请求块的传输缓冲区(cmd指向的空间)中 udl_compress_hline16(&next_pixel, line_end, &base16, (u8 **) &cmd, (u8 *) cmd_end, log_bpp); //判断是否填满传输缓冲区,填满进行传输,并获取新的urb if (cmd >= cmd_end) { 
    int len = cmd - (u8 *) urb->transfer_buffer; //计算已经填充到传输缓冲区中的数据长度 int ret = udl_submit_urb(dev, urb, len); //调用 udl_submit_urb 函数将当前的 USB 请求块提交给设备 if (ret) return ret; urb = udl_get_urb(dev);//获取一个新的 USB 请求块,并将其更新到 urb_ptr 指向的地址 if (!urb) return -EAGAIN; *urb_ptr = urb; cmd = urb->transfer_buffer; //更新cmd指针为新的USB请求块的传输缓冲区起始地址,cmd_end 指针为传输缓冲区的末尾地址。 cmd_end = &cmd[urb->transfer_buffer_length]; } } *urb_buf_ptr = cmd; return 0; } 

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/157404.html

(0)
上一篇 2025-02-06 14:20
下一篇 2025-02-06 14:25

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信