1. 程式人生 > >Linux核心Socket CAN中文文件

Linux核心Socket CAN中文文件

轉載地址:https://blog.csdn.net/zhangxiaopeng0829/article/details/7646639

自己在年假中空閒之餘翻譯的核心中Socket CAN的文件,原文地址在:

http://lxr.linux.no/linux+v2.6.34/Documentation/networking/can.txt

但是這篇文件沒有涉及廣播管理協議套接字 (SOCK_DGRAM) 的內容。

另外一篇比較好的Socket CAN的英文文件是(詳細介紹了廣播管理協議套接字):

Low Level CAN Framework Application Programmers Interface

http://www.brownhat.org/docs/socketcan/llcf-api.html#SECTION00051000000000000000

自己暫時沒時間翻譯這篇文章了,有空再說吧。

 

自己英文水平有限,希望高人指正。

================================================================================

 

這篇文章主要針對can協議簇(aka socket can)

這篇文章包含以下內容:

===============

1 概述--什麼是Socket CAN?

 

2 動機--為什麼使用socket API介面?

 

3 Socket CAN詳解

     3.1 接收佇列

     3.2 傳送幀的本地迴環

     3.3 網路安全相關

     3.4 網路故障監測

 

4 如何使用Socket CAN

     4.1 使用can_filter的原始套接字 (RAW socket)

          4.1.1 原始套接字選項 CAN_RAW_FILTER

          4.1.2 原始套接字選項 CAN_RAW_ERR_FILTER

          4.1.3 原始套接字選項 CAN_RAW_LOOPBACK

          4.1.4 原始套接字選項 CAN_RAW_RECV_OWN_MSGS

     4.2 廣播管理協議套接字 (SOCK_DGRAM)

     4.3 面向連線的傳輸協議 (SOCK_SEQPACKET)

     4.4 無連線的傳輸協議  (SOCK_DGRAM)

 

5 Socket CAN核心模組

     5.1 can.ko模組的引數

     5.2 procfs介面

     5.3 寫一個自己的CAN協議模組

 

6 CAN網路驅動

     6.1 常見設定

     6.2 傳送幀的本地迴環

     6.3 CAN控制器的硬體過濾

     6.4 虛擬的CAN驅動 (vcan)

     6.5 CAN網路裝置驅動介面

          6.5.1 Netlink介面--設定/獲取裝置屬性

          6.5.2 設定CAN的位元_時序

          6.5.3 啟動和停止CAN網路裝置

     6.6 支援Socket CAN的硬體

 

7 學習Socket CAN的相關資源

 

8 貢獻者名單

 

==============現在開始===================

1. 概述--什麼是Socket CAN?

==================

 

socketcan子系統是在Linux下CAN協議(Controller Area Network)實現的一種實現方法。 CAN是一種在世界範圍內廣泛用於自動控制、嵌入式裝置和汽車領域的網路技術。Linux下最早使用CAN的方法是基於字元裝置來實現的,與之不同的是Socket CAN使用伯克利的socket介面和linux網路協議棧,這種方法使得can裝置驅動可以通過網路介面來呼叫。Socket CAN的介面被設計的儘量接近TCP/IP的協議,讓那些熟悉網路程式設計的程式設計師能夠比較容易的學習和使用。

 

2. 動機--為什麼使用socket API介面?

=======================

 

在Socket CAN之前Linux中已經有了一些CAN的實現方法,那為什麼還要啟動Socket CAN這個專案呢?大多數已經存在的實現方法僅僅作為某個具體硬體的裝置驅動,它們往往基於字元裝置並且提供的功能很少。那些方案通常是由一個針對具體硬體的裝置驅動提供的字元裝置介面來實現原始can幀的傳送和接收,並且直接和控制器硬體打交道。幀佇列和ISO-TP這樣的高層協議必須在使用者空間來實現。就像串列埠裝置介面一樣,大多數基於字元裝置的實現在同一時刻僅僅支援一個程序的訪問。如果更換了CAN控制器,那麼同時也要更換另一個裝置驅動,並且需要大多數應用程式重新調整以適應新驅動的API。

 

Socket CAN被設計用來克服以上種種不足。這種新的協議族實現了使用者空間的socket介面,它構建於Linux網路層之上,因此可以直接使用已有的佇列功能。CAN控制器的裝置驅動將自己作為一個網路設備註冊進Linux的網路層,CAN控制器收到的CAN幀可以傳輸給高層的網路協議和CAN協議族,反之,傳送的幀也會通過高層給CAN控制器。傳輸協議模組可以使用協議族提供的介面註冊自己,所以可以動態的載入和解除安裝多個傳輸協議。事實上,CAN核心模組不提供任何協議,也不能在沒有載入其它協議的情況下單獨使用。同一時間可以在相同或者不同的協議上開啟多個套接字,可以在相同或者不同的CAN ID上同時監聽和傳送(listen/send)。幾個同時監聽具有相同ID幀的套接字可以在匹配的幀到來後接收到相同的內容。如果一個應用程式希望使用一個特殊的協議(比如ISO-TP)進行通訊,只要在開啟套接字的時候選擇那個協議就可以了,接下來就可以讀取和寫入應用資料流了,根本無需關心CAN-ID和幀的結構等資訊。

 

 

