以下内容,来自正点原子Linux驱动开发文档:正点原子
MII、GMII、RMII、SGMII、XGMII 接口区别
SGMII
RGMII

网络设备硬件接口

网络设备硬件是由 macphy 两部分组成。
有的芯片内置mac外设,这种被称为支持网络的;有的内置没有mac外设,不支持网络。根据这两种芯片产生了两种网络设备方案。

无内置mac 外设

我们一般说某个 SOC 不支持网络,说的就是它没有网络 MAC。那么这个芯片就不能上网了吗?显然不是的,既然没有内部 MAC,那么可以找个外置的 MAC 芯片啊,不过 **一般这种外置的网络芯片都是 MAC+PHY 一体的。**比如三星 linux 开发板里面用的最多的 DM9000,因为三星的芯片基本没有内部 MAC(比如 S3C2440、 S5PV210, 4412 等),所以三星的开发板都是通过外置的 DM9000 来完成有线网络功能的, DM9000 对 SOC 提供了一个 SRAM 接口, SOC 会以 SRAM 的方式操作 DM9000。

有些外置的网络芯片更强大,内部甚至集成了硬件 TCP/IP 协议栈,对外提供一个 SPI 接口,比如 W5500。这个一般用于单片机领域,单片机通过 SPI 接口与 W5500 进行通信,犹豫W5500 内置了硬件 TCP/IP 协议栈,因此单片机就不需要移植负责的软件协议栈,直接通过 SPI来操作 W5500,简化了单片机联网方案。
这种方案的优点就是让不支持网络的 SOC 能够另辟蹊径,实现网络功能,但是缺点就是网络效率不高,因为一般芯片内置的 MAC 会有网络加速引擎,比如网络专用 DMA,网络处理效率会很高。而且此类芯片网速都不快,基本就是 10/100M。另外,相比 PHY 芯片而言,此类芯片的成本也比较高,可选择比较少。

外置mac 芯片 网络硬件结构如图:

内置mac 外设

我们一般说某个 SOC 支持网络,说的就是他内部集成网络 MAC 外设,此时我们还需要外接一个网络 PHY 芯片。
一般常见的通用 SOC 都会集成网络 MAC 外设,比如 STM32F4/F7/H7 系列、 NXP 的 I.MX系列,内部集成网络 MAC 的优点如下:

  1. 内部 MAC 外设会有专用的加速模块,比如专用的 DMA,加速网速数据的处理。
  2. 网速快,可以支持 10/100/1000M 网速。
  3. 外接 PHY 可选择性多,成本低。

内部的 MAC 外设会通过 MII 或者 RMII 接口来连接外部的 PHY 芯片, MII/RMII 接口用来传输网络数据。
另外主控需要配置或读取 PHY 芯片,也就是读写 PHY 的内部寄存器,所以还需要一个控制接口,叫做 MIDO, MDIO 很类似 IIC,也是两根线,一根数据线叫做 MDIO,一根时钟线叫做 MDC。

SOC 内部 MAC 外设与外部 PHY 芯片的连接如图 69.1.1.2 所示:
MII/RMII 用于传输数据,MDIO 用于读写phy内部寄存器,控制phy芯片。

MII/RMII 接口

前面我们说了,内部 MAC 通过 MII/RMII 接口来与外部的 PHY 芯片连接,完成网络数据传输。

1、 MII 接口

MII 全称是 Media Independent Interface,直译过来就是介质独立接口,它是 IEEE-802.3 定义的以太网标准接口, MII 接口用于以太网 MAC 连接 PHY 芯片,连接示意图如图 69.1.2.1 所示:

MII 接口一共有 16 根信号线,含义如下:
TX_CLK: 发送时钟,如果网速为 100M 的话时钟频率为 25MHz, 10M 网速的话时钟频率为 2.5MHz,此时钟由 PHY 产生并发送给 MAC。
TX_EN: 发送使能信号。
TX_ER: 发送错误信号,高电平有效,表示 TX_ER 有效期内传输的数据无效。 10Mpbs 网速下 TX_ER 不起作用。
TXD[3:0]: 发送数据信号线,一共 4 根。
RXD[3:0]: 接收数据信号线,一共 4 根。
RX_CLK: 接收时钟信号,如果网速为 100M 的话时钟频率为 25MHz, 10M 网速的话时钟频率为 2.5MHz, RX_CLK 也是由 PHY 产生的。
RX_ER: 接收错误信号,高电平有效,表示 RX_ER 有效期内传输的数据无效。 10Mpbs 网速下 RX_ER 不起作用。
RX_DV: 接收数据有效,作用类似 TX_EN。
CRS: 载波侦听信号。
COL: 冲突检测信号。
MII 接口的缺点就是所需信号线太多,这还没有算 MDIO 和 MDC 这两根管理接口的数据线,因此 MII 接口使用已经越来越少了。

2、 RMII 接口

RMII 全称是 Reduced Media Independent Interface,翻译过来就是精简的介质独立接口,也就是 MII 接口的精简版本。 RMII 接口只需要 7 根数据线,相比 MII 直接减少了 9 根,极大的方便了板子布线, RMII 接口连接 PHY 芯片的示意图如图 69.1.2.2 所示:

TX_EN: 发送使能信号。
TXD[1:0]: 发送数据信号线,一共 2 根。
RXD[1:0]: 接收数据信号线,一共 2 根。
CRS_DV: 相当于 MII 接口中的 RX_DV 和 CRS 这两个信号的混合。
REF_CLK: 参考时钟,由外部时钟源提供, 频率为 50MHz。这里与 MII 不同, MII 的接收和发送时钟是独立分开的,而且都是由 PHY 芯片提供的。

除了 MII 和 RMII 以外,还有其他接口,比如 GMII、 RGMII、 SMII、 SMII 等,关于其他接口基本都是大同小异的 ,正点原子 ALPAH 开发板上的两个网口都是采用 RMII 接口来连接 MAC 与外部 PHY 芯片。

MDIO 接口

MDIO 全称是 Management Data Input/Output,直译过来就是管理数据输入输出接口,是一个简单的两线串行接口,一根 MDIO 数据线,一根 MDC 时钟线。
驱动程序可以通过 MDIO 和MDC 这两根线访问 PHY 芯片的任意一个寄存器。 MDIO 接口支持多达 32 个 PHY。同一时刻内只能对一个 PHY 进行操作,那么如何区分这 32 个 PHY 芯片呢?和 IIC 一样,使用器件地址即可。同一 MDIO 接口下的所有 PHY 芯片,其器件地址不能冲突,必须保证唯一,具体器件地址值要查阅相应的 PHY 数据手册。(通常可以使用硬件来决定phy地址)

因此, MAC 和外部 PHY 芯片进行连接的时候主要是 MII/RMII 和 MDIO 接口,另外可能还需要复位、中断等其他引脚。

RJ45 接口

网络设备是通过网线连接起来的,插入网线的叫做 RJ45 座,如图 69.1.4.1 所示:

RJ45 座要与 PHY 芯片连接在一起,但是中间需要一个网络变压器,网络编译器用于隔离
以及滤波等,网络变压器也是一个芯片,外形一般如图 69.1.4.2 所示:

RJ45 座子上一般有两个灯,一个黄色(橙色),一个绿色,绿色亮的话表示网络连接正常,黄色闪烁的话说明当前正在进行网络通信。这两个灯由 PHY 芯片控制, PHY 芯片会有两个引脚来连接 RJ45 座上的这两个灯。内部 MAC+外部 PHY+RJ45 座(内置网络变压器)就组成了一
个完整的嵌入式网络接口硬件,如图 69.1.4.2 所示:

PHY 基础知识简介

PHY 是 IEEE 802.3 规定的一个标准模块,前面说了, SOC 可以对 PHY 进行配置或者读取PHY 相关状态,这个就需要 PHY 内部寄存器去实现了。 PHY 芯片寄存器地址空间为 5 位,地址 0-31 共 32 个寄存器, IEEE 定义了 0-15 这 16 个寄存器的功能, 16~31 这 16 个寄存器由厂商自行实现。 也就是说不管你用的哪个厂家的 PHY 芯片,其中 0~15 这 16 个寄存器是一模一样的。 仅靠这 16 个寄存器是完全可以驱动起 PHY 芯片的,至少能保证基本的网络数据通信。
因此 Linux 内核有通用 PHY 驱动,按道理来讲,不管你使用的哪个厂家的 PHY 芯片,都可以使用 Linux 的这个通用 PHY 驱动来验证网络工作是否正常。

但是,随着现在的 PHY 芯片性能越来越强大, 32 个寄存器可能满足不了厂商的需求,因此很多厂商采用分页技术来扩展寄存器地址空间,以求定义更多的寄存器。

如图为phy 寄存器列表:

PHY 地址设置

MAC 层通过 MDIO/MDC 总线对 PHY 进行读写操作, MDIO 最多可以控制 32 个 PHY 芯片,通过不同的 PHY 芯片地址来对不同的 PHY 操作。
LAN8720A(phy)芯片 通过设置 RXER/PHYAD0引脚来设置其 PHY 地址,默认情况下为 0,其地址设置如表 69.2.2.1 所示。

linux 内核网络驱动框架

1、net_device 结构体

Linux 内核用一个 net_device 结构体来描述一个网络设备。
网络驱动的核心就是初始化 net_device 结构体中的各个成员变量,然后将初始化完成以后的 net_device 注册到 Linux 内核中。
net_device 结构体定义在 include/linux/netdevice.h 中,net_device 是一个庞大的结构体,内容如下(有缩减):

示例代码 69.3.1.1 net_device 结构体
1 struct net_device {2 char name[IFNAMSIZ];
3 struct hlist_node name_hlist;
4 char *ifalias;
5 /*
6 * I/O specific fields
7 * FIXME: Merge these and struct ifmap into one
8 */
9 unsigned long mem_end;
10 unsigned long mem_start;
11 unsigned long base_addr;
12 int irq;
13
14 atomic_t carrier_changes;
15
16 /*
17 * Some hardware also needs these fields (state,dev_list,
18 * napi_list,unreg_list,close_list) but they are not
19 * part of the usual set specified in Space.c.
20 */
21
22 unsigned long state;
23
24 struct list_head dev_list;
25 struct list_head napi_list;
26 struct list_head unreg_list;
27 struct list_head close_list;
......
60 const struct net_device_ops *netdev_ops;
61 const struct ethtool_ops *ethtool_ops;
62 #ifdef CONFIG_NET_SWITCHDEV
63 const struct swdev_ops *swdev_ops;
64 #endif
65
66 const struct header_ops *header_ops;
67
68 unsigned int flags;
......
77 unsigned char if_port;
78 unsigned char dma;
79
80 unsigned int mtu;
81 unsigned short type;
82 unsigned short hard_header_len;
83
84 unsigned short needed_headroom;
85 unsigned short needed_tailroom;
86
87 /* Interface address info. */
88 unsigned char perm_addr[MAX_ADDR_LEN];
89 unsigned char addr_assign_type;
90 unsigned char addr_len;
......
130 /*
131 * Cache lines mostly used on receive path (including
eth_type_trans())
132 */
133 unsigned long last_rx;
134
135 /* Interface address info used in eth_type_trans() */
136 unsigned char *dev_addr;
137
138
139 #ifdef CONFIG_SYSFS
140 struct netdev_rx_queue *_rx;
141
142 unsigned int num_rx_queues;
143 unsigned int real_num_rx_queues;
144
145 #endif
......
158 /*
159 * Cache lines mostly used on transmit path
160 */
161 struct netdev_queue *_tx ____cacheline_aligned_in_smp;
162 unsigned int num_tx_queues;
163 unsigned int real_num_tx_queues;
164 struct Qdisc *qdisc;
165 unsigned long tx_queue_len;
166 spinlock_t tx_global_lock;
167 int watchdog_timeo;
......
173 /* These may be needed for future network-power-down code. */
174
175 /*
176 * trans_start here is expensive for high speed devices on SMP,
177 * please use netdev_queue->trans_start instead.
178 */
179 unsigned long trans_start;
......
248 struct phy_device *phydev;
249 struct lock_class_key *qdisc_tx_busylock;
250 };

下面介绍一些关键的成员变量,如下:
第 2 行: name 是网络设备的名字。
第 9 行: mem_end 是共享内存结束地址。
第 10 行: mem_start 是共享内存起始地址。
第 11 行: base_addr 是网络设备 I/O 地址。
第 12 行: irq 是网络设备的中断号。
第 24 行: dev_list 是全局网络设备列表。
第 25 行: napi_list 是 napi 网络设备的列表入口。
第 26 行: unreg_list 是注销(unregister)的网络设备列表入口。
第 27 行: close_list 是关闭的网络设备列表入口。
第 60 行: netdev_ops 是网络设备的操作集函数,包含了一系列的网络设备操作回调函数,类似字符设备中的 file_operations。
第 61 行: ethtool_ops 是网络管理工具相关函数集,用户空间网络管理工具会调用此结构体中的相关函数获取网卡状态或者配置网卡。
第 66 行: header_ops 是头部的相关操作函数集,比如创建、解析、缓冲等。
第 68 行: flags 是网络接口标志 ,标志类型定义在 include/uapi/linux/if.h 文件中,为一个枚举类型 ,内容如下:

