1. 项目概述
在嵌入式网络设备开发中,Zynq SoC因其独特的ARM+FPGA架构而广受欢迎。最近我在一个工业网关项目中遇到了一个需求:需要在PL端实现4个独立的千兆以太网接口,并通过裸机程序进行驱动控制。这个方案相比传统的PS端网口扩展,能够提供更高的灵活性和性能。
这个实现基于Vivado 2019.1开发环境和LWIP 2.1.1协议栈,使用了Xilinx的AXI 1G/2.5G Ethernet Subsystem IP核。核心挑战在于如何高效管理多个网口的状态监测和速度协商,特别是当我们需要实时响应链路状态变化时。
2. 硬件架构设计
2.1 AXI Ethernet子系统配置
在Vivado中,我们为每个网口实例化了一个AXI Ethernet Subsystem IP核。关键配置参数包括:
- 数据宽度:64位AXI4-Stream接口
- 包含DMA引擎:减轻CPU负担
- 支持1G/2.5G速率
- 启用所有统计计数器
每个IP核需要独立连接到PS端的DMA控制器,并在地址空间中分配独立的基地址。我们的硬件设计中,四个网口的基地址配置如下:
| 网口编号 | 基地址宏定义 | 物理地址 |
|---|---|---|
| 网口0 | PLATFORM_EMAC_0_BASEADDR | 0x41000000 |
| 网口1 | PLATFORM_EMAC_1_BASEADDR | 0x41010000 |
| 网口2 | PLATFORM_EMAC_2_BASEADDR | 0x41020000 |
| 网口3 | PLATFORM_EMAC_3_BASEADDR | 0x41030000 |
2.2 PHY芯片选型与连接
我们选用了Microchip的KSZ9031RNX PHY芯片,主要考虑因素包括:
- 支持10/100/1000Mbps自适应
- RGMII接口与AXI Ethernet IP完美匹配
- 工业级温度范围
- 单电源供电简化设计
PHY芯片通过RGMII接口直接连接到FPGA的Bank 1,需要注意以下几点:
- 确保时钟走线等长
- 电源去耦电容靠近PHY放置
- 复位信号需要适当延时
3. 软件架构实现
3.1 LWIP协议栈配置
LWIP配置需要特别注意多网口支持。在lwipopts.h中,我们做了以下关键设置:
c复制#define LWIP_NETIF_API 1 // 启用netif API
#define LWIP_NETIF_STATUS_CALLBACK 1 // 启用状态回调
#define LWIP_NUM_NETIF_CLIENT_DATA 1 // 每个netif的客户端数据
#define MEMP_NUM_NETCONN 16 // 增加连接数
#define PBUF_POOL_SIZE 32 // 增大pbuf池
对于多网口环境,必须确保每个netif结构体有独立的内存空间。我们为每个网口分配了独立的netif实例:
c复制static struct netif server_netif; // 网口0
static struct netif server_netif1; // 网口1
static struct netif server_netif2; // 网口2
static struct netif server_netif3; // 网口3
3.2 多网口初始化流程
系统启动时,需要依次初始化每个网口。关键步骤如下:
- 初始化LWIP协议栈
- 为每个网口设置独立的IP地址和MAC地址
- 调用xemac_add()添加网络接口
- 启用中断处理
- 启动链路状态监测
c复制// MAC地址配置示例
unsigned char mac_ethernet_address[] = {0x00, 0x0a, 0x35, 0x00, 0x01, 0x02};
unsigned char mac_ethernet_address1[] = {0x00, 0x0a, 0x35, 0x00, 0x01, 0x03};
// ...其他网口MAC地址
// IP地址配置
IP4_ADDR(&ipaddr, 100, 100, 0, 10);
IP4_ADDR(&netmask, 255, 255, 255, 0);
IP4_ADDR(&gw, 100, 100, 0, 1);
// ...其他网口IP配置
4. 链路状态监测实现
4.1 PHY寄存器访问机制
KSZ9031 PHY芯片提供了丰富的状态寄存器,我们主要使用:
- 0x01 - Basic Status Register:链路状态指示
- 0x1F - PHY Control/Speed Register:速度协商结果
通过XAxiEthernet_PhyRead()函数可以访问这些寄存器:
c复制u16 status = 0;
XAxiEthernet_PhyRead(xaxiemacp, phy_addr, KSZ9031_PHY_STATUS_REG, &status);
4.2 链路状态数据结构设计
为了跟踪每个网口的状态,我们设计了LinkState_t结构体:
c复制typedef struct {
u32 phy_addr; // PHY地址
int link_up; // 当前链路状态
u32 current_speed; // 当前速度(10/100/1000)
XTime last_check_time; // 上次检测时间戳
} LinkState_t;
static LinkState_t g_link_states[4]; // 四个网口的状态
4.3 状态监测主逻辑
链路监测的核心是定期检查PHY状态寄存器,并根据状态变化采取相应措施:
c复制int Link_Monitor_Poll(struct netif *netif) {
// 获取状态结构体
LinkState_t *pState = get_link_state_by_netif(netif);
// 检查时间间隔
XTime current_time;
XTime_GetTime(¤t_time);
if ((current_time - pState->last_check_time) < LINK_CHECK_TICKS) {
return 0;
}
pState->last_check_time = current_time;
// 读取PHY状态
u16 status = 0;
XAxiEthernet_PhyRead(xaxiemacp, pState->phy_addr,
KSZ9031_PHY_STATUS_REG, &status);
int new_link_up = (status & KSZ9031_LINK_STATUS_BIT) ? 1 : 0;
// 处理链路状态变化
if (pState->link_up && !new_link_up) {
// 链路断开处理
netif_set_link_down(netif);
return 1;
} else if (!pState->link_up && new_link_up) {
// 链路恢复处理
netif_set_link_up(netif);
return 1;
}
return 0;
}
5. 速度协商与MAC配置
5.1 速度检测实现
当链路恢复时,需要检测协商的速度并配置MAC层:
c复制u32 get_phy_speed_ksz9031(XAxiEthernet *xaxiemacp, u32 phy_addr) {
u16 phy_ctrl = 0;
XAxiEthernet_PhyRead(xaxiemacp, phy_addr,
KSZ9031_PHY_CTRL_REG, &phy_ctrl);
if ((phy_ctrl & 0x40) == 0x40) return 1000;
else if ((phy_ctrl & 0x20) == 0x20) return 100;
else if ((phy_ctrl & 0x10) == 0x10) return 10;
else return 100; // 默认值
}
5.2 MAC速度动态调整
检测到速度变化后,需要重新配置MAC层:
c复制void Update_Mac_Speed(struct netif *netif, u32 new_speed) {
XAxiEthernet *xaxiemacp = get_axiethernet_instance(netif);
// 停止MAC
XAxiEthernet_Stop(xaxiemacp);
// 设置新速度
XAxiEthernet_SetOperatingSpeed(xaxiemacp, (u16)new_speed);
// 短暂延时
usleep(1000);
// 重启MAC
XAxiEthernet_Start(xaxiemacp);
}
6. 多网口数据收发处理
6.1 接收数据处理流程
在主循环中,需要轮询处理每个网口的数据接收:
c复制while (1) {
// TCP定时器处理
if (TcpFastTmrFlag) {
tcp_fasttmr();
TcpFastTmrFlag = 0;
}
// 接收数据处理
xemacif_input(echo_netif);
xemacif_input(echo_netif1);
xemacif_input(echo_netif2);
xemacif_input(echo_netif3);
// 链路状态监测
Link_Monitor_Poll(echo_netif);
// ...其他网口监测
}
6.2 中断处理优化
为了提高响应速度,我们实现了中断驱动的接收机制:
- 在platform_enable_interrupts()中启用DMA和MAC中断
- 中断服务程序仅设置标志位
- 主循环检查标志位并处理数据
这种混合轮询+中断的方式在保证实时性的同时降低了中断频率。
7. 常见问题与解决方案
7.1 自协商失败问题
在调试过程中,我们遇到了PHY自协商失败的问题,串口输出如下:
code复制WARNING: Not a Marvell or TI Ethernet PHY.
Please verify the initialization sequence
Warn: Auto-negotiation timed out! (No Cable?)
解决方案包括:
- 检查PHY复位时序,确保复位时间足够长
- 验证RGMII信号完整性
- 在初始化后添加适当延时
- 检查电源稳定性
7.2 多网口带宽竞争
当四个网口同时工作时,可能出现带宽竞争导致性能下降。我们通过以下方式优化:
- 为每个网口分配独立的DMA通道
- 调整LWIP内存池大小
- 实现优先级调度
7.3 调试技巧
- 使用Xilinx的XSCT工具可以实时查看寄存器状态
- 在Vivado中插入ILA核监测关键信号
- 通过串口打印详细的调试信息
- 使用Wireshark抓包分析网络流量
8. 性能测试结果
我们对系统进行了全面测试,主要指标如下:
| 测试项目 | 网口0 | 网口1 | 网口2 | 网口3 |
|---|---|---|---|---|
| 最大吞吐量(Mbps) | 940 | 938 | 935 | 942 |
| Ping延迟(ms) | 0.12 | 0.13 | 0.15 | 0.11 |
| 连接稳定性 | 优秀 | 优秀 | 优秀 | 优秀 |
测试环境:
- 使用Ixia网络测试仪
- 64字节小包测试
- 持续压力测试24小时
9. 关键代码解析
9.1 网口初始化代码
c复制int init_network_interfaces() {
// 初始化LWIP
lwip_init();
// 添加网络接口
if (!xemac_add(echo_netif, &ipaddr, &netmask, &gw,
mac_ethernet_address, PLATFORM_EMAC_0_BASEADDR)) {
xil_printf("Error adding N/W interface 0\n\r");
return -1;
}
// ...其他网口初始化
// 设置默认网关
netif_set_default(echo_netif);
// 启用中断
platform_enable_interrupts();
// 启动网口
netif_set_up(echo_netif);
// ...其他网口启动
return 0;
}
9.2 链路监测核心逻辑
c复制int check_link_status(struct netif *netif, LinkState_t *pState) {
XAxiEthernet *xaxiemacp = get_axiethernet_instance(netif);
// 读取PHY状态
u16 status = 0;
XAxiEthernet_PhyRead(xaxiemacp, pState->phy_addr,
KSZ9031_PHY_STATUS_REG, &status);
int new_link_up = (status & KSZ9031_LINK_STATUS_BIT) ? 1 : 0;
// 链路状态变化处理
if (pState->link_up != new_link_up) {
if (new_link_up) {
// 链路恢复处理
u32 speed = get_current_speed(xaxiemacp, pState->phy_addr);
update_mac_speed(netif, speed);
netif_set_link_up(netif);
} else {
// 链路断开处理
netif_set_link_down(netif);
}
pState->link_up = new_link_up;
return 1; // 状态变化
}
return 0; // 无变化
}
10. 项目总结与展望
这个项目成功实现了Zynq PL端4个千兆网口的裸机驱动,主要成果包括:
- 完整的硬件设计参考方案
- 高效的多网口管理架构
- 可靠的链路状态监测机制
- 动态速度调整功能
在实际部署中,这个方案已经稳定运行超过6个月,处理了超过1TB的网络数据。对于未来改进,我考虑的方向包括:
- 添加VLAN支持
- 实现QoS功能
- 支持热插拔检测
- 优化中断处理机制
这个项目的完整代码已经托管在GitHub上,包含了详细的注释和测试用例,可以作为类似项目的参考基础。