From b0e8adb2ccb43009796897ced09f91636685c9d3 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 22 Jul 2026 07:14:36 -0700 Subject: [PATCH 01/19] hwmon: (nct6775-core) Fix number of temperature registers for NCT6116 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unlike NCT6106, NCT6116 only has three temperature registers, and with it only three temperature source and temperature source configuration registers. The register addresses match those of NCT6106 and can be re-used. The code used a separate array to list the temperature source registers for NCT6116, but used the size of the NCT6106 register array to set the number of registers. The NCT6106 register array provides six addresses, while the temperature source register array for NCT6116 only provides three addresses. This causes a KASAN report. BUG: KASAN: global-out-of-bounds in nct6775_probe+0x936/0x46f0 [nct6775] Read of size 2 at addr ffffffffc19561a6 by task modprobe/954 ... Call Trace: dump_stack+0x7d/0xa7 print_address_description.constprop.0+0x1c/0x220 ? __kasan_kmalloc.constprop.0+0xc9/0xd0 ? __kmalloc_node_track_caller+0x194/0x5b0 ? nct6775_probe+0x936/0x46f0 [nct6775] ? nct6775_probe+0x936/0x46f0 [nct6775] ... Fix the problem by hard-coding the number of temperature and temperature configuration registers to three for NCT6116. Drop the unnecessary NCT6116_REG_TEMP_SOURCE array and re-use NCT6106_REG_TEMP_SOURCE. Reported-by: Florian Bezdeka Closes: https://lore.kernel.org/linux-hwmon/57cfc3fa-d4e9-4c10-8aa7-4ad0af7ebebe@roeck-us.net/T/#t Fixes: 29c7cb485b32 ("hwmon: (nct6775) Integrate new model nct6116") Cc: Björn Gerhart Signed-off-by: Guenter Roeck --- drivers/hwmon/nct6775-core.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/hwmon/nct6775-core.c b/drivers/hwmon/nct6775-core.c index d668dc390def..51253acff4b0 100644 --- a/drivers/hwmon/nct6775-core.c +++ b/drivers/hwmon/nct6775-core.c @@ -846,8 +846,6 @@ static const u16 NCT6116_FAN_PULSE_SHIFT[] = { 0, 2, 4, 6, 6 }; static const u16 NCT6116_REG_PWM[] = { 0x119, 0x129, 0x139, 0x199, 0x1a9 }; static const u16 NCT6116_REG_FAN_MODE[] = { 0x113, 0x123, 0x133, 0x193, 0x1a3 }; static const u16 NCT6116_REG_TEMP_SEL[] = { 0x110, 0x120, 0x130, 0x190, 0x1a0 }; -static const u16 NCT6116_REG_TEMP_SOURCE[] = { - 0xb0, 0xb1, 0xb2 }; static const u16 NCT6116_REG_CRITICAL_TEMP[] = { 0x11a, 0x12a, 0x13a, 0x19a, 0x1aa }; @@ -3652,7 +3650,7 @@ int nct6775_probe(struct device *dev, struct nct6775_data *data, = NCT6106_CRITICAL_PWM_ENABLE_MASK; data->REG_CRITICAL_PWM = NCT6116_REG_CRITICAL_PWM; data->REG_TEMP_OFFSET = NCT6106_REG_TEMP_OFFSET; - data->REG_TEMP_SOURCE = NCT6116_REG_TEMP_SOURCE; + data->REG_TEMP_SOURCE = NCT6106_REG_TEMP_SOURCE; data->REG_TEMP_SEL = NCT6116_REG_TEMP_SEL; data->REG_WEIGHT_TEMP_SEL = NCT6106_REG_WEIGHT_TEMP_SEL; data->REG_WEIGHT_TEMP[0] = NCT6106_REG_WEIGHT_TEMP_STEP; @@ -3666,13 +3664,13 @@ int nct6775_probe(struct device *dev, struct nct6775_data *data, reg_temp = NCT6106_REG_TEMP; reg_temp_mon = NCT6106_REG_TEMP_MON; - num_reg_temp = ARRAY_SIZE(NCT6106_REG_TEMP); + num_reg_temp = 3; num_reg_temp_mon = ARRAY_SIZE(NCT6106_REG_TEMP_MON); num_reg_tsi_temp = ARRAY_SIZE(NCT6116_REG_TSI_TEMP); reg_temp_over = NCT6106_REG_TEMP_OVER; reg_temp_hyst = NCT6106_REG_TEMP_HYST; reg_temp_config = NCT6106_REG_TEMP_CONFIG; - num_reg_temp_config = ARRAY_SIZE(NCT6106_REG_TEMP_CONFIG); + num_reg_temp_config = 3; reg_temp_alternate = NCT6106_REG_TEMP_ALTERNATE; reg_temp_crit = NCT6106_REG_TEMP_CRIT; reg_temp_crit_l = NCT6106_REG_TEMP_CRIT_L; From a64a7e8a0b012ba81b0eadbd7afc84ab0dbfd70c Mon Sep 17 00:00:00 2001 From: Vincent Jardin Date: Thu, 23 Jul 2026 17:44:56 +0200 Subject: [PATCH 02/19] hwmon: (pmbus/core) notify on the hwmon device, not the i2c client pmbus_notify() calls sysfs_notify() and kobject_uevent() on the i2c client's kobject, but the alarm attributes live on the hwmon class device registered by pmbus_do_probe(). Notifying the parent i2c device is a no-op for both poll(POLLPRI) waiters and udev listeners: the named attribute does not exist on that kobject. Notify the hwmon device instead, so poll() wakes up and "change" uevents fire on the inX_alarm/tempX_alarm attributes when SMBALERT# reports a fault. Fixes: f469bde9afd1 ("hwmon: (pmbus/core) Notify hwmon events") Cc: stable@vger.kernel.org # v6.4+ Signed-off-by: Vincent Jardin Link: https://lore.kernel.org/r/20260723-fix_hwmon_notify_v1-v1-1-5a24c528686d@free.fr Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/pmbus_core.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c index 3143b9e0316c..0081f16c3a95 100644 --- a/drivers/hwmon/pmbus/pmbus_core.c +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -2985,8 +2985,9 @@ static void pmbus_notify(struct pmbus_data *data, int page, int reg, int flags) if (reg == sreg && page == spage && (smask & flags)) { dev_dbg(data->dev, "sysfs notify: %s", da->attr.name); - sysfs_notify(&data->dev->kobj, NULL, da->attr.name); - kobject_uevent(&data->dev->kobj, KOBJ_CHANGE); + sysfs_notify(&data->hwmon_dev->kobj, NULL, + da->attr.name); + kobject_uevent(&data->hwmon_dev->kobj, KOBJ_CHANGE); flags &= ~smask; } From e6c80061ca239f45c0eaf7e47a91d6d6df9bd636 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 10 Jun 2026 07:46:16 -0700 Subject: [PATCH 03/19] hwmon: (ina2xx) Fix various overflow issues Sashiko reports several integer overflow problems in the ina2xx driver caused by unbounded multiplications and inadequate types for intermediate calculations. Specifically: - In ina2xx_get_value(), the return type is changed from int to long. Intermediate calculations for current are now performed using 64-bit types to prevent 32-bit integer overflow before the division by 1000. - When calculating power in ina2xx_get_value() and sy24655_average_power_read(), interim values are cast to u64 and clamped to LONG_MAX. This prevents overflow when regval or accumulator_24 is multiplied by power_lsb_uW. - In ina226_alert_to_reg(), the clamping logic is rewritten using min_t(). This safely avoids integer overflows when scaling user-provided values for shunt voltage, bus voltage, power, and current limits. Cc: Loic Poulain Fixes: ab7fbee452be ("hwmon: (ina2xx) Fix various overflow issues") Signed-off-by: Guenter Roeck --- drivers/hwmon/ina2xx.c | 61 ++++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/drivers/hwmon/ina2xx.c b/drivers/hwmon/ina2xx.c index c4742e84b999..449a72c6b40b 100644 --- a/drivers/hwmon/ina2xx.c +++ b/drivers/hwmon/ina2xx.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -266,30 +267,34 @@ static u16 ina226_interval_to_reg(long interval) return FIELD_PREP(INA226_AVG_RD_MASK, avg_bits); } -static int ina2xx_get_value(struct ina2xx_data *data, u8 reg, - unsigned int regval) +static long ina2xx_get_value(struct ina2xx_data *data, u8 reg, + unsigned int regval) { - int val; + s64 val64; + long val; switch (reg) { case INA2XX_SHUNT_VOLTAGE: /* signed register */ - val = (s16)regval >> data->config->shunt_voltage_shift; - val = DIV_ROUND_CLOSEST(val, data->config->shunt_div); + val = DIV_ROUND_CLOSEST((s16)regval >> data->config->shunt_voltage_shift, + data->config->shunt_div); break; case INA2XX_BUS_VOLTAGE: - val = (regval >> data->config->bus_voltage_shift) * - data->config->bus_voltage_lsb; - val = DIV_ROUND_CLOSEST(val, 1000); + val = DIV_ROUND_CLOSEST((regval >> data->config->bus_voltage_shift) * + data->config->bus_voltage_lsb, 1000); break; case INA2XX_POWER: - val = regval * data->power_lsb_uW; + val = min_t(u64, (u64)regval * data->power_lsb_uW, LONG_MAX); break; case INA2XX_CURRENT: /* signed register, result in mA */ - val = ((s16)regval >> data->config->current_shift) * + val64 = (s64)((s16)regval >> data->config->current_shift) * data->current_lsb_uA; - val = DIV_ROUND_CLOSEST(val, 1000); + if (val64 < 0) + val64 = -DIV_ROUND_CLOSEST_ULL(-val64, 1000); + else + val64 = DIV_ROUND_CLOSEST_ULL(val64, 1000); + val = clamp_val(val64, LONG_MIN, LONG_MAX); break; case INA2XX_CALIBRATION: val = regval; @@ -378,27 +383,29 @@ static int ina2xx_read_init(struct device *dev, int reg, long *val) */ static u16 ina226_alert_to_reg(struct ina2xx_data *data, int reg, long val) { + long limit; + switch (reg) { case INA2XX_SHUNT_VOLTAGE: - val = clamp_val(val, 0, SHRT_MAX * data->config->shunt_div); - val *= data->config->shunt_div; - val <<= data->config->shunt_voltage_shift; - return clamp_val(val, 0, SHRT_MAX); + val = min_t(long, val, DIV_ROUND_CLOSEST(SHRT_MAX, data->config->shunt_div)); + return min_t(long, (val * data->config->shunt_div) << data->config->shunt_voltage_shift, + SHRT_MAX); case INA2XX_BUS_VOLTAGE: - val = clamp_val(val, 0, 200000); - val = (val * 1000) << data->config->bus_voltage_shift; - val = DIV_ROUND_CLOSEST(val, data->config->bus_voltage_lsb); - return clamp_val(val, 0, USHRT_MAX); + val = min_t(long, val, 130000); + return min_t(long, + DIV_ROUND_CLOSEST((val * 1000) << data->config->bus_voltage_shift, + data->config->bus_voltage_lsb), + USHRT_MAX); case INA2XX_POWER: - val = clamp_val(val, 0, UINT_MAX - data->power_lsb_uW); - val = DIV_ROUND_CLOSEST(val, data->power_lsb_uW); - return clamp_val(val, 0, USHRT_MAX); + val = min_t(long, val, LONG_MAX - data->power_lsb_uW); + return min_t(long, DIV_ROUND_CLOSEST(val, data->power_lsb_uW), USHRT_MAX); case INA2XX_CURRENT: - val = clamp_val(val, INT_MIN / 1000, INT_MAX / 1000); + limit = (LONG_MAX - (data->current_lsb_uA / 2)) / 1000; + val = min_t(long, val, limit); /* signed register, result in mA */ val = DIV_ROUND_CLOSEST(val * 1000, data->current_lsb_uA); - val <<= data->config->current_shift; - return clamp_val(val, SHRT_MIN, SHRT_MAX); + limit = SHRT_MAX >> data->config->current_shift; + return (u16)(min_t(long, val, limit) << data->config->current_shift); default: /* programmer goofed */ WARN_ON_ONCE(1); @@ -537,6 +544,7 @@ static int sy24655_average_power_read(struct ina2xx_data *data, u8 reg, long *va u8 template[6]; int ret; long accumulator_24, sample_count; + u64 val64; /* 48-bit register read */ ret = i2c_smbus_read_i2c_block_data(data->client, reg, 6, template); @@ -555,7 +563,8 @@ static int sy24655_average_power_read(struct ina2xx_data *data, u8 reg, long *va return 0; } - *val = DIV_ROUND_CLOSEST(accumulator_24, sample_count) * data->power_lsb_uW; + val64 = (u64)DIV_ROUND_CLOSEST(accumulator_24, sample_count) * data->power_lsb_uW; + *val = min_t(u64, val64, LONG_MAX); return 0; } From 00feb1cce93dab948a299b69753d99c681d45a0b Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 5 Feb 2025 12:27:15 -0800 Subject: [PATCH 04/19] hwmon: (ltc4282) Fix reading the minimum alarm voltage Coverity reports an out-of-bounds access when reading the minimum alarm voltage for the VGPIO channel. Add the missing return statement to fix the problem. Fixes: cbc29538dbf7 ("hwmon: Add driver for LTC4282") Cc: Nuno Sa Signed-off-by: Guenter Roeck --- drivers/hwmon/ltc4282.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/ltc4282.c b/drivers/hwmon/ltc4282.c index 39b9d3abca99..cc698803f8bf 100644 --- a/drivers/hwmon/ltc4282.c +++ b/drivers/hwmon/ltc4282.c @@ -374,8 +374,8 @@ static int ltc4282_read_in(struct ltc4282_state *st, u32 attr, long *val, channel, val); case hwmon_in_min_alarm: if (channel == LTC4282_CHAN_VGPIO) - ltc4282_read_alarm(st, LTC4282_ADC_ALERT_LOG, - LTC4282_GPIO_ALARM_L_MASK, val); + return ltc4282_read_alarm(st, LTC4282_ADC_ALERT_LOG, + LTC4282_GPIO_ALARM_L_MASK, val); return ltc4282_vdd_source_read_alm(st, LTC4282_VSOURCE_ALARM_L_MASK, From f46d5ab43a572b84773015a76966f5da56fc1748 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sat, 25 Jul 2026 09:34:46 -0700 Subject: [PATCH 05/19] hwmon: (sht3x) Fix unaligned accesses Sashiko reports: In sht3x_update_client(), the 16-bit temperature and humidity values are extracted from a stack-allocated byte array using be16_to_cpup(). The pointers passed to this function are calculated as buf and buf + 3. Since the difference between the two pointers is an odd number of bytes, at least one of them is guaranteed to be at an unaligned offset. This will trigger an alignment fault on strict-alignment architectures such as ARMv5 or SPARC, resulting in a kernel panic. Fix the problem by using get_unaligned_be16() instead of be16_to_cpup(), and put_unaligned_be16() instead of cpu_to_be16(). Fixes: 7c84f7f80d6f ("hwmon: add support for Sensirion SHT3x sensors") Reported-by: Sashiko Signed-off-by: Guenter Roeck --- drivers/hwmon/sht3x.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/hwmon/sht3x.c b/drivers/hwmon/sht3x.c index c2f6b73aa7f3..4d90f89a9929 100644 --- a/drivers/hwmon/sht3x.c +++ b/drivers/hwmon/sht3x.c @@ -21,6 +21,7 @@ #include #include #include +#include /* commands (high repeatability mode) */ static const unsigned char sht3x_cmd_measure_single_hpm[] = { 0x24, 0x00 }; @@ -276,9 +277,9 @@ static struct sht3x_data *sht3x_update_client(struct device *dev) if (ret) goto out; - val = be16_to_cpup((__be16 *)buf); + val = get_unaligned_be16(buf); data->temperature = sht3x_extract_temperature(val); - val = be16_to_cpup((__be16 *)(buf + 3)); + val = get_unaligned_be16(buf + 3); data->humidity = sht3x_extract_humidity(val); data->last_update = jiffies; } @@ -336,7 +337,7 @@ static int limits_update(struct sht3x_data *data) if (ret) return ret; - raw = be16_to_cpup((__be16 *)buffer); + raw = get_unaligned_be16(buffer); temperature = sht3x_extract_temperature((raw & 0x01ff) << 7); humidity = sht3x_extract_humidity(raw & 0xfe00); data->temperature_limits[index] = temperature; @@ -389,7 +390,7 @@ static size_t limit_write(struct device *dev, raw = ((u32)(temperature + 45000) * 24543) >> (16 + 7); raw |= ((humidity * 42950) >> 16) & 0xfe00; - *((__be16 *)position) = cpu_to_be16(raw); + put_unaligned_be16(raw, position); position += SHT3X_WORD_LEN; *position = crc8(sht3x_crc8_table, position - SHT3X_WORD_LEN, From aa9429edf9fc0e90d6f4da19ea4b5495a54ab117 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sat, 25 Jul 2026 15:27:28 -0700 Subject: [PATCH 06/19] hwmon: (lm90) Only report alarms if driver is ready Userspace can read sysfs attributes before driver registration is complete, immediately after devm_hwmon_device_register_with_info() has been called. At that time, data->hwmon_dev is not yet initialized. This can trigger a NULL pointer access since lm90_update_device() and with it lm90_update_alarms_locked() will be called. This call schedules report_work and lm90_report_alarms(), which passes the still-NULL data->hwmon_dev to hwmon_notify_event() and triggers a NULL pointer dereference. Fix the problem by only scheduling the report and alert workers data->hwmon_dev is set. Reported-by: Sashiko Fixes: f6d0775119fb9 ("hwmon: (lm90) Rework alarm/status handling") Signed-off-by: Guenter Roeck --- drivers/hwmon/lm90.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/lm90.c b/drivers/hwmon/lm90.c index 4b9c0ccdf260..c6186505661f 100644 --- a/drivers/hwmon/lm90.c +++ b/drivers/hwmon/lm90.c @@ -1194,7 +1194,7 @@ static int lm90_update_alarms_locked(struct lm90_data *data, bool force) check_enable = (client->irq || !(data->config_orig & 0x80)) && (data->config & 0x80); - if (force || check_enable) + if (data->hwmon_dev && (force || check_enable)) schedule_work(&data->report_work); /* @@ -1202,7 +1202,7 @@ static int lm90_update_alarms_locked(struct lm90_data *data, bool force) * alarms are all clear, and alerts are currently disabled. * Otherwise (re)schedule worker if needed. */ - if (check_enable) { + if (check_enable && data->hwmon_dev) { if (!(data->current_alarms & data->alert_alarms)) { dev_dbg(&client->dev, "Re-enabling ALERT#\n"); lm90_update_confreg(data, data->config & ~0x80); From 080bbf42faf77e6489ab30d5114c5f8f6ccbb1b8 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Mon, 27 Jul 2026 09:54:23 -0700 Subject: [PATCH 07/19] hwmon: (nzxt-smart2) DMA-align output buffer Sashiko reports: When send_output_report() calls hid_hw_output_report(), the underlying USB HID core calls usb_interrupt_msg() which maps this buffer directly for DMA. When the DMA mapping flushes or invalidates the cacheline, it will corrupt the adjacent variables (mutex, update_interval) that were modified concurrently by the CPU. This causes memory corruption due to cacheline sharing on non-coherent CPU architectures (such as ARM or MIPS). The DMA API debugging tool (CONFIG_DMA_API_DEBUG) will trigger runtime warnings for this violation. Any operation that triggers send_output_report() (like setting a fan speed or updating the interval) causes the USB DMA mapping. On systems with non-coherent caches, this structural bug causes immediate and deterministic memory corruption. Align the output buffer to ARCH_DMA_MINALIGN to fix the problem. Reported-by: Sashiko Fixes: 53e68c20aeb1 ("hwmon: add driver for NZXT RGB&Fan Controller/Smart Device v2.") Cc: Aleksandr Mezin Signed-off-by: Guenter Roeck --- drivers/hwmon/nzxt-smart2.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/nzxt-smart2.c b/drivers/hwmon/nzxt-smart2.c index e2316c46629d..ff0c0bee0e83 100644 --- a/drivers/hwmon/nzxt-smart2.c +++ b/drivers/hwmon/nzxt-smart2.c @@ -203,7 +203,7 @@ struct drvdata { */ struct mutex mutex; long update_interval; - u8 output_buffer[OUTPUT_REPORT_SIZE]; + u8 output_buffer[OUTPUT_REPORT_SIZE] __aligned(ARCH_DMA_MINALIGN); }; static long scale_pwm_value(long val, long orig_max, long new_max) From a60f58eb70e4e4c2ba4ace8b292dea64e5b7b290 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Mon, 27 Jul 2026 13:13:54 -0700 Subject: [PATCH 08/19] hwmon: (lm63) Mask PWM frequency multiplier to supported bits Sashiko is concerned that reading a PWM frequency multiplier outside the supported range of [1, 31] might result in bad PWM values written to the chip. Technically, the chip should never return a value with the upper 3 bits set, so this should never happen. However, it is unknown if there are LM63 variants where the upper bits of the register can be written. Mask the register value read from the chip to only accept the lower 5 bit when reading it from the chip to avoid the problem. Reported-by: Sashiko Signed-off-by: Guenter Roeck --- drivers/hwmon/lm63.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/hwmon/lm63.c b/drivers/hwmon/lm63.c index e2a429e579ac..da09770c05e7 100644 --- a/drivers/hwmon/lm63.c +++ b/drivers/hwmon/lm63.c @@ -247,8 +247,7 @@ static struct lm63_data *lm63_update_device(struct device *dev) LM63_REG_TACH_LIMIT_MSB) << 8); } - data->pwm1_freq = i2c_smbus_read_byte_data(client, - LM63_REG_PWM_FREQ); + data->pwm1_freq = i2c_smbus_read_byte_data(client, LM63_REG_PWM_FREQ) & 0x1f; if (data->pwm1_freq == 0) data->pwm1_freq = 1; data->pwm1[0] = i2c_smbus_read_byte_data(client, @@ -1187,7 +1186,7 @@ static void lm63_init_client(struct lm63_data *data) data->config |= 0x04; /* We may need pwm1_freq before ever updating the client data */ - data->pwm1_freq = i2c_smbus_read_byte_data(client, LM63_REG_PWM_FREQ); + data->pwm1_freq = i2c_smbus_read_byte_data(client, LM63_REG_PWM_FREQ) & 0x1f; if (data->pwm1_freq == 0) data->pwm1_freq = 1; From d0b704e569ac3b8416d8e02270cdc9bf830ed395 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Mon, 27 Jul 2026 13:35:37 -0700 Subject: [PATCH 09/19] hwmon: (nct6775-core) Prevent access to unsupported weight registers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sashiko reports: During initialization of the nct6116 chip, the driver sets data->pwm_num to 5. However, it assigns several NCT6106 register arrays (such as NCT6106_REG_WEIGHT_DUTY_STEP, NCT6106_REG_WEIGHT_TEMP_SEL, and NCT6106_REG_WEIGHT_TEMP_*) to data->REG_PWM and data->REG_WEIGHT_TEMP. These arrays only contain 3 elements. In nct6775_update_pwm(), the driver iterates up to data->pwm_num. If data->has_pwm has bits 3 or 4 set (which is structurally possible for nct6116), the loop attempts to read elements at index 3 and 4 from these 3-element arrays. This results in a global out-of-bounds read, which can be caught by KASAN. Furthermore, the driver uses these garbage out-of-bounds values as hardware register addresses for subsequent read and write operations. This leads to invalid hardware register access, potentially causing hardware misconfiguration or system crashes. The underlying problem is that the chip does support up to five fan control channels, but only the first three support weight control. Fix the problem by extending the affected weight register arrays with zeroed fields. The driver uses zeroed register addresses to determine if a register is supported or not, and skips accesses for unsupported registers. Reported-by: Sashiko Fixes: 29c7cb485b32 ("hwmon: (nct6775) Integrate new model nct6116") Cc: Björn Gerhart Cc: Florian Bezdeka Signed-off-by: Guenter Roeck --- drivers/hwmon/nct6775-core.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/hwmon/nct6775-core.c b/drivers/hwmon/nct6775-core.c index 51253acff4b0..94482c8092cd 100644 --- a/drivers/hwmon/nct6775-core.c +++ b/drivers/hwmon/nct6775-core.c @@ -791,12 +791,12 @@ static const u16 NCT6106_REG_TOLERANCE_H[] = { 0x112, 0x122, 0x132 }; static const u16 NCT6106_REG_TARGET[] = { 0x111, 0x121, 0x131 }; -static const u16 NCT6106_REG_WEIGHT_TEMP_SEL[] = { 0x168, 0x178, 0x188 }; -static const u16 NCT6106_REG_WEIGHT_TEMP_STEP[] = { 0x169, 0x179, 0x189 }; -static const u16 NCT6106_REG_WEIGHT_TEMP_STEP_TOL[] = { 0x16a, 0x17a, 0x18a }; -static const u16 NCT6106_REG_WEIGHT_DUTY_STEP[] = { 0x16b, 0x17b, 0x18b }; -static const u16 NCT6106_REG_WEIGHT_TEMP_BASE[] = { 0x16c, 0x17c, 0x18c }; -static const u16 NCT6106_REG_WEIGHT_DUTY_BASE[] = { 0x16d, 0x17d, 0x18d }; +static const u16 NCT6106_REG_WEIGHT_TEMP_SEL[] = { 0x168, 0x178, 0x188, 0, 0 }; +static const u16 NCT6106_REG_WEIGHT_TEMP_STEP[] = { 0x169, 0x179, 0x189, 0, 0 }; +static const u16 NCT6106_REG_WEIGHT_TEMP_STEP_TOL[] = { 0x16a, 0x17a, 0x18a, 0, 0 }; +static const u16 NCT6106_REG_WEIGHT_DUTY_STEP[] = { 0x16b, 0x17b, 0x18b, 0, 0 }; +static const u16 NCT6106_REG_WEIGHT_TEMP_BASE[] = { 0x16c, 0x17c, 0x18c, 0, 0 }; +static const u16 NCT6106_REG_WEIGHT_DUTY_BASE[] = { 0x16d, 0x17d, 0x18d, 0, 0 }; static const u16 NCT6106_REG_AUTO_TEMP[] = { 0x160, 0x170, 0x180 }; static const u16 NCT6106_REG_AUTO_PWM[] = { 0x164, 0x174, 0x184 }; From 625a2c02a1c04571232a746fe188b4d9a8d63edd Mon Sep 17 00:00:00 2001 From: Luiz Angelo Daros de Luca Date: Mon, 27 Jul 2026 21:22:17 -0300 Subject: [PATCH 10/19] hwmon: (adt7470) Fix fans stuck in manual mode on I2C errors During adt7470_read_temperatures(), the driver temporarily switches the PWM channels to manual mode, performs the temperature collection, and then restores the original configuration registers. However, if an I2C transaction fails at any point after entering manual mode, the function aborts and returns immediately. This leaves the configuration registers un-restored, permanently trapping the fans in manual mode. Introduce a recovery path to ensure that the original PWM configuration registers are always restored, even when intermediate I2C operations fail. Reported-by: sashiko-bot@kernel.org Closes: https://lore.kernel.org/r/20260716213252.EACA71F000E9@smtp.kernel.org Fixes: ef67959c4253 ("hwmon: (adt7470) Convert to use regmap") Signed-off-by: Luiz Angelo Daros de Luca Link: https://lore.kernel.org/r/20260727-adt7470_fixes-v2-1-598e38a46ba6@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/adt7470.c | 42 +++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/drivers/hwmon/adt7470.c b/drivers/hwmon/adt7470.c index 664349756dc2..481d51617f4b 100644 --- a/drivers/hwmon/adt7470.c +++ b/drivers/hwmon/adt7470.c @@ -205,11 +205,12 @@ static inline int adt7470_write_word_data(struct adt7470_data *data, unsigned in /* Probe for temperature sensors. Assumes lock is held */ static int adt7470_read_temperatures(struct adt7470_data *data) { - unsigned long res; - unsigned int pwm_cfg[2]; - int err; - int i; + struct device *dev = regmap_get_device(data->regmap); u8 pwm[ADT7470_FAN_COUNT]; + unsigned int pwm_cfg[2]; + unsigned long res; + int err, err2; + int i; /* save pwm[1-4] config register */ err = regmap_read(data->regmap, ADT7470_REG_PWM_CFG(0), &pwm_cfg[0]); @@ -233,19 +234,19 @@ static int adt7470_read_temperatures(struct adt7470_data *data) err = regmap_update_bits(data->regmap, ADT7470_REG_PWM_CFG(2), ADT7470_PWM_AUTO_MASK, 0); if (err < 0) - return err; + goto out_restore; /* write pwm control to whatever it was */ err = regmap_bulk_write(data->regmap, ADT7470_REG_PWM(0), &pwm[0], ADT7470_PWM_COUNT); if (err < 0) - return err; + goto out_restore; /* start reading temperature sensors */ err = regmap_update_bits(data->regmap, ADT7470_REG_CFG, ADT7470_T05_STB_MASK, ADT7470_T05_STB_MASK); if (err < 0) - return err; + goto out_restore; /* Delay is 200ms * number of temp sensors. */ res = msleep_interruptible((data->num_temp_sensors >= 0 ? @@ -256,13 +257,30 @@ static int adt7470_read_temperatures(struct adt7470_data *data) err = regmap_update_bits(data->regmap, ADT7470_REG_CFG, ADT7470_T05_STB_MASK, 0); if (err < 0) - return err; + goto out_restore; +out_restore: /* restore pwm[1-4] config registers */ - err = regmap_write(data->regmap, ADT7470_REG_PWM_CFG(0), pwm_cfg[0]); - if (err < 0) - return err; - err = regmap_write(data->regmap, ADT7470_REG_PWM_CFG(2), pwm_cfg[1]); + err2 = regmap_write(data->regmap, ADT7470_REG_PWM_CFG(0), pwm_cfg[0]); + if (err2 < 0) { + dev_warn_ratelimited(dev, + "failed to restore PWM{1,2} config (%d)\n", + err2); + + if (!err) + err = err2; + } + + err2 = regmap_write(data->regmap, ADT7470_REG_PWM_CFG(2), pwm_cfg[1]); + if (err2 < 0) { + dev_warn_ratelimited(dev, + "failed to restore PWM{3,4} config (%d)\n", + err2); + + if (!err) + err = err2; + } + if (err < 0) return err; From 05270bd38d9bf88a2f4c212246a8fa29f4032078 Mon Sep 17 00:00:00 2001 From: Luiz Angelo Daros de Luca Date: Mon, 27 Jul 2026 21:22:18 -0300 Subject: [PATCH 11/19] hwmon: (adt7470) Fix cache updated before hardware write on I2C error adt7470_temp_write() and adt7470_pwm_write() update the driver's cached values (temp_min, temp_max, pwm_input, pwm_enable) before issuing the corresponding regmap_write(), and never check whether the write succeeded before committing that update. If the I2C transaction fails, the function correctly propagates the error to the caller, but the cache silently keeps the new value, which was never actually applied to the hardware. Subsequent reads then report a value that does not match the device state. Reorder both write paths to update the cache only after a successful regmap_write(), so the cache always reflects what was actually written to the hardware. Fixes: ef67959c4253 ("hwmon: (adt7470) Convert to use regmap") Signed-off-by: Luiz Angelo Daros de Luca Link: https://lore.kernel.org/r/20260727-adt7470_fixes-v2-2-598e38a46ba6@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/adt7470.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/drivers/hwmon/adt7470.c b/drivers/hwmon/adt7470.c index 481d51617f4b..62ec68ea0a40 100644 --- a/drivers/hwmon/adt7470.c +++ b/drivers/hwmon/adt7470.c @@ -589,14 +589,16 @@ static int adt7470_temp_write(struct device *dev, u32 attr, int channel, long va switch (attr) { case hwmon_temp_min: mutex_lock(&data->lock); - data->temp_min[channel] = val; err = regmap_write(data->regmap, ADT7470_TEMP_MIN_REG(channel), val); + if (!err) + data->temp_min[channel] = val; mutex_unlock(&data->lock); break; case hwmon_temp_max: mutex_lock(&data->lock); - data->temp_max[channel] = val; err = regmap_write(data->regmap, ADT7470_TEMP_MAX_REG(channel), val); + if (!err) + data->temp_max[channel] = val; mutex_unlock(&data->lock); break; default: @@ -831,9 +833,10 @@ static int adt7470_pwm_write(struct device *dev, u32 attr, int channel, long val case hwmon_pwm_input: val = clamp_val(val, 0, 255); mutex_lock(&data->lock); - data->pwm[channel] = val; err = regmap_write(data->regmap, ADT7470_REG_PWM(channel), - data->pwm[channel]); + val); + if (!err) + data->pwm[channel] = val; mutex_unlock(&data->lock); break; case hwmon_pwm_enable: @@ -847,10 +850,11 @@ static int adt7470_pwm_write(struct device *dev, u32 attr, int channel, long val val--; mutex_lock(&data->lock); - data->pwm_automatic[channel] = val; err = regmap_update_bits(data->regmap, ADT7470_REG_PWM_CFG(channel), pwm_auto_reg_mask, val ? pwm_auto_reg_mask : 0); + if (!err) + data->pwm_automatic[channel] = val; mutex_unlock(&data->lock); break; case hwmon_pwm_freq: From cb0b7f9c43b0abbd422a7e4c2c85e91db429207c Mon Sep 17 00:00:00 2001 From: Luiz Angelo Daros de Luca Date: Mon, 27 Jul 2026 21:22:19 -0300 Subject: [PATCH 12/19] hwmon: (adt7470) Fix busy-loop and I2C flooding in update thread When userspace configures 'auto_update_interval' to 0 via sysfs, the background kthread executes schedule_timeout_interruptible(0), which returns immediately. If 'num_temp_sensors' is concurrently or previously set to 0, the msleep_interruptible() delay inside adt7470_read_temperatures() also becomes 0. This combination forces the background thread into a tight, unbounded busy-loop, hogging the CPU and flooding the I2C bus with a continuous stream of transactions. Fix this vulnerability by raising the lower limit of the clamp_val in auto_update_interval_store() from 0 to 500 milliseconds. This guarantees a reasonable minimum sleep window between sensor updates, protecting the system from intentional or accidental I2C bus denial of service. Reported-by: sashiko-bot@kernel.org Closes: https://lore.kernel.org/r/20260716213252.EACA71F000E9@smtp.kernel.org Fixes: 89fac11cb3e7 ("adt7470: make automatic fan control really work") Signed-off-by: Luiz Angelo Daros de Luca Link: https://lore.kernel.org/r/20260727-adt7470_fixes-v2-3-598e38a46ba6@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/adt7470.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/adt7470.c b/drivers/hwmon/adt7470.c index 62ec68ea0a40..0b19b0925d1c 100644 --- a/drivers/hwmon/adt7470.c +++ b/drivers/hwmon/adt7470.c @@ -509,7 +509,7 @@ static ssize_t auto_update_interval_store(struct device *dev, if (kstrtol(buf, 10, &temp)) return -EINVAL; - temp = clamp_val(temp, 0, 60000); + temp = clamp_val(temp, 500, 60000); mutex_lock(&data->lock); data->auto_update_interval = temp; From 1a18c79c4bc44cc5349c60e16b0b744dc6ec5f77 Mon Sep 17 00:00:00 2001 From: Luiz Angelo Daros de Luca Date: Mon, 27 Jul 2026 21:22:21 -0300 Subject: [PATCH 13/19] hwmon: (adt7470) Fix temperature alarm logic in hwmon_temp_read() During the conversion the alarm callback started interpreting the channel index as an alarm bitmask, resulting in incorrect alarm reporting. Compute the proper alarm bit instead. Reported-by: sashiko-bot@kernel.org Closes: https://lore.kernel.org/r/20260717211224.B9E291F000E9@smtp.kernel.org Fixes: fc958a61ff6d ("hwmon: (adt7470) Convert to devm_hwmon_device_register_with_info API") Signed-off-by: Luiz Angelo Daros de Luca Link: https://lore.kernel.org/r/20260727-adt7470_fixes-v2-5-598e38a46ba6@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/adt7470.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/adt7470.c b/drivers/hwmon/adt7470.c index 0b19b0925d1c..428bd1d91e70 100644 --- a/drivers/hwmon/adt7470.c +++ b/drivers/hwmon/adt7470.c @@ -110,6 +110,21 @@ static const unsigned short normal_i2c[] = { 0x2C, 0x2E, 0x2F, I2C_CLIENT_END }; #define ALARM2(x) ((x) << 8) +/* TEMP1..TEMP7 (ch 0..6) are, respectively BIT(0)..BIT(6) of reg 0x41 and + * 0x72, or BIT(0)..BIT(6) of data->alarm. + * TEMP8..TEMP9 (ch 7..9) are, respectively BIT(0)..BIT(2) of reg 0x42 and + * 0x73, or BIT(8)..BIT(10) of data->alarm. + */ +#define TEMP_ALARM_BIT(ch) ({ \ + typeof(ch) _ch = (ch); \ + (1 << (_ch < 7 ? _ch : _ch + 1)); \ +}) + +/* FAN1..FAN4 (ch 0..3) are respectively BIT(4)..BIT(7) in + * reg 0x42 and 0x73 or BIT(12)..BIT(15) in data->alarm. + */ +#define FAN_ALARM_BIT(ch) (1 << (12 + (ch))) + #define ADT7470_VENDOR 0x41 #define ADT7470_DEVICE 0x70 /* datasheet only mentions a revision 2 */ @@ -569,7 +584,7 @@ static int adt7470_temp_read(struct device *dev, u32 attr, int channel, long *va *val = 1000 * data->temp_max[channel]; break; case hwmon_temp_alarm: - *val = !!(data->alarm & channel); + *val = !!(data->alarm & TEMP_ALARM_BIT(channel)); break; default: return -EOPNOTSUPP; @@ -668,7 +683,7 @@ static int adt7470_fan_read(struct device *dev, u32 attr, int channel, long *val *val = 0; break; case hwmon_fan_alarm: - *val = !!(data->alarm & (1 << (12 + channel))); + *val = !!(data->alarm & FAN_ALARM_BIT(channel)); break; default: return -EOPNOTSUPP; From a3850231521b06bbbb18c8ebea100320c14a08be Mon Sep 17 00:00:00 2001 From: Luiz Angelo Daros de Luca Date: Mon, 27 Jul 2026 21:22:20 -0300 Subject: [PATCH 14/19] hwmon: (adt7470) Fix swapped PWM3 and PWM4 auto mode masks The ADT7470_PWM3_AUTO_MASK and ADT7470_PWM4_AUTO_MASK macros are currently defined with swapped bit values. According to Table 22 of the ADT7470 datasheet, the Fan Control Mode Configuration for register 0x69 follows the exact same bit position layout as register 0x68: - 0x68 Bit[7] corresponds to BHVR1 (PWM1) -> 0x80 - 0x68 Bit[6] corresponds to BHVR2 (PWM2) -> 0x40 - 0x69 Bit[7] corresponds to BHVR3 (PWM3) -> 0x80 - 0x69 Bit[6] corresponds to BHVR4 (PWM4) -> 0x40 Consequently, PWM3 should use mask 0x80 and PWM4 should use 0x40. This typo did not cause any functional bugs because these specific macros are never referenced in the driver code. Instead, the driver correctly applies the configuration by relying on the modulo parity of the channel index (e.g., `channel % 2`) to selectively apply either ADT7470_PWM1_AUTO_MASK (0x80) or ADT7470_PWM2_AUTO_MASK (0x40). Since the bit layout is identical between the two configuration registers, the hardware is currently configured correctly. Fix the macro definitions to reflect the datasheet accurately and prevent future bugs or confusion during code review and refactoring. As this is a purely cosmetic fix with no functional impact, a backport to stable kernels is not necessary. Fixes: 6f9703d0be16 ("hwmon: add support for adt7470") Signed-off-by: Luiz Angelo Daros de Luca Link: https://lore.kernel.org/r/20260727-adt7470_fixes-v2-4-598e38a46ba6@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/adt7470.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/adt7470.c b/drivers/hwmon/adt7470.c index 428bd1d91e70..c6fc7d38d698 100644 --- a/drivers/hwmon/adt7470.c +++ b/drivers/hwmon/adt7470.c @@ -70,8 +70,8 @@ static const unsigned short normal_i2c[] = { 0x2C, 0x2E, 0x2F, I2C_CLIENT_END }; #define ADT7470_PWM1_AUTO_MASK 0x80 #define ADT7470_PWM_AUTO_MASK 0xC0 #define ADT7470_REG_PWM34_CFG 0x69 -#define ADT7470_PWM3_AUTO_MASK 0x40 -#define ADT7470_PWM4_AUTO_MASK 0x80 +#define ADT7470_PWM4_AUTO_MASK 0x40 +#define ADT7470_PWM3_AUTO_MASK 0x80 #define ADT7470_REG_PWM_MIN_BASE_ADDR 0x6A #define ADT7470_REG_PWM_MIN_MAX_ADDR 0x6D #define ADT7470_REG_PWM_TEMP_MIN_BASE_ADDR 0x6E From 60677cd4c28f44d5b307d3029dccece38fcce90f Mon Sep 17 00:00:00 2001 From: Luiz Angelo Daros de Luca Date: Mon, 27 Jul 2026 21:22:22 -0300 Subject: [PATCH 15/19] hwmon: (adt7470) Use cached PWM frequency value adt7470_pwm_read() currently ignores failures returned by pwm1_freq_get(). If the register read fails, the negative error code is returned through *val while the function itself reports success, potentially exposing a negative PWM frequency through sysfs. Fix this by using the cached PWM frequency maintained by the driver, eliminating the register access from the read path. Apart from the corrected error propagation and using the cached value, no functional change is intended. Fixes: ef67959c4253 ("hwmon: (adt7470) Convert to use regmap") Signed-off-by: Luiz Angelo Daros de Luca Link: https://lore.kernel.org/r/20260727-adt7470_fixes-v2-6-598e38a46ba6@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/adt7470.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/adt7470.c b/drivers/hwmon/adt7470.c index c6fc7d38d698..1fbca4869b7b 100644 --- a/drivers/hwmon/adt7470.c +++ b/drivers/hwmon/adt7470.c @@ -182,6 +182,7 @@ struct adt7470_data { u8 pwm_min[ADT7470_PWM_COUNT]; s8 pwm_tmin[ADT7470_PWM_COUNT]; u8 pwm_auto_temp[ADT7470_PWM_COUNT]; + u32 pwm_freq; struct task_struct *auto_update; unsigned int auto_update_interval; @@ -756,7 +757,7 @@ static ssize_t force_pwm_max_store(struct device *dev, } /* These are the valid PWM frequencies to the nearest Hz */ -static const int adt7470_freq_map[] = { +static const u32 adt7470_freq_map[] = { 11, 15, 22, 29, 35, 44, 59, 88, 1400, 22500 }; @@ -796,7 +797,7 @@ static int adt7470_pwm_read(struct device *dev, u32 attr, int channel, long *val *val = 1 + data->pwm_automatic[channel]; break; case hwmon_pwm_freq: - *val = pwm1_freq_get(dev); + *val = data->pwm_freq; break; default: return -EOPNOTSUPP; @@ -809,12 +810,14 @@ static int pwm1_freq_set(struct device *dev, long freq) { struct adt7470_data *data = dev_get_drvdata(dev); unsigned int low_freq = ADT7470_CFG_LF; + u32 closest_freq; int index; int err; /* Round the user value given to the closest available frequency */ index = find_closest(freq, adt7470_freq_map, ARRAY_SIZE(adt7470_freq_map)); + closest_freq = adt7470_freq_map[index]; if (index >= 8) { index -= 8; @@ -832,6 +835,10 @@ static int pwm1_freq_set(struct device *dev, long freq) err = regmap_update_bits(data->regmap, ADT7470_REG_CFG_2, ADT7470_FREQ_MASK, index << ADT7470_FREQ_SHIFT); + if (err < 0) + goto out; + + data->pwm_freq = closest_freq; out: mutex_unlock(&data->lock); @@ -1285,6 +1292,7 @@ static int adt7470_probe(struct i2c_client *client) struct device *dev = &client->dev; struct adt7470_data *data; struct device *hwmon_dev; + int freq_val; int err; data = devm_kzalloc(dev, sizeof(struct adt7470_data), GFP_KERNEL); @@ -1309,6 +1317,14 @@ static int adt7470_probe(struct i2c_client *client) if (err < 0) return err; + freq_val = pwm1_freq_get(dev); + if (freq_val <= 0) { + err = freq_val < 0 ? freq_val : -EINVAL; + return err; + } + + data->pwm_freq = (u32)freq_val; + /* Register sysfs hooks */ hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data, &adt7470_chip_info, From 1b46fe9dc8f8de59310f37e6c5e5c0e05ded46c3 Mon Sep 17 00:00:00 2001 From: Luiz Angelo Daros de Luca Date: Mon, 27 Jul 2026 21:22:23 -0300 Subject: [PATCH 16/19] hwmon: (adt7470) Fix divide-by-zero TOCTOU crash in fan speed read If the fan data becomes 0 between the FAN_DATA_VALID() check and the FAN_PERIOD_TO_RPM() conversion, it will result in a divide-by-zero crash due to a race with a concurrent update of the cached fan value. Fix a TOCTOU issue by reading fan data once. Reported-by: sashiko-bot@kernel.org Closes: https://lore.kernel.org/r/20260727034929.E29B71F000E9@smtp.kernel.org/ Fixes: fc958a61ff6d ("hwmon: (adt7470) Convert to devm_hwmon_device_register_with_info API") Signed-off-by: Luiz Angelo Daros de Luca Link: https://lore.kernel.org/r/20260727-adt7470_fixes-v2-7-598e38a46ba6@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/adt7470.c | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/drivers/hwmon/adt7470.c b/drivers/hwmon/adt7470.c index 1fbca4869b7b..772d2a409bb5 100644 --- a/drivers/hwmon/adt7470.c +++ b/drivers/hwmon/adt7470.c @@ -660,36 +660,33 @@ static ssize_t alarm_mask_store(struct device *dev, static int adt7470_fan_read(struct device *dev, u32 attr, int channel, long *val) { struct adt7470_data *data = adt7470_update_device(dev); + u16 fan_data; if (IS_ERR(data)) return PTR_ERR(data); switch (attr) { case hwmon_fan_input: - if (FAN_DATA_VALID(data->fan[channel])) - *val = FAN_PERIOD_TO_RPM(data->fan[channel]); - else - *val = 0; + fan_data = READ_ONCE(data->fan[channel]); break; case hwmon_fan_min: - if (FAN_DATA_VALID(data->fan_min[channel])) - *val = FAN_PERIOD_TO_RPM(data->fan_min[channel]); - else - *val = 0; + fan_data = READ_ONCE(data->fan_min[channel]); break; case hwmon_fan_max: - if (FAN_DATA_VALID(data->fan_max[channel])) - *val = FAN_PERIOD_TO_RPM(data->fan_max[channel]); - else - *val = 0; + fan_data = READ_ONCE(data->fan_max[channel]); break; case hwmon_fan_alarm: *val = !!(data->alarm & FAN_ALARM_BIT(channel)); - break; + return 0; default: return -EOPNOTSUPP; } + if (FAN_DATA_VALID(fan_data)) + *val = FAN_PERIOD_TO_RPM(fan_data); + else + *val = 0; + return 0; } From 92413f439d1ec5e55b73ede8d66a7b971cbd1ced Mon Sep 17 00:00:00 2001 From: Luiz Angelo Daros de Luca Date: Mon, 27 Jul 2026 21:22:24 -0300 Subject: [PATCH 17/19] hwmon: (adt7470) Fix PWM auto temp state array and bounds check In pwm_auto_temp_store(), the parsed user input was missing bounds checks, allowing values > 0xF to overflow into the adjacent channel's bits. Furthermore, the value was being incorrectly written to the pwm_automatic state array instead of pwm_auto_temp. Fix this by rejecting values > 0xF with -EINVAL, and assigning the value to the correct array only after a successful I2C write. Reported-by: sashiko-bot@kernel.org Closes: https://lore.kernel.org/all/20260727034932.0B7C41F000E9@smtp.kernel.org/#t Fixes: 6f9703d0be16 ("hwmon: add support for adt7470") Signed-off-by: Luiz Angelo Daros de Luca Link: https://lore.kernel.org/r/20260727-adt7470_fixes-v2-8-598e38a46ba6@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/adt7470.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/adt7470.c b/drivers/hwmon/adt7470.c index 772d2a409bb5..c45b984c02e6 100644 --- a/drivers/hwmon/adt7470.c +++ b/drivers/hwmon/adt7470.c @@ -1049,8 +1049,10 @@ static ssize_t pwm_auto_temp_store(struct device *dev, if (temp < 0) return temp; + if (temp > 0xF) + return -EINVAL; + mutex_lock(&data->lock); - data->pwm_automatic[attr->index] = temp; if (!(attr->index % 2)) { mask = 0xF0; @@ -1061,6 +1063,9 @@ static ssize_t pwm_auto_temp_store(struct device *dev, } err = regmap_update_bits(data->regmap, pwm_auto_reg, mask, val); + if (!err) + data->pwm_auto_temp[attr->index] = temp; + mutex_unlock(&data->lock); return err < 0 ? err : count; From a19038a200f18d9e74ac30081797917d0886e16b Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 28 Jul 2026 08:41:40 -0700 Subject: [PATCH 18/19] hwmon: (pmbus) Fix return value from pmbus_update_byte_data() pmbus_update_byte_data() is supposed to return a negative error code or 0. However, if no change is made to the register, it actually returns the register value. This can result in problems if the calling code explicitly expects to see an error code or 0. Fix it to return 0 on success or the error code as expected. Fixes: 11c119986f270 ("hwmon: (pmbus) add helpers for byte write and read modify write") Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/pmbus_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c index 0081f16c3a95..5567d37f13fe 100644 --- a/drivers/hwmon/pmbus/pmbus_core.c +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -513,7 +513,7 @@ int pmbus_update_byte_data(struct i2c_client *client, int page, u8 reg, if (tmp != rv) rv = _pmbus_write_byte_data(client, page, reg, tmp); - return rv; + return rv < 0 ? rv : 0; } EXPORT_SYMBOL_NS_GPL(pmbus_update_byte_data, "PMBUS"); From f27f6976ea269219c1259a7c2f8c6dfe782540a3 Mon Sep 17 00:00:00 2001 From: Hongyan Xu Date: Wed, 29 Jul 2026 18:01:16 +0800 Subject: [PATCH 19/19] hwmon: (npcm750-pwm-fan): stop fan timer on device detach When a fan tach channel is present, npcm7xx_pwm_fan_probe() starts fan_timer. The timer callback polls tach state and rearms the timer, but the driver has no remove callback or devm cleanup action to stop it. On device detach, the devm-managed driver data and I/O mappings can be released while the timer is still pending or running. Register a devm cleanup action before starting the timer and shut the timer down synchronously from that action. This issue was found by a static analysis tool. Fixes: f1fd4a4db777 ("hwmon: Add NPCM7xx PWM and Fan driver") Cc: stable@vger.kernel.org Signed-off-by: Hongyan Xu Link: https://lore.kernel.org/r/20260729100116.790-1-getshell@seu.edu.cn Signed-off-by: Guenter Roeck --- drivers/hwmon/npcm750-pwm-fan.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/hwmon/npcm750-pwm-fan.c b/drivers/hwmon/npcm750-pwm-fan.c index aea0b8659f5f..6ff2a6bc06c1 100644 --- a/drivers/hwmon/npcm750-pwm-fan.c +++ b/drivers/hwmon/npcm750-pwm-fan.c @@ -358,6 +358,11 @@ static void npcm7xx_fan_polling(struct timer_list *t) add_timer(&data->fan_timer); } +static void npcm7xx_fan_cleanup(void *timer) +{ + timer_shutdown_sync(timer); +} + static inline void npcm7xx_fan_compute(struct npcm7xx_pwm_fan_data *data, u8 fan, u8 cmp, u8 fan_id, u8 flag_int, u8 flag_mode, u8 flag_clear) @@ -1020,6 +1025,12 @@ static int npcm7xx_pwm_fan_probe(struct platform_device *pdev) msecs_to_jiffies(NPCM7XX_FAN_POLL_TIMER_200MS); timer_setup(&data->fan_timer, npcm7xx_fan_polling, 0); + ret = devm_add_action_or_reset(dev, + npcm7xx_fan_cleanup, + &data->fan_timer); + if (ret) + return ret; + add_timer(&data->fan_timer); break; }