0

I have a driver that does a DMA transfer to a memory thats malloc'ed in a userspace application and then handed down to the kernel (get_user_pages...).

Problem: I get the "invalid pointer" message when calling free(). I print both pointer values, they are equal, not changed. The error does NOT happen when I use posix_memalign() instead malloc(). In case of posix_memalign() I get always page aligned pointers ( 0x....000). With malloc() its often unaligned addresses.

I tried already skipping the Driver DMA transfer call completely, basically just doing malloc() and free() in the application - it then always works!

That tells me that somehow the kernel seem to think the pages are still "locked" or so in kernel space.

I digged already through some drivers, especially the drivers/media/pci/ivtv/ivtv-udma.c and ivtv-yuv.c look interesting since they seem to do exactly the same.

There I found the function "put_page" which I tried already in my driver, but it didnt help, more, the driver then stuck.

Can someone point me in the right direction or an information source how the correct handling of such user pages in kernel space is ?

Here the relevant code. The order is basically: get_user_pages -> dma_map_page -> kick off HW DMA (in our FPGA) -> dma_unmap_page -> SetPageDirty -> put_page

But still same error as below occurs for non-aligned addresses.

perform_user_dma_func(..)
{
[...]

    rv = get_user_pages( current, current->mm, uaddr, nr_pages, (direction == DMA_FROM_DEVICE), 0, pages, NULL);
    if( rv < nr_pages )
        goto CLEANUP;

    /*--- build scatter/gather list ---*/
    offset = uaddr & ~PAGE_MASK;
#ifdef VME4L_DBG_DMA_DATA
    initOffs = offset;
#endif

    for ( i = 0; i < nr_pages; ++i, sgList++ ) {
        struct page *page = pages[i];
        sgList->dmaLength = PAGE_SIZE - offset;
        dmaAddr = dma_map_page( pDev, page, 0x0, PAGE_SIZE, direction );
        if ( dma_mapping_error( pDev, dmaAddr ) ) {
            printk( KERN_ERR "error mapping DMA space with dma_map_page\n" );
            goto CLEANUP;
        } else {
            sgList->dmaDataAddress = dmaAddr + offset; /* Add offset between page begin and payload data, often > 0 */
            sgList->dmaPageAddress = dmaAddr; /* store page address for later dma_unmap_page */
        }

        if( totlen + sgList->dmaLength > count )
            sgList->dmaLength = count - totlen;

        VME4LDBG(" sglist %d: pageAddr=%p off=0x%lx dmaAddr=%p length=0x%x\n", i, page_address(page), offset, dmaAddr, sgList->dmaLength);
        totlen += sgList->dmaLength;
        offset = 0;
    }

    /*--- now do DMA in HW (device touches memory) ---*/
    rv = vme4l_perform_zc_dma( spc, sgListStart, nr_pages, blk->direction, blk->vmeAddr, swapMode );

CLEANUP:

    /*--- free pages ---*/
    sgList = sgListStart;
    for (i = 0; i < nr_pages; i++, sgList++) {
        dma_unmap_page( pDev, sgList->dmaPageAddress, PAGE_SIZE, direction );
    }

    /* mark pages as dirty */
    if( blk->direction == READ ) {
        for (i = 0; i < nr_pages; i++ ) {
            if ( !PageReserved( pages[i] ))
                SetPageDirty( pages[i] );
        }
    }

    sgList = sgListStart;
    for (i = 0; i < nr_pages; i++, sgList++) {
        /* __free_page( pages[i] ); */
        put_page( pages[i] );
    }
[...]
}

The error message when usimg malloc buffer (unaligned memory):

