Choisir un master EtherCAT open source sous Linux embarqué

ethercattemps réellinux embarquéyoctorust

Côté calculateur (PC industriel, SBC…), EtherCAT tient une promesse simple : une interface Ethernet ordinaire suffit, sans carte de bus ni matériel propriétaire. C’est souvent l’argument qui fait retenir ce bus plutôt qu’un autre, et c’est la première chose que le choix du MainDevice peut préserver ou casser.

Sur un calculateur Linux, ce choix n’est donc pas d’abord un choix de bibliothèque. C’est un choix sur ce qui entre dans l’image, sur ce qu’il faudra reconstruire à chaque montée de version du kernel, et sur le matériel que l’implémentation autorise encore.

Deux implémentations open source sont aujourd’hui couramment retenues, SOEM et IgH EtherLab. Une troisième, EtherCrab, est beaucoup plus récente. Les trois sont comparées ici, les offres commerciales sont citées en fin d’article.

Vocabulaire

MainDevice, SubDevice

Terminologie officielle de l’EtherCAT Technology Group depuis son renommage : le MainDevice remplace le master, le SubDevice remplace le slave. Les deux vocabulaires coexistent, la documentation et le code des implémentations emploient encore massivement master et slave. Cet article utilise les termes actuels, à l’exception du titre.

Kernel module, user space

Un MainDevice en kernel module s’exécute dans le noyau Linux. En user space, il s’exécute comme un processus ordinaire, dans les threads de l’application. La différence se paie surtout au moment de construire l’image.

Working Counter (WKC)

Compteur incrémenté par chaque SubDevice ayant traité la trame. Sa valeur en retour indique combien de participants ont effectivement répondu, et sert de premier diagnostic.

Distributed Clocks (DC)

Synchronisation matérielle du bus. Chaque SubDevice aligne son horloge sur une horloge de référence, ce qui impose à la trame d’arriver avant un top d’horloge et non simplement à l’heure en moyenne.

Image process

Zone mémoire échangée cycliquement, dans laquelle l’application écrit ses consignes et lit les états. Son organisation est décrite par le PDO mapping.

Avant de choisir : le kernel et la carte

Aucune implémentation ne rattrape un kernel mal choisi, et deux questions se posent donc en amont du comparatif.

Le BSP du fondeur (NXP, ST, TI, Intel…) permet-il PREEMPT_RT ? Depuis la 6.12, PREEMPT_RT est intégré au kernel mainline, ce qui supprime l’alternance entre « kernel à jour » et « kernel temps réel ». Mais beaucoup de BSP restent figés sur une version ancienne, parfois sans patch RT disponible. Sous Yocto, la recette linux-yocto-rt couvre le cas générique. Un kernel vendeur, lui, se vérifie avant de s’engager.

Quelle carte réseau ? Un adaptateur USB vers Ethernet est à exclure pour le bus : le jitter d’interrogation USB rend les mesures inexploitables. Le piège est que certains PC industriels et SBC du marché ont en réalité leurs ports Ethernet derrière un contrôleur USB interne.

Une interface native est nécessaire, par exemple directement sur PCIe, et elle doit être réservée au bus, pas partagée avec le trafic applicatif.

Le MainDevice est en revanche indifférent au média : un segment peut être en cuivre ou en fibre par convertisseur, sans incidence sur lui ni sur l’application.

Trois critères

Ils se classent par ordre de coût : le premier engage la maintenance sur toute la vie du produit, le deuxième le matériel, le troisième se découvre en cours d’intégration quand il n’a pas été vérifié.

Ce qui entre dans l’image

MainDevice en user spaceMainDevice en kernel modulekernel + PREEMPT_RTapplicationMainDevice compilé dedanskernel + PREEMPT_RTmodule out-of-treeà reconstruiredriver natif, si retenuà reconstruireapplication
Figure 1. Ce que chaque famille ajoute à l’image, et ce qu’une montée de kernel oblige à reconstruire

Un MainDevice en kernel module est out-of-tree : il se construit contre les sources du kernel de l’image et doit être reconstruit à chaque montée de version. Sous Yocto, cela signifie une recette de module liée à kernel-module-*, un chargement à déclarer, et une dépendance dure entre deux éléments qui évoluent à des rythmes différents.

En user space, le MainDevice est une bibliothèque ou une dépendance de l’application. Il ne connaît pas la version du kernel et ne se reconstruit pas quand elle change.

Le user space ne rend pas le système meilleur en temps réel, il rend la maintenance plus prévisible. Sur un produit maintenu dix ans, avec des correctifs de sécurité à livrer, la différence se paie à chaque montée de version.

Le chemin réseau, et ce qu’il exige du matériel

