8250_dw: do not int overflow when rate can not be aplied

Message ID 20180111133832.13125-1-nunojpg@gmail.com
State New
Headers show
Series
  • 8250_dw: do not int overflow when rate can not be aplied
Related show

Commit Message

Nuno Goncalves Jan. 11, 2018, 1:38 p.m.
When target_rate is big enough and not permitted in hardware,
then i is looped to UART_DIV_MAX (0xFFFF), and i * max_rate will overflow
(32b signed).

A fix is to quit the loop early enough, as soon as rate < i * min_rate as it
means the rate is not permitted.

This avoids arbitraty rates to be applied. Still in my hardware the max
allowed rate (1500000) is aplied when a higher is requested. This seems a
artifact of clk_round_rate which is not understood by me and independent of
this fix. Might or might not be another bug.

Signed-off-by: Nuno Goncalves <nunojpg@gmail.com>

---
 drivers/tty/serial/8250/8250_dw.c | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

-- 
2.11.0

--
To unsubscribe from this list: send the line "unsubscribe linux-serial" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Comments

Nuno Goncalves Jan. 11, 2018, 5:28 p.m. | #1
I have to disagree :)

if (rate < i * min_rate) is true to i=a, then

(rate >= i * min_rate && rate <= i * max_rate) will always be false
for any i=b, where b>a.

If this condition is true, it means the old condition would be always
false for the remaining of the iteration.

My patch "only" avoids integer overflow and terminates the search as
soon as possible, since no need to search for bigger dividers if the
current one would already mean a rate below min_rate (that it, this is
the closer).

So in fact we also break when the closer divider have been found.

Thanks,
Nuno

On Thu, Jan 11, 2018 at 6:18 PM, Ed Blake <ed.blake@sondrel.com> wrote:
> Hi Nuno,

>

> Thanks for reporting this and the patch.

>

> On 11/01/18 13:38, Nuno Goncalves wrote:

>> When target_rate is big enough and not permitted in hardware,

>> then i is looped to UART_DIV_MAX (0xFFFF), and i * max_rate will overflow

>> (32b signed).

>>

>> A fix is to quit the loop early enough, as soon as rate < i * min_rate as it

>> means the rate is not permitted.

>

> 'rate < i * min_rate' does not mean the rate is not permitted.  clk_round_rate() gives the nearest achievable rate to the one requested, which may be lower than i * min_rate.  This is not an error and in this case we want to continue the loop searching for an acceptable rate.

>

>

>>

>> This avoids arbitraty rates to be applied. Still in my hardware the max

>> allowed rate (1500000) is aplied when a higher is requested. This seems a

>> artifact of clk_round_rate which is not understood by me and independent of

>> this fix. Might or might not be another bug.

>>

>> Signed-off-by: Nuno Goncalves <nunojpg@gmail.com>

>> ---

>>  drivers/tty/serial/8250/8250_dw.c | 8 +++++++-

>>  1 file changed, 7 insertions(+), 1 deletion(-)

>>

>> diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c

>> index 5bb0c42c88dd..a27ea916abbf 100644

>> --- a/drivers/tty/serial/8250/8250_dw.c

>> +++ b/drivers/tty/serial/8250/8250_dw.c

