无线网卡收包流程

Posted Li-Yongjun

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了无线网卡收包流程相关的知识,希望对你有一定的参考价值。

环境

x86_64 Linux, AR9462 无线网卡,PCIe 接口
ath9k 驱动

收包过程


① 无线网卡从空气中捕获到无线数据包 【物理层】
② 无线网卡把帧 DMA 到内存的 Ring Buffer
③ 无线网卡向 CPU 发起中断请求
④ CPU 响应中断,执行 ISR,调度 tasklet,退出 ISR
⑤ tasklet 从 Ring Buffer 中取走数据包,保存为一个 skb 【数据链路层】
⑥ 驱动调用 netif_receive_skb() 将包送往内核网络协议栈
⑦ 根据包的类型,协议栈逐级向上送达
⑧ ip_rcv() 【网络层】
⑨ tcp_v4_rcv() 【传输层】
⑩ socket 接收队列
⑪ read() 【应用层】

准备工作

要想完成上面的流程,事先要一些准备工作。
无线网卡能够从空气中捕获到无线数据包的前提,是无线网卡得能正常工作,如何让无线网卡正常工作,那就是

  • 硬件正常(供电、时钟、连线),这部分是硬件工程师的工作,软件工程师不必过多关心
  • 无线网卡配置正常
    • 安装驱动
      • 初始化无线网卡(使能、读写配置空间、注册中断、初始化 DMA)
      • 初始化软件资源(初始化中断下半部(tasklet、workqueue、softirq)、申请 RingBuff)
      • 向内核注册网络设备

其中

  • 使能无线网卡,是为了让无线网卡开始工作
  • 注册中断,是为了无线网卡有数据时可以通知 CPU
  • 初始化 DMA,是为了无线网卡能够直接向内存读写数据,提高吞吐量
  • 初始化中断下半部,是为了让上半部能尽可能快的返回,将剩余任务留给下半部执行。减轻中断对 CPU 响应其它中断的影响,减轻中断对 CPU 调度进程的影响。
  • 申请 RingBuff 主要是给 DMA 使用
  • 向内核注册网络设备,是为了和内核网络协议栈打通

准备工作代码分析

module_init(ath9k_init);
	ath9k_init()
		ath_pci_init()
			pci_register_driver(ath_pci_driver)

static struct pci_driver ath_pci_driver = 
	.name       = "ath9k",
	.id_table   = ath_pci_id_table,
	.probe      = ath_pci_probe,
	.remove     = ath_pci_remove,
	.driver.pm  = ATH9K_PM_OPS,
;

ath_pci_probe
	pcim_enable_device()    // 在驱动程序可以访问 PCI 设备的任何设备资源之前(I/O 区域或者中断),驱动程序必须先调用 pci_enable_device(),该函数会激活设备,把设备唤醒,在某些情况下还指派它的中断线和 I/O 区域
		pci_enable_device() // 追代码,发现其实就是将 Command 域的 bit0 和 bit1 置为 1,从而达到开启设备的目的
			pci_enable_device_flags()//使能设备
			pci_read_config_word(dev, dev->pm_cap + PCI_PM_CTRL, &pmcsr);    // 读取电源状态,Power Management Registers
			pci_upstream_bridge() // 所在的桥
			pci_enable_bridge(bridge); // 使能桥
			do_pci_enable_device()
			如果开启 msi 中断,直接 return,
			否则 pci_read_config_byte(dev, PCI_INTERRUPT_PIN, &pin); //读取中断引脚,并使能中断
			pci_set_dma_mask()
			pci_set_consistent_dma_mask() // 会将掩码赋值给 struct device 结构体中的 coherent_dma_mask,当调用dma_alloc_coherent 申请 DMA 内存时,会检测 DMA mask,从而根据对应的掩码来分配地址
			pci_read_config_byte(pdev, PCI_CACHE_LINE_SIZE, &csz); // 读取缓存行大小
			pci_write_config_byte(pdev, PCI_LATENCY_TIMER, 0xa8);
	pci_set_master(pdev);    // 将该设备设置为主设备
	ath9k_fill_chanctx_ops()
	hw = ieee80211_alloc_hw(sizeof(struct ath_softc), &ath9k_ops); // This must be called once for each hardware device
	request_irq(, ath_isr()) // ath_isr() : interrupt service routine 中断服务程序
		request_threaded_irq()
			__setup_irq() // 注册中断
				enable_irq(irq);    // 使能中断
	ath9k_init_device()
		ath9k_init_softc() // Bring up device
			ah->reg_ops.read = ath9k_ioread32;              
			ah->reg_ops.write = ath9k_iowrite32;
			init_waitqueue_head(&sc->tx_wait);
			ath9k_init_soc_platform()
			ath9k_eeprom_request(sc, pdata->eeprom_name);
			spin_lock_init(&common->cc_lock);spin_lock_init(&sc->sc_serial_rw);
			spin_lock_init(&sc->sc_pm_lock);
			spin_lock_init(&sc->chan_lock);
			mutex_init(&sc->mutex);
			tasklet_init(&sc->intr_tq, ath9k_tasklet, (unsigned long)sc);
			tasklet_init(&sc->bcon_tasklet, ath9k_beacon_tasklet, (unsigned long)sc);
			setup_timer(&sc->sleep_timer, ath_ps_full_sleep, (unsigned long)sc);
			INIT_WORK(&sc->hw_reset_work, ath_reset_work);
			INIT_WORK(&sc->paprd_work, ath_paprd_calibrate);
			INIT_DELAYED_WORK(&sc->hw_pll_work, ath_hw_pll_work);
			ath9k_init_channel_context()
			ath_read_cachesize(common, &csz); // 读取缓存行大小,一般为 64 字节 ,用来和硬件通信
			ath9k_hw_init(ah) // 初始化硬件
				__ath9k_hw_init(ah);
					ath9k_hw_set_reset_reg(ah, ATH9K_RESET_POWER_ON) //reset
						ath9k_hw_set_reset_power_on()
							ath9k_hw_set_reset(ah, ATH9K_RESET_WARM)
						ath9k_hw_init_defaults(ah);//硬件初始化为默认值
						ath9k_hw_attach_ops()
							ar9003_hw_attach_ops(ah);
						ath9k_hw_post_init()
							ath9k_hw_ani_init()//ATH9K_ANI_OFDM,ATH9K_ANI_CCK
								ath9k_ani_restart(ah);     // reset
				get_eeprom(ah, EEP_REG_0)//eeprom
				ath9k_hw_fill_cap_info()//填充硬件能力
				ath9k_hw_init_macaddr()//初始化MAC
				ath9k_hw_init_hang_checks()//hang 检测
			ath9k_init_queues()
				ath9k_hw_beaconq_setup()
					ath9k_hw_setuptxqueue()
						ath9k_hw_set_txq_props()
							ath9k_hw_set_txq_props()
				ath_txq_setup()
			ath9k_cmn_init_channels_rates() // 初始化信道速率
			ath9k_init_p2p()
			ath9k_cmn_init_crypto()
			ath_fill_led_pin()
		ath_tx_init(sc, ATH_TXBUF) /* Setup TX DMA */ ATH_TXBUF = 512
			ath_descdma_setup()//描述符 DMA
		ath_rx_init(sc, ATH_RXBUF);/* Setup RX DMA */
		ath9k_init_txpower_limits()//发射功率限制
		ieee80211_register_hw() // Register with mac80211 注册网络接口,内核实现
			wiphy_register()
				rfkill_register()
			ieee80211_if_add(local, "wlan%d", NET_NAME_ENUM, NULL,  NL80211_IFTYPE_STATION, &params);
				cfg80211_register_netdevice()
					register_netdevice()
		ath9k_init_debug()
		ath_start_rfkill_poll()
	ath9k_hw_name()

