3 points par GN⁺ 2024-04-10 | 1 commentaires | Partager sur WhatsApp
  • Récit de fabrication d’un matériel ajoutant des fonctions d’émission/réception Ethernet 10BASE-T à un ordinateur en logique discrète réalisé sans CPU commercial ni puce réseau dédiée
  • Au-dessus d’un précédent adaptateur de couche physique 10BASE-T↔SPI, un module de couche MAC a été ajouté pour le relier à un homebrew computer, avec une structure full duplex où l’émetteur et le récepteur fonctionnent indépendamment
  • Le récepteur convertit les données SPI en octets et les stocke dans une SRAM de 2 kB, en vérifiant dans le matériel les 6 premiers octets pour n’accepter que FE:FA:F6:F2:EE:EA ou l’adresse MAC de broadcast
  • Pour simplifier le circuit, l’émetteur confie au logiciel la génération du FCS et la préparation du préambule, et ne prend en charge que des trames de longueur fixe de 1024 octets
  • Un compilateur C capable de compiler uIP 1.0 a même été créé pour exécuter des applications réseau, avec comme résultats un ping moyen de 85 ms et un téléchargement HTTP de fichiers statiques à 2.6 kB/s

Ajouter Ethernet à un ordinateur en logique discrète

  • Dans le prolongement du travail consistant à construire un système informatique complet avec des composants en logique discrète, un adaptateur Ethernet capable d’exécuter des applications réseau a été réalisé
  • Auparavant, un adaptateur de couche physique convertissant les signaux Ethernet 10BASE-T vers SPI et inversement avait été conçu, et un microcontrôleur STM32 avait alors servi pour les tests de fonctionnement
  • Le cœur de ce travail est un module de couche MAC destiné à connecter cet adaptateur au homebrew computer
  • L’adaptateur adopte une architecture full duplex, avec des sections d’émission et de réception fonctionnant indépendamment

Récepteur : stocker les données SPI dans un tampon de trame

  • Le récepteur convertit les données série SPI en données parallèles par octet et extrait l’horloge des octets
  • L’adresse MAC de destination est vérifiée sur les 6 premiers octets, et les trames qui ne correspondent pas aux critères sont rejetées
  • Les octets reçus sont écrits dans un tampon SRAM 6116 de 2 kB
  • À la fin d’une trame, le récepteur est désactivé et n’accepte plus de trame supplémentaire tant qu’il n’est pas réactivé
  • Le compteur d’octets conserve sa valeur même après l’arrêt, afin que le CPU puisse lire la longueur reçue
  • Le FCS n’est pas vérifié par le matériel
  • Acquisition des données et accès au tampon

    • Les données série SPI entrent dans le registre à décalage U32, tandis que U30 et U31 comptent respectivement les bits et les octets
    • La bascule D U29B génère le signal d’écriture SRAM recv_buf_we, qui passe brièvement à l’état bas tous les 8 bits de données en entrée
    • Les octets reçus sont écrits dans la SRAM 6116 U20
    • U13, U16 et U18 forment un multiplexeur d’adresses qui sélectionne soit le compteur d’octets, soit le bus d’adresses système pour l’entrée d’adresses de la SRAM
    • U21 joue le rôle de tampon trois états pour transférer les octets reçus vers la RAM
    • La RAM et le compteur d’octets sont reliés au bus de données système pour permettre au CPU d’accéder aux données reçues et à leur longueur
    • U25 relie la RAM de réception au bus de données système
    • Après la fin d’une trame, la valeur du compteur d’octets est conservée sur le bus recv_byte_cnt
    • U26 et U27 envoient cette valeur sur le bus de données système lorsqu’une lecture à une adresse spécifique est demandée
    • L’autre moitié de U27 forme un registre d’état en lecture seule sur 2 bits permettant de consulter l’état du récepteur et de l’émetteur

