欢迎访问电子工程网!   登录 | 免费注册 ]   

yd2763132的个人空间 http://www.eechina.com/space-uid-36266.html [收藏] [复制] [分享] [RSS]

博客

网卡驱动(1)

已有 453 次阅读2011-5-13 22:16 |个人分类:linux| 关键词:

在嵌入式系统中网络有着很重要的作用,那么网卡的驱动也是很重要。
?? 有的ARM嵌入MAC控制器可以直接使用PHY网卡(如S3C4510),有的ARM没有嵌入MAC控制器则需要使用MAC的网卡(如S3C2410).常用的PHY网卡有DM9161.IP101等。常用的带MAC控制器网卡有DM9000系列.CS8900等。对嵌入式系统资源有限而言DM9000使用A系列较好,因为A系列虽没片外拓展MII接口功能,但48pin较100pin要小的多可以节约大量PCB空间。鉴于源码包中DM9000和CS8900唾手可得,现以此两种网卡为例进行说明:
??DM9000和CS8900均为ISA总线芯片,但是后者较前者复杂的多。
??
??实现DM9000与S3C2410连接必须对两者间的数据、地址、控制三大总线进行连接和转换。S3C2410 是32 位微处理器,有32 根地址线,支持4GB 存储空间。其中0—40000000 的1G 空间被分为8 块128M 空间,分别NGCS0—NGCS7片选。DM9000为16位以太网控制芯片。下图给出了S3C2410 与DM9000 的连接方法。
?
??对DM9000读写操作,首先对DM9000 正确寻址。AEN (地址允许)是输入引脚片选信号。SA4~SA9 是地址总线4~9位,当AEN低且SA9和SA8高,而SA7、SA6、SA5、SA4为低时,则DM9000被选中。DM9000 默认I/0 基地址为300H。CMD 引脚用于设置COMMAND 模式,CMD为高时,选择数据端口。CMD 为低时,选地址端口。数据端口和地址端口的地址码由下式决定:
DM9000 地址端口=高位片选地址+300H+0H
DM9000 数据端口=高位片选地址+300H+4H
其中,高位片选地址由S3C2410 的GCS3 提供,即为:0X180000000H。
??
CS8900共有IO模式.存储器模式.DMA模式三种工作模式。一般使用其缺省IO模式,此方式和DM9000 IO模式较相近。其采用封装页结构模式,一般是操作16个寄存器;他们分别为如下几个:
0000h Receive/Transmit Data(Port 0):用于16bit的读写数据操作。
0002h Receive/Transmit Data(Port 1):和0端口用于32bit读写数据操作。
0004h TxCMD (Transmit Command):发送命令
0006h TxLength (Transmit Length):发送数据包长度。
0008h Interrupt Status Queue:中断状态
000Ah PacketPage Pointer:封装页地址
000Ch PacketPage Data (Port 0):封装页数据--用于16bit
000Eh PacketPage Data (Port 1):封装页数据--用于32bit??
对寄存器的操作与9000相似,将寄存器的地址写入PacketPage Pointer中,从PacketPage Data读/写数据。
 

由以上图可知处于IO模式的首地址为:高位片选地址+ADDR24选通地址+300H
高位片选地址使用GCS3选通芯片即为:0X180000000H
ADDR24选通地址:因为读写信号低有效,所以只有为高电位时才能使能读写信号即地址为:0x01000000??
在使用过程中一般是先将19000000H映射后再加上300来使用,而9000中的300H是ADDR2来决定的所以其是加起来后才映射地址。??
??
网卡驱动所能看到的仅仅是MAC帧,MAC帧独立于任何上层协议,仅仅属于数据链路层。
MAC 帧与包格式:
MAC 发送所有域的每一个字节,除帧检测序列FCS 外,总是最低位在前。在此,对帧和包的概念作一个说明,“包”通常是指发送或接收的所有字节序列。而“帧”仅指由站发送或接收的字节。??
MAC 帧与包的格式描述??
?
 
简单表示就是:PR|SD|DA|SA|TYPE|IP packet|FCS
?
发送的skb是什么样?(内核决定)
发送的MAC帧格式:PR|SD|DA|SA|TYPE|IP packet|FCS. SD|DA|SA|TYPE|IP packet是驱动程序需要提供的MAC帧内容,PR|SD以及FCS的部分由网卡自动填充进去。

接收的skb如何?(网卡决定)
从物理线路上接收到的是标准的MAC帧,和发送是一致.
现在已DM9000为例说明其从物理线路上接受到的数据:
01|status|byte count low|byte count high|DA|SA|TYPE|IP packet|FCS 前4个是DM9000自动产生,不是从线路接收到的。后面的则是从线路接收到的。??
??
linux网络设备驱动的结构??
从上到下可分为4层,依次为网络协议接口层.网络设备接口层.提供实际功能的设备驱动层以及网络设备与媒介层:
1.网络协议接口层向网络层协议提供统一的数据包收发接口,不论上层协议为ARP还是IP,都通过dev_queue_xmit函数发送数据,并通过哦netif_rx函数接受数据。这一层的存在使得上层协议独立于具体的设备。
2.网络设备接口层向协议接口层提供统一的用于描述具体网络设备属性和操作侧结构体net_device,该结构体是设备驱动功能层中各函数的容器。实际上,网络设备接口层从宏观上规划了具体操作硬件的设备驱动功能层的结构。
3.设备驱动层各函数是网络设备接口层net_device数据结构的具体成员,是驱动网络设备硬件完成相应动作的程序,它通过hard_start_xmit函数启动发送操作,并通过网络设备上的中断触发接收操作。
4.网络设备与媒介层是完成数据包发送和接收的物理实体,包括网络适配器和具体的传输媒介,网络适配器被设备驱动功能层中的函数物理上驱动。对于linux而言,网络设备和媒介都可以是虚拟的。

