From patchwork Wed Jan 8 00:58:46 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stephen Boyd X-Patchwork-Id: 13929751 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5FF502AE8D; Wed, 8 Jan 2025 00:58:56 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736297936; cv=none; b=RHenNgvVOV6wtFZOBudlRpC0vB3+oKDWeZRfsxLDENIsyrULkDxnHDLPVWhYehDpoY8OSU40TpR7jJW9Xj/n3gPEQawT8Q/7tEidRDn2GlHuSn1AgFJnX47uK2e74dCeh5UPzC0wrb+hY0TIrF52GSFDb2ANW936p4JLOB19Sew= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736297936; c=relaxed/simple; bh=tc1+ZtikjJOS4fHNg80I6c5UnBanl3/atrx9Cnr9TWg=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=GuK5PiE700Tjj/GJSpqzT8oaCF5jCo7t49p1e55gDSYGK6X/B/b/FhpNDzJDvjxV5mSoCNUkb5FlI1/iyAoRNgwcm49jLsjDUn4oOLNC7RGw/6v5Gr5YAVkRcO6Ib2pJnlyL13CdZZaSq3iLw71PRKGmRvZXFyWrAJ0RO9fnfO4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=GmlutyXb; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="GmlutyXb" Received: by smtp.kernel.org (Postfix) with ESMTPSA id A75A3C4CEDF; Wed, 8 Jan 2025 00:58:55 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1736297936; bh=tc1+ZtikjJOS4fHNg80I6c5UnBanl3/atrx9Cnr9TWg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=GmlutyXb68mKdGKRjQa61Iljjt3TC610fyIponekRDr2icx7GQN7ljaXZ4OWvwzgI DATeF4aDGYiR+ljCVIO8unCjUjS4JHDTYq4E1YubqjGrngF6U9EqGZZLNPBZjgQKuz 53CnwYJGtxqo4tNVDD4ZK89EJyVi1nAgW0GJ7ILbNMSNMtREuapKcwU6Z0SBX7dHTe nMZ7smNYlDiY8kbIBGVy1LnQxulvxGvV1nlaAkJEAFjc5D3BPnjQjY14wLtwwZUaHb qWJIkELS5qVZ96AQ/i422D+/rQ4RV/ICsrr0lXCnoz+3M9eyljFIcLcpt2D4prcIPu gIEcNe/AUyNZQ== From: Stephen Boyd To: Michael Turquette , Stephen Boyd Cc: linux-kernel@vger.kernel.org, linux-clk@vger.kernel.org, patches@lists.linux.dev Subject: [PATCH 1/4] clk: Prepare clk_debug_create_one() to be split off Date: Tue, 7 Jan 2025 16:58:46 -0800 Message-ID: <20250108005854.2973184-2-sboyd@kernel.org> X-Mailer: git-send-email 2.47.1.613.gc27f4b7a9f-goog In-Reply-To: <20250108005854.2973184-1-sboyd@kernel.org> References: <20250108005854.2973184-1-sboyd@kernel.org> Precedence: bulk X-Mailing-List: linux-clk@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 In a later patch we're going to move clk_debug_create_one() to a loadable kernel module. Prepare for that step by returning the dentry created in there from the function and wrap that all up in a new function, clk_core_debug_create_one(), that we can call in both places the dentry is created. Similarly, don't pass the 'pdentry' argument to clk_debug_create_one() because it's always the global 'rootdir', so just use that where it is used instead of passing it. We can remove the NULL checks too because we know the pointers are always valid. Signed-off-by: Stephen Boyd --- drivers/clk/clk.c | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c index bdc6e5b90da5..1a94a27194c9 100644 --- a/drivers/clk/clk.c +++ b/drivers/clk/clk.c @@ -3705,14 +3705,11 @@ static int clk_max_rate_show(struct seq_file *s, void *data) } DEFINE_SHOW_ATTRIBUTE(clk_max_rate); -static void clk_debug_create_one(struct clk_core *core, struct dentry *pdentry) +static struct dentry *clk_debug_create_one(struct clk_core *core) { struct dentry *root; - if (!core || !pdentry) - return; - - root = debugfs_create_dir(core->name, pdentry); + root = debugfs_create_dir(core->name, rootdir); core->dentry = root; debugfs_create_file("clk_rate", clk_rate_mode, root, core, @@ -3746,8 +3743,19 @@ static void clk_debug_create_one(struct clk_core *core, struct dentry *pdentry) debugfs_create_file("clk_possible_parents", 0444, root, core, &possible_parents_fops); + return root; +} + +static void clk_core_debug_create_one(struct clk_core *core) +{ + struct clk_hw *hw = core->hw; + + if (!inited) + return; + + core->dentry = clk_debug_create_one(core); if (core->ops->debug_init) - core->ops->debug_init(core->hw, core->dentry); + core->ops->debug_init(hw, core->dentry); } /** @@ -3762,8 +3770,7 @@ static void clk_debug_register(struct clk_core *core) { mutex_lock(&clk_debug_lock); hlist_add_head(&core->debug_node, &clk_debug_list); - if (inited) - clk_debug_create_one(core, rootdir); + clk_core_debug_create_one(core); mutex_unlock(&clk_debug_lock); } @@ -3827,10 +3834,9 @@ static int __init clk_debug_init(void) &clk_dump_fops); mutex_lock(&clk_debug_lock); - hlist_for_each_entry(core, &clk_debug_list, debug_node) - clk_debug_create_one(core, rootdir); - inited = 1; + hlist_for_each_entry(core, &clk_debug_list, debug_node) + clk_core_debug_create_one(core); mutex_unlock(&clk_debug_lock); return 0; From patchwork Wed Jan 8 00:58:47 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stephen Boyd X-Patchwork-Id: 13929752 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B9C4B4964F; Wed, 8 Jan 2025 00:58:56 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736297936; cv=none; b=Efi65bMJF7RLlBieLMmicguGy6IksfIBibpMUjjH6WusisQI1z9QNCdCT3CLRhPDCAb77m3Z56CWsLjDz9okMfUkAb4RHqOJfeiqY+Z/XIe9jQWJeFfCOPQnACAdVToInRlUx3pW4sc37k9KuYy4d1r82WnchWFfXvso/qZKdAA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736297936; c=relaxed/simple; bh=kPgLOQweT9CS4WFiS7RRBYHt5H6n1zD1v4nqNrenwUM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=ud6fHn9aKnaHHB7yhdmCnHHTnPPYtmPRzuaDwRpYWHnJmRa1etsiRdJQz3uBaPG4jeAjfoLnSv9OZWkv7LROCylKFGLqhZuvaPGyy41XIVQ25+jU41GvqYxO4tdo5KquL2nGDavokc9QxSAUkP8GmHWbvGUtmGycyoWmzhNQknM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=AsL14DjS; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="AsL14DjS" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 276EFC4CEE0; Wed, 8 Jan 2025 00:58:56 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1736297936; bh=kPgLOQweT9CS4WFiS7RRBYHt5H6n1zD1v4nqNrenwUM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=AsL14DjS4HP86gf8eHfAzLuwBIlEhTu/8pgMM91Kxx2BGayKenigyU9i1WzkrTAvH cu2xyjf9u/jWDvQbKKnZBZImjq48RG20OM39DgeXxNYIcA7hQ/NtLcZvG11ABrVkCE VvSJmXZ+vOR7rngEig/c6PAk9/74uP553CbOLjSDUSqTW7CA6SyMxFVgxcF6chn5Bu G+rQyjzBlKH0Czdvqfp+J8ulof5u3uQiddHCWU2xbWA0739QO2DlrcYiYexpyiaPVQ OCzDjGSvmk+VUlenE8IQXMRt5XaS3ZLkMiPx0LyfBgHotUSDW0duPUOyrngUtGi5H+ rIJkrf4ukewXw== From: Stephen Boyd To: Michael Turquette , Stephen Boyd Cc: linux-kernel@vger.kernel.org, linux-clk@vger.kernel.org, patches@lists.linux.dev Subject: [PATCH 2/4] clk: Use struct clk_hw instead of struct clk_core for debugfs Date: Tue, 7 Jan 2025 16:58:47 -0800 Message-ID: <20250108005854.2973184-3-sboyd@kernel.org> X-Mailer: git-send-email 2.47.1.613.gc27f4b7a9f-goog In-Reply-To: <20250108005854.2973184-1-sboyd@kernel.org> References: <20250108005854.2973184-1-sboyd@kernel.org> Precedence: bulk X-Mailing-List: linux-clk@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 The clk debugfs code uses struct clk_core because it is part of the core framework file where the struct isn't opaque. Introduce helpers that use struct clk_hw and use them in the clk debugfs code so that a later patch can split out the clk debugfs code to a loadable kernel module while keeping struct clk_core opaque. Signed-off-by: Stephen Boyd --- drivers/clk/clk.c | 412 +++++++++++++++++++++++++++++++++------------- 1 file changed, 294 insertions(+), 118 deletions(-) diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c index 1a94a27194c9..e2f8fcbc1d4f 100644 --- a/drivers/clk/clk.c +++ b/drivers/clk/clk.c @@ -818,6 +818,14 @@ void clk_hw_get_rate_range(struct clk_hw *hw, unsigned long *min_rate, } EXPORT_SYMBOL_GPL(clk_hw_get_rate_range); +static void clk_debug_get_rate_range(struct clk_hw *hw, unsigned long *min_rate, + unsigned long *max_rate) +{ + clk_prepare_lock(); + clk_hw_get_rate_range(hw, min_rate, max_rate); + clk_prepare_unlock(); +} + static bool clk_core_check_boundaries(struct clk_core *core, unsigned long min_rate, unsigned long max_rate) @@ -1897,6 +1905,11 @@ static long clk_core_get_accuracy_recalc(struct clk_core *core) return clk_core_get_accuracy_no_lock(core); } +static long clk_hw_get_accuracy_recalc(struct clk_hw *hw) +{ + return clk_core_get_accuracy_recalc(hw->core); +} + /** * clk_get_accuracy - return the accuracy of clk * @clk: the clk whose accuracy is being returned @@ -1983,6 +1996,11 @@ static unsigned long clk_core_get_rate_recalc(struct clk_core *core) return clk_core_get_rate_nolock(core); } +static unsigned long clk_hw_get_rate_recalc(struct clk_hw *hw) +{ + return clk_core_get_rate_recalc(hw->core); +} + /** * clk_get_rate - return the rate of clk * @clk: the clk whose rate is being returned @@ -3059,6 +3077,11 @@ static int clk_core_get_phase(struct clk_core *core) return ret; } +static int clk_hw_get_phase(struct clk_hw *hw) +{ + return clk_core_get_phase(hw->core); +} + /** * clk_get_phase - return the phase shift of a clock signal * @clk: clock signal source @@ -3219,17 +3242,19 @@ static int clk_core_get_scaled_duty_cycle(struct clk_core *core, struct clk_duty *duty = &core->duty; int ret; - clk_prepare_lock(); - ret = clk_core_update_duty_cycle_nolock(core); if (!ret) ret = mult_frac(scale, duty->num, duty->den); - clk_prepare_unlock(); - return ret; } +static int clk_hw_get_scaled_duty_cycle(struct clk_hw *hw, + unsigned int scale) +{ + return clk_core_get_scaled_duty_cycle(hw->core, scale); +} + /** * clk_get_scaled_duty_cycle - return the duty cycle ratio of a clock signal * @clk: clock signal source @@ -3240,10 +3265,16 @@ static int clk_core_get_scaled_duty_cycle(struct clk_core *core, */ int clk_get_scaled_duty_cycle(struct clk *clk, unsigned int scale) { + int ret; + if (!clk) return 0; - return clk_core_get_scaled_duty_cycle(clk->core, scale); + clk_prepare_lock(); + ret = clk_core_get_scaled_duty_cycle(clk->core, scale); + clk_prepare_unlock(); + + return ret; } EXPORT_SYMBOL_GPL(clk_get_scaled_duty_cycle); @@ -3288,45 +3319,95 @@ static struct hlist_head *orphan_list[] = { NULL, }; -static void clk_summary_show_one(struct seq_file *s, struct clk_core *c, +/* + * 1: Enabled in hardware + * 0: Disabled in hardware + * -1: Unknown enable state + */ +static int clk_hw_enable_state(struct clk_hw *hw) +{ + struct clk_core *core = hw->core; + + if (core->ops->is_enabled) + return clk_hw_is_enabled(hw) ? 1 : 0; + if (!core->ops->enable) + return 1; + + return -1; +} + +static unsigned int clk_hw_enable_count(struct clk_hw *hw) +{ + return hw->core->enable_count; +} + +static unsigned int clk_hw_prepare_count(struct clk_hw *hw) +{ + return hw->core->prepare_count; +} + +static unsigned int clk_hw_protect_count(struct clk_hw *hw) +{ + return hw->core->protect_count; +} + +static const char *clk_con_id(struct clk *clk) +{ + return clk->con_id; +} + +static const char *clk_dev_id(struct clk *clk) +{ + return clk->dev_id; +} + +static struct clk *clk_hw_next_consumer(struct clk_hw *hw, struct clk *prev) +{ + if (prev) + return hlist_entry_safe(prev->clks_node.next, struct clk, clks_node); + + return hlist_entry_safe(hw->core->clks.first, struct clk, clks_node); +} + +static void clk_summary_show_one(struct seq_file *s, struct clk_hw *hw, int level) { + int enable; int phase; - struct clk *clk_user; + struct clk *clk_user = NULL; int multi_node = 0; seq_printf(s, "%*s%-*s %-7d %-8d %-8d %-11lu %-10lu ", level * 3 + 1, "", - 35 - level * 3, c->name, - c->enable_count, c->prepare_count, c->protect_count, - clk_core_get_rate_recalc(c), - clk_core_get_accuracy_recalc(c)); + 35 - level * 3, clk_hw_get_name(hw), + clk_hw_enable_count(hw), clk_hw_prepare_count(hw), + clk_hw_protect_count(hw), + clk_hw_get_rate_recalc(hw), + clk_hw_get_accuracy_recalc(hw)); - phase = clk_core_get_phase(c); + phase = clk_hw_get_phase(hw); if (phase >= 0) seq_printf(s, "%-5d", phase); else seq_puts(s, "-----"); - seq_printf(s, " %-6d", clk_core_get_scaled_duty_cycle(c, 100000)); + seq_printf(s, " %-6d", clk_hw_get_scaled_duty_cycle(hw, 100000)); - if (c->ops->is_enabled) - seq_printf(s, " %5c ", clk_core_is_enabled(c) ? 'Y' : 'N'); - else if (!c->ops->enable) - seq_printf(s, " %5c ", 'Y'); + enable = clk_hw_enable_state(hw); + if (enable >= 0) + seq_printf(s, " %5c ", enable ? 'Y' : 'N'); else seq_printf(s, " %5c ", '?'); - hlist_for_each_entry(clk_user, &c->clks, clks_node) { + while ((clk_user = clk_hw_next_consumer(hw, clk_user))) { seq_printf(s, "%*s%-*s %-25s\n", level * 3 + 2 + 105 * multi_node, "", 30, - clk_user->dev_id ? clk_user->dev_id : "deviceless", - clk_user->con_id ? clk_user->con_id : "no_connection_id"); + clk_dev_id(clk_user) ? : "deviceless", + clk_con_id(clk_user) ? : "no_connection_id"); multi_node = 1; } - } static void clk_summary_show_subtree(struct seq_file *s, struct clk_core *c, @@ -3334,7 +3415,7 @@ static void clk_summary_show_subtree(struct seq_file *s, struct clk_core *c, { struct clk_core *child; - clk_summary_show_one(s, c, level); + clk_summary_show_one(s, c->hw, level); hlist_for_each_entry(child, &c->children, child_node) clk_summary_show_subtree(s, child, level + 1); @@ -3367,34 +3448,34 @@ static int clk_summary_show(struct seq_file *s, void *data) } DEFINE_SHOW_ATTRIBUTE(clk_summary); -static void clk_dump_one(struct seq_file *s, struct clk_core *c, int level) +static void clk_dump_one(struct seq_file *s, struct clk_hw *hw, int level) { int phase; unsigned long min_rate, max_rate; - clk_core_get_boundaries(c, &min_rate, &max_rate); + clk_hw_get_rate_range(hw, &min_rate, &max_rate); /* This should be JSON format, i.e. elements separated with a comma */ - seq_printf(s, "\"%s\": { ", c->name); - seq_printf(s, "\"enable_count\": %d,", c->enable_count); - seq_printf(s, "\"prepare_count\": %d,", c->prepare_count); - seq_printf(s, "\"protect_count\": %d,", c->protect_count); - seq_printf(s, "\"rate\": %lu,", clk_core_get_rate_recalc(c)); + seq_printf(s, "\"%s\": { ", clk_hw_get_name(hw)); + seq_printf(s, "\"enable_count\": %d,", clk_hw_enable_count(hw)); + seq_printf(s, "\"prepare_count\": %d,", clk_hw_prepare_count(hw)); + seq_printf(s, "\"protect_count\": %d,", clk_hw_protect_count(hw)); + seq_printf(s, "\"rate\": %lu,", clk_hw_get_rate_recalc(hw)); seq_printf(s, "\"min_rate\": %lu,", min_rate); seq_printf(s, "\"max_rate\": %lu,", max_rate); - seq_printf(s, "\"accuracy\": %lu,", clk_core_get_accuracy_recalc(c)); - phase = clk_core_get_phase(c); + seq_printf(s, "\"accuracy\": %lu,", clk_hw_get_accuracy_recalc(hw)); + phase = clk_hw_get_phase(hw); if (phase >= 0) seq_printf(s, "\"phase\": %d,", phase); seq_printf(s, "\"duty_cycle\": %u", - clk_core_get_scaled_duty_cycle(c, 100000)); + clk_hw_get_scaled_duty_cycle(hw, 100000)); } static void clk_dump_subtree(struct seq_file *s, struct clk_core *c, int level) { struct clk_core *child; - clk_dump_one(s, c, level); + clk_dump_one(s, c->hw, level); hlist_for_each_entry(child, &c->children, child_node) { seq_putc(s, ','); @@ -3436,21 +3517,24 @@ static int clk_dump_show(struct seq_file *s, void *data) } DEFINE_SHOW_ATTRIBUTE(clk_dump); -#undef CLOCK_ALLOW_WRITE_DEBUGFS -#ifdef CLOCK_ALLOW_WRITE_DEBUGFS /* * This can be dangerous, therefore don't provide any real compile time * configuration option for this feature. * People who want to use this will need to modify the source code directly. */ +#undef CLOCK_ALLOW_WRITE_DEBUGFS +#ifdef CLOCK_ALLOW_WRITE_DEBUGFS static int clk_rate_set(void *data, u64 val) { - struct clk_core *core = data; + struct clk_hw *hw = data; + struct clk *clk = clk_hw_get_clk(hw, "debugfs_rate_set"); int ret; - clk_prepare_lock(); - ret = clk_core_set_rate_nolock(core, val); - clk_prepare_unlock(); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + ret = clk_set_rate(clk, val); + clk_put(clk); return ret; } @@ -3459,13 +3543,16 @@ static int clk_rate_set(void *data, u64 val) static int clk_phase_set(void *data, u64 val) { - struct clk_core *core = data; + struct clk_hw *hw = data; + struct clk *clk = clk_hw_get_clk(hw, "debugfs_phase_set"); int degrees = do_div(val, 360); int ret; - clk_prepare_lock(); - ret = clk_core_set_phase_nolock(core, degrees); - clk_prepare_unlock(); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + ret = clk_set_phase(clk, degrees); + clk_put(clk); return ret; } @@ -3474,22 +3561,27 @@ static int clk_phase_set(void *data, u64 val) static int clk_prepare_enable_set(void *data, u64 val) { - struct clk_core *core = data; + struct clk_hw *hw = data; + struct clk *clk = clk_hw_get_clk(hw, "debugfs_prepare_enable_set"); int ret = 0; + if (IS_ERR(clk)) + return PTR_ERR(clk); + if (val) - ret = clk_prepare_enable(core->hw->clk); + ret = clk_prepare_enable(clk); else - clk_disable_unprepare(core->hw->clk); + clk_disable_unprepare(clk); + clk_put(clk); return ret; } static int clk_prepare_enable_get(void *data, u64 *val) { - struct clk_core *core = data; + struct clk_hw *hw = data; - *val = core->enable_count && core->prepare_count; + *val = clk_hw_is_prepared(hw) && clk_hw_is_enabled(hw); return 0; } @@ -3506,11 +3598,14 @@ DEFINE_DEBUGFS_ATTRIBUTE(clk_prepare_enable_fops, clk_prepare_enable_get, static int clk_rate_get(void *data, u64 *val) { - struct clk_core *core = data; + struct clk_hw *hw = data; + struct clk *clk = clk_hw_get_clk(hw, "debugfs_rate_get"); - clk_prepare_lock(); - *val = clk_core_get_rate_recalc(core); - clk_prepare_unlock(); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + *val = clk_get_rate(clk); + clk_put(clk); return 0; } @@ -3519,9 +3614,15 @@ DEFINE_DEBUGFS_ATTRIBUTE(clk_rate_fops, clk_rate_get, clk_rate_set, "%llu\n"); static int clk_phase_get(void *data, u64 *val) { - struct clk_core *core = data; + struct clk_hw *hw = data; + struct clk *clk = clk_hw_get_clk(hw, "debugfs_phase_get"); + + if (IS_ERR(clk)) + return PTR_ERR(clk); + + *val = clk_get_phase(clk); + clk_put(clk); - *val = core->phase; return 0; } @@ -3549,8 +3650,8 @@ static const struct { static int clk_flags_show(struct seq_file *s, void *data) { - struct clk_core *core = s->private; - unsigned long flags = core->flags; + struct clk_hw *hw = s->private; + unsigned long flags = clk_hw_get_flags(hw); unsigned int i; for (i = 0; flags && i < ARRAY_SIZE(clk_flags); i++) { @@ -3568,10 +3669,11 @@ static int clk_flags_show(struct seq_file *s, void *data) } DEFINE_SHOW_ATTRIBUTE(clk_flags); -static void possible_parent_show(struct seq_file *s, struct clk_core *core, - unsigned int i, char terminator) +static void clk_hw_show_parent_by_index(struct seq_file *s, struct clk_hw *hw, + unsigned int i, char terminator) { - struct clk_core *parent; + struct clk_hw *parent; + struct clk_core *core = hw->core; const char *name = NULL; /* @@ -3586,9 +3688,9 @@ static void possible_parent_show(struct seq_file *s, struct clk_core *core, * specified directly via a struct clk_hw pointer, but it isn't * registered (yet). */ - parent = clk_core_get_parent_by_index(core, i); + parent = clk_hw_get_parent_by_index(hw, i); if (parent) { - seq_puts(s, parent->name); + seq_puts(s, clk_hw_get_name(parent)); } else if (core->parents[i].name) { seq_puts(s, core->parents[i].name); } else if (core->parents[i].fw_name) { @@ -3607,13 +3709,13 @@ static void possible_parent_show(struct seq_file *s, struct clk_core *core, static int possible_parents_show(struct seq_file *s, void *data) { - struct clk_core *core = s->private; + struct clk_hw *hw = s->private; int i; - for (i = 0; i < core->num_parents - 1; i++) - possible_parent_show(s, core, i, ' '); + for (i = 0; i < clk_hw_get_num_parents(hw) - 1; i++) + clk_hw_show_parent_by_index(s, hw, i, ' '); - possible_parent_show(s, core, i, '\n'); + clk_hw_show_parent_by_index(s, hw, i, '\n'); return 0; } @@ -3621,10 +3723,11 @@ DEFINE_SHOW_ATTRIBUTE(possible_parents); static int current_parent_show(struct seq_file *s, void *data) { - struct clk_core *core = s->private; + struct clk_hw *hw = s->private; + struct clk_hw *parent = clk_hw_get_parent(hw); - if (core->parent) - seq_printf(s, "%s\n", core->parent->name); + if (parent) + seq_printf(s, "%s\n", clk_hw_get_name(parent)); return 0; } @@ -3635,26 +3738,38 @@ static ssize_t current_parent_write(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos) { struct seq_file *s = file->private_data; - struct clk_core *core = s->private; - struct clk_core *parent; + struct clk *clk, *parent; + struct clk_hw *hw = s->private; + struct clk_hw *parent_hw; u8 idx; - int err; + int ret; - err = kstrtou8_from_user(ubuf, count, 0, &idx); - if (err < 0) - return err; + ret = kstrtou8_from_user(ubuf, count, 0, &idx); + if (ret < 0) + return ret; - parent = clk_core_get_parent_by_index(core, idx); - if (!parent) + parent_hw = clk_hw_get_parent_by_index(hw, idx); + if (!parent_hw) return -ENOENT; - clk_prepare_lock(); - err = clk_core_set_parent_nolock(core, parent); - clk_prepare_unlock(); - if (err) - return err; + clk = clk_hw_get_clk(hw, "debugfs_write"); + if (IS_ERR(clk)) + return PTR_ERR(clk); - return count; + parent = clk_hw_get_clk(parent_hw, "debugfs_write"); + if (IS_ERR(parent)) { + ret = PTR_ERR(parent); + goto err; + } + + ret = clk_set_parent(clk, parent); + if (!ret) + ret = count; + + clk_put(parent); +err: + clk_put(clk); + return ret; } static const struct file_operations current_parent_rw_fops = { @@ -3666,12 +3781,19 @@ static const struct file_operations current_parent_rw_fops = { }; #endif +static void clk_hw_get_duty(struct clk_hw *hw, struct clk_duty *duty) +{ + memcpy(duty, &hw->core->duty, sizeof(*duty)); +} + static int clk_duty_cycle_show(struct seq_file *s, void *data) { - struct clk_core *core = s->private; - struct clk_duty *duty = &core->duty; + struct clk_hw *hw = s->private; + struct clk_duty duty = { }; - seq_printf(s, "%u/%u\n", duty->num, duty->den); + clk_hw_get_duty(hw, &duty); + + seq_printf(s, "%u/%u\n", duty.num, duty.den); return 0; } @@ -3679,12 +3801,10 @@ DEFINE_SHOW_ATTRIBUTE(clk_duty_cycle); static int clk_min_rate_show(struct seq_file *s, void *data) { - struct clk_core *core = s->private; + struct clk_hw *hw = s->private; unsigned long min_rate, max_rate; - clk_prepare_lock(); - clk_core_get_boundaries(core, &min_rate, &max_rate); - clk_prepare_unlock(); + clk_debug_get_rate_range(hw, &min_rate, &max_rate); seq_printf(s, "%lu\n", min_rate); return 0; @@ -3693,54 +3813,110 @@ DEFINE_SHOW_ATTRIBUTE(clk_min_rate); static int clk_max_rate_show(struct seq_file *s, void *data) { - struct clk_core *core = s->private; + struct clk_hw *hw = s->private; unsigned long min_rate, max_rate; - clk_prepare_lock(); - clk_core_get_boundaries(core, &min_rate, &max_rate); - clk_prepare_unlock(); + clk_debug_get_rate_range(hw, &min_rate, &max_rate); seq_printf(s, "%lu\n", max_rate); return 0; } DEFINE_SHOW_ATTRIBUTE(clk_max_rate); -static struct dentry *clk_debug_create_one(struct clk_core *core) +static int clk_accuracy_show(struct seq_file *s, void *data) +{ + struct clk_hw *hw = s->private; + struct clk *clk = clk_hw_get_clk(hw, "debugfs_accuracy"); + unsigned long accuracy; + + if (IS_ERR(clk)) + return PTR_ERR(clk); + + accuracy = clk_get_accuracy(clk); + seq_printf(s, "%lu\n", accuracy); + clk_put(clk); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(clk_accuracy); + +static int clk_prepare_show(struct seq_file *s, void *data) +{ + struct clk_hw *hw = s->private; + + seq_printf(s, "%u\n", clk_hw_prepare_count(hw)); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(clk_prepare); + +static int clk_enable_show(struct seq_file *s, void *data) +{ + struct clk_hw *hw = s->private; + + seq_printf(s, "%u\n", clk_hw_enable_count(hw)); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(clk_enable); + +static int clk_protect_show(struct seq_file *s, void *data) +{ + struct clk_hw *hw = s->private; + + seq_printf(s, "%u\n", clk_hw_protect_count(hw)); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(clk_protect); + +static unsigned int clk_hw_notifier_count(struct clk_hw *hw) +{ + return hw->core->notifier_count; +} + +static int clk_notifier_show(struct seq_file *s, void *data) +{ + struct clk_hw *hw = s->private; + + seq_printf(s, "%u\n", clk_hw_notifier_count(hw)); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(clk_notifier); + +static struct dentry *clk_hw_debug_create_one(struct clk_hw *hw) { struct dentry *root; - root = debugfs_create_dir(core->name, rootdir); - core->dentry = root; + root = debugfs_create_dir(clk_hw_get_name(hw), rootdir); - debugfs_create_file("clk_rate", clk_rate_mode, root, core, - &clk_rate_fops); - debugfs_create_file("clk_min_rate", 0444, root, core, &clk_min_rate_fops); - debugfs_create_file("clk_max_rate", 0444, root, core, &clk_max_rate_fops); - debugfs_create_ulong("clk_accuracy", 0444, root, &core->accuracy); - debugfs_create_file("clk_phase", clk_phase_mode, root, core, - &clk_phase_fops); - debugfs_create_file("clk_flags", 0444, root, core, &clk_flags_fops); - debugfs_create_u32("clk_prepare_count", 0444, root, &core->prepare_count); - debugfs_create_u32("clk_enable_count", 0444, root, &core->enable_count); - debugfs_create_u32("clk_protect_count", 0444, root, &core->protect_count); - debugfs_create_u32("clk_notifier_count", 0444, root, &core->notifier_count); - debugfs_create_file("clk_duty_cycle", 0444, root, core, - &clk_duty_cycle_fops); + debugfs_create_file("clk_rate", clk_rate_mode, root, hw, &clk_rate_fops); + debugfs_create_file("clk_min_rate", 0444, root, hw, &clk_min_rate_fops); + debugfs_create_file("clk_max_rate", 0444, root, hw, &clk_max_rate_fops); + debugfs_create_file("clk_accuracy", 0444, root, hw, &clk_accuracy_fops); + debugfs_create_file("clk_phase", clk_phase_mode, root, hw, &clk_phase_fops); + debugfs_create_file("clk_flags", 0444, root, hw, &clk_flags_fops); + debugfs_create_file("clk_prepare_count", 0444, root, hw, &clk_prepare_fops); + debugfs_create_file("clk_enable_count", 0444, root, hw, &clk_enable_fops); + debugfs_create_file("clk_protect_count", 0444, root, hw, &clk_protect_fops); + debugfs_create_file("clk_notifier_count", 0444, root, hw, &clk_notifier_fops); + debugfs_create_file("clk_duty_cycle", 0444, root, hw, &clk_duty_cycle_fops); #ifdef CLOCK_ALLOW_WRITE_DEBUGFS - debugfs_create_file("clk_prepare_enable", 0644, root, core, + debugfs_create_file("clk_prepare_enable", 0644, root, hw, &clk_prepare_enable_fops); - if (core->num_parents > 1) - debugfs_create_file("clk_parent", 0644, root, core, + if (clk_hw_get_num_parents(hw) > 1) + debugfs_create_file("clk_parent", 0644, root, hw, ¤t_parent_rw_fops); else #endif - if (core->num_parents > 0) - debugfs_create_file("clk_parent", 0444, root, core, + if (clk_hw_get_num_parents(hw) > 0) + debugfs_create_file("clk_parent", 0444, root, hw, ¤t_parent_fops); - if (core->num_parents > 1) - debugfs_create_file("clk_possible_parents", 0444, root, core, + if (clk_hw_get_num_parents(hw) > 1) + debugfs_create_file("clk_possible_parents", 0444, root, hw, &possible_parents_fops); return root; @@ -3753,7 +3929,7 @@ static void clk_core_debug_create_one(struct clk_core *core) if (!inited) return; - core->dentry = clk_debug_create_one(core); + core->dentry = clk_hw_debug_create_one(hw); if (core->ops->debug_init) core->ops->debug_init(hw, core->dentry); } From patchwork Wed Jan 8 00:58:48 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stephen Boyd X-Patchwork-Id: 13929753 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5EB467DA6A; Wed, 8 Jan 2025 00:58:57 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736297937; cv=none; b=MmyuirKgiMHIIfxRKRkpsP22QESOjgADAju84NTQNqR9pM+hsL0eMtiK5ssr560vr8ptxga58JOGaX6HguvKlpsv588iPTaXCIECQNAD7ndUxyFvTFi2V1dt2hU78ke9+js4mDRg4A8FAdBamTQKKZybyaUjhYz0j5byXv8qa9k= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736297937; c=relaxed/simple; bh=dYnYqamwA1aPKPYpUg7a2HmrnCCIu9jg59bQu0adTtg=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=V1c5G/Rx1uOyZ0AnCPaOBIb1Ugo6Dugi1nzpCO+Rhk1q893zXykLqT9If5uw++Ld8C5bJ5O4eN45MEhAjqkDcHX3tKAn0qPUudlg9ZcyZc69PcXLdkJSuU+yhENSThTlDOjQz53ydlnYNtpOlfmIWGBvMpjff3BD8a+9JyEE8gA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=oum/x20E; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="oum/x20E" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 9D205C4CEE2; Wed, 8 Jan 2025 00:58:56 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1736297936; bh=dYnYqamwA1aPKPYpUg7a2HmrnCCIu9jg59bQu0adTtg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=oum/x20EyJrWP/zYXv7zodBTgTuPlNS729x3TVTWkfqNLJhoDdvMTR0rCGLV8s5tq jX9YKUIi6ujkbkt0fPAyNoOm8n1iPl6QRf40F2D6qfpHpHs+Se9Fkq89lnaeB0q35c V8RSPNloC0vymsMxX8BPs3NdJmu1+pJ+dEcPY88vnuJrkzVoKfZZB7LK0XjpegjC5Y ZVPHhK0rM4ZCJLBN4QPiDZNReT+fN2Vyaz5UT/mMS4yjPQwZ6XFQb50b73qMjOeZIW U8jXVru+LOpFn0YszDSgOvaTD1DBnhOYUc2V9qiT8bN6b9tw8xEL9ymaIytS13ErAe Vflqey9CE4GqQ== From: Stephen Boyd To: Michael Turquette , Stephen Boyd Cc: linux-kernel@vger.kernel.org, linux-clk@vger.kernel.org, patches@lists.linux.dev Subject: [PATCH 3/4] clk: Iterate instead of recurse for debugfs printing Date: Tue, 7 Jan 2025 16:58:48 -0800 Message-ID: <20250108005854.2973184-4-sboyd@kernel.org> X-Mailer: git-send-email 2.47.1.613.gc27f4b7a9f-goog In-Reply-To: <20250108005854.2973184-1-sboyd@kernel.org> References: <20250108005854.2973184-1-sboyd@kernel.org> Precedence: bulk X-Mailing-List: linux-clk@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 The implementation for clk debugfs files like clk_summary and clk_dump are recursive and entangled with the tree walking logic to the point that it's not easy to split out the code that walks the clk tree from the code that prints details of a clk. Harmonize the clk_dump_show() and clk_summary_show() clk tree walking details by linking all the clks to be shown onto a linked list in the order that the clks are walked and then iterate the list calling a "show" function supplied by the caller. Replacing recursion with iteration avoids exceeding limited kernel stacks as well as allows us to split the "show" function out to a loadable kernel module. The downside is the code is more complicated. Signed-off-by: Stephen Boyd --- drivers/clk/clk.c | 171 +++++++++++++++++++++++++--------------------- 1 file changed, 95 insertions(+), 76 deletions(-) diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c index e2f8fcbc1d4f..37c7963d685f 100644 --- a/drivers/clk/clk.c +++ b/drivers/clk/clk.c @@ -89,6 +89,7 @@ struct clk_core { struct hlist_node child_node; struct hlist_head clks; unsigned int notifier_count; + struct hlist_node iterate_node; #ifdef CONFIG_DEBUG_FS struct dentry *dentry; struct hlist_node debug_node; @@ -3314,11 +3315,6 @@ static int inited = 0; static DEFINE_MUTEX(clk_debug_lock); static HLIST_HEAD(clk_debug_list); -static struct hlist_head *orphan_list[] = { - &clk_orphan_list, - NULL, -}; - /* * 1: Enabled in hardware * 0: Disabled in hardware @@ -3369,9 +3365,85 @@ static struct clk *clk_hw_next_consumer(struct clk_hw *hw, struct clk *prev) return hlist_entry_safe(hw->core->clks.first, struct clk, clks_node); } -static void clk_summary_show_one(struct seq_file *s, struct clk_hw *hw, - int level) +static void clk_walk_trees(struct hlist_head *list, bool only_orphans) { + struct clk_core *root, *core, *child; + struct clk_core *last = NULL; + const struct hlist_head **lists = all_lists; + + if (only_orphans) + lists++; + + for (; *lists; lists++) { + hlist_for_each_entry(root, *lists, child_node) { + if (last) + hlist_add_behind(&root->iterate_node, &last->iterate_node); + else + hlist_add_head(&root->iterate_node, list); + + core = root; + hlist_for_each_entry_from(core, iterate_node) { + last = core; + hlist_for_each_entry(child, &core->children, child_node) { + hlist_add_behind(&child->iterate_node, &last->iterate_node); + last = child; + } + } + } + } +} + +static int clk_show_tree(void (*show_fn)(struct clk_hw *hw, int level, + int next_level, bool first, + void *data), + void *data, bool orphan_only) +{ + struct clk_core *core, *parent; + struct hlist_node *tmp; + HLIST_HEAD(list); + int ret; + int level = 0; + int next_level; + bool first = true; + + ret = clk_pm_runtime_get_all(); + if (ret) + return ret; + + clk_prepare_lock(); + + clk_walk_trees(&list, orphan_only); + hlist_for_each_entry_safe(core, tmp, &list, iterate_node) { + next_level = level; + parent = core; + /* + * If this is the right most (i.e. last) clk at this level + * figure out how many levels lower the next clk will be by + * finding the next left side of the tree. + */ + while (!parent->child_node.next && (parent = parent->parent)) + next_level--; + /* If the clk has children the next node is at the next level */ + if (!hlist_empty(&core->children)) + next_level = level + 1; + + show_fn(core->hw, level, next_level, first, data); + first = false; + + hlist_del_init(&core->iterate_node); + level = next_level; + } + + clk_prepare_unlock(); + clk_pm_runtime_put_all(); + + return 0; +} + +static void clk_summary_show_one(struct clk_hw *hw, int level, int next_level, + bool first, void *data) +{ + struct seq_file *s = data; int enable; int phase; struct clk *clk_user = NULL; @@ -3410,51 +3482,29 @@ static void clk_summary_show_one(struct seq_file *s, struct clk_hw *hw, } } -static void clk_summary_show_subtree(struct seq_file *s, struct clk_core *c, - int level) -{ - struct clk_core *child; - - clk_summary_show_one(s, c->hw, level); - - hlist_for_each_entry(child, &c->children, child_node) - clk_summary_show_subtree(s, child, level + 1); -} - static int clk_summary_show(struct seq_file *s, void *data) { - struct clk_core *c; - struct hlist_head **lists = s->private; - int ret; + bool orphan_only = s->private; seq_puts(s, " enable prepare protect duty hardware connection\n"); seq_puts(s, " clock count count count rate accuracy phase cycle enable consumer id\n"); seq_puts(s, "---------------------------------------------------------------------------------------------------------------------------------------------\n"); - ret = clk_pm_runtime_get_all(); - if (ret) - return ret; - - clk_prepare_lock(); - - for (; *lists; lists++) - hlist_for_each_entry(c, *lists, child_node) - clk_summary_show_subtree(s, c, 0); - - clk_prepare_unlock(); - clk_pm_runtime_put_all(); - - return 0; + return clk_show_tree(clk_summary_show_one, s, orphan_only); } DEFINE_SHOW_ATTRIBUTE(clk_summary); -static void clk_dump_one(struct seq_file *s, struct clk_hw *hw, int level) +static void clk_dump_one(struct clk_hw *hw, int level, int next_level, bool first, void *data) { + struct seq_file *s = data; int phase; unsigned long min_rate, max_rate; clk_hw_get_rate_range(hw, &min_rate, &max_rate); + if (!first) + seq_putc(s, ','); + /* This should be JSON format, i.e. elements separated with a comma */ seq_printf(s, "\"%s\": { ", clk_hw_get_name(hw)); seq_printf(s, "\"enable_count\": %d,", clk_hw_enable_count(hw)); @@ -3469,50 +3519,19 @@ static void clk_dump_one(struct seq_file *s, struct clk_hw *hw, int level) seq_printf(s, "\"phase\": %d,", phase); seq_printf(s, "\"duty_cycle\": %u", clk_hw_get_scaled_duty_cycle(hw, 100000)); -} -static void clk_dump_subtree(struct seq_file *s, struct clk_core *c, int level) -{ - struct clk_core *child; - - clk_dump_one(s, c->hw, level); - - hlist_for_each_entry(child, &c->children, child_node) { - seq_putc(s, ','); - clk_dump_subtree(s, child, level + 1); - } - - seq_putc(s, '}'); + while (level-- >= next_level) + seq_putc(s, '}'); } static int clk_dump_show(struct seq_file *s, void *data) { - struct clk_core *c; - bool first_node = true; - struct hlist_head **lists = s->private; - int ret; - - ret = clk_pm_runtime_get_all(); - if (ret) - return ret; + bool orphan_only = s->private; seq_putc(s, '{'); - - clk_prepare_lock(); - - for (; *lists; lists++) { - hlist_for_each_entry(c, *lists, child_node) { - if (!first_node) - seq_putc(s, ','); - first_node = false; - clk_dump_subtree(s, c, 0); - } - } - - clk_prepare_unlock(); - clk_pm_runtime_put_all(); - + clk_show_tree(clk_dump_one, s, orphan_only); seq_puts(s, "}\n"); + return 0; } DEFINE_SHOW_ATTRIBUTE(clk_dump); @@ -4000,13 +4019,13 @@ static int __init clk_debug_init(void) rootdir = debugfs_create_dir("clk", NULL); - debugfs_create_file("clk_summary", 0444, rootdir, &all_lists, + debugfs_create_file("clk_summary", 0444, rootdir, (void *)0UL, &clk_summary_fops); - debugfs_create_file("clk_dump", 0444, rootdir, &all_lists, + debugfs_create_file("clk_dump", 0444, rootdir, (void *)0UL, &clk_dump_fops); - debugfs_create_file("clk_orphan_summary", 0444, rootdir, &orphan_list, + debugfs_create_file("clk_orphan_summary", 0444, rootdir, (void *)1UL, &clk_summary_fops); - debugfs_create_file("clk_orphan_dump", 0444, rootdir, &orphan_list, + debugfs_create_file("clk_orphan_dump", 0444, rootdir, (void *)1UL, &clk_dump_fops); mutex_lock(&clk_debug_lock); From patchwork Wed Jan 8 00:58:49 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stephen Boyd X-Patchwork-Id: 13929754 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 93200126BEE; Wed, 8 Jan 2025 00:58:57 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736297937; cv=none; b=MdCpRw5mioAfgsrH8oyBIUjmZQlG+cGl4E2X0+DwVODfPcwE2jGh+XlGbDjW2cxMKLXePHn45BcOvO/wKzjagayqYtGaTndixrKvAHrT0S+QhgnsepXVjVulU8YnNz7wT4siBQgbm/Ym38iIVUl2nI5MpfXn9ugDRiT/c4kJSk0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736297937; c=relaxed/simple; bh=YFVVlulGLUTVHXMPc1opVRj7WKpmAFMKYCC8IdVDVTc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=K0xndx2iwUXtV8OXibTFcd6YO2cFJ5+d2reEkLnYQclpvieI8KH536UNdUU9gwqFF9C1BqAM4tr1rMUms8+lnVlvJclmUqGyfhDTeRo7RJvm0B8xc0n5iWPppagWqzQuGauKdRto9VrUQIu92OENKqg1zo+U+WbeE7KMUiW27Bg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=DAOHAgVu; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="DAOHAgVu" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 1F7E5C4CED6; Wed, 8 Jan 2025 00:58:57 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1736297937; bh=YFVVlulGLUTVHXMPc1opVRj7WKpmAFMKYCC8IdVDVTc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=DAOHAgVuTT7889gmFlt2NRoKS4IcrNkj2uqWu3yfHqIlARgdF8SasGYwTXCE7jSi4 W8YBwcWeMAVRX7yUWEvRFBKwjjA4ShprbuS93oxfuq4vawiSurnuifaPUcCgaRDXDR Ai+M0nArNerCKO+dsEtXTrxIr3GOXKAEwB+ndNcETNu9A7TejbsJcV1sxCvp0bfUqT TryUpV0Bjt0xP+BkG7zT25WWP1T8RZuvkbUe3nR28N/e7P3jdVC271j48p/TaHeoFD S1536rJ0xpfGHBVuD7GsDcyACLFtFZUkOjk6Qrlz9SjCZIZtlwoSMkhHwRGHsAqmFx DCHQM5HzrGpyw== From: Stephen Boyd To: Michael Turquette , Stephen Boyd Cc: linux-kernel@vger.kernel.org, linux-clk@vger.kernel.org, patches@lists.linux.dev Subject: [PATCH 4/4] clk: Make debugfs code into a loadable kernel module Date: Tue, 7 Jan 2025 16:58:49 -0800 Message-ID: <20250108005854.2973184-5-sboyd@kernel.org> X-Mailer: git-send-email 2.47.1.613.gc27f4b7a9f-goog In-Reply-To: <20250108005854.2973184-1-sboyd@kernel.org> References: <20250108005854.2973184-1-sboyd@kernel.org> Precedence: bulk X-Mailing-List: linux-clk@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Split the debugfs code for the clk framework into a separate loadable kernel module. This allows the debugfs code to be loaded later, or not at all. Signed-off-by: Stephen Boyd --- drivers/clk/Kconfig | 8 + drivers/clk/Makefile | 1 + drivers/clk/clk-debug.c | 529 ++++++++++++++++++++++++++++++++++ drivers/clk/clk-debug.h | 33 +++ drivers/clk/clk.c | 607 +++++----------------------------------- 5 files changed, 643 insertions(+), 535 deletions(-) create mode 100644 drivers/clk/clk-debug.c create mode 100644 drivers/clk/clk-debug.h diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index 713573b6c86c..fb84c2698cbd 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -33,6 +33,14 @@ menuconfig COMMON_CLK if COMMON_CLK +config COMMON_CLK_DEBUG + tristate "Common Clock Framework debug support" + depends on DEBUG_FS || COMPILE_TEST + default COMMON_CLK + help + Say yes here to enable debugging support for the common clock + framework. + config COMMON_CLK_WM831X tristate "Clock driver for WM831x/2x PMICs" depends on MFD_WM831X diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index bf4bd45adc3a..10e1dab55cc9 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -2,6 +2,7 @@ # common clock types obj-$(CONFIG_HAVE_CLK) += clk-devres.o clk-bulk.o clkdev.o obj-$(CONFIG_COMMON_CLK) += clk.o +obj-$(CONFIG_COMMON_CLK_DEBUG) += clk-debug.o obj-$(CONFIG_CLK_KUNIT_TEST) += clk-test.o clk-test-y := clk_test.o \ kunit_clk_assigned_rates_u64_one.dtbo.o \ diff --git a/drivers/clk/clk-debug.c b/drivers/clk/clk-debug.c new file mode 100644 index 000000000000..47af5f25c4e3 --- /dev/null +++ b/drivers/clk/clk-debug.c @@ -0,0 +1,529 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2010-2011 Canonical Ltd + * Copyright (C) 2011-2012 Linaro Ltd + * + * Debugfs functionality for the common clock framework. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "clk-debug.h" + +MODULE_IMPORT_NS("clk-debug"); + +static struct dentry *rootdir; + +static void clk_summary_show_one(struct clk_hw *hw, int level, int next_level, + bool first, void *data) +{ + struct seq_file *s = data; + int enable; + int phase; + struct clk *clk_user = NULL; + int multi_node = 0; + + seq_printf(s, "%*s%-*s %-7d %-8d %-8d %-11lu %-10lu ", + level * 3 + 1, "", + 35 - level * 3, clk_hw_get_name(hw), + clk_hw_enable_count(hw), clk_hw_prepare_count(hw), + clk_hw_protect_count(hw), + clk_hw_get_rate_recalc(hw), + clk_hw_get_accuracy_recalc(hw)); + + phase = clk_hw_get_phase(hw); + if (phase >= 0) + seq_printf(s, "%-5d", phase); + else + seq_puts(s, "-----"); + + seq_printf(s, " %-6d", clk_hw_get_scaled_duty_cycle(hw, 100000)); + + enable = clk_hw_enable_state(hw); + if (enable >= 0) + seq_printf(s, " %5c ", enable ? 'Y' : 'N'); + else + seq_printf(s, " %5c ", '?'); + + while ((clk_user = clk_hw_next_consumer(hw, clk_user))) { + seq_printf(s, "%*s%-*s %-25s\n", + level * 3 + 2 + 105 * multi_node, "", + 30, + clk_dev_id(clk_user) ? : "deviceless", + clk_con_id(clk_user) ? : "no_connection_id"); + + multi_node = 1; + } +} + +static int clk_summary_show(struct seq_file *s, void *data) +{ + bool orphan_only = s->private; + + seq_puts(s, " enable prepare protect duty hardware connection\n"); + seq_puts(s, " clock count count count rate accuracy phase cycle enable consumer id\n"); + seq_puts(s, "---------------------------------------------------------------------------------------------------------------------------------------------\n"); + + return clk_show_tree(clk_summary_show_one, s, orphan_only); +} +DEFINE_SHOW_ATTRIBUTE(clk_summary); + +static void clk_dump_one(struct clk_hw *hw, int level, int next_level, bool first, void *data) +{ + struct seq_file *s = data; + int phase; + unsigned long min_rate, max_rate; + + clk_hw_get_rate_range(hw, &min_rate, &max_rate); + + if (!first) + seq_putc(s, ','); + + /* This should be JSON format, i.e. elements separated with a comma */ + seq_printf(s, "\"%s\": { ", clk_hw_get_name(hw)); + seq_printf(s, "\"enable_count\": %d,", clk_hw_enable_count(hw)); + seq_printf(s, "\"prepare_count\": %d,", clk_hw_prepare_count(hw)); + seq_printf(s, "\"protect_count\": %d,", clk_hw_protect_count(hw)); + seq_printf(s, "\"rate\": %lu,", clk_hw_get_rate_recalc(hw)); + seq_printf(s, "\"min_rate\": %lu,", min_rate); + seq_printf(s, "\"max_rate\": %lu,", max_rate); + seq_printf(s, "\"accuracy\": %lu,", clk_hw_get_accuracy_recalc(hw)); + phase = clk_hw_get_phase(hw); + if (phase >= 0) + seq_printf(s, "\"phase\": %d,", phase); + seq_printf(s, "\"duty_cycle\": %u", + clk_hw_get_scaled_duty_cycle(hw, 100000)); + + while (level-- >= next_level) + seq_putc(s, '}'); +} + +static int clk_dump_show(struct seq_file *s, void *data) +{ + bool orphan_only = s->private; + + seq_putc(s, '{'); + clk_show_tree(clk_dump_one, s, orphan_only); + seq_puts(s, "}\n"); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(clk_dump); + +/* + * This can be dangerous, therefore don't provide any real compile time + * configuration option for this feature. + * People who want to use this will need to modify the source code directly. + */ +#undef CLOCK_ALLOW_WRITE_DEBUGFS +#ifdef CLOCK_ALLOW_WRITE_DEBUGFS +static int clk_rate_set(void *data, u64 val) +{ + struct clk_hw *hw = data; + struct clk *clk = clk_hw_get_clk(hw, "debugfs_rate_set"); + int ret; + + if (IS_ERR(clk)) + return PTR_ERR(clk); + + ret = clk_set_rate(clk, val); + clk_put(clk); + + return ret; +} + +#define clk_rate_mode 0644 + +static int clk_phase_set(void *data, u64 val) +{ + struct clk_hw *hw = data; + struct clk *clk = clk_hw_get_clk(hw, "debugfs_phase_set"); + int degrees = do_div(val, 360); + int ret; + + if (IS_ERR(clk)) + return PTR_ERR(clk); + + ret = clk_set_phase(clk, degrees); + clk_put(clk); + + return ret; +} + +#define clk_phase_mode 0644 + +static int clk_prepare_enable_set(void *data, u64 val) +{ + struct clk_hw *hw = data; + struct clk *clk = clk_hw_get_clk(hw, "debugfs_prepare_enable_set"); + int ret = 0; + + if (IS_ERR(clk)) + return PTR_ERR(clk); + + if (val) + ret = clk_prepare_enable(clk); + else + clk_disable_unprepare(clk); + clk_put(clk); + + return ret; +} + +static int clk_prepare_enable_get(void *data, u64 *val) +{ + struct clk_hw *hw = data; + + *val = clk_hw_is_prepared(hw) && clk_hw_is_enabled(hw); + return 0; +} + +DEFINE_DEBUGFS_ATTRIBUTE(clk_prepare_enable_fops, clk_prepare_enable_get, + clk_prepare_enable_set, "%llu\n"); + +#else +#define clk_rate_set NULL +#define clk_rate_mode 0444 + +#define clk_phase_set NULL +#define clk_phase_mode 0644 +#endif + +static int clk_rate_get(void *data, u64 *val) +{ + struct clk_hw *hw = data; + struct clk *clk = clk_hw_get_clk(hw, "debugfs_rate_get"); + + if (IS_ERR(clk)) + return PTR_ERR(clk); + + *val = clk_get_rate(clk); + clk_put(clk); + + return 0; +} + +DEFINE_DEBUGFS_ATTRIBUTE(clk_rate_fops, clk_rate_get, clk_rate_set, "%llu\n"); + +static int clk_phase_get(void *data, u64 *val) +{ + struct clk_hw *hw = data; + struct clk *clk = clk_hw_get_clk(hw, "debugfs_phase_get"); + + if (IS_ERR(clk)) + return PTR_ERR(clk); + + *val = clk_get_phase(clk); + clk_put(clk); + + return 0; +} + +DEFINE_DEBUGFS_ATTRIBUTE(clk_phase_fops, clk_phase_get, clk_phase_set, "%llu\n"); + +static const struct { + unsigned long flag; + const char *name; +} clk_flags[] = { +#define ENTRY(f) { f, #f } + ENTRY(CLK_SET_RATE_GATE), + ENTRY(CLK_SET_PARENT_GATE), + ENTRY(CLK_SET_RATE_PARENT), + ENTRY(CLK_IGNORE_UNUSED), + ENTRY(CLK_GET_RATE_NOCACHE), + ENTRY(CLK_SET_RATE_NO_REPARENT), + ENTRY(CLK_GET_ACCURACY_NOCACHE), + ENTRY(CLK_RECALC_NEW_RATES), + ENTRY(CLK_SET_RATE_UNGATE), + ENTRY(CLK_IS_CRITICAL), + ENTRY(CLK_OPS_PARENT_ENABLE), + ENTRY(CLK_DUTY_CYCLE_PARENT), +#undef ENTRY +}; + +static int clk_flags_show(struct seq_file *s, void *data) +{ + struct clk_hw *hw = s->private; + unsigned long flags = clk_hw_get_flags(hw); + unsigned int i; + + for (i = 0; flags && i < ARRAY_SIZE(clk_flags); i++) { + if (flags & clk_flags[i].flag) { + seq_printf(s, "%s\n", clk_flags[i].name); + flags &= ~clk_flags[i].flag; + } + } + if (flags) { + /* Unknown flags */ + seq_printf(s, "0x%lx\n", flags); + } + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(clk_flags); + +static int possible_parents_show(struct seq_file *s, void *data) +{ + struct clk_hw *hw = s->private; + int i; + + for (i = 0; i < clk_hw_get_num_parents(hw) - 1; i++) + clk_hw_show_parent_by_index(s, hw, i, ' '); + + clk_hw_show_parent_by_index(s, hw, i, '\n'); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(possible_parents); + +static int current_parent_show(struct seq_file *s, void *data) +{ + struct clk_hw *hw = s->private; + struct clk_hw *parent = clk_hw_get_parent(hw); + + if (parent) + seq_printf(s, "%s\n", clk_hw_get_name(parent)); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(current_parent); + +#ifdef CLOCK_ALLOW_WRITE_DEBUGFS +static ssize_t current_parent_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct seq_file *s = file->private_data; + struct clk *clk, *parent; + struct clk_hw *hw = s->private; + struct clk_hw *parent_hw; + u8 idx; + int ret; + + ret = kstrtou8_from_user(ubuf, count, 0, &idx); + if (ret < 0) + return ret; + + parent_hw = clk_hw_get_parent_by_index(hw, idx); + if (!parent_hw) + return -ENOENT; + + clk = clk_hw_get_clk(hw, "debugfs_write"); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + parent = clk_hw_get_clk(parent_hw, "debugfs_write"); + if (IS_ERR(parent)) { + ret = PTR_ERR(parent); + goto err; + } + + ret = clk_set_parent(clk, parent); + if (!ret) + ret = count; + + clk_put(parent); +err: + clk_put(clk); + return ret; +} + +static const struct file_operations current_parent_rw_fops = { + .open = current_parent_open, + .write = current_parent_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; +#endif + +static int clk_duty_cycle_show(struct seq_file *s, void *data) +{ + struct clk_hw *hw = s->private; + struct clk_duty duty = { }; + + clk_hw_get_duty(hw, &duty); + + seq_printf(s, "%u/%u\n", duty.num, duty.den); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(clk_duty_cycle); + +static int clk_min_rate_show(struct seq_file *s, void *data) +{ + struct clk_hw *hw = s->private; + unsigned long min_rate, max_rate; + + clk_debug_get_rate_range(hw, &min_rate, &max_rate); + seq_printf(s, "%lu\n", min_rate); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(clk_min_rate); + +static int clk_max_rate_show(struct seq_file *s, void *data) +{ + struct clk_hw *hw = s->private; + unsigned long min_rate, max_rate; + + clk_debug_get_rate_range(hw, &min_rate, &max_rate); + seq_printf(s, "%lu\n", max_rate); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(clk_max_rate); + +static int clk_accuracy_show(struct seq_file *s, void *data) +{ + struct clk_hw *hw = s->private; + struct clk *clk = clk_hw_get_clk(hw, "debugfs_accuracy"); + unsigned long accuracy; + + if (IS_ERR(clk)) + return PTR_ERR(clk); + + accuracy = clk_get_accuracy(clk); + seq_printf(s, "%lu\n", accuracy); + clk_put(clk); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(clk_accuracy); + +static int clk_prepare_show(struct seq_file *s, void *data) +{ + struct clk_hw *hw = s->private; + + seq_printf(s, "%u\n", clk_hw_prepare_count(hw)); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(clk_prepare); + +static int clk_enable_show(struct seq_file *s, void *data) +{ + struct clk_hw *hw = s->private; + + seq_printf(s, "%u\n", clk_hw_enable_count(hw)); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(clk_enable); + +static int clk_protect_show(struct seq_file *s, void *data) +{ + struct clk_hw *hw = s->private; + + seq_printf(s, "%u\n", clk_hw_protect_count(hw)); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(clk_protect); + +static int clk_notifier_show(struct seq_file *s, void *data) +{ + struct clk_hw *hw = s->private; + + seq_printf(s, "%u\n", clk_hw_notifier_count(hw)); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(clk_notifier); + +static struct dentry *clk_hw_debug_create_one(struct clk_hw *hw) +{ + struct dentry *root; + + root = debugfs_create_dir(clk_hw_get_name(hw), rootdir); + + debugfs_create_file("clk_rate", clk_rate_mode, root, hw, &clk_rate_fops); + debugfs_create_file("clk_min_rate", 0444, root, hw, &clk_min_rate_fops); + debugfs_create_file("clk_max_rate", 0444, root, hw, &clk_max_rate_fops); + debugfs_create_file("clk_accuracy", 0444, root, hw, &clk_accuracy_fops); + debugfs_create_file("clk_phase", clk_phase_mode, root, hw, &clk_phase_fops); + debugfs_create_file("clk_flags", 0444, root, hw, &clk_flags_fops); + debugfs_create_file("clk_prepare_count", 0444, root, hw, &clk_prepare_fops); + debugfs_create_file("clk_enable_count", 0444, root, hw, &clk_enable_fops); + debugfs_create_file("clk_protect_count", 0444, root, hw, &clk_protect_fops); + debugfs_create_file("clk_notifier_count", 0444, root, hw, &clk_notifier_fops); + debugfs_create_file("clk_duty_cycle", 0444, root, hw, &clk_duty_cycle_fops); +#ifdef CLOCK_ALLOW_WRITE_DEBUGFS + debugfs_create_file("clk_prepare_enable", 0644, root, hw, + &clk_prepare_enable_fops); + + if (clk_hw_get_num_parents(hw) > 1) + debugfs_create_file("clk_parent", 0644, root, hw, + ¤t_parent_rw_fops); + else +#endif + if (clk_hw_get_num_parents(hw) > 0) + debugfs_create_file("clk_parent", 0444, root, hw, + ¤t_parent_fops); + + if (clk_hw_get_num_parents(hw) > 1) + debugfs_create_file("clk_possible_parents", 0444, root, hw, + &possible_parents_fops); + + return root; +} + +/** + * clk_debug_init - lazily populate the debugfs clk directory + * + * clks are often initialized very early during boot before memory can be + * dynamically allocated and well before debugfs is setup. This function + * populates the debugfs clk directory once at boot-time when we know that + * debugfs is setup. It should only be called once at boot-time, all other clks + * added dynamically will be done so with clk_debug_register. + */ +static int __init clk_debug_init(void) +{ +#ifdef CLOCK_ALLOW_WRITE_DEBUGFS + pr_warn("\n"); + pr_warn("********************************************************************\n"); + pr_warn("** NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE **\n"); + pr_warn("** **\n"); + pr_warn("** WRITEABLE clk DebugFS SUPPORT HAS BEEN ENABLED IN THIS KERNEL **\n"); + pr_warn("** **\n"); + pr_warn("** This means that this kernel is built to expose clk operations **\n"); + pr_warn("** such as parent or rate setting, enabling, disabling, etc. **\n"); + pr_warn("** to userspace, which may compromise security on your system. **\n"); + pr_warn("** **\n"); + pr_warn("** If you see this message and you are not debugging the **\n"); + pr_warn("** kernel, report this immediately to your vendor! **\n"); + pr_warn("** **\n"); + pr_warn("** NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE **\n"); + pr_warn("********************************************************************\n"); +#endif + + rootdir = debugfs_create_dir("clk", NULL); + + debugfs_create_file("clk_summary", 0444, rootdir, (void *)0UL, + &clk_summary_fops); + debugfs_create_file("clk_dump", 0444, rootdir, (void *)0UL, + &clk_dump_fops); + debugfs_create_file("clk_orphan_summary", 0444, rootdir, (void *)1UL, + &clk_summary_fops); + debugfs_create_file("clk_orphan_dump", 0444, rootdir, (void *)1UL, + &clk_dump_fops); + + clk_hw_debug_for_each_init(clk_hw_debug_create_one); + + return 0; +} +late_initcall(clk_debug_init); + +static void __exit clk_debug_exit(void) +{ + clk_hw_debug_exit(); + debugfs_remove_recursive(rootdir); +} +module_exit(clk_debug_exit); + +MODULE_LICENSE("GPL"); diff --git a/drivers/clk/clk-debug.h b/drivers/clk/clk-debug.h new file mode 100644 index 000000000000..007fa32237c0 --- /dev/null +++ b/drivers/clk/clk-debug.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +struct clk; +struct clk_duty; +struct clk_hw; +struct seq_file; + +extern int clk_hw_enable_state(struct clk_hw *hw); +extern unsigned int clk_hw_enable_count(struct clk_hw *hw); +extern unsigned int clk_hw_prepare_count(struct clk_hw *hw); +extern unsigned int clk_hw_protect_count(struct clk_hw *hw); +extern int clk_hw_get_phase(struct clk_hw *hw); +extern int clk_hw_get_scaled_duty_cycle(struct clk_hw *hw, unsigned int scale); +extern void clk_hw_get_duty(struct clk_hw *hw, struct clk_duty *duty); +extern unsigned long clk_hw_get_rate_recalc(struct clk_hw *hw); +extern long clk_hw_get_accuracy_recalc(struct clk_hw *hw); +extern void clk_debug_get_rate_range(struct clk_hw *hw, unsigned long *min_rate, + unsigned long *max_rate); +extern unsigned int clk_hw_notifier_count(struct clk_hw *hw); + +extern const char *clk_con_id(struct clk *clk); +extern const char *clk_dev_id(struct clk *clk); +extern struct clk *clk_hw_next_consumer(struct clk_hw *hw, struct clk *prev); +extern void clk_hw_show_parent_by_index(struct seq_file *s, struct clk_hw *hw, + unsigned int i, char terminator); + +extern int clk_show_tree(void (*show_fn)(struct clk_hw *hw, int level, + int next_level, bool first, + void *data), + void *data, bool orphan_only); + +extern void clk_hw_debug_for_each_init(struct dentry *(*fn)(struct clk_hw *hw)); +extern void clk_hw_debug_exit(void); diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c index 37c7963d685f..a3529595e71b 100644 --- a/drivers/clk/clk.c +++ b/drivers/clk/clk.c @@ -23,6 +23,7 @@ #include #include "clk.h" +#include "clk-debug.h" static DEFINE_SPINLOCK(enable_lock); static DEFINE_MUTEX(prepare_lock); @@ -819,13 +820,14 @@ void clk_hw_get_rate_range(struct clk_hw *hw, unsigned long *min_rate, } EXPORT_SYMBOL_GPL(clk_hw_get_rate_range); -static void clk_debug_get_rate_range(struct clk_hw *hw, unsigned long *min_rate, - unsigned long *max_rate) +void clk_debug_get_rate_range(struct clk_hw *hw, unsigned long *min_rate, + unsigned long *max_rate) { clk_prepare_lock(); clk_hw_get_rate_range(hw, min_rate, max_rate); clk_prepare_unlock(); } +EXPORT_SYMBOL_NS_GPL(clk_debug_get_rate_range, "clk-debug"); static bool clk_core_check_boundaries(struct clk_core *core, unsigned long min_rate, @@ -1906,10 +1908,11 @@ static long clk_core_get_accuracy_recalc(struct clk_core *core) return clk_core_get_accuracy_no_lock(core); } -static long clk_hw_get_accuracy_recalc(struct clk_hw *hw) +long clk_hw_get_accuracy_recalc(struct clk_hw *hw) { return clk_core_get_accuracy_recalc(hw->core); } +EXPORT_SYMBOL_NS_GPL(clk_hw_get_accuracy_recalc, "clk-debug"); /** * clk_get_accuracy - return the accuracy of clk @@ -1997,10 +2000,11 @@ static unsigned long clk_core_get_rate_recalc(struct clk_core *core) return clk_core_get_rate_nolock(core); } -static unsigned long clk_hw_get_rate_recalc(struct clk_hw *hw) +unsigned long clk_hw_get_rate_recalc(struct clk_hw *hw) { return clk_core_get_rate_recalc(hw->core); } +EXPORT_SYMBOL_NS_GPL(clk_hw_get_rate_recalc, "clk-debug"); /** * clk_get_rate - return the rate of clk @@ -3078,10 +3082,11 @@ static int clk_core_get_phase(struct clk_core *core) return ret; } -static int clk_hw_get_phase(struct clk_hw *hw) +int clk_hw_get_phase(struct clk_hw *hw) { return clk_core_get_phase(hw->core); } +EXPORT_SYMBOL_NS_GPL(clk_hw_get_phase, "clk-debug"); /** * clk_get_phase - return the phase shift of a clock signal @@ -3250,11 +3255,11 @@ static int clk_core_get_scaled_duty_cycle(struct clk_core *core, return ret; } -static int clk_hw_get_scaled_duty_cycle(struct clk_hw *hw, - unsigned int scale) +int clk_hw_get_scaled_duty_cycle(struct clk_hw *hw, unsigned int scale) { return clk_core_get_scaled_duty_cycle(hw->core, scale); } +EXPORT_SYMBOL_NS_GPL(clk_hw_get_scaled_duty_cycle, "clk-debug"); /** * clk_get_scaled_duty_cycle - return the duty cycle ratio of a clock signal @@ -3307,11 +3312,9 @@ EXPORT_SYMBOL_GPL(clk_is_match); /*** debugfs support ***/ -#ifdef CONFIG_DEBUG_FS +#if IS_ENABLED(CONFIG_COMMON_CLK_DEBUG) #include -static struct dentry *rootdir; -static int inited = 0; static DEFINE_MUTEX(clk_debug_lock); static HLIST_HEAD(clk_debug_list); @@ -3320,7 +3323,7 @@ static HLIST_HEAD(clk_debug_list); * 0: Disabled in hardware * -1: Unknown enable state */ -static int clk_hw_enable_state(struct clk_hw *hw) +int clk_hw_enable_state(struct clk_hw *hw) { struct clk_core *core = hw->core; @@ -3331,39 +3334,58 @@ static int clk_hw_enable_state(struct clk_hw *hw) return -1; } +EXPORT_SYMBOL_NS_GPL(clk_hw_enable_state, "clk-debug"); -static unsigned int clk_hw_enable_count(struct clk_hw *hw) +unsigned int clk_hw_enable_count(struct clk_hw *hw) { return hw->core->enable_count; } +EXPORT_SYMBOL_NS_GPL(clk_hw_enable_count, "clk-debug"); -static unsigned int clk_hw_prepare_count(struct clk_hw *hw) +unsigned int clk_hw_prepare_count(struct clk_hw *hw) { return hw->core->prepare_count; } +EXPORT_SYMBOL_NS_GPL(clk_hw_prepare_count, "clk-debug"); -static unsigned int clk_hw_protect_count(struct clk_hw *hw) +unsigned int clk_hw_protect_count(struct clk_hw *hw) { return hw->core->protect_count; } +EXPORT_SYMBOL_NS_GPL(clk_hw_protect_count, "clk-debug"); -static const char *clk_con_id(struct clk *clk) +const char *clk_con_id(struct clk *clk) { return clk->con_id; } +EXPORT_SYMBOL_NS_GPL(clk_con_id, "clk-debug"); -static const char *clk_dev_id(struct clk *clk) +const char *clk_dev_id(struct clk *clk) { return clk->dev_id; } +EXPORT_SYMBOL_NS_GPL(clk_dev_id, "clk-debug"); -static struct clk *clk_hw_next_consumer(struct clk_hw *hw, struct clk *prev) +struct clk *clk_hw_next_consumer(struct clk_hw *hw, struct clk *prev) { if (prev) return hlist_entry_safe(prev->clks_node.next, struct clk, clks_node); return hlist_entry_safe(hw->core->clks.first, struct clk, clks_node); } +EXPORT_SYMBOL_NS_GPL(clk_hw_next_consumer, "clk-debug"); + +void clk_hw_get_duty(struct clk_hw *hw, struct clk_duty *duty) +{ + memcpy(duty, &hw->core->duty, sizeof(*duty)); +} +EXPORT_SYMBOL_NS_GPL(clk_hw_get_duty, "clk-debug"); + +unsigned int clk_hw_notifier_count(struct clk_hw *hw) +{ + return hw->core->notifier_count; +} +EXPORT_SYMBOL_NS_GPL(clk_hw_notifier_count, "clk-debug"); static void clk_walk_trees(struct hlist_head *list, bool only_orphans) { @@ -3393,10 +3415,9 @@ static void clk_walk_trees(struct hlist_head *list, bool only_orphans) } } -static int clk_show_tree(void (*show_fn)(struct clk_hw *hw, int level, - int next_level, bool first, - void *data), - void *data, bool orphan_only) +int clk_show_tree(void (*show_fn)(struct clk_hw *hw, int level, int next_level, + bool first, void *data), + void *data, bool orphan_only) { struct clk_core *core, *parent; struct hlist_node *tmp; @@ -3439,257 +3460,10 @@ static int clk_show_tree(void (*show_fn)(struct clk_hw *hw, int level, return 0; } +EXPORT_SYMBOL_NS_GPL(clk_show_tree, "clk-debug"); -static void clk_summary_show_one(struct clk_hw *hw, int level, int next_level, - bool first, void *data) -{ - struct seq_file *s = data; - int enable; - int phase; - struct clk *clk_user = NULL; - int multi_node = 0; - - seq_printf(s, "%*s%-*s %-7d %-8d %-8d %-11lu %-10lu ", - level * 3 + 1, "", - 35 - level * 3, clk_hw_get_name(hw), - clk_hw_enable_count(hw), clk_hw_prepare_count(hw), - clk_hw_protect_count(hw), - clk_hw_get_rate_recalc(hw), - clk_hw_get_accuracy_recalc(hw)); - - phase = clk_hw_get_phase(hw); - if (phase >= 0) - seq_printf(s, "%-5d", phase); - else - seq_puts(s, "-----"); - - seq_printf(s, " %-6d", clk_hw_get_scaled_duty_cycle(hw, 100000)); - - enable = clk_hw_enable_state(hw); - if (enable >= 0) - seq_printf(s, " %5c ", enable ? 'Y' : 'N'); - else - seq_printf(s, " %5c ", '?'); - - while ((clk_user = clk_hw_next_consumer(hw, clk_user))) { - seq_printf(s, "%*s%-*s %-25s\n", - level * 3 + 2 + 105 * multi_node, "", - 30, - clk_dev_id(clk_user) ? : "deviceless", - clk_con_id(clk_user) ? : "no_connection_id"); - - multi_node = 1; - } -} - -static int clk_summary_show(struct seq_file *s, void *data) -{ - bool orphan_only = s->private; - - seq_puts(s, " enable prepare protect duty hardware connection\n"); - seq_puts(s, " clock count count count rate accuracy phase cycle enable consumer id\n"); - seq_puts(s, "---------------------------------------------------------------------------------------------------------------------------------------------\n"); - - return clk_show_tree(clk_summary_show_one, s, orphan_only); -} -DEFINE_SHOW_ATTRIBUTE(clk_summary); - -static void clk_dump_one(struct clk_hw *hw, int level, int next_level, bool first, void *data) -{ - struct seq_file *s = data; - int phase; - unsigned long min_rate, max_rate; - - clk_hw_get_rate_range(hw, &min_rate, &max_rate); - - if (!first) - seq_putc(s, ','); - - /* This should be JSON format, i.e. elements separated with a comma */ - seq_printf(s, "\"%s\": { ", clk_hw_get_name(hw)); - seq_printf(s, "\"enable_count\": %d,", clk_hw_enable_count(hw)); - seq_printf(s, "\"prepare_count\": %d,", clk_hw_prepare_count(hw)); - seq_printf(s, "\"protect_count\": %d,", clk_hw_protect_count(hw)); - seq_printf(s, "\"rate\": %lu,", clk_hw_get_rate_recalc(hw)); - seq_printf(s, "\"min_rate\": %lu,", min_rate); - seq_printf(s, "\"max_rate\": %lu,", max_rate); - seq_printf(s, "\"accuracy\": %lu,", clk_hw_get_accuracy_recalc(hw)); - phase = clk_hw_get_phase(hw); - if (phase >= 0) - seq_printf(s, "\"phase\": %d,", phase); - seq_printf(s, "\"duty_cycle\": %u", - clk_hw_get_scaled_duty_cycle(hw, 100000)); - - while (level-- >= next_level) - seq_putc(s, '}'); -} - -static int clk_dump_show(struct seq_file *s, void *data) -{ - bool orphan_only = s->private; - - seq_putc(s, '{'); - clk_show_tree(clk_dump_one, s, orphan_only); - seq_puts(s, "}\n"); - - return 0; -} -DEFINE_SHOW_ATTRIBUTE(clk_dump); - -/* - * This can be dangerous, therefore don't provide any real compile time - * configuration option for this feature. - * People who want to use this will need to modify the source code directly. - */ -#undef CLOCK_ALLOW_WRITE_DEBUGFS -#ifdef CLOCK_ALLOW_WRITE_DEBUGFS -static int clk_rate_set(void *data, u64 val) -{ - struct clk_hw *hw = data; - struct clk *clk = clk_hw_get_clk(hw, "debugfs_rate_set"); - int ret; - - if (IS_ERR(clk)) - return PTR_ERR(clk); - - ret = clk_set_rate(clk, val); - clk_put(clk); - - return ret; -} - -#define clk_rate_mode 0644 - -static int clk_phase_set(void *data, u64 val) -{ - struct clk_hw *hw = data; - struct clk *clk = clk_hw_get_clk(hw, "debugfs_phase_set"); - int degrees = do_div(val, 360); - int ret; - - if (IS_ERR(clk)) - return PTR_ERR(clk); - - ret = clk_set_phase(clk, degrees); - clk_put(clk); - - return ret; -} - -#define clk_phase_mode 0644 - -static int clk_prepare_enable_set(void *data, u64 val) -{ - struct clk_hw *hw = data; - struct clk *clk = clk_hw_get_clk(hw, "debugfs_prepare_enable_set"); - int ret = 0; - - if (IS_ERR(clk)) - return PTR_ERR(clk); - - if (val) - ret = clk_prepare_enable(clk); - else - clk_disable_unprepare(clk); - clk_put(clk); - - return ret; -} - -static int clk_prepare_enable_get(void *data, u64 *val) -{ - struct clk_hw *hw = data; - - *val = clk_hw_is_prepared(hw) && clk_hw_is_enabled(hw); - return 0; -} - -DEFINE_DEBUGFS_ATTRIBUTE(clk_prepare_enable_fops, clk_prepare_enable_get, - clk_prepare_enable_set, "%llu\n"); - -#else -#define clk_rate_set NULL -#define clk_rate_mode 0444 - -#define clk_phase_set NULL -#define clk_phase_mode 0644 -#endif - -static int clk_rate_get(void *data, u64 *val) -{ - struct clk_hw *hw = data; - struct clk *clk = clk_hw_get_clk(hw, "debugfs_rate_get"); - - if (IS_ERR(clk)) - return PTR_ERR(clk); - - *val = clk_get_rate(clk); - clk_put(clk); - - return 0; -} - -DEFINE_DEBUGFS_ATTRIBUTE(clk_rate_fops, clk_rate_get, clk_rate_set, "%llu\n"); - -static int clk_phase_get(void *data, u64 *val) -{ - struct clk_hw *hw = data; - struct clk *clk = clk_hw_get_clk(hw, "debugfs_phase_get"); - - if (IS_ERR(clk)) - return PTR_ERR(clk); - - *val = clk_get_phase(clk); - clk_put(clk); - - return 0; -} - -DEFINE_DEBUGFS_ATTRIBUTE(clk_phase_fops, clk_phase_get, clk_phase_set, "%llu\n"); - -static const struct { - unsigned long flag; - const char *name; -} clk_flags[] = { -#define ENTRY(f) { f, #f } - ENTRY(CLK_SET_RATE_GATE), - ENTRY(CLK_SET_PARENT_GATE), - ENTRY(CLK_SET_RATE_PARENT), - ENTRY(CLK_IGNORE_UNUSED), - ENTRY(CLK_GET_RATE_NOCACHE), - ENTRY(CLK_SET_RATE_NO_REPARENT), - ENTRY(CLK_GET_ACCURACY_NOCACHE), - ENTRY(CLK_RECALC_NEW_RATES), - ENTRY(CLK_SET_RATE_UNGATE), - ENTRY(CLK_IS_CRITICAL), - ENTRY(CLK_OPS_PARENT_ENABLE), - ENTRY(CLK_DUTY_CYCLE_PARENT), -#undef ENTRY -}; - -static int clk_flags_show(struct seq_file *s, void *data) -{ - struct clk_hw *hw = s->private; - unsigned long flags = clk_hw_get_flags(hw); - unsigned int i; - - for (i = 0; flags && i < ARRAY_SIZE(clk_flags); i++) { - if (flags & clk_flags[i].flag) { - seq_printf(s, "%s\n", clk_flags[i].name); - flags &= ~clk_flags[i].flag; - } - } - if (flags) { - /* Unknown flags */ - seq_printf(s, "0x%lx\n", flags); - } - - return 0; -} -DEFINE_SHOW_ATTRIBUTE(clk_flags); - -static void clk_hw_show_parent_by_index(struct seq_file *s, struct clk_hw *hw, - unsigned int i, char terminator) +void clk_hw_show_parent_by_index(struct seq_file *s, struct clk_hw *hw, + unsigned int i, char terminator) { struct clk_hw *parent; struct clk_core *core = hw->core; @@ -3725,227 +3499,15 @@ static void clk_hw_show_parent_by_index(struct seq_file *s, struct clk_hw *hw, seq_putc(s, terminator); } +EXPORT_SYMBOL_NS_GPL(clk_hw_show_parent_by_index, "clk-debug"); -static int possible_parents_show(struct seq_file *s, void *data) -{ - struct clk_hw *hw = s->private; - int i; - - for (i = 0; i < clk_hw_get_num_parents(hw) - 1; i++) - clk_hw_show_parent_by_index(s, hw, i, ' '); - - clk_hw_show_parent_by_index(s, hw, i, '\n'); - - return 0; -} -DEFINE_SHOW_ATTRIBUTE(possible_parents); - -static int current_parent_show(struct seq_file *s, void *data) -{ - struct clk_hw *hw = s->private; - struct clk_hw *parent = clk_hw_get_parent(hw); - - if (parent) - seq_printf(s, "%s\n", clk_hw_get_name(parent)); - - return 0; -} -DEFINE_SHOW_ATTRIBUTE(current_parent); - -#ifdef CLOCK_ALLOW_WRITE_DEBUGFS -static ssize_t current_parent_write(struct file *file, const char __user *ubuf, - size_t count, loff_t *ppos) -{ - struct seq_file *s = file->private_data; - struct clk *clk, *parent; - struct clk_hw *hw = s->private; - struct clk_hw *parent_hw; - u8 idx; - int ret; - - ret = kstrtou8_from_user(ubuf, count, 0, &idx); - if (ret < 0) - return ret; - - parent_hw = clk_hw_get_parent_by_index(hw, idx); - if (!parent_hw) - return -ENOENT; - - clk = clk_hw_get_clk(hw, "debugfs_write"); - if (IS_ERR(clk)) - return PTR_ERR(clk); - - parent = clk_hw_get_clk(parent_hw, "debugfs_write"); - if (IS_ERR(parent)) { - ret = PTR_ERR(parent); - goto err; - } - - ret = clk_set_parent(clk, parent); - if (!ret) - ret = count; - - clk_put(parent); -err: - clk_put(clk); - return ret; -} - -static const struct file_operations current_parent_rw_fops = { - .open = current_parent_open, - .write = current_parent_write, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; -#endif - -static void clk_hw_get_duty(struct clk_hw *hw, struct clk_duty *duty) -{ - memcpy(duty, &hw->core->duty, sizeof(*duty)); -} - -static int clk_duty_cycle_show(struct seq_file *s, void *data) -{ - struct clk_hw *hw = s->private; - struct clk_duty duty = { }; - - clk_hw_get_duty(hw, &duty); - - seq_printf(s, "%u/%u\n", duty.num, duty.den); - - return 0; -} -DEFINE_SHOW_ATTRIBUTE(clk_duty_cycle); - -static int clk_min_rate_show(struct seq_file *s, void *data) -{ - struct clk_hw *hw = s->private; - unsigned long min_rate, max_rate; - - clk_debug_get_rate_range(hw, &min_rate, &max_rate); - seq_printf(s, "%lu\n", min_rate); - - return 0; -} -DEFINE_SHOW_ATTRIBUTE(clk_min_rate); - -static int clk_max_rate_show(struct seq_file *s, void *data) -{ - struct clk_hw *hw = s->private; - unsigned long min_rate, max_rate; - - clk_debug_get_rate_range(hw, &min_rate, &max_rate); - seq_printf(s, "%lu\n", max_rate); - - return 0; -} -DEFINE_SHOW_ATTRIBUTE(clk_max_rate); - -static int clk_accuracy_show(struct seq_file *s, void *data) -{ - struct clk_hw *hw = s->private; - struct clk *clk = clk_hw_get_clk(hw, "debugfs_accuracy"); - unsigned long accuracy; - - if (IS_ERR(clk)) - return PTR_ERR(clk); - - accuracy = clk_get_accuracy(clk); - seq_printf(s, "%lu\n", accuracy); - clk_put(clk); - - return 0; -} -DEFINE_SHOW_ATTRIBUTE(clk_accuracy); - -static int clk_prepare_show(struct seq_file *s, void *data) -{ - struct clk_hw *hw = s->private; - - seq_printf(s, "%u\n", clk_hw_prepare_count(hw)); - - return 0; -} -DEFINE_SHOW_ATTRIBUTE(clk_prepare); - -static int clk_enable_show(struct seq_file *s, void *data) -{ - struct clk_hw *hw = s->private; - - seq_printf(s, "%u\n", clk_hw_enable_count(hw)); - - return 0; -} -DEFINE_SHOW_ATTRIBUTE(clk_enable); - -static int clk_protect_show(struct seq_file *s, void *data) -{ - struct clk_hw *hw = s->private; - - seq_printf(s, "%u\n", clk_hw_protect_count(hw)); - - return 0; -} -DEFINE_SHOW_ATTRIBUTE(clk_protect); - -static unsigned int clk_hw_notifier_count(struct clk_hw *hw) -{ - return hw->core->notifier_count; -} - -static int clk_notifier_show(struct seq_file *s, void *data) -{ - struct clk_hw *hw = s->private; - - seq_printf(s, "%u\n", clk_hw_notifier_count(hw)); - - return 0; -} -DEFINE_SHOW_ATTRIBUTE(clk_notifier); - -static struct dentry *clk_hw_debug_create_one(struct clk_hw *hw) -{ - struct dentry *root; - - root = debugfs_create_dir(clk_hw_get_name(hw), rootdir); - - debugfs_create_file("clk_rate", clk_rate_mode, root, hw, &clk_rate_fops); - debugfs_create_file("clk_min_rate", 0444, root, hw, &clk_min_rate_fops); - debugfs_create_file("clk_max_rate", 0444, root, hw, &clk_max_rate_fops); - debugfs_create_file("clk_accuracy", 0444, root, hw, &clk_accuracy_fops); - debugfs_create_file("clk_phase", clk_phase_mode, root, hw, &clk_phase_fops); - debugfs_create_file("clk_flags", 0444, root, hw, &clk_flags_fops); - debugfs_create_file("clk_prepare_count", 0444, root, hw, &clk_prepare_fops); - debugfs_create_file("clk_enable_count", 0444, root, hw, &clk_enable_fops); - debugfs_create_file("clk_protect_count", 0444, root, hw, &clk_protect_fops); - debugfs_create_file("clk_notifier_count", 0444, root, hw, &clk_notifier_fops); - debugfs_create_file("clk_duty_cycle", 0444, root, hw, &clk_duty_cycle_fops); -#ifdef CLOCK_ALLOW_WRITE_DEBUGFS - debugfs_create_file("clk_prepare_enable", 0644, root, hw, - &clk_prepare_enable_fops); - - if (clk_hw_get_num_parents(hw) > 1) - debugfs_create_file("clk_parent", 0644, root, hw, - ¤t_parent_rw_fops); - else -#endif - if (clk_hw_get_num_parents(hw) > 0) - debugfs_create_file("clk_parent", 0444, root, hw, - ¤t_parent_fops); - - if (clk_hw_get_num_parents(hw) > 1) - debugfs_create_file("clk_possible_parents", 0444, root, hw, - &possible_parents_fops); - - return root; -} +static struct dentry *(*clk_hw_debug_create_one)(struct clk_hw *hw); static void clk_core_debug_create_one(struct clk_core *core) { struct clk_hw *hw = core->hw; - if (!inited) + if (!clk_hw_debug_create_one) return; core->dentry = clk_hw_debug_create_one(hw); @@ -3969,6 +3531,13 @@ static void clk_debug_register(struct clk_core *core) mutex_unlock(&clk_debug_lock); } +static void clk_core_debug_remove_one(struct clk_core *core) +{ + hlist_del_init(&core->debug_node); + debugfs_remove_recursive(core->dentry); + core->dentry = NULL; +} + /** * clk_debug_unregister - remove a clk node from the debugfs clk directory * @core: the clk being removed from the debugfs clk directory @@ -3980,68 +3549,36 @@ static void clk_debug_register(struct clk_core *core) static void clk_debug_unregister(struct clk_core *core) { mutex_lock(&clk_debug_lock); - hlist_del_init(&core->debug_node); - debugfs_remove_recursive(core->dentry); - core->dentry = NULL; + clk_core_debug_remove_one(core); mutex_unlock(&clk_debug_lock); } -/** - * clk_debug_init - lazily populate the debugfs clk directory - * - * clks are often initialized very early during boot before memory can be - * dynamically allocated and well before debugfs is setup. This function - * populates the debugfs clk directory once at boot-time when we know that - * debugfs is setup. It should only be called once at boot-time, all other clks - * added dynamically will be done so with clk_debug_register. - */ -static int __init clk_debug_init(void) +void clk_hw_debug_for_each_init(struct dentry *(*fn)(struct clk_hw *hw)) { struct clk_core *core; -#ifdef CLOCK_ALLOW_WRITE_DEBUGFS - pr_warn("\n"); - pr_warn("********************************************************************\n"); - pr_warn("** NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE **\n"); - pr_warn("** **\n"); - pr_warn("** WRITEABLE clk DebugFS SUPPORT HAS BEEN ENABLED IN THIS KERNEL **\n"); - pr_warn("** **\n"); - pr_warn("** This means that this kernel is built to expose clk operations **\n"); - pr_warn("** such as parent or rate setting, enabling, disabling, etc. **\n"); - pr_warn("** to userspace, which may compromise security on your system. **\n"); - pr_warn("** **\n"); - pr_warn("** If you see this message and you are not debugging the **\n"); - pr_warn("** kernel, report this immediately to your vendor! **\n"); - pr_warn("** **\n"); - pr_warn("** NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE **\n"); - pr_warn("********************************************************************\n"); -#endif - - rootdir = debugfs_create_dir("clk", NULL); - - debugfs_create_file("clk_summary", 0444, rootdir, (void *)0UL, - &clk_summary_fops); - debugfs_create_file("clk_dump", 0444, rootdir, (void *)0UL, - &clk_dump_fops); - debugfs_create_file("clk_orphan_summary", 0444, rootdir, (void *)1UL, - &clk_summary_fops); - debugfs_create_file("clk_orphan_dump", 0444, rootdir, (void *)1UL, - &clk_dump_fops); - mutex_lock(&clk_debug_lock); - inited = 1; + clk_hw_debug_create_one = fn; hlist_for_each_entry(core, &clk_debug_list, debug_node) clk_core_debug_create_one(core); mutex_unlock(&clk_debug_lock); - - return 0; } -late_initcall(clk_debug_init); +EXPORT_SYMBOL_NS_GPL(clk_hw_debug_for_each_init, "clk-debug"); + +void clk_hw_debug_exit(void) +{ + struct clk_core *core; + + mutex_lock(&clk_debug_lock); + clk_hw_debug_create_one = NULL; + hlist_for_each_entry(core, &clk_debug_list, debug_node) + clk_core_debug_remove_one(core); + mutex_unlock(&clk_debug_lock); +} +EXPORT_SYMBOL_NS_GPL(clk_hw_debug_exit, "clk-debug"); #else static inline void clk_debug_register(struct clk_core *core) { } -static inline void clk_debug_unregister(struct clk_core *core) -{ -} +static inline void clk_debug_unregister(struct clk_core *core) { } #endif static void clk_core_reparent_orphans_nolock(void)