使用字元裝置也可以讓使用者空間獲得類似的便利,但是這中解決方案在技術上不夠優雅,原因如下:

 

*複雜的操作。使用Socket CAN,只需要向socket(2)函式傳遞協議引數並使用bind(2)選擇CAN介面和CAN ID,基於字元裝置實現這樣的功能卻要使用ioctl(2)來完成所有的操作。

 

*無法程式碼複用。字元裝置無法使用Linux的網路佇列程式碼,所以必須為CAN 網路重寫這部分功能。

 

*缺乏抽象性。在大多數已經實現的字元裝置方案中,硬體相關的CAN控制器裝置驅動直接提供應用程式需要的字元裝置。在Unix系統中,無論對於字元裝置還是塊裝置,這都是不常見的。比如,你不會為某個串列埠、電腦上的某個音效卡、訪問磁帶和硬碟的SCSI/IDE控制器直接建立一個字元裝置。相反,你會將嚮應用程式提供統一字元裝置/塊裝置介面的功能交給一個抽象層來做,你自己僅僅提供硬體相關的裝置驅動介面。這種抽象是通過子系統來完成的,比如tty層子系統、音效卡子系統和SCSI/IDE子系統。

 

實現CAN裝置驅動最簡單的辦法就是直接提供一個字元裝置而不使用(或不完全使用)抽象層,大多數已經存在的驅動也是這麼做的。但是正確的方法卻要增加抽象層以支援各種功能,如註冊一個特定的CAN-ID,支援多次開啟的操作和這些操作之間的CAN幀複用,支援CAN幀複雜的佇列功能,還要提供註冊裝置驅動的API。然而使用Linux核心提供的網路框架將會大大簡化,這就是Socket CAN要做的。

 

在Linux中實現CAN功能最自然和合適的方式就是使用核心的網路框架。

 

3. Socket CAN詳解

============

 

就像第二章所講的那樣,使用Socket CAN的主要目的就是為使用者空間的應用程式提供基於Linux網路層的套接字介面。與廣為人知的TCP/IP協議以及乙太網不同,CAN匯流排沒有類似乙太網的MAC層地址,只能用於廣播。CAN ID僅僅用來進行匯流排的仲裁。因此CAN ID在總線上必須是唯一的。當設計一個CAN-ECU(Electronic Control Unit 電子控制單元)網路的時候,CAN-ID可以對映到具體的ECU。因此CAN-ID可以當作傳送源的地址來使用。

 

3.1 接收佇列

---------------

 

允許多個應用程式同時訪問網路導致了新的問題出現,那就是不同的應用程式可能會在同一個CAN網路介面上對具有相同CAN-ID的幀感興趣。Socket CAN的核心部分實現了Socket CAN的協議族,通過高效的接收佇列解決了這個問題。比如一個使用者空間的程式打開了一個原始CAN套接字,原始協議模組將向CAN套接字的核心模組申請使用者空間需要的一系列CAN-ID。Socket CAN的核心向CAN協議模組提供預約和解約CAN-ID的介面--can_rx_(un)register(),無論這個CAN-ID是針對一個具體的CAN介面還是所有已知的CAN介面(參考第5章)。


為了優化CPU的執行效率,每個裝置都對應一個接收佇列,這樣比較容易實現各種報文過濾規則。

3.2 傳送幀的本地迴環
-----------------

在其它種類的網路中,在相同或者不同網路節點上的應用程式都可以相互交換資料。
 

         ___   ___   ___                   _______   ___

        | _ | | _ | | _ |                 | _   _ | | _ |

        ||A|| ||B|| ||C||                 ||A| |B|| ||C||

        |___| |___| |___|                 |_______| |___|

          |     |     |                       |       |

        -----------------(1)- CAN bus -(2)---------------

 

 


請看上圖的兩個例子。為了保證應用程式A在兩個例子中能夠接收到同樣的例子(例2中A和B在同一個CAN裝置上),傳送出去的CAN幀需要能夠在本地迴環。

Linux下的網路裝置僅僅處理物理媒介上幀的傳送和接受。匯流排仲裁機制下,高優先順序的幀會將低優先順序的幀延後。為了正確反映實際的通訊活動,迴環必須在正確傳輸成功之後進行。如果CAN網路的硬體不支援迴環功能,一種低效的方案是使用Socket CAN核心部分來實現軟體迴環。具體的情況請參考6.2小節。

