J1939 文件

概述 / 什麼是 J1939

SAE J1939 在 CAN 上定義了一個更高的層協議。它實現了一個更復雜的定址方案,並將最大資料包大小擴充套件到 8 位元組以上。存在一些派生的規範,它們在應用層面上與原始 J1939 不同,例如 MilCAN A、NMEA2000,特別是 ISO-11783 (ISOBUS)。最後一個規範指定了所謂的 ETP(擴充套件傳輸協議),該協議已包含在此實現中。這導致最大資料包大小為 ((2 ^ 24) - 1) * 7 位元組 == 111 MiB。

使用的規範

  • SAE J1939-21 : 資料鏈路層

  • SAE J1939-81 : 網路管理

  • ISO 11783-6 : 虛擬終端(擴充套件傳輸協議)

動機

鑑於存在類似於 BSD 套接字的 API 的 SocketCAN,我們發現了一些理由來證明 J1939 使用的定址和傳輸方法的核心實現是合理的。

  • 定址:當 ECU 上的程序透過 J1939 進行通訊時,它不一定需要知道其源地址。雖然每個 ECU 至少有一個程序應該知道源地址。其他程序應該能夠重用該地址。這樣,為同一個 ECU 協作的不同程序的地址引數就不會重複。這種工作方式與 UNIX 概念密切相關,即程式只做一件事,並且做得很好。

  • 動態定址: J1939 中的地址宣告對時間要求很高。此外,在地址協商期間,應正確處理資料傳輸。將此功能放入核心消除了 _每個_ 透過 J1939 通訊的使用者空間程序的要求。這產生了一個具有適當定址的一致的 J1939 匯流排。

  • 傳輸: TP 和 ETP 都重用一些 PGN 來透過它們中繼大資料包。因此,不同的程序可能會使用相同的 TP 和 ETP PGN,而實際上並不知道。不同的程序之間的各個 TP 和 ETP 會話 _必須_ 被序列化(同步)。核心正確地解決了這個問題,並消除了 _每個_ 透過 J1939 通訊的使用者空間程序對序列化(同步)的要求。

J1939 定義了一些其他功能(中繼、閘道器、快速資料包傳輸等)。這些功能的核心內程式碼不會有助於協議的穩定性。因此,這些部分留給使用者空間處理。

J1939 套接字在 CAN 網路裝置上執行(參見 SocketCAN)。任何在 CAN 原始套接字上執行的 J1939 使用者空間庫仍然可以正常執行。由於此類庫不與核心內實現通訊,因此必須注意這兩個庫不要相互干擾。實際上,這意味著它們不能共享 ECU 地址。庫或核心內系統專門使用單個 ECU(或虛擬 ECU)地址。

J1939 概念

傳送到 J1939 堆疊的資料

從使用者空間傳送到 J1939 堆疊的資料緩衝區本身不是 CAN 幀。相反,它們是有效負載,J1939 堆疊根據緩衝區的大小和傳輸型別將其轉換為適當的 CAN 幀。緩衝區的大小會影響堆疊處理資料的方式,並確定用於傳輸的內部程式碼路徑。

不同緩衝區大小的處理

  • 大小為 8 位元組或更小的緩衝區

    • 這些在內部由堆疊作為簡單會話處理。

    • 堆疊直接將緩衝區轉換為單個 CAN 幀,而無需分片。

    • 這種型別的傳輸不需要接收端的實際客戶端(接收器)。

  • 最大 1785 位元組的緩衝區

    • 這些會自動作為 J1939 傳輸協議 (TP) 傳輸處理。

    • 在內部,堆疊將緩衝區拆分為多個 8 位元組的 CAN 幀。

    • TP 傳輸可以是單播或廣播。

    • 廣播 TP:不需要另一端的接收器,並且可以用於廣播場景。

    • 單播 TP:需要在另一端有一個活動的接收器(客戶端)來確認傳輸。

  • 從 1786 位元組到 111 MiB 的緩衝區

    • 這些作為 ISO 11783 擴充套件傳輸協議 (ETP) 傳輸處理。

    • ETP 傳輸用於更大的有效負載,並在內部拆分為多個 CAN 幀。

    • ETP 傳輸(單播):需要在另一端有一個接收器來處理傳入資料並確認傳輸的每個步驟。

    • ETP 傳輸不能像 TP 傳輸一樣廣播,並且始終需要接收器才能執行。

使用 `MSG_DONTWAIT` 的非阻塞操作

當與 MSG_DONTWAIT 標誌結合使用時,J1939 堆疊支援非阻塞操作。在此模式下,堆疊會嘗試獲取儘可能多的資料,以socket的可用記憶體允許的範圍內。它返回成功獲取的資料量,使用者空間負責監視此值並處理部分傳輸。

  • 如果堆疊無法獲取整個緩衝區,它會返回成功獲取的位元組數,使用者空間應處理剩餘部分。

  • 錯誤處理:使用 MSG_DONTWAIT 時,使用者必須依靠錯誤佇列來檢測傳輸錯誤。有關如何訂閱錯誤通知的詳細資訊,請參閱 SO_J1939_ERRQUEUE 部分。如果沒有錯誤佇列,使用者空間就沒有其他方式來通知非阻塞操作期間的傳輸錯誤。