*** Error in `vme4l_rwex': free(): invalid pointer: 0x00000000008e4010 ***
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x777e5)[0x7fe0a34367e5]
/lib/x86_64-linux-gnu/libc.so.6(+0x8037a)[0x7fe0a343f37a]
/lib/x86_64-linux-gnu/libc.so.6(cfree+0x4c)[0x7fe0a344353c]
vme4l_rwex[0x4010f8]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0)[0x7fe0a33df830]
vme4l_rwex[0x401419]
======= Memory map: ========
00400000-00402000 r-xp 00000000 08:15 931122                             /usr/local/bin/vme4l_rwex
00601000-00602000 r--p 00001000 08:15 931122                             /usr/local/bin/vme4l_rwex
00602000-00603000 rw-p 00002000 08:15 931122                             /usr/local/bin/vme4l_rwex
008e4000-00906000 rw-p 00000000 00:00 0                                  [heap]
7fe09c000000-7fe09c021000 rw-p 00000000 00:00 0
7fe09c021000-7fe0a0000000 ---p 00000000 00:00 0
7fe0a31a9000-7fe0a31bf000 r-xp 00000000 08:15 1035123                    /lib/x86_64-linux-gnu/libgcc_s.so.1
7fe0a31bf000-7fe0a33be000 ---p 00016000 08:15 1035123                    /lib/x86_64-linux-gnu/libgcc_s.so.1
7fe0a33be000-7fe0a33bf000 rw-p 00015000 08:15 1035123                    /lib/x86_64-linux-gnu/libgcc_s.so.1
7fe0a33bf000-7fe0a357f000 r-xp 00000000 08:15 1045483                    /lib/x86_64-linux-gnu/libc-2.23.so
7fe0a357f000-7fe0a377f000 ---p 001c0000 08:15 1045483                    /lib/x86_64-linux-gnu/libc-2.23.so
7fe0a377f000-7fe0a3783000 r--p 001c0000 08:15 1045483                    /lib/x86_64-linux-gnu/libc-2.23.so
7fe0a3783000-7fe0a3785000 rw-p 001c4000 08:15 1045483                    /lib/x86_64-linux-gnu/libc-2.23.so
7fe0a3785000-7fe0a3789000 rw-p 00000000 00:00 0
7fe0a3789000-7fe0a378b000 r-xp 00000000 08:15 905612                     /usr/local/lib/libusr_utl.so
7fe0a378b000-7fe0a398a000 ---p 00002000 08:15 905612                     /usr/local/lib/libusr_utl.so
7fe0a398a000-7fe0a398b000 r--p 00001000 08:15 905612                     /usr/local/lib/libusr_utl.so
7fe0a398b000-7fe0a398c000 rw-p 00002000 08:15 905612                     /usr/local/lib/libusr_utl.so
7fe0a398c000-7fe0a398e000 r-xp 00000000 08:15 905739                     /usr/local/lib/libvme4l_api.so
7fe0a398e000-7fe0a3b8e000 ---p 00002000 08:15 905739                     /usr/local/lib/libvme4l_api.so
7fe0a3b8e000-7fe0a3b8f000 r--p 00002000 08:15 905739                     /usr/local/lib/libvme4l_api.so
7fe0a3b8f000-7fe0a3b90000 rw-p 00003000 08:15 905739                     /usr/local/lib/libvme4l_api.so
7fe0a3b90000-7fe0a3bb6000 r-xp 00000000 08:15 1045455                    /lib/x86_64-linux-gnu/ld-2.23.so
7fe0a3da6000-7fe0a3da9000 rw-p 00000000 00:00 0
7fe0a3db2000-7fe0a3db5000 rw-p 00000000 00:00 0
7fe0a3db5000-7fe0a3db6000 r--p 00025000 08:15 1045455                    /lib/x86_64-linux-gnu/ld-2.23.so
7fe0a3db6000-7fe0a3db7000 rw-p 00026000 08:15 1045455                    /lib/x86_64-linux-gnu/ld-2.23.so
7fe0a3db7000-7fe0a3db8000 rw-p 00000000 00:00 0
7ffd66125000-7ffd66146000 rw-p 00000000 00:00 0                          [stack]
7ffd661b4000-7ffd661b6000 r--p 00000000 00:00 0                          [vvar]
7ffd661b6000-7ffd661b8000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
Aborted
Thomas S.
  • 61
  • 4
  • 1
    Could you please show at least some of your code here? Maybe, the most significant parts of your in-lernel DMA setup and teardown. You know, it's a tough task to debug the code that we can't see. –  Oct 05 '17 at 18:33
  • Did you mark the pages as "dirty" before "putting" them? – Ian Abbott Oct 06 '17 at 16:45
  • Hi Bronislav, Ian, find above the attached code snipped. I do the SetPageDirty there, but it still brings the same results. Thanks! – Thomas S. Oct 09 '17 at 10:02
  • It turned out that chunk storage information from malloc() was overwritten. If I dont do dma_map_page() at all, and just get the user pages phys. address and pass that one to the DMA, the transfer works without the invalid pointer error. But there seems to be a cache flushing problem remaining, I see the arriving DMA data in physical RAM, but not reflected in the applications malloc()ed buffer. – Thomas S. Nov 09 '17 at 11:20

1 Answers1

0

Solved it myself.

turned out that mapping with dma_map_page() makes the whole transferred data visible correctly in RAM, but every other data in that page is overwritten (including malloc's pointer management data right before and after the transferred data, which leads to the libc error prompt), the caching somehow doesn't work like expected. Upon dma_unmap_page() the data were visible only every several call to the function.

With dma_map_single() things look better. I do get_user_pages_fast(), then dma_map_single() the pages virtual address plus the offset - works fine and DMA debug functions in the kernel don't complain. See code below.

    for (i = 0; i < nr_pages; ++i, sgList++) {
            struct page *page = pages[i];
            sgList->dmaLength  = PAGE_SIZE - offset;
            pVirtAddr = ((unsigned char*)(page_address( page ))) + offset;

            if( totlen + sgList->dmaLength > count )
                sgList->dmaLength = count - totlen;

            dmaAddr = dma_map_single( pDev, pVirtAddr, sgList->dmaLength, direction );
            if ( dma_mapping_error( pDev, dmaAddr ) ) {
                   printk( KERN_ERR "*** error mapping DMA space!\n" );
                   goto CLEANUP;
            } else {
                   sgList->dmaAddress = dmaAddr;
            }

            VME4LDBG(" sglist %d: pageAddr=%p off=0x%04lx dmaAddr=%p length=0x%04x\n", i, page_address(page), offset, dmaAddr, sgList->dmaLength);
            totlen += sgList->dmaLength;
            offset = 0;
       }

       /*--- now do DMA in HW (device touches memory) ---*/
       rv = vme4l_perform_zc_dma( spc, sgListStart, nr_pages, blk->direction, blk->vmeAddr, swapMode );

CLEANUP:
       /*--- free pages ---*/
       if( locked ) {
             sgList = sgListStart;
             for (i = 0; i < nr_pages; i++, sgList++) {
                 dma_unmap_single( pDev, sgList->dmaAddress, sgList->dmaLength , direction );
                 put_page( pages[i] ); /* release pages locked with get_user_pages */
             }
   } 
Thomas S.
  • 61
  • 4