0
votes

I need to access and write to some physical addresses in my RAM. I was looking at this answer and the definition of mmap.

If addr is NULL, then the kernel chooses the (page-aligned) address at which to create the mapping; this is the most portable method of creating a new mapping. If addr is not NULL, then the kernel takes it as a hint about where to place the mapping; on Linux, the kernel will pick a nearby page boundary (but always above or equal to the value specified by /proc/sys/vm/mmap_min_addr) and attempt to create the mapping there.

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
    if (argc < 3) {
        printf("Usage: %s <phys_addr> <offset>\n", argv[0]);
        return 0;
    }

    off_t offset = strtoul(argv[1], NULL, 0);
    size_t len = strtoul(argv[2], NULL, 0);

    // Truncate offset to a multiple of the page size, or mmap will fail.
    size_t pagesize = sysconf(_SC_PAGE_SIZE);
    off_t page_base = (offset / pagesize) * pagesize;
    off_t page_offset = offset - page_base;

    int fd = open("/dev/mem", O_SYNC);
    unsigned char *mem = mmap(NULL, page_offset + len, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, page_base);
    if (mem == MAP_FAILED) {
        perror("Can't map memory");
        return -1;
    }

    size_t i;
    for (i = 0; i < len; ++i)
        printf("%02x ", (int)mem[page_offset + i]);

    return 0;
}

Why is the first argument to mmap function NULL ? Shouldn't it be page_base ? We want the mapping to start at page base and extend till offset. I have to do something similar where I have to copy an array of values into the RAM starting from exactly the same locations. Shouldn't this be the call to mmap:

unsigned char *mem = mmap(page_base, page_offset + len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, page_base);

1

1 Answers

1
votes

mmap does not take in physical addresses - all these addresses are virtual. The first argument is a hint telling the virtual address where the mapping should be placed.

You do not need the virtual and physical addresses to be identical in order to be able to write at some physical address. Instead you'd just need to translate physical addresses to virtual addresses. What you need to do, however, is to use MAP_SHARED so that any changes you make into memory will be conveyed to /dev/mem and you won't have a private copy.


If you really want to do an identity mapping, then there is a special flag (or two) to mmap that you have to use for this to really work, which you should | with either MAP_PRIVATE or MAP_SHARED.

MAP_FIXED

Don't interpret addr as a hint: place the mapping at exactly that address. addr must be suitably aligned: for most architectures a multiple of the page size is sufficient; however, some architectures may impose additional restrictions. If the memory region specified by addr and len overlaps pages of any existing mapping(s), then the overlapped part of the existing mapping(s) will be discarded. If the specified address cannot be used, mmap() will fail.

Software that aspires to be portable should use the MAP_FIXED flag with care, keeping in mind that the exact layout of a process's memory mappings is allowed to change significantly between kernel versions, C library versions, and operating system releases. Carefully read the discussion of this flag in NOTES!

This is for example what ld.so uses to load the program at the predefined address

There is a safer version of the flag: MAP_FIXED_NOREPLACE in later Linux kernels, which does not evict the previous mapping if it exists, but returns an error.