Filtrage matériel de l’adresse MAC

  • L’analyse du trafic Ethernet a montré que les trames arrivaient généralement en petits groupes de 3 à 4, séparés par de courts délais, et qu’au sein d’un même groupe les adresses MAC de destination différaient souvent
  • L’ordinateur risquait de ne pas être assez rapide pour effectuer le filtrage MAC en logiciel puis réactiver le récepteur, d’où la nécessité d’un filtrage MAC matériel
  • L’option consistant à stocker une adresse MAC personnalisée puis à la comparer aux 6 premiers octets a été écartée car trop complexe
  • Une adresse MAC composée d’un seul octet répété aurait aussi été possible, mais l’adresse a finalement été définie comme une fonction de l’index de l’octet
    • le bit 0 est fixé à 0
    • le bit 1 est fixé à 1
    • les bits 2 à 4 sont l’inversion de l’index de l’octet
    • les bits 5 à 7 sont fixés à 1
  • L’adresse MAC obtenue avec cette règle est FE:FA:F6:F2:EE:EA
  • Pour le fonctionnement d’ARP, l’adresse MAC de broadcast FF:FF:FF:FF:FF:FF est également acceptée
  • U33 compare le bit de donnée 0 et les bits 2 à 4 aux valeurs attendues, et la sortie de U34A passe à l’état haut lorsque ces bits correspondent
  • U35A implémente la vérification de l’adresse MAC de broadcast, avec une sortie haute si le bit 0 et les bits 2 à 4 valent tous 1
  • Ces deux signaux sont combinés par un OR à diodes utilisant D7 et R6, tandis que U35B vérifie que tous les autres bits valent 1
  • Le résultat de validité pour un octet unique est accumulé dans U10A
    • lorsqu’aucune trame n’est reçue, le signal SPI slave select entrant ss est bas et U10A est positionné à 1
    • pendant la réception d’une trame, la valeur est mise à jour à chaque octet reçu
    • si l’adresse MAC de destination correspond aux critères, la valeur de U10A reste haute
    • lorsque l’adresse d’octet atteint 5, la valeur finale est verrouillée dans U36B, et la réception de la trame est bloquée si l’adresse de destination ne correspond pas

Émetteur : simplifier le circuit avec des trames de longueur fixe

  • Comme pour le récepteur, l’émetteur ne réalise pas en matériel la génération du FCS, qui est traitée par le logiciel
  • Pour réduire le circuit, l’émetteur ne prend en charge que des trames de longueur fixe
  • La longueur de trame choisie est de 1024 octets, une valeur proche du MTU courant de 1500 octets
  • Le préambule requis par le 10BASE-T est composé de plusieurs 0x55 suivis d’un 0xD5 final, et le logiciel doit aussi le charger dans ces 1024 octets
  • La longueur fixe des trames n’affecte pas les protocoles de niveau supérieur
    • les protocoles supérieurs encodent la taille du paquet dans l’en-tête
    • ils ne dépendent pas de la longueur réelle de la trame Ethernet
  • Flux de données en émission

    • Les données à émettre sont stockées en SRAM
    • Une horloge de 20 MHz alimente un compteur 4 bits, dont la sortie de débordement sert d’horloge d’octet
    • L’écriture d’une valeur quelconque à un emplacement mémoire spécifique active le compteur et lance l’émission de la trame
    • Les données parallèles par octet sont sérialisées via un registre à décalage
    • Comme côté réception, U12 compte les bits et U14 compte les octets
    • L’horloge 20 MHz provient d’un oscillateur intégré et n’est pas utilisée directement, mais au minimum après division par 2
    • Cette méthode évite que le rapport cyclique de l’oscillateur n’influence le signal de sortie
  • RAM, registre à décalage et temporisation

    • Comme pour le récepteur, trois multiplexeurs 74HC157 sont utilisés pour sélectionner l’entrée d’adresses de la RAM U22
    • U23 sert à charger les données dans la RAM
    • U24 joue le rôle de stockage intermédiaire pour l’octet en cours d’émission
    • Le compteur d’octets 74HC4040 est un compteur ripple, donc lent à se stabiliser
    • U24 fournit une sortie stable pendant que la sortie RAM n’est pas encore valide
    • Les données entrent dans le registre à décalage U28, puis sont décalées bit par bit
    • Un bug matériel avait inversé le câblage de l’ordre des bits entre la RAM et le registre à décalage, ce qui a dû être contourné en mélangeant les bits côté logiciel
    • MOSI et SCK doivent être synchronisés avec précision pour produire un bon signal 10BASE-T
    • U11A et U8B assurent cette synchronisation
    • tx_cnt0 est le bit 0 du compteur de bits, utilisé comme horloge avec le signal 20 MHz divisé par 2
    • U11A modifie la sortie en suivant ce signal
    • U8B retarde l’horloge pour s’aligner sur le délai introduit par U11A
    • Le verrou D est plus complexe qu’une simple porte AND et introduit environ 5 ns de délai supplémentaire, d’où l’usage d’un 74LV74A plus rapide
    • Le 74LV74A est la seule puce de famille rapide utilisée sur cette carte

