网络设备的概念
- 网络设备是完成用户数据包在网络媒介上发送和接收的设备,它将上层协议传递下来的数据包以特定的媒介访问控制方式进行发送,并将收到的数据包传递到上层协议。
- linux系统对网络设备驱动定义了4个层次,这4个层次分别为:网络协议接口层、网路设备接口层、提供实际功能的设备驱动功能层和网路设备媒介层。
linux网络设备驱动结构
- 网络协议接口层向网络层协议提供统一的数据包收发接口,不论上层协议是ARP,还是IP,都通过dev_queue_xmit()函数发送数据,并通过netif_rx()函数接收数据。这一层的存在使得上层协议独立于具体的设备。
- 网络设备接口层向协议接口层提供统一的用于描述具体网络设备属性和操作的结构体net_device,该结构体是设备驱动功能层中各函数的容器。实际上,网络设备接口层从宏观上规划了具体操作硬件的设备驱动功能层的结构。
- 设备驱动功能层的各函数是网络设备接口层net_device数据结构的具体成员,是驱使网络设备硬件完成相应动作的程序,它通过hard_start_xmit()函数启动发送操作,并通过网络设备上的中断触发接收操作。
- 网络设备与媒介层是完成数据包发送和接收的物理实体,包括网络适配器和具体的传输媒介,网络适配器被设备驱动功能层中的函数在物理上驱动。对于Linux系统而言,网络设备和媒介都可以是虚拟的。示意图 如下所示:
- 在设计具体的网络设备驱动程序时,我们需要完成的主要工作是编写设备驱动功能层的相关函数以填充net_device数据结构的内容并将net_device注册入内核。
网络协议接口层
- 上层协议对网络数据包的收发使用接口为网络协议层的接口,收发函数如下:
int dev_queue_xmit(struct sk_buff *skb); // 发送数据
int netif_rx(struct sk_buff *skb); // 接收数据
- 上面的sk_buff结构体很重要,是网络子系统数据传递的“中枢神经”,我们看下其主要的成员示意图:
- 尤其值得注意的是head和end指向缓冲区的头部和尾部,而data和tail指向实际数据的头部和尾部。每一层会在head和data之间填充协议头,或者在tail和end之间添加新的协议数据。
- 分配套接字缓冲区:
struct sk_buff *alloc_skb(unsigned int len, gfp_t priority);
struct sk_buff *dev_alloc_skb(unsigned int len); // 原子优先级申请
- 释放套接字缓冲区:
void kfree_skb(struct sk_buff *skb);
void dev_kfree_skb(struct sk_buff *skb);
void dev_kfree_skb_irq(struct sk_buff *skb);
void dev_kfree_skb_any(struct sk_buff *skb);
- 在缓冲区中增加数据
// 在缓冲区尾部增加数据
unsigned char *skb_put(struct sk_buff *skb, unsigned int len);
// 在缓冲区开头增加数据
unsigned char *skb_push(struct sk_buff *skb, unsigned int len);
// 对于一个空的缓冲区而言,调用如下函数可以调整缓冲区头部
static inline void skb_reserve(struct sk_buff *skb, int len);
网络设备注册与注销
- 函数原型
int register_netdev(struct net_device *dev);
void unregister_netdev(struct net_device *dev);
- 例程
static int xxx_register(void)
{
...
/* 分配 net_device 结构体并对其成员赋值 */
xxx_dev = alloc_netdev(sizeof(struct xxx_priv), "sn%d", xxx_init);6 if (xxx_dev == NULL)
... /* 分配 net_device 失败 */
/* 注册 net_device 结构体 */
if ((result = register_netdev(xxx_dev)))
...
}
static void xxx_unregister(void)
{
...
/* 注销 net_device 结构体 */
unregister_netdev(xxx_dev);
/* 释放 net_device 结构体 */
free_netdev(xxx_dev);
}
网络设备初始化
-
进行硬件上的准备工作,检查网络设备是否存在,如果存在,则检测设备所使用的硬件资源。
-
进行软件接口上的准备工作,分配net_device结构体并对其数据和函数指针成员赋值。
-
获得设备的私有信息指针并初始化各成员的值。如果私有信息中包括自旋锁或信号量等并发或同步机制,则需对其进行初始化。
-
网络设备初始化模板:
void xxx_init(struct net_device *dev)
{
/* 设备的私有信息结构体 */
struct xxx_priv *priv;
/* 检查设备是否存在和设备所使用的硬件资源 */
xxx_hw_init();
/* 初始化以太网设备的公用成员 */
ether_setup(dev);
/* 设置设备的成员函数指针 */
ndev->netdev_ops = &xxx_netdev_ops;
ndev->ethtool_ops = &xxx_ethtool_ops;
dev->watchdog_timeo = timeout;
/* 取得私有信息,并初始化它 */
priv = netdev_priv(dev);
... /* 初始化设备私有数据区 */
}
网络设备的打开和释放
打开函数需要完成如下工作:
- 使能设备使用的硬件资源,申请I/O区域、中断和DMA通道等。
- 调用Linux内核提供的netif_start_queue()函数,激活设备发送队列。
网络设备的关闭函数需要完成如下工作。
- 调用Linux内核提供的netif_stop_queue()函数,停止设备传输包。
- 释放设备所使用的I/O区域、中断和DMA资源。
打开和释放函数模板
static int xxx_open(struct net_device *dev)
{
/* 申请端口、 IRQ 等,类似于 fops->open */
ret = request_irq(dev->irq, &xxx_interrupt, 0, dev->name, dev);
...
netif_start_queue(dev);
...
}
static int xxx_release(struct net_device *dev)
{
/* 释放端口、 IRQ 等,类似于 fops->close */
free_irq(dev->irq, dev);
...
netif_stop_queue(dev);
/* can't transmit any more */
...
}
数据发送流程
-
网络设备驱动程序从上层协议传递过来的sk_buff参数获得数据包的有效数据和长度,将有效数据放入临时缓冲区。
-
对于以太网,如果有效数据的长度小于以太网冲突检测所要求数据帧的最小长度ETH_ZLEN,则给临时缓冲区的末尾填充0。
-
设置硬件的寄存器,驱使网络设备进行数据发送操作。
-
驱动程序的数据包发送函数模板
int xxx_tx(struct sk_buff *skb, struct net_device *dev)
{
int len;
char *data, shortpkt[ETH_ZLEN];
if (xxx_send_available(...)) { /* 发送队列未满,可以发送 */
/* 获得有效数据指针和长度 */
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; /* 记录发送时间戳 */
if (avail) {/* 设置硬件寄存器,让硬件把数据包发送出去 */
xxx_hw_tx(data, len, dev);
} else {
netif_stop_queue(dev);
...
}
}
数据接收流程
- 网络设备接收数据的主要方法是由中断引发设备的中断处理函数,中断处理函数判断中断类型,如果为接收中断,则读取接收到的数据,分配sk_buffer数据结构和数据缓冲区,将接收到的数据复制到数据缓冲区,并调用netif_rx()函数将sk_buffer传递给上层协议。
- 驱动的中断处理函数模板:
static void xxx_interrupt(int irq, void *dev_id)
{
...
switch (status &ISQ_EVENT_MASK) {
case ISQ_RECEIVER_EVENT:
/* 获取数据包 */
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;
...
}
网络连接状态
- 网络设备驱动可以通过netif_carrier_on()和netif_carrier_off()函数改变设备的连接状态,如果驱动检测到连接状态发生变化,也应该以netif_carrier_on()和netif_carrier_off()函数显式地通知内核。除了netif_carrier_on()和netif_carrier_off()函数以外,另一个函数netif_carrier_ok()可用于向调用者返回链路上的载波信号是否存在。
- 这几个函数都接收一个net_device设备结构体指针作为参数,原型分别为:
void netif_carrier_on(struct net_device *dev);
void netif_carrier_off(struct net_device *dev);
int netif_carrier_ok(struct net_device *dev);
- 驱动用定时器周期性检查链路状态
static void xxx_timer(unsigned long data)
{
struct net_device *dev = (struct net_device*)data;
u16link;
...
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.expires = jiffies + 1* Hz;
priv->timer.data = (unsigned long)dev;
priv->timer.function = &xxx_timer; /* timer handler */
add_timer(&priv->timer);
}
- 驱动的打开函数中初始化定时器
static int xxx_open(struct net_device *dev)
{
struct xxx_priv *priv = netdev_priv(dev);
...
priv->timer.expires = jiffies + 3* Hz;
priv->timer.data = (unsigned long)dev;
priv->timer.function = &xxx_timer; /* 定时器处理函数 */
add_timer(&priv->timer);
...
}
参数设置和统计数据
- 设置网络设备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;
}
- 驱动的set_config函数模板
static int xxx_config(struct net_device *dev, struct ifmap *map)
{
if (netif_running(dev))
/* 不能设置一个正在运行状态的设备 */
return - EBUSY;
/* 假设不允许改变 I/O 地址 */
if (map->base_addr != dev->base_addr) {
printk(KERN_WARNING "xxx: Can't change I/O address\n");
return - EOPNOTSUPP;
}
/* 假设允许改变 IRQ */
if (map->irq != dev->irq)14
dev->irq = map->irq;
return 0;
}