// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2026 Qualcomm Technologies, Inc. and/or its subsidiaries */ #include #include #include #include #include #include #include #include #include #include #include #define PWRSEQ_TEST_PARENT "pwrseq-test-parent" #define PWRSEQ_TEST_CONSUMER "pwrseq-test-consumer" #define PWRSEQ_SWNODE_TEST_PROVIDER "pwrseq-swnode-test-provider" #define PWRSEQ_SWNODE_TEST_PROVIDER_2 "pwrseq-swnode-test-provider-2" #define PWRSEQ_PROBE_ORDER_TEST_CONSUMER "pwrseq-probe-order-test-consumer" #define PWRSEQ_PROBE_DEFER_TEST_CONSUMER "pwrseq-probe-defer-test-consumer" static const struct software_node pwrseq_test_provider_swnode = { .name = "pwrseq-test-provider", }; KUNIT_DEFINE_ACTION_WRAPPER(pwrseq_device_unregister_wrapper, pwrseq_device_unregister, struct pwrseq_device *); static struct pwrseq_device * kunit_pwrseq_device_register(struct kunit *test, const struct pwrseq_config *config) { struct pwrseq_device *pwrseq; int ret; pwrseq = pwrseq_device_register(config); if (IS_ERR(pwrseq)) return pwrseq; ret = kunit_add_action_or_reset(test, pwrseq_device_unregister_wrapper, pwrseq); if (ret) return ERR_PTR(ret); return pwrseq; } KUNIT_DEFINE_ACTION_WRAPPER(pwrseq_put_wrapper, pwrseq_put, struct pwrseq_desc *); static struct pwrseq_desc * kunit_pwrseq_get(struct kunit *test, struct device *dev, const char *target) { struct pwrseq_desc *desc; int ret; desc = pwrseq_get(dev, target); if (IS_ERR(desc)) return desc; ret = kunit_add_action_or_reset(test, pwrseq_put_wrapper, desc); if (ret) return ERR_PTR(ret); return desc; } struct pwrseq_test_ctx { const char *consumer_name; int enable_count_a; int enable_count_b; int disable_count_a; int disable_count_b; int enable_calls_a; int post_enable_calls; bool enable_error_a; bool enable_error_b; bool post_enable_error; }; static int pwrseq_test_no_match(struct pwrseq_device *pwrseq, struct device *dev) { return PWRSEQ_NO_MATCH; } static int pwrseq_test_match_by_name(struct pwrseq_device *pwrseq, struct device *dev) { struct pwrseq_test_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); return strcmp(dev_name(dev), ctx->consumer_name) == 0 ? PWRSEQ_MATCH_OK : PWRSEQ_NO_MATCH; } static int pwrseq_test_match_always(struct pwrseq_device *pwrseq, struct device *dev) { return PWRSEQ_MATCH_OK; } static int pwrseq_test_enable_a(struct pwrseq_device *pwrseq) { struct pwrseq_test_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); ctx->enable_calls_a++; if (ctx->enable_error_a) return -EIO; ctx->enable_count_a++; return 0; } static int pwrseq_test_disable_a(struct pwrseq_device *pwrseq) { struct pwrseq_test_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); ctx->disable_count_a++; return 0; } static int pwrseq_test_enable_b(struct pwrseq_device *pwrseq) { struct pwrseq_test_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); if (ctx->enable_error_b) return -EIO; ctx->enable_count_b++; return 0; } static int pwrseq_test_disable_b(struct pwrseq_device *pwrseq) { struct pwrseq_test_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); ctx->disable_count_b++; return 0; } static int pwrseq_test_post_enable(struct pwrseq_device *pwrseq) { struct pwrseq_test_ctx *ctx = pwrseq_device_get_drvdata(pwrseq); ctx->post_enable_calls++; return ctx->post_enable_error ? -EIO : 0; } static int pwrseq_test_parent_probe(struct platform_device *pdev) { return 0; } static struct platform_driver pwrseq_test_parent_driver = { .probe = pwrseq_test_parent_probe, .driver = { .name = PWRSEQ_TEST_PARENT, }, }; static int pwrseq_test_parent_init(struct kunit *test) { static const struct platform_device_info pdevinfo = { .name = PWRSEQ_TEST_PARENT, .id = PLATFORM_DEVID_NONE, }; struct platform_device *pdev; bool bound; int ret; ret = kunit_platform_driver_register(test, &pwrseq_test_parent_driver); KUNIT_ASSERT_EQ(test, ret, 0); pdev = kunit_platform_device_register_full(test, &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); test->priv = pdev; return 0; } /* * Test that registering a power sequencer whose unit dependency graph * contains a two-node cycle (A -> B -> A) is rejected with -EINVAL. */ static void pwrseq_circular_deps(struct kunit *test) { const struct pwrseq_unit_data *unit_a_deps[2] = { }; const struct pwrseq_unit_data *unit_b_deps[2] = { }; const struct pwrseq_target_data *targets[2] = { }; struct platform_device *pdev = test->priv; struct pwrseq_unit_data unit_a, unit_b; struct pwrseq_target_data target; struct pwrseq_device *pwrseq; struct pwrseq_config config; unit_a = (struct pwrseq_unit_data){ .name = "unit-a", .deps = unit_a_deps, }; unit_b = (struct pwrseq_unit_data){ .name = "unit-b", .deps = unit_b_deps, }; unit_a_deps[0] = &unit_b; unit_b_deps[0] = &unit_a; target = (struct pwrseq_target_data){ .name = "test-target", .unit = &unit_a, }; targets[0] = ⌖ config = (struct pwrseq_config){ .parent = &pdev->dev, .match = pwrseq_test_no_match, .targets = targets, }; kunit_warning_suppress(test) { pwrseq = kunit_pwrseq_device_register(test, &config); KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1); } KUNIT_EXPECT_TRUE(test, IS_ERR(pwrseq)); KUNIT_EXPECT_EQ(test, PTR_ERR(pwrseq), -EINVAL); } /* * Test that a longer chain cycle (A -> B -> C -> D -> A) is also rejected * with -EINVAL. */ static void pwrseq_circular_deps_chain(struct kunit *test) { struct pwrseq_unit_data unit_a, unit_b, unit_c, unit_d; const struct pwrseq_unit_data *unit_a_deps[2] = { }; const struct pwrseq_unit_data *unit_b_deps[2] = { }; const struct pwrseq_unit_data *unit_c_deps[2] = { }; const struct pwrseq_unit_data *unit_d_deps[2] = { }; const struct pwrseq_target_data *targets[2] = { }; struct platform_device *pdev = test->priv; struct pwrseq_target_data target; struct pwrseq_device *pwrseq; struct pwrseq_config config; unit_a = (struct pwrseq_unit_data){ .name = "unit-a", .deps = unit_a_deps, }; unit_b = (struct pwrseq_unit_data){ .name = "unit-b", .deps = unit_b_deps, }; unit_c = (struct pwrseq_unit_data){ .name = "unit-c", .deps = unit_c_deps, }; unit_d = (struct pwrseq_unit_data){ .name = "unit-d", .deps = unit_d_deps, }; unit_a_deps[0] = &unit_b; unit_b_deps[0] = &unit_c; unit_c_deps[0] = &unit_d; unit_d_deps[0] = &unit_a; target = (struct pwrseq_target_data){ .name = "test-target", .unit = &unit_a, }; targets[0] = ⌖ config = (struct pwrseq_config){ .parent = &pdev->dev, .match = pwrseq_test_no_match, .targets = targets, }; kunit_warning_suppress(test) { pwrseq = kunit_pwrseq_device_register(test, &config); KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1); } KUNIT_EXPECT_TRUE(test, IS_ERR(pwrseq)); KUNIT_EXPECT_EQ(test, PTR_ERR(pwrseq), -EINVAL); } /* * Test that a valid acyclic dependency graph (A -> B) registers successfully. */ static void pwrseq_register_valid(struct kunit *test) { const struct pwrseq_unit_data *unit_a_deps[2] = { }; const struct pwrseq_target_data *targets[2] = { }; struct platform_device *pdev = test->priv; struct pwrseq_unit_data unit_a, unit_b; struct pwrseq_target_data target; struct pwrseq_device *pwrseq; struct pwrseq_config config; unit_b = (struct pwrseq_unit_data){ .name = "unit-b", }; unit_a_deps[0] = &unit_b; unit_a = (struct pwrseq_unit_data){ .name = "unit-a", .deps = unit_a_deps, }; target = (struct pwrseq_target_data){ .name = "test-target", .unit = &unit_a, }; targets[0] = ⌖ config = (struct pwrseq_config){ .parent = &pdev->dev, .match = pwrseq_test_no_match, .targets = targets, }; pwrseq = kunit_pwrseq_device_register(test, &config); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pwrseq); } /* * Test that registration is rejected with -EINVAL when .parent, .match or * .targets is missing, or when .targets is a non-NULL but empty array. */ static void pwrseq_register_invalid_args(struct kunit *test) { const struct pwrseq_target_data *empty_targets[1] = { }; const struct pwrseq_target_data *targets[2] = { }; struct platform_device *pdev = test->priv; struct pwrseq_target_data target; struct pwrseq_unit_data unit; struct pwrseq_device *pwrseq; struct pwrseq_config config; unit = (struct pwrseq_unit_data){ .name = "unit-a" }; target = (struct pwrseq_target_data){ .name = "test-target", .unit = &unit, }; targets[0] = ⌖ config = (struct pwrseq_config){ .parent = &pdev->dev, .match = pwrseq_test_no_match, .targets = targets, }; config.parent = NULL; pwrseq = kunit_pwrseq_device_register(test, &config); KUNIT_EXPECT_TRUE(test, IS_ERR(pwrseq)); KUNIT_EXPECT_EQ(test, PTR_ERR(pwrseq), -EINVAL); config.parent = &pdev->dev; config.match = NULL; pwrseq = kunit_pwrseq_device_register(test, &config); KUNIT_EXPECT_TRUE(test, IS_ERR(pwrseq)); KUNIT_EXPECT_EQ(test, PTR_ERR(pwrseq), -EINVAL); config.match = pwrseq_test_no_match; config.targets = NULL; pwrseq = kunit_pwrseq_device_register(test, &config); KUNIT_EXPECT_TRUE(test, IS_ERR(pwrseq)); KUNIT_EXPECT_EQ(test, PTR_ERR(pwrseq), -EINVAL); config.targets = empty_targets; pwrseq = kunit_pwrseq_device_register(test, &config); KUNIT_EXPECT_TRUE(test, IS_ERR(pwrseq)); KUNIT_EXPECT_EQ(test, PTR_ERR(pwrseq), -EINVAL); } /* * Test that a target without a unit is rejected with -EINVAL. */ static void pwrseq_register_target_without_unit(struct kunit *test) { const struct pwrseq_target_data *targets[2] = { }; struct platform_device *pdev = test->priv; struct pwrseq_target_data target; struct pwrseq_device *pwrseq; struct pwrseq_config config; target = (struct pwrseq_target_data){ .name = "test-target", .unit = NULL, }; targets[0] = ⌖ config = (struct pwrseq_config){ .parent = &pdev->dev, .match = pwrseq_test_no_match, .targets = targets, }; pwrseq = kunit_pwrseq_device_register(test, &config); KUNIT_EXPECT_TRUE(test, IS_ERR(pwrseq)); KUNIT_EXPECT_EQ(test, PTR_ERR(pwrseq), -EINVAL); } /* * Test that enabling and disabling a single-unit target increments and * decrements enable_count correctly and fires the enable/disable callbacks * exactly once. */ static void pwrseq_enable_disable(struct kunit *test) { const struct pwrseq_target_data *targets[2] = { }; struct platform_device *pdev = test->priv; struct platform_device_info pdevinfo; struct pwrseq_target_data target; struct pwrseq_test_ctx *ctx; struct pwrseq_unit_data unit; struct pwrseq_device *pwrseq; struct pwrseq_config config; struct pwrseq_desc *desc; int ret; ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); unit = (struct pwrseq_unit_data){ .name = "unit-a", .enable = pwrseq_test_enable_a, .disable = pwrseq_test_disable_a, }; target = (struct pwrseq_target_data){ .name = "test-target", .unit = &unit, }; targets[0] = ⌖ ctx->consumer_name = PWRSEQ_TEST_CONSUMER; config = (struct pwrseq_config){ .parent = &pdev->dev, .drvdata = ctx, .match = pwrseq_test_match_by_name, .targets = targets, }; pwrseq = kunit_pwrseq_device_register(test, &config); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pwrseq); pdevinfo = (struct platform_device_info){ .name = PWRSEQ_TEST_CONSUMER, .id = PLATFORM_DEVID_NONE, }; pdev = kunit_platform_device_register_full(test, &pdevinfo); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev); desc = kunit_pwrseq_get(test, &pdev->dev, "test-target"); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, desc); ret = pwrseq_enable(desc); KUNIT_EXPECT_EQ(test, ret, 0); KUNIT_EXPECT_EQ(test, ctx->enable_count_a, 1); KUNIT_EXPECT_EQ(test, ctx->disable_count_a, 0); ret = pwrseq_disable(desc); KUNIT_EXPECT_EQ(test, ret, 0); KUNIT_EXPECT_EQ(test, ctx->enable_count_a, 1); KUNIT_EXPECT_EQ(test, ctx->disable_count_a, 1); } /* * Test that two consumers sharing a common dependency unit cause it to be * enabled only once and disabled only once, while enable_count tracks each * consumer correctly. */ static void pwrseq_shared_deps(struct kunit *test) { const struct pwrseq_unit_data *unit_a_deps[2] = { }; const struct pwrseq_unit_data *unit_b_deps[2] = { }; const struct pwrseq_target_data *targets[3] = { }; struct pwrseq_unit_data dep_unit, unit_a, unit_b; struct pwrseq_target_data target_a, target_b; struct platform_device *parent = test->priv; struct platform_device *pdev_a, *pdev_b; struct platform_device_info pdevinfo; struct pwrseq_desc *desc_a, *desc_b; struct pwrseq_device *pwrseq; struct pwrseq_config config; struct pwrseq_test_ctx *ctx; int ret; ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); dep_unit = (struct pwrseq_unit_data){ .name = "dep", .enable = pwrseq_test_enable_a, .disable = pwrseq_test_disable_a, }; unit_a_deps[0] = &dep_unit; unit_a = (struct pwrseq_unit_data){ .name = "unit-a", .deps = unit_a_deps, .enable = pwrseq_test_enable_b, .disable = pwrseq_test_disable_b, }; unit_b_deps[0] = &dep_unit; unit_b = (struct pwrseq_unit_data){ .name = "unit-b", .deps = unit_b_deps, }; target_a = (struct pwrseq_target_data){ .name = "target-a", .unit = &unit_a, }; target_b = (struct pwrseq_target_data){ .name = "target-b", .unit = &unit_b, }; targets[0] = &target_a; targets[1] = &target_b; ctx->consumer_name = PWRSEQ_TEST_CONSUMER "-a"; config = (struct pwrseq_config){ .parent = &parent->dev, .drvdata = ctx, .match = pwrseq_test_match_by_name, .targets = targets, }; pwrseq = kunit_pwrseq_device_register(test, &config); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pwrseq); pdevinfo = (struct platform_device_info){ .name = PWRSEQ_TEST_CONSUMER "-a", .id = PLATFORM_DEVID_NONE, }; pdev_a = kunit_platform_device_register_full(test, &pdevinfo); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev_a); pdevinfo = (struct platform_device_info){ .name = PWRSEQ_TEST_CONSUMER "-b", .id = PLATFORM_DEVID_NONE, }; pdev_b = kunit_platform_device_register_full(test, &pdevinfo); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev_b); /* * match_by_name() only matches one consumer name at a time, so * reconfigure ctx->consumer_name between the two pwrseq_get() calls * below to acquire descriptors for both consumer-a and consumer-b. */ desc_a = kunit_pwrseq_get(test, &pdev_a->dev, "target-a"); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, desc_a); ctx->consumer_name = PWRSEQ_TEST_CONSUMER "-b"; desc_b = kunit_pwrseq_get(test, &pdev_b->dev, "target-b"); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, desc_b); ret = pwrseq_enable(desc_a); KUNIT_EXPECT_EQ(test, ret, 0); KUNIT_EXPECT_EQ(test, ctx->enable_count_a, 1); KUNIT_EXPECT_EQ(test, ctx->enable_count_b, 1); ret = pwrseq_enable(desc_b); KUNIT_EXPECT_EQ(test, ret, 0); KUNIT_EXPECT_EQ(test, ctx->enable_count_a, 1); ret = pwrseq_disable(desc_a); KUNIT_EXPECT_EQ(test, ret, 0); KUNIT_EXPECT_EQ(test, ctx->disable_count_a, 0); KUNIT_EXPECT_EQ(test, ctx->disable_count_b, 1); ret = pwrseq_disable(desc_b); KUNIT_EXPECT_EQ(test, ret, 0); KUNIT_EXPECT_EQ(test, ctx->disable_count_a, 1); } /* * Test that calling pwrseq_enable() twice on the same descriptor has no * effect. */ static void pwrseq_enable_idempotent(struct kunit *test) { const struct pwrseq_target_data *targets[2] = { }; struct platform_device *pdev = test->priv; struct platform_device_info pdevinfo; struct pwrseq_target_data target; struct pwrseq_device *pwrseq; struct pwrseq_unit_data unit; struct pwrseq_config config; struct pwrseq_test_ctx *ctx; struct pwrseq_desc *desc; int ret; ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); unit = (struct pwrseq_unit_data){ .name = "unit-a", .enable = pwrseq_test_enable_a, .disable = pwrseq_test_disable_a, }; target = (struct pwrseq_target_data){ .name = "test-target", .unit = &unit, }; targets[0] = ⌖ ctx->consumer_name = PWRSEQ_TEST_CONSUMER; config = (struct pwrseq_config){ .parent = &pdev->dev, .drvdata = ctx, .match = pwrseq_test_match_by_name, .targets = targets, }; pwrseq = kunit_pwrseq_device_register(test, &config); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pwrseq); pdevinfo = (struct platform_device_info){ .name = PWRSEQ_TEST_CONSUMER, .id = PLATFORM_DEVID_NONE, }; pdev = kunit_platform_device_register_full(test, &pdevinfo); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev); desc = kunit_pwrseq_get(test, &pdev->dev, "test-target"); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, desc); ret = pwrseq_enable(desc); KUNIT_EXPECT_EQ(test, ret, 0); KUNIT_EXPECT_EQ(test, ctx->enable_count_a, 1); /* Second power_on on same descriptor must be a no-op. */ ret = pwrseq_enable(desc); KUNIT_EXPECT_EQ(test, ret, 0); KUNIT_EXPECT_EQ(test, ctx->enable_count_a, 1); ret = pwrseq_disable(desc); KUNIT_EXPECT_EQ(test, ret, 0); KUNIT_EXPECT_EQ(test, ctx->disable_count_a, 1); } /* * Test that when a dependency unit's enable() callback fails, the error * propagates to the caller, the top unit's enable callback is never reached, * and target->post_enable() is never invoked. */ static void pwrseq_enable_enable_error(struct kunit *test) { const struct pwrseq_target_data *targets[2] = { }; const struct pwrseq_unit_data *top_deps[2] = { }; struct pwrseq_unit_data dep_unit, top_unit; struct platform_device *pdev = test->priv; struct platform_device_info pdevinfo; struct pwrseq_target_data target; struct pwrseq_device *pwrseq; struct pwrseq_config config; struct pwrseq_test_ctx *ctx; struct pwrseq_desc *desc; int ret; ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); dep_unit = (struct pwrseq_unit_data){ .name = "dep", .enable = pwrseq_test_enable_a, }; top_deps[0] = &dep_unit; top_unit = (struct pwrseq_unit_data){ .name = "top", .deps = top_deps, .enable = pwrseq_test_enable_b, }; target = (struct pwrseq_target_data){ .name = "test-target", .unit = &top_unit, .post_enable = pwrseq_test_post_enable, }; targets[0] = ⌖ ctx->consumer_name = PWRSEQ_TEST_CONSUMER; ctx->enable_error_a = true; config = (struct pwrseq_config){ .parent = &pdev->dev, .drvdata = ctx, .match = pwrseq_test_match_by_name, .targets = targets, }; pwrseq = kunit_pwrseq_device_register(test, &config); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pwrseq); pdevinfo = (struct platform_device_info){ .name = PWRSEQ_TEST_CONSUMER, .id = PLATFORM_DEVID_NONE, }; pdev = kunit_platform_device_register_full(test, &pdevinfo); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev); desc = kunit_pwrseq_get(test, &pdev->dev, "test-target"); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, desc); ret = pwrseq_enable(desc); KUNIT_EXPECT_EQ(test, ret, -EIO); /* * Dependency's enable() was attempted but failed, so it never * incremented. */ KUNIT_EXPECT_EQ(test, ctx->enable_calls_a, 1); KUNIT_EXPECT_EQ(test, ctx->enable_count_a, 0); /* Target unit's enable() was never reached. */ KUNIT_EXPECT_EQ(test, ctx->enable_count_b, 0); /* Target's .post_enable() must not run on an unpowered sequencer. */ KUNIT_EXPECT_EQ(test, ctx->post_enable_calls, 0); } /* * Test that when the top unit's own enable() callback fails after its * dependency's enable() already succeeded, the dependency is rolled back. */ static void pwrseq_enable_rollback_on_top_failure(struct kunit *test) { const struct pwrseq_target_data *targets[2] = { }; const struct pwrseq_unit_data *top_deps[2] = { }; struct pwrseq_unit_data dep_unit, top_unit; struct platform_device *pdev = test->priv; struct platform_device_info pdevinfo; struct pwrseq_target_data target; struct pwrseq_device *pwrseq; struct pwrseq_config config; struct pwrseq_test_ctx *ctx; struct pwrseq_desc *desc; int ret; ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); dep_unit = (struct pwrseq_unit_data){ .name = "dep", .enable = pwrseq_test_enable_a, .disable = pwrseq_test_disable_a, }; top_deps[0] = &dep_unit; top_unit = (struct pwrseq_unit_data){ .name = "top", .deps = top_deps, .enable = pwrseq_test_enable_b, }; target = (struct pwrseq_target_data){ .name = "test-target", .unit = &top_unit, }; targets[0] = ⌖ ctx->consumer_name = PWRSEQ_TEST_CONSUMER; ctx->enable_error_b = true; config = (struct pwrseq_config){ .parent = &pdev->dev, .drvdata = ctx, .match = pwrseq_test_match_by_name, .targets = targets, }; pwrseq = kunit_pwrseq_device_register(test, &config); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pwrseq); pdevinfo = (struct platform_device_info){ .name = PWRSEQ_TEST_CONSUMER, .id = PLATFORM_DEVID_NONE, }; pdev = kunit_platform_device_register_full(test, &pdevinfo); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev); desc = kunit_pwrseq_get(test, &pdev->dev, "test-target"); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, desc); ret = pwrseq_enable(desc); KUNIT_EXPECT_EQ(test, ret, -EIO); /* * Dependency was enabled, then rolled back once top's own enable * failed. */ KUNIT_EXPECT_EQ(test, ctx->enable_count_a, 1); KUNIT_EXPECT_EQ(test, ctx->disable_count_a, 1); /* * Target unit's own enable never incremented since it returned an * error. */ KUNIT_EXPECT_EQ(test, ctx->enable_count_b, 0); } /* * Test target->post_enable() on both the success and failure paths: it must * run exactly once per pwrseq_enable() call that actually powers the target * unit on, and a failing post_enable() must roll the unit back and clear * powered_on so a subsequent pwrseq_enable() is called again. */ static void pwrseq_enable_post_enable(struct kunit *test) { const struct pwrseq_target_data *targets[2] = { }; struct platform_device *pdev = test->priv; struct platform_device_info pdevinfo; struct pwrseq_target_data target; struct pwrseq_unit_data unit; struct pwrseq_device *pwrseq; struct pwrseq_config config; struct pwrseq_test_ctx *ctx; struct pwrseq_desc *desc; int ret; ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); unit = (struct pwrseq_unit_data){ .name = "unit-a", .enable = pwrseq_test_enable_a, .disable = pwrseq_test_disable_a, }; target = (struct pwrseq_target_data){ .name = "test-target", .unit = &unit, .post_enable = pwrseq_test_post_enable, }; targets[0] = ⌖ ctx->consumer_name = PWRSEQ_TEST_CONSUMER; config = (struct pwrseq_config){ .parent = &pdev->dev, .drvdata = ctx, .match = pwrseq_test_match_by_name, .targets = targets, }; pwrseq = kunit_pwrseq_device_register(test, &config); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pwrseq); pdevinfo = (struct platform_device_info){ .name = PWRSEQ_TEST_CONSUMER, .id = PLATFORM_DEVID_NONE, }; pdev = kunit_platform_device_register_full(test, &pdevinfo); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev); desc = kunit_pwrseq_get(test, &pdev->dev, "test-target"); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, desc); /* Success path: post_enable() runs once and reports success. */ ret = pwrseq_enable(desc); KUNIT_EXPECT_EQ(test, ret, 0); KUNIT_EXPECT_EQ(test, ctx->post_enable_calls, 1); KUNIT_EXPECT_EQ(test, ctx->enable_count_a, 1); ret = pwrseq_disable(desc); KUNIT_ASSERT_EQ(test, ret, 0); /* Failure path: post_enable() fails, unit is rolled back. */ ctx->post_enable_error = true; ret = pwrseq_enable(desc); KUNIT_EXPECT_EQ(test, ret, -EIO); KUNIT_EXPECT_EQ(test, ctx->post_enable_calls, 2); KUNIT_EXPECT_EQ(test, ctx->enable_count_a, 2); KUNIT_EXPECT_EQ(test, ctx->disable_count_a, 2); /* powered_on must have been cleared. */ ret = pwrseq_enable(desc); KUNIT_EXPECT_EQ(test, ret, -EIO); KUNIT_EXPECT_EQ(test, ctx->enable_count_a, 3); } /* * Test that pwrseq_get() returns -ENOENT when the matched provider does not * have the requested target name. */ static void pwrseq_get_target_not_found(struct kunit *test) { const struct pwrseq_target_data *targets[2] = { }; struct platform_device *pdev = test->priv; struct platform_device_info pdevinfo; struct pwrseq_target_data target; struct pwrseq_unit_data unit; struct pwrseq_device *pwrseq; struct pwrseq_config config; struct pwrseq_desc *desc; unit = (struct pwrseq_unit_data){ .name = "unit-a", }; target = (struct pwrseq_target_data){ .name = "real-target", .unit = &unit, }; targets[0] = ⌖ config = (struct pwrseq_config){ .parent = &pdev->dev, .match = pwrseq_test_match_always, .targets = targets, }; pwrseq = kunit_pwrseq_device_register(test, &config); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pwrseq); pdevinfo = (struct platform_device_info){ .name = PWRSEQ_TEST_CONSUMER, .id = PLATFORM_DEVID_NONE, }; pdev = kunit_platform_device_register_full(test, &pdevinfo); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev); desc = kunit_pwrseq_get(test, &pdev->dev, "nonexistent-target"); KUNIT_EXPECT_TRUE(test, IS_ERR(desc)); KUNIT_EXPECT_EQ(test, PTR_ERR(desc), -ENOENT); } /* * Test that pwrseq_put() on a descriptor that is still powered on disables * it as part of tear-down, instead of leaking the power-on state. */ static void pwrseq_put_disables_powered_desc(struct kunit *test) { const struct pwrseq_target_data *targets[2] = { }; struct platform_device *pdev = test->priv; struct platform_device_info pdevinfo; struct pwrseq_target_data target; struct pwrseq_unit_data unit; struct pwrseq_device *pwrseq; struct pwrseq_test_ctx *ctx; struct pwrseq_config config; struct pwrseq_desc *desc; int ret; ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); unit = (struct pwrseq_unit_data){ .name = "unit-a", .enable = pwrseq_test_enable_a, .disable = pwrseq_test_disable_a, }; target = (struct pwrseq_target_data){ .name = "test-target", .unit = &unit, }; targets[0] = ⌖ config = (struct pwrseq_config){ .parent = &pdev->dev, .drvdata = ctx, .match = pwrseq_test_match_always, .targets = targets, }; pwrseq = kunit_pwrseq_device_register(test, &config); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pwrseq); pdevinfo = (struct platform_device_info){ .name = PWRSEQ_TEST_CONSUMER, .id = PLATFORM_DEVID_NONE, }; pdev = kunit_platform_device_register_full(test, &pdevinfo); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev); desc = kunit_pwrseq_get(test, &pdev->dev, "test-target"); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, desc); ret = pwrseq_enable(desc); KUNIT_ASSERT_EQ(test, ret, 0); KUNIT_EXPECT_EQ(test, ctx->disable_count_a, 0); /* * Run the deferred pwrseq_put() action now instead of at test * teardown, so its effect can be asserted below. */ kunit_release_action(test, pwrseq_put_wrapper, desc); KUNIT_EXPECT_EQ(test, ctx->disable_count_a, 1); } /* * Test that pwrseq_device_unregister() on a target with an active user * triggers "REMOVING POWER SEQUENCER WITH ACTIVE USERS", and that both * pwrseq_enable() and pwrseq_disable() start returning -ENODEV for * previously acquired descriptors afterwards. */ static void pwrseq_unregister_with_active_user(struct kunit *test) { struct platform_device *pdev = test->priv, *cons_a, *cons_b; const struct pwrseq_target_data *targets[2] = { }; struct platform_device_info pdevinfo; struct pwrseq_desc *desc_a, *desc_b; struct pwrseq_target_data target; struct pwrseq_unit_data unit; struct pwrseq_device *pwrseq; struct pwrseq_test_ctx *ctx; struct pwrseq_config config; bool bound; int ret; ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); unit = (struct pwrseq_unit_data){ .name = "unit-a", .enable = pwrseq_test_enable_a, .disable = pwrseq_test_disable_a, }; target = (struct pwrseq_target_data){ .name = "test-target", .unit = &unit, }; targets[0] = ⌖ config = (struct pwrseq_config){ .parent = &pdev->dev, .drvdata = ctx, .match = pwrseq_test_match_always, .targets = targets, }; pwrseq = kunit_pwrseq_device_register(test, &config); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pwrseq); pdevinfo = (struct platform_device_info){ .name = PWRSEQ_TEST_CONSUMER, .id = 0, }; cons_a = kunit_platform_device_register_full(test, &pdevinfo); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cons_a); wait_for_device_probe(); scoped_guard(device, &cons_a->dev) bound = device_is_bound(&cons_a->dev); KUNIT_ASSERT_FALSE(test, bound); pdevinfo.id = 1; cons_b = kunit_platform_device_register_full(test, &pdevinfo); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cons_b); wait_for_device_probe(); scoped_guard(device, &cons_b->dev) bound = device_is_bound(&cons_b->dev); KUNIT_ASSERT_FALSE(test, bound); desc_a = kunit_pwrseq_get(test, &cons_a->dev, "test-target"); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, desc_a); desc_b = kunit_pwrseq_get(test, &cons_b->dev, "test-target"); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, desc_b); ret = pwrseq_enable(desc_a); KUNIT_ASSERT_EQ(test, ret, 0); kunit_warning_suppress(test) { kunit_release_action(test, pwrseq_device_unregister_wrapper, pwrseq); KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1); } ret = pwrseq_enable(desc_b); KUNIT_EXPECT_EQ(test, ret, -ENODEV); ret = pwrseq_disable(desc_a); KUNIT_EXPECT_EQ(test, ret, -ENODEV); } /* * Test that pwrseq_to_device() returns the pwrseq provider's own device, * and NULL for a NULL descriptor. */ static void pwrseq_to_device_test(struct kunit *test) { struct platform_device *parent = test->priv, *cons; const struct pwrseq_target_data *targets[2] = { }; struct platform_device_info pdevinfo; struct pwrseq_target_data target; struct pwrseq_unit_data unit; struct pwrseq_device *pwrseq; struct pwrseq_config config; struct pwrseq_desc *desc; struct device *dev; unit = (struct pwrseq_unit_data){ .name = "unit-a" }; target = (struct pwrseq_target_data){ .name = "test-target", .unit = &unit, }; targets[0] = ⌖ config = (struct pwrseq_config){ .parent = &parent->dev, .match = pwrseq_test_match_always, .targets = targets, }; pwrseq = kunit_pwrseq_device_register(test, &config); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pwrseq); pdevinfo = (struct platform_device_info){ .name = PWRSEQ_TEST_CONSUMER, .id = PLATFORM_DEVID_NONE, }; cons = kunit_platform_device_register_full(test, &pdevinfo); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cons); desc = kunit_pwrseq_get(test, &cons->dev, "test-target"); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, desc); dev = pwrseq_to_device(desc); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); KUNIT_EXPECT_PTR_EQ(test, dev->parent, &parent->dev); KUNIT_EXPECT_TRUE(test, device_is_registered(dev)); KUNIT_EXPECT_PTR_EQ(test, pwrseq_to_device(NULL), NULL); } static struct kunit_case pwrseq_tests[] = { KUNIT_CASE(pwrseq_circular_deps), KUNIT_CASE(pwrseq_circular_deps_chain), KUNIT_CASE(pwrseq_register_valid), KUNIT_CASE(pwrseq_register_invalid_args), KUNIT_CASE(pwrseq_register_target_without_unit), KUNIT_CASE(pwrseq_enable_disable), KUNIT_CASE(pwrseq_shared_deps), KUNIT_CASE(pwrseq_enable_idempotent), KUNIT_CASE(pwrseq_enable_enable_error), KUNIT_CASE(pwrseq_enable_rollback_on_top_failure), KUNIT_CASE(pwrseq_enable_post_enable), KUNIT_CASE(pwrseq_get_target_not_found), KUNIT_CASE(pwrseq_put_disables_powered_desc), KUNIT_CASE(pwrseq_unregister_with_active_user), KUNIT_CASE(pwrseq_to_device_test), { } }; static struct kunit_suite pwrseq_test_suite = { .name = "pwrseq", .init = pwrseq_test_parent_init, .test_cases = pwrseq_tests, }; /* * Match a consumer to this test provider by resolving its "pwrseq-provider" * software-node reference and comparing it to the provider's own fwnode. */ static int pwrseq_test_match_by_swnode_ref(struct pwrseq_device *pwrseq, struct device *dev) { const struct software_node *provider_swnode; struct fwnode_handle *provider_fwnode, *ref; bool match; if (!dev_fwnode(dev)) return PWRSEQ_NO_MATCH; ref = fwnode_find_reference(dev_fwnode(dev), "pwrseq-provider", 0); if (IS_ERR_OR_NULL(ref)) return PWRSEQ_NO_MATCH; provider_swnode = pwrseq_device_get_drvdata(pwrseq); provider_fwnode = software_node_fwnode(provider_swnode); match = (ref == provider_fwnode); fwnode_handle_put(ref); return match ? PWRSEQ_MATCH_OK : PWRSEQ_NO_MATCH; } static const struct pwrseq_unit_data pwrseq_swnode_test_unit = { .name = "test-target", }; static const struct pwrseq_target_data pwrseq_swnode_test_target = { .name = "test-target", .unit = &pwrseq_swnode_test_unit, }; static const struct pwrseq_target_data *pwrseq_swnode_test_targets[] = { &pwrseq_swnode_test_target, NULL, }; struct pwrseq_swnode_provider_pdata { const struct software_node *provider_swnode; }; static int pwrseq_swnode_provider_probe(struct platform_device *pdev) { const struct pwrseq_swnode_provider_pdata *pdata = dev_get_platdata(&pdev->dev); struct pwrseq_config config; config = (struct pwrseq_config){ .parent = &pdev->dev, .drvdata = (void *)pdata->provider_swnode, .match = pwrseq_test_match_by_swnode_ref, .targets = pwrseq_swnode_test_targets, }; return PTR_ERR_OR_ZERO(devm_pwrseq_device_register(&pdev->dev, &config)); } static struct platform_driver pwrseq_swnode_provider_driver = { .probe = pwrseq_swnode_provider_probe, .driver = { .name = PWRSEQ_SWNODE_TEST_PROVIDER, }, }; static struct platform_driver pwrseq_swnode_provider_driver_2 = { .probe = pwrseq_swnode_provider_probe, .driver = { .name = PWRSEQ_SWNODE_TEST_PROVIDER_2, }, }; struct pwrseq_probe_order_pdata { unsigned int probe_count; int pwrseq_err; }; static const struct pwrseq_probe_order_pdata pwrseq_probe_order_pdata_template; static int pwrseq_probe_order_consumer_probe(struct platform_device *pdev) { struct pwrseq_probe_order_pdata *pdata = dev_get_platdata(&pdev->dev); struct pwrseq_desc *desc; pdata->probe_count++; desc = devm_pwrseq_get(&pdev->dev, "test-target"); pdata->pwrseq_err = PTR_ERR_OR_ZERO(desc); if (IS_ERR(desc)) return PTR_ERR(desc); return 0; } static struct platform_driver pwrseq_probe_order_consumer_driver = { .probe = pwrseq_probe_order_consumer_probe, .driver = { .name = PWRSEQ_PROBE_ORDER_TEST_CONSUMER, }, }; static struct platform_driver pwrseq_probe_defer_consumer_driver = { .probe = pwrseq_probe_order_consumer_probe, .driver = { .name = PWRSEQ_PROBE_DEFER_TEST_CONSUMER, }, }; /* * Verify that driver core orders the probe of a pwrseq consumer after its * provider. The consumer references the provider through a software node and * is registered first and we rely on devlink for ordering. */ static void pwrseq_swnode_probe_order(struct kunit *test) { struct property_entry properties[2] = { }; struct pwrseq_probe_order_pdata *pdata; struct platform_device_info pdevinfo; struct platform_device *prvd, *cons; struct fwnode_handle *fwnode; bool bound = false; int ret; ret = kunit_platform_driver_register(test, &pwrseq_swnode_provider_driver); KUNIT_ASSERT_EQ(test, ret, 0); ret = kunit_platform_driver_register(test, &pwrseq_probe_order_consumer_driver); KUNIT_ASSERT_EQ(test, ret, 0); fwnode = kunit_software_node_register(test, &pwrseq_test_provider_swnode); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fwnode); properties[0] = PROPERTY_ENTRY_REF("pwrseq-provider", &pwrseq_test_provider_swnode); pdevinfo = (struct platform_device_info){ .name = PWRSEQ_PROBE_ORDER_TEST_CONSUMER, .id = PLATFORM_DEVID_NONE, .data = &pwrseq_probe_order_pdata_template, .size_data = sizeof(pwrseq_probe_order_pdata_template), .properties = properties, }; cons = kunit_platform_device_register_full(test, &pdevinfo); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cons); wait_for_device_probe(); scoped_guard(device, &cons->dev) bound = device_is_bound(&cons->dev); KUNIT_ASSERT_FALSE(test, bound); pdata = dev_get_platdata(&cons->dev); KUNIT_ASSERT_EQ(test, pdata->probe_count, 0); pdevinfo = (struct platform_device_info){ .name = PWRSEQ_SWNODE_TEST_PROVIDER, .id = PLATFORM_DEVID_NONE, .swnode = &pwrseq_test_provider_swnode, .data = &(const struct pwrseq_swnode_provider_pdata){ .provider_swnode = &pwrseq_test_provider_swnode, }, .size_data = sizeof(struct pwrseq_swnode_provider_pdata), }; prvd = kunit_platform_device_register_full(test, &pdevinfo); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, prvd); wait_for_device_probe(); scoped_guard(device, &prvd->dev) bound = device_is_bound(&prvd->dev); KUNIT_ASSERT_TRUE(test, bound); scoped_guard(device, &cons->dev) bound = device_is_bound(&cons->dev); KUNIT_ASSERT_TRUE(test, bound); pdata = dev_get_platdata(&cons->dev); KUNIT_EXPECT_EQ(test, pdata->probe_count, 1); KUNIT_EXPECT_EQ(test, pdata->pwrseq_err, 0); } /* * Verify that a pwrseq consumer referencing a provider whose software node is * not registered yet, defers its probe instead of failing. * * The provider software node is deliberately left unregistered when the * consumer is added. fw_devlink cannot resolve the reference, so it creates no * supplier link and does not order the consumer - the consumer's probe() runs * and calls pwrseq_get(), which finds no matching provider and returns * -EPROBE_DEFER. Once the provider software node and device appear, the * deferred consumer probes again and binds. */ static void pwrseq_swnode_probe_defer_on_unregistered(struct kunit *test) { struct property_entry properties[2] = { }; struct pwrseq_probe_order_pdata *pdata; struct platform_device_info pdevinfo; struct platform_device *prvd, *cons; struct fwnode_handle *fwnode; bool bound = false; int ret; ret = kunit_platform_driver_register(test, &pwrseq_swnode_provider_driver_2); KUNIT_ASSERT_EQ(test, ret, 0); ret = kunit_platform_driver_register(test, &pwrseq_probe_defer_consumer_driver); KUNIT_ASSERT_EQ(test, ret, 0); properties[0] = PROPERTY_ENTRY_REF("pwrseq-provider", &pwrseq_test_provider_swnode); pdevinfo = (struct platform_device_info){ .name = PWRSEQ_PROBE_DEFER_TEST_CONSUMER, .id = PLATFORM_DEVID_NONE, .data = &pwrseq_probe_order_pdata_template, .size_data = sizeof(pwrseq_probe_order_pdata_template), .properties = properties, }; cons = kunit_platform_device_register_full(test, &pdevinfo); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cons); wait_for_device_probe(); scoped_guard(device, &cons->dev) bound = device_is_bound(&cons->dev); KUNIT_ASSERT_FALSE(test, bound); pdata = dev_get_platdata(&cons->dev); KUNIT_ASSERT_GT(test, pdata->probe_count, 0); KUNIT_ASSERT_EQ(test, pdata->pwrseq_err, -EPROBE_DEFER); fwnode = kunit_software_node_register(test, &pwrseq_test_provider_swnode); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fwnode); pdevinfo = (struct platform_device_info){ .name = PWRSEQ_SWNODE_TEST_PROVIDER_2, .id = PLATFORM_DEVID_NONE, .swnode = &pwrseq_test_provider_swnode, .data = &(const struct pwrseq_swnode_provider_pdata){ .provider_swnode = &pwrseq_test_provider_swnode, }, .size_data = sizeof(struct pwrseq_swnode_provider_pdata), }; prvd = kunit_platform_device_register_full(test, &pdevinfo); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, prvd); wait_for_device_probe(); scoped_guard(device, &prvd->dev) bound = device_is_bound(&prvd->dev); KUNIT_ASSERT_TRUE(test, bound); scoped_guard(device, &cons->dev) bound = device_is_bound(&cons->dev); KUNIT_ASSERT_TRUE(test, bound); pdata = dev_get_platdata(&cons->dev); KUNIT_EXPECT_EQ(test, pdata->pwrseq_err, 0); } static int pwrseq_swnode_test_init(struct kunit *test) { /* * A device link teardown from an earlier test case, or from this * suite's previous module load, may still be queued on device_link_mq. * Flush it so software_node_register() below doesn't spuriously see * the about-to-be-reused node name as still taken. */ device_link_wait_removal(); return 0; } static struct kunit_case pwrseq_swnode_probe_order_tests[] = { KUNIT_CASE(pwrseq_swnode_probe_order), KUNIT_CASE(pwrseq_swnode_probe_defer_on_unregistered), { } }; static struct kunit_suite pwrseq_swnode_probe_order_test_suite = { .name = "pwrseq-swnode-probe-order", .init = pwrseq_swnode_test_init, .test_cases = pwrseq_swnode_probe_order_tests, }; kunit_test_suites(&pwrseq_test_suite, &pwrseq_swnode_probe_order_test_suite); MODULE_DESCRIPTION("KUnit test cases for the power sequencing subsystem"); MODULE_AUTHOR("Bartosz Golaszewski "); MODULE_LICENSE("GPL");