Page principale | Liste des fichiers | Portée globale

Serveur/client TCP/IP dans un FPGA Xilinx utilisant l'API RAW de LwIP 2.00.a (0.7.2)

Table des matières

Introduction

LwIP

LwIP est une pile réseau dont l'empreinte mémoire est très faible, ce qui la rend idéale pour les systèmes embarqués.

La licence utilisée par le projet LwIP est du même type que la licence BSD, ce qui dans notre cas nous permet de créer un produit propriétaire sans être obligé de redistribuer les sources. Attention, il existe tout de même des clauses à respecter, et j'invite donc le lecteur à en prendre connaissance.

Il faut savoir que LwIP est utilisé par un très grand nombre de projets libres et propriétaires, et que des améliorations sont donc constamment apportées. De plus, elle a été conçue de telle manière qu'elle soit extrêmement portable ce qui la rend idéale pour la réutilisation de projet.

Toutes ces raisons font que nous avons choisi de développer nos applications réseau avec LwIP plutôt qu'avec Xilnet. De plus Xilnet ne possède pas de fonctions TCP "client" ce qui nous conforte dans notre choix.

LwIP possède trois API de programmation :

Le projet

Ce projet fourni un exemple d'implémentation d'un serveur et d'un client TCP avec LwIP. Tout ce qui concerne la configuration se trouve dans le fichier config.h. Ainsi, on peut configurer le serveur pour qu'il écoute sur le port 80, et se connecter dessus avec un client HTTP (Firefox, Netscape, Opera, Internet Explorer, ...). Etant donné que ce serveur ne fait rien d'autre que de renvoyer ce qu'il reçoit, on verra alors la requète HTTP s'afficher sur notre navigateur.

En ce qui concerne le client, il ne fait rien d'autre que d'envoyer le nom de l'entreprise "SMP". Pour voir facilement le résultat, il suffit de configurer un serveur sur notre ordinateur. Prenons par exemple NetCat :

nc.exe -s 149.199.6.132 -p 1302 -l

Attention à ne pas confondre le serveur et le client dans le code...

Mise en oeuvre

Le but de ce projet n'est pas de créer un tutoriel sur LwIP (la documentation officielle est très bien faite, cf. rawapi.txt), mais plutôt de fournir un exercice pour montrer que l'on (SMP) peut facilement créer une application réseau tournant sur un PPC du Virtex II Pro.

Voici les étapes que nous avons suivi pour mettre en oeuvre ce projet :

  1. faire son design avec une IP emac, par exemple grâce au "Base System Builder" pour aller plus vite.
  2. ajouter un projet "logiciel", faire attention qu'il ne soit pas utilisé pour initialiser la BRAM. En effet, le code sera trop gros, nous devrons donc le télécharger dans la SDRAM après le démarrage.
  3. ajouter les fichiers sources à ce projet.
  4. dans la boite de dialogue "Set Compiler settings", à l'onglet "Environment", remplir le champ "Program Start Address" avec l'adresse de début de la SDRAM (0x02000000, dans notre cas).
  5. dans la boite de dialogue "Set Compiler settings", à l'onglet "Directories", remplir le champ "Libs to link" avec "-llwip4"
  6. dans la boite de dialogue "Software Platform Setting", à l'onglet "Software Platform", cocher "lwip 2.00.a".
  7. dans la boite de dialogue "Software Platform Setting", à l'onglet "Library/OS Parameters", remplir le champ "emac_instances" ((Ethernet_MAC,0x00,0x01,0x02,0x03,0x04,0x05), dans notre cas)
  8. compiler le projet "logiciel", synthétiser le projet "matériel", et configurer le FPGA avec ce dernier.
  9. lancer XMD.
  10. aller dans le répertoire où ce situe le code exécutable de notre projet.
  11. lancer les commandes "dow executable.elf" et "run".

Interface LoopBack

Le pilote Xilinx de l'interface Ethernet n'est pas capable d'émettre vers lui-même. Par défaut, l'interface LoopBack (le fameux 127.0.0.1) n'est pas activé dans LwIP fourni par Xilinx, nous allons donc voir comment y remédier.

