1. 程式人生 > 實用技巧 >Linux網路裝置驅動之引數設定和統計資料(八)

Linux網路裝置驅動之引數設定和統計資料(八)

  網路裝置的驅動程式提供一些供系統對裝置的引數進行設定 或 讀取裝置相關資訊的方法。

  當用戶呼叫 ioctl() 函式,並指定 SIOCSIFHWADDR 命令時,意味著要設定這個裝置的 MAC 地址。設定網路裝置的 MAC 地址可用如下程式碼所示的模板:

 1 /*
 2  *  設定網路裝置的 MAC 地址
 3  */
 4 
 5 static int set_mac_address(struct net_device *dev, void *addr)
 6 {
 7     if (netif_running(dev))
 8         return -EBUSY;
 9
10 /* 設定乙太網的 MAC 地址 */ 11 xxx_set_mac(dev, addr); 12 13 return 0; 14 }

  以上程式首先用 netif_running() 巨集判斷裝置是否正在執行,如果是,則意味著裝置忙,此時不允許設定 MAC 地址;否則,呼叫 xxx_set_mac() 函式在網路介面卡硬體內寫入新的 MAC 地址。這要求裝置在硬體上支援 MAC 地址的修改,而實際上,許多裝置並不提供修改 MAC 地址的介面。

  netif_running() 巨集的定義為:

 1 /**
 2  *    netif_running - test if up
3 * @dev: network device 4 * 5 * Test if the device has been brought up. 6 */ 7 static inline bool netif_running(const struct net_device *dev) 8 { 9 return test_bit(__LINK_STATE_START, &dev->state); 10 }

  當用戶呼叫 ioctl() 函式時,若命令為 SIOCSIFMAP (如在控制檯中執行網路配置命令 ifconfig 就會引發這一呼叫),系統會呼叫驅動程式的 set_config() 函式。

  系統會向 set_config() 函式傳遞一個 ifmap 結構體,該結構體主要包含使用者欲設定的裝置要使用的 I/O 地址、中斷等資訊。注意,並不是 ifmap 結構體中給出的所有修改都是可以接受的。實際上,大多數裝置並不適合包含 set_config() 函式。set_config() 函式的例子如以下程式碼所示:

 1 /**
 2  * 網路裝置驅動的 set_config 函式模板
 3  */
 4 
 5 static int xxx_config(struct net_device *dev, struct ifmap *map)
 6 {
 7     if (netif_running(dev))      /* 不能設定一個正在執行狀態的裝置哦 */
 8         return -EBUSY;
 9 
10     /* 假設不允許改變 I/O 地址 */
11     if (map->base_addr != dev->base_addr) {
12         printk(KERN_WARNING "xxx: Can't change I/O address\n");
13         return -EOPNOTSUPP;    
14     }
15 
16     /* 假設允許改變 IRQ */
17     if(map->irq != dev->irq)
18         dev->irq = map->irq;
19 
20     return 0;
21 }

  上述程式碼中的 set_config() 函式接受 IRQ 的修改,拒絕裝置 I/O 地址的修改。具體的裝置是否接受這些資訊的修改,要視硬體的設計而定。

  如果使用者呼叫 ioctl() 時,命令型別在 SIOCDEVPRIVATE 和 SIOCDEVPRIVATE+15 之間。系統會呼叫驅動程式的 do_ioctl() 函式,以進行裝置專用資料的設定。這個設定在大多數情況下也不需要。

  驅動程式還應提供 get_stats() 函式以向用戶反饋裝置狀態和統計資訊,該函式返回的是一個 net_device_stats 結構體,程式碼如下所示:

