linux网络-网卡驱动分析(基于imx6ul和ZYNQ分析)

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 = <&ethphy0>;
    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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值