行為和要求

  • 簡單傳輸(<= 8 位元組):不需要另一端的接收器,因此可以輕鬆傳送,而無需地址宣告或與目標協調。

  • 單播 TP/ETP:需要在另一端有一個接收器才能完成傳輸。接收器必須確認傳輸才能使會話成功進行。

  • 廣播 TP:允許在沒有接收器的情況下發送資料,但僅適用於 TP 傳輸。ETP 不能廣播,並且始終需要接收客戶端。

這些不同的行為在很大程度上取決於提供給堆疊的緩衝區的大小,並且會根據有效負載大小選擇適當的傳輸機制(TP 或 ETP)。堆疊會自動管理大型有效負載的分片和重組,並確保為每個會話生成和傳輸正確的 CAN 幀。

PGN

J1939 協議使用具有以下結構的 29 位 CAN 識別符號

29 位 CAN-ID

CAN-ID 中的位位置

28 ... 26

25 ... 8

7 ... 0

優先順序

PGN

SA(源地址)

PGN(引數組號)是一個用於標識資料包的數字。PGN 的組成如下

PGN

CAN-ID 中的位位置

25

24

23 ... 16

15 ... 8

R(保留)

DP(資料頁)

PF(PDU 格式)

PS(PDU 特定)

在 J1939-21 中,PDU1 格式(其中 PF < 240)和 PDU2 格式(其中 PF >= 240)之間存在區別。此外,當使用 PDU2 格式時,PS 欄位包含所謂的組擴充套件,它是 PGN 的一部分。當使用 PDU2 格式時,組擴充套件在 PS 欄位中設定。

PDU1 格式(特定)(點對點)

CAN-ID 中的位位置

23 ... 16

15 ... 8

00h ... EFh

DA(目標地址)

PDU2 格式(全域性)(廣播)

CAN-ID 中的位位置

23 ... 16

15 ... 8

F0h ... FFh

GE(組擴充套件)

另一方面,當使用 PDU1 格式時,PS 欄位包含所謂的目的地地址,該地址 _不是_ PGN 的一部分。當從使用者空間到核心(或反之亦然)通訊 PGN 並且使用 PDU1 格式時,PGN 的 PS 欄位應設定為零。目的地地址應在其他地方設定。

關於 PGN 到 29 位 CAN 識別符號的對映,目的地地址應由核心從識別符號的適當位獲取/設定到識別符號的適當位。

定址

可以使用靜態和動態定址方法。

對於靜態地址,核心不進行額外的檢查,並且提供的地址被認為是正確的。此責任由 OEM 或系統整合商承擔。

對於動態定址,即所謂的地址宣告,核心中預見了額外的支援。在 J1939 中,任何 ECU 都以其 64 位 NAME 標識。在成功宣告地址時,核心會跟蹤正在宣告的 NAME 和源地址。這用作篩選方案的基礎。預設情況下,將拒絕目的地不是本地的資料包。

允許混合模式資料包(從靜態地址到動態地址或反之亦然)。BSD 套接字為獲取/設定本地和遠端地址定義了單獨的 API 呼叫,這些呼叫適用於 J1939 套接字。

篩選

J1939 為每個套接字定義了白名單篩選器,使用者可以設定這些篩選器以便接收 J1939 流量的子集。篩選可以基於

  • SA

  • SOURCE_NAME

  • PGN

當單個套接字有多個篩選器,並且傳入的資料包與其中幾個篩選器匹配時,該套接字僅接收一次該資料包。

如何使用 J1939

API 呼叫

在 CAN 上,您首先需要開啟一個套接字才能透過 CAN 網路進行通訊。要使用 J1939,#include <linux/can/j1939.h>。從那裡,也將包含 <linux/can.h>。要開啟套接字,請使用

s = socket(PF_CAN, SOCK_DGRAM, CAN_J1939);

J1939 使用 SOCK_DGRAM 套接字。在 J1939 規範中,連線是在傳輸協議會話的上下文中提及的。這些會話仍然將資料包傳遞到另一端(使用多個 CAN 資料包)。不支援 SOCK_STREAM

成功建立套接字後,通常會使用 bind(2) 和/或 connect(2) 系統呼叫將套接字繫結到 CAN 介面。繫結和/或連線套接字後,您可以 read(2)write(2) 從/到套接字,或者像往常一樣在套接字上使用 send(2)sendto(2)sendmsg(2)recv*() 對應操作。下面還介紹了 J1939 特定的套接字選項。

為了傳送資料,必須成功執行 bind(2)bind(2) 將本地地址分配給套接字。

與 CAN 不同的是,有效負載資料只是傳送的資料,沒有其頭資訊。頭資訊源自提供給 bind(2)connect(2)sendto(2)recvfrom(2) 的 sockaddr。大小為 4 的 write(2) 將產生一個包含 4 個位元組的資料包。

sockaddr 結構具有擴充套件,可用於 J1939,如下所示

struct sockaddr_can {
   sa_family_t can_family;
   int         can_ifindex;
   union {
      struct {
         __u64 name;
                  /* pgn:
                   * 8 bit: PS in PDU2 case, else 0
                   * 8 bit: PF
                   * 1 bit: DP
                   * 1 bit: reserved
                   */
         __u32 pgn;
         __u8  addr;
      } j1939;
   } can_addr;
}

can_familycan_ifindex 的作用與其他 SocketCAN 套接字相同。

can_addr.j1939.pgn 指定 PGN(最大 0x3ffff)。各個位的指定如上所述。

can_addr.j1939.name 包含 64 位 J1939 NAME。

