Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

clk: tegra: Fix refcounting of gate clocks

The refcounting of the gate clocks has a bug causing the enable_refcnt
to underflow when unused clocks are disabled. This happens because clk
provider erroneously bumps the refcount if clock is enabled at a boot
time, which it shouldn't be doing, and it does this only for the gate
clocks, while peripheral clocks are using the same gate ops and the
peripheral clocks are missing the initial bump. Hence the refcount of
the peripheral clocks is 0 when unused clocks are disabled and then the
counter is decremented further by the gate ops, causing the integer
underflow.

Fix this problem by removing the erroneous bump and by implementing the
disable_unused() callback, which disables the unused gates properly.

The visible effect of the bug is such that the unused clocks are never
gated if a loaded kernel module grabs the unused clocks and starts to use
them. In practice this shouldn't cause any real problems for the drivers
and boards supported by the kernel today.

Acked-by: Thierry Reding <treding@nvidia.com>
Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
Signed-off-by: Thierry Reding <treding@nvidia.com>

authored by

Dmitry Osipenko and committed by
Thierry Reding
c592c8a2 56bb7c28

+58 -25
+47 -25
drivers/clk/tegra/clk-periph-gate.c
··· 48 48 return state; 49 49 } 50 50 51 - static int clk_periph_enable(struct clk_hw *hw) 51 + static void clk_periph_enable_locked(struct clk_hw *hw) 52 52 { 53 53 struct tegra_clk_periph_gate *gate = to_clk_periph_gate(hw); 54 - unsigned long flags = 0; 55 - 56 - spin_lock_irqsave(&periph_ref_lock, flags); 57 - 58 - gate->enable_refcnt[gate->clk_num]++; 59 - if (gate->enable_refcnt[gate->clk_num] > 1) { 60 - spin_unlock_irqrestore(&periph_ref_lock, flags); 61 - return 0; 62 - } 63 54 64 55 write_enb_set(periph_clk_to_bit(gate), gate); 65 56 udelay(2); ··· 69 78 udelay(1); 70 79 writel_relaxed(0, gate->clk_base + LVL2_CLK_GATE_OVRE); 71 80 } 81 + } 82 + 83 + static void clk_periph_disable_locked(struct clk_hw *hw) 84 + { 85 + struct tegra_clk_periph_gate *gate = to_clk_periph_gate(hw); 86 + 87 + /* 88 + * If peripheral is in the APB bus then read the APB bus to 89 + * flush the write operation in apb bus. This will avoid the 90 + * peripheral access after disabling clock 91 + */ 92 + if (gate->flags & TEGRA_PERIPH_ON_APB) 93 + tegra_read_chipid(); 94 + 95 + write_enb_clr(periph_clk_to_bit(gate), gate); 96 + } 97 + 98 + static int clk_periph_enable(struct clk_hw *hw) 99 + { 100 + struct tegra_clk_periph_gate *gate = to_clk_periph_gate(hw); 101 + unsigned long flags = 0; 102 + 103 + spin_lock_irqsave(&periph_ref_lock, flags); 104 + 105 + if (!gate->enable_refcnt[gate->clk_num]++) 106 + clk_periph_enable_locked(hw); 72 107 73 108 spin_unlock_irqrestore(&periph_ref_lock, flags); 74 109 ··· 108 91 109 92 spin_lock_irqsave(&periph_ref_lock, flags); 110 93 111 - gate->enable_refcnt[gate->clk_num]--; 112 - if (gate->enable_refcnt[gate->clk_num] > 0) { 113 - spin_unlock_irqrestore(&periph_ref_lock, flags); 114 - return; 115 - } 94 + WARN_ON(!gate->enable_refcnt[gate->clk_num]); 95 + 96 + if (--gate->enable_refcnt[gate->clk_num] == 0) 97 + clk_periph_disable_locked(hw); 98 + 99 + spin_unlock_irqrestore(&periph_ref_lock, flags); 100 + } 101 + 102 + static void clk_periph_disable_unused(struct clk_hw *hw) 103 + { 104 + struct tegra_clk_periph_gate *gate = to_clk_periph_gate(hw); 105 + unsigned long flags = 0; 106 + 107 + spin_lock_irqsave(&periph_ref_lock, flags); 116 108 117 109 /* 118 - * If peripheral is in the APB bus then read the APB bus to 119 - * flush the write operation in apb bus. This will avoid the 120 - * peripheral access after disabling clock 110 + * Some clocks are duplicated and some of them are marked as critical, 111 + * like fuse and fuse_burn for example, thus the enable_refcnt will 112 + * be non-zero here if the "unused" duplicate is disabled by CCF. 121 113 */ 122 - if (gate->flags & TEGRA_PERIPH_ON_APB) 123 - tegra_read_chipid(); 124 - 125 - write_enb_clr(periph_clk_to_bit(gate), gate); 114 + if (!gate->enable_refcnt[gate->clk_num]) 115 + clk_periph_disable_locked(hw); 126 116 127 117 spin_unlock_irqrestore(&periph_ref_lock, flags); 128 118 } ··· 138 114 .is_enabled = clk_periph_is_enabled, 139 115 .enable = clk_periph_enable, 140 116 .disable = clk_periph_disable, 117 + .disable_unused = clk_periph_disable_unused, 141 118 }; 142 119 143 120 struct clk *tegra_clk_register_periph_gate(const char *name, ··· 172 147 gate->flags = gate_flags; 173 148 gate->enable_refcnt = enable_refcnt; 174 149 gate->regs = pregs; 175 - 176 - if (read_enb(gate) & periph_clk_to_bit(gate)) 177 - enable_refcnt[clk_num]++; 178 150 179 151 /* Data in .init is copied by clk_register(), so stack variable OK */ 180 152 gate->hw.init = &init;
+11
drivers/clk/tegra/clk-periph.c
··· 100 100 gate_ops->disable(gate_hw); 101 101 } 102 102 103 + static void clk_periph_disable_unused(struct clk_hw *hw) 104 + { 105 + struct tegra_clk_periph *periph = to_clk_periph(hw); 106 + const struct clk_ops *gate_ops = periph->gate_ops; 107 + struct clk_hw *gate_hw = &periph->gate.hw; 108 + 109 + gate_ops->disable_unused(gate_hw); 110 + } 111 + 103 112 static void clk_periph_restore_context(struct clk_hw *hw) 104 113 { 105 114 struct tegra_clk_periph *periph = to_clk_periph(hw); ··· 135 126 .is_enabled = clk_periph_is_enabled, 136 127 .enable = clk_periph_enable, 137 128 .disable = clk_periph_disable, 129 + .disable_unused = clk_periph_disable_unused, 138 130 .restore_context = clk_periph_restore_context, 139 131 }; 140 132 ··· 145 135 .is_enabled = clk_periph_is_enabled, 146 136 .enable = clk_periph_enable, 147 137 .disable = clk_periph_disable, 138 + .disable_unused = clk_periph_disable_unused, 148 139 .restore_context = clk_periph_restore_context, 149 140 }; 150 141