3

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:

  • Board : Microzed
  • SoC: Zynq 7020
  • OS : Petalinux 2019.2
  • Kernel version : 4.19.0-xilinx-v2019.2
  • Processor type : ARMv7 Processor rev 0 v7l (armv7l)

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/ :

  • addr = 0x40000000
  • offset = 0x0
  • size = 0x00008000

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 ?...

tjustel
  • 80
  • 1
  • 9
  • Does it happen for *any* positive value of `filePage`? – Some programmer dude Jul 27 '20 at 12:14
  • 1
    @Someprogrammerdude I tried 1, 2, 3, 4 and still errno 22... – tjustel Jul 27 '20 at 12:16
  • What's more `sysconf(_SC_PAGE_SIZE)` and `getconf PAGE_SIZE` are both `4096`. – tjustel Jul 27 '20 at 12:19
  • The mmap flags argument needs to be `MAP_SHARED`, not `MAP_PRIVATE`. – Ian Abbott Jul 27 '20 at 12:28
  • 1
    It could be that nobody ever got around to implementing support for offset > 0 in the generic-uio driver. Does the mmap call succeed if you replace /dev/uio1 with a regular file that's big enough to cover the mapping you're trying to create? – zwol Jul 27 '20 at 12:31
  • @IanAbbott I tried this too, but it doesn't work. Why should it be `MAP_SHARED` ? – tjustel Jul 27 '20 at 12:34
  • @zwol It works when using a regular file so I guess you're right ! Do you want to answer the question or can I do it myself ? – tjustel Jul 27 '20 at 12:54
  • @tjustel Looks like ensc already covered it. – zwol Jul 27 '20 at 13:25
  • 1
    @tjustel `MAP_PRIVATE` certainly wouldn't work if you need to write to device memory because it maps virtual memory as "copy on write". I'm not sure if it can be used for read-only memory, but the convention is to use `MAP_SHARED` for mapping device memory. – Ian Abbott Jul 27 '20 at 13:47

1 Answers1

3

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.

ensc
  • 6,704
  • 14
  • 22
  • What do you mean ? I read [here](https://linux-kernel-labs.github.io/refs/heads/master/labs/memory_mapping.html) _vm_pgoff - the offset of the area within the file_ so isn't it what I want ? – tjustel Jul 27 '20 at 12:45
  • @tjustel It's up to the driver implementing mmap how `vm_pgoff` is to be interpreted. UIO interprets it as an index to select which memory mapping to use, since a UIO device can have several memory mappings. See [here](https://www.kernel.org/doc/html/v4.14/driver-api/uio-howto.html#mmap-device-memory). – Ian Abbott Jul 27 '20 at 12:57
  • All right can you provide the sources of this snippet, and a bit more information on this ? – tjustel Jul 27 '20 at 13:01
  • 1
    @tjustel The snippet is from "drivers/uio/uio.c". – Ian Abbott Jul 27 '20 at 13:32
  • So this is a design bug in the driver? I don't understand how this is even plausibly *supposed* to work since on most archs the actual mmap (mmap2) syscall takes offset as a number of 4096-byte units, which gets multiplied by 4096 before it's passed to other layers in the kernel. – R.. GitHub STOP HELPING ICE Jul 27 '20 at 15:52