Un port Ethernet supplémentaire sur Raspberry Pi

Publié par cpb
Déc 20 2019
Wiznet w5500

J’ai eu l’occasion, pour le projet d’un client, de tester un petit circuit permettant de disposer d’un port Ethernet sur bus SPI. Je me suis amusé à l’installer sur un Raspberry Pi 3, cela fonctionne assez facilement. Peut-être serez-vous également tentés d’ajouter un port Ethernet à votre Pi ? Cela peut aussi être une bonne solution pour ajouter la connectivité Ethernet à un Compute Module 3 ou tout autre module du même style.

Configuration matérielle

Contrôleur w5500
Contrôleur w5500

Le circuit en question est construit autour d’un contrôleur Wiznet w5500 relativement courant. Il existe plusieurs versions de ce module, disponibles auprès de nombreux fournisseurs. Il est adapté pour fonctionner avec des signaux de tension 3.3V comme le Raspberry Pi ou 5V comme l’Arduino.

Douze broches sont exposées sur le modèle que mon client m’a envoyé, mais seules sept d’entre-elles nous serviront :

Module Wiznet w5500
Brochage du module pour w5500
  • Vcc (broche 4) et Gnd (broche 2) seront reliées à des sorties d’alimentation du Raspberry Pi. D’après mes essais, la consommation du w5500 est suffisamment faible pour qu’on l’alimente directement depuis les broches 1 (+3.3V) et 6 (Gnd) du Raspberry Pi. Si on souhaite l’intégrer sur une carte porteuse, on préférera probablement une alimentation externe.
  • Mi (broche 12) et Mo (broche 5) seront reliées respectivement aux broches Miso (Master In Slave Out – broche 21) et Mosi (Master Out Slave In – broche 19) du Raspberry Pi.
  • Sck (broche 7) sera connectée sur la sortie SPI Clock (broche 23) et Cs (Chip Select – broche 9) sur la sortie SPI Enable 0 ( broche 24) du Raspberry Pi. Le contrôleur sera donc accessible sur le bus SPI 0.0. Si vous préférez utiliser SPI 0.1, il vous faudra utiliser la broche 26 et modifier légèrement le fichier du Device Tree.
  • Enfin Int (broche 11) sera reliée à une entrée GPIO du Raspberry Pi pour le notifier, par une interruption, de la réception de données. J’ai choisi la GPIO 25 (broche 22), car elle se trouve juste à côté des broches SPI du connecteur d’extension du Raspberry Pi.
  • Je n’ai pas connecté la broche Rst (Reset 10). Il me semble que cela n’est pas nécessaire, je suppose qu’une résistance de pulldown interne au circuit nous dispense de la piloter. Si des problèmes se posent, nous pourrons toujours utiliser une autre GPIO, par exemple GPIO 24 (broche 18).

La connexion n’est donc pas très compliquée, je l’ai réalisée sur une plaquette d’essai utilisée en session de formation. Bien sûr, pour une utilisation plus avancée on évitera les fils volants !

Module w5500 et Raspberry Pi 3
Connexion du module au Raspberry Pi 3

Configuration logicielle

Le noyau Linux dispose d’un driver nommé w5100-spi capable de piloter les contrôleurs w5100, w5200 et w5500. Ce driver peut être généralement compilé sous forme de module que nous pourrions charger dynamiquement. Malheureusement, il n’est pas disponible sur la distribution Raspbian Buster Full du 26 septembre 2019 sur laquelle j’ai commencé mon test.

Nous avons alors deux possibilités :

  • recompiler le noyau Linux sur le Raspberry Pi en ajoutant le driver nécessaire, ce n’est pas très compliqué mais cela prend un temps de compilation assez long,
  • cross-compiler une image spécifique depuis un PC en utilisant Buildroot par exemple. Cette solution est rapide (une petite heure) et simple, c’est celle que j’utilise tout d’abord.

Charger le driver n’est pas suffisant. Nous devons également indiquer au noyau que le périphérique est bien présent, comment l’activer (le numéro de Chip Enable) et sur quelle broche GPIO il nous notifiera des réceptions de paquets. Indiquer au noyau la présence des périphériques qu’il ne peut pas détecter automatiquement (et lui indiquer également la configuration matérielle sur laquelle il s’exécute) est le rôle du device tree.

Nous allons utiliser un petit fichier d’overlay pour le device tree qui viendra le compléter (indiquer la présence du w5500 et la GPIO à utiliser) et le surcharger (pour interdire l’utilisation du bus Spi 0.0 par le module spidev par exemple). Je me suis inspiré de l’overlay pour le module QCA7000 qui se trouve dans les sources du noyau.

Device Tree Overlay