示例代码 69.3.1.2 网络标志类型
1 enum net_device_flags {2 IFF_UP = 1<<0, /* sysfs */
3 IFF_BROADCAST = 1<<1, /* volatile */
4 IFF_DEBUG = 1<<2, /* sysfs */
5 IFF_LOOPBACK = 1<<3, /* volatile */
6 IFF_POINTOPOINT = 1<<4, /* volatile */
7 IFF_NOTRAILERS = 1<<5, /* sysfs */
8 IFF_RUNNING = 1<<6, /* volatile */
9 IFF_NOARP = 1<<7, /* sysfs */
10 IFF_PROMISC = 1<<8, /* sysfs */
11 IFF_ALLMULTI = 1<<9, /* sysfs */
12 IFF_MASTER = 1<<10, /* volatile */
13 IFF_SLAVE = 1<<11, /* volatile */
14 IFF_MULTICAST = 1<<12, /* sysfs */
15 IFF_PORTSEL = 1<<13, /* sysfs */
16 IFF_AUTOMEDIA = 1<<14, /* sysfs */
17 IFF_DYNAMIC = 1<<15, /* sysfs */
18 IFF_LOWER_UP = 1<<16, /* volatile */
19 IFF_DORMANT = 1<<17, /* volatile */
20 IFF_ECHO = 1<<18, /* volatile */
21 };

第 77 行: **if_port 指定接口的端口类型,如果设备支持多端口的话就通过 if_port 来指定所使用的端口类型。**可选的端口类型定义在 include/uapi/linux/netdevice.h 中,为一个枚举类型,如下所示:

示例代码 69.3.1.3 端口类型
1 enum {2 IF_PORT_UNKNOWN = 0,
3 IF_PORT_10BASE2,
4 IF_PORT_10BASET,
5 IF_PORT_AUI,
6 IF_PORT_100BASET,
7 IF_PORT_100BASETX,
8 IF_PORT_100BASEFX
9 };

第 78 行: dma 是网络设备所使用的 DMA 通道,不是所有的设备都会用到 DMA。
第 80 行: mtu 是网络最大传输单元,为 1500。
第 81 行: type 用于指定 ARP 模块的类型,以太网的 ARP 接口为 ARPHRD_ETHER, Linux内核所支持的 ARP 协议定义在 include/uapi/linux/if_arp.h 中,大家自行查阅。
第 88 行: perm_addr 是永久的硬件地址,如果某个网卡设备有永久的硬件地址,那么就会填充 perm_addr。
第 90 行: addr_len 是硬件地址长度。
第 133 行: last_rx 是最后接收的数据包时间戳,记录的是 jiffies。
第 136 行: dev_addr 也是硬件地址,是当前分配的 MAC 地址,可以通过软件修改。
第 140 行: ** _rx 是接收队列。**
第 142 行: num_rx_queues 是接收队列数量,在调用 register_netdev 注册网络设备的时候会分配指定数量的接收队列。
第 143 行: real_num_rx_queues 是当前有效的队列数量。
第 161 行: ** _tx 是发送队列。**
第 162 行: num_tx_queues 是发送队列数量,通过 alloc_netdev_mq 函数分配指定数量的发送队列。
第 163 行: real_num_tx_queues 是当前有效的发送队列数量。
第 179 行: trans_start 是最后的数据包发送的时间戳,记录的是 jiffies。
第 248 行: phydev 是对应的 PHY 设备。

注册与卸载网络驱动

申请 net_device (分配net_device 空间)

编写网络驱动的时候首先要申请 net_device,使用 alloc_netdev 函数 来申请 net_device,这是一个宏,宏定义如下:

示例代码 69.3.1.4 alloc_netdev
#define alloc_netdev(sizeof_priv, name, name_assign_type, setup) \alloc_netdev_mqs(sizeof_priv, name, name_assign_type, setup, 1, 1)

可以看出 alloc_netdev 的本质是 alloc_netdev_mqs 函数,此函数原型如下:

struct net_device * alloc_netdev_mqs ( int sizeof_priv,const char *name,void (*setup) (struct net_device *),unsigned int txqs,unsigned int rxqs);

sizeof_priv: 私有数据块大小。
name: 设备名字。
setup: 回调函数,初始化设备时调用此函数。
txqs: 分配的发送队列数量。
rxqs: 分配的接收队列数量。
返回值: 如果申请成功的话就返回申请到的 net_device 指针,失败的话就返回 NULL。

事实上网络设备有多种,大家不要以为就只有以太网一种。 Linux 内核内核支持的网络接口有很多,比如光纤分布式数据接口(FDDI)、以太网设备(Ethernet)、红外数据接口(InDA)、高性能并行接口(HPPI)、 CAN 网络等

内核针对不同的网络设备在 alloc_netdev 的基础上提供了一层封装 ,比如我们本章讲解的以太网,针对以太网封装的 net_device 申请函数是 alloc_etherdev和,这也是一个宏,内容如下:

示例代码 69.3.1.5 alloc_etherdev 函数
#define alloc_etherdev(sizeof_priv)             alloc_etherdev_mq(sizeof_priv, 1)
#define alloc_etherdev_mq(sizeof_priv, count)   alloc_etherdev_mqs(sizeof_priv, count, count)struct net_device *alloc_etherdev_mqs(int sizeof_priv,unsigned int txqs,unsigned int rxqs)
{// eth%d  就是网口设备名return alloc_netdev_mqs(sizeof_priv, "eth%d", NET_NAME_UNKNOWN,ether_setup, txqs, rxqs);
}

这里设置了以太网的 setup 函数为 ether_setup,不同的网络设备其 setup函数不同,比如 CAN 网络里面 setup 函数就是 can_setup。
ether_setup 函数会对 net_device 做初步的初始化,函数内容如下所示:

void ether_setup(struct net_device *dev)
{dev->header_ops = &eth_header_ops;dev->type = ARPHRD_ETHER;dev->hard_header_len = ETH_HLEN;dev->mtu = ETH_DATA_LEN;dev->addr_len = ETH_ALEN;dev->tx_queue_len = 1000; /* Ethernet wants good queues */dev->flags = IFF_BROADCAST|IFF_MULTICAST;dev->priv_flags |= IFF_TX_SKB_SHARING;eth_broadcast_addr(dev->broadcast);
}

2、 注册 net_device

net_device 申请并初始化完成以后就需要向内核注册 net_device,要用到函数 register_netdev ,函数原型如下:

int register_netdev(struct net_device *dev)

dev: 要注册的 net_device 指针。
返回值: 0 注册成功,负值 注册失败。

3、 注销 net_device

既然有注册,那么必然有注销,注销 net_device 使用函数 unregister_netdev ,函数原型如下:

void unregister_netdev(struct net_device *dev)

dev: 要注销的 net_device 指针。
返回值: 无。

4、删除 net_device

当我们注销网络驱动的时候需要释放掉前面已经申请到的 net_device,释放函数为free_netdev ,函数原型如下:

void free_netdev(struct net_device *dev)

dev: 要释放掉的 net_device 指针。
返回值: 无。

net_device_ops 结构体

net_device 有个非常重要的成员变量: netdev_ops,为 net_device_ops 结构体指针类型,这就是网络设备的操作集。
net_device_ops 结构体定义在 include/linux/netdevice.h 文件中,net_device_ops 结构体里面都是一些以“ndo_”开头的函数,这些函数就需要网络驱动编写人员去实现。
结构体内容如下所示(结构体比较大,这里有缩减):

示例代码 69.3.2.1 net_device_ops 结构体
1 struct net_device_ops {2 int (*ndo_init)(struct net_device *dev);
3 void (*ndo_uninit)(struct net_device *dev);
4 int (*ndo_open)(struct net_device *dev);
5 int (*ndo_stop)(struct net_device *dev);
6 netdev_tx_t (*ndo_start_xmit) (struct sk_buff *skb,
7 struct net_device *dev);
8 u16 (*ndo_select_queue)(struct net_device *dev,
9 struct sk_buff *skb,
10 void *accel_priv,
11 select_queue_fallback_t fallback);
12 void (*ndo_change_rx_flags)(struct net_device *dev,
13 int flags);
14 void (*ndo_set_rx_mode)(struct net_device *dev);
15 int (*ndo_set_mac_address)(struct net_device *dev,
16 void *addr);
17 int (*ndo_validate_addr)(struct net_device *dev);
18 int (*ndo_do_ioctl)(struct net_device *dev,
19 struct ifreq *ifr, int cmd);
20 int (*ndo_set_config)(struct net_device *dev,
21 struct ifmap *map);
22 int (*ndo_change_mtu)(struct net_device *dev,
23 int new_mtu);
24 int (*ndo_neigh_setup)(struct net_device *dev,
25 struct neigh_parms *);
26 void (*ndo_tx_timeout) (struct net_device *dev);
......
36 #ifdef CONFIG_NET_POLL_CONTROLLER
37 void (*ndo_poll_controller)(struct net_device *dev);
38 int (*ndo_netpoll_setup)(struct net_device *dev,
39 struct netpoll_info *info);
40 void (*ndo_netpoll_cleanup)(struct net_device *dev);
41 #endif
......
104 int (*ndo_set_features)(struct net_device *dev,
105 netdev_features_t features);
......
166 };

第 2 行: ndo_init 函数,当第一次注册网络设备的时候此函数会执行,设备可以在此函数中做一些需要退后初始化的内容,不过一般驱动中不使用此函数,虚拟网络设备可能会使用。
第 3 行: ndo_uninit 函数,卸载网络设备的时候此函数会执行。
第 4 行: ndo_open 函数,打开网络设备的时候此函数会执行,网络驱动程序需要实现此函数 ,非常重要!以 NXP 的 I.MX 系列 SOC 网络驱动为例,会在此函数中做如下工作:

  • 使能网络外设时钟。
  • 申请网络所使用的环形缓冲区。
  • 初始化 MAC 外设。
  • 绑定接口对应的 PHY。
  • 如果使用 NAPI 的话要使能 NAPI 模块,通过 napi_enable 函数来使能。
  • 开启 PHY。
  • 调用 netif_tx_start_all_queues 来使能传输队列,也可能调用 netif_start_queue 函数。
  • ……
    第 5 行: ndo_stop 函数,关闭网络设备的时候此函数会执行,网络驱动程序也需要实现此函数。 以 NXP 的 I.MX 系列 SOC 网络驱动为例,会在此函数中做如下工作:
  • 停止 PHY。
  • 停止 NAPI 功能。
  • 停止发送功能。
  • 关闭 MAC。
  • 断开 PHY 连接。
  • 关闭网络时钟。
  • 释放数据缓冲区。
  • ……
    第 6 行: ndo_start_xmit 函数,当需要发送数据的时候此函数就会执行,此函数有一个参数为 sk_buff 结构体指针, sk_buff 结构体在Linux 的网络驱动中非常重要, sk_buff 保存了上层传递给网络驱动层的数据。也就是说,要发送出去的数据都存在了 sk_buff 中。
    如果发送成功的话此函数返回 NETDEV_TX_OK ,如果发送失败了就返回NETDEV_TX_BUSY ,如果发送失败了我们就需要停止队列。
    第 8 行: ndo_select_queue 函数,当设备支持多传输队列的时候选择使用哪个队列。
    第 14 行: ndo_set_rx_mode 函数,此函数用于改变地址过滤列表,根据 net_device 的 flags成员变量来设置 SOC 的网络外设寄存器。比如 flags 可能为 IFF_PROMISC、 IFF_ALLMULTI 或IFF_MULTICAST,分别表示混杂模式、单播模式或多播模式。
    第 15 行: ndo_set_mac_address 函数,此函数用于修改网卡的 MAC 地址,设置 net_device的 dev_addr 成员变量, 并且将 MAC 地址写入到网络外设的硬件寄存器中。
    第 17 行: ndo_validate_addr 函数,验证 MAC 地址是否合法,也即是验证 net_device 的 dev_addr 中的 MAC 地址是否合法,直接调用 is_valid_ether_addr 函数。
    第 18 行: ndo_do_ioctl 函数,用户程序调用 ioctl 的时候此函数就会执行,比如 PHY 芯片相关的命令操作,一般会直接调用 phy_mii_ioctl 函数。
    第 22 行: ndo_change_mtu 函数,更改 MTU 大小。
    第 26 行: ndo_tx_timeout 函数,当发送超时的时候产生会执行,一般都是网络出问题了导致发送超时。一般可能会重启 MAC 和 PHY,重新开始数据发送等。
    第 37 行: ndo_poll_controller 函数,使用查询方式来处理网卡数据的收发。
    第 104 行: ndo_set_features 函数,修改 net_device 的 features 属性,设置相应的硬件属性。

sk_buff 结构体

网络是分层的,对于应用层而言不用关系具体的底层是如何工作的,只需要按照协议将要发送或接收的数据打包好即可。
打包好以后都通过 dev_queue_xmit 函数将数据发送出去,接收数据的话使用 netif_rx 函数即可 ,我们依次来看一下这两个函数。

