Chinaunix首页 | 论坛 | 博客
  • 博客访问: 292489
  • 博文数量: 76
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 715
  • 用 户 组: 普通用户
  • 注册时间: 2015-05-20 20:38
文章分类
文章存档

2016年(20)

2015年(56)

分类: 嵌入式

2015-06-13 21:13:13

P { margin-bottom: 0.21cm; }

网卡驱动程序

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)中,简称为skbsk_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->dataskb->tailskb->headdev_alloc_skbGFP_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)在能够处理数据包之前,网络层必须知道数据包的一些信息,为此,必须在将缓冲区传递到长层之前,对devprotocol成员正确赋值。

以太网支持代码导出了辅助函数eth_type_trans,用来查找protocol中的正确值。

skb->ip_summed的可能策略如下:CHECKSUM_HWCHECKSUM_NONECHECKSUM_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_packetstx_packetsrx_bytes tx_bytes,其中包含了已接收和已发送的数据包个数以及已传输的octet总量。

5)接收数据包过程中的最后一个步骤由netif_rx执行,它将套接字缓冲区传递给上层软件处理。


(三)这个程序的目的,它打算模拟网卡的发送接收数据包的过程。通过对调每个数据包IP头中的源,目的,来模拟发送和接收过程,但是不会检查数据包的是否真正传送IP信息。从这个角度来理解网卡驱动程序的发送接收过程。


(四)下面总结写网卡驱动程序的步骤:

1)先分配一个net_device结构体,并通过动态分配函数里面的ether_setup函数完成其他一些通用信息的初始化,但是一些重要的初始化如hard_start_xmit需要自己初始化。之后再指定一些如flagsfeatures等信息。初始化完成以后注册这个结构体。

2)写出来网卡的数据包发送函数,即上面提到的hard_start_xmit函数。在发送过程中需要注意并发控制传输的问题,先要通过调用netif_stop_queue函数来中断传输队列,之后发送数据包,发送完毕以后再次打开队列。

3)在emulator_rx_packet这个函数中,前面的操作是对于IP数据包报头的信息的处理,包括对调MAC地址和IP地址等,这些在普通的数据包传输过程不是必须的,是虚拟的一个过程。之后是真正的接收过程,先构造一个sk_buff结构体,分配缓冲区,并调用memcpy将数据包数据拷贝到缓冲区中。对devprotocol成员正确赋值。更新统计计数器,最后调用netif_rx函数提交sk_buff结构体,传递给上层软件处理。

阅读(1455) | 评论(0) | 转发(0) |
0

上一篇:NOR FLASH驱动程序

下一篇:Mount 命令

给主人留下些什么吧!~~