CAN網路的迴環功能是預設開啟的。由於RT-SocketCAN 的特殊需求,每個套接字的迴環功能可以被獨立關閉。CAN原始套接字的控制選項請參考4.1小節。

*當你在同一個節點上執行CAN分析命令“candump”或者“cansniffer”的時候就會發現迴環功能真的很有用。

3.3 網路安全相關
--------------------

CAN網路是一種現場匯流排,僅僅支援沒有路由和安全控制的廣播傳輸。大部分應用程式都需要要直接處理原始CAN幀,所以和其它型別的網路一樣,CAN網路對所有的使用者(而不僅僅是root使用者)訪問沒有任何限制。由於當前CAN_RAW和CAN_BCM的實現僅僅支援對CAN介面的讀寫,所以允許所有的使用者訪問CAN並不影響其它型別網路的安全性。為了使能非root使用者對CAN_RAW和CAN_BCM協議套接字的訪問,必須在編譯核心的時候選上Kconfig的CAN_RAW_USER/CAN_BCM_USER選項。

 

3.4 網路故障監測

--------------------

 

使用CAN匯流排的時候,可能會遇到物理和mac(media access control)層的問題。為了方便使用者分析物理收發器的硬體錯誤、匯流排仲裁錯誤和不同的ECU( Electrical Conversion Unit,電氣轉換裝置)引起的錯誤,對於底層(物理和mac層)的監測和記錄是至關重要的。擁有精確時間戳的錯誤監測對於診斷錯誤是非常重要的。基於以上原因,CAN介面的驅動可以可以選擇性的產生所謂的錯誤幀,這些幀以和其它的CAN幀一樣的方式傳遞給使用者程式。當一個物理層或者MAC層的錯誤被(CAN控制器)檢測到之後,驅動建立一個相應的錯誤幀。錯誤幀可以被應用程式通過CAN的過濾機制請求得到。過濾機制允許選擇需要的錯誤幀的型別。預設情況下,接收錯誤幀的功能是禁止的。

CAN錯誤幀的詳細格式定義在linux標頭檔案中:include/linux/can/error.h。

 

4. 如何使用Socket CAN

===============

 

就像TCP/IP協議一樣,在使用CAN網路之前你首先需要開啟一個套接字。CAN的套接字使用到了一個新的協議族,所以在呼叫socket(2)這個系統函式的時候需要將PF_CAN作為第一個引數。當前有兩個CAN的協議可以選擇,一個是原始套接字協議( raw socket protocol),另一個是廣播管理協議BCM(broadcast manager)。你可以這樣來開啟一個套接字:

 

     s = socket(PF_CAN, SOCK_RAW, CAN_RAW);

或者

     s = socket(PF_CAN, SOCK_DGRAM, CAN_BCM);

 

在成功建立一個套接字之後,你通常需要使用bind(2)函式將套接字繫結在某個CAN介面上(這和TCP/IP使用不同的IP地址不同,參見第3章)。在繫結 (CAN_RAW)或連線(CAN_BCM)套接字之後,你可以在套接字上使用read(2)/write(2),也可以使用send(2)/sendto(2)/sendmsg(2)和對應的recv*操作。當然也會有CAN特有的套接字選項,下面將會說明。

 

基本的CAN幀結構體和套接字地址結構體定義在include/linux/can.h:

 

/*

* 擴充套件格式識別符由 29 位組成。其格式包含兩個部分:11 位基本 ID、18 位擴充套件 ID。
* Controller Area Network Identifier structure
*
* bit 0-28     : CAN識別符 (11/29 bit)
* bit 29     : 錯誤幀標誌 (0 = data frame, 1 = error frame)
* bit 30     : 遠端傳送請求標誌 (1 = rtr frame)

* bit 31     :幀格式標誌 (0 = standard 11 bit, 1 = extended 29 bit)
*/
typedef __u32 canid_t;

 

struct can_frame { 

     canid_t can_id;  /* 32 bit CAN_ID + EFF/RTR/ERR flags */
      __u8    can_dlc; /* 資料長度: 0 .. 8 */
      __u8    data[8] __attribute__((aligned(8))); 

 };

 

結構體的有效資料在data[]陣列中,它的位元組對齊是64bit的,所以使用者可以比較方便的在data[]中傳輸自己定義的結構體和共用體。CAN匯流排中沒有預設的位元組序。在CAN_RAW套接字上呼叫read(2),返回給使用者空間的資料是一個struct can_frame結構體。

 

就像PF_PACKET套接字一樣,sockaddr_can結構體也有介面的索引,這個索引綁定了特定介面:

 

    struct sockaddr_can { 

          sa_family_t can_family;
          int         can_ifindex;
          union { 

                    /* transport protocol class address info (e.g. ISOTP) */
                    struct { canid_t rx_id, tx_id; } tp;
                    /* reserved for future CAN protocols address information */ 

          } can_addr; 

    };

 

