PROBLEMA
- Normalmente se vuoi allocare un buffer DMA o ottenere un indirizzo fisico, questo viene fatto nello spazio del kernel, poiché il codice utente non dovrebbe mai avere a che fare con gli indirizzi fisici.
- Hugetlbfs fornisce solo mappature dello spazio utente per allocare pagine enormi da 1 GB e ottenere indirizzi virtuali dello spazio utente
- Non esiste alcuna funzione per mappare l'indirizzo virtuale di un utente hugepage a un indirizzo fisico
EUREKA
Ma la funzione esiste! Sepolta in profondità nel codice sorgente del kernel 2.6 si trova questa funzione per ottenere una pagina struct da un indirizzo virtuale, contrassegnata come "solo per test" e bloccata con #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;
}
SOLUZIONE:Poiché la funzione di cui sopra non è effettivamente compilata nel kernel, sarà necessario aggiungerla al sorgente del driver.
FLUSSO DI LAVORO LATO UTENTE
- Assegna 1 GB di pagine enormi all'avvio con le opzioni di avvio del kernel
- Chiama get_huge_pages() con hugetlbfs per ottenere il puntatore dello spazio utente (indirizzo virtuale)
- Passa l'indirizzo virtuale dell'utente (puntatore normale convertito in unsigned long) al driver ioctl
FLUSSO DI LAVORO DEL DRIVER DEL KERNEL
- Accetta l'indirizzo virtuale dell'utente tramite ioctl
- Chiama follow_huge_addr per ottenere la pagina struct*
- Chiama page_to_phys sulla pagina struct* per ottenere l'indirizzo fisico
- Fornisci l'indirizzo fisico al dispositivo per DMA
- Chiama kmap sulla pagina struct* se vuoi anche un puntatore virtuale del kernel
DISCLAIMER
- I passaggi precedenti vengono ricordati diversi anni dopo. Ho perso l'accesso al codice sorgente originale. Fai la dovuta diligenza e assicurati che non dimentichi un passaggio.
- L'unico motivo per cui funziona è perché all'avvio vengono allocate pagine enormi da 1 GB e i loro indirizzi fisici sono bloccati in modo permanente. Non provare a mappare un indirizzo virtuale utente non supportato da 1 GB di pagine enormi in un indirizzo fisico DMA! Ti divertirai un mondo!
- Testa attentamente sul tuo sistema per confermare che le tue enormi pagine da 1 GB siano effettivamente bloccate nella memoria fisica e che tutto funzioni esattamente. Questo codice ha funzionato perfettamente sulla mia configurazione, ma qui c'è un grande pericolo se qualcosa va storto.
- Si garantisce che questo codice funzioni solo su architettura x86/x64 (dove indirizzo fisico ==indirizzo bus) e su kernel versione 2.6.XX. Potrebbe esserci un modo più semplice per farlo nelle versioni successive del kernel, o potrebbe essere completamente impossibile ora.
Questo non è comunemente fatto nello spazio del kernel, quindi non ci sono troppi esempi.
Proprio come qualsiasi altra pagina, le pagine enormi vengono assegnate con alloc_pages, al ritmo:
struct page *p = alloc_pages(GFP_TRANSHUGE, HPAGE_PMD_ORDER);
HPAGE_PMD_ORDER è una macro, che definisce un ordine di una singola pagina enorme in termini di pagine normali. Quanto sopra implica che le pagine enormi trasparenti siano abilitate nel kernel.
Quindi puoi procedere a mappare il puntatore di pagina ottenuto nel solito modo con kmap().
Dichiarazione di non responsabilità:non l'ho mai provato da solo, quindi potresti dover fare qualche esperimento in giro. Una cosa da verificare è questa:HPAGE_PMD_SHIFT rappresenta un ordine di una pagina "enorme" più piccola. Se desideri utilizzare quelle pagine giganti da 1 GB, probabilmente dovrai provare un ordine diverso, probabilmente PUD_SHIFT - PAGE_SHIFT.