网络设备与媒介层
/* 寄存器定义 */
#define DATA_REG 0x0004
#define CMD_REG 0x0008
/* 寄存器读写函数*/
static u16 xxx_readword(u32 base_addr,int portno)
{
/* 读寄存器的值并返回 */
}
static void xxx_writeword(u32 base_addr,int portno,u16 value)
{
/* 向寄存器写入数据 */
}
网络设备驱动加载函数模板
int xxx_init_module(void)
{
...
/*分配net_device结构并对其成员赋值*/
xxx_dev = alloc_netdev(sizeof(struct xxx_priv),"sn%d",xxx_init);
if(xxx_dev == NULL)
/*分配net_device失败*/
/*注册net_device*/
if((result = register_netdev(xxx_dev)))
...
}
void xxx_cleanup(void)
{
...
/*注销net_device*/
unregister_netdev(xxx_dev);
/*释放net_device*/
free_netdev(xxx_dev);
}
网络设备驱动的初始化函数模板
void xxx_init(struct net_device *dev)
{
/*设备的私有信息结构体*/
struct xxx_priv *priv;
/*检查设备是否存在和设备所使用的硬件资源*/
xxx_hw_init();
/*初始化以太网设备公用成员*/
ether_setup(dev);
/*设置设备成员的函数指针*/
dev->open = xxx_open;
dev->stop = xxx_release;
dev->set_config = xxx_config;
dev->hard_start_xmit = xxx_tx;
dev->do_ioctl = xxx_ioctl;
dev->get_stats = xxx_stats;
dev->change_mtu = xxx_change_mtu;
dev->rebuild_header = xxx_rebuild_header;
dev->hard_header = xxx_header;
dev->tx_timeout = xxx_tx_timeout;
dev_>watchdog_timeo = timeout;
/*取得私有指针并初始化他 */
priv = netdev_priv(dev);
.../*初始化设备私有区数据*/
}
探测xxx网络设备是否存在。方法类似于数学的反证法
探测设备的的具体硬件配置。对于同类的设备使用统一的驱动,需在初始化时探测设备的具体型号。另外即使是同一设备在硬件上的具体配置也可能不一样,我们也可以探测设备所使用的硬件资源
申请设备所用的硬件资源,如用request_region()函数进行IO端口的申请,该过程可在xxx_open函数中完成
网络设备的打开函数,需完成如下工作
使能设备使用的硬件资源,申请IO区域、中断和DMA通道等
使用内核提供的netif_start_queue()激活发送哦队列
网络设备的关闭函数,需完成如下工作
使用内核提供的netif_stop_queue(),停止设备传输包
释放所使用的IO区域,中断和DMA资源
网络设备打开和释放函数模板
int xxx_open(struct net_device *dev)
{
/*申请端口、IRQ等*/
ret = request_irq(dev->irq,&xxx_interrupt,0,dev->name,dev);
...
netif_start_queue(dev);
...
ether_setup(dev);
}
void xxx_release(struct net_device *dev)
{
/*释放端口、IRQ等*/
free_irq(dev->irq,dev);
...
netif_stop_queue(dev);
...
}
数据发送流程 :网络设备驱动完成数据包发送流程如下
网络设备驱动程序从上层协议传递协议传递过来的sk_buff参数获得数据包的有效 数据和长度,将有效数据放入到临时缓冲区
对于以特网,若有效数据的长度小于以太网冲突区检测所要求的数据帧的最小长度ETH_ZLEN,则给临时缓冲区的末尾写0
设置硬的寄存器,驱动网络设备进行数据发送操作。
网络设备驱动程序的数据包发送函数模板
int xxx_tx(struct sk_buff *skb,struct net_device *dev)
{
int len;
char *data,shorttpkt[ETH_ZLEN];
if(xxx_send_avaiable(...)){/*发送队列未满可以发送*/
/*获取有效数据指针和长度*/
data = skb->data;
len = skb_len;
if(len < ETH_ZLEN){
/*若帧小于以太网帧最小长度,补0*/
memset(shortpkt,0,ETH_ZLEN);
memcpy(shortpkt,skb->data,skb->len);
len = ETH_ZLEN;
data = shortpkt;
}
dev->trans_start = jiffies;/*记录发送时间戳*/
/*设置硬件寄存器让硬件把数据包发送出去*/
xxx_hw_tx(data,len,dev);
...
}else{
netif_stop_queue(dev);
...
}
}
发送队列为满或因其他原因来不及发送当前上层传下来的包,则调用23行此函数阻止上层继续向网络设备驱动传送数据包。当忙于发送的数据包被发送完成后,TX结束的中断处理中,应该调用netif_wake_queue()唤醒被阻塞的上层,以启动它继续向网络设备发送数据。
当数据传输超时时,意味着当前的发送操作失败或硬件陷入未知状态,此时数据包发送超时函数xxx_tx_timeout()将被调用
网络设备驱动程序的数据包发送超时函数模板
void xxx_tx_timeout(struct net_device *dev)
{
...
netif_wake_queue(dev);/*重新启动发送队列*/
}
数据接收流程,网络设备接收数据的主要方法是由中断引发设备的中断处理函数,中断处理函数判断中断类型,若为接收中断则读取收到的数据,分配sk_buff数据结构和数据缓冲区,将接收到的数据复制到数据缓冲区,并调用netif_rx()函数将sk_buff传递给上层协议
网络设备驱动中断处理函数模板
static void xxx_interrupt(int irq,void *dev_id)
{
...
switch(status & ISQ_EVENT_MASK){
/*获取数据包*/
xxx_rx(dev);
break;
/*其他类型的中断*/
}
}
static void xxx_rx(struct xxx_device *dev)
{
...
length = get_rev_len(...);
/*分配新的套接字缓冲区*/
skb = dev_alloc_skb(length + 2);
skb_reserve(skb,2);/*对齐*/
skb->dev = dev;
/*读取硬件上接收到的数据*/
insw(ioaddr + RX_FRAME_PORT,skb_put(skb,length),length >> 1);
if(length & 1)
skb->data[length - 1] = inw(ioaddr + RX_FRAME_PORT);
/*获取上层协议类型*/
skb->protocol = eth_type_trans(skb,dev);
/*把数据包交给上层*/
netif_rx(skb);
/*记录接收时间戳*/
dev->last_rx = jiffies;
...
}
接收数据顺序判断中断类型
从硬件中读取数据包有效长度
分配sk_buff
读取硬件上接收到的数据并将数据放入到skb_buff
调用net_if()发将数据包传递给上层协议
通常情况下,网络设备驱动以中断方式接收数据包,而poll_controller()则采用中断轮训方式,另外一种接收数据方式是NAPI(New API)接收流程如下:接收中断来临,关闭接收中断,以轮训方式接收所有数据包直到收空,开启接收中断,接收中断来临。
如果收NAPI兼容的设备驱动,则可以通过poll方式接收数据包。这种情况下,需为该设备驱动提供作为netif_napi_add()参数的xxx_poll()函数,模板如下所示
static int xxx_poll(struct napi_struct *napi,int budget)
{
int npackets = 0;
struct sk_buff *skb;
struct xxx_priv *priv = container_of(napi,struct xxx_priv,napi);
struct xxx_packet *pkt;
while(npackets < budget && priv->rx_queue){
/* 从队列中取出数据包*/
pkt = xxx_dequeue_buf(dev);
/*接下来的处理和中断触发的数据包一致*/
skb = dev_alloc_skb(pkt->datalen + 2);
...
skb_reserve(skb,2);
memcpy(skb_put(skb,pkt->datalen),pkt->data,pkt->datalen);
skb->dev = dev;
skb->protocol = eth_type_trans(skb,dev);
/*调用netif_receive_skb将数据包交给上层协议*/
netif_receive_skb(skb);
/*更改统计数据*/
priv->stats.rx_packets++;
priv->stats.rx_bytes += pkt->datalen;
xxx_release_buffer(pkt);
npackets++;
}
if(npackets <budget){
napi_complete(napi);
xxx_enable_rx_int(...)/*再次使能网络设备的接收中断*/
}
return npackets;
}
上述代码中budget是在初始化阶段分配给接口的weight值,poll函数每次只能接受最多budget数量的数据包。第八行的while循环读取设备的接收缓冲区,读取数据包并提交给上层。
虽然NPAI兼容的设备驱动以poll方式接收数据包,但是仍然需要首次数据包接收中断来触发poll过程。与数据包的终端接收方式不同的是,以轮训方式接收数据包时,当第一次中断发生后,中断处理程序要禁止设备的数据包接收中断并调度NAPI
static void xxx_poll_interrupt(int irq,void *dev_id)
{
switch(status & ISQ_EVENT_MASK){
case ISQ_RECEIVER_EVENT:
.../*获取数据包*/
xxx_disable_rx_int(...)/*禁止接收中断*/
napi_schedule(&priv->napi);
break;
... /*其他类型的中断*/
}
}
napi_schedule()函数被轮训方式驱动的中断程序调用,将设备的poll方法添加到网络层的poll处理队列中,排队并且准备接收数据包,最终触发一个NET_RX_SOFTIRQ软中断,通知网络层接收数据包。
网络连接状态
网络设备驱动可通过netif_carrier_on和netif_carrier函数改变设备的连接状态,若驱动检测到设备状态发生变化,也应该以netif_carrier_on和netif_carrier函数显示的通知内核
void netif_carrier_on(struct net_device *dev);
void netif_carrier_off(struct net_device *dev);
/*向调用者返回链路上的载波信号是否存在*/
void netif_carrier_ok(struct net_device *dev);
网络设备驱动程序中可采用一定手段检测和报告链路连接器状态,例如设置一个定时器来对链路状态进行周期性检测。当定时器到时后,在定时器处理函数中读取物理设备的相关寄存器获得载波状态,从而更新设备连接器的状态,如以下代码清单
static void xxx_timer(unsigned long data)
{
struct net_device *dev = (struct net_device *data);
u16 link;
...
if(!(dev->flags & IFF_UP))
goto set_timer;
/*获取物理上的连接状态*/
if(link == xxx_chk_link(dev)){
if(!(dev->flags & IFF_RUNNING)){
netif_carrier_on(dev);
dev->flags |= IFF_RUNNING;
printk(KERN_DEBUG "%s:link up\n",dev->name);
}
}else{
if(dev->flags & IFF_RUNNING){
netif_carrier_off(dev);
dev->flags &= ~IFF_RUNNING;
printk(KERN_DEBUG "%s:link down\n",dev->name);
}
}
set_timer:
priv->timer.expries = jiffies +1*HZ;
priv->timer.data = (unsigned long)dev;
priv->timer.function = &xxx_timer; //timer handler
add_timer(&priv->timer);
}
9行xxx_chk_link用于读取网络适配器硬件相关寄存器以获得相关链路的连接状态,具体实现状态由硬件决定。当链路连接上时netif_carrier_on显式的通知内黑链路正常,反之netif_carrier_off通知内核链路失去连接。
上述代码中,定时器处理函数不停的利用set_timer代码启动新的定时器以实现周期检测的目的。最初启动定时器适合在设备的打开函数中实现
static int xxx_open(struct net_device *dev)
{
struct xxx_priv *priv = (struct xxx_priv*)dev->priv;
...
priv->timer.expries = jiffies +3*HZ;
priv->timer.data = (unsigned long)dev;
priv->timer.function = &xxx_timer; //timer handler
add_timer(&priv->timer);
...
}
参数的设置和统计数据
用户调用ioctl,并指定SIOCSIFHWADDR命令时,意味着要设置这个设备的MAC地址,设置MAC地址模板
static int set_mac_address(struct net_device *dev,void *addr)
{
if(netif_running(dev)) /*设备忙*/
return -EBUSY;
/*设置以太网MAC地址*/
xxx_set_mac(dev,addr);
return 0;
}
//当然要实现上述函数,需硬件支持,但是实际上很多硬件不支持
//#define netif_running(dev) (dev->flags & IFF_UP)
用户调用ioctl,并指定SIOCSIFMAP(控制台执行ifconfig就会引发这一调用)命令时,系统会调用驱动程序的set_config,系统会向set_config函数传递一个ifmap结构体,包含用户欲设置的设备要使用的I/O地址、中断等信息,实际上大多数设备并不宜包含set_config
网络设备驱动的set_config函数模板
int xxx_config(struct net_device *dev,struct ifmap *map)
{
if(netif_running(dev)); /*不能设置一个正在运行状态的设备*/
return -EBUSY;
/*假设不允许改变IO地址*/
if(map->base_addr != dev->base_addr){
printk(KERN_WARNING "xxx:can't change io address\n");
return -EOPNOTSUPP;
}
/*假设允许改变IRQ*/
if(map->irq != dev->irq)
dev->irq = map->irq;
return 0;
}