指定介面索引需要呼叫ioctl()(比如對於沒有錯誤檢查CAN_RAW套接字):

 

    int s;

    struct sockaddr_can addr;

    struct ifreq ifr;

 

    s = socket(PF_CAN, SOCK_RAW, CAN_RAW);

 

    strcpy(ifr.ifr_name, "can0" );

    ioctl(s, SIOCGIFINDEX, &ifr);

 

    addr.can_family = AF_CAN;

    addr.can_ifindex = ifr.ifr_ifindex;

 

    bind(s, (struct sockaddr *)&addr, sizeof(addr));

 

    (..)

 

為了將套接字和所有的CAN介面繫結,介面索引必須是0。這樣套接字便可以從所有使能的CAN介面接收CAN幀。recvfrom(2)可以指定從哪個介面接收。在一個已經和所有CAN介面繫結的套接字上,sendto(2)可以指定從哪個介面傳送。

 

從一個CAN_RAW套接字上讀取CAN幀也就是讀取struct can_frame結構體:

 

    struct can_frame frame;

 

    nbytes = read(s, &frame, sizeof(struct can_frame));

 

    if (nbytes < 0) {

            perror("can raw socket read");

            return 1;

    }

 

    /* paranoid check ... */

    if (nbytes < sizeof(struct can_frame)) {

            fprintf(stderr, "read: incomplete CAN frame\n");

            return 1;

    }

 

    /* do something with the received CAN frame */

 

寫CAN幀也是類似的,需要用到write (2)函式:

 

    nbytes = write(s, &frame, sizeof(struct can_frame)); 

 

如果套接字跟所有的CAN介面都綁定了(addr.can_index = 0),推薦使用recvfrom(2)獲取資料來源介面的資訊:
 

    struct sockaddr_can addr;

    struct ifreq ifr;

    socklen_t len = sizeof(addr);

    struct can_frame frame;

 

    nbytes = recvfrom(s, &frame, sizeof(struct can_frame),

                      0, (struct sockaddr*)&addr, &len);

 

    /* get interface name of the received CAN frame */

    ifr.ifr_ifindex = addr.can_ifindex;

    ioctl(s, SIOCGIFNAME, &ifr);

    printf("Received a CAN frame from interface %s", ifr.ifr_name);

 

對於綁定了所有介面的套接字,向某個埠傳送資料必須指定介面的詳細資訊:

 

    strcpy(ifr.ifr_name, "can0");

    ioctl(s, SIOCGIFINDEX, &ifr);

    addr.can_ifindex = ifr.ifr_ifindex;

    addr.can_family  = AF_CAN;

 

    nbytes = sendto(s, &frame, sizeof(struct can_frame),

                    0, (struct sockaddr*)&addr, sizeof(addr));

 

4.1 使用can_filter的原始套接字 (RAW socket)

----------------------------------------------------

 

CAN_RAW套接字的用法和CAN字元裝置的用法是類似的。為了使用CAN套接字的新特性,在繫結原始套接字的時候將會預設開啟以下特性:

 

- filter將會接收所有的資料

- 套接字僅僅接收有效的資料幀(=> no error frames)

- 傳送幀的迴環功能被開啟(參見 3.2節)

- (迴環模式下)套接字不接收它自己傳送的幀

 

這些特性的設定可以在繫結之前和之後修改。為了使用CAN_RAW套接字相關的選項,必須包含<linux/can/raw.h>。

 

  • 4.1.1 原始套接字選項 CAN_RAW_FILTER

CAN_RAW套接字的接收可以使用CAN_RAW_FILTER套接字選項指定的多個過濾規則(過濾器)來過濾。

 

過濾規則(過濾器)的定義在 include/linux/can.h中:

 

    struct can_filter {

            canid_t can_id;

            canid_t can_mask;

    };

 

過濾規則的匹配:

 

    <received_can_id> & mask == can_id & mask

 

/*

#define CAN_INV_FILTER 0x20000000U /* to be set in can_filter.can_id */

#define CAN_ERR_FLAG   0x20000000U /* error frame */

*/

這和大家所熟知的CAN控制器硬體過濾非常相似。可以使用 CAN_INV_FILTER這個巨集將can_filter結構體的成員can_id中的位元位反轉。和CAN控制器的硬體過濾形成鮮明對比的是,使用者可以為每一個開啟的套接字設定多個獨立的過濾規則(過濾器):

 

/*

 /* valid bits in CAN ID for frame formats */