can_addr.j1939.addr 包含地址。

bind(2) 系統呼叫分配本地地址,即傳送資料包時的源地址。如果在 bind(2) 期間設定了 PGN,則它將用作 RX 篩選器。即,僅接收具有匹配 PGN 的資料包。如果設定了 ADDR 或 NAME,它也將用作接收篩選器。它將匹配傳入資料包的目標 NAME 或 ADDR。只有在 CAN 總線上為此名稱完成了適當的地址宣告並由核心註冊/快取後,NAME 篩選器才能工作。

另一方面,connect(2) 分配遠端地址,即目標地址。來自 connect(2) 的 PGN 用作傳送資料包時的預設 PGN。如果設定了 ADDR 或 NAME,它將用作預設的目標 ADDR 或 NAME。此外,在 connect(2) 期間設定的 ADDR 或 NAME 用作接收篩選器。它將匹配傳入資料包的源 NAME 或 ADDR。

write(2)send(2) 都將傳送一個數據包,其中包含來自 bind(2) 的本地地址和來自 connect(2) 的遠端地址。使用 sendto(2) 覆蓋目標地址。

如果設定了 can_addr.j1939.name(!= 0),則核心會查詢 NAME 並使用相應的 ADDR。如果未設定 can_addr.j1939.name(== 0),則使用 can_addr.j1939.addr

建立套接字時,會設定合理的預設值。可以使用 setsockopt(2)getsockopt(2) 修改某些選項。

與 RX 路徑相關的選項

  • SO_J1939_FILTER - 配置篩選器陣列

  • SO_J1939_PROMISC - 停用由 bind(2)connect(2) 設定的篩選器

預設情況下,無法傳送或接收廣播資料包。要啟用傳送或接收廣播資料包,請使用套接字選項 SO_BROADCAST

int value = 1;
setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &value, sizeof(value));

下圖說明了 RX 路徑

               +--------------------+
               |  incoming packet   |
               +--------------------+
                         |
                         V
               +--------------------+
               | SO_J1939_PROMISC?  |
               +--------------------+
                        |  |
                    no  |  | yes
                        |  |
              .---------'  `---------.
              |                      |
+---------------------------+        |
| bind() + connect() +      |        |
| SOCK_BROADCAST filter     |        |
+---------------------------+        |
              |                      |
              |<---------------------'
              V
+---------------------------+
|      SO_J1939_FILTER      |
+---------------------------+
              |
              V
+---------------------------+
|        socket recv()      |
+---------------------------+

與 TX 路徑相關的選項:SO_J1939_SEND_PRIO - 更改套接字的預設傳送優先順序

recvmsg(2)

在大多數情況下,如果您想提取比 recvfrom(2) 可以提供的更多資訊,則需要 recvmsg(2)。例如,資料包優先順序和時間戳。目標地址、名稱和資料包優先順序(如果適用)附加到 recvmsg(2) 呼叫中的 msghdr 中。可以使用 cmsg(3) 宏提取它們,其中 cmsg_level == SOL_J1939 && cmsg_type == SCM_J1939_DEST_ADDRSCM_J1939_DEST_NAMESCM_J1939_PRIO。返回的資料是 prioritydst_addruint8_t,以及 dst_nameuint64_t

uint8_t priority, dst_addr;
uint64_t dst_name;

for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
        switch (cmsg->cmsg_level) {
        case SOL_CAN_J1939:
                if (cmsg->cmsg_type == SCM_J1939_DEST_ADDR)
                        dst_addr = *CMSG_DATA(cmsg);
                else if (cmsg->cmsg_type == SCM_J1939_DEST_NAME)
                        memcpy(&dst_name, CMSG_DATA(cmsg), cmsg->cmsg_len - CMSG_LEN(0));
                else if (cmsg->cmsg_type == SCM_J1939_PRIO)
                        priority = *CMSG_DATA(cmsg);
                break;
        }
}

setsockopt(2)

setsockopt(2) 函式用於配置 J1939 通訊的各種套接字級別選項。支援以下選項

SO_J1939_FILTER

bind(2)connect(2) 的預設行為對於特定用例不足時,SO_J1939_FILTER 選項至關重要。預設情況下,bind(2)connect(2) 允許將套接字與單個單播或廣播地址關聯。但是,在某些情況下,需要對傳入訊息進行更精細的控制,例如按引數組號 (PGN) 而不是按地址進行篩選。

例如,在一個傳輸多種型別的 J1939 訊息的系統中,一個程序可能只對這些訊息的子集感興趣,例如特定的 PGN,而不希望接收所有傳送到其地址或廣播到匯流排的訊息。

透過應用 SO_J1939_FILTER 選項,您可以基於以下內容篩選訊息

  • 源地址 (SA):篩選來自特定源地址的訊息。

  • 源名稱:篩選來自具有特定 NAME 識別符號的 ECU 的訊息。

  • 引數組號 (PGN):專注於接收具有特定 PGN 的訊息,篩選掉不相關的訊息。

當出現以下情況時,此篩選機制特別有用

  • 您想根據訊息的 PGN 接收訊息的子集,即使地址相同。

  • 您需要處理廣播和單播訊息,但只關心某些訊息型別或引數。

  • bind(2)connect(2) 函式只允許繫結到單個地址,如果程序需要處理多個 PGN 但不想開啟多個套接字,則這可能不夠用。