L’argument classique contre le user space porte sur le trajet d’une trame entre la carte et l’application. Le chemin historique passe par une socket brute (AF_PACKET), et son coût se paie au retour du softirq vers le user space et à la sortie d’appel système.

Deux mécanismes plus récents du kernel réduisent ce trajet.

io_uring

Les soumissions et les complétions passent par des anneaux partagés entre le kernel et l’application, ce qui supprime un appel système par opération.

XDP et AF_XDP

Un programme eBPF intercepte la trame au niveau du driver, avant l’allocation du skb, et la redirige vers une socket AF_XDP lue depuis le user space. La pile réseau est court-circuitée, avec copie évitable.

Ces mécanismes resserrent l’écart historique entre user space et kernel module, celui-là même qui justifiait le coût d’intégration d’un module out-of-tree.

Ils ne sont pas gratuits pour autant. XDP demande libxdp, libbpf et une configuration soignée, et surtout, en mode natif, un driver réseau qui le supporte, ce qui n’est pas le cas de beaucoup. Le mode générique fonctionne partout, mais repasse par le skb et perd l’essentiel du bénéfice.

À retenir pour le choix du matériel : la contrainte sur le contrôleur ne pèse pas que sur IgH et ses drivers natifs, elle revient dès qu’on veut AF_XDP en user space. Le chemin AF_PACKET, lui, reste disponible sur n’importe quelle carte.

Ce que l’implémentation couvre du protocole

Un MainDevice n’implémente pas tout EtherCAT, et le périmètre se vérifie à partir de la liste des SubDevices à piloter.

Le fonctionnement cyclique ne suffit pas : la configuration des SubDevices, la mise à jour de leur firmware et le tunnelage de protocoles passent par des mailbox protocols distincts.

CoE, CANopen over EtherCAT

Configuration par SDO.

FoE, File over EtherCAT

Mise à jour de firmware par le bus.

EoE, Ethernet over EtherCAT

Tunnelage d’Ethernet vers un SubDevice, ce dont certains outils de configuration constructeur ont besoin. Typiquement pour faire passer du trafic TCP/IP jusqu’à un SubDevice.

SoE, Servo over EtherCAT

Profil d’entraînement pour variateurs, alternatif au profil DS402 porté par CoE.

Des bornes de liaison série, par exemple, se configurent par CoE : le débit, la parité et le format de trame sont des objets SDO. Sans CoE, elles restent inertes.

La vérification tient en trois gestes : lister les SubDevices retenus, relever pour chacun les mailbox protocols que sa mise en œuvre exige, puis confronter cette liste aux fonctions annoncées par chaque implémentation. Une fonction manquante se contourne rarement au niveau applicatif.

Un dernier point si des cartes spécifiques doivent être développées : la spécification EtherCAT s’obtient par l’adhésion à l’EtherCAT Technology Group, qui délivre aussi l’identifiant fabricant. L’adhésion comme l’identifiant sont gratuits, mais la spécification n’est accessible qu’à ce titre.

Les trois implémentations

SOEM

SOEM est une bibliothèque C en user space, éprouvée et largement déployée.

Intégration. Une recette à écrire, simple parce que sans dépendance au kernel : ni Buildroot ni les couches OpenEmbedded courantes ne l’empaquettent, seules les couches ROS le font. Depuis une application Rust, l’accès passe par FFI (Foreign Function Interface), donc par une couche unsafe à écrire et à maintenir, qui devient elle-même un composant du projet.

Licence. Elle a changé en cours de route. Les versions 1.x sont en GPLv2 avec une exception d’édition de liens, qui autorise la liaison avec du code propriétaire. La version 2.0 est en GPLv3 ou licence commerciale chez RT-Labs, sans cette exception. Pour un produit fermé, la version retenue détermine le modèle de licence.

L’architecture bloquante de SOEM impose par ailleurs une gestion manuelle de la mémoire et des verrous explicites.

IgH EtherLab

IgH EtherCAT Master est implanté en kernel module, avec les meilleures caractéristiques temps réel des trois. Le code est sur GitLab, la documentation est publiée en PDF.

Intégration. C’est le plus coûteux, mais il est empaqueté : Buildroot fournit igh-ethercat, qui construit le module contre le kernel cible avec une option par driver natif, et OpenEmbedded a une recette équivalente dans meta-tanowrt. Le module se reconstruit à chaque montée de version du kernel.

IgH propose deux familles de drivers réseau. Les drivers natifs, pour des puces précises (Intel I210 à I226, e1000e, r8169, entre autres), pilotent la carte sans interruption et donnent les meilleures performances, mais ils suivent le kernel au même rythme que le module et contraignent le choix de la carte. Le driver générique fonctionne avec n’importe quelle carte gérée par Linux, au prix d’un passage par la pile réseau.

