SocketCAN - 控制器區域網¶
概述 / 什麼是 SocketCAN¶
socketcan 包是 Linux 的 CAN 協議(控制器區域網)的實現。CAN 是一種網路技術,廣泛應用於自動化、嵌入式裝置和汽車領域。雖然之前有其他基於字元裝置的 Linux CAN 實現,但 SocketCAN 使用 Berkeley socket API、Linux 網路堆疊,並將 CAN 裝置驅動程式實現為網路介面。CAN socket API 的設計儘可能與 TCP/IP 協議相似,以便熟悉網路程式設計的程式設計師能夠輕鬆學習如何使用 CAN socket。
動機 / 為什麼使用 Socket API¶
在 SocketCAN 之前已經有 Linux 的 CAN 實現,所以出現了一個問題,為什麼我們要啟動另一個專案。大多數現有的實現都是作為某些 CAN 硬體的裝置驅動程式提供的,它們基於字元裝置,並且提供的功能相對較少。通常,只有一個硬體特定的裝置驅動程式,它提供一個字元裝置介面來直接從/向控制器硬體傳送和接收原始 CAN 幀。幀的排隊和更高級別的傳輸協議(如 ISO-TP)必須在使用者空間應用程式中實現。此外,大多數字符裝置實現一次只支援單個程序開啟裝置,類似於序列介面。更換 CAN 控制器需要使用另一個裝置驅動程式,並且通常需要調整應用程式的大部分以適應新的驅動程式 API。
SocketCAN 的設計目的是克服所有這些限制。實現了一個新的協議族,它為使用者空間應用程式提供了一個 socket 介面,並且建立在 Linux 網路層之上,從而可以使用所有提供的排隊功能。CAN 控制器硬體的裝置驅動程式將自己註冊為 Linux 網路層中的一個網路裝置,以便來自控制器的 CAN 幀可以傳遞到網路層,然後傳遞到 CAN 協議族模組,反之亦然。此外,協議族模組還為傳輸協議模組提供了一個 API 來註冊,以便可以動態載入或解除安裝任意數量的傳輸協議。事實上,can core 模組本身不提供任何協議,如果沒有載入至少一個額外的協議模組,就無法使用。可以同時開啟多個 socket,在不同的或相同的協議模組上,它們可以監聽/傳送不同或相同的 CAN ID 的幀。多個 socket 在同一個介面上監聽具有相同 CAN ID 的幀,都會收到相同的匹配 CAN 幀。希望使用特定傳輸協議(例如 ISO-TP)進行通訊的應用程式只需在開啟 socket 時選擇該協議,然後就可以讀取和寫入應用程式資料位元組流,而無需處理 CAN-ID、幀等。
使用者空間可見的類似功能也可以透過字元裝置提供,但這將導致一些技術上不優雅的解決方案,原因如下:
複雜的使用方式:應用程式需要使用 ioctl(2) 而不是將協議引數傳遞給 socket(2) 並使用 bind(2) 來選擇 CAN 介面和 CAN ID 來完成所有這些操作。
程式碼重複:字元裝置無法使用 Linux 網路排隊程式碼,因此所有這些程式碼都必須為 CAN 網路重複。
抽象:在大多數現有的字元裝置實現中,CAN 控制器的硬體特定裝置驅動程式直接為應用程式提供字元裝置。這在 Unix 系統中對於字元裝置和塊裝置來說至少是非常不尋常的。例如,您沒有用於序列介面的某個 UART、計算機中的某個音效卡晶片、SCSI 或 IDE 控制器(用於訪問您的硬碟或磁帶流媒體裝置)的字元裝置。相反,您有抽象層,這些抽象層一方面為應用程式提供統一的字元或塊裝置介面,另一方面為硬體特定裝置驅動程式提供介面。這些抽象由子系統提供,例如 tty 層、音訊子系統或上述裝置的 SCSI 和 IDE 子系統。
實現 CAN 裝置驅動程式的最簡單方法是將其作為沒有這種(完整)抽象層的字元裝置來實現,就像大多數現有驅動程式所做的那樣。然而,正確的方法是新增這樣一個層,其中包含所有功能,例如註冊某些 CAN ID、支援多個開啟的檔案描述符以及(解)多路複用它們之間的 CAN 幀、(複雜的)CAN 幀排隊,並提供一個裝置驅動程式註冊的 API。然而,這樣一來,使用 Linux 核心提供的網路框架就不會更困難,或者可能更容易,這就是 SocketCAN 所做的。
使用 Linux 核心的網路框架只是為 Linux 實現 CAN 的自然和最合適的方式。
SocketCAN 概念¶
正如在 動機 / 為什麼使用 Socket API 中描述的那樣,SocketCAN 的主要目標是為使用者空間應用程式提供一個建立在 Linux 網路層之上的 socket 介面。與通常已知的 TCP/IP 和乙太網網路不同,CAN 匯流排是一個僅廣播的媒介,沒有像乙太網這樣的 MAC 層定址。CAN 識別符號 (can_id) 用於 CAN 總線上的仲裁。因此,CAN-ID 必須在總線上唯一選擇。在設計 CAN-ECU 網路時,CAN-ID 被對映為由特定的 ECU 傳送。因此,CAN-ID 可以最好地被視為一種源地址。
接收列表¶
多個應用程式的網路透明訪問導致不同的應用程式可能對來自同一 CAN 網路介面的相同 CAN-ID 感興趣的問題。SocketCAN 核心模組(實現協議族 CAN)為此提供了幾個高效的接收列表。例如,如果一個使用者空間應用程式開啟一個 CAN RAW socket,則 raw 協議模組本身會從 SocketCAN 核心請求使用者請求的(範圍)CAN-ID。CAN-ID 的訂閱和取消訂閱可以針對特定的 CAN 介面或所有已知 CAN 介面完成,使用 SocketCAN 核心提供給 CAN 協議模組的 can_rx_(un)register() 函式(參見 SocketCAN 核心模組)。為了最佳化執行時的 CPU 使用率,接收列表被分為每個裝置的幾個特定列表,這些列表匹配給定用例的請求的過濾器複雜性。
傳送幀的本地環回¶
正如從其他網路概念中已知的那樣,資料交換應用程式可以在相同或不同的節點上執行,而無需任何更改(除了相關的定址資訊)
___ ___ ___ _______ ___
| _ | | _ | | _ | | _ _ | | _ |
||A|| ||B|| ||C|| ||A| |B|| ||C||
|___| |___| |___| |_______| |___|
| | | | |
-----------------(1)- CAN bus -(2)---------------
為了確保應用程式 A 在示例 (2) 中接收到與示例 (1) 中接收到的相同資訊,需要對相應節點上的傳送 CAN 幀進行某種本地環回。
Linux 網路裝置(預設情況下)只能處理媒體相關幀的傳輸和接收。由於 CAN 總線上的仲裁,低優先順序 CAN-ID 的傳輸可能會因高優先順序 CAN 幀的接收而延遲。為了反映正確的 [1] 節點上的流量,傳送資料的環回必須在成功傳輸後立即執行。如果 CAN 網路介面由於某種原因無法執行環回,則 SocketCAN 核心可以作為後備解決方案執行此任務。有關詳細資訊,請參見 傳送幀的本地環回(推薦)。
預設情況下啟用環回功能,以反映 CAN 應用程式的標準網路行為。由於 RT-SocketCAN 組的一些請求,可以選擇為每個單獨的 socket 停用環回。有關詳細資訊,請參見 帶有 can_filters 的 RAW 協議 Socket (SOCK_RAW) 中的 CAN RAW socket 的 sockopts。
網路問題通知¶
CAN 匯流排的使用可能會導致物理和媒體訪問控制層上的幾個問題。檢測和記錄這些較低層的問題是 CAN 使用者識別物理收發器層上的硬體問題以及由不同 ECU 引起的仲裁問題和錯誤幀的重要要求。檢測到的錯誤的發生對於診斷很重要,並且必須與確切的時間戳一起記錄。因此,CAN 介面驅動程式可以生成所謂的錯誤訊息幀,這些幀可以選擇以與其他 CAN 幀相同的方式傳遞到使用者應用程式。每當檢測到物理層或 MAC 層上的錯誤時(例如,由 CAN 控制器),驅動程式會建立一個適當的錯誤訊息幀。使用者應用程式可以使用通用的 CAN 過濾器機制請求錯誤訊息幀。在此過濾器定義中,可以選擇(感興趣的)錯誤型別。預設情況下停用錯誤訊息的接收。CAN 錯誤訊息幀的格式在 Linux 標頭檔案“include/uapi/linux/can/error.h”中簡要描述。
如何使用 SocketCAN¶
與 TCP/IP 一樣,您首先需要開啟一個 socket 才能透過 CAN 網路進行通訊。由於 SocketCAN 實現了一個新的協議族,因此您需要將 PF_CAN 作為第一個引數傳遞給 socket(2) 系統呼叫。目前,有兩種 CAN 協議可供選擇,即 raw socket 協議和廣播管理器 (BCM)。因此,要開啟一個 socket,您可以編寫
s = socket(PF_CAN, SOCK_RAW, CAN_RAW);
和
s = socket(PF_CAN, SOCK_DGRAM, CAN_BCM);
分別。成功建立 socket 後,您通常會使用 bind(2) 系統呼叫將 socket 繫結到 CAN 介面(由於不同的定址,這與 TCP/IP 不同 - 參見 SocketCAN 概念)。繫結 (CAN_RAW) 或連線 (CAN_BCM) socket 後,您可以像往常一樣從/向 socket read(2) 和 write(2),或者在 socket 上使用 send(2)、sendto(2)、sendmsg(2) 和 recv* 對等操作。下面還介紹了 CAN 特定的 socket 選項。
Classical CAN 幀結構(也稱為 CAN 2.0B)、CAN FD 幀結構和 sockaddr 結構在 include/linux/can.h 中定義
struct can_frame {
canid_t can_id; /* 32 bit CAN_ID + EFF/RTR/ERR flags */
union {
/* CAN frame payload length in byte (0 .. CAN_MAX_DLEN)
* was previously named can_dlc so we need to carry that
* name for legacy support
*/
__u8 len;
__u8 can_dlc; /* deprecated */
};
__u8 __pad; /* padding */
__u8 __res0; /* reserved / padding */
__u8 len8_dlc; /* optional DLC for 8 byte payload length (9 .. 15) */
__u8 data[8] __attribute__((aligned(8)));
};
備註:len 元素包含有效負載長度(以位元組為單位),應代替 can_dlc 使用。已棄用的 can_dlc 具有誤導性的名稱,因為它始終包含以位元組為單位的普通有效負載長度,而不是所謂的“資料長度程式碼”(DLC)。
要將原始 DLC 從/向 Classical CAN 網路裝置傳遞,當 len 元素為 8 時,len8_dlc 元素可以包含值 9 .. 15(所有大於或等於 8 的 DLC 值的實際有效負載長度)。
(線性)有效負載資料 [] 與 64 位邊界的對齊允許使用者定義自己的結構和聯合,以便輕鬆訪問 CAN 有效負載。預設情況下,CAN 總線上沒有給定的位元組順序。對 CAN_RAW socket 的 read(2) 系統呼叫會將 struct can_frame 傳輸到使用者空間。
sockaddr_can 結構具有像 PF_PACKET socket 一樣的介面索引,它也繫結到特定的介面
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;
/* J1939 address information */
struct {
/* 8 byte name when using dynamic addressing */
__u64 name;
/* pgn:
* 8 bit: PS in PDU2 case, else 0
* 8 bit: PF
* 1 bit: DP
* 1 bit: reserved
*/
__u32 pgn;
/* 1 byte address */
__u8 addr;
} j1939;
/* reserved for future CAN protocols address information */
} can_addr;
};
要確定介面索引,必須使用適當的 ioctl()(CAN_RAW socket 的示例,沒有錯誤檢查)
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));
(..)
要將 socket 繫結到所有 CAN 介面,介面索引必須為 0(零)。在這種情況下,socket 會收到來自每個啟用的 CAN 介面的 CAN 幀。要確定原始 CAN 介面,可以使用系統呼叫 recvfrom(2) 代替 read(2)。要從繫結到“任何”介面的 socket 傳送,需要 sendto(2) 來指定傳出介面。
從繫結的 CAN_RAW socket 讀取 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 介面繫結到“任何”現有 CAN 介面時 (addr.can_ifindex = 0),如果需要有關原始 CAN 介面的資訊,建議使用 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);
要在繫結到“任何”CAN 介面的 socket 上寫入 CAN 幀,必須確定傳出介面
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));
透過在從 socket 讀取訊息後使用 ioctl(2) 呼叫可以獲得準確的時間戳
struct timeval tv;
ioctl(s, SIOCGSTAMP, &tv);
時間戳的解析度為一微秒,並在接收到 CAN 幀時自動設定。
關於 CAN FD(靈活資料速率)支援的備註
通常,CAN FD 的處理方式與前面描述的示例非常相似。新的支援 CAN FD 的 CAN 控制器支援兩種不同的位元率,分別用於仲裁階段和 CAN FD 幀的有效負載階段,以及最多 64 位元組的有效負載。這種擴充套件的有效負載長度破壞了所有嚴重依賴於具有固定八位元組有效負載的 CAN 幀 (struct can_frame) 的核心介面 (ABI),例如 CAN_RAW socket。因此,例如,CAN_RAW socket 支援一個新的 socket 選項 CAN_RAW_FD_FRAMES,該選項將 socket 切換到一種模式,該模式允許同時處理 CAN FD 幀和 Classical CAN 幀(參見 RAW Socket 選項 CAN_RAW_FD_FRAMES)。
struct canfd_frame 在 include/linux/can.h 中定義
struct canfd_frame {
canid_t can_id; /* 32 bit CAN_ID + EFF/RTR/ERR flags */
__u8 len; /* frame payload length in byte (0 .. 64) */
__u8 flags; /* additional flags for CAN FD */
__u8 __res0; /* reserved / padding */
__u8 __res1; /* reserved / padding */
__u8 data[64] __attribute__((aligned(8)));
};
struct canfd_frame 和現有的 struct can_frame 在其結構中的相同偏移處具有 can_id、有效負載長度和有效負載資料。這允許非常相似地處理不同的結構。當 struct can_frame 的內容被複制到 struct canfd_frame 中時,所有結構元素都可以按原樣使用 - 只有 data[] 變得擴充套件了。
當引入 struct canfd_frame 時,事實證明 struct can_frame 的資料長度程式碼 (DLC) 被用作長度資訊,因為長度和 DLC 在 0 .. 8 的範圍內具有 1:1 的對映。為了保留長度資訊的易於處理,canfd_frame.len 元素包含一個從 0 .. 64 的普通長度值。因此,canfd_frame.len 和 can_frame.len 都相等並且包含長度資訊,而不是 DLC。有關 CAN 和支援 CAN FD 的裝置以及到匯流排相關資料長度程式碼 (DLC) 的對映的詳細資訊,請參見 CAN FD(靈活資料速率)驅動程式支援。
兩個 CAN(FD) 幀結構的長度定義了 CAN(FD) 網路介面和 skbuff 資料長度的最大傳輸單元 (MTU)。在 include/linux/can.h 中指定了兩個用於 CAN 特定 MTU 的定義
#define CAN_MTU (sizeof(struct can_frame)) == 16 => Classical CAN frame
#define CANFD_MTU (sizeof(struct canfd_frame)) == 72 => CAN FD frame
返回的訊息標誌¶
當在 RAW 或 BCM socket 上使用系統呼叫 recvmsg(2) 時,msg->msg_flags 欄位可能包含以下標誌
帶有 can_filters 的 RAW 協議 Socket (SOCK_RAW)¶
使用 CAN_RAW socket 與通常已知的訪問 CAN 字元裝置非常相似。為了滿足多使用者 SocketCAN 方法提供的新可能性,在 RAW socket 繫結時設定了一些合理的預設值
過濾器設定為只有一個過濾器接收所有內容
socket 只接收有效的資料幀(=> 沒有錯誤訊息幀)
啟用傳送 CAN 幀的環回(參見 傳送幀的本地環回)
socket 不接收自己傳送的幀(在環回模式下)
這些預設設定可以在繫結 socket 之前或之後更改。要使用 CAN_RAW socket 的 socket 選項的引用定義,請包含
RAW socket 選項 CAN_RAW_FILTER¶
使用 CAN_RAW socket 接收 CAN 幀可以透過定義 0 .. n 個帶有 CAN_RAW_FILTER socket 選項的過濾器來控制。
CAN 過濾器結構在 include/linux/can.h 中定義
struct can_filter {
canid_t can_id;
canid_t can_mask;
};
當
<received_can_id> & mask == can_id & mask
時,過濾器匹配,這類似於已知的 CAN 控制器硬體過濾器語義。當在 can_filter 結構的 can_id 元素中設定了 CAN_INV_FILTER 位時,可以在此語義中反轉過濾器。與 CAN 控制器硬體過濾器相反,使用者可以為每個開啟的 socket 單獨設定 0 .. n 個接收過濾器
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 socket 上停用 CAN 幀的接收
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0);
將過濾器設定為零個過濾器是相當過時的,因為不讀取資料會導致 raw socket 丟棄收到的 CAN 幀。但是,有了這個“僅傳送”用例,我們可以刪除核心中的接收列表,以節省一點(確實是非常少!)CPU 使用率。
CAN 過濾器使用最佳化¶
CAN 過濾器在 CAN 幀接收時按裝置過濾器列表進行處理。為了減少在遍歷過濾器列表時需要執行的檢查次數,當過濾器訂閱專注於單個 CAN ID 時,CAN 核心提供最佳化的過濾器處理。
對於可能的 2048 個 SFF CAN 識別符號,識別符號用作索引來訪問相應的訂閱列表,而無需任何進一步的檢查。對於 2^29 個可能的 EFF CAN 識別符號,使用 10 位 XOR 摺疊作為雜湊函式來檢索 EFF 表索引。
為了從單個 CAN 識別符號的最佳化過濾器中受益,CAN_SFF_MASK 或 CAN_EFF_MASK 必須與設定的 CAN_EFF_FLAG 和 CAN_RTR_FLAG 位一起設定到 can_filter.mask 中。can_filter.mask 中的設定的 CAN_EFF_FLAG 位清楚地表明訂閱 SFF 或 EFF CAN ID 是否重要。例如,在上面的示例中
rfilter[0].can_id = 0x123;
rfilter[0].can_mask = CAN_SFF_MASK;
具有 CAN ID 0x123 的 SFF 幀和具有 0xXXXXX123 的 EFF 幀都可以透過。
要僅過濾 0x123 (SFF) 和 0x12345678 (EFF) CAN 識別符號,必須以這種方式定義過濾器才能從最佳化過濾器中受益
struct can_filter rfilter[2];
rfilter[0].can_id = 0x123;
rfilter[0].can_mask = (CAN_EFF_FLAG | CAN_RTR_FLAG | CAN_SFF_MASK);
rfilter[1].can_id = 0x12345678 | CAN_EFF_FLAG;
rfilter[1].can_mask = (CAN_EFF_FLAG | CAN_RTR_FLAG | CAN_EFF_MASK);
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter));
RAW Socket 選項 CAN_RAW_ERR_FILTER¶
如 網路問題通知 中所述,CAN 介面驅動程式可以生成所謂的錯誤訊息幀,這些幀可以選擇以與其他 CAN 幀相同的方式傳遞到使用者應用程式。可能的錯誤分為不同的錯誤類別,可以使用適當的錯誤掩碼進行過濾。要註冊每個可能的錯誤條件,可以使用 CAN_ERR_MASK 作為錯誤掩碼的值。錯誤掩碼的值在 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));
RAW Socket 選項 CAN_RAW_LOOPBACK¶
為了滿足多使用者需求,預設情況下啟用本地環回(有關詳細資訊,請參見 傳送幀的本地環回)。但是在某些嵌入式用例中(例如,當只有一個應用程式使用 CAN 匯流排時),可以停用此環回功能(每個 socket 單獨停用)
int loopback = 0; /* 0 = disabled, 1 = enabled (default) */
setsockopt(s, SOL_CAN_RAW, CAN_RAW_LOOPBACK, &loopback, sizeof(loopback));
RAW socket 選項 CAN_RAW_RECV_OWN_MSGS¶
當啟用本地環回時,所有傳送的 CAN 幀都會環回到在給定介面上註冊 CAN 幀的 CAN-ID 的開啟的 CAN socket,以滿足多使用者需求。假設接收 CAN 幀的 socket 與傳送 CAN 幀的 socket 相同是不需要的,因此預設情況下停用。可以根據需要更改此預設行為
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));
請注意,接收 socket 自己的 CAN 幀與接收其他 CAN 幀受到相同的過濾(參見 RAW socket 選項 CAN_RAW_FILTER)。
RAW Socket 選項 CAN_RAW_FD_FRAMES¶
可以透過新的 socket 選項 CAN_RAW_FD_FRAMES 在 CAN_RAW socket 中啟用 CAN FD 支援,預設情況下停用此選項。當 CAN_RAW socket 不支援新的 socket 選項時(例如,在舊核心上),切換 CAN_RAW_FD_FRAMES 選項會返回錯誤 -ENOPROTOOPT。
一旦啟用了 CAN_RAW_FD_FRAMES,應用程式就可以傳送 CAN 幀和 CAN FD 幀。OTOH,應用程式必須在從 socket 讀取時處理 CAN 幀和 CAN FD 幀
CAN_RAW_FD_FRAMES enabled: CAN_MTU and CANFD_MTU are allowed
CAN_RAW_FD_FRAMES disabled: only CAN_MTU is allowed (default)
示例
[ remember: CANFD_MTU == sizeof(struct canfd_frame) ]
struct canfd_frame cfd;
nbytes = read(s, &cfd, CANFD_MTU);
if (nbytes == CANFD_MTU) {
printf("got CAN FD frame with length %d\n", cfd.len);
/* cfd.flags contains valid data */
} else if (nbytes == CAN_MTU) {
printf("got Classical CAN frame with length %d\n", cfd.len);
/* cfd.flags is undefined */
} else {
fprintf(stderr, "read: invalid CAN(FD) frame\n");
return 1;
}
/* the content can be handled independently from the received MTU size */
printf("can_id: %X data length: %d data: ", cfd.can_id, cfd.len);
for (i = 0; i < cfd.len; i++)
printf("%02X ", cfd.data[i]);
當僅讀取大小為 CANFD_MTU 的套接字返回從套接字收到的 CAN_MTU 位元組時,一個 Classical CAN 幀已被讀取到提供的 CAN FD 結構中。請注意,canfd_frame.flags 資料欄位未在 struct can_frame 中指定,因此它僅在 CANFD_MTU 大小的 CAN FD 幀中有效。
新 CAN 應用程式的實現提示
要構建一個 CAN FD 感知應用程式,請使用 struct canfd_frame 作為基於 CAN_RAW 的應用程式的基本 CAN 資料結構。當應用程式在較舊的 Linux 核心上執行並且切換 CAN_RAW_FD_FRAMES socket 選項返回錯誤時:沒問題。您將獲得 Classical CAN 幀或 CAN FD 幀,並且可以以相同的方式處理它們。
當傳送到 CAN 裝置時,請確保該裝置能夠透過檢查裝置最大傳輸單元是否為 CANFD_MTU 來處理 CAN FD 幀。CAN 裝置 MTU 可以透過例如 SIOCGIFMTU ioctl() 系統呼叫檢索。
RAW socket 選項 CAN_RAW_JOIN_FILTERS¶
CAN_RAW socket 可以設定多個 CAN 識別符號特定過濾器,這會導致 af_can.c 過濾器處理中的多個過濾器。這些過濾器彼此獨立,這會導致應用時進行邏輯 OR 過濾(參見 RAW socket 選項 CAN_RAW_FILTER)。
此 socket 選項以只有匹配所有給定 CAN 過濾器的 CAN 幀才能傳遞到使用者空間的方式連線給定的 CAN 過濾器。因此,應用於過濾器的語義更改為邏輯 AND。
當過濾器集是其中設定了 CAN_INV_FILTER 標誌的過濾器的組合,以便從傳入流量中排除單個 CAN ID 或 CAN ID 範圍時,這尤其有用。
廣播管理器協議 Socket (SOCK_DGRAM)¶
廣播管理器協議提供了一個基於命令的配置介面,用於過濾和傳送(例如,迴圈)核心空間中的 CAN 訊息。
接收過濾器可用於向下取樣頻繁訊息;檢測諸如訊息內容更改、資料包長度更改之類的事件,並對接收到的訊息進行超時監控。
可以在執行時建立和修改 CAN 幀或一系列 CAN 幀的週期性傳輸任務;可以更改訊息內容和兩個可能的傳輸間隔。
CAN_BCM socket 不用於像從 CAN_RAW socket 已知的那樣使用 struct can_frame 傳送單個 CAN 幀。而是定義了一個特殊的 BCM 配置訊息。用於與廣播管理器通訊的基本 BCM 配置訊息和可用操作在 linux/can/bcm.h 包含中定義。BCM 訊息由訊息頭和命令 ('opcode') 以及零個或多個 CAN 幀組成。廣播管理器以相同的形式將響應傳送到使用者空間
struct bcm_msg_head {
__u32 opcode; /* command */
__u32 flags; /* special flags */
__u32 count; /* run 'count' times with ival1 */
struct timeval ival1, ival2; /* count and subsequent interval */
canid_t can_id; /* unique can_id for task */
__u32 nframes; /* number of can_frames following */
struct can_frame frames[0];
};
對齊的有效負載“幀”使用在 RAW Socket 選項 CAN_RAW_FD_FRAMES 開頭和 include/linux/can.h 包含中定義的相同基本 CAN 幀結構。來自使用者空間的傳送到廣播管理器的所有訊息都具有此結構。
請注意,在 socket 建立後,必須連線而不是繫結 CAN_BCM socket(沒有錯誤檢查的示例)
int s;
struct sockaddr_can addr;
struct ifreq ifr;
s = socket(PF_CAN, SOCK_DGRAM, CAN_BCM);
strcpy(ifr.ifr_name, "can0");
ioctl(s, SIOCGIFINDEX, &ifr);
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
connect(s, (struct sockaddr *)&addr, sizeof(addr));
(..)
廣播管理器 socket 能夠併發處理任意數量的飛行中傳輸或接收過濾器。不同的 RX/TX 作業透過每個 BCM 訊息中唯一的 can_id 來區分。但是,建議使用額外的 CAN_BCM socket 在多個 CAN 介面上進行通訊。當廣播管理器 socket 繫結到“任何”CAN 介面時(=> 介面索引設定為零),除非使用 sendto() 系統呼叫來覆蓋“任何”CAN 介面索引,否則配置的接收過濾器將應用於任何 CAN 介面。當使用 recvfrom() 而不是 read() 來檢索 BCM socket 訊息時,原始 CAN 介面在 can_ifindex 中提供。
廣播管理器操作¶
opcode 定義了廣播管理器要執行的操作,或詳細說明了廣播管理器對幾個事件的響應,包括使用者請求。
傳輸操作(使用者空間到廣播管理器)
- TX_SETUP
建立(迴圈)傳輸任務。
- TX_DELETE
刪除(迴圈)傳輸任務,僅需要 can_id。
- TX_READ
讀取 can_id 的(迴圈)傳輸任務的屬性。
- TX_SEND
傳送一個 CAN 幀。
傳輸響應(廣播管理器到使用者空間)
- TX_STATUS
回覆 TX_READ 請求(傳輸任務配置)。
- TX_EXPIRED
計數器完成以初始間隔 'ival1' 傳送時的通知。需要在 TX_SETUP 時設定 TX_COUNTEVT 標誌。
接收操作(使用者空間到廣播管理器)
- RX_SETUP
建立 RX 內容過濾器訂閱。
- RX_DELETE
刪除 RX 內容過濾器訂閱,僅需要 can_id。
- RX_READ
讀取 can_id 的 RX 內容過濾器訂閱的屬性。
接收響應(廣播管理器到使用者空間)
- RX_STATUS
回覆 RX_READ 請求(過濾器任務配置)。
- RX_TIMEOUT
檢測到迴圈訊息不存在(計時器 ival1 已過期)。
- RX_CHANGED
具有更新的 CAN 幀的 BCM 訊息(檢測到內容更改)。在收到第一條訊息或收到修訂後的 CAN 訊息時傳送。
廣播管理器訊息標誌¶
向廣播管理器傳送訊息時,“flags”元素可能包含以下影響行為的標誌定義
- SETTIMER
設定 ival1、ival2 和 count 的值
- STARTTIMER
使用 ival1、ival2 和 count 的實際值啟動計時器。啟動計時器同時會導致發出 CAN 幀。
- TX_COUNTEVT
當 count 過期時,建立訊息 TX_EXPIRED
- TX_ANNOUNCE
程序對資料的更改會立即發出。
- TX_CP_CAN_ID
將訊息標頭中的 can_id 複製到 frames 中的每個後續幀。這旨在簡化用法。對於 TX 任務,訊息標頭中的唯一 can_id 可能與後續 struct can_frame(s) 中儲存的用於傳輸的 can_id 不同。
- RX_FILTER_ID
僅按 can_id 過濾,無需幀(nframes=0)。
- RX_CHECK_DLC
DLC 的更改會導致 RX_CHANGED。
- RX_NO_AUTOTIMER
防止自動啟動超時監視器。
- RX_ANNOUNCE_RESUME
如果在 RX_SETUP 處傳遞並且發生了接收超時,則在(迴圈)接收重新啟動時將生成 RX_CHANGED 訊息。
- TX_RESET_MULTI_IDX
重置多幀傳輸的索引。
- RX_RTR_FRAME
傳送 RTR 請求的回覆(放置在 op->frames[0] 中)。
- CAN_FD_FRAME
bcm_msg_head 之後的 CAN 幀是 struct canfd_frame's
廣播管理器傳輸計時器¶
週期性傳輸配置最多可以使用兩個間隔計時器。在這種情況下,BCM 以間隔“ival1”傳送一定數量的訊息(“count”),然後繼續以另一個給定間隔“ival2”傳送。當只需要一個計時器時,“count”設定為零,並且僅使用“ival2”。當設定了 SET_TIMER 和 START_TIMER 標誌時,計時器被啟用。當僅設定 SET_TIMER 時,可以在執行時更改計時器值。
廣播管理器訊息序列傳輸¶
在迴圈 TX 任務配置的情況下,最多可以在序列中傳輸 256 個 CAN 幀。CAN 幀的數量在 BCM 訊息頭的“nframes”元素中提供。定義的 CAN 幀數量作為陣列新增到 TX_SETUP BCM 配置訊息
/* create a struct to set up a sequence of four CAN frames */
struct {
struct bcm_msg_head msg_head;
struct can_frame frame[4];
} mytxmsg;
(..)
mytxmsg.msg_head.nframes = 4;
(..)
write(s, &mytxmsg, sizeof(mytxmsg));
每次傳輸時,CAN 幀陣列中的索引都會增加,並在索引溢位時設定為零。
廣播管理器接收過濾器計時器¶
計時器值 ival1 或 ival2 可以在 RX_SETUP 處設定為非零值。當設定了 SET_TIMER 標誌時,計時器被啟用
- ival1
當在給定時間內未再次收到接收到的訊息時,傳送 RX_TIMEOUT。當在 RX_SETUP 處設定 START_TIMER 時,即使沒有以前的 CAN 幀接收,也會直接啟用超時檢測。
- ival2
將接收到的訊息速率降低到 ival2 的值。當 CAN 幀內的訊號是無狀態的時,這對於減少應用程式的訊息很有用,因為 ival2 週期內的狀態更改可能會丟失。
廣播管理器多路複用訊息接收過濾器¶
為了過濾多路複用訊息序列中的內容更改,可以在 RX_SETUP 配置訊息中傳遞一個包含多個 CAN 幀的陣列。第一個 CAN 幀的資料位元組包含相關位的掩碼,這些位必須與後續 CAN 幀中接收到的 CAN 幀匹配。如果其中一個後續 CAN 幀與該幀資料標記中的位相匹配,則標記要與先前接收到的內容進行比較的相關內容。最多可以將 257 個 CAN 幀(多路複用過濾器位掩碼 CAN 幀加上 256 個 CAN 過濾器)作為陣列新增到 TX_SETUP BCM 配置訊息
/* usually used to clear CAN frame data[] - beware of endian problems! */
#define U64_DATA(p) (*(unsigned long long*)(p)->data)
struct {
struct bcm_msg_head msg_head;
struct can_frame frame[5];
} msg;
msg.msg_head.opcode = RX_SETUP;
msg.msg_head.can_id = 0x42;
msg.msg_head.flags = 0;
msg.msg_head.nframes = 5;
U64_DATA(&msg.frame[0]) = 0xFF00000000000000ULL; /* MUX mask */
U64_DATA(&msg.frame[1]) = 0x01000000000000FFULL; /* data mask (MUX 0x01) */
U64_DATA(&msg.frame[2]) = 0x0200FFFF000000FFULL; /* data mask (MUX 0x02) */
U64_DATA(&msg.frame[3]) = 0x330000FFFFFF0003ULL; /* data mask (MUX 0x33) */
U64_DATA(&msg.frame[4]) = 0x4F07FC0FF0000000ULL; /* data mask (MUX 0x4F) */
write(s, &msg, sizeof(msg));
廣播管理器 CAN FD 支援¶
CAN_BCM 的程式設計 API 取決於 struct can_frame,它直接作為陣列在 bcm_msg_head 結構之後給出。為了遵循 CAN FD 幀的這個模式,bcm_msg_head 標誌中的新標誌“CAN_FD_FRAME”表示 bcm_msg_head 之後連線的 CAN 幀結構被定義為 struct canfd_frame
struct {
struct bcm_msg_head msg_head;
struct canfd_frame frame[5];
} msg;
msg.msg_head.opcode = RX_SETUP;
msg.msg_head.can_id = 0x42;
msg.msg_head.flags = CAN_FD_FRAME;
msg.msg_head.nframes = 5;
(..)
當使用 CAN FD 幀進行多路複用過濾時,MUX 掩碼仍然預期在 struct canfd_frame 資料部分的第一個 64 位中。
連線的傳輸協議 (SOCK_SEQPACKET)¶
(待編寫)
未連線的傳輸協議 (SOCK_DGRAM)¶
(待編寫)
SocketCAN 核心模組¶
SocketCAN 核心模組實現了協議族 PF_CAN。CAN 協議模組在執行時由核心模組載入。核心模組為 CAN 協議模組提供了一個介面來訂閱需要的 CAN ID(參見接收列表)。
can.ko 模組引數¶
stats_timer:為了計算 SocketCAN 核心統計資訊(例如,當前/最大每秒幀數),預設情況下在 can.ko 模組啟動時呼叫此 1 秒計時器。可以使用模組命令列上的 stattimer=0 停用此計時器。
debug:(自 SocketCAN SVN r546 以來已刪除)
procfs 內容¶
如接收列表中所述,SocketCAN 核心使用多個過濾器列表將接收到的 CAN 幀傳遞給 CAN 協議模組。可以在相應的接收列表中檢查這些接收列表、它們的過濾器和過濾器匹配計數。所有條目都包含裝置和協議模組識別符號
foo@bar:~$ 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)
在此示例中,應用程式請求來自 vcan0 的任何 CAN 流量
rcvlist_all - list for unfiltered entries (no filter operations)
rcvlist_eff - list for single extended frame (EFF) entries
rcvlist_err - list for error message frames masks
rcvlist_fil - list for mask/value filters
rcvlist_inv - list for mask/value filters (inverse semantic)
rcvlist_sff - list for single standard frame (SFF) entries
/proc/net/can 中的其他 procfs 檔案
stats - SocketCAN core statistics (rx/tx frames, match ratios, ...)
reset_stats - manual statistic reset
version - prints SocketCAN core and ABI version (removed in Linux 5.10)
編寫自己的 CAN 協議模組¶
為了在協議族 PF_CAN 中實現新協議,必須在 include/linux/can.h 中定義一個新協議。可以透過包含 include/linux/can/core.h 來訪問使用 SocketCAN 核心的原型和定義。除了註冊 CAN 協議和 CAN 裝置通知鏈的函式之外,還有一些函式可以訂閱 CAN 介面接收到的 CAN 幀和傳送 CAN 幀
can_rx_register - subscribe CAN frames from a specific interface
can_rx_unregister - unsubscribe CAN frames from a specific interface
can_send - transmit a CAN frame (optional with local loopback)
有關詳細資訊,請參見 net/can/af_can.c 中的 kerneldoc 文件或 net/can/raw.c 或 net/can/bcm.c 的原始碼。
CAN 網路驅動程式¶
編寫 CAN 網路裝置驅動程式比編寫 CAN 字元裝置驅動程式容易得多。與其他已知的網路裝置驅動程式類似,您主要需要處理
TX:將來自套接字緩衝區的 CAN 幀放入 CAN 控制器。
RX:將來自 CAN 控制器的 CAN 幀放入套接字緩衝區。
例如,請參見網路裝置、核心和您!。下面描述了編寫 CAN 網路裝置驅動程式的差異
常規設定¶
dev->type = ARPHRD_CAN; /* the netdevice hardware type */
dev->flags = IFF_NOARP; /* CAN has no arp */
dev->mtu = CAN_MTU; /* sizeof(struct can_frame) -> Classical CAN interface */
or alternative, when the controller supports CAN with flexible data rate:
dev->mtu = CANFD_MTU; /* sizeof(struct canfd_frame) -> CAN FD interface */
struct can_frame 或 struct canfd_frame 是協議族 PF_CAN 中每個套接字緩衝區 (skbuff) 的有效負載。
傳送幀的本地環回¶
如傳送幀的本地環回中所述,CAN 網路裝置驅動程式應支援類似於 tty 裝置本地回顯的本地環回功能。在這種情況下,必須設定驅動程式標誌 IFF_ECHO 以防止 PF_CAN 核心本地回顯(又名環回)作為後備解決方案發送的幀
dev->flags = (IFF_NOARP | IFF_ECHO);
CAN 控制器硬體過濾器¶
為了減少深度嵌入式系統上的中斷負載,一些 CAN 控制器支援過濾 CAN ID 或 CAN ID 範圍。這些硬體過濾器功能因控制器而異,並且必須在多使用者網路方法中識別為不可行。使用非常特定於控制器的硬體過濾器在非常專用的用例中可能是有意義的,因為驅動程式級別的過濾器會影響多使用者系統中的所有使用者。PF_CAN 核心內部的高效過濾器集允許為每個套接字分別設定不同的多個過濾器。因此,硬體過濾器的使用屬於“深度嵌入式系統上的手工調整”類別。作者正在執行 MPC603e @133MHz,帶有來自 2002 年的四個 SJA1000 CAN 控制器,在高匯流排負載下沒有任何問題...
可切換終端電阻¶
CAN 匯流排需要跨差分對的特定阻抗,通常由匯流排最遠節點上的兩個 120Ohm 電阻提供。一些 CAN 控制器支援啟用/停用終端電阻以提供正確的阻抗。
查詢可用的電阻
$ ip -details link show can0
...
termination 120 [ 0, 120 ]
啟用終端電阻
$ ip link set dev can0 type can termination 120
停用終端電阻
$ ip link set dev can0 type can termination 0
要為 can-controller 啟用終端電阻支援,請在控制器的 struct can-priv 中實現
termination_const
termination_const_cnt
do_set_termination
或使用來自 Documentation/devicetree/bindings/net/can/can-controller.yaml 的裝置樹條目新增 gpio 控制
虛擬 CAN 驅動程式 (vcan)¶
類似於網路環回裝置,vcan 提供了一個虛擬本地 CAN 介面。CAN 上的完整限定地址包括
唯一的 CAN 識別符號 (CAN ID)
傳輸此 CAN ID 的 CAN 匯流排(例如,can0)
因此,在常見用例中,需要多個虛擬 CAN 介面。
虛擬 CAN 介面允許傳輸和接收 CAN 幀,而無需真實的 CAN 控制器硬體。虛擬 CAN 網路裝置通常命名為“vcanX”,如 vcan0 vcan1 vcan2 ... 當編譯為模組時,虛擬 CAN 驅動程式模組稱為 vcan.ko
自 Linux 核心版本 2.6.24 以來,vcan 驅動程式支援核心 netlink 介面來建立 vcan 網路裝置。可以使用 ip(8) 工具管理 vcan 網路裝置的建立和刪除
- Create a virtual CAN network interface:
$ ip link add type vcan
- Create a virtual CAN network interface with a specific name 'vcan42':
$ ip link add dev vcan42 type vcan
- Remove a (virtual CAN) network interface 'vcan42':
$ ip link del vcan42
CAN 網路裝置驅動程式介面¶
CAN 網路裝置驅動程式介面提供了一個通用介面來設定、配置和監視 CAN 網路裝置。然後,使用者可以使用來自“IPROUTE2”實用程式套件的程式“ip”透過 netlink 介面配置 CAN 裝置,例如設定位定時引數。以下章節簡要介紹瞭如何使用它。此外,該介面使用通用的資料結構並匯出了一組通用函式,所有真實的 CAN 網路裝置驅動程式都應使用這些函式。請檢視 SJA1000 或 MSCAN 驅動程式以瞭解如何使用它們。模組名稱為 can-dev.ko。
Netlink 介面用於設定/獲取裝置屬性¶
必須透過 netlink 介面配置 CAN 裝置。支援的 netlink 訊息型別在“include/linux/can/netlink.h”中定義並簡要描述。IPROUTE2 實用程式套件的程式“ip”的 CAN 連結支援可用,可以如下所示使用
設定 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 ] ]
[ dbitrate BITRATE [ dsample-point SAMPLE-POINT] ] |
[ dtq TQ dprop-seg PROP_SEG dphase-seg1 PHASE-SEG1
dphase-seg2 PHASE-SEG2 [ dsjw SJW ] ]
[ loopback { on | off } ]
[ listen-only { on | off } ]
[ triple-sampling { on | off } ]
[ one-shot { on | off } ]
[ berr-reporting { on | off } ]
[ fd { on | off } ]
[ fd-non-iso { on | off } ]
[ presume-ack { on | off } ]
[ cc-len8-dlc { 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 或 TRIPLE-SAMPLING。
- “state ERROR-ACTIVE”
CAN 控制器的當前狀態:“ERROR-ACTIVE”、“ERROR-WARNING”、“ERROR-PASSIVE”、“BUS-OFF”或“STOPPED”
- “restart-ms 100”
自動重啟延遲時間。如果設定為非零值,則在匯流排關閉的情況下,將在指定的延遲時間(以毫秒為單位)後自動觸發 CAN 控制器的重啟。預設情況下,它是關閉的。
- “bitrate 125000 sample-point 0.875”
顯示真實的位元率(以位元/秒為單位)和範圍為 0.000..0.999 的取樣點。如果在核心中啟用了位元率計算(CONFIG_CAN_CALC_BITTIMING=y),則可以透過設定“bitrate”引數來定義位元率。可以選擇指定“sample-point”。預設情況下,它是 0.000,假設 CIA 推薦的取樣點。
- “tq 125 prop-seg 6 phase-seg1 7 phase-seg2 2 sjw 1”
以 tq 單位顯示時間量子(以 ns 為單位)、傳播段、相位緩衝段 1 和 2 以及同步跳轉寬度。它們允許以硬體無關的格式定義 CAN 位元定時,如 Bosch CAN 2.0 規範(參見http://www.semiconductors.bosch.de/pdf/can2spec.pdf的第 8 章)中提出的那樣。
- “sja1000: tseg1 1..16 tseg2 1..8 sjw 1..4 brp 1..64 brp-inc 1 clock 8000000”
顯示 CAN 控制器的位元定時常數,此處為“sja1000”。時間段 1 和 2 的最小值和最大值、同步跳轉寬度(以 tq 單位)、位元率預分頻器和 CAN 系統時鐘頻率(以 Hz 為單位)。這些常數可用於使用者定義的(非標準)使用者空間位元率計算演算法。
- “re-started bus-errors arbit-lost error-warn error-pass bus-off”
顯示重啟、匯流排和仲裁丟失錯誤以及狀態更改為錯誤警告、錯誤被動和匯流排關閉狀態的次數。RX 溢位錯誤列在標準網路統計資訊的“overrun”欄位中。
設定 CAN 位元定時¶
CAN 位元定時引數始終可以採用硬體無關的格式定義,如 Bosch CAN 2.0 規範中所述,指定引數“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”指定了位元率,則將計算 CIA 推薦的 CAN 位元定時引數
$ ip link set canX type can bitrate 125000
請注意,這對於大多數具有標準位元率的常見 CAN 控制器來說都可以正常工作,但對於異構位元率或 CAN 系統時鐘頻率可能失敗。停用 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
啟動和停止 CAN 網路裝置¶
可以使用命令“ifconfig canX up/down”或“ip link set canX up/down”像往常一樣啟動或停止 CAN 網路裝置。請注意,在啟動真實 CAN 裝置之前,必須為其實際 CAN 裝置定義適當的位元定時引數,以避免容易出錯的預設設定
$ ip link set canX up type can bitrate 125000
如果 CAN 總線上發生過多錯誤,裝置可能會進入“bus-off”狀態。然後不再接收或傳送任何訊息。可以透過將“restart-ms”設定為非零值來啟用自動匯流排關閉恢復,例如
$ ip link set canX type can restart-ms 100
或者,應用程式可以透過監視 CAN 錯誤訊息幀來識別“bus-off”情況,並在適當時使用以下命令進行重啟
$ ip link set canX type can restart
請注意,重啟也會建立一個 CAN 錯誤訊息幀(另請參見網路問題通知)。
CAN FD(靈活資料速率)驅動程式支援¶
支援 CAN FD 的 CAN 控制器支援 CAN FD 幀的仲裁階段和有效負載階段的兩種不同的位元率。因此,必須指定第二個位元定時才能啟用 CAN FD 位元率。
此外,支援 CAN FD 的 CAN 控制器支援最多 64 位元組的有效負載。使用者空間應用程式和 Linux 網路層內部的 can_frame.len 和 canfd_frame.len 中此長度的表示形式是從 0 .. 64 開始的純值,而不是 CAN“資料長度程式碼”。資料長度程式碼無論如何都是經典 CAN 幀中有效負載長度的 1:1 對映。有效負載長度到匯流排相關 DLC 對映僅在 CAN 驅動程式內部執行,最好使用輔助函式 can_fd_dlc2len() 和 can_fd_len2dlc()。
CAN netdevice 驅動程式功能可以透過網路裝置的最大傳輸單元 (MTU) 來區分
MTU = 16 (CAN_MTU) => sizeof(struct can_frame) => Classical CAN device
MTU = 72 (CANFD_MTU) => sizeof(struct canfd_frame) => CAN FD capable device
例如,可以使用 SIOCGIFMTU ioctl() 系統呼叫檢索 CAN 裝置 MTU。注意:支援 CAN FD 的裝置也可以處理和傳送經典 CAN 幀。
配置支援 CAN FD 的 CAN 控制器時,必須設定額外的“data”位元率。CAN FD 幀的資料階段的此位元率必須至少是為仲裁階段配置的位元率。第二個位元率與第一個位元率類似指定,但“data”位元率的位元率設定關鍵字以“d”開頭,例如 dbitrate、dsample-point、dsjw 或 dtq 和類似設定。當在配置過程中設定了資料位元率時,可以指定控制器選項“fd on”以在 CAN 控制器中啟用 CAN FD 模式。此控制器選項還會將裝置 MTU 切換為 72 (CANFD_MTU)。
在國際 CAN 會議 2012 上作為白皮書提出的第一個 CAN FD 規範需要改進,以確保資料完整性。因此,今天必須區分兩種 CAN FD 實現
ISO 相容:ISO 11898-1:2015 CAN FD 實現(預設)
非 ISO 相容:遵循 2012 年白皮書的 CAN FD 實現
最後有三種類型的 CAN FD 控制器
ISO 相容(固定)
非 ISO 相容(固定,如 m_can.c 中的 M_CAN IP core v3.0.1)
ISO/非 ISO CAN FD 控制器(可切換,如 PEAK PCAN-USB FD)
當前 ISO/非 ISO 模式由 CAN 控制器驅動程式透過 netlink 宣佈,並由“ip”工具(控制器選項 FD-NON-ISO)顯示。只能為可切換 CAN FD 控制器設定“fd-non-iso {on|off}”來更改 ISO/非 ISO 模式。
示例:配置 500 kbit/s 仲裁位元率和 4 Mbit/s 資料位元率
$ ip link set can0 up type can bitrate 500000 sample-point 0.75 \
dbitrate 4000000 dsample-point 0.8 fd on
$ ip -details link show can0
5: can0: <NOARP,UP,LOWER_UP,ECHO> mtu 72 qdisc pfifo_fast state UNKNOWN \
mode DEFAULT group default qlen 10
link/can promiscuity 0
can <FD> state ERROR-ACTIVE (berr-counter tx 0 rx 0) restart-ms 0
bitrate 500000 sample-point 0.750
tq 50 prop-seg 14 phase-seg1 15 phase-seg2 10 sjw 1
pcan_usb_pro_fd: tseg1 1..64 tseg2 1..16 sjw 1..16 brp 1..1024 \
brp-inc 1
dbitrate 4000000 dsample-point 0.800
dtq 12 dprop-seg 7 dphase-seg1 8 dphase-seg2 4 dsjw 1
pcan_usb_pro_fd: dtseg1 1..16 dtseg2 1..8 dsjw 1..4 dbrp 1..1024 \
dbrp-inc 1
clock 80000000
在此可切換 CAN FD 介面卡上新增“fd-non-iso on”時的示例
can <FD,FD-NON-ISO> state ERROR-ACTIVE (berr-counter tx 0 rx 0) restart-ms 0
支援的 CAN 硬體¶
請檢視“drivers/net/can”中的“Kconfig”檔案以獲取支援的 CAN 硬體的實際列表。在 SocketCAN 專案網站上(參見SocketCAN 資源),可能還有其他驅動程式可用,也適用於舊版本的核心。
SocketCAN 資源¶
Linux CAN / SocketCAN 專案資源(專案站點/郵件列表)在 Linux 原始碼樹的 MAINTAINERS 檔案中引用。搜尋 CAN NETWORK [LAYERS|DRIVERS]。
致謝¶
Oliver Hartkopp (PF_CAN 核心、過濾器、驅動程式、bcm、SJA1000 驅動程式)
Urs Thuermann (PF_CAN 核心、核心整合、套接字介面、raw、vcan)
Jan Kizka (RT-SocketCAN 核心、套接字 API 協調)
Wolfgang Grandegger (RT-SocketCAN 核心 & 驅動程式、Raw Socket-API 審查、CAN 裝置驅動程式介面、MSCAN 驅動程式)
Robert Schwebel (設計審查、PTXdist 整合)
Marc Kleine-Budde (設計審查、Kernel 2.6 清理、驅動程式)
Benedikt Spranger (審查)
Thomas Gleixner (LKML 審查、編碼風格、釋出提示)
Andrey Volkov (核心子樹結構、ioctls、MSCAN 驅動程式)
Matthias Brukner (第一個 SJA1000 CAN netdevice 實現 Q2/2003)
Klaus Hitschler (PEAK 驅動程式整合)
Uwe Koppe (具有 PF_PACKET 方法的 CAN netdevices)
Michael Schulze (驅動程式層環回要求、RT CAN 驅動程式審查)
Pavel Pisa (位元率計算)
Sascha Hauer (SJA1000 平臺驅動程式)
Sebastian Haas (SJA1000 EMS PCI 驅動程式)
Markus Plessing (SJA1000 EMS PCI 驅動程式)
Per Dalen (SJA1000 Kvaser PCI 驅動程式)
Sam Ravnborg (審查、編碼風格、kbuild 幫助)