网卡设备不同于字符设备和块设备, 网络设备并不对应于/dev目录下的文件,它存放在/sys/class/net目录下。
Linux系统对网络设备驱动定义了四个层次:
1. 网络协议接口层:用于实现统一的数据包收发的协议,通过dev_queue_xmit()函数发送数据, 通过netif_rx()函数接收数据
2. 网络设备接口层:通过struct net_device来描述一个具体的网络设备的信息
3. 设备驱动功能层:同于驱动网络设备实现各个功能,通过hard_start_xmit()函数发送数据,通过网络设备上的中断函数接收数据
4. 网络设备与媒介层:用于负责完成数据包发送和接收的物理实体
层次结构如下图(此图基于Linux kernel 4.0,个别函数不适用于kernel 3.5):
在设计具体的网络设备驱动程序时,我们需要完成的主要工作是编写设备驱动功能层的函数填充struct net_device并将其注册入内核。struct 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; /* 中断号 */ ... struct net_device_stats stats; /* 用于保存统计信息 */ const struct net_device_ops *netdev_ops; /* 网络设备操作函数 */ ... unsigned int mtu; /* 最大数据包 */ unsigned short type; /* 接口硬件类型 */ unsigned short hard_header_len; /* hardware hdr length,一般赋值为ETH_HLEN */ ... unsigned char perm_addr[MAX_ADDR_LEN]; /* 设备MAC地址可以通过ifconfig看到 */ ... /* Called from unregister, can be used to call free_netdev */ void (*destructor)(struct net_device *dev); ... };
其中,
1. struct net_device_stats定义如下:
struct net_device_stats { unsigned long rx_packets; /* 收到的数据包数 */ unsigned long tx_packets; /* 发送的数据包数 */ unsigned long rx_bytes; /* 收到的字节数 */ unsigned long tx_bytes; /* 发送的字节数 */ unsigned long rx_errors; /* 收到的错误数据包数 */ unsigned long tx_errors; /* 发送的错误数据包数 */ ... };
2. struct net_device_ops定义如下:
struct net_device_ops { int (*ndo_init)(struct net_device *dev); void (*ndo_uninit)(struct net_device *dev); int (*ndo_open)(struct net_device *dev); int (*ndo_stop)(struct net_device *dev); /* 数据包发送函数,也就是图中的hard_start_xmit() */ netdev_tx_t (*ndo_start_xmit) (struct sk_buff *skb, struct net_device *dev); ... void (*ndo_set_rx_mode)(struct net_device *dev); int (*ndo_set_mac_address)(struct net_device *dev, void *addr); ... /* 发送超时函数 */ void (*ndo_tx_timeout) (struct net_device *dev); .. struct net_device_stats* (*ndo_get_stats)(struct net_device *dev); ... };
在数据包发送函数中,我们可以知道数据包是struct sk_buff(socket buffer,套接字缓冲区),此结构体定义如下:
struct sk_buff { struct sk_buff *next; /* 双向链表 */ struct sk_buff *prev; ... unsigned int len, /* 数据包大小 */ data_len; /* 数据大小 */ __u16 mac_len, /* MAC包大小 */ hdr_len; /* 帧大小 */ ... __u32 priority; /* 优先级 */ ... __be16 protocol; /* 存放上层的协议类型,可通过eth_type_trans()获取 */ ... sk_buff_data_t transport_header; /* 传输层头部的偏移值 */ sk_buff_data_t network_header; /* 网络层头部的偏移值 */ sk_buff_data_t mac_header; /* MAC数据链路层头部的偏移值 */ /* These elements must be at the end, see alloc_skb() for details. */ sk_buff_data_t tail; /* 数据包的尾部 */ sk_buff_data_t end; /* 尾部空间的尾部 */ unsigned char *head, /* 头部空间的头部 */ *data; /* 数据包的头部 */ ... };
此结构体数据存放格式如下图:
其中头部空间用于存放协议类型,尾部空间用于存储应用层数据。数据包格式如下:
分析完了各个结构体,现在我们通过分析具体文件确定结构体的注册和初始化方式。我选用的设备驱动文件为/drivers/net/cs89x0.c。
文件链接:
https://files.cnblogs.com/files/Lioker/20_cs89x0.zip
二、网络设备驱动分析
在此不再一个一个函数分析,直接给出调用关系:
int __init cs89x0_init(void) -> platform_driver_probe(&cs89x0_driver, cs89x0_platform_probe); -> struct net_device *dev = alloc_etherdev(sizeof(struct net_local)); -> alloc_netdev_mqs(sizeof_priv, "eth%d", ether_setup, txqs, rxqs); -> setup(dev); /* 调用传入的ether_setup()设置net_device成员 */ -> netif_alloc_netdev_queues(dev) /* 分配接收队列和发送队列 */ -> cs89x0_probe1(dev, virt_addr, 0); -> iowrite16(PP_ChipID, ioaddr + ADD_PORT); /* 设置寄存器 */ -> dev->netdev_ops = &net_ops; /* net_device操作函数 */ -> register_netdev(dev); /* 注册net_device */ -> register_netdevice(dev); -> netdev_register_kobject(dev); -> device_add(dev);
init()函数所做的有以下几点:
1. 使用alloc_etherdev()分配struct net_device
2. 设置网卡硬件相关寄存器
3. 设置struct net_device的成员
4. 使用register_netdev()注册struct net_device
接下来,我们来看net_device操作函数net_ops的数据包发送函数。
static const struct net_device_ops net_ops = { ... .ndo_start_xmit = net_send_packet, ... };
此函数调用关系如下:
static netdev_tx_t net_send_packet(struct sk_buff *skb, struct net_device *dev) -> netif_stop_queue(dev); /* 停止上层传下来的数据包 */ -> set_bit(__QUEUE_STATE_DRV_XOFF, &dev_queue->state); -> iowrite16(skb->len, lp->virt_addr + TX_LEN_PORT); /* 设置寄存器,初始化传输队列 */ -> writewords(lp, TX_FRAME_PORT, skb->data, (skb->len + 1) >> 1); /* 发送包 */ -> iowrite16(tmp16, lp->virt_addr + portno); -> dev->stats.tx_bytes += skb->len; -> dev_kfree_skb(skb); /* 释放sk_buff */
现在出现一个问题,在ndo_start_xmit()有停止函数netif_stop_queue(),但没有唤醒函数,因此我们可以在文件中搜索“netif_”查找唤醒函数。
在net_timeout()和net_interrupt()中查找到了netif_wake_queue(),net_timeout定义在net_ops中,那么net_interrupt()又是在哪里注册的呢?
经搜索后,确定net_interrupt()在net_open()中注册。
也就是数据包发送成功后,会产生中断,调用net_interrupt()唤醒上层继续传输数据包
因此,ndo_start_xmit()函数所做的有以下几点:
1. 数据包发送之前,使用netif_stop_queue()停止上层传输数据包
2. 设置寄存器,通过网络设备硬件发送数据包
3. 当数据包发送后,使用dev_kfree_skb()释放struct sk_buff
4. 当数据包发送后,会产生中断,调用net_interrupt()唤醒上层继续传输数据包并更新net_device_stats数据
5. 若数据包发送超时,会调用net_timeout()唤醒上层继续传输数据包
分析完初始化和发包过程,接下来需要分析收包过程。
收包主要通过中断函数net_interrupt()处理,函数内部判断中断类型,若为ISQ_RECEIVER_EVENT,表示为接收中断,调用net_rx()将数据包传输给上层。中断函数net_interrupt()定义如下:
1 static irqreturn_t net_interrupt(int irq, void *dev_id) 2 { 3 struct net_device *dev = dev_id; 4 struct net_local *lp; 5 int status; 6 int handled = 0; 7 8 lp = netdev_priv(dev); 9 ... 10 while ((status = ioread16(lp->virt_addr + ISQ_PORT))) { 11 cs89_dbg(4, debug, "%s: event=%04x\n", dev->name, status); 12 handled = 1; 13 switch (status & ISQ_EVENT_MASK) { 14 case ISQ_RECEIVER_EVENT: 15 /* 接包 */ 16 net_rx(dev); 17 break; 18 case ISQ_TRANSMITTER_EVENT: 19 dev->stats.tx_packets++; 20 netif_wake_queue(dev); /* 唤醒 */ 21 /* 更新net_device_stats数据 */ 22 if ((status & (TX_OK | TX_LOST_CRS | TX_SQE_ERROR | TX_LATE_COL | TX_16_COL)) != TX_OK) { 23 if ((status & TX_OK) == 0) 24 dev->stats.tx_errors++; 25 if (status & TX_LOST_CRS) 26 dev->stats.tx_carrier_errors++; 27 if (status & TX_SQE_ERROR) 28 dev->stats.tx_heartbeat_errors++; 29 if (status & TX_LATE_COL) 30 dev->stats.tx_window_errors++; 31 if (status & TX_16_COL) 32 dev->stats.tx_aborted_errors++; 33 } 34 break; 35 ... 36 break; 37 case ISQ_RX_MISS_EVENT: 38 dev->stats.rx_missed_errors += (status >> 6); 39 break; 40 case ISQ_TX_COL_EVENT: 41 dev->stats.collisions += (status >> 6); 42 break; 43 } 44 } 45 return IRQ_RETVAL(handled); 46 }