[PATCH 05/10] PCI: Add PCI_TEST module for resource allocation

From: Yinghai Lu
Date: Sat Aug 05 2017 - 02:38:44 EST


Read from data file and mask file, to build simulated data structure, and
have pci_ops to use them.

Extract calling for pci_create_root_bus, scan_child_bus, resource survey
and resource assign ... to see if those functions work as expected with
simulated data.

mask is with rw bits on pci registers, so we can make pci BAR sizing working.

It also support bus number assign-all.

Only tested on x86 64bit arch.

Signed-off-by: Yinghai Lu <yinghai@xxxxxxxxxx>
---
drivers/pci/Kconfig | 6 +
drivers/pci/Makefile | 2 +
drivers/pci/pci_test.c | 1281 ++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 1289 insertions(+)
create mode 100644 drivers/pci/pci_test.c

diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig
index c32a77f..3183331 100644
--- a/drivers/pci/Kconfig
+++ b/drivers/pci/Kconfig
@@ -134,6 +134,12 @@ config PCI_HYPERV
The PCI device frontend driver allows the kernel to import arbitrary
PCI devices from a PCI backend to support PCI driver domains.

+config PCI_TEST
+ tristate "PCI test"
+ depends on PCI && X86_64
+ help
+ PCI test module to verify PCI resource allocation with simulated data
+
source "drivers/pci/hotplug/Kconfig"
source "drivers/pci/dwc/Kconfig"
source "drivers/pci/host/Kconfig"
diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile
index 66a21ac..043b979 100644
--- a/drivers/pci/Makefile
+++ b/drivers/pci/Makefile
@@ -51,6 +51,8 @@ obj-$(CONFIG_XEN_PCIDEV_FRONTEND) += xen-pcifront.o

obj-$(CONFIG_OF) += of.o

+obj-$(CONFIG_PCI_TEST) += pci_test.o
+
ccflags-$(CONFIG_PCI_DEBUG) := -DDEBUG

