版權聲明:本文爲本文爲博主原創文章,轉載請註明出處。若有問題,歡迎指正。博客地址:https://www.cnblogs.com/wsg1100/linux
igh經過腳原本啓動,能夠是systemd、init.d或sysconfig。分別位於源碼script目錄下:數組
對於systemd方式,編譯時由ethercat.service.in
文件生成ethercat.service
。ethercat.service
中指定了執行文件爲ethercatctl
.ethercatctl
文件由``ethercatctl.in`生成。init.d和sysconfig相似,都是生成一個可執行腳本,且腳本完成的工做一致,主要完成加載主站模塊、網卡驅動、給主站內核模塊傳遞參數、卸載模塊等操做。網絡
ethercat.conf
共同的配置文件,配置主站使用的網卡、驅動等信息。下面看腳本start和stop所作的工做。異步
加載ec_master.ko函數
模塊參數:工具
遍歷配置文件中/etc/sysconfig/ethercat
的環境變量DEVICE_MODULES,位於ethercat.conf
中。大數據
在每一個DEVICE_MODULES前添加前綴ec_
替換,如DEVICE_MODULES爲igb
的話,添加前綴後爲ec_igb
。ui
modinfo
檢查該模塊是否存在。命令行
對於非generic
和rtdm
的驅動,須要先將網卡與當前驅動unbind
,unbind後的網卡才能被新驅動接管。線程
加載該驅動。
start加載了兩個內核模塊,ec_master.ko和網卡驅動ec_xxx.ko,ec_master先根據內核參數(網卡MAC)來建立主站實例,此時主站處於Orphaned phase。後續加載網卡驅動ec_xxx.ko,執行網卡驅動probe,根據MAC地址將網卡與主站實例匹配,此時主站獲得操做的網卡設備,進入Idle phase。詳細過程見後文。
卸載內核模塊ec_master.ko和網卡驅動ec_xxx.ko。
後文「主站」和」master「均表示主站或主站實例對象,slave和從站表示從站或從站對象,中英混用,不刻意區分。
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); ... }
igh中,狀態機是其核心思想,一切操做基於狀態機來執行,對建立的每一個EtherCAT主站實例都須要通過以下階段轉換(見圖2.3),主站各階段操做以下:
Orphaned phase 此時主站實例已經分配初始化,正在等待以太網設備鏈接,即尚未與網卡驅動聯繫起來,此時沒法使用總線通信。
Idle phase 主站接受以太網設備全部請求,但沒有應用請求時該模式生效。master運行master狀態機(請參閱第5.3節),自動掃描總線上的從站,並從應用空間接口執行掛起操做(如SDO訪問)。該階段命令行工具可以訪問總線,但沒法進行過程數據交換,由於還缺乏總線配置。
Operation phase 能夠經過提供總線配置和交換過程數據的應用程序請求主站。
繼續看master初始化代碼ec_master_init
前,咱們先了解數據報與狀態機的關係,這對後續理解頗有幫助。
EtherCAT是以以太網爲基礎的現場總線系統,EtherCAT使用標準的IEEE802.3以太網幀,在主站一側使用標準的以太網控制器,不須要額外的硬件。並在以太網幀頭使用以太網類型0x88A4來和其餘以太網幀相區別(EtherCAT數據還能夠經過UDP/IP 來傳輸,本文已忽略),標準的IEEE802.3以太網幀中數據部分爲EtherCAT的數據,標準的IEEE802.3以太網幀與EtherCAT數據幀關係以下:
EtherCAT數據位於以太網幀數據區,EtherCAT數據由EtherCAT頭和若干EtherCAT數據報文組成。其中EtheRCAT頭中記錄了EtherCAT數據報的長度、和類型,類型爲1表示與從站通信。EtherCAT數據報文內包含多個子報文,每一個子報文又由子報文頭、數據和WKC域組成。子報文結構含義以下。
整個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種狀態:
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 con guration 監視從站的應用程序層狀態。若是從站未處於其應有的狀態,則從站將被(從新)配置
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狀態機主要目的是:
數據報對象的管理
上面簡單介紹了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狀態機及其子狀態機執行過程當中動態使用
應用數據報: 它並不固定屬於某個狀態機,主要經過接口提供給應用直接使用:
初始化各狀態機須要先爲數據報數據區(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字節,如圖:
若是過程數據鏡像的大小超過該限制,就必須發送多個幀,而且必須對映像進行分區以使用多個數據報。 Domain自動進行管理。
對狀態機及數據報對象有初步認識後,咱們回到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;
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創建聯繫過程當中設置。
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
.
因爲主站位於內核空間,用戶空間應用與主站交互經過字符設備來交互;
建立普通字符設備,給普通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
網卡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); ...... } ...... }
給主站提供網絡設備: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); }
調用網絡設備接口打開網絡設備
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; }
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。
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; }
先簡單介紹到這,敬請關注後續文章。。。。