要刪除現有的篩選器,您可以將 optval == NULLoptlen == 0 傳遞給 setsockopt(2)。這將清除所有當前設定的篩選器。如果要 更新 篩選器集,則必須將更新後的篩選器集傳遞給 setsockopt(2),因為新的篩選器集將完全 替換 舊的篩選器集。此行為可確保放棄任何先前的篩選器配置,並且僅應用新的篩選器集。

刪除所有篩選器的示例

setsockopt(sock, SOL_CAN_J1939, SO_J1939_FILTER, NULL, 0);

最大篩選器數量: 可以使用 SO_J1939_FILTER 應用的最大篩選器數量由 J1939_FILTER_MAX 定義,該值設定為 512。這意味著您可以配置最多 512 個單獨的篩選器以滿足您的特定篩選需求。

實際用例:監視地址宣告

一個實際的用例是透過篩選與地址宣告相關的特定 PGN 來監視 J1939 地址宣告過程。這允許程序監視和處理地址宣告,而無需處理不相關的訊息。

示例

struct j1939_filter filt[] = {
    {
        .pgn = J1939_PGN_ADDRESS_CLAIMED,
        .pgn_mask = J1939_PGN_PDU1_MAX,
    }, {
        .pgn = J1939_PGN_REQUEST,
        .pgn_mask = J1939_PGN_PDU1_MAX,
    }, {
        .pgn = J1939_PGN_ADDRESS_COMMANDED,
        .pgn_mask = J1939_PGN_MAX,
    },
};
setsockopt(sock, SOL_CAN_J1939, SO_J1939_FILTER, &filt, sizeof(filt));

在此示例中,套接字將僅接收具有與地址宣告相關的 PGN 的訊息:J1939_PGN_ADDRESS_CLAIMEDJ1939_PGN_REQUESTJ1939_PGN_ADDRESS_COMMANDED。這在您希望監視和處理地址宣告而不被 J1939 網路上的其他流量淹沒的情況下特別有用。

SO_J1939_PROMISC

SO_J1939_PROMISC 選項啟用套接字級別的混雜模式。啟用此選項後,套接字將接收所有 J1939 流量,而不管 bind()connect() 設定的任何篩選器。這類似於為乙太網介面啟用混雜模式,其中捕獲網路段上的所有流量。

但是,與 SO_J1939_PROMISC 相比,`SO_J1939_FILTER` 具有更高的優先順序。這意味著即使在混雜模式下,您也可以透過使用 SO_J1939_FILTER 應用特定的篩選器來減少接收到的資料包數量。篩選器將限制傳遞到套接字的資料包,從而允許在混雜模式處於活動狀態時進行更精細的流量選擇。

此選項的可接受值大小為 sizeof(int),並且該值僅在 0 和非零之間區分。值為 0 將停用混雜模式,而任何非零值將啟用它。

這種組合對於除錯或監視特定型別的流量,同時仍然捕獲廣泛的訊息集很有用。

示例

int value = 1;
setsockopt(sock, SOL_CAN_J1939, SO_J1939_PROMISC, &value, sizeof(value));

在此示例中,將 value 設定為任何非零值(例如,1)將啟用混雜模式,從而允許套接字接收網路上的所有 J1939 流量。

SO_BROADCAST

SO_BROADCAST 選項啟用廣播訊息的傳送和接收。預設情況下,J1939 套接字停用了廣播訊息。啟用此選項後,將允許套接字在 J1939 網路上傳送和接收廣播資料包。

由於 CAN 匯流排是一種共享介質,因此總線上傳送的所有訊息對所有參與者都是可見的。在 J1939 的上下文中,廣播指的是使用特定的目標地址欄位,其中目標地址被設定為一個表示訊息是傳送給所有參與者的值(通常是全域性地址,例如 0xFF)。啟用廣播選項允許套接字傳送和接收此類廣播訊息。

此選項可接受的值大小為 sizeof(int),並且該值僅區分 0 和非零值。值為 0 時停用傳送和接收廣播訊息的能力,而任何非零值都將啟用它。

示例

int value = 1;
setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &value, sizeof(value));

在此示例中,將 value 設定為任何非零值(例如,1)都將啟用套接字以傳送和接收廣播訊息。

SO_J1939_SEND_PRIO

SO_J1939_SEND_PRIO 選項設定套接字發出的 J1939 訊息的優先順序。在 J1939 中,訊息可以具有不同的優先順序,並且數值越小表示優先順序越高。此選項允許使用者透過調整 CAN 識別符號中的優先順序位來控制從套接字傳送的訊息的優先順序。

此選項可接受的 大小sizeof(int),並且該值應在 0 到 7 的範圍內,其中 0 是最高優先順序,7 是最低優先順序。預設情況下,如果未顯式配置此選項,則優先順序設定為 6

請注意,只有當程序具有 CAP_NET_ADMIN 許可權時,才能設定優先順序值 01。這些值保留用於高優先順序流量,並且需要管理許可權。

示例

int prio = 3;  // Priority value between 0 (highest) and 7 (lowest)
setsockopt(sock, SOL_CAN_J1939, SO_J1939_SEND_PRIO, &prio, sizeof(prio));

在此示例中,優先順序設定為 3,這意味著發出的訊息將以中等優先順序級別傳送。

SO_J1939_ERRQUEUE

