1、硬件環境
intel82546:PHY與MAC集成在一塊兒的PCI網卡芯片,很強大
bcm5461: PHY芯片,與之對應的MAC是TSEC
TSEC: Three Speed Ethernet Controller,三速以太網控制器,PowerPc 架構CPU裏面的MAC模塊
注意,TSEC內部有DMA子模塊
話說如今的CPU愈來愈牛叉了,什麼功能都往裏面加,最多見的如MAC功能。
TSEC只是MAC功能模塊的一種,其餘架構的cpu也有和TSEC相似的MAC功能模塊。
這些集成到CPU芯片上的功能模塊有個學名,叫平臺設備,即 platform device。
2、網絡收包原理
網絡驅動收包大體有3種狀況:
no NAPI:mac每收到一個以太網包,都會產生一個接收中斷給cpu,即徹底靠中斷方式來收包
缺點是當網絡流量很大時,cpu大部分時間都耗在了處理mac的中斷。
netpoll:在網絡和I/O子系統尚不能完整可用時,模擬了來自指定設備的中斷,即輪詢收包。
缺點是實時性差
NAPI: 採用 中斷 + 輪詢 的方式:mac收到一個包來後會產生接收中斷,可是立刻關閉。
直到收夠了netdev_max_backlog個包(默認300),或者收完mac上全部包後,纔再打開接收中斷
經過sysctl來修改 net.core.netdev_max_backlog
或者經過proc修改 /proc/sys/net/core/netdev_max_backlog
下面只寫內核配置成使用NAPI的狀況,只寫TSEC驅動。(非NAPI的狀況和PCI網卡驅動 之後再說)
內核版本 linux 2.6.24
3、NAPI 相關數據結構
每一個網絡設備(MAC層)都有本身的net_device數據結構,這個結構上有napi_struct。
每當收到數據包時,網絡設備驅動會把本身的napi_struct掛到CPU私有變量上。
這樣在軟中斷時,net_rx_action會遍歷cpu私有變量的poll_list,
執行上面所掛的napi_struct結構的poll鉤子函數,將數據包從驅動傳到網絡協議棧。
4、內核啓動時的準備工做
4.1 初始化網絡相關的全局數據結構,並掛載處理網絡相關軟中斷的鉤子函數
start_kernel()
--> rest_init()
--> do_basic_setup()
--> do_initcall
-->net_dev_init
__init net_dev_init()
{
//每一個CPU都有一個CPU私有變量 _get_cpu_var(softnet_data)
//_get_cpu_var(softnet_data).poll_list很重要,軟中斷中須要遍歷它的
for_each_possible_cpu(i) {
struct softnet_data *queue;
queue = &per_cpu(softnet_data, i);
skb_queue_head_init(&queue->input_pkt_queue);
queue->completion_queue = NULL;
INIT_LIST_HEAD(&queue->poll_list);
queue->backlog.poll = process_backlog;
queue->backlog.weight = weight_p;
}
open_softirq(NET_TX_SOFTIRQ, net_tx_action, NULL); //在軟中斷上掛網絡發送handler
open_softirq(NET_RX_SOFTIRQ, net_rx_action, NULL); //在軟中斷上掛網絡接收handler
}
4.2 加載網絡設備的驅動
NOTE:這裏的網絡設備是指MAC層的網絡設備,即TSEC和PCI網卡(bcm5461是phy)
在網絡設備驅動中建立net_device數據結構,並初始化其鉤子函數 open(),close() 等
掛載TSEC的驅動的入口函數是 gfar_probe
// 平臺設備 TSEC 的數據結構
static struct platform_driver gfar_driver = {
.probe = gfar_probe,
.remove = gfar_remove,
.driver = {
.name = "fsl-gianfar",
},
};
int gfar_probe(struct platform_device *pdev)
{
dev = alloc_etherdev(sizeof (*priv)); // 建立net_device數據結構
dev->open = gfar_enet_open;
dev->hard_start_xmit = gfar_start_xmit;
dev->tx_timeout = gfar_timeout;
dev->watchdog_timeo = TX_TIMEOUT;
#ifdef CONFIG_GFAR_NAPI
netif_napi_add(dev, &priv->napi,gfar_poll,GFAR_DEV_WEIGHT); //軟中斷裏會調用poll鉤子函數
#endif
#ifdef CONFIG_NET_POLL_CONTROLLER
dev->poll_controller = gfar_netpoll;
#endif
dev->stop = gfar_close;
dev->change_mtu = gfar_change_mtu;
dev->mtu = 1500;
dev->set_multicast_list = gfar_set_multi;
dev->set_mac_address = gfar_set_mac_address;
dev->ethtool_ops = &gfar_ethtool_ops;
}
5、啓用網絡設備
5.1 用戶調用ifconfig等程序,而後經過ioctl系統調用進入內核
socket的ioctl()系統調用
--> sock_ioctl()
--> dev_ioctl() //判斷SIOCSIFFLAGS
--> __dev_get_by_name(net, ifr->ifr_name) //根據名字選net_device
--> dev_change_flags() //判斷IFF_UP
--> dev_open(net_device) //調用open鉤子函數
對於TSEC來講,掛的鉤子函數是 gfar_enet_open(net_device)
5.2 在網絡設備的open鉤子函數裏,分配接收bd,掛中斷ISR(包括rx、tx、err),對於TSEC來講
gfar_enet_open
--> 給Rx Tx Bd 分配一致性DMA內存
--> 把Rx Bd的「EA地址」賦給數據結構,物理地址賦給TSEC寄存器
--> 把Tx Bd的「EA地址」賦給數據結構,物理地址賦給TSEC寄存器
--> 給 tx_skbuff 指針數組 分配內存,並初始化爲NULL
--> 給 rx_skbuff 指針數組 分配內存,並初始化爲NULL
--> 初始化Tx Bd
--> 初始化Rx Bd,提早分配存儲以太網包的skb,這裏使用的是一次性dma映射
(注意:#define DEFAULT_RX_BUFFER_SIZE 1536保證了skb能存一個以太網包)
rxbdp = priv->rx_bd_base;
for (i = 0; i < priv->rx_ring_size; i++) {
struct sk_buff *skb = NULL;
rxbdp->status = 0;
//這裏真正分配skb,而且初始化rxbpd->bufPtr, rxbdpd->length
skb = gfar_new_skb(dev, rxbdp);
priv->rx_skbuff[i] = skb;
rxbdp++;
}
rxbdp--;
rxbdp->status |= RXBD_WRAP; // 給最後一個bd設置標記WRAP標記
--> 註冊TSEC相關的中斷handler: 錯誤,接收,發送
request_irq(priv->interruptError, gfar_error, 0, "enet_error", dev)
request_irq(priv->interruptTransmit, gfar_transmit, 0, "enet_tx", dev)//包發送完
request_irq(priv->interruptReceive, gfar_receive, 0, "enet_rx", dev) //包接收完
-->gfar_start(net_device)
// 使能Rx、Tx
// 開啓TSEC的 DMA 寄存器
// Mask 掉咱們不關心的中斷event
最終,TSEC相關的Bd等數據結構應該是下面這個樣子的
6、中斷裏接收以太網包
TSEC的RX已經使能了,網絡數據包進入內存的流程爲:
網線 --> Rj45網口 --> MDI 差分線
--> bcm5461(PHY芯片進行數模轉換) --> MII總線
--> TSEC的DMA Engine 會自動檢查下一個可用的Rx bd
--> 把網絡數據包 DMA 到 Rx bd 所指向的內存,即skb->data
接收到一個完整的以太網數據包後,TSEC會根據event mask觸發一個 Rx 外部中斷。
cpu保存現場,根據中斷向量,開始執行外部中斷處理函數do_IRQ()
do_IRQ 僞代碼
{
上半部處理硬中斷
查看中斷源寄存器,得知是網絡外設產生了外部中斷
執行網絡設備的rx中斷handler(設備不一樣,函數不一樣,但流程相似,TSEC是gfar_receive)
1. mask 掉 rx event,再來數據包就不會產生rx中斷
2. 給napi_struct.state加上 NAPI_STATE_SCHED 狀態
3. 掛網絡設備本身的napi_struct結構到cpu私有變量_get_cpu_var(softnet_data).poll_list
4. 觸發網絡接收軟中斷
下半部處理軟中斷
依次執行全部軟中斷handler,包括timer,tasklet等等
執行網絡接收的軟中斷handler net_rx_action
1. 遍歷cpu私有變量_get_cpu_var(softnet_data).poll_list
2. 取出poll_list上面掛的napi_struct 結構,執行鉤子函數napi_struct.poll()
(設備不一樣,鉤子函數不一樣,流程相似,TSEC是gfar_poll)
3. 若poll鉤子函數處理完全部包,則打開rx event mask,再來數據包的話會產生rx中斷
4. 調用napi_complete(napi_struct *n)
把napi_struct 結構從_get_cpu_var(softnet_data).poll_list 上移走
同時去掉 napi_struct.state 的 NAPI_STATE_SCHED 狀態
}
6.1 TSEC的接收中斷處理函數
gfar_receive
{
#ifdef CONFIG_GFAR_NAPI
// test_and_set當前net_device的napi_struct.state 爲 NAPI_STATE_SCHED
// 在軟中斷裏調用 net_rx_action 會檢查狀態 napi_struct.state
if (netif_rx_schedule_prep(dev, &priv->napi)) {
tempval = gfar_read(&priv->regs->imask);
tempval &= IMASK_RX_DISABLED; //mask掉rx,再也不產生rx中斷
gfar_write(&priv->regs->imask, tempval);
// 將當前net_device的 napi_struct.poll_list 掛到
// CPU私有變量__get_cpu_var(softnet_data).poll_list 上,並觸發軟中斷
// 因此,在軟中斷中調用 net_rx_action 的時候,就會執行當前net_device的
// napi_struct.poll()鉤子函數,即 gfar_poll()
__netif_rx_schedule(dev, &priv->napi);
}
#else
gfar_clean_rx_ring(dev, priv->rx_ring_size);
#endif
}
6.2 網絡接收軟中斷net_rx_action
net_rx_action()
{
struct list_head *list = &__get_cpu_var(softnet_data).poll_list;
//經過 napi_struct.poll_list, 將N多個 napi_struct 連接到一條鏈上
//經過 CPU私有變量,咱們找到了鏈頭,而後開始遍歷這個鏈
int budget = netdev_budget; //這個值就是 net.core.netdev_max_backlog,經過sysctl來修改
while (!list_empty(list)) {
struct napi_struct *n;
int work, weight;
local_irq_enable();
//從鏈上取一個 napi_struct 結構(接收中斷處理函數里加到鏈表上的,如gfar_receive)
n = list_entry(list->next, struct napi_struct, poll_list);
weight = n->weight;
work = 0;
if (test_bit(NAPI_STATE_SCHED, &n->state)) //檢查狀態標記,此標記在接收中斷里加上的
work = n->poll(n, weight); //使用NAPI的話,使用的是網絡設備本身的napi_struct.poll
//對於TSEC是,是gfar_poll
WARN_ON_ONCE(work > weight);
budget -= work;
local_irq_disable();
if (unlikely(work == weight)) {
if (unlikely(napi_disable_pending(n)))
__napi_complete(n); //操做napi_struct,把去掉NAPI_STATE_SCHED狀態,從鏈表中刪去
else
list_move_tail(&n->poll_list, list);
}
netpoll_poll_unlock(have);
}
out:
local_irq_enable();
}
static int gfar_poll(struct napi_struct *napi, int budget)
{
struct gfar_private *priv = container_of(napi, struct gfar_private, napi);
struct net_device *dev = priv->dev; //TSEC對應的網絡設備
int howmany;
//根據dev的rx bd,獲取skb並送入協議棧,返回處理的skb的個數,即以太網包的個數
howmany = gfar_clean_rx_ring(dev, budget);
// 下面這個判斷比較有講究的
// 收到的包的個數小於budget,表明咱們在一個軟中斷裏就全處理完了,因此打開 rx硬中斷
// 要是收到的包的個數大於budget,表示一個軟中斷裏處理不完全部包,那就不打開 rx硬中斷,
// 這次軟中斷的下一輪循環裏再接着處理,直到包處理完(即howmany<budget),再打開 rx硬中斷
if (howmany < budget) {
netif_rx_complete(dev, napi);
gfar_write(&priv->regs->rstat, RSTAT_CLEAR_RHALT);
//打開 rx 硬中斷,rx 硬中斷是在gfar_receive()中被關閉的
gfar_write(&priv->regs->imask, IMASK_DEFAULT);
}
return howmany;
}
gfar_clean_rx_ring(dev, budget)
{
bdp = priv->cur_rx;
while (!((bdp->status & RXBD_EMPTY) || (--rx_work_limit < 0))) {
rmb();
skb = priv->rx_skbuff[priv->skb_currx]; //從rx_skbuff[]中獲取skb
howmany++;
dev->stats.rx_packets++;
pkt_len = bdp->length - 4; //從length中去掉以太網包的FCS長度
gfar_process_frame(dev, skb, pkt_len);
dev->stats.rx_bytes += pkt_len;
dev->last_rx = jiffies;
bdp->status &= ~RXBD_STATS; //清rx bd的狀態
skb = gfar_new_skb(dev, bdp); // Add another skb for the future
priv->rx_skbuff[priv->skb_currx] = skb;
if (bdp->status & RXBD_WRAP) //更新指向bd的指針
bdp = priv->rx_bd_base; //bd有WARP標記,說明是最後一個bd了,須要「繞回來」
else
bdp++;
priv->skb_currx = (priv->skb_currx + 1) & RX_RING_MOD_MASK(priv->rx_ring_size);
}
priv->cur_rx = bdp; /* Update the current rxbd pointer to be the next one */
return howmany;
}
gfar_process_frame()
-->skb->protocol = eth_type_trans(skb, dev); //肯定網絡層包類型,IP、ARP、VLAN等等
-->RECEIVE(skb) //調用netif_receive_skb(skb)進入協議棧
#ifdef CONFIG_GFAR_NAPI
#define RECEIVE(x) netif_receive_skb(x)
#else
#define RECEIVE(x) netif_rx(x)
#endif
------------------------------------ 華麗的分割線 ---------------------------------------
呼,netif_receive_skb(skb) 可算到協議棧了,歇會兒....
以太網的FCS會在網卡中斷(如gfar_clean_rx_ring)中忽略掉
/* Remove the FCS from the packet length */
pkt_len = bdp->length - 4;
至於填充數據,是在協議棧中被忽略掉的,好比ip協議ip_rcv()
/* Our transport medium may have padded the buffer out. Now we know it
* is IP we can trim to the true length of the frame.
* Note this now means skb->len holds ntohs(iph->tot_len).
*/
if (pskb_trim_rcsum(skb, len)) {
IP_INC_STATS_BH(IPSTATS_MIB_INDISCARDS);
goto drop;
}html
轉載自http://blog.chinaunix.net/uid-24148050-id-464587.htmllinux