summaryrefslogtreecommitdiffstats
path: root/drivers/base/test
diff options
context:
space:
mode:
authorRosen Penev <rosenp@gmail.com>2026-09-27 13:35:01 -0700
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2026-10-01 11:04:53 +0200
commit8167c1f071426706c233e93ecfd13aba7d5c06c8 (patch)
tree2c0cd5f5e1cc35eadee98d49fe034f96a88856ce /drivers/base/test
downloadlinux-stable-8167c1f071426706c233e93ecfd13aba7d5c06c8.tar.gz
linux-stable-8167c1f071426706c233e93ecfd13aba7d5c06c8.zip
tty: serial: mpc52xx_uart: move static declarations up.grafted
Avoid a compilation error so that they're not used before being declared. Fixes: 4d105880666a ("tty: serial: mpc52xx_uart: add bounds check for psc_num array index") Reported-by: kernel test robot <lkp@intel.com> Closes: https://lore.kernel.org/oe-kbuild-all/202609260356.tJWbd1WU-lkp@intel.com/ Signed-off-by: Rosen Penev <rosenp@gmail.com> Link: https://patch.msgid.link/20260927203501.14105-1-rosenp@gmail.com Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/base/test')
-rw-r--r--drivers/base/test/.kunitconfig2
-rw-r--r--drivers/base/test/Kconfig25
-rw-r--r--drivers/base/test/Makefile10
-rw-r--r--drivers/base/test/platform-device-test.c443
-rw-r--r--drivers/base/test/property-entry-test.c652
-rw-r--r--drivers/base/test/root-device-test.c112
-rw-r--r--drivers/base/test/swnode-devlink-test.c337
-rw-r--r--drivers/base/test/test_async_driver_probe.c299
8 files changed, 1880 insertions, 0 deletions
diff --git a/drivers/base/test/.kunitconfig b/drivers/base/test/.kunitconfig
new file mode 100644
index 000000000..473923f09
--- /dev/null
+++ b/drivers/base/test/.kunitconfig
@@ -0,0 +1,2 @@
+CONFIG_KUNIT=y
+CONFIG_DM_KUNIT_TEST=y
diff --git a/drivers/base/test/Kconfig b/drivers/base/test/Kconfig
new file mode 100644
index 000000000..1ecf07912
--- /dev/null
+++ b/drivers/base/test/Kconfig
@@ -0,0 +1,25 @@
+# SPDX-License-Identifier: GPL-2.0
+config TEST_ASYNC_DRIVER_PROBE
+ tristate "Build kernel module to test asynchronous driver probing"
+ depends on m
+ help
+ Enabling this option produces a kernel module that allows
+ testing asynchronous driver probing by the device core.
+ The module name will be test_async_driver_probe.ko
+
+ If unsure say N.
+
+config DM_KUNIT_TEST
+ tristate "KUnit Tests for the device model" if !KUNIT_ALL_TESTS
+ depends on KUNIT
+ default KUNIT_ALL_TESTS
+
+config DRIVER_PE_KUNIT_TEST
+ tristate "KUnit Tests for property entry API" if !KUNIT_ALL_TESTS
+ depends on KUNIT
+ default KUNIT_ALL_TESTS
+
+config DRIVER_SWNODE_KUNIT_TEST
+ tristate "KUnit Tests for software node fw_devlink links" if !KUNIT_ALL_TESTS
+ depends on KUNIT
+ default KUNIT_ALL_TESTS
diff --git a/drivers/base/test/Makefile b/drivers/base/test/Makefile
new file mode 100644
index 000000000..9ced7bbd5
--- /dev/null
+++ b/drivers/base/test/Makefile
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_TEST_ASYNC_DRIVER_PROBE) += test_async_driver_probe.o
+
+obj-$(CONFIG_DM_KUNIT_TEST) += root-device-test.o
+obj-$(CONFIG_DM_KUNIT_TEST) += platform-device-test.o
+
+obj-$(CONFIG_DRIVER_PE_KUNIT_TEST) += property-entry-test.o
+CFLAGS_property-entry-test.o += $(DISABLE_STRUCTLEAK_PLUGIN)
+
+obj-$(CONFIG_DRIVER_SWNODE_KUNIT_TEST) += swnode-devlink-test.o
diff --git a/drivers/base/test/platform-device-test.c b/drivers/base/test/platform-device-test.c
new file mode 100644
index 000000000..9ce563f76
--- /dev/null
+++ b/drivers/base/test/platform-device-test.c
@@ -0,0 +1,443 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <kunit/fwnode.h>
+#include <kunit/platform_device.h>
+#include <kunit/resource.h>
+
+#include <linux/device.h>
+#include <linux/device/bus.h>
+#include <linux/fwnode.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+
+#define DEVICE_NAME "test"
+
+struct test_priv {
+ bool probe_done;
+ bool release_done;
+ wait_queue_head_t probe_wq;
+ wait_queue_head_t release_wq;
+ struct device *dev;
+};
+
+static int platform_device_devm_init(struct kunit *test)
+{
+ struct test_priv *priv;
+
+ priv = kunit_kzalloc(test, sizeof(*priv), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv);
+ init_waitqueue_head(&priv->probe_wq);
+ init_waitqueue_head(&priv->release_wq);
+
+ test->priv = priv;
+
+ return 0;
+}
+
+static void devm_device_action(void *ptr)
+{
+ struct test_priv *priv = ptr;
+
+ priv->release_done = true;
+ wake_up_interruptible(&priv->release_wq);
+}
+
+static void devm_put_device_action(void *ptr)
+{
+ struct test_priv *priv = ptr;
+
+ put_device(priv->dev);
+ priv->release_done = true;
+ wake_up_interruptible(&priv->release_wq);
+}
+
+#define RELEASE_TIMEOUT_MS 100
+
+/*
+ * Tests that a platform bus, non-probed device will run its
+ * device-managed actions when unregistered.
+ */
+static void platform_device_devm_register_unregister_test(struct kunit *test)
+{
+ struct platform_device *pdev;
+ struct test_priv *priv = test->priv;
+ int ret;
+
+ pdev = platform_device_alloc(DEVICE_NAME, PLATFORM_DEVID_NONE);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev);
+
+ ret = platform_device_add(pdev);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ priv->dev = &pdev->dev;
+
+ ret = devm_add_action_or_reset(priv->dev, devm_device_action, priv);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ platform_device_unregister(pdev);
+
+ ret = wait_event_interruptible_timeout(priv->release_wq, priv->release_done,
+ msecs_to_jiffies(RELEASE_TIMEOUT_MS));
+ KUNIT_EXPECT_GT(test, ret, 0);
+}
+
+/*
+ * Tests that a platform bus, non-probed device will run its
+ * device-managed actions when unregistered, even if someone still holds
+ * a reference to it.
+ */
+static void platform_device_devm_register_get_unregister_with_devm_test(struct kunit *test)
+{
+ struct platform_device *pdev;
+ struct test_priv *priv = test->priv;
+ int ret;
+
+ pdev = platform_device_alloc(DEVICE_NAME, PLATFORM_DEVID_NONE);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev);
+
+ ret = platform_device_add(pdev);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ priv->dev = &pdev->dev;
+
+ get_device(priv->dev);
+
+ ret = devm_add_action_or_reset(priv->dev, devm_put_device_action, priv);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ platform_device_unregister(pdev);
+
+ ret = wait_event_interruptible_timeout(priv->release_wq, priv->release_done,
+ msecs_to_jiffies(RELEASE_TIMEOUT_MS));
+ KUNIT_EXPECT_GT(test, ret, 0);
+}
+
+static int fake_probe(struct platform_device *pdev)
+{
+ struct test_priv *priv = platform_get_drvdata(pdev);
+
+ priv->probe_done = true;
+ wake_up_interruptible(&priv->probe_wq);
+
+ return 0;
+}
+
+static struct platform_driver fake_driver = {
+ .probe = fake_probe,
+ .driver = {
+ .name = DEVICE_NAME,
+ },
+};
+
+/*
+ * Tests that a platform bus, probed device will run its device-managed
+ * actions when unregistered.
+ */
+static void probed_platform_device_devm_register_unregister_test(struct kunit *test)
+{
+ struct platform_device *pdev;
+ struct test_priv *priv = test->priv;
+ int ret;
+
+ ret = platform_driver_register(&fake_driver);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ pdev = platform_device_alloc(DEVICE_NAME, PLATFORM_DEVID_NONE);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev);
+
+ priv->dev = &pdev->dev;
+ platform_set_drvdata(pdev, priv);
+
+ ret = platform_device_add(pdev);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ ret = wait_event_interruptible_timeout(priv->probe_wq, priv->probe_done,
+ msecs_to_jiffies(RELEASE_TIMEOUT_MS));
+ KUNIT_ASSERT_GT(test, ret, 0);
+
+ ret = devm_add_action_or_reset(priv->dev, devm_device_action, priv);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ platform_device_unregister(pdev);
+
+ ret = wait_event_interruptible_timeout(priv->release_wq, priv->release_done,
+ msecs_to_jiffies(RELEASE_TIMEOUT_MS));
+ KUNIT_EXPECT_GT(test, ret, 0);
+
+ platform_driver_unregister(&fake_driver);
+}
+
+/*
+ * Tests that a platform bus, probed device will run its device-managed
+ * actions when unregistered, even if someone still holds a reference to
+ * it.
+ */
+static void probed_platform_device_devm_register_get_unregister_with_devm_test(struct kunit *test)
+{
+ struct platform_device *pdev;
+ struct test_priv *priv = test->priv;
+ int ret;
+
+ ret = platform_driver_register(&fake_driver);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ pdev = platform_device_alloc(DEVICE_NAME, PLATFORM_DEVID_NONE);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev);
+
+ priv->dev = &pdev->dev;
+ platform_set_drvdata(pdev, priv);
+
+ ret = platform_device_add(pdev);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ ret = wait_event_interruptible_timeout(priv->probe_wq, priv->probe_done,
+ msecs_to_jiffies(RELEASE_TIMEOUT_MS));
+ KUNIT_ASSERT_GT(test, ret, 0);
+
+ get_device(priv->dev);
+
+ ret = devm_add_action_or_reset(priv->dev, devm_put_device_action, priv);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ platform_device_unregister(pdev);
+
+ ret = wait_event_interruptible_timeout(priv->release_wq, priv->release_done,
+ msecs_to_jiffies(RELEASE_TIMEOUT_MS));
+ KUNIT_EXPECT_GT(test, ret, 0);
+
+ platform_driver_unregister(&fake_driver);
+}
+
+static struct kunit_case platform_device_devm_tests[] = {
+ KUNIT_CASE(platform_device_devm_register_unregister_test),
+ KUNIT_CASE(platform_device_devm_register_get_unregister_with_devm_test),
+ KUNIT_CASE(probed_platform_device_devm_register_unregister_test),
+ KUNIT_CASE(probed_platform_device_devm_register_get_unregister_with_devm_test),
+ {}
+};
+
+static struct kunit_suite platform_device_devm_test_suite = {
+ .name = "platform-device-devm",
+ .init = platform_device_devm_init,
+ .test_cases = platform_device_devm_tests,
+};
+
+static void platform_device_find_by_null_test(struct kunit *test)
+{
+ struct platform_device *pdev;
+ int ret;
+
+ pdev = kunit_platform_device_alloc(test, DEVICE_NAME, PLATFORM_DEVID_NONE);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev);
+
+ ret = kunit_platform_device_add(test, pdev);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ KUNIT_EXPECT_PTR_EQ(test, of_find_device_by_node(NULL), NULL);
+
+ KUNIT_EXPECT_PTR_EQ(test, bus_find_device_by_of_node(&platform_bus_type, NULL), NULL);
+ KUNIT_EXPECT_PTR_EQ(test, bus_find_device_by_fwnode(&platform_bus_type, NULL), NULL);
+ KUNIT_EXPECT_PTR_EQ(test, bus_find_device_by_acpi_dev(&platform_bus_type, NULL), NULL);
+
+ KUNIT_EXPECT_FALSE(test, device_match_of_node(&pdev->dev, NULL));
+ KUNIT_EXPECT_FALSE(test, device_match_fwnode(&pdev->dev, NULL));
+ KUNIT_EXPECT_FALSE(test, device_match_acpi_dev(&pdev->dev, NULL));
+ KUNIT_EXPECT_FALSE(test, device_match_acpi_handle(&pdev->dev, NULL));
+}
+
+static struct kunit_case platform_device_match_tests[] = {
+ KUNIT_CASE(platform_device_find_by_null_test),
+ {}
+};
+
+static struct kunit_suite platform_device_match_test_suite = {
+ .name = "platform-device-match",
+ .test_cases = platform_device_match_tests,
+};
+
+static int platform_device_swnode_test_probe(struct platform_device *pdev)
+{
+ return 0;
+}
+
+static struct platform_driver platform_swnode_test_driver = {
+ .probe = platform_device_swnode_test_probe,
+ .driver = {
+ .name = DEVICE_NAME,
+ },
+};
+
+static const struct software_node platform_device_test_swnode = { };
+
+/*
+ * Check that reusing a software node works correctly. If the call to
+ * platform_device_register_full() fails after adding the secondary firmware
+ * node, the software node must be unregistered in the device's release()
+ * callback or the subsequent call to platform_device_register_full() will fail
+ * with -EBUSY due to the software node already having been registered.
+ */
+static void platform_device_swnode_add_twice(struct kunit *test)
+{
+ struct platform_device_info pdevinfo;
+ struct platform_device *pdev;
+ struct fwnode_handle *fwnode;
+ bool bound = false;
+ int ret;
+
+ fwnode = kunit_kzalloc(test, sizeof(*fwnode), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fwnode);
+
+ ret = kunit_platform_driver_register(test, &platform_swnode_test_driver);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ fwnode_init(fwnode, NULL);
+ pdevinfo = (struct platform_device_info){
+ .name = DEVICE_NAME,
+ .id = PLATFORM_DEVID_NONE,
+ .fwnode = fwnode,
+ .swnode = &platform_device_test_swnode,
+ };
+
+ pdev = platform_device_register_full(&pdevinfo);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev);
+
+ wait_for_device_probe();
+ scoped_guard(device, &pdev->dev)
+ bound = device_is_bound(&pdev->dev);
+
+ KUNIT_ASSERT_TRUE(test, bound);
+
+ platform_device_unregister(pdev);
+
+ pdev = platform_device_register_full(&pdevinfo);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev);
+
+ wait_for_device_probe();
+ scoped_guard(device, &pdev->dev)
+ bound = device_is_bound(&pdev->dev);
+
+ KUNIT_ASSERT_TRUE(test, bound);
+
+ platform_device_unregister(pdev);
+}
+
+/*
+ * Check that passing a software node as the primary firmware node of the
+ * platform device does not result in it being unregistered by the call to
+ * device_remove_software_node() in its release path.
+ */
+static void platform_device_swnode_as_primary(struct kunit *test)
+{
+ struct platform_device_info pdevinfo;
+ struct platform_device *pdev;
+ struct fwnode_handle *fwnode;
+ bool bound = false;
+ int ret;
+
+ ret = kunit_platform_driver_register(test, &platform_swnode_test_driver);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ fwnode = kunit_software_node_register(test, &platform_device_test_swnode);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fwnode);
+
+ pdevinfo = (struct platform_device_info){
+ .name = DEVICE_NAME,
+ .id = PLATFORM_DEVID_NONE,
+ .fwnode = fwnode,
+ };
+
+ pdev = platform_device_register_full(&pdevinfo);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev);
+
+ wait_for_device_probe();
+ scoped_guard(device, &pdev->dev)
+ bound = device_is_bound(&pdev->dev);
+
+ KUNIT_ASSERT_TRUE(test, bound);
+
+ platform_device_unregister(pdev);
+
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, software_node_fwnode(&platform_device_test_swnode));
+}
+
+/*
+ * Check that passing two software nodes to platform_device_register_full()
+ * fails.
+ */
+static void platform_device_two_swnodes(struct kunit *test)
+{
+ static const struct property_entry properties[] = {
+ PROPERTY_ENTRY_U32("foo", 42),
+ { }
+ };
+
+ struct platform_device_info pdevinfo;
+ struct platform_device *pdev;
+ struct fwnode_handle *fwnode;
+ int ret;
+
+ ret = kunit_platform_driver_register(test, &platform_swnode_test_driver);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ fwnode = kunit_software_node_register(test, &platform_device_test_swnode);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fwnode);
+
+ pdevinfo = (struct platform_device_info){
+ .name = DEVICE_NAME,
+ .id = PLATFORM_DEVID_NONE,
+ .fwnode = fwnode,
+ .swnode = &platform_device_test_swnode,
+ };
+
+ pdev = platform_device_register_full(&pdevinfo);
+ KUNIT_ASSERT_TRUE(test, IS_ERR(pdev));
+ KUNIT_ASSERT_EQ_MSG(test, PTR_ERR(pdev), -EINVAL,
+ "Expected errno == -EINVAL, got: %pe", pdev);
+
+ pdevinfo = (struct platform_device_info){
+ .name = DEVICE_NAME,
+ .id = PLATFORM_DEVID_NONE,
+ .swnode = &platform_device_test_swnode,
+ .properties = properties,
+ };
+
+ pdev = platform_device_register_full(&pdevinfo);
+ KUNIT_ASSERT_TRUE(test, IS_ERR(pdev));
+ KUNIT_ASSERT_EQ_MSG(test, PTR_ERR(pdev), -EINVAL,
+ "Expected errno == -EINVAL, got: %pe", pdev);
+
+ pdevinfo = (struct platform_device_info){
+ .name = DEVICE_NAME,
+ .id = PLATFORM_DEVID_NONE,
+ .fwnode = fwnode,
+ .properties = properties,
+ };
+
+ pdev = platform_device_register_full(&pdevinfo);
+ KUNIT_ASSERT_TRUE(test, IS_ERR(pdev));
+ KUNIT_ASSERT_EQ_MSG(test, PTR_ERR(pdev), -EINVAL,
+ "Expected errno == -EINVAL, got: %pe", pdev);
+}
+
+static struct kunit_case platform_device_swnode_tests[] = {
+ KUNIT_CASE(platform_device_swnode_add_twice),
+ KUNIT_CASE(platform_device_swnode_as_primary),
+ KUNIT_CASE(platform_device_two_swnodes),
+ {}
+};
+
+static struct kunit_suite platform_device_swnode_test_suite = {
+ .name = "platform-device-swnode",
+ .test_cases = platform_device_swnode_tests,
+};
+
+kunit_test_suites(
+ &platform_device_devm_test_suite,
+ &platform_device_match_test_suite,
+ &platform_device_swnode_test_suite,
+);
+
+MODULE_DESCRIPTION("Test module for platform devices");
+MODULE_AUTHOR("Maxime Ripard <mripard@kernel.org>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/base/test/property-entry-test.c b/drivers/base/test/property-entry-test.c
new file mode 100644
index 000000000..89cdfc2f8
--- /dev/null
+++ b/drivers/base/test/property-entry-test.c
@@ -0,0 +1,652 @@
+// SPDX-License-Identifier: GPL-2.0
+// Unit tests for property entries API
+//
+// Copyright 2019 Google LLC.
+
+#include <kunit/test.h>
+
+#include <linux/of.h>
+#include <linux/property.h>
+#include <linux/types.h>
+
+static void pe_test_uints(struct kunit *test)
+{
+ static const struct property_entry entries[] = {
+ PROPERTY_ENTRY_U8("prop-u8", 8),
+ PROPERTY_ENTRY_U16("prop-u16", 16),
+ PROPERTY_ENTRY_U32("prop-u32", 32),
+ PROPERTY_ENTRY_U64("prop-u64", 64),
+ { }
+ };
+
+ struct fwnode_handle *node;
+ u8 val_u8, array_u8[2];
+ u16 val_u16, array_u16[2];
+ u32 val_u32, array_u32[2];
+ u64 val_u64, array_u64[2];
+ int error;
+
+ node = fwnode_create_software_node(entries, NULL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, node);
+
+ error = fwnode_property_count_u8(node, "prop-u8");
+ KUNIT_EXPECT_EQ(test, error, 1);
+
+ error = fwnode_property_read_u8(node, "prop-u8", &val_u8);
+ KUNIT_EXPECT_EQ(test, error, 0);
+ KUNIT_EXPECT_EQ(test, val_u8, 8);
+
+ error = fwnode_property_read_u8_array(node, "prop-u8", array_u8, 1);
+ KUNIT_EXPECT_EQ(test, error, 0);
+ KUNIT_EXPECT_EQ(test, array_u8[0], 8);
+
+ error = fwnode_property_read_u8_array(node, "prop-u8", array_u8, 2);
+ KUNIT_EXPECT_NE(test, error, 0);
+
+ error = fwnode_property_read_u8(node, "no-prop-u8", &val_u8);
+ KUNIT_EXPECT_NE(test, error, 0);
+
+ error = fwnode_property_read_u8_array(node, "no-prop-u8", array_u8, 1);
+ KUNIT_EXPECT_NE(test, error, 0);
+
+ error = fwnode_property_read_u16(node, "prop-u16", &val_u16);
+ KUNIT_EXPECT_EQ(test, error, 0);
+ KUNIT_EXPECT_EQ(test, val_u16, 16);
+
+ error = fwnode_property_count_u16(node, "prop-u16");
+ KUNIT_EXPECT_EQ(test, error, 1);
+
+ error = fwnode_property_read_u16_array(node, "prop-u16", array_u16, 1);
+ KUNIT_EXPECT_EQ(test, error, 0);
+ KUNIT_EXPECT_EQ(test, array_u16[0], 16);
+
+ error = fwnode_property_read_u16_array(node, "prop-u16", array_u16, 2);
+ KUNIT_EXPECT_NE(test, error, 0);
+
+ error = fwnode_property_read_u16(node, "no-prop-u16", &val_u16);
+ KUNIT_EXPECT_NE(test, error, 0);
+
+ error = fwnode_property_read_u16_array(node, "no-prop-u16", array_u16, 1);
+ KUNIT_EXPECT_NE(test, error, 0);
+
+ error = fwnode_property_read_u32(node, "prop-u32", &val_u32);
+ KUNIT_EXPECT_EQ(test, error, 0);
+ KUNIT_EXPECT_EQ(test, val_u32, 32);
+
+ error = fwnode_property_count_u32(node, "prop-u32");
+ KUNIT_EXPECT_EQ(test, error, 1);
+
+ error = fwnode_property_read_u32_array(node, "prop-u32", array_u32, 1);
+ KUNIT_EXPECT_EQ(test, error, 0);
+ KUNIT_EXPECT_EQ(test, array_u32[0], 32);
+
+ error = fwnode_property_read_u32_array(node, "prop-u32", array_u32, 2);
+ KUNIT_EXPECT_NE(test, error, 0);
+
+ error = fwnode_property_read_u32(node, "no-prop-u32", &val_u32);
+ KUNIT_EXPECT_NE(test, error, 0);
+
+ error = fwnode_property_read_u32_array(node, "no-prop-u32", array_u32, 1);
+ KUNIT_EXPECT_NE(test, error, 0);
+
+ error = fwnode_property_read_u64(node, "prop-u64", &val_u64);
+ KUNIT_EXPECT_EQ(test, error, 0);
+ KUNIT_EXPECT_EQ(test, val_u64, 64);
+
+ error = fwnode_property_count_u64(node, "prop-u64");
+ KUNIT_EXPECT_EQ(test, error, 1);
+
+ error = fwnode_property_read_u64_array(node, "prop-u64", array_u64, 1);
+ KUNIT_EXPECT_EQ(test, error, 0);
+ KUNIT_EXPECT_EQ(test, array_u64[0], 64);
+
+ error = fwnode_property_read_u64_array(node, "prop-u64", array_u64, 2);
+ KUNIT_EXPECT_NE(test, error, 0);
+
+ error = fwnode_property_read_u64(node, "no-prop-u64", &val_u64);
+ KUNIT_EXPECT_NE(test, error, 0);
+
+ error = fwnode_property_read_u64_array(node, "no-prop-u64", array_u64, 1);
+ KUNIT_EXPECT_NE(test, error, 0);
+
+ /* Count 64-bit values as 16-bit */
+ error = fwnode_property_count_u16(node, "prop-u64");
+ KUNIT_EXPECT_EQ(test, error, 4);
+
+ fwnode_remove_software_node(node);
+}
+
+static void pe_test_uint_arrays(struct kunit *test)
+{
+ static const u8 a_u8[10] = { 8, 9 };
+ static const u16 a_u16[10] = { 16, 17 };
+ static const u32 a_u32[10] = { 32, 33 };
+ static const u64 a_u64[10] = { 64, 65 };
+ static const struct property_entry entries[] = {
+ PROPERTY_ENTRY_U8_ARRAY("prop-u8", a_u8),
+ PROPERTY_ENTRY_U16_ARRAY("prop-u16", a_u16),
+ PROPERTY_ENTRY_U32_ARRAY("prop-u32", a_u32),
+ PROPERTY_ENTRY_U64_ARRAY("prop-u64", a_u64),
+ { }
+ };
+
+ struct fwnode_handle *node;
+ u8 val_u8, array_u8[32];
+ u16 val_u16, array_u16[32];
+ u32 val_u32, array_u32[32];
+ u64 val_u64, array_u64[32];
+ int error;
+
+ node = fwnode_create_software_node(entries, NULL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, node);
+
+ error = fwnode_property_read_u8(node, "prop-u8", &val_u8);
+ KUNIT_EXPECT_EQ(test, error, 0);
+ KUNIT_EXPECT_EQ(test, val_u8, 8);
+
+ error = fwnode_property_count_u8(node, "prop-u8");
+ KUNIT_EXPECT_EQ(test, error, 10);
+
+ error = fwnode_property_read_u8_array(node, "prop-u8", array_u8, 1);
+ KUNIT_EXPECT_EQ(test, error, 0);
+ KUNIT_EXPECT_EQ(test, array_u8[0], 8);
+
+ error = fwnode_property_read_u8_array(node, "prop-u8", array_u8, 2);
+ KUNIT_EXPECT_EQ(test, error, 0);
+ KUNIT_EXPECT_EQ(test, array_u8[0], 8);
+ KUNIT_EXPECT_EQ(test, array_u8[1], 9);
+
+ error = fwnode_property_read_u8_array(node, "prop-u8", array_u8, 17);
+ KUNIT_EXPECT_NE(test, error, 0);
+
+ error = fwnode_property_read_u8(node, "no-prop-u8", &val_u8);
+ KUNIT_EXPECT_NE(test, error, 0);
+
+ error = fwnode_property_read_u8_array(node, "no-prop-u8", array_u8, 1);
+ KUNIT_EXPECT_NE(test, error, 0);
+
+ error = fwnode_property_read_u16(node, "prop-u16", &val_u16);
+ KUNIT_EXPECT_EQ(test, error, 0);
+ KUNIT_EXPECT_EQ(test, val_u16, 16);
+
+ error = fwnode_property_count_u16(node, "prop-u16");
+ KUNIT_EXPECT_EQ(test, error, 10);
+
+ error = fwnode_property_read_u16_array(node, "prop-u16", array_u16, 1);
+ KUNIT_EXPECT_EQ(test, error, 0);
+ KUNIT_EXPECT_EQ(test, array_u16[0], 16);
+
+ error = fwnode_property_read_u16_array(node, "prop-u16", array_u16, 2);
+ KUNIT_EXPECT_EQ(test, error, 0);
+ KUNIT_EXPECT_EQ(test, array_u16[0], 16);
+ KUNIT_EXPECT_EQ(test, array_u16[1], 17);
+
+ error = fwnode_property_read_u16_array(node, "prop-u16", array_u16, 17);
+ KUNIT_EXPECT_NE(test, error, 0);
+
+ error = fwnode_property_read_u16(node, "no-prop-u16", &val_u16);
+ KUNIT_EXPECT_NE(test, error, 0);
+
+ error = fwnode_property_read_u16_array(node, "no-prop-u16", array_u16, 1);
+ KUNIT_EXPECT_NE(test, error, 0);
+
+ error = fwnode_property_read_u32(node, "prop-u32", &val_u32);
+ KUNIT_EXPECT_EQ(test, error, 0);
+ KUNIT_EXPECT_EQ(test, val_u32, 32);
+
+ error = fwnode_property_count_u32(node, "prop-u32");
+ KUNIT_EXPECT_EQ(test, error, 10);
+
+ error = fwnode_property_read_u32_array(node, "prop-u32", array_u32, 1);
+ KUNIT_EXPECT_EQ(test, error, 0);
+ KUNIT_EXPECT_EQ(test, array_u32[0], 32);
+
+ error = fwnode_property_read_u32_array(node, "prop-u32", array_u32, 2);
+ KUNIT_EXPECT_EQ(test, error, 0);
+ KUNIT_EXPECT_EQ(test, array_u32[0], 32);
+ KUNIT_EXPECT_EQ(test, array_u32[1], 33);
+
+ error = fwnode_property_read_u32_array(node, "prop-u32", array_u32, 17);
+ KUNIT_EXPECT_NE(test, error, 0);
+
+ error = fwnode_property_read_u32(node, "no-prop-u32", &val_u32);
+ KUNIT_EXPECT_NE(test, error, 0);
+
+ error = fwnode_property_read_u32_array(node, "no-prop-u32", array_u32, 1);
+ KUNIT_EXPECT_NE(test, error, 0);
+
+ error = fwnode_property_read_u64(node, "prop-u64", &val_u64);
+ KUNIT_EXPECT_EQ(test, error, 0);
+ KUNIT_EXPECT_EQ(test, val_u64, 64);
+
+ error = fwnode_property_count_u64(node, "prop-u64");
+ KUNIT_EXPECT_EQ(test, error, 10);
+
+ error = fwnode_property_read_u64_array(node, "prop-u64", array_u64, 1);
+ KUNIT_EXPECT_EQ(test, error, 0);
+ KUNIT_EXPECT_EQ(test, array_u64[0], 64);
+
+ error = fwnode_property_read_u64_array(node, "prop-u64", array_u64, 2);
+ KUNIT_EXPECT_EQ(test, error, 0);
+ KUNIT_EXPECT_EQ(test, array_u64[0], 64);
+ KUNIT_EXPECT_EQ(test, array_u64[1], 65);
+
+ error = fwnode_property_read_u64_array(node, "prop-u64", array_u64, 17);
+ KUNIT_EXPECT_NE(test, error, 0);
+
+ error = fwnode_property_read_u64(node, "no-prop-u64", &val_u64);
+ KUNIT_EXPECT_NE(test, error, 0);
+
+ error = fwnode_property_read_u64_array(node, "no-prop-u64", array_u64, 1);
+ KUNIT_EXPECT_NE(test, error, 0);
+
+ /* Count 64-bit values as 16-bit */
+ error = fwnode_property_count_u16(node, "prop-u64");
+ KUNIT_EXPECT_EQ(test, error, 40);
+
+ /* Other way around */
+ error = fwnode_property_count_u64(node, "prop-u16");
+ KUNIT_EXPECT_EQ(test, error, 2);
+
+ fwnode_remove_software_node(node);
+}
+
+static void pe_test_strings(struct kunit *test)
+{
+ static const char *strings[] = {
+ "string-a",
+ "string-b",
+ };
+
+ static const struct property_entry entries[] = {
+ PROPERTY_ENTRY_STRING("str", "single"),
+ PROPERTY_ENTRY_STRING("empty", ""),
+ PROPERTY_ENTRY_STRING_ARRAY("strs", strings),
+ { }
+ };
+
+ struct fwnode_handle *node;
+ const char *str;
+ const char *strs[10];
+ int error;
+
+ node = fwnode_create_software_node(entries, NULL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, node);
+
+ error = fwnode_property_read_string(node, "str", &str);
+ KUNIT_EXPECT_EQ(test, error, 0);
+ KUNIT_EXPECT_STREQ(test, str, "single");
+
+ error = fwnode_property_string_array_count(node, "str");
+ KUNIT_EXPECT_EQ(test, error, 1);
+
+ error = fwnode_property_read_string_array(node, "str", strs, 1);
+ KUNIT_EXPECT_EQ(test, error, 1);
+ KUNIT_EXPECT_STREQ(test, strs[0], "single");
+
+ /* asking for more data returns what we have */
+ error = fwnode_property_read_string_array(node, "str", strs, 2);
+ KUNIT_EXPECT_EQ(test, error, 1);
+ KUNIT_EXPECT_STREQ(test, strs[0], "single");
+
+ error = fwnode_property_read_string(node, "no-str", &str);
+ KUNIT_EXPECT_NE(test, error, 0);
+
+ error = fwnode_property_read_string_array(node, "no-str", strs, 1);
+ KUNIT_EXPECT_LT(test, error, 0);
+
+ error = fwnode_property_read_string(node, "empty", &str);
+ KUNIT_EXPECT_EQ(test, error, 0);
+ KUNIT_EXPECT_STREQ(test, str, "");
+
+ error = fwnode_property_string_array_count(node, "strs");
+ KUNIT_EXPECT_EQ(test, error, 2);
+
+ error = fwnode_property_read_string_array(node, "strs", strs, 3);
+ KUNIT_EXPECT_EQ(test, error, 2);
+ KUNIT_EXPECT_STREQ(test, strs[0], "string-a");
+ KUNIT_EXPECT_STREQ(test, strs[1], "string-b");
+
+ error = fwnode_property_read_string_array(node, "strs", strs, 1);
+ KUNIT_EXPECT_EQ(test, error, 1);
+ KUNIT_EXPECT_STREQ(test, strs[0], "string-a");
+
+ /* NULL argument -> returns size */
+ error = fwnode_property_read_string_array(node, "strs", NULL, 0);
+ KUNIT_EXPECT_EQ(test, error, 2);
+
+ /* accessing array as single value */
+ error = fwnode_property_read_string(node, "strs", &str);
+ KUNIT_EXPECT_EQ(test, error, 0);
+ KUNIT_EXPECT_STREQ(test, str, "string-a");
+
+ fwnode_remove_software_node(node);
+}
+
+static void pe_test_bool(struct kunit *test)
+{
+ static const struct property_entry entries[] = {
+ PROPERTY_ENTRY_BOOL("prop"),
+ { }
+ };
+
+ struct fwnode_handle *node;
+
+ node = fwnode_create_software_node(entries, NULL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, node);
+
+ KUNIT_EXPECT_TRUE(test, fwnode_property_read_bool(node, "prop"));
+ KUNIT_EXPECT_FALSE(test, fwnode_property_read_bool(node, "not-prop"));
+
+ fwnode_remove_software_node(node);
+}
+
+/* Verifies that small U8 array is stored inline when property is copied */
+static void pe_test_move_inline_u8(struct kunit *test)
+{
+ static const u8 u8_array_small[8] = { 1, 2, 3, 4 };
+ static const u8 u8_array_big[128] = { 5, 6, 7, 8 };
+ static const struct property_entry entries[] = {
+ PROPERTY_ENTRY_U8_ARRAY("small", u8_array_small),
+ PROPERTY_ENTRY_U8_ARRAY("big", u8_array_big),
+ { }
+ };
+
+ struct property_entry *copy;
+ const u8 *data_ptr;
+
+ copy = property_entries_dup(entries);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, copy);
+
+ KUNIT_EXPECT_TRUE(test, copy[0].is_inline);
+ data_ptr = (u8 *)&copy[0].value;
+ KUNIT_EXPECT_EQ(test, data_ptr[0], 1);
+ KUNIT_EXPECT_EQ(test, data_ptr[1], 2);
+
+ KUNIT_EXPECT_FALSE(test, copy[1].is_inline);
+ data_ptr = copy[1].pointer;
+ KUNIT_EXPECT_EQ(test, data_ptr[0], 5);
+ KUNIT_EXPECT_EQ(test, data_ptr[1], 6);
+
+ property_entries_free(copy);
+}
+
+/* Verifies that single string array is stored inline when property is copied */
+static void pe_test_move_inline_str(struct kunit *test)
+{
+ static char *str_array_small[] = { "a" };
+ static char *str_array_big[] = { "b", "c", "d", "e" };
+ static char *str_array_small_empty[] = { "" };
+ static struct property_entry entries[] = {
+ PROPERTY_ENTRY_STRING_ARRAY("small", str_array_small),
+ PROPERTY_ENTRY_STRING_ARRAY("big", str_array_big),
+ PROPERTY_ENTRY_STRING_ARRAY("small-empty", str_array_small_empty),
+ { }
+ };
+
+ struct property_entry *copy;
+ const char * const *data_ptr;
+
+ copy = property_entries_dup(entries);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, copy);
+
+ KUNIT_EXPECT_TRUE(test, copy[0].is_inline);
+ KUNIT_EXPECT_STREQ(test, copy[0].value.str[0], "a");
+
+ KUNIT_EXPECT_FALSE(test, copy[1].is_inline);
+ data_ptr = copy[1].pointer;
+ KUNIT_EXPECT_STREQ(test, data_ptr[0], "b");
+ KUNIT_EXPECT_STREQ(test, data_ptr[1], "c");
+
+ KUNIT_EXPECT_TRUE(test, copy[2].is_inline);
+ KUNIT_EXPECT_STREQ(test, copy[2].value.str[0], "");
+
+ property_entries_free(copy);
+}
+
+/* Handling of reference properties */
+static void pe_test_reference(struct kunit *test)
+{
+ static const struct software_node node1 = { .name = "1" };
+ static const struct software_node node2 = { .name = "2" };
+ static const struct software_node *group[] = { &node1, &node2, NULL };
+
+ static const struct software_node_ref_args refs[] = {
+ SOFTWARE_NODE_REFERENCE(&node1),
+ SOFTWARE_NODE_REFERENCE(&node2, 3, 4),
+ };
+
+ const struct property_entry entries[] = {
+ PROPERTY_ENTRY_REF("ref-1", &node1),
+ PROPERTY_ENTRY_REF("ref-2", &node2, 1, 2),
+ PROPERTY_ENTRY_REF_ARRAY("ref-3", refs),
+ { }
+ };
+
+ struct fwnode_handle *node;
+ struct fwnode_reference_args ref;
+ int error;
+
+ error = software_node_register_node_group(group);
+ KUNIT_ASSERT_EQ(test, error, 0);
+
+ node = fwnode_create_software_node(entries, NULL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, node);
+
+ error = fwnode_property_get_reference_args(node, "ref-1", NULL,
+ 0, 0, &ref);
+ KUNIT_ASSERT_EQ(test, error, 0);
+ KUNIT_EXPECT_PTR_EQ(test, to_software_node(ref.fwnode), &node1);
+ KUNIT_EXPECT_EQ(test, ref.nargs, 0U);
+
+ /* wrong index */
+ error = fwnode_property_get_reference_args(node, "ref-1", NULL,
+ 0, 1, &ref);
+ KUNIT_EXPECT_NE(test, error, 0);
+
+ error = fwnode_property_get_reference_args(node, "ref-2", NULL,
+ 1, 0, &ref);
+ KUNIT_ASSERT_EQ(test, error, 0);
+ KUNIT_EXPECT_PTR_EQ(test, to_software_node(ref.fwnode), &node2);
+ KUNIT_EXPECT_EQ(test, ref.nargs, 1U);
+ KUNIT_EXPECT_EQ(test, ref.args[0], 1LLU);
+
+ /* asking for more args, padded with zero data */
+ error = fwnode_property_get_reference_args(node, "ref-2", NULL,
+ 3, 0, &ref);
+ KUNIT_ASSERT_EQ(test, error, 0);
+ KUNIT_EXPECT_PTR_EQ(test, to_software_node(ref.fwnode), &node2);
+ KUNIT_EXPECT_EQ(test, ref.nargs, 3U);
+ KUNIT_EXPECT_EQ(test, ref.args[0], 1LLU);
+ KUNIT_EXPECT_EQ(test, ref.args[1], 2LLU);
+ KUNIT_EXPECT_EQ(test, ref.args[2], 0LLU);
+
+ /* wrong index */
+ error = fwnode_property_get_reference_args(node, "ref-2", NULL,
+ 2, 1, &ref);
+ KUNIT_EXPECT_NE(test, error, 0);
+
+ /* array of references */
+ error = fwnode_property_get_reference_args(node, "ref-3", NULL,
+ 0, 0, &ref);
+ KUNIT_ASSERT_EQ(test, error, 0);
+ KUNIT_EXPECT_PTR_EQ(test, to_software_node(ref.fwnode), &node1);
+ KUNIT_EXPECT_EQ(test, ref.nargs, 0U);
+
+ /* second reference in the array */
+ error = fwnode_property_get_reference_args(node, "ref-3", NULL,
+ 2, 1, &ref);
+ KUNIT_ASSERT_EQ(test, error, 0);
+ KUNIT_EXPECT_PTR_EQ(test, to_software_node(ref.fwnode), &node2);
+ KUNIT_EXPECT_EQ(test, ref.nargs, 2U);
+ KUNIT_EXPECT_EQ(test, ref.args[0], 3LLU);
+ KUNIT_EXPECT_EQ(test, ref.args[1], 4LLU);
+
+ /* wrong index */
+ error = fwnode_property_get_reference_args(node, "ref-1", NULL,
+ 0, 2, &ref);
+ KUNIT_EXPECT_NE(test, error, 0);
+
+ fwnode_remove_software_node(node);
+ software_node_unregister_node_group(group);
+}
+
+static struct fwnode_handle *create_device_node(struct kunit *test,
+ const char *name,
+ const char *full_name,
+ struct device_node *parent)
+{
+ struct device_node *node;
+
+ node = kunit_kzalloc(test, sizeof(*node), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, node);
+
+ node->name = kunit_kstrdup(test, name, GFP_KERNEL);
+ node->full_name = kunit_kstrdup(test, full_name, GFP_KERNEL);
+
+ if (parent) {
+ node->sibling = parent->child;
+ /* set the node as the first child of the parent */
+ parent->child = node;
+ node->parent = parent;
+ }
+
+ of_node_init(node);
+ return of_fwnode_handle(node);
+}
+
+/* Verifies that fwnode_for_each_child_node() can output correct children */
+static void pe_test_child_iteration(struct kunit *test)
+{
+ struct fwnode_handle *of_node, *of_node1;
+ struct fwnode_handle *sw_node, *sw_node1;
+ struct fwnode_handle *child;
+ int error, i, num;
+
+ if (!IS_ENABLED(CONFIG_OF))
+ kunit_skip(test, "requires CONFIG_OF");
+
+ static const struct software_node node = { .name = "sw" };
+ static const struct software_node node1 = { .name = "sw-1", .parent = &node};
+ static const struct software_node node2 = { .name = "sw-2", .parent = &node};
+ static const struct software_node node3 = { .name = "sw-3", .parent = &node};
+ static const struct software_node *group[] = { &node, &node1, &node2, &node3, NULL };
+
+ static const char * const of_child_array[] = { "of-1", "of-2", "of-3" };
+ static const char * const sw_child_array[] = { "sw-1", "sw-2", "sw-3" };
+ static const char * const of_sw_child_array[] = { "of-1", "of-2", "of-3",
+ "sw-1", "sw-2", "sw-3" };
+ static const char * const sw_of_child_array[] = { "sw-1", "sw-2", "sw-3",
+ "of-1", "of-2", "of-3" };
+
+ /* 1. Test OF node child iteration */
+
+ of_node = create_device_node(test, "of", "of", NULL);
+ create_device_node(test, "of", "of-3", to_of_node(of_node));
+ create_device_node(test, "of", "of-2", to_of_node(of_node));
+ of_node1 = create_device_node(test, "of", "of-1", to_of_node(of_node));
+
+ i = 0;
+ num = ARRAY_SIZE(of_child_array);
+ fwnode_for_each_child_node(of_node, child) {
+ KUNIT_ASSERT_LT(test, i, num);
+ KUNIT_EXPECT_STREQ(test, of_child_array[i++], fwnode_get_name(child));
+ }
+ KUNIT_EXPECT_PTR_EQ(test, child, NULL);
+
+ /* 2. Test SW node child iteration */
+
+ error = software_node_register_node_group(group);
+ KUNIT_ASSERT_EQ(test, error, 0);
+
+ sw_node = software_node_fwnode(&node);
+
+ i = 0;
+ num = ARRAY_SIZE(sw_child_array);
+ fwnode_for_each_child_node(sw_node, child) {
+ KUNIT_ASSERT_LT(test, i, num);
+ KUNIT_EXPECT_STREQ(test, sw_child_array[i++], fwnode_get_name(child));
+ }
+ KUNIT_EXPECT_PTR_EQ(test, child, NULL);
+
+ /* 3. Test OF (primary) + SW (secondary) node child iteration */
+
+ of_node->secondary = sw_node;
+ sw_node->secondary = ERR_PTR(-ENODEV);
+
+ i = 0;
+ num = ARRAY_SIZE(of_sw_child_array);
+ fwnode_for_each_child_node(of_node, child) {
+ KUNIT_ASSERT_LT(test, i, num);
+ KUNIT_EXPECT_STREQ(test, of_sw_child_array[i++], fwnode_get_name(child));
+ }
+ KUNIT_EXPECT_PTR_EQ(test, child, NULL);
+
+ /* 4. Test SW (primary) + OF (secondary) node child iteration */
+
+ sw_node->secondary = of_node;
+ of_node->secondary = ERR_PTR(-ENODEV);
+
+ i = 0;
+ num = ARRAY_SIZE(sw_of_child_array);
+ fwnode_for_each_child_node(sw_node, child) {
+ KUNIT_ASSERT_LT(test, i, num);
+ KUNIT_EXPECT_STREQ(test, sw_of_child_array[i++], fwnode_get_name(child));
+ }
+ KUNIT_EXPECT_PTR_EQ(test, child, NULL);
+
+ /* 5. Test OF (primary) + SW (secondary, but no children) node child iteration */
+
+ sw_node1 = software_node_fwnode(&node1);
+ of_node->secondary = sw_node1;
+ sw_node->secondary = ERR_PTR(-ENODEV);
+
+ i = 0;
+ num = ARRAY_SIZE(of_child_array);
+ fwnode_for_each_child_node(of_node, child) {
+ KUNIT_ASSERT_LT(test, i, num);
+ KUNIT_EXPECT_STREQ(test, of_child_array[i++], fwnode_get_name(child));
+ }
+ KUNIT_EXPECT_PTR_EQ(test, child, NULL);
+
+ /* 6. Test SW (primary) + OF (secondary, but no children) node child iteration */
+
+ sw_node->secondary = of_node1;
+ of_node->secondary = ERR_PTR(-ENODEV);
+
+ i = 0;
+ num = ARRAY_SIZE(sw_child_array);
+ fwnode_for_each_child_node(sw_node, child) {
+ KUNIT_ASSERT_LT(test, i, num);
+ KUNIT_EXPECT_STREQ(test, sw_child_array[i++], fwnode_get_name(child));
+ }
+ KUNIT_EXPECT_PTR_EQ(test, child, NULL);
+
+ of_node->secondary = NULL;
+ sw_node->secondary = NULL;
+ software_node_unregister_node_group(group);
+}
+
+static struct kunit_case property_entry_test_cases[] = {
+ KUNIT_CASE(pe_test_uints),
+ KUNIT_CASE(pe_test_uint_arrays),
+ KUNIT_CASE(pe_test_strings),
+ KUNIT_CASE(pe_test_bool),
+ KUNIT_CASE(pe_test_move_inline_u8),
+ KUNIT_CASE(pe_test_move_inline_str),
+ KUNIT_CASE(pe_test_reference),
+ KUNIT_CASE(pe_test_child_iteration),
+ { }
+};
+
+static struct kunit_suite property_entry_test_suite = {
+ .name = "property-entry",
+ .test_cases = property_entry_test_cases,
+};
+
+kunit_test_suite(property_entry_test_suite);
+
+MODULE_DESCRIPTION("Test module for the property entry API");
+MODULE_AUTHOR("Dmitry Torokhov <dtor@chromium.org>");
+MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");
+MODULE_LICENSE("GPL");
diff --git a/drivers/base/test/root-device-test.c b/drivers/base/test/root-device-test.c
new file mode 100644
index 000000000..9aea23c91
--- /dev/null
+++ b/drivers/base/test/root-device-test.c
@@ -0,0 +1,112 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright 2023 Maxime Ripard <mripard@kernel.org>
+
+#include <kunit/resource.h>
+
+#include <linux/device.h>
+
+#define DEVICE_NAME "test"
+
+struct test_priv {
+ bool probe_done;
+ bool release_done;
+ wait_queue_head_t release_wq;
+ struct device *dev;
+};
+
+static int root_device_devm_init(struct kunit *test)
+{
+ struct test_priv *priv;
+
+ priv = kunit_kzalloc(test, sizeof(*priv), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv);
+ init_waitqueue_head(&priv->release_wq);
+
+ test->priv = priv;
+
+ return 0;
+}
+
+static void devm_device_action(void *ptr)
+{
+ struct test_priv *priv = ptr;
+
+ priv->release_done = true;
+ wake_up_interruptible(&priv->release_wq);
+}
+
+#define RELEASE_TIMEOUT_MS 100
+
+/*
+ * Tests that a bus-less, non-probed device will run its device-managed
+ * actions when unregistered.
+ */
+static void root_device_devm_register_unregister_test(struct kunit *test)
+{
+ struct test_priv *priv = test->priv;
+ int ret;
+
+ priv->dev = root_device_register(DEVICE_NAME);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->dev);
+
+ ret = devm_add_action_or_reset(priv->dev, devm_device_action, priv);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ root_device_unregister(priv->dev);
+
+ ret = wait_event_interruptible_timeout(priv->release_wq, priv->release_done,
+ msecs_to_jiffies(RELEASE_TIMEOUT_MS));
+ KUNIT_EXPECT_GT(test, ret, 0);
+}
+
+static void devm_put_device_action(void *ptr)
+{
+ struct test_priv *priv = ptr;
+
+ put_device(priv->dev);
+ priv->release_done = true;
+ wake_up_interruptible(&priv->release_wq);
+}
+
+/*
+ * Tests that a bus-less, non-probed device will run its device-managed
+ * actions when unregistered, even if someone still holds a reference to
+ * it.
+ */
+static void root_device_devm_register_get_unregister_with_devm_test(struct kunit *test)
+{
+ struct test_priv *priv = test->priv;
+ int ret;
+
+ priv->dev = root_device_register(DEVICE_NAME);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->dev);
+
+ get_device(priv->dev);
+
+ ret = devm_add_action_or_reset(priv->dev, devm_put_device_action, priv);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ root_device_unregister(priv->dev);
+
+ ret = wait_event_interruptible_timeout(priv->release_wq, priv->release_done,
+ msecs_to_jiffies(RELEASE_TIMEOUT_MS));
+ KUNIT_EXPECT_GT(test, ret, 0);
+}
+
+static struct kunit_case root_device_devm_tests[] = {
+ KUNIT_CASE(root_device_devm_register_unregister_test),
+ KUNIT_CASE(root_device_devm_register_get_unregister_with_devm_test),
+ {}
+};
+
+static struct kunit_suite root_device_devm_test_suite = {
+ .name = "root-device-devm",
+ .init = root_device_devm_init,
+ .test_cases = root_device_devm_tests,
+};
+
+kunit_test_suite(root_device_devm_test_suite);
+
+MODULE_DESCRIPTION("Test module for root devices");
+MODULE_AUTHOR("Maxime Ripard <mripard@kernel.org>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/base/test/swnode-devlink-test.c b/drivers/base/test/swnode-devlink-test.c
new file mode 100644
index 000000000..f53549e92
--- /dev/null
+++ b/drivers/base/test/swnode-devlink-test.c
@@ -0,0 +1,337 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) Qualcomm Technologies, Inc. and/or its subsidiaries
+ */
+
+#include <linux/array_size.h>
+#include <linux/device.h>
+#include <linux/fwnode.h>
+#include <linux/list.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/time.h>
+#include <linux/types.h>
+#include <linux/wait.h>
+
+#include <kunit/fwnode.h>
+#include <kunit/platform_device.h>
+#include <kunit/test.h>
+
+static int swnode_count_suppliers(struct fwnode_handle *fwnode)
+{
+ struct fwnode_link *link;
+ unsigned int count = 0;
+
+ /*
+ * The suppliers and consumers lists should typically only be accessed
+ * with the fwnode_link_lock taken but it's private to the driver core.
+ *
+ * These are tests and at this point nobody should be modifying them so
+ * let's just access the list.
+ */
+ list_for_each_entry(link, &fwnode->suppliers, c_hook)
+ count++;
+
+ return count;
+}
+
+/* True if a supplier link con->sup exists, checked from both list ends. */
+static bool swnode_has_link(struct fwnode_handle *consumer,
+ struct fwnode_handle *supplier)
+{
+ bool from_con = false, from_sup = false;
+ struct fwnode_link *link;
+
+ list_for_each_entry(link, &consumer->suppliers, c_hook) {
+ if (link->supplier == supplier && link->consumer == consumer)
+ from_con = true;
+ }
+
+ list_for_each_entry(link, &supplier->consumers, s_hook) {
+ if (link->supplier == supplier && link->consumer == consumer)
+ from_sup = true;
+ }
+
+ return from_con && from_sup;
+}
+
+/* A single reference creates exactly one supplier link, on both list ends. */
+static void swnode_devlink_test_single_ref(struct kunit *test)
+{
+ static const struct software_node supp_swnode = {
+ .name = "swnode-devlink-test-supplier",
+ };
+
+ struct fwnode_handle *cons_fwnode, *supp_fwnode;
+ int ret;
+
+ const struct property_entry props[] = {
+ PROPERTY_ENTRY_REF("supplier", &supp_swnode),
+ { }
+ };
+
+ supp_fwnode = kunit_software_node_register(test, &supp_swnode);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, supp_fwnode);
+
+ cons_fwnode = kunit_fwnode_create_software_node(test, props, NULL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cons_fwnode);
+
+ ret = fwnode_call_int_op(cons_fwnode, add_links);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+
+ KUNIT_EXPECT_EQ(test, swnode_count_suppliers(cons_fwnode), 1);
+ KUNIT_EXPECT_TRUE(test, swnode_has_link(cons_fwnode, supp_fwnode));
+}
+
+/* Multiple distinct references create multiple supplier links. */
+static void swnode_devlink_test_multiple_refs(struct kunit *test)
+{
+ static const struct software_node supp1_swnode = {
+ .name = "swnode-devlink-test-supplier-1",
+ };
+ static const struct software_node supp2_swnode = {
+ .name = "swnode-devlink-test-supplier-2",
+ };
+ static const struct software_node *supp_nodes[] = {
+ &supp1_swnode, &supp2_swnode, NULL
+ };
+
+ const struct property_entry props[] = {
+ PROPERTY_ENTRY_REF("foo", &supp1_swnode),
+ PROPERTY_ENTRY_REF("bar", &supp2_swnode),
+ { }
+ };
+
+ struct fwnode_handle *fwnode;
+ int ret;
+
+ ret = kunit_software_node_register_node_group(test, supp_nodes);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ fwnode = kunit_fwnode_create_software_node(test, props, NULL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fwnode);
+
+ ret = fwnode_call_int_op(fwnode, add_links);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+
+ KUNIT_EXPECT_EQ(test, swnode_count_suppliers(fwnode), 2);
+ KUNIT_EXPECT_TRUE(test, swnode_has_link(fwnode, software_node_fwnode(&supp1_swnode)));
+ KUNIT_EXPECT_TRUE(test, swnode_has_link(fwnode, software_node_fwnode(&supp2_swnode)));
+}
+
+/* A reference to an unregistered node creates no link (graceful skip). */
+static void swnode_devlink_test_unregistered_ref(struct kunit *test)
+{
+ static const struct software_node supp_swnode = {
+ .name = "swnode-devlink-test-supplier",
+ };
+
+ const struct property_entry props[] = {
+ PROPERTY_ENTRY_REF("supplier", &supp_swnode),
+ { }
+ };
+
+ struct fwnode_handle *fwnode;
+ int ret;
+
+ fwnode = kunit_fwnode_create_software_node(test, props, NULL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fwnode);
+
+ ret = fwnode_call_int_op(fwnode, add_links);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, swnode_count_suppliers(fwnode), 0);
+}
+
+/* Graph "remote-endpoint" references are excluded. */
+static void swnode_devlink_test_remote_endpoint_excluded(struct kunit *test)
+{
+ static const struct software_node ep_swnode = {
+ .name = "swnode-devlink-test-end-point"
+ };
+
+ const struct property_entry props[] = {
+ PROPERTY_ENTRY_REF("remote-endpoint", &ep_swnode),
+ { }
+ };
+
+ struct fwnode_handle *cons_fwnode, *supp_fwnode;
+ int ret;
+
+ supp_fwnode = kunit_software_node_register(test, &ep_swnode);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, supp_fwnode);
+
+ cons_fwnode = kunit_fwnode_create_software_node(test, props, NULL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cons_fwnode);
+
+ ret = fwnode_call_int_op(cons_fwnode, add_links);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, swnode_count_suppliers(cons_fwnode), 0);
+}
+
+/* A reference array creates one link per registered element. */
+static void swnode_devlink_test_ref_array(struct kunit *test)
+{
+ static const struct software_node supp1_swnode = {
+ .name = "swnode-devlink-test-supplier-1",
+ };
+ static const struct software_node supp2_swnode = {
+ .name = "swnode-devlink-test-supplier-2",
+ };
+ static const struct software_node *supp_nodes[] = {
+ &supp1_swnode, &supp2_swnode, NULL
+ };
+ static const struct software_node_ref_args refs[] = {
+ SOFTWARE_NODE_REFERENCE(&supp1_swnode),
+ SOFTWARE_NODE_REFERENCE(&supp2_swnode, 4, 2),
+ };
+
+ const struct property_entry props[] = {
+ PROPERTY_ENTRY_REF_ARRAY("suppliers", refs),
+ { }
+ };
+
+ struct fwnode_handle *fwnode;
+ int ret;
+
+ ret = kunit_software_node_register_node_group(test, supp_nodes);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ fwnode = kunit_fwnode_create_software_node(test, props, NULL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fwnode);
+
+ ret = fwnode_call_int_op(fwnode, add_links);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+
+ KUNIT_EXPECT_EQ(test, swnode_count_suppliers(fwnode), 2);
+ KUNIT_EXPECT_TRUE(test, swnode_has_link(fwnode, software_node_fwnode(&supp1_swnode)));
+ KUNIT_EXPECT_TRUE(test, swnode_has_link(fwnode, software_node_fwnode(&supp2_swnode)));
+}
+
+/*
+ * End-to-end test: fw_devlink must defer a consumer's probe until its
+ * supplier has probed.
+ *
+ * The reference created by software_node_add_links() is only useful if the
+ * driver core promotes it to a real device_link and uses it to order probing.
+ * This test drives actual probing through the platform bus and asserts the
+ * supplier binds before the consumer.
+ */
+
+#define SWNODE_DEVLINK_TEST_SUPPLIER "swnode-link-supplier"
+#define SWNODE_DEVLINK_TEST_CONSUMER "swnode-link-consumer"
+#define SWNODE_DEVLINK_TEST_TIMEOUT_MS (2 * MSEC_PER_SEC)
+
+struct swnode_test_probe_order {
+ /* Names in the order their drivers' .probe ran. */
+ const char *probed[2];
+ unsigned int count;
+ wait_queue_head_t wq;
+};
+
+static int swnode_test_record_probe(struct platform_device *pdev)
+{
+ struct swnode_test_probe_order *order = platform_get_drvdata(pdev);
+
+ if (order && order->count < ARRAY_SIZE(order->probed)) {
+ order->probed[order->count++] = dev_name(&pdev->dev);
+ wake_up_interruptible(&order->wq);
+ }
+
+ return 0;
+}
+
+static struct platform_driver swnode_test_supplier_driver = {
+ .probe = swnode_test_record_probe,
+ .driver = {
+ .name = SWNODE_DEVLINK_TEST_SUPPLIER,
+ },
+};
+
+static struct platform_driver swnode_test_consumer_driver = {
+ .probe = swnode_test_record_probe,
+ .driver = {
+ .name = SWNODE_DEVLINK_TEST_CONSUMER,
+ },
+};
+
+static void swnode_devlink_test_probe_order(struct kunit *test)
+{
+ static const struct software_node supplier_swnode = {
+ .name = "swnode-devlink-test-supplier",
+ };
+
+ const struct property_entry consumer_props[] = {
+ PROPERTY_ENTRY_REF("supplier-ref", &supplier_swnode),
+ { }
+ };
+
+ struct platform_device *supplier, *consumer;
+ struct swnode_test_probe_order *order;
+ struct fwnode_handle *fwnode;
+ int ret;
+
+ order = kunit_kzalloc(test, sizeof(*order), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, order);
+ init_waitqueue_head(&order->wq);
+
+ fwnode = kunit_software_node_register(test, &supplier_swnode);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fwnode);
+
+ ret = kunit_platform_driver_register(test, &swnode_test_supplier_driver);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+ ret = kunit_platform_driver_register(test, &swnode_test_consumer_driver);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ supplier = kunit_platform_device_alloc(test, SWNODE_DEVLINK_TEST_SUPPLIER,
+ PLATFORM_DEVID_NONE);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, supplier);
+ consumer = kunit_platform_device_alloc(test, SWNODE_DEVLINK_TEST_CONSUMER,
+ PLATFORM_DEVID_NONE);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, consumer);
+
+ platform_set_drvdata(supplier, order);
+ platform_set_drvdata(consumer, order);
+
+ ret = kunit_device_add_software_node(test, &supplier->dev, &supplier_swnode);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+ ret = device_create_managed_software_node(&consumer->dev,
+ consumer_props, NULL);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ ret = kunit_platform_device_add(test, consumer);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+ ret = kunit_platform_device_add(test, supplier);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ ret = wait_event_interruptible_timeout(order->wq,
+ order->count == 2,
+ msecs_to_jiffies(SWNODE_DEVLINK_TEST_TIMEOUT_MS));
+ KUNIT_ASSERT_GT(test, ret, 0);
+
+ KUNIT_EXPECT_STREQ(test, order->probed[0], SWNODE_DEVLINK_TEST_SUPPLIER);
+ KUNIT_EXPECT_STREQ(test, order->probed[1], SWNODE_DEVLINK_TEST_CONSUMER);
+
+ /* Tear down the consumer (and its device link) before the supplier. */
+ kunit_platform_device_unregister(test, consumer);
+}
+
+static struct kunit_case swnode_test_cases[] = {
+ KUNIT_CASE(swnode_devlink_test_single_ref),
+ KUNIT_CASE(swnode_devlink_test_multiple_refs),
+ KUNIT_CASE(swnode_devlink_test_unregistered_ref),
+ KUNIT_CASE(swnode_devlink_test_remote_endpoint_excluded),
+ KUNIT_CASE(swnode_devlink_test_ref_array),
+ KUNIT_CASE(swnode_devlink_test_probe_order),
+ { }
+};
+
+static struct kunit_suite swnode_test_suite = {
+ .name = "software-node-links",
+ .test_cases = swnode_test_cases,
+};
+
+kunit_test_suite(swnode_test_suite);
+
+MODULE_DESCRIPTION("Test module for software node fw_devlink support");
+MODULE_AUTHOR("Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/base/test/test_async_driver_probe.c b/drivers/base/test/test_async_driver_probe.c
new file mode 100644
index 000000000..3465800ba
--- /dev/null
+++ b/drivers/base/test/test_async_driver_probe.c
@@ -0,0 +1,299 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2014 Google, Inc.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/hrtimer.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/time.h>
+#include <linux/numa.h>
+#include <linux/nodemask.h>
+#include <linux/topology.h>
+
+#define TEST_PROBE_DELAY (5 * 1000) /* 5 sec */
+#define TEST_PROBE_THRESHOLD (TEST_PROBE_DELAY / 2)
+
+static atomic_t warnings, errors, timeout, async_completed;
+
+static int test_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+
+ /*
+ * Determine if we have hit the "timeout" limit for the test if we
+ * have then report it as an error, otherwise we wil sleep for the
+ * required amount of time and then report completion.
+ */
+ if (atomic_read(&timeout)) {
+ dev_err(dev, "async probe took too long\n");
+ atomic_inc(&errors);
+ } else {
+ dev_dbg(&pdev->dev, "sleeping for %d msecs in probe\n",
+ TEST_PROBE_DELAY);
+ msleep(TEST_PROBE_DELAY);
+ dev_dbg(&pdev->dev, "done sleeping\n");
+ }
+
+ /*
+ * Report NUMA mismatch if device node is set and we are not
+ * performing an async init on that node.
+ */
+ if (dev->driver->probe_type == PROBE_PREFER_ASYNCHRONOUS) {
+ if (IS_ENABLED(CONFIG_NUMA) &&
+ dev_to_node(dev) != numa_node_id()) {
+ dev_warn(dev, "NUMA node mismatch %d != %d\n",
+ dev_to_node(dev), numa_node_id());
+ atomic_inc(&warnings);
+ }
+
+ atomic_inc(&async_completed);
+ }
+
+ return 0;
+}
+
+static struct platform_driver async_driver = {
+ .driver = {
+ .name = "test_async_driver",
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+ .probe = test_probe,
+};
+
+static struct platform_driver sync_driver = {
+ .driver = {
+ .name = "test_sync_driver",
+ .probe_type = PROBE_FORCE_SYNCHRONOUS,
+ },
+ .probe = test_probe,
+};
+
+static struct platform_device *async_dev[NR_CPUS * 2];
+static struct platform_device *sync_dev[2];
+
+static struct platform_device *
+test_platform_device_register_node(char *name, int id, int nid)
+{
+ struct platform_device *pdev;
+ int ret;
+
+ pdev = platform_device_alloc(name, id);
+ if (!pdev)
+ return ERR_PTR(-ENOMEM);
+
+ if (nid != NUMA_NO_NODE)
+ set_dev_node(&pdev->dev, nid);
+
+ ret = platform_device_add(pdev);
+ if (ret) {
+ platform_device_put(pdev);
+ return ERR_PTR(ret);
+ }
+
+ return pdev;
+
+}
+
+static int __init test_async_probe_init(void)
+{
+ struct platform_device **pdev = NULL;
+ int async_id = 0, sync_id = 0;
+ unsigned long long duration;
+ ktime_t calltime;
+ int err, nid, cpu;
+
+ pr_info("registering first set of asynchronous devices...\n");
+
+ for_each_online_cpu(cpu) {
+ nid = cpu_to_node(cpu);
+ pdev = &async_dev[async_id];
+ *pdev = test_platform_device_register_node("test_async_driver",
+ async_id,
+ nid);
+ if (IS_ERR(*pdev)) {
+ err = PTR_ERR(*pdev);
+ *pdev = NULL;
+ pr_err("failed to create async_dev: %d\n", err);
+ goto err_unregister_async_devs;
+ }
+
+ async_id++;
+ }
+
+ pr_info("registering asynchronous driver...\n");
+ calltime = ktime_get();
+ err = platform_driver_register(&async_driver);
+ if (err) {
+ pr_err("Failed to register async_driver: %d\n", err);
+ goto err_unregister_async_devs;
+ }
+
+ duration = (unsigned long long)ktime_ms_delta(ktime_get(), calltime);
+ pr_info("registration took %lld msecs\n", duration);
+ if (duration > TEST_PROBE_THRESHOLD) {
+ pr_err("test failed: probe took too long\n");
+ err = -ETIMEDOUT;
+ goto err_unregister_async_driver;
+ }
+
+ pr_info("registering second set of asynchronous devices...\n");
+ calltime = ktime_get();
+ for_each_online_cpu(cpu) {
+ nid = cpu_to_node(cpu);
+ pdev = &async_dev[async_id];
+
+ *pdev = test_platform_device_register_node("test_async_driver",
+ async_id,
+ nid);
+ if (IS_ERR(*pdev)) {
+ err = PTR_ERR(*pdev);
+ *pdev = NULL;
+ pr_err("failed to create async_dev: %d\n", err);
+ goto err_unregister_async_driver;
+ }
+
+ async_id++;
+ }
+
+ duration = (unsigned long long)ktime_ms_delta(ktime_get(), calltime);
+ dev_info(&(*pdev)->dev,
+ "registration took %lld msecs\n", duration);
+ if (duration > TEST_PROBE_THRESHOLD) {
+ dev_err(&(*pdev)->dev,
+ "test failed: probe took too long\n");
+ err = -ETIMEDOUT;
+ goto err_unregister_async_driver;
+ }
+
+
+ pr_info("registering first synchronous device...\n");
+ nid = cpu_to_node(cpu);
+ pdev = &sync_dev[sync_id];
+
+ *pdev = test_platform_device_register_node("test_sync_driver",
+ sync_id,
+ NUMA_NO_NODE);
+ if (IS_ERR(*pdev)) {
+ err = PTR_ERR(*pdev);
+ *pdev = NULL;
+ pr_err("failed to create sync_dev: %d\n", err);
+ goto err_unregister_async_driver;
+ }
+
+ sync_id++;
+
+ pr_info("registering synchronous driver...\n");
+ calltime = ktime_get();
+ err = platform_driver_register(&sync_driver);
+ if (err) {
+ pr_err("Failed to register async_driver: %d\n", err);
+ goto err_unregister_sync_devs;
+ }
+
+ duration = (unsigned long long)ktime_ms_delta(ktime_get(), calltime);
+ pr_info("registration took %lld msecs\n", duration);
+ if (duration < TEST_PROBE_THRESHOLD) {
+ dev_err(&(*pdev)->dev,
+ "test failed: probe was too quick\n");
+ err = -ETIMEDOUT;
+ goto err_unregister_sync_driver;
+ }
+
+ pr_info("registering second synchronous device...\n");
+ pdev = &sync_dev[sync_id];
+ calltime = ktime_get();
+
+ *pdev = test_platform_device_register_node("test_sync_driver",
+ sync_id,
+ NUMA_NO_NODE);
+ if (IS_ERR(*pdev)) {
+ err = PTR_ERR(*pdev);
+ *pdev = NULL;
+ pr_err("failed to create sync_dev: %d\n", err);
+ goto err_unregister_sync_driver;
+ }
+
+ sync_id++;
+
+ duration = (unsigned long long)ktime_ms_delta(ktime_get(), calltime);
+ dev_info(&(*pdev)->dev,
+ "registration took %lld msecs\n", duration);
+ if (duration < TEST_PROBE_THRESHOLD) {
+ dev_err(&(*pdev)->dev,
+ "test failed: probe was too quick\n");
+ err = -ETIMEDOUT;
+ goto err_unregister_sync_driver;
+ }
+
+ /*
+ * The async events should have completed while we were taking care
+ * of the synchronous events. We will now terminate any outstanding
+ * asynchronous probe calls remaining by forcing timeout and remove
+ * the driver before we return which should force the flush of the
+ * pending asynchronous probe calls.
+ *
+ * Otherwise if they completed without errors or warnings then
+ * report successful completion.
+ */
+ if (atomic_read(&async_completed) != async_id) {
+ pr_err("async events still pending, forcing timeout\n");
+ atomic_inc(&timeout);
+ err = -ETIMEDOUT;
+ } else if (!atomic_read(&errors) && !atomic_read(&warnings)) {
+ pr_info("completed successfully\n");
+ return 0;
+ }
+
+err_unregister_sync_driver:
+ platform_driver_unregister(&sync_driver);
+err_unregister_sync_devs:
+ while (sync_id--)
+ platform_device_unregister(sync_dev[sync_id]);
+err_unregister_async_driver:
+ platform_driver_unregister(&async_driver);
+err_unregister_async_devs:
+ while (async_id--)
+ platform_device_unregister(async_dev[async_id]);
+
+ /*
+ * If err is already set then count that as an additional error for
+ * the test. Otherwise we will report an invalid argument error and
+ * not count that as we should have reached here as a result of
+ * errors or warnings being reported by the probe routine.
+ */
+ if (err)
+ atomic_inc(&errors);
+ else
+ err = -EINVAL;
+
+ pr_err("Test failed with %d errors and %d warnings\n",
+ atomic_read(&errors), atomic_read(&warnings));
+
+ return err;
+}
+module_init(test_async_probe_init);
+
+static void __exit test_async_probe_exit(void)
+{
+ int id = 2;
+
+ platform_driver_unregister(&async_driver);
+ platform_driver_unregister(&sync_driver);
+
+ while (id--)
+ platform_device_unregister(sync_dev[id]);
+
+ id = NR_CPUS * 2;
+ while (id--)
+ platform_device_unregister(async_dev[id]);
+}
+module_exit(test_async_probe_exit);
+
+MODULE_DESCRIPTION("Test module for asynchronous driver probing");
+MODULE_AUTHOR("Dmitry Torokhov <dtor@chromium.org>");
+MODULE_LICENSE("GPL");