04
29

NAPI與pure interrupt driver的效能比較

下星期就要出差去美國了,擔心的反而不是豬流感,而是未來一星期的IOT會不會出問題,雖然測試好幾遍了,但工作那麼多年,根據經驗,只要是正式的測試場合,平常不會出trouble的地方全部都會在那時出現,不過這種東西是危機也是轉機,看個人造化

NAPI的相關資料可查尋此網站,它推出的目的就是為了在interrupt和polling之間取得一個效能平衡,interrupt的缺點就是當封包數目多且中斷頻繁發生時,可能會有upper layer skb queue starvation的情況,嚴重者會造成其它的driver無法取得中斷資源,而polling會耗費大量的cpu time,為了終結以上兩個情況,在2.6的kernel已經把NAPI的相關函式提供給driver programmer,NAPI會在中斷時提供一polling函式把drvier queue裡面的skb一口氣讀出(假設weight為64,就是一次讀取64個skb),這樣不但減輕interrupt對系統造成的負擔,也間接解決drvier資源相競的問題

在這邊我寫的NAPI範例是架構在CS8900A上,而重點如下

1. static inline void netif_napi_add(struct net_device *dev,
      struct napi_struct *napi,
      int (*poll)(struct napi_struct *, int),
      int weight)
註冊driver poll函式並輸入driver的權重,此函式會把driver掛上device polling queue

2. static inline void napi_enable(struct napi_struct *n)
開啟napi的功能,通常寫在drvier open函式


3. static inline void napi_disable(struct napi_struct *n)
關閉napi的功能,通常寫在drvier close函式


4. static inline void netif_rx_schedule(struct net_device *dev,
         struct napi_struct *napi)
這函式通常寫在interrupt routine,當device收到封包觸發中斷時,可用此函式通知kernel polling我們devcie的skb queue

5. static inline void netif_rx_complete(struct net_device *dev,
         struct napi_struct *napi)
當polling已經把device queue清空後,可以呼叫此函式通知核心不用在poll了

6.int netif_receive_skb(struct sk_buff *skb)(4.30新增)
收到封包時用這個函式取代netif_rx(skb)把skb丟給upper protocol layer

完整程式碼如下,要特別注意進入到polling function時要關閉IRQ,在完成polling時,確定device queue清空後再開啟IRQ

/*
* cs8900a.c: cs8900a ethernet driver for QT2410 platform.
*
* Copyright (C) 2008 Joey Cheng (jemicheng@gmail.com)
*/

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/interrupt.h>
#include <linux/string.h>
#include <linux/delay.h>
#include <linux/netdevice.h>
#include <linux/platform_device.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>
#include <linux/irq.h>
#include <asm/irq.h>
#include <asm/hardware.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include "cs8900a.h"
 
static char *cs8900astr = "CS8900A";
u_char CS8900Amac[6]={0x00,0x80,0x48,0x12,0x34,0x56};
static struct napi_struct csnapi;
 
//static struct device *cs8900a_dev; /* platform device attached to */
static struct resource *cs8900a_mem;
static struct resource *cs8900a_irq;
static void __iomem *cs8900a_base;
 
#define IOREAD(base,o) ((u_short)*((volatile u_short *)(base + (o))))
#define IOWRITE(base,o, d) *((volatile u_short *)(base + (o))) = (u_short)(d)
#define USE_NAPI
struct cs8900a_private
{
spinlock_t lock;
};
 
//according to data sheet page 75. I/O Space operation
//Specify the offset of the PacketPage Memory to visit the registers
//inside the pktpage memory.There is a pkt page pointer which is a base pointer
//for us to use
u_short ReadPktPageReg(void __iomem *base,u_short offset)
{
*
((volatile u_short *)(base+PKPGPTR))=offset;
return (u_short)(*((volatile u_short *)(base +PKPGDATA0)));
 
}
 
u_short WritePktPageReg(void __iomem *base,u_short offset,u_short data)
{
*
((volatile u_short *)(base+PKPGPTR))=offset;
*
((volatile u_short *)(base+PKPGDATA0))=data;
return 0;
 
}
 