Interface CPU et mappage mémoire

  • Du point de vue du programmeur, l’adaptateur Ethernet apparaît comme une interface mappée en mémoire
  • Les deux tampons de trame sont mappés à 0xF000
  • Il y a deux registres en lecture seule
    • Le registre d’état 8 bits à 0xFB00 contient les indicateurs RX_FULL et TX_BUSY
    • RX_FULL indique qu’une réception de trame est terminée
    • TX_BUSY indique qu’une transmission de trame est en cours
    • Le registre 16 bits à 0xFB02 contient la longueur des données reçues
  • Les opérations d’écriture servent de commandes de contrôle
    • Écrire n’importe quelle valeur à 0xFB00 réactive le récepteur
    • Écrire n’importe quelle valeur à 0xFB01 lance une transmission
  • Le CPU ne prenant pas en charge les interruptions, il n’y a pas d’interruptions
  • Toutes les adresses concernées commencent par F, c’est-à-dire avec les 4 bits de poids fort à 1, une condition vérifiée par U2A
  • Les adresses de tampon doivent avoir le bit 11 à 0, ce qui est vérifié par U1D, D2, R2 et U1E
  • Les adresses de registre doivent avoir B (1011) comme deuxième chiffre hexadécimal, ce que vérifient U1B et U2B
  • Les décodeurs U4A et U4B servent à sélectionner les fonctions individuelles
  • Deux LED indiquent un accès au tampon ou aux registres

Programmation et performances

  • Il voulait le support réseau, mais pas implémenter lui-même une pile TCP/IP, et la programmation en assembleur étant peu pratique, il a créé un compilateur C
  • Ce compilateur est désormais suffisamment mature pour compiler uIP 1.0, une petite bibliothèque TCP/IP
  • La densité de code du CPU est très faible, mais uIP tient en RAM et il reste encore de la place pour les applications réelles
  • Les performances réseau sont modestes, mais il s’agit d’un résultat obtenu sans CPU commercial ni puce spécialisée
    • aller-retour moyen d’un ping : 85 ms
    • vitesse de téléchargement du serveur HTTP : 2.6 kB/s
    • le serveur HTTP fournit des fichiers statiques depuis une carte SD
  • Les modèles, schémas et fichiers de PCB sont disponibles dans le dépôt GitHub