SO_J1939_ERRQUEUE 選項使套接字能夠從錯誤佇列接收錯誤訊息,從而提供有關 J1939 通訊期間發生的傳輸失敗、協議違規或其他問題的診斷資訊。設定此選項後,使用者空間需要處理 MSG_ERRQUEUE 訊息。

SO_J1939_ERRQUEUE 設定為 0 將清除錯誤佇列中當前存在的任何錯誤訊息。啟用後,可以使用 recvmsg(2) 系統呼叫檢索錯誤訊息。

訂閱錯誤佇列時,可以訪問以下錯誤事件

  • ``J1939_EE_INFO_TX_ABORT``:傳輸中止錯誤。

  • ``J1939_EE_INFO_RX_RTS``:接收 RTS(傳送請求)控制幀。

  • ``J1939_EE_INFO_RX_DPO``:接收帶有資料頁偏移量 (DPO) 的資料包。

  • ``J1939_EE_INFO_RX_ABORT``:接收中止錯誤。

錯誤佇列可用於使用會話 ID (tskey) 將錯誤與特定的訊息傳輸會話相關聯。會話 ID 透過 SOF_TIMESTAMPING_OPT_ID 標誌分配,該標誌透過啟用 SO_TIMESTAMPING 選項來設定。

如果激活了 SO_J1939_ERRQUEUE,則使用者需要從錯誤佇列中拉取訊息,這意味著僅使用 recv(2) 已不再足夠。使用者必須使用帶有適當標誌的 recvmsg(2) 來處理錯誤訊息。否則可能會導致套接字因佇列中未處理的錯誤訊息而被阻塞。

強烈建議在大多數情況下將 SO_J1939_ERRQUEUESO_TIMESTAMPING 結合使用。這樣可以實現適當的錯誤處理以及會話跟蹤和時間戳記錄,從而提供對訊息傳輸和錯誤的更詳細分析。

此選項可接受的 大小sizeof(int),並且該值僅區分 0 和非零值。值為 0 時停用錯誤佇列接收並清除任何現有錯誤訊息,而任何非零值都將啟用它。

示例

int enable = 1;  // Enable error queue reception
setsockopt(sock, SOL_CAN_J1939, SO_J1939_ERRQUEUE, &enable, sizeof(enable));

// Enable timestamping with session tracking via tskey
int timestamping = SOF_TIMESTAMPING_OPT_ID | SOF_TIMESTAMPING_TX_ACK |
                   SOF_TIMESTAMPING_TX_SCHED |
                   SOF_TIMESTAMPING_RX_SOFTWARE | SOF_TIMESTAMPING_OPT_CMSG;
setsockopt(sock, SOL_SOCKET, SO_TIMESTAMPING, &timestamping,
           sizeof(timestamping));

啟用後,可以使用 recvmsg(2) 檢索錯誤訊息。透過將 SO_J1939_ERRQUEUESO_TIMESTAMPING(啟用 SOF_TIMESTAMPING_OPT_IDSOF_TIMESTAMPING_OPT_CMSG)相結合,使用者可以跟蹤訊息傳輸,檢索精確的時間戳,並將錯誤與特定會話相關聯。

有關啟用時間戳和會話跟蹤的更多資訊,請參閱 SO_TIMESTAMPING 部分。

SO_TIMESTAMPING

SO_TIMESTAMPING 選項允許套接字接收與 J1939 中的訊息傳輸和接收相關的各種事件的時間戳。此選項通常與 SO_J1939_ERRQUEUE 結合使用,以提供詳細的診斷資訊、會話跟蹤和訊息傳輸的精確計時資料。

在 J1939 中,使用者空間提供的所有有效負載,無論大小,都由核心作為會話處理。這包括單幀訊息(最多 8 個位元組)和多幀協議,例如傳輸協議 (TP) 和擴充套件傳輸協議 (ETP)。即使對於小的單幀訊息,核心也會建立一個會話來管理傳輸和接收。會話的概念允許核心管理協議的各個方面,例如重新組裝多幀訊息和跟蹤傳輸的狀態。

從錯誤佇列接收擴充套件錯誤訊息時,錯誤資訊透過 struct sock_extended_err 傳遞,該結構可以透過使用 recvmsg(2) 系統呼叫檢索的控制訊息 (cmsg) 訪問。

