0、说明
内核网络驱动总结,从设备树到内核驱动加载初始化及网卡通信整个流程。
1、环境
1.1 硬件环境
NXP imx6ul 平台
1.2 参考资料
IMX6ULLRM.pdf 22 章 10/100-Mbps Ethernet MAC (ENET)
2、网卡驱动
2.1 内核网卡驱动启动信息
fec 2188000.ethernet (unnamed net_device) (uninitialized): Invalid MAC address: 00:00:00:00:00:00
fec 2188000.ethernet (unnamed net_device) (uninitialized): Using random MAC address: a2:a4:3a:f9:1d:81
libphy: fec_enet_mii_bus: probed
fec 2188000.ethernet eth0: registered PHC device 0
fec_enet_mii_bus: probed可以定位到驱动位置:
drivers/net/ethernet/freescale/fec_main.c
2.2 设备树文件
arch/arm/boot/dts/imx6ull.dtsi
arch/arm/boot/dts/myb-y6ull-14x14.dts
fec1: ethernet@02188000 {
compatible = "fsl,imx6ul-fec", "fsl,imx6q-fec";
reg = <0x02188000 0x4000>;
interrupts = <GIC_SPI 118 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 119 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_ENET>,
<&clks IMX6UL_CLK_ENET_AHB>,
<&clks IMX6UL_CLK_ENET_PTP>,
<&clks IMX6UL_CLK_ENET_REF>,
<&clks IMX6UL_CLK_ENET_REF>;
clock-names = "ipg", "ahb", "ptp",
"enet_clk_ref", "enet_out";
stop-mode = <&gpr 0x10 3>;
fsl,num-tx-queues=<1>;
fsl,num-rx-queues=<1>;
fsl,magic-packet;
fsl,wakeup_irq = <0>;
status = "disabled";
};
&fec1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_enet1>;
phy-mode = "rmii";
phy-handle = <ðphy0>;
phy-reset-gpios = <&gpio5 9 GPIO_ACTIVE_LOW>;
phy-reset-duration = <26>;
status = "okay";
mdio {
#address-cells = <1>;
#size-cells = <0>;
ethphy0: ethernet-phy@0 {
compatible = "ethernet-phy-ieee802.3-c22";
smsc,disable-energy-detect;
reg = <0>;
};
ethphy1: ethernet-phy@1 {
compatible = "ethernet-phy-ieee802.3-c22";
smsc,disable-energy-detect;
reg = <1>;
};
};
};
设备树compatible = "fsl,imx6ul-fec", "fsl,imx6q-fec";与驱动的匹配
static const struct of_device_id fec_dt_ids[] = {
{ .compatible = "fsl,imx25-fec", .data = &fec_devtype[IMX25_FEC], },
{ .compatible = "fsl,imx27-fec", .data = &fec_devtype[IMX27_FEC], },
{ .compatible = "fsl,imx28-fec", .data = &fec_devtype[IMX28_FEC], },
{ .compatible = "fsl,imx6q-fec", .data = &fec_devtype[IMX6Q_FEC], },
{ .compatible = "fsl,mvf600-fec", .data = &fec_devtype[MVF600_FEC], },
{ .compatible = "fsl,imx6sx-fec", .data = &fec_devtype[IMX6SX_FEC], },
{ .compatible = "fsl,imx6ul-fec", .data = &fec_devtype[IMX6UL_FEC], },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, fec_dt_ids);
驱动fec_probe调用,开始网卡硬件的初始化及准备。
2.3 网卡驱动probe
最主要任务就是根据设备树中的信息及硬件信息,初始化网卡硬件,并向内核注册网卡设备,从而产生网络设备提供应用层使用。
其中涉及重要的结构体。主要分为内核结构体(platform_device、net_device)和厂家自定义板级结构体(如NXP的fec_enet_private,一般以私有数据排列在net_device后:fep = netdev_priv(ndev))芯片原厂定义自己的网卡结构体结合内核net_device,完成网卡驱动维护。
网卡probe主要任务
- 获取设备树队列数量
- 分配net_device结构体 alloc_etherdev_mqs
- SET_NETDEV_DEV(net_device和platform_device关联)
- 板级结构体安排到net_device后排列,netdev_priv
- of_match_device 绑定网卡信息
- 初步初始化板级结构体。fep->netdev = ndev;fep->num_rx_queues = num_rx_qs;....
- 获取网卡寄存器地址并映射虚拟空间platform_get_resource、devm_ioremap_resource
- 网卡配置解析(FEC_WOL_HAS_MAGIC_PACKET)
- 获取phy信息(of_parse_phandle(np, "phy-handle", 0)、of_get_phy_mode)
- 网卡时钟填充至板级结构体(devm_clk_get)
- 复位phy(fec_reset_phy、reset_phy)
- 申请描述符BD空间(fec_enet_init)
- 获取中断信息并注册中断(platform_get_irq、devm_request_irq)
- MII总结-mdio初始化(fec_enet_mii_init、mdiobus_register)
- 关闭网卡netif_carrier_off(ndev) 等待phy启动它
- register_netdev注册内核空间,产生网卡
综上,网卡probe函数最主要是完成net_device的创建,其中主要需要关联net_device、platform_device、fec_enet_private进行后续网卡维护使用、获取设备树信息来初始化网卡配置、初始化phy并注册mii_bus及phy_device、申请中断、完成描述符创建及初始化、注册网卡进内核空间。
2.4 网卡收发队列
板级结构体中,定义有收发队列的变量。同时在注册网卡alloc_etherdev_mqs的时候也传递了队列数量。
unsigned int num_tx_queues; ~
unsigned int num_rx_queues; ~
~
/* The saved address of a sent-in-place packet/buffer, for skfree(). */ ~
struct fec_enet_priv_tx_q *tx_queue[FEC_ENET_MAX_TX_QS]; ~
struct fec_enet_priv_rx_q *rx_queue[FEC_ENET_MAX_RX_QS];
发送队列
struct fec_enet_priv_tx_q { ~
int index; ~
//open的时候未tx_bounce分配控件,用于与skb复制
unsigned char *tx_bounce[TX_RING_SIZE]; ~
//在发送后与skb一致,用于发送完成后skb释放使用
struct sk_buff *tx_skbuff[TX_RING_SIZE]; ~
~
//在申请描述符的时候得到的发送描述符对应的物理地址--物理地址
dma_addr_t bd_dma; ~
//发送描述符BD的基址,在分配描述符后计算得出--虚拟地址
struct bufdesc *tx_bd_base;
//环形缓冲的大小,也是申请发送描述符的大小 ~
uint tx_ring_size; ~
~
unsigned short tx_stop_threshold; ~
unsigned short tx_wake_threshold; ~
~
//指示下一次发送使用的描述符
struct bufdesc *cur_tx; ~
//指示已经使用但为在中断中处理的描述符
struct bufdesc *dirty_tx; ~
char *tso_hdrs; ~
dma_addr_t tso_hdrs_dma; ~
};
接收队列
struct fec_enet_priv_rx_q { ~
int index; ~
struct sk_buff *rx_skbuff[RX_RING_SIZE]; ~
~
dma_addr_t bd_dma; ~
struct bufdesc *rx_bd_base; ~
uint rx_ring_size; ~
~
struct bufdesc *cur_rx; ~
};
2.5 网卡open
执行ifconfig将网卡up,在PHY协商后同步到mac后网卡更新至running状态。
open中主要任务
- 开启时钟
- 复位phy
- 初始化描述符
- 复位mac
- 连接至phy上: phy_connect/of_phy_connect
- 使能napi_enable、phy_start、netif_tx_start_all_queues
2.6 网卡close
2.7 网卡中断
2.8 网卡发包ndo_start_xmit
发包流程:
- ndo_start_xmit函数被调用
- skb提取队列index,准备队列调用二级发送函数fec_enet_txq_submit_skb(txq, skb, ndev);
- 剩余描述符大小判断,需要大于最小分片包数
- 得到一个描述符头
- 提取skb地址和数据长度信息
- 进行sdk数据映射到dma地址
- 设置描述符一些标志
- 将dma物理地址和数据长度写入描述符
- 更新描述符头指针
- 置位描述符发送准备就绪标志,等待硬件完成传输
- 发送完成触发中断,中断读取事件类型
- 中断处理,判断发送标志置位,则释放资源
- 完成一次发送
如图,imx6ul共计512个发送环形描述符。由于描述符地址空间连续,可以很方便的使用和维护队列。网卡初始化完成后cur_rx为0,dirty_tx为511。当发送一包后,cur_rx向前移动,发几包移动几个。注意此时发送数量可能大于中断数量,因为dma发送需要时间。当发送完成中断被触发,在中断中判断完成标志,将发送完成标志置位的数据一起处理后移动dirty_tx指针。
dirty_tx下一个与cur_rx直接的描述符为未释放空间,表示发送已经执行,单未在中断中处理。
2.9 网卡收包
收包流程
- 触发中断,开启软中断处理
- 查寄存器判断中断触发类型
- 遍历接收描述符
- 判断状态位是否发生错误包
- 丢去FCS
- 处理可能的VLAN
- 上报协议栈napi_gro_receive(&fep->napi, skb);
- 循环NAPI_POLL_WEIGHT
2.10 MDIO PHY MII驱动
根据如上设备树以及在mac probe中,有队mii的初始化,同时在open时候也有队mii的probe。如fec_enet_mii_init、fec_enet_mii_probe。
网卡驱动中对于MDIO的操作主要有注册mii,注册phy_devcie。启动phy状态机。
网卡驱动中的probe(fec_enet_mii_init)
- 注册mii总线of_mdiobus_register(fep->mii_bus, node);
- 注册phy设备of_mdiobus_register_phy,同时匹配phy驱动,在phy_device时候初始化了一个delay_work:INIT_DELAYED_WORK(&dev->state_queue, phy_state_machine);
网卡驱动open(fec_enet_mii_probe)
- 连接至phy_device,of_phy_connect
- 初始化phy硬件phy_init_hw,完成复位及config
- 填充adjust_link函数phy_prepare_link
- 启动状态机phy_start_machine(循环调用DELAYED_WORK)
- 进入phy状态机运行状态,反复读取phy寄存器
- 同步phy状态到网卡。
总结
基于NAPI的网卡驱动,一次中断不再面对一个包,而是多个包,通过中断+轮询的方式给CPU降低持续网络通信带来的中断消耗。
probe中网卡的注册,包含注册网卡设备,申请中断,初始化时钟及相关网卡寄存器,申请描述符、注册mdio相关等。
发送函数中,将sdk映射到dma物理地址上,同时给描述符标记包准备就绪。发送完成后触发中断,中断中根据描述符中发送完成标志对已经发送的一系列包做释放。同时对连续地址的环形队列做移动。
接收函数中,判断描述符中头指针开始的一系列接收完成的数据包,进行包错误检查,skb预处理后上交协议栈,并移动头描述符。
PHY的信息在probe中获取,并注册内核mii_bus,同时将phy以phy_device的形式注册进内核与phy_driver进行匹配,在open中,连接至phy,启动状态机,检测phy寄存器变化将网络情况同步至mac。