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

From: Guo Zihao

Date: Fri Sep 18 2026 - 03:44:29 EST


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@xxxxxxxxxxxx>
Signed-off-by: Guo Zihao <guozh23@xxxxxxxxxxxx>
---
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