Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1731783
  • 博文数量: 782
  • 博客积分: 2455
  • 博客等级: 大尉
  • 技术积分: 4140
  • 用 户 组: 普通用户
  • 注册时间: 2011-04-06 21:37
个人简介

Linux ,c/c++, web,前端,php,js

文章分类

全部博文(782)

文章存档

2015年(8)

2014年(28)

2013年(110)

2012年(307)

2011年(329)

分类: LINUX

2013-07-31 22:04:25

原文地址:madwifi学习记录 作者:boonie

最近madwifi作为ap时,丢包比较严重,正好有人写了学习记录,感谢原创者!

1 madwifi的结构,主要是有三层,hal是硬件层,然后是ath层,在之上的是802.11层,整个madwifi源码中重要的就是hal文件夹(硬件),ath文件夹,ath_rate文件夹(动态调整tx rate的三种算法,默认使用sample),net80211文件夹(802.11协议相关),tools文件夹(一些工具)
2 madwifi中hal是硬件相关的函数信息等,在hal文件夹下提供了需要的头文件,在hal/public下有该版本madwifi可以适应的硬件体系结构,比如用powerpc-be-elf.hal.o.uu,需要用uudecode这个工具来解码他,命令为uudecode powerpc-be-elf.hal.o.uu,如果出现了提示没有‘end’的错误,可能是文件格式问题,用dos2unix把该文件格式转换一下就可以,然后如果出现编译器命令不对,则可能修改powerpc-be-elf.inc文件中的TOOLPREFIX,指向编译器
3 madwifi启动的时候会先扫描硬件网卡,然后如果发现了设备就会注册该设备(ath),其注册的过程中,会检查改网卡的设备id,这个东西有两个:在ath下的if_ath_pci.c下pci_device_id 结构中,有一个静态表,描述了PCI id,第一个0×168c,是指atheros厂商的标志vendor,第二个是网卡的device id,如果扫描到的硬件不在这个表里是无法加载的,其定义可以在hal下的ah_devid.h文件中找到
4 madwifi加载到内核的过程需要先加载模块,有两种方式modprobe命令和insmod命令(看看开发板是不是支持第一个命令),用modeprobe可以直接加载ath_pci.o然后会把相关的模块(根据依赖关系)加载进来,如果不支持该命令则需要用insmod自己手动根据依赖关系来加载各个模块,需要加载的模块及其顺序为:

insmod wlan.o
insmod ath_hal.o
insmod ath_rate_amrr.o
insmod ath_rate_onoe.o
insmod ath_rate_sample.o
insmod wlan_acl.o
insmod wlan_ccmp.o
insmod wlan_scan_ap.o
insmod wlan_scan_sta.o
insmod wlan_tkip.o
insmod wlan_wep.o
insmod wlan_xauth.o
insmod ath_pci.o



5 在识别了网卡后,ath_pci_probe会使内核先初始化该设备,然后检查内核是否支持本设备的DMA寻址位数,建立内存映射等,创建ath_pci_softc结构实例,部分初始化ath_softc中的net_device dev,最后调用ath_attach()函数,该函数会将识别的网卡device id进行处理,比如对比version(就是一个表示时间的串),然后准备激活网卡

1 madwifi发送数据的过程为:首先在初始化的时候ath_attach函数会设置发送数据函数dev->hard_start_xmit = ath_hardstart, 然后在ath_hardstart函数中会有这些设置:
if ((dev->flags & IFF_RUNNING) == 0 || sc->sc_invalid) 检测硬件是否已经启用并且判断sc是否与hal层连接好;
STAILQ_INIT(&bf_head);初始化bf_head结构体
skb->data是上层传过来的数据,可以(struct ether_header *) skb->data
ATH_SUPERG_FF的那个define是关于G模式的一个“快速帧”功能,Atheros的某种技术
skb = ieee80211_encap(ni, skb, &framecnt);为数据包封装802.11帧头
framecnt表示分段的个数,如果是只有一个就单纯发送,如果是多个,要进行一些判断然后为每段分配一个硬件缓冲区,并且发送
在进行了上述过程后调用ath_tx_start函数进行下一步的参数设置
2 总体发送数据过程为:ath_hardstart 、 ath_tx_start 、 ath_tx_txqaddbuf(放入发送队列)、ath_hal_txstart(发送),ath_hal_txstart就是在hal中定义的了,只能找到相应的define
3 madwifi接收数据是通过中断执行,madwifi的中断处理函数为:ath_intr(),用status & HAL_INT_RX表示为数据接收,然后做相应处理
4 总体接收数据过程为:ath_rx_tasklet 、 ieee80211_input 、 ieee80211_deliver_data 、netif_rx(skb)(linux内核提供的函数)