int ProbeCS8900A(void __iomem *base)
{
u_short pic=0;
 
//After reset, the CS8900A packet page pointer
//register (IObase+0Ah) is set to 3000h. The
//3000h value can be used as part of the
//CS8900A signature when the system scans
//for the CS8900A.
 
pic=(u_short)*((volatile u_short *)(base+PKPGPTR));
//if (pic!= CS8900_SIGNATURE)
//{
// printk("CS8900A SIGNATURE FAILED\n");
// return 0;
//}
//else printk("CS8900A signature:%x\n\r",pic);
//read EISA
pic=ReadPktPageReg(base,PKTPG_EISA_NUMBER);
if (pic != CS8900_EISA_NUMBER)
{
printk("CS8900A EISA NUMBER FAILED\n");
return 0;
}
else printk("EISA Registration number:%x\n\r",pic);
//read PIC
pic=ReadPktPageReg(base,PKTPG_PRDCT_ID_CODE);
pic=pic&0x00ff;
if (pic != CS8900_PRDCT_ID)
{
printk("CS8900A PRODUCT ID FAILED\n");
return 0;
}
else printk("Product ID number:%x\n\r",pic);
 
return 1;
}
 
int ResetCS8900A(void __iomem *base)
{
u_short temp;
int event=0;
 
WritePktPageReg(base,PKTPG_SELF_CTL, SELF_CTL_RESET | SELF_CTL_LOW_BITS);
 
while (1)
{
//Delay(100);
msleep(100);
temp=ReadPktPageReg(base,PKTPG_SELF_ST);
if (temp & SELF_ST_INITD )
{
event=0;
break;
}else ++event;
 
if (event>100) return 0;
 
}
printk("CS8900A is in INITD state\n\r");
while (1)
{
msleep(100);
temp=ReadPktPageReg(base,PKTPG_SELF_ST);
if ((temp & SELF_ST_SIBUSY)==0 )
{
event=0;
break;
}else ++event;
 
if (event>100) return 0;
}
printk("CS8900A finished checking EEPROM\n\r");
 
return 1;
}
 
void EnableCS8900AIRQ(void __iomem *base)
{
u_short temp;
 
/* If INTERRUPT_NUMBER is 0, */
/* Interrupt request will be generated from INTRQ0 pin */
WritePktPageReg(base,PKTPG_INTERRUPT_NUMBER, INTERRUPT_NUMBER);
temp = ReadPktPageReg(base,PKTPG_BUS_CTL) | BUS_CTL_ENABLE_IRQ;
WritePktPageReg(base,PKTPG_BUS_CTL, temp);
temp = ReadPktPageReg(base,PKTPG_LINE_CTL) | LINE_CTL_RX_ON | LINE_CTL_TX_ON;
WritePktPageReg(base,PKTPG_LINE_CTL,temp);
}
 
void OpenIRQ(void)
{
u_short temp;
 
/* If INTERRUPT_NUMBER is 0, */
/* Interrupt request will be generated from INTRQ0 pin */
WritePktPageReg(cs8900a_base,PKTPG_INTERRUPT_NUMBER, INTERRUPT_NUMBER);
temp = ReadPktPageReg(cs8900a_base,PKTPG_BUS_CTL) | BUS_CTL_ENABLE_IRQ;
WritePktPageReg(cs8900a_base,PKTPG_BUS_CTL, temp);
}
 
void CloseIRQ(void)
{
u_short temp;
 
/* If INTERRUPT_NUMBER is 0, */
/* Interrupt request will be generated from INTRQ0 pin */
WritePktPageReg(cs8900a_base,PKTPG_INTERRUPT_NUMBER, INTERRUPT_NUMBER);
temp = ReadPktPageReg(cs8900a_base,PKTPG_BUS_CTL) & ~BUS_CTL_ENABLE_IRQ;
WritePktPageReg(cs8900a_base,PKTPG_BUS_CTL, temp);
}
 
