1、描述
网卡的驱动其实很简单,它还是与硬件相关,主要是负责收发网络的数据包,它将上层协议传递下来的数据包以特定的媒介访问控制方式进行发送,并将接受到的数据包传递给上层协议。
网卡设备与字符设备和块设备不同,网络设备并不对应于/dev/目录下的文件,不过会存放在/sys/class/net目录下
2、Linux系统对网络设备驱动定义了4个层次,这4个层次有到下分为:
1)网络协议接口层:
实现统一的数据包收发的协议,该层主要负责调用dev_queue_xmit()函数发送数据,netif_rx()函数接收数据
2)网络设备接口层:
通过net_device结构体来描述一个具体的网络设备的信息,实现不同的硬件的统一
3)设备驱动功能层:
用来负责驱动网路设备硬件来完成各个功能,它通过hard_start_xmit()函数启动发送操作,并通过网络设备上的中断触发接收操作
4)网络设备与媒体层:
用来负责完成数据包发送和接受的物理实体,设备驱动功能层的函数都在这物理上驱动的
层次结构如下图所示:
3、网卡驱动初始化
而我们的网卡驱动程序,只需要编写网络设备接口层,填充net_device数据结构的内容并将net_device注册入内核,设置硬件相关操作,使能中断处理等。
3.1 其中net_device结构体的重要成员,整理后如下所示:
struct net_device
{
char name[IFNAMSIZ]; //网卡设备名称
unsigned long mem_end; //该设备的内存结束地址
unsigned long mem_start; //该设备的内存起始地址
unsigned long base_addr; //该设备的内存I/O基地址
unsigned int irq; //该设备的中断号
unsigned char if_port; //多端口设备使用的端口类型
unsigned char dma; //该设备的DMA通道
unsigned long state; //网络设备和网络适配器的状态信息
struct net_device_stats* (*get_stats)(struct net_device *dev); //获取流量的统计信息
//运行ifconfig便会调用该成员函数,并返回一个net_device_stats结构体获取信息
struct net_device_stats stats; //用来保存统计信息的net_device_stats结构体
unsigned long features; //接口特征,
unsigned int flags; //flags指网络接口标志,以IFF_(Interface Flags)开头
//当flags =IFF_UP( 当设备被激活并可以开始发送数据包时, 内核设置该标志)、 IFF_AUTOMEDIA(设置设备可在多种媒介间切换)、
IFF_BROADCAST( 允许广播)、IFF_DEBUG( 调试模式, 可用于控制printk调用的详细程度) 、 IFF_LOOPBACK( 回环)、
IFF_MULTICAST( 允许组播) 、 IFF_NOARP( 接口不能执行ARP,点对点接口就不需要运行 ARP) 和IFF_POINTOPOINT( 接口连接到点到点链路) 等。
unsigned mtu; //最大传输单元,也叫最大数据包
unsigned short type; //接口的硬件类型
unsigned short hard_header_len; //硬件帧头长度,一般被赋为ETH_HLEN,即14
unsigned char dev_addr[MAX_ADDR_LEN]; //存放设备的MAC地址
unsigned long last_rx; //接收数据包的时间戳,调用netif_rx()后赋上jiffies即可
unsigned long trans_start; //发送数据包的时间戳,当要发送的时候赋上jiffies即可
unsigned char dev_addr[MAX_ADDR_LEN]; //MAC地址
int (*hard_start_xmit) (struct sk_buff *skb, struct net_device *dev);
//数据包发送函数, sk_buff就是用来收发数据包的结构体
void (*tx_timeout) (struct net_device *dev); //发包超时处理函数
... ...
}
上面提到的统计信息net_device_stats结构体,其中重要成员如下所示:
struct net_device_stats
{
unsigned long rx_packets; /*收到的数据包数*/
unsigned long tx_packets; /*发送的数据包数 */
unsigned long rx_bytes; /*收到的字节数,可以通过sk_buff结构体的成员len来获取*/
unsigned long tx_bytes; /*发送的字节数,可以通过sk_buff结构体的成员len来获取*/
unsigned long rx_errors; /*收到的错误数据包数*/
unsigned long tx_errors; /*发送的错误数据包数*/
... ...
}
3.2 所以init()函数,初始化网卡步骤如下所示:
1) 使用alloc_netdev()来分配一个net_device结构体
2) 设置网卡硬件相关的寄存器
3) 设置net_device结构体的成员
4) 使用register_netdev()来注册net_device结构体
4、网卡驱动发包过程
在内核中,当上层要发送一个数据包时,就会调用网络设备层里net_device数据结构的成员hard_start_xmit()将数据包发送出去。
hard_start_xmit()发包函数需要我们自己构建,该函数原型如下所示:
int (*hard_start_xmit) (struct sk_buff *skb, struct net_device *dev);
在这个函数中,需要涉及到sk_buff结构体,含义为(socket buffer)套接字缓冲区,用来网络各个层次之间传递数据。
4.1 sk_buff结构体是一个双向链表,其中重要成员如下所示:
struct sk_buff {
/* These two members must be first. */
struct sk_buff *next; //指向下一个sk_buff结构体
struct sk_buff *prev; //指向前一个sk_buff结构体
... ...
unsigned int len, //数据包的总长度,包括线性数据和非线性数据
data_len, //非线性的数据长度
mac_len; //mac包头长度
__u32 priority; //该sk_buff结构体的优先级
__be16 protocol; //存放上层的协议类型,可以通过eth_type_trans()来获取
... ...
sk_buff_data_t transport_header; //传输层头部的偏移值
sk_buff_data_t network_header; //网络层头部的偏移值
sk_buff_data_t mac_header; //MAC数据链路层头部的偏移值
sk_buff_data_t tail; //指向缓冲区的数据包末尾
sk_buff_data_t end; //指向缓冲区的末尾
unsigned char *head, //指向缓冲区的协议头开始位置
*data; //指向缓冲区的数据包开始位置
... ...
}
其中sk_buff结构体的空间,如下图所示:
其中sk_buff->data数据包格式如下图所示:
4.2 所以,hard_start_xmit()发包函数处理步骤如下所示:
1)数据包发送出去之前,需要使用netif_stop_queue()来停止上层传下来的数据包
2)设置寄存器,通过网络设备硬件,来发送数据
3)当数据包发出去后,再调用dev_free_skb()函数来释放sk_buff,该函数原型如下:
void dev_kfree_skb(struct sk_buff *skb);
4)当数据包发出成功,就会进入TX中断函数,然后更新统计信息,调用netif_wake_queue来唤醒,启动上层继续发数据包下来
5)若数据包发出去超时,一直进不到TX中断函数,就会调用net_device结构体的(*tx_timeout)超时成员函数,在该函数中更新
统计信息,调用netif_wake_queue()来唤醒
其中netif_wake_queue()和netif_stop_queue()函数原型如下所示:
void netif_wake_queue(struct net_device *dev); //唤醒被阻塞的上层,启动继续向网络设备驱动层发送数据包
void netif_stop_queue(struct net_device *dev); //阻止上层向网络设备驱动层发送数据包
5、网卡驱动收包过程
而接收数据包主要是通过中断函数处理,来判断中断类型,如果等于ISQ_RECEIVER_EVENT,表示为接受中断,然后进入接收数据函数,通过netif_rx()将数据上交给上层
例如下图所示,参考的内核中自带的网卡驱动:drivers/net/cs89x0.c
如上图所示,通过获取的status标志来判断是什么中断,如果是接受中断,就进入net_rx()
5.1 其中net_rx()收包函数处理步骤如下所示:
1)使用dev_alloc_skb()来构造一个新的sk_buff
2)使用skb_reserve(rx_skb,2);将sk_buff缓冲区里的数据包先后位移2字节,来腾出sk_buff缓冲区里的头部空间
3)读取网络设备硬件上接收到的数据
4)使用memcpy()将数据复制到新的sk_buff里的data成员指向的地址处,可以使用skb_put()来动态扩大sk_buff结构体里中的数据区
5)使用eth_type_trans()来获取上层协议,将返回值赋给sk_buff的protocol成员里
6)然后更新统计信息,最后使用netif_rx()来将sk_buffer传递给上层协议中
其中skb_put()函数原型如下所示:
static inline unsigned char *skb_put(struct sk_buff *skb, unsigned int len);
//len:将数据区向下扩大len字节
使用skb_put()函数后,其中sk_buff缓冲区变化如下图:
6、写虚拟网卡驱动
本届便开始来写一个简单的虚拟网卡驱动,也就是说不需要硬件相关操作,所以就没有中断函数,我们通过linux的ping命令来实现发包,然后在发包函数中,伪造一个收的ping包函数,实现ping通任何ip地址
在init初始函数中:
1)使用alloc_netdev()来分配一个net_device结构体
2)设置net_device结构体的成员
3)使用register_netdev()来注册net_device结构体
在发包函数中:
1)使用netif_stop_queue()来阻止上层向网络设备驱动层发送数据包
2)调用收包函数,并代入发送的sk_buff缓冲区,里面来伪造一个收的ping包函数
3)使用dev_kfree_skb()函数来释放发送的sk_buff缓存区
4)更新发送的统计信息
5)使用netif_wake_queue()来唤醒被阻塞的上层
在收包函数中:
首先修改发送的sk_buff里数据包的数据,使它变为一个接受的sk_buff,其中数据包结构如下图所示:
1)需要对调上的ethhdr结构体“源/目的”MAC地址
2)需要对调上图的iphdr结构体"源/目的"IP地址
3)使用ip_fast_csum()来重新获取iphdr结构体的校验码
4)设置上图数据包的数据类型,之前是发送ping包0x08,需要改为0x00,表示接受ping包
5)使用dev_alloc_skb()来构造一个新的sk_buff
6)使用skb_reserve(rx_skb,2);将sk_buff缓冲区里的数据包先后位移2字节,来腾出sk_buff缓冲区里的头部空间
7)使用memcpy()将之前修改好的sk_buff->data复制到新的sk_buff里的data成员指向的地址处
memcpy(skb_put(rx_skb, skb->len), skb->data, skb->len);
// skb_put():来动态扩大sk_buff结构体里中的数据区,避免溢出
8)设置新的sk_buff其它成员
9)使用eth_type_trans()来获取上层协议,将返回值赋给sk_buff的protocol成员里
10)然后更新接收统计信息,最后使用netif_rx()来将sk_fuffer传递给上层协议中
7、驱动具体代码如下:
/* 参考
* driversnetcs89x0.c
*/
#include #include #include #include #include #include
上一篇:S3C2440 Nor Flash驱动(二十四)
下一篇:S3C2440 热拔插驱动 修改mdev配置支持U盘自动挂载(三十三)
推荐阅读最新更新时间:2024-11-02 12:06
设计资源 培训 开发板 精华推荐
- LTC3414,单片式 1.5V/4A 稳压器
- DC9003A-B,基于 LTC5800 贴片天线的 SmartMesh IP 评估/开发节点
- 用于汽车照明的四路 SPST 模拟开关
- 用于白光 LED 驱动器的 TB62737FPG 升压型 DC-DC 转换器的典型应用
- ADR361B、2.5V 低功耗、低噪声堆叠电压基准的典型应用
- 来自 12Vin 的 LTC3859EUHF 高效三路 24V/1V/1.2V 转换器的典型应用电路
- ADD5043-868-2-GEVK:用于 DVK-2 评估套件的 AX5043 868 MHz 附加套件
- LTM8056 的典型应用 - 58VIN、48Vout 降压-升压模块稳压器
- 四通道PCI Express、热插拔控制器MAX5960评估板图
- LTC3621HMS8E 1.2Vout、同步至 600kHz、强制连续模式同步降压型稳压器的典型应用
- #micropython大作战#多种参与姿势,大家一起来玩micropython!
- 电子工程师,如何更好地拥抱GaN?参与问卷有好礼!
- 拆惊喜抽奖:泰克全新3系列MDO和4系列MSO示波器来啦
- 【白皮书免费下载】施耐德电气三宝典:智能配电、关键电源,全生命周期服务
- 【抢楼有礼】TI TMS320F28377S 入门经验大搜集!
- 有奖直播:ADI在中国能源互联网应用中的技术及产品 1月8日上午10:00-11:30 准时开启!
- 与PI一起探索 LinkSwitch-TN2 的秘密看视频答题赢好礼!
- 话说我接触的ADI实验室电路“评估板”
- 下载泰克电源设计测试方案+图文攻略 帮助工程师解决电源效率问题。有好礼
- 有奖直播:安森美光伏和储能产品介绍