You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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");

原因分析

  1. remove函数操作顺序错误:原代码中drm_atomic_helper_shutdown在drm_dev_unplug之后调用,导致DRM子系统清理设备时仍持有无效的硬件状态,触发内核非法内存访问。
  2. fbdev资源未清理:mpro_fbdev_setup创建的fbdev设备在设备移除时未销毁,系统后续尝试访问已失效的帧缓冲设备,引发内核panic。
  3. 自定义内存未释放:mpro_data_alloc分配的帧缓冲内存未在remove中释放,可能导致内存泄漏或野指针访问。
  4. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 10:07:02