Message ID | 20230630183835.464216-1-sebastian.reichel@collabora.com (mailing list archive) |
---|---|
State | Changes Requested, archived |
Headers | show |
Series | [v3,1/1] clk: divider: Fix divisor masking on 64 bit platforms | expand |
Quoting Sebastian Reichel (2023-06-30 11:38:35) > The clock framework handles clock rates as "unsigned long", so u32 on > 32-bit architectures and u64 on 64-bit architectures. > > The current code casts the dividend to u64 on 32-bit to avoid a > potential overflow. For example DIV_ROUND_UP(3000000000, 1500000000) > = (3.0G + 1.5G - 1) / 1.5G = = OVERFLOW / 1.5G, which has been > introduced in commit 9556f9dad8f5 ("clk: divider: handle integer overflow > when dividing large clock rates"). > > On 64 bit platforms this masks the divisor, so that only the lower > 32 bit are used. Thus requesting a frequency >= 4.3GHz results > in incorrect values. For example requesting 4300000000 (4.3 GHz) will > effectively request ca. 5 MHz. Requesting clk_round_rate(clk, ULONG_MAX) > is a bit of a special case, since that still returns correct values as > long as the parent clock is below 8.5 GHz. > > Fix this by introducing a new helper, which avoids the overflow > by using a modulo operation instead of math tricks. This avoids > any requirements on the arguments (except that divisor should not > be 0 obviously). > > Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com> > --- Sorry this one fell off my review list :( > Changes since PATCHv2: > * https://lore.kernel.org/all/20230526171057.66876-1-sebastian.reichel@collabora.com/ > * Drop first patch (applied) > * Update second patch to use newly introduced DIV_ROUND_UP_NO_OVERFLOW > > Changes since PATCHv1: > * https://lore.kernel.org/linux-clk/20230519190522.194729-1-sebastian.reichel@collabora.com/ > * Add Christopher Obbard's Reviewed-by to the first patch > * Update the second patch to use DIV_ROUND_UP instead of DIV64_U64_ROUND_UP > --- > drivers/clk/clk-divider.c | 6 +++--- > include/linux/math.h | 11 +++++++++++ > 2 files changed, 14 insertions(+), 3 deletions(-) > > diff --git a/drivers/clk/clk-divider.c b/drivers/clk/clk-divider.c > index a2c2b5203b0a..94b4fb66a60f 100644 > --- a/drivers/clk/clk-divider.c > +++ b/drivers/clk/clk-divider.c > @@ -220,7 +220,7 @@ static int _div_round_up(const struct clk_div_table *table, > unsigned long parent_rate, unsigned long rate, > unsigned long flags) > { > - int div = DIV_ROUND_UP_ULL((u64)parent_rate, rate); > + int div = DIV_ROUND_UP_NO_OVERFLOW(parent_rate, rate); > > if (flags & CLK_DIVIDER_POWER_OF_TWO) > div = __roundup_pow_of_two(div); > @@ -237,7 +237,7 @@ static int _div_round_closest(const struct clk_div_table *table, > int up, down; > unsigned long up_rate, down_rate; > > - up = DIV_ROUND_UP_ULL((u64)parent_rate, rate); > + up = DIV_ROUND_UP_NO_OVERFLOW(parent_rate, rate); > down = parent_rate / rate; > > if (flags & CLK_DIVIDER_POWER_OF_TWO) { > @@ -473,7 +473,7 @@ int divider_get_val(unsigned long rate, unsigned long parent_rate, > { > unsigned int div, value; > > - div = DIV_ROUND_UP_ULL((u64)parent_rate, rate); > + div = DIV_ROUND_UP_NO_OVERFLOW(parent_rate, rate); > > if (!_is_valid_div(table, div, flags)) > return -EINVAL; > diff --git a/include/linux/math.h b/include/linux/math.h > index 2d388650c556..cf14d436fc2e 100644 > --- a/include/linux/math.h > +++ b/include/linux/math.h > @@ -36,6 +36,17 @@ > > #define DIV_ROUND_UP __KERNEL_DIV_ROUND_UP > > +/** > + * DIV_ROUND_UP_NO_OVERFLOW - divide two numbers and always round up > + * @n: numerator / dividend > + * @d: denominator / divisor > + * > + * This functions does the same as DIV_ROUND_UP, but internally uses a > + * division and a modulo operation instead of math tricks. This way it > + * avoids overflowing when handling big numbers. > + */ > +#define DIV_ROUND_UP_NO_OVERFLOW(n, d) (((n) / (d)) + !!((n) % (d))) Can you get someone to review/ack this macro? Maybe Andy? > + > #define DIV_ROUND_DOWN_ULL(ll, d) \ > ({ unsigned long long _tmp = (ll); do_div(_tmp, d); _tmp; }) >
On Mon, Oct 23, 2023 at 08:34:12PM -0700, Stephen Boyd wrote: > Quoting Sebastian Reichel (2023-06-30 11:38:35) > > --- a/include/linux/math.h > > +++ b/include/linux/math.h > > @@ -36,6 +36,17 @@ > > > > #define DIV_ROUND_UP __KERNEL_DIV_ROUND_UP > > > > +/** > > + * DIV_ROUND_UP_NO_OVERFLOW - divide two numbers and always round up > > + * @n: numerator / dividend > > + * @d: denominator / divisor > > + * > > + * This functions does the same as DIV_ROUND_UP, but internally uses a > > + * division and a modulo operation instead of math tricks. This way it > > + * avoids overflowing when handling big numbers. > > + */ > > +#define DIV_ROUND_UP_NO_OVERFLOW(n, d) (((n) / (d)) + !!((n) % (d))) > > Can you get someone to review/ack this macro? Maybe Andy? > > > + > > #define DIV_ROUND_DOWN_ULL(ll, d) \ > > ({ unsigned long long _tmp = (ll); do_div(_tmp, d); _tmp; }) First of all, it should be a separate patch and second, same issue is being discussed here (as it needs to be fixed in UAPI header directly): https://lore.kernel.org/all/your-ad-here.call-01697881440-ext-2458@work.hours/
diff --git a/drivers/clk/clk-divider.c b/drivers/clk/clk-divider.c index a2c2b5203b0a..94b4fb66a60f 100644 --- a/drivers/clk/clk-divider.c +++ b/drivers/clk/clk-divider.c @@ -220,7 +220,7 @@ static int _div_round_up(const struct clk_div_table *table, unsigned long parent_rate, unsigned long rate, unsigned long flags) { - int div = DIV_ROUND_UP_ULL((u64)parent_rate, rate); + int div = DIV_ROUND_UP_NO_OVERFLOW(parent_rate, rate); if (flags & CLK_DIVIDER_POWER_OF_TWO) div = __roundup_pow_of_two(div); @@ -237,7 +237,7 @@ static int _div_round_closest(const struct clk_div_table *table, int up, down; unsigned long up_rate, down_rate; - up = DIV_ROUND_UP_ULL((u64)parent_rate, rate); + up = DIV_ROUND_UP_NO_OVERFLOW(parent_rate, rate); down = parent_rate / rate; if (flags & CLK_DIVIDER_POWER_OF_TWO) { @@ -473,7 +473,7 @@ int divider_get_val(unsigned long rate, unsigned long parent_rate, { unsigned int div, value; - div = DIV_ROUND_UP_ULL((u64)parent_rate, rate); + div = DIV_ROUND_UP_NO_OVERFLOW(parent_rate, rate); if (!_is_valid_div(table, div, flags)) return -EINVAL; diff --git a/include/linux/math.h b/include/linux/math.h index 2d388650c556..cf14d436fc2e 100644 --- a/include/linux/math.h +++ b/include/linux/math.h @@ -36,6 +36,17 @@ #define DIV_ROUND_UP __KERNEL_DIV_ROUND_UP +/** + * DIV_ROUND_UP_NO_OVERFLOW - divide two numbers and always round up + * @n: numerator / dividend + * @d: denominator / divisor + * + * This functions does the same as DIV_ROUND_UP, but internally uses a + * division and a modulo operation instead of math tricks. This way it + * avoids overflowing when handling big numbers. + */ +#define DIV_ROUND_UP_NO_OVERFLOW(n, d) (((n) / (d)) + !!((n) % (d))) + #define DIV_ROUND_DOWN_ULL(ll, d) \ ({ unsigned long long _tmp = (ll); do_div(_tmp, d); _tmp; })
The clock framework handles clock rates as "unsigned long", so u32 on 32-bit architectures and u64 on 64-bit architectures. The current code casts the dividend to u64 on 32-bit to avoid a potential overflow. For example DIV_ROUND_UP(3000000000, 1500000000) = (3.0G + 1.5G - 1) / 1.5G = = OVERFLOW / 1.5G, which has been introduced in commit 9556f9dad8f5 ("clk: divider: handle integer overflow when dividing large clock rates"). On 64 bit platforms this masks the divisor, so that only the lower 32 bit are used. Thus requesting a frequency >= 4.3GHz results in incorrect values. For example requesting 4300000000 (4.3 GHz) will effectively request ca. 5 MHz. Requesting clk_round_rate(clk, ULONG_MAX) is a bit of a special case, since that still returns correct values as long as the parent clock is below 8.5 GHz. Fix this by introducing a new helper, which avoids the overflow by using a modulo operation instead of math tricks. This avoids any requirements on the arguments (except that divisor should not be 0 obviously). Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com> --- Changes since PATCHv2: * https://lore.kernel.org/all/20230526171057.66876-1-sebastian.reichel@collabora.com/ * Drop first patch (applied) * Update second patch to use newly introduced DIV_ROUND_UP_NO_OVERFLOW Changes since PATCHv1: * https://lore.kernel.org/linux-clk/20230519190522.194729-1-sebastian.reichel@collabora.com/ * Add Christopher Obbard's Reviewed-by to the first patch * Update the second patch to use DIV_ROUND_UP instead of DIV64_U64_ROUND_UP --- drivers/clk/clk-divider.c | 6 +++--- include/linux/math.h | 11 +++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-)