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

From: Guo Zihao

Date: Tue Sep 22 2026 - 04:01:56 EST


cxd2880_set_ber_per_period_t2() computes the BER measurement intervals
from the number of blocks the demodulator reports, and uses the result
as a divisor:

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 register, not a
corrupt one: num_blocks_max comes from
cxd2880_tnrdmd_dvbt2_mon_active_plp() reading the demodulator, and the
field is zero before the demodulator has locked.

intlog2() is called on the same value and warns for a zero argument
before returning, so the warning is reached first and the division
follows.

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.

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>
Assisted-by: LLM
Signed-off-by: Guo Zihao <guozh23@xxxxxxxxxxxx>
---
v2: add the CXD2880 maintainer to the recipients.

The previous version also claimed the DVB-T function
(cxd2880_set_ber_per_period_t()) reaches zero the same way. Its rate
is computed from cr_table[] and denominator_tbl[], and the smallest
value those produce is well above zero, so that claim is dropped.
The clamp is still applied there, because the input comes from the
demodulator in the same way, but the DVB-T2 path is the one with a
case that reaches zero.

Add the Assisted-by tag.

The values come from 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.
---
.../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