>> @@ -267,7 +267,13 @@ static void dw8250_set_termios(struct uart_port *p, struct ktermios *termios,

>>

>>       for (i = 1; i <= UART_DIV_MAX; i++) {

>>               rate = clk_round_rate(d->clk, i * target_rate);

>> -             if (rate >= i * min_rate && rate <= i * max_rate)

>> +

>> +             if (rate < i * min_rate) {

>> +                     i = UART_DIV_MAX + 1;

>> +                     break;

>> +             }

>> +

>> +             if (rate <= i * max_rate)

>>                       break;

>>       }

>>       if (i <= UART_DIV_MAX) {

>

> --

> Ed

>

--
To unsubscribe from this list: send the line "unsubscribe linux-serial" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Ed Blake Jan. 11, 2018, 5:48 p.m. | #2
On 11/01/18 17:28, Nuno Gonçalves wrote:
> I have to disagree :)

>

> if (rate < i * min_rate) is true to i=a, then

>

> (rate >= i * min_rate && rate <= i * max_rate) will always be false

> for any i=b, where b>a.


No, because 'rate' is assigned from clk_round_rate() each iteration.

The idea of this code is to iterate through integer multiples of baud *
16 until you find an achievable rate that is within the +/- 1.6% range. 
Until then, the rate returned from clk_round_rate() could be lower than
i * min_rate or higher than i * max_rate, in which case you keep going.

> If this condition is true, it means the old condition would be always

> false for the remaining of the iteration.

>

> My patch "only" avoids integer overflow and terminates the search as

> soon as possible, since no need to search for bigger dividers if the

> current one would already mean a rate below min_rate (that it, this is

> the closer).


It terminates the search as soon as the rate returned from
clk_round_rate() is lower than i * min_rate, which is too soon.

> So in fact we also break when the closer divider have been found.

>

> Thanks,

> Nuno

>

> On Thu, Jan 11, 2018 at 6:18 PM, Ed Blake <ed.blake@sondrel.com> wrote:

>> Hi Nuno,

>>

>> Thanks for reporting this and the patch.

>>

>> On 11/01/18 13:38, Nuno Goncalves wrote:

>>> When target_rate is big enough and not permitted in hardware,

>>> then i is looped to UART_DIV_MAX (0xFFFF), and i * max_rate will overflow

>>> (32b signed).

>>>

>>> A fix is to quit the loop early enough, as soon as rate < i * min_rate as it

>>> means the rate is not permitted.

>> 'rate < i * min_rate' does not mean the rate is not permitted.  clk_round_rate() gives the nearest achievable rate to the one requested, which may be lower than i * min_rate.  This is not an error and in this case we want to continue the loop searching for an acceptable rate.

>>

>>

>>> This avoids arbitraty rates to be applied. Still in my hardware the max

>>> allowed rate (1500000) is aplied when a higher is requested. This seems a

>>> artifact of clk_round_rate which is not understood by me and independent of

>>> this fix. Might or might not be another bug.

>>>

>>> Signed-off-by: Nuno Goncalves <nunojpg@gmail.com>

>>> ---

>>>  drivers/tty/serial/8250/8250_dw.c | 8 +++++++-

>>>  1 file changed, 7 insertions(+), 1 deletion(-)

>>>

>>> diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c

>>> index 5bb0c42c88dd..a27ea916abbf 100644

>>> --- a/drivers/tty/serial/8250/8250_dw.c

>>> +++ b/drivers/tty/serial/8250/8250_dw.c

>>> @@ -267,7 +267,13 @@ static void dw8250_set_termios(struct uart_port *p, struct ktermios *termios,

>>>

>>>       for (i = 1; i <= UART_DIV_MAX; i++) {

>>>               rate = clk_round_rate(d->clk, i * target_rate);

>>> -             if (rate >= i * min_rate && rate <= i * max_rate)

>>> +

>>> +             if (rate < i * min_rate) {

>>> +                     i = UART_DIV_MAX + 1;

>>> +                     break;

>>> +             }

>>> +

>>> +             if (rate <= i * max_rate)

>>>                       break;

>>>       }

>>>       if (i <= UART_DIV_MAX) {

>> --

>> Ed

>>


-- 
Ed

--
To unsubscribe from this list: send the line "unsubscribe linux-serial" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Nuno Goncalves Jan. 11, 2018, 5:55 p.m. | #3
So, for me clk_round_rate() always returns 24000000, and only the loop
variable i changes, so the search is monotonic, from the highest baud
to the lowest (increasing divider).

I am using a Allwiner H2+, with the serial port configuration from
sunxi-h3-h5.dtsi.

Are you sure that clk_round_rate can return differet values? Is that
because some boards might have several clock options beside the
adjustable divider?

I really need to understand what is the problem, to be able to suggest
a solution to the integer overflow that is being allowed to happen.

Thanks,
Nuno



On Thu, Jan 11, 2018 at 6:48 PM, Ed Blake <ed.blake@sondrel.com> wrote:
> On 11/01/18 17:28, Nuno Gonçalves wrote:

>> I have to disagree :)

>>

>> if (rate < i * min_rate) is true to i=a, then

>>

>> (rate >= i * min_rate && rate <= i * max_rate) will always be false

>> for any i=b, where b>a.

>

> No, because 'rate' is assigned from clk_round_rate() each iteration.

>

> The idea of this code is to iterate through integer multiples of baud *

> 16 until you find an achievable rate that is within the +/- 1.6% range.

> Until then, the rate returned from clk_round_rate() could be lower than

> i * min_rate or higher than i * max_rate, in which case you keep going.

>

>> If this condition is true, it means the old condition would be always

>> false for the remaining of the iteration.

>>

>> My patch "only" avoids integer overflow and terminates the search as

>> soon as possible, since no need to search for bigger dividers if the

>> current one would already mean a rate below min_rate (that it, this is

>> the closer).

>

> It terminates the search as soon as the rate returned from

> clk_round_rate() is lower than i * min_rate, which is too soon.

>

>> So in fact we also break when the closer divider have been found.

>>

>> Thanks,

>> Nuno

>>

>> On Thu, Jan 11, 2018 at 6:18 PM, Ed Blake <ed.blake@sondrel.com> wrote:

>>> Hi Nuno,

>>>

>>> Thanks for reporting this and the patch.

>>>

>>> On 11/01/18 13:38, Nuno Goncalves wrote:

>>>> When target_rate is big enough and not permitted in hardware,

>>>> then i is looped to UART_DIV_MAX (0xFFFF), and i * max_rate will overflow

>>>> (32b signed).

>>>>

>>>> A fix is to quit the loop early enough, as soon as rate < i * min_rate as it

>>>> means the rate is not permitted.

>>> 'rate < i * min_rate' does not mean the rate is not permitted.  clk_round_rate() gives the nearest achievable rate to the one requested, which may be lower than i * min_rate.  This is not an error and in this case we want to continue the loop searching for an acceptable rate.

>>>

>>>

>>>> This avoids arbitraty rates to be applied. Still in my hardware the max

>>>> allowed rate (1500000) is aplied when a higher is requested. This seems a

>>>> artifact of clk_round_rate which is not understood by me and independent of

>>>> this fix. Might or might not be another bug.

>>>>

>>>> Signed-off-by: Nuno Goncalves <nunojpg@gmail.com>

>>>> ---

>>>>  drivers/tty/serial/8250/8250_dw.c | 8 +++++++-

>>>>  1 file changed, 7 insertions(+), 1 deletion(-)

>>>>

>>>> diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c

>>>> index 5bb0c42c88dd..a27ea916abbf 100644

>>>> --- a/drivers/tty/serial/8250/8250_dw.c

>>>> +++ b/drivers/tty/serial/8250/8250_dw.c

>>>> @@ -267,7 +267,13 @@ static void dw8250_set_termios(struct uart_port *p, struct ktermios *termios,

>>>>

>>>>       for (i = 1; i <= UART_DIV_MAX; i++) {

>>>>               rate = clk_round_rate(d->clk, i * target_rate);

>>>> -             if (rate >= i * min_rate && rate <= i * max_rate)

>>>> +

>>>> +             if (rate < i * min_rate) {

>>>> +                     i = UART_DIV_MAX + 1;

>>>> +                     break;

>>>> +             }

>>>> +

>>>> +             if (rate <= i * max_rate)

>>>>                       break;

>>>>       }

>>>>       if (i <= UART_DIV_MAX) {

>>> --

>>> Ed

>>>

>

> --

> Ed

>

--
To unsubscribe from this list: send the line "unsubscribe linux-serial" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Ed Blake Jan. 11, 2018, 6:03 p.m. | #4
On 11/01/18 17:55, Nuno Gonçalves wrote:
> So, for me clk_round_rate() always returns 24000000, and only the loop

> variable i changes, so the search is monotonic, from the highest baud

> to the lowest (increasing divider).

>

> I am using a Allwiner H2+, with the serial port configuration from

> sunxi-h3-h5.dtsi.

>

> Are you sure that clk_round_rate can return differet values? Is that

> because some boards might have several clock options beside the

> adjustable divider?


Yes I'm sure.  Some platforms allow the clock rate to be varied, hence
the existence of clk_round_rate() and clk_set_rate().

> I really need to understand what is the problem, to be able to suggest

> a solution to the integer overflow that is being allowed to happen.


Some sort of overflow check on i * max_rate could work?

> Thanks,

> Nuno


--
To unsubscribe from this list: send the line "unsubscribe linux-serial" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Ed Blake Jan. 12, 2018, 1:33 p.m. | #5
On 11/01/18 18:03, Ed Blake wrote:
> On 11/01/18 17:55, Nuno Gonçalves wrote:

>> So, for me clk_round_rate() always returns 24000000, and only the loop

>> variable i changes, so the search is monotonic, from the highest baud

>> to the lowest (increasing divider).

>>

>> I am using a Allwiner H2+, with the serial port configuration from

>> sunxi-h3-h5.dtsi.

>>

>> Are you sure that clk_round_rate can return differet values? Is that

>> because some boards might have several clock options beside the

>> adjustable divider?

> Yes I'm sure.  Some platforms allow the clock rate to be varied, hence

> the existence of clk_round_rate() and clk_set_rate().

>

>> I really need to understand what is the problem, to be able to suggest

>> a solution to the integer overflow that is being allowed to happen.

> Some sort of overflow check on i * max_rate could work?


Actually I have another suggestion.  I'll submit a separate patch.

-- 
Ed

--
To unsubscribe from this list: send the line "unsubscribe linux-serial" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Patch

diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
index 5bb0c42c88dd..a27ea916abbf 100644
--- a/drivers/tty/serial/8250/8250_dw.c
+++ b/drivers/tty/serial/8250/8250_dw.c
@@ -267,7 +267,13 @@  static void dw8250_set_termios(struct uart_port *p, struct ktermios *termios,
 
 	for (i = 1; i <= UART_DIV_MAX; i++) {
 		rate = clk_round_rate(d->clk, i * target_rate);
-		if (rate >= i * min_rate && rate <= i * max_rate)
+
+		if (rate < i * min_rate) {
+			i = UART_DIV_MAX + 1;
+			break;
+		}
+
+		if (rate <= i * max_rate)
 			break;
 	}
 	if (i <= UART_DIV_MAX) {