背景实验室里用的路由器是千兆的,但也仅限于有线,无线大概极限在四百兆,导致在传送数据时部分损坏。
损坏还是有点难绷的。丢几张还是小事,但因为我偷懒没写错误处理,后端直接崩了,队列里压了两百多个任务,某对应该一一对应的ID直接相差出200+
处理完问题后,我在想能不能搞个更快的网路,现在的情况下千兆也是勉强够用,保底2.5g才能满足要求,正好在旁边工控机拆出来一块万兆网卡 ,那就用它来负责单独一路传输吧
当时没拍全貌,芯片如图
需求Linux上使用:服务器系统是Ubuntu 自动分配IP:即插即用,不要手动设置 安装 WindowsLinux下我想都不用想绝对比Windows下麻烦,而且也不清楚好坏,所以先用Windows试了下,在官网 下载驱动(9710)直接安装即可
Linux刚刚下载的压缩包里有linux的驱动,理论上来说直接编译安装就行了,但问题是这个古董驱动适配的内核范围在2.6.32 - 4.15,服务器上是6.18,一番搜索后找到了acooks/tn40xx-driver
运行以下命令来安装。
1 2 3 4 5 6 7 8 9 10 11 sudo rmmod tn40xx sudo make clean sudo make MV88X3310=YES -j$(nproc ) sudo make install sudo depmod -a sudo modprobe tn40xx
在加载内核以后,可以通过sudo dmesg | grep -C 5 "tn40xx"来查询系统日志
发现弹出了PHY init failed。等等,什么是PHY?
PHY是Physical Layer的缩写,相对应的还有MAC(Media Access Control ),控制器。两者功能不同,同时还有发热量不同、电路性质不同等因素,故在工程上是模块化的。而为了可以热修复bug,物理层芯片的固件是没有固化在内部的,需要由驱动推流给芯片
而现在安装的这个驱动程序正是控制器的驱动程序,装上驱动后,电脑知道如何与控制器沟通,但控制器不知道为物理层芯片提供什么固件,导致出现这个问题
看了一眼旧驱动的Readme,里面有提到了这部分内容
1 2 3 4 5 6 7 8 9 10 11 12 13 Special notes for Marvell PHYs: =============================== This driver package does not include Marvell PHY firmware files. If you own a TN9210 or TN9710 NIC based on Marvell PHY, you will need to obtain the firmware file in .hdr format prior to compiling this driver For TN9210 (DeviceID 4024) please extract the file 88X3140-FW-R02-06-03.hdr from 88X3140-FW-R02-06-03.zip For TN9710P (DeviceID 4027) please obtain the file x3310fw_0_3_3_0_9374.hdr For TN9710Q (DeviceID 4527) please obtain the file e2010fw_0_3_3_0_9374.hdr If you are missing these exact files, the driver will compile without support for these PHYs ********************************* Important *************************************** xxd is required for compiling TN9210 and TN9710 drivers. On some distributions such as Fedora, it is not installed by default and build will fail. Please make sure xxd is installed prior to building the driver ***********************************************************************************
估计是版权问题,这破驱动也太不省心了,上哪去找这个老古董的hdr(Header File)啊
不过好在有大佬提供了这些文件:Problem with .hdr file versions · Issue #3 · acooks/tn40xx-driver ,我下载了x3310fw_0_3_10_0_10860.hdr这个版本,同时也有大佬提供了一个解包Windows驱动程序来得到固件的程序 ,我顺便也提取了一份9374版本的,但文件丢了🥲
放置到驱动程序同级目录下,调整Makefile里面的路径变量,随后再执行编译
Makefile:27 1 2 - MV88X3310_HDR := x3310fw_0_3_4_0_9445.hdr + MV88X3310_HDR := x3310fw_0_3_10_0_10860.hdr
重新下载驱动以后,发现还不行。那只能看一下是不是它驱动本身有问题了,在代码里定位到物理层的初始化函数
tn40.c 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 37 38 39 40 41 42 static enum PHY_TYPE bdx_phy_init (struct bdx_priv *priv) { struct pci_dev *pdev = priv->pdev; enum PHY_TYPE phy_type ; u32 phy_id; char *desc; phy_type = bdx_get_phy_by_id(pdev->vendor, pdev->device, pdev->subsystem_device); if (phy_type == PHY_TYPE_NA) return PHY_TYPE_NA; bdx_mdio_set_speed(priv->pBdxRegs, MDIO_SPEED_1MHZ); phy_id = bdx_mdio_scan_phy_id(priv); if (!priv->phy_mdio_port) return PHY_TYPE_NA; priv->phy_type = bdx_phy_register(priv, phy_id, &desc); if (priv->phy_type == PHY_TYPE_NA) { dev_info(&priv->pdev->dev, "Unsupported PHY ID=%X" , phy_id); return PHY_TYPE_NA; } if (phy_type != priv->phy_type) dev_info(&priv->pdev->dev, "SVID PHY type %u; MDIO scan Found %u\n" , phy_type, priv->phy_type); dev_info(&priv->pdev->dev, "PHY detected ID=%X - %s\n" , phy_id, desc); bdx_mdio_set_speed(priv->pBdxRegs, priv->phy_ops.mdio_speed); if (priv->phy_ops.mdio_reset(priv, 1 , priv->phy_type)) return PHY_TYPE_NA; return phy_type; }
可以看出来,初始化时分为多个阶段。首先根据PCI信息来获取phy_type,然后扫描MDIO总线获取与芯片型号对应的phy_id,随后根据phy_id再次查表来确定phy_type对不对的上,最后发送复位请求。整个流程有多处判断是否正常,若不正常则返回PHY_TYPE_NA,故可以以此判断究竟是在哪一步出的问题
考虑到搭一个完整的调试环境时间成本太大了,我直接代码里插了几行pr_info打印输出调试信息
tn40.c 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 static u32 bdx_mdio_scan_phy_id(struct bdx_priv *priv) { int i; u16 phy_id_hi, phy_id_lo; u16 phy_id_addr_hi = 0x0002; u16 phy_id_addr_lo = 0x0003; for (i = 0; i < 32; i++) { phy_id_hi = bdx_mdio_read(priv, 1, i, phy_id_addr_hi); + pr_info("MDIO SCAN: addr %d, read value: 0x%04x\n", i, phy_id_hi); if (phy_id_hi != 0xFFFF && phy_id_hi != 0) { phy_id_lo = bdx_mdio_read(priv, 1, i, phy_id_addr_lo); + pr_info("FOUND PHY at addr %d: HI=0x%04x, LO=0x%04x\n", i, phy_id_hi, phy_id_lo); break; } msleep(10); } if (i == 32) { dev_err(&priv->pdev->dev, "no PHY found\n"); return 0; } priv->phy_mdio_port = i; return phy_id_hi << 16 | phy_id_lo; }
日志如下:
1 2 3 4 5 6 7 [ 2316.710987] tn40xx: Driver unloaded [ 2322.327862] tn40xx: Tehuti Network Driver from https://github.com/acooks/tn40xx-driver, linux-6.7.y-1 [ 2322.327866] tn40xx: Supported phys : MV88X3310 [ 2322.328082] tn40xx 0000:07:00.0: srom 0x0 HWver 16 build 0 lane# 4 max_pl 0x1 mrrs 0x2 [ 2322.436830] tn40xx: MDIO SCAN: addr 0, read value: 0x002b [ 2322.436990] tn40xx: FOUND PHY at addr 0: HI=0x002b, LO=0x09ab [ 2322.436998] tn40xx 0000:07:00.0: PHY init failed
可以看到,能够正常扫描到物理层芯片,且MDIO总线物理地址是 0
那不是巧了吗,bdx_phy_init里有一行if (!priv->phy_mdio_port),直接就把这个当成错误处理掉了。而根据IEEE 802.3 标准 ,MDIO总线的PHY地址段是 0 - 31,0是一个合法的地址。源代码在遍历32个地址仍然找不到PHY时,会将地址置为0,相当于非0才正常。旧驱动中的实现是置为-1,在初始化时判断地址是否大于等于0。
旧驱动
tn40.c 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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 int bdx_mdio_look_for_phy (struct bdx_priv *priv, int port) { int phy_id, i; int rVal = -1 ; i=port; setMDIOSpeed(priv, MDIO_SPEED_1MHZ); phy_id = bdx_mdio_read(priv, 1 , i, 0x0002 ); phy_id &=0xFFFF ; for (i = 0 ; i < 32 ; i++) { msleep(10 ); DBG("LOOK FOR PHY: port=0x%x\n" ,i); phy_id = bdx_mdio_read(priv, 1 , i, 0x0002 ); phy_id &=0xFFFF ; if (phy_id!=0xFFFF && phy_id!= 0 ) { rVal = i; break ; } } if (rVal == -1 ) { ERR("PHY not found\n" ); } return rVal; } ...省略... static int __init bdx_mdio_phy_search (struct bdx_priv *priv, void __iomem * regs, int *port_t , unsigned short *phy_t ) { int i, phy_id; char *s; if (bdx_force_no_phy_mode) { ERR("Forced NO PHY mode\n" ); i = 0 ; } else { i = bdx_mdio_look_for_phy(priv,*port_t ); if (i >= 0 ) { *port_t = i; phy_id = bdx_mdio_read(priv, 1 , *port_t , 0x0002 ); i = phy_id << 16 ; phy_id = bdx_mdio_read(priv, 1 , *port_t , 0x0003 ); phy_id &=0xFFFF ; i |= phy_id; } } ...
同时,下面还有一行if (priv->phy_ops.mdio_reset(priv, 1, priv->phy_type)),写死了向地址1发送复位请求,也是离谱,改成if (priv->phy_ops.mdio_reset(priv, priv->phy_mdio_port, priv->phy_type))
完整更改
tn40.c 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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 static u32 bdx_mdio_scan_phy_id(struct bdx_priv *priv) { int i; u16 phy_id_hi, phy_id_lo; u16 phy_id_addr_hi = 0x0002; u16 phy_id_addr_lo = 0x0003; for (i = 0; i < 32; i++) { phy_id_hi = bdx_mdio_read(priv, 1, i, phy_id_addr_hi); if (phy_id_hi != 0xFFFF && phy_id_hi != 0) { phy_id_lo = bdx_mdio_read(priv, 1, i, phy_id_addr_lo); break; } msleep(10); } if (i == 32) { dev_err(&priv->pdev->dev, "no PHY found\n"); - return 0; + return -1; } priv->phy_mdio_port = i; return phy_id_hi << 16 | phy_id_lo; } ...省略... static enum PHY_TYPE bdx_phy_init(struct bdx_priv *priv) { struct pci_dev *pdev = priv->pdev; enum PHY_TYPE phy_type; u32 phy_id; char *desc; phy_type = bdx_get_phy_by_id(pdev->vendor, pdev->device, pdev->subsystem_device); if (phy_type == PHY_TYPE_NA) return PHY_TYPE_NA; /* NIC definition has no PHY. */ bdx_mdio_set_speed(priv->pBdxRegs, MDIO_SPEED_1MHZ); phy_id = bdx_mdio_scan_phy_id(priv); /* set phy_mdio_port */ - if (!priv->phy_mdio_port) + if (priv->phy_mdio_port < 0) return PHY_TYPE_NA; /* No PHY detected on MDIO bus. */ /* register the PHY-specific callbacks */ priv->phy_type = bdx_phy_register(priv, phy_id, &desc); if (priv->phy_type == PHY_TYPE_NA) { dev_info(&priv->pdev->dev, "Unsupported PHY ID=%X", phy_id); return PHY_TYPE_NA; } if (phy_type != priv->phy_type) dev_info(&priv->pdev->dev, "SVID PHY type %u; MDIO scan Found %u\n", phy_type, priv->phy_type); dev_info(&priv->pdev->dev, "PHY detected ID=%X - %s\n", phy_id, desc); bdx_mdio_set_speed(priv->pBdxRegs, priv->phy_ops.mdio_speed); - if (priv->phy_ops.mdio_reset(priv, 1, priv->phy_type)) + if (priv->phy_ops.mdio_reset(priv, priv->phy_mdio_port, priv->phy_type)) return PHY_TYPE_NA; return phy_type; }
再次编译后可以正常驱动
配置DHCP我这里使用nmcli配置网络,连上网线后网卡状态不再是unavailable(物理层连接后才可配置,无法逻辑启用),可以配置连接
1 2 3 4 5 6 7 8 9 10 sudo nmcli connection modify "Wired connection 1" \ ipv4.method shared \ ipv4.addresses 10.42.0.1/24 \ connection.autoconnect yes \ ipv4.route-metric 200 sudo nmcli connection modify "Wired connection 1" connection.id "shared-net" sudo nmcli connection up "Wired connection 1"
同时,在防火墙里放行相关端口
或者可以直接配置为本地链路模式
1 sudo nmcli connection modify "Wired connection 2" ipv4.method link-local sudo nmcli connection up "Wired connection 2"
测试笔记本(螃蟹2.5G网卡)使用网线直连,笔记本上传到服务器可以跑满 2.5G 带宽,下行跑不满,应该是笔记本问题
在实际采集时观察到大量错误
嗯。。刚买的超六类线,应该不是线的问题;一个合格的厂商应该不至于做出来这么烂的产品,所以可能还是驱动某些逻辑问题,根据报错码0x10可以定位到属于TCP checksum error,具体的可以看AI解析,因为我复现不出来了,没法调试🥲
如果碰到类似问题,或许可以尝试关闭 Rx/Tx 校验和卸载
1 2 3 4 sudo ethtool -K <网卡名> rx off tx off tso off gso off sudo ethtool -S <网卡名>
顺便吐槽一下鸡哥这个网口:
不过无所谓,鸽了半年才发出来,现在已经不用我们这边协助采集数据了,实在要用也可以限制协商速率为1G,再多网卡分流