1 commentaires

 
GN⁺ 2024-04-10
Commentaires Hacker News
  • Beau travail, merci pour le partage. La trace de pile du raisonnement était particulièrement intéressante, et le fait d’avoir déduit beaucoup de choses à partir des premiers principes, ou d’avoir essayé de les expliquer du point de vue d’un débutant, est excellent sur le plan pédagogique.
    Même si ce n’est pas pratique pour du réseau réel, je ne pense pas que ce soit juste un simple bricolage. À une époque où l’on découvre des portes dérobées dans des puces réseau excessivement complexes, il pourrait bien y avoir à l’avenir un lectorat plus sérieux, ou des motivations de projet plus fortes.

    • Je me demande combien de vulnérabilités cachées dans le silicium moderne il peut y avoir. On trouve presque tous les jours des vulnérabilités dans quelques milliers de lignes de code ; or, dans du silicium câblé en dur, on a en quelque sorte des micropuces équivalant à des milliards de lignes de code.
  • Comme c’est destiné à un ordinateur entièrement custom, c’est déjà beaucoup plus impressionnant en soi, sans même parler du passage « donc j’ai écrit un compilateur C ». Cela dit, je me demande à quoi ressemblerait une implémentation minimale d’une carte Ethernet pour PC « ordinaire ».
    Une bonne partie serait probablement similaire, et les sommes de contrôle pourraient peut-être être traitées par le CPU du PC. Pour la connexion, il faudrait du série brut ou, plus pratiquement, de l’USB, et au final il faudrait sans doute utiliser un « vrai » pilote ou tout remonter en espace utilisateur pour le traitement.
    En regardant des choses similaires, je me suis demandé si, en faisant implémenter à l’appareil https://en.wikipedia.org/wiki/USB_communications_device_clas..., on ne pourrait pas le faire « juste fonctionner » sans pilote maison, mais ça ne semble pas très compatible avec le fait de traiter toutes les sommes de contrôle côté hôte.
    En cherchant, je suis aussi tombé sur https://en.wikipedia.org/wiki/Ethernet_over_USB ; peut-être que cela signifie qu’on peut fabriquer un adaptateur qui ne fait que convertir la connexion physique en USB, puis laisser l’ordinateur gérer le reste.

    • On peut considérer que l’USB est beaucoup plus complexe que le 10base2 Ethernet apparu il y a longtemps. Si l’on veut se connecter à un réseau Ethernet 10base2 via PCIe ou USB, les deux représentent beaucoup plus de travail que la partie Ethernet.
      Peut-être pourrait-on convaincre un périphérique USB façon FTDI de bit-banger du 10base2 Ethernet. L’idée serait de n’implémenter que la partie « PHY » qui transforme le trafic sur la ligne en flux de bits propre et aligne le début des trames, puis de laisser le PC faire tout le reste en logiciel.
    • Si les PC ordinaires avaient encore un bus ISA comme il y a 30 ans, ma carte réseau aurait pu s’y connecter avec seulement quelques modifications.
    • Implémenter une NIC en FPGA, généralement avec une connexion PCIe, est très courant.
      Côté USB, CDC-NCM en soi n’est pas difficile à implémenter sur n’importe quel MCU, mais implémenter un PHY USB HS nécessite en pratique du matériel ASIC.
      Avec un PHY USB HS ULPI à 0,30 dollar, il devrait être assez facile d’implémenter USB CDC-NCM sur un FPGA.
  • À la fin, il y a un lien vers le compilateur C créé pour ce projet : https://github.com/imihajlow/ccpu-cc
    Il semble aussi y avoir un linker et une libc. Je ne sais pas vraiment à quel point la conception matérielle est complexe, mais le fait d’avoir ajouté presque en passant un compilateur C est impressionnant.

    • C’est un compilateur C écrit en Rust, et il utilise le crate lang_c pour parser le langage.
  • Vraiment impressionnant. J’aimerais essayer de faire moi-même ce genre de projet, et je respecte la passion et les innombrables heures qu’il faut pour comprendre le système puis le construire.
    Je n’aspire pas spécialement à la retraite, mais c’est peut-être à ce moment-là que je finirai par consacrer du temps à ce genre de projets matériel-logiciel.

  • Alors, est-ce mieux ou pire qu’une Etherlink 3c501 ? :-D
    https://mirror.math.princeton.edu/pub/oldlinux/Linux.old/net...
    Si mes souvenirs sont bons, un nouveau paquet entrant du réseau pouvait écraser le tampon que le CPU essayait de lire. Je l’ai utilisée quelque temps sous Linux, et les performances étaient vraiment mauvaises.

  • J’ai trouvé intéressant le passage disant que « fixer la longueur des trames n’a pas d’incidence sur les protocoles supérieurs, car ceux-ci encodent la taille du paquet dans leur en-tête et ne dépendent pas de la longueur réelle de la trame Ethernet ».
    J’ai récemment écrit un décodeur de paquets, et à chaque couche je vérifiais explicitement que la longueur de la couche inférieure correspondait. Pour IP, dans mon décodeur, la longueur du datagramme IP doit correspondre exactement à la longueur de la trame Ethernet et à celle de l’en-tête de couche liaison.
    Ce n’était pas pour être tatillon, mais pour détecter les trames trop courtes ; ensuite, j’ai aussi décidé de traiter les trames trop longues comme des erreurs. L’auteur utilise uIP, mais je me demande comment Linux ou d’autres OS modernes gèrent ça. Je me demande aussi s’il a fait des tests d’interopérabilité.

    • J’envoie des trames longues sur le réseau, et aucun des OS que j’ai sous la main ne montre de problème. J’ai lu quelque part que certains routeurs utilisent effectivement des trames longues pour stocker des métadonnées à la suite du paquet.
    • Les horodatages et d’autres formes de télémétrie réseau in-band peuvent aussi être insérés dans les trames sous forme de trailer. Un nouveau FCS est alors ajouté.
      Si l’application ne voit pas les données L2, la pile IP de Linux les ignore tout simplement.
  • C’est physiquement beaucoup plus petit que le premier ensemble de cartes Ethernet SSI de DEC : https://i.ebayimg.com/images/g/NEYAAOSw-mZlg0lZ/s-l1600.jpg
    Les cartes DEC DEUNA font plus d’un pied de long, mais elles ont aussi beaucoup plus de fonctionnalités. DEUNA est une « vraie » NIC : elle possède des files d’émission et de réception, les traite de façon autonome et fait du DMA. Bien sûr, il y a aussi son propre PDP-11 sur la carte pour faire tourner tout ça.

  • Vraiment cool. Je me demande combien de temps ça a pris.

    • Le module réseau a pris environ un mois, mais l’écriture du compilateur a demandé beaucoup plus de temps.
  • Cela montre à quel point il est facile de mettre une porte dérobée dans une puce connectée à un port réseau.

  • Dans un cours de Communication Systems Engineering, j’ai implémenté le traitement des signaux Ethernet, puis une pile TCP/IP avec ARP et commutation en assembleur Motorola 68k QUIC.
    Les 18 mois les plus longs de ma vie.