本次madwifi学习的记录主要跟txq相关,结构为struct ath_txq,做个和的连接,以及夭折了的1 ath_txq结构里比较有用的几个字段:qnum是记录在哪个队列,即队列号,depth记录当前队列长度,totalqueued记录总共曾经有多少个包放入过队列,axq_q貌似就是ath_buf的一个链表,会存着真正的数据
2 基本上对于txq的操作是通过ATH_TXQ_XXXXX函数来实现了,除了LOCK和UNLOCK之外,比较有用的是 ATH_TXQ_INSERT_TAIL,和ATH_TXQ_REMOVE_HEAD,他们除了会更新txq那个链表,也会调整depth,链表的最根本定义及使用方法在/madwifi/include/sys/queue.h下
3 在ath_tx_txqaddbuf中调用ath_hal_txstart后不会立即减少depth,depth的减少是在processq中ATH_TXQ_REMOVE_HEAD后导致的。
4 硬件发包具体的发送方式猜测是多个包同时发送出去,或者说是将一个txq->axq_qnum中的包发干净,之后再传送中断,才会有q0123的捕获。主要依据有:
(1)在ath_tx_txqaddbuf中调用ath_hal_txstart之前设置当txq->axq_depth大于10的时候再调用 ath_hal_txstart,否则直接退出该函数,结果没有进入processq的结果输出,只有又有新包调用ath_tx_txqaddbuf,换句话讲没有再出现发送包之后的中断,只有txq->axq_depth的累加,猜测是积累的包没有发完又添加新包,结果该qnum的队列发不干净,没有返回中断
(2)每次ath_tx_tasklet_q0123函数都会在接到中断后调用 ath_tx_processq,在ath_tx_processq中会将该qnum的txq不断的ATH_TXQ_REMOVE_HEAD,depth 不断的减,直到该txq为空,才会退出。
5 在if_athvar.h中定义了TAIL_DROP_COUNT, 默认值为50, 这个数值是在限定txq的长度,如果txq的depth长于这个限定会丢包, 不会发送,ath_hardstart中使用了它
if (txq->axq_depth > TAIL_DROP_COUNT) {
sc->sc_stats.ast_tx_discard++;
goto hardstart_fail;
}
6 在hal/ah.h中定义了qnum的类型,HAL_NUM_TX_QUEUES 为最大允许定义的队列的个数,typedef的HAL_TX_QUEUE定义了各个序号的qnum的职能

typedef enum {
HAL_TX_QUEUE_INACTIVE = 0, /* queue is inactive/unused */
HAL_TX_QUEUE_DATA = 1, /* data xmit q’s */
HAL_TX_QUEUE_BEACON = 2, /* beacon xmit q */
HAL_TX_QUEUE_CAB = 3, /* “crap after beacon” xmit q */
HAL_TX_QUEUE_UAPSD = 4, /* u-apsd power save xmit q */
} HAL_TX_QUEUE;

本次madwifi学习记录主要针对数据收发在内存和外设之间如何实现进行研究
1 在ath_tx_start中,有个函数bus_map_single, 它主要用于将数据由内存拷贝到总线设备的存储空间中,同时返回设备中的地址,应该是真正的物理地址
2 同样在ath_rx_tasklet中,有函数bus_unmap_single, 作用和bus_map_single相反
3 在ath_rx_tasklet中出现bus_unmap_single出现之前,有函数bus_dma_sync_single,作用和bus_unmap_single基本相同,主要定义都是dma_cache_wback_inv
4 关于bus函数的参数中,最后一个参数表明传递方向,在if_ath_ahb.h中有相关定义

