**
一、基本概念
**
网络设备硬件是由 mac 和phy 两部分组成。
1、我们一般说某个 SOC 支持网络,说的就是他内部集成网络 MAC 外设,此时我们还需要外接一个网络 PHY 芯片。 一般常见的通用 SOC 都会集成网络 MAC 外设,比如 STM32F4/F7/H7 系列、 NXP 的 I.MX系列,内部集成网络 MAC 的优点如下:
- 内部 MAC 外设会有专用的加速模块,比如专用的 DMA,加速网速数据的处理。
- 网速快,可以支持 10/100/1000M 网速。
- 外接 PHY 可选择性多,成本低。
2、内部的 MAC 外设会通过 MII 或者 RMII 接口来连接外部的 PHY 芯片, MII/RMII 接口用来传输网络数据。 另外主控需要配置或读取 PHY 芯片,也就是读写 PHY 的内部寄存器,所以还需要一个控制接口,叫做 MDIO, MDIO 很类似 IIC,也是两根线,一根数据线叫做 MDIO,一根时钟线叫做 MDC。
SOC 内部 MAC 外设与外部 PHY 芯片的连接如图 69.1.1.2 所示: MII/RMII 用于传输数据,MDIO 用于读写phy内部寄存器,控制phy芯片。
3、MDIO 接口 MDIO 全称是 Management Data Input/Output,直译过来就是管理数据输入输出接口,是一个简单的两线串行接口,一根 MDIO 数据线,一根 MDC 时钟线。 驱动程序可以通过 MDIO 和MDC 这两根线访问 PHY 芯片的任意一个寄存器。 MDIO 接口支持多达 32 个 PHY。同一时刻内只能对一个 PHY 进行操作,那么如何区分这 32 个 PHY 芯片呢?和 IIC 一样,使用器件地址即可。同一 MDIO 接口下的所有 PHY 芯片,其器件地址不能冲突,必须保证唯一,具体器件地址值要查阅相应的 PHY 数据手册。(通常可以使用硬件来决定phy地址)
4、RJ45 接口 网络设备是通过网线连接起来的,插入网线的叫做 RJ45 座. RJ45 座要与 PHY 芯片连接在一起,但是中间需要一个网络变压器,网络编译器用于隔离以及滤波等,网络变压器也是一个芯片,外形一般如图 69.1.4.2 所示: 完整的网络接口硬件: 5、Linux网络子系统
我们这里研究内核空间即可,在内核空间分成5层,分别是:
- 1、系统调用接口,它面向的客户是应用层序,为应用程序提供访问网络子系统的统一方法,比如说socket,send等函数的系统调用
- 2、协议无关接口,它提供通用的方法来使用传输层协议,把所有的协议统一起来
- 3、网络协议,它的作用就是实现具体的网络协议
- 4、设备无关接口,这个接口是为了屏蔽底层硬件的差异
- 5、设备驱动,这里实现具体的网卡驱动
**
二、网络设备驱动框架
**
1、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
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
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
88 unsigned char perm_addr[MAX_ADDR_LEN];
89 unsigned char addr_assign_type;
90 unsigned char addr_len;
......
130
133 unsigned long last_rx;
134
135
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
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
174
175
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,
3 IFF_BROADCAST = 1<<1,
4 IFF_DEBUG = 1<<2,
5 IFF_LOOPBACK = 1<<3,
6 IFF_POINTOPOINT = 1<<4,
7 IFF_NOTRAILERS = 1<<5,
8 IFF_RUNNING = 1<<6,
9 IFF_NOARP = 1<<7,
10 IFF_PROMISC = 1<<8,
11 IFF_ALLMULTI = 1<<9,
12 IFF_MASTER = 1<<10,
13 IFF_SLAVE = 1<<11,
14 IFF_MULTICAST = 1<<12,
15 IFF_PORTSEL = 1<<13,
16 IFF_AUTOMEDIA = 1<<14,
17 IFF_DYNAMIC = 1<<15,
18 IFF_LOWER_UP = 1<<16,
19 IFF_DORMANT = 1<<17,
20 IFF_ECHO = 1<<18,
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 设备。
2、注册与卸载网络驱动
内核模块初始化函数static int __init init_func() 和退出函数 static void _exit exit_func()
从中我们可以得出 __init 是告知编译器,将变量或函数放在一个特殊的区域,这个区域定义在vmlinux.lds中。__init 将函数放在代码段的一个子段 “.init.text”(初始化代码段)中,__initdata将数据放在数据段的子段".init.data"(初始化数据段)中。 标记_init的函数,表明该函数在使用一次后就会被丢掉,讲占用的内存释放。
同理也就可以知道_exit 标记的函数只有对模块才起作用,是指明函数是放在代码段的".exit.text"中,特点是只有在模块被卸载的时候该函数才会被调用
mt7915驱动入口在drivers\net\wireless\mediatek\mt_wifi\os\linux\pci_main_dev.c中int __init rt_pci_init_module(void)
|