linux/drivers/media/rc/ir-spi.c
Cosmin Tanislav c0b1da281d media: rc: ir-spi: avoid overflow in multiplication
Switch to u64 arithmetic and use DIV_ROUND_CLOSEST_ULL() to avoid
the overflow.

buffer[i] is unsigned int and is limited by the lirc core to
IR_MAX_DURATION, which is 500000.

idata->freq is u32, which has a max value of 0xFFFFFFFF.

In the case where buffer[i] is 500000, idata->freq overflows the u32
multiplication for any values >= 8590.

0xFFFFFFFF / 500000 ~= 8589

By casting buffer[i] to u64, idata->freq can be any u32 value without
overflowing the multiplication.

0xFFFFFFFFFFFFFFFF / 500000 ~= 36893488147419 (> 4294967295)

The result of the final operation will fit back into the unsigned int
limits without any issues.

500000 * 0xFFFFFFFF / 1000000 = 0x80000000 (< 0xFFFFFFFF)

Signed-off-by: Cosmin Tanislav <demonsingur@gmail.com>
Signed-off-by: Sean Young <sean@mess.org>
Signed-off-by: Hans Verkuil <hverkuil@xs4all.nl>
2025-06-23 12:11:04 +02:00

195 lines
4.3 KiB
C

// SPDX-License-Identifier: GPL-2.0
// SPI driven IR LED device driver
//
// Copyright (c) 2016 Samsung Electronics Co., Ltd.
// Copyright (c) Andi Shyti <andi@etezian.org>
#include <linux/bits.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/math.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/property.h>
#include <linux/regulator/consumer.h>
#include <linux/spi/spi.h>
#include <linux/string.h>
#include <linux/types.h>
#include <media/rc-core.h>
#define IR_SPI_DRIVER_NAME "ir-spi"
#define IR_SPI_DEFAULT_FREQUENCY 38000
#define IR_SPI_BITS_PER_PULSE 16
struct ir_spi_data {
u32 freq;
bool negated;
u16 pulse;
u16 space;
struct rc_dev *rc;
struct spi_device *spi;
struct regulator *regulator;
};
static int ir_spi_tx(struct rc_dev *dev, unsigned int *buffer, unsigned int count)
{
int i;
int ret;
unsigned int len = 0;
struct ir_spi_data *idata = dev->priv;
struct spi_transfer xfer;
u16 *tx_buf;
/* convert the pulse/space signal to raw binary signal */
for (i = 0; i < count; i++) {
buffer[i] = DIV_ROUND_CLOSEST_ULL((u64)buffer[i] * idata->freq,
1000000);
len += buffer[i];
}
tx_buf = kmalloc_array(len, sizeof(*tx_buf), GFP_KERNEL);
if (!tx_buf)
return -ENOMEM;
len = 0;
for (i = 0; i < count; i++) {
int j;
u16 val;
/*
* The first value in buffer is a pulse, so that 0, 2, 4, ...
* contain a pulse duration. On the contrary, 1, 3, 5, ...
* contain a space duration.
*/
val = (i % 2) ? idata->space : idata->pulse;
for (j = 0; j < buffer[i]; j++)
tx_buf[len++] = val;
}
memset(&xfer, 0, sizeof(xfer));
xfer.speed_hz = idata->freq * IR_SPI_BITS_PER_PULSE;
xfer.len = len * sizeof(*tx_buf);
xfer.tx_buf = tx_buf;
ret = regulator_enable(idata->regulator);
if (ret)
goto err_free_tx_buf;
ret = spi_sync_transfer(idata->spi, &xfer, 1);
if (ret)
dev_err(&idata->spi->dev, "unable to deliver the signal\n");
regulator_disable(idata->regulator);
err_free_tx_buf:
kfree(tx_buf);
return ret ? ret : count;
}
static int ir_spi_set_tx_carrier(struct rc_dev *dev, u32 carrier)
{
struct ir_spi_data *idata = dev->priv;
if (!carrier)
return -EINVAL;
if (carrier > idata->spi->max_speed_hz / IR_SPI_BITS_PER_PULSE)
return -EINVAL;
idata->freq = carrier;
return 0;
}
static int ir_spi_set_duty_cycle(struct rc_dev *dev, u32 duty_cycle)
{
struct ir_spi_data *idata = dev->priv;
int bits = (duty_cycle * 15) / 100;
idata->pulse = GENMASK(bits, 0);
if (idata->negated) {
idata->pulse = ~idata->pulse;
idata->space = 0xffff;
} else {
idata->space = 0;
}
return 0;
}
static int ir_spi_probe(struct spi_device *spi)
{
struct device *dev = &spi->dev;
int ret;
u8 dc;
struct ir_spi_data *idata;
idata = devm_kzalloc(dev, sizeof(*idata), GFP_KERNEL);
if (!idata)
return -ENOMEM;
idata->regulator = devm_regulator_get(dev, "irda_regulator");
if (IS_ERR(idata->regulator))
return PTR_ERR(idata->regulator);
idata->rc = devm_rc_allocate_device(&spi->dev, RC_DRIVER_IR_RAW_TX);
if (!idata->rc)
return -ENOMEM;
idata->rc->tx_ir = ir_spi_tx;
idata->rc->s_tx_carrier = ir_spi_set_tx_carrier;
idata->rc->s_tx_duty_cycle = ir_spi_set_duty_cycle;
idata->rc->device_name = "IR SPI";
idata->rc->driver_name = IR_SPI_DRIVER_NAME;
idata->rc->priv = idata;
idata->spi = spi;
idata->negated = device_property_read_bool(dev, "led-active-low");
ret = device_property_read_u8(dev, "duty-cycle", &dc);
if (ret)
dc = 50;
/*
* ir_spi_set_duty_cycle() cannot fail, it returns int
* to be compatible with the rc->s_tx_duty_cycle function.
*/
ir_spi_set_duty_cycle(idata->rc, dc);
idata->freq = IR_SPI_DEFAULT_FREQUENCY;
return devm_rc_register_device(dev, idata->rc);
}
static const struct of_device_id ir_spi_of_match[] = {
{ .compatible = "ir-spi-led" },
{}
};
MODULE_DEVICE_TABLE(of, ir_spi_of_match);
static const struct spi_device_id ir_spi_ids[] = {
{ "ir-spi-led" },
{}
};
MODULE_DEVICE_TABLE(spi, ir_spi_ids);
static struct spi_driver ir_spi_driver = {
.probe = ir_spi_probe,
.id_table = ir_spi_ids,
.driver = {
.name = IR_SPI_DRIVER_NAME,
.of_match_table = ir_spi_of_match,
},
};
module_spi_driver(ir_spi_driver);
MODULE_AUTHOR("Andi Shyti <andi@etezian.org>");
MODULE_DESCRIPTION("SPI IR LED");
MODULE_LICENSE("GPL v2");