#define CAN_SFF_MASK 0x000007FFU /* 標準幀格式 (SFF) */
#define CAN_EFF_MASK 0x1FFFFFFFU /* 擴充套件幀格式 (EFF) */
#define CAN_ERR_MASK 0x1FFFFFFFU /* 忽略EFF, RTR, ERR標誌 */

 */

 

    struct can_filter rfilter[2];

 

    rfilter[0].can_id   = 0x123;

    rfilter[0].can_mask = CAN_SFF_MASK;

    rfilter[1].can_id   = 0x200;

    rfilter[1].can_mask = 0x700;

 

    setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter));

 

為了在指定的CAN_RAW套接字上禁用接收過濾規則,可以這樣:

 

  setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0);

 

在一些極端情況下不需要讀取資料,可以把過濾規則清零(所有成員設為0),這樣原始套接字就會忽略接收到的CAN幀。在這種僅僅傳送資料(不讀取)的應用中可以在核心中省略接收佇列,以此減少CPU的負載(雖然只能減少一點點)。

 

  • 4.1.2 原始套接字選項 CAN_RAW_ERR_FILTER

正如3.4節所說,CAN介面驅動可以選擇性的產生錯誤幀,錯誤幀和正常幀以相同的方式傳給應用程式。可能產生的錯誤被分為不同的種類,使用適當的錯誤掩碼可以過濾它們。為了註冊所有可能的錯誤情況,CAN_ERR_MASK(0x1FFFFFFFU)這個巨集可以用來作為錯誤掩碼。這個錯誤掩碼定義在linux/can/error.h。

 

    can_err_mask_t err_mask = ( CAN_ERR_TX_TIMEOUT | CAN_ERR_BUSOFF );

 

    setsockopt(s, SOL_CAN_RAW, CAN_RAW_ERR_FILTER,

               &err_mask, sizeof(err_mask));

 

  • 4.1.3 原始套接字選項 CAN_RAW_LOOPBACK

為了滿足眾多應用程式的需要,本地迴環功能預設是開啟的(詳細情況參考3.2節)。但是在一些嵌入式應用場景中(比如只有一個使用者在使用CAN匯流排),迴環功能可以被關閉(各個套接字之間是獨立的):

 

    int loopback = 0; /* 0 = disabled, 1 = enabled (default) */

 

    setsockopt(s, SOL_CAN_RAW, CAN_RAW_LOOPBACK, &loopback, sizeof(loopback));

 

  • 4.1.4 原始套接字選項 CAN_RAW_RECV_OWN_MSGS

在本地迴環功能開啟的情況下,所有的傳送幀都會被迴環到在相應CAN介面上註冊了同樣CAN-ID(和傳送幀的相同)的套接字上。傳送CAN幀的套接字被假設不想接收自己傳送的CAN幀,因此在傳送套接字上的迴環功能預設是關閉的。可以在需要的時候改變這一預設行為:

 

    int recv_own_msgs = 1; /* 0 = disabled (default), 1 = enabled */

 

    setsockopt(s, SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS,

               &recv_own_msgs, sizeof(recv_own_msgs));

 

(yll:下面三小節沒有內容)

4.2 廣播管理協議套接字 (SOCK_DGRAM)

-----------------------------------------------

 

4.3 面向連線的傳輸協議 (SOCK_SEQPACKET)

----------------------------------------------------

 

4.4 無連線的傳輸協議  (SOCK_DGRAM)

---------------------------------------------

 

5. Socket CAN核心模組

==============

 

CAN套接字的核心模組實現了PF_CAN協議族。CAN協議模組在核心模組執行後加載。CAN核心模組為協議模組提供了申請需要的ID的介面(參考3.1小節)。

 

5.1 can.ko模組的引數

-------------------------

 

- stats_timer: 為了計算CAN套接字的統計資訊(比如最近一秒的幀數和每秒最大的幀數),can.ko呼叫一個定時間隔為1秒的定時器,預設情況下這個定時器是開啟的。這個定時器也可以在模組引數中傳入stattimer=0來禁止。

 

- debug :  (removed since SocketCAN SVN r546)

 

5.2 procfs介面

-----------------

 

就像3.1節描述的那樣,CAN套接字核心藉助於一些帶有過濾規則的佇列向CAN協議模組傳遞接收到的CAN幀。可以在procfs中檢視這些接收佇列的的過濾規則和匹配規則的次數。所有的條目都包了裝置名和協議模組標識:

 

    [email protected]:~$ cat /proc/net/can/rcvlist_all
    

    receive list 'rx_all': (vcan3: no entry)
      (vcan2: no entry)
      (vcan1: no entry)
      device   can_id   can_mask  function  userdata   matches  ident

      vcan0     000    00000000  f88e6370  f6c6f400         0       raw

     (any: no entry)

     (yll補充:function是一個函式指標,userdata是一個void *指標,所以這兩個值打印出來沒有太大意義)

 

