Je travaille actuellement avec le pilote Xilinx XDMA (voir ici pour le code source: XDMA Source ), et tente de le faire fonctionner (avant de vous demander: j'ai contacté mon point de contact du support technique et le forum Xilinx est criblé de personnes ayant le même problème). Cependant, j'ai peut-être trouvé un problème dans le code de Xilinx qui pourrait être un facteur décisif pour moi. J'espère qu'il y a quelque chose que je n'envisage pas.
Tout d’abord, il existe deux modes principaux du pilote, AXI-Memory Mapped (AXI-MM) et AXI-Streaming (AXI-ST). Pour mon application particulière, j'ai besoin d'AXI-ST, car les données circulent en permanence à partir de l'appareil.
Le pilote est écrit pour tirer parti des listes de dispersion. En mode AXI-MM, cela fonctionne car les lectures sont des événements plutôt aléatoires (c’est-à-dire qu’il n’ya pas de flux de données sortant du périphérique, mais que l’espace utilisateur demande simplement des données quand il le faut). En tant que tel, le transfert DMA est construit, les données sont transférées, puis le transfert est annulé. Ceci est une combinaison de get_user_pages()
, pci_map_sg()
et pci_unmap_sg()
.
Pour AXI-ST, les choses deviennent étranges et le code source est loin d'être orthodoxe. Le pilote alloue un tampon circulaire dans lequel les données doivent circuler en continu. Ce tampon est généralement assez volumineux (le mien est de l’ordre de 32 Mo), car vous voulez pouvoir gérer les événements transitoires dans lesquels l’espace utilisateur a oublié le pilote et peut ensuite traiter les données entrantes.
Voici où les choses se gâtent ... le tampon circulaire est alloué en utilisant vmalloc32()
et les pages de cette allocation sont mappées de la même manière que le tampon de l'espace utilisateur est en mode AXI-MM (c'est-à-dire, en utilisant l'interface pci_map_sg()
). En conséquence, étant donné que la mémoire tampon circulaire est partagée entre le périphérique et la CPU, chaque appel read()
nécessite que j'appelle pci_dma_sync_sg_for_cpu()
et pci_dma_sync_sg_for_device()
, ce qui détruit absolument mes performances (je ne peux pas suivre le périphérique!), Car cela fonctionne sur l'ensemble du système. tampon. Assez drôle, Xilinx n’incluait jamais ces appels de synchronisation dans son code. Je savais donc que j’avais un problème lorsque j’ai modifié leur script de test pour tenter plus d’un transfert DMA avant de quitter et que le tampon de données résultant était corrompu.
En conséquence, je me demande comment résoudre ce problème. J'ai envisagé de réécrire le code pour construire mon propre tampon alloué en utilisant pci_alloc_consistent()/dma_alloc_coherent()
, mais cela est plus facile à dire qu'à faire. Notamment, l'architecture du code est supposée utiliser des listes de diffusion dispersée partout (il semble exister un étrange mappage propriétaire entre la liste de diffusion dispersée et les descripteurs de mémoire que le FPGA comprend).
Existe-t-il d'autres appels d'API dont je devrais être informé? Puis-je utiliser les variantes "simples" (c'est-à-dire, pci dma_sync_single_for_cpu()
) via un mécanisme de traduction pour ne pas synchroniser tout le tampon? Sinon, existe-t-il une fonction qui peut rendre le tampon circulaire alloué avec vmalloc()
cohérent?
D'accord, je l'ai compris.
Fondamentalement, mes hypothèses et/ou compréhension de la documentation du noyau concernant l'API de synchronisation étaient totalement incorrectes. À savoir, je me suis trompé sur deux hypothèses clés:
read()
.read()
, je découvre quelles pages vont être affectées par l'appel copy_to_user()
(c'est-à-dire ce qui va être copié à partir du tampon circulaire) et ne synchronise que les pages qui me tiennent à cœur. En gros, je peux appeler quelque chose comme pci_dma_sync_sg_for_cpu(lro->pci_dev, &transfer->sgm->sgl[sgl_index], pages_to_sync, DMA_FROM_DEVICE)
où sgl_index
est l'endroit où j'ai supposé que la copie va commencer et pages_to_sync
est la taille des données en nombre de pages.Avec les deux modifications ci-dessus, mon code répond maintenant à mes exigences en matière de débit.
Je pense que XDMA a été écrit à l'origine pour x86, auquel cas les fonctions de synchronisation ne font rien.
Il ne semble pas probable que vous puissiez utiliser les variantes de synchronisation unique à moins de modifier le tampon circulaire. Remplacer le tampon circulaire par une liste de tampons à envoyer me semble une bonne idée. Vous pré-allouez un certain nombre de ces tampons, vous disposez d'une liste de tampons à envoyer et d'une liste libre que votre application peut réutiliser.
Si vous utilisez un FPGA Zynq, vous pouvez connecter le moteur DMA au port ACP afin que l'accès à la mémoire du FPGA soit cohérent. Alternativement, vous pouvez mapper les régions de la mémoire comme non mises en cache/en mémoire tampon au lieu de mises en cache.
Enfin, dans mes applications FPGA, je mappe les registres de contrôle et les tampons dans le processus d’application et n’implémente que mmap () et poll () dans le pilote, pour donner aux applications plus de flexibilité dans leur façon de faire DMA. J'implémente généralement mes propres moteurs DMA.
Pete, je suis le développeur original du code du pilote (avant la mise en place du X de XMDA).
Le tampon de sonnerie a toujours été une chose peu orthodoxe et, en fait, destiné aux systèmes cohérents avec le cache et désactivé par défaut. Son objectif initial était de se débarrasser de la latence DMA (re) start; même avec une prise en charge asynchrone complète des entrées/sorties (même avec dans certains cas un chaînage de descripteurs à latence nulle), nous avions des cas d'utilisation où cela ne pouvait pas être garanti et où un véritable mode tampon/cyclique/boucle matériel était requis.
Il n'y a pas d'équivalent d'une API ringbuffer sous Linux, c'est donc un peu codé.
Je suis heureux de repenser la conception IP/pilote.
Pouvez-vous partager votre solution?