PROBLEM
- Wenn Sie einen DMA-Puffer zuweisen oder eine physische Adresse erhalten möchten, geschieht dies normalerweise im Kernel-Bereich, da der Benutzercode niemals mit physischen Adressen herumspielen muss.
- Hugetlbfs bietet nur User-Space-Mappings, um 1 GB große Seiten zuzuweisen und virtuelle Adressen des User-Space zu erhalten
- Es gibt keine Funktion, um eine virtuelle Benutzer-Hugepage-Adresse einer physischen Adresse zuzuordnen
EUREKA
Aber die Funktion ist vorhanden! Tief im Kernel-Quellcode von 2.6 vergraben liegt diese Funktion zum Abrufen einer Strukturseite von einer virtuellen Adresse, die als "nur zum Testen" markiert und mit #if 0:
blockiert ist#if 0 /* This is just for testing */
struct page *
follow_huge_addr(struct mm_struct *mm, unsigned long address, int write)
{
unsigned long start = address;
int length = 1;
int nr;
struct page *page;
struct vm_area_struct *vma;
vma = find_vma(mm, addr);
if (!vma || !is_vm_hugetlb_page(vma))
return ERR_PTR(-EINVAL);
pte = huge_pte_offset(mm, address);
/* hugetlb should be locked, and hence, prefaulted */
WARN_ON(!pte || pte_none(*pte));
page = &pte_page(*pte)[vpfn % (HPAGE_SIZE/PAGE_SIZE)];
WARN_ON(!PageHead(page));
return page;
}
LÖSUNG:Da die obige Funktion nicht wirklich in den Kernel kompiliert ist, müssen Sie sie zu Ihrer Treiberquelle hinzufügen.
BENUTZERSEITIGER ARBEITSABLAUF
- Ordnen Sie 1-GB-Hugepages beim Booten mit den Kernel-Boot-Optionen zu
- Rufen Sie get_huge_pages() mit hugetlbfs auf, um den Userspace-Zeiger (virtuelle Adresse) zu erhalten
- Übergeben Sie die virtuelle Adresse des Benutzers (normaler Zeiger in unsigned long umgewandelt) an den Treiber ioctl
ARBEITSABLAUF DES KERNEL-TREIBERS
- Akzeptiere die virtuelle Benutzeradresse über ioctl
- Rufen Sie follow_huge_addr auf, um die Strukturseite zu erhalten*
- Rufen Sie page_to_phys auf der Strukturseite* auf, um die physische Adresse zu erhalten
- Physische Adresse für Gerät für DMA bereitstellen
- Rufen Sie kmap auf der Strukturseite* auf, wenn Sie auch einen virtuellen Kernel-Zeiger wollen
HAFTUNGSAUSSCHLUSS
- Die obigen Schritte werden einige Jahre später in Erinnerung gerufen. Ich habe den Zugriff auf den ursprünglichen Quellcode verloren. Gehen Sie sorgfältig vor und stellen Sie sicher, dass ich keinen Schritt vergesse.
- Der einzige Grund, warum dies funktioniert, ist, dass 1 GB große Seiten beim Booten zugewiesen werden und ihre physischen Adressen dauerhaft gesperrt sind. Versuchen Sie nicht, eine virtuelle Benutzeradresse, die nicht 1 GB groß ist, auf eine physische DMA-Adresse abzubilden! Du wirst eine schlechte Zeit haben!
- Testen Sie Ihr System sorgfältig, um zu bestätigen, dass Ihre 1 GB großen Seiten tatsächlich im physischen Speicher gesperrt sind und dass alles genau funktioniert. Dieser Code hat bei meinem Setup einwandfrei funktioniert, aber hier besteht große Gefahr, wenn etwas schief geht.
- Dieser Code funktioniert garantiert nur auf x86/x64-Architektur (wobei physikalische Adresse ==Busadresse) und auf Kernel-Version 2.6.XX. In späteren Kernel-Versionen gibt es möglicherweise einen einfacheren Weg, dies zu tun, oder es kann jetzt völlig unmöglich sein.
Dies wird im Kernel-Bereich nicht häufig gemacht, also nicht zu viele Beispiele.
Wie jede andere Seite werden riesige Seiten mit alloc_pages zugewiesen, nach der Melodie:
struct page *p = alloc_pages(GFP_TRANSHUGE, HPAGE_PMD_ORDER);
HPAGE_PMD_ORDER ist ein Makro, das eine Reihenfolge einer einzelnen riesigen Seite in Bezug auf normale Seiten definiert. Das Obige impliziert, dass transparente riesige Seiten im Kernel aktiviert sind.
Dann können Sie den erhaltenen Seitenzeiger auf die übliche Weise mit kmap() abbilden.
Haftungsausschluss:Ich habe es nie selbst ausprobiert, daher müssen Sie möglicherweise etwas herumexperimentieren. Eine Sache, die Sie überprüfen sollten, ist Folgendes:HPAGE_PMD_SHIFT repräsentiert eine Bestellung einer kleineren "riesigen" Seite. Wenn Sie diese riesigen 1-GB-Seiten verwenden möchten, müssen Sie wahrscheinlich eine andere Reihenfolge ausprobieren, wahrscheinlich PUD_SHIFT - PAGE_SHIFT.