int InitControlReg(void __iomem *base)
{
u_short temp;
u_short *MAC;
 
MAC=(u_short *)CS8900Amac;
temp =ReadPktPageReg(base,PKTPG_LINE_CTL);
temp|= LINE_CTL_10_BASE_T;
temp|= LINE_CTL_MOD_BACKOFF;
 
WritePktPageReg(base,PKTPG_LINE_CTL, temp);
WritePktPageReg(base,PKTPG_RX_CFG, RX_CFG_RX_OK_I_E | RX_CFG_LOW_BITS);
WritePktPageReg(base,PKTPG_RX_CTL, RX_CTL_RX_OK_A | RX_CTL_IND_ADDR_A | RX_CTL_BROADCAST_A | RX_CTL_LOW_BITS);
WritePktPageReg(base,PKTPG_INDIVISUAL_ADDR + 0, *MAC++);
WritePktPageReg(base,PKTPG_INDIVISUAL_ADDR + 2, *MAC++);
WritePktPageReg(base,PKTPG_INDIVISUAL_ADDR + 4, *MAC );
WritePktPageReg(base,PKTPG_TX_CFG, TX_CFG_LOW_BITS);
EnableCS8900AIRQ(base);
printk("-=====CS8900 init finished=====-\n\r");
return 1;
}
 
static void InitEthernet(void)
{
if (ResetCS8900A(cs8900a_base)==0)
{
printk("Reset CS8900A failed\n\r");
}
InitControlReg(cs8900a_base);
}
 
static inline void __cs8900a_set_mac_address(struct net_device *dev)
{
 
}
 
static int cs8900a_set_mac_address(struct net_device *dev, void *addr)
{
 
 
return 0;
}
 
static irqreturn_t cs8900a_interrupt(int irq, void *dev_id)
{
 
struct net_device *dev = (struct net_device *) dev_id;
struct cs8900a_private *cp=(struct cs8900a_private *)dev->priv;
#ifndef USE_NAPI
struct sk_buff *skb;
u_short len,pktlen,temp,*ptr;
u_short event;
#endif
unsigned long flags;
 
 
spin_lock_irqsave(&cp->lock, flags);
#ifdef USE_NAPI
//printk("cs8900a interrupt\n");
netif_rx_schedule(dev, &csnapi);
#else
event = IOREAD(cs8900a_base,ISQ);
do
{
if ( ((event & ISQ_REG_NUM ) == REG_NUM_RX_EVENT ) &&
((event & RX_EVENT_RX_OK) == RX_EVENT_RX_OK ) &&
((event & RX_EVENT_IND_ADDR) | (event & RX_CTL_BROADCAST_A)))
{
temp=IOREAD(cs8900a_base,IODATA0);//discard RxStatus
len=IOREAD(cs8900a_base,IODATA0);//read frame length
skb = dev_alloc_skb(len+2);
skb->dev = dev;
skb_reserve(skb, 2);
 
 
ptr=(u_short *)skb->data;
 
pktlen=len;
while (len>0)
{
 
temp=IOREAD(cs8900a_base,IODATA0);
if (len==1)
{
*
((char *)ptr)=(char)temp;
len-=1;
continue;
}
 
*
ptr=temp;
len-=2;
++
ptr;
}
 
skb_put(skb, pktlen);
skb->protocol = eth_type_trans(skb, dev);
netif_rx(skb);
dev->stats.rx_packets++;
dev->stats.rx_bytes += pktlen;
}
else
{
dev->stats.rx_dropped++;
}
event = IOREAD(cs8900a_base,ISQ);
}while (event>0);
#endif
spin_unlock_irqrestore(&cp->lock, flags);
return IRQ_HANDLED;
}
 
static int cs8900a_open(struct net_device *dev)
{
//struct cs8900a_private *cp = netdev_priv(dev);
unsigned int irq = dev->irq;
int err;
 
InitEthernet();
set_irq_type(dev->irq, IRQT_RISING);
if (request_irq(irq, cs8900a_interrupt, 0, cs8900astr, dev))
{
printk(KERN_ERR "CS8900A: Can't get irq %d\n", dev->irq);
err = -EAGAIN;
return err;
}
#ifdef USE_NAPI
napi_enable(&csnapi);
#endif
netif_start_queue(dev);
return 0;
}
 
