Commit 26409dd0 authored by Lizhi Hou's avatar Lizhi Hou Committed by Rob Herring
Browse files

of: unittest: Add pci_dt_testdrv pci driver



pci_dt_testdrv is bound to QEMU PCI Test Device. It reads
overlay_pci_node fdt fragment and apply it to Test Device. Then it
calls of_platform_default_populate() to populate the platform
devices.

Tested-by: default avatarHerve Codina <herve.codina@bootlin.com>
Signed-off-by: default avatarLizhi Hou <lizhi.hou@amd.com>
Link: https://lore.kernel.org/r/1692120000-46900-6-git-send-email-lizhi.hou@amd.com


Signed-off-by: default avatarRob Herring <robh@kernel.org>
parent 47284862
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -32,7 +32,8 @@ obj-$(CONFIG_OF_OVERLAY) += overlay.dtbo.o \
			    overlay_gpio_02b.dtbo.o \
			    overlay_gpio_03.dtbo.o \
			    overlay_gpio_04a.dtbo.o \
			    overlay_gpio_04b.dtbo.o
			    overlay_gpio_04b.dtbo.o \
			    overlay_pci_node.dtbo.o

# enable creation of __symbols__ node
DTC_FLAGS_overlay += -@
+22 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/ {
	fragment@0 {
		target-path="";
		__overlay__ {
			#address-cells = <3>;
			#size-cells = <2>;
			pci-ep-bus@0 {
				compatible = "simple-bus";
				#address-cells = <1>;
				#size-cells = <1>;
				ranges = <0x0 0x0 0x0 0x0 0x1000>;
				reg = <0 0 0 0 0>;
				unittest-pci@100 {
					compatible = "unittest-pci";
					reg = <0x100 0x200>;
				};
			};
		};
	};
};
+189 −0
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/pci.h>
#include <linux/kernel.h>

#include <linux/i2c.h>
@@ -3326,6 +3327,7 @@ OVERLAY_INFO_EXTERN(overlay_gpio_02b);
OVERLAY_INFO_EXTERN(overlay_gpio_03);
OVERLAY_INFO_EXTERN(overlay_gpio_04a);
OVERLAY_INFO_EXTERN(overlay_gpio_04b);
OVERLAY_INFO_EXTERN(overlay_pci_node);
OVERLAY_INFO_EXTERN(overlay_bad_add_dup_node);
OVERLAY_INFO_EXTERN(overlay_bad_add_dup_prop);
OVERLAY_INFO_EXTERN(overlay_bad_phandle);
@@ -3361,6 +3363,7 @@ static struct overlay_info overlays[] = {
	OVERLAY_INFO(overlay_gpio_03, 0),
	OVERLAY_INFO(overlay_gpio_04a, 0),
	OVERLAY_INFO(overlay_gpio_04b, 0),
	OVERLAY_INFO(overlay_pci_node, 0),
	OVERLAY_INFO(overlay_bad_add_dup_node, -EINVAL),
	OVERLAY_INFO(overlay_bad_add_dup_prop, -EINVAL),
	OVERLAY_INFO(overlay_bad_phandle, -EINVAL),
@@ -3731,6 +3734,191 @@ static inline __init void of_unittest_overlay_high_level(void) {}

#endif

#ifdef CONFIG_PCI_DYNAMIC_OF_NODES

static int of_unittest_pci_dev_num;
static int of_unittest_pci_child_num;

/*
 * PCI device tree node test driver
 */
static const struct pci_device_id testdrv_pci_ids[] = {
	{ PCI_DEVICE(PCI_VENDOR_ID_REDHAT, 0x5), }, /* PCI_VENDOR_ID_REDHAT */
	{ 0, }
};

static int testdrv_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
	struct overlay_info *info;
	struct device_node *dn;
	int ret, ovcs_id;
	u32 size;

	dn = pdev->dev.of_node;
	if (!dn) {
		dev_err(&pdev->dev, "does not find bus endpoint");
		return -EINVAL;
	}

	for (info = overlays; info && info->name; info++) {
		if (!strcmp(info->name, "overlay_pci_node"))
			break;
	}
	if (!info || !info->name) {
		dev_err(&pdev->dev, "no overlay data for overlay_pci_node");
		return -ENODEV;
	}

	size = info->dtbo_end - info->dtbo_begin;
	ret = of_overlay_fdt_apply(info->dtbo_begin, size, &ovcs_id, dn);
	of_node_put(dn);
	if (ret)
		return ret;

	of_platform_default_populate(dn, NULL, &pdev->dev);
	pci_set_drvdata(pdev, (void *)(uintptr_t)ovcs_id);

	return 0;
}

static void testdrv_remove(struct pci_dev *pdev)
{
	int ovcs_id = (int)(uintptr_t)pci_get_drvdata(pdev);

	of_platform_depopulate(&pdev->dev);
	of_overlay_remove(&ovcs_id);
}

