pci: preserve upper address bits of small 64-bit BARs (#885) For a nonzero 64-bit BAR smaller than 4 GiB, shifting its size by 32 produces zero. The config write path uses that as the upper BAR's effective size, clearing programmed upper address bits and making size probes report the wrong size. Return one from get_bar_size_for_mask for those upper BAR registers so the write mask keeps every upper address bit writable. Preserve zero for unconfigured BARs. Add a protocol regression for writing a high address, probing the BAR size, and restoring the address. Verified the new BAR protocol test, existing 32-bit and 4 GiB or larger 64-bit BAR tests, a separate manual zero-sized 64-bit BAR probe, and the Meson unit suite. Signed-off-by: Tony Gutierrez <anthony.gutierrez@amd.com> Reviewed-by: Thanos Makatos <thanos.makatos@nutanix.com>
diff --git a/lib/pci.c b/lib/pci.c index 0217b0d..78044e6 100644 --- a/lib/pci.c +++ b/lib/pci.c
@@ -45,19 +45,25 @@ /* - * Return "effective" size of the bar. - * For even-indexed BARs (0, 2, 4), return the size of the bar and flag. - * For odd-indexed BARs (1, 3, 5), if lower bar is configured as 64-bit, - * return the upper 32 bits of the 64b bar size and 0. - * Otherwise, return the size of the bar and flag. + * Return the BAR size used to calculate the config write address mask. + * For the upper half of a 64-bit BAR, return the upper 32 bits of the lower + * BAR's size, or 1 for a nonzero size below 4 GiB so that all upper address + * bits remain writable. For other BARs, return the configured size. */ static inline uint64_t -get_bar_eff_size(vfu_ctx_t *vfu_ctx, uint16_t bar_index) +get_bar_size_for_mask(vfu_ctx_t *vfu_ctx, uint16_t bar_index) { assert(vfu_ctx != NULL); if (bar_index % 2 == 1) { if (vfu_ctx->reg_info[bar_index - 1].flags & VFU_REGION_FLAG_64_BITS) { - return vfu_ctx->reg_info[bar_index - 1].size >> 32; + uint64_t size = vfu_ctx->reg_info[bar_index - 1].size; + + /* A sub-4GiB 64-bit BAR still has a fully writable upper + * address register. Zero would mask every high address bit. */ + if (size > 0 && size < (1ULL << 32)) { + return 1; + } + return size >> 32; } } return vfu_ctx->reg_info[bar_index].size; @@ -101,7 +107,7 @@ cfg_addr = *(uint32_t *) buf; - effective_size = get_bar_eff_size(vfu_ctx, bar_index); + effective_size = get_bar_size_for_mask(vfu_ctx, bar_index); flag_mask = get_bar_flag_mask(vfu_ctx, bar_index); mask = 0;
diff --git a/test/py/test_device_get_region_info.py b/test/py/test_device_get_region_info.py index 05298b5..29322c8 100644 --- a/test/py/test_device_get_region_info.py +++ b/test/py/test_device_get_region_info.py
@@ -467,4 +467,32 @@ vfu_destroy_ctx(ctx) +def test_small_64bit_bar_size_and_upper_address(): + ctx = vfu_create_ctx(flags=LIBVFIO_USER_FLAG_ATTACH_NB) + assert ctx is not None + + assert setup_bar_region(ctx, 0, 0x1000, is_64b=True) == 0 + vfu_pci_init(ctx) + assert vfu_setup_region(ctx, index=VFU_PCI_DEV_CFG_REGION_IDX, + size=PCI_CFG_SPACE_EXP_SIZE, + flags=VFU_REGION_FLAG_RW) == 0 + assert vfu_realize_ctx(ctx) == 0 + + client = connect_client(ctx) + + # A small 64-bit BAR must accept a high address and report its full + # 64-bit sizing mask when probed through PCI config space. + upper = struct.pack("<I", 0x12345678) + write_region(ctx, client.sock, VFU_PCI_DEV_CFG_REGION_IDX, + offset=PCI_BASE_ADDRESS_1, count=4, data=upper) + assert read_region(ctx, client.sock, VFU_PCI_DEV_CFG_REGION_IDX, + offset=PCI_BASE_ADDRESS_1, count=4) == upper + assert detect_bar_sizes(ctx, client) == [0x1000, 0, 0, 0, 0, 0] + assert read_region(ctx, client.sock, VFU_PCI_DEV_CFG_REGION_IDX, + offset=PCI_BASE_ADDRESS_1, count=4) == upper + + client.disconnect(ctx) + vfu_destroy_ctx(ctx) + + # ex: set tabstop=4 shiftwidth=4 softtabstop=4 expandtab: #