mac80211子系统学习-rx部分
wifi网卡架构
Linux将wifi芯片分成了full mac和soft mac两种类型,两种类型的区别在于mlme被driver + hardware实现还是被kernel实现,用wireless wiki上的话来讲:
SoftMAC is a term used to describe a type of WNIC where the MLME is expected to be managed in software. mac80211 is a driver API for SoftMAC WNIC, for example.
FullMAC is a term used to describe a type of wireless card where the MLME is managed in hardware. You would not use mac80211 to write a FullMAC wireless driver.
在代码的实现层面,full mac需要自己实现net_device_ops回调函数集合用于数据的发送,需要实现cfg80211_ops回调函数集合用于对网卡进行管理(mlme);而soft mac架构的网卡,其驱动仅需实现数据面的ieee80211_ops回调函数集合即可。
两种不同架构的网卡并无世代前后之分,各家对自己的无线网卡都有自己的理解,瑞昱,联发科,高通等厂商既发布full mac驱动也发布soft mac驱动;
虽然Linux为两种架构的网卡划分了清晰的分界线,实际上大多数驱动会在实现ieee80211_ops的基础上,对cfg80211_ops进行改造,以最大程度的发挥硬件加速的优势;
以高通的无线mac驱动ath11k为例,其在驱动中实现了一部分mlme的工作(在ath11k中被称为UMAC)作为对mac80211的补充。
rx数据通路
对于简单的softmac架构的wifi网卡和驱动来说,接收数据的流程可以按照帧的类型分为两个部分:
-
对于控制帧和管理帧,在mac80211子系统中消化掉;
-
对于数据帧,转化为802.3协议的帧,然后提交给kernel处理;
对于复杂的网卡来说,以高通为例,其将部分mac层转发(Switch),甚至网络层路由功能(router)等对延时要求比较高的部分都放到了Network subsystem中使用NPU(实际上是一个ubicom32 DSP)来做,这种仙人成就过于高深,实在难懂;
一台计算机不可能从一开机就知道要接受空中的哪些数据帧,也不可能在等待接收数据帧的时间段内停机等待,因此Linux的wifi驱动的接收操作由中断触发;
注册中断
接收中断的注册写在与ieee80211_ops回调函数集合中start绑定的方法中;当网卡驱动被加载到kernel中,并使用ifconfig up使能该网卡工作后,start函数被触发,之后开始初始化与wifi设备相关的参数,例如tx、rx的DMA通道,例如此处提到的rx的接收中断;以openwifi的wifi驱动为例:
static int openwifi_start(struct ieee80211_hw *dev)
{
...
priv->irq_rx = irq_of_parse_and_map(priv->pdev->dev.of_node, 1);
ret = request_irq(priv->irq_rx, openwifi_rx_interrupt,
IRQF_SHARED, "sdr,rx_pkt_intr", dev);
if (ret) {
wiphy_err(dev->wiphy, "openwifi_start:failed to register IRQ handler openwifi_rx_interrupt\n");
goto err_free_rings;
} else {
printk("%s openwifi_start: irq_rx %d\n", sdr_compatible_str, priv->irq_rx);
}
...
}
其向Linux的中断系统注册了一个硬终端,中断号为和设备树中的设备结点绑定的1号中断,中断处理函数为openwifi_rx_interrupt
按照对中断的朴素理解,中断处理函数应当被快速执行完成,防止阻塞到下个中断,但显然接收操作并不可能在短时间内被处理完毕;Linux中将这类不能及时完成操作的中断分成中断的上半部和下半部两个部分,下半部用于处理耗时的操作,上半部用于对中断的快速响应;两个部分靠任务队列机制连接,这有点类似于生产者--消费者模型(笑)
这也产生了不可预测的延时,操作系统对于下半部的任务调度是不可知的,解决方案可能只能依靠其他技术。。。
触发中断--上半部
当导线上的电平发生变动后,中断处理函数立刻开始响应执行中断的上半部分,以openwifi的驱动为例,该部分主要是组建sk_buff结构体的data部分和ieee80211_rx_status部分,这两部分的依赖与硬件dma设计和寄存器对应于内存中的地址:
static irqreturn_t openwifi_rx_interrupt(int irq, void *dev_id)
{
...
skb = dev_alloc_skb(len);
if (skb) {
skb_put_data(skb,pdata_tmp+16,len);
...
memcpy(IEEE80211_SKB_RXCB(skb), &rx_status, sizeof(rx_status));
这之后驱动调用mac80211系统中断上半部的剩余部分,向任务队列中添加新的接收处理任务:
ieee80211_rx_irqsafe(dev, skb);
在mac80211系统的ieee80211_rx_irqsafe
中的任务队列并非是计算机中熟知的指令与数据同一存放的冯诺依曼架构,而是数据和任务分离存放的哈佛架构,为追求迅速执行,代码中仅包含两个入队操作,甚至没有多余的判断分支:
void ieee80211_rx_irqsafe(struct ieee80211_hw *hw, struct sk_buff *skb)
{
struct ieee80211_local *local = hw_to_local(hw);
BUILD_BUG_ON(sizeof(struct ieee80211_rx_status) > sizeof(skb->cb));
skb->pkt_type = IEEE80211_RX_MSG;
skb_queue_tail(&local->skb_queue, skb);
tasklet_schedule(&local->tasklet);
}
EXPORT_SYMBOL(ieee80211_rx_irqsafe);
触发中断-下半部
上半部将数据和任务放入到队列中,但此时我们还并不知道放入队列的任务会被哪个处理方法调度。驱动的生命开始于用户手动 insmod ,此时会触发驱动回调函数集合中和probe
绑定的函数,用来进行驱动的初始化工作。
这里就有几个问题:
-
kernel是如何将该驱动和某个设备对应上的?
-
网络设备是如何与自定义的ieee80211_ops绑定的?
-
kernel是如何将该设备识别为网络设备的?
前一个问题和设备树相关,在driver编写时需要将设备树中的结点名称和驱动进行绑定;
后几个问题在probe
方法中进行解答,在openwifi_probe方法中创造了一个与自定义回调函数集合绑定的ieee80211_hw结构体:
static int openwifi_dev_probe(struct platform_device *pdev)
{
struct ieee80211_hw *dev;
...
dev = ieee80211_alloc_hw(sizeof(*priv), &openwifi_ops);
之后又将该hw注册到了内核当中:
err = ieee80211_register_hw(dev);
在创建hw时,经过一条调用链,可以定位到如下的对任务队列的初始化方法:
struct ieee80211_hw *ieee80211_alloc_hw_nm(size_t priv_data_len,
const struct ieee80211_ops *ops,
const char *requested_name)
{
tasklet_init(&local->tasklet,
ieee80211_tasklet_handler,
(unsigned long) local);
当local->tasklet被调度时,Kernel会自动的触发ieee80211_tasklet_handler函数,并利用传入的参数local进行对skb队列的操作:
static void ieee80211_tasklet_handler(unsigned long data)
{
struct ieee80211_local *local = (struct ieee80211_local *) data;
struct sk_buff *skb;
while ((skb = skb_dequeue(&local->skb_queue)) ||
(skb = skb_dequeue(&local->skb_queue_unreliable))) {
switch (skb->pkt_type) {
case IEEE80211_RX_MSG:
/* Clear skb->pkt_type in order to not confuse kernel
* netstack. */
skb->pkt_type = 0;
ieee80211_rx(&local->hw, skb);
break;
...
接下来就是rx操作的主要部分,ieee80211_rx(&local->hw, skb),参考ath9k驱动的函数调用即可
标签:struct,hw,rx,ieee80211,mac80211,skb,local,子系统 From: https://www.cnblogs.com/polariszg/p/18182083https://github.com/WeitaoZhu/wi-fi_books/blob/master/driver-mac80211_intro.pdf