1、 dev_queue_xmit 函数

此函数用于将网络数据发送出去,函数定义在 include/linux/netdevice.h 中,函数原型如下:

static inline int dev_queue_xmit(struct sk_buff *skb)

skb: 要发送的数据, 这是一个 sk_buff 结构体指针, sk_buff 是 Linux 网络驱动中一个非常重要的结构体,网络数据就是以 sk_buff 保存的 ,各个协议层在 sk_buff 中添加自己的协议头,最终由底层驱动讲 sk_buff 中的数据发送出去。网络数据的接收过程恰好相反,网络底层驱动将接收到的原始数据打包成 sk_buff,然后发送给上层协议,上层会取掉相应的头部,然后将最终的数据发送给用户。
网络协议层
返回值: 0 发送成功,负值 发送失败。

dev_queue_xmit 函数最终是通过net_device_ops 操作集里面的 ndo_start_xmit 函数来完成最终发送了 , ndo_start_xmit 就是网络驱动编写人员去实现的,整个流程如图 69.3.3.1 所示:

2、 netif_rx 函数

上层接收数据的话使用 netif_rx 函数,但是最原始的网络数据一般是通过轮询、中断或 NAPI的方式来接收。 netif_rx 函数定义在 net/core/dev.c 中,函数原型如下:

int netif_rx(struct sk_buff *skb)

skb: 保存接收数据的 sk_buff。
返回值: NET_RX_SUCCESS 成功, NET_RX_DROP 数据包丢弃。

我们重点来看一下 sk_buff 这个结构体, sk_buff 是 Linux 网络重要的数据结构,用于管理接收或发送数据包 , sk_buff 结构体定义在 include/linux/skbuff.h 中,结构体内容如下(有缩减):

1 struct sk_buff {2       union {3           struct {4           /* These two members must be first. */
5               struct sk_buff *next;
6               struct sk_buff *prev;
7
8               union {9                   ktime_t tstamp;
10                  struct skb_mstamp skb_mstamp;
11              };
12          };
13          struct rb_node rbnode; /* used in netem & tcp stack */
14      };
15      struct sock *sk;
16      struct net_device *dev;
17
18 /*
19 * This is the control buffer. It is free to use for every
20 * layer. Please put your private variables there. If you
21 * want to keep them across layers you have to do a skb_clone()
22 * first. This is owned by whoever has the skb queued ATM.
23 */
24      char cb[48] __aligned(8);
25
26      unsigned long _skb_refdst;
27      void (*destructor)(struct sk_buff *skb);......
37      unsigned int len, data_len;
38      __u16 mac_len, hdr_len;......
145     __be16 protocol;
146     __u16 transport_header;
147     __u16 network_header;
148     __u16 mac_header;
149
150     /* private: */
151     __u32 headers_end[0];
152     /* public: */
153
154     /* These elements must be at the end, see alloc_skb() for details. */
155     sk_buff_data_t tail;
156     sk_buff_data_t end;
157     unsigned char *head, *data;
158     unsigned int truesize;
159     atomic_t users;
160 };

第 5~6 行: next 和 prev 分别指向下一个和前一个 sk_buff,构成一个双向链表。
第 9 行: tstamp 表示数据包接收时或准备发送时的时间戳。
第 15 行: sk 表示当前 sk_buff 所属的 Socket。
第 16 行: dev 表示当前 sk_buff 从哪个设备接收到或者发出的。
第 24 行: cb 为控制缓冲区,不管哪个层都可以自由使用此缓冲区,用于放置私有数据。
第 27 行: destructor 函数,当释放缓冲区的时候可以在此函数里面完成某些动作。
第 37 行: len 为实际的数据长度,包括主缓冲区中数据长度和分片中的数据长度。 data_len为数据长度,只计算分片中数据的长度。
第 38 行: mac_len 为连接层头部长度,也就是 MAC 头的长度。
第 145 行: protocol 协议。
第 146 行: transport_header 为传输层头部。
第 147 行: network_header 为网络层头部
第 148 行: mac_header 为链接层头部。
第 155 行: tail 指向实际数据的尾部。
第 156 行: end 指向缓冲区的尾部。
第 157 行: head 指向缓冲区的头部, data 指向实际数据的头部。 data 和 tail 指向实际数据的头部和尾部, head 和 end 指向缓冲区的头部和尾部。 结构如图 69.3.3.2 所示:

针对 sk_buff 内核提供了一系列的操作与管理函数,我们简单看一些常见的 API 函数:

1、 分配 sk_buff

要使用 sk_buff 必须先分配,首先来看一下 alloc_skb 这个函数 ,此函数定义在include/linux/skbuff.h 中,函数原型如下:

static inline struct sk_buff *alloc_skb(unsigned int size,gfp_t priority)

size: 要分配的大小,也就是 skb 数据段大小。
priority: 为 GFP MASK 宏,比如 GFP_KERNEL、 GFP_ATOMIC 等。
返回值: 分配成功的话就返回申请到的 sk_buff 首地址,失败的话就返回 NULL。

在网络设备驱动中常常使用 netdev_alloc_skb 来为某个设备申请一个用于接收的 skb_buff。
此函数也定义在 include/linux/skbuff.h 中,函数原型如下:

static inline struct sk_buff *netdev_alloc_skb(struct net_device *dev,unsigned int length)

dev: 要给哪个设备分配 sk_buff。
length: 要分配的大小。
返回值: 分配成功的话就返回申请到的 sk_buff 首地址,失败的话就返回 NULL。

2、 释放 sk_buff

当使用完成以后就要释放掉 sk_buff,释放函数可以使用 kfree_skb,函数定义在include/linux/skbuff.c 中,函数原型如下:

void kfree_skb(struct sk_buff *skb)

skb: 要释放的 sk_buff。
返回值: 无。

对于网络设备而言最好使用如下所示释放函数:

void dev_kfree_skb (struct sk_buff *skb)

函数只要一个参数 skb,就是要释放的 sk_buff。

3、 skb_put、 skb_push、 sbk_pull 和 skb_reserve

这四个函数用于变更 sk_buff,先来看一下 skb_put 函数,此函数用于在尾部扩展 skb_buff的数据区,也就将 skb_buff 的 tail 后移 n 个字节,从而导致 skb_buff 的 len 增加 n 个字节 ,原型如下:

unsigned char *skb_put(struct sk_buff *skb, unsigned int len)

skb: 要操作的 sk_buff。
len: 要增加多少个字节。
返回值: 扩展出来的那一段数据区首地址。
skb_put 操作之前和操作之后的数据区如图 69.3.3.3 所示:

kb_push 函数 用于在头部扩展 skb_buff 的数据区 ,函数原型如下所示:

unsigned char *skb_push(struct sk_buff *skb, unsigned int len)

skb: 要操作的 sk_buff。
len: 要增加多少个字节。
返回值: 扩展完成以后新的数据区首地址。

sbk_pull 函数 用于从 sk_buff 的数据区起始位置删除数据 ,函数原型如下所示:

unsigned char *skb_pull(struct sk_buff *skb, unsigned int len)

skb: 要操作的 sk_buff。
len: 要删除的字节数。
返回值: 删除以后新的数据区首地址。

sbk_reserve 函数 用于调整缓冲区的头部大小,方法很简单讲 skb_buff 的 data 和 tail 同时后移 n 个字节即可,函数原型如下所示:

static inline void skb_reserve(struct sk_buff *skb, int len)

skb: 要操作的 sk_buff。
len: 要增加的缓冲区头部大小。
返回值: 无。

网络 NAPI 处理机制

如果玩过单片机的话应该都知道,像 IIC、 SPI、网络等这些通信接口,接收数据有两种方法:轮询或中断
Linux 里面的网络数据接收也轮询和中断两种,中断的好处就是响应快,数据量小的时候处理及时,速度快,但是一旦当数据量大,而且都是短帧的时候会导致中断频繁发生,消耗大量的 CPU 处理时间在中断自身处理上。轮询恰好相反,响应没有中断及时,但是在处理大量数据的时候不需要消耗过多的 CPU 处理时间。

Linux 在这两个处理方式的基础上提出了另外一种网络数据接收的处理方法: NAPI(New API), NAPI 是一种高效 的网络处理技术。NAPI 的核心思想就是不全部采用中断来读取网络数据 ,而是采用中断来唤醒数据接收服务程序,在接收服务程序中采用 POLL 的方法来轮询处理数据。 这种方法的好处就是可以提高短数据包的接收效率,减少中断处理的时间
目前 NAPI 已经在 Linux 的网络驱动中得到了大量的应用, NXP 官方编写的网络驱动都是采用的 NAPI 机制。

Linux 内核使用结构体 napi_struct 表示 NAPI,在使用 NAPI 之前要先初始化一个 napi_struct 实例。

1、初始化 NAPI

首先要初始化一个 napi_struct 实例,并且关于对应的网络设备 ,使用 netif_napi_add 函数,此函数定义在 net/core/dev.c 中,函数原型如下:

void netif_napi_add(struct net_device *dev, struct napi_struct *napi, int (*poll)(struct napi_struct *, int), int weight)

dev: 每个 NAPI 必须关联一个网络设备,此参数指定 NAPI 要关联的网络设备。
napi: 要初始化的 NAPI 实例。
poll: NAPI 所使用的轮询函数,非常重要,一般在此轮询函数中完成网络数据接收的工作。
weight: NAPI 默认权重(weight),一般为 NAPI_POLL_WEIGHT。
返回值: 无。

2、删除 NAPI

如果要删除 NAPI,使用 netif_napi_del 函数即可,函数原型如下:

void netif_napi_del(struct napi_struct *napi)

napi: 要删除的 NAPI。
返回值: 无。

3、 使能 NAPI

初始化完 NAPI 以后,必须使能才能使用,使用函数 napi_enable,函数原型如下:

inline void napi_enable(struct napi_struct *n)

n: 要使能的 NAPI。
返回值: 无。

4、关闭 NAPI

关闭 NAPI 使用 napi_disable 函数即可,函数原型如下:

void napi_disable(struct napi_struct *n)

n: 要关闭的 NAPI。
返回值: 无。

5、检查 NAPI 是否可以进行调度

使用 napi_schedule_prep 函数检查 NAPI 是否可以进行调度,函数原型如下:

inline bool napi_schedule_prep(struct napi_struct *n)

函数参数和返回值含义如下:
n: 要检查的 NAPI。
返回值: 如果可以调度就返回真,如果不可调度就返回假。

6、 NAPI 调度

如果可以调度的话就进行调度,使用 ** __napi_schedule 函数** 完成 NAPI 调度,函数原型如下:

void __napi_schedule(struct napi_struct *n)

函数参数和返回值含义如下:
n: 要调度的 NAPI。
返回值: 无。

我们也可以使用 napi_schedule 函数来一次完成 napi_schedule_prep 和__napi_schedule 这两个函数的工作 , napi_schedule 函数内容如下所示:

static inline void napi_schedule(struct napi_struct *n)
{if (napi_schedule_prep(n))__napi_schedule(n);
}

7、 NAPI 处理完成

NAPI 处理完成以后需要调用 napi_complete 函数来标记 NAPI 处理完成,函数原型如下:

inline void napi_complete(struct napi_struct *n)

函数参数和返回值含义如下:
n: 处理完成的 NAPI。
返回值: 无。

I.MX6ULL 网络驱动简介

1、I.MX6ULL 网络外设设备树

本节我们就来简单分析一下 I.MX6ULL 的网络驱动源码。 I.MX6ULL 有两个 10/100M 的网络 MAC 外设,因此 I.MX6ULL网络驱动主要就是这两个网络 MAC 外设的驱动。这两个外设的驱动都是一样的,我们分析其中 一 个 就 行 了 , 首 先 肯 定 是 设 备 树 , NXP 的 I.MX 系 列 SOC 网 络 绑 定 文 档 为Documentation/devicetree/bindings/net/fsl-fec.txt,此绑定文档描述了 I.MX 系列 SOC 网络设备树节点的要求。

①、 必要属性

compatible: 这个肯定是必须的,一般是“fsl,-fec”,比如 I.MX6ULL 的 compatible 属性就是"fsl,imx6ul-fec",和"fsl,imx6q-fec"。
reg: SOC 网络外设寄存器地址范围。
interrupts: 网络中断。应该还会有 interrupt-parent 描述其上级中断控制器。
phy-mode: 网络所使用的 PHY 接口模式,是 MII 还是 RMII。

②、 可选属性

phy-reset-gpios: PHY 芯片的复位引脚。
phy-reset-duration: PHY 复位引脚复位持续时间,单位为毫秒。只有当设置了 phy-resetgpios 属性此属性才会有效,如果不设置此属性的话 PHY 芯片复位引脚的复位持续时间默认为1 毫秒,数值不能大于 1000 毫秒,大于 1000 毫秒的话就会强制设置为 1 毫秒。
phy-supply: PHY 芯片的电源调节。
phy-handle: 连接到此网络设备的 PHY 芯片句柄。
fsl,num-tx-queues: 此属性指定发送队列的数量,如果不指定的话默认为 1。
fsl,num-rx-queues: 此属性指定接收队列的数量,如果不指定的话默认为 2。
fsl,magic-packet: 此属性不用设置具体的值,直接将此属性名字写到设备树里面即可,表示支持硬件魔术帧唤醒。
fsl,wakeup_irq: 此属性设置唤醒中断索引。
stop-mode: 如果此属性存在的话表明 SOC 需要设置 GPR 位来请求停止模式。

