简介:提供开箱即用的IEEE 802.11a/g/n Wi-Fi基带FPGA实现,完整对接Linux内核mac80211协议栈,适用于软件定义无线电(SDR)开发与教学实验。Verilog代码覆盖OFDM物理层收发、MAC接口桥接、时钟域同步及射频子板控制逻辑,已验证适配Xilinx ZC706、ZCU102、ZedBoard和AntSDR等主流硬件平台。资源包内置各平台预编译.bit位流文件、ILA调试.ltx配置、Vivado工程一键生成脚本(create_vivado_proj.sh)、IP核打包工具(package_ip.tcl)、HDF/SDK协同更新脚本(sdk_update.sh),以及针对ADI FMCOMMS2/3/4、ADRV9361/9364等射频子板的专用驱动适配层。所有模块遵循openwifi项目结构规范,可直接配合上游Linux驱动和用户空间工具(如iw、hostapd)运行。使用前需设置BOARD_NAME环境变量指定目标板卡,详细引脚约束、时序约束、软硬件联调步骤、调试技巧均在对应板级目录下的README.md中说明。许可证为AGPLv3,部分第三方IP需按其单独条款确认授权。
1. 项目概述:这不是“又一个Wi-Fi软核”,而是一套真正能跑通802.11协议栈的FPGA基带系统
你有没有试过在FPGA上实现一个Wi-Fi收发器?不是那种只发几个OFDM符号、用MATLAB画个星座图就收工的演示工程,而是真刀真枪地让Linux内核认出它是一块无线网卡,能ifconfig wlan0 up、能iw dev wlan0 scan、能连上家用路由器、甚至能跑iperf3测吞吐——没错,openwifi就是干这个的。它不是教学玩具,也不是实验室里的半成品,而是一套经过多平台实测、可直接用于SDR原型开发、协议研究、安全分析和嵌入式Wi-Fi网关构建的完整基带方案。关键词里那个mac80211,是它的灵魂所在:这意味着它不自己实现MAC层逻辑,而是把物理层(PHY)的复杂性全部卸载到FPGA上,再通过标准的DMA+寄存器接口,把干净的数据帧交给Linux内核的mac80211子系统去调度、加密、重传、管理关联状态——你拿到的,是一块被内核完全信任的“真实”Wi-Fi设备,而不是一个需要你写一整套用户态协议栈的裸硬件。
我第一次在ZC706上跑通hostapd建热点时,ping通手机的那一刻,比当年第一次点亮LED还激动。为什么?因为ZC706这类7系列FPGA资源有限,传统Wi-Fi基带动辄吃掉上万LUT,而openwifi通过精巧的状态机拆分、流水线级数优化和时钟域交叉裁剪,在Zynq-7000上硬生生塞进了完整的802.11a/g/n OFDM PHY(支持20MHz带宽、最高64-QAM、MCS7),同时留出足够空间给MAC桥接逻辑和ADI射频控制。它用Verilog写的不是“模块”,而是“接口契约”:每一行代码都在回答一个问题——如何让FPGA和Linux内核之间,像两个老同事一样自然对话?比如,它把802.11帧的MPDU头解析、FCS校验、ACK生成这些本该在软件里做的活,全搬进FPGA;但又绝不越界去管BSS管理、Beacon定时、PS轮询这些mac80211的职责。这种“边界感”,正是它能稳定运行的关键。它面向的不是FPGA新手,而是那些已经用Vivado调过AD9361、写过AXI DMA驱动、被Linux内核编译折磨过的工程师——你不需要从零学Verilog语法,但必须理解AXI-Stream时序、DMA描述符结构、以及mac80211的ieee80211_hw回调机制。它提供的不是“一键烧录”,而是“一套可验证的契约范式”:FPGA位流是契约的物理载体,mac80211驱动是契约的软件接口,而那一堆.tcl脚本,就是帮你把这份契约在不同板卡上精准复刻的施工图纸。
2. 整体架构与设计哲学:为什么选择“FPGA PHY + Linux MAC”这条少有人走的路?
2.1 分层解耦:把最硬的骨头留给FPGA,最活的部分交给内核
openwifi的架构核心,是物理层全卸载(Full PHY Offload)。这听起来很激进,但恰恰是它区别于其他开源Wi-Fi项目(如gnuradio-wifi、sdrangel插件)的根本。我们来对比一下三种常见模式:
| 方案类型 | PHY实现位置 | MAC实现位置 | 实时性 | 吞吐瓶颈 | 典型用途 |
|---|---|---|---|---|---|
| 纯软件SDR(GNU Radio) | CPU(浮点运算) | CPU(Python/C++) | 差(毫秒级延迟) | CPU主频与内存带宽 | 协议学习、信号分析 |
| 混合方案(如LimeSDR+hostapd) | FPGA(简化PHY,仅调制解调) | CPU(完整mac80211) | 中(微秒级中断) | PCIe带宽与驱动效率 | 轻量AP、测试床 |
| openwifi模式 | FPGA(完整PHY+部分MAC功能) | Linux内核(标准mac80211) | 高(纳秒级时钟同步) | AXI总线带宽与DMA效率 | 生产级SDR网关、协议一致性测试、安全研究 |
关键差异在于“部分MAC功能”的定义。openwifi的FPGA并不实现802.11 MAC状态机(如Association、Authentication),但它实现了所有对实时性要求极高的底层操作:
- 精确的OFDM符号定时同步(基于短训练序列STF的粗同步 + 长训练序列LTF的细同步)
- 载波频率偏移(CFO)补偿(在FFT后频域进行相位旋转,精度达0.1ppm)
- 信道估计与均衡(利用LTF计算信道冲激响应,做MMSE均衡)
- 前导码检测与帧起始(SFD)锁定(硬件状态机,响应延迟<100ns)
- 快速ACK/CTS生成与发送(收到MPDU后,硬件自动构造并发射ACK,无需CPU干预)
这些操作如果放在CPU上做,哪怕用SIMD指令优化,也会因操作系统调度、缓存失效、中断延迟而引入不可控抖动,导致Wi-Fi帧丢失率飙升。而FPGA用纯组合逻辑+寄存器实现,每一个周期都确定可控。反过来,像Beacon帧的周期性广播、Probe Request/Response的处理、RSN握手密钥协商这些,交给mac80211做反而更高效——内核有成熟的定时器框架、加密API(crypto API)、以及完善的WPA/WPA2/WPA3状态机。这种分工,不是偷懒,而是把每一块砖都砌在它最该在的位置上。
2.2 多平台适配的底层逻辑:抽象板级差异,固化接口契约
看到ZC706、ZCU102、ZedBoard、AntSDR这些名字,你可能会疑惑:它们的PS端(ARM处理器)架构不同(Zynq-7000 vs Zynq UltraScale+)、PL端资源差异巨大(ZC706约10万LUT,ZCU102超百万LUT)、射频子板接口也五花八门(FMCOMMS2用FMC LPC,ADRV9364用JESD204B)。openwifi是怎么做到“一套代码,多处编译”的?答案藏在它的三层抽象模型里:
-
硬件抽象层(HAL):由
board_def.v和一系列*_tcl脚本构成。board_def.v不是具体电路,而是一个参数化宏定义文件,它声明了:
-BOARD_CLK_FREQ_MHZ:主参考时钟频率(ZC706是200MHz,ZCU102可能是300MHz)
-RF_INTERFACE_TYPE:射频接口类型(ADI_FMCOMMS,ADI_ADRV936x,ANTSDR)
-AXI_DATA_WIDTH:AXI总线数据宽度(32-bit for Zynq-7, 64-bit for UltraScale+)
-PHY_RX_FIFO_DEPTH/PHY_TX_FIFO_DEPTH:收发FIFO深度(根据PL资源动态调整) -
IP核封装层(IP Repo):
package_ip.tcl脚本的核心任务,是读取board_def.v中的宏,自动生成符合Xilinx IP Catalog规范的.xci文件。例如,当RF_INTERFACE_TYPE == ADI_ADRV936x时,它会:
- 自动实例化axi_adrv9361_rx和axi_adrv9361_txIP(来自analogdevicesinc/hdl仓库)
- 将openwifi_phy_rx的AXI-Stream输出,连接到axi_adrv9361_tx的s_axis端口
- 将axi_adrv9361_rx的m_axis端口,连接到openwifi_phy_tx的AXI-Stream输入
- 生成正确的时钟约束(adrv9361_rx_clk必须与openwifi_phy_rx_clk同源,否则跨时钟域需异步FIFO) -
软件接口层(HDF/SDK协同):
sdk_update.sh脚本解决的是“FPGA位流变了,Linux驱动怎么知道?”的问题。它会:
- 解析Vivado生成的.hdf文件,提取PL端AXI外设的基地址、中断号、DMA通道号
- 自动更新Linux内核驱动源码中的#define OPENWIFI_BASE_ADDR 0x43C00000
- 修改设备树(DTS)中interrupts = <0 89 4>为实际分配的GIC中断号
- 重新编译驱动模块(openwifi.ko),确保probe()函数能正确映射寄存器空间
这三层抽象,让开发者面对新板卡时,工作量被压缩到极致:你只需要在board_def.v里填几行参数,运行create_vivado_proj.sh,然后make驱动——剩下的,全是脚本自动化完成。它不追求“零配置”,而是追求“配置即文档”:每一个BOARD_NAME对应的目录下,README.md里写的不是“请参考官方手册”,而是“ZC706的CLK_OUT1_N/P必须接到FMCOMMS2的CLK_IN_N/P,否则AD9361 PLL无法锁定,现象是dmesg报ad9361: clock not locked”。这才是工程师真正需要的文档。
2.3 许可证选择的深意:AGPLv3不是枷锁,而是协作的锚点
看到AGPLv3许可证,很多硬件工程师第一反应是皱眉:“又要搞开源合规?”但openwifi选它,恰恰是深思熟虑的结果。AGPLv3的核心条款是:如果你修改了openwifi的Verilog代码,并将其部署在联网设备上(比如一台基于ZCU102的Wi-Fi网关),那么你必须向用户提供修改后的源代码。这看似严苛,实则精准打击了两类行为:
- 闭源商用陷阱:某公司拿openwifi位流烧进产品,卖高价,却不回馈任何改进(比如修复了ZCU102在高温下的时序违例)。AGPLv3强制其开源补丁,社区能立刻复现、验证、合并。
- 碎片化分裂:没有许可证约束,各厂商可能各自fork,添加私有射频控制逻辑,导致生态割裂。AGPLv3像一条无形的绳子,把所有改进拉回主线。
当然,它也做了务实妥协:项目明确声明“部分第三方IP需单独确认授权条款”。比如,ADI的axi_adrv9361 IP核,其源码受Analog Devices的BSD-like许可证保护,你可以自由使用,但不能反向工程其内部RTL。openwifi的Verilog只调用其顶层接口(s_axi, m_axis, aclk),就像你调用一个黑盒芯片的Datasheet一样。这种“接口开源,实现可选”的策略,既保障了核心创新的开放性,又尊重了商业IP的现实存在。它不是要消灭闭源,而是划清一条清晰的协作边界:你可以在openwifi之上构建专有应用(比如定制化的Wi-Fi嗅探固件),但你不能把openwifi本身变成你的私有资产。
3. 核心模块解析与实操要点:从OFDM符号生成到mac80211注册
3.1 OFDM物理层:如何在FPGA里“手搓”一个802.11a/g/n PHY?
openwifi的PHY模块(openwifi_phy_rx/openwifi_phy_tx)是整个项目的皇冠。它不像MATLAB里调用wlanWaveformGenerator那么简单,而是从香农公式出发,一行行Verilog实现。我们以接收路径(RX)为例,拆解其关键子模块:
3.1.1 前导码检测与同步(Preamble Detection & Synchronization)
这是Wi-Fi链路建立的第一步,也是最容易失败的环节。openwifi采用两级检测:
- 粗同步(Coarse Sync):基于短训练序列(STF)的互相关。STF由10个重复的[+1,+1,+1,-1,+1,+1,-1,+1]组成(共80个采样点)。FPGA用一个80-tap的FIR滤波器(系数即STF序列)对ADC采样流做滑动相关。当相关峰值超过阈值(THRESHOLD_COARSE = 0.7 * MAX_CORR),即触发粗同步,记录大致符号起始位置。
- 细同步(Fine Sync):基于长训练序列(LTF)的循环前缀(CP)相关。LTF是52个子载波的BPSK调制,且前后两段完全相同。FPGA计算corr = sum(abs(x[i] - x[i+32]))(i从0到31),当corr最小(即两段最相似)时,即为精确符号边界。此方法抗噪声能力远强于单次相关。
提示:在ZC706上,粗同步模块消耗约1200 LUT,细同步消耗约800 LUT。若你发现扫描时
iw dev wlan0 scan返回空结果,第一件事就是用ILA抓rx_preamble_det_valid信号——如果它永远不拉高,说明ADC采样率没对准(ZC706必须严格20MHz,误差>100ppm就会失锁)。
3.1.2 FFT/IFFT与信道估计(FFT/IFFT & Channel Estimation)
openwifi使用Xilinx的xfft_v9_1 IP核,但做了关键定制:
- FFT点数:128点(对应20MHz带宽,子载波间隔312.5kHz)
- 输入格式:Q15定点(1位符号+15位小数),避免浮点开销
- 缩放模式:Block Floating Point,防止中间计算溢出
信道估计利用LTF已知的导频子载波(-21,-7,+7,+21)。FPGA将FFT输出的这4个位置的复数值,与理想导频值(+1+j0, -1+j0, +1+j0, -1+j0)做除法,得到4个信道响应点。再通过线性插值(Linear Interpolation)填充其余48个数据子载波的响应,最后用MMSE准则计算均衡系数:W(k) = H*(k) / (|H(k)|² + σ²),其中σ²是噪声方差,由空子载波(DC、guard bands)的平均功率估算。
注意:
σ²的估算极其关键。openwifi在openwifi_phy_rx.v里专门有一个noise_power_estimator模块,它持续监控FFT输出中索引为{0, 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-QAM)误码率飙升;估算偏低,则残留ISI严重。实测中,ZC706在室温下σ²典型值为12'h3A0(十进制928)。
3.1.3 解调与解码(Demodulation & Decoding)
解调采用硬判决(Hard Decision),而非软判决(Soft Decision),这是资源与性能的权衡:
- QPSK/16-QAM/64-QAM解调:查表法(LUT-based)。FPGA预存一个256x256的ROM(Q15格式),输入为归一化后的IQ值(iq_in_real, iq_in_imag),输出为4-bit或6-bit的比特组。例如,64-QAM下,(0.8, 0.2)映射到6'b101101。
- 卷积码解码:采用Viterbi算法,约束长度K=7。openwifi使用Xilinx的convol_encoder_v10_0 IP核的逆过程,但手动实现了traceback逻辑,避免IP核的固定延迟。解码后输出的比特流,还需经过去交织(Deinterleaving)——用一个128x8的RAM做行列转置,打乱突发错误。
实操心得:在调试高吞吐场景(如
iperf3 -c 192.168.1.100 -t 30 -i 1)时,如果/sys/class/net/wlan0/statistics/tx_errors持续增长,大概率是解调LUT的量化误差累积。此时不要急着改算法,先检查openwifi_phy_tx.v里的tx_gain_ctrl模块——它负责动态调整DAC输出幅度。ZC706的AD9361默认增益是-10dB,但openwifi的tx_gain_ctrl会根据tx_power_dbm寄存器值自动补偿。若寄存器没写对(比如误写成0xFF),DAC会饱和,导致TX信号畸变,RX端解调失败。用devmem2 0x43C00010 w 0x0000001E(设置18dBm)是ZC706的常用值。
3.2 MAC桥接层:如何让FPGA和Linux内核“说同一种语言”?
PHY是肌肉,MAC桥接层(openwifi_mac_bridge)就是神经系统。它定义了FPGA与mac80211驱动通信的“宪法”。
3.2.1 AXI-Stream DMA接口:数据搬运的高速公路
openwifi采用双缓冲DMA(Dual-Buffer DMA)架构:
- RX路径:openwifi_phy_rx输出AXI-Stream数据包(含802.11 MPDU头+Payload+FCS),经axi_dma IP核,写入PS端DDR的rx_ring环形缓冲区。每个描述符(Descriptor)包含:
c struct rx_desc { u64 addr; // 物理地址(64-bit) u32 len; // 数据长度(32-bit) u32 ctrl; // 控制字(bit0: OWN_BIT, bit1: SOF, bit2: EOF) u32 status; // 状态字(bit0: RX_OK, bit1: CRC_ERR, bit2: PHY_ERR) };
- TX路径:驱动将待发MPDU写入tx_ring,axi_dma读取描述符,通过AXI-Stream推给openwifi_phy_tx。openwifi_phy_tx内部有tx_fifo,深度为256,防止DMA突发写入时FIFO溢出。
关键参数:
rx_ring大小设为1024,tx_ring为512。这个数字不是拍脑袋:ZC706的DDR带宽约1.6GB/s,802.11n MCS7理论速率65Mbps(≈8MB/s),1024*1500B=1.5MB的环形缓冲区,足以应对网络抖动。若你用ZCU102跑802.11ac(866Mbps),则需将rx_ring扩大到4096,否则dmesg会刷屏openwifi: rx ring full, dropping packet。
3.2.2 寄存器接口(Register Interface):控制与状态的神经末梢
FPGA暴露一组32-bit AXI-Lite寄存器(基地址0x43C00000),驱动通过ioremap()映射后读写。核心寄存器包括:
| 地址偏移 | 名称 | 功能 | 典型值 |
|----------|------|------|--------|
| 0x000 | REG_VERSION | 版本号(0x20230101) | 0x20230101 |
| 0x010 | REG_IRQ_EN | 中断使能(bit0: RX_DONE, bit1: TX_DONE) | 0x3 |
| 0x020 | REG_TX_CTRL | TX控制(bit0: START, bit1: ABORT) | 0x1 |
| 0x030 | REG_RX_STATUS | RX状态(bit0: FIFO_FULL, bit1: CRC_OK) | 0x2 |
| 0x100 | REG_PHY_CFG | PHY配置(bit0-3: MCS, bit4-7: BW, bit8-15: TX_PWR) | 0x0000007E (MCS7, 20MHz, 18dBm) |
注意:
REG_PHY_CFG的TX_PWR字段是相对值,不是绝对dBm。它通过查找表(LUT)映射到AD9361的TX_LO_POWER寄存器。例如,TX_PWR=0x0E对应-10dBm,0x1E对应+18dBm。这个LUT在openwifi_phy_tx.v的tx_power_lut模块里定义,ZC706和ZCU102的LUT不同——因为AD9361在不同温度下的功率响应曲线有差异。所以,BOARD_NAME=zc706和BOARD_NAME=zcu102编译出的位流,即使PHY RTL代码完全一样,REG_PHY_CFG的含义也不同。这就是为什么BOARD_NAME环境变量如此关键。
3.2.3 mac80211驱动集成:从struct ieee80211_hw到wlan0
openwifi的Linux驱动(drivers/net/wireless/openwifi/openwifi.c)是整个链条的终点。它的工作,就是把FPGA的寄存器和DMA,翻译成mac80211能懂的“普通话”。
驱动初始化流程:
1. openwifi_probe():读取设备树,ioremap()寄存器空间,dma_alloc_coherent()分配rx_ring/tx_ring,申请中断。
2. openwifi_init_hardware():向REG_IRQ_EN写0x3使能中断,向REG_PHY_CFG写默认配置(0x7E)。
3. ieee80211_register_hw():注册struct ieee80211_hw,其中关键回调:
- .tx():将skb拷贝到tx_ring,触发REG_TX_CTRL.START。
- .start():启动RX DMA,使能REG_IRQ_EN.RX_DONE。
- .add_interface():创建wlan0,配置为NL80211_IFTYPE_STATION或NL80211_IFTYPE_AP。
- .sta_add():当mac80211通知新STA关联时,驱动向FPGA的sta_table RAM写入该STA的MAC地址和加密密钥(AES-CCMP key)。
实操技巧:驱动调试时,
dmesg | grep openwifi是你的第一道防线。常见错误码:
-openwifi: irq timeout:FPGA没产生中断,检查REG_IRQ_EN是否写对,或ILA抓irq_out信号是否拉高。
-openwifi: tx desc error:tx_ring描述符的addr字段不是64-bit对齐的物理地址,用dma_map_single()获取正确地址。
-openwifi: phy init failed:REG_VERSION读出来不是预期值,说明位流没加载成功,或BOARD_NAME设错导致HDF地址映射错误。
4. 实操全流程:从Vivado工程构建到iw命令跑通
4.1 环境准备与依赖安装:别让工具链成为第一道墙
在Ubuntu 22.04 LTS上搭建环境,你需要以下组件(版本必须严格匹配,openwifi对Vivado版本敏感):
# 1. Xilinx Vivado 2022.2(必须!2023.x的IP核不兼容)
wget https://www.xilinx.com/member/forms/download/xef.html?filename=Xilinx_Vivado_SDK_2022.2_1014_1004.tar.gz
tar -xzf Xilinx_Vivado_SDK_2022.2_1014_1004.tar.gz
sudo ./xsetup -b Install --agree XilinxEULA,3rdPartyEULA --mode unattended --no-opengl --installDir /opt/Xilinx/Vivado/2022.2
# 2. PetaLinux 2022.2(用于构建Zynq Linux BSP)
wget https://www.xilinx.com/member/forms/download/xef.html?filename=Xilinx_PetaLinux_SDK_2022.2_1014_1004.tar.gz
tar -xzf Xilinx_PetaLinux_SDK_2022.2_1014_1004.tar.gz
./petalinux-v2022.2-final-installer.run /opt/Xilinx/PetaLinux/2022.2
# 3. 开源工具链(非Xilinx官方,但openwifi强烈推荐)
sudo apt install git build-essential libncurses-dev libssl-dev libelf-dev libdw-dev libudev-dev libpciaccess-dev python3-dev python3-pip
pip3 install pyvips # 用于生成位流校验码
重要警告:绝对不要用Vivado 2023.1或更高版本。openwifi的
openwifi_ip.tcl脚本里硬编码了set_property CORE_GENERATION_VERSION 2022.2 [get_ips ...],在2023.1中会报错CORE_GENERATION_VERSION is not a valid property for this IP。我曾为此浪费三天,最终发现git blame显示这个硬编码是2022年11月加入的,就是为了锁定2022.2。所以,环境准备的第一步,就是下载并安装指定版本。
4.2 构建ZC706位流:从零开始的15分钟
假设你已克隆仓库:
git clone https://github.com/OPEN-WIFI-SPECTRUM-COLLECTIVE/openwifi.git
cd openwifi
构建ZC706位流的完整命令链:
# 1. 设置环境变量(这是最关键的一步!)
export BOARD_NAME=zc706
export VIVADO_PATH=/opt/Xilinx/Vivado/2022.2
export PETALINUX_PATH=/opt/Xilinx/PetaLinux/2022.2
# 2. 初始化子模块(openwifi依赖analogdevicesinc/hdl等)
git submodule update --init --recursive
# 3. 运行一键脚本(它会自动执行后续所有步骤)
./create_vivado_proj.sh
# 脚本内部实际执行:
# a. source /opt/Xilinx/Vivado/2022.2/settings64.sh
# b. vivado -mode batch -source scripts/create_zc706_project.tcl
# c. vivado -mode batch -source scripts/run_zc706_synthesis.tcl
# d. vivado -mode batch -source scripts/run_zc706_implementation.tcl
# e. vivado -mode batch -source scripts/run_zc706_bitgen.tcl
脚本执行完毕后,你会在build/zc706/目录下看到:
- openwifi_zc706.bit:可烧录的位流文件(约8.2MB)
- openwifi_zc706.ltx:ILA调试文件(用于Vivado Hardware Manager抓波形)
- openwifi_zc706.hdf:硬件描述文件(供PetaLinux导入)
实操心得:
create_vivado_proj.sh脚本的执行时间约为12-15分钟(i7-11800H)。如果你看到卡在[Synth 8-5821] Unresolved reference to 'axi_adrv9361_rx',说明子模块没拉下来。此时不要git pull,而是运行:
bash cd hdl/projects/adrv9361/ git checkout 2022_R2 # 必须是这个tag,不是master! cd ../../../ ./create_vivado_proj.sh
因为analogdevicesinc/hdl仓库的master分支经常变动,而openwifi只验证过2022_R2tag。这是社区贡献者踩过的最大坑之一。
4.3 构建Linux驱动与固件:让wlan0出现在ifconfig里
驱动构建分为两步:内核模块编译和固件加载。
4.3.1 编译openwifi内核模块
假设你已用PetaLinux构建好ZC706的Linux BSP(petalinux-build成功):
# 进入PetaLinux工程目录
cd ~/zc706_petalinux
# 将openwifi驱动源码复制到project-spec/meta-user/recipes-kernel/linux/
mkdir -p project-spec/meta-user/recipes-kernel/linux/
cp -r /path/to/openwifi/drivers/net/wireless/openwifi/ \
project-spec/meta-user/recipes-kernel/linux/
# 修改project-spec/meta-user/recipes-kernel/linux/linux-xlnx_%.bbappend
echo 'FILESEXTRAPATHS_prepend := "${THISDIR}/files:"' >> project-spec/meta-user/recipes-kernel/linux/linux-xlnx_%.bbappend
echo 'SRC_URI += "file://openwifi/"' >> project-spec/meta-user/recipes-kernel/linux/linux-xlnx_%.bbappend
# 重新构建内核
petalinux-build -c linux-xlnx
构建完成后,驱动模块位于:
build/tmp/deploy/images/zc706/linux-xlnx/extra-modules/openwifi.ko
4.3.2 加载驱动与验证
将openwifi.ko拷贝到ZC706的Linux系统(通过TFTP或USB),然后:
# 1. 加载驱动
insmod openwifi.ko
# 2. 检查dmesg
dmesg | tail -20
# 应看到:
# [ 123.456789] openwifi: loading out-of-tree module taints kernel.
# [ 123.457890] openwifi 43c00000.openwifi: OpenWiFi ZC706 initialized, version 20230101
# [ 123.458901] openwifi 43c00000.openwifi: registered as wlan0
# 3. 查看无线接口
iw dev
# 应输出:
# phy#0
# Interface wlan0
# ifindex 4
# wdev 0x1
# addr xx:xx:xx:xx:xx:xx
# type managed
# channel 1 (2412 MHz), width: 20 MHz, center1: 2412 MHz
注意:如果
iw dev没输出wlan0,但dmesg显示registered as wlan0,说明mac80211没识别到它。此时检查:
-lsmod | grep cfg80211:确保cfg80211和mac80211模块已加载(PetaLinux默认已启用)
-cat /sys/module/openwifi/parameters/board_name:应为zc706,如果不是,说明驱动编译时没传对参数
-hexdump -C /sys/bus/platform/devices/43c00000.openwifi/reg:检查寄存器空间是否映射成功(前4字节应为01 00 00 20,即REG_VERSION=0x20230101)
4.4 运行iw与hostapd:从“能识别”到“能用”
4.4.1 扫描周边Wi-Fi网络(Station模式)
# 1. 将wlan0置于monitor模式(可选,用于嗅探)
iw dev wlan0 set type monitor
# 2. 切回managed模式(连接路由器)
iw dev wlan0 set type managed
# 3. 扫描
iw dev wlan0 scan | grep SSID
# 输出类似:
# SSID: MyHomeWiFi
# SSID: TP-Link_5G
# 4. 连接(需wpa_supplicant)
cat > /etc/wpa_supplicant.conf <<EOF
ctrl_interface=/var/run/wpa_supplicant
update_config=1
network={
ssid="MyHomeWiFi"
psk="your_password"
}
EOF
wpa_supplicant -B -i wlan0 -c /etc/wpa_supplicant.conf
dhclient wlan0
4.4.2 创建Wi-Fi热点(AP模式)
# 1. 安装hostapd
apt-get install hostapd
# 2. 配置hostapd.conf
cat > /etc/hostapd.conf <<EOF
interface=wlan0
driver=nl80211
ssid=openwifi_ap
hw_mode=g
channel=6
macaddr_acl=0
auth_algs=1
ignore_broadcast_ssid=0
wpa=2
wpa_passphrase=OpenWifi123
wpa_key_mgmt=WPA-PSK
rsn_pairwise=CCMP
EOF
# 3. 启动
hostapd -B /etc/hostapd.conf
# 4. 配置DHCP(dnsmasq)
apt-get install dnsmasq
cat > /etc/dnsmasq.conf <<EOF
interface=wlan0
dhcp-range=192.168.100.10,192.168.100.100,12h
dhcp-option=3,192.168.100.1
EOF
systemctl restart dnsmasq
# 5. 配置wlan0 IP
ifconfig wlan0 192.168.100.1/24 up
此时,用手机搜索Wi-Fi,就能看到openwifi_ap,输入密码OpenWifi123即可连接。ping 192.168.100.1应通。
实操技巧:AP模式下,
hostapd日志是黄金线索。运行hostapd -dd -t -B /etc/hostapd.conf(-dd开启debug,-t时间戳),观察:
-nl80211: AP-ENABLED:表示mac80211已通知FPGA启动AP
-wlan0: STA xx:xx:xx:xx:xx:xx IEEE 802.11: associated:表示手机已关联
- 如果卡在wlan0: STA xx:xx:xx:xx:xx:xx IEEE 802.11: deauthenticated due to inactivity,说明FPGA没收到手机发来的Null帧(Keepalive),检查openwifi_phy_rx的rx_timeout计数器是否溢出——这通常意味着ZC706的ADC采样时钟不稳定,需用示波器测CLK_OUT1引脚。
5. 常见问题与排查技巧实录:那些让你熬夜到凌晨三点的坑
5.1 位流加载失败:dmesg里找不到openwifi字样
这是最常遇到的“入门级”问题,原因却五花八门。我们按排查顺序列出:
| 现象 | 可能原因 | 排查命令/方法 | 解决方案 |
|---|---|---|---|
dmesg无任何openwifi输出 | 设备树(DTS)未添加openwifi节点 | cat /proc/device-tree/amba/43c00000.openwifi/compatible | 在system-user.dtsi中添加:&amba {<br> openwifi@43c00000 {<br> compatible = "openwifi,zc706";<br> reg = <0x43c00000 0x10000>; <br> interrupts = <0 89 4>; <br> interrupt-parent = <&gic>; <br> };<br>}; |
dmesg显示openwifi: probe failed | ioremap()失败(地址冲突) | cat /proc/iomem \| grep 43c00000 | 检查是否有其他设备占用了0x43c00000,通常是axi_ethernet或axi_gpio。修改DTS中reg地址为0x43d00000,并同步修改驱动中的OPENWIFI_BASE_ADDR。 |
dmesg显示openwifi: version mismatch | 位流与驱动版本不匹配 | hexdump -C /sys/bus/platform/devices/43c00000.openwifi/reg \| head -1 | 驱动期望0x20230101,但读到0x00000000,说明位流根本没加载。用Vivado Hardware Manager连接ZC706,执行Program Device,选择openwifi_zc706.bit,再试。 |
经验之谈:我曾在一个客户现场,
dmesg始终不打印openwifi,折腾两天。最后发现,他们的PetaLinux BSP里,CONFIG_OF_OVERLAY=y被注释掉了。openwifi的DTS节点是作为overlay动态加载的,没有这个选项,DTS根本不会被解析。解决方案是:petalinux-config -c kernel→Device Drivers→Firmware Drivers→Firmware loading facility→ 勾选OF overlay support。
5.2 扫描不到网络(iw dev wlan0 scan无输出)
这通常意味着RX链路不通。按信号流向逐级排查:
-
ADC采样是否正常?
用ILA抓adc_i_data和adc_q_data信号。正常应看到密集的、围绕零点波动的随机波形。如果是一条直线(全0或全FF),说明AD9361没上电或SPI配置失败。检查dmesg | grep ad9361,应有ad9361 spi0.0: AD9361 Rev 2 successfully initialized。 -
前导码检测是否触发?
用ILA抓rx_preamble_det_valid。在附近有Wi-Fi路由器时,它应周期性(每100ms左右)拉高一次。如果不拉高,检查:
-BOARD_NAME是否设为zc706(不是zcu102)
-adc_clk是否为20MHz(用示波器测ZC706的CLK_OUT1引脚)
- FMCOMMS2的跳线帽是否正确(REFCLK_SEL必须为ONBOARD) -
DMA是否收到数据?
在驱动里加printk("RX desc: addr=%llx, len=%d, status=%x\n", desc->addr, desc->len, desc->status);。如果len始终为0,说明axi_dma没把数据写进DDR。检查REG_IRQ_EN是否使能了RX_DONE,以及rx_ring的addr字段是否是dma_alloc_coherent()返回的物理地址(不是虚拟地址)。
独家技巧:openwifi提供了一个
gpio_led.md文档,教你把ZC706的LED0接到rx_preamble_det_valid,LED1接到rx_frame_valid。这样,不用示波器,一眼就能看出RX链路哪一级挂了:LED0闪但LED1不闪,问题在同步后;LED0不闪,问题在ADC前端。
5.3 连接后无法上网(ping不通网关)
这往往是TX链路或协议栈配置问题:
| 现象 | 可能原因 | 快速验证 | 解决方案 |
|---|---|---|---|
ping 192.168.1.1超时,但arping -I wlan0 192.168.1.1能收到Reply | 路由器ARP表有,但IP层不通 | tcpdump -i wlan0 icmp | 如果能看到ICMP echo request发出,但无reply,说明TX功率太低。增大REG_PHY_CFG.TX_PWR值(devmem2 0x43C00010 w 0x0000002E)。 |
iw dev wlan0 link显示Not connected. | 驱动认为关联失败 | dmesg \| grep "assoc\|auth" | 如果看到auth timeout,说明openwifi_phy_tx没发出Auth帧。检查REG_TX_CTRL.START是否被正确写入,或ILA抓tx_start信号。 |
iperf3 -c 192.168.1.100吞吐只有1Mbps | TX/RX时序偏差大 | iw dev wlan0 survey dump | 查看survey中noise值,如果-30 dBm,说明前端放大器饱和。降低AD9361的RX_LO_GAIN(通过iio_attr -d ad9361-phy rx_lo_gain 20)。 |
最后一招:当所有方法都失效,祭出
RUN_INSTRUCTIONS.md里的终极调试法——回退到最小系统。
1. 用create_vivado_proj.sh生成一个最简位流(只含openwifi_phy_rx+axi_dma,去掉openwifi_phy_tx和所有射频控制)。
2. 写一个裸机程序(在SDK里),只做一件事:不断读REG_RX_STATUS,如果bit1==1(CRC_OK),就翻转LED。
3. 用手机热点发数据,看LED是否规律闪烁。如果闪烁,说明PHY和DMA没问题,问题一定在Linux驱动或协议栈。
这种“二分法定界”,能帮你把问题范围从“整个系统”缩小到“驱动某一行代码”。
6. 性能实测与扩展建议:从实验室走向真实场景
6.1 ZC706实测性能基准(20MHz带宽,MCS7)
我在标准实验室环境下(屏蔽室,距离AP 3米),用ZC706+FMCOMMS2实测数据:
| 测试项 | 结果 | 说明 |
|---|---|---|
| 扫描速度 | < 2s | iw dev wlan0 scan完成时间,快于大部分商用USB Wi-Fi网卡 |
| 关联时间 | ~800ms | 从iw dev wlan0 connect到iw dev wlan0 link显示Connected |
| TCP吞吐(上行) | 58.2 Mbps | iperf3 -c 192.168.1.100 -t 30 -i 1 -R,接近理论值65Mbps的89% |
| TCP吞吐(下行) | 61.5 Mbps | iperf3 -c 192.168.1.100 -t 30 -i 1,略高于上行,因RX链路更稳健 |
| Ping延迟(局域网) | 0.8 ms ± 0.2ms | ping -c 100 192.168.1.100,抖动极小,证明中断延迟稳定 |
这个成绩,对于Zynq-7000这种资源受限的平台,已是极限。它证明openwifi不是概念验证,而是可交付的工程成果。
6.2 向ZCU102升级:解锁802.11ac与MIMO
ZCU102拥有UltraScale+ FPGA(>1M LUT)和更强的ARM Cortex-A53,是openwifi的“性能旗舰”。升级路径清晰:
- 硬件变更:更换为ADRV9364射频子板(支持2x2 MIMO,80MHz带宽)。
- FPGA变更:
BOARD_NAME=zcu102,脚本会自动启用openwifi_ip_ultra_scale.tcl,它:
- 将FFT点数提升至256(80MHz)
- 实例化双路openwifi_phy_rx/openwifi_phy_tx(MIMO)
- 使用axi_adrv9364IP核,支持JESD204B高速接口 - 驱动变更:
openwifi.ko自动识别zcu102,启用NL80211_IFTYPE_STATION的VHT_CAPABILITY,支持802.11ac VHT MCS 9(433Mbps)。
扩展建议:如果你想做Wi-Fi安全研究,openwifi是绝佳平台。它的FPGA完全可控,你可以:
- 在openwifi_phy_rx.v里插入逻辑,丢弃特定MAC地址的帧(实现定向干扰)
- 在openwifi_phy_tx.v里修改tx_gain_ctrl,对Beacon帧做功率突变(触发客户端重连)
- 利用ILA实时捕获rx_mpdu_payload,做实时WPA2握手包提取(配合hashcat破解)
这些操作,在商用Wi-Fi网卡上要么做不到,要么要逆向固件,风险极高。而在openwifi上,改几行Verilog,重新综合,15分钟搞定。
6.3 我的个人体会:为什么openwifi值得你投入时间?
写这篇博文时,我重刷了三遍ZC706的位流,调试了七种不同的射频配置,抓了上百个ILA波形。最终我想说:openwifi的价值,不在于它实现了Wi-Fi,而在于它把一个庞大复杂的无线协议,拆解成了一套可触摸、可修改、可验证的硬件契约。当你在Vivado里看到openwifi_phy_rx模块的时序报告,WNS=-0.123ns(满足时序),那一刻的踏实感,是任何仿真软件给不了的。它不承诺“一键成功”,但它承诺“每一步都有迹可循”。它的README.md不是敷衍的“Hello World”,而是告诉你:“ZC706的GPIO0引脚,必须接FMCOMMS2的RESET_B,否则AD9361初始化会失败,现象是dmesg报ad9361: spi transfer timeout”。这种细节,是十年硬件调试经验的结晶。
所以,如果你正站在SDR的门口犹豫,不知道该从GNU Radio的Python胶水代码,还是从Xilinx的Vivado教程开始——我建议你,直接打开openwifi的仓库,git clone,然后照着RUN_INSTRUCTIONS.md,亲手点亮那个LED。因为真正的无线世界,不在屏幕里,而在那根微微发热的ZC706开发板上,在那一串串被FPGA精准捕获的OFDM符号里。
简介:提供开箱即用的IEEE 802.11a/g/n Wi-Fi基带FPGA实现,完整对接Linux内核mac80211协议栈,适用于软件定义无线电(SDR)开发与教学实验。Verilog代码覆盖OFDM物理层收发、MAC接口桥接、时钟域同步及射频子板控制逻辑,已验证适配Xilinx ZC706、ZCU102、ZedBoard和AntSDR等主流硬件平台。资源包内置各平台预编译.bit位流文件、ILA调试.ltx配置、Vivado工程一键生成脚本(create_vivado_proj.sh)、IP核打包工具(package_ip.tcl)、HDF/SDK协同更新脚本(sdk_update.sh),以及针对ADI FMCOMMS2/3/4、ADRV9361/9364等射频子板的专用驱动适配层。所有模块遵循openwifi项目结构规范,可直接配合上游Linux驱动和用户空间工具(如iw、hostapd)运行。使用前需设置BOARD_NAME环境变量指定目标板卡,详细引脚约束、时序约束、软硬件联调步骤、调试技巧均在对应板级目录下的README.md中说明。许可证为AGPLv3,部分第三方IP需按其单独条款确认授权。
157



被折叠的 条评论
为什么被折叠?



