weiweishuo 发表于 2016-06-30 20:10

papaya内核笔记 4 网卡收包及其它

本帖最后由 weiweishuo 于 2016-06-30 20:55 编辑

【Rx Buffer里的package们】
注意,在Rx buffer里,接收到的packet一个接一个的躺着,它们之间并非密不透缝。每个packet最前面,有4个字节,struct { u16 status; u16 size}。其中status描述这个包的接收状态::它是个广播包,组播包,还是MAC matched包?它的CRC校验和正确吗?等等。。最关键的当然是ROK这个bit。
除了包开头有一个4字节的header,包末尾还有4字节的CRC,CRC后面还可能有1~3个字节的padding,是为了下一个包对齐到4字节(8139芯片的特性,手册上说是word aligned, 但pseudo code里暗示的4字节对齐)。当然,这一切都是芯片完成的,驱动只管去读。
Realtek的这种“给收到的数据包加header”的设计,其实不出我们的预料。因为8139在网卡收包上放弃了descriptor based的设计,但每个收到的包都要有相应的描述信息,这一点是跑不了的。于是收包时,顺便把这些信息组织在包的开头。
忘了说了,package header里的size字段是描述这个包的长度,包括CRC,但不包括header本身,也不包括最末尾可能存在的填充字节。(当然不该包括)


【别忘了打开IMR的所有bit】
我们在测试初期,会只打开IMR的ROK和TOK位。
基本的测试通过后,要赶紧把IMR上别的bit都打开。我这儿实在是在废话,但今天就在这上面浪费了一天:
测试机上的内核从网线上收到一个包之后,我再怎么ping它,都没反应。连中断都没有。但是按键盘还有反应,可见硬中断是开着的。
最后发现,原来IMR只打开了TOK,ROK两个位,收发包的结果可不仅是这两个,那当然没反应————这一回是Rx Buffer Overflow。

【CBA, CAPR 和CMD.BUFF_EMPTY】
在我的内核里,CBA叫CursorToWrite, CAPR叫CursorToRead。它们是两个指针(或者叫偏移)寄存器。
CBA指向Rx buffer里躺着的最后一个包的末尾,若新的包来了,8139就往CBA指的地方继续写,所以叫它CursorToWrite。 驱动程序用不着CBA寄存器,它是8139自己维护的。
CAPR的作用是:你驱动程序从Rx buffer里读包,你读到哪儿了,你得告诉我芯片(就通过CAPR),如果我发现你已经读完最后一个包了,我就把CMD.BUFF_EMTPY位置1;
具体的讲,如果是CAPR追到和CBA相等,就意味着我们驱动已经读完了读完了Rx Buffer上的所有package。
但写驱动时,我们每读完一个package,不能耿直的把CAPR更新到那个package的末尾。你可能问,CBA也是指在package的末尾,不这样做,哪有机会让CAPR和CBA相等呢?
这就是硬件为什么脏。我先说明,我们想的没错,我们想的就是正常人的思路。
但是,Realtek的设计里,CAPR == CBA会触发Buffer Overflow中断!
我们先放下我们的疑惑,站在Realtek的角度。
Realtek考虑到另一个问题:如果你驱动一直不读Rx Buffer,而CBA会随着硬件收包不停的推移(到Rx Buffer的末尾会Wrap),当CBA的值再次追到和CAPR相等的时候,整个Rx Buffer里都是崭新的需要驱动去读的package。再没有空间收包了,这就是Buffer Overflow。
现在,我们再回到刚才的问题,就觉得不是那么怒气冲冲了。
那,怎么才能标识CAPR已经读完了最后一个包呢? Realtek的设计是,你驱动每次读完一个package,就把CAPR更新到离这个包末尾还差16个字节的地方。当8139发现CAPR逼近CBA只差16个字节,就知道Rx Buffer里的包读完了————16个字节是肯定放不下一个包的。
这就分析完了。但我有一点不懂,Realtek一旦这样设计,那CBA就永远没有跟CAPR相等的机会了。还怎么检测over flow?

【调试】
现在发现最好的调试方式,是一台裸机测试内核,用一条网线连到装有linux系统的机器上(像比自己正在写程序的电脑)。bash自带的命令几乎就够用了,linux真是为程序员而生的。
后期往tcp上层调试的时候,再考虑用虚拟机。

