pci: keep the allocation cursors 64-bit at the 4 GiB pool end

The cursors mem, mem_pf and io advanced as 32-bit values: after a
BAR allocation whose end is exactly the pool end 0x100000000 (now
reachable), *base = bar_value + length wrapped to 0.  Every later
allocation then passed the start and end checks and programmed its
BAR at address 0, over the legacy IO range and DRAM.

Widen the three cursors to 64-bit and advance with a 64-bit sum, so
an exhausted pool leaves the cursor at the end.  The address
parameters of pci_enum_next_aligned32 and pci_align_check_up widen
with them; pci_enum_next_aligned32 computes in uint64_t rather than
uintptr_t, which on 32-bit targets would truncate the exhausted
cursor back to 0 and defeat the addr > 0xffffffff rejection.  The
programmed BAR value stays 32-bit.

The 4 GiB pool unit test now also adds a second device with a
preset (previously programmed) BAR: exactly filling the pool must
leave that BAR untouched instead of re-allocating it from a wrapped
cursor.  The mock learns to seed a BAR preset from the bar info,
and the loop variable shadowing in test_pci_commit that it exposed
is fixed (the inner preset loop clobbered the outer node loop
counter, so only the first node was ever committed).

Verified: unit-pci 30/30, unit-pci-4gib green, full nxp_t1024
powerpc build green.
pull/874/head
Daniele Lacamera 2026-08-26 20:05:56 +02:00
parent ceae23e34f
commit 2fdc99f323
3 changed files with 63 additions and 33 deletions

View File

@ -89,12 +89,14 @@ typedef struct {
} pci_ctrlr_info_t; } pci_ctrlr_info_t;
struct pci_enum_info { struct pci_enum_info {
uint32_t mem; /* Allocation cursors and exclusive pool ends. All 64-bit: a pool
/* Exclusive pool ends. 64-bit: a pool may end exactly at 4 GiB * may end exactly at 4 GiB (0x100000000), which a 32-bit value
* (0x100000000), which a 32-bit field cannot represent. */ * cannot represent, and an exhausted cursor must stay at the pool
* end instead of wrapping to 0 and re-allocating over address 0. */
uint64_t mem;
uint64_t mem_limit; uint64_t mem_limit;
uint32_t io; uint64_t io;
uint32_t mem_pf; uint64_t mem_pf;
uint64_t mem_pf_limit; uint64_t mem_pf_limit;
uint8_t curr_bus_number; uint8_t curr_bus_number;
}; };

View File

