【原創】EtherCAT主站IgH解析(一)--主站初始化、狀態機與EtherCAT報文

版權聲明:本文爲本文爲博主原創文章,轉載請註明出處。如有問題,歡迎指正。博客地址:https://www.cnblogs.com/wsg1100/

1 啓動腳本

igh通過腳本來啓動,可以是systemd、init.d或sysconfig。分別位於源碼script目錄下:

image-20210219114206819

對於systemd方式,編譯時由ethercat.service.in文件生成ethercat.serviceethercat.service中指定了執行文件爲ethercatctl.ethercatctl文件由``ethercatctl.in`生成。init.d和sysconfig類似,都是生成一個可執行腳本,且腳本完成的工作一致,主要完成加載主站模塊、網卡驅動、給主站內核模塊傳遞參數、卸載模塊等操作。

ethercat.conf共同的配置文件,配置主站使用的網卡、驅動等信息。下面看腳本start和stop所做的工作。

1.1 start

  1. 加載ec_master.ko

    模塊參數:

    • main_devices :主網卡MAC地址,多個main_devices 表示創建多個主站,MAC參數個數master_count。
    • backup_devices :備用網卡MAC地址,多個backup_devices 表示創建多個主站,MAC參數個數backup_count。
    • debug_level :調試level,調試信息輸出級別。
    • eoe_interfaces eoe接口,eoe_count表示eoe_interfaces 的個數。
    • eoe_autocreate 是否自動創建eoe handler。
    • pcap_size Pcap buffer size。
  2. 遍歷配置文件中/etc/sysconfig/ethercat的環境變量DEVICE_MODULES,位於ethercat.conf中。

  3. 在每個DEVICE_MODULES前添加前綴ec_替換,如DEVICE_MODULES爲igb的話,添加前綴後爲ec_igb

  4. modinfo檢查該模塊是否存在。

  5. 對於非genericrtdm的驅動,需要先將網卡與當前驅動unbindunbind後的網卡才能被新驅動接管

  6. 加載該驅動。

start加載了兩個內核模塊,ec_master.ko和網卡驅動ec_xxx.ko,ec_master先根據內核參數(網卡MAC)來創建主站實例,此時主站處於Orphaned phase。後續加載網卡驅動ec_xxx.ko,執行網卡驅動probe,根據MAC地址將網卡與主站實例匹配,此時主站得到操作的網卡設備,進入Idle phase。詳細過程見後文。

ecdev-offfer

1.2 stop

卸載內核模塊ec_master.ko和網卡驅動ec_xxx.ko。

  1. 遍歷配置文件中的環境變量DEVICE_MODULES。
  2. 在每個DEVICE_MODULES前添加前綴‘ec_’替換。
  3. lsmod檢查該模塊是否被加載。
  4. 卸載模塊。

後文“主站”和”master“均表示主站或主站實例對象,slave和從站表示從站或從站對象,中英混用,不刻意區分。

2 主站實例創建

start過程中執行insmod ec_master.ko,這個時候,先調用的就是 module_init 調用的初始化函數ec_init_module()。先根據參數main_devices 個數master_count,註冊master_count字符設備的主次設備號device_number和名稱。

if (master_count) {
        if (alloc_chrdev_region(&device_number,
                    0, master_count, "EtherCAT")) {
            EC_ERR("Failed to obtain device number(s)!\n");
...
        }
    }

 class = class_create(THIS_MODULE, "EtherCAT");

解析模塊參數得到MAC地址,保存到數組macs中。

 for (i = 0; i < master_count; i++) {
        ret = ec_mac_parse(macs[i][0], main_devices[i], 0);

        if (i < backup_count) {
            ret = ec_mac_parse(macs[i][1], backup_devices[i], 1);
        }
    }

分配master_count個主站對象的內存,調用ec_master_init()初始化這些實例。

    if (master_count) {
        if (!(masters = kmalloc(sizeof(ec_master_t) * master_count,
                        GFP_KERNEL))) {
            ...
        }
    }

    for (i = 0; i < master_count; i++) {
        ret = ec_master_init(&masters[i], i, macs[i][0], macs[i][1],
                    device_number, class, debug_level);
       ...
    }

2.1 Master Phases

igh中,狀態機是其核心思想,一切操作基於狀態機來執行,對創建的每個EtherCAT主站實例都需要經過如下階段轉換(見圖2.3),主站各階段操作如下:

image-20200917142438559

Orphaned phase 此時主站實例已經分配初始化,正在等待以太網設備連接,即還沒有與網卡驅動聯繫起來,此時無法使用總線通訊。

Idle phase 主站接受以太網設備所有請求,但沒有應用請求時該模式生效。master運行master狀態機(請參閱第5.3節),自動掃描總線上的從站,並從應用空間接口執行掛起操作(如SDO訪問)。該階段命令行工具能夠訪問總線,但無法進行過程數據交換,因爲還缺少總線配置。

Operation phase 可以通過提供總線配置和交換過程數據的應用程序請求主站。

2.2 數據報與狀態機

繼續看master初始化代碼ec_master_init前,我們先了解數據報與狀態機的關係,這對後續理解很有幫助。

數據報

EtherCAT是以以太網爲基礎的現場總線系統,EtherCAT使用標準的IEEE802.3以太網幀,在主站一側使用標準的以太網控制器,不需要額外的硬件。並在以太網幀頭使用以太網類型0x88A4來和其他以太網幀相區別(EtherCAT數據還可以通過UDP/IP 來傳輸,本文已忽略),標準的IEEE802.3以太網幀中數據部分爲EtherCAT的數據,標準的IEEE802.3以太網幀與EtherCAT數據幀關係如下:

ethercat-fram-format

EtherCAT數據位於以太網幀數據區,EtherCAT數據由EtherCAT頭若干EtherCAT數據報文組成。其中EtheRCAT頭中記錄了EtherCAT數據報的長度、和類型,類型爲1表示與從站通訊。EtherCAT數據報文內包含多個子報文,每個子報文又由子報文頭、數據和WKC域組成。子報文結構含義如下。

datagram-hy

整個EtherCAT網絡形成一個環狀,主站與從站之間是通過EtherCAT數據報來交互,一個EtherCAT報文從網卡TX發出後,從站ESC芯片EtherCAT報文進行交換數據,最後該報文回到主站。網上有個經典的EtherCAT動態圖.

認識EtherCAT數據幀結構後,我們看IgH內是如何表示一個EtherCAT數據報文的?EtherCAT數據報文在igh中用對象ec_datagram_t表示。

typedef struct {
    struct list_head queue; /**< 發送和接收時插入主站幀隊列. */
    struct list_head sent; /**< 已發送數據報的主站列表項. */
    ec_device_index_t device_index; /**< 發送/接收數據報的設備。 */
	
    ec_datagram_type_t type; /**< 幀類型 (APRD, BWR, etc.). */
    uint8_t address[EC_ADDR_LEN]; /**< Recipient address. */
    uint8_t *data; /**< 數據. */
    ec_origin_t data_origin; /**< 數據保存的地方. */
    size_t mem_size; /**< Datagram \a data memory size. */
    size_t data_size; /**< Size of the data in \a data. */
    uint8_t index; /**< Index (set by master). */
    uint16_t working_counter; /**< 工作計數. */
    ec_datagram_state_t state; /**數據幀狀態 */
#ifdef EC_HAVE_CYCLES
    cycles_t cycles_sent; /**< Time, 數據報何時發送. */
#endif
    unsigned long jiffies_sent; /**< Jiffies,數據報何時發送. */
#ifdef EC_HAVE_CYCLES
    cycles_t cycles_received; /**< Time, 何時被接收. */
#endif
    unsigned long jiffies_received; /**< Jiffies,何時被接收. */
    unsigned int skip_count; /**< 尚未收到的重新排隊數. */
    unsigned long stats_output_jiffies; /**< Last statistics output. */
    char name[EC_DATAGRAM_NAME_SIZE]; /**< Description of the datagram. */
} ec_datagram_t;

可以看到上面子報文中各字段大都在ec_datagram_t中有表示了,不過多介紹,其它幾個成員簡單介紹下,

device_index表示這個數據報是屬於哪個網卡設備發送接收的,IgH中一個master實例可以使用多個多個網卡設備來發送/接收EtherCAT數據幀,device_index就是表示master下的網絡設備的。

data_origin表示每個master管理着多個空閒的ec_datagram_t,這些ec_datagram_t分成了三類,給不同的狀態機使用,具體的後文馬上會細說,data_origin就是表示這個ec_datagram_t是屬於哪類的。

index表示該數據報是EtherCAT數據區的第index個子報文,在master發送一個完整的EtherCAT數據報時,組裝以太網數據幀時使用。

data這個指向子報文的數據內存區,由於每個子報文交換數據不同,其大小不同,所以數據區爲動態分配,mem_size表示的就是分配的大小。

data_size表示數據報操作的數據大小,比如該數據報用於讀從站的某個寄存器,該值就是這個寄存器的大小。

jiffies_sent、jiffies_received、cycles_sent、cycles_received使用不同時鐘方式是記錄數據幀發送接收時間的,用於統計。

​ 此處缺張圖。

ec_datagram_state_t表示數據報的狀態,每個數據報(ec_datagram_t)也是基於狀態來處理,有6種狀態:

  • EC_DATAGRAM_INIT :數據報已經初始化
  • EC_DATAGRAM_QUEUED :插入發送隊列準備發送
  • EC_DATAGRAM_SENT :已經發送(還存在隊列中)
  • EC_DATAGRAM_RECEIVED:該數據報已接收,並從發送隊列刪除
  • EC_DATAGRAM_TIMED_OUT :該數據報發送後,接收超時,從發送隊列刪除
  • EC_DATAGRAM_ERROR :發送和接收過程中出錯(從隊列刪除),校驗錯誤、不匹配等。

M位在master發送時組裝EtherCAT數據幀時確定,接收時也根據該位判斷後面還有沒有子報文。

數據報對象初始化由函數ec_datagram_init()完成:

void ec_datagram_init(ec_datagram_t *datagram /**< EtherCAT datagram. */)
{
    INIT_LIST_HEAD(&datagram->queue); // mark as unqueued
    datagram->device_index = EC_DEVICE_MAIN;  /*默認主設備使用*/
    datagram->type = EC_DATAGRAM_NONE;		/*數據報類型*/
    memset(datagram->address, 0x00, EC_ADDR_LEN);  /*數據報地址清零*/
    datagram->data = NULL;
    datagram->data_origin = EC_ORIG_INTERNAL; 	/*默認內部數據*/
    datagram->mem_size = 0;
    datagram->data_size = 0;
    datagram->index = 0x00;
    datagram->working_counter = 0x0000;
    datagram->state = EC_DATAGRAM_INIT; /*初始狀態*/
#ifdef EC_HAVE_CYCLES
    datagram->cycles_sent = 0;
#endif
    datagram->jiffies_sent = 0;
#ifdef EC_HAVE_CYCLES
    datagram->cycles_received = 0;
#endif
    datagram->jiffies_received = 0;
    datagram->skip_count = 0;
    datagram->stats_output_jiffies = 0;
    memset(datagram->name, 0x00, EC_DATAGRAM_NAME_SIZE);
}

狀態機

說完IgH數據報對象,我們來看有限狀態機(fsm),有限狀態機(fsm):表示有限個狀態以及在這些狀態之間的轉移和動作等行爲的數學模型。說起狀態機,相信大家大學時候都有用過吧,不管是單片機、FPGA,用它寫按鍵、菜單、協議處理、控制器什麼的爽的一塌糊塗。其實我們使用的計算機就是本就是基於狀態機作爲計算模型的,它對數字系統的設計具有十分重要的作用。另外Linux TCP協議也是由狀態機實現。同樣igh主站內部機制使用有限狀態機來實現,IgH內狀態機的基本表示如下:

struct ec_fsm_xxx {
    ec_datagram_t *datagram; /**< 主站狀態機使用的數據報對象 */
    void (*state)(ec_fsm_master_t *); /**< 狀態函數 */
    ....
};

每個狀態機管理着某個對象的狀態、功能實現的狀態裝換,而這些狀態轉換是基於EtherCAT數據報來進行的,所以IgH EtherCAT協議棧中,每個狀態機內都包含有指向該狀態機操作的數據報對象指針datagram和狀態執行的狀態函數void (*state)(ec_fsm_master_t *)

總結一句話:狀態機是根據數據報的狀態來執行,每個狀態機都對應一個數據報對象

現在知道了狀態機與數據報的關係,下面介紹IgH EtherCAT協議棧中有哪些狀態機,及狀態機使用的數據報對象是從哪裏分配如何管理的。

master狀態機

前面說到主站具有的三個階段,當主站與網卡設備attach後進入Idle phase,處於Idle phase後,開始執行主站狀態機。

主站狀態機包含1個主狀態機和許多子狀態機,matser狀態機主要目的是:

  • Bus monitoring 監控EtherCAT總線拓撲結構,如果發生改變,則重新掃描。

  • Slave conguration 監視從站的應用程序層狀態。如果從站未處於其應有的狀態,則從站將被(重新)配置

  • Request handling 請求(源自應用程序或外部來源)和處理,主站任務應該處理異步請求,例如:SII訪問,SDO訪問或類似。

主狀態機ec_fsm_master_t結構如下:

struct ec_fsm_master {
    ec_master_t *master; /**< master the FSM runs on */
    ec_datagram_t *datagram; /**< 主站狀態機使用的數據報對象 */
    unsigned int retries; /**< retries on datagram timeout. */

    void (*state)(ec_fsm_master_t *); /**< master state function */
    ec_device_index_t dev_idx; /**< Current device index (for scanning etc.).
                                */
    int idle; /**< state machine is in idle phase */
    unsigned long scan_jiffies; /**< beginning of slave scanning */
    uint8_t link_state[EC_MAX_NUM_DEVICES]; /**< Last link state for every
                                              device. */
    unsigned int slaves_responding[EC_MAX_NUM_DEVICES]; /**<每個設備的響應從站數。*/
    unsigned int rescan_required; /**< A bus rescan is required. */
    ec_slave_state_t slave_states[EC_MAX_NUM_DEVICES]; /**< AL states of
                                                         responding slaves for
                                                         every device. */
    ec_slave_t *slave; /**< current slave */
    ec_sii_write_request_t *sii_request; /**< SII write request */
    off_t sii_index; /**< index to SII write request data */
    ec_sdo_request_t *sdo_request; /**< SDO request to process. */

    ec_fsm_coe_t fsm_coe; /**< CoE state machine */
    ec_fsm_soe_t fsm_soe; /**< SoE state machine */
    ec_fsm_pdo_t fsm_pdo; /**< PDO configuration state machine. */
    ec_fsm_change_t fsm_change; /**< State change state machine */
    ec_fsm_slave_config_t fsm_slave_config; /**< slave state machine */
    ec_fsm_slave_scan_t fsm_slave_scan; /**< slave state machine */
    ec_fsm_sii_t fsm_sii; /**< SII state machine */
};

可以看到,主站狀態機結構下還有很多子狀態機,想象一下如果主站的所有功能通過一個狀態機來完成,那麼這個狀態機的狀態數量、各狀態之間的聯繫會有多恐怖,複雜性級別將會提高到無法管理的水平。爲此,IgH中,將EtherCAT主狀態機的某些功能用子狀態機完成。這有助於封裝相關工作流,並且避免“狀態爆炸”現象。這樣當主站完成coe功能時,可以由子狀態機fsm_coe去完成。具體各功能是如何通過狀態機完成的,文章後面會介紹。

slave狀態機

slave狀態機管理着每個從站的狀態,所以位於從站對象(ec_slave_t)內:

struct ec_slave
{
    ec_master_t *master; /**< Master owning the slave. */
.....
    ec_fsm_slave_t fsm; /**< Slave state machine. */
.....
};
struct ec_fsm_slave {
    ec_slave_t *slave; /**< slave the FSM runs on */
    struct list_head list; /**< Used for execution list. */
    ec_dict_request_t int_dict_request; /**< Internal dictionary request. */

    void (*state)(ec_fsm_slave_t *, ec_datagram_t *); /**< State function. */
    ec_datagram_t *datagram; /**< Previous state datagram. */
    ec_sdo_request_t *sdo_request; /**< SDO request to process. */
    ec_reg_request_t *reg_request; /**< Register request to process. */
    ec_foe_request_t *foe_request; /**< FoE request to process. */
    off_t foe_index; /**< Index to FoE write request data. */
    ec_soe_request_t *soe_request; /**< SoE request to process. */
    ec_eoe_request_t *eoe_request; /**< EoE request to process. */
    ec_mbg_request_t *mbg_request; /**< MBox Gateway request to process. */
    ec_dict_request_t *dict_request; /**< Dictionary request to process. */

    ec_fsm_coe_t fsm_coe; /**< CoE state machine. */
    ec_fsm_foe_t fsm_foe; /**< FoE state machine. */
    ec_fsm_soe_t fsm_soe; /**< SoE state machine. */
    ec_fsm_eoe_t fsm_eoe; /**< EoE state machine. */
    ec_fsm_mbg_t fsm_mbg; /**< MBox Gateway state machine. */
    ec_fsm_pdo_t fsm_pdo; /**< PDO configuration state machine. */
    ec_fsm_change_t fsm_change; /**< State change state machine */
    ec_fsm_slave_scan_t fsm_slave_scan; /**< slave scan state machine */
    ec_fsm_slave_config_t fsm_slave_config; /**< slave config state machine. */
};

slave狀態機和master狀態機類似,slave狀態機內還包含許多子狀態機。slave狀態機主要目的是:

  • 主站管理從站狀態
  • 主站與從站應用層(AL)通訊。比如具有EoE功能的從站,主站通過該從站下的子狀態機fsm_eoe來管理主站與從站應用層的EOE通訊。

數據報對象的管理

上面簡單介紹了IgH內的狀態機,fsm對象內只有數據報對象的指針,那fsm工作過程中的數據報對象從哪裏分配?

在master實例我們可以看到下面的數據報對象:

struct ec_master {
    ...
    ec_datagram_t fsm_datagram; /**< Datagram used for state machines. */
    ...
    ec_datagram_t ref_sync_datagram; /**< Datagram used for synchronizing the
                                       reference clock to the master clock.*/
    ec_datagram_t sync_datagram; /**< Datagram used for DC drift
                                   compensation. */
    ec_datagram_t sync_mon_datagram; /**< Datagram used for DC synchronisation
                                       monitoring. */
    ...
    ec_datagram_t ext_datagram_ring[EC_EXT_RING_SIZE]; 
}

這些數據報對象都是已經分配內存的(還未分配data內存),分爲三類:

  • 主站狀態機及子fsm使用的數據報對象:fsm_datagram

  • 外部數據報:用於slave狀態機及其子狀態機執行過程中動態使用

    • ext_datagram_ring[]
  • 應用數據報: 它並不固定屬於某個狀態機,主要通過接口提供給應用直接使用:

    • ref_sync_datagram
    • sync_datagram
    • sync64_datagram
    • sync_mon_datagram

初始化各狀態機需要先爲數據報數據區(data)分配內存,數據報內的數據區內存通過ec_datagram_prealloc()來分配.

int ec_datagram_prealloc(
        ec_datagram_t *datagram, /**< EtherCAT datagram. */
        size_t size /**< New payload size in bytes. */
        )
{
    if (datagram->data_origin == EC_ORIG_EXTERNAL
            || size <= datagram->mem_size)
        return 0;
	......

    if (!(datagram->data = kmalloc(size, GFP_KERNEL))) {
	......
    }

    datagram->mem_size = size;
    return 0;
}

數據區的大小爲一個以太網幀中單個Ethercat數據報的最大數據大小EC_MAX_DATA_SIZE

/** Size of an EtherCAT frame header. */
#define EC_FRAME_HEADER_SIZE 2

/** Size of an EtherCAT datagram header. */
#define EC_DATAGRAM_HEADER_SIZE 10

/** Size of an EtherCAT datagram footer. */
#define EC_DATAGRAM_FOOTER_SIZE 2

/** Size of the EtherCAT address field. */
#define EC_ADDR_LEN 4

/** Resulting maximum data size of a single datagram in a frame. */
#define EC_MAX_DATA_SIZE (ETH_DATA_LEN - EC_FRAME_HEADER_SIZE \
                          - EC_DATAGRAM_HEADER_SIZE - EC_DATAGRAM_FOOTER_SIZE)

由於以太網幀的大小有限,因此數據報的最大大小受到限制,即以太網幀長度 1500 - ethercat頭2byte- ethercat子數據報報頭10字節-WKC 2字節,如圖:

image-20210222222428543

如果過程數據鏡像的大小超過該限制,就必鬚髮送多個幀,並且必須對映像進行分區以使用多個數據報。 Domain自動進行管理。

2.3 master狀態機及數據報初始化

對狀態機及數據報對象有初步認識後,我們回到ec_master.ko模塊入口函數ec_init_module()主站實例初始化ec_master_init(),主要完成主站狀態機初始化及數據報:

    // init state machine datagram
    ec_datagram_init(&master->fsm_datagram); /*初始化數據報對象*/
    snprintf(master->fsm_datagram.name, EC_DATAGRAM_NAME_SIZE, "master-fsm");
    ret = ec_datagram_prealloc(&master->fsm_datagram, EC_MAX_DATA_SIZE);

    // create state machine object
    ec_fsm_master_init(&master->fsm, master, &master->fsm_datagram); /*初始化master fsm*/

其中ec_fsm_master_init初始化master fsm和子狀態機,並指定了master fsm使用的數據報對象fsm_datagram

void ec_fsm_master_init(
        ec_fsm_master_t *fsm, /**< Master state machine. */
        ec_master_t *master, /**< EtherCAT master. */
        ec_datagram_t *datagram /**< Datagram object to use. */
        )
{
    fsm->master = master;
    fsm->datagram = datagram;
    ec_fsm_master_reset(fsm);

    // init sub-state-machines
    ec_fsm_coe_init(&fsm->fsm_coe);
    ec_fsm_soe_init(&fsm->fsm_soe);
    ec_fsm_pdo_init(&fsm->fsm_pdo, &fsm->fsm_coe);
    ec_fsm_change_init(&fsm->fsm_change, fsm->datagram);
    ec_fsm_slave_config_init(&fsm->fsm_slave_config, fsm->datagram,
            &fsm->fsm_change, &fsm->fsm_coe, &fsm->fsm_soe, &fsm->fsm_pdo);
    ec_fsm_slave_scan_init(&fsm->fsm_slave_scan, fsm->datagram,
            &fsm->fsm_slave_config, &fsm->fsm_pdo);
    ec_fsm_sii_init(&fsm->fsm_sii, fsm->datagram);
}

初始化外部數據報隊列

外部數據報隊列用於從站狀態機,每個狀態機執行期間使用的數據報從該區域分配,下面是初始化ext_datagram_ring中每個結構:

     for (i = 0; i < EC_EXT_RING_SIZE; i++) {
        ec_datagram_t *datagram = &master->ext_datagram_ring[i];
        ec_datagram_init(datagram);
        snprintf(datagram->name, EC_DATAGRAM_NAME_SIZE, "ext-%u", i);
    }

非應用數據報隊列鏈表,如EOE數據報會插入該隊列後發送。

INIT_LIST_HEAD(&master->ext_datagram_queue);

同樣初始化幾個應用數據報對象,爲他們分配報文數據區,就不貼代碼了,需要注意的是數據報對象sync_mon_datagram,它的作用是用於同步監控,獲取從站系統時間差,所以是一個BRD數據報,在此直接將數據報操作偏移地址初始化。

    ec_datagram_init(&master->sync_mon_datagram);
	......
    ret = ec_datagram_brd(&master->sync_mon_datagram, 0x092c, 4);
地址 名稱 描述 復位值
0x092c~0x092F 0~30 系統時間差 本地系統時間副本與參考時鐘系統時間值之差 0
31 符號 0:本地系統時間≥參考時鐘時間
1:本地系統時間<參考時鐘時間
0

另外比較重要的是將使用的網卡MAC地址放到macs[]中,在網卡驅動probe過程中根據MAC來匹配主站使用哪個網卡。

    for (dev_idx = EC_DEVICE_MAIN; dev_idx < EC_MAX_NUM_DEVICES; dev_idx++) {
        master->macs[dev_idx] = NULL;
    }

    master->macs[EC_DEVICE_MAIN] = main_mac;

2.4 初始化EtherCAT device

master協議棧主要完成EtherCAT數據報的解析和組裝,然後需要再添加EtherNet報頭和FCS組成一個完整的以太網幀,最後通過網卡設備發送出去。爲與以太網設備驅動層解耦,igh使用ec_device_t來封裝底層以太網設備,一般來說每個master只有一個ec_device_t,這個編譯時配置決定:

struct ec_device
{
    ec_master_t *master; /**< EtherCAT master */
    struct net_device *dev; /**< 使用的網絡設備 */
    ec_pollfunc_t poll; /**< pointer to the device's poll function */
    struct module *module; /**< pointer to the device's owning module */
    uint8_t open; /**< true, if the net_device has been opened */
    uint8_t link_state; /**< device link state */
    struct sk_buff *tx_skb[EC_TX_RING_SIZE]; /**< transmit skb ring */
    unsigned int tx_ring_index; /**< last ring entry used to transmit */
#ifdef EC_HAVE_CYCLES
    cycles_t cycles_poll; /**< cycles of last poll */
#endif
#ifdef EC_DEBUG_RING
    struct timeval timeval_poll;
#endif
    unsigned long jiffies_poll; /**< jiffies of last poll */

    // Frame statistics
    u64 tx_count; /**< 發送的幀數 */
    u64 last_tx_count; /**<上次統計週期發送的幀數。 */
    u64 rx_count; /**< 接收的幀數 */
    u64 last_rx_count; /**< 上一個統計週期收到的幀數。 */
	
    u64 tx_bytes; /**< 發送的字節數 */
    u64 last_tx_bytes; /**< 上一個統計週期發送的字節數。 */
    u64 rx_bytes; /**< Number of bytes received. */
    u64 last_rx_bytes; /**< Number of bytes received of last statistics cycle.
                        */
    u64 tx_errors; /**< Number of transmit errors. */
    s32 tx_frame_rates[EC_RATE_COUNT]; /**< Transmit rates in frames/s for
                                         different statistics cycle periods.
                                        */
    s32 rx_frame_rates[EC_RATE_COUNT]; /**< Receive rates in frames/s for
                                         different statistics cycle periods.
                                        */
    s32 tx_byte_rates[EC_RATE_COUNT]; /**< Transmit rates in byte/s for
                                        different statistics cycle periods. */
    s32 rx_byte_rates[EC_RATE_COUNT]; /**< Receive rates in byte/s for
                                        different statistics cycle periods. */

......
};

成員*master表示改對象屬於哪個master,*dev指向使用的以太網設備net_device,poll該網絡設備poll函數,tx_skb[]以太網幀發送緩衝區隊列,需要發送的以太網幀會先放到該隊裏,tx_ring_index管理tx_skb[],以及一些網絡統計變量,下面初始化ec_device_t對象:

/*\master\master.c*/
    for (dev_idx = EC_DEVICE_MAIN; dev_idx < ec_master_num_devices(master);
            dev_idx++) {
        ret = ec_device_init(&master->devices[dev_idx], master);
        if (ret < 0) {
            goto out_clear_devices;
        }
    }
/*\master\device.c*/
int ec_device_init(
        ec_device_t *device, /**< EtherCAT device */
        ec_master_t *master /**< master owning the device */
        )
{
    int ret;
    unsigned int i;
    struct ethhdr *eth;
....

    device->master = master;
    device->dev = NULL;
    device->poll = NULL;
    device->module = NULL;
    device->open = 0;
    device->link_state = 0;
    for (i = 0; i < EC_TX_RING_SIZE; i++) {
        device->tx_skb[i] = NULL;
    }
......

    ec_device_clear_stats(device);
......
    for (i = 0; i < EC_TX_RING_SIZE; i++) {
        if (!(device->tx_skb[i] = dev_alloc_skb(ETH_FRAME_LEN))) {
            ......
        }

        // add Ethernet-II-header
        skb_reserve(device->tx_skb[i], ETH_HLEN);
        eth = (struct ethhdr *) skb_push(device->tx_skb[i], ETH_HLEN);
        eth->h_proto = htons(0x88A4);
        memset(eth->h_dest, 0xFF, ETH_ALEN);
    }
.....
}

主要關注分配以太網幀發送隊列內存tx_skb[],並填充Ethernet報頭中的以太網類型字段爲0x88A4,目標MAC地址0xFFFFFFFF FFFF,對於源MAC地址、sk_buff所屬網絡設備、ec_device_t對象使用的網絡設備net_device,將在網卡驅動初始化與master建立聯繫過程中設置。

2.5 設置IDLE 線程的發送間隔:

ec_master_set_send_interval(master, 1000000 / HZ);

根據網卡速率計算:

void ec_master_set_send_interval(
        ec_master_t *master, /**< EtherCAT master */
        unsigned int send_interval /**< Send interval */
        )
{
    master->send_interval = send_interval;  //發送間隔 us
    master->max_queue_size =
        (send_interval * 1000) / EC_BYTE_TRANSMISSION_TIME_NS;
    master->max_queue_size -= master->max_queue_size / 10;
}

100Mbps網卡發送一字節數據需要的時間EC_BYTE_TRANSMISSION_TIME_NS: 1/(100 MBit/s / 8 bit/byte) = 80 ns/byte.

2.6 初始化字符設備

由於主站位於內核空間,用戶空間應用與主站交互通過字符設備來交互;
創建普通字符設備,給普通linux應用和Ethercat tool使用。若使用xenomai或RTAI,則再創建實時字符設備,提供給實時應用使用。

	......
	master->class_device = device_create(class, NULL,
            MKDEV(MAJOR(device_number), master->index), NULL,
            "EtherCAT%u", master->index);
    ......
#ifdef EC_RTDM
    // init RTDM device
    ret = ec_rtdm_dev_init(&master->rtdm_dev, master);
   ...
#endif

到這裏明白了IgH中的狀態機與數據報之間的關係,主站對象也創建好了,但是主站還沒有網卡設備與之關聯,主站也還沒有工作,下面簡單看一下ecdev_offer流程。

關於網卡驅動代碼詳細解析推薦這兩篇文章:

Monitoring and Tuning the Linux Networking Stack: Sending Data

Monitoring and Tuning the Linux Networking Stack: Receiving Data

3 網卡

  1. 網卡probe

    static int igb_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
    {	
        ......
    	adapter->ecdev = ecdev_offer(netdev, ec_poll, THIS_MODULE);
    	if (adapter->ecdev) {	/*註冊打開ec_net設備*/
    		err = ecdev_open(adapter->ecdev);
    		.....
    		adapter->ec_watchdog_jiffies = jiffies;
    	} else { /*註冊普通網絡設備*/
    		......
    		err = register_netdev(netdev);
    		......
    	}
        ......
    }
    
  2. 給主站提供網絡設備:ecdev_offer

    根據MAC地址找到master下的ec_device_t對象

        device->dev = net_dev;
        device->poll = poll;
        device->module = module;
    

    上面我們只設置了ec_device_t->tx_skb[]中sk_buff的以太網類型和目的地址,現在繼續填充源MAC地址爲網卡的MAC地址、sk_buff所屬的net_device:

        for (i = 0; i < EC_TX_RING_SIZE; i++) {
            device->tx_skb[i]->dev = net_dev;
            eth = (struct ethhdr *) (device->tx_skb[i]->data);
            memcpy(eth->h_source, net_dev->dev_addr, ETH_ALEN);
        }
    
  3. 調用網絡設備接口打開網絡設備

int ec_device_open(
        ec_device_t *device /**< EtherCAT device */
        )
{
    int ret;
.....
    ret = device->dev->open(device->dev);
    if (!ret)
        device->open = 1;
....
    return ret;
}
  1. 當master下的所有的網絡設備都open後,master從ORPHANED轉到IDLE階段
int ec_master_enter_idle_phase(
        ec_master_t *master /**< EtherCAT master */
        )
{
    int ret;
    ec_device_index_t dev_idx;
	......
    master->send_cb = ec_master_internal_send_cb;
    master->receive_cb = ec_master_internal_receive_cb;
    master->cb_data = master;

    master->phase = EC_IDLE;  /*更新master狀態*/

    // reset number of responding slaves to trigger scanning
    for (dev_idx = EC_DEVICE_MAIN; dev_idx < ec_master_num_devices(master);
            dev_idx++) {
        master->fsm.slaves_responding[dev_idx] = 0;
    }

    ret = ec_master_nrthread_start(master, ec_master_idle_thread,
            "EtherCAT-IDLE");
    ....
    return ret;
}

其中主要設置master發送和接收回調函數,應用通過發送和接收數據時,將通過這兩接口直接發送和接收。創建master idle線程ec_master_idle_thread。

4 IDLE階段內核線程

Idle phase 主站接受以太網設備所有請求,但沒有應用request_master時處於IDLE階段。主站實例運行其狀態機,自動掃描總線上的從站,並從應用空間接口執行掛起操作(如SDO訪問)。此時命令行工具能夠訪問總線,但無法進行過程數據交換,因爲還缺少總線配置。

整個過程由內核線程ec_master_idle_thread完成。

static int ec_master_idle_thread(void *priv_data)
{
    ec_master_t *master = (ec_master_t *) priv_data;
    int fsm_exec;
#ifdef EC_USE_HRTIMER
    size_t sent_bytes;
#endif

    // send interval in IDLE phase
    ec_master_set_send_interval(master, 250000 / HZ);
    
    while (!kthread_should_stop()) {
        // receive
        ecrt_master_receive(master);
		......
        // execute master & slave state machines
        ......
        fsm_exec = ec_fsm_master_exec(&master->fsm);

        ec_master_exec_slave_fsms(master);
		......
        if (fsm_exec) {
            ec_master_queue_datagram(master, &master->fsm_datagram);
        }
        // send
        ecrt_master_send(master);
        
        sent_bytes = master->devices[EC_DEVICE_MAIN].tx_skb[
            master->devices[EC_DEVICE_MAIN].tx_ring_index]->len;
        up(&master->io_sem);

        if (ec_fsm_master_idle(&master->fsm)) {
            ec_master_nanosleep(master->send_interval * 1000);
            set_current_state(TASK_INTERRUPTIBLE);
            schedule_timeout(1);
        } else {
            ec_master_nanosleep(sent_bytes * EC_BYTE_TRANSMISSION_TIME_NS);
        }
    }

    EC_MASTER_DBG(master, 1, "Master IDLE thread exiting...\n");

    return 0;
}

先簡單介紹到這,敬請關注後續文章。。。。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章