高通: Ethernet mac
kernel: 3.18
(没有soc的mac的介绍,纯从代码角度试着分析该发送过程,从dma的设置以及其他寄存器的设置的角度)
因实在未找到任何有关分析mac发送数据的过程的文章,(大家都知道是用dma来传数据,具体怎么做的,没有找到相关介绍过的文章)最近又正好在查mac的发送问题,便记录下该过程。
首先看看相关数据结构的定义:
从666行以及670行可知,发送队列中需要用到两个重要的寄存器,生产者寄存器produce_reg和消费者寄存器consume_reg;662行是该mac特有的数据成员tpd;
从623行可知该数据结构名称为传输包描述符(tpd)环,
tpbuff 存储skb和dma的buffer
tpdesc dma映射得到的虚拟地址
tpdma dma映射得到的物理地址
size 总的tpbuff的长度(sizeof(struct emac_buffer) * count)
count 该环的tpbuff的数量
produce_idx 生产者的下标
consume_idx 消费者的下标
last_produce_idx 上一次的生产者的下标
从555行到556行的注释可知 emac_ring_header表示为三个描述符环(tpd, rfd, rrd)映射的单个连续的DMA空间块;
desc dma分配的虚拟地址
dma dma分配的物理地址
size dma分配的内存总大小
used 当面已使用的大小
接下来看看这些数据结构的初始化:
从1601行可看到ring_header的desc及dma的初始化是由通过调用dma_alloc_coherent来完成的,其size在1589行初始赋值;从1590行到1593行可知还需要知道num_tques,num_tx_descs以及tpdesc_size这几个变量的值;
num_tques的初始赋值
由2367行和303行可知num_txques的初始值为 1;
num_txdescs的初始赋值
从2513行以及315行可知 num_txdescs的初始值值为 512;
最后看下tpdesc_size的初始赋值
由3219行和70行可知 tpdesc_size 的初始值为 4;接下来带入计算下该ring_head的size的大小:
1589 ring_header->size =
1590 1 * 512 * (4 * 4) + 8192
1591 1 * 256 * (2 * 4) + 2048
1592 1 * 256 * (4 * 4) + 4096
1593 1 * 8 + 1 * 2 * 8; 2414360 byte
从emac_alloc_all_rtx_descriptor函数的1609行可知,此处首次根据物理地址按8字节对齐调整used的值;接着1611行便调用了emac_alloc_all_tx_descriptor函数来分配tx的描述符,接下来看看该函数的实现;
从1448行,可发现tpd的count在这之前便已经赋值,回到emac_alloc_all_rtx_descriptor函数的1579行才知,num_txdescs便是tpd的count的值;
tpd.count 512
tpd.size sizeof(struct emac_buffer) * 512
tpd.tpbuff size大小的已清零的内存地址
tpd.tpdma ring_header的物理地址dma加上已使用的偏移used
tpd.tpdesc ring_header的虚拟地址desc加上已使用的偏移used
ring_header->used 根据tdp的size进行8字节对齐并更新
tdp.produce_idx 0
tpd.consume_idx 0
接下来看看tpdma在什么地方被使用;
在 emac_mac_dma_rings_config这个函数的296到297行,以及312到313行可知,tpdma这个物理地址被分成高位和低位分别写到了mac的EMAC_DESC_CTRL_1寄存器和EMAC_DESC_CTRL_8寄存中,EMAC_DESC_CTRL_9寄存器中写入了该ring的长度count;到此mac硬件上与软件通过此物理地址联系起来了。再来看看emac_mac_dma_rings_config函数被什么函数调用以及什么情况下会调用;
由468行可知emac_mac_dma_rings_config函数被emac_hw_config_mac函数调用;
由1919行可知emac_hw_config_mac函数被emac_mac_up函数调用,到此已可知其被调用的场景便是NIC up的时。
接着从emac的ndo_start_xmit函数开始分析其发送过程:
从2314行可知,emac_start_xmit函数是emac的ndo_start_xmit函数的实现;1139行取出了tx队列后,便在1140行调用了emac_start_xmit_frame函数,说明需要分析的是该函数。
由1085行可知,这里是当设置了ADPT_STATE_DOWN标志时,就直接释放skb并返回;接着1090行调用了emac_check_num_tpdescs函数,当该函数检测结果失败时,则调用了netif_stop_queue来停止队列,接下来看看emac_check_num_tpdescs函数的实现;
emac_check_num_tpdescs该函数主要是从881行到890行都是在计算需要用到的tpd的数量,然后调用emac_get_num_free_tpdescs函数来获取当前空闲的tpd,最后比较这两个值的大小;881行则是判断该skb是否存在分段;882行计算出整个协议头首部的长度,883行检查当非分段的数据的长度大于协议头首部的长度时(非空payload),才认为需要一个tpd;885行则是该skb的分段类型是SKB_GSO_TCPV6时,也会增加一个tpd;889行则是根据该skb的分断数量来确定需要的tpd的数量;接着在看下emac_get_num_free_tpdescs函数;
由802行可知,空闲的tpd是这样计算的:
- 消费者下标 大于 生产者下标时,消费者下标 减去 生产者下标 再减一
- 消费者下标 小于或等于 生产者下标时,count 加上 消费者下标 再减去 生产者下标 再减一
接着回到emac_start_xmit_frame函数1098行,该行调用emac_tso_csum函数,从函数名上看是计算分段的校验和信息;
由895行的注释可知该函数是给tpd填充分段和校验和信息;该函数的框架主要是904行和962行的条件分支,904行是处理该skb存在分段时处理分段的信息填充的情况,962行则是填充校验和信息;905行到909行是当skb的首部已经被复制过,则再次复制一次其首部(应该是为后面计算校验和做准备);911行到917行是当该skb是IP包时,计算了IP包的长度,以及重新调整该skb的数据长度;919行到925行则是通过计算skb首部的长度来判断tcp的数据为0的情况(为0时不进行分段处理,直接计算检验和);接着927行到934行处理该skb是ipv4的分段时,计算tcp最终的校验和,并设置ipv4的标志(stpd->genr.ipv4);936行到953行则是处理该skb是ipv6的分段的情况,在944行计算了ipv6的检验和,然后创建了个临时变量extra_tpd,便调用了emac_set_tpdesc函数,还设置了lso版本2的标志(stpd->tso.lso_v2);955行到957行则是填充传输描述符的信息(lso,tcp首部的偏移,mss);962行CHECKSUM_PARTIAL表示还需要再处理部分校验和信息,965行到969行检查了skb的tcp数据的偏移的合法性,970行计算了校验和的偏移值,972行到974行则是将前面计算的信息(tcp数据的偏移值,校验和的偏移值,以及设置自定义校验和);
由于在951行调用了emac_set_tpdesc函数;且emac_tso_csum函数的一个参数是stpd,到这需要看看stpd的数据结构定义:
从488行该结构emac_sw_tpdesc的定义可知,这是一个联合体,总大小为16字节,主要作用是描述数据的格式;从489到491行可知其主要有这三类参数格式,而496行的dfmt则是为了方便取其值而设计的;从emac_sw_tpdes_general的定义中的注释可知,该结构的成员大小完全对应dfmt的dw;从425行以及430行可知,mac硬件上的dma的地址长度总长为45bit;从449行可知ipv4代表是ipv4的包;从emac_sw_tpdes_tso的484行可知pkt_len是额外的tpd的包长度;475行的lso则是tcp大包的发送;470行的tcphdr_offset则是tcp首部的偏移;481行的mss则是使用tcp大包发送时的最大报文段长度;从emac_sw_tpdes_checksum的440行可知payld_offset是payload的在报文中的偏移;451行的cxsum_offset是校验和在报文中的偏移;441行的c_csum是使用自定义的校验和offload。
接着看下emac_set_tpdest函数的实现:
由449行可知,emac_set_tpdest 函数在此使用当前的生产者下标produce_idx来更新为上一次的生产者的下标last_produce_idx;450行以及455到458行则是根据当前生产者下标produce_idx来取得dma映射得到的虚拟地址并将其数据格式dfmt写到该地址;452行到453行则是将生产者下标后移一位,并检查当生产者下标达到最大值count时,再将其清零;由此可知调用该函数就是设置tpd,并且会更新生产者下标;
回到emac_tso_csum函数的936行到953行的分支,其作用是对ipv6的分段的skb,会额外的先传输该分段的长度等信息;
在回到emac_start_xmit_frame函数的1103行到1110行的分支,此处是处理skb中存在vlan tag的情况,1104行先取出vlan的相关控制信息,1107行则是在从vlan的控制信息中取出tag,再赋值到stpd中,并设置stpd中vlan的标志(stpd.genr.ins_cvtag );
1112行到1113行的分支则是根据以太网首部的长度来判断其是以太网帧的类型(0为ETHERNET_II,1为SNAP);
接下来1115行则是主要的mac把数据准备到dma的过程,也就是emac_tx_map函数的实现:
从980行的注释可知,该函数主要功能是填充传输描述符;先从大体分支来分析,其主要是996行,1011行,1022行这三个分支中都调用了dma_map_xxx的函数,然后再调用了emac_set_tpdest函数,再之后调用了ema_set_tpdesc_lastfrag;从1042行可知,该分支是处理使能硬件tx测试的,最后从1069行到1070行的注释可知,在dma_unmap时才会释放skb;接下来分别看看emac_tx_map函数中三个分支的内容:
emac_tx_map函数的第一个分支996行的条件是处理该skb需要分段的情况,997行是计算该skb的整个首部的长度,1000行则是其根据生产者下标produce_idx获取到对应的tpbuff的地址,然后1001行赋值该tpbuff的length字段,1002行则是将skb的data,长度为hdr_len,方向为DMA_TO_DEVICE的虚拟地址映射而得到其物理地址,并赋值给tpbuff的dma;1005到1006行则分别将tpbuff的dma物理地址给赋值到stpd的genr.addr_lo和genr.add_hi,1007行则是赋值其长度genr.buffer_len,最后1008行调用了emac_set_tpdesc函数来设置stpd(可看出这里是先传输skb的首部);
emac_tx_map函数的第二个分支1011行则是正常处理skb的data的部分,映射的dma地址变成了skb的data加上前面可能已经发送的长度,dma的长度也变成了skb的非分段的长度减去前面可能已经发送的长度,1016行到1019行也是一样填充stpd的必要字段,再调用emac_set_tpdesc函数来设置stpd(这里是正常的传输skb的data);
emac_tx_map函数的第三个分支1022行则是根据该skb的分段的数量来依次映射dma,调用emac_set_tpdesc函数来设置stpd(这里是处理skb的分段的数据);这三个主分支结束后,调用了ema_set_tpdesc_lastfrag函数来设置最后一个tpd,来看看其实现;
由472行可知,根据上一个的生产者下标获取其tpd,475行到476行则是在原来的值上增加EMAC_TPD_LAST_FRAGMENT这是最后一个下标的标志,477行则是写入该值;emac_tx_map函数分析到此,最后一部分的1042行分支是处理使能硬件tx测试的情况,不再分析此分支;回到emac_start_xmit_frame函数,1117行调用了netdev_sent_queue函数,该函数netdev_sent_queue是用来上报等待发送到网络设备硬件队列的字节数;由1120行到1125行可知1123行这里是单独更新mac的tx队列的生产者下标的寄存器,到此表示驱动程序已经把数据(帧)准备好了,还需要等待硬件真正发送完成,而硬件发送完成时,会产生发送完成的中断,这时才是帧真正的传输完成。emac_start_xmit_frame函数分析完毕,接下来需要分析发送完成的中断处理了;
emac_isr是mac的中断处理函数,1212行到1213行首先关闭掉硬件mac的中断功能;1216行到1217行读取mac的中断状态寄存器的值;1222行到1228行处理有发生中断错误的情况,发生错误时使用了工作队列来重新初始化mac;1233行到1238行处理是接收中断的情况,这时使用napi来产生软中断来轮询处理接收mac的数据;1240行到1249行则是处理发送中断的情况,稍后再进行分析发送中断的处理;1251行打印告警发生overflow中断的情况;1256行到1260行处理phy的link事件;1262行到1263行则是处理1588PTP同步中断的处理;最后1267行到1268行再重新打开mac的硬件中断功能;最后分析下发送中断的处理,由1240行到1242行可知,当高通emac中断状态位中ISR_TX_PKT被置位时,便会调用emac_handle_tx函数。
816行在从消费者寄存器读取消费者下标,由于只读了一次,说明其应该是在更新了生产者寄存器后全部传输完毕后才产生该中断(而不是每传输完一个生产者就产生一次消费者);从822行到839行则是循环根据取出的消费者下标来取消dma的映射,并释放skb;822行的循环条件是当前的消费者下标不超过从寄存器读出消费者下标;823行根据当前的消费者下标取出其tpbuf;824行到828行便是取消该tpbuf的dma的映tpbuf的dma清零;830行到835行在统计skb的数量及总长度,再调用dev_kfree_skb_irq函数来释放skb(因其在中断上下文中,所以用的是带_irq的接口);837行到838行则是检查消费者下标达到最大值count时会清零的处理;最后841行到842行则是调用了netdev_completed_queue函数来上报已发送的包数和字节数;到此高通emac发送数据的过程已分析完毕,最后总结下整个过程。
- ring_header保存了申请的很大的一块dma内存。
- tpd的dma虚拟地址和物理地址都是来源于ring_header的dma的一部分。
- 在NIC up的时候,会把tpd的dma物理地址以及tpd的总数量写入到mac的寄存器里,这就与硬件mac建立了联系。
- 当要传输数据时,先检查当前空闲的tpd数量是否足够用。
- 处理校验和相关(有分段的包,和无分段的包,确定校验和offload相关信息)。
- 处理存在vlan的情况。
- 传输的处理,使用传输描述符来传输。
- 存在分段的skb会先使用skb的首部来创建一个传输描述符,再使用非分段的skb的payload来创建另一个传输描述符,接着循环使用分段的page来创建多个传输描述符。
- 传输时的操作,使用skb的data及长度来映射dma,再将得到的物理地址及长度写入到tpbuf中(dma映射的虚拟地址),把生产者下标自增一。
- 创建完需要传输的传输描述后,上报将要发送的字节数。
- 将最后的生产者下标写入到mac的寄存器中,硬件开始传输。
- 收到硬件发送完成的中断时,从mac的寄存器中读取消费者下标。
- 当前消费者下标小于寄存器中读出的消费者下标时,循环取消dma的映射,同时统计已发送的包数和字节数并释放skb,消费者下标自增一。
- 退出循环时,则上报已发送完成的字节数。