2

I'm trying to reading PCI CSR (Configuration Space Register) on my system via open,mmap /dev/mem.

I met some problems when using 8 byte length reading

Here is the minimal working example of my code

#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#define FATAL                                                                 \
    do {                                                                      \
        fprintf(stderr,                                                       \
                "Error at line %d, file %s (%d) [%s]\n",                      \
                __LINE__,                                                     \
                __FILE__,                                                     \
                errno,                                                        \
                strerror(errno));                                             \
        exit(1);                                                              \
    } while(0)
#define PAGE_SIZE 4096UL
#define PAGE_MASK (PAGE_SIZE - 1)

typedef struct rw_config rw_config;

struct rw_config {
    uint64_t address;
    uint64_t data;
};

static uint64_t _mmio_read_worker(uint64_t address) {
    int fd;
    void *map_base = NULL;
    void *map_address = NULL;
    uint64_t result = 0UL;

    if((fd = open("/dev/mem", O_RDONLY | O_SYNC)) < 0) FATAL;
    // PAGE_SIZE = 4096UL
    // PAGE_MASK = (PAGE_SIZE - 1) = 4095UL
    if((map_base = mmap(NULL,
                        PAGE_SIZE,
                        PROT_READ,
                        MAP_SHARED,
                        fd,
                        (address & ~PAGE_MASK)))
       == MAP_FAILED)
        FATAL;
    map_address = map_base + (address & PAGE_MASK);

    result = *(uint64_t *)map_address;
    printf("uint32_t 0x%016x, uint64_t 0x%016lx\n",
           (*(uint32_t *)map_address),
           (*(uint64_t *)map_address));

    close(fd);
    return result;
}

void rw_worker(rw_config *cfg) {
    cfg->data = _mmio_read_worker(cfg->address);
    return;
}

int main(int argc, char *argv[]) {
    rw_config *cfg = malloc(sizeof(rw_config));

    cfg->address = 0x80000000;
    cfg->data = 0x0;

    rw_worker(cfg);

    return 0;
}

Reading the address = 0x80000000 which is pci mmio base address.

The output of my code is as follows:

uint32_t 0x0000000009a28086, uint64_t 0xffffffffffffffff

And I try to using gdb to get some information.

(gdb) printf "0x%llx\n",(*(uint64_t *)map_address)
0x10000009a28086
# before assigning 'result'
(gdb) printf "0x%llx\n",result
0x0
(gdb) next
# after assigning 'result'
(gdb) printf "0x%llx\n",result
0xffffffffffffffff
(gdb) print map_address
$2 = (void *) 0x7ffff7ffb000
(gdb) x/1xg 0x7ffff7ffb000
0x7ffff7ffb000: 0x0010000009a28086

I guess I fail to casting (void*) to *(uint64_t *), but why?

The value storage in map_address is correct, am I using the wrong way to get the value?

imotfnir
  • 21
  • 3
  • Edit the question to provide a [mre], including a call to `_mmio_read_worker` showing how the address is obtained. – Eric Postpischil Jan 06 '23 at 08:30
  • What is `sizeof(long unsigned)` on your system? Not sure that's the bug but printing `uint64_t` (and friends) using the "normal" %-specifier is dangerous. Ude PRIu64 or PRIx64 – Support Ukraine Jan 06 '23 at 08:30
  • Which value did `address` have in your test? – Support Ukraine Jan 06 '23 at 08:34
  • 2
    In gdb you use `%llx` for printing 64 bit values but in the code you do `%lx`. Why? – Support Ukraine Jan 06 '23 at 08:36
  • `sizeof(long unsigned)` = 8 – imotfnir Jan 06 '23 at 09:24
  • `address = 0x80000000` which is pci base address – imotfnir Jan 06 '23 at 09:28
  • In case of debugging purposes, so I use the longest format – imotfnir Jan 06 '23 at 09:29
  • 1
    I'm not too familiar with the details of PCI, but I suppose there is some memory-mapped hardware register at address 0x80000000. When you read an I/O register, you have to use the proper size. If it's documented as a 32-bit register, you have to do a 32-bit read. It is quite possible for a read of a different size to return incorrect and inconsistent results. Although the hardware is mapped into the memory address space, it isn't actually memory and you shouldn't expect it to behave like memory. – Nate Eldredge Jan 06 '23 at 17:23
  • Also, for MMIO, you need to make sure all your reads and writes are `volatile` so that the compiler really does emit a single load or store of the correct size. So make it `*(volatile uint32_t *)map_address`. – Nate Eldredge Jan 06 '23 at 17:24
  • The point that others are making is that `%x` is to go with an `unsigned int` argument. You are passing a `uint32_t` which is probably the same on your platform, but in general may not be. If you use `%x` then you ought to be casting the argument to `unsigned int`, or better, using the `PRIx32` macro as @SupportUkraine suggests. Likewise `%lx` is `unsigned long` and `%llx` is `unsigned long long`. However, I doubt that this is related to your actual problem. – Nate Eldredge Jan 06 '23 at 17:28
  • I tried testing with another address and it got the correct value. As @NateEldredge say, I think this should be related to the access mechanism of PCI configuration space registers – imotfnir Jan 06 '23 at 18:06