J1939 中擴充套件錯誤訊息通常有兩個來源

  1. serr->ee_origin == SO_EE_ORIGIN_TIMESTAMPING:

    在這種情況下,serr->ee_info 欄位將包含以下時間戳型別之一

    • SCM_TSTAMP_SCHED:此時間戳對於擴充套件傳輸協議 (ETP) 傳輸和簡單傳輸(8 位元組或更少)有效。它指示訊息或一組幀何時被安排用於傳輸。

      • 對於簡單傳輸(8 位元組或更少),它標記訊息排隊並準備好傳送到 CAN 匯流排的時間點。

      • 對於 ETP 傳輸,它在傳送方收到 CTS(允許傳送)幀後傳送,指示已安排一組新幀用於傳輸。

      • 傳輸協議 (TP) 情況目前未為此時間戳實現。

      • 在接收方,ETP 的此事件對應項由 J1939_EE_INFO_RX_DPO 訊息表示,該訊息指示接收到資料頁偏移量 (DPO) 控制幀。

    • SCM_TSTAMP_ACK:此時間戳指示訊息或會話的確認。

      • 對於簡單傳輸(8 位元組或更少),它標記訊息已傳送並且已收到來自 CAN 控制器的回聲確認,指示該幀已傳輸到總線上。

      • 對於多幀傳輸(TP 或 ETP),它表示整個會話已被確認,通常在接收到訊息結束確認 (EOMA) 資料包之後。

  2. serr->ee_origin == SO_EE_ORIGIN_LOCAL:

    在這種情況下,serr->ee_info 欄位將包含以下 J1939 堆疊特定的訊息型別之一

    • J1939_EE_INFO_TX_ABORT:此訊息指示訊息或會話的傳輸已中止。中止的原因可能來自各種來源

      • CAN 堆疊故障:J1939 堆疊無法將幀傳遞到 CAN 框架以進行傳輸。

      • 回聲失敗:J1939 堆疊未收到來自 CAN 控制器的回聲確認,這意味著該幀可能未成功傳輸到 CAN 匯流排。

      • 協議級別問題:對於多幀傳輸 (TP/ETP),這可能包括與協議相關的錯誤,例如接收方發出的中止訊號或協議級別的超時,這會導致會話過早終止。

      • 相應的錯誤程式碼儲存在 serr->ee_data(核心端的 session->err)中,提供了有關中止的具體原因的更多詳細資訊。

    • J1939_EE_INFO_RX_RTS:此訊息指示 J1939 堆疊已收到傳送請求 (RTS) 控制幀,指示使用傳輸協議 (TP) 或擴充套件傳輸協議 (ETP) 開始多幀傳輸。

      • 它通知接收方傳送方已準備好傳輸多幀訊息,幷包括有關總訊息大小和要傳送的幀數的詳細資訊。

      • 諸如 J1939_NLA_TOTAL_SIZEJ1939_NLA_PGNJ1939_NLA_SRC_NAMEJ1939_NLA_DEST_NAME 之類的統計資訊與 J1939_EE_INFO_RX_RTS 訊息一起提供,從而提供有關傳入傳輸的詳細資訊。

    • J1939_EE_INFO_RX_DPO:此訊息指示 J1939 堆疊已收到資料頁偏移量 (DPO) 控制幀,該控制幀是擴充套件傳輸協議 (ETP) 的一部分。

      • DPO 幀透過指示傳輸的資料中的偏移位置來表示 ETP 多幀訊息的繼續。它透過識別正在接收的訊息的哪個部分來幫助接收方管理大型資料集。

      • 它通常與傳送方的對應 SCM_TSTAMP_SCHED 事件配對,該事件指示何時安排下一組幀進行傳輸。

      • 此事件包括諸如 J1939_NLA_BYTES_ACKED 之類的統計資訊,該統計資訊跟蹤在該會話中直到該點為止已確認的位元組數。

    • J1939_EE_INFO_RX_ABORT:此訊息指示多幀訊息(傳輸協議或擴充套件傳輸協議)的接收已中止。

      • 中止可能由協議級別錯誤(例如超時、意外幀或來自發送方的特定中止請求)觸發。

      • 此訊息表示接收方無法繼續處理傳輸,並且會話已終止。

      • 相應的錯誤程式碼儲存在 serr->ee_data(核心端的 session->err)中,從而提供有關中止原因的更多詳細資訊,例如協議違規或超時。

      • 收到此訊息後,接收方會丟棄部分接收的幀,並且多幀會話被認為是不完整的。

在這兩種情況下,如果啟用了 SOF_TIMESTAMPING_OPT_ID,則 serr->ee_data 將設定為會話的唯一識別符號 (session->tskey)。這允許使用者空間透過其會話識別符號跨多個幀或階段跟蹤訊息傳輸。

在所有其他情況下,serr->ee_errno 將設定為 ENOMSG,但 J1939_EE_INFO_TX_ABORTJ1939_EE_INFO_RX_ABORT 情況除外,在這些情況下,核心會將 serr->ee_data 設定為儲存在 session->err 中的錯誤。所有特定於協議的錯誤都將轉換為標準核心錯誤值並存儲在 session->err 中。這些錯誤值在系統呼叫和 serr->ee_errno 中統一。一些已知的錯誤值在 J1939 堆疊中的錯誤程式碼 部分中進行了描述。

當提供 J1939_EE_INFO_RX_RTS 訊息時,它將包括以下多幀訊息(TP 和 ETP)的統計資訊

  • J1939_NLA_TOTAL_SIZE:會話中訊息的總大小。

  • J1939_NLA_PGN:引數組號 (PGN),用於標識訊息型別。

  • J1939_NLA_SRC_NAME:源 ECU 的 64 位名稱。

  • J1939_NLA_DEST_NAME:目標 ECU 的 64 位名稱。

  • J1939_NLA_SRC_ADDR:傳送 ECU 的 8 位源地址。

  • J1939_NLA_DEST_ADDR:接收 ECU 的 8 位目標地址。

  • 對於其他訊息(包括單幀訊息),僅包括以下統計資訊

    • J1939_NLA_BYTES_ACKED:會話中成功確認的位元組數。