Dans un premier temps, copiez le répertoire $XILINX_EDK/sw/lib/sw_services/Lwip_v2_00_a/ vers le répertoire $XILINX_EDK/sw/lib/sw_services/Lwip_v2_00_b/ , pour différentier la bibliothèque LwIP modifiée de celle d'origine.

Modifiez le fichier $XILINX_EDK/sw/lib/sw_services/Lwip_v2_00_a/src/Makefile_ppc de la manière suivante :

# NETIFFILES: Files implementing various generic network interface functions.
NETIFFILES=$(LWIPDIR)/netif/etharp.c \
	$(LWIPDIR)/netif/loopif.c

Ainsi que le fichier $XILINX_EDK/sw/lib/sw_services/Lwip_v0_00_a/src/contrib/ports/v2pro/lwipopts.h :

 Support loop interface (127.0.0.1) 
#define LWIP_HAVE_LOOPIF		1

Pour que l'interface LoopBack fonctionne correctement, nous avons besoin de la fonction sys_timeout qui n'est malheureusement pas définie en mode RAW. Pour remédier à ce problème, il suffit de modifier le fichier $XILINX_EDK/sw/lib/sw_services/Lwip_v2_00_b/src/Lwip/src/netif/loopif.c de la manière suivante :

*
 * workaround (patch #1779) to try to prevent bug #2595:
 * When connecting to "localhost" with the loopif interface,
 * tcp_output doesn't get the opportunity to finnish sending the
 * segment before tcp_process gets it, resulting in tcp_process
 * referencing pcb->unacked-> which still is NULL.
 * 
 * TODO: Is there still a race condition here? Leon
 
//sys_timeout( 1, loopif_input, arg );
loopif_input(arg);

En ce qui concerne ce problème de "race-condition", nous le contournons simplement en utilisant un PCB différent entre le serveur et le client.

Enfin, il ne reste plus qu'à adapter le code pour utiliser l'interface LoopBack à la place de l'interface Xemac. Il faut alors enlever les appels de fonctions relatifs à l'ancienne interface :

//xemacif_setmac(0, (u8_t *)mac);
//xemacif_input(netif);
//etharp_tmr();

Puis configurer correctement l'interface LoobBack :

//netif = netif_add(netif, &ipaddr, &netmask, &gw, &XEmacIf_ConfigTable[0], xemacif_init,
netif = netif_add(netif, &ipaddr, &netmask, &gw, NULL, loopif_init, ip_input);

Dans l'exemple fourni, l'interface Xemac n'est pas utilisable en même temps que l'interface LoopBack, mais ce n'est uniquement pour simplifier la compréhension du code ! En effet, il est tout à fait envisageable de les utiliser en même temps à partir du moment où elles sont configurer avec des adresses IP/Mask différentes !

Il est à noter que si l'on teste cette exemple du LoopBack, il va s'arrêter automatiquement au bout d'un certain temps. En effet la file d'envoie est limiter en taille...

LoopBack sans interface Ethernet

Si l'on souhaite utiliser LwIP uniquement en LoopBack (donc sans interface Ethernet), les modifications précédentes ne suffisent pas. Il suffit alors de modifier le fichier $XILINX_EDK/sw/lib/sw_services/Lwip_v2_00_a/src/Makefile_ppc de la manière suivante :
# NETIFFILES: Files implementing various generic network interface functions.
NETIFFILES=$(LWIPDIR)/netif/loopif.c

# ARCHFILES: Archiecture specific files.
ARCHFILES=$(ARCHDIR)/lib_arch.c \
          $(ARCHDIR)/perf.c 

Problèmes et solutions

Accélérer le développement

A chaque fois que l'on fait une modification dans notre projet, XPS recompile tout le projet logiciel, ce qui signifie que toutes les bibliothèques sont recompilées, ainsi que tous les autres projets logiciels... Une solution consiste à faire son propre Makefile. Pour cela il suffit de noter la commande qui commence par "powerpc-eabi-gcc", et de la mettre dans un fichier Makefile comme présenté ci-après.

journal de XPS :

LibGen Done. 
powerpc-eabi-gcc (...)
powerpc-eabi-size LwIP/executable.elf 
   text	   data	    bss	    dec	    hex	filename
  99476	   1252	 826564	 927292	  e263c	LwIP/executable.elf
Done.

fichier Makefile:

program:
	cd ../..; powerpc-eabi-gcc (...)

Ensuite, il suffit de lancer un terminal (bouton Xygwin Shell), d'aller dans le répertoire où ce trouvent les sources, et de lancer la commande "make".

Instance not found

Il se peut qu'après avoir lancé la boite de dialogue "Software Platform Setting", le projet ne se compile plus et affiche l'erreur suivante :
ERROR:MDT - xget_handle 39406316 ipinst Ethernet_MAC peripheral : xget_handle:
   Ip instance Ethernet_MAC not found
ERROR:MDT - ERROR FROM TCL:- Lwip () - 
       while executing
   "xget_handle $processor_handle "ipinst" $ipinst_name "peripheral""
       (procedure "xget_sw_ipinst_handle_from_processor" line 2)
       invoked from within
   "xget_sw_ipinst_handle_from_processor $sw_proc_handle $inst"
       (procedure "::sw_Lwip_v2_00_a::Lwip_drc" line 21)
       invoked from within
   "::sw_Lwip_v2_00_a::Lwip_drc 39441932" 
ERROR:MDT - Error while running DRC for processor ppc405_1...
make: *** [ppc405_1/lib/libxil.a] Error 2
Done.

La solution consiste à commenter les informations concernant le processeur non utilisé :

# BEGIN OS
#  PARAMETER OS_NAME = standalone
#  PARAMETER OS_VER = 1.00.a
#  PARAMETER PROC_INSTANCE = ppc405_1
# END

Le pointeur NULL

Il faut faire attention à ce que la mémoire où se situe le code et les données ne commence pas à l'adresse 0x00000000. En effet, prenons par exemple le cas des callbacks de LwIP, ce dernier compare les pointeurs de fonctions (callback) au pointeur NULL pour savoir s'il faut les appeler ou non. Si la fonction est mappée à l'adresse 0x00000000, son pointeur sera bien évidemment égale au pointeur NULL.

Documentation de tcp_close()

Malgré ce qui est dit dans la documentation de l'API RAW de LwIP, le PCB n'est pas désalloué lors d'un appel à tcp_close() s'il à au moins servi à une connexion... Cependant, dans la prochaine version de LwIP, un second appel à tcp_close() désallouera effectivement le PCB...

XMD et GDB

Il faut bien faire attention de ne pas charger l'exécutable avec XMD, mais le faire à partir de l'interface GDB, si l'on souhaite utiliser ce dernier. En effet, on risque de rencontrer des difficultés (plantage sur la cible) si l'on ne respecte pas cette procédure...

Débogage des bibliothèques

Il peut être pratique de voir comment se comportent les bibliothèques durant l'exécution. Cependant, par défaut, elles sont compilées de manière optimisée, ce qui veut dire qu'utiliser un débogueur dans de telles conditions est assez difficile...

Une solution consiste à forcer la non-optimisation. Cela s'effectue en plaçant l'option "-O0" dans la boite de dialogue "Software Platform Setting" à l'onglet "Processor, Driver Parameters, Interrupt Handlers" dans le champ "EXTRA_COMPILER_FLAGS".

Nombre d'envoie limité

Le nombre d'envoie dans une même connexion est limiter, ce qui veut dire que l'on ne peut pas garder une connexion ouverte pour dialoguer librement... Il faut donc faire attention à cette limite :
  Check if the queue length exceeds the configured maximum queue
  * length. If so, we return an error. 
queuelen = pcb->snd_queuelen;
if (queuelen >= TCP_SND_QUEUELEN) {
    LWIP_DEBUGF(TCP_OUTPUT_DEBUG | 3, ("tcp_enqueue: too long queue %u (max %u)\n", queuelen, TCP_SND_QUEUELEN));
    goto memerr;
}

Généré le Tue Nov 1 15:13:48 2005 par  doxygen 1.4.4