【wireshark】
wireshark用的感觉不错。网络调试时,图形界面的优势就显出来了。可以统计packet数量,往指定的packet跳转,查看字段时鼠标点击有提示(我承认裸眼看xxd是很牛),等等。
多一个抓包工具是必要的,跟tcpdump的输出结果对照着看。 之前调ARP包,发现papaya发出的ARP包一直得不到回应,从tcpdump的抓包看,papaya发出的ARP包似乎不符合标准:
tcpdump平时不显示14字节的ether头部,但对我的包却格外照顾,莫名奇妙的显示出最后两个字节,0x0806。
检查了很久,终于决定用另一个抓包软件看看。在wireshark上很快确定ARP包的结构没问题(成员排列没问题)。

【目的IP为0的ARP报文】
若linux接受到一个“目的IP为0”的ARP报文,linux会忽略它。
必须把对方的IP写死在ARP报文里。似乎本来就该这样,我原先是看错了wiki上的一句话,以为把target IP设成0,那所有的局域网主机都会响应这个ARP报文。
   An ARP probe is an ARP request constructed with an all-zero sender IP address (SPA).


【arp协议的作用】
arp协议应该是“最后100米”的寻址协议。
当ip报文穿越最后一个路由器,进入局域网。停在交换机上:接下来就要靠MAC寻址了。这依赖于ARP协议。它维护着IP到MAC地址的映射。


【rtl8139_init_one() 和 rtl8139_open()】
   宏观的看,前者在我们运行"insmod 8139too.o"时被调用。后者是运行"ifconfig eth0 up"时被调用。
   两者都是做初始化,一先一后,_init_one侧重于PCI部分(网卡首先是一个PCI设备),_open侧重于网卡本职功能。
   我只能理解到这个程度了,其实两个函数还是有交集的,特别是初始化netdevice的工作。

【linux上的一段注释:bitfield & endian】
----------drbd.h----------
38 /* Although the Linux source code makes a difference between
39    generic endianness and the bitfields' endianness, there is no
40    architecture as of Linux-2.6.24-rc4 where the bitfields' endianness
41    does not match the generic endianness. */
42
43 #if __BYTE_ORDER == __LITTLE_ENDIAN
44 #define __LITTLE_ENDIAN_BITFIELD
45 #elif __BYTE_ORDER == __BIG_ENDIAN
46 #define __BIG_ENDIAN_BITFIELD
47 #else
48 # error "sorry, weird endianness on this box"
49 #endif
在网络传输中,大端主机先传送的是MSB,小端主机线传送的是LSB。
从结果上来说,这句话不错。 但说这句话的人,必然对endian存在误区。因为网络传输中,是不考虑CPU的endian的。 网卡只是忠实的把主机A上的一段内存印象,“拷贝”到主机B的某一段内存里去。

【bochs的usb_uhci failed】
bochs启动"--enable-usb"选项的话,启动时会panic, "dlopen usb_uhci failed"。
在一个邮件列表里,看到15年12月份有人说,最新的svn的版本已经打了相应的patch。
因为源码在sourceforge,折腾了很久,还是不行。
最后从镜像网站下的,虽然是2.6.8,但是是2015年5月的release。
问题依旧。只好configure时把usb选项关掉。
顺便贴上自己的配置选项:
./configure --with-x11 --with-wx --enable-ne2000 --enable-iodebug --enable-gdb-stub --enable-e1000 --enable-pci --enable-pcidev --enable-plugins --disable-docbook
哦,我重新编译bochs,是想写e1000的驱动。

【最后,贴两份“收包中断”的代码】
我上次推荐的那篇8139入门的文章,在收包中断的处理上也有错误(而且是代码和解释都错了)。还是看手册上的pseudo code和linux的源码靠谱。
下面贴的两段,一个是linux2.0时期的源码,有删减(注意它虽然写的是8129。。);另一个是手册上的pseudo code,可谓字字如金,体现了一些未文档化但很重要的tips。
/* The data sheet doesn't describe the Rx ring at all, so I'm guessing at the
   field alignments and semantics. */
