[PATCH v2 1/5] spi: sifive: clamp the clock divider to the register range

From: shao.mingyin

Date: Tue Nov 10 2026 - 10:59:10 EST


From: Shao Mingyin <shao.mingyin@xxxxxxxxxx>

sifive_spi_prep_transfer() divides by t->speed_hz, which can remain
zero when neither the device nor the controller provides a maximum
speed. For a nonzero but very low speed, masking the divider to 12 bits
can also wrap it to a small value and program a much faster clock.

Use the maximum divider when no speed is supplied, and saturate an
out-of-range divider to the same value. Keep the calculation in an
unsigned long until it has been clamped, so it is not truncated to u32
before the range check.

Fixes: 484a9a68d669 ("spi: sifive: Add driver for the SiFive SPI controller")
Suggested-by: Mark Brown <broonie@xxxxxxxxxx>
Signed-off-by: Shao Mingyin <shao.mingyin@xxxxxxxxxx>
---
v2:
- use the maximum SCKDIV directly for a zero speed, and saturate an
excessive divider instead of masking away high bits (Mark Brown)
- keep the divider wide until after the range check
v1: https://lore.kernel.org/all/20261106234756882V9wUQNCEZAYUGTAbexMSL@xxxxxxxxxx/

Validation on x86-64 with GCC 13 and UBSAN: the verbatim preparation
function ran with clock/register stubs over all representable SCKDIV
values and the first overflow. A zero speed bypassed division and
programmed 0xfff. A 1 Hz request needing divider 4096 programmed 0xfff
instead of wrapping to zero; a divider exceeding u32 also saturated.
In-range requests retained their original register values.

drivers/spi/spi-sifive.c | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/drivers/spi/spi-sifive.c b/drivers/spi/spi-sifive.c
index cee4d92e46f437319fa40f6377e5511e692d12c7..ca7b04520a682c3fffb070169ab0e3c485e323de 100644
--- a/drivers/spi/spi-sifive.c
+++ b/drivers/spi/spi-sifive.c
@@ -167,12 +167,15 @@ static int
sifive_spi_prep_transfer(struct sifive_spi *spi, struct spi_device *device,
struct spi_transfer *t)
{
+ unsigned long div = SIFIVE_SPI_SCKDIV_DIV_MASK;
u32 cr;
unsigned int mode;

- /* Calculate and program the clock rate */
- cr = DIV_ROUND_UP(clk_get_rate(spi->clk) >> 1, t->speed_hz) - 1;
- cr &= SIFIVE_SPI_SCKDIV_DIV_MASK;
+ /* Use the slowest clock if no speed is specified. */
+ if (t->speed_hz)
+ div = DIV_ROUND_UP(clk_get_rate(spi->clk) >> 1,
+ t->speed_hz) - 1;
+ cr = min_t(unsigned long, div, SIFIVE_SPI_SCKDIV_DIV_MASK);
sifive_spi_write(spi, SIFIVE_SPI_REG_SCKDIV, cr);

mode = max_t(unsigned int, t->rx_nbits, t->tx_nbits);