2015-11-20 31 views
8

Pracuję z szybką kartą szeregową do przesyłania danych o wysokiej szybkości z zewnętrznego źródła do Linux-a z kartą PCIe. Karta PCIe została dostarczona z niektórymi sterownikami innych firm, które używają dma_alloc_coherent do alokacji buforów DMA w celu odbioru danych. Ze względu na ograniczenia Linuksa takie podejście ogranicza transfer danych do 4 MB. Czytałem i próbowałem wielu metod przydzielania dużego bufora DMA i nie udało mi się go uruchomić.Duży PCIe DMA Linux x86-64

Ten system ma 32 GB pamięci i działa w Red Hat z wersją jądra 3.10 i chciałbym udostępnić 4 GB tego pliku dla ciągłego DMA. Wiem, że preferowaną metodą jest rozproszenie/zbieranie, ale nie jest to możliwe w mojej sytuacji, ponieważ istnieje sprzętowy układ, który przetłumaczył protokół szeregowy na DMA poza moją kontrolą, gdzie jedyną rzeczą, którą mogę kontrolować jest dodanie przesunięcia do adresy przychodzące (tj. adres zerowy widziany z systemu zewnętrznego można odwzorować na adres 0x700000000 na lokalnej magistrali).

Ponieważ jest to jednorazowy komputer laboratoryjny, myślę, że najszybszym/najłatwiejszym podejściem byłoby użycie parametru konfiguracyjnego boot = 28 GB. Mam to działa dobrze, ale następnym krokiem do uzyskania dostępu do tej pamięci z wirtualnej przestrzeni jest miejsce, w którym mam problemy. Tu jest mój kod skondensowana do odpowiednich komponentów:

W module jądra:

size_t len = 0x100000000ULL; // 4GB 
size_t phys = 0x700000000ULL; // 28GB 
size_t virt = ioremap_nocache(phys, len); // address not usable via direct reference 
size_t bus = (size_t)virt_to_bus((void*)virt); // this should be the same as phys for x86-64, shouldn't it? 

// OLD WAY 
/*size_t len = 0x400000; // 4MB 
size_t bus; 
size_t virt = dma_alloc_coherent(devHandle, len, &bus, GFP_ATOMIC); 
size_t phys = (size_t)virt_to_phys((void*)virt);*/ 

W aplikacji:

// Attempt to make a usable virtual pointer 
u32 pSize = sysconf(_SC_PAGESIZE); 
void* mapAddr = mmap(0, len+(phys%pSize), PROT_READ|PROT_WRITE, MAP_SHARED, devHandle, phys-(phys%pSize)); 
virt = (size_t)mapAddr + (phys%pSize); 

// do DMA to 0x700000000 bus address 

printf("Value %x\n", *((u32*)virt)); // this is returning zero 

Inną ciekawą rzeczą jest to, że przed wykonaniem tego wszystkiego, fizyczne adres zwrócony z dma_alloc_coherent jest większy niż ilość pamięci RAM w systemie (0x83d000000). Myślałem, że w x86 pamięć RAM będzie zawsze najniższym adresem i dlatego spodziewam się adresu mniejszego niż 32 GB.

Każda pomoc zostanie doceniona.

+1

Błąd ... '0x770000000ULL' to 29,75 GB, nie 28 ... Zamiast tego wypróbuj' 0x700000000'. –

+0

Dope, głupi błąd matematyczny. Nadal nie powinno mieć znaczenia, ponieważ obszar ten nadal powinien być prawidłową pamięcią RAM. Nie poszedłem jeszcze do walizki testowej o pojemności 4 GB i nadal używałem tylko 4 MB. Zaktualizuje pytanie. – LINEMAN78

+0

Mam pod ręką system pamięci 32 GB. Czy mógłbyś opublikować absolutny-barebone, ale kompletny plik źródłowy modułu jądra, jak również bezwzględnie minimum program trybu użytkownika do przetestowania? Ponadto, dlaczego oznaczono tagami [C++], gdy jądro systemu Linux jest wyłącznie [c], a fragment kodu użytkownika, który wyświetlasz, używa wyłącznie C API? –

Odpowiedz

0

Zamiast ograniczenie ilości pamięci systemowej poprzez mem, spróbuj użyć CMA: https://lwn.net/Articles/486301/

Korzystanie z wiersza poleceń jądra argumentu CMA pozwala zarezerwować pewną ilość pamięci dla operacji DMA, które jest gwarantowane być ciągłe. Jądro zezwoli procesom innym niż DMA na dostęp do tej pamięci, ale gdy tylko operacja DMA potrzebuje tej pamięci, procesy inne niż DMA zostaną wyeksmitowane. Tak więc radziłbym nie zmieniać twojego parametru mem, ale dodawać cma=4G do twojego cmdline. dma_alloc_coherent powinien automatycznie pobierać dane z zarezerwowanego miejsca, ale możesz włączyć debugowanie CMA w konfiguracji jądra, aby się upewnić.