SO_TIMESTAMPING 的主要標誌包括

  • SOF_TIMESTAMPING_OPT_ID:啟用為每個傳輸使用唯一會話識別符號 (tskey)。此識別符號有助於跟蹤訊息傳輸和錯誤,使其成為使用者空間中的不同會話。啟用此選項後,serr->ee_data 將設定為 session->tskey

  • SOF_TIMESTAMPING_OPT_CMSG:透過控制訊息 (struct scm_timestamping) 傳送時間戳資訊,允許應用程式檢索與資料一起的時間戳。

  • SOF_TIMESTAMPING_TX_SCHED:提供訊息安排用於傳輸時的時間戳 (SCM_TSTAMP_SCHED)。

  • SOF_TIMESTAMPING_TX_ACK:提供訊息傳輸已完全確認時的時間戳 (SCM_TSTAMP_ACK)。

  • SOF_TIMESTAMPING_RX_SOFTWARE:提供與接收相關的事件的時間戳(例如,J1939_EE_INFO_RX_RTSJ1939_EE_INFO_RX_DPOJ1939_EE_INFO_RX_ABORT)。

這些標誌支援對訊息生命週期的詳細監視,包括傳輸排程、確認、接收時間戳以及收集有關通訊會話的詳細統計資訊,特別是對於像 TP 和 ETP 這樣的多幀有效負載。

示例

// Enable timestamping with various options, including session tracking and
// statistics
int sock_opt = SOF_TIMESTAMPING_OPT_CMSG |
               SOF_TIMESTAMPING_TX_ACK |
               SOF_TIMESTAMPING_TX_SCHED |
               SOF_TIMESTAMPING_OPT_ID |
               SOF_TIMESTAMPING_RX_SOFTWARE;

setsockopt(sock, SOL_SOCKET, SO_TIMESTAMPING, &sock_opt, sizeof(sock_opt));

動態定址

必須區分使用已宣告的地址和進行地址宣告。要使用已宣告的地址,必須填寫 j1939.name 成員並將其提供給 bind(2)。如果該名稱先前已宣告地址,則所有進一步傳送的訊息都將使用該地址。並且將忽略 j1939.addr 成員。

此例外的 PGN 為 0x0ee00。這是“地址宣告/無法宣告地址”訊息,如果需要,核心將使用該 PGN 的 j1939.addr 成員。

要宣告地址,可以使用以下程式碼示例

struct sockaddr_can baddr = {
        .can_family = AF_CAN,
        .can_addr.j1939 = {
                .name = name,
                .addr = J1939_IDLE_ADDR,
                .pgn = J1939_NO_PGN,    /* to disable bind() rx filter for PGN */
        },
        .can_ifindex = if_nametoindex("can0"),
};

bind(sock, (struct sockaddr *)&baddr, sizeof(baddr));

/* for Address Claiming broadcast must be allowed */
int value = 1;
setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &value, sizeof(value));

/* configured advanced RX filter with PGN needed for Address Claiming */
const struct j1939_filter filt[] = {
        {
                .pgn = J1939_PGN_ADDRESS_CLAIMED,
                .pgn_mask = J1939_PGN_PDU1_MAX,
        }, {
                .pgn = J1939_PGN_REQUEST,
                .pgn_mask = J1939_PGN_PDU1_MAX,
        }, {
                .pgn = J1939_PGN_ADDRESS_COMMANDED,
                .pgn_mask = J1939_PGN_MAX,
        },
};

setsockopt(sock, SOL_CAN_J1939, SO_J1939_FILTER, &filt, sizeof(filt));

uint64_t dat = htole64(name);
const struct sockaddr_can saddr = {
        .can_family = AF_CAN,
        .can_addr.j1939 = {
                .pgn = J1939_PGN_ADDRESS_CLAIMED,
                .addr = J1939_NO_ADDR,
        },
};

/* Afterwards do a sendto(2) with data set to the NAME (Little Endian). If the
 * NAME provided, does not match the j1939.name provided to bind(2), EPROTO
 * will be returned.
 */
sendto(sock, dat, sizeof(dat), 0, (const struct sockaddr *)&saddr, sizeof(saddr));

如果在傳輸後 250 毫秒內沒有人爭奪該地址宣告,則核心會將 NAME-SA 分配標記為有效。有效的分配將與其他有效的 NAME-SA 分配一起保留。從那時起,任何繫結到 NAME 的套接字都可以傳送資料包。

如果另一個 ECU 宣告該地址,則核心將標記 NAME-SA 已過期。繫結到 NAME 的任何套接字都無法傳送資料包(地址宣告除外)。要宣告另一個地址,繫結到 NAME 的某些套接字必須再次 bind(2),但僅將 j1939.addr 更改為新的 SA,然後必須傳送有效的地址宣告資料包。這將為此 NAME 重新啟動核心(以及總線上任何其他參與者)中的狀態機。

can-utils 還包括 j1939acd 工具,因此它可以用作程式碼示例或預設的地址宣告守護程式。

傳送示例

靜態定址

此示例將從 SA 0x20 向 DA 0x30 傳送 PGN (0x12300)。

繫結

struct sockaddr_can baddr = {
        .can_family = AF_CAN,
        .can_addr.j1939 = {
                .name = J1939_NO_NAME,
                .addr = 0x20,
                .pgn = J1939_NO_PGN,
        },
        .can_ifindex = if_nametoindex("can0"),
};

bind(sock, (struct sockaddr *)&baddr, sizeof(baddr));

現在,套接字“sock”已繫結到 SA 0x20。由於沒有呼叫 connect(2),因此此時我們只能使用 sendto(2)sendmsg(2)

傳送

const struct sockaddr_can saddr = {
        .can_family = AF_CAN,
        .can_addr.j1939 = {
                .name = J1939_NO_NAME;
                .addr = 0x30,
                .pgn = 0x12300,
        },
};