③、可选子节点

mdio: 可以设置名为“mdio”的子节点,此子节点用于指定网络外设所使用的 MDIO 总线,主要做为 PHY 节点的容器,也就是在 mdio 子节点下指定 PHY 相关的属性信息 ,具体信息可以参考 PHY 的绑定文档 Documentation/devicetree/bindings/net/phy.txt。

PHY 节点相关属性内容如下:

interrupts: 中断属性,可以不需要。
interrupt-parent: 中断控制器句柄,可以不需要。
reg: PHY 芯片地址,必须的!
compatible: 兼容性列表,一般为“ethernet-phy-ieee802.3-c22”或“ethernet-phy-ieee802.3-c45”,分别对应 IEEE802.3 的 22 簇和 45 簇,默认是 22 簇。也可以设置为其他值,如果 PHY的 ID 不知道的话可以 compatible 属性可以设置为“ethernet-phy-idAAAA.BBBB”,AAAA 和BBBB 的含义如下:
AAAA: PHY 的 16 位 ID 寄存器 1 值,也就是 OUI 的 bit3~18, 16 进制格式。
BBBB: PHY 的 16 位 ID 寄存器 2 值,也就是 OUI 的 bit19~24, 16 进制格式。
max-speed: PHY 支持的最高速度,比如 10、 100 或 1000。

打开 imx6ull.dtsi,找到如下 I.MX6ULL 的两个网络外设节点,如下所示:

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";
};fec2: ethernet@020b4000 {compatible = "fsl,imx6ul-fec", "fsl,imx6q-fec";reg = <0x020b4000 0x4000>;interrupts = <GIC_SPI 120 IRQ_TYPE_LEVEL_HIGH>,<GIC_SPI 121 IRQ_TYPE_LEVEL_HIGH>;clocks = <&clks IMX6UL_CLK_ENET>,<&clks IMX6UL_CLK_ENET_AHB>,<&clks IMX6UL_CLK_ENET_PTP>,<&clks IMX6UL_CLK_ENET2_REF_125M>,<&clks IMX6UL_CLK_ENET2_REF_125M>;clock-names = "ipg", "ahb", "ptp","enet_clk_ref", "enet_out";stop-mode = <&gpr 0x10 4>;fsl,num-tx-queues=<1>;fsl,num-rx-queues=<1>;fsl,magic-packet;fsl,wakeup_irq = <0>;status = "disabled";
};

fec1 和 fec2 分别对应 I.MX6ULL 的 ENET1 和 ENET2,示例代码 69.4.1.1 是 NXP 官方编写的,我们不需要去修改,但是示例代码 69.4.1.1 是不能工作的,还需要根据实际情况添加或修改一些属性(在具体的板机设备树修改)。打开 imx6ull-alientek-emmc.dts,找到如下内容:

&fec1 {pinctrl-names = "default";pinctrl-0 = <&pinctrl_enet1 &pinctrl_enet1_reset>;        //通过该节点找到 io 控制节点phy-mode = "rmii";              //标明 mac 与phy 连接的接口模式phy-handle = <&ethphy0>;        //从该属性可以找到phy 节点phy-reset-gpios = <&gpio5 7 GPIO_ACTIVE_LOW>;        //复位引脚 gpio描述phy-reset-duration = <200>;                         //复位有效时间:msstatus = "okay";               //使能该控制器
};&fec2 {pinctrl-names = "default";pinctrl-0 = <&pinctrl_enet2 &pinctrl_enet2_reset>;phy-mode = "rmii";phy-handle = <&ethphy1>;phy-reset-gpios = <&gpio5 8 GPIO_ACTIVE_LOW>;phy-reset-duration = <200>;status = "okay";mdio {                  //用于描述 MIDO 总线,在此子节点内会包含 PHY 节点信息。#address-cells = <1>;       //为了确定子节点的reg 属性的cells#size-cells = <0>;ethphy0: ethernet-phy@0 {       //描述phy 节点compatible = "ethernet-phy-ieee802.3-c22";reg = <0>;                //phy 芯片地址};ethphy1: ethernet-phy@1 {compatible = "ethernet-phy-ieee802.3-c22";reg = <1>;};};
};pinctrl_enet1: enet1grp { //enet1 引脚配置节点fsl,pins = <MX6UL_PAD_ENET1_RX_EN__ENET1_RX_EN 0x1b0b0MX6UL_PAD_ENET1_RX_ER__ENET1_RX_ER 0x1b0b0MX6UL_PAD_ENET1_RX_DATA0__ENET1_RDATA00 0x1b0b0MX6UL_PAD_ENET1_RX_DATA1__ENET1_RDATA01 0x1b0b0MX6UL_PAD_ENET1_TX_EN__ENET1_TX_EN 0x1b0b0MX6UL_PAD_ENET1_TX_DATA0__ENET1_TDATA00 0x1b0b0MX6UL_PAD_ENET1_TX_DATA1__ENET1_TDATA01 0x1b0b0MX6UL_PAD_ENET1_TX_CLK__ENET1_REF_CLK1 0x4001b009>;
};pinctrl_enet2: enet2grp {fsl,pins = <
MX6UL_PAD_GPIO1_IO07__ENET2_MDC 0x1b0b0
MX6UL_PAD_GPIO1_IO06__ENET2_MDIO 0x1b0b0
MX6UL_PAD_ENET2_RX_EN__ENET2_RX_EN 0x1b0b0
MX6UL_PAD_ENET2_RX_ER__ENET2_RX_ER 0x1b0b0
MX6UL_PAD_ENET2_RX_DATA0__ENET2_RDATA00 0x1b0b0
MX6UL_PAD_ENET2_RX_DATA1__ENET2_RDATA01 0x1b0b0
MX6UL_PAD_ENET2_TX_EN__ENET2_TX_EN 0x1b0b0
MX6UL_PAD_ENET2_TX_DATA0__ENET2_TDATA00 0x1b0b0
MX6UL_PAD_ENET2_TX_DATA1__ENET2_TDATA01 0x1b0b0
MX6UL_PAD_ENET2_TX_CLK__ENET2_REF_CLK2 0x4001b009
>;
};/*enet1 reset phy 芯片复位应交配置*/
pinctrl_enet1_reset: enet1resetgrp {fsl,pins = </* used for enet1 reset */MX6ULL_PAD_SNVS_TAMPER7__GPIO5_IO07 0x10B0>;
};/*enet2 reset */
pinctrl_enet2_reset: enet2resetgrp {fsl,pins = </* used for enet2 reset */MX6ULL_PAD_SNVS_TAMPER8__GPIO5_IO08 0x10B0>;
};

注意: 不是所有的IO 都属于同一个 IO 控制器,所以引脚的配置节点,需要放置在各自的IO 控制器节点下。
pinctrl_enet1 和 pinctrl_enet1_reset 问 ENET1 所有的 IO 引脚 pinctrl 信息,之所以分两部分主 要 是 因 为 ENET1 的 复 位 引 脚 为 GPIO5_IO07 , 而 GPIO5_IO07 对 应 的 引 脚 就 是SNVS_TAMPER7,要放到 iomuxc_snvs 节点下,所以就分成了两部分。

IMX6U 网口驱动源码分析

对于imx6ull 而言网络驱动分为 网络外设驱动phy驱动 两部分组成,网络外设驱动又NXP 编写,phy驱动有通用的驱动程序或是厂商定制的驱动。一般不需要我们编写。

网络外设驱动

从设备树的compatible属性,我们可以找到与之匹配的驱动程序。of_device_id 用于匹配设备树与驱动程序。(imx6ull驱动源码在 drivers/net/ethernet/freescale/fec_main.c)

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 */ }
};

probe 函数就是驱动的加载入口,remove 卸载入口。