static int cs8900a_close(struct net_device *dev)
{
//struct cs8900a_private *cp = netdev_priv(dev);
unsigned int irq = dev->irq;
 
#ifdef USE_NAPI
napi_disable(&csnapi);
#endif
WritePktPageReg(cs8900a_base,PKTPG_LINE_CTL,0);
netif_stop_queue(dev);
/* Shutdown the Seeq. */
free_irq(irq, dev);
return 0;
}
 
static inline int cs8900a_reset(struct net_device *dev)
{
 
 
dev->trans_start = jiffies;
netif_wake_queue(dev);
 
return 0;
}
 
void TransmitPacket(void __iomem *base,u_char *buffer,u_short len)
{
int event=0;
u_short data,*ptr;
u_char tmp;
 
 
//write TxCMD register
IOWRITE(base,TxCMD, TX_CMD_START_ALL | TX_CMD_LOW_BITS);
IOWRITE(base,TxLength, len);
 
//read BusST register
 
while (1)
{
data=ReadPktPageReg(base,PKTPG_BUS_ST);
if (data & BUS_ST_RDY_4_TX_NOW) break;
else ++event;
 
if (event>100) return;
}
 
ptr=(u_short *)buffer;
//transmit packet 2 bytes a round
while (len>0)
{
IOWRITE(base,IODATA0, *ptr);
len-=2;
++
ptr;
if (len==1)
{
tmp=*((char *)ptr);
IOWRITE(base,IODATA0, tmp);
break;
}
}
//printf("Transmit packet success\n\r");
}
 
static int cs8900a_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
unsigned long flags;
struct cs8900a_private *cp=(struct cs8900a_private *)dev->priv;
 
spin_lock_irqsave(&cp->lock, flags);
dev->stats.tx_bytes += skb->len;
dev->stats.tx_packets++;
TransmitPacket(cs8900a_base,skb->data,skb->len);
dev->trans_start = jiffies;
spin_unlock_irqrestore(&cp->lock, flags);
dev_kfree_skb(skb);
return 0;
}
 
static void timeout(struct net_device *dev)
{
printk(KERN_NOTICE "%s: transmit timed out, resetting\n", dev->name);
 
 
dev->trans_start = jiffies;
netif_wake_queue(dev);
}
 
static void cs8900a_set_multicast(struct net_device *dev)
{
 
}
#ifdef CONFIG_NET_POLL_CONTROLLER
static void cs8900a_poll_controller(struct net_device *dev)
{
disable_irq(dev->irq);
cs8900a_interrupt(dev->irq, dev);
enable_irq(dev->irq);
}
#endif
 
#ifdef USE_NAPI
static int cs8900a_poll(struct napi_struct *napi, int budget)
{
struct sk_buff *skb;
u_short len,pktlen,temp,*ptr;
struct net_device *dev = napi->dev;
struct cs8900a_private *cp=(struct cs8900a_private *)dev->priv;
u_short event;
unsigned long flags;
int pkt_count=0;
 
//printk("enter polling state\n");
CloseIRQ();
event = IOREAD(cs8900a_base,ISQ);
do
{
if ( ((event & ISQ_REG_NUM ) == REG_NUM_RX_EVENT ) &&
((event & RX_EVENT_RX_OK) == RX_EVENT_RX_OK ) &&
((event & RX_EVENT_IND_ADDR) | (event & RX_CTL_BROADCAST_A)))
{
pkt_count++;
temp=IOREAD(cs8900a_base,IODATA0);//discard RxStatus
len=IOREAD(cs8900a_base,IODATA0);//read frame length
skb = dev_alloc_skb(len+2);
skb->dev = dev;
skb_reserve(skb, 2);
 
 
ptr=(u_short *)skb->data;
 
pktlen=len;
while (len>0)
{
 
temp=IOREAD(cs8900a_base,IODATA0);
if (len==1)
{
*
((char *)ptr)=(char)temp;
len-=1;
continue;
}
 
*
ptr=temp;
len-=2;
++
ptr;
}
 
skb_put(skb, pktlen);
skb->protocol = eth_type_trans(skb, dev);
netif_receive_skb(skb);
dev->stats.rx_packets++;
dev->stats.rx_bytes += pktlen;
}
else
{
dev->stats.rx_dropped++;
}
if (pkt_count==budget)
{
printk("cs8900a serious error\n");
break;
}
event = IOREAD(cs8900a_base,ISQ);
}while (event>0);
 
 
if (pkt_count<budget)
{
//printk("cs8900a netif_rx_complete\n");
netif_rx_complete(dev, napi);
OpenIRQ();
}
 
return pkt_count;
}
#endif
static int __init cs8900a_probe(struct platform_device *pdev)
{
int err=0;
struct net_device *dev;
struct cs8900a_private *cp;
struct resource *res;
int size;
DECLARE_MAC_BUF(mac);
printk("cs8900a probing\n");
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res == NULL)
{
printk("no memory resource specified\n");
return -ENOENT;
}
 