在這個例子中,這個應用程式接收所有vcan0上傳輸的資料.

 

     rcvlist_all - 沒有過濾規則的佇列

     rcvlist_eff - 擴充套件幀(EFF)的佇列

     rcvlist_err - 錯誤幀佇列

     rcvlist_fil - 通過過濾規則的佇列

     rcvlist_inv - 未通過過濾規則的佇列

     rcvlist_sff - 標準幀的佇列

 

在/proc/net/can中還有另外一些檔案:

 

     stats - CAN套接字核心的統計資訊(接收/傳送的幀數,匹配率等)

     reset_stats - 復位統計資訊

     version - 列印CAN套接字核心的版本和ABI的版本

 

5.3 寫一個自己的CAN協議模組

-----------------------------------

 

要在PF_CAN中增加一個新的協議,必須在include/linux/can.h中為新的協議增加相應的定義。包含include/linux/can.h這個檔案便可以使用增加的原型和定義。核心除了提供了註冊CAN協議和CAN裝置的通知列表的功能,也提供了在一個特定CAN介面上註冊感興趣的CAN幀或者傳送CAN幀的功能。

 

    can_rx_register   - 在一個特定介面上註冊希望接收到的CAN幀的資訊  (yll:這個函式的定義在核心的net/can/af_can.c中)

    can_rx_unregister - 登出上面的申請 

    can_send          - 傳送CAN幀(可以選擇是否開啟本地迴環) 

 

詳細的資訊請參考核心中的原始碼net/can/af_can.c、net/can/raw.c、net/can/bcm.c。

 

6. CAN網路驅動

==========

編寫一個CAN網路裝置驅動要比寫一個CAN字元裝置驅動要容易的多。和編寫其它網路裝置驅動類似,你只要處理以下事宜:

 

- TX :將套接字緩衝區的CAN幀傳送到CAN控制器

- RX :從CAN控制器的CAN幀讀取到套接字緩衝區

 

Documentation/networking/netdevices.txt中是網路裝置驅動的例子。下面將會介紹CAN網路裝置驅動的一些獨有特性。

 

6.1 常見設定

---------------

 

    dev->type  = ARPHRD_CAN; /* the netdevice hardware type */
    dev->flags = IFF_NOARP;  /* CAN has no arp */
    dev->mtu   = sizeof(struct can_frame);

 

結構體can_frame是PF_CAN協議族套接字緩衝區的陣列載體。

 

6.2 傳送幀的本地迴環

-------------------------

 

如3.2小節所述,CAN網路裝置驅動應該支援類似TTY裝置回顯功能的本地迴環功能。如果驅動支援這個功能,則要裝置IFF_ECHO標誌來防止PF_CAN核心回顯傳送幀(又稱迴環)。

 

    dev->flags = (IFF_NOARP | IFF_ECHO);

 

6.3 CAN控制器的硬體過濾

------------------------------

 

為了減小一些嵌入式系統的中斷負載,一些CAN控制器支援多個CAN-ID或者多個CAN-ID區間的過濾功能。硬體過濾功能在不同的控制器之間差異很大,並且不能同時滿足多個使用者的不同過濾需求。在單使用者應用中使用控制器的硬體過濾或許還有意義,但是在一個多使用者系統中驅動層的過濾將會影響所有使用者。PF_CAN核心內建的過濾規則集合允許對每個套接字獨立的設定多個過濾規則。因此使用硬體過濾屬於嵌入式系統中“手動調整”的範疇。從2002年開始筆者一直使用擁有四路SJA1000 CAN控制器的MPC603e @133MHz,匯流排負載雖然很高,但是到目前為止還沒有什麼問題。

 

6.4 虛擬的CAN驅動 (vcan)

------------------------------

 

和網路迴環裝置一樣,vcan提供一個虛擬的本地CAN介面。CAN中一個有效的地址包括:

 

- 一個有效的CAN識別符號(CAN ID)

- 這個CAN識別符號將要發往的匯流排(比如 can0).

 

所以在一般的應用場景中往往需要多個vcan介面。

 

vcan介面允許在沒有控制器硬體的情況下進行傳送和接收。vcan網路裝置的命名一般採用‘vcanX’,比如can1 vcan2等。當編譯為單獨的模組的時候,vcan驅動的模組名為vcan.ko。

 

vcan驅動從linux2.6.24開始支援netlink介面,使得建立vcan網路裝置變的可能。可以使用ip(8)命令工具管理vcan網路裝置的建立和移除:

 

 - 建立一個vcan網路介面:

        $ ip link add type vcan

 

- 使用給定的名字 'vcan42'建立 一個vcan網路介面:

       $ ip link add dev vcan42 type vcan

 

- 移除vcan網路介面'vcan42':

       $ ip link del vcan42  

 

6.5 CAN網路裝置驅動介面

------------------------------

 