#define BUS_DMA_FROMDEVICE 0
#define BUS_DMA_TODEVICE   1


5 关于bus函数的参数说明:总线设备描述符(这个还存在疑问,注释为associated bus device),要传输的数据,数据长度,方向
6 bus_map_single和bus_unmap_single的定义在不同情况下不同,在if_ath.c的开头有这样一段

#ifdef ATH_PCI /* PCI BUS */
#include “if_ath_pci.h”
#endif /* PCI BUS */
#ifdef ATH_AHB /* AHB BUS */
#include “if_ath_ahb.h”
#endif /* AHB BUS */


在 if_ath_pci.h 中定义的bus实际上就是pci_map_single和pci_unmap_single,这两个为linux系统函数,貌似和DMA相关,具体定义可以去在线网站查询
在 if_ath_ahb.h 中定义的bus实际上就是前面说过的dma_cache_wback_inv

static __inline void bus_dma_sync_single(void *hwdev, dma_addr_t dma_handle, size_t size, int direction)
{
unsigned long addr;
addr = (unsigned long) __va(dma_handle);
dma_cache_wback_inv(addr, size);
}

static __inline dma_addr_t bus_map_single(void *hwdev, void *ptr, size_t size, int direction)
{
dma_cache_wback_inv((unsigned long) ptr, size);
return __pa(ptr);
}

static __inline void bus_unmap_single(void *hwdev, dma_addr_t dma_addr, size_t size, int direction)
{
if (direction != BUS_DMA_TODEVICE) {
unsigned long addr;
addr = (unsigned long)__va(dma_addr);
dma_cache_wback_inv(addr, size);
}
}


对比三段定义发现基本都是在用一个函数,关于dma_cache_wback_inv在一个英文论坛里有部分解释:
dma_cache_wback() : This function write back the cache data to memory
dma_cache_inv : This function invalidate the cache tags. so subsequent access will fetch from memory
换句话讲, dma_cache_wback_inv应该是在进行cache和memory的数据交换,It does both write back and invalidate
7 在chinaunix中有一段关于DMA的说法挺好,最后引用过来:
现在的网卡大多数都是采用主动DMA的方式。也就是当网卡从网线上接收到数据之后,就会自己启动dma将数据从网卡内部的FIFO传送到配置寄存器指定的内存地址。当一个数据包接收完成之后,产生一个中断通知driver进行处理。整个过程不需要cpu进行干涉。当driver接收到中断的时候,数据已经在内存里存放好了。

首先做一个更正,对于中出现了一些误解,有点问题。

没有什么把数据放到设备上的过程,数据包一直在内存里,网卡硬件也是去内存里读取这个包,这是不是传说的DMA咱不清楚,但关键是什么时候把这个包的地址告诉硬件,就是ath_hal_puttxbuf函数。通过把描述符的地址告诉硬件,间接地把包的地址告诉硬件。

首先要肯定的是sc只有一个,对应着物理设备。在网卡初始化时,会调用ath_desc_alloc,继而调用ath_descdma_setup,在这里,会为sc->sc_txbuf分配200个buffer,然后为每个buffer分配一个描述符。也就是说网卡初始化之后,就有200个tx buffer和200个描述符,一一对应地存在于内存中了。

然后将每个buffer的bf_daddr指针指向自己对应描述符的:物理地址;每个buffer的bf_desc指针则指向对应描述符的:虚拟地址。发送包时,首先调ath_hardstart函数,在这里,用ATH_HARDSTART_GET_TX_BUF_WITH_LOCK这个宏,取得这200个buffer中的某一个赋给bf这个变量。然后调用ath_tx_start函数,在这里,将数据包的真实物理地址,赋给此bf的bf_skbaddr指针。

然后定义一个ds指针,将这个指针指向此bf对应的描述符,也就是bf_desc,对ds进行一系列操作,也就是把此bf对应的描述符里的有用的内容都填好。然后将ds的ds_data指针指向bf的bf_skbaddr。这样,只要找到描述符,就可以找到数据包的物理地址了。

