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,做个第一次第二次 的连接,以及夭折了的softmac
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接收到中断的时候,数据已经 在内存里存放好了。


madwifi学习记录(5)


首先做一个更正,对于学习记录(4) 中出现了一些误解,有点问题。

没有什么把数据放到设备上的过程,数据包一直在内存里,网卡硬件也是去内存里读取这个包,这是不是传说的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就是数据包的物理地址—–>找到数据包—–>发送。