PCI Express is an evolution of PCI, itself an evolution of ISA bus. These have something in common: they are all based on Intel x86 architecture. They’re all also heavily influenced by Microsoft, who for their part nurtured and promoted ‘jumper-free’ configuration.
One feature of x86 architecture is that has a split I/O vs. memory address space. PCIe, like PCI before it, uses I/O space for card enumeration. Each card geographically addressed in I/O space. That is, each ‘slot’ or endpoint is decoded by the bridging logic into its own unique I/O map. Thus, the endpoints don’t ‘know’ about each other, at least at this level: they have non-overlapping I/O space maps from the host point of view.
Within that card I/O map lives the Configuration Space, which is standardized by the PCIe spec. It occupies between 256 and 4K bytes of space. The BAR registers are mapped into that space in known locations.
(Side note: sometimes you’ll hear Configuration Space called Configuration Information Space, or CIS. CIS was introduced with PCMCIA; PCI followed soon after with Configuration Space. They’re very similar in functionality and structure. ISA Plug ‘n’ Play is closely related to these, though it didn’t work nearly as well owing to the need to coexist with legacy non-PNP hardware.)
During enumeration the host scans all its possible geographical ‘slot’ I/O spaces to find cards. As it finds each one it dumps its Configuration information, parses it, and makes this information available for the kernel. This is done by the BIOS or its equivalent.
As startup continues, the kernel uses the BIOS-provided data to install drivers and set up each card’s registers for operation, mapping their BARs and their extents into memory space segments that don’t overlap, and setting up other slot resources like interrupts and power management.
The kernel uses the same BAR information to create entries in the TLB to map virtual addresses into physical addresses for the endpoints (more below.)
The takeaway is, cards don’t access each other’s Configuration Space, this dealt with by the BIOS and kernel. They can access each other’s memory space if the platform supports it (more on this below.)
The root complex is what you think it is: a specific kind of bridge. In the past it was a separate chip, as part of the ‘south bridge’. Nowadays it’s inbuilt into the CPU and closely linked to its on-chip fabric.
Where is the endpoint Configuration Space stored then? It lives in the endpoint’s PCIe block, and is usually initialized from a nonvolatile memory at power on. The loaded-up endpoint map that it presents upstream follows specific rules defined by the PCIe spec for the I/O register map, including the BARs. The host determines this map as it parses Configuration Space before it begins setting things up.
Finally, to use a card’s memory resource, you make a system call to tell the kernel you need it mapped, which tells the driver to create the TLB entry to map the physical BAR-defined space into virtual space. An example of how to do this can be found at https://github.com/billfarrow/pcimem.
If your card uses DMA (usually the case in PC platforms), anything it communicates with also needs to be mapped. Host memory for example needs to have DMA-reachable segments mapped in the root complex for the cards to use.
More about PCIe BARs here: https://stackoverflow.com/questions/30190050/what-is-the-base-address-register-bar-in-pcie
PCIe also has the ability to do peer-to-peer communication. This requires additional hardware in either the root complex or a switch. It’s possible (and common) to build large multiprocessor systems this way, even regarding PCIe as a kind of IP backbone.