几个重点函数

  • pci_enable_device() : 设备使能
  • request_irq() : 注册中断
  • ieee80211_register_hw() : 向内核注册网络设备
  • tasklet_init() : 初始化 tasklet,为中断下半部做好准备

收包流程代码分析

ath_isr()
    tasklet_schedule(&sc->intr_tq); // 调度 tasklet
        ath9k_tasklet()
            case rxmask: // 处理 Rx 方向数据包
                ath_rx_tasklet()
                    bf = ath_edma_get_next_rx_buf(sc, &rs, qtype);
                        ath_edma_get_buffers(sc, qtype, rs, &bf)
                    ath9k_rx_skb_preprocess();
                    ieee80211_rx(hw, skb); // include/net/mac80211.h
                        ieee80211_rx_napi() // net/mac80211/rx.c
                            ieee80211_rx_monitor()
                            netif_receive_skb() // net/core/dev.c,将包送往内核网络协议栈
                                netif_receive_skb_internal()
                                    __netif_receive_skb()
                                        __netif_receive_skb_core()
                                            type = skb->protocol; // ip
                                                deliver_ptype_list_skb()
                                                    deliver_skb()
                                                        pt_prev->func() // 协议对应的回调函数,对于 IP 包来说,就会进入 ip_rcv()
                                                            ip_rcv()
                                                                ip_rcv_finish()
                                                                    ip_route_input_noref()
                                                                        ip_route_input_rcu()
                                                                            ip_route_input_mc()
                                                                                rth->dst.input = ip_mr_input;
                                                                    dst_input()
                                                                        ip_mr_input()
                                                                            ip_local_deliver()
                                                                                ip_local_deliver_finish()
                                                                                    ipprot = rcu_dereference(inet_protos[protocol]); // inet_protos 中保存着 tcp_v4_rcv 和 udp_rcv 的函数地址
                                                                                        tcp_v4_rcv()

几个重点函数

  • ath_isr():硬中断,中断上半部
  • tasklet_schedule():调度 tasklet
  • ath9k_tasklet():中断下半部(中断下半部除了这里使用的 tasklet,还有 softirq 和 workqueue)
  • netif_receive_skb():将包送往内核网络协议栈
  • ip_rcv():到达网络层
  • tcp_v4_rcv():到达传输层

以上是关于无线网卡收包流程的主要内容,如果未能解决你的问题,请参考以下文章

无线网卡收包流程

C 语言网络编程 — 内核协议栈收包/发包流程

推荐几款经典的 Linux 收包引擎

Linux Kernel TCP/IP Stack — 协议栈收包处理流程

Linux Kernel TCP/IP Stack — 协议栈收包处理流程

linux内核网络收包过程—UDP协议处理