树莓派 5Ghz WIFI 频繁断开连接

First Post:

Last Update:

参考文章:树莓派CM4 wifi频繁断开连接

问题描述

Pi4B 在使用内置 5GHz WIFI 网卡连接小米路由器时,网络会周期性断开连接,导致 SSH 经常卡顿掉线、文件传输容易失败等问题。

问题原因

如果路由器本身使能了 ieee80211d=1,那么 WIFI 模块的驱动会通过 Country IE 重新获取国家码,然后重设 WIFI 模块的国家码。这属于正常行为,但树莓派核心板上 WIFI 模块在国家码设置为 CN 时,不支持 80MHz 信道宽度和 36、40、44、48 等 5GHz 信道,所以会断开重新连接 AP。因为 wpa_supplicant 的配置文件设置的国家码为 US,所以 WIFI 模块连接上 AP 以后如果还是工作在 80MHz 信道宽度,会又重复上面断开连接的情况。如果 WIFI 模块连接上 AP 以后工作在 40MHz 信道宽度(如上面所示),那么可以稳定工作在 40MHz 信道宽度,不会再断开连接。而小米路由器默认频宽为 80MHz,即会出现上述情况。

这也可以解释设置国家码为 CN 时,无法搜索到小米 5GHz WIFI 的问题。

解决方案

方法一:修改路由器设置

将国家码设置为 CN,并将路由器设置为 40MHz 频宽。

该方法较为容易实现,但无法使用 80MHz 频宽和 36-48 信道。

方法二:修改内核模块

修改并重新编译内核模块,使国家码保持在 US

该方法既能解决频繁掉线问题,也能使用 80MHz 频宽,但配置花费更多时间和精力。

注意:此修改强制让 WIFI 模块本身固定工作在 US 国家码下。即使系统层面通过 iw reg set CN 把国家码设为 CN,WIFI 驱动内部仍为 US,从而提高 WIFI 模块的兼容性。
这将会导致系统里通过 iw reg get 得到的信息并不一定等于 WIFI 模块实际工作的信息。

注意:不同硬件在配置过程中需要的参数不同,请务必先阅读官方文档
以下过程以 Pi4B,arm64 为例。

获取源代码

获取当前内核版本:

1
uname -r

随后从官方的源代码仓库中克隆代码。需要根据内核版本选择合适的分支:

1
git clone --depth=1 --branch <分支名> https://github.com/raspberrypi/linux

--depth=1 表示限制 clone 的深度,不会下载 Git 协作的历史记录,这样可以大大加快克隆的速度。

修改内核模块

修改 drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c 文件。

添加模块参数 regulatory_domain_force_us

1
2
3
static int regulatory_domain_force_us = 0; 
module_param(regulatory_domain_force_us, int, S_IRUGO);
MODULE_PARM_DESC(regulatory_domain_force_us, "Force set regulatory domain to US.");

修改 static void brcmf_cfg80211_reg_notifier(struct wiphy *wiphy, struct regulatory_request *req) 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
static void brcmf_cfg80211_reg_notifier(struct wiphy *wiphy, struct regulatory_request *req)
{
struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy);
struct brcmf_if *ifp = brcmf_get_ifp(cfg->pub, 0);
struct brcmf_pub *drvr = cfg->pub;
struct brcmf_fil_country_le ccreq;
char *alpha2;
s32 err;
int i;
+ char us_reg_code[3] = "US";

err = brcmf_fil_iovar_data_get(ifp, "country", &ccreq, sizeof(ccreq));
if (err) {
bphy_err(drvr, "Country code iovar returned err = %d\n", err);
return;
}

/* The country code gets set to "00" by default at boot - substitute
* any saved ccode from the nvram file unless there is a valid code
* already set.
*/
- alpha2 = req->alpha2;
+ if (regulatory_domain_force_us)
+ alpha2 = us_reg_code;
+ else
+ alpha2 = req->alpha2;

if (alpha2[0] == '0' && alpha2[1] == '0') {
extern char saved_ccode[2];

if ((isupper(ccreq.country_abbrev[0]) &&
isupper(ccreq.country_abbrev[1])) ||
!saved_ccode[0])
return;
alpha2 = saved_ccode;
pr_debug("brcmfmac: substituting saved ccode %c%c\n",

编译内核模块

进行编译前的配置:

1
2
3
cd linux
KERNEL=kernel8
make bcm2711_defconfig

编译内核模块,使用 4 个线程:

1
make -j4 modules

替换内核模块

为了方便,这里采用直接替换文件的方式替换内核模块。理论上也可以移除再重新安装模块。

注意:修改系统前务必保证自己有办法恢复!

需要用到的编译好的内核模块文件位于:

1
drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko

要替换的文件一般位于:

1
/usr/lib/modules/您的内核版本/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko

您的内核版本 指代第一步时获取的内核版本。
如果找不到该文件,可以使用如下命令进行搜索:

1
sudo find / -name brcmfmac.ko

备份原模块文件:

1
sudo mv /usr/lib/modules/您的内核版本/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko /usr/lib/modules/您的内核版本/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko.bak

复制新模块文件:

1
sudo cp drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko /usr/lib/modules/您的内核版本/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko

添加 /etc/modprobe.d/brcmfmac.conf 文件,内容如下:

1
options brcmfmac regulatory_domain_force_us=1

在 WIFI 驱动 brcmfmac.ko 加载时即会传入模块参数 regulatory_domain_force_us=1,作用于修改过的代码并强制指定国家码为 US

重新启动树莓派:

1
sudo shutdown -r now

可通过 cat /sys/module/brcmfmac/parameters/regulatory_domain_force_us 查看 regulatory_domain_force_us 字段的值。若有该值且为 1,则表示修改成功。