0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

解决USB DWC3控制器两大内存泄漏问题!基于RV1103B/RK3588的实战补丁解析

jf_44130326 来源:Linux1024 2026-02-09 16:30 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

嵌入式Linux领域,USB控制器是连接外部设备的核心组件,而USB DWC3控制器因其高性能、支持OTG/Host/Device多模式,被广泛应用于瑞芯微RV1103BRK3588等主流嵌入式平台。但在实际开发中,工程师常遇到一个棘手问题:动态模式切换或驱动装卸时的内存泄漏——长期运行会导致系统内存逐渐耗尽,引发卡顿、崩溃等稳定性问题。

今天我们就从问题复现、根源分析到补丁落地,完整拆解USB DWC3控制器的内存泄漏解决方案,附上可直接参考的代码片段与作用分析,所有方案均已通过硬件验证。

wKgZPGkaiwyAPRO7AACoubsRDH8185.png

一、问题背景:两类高频场景触发内存泄漏

在对RV1103B/RK3588平台的USB DWC3控制器测试中,我们发现两种高频使用场景会稳定触发内存泄漏,且可通过简单脚本复现:

场景1OTG模式动态切换(Device Host

USB DWC3支持通过otg_mode节点动态切换工作模式(比如从设备模式切换为主机模式),但循环执行切换操作时,内存会持续减少。

复现脚本

whiletruedo# 切换为OTG模式echootg > /sys/devices/platform/[usb_phy节点]/otg_modesleep1# 切换为Host模式echohost > /sys/devices/platform/[usb_phy节点]/otg_modesleep1# 清理页缓存、目录项缓存和inode缓存echo3 > /proc/sys/vm/drop_cachessleep1# 查看剩余内存,观察是否持续减少cat/proc/meminfo | grep MemFreedone&

根源:模式切换过程中调用的debugfs_lookup()函数存在缺陷——该函数获取dentry(目录项)后,需要手动调用dput()释放引用,否则会导致内存无法回收,循环切换会持续累积泄漏。

场景2Host模式下驱动bind/unbind

DWC3控制器工作在Host only模式时,通过bind/unbind操作加载/卸载驱动(常见于驱动调试或动态设备管理),同样会出现内存泄漏。

复现脚本

whiletruedo# 卸载DWC3驱动echo"usb控制器节点"> /sys/bus/platform/drivers/dwc3/unbindsleep1# 重新加载DWC3驱动echo"usb控制器节点"> /sys/bus/platform/drivers/dwc3/bindsleep1# 清理缓存echo3 > /proc/sys/vm/drop_cachessleep1# 观察剩余内存cat/proc/meminfo | grep MemFreedone&

根源:驱动加载时使用platform_device_add_properties()添加设备属性,但该函数创建的软件节点(software node)生命周期未与设备绑定——设备卸载时节点未被自动回收,导致内存泄漏。

二、补丁方案:从API替换到生命周期管理

针对上述两个核心问题,我们通过5个关键补丁(含上游同步和回溯补丁)实现彻底修复,每个补丁都包含明确的代码修改与功能定位,且已通过RV1103B/RK3588平台验证。

1.修复OTG切换泄漏:用debugfs_lookup_and_remove替代debugfs_lookup

核心思路debugfs_lookup()需要手动释放引用,而debugfs_lookup_and_remove()会自动完成查找+删除+释放流程,同时重构debugfs管理逻辑,避免重复查找开销。

1.1修改struct dwc3结构体(drivers/usb/dwc3/core.h

先调整DWC3核心结构体,用debug_root保存debugfs根目录,替代旧的root指针,避免重复查找:

// 旧代码structdwc3 { // ... 其他成员 structdentry  *root; // 旧的debugfs根目录指针 // ... 其他成员};// 新代码structdwc3 { // ... 其他成员 structdentry  *debug_root; // 新的debugfs根目录指针,保存设备专属根目录 // ... 其他成员 // 移除旧的struct dentry *root;};

作用debug_root会在dwc3_debugfs_init()中初始化,后续操作直接复用该指针,无需反复调用debugfs_lookup()查找根目录,减少冗余操作与泄漏风险。

1.2新增端点目录删除函数(drivers/usb/dwc3/debug.h

添加dwc3_debugfs_remove_endpoint_dir()声明,统一处理端点目录的删除逻辑:

// 旧代码#ifdefCONFIG_DEBUG_FSexternvoiddwc3_debugfs_create_endpoint_dir(structdwc3_ep *dep);externvoiddwc3_debugfs_init(structdwc3 *d);externvoiddwc3_debugfs_exit(structdwc3 *d);#elsestaticinlinevoiddwc3_debugfs_create_endpoint_dir(structdwc3_ep *dep){ }staticinlinevoiddwc3_debugfs_init(structdwc3 *d){ }staticinlinevoiddwc3_debugfs_exit(structdwc3 *d){ }#endif// 新代码#ifdefCONFIG_DEBUG_FSexternvoiddwc3_debugfs_create_endpoint_dir(structdwc3_ep *dep);externvoiddwc3_debugfs_remove_endpoint_dir(structdwc3_ep *dep); // 新增函数声明externvoiddwc3_debugfs_init(structdwc3 *d);externvoiddwc3_debugfs_exit(structdwc3 *d);#elsestaticinlinevoiddwc3_debugfs_create_endpoint_dir(structdwc3_ep *dep){ }staticinlinevoiddwc3_debugfs_remove_endpoint_dir(structdwc3_ep *dep){ } // 新增静态内联实现staticinlinevoiddwc3_debugfs_init(structdwc3 *d){ }staticinlinevoiddwc3_debugfs_exit(structdwc3 *d){ }#endif

作用:为后续驱动卸载时删除端点目录提供统一接口,避免分散调用debugfs_lookup()导致的泄漏。

1.3实现debugfs核心逻辑(drivers/usb/dwc3/debugfs.c

替换debugfs_lookup()debugfs_lookup_and_remove(),并调整目录创建/删除逻辑:

// 1. 重构端点目录创建函数// 旧代码staticvoiddwc3_debugfs_create_endpoint_files(structdwc3_ep *dep,structdentry *parent){ inti; for(i =0; i < ARRAY_SIZE(dwc3_ep_file_map); i++) {   conststructfile_operations*fops = dwc3_ep_file_map[i].fops;   constchar*name = dwc3_ep_file_map[i].name;   debugfs_create_file(name,0444, parent, dep, fops);  }}voiddwc3_debugfs_create_endpoint_dir(structdwc3_ep *dep){ structdentry*dir;  dir =debugfs_create_dir(dep->name, dep->dwc->root); // 依赖旧的root指针 dwc3_debugfs_create_endpoint_files(dep, dir);}// 新代码voiddwc3_debugfs_create_endpoint_dir(structdwc3_ep *dep){ structdentry*dir; // 用debug_root替代root,直接复用已保存的根目录  dir =debugfs_create_dir(dep->name, dep->dwc->debug_root); for(inti =0; i < ARRAY_SIZE(dwc3_ep_file_map); i++) {   conststructfile_operations*fops = dwc3_ep_file_map[i].fops;   constchar*name = dwc3_ep_file_map[i].name;   debugfs_create_file(name,0444, dir, dep, fops);  }}// 2. 实现端点目录删除函数voiddwc3_debugfs_remove_endpoint_dir(structdwc3_ep *dep){ // 自动完成“查找+删除+释放dentry”,无需手动dput() debugfs_lookup_and_remove(dep->name, dep->dwc->debug_root);}// 3. 初始化debugfs根目录voiddwc3_debugfs_init(structdwc3 *dwc){ structdentry*root; // ... 其他初始化逻辑  root =debugfs_create_dir(dev_name(dwc->dev), usb_debug_root);  dwc->debug_root = root; // 保存根目录到debug_root // ... 其他文件创建逻辑(如regdump、lsp_dump)}// 4. 清理debugfs资源voiddwc3_debugfs_exit(structdwc3 *dwc){ // 直接删除根目录,避免残留文件 debugfs_lookup_and_remove(dev_name(dwc->dev), usb_debug_root); kfree(dwc->regset);}

作用

debugfs_lookup_and_remove():内部会自动调用dput()释放dentry引用,彻底解决查找后未释放的泄漏问题;

复用debug_root:避免每次创建/删除端点目录时重复查找根目录,提升效率的同时减少错误。

1.4驱动卸载时调用新函数(drivers/usb/dwc3/gadget.c

在端点释放逻辑中,用新的删除函数替代旧的手动查找:

// 旧代码staticvoiddwc3_gadget_free_endpoints(struct dwc3 *dwc) { // ... 其他逻辑 debugfs_remove_recursive(debugfs_lookup(dep->name, dwc->root)); // 未释放dentry kfree(dep); // ... 其他逻辑}// 新代码staticvoiddwc3_gadget_free_endpoints(struct dwc3 *dwc) { // ... 其他逻辑 dwc3_debugfs_remove_endpoint_dir(dep); // 调用新函数,自动释放 kfree(dep); // ... 其他逻辑}

作用:驱动卸载时,通过统一接口安全删除端点目录,彻底杜绝该场景下的内存泄漏。

2.修复驱动bind/unbind泄漏:引入托管软件节点” API

这类泄漏的核心是节点生命周期未绑定设备,解决方案分三步:先提供托管API,再修复API缺陷,最后替换DWC3驱动中的旧调用。

步骤1:新增device_create_managed_software_nodeAPIdrivers/base/swnode.c+include/linux/property.h

先实现一个托管式软件节点创建函数,让节点生命周期与设备强绑定:

2.1.1定义托管标记(drivers/base/swnode.c

struct swnode中添加managed标记,区分托管节点与普通节点:

// 旧代码structswnode{ structswnode*parent; unsignedintallocated:1; // 标记是否动态分配};// 新代码structswnode{ structswnode*parent; unsignedintallocated:1; unsignedintmanaged:1;  // 新增:标记是否为托管节点(生命周期绑定设备)};
2.1.2实现托管APIdrivers/base/swnode.c

/*** device_create_managed_software_node - 为设备创建托管软件节点* @dev: 绑定的设备* @properties: 节点属性列表* @parent: 父节点(可选)* 返回:0成功,负数错误码*/intdevice_create_managed_software_node(structdevice *dev,                   conststructproperty_entry *properties,                   conststructsoftware_node *parent) { structfwnode_handle *p = software_node_fwnode(parent); structfwnode_handle *fwnode; // 父节点无效时返回错误 if(parent && !p)   return-EINVAL; // 创建软件节点(深拷贝属性,避免原数据修改影响)  fwnode = fwnode_create_software_node(properties, p); if(IS_ERR(fwnode))   returnPTR_ERR(fwnode); // 标记为托管节点,生命周期绑定设备  to_swnode(fwnode)->managed =true; // 将节点绑定到设备的次要fwnode  set_secondary_fwnode(dev, fwnode); return0;}EXPORT_SYMBOL_GPL(device_create_managed_software_node);
2.1.3声明APIinclude/linux/property.h

在头文件中添加函数声明,供其他驱动调用:

// 旧代码intdevice_add_software_node(structdevice *dev,conststructsoftware_node *node);voiddevice_remove_software_node(structdevice *dev);// 新代码intdevice_add_software_node(structdevice *dev,conststructsoftware_node *node);voiddevice_remove_software_node(structdevice *dev);// 新增托管API声明intdevice_create_managed_software_node(structdevice *dev,                   conststructproperty_entry *properties,                   conststructsoftware_node *parent);

作用

托管特性:managed标记会让节点在设备删除时自动回收,无需手动调用删除函数;

深拷贝属性:避免原属性列表被释放后,节点引用无效内存;

支持层级:可指定父节点,满足复杂设备的节点结构需求。

步骤2:修复托管API的引用计数下溢(drivers/base/swnode.c

问题software_node_notify()处理KOBJ_REMOVE事件时,会对托管节点执行两次引用计数递减,导致refcount_warn_saturate内核错误。

修复:在API中添加引用计数平衡逻辑:

// 旧代码intdevice_create_managed_software_node(structdevice *dev,                   conststructproperty_entry *properties,                   conststructsoftware_node *parent) { // ... 前面的创建逻辑  to_swnode(fwnode)->managed =true;  set_secondary_fwnode(dev, fwnode); return0;}// 新代码intdevice_create_managed_software_node(structdevice *dev,                   conststructproperty_entry *properties,                   conststructsoftware_node *parent) { // ... 前面的创建逻辑  to_swnode(fwnode)->managed =true;  set_secondary_fwnode(dev, fwnode); // 新增:设备已注册时,触发KOBJ_ADD事件,平衡后续KOBJ_REMOVE的引用计数 if(device_is_registered(dev))    software_node_notify(dev, KOBJ_ADD); return0;}

作用KOBJ_ADD事件会触发一次引用计数递增,后续KOBJ_REMOVE事件的两次递减会被抵消一次,最终引用计数正常归零,避免下溢错误。

步骤3DWC3 Host驱动替换旧APIdrivers/usb/dwc3/host.c

platform_device_add_properties()替换为新的托管API,让属性节点随设备回收:

// 旧代码intdwc3_host_init(structdwc3 *dwc){ // ... 其他逻辑 if(prop_idx) {   // 旧API:节点生命周期未绑定设备,卸载时泄漏    ret =platform_device_add_properties(xhci, props);   if(ret) {     dev_err(dwc->dev,"failed to add properties to xHCIn");     gotoerr;    }  } // ... 其他逻辑}// 新代码intdwc3_host_init(structdwc3 *dwc){ // ... 其他逻辑 if(prop_idx) {   // 新API:节点生命周期绑定xhci->dev,设备卸载时自动回收    ret =device_create_managed_software_node(&xhci->dev, props,NULL);   if(ret) {     dev_err(dwc->dev,"failed to add properties to xHCIn");     gotoerr;    }  } // ... 其他逻辑}

作用xhci设备卸载时,托管节点会被自动删除,彻底解决“bind/unbind”场景下的内存泄漏。

3.补充修复:probe阶段的电源管理泄漏(drivers/usb/dwc3/core.c

除了上述两大核心问题,DWC3 probe阶段若初始化失败,会导致电源供应器引用未释放,需补充错误分支处理:

// 旧代码staticintdwc3_probe(structplatform_device *pdev){ // ... 其他逻辑  dwc3_get_properties(dwc); // 内部调用power_supply_get_by_name获取usb_psy // 重置控制器获取失败时,直接返回错误,未释放usb_psy  dwc->reset = devm_reset_control_array_get_optional_shared(dev); if(IS_ERR(dwc->reset))   returnPTR_ERR(dwc->reset); // 时钟获取失败(EPROBE_DEFER)时,未释放usb_psy if(dev->of_node) {    ret = devm_clk_bulk_get_all(dev, &dwc->clks);   if(ret == -EPROBE_DEFER)     returnret;   // ... 其他逻辑  } // 重置解除失败时,未释放usb_psy  ret = reset_control_deassert(dwc->reset); if(ret)   returnret; // ... 其他逻辑assert_reset:  reset_control_assert(dwc->reset); // 未处理usb_psy释放}// 新代码staticintdwc3_probe(structplatform_device *pdev){ // ... 其他逻辑  dwc3_get_properties(dwc); // 重置控制器获取失败:跳转到put_usb_psy释放引用  dwc->reset = devm_reset_control_array_get_optional_shared(dev); if(IS_ERR(dwc->reset)) {    ret = PTR_ERR(dwc->reset);   gotoput_usb_psy;  } // 时钟获取失败:跳转到put_usb_psy释放引用 if(dev->of_node) {    ret = devm_clk_bulk_get_all(dev, &dwc->clks);   if(ret == -EPROBE_DEFER)     gotoput_usb_psy;   // ... 其他逻辑  } // 重置解除失败:跳转到put_usb_psy释放引用  ret = reset_control_deassert(dwc->reset); if(ret)   gotoput_usb_psy; // ... 其他逻辑assert_reset:  reset_control_assert(dwc->reset);// 新增错误分支:释放usb_psy引用put_usb_psy: if(dwc->usb_psy)    power_supply_put(dwc->usb_psy);}

作用:无论probe阶段哪个步骤失败,都会通过goto put_usb_psy释放power_supply_get_by_name()获取的引用,避免电源管理相关内存泄漏。

三、验证结果:RV1103B/RK3588上的稳定性测试

所有补丁均在RV1103B(嵌入式边缘计算平台)RK3588(高性能AI平台)上完成验证,测试标准如下:

1.稳定性:执行两种场景的复现脚本,持续运行24小时,MemFree数值波动范围≤1%(属于正常缓存变化),无持续减少;

2.无错误日志:查看dmesg,无refcount_warn_saturate、内存分配失败(out of memory)等错误;

3.功能正常:长时间运行后,USB设备插拔、OTG模式切换、Host驱动装卸均正常响应,无功能异常。

四、开发启示:内存泄漏修复的3个关键思路

从这次DWC3控制器的泄漏修复中,我们可以提炼出嵌入式Linux驱动开发的通用经验:

1.优先使用托管类API”:内核提供的devm_*(设备托管)、managedAPI(如本次的device_create_managed_software_node)会自动管理资源生命周期,避免手动释放遗漏——这是预防泄漏的最佳实践,能减少80%以上的人为失误。

2.场景化复现是关键:内存泄漏需结合实际使用场景(如模式切换、驱动装卸)设计复现脚本,通过/proc/meminfo观察整体内存变化,用slabtop定位具体泄漏的内存slab(如dentrysoftware_node相关),精准锁定泄漏点。

3.重视上游补丁同步:本次修复的核心API(如debugfs_lookup_and_remove、托管软件节点)均来自Linux内核上游(5.12 +版本),回溯上游补丁不仅能保证方案的稳定性,还能减少后续内核升级的适配成本——避免自己造轮子导致的兼容性问题。

补丁获取与适配建议

若你的项目使用了USB DWC3控制器(尤其是RV1103BRK3588等瑞芯微平台),可按以下方式适配:

1.获取补丁:补丁已提交至瑞芯微官方内核仓库(linux-rockchip),可直接提取上述代码片段手动修改,或基于5.10 +内核版本同步相关提交;

2.适配其他平台:若使用非瑞芯微平台(如高通NXP),需确认DWC3驱动版本——核心修改(debugfs替换、托管节点)通用,但需调整平台相关的设备节点(如usb_phy节点路径);

3.测试验证:适配后务必执行本文中的复现脚本,持续运行至少4小时,确认内存无泄漏且功能正常。

内存泄漏是嵌入式设备长期稳定运行的隐形杀手,尤其是USB这类高频使用的外设。希望本次DWC3控制器的修复案例,能为大家提供实用的排查和解决思路,让设备跑得更稳、更久~

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 控制器
    +关注

    关注

    114

    文章

    17915

    浏览量

    195833
  • 嵌入式
    +关注

    关注

    5212

    文章

    20763

    浏览量

    338799
  • usb
    usb
    +关注

    关注

    60

    文章

    8487

    浏览量

    286653
  • 内存泄漏
    +关注

    关注

    0

    文章

    42

    浏览量

    9552
收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    瑞芯微RK3588开发板RK3588 EVB和RK3588S EVB解读

    开发工程师Damon的解答。 RK3588开发板的款产品分别为RK3588 EVB及RK3588S EVB。RK3588 EVB 主要面向
    的头像 发表于 09-22 15:54 2.3w次阅读
    瑞芯微<b class='flag-5'>RK3588</b>开发板<b class='flag-5'>RK3588</b> EVB和<b class='flag-5'>RK3588</b>S EVB解读

    RK3588 PCB推荐叠层及阻抗设计

    近期华秋电子联合瑞芯微、凡亿重磅发布了:《RK3588 PCB设计指导白皮书》,帮助开发者更好地规范利用RK3588开发产品,提高所设计的PCB质量,在实战中巩固及提高PCB设计水平。本文
    发表于 08-10 09:32 2118次阅读
    <b class='flag-5'>RK3588</b> PCB推荐叠层及阻抗设计

    米尔RK3576和RK3588怎么选?-看这篇就够了

    ISP带有HDR和3DNR,RK3588的像素ISP分辨率更高(48M对比16M) 具备丰富的接口配置 者都配备了丰富的接口配置,PCIe/ SATA/ TYPE C/ USB
    发表于 12-27 11:44

    RK3588 EVB开发板原理图讲解【八】 RK3588 power Tree

    本帖最后由 瑞芯微方案开发老王 于 2025-3-1 11:41 编辑 一、RK3588电源架构核心特点 ​多电源域设计​ 芯片通常划分为多个独立电源域(Power Domain),例如
    发表于 03-01 11:38

    RK这2款旗舰芯片RK3588 PK RK3576,谁是最优选

    Mali G52 MC3RK3588 配备 ARM Mali - G610MC4,都支持 OpenGL ES 1.1、2.0 和 3.2,Vulkan 1.2,在支持的图形标准上者类似,但在
    发表于 07-10 18:24

    RK3399平台上USB控制器和PHY的连接方式和配置说明

    USB3.0 OTG具有USB3.0 OTG功能,且向下兼容USB2.0 OTG功能,大传输速率为5Gbps。USB3.0 HOST控制器
    发表于 05-12 17:46

    分享一种RK3588 USB芯片级DTSI的配置方法

    RK3588 DTSI 文件中 USB 控制器和 PHY 相关的主要节点如下所示,因为 USB DTSI 节点配置的是 USB
    发表于 05-23 11:27

    RK3588RK3588S在ARM阵列服务上的应用

      RK3588RK3588S是瑞芯微2022年初量产的旗舰8核高能处理,CPU、GPU、NPU性能强劲,并支持最高32GB 内存LPDDR4/LPDDR4x/LPDDR5,特别适
    发表于 07-18 17:54

    【新品】工业级别!RK3588行业主机系列

    RK3588行业主机系列采用RK3588J/RK3588八核64位处理,最大可配32GB大内存;支持8K视频编解码;支持千兆以太网、WiF
    的头像 发表于 03-15 11:23 4729次阅读
    【新品】工业级别!<b class='flag-5'>RK3588</b>行业主机系列

    rk3588rk3588s的区别

    高性能、低功耗的处理,是针对不同市场设计的不同版本。这篇文章将介绍这种处理的区别。 RK3588RK3588S的概述 -
    的头像 发表于 08-15 16:44 2.1w次阅读

    RK3588RK3588S比较哪个好?

    、效率和功能上都具有举足轻重的作用。现在,RK3588RK3588S这款处理都成为了热门话题,是值得一提的款芯片。那么,在这
    的头像 发表于 08-15 16:44 1.4w次阅读

    RK35883588s的区别

    RK35883588s的区别 Rockchip RK3588RK3588s是种功能强大且广受欢迎的片上系统(SoC)解决方案,用于一系
    的头像 发表于 08-15 17:03 2.9w次阅读

    rk3588参数详解 rk3588芯片参数

    rk3588参数详解 rk3588芯片参数 Rockchip官方已经推出了全新一代的高端芯片RK3588,作为旗舰芯片,其蕴含的高性能与先进科技引起了广泛关注。本篇文章将详细介绍RK3588
    的头像 发表于 08-21 17:16 4.6w次阅读

    一文搞懂 RK3588 PCIe:从硬件资源到拆分配置 + 避坑指南(含脑图)

    资源解析3  大拆分方案实战、关键配置步骤及避坑要点,附带可视化脑图,助力开发者快速落地  PCIe  相关项目。       一、 RK3588 PCIe  核心硬件资源   1
    的头像 发表于 11-20 18:18 5176次阅读
    一文搞懂 <b class='flag-5'>RK3588</b> PCIe:从硬件资源到拆分配置 + 避坑指南(含脑图)

    【技术分享】RK3588如何搭建xenomai3+ethercat

    说明使用的RK3588的分支版本是linux-6.1-stan-rkr6内核版本是6.1.99把瑞芯微的SDK更新到linux-6.1-stan-rkr6这个版本即可。编译xenomai3的内核请参考上一篇技术分享:技术分享|RK358
    的头像 发表于 12-11 17:26 1521次阅读
    【技术分享】<b class='flag-5'>RK3588</b>如何搭建xenomai<b class='flag-5'>3</b>+ethercat