在嵌入式Linux領(lǐng)域,USB控制器是連接外部設(shè)備的核心組件,而USB DWC3控制器因其高性能、支持OTG/Host/Device多模式,被廣泛應(yīng)用于瑞芯微RV1103B、RK3588等主流嵌入式平臺。但在實(shí)際開發(fā)中,工程師常遇到一個棘手問題:動態(tài)模式切換或驅(qū)動裝卸時的內(nèi)存泄漏——長期運(yùn)行會導(dǎo)致系統(tǒng)內(nèi)存逐漸耗盡,引發(fā)卡頓、崩潰等穩(wěn)定性問題。
今天我們就從問題復(fù)現(xiàn)、根源分析到補(bǔ)丁落地,完整拆解USB DWC3控制器的內(nèi)存泄漏解決方案,附上可直接參考的代碼片段與作用分析,所有方案均已通過硬件驗(yàn)證。

一、問題背景:兩類高頻場景觸發(fā)內(nèi)存泄漏
在對RV1103B/RK3588平臺的USB DWC3控制器測試中,我們發(fā)現(xiàn)兩種高頻使用場景會穩(wěn)定觸發(fā)內(nèi)存泄漏,且可通過簡單腳本復(fù)現(xiàn):
場景1:OTG模式動態(tài)切換(Device Host)
USB DWC3支持通過otg_mode節(jié)點(diǎn)動態(tài)切換工作模式(比如從設(shè)備模式切換為主機(jī)模式),但循環(huán)執(zhí)行切換操作時,內(nèi)存會持續(xù)減少。
復(fù)現(xiàn)腳本:
whiletruedo# 切換為OTG模式echootg > /sys/devices/platform/[usb_phy節(jié)點(diǎn)]/otg_modesleep1# 切換為Host模式echohost > /sys/devices/platform/[usb_phy節(jié)點(diǎn)]/otg_modesleep1# 清理頁緩存、目錄項(xiàng)緩存和inode緩存echo3 > /proc/sys/vm/drop_cachessleep1# 查看剩余內(nèi)存,觀察是否持續(xù)減少cat/proc/meminfo | grep MemFreedone&
根源:模式切換過程中調(diào)用的debugfs_lookup()函數(shù)存在缺陷——該函數(shù)獲取dentry(目錄項(xiàng))后,需要手動調(diào)用dput()釋放引用,否則會導(dǎo)致內(nèi)存無法回收,循環(huán)切換會持續(xù)累積泄漏。
場景2:Host模式下驅(qū)動bind/unbind
當(dāng)DWC3控制器工作在Host only模式時,通過bind/unbind操作加載/卸載驅(qū)動(常見于驅(qū)動調(diào)試或動態(tài)設(shè)備管理),同樣會出現(xiàn)內(nèi)存泄漏。
復(fù)現(xiàn)腳本:
whiletruedo# 卸載DWC3驅(qū)動echo"usb控制器節(jié)點(diǎn)"> /sys/bus/platform/drivers/dwc3/unbindsleep1# 重新加載DWC3驅(qū)動echo"usb控制器節(jié)點(diǎn)"> /sys/bus/platform/drivers/dwc3/bindsleep1# 清理緩存echo3 > /proc/sys/vm/drop_cachessleep1# 觀察剩余內(nèi)存cat/proc/meminfo | grep MemFreedone&
根源:驅(qū)動加載時使用platform_device_add_properties()添加設(shè)備屬性,但該函數(shù)創(chuàng)建的軟件節(jié)點(diǎn)(software node)生命周期未與設(shè)備綁定——設(shè)備卸載時節(jié)點(diǎn)未被自動回收,導(dǎo)致內(nèi)存泄漏。
二、補(bǔ)丁方案:從API替換到生命周期管理
針對上述兩個核心問題,我們通過5個關(guān)鍵補(bǔ)丁(含上游同步和回溯補(bǔ)?。?shí)現(xiàn)徹底修復(fù),每個補(bǔ)丁都包含明確的代碼修改與功能定位,且已通過RV1103B/RK3588平臺驗(yàn)證。
1.修復(fù)OTG切換泄漏:用debugfs_lookup_and_remove替代debugfs_lookup
核心思路:debugfs_lookup()需要手動釋放引用,而debugfs_lookup_and_remove()會自動完成“查找+刪除+釋放”流程,同時重構(gòu)debugfs管理邏輯,避免重復(fù)查找開銷。
1.1修改struct dwc3結(jié)構(gòu)體(drivers/usb/dwc3/core.h)
先調(diào)整DWC3核心結(jié)構(gòu)體,用debug_root保存debugfs根目錄,替代舊的root指針,避免重復(fù)查找:
// 舊代碼structdwc3 {// ... 其他成員structdentry *root; // 舊的debugfs根目錄指針// ... 其他成員};// 新代碼structdwc3 {// ... 其他成員structdentry *debug_root; // 新的debugfs根目錄指針,保存設(shè)備專屬根目錄// ... 其他成員// 移除舊的struct dentry *root;};
作用:debug_root會在dwc3_debugfs_init()中初始化,后續(xù)操作直接復(fù)用該指針,無需反復(fù)調(diào)用debugfs_lookup()查找根目錄,減少冗余操作與泄漏風(fēng)險(xiǎn)。
1.2新增端點(diǎn)目錄刪除函數(shù)(drivers/usb/dwc3/debug.h)
添加dwc3_debugfs_remove_endpoint_dir()聲明,統(tǒng)一處理端點(diǎn)目錄的刪除邏輯:
// 舊代碼externvoiddwc3_debugfs_create_endpoint_dir(structdwc3_ep *dep);externvoiddwc3_debugfs_init(structdwc3 *d);externvoiddwc3_debugfs_exit(structdwc3 *d);staticinlinevoiddwc3_debugfs_create_endpoint_dir(structdwc3_ep *dep){ }staticinlinevoiddwc3_debugfs_init(structdwc3 *d){ }staticinlinevoiddwc3_debugfs_exit(structdwc3 *d){ }// 新代碼externvoiddwc3_debugfs_create_endpoint_dir(structdwc3_ep *dep);externvoiddwc3_debugfs_remove_endpoint_dir(structdwc3_ep *dep); // 新增函數(shù)聲明externvoiddwc3_debugfs_init(structdwc3 *d);externvoiddwc3_debugfs_exit(structdwc3 *d);staticinlinevoiddwc3_debugfs_create_endpoint_dir(structdwc3_ep *dep){ }staticinlinevoiddwc3_debugfs_remove_endpoint_dir(structdwc3_ep *dep){ } // 新增靜態(tài)內(nèi)聯(lián)實(shí)現(xiàn)staticinlinevoiddwc3_debugfs_init(structdwc3 *d){ }staticinlinevoiddwc3_debugfs_exit(structdwc3 *d){ }
作用:為后續(xù)驅(qū)動卸載時刪除端點(diǎn)目錄提供統(tǒng)一接口,避免分散調(diào)用debugfs_lookup()導(dǎo)致的泄漏。
1.3實(shí)現(xiàn)debugfs核心邏輯(drivers/usb/dwc3/debugfs.c)
替換debugfs_lookup()為debugfs_lookup_and_remove(),并調(diào)整目錄創(chuàng)建/刪除邏輯:
// 1. 重構(gòu)端點(diǎn)目錄創(chuàng)建函數(shù)// 舊代碼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,直接復(fù)用已保存的根目錄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. 實(shí)現(xiàn)端點(diǎn)目錄刪除函數(shù)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// ... 其他文件創(chuàng)建邏輯(如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():內(nèi)部會自動調(diào)用dput()釋放dentry引用,徹底解決“查找后未釋放”的泄漏問題;
?復(fù)用debug_root:避免每次創(chuàng)建/刪除端點(diǎn)目錄時重復(fù)查找根目錄,提升效率的同時減少錯誤。
1.4驅(qū)動卸載時調(diào)用新函數(shù)(drivers/usb/dwc3/gadget.c)
在端點(diǎn)釋放邏輯中,用新的刪除函數(shù)替代舊的手動查找:
// 舊代碼staticvoiddwc3_gadget_free_endpoints(struct dwc3 *dwc) {// ... 其他邏輯debugfs_remove_recursive(debugfs_lookup(dep->name, dwc->root)); // 未釋放dentrykfree(dep);// ... 其他邏輯}// 新代碼staticvoiddwc3_gadget_free_endpoints(struct dwc3 *dwc) {// ... 其他邏輯dwc3_debugfs_remove_endpoint_dir(dep); // 調(diào)用新函數(shù),自動釋放kfree(dep);// ... 其他邏輯}
作用:驅(qū)動卸載時,通過統(tǒng)一接口安全刪除端點(diǎn)目錄,徹底杜絕該場景下的內(nèi)存泄漏。
2.修復(fù)驅(qū)動bind/unbind泄漏:引入“托管軟件節(jié)點(diǎn)” API
這類泄漏的核心是“節(jié)點(diǎn)生命周期未綁定設(shè)備”,解決方案分三步:先提供托管API,再修復(fù)API缺陷,最后替換DWC3驅(qū)動中的舊調(diào)用。
步驟1:新增device_create_managed_software_nodeAPI(drivers/base/swnode.c+include/linux/property.h)
先實(shí)現(xiàn)一個“托管式”軟件節(jié)點(diǎn)創(chuàng)建函數(shù),讓節(jié)點(diǎn)生命周期與設(shè)備強(qiáng)綁定:
2.1.1定義托管標(biāo)記(drivers/base/swnode.c)
在struct swnode中添加managed標(biāo)記,區(qū)分托管節(jié)點(diǎn)與普通節(jié)點(diǎn):
// 舊代碼structswnode{structswnode*parent;unsignedintallocated:1; // 標(biāo)記是否動態(tài)分配};// 新代碼structswnode{structswnode*parent;unsignedintallocated:1;unsignedintmanaged:1; // 新增:標(biāo)記是否為托管節(jié)點(diǎn)(生命周期綁定設(shè)備)};
2.1.2實(shí)現(xiàn)托管API(drivers/base/swnode.c)
/*** device_create_managed_software_node - 為設(shè)備創(chuàng)建托管軟件節(jié)點(diǎn)* @dev: 綁定的設(shè)備* @properties: 節(jié)點(diǎn)屬性列表* @parent: 父節(jié)點(diǎn)(可選)* 返回:0成功,負(fù)數(shù)錯誤碼*/intdevice_create_managed_software_node(structdevice *dev,conststructproperty_entry *properties,conststructsoftware_node *parent) {structfwnode_handle *p = software_node_fwnode(parent);structfwnode_handle *fwnode;// 父節(jié)點(diǎn)無效時返回錯誤if(parent && !p)return-EINVAL;// 創(chuàng)建軟件節(jié)點(diǎn)(深拷貝屬性,避免原數(shù)據(jù)修改影響)fwnode = fwnode_create_software_node(properties, p);if(IS_ERR(fwnode))returnPTR_ERR(fwnode);// 標(biāo)記為托管節(jié)點(diǎn),生命周期綁定設(shè)備to_swnode(fwnode)->managed =true;// 將節(jié)點(diǎn)綁定到設(shè)備的次要fwnodeset_secondary_fwnode(dev, fwnode);return0;}EXPORT_SYMBOL_GPL(device_create_managed_software_node);
2.1.3聲明API(include/linux/property.h)
在頭文件中添加函數(shù)聲明,供其他驅(qū)動調(diào)用:
// 舊代碼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標(biāo)記會讓節(jié)點(diǎn)在設(shè)備刪除時自動回收,無需手動調(diào)用刪除函數(shù);
?深拷貝屬性:避免原屬性列表被釋放后,節(jié)點(diǎn)引用無效內(nèi)存;
?支持層級:可指定父節(jié)點(diǎn),滿足復(fù)雜設(shè)備的節(jié)點(diǎn)結(jié)構(gòu)需求。
步驟2:修復(fù)托管API的引用計(jì)數(shù)下溢(drivers/base/swnode.c)
問題:software_node_notify()處理KOBJ_REMOVE事件時,會對托管節(jié)點(diǎn)執(zhí)行兩次引用計(jì)數(shù)遞減,導(dǎo)致refcount_warn_saturate內(nèi)核錯誤。
修復(fù):在API中添加引用計(jì)數(shù)平衡邏輯:
// 舊代碼intdevice_create_managed_software_node(structdevice *dev,conststructproperty_entry *properties,conststructsoftware_node *parent) {// ... 前面的創(chuàng)建邏輯to_swnode(fwnode)->managed =true;set_secondary_fwnode(dev, fwnode);return0;}// 新代碼intdevice_create_managed_software_node(structdevice *dev,conststructproperty_entry *properties,conststructsoftware_node *parent) {// ... 前面的創(chuàng)建邏輯to_swnode(fwnode)->managed =true;set_secondary_fwnode(dev, fwnode);// 新增:設(shè)備已注冊時,觸發(fā)KOBJ_ADD事件,平衡后續(xù)KOBJ_REMOVE的引用計(jì)數(shù)if(device_is_registered(dev))software_node_notify(dev, KOBJ_ADD);return0;}
作用:KOBJ_ADD事件會觸發(fā)一次引用計(jì)數(shù)遞增,后續(xù)KOBJ_REMOVE事件的兩次遞減會被抵消一次,最終引用計(jì)數(shù)正常歸零,避免下溢錯誤。
步驟3:DWC3 Host驅(qū)動替換舊API(drivers/usb/dwc3/host.c)
將platform_device_add_properties()替換為新的托管API,讓屬性節(jié)點(diǎn)隨設(shè)備回收:
// 舊代碼intdwc3_host_init(structdwc3 *dwc){// ... 其他邏輯if(prop_idx) {// 舊API:節(jié)點(diǎn)生命周期未綁定設(shè)備,卸載時泄漏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:節(jié)點(diǎn)生命周期綁定xhci->dev,設(shè)備卸載時自動回收ret =device_create_managed_software_node(&xhci->dev, props,NULL);if(ret) {dev_err(dwc->dev,"failed to add properties to xHCIn");gotoerr;}}// ... 其他邏輯}
作用:xhci設(shè)備卸載時,托管節(jié)點(diǎn)會被自動刪除,徹底解決“bind/unbind”場景下的內(nèi)存泄漏。
3.補(bǔ)充修復(fù):probe階段的電源管理泄漏(drivers/usb/dwc3/core.c)
除了上述兩大核心問題,DWC3 probe階段若初始化失敗,會導(dǎo)致電源供應(yīng)器引用未釋放,需補(bǔ)充錯誤分支處理:
// 舊代碼staticintdwc3_probe(structplatform_device *pdev){// ... 其他邏輯dwc3_get_properties(dwc); // 內(nèi)部調(diào)用power_supply_get_by_name獲取usb_psy// 重置控制器獲取失敗時,直接返回錯誤,未釋放usb_psydwc->reset = devm_reset_control_array_get_optional_shared(dev);if(IS_ERR(dwc->reset))returnPTR_ERR(dwc->reset);// 時鐘獲取失?。‥PROBE_DEFER)時,未釋放usb_psyif(dev->of_node) {ret = devm_clk_bulk_get_all(dev, &dwc->clks);if(ret == -EPROBE_DEFER)returnret;// ... 其他邏輯}// 重置解除失敗時,未釋放usb_psyret = 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);// 重置控制器獲取失?。禾D(zhuǎn)到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;}// 時鐘獲取失?。禾D(zhuǎn)到put_usb_psy釋放引用if(dev->of_node) {ret = devm_clk_bulk_get_all(dev, &dwc->clks);if(ret == -EPROBE_DEFER)gotoput_usb_psy;// ... 其他邏輯}// 重置解除失?。禾D(zhuǎn)到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()獲取的引用,避免電源管理相關(guān)內(nèi)存泄漏。
三、驗(yàn)證結(jié)果:RV1103B/RK3588上的穩(wěn)定性測試
所有補(bǔ)丁均在RV1103B(嵌入式邊緣計(jì)算平臺)和RK3588(高性能AI平臺)上完成驗(yàn)證,測試標(biāo)準(zhǔn)如下:
1.穩(wěn)定性:執(zhí)行兩種場景的復(fù)現(xiàn)腳本,持續(xù)運(yùn)行24小時,MemFree數(shù)值波動范圍≤1%(屬于正常緩存變化),無持續(xù)減少;
2.無錯誤日志:查看dmesg,無refcount_warn_saturate、內(nèi)存分配失?。?/span>out of memory)等錯誤;
3.功能正常:長時間運(yùn)行后,USB設(shè)備插拔、OTG模式切換、Host驅(qū)動裝卸均正常響應(yīng),無功能異常。
四、開發(fā)啟示:內(nèi)存泄漏修復(fù)的3個關(guān)鍵思路
從這次DWC3控制器的泄漏修復(fù)中,我們可以提煉出嵌入式Linux驅(qū)動開發(fā)的通用經(jīng)驗(yàn):
1.優(yōu)先使用“托管類API”:內(nèi)核提供的devm_*(設(shè)備托管)、managed類API(如本次的device_create_managed_software_node)會自動管理資源生命周期,避免手動釋放遺漏——這是預(yù)防泄漏的最佳實(shí)踐,能減少80%以上的人為失誤。
2.場景化復(fù)現(xiàn)是關(guān)鍵:內(nèi)存泄漏需結(jié)合實(shí)際使用場景(如模式切換、驅(qū)動裝卸)設(shè)計(jì)復(fù)現(xiàn)腳本,通過/proc/meminfo觀察整體內(nèi)存變化,用slabtop定位具體泄漏的內(nèi)存slab(如dentry、software_node相關(guān)),精準(zhǔn)鎖定泄漏點(diǎn)。
3.重視上游補(bǔ)丁同步:本次修復(fù)的核心API(如debugfs_lookup_and_remove、托管軟件節(jié)點(diǎn))均來自Linux內(nèi)核上游(5.12 +版本),回溯上游補(bǔ)丁不僅能保證方案的穩(wěn)定性,還能減少后續(xù)內(nèi)核升級的適配成本——避免自己造輪子導(dǎo)致的兼容性問題。
補(bǔ)丁獲取與適配建議
若你的項(xiàng)目使用了USB DWC3控制器(尤其是RV1103B、RK3588等瑞芯微平臺),可按以下方式適配:
1.獲取補(bǔ)丁:補(bǔ)丁已提交至瑞芯微官方內(nèi)核倉庫(linux-rockchip),可直接提取上述代碼片段手動修改,或基于5.10 +內(nèi)核版本同步相關(guān)提交;
2.適配其他平臺:若使用非瑞芯微平臺(如高通、NXP),需確認(rèn)DWC3驅(qū)動版本——核心修改(debugfs替換、托管節(jié)點(diǎn))通用,但需調(diào)整平臺相關(guān)的設(shè)備節(jié)點(diǎn)(如usb_phy節(jié)點(diǎn)路徑);
3.測試驗(yàn)證:適配后務(wù)必執(zhí)行本文中的復(fù)現(xiàn)腳本,持續(xù)運(yùn)行至少4小時,確認(rèn)內(nèi)存無泄漏且功能正常。
內(nèi)存泄漏是嵌入式設(shè)備長期穩(wěn)定運(yùn)行的“隱形殺手”,尤其是USB這類高頻使用的外設(shè)。希望本次DWC3控制器的修復(fù)案例,能為大家提供實(shí)用的排查和解決思路,讓設(shè)備跑得更穩(wěn)、更久~
-
控制器
+關(guān)注
關(guān)注
114文章
17787瀏覽量
193068 -
嵌入式
+關(guān)注
關(guān)注
5198文章
20442瀏覽量
333961 -
usb
+關(guān)注
關(guān)注
60文章
8438瀏覽量
284434 -
內(nèi)存泄漏
+關(guān)注
關(guān)注
0文章
41瀏覽量
9518
發(fā)布評論請先 登錄
瑞芯微RK3588開發(fā)板RK3588 EVB和RK3588S EVB解讀
RK3588 PCB推薦疊層及阻抗設(shè)計(jì)
米爾RK3576和RK3588怎么選?-看這篇就夠了
RK3588 EVB開發(fā)板原理圖講解【八】 RK3588 power Tree
RK這2款旗艦芯片RK3588 PK RK3576,誰是最優(yōu)選
RK3399平臺上USB控制器和PHY的連接方式和配置說明
分享一種RK3588 USB芯片級DTSI的配置方法
RK3588與RK3588S在ARM陣列服務(wù)器上的應(yīng)用
【新品】工業(yè)級別!RK3588行業(yè)主機(jī)系列
rk3588和rk3588s的區(qū)別
RK3588與3588s的區(qū)別
RK3588與RK3399的區(qū)別
一文搞懂?RK3588 PCIe:從硬件資源到拆分配置?+?避坑指南(含腦圖)
【技術(shù)分享】RK3588如何搭建xenomai3+ethercat
解決USB DWC3控制器兩大內(nèi)存泄漏問題!基于RV1103B/RK3588的實(shí)戰(zhàn)補(bǔ)丁解析
評論