size = (res->end-res->start)+1;
cs8900a_mem = request_mem_region(res->start, size, pdev->name);
if (cs8900a_mem == NULL)
{
printk("failed to get memory region\n");
err = -ENOENT;
goto err_out;
}
 
cs8900a_base = ioremap(res->start, size);
if (cs8900a_base == 0)
{
printk("failed to ioremap() region\n");
err = -EINVAL;
goto err_out;
}
printk("cs8900a base address:%p\n",cs8900a_base);
cs8900a_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (cs8900a_irq == NULL)
{
printk("no irq resource specified\n");
err = -ENOENT;
goto err_out;
}
printk("CS8900A:irq no %d\n",cs8900a_irq->start);
 
if (ProbeCS8900A(cs8900a_base)==0) goto err_out;
 
dev = alloc_etherdev(sizeof (struct cs8900a_private));
if (!dev)
{
printk(KERN_ERR "cs8900a: Etherdev alloc failed, aborting.\n");
err = -ENOMEM;
goto err_out;
}
platform_set_drvdata(pdev, dev);
cp = netdev_priv(dev);
spin_lock_init(&cp->lock);
 
dev->dev_addr[0] = CS8900Amac[0];
dev->dev_addr[1] = CS8900Amac[1];
dev->dev_addr[2] = CS8900Amac[2];
dev->dev_addr[3] = CS8900Amac[3];
dev->dev_addr[4] = CS8900Amac[4];
dev->dev_addr[5] = CS8900Amac[5];
 
dev->open = cs8900a_open;
dev->stop = cs8900a_close;
dev->hard_start_xmit = cs8900a_start_xmit;
dev->tx_timeout = timeout;
dev->set_multicast_list = cs8900a_set_multicast;
dev->set_mac_address = cs8900a_set_mac_address;
dev->irq = cs8900a_irq->start;
#ifdef CONFIG_NET_POLL_CONTROLLER
dev->poll_controller = cs8900a_poll_controller;
#endif
 
//printk("CS8900A:irq no %d\n",cs8900a_irq);
if (register_netdev(dev)) {
printk(KERN_ERR "CS8900a: Cannot register net device, "
"aborting.\n");
err = -ENODEV;
goto err_out;
}
#ifdef USE_NAPI
netif_napi_add(dev, &csnapi, cs8900a_poll, 64);
#endif
//printk(KERN_INFO "%s: %s %s\n",
// dev->name, cs8900astr, print_mac(mac, dev->dev_addr));
 
return 0;
 
//err_out_free_dev:
// kfree(dev);
 
err_out:
return err;
}
 
static int __exit cs8900a_remove(struct platform_device *pdev)
{
struct net_device *dev = platform_get_drvdata(pdev);
//struct cs8900a_private *cp = netdev_priv(dev);
 
free_irq(dev->irq,dev);
unregister_netdev(dev);
free_netdev(dev);
platform_set_drvdata(pdev, NULL);
 
return 0;
}
 
static struct platform_driver cs8900a_driver = {
.
probe = cs8900a_probe,
.
remove = __devexit_p(cs8900a_remove),
.
driver = {
.
name = "cirrus-cs89x0",
.
owner = THIS_MODULE,
}
};
 