Le fichier d’overlay complet est disponible ici. Il contient trois parties (trois fragments) qui agiront à différents endroits du device tree. Il commence par l’entête plugin indiquant qu’il s’agit d’un overlay et que certaines références resteront irrésolues à l’issue de la compilation sans que cela soit une erreur.

/dts-v1/;
/plugin/;

Ensuite commence l’overlay proprement dit, qui indique d’abord sur quel device tree il peut s’appliquer : celui du Raspberry Pi. Suivi de trois fragments que nous analyserons ensuite.

/ {
    compatible = "brcm,bcm2708";
    fragment@0 {
        ...
    };

    fragment@1 {
        ...
    };

    fragment@2 {
        ...
    };
}

Le premier fragment s’applique sur le bus Spi 0, et ajoute un périphérique nommé eth1. Le type de contrôleur est indiqué, suivi du numéro de registre (0). C’est ce qui permet de choisir entre les bus Spi 0.0 et Spi 0.1 correspondant aux sorties Chip Select 0 (broche 24 du Raspberry Pi) et Chip Select 1 (broche 26).

Le type d’interruption utilisé par le contrôleur (GPIO) ainsi que son numéro (25) et le type de front (2 pour falling edge, la documentation du w5500 indiquant que cette broche est active à l’état bas) sont suivis de la fréquence SPI maximale (31,25 MHz) déterminée un peu arbitrairement.

fragment@0 {
    target = <&spi0>;
    __overlay__ {
        #address-cells = <1>;
        #size-cells = <0>;
        status = "okay";

        eth1: w5500@0 {
            compatible = "wiznet,w5500";
            reg = <0>;
            pinctrl-names = "default";
            pinctrl-0 = <&eth1_pins>;
            interrupt-parent = <&gpio>;
            interrupts = <25 2>;
            spi-max-frequency = <31250000>;
            status = "okay";
        };
    };
};

Le deuxième fragment d’overlay sert à désactiver l’accès par le module spidev au bus Spi 0.0 pour éviter toute collision avec le driver w5100-spi.

fragment@1 {
    target = <&spidev0>;
    __overlay__ {
        status = "disabled";
    };
};

Le dernier fragment, enfin, réserve la GPIO que nous utilisons dans le driver (25). Son éventuelle fonction et la résistance de pulldown interne sont désactivées.

fragment@2 {
    target = <&gpio>;
    __overlay__ {
        eth1_pins: eth1_interrupt {
            brcm,pins = <25>;
            brcm,function = <0>;
            brcm,pull = <0>;
        };
    };
};

Compilation de l’overlay

La version du Device Tree Compiler dtc, dont je dispose sur mon PC Ubuntu est trop ancienne pour arriver à compiler correctement cet overlay. Elle échoue avec l’erreur suivante :

