2010-07-26 14 views
11

J'écris un pilote de périphérique sous linux-2.6.26. Je veux avoir un tampon dma mappé dans l'espace utilisateur pour envoyer des données d'un pilote à l'application de l'espace utilisateur. S'il vous plaît suggérer un bon tutoriel à ce sujet.Mappage des tampons DMA à l'espace utilisateur

Merci

+0

Achetez LDD http://lwn.net/Kernel/LDD3/ et recherchez l'implémentation de mmap() dans un pilote de périphérique. – Dummy00001

+0

J'ai LDD ... pouvez-vous me dire le nom de certains pilotes de périphériques à regarder? –

+0

@ Dummy00001 Qu'en est-il de l'adressage physique/virtuel? –

Répondre

5

Eh bien, si vous avez LDD, vous pouvez consulter le chapitre 15, et plus précisément la page 435, où des opérations directes d'E/S sont décrits.

L'appel du noyau qui vous aidera à atteindre cet objectif est get_user_pages. Dans votre cas puisque vous voulez envoyer des données du noyau vers l'espace utilisateur, vous devez définir l'indicateur d'écriture sur 1.

Sachez également que les E/S asynchrones peuvent vous permettre d'obtenir les mêmes résultats mais avec votre application d'espace utilisateur non avoir à attendre la lecture pour finir ce qui peut être mieux.

+2

'get_user_pages' cartes dans un tampon de l'espace utilisateur dans l'espace du noyau (droit?) Mais comment est-ce qu'un lien à une opération DMA? Déclencher un périphérique DMA pour écrire dans la mémoire que l'espace utilisateur peut accéder? (Voir http://stackoverflow.com/q/5539375/119790) –

4

Regardez bien les pilotes Infiniband. Ils font beaucoup d'efforts pour faire du DMA et du RDMA zéro copie dans le travail de l'espace utilisateur.

J'ai oublié d'ajouter avant de sauvegarder:

Faire DMA directement aux applications de mémoire de l'espace utilisateur est plein de problèmes, donc à moins que vous avez de très hautes exigences de performance comme Infiniband ou 10 Gb Ethernet, ne pas le faire . Au lieu de cela, copiez les données DMA dans les mémoires tampons de l'espace utilisateur. Cela vous épargnera beaucoup de chagrin.

Pour un seul exemple, que se passe-t-il si le programme de l'utilisateur se termine avant la fin du DMA? Que se passe-t-il si la mémoire utilisateur est réaffectée à un autre processus après la sortie, mais que le matériel est toujours défini sur DMA dans cette page? Catastrophe!

+0

Pouvez-vous suggérer un des fichiers Infiniband les plus faciles? Ils sont tous assez complexes! –

+0

La copie supplémentaire est une perte de temps dans mon esprit. Si nous voulons risquer les problèmes, quelle est exactement la solution? –

+3

'get_user_pages' épingle les pages. Cela n'a donc pas d'importance si le programme se termine avant que le DMA ne soit terminé ... Les pages ne peuvent pas être réutilisées tant que vous n'avez pas fait le put_page correspondant pour les libérer. – Nemo

7

OK, c'est ce que j'ai fait. Clause de non responsabilité: Je suis un hacker au sens propre du terme et mon code n'est pas le plus joli. J'ai lu LDD3 et le code source infiniband et d'autres choses prédécesseur et a décidé que get_user_pages et épingler eux et tout ce que l'autre rigmarole était juste trop douloureux à contempler pendant la gueule de bois. En outre, je travaillais avec l'autre personne sur le bus PCIe et j'étais également responsable de la «conception» de l'application de l'espace utilisateur.

j'ai écrit le conducteur de telle sorte qu'au moment du chargement, il préalloue autant de tampons que il peut avec la plus grande taille en appelant la fonction myAddr[i] = pci_alloc_consistent(blah,size,&pci_addr[i]) jusqu'à ce qu'il échoue. (échec ->myAddr[i] est NULL je pense, j'oublie). J'ai été en mesure d'allouer environ 2,5 Go de tampons, chacun de 4 Mo de taille dans ma maigre machine qui a seulement 4 Go de mémoire. Le nombre total de tampons varie en fonction du moment où le module du noyau est chargé. Chargez le pilote au démarrage et le plus de tampons sont alloués. La taille de chaque tampon individuel a atteint 4MiB dans mon système. Pas certain de pourquoi. Je me suis assuré que je ne faisais pas de bêtises, ce qui est bien sûr ma routine de départ habituelle.

Le pilote procède ensuite à donner le tableau de pci_addr au périphérique PCIe avec leurs tailles. Le conducteur s'assoit juste là attendant la tempête d'interruption pour commencer. Pendant ce temps dans l'espace utilisateur, l'application ouvre le pilote, interroge le nombre de tampons alloués (n) et leurs tailles (en utilisant ioctl s ou read s etc) puis procède à appeler l'appel système mmap() plusieurs fois (n). Bien sûr mmap() doit être correctement implémenté dans le pilote et les pages LDD3 422-423 étaient pratiques.

L'espace utilisateur contient désormais n pointeurs sur n zones de la mémoire du pilote. Lorsque le pilote est interrompu par le périphérique PCIe, les tampons sont "pleins" ou "disponibles" pour être aspirés. L'application est à son tour en attente sur un read() ou ioctl() pour savoir quels tampons sont pleins de données utiles. La partie délicate consistait à gérer l'espace utilisateur pour la synchronisation de l'espace du noyau de sorte que les tampons qui sont en cours de DMA par le PCIe ne soient pas également modifiés par l'espace utilisateur, mais c'est ce pour quoi nous sommes payés. J'espère que cela a du sens et je serais plus qu'heureux de me faire dire que je suis un idiot mais s'il vous plaît dites-moi pourquoi. Je recommande également ce livre: http://www.amazon.com/Linux-Programming-Interface-System-Handbook/dp/1593272200. J'aimerais avoir ce livre il y a sept ans quand j'ai écrit mon premier pilote Linux. Il existe un autre type de supercherie en ajoutant encore plus de mémoire et en ne laissant pas le noyau l'utiliser et mmap ping des deux côtés de la division espace utilisateur/noyau, mais le périphérique PCI doit également prendre en charge l'adressage DMA supérieur à 32 bits. Je n'ai pas essayé mais je ne serais pas surpris si je devais éventuellement y être obligé.

9

Voici ce que je l'ai utilisé, en bref ...

get_user_pages à la broche la page d'utilisateur (s) et vous donner un tableau de pointeurs struct page *.

dma_map_page sur chaque struct page * pour obtenir l'adresse DMA (alias "adresse d'E/S") pour la page. Cela crée également un mappage IOMMU (si nécessaire sur votre plate-forme).

Dites maintenant à votre appareil d'exécuter le DMA dans la mémoire en utilisant ces adresses DMA. De toute évidence, ils peuvent être non contigus; La mémoire est seulement garantie contiguë dans les multiples de la taille de la page.

dma_sync_single_for_cpu pour effectuer les vidages de mémoire cache ou les tampons de tampon de fusion nécessaires. Cet appel garantit que le processeur peut réellement voir le résultat du DMA, puisque sur de nombreux systèmes, la modification de la RAM physique derrière le dos du processeur entraîne des caches périmés.

dma_unmap_page pour libérer le mappage IOMMU (si nécessaire sur votre plate-forme).

put_page de désélectionner la (les) page (s) utilisateur (s).

Notez que doit vérifier les erreurs tout le chemin ici, car il y a des ressources limitées partout. get_user_pages renvoie un nombre négatif pour une erreur pure et simple (-errno), mais il peut renvoyer un nombre positif pour vous indiquer le nombre de pages qu'il a réellement réussi à épingler (la mémoire physique n'est pas illimitée). Si cela est inférieur à ce que vous avez demandé, vous devez toujours parcourir toutes les pages pin pour appeler put_page sur eux. (Sinon, vous perdez la mémoire du noyau, très mauvais.)

dma_map_page peut également renvoyer une erreur (-errno), car les mappages IOMMU sont une autre ressource limitée.

dma_unmap_page et put_page retour void, comme d'habitude pour Linux "libérer" les fonctions. (Les routines de gestion des ressources du noyau Linux ne renvoient des erreurs que parce que quelque chose s'est mal passé, pas parce que vous avez foiré un mauvais pointeur ou quelque chose comme ça ... L'hypothèse de base est que vous ne voyez jamais le code noyau.Bien que get_user_pages vérifie la validité des adresses utilisateur et renvoie une erreur si l'utilisateur vous a donné un mauvais pointeur.)