static struct pci_driver testdrv_driver = {
	.name = "pci_dt_testdrv",
	.id_table = testdrv_pci_ids,
	.probe = testdrv_probe,
	.remove = testdrv_remove,
};

static int unittest_pci_probe(struct platform_device *pdev)
{
	struct resource *res;
	struct device *dev;
	u64 exp_addr;

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!res)
		return -ENODEV;

	dev = &pdev->dev;
	while (dev && !dev_is_pci(dev))
		dev = dev->parent;
	if (!dev) {
		pr_err("unable to find parent device\n");
		return -ENODEV;
	}

	exp_addr = pci_resource_start(to_pci_dev(dev), 0) + 0x100;
	unittest(res->start == exp_addr, "Incorrect translated address %llx, expected %llx\n",
		 (u64)res->start, exp_addr);

	of_unittest_pci_child_num++;

	return 0;
}

static const struct of_device_id unittest_pci_of_match[] = {
	{ .compatible = "unittest-pci" },
	{ }
};

static struct platform_driver unittest_pci_driver = {
	.probe = unittest_pci_probe,
	.driver = {
		.name = "unittest-pci",
		.of_match_table = unittest_pci_of_match,
	},
};

static int of_unittest_pci_node_verify(struct pci_dev *pdev, bool add)
{
	struct device_node *pnp, *np = NULL;
	struct device *child_dev;
	char *path = NULL;
	const __be32 *reg;
	int rc = 0;

	pnp = pdev->dev.of_node;
	unittest(pnp, "Failed creating PCI dt node\n");
	if (!pnp)
		return -ENODEV;

	if (add) {
		path = kasprintf(GFP_KERNEL, "%pOF/pci-ep-bus@0/unittest-pci@100", pnp);
		np = of_find_node_by_path(path);
		unittest(np, "Failed to get unittest-pci node under PCI node\n");
		if (!np) {
			rc = -ENODEV;
			goto failed;
		}

		reg = of_get_property(np, "reg", NULL);
		unittest(reg, "Failed to get reg property\n");
		if (!reg)
			rc = -ENODEV;
	} else {
		path = kasprintf(GFP_KERNEL, "%pOF/pci-ep-bus@0", pnp);
		np = of_find_node_by_path(path);
		unittest(!np, "Child device tree node is not removed\n");
		child_dev = device_find_any_child(&pdev->dev);
		unittest(!child_dev, "Child device is not removed\n");
	}

failed:
	kfree(path);
	if (np)
		of_node_put(np);

	return rc;
}

static void __init of_unittest_pci_node(void)
{
	struct pci_dev *pdev = NULL;
	int rc;

	rc = pci_register_driver(&testdrv_driver);
	unittest(!rc, "Failed to register pci test driver; rc = %d\n", rc);
	if (rc)
		return;

	rc = platform_driver_register(&unittest_pci_driver);
	if (unittest(!rc, "Failed to register unittest pci driver\n")) {
		pci_unregister_driver(&testdrv_driver);
		return;
	}

	while ((pdev = pci_get_device(PCI_VENDOR_ID_REDHAT, 0x5, pdev)) != NULL) {
		of_unittest_pci_node_verify(pdev, true);
		of_unittest_pci_dev_num++;
	}
	if (pdev)
		pci_dev_put(pdev);

	unittest(of_unittest_pci_dev_num,
		 "No test PCI device been found. Please run QEMU with '-device pci-testdev'\n");
	unittest(of_unittest_pci_dev_num == of_unittest_pci_child_num,
		 "Child device number %d is not expected %d", of_unittest_pci_child_num,
		 of_unittest_pci_dev_num);

	platform_driver_unregister(&unittest_pci_driver);
	pci_unregister_driver(&testdrv_driver);

	while ((pdev = pci_get_device(PCI_VENDOR_ID_REDHAT, 0x5, pdev)) != NULL)
		of_unittest_pci_node_verify(pdev, false);
	if (pdev)
		pci_dev_put(pdev);
}
#else
static void __init of_unittest_pci_node(void) { }
#endif

static int __init of_unittest(void)
{
	struct device_node *np;
@@ -3781,6 +3969,7 @@ static int __init of_unittest(void)
	of_unittest_platform_populate();
	of_unittest_overlay();
	of_unittest_lifecycle();
	of_unittest_pci_node();

	/* Double check linkage after removing testcase data */
	of_unittest_check_tree_linkage();
+1 −0
Original line number Diff line number Diff line
@@ -6149,3 +6149,4 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9a31, dpc_log_size);
 */
DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_XILINX, 0x5020, of_pci_make_dev_node);
DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_XILINX, 0x5021, of_pci_make_dev_node);
DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_REDHAT, 0x0005, of_pci_make_dev_node);