static struct platform_driver fec_driver = {.driver = {.name   = DRIVER_NAME,.pm  = &fec_pm_ops,.of_match_table = fec_dt_ids,},.id_table = fec_devtype,.probe  = fec_probe,.remove    = fec_drv_remove,
};
&fec1 {pinctrl-names = "default";pinctrl-0 = <&pinctrl_enet1 &pinctrl_enet1_reset>;        //通过该节点找到 io 控制节点phy-mode = "rmii";              //标明 mac 与phy 连接的接口模式phy-handle = <&ethphy0>;        //从该属性可以找到phy 节点phy-reset-gpios = <&gpio5 7 GPIO_ACTIVE_LOW>;        //复位引脚 gpio描述phy-reset-duration = <200>;                         //复位有效时间:msstatus = "okay";               //使能该控制器
};&fec2 {pinctrl-names = "default";pinctrl-0 = <&pinctrl_enet2 &pinctrl_enet2_reset>;phy-mode = "rmii";phy-handle = <&ethphy1>;phy-reset-gpios = <&gpio5 8 GPIO_ACTIVE_LOW>;phy-reset-duration = <200>;status = "okay";mdio {                  //用于描述 MIDO 总线,在此子节点内会包含 PHY 节点信息。#address-cells = <1>;       //为了确定子节点的reg 属性的cells#size-cells = <0>;ethphy0: ethernet-phy@0 {       //描述phy 节点compatible = "ethernet-phy-ieee802.3-c22";reg = <0>;                //phy 芯片地址};ethphy1: ethernet-phy@1 {compatible = "ethernet-phy-ieee802.3-c22";reg = <1>;};};
};

probe 函数

fec_enet_get_queue_num: 从设备树获取接收和发送队列的数量。
alloc_etherdev_mqs: 申请net_device 结构体。
platform_get_resource(pdev, IORESOURCE_MEM, 0): 获取网络外设寄存器物理地址
fep->hwp = platform_get_resource(&pdev->dev, r): 映射物理地址,并保存到fep->hwp。
phy_node = of_parse_phandle(np, “phy-handle”, 0): 解析**“phy-handle”** 属性,获取phy节点。
of_get_phy_mode(pdev->dev.of_node): cpu与phy 接口模式获取。
ipg、 ahb、 enet_out、 enet_clk_ref 和 ptp 时钟的获取与使能。
fec_reset_phy: 使用复位引脚复位phy芯片。
fec_enet_init: enet初始化。收发队列内存申请、dma申请、mac地址设置、netdev_ops和ethtool_ops设置、napi添加、一些硬件寄存器设置。
devm_request_irq: 申请中断号。
fec_enet_mii_init: MDIO总线注册、mdio子节点下所有的 phy设备注册。
fec_enet_clk_enable: 网络时钟使能。
register_netdev: 注册网络设备

static int fec_probe(struct platform_device *pdev)
{struct fec_enet_private *fep;struct fec_platform_data *pdata;struct net_device *ndev;int i, irq, ret = 0;struct resource *r;const struct of_device_id *of_id;static int dev_id;struct device_node *np = pdev->dev.of_node, *phy_node;int num_tx_qs;int num_rx_qs;fec_enet_get_queue_num(pdev, &num_tx_qs, &num_rx_qs);    //从设备树获取发送队列与接收队列的数量/* Init network device */ndev = alloc_etherdev_mqs(sizeof(struct fec_enet_private),     //申请net_device 结构体num_tx_qs, num_rx_qs);if (!ndev)return -ENOMEM;SET_NETDEV_DEV(ndev, &pdev->dev);/* setup board info structure */fep = netdev_priv(ndev);             //获取私有数据结构体:记录设备的信息of_id = of_match_device(fec_dt_ids, &pdev->dev);  //匹配平台获取设备信息if (of_id)pdev->id_entry = of_id->data;fep->quirks = pdev->id_entry->driver_data;fep->netdev = ndev;fep->num_rx_queues = num_rx_qs;fep->num_tx_queues = num_tx_qs;#if !defined(CONFIG_M5272)/* default enable pause frame auto negotiation */if (fep->quirks & FEC_QUIRK_HAS_GBIT)fep->pause_flag |= FEC_PAUSE_FLAG_AUTONEG;
#endif/* Select default pin state */pinctrl_pm_select_default_state(&pdev->dev);/*platform_get_resource: 获取imx6ull 网络外设(ENET)相关寄存器起始地址。ENET1 的寄存器起始地址 0X02188000, ENET2 的寄存器起始地址 0X20B4000。platform_get_resource:转化为虚拟地址,并保存在fep->hwp。
*/r = platform_get_resource(pdev, IORESOURCE_MEM, 0);fep->hwp = platform_get_resource(&pdev->dev, r);if (IS_ERR(fep->hwp)) {ret = PTR_ERR(fep->hwp);goto failed_ioremap;}fep->pdev = pdev;fep->dev_id = dev_id++;platform_set_drvdata(pdev, ndev);fec_enet_of_parse_stop_mode(pdev);/*解析设备树是否存在fsl,magic-packet 属性,存在表示支持魔术包fep->wol_flag |= FEC_WOL_HAS_MAGIC_PACKET 表示支持魔术包*/if (of_get_property(np, "fsl,magic-packet", NULL))fep->wol_flag |= FEC_WOL_HAS_MAGIC_PACKET;/*获取phy 设备节点。设备树中 网络节点fec1、fec2 使用了phy-handle 属性指定了phy设备节点,解析属性值即可获得phy节点。*/phy_node = of_parse_phandle(np, "phy-handle", 0);if (!phy_node && of_phy_is_fixed_link(np)) {ret = of_phy_register_fixed_link(np);if (ret < 0) {dev_err(&pdev->dev,"broken fixed-link specification\n");goto failed_phy;}phy_node = of_node_get(np);}fep->phy_node = phy_node;/*获取cpu 连接phy芯片的接口模式:mii、rmii等等。记录到fec->phy_interface 中。*/ret = of_get_phy_mode(pdev->dev.of_node);if (ret < 0) {pdata = dev_get_platdata(&pdev->dev);if (pdata)fep->phy_interface = pdata->phy;elsefep->phy_interface = PHY_INTERFACE_MODE_MII;} else {fep->phy_interface = ret;}/*各路时钟的获取与使能ipg、 ahb、 enet_out、 enet_clk_ref 和 ptp。*/fep->clk_ipg = devm_clk_get(&pdev->dev, "ipg");if (IS_ERR(fep->clk_ipg)) {ret = PTR_ERR(fep->clk_ipg);goto failed_clk;}fep->clk_ahb = devm_clk_get(&pdev->dev, "ahb");if (IS_ERR(fep->clk_ahb)) {ret = PTR_ERR(fep->clk_ahb);goto failed_clk;}fep->itr_clk_rate = clk_get_rate(fep->clk_ahb);/* enet_out is optional, depends on board */fep->clk_enet_out = devm_clk_get(&pdev->dev, "enet_out");if (IS_ERR(fep->clk_enet_out))fep->clk_enet_out = NULL;fep->ptp_clk_on = false;mutex_init(&fep->ptp_clk_mutex);/* clk_ref is optional, depends on board */fep->clk_ref = devm_clk_get(&pdev->dev, "enet_clk_ref");if (IS_ERR(fep->clk_ref))fep->clk_ref = NULL;fep->bufdesc_ex = fep->quirks & FEC_QUIRK_HAS_BUFDESC_EX;fep->clk_ptp = devm_clk_get(&pdev->dev, "ptp");if (IS_ERR(fep->clk_ptp)) {fep->clk_ptp = NULL;fep->bufdesc_ex = false;}pm_runtime_enable(&pdev->dev);ret = fec_enet_clk_enable(ndev, true);if (ret)goto failed_clk;fep->reg_phy = devm_regulator_get(&pdev->dev, "phy");if (!IS_ERR(fep->reg_phy)) {ret = regulator_enable(fep->reg_phy);if (ret) {dev_err(&pdev->dev,"Failed to enable phy regulator: %d\n", ret);goto failed_regulator;}} else {fep->reg_phy = NULL;}/*复位phy芯片*/fec_reset_phy(pdev);if (fep->bufdesc_ex)fec_ptp_init(pdev);//enet 网络外设初始化(重要)ret = fec_enet_init(ndev);if (ret)goto failed_init;/*获取中断号,并申请中断号
*/for (i = 0; i < FEC_IRQ_NUM; i++) {irq = platform_get_irq(pdev, i);if (irq < 0) {if (i)break;ret = irq;goto failed_irq;}ret = devm_request_irq(&pdev->dev, irq, fec_enet_interrupt,0, pdev->name, ndev);if (ret)goto failed_irq;fep->irq[i] = irq;}/*从设备树中获取属性“fsl,wakeup_irq”的值,也就是唤醒中断*/ret = of_property_read_u32(np, "fsl,wakeup_irq", &irq);if (!ret && irq < FEC_IRQ_NUM)fep->wake_irq = fep->irq[irq];elsefep->wake_irq = fep->irq[0];//初始化完成变量init_completion(&fep->mdio_done);/*MDIO 总线注册、MDIO读写函数设置、phy芯片注册*/ret = fec_enet_mii_init(pdev);if (ret)goto failed_mii_init;/* Carrier starts down, phylib will bring it up */netif_carrier_off(ndev);// 使能网络时钟fec_enet_clk_enable(ndev, false);pinctrl_pm_select_sleep_state(&pdev->dev);//注册网络设备ret = register_netdev(ndev);if (ret)goto failed_register;device_init_wakeup(&ndev->dev, fep->wol_flag &FEC_WOL_HAS_MAGIC_PACKET);if (fep->bufdesc_ex && fep->ptp_clock)netdev_info(ndev, "registered PHC device %d\n", fep->dev_id);fep->rx_copybreak = COPYBREAK_DEFAULT;INIT_WORK(&fep->tx_timeout_work, fec_enet_timeout_work);return 0;failed_register:fec_enet_mii_remove(fep);
failed_mii_init:
failed_irq:
failed_init:if (fep->reg_phy)regulator_disable(fep->reg_phy);
failed_regulator:fec_enet_clk_enable(ndev, false);
failed_clk:
failed_phy:of_node_put(phy_node);
failed_ioremap:free_netdev(ndev);return ret;
}

复位phy芯片:fec_reset_phy

此函数使用phy 芯片复位引脚以硬件方式复位。

static void fec_reset_phy(struct platform_device *pdev)
{int err, phy_reset;int msec = 1;struct device_node *np = pdev->dev.of_node;if (!np)return;/*设备树中phy-reset-duration 属性描述复位phy芯片的有效时间。phy-reset-gpios:描述phy 复位所使用的gpio*/err = of_property_read_u32(np, "phy-reset-duration", &msec);/* A sane reset duration should not be longer than 1s */if (!err && msec > 1000)msec = 1;phy_reset = of_get_named_gpio(np, "phy-reset-gpios", 0);if (!gpio_is_valid(phy_reset))return;//请求gpioerr = devm_gpio_request_one(&pdev->dev, phy_reset,GPIOF_OUT_INIT_LOW, "phy-reset");if (err) {dev_err(&pdev->dev, "failed to get phy-reset-gpios: %d\n", err);return;}msleep(msec);gpio_set_value(phy_reset, 1);
}

网络外设初始化(重要):fec_enet_init

fec_enet_alloc_queue:申请了接收和发送队列内存。
dma_alloc_coherent:申请dma 内存。
fec_get_mac:获取mac地址。
fec_set_mac_address:设置mac地址。
ndev->netdev_ops = &fec_netdev_ops: 设置了net_device_ops。
ndev->ethtool_ops = &fec_enet_ethtool_ops: 设置ethtool_ops 。
netif_napi_add: 添加napi 接收回调函数。
net_device->hw_features 与net_device->features设置: 根据需要的功能设置硬件寄存器。
fec_restart:重启网络。

static int fec_enet_init(struct net_device *ndev)
{struct fec_enet_private *fep = netdev_priv(ndev);struct fec_enet_priv_tx_q *txq;struct fec_enet_priv_rx_q *rxq;struct bufdesc *cbd_base;dma_addr_t bd_dma;int bd_size;unsigned int i;#if defined(CONFIG_ARM)fep->rx_align = 0xf;fep->tx_align = 0xf;
#elsefep->rx_align = 0x3;fep->tx_align = 0x3;
#endif/*申请接收和发送队列的内存*/fec_enet_alloc_queue(ndev);if (fep->bufdesc_ex)fep->bufdesc_size = sizeof(struct bufdesc_ex);elsefep->bufdesc_size = sizeof(struct bufdesc);bd_size = (fep->total_tx_ring_size + fep->total_rx_ring_size) *fep->bufdesc_size;/* Allocate memory for buffer descriptors. */// dma 内存申请cbd_base = dma_alloc_coherent(NULL, bd_size, &bd_dma,GFP_KERNEL);if (!cbd_base) {return -ENOMEM;}memset(cbd_base, 0, bd_size);/*获取mac地址,并设置mac地址*//* Get the Ethernet address */fec_get_mac(ndev);/* make sure MAC we just acquired is programmed into the hw */fec_set_mac_address(ndev, NULL);/* Set receive and transmit descriptor base. */for (i = 0; i < fep->num_rx_queues; i++) {rxq = fep->rx_queue[i];rxq->index = i;rxq->rx_bd_base = (struct bufdesc *)cbd_base;rxq->bd_dma = bd_dma;if (fep->bufdesc_ex) {bd_dma += sizeof(struct bufdesc_ex) * rxq->rx_ring_size;cbd_base = (struct bufdesc *)(((struct bufdesc_ex *)cbd_base) + rxq->rx_ring_size);} else {bd_dma += sizeof(struct bufdesc) * rxq->rx_ring_size;cbd_base += rxq->rx_ring_size;}}for (i = 0; i < fep->num_tx_queues; i++) {txq = fep->tx_queue[i];txq->index = i;txq->tx_bd_base = (struct bufdesc *)cbd_base;txq->bd_dma = bd_dma;if (fep->bufdesc_ex) {bd_dma += sizeof(struct bufdesc_ex) * txq->tx_ring_size;cbd_base = (struct bufdesc *)(((struct bufdesc_ex *)cbd_base) + txq->tx_ring_size);} else {bd_dma += sizeof(struct bufdesc) * txq->tx_ring_size;cbd_base += txq->tx_ring_size;}}/*设置netdev_ops 和ethtool_ops *//* The FEC Ethernet specific entries in the device structure */ndev->watchdog_timeo = TX_TIMEOUT;ndev->netdev_ops = &fec_netdev_ops;ndev->ethtool_ops = &fec_enet_ethtool_ops;/*添加napi接收回调函数*/writel(FEC_RX_DISABLED_IMASK, fep->hwp + FEC_IMASK);netif_napi_add(ndev, &fep->napi, fec_enet_rx_napi, NAPI_POLL_WEIGHT);if (fep->quirks & FEC_QUIRK_HAS_VLAN)/* enable hw VLAN support */ndev->features |= NETIF_F_HW_VLAN_CTAG_RX;if (fep->quirks & FEC_QUIRK_HAS_CSUM) {ndev->gso_max_segs = FEC_MAX_TSO_SEGS;/* enable hw accelerator */ndev->features |= (NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM| NETIF_F_RXCSUM | NETIF_F_SG | NETIF_F_TSO);fep->csum_flags |= FLAG_RX_CSUM_ENABLED;}if (fep->quirks & FEC_QUIRK_HAS_AVB) {fep->tx_align = 0;fep->rx_align = 0x3f;}ndev->hw_features = ndev->features;fec_restart(ndev);return 0;
}

fec_enet_mii_init:MDIO总线注册 与 phy芯片注册

mii_speed = DIV_ROUND_UP(clk_get_rate(fep->clk_ipg), 5000000): mii 总线速度。
writel(fep->phy_speed, fep->hwp + FEC_MII_SPEED): phy 速度设置,hwp 是网络外设寄存器映射的虚拟地址。
fep->mii_bus = mdiobus_alloc(): 申请mdio总线内存。
**fep->mii_bus->read = fec_enet_mdio_read; fep->mii_bus->write = fec_enet_mdio_write; ** :MDIO读写函数设置,用于读写phy芯片寄存器。
of_mdiobus_register/mdiobus_register: 向内核注册MDIO总线(其中会注册phy芯片)。

static int fec_enet_mii_init(struct platform_device *pdev)
{static struct mii_bus *fec0_mii_bus;static int *fec_mii_bus_share;struct net_device *ndev = platform_get_drvdata(pdev);struct fec_enet_private *fep = netdev_priv(ndev);struct device_node *node;int err = -ENXIO, i;u32 mii_speed, holdtime;if ((fep->quirks & FEC_QUIRK_ENET_MAC) && fep->dev_id > 0) {/* fec1 uses fec0 mii_bus */if (mii_cnt && fec0_mii_bus) {fep->mii_bus = fec0_mii_bus;*fec_mii_bus_share = FEC0_MII_BUS_SHARE_TRUE;mii_cnt++;return 0;}return -ENOENT;}fep->mii_timeout = 0;/** Set MII speed to 2.5 MHz (= clk_get_rate() / 2 * phy_speed)** The formula for FEC MDC is 'ref_freq / (MII_SPEED x 2)' while* for ENET-MAC is 'ref_freq / ((MII_SPEED + 1) x 2)'.  The i.MX28* Reference Manual has an error on this, and gets fixed on i.MX6Q* document.*/mii_speed = DIV_ROUND_UP(clk_get_rate(fep->clk_ipg), 5000000);if (fep->quirks & FEC_QUIRK_ENET_MAC)mii_speed--;if (mii_speed > 63) {dev_err(&pdev->dev,"fec clock (%lu) to fast to get right mii speed\n",clk_get_rate(fep->clk_ipg));err = -EINVAL;goto err_out;}holdtime = DIV_ROUND_UP(clk_get_rate(fep->clk_ipg), 100000000) - 1;//设置phy芯片速度(网口速度:10/100/1000M)fep->phy_speed = mii_speed << 1 | holdtime << 8;writel(fep->phy_speed, fep->hwp + FEC_MII_SPEED);//mii 总线申请fep->mii_bus = mdiobus_alloc();if (fep->mii_bus == NULL) {err = -ENOMEM;goto err_out;}/*MDIO 读写函数设置:用于读写phy芯片寄存器 */fep->mii_bus->name = "fec_enet_mii_bus";fep->mii_bus->read = fec_enet_mdio_read;fep->mii_bus->write = fec_enet_mdio_write;snprintf(fep->mii_bus->id, MII_BUS_ID_SIZE, "%s-%x",pdev->name, fep->dev_id + 1);fep->mii_bus->priv = fep;fep->mii_bus->parent = &pdev->dev;fep->mii_bus->irq = kmalloc(sizeof(int) * PHY_MAX_ADDR, GFP_KERNEL);if (!fep->mii_bus->irq) {err = -ENOMEM;goto err_out_free_mdiobus;}for (i = 0; i < PHY_MAX_ADDR; i++)fep->mii_bus->irq[i] = PHY_POLL;/*获取设备树中MDIO 节点,并注册MDIO 总线*/node = of_get_child_by_name(pdev->dev.of_node, "mdio");if (node) {err = of_mdiobus_register(fep->mii_bus, node);of_node_put(node);} else {err = mdiobus_register(fep->mii_bus);}if (err)goto err_out_free_mdio_irq;mii_cnt++;/* save fec0 mii_bus */if (fep->quirks & FEC_QUIRK_ENET_MAC) {fec0_mii_bus = fep->mii_bus;fec_mii_bus_share = &fep->mii_bus_share;}return 0;err_out_free_mdio_irq:kfree(fep->mii_bus->irq);
err_out_free_mdiobus:mdiobus_free(fep->mii_bus);
err_out:return err;
}

of_mdiobus_register:设备树的方式注册mdio

Linux内核采用 mii_bus 结构体表示一个MDIO总线,定义在include/linux/phy.h 文件中, mii_bus 结构体如下所示:
其中读写的函数是比较重要的。

/** The Bus class for PHYs.  Devices which provide access to* PHYs should register using this structure*/
struct mii_bus {const char *name;       char id[MII_BUS_ID_SIZE];void *priv;int (*read)(struct mii_bus *bus, int phy_id, int regnum);       int (*write)(struct mii_bus *bus, int phy_id, int regnum, u16 val);int (*reset)(struct mii_bus *bus);/** A lock to ensure that only one thing can read/write* the MDIO bus at a time*/struct mutex mdio_lock;struct device *parent;enum {MDIOBUS_ALLOCATED = 1,MDIOBUS_REGISTERED,MDIOBUS_UNREGISTERED,MDIOBUS_RELEASED,} state;struct device dev;/* list of all PHYs on bus */struct phy_device *phy_map[PHY_MAX_ADDR];/* PHY addresses to be ignored when probing */u32 phy_mask;/** Pointer to an array of interrupts, each PHY's* interrupt at the index matching its address*/int *irq;
};

mdiobus_register: 注册mdio总线。
for_each_available_child_of_node(np, child): 获取MDIO 下的所有子节点。(都是挂在mdio总线下的phy设备)
addr = of_mdio_parse_addr(&mdio->dev, child): 解析mdio 地址,也就是phy地址。
rc = of_mdiobus_register_phy(mdio, child, addr): 向内核注册phy设备。

int of_mdiobus_register(struct mii_bus *mdio, struct device_node *np)
{struct device_node *child;const __be32 *paddr;bool scanphys = false;int addr, rc, i;/* Mask out all PHYs from auto probing.  Instead the PHYs listed in* the device tree are populated after the bus has been registered */mdio->phy_mask = ~0;/* Clear all the IRQ properties */if (mdio->irq)for (i=0; i<PHY_MAX_ADDR; i++)mdio->irq[i] = PHY_POLL;//获取mdio 节点mdio->dev.of_node = np;/* Register the MDIO bus */rc = mdiobus_register(mdio);if (rc)return rc;/*扫描mdio:下的phy设备字节点,获取phy地址并向内核注册phy*//* Loop over the child nodes and register a phy_device for each one */for_each_available_child_of_node(np, child) {addr = of_mdio_parse_addr(&mdio->dev, child);if (addr < 0) {scanphys = true;continue;}rc = of_mdiobus_register_phy(mdio, child, addr);if (rc)continue;}if (!scanphys)return 0;/* auto scan for PHYs with empty reg property */for_each_available_child_of_node(np, child) {/* Skip PHYs with reg property set */paddr = of_get_property(child, "reg", NULL);if (paddr)continue;for (addr = 0; addr < PHY_MAX_ADDR; addr++) {/* skip already registered PHYs */if (mdio->phy_map[addr])continue;/* be noisy to encourage people to set reg property */dev_info(&mdio->dev, "scan phy %s at address %i\n",child->name, addr);rc = of_mdiobus_register_phy(mdio, child, addr);if (rc)continue;}}return 0;
}
EXPORT_SYMBOL(of_mdiobus_register);

注册phy 设备:of_mdiobus_register_phy

此函数是在上面的of_mdiobus_register 函数中调用的,参数如下:
mdio:mdio总线。
child:phy设备节点。
addr:phy地址。

static int of_mdiobus_register_phy(struct mii_bus *mdio, struct device_node *child,u32 addr)
{struct phy_device *phy;bool is_c45;int rc;u32 phy_id;//确认phy设备compatible 类型是否为c45is_c45 = of_device_is_compatible(child,"ethernet-phy-ieee802.3-c45");/*如果是c45,并且获取到phy_id时创建phy设备;否则,直接获取phy设备。*/if (!is_c45 && !of_get_phy_id(child, &phy_id))phy = phy_device_create(mdio, addr, phy_id, 0, NULL);elsephy = get_phy_device(mdio, addr, is_c45);if (!phy || IS_ERR(phy))return 1;
/*解析phy中断号,并记录在mdio总线和phy的数据结构。
*/rc = irq_of_parse_and_map(child, 0);if (rc > 0) {phy->irq = rc;if (mdio->irq)mdio->irq[addr] = rc;} else {if (mdio->irq)phy->irq = mdio->irq[addr];}/* Associate the OF node with the device structure so it* can be looked up later */of_node_get(child);phy->dev.of_node = child;/* All data is now stored in the phy struct;* register it */* 向Linux 内核注册phy设备rc = phy_device_register(phy);if (rc) {phy_device_free(phy);of_node_put(child);return 1;}
//打印信息注册信息dev_dbg(&mdio->dev, "registered phy %s at address %i\n",child->name, addr);return 0;
}

MDIO总线注册phy 的调用过程:

struct net_device_ops

fec_probe 函数设置了网卡驱动的 net_dev_ops 操作集为 fec_netdev_ops, fec_netdev_ops 内容如下:

static const struct net_device_ops fec_netdev_ops = {.ndo_open      = fec_enet_open,.ndo_stop      = fec_enet_close,.ndo_start_xmit       = fec_enet_start_xmit,.ndo_select_queue       = fec_enet_select_queue,.ndo_set_rx_mode    = set_multicast_list,.ndo_change_mtu       = eth_change_mtu,.ndo_validate_addr    = eth_validate_addr,.ndo_tx_timeout        = fec_timeout,.ndo_set_mac_address = fec_set_mac_address,.ndo_do_ioctl        = fec_enet_ioctl,
#ifdef CONFIG_NET_POLL_CONTROLLER.ndo_poll_controller   = fec_poll_controller,
#endif.ndo_set_features = fec_set_features,
};

1、ndo_open 函数

(命令行执行:ifconfig ethx up)打开一个网卡的时候 fec_enet_open 函数就会执行,函数源码如下所示(限于篇幅原因,有省略):

fec_enet_clk_enable: 函数使能 enet 时钟。
fec_enet_alloc_buffers: 函数申请环形缓冲区 buffer,此函数里面会调用fec_enet_alloc_rxq_buffers 和 fec_enet_alloc_txq_buffers 这两个函数分别实现发送队列和接收队列缓冲区的申请。
fec_restart: 重启网络,一般连接状态改变、传输超时或者配置网络的时候都会调用 fec_restart函数。
fec_enet_mii_probe:打开网卡的时候调用 fec_enet_mii_probe 函数来探测并连接某个网口对应的 PHY 设备 。
napi_enable: napi_enable 函数使能 NAPI 调度
phy_start: 开启 PHY 设备。
netif_tx_start_all_queues : 激活发送队列。

static int fec_enet_open(struct net_device *ndev)
{struct fec_enet_private *fep = netdev_priv(ndev);const struct platform_device_id *id_entry =platform_get_device_id(fep->pdev);int ret;pinctrl_pm_select_default_state(&fep->pdev->dev);ret = fec_enet_clk_enable(ndev, true);if (ret)return ret;/* I should reset the ring buffers here, but I don't yet know* a simple way to do that.*/ret = fec_enet_alloc_buffers(ndev);if (ret)goto err_enet_alloc;/* Init MAC prior to mii bus probe */fec_restart(ndev);/* Probe and connect to PHY when open the interface */ret = fec_enet_mii_probe(ndev);if (ret)goto err_enet_mii_probe;napi_enable(&fep->napi);phy_start(fep->phy_dev);netif_tx_start_all_queues(ndev);pm_runtime_get_sync(ndev->dev.parent);if ((id_entry->driver_data & FEC_QUIRK_BUG_WAITMODE) &&!fec_enet_irq_workaround(fep))pm_qos_add_request(&fep->pm_qos_req,PM_QOS_CPU_DMA_LATENCY,0);elsepm_qos_add_request(&fep->pm_qos_req,PM_QOS_CPU_DMA_LATENCY,PM_QOS_DEFAULT_VALUE);device_set_wakeup_enable(&ndev->dev, fep->wol_flag &FEC_WOL_FLAG_ENABLE);fep->miibus_up_failed = false;return 0;err_enet_mii_probe:fec_enet_free_buffers(ndev);
err_enet_alloc:fep->miibus_up_failed = true;if (!fep->mii_bus_share)pinctrl_pm_select_sleep_state(&fep->pdev->dev);return ret;
}

2、 fec_enet_close 函数简析

(命令行执行:ifconfig ethx down)关闭网卡的时候 fec_enet_close 函数就会执行,函数内容如下:

phy_stop 函数停止 PHY 设备。
napi_disable 函数关闭 NAPI 调度。
netif_tx_disable 函数关闭 NAPI 的发送队列。
fec_stop 函数关闭 I.MX6ULL 的 ENET 外设。
phy_disconnect 函数断开与 PHY 设备的连接。
fec_enet_clk_enable 函数关闭 ENET 外设时钟。
fec_enet_free_buffers 函数释放发送和接收的环形缓冲区内存

static int fec_enet_close(struct net_device *ndev)
{struct fec_enet_private *fep = netdev_priv(ndev);phy_stop(fep->phy_dev);if (netif_device_present(ndev)) {napi_disable(&fep->napi);netif_tx_disable(ndev);fec_stop(ndev);}phy_disconnect(fep->phy_dev);fep->phy_dev = NULL;fec_enet_clk_enable(ndev, false);pm_qos_remove_request(&fep->pm_qos_req);pinctrl_pm_select_sleep_state(&fep->pdev->dev);pm_runtime_put_sync_suspend(ndev->dev.parent);fec_enet_free_buffers(ndev);return 0;
}

3、 fec_enet_start_xmit 函数简析

I.MX6ULL 的网络数据发送是通过 fec_enet_start_xmit 函数来完成的,这个函数将上层传递过来的 sk_buff 中的数据通过硬件发送出去 , 函数源码如下:
skb: 上层应用传递下来的要发送的网络数据
ndev: 要发送数据的设备。

第 14 行,判断 skb 是否为 GSO(Generic Segmentation Offload),如果是 GSO 的话就通过fec_enet_txq_submit_tso 函数发送,如果不是的话就通过 fec_enet_txq_submit_skb 发送。这里简单讲一下 TSO 和 GSO:
TSO: 全称是 TCP Segmentation Offload,利用网卡对大数据包进行自动分段处理,降低 CPU负载。
GSO: 全称是 Generic Segmentation Offload,在发送数据之前先检查一下网卡是否支持 TSO,如果支持的话就让网卡分段,不过不支持的话就由协议栈进行分段处理,分段处理完成以后再交给网卡去发送。
第 21 行,通过 fec_enet_get_free_txdesc_num 函数获取剩余的发送描述符 数量。
第 23 行,如果剩余的发送描述符的数量小于设置的阈值(tx_stop_threshold)的话就调用函数netif_tx_stop_queu 来暂停发送,通过暂停发送来通知应用层停止向网络发送 skb,发送中断中会重新开启 的。

1 static netdev_tx_t fec_enet_start_xmit(struct sk_buff *skb,struct net_device *ndev)
2 {3       struct fec_enet_private *fep = netdev_priv(ndev);
4       int entries_free;
5       unsigned short queue;
6       struct fec_enet_priv_tx_q *txq;
7       struct netdev_queue *nq;
8       int ret;
9
10      queue = skb_get_queue_mapping(skb);
11      txq = fep->tx_queue[queue];
12      nq = netdev_get_tx_queue(ndev, queue);
13
14      if (skb_is_gso(skb))
15          ret = fec_enet_txq_submit_tso(txq, skb, ndev);
16      else
17          ret = fec_enet_txq_submit_skb(txq, skb, ndev);
18      if (ret)
19          return ret;
20
21      entries_free = fec_enet_get_free_txdesc_num(fep, txq);
22      if (entries_free <= txq->tx_stop_threshold)
23          netif_tx_stop_queue(nq);
24
25      return NETDEV_TX_OK;
26 }

4、 fec_enet_interrupt 中断服务函数简析

前面说了 I.MX6ULL 的网络数据接收采用 NAPI 框架,所以肯定要用到中断。 fec_probe 函数会初始化网络中断,中断服务函数为 fec_enet_interrupt,函数内容如下:
可以看出中断服务函数非常短!而且也没有见到有关数据接收的处理过程,那是因为I.MX6ULL 的网络驱动使用了 NAPI,具体的网络数据收发是在 NAPI 的 poll 函数中完成的,中断里面只需要进行 napi 调度即可,这个就是中断的上半部和下半部处理机制。

int_events = readl(fep->hwp + FEC_IEVENT); 读取 NENT 的中断状态寄存器 EIR,获取中断状态,
writel(int_events, fep->hwp + FEC_IEVENT); 将获取到的中断状态值又写入 EIR 寄存器,用于清除中断状态寄存器。
fec_enet_collect_events 函数统计中断信息,也就是统计都发生了哪些中断 。fep 中成员变量 work_tx 和 work_rx 的 bit0、 bit1 和 bit2 用来做不同的标记, work_rx 的 bit2 表示接收到数据帧, work_tx 的 bit2 表示发送完数据帧。
napi_schedule_prep: 检查 NAPI 是否可以进行调度。
如果使能了相关中断就要先关闭这些中断,向 EIMR 寄存器的 bit23 写 1 即可关闭相关中断。
** __napi_schedule:** 启动 NAPI 调度,这个时候 napi 的 poll 函数就会执行,在本网络驱动中就是 fec_enet_rx_napi 函数。

static irqreturn_t
fec_enet_interrupt(int irq, void *dev_id)
{struct net_device *ndev = dev_id;struct fec_enet_private *fep = netdev_priv(ndev);uint int_events;irqreturn_t ret = IRQ_NONE;
/*读取网络中断的事件,并清除中断标志位
*/int_events = readl(fep->hwp + FEC_IEVENT);writel(int_events, fep->hwp + FEC_IEVENT);fec_enet_collect_events(fep, int_events);
//判断是否发生有数据可读、发送完成等中断if ((fep->work_tx || fep->work_rx) && fep->link) {ret = IRQ_HANDLED;
/*判断是否能进行napi调度:可以的话先禁用中断;开始napi调度,napi poll函数会执行(本例程是接收数据函数)
*/if (napi_schedule_prep(&fep->napi)) {/* Disable the NAPI interrupts */writel(FEC_ENET_MII, fep->hwp + FEC_IMASK);__napi_schedule(&fep->napi);}}if (int_events & FEC_ENET_MII) {ret = IRQ_HANDLED;complete(&fep->mdio_done);}if (fep->ptp_clock)fec_ptp_check_pps_event(fep);return ret;
}