网络协议接口层最主要的功能时给上层协议提供了透明的数据包发送和接收接口。当上层ARP或IP协议需要发送数据包时,将会调用网络协议接口层的dev_queue_xmit函数,该函数将会调用设备驱动功能层定义的hard_start_xmit函数启动发送操作发送数据包到物理层。同样地,在接受过程中也通过netif_rx函数给上层传递一个数据缓冲。这样双方操作的对象就是套接字缓冲结构struct sk_buff
struct sk_buff {
struct sk_buff *next;//sk_buff是一个双向链表
struct sk_buff *prev;

struct sock *sk;//缓冲所属的套接字
struct skb_timeval tstamp;//到达的时间
struct net_device *dev;//接受和发送该缓冲区的设备
struct net_device *input_dev;//

union {
struct tcphdr *th;
struct udphdr *uh;
struct icmphdr *icmph;
struct igmphdr *igmph;
struct iphdr *ipiph;
struct ipv6hdr *ipv6h;
unsigned char *raw;
} h;//传输层TCP/IP协议头

union {
struct iphdr *iph;
struct ipv6hdr *ipv6h;
struct arphdr *arph;
unsigned char *raw;
} nh;//网络层协议头

union {
unsigned char *raw;
} mac;//链路层协议头

struct dst_entry *dst;//发送目的地址,存放了路由路径中下一台主机地址
struct sec_path *sp://安全路径

char cb[40];//任何层均可使用的缓冲,一般用来存放控制指令和控制数据

unsigned int len,//当前协议数据包长度
data_len,//数据长度
mac_len,//MAC地址长度
csum;//校验和
__u32 priority;//数据队列优先级
__u8 local_df:1,//容许本地存在数据残片
cloned:1,//容许复制头部
ip_summed:2,//驱动反馈的IP数据包校验和
nohdr:1,//负载参考,不能修改头部
nfctinfo:3;//该缓冲与连接件的关系
__u8 pkt_type:3,//数据包类型
fclone:2;//
__be16 protocol;//驱动定义的数据包协议

void (*destructor)(struct sk_buff *skb);//销毁缓冲函数
#ifdef CONFIG_NETFILTER//配置网络过滤器
__u32 nfmark;//使能使用钩之间通讯
struct nf_conntrack *nfct;//相关联系
#if defined(CONFIG_IP_VS) || defined(CONFIG_IP_VS_MODULE)
__u8 ipvs_property:1;//IP特性
#endif
#ifdef CONFIG_BRIDGE_NETFILTER
struct nf_bridge_info *nf_bridge;//保存桥接框架数据结构
#endif
#endif /* CONFIG_NETFILTER */
#ifdef CONFIG_NET_SCHED
__u16 tc_index; //交通控制索引
#ifdef CONFIG_NET_CLS_ACT
__u16 tc_verd; //交通控制裁定
#endif
#endif

unsigned int truesize;//缓冲区大小
atomic_t users;//使用者计数
unsigned char *head,//缓冲头起始地址
*data,//数据缓冲起始地址
*tail,//数据缓冲结束地址
*end;//缓冲头结束地址
};??
sk_buff有两个非常重要的长度字段,即len和truesize,分别用来描述当前协议数据包的长度和数据缓冲区的实际长度。skb_init函数创建了sk_buff的对象缓存,由全局变量skbuff_head_cache指向这个缓存。

struct sk_buff *alloc_skb(unsigned int size,gfp_t priority)??
struct sk_buff *dev_alloc_skb(unsigned int size)??
以上为内核用于分配套接字缓冲区的函数,分配成功之后,因为还没有存放具体的网络数据包,所以sk_buff的data.tail指针都指向存储空间的起始地址head,而len的大小为0.??
??
struct sk_buff *alloc_skb(unsigned int size,gfp_t priority)
struct sk_buff *dev_kfree_skb(struct sk_buff *skb)
用于释放套接字缓冲区和数据缓冲区。

以下为几个操作套接字缓冲区的操作函数:
skb_put:将tail指针下移,增加sk_buff的len值,主要用于建立数据缓冲区。
skb_push:将data指针上移,主要用于在数据包发送时添加头部。
skb_pull:将data指针下移,主要用于下层向上层协议提交数据包,使data指针向上一层协议的协议头。
skb_reserve:将data和tail指针同时下移。
??
数据链路层通过调用skb_pull剥离以太网协议头,向网络层IP传递数据包。网络层通过调用skb_pull剥离IP头。应用层在调用recv接受数据时,从skb->data+sizeof的位置开始复制到应用层缓冲区而不剥离udp头部到最后也没有被剥离。