# PCI host controller drivers
diff --git a/drivers/pci/pci_test.c b/drivers/pci/pci_test.c
new file mode 100644
index 0000000..02ab2d2
--- /dev/null
+++ b/drivers/pci/pci_test.c
@@ -0,0 +1,1281 @@
+/*
+ * drivers/pci/pci_test.c
+ * by Yinghai Lu <yinghai@xxxxxxxxxx>
+ *
+ * # insmod pci_test.ko data_file=pci_test_data.txt mask_file=pci_test_mask.txt
+ * # lspci -tv
+ * # cat /proc/ioports_test
+ * # cat /proc/iomem_test
+ * # rmmod pci_test
+ *
+ * data_file: is root bus resource from boot log + "lspci -vvxxxx"
+ * mask_file: modified from "lspci -vvxxxx" with mask head.
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/errno.h>
+#include <linux/ioport.h>
+#include <linux/cache.h>
+#include <linux/slab.h>
+#include <linux/ctype.h>
+#include <linux/fs.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+
+struct test_pci_root {
+ struct list_head node;
+ u16 segment;
+ u8 busnumber;
+ struct resource secondary;
+ struct resource c_pri; /* holder for child primary */
+ struct list_head root_res_list;
+ struct resource secondary_os;
+ struct pci_bus *bus;
+};
+
+struct test_pci_dev {
+ struct list_head node;
+ u32 mbdf;
+ int space_size; /* 256 or 4096 */
+ unsigned char *reg;
+
+ int mask_size;
+ unsigned char *mask;
+
+ int invisiable;
+ struct test_pci_dev *parent; /* device on root bus has no parent */
+ struct list_head dev_list; /* node in children devices */
+ int bridge;
+ struct resource secondary;
+ struct resource c_pri; /* holder for child primary */
+ struct list_head children; /* bridge to point to children devices */
+};
+
+struct test_pci_mask {
+ struct list_head node;
+ u32 vendor_device_id;
+ int space_size; /* 256 or 4096 */
+ unsigned char *reg; /* for mask: bit set means rw */
+};
+
+static LIST_HEAD(test_pci_root_list);
+static LIST_HEAD(test_pci_dev_list);
+static LIST_HEAD(test_pci_mask_list);
+
+static struct test_pci_root *test_pci_get_root(int mn, int bn)
+{
+ struct test_pci_root *root;
+
+ list_for_each_entry(root, &test_pci_root_list, node)
+ if (root->segment == (mn & 0xffff) &&
+ root->busnumber == (bn & 0xff))
+ return root;
+
+ return NULL;
+}
+
+static struct test_pci_root *test_pci_add_root(int mn, int bn, int bn1, int bn2)
+{
+ struct test_pci_root *root;
+
+ if (bn > bn1 || bn1 > bn2)
+ return NULL;
+
+ root = test_pci_get_root(mn, bn);
+ if (root) /* bus come last ? */
+ goto set_secondary;
+
+ root = kzalloc(sizeof(struct test_pci_root), GFP_KERNEL);
+ if (!root)
+ return NULL;
+
+ INIT_LIST_HEAD(&root->root_res_list);
+ list_add_tail(&root->node, &test_pci_root_list);
+
+ root->segment = mn & 0xffff;
+ root->busnumber = bn & 0xff;
+
+set_secondary:
+ if (root->c_pri.parent)
+ release_resource(&root->c_pri);
+ root->secondary.flags = IORESOURCE_BUS;
+ root->secondary.start = bn1 & 0xff;
+ root->secondary.end = bn2 & 0xff;
+ root->c_pri.flags = IORESOURCE_BUS;
+ root->c_pri.start = root->secondary.start;
+ root->c_pri.end = root->secondary.start;
+ request_resource(&root->secondary, &root->c_pri);
+
+ /* for os */
+ root->secondary_os.flags = IORESOURCE_BUS;
+ root->secondary_os.start = bn1 & 0xff;
+ root->secondary_os.end = bn2 & 0xff;
+
+ return root;
+}
+
+static int test_pci_add_root_res(int mn, int bn,
+ resource_size_t start, resource_size_t end,
+ int flags, resource_size_t offset)
+{
+ struct resource_entry *entry;
+ struct test_pci_root *root;
+
+ root = test_pci_get_root(mn, bn);
+ if (!root) {
+ root = test_pci_add_root(mn, bn, bn, bn);
+ if (!root)
+ return -ENOMEM;
+ }
+
+ entry = resource_list_create_entry(NULL, 0);
+ if (!entry)
+ return -ENOMEM;
+
+ entry->res->start = start;
+ entry->res->end = end;
+ entry->res->flags = flags;
+ entry->offset = offset;
+ resource_list_add_tail(entry, &root->root_res_list);
+
+ return 0;
+}
+
+static void test_pci_free_all_root_res(struct list_head *head)
+{
+ struct resource_entry *entry, *tmp;
+
+ resource_list_for_each_entry_safe(entry, tmp, head)
+ resource_list_destroy_entry(entry);
+}
+
+#define MBDF(m, b, d, f) ((((m)&0xffff)<<16) | (((b)&0xff)<<8) | \
+ (((d)&0x1f)<<3) | ((f)&7))
+
+static struct test_pci_dev *test_pci_add_dev(int mn, int bn,
+ int dn, int fn, int space_size, int bn1, int bn2)
+{
+ u32 mbdf = MBDF(mn, bn, dn, fn);
+ struct test_pci_dev *dev;
+
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ if (!dev)
+ return NULL;
+
+ list_add_tail(&dev->node, &test_pci_dev_list);
+
+ dev->reg = kzalloc(space_size, GFP_KERNEL);
+ if (!dev->reg)
+ return NULL;
+
+ dev->mbdf = mbdf;
+ dev->space_size = space_size;
+
+ if (bn1 != -1) {
+ dev->secondary.flags = IORESOURCE_BUS;
+ dev->secondary.start = bn1;
+ dev->secondary.end = bn2;
+ dev->c_pri.flags = IORESOURCE_BUS;
+ dev->c_pri.start = bn1;
+ dev->c_pri.end = bn1;
+ }
+
+ return dev;
+}
+
+static int test_pci_relocate_dev_reg(struct test_pci_dev *dev,
+ int space_size)
+{
+ unsigned char *reg;
+
+ if (dev->space_size >= space_size)
+ return 0;
+
+ reg = kzalloc(space_size, GFP_KERNEL);
+ if (!reg)
+ return -ENOMEM;
+
+ memcpy(reg, dev->reg, dev->space_size);
+ kfree(dev->reg);
+ dev->space_size = space_size;
+ dev->reg = reg;
+
+ return 0;
+}
+
+static void test_pci_free_dev(void)
+{
+ struct test_pci_dev *dev, *tmp;
+
+ list_for_each_entry_safe(dev, tmp, &test_pci_dev_list, node) {
+ list_del(&dev->node);
+ kfree(dev->reg);
+ kfree(dev);
+ }
+}
+
+static struct test_pci_mask *test_pci_add_mask(int vendor_id,
+ int device_id, int space_size)
+{
+ u32 vendor_device_id = vendor_id | (device_id<<16);
+ struct test_pci_mask *mask;
+
+ mask = kzalloc(sizeof(*mask), GFP_KERNEL);
+ if (!mask)
+ return NULL;
+
+ list_add_tail(&mask->node, &test_pci_mask_list);
+
+ mask->reg = kzalloc(space_size, GFP_KERNEL);
+ if (!mask->reg)
+ return NULL;
+
+ mask->vendor_device_id = vendor_device_id;
+ mask->space_size = space_size;
+
+ return mask;
+}
+
+static int test_pci_relocate_mask_reg(struct test_pci_mask *mask,
+ int space_size)
+{
+ unsigned char *reg;
+
+ if (mask->space_size >= space_size)
+ return 0;
+
+ reg = kzalloc(space_size, GFP_KERNEL);
+ if (!reg)
+ return -ENOMEM;
+
+ memcpy(reg, mask->reg, mask->space_size);
+ kfree(mask->reg);
+ mask->space_size = space_size;
+ mask->reg = reg;
+
+ return 0;
+}
+
+static void test_pci_free_mask(void)
+{
+ struct test_pci_mask *mask, *tmp;
+
+ list_for_each_entry_safe(mask, tmp, &test_pci_mask_list, node) {
+ list_del(&mask->node);
+ kfree(mask->reg);
+ kfree(mask);
+ }
+}
+
+static void test_pci_free(void)
+{
+ struct test_pci_root *root, *tmp;
+
+ list_for_each_entry_safe(root, tmp, &test_pci_root_list, node) {
+ test_pci_free_all_root_res(&root->root_res_list);
+ kfree(root);
+ }
+
+ test_pci_free_dev();
+ test_pci_free_mask();
+}
+
+/* dump_validate and dump_init are adapted from pci_utils lib/dump.c */
+static int dump_validate(char *s, char *fmt)
+{
+ while (*fmt) {
+ if (*fmt == '#' ? !isxdigit(*s) : *fmt != *s)
+ return 0;
+ fmt++, s++;
+ }
+
+ return 1;
+}
+
+static int dump_init(const char *name, int mn_offset)
+{
+ struct file *f;
+ char buf[256];
+ struct test_pci_dev *dev = NULL;
+ int len, mn, bn, dn, fn, i, j;
+ int seg, bn1, bn2;
+ unsigned long rstart, rend, bstart, bend, offset;
+ int good;
+ loff_t pos = 0;
+
+ f = filp_open(name, O_RDONLY, 0);
+ if (!f) {
+ pr_err("pci_test: Cannot open %s", name);
+ return -ENOENT;
+ }
+
+ while (kernel_read(f, pos, buf, sizeof(buf)-1) > 0) {
+ char *z = strchr(buf, '\n');
+
+ if (!z) {
+ filp_close(f, 0);
+ pr_err("pci_test: line too long or unterminated");
+ return -EINVAL;
+ }
+ pos += z - buf + 1;
+
+ *z-- = 0;
+ if (z >= buf && *z == '\r')
+ *z-- = 0;
+ len = z - buf + 1;
+ mn = 0;
+ if (dump_validate(buf, "pci_bus ####:##: root bus resource [bus ##-##]") &&
+ sscanf(buf, "pci_bus %x:%x: root bus resource [bus %x-%x]",
+ &seg, &bn, &bn1, &bn2) == 4) {
+ if (!test_pci_add_root(seg + mn_offset, bn, bn1, bn2))
+ return -ENOMEM;
+ } else if (dump_validate(buf, "pci_bus ####:##: root bus resource [bus ##]") &&
+ sscanf(buf, "pci_bus %x:%x: root bus resource [bus %x]",
+ &seg, &bn, &bn1) == 3) {
+ if (!test_pci_add_root(seg + mn_offset, bn, bn1, bn1))
+ return -ENOMEM;
+ } else if (dump_validate(buf, "pci_bus ####:##: root bus resource [io")) {
+ offset = 0;
+ good = 0;
+ if (strstr(buf, "bus address") &&
+ sscanf(buf, "pci_bus %x:%x: root bus resource [io 0x%lx-0x%lx] (bus address [0x%lx-0x%lx])",
+ &seg, &bn, &rstart, &rend, &bstart, &bend) == 6) {
+ offset = rstart - bstart;
+ good = 1;
+ } else if (sscanf(buf, "pci_bus %x:%x: root bus resource [io 0x%lx-0x%lx]",
+ &seg, &bn, &rstart, &rend) == 4)
+ good = 1;
+
+ if (good && test_pci_add_root_res(seg + mn_offset,
+ bn, rstart, rend,
+ IORESOURCE_IO, offset))
+ return -ENOMEM;
+ } else if (dump_validate(buf, "pci_bus ####:##: root bus resource [mem")) {
+ offset = 0;
+ good = 0;
+ if (strstr(buf, "bus address") &&
+ sscanf(buf, "pci_bus %x:%x: root bus resource [mem 0x%lx-0x%lx] (bus address [0x%lx-0x%lx])",
+ &seg, &bn, &rstart, &rend, &bstart, &bend) == 6) {
+ offset = rstart - bstart;
+ good = 1;
+ } else if (sscanf(buf, "pci_bus %x:%x: root bus resource [mem 0x%lx-0x%lx]",
+ &seg, &bn, &rstart, &rend) == 4)
+ good = 1;
+
+ if (good && test_pci_add_root_res(seg + mn_offset,
+ bn, rstart, rend,
+ IORESOURCE_MEM, offset))
+ return -ENOMEM;
+ } else if ((dump_validate(buf, "##:##.# [##-##]") &&
+ sscanf(buf, "%x:%x.%d [%x-%x]", &bn, &dn, &fn, &bn1, &bn2) == 5) ||
+ (dump_validate(buf, "####:##:##.# [##-##]") &&
+ sscanf(buf, "%x:%x:%x.%d [%x-%x]", &mn, &bn, &dn, &fn, &bn1, &bn2) == 6)) {
+ dev = test_pci_add_dev(mn + mn_offset, bn,
+ dn, fn, 256, bn1, bn2);
+ if (!dev)
+ return -ENOMEM;
+ } else if ((dump_validate(buf, "##:##.# ") &&
+ sscanf(buf, "%x:%x.%d", &bn, &dn, &fn) == 3) ||
+ (dump_validate(buf, "####:##:##.# ") &&
+ sscanf(buf, "%x:%x:%x.%d", &mn, &bn, &dn, &fn) == 4)) {
+ dev = test_pci_add_dev(mn + mn_offset, bn,
+ dn, fn, 256, -1, -1);
+ if (!dev)
+ return -ENOMEM;
+ } else if (!len) {
+ dev = NULL;
+ } else if (dev &&
+ (dump_validate(buf, "##: ") ||
+ dump_validate(buf, "###: ")) &&
+ sscanf(buf, "%x: ", &i) == 1) {
+ z = strchr(buf, ' ') + 1;
+ while (isxdigit(z[0]) && isxdigit(z[1]) &&
+ (!z[2] || z[2] == ' ') &&
+ sscanf(z, "%x", &j) == 1 && j < 256) {
+ if (i >= 4096) {
+ filp_close(f, 0);
+ pr_err("pci_test: At most 4096 bytes of config space are supported");
+ }
+ if (i >= dev->space_size)
+ test_pci_relocate_dev_reg(dev, 4096);
+ dev->reg[i++] = j;
+ z += 2;
+ if (*z)
+ z++;
+ }
+
+ if (*z) {
+ filp_close(f, 0);
+ pr_err("pci_test: Malformed line");
+ return -EINVAL;
+ }
+ }
+ }
+
+ filp_close(f, 0);
+
+ return 0;
+}
+
+static int mask_dump_init(const char *name)
+{
+ struct file *f;
+ char buf[256];
+ struct test_pci_mask *mask = NULL;
+ int len, vendor_id, device_id, i, j;
+ loff_t pos = 0;
+
+ f = filp_open(name, O_RDONLY, 0);
+ if (!f) {
+ pr_err("pci_test: Cannot open %s", name);
+ return -ENOENT;
+ }
+
+ while (kernel_read(f, pos, buf, sizeof(buf)-1) > 0) {
+ char *z = strchr(buf, '\n');
+
+ if (!z) {
+ filp_close(f, 0);
+ pr_err("pci_test: line too long or unterminated");
+ return -EINVAL;
+ }
+ pos += z - buf + 1;
+
+ *z-- = 0;
+ if (z >= buf && *z == '\r')
+ *z-- = 0;
+ len = z - buf + 1;
+ if (dump_validate(buf, "mask: ####:####") &&
+ sscanf(buf, "mask: %x:%x", &vendor_id, &device_id) == 2) {
+ mask = test_pci_add_mask(vendor_id,
+ device_id, 256);
+ if (!mask)
+ return -ENOMEM;
+ } else if (!len) {
+ mask = NULL;
+ } else if (mask &&
+ (dump_validate(buf, "##: ") ||
+ dump_validate(buf, "###: ")) &&
+ sscanf(buf, "%x: ", &i) == 1) {
+ z = strchr(buf, ' ') + 1;
+ while (isxdigit(z[0]) && isxdigit(z[1]) &&
+ (!z[2] || z[2] == ' ') &&
+ sscanf(z, "%x", &j) == 1 && j < 256) {
+ if (i >= 4096) {
+ filp_close(f, 0);
+ pr_err("pci_test: At most 4096 bytes of config space are supported");
+ }
+ if (i >= mask->space_size)
+ test_pci_relocate_mask_reg(mask, 4096);
+ mask->reg[i++] = j;
+ z += 2;
+ if (*z)
+ z++;
+ }
+
+ if (*z) {
+ filp_close(f, 0);
+ pr_err("pci_test: Malformed line");
+ return -EINVAL;
+ }
+ }
+ }
+
+ filp_close(f, 0);
+
+ return 0;
+}
+
+#define MASK_WARN_NUM 512
+static u32 mask_warn[MASK_WARN_NUM];
+static void warn_mask_once(int vendor_id, int device_id)
+{
+ int i;
+ u32 mask = vendor_id | (device_id << 16);
+
+ for (i = 0; i < MASK_WARN_NUM; i++) {
+ if (mask_warn[i] == mask)
+ return;
+
+ if (!mask_warn[i]) {
+ mask_warn[i] = mask;
+ break;
+ }
+ }
+
+ pr_warn("pci_test: no mask for %04x:%04x\n", vendor_id, device_id);
+}
+
+static struct test_pci_mask *test_pci_get_mask(int vendor_id, int device_id)
+{
+ int vendor_device_id = vendor_id | (device_id << 16);
+ struct test_pci_mask *mask;
+
+ list_for_each_entry(mask, &test_pci_mask_list, node)
+ if (vendor_device_id == mask->vendor_device_id)
+ return mask;
+
+ return NULL;
+}
+
+static unsigned char bridge_mask[0x40] = {
+/*00:*/ 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x10, 0x00,
+ 0x04, 0x00, 0x04, 0x06, 0x10, 0x00, 0x81, 0x00,
+/*10:*/ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xff, 0xff, 0x00, 0xf0, 0xf0, 0x00, 0x20,
+/*20:*/ 0xf0, 0xff, 0xf0, 0xff, 0xf0, 0xff, 0xf0, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+/*30:*/ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x0b, 0x01, 0x13, 0x00,
+};
+
+static void test_pci_probe_mask(void)
+{
+ struct test_pci_dev *dev;
+
+ list_for_each_entry(dev, &test_pci_dev_list, node) {
+ unsigned char *reg = dev->reg;
+ u16 vendor_id = reg[0] | (reg[1] << 8);
+ u16 device_id = reg[2] | (reg[3] << 8);
+ struct test_pci_mask *mask;
+
+ mask = test_pci_get_mask(vendor_id, device_id);
+ if (!mask) {
+ u16 class = (reg[0xb] << 8) | reg[0xa];
+
+ warn_mask_once(vendor_id, device_id);
+ if (class == PCI_CLASS_BRIDGE_PCI) {
+ pr_warn("pci_test: use default bridge mask for %04x:%04x\n",
+ vendor_id, device_id);
+ dev->mask = bridge_mask;
+ dev->mask_size = ARRAY_SIZE(bridge_mask);
+ }
+ } else {
+ dev->mask = mask->reg;
+ dev->mask_size = mask->space_size;
+ }
+ }
+}
+
+static void test_pci_update_secondary_from_reg(struct test_pci_dev *parent)
+{
+ unsigned char *reg = parent->reg;
+
+ parent->secondary.flags = IORESOURCE_BUS;
+ parent->secondary.start = reg[0x19];
+ parent->secondary.end = reg[0x1a];
+ parent->c_pri.flags = IORESOURCE_BUS;
+ parent->c_pri.start = parent->c_pri.end = reg[0x19];
+}
+
+static void test_pci_link_devices(void)
+{
+ struct test_pci_dev *dev, *parent;
+
+ /* set mask and find out all bridges */
+ list_for_each_entry(dev, &test_pci_dev_list, node) {
+ unsigned char *reg = dev->reg;
+ u16 class = (reg[0xb] << 8) | reg[0xa];
+
+ if (class != PCI_CLASS_BRIDGE_PCI)
+ continue;
+
+ INIT_LIST_HEAD(&dev->children);
+ dev->bridge = 1;
+
+ /* if bus range is not passed from ##:##.0 [##-##] */
+ if (!dev->secondary.flags)
+ test_pci_update_secondary_from_reg(dev);
+ }
+
+ /* link children to parent */
+ list_for_each_entry(dev, &test_pci_dev_list, node) {
+ int mn = (dev->mbdf >> 16) & 0xffff;
+ int bn = (dev->mbdf >> 8) & 0xff;
+
+ /* check if it is on root bus */
+ if (test_pci_get_root(mn, bn))
+ continue;
+
+ /* find parent for it */
+ list_for_each_entry(parent, &test_pci_dev_list, node) {
+ int parent_mn;
+
+ if (!parent->bridge)
+ continue;
+
+ parent_mn = (parent->mbdf >> 16) & 0xffff;
+ if (parent_mn != mn)
+ continue;
+
+ if (bn != parent->secondary.start)
+ continue;
+
+ /* it is */
+ list_add_tail(&dev->dev_list, &parent->children);
+ dev->parent = parent;
+ break;
+ }
+
+ if (!dev->parent) {
+ int dn = (dev->mbdf >> 3) & 0x1f;
+ int fn = dev->mbdf & 0x07;
+
+ pr_warn("pci_test: no parent for %04x:%02x:%02x.%1x\n",
+ mn, bn, dn, fn);
+ }
+ }
+}
+
+static int pci_test_request_bus(struct resource *parent_res,
+ struct resource *child_res)
+{
+ int ret;
+
+ ret = request_resource(parent_res, child_res);
+ if (!ret)
+ return 0;
+
+ /* child_res could have 0xff for probing, so shrink it to parent's */
+ if (child_res->end != 0xff || child_res->start > parent_res->end)
+ return ret;
+
+ child_res->end = parent_res->end;
+ ret = request_resource(parent_res, child_res);
+ if (!ret)
+ return 0;
+
+ child_res->end = 0xff;
+
+ return ret;
+}
+
+static void test_pci_update_invisiable_bridge(struct test_pci_dev *parent)
+{
+ struct test_pci_dev *child;
+ int invisiable = 1;
+
+ if (parent->c_pri.parent)
+ invisiable = 0;
+
+ list_for_each_entry(child, &parent->children, dev_list) {
+ child->invisiable = invisiable;
+
+ if (!child->bridge)
+ continue;
+
+ if (!invisiable &&
+ !pci_test_request_bus(&parent->secondary,
+ &child->secondary))
+ request_resource(&child->secondary, &child->c_pri);
+
+ test_pci_update_invisiable_bridge(child);
+ }
+}
+
+static void test_pci_update_invisiable(void)
+{
+ struct test_pci_dev *parent;
+
+ /* read bus info from reg */
+ list_for_each_entry(parent, &test_pci_dev_list, node) {
+ if (!parent->bridge)
+ continue;
+
+ test_pci_update_secondary_from_reg(parent);
+ }
+
+ /* if can not allocate bridge secondary properly, then invisiable */
+ list_for_each_entry(parent, &test_pci_dev_list, node) {
+ int mn, bn;
+ struct test_pci_root *root;
+
+ if (!parent->bridge)
+ continue;
+
+ mn = (parent->mbdf >> 16) & 0xffff;
+ bn = (parent->mbdf >> 8) & 0xff;
+ root = test_pci_get_root(mn, bn);
+ if (!root)
+ continue;
+
+ if (!pci_test_request_bus(&root->secondary, &parent->secondary))
+ request_resource(&parent->secondary, &parent->c_pri);
+
+ test_pci_update_invisiable_bridge(parent);
+ }
+}
+
+static void test_pci_update_bus(struct test_pci_dev *parent, int b_19_orig,
+ int b_1a_orig, int b_19, int b_1a)
+{
+ struct test_pci_dev *child;
+ struct resource *parent_res;
+
+ if (!parent->bridge)
+ return;
+
+ if ((b_19_orig == b_19) && (b_1a_orig == b_1a))
+ return;
+
+ {
+ int mn = (parent->mbdf >> 16) & 0xffff;
+ int bn = (parent->mbdf >> 8) & 0xff;
+ int dn = (parent->mbdf >> 3) & 0x1f;
+ int fn = parent->mbdf & 0x07;
+
+ pr_warn("pci_test: %04x:%02x:%02x.%1x update bus [%02x-%02x] ==> [%02x-%02x]\n",
+ mn, bn, dn, fn, b_19_orig, b_1a_orig, b_19, b_1a);
+ }
+
+ parent_res = parent->secondary.parent;
+ if (parent_res) {
+ release_child_resources(&parent->secondary);
+ release_resource(&parent->secondary);
+ } else {
+ /* bridge on root bus */
+ struct test_pci_root *root;
+ int mn = (parent->mbdf >> 16) & 0xffff;
+ int bn = (parent->mbdf >> 8) & 0xff;
+
+ root = test_pci_get_root(mn, bn);
+ if (!root)
+ return;
+
+ parent_res = &root->secondary;
+ }
+
+ list_for_each_entry(child, &parent->children, dev_list) {
+ child->mbdf &= 0xffff00ff;
+ child->mbdf |= b_19 << 8;
+
+ if (child->bridge)
+ child->reg[0x18] = b_19;
+ }
+
+ test_pci_update_secondary_from_reg(parent);
+
+ if (!pci_test_request_bus(parent_res, &parent->secondary))
+ request_resource(&parent->secondary, &parent->c_pri);
+
+ test_pci_update_invisiable_bridge(parent);
+}
+
+static int test_pci_init(const char *fn, int domain_offset, const char *mask_fn)
+{
+ if (dump_init(fn, domain_offset)) {
+ test_pci_free();
+ return -EINVAL;
+ }
+
+ mask_dump_init(mask_fn);
+
+ test_pci_probe_mask();
+
+ test_pci_link_devices();
+
+ test_pci_update_invisiable();
+
+ return 0;
+}
+
+/*--------------------------------------------*/
+
+static struct test_pci_dev *test_pci_get_dev(int mn, int bn,
+ int dn, int fn)
+{
+ int mbdf = MBDF(mn, bn, dn, fn);
+ struct test_pci_dev *dev;
+
+ list_for_each_entry(dev, &test_pci_dev_list, node)
+ if (mbdf == dev->mbdf && !dev->invisiable)
+ return dev;
+
+ return NULL;
+}
+
+static int test_pci_read(struct test_pci_dev *dev,
+ int where, int size, u32 *val)
+{
+ int i;
+ unsigned char *buf = (unsigned char *)val;
+ unsigned char *reg = dev->reg;
+
+ for (i = 0; i < size; i++)
+ buf[i] = reg[where + i];
+
+ return 0;
+}
+
+static int test_pci_write(struct test_pci_dev *dev,
+ int where, int size, u32 val)
+{
+ int i;
+ unsigned char *buf = (unsigned char *)&val;
+ unsigned char *reg = dev->reg;
+ unsigned char mask;
+
+ /* bit set means: rw */
+ for (i = 0; i < size; i++) {
+ if (dev->mask && (where + i < dev->mask_size))
+ mask = dev->mask[where + i];
+ else {
+ /* just guess */
+ if (!reg[where + i])
+ mask = 0x00;
+ else
+ mask = 0xff;
+ }
+ reg[where + i] &= ~mask;
+ reg[where + i] |= buf[i] & mask;
+ }
+
+ return 0;
+}
+
+/*--------------------------------------------*/
+
+static int pci_test_read(struct pci_bus *bus, unsigned int devfn,
+ int where, int size, u32 *val)
+{
+ struct test_pci_dev *dev;
+
+ dev = test_pci_get_dev(pci_domain_nr(bus),
+ bus->number, devfn >> 3, devfn & 7);
+ if (!dev) {
+ *val = 0xffffffff; /* or 0 ? */
+
+ return -EINVAL;
+ }
+
+ return test_pci_read(dev, where, size, val);
+}
+
+static int pci_test_write(struct pci_bus *bus, unsigned int devfn,
+ int where, int size, u32 val)
+{
+ struct test_pci_dev *dev;
+ unsigned char b_19_orig, b_1a_orig, *reg;
+ int ret;
+
+ dev = test_pci_get_dev(pci_domain_nr(bus),
+ bus->number, devfn >> 3, devfn & 7);
+ if (!dev)
+ return -EINVAL;
+
+ reg = dev->reg;
+ b_19_orig = reg[0x19];
+ b_1a_orig = reg[0x1a];
+ ret = test_pci_write(dev, where, size, val);
+ test_pci_update_bus(dev, b_19_orig, b_1a_orig, reg[0x19], reg[0x1a]);
+
+ return ret;
+}
+
+static struct pci_ops pci_test_ops = {
+ .read = pci_test_read,
+ .write = pci_test_write,
+};
+
+static int pci_test_get_root_resources(struct test_pci_root *root,
+ struct list_head *list)
+{
+ struct resource_entry *src, *dst;
+
+ /* set the resource list */
+ resource_list_for_each_entry(src, &root->root_res_list) {
+ dst = resource_list_create_entry(NULL, 0);
+ if (!dst)
+ return -ENOMEM;
+
+ dst->res->start = src->res->start;
+ dst->res->end = src->res->end;
+ dst->res->flags = src->res->flags;
+ dst->offset = src->offset;
+ resource_list_add_tail(dst, list);
+ }
+
+ return 0;
+}
+
+/*--------------------------------------------*/
+static struct resource pci_test_ioport_resource = {
+ .name = "PCI IO",
+ .start = 0,
+ .end = IO_SPACE_LIMIT,
+ .flags = IORESOURCE_IO,
+};
+
+static struct resource pci_test_iomem_resource = {
+ .name = "PCI mem",
+ .start = 0,
+ .end = -1,
+ .flags = IORESOURCE_MEM,
+};
+
+struct pci_test_root_info;
+
+struct pci_test_root_ops {
+ struct pci_ops *pci_ops;
+ void (*release_info)(struct pci_test_root_info *info);
+ int (*prepare_resources)(struct pci_test_root_info *info);
+};
+
+struct pci_test_root_info {
+ struct test_pci_root *root;
+ struct pci_test_root_ops *ops;
+ struct list_head resources;
+ char name[16];
+};
+
+struct pci_root_info_ext {
+ struct pci_test_root_info common;
+ struct pci_sysdata sd;
+};
+
+static int pci_test_root_prepare_resources(struct pci_test_root_info *ci)
+{
+ int ret;
+ struct list_head *list = &ci->resources;
+ struct resource_entry *entry;
+
+ ret = pci_test_get_root_resources(ci->root, list);
+ resource_list_for_each_entry(entry, list)
+ entry->res->name = ci->name;
+
+ return ret;
+}
+
+static void pci_test_root_info_release(struct pci_test_root_info *ci)
+{
+ kfree(container_of(ci, struct pci_root_info_ext, common));
+}
+
+static struct pci_test_root_ops pci_test_root_ops = {
+ .pci_ops = &pci_test_ops,
+ .release_info = pci_test_root_info_release,
+ .prepare_resources = pci_test_root_prepare_resources,
+};
+
+static void __pci_test_root_release_info(struct pci_test_root_info *info)
+{
+ struct resource *res;
+ struct resource_entry *entry, *tmp;
+
+ if (!info)
+ return;
+
+ resource_list_for_each_entry_safe(entry, tmp, &info->resources) {
+ res = entry->res;
+ if (res->parent &&
+ (res->flags & (IORESOURCE_MEM | IORESOURCE_IO)))
+ release_resource(res);
+ resource_list_destroy_entry(entry);
+ }
+
+ info->ops->release_info(info);
+}
+
+static void pci_test_root_release_info(struct pci_host_bridge *bridge)
+{
+ struct resource *res;
+ struct resource_entry *entry;
+
+ resource_list_for_each_entry(entry, &bridge->windows) {
+ res = entry->res;
+ if (res->parent &&
+ (res->flags & (IORESOURCE_MEM | IORESOURCE_IO)))
+ release_resource(res);
+ }
+ __pci_test_root_release_info(bridge->release_data);
+}
+
+static void pci_test_root_add_resources(struct pci_test_root_info *info)
+{
+ struct resource_entry *entry, *tmp;
+ struct resource *res, *root = NULL;
+
+ resource_list_for_each_entry_safe(entry, tmp, &info->resources) {
+ res = entry->res;
+ if (res->flags & IORESOURCE_MEM)
+ root = &pci_test_iomem_resource;
+ else if (res->flags & IORESOURCE_IO)
+ root = &pci_test_ioport_resource;
+ else
+ continue;
+
+ if (res == root)
+ continue;
+
+ if (insert_resource(root, res)) {
+ pr_info("ignoring host bridge window %pR\n", res);
+ resource_list_destroy_entry(entry);
+ }
+ }
+}
+
+static struct pci_bus *pci_test_root_create(struct test_pci_root *root,
+ struct pci_test_root_ops *ops,
+ struct pci_test_root_info *info,
+ void *sysdata)
+{
+ int ret = -1, busnum = root->secondary.start;
+ struct pci_bus *bus;
+
+ info->root = root;
+ info->ops = ops;
+ INIT_LIST_HEAD(&info->resources);
+ snprintf(info->name, sizeof(info->name), "PCI Bus %04x:%02x",
+ root->segment, busnum);
+
+ if (ops->prepare_resources)
+ ret = ops->prepare_resources(info);
+ if (ret < 0)
+ goto out_release_info;
+
+ pci_test_root_add_resources(info);
+ pci_add_resource(&info->resources, &root->secondary_os);
+ bus = pci_create_root_bus(NULL, busnum, ops->pci_ops,
+ sysdata, &info->resources);
+ if (!bus)
+ goto out_release_info;
+
+ bus->ioport_res = &pci_test_ioport_resource;
+ bus->iomem_res = &pci_test_iomem_resource;
+
+ pci_scan_child_bus(bus);
+ pci_set_host_bridge_release(to_pci_host_bridge(bus->bridge),
+ pci_test_root_release_info, info);
+
+ return bus;
+
+out_release_info:
+ __pci_test_root_release_info(info);
+
+ return NULL;
+}
+
+static struct pci_bus *pci_test_scan_root(struct test_pci_root *root)
+{
+ struct pci_root_info_ext *info_ext;
+ struct pci_bus *bus;
+
+ info_ext = kzalloc(sizeof(*info_ext), GFP_KERNEL);
+ if (!info_ext)
+ return NULL;
+
+ info_ext->sd.domain = root->segment;
+ bus = pci_test_root_create(root, &pci_test_root_ops, &info_ext->common,
+ &info_ext->sd);
+
+ return bus;
+}
+
+static void pci_bus_disable_match_devices(struct pci_bus *bus)
+{
+ struct pci_dev *dev;
+ struct pci_bus *child;
+
+ list_for_each_entry(dev, &bus->devices, bus_list)
+ dev->match_driver = -1;
+
+ list_for_each_entry(dev, &bus->devices, bus_list) {
+ child = dev->subordinate;
+ if (child)
+ pci_bus_disable_match_devices(child);
+ }
+}
+
+static char *data_file;
+static int domain_nr_offset = 0xf000;
+static char *mask_file;
+static bool survey_first = true;
+module_param(data_file, charp, 0444);
+module_param(domain_nr_offset, int, 0444);
+module_param(mask_file, charp, 0444);
+module_param(survey_first, bool, 0444);
+MODULE_PARM_DESC(data_file, "data file for pci root bus res and register info");
+MODULE_PARM_DESC(domain_nr_offset, "offset for change domain_nr in data file");
+MODULE_PARM_DESC(mask_file, "mask file for register mask info");
+MODULE_PARM_DESC(survey_first, "survey BARs in register (from firmware) and reserve them at first");
+
+#ifdef CONFIG_PROC_FS
+static struct resource *next_resource(struct resource *p, bool sibling_only)
+{
+ /* Caller wants to traverse through siblings only */
+ if (sibling_only)
+ return p->sibling;
+
+ if (p->child)
+ return p->child;
+ while (!p->sibling && p->parent)
+ p = p->parent;
+ return p->sibling;
+}
+
+static void *r_next(struct seq_file *m, void *v, loff_t *pos)
+{
+ struct resource *p = v;
+ (*pos)++;
+ return (void *)next_resource(p, false);
+}
+
+enum { MAX_IORES_LEVEL = 5 };
+
+static void *r_start(struct seq_file *m, loff_t *pos)
+{
+ struct resource *p = m->private;
+ loff_t l = 0;
+
+ for (p = p->child; p && l < *pos; p = r_next(m, p, &l))
+ ;
+ return p;
+}
+
+static void r_stop(struct seq_file *m, void *v)
+{
+}
+
+static int r_show(struct seq_file *m, void *v)
+{
+ struct resource *root = m->private;
+ struct resource *r = v, *p;
+ int width = root->end < 0x10000 ? 4 : 8;
+ int depth;
+
+ for (depth = 0, p = r; depth < MAX_IORES_LEVEL; depth++, p = p->parent)
+ if (p->parent == root)
+ break;
+ seq_printf(m, "%*s%0*llx-%0*llx : %s\n",
+ depth * 2, "",
+ width, (unsigned long long) r->start,
+ width, (unsigned long long) r->end,
+ r->name ? r->name : "<BAD>");
+ return 0;
+}
+
+static const struct seq_operations resource_op = {
+ .start = r_start,
+ .next = r_next,
+ .stop = r_stop,
+ .show = r_show,
+};
+
+static int ioports_open(struct inode *inode, struct file *file)
+{
+ int res = seq_open(file, &resource_op);
+
+ if (!res) {
+ struct seq_file *m = file->private_data;
+
+ m->private = &pci_test_ioport_resource;
+ }
+ return res;
+}
+
+static int iomem_open(struct inode *inode, struct file *file)
+{
+ int res = seq_open(file, &resource_op);
+
+ if (!res) {
+ struct seq_file *m = file->private_data;
+
+ m->private = &pci_test_iomem_resource;
+ }
+ return res;
+}
+
+static const struct file_operations proc_ioports_operations = {
+ .open = ioports_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = seq_release,
+};
+
+static const struct file_operations proc_iomem_operations = {
+ .open = iomem_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = seq_release,
+};
+
+struct proc_dir_entry *ioports_proc;
+struct proc_dir_entry *iomem_proc;
+
+static void ioresources_init(void)
+{
+ ioports_proc = proc_create("ioports_test", 0, NULL,
+ &proc_ioports_operations);
+ iomem_proc = proc_create("iomem_test", 0, NULL,
+ &proc_iomem_operations);
+}
+
+static void ioresources_exit(void)
+{
+ proc_remove(ioports_proc);
+ proc_remove(iomem_proc);
+}
+#else
+static void ioresources_init(void)
+{
+}
+static void ioresources_exit(void)
+{
+}
+#endif /* CONFIG_PROC_FS */
+
+static __init int pci_test_init(void)
+{
+ struct test_pci_root *root;
+
+ if (!data_file)
+ return 0;
+
+ pr_info("data file: %s domain_nr_offset: %04x\n",
+ data_file, domain_nr_offset);
+
+ if (test_pci_init(data_file, domain_nr_offset, mask_file))
+ return 0;
+
+ list_for_each_entry(root, &test_pci_root_list, node) {
+ root->bus = pci_test_scan_root(root);
+ if (!root->bus)
+ continue;
+
+ if (survey_first)
+ pcibios_resource_survey_bus(root->bus);
+
+ pci_assign_unassigned_root_bus_resources(root->bus);
+ pci_bus_disable_match_devices(root->bus);
+ pci_bus_add_devices(root->bus);
+ }
+
+ ioresources_init();
+
+ return 0;
+}
+
+static void __exit pci_test_exit(void)
+{
+ struct test_pci_root *root;
+
+ list_for_each_entry(root, &test_pci_root_list, node)
+ if (root->bus) {
+ pci_stop_root_bus(root->bus);
+ pci_remove_root_bus(root->bus);
+ }
+
+ ioresources_exit();
+
+ test_pci_free();
+}
+
+module_init(pci_test_init);
+module_exit(pci_test_exit);
+
+MODULE_AUTHOR("Yinghai Lu <yinghai@xxxxxxxxxx>");
+MODULE_DESCRIPTION("PCI test for resource allocation code");
+MODULE_LICENSE("GPL v2");
--
2.9.4