$ dtc -I dts -O dtb < w5500.dts > w5500.dtbo 
<stdout> : Warning (unit_address_vs_reg): Node /fragment@0 has a unit name, but no reg property
<stdout> : Warning (unit_address_vs_reg): Node /fragment@1 has a unit name, but no reg property
<stdout> : Warning (unit_address_vs_reg): Node /fragment@2 has a unit name, but no reg property
dtc: livetree.c:521: get_node_by_phandle: Assertion `(phandle != 0) && (phandle != -1)' failed.
Abandon

J’ai donc compilé une version plus récente de dtc :

$ git clone https://github.com/dgibson/dtc
Clonage dans 'dtc'…
remote: Enumerating objects: 294, done.
remote: Counting objects: 100% (294/294), done.
remote: Compressing objects: 100% (220/220), done.
remote: Total 5173 (delta 131), reused 143 (delta 74), pack-reused 4879
Réception d'objets: 100% (5173/5173), 1.53 MiB | 2.89 MiB/s, fait.
Résolution des deltas: 100% (3784/3784), fait.

$ cd dtc/

$ make
  [...]
  AR libfdt/libfdt.a
 Skipping pylibfdt (install python dev and swig to build)

$ cd ..

$ dtc/dtc -@ -I dts -O dtb < external-tree-pi3-w5500/dts/w5500.dts > external-tree-pi3-w5500/dts/w5500.dtbo 

$

Installation de l’overlay

À présent que nous pouvons compiler notre fichier overlay, il est temps de le tester sur une image. Pour cela nous générons une image minimale standard pour Raspberry Pi 3 avec Buildroot. Commençons par en télécharger la dernière version disponible au moment de la rédaction de ces lignes :

$ wget https://buildroot.org/downloads/buildroot-2019.11.tar.bz2
  [...]
$ tar xf buildroot-2019.11.tar.bz2

$ cd buildroot-2019.11.tar.bz2

Je prépare une configuration standard pour Raspberry Pi 3, mais cela peut s’adapter avec n’importe quel modèle (appeler make list-defconfigs pour voir les configurations prédéfinies existantes). J’utilise l’option O=... (la lettre 0 majuscule, pas un zéro) pour créer un répertoire de travail et ne pas polluer les sources de Buildroot.

$ make O=../build-pi3  raspberrypi3_defconfig
  [...]
$ cd ../build-pi3

Par défaut, le driver pour w5100/w5200/w5500 n’est pas compilé avec le noyau pour Raspberry Pi 3. Ajustons donc sa configuration :

$ make linux-menuconfig

Cette étape prend du temps avant de nous afficher le menu de configuration du noyau, car Buildroot génère d’abord sa chaîne de compilation.

Lorsque le menu s’affiche, activons les options

Device Drivers --->
  [*] Network device support --->
    [*] Ethernet driver support --->
       [*] WIZnet devices
          <M> WIZnet W5100 Ethernet support
          <M> WIZnet W5100/W5200/W5500 Ethernet support for SPI mode

J’ai choisi dans un premier temps de compiler les drivers sous forme de modules (<M> plutôt que <*>) afin de maîtriser leurs insertions et retraits du noyau.

On termine la compilation de l’image avec :

$ make
 [...]
 INFO: hdimage(sdcard.img): writing MBR
$

Je vais transférer l’image sur une carte micro-SD que j’insère sur mon PC via un adaptateur USB – micro-SD. Elle est reconnue comme périphérique /dev/sdb et ses deux précédentes partitions sont automatiquement montées, comme je le vois avec la commande lsblk.

$ lsblk
NAME           MAJ:MIN RM   SIZE RO TYPE  MOUNTPOINT
 sda              8:0    0 465,8G  0 disk  
 ├─sda1           8:1    0   400G  0 part  /home/testing
 └─sda2           8:2    0     8G  0 part  [SWAP]
 sdb              8:16   1   1,9G  0 disk  
 ├─sdb1           8:17   1    32M  0 part  /media/cpb/82FE-5A11
 └─sdb2           8:18   1   120M  0 part  /media/cpb/747675ea-
 nvme0n1        259:0    0 232,9G  0 disk  
 ├─nvme0n1p1    259:1    0   512M  0 part  /boot/efi
 ├─nvme0n1p2    259:2    0 224,6G  0 part  /
 └─nvme0n1p3    259:3    0   7,8G  0 part  
   └─cryptswap1 253:0    0   7,8G  0 crypt 

Je commence par démonter les deux partitions, puis je copie l’image fournie par Buildroot en écrasant l’ensemble de la carte.

$ umount /dev/sdb?

$ sudo  cp  images/sdcard.img  /dev/sdb

Après une bonne vingtaine de secondes de copie, ma carte est prête. Toutefois, je veux ajouter l’overlay compilé plus haut sur sa partition de boot. J’extrais et réinsère ma carte à nouveau pour qu’elle soit auto-montée et je vérifie le répertoire de montage de la première partition

$ lsblk
 [...]
sdb              8:16   1   1,9G  0 disk  
 ├─sdb1           8:17   1    32M  0 part  /media/cpb/0B53-B858
 └─sdb2           8:18   1   120M  0 part  /media/cpb/d396131c-
[...]

Je copie mon overlay dans le sous-répertoire overlays/ de la partition de boot :

$ cp ../w5500.dtbo /media/cpb/0B53-B858/overlays

Puis j’édite le fichier de configuration du bootloader afin de lui demander de fournir l’overlay au noyau :

$ nano /media/cpb/0B53-B858/config.txt

J’ajoute à la fin du fichier la ligne

dtoverlay=w5500

Puis je démonte ma carte SD et l’insère dans le Raspberry Pi 3

$ umount /dev/sdb?

Le noyau démarre normalement. A priori l’overlay est appliqué, mais le matériel n’est pas pris en compte car ni le driver SPI ni le driver pour le w5500 ne sont chargés.

# ip link
 1: lo:  mtu 65536 qdisc noqueue qlen 1000
     link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
 2: eth0:  mtu 1500 qdisc pfifo_fast qlen 1000
     link/ether b8:27:eb:e9:e1:11 brd ff:ff:ff:ff:ff:ff

Insérons le driver pour le bus SPI du Raspberry Pi

# modprobe spi-bcm2835

Puis le driver pour le module WIZnet :

# modprobe w5100-spi

Vérifions les interfaces réseau :

# ip link
 1: lo:  mtu 65536 qdisc noqueue qlen 1000
     link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
 2: eth0:  mtu 1500 qdisc pfifo_fast qlen 1000
     link/ether b8:27:eb:e9:e1:11 brd ff:ff:ff:ff:ff:ff
 3: eth1:  mtu 1500 qdisc noop qlen 1000
     link/ether 2e:93:f5:80:c7:60 brd ff:ff:ff:ff:ff:ff

Bonne nouvelle ! Une nouvelle interface eth1 est apparue. Je la connecte au réseau du bureau, dont le routeur assure le rôle de serveur DHCP.

# ip addr show  eth1
 3: eth1:  mtu 1500 qdisc noop qlen 1000
     link/ether 2e:93:f5:80:c7:60 brd ff:ff:ff:ff:ff:ff

L’interface n’a pas encore d’adresse IP, je dois lancer manuellement le client DHCP – de Busybox – en lui indiquant l’interface à paramétrer :

# udhcpc -i eth1
 udhcpc: started, v1.31.1
 udhcpc: sending discover
 udhcpc: sending select for 192.168.0.114
 udhcpc: lease of 192.168.0.114 obtained, lease time 7200
 deleting routers
 adding dns 8.8.8.8
 adding dns 8.8.4.4

# ping www.kernel.org
 PING www.kernel.org (136.144.49.103): 56 data bytes
 64 bytes from 136.144.49.103: seq=0 ttl=47 time=15.202 ms
 64 bytes from 136.144.49.103: seq=1 ttl=47 time=14.456 ms
 64 bytes from 136.144.49.103: seq=2 ttl=47 time=14.432 ms
 ^C
 --- www.kernel.org ping statistics ---
 3 packets transmitted, 3 packets received, 0% packet loss
 round-trip min/avg/max = 14.432/14.696/15.202 ms

Cette interface Ethernet est la seule connectée, la communication avec Internet passe donc bien par elle.

Compilation statique des drivers

La compilation des drivers sous forme de modules extérieurs au noyau est pratique pour la mise au point, et leur chargement peut se faire facilement à l’aide d’un script de démarrage. Nous voyons les modules nécessaires :

# lsmod
 Module                  Size  Used by    Not tainted
 w5100_spi              16384  0 
 w5100                  20480  1 w5100_spi
 spi_bcm2835            20480  0 
 ipv6                  458752 16 [permanent]

Toutefois, il peut s’avérer intéressant de compiler les drivers statiquement dans l’image de noyau. J’ai donc relancé sur mon PC un nouveau :

$ make linux-menuconfig

et j’ai activé la compilation statique des options :

Device Drivers ---->
  [*] Network device support ---->
    [*] Ethernet driver support --->
      [*] WIZnet devices
        <*> WIZnet w5100 Ethernet support
        <*> WIZnet w5100/w5200/w5500 Ethernet support for SPI mode
  [*] SPI support ---->
    <*> BCM2835 SPI controller

Et regénéré une image avec

$ make

Au reboot du système, l’interface a bien été automatiquement détectée. Une petite surprise : comme l’initialisation du contrôleur SPI est sensiblement plus rapide que celle du contrôleur USB pilotant l’interface réseau habituelle, notre nouveau connecteur Ethernet se retrouve nommé eth0 et l’ancien connecteur eth1 ! D’ailleurs le client DHCP a été automatiquement lancé au démarrage et notre interface dispose déjà d’une adresse IP.

# ip addr show
 1: lo:  mtu 65536 qdisc noqueue qlen 1000
     link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
     inet 127.0.0.1/8 scope host lo
        valid_lft forever preferred_lft forever
     inet6 ::1/128 scope host 
        valid_lft forever preferred_lft forever
 2: eth0:  mtu 1500 qdisc pfifo_fast qlen 1000
     link/ether 3e:ff:4b:58:f9:9d brd ff:ff:ff:ff:ff:ff
     inet 192.168.0.115/24 brd 192.168.0.255 scope global eth0
        valid_lft forever preferred_lft forever
     inet6 fe80::3cff:4bff:fe58:f99d/64 scope link 
        valid_lft forever preferred_lft forever
 3: eth1:  mtu 1500 qdisc noop qlen 1000
     link/ether b8:27:eb:e9:e1:11 brd ff:ff:ff:ff:ff:ff

Conclusion

L’interface fonctionne très bien, toutefois le débit n’est pas excellent, de l’ordre de 15 à 20 Mbits/sec. d’après mes premières mesures. Je suppose que l’on pourrait optimiser le paramétrage SPI (fréquence, mode, etc.)

Pour le projet de mon client, nous n’utiliserons pas un Raspberry Pi, et je peux espérer que la débit SPI soit amélioré. À cette occasion, la configuration de Buildroot sera industrialisée, en employant une arborescence externe pour intégrer l’overlay du device tree, la configuration du noyau et le paramétrage du bootloader.

URL de trackback pour cette page