分类: 嵌入式
2015-06-13 21:13:13
网卡驱动程序
15年6月13日21:08:45
程序如下:
1
2
#include
3
#include
4
#include
5
#include
6
#include
7
#include
8
#include
9
#include
10
#include
11
#include
12
#include
13
#include
14
#include
15
#include
16
#include
17
#include
18
#include
19
#include
20
21
#include
22
#include
23
#include
24
25 static struct net_device *virt_net_dev;
26
27 static int emulator_rx_packet(struct sk_buff *skb, struct net_device *dev)
28 {
29 /* 参考 LDD3 */
30 unsigned char *type;
31 struct iphdr *ih;
32 __be32 *saddr, *daddr, tmp;
33 unsigned char tmp_dev_addr[ETH_ALEN];
34 struct ethhdr *ethhdr;
35
36 struct sk_buff *rx_skb;
37
38 // 从硬件读出/保存数据
39 /* 对调源/目的MAC地址 */
40 ethhdr = (struct ethhdr *)skb->data;
41 memcpy(tmp_dev_addr, ethhdr->h_dest, ETH_ALEN);
42 memcpy(ethhdr->h_dest, ethhdr->h_source, ETH_ALEN);
43 memcpy(ethhdr->h_source, tmp_dev_addr, ETH_ALEN);
44
45 /* 对调源/目的IP地址 */
46 ih = (struct iphdr *)(skb->data + sizeof(struct ethhdr));
47 saddr = &ih->saddr;
48 daddr = &ih->daddr;
49
50 tmp = *saddr;
51 *saddr = *daddr;
52 *daddr = tmp;
53
54 //((u8 *)saddr)[2] ^= 1; /* change the third octet (class C) */
55 //((u8 *)daddr)[2] ^= 1;
56 type = skb->data + sizeof(struct ethhdr) + sizeof(struct iphdr);
57 //printk("tx package type = %02x\n", *type);
58 // 修改类型,原来0x8 表示 ping
59 *type = 0; /* 0 表示 reply */
60
61 ih->check = 0; /* and rebuild the checksum (ip needs it) */
62 ih->check = ip_fast_csum((unsigned char *)ih,ih->ihl);
63
64 // 构造一个 sk_buff 结构体
65 rx_skb = dev_alloc_skb(skb->len + 2);
66 skb_reserve(rx_skb, 2); /* align IP on 16B boundary */
67 memcpy(skb_put(rx_skb, skb->len), skb->data, skb->len);
68
69 /* Write metadata, and then pass to the receive level */
70 rx_skb->dev = dev;
71 rx_skb->protocol = eth_type_trans(rx_skb, dev);
72 rx_skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */
73 dev->stats.rx_packets++;
74 dev->stats.rx_bytes += skb->len;
75
76 // 提交 sk_buff
77 netif_rx(rx_skb);
78 }
79
80 static int vnet_sent_packet(struct sk_buff *skb, struct net_device *dev)
81 {
82 static int cnt = 0;
83 printk("vnet_sent_packet cnt = %d\n", ++cnt);
84
85 netif_stop_queue(dev);
86
87 emulator_rx_packet(skb, dev);
88
89 dev_kfree_skb(skb);
90 netif_wake_queue(dev);
91
92 dev->stats.tx_packets++;
93 dev->stats.tx_bytes += skb->len;
94
95 return 0;
96 }
97
98
99 static int virt_net_init(void)
100 {
101 virt_net_dev = alloc_netdev(0, "vnet%d", ether_setup);
102
103 virt_net_dev->hard_start_xmit = vnet_sent_packet;
104
105 /* MAC ADDRESS */
106 virt_net_dev->dev_addr[0] = 12;
107 virt_net_dev->dev_addr[1] = 34;
108 virt_net_dev->dev_addr[2] = 56;
109 virt_net_dev->dev_addr[3] = 78;
110
111 virt_net_dev->flags |= IFF_NOARP;
112 virt_net_dev->features |= NETIF_F_NO_CSUM;
113
114 register_netdev(virt_net_dev);
115 return 0;
116 }
117
118 static void virt_net_exit(void)
119 {
120 unregister_netdev(virt_net_dev);
121 free_netdev(virt_net_dev);
122 }
123
124 module_init(virt_net_init);
125 module_exit(virt_net_exit);
126
127 MODULE_LICENSE("GPL");
128
(一)
网卡驱动程序中,每一个接口由一个net_device结构体来描述,这个结构体既大又复杂,在此不一一列举,这个结构体必须动态分配,用来分配的内核函数是alloc_netdev,函数原型如下:
struct net_device *alloc_netdev(int sizeof_priv, const char *name,
void (*setup)(struct net_device *));
第一个参数是私有数据,可以设置为0;第二个参数是名字,比如eth0等的形式,里面含有“%d”的形式,内核自动用下一个可用的接口号代替%d,名字可以自己起,最后一个参数是setup函数,是一个初始化函数,用来设置net_device 结构体通用的部分,例如在本例中,
virt_net_dev = alloc_netdev(0, "vnet%d", ether_setup);
我们第三个参数用 ether_setup来实现通用的设置。ether_setup是内核提供的对net_device结构体的共有成员快速赋值的函数。
除了用 ether_setup来实现通用的设置外,还需要设定一些重要的成员初始化,其中比较重要的是:
int (*hard_start_xmit) (struct sk_buff *skb, struct net_device *dev); //初始化数据包的传输。
int (*open)(struct net_device *dev); //打开接口。
int (*stop)(struct net_device *dev); //关闭接口。
int (*hard_header) (struct sk_buff *skb, struct net_device *dev, unsigned short type,
void *daddr, void *saddr, unsigned len); //根据源和目标硬件建立硬件头。
void (*tx_timeout) (struct net_device *dev); //数据包在合理的传输时间内失败后的调用函数。
等几个函数。
完成初始化以后,用int register_netdev(struct net_device *dev);函数来完成注册。
同理,结构体的注销用free_netdev函数来完成,模块的卸载用
void unregister_netdev(struct net_device *dev);来完成。
(二)数据包的发送与接收
无论何时,内核要发送一个数据包,它都会调用hard_start_transmit函数来将数据放入外发队列。而这个要发送的数据包位于一个套接字缓冲区结构(sk_buff)中,简称为skb, sk_buff可以看作是linux网络子系统中数据传输的“中枢神经”,当发送数据包的时候,网络出路模块必须建立一个包含要传输数据的sk_buff,然后将这个sk_buff交给下层,各层在sk_buff中添加不同的协议头直至交给网络设备发送。相反,在接收过程中,当网络设备从网络媒介上接收到数据包以后,各层剥去不同的协议头,直至交送给用户。
下面列举部分重要成员如下:
struct sk_buff {
/* These two members must be first. */
struct sk_buff *next;
struct sk_buff *prev;
。。。。。。。。。
unsigned int len,
data_len,
mac_len;
__be16 protocol;
。。。。。。。。。。。。。。。
sk_buff_data_t transport_header;
sk_buff_data_t network_header;
sk_buff_data_t mac_header;
/* 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;
unsigned int truesize;
atomic_t users;
};
sk_buff的函数如下:
struct sk_buff *alloc_skb(unsigned int len, int priority);
struct sk_buff *dev_alloc_skb(unsigned int len);
分配一个缓冲区,。alloc_dev函数分配一个缓冲区并初始化skb->data和skb->tail为skb->head。dev_alloc_skb一GFP_ATOMIC优先级调用alloc_skb。
void dev_kfree_skb(struct sk_buff *skb)
驱动程序使用这个函数释放一个缓冲区。
unsigned char *skb_put(struct sk_buff *skb, unsigned int len);
用这个函数在缓冲区尾部添加数据,驱动程序调用memcpy(skb_put(rx_skb, skb->len), skb->data, skb->len);来拷贝数据。
下面介绍数据包发送的过程:
上面介绍了, 内核通过调用hard_start_transmit函数来发送数据包,hard_start_transmit函数通过net_device结构中的一个自旋锁(xmit_lock)获得并发时的保护,但是由于实际的硬件接口是异步传输数据包的,而且用来保存外发数据包的存储空间有限(对于某些硬件,也许单个外发数据包的传输就会使内存耗尽),所以驱动程序需要告诉网络系统在硬件能够接受新数据之前,不能启动其他的数据包传输,所以调用netif_stop_queue来完成这一通知,
static inline void netif_stop_queue(struct net_device *dev)
然后开始发送这次数据包的传输,当完成本次传输以后,驱动程序需要重新启动该队列。这时候需要调用static inline void netif_wake_queue(struct net_device *dev)来完成。
下面介绍数据包接收的过程:
(1)第一步是分配一个保存数据包的缓冲区,注意缓冲区的分配函数(dev_alloc_skb)需要知道数据长度。
(2)一旦拥有一个合法的skb指针,则调用memcpy将数据包数据拷贝到缓冲区内,skb_put函数刷新缓冲区内的数据末尾指针,并且返回新创建数据区的指针。例如本例中:
memcpy(skb_put(rx_skb, skb->len), skb->data, skb->len);
(3)在能够处理数据包之前,网络层必须知道数据包的一些信息,为此,必须在将缓冲区传递到长层之前,对dev和protocol成员正确赋值。
以太网支持代码导出了辅助函数eth_type_trans,用来查找protocol中的正确值。
skb->ip_summed的可能策略如下:CHECKSUM_HW,CHECKSUM_NONE, CHECKSUM_UNNECESSARY。本例中用如下两句完成这两条的初始化:
rx_skb->protocol = eth_type_trans(rx_skb, dev);
rx_skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */
(4)驱动程序更新其统计计数器,以记录已接收到的每个数据包。统计结构包含如下成员:
struct net_device_stats
{
unsigned long rx_packets; /* total packets received */
unsigned long tx_packets; /* total packets transmitted */
unsigned long rx_bytes; /* total bytes received */
unsigned long tx_bytes; /* total bytes transmitted */
unsigned long rx_errors; /* bad packets received */
unsigned long tx_errors; /* packet transmit problems */
unsigned long rx_dropped; /* no space in linux buffers */
unsigned long tx_dropped; /* no space available in linux */
unsigned long multicast; /* multicast packets received */
unsigned long collisions;
/* detailed rx_errors: */
unsigned long rx_length_errors;
unsigned long rx_over_errors; /* receiver ring buff overflow */
unsigned long rx_crc_errors; /* recved pkt with crc error */
unsigned long rx_frame_errors; /* recv'd frame alignment error */
unsigned long rx_fifo_errors; /* recv'r fifo overrun */
unsigned long rx_missed_errors; /* receiver missed packet */
/* detailed tx_errors */
unsigned long tx_aborted_errors;
unsigned long tx_carrier_errors;
unsigned long tx_fifo_errors;
unsigned long tx_heartbeat_errors;
unsigned long tx_window_errors;
/* for cslip etc */
unsigned long rx_compressed;
unsigned long tx_compressed;
};
最重要的有 rx_packets, tx_packets, rx_bytes, tx_bytes,其中包含了已接收和已发送的数据包个数以及已传输的octet总量。
(5)接收数据包过程中的最后一个步骤由netif_rx执行,它将套接字缓冲区传递给上层软件处理。
(三)这个程序的目的,它打算模拟网卡的发送接收数据包的过程。通过对调每个数据包IP头中的源,目的,来模拟发送和接收过程,但是不会检查数据包的是否真正传送IP信息。从这个角度来理解网卡驱动程序的发送接收过程。
(四)下面总结写网卡驱动程序的步骤:
(1)先分配一个net_device结构体,并通过动态分配函数里面的ether_setup函数完成其他一些通用信息的初始化,但是一些重要的初始化如hard_start_xmit需要自己初始化。之后再指定一些如flags和features等信息。初始化完成以后注册这个结构体。
(2)写出来网卡的数据包发送函数,即上面提到的hard_start_xmit函数。在发送过程中需要注意并发控制传输的问题,先要通过调用netif_stop_queue函数来中断传输队列,之后发送数据包,发送完毕以后再次打开队列。
(3)在emulator_rx_packet这个函数中,前面的操作是对于IP数据包报头的信息的处理,包括对调MAC地址和IP地址等,这些在普通的数据包传输过程不是必须的,是虚拟的一个过程。之后是真正的接收过程,先构造一个sk_buff结构体,分配缓冲区,并调用memcpy将数据包数据拷贝到缓冲区中。对dev和protocol成员正确赋值。更新统计计数器,最后调用netif_rx函数提交sk_buff结构体,传递给上层软件处理。