QNX开发之ECAT专用网卡驱动ecpkt · 03-完成自协商
在上一篇中我们准备好了Resource Manager框架,得到了一个可以编译运行、但还没有任何功能的工程。这一篇的目标是完成硬件配置,从硬件层面得到一条完整可用的链路,为下一步的收发报文做准备。GEM支持千兆速率的自协商,完成自协商可以作为硬件链路可用的标志,我们就把它作为这一步的目标——具体来说:把电路板上的网卡接到PC的网卡上,在PC上看到网口状态变为“已连接”,并协商出一个可用的速率,就代表自协商成功。
1. 从原生iopkt框架中剥离出目标网卡
在原生iopkt框架中,系统里所有的网卡都交给iopkt控制。ecpkt的第一步就是把目标网卡从iopkt中剥离出来,由ecpkt取得完全的控制权——否则iopkt会和ecpkt同时操作这块网卡,通信无法正常进行。
原生devnp驱动会自动挂载系统中所有注册过的网卡:
devices_attached = 0; for (idx = 0; idx < XZYNQ_MAX_INSTANCES; idx++) { /* Use "deviceindex" values if specified. Otherwise, try every interface listed in hwinfo. */ if (num_dev_idx_opts > 0) { if (dev_idx_opts[idx] != ~0U) { attach_args.device_index = dev_idx_opts[idx]; } else { /* Reached the end of the deviceindex values. */ break; } } else { /* Look up this device index in hwinfo. */ hwi_off = hwi_find_device(XZYNQ_HWI_ENET, idx); if (hwi_off == HWI_NULL_OFF) { /* Reached the end of the hwinfo list. */ break; } attach_args.device_index = idx; } err = dev_attach("eth", options, &xzynq_ca, &attach_args, &single, &dev[devices_attached], NULL);可以看到,devnp根据启动参数deviceindex决定调用dev_attach挂载哪些网卡。所以我们只要在iopkt的启动参数里指定deviceindex,就能限定iopkt只挂载指定的网卡,从而把ecpkt的目标网卡从iopkt框架里剥离出来:
io-pkt-v6-hc -dxzynq-ultrascaledeviceindex=0p0_mac=000A350023C8
这里我把网卡0交给iopkt去做Ethernet通信,网卡1预留给ecpkt去做EtherCAT通信,所以指定deviceindex为0。
补充一下:我最初尝试剥离网卡时,没有注意到deviceindex这个参数,陷入了另一个误区。我的系统没有用到设备树,系统硬件都是在QNX的startup模块里注册的:
/* Add GEM0 */ hwidev_add_emac(emac_interface_idx++, MPSOC_EMAC0_BASE, MPSOC_IRQ_GEM0, 0); /* Add GEM3 */ hwidev_add_emac(emac_interface_idx++, MPSOC_EMAC3_BASE, MPSOC_IRQ_GEM3, 0);最初我认为直接在startup里去掉GEM3(对应网卡1)是最干净的,事实上iopkt也确实不再(也不能)挂载GEM3了,这种做法确实能保证GEM3完全由ecpkt控制。但代价是GEM3对QNX也不可见了,会不会产生别的影响不好说,所以并不推荐这样做。
另一个顾虑在于startup的职责:Zynq的startup似乎只是把硬件设备注册到系统设备列表,但按我的理解,startup模块还可以根据注册的硬件来使能对应模块的power和clock,以此降低系统功耗——Zynq的BSP里没有这样做,其他BSP里却可能会有,所以还是尽量不要通过改动startup来分离硬件。从分层的角度来讲:
Startup → 注册硬件 → 使能硬件
iopkt/ecpkt → 驱动硬件
startup只负责硬件这一层的管理;至于硬件由哪个软件来驱动——比如网卡由iopkt还是ecpkt来驱动——则由驱动层负责。
2. 开始自协商
网卡硬件分离完成后,就可以开始尝试自协商了。上一篇里我们已经完成了软件逻辑部分,包括GEM和PHY的基本配置,并把devnp中的MDI控制移植到了ecpkt里,理论上自协商可以直接进行了。为了监控自协商的过程,我们在PHY的控制函数(寄存器读写)里加上了打印,用来观察PHY芯片在自协商过程中的行为和最终结果:
uint16_t xzynq_mdi_read(void *hdl, uint8_t phyid, uint8_t reg) { … printf("=====%s:%d:%x:%x=====\r\n", __func__, __LINE__, reg, data); … } void xzynq_mdi_write(void *hdl, uint8_t phyid, uint8_t reg, uint16_t data) { … printf("=====%s:%d:%x:%x=====\r\n", __func__, __LINE__, reg, data); … }现在插上网线,运行程序,自协商会自动开始,我们得到了下面的日志:
=====xzynq_mdi_read:67:1:7949=====← BMSR:自协商重启中,AN 未完成、link down
=====xzynq_mdi_read:67:1:7949=====
=====xzynq_mdi_read:67:1:7969=====← ★ bit5(0x0020) 置位:自协商完成
=====xzynq_mdi_read:67:1:796d=====← ★ bit2(0x0004) 置位:链路建立
这就表示自协商完成、链路成功建立,此时在PC上可以看到对应的网口状态变成:
可以看到,网卡工作在1.0Gbps 的全双工链路,与协商结果一致
至此初始化阶段全部完成,驱动第一次真正“拥有”了这块网卡:PHY自协商成功、link状态正常。接下来就可以开发最关键的报文收发功能了。