4

I am trying to read some physical address that is mapped to PCIe device using /dev/mem. The PCIe device is mapped to 0x387ffa000000:

bash# lspci -s 1a:00.0 -v | grep Memory
      Memory at 387ffa000000 (64-bit, prefetchable) [size=32M]

So I wrote some program that's based on what I found here at SO which makes use of mmap():

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);

    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);
    if (fd < 0) {
        perror("Can't open");
        return 1;
    }

    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]);
    printf("\n");

    return 0;
}

Although this works:

bash# ./mem_read.out 0x387ffa000000 0x10
00 1f 00 18 00 05 07 08 00 00 00 00 00 00 00 00

I wanted to try the same only with read()/write() calls instead of mmap().
But when I tried this:

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

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

    if (!len) {
        printf("size argument can't be 0\n");
        return 1;
    }

    int fd = open("/dev/mem", O_SYNC);
    if (fd < 0) {
        perror("Can't open");
        return 1;
    }

    if (lseek(fd, offset, SEEK_SET) < 0) {
        perror("Can't seek");
        return 1;
    }

    char* buff = (char*)malloc(len * sizeof(char) + 1);
    if (read(fd, buff, len) < 0) {
        perror("Can't read");
        return 1;
    }

    printf("%s\n", buff);

    return 0;
}

It fails with:

bash# ./mem_read.out 0x387ffa000000 0x10
Can't read: Bad address

That same code works when instead of /dev/mem I use a regular text file.
Does anyone have an idea why the former works but the latter does not?

Just for additional context, "Bad address" is printed when errno == EFAULT, which returned by read() when:

buf is outside your accessible address space.

which makes no sense to me.

so.very.tired
  • 2,958
  • 4
  • 41
  • 69
  • 1
    Please don't use externally-hosted images to show text. Copy/paste the text *into the question*, where we can see it. – unwind Sep 29 '21 at 08:59

1 Answers1

1

Looking at the code for the /dev/mem device, it seems to check that the address is a valid physical address.

haggai_e
  • 4,689
  • 1
  • 24
  • 37
  • OK, I see that in the `valid_phys_addr_range()` check it says in comment: `/* Can we access it for direct reading/writing? Must be RAM: */` In my case, the address isn't mapped to RAM but rather to I/O... – so.very.tired Oct 01 '21 at 08:49