Wie kann man aus einer virtuellen Adresse eine physikalische ausrechnen?



  • Hallo Forum,

    ich habe einen Treiber, der einen DMA-Controller initialisieren und damit Übertragungen starten soll. Der DMA-Controller arbeitet mit physikalischen Adressen. Die "User Space" Applikationen übergeben ihre virtuellen gewünschten Ziel- und Quelladressen an den Treiber, der daraus die physikalischen Adressen berechnet. Ich habe bis jetzt zwei Möglichkeiten ausprobiert, die bis jetzt funktioniert haben:

    static dma_addr_t virt_to_dma_addr(void * const p_virt)
    {
    #if 0
        const unsigned long addr = (unsigned long)(p_virt);
        dma_addr_t dma_addr = 0;
        dma_addr_t offset = 0;
    
        dma_addr = (dma_addr_t)virt_to_bus(page_address(pte_page(*pte_offset_kernel(pmd_offset(pgd_offset(current->mm, addr), addr), addr))));
        offset = (addr & (~PAGE_MASK));
    
        return (dma_addr | offset);
    #else
        const unsigned long addr = (unsigned long)(p_virt);
        struct page *p_page = NULL;
        dma_addr_t dma_addr = 0;
        dma_addr_t offset = 0;
        int ret = 0;
    
        ret = get_user_pages(current, current->mm, addr, 1, 0, 0, &p_page, NULL);
    
        if ((ret >= 0) && (NULL != p_page))
        {
            dma_addr = page_to_pfn(p_page) * PAGE_SIZE;
            offset = (addr & (~PAGE_MASK));
        }
    
        return (dma_addr | offset);
    #endif
    }
    

    Nun hat sich in der User Space Applikation folgendes geändert: Sie öffnet eine Framebuffer-Gerätedatei und holt sich mit mmap() einen Zeiger auf den Framebuffer. Dieser Zeiger kommt bei meinem Treiber an und die obige Funktion berechnet daraus aus unerklärlichen Gründen eine falsche physikalische Adresse.

    Hat jemand eine Idee, wie man es mit der Umrechnung richtig macht? Aus einer User-Space virtuellen Adresse eine physikalische? Die Kernelversion ist 2.6.27.18. Die CPU ist ARMv6-compatible processor rev 4 (v6l).



  • Habe es nun wie folgt gemacht:

    static dma_addr_t virt_to_dma_addr(void * const p_virt)
    {
        const unsigned long addr = (unsigned long)p_virt;
        const struct vm_area_struct *p_vma = NULL;
        dma_addr_t dma_addr = 0;
    
        p_vma = find_vma(current->mm, addr);
    
        if (NULL == p_vma)
        {
            /* find_vma() failed */
            printk(KERN_ALERT "Kernel driver: find_vma() failed. (%s, %u)\n", __FILE__, __LINE__);
        }
        else
        {
            dma_addr = ((p_vma->vm_pgoff * PAGE_SIZE) + (addr - p_vma->vm_start));
        }
    
        return dma_addr;
    }
    

    Weiss vielleicht jemand, ob find_vma() in diesem Fall irgendwelche Nebeneffekte haben könnte?



  • Das ist kein Kernel-Coding-Style!

    Der einzige Seiteneffekt ist das aktualisieren des Caches, siehe hier:

    /* Look up the first VMA which satisfies  addr < vm_end,  NULL if none. */
    struct vm_area_struct * find_vma(struct mm_struct * mm, unsigned long addr)
    {
      struct vm_area_struct *vma = NULL;
    
      if (mm) {
        /* Check the cache first. */
        /* (Cache hit rate is typically around 35%.) */
        vma = mm->mmap_cache;
        if (!(vma && vma->vm_end > addr && vma->vm_start <= addr)) {
          struct rb_node * rb_node;
    
          rb_node = mm->mm_rb.rb_node;
          vma = NULL;
    
          while (rb_node) {
    	struct vm_area_struct * vma_tmp;
    
    	vma_tmp = rb_entry(rb_node,
    			   struct vm_area_struct, vm_rb);
    
    	if (vma_tmp->vm_end > addr) {
    	  vma = vma_tmp;
    	  if (vma_tmp->vm_start <= addr)
    	    break;
    	  rb_node = rb_node->rb_left;
    	} else
    	  rb_node = rb_node->rb_right;
          }
          if (vma)
    	mm->mmap_cache = vma;
        }
      }
      return vma;
    }
    


  • Kernel-Hacker schrieb:

    Das ist kein Kernel-Coding-Style!

    Ok. Mein Coding-Style wird aber überall sauber eingerückt, wie in guten so auch in schlechten Zei.. Editoren 😉

    Kernel-Hacker schrieb:

    Der einzige Seiteneffekt ist das aktualisieren des Caches

    Ok, was meinst Du genau mit "aktualisieren des Caches", Prefetching? In welcher Zeile wird der Cache aktualisiert?



  • abc.w schrieb:

    Kernel-Hacker schrieb:

    Das ist kein Kernel-Coding-Style!

    Ok. Mein Coding-Style wird aber überall sauber eingerückt, wie in guten so auch in schlechten Zei.. Editoren 😉

    Kernel-Hacker schrieb:

    Der einzige Seiteneffekt ist das aktualisieren des Caches

    Ok, was meinst Du genau mit "aktualisieren des Caches", Prefetching? In welcher Zeile wird der Cache aktualisiert?

    Mein Snippet wurde leider furchtbar verstümmelt. In Zeile 30 wird geprüft ob eine VMA gefunden wurde und falls ja wird mm->mmap_cache aktualisiert. Das meinte ich mit Cache aktualisieren.



  • Ach so, ich sehe es - danke schön für die Aufklärung! 🙂



  • The MmGetPhysicalAddress routine returns the physical address corresponding to a valid nonpaged virtual address.

    PHYSICAL_ADDRESS
    MmGetPhysicalAddress(
    IN PVOID BaseAddress
    );

    Parameters
    BaseAddress
    Pointer to the virtual address for which to return the physical address.

    Return Value

    MmGetPhysicalAddress returns the physical address that corresponds to the given virtual address.

    Do not use this routine to obtain physical addresses for use with DMA operations. For information about the proper techniques for performing DMA operations, see Adapter Objects and DMA .



  • punky schrieb:

    The MmGetPhysicalAddress routine returns the physical address corresponding to a valid nonpaged virtual address.

    PHYSICAL_ADDRESS
    MmGetPhysicalAddress(
    IN PVOID BaseAddress
    );

    Parameters
    BaseAddress
    Pointer to the virtual address for which to return the physical address.

    Return Value

    MmGetPhysicalAddress returns the physical address that corresponds to the given virtual address.

    Do not use this routine to obtain physical addresses for use with DMA operations. For information about the proper techniques for performing DMA operations, see Adapter Objects and DMA .

    a) das ist für Windows
    b) abc.w schreibt in seinem ersten Posting, dass er es für DMA Operationen benötigt und was steht in dem letzen Satz von deinem nicht als Zitat markierten Zitat?



  • http://www.linux-books.us/linux_general_0014.php
    Seite 42, Abschnitt 3.7 sollte sein was du brauchst, wenn ich dich richtig verstehe. Die Funktion heißt virt_to_phys(void*) und sollte auch unter anderen Architekturen als i386 verfügbar sein.

    @punky: Du hast aber schon bemerkt, dass wir hier nicht Windows besprechen?!
    Und dann wär dein Hinweis auch noch sowieso unbrauchbar:
    "Do not use this routine to obtain physical addresses for use with DMA operations. For information about the proper techniques for performing DMA operations, see Adapter Objects and DMA ."



  • YASC schrieb:

    http://www.linux-books.us/linux_general_0014.php
    Seite 42, Abschnitt 3.7 sollte sein was du brauchst, wenn ich dich richtig verstehe. Die Funktion heißt virt_to_phys(void*) und sollte auch unter anderen Architekturen als i386 verfügbar sein.

    Danke schön für den Link!
    Bezüglich der virt_to_phys() Funktion - diese liefert in meinem Fall auch falsche physikalische Adressen, ich hatte sie noch in meinen ersten Versuchen ausprobiert...



  • YASC schrieb:

    http://www.linux-books.us/linux_general_0014.php
    Seite 42, Abschnitt 3.7 sollte sein was du brauchst, wenn ich dich richtig verstehe. Die Funktion heißt virt_to_phys(void*) und sollte auch unter anderen Architekturen als i386 verfügbar sein.

    Die Funktion ist nur im Kernel-Space zu gebrauchen. Er bekommt die virtuelle Adresse von einer Userspace-Anwendung, daher geht das (leider) nicht.


Anmelden zum Antworten