Avec un driver natif, l’image compte donc trois éléments couplés à valider ensemble : kernel, module et driver. Avec le driver générique, deux.

Licence. Les kernel modules sont en GPLv2, la bibliothèque user space en LGPLv2.1 : une application propriétaire peut s’y lier dynamiquement. La contrainte porte sur la maintenance du module, pas sur la diffusion du code applicatif.

EtherCrab

EtherCrab est un MainDevice écrit en Rust, exécuté en user space, sans dépendance C ni kernel module, sous licence MIT ou Apache 2.0. L’API est documentée sur docs.rs.

Le développeur principal détaille plusieurs points de conception sur son blog.

Intégration. Elle n’a de sens que dans une application Rust. Le MainDevice y est une simple dépendance, déclarée dans Cargo.toml et compilée avec elle. Rien n’apparaît séparément dans l’image. Sous Yocto, Rust est pris en charge depuis la 3.4 : la classe cargo gère la compilation croisée, et cargo-bitbake génère une recette qui fige l’index des crates pour la reproductibilité. Les chemins io_uring et XDP décrits plus haut sont exposés en options de compilation, via des features Cargo.

Réserve. Le crate est en 0.x et son interface évolue encore. Sur la couverture fonctionnelle, il annonce CoE, l’autoconfiguration des SubDevices depuis leur EEPROM et les Distributed Clocks avec compensation de dérive ; EoE, FoE et SoE ne figurent pas dans la liste des fonctions annoncées.

La version se fige dans Cargo.toml, avec la possibilité de pointer une révision git ou un chemin local pour corriger un défaut sans attendre une publication, ce qui reste le meilleur recours face à une fonction manquante.

Ces réserves posées, je l’ai mis en œuvre chez plusieurs clients, sur des distributions construites avec Yocto.

Comparatif

EtherCrabSOEMIgH EtherLab

Espace d’exécution

user space

user space

kernel module

Dans l’image

rien de séparé

bibliothèque

module + driver réseau

Montée de kernel

sans effet

sans effet

module et driver à reconstruire

Contraint la carte réseau

non en AF_PACKET, oui en AF_XDP

non

non avec le driver générique, oui avec un driver natif

Intégration depuis Rust

native

FFI

FFI

Licence

MIT / Apache 2.0

GPLv2 + exception (1.x), GPLv3 ou commerciale (2.0)

GPLv2 kernel, LGPLv2.1 user space

Maturité

0.x

éprouvé

éprouvé

Synthèse

EtherCrab pour une application Rust : rien de séparé dans l’image, aucune dépendance au kernel, licence permissive. En contrepartie, un crate en 0.x dont la version doit être figée, et une couverture protocolaire à vérifier avant de s’engager.

SOEM sur une base C existante, avec une recette simple et sans couplage au kernel. Vérifier d’abord quelle version, donc quelle licence, est engagée.

IgH EtherLab quand le cyclique le plus strict prime et que l’équipe accepte de maintenir un module au rythme du kernel, et un driver natif avec lui si les performances l’exigent, avec la contrainte sur la carte qui va avec.

Dans les trois cas, le choix ne suffit pas : le déterminisme ne vient pas que du MainDevice, il vient surtout du système qui l’exécute. Isolation des cœurs, réglages de l’interface, conception de la boucle applicative, tout cela reste à faire une fois l’implémentation retenue.

Les options commerciales

Elles sortent du périmètre de ce comparatif, mais elles existent et méritent d’être connues. La liste n’est pas exhaustive.

Acontis EC-Master

MainDevice commercial, sous licence propriétaire avec redevance par équipement.

Beckhoff TwinCAT

Beckhoff propose désormais un runtime TwinCAT pour Linux : il ne se limite plus à Windows et à TwinCAT/BSD. Ce runtime s’exécute cependant sur la distribution Linux de Beckhoff, dérivée de Debian et livrée avec un kernel temps réel, d’abord sur les PC embarqués ARM CX82x0 et CX9240, puis progressivement sur leurs autres IPC.

TwinCAT ne s’ajoute donc pas à une image construite avec Yocto ou Buildroot : c’est la distribution et le matériel qui sont adoptés avec lui. Le choix se pose au niveau de l’écosystème, pas de la recette, et l’application n’y possède pas le cycle, elle dialogue avec le runtime.

À noter que la frontière n’est pas étanche : SOEM 2.0 propose lui aussi une licence commerciale à côté de la GPLv3.

Hors périmètre

Le réglage du système qui exécute le MainDevice, isolation des cœurs et paramètres de l’interface réseau, fera l’objet d’un article séparé. La configuration des SubDevices depuis leurs fichiers de description ESI, le PDO mapping, le diagnostic de topologie et la redondance de câblage ne sont pas traités ici non plus.