PROBLÉM
- Běžně, pokud chcete alokovat vyrovnávací paměť DMA nebo získat fyzickou adresu, provádí se to v prostoru jádra, protože uživatelský kód by se nikdy neměl potýkat s fyzickými adresami.
- Hugetlbfs poskytuje pouze mapování uživatelského prostoru pro přidělení 1 GB velkých stránek a získání virtuálních adres uživatelského prostoru
- Neexistuje žádná funkce pro mapování virtuální adresy obrovské stránky uživatele na fyzickou adresu
EUREKA
Ale funkce existuje! Tato funkce je ukryta hluboko ve zdrojovém kódu jádra 2.6 pro získání stránky struktury z virtuální adresy, označené jako „jen pro testování“ a blokované #if 0:
#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;
}
ŘEŠENÍ:Protože výše uvedená funkce není ve skutečnosti zkompilována do jádra, budete ji muset přidat do zdrojového kódu ovladače.
PRACOVNÍ POSTUP NA STRANĚ UŽIVATELE
- Přidělte 1 GB obrovských stránek při spouštění pomocí možností spouštění jádra
- Zavolejte get_huge_pages() s hugetlbfs a získejte ukazatel uživatelského prostoru (virtuální adresu)
- Předejte virtuální adresu uživatele (normální přetypování ukazatele na unsigned long) ovladači ioctl
PRACOVNÍ POSTUP OVLADAČE JADER
- Přijměte virtuální adresu uživatele prostřednictvím ioctl
- Zavolejte na follow_huge_addr a získejte stránku struct*
- Zavolejte page_to_phys na stránce struktur* a získejte fyzickou adresu
- Zadejte fyzickou adresu zařízení pro DMA
- Zavolejte kmap na stránce struct*, pokud chcete také virtuální ukazatel jádra
ODMÍTNUTÍ ODPOVĚDNOSTI
- Výše uvedené kroky si připomínáme o několik let později. Ztratil jsem přístup k původnímu zdrojovému kódu. Udělejte si náležitou péči a ujistěte se, že nezapomenu na krok.
- Jediným důvodem, proč to funguje, je to, že při spouštění je alokováno 1 GB velkých stránek a jejich fyzické adresy jsou trvale uzamčeny. Nepokoušejte se namapovat uživatelskou virtuální adresu, která není zálohována obrovskou stránkou 1 GB, na fyzickou adresu DMA! Budeš se mít špatně!
- Pečlivě otestujte svůj systém, abyste se ujistili, že vaše stránky o velikosti 1 GB jsou ve skutečnosti uzamčeny ve fyzické paměti a že vše funguje přesně. Tento kód fungoval na mém nastavení bezchybně, ale pokud se něco pokazí, existuje zde velké nebezpečí.
- Je zaručeno, že tento kód bude fungovat pouze na architektuře x86/x64 (kde fyzická adresa ==adresa sběrnice) a na jádře verze 2.6.XX. Může existovat jednodušší způsob, jak to udělat v pozdějších verzích jádra, nebo to může být nyní zcela nemožné.
To se v prostoru jádra běžně neprovádí, takže není příliš mnoho příkladů.
Stejně jako každá jiná stránka jsou velké stránky přiřazeny pomocí alloc_pages, podle melodie:
struct page *p = alloc_pages(GFP_TRANSHUGE, HPAGE_PMD_ORDER);
HPAGE_PMD_ORDER je makro, které definuje pořadí jedné velké stránky z hlediska normálních stránek. Výše uvedené znamená, že v jádře jsou povoleny průhledné velké stránky.
Poté můžete pokračovat v mapování získaného ukazatele stránky obvyklým způsobem pomocí kmap().
Upozornění:Sám jsem to nikdy nezkoušel, takže možná budete muset trochu experimentovat. Jedna věc, kterou je třeba zkontrolovat, je toto:HPAGE_PMD_SHIFT představuje pořadí menší "obrovské" stránky. Pokud chcete použít tyto obří 1GB stránky, budete pravděpodobně muset zkusit jiné pořadí, pravděpodobně PUD_SHIFT - PAGE_SHIFT.