- 论坛徽章:
- 13
|
本帖最后由 karma303 于 2016-07-05 05:18 编辑
RTL8139驱动的发包部分原先写好了,也测过了,只是觉得脏,改来改去,今天才得到一个比较舒服的版本。 不过跟linux内核的实现已经相差甚远了。
先贴上一篇写的不错的入门文章- m.blog.csdn.net/article/details?id=9191011
复制代码 美中不足的是,它有一段代码分析错了:- static void rtl8139_interrupt (int irq, void *dev_instance, struct pt_regs *regs)
- {
- ...
- if((isr & TxOK) || (isr & TxErr))
- {
- while((tp->dirty_tx != tp->cur_tx) || netif_queue_stopped(dev))
- {
- unsigned int txstatus =
- readl(ioaddr + TSD0 + tp->dirty_tx * sizeof(int));
-
- if(!(txstatus & (TxStatOK | TxAborted | TxUnderrun)))
- break; /* yet not transmitted */
-
- if(txstatus & TxStatOK) {
- LOG_MSG("Transmit OK interrupt\n");
- tp->stats.tx_bytes += (txstatus & 0x1fff);
- tp->stats.tx_packets++;
- }
- else {
- LOG_MSG("Transmit Error interrupt\n");
- tp->stats.tx_errors++;
- }
-
- tp->dirty_tx++;
- tp->dirty_tx = tp->dirty_tx % NUM_TX_DESC;
-
- if((tp->dirty_tx == tp->cur_tx) & netif_queue_stopped(dev))
- {
- LOG_MSG("waking up queue\n");
- netif_wake_queue(dev);
- }
- }
- }
- .......
- }
复制代码 先不管那篇文章怎么分析的,我们自己来看看。
截取的这段代码,是RTL8139发包中断的handler,其中有一个while循环,它是在尽可能多的探测已经空闲(发包完成)的descriptor。
按理说,一个TOK中断,对应着一个descriptor重新可用,为什么要探测更多呢?
很简单,设想这样的情景:我们一口气写了4个descriptor,就是说,一口气启动了4个包的发送,过了一会儿,descriptor 1发包完成了,上报给CPU一个TOK中断,但是,CPU很忙(可能正陷在别的硬中断里),就在这一小会儿,descriptor2,3,4也完成了发包,他们的TOK接踵而至。 只有一个ISR,当然是前浪死在沙滩上,等CPU缓过神,转向网卡的TOK中断routine,好几个TOK中断已经丢失了。
但这不妨事,因为网卡上就4个descriptor,我们逐个检查一下它们各自TSR的某些位,若为1,则表明这个descriptor已经完成发包了。
这就是上面的while循环的目的。在文档里是有说明的。
再说那篇文章,他写的是,“那是因为8139不是在成功发送一个包后发出中断,而是将缓冲区上所有包发走后才发出中断的”。
我不是想接人家的短,只是看到他的错,觉得很欣慰。那个作者心里肯定对"8139为什么有4个descriptor”有一些猜想,所以分析问题时尝试往上靠。 我也一直没明白8139为什么有4个descriptor。
刚才说到文档,其实官网上我没找到,google到的两篇很好的,竟然来自于美国两所大学的网站。- phy.duke.edu/~rgb/brahma/Resources/beowulf/linux/drivers/rtl8139.html
- cs.usfca.edu/~cruse/cs326/RTL8139_ProgrammersGuide.pdf
复制代码 原先学init进程时,看到一篇国内大学网站上的文章,也很惊艳,是个老师写的。能在大学里学内核,或者教内核,都是挺幸福的。
回到正题。刚才说到那个while循环,手册上有提醒。而且手册上还提醒了第二点:
Case 2: No packet TOK need to be handled, but ISR routine called
就是提醒驱动编写者,有些个TOK中断送到CPU上时,中断例程其实无事可做
什么原因呢?想象这样一个情景:Descriptor A的TOK routine正在运行,此时Descriptor B的TOK发生了,但CPU听不到,因为还在Descriptor A的中断例程里,但是,就在这个例程里,我们能检测到Descriptor B已经可用了(你还记得那个while循环吗),并做相关的处理。等从这次中断routine出去,回立刻陷入Descriptor B的TOK的中断例程。但是,已经无事可做了。驱动编写者要考虑到这一点。
以上是本文的上半部分。
在papaya内核里,我主要是去掉了队列的"STOPPED"标志位,相应的,netif_start_queue, netif_stop_queue等一些函数也就删掉了。因为我想了很久,觉得没必要给tx_queue设置一个"running/stopped"的属性。
那网卡忙碌需要暂停发包时,该怎么通知内核的framework呢。我给start_xmit设计了一个返回值,用来反映当前网卡是否忙碌(是否还有发包的能力)。- void nic_wake_queue(struct net_device *netdev){
- cli();
- if((netdev->flags & IN_WAKE_QUEUE) == 1) return sti();
- if(!netdev->tx_queue.root) return sti();
- if(netdev->tx_busy(netdev)) return sti();
- netdev->flags |= IN_WAKE_QUEUE;
- sti();
-
- struct skb_queue *list = &netdev->tx_queue;
- struct sk_buff * one;
- int code = 0;
- while(1){
- oprintf(" %u done ", netdev->tx_count);
- one = list->root;
- LL2_POP(list);
- cli();
- code = netdev->start_xmit(one, netdev);
- if(code == -1 || list->root == 0){ //-1 indicates busy
- netdev->flags &= ~IN_WAKE_QUEUE;
- break;
- }
- sti();
- }
- sti();
- }
复制代码 因为不用维护任何全局的flags,代码很简单。
一开始三个if语句,分别是防止自身重入,检测队列空,检测网卡忙。
为什么nic_wake_queue不允许自身重入呢。我其实没有想“自身重入会带来哪些坏处”(我没有脑细胞去想了),因为这个函数没有任何重入的理由,他能在队列异步生长(因为中断尾随的bottom half)的同时很好的工作。
这个函数可以直接由用户调用(通过syscall),也可以在bottom half里由内核routine调用。BH与BH之间不存在重入的可能,因为papaya内核里,BH是严禁嵌套的,连不同channel上的BH都不嵌套。这根早期的linux很像。
重入发生的情形是:用户调用nic_wake_queue,运行期间被硬中断,其尾随的bottom half里,可能会调用nic_wake_queue。像比TOK中断。我们的if就是防止这个。
第二个话题,我在调用start_xmit的前后,用cli()/sti()把它包围起来,这个cli()来的似乎有些早,以后可以优化,但sti()一点儿也不早。start_xmit的返回值非常重要,必须保证它平安的返回。(即,在cli模式下顺利的ret回来)
接下来说说tx_queue的“运行”靠什么维持。
tx_queue会经常暂停,因为网卡忙碌,或者是自身空了。那再次启动靠什么呢?
我的设计是,提交skb到发包队列时,如果发现队列是空的,就手动调用一下nic_wake_queue()。否则,就不用管它,待会儿肯定有TOK中断发生。TOK中断是“驱动”发包队列的主力。
往发包队列里提交skb是个很常用的操作:- void waiting_for_transmit(struct sk_buff *skb){
- cli();
- bool tx_queue_empty = !(bool)skb->dev->tx_queue.root;
- LL2_A( &skb->dev->tx_queue, skb);
- sti();
- if(tx_queue_empty) nic_wake_queue(skb->dev);
- }
复制代码 这段代码不很优雅,就是队列为空,不代表待会儿就没有TOK中断发生了。所以应该再做进一步的检测,不到万不得已,不手动调用。
【关于tx_queue的”排队“】
很多人写了netif_wake_queue之后,大概都希望看到很多skb在tx_queue里排队的情形。实际上,8139发包的速度是很快的,不容易看到”排队“。
要想让包排队,要求是,在你依次写完1,2,3,4号descriptor之后,1号descriptor的包还没发出去。这时候,你再提交的包就只能排队等候了。
我今天是把发包的大小从60byte增加到1500byte,才第一次捕捉到”排队“:因为包大了,发送的相对就慢了。
我测试发送100个包,指定不同的发包长度,看分别有多少个包排队。- 发包长度(byte) 1000 800 700 670 650 600
- 排队个数(个/100) 94 92 89 87 0 0
复制代码 以上仅限于真实机器,如果你在QEMU上测,估计永远是瞬间发出去,什么也看不到。我对QEMU是越来越失望了。在考虑是不是写一个e1000的驱动,这样可以回归bochs。
【关于OWN位】
写8139驱动的一个细节。如果你想检测一个descriptor的包是否发出去了,记住,不要检测TSR的OWN位,它置1,仅代表这个包从内存DMA到FIFO里了。并不一定到网线上了。
要检测的是 if(!TSR.TOK && !TSR.TUN && !TSR.TABT)。这三个bit分别代表”发包成功“,"FIFO exhausted", "发包终止"。 如果这三个bit都为0,那说明结果还没出来,包还没发出去。
【8139芯片的word aligned】
驱动提供给8139的内存buffer的起始地址,无论是用来接收,还是发送,都必须4字节对齐。 这是8139芯片要求的。
我开始只是在收包时注意它,发包时没管。
后来发现,我发出去的包,每间隔一个包就是坏包:mac头都是错的。
因为我申请4个发包缓冲区时,不是一个个申请的,而是:-
- private->tx_buf = kmalloc2( TX_BUF_SIZE * NUM_TX_DESC, __GFP_DMA);
- for(int i = 0; i < NUM_TX_DESC; i++){
- private->tx_bufs[i] = private->tx_buf + i * TX_BUF_SIZE;
- }
复制代码 而我把TX_BUF_SIZE定义成8139发送单包的体积上线:1762。
改成 #define TX_BUF_SIZE (1762 & ~3)就好了。
|
|