5、 napi poll函数简析

fec_enet_init 函数初始化网络的时候会调用 netif_napi_add 来设置 NAPI 的 poll 函数为fec_enet_rx_napi,函数内容如下:
每次调用napi函数接收一帧数据,接收完后调用napi_complete表示完成,并且重新使能中断退出,napi函数。
fec_enet_rx 函数进行真正的数据接收。
fec_enet_tx 函数进行数据发送。
napi_complete 函数来宣布一次轮询结束。
设置 ENET 的 EIMR 寄存器,重新使能中断。

static int fec_enet_rx_napi(struct napi_struct *napi, int budget)
{struct net_device *ndev = napi->dev;struct fec_enet_private *fep = netdev_priv(ndev);int pkts;pkts = fec_enet_rx(ndev, budget);fec_enet_tx(ndev);if (pkts < budget) {napi_complete(napi);writel(FEC_DEFAULT_IMASK, fep->hwp + FEC_IMASK);}return pkts;
}

Linux 内核 PHY 子系统与 MDIO 总线简析

前面了解 MDIO 总线的时候讲过,注册 MDIO 总线的时候也会向内核注册 PHY 设备,本节我们就来简单了解一下 PHY 子系统。 PHY 子系统 就是用于 PHY 设备相关内容的,分为 PHY 设备和 PHY 驱动,和 platform 总线一样, PHY 子系统也是一个设备、总线和驱动模型。
phy 设备是注册到MDIO 总线下,也就是挂载在总线上,就像一棵树上挂了许多果子,一个phy设备就是其中一颗果子。