在ath_tx_start函数里还会根据情况选择一个txq,但是网卡硬件是用不到这个txq结构的,硬件关心的只是这是第几个q,也就是txq->axq_qnum,最后到了ath_tx_txqaddbuf函数,在if (txq->axq_link == NULL)的时候,会调用ath_hal_puttxbuf(ah, txq->axq_qnum, bf->bf_daddr),这里的参数txq->axq_qnum是q号码,bf->bf_daddr呢?就是这个bf对应的描述符的物理地址,这个函数实际上就是把这个描述符物理地址写到一个寄存器里。比如网卡里有四个寄存器,ds_for_q1,ds_for_q2,ds_for_q3,ds_for_q4,作用是储存4个q对应的描述符的物理地址,那么ath_hal_puttxbuf就是把描述符物理地址写到相应寄存器里。

最后调用ath_hal_txstart(ah, txq->axq_qnum)了,参数只有一个q号码,那么接下来,硬件的动作是:
网卡通过这个q号码—–>找到对应的寄存器—–>里面放的是一个描述符的物理地址—–>找到这个描述符—–>这个描述符里有一个指针ds_data——>ds_data就是数据包的物理地址—–>找到数据包—–>发送。

madwifi学习记录的学习记录系列以后应该还会有更新,但是频率嘛,我就不好说了,呵呵。毕业设计差不多快弄完了,但是估计和madwifi的关系不会太早就结束,没准整个研究生阶段都会与它打交道,呵呵。写一点最近的学习心得。

1 关于txq的话题其实有很多,这也是我一直所关心的问题。说过在madwifi中txq本身定义了四个,当然其设计时也就会用到四个,虽然其允许支持到10个。在struct ath_softc中有一个sc_ac2q结构,这个结构和txq是相对应的。WME_NUM_AC定义的个数是4,同样对应四中类型,可以说是与之关联的:

#define WME_AC_BE 0 /* best effort */
#define WME_AC_BK 1 /* background */
#define WME_AC_VI 2 /* video */
#define WME_AC_VO 3 /* voice */

2 madwifi支持多种工作模式,adhoc,ap等等吧,不过最近在用的是ahdemo模式,其实是adhoc模式的一个变种,不会发送管理帧。顺便提下管理帧的定义,在ieee80211.h中,IEEE80211_FC0_TYPE_MASK和IEEE80211_FC0_SUBTYPE_MASK用来判断包的类型,如果是管理帧的话type应该是0,然后对应相应的subtype,比如IEEE80211_FC0_SUBTYPE_BEACON就是0×80, 用法为:

struct ieee80211_frame *wh = (struct ieee80211_frame *)bf->bf_skb->data;
u_int8_t type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK;
u_int8_t subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK;
if (type == IEEE80211_FC0_TYPE_MGT && subtype == IEEE80211_FC0_SUBTYPE_BEACON) {……}

3 关于中断方面。首先,有一种叫做软中断的东西,是Linux的东西,madwifi把它封装了一下,然后给驱动程序用。其实本质是这样的:首先向系统注册该中断,对应一个专属中断队列、处理函数和设备指针,在初始化设置之后,系统中的一个内核线程会周期的遍历中断向量列表,一旦发现某个软中断向量挂起,则会迅速调用其处理函数。这种软中断的使用是非常有意义的,能够很快的进行处理,而又不会死循环,造成无法释放资源。在ath/if_ath.c中,有很多这种软中断,在ath_attach中 ATH_INIT_TQUEUE(&sc->sc_rxtq,    ath_rx_tasklet,        dev);就是一个init,而用ATH_SCHEDULE_TQUEUE就能完成调度的申请。当然因为内核版本问题,madwifi给封装成了这两个“函数”,需要再用个 if(needmark) {mark_bh(IMMEDIATE_BH);}

4 当硬件产生一个中断的时候,中断会提供给ath_intr函数,该函数根据相应的情况传递给相应的处理函数,比如如果是硬件发送了一个数据包,并产生中断的话,就在if (status & HAL_INT_TX) 处理,并传递给processq。如果想让发送的包在硬件发包后产生硬件中断的话,就在ath_tx_start函数中设置flags时设置一个位, flags |= HAL_TXDESC_INTREQ;







阅读(1070) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~