1网络设备驱动的结构
Linux网络设备驱动程序体系结构如下图,从上到下依次划分为4层,依次为网路协议接口层、网络设备接口层,提供实际功能的设备驱动功能层以及网络设备与媒介层。
(1) 网络协议接口层向网络层协议提供统一的数据包收发接口,不论上层协议为ARP还是IP,都通过dev_queue_xmit()函数发送数据,并通过netif_rx()函数接收数据。这一层的存在使得上层协议独立于具体的设备。
(2) 网络设备接口层向网络协议接口层提供统一的、用于描述具体网络设备属性和操作的结构体net_device,该结构体也是设备驱动功能层各个函数的容器。网络设备接口层从整体上规划了具体操作硬件的设备驱动功能层的结构。
(3) 设备驱动功能层各个函数是网络设备接口层定义的net_device结构体的具体成员,是驱动网络设备硬件完成相关功能的层序,到达该层的网络数据通过hard_start_xmit()函数发送到网络设备与媒介层,并通过网络设备上的中断触发接收从网络设备与媒介发送过来的网络帧。
(4) 网络设备与媒介层是完成数据包发送和接收的物理实体,包括网络适配器和具体的传输媒介,网络适配器被设备驱动功能层中的函数物理上驱动。
在设计具体的网络设备驱动程序时,我们要完成的主要工作是编写设备驱动功能层的相关函数以填充net_device结构体的内容并将net_device结构体注册到linux内核。
2 设备驱动功能层的代码实现
根据3.2.1的理论基础,找到设备驱动功能层所在的路径为
“/home/seen/pdb/uClinux-dist/linux-2.4.x/drivers/net”
2.2.1网络设备驱动的注册与注销
网络设备驱动的注册与注销使用成对出现的register_netdev()和unregister_netdev()函数完成。这两个函数的原型为:
int register_netdev(struct net_device *dev);//注册
void unregister_netdev(struct net_device *dev);//注销
这两个函数都接收一个net_device结构体指针为参数,可见net_device数据结构在网络设备驱动中的核心地位。
2.2.2网络设备驱动程序的模块加载函数
net_device结构体的分配和网络设备驱动注册需要在网络设备驱动程序的模块加载函数中进行,而net_device结构体的释放和网络设备的注销则需要在模块卸载函数中完成,代码如下所示.
int init_module(void) //模块加载函数
{
int ret;
memset((void *)xxx_netdevice[0].name ,0 ,IFNAMSIZ);
ret=register_netdev((struct net_device *)&xxx_netdevice[0]);//注册
if(ret!=0)
{
printk("Regiter EMC 0 xxx FAILED\n");
TRACE_ERROR("Regiter EMC 0 xxx FAILED\n");
return -ENODEV;
}
return 0;
}
void cleanup_module(void) //模块注销函数
{
unregister_netdev((struct net_device *)&xxx_netdevice[0]);//注销
}
2.2.3网络设备的初始化
网络设备的初始化主要完成如下几个方面的工作:
(1) 进行硬件上的准备工作,检查网络设备是否存在,如果存在,则检查设备所使用的硬件资源。
(2) 进行软件接口上的准备工作,分配net_device结构体并对其数据和函数指针成员赋值。
(3) 获得设备的私有信息指针并初始化其各成员的值。如果私有信息中包括自旋锁或信号量等并发或同步操作,则需对其进行初始化。
网络设备初始化代码如下:
int xxx_init(struct net_device *dev)
{
static int which=0 ; //Only one mac for NUC700
struct xxx_priv *priv; //设备私有信息结构体
ether_setup(dev); //初始化以太网设备的公用成员
dev->open=xxx_open;//网络设备的打开
dev->stop=xxx_close;//网络设备的关闭
dev->do_ioctl=xxx_do_ioctl;
dev->hard_start_xmit=xxx_start_xmit;//send packets which were received from up layer
dev->tx_timeout=xxx_timeout; //发送超时函数
dev->get_stats=xxx_get_stats; //设备状态和信息统计
dev->watchdog_timeo =TX_TIMEOUT; //HZ
dev->irq=INT_EMCTXINT0+which;
dev->set_multicast_list=xxx_set_multicast_list;
dev->set_mac_address=xxx_set_mac_address;//设置设备MAC地址
dev->priv=(void*)(((unsigned long)kmalloc(sizeof(struct xxx_priv),GFP_KERNEL))|NON_CACHE_FLAG);
if(dev->priv == NULL)
return -ENOMEM;
memset(dev->priv, 0, sizeof(struct xxx_priv));
memcpy(xxx_mac_address0,(char*)(MAC_ADDR),ETH_ALEN);
memcpy(dev->dev_addr,xxx_mac_address0,ETH_ALEN);
priv=(struct xxx_priv *)dev->priv;
priv->which=which;
priv->cur_tx_entry=0;
priv->cur_rx_entry=0;
printk("%s initial ok!\n",dev->name);
return 0;
}
2.2.4网络设备open函数
网络设备打开函数主要完成以下工作:
(1) 使能设备的硬件资源,申请I/O区域,激活DMA通道等。
(2) 调用linux内核提供的netif_start_queue()函数,激活设备发送队列。
int xxx_open(struct net_device *dev)
{
申请端口、IRQ等
#if HUBMODEL //hub model.
Hub_Init(dev,which);//hub相关寄存器使能。
#else //router model
Router_Init(dev,which);//router相关寄存器使能
#endif…
request_irq(INT_EMCTXINT0+which,&tx_interrupt,SA_INTERRUPT,"",dev) //发包中断
irequest_irq(INT_EMCRXINT0+which,&rx_interrupt,SA_INTERRUPT,"",dev) //收包中断,当有数据包到达网口时,调用该中断函数处理接 收数据包
…
netif_start_queue(dev); //激活发送队列
}
2.2.5 安全芯片HUB模式相关寄存器使能
void Hub_Init(struct net_device *dev,int num)
{
}
1.2.5 安全芯片ROUTER模式相关寄存器使能
void Router_Init(struct net_device *dev,int num)
{
}
2.2.6发送网络包
如4.1网络设备驱动结构的流程图所示,网络设备驱动完成数据包的发送流程如下:
(1) 网络设备驱动层序从上层协议传递过来的sk_buff参数中获得数据包的有效数据和长度,将有效数据放入临时缓冲区。
(2)对于以太网,如果有效数据有效长度小于以太网冲突检测所要求数据帧的最小长度ETH_ZLEN,则给临时缓冲区的末尾填0.
(3)设置硬件的寄存器,驱使网络设备进行数据发送操作。
对于本工程而言,发送数据包的函数原型为:
int xxx_start_xmit(struct sk_buff *skb, struct net_device *dev)
该函数对应于dev->hard_start_xmit;
static int xxx_init(struct net_device *dev)
{
.......
//发送数据包
dev->hard_start_xmit=nuc700_start_xmit;
.......
}
对于发送数据包函数xxx_start_xmit()来说,此时的数据包是最完整的ip网络数据包,通过解析数据包的目标ip地址,来决定数据包最终向wan口转发,还是向lan口转发。需要向数据包中加入2个字节的flags。flag值为0x8004表示向lan口转发数据,flag值为0x80A0表示向wan口转发数据。
2.2.7 接收网络包
如4.1网络设备驱动结构的流程图所示,当设备的中断处理程序rx_interrupt()判断中断类型为数据包接收中断时,则调用函数netdev_rx()完成更深入的数据包接收工作。
void netdev_rx(struct net_device *dev);
netdev_rx()做了三件事情:
首先,从硬件读取到接收数据包有效数据的长度;
然后,读取硬件上接收的数据并放入数据缓冲区;
最后,将数据包上交给上层协议。
接收网络包部分的代码比较易懂,这里我就不再列出。可以阅读网络驱动内核源码相关部分。
2.2.8网线插拔状态监测
顾名思义,驱动层能够要知道当前2个网口的网线是否连接正常。
通过一个定时器,每经过若干个时钟周期检测一下网口的硬件连接状态。
xxx_autodetect()具体实现了这一功能。具体见源码,这里不再说明。
3应用层与设备驱动层通信机制
通过http设置Router和Hub模式的切换,通过ioctl来告知kernel层这一变化。Kernel层根据这一变化决定数据包如何转发。
当通过http方式“Enable”路由模式时,协议层通过html_para()获知这一信息。html_para()调用ioctl,ioclt通过SIOICCONFIGROUTER命令字将这一消息告诉kernel层。
对于kernel层来说是通过xxx_do_ioctl()这一函数来实现,该函数包含一个switch语句,用来解析协议层传递过来的命令字,当发现命令字为SIOICCONFIGROUTER时,会把全局变量router_model置1,然后kernel层就开始以Router的方式对网络包经行转发。
skb_put(skb,len)和skb_push(skb,len)的区别
skb_put() 增长数据区的长度来为memcpy准备空间. 许多的网络操作需要加入一些桢头, 这可以使用skb_push来将数据区向后推, 为头留出空间.
请参见下图:
----------------------------------------
| head | data | |
----------------------------------------
skb_put
-----------------------------------------
| head | data | put_data | |
-----------------------------------------
skb_push
------------------------------------------
| head | push_data | data | |
------------------------------------------