2 Answers2

0

After reading the replies from other members, I read some documents that may be related to this bug, and the following are some of my insights:

  • I tried testing with another address which NOT in PCI CSR(Configuration Space Register), and got the correct value. So I think this bug is related to hardware registers rather than software implementation

  • In EDK II UEFI Writer Guide link, using 64bits read on PCI BAR(Base Address Register, which is a part of PCI CSR) may cause an alignment fault, you should use 2x of 32bits read to achieve 64bits read. Although in the example it is not enforced that the whole CSR has this limitation, but I think there is already a good reason for this bug.

imotfnir
  • 21
  • 3
0

PCIe config space must be read using 1, 2, or 4-byte accesses. 8-byte accesses are not permitted.

This is specified in the PCIe spec, section 2.2.7.1: "For Configuration Requests, Length[9:0] must be 00 0000 01b." (Length is specified in DW.)

My experience is that 8-byte accesses always return FF in all bytes.

prl
  • 11,716
  • 2
  • 13
  • 31
  • Are talking about Type 1 accesses or Type 2? I believe Type 2 can be read in 8 bytes if CPU supports that. – 0andriy Jan 09 '23 at 15:42
  • @0andriy, I think the actual read to the device cannot be 8 bytes because the PCIe message definition for config space accesses doesn't have a way to represent an 8-byte access. The root complex is allowed to break up an 8-byte access into two 4-byte accesses, but I don't know if any actually do that. – prl Jan 10 '23 at 03:47
  • Maybe I was not clear. Under Type 2 I, probably mistakenly, meant ECAM access. I'm not sure how this can't be supported of reading 8-byte at a time (on 64-bit CPU, of course). – 0andriy Jan 12 '23 at 18:36
  • @0andriy, yes, I understood what you meant. I assure you it isn't supported. It will return FFFFFFFFFFFFFFFF, because the PCIe protocol doesn't define a way to perform an 8-byte access to the device's config space and the root complex doesn't break it into two PCIe transactions. – prl Jan 13 '23 at 20:51
  • @0andriy, this is specified in PCIe spec 2.2.7.1: "For Configuration Requests, Length[9:0] must be 00 0000 01b." (Length is specified in DW.) – prl Jan 13 '23 at 21:00
  • What you are telling is not what I'm telling about. Read 7.2.2. It stays that the 8-bytes or whatever that is crossing dword alignment is HW implementation defined. It may or may not work. I believe on Intel it will work. – 0andriy Jan 14 '23 at 10:11
  • @0andriy, I've tried it on Intel systems many times, including yesterday before I responded again, just to be sure I wasn't misremembering. I tested it on a Kaby Lake; what have you tried it on? – prl Jan 15 '23 at 01:26
  • Okay, I have tested on two quite different Intel SoCs and indeed, this does not work. The side effects are (quite) interesting, but it's out of scope of this discussuion. In any case, I would love to find a PCI implementation that allows to cross the boundaries as stated in 7.2.2. – 0andriy Jan 16 '23 at 17:41