无线网卡驱动可以借助PCI,SDIO,UART,USB来用于总线通讯。MAC层可以用软件来实现,也可以用硬件来实现。一般有mac和phy组成。构图:
mac:一般用于数据的过滤
phy: 操作数据实际的收发。
wifi架构图
以USB接口的WIFI模块进行分析:
1、从USB总线的角度去看,它是USB设备
2、从Linux设备的分类上看,它又是网络设备
3、从WIFI本身的角度去看,它又有自己独特的功能及属性,因此它又是一个私有的设备
只要抓住这三条线索深入去分析驱动,整个WIFI驱动框架就会浮现在你眼前。
一、总线设备驱动层
首先,现在我们先从USB设备开始,要写一个USB设备驱动,那么大致步骤如下:
1、需要针对该设备定义一个USB驱动,对应到代码中即定义一个usb_driver结构体变量。
2、填充该设备的usb_driver结构体成员变量
3、将该驱动注册到USB子系统
二、网络设备驱动层
其次,接下来是网络设备的角度要完成的事。
1、网络协议接口层向网络层协议提供统一的数据包收发接口,不论上层协议为ARP 还是 IP,都通过 dev_queue_xmit()函数发送数据,并通过 netif_rx()函数接收数据。这一层的存在使得上层协议独立于具体的设备。
2、网络设备接口层向协议接口层提供统一的用于描述具体网络设备属性和操作的结构体 net_device,该结构体是设备驱动功能层中各函数的容器。实际上,网络设备接口层从宏观上规划了具体操作硬件的设备驱动功能层的结构。
3、设备驱动功能层各函数是网络设备接口层 net_device 数据结构的具体成员,是驱使网络设备硬件完成相应动作的程序,它通过 hard_start_xmit()函数启动发送操作,并通过网络设备上的中断触发接收操作。
4、网络设备与媒介层是完成数据包发送和接收的物理实体,包括网络适配器和具体的传输媒介,网络适配器被设备驱动功能层中的函数物理上驱动。对于Linux 系统而言,网络设备和媒介都可以是虚拟的。
下面对这四层做一个较为详细解释:
1. 网络接口层
网络协议接口层最主要的功能是给上层协议提供了透明的数据包发送和接收接口。当上层 ARP 或 IP 协 议需要发送数据 包时,它将调用网络协议 接口层的dev_queue_xmit()函数发送该数据包,同时需传递给该函数一个指向 struct sk_buff 数据结构的指针。dev_queue_xmit()函数的原型为:
dev_queue_xmit (struct sk_buff * skb );
同样地,上层对数据包的接收也通过向 netif_rx()函数传递一个 struct sk_buff 数据结构的指针来完成。netif_rx()函数的原型为:
int netif_rx(struct sk_buff *skb);
sk_buff 结构体非常重要,它的含义为“套接字缓冲区”,用于在 Linux 网络子系统中的各层之间传递数据,是 Linux 网络子系统数据传递的“中枢神经”。当发送数据包时,Linux 内核的网络处理模块必须建立一个包含要传输的数据包的 sk_buff,然后将 sk_buff 递交给下层,各层在 sk_buff 中添加不同的协议头直至交给网络设备发送。同样地,当网络设备从网络媒介上接收到数据包后,它必须将接收到的数据转换为 sk_buff 数据结构并传递给上层,各层剥去相应的协议头直至交给用户。
其针对sk_buff的成员变量,操作(分配,释放,指针移动)等是这一层的核心内容。这一层是属于协议层通用的。驱动代码不需要改动。
2. 设备接口层
设备接口层的主要功能是为千变万化的网络设备定义了统一、抽象的数据结构 net_device 结构体,以不变应万变,实现多种硬件在软件层次上的统一。
net_device 结构体在内核中指代一个网络设备,网络设备驱动程序只需通过填充net_device 的具体成员并注册 net_device 即可实现硬件操作函数与内核的挂接。
net_device 本身是一个巨型结构体,包含网络设备的属性描述和操作接口。当我们编写网络设备驱动程序时,只需要了解其中的一部分。比如网卡设备名,硬件信息,接口信息(MTU等),设备操作函数(ioctl,open,stop,poll等)。
3. 设备驱动功能层
net_device 结构体的成员(属性和函数指针)需要被设备驱动功能层的具体数值和函数赋予。对于具体的设备,工程师应该编写设备驱动功能层的函数,这些函数形如 xx_open()、xxx_stop()、xxx_get_stats()、xxx_tx_timeout()、xxx_poll()等。
由于网络数据包的接收可由中断引发,设备驱动功能层中另一个主体部分将是中断处理函数,它负责读取硬件上接收的数据包并传送给上层协议,可能包含xxx_interrupt()和 xxx_rx()函数,前者完成中断类型判断等基本的工作,后者则需完成数据包的生成和递交上层等复杂工作。
对于特定的设备,我们还可以定义其相关私有数据和操作,并封装为一个私有信息结构体xxx_private,让其指针被赋值给net_device 的priv成员。xxx_private结构体中可包含设备特殊的属性和操作、自旋锁与信号量、定时器以及统计信息等,由开发者自定义。
4. 网络设备和媒介层
网络设备与媒介层直接对应于实际的硬件设备。为了给设备的物理配置和寄存器操作一个更一般的描述,我们可以定义一组宏和一组访问设备内部寄存器的函数,具体的宏和函数与特定的硬件紧密相关。主要是配置底层硬件,寄存器的定义和寄存器的读写函数。
从网卡驱动实现的主要功能的角度来讲,wifi驱动还需要以下的代码实现:
网络设备驱动的注册与注销:这个是网络设备通用的驱动框架。
网络设备的打开与释放:
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);
...
}
int xxx_release(struct net_device *dev)
{
/* 释放端口、IRQ 等,类似于 fops->close */
free_irq(dev->irq, dev);
...
netif_stop_queue(dev); /* can't transmit any more */
...
}
数据发送流程:
int xxx_tx(struct sk_buff *skb, struct net_device *dev)
{
int len;
char *data, shortpkt[ETH_ZLEN];
/* 获得有效数据指针和长度 */
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);
...
}
当数据传输超时时,意味着当前的发送操作失败,此时,数据包发送超时处理函数 xxx_tx_ timeout()将被调用。这个函数需要调用 Linux 内核提供的 netif_wake_queue()函数重新启动设备发送队列。
void xxx_tx_timeout(struct net_device *dev)
{
...
netif_wake_queue(dev); /* 重新启动设备发送队列 */
}
数据接收流程:
网络设备接收数据的主要方法是由中断引发设备的中断处理函数,中断处理函数判断中断类型,如果为接收中断,则读取接收到的数据,分配 sk_buffer 数据结构和数据缓冲区,将接收到的数据复制到数据缓冲区,并调用 netif_rx()函数将 sk_buffer 传递给上层协议。
比较简单的处理方式是中断方式:
static void xxx_interrupt(int irq, void *dev_id, struct pt_regs
*regs)
{
...
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;
...
}
复杂一些的是poll的接收方式:
static int xxx_poll(struct net_device *dev, int *budget)
{
int npackets = 0, quota = min(dev->quota, *budget);
struct sk_buff *skb;
struct xxx_priv *priv = netdev_priv(dev);
struct xxx_packet *pkt;
while (npackets < quota && priv->rx_queue)
{
/*从队列中取出数据包*/
pkt = xxx_dequeue_buf(dev);
/*接下来的处理,和中断触发的数据包接收一致*/
skb = dev_alloc_skb(pkt->datalen + 2);
if (!skb)
{
...
continue;
}
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 而不是 net_rx 将数据包交给上层协议*/
netif_receive_skb(skb);
/*更改统计数据 */
priv->stats.rx_packets++;
priv->stats.rx_bytes += pkt->datalen;
xxx_release_buffer(pkt);
}
/* 处理完所有数据包*/
*budget -= npackets;
dev->quota -= npackets;
if (!priv->rx_queue)
{
netif_rx_complete(dev);
xxx_enable_rx_int (...); /* 再次使能网络设备的接收中断 */
return 0;
}
return 1;
}
虽然 NAPI 兼容的设备驱动以 poll 方式接收数据包,但是仍然需要首次数据包接收中断来触发 poll 过程。与数据包的中断接收方式不同的是,以轮询方式接收数据包时,当第一次中断发生后,中断处理程序要禁止设备的数据包接收中断,如下:
static void xxx_poll_interrupt(int irq, void *dev_id, struct
pt_regs *regs)
{
switch (status &ISQ_EVENT_MASK)
{
case ISQ_RECEIVER_EVENT:
... /* 获取数据包 */
xxx_disable_rx_int(...); /* 禁止接收中断 */
netif_rx_schedule(dev);
break;
... /* 其他类型的中断 */
}
}
还剩下一些网络连接状态,参数设置和统计数据的函数需要实现,这部分不再多做说明。
WIFI设备本身私有的功能及属性,如自身的配置及初始化、建立与用户空间的交互接口、自身功能的实现等。
1、自身的配置及初始化,比如xxx_read_chip_info(); xxx_chip_configure(); xxx_hal_init();
2、主要是在proc和sys文件系统上建立与用户空间的交互接口,比如xxx_drv_proc_init();xxx_ndev_notifier_register();
3、wifi自身的一些功能实现,比如扫描接入和功耗,电源管理相关的实现。