CAN網路裝置驅動提供了進行安裝、配置和監控CAN網路裝置的介面。可以使用IPROUTE2工具集中的“ip”命令通過netlink介面配置CAN裝置,比如設定波特率。本章剩餘的部分將會簡介如何使用這一工具。另外這些介面使用一些通用的資料結構並且提供了一系列常用的功能,這些功能都是CAN網路裝置驅動需要用到的。請參考SJA1000或者MSCAN的驅動去了解如何使用它們。模組的名字是can-dev.ko。

  • 6.5.1 Netlink介面--設定/獲取裝置屬性

CAN裝置必須使用netlink來配置。在"include/linux/can/netlink.h"有對netlink訊息型別的定義和簡短描述。IPROUTE2工具集中的“ip”命令可以使用CAN的netlink支援,下面是使用示例:

 

- 設定CAN裝置屬性:

 

    $ ip link set can0 type can help

    Usage: ip link set DEVICE type can [ bitrate BITRATE [ sample-point SAMPLE-POINT] ] |

    [ tq TQ prop-seg PROP_SEG phase-seg1 PHASE-SEG1 phase-seg2 PHASE-SEG2 [ sjw SJW ] ]

    [ loopback { on | off } ]

    [ listen-only { on | off } ]

    [ triple-sampling { on | off } ]

    [ restart-ms TIME-MS ]

    [ restart ]

    Where: BITRATE       := { 1..1000000 } SAMPLE-POINT  := { 0.000..0.999 }

          TQ            := { NUMBER }

          PROP-SEG      := { 1..8 }

          PHASE-SEG1    := { 1..8 }

          PHASE-SEG2    := { 1..8 }

          SJW           := { 1..4 }

          RESTART-MS    := { 0 | NUMBER }

 

- 顯示CAN裝置的詳情和統計資訊:

 

    $ ip -details -statistics link show can0

 2: can0: <NOARP,UP,LOWER_UP,ECHO> mtu 16 qdisc pfifo_fast state UP qlen 10 

     link/can
      can <TRIPLE-SAMPLING> state ERROR-ACTIVE restart-ms 100
      bitrate 125000 sample_point 0.875
      tq 125 prop-seg 6 phase-seg1 7 phase-seg2 2 sjw 1
      sja1000: tseg1 1..16 tseg2 1..8 sjw 1..4 brp 1..64 brp-inc 1
      clock 8000000
      re-started bus-errors arbit-lost error-warn error-pass bus-off 

      41         17457      0          41         42         41
      RX: bytes  packets  errors  dropped overrun mcast 

      140859     17608    17457   0       0       0
      TX: bytes  packets  errors  dropped carrier collsns 

      861        112      0       41      0       0

 

下面是上面一些名詞的解釋:

 

    "<TRIPLE-SAMPLING>"

表示選中的CAN控制器的模式:LOOPBACK, LISTEN-ONLY, or TRIPLE-SAMPLING。

 

    "state ERROR-ACTIVE"

CAN控制器的當前狀態:"ERROR-ACTIVE", "ERROR-WARNING", "ERROR-PASSIVE", "BUS-OFF" or "STOPPED"

 

    "restart-ms 100"

自動重啟的延時時間。如果設為非零值, 在匯流排關閉的情況下,在設定的數量毫秒後CAN控制器被自動觸發。這個功能預設是關閉的。

 

    "bitrate 125000 sample_point 0.875"

使用bits/sec作為單位顯示位時間並顯示0.000~0.999的取樣點位置。如果核心中使能了統計位時間的功能(CONFIG_CAN_CALC_BITTIMING=y),位時間可以使用"bitrate"引數來設定。可選的"sample-point"也是可以配置的。預設使用0.000這個推薦值。

 

    "tq 125 prop-seg 6 phase-seg1 7 phase-seg2 2 sjw 1"

