mirror of https://github.com/wolfSSL/wolfBoot.git
1850 lines
61 KiB
C
1850 lines
61 KiB
C
/* unit-pci.c
|
|
*
|
|
* Unit test for pci functions
|
|
*
|
|
*
|
|
* Copyright (C) 2026 wolfSSL Inc.
|
|
*
|
|
* This file is part of wolfBoot.
|
|
*
|
|
* wolfBoot is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* wolfBoot is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
|
|
*/
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <check.h>
|
|
|
|
#define MOCKED_BASE (2*1024*1024*1024ULL)
|
|
#define PCI_USE_ECAM
|
|
#define PCI_ECAM_BASE MOCKED_BASE
|
|
|
|
#include <pci.h>
|
|
#include <pci.c>
|
|
|
|
/*
|
|
* Mock PCI topology infrastructure
|
|
*/
|
|
|
|
#define TEST_PCI_MAX_NODES 10
|
|
#define TEST_PCI_MAX_BARS 6
|
|
#define TEST_PCI_ROOT_BUS (-1)
|
|
#define TEST_PCI_CFG_SIZE 256
|
|
|
|
#define TEST_PCI_BAR_MMIO 0x00
|
|
#define TEST_PCI_BAR_IO 0x01
|
|
#define TEST_PCI_BAR_64BIT 0x02
|
|
#define TEST_PCI_BAR_PF 0x04
|
|
|
|
#define PCI_CLASS_CODE_BYTE_OFFSET 0x0B
|
|
#define PCI_SUBCLASS_BYTE_OFFSET 0x0A
|
|
|
|
struct test_pci_bar_info {
|
|
uint32_t size; /* power-of-2 bytes, 0 = not implemented */
|
|
uint8_t is_io; /* 1=IO, 0=MMIO */
|
|
uint8_t is_64bit; /* 1=64-bit MMIO (consumes next BAR slot too) */
|
|
uint8_t is_prefetch; /* 1=prefetchable */
|
|
uint8_t io_hi16_zero;/* 1=IO BAR only decodes 16 bits (upper 16 of mask are 0) */
|
|
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 */
|
|
};
|
|
|
|
struct test_pci_node {
|
|
uint8_t in_use;
|
|
uint8_t dev; /* device slot on parent bus */
|
|
uint8_t func; /* function number */
|
|
uint8_t is_bridge;
|
|
int parent_bus; /* TEST_PCI_ROOT_BUS for root, else index of parent bridge */
|
|
uint16_t vendor_id;
|
|
uint16_t device_id;
|
|
struct test_pci_bar_info bars[TEST_PCI_MAX_BARS];
|
|
uint8_t bar_probed[TEST_PCI_MAX_BARS]; /* tracks which BARs had 0xFFFFFFFF written */
|
|
uint8_t cfg[TEST_PCI_CFG_SIZE]; /* PCI config register backing store */
|
|
};
|
|
|
|
struct test_pci_topology {
|
|
struct test_pci_node nodes[TEST_PCI_MAX_NODES];
|
|
int count;
|
|
};
|
|
|
|
static struct test_pci_topology *current_topology = NULL;
|
|
|
|
static void test_pci_init(struct test_pci_topology *t)
|
|
{
|
|
memset(t, 0, sizeof(*t));
|
|
}
|
|
|
|
static int test_pci_add_node(struct test_pci_topology *t,
|
|
uint8_t dev, uint8_t func,
|
|
uint16_t vendor_id, uint16_t device_id,
|
|
int parent_bus, uint8_t is_bridge)
|
|
{
|
|
int idx = t->count;
|
|
ck_assert_msg(idx < TEST_PCI_MAX_NODES,
|
|
"topology full (%d nodes)", TEST_PCI_MAX_NODES);
|
|
ck_assert_msg(parent_bus == TEST_PCI_ROOT_BUS ||
|
|
(parent_bus >= 0 && parent_bus < idx &&
|
|
t->nodes[parent_bus].is_bridge),
|
|
"invalid parent_bus %d", parent_bus);
|
|
|
|
struct test_pci_node *n = &t->nodes[idx];
|
|
n->in_use = 1;
|
|
n->dev = dev;
|
|
n->func = func;
|
|
n->vendor_id = vendor_id;
|
|
n->device_id = device_id;
|
|
n->is_bridge = is_bridge;
|
|
n->parent_bus = parent_bus;
|
|
t->count++;
|
|
return idx;
|
|
}
|
|
|
|
static int test_pci_add_dev(struct test_pci_topology *t,
|
|
uint8_t dev, uint8_t func,
|
|
uint16_t vendor_id, uint16_t device_id,
|
|
int parent_bus)
|
|
{
|
|
return test_pci_add_node(t, dev, func, vendor_id, device_id, parent_bus, 0);
|
|
}
|
|
|
|
static int test_pci_add_bridge(struct test_pci_topology *t,
|
|
uint8_t dev, uint8_t func,
|
|
uint16_t vendor_id, uint16_t device_id,
|
|
int parent_bus)
|
|
{
|
|
return test_pci_add_node(t, dev, func, vendor_id, device_id, parent_bus, 1);
|
|
}
|
|
|
|
static void test_pci_dev_set_bar(struct test_pci_topology *t, int node_idx,
|
|
int bar_idx, uint32_t size,
|
|
unsigned int type)
|
|
{
|
|
ck_assert(node_idx >= 0 && node_idx < t->count);
|
|
ck_assert(bar_idx >= 0 && bar_idx < TEST_PCI_MAX_BARS);
|
|
ck_assert_msg(size == 0 || (size & (size - 1)) == 0,
|
|
"BAR size must be power of 2");
|
|
ck_assert_msg(!(type & TEST_PCI_BAR_IO) ||
|
|
!(type & (TEST_PCI_BAR_64BIT | TEST_PCI_BAR_PF)),
|
|
"IO BAR cannot be combined with 64BIT or PF flags");
|
|
|
|
struct test_pci_bar_info *b = &t->nodes[node_idx].bars[bar_idx];
|
|
b->size = size;
|
|
b->is_io = (type & TEST_PCI_BAR_IO) != 0;
|
|
b->is_64bit = (type & TEST_PCI_BAR_64BIT) != 0;
|
|
b->is_prefetch = (type & TEST_PCI_BAR_PF) != 0;
|
|
}
|
|
|
|
static void test_pci_commit(struct test_pci_topology *t)
|
|
{
|
|
int i;
|
|
for (i = 0; i < t->count; i++) {
|
|
struct test_pci_node *n = &t->nodes[i];
|
|
if (!n->in_use)
|
|
continue;
|
|
memset(n->cfg, 0, TEST_PCI_CFG_SIZE);
|
|
memset(n->bar_probed, 0, TEST_PCI_MAX_BARS);
|
|
memcpy(&n->cfg[PCI_VENDOR_ID_OFFSET], &n->vendor_id, 2);
|
|
memcpy(&n->cfg[PCI_DEVICE_ID_OFFSET], &n->device_id, 2);
|
|
n->cfg[PCI_HEADER_TYPE_OFFSET] = n->is_bridge ?
|
|
PCI_HEADER_TYPE_BRIDGE : PCI_HEADER_TYPE_DEVICE;
|
|
if (n->is_bridge) {
|
|
n->cfg[PCI_CLASS_CODE_BYTE_OFFSET] = 0x06;
|
|
n->cfg[PCI_SUBCLASS_BYTE_OFFSET] = 0x04;
|
|
}
|
|
}
|
|
current_topology = t;
|
|
}
|
|
|
|
static void test_pci_cleanup(struct test_pci_topology *t)
|
|
{
|
|
(void)t;
|
|
current_topology = NULL;
|
|
}
|
|
|
|
static uint8_t test_pci_node_bus(struct test_pci_topology *t, int node_idx)
|
|
{
|
|
struct test_pci_node *n = &t->nodes[node_idx];
|
|
if (n->parent_bus == TEST_PCI_ROOT_BUS)
|
|
return 0;
|
|
/* Bus number is the parent bridge's secondary bus */
|
|
return t->nodes[n->parent_bus].cfg[PCI_SECONDARY_BUS];
|
|
}
|
|
|
|
static int test_pci_is_bus_reachable(struct test_pci_topology *t,
|
|
uint8_t target_bus)
|
|
{
|
|
uint8_t current_bus = 0;
|
|
int i;
|
|
|
|
if (target_bus == 0)
|
|
return 1;
|
|
|
|
while (current_bus != target_bus) {
|
|
int advance = 0;
|
|
for (i = 0; i < t->count; i++) {
|
|
struct test_pci_node *n = &t->nodes[i];
|
|
uint8_t sec, sub;
|
|
|
|
if (!n->in_use || !n->is_bridge)
|
|
continue;
|
|
if (test_pci_node_bus(t, i) != current_bus)
|
|
continue;
|
|
|
|
sec = n->cfg[PCI_SECONDARY_BUS];
|
|
sub = n->cfg[PCI_SUB_SEC_BUS];
|
|
|
|
if (sec == 0)
|
|
continue; /* bridge not yet configured */
|
|
if (sec <= target_bus && target_bus <= sub) {
|
|
ck_assert_msg(sec > current_bus,
|
|
"bridge sec bus must be > current bus");
|
|
current_bus = sec;
|
|
advance = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (!advance)
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static struct test_pci_node *test_pci_find_node(struct test_pci_topology *t,
|
|
uint8_t bus, uint8_t dev,
|
|
uint8_t func)
|
|
{
|
|
int i;
|
|
|
|
if (!test_pci_is_bus_reachable(t, bus))
|
|
return NULL;
|
|
|
|
for (i = 0; i < t->count; i++) {
|
|
struct test_pci_node *n = &t->nodes[i];
|
|
if (!n->in_use)
|
|
continue;
|
|
if (n->dev != dev || n->func != func)
|
|
continue;
|
|
if (bus == 0 && n->parent_bus == TEST_PCI_ROOT_BUS)
|
|
return n;
|
|
if (bus > 0 && n->parent_bus >= 0 &&
|
|
test_pci_node_bus(t, i) == bus)
|
|
return n;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static void ecam_decode(uintptr_t addr, uint8_t *bus, uint8_t *dev,
|
|
uint8_t *func, uint16_t *off)
|
|
{
|
|
uintptr_t rel = addr - PCI_ECAM_BASE;
|
|
*bus = (rel >> 20) & 0xFF;
|
|
*dev = (rel >> 15) & 0x1F;
|
|
*func = (rel >> 12) & 0x7;
|
|
*off = rel & 0xFFF;
|
|
}
|
|
|
|
static uint32_t test_pci_bar_probe_mask(struct test_pci_node *n, int bar_idx)
|
|
{
|
|
int max_bars = n->is_bridge ? 2 : 6;
|
|
struct test_pci_bar_info *b;
|
|
|
|
if (bar_idx < 0 || bar_idx >= max_bars)
|
|
return 0;
|
|
|
|
b = &n->bars[bar_idx];
|
|
if (b->has_raw_probe)
|
|
return b->raw_probe;
|
|
if (b->size > 0) {
|
|
uint32_t mask;
|
|
if (b->is_io) {
|
|
mask = (~(b->size - 1)) & 0xFFFFFFFC;
|
|
if (b->io_hi16_zero)
|
|
mask &= 0x0000FFFF;
|
|
mask |= 0x1;
|
|
} else {
|
|
mask = (~(b->size - 1)) & 0xFFFFFFF0;
|
|
if (b->is_64bit)
|
|
mask |= 0x4;
|
|
if (b->is_prefetch)
|
|
mask |= 0x8;
|
|
}
|
|
return mask;
|
|
}
|
|
|
|
/* Check if this is the upper half of a 64-bit BAR */
|
|
if (bar_idx > 0 &&
|
|
n->bars[bar_idx - 1].is_64bit &&
|
|
n->bars[bar_idx - 1].size > 0) {
|
|
uint32_t um = n->bars[bar_idx - 1].upper_mask;
|
|
return um ? um : 0xFFFFFFFF;
|
|
}
|
|
|
|
return 0; /* BAR not implemented */
|
|
}
|
|
|
|
/*
|
|
* Mock functions
|
|
*/
|
|
void mmio_write32(uintptr_t address, uint32_t value)
|
|
{
|
|
uint8_t bus, dev, func;
|
|
uint16_t off;
|
|
struct test_pci_node *n;
|
|
int max_bars;
|
|
int bar_idx;
|
|
|
|
ck_assert_ptr_nonnull(current_topology);
|
|
|
|
ecam_decode(address, &bus, &dev, &func, &off);
|
|
n = test_pci_find_node(current_topology, bus, dev, func);
|
|
if (n == NULL)
|
|
return; /* write to void */
|
|
if (off + 4 > TEST_PCI_CFG_SIZE)
|
|
return;
|
|
|
|
max_bars = n->is_bridge ? 2 : 6;
|
|
|
|
/* BAR probing: writing 0xFFFFFFFF to a BAR offset */
|
|
if (value == 0xFFFFFFFF) {
|
|
if (off >= PCI_BAR0_OFFSET &&
|
|
off < (uint16_t)(PCI_BAR0_OFFSET + max_bars * 4)) {
|
|
uint32_t mask;
|
|
bar_idx = (off - PCI_BAR0_OFFSET) / 4;
|
|
n->bar_probed[bar_idx] = 1;
|
|
|
|
/* Lower half of a 64-bit BAR pair: only return the full size
|
|
* mask when the upper half has also been written with
|
|
* 0xFFFFFFFF. Real hardware requires both registers to be in
|
|
* probe mode before either reports a valid size. When only the
|
|
* lower half is probed, return just the type bits so the caller
|
|
* can still detect MMIO/64-bit/prefetch, but the size portion
|
|
* is zero — exposing code that reads the mask too early. */
|
|
if (n->bars[bar_idx].is_64bit && n->bars[bar_idx].size > 0) {
|
|
if (!n->bar_probed[bar_idx + 1]) {
|
|
uint32_t type_bits = 0;
|
|
if (n->bars[bar_idx].is_64bit)
|
|
type_bits |= 0x4;
|
|
if (n->bars[bar_idx].is_prefetch)
|
|
type_bits |= 0x8;
|
|
memcpy(&n->cfg[off], &type_bits, 4);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Upper half of a 64-bit BAR pair: write its own mask and, if
|
|
* the lower half was already probed, retroactively fix the
|
|
* lower half's cfg entry with the correct full mask. */
|
|
if (bar_idx > 0 &&
|
|
n->bars[bar_idx - 1].is_64bit &&
|
|
n->bars[bar_idx - 1].size > 0) {
|
|
mask = test_pci_bar_probe_mask(n, bar_idx);
|
|
memcpy(&n->cfg[off], &mask, 4);
|
|
if (n->bar_probed[bar_idx - 1]) {
|
|
uint32_t low_mask = test_pci_bar_probe_mask(n, bar_idx - 1);
|
|
uint16_t low_off = PCI_BAR0_OFFSET + (bar_idx - 1) * 4;
|
|
memcpy(&n->cfg[low_off], &low_mask, 4);
|
|
}
|
|
return;
|
|
}
|
|
|
|
mask = test_pci_bar_probe_mask(n, bar_idx);
|
|
memcpy(&n->cfg[off], &mask, 4);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Normal write: clear probe state for BAR offsets */
|
|
if (off >= PCI_BAR0_OFFSET &&
|
|
off < (uint16_t)(PCI_BAR0_OFFSET + max_bars * 4)) {
|
|
bar_idx = (off - PCI_BAR0_OFFSET) / 4;
|
|
n->bar_probed[bar_idx] = 0;
|
|
}
|
|
|
|
memcpy(&n->cfg[off], &value, 4);
|
|
}
|
|
|
|
uint32_t mmio_read32(uintptr_t address)
|
|
{
|
|
uint8_t bus, dev, func;
|
|
uint16_t off;
|
|
struct test_pci_node *n;
|
|
uint32_t val;
|
|
|
|
ck_assert_ptr_nonnull(current_topology);
|
|
|
|
ecam_decode(address, &bus, &dev, &func, &off);
|
|
n = test_pci_find_node(current_topology, bus, dev, func);
|
|
if (n == NULL)
|
|
return 0xFFFFFFFF;
|
|
if (off + 4 > TEST_PCI_CFG_SIZE)
|
|
return 0xFFFFFFFF;
|
|
|
|
memcpy(&val, &n->cfg[off], 4);
|
|
return val;
|
|
}
|
|
|
|
void panic(void)
|
|
{
|
|
ck_abort_msg("panic!");
|
|
}
|
|
|
|
/*
|
|
* Test cases
|
|
*/
|
|
|
|
/* Migrated: test_pci_config_write (uses topology instead of mmap) */
|
|
|
|
/* test_topology_build: verify topology construction */
|
|
START_TEST(test_topology_build)
|
|
{
|
|
struct test_pci_topology t;
|
|
int d0, d1, br, d2;
|
|
|
|
test_pci_init(&t);
|
|
d0 = test_pci_add_dev(&t, 0, 0, 0x1111, 0x2222, TEST_PCI_ROOT_BUS);
|
|
d1 = test_pci_add_dev(&t, 1, 0, 0x3333, 0x4444, TEST_PCI_ROOT_BUS);
|
|
br = test_pci_add_bridge(&t, 2, 0, 0x5555, 0x6666, TEST_PCI_ROOT_BUS);
|
|
d2 = test_pci_add_dev(&t, 0, 0, 0x7777, 0x8888, br);
|
|
|
|
ck_assert_int_eq(t.count, 4);
|
|
|
|
/* d0 */
|
|
ck_assert_uint_eq(t.nodes[d0].in_use, 1);
|
|
ck_assert_uint_eq(t.nodes[d0].dev, 0);
|
|
ck_assert_uint_eq(t.nodes[d0].func, 0);
|
|
ck_assert_uint_eq(t.nodes[d0].is_bridge, 0);
|
|
ck_assert_int_eq(t.nodes[d0].parent_bus, TEST_PCI_ROOT_BUS);
|
|
ck_assert_uint_eq(t.nodes[d0].vendor_id, 0x1111);
|
|
ck_assert_uint_eq(t.nodes[d0].device_id, 0x2222);
|
|
|
|
/* d1 */
|
|
ck_assert_uint_eq(t.nodes[d1].dev, 1);
|
|
ck_assert_int_eq(t.nodes[d1].parent_bus, TEST_PCI_ROOT_BUS);
|
|
|
|
/* bridge */
|
|
ck_assert_uint_eq(t.nodes[br].is_bridge, 1);
|
|
ck_assert_uint_eq(t.nodes[br].dev, 2);
|
|
|
|
/* device behind bridge */
|
|
ck_assert_uint_eq(t.nodes[d2].dev, 0);
|
|
ck_assert_int_eq(t.nodes[d2].parent_bus, br);
|
|
ck_assert_uint_eq(t.nodes[d2].is_bridge, 0);
|
|
|
|
/* node_bus before commit: cfg is all zeros */
|
|
test_pci_commit(&t);
|
|
ck_assert_uint_eq(test_pci_node_bus(&t, d0), 0);
|
|
ck_assert_uint_eq(test_pci_node_bus(&t, d1), 0);
|
|
ck_assert_uint_eq(test_pci_node_bus(&t, br), 0);
|
|
/* d2's bus = bridge's secondary_bus = 0 (not yet configured) */
|
|
ck_assert_uint_eq(test_pci_node_bus(&t, d2), 0);
|
|
|
|
test_pci_cleanup(&t);
|
|
}
|
|
END_TEST
|
|
|
|
/* test_topology_commit: verify cfg[] initialization */
|
|
START_TEST(test_topology_commit)
|
|
{
|
|
struct test_pci_topology t;
|
|
int ep, br;
|
|
uint16_t vid, did;
|
|
|
|
test_pci_init(&t);
|
|
ep = test_pci_add_dev(&t, 3, 0, 0xAAAA, 0xBBBB, TEST_PCI_ROOT_BUS);
|
|
br = test_pci_add_bridge(&t, 5, 0, 0xCCCC, 0xDDDD, TEST_PCI_ROOT_BUS);
|
|
test_pci_commit(&t);
|
|
|
|
/* Endpoint: vendor/device */
|
|
vid = pci_config_read16(0, 3, 0, PCI_VENDOR_ID_OFFSET);
|
|
did = pci_config_read16(0, 3, 0, PCI_DEVICE_ID_OFFSET);
|
|
ck_assert_uint_eq(vid, 0xAAAA);
|
|
ck_assert_uint_eq(did, 0xBBBB);
|
|
/* Endpoint: header_type = 0x00 */
|
|
ck_assert_uint_eq(pci_config_read8(0, 3, 0, 0x0E), 0x00);
|
|
|
|
/* Bridge: vendor/device */
|
|
vid = pci_config_read16(0, 5, 0, PCI_VENDOR_ID_OFFSET);
|
|
did = pci_config_read16(0, 5, 0, PCI_DEVICE_ID_OFFSET);
|
|
ck_assert_uint_eq(vid, 0xCCCC);
|
|
ck_assert_uint_eq(did, 0xDDDD);
|
|
/* Bridge: header_type = 0x01 */
|
|
ck_assert_uint_eq(pci_config_read8(0, 5, 0, 0x0E), 0x01);
|
|
/* Bridge: class=0x06, subclass=0x04 */
|
|
ck_assert_uint_eq(pci_config_read8(0, 5, 0, 0x0B), 0x06);
|
|
ck_assert_uint_eq(pci_config_read8(0, 5, 0, 0x0A), 0x04);
|
|
|
|
test_pci_cleanup(&t);
|
|
}
|
|
END_TEST
|
|
|
|
/* test_find_node_root_bus: find devices on bus 0 */
|
|
START_TEST(test_find_node_root_bus)
|
|
{
|
|
struct test_pci_topology t;
|
|
struct test_pci_node *found;
|
|
int d0, d2, d5;
|
|
|
|
test_pci_init(&t);
|
|
d0 = test_pci_add_dev(&t, 0, 0, 0x1000, 0x0001, TEST_PCI_ROOT_BUS);
|
|
d2 = test_pci_add_dev(&t, 2, 0, 0x1000, 0x0002, TEST_PCI_ROOT_BUS);
|
|
d5 = test_pci_add_dev(&t, 5, 0, 0x1000, 0x0005, TEST_PCI_ROOT_BUS);
|
|
test_pci_commit(&t);
|
|
|
|
/* Find existing devices */
|
|
found = test_pci_find_node(&t, 0, 0, 0);
|
|
ck_assert_ptr_nonnull(found);
|
|
ck_assert_ptr_eq(found, &t.nodes[d0]);
|
|
|
|
found = test_pci_find_node(&t, 0, 2, 0);
|
|
ck_assert_ptr_nonnull(found);
|
|
ck_assert_ptr_eq(found, &t.nodes[d2]);
|
|
|
|
found = test_pci_find_node(&t, 0, 5, 0);
|
|
ck_assert_ptr_nonnull(found);
|
|
ck_assert_ptr_eq(found, &t.nodes[d5]);
|
|
|
|
/* Non-existent devices */
|
|
ck_assert_ptr_null(test_pci_find_node(&t, 0, 1, 0));
|
|
ck_assert_ptr_null(test_pci_find_node(&t, 0, 3, 0));
|
|
ck_assert_ptr_null(test_pci_find_node(&t, 0, 31, 0));
|
|
/* Wrong function number */
|
|
ck_assert_ptr_null(test_pci_find_node(&t, 0, 0, 1));
|
|
|
|
test_pci_cleanup(&t);
|
|
}
|
|
END_TEST
|
|
|
|
/* test_find_node_behind_bridge: bus routing validation */
|
|
START_TEST(test_find_node_behind_bridge)
|
|
{
|
|
struct test_pci_topology t;
|
|
struct test_pci_node *found;
|
|
int br, d_behind;
|
|
|
|
test_pci_init(&t);
|
|
br = test_pci_add_bridge(&t, 1, 0, 0xAAAA, 0xBBBB, TEST_PCI_ROOT_BUS);
|
|
d_behind = test_pci_add_dev(&t, 0, 0, 0xCCCC, 0xDDDD, br);
|
|
test_pci_commit(&t);
|
|
|
|
/* Before configuring bridge: bus 1 not routable */
|
|
found = test_pci_find_node(&t, 1, 0, 0);
|
|
ck_assert_ptr_null(found);
|
|
|
|
/* Configure bridge: primary=0, secondary=1, subordinate=1 */
|
|
t.nodes[br].cfg[PCI_PRIMARY_BUS] = 0;
|
|
t.nodes[br].cfg[PCI_SECONDARY_BUS] = 1;
|
|
t.nodes[br].cfg[PCI_SUB_SEC_BUS] = 1;
|
|
|
|
/* Now bus 1 is routable */
|
|
found = test_pci_find_node(&t, 1, 0, 0);
|
|
ck_assert_ptr_nonnull(found);
|
|
ck_assert_ptr_eq(found, &t.nodes[d_behind]);
|
|
|
|
/* Verify node_bus for the child */
|
|
ck_assert_uint_eq(test_pci_node_bus(&t, d_behind), 1);
|
|
|
|
/* Bus 2 is still unreachable (subordinate=1) */
|
|
ck_assert_ptr_null(test_pci_find_node(&t, 2, 0, 0));
|
|
|
|
test_pci_cleanup(&t);
|
|
}
|
|
END_TEST
|
|
|
|
/* test_find_node_nested_bridges: multi-level routing */
|
|
START_TEST(test_find_node_nested_bridges)
|
|
{
|
|
struct test_pci_topology t;
|
|
struct test_pci_node *found;
|
|
int brA, brB, dev_leaf;
|
|
|
|
test_pci_init(&t);
|
|
brA = test_pci_add_bridge(&t, 0, 0, 0x1111, 0x2222, TEST_PCI_ROOT_BUS);
|
|
brB = test_pci_add_bridge(&t, 0, 0, 0x3333, 0x4444, brA);
|
|
dev_leaf = test_pci_add_dev(&t, 0, 0, 0x5555, 0x6666, brB);
|
|
test_pci_commit(&t);
|
|
|
|
/* Configure bridge A: primary=0, secondary=1, subordinate=2 */
|
|
t.nodes[brA].cfg[PCI_PRIMARY_BUS] = 0;
|
|
t.nodes[brA].cfg[PCI_SECONDARY_BUS] = 1;
|
|
t.nodes[brA].cfg[PCI_SUB_SEC_BUS] = 2;
|
|
|
|
/* Configure bridge B: primary=1, secondary=2, subordinate=2 */
|
|
t.nodes[brB].cfg[PCI_PRIMARY_BUS] = 1;
|
|
t.nodes[brB].cfg[PCI_SECONDARY_BUS] = 2;
|
|
t.nodes[brB].cfg[PCI_SUB_SEC_BUS] = 2;
|
|
|
|
/* Device on bus 2 is reachable */
|
|
found = test_pci_find_node(&t, 2, 0, 0);
|
|
ck_assert_ptr_nonnull(found);
|
|
ck_assert_ptr_eq(found, &t.nodes[dev_leaf]);
|
|
|
|
/* Break routing: set bridge A subordinate to 1 (doesn't include bus 2) */
|
|
t.nodes[brA].cfg[PCI_SUB_SEC_BUS] = 1;
|
|
found = test_pci_find_node(&t, 2, 0, 0);
|
|
ck_assert_ptr_null(found);
|
|
|
|
/* Fix bridge A, break bridge B: set secondary to 0 (not configured) */
|
|
t.nodes[brA].cfg[PCI_SUB_SEC_BUS] = 2;
|
|
t.nodes[brB].cfg[PCI_SECONDARY_BUS] = 0;
|
|
found = test_pci_find_node(&t, 2, 0, 0);
|
|
ck_assert_ptr_null(found);
|
|
|
|
test_pci_cleanup(&t);
|
|
}
|
|
END_TEST
|
|
|
|
/* test_bar_probe_mask: BAR mask computation */
|
|
START_TEST(test_bar_probe_mask)
|
|
{
|
|
struct test_pci_topology t;
|
|
struct test_pci_node *n;
|
|
int dev_node;
|
|
uint32_t mask;
|
|
|
|
test_pci_init(&t);
|
|
dev_node = test_pci_add_dev(&t, 0, 0, 0x1234, 0x5678, TEST_PCI_ROOT_BUS);
|
|
n = &t.nodes[dev_node];
|
|
|
|
/* 32-bit MMIO 64KB */
|
|
memset(n->bars, 0, sizeof(n->bars));
|
|
n->bars[0].size = 0x10000;
|
|
n->bars[0].is_io = 0;
|
|
n->bars[0].is_64bit = 0;
|
|
n->bars[0].is_prefetch = 0;
|
|
mask = test_pci_bar_probe_mask(n, 0);
|
|
ck_assert_uint_eq(mask, 0xFFFF0000);
|
|
|
|
/* 32-bit MMIO 4KB */
|
|
n->bars[0].size = 0x1000;
|
|
mask = test_pci_bar_probe_mask(n, 0);
|
|
ck_assert_uint_eq(mask, 0xFFFFF000);
|
|
|
|
/* 64-bit prefetchable MMIO 1MB — lower half */
|
|
memset(n->bars, 0, sizeof(n->bars));
|
|
n->bars[0].size = 0x100000;
|
|
n->bars[0].is_io = 0;
|
|
n->bars[0].is_64bit = 1;
|
|
n->bars[0].is_prefetch = 1;
|
|
mask = test_pci_bar_probe_mask(n, 0);
|
|
ck_assert_uint_eq(mask, 0xFFF0000C); /* ~(1MB-1) & 0xFFFFFFF0 | 0x4 | 0x8 */
|
|
|
|
/* 64-bit BAR — upper half (bar index 1) */
|
|
mask = test_pci_bar_probe_mask(n, 1);
|
|
ck_assert_uint_eq(mask, 0xFFFFFFFF);
|
|
|
|
/* IO BAR 256 bytes */
|
|
memset(n->bars, 0, sizeof(n->bars));
|
|
n->bars[2].size = 256;
|
|
n->bars[2].is_io = 1;
|
|
n->bars[2].is_64bit = 0;
|
|
n->bars[2].is_prefetch = 0;
|
|
mask = test_pci_bar_probe_mask(n, 2);
|
|
ck_assert_uint_eq(mask, 0xFFFFFF01); /* ~(256-1) & 0xFFFFFFFC | 0x1 */
|
|
|
|
/* Unimplemented BAR */
|
|
mask = test_pci_bar_probe_mask(n, 3);
|
|
ck_assert_uint_eq(mask, 0x00000000);
|
|
|
|
/* Out of range BAR index */
|
|
mask = test_pci_bar_probe_mask(n, 6);
|
|
ck_assert_uint_eq(mask, 0x00000000);
|
|
mask = test_pci_bar_probe_mask(n, -1);
|
|
ck_assert_uint_eq(mask, 0x00000000);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_mmio_mock_bar_probe)
|
|
{
|
|
struct test_pci_topology t;
|
|
uint32_t val;
|
|
int dev_node;
|
|
|
|
test_pci_init(&t);
|
|
dev_node = test_pci_add_dev(&t, 3, 0, 0xAAAA, 0xBBBB, TEST_PCI_ROOT_BUS);
|
|
test_pci_dev_set_bar(&t, dev_node, 0, 0x10000, TEST_PCI_BAR_MMIO); /* 64KB MMIO */
|
|
test_pci_commit(&t);
|
|
|
|
/* Probe BAR: write 0xFFFFFFFF, read back size mask */
|
|
pci_config_write32(0, 3, 0, PCI_BAR0_OFFSET, 0xFFFFFFFF);
|
|
val = pci_config_read32(0, 3, 0, PCI_BAR0_OFFSET);
|
|
ck_assert_uint_eq(val, 0xFFFF0000);
|
|
|
|
/* Write a normal address, read it back */
|
|
pci_config_write32(0, 3, 0, PCI_BAR0_OFFSET, 0x80010000);
|
|
val = pci_config_read32(0, 3, 0, PCI_BAR0_OFFSET);
|
|
ck_assert_uint_eq(val, 0x80010000);
|
|
|
|
/* Probe unimplemented BAR */
|
|
pci_config_write32(0, 3, 0, PCI_BAR0_OFFSET + 4, 0xFFFFFFFF);
|
|
val = pci_config_read32(0, 3, 0, PCI_BAR0_OFFSET + 4);
|
|
ck_assert_uint_eq(val, 0x00000000);
|
|
|
|
test_pci_cleanup(&t);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_mmio_mock_unreachable)
|
|
{
|
|
struct test_pci_topology t;
|
|
uint32_t val;
|
|
int br;
|
|
|
|
test_pci_init(&t);
|
|
br = test_pci_add_bridge(&t, 1, 0, 0x1111, 0x2222, TEST_PCI_ROOT_BUS);
|
|
test_pci_add_dev(&t, 0, 0, 0x3333, 0x4444, br);
|
|
test_pci_commit(&t);
|
|
|
|
/* Device behind unconfigured bridge: 0xFFFFFFFF */
|
|
val = pci_config_read32(1, 0, 0, PCI_VENDOR_ID_OFFSET);
|
|
ck_assert_uint_eq(val, 0xFFFFFFFF);
|
|
|
|
/* Non-existent device on bus 0 */
|
|
val = pci_config_read32(0, 31, 0, PCI_VENDOR_ID_OFFSET);
|
|
ck_assert_uint_eq(val, 0xFFFFFFFF);
|
|
|
|
/* Non-existent bus entirely */
|
|
val = pci_config_read32(5, 0, 0, PCI_VENDOR_ID_OFFSET);
|
|
ck_assert_uint_eq(val, 0xFFFFFFFF);
|
|
|
|
/* Bridge itself IS visible on bus 0 */
|
|
val = pci_config_read32(0, 1, 0, PCI_VENDOR_ID_OFFSET);
|
|
ck_assert_uint_ne(val, 0xFFFFFFFF);
|
|
|
|
test_pci_cleanup(&t);
|
|
}
|
|
END_TEST
|
|
|
|
/*
|
|
* Tests exercising actual pci.c code
|
|
*/
|
|
|
|
START_TEST(test_pci_program_bar_64bit)
|
|
{
|
|
struct test_pci_topology t;
|
|
struct pci_enum_info info;
|
|
uint8_t is_64bit = 0;
|
|
int dev_node;
|
|
int ret;
|
|
|
|
test_pci_init(&t);
|
|
dev_node = test_pci_add_dev(&t, 0, 0, 0x1234, 0x5678, TEST_PCI_ROOT_BUS);
|
|
/* 64-bit prefetchable MMIO BAR, 1MB */
|
|
test_pci_dev_set_bar(&t, dev_node, 0, 0x100000, TEST_PCI_BAR_64BIT | TEST_PCI_BAR_PF);
|
|
test_pci_commit(&t);
|
|
|
|
memset(&info, 0, sizeof(info));
|
|
info.mem_pf = 0x90000000;
|
|
info.mem_pf_limit = 0xFFFFFFFF;
|
|
info.mem = 0x80000000;
|
|
info.mem_limit = 0x88000000;
|
|
info.io = 0x2000;
|
|
|
|
ret = pci_program_bar(0, 0, 0, 0, &info, &is_64bit);
|
|
|
|
/* pci_program_bar must succeed and recognise this as a 64-bit BAR */
|
|
ck_assert_int_eq(ret, 0);
|
|
ck_assert_uint_eq(is_64bit, 1);
|
|
|
|
/* The prefetchable memory base must advance by the BAR size (1MB).
|
|
* With the ordering bug the lower BAR mask is read before the upper
|
|
* register is written with 0xFFFFFFFF, so the mock returns only type
|
|
* bits (size portion = 0) and pci_program_bar computes length = 0,
|
|
* leaving mem_pf unchanged. */
|
|
ck_assert_uint_eq(info.mem_pf, 0x90000000 + 0x100000);
|
|
|
|
test_pci_cleanup(&t);
|
|
}
|
|
END_TEST
|
|
|
|
/* test_pci_program_bar_restore: verify restore_bar on error path */
|
|
START_TEST(test_pci_program_bar_restore)
|
|
{
|
|
struct test_pci_topology t;
|
|
struct pci_enum_info info;
|
|
uint8_t is_64bit = 0;
|
|
int dev_node;
|
|
int ret;
|
|
uint32_t bar0_val, vid_did;
|
|
|
|
test_pci_init(&t);
|
|
dev_node = test_pci_add_dev(&t, 0, 0, 0x1234, 0x5678, TEST_PCI_ROOT_BUS);
|
|
/* BAR0 is unimplemented (size=0) — probing will read back 0, triggering
|
|
* the restore_bar path in pci_program_bar. */
|
|
test_pci_commit(&t);
|
|
|
|
/* Pre-fill BAR0 config register with a known value */
|
|
{
|
|
uint32_t orig = 0xDEAD0000;
|
|
memcpy(&t.nodes[dev_node].cfg[PCI_BAR0_OFFSET], &orig, 4);
|
|
}
|
|
|
|
memset(&info, 0, sizeof(info));
|
|
info.mem = 0x80000000;
|
|
info.mem_limit = 0x88000000;
|
|
info.mem_pf = 0x90000000;
|
|
info.mem_pf_limit = 0xFFFFFFFF;
|
|
info.io = 0x2000;
|
|
|
|
ret = pci_program_bar(0, 0, 0, 0, &info, &is_64bit);
|
|
/* pci_program_bar should return 0 */
|
|
ck_assert_int_eq(ret, 0);
|
|
|
|
/* BAR0 must be restored to the original value 0xDEAD0000. */
|
|
bar0_val = pci_config_read32(0, 0, 0, PCI_BAR0_OFFSET);
|
|
ck_assert_uint_eq(bar0_val, 0xDEAD0000);
|
|
|
|
/* Also verify vendor/device ID was NOT corrupted */
|
|
vid_did = pci_config_read32(0, 0, 0, PCI_VENDOR_ID_OFFSET);
|
|
ck_assert_uint_ne(vid_did, 0xDEAD0000);
|
|
|
|
test_pci_cleanup(&t);
|
|
}
|
|
END_TEST
|
|
|
|
/* test_program_bar_types: parameterized single-BAR programming */
|
|
START_TEST(test_program_bar_types)
|
|
{
|
|
struct bar_type_case {
|
|
const char *label;
|
|
uint32_t size;
|
|
int type;
|
|
int io_hi16_zero;
|
|
uint32_t exp_bar; /* expected BAR value after programming */
|
|
uint32_t exp_mem; /* expected info.mem after */
|
|
uint32_t exp_mem_pf; /* expected info.mem_pf after */
|
|
uint32_t exp_io; /* expected info.io after */
|
|
};
|
|
static const struct bar_type_case cases[] = {
|
|
{
|
|
"32-bit MMIO 64KB", 0x10000, TEST_PCI_BAR_MMIO, 0,
|
|
0x80000000,
|
|
0x80000000 + 0x10000, 0x90000000, 0x2000
|
|
},
|
|
{
|
|
"IO 256B", 256, TEST_PCI_BAR_IO, 0,
|
|
0x2000,
|
|
0x80000000, 0x90000000, 0x2000 + 256
|
|
},
|
|
{
|
|
"IO 256B hi16_zero", 256, TEST_PCI_BAR_IO, 1,
|
|
0x2000,
|
|
0x80000000, 0x90000000, 0x2000 + 256
|
|
},
|
|
};
|
|
int nc = sizeof(cases) / sizeof(cases[0]);
|
|
int c;
|
|
|
|
for (c = 0; c < nc; c++) {
|
|
const struct bar_type_case *tc = &cases[c];
|
|
struct test_pci_topology t;
|
|
struct pci_enum_info info;
|
|
int dev_node;
|
|
int ret;
|
|
uint32_t bar_val;
|
|
|
|
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, tc->size, tc->type);
|
|
if (tc->io_hi16_zero)
|
|
t.nodes[dev_node].bars[0].io_hi16_zero = 1;
|
|
test_pci_commit(&t);
|
|
|
|
memset(&info, 0, sizeof(info));
|
|
info.mem = 0x80000000;
|
|
info.mem_limit = 0x88000000;
|
|
info.mem_pf = 0x90000000;
|
|
info.mem_pf_limit = 0xFFFFFFFF;
|
|
info.io = 0x2000;
|
|
|
|
ret = pci_enum_bus(0, &info);
|
|
ck_assert_msg(ret == 0, "%s: ret", tc->label);
|
|
|
|
bar_val = pci_config_read32(0, 0, 0, PCI_BAR0_OFFSET);
|
|
ck_assert_msg(bar_val == tc->exp_bar, "%s: bar", tc->label);
|
|
ck_assert_msg(info.mem == tc->exp_mem, "%s: mem", tc->label);
|
|
ck_assert_msg(info.mem_pf == tc->exp_mem_pf, "%s: mem_pf", tc->label);
|
|
ck_assert_msg(info.io == tc->exp_io, "%s: io", tc->label);
|
|
|
|
test_pci_cleanup(&t);
|
|
}
|
|
}
|
|
END_TEST
|
|
|
|
/* test_program_bar_out_of_range: bar_idx >= 6 returns -1 */
|
|
START_TEST(test_program_bar_out_of_range)
|
|
{
|
|
struct test_pci_topology t;
|
|
struct pci_enum_info info;
|
|
uint8_t is_64bit = 0;
|
|
int ret;
|
|
|
|
test_pci_init(&t);
|
|
test_pci_add_dev(&t, 0, 0, 0x1234, 0x5678, TEST_PCI_ROOT_BUS);
|
|
test_pci_commit(&t);
|
|
|
|
memset(&info, 0, sizeof(info));
|
|
ret = pci_program_bar(0, 0, 0, 6, &info, &is_64bit);
|
|
ck_assert_int_eq(ret, -1);
|
|
|
|
test_pci_cleanup(&t);
|
|
}
|
|
END_TEST
|
|
|
|
/* test_program_bar_64bit_upper_reject: upper half != 0xFFFFFFFF */
|
|
START_TEST(test_program_bar_64bit_upper_reject)
|
|
{
|
|
struct test_pci_topology t;
|
|
struct pci_enum_info info;
|
|
uint8_t is_64bit = 0;
|
|
int dev_node;
|
|
int ret;
|
|
uint32_t bar0_val, bar1_val;
|
|
|
|
test_pci_init(&t);
|
|
dev_node = test_pci_add_dev(&t, 0, 0, 0x1234, 0x5678, TEST_PCI_ROOT_BUS);
|
|
/* 64-bit prefetchable MMIO BAR, 1MB, but upper mask = 0 (not 0xFFFFFFFF)
|
|
* our implementation refuses to map so much address space for now */
|
|
test_pci_dev_set_bar(&t, dev_node, 0, 0x100000, TEST_PCI_BAR_64BIT | TEST_PCI_BAR_PF);
|
|
/* set it manually as test_pci_dev_set_bar only handle 32-bit size */
|
|
t.nodes[dev_node].bars[0].upper_mask = 0x0000000F;
|
|
test_pci_commit(&t);
|
|
|
|
/* Pre-fill BAR0 and BAR1 with known values */
|
|
{
|
|
uint32_t orig0 = 0xAABB0000, orig1 = 0xCCDD0000;
|
|
memcpy(&t.nodes[dev_node].cfg[PCI_BAR0_OFFSET], &orig0, 4);
|
|
memcpy(&t.nodes[dev_node].cfg[PCI_BAR0_OFFSET + 4], &orig1, 4);
|
|
}
|
|
|
|
memset(&info, 0, sizeof(info));
|
|
info.mem = 0x80000000;
|
|
info.mem_limit = 0x88000000;
|
|
info.mem_pf = 0x90000000;
|
|
info.mem_pf_limit = 0xFFFFFFFF;
|
|
info.io = 0x2000;
|
|
|
|
ret = pci_program_bar(0, 0, 0, 0, &info, &is_64bit);
|
|
/* Should return 0 (ret is initialized to 0, "too much memory" path
|
|
* doesn't change ret before goto restore_bar) */
|
|
ck_assert_int_eq(ret, 0);
|
|
|
|
/* BAR0 must be restored, BAR1 (upper half) must be restored */
|
|
bar0_val = pci_config_read32(0, 0, 0, PCI_BAR0_OFFSET);
|
|
ck_assert_uint_eq(bar0_val, 0xAABB0000);
|
|
bar1_val = pci_config_read32(0, 0, 0, PCI_BAR0_OFFSET + 4);
|
|
ck_assert_uint_eq(bar1_val, 0xCCDD0000);
|
|
|
|
/* is_64bit must be set so the caller skips the next BAR index */
|
|
ck_assert_uint_eq(is_64bit, 1);
|
|
|
|
/* Allocators unchanged */
|
|
ck_assert_uint_eq(info.mem_pf, 0x90000000);
|
|
|
|
test_pci_cleanup(&t);
|
|
}
|
|
END_TEST
|
|
|
|
/* test_program_bar_no_space: limit exceeded → restore_bar */
|
|
START_TEST(test_program_bar_no_space)
|
|
{
|
|
struct test_pci_topology t;
|
|
struct pci_enum_info info;
|
|
uint8_t is_64bit = 0;
|
|
int dev_node;
|
|
int ret;
|
|
uint32_t bar0_val;
|
|
|
|
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, 0x100000, TEST_PCI_BAR_MMIO); /* 1MB MMIO */
|
|
test_pci_commit(&t);
|
|
|
|
/* Pre-fill BAR0 */
|
|
{
|
|
uint32_t orig = 0xBEEF0000;
|
|
memcpy(&t.nodes[dev_node].cfg[PCI_BAR0_OFFSET], &orig, 4);
|
|
}
|
|
|
|
memset(&info, 0, sizeof(info));
|
|
/* mem not aligned to 1MB and limit too close → alignment overshoots */
|
|
info.mem = 0x80080000;
|
|
info.mem_limit = 0x80100000;
|
|
info.mem_pf = 0x90000000;
|
|
info.mem_pf_limit = 0xFFFFFFFF;
|
|
info.io = 0x2000;
|
|
|
|
ret = pci_program_bar(0, 0, 0, 0, &info, &is_64bit);
|
|
ck_assert_int_ne(ret, 0);
|
|
|
|
/* BAR0 restored */
|
|
bar0_val = pci_config_read32(0, 0, 0, PCI_BAR0_OFFSET);
|
|
ck_assert_uint_eq(bar0_val, 0xBEEF0000);
|
|
|
|
/* Allocator unchanged */
|
|
ck_assert_uint_eq(info.mem, 0x80080000);
|
|
|
|
test_pci_cleanup(&t);
|
|
}
|
|
END_TEST
|
|
|
|
/* test_program_bars_iteration: full BAR iteration with mixed types */
|
|
START_TEST(test_program_bars_iteration)
|
|
{
|
|
struct test_pci_topology t;
|
|
struct pci_enum_info info;
|
|
struct test_pci_node *n;
|
|
int dev_node;
|
|
uint32_t bar_val;
|
|
uint16_t cmd_before, cmd_after;
|
|
|
|
test_pci_init(&t);
|
|
dev_node = test_pci_add_dev(&t, 0, 0, 0x1234, 0x5678, TEST_PCI_ROOT_BUS);
|
|
/* BAR0: 32-bit MMIO non-prefetch 64KB */
|
|
test_pci_dev_set_bar(&t, dev_node, 0, 0x10000, TEST_PCI_BAR_MMIO);
|
|
/* BAR1: unimplemented */
|
|
/* BAR2: 64-bit prefetchable MMIO 1MB (consumes BAR2+BAR3) */
|
|
test_pci_dev_set_bar(&t, dev_node, 2, 0x100000, TEST_PCI_BAR_64BIT | TEST_PCI_BAR_PF);
|
|
/* BAR4: IO 256 bytes */
|
|
test_pci_dev_set_bar(&t, dev_node, 4, 256, TEST_PCI_BAR_IO);
|
|
/* BAR5: unimplemented */
|
|
test_pci_commit(&t);
|
|
n = &t.nodes[dev_node];
|
|
|
|
/* Set a known command register value */
|
|
cmd_before = 0x0007;
|
|
{
|
|
uint16_t cmd = cmd_before;
|
|
memcpy(&n->cfg[PCI_COMMAND_OFFSET], &cmd, 2);
|
|
}
|
|
|
|
memset(&info, 0, sizeof(info));
|
|
info.mem = 0x80000000;
|
|
info.mem_limit = 0x88000000;
|
|
info.mem_pf = 0x90000000;
|
|
info.mem_pf_limit = 0xFFFFFFFF;
|
|
info.io = 0x2000;
|
|
|
|
pci_enum_bus(0, &info);
|
|
|
|
/* BAR0: programmed from mem */
|
|
bar_val = pci_config_read32(0, 0, 0, PCI_BAR0_OFFSET);
|
|
ck_assert_uint_eq(bar_val, 0x80000000);
|
|
ck_assert_uint_eq(info.mem, 0x80000000 + 0x10000);
|
|
|
|
/* BAR2: programmed from mem_pf (64-bit) */
|
|
bar_val = pci_config_read32(0, 0, 0, PCI_BAR0_OFFSET + 2 * 4);
|
|
ck_assert_uint_eq(bar_val, 0x90000000);
|
|
/* BAR3 (upper half): should be 0 */
|
|
bar_val = pci_config_read32(0, 0, 0, PCI_BAR0_OFFSET + 3 * 4);
|
|
ck_assert_uint_eq(bar_val, 0x00000000);
|
|
ck_assert_uint_eq(info.mem_pf, 0x90000000 + 0x100000);
|
|
|
|
/* BAR4: programmed from io */
|
|
bar_val = pci_config_read32(0, 0, 0, PCI_BAR0_OFFSET + 4 * 4);
|
|
ck_assert_uint_eq(bar_val, 0x2000);
|
|
ck_assert_uint_eq(info.io, 0x2000 + 256);
|
|
|
|
/* Command register restored */
|
|
cmd_after = pci_config_read16(0, 0, 0, PCI_COMMAND_OFFSET);
|
|
ck_assert_uint_eq(cmd_after, cmd_before);
|
|
|
|
test_pci_cleanup(&t);
|
|
}
|
|
END_TEST
|
|
|
|
/* test_program_bar_zero_align: a hostile/malformed MMIO BAR whose probe
|
|
* readback has all address bits (31:4) zero but is non-zero (e.g. 0x8, just
|
|
* the prefetch indicator) must be treated as unimplemented. Otherwise
|
|
* bar_align == 0 makes length = (~0)+1 wrap to 0, the allocator cursor is not
|
|
* advanced, and the following BAR is programmed onto the same address. */
|
|
START_TEST(test_program_bar_zero_align)
|
|
{
|
|
struct test_pci_topology t;
|
|
struct pci_enum_info info;
|
|
int dev_node;
|
|
uint32_t bar0_val, bar1_val;
|
|
|
|
test_pci_init(&t);
|
|
dev_node = test_pci_add_dev(&t, 0, 0, 0x1234, 0x5678, TEST_PCI_ROOT_BUS);
|
|
/* BAR0: malformed prefetchable MMIO — probe returns only the prefetch
|
|
* bit (0x8), so bar_align == 0. Use a raw override; no power-of-2 size
|
|
* can produce this readback. */
|
|
t.nodes[dev_node].bars[0].has_raw_probe = 1;
|
|
t.nodes[dev_node].bars[0].raw_probe = 0x00000008;
|
|
/* BAR1: normal 64KB prefetchable MMIO (same mem_pf window). */
|
|
test_pci_dev_set_bar(&t, dev_node, 1, 0x10000, TEST_PCI_BAR_PF);
|
|
test_pci_commit(&t);
|
|
|
|
/* Pre-fill BAR0 so we can confirm it is restored, not programmed. */
|
|
{
|
|
uint32_t orig = 0xDEAD0008;
|
|
memcpy(&t.nodes[dev_node].cfg[PCI_BAR0_OFFSET], &orig, 4);
|
|
}
|
|
|
|
memset(&info, 0, sizeof(info));
|
|
info.mem = 0x80000000;
|
|
info.mem_limit = 0x88000000;
|
|
info.mem_pf = 0x90000000;
|
|
info.mem_pf_limit = 0xFFFFFFFF;
|
|
info.io = 0x2000;
|
|
|
|
pci_enum_bus(0, &info);
|
|
|
|
bar0_val = pci_config_read32(0, 0, 0, PCI_BAR0_OFFSET);
|
|
bar1_val = pci_config_read32(0, 0, 0, PCI_BAR0_OFFSET + 4);
|
|
|
|
/* The malformed BAR0 must be skipped and restored to its original value,
|
|
* never programmed onto the MMIO window. */
|
|
ck_assert_uint_eq(bar0_val, 0xDEAD0008);
|
|
|
|
/* BAR1 takes the head of the prefetchable window. */
|
|
ck_assert_uint_eq(bar1_val, 0x90000000);
|
|
|
|
/* The two BARs must not collide on the same MMIO address. */
|
|
ck_assert_uint_ne(bar0_val, bar1_val);
|
|
|
|
/* mem_pf advanced only by BAR1's size. */
|
|
ck_assert_uint_eq(info.mem_pf, 0x90000000 + 0x10000);
|
|
|
|
test_pci_cleanup(&t);
|
|
}
|
|
END_TEST
|
|
|
|
/* test_program_bridge: parameterized bridge programming tests */
|
|
|
|
START_TEST(test_program_bridge)
|
|
{
|
|
struct bar_spec {
|
|
int idx;
|
|
uint32_t size;
|
|
int type;
|
|
};
|
|
struct bridge_case {
|
|
const char *label;
|
|
int num_bars;
|
|
struct bar_spec bars[3];
|
|
uint32_t exp_bars[3];
|
|
uint16_t exp_mbase, exp_mlimit;
|
|
uint16_t exp_pfbase, exp_pflimit;
|
|
uint8_t exp_iobase, exp_iolimit;
|
|
uint16_t exp_cmd;
|
|
};
|
|
static const struct bridge_case cases[] = {
|
|
{
|
|
"no devices", 0, {{0, 0, 0}}, {0},
|
|
0xFFFF, 0x0000, 0xFFFF, 0x0000, 0xFF, 0x00, 0x0004
|
|
},
|
|
{
|
|
"MMIO 64KB", 1,
|
|
{{0, 0x10000, TEST_PCI_BAR_MMIO}},
|
|
{0x80000000},
|
|
0x8000, 0x800F, 0xFFFF, 0x0000, 0xFF, 0x00, 0x0006
|
|
},
|
|
{
|
|
"PF 1MB 64bit", 1,
|
|
{{0, 0x100000, TEST_PCI_BAR_64BIT | TEST_PCI_BAR_PF}},
|
|
{0x90000000},
|
|
0xFFFF, 0x0000, 0x9000, 0x900F, 0xFF, 0x00, 0x0006
|
|
},
|
|
{
|
|
"IO 256B", 1,
|
|
{{0, 256, TEST_PCI_BAR_IO}},
|
|
{0x2000},
|
|
0xFFFF, 0x0000, 0xFFFF, 0x0000, 0x20, 0x2F, 0x0005
|
|
},
|
|
{
|
|
"all windows", 3,
|
|
{{0, 0x10000, TEST_PCI_BAR_MMIO},
|
|
{1, 0x10000, TEST_PCI_BAR_PF},
|
|
{2, 256, TEST_PCI_BAR_IO}},
|
|
{0x80000000, 0x90000000, 0x2000},
|
|
0x8000, 0x800F, 0x9000, 0x900F, 0x20, 0x2F, 0x0007
|
|
},
|
|
};
|
|
int nc = sizeof(cases) / sizeof(cases[0]);
|
|
int c;
|
|
|
|
for (c = 0; c < nc; c++) {
|
|
const struct bridge_case *tc = &cases[c];
|
|
struct test_pci_topology t;
|
|
struct pci_enum_info info;
|
|
int br, ep, i;
|
|
uint8_t sec;
|
|
|
|
test_pci_init(&t);
|
|
br = test_pci_add_bridge(&t, 1, 0, 0xAAAA, 0xBBBB, TEST_PCI_ROOT_BUS);
|
|
if (tc->num_bars > 0) {
|
|
ep = test_pci_add_dev(&t, 0, 0, 0xCCCC, 0xDDDD, br);
|
|
for (i = 0; i < tc->num_bars; i++)
|
|
test_pci_dev_set_bar(&t, ep, tc->bars[i].idx,
|
|
tc->bars[i].size, tc->bars[i].type);
|
|
}
|
|
test_pci_commit(&t);
|
|
|
|
memset(&info, 0, sizeof(info));
|
|
info.mem = 0x80000000;
|
|
info.mem_limit = 0x88000000;
|
|
info.mem_pf = 0x90000000;
|
|
info.mem_pf_limit = 0xFFFFFFFF;
|
|
info.io = 0x2000;
|
|
info.curr_bus_number = 0;
|
|
|
|
pci_enum_bus(0, &info);
|
|
|
|
/* Bus numbering */
|
|
ck_assert_msg(pci_config_read8(0, 1, 0, PCI_PRIMARY_BUS) == 0,
|
|
"%s: prim", tc->label);
|
|
sec = pci_config_read8(0, 1, 0, PCI_SECONDARY_BUS);
|
|
ck_assert_msg(sec != 0, "%s: sec", tc->label);
|
|
ck_assert_msg(pci_config_read8(0, 1, 0, PCI_SUB_SEC_BUS) >= sec,
|
|
"%s: sub", tc->label);
|
|
|
|
/* Endpoint BARs */
|
|
for (i = 0; i < tc->num_bars; i++) {
|
|
uint32_t bar_val = pci_config_read32(sec, 0, 0,
|
|
PCI_BAR0_OFFSET + tc->bars[i].idx * 4);
|
|
ck_assert_msg(bar_val == tc->exp_bars[i],
|
|
"%s: BAR%d", tc->label, tc->bars[i].idx);
|
|
}
|
|
|
|
/* Bridge windows */
|
|
ck_assert_msg(pci_config_read16(0, 1, 0, PCI_MMIO_BASE_OFF)
|
|
== tc->exp_mbase, "%s: mbase", tc->label);
|
|
ck_assert_msg(pci_config_read16(0, 1, 0, PCI_MMIO_LIMIT_OFF)
|
|
== tc->exp_mlimit, "%s: mlimit", tc->label);
|
|
ck_assert_msg(pci_config_read16(0, 1, 0, PCI_PREFETCH_BASE_OFF)
|
|
== tc->exp_pfbase, "%s: pfbase", tc->label);
|
|
ck_assert_msg(pci_config_read16(0, 1, 0, PCI_PREFETCH_LIMIT_OFF)
|
|
== tc->exp_pflimit, "%s: pflimit", tc->label);
|
|
ck_assert_msg(pci_config_read8(0, 1, 0, PCI_IO_BASE_OFF)
|
|
== tc->exp_iobase, "%s: iobase", tc->label);
|
|
ck_assert_msg(pci_config_read8(0, 1, 0, PCI_IO_LIMIT_OFF)
|
|
== tc->exp_iolimit, "%s: iolimit", tc->label);
|
|
|
|
/* Command register */
|
|
ck_assert_msg(pci_config_read16(0, 1, 0, PCI_COMMAND_OFFSET)
|
|
== tc->exp_cmd, "%s: cmd", tc->label);
|
|
|
|
test_pci_cleanup(&t);
|
|
}
|
|
}
|
|
END_TEST
|
|
|
|
/* test_program_bridge_io_64k_no_narrow: a device advertising a 64KB IO BAR
|
|
* must not push the IO allocator past the 16-bit IO space (0xFFFF). The PCI
|
|
* bridge IO base/limit registers carry only address bits [15:8] in a uint8_t,
|
|
* so an io cursor >= 0x10000 truncates: io_start 0x20000 -> base reg 0x00,
|
|
* programming a bogus window 0x0000-0x0FFF that forwards legacy IO (8259A PIC,
|
|
* 8254 PIT, MC146818 RTC) to the secondary bus. With the allocator capped at
|
|
* the 16-bit IO ceiling the oversized BAR is skipped and the bridge window is
|
|
* programmed from the real cursor. */
|
|
START_TEST(test_program_bridge_io_64k_no_narrow)
|
|
{
|
|
struct test_pci_topology t;
|
|
struct pci_enum_info info;
|
|
int dev0, br, ep;
|
|
uint8_t iobase, iolimit;
|
|
|
|
test_pci_init(&t);
|
|
/* dev 0 on bus 0: hostile/oversized 64KB IO BAR (decodes only 16 bits) */
|
|
dev0 = test_pci_add_dev(&t, 0, 0, 0x1111, 0x2222, TEST_PCI_ROOT_BUS);
|
|
test_pci_dev_set_bar(&t, dev0, 0, 0x10000, TEST_PCI_BAR_IO);
|
|
t.nodes[dev0].bars[0].io_hi16_zero = 1;
|
|
/* dev 1 on bus 0: bridge with a small 256B IO device behind it */
|
|
br = test_pci_add_bridge(&t, 1, 0, 0xAAAA, 0xBBBB, TEST_PCI_ROOT_BUS);
|
|
ep = test_pci_add_dev(&t, 0, 0, 0xCCCC, 0xDDDD, br);
|
|
test_pci_dev_set_bar(&t, ep, 0, 256, TEST_PCI_BAR_IO);
|
|
test_pci_commit(&t);
|
|
|
|
memset(&info, 0, sizeof(info));
|
|
info.mem = 0x80000000;
|
|
info.mem_limit = 0x88000000;
|
|
info.mem_pf = 0x90000000;
|
|
info.mem_pf_limit = 0xFFFFFFFF;
|
|
info.io = 0x2000;
|
|
info.curr_bus_number = 0;
|
|
|
|
pci_enum_bus(0, &info);
|
|
|
|
/* The oversized IO BAR must be skipped, leaving the cursor in 16-bit IO
|
|
* space; the bridge IO window must reflect the real device (0x2000-0x2FFF)
|
|
* and never decode down to 0x0000 over the legacy IO range. */
|
|
iobase = pci_config_read8(0, 1, 0, PCI_IO_BASE_OFF);
|
|
iolimit = pci_config_read8(0, 1, 0, PCI_IO_LIMIT_OFF);
|
|
ck_assert_uint_eq(iobase, 0x20);
|
|
ck_assert_uint_eq(iolimit, 0x2F);
|
|
ck_assert_uint_le(info.io, 0x10000);
|
|
|
|
test_pci_cleanup(&t);
|
|
}
|
|
END_TEST
|
|
|
|
/* test_program_bridge_oom_initial: initial alignment failures */
|
|
START_TEST(test_program_bridge_oom_initial)
|
|
{
|
|
struct {
|
|
const char *label;
|
|
uint16_t cmd_before;
|
|
struct pci_enum_info info;
|
|
} cases[] = {
|
|
{
|
|
"pf: 1MB align wraps past 32-bit",
|
|
0x0007,
|
|
{ .mem_pf = 0xFFF00001, .mem_pf_limit = 0xFFFFFFFF,
|
|
.mem = 0x80000000, .mem_limit = 0x88000000,
|
|
.io = 0x2000 }
|
|
},
|
|
{
|
|
"mem: 1MB align wraps past 32-bit",
|
|
0x0003,
|
|
{ .mem_pf = 0x90000000, .mem_pf_limit = 0xFFFFFFFF,
|
|
.mem = 0xFFF00001, .mem_limit = 0xFFFFFFFF,
|
|
.io = 0x2000 }
|
|
},
|
|
{
|
|
"io: 4KB align wraps past 32-bit",
|
|
0x0005,
|
|
{ .mem_pf = 0x90000000, .mem_pf_limit = 0xFFFFFFFF,
|
|
.mem = 0x80000000, .mem_limit = 0x88000000,
|
|
.io = 0xFFFFF001 }
|
|
},
|
|
};
|
|
int i;
|
|
|
|
for (i = 0; i < (int)(sizeof(cases) / sizeof(cases[0])); i++) {
|
|
struct test_pci_topology t;
|
|
struct pci_enum_info saved;
|
|
int br, ret;
|
|
|
|
test_pci_init(&t);
|
|
br = test_pci_add_bridge(&t, 1, 0, 0xAAAA, 0xBBBB, TEST_PCI_ROOT_BUS);
|
|
test_pci_commit(&t);
|
|
memcpy(&t.nodes[br].cfg[PCI_COMMAND_OFFSET], &cases[i].cmd_before, 2);
|
|
|
|
saved = cases[i].info;
|
|
ret = pci_program_bridge(0, 1, 0, &cases[i].info);
|
|
ck_assert_msg(ret == -1, "expected failure for: %s", cases[i].label);
|
|
{
|
|
uint16_t cmd = pci_config_read16(0, 1, 0, PCI_COMMAND_OFFSET);
|
|
ck_assert_msg(cmd == cases[i].cmd_before,
|
|
"command register changed for: %s", cases[i].label);
|
|
}
|
|
/* info state must be fully restored */
|
|
ck_assert_msg(cases[i].info.curr_bus_number == saved.curr_bus_number,
|
|
"%s: curr_bus_number not restored", cases[i].label);
|
|
ck_assert_msg(cases[i].info.mem == saved.mem,
|
|
"%s: mem not restored", cases[i].label);
|
|
ck_assert_msg(cases[i].info.mem_pf == saved.mem_pf,
|
|
"%s: mem_pf not restored", cases[i].label);
|
|
ck_assert_msg(cases[i].info.io == saved.io,
|
|
"%s: io not restored", cases[i].label);
|
|
/* bridge bus registers must be cleared */
|
|
ck_assert_msg(pci_config_read8(0, 1, 0, PCI_SECONDARY_BUS) == 0,
|
|
"%s: secondary bus not cleared", cases[i].label);
|
|
ck_assert_msg(pci_config_read8(0, 1, 0, PCI_SUB_SEC_BUS) == 0,
|
|
"%s: subordinate bus not cleared", cases[i].label);
|
|
test_pci_cleanup(&t);
|
|
}
|
|
}
|
|
END_TEST
|
|
|
|
/* test_program_bridge_oom_post_enum: pf/mem/io space exhausted after enum */
|
|
START_TEST(test_program_bridge_oom_post_enum)
|
|
{
|
|
struct {
|
|
const char *label;
|
|
uint32_t bar_size;
|
|
unsigned int bar_type;
|
|
struct pci_enum_info info;
|
|
} cases[] = {
|
|
{
|
|
"pf: post-enum 1MB align exceeds limit",
|
|
0x10000, TEST_PCI_BAR_PF,
|
|
{ .mem = 0x80000000, .mem_limit = 0x88000000,
|
|
.mem_pf = 0x90000000, .mem_pf_limit = 0x90100000,
|
|
.io = 0x2000 }
|
|
},
|
|
{
|
|
"mem: post-enum 1MB align exceeds limit",
|
|
0x10000, TEST_PCI_BAR_MMIO,
|
|
{ .mem = 0x80000000, .mem_limit = 0x80100000,
|
|
.mem_pf = 0x90000000, .mem_pf_limit = 0xFFFFFFFF,
|
|
.io = 0x2000 }
|
|
},
|
|
/* No "io: post-enum align wraps" case: the IO allocator is capped at
|
|
* the 16-bit IO ceiling (PCI_IO32_LIMIT), so info.io can never reach
|
|
* the top of the 32-bit range and the post-enum IO alignment cannot
|
|
* wrap. Oversized IO BARs are now skipped at allocation time, covered
|
|
* by test_program_bridge_io_64k_no_narrow. */
|
|
};
|
|
int i;
|
|
|
|
for (i = 0; i < (int)(sizeof(cases) / sizeof(cases[0])); i++) {
|
|
struct test_pci_topology t;
|
|
struct pci_enum_info saved;
|
|
int br, ep, ret;
|
|
uint16_t cmd_before = 0x0007;
|
|
|
|
test_pci_init(&t);
|
|
br = test_pci_add_bridge(&t, 1, 0, 0xAAAA, 0xBBBB, TEST_PCI_ROOT_BUS);
|
|
ep = test_pci_add_dev(&t, 0, 0, 0xCCCC, 0xDDDD, br);
|
|
test_pci_dev_set_bar(&t, ep, 0, cases[i].bar_size, cases[i].bar_type);
|
|
test_pci_commit(&t);
|
|
memcpy(&t.nodes[br].cfg[PCI_COMMAND_OFFSET], &cmd_before, 2);
|
|
|
|
cases[i].info.curr_bus_number = 0;
|
|
saved = cases[i].info;
|
|
ret = pci_program_bridge(0, 1, 0, &cases[i].info);
|
|
ck_assert_msg(ret == -1, "expected failure for: %s", cases[i].label);
|
|
{
|
|
uint16_t cmd = pci_config_read16(0, 1, 0, PCI_COMMAND_OFFSET);
|
|
ck_assert_msg(cmd == cmd_before,
|
|
"command register changed for: %s", cases[i].label);
|
|
}
|
|
/* info state must be fully restored */
|
|
ck_assert_msg(cases[i].info.curr_bus_number == saved.curr_bus_number,
|
|
"%s: curr_bus_number not restored", cases[i].label);
|
|
ck_assert_msg(cases[i].info.mem == saved.mem,
|
|
"%s: mem not restored", cases[i].label);
|
|
ck_assert_msg(cases[i].info.mem_pf == saved.mem_pf,
|
|
"%s: mem_pf not restored", cases[i].label);
|
|
ck_assert_msg(cases[i].info.io == saved.io,
|
|
"%s: io not restored", cases[i].label);
|
|
/* bridge bus registers must be cleared */
|
|
ck_assert_msg(pci_config_read8(0, 1, 0, PCI_SECONDARY_BUS) == 0,
|
|
"%s: secondary bus not cleared", cases[i].label);
|
|
ck_assert_msg(pci_config_read8(0, 1, 0, PCI_SUB_SEC_BUS) == 0,
|
|
"%s: subordinate bus not cleared", cases[i].label);
|
|
test_pci_cleanup(&t);
|
|
}
|
|
}
|
|
END_TEST
|
|
|
|
/* test_enum_bus_topology: device dispatch + multifunction handling */
|
|
START_TEST(test_enum_bus_topology)
|
|
{
|
|
struct test_pci_topology t;
|
|
struct pci_enum_info info;
|
|
int d0, mf0, mf1;
|
|
uint32_t bar_val;
|
|
|
|
test_pci_init(&t);
|
|
/* dev 0: single-function endpoint with 32-bit MMIO BAR */
|
|
d0 = test_pci_add_dev(&t, 0, 0, 0x1111, 0x2222, TEST_PCI_ROOT_BUS);
|
|
test_pci_dev_set_bar(&t, d0, 0, 0x10000, TEST_PCI_BAR_MMIO);
|
|
/* dev 1 func 0: multifunction endpoint with IO BAR */
|
|
mf0 = test_pci_add_dev(&t, 1, 0, 0x3333, 0x4444, TEST_PCI_ROOT_BUS);
|
|
test_pci_dev_set_bar(&t, mf0, 0, 256, TEST_PCI_BAR_IO);
|
|
/* dev 1 func 1: second function with IO BAR */
|
|
mf1 = test_pci_add_dev(&t, 1, 1, 0x3333, 0x5555, TEST_PCI_ROOT_BUS);
|
|
test_pci_dev_set_bar(&t, mf1, 0, 256, TEST_PCI_BAR_IO);
|
|
test_pci_commit(&t);
|
|
|
|
/* Mark dev 1 func 0 as multifunction */
|
|
t.nodes[mf0].cfg[PCI_HEADER_TYPE_OFFSET] |= PCI_HEADER_TYPE_MULTIFUNC_MASK;
|
|
|
|
memset(&info, 0, sizeof(info));
|
|
info.mem = 0x80000000;
|
|
info.mem_limit = 0x88000000;
|
|
info.mem_pf = 0x90000000;
|
|
info.mem_pf_limit = 0xFFFFFFFF;
|
|
info.io = 0x2000;
|
|
info.curr_bus_number = 0;
|
|
|
|
pci_enum_bus(0, &info);
|
|
|
|
/* dev 0 BAR0 should be programmed (MMIO) */
|
|
bar_val = pci_config_read32(0, 0, 0, PCI_BAR0_OFFSET);
|
|
ck_assert_uint_eq(bar_val, 0x80000000);
|
|
|
|
/* dev 1 func 0 BAR0 should be programmed (IO) */
|
|
bar_val = pci_config_read32(0, 1, 0, PCI_BAR0_OFFSET);
|
|
ck_assert_uint_eq(bar_val, 0x2000);
|
|
|
|
/* dev 1 func 1 BAR0 should also be programmed (multifunction).
|
|
* IO base after func 0 is 0x2100, but 4KB alignment rounds up to 0x3000 */
|
|
bar_val = pci_config_read32(0, 1, 1, PCI_BAR0_OFFSET);
|
|
ck_assert_uint_eq(bar_val, 0x3000);
|
|
|
|
test_pci_cleanup(&t);
|
|
}
|
|
END_TEST
|
|
|
|
/* test_enum_do_full: end-to-end via pci_enum_do */
|
|
|
|
START_TEST(test_enum_do_full)
|
|
{
|
|
struct test_pci_topology t;
|
|
int br, ep;
|
|
uint32_t bar_val;
|
|
uint8_t sec_bus;
|
|
int ret;
|
|
|
|
test_pci_init(&t);
|
|
/* Bridge at 0:1.0 */
|
|
br = test_pci_add_bridge(&t, 1, 0, 0x1234, 0x0002, TEST_PCI_ROOT_BUS);
|
|
/* Endpoint behind bridge */
|
|
ep = test_pci_add_dev(&t, 0, 0, 0x1234, 0x0003, br);
|
|
test_pci_dev_set_bar(&t, ep, 0, 0x10000, TEST_PCI_BAR_MMIO); /* 64KB MMIO */
|
|
test_pci_commit(&t);
|
|
|
|
ret = pci_enum_do();
|
|
ck_assert_int_eq(ret, 0);
|
|
|
|
/* Bridge is on the root bus */
|
|
ck_assert_uint_eq(pci_config_read8(0, 1, 0, PCI_PRIMARY_BUS), 0);
|
|
|
|
/* Bridge should have bus numbers assigned */
|
|
sec_bus = pci_config_read8(0, 1, 0, PCI_SECONDARY_BUS);
|
|
ck_assert_uint_ne(sec_bus, 0);
|
|
|
|
/* No other bridge behind this one */
|
|
ck_assert_uint_ge(pci_config_read8(0, 1, 0, PCI_SUB_SEC_BUS), sec_bus);
|
|
|
|
/* Endpoint BAR should be programmed */
|
|
bar_val = pci_config_read32(sec_bus, 0, 0, PCI_BAR0_OFFSET);
|
|
ck_assert_uint_ne(bar_val, 0);
|
|
ck_assert_uint_ne(bar_val, 0xFFFFFFFF);
|
|
|
|
/* TODO: check bridge windows */
|
|
|
|
test_pci_cleanup(&t);
|
|
}
|
|
END_TEST
|
|
|
|
/* test_enum_do_nested_bridges: end-to-end nested bridge enumeration */
|
|
|
|
START_TEST(test_enum_do_nested_bridges)
|
|
{
|
|
struct test_pci_topology t;
|
|
int brA, brB, ep;
|
|
uint32_t bar_val;
|
|
uint8_t secA, subA, secB, subB;
|
|
int ret;
|
|
|
|
test_pci_init(&t);
|
|
brA = test_pci_add_bridge(&t, 1, 0, 0x1111, 0x2222, TEST_PCI_ROOT_BUS);
|
|
brB = test_pci_add_bridge(&t, 0, 0, 0x3333, 0x4444, brA);
|
|
ep = test_pci_add_dev(&t, 0, 0, 0x5555, 0x6666, brB);
|
|
test_pci_dev_set_bar(&t, ep, 0, 0x10000, TEST_PCI_BAR_MMIO);
|
|
test_pci_commit(&t);
|
|
|
|
ret = pci_enum_do();
|
|
ck_assert_int_eq(ret, 0);
|
|
|
|
/* Bridge A: primary=0, secondary assigned, subordinate >= secondary */
|
|
ck_assert_uint_eq(pci_config_read8(0, 1, 0, PCI_PRIMARY_BUS), 0);
|
|
secA = pci_config_read8(0, 1, 0, PCI_SECONDARY_BUS);
|
|
subA = pci_config_read8(0, 1, 0, PCI_SUB_SEC_BUS);
|
|
ck_assert_uint_ne(secA, 0);
|
|
ck_assert_uint_ge(subA, secA);
|
|
|
|
/* Bridge B: primary=secA, secondary assigned > secA, subordinate >= secB */
|
|
ck_assert_uint_eq(pci_config_read8(secA, 0, 0, PCI_PRIMARY_BUS), secA);
|
|
secB = pci_config_read8(secA, 0, 0, PCI_SECONDARY_BUS);
|
|
subB = pci_config_read8(secA, 0, 0, PCI_SUB_SEC_BUS);
|
|
ck_assert_uint_gt(secB, secA);
|
|
ck_assert_uint_ge(subB, secB);
|
|
|
|
/* Bridge A subordinate must cover bridge B's range */
|
|
ck_assert_uint_ge(subA, subB);
|
|
|
|
/* Endpoint BAR on bus secB should be programmed */
|
|
bar_val = pci_config_read32(secB, 0, 0, PCI_BAR0_OFFSET);
|
|
ck_assert_uint_ne(bar_val, 0);
|
|
ck_assert_uint_ne(bar_val, 0xFFFFFFFF);
|
|
|
|
test_pci_cleanup(&t);
|
|
}
|
|
END_TEST
|
|
|
|
/* test_config_rw_8bit_all_positions: read8/write8 at all byte offsets */
|
|
|
|
START_TEST(test_config_rw_8bit_all_positions)
|
|
{
|
|
struct test_pci_topology t;
|
|
int dev_node;
|
|
uint32_t base_off;
|
|
int i;
|
|
|
|
test_pci_init(&t);
|
|
dev_node = test_pci_add_dev(&t, 0, 0, 0x1234, 0x5678, TEST_PCI_ROOT_BUS);
|
|
test_pci_commit(&t);
|
|
|
|
/* Use an offset in cfg space that won't conflict with vendor/device ID.
|
|
* Write distinct values at each byte position within a dword. */
|
|
base_off = 0x40; /* arbitrary config space offset */
|
|
|
|
/* Zero the dword first */
|
|
pci_config_write32(0, 0, 0, base_off, 0x00000000);
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
uint8_t write_val = 0x10 * (i + 1); /* 0x10, 0x20, 0x30, 0x40 */
|
|
uint8_t read_val;
|
|
|
|
pci_config_write8(0, 0, 0, base_off + i, write_val);
|
|
read_val = pci_config_read8(0, 0, 0, base_off + i);
|
|
ck_assert_uint_eq(read_val, write_val);
|
|
}
|
|
|
|
/* Verify the full dword has all four bytes */
|
|
{
|
|
uint32_t full;
|
|
full = pci_config_read32(0, 0, 0, base_off);
|
|
ck_assert_uint_eq(full, 0x40302010);
|
|
}
|
|
|
|
test_pci_cleanup(&t);
|
|
}
|
|
END_TEST
|
|
|
|
/* test_enum_next_aligned_overflow: edge cases for alignment helper */
|
|
|
|
START_TEST(test_enum_next_aligned_overflow)
|
|
{
|
|
uint32_t next;
|
|
int ret;
|
|
|
|
/* Already aligned: should return same address */
|
|
ret = pci_enum_next_aligned32(0x80000000, &next, 0x1000, 0xFFFFFFFF);
|
|
ck_assert_int_eq(ret, 0);
|
|
ck_assert_uint_eq(next, 0x80000000);
|
|
|
|
/* Not aligned: should round up */
|
|
ret = pci_enum_next_aligned32(0x80000001, &next, 0x1000, 0xFFFFFFFF);
|
|
ck_assert_int_eq(ret, 0);
|
|
ck_assert_uint_eq(next, 0x80001000);
|
|
|
|
/* Aligned result just below limit: should succeed */
|
|
ret = pci_enum_next_aligned32(0x80000000, &next, 0x1000, 0x80001000);
|
|
ck_assert_int_eq(ret, 0);
|
|
ck_assert_uint_eq(next, 0x80000000);
|
|
|
|
/* addr >= limit: aligned address equals limit exactly */
|
|
next = 0xDEAD;
|
|
ret = pci_enum_next_aligned32(0x80000000, &next, 0x1000, 0x80000000);
|
|
ck_assert_int_ne(ret, 0);
|
|
ck_assert_uint_eq(next, 0xDEAD); /* *next unchanged on failure */
|
|
|
|
/* addr >= limit: alignment pushes past limit */
|
|
next = 0xDEAD;
|
|
ret = pci_enum_next_aligned32(0x80000001, &next, 0x100000, 0x80100000);
|
|
ck_assert_int_ne(ret, 0);
|
|
ck_assert_uint_eq(next, 0xDEAD);
|
|
|
|
/* addr > 0xFFFFFFFF: alignment overshoots 32-bit range
|
|
* On 64-bit host: (0xFFFFF001 + 0xFFF) & ~0xFFF = 0x100000000
|
|
* which is > 0xFFFFFFFF */
|
|
next = 0xDEAD;
|
|
ret = pci_enum_next_aligned32(0xFFFFF001, &next, 0x1000, 0xFFFFFFFF);
|
|
ck_assert_int_ne(ret, 0);
|
|
ck_assert_uint_eq(next, 0xDEAD);
|
|
|
|
/* Larger alignment near top of address space */
|
|
next = 0xDEAD;
|
|
ret = pci_enum_next_aligned32(0xFF000001, &next, 0x1000000, 0xFFFFFFFF);
|
|
ck_assert_int_ne(ret, 0);
|
|
ck_assert_uint_eq(next, 0xDEAD);
|
|
|
|
/* address = 0xFFFFFFFF with any alignment: always fails */
|
|
next = 0xDEAD;
|
|
ret = pci_enum_next_aligned32(0xFFFFFFFF, &next, 0x1000, 0xFFFFFFFF);
|
|
ck_assert_int_ne(ret, 0);
|
|
ck_assert_uint_eq(next, 0xDEAD);
|
|
|
|
/* address = 0, align = 0x1000: trivial success */
|
|
ret = pci_enum_next_aligned32(0x0, &next, 0x1000, 0xFFFFFFFF);
|
|
ck_assert_int_eq(ret, 0);
|
|
ck_assert_uint_eq(next, 0x0);
|
|
|
|
/* Large alignment, address well below: rounds up correctly */
|
|
ret = pci_enum_next_aligned32(0x00100001, &next, 0x100000, 0xFFFFFFFF);
|
|
ck_assert_int_eq(ret, 0);
|
|
ck_assert_uint_eq(next, 0x00200000);
|
|
|
|
/* Exact top of 32-bit range that's still valid:
|
|
* 0xFFFFF000 aligned to 0x1000 with limit > that → succeeds */
|
|
ret = pci_enum_next_aligned32(0xFFFFF000, &next, 0x1000, 0xFFFFFFFF);
|
|
ck_assert_int_eq(ret, 0);
|
|
ck_assert_uint_eq(next, 0xFFFFF000);
|
|
}
|
|
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;
|
|
int ret;
|
|
|
|
/* Normal case: already aligned */
|
|
ret = pci_align_check_up(0x80000000, 0x100000, 0x90000000, &aligned);
|
|
ck_assert_int_eq(ret, 0);
|
|
ck_assert_uint_eq(aligned, 0x80000000);
|
|
|
|
/* Normal case: needs alignment */
|
|
ret = pci_align_check_up(0x80000001, 0x100000, 0x90000000, &aligned);
|
|
ck_assert_int_eq(ret, 0);
|
|
ck_assert_uint_eq(aligned, 0x80100000);
|
|
|
|
/* At limit: aligned == limit → fail */
|
|
ret = pci_align_check_up(0x80000000, 0x100000, 0x80000000, &aligned);
|
|
ck_assert_int_ne(ret, 0);
|
|
|
|
/* Exceeds limit after alignment */
|
|
ret = pci_align_check_up(0x80000001, 0x100000, 0x80100000, &aligned);
|
|
ck_assert_int_ne(ret, 0);
|
|
|
|
/* Overflow: address near 0xFFFFFFFF, align_up wraps to 0 */
|
|
ret = pci_align_check_up(0xFFF00001, 0x100000, 0xFFFFFFFF, &aligned);
|
|
ck_assert_int_ne(ret, 0);
|
|
|
|
/* Overflow: address is 0xFFFFFFFF */
|
|
ret = pci_align_check_up(0xFFFFFFFF, 0x1000, 0xFFFFFFFF, &aligned);
|
|
ck_assert_int_ne(ret, 0);
|
|
|
|
/* Just below limit: should succeed */
|
|
ret = pci_align_check_up(0x80000000, 0x100000, 0x80000001, &aligned);
|
|
ck_assert_int_eq(ret, 0);
|
|
ck_assert_uint_eq(aligned, 0x80000000);
|
|
}
|
|
END_TEST
|
|
|
|
/*
|
|
* Suite registration
|
|
*/
|
|
|
|
Suite *wolfboot_suite(void)
|
|
{
|
|
Suite *s = suite_create("wolfboot-pci");
|
|
|
|
TCase *tc_build = tcase_create("topology-build");
|
|
tcase_add_test(tc_build, test_topology_build);
|
|
suite_add_tcase(s, tc_build);
|
|
|
|
TCase *tc_commit = tcase_create("topology-commit");
|
|
tcase_add_test(tc_commit, test_topology_commit);
|
|
suite_add_tcase(s, tc_commit);
|
|
|
|
TCase *tc_find_root = tcase_create("find-node-root-bus");
|
|
tcase_add_test(tc_find_root, test_find_node_root_bus);
|
|
suite_add_tcase(s, tc_find_root);
|
|
|
|
TCase *tc_find_bridge = tcase_create("find-node-behind-bridge");
|
|
tcase_add_test(tc_find_bridge, test_find_node_behind_bridge);
|
|
suite_add_tcase(s, tc_find_bridge);
|
|
|
|
TCase *tc_find_nested = tcase_create("find-node-nested-bridges");
|
|
tcase_add_test(tc_find_nested, test_find_node_nested_bridges);
|
|
suite_add_tcase(s, tc_find_nested);
|
|
|
|
TCase *tc_bar_mask = tcase_create("bar-probe-mask");
|
|
tcase_add_test(tc_bar_mask, test_bar_probe_mask);
|
|
suite_add_tcase(s, tc_bar_mask);
|
|
|
|
TCase *tc_mock_probe = tcase_create("mmio-mock-bar-probe");
|
|
tcase_add_test(tc_mock_probe, test_mmio_mock_bar_probe);
|
|
suite_add_tcase(s, tc_mock_probe);
|
|
|
|
TCase *tc_mock_unreach = tcase_create("mmio-mock-unreachable");
|
|
tcase_add_test(tc_mock_unreach, test_mmio_mock_unreachable);
|
|
suite_add_tcase(s, tc_mock_unreach);
|
|
|
|
TCase *tc_bar64 = tcase_create("program-bar-64bit");
|
|
tcase_add_test(tc_bar64, test_pci_program_bar_64bit);
|
|
suite_add_tcase(s, tc_bar64);
|
|
|
|
TCase *tc_bar_restore = tcase_create("program-bar-restore");
|
|
tcase_add_test(tc_bar_restore, test_pci_program_bar_restore);
|
|
suite_add_tcase(s, tc_bar_restore);
|
|
|
|
TCase *tc_bar_types = tcase_create("program-bar-types");
|
|
tcase_add_test(tc_bar_types, test_program_bar_types);
|
|
suite_add_tcase(s, tc_bar_types);
|
|
|
|
TCase *tc_bar_oor = tcase_create("program-bar-out-of-range");
|
|
tcase_add_test(tc_bar_oor, test_program_bar_out_of_range);
|
|
suite_add_tcase(s, tc_bar_oor);
|
|
|
|
TCase *tc_bar_ureject = tcase_create("program-bar-64bit-upper-reject");
|
|
tcase_add_test(tc_bar_ureject, test_program_bar_64bit_upper_reject);
|
|
suite_add_tcase(s, tc_bar_ureject);
|
|
|
|
TCase *tc_bar_nospace = tcase_create("program-bar-no-space");
|
|
tcase_add_test(tc_bar_nospace, test_program_bar_no_space);
|
|
suite_add_tcase(s, tc_bar_nospace);
|
|
|
|
TCase *tc_bars_iter = tcase_create("program-bars-iteration");
|
|
tcase_add_test(tc_bars_iter, test_program_bars_iteration);
|
|
suite_add_tcase(s, tc_bars_iter);
|
|
|
|
TCase *tc_bar_zalign = tcase_create("program-bar-zero-align");
|
|
tcase_add_test(tc_bar_zalign, test_program_bar_zero_align);
|
|
suite_add_tcase(s, tc_bar_zalign);
|
|
|
|
TCase *tc_bridge = tcase_create("program-bridge");
|
|
tcase_add_test(tc_bridge, test_program_bridge);
|
|
suite_add_tcase(s, tc_bridge);
|
|
|
|
TCase *tc_io_64k = tcase_create("bridge-io-64k-no-narrow");
|
|
tcase_add_test(tc_io_64k, test_program_bridge_io_64k_no_narrow);
|
|
suite_add_tcase(s, tc_io_64k);
|
|
|
|
TCase *tc_oom_init = tcase_create("bridge-oom-initial");
|
|
tcase_add_test(tc_oom_init, test_program_bridge_oom_initial);
|
|
suite_add_tcase(s, tc_oom_init);
|
|
|
|
TCase *tc_oom_post = tcase_create("bridge-oom-post-enum");
|
|
tcase_add_test(tc_oom_post, test_program_bridge_oom_post_enum);
|
|
suite_add_tcase(s, tc_oom_post);
|
|
|
|
TCase *tc_enum_topo = tcase_create("enum-bus-topology");
|
|
tcase_add_test(tc_enum_topo, test_enum_bus_topology);
|
|
suite_add_tcase(s, tc_enum_topo);
|
|
|
|
TCase *tc_enum_do = tcase_create("enum-do-full");
|
|
tcase_add_test(tc_enum_do, test_enum_do_full);
|
|
suite_add_tcase(s, tc_enum_do);
|
|
|
|
TCase *tc_enum_nested = tcase_create("enum-do-nested-bridges");
|
|
tcase_add_test(tc_enum_nested, test_enum_do_nested_bridges);
|
|
suite_add_tcase(s, tc_enum_nested);
|
|
|
|
TCase *tc_rw8 = tcase_create("config-rw-8bit-positions");
|
|
tcase_add_test(tc_rw8, test_config_rw_8bit_all_positions);
|
|
suite_add_tcase(s, tc_rw8);
|
|
|
|
TCase *tc_align = tcase_create("enum-next-aligned-overflow");
|
|
tcase_add_test(tc_align, test_enum_next_aligned_overflow);
|
|
suite_add_tcase(s, tc_align);
|
|
|
|
TCase *tc_align_check = tcase_create("align-check-up-overflow");
|
|
tcase_add_test(tc_align_check, test_pci_align_check_up_overflow);
|
|
suite_add_tcase(s, tc_align_check);
|
|
|
|
return s;
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
int fails;
|
|
Suite *s = wolfboot_suite();
|
|
SRunner *sr = srunner_create(s);
|
|
srunner_run_all(sr, CK_NORMAL);
|
|
fails = srunner_ntests_failed(sr);
|
|
srunner_free(sr);
|
|
return fails;
|
|
}
|