16.4 網絡設備的打開與釋放
網絡設備打開:
使能設備使用的硬件資源,申請I/O區域,中斷和DMA通道等。
調用Linux內核提供的netif_start_queue()函數,激活設備發送隊列。
網絡設備關閉:
調用Linux內核提供的netif_stop_queue()函數,停止設備傳輸包。
釋放設備所使用的I/O區域,中斷和DMA資源。
<pre name="code" class="cpp">
/* 網絡設備打開和釋放函數模板 */
int xxx_open(struct net_device *dev)
{
/* 申請端口,IRQ等,類似於fops->open */
ret = request_irq(dev->irq, &xxx_interrupt, 0, dev->name, dev);
...
netif_start_queue(dev);
...
}
<pre name="code" class="plain">
int xxx_release(struct net_device *dev)
{
/* 釋放端口,IRQ等,類似於 fops->close */
free_irq(dev->irq, dev);
...
netif_stop_queue(dev); /* can't transmit any more */
...
}
16.5 數據發送流程
Linux網絡子系統在發送數據包時,會調用驅動程序提供的hard_start_transmit()函數,該函數用於啓動數據包的發送。
網絡設備驅動完成數據包發送的流程如下:
(1)網絡設備驅動程序從上層協議傳遞過來的sk_buff參數獲得數據包的有效數據和長度,將有效數據放入臨時緩衝區。
(2)對於以太網,如果有效數據的長度小於以太網衝突檢測所要求數據幀的最小長度ETH_ZLEN,則臨時緩衝區的末尾填充0.
(3)設置硬件的寄存器,驅動網絡設備進行數據發送操作。
/* 網絡設備驅動程序的數據包發送函數模板 */
int xxx_tx(struct sk_buff *skb, struct net_device *dev)
{
int len;
char *data , shortpkt[ETH_ZLEN];
if( xxx_send_available(...) ){ /* 發送隊列未滿,可以發送 */
/* 獲得有效數據指針和長度 */
data = skb->data;
len = skb->len;
if( len < ETH_ZLEN ){
/* 如果幀長小於以太網最小長度,補0 */
memset(shortpkt, 0, ETH_ZLEN);
len = ETH_ZLEN;
data = shortpkt;
}
dev->trans_start = jiffies; /* 記錄發送實踐戳 */
/* 設置硬件寄存器讓硬件把數據包發送出去 */
xxx_hw_tx(data, len, dev);
...
}else{
netif_stop_queue(dev); //當發送隊列爲滿或因其他原因來不及發送當前上層傳下來的包,則調用次函數阻止上層繼續向網絡設備驅動傳輸數據包
... //當忙於發送的數據包被髮送完成後,TX結束的中斷處理中,應該調用netif_wake_queue()喚醒被阻塞的上層,以啓動它繼續向網絡設備驅動傳輸數據包
}
}
當數據傳輸超時時,意味着當前的發送操作失敗或硬件已陷入未知狀態,此時,數據包發送超時處理函數xxx_tx_timeout()將被調用,這個函數也需要調用Linux內核提供的netif_wake_queue()函數重啓設備發送隊列。
/* 網絡設備驅動程序的數據包發送超時函數 */
void xxx_tx_timeout(struct net_device *dev)
{
...
netif_wake_queue(dev); //重啓設備發送隊列
}
從上文可知,netif_wake_queue() 和 netif_stop_queue() 是數據發送流程中要調用的兩個非常重要的函數,分別用於喚醒和阻止上層向下傳送數據包,它們的原型定義在 include/linux/netdevice.h,如下:
static inline void netif_wake_queue(struct net_device *dev):
static inline void netif_stop_queue(struct net_device *dev):
16.6 數據接收流程
網絡設備接收數據的主要方式是由中斷引發設備的中斷處理函數。中斷處理函數判斷中斷類型,如果爲接收中斷,則讀取接收到的數據,分配sk_buff數據結構和數據緩衝區,將接收到的數據複製到數據緩衝區,並調用netif_rx()將sk_buff傳遞給上層協議。
/* 網絡設備驅動的中斷處理函數 */
static void xxx_interrupt(int irq, void *dev_id)
{
...
switch(status & ISQ_EVENT_MASK){
case ISQ_RECEIVER_EVENT:
/* 獲取數據包 */
xxx_rx(dev);
break;
/* 其他類型的中斷 */
}
}
<pre name="code" class="cpp">
static void xxx_rx(struct xxx_device *dev)
{
...
length = get_rev_len(...);
/* 分配新的套接字緩衝區 */
skb = dev_alloc_skb(length + 2);
skb_reserve(skb, 2); //對齊
skb->dev = dev;
/* 讀取硬件上接收到的數據 */、
insw(ioaddr + RX_FRAME_PORT, skb_put(skb, length), length >> 1);
if( length & 1 )
skb->data[length - 1] = inw(ioaddr + RX_FRAME_PORT);
/* 獲取上層協議類型 */
skb->protocol = eth_type_trans(skb, dev);
/* 把數據包遞交給上層 */
netif_rx(skb);
/* 記錄接收實踐戳 */
dev->last_rx = jiffies;
...
}
如果是NAPI兼容的設備驅動,則可以通過poll方式接收數據包,這種情況下,我們需要爲設備驅動提供作爲netif_napi_add()參數的xxx_poll()函數