VoCore USB屏幕自定义Linux DRM驱动拔插触发系统重启问题排查
USB VoCore屏幕DRM驱动热插拔重启问题排查与修复
问题描述
为基于USB的VoCore屏幕开发的Linux内核DRM驱动,经kmscube、Weston测试运行正常,但拔下设备时电脑无任何错误输出直接重启,已确认问题出在DRM相关逻辑(USB部分逻辑简单无异常)。驱动基于simpledrm开发,而simpledrm本身不支持热插拔设备。
相关代码片段
驱动头文件
... struct mpro_device { struct drm_device dev; struct device *dmadev; /* simplefb settings */ struct drm_display_mode mode; const struct drm_format_info *format; unsigned int pitch; // pixels on line!! /* memory management */ struct iosys_map screen_base; unsigned char* data; unsigned int block_size; /* modesetting */ uint32_t formats[8]; struct drm_plane primary_plane; struct drm_crtc crtc; struct drm_encoder encoder; struct drm_connector connector; /* device info */ unsigned int screen; unsigned int version; unsigned char id[8]; struct mpro_info info; struct mpro_config config; unsigned char cmd[64]; // 新增:用于保存fbdev实例,后续销毁用 struct drm_fbdev_cma *fbdev; }; #endif /* _MPRO_H_ */
主驱动核心代码
... static struct mpro_device *mpro_device_create(struct drm_driver *drv, struct usb_interface *interface) { struct mpro_device *mpro; struct drm_device *dev; int ret; mpro = devm_drm_dev_alloc(&interface->dev, drv, struct mpro_device, dev); if ( IS_ERR(mpro)) return ERR_CAST(mpro); dev = &mpro->dev; /* Config */ mpro->config.flipx = flipx == 0 ? 0 : 1; mpro->config.partial = partial == 0 ? 0 : 1; /* Hardware setup */ mpro->dmadev = usb_intf_get_dma_device(to_usb_interface(dev->dev)); if ( !mpro->dmadev ) drm_warn(dev, "buffer sharing not supported"); /* not an error */ ret = mpro_mode(mpro); if ( ret ) return ERR_PTR(ret); if ( mpro->config.flipx ) drm_info(dev, "image flip on x axis is enabled"); if ( mpro->config.partial > 0 ) drm_info(dev, "partial frame updates enabled"); /* Memory management */ ret = mpro_data_alloc(mpro); if ( ret ) return ERR_PTR(ret); iosys_map_set_vaddr(&mpro->screen_base, mpro->data); if ( iosys_map_is_null(&mpro->screen_base)) { drm_err(dev, "failed to allocate buffer"); return ERR_CAST(mpro); } /* Modesetting */ ret = mpro_modeset(mpro); if ( ret ) return ERR_PTR(ret); /* Primary plane */ ret = mpro_init_planes(mpro); if (ret) return ERR_PTR(ret); /* CRTC, Encoder, Connector */ ret = mpro_init_connector(mpro); if (ret) return ERR_PTR(ret); drm_mode_config_reset(dev); return mpro; } DEFINE_DRM_GEM_FOPS(mpro_fops); static struct drm_driver mpro_driver = { DRM_GEM_SHMEM_DRIVER_OPS, .name = DRIVER_NAME, .desc = DRIVER_DESC, .date = DRIVER_DATE, .major = DRIVER_MAJOR, .minor = DRIVER_MINOR, .driver_features = DRIVER_ATOMIC | DRIVER_GEM | DRIVER_MODESET, .fops = &mpro_fops, }; static int mpro_probe(struct usb_interface* interface, const struct usb_device_id* id) { struct mpro_device *mpro; int ret; mpro = mpro_device_create(&mpro_driver, interface); if ( IS_ERR(mpro)) return PTR_ERR(mpro); ret = mpro_init_sysfs(mpro); if ( ret ) drm_warn(&mpro->dev, "failed to add sysfs entries"); // 保存fbdev实例到设备结构体 mpro->fbdev = drm_fbdev_cma_init(&mpro->dev, MPRO_BPP, 1, &mpro->connector); if (IS_ERR(mpro->fbdev)) { drm_warn(&mpro->dev, "fbdev init failed"); mpro->fbdev = NULL; } return 0; } static void mpro_remove(struct usb_interface *interface) { struct drm_device *dev = usb_get_intfdata(interface); struct mpro_device *mpro = to_mpro(dev); // 1. 销毁fbdev资源,避免系统访问已失效的帧缓冲 if (mpro->fbdev) { drm_fbdev_cma_fini(mpro->fbdev); mpro->fbdev = NULL; } // 2. 清理DRM原子状态,释放硬件相关的原子上下文 drm_atomic_helper_shutdown(dev); // 3. 通知DRM子系统设备已移除,触发用户空间相关清理 drm_dev_unplug(dev); // 4. 注销DRM设备,从系统中移除设备节点 drm_dev_unregister(dev); // 5. 释放自定义分配的帧缓冲内存 if (mpro->data) { kfree(mpro->data); mpro->data = NULL; } // 6. 释放DMA设备引用 if (mpro->dmadev) { put_device(mpro->dmadev); mpro->dmadev = NULL; } } ... MODULE_LICENSE("Dual MIT/GPL");
原因分析
- remove函数操作顺序错误:原代码中
drm_atomic_helper_shutdown在drm_dev_unplug之后调用,导致DRM子系统清理设备时仍持有无效的硬件状态,触发内核非法内存访问。 - fbdev资源未清理:
mpro_fbdev_setup创建的fbdev设备在设备移除时未销毁,系统后续尝试访问已失效的帧缓冲设备,引发内核panic。 - 自定义内存未释放:
mpro_data_alloc分配的帧缓冲内存未在remove中释放,可能导致内存泄漏或野指针访问。 - simpledrm无热插拔支持:simpledrm设计为固定设备,未实现热插拔所需的完整资源清理流程,需要手动补充。
修复方案
1. 调整remove函数流程
遵循DRM设备销毁的标准顺序:先清理用户态/帧缓冲资源,再清理DRM原子状态,最后注销设备并释放内存。
2. 保存并销毁fbdev资源
在struct mpro_device中添加fbdev指针,probe时保存初始化后的fbdev实例,remove时调用drm_fbdev_cma_fini销毁。
3. 释放自定义分配的内存
确保mpro_data_alloc分配的内存(mpro->data)在remove中被正确释放。
4. 验证DRM原子状态清理
drm_atomic_helper_shutdown必须在drm_dev_unplug之前调用,确保硬件上下文被正确重置,避免后续访问失效资源。
额外建议
- 开启内核串口控制台,收集重启前的panic日志,精准定位问题点(无错误输出大概率是内核panic导致系统直接重启)。
- 测试时使用
echo 1 > /sys/module/printk/parameters/ignore_loglevel提升日志级别,确保所有错误信息被输出。
内容的提问来源于stack exchange,提问作者osku
相关产品推荐
相关产品推荐

