Skip to content

Commit c592c8a

Browse files
digetxthierryreding
authored andcommitted
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>
1 parent 56bb7c2 commit c592c8a

2 files changed

Lines changed: 58 additions & 25 deletions

File tree

drivers/clk/tegra/clk-periph-gate.c

Lines changed: 47 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -48,18 +48,9 @@ static int clk_periph_is_enabled(struct clk_hw *hw)
4848
return state;
4949
}
5050

51-
static int clk_periph_enable(struct clk_hw *hw)
51+
static void clk_periph_enable_locked(struct clk_hw *hw)
5252
{
5353
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-
}
6354

6455
write_enb_set(periph_clk_to_bit(gate), gate);
6556
udelay(2);
@@ -78,6 +69,32 @@ static int clk_periph_enable(struct clk_hw *hw)
7869
udelay(1);
7970
writel_relaxed(0, gate->clk_base + LVL2_CLK_GATE_OVRE);
8071
}
72+
}
73+
74+
static void clk_periph_disable_locked(struct clk_hw *hw)
75+
{
76+
struct tegra_clk_periph_gate *gate = to_clk_periph_gate(hw);
77+
78+
/*
79+
* If peripheral is in the APB bus then read the APB bus to
80+
* flush the write operation in apb bus. This will avoid the
81+
* peripheral access after disabling clock
82+
*/
83+
if (gate->flags & TEGRA_PERIPH_ON_APB)
84+
tegra_read_chipid();
85+
86+
write_enb_clr(periph_clk_to_bit(gate), gate);
87+
}
88+
89+
static int clk_periph_enable(struct clk_hw *hw)
90+
{
91+
struct tegra_clk_periph_gate *gate = to_clk_periph_gate(hw);
92+
unsigned long flags = 0;
93+
94+
spin_lock_irqsave(&periph_ref_lock, flags);
95+
96+
if (!gate->enable_refcnt[gate->clk_num]++)
97+
clk_periph_enable_locked(hw);
8198

8299
spin_unlock_irqrestore(&periph_ref_lock, flags);
83100

@@ -91,21 +108,28 @@ static void clk_periph_disable(struct clk_hw *hw)
91108

92109
spin_lock_irqsave(&periph_ref_lock, flags);
93110

94-
gate->enable_refcnt[gate->clk_num]--;
95-
if (gate->enable_refcnt[gate->clk_num] > 0) {
96-
spin_unlock_irqrestore(&periph_ref_lock, flags);
97-
return;
98-
}
111+
WARN_ON(!gate->enable_refcnt[gate->clk_num]);
112+
113+
if (--gate->enable_refcnt[gate->clk_num] == 0)
114+
clk_periph_disable_locked(hw);
115+
116+
spin_unlock_irqrestore(&periph_ref_lock, flags);
117+
}
118+
119+
static void clk_periph_disable_unused(struct clk_hw *hw)
120+
{
121+
struct tegra_clk_periph_gate *gate = to_clk_periph_gate(hw);
122+
unsigned long flags = 0;
123+
124+
spin_lock_irqsave(&periph_ref_lock, flags);
99125

100126
/*
101-
* If peripheral is in the APB bus then read the APB bus to
102-
* flush the write operation in apb bus. This will avoid the
103-
* peripheral access after disabling clock
127+
* Some clocks are duplicated and some of them are marked as critical,
128+
* like fuse and fuse_burn for example, thus the enable_refcnt will
129+
* be non-zero here if the "unused" duplicate is disabled by CCF.
104130
*/
105-
if (gate->flags & TEGRA_PERIPH_ON_APB)
106-
tegra_read_chipid();
107-
108-
write_enb_clr(periph_clk_to_bit(gate), gate);
131+
if (!gate->enable_refcnt[gate->clk_num])
132+
clk_periph_disable_locked(hw);
109133

110134
spin_unlock_irqrestore(&periph_ref_lock, flags);
111135
}
@@ -114,6 +138,7 @@ const struct clk_ops tegra_clk_periph_gate_ops = {
114138
.is_enabled = clk_periph_is_enabled,
115139
.enable = clk_periph_enable,
116140
.disable = clk_periph_disable,
141+
.disable_unused = clk_periph_disable_unused,
117142
};
118143

119144
struct clk *tegra_clk_register_periph_gate(const char *name,
@@ -148,9 +173,6 @@ struct clk *tegra_clk_register_periph_gate(const char *name,
148173
gate->enable_refcnt = enable_refcnt;
149174
gate->regs = pregs;
150175

151-
if (read_enb(gate) & periph_clk_to_bit(gate))
152-
enable_refcnt[clk_num]++;
153-
154176
/* Data in .init is copied by clk_register(), so stack variable OK */
155177
gate->hw.init = &init;
156178

drivers/clk/tegra/clk-periph.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,15 @@ static void clk_periph_disable(struct clk_hw *hw)
100100
gate_ops->disable(gate_hw);
101101
}
102102

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+
103112
static void clk_periph_restore_context(struct clk_hw *hw)
104113
{
105114
struct tegra_clk_periph *periph = to_clk_periph(hw);
@@ -126,6 +135,7 @@ const struct clk_ops tegra_clk_periph_ops = {
126135
.is_enabled = clk_periph_is_enabled,
127136
.enable = clk_periph_enable,
128137
.disable = clk_periph_disable,
138+
.disable_unused = clk_periph_disable_unused,
129139
.restore_context = clk_periph_restore_context,
130140
};
131141

@@ -135,6 +145,7 @@ static const struct clk_ops tegra_clk_periph_nodiv_ops = {
135145
.is_enabled = clk_periph_is_enabled,
136146
.enable = clk_periph_enable,
137147
.disable = clk_periph_disable,
148+
.disable_unused = clk_periph_disable_unused,
138149
.restore_context = clk_periph_restore_context,
139150
};
140151

0 commit comments

Comments
 (0)