static int rtl8129_rx(struct device *dev)
{
        struct rtl8129_private *tp = (struct rtl8129_private *)dev->priv;
        long ioaddr = dev->base_addr;
        unsigned char *rx_ring = tp->rx_ring;
        u16 cur_rx = tp->cur_rx;

        while ((inb(ioaddr + ChipCmd) & 1) == 0) {
                int ring_offset = cur_rx % RX_BUF_LEN;
                u32 rx_status = *(u32*)(rx_ring + ring_offset);
                int rx_size = rx_status >> 16;

                if (rx_status & RxTooLong) {
                        ...
                } else if (rx_status &
                                   (RxBadSymbol|RxRunt|RxTooLong|RxCRCErr|RxBadAlign)) {
                        ...
                } else {
                        /* Malloc up new buffer, compatible with net-2e. */
                        /* Omit the four octet CRC from the length. */
                        struct sk_buff *skb;

                        skb = dev_alloc_skb(rx_size + 2);
                        ...
                        skb->dev = dev;
                        skb_reserve(skb, 2);        /* 16 byte align the IP fields. */
                        if (ring_offset+rx_size+4 > RX_BUF_LEN) {
                                int semi_count = RX_BUF_LEN - ring_offset - 4;
                                memcpy(skb_put(skb, semi_count), &rx_ring,
                                           semi_count);
                                memcpy(skb_put(skb, rx_size-semi_count), rx_ring,
                                           rx_size-semi_count);
                                if (rtl8129_debug > 4) {
                                        int i;
                                        ...
                                        memset(rx_ring, 0xcc, 16);
                                }
                        } else {
                                eth_copy_and_sum(skb, &rx_ring,
                                                               rx_size, 0);
                                skb_put(skb, rx_size);
                                ...
                        }
                        skb->protocol = eth_type_trans(skb, dev);
                        netif_rx(skb);
                        tp->stats.rx_bytes += rx_size;
                        tp->stats.rx_packets++;
                }

                cur_rx = (cur_rx + rx_size + 4 + 3) & ~3;
                outw(cur_rx - 16, ioaddr + RxBufPtr);
        }
        ...
        tp->cur_rx = cur_rx;
        return 0;
}
BOOLEAN PacketOK(        PPACKETHEADER pPktHdr        )
{
        BOOLEAN BadPacket = pPktHdr->RUNT ||
        pPktHdr->LONG ||
        pPktHdr->CRC ||        pPktHdr->FAE;
        if( ( !BadPacket ) && ( pPktHdr->ROK ) )
        {
                if ( (pPktHdr->PacketLength > RX_MAX_PACKET_LENGTH ) ||
                (pPktHdr->PacketLength < RX_MIN_PACKET_LENGTH ))                return(FALSE);        
                PacketReceivedGood++;
                ByteReceived += pPktHdr->PacketLength;#endif
                return TRUE ;
        }
        else        return FALSE;
}
BOOLEAN        RxInterruptHandler()
{
        unsigned char TmpCMD;
        unsigned int PktLength;
        unsigned char *pIncomePacket, *RxReadPtr;
        PPACKETHEADER        pPacketHeader;
        while (TRUE)
        {
                TmpCMD = inportb(IOBase + CR);
                if (TmpCMD & CR_BUFE)        break;
        do
        {
                RxReadPtr        = RxBuffer + RxReadPtrOffset;
                pPacketHeader = (PPACKETHEADER)RxReadPtr;
                pIncomePacket = RxReadPtr + 4;
                PktLength = pPacketHeader->PacketLength;
                //this length include CRC
                if ( PacketOK( pPacketHeader ) )
                {
                        if ( (RxReadPtrOffset + PktLength) > RX_BUFFER_SIZE )
                                //wrap around to end of RxBuffer
                                memcpy( RxBuffer + RX_BUFFER_SIZE , RxBuffer,
                                                        (RxReadPtrOffset + PktLength - RX_BUFFER_SIZE) );
                        //copy the packet out here
                        CopyPacket(pIncomePacket,PktLength - 4);//don't copy 4 bytes CRC
                        //update Read Pointer
                        RxReadPtrOffset = (RxReadPtrOffset + PktLength + 4 + 3) & RX_READ_POINTER_MASK;
                        //4:for header length(PktLength include 4 bytes CRC)
                        //3:for dword alignment
                        outport( IOBase + CAPR, RxReadPtrOffset - 0x10); //-4:avoid overflow
                }
                else
                {
                        ResetRx();
                        break;
                }
                TmpCMD = inportb(IOBase + CR);
        }
        while (!(TmpCMD & CR_BUFE));
        }
        return (TRUE);
//Done
}
页: [1]
查看完整版本: papaya内核笔记 4 网卡收包及其它