网络设备接口层提供了统一抽象的数据结构struct net_device,该设备是一个巨型结构体,写驱动时只需了解其中的一部分。
char name[IFNAMSIZ];设备名称
unsigned long mem_end;
unsigned long mem_start;//共享内存的起始和结束地址

unsigned long base_addr;//网络设备IO基地址
unsigned int irq;//终端号
unsigned char if_port;//端口类型
unsigned char dma;//DMA通道号

unsigned short hard_header_len;//网络设备硬件头长度,驱动为数据链路层的以太网长度为14BYTE
unsigned mtu; //最大传输单元

unsigned char dev_addr[MAX_ADDR_LEN];//物理地址
unsigned char broadcast[MAX_ADDR_LEN];//广播地址

unsigned short flags;//网络接口标志,以IFF开头

对于设备操作函数在新的内核中以struct net_device_ops总结起来了,其中常用的成员如下:
int (*open)(struct net_device *dev);
int (*stop)(struct net_device *dev);
int (*hard_start_xmit) (struct sk_buff *skb,struct net_device *dev);
void (*tx_timeout) (struct net_device *dev);
int (*hard_header) (struct sk_buff *skb,struct net_device *dev,
unsigned short type,void *daddr,void *saddr,unsigned len);
struct net_device_stats* (*get_stats)(struct net_device *dev);
int (*do_ioctl)(struct net_device *dev,struct ifreq *ifr, int cmd);
int (*set_config)(struct net_device *dev,struct ifmap *map);
int (*set_mac_address)(struct net_device *dev,void *addr);
int (*poll) (struct net_device *dev, int *quota);//支持NAPI兼容的设备驱动,以轮询方式操作接口
//========================================================================================
unsigned long trans_start;//发送时间
unsigned long last_rx; /* Time of last Rx */

void *priv;//用于指向驱动程序中的私有数据结构是很重要的一个数据结构

设备驱动函数通过
int register_netdevice(struct net_device *dev);
int unregister_netdevice(struct net_device *dev);
来注册和注销网络设备函数。

驱动函数的分解讲析:
1.初始化函数:进行硬件的探测和使能工作;分配net_device结构体且初始化相关数据和操作成员;获取私有结构体且初始化其成员。通过调用ether_setup函数对net_device公共成员进行初始化,而私有成员于具体的设备有关(即上面列出的struct net_device_ops成员)需要单独初始化。

void ether_setup(struct net_device *dev)
{
dev->change_mtu = eth_change_mtu;//改变最大传输值
dev->hard_header = eth_header;//添加帧头类型和硬件地址
dev->rebuild_header = eth_rebuild_header;//完成地址解析后更换帧头
dev->set_mac_address = eth_mac_addr;//设置设备的MAC地址
dev->hard_header_cache = eth_header_cache;
dev->header_cache_update = eth_header_cache_update;
//在响应一个变化中, 更新hh_cache结构中的目的地址的方法
dev->hard_header_parse = eth_header_parse;//解析以太网帧头

dev->type = ARPHRD_ETHER;//数据链路层使用以太网
dev->hard_header_len = ETH_HLEN;//帧数据头位组数14
dev->mtu = 1500;//以太网数据包最大数
dev->addr_len = ETH_ALEN;//6
dev->tx_queue_len = 1000; //每队列容许最大帧数
dev->flags = IFF_BROADCAST|IFF_MULTICAST;//广播和组播

memset(dev->broadcast,0xFF, ETH_ALEN);//广播地址为0xFF 0xFF 0xFF 0xFF 0xFF
}
具体操作查看eth.c文件.

2.网络设备的打开和释放函数:主要申请IO区域 中断 和DMA通道,使用netif_start_queue函数,激活设备发送队列;调用内核提供的netif_stop_queue函数停止设备队列包,释放IO区域 中断 和DMA通道

3.网络设备驱动从上层协议传递过来的套接字缓冲放到临时缓冲中,设置硬件的寄存器驱使设备进行数据发送操作。

4.设备接收函数主要是由中断引发的中断处理,主要是分配套接字缓冲和数据缓冲区,将接收到的数据复制到数据缓冲区中,并调用netif_rx函数将缓冲上传给上层协议即IP层;对于NAPI处理接收,是采用轮询方式来处理的,在中断函数中要采用netif_rx_schedule函数调度以等待软中断的发生,当软中断发生时则调用poll函数。

5.网路连接状态:程序往往使用一个定时器来对链路状态进行周期性检查,在打开函数中要定义一个定时器或者使用定时工作队列处理。netif_carrier_on和netif_carrier_off来改变设备的连接状态。

6.使用ethool工具内核调用函数来支持ethool命令控制网卡。
 


路过

鸡蛋

鲜花

握手

雷人

评论 (0 个评论)

facelist

您需要登录后才可以评论 登录 | 立即注册

回顶部