– delphij
bce(4) 是一款常见的千兆网卡。我们在使用过程中发现它在高负载、多处理器环境下会出现 watchdog timeout 的现象。查看代码发现它使用的是旧式的 if_watchdog KPI。由于这个变动对于编写其他网卡驱动程序具有一定的参考意义,因此我把具体的修改过程写出来。
传统的旧式 if_watchdog KPI 在网卡注册的过程中会由网卡驱动注册一个回调函数 if_watchdog,它是一个 void f(struct ifnet *ifp) 函数指针,会在内核发现网卡出现长时间不产生 tx/rx 中断时调用。旧式 if_watchdog 回调时并不会上 softclock 的锁(因为内核并不知道该上哪个锁)。传统回调函数需要做的事情大致如下:
与此相反,新式的 watchdog KPI 写法,不再由内核统一进行 watchdog 计数,而是将这一工作交给网卡驱动来进行。也就是说,网卡需要在自己的 softclock 结构中维护一个计数(为了一致起见,我们通常将其命名为 watchdog_timer),而不再维护先前在 ifnet 结构中的 if_timer 计数(这个计数是由内核的旧式 if_watchdog KPI 使用的)。对于 bce(4) 而言,这主要是一个 s/ifp→if_timer/sc→watchdog_timer/g 的替换。
新式的 watchdog 需要挂在网卡的 tick callout 上。因此,我们在 bce_tick_locked() 函数进行 callout_reset 之前调用一次 watchdog 函数:
/* Check that chip hasn't hang. */ bce_watchdog(sc);
注意,由于内核不再帮我们维护 if_timer 计数,因此 watchdog 函数必须自行维护:
if (sc->watchdog_timer == 0 || --sc->watchdog_timer) return;
Jack Vagel 在 Intel 的 em(4) 网卡驱动中新增了一个特性,即,如果网卡收到了停止帧,则此时 watchdog timeout 不对网卡进行 reset。我也为 bce(4) 驱动增加了这项能力:
/* * If we are in this routine because of pause frames, then * don't reset the hardware. */ if (REG_RD(sc, BCE_EMAC_TX_STATUS) & BCE_EMAC_TX_STATUS_XOFFED) return;
修改之后的 watchdog 函数:
bce_watchdog(struct bce_softc *sc) { BCE_LOCK_ASSERT(sc); if (sc->watchdog_timer == 0 || --sc->watchdog_timer) return; /* * If we are in this routine because of pause frames, then * don't reset the hardware. */ if (REG_RD(sc, BCE_EMAC_TX_STATUS) & BCE_EMAC_TX_STATUS_XOFFED) return; BCE_PRINTF("%s(%d): Watchdog timeout occurred, resetting!\n", __FILE__, __LINE__); DBRUN(BCE_VERBOSE_SEND, bce_dump_driver_state(sc); bce_dump_status_block(sc)); /* DBRUN(BCE_FATAL, bce_breakpoint(sc)); */ sc->bce_ifp->if_drv_flags &= ~IFF_DRV_RUNNING; bce_init_locked(sc); sc->bce_ifp->if_oerrors++; }
修改过程将 bce_tick_locked 改为了 bce_tick,同时对这一 callout 进行了修改以适应这一变动,也就是将 callout_init(&sc→bce_stat_ch, CALLOUT_MPSAFE); 改成了 callout_init_mtx(&sc→bce_stat_ch, &sc→bce_mtx, 0);。
具体变动请参见 sys/dev/bce/if_bce.c:1.23 和 sys/dev/bce/if_bcereg.h:1.11。