@ -100,18 +100,18 @@
static int pci_enum_is_64bit(uint32_t value); static int pci_enum_is_64bit(uint32_t value);
static int pci_enum_is_mmio(uint32_t value); static int pci_enum_is_mmio(uint32_t value);
static inline uint32_t align_up(uint32_t address, uint32_t alignment) { static inline uint64_t align_up(uint64_t address, uint32_t alignment) {
return (address + alignment - 1) & ~(alignment - 1); return (address + alignment - 1) & ~(uint64_t)(alignment - 1);
} }
static inline uint32_t align_down(uint32_t address, uint32_t alignment) { static inline uint32_t align_down(uint32_t address, uint32_t alignment) {
return address & ~(alignment - 1); return address & ~(alignment - 1);
} }
static int pci_align_check_up(uint32_t address, uint32_t alignment, static int pci_align_check_up(uint64_t address, uint32_t alignment,
uint64_t limit, uint32_t *aligned) uint64_t limit, uint64_t *aligned)
{ {
uint32_t a; uint64_t a;
a = align_up(address, alignment); a = align_up(address, alignment);
if (a < address || a >= limit) if (a < address || a >= limit)
return -1; return -1;
@ -363,17 +363,20 @@ static int pci_enum_is_mmio(uint32_t value)
return (value & PCI_ENUM_MMIND_MASK) == 0; return (value & PCI_ENUM_MMIND_MASK) == 0;
} }
static int pci_enum_next_aligned32(uint32_t address, uint32_t *next, static int pci_enum_next_aligned32(uint64_t address, uint32_t *next,
uint32_t align, uint64_t limit) uint32_t align, uint64_t limit)
{ {
uintptr_t addr; uint64_t addr;
addr = (uintptr_t)address; /* 64-bit on purpose: an exhausted pool leaves the cursor at
* 0x100000000, which a 32-bit type (uintptr_t included on 32-bit
* targets) would truncate back to 0. */
addr = address;
align = align-1; align = align-1;
addr = (addr + align) & (~align); addr = (addr + align) & (~(uint64_t)align);
if (addr > 0xffffffff) if (addr > 0xffffffff)
return -1; return -1;
if (addr < (uintptr_t)address) if (addr < address)
return -1; return -1;
if (addr >= limit) if (addr >= limit)
return -1; return -1;
@ -421,7 +424,7 @@ static int pci_program_bar(uint8_t bus, uint8_t dev, uint8_t fun,
uint32_t length, align; uint32_t length, align;
uint8_t bar_off; uint8_t bar_off;
int is_prefetch; int is_prefetch;
uint32_t *base; uint64_t *base;
uint64_t limit; uint64_t limit;
uint32_t reg; uint32_t reg;
int is_mmio; int is_mmio;
@ -524,7 +527,7 @@ static int pci_program_bar(uint8_t bus, uint8_t dev, uint8_t fun,
pci_config_write32(bus, dev, fun, bar_off, bar_value); pci_config_write32(bus, dev, fun, bar_off, bar_value);
if (*is_64bit) if (*is_64bit)
pci_config_write32(bus, dev, fun, bar_off + 4, 0x0); pci_config_write32(bus, dev, fun, bar_off + 4, 0x0);
*base = bar_value + length; *base = (uint64_t)bar_value + length;
PCI_DEBUG_PRINTF("PCI enum: %s bus: %x:%x.%x bar: %d [%x,%x] (0x%x %s %s)\r\n", PCI_DEBUG_PRINTF("PCI enum: %s bus: %x:%x.%x bar: %d [%x,%x] (0x%x %s %s)\r\n",
(is_mmio ? "mm" : "io"), bus, dev, fun, bar_idx, bar_value, (is_mmio ? "mm" : "io"), bus, dev, fun, bar_idx, bar_value,
bar_value + length, length, (*is_64bit) ? "64bit" : "", bar_value + length, length, (*is_64bit) ? "64bit" : "",
@ -617,14 +620,14 @@ static inline void pci_dump_bridge(uint8_t bus, uint8_t dev, uint8_t fun)
static int pci_program_bridge(uint8_t bus, uint8_t dev, uint8_t fun, static int pci_program_bridge(uint8_t bus, uint8_t dev, uint8_t fun,
struct pci_enum_info *info) struct pci_enum_info *info)
{ {
uint32_t prefetch_start; uint64_t prefetch_start;
uint32_t mem_start; uint64_t mem_start;
uint32_t io_start; uint64_t io_start;
uint32_t orig_cmd; uint32_t orig_cmd;
uint8_t saved_bus; uint8_t saved_bus;
uint32_t saved_mem; uint64_t saved_mem;
uint32_t saved_pf; uint64_t saved_pf;
uint32_t saved_io; uint64_t saved_io;
int ret; int ret;
saved_bus = info->curr_bus_number; saved_bus = info->curr_bus_number;
@ -967,16 +970,17 @@ int pci_enum_do(void)
ret = pci_enum_bus(0, &enum_info); ret = pci_enum_bus(0, &enum_info);
PCI_DEBUG_PRINTF("PCI Memory Mapped I/O range [0x%x,0x%x] (0x%x)\r\n", PCI_DEBUG_PRINTF("PCI Memory Mapped I/O range [0x%x,0x%x] (0x%x)\r\n",
(uint32_t)PCI_MMIO32_BASE, enum_info.mem, (uint32_t)PCI_MMIO32_BASE, (uint32_t)enum_info.mem,
enum_info.mem - PCI_MMIO32_BASE); (uint32_t)(enum_info.mem - PCI_MMIO32_BASE));
PCI_DEBUG_PRINTF("PCI Memory Mapped I/O range (prefetch) [0x%x,0x%x] (0x%x)\r\n", PCI_DEBUG_PRINTF("PCI Memory Mapped I/O range (prefetch) [0x%x,0x%x] (0x%x)\r\n",
(uint32_t)PCI_MMIO32_PREFETCH_BASE, enum_info.mem_pf, (uint32_t)PCI_MMIO32_PREFETCH_BASE,
enum_info.mem_pf - PCI_MMIO32_PREFETCH_BASE); (uint32_t)enum_info.mem_pf,
(uint32_t)(enum_info.mem_pf - PCI_MMIO32_PREFETCH_BASE));
PCI_DEBUG_PRINTF("PCI I/O range [0x%x,0x%x] (0x%x)\r\n", PCI_DEBUG_PRINTF("PCI I/O range [0x%x,0x%x] (0x%x)\r\n",
(uint32_t)PCI_IO32_BASE, enum_info.io, (uint32_t)PCI_IO32_BASE, (uint32_t)enum_info.io,
enum_info.io - PCI_IO32_BASE); (uint32_t)(enum_info.io - PCI_IO32_BASE));
return ret; return ret;
} }

View File

@ -59,6 +59,7 @@ struct test_pci_bar_info {
uint32_t upper_mask; /* 64-bit BARs: upper half probe mask (0 = use default 0xFFFFFFFF) */ uint32_t upper_mask; /* 64-bit BARs: upper half probe mask (0 = use default 0xFFFFFFFF) */
uint8_t has_raw_probe;/* 1=override probe readback with raw_probe (hostile/malformed BAR) */ uint8_t has_raw_probe;/* 1=override probe readback with raw_probe (hostile/malformed BAR) */
uint32_t raw_probe; /* raw value returned on probe when has_raw_probe is set */ uint32_t raw_probe; /* raw value returned on probe when has_raw_probe is set */
uint32_t preset; /* initial BAR register value (previously programmed) */
}; };
struct test_pci_node { struct test_pci_node {
@ -146,9 +147,19 @@ static void test_pci_dev_set_bar(struct test_pci_topology *t, int node_idx,
b->is_prefetch = (type & TEST_PCI_BAR_PF) != 0; b->is_prefetch = (type & TEST_PCI_BAR_PF) != 0;
} }
static void test_pci_dev_set_bar_preset(struct test_pci_topology *t,
int node_idx, int bar_idx,
uint32_t value)
{
ck_assert(node_idx >= 0 && node_idx < t->count);
ck_assert(bar_idx >= 0 && bar_idx < TEST_PCI_MAX_BARS);
t->nodes[node_idx].bars[bar_idx].preset = value;
}
static void test_pci_commit(struct test_pci_topology *t) static void test_pci_commit(struct test_pci_topology *t)
{ {
int i; int i;
int j;
for (i = 0; i < t->count; i++) { for (i = 0; i < t->count; i++) {
struct test_pci_node *n = &t->nodes[i]; struct test_pci_node *n = &t->nodes[i];
if (!n->in_use) if (!n->in_use)
@ -163,6 +174,8 @@ static void test_pci_commit(struct test_pci_topology *t)
n->cfg[PCI_CLASS_CODE_BYTE_OFFSET] = 0x06; n->cfg[PCI_CLASS_CODE_BYTE_OFFSET] = 0x06;
n->cfg[PCI_SUBCLASS_BYTE_OFFSET] = 0x04; n->cfg[PCI_SUBCLASS_BYTE_OFFSET] = 0x04;
} }
for (j = 0; j < TEST_PCI_MAX_BARS; j++)
memcpy(&n->cfg[PCI_BAR0_OFFSET + j * 4], &n->bars[j].preset, 4);
} }
current_topology = t; current_topology = t;
} }
@ -1740,22 +1753,33 @@ END_TEST
START_TEST (test_pool_end_4gib) START_TEST (test_pool_end_4gib)
{ {
struct test_pci_topology t; struct test_pci_topology t;
int dev_node; int dev_node, dev_next;
uint32_t bar_val; uint32_t bar_val;
int ret; int ret;
test_pci_init(&t); test_pci_init(&t);
dev_node = test_pci_add_dev(&t, 0, 0, 0x1234, 0x5678, TEST_PCI_ROOT_BUS); dev_node = test_pci_add_dev(&t, 0, 0, 0x1234, 0x5678, TEST_PCI_ROOT_BUS);
test_pci_dev_set_bar(&t, dev_node, 0, 0x00100000, TEST_PCI_BAR_MMIO); dev_next = test_pci_add_dev(&t, 1, 0, 0x9ABC, 0xDEF0, TEST_PCI_ROOT_BUS);
/* 1 GB BAR: exactly fills the [0xC0000000, 0x100000000) pool */
test_pci_dev_set_bar(&t, dev_node, 0, 0x40000000, TEST_PCI_BAR_MMIO);
/* The next device's BAR was programmed by a previous boot */
test_pci_dev_set_bar(&t, dev_next, 0, 0x00100000, TEST_PCI_BAR_MMIO);
test_pci_dev_set_bar_preset(&t, dev_next, 0, 0x40000000);
test_pci_commit(&t); test_pci_commit(&t);
ret = pci_enum_do(); ret = pci_enum_do();
ck_assert_int_eq(ret, 0); ck_assert_int_eq(ret, 0);
/* The BAR is allocated at the pool base */ /* The 1 GB BAR is allocated at the pool base */
bar_val = pci_config_read32(0, 0, 0, PCI_BAR0_OFFSET); bar_val = pci_config_read32(0, 0, 0, PCI_BAR0_OFFSET);
ck_assert_uint_eq(bar_val, 0xC0000000); ck_assert_uint_eq(bar_val, 0xC0000000);
/* The pool is now exhausted exactly at 4 GiB. The allocation
* cursor must stay at the pool end, not wrap to 0 and program
* the next BAR over address 0: the second BAR is left untouched. */
bar_val = pci_config_read32(0, 1, 0, PCI_BAR0_OFFSET);
ck_assert_uint_eq(bar_val, 0x40000000);
test_pci_cleanup(&t); test_pci_cleanup(&t);
} }
END_TEST END_TEST
@ -1922,7 +1946,7 @@ END_TEST
/* test_pci_align_check_up_overflow: edge cases for pci_align_check_up */ /* test_pci_align_check_up_overflow: edge cases for pci_align_check_up */
START_TEST(test_pci_align_check_up_overflow) START_TEST(test_pci_align_check_up_overflow)
{ {
uint32_t aligned; uint64_t aligned;
int ret; int ret;
/* Normal case: already aligned */ /* Normal case: already aligned */