无线网卡收包流程
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, ¶ms);
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():到达传输层
以上是关于无线网卡收包流程的主要内容,如果未能解决你的问题,请参考以下文章
Linux Kernel TCP/IP Stack — 协议栈收包处理流程