以ns為單位顯示時間份額(tq-time quanta)、傳播段(prop-seg : propagation segment)、相位緩衝段1和2(phase-seg:phase buffer),以tq為單位顯示同步跳轉寬度(sjw:synchronisation jump width)。這些變數允許定義與硬體無關的位時序,這也是Bosch CAN 2.0 spec所推薦的(參考第八章http://www.semiconductors.bosch.de/pdf/can2spec.pdf)。

 

    "sja1000: tseg1 1..16 tseg2 1..8 sjw 1..4 brp 1..64 brp-inc 1 clock 8000000"

顯示CAN控制器的位元時序常量,這裡的例子以sja1000為例。時間段(tseg -time segment)1和2的最小和最大值,以tq為單位的同步跳轉寬度,位元速率的預分頻器(brp--pre-scaler)和CAN系統時鐘(以HZ為單位)。這些常量可以被使用者空間的位元時序統計演算法所使用。

 

    "re-started bus-errors arbit-lost error-warn error-pass bus-off"

顯示重啟的次數、匯流排和仲裁丟失錯誤,錯誤主動(error-warning)、錯誤被動(error-passive)、和匯流排關閉的狀態變化。接收的過載錯誤在 統計資訊的"overrun"域下面列出。

 

  • 6.5.2 設定CAN的位元時序

CAN位元時序引數可以使用硬體無關的定義方法。這些引數是: "tq", "prop_seg", "phase_seg1", "phase_seg2" 和 "sjw":

 

    $ ip link set canX type can tq 125 prop-seg 6 \
                    phase-seg1 7 phase-seg2 2 sjw 1

 

在核心選項CONFIG_CAN_CALC_BITTIMING被使能的情況下,如果位元率(波特率)引數 "bitrate"被設定了,CAN的這些引數將會生效:

    

$ ip link set canX type can bitrate 125000 

 

請注意,這條命令在大部分使用標準波特率的CAN控制器上工作良好,但是使用一些少見的波特率值(如115000)和時鐘頻率值將會失敗。禁用核心的CONFIG_CAN_CALC_BITTIMING選項可以節省一些記憶體空間並且允許使用者空間的命令工具完全的控制位元時序引數。使用CAN控制器的位元時序常量就可以達到這個目的(使用者空間控制位元時序)。下面的命令將會列出這些變數:

 

  $ ip -details link show can0 

  ... 

     sja1000: clock 8000000 tseg1 1..16 tseg2 1..8 sjw 1..4 brp 1..64 brp-inc 1 

 

  • 6.5.3 啟動和停止CAN網路裝置

 

一個CAN網路裝置可以使用"ifconfig canX up/down" 或者 "ip link set canX up/down"來開啟和關閉。為了避免錯誤的預設值,必須在啟動CAN裝置之前設定它的位元時序引數:

 

    $ ip link set canX up type can bitrate 125000

 

如果總線上出現太多的錯誤裝置可能進入匯流排關閉狀態(也就是從匯流排脫離)。進入匯流排關閉狀態之後裝置不會再發送和接收資訊。給"restart-ms"設定一個非零值可以開啟匯流排關閉自動恢復的功能(也就是進入匯流排關閉狀態後重新開啟),下面是一個示例:

 

    $ ip link set canX type can restart-ms 100

 

應用程式可以通過監測CAN的錯誤幀意識到已經進入匯流排關閉狀態,並且可以使用以下命令重啟:

 

    $ ip link set canX type can restart

 

注意,重啟也會生成一個CAN錯誤幀(參見3.4節)。

 

6.6 支援Socket CAN的硬體

------------------------------

 

"drivers/net/can"中的“Kconfig”檔案中可以檢視到所有支援的硬體列表。在CAN套接字專案網站上(參見第7章)有更多驅動何以獲得,當然也有對早些時期版本核心的支援 。

 

7. 學習Socket CAN的相關資源

===================

 

你可在BerliOS OSS專案網站的CAN套接字頁面中發現更多資源,比如使用者空間工具、對舊版核心的支援、更多的驅動、郵件列表等:

 

    http://developer.berlios.de/projects/socketcan

 

如果你有任何問題或者發現了BUG,不要遲疑,立馬傳送郵件到CAN套接字的使用者郵件列表。但是在傳送之前請首先搜尋郵件記錄中是否已經有了相同的問題。

 

8.  貢獻者名單

=========
  

Oliver Hartkopp (PF_CAN core, filters, drivers, bcm, SJA1000 driver) Urs Thuermann (PF_CAN core, kernel integration, socket interfaces, raw, vcan) Jan Kizka (RT-SocketCAN core, Socket-API reconciliation)
  Wolfgang Grandegger (RT-SocketCAN core & drivers, Raw Socket-API reviews, CAN device driver interface, MSCAN driver) Robert Schwebel (design reviews, PTXdist integration)
  Marc Kleine-Budde (design reviews, Kernel 2.6 cleanups, drivers) Benedikt Spranger (reviews)
  Thomas Gleixner (LKML reviews, coding style, posting hints)
  Andrey Volkov (kernel subtree structure, ioctls, MSCAN driver) Matthias Brukner (first SJA1000 CAN netdevice implementation Q2/2003) Klaus Hitschler (PEAK driver integration)
  Uwe Koppe (CAN netdevices with PF_PACKET approach)
  Michael Schulze (driver layer loopback requirement, RT CAN drivers review) Pavel Pisa (Bit-timing calculation)
  Sascha Hauer (SJA1000 platform driver)
  Sebastian Haas (SJA1000 EMS PCI driver)
  Markus Plessing (SJA1000 EMS PCI driver)
  Per Dalen (SJA1000 Kvaser PCI driver)
  Sam Ravnborg (reviews, coding style, kbuild help)