diff --git a/include/pci.h b/include/pci.h index e4819871..9c012888 100644 --- a/include/pci.h +++ b/include/pci.h @@ -89,12 +89,14 @@ typedef struct { } pci_ctrlr_info_t; struct pci_enum_info { - uint32_t mem; - /* Exclusive pool ends. 64-bit: a pool may end exactly at 4 GiB - * (0x100000000), which a 32-bit field cannot represent. */ + /* Allocation cursors and exclusive pool ends. All 64-bit: a pool + * may end exactly at 4 GiB (0x100000000), which a 32-bit value + * 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; - uint32_t io; - uint32_t mem_pf; + uint64_t io; + uint64_t mem_pf; uint64_t mem_pf_limit; uint8_t curr_bus_number; }; diff --git a/src/pci.c b/src/pci.c index b2376f38..d87b6b96 100644 --- a/src/pci.c +++ b/src/pci.c @@ -100,18 +100,18 @@ static int pci_enum_is_64bit(uint32_t value); static int pci_enum_is_mmio(uint32_t value); -static inline uint32_t align_up(uint32_t address, uint32_t alignment) { - return (address + alignment - 1) & ~(alignment - 1); +static inline uint64_t align_up(uint64_t address, uint32_t alignment) { + return (address + alignment - 1) & ~(uint64_t)(alignment - 1); } static inline uint32_t align_down(uint32_t address, uint32_t alignment) { return address & ~(alignment - 1); } -static int pci_align_check_up(uint32_t address, uint32_t alignment, - uint64_t limit, uint32_t *aligned) +static int pci_align_check_up(uint64_t address, uint32_t alignment, + uint64_t limit, uint64_t *aligned) { - uint32_t a; + uint64_t a; a = align_up(address, alignment); if (a < address || a >= limit) return -1; @@ -363,17 +363,20 @@ static int pci_enum_is_mmio(uint32_t value) 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) { - 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; - addr = (addr + align) & (~align); + addr = (addr + align) & (~(uint64_t)align); if (addr > 0xffffffff) return -1; - if (addr < (uintptr_t)address) + if (addr < address) return -1; if (addr >= limit) return -1; @@ -421,7 +424,7 @@ static int pci_program_bar(uint8_t bus, uint8_t dev, uint8_t fun, uint32_t length, align; uint8_t bar_off; int is_prefetch; - uint32_t *base; + uint64_t *base; uint64_t limit; uint32_t reg; 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); if (*is_64bit) 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", (is_mmio ? "mm" : "io"), bus, dev, fun, bar_idx, bar_value, 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, struct pci_enum_info *info) { - uint32_t prefetch_start; - uint32_t mem_start; - uint32_t io_start; + uint64_t prefetch_start; + uint64_t mem_start; + uint64_t io_start; uint32_t orig_cmd; uint8_t saved_bus; - uint32_t saved_mem; - uint32_t saved_pf; - uint32_t saved_io; + uint64_t saved_mem; + uint64_t saved_pf; + uint64_t saved_io; int ret; saved_bus = info->curr_bus_number; @@ -967,16 +970,17 @@ int pci_enum_do(void) ret = pci_enum_bus(0, &enum_info); 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, - enum_info.mem - PCI_MMIO32_BASE); + (uint32_t)PCI_MMIO32_BASE, (uint32_t)enum_info.mem, + (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", - (uint32_t)PCI_MMIO32_PREFETCH_BASE, enum_info.mem_pf, - enum_info.mem_pf - PCI_MMIO32_PREFETCH_BASE); + (uint32_t)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", - (uint32_t)PCI_IO32_BASE, enum_info.io, - enum_info.io - PCI_IO32_BASE); + (uint32_t)PCI_IO32_BASE, (uint32_t)enum_info.io, + (uint32_t)(enum_info.io - PCI_IO32_BASE)); return ret; } diff --git a/tools/unit-tests/unit-pci.c b/tools/unit-tests/unit-pci.c index 6dd2527e..67d5dd39 100644 --- a/tools/unit-tests/unit-pci.c +++ b/tools/unit-tests/unit-pci.c @@ -59,6 +59,7 @@ struct test_pci_bar_info { 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) */ 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 { @@ -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; } +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) { int i; + int j; for (i = 0; i < t->count; i++) { struct test_pci_node *n = &t->nodes[i]; 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_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; } @@ -1740,22 +1753,33 @@ END_TEST START_TEST (test_pool_end_4gib) { struct test_pci_topology t; - int dev_node; + int dev_node, dev_next; uint32_t bar_val; int ret; test_pci_init(&t); 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); ret = pci_enum_do(); 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); 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); } END_TEST @@ -1922,7 +1946,7 @@ END_TEST /* test_pci_align_check_up_overflow: edge cases for pci_align_check_up */ START_TEST(test_pci_align_check_up_overflow) { - uint32_t aligned; + uint64_t aligned; int ret; /* Normal case: already aligned */