fix: NULL-guard bus teardown and clear drvdata after cleanup completes

Review-driven rework of the teardown lifetime (shenmintao/aic8800d80#103):

- aicwf_bus_deinit(): keep the entry NULL guard (sequential double-deinit
  on probe-failure paths), but move dev_set_drvdata(dev, NULL) to the END
  of completed cleanup — after URB cancel, platform/rx/cmd teardown and the
  kthread_stop() join of bustx_thread. The previous placement (right after
  bus_stop) opened a real race: aicwf_usb_bus_txdata() re-reads drvdata and
  runs from bustx_thread, which is joined only later, and the TX-path state
  check is not a synchronization point. With the new ordering every cleanup
  callback and TX reader observes a non-NULL pointer throughout.
- aicwf_usb_bus_stop(): NULL-check bus_if before dereferencing
  bus_if->bus_priv.usb (defense in depth; historical host oops
  RIP aicwf_usb_bus_stop+0x11 on probe-failure rollback).

Verification (isolated VFIO lab, one 2357:0147 unit, sustained ping -f):
a test-only fault-injection build (not included) reaches the repeated-deinit
path and prints "already deinitialized, skip" with BUG=0; missing-firmware
init failure runs one clean deinit; physical unplug x8, sysfs unbind and
authorized cold cycles, disconnect during scanning and during connection
establishment, ifdown/up x5, rmmod/insmod x3 -> BUG=0 throughout (only the
pre-existing WARNs at rwnx_main.c:1529 and rwnx_cfg80211_disconnect:3328).
This commit is contained in:
xuxinchun1003 committed 2026-10-07 07:42:31 +08:00
1 parent b7a476c517
commit 089ee0bf87
2 files changed
+30 -1

No files matched your search

@@ -210,6 +210,15 @@ void aicwf_bus_deinit(struct device *dev)
AICWFDBG(LOGINFO, "%s Enter\r\n", __func__); AICWFDBG(LOGINFO, "%s Enter\r\n", __func__);
bus_if = dev_get_drvdata(dev); bus_if = dev_get_drvdata(dev);
/* 入口判空:本函数在探测失败路径上会被调用**两次**
* aicwf_bus_init() 的 `fail:` 先调一次,随后 aicwf_usb_probe() 的
* `out_free_bus:` 再调一次(用局部 bus_if 做 kfree,不依赖 drvdata)。
* 第二次进入时 drvdata 已在**上一次调用的末尾**被清空 → 必然命中本判空。
* 若不清空,第二次会对已完整拆解的对象重复执行整套 teardown。 */
if (!bus_if) {
AICWFDBG(LOGINFO, "%s: already deinitialized, skip\n", __func__);
return;
}
aicwf_bus_stop(bus_if); aicwf_bus_stop(bus_if);
#ifdef AICWF_USB_SUPPORT #ifdef AICWF_USB_SUPPORT
@@ -260,6 +269,20 @@ void aicwf_bus_deinit(struct device *dev)
tasklet_kill(&usb->xmit_tasklet); tasklet_kill(&usb->xmit_tasklet);
#endif #endif
/* 清空 drvdata:全部拆解完成之后、对象由调用方释放之前。
* (评审 shenmintao/aic8800d80#103 指出的顺序问题:此前清空点位于
* bus_stop 之后、URB/platform/rx/cmd 拆解与 TX 线程 join 之前。)
* 1) 顺序双 deinit:第二次调用靠上方入口判空在本行之后必然 skip;
* 2) 过早清空会伤害仍在运行的读者——TX 链
* usb_bustx_thread → aicwf_bus_txdata(usbdev->bus_if) →
* ops->txdata = aicwf_usb_bus_txdata()(usb.c 在该处重读
* dev_get_drvdata)仅靠 state 检查做 check-then-act、无锁,
* 且该线程直到上方 kthread_stop(bustx_thread) 才被 join——
* 必须先 join 再清;
* 3) cmd/rx/platform 拆解与 rwnx_close() 等读者在本行之前全程
* 看到非空 drvdata,行为与未打补丁树一致。 */
dev_set_drvdata(dev, NULL);
AICWFDBG(LOGINFO, "%s Exit \n", __func__); AICWFDBG(LOGINFO, "%s Exit \n", __func__);
} }
+7 -1
View File
@@ -1946,9 +1946,15 @@ void aicwf_usb_cancel_all_urbs(struct aic_usb_dev *usb_dev){
static void aicwf_usb_bus_stop(struct device *dev) static void aicwf_usb_bus_stop(struct device *dev)
{ {
struct aicwf_bus *bus_if = dev_get_drvdata(dev); struct aicwf_bus *bus_if = dev_get_drvdata(dev);
struct aic_usb_dev *usb_dev = bus_if->bus_priv.usb; struct aic_usb_dev *usb_dev;
AICWFDBG(LOGINFO, "%s\r\n", __func__); AICWFDBG(LOGINFO, "%s\r\n", __func__);
/* 正常时序下 aicwf_bus_init() 已 set_drvdata、且 aicwf_bus_deinit()
* 调到本函数时 drvdata 尚未清空(清空在 deinit 末尾),故此处应非空;
* 判空为纵深防御:覆盖 probe 早期失败(set_drvdata 之前)等异常来源。 */
if (bus_if == NULL)
return;
usb_dev = bus_if->bus_priv.usb;
if (usb_dev == NULL) if (usb_dev == NULL)
return; return;