Vous pouvez également utiliser les fonctions _sg si vous voulez qu'une interface conviviale soit dispersée/rassemblée. Ensuite, vous appelleriez dma_map_sg au lieu de dma_map_page, dma_sync_sg_for_cpu au lieu de dma_sync_single_for_cpu, etc.

Notez également que bon nombre de ces fonctions peuvent être plus ou moins pas d'habitation sur votre plate-forme, de sorte que vous pouvez souvent obtenir loin d'être bâclée . (En particulier, dma_sync _... et dma_unmap _... ne font rien sur mon système x86_64.) Mais sur ces plateformes, les appels eux-mêmes sont compilés dans rien, donc il n'y a aucune excuse pour être bâclé.

+1

J'ai récemment été embauché pour implémenter un pilote pour le port SSI d'un processeur iMX pour un projet embarqué et DMA ne fonctionnait pas avec ces fonctions. BSP (Board Support Package) de Freescale a une foule de fonctions (appels API) pour leur cœur SDMA sur la puce iMX51. Je suppose que l'OP est sur le matériel Intel x86 ... Linux est un sac si mélangé lorsque vous sortez des sentiers battus. – Eric

2

La fonction remap_pfn_range (utilisée dans l'appel du pilote mmap) peut être utilisée pour mapper la mémoire du noyau à l'espace utilisateur. Un exemple réel peut être trouvé dans le pilote de caractères mem drivers/char/mem.c.