sendto(sock, dat, sizeof(dat), 0, (const struct sockaddr *)&saddr, sizeof(saddr));

J1939 堆疊中的錯誤程式碼

本節列出了與 J1939 堆疊互動時可能暴露給使用者空間的所有潛在核心錯誤程式碼。它包括標準錯誤程式碼和源自特定於協議的中止程式碼的錯誤程式碼。

  • EAGAIN:操作將阻塞;重試可能會成功。一個常見原因是存在活動的 TP 或 ETP 會話,並且嘗試在同一對等方之間啟動新的重疊 TP 或 ETP 會話。

  • ENETDOWN:網路已關閉。當 CAN 介面切換到“down”狀態時,會發生這種情況。

  • ENOBUFS:沒有可用的緩衝區空間。當 CAN 介面的傳輸 (TX) 佇列已滿並且無法再將任何訊息排隊時,會發生此錯誤。

  • EOVERFLOW:值對於定義的資料型別來說太大。在 J1939 中,如果請求的資料位於排隊的緩衝區之外,則可能會發生這種情況。例如,如果 CTS(允許傳送)請求核心緩衝區中不可用的偏移量,因為使用者空間沒有提供足夠的資料。

  • EBUSY:裝置或資源正忙。例如,如果相同的會話已經處於活動狀態並且堆疊無法從該狀況中恢復,則會發生這種情況。

  • EACCES:許可權被拒絕。例如,嘗試傳送廣播訊息但未配置 SO_BROADCAST 時,可能會發生此錯誤。

  • EADDRNOTAVAIL:地址不可用。在以下情況下會發生此錯誤

    • 嘗試使用 getsockname(2) 檢索對等方的地址,但套接字未連線。

    • 嘗試將資料傳送到 NAME 或從 NAME 傳送資料,但 NAME 的地址宣告未執行或未被堆疊檢測到。

  • EBADFD:檔案描述符狀態不佳。如果出現以下情況,可能會發生此錯誤

    • 嘗試將資料傳送到未繫結的套接字。

    • 套接字已繫結但沒有源名稱,並且源地址為 J1939_NO_ADDR

    • can_ifindex 不正確。

  • EFAULT:地址錯誤。當堆疊無法複製自 sockptr 或複製到 sockptr、使用者空間的資料不足或使用者空間提供的緩衝區對於請求的資料來說不夠大時,最常發生此錯誤。

  • EINTR:在傳輸任何資料之前發生了訊號;請參閱 signal(7)

  • EINVAL:傳遞了無效引數。例如

    • msg->msg_namelen 小於 J1939_MIN_NAMELEN

    • addr->can_family 不等於 AF_CAN

    • 提供了不正確的 PGN。

  • ENODEV:沒有這樣的裝置。當找不到提供的 can_ifindex 的 CAN 網路裝置或者 can_ifindex 為 0 時,會發生這種情況。

  • ENOMEM:記憶體不足。通常與堆疊中的記憶體分配問題有關。

  • ENOPROTOOPT:協議不可用。如果請求的套接字選項不可用,則在使用 getsockopt(2)setsockopt(2) 時,可能會發生這種情況。

  • EDESTADDRREQ:需要目標地址。發生此錯誤

    • 對於 connect(2),如果 struct sockaddr *uaddrNULL

    • 對於 send*(2),如果嘗試將 ETP 訊息傳送到廣播地址。

  • EDOM:引數超出域。如果嘗試將 TP 或 ETP 訊息傳送到保留用於 TP 或 ETP 操作的控制 PGN 的 PGN,則可能會發生此錯誤。

  • EIO:I/O 錯誤。如果為 TP 或 ETP 會話提供給套接字的資料量與會話的已公佈資料量不匹配,則可能會發生這種情況。

  • ENOENT:沒有這樣的檔案或目錄。當堆疊嘗試傳輸 CTS 或 EOMA 但找不到匹配的接收套接字時,可能會發生這種情況。

  • ENOIOCTLCMD:套接字層沒有可用的 ioctl。

  • EPERM:操作不允許。例如,如果請求的操作需要 CAP_NET_ADMIN 許可權,則可能會發生這種情況。

  • ENETUNREACH:網路無法訪問。最有可能的是,當幀無法傳輸到 CAN 匯流排時,會發生這種情況。

  • ETIME:計時器已過期。如果在嘗試傳送簡單訊息時發生超時,例如,當未收到來自控制器的回聲訊息時,可能會發生這種情況。

  • EPROTO:協議錯誤。

    • 用於 J1939 中的各種協議級別錯誤,包括

      • 重複的序列號。

      • 意外的 EDPO 或 ECTS 資料包。

      • EDPO/ECTS 中的無效 PGN 或偏移量。

      • EDPO 資料包的數量超過了 CTS 允許的數量。

      • 任何其他協議級別錯誤。

  • EMSGSIZE: 訊息過長。

  • ENOMSG: 沒有可用的訊息。

  • EALREADY: ECU 已經參與一個或多個連線管理會話,並且無法支援另一個。

  • EHOSTUNREACH: 發生超時,會話已中止。

  • EBADMSG: 在活動資料傳輸期間接收到 CTS (允許傳送) 訊息,導致中止。

  • ENOTRECOVERABLE: 達到最大重傳請求限制,會話無法恢復。

  • ENOTCONN: 接收到意外的資料傳輸包。

  • EILSEQ: 接收到錯誤的序列號,軟體無法恢復。