Search code examples
carmdebianmmap

mmap() fails while devmem2 succeeds (C/CPP) [Allwinner A20]


I am trying to access the hardware registers of an A20 SOM by mapping them to userspace. In this case, target is the PIO, listed at physical address 0x01C20800.

The official Olimex Debian7 (wheezy) image is being used. Kernel Linux a20-olimex 3.4.90+

I was able to verify the location by using the devmem2 tool and Allwinner's documentation on the said memory space (switched the pinmode and level with devmem).

The mmap call on the other hand

*map = mmap(
        NULL,
        BLOCK_SIZE,    // = (4 * 1024)
        PROT_READ | PROT_WRITE,
        MAP_SHARED,
        *mem_fd,
        *addr_p       
);

fails with mmap error: Invalid argument

Here's a more complete version of the code: http://pastebin.com/mfEuVdbJ

Don't worry about the pointers as the same code does work when accessing UART0 at 0x01C28000. Although only UART0 (and UART4), which is used as serial console. I've decompiled the script.bin (still in use despite DTB) without success, as UART 0, 7 and 8 are enabled there.

I am also logged in as user root

I would still guess something related to permissions but I'm pretty lost right now since devmem has no problem at all

> root@a20-olimex:~# devmem2 0x01c20800 w /dev/mem opened. Memory mapped
> at address 0xb6f85000.

Solution

  • While sourcejedi didn't certainly fix my issue, he gave me the right approach. I took a look at the forementioned devmem tool's source to discover that the mmap call's address is masked

    address & ~MAP_MASK to get the entire page, which is essentially the same operation as in my comment.

    However, to get back to the right place after the mapping has been done, you have to add the mask back

    final_address = mapped_address + (target_address & MAP_MASK);

    This resulted in following code (based on OP's pastebin)

    Where MAP_MASK = (sysconf(_SC_PAGE_SIZE) - 1) in this case 4095

    int map_peripheral(unsigned long *addr_p, int *mem_fd, void **map, volatile unsigned int **addr)
    {
        if (!(*addr_p)) {
            printf("Called map_peripheral with uninitilized struct.\n");
            return -1;
        }
    
        // Open /dev/mem
        if ((*mem_fd = open("/dev/mem", O_RDWR | O_SYNC)) < 0) {
            printf("Failed to open /dev/mem, try checking permissions.\n");
            return -1;
        }
    
        *map = mmap(
            NULL,
            MAP_SIZE,
            PROT_READ | PROT_WRITE,
            MAP_SHARED,
            *mem_fd,                    // file descriptor to physical memory      virtual file '/dev/mem'
    
            *addr_p & ~MAP_MASK         // address in physical map to be exposed
    /************* magic is here **************************************/
        );
    
        if (*map == MAP_FAILED) {
            perror("mmap error");
            return -1;
        }
    
        *addr = (volatile unsigned int *)(*map + (*addr_p & MAP_MASK));
    /************* and here ******************************************/
        return 0;
    }