1 /**
2  *  網路裝置驅動的 get_stats() 函式模板
3  */
4 
5 struct net_device_stats * xxx_stats(struct net_device *dev)
6 {
7     ···
8     return &dev->stats;
9 }

  有的網絡卡硬體比較強大,可以從硬體的暫存器中讀出一些統計資訊,如 rx_missed_errors、tx_aborted_errors、rx_dropped、rx_length_errors等。這個時候,我們應該從硬體暫存器讀取統計資訊,填充到 net_device 的 stats 欄位中,並返回。具體例子參見 drivers/net/ethernet/adaptec/starfire.c 中的 get_stats() 函式。

  net_device_stats 結構體定義在核心的 include/linux/netdevice.h 檔案中,它包含了比較完整的統計資訊,如以下程式碼所示:

 1 /*
 2  *    Old network device statistics. Fields are native words
 3  *    (unsigned long) so they can be read and written atomically.
 4  */
 5 
 6 struct net_device_stats {
 7     unsigned long    rx_packets;          /* 收到的資料包數 */
 8     unsigned long    tx_packets;          /* 傳送的資料包數 */
 9     unsigned long    rx_bytes;            /* 收到的位元組數 */
10     unsigned long    tx_bytes;            /* 傳送的位元組數 */
11     unsigned long    rx_errors;           /* 收到的錯誤資料包數 */
12     unsigned long    tx_errors;           /* 發生傳送錯誤的資料包數 */
13     unsigned long    rx_dropped;
14     unsigned long    tx_dropped;
15     unsigned long    multicast;
16     unsigned long    collisions;
17     unsigned long    rx_length_errors;
18     unsigned long    rx_over_errors;
19     unsigned long    rx_crc_errors;
20     unsigned long    rx_frame_errors;
21     unsigned long    rx_fifo_errors;
22     unsigned long    rx_missed_errors;
23     unsigned long    tx_aborted_errors;
24     unsigned long    tx_carrier_errors;
25     unsigned long    tx_fifo_errors;
26     unsigned long    tx_heartbeat_errors;
27     unsigned long    tx_window_errors;
28     unsigned long    rx_compressed;
29     unsigned long    tx_compressed;
30 };

  上述程式碼列出了 net_device_stats 包含的所有統計資訊,包含主專案統計資訊以及子專案統計資訊。

  net_device_stats 結構體已經內嵌在與網路裝置對應的 net_device 結構體中,而其中統計資訊的修改則應該在裝置驅動的與傳送和接收相關的具體函式中完成,這些函式包括中斷處理程式、資料包傳送函式、資料包傳送超時函式和資料包接收相關函式等。我們應該在這些函式中新增相應的程式碼,如下所示:

 1 /**
 2  *  net_device_stats 結構體中統計資訊的維護
 3  */
 4 
 5 /* 傳送超時函式 */
 6 void xxx_tx_timeout(struct net_device *dev)
 7 {
 8     ···
 9     dev->stats.tx_errors++;    /* 傳送錯誤包數加1 */
10     ···   
11 }
12 
13 /* 中斷處理函式 */
14 static void xxx_interrupt(int irq, void *dev_id)
15 {
16     struct net_device *dev = dev_id;
17     switch (status & ISQ_EVENT_MASK) {
18      ···   
19     case ISQ_TRANSMITTER_EVENT:
20         dev->stats.tx_packets++;    /* 資料包傳送成功,tx_packets 資訊加 1 */
21         netif_wake_queue(dev);      /* 通知上層協議 */
22         if ((status & (TX_OK | TX_LOST_CRS | TX_SQE_ERROR |
23                TX_LATE_COL | TX_16_COL)) != TX_OK) {   /* 讀取硬體上的出錯標誌 */
24             /* 根據錯誤的不同情況,對 net_device_stats 的不同成員加 1  */
25             if ((status & TX_OK) == 0) 
26                 dev->stats.tx_errors++;
27             if (status & TX_LOST_CRS)
28                 dev->stats.tx_carrier_errors++;
29             if (status & TX_SQE_ERROR)
30                 dev->stats.tx_heartbeat_errors++;
31             if (status & TX_LATE_COL)
32                 dev->stats.tx_window_errors++;
33             if (status & TX_16_COL)
34                 dev->stats.tx_aborted_errors++;
35         }
36         break;
37 
38     case ISQ_RX_MISS_EVENT:
39         dev->stats.rx_missed_errors += (status >> 6);
40         break;
41     
42     case ISQ_TX_COL_EVENT:
43         dev->stats.collisions += (status >> 6);
44         break;
45     }
46 }               

  上述程式碼第 9 行意味著在傳送資料包超時時,將發生傳送錯誤的資料包數加 1。第17 ~ 44 行則意味著當網路裝置中斷產生時,中斷處理程式讀取硬體的相關資訊以決定修改 net_device_stats 統計資訊中的哪些專案和子專案,並將相應的專案加 1。