[PATCH] media: cxd2880: avoid a division by zero in the BER period setup

Guo Zihao posted 1 patch 6 days, 16 hours ago
There is a newer version of this series
.../media/dvb-frontends/cxd2880/cxd2880_top.c | 21 +++++++++++++++++++
1 file changed, 21 insertions(+)
[PATCH] media: cxd2880: avoid a division by zero in the BER period setup
Posted by Guo Zihao 6 days, 16 hours ago
cxd2880_set_ber_per_period_t() and cxd2880_set_ber_per_period_t2()
compute the BER measurement intervals by dividing by the pre/post BER
rate and by the uncorrected block rate, all of which are derived from
values read back from the demodulator registers:

        pre_ber_rate =
                (plp.num_blocks_max * 1000000 + (denominator / 2)) /
                denominator;

        post_ber_rate = pre_ber_rate;

        mes_exp = intlog2(pre_ber_rate) >> 24;
        priv->pre_ber_interval =
                ((1U << mes_exp) * 1000 + (pre_ber_rate / 2)) /
                pre_ber_rate;

With num_blocks_max zero the numerator is denominator / 2, which is
smaller than denominator, so integer division gives zero and the
division below it traps. That is a normal state for the registers, not a
corrupt one: the field is zero before the demodulator has locked, and
the DVB-T path (cxd2880_set_ber_per_period_t()) has the same shape with
ucblock_rate as the divisor.

Clamp the rates to 1 before the divisions, in both functions, so that a
zero derived from the registers produces a large interval instead of a
trap. The stored intervals keep their meaning: a rate of 1 is as far from
a real measurement as a zero rate, and the entry points can be reached
from FE_READ_BER and FE_READ_UNCORRECTED_BLOCKS before any lock.

No Fixes tag. Both functions came in with the driver, 9593810cd42a
("media: cxd2880: Add top level of the driver").

Reviewed-by: Liu Chao <liuc63@xiaopeng.com>
Signed-off-by: Guo Zihao <guozh23@xiaopeng.com>
---
The values come from cxd2880_tnrdmd_dvbt2_mon_active_plp() and its
DVB-T counterpart, which read the demodulator registers, so the rates
depend on the state of the tuner rather than on anything userspace
supplies. Reachable through FE_READ_BER / FE_READ_UNCORRECTED_BLOCKS.

intlog2() is called on these values as well, and it warns and returns 0
for a zero argument, so the clamp keeps that call meaningful too.

 .../media/dvb-frontends/cxd2880/cxd2880_top.c | 21 +++++++++++++++++++
 1 file changed, 21 insertions(+)

diff --git a/drivers/media/dvb-frontends/cxd2880/cxd2880_top.c b/drivers/media/dvb-frontends/cxd2880/cxd2880_top.c
index 0d058b59a..1a1c58613 100644
--- a/drivers/media/dvb-frontends/cxd2880/cxd2880_top.c
+++ b/drivers/media/dvb-frontends/cxd2880/cxd2880_top.c
@@ -764,6 +764,12 @@ static int cxd2880_set_ber_per_period_t(struct dvb_frontend *fe)
 		}
 	}
 
+	/*
+	 * A zero rate can be derived from bogus or not yet locked
+	 * demodulator registers. Avoid dividing by it below.
+	 */
+	if (!pre_ber_rate)
+		pre_ber_rate = 1;
 	mes_exp = pre_ber_rate < 8192 ? 8 : intlog2(pre_ber_rate) >> 24;
 	priv->pre_ber_interval =
 		((1U << mes_exp) * 1000 + (pre_ber_rate / 2)) /
@@ -772,6 +778,8 @@ static int cxd2880_set_ber_per_period_t(struct dvb_frontend *fe)
 			       CXD2880_TNRDMD_CFG_DVBT_VBER_PERIOD,
 			       mes_exp == 8 ? 0 : mes_exp - 12);
 
+	if (!post_ber_rate)
+		post_ber_rate = 1;
 	mes_exp = intlog2(post_ber_rate) >> 24;
 	priv->post_ber_interval =
 		((1U << mes_exp) * 1000 + (post_ber_rate / 2)) /
@@ -780,6 +788,8 @@ static int cxd2880_set_ber_per_period_t(struct dvb_frontend *fe)
 			       CXD2880_TNRDMD_CFG_DVBT_BERN_PERIOD,
 			       mes_exp);
 
+	if (!ucblock_rate)
+		ucblock_rate = 1;
 	mes_exp = intlog2(ucblock_rate) >> 24;
 	priv->ucblock_interval =
 		((1U << mes_exp) * 1000 + (ucblock_rate / 2)) /
@@ -886,6 +896,13 @@ static int cxd2880_set_ber_per_period_t2(struct dvb_frontend *fe)
 
 	post_ber_rate = pre_ber_rate;
 
+	/*
+	 * A zero rate can be derived from bogus or not yet locked
+	 * demodulator registers (e.g. plp.num_blocks_max == 0).
+	 * Avoid dividing by it below.
+	 */
+	if (!pre_ber_rate)
+		pre_ber_rate = 1;
 	mes_exp = intlog2(pre_ber_rate) >> 24;
 	priv->pre_ber_interval =
 		((1U << mes_exp) * 1000 + (pre_ber_rate / 2)) /
@@ -894,6 +911,8 @@ static int cxd2880_set_ber_per_period_t2(struct dvb_frontend *fe)
 			       CXD2880_TNRDMD_CFG_DVBT2_LBER_MES,
 			       mes_exp);
 
+	if (!post_ber_rate)
+		post_ber_rate = 1;
 	mes_exp = intlog2(post_ber_rate) >> 24;
 	priv->post_ber_interval =
 		((1U << mes_exp) * 1000 + (post_ber_rate / 2)) /
@@ -929,6 +948,8 @@ static int cxd2880_set_ber_per_period_t2(struct dvb_frontend *fe)
 		goto error_ucblock_setting;
 	}
 
+	if (!ucblock_rate)
+		ucblock_rate = 1;
 	mes_exp = intlog2(ucblock_rate) >> 24;
 	priv->ucblock_interval =
 		((1U << mes_exp) * 1000 + (ucblock_rate / 2)) /
-- 
2.50.1