cdriverembedded-linuxmmappetalinux

Why is mmap failing with EINVAL when offset is non-negative (but is a multiple of sysconf(_SC_PAGE_SIZE))?


Firstly a bit of context about the machine I'm working on (it's a SOM with a SoC that includes a FPGA and two CPU, and on one of them there is a linux OS running).

Characteristics:

I have a custom peripheral implemented on the FPGA that provides a standard AXI4 interface to a BRAM memory (thanks to this component). This peripheral is registered in my device-tree and is accessible through the generic-uio driver of the kernel (more documentation about this).

The size of the BRAM memory is 32kB. Here are the mapping characteristics that appear in /sys/class/uio/uioX/maps/map0/ :

I'm working on a C code that simply aims to read this BRAM memory and log what is inside in a text file. In order to do so I use mmap (here is its man page) to create some memory projections of the file located in /dev/uioX (that corresponds to my device).

I want to use only the file pages I need, more precisely mapping the file page per page. Here is the function I use to create the mapping of one page:

/**
 * @brief Get a pointer to the corresponding file page
 * 
 * @param filePage file page number
 * @param fd pointer where the file descriptor will be stored
 * @return int* 
 */
int *getFilePagePointer(int *fd, unsigned int filePage)
{
    // open driver file
    *fd = open("/dev/uio1", O_RDWR);
    if (*fd == -1)
    {
        int errsv = errno;
        fprintf(stderr, "Error while opening /dev/uio1 : errno %d (%s).\n", errsv, strerror(errsv));
        exit(EXIT_FAILURE);
    }

    // choose projection parameters
    int length = sysconf(_SC_PAGE_SIZE);
    int offset = filePage * sysconf(_SC_PAGE_SIZE);

    // create mapping
    void *filePagePointer = mmap((void *)NULL, (size_t)length, PROT_READ, MAP_PRIVATE, *fd, (off_t)offset);
    if (filePagePointer == MAP_FAILED)
    {
        int errsv = errno;
        fprintf(stderr, "Error while mapping file page : errno %d (%s).\n", errsv, strerror(errsv));
        exit(EXIT_FAILURE);
    }

    return (int *)filePagePointer;
}

And then the issue: it works perfectly well if filePage (i.e. offset) is 0 but if it is strictly positive, it fails with errno = 22.

Unless I missed something in the man page, all parameters are valid (offset is a multiple of sysconf(_SC_PAGE_SIZE), addr is NULL, lengthis strictly positive and not too large, and flags are correct). So, why an EINVAL ?...


Solution

  • kernel driver does

    static int uio_find_mem_index(struct vm_area_struct *vma)
    {
        struct uio_device *idev = vma->vm_private_data;
    
        if (vma->vm_pgoff < MAX_UIO_MAPS) {
            if (idev->info->mem[vma->vm_pgoff].size == 0)
                return -1;
            return (int)vma->vm_pgoff;
        }
        return -1;
    }
    

    So it seems, offset is the map index (like in map0) but not the position within the memory.

    You have to mmap() the whole memory.