summaryrefslogtreecommitdiffstats
path: root/drivers/clk/meson/vclk.c
diff options
context:
space:
mode:
authorKumar Kartikeya Dwivedi <memxor@gmail.com>2026-09-04 10:43:19 +0200
committerAlexei Starovoitov <ast@kernel.org>2026-09-04 07:58:36 -0700
commit6668ed271eaefaa63e686bdfbedaeb7b8e492722 (patch)
tree58e8288fead484f6755ad0815fc74c3742663761 /drivers/clk/meson/vclk.c
downloadlinux-stable-6668ed271eaefaa63e686bdfbedaeb7b8e492722.tar.gz
linux-stable-6668ed271eaefaa63e686bdfbedaeb7b8e492722.zip
selftests/bpf: Reject graph kptr use after RCU unlockgrafted
Add a sleepable verifier test that loads a graph-node local kptr in an explicit RCU read-side critical section, then passes its node to bpf_rbtree_remove() after the section ends. Before the verifier fix, the stale NON_OWN_REF flag makes the node look like a live borrowed reference and the program is accepted. After the fix, the pointer is demoted without NON_OWN_REF and the graph kfunc argument is rejected. Also exercise a graph kptr loaded while a spin lock provides implicit RCU protection. The pointer must be invalidated when the lock is released, which guards the required ordering between non-owning-reference invalidation and RCU demotion. Update the existing fault-protected load test state description. The post-unlock pointer no longer carries NON_OWN_REF, but remains readable because the load is rewritten to use BPF_PROBE_MEM. Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com> Link: https://lore.kernel.org/r/20260904084325.52250-7-memxor@gmail.com Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Diffstat (limited to 'drivers/clk/meson/vclk.c')
-rw-r--r--drivers/clk/meson/vclk.c144
1 files changed, 144 insertions, 0 deletions
diff --git a/drivers/clk/meson/vclk.c b/drivers/clk/meson/vclk.c
new file mode 100644
index 000000000..009bd1193
--- /dev/null
+++ b/drivers/clk/meson/vclk.c
@@ -0,0 +1,144 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2024 Neil Armstrong <neil.armstrong@linaro.org>
+ */
+
+#include <linux/module.h>
+#include "vclk.h"
+
+/* The VCLK gate has a supplementary reset bit to pulse after ungating */
+
+static inline struct meson_vclk_gate_data *
+clk_get_meson_vclk_gate_data(struct clk_regmap *clk)
+{
+ return (struct meson_vclk_gate_data *)clk->data;
+}
+
+static int meson_vclk_gate_enable(struct clk_hw *hw)
+{
+ struct clk_regmap *clk = to_clk_regmap(hw);
+ struct meson_vclk_gate_data *vclk = clk_get_meson_vclk_gate_data(clk);
+
+ meson_parm_write(clk->map, &vclk->enable, 1);
+
+ /* Do a reset pulse */
+ meson_parm_write(clk->map, &vclk->reset, 1);
+ meson_parm_write(clk->map, &vclk->reset, 0);
+
+ return 0;
+}
+
+static void meson_vclk_gate_disable(struct clk_hw *hw)
+{
+ struct clk_regmap *clk = to_clk_regmap(hw);
+ struct meson_vclk_gate_data *vclk = clk_get_meson_vclk_gate_data(clk);
+
+ meson_parm_write(clk->map, &vclk->enable, 0);
+}
+
+static int meson_vclk_gate_is_enabled(struct clk_hw *hw)
+{
+ struct clk_regmap *clk = to_clk_regmap(hw);
+ struct meson_vclk_gate_data *vclk = clk_get_meson_vclk_gate_data(clk);
+
+ return meson_parm_read(clk->map, &vclk->enable);
+}
+
+const struct clk_ops meson_vclk_gate_ops = {
+ .init = clk_regmap_init,
+ .enable = meson_vclk_gate_enable,
+ .disable = meson_vclk_gate_disable,
+ .is_enabled = meson_vclk_gate_is_enabled,
+};
+EXPORT_SYMBOL_NS_GPL(meson_vclk_gate_ops, "CLK_MESON");
+
+/* The VCLK Divider has supplementary reset & enable bits */
+
+static inline struct meson_vclk_div_data *
+clk_get_meson_vclk_div_data(struct clk_regmap *clk)
+{
+ return (struct meson_vclk_div_data *)clk->data;
+}
+
+static unsigned long meson_vclk_div_recalc_rate(struct clk_hw *hw,
+ unsigned long prate)
+{
+ struct clk_regmap *clk = to_clk_regmap(hw);
+ struct meson_vclk_div_data *vclk = clk_get_meson_vclk_div_data(clk);
+
+ return divider_recalc_rate(hw, prate, meson_parm_read(clk->map, &vclk->div),
+ vclk->table, vclk->flags, vclk->div.width);
+}
+
+static int meson_vclk_div_determine_rate(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ struct clk_regmap *clk = to_clk_regmap(hw);
+ struct meson_vclk_div_data *vclk = clk_get_meson_vclk_div_data(clk);
+
+ return divider_determine_rate(hw, req, vclk->table, vclk->div.width,
+ vclk->flags);
+}
+
+static int meson_vclk_div_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct clk_regmap *clk = to_clk_regmap(hw);
+ struct meson_vclk_div_data *vclk = clk_get_meson_vclk_div_data(clk);
+ int ret;
+
+ ret = divider_get_val(rate, parent_rate, vclk->table, vclk->div.width,
+ vclk->flags);
+ if (ret < 0)
+ return ret;
+
+ meson_parm_write(clk->map, &vclk->div, ret);
+
+ return 0;
+};
+
+static int meson_vclk_div_enable(struct clk_hw *hw)
+{
+ struct clk_regmap *clk = to_clk_regmap(hw);
+ struct meson_vclk_div_data *vclk = clk_get_meson_vclk_div_data(clk);
+
+ /* Unreset the divider when ungating */
+ meson_parm_write(clk->map, &vclk->reset, 0);
+ meson_parm_write(clk->map, &vclk->enable, 1);
+
+ return 0;
+}
+
+static void meson_vclk_div_disable(struct clk_hw *hw)
+{
+ struct clk_regmap *clk = to_clk_regmap(hw);
+ struct meson_vclk_div_data *vclk = clk_get_meson_vclk_div_data(clk);
+
+ /* Reset the divider when gating */
+ meson_parm_write(clk->map, &vclk->enable, 0);
+ meson_parm_write(clk->map, &vclk->reset, 1);
+}
+
+static int meson_vclk_div_is_enabled(struct clk_hw *hw)
+{
+ struct clk_regmap *clk = to_clk_regmap(hw);
+ struct meson_vclk_div_data *vclk = clk_get_meson_vclk_div_data(clk);
+
+ return meson_parm_read(clk->map, &vclk->enable);
+}
+
+const struct clk_ops meson_vclk_div_ops = {
+ .init = clk_regmap_init,
+ .recalc_rate = meson_vclk_div_recalc_rate,
+ .determine_rate = meson_vclk_div_determine_rate,
+ .set_rate = meson_vclk_div_set_rate,
+ .enable = meson_vclk_div_enable,
+ .disable = meson_vclk_div_disable,
+ .is_enabled = meson_vclk_div_is_enabled,
+};
+EXPORT_SYMBOL_NS_GPL(meson_vclk_div_ops, "CLK_MESON");
+
+MODULE_DESCRIPTION("Amlogic vclk clock driver");
+MODULE_AUTHOR("Neil Armstrong <neil.armstrong@linaro.org>");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("CLK_MESON");