1、 PHY 设备

Linux 内核使用 phy_device 结构体来表示 PHY 设备,结构体定义在 include/linux/phy.h,结构体内容如下:
phy_driver: phy 设备驱动。
mii_bus : 对应的mdio总线。
device : 设备文件,就是/dev/ 目录下的文件节点。
phy_id: phy id,phy寄存器里的id。
state: phy状态。
interface: phy接口。
addr: phy 地址,只允许0~31个(刚好5bit)。
speed: 网口速度。
duplex: 双工模式。
link: 连接状态。
interrupts: 中断使能标志。
attached_dev: PHY 芯片对应的网络设备,也就是mac外设。
adjust_link: 应该是改变连接状态的函数,插入或拔出网线时调用。

struct phy_device {/* Information about the PHY type *//* And management functions */struct phy_driver *drv;struct mii_bus *bus;struct device dev;u32 phy_id;struct phy_c45_device_ids c45_ids;bool is_c45;bool is_internal;bool has_fixups;bool suspended;enum phy_state state;u32 dev_flags;phy_interface_t interface;/* Bus address of the PHY (0-31) */int addr;/** forced speed & duplex (no autoneg)* partner speed & duplex & pause (autoneg)*/int speed;int duplex;int pause;int asym_pause;/* The most recently read link state */int link;/* Enabled Interrupts */u32 interrupts;/* Union of PHY and Attached devices' supported modes *//* See mii.h for more info */u32 supported;u32 advertising;u32 lp_advertising;int autoneg;int link_timeout;/** Interrupt number for this PHY* -1 means no interrupt*/int irq;/* private data pointer *//* For use by PHYs to maintain extra state */void *priv;/* Interrupt and Polling infrastructure */struct work_struct phy_queue;struct delayed_work state_queue;atomic_t irq_disable;struct mutex lock;struct net_device *attached_dev;void (*adjust_link)(struct net_device *dev);
};

一个 PHY 设备对应一个 phy_device 实例,然后需要向 Linux 内核注册这个实例。使用phy_device_register 函数完成 PHY 设备的注册 ,函数原型如下:

int phy_device_register(struct phy_device *phy)

函数参数和返回值含义如下:
phy: 需要注册的 PHY 设备。
返回值: 0 成功,负值 失败。
PHY 设备的注册过程一般是先调用 get_phy_device 函数获取 PHY 设备 ,此函数内容如下
第 7 行,调用 get_phy_id 函数获取 PHY ID,也就是读取 PHY 芯片的那两个 ID 寄存器,得到 PHY 芯片 ID 信息
第 15 行,调用 phy_device_create 函数创建 phy_device,此函数先申请 phy_device 内存,然后初始化 phy_device 的各个结构体成员,最终返回创建好的 phy_device。 phy_device_register 函数注册的就是这个创建好的 phy_device。 (phy_device 是在注册时才创建的!!!)

1 struct phy_device *get_phy_device(struct mii_bus *bus, int addr,bool is_c45)
2 {3       struct phy_c45_device_ids c45_ids = {0};
4       u32 phy_id = 0;
5       int r;
6       /*读取phy 寄存器中的id值*/
7       r = get_phy_id(bus, addr, &phy_id, is_c45, &c45_ids);
8       if (r)
9           return ERR_PTR(r);
10
11      /* If the phy_id is mostly Fs, there is no device there *//*当读到的值都是f 时,读取失败。立即返回*/
12      if ((phy_id & 0x1fffffff) == 0x1fffffff)
13          return NULL;
14      // 创建phy设备并返回
15      return phy_device_create(bus, addr, phy_id, is_c45, &c45_ids);
16 }

2、 PHY 驱动

PHY 驱动使用结构体 phy_driver 表示,结构体也定义在 include/linux/phy.h 文件中,结构体内容如下(为了缩小篇幅,省略了注释部分):
phy_driver 重点是大量的函数,编写 PHY 驱动的主要工作就是实现这些函数。

1 struct phy_driver {2       u32 phy_id;               /* PHY ID */
3       char *name;
4       unsigned int phy_id_mask; /* PHY ID 掩码 */
5       u32 features;
6       u32 flags;
7       const void *driver_data;
8
9       int (*soft_reset)(struct phy_device *phydev);
10      int (*config_init)(struct phy_device *phydev);
11      int (*probe)(struct phy_device *phydev);
12      int (*suspend)(struct phy_device *phydev);
13      int (*resume)(struct phy_device *phydev);
14      int (*config_aneg)(struct phy_device *phydev);
15      int (*aneg_done)(struct phy_device *phydev);
16      int (*read_status)(struct phy_device *phydev);
17      int (*ack_interrupt)(struct phy_device *phydev);
18      int (*config_intr)(struct phy_device *phydev);
19      int (*did_interrupt)(struct phy_device *phydev);
20      void (*remove)(struct phy_device *phydev);
21      int (*match_phy_device)(struct phy_device *phydev);
22      int (*ts_info)(struct phy_device *phydev,struct ethtool_ts_info *ti);
23      int (*hwtstamp)(struct phy_device *phydev, struct ifreq *ifr);
24      bool (*rxtstamp)(struct phy_device *dev, struct sk_buff *skb,int type);
25      void (*txtstamp)(struct phy_device *dev, struct sk_buff *skb,int type);
26      int (*set_wol)(struct phy_device *dev,struct ethtool_wolinfo *wol);
27      void (*get_wol)(struct phy_device *dev,struct ethtool_wolinfo *wol);
28      void (*link_change_notify)(struct phy_device *dev);
29      int (*read_mmd_indirect)(struct phy_device *dev, int ptrad,int devnum, int regnum);
30
31      void (*write_mmd_indirect)(struct phy_device *dev, int ptrad,int devnum, int regnum, u32 val);
32
33      int (*module_info)(struct phy_device *dev,
34      struct ethtool_modinfo *modinfo);
35      int (*module_eeprom)(struct phy_device *dev,
36      struct ethtool_eeprom *ee, u8 *data);
37
38      struct device_driver driver;
39 };

①、注册 PHY 驱动

注意: 这里注册的是驱动,前面注册的是设备。
phy_driver 结构体初始化完成以后,就需要向 Linux 内核注册, PHY 驱动的注册使用phy_driver_register 函数,注册 phy 驱动时候会设置驱动的总线为 mdio_bus_type,也就是 MDIO总线 ,函数原型如下:
int phy_driver_register(struct phy_driver *new_driver)
函数参数和返回值含义如下:
new_driver: 需要注册的 PHY 驱动。
返回值: 0 成功,负值 失败。

②、连续注册多个 PHY 驱动

一个厂家会生产多种 PHY 芯片,这些 PHY 芯片内部差别一般不大,如果一个个的去注册驱动将会导致一堆重复的驱动文件 ,因此 Linux 内核提供了一个连续注册多个 PHY 驱动的函数phy_drivers_register 。首先准备一个 phy_driver 数组,一个数组元素就表示一个 PHY 芯片的驱动,然后调用 phy_drivers_register 一次性注册整个数组中的所有驱动,函数原型如下:
int phy_drivers_register(struct phy_driver *new_driver, int n)
函数参数和返回值含义如下:
new_driver: 需要注册的多个 PHY 驱动数组。
n: 要注册的驱动数量。
返回值: 0 成功,负值 失败。

③、卸载 PHY 驱动

卸载 PHY 驱动的话使用 phy_driver_unregister 函数,函数原型如下:
void phy_driver_unregister(struct phy_driver *drv)
函数参数和返回值含义如下:
new_driver: 需要卸载的 PHY 驱动。
返回值: 无。

3、 MDIO 总线

前面说了, PHY 子系统也是遵循设备、总线、驱动模型的,设备和驱动就是 phy_device 和phy_driver。总线就是 MDIO 总线,因为 PHY 芯片是通过 MIDO 接口来管理的, MDIO 总线最主要的工作就是匹配 PHY 设备和 PHY 驱动。
在文件 drivers/net/phy/mdio_bus.c 中有如下定义:

1 struct bus_type mdio_bus_type = {2       .name = "mdio_bus",
3       .match = mdio_bus_match,
4       .pm = MDIO_BUS_PM_OPS,
5       .dev_groups = mdio_dev_groups,
6 };

如上定义了一个名为“mdio_bus_type”的总线,这个就是 MDIO 总线,总线的名字为“mdio_bus”,重点是总线的匹配函数为 mdio_bus_match 。(前面说了phy驱动可以使用厂家指定的驱动也可以使用通用驱动,此函数就是用来匹配phy芯片与它们指定驱动的)此函数内容如下:

第 6 行,采用设备树的话先尝试使用 of_driver_match_device 来对设备和驱动进行匹配,也就是检查 compatible 属性值与匹配表 of_match_table 里面的内容是否一致。
第 9、 10 行,检查 PHY 驱动有没有提供匹配函数 match_phy_device,如果有的话就直接调用 PHY 驱动提供的匹配函数完成与设备的匹配。 这个应该是编写phy 驱动人员提供。
第 12、 13 行,如果上面两个匹配方法都无效的话就使用最后一种, phy_driver 里面有两个成员变量 phy_id 和 phy_id_mask,表示此驱动所匹配的 PHY 芯片 ID 以及 ID 掩码, PHY 驱动编写人员需要给这两个成员变量赋值。 phy_device 也有一个 phy_id 成员变量,表示此 PHY 芯片的 ID, phy_device 里面的 phy_id 是在注册 PHY 设备的时候调用 get_phy_id 函数直接读取PHY 芯片内部 ID 寄存器得到的!很明显 PHY 驱动和 PHY 设备中的 ID 要一样,这样才能匹配起来。所以最后一种方法就是对比 PHY 驱动和 PHY 设备中的 phy_id 是否一致,这里需要与PHY 驱动里面的 phy_id_mask 进行与运算,如果结果一致的话就说明驱动和设备匹配。

如果 PHY 设备和 PHY 驱动匹配,那么就使用指定的 PHY 驱动,如果不匹配的话就使用Linux 内核自带的通用 PHY 驱动。

1 static int mdio_bus_match(struct device *dev,struct device_driver *drv)
2 {//获取到phy 设备与驱动
3       struct phy_device *phydev = to_phy_device(dev);
4       struct phy_driver *phydrv = to_phy_driver(drv);
5       /*使用设备树的方式匹配phy设备与驱动,也就是检查 compatible 属性值与匹配表 of_match_table 里面的内容是否一致。*/
6       if (of_driver_match_device(dev, drv))
7           return 1;
8
9       if (phydrv->match_phy_device)
10          return phydrv->match_phy_device(phydev);
11
12      return (phydrv->phy_id & phydrv->phy_id_mask) == (phydev->phy_id & phydrv->phy_id_mask);
13 }

3、 通用 PHY 驱动

前面多次提到Linux内核已经集成了通用PHY驱动,通用PHY驱动名字为“Generic PHY”,打开 drivers/net/phy/phy_device.c,找到 phy_init 函数,内容如下:

static int __init phy_init(void)
{int rc;rc = mdio_bus_init();if (rc)return rc;rc = phy_drivers_register(genphy_driver,ARRAY_SIZE(genphy_driver));if (rc)mdio_bus_exit();return rc;
}

phy_init 是整个 PHY 子系统的入口函数,调用 phy_drivers_register 函数向内核直接注册一个通用 PHY 驱动: genphy_driver,也就是通用 PHY 驱动,也就是说 Linux 系统启动以后默认就已经存在了通用 PHY 驱动。
genphy_driver 是一个数组,有两个数组元素,表示有两个通用的 PHY 驱动,一个是针对10/100/1000M 网络的,一个是针对 10G 网络的。
genphy_driver 定义在 drivers/net/phy/phy_device.c里面,内容如下:

static struct phy_driver genphy_driver[] = {{   // 10/100/1000M 的 PHY 驱动.phy_id     = 0xffffffff,.phy_id_mask  = 0xffffffff,      //phy id掩码.name     = "Generic PHY",.soft_reset  = genphy_soft_reset,                   //phy 软件复位.config_init  = genphy_config_init,              //初始化phy设备.features = PHY_GBIT_FEATURES | SUPPORTED_MII |  //支持的一些功能特性SUPPORTED_AUI | SUPPORTED_FIBRE |SUPPORTED_BNC,.config_aneg  = genphy_config_aneg,              //phy 自动协商.aneg_done    = genphy_aneg_done,.read_status    = genphy_read_status,              //phy状态.suspend = genphy_suspend,.resume       = genphy_resume,.driver        = { .owner = THIS_MODULE, },
}, {    // 10G 的 PHY 驱动.phy_id         = 0xffffffff,.phy_id_mask    = 0xffffffff,.name           = "Generic 10G PHY",.soft_reset   = gen10g_soft_reset,.config_init    = gen10g_config_init,.features       = 0,.config_aneg    = gen10g_config_aneg,.read_status    = gen10g_read_status,.suspend        = gen10g_suspend,.resume         = gen10g_resume,.driver         = {.owner = THIS_MODULE, },
} };

4、 LAN8720A 驱动

最 后 我 们 来 看 一 下 LAN8720A 的 Linux 驱 动 , LAN8720A 的 驱 动 文 件 为drivers/net/phy/smsc.c,这个文件是 SMSC 公司针对自家的一些 PHY 芯片编写的驱动文件,其中就包含了 LAN8720A 这个 PHY 芯片。默认情况下, LAN8720A 这个驱动是没有打开的,我们需
要配置 linux 内核,打开此驱动选项,配置路径如下:

-> Device Drivers-> Network device support-> PHY Device support and infrastructure-> Drivers for SMSC PHYs


这个驱动文件在 drivers/net/phy/smsc.c,找到如下所示内容(限于篇幅,有删减):

static struct phy_driver smsc_phy_driver[] = {{.phy_id        = 0x0007c0a0, /* OUI=0x00800f, Model#=0x0a */.phy_id_mask    = 0xfffffff0,.name     = "SMSC LAN83C185",.......
}, {.phy_id     = 0x0007c0b0, /* OUI=0x00800f, Model#=0x0b */.phy_id_mask    = 0xfffffff0,.name     = "SMSC LAN8187",.......
}, {.phy_id     = 0x0007c0c0, /* OUI=0x00800f, Model#=0x0c */.phy_id_mask    = 0xfffffff0,.name     = "SMSC LAN8700",.......
}, {.phy_id     = 0x0007c0d0, /* OUI=0x00800f, Model#=0x0d */.phy_id_mask    = 0xfffffff0,.name     = "SMSC LAN911x Internal PHY",.......
}, {.phy_id     = 0x0007c0f0, /* OUI=0x00800f, Model#=0x0f */.phy_id_mask    = 0xfffffff0,.name     = "SMSC LAN8710/LAN8720",.features   = (PHY_BASIC_FEATURES | SUPPORTED_Pause| SUPPORTED_Asym_Pause),.flags      = PHY_HAS_INTERRUPT | PHY_HAS_MAGICANEG,/* basic functions */.config_aneg  = genphy_config_aneg,.read_status  = lan87xx_read_status,.config_init = smsc_phy_config_init,.soft_reset = smsc_phy_reset,/* IRQ related */.ack_interrupt   = smsc_phy_ack_interrupt,.config_intr  = smsc_phy_config_intr,.suspend    = genphy_suspend,.resume       = genphy_resume,.driver        = { .owner = THIS_MODULE, }
} };

以上可以看出, smsc_phy_driver 还是支持了不少 SMSC 家的 PHY 芯片,比如 LAN83C185、 LAN8187、 LAN8700 等等,当然了,肯定也包括了 LAN8720 系列。
PHY ID 为 0X0007C0F0。
PHY 的 ID 掩码为 0XFFFFFFF0,也就是高 28 位有效,在进行匹配的时候只需要比较前 28 位,低 4 位不用比较。

驱动名字为“SMSC LAN8710/LAN8720”,系统启动以后,打开网卡就会提示当前 PHY 驱动名字为“SMSC LAN8710/LAN8720”。
最后,使用 module_phy_driver(本质是一个宏)来完成 smsc_phy_driver 的注册。
此驱动里面的成员函数有一些是 SMSC 自己编写的,有一些是直接用的通用 PHY 驱动的,比如 genphy_config_aneg、genphy_suspend 等。

Linux 网络驱动相关推荐

  1. Linux网络驱动架构

    转载来自:https://blog.csdn.net/zhoudengqing/article/details/47406821 转 Linux网络驱动架构 2015年08月10日 22:54:39 ...

  2. Linux 网络驱动-内核网络驱动框架(二)

    net_device 结构体 Linux 内核使用 net_device 结构体表示一个具体的网络设备,net_device 是整个网络驱动的 灵魂.网络驱动的核心就是初始化 net_device 结 ...

  3. Linux 网络驱动实验(有线)

    目录 嵌入式网络简介 嵌入式下的网络硬件接口 MII/RMII 接口 MDIO 接口 RJ45 接口 I.MX6ULL ENET 接口简介 PHY 芯片详解 PHY 基础知识简介 LAN8720A 详 ...

  4. linux网络驱动架构,Linux网络体系架构和网卡驱动设计

    Linux网络体系架构 1.Linux的协议栈层次 2.Linux的网络子系统架构 Linux的协议栈层次 Linux的优点之一在于它丰富而稳定的网络协议栈.其范围从协议无关层(例如通用socket层 ...

  5. Linux 网络驱动 phy 读写寄存器调试方法

    Linux 网络 phy 调试方法 目前嵌入式芯片支持双网卡的有很多.在调试网络驱动时,需要通过mdc 和mdio信号对phy的寄存器进行操作调试.如果每次调试都修改网络驱动的话会很麻烦.下面提供我常 ...

  6. linux网络驱动lookback,Linux lookback驱动分析

    Linux的网络驱动中,lookback 驱动算是最为简单的.本次分析的程序来自 Linux-2.6.32.68 源码,其中 lookback.c 驱动程序位于 /drives/net/ 目录下. 普 ...

  7. LINUX网络驱动开发

    1 Linux网络设备驱动程序概述 Linux网络驱动程序遵循通用的接口.设计时采用的是面向对象的方法. 一个设备就是一个对象(net_device 结构),它内部有自己的数据和方法. 一个网络设备最 ...

  8. linux 源码 网络驱动,Linux网络驱动源码分析(一)

    功能:注册PCI驱动,参数为要注册的pci驱动的结构体. 下面来详细的分析以下这个函数,如此,才能更清楚的了解驱动和设备的匹配过程. pci_register_driver->driver_re ...

  9. NAPI技术--在Linux 网络驱动上的应用和完善(二)

    前言 NAPI 是 Linux 上采用的一种提高网络处理效率的技术,它的核心概念就是不采用中断的方式读取数据,而代之以首先采用中断唤醒数据接收的服务程序,然后 POLL 的方法来轮询数据,(类似于底半 ...

  10. NAPI 技术在 Linux 网络驱动上的应用和完善

    前言: NAPI 是 Linux 上采用的一种提高网络处理效率的技术,它的核心概念就是不采用中断的方式读取数据,而代之以首先采用中断唤醒数据接收的服务程序,然后 POLL 的方法来轮询数据,(类似于底 ...

最新文章

  1. Acwing第 6 场周赛【未完结】
  2. 智能家居——IoT零基础入门篇
  3. 前端学习(2123):知识回顾
  4. matlab 多项式表达,MATLAB自学笔记(十七):多项式及其函数
  5. 广播地址的作用_跟百哥学网络16:ARP地址解析协议分析
  6. java bigram_Java BiGramDictionary.getBiFrequency方法代碼示例
  7. JAVA获得当前时间的几种方法
  8. xcode6以后, 使用.pch
  9. QUIC 协议在蚂蚁落地综述
  10. php获取m3u8的地址,如何获取各大平台的播放地址(获得优酷的m3u8播放地址)为例...
  11. 物联卡代理商究竟如何选择?51物联卡告诉你正确答案
  12. 方维众筹1.7短信插件短信接口开发
  13. windows Servers服务器系统时间总是自动更新为错误时间,修改了还是不对解决办法
  14. 错误:VM5729:1 Blocked script execution in ‘about:blank‘
  15. 鸟瞰Atlassian认证
  16. 批改网作文提交时分析不出来_人物专访 I 苗晋:我的生活都为“批改作文”而让路...
  17. 模拟实现图片长按保存功能
  18. 淘宝/天猫图片识别商品接口,1688图片识别商品API接口
  19. 《淘宝店铺营销推广一册通》一2.2 选择店铺行业
  20. linux批量删除screen,你不知道的linux系统中强大的screen命令

热门文章

  1. 分享几款好用的强力数据恢复软件
  2. His系统数据库服务器关系,his系统数据库服务器
  3. HTML+CSS期末大作业:家乡旅游网站设计——山东菏泽(6页) 简单个人网页设计作业 静态HTML旅行主题网页作业 DW个人网站模板下载 大学生简单个人网页作品代码
  4. BlackBerry7290软件安装——电子书阅读Mobipocket
  5. chromium os系统编译与环境搭建
  6. java中移动光标怎么写_java 程序当中无法做到光标跟随
  7. 基于国产处理器架构的高能物理数据处理系统
  8. 微信小程序开发之组件view,scroll-view,swiper,text,rich-text,button,image,navigator
  9. a5松下驱动器参数设置表_「精品干货」松下A5伺服驱动器参数设置与常见故障解决分析...
  10. matlab陷波带阻滤波器,matlab陷波滤波器 陷波滤波器器应放在系统的闭环吗