static int __init cs8900a_module_init(void)
{
printk("CS8900A driver initial\n");
if (platform_driver_register(&cs8900a_driver))
{
printk("CS8900A Driver registration failed\n");
return -ENODEV;
}
 
return 0;
}
 
static void __exit cs8900a_module_exit(void)
{
platform_driver_unregister(&cs8900a_driver);
}
 
module_init(cs8900a_module_init);
module_exit(cs8900a_module_exit);
 
MODULE_DESCRIPTION("CS8900A");
MODULE_AUTHOR("Joey Cheng<jemicheng@gmail.com>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:cirrus-cs89x0");

iperf比較bandwidth的結果如下
[interrupt style]
[TCP]

Command:iperf -c 192.168.15.1  -t 20 -i 2
Result:0.0-20.0 sec  21.8 MBytes  9.13 Mbits/sec

[UDP]
Command:iperf -c 192.168.15.1  -t 20 -i 2  -u
Result:0.0-20.0 sec  2.50 MBytes  1.05 Mbits/sec

[NAPI style]
[TCP]
Result:0.0-20.0 sec  21.8 MBytes  9.13 Mbits/sec
[UDP]
Result:0.0-20.1 sec  2.50 MBytes  1.04 Mbits/sec


看結果好像都差不多,不過我後來想想,cs8900a的中斷致能的條件是device event(ISQ register)必需被讀完,換句話說,就是把device queue清空,所以如果要有效的利用NAPI,必需搭配硬體的設計才行

 

 

標籤: linux
評論: 7 | 引用: 0 | 閱讀: 13994
  • 1 
Albert [ 2009-05-22 17:32 | 回覆 | 編輯 刪除 ]
Joey大您好
請問netif_napi_add這個function是不是最近的kernel版本新增的API啊?
我在較早期的kernel source中沒看過(如2.6.1X),市面上幾本Linux driver
的書也都沒有講到此function.但在新版kernel似乎要用NAPI的話就一定要
call netif_napi_add.
因為我是Linux device driver的初學者,不知道該到那裡去找最新的Linux documentation. (您在上面介紹的NAPI-howto文件中也沒提及)
Joey [ 回復於2009-05-22 18:34 郵箱 | 編輯 刪除 ]
Albert大您好:
因為我用的是linux-2.6.26的版本,所以我自己也不是很清楚以前的kernel是怎麼做netpoll driver.市面上的書有時跟不上kernel update的進度,所以找不到某些function還蠻正常的

建議您玩linux要用最直接的方法
1.trace kernel code<==用關鍵字拆出driver架構很快,也很容易懂各個api的來龍去脈
2.看mailing list
3.lwn.net
上面的東西比你去找書上的資料踏實多了..
Albert [ 2009-05-22 22:42 | 回覆 | 編輯 刪除 ]
謝謝您的指教.
再請問一下,現在明白要寫Linux driver一定要trace kernel.
但因為kernel codes十分龐大,您說用關鍵字的意思是說,
就從我使用的API下手去trace嗎?
謝謝
Joey [ 回復於2009-05-25 09:32 郵箱 | 編輯 刪除 ]
是的,的確如此
TMAC [ 2011-04-30 12:02 | 回覆 | 編輯 刪除 ]
JOEY大大您好
請問platform_driver_register而不使用register_netdev的原因是核心版本嗎??
Joey [ 回復於2011-05-03 20:04 郵箱 | 編輯 刪除 ]
好像沒差....
platform_driver_register只是把hardware setting宣告在platformxxx.c那隻檔案裡面,便於管理...

register_netdev也可以,但就如你所說的,kernel API每版都有變化,所以維護比較麻煩
tmac [ 2011-05-15 01:12 | 回覆 | 編輯 刪除 ]
謝謝    JOEY大大!!
不好意思想再請教一個問題,如果想用NAPI這個機制,但卻沒有實際的硬體中斷去觸發,不知道是否可以做到模擬NAPI功能??
謝謝!!(小弟剛摸driver不久,觀念比較沒那麼好,敬請見諒)
  • 1 
發表評論
暱 稱: 密 碼:
網 址: E - mail:
驗證碼: 驗證碼圖片 選 項:
頭 像:
內 容: