From 5eab1e7f7ed12893f3864fe46ffcd5a41a752d6a Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Fri, 23 Oct 2026 21:40:44 -0400 Subject: [PATCH 01/41] hwmon: (sht15) Stop deferred work before releasing its resources The IRQ handler and regulator notifier can leave work pending after a measurement or notification completes. Neither work item is drained before its resources are released on removal or probe failure. Disable read_work before freeing the IRQ, since the worker can re-enable it. Use request_irq()/free_irq() for explicit teardown. Unregister the regulator notifier before cancelling update_supply_work and disabling the regulator. Apply the same ordering to probe failures. Fixes: 251eb40f5ccd ("hwmon: sht15 humidity sensor driver") Cc: stable@vger.kernel.org Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Link: https://patch.msgid.link/20261024014044.27853-1-mhun512@gmail.com [groeck: Dropped obsolete 'Assisted-by: tag] Signed-off-by: Guenter Roeck --- drivers/hwmon/sht15.c | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/drivers/hwmon/sht15.c b/drivers/hwmon/sht15.c index 7bb2105d1ae2..48d93c61345c 100644 --- a/drivers/hwmon/sht15.c +++ b/drivers/hwmon/sht15.c @@ -31,6 +31,7 @@ #include #include #include +#include /* Commands */ #define SHT15_MEASURE_TEMP 0x03 @@ -976,25 +977,22 @@ static int sht15_probe(struct platform_device *pdev) goto err_release_reg; } - ret = devm_request_irq(&pdev->dev, gpiod_to_irq(data->data), - sht15_interrupt_fired, - IRQF_TRIGGER_FALLING, - "sht15 data", - data); + ret = request_irq(gpiod_to_irq(data->data), sht15_interrupt_fired, + IRQF_TRIGGER_FALLING, "sht15 data", data); if (ret) goto err_release_reg; disable_irq_nosync(gpiod_to_irq(data->data)); ret = sht15_connection_reset(data); if (ret) - goto err_release_reg; + goto err_free_irq; ret = sht15_soft_reset(data); if (ret) - goto err_release_reg; + goto err_free_irq; ret = sysfs_create_group(&pdev->dev.kobj, &sht15_attr_group); if (ret) { dev_err(&pdev->dev, "sysfs create failed\n"); - goto err_release_reg; + goto err_free_irq; } data->hwmon_dev = hwmon_device_register(data->dev); @@ -1007,9 +1005,13 @@ static int sht15_probe(struct platform_device *pdev) err_release_sysfs_group: sysfs_remove_group(&pdev->dev.kobj, &sht15_attr_group); +err_free_irq: + disable_work_sync(&data->read_work); + free_irq(gpiod_to_irq(data->data), data); err_release_reg: if (!IS_ERR(data->reg)) { regulator_unregister_notifier(data->reg, &data->nb); + cancel_work_sync(&data->update_supply_work); regulator_disable(data->reg); } return ret; @@ -1023,12 +1025,16 @@ static void sht15_remove(struct platform_device *pdev) hwmon_device_unregister(data->hwmon_dev); sysfs_remove_group(&pdev->dev.kobj, &sht15_attr_group); + disable_work_sync(&data->read_work); + free_irq(gpiod_to_irq(data->data), data); + ret = sht15_soft_reset(data); if (ret) dev_err(&pdev->dev, "Failed to reset device (%pe)\n", ERR_PTR(ret)); if (!IS_ERR(data->reg)) { regulator_unregister_notifier(data->reg, &data->nb); + cancel_work_sync(&data->update_supply_work); regulator_disable(data->reg); } } From e2c26431b4e47892378582a414b4bbaa9edf1798 Mon Sep 17 00:00:00 2001 From: "Baek Kyu, Kim" Date: Wed, 21 Oct 2026 01:09:41 +0900 Subject: [PATCH 02/41] hwmon: (corsair-cpro) Cache fan target only after a successful command set_target() updates the cached fan target before sending the command. If the command fails, subsequent fanN_target reads report the failed request instead of the previous value. For example, after successfully setting 1200 RPM, a rejected request for 1800 RPM returns an error but leaves fanN_target reporting 1800. A failed request also replaces -ENODATA when no target has been successfully set. Update the target cache under the existing mutex only after send_usb_cmd() succeeds, as set_pwm() already does when invalidating the target. Preserve the previous cached value and return the original error on failure. Fixes: 40c3a4454225 ("hwmon: add Corsair Commander Pro driver") Cc: stable@vger.kernel.org Signed-off-by: Baek Kyu, Kim Link: https://patch.msgid.link/20261020160941.36787-1-pocj8ur4in@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/corsair-cpro.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/corsair-cpro.c b/drivers/hwmon/corsair-cpro.c index e6d31d367698..6ce5da37de03 100644 --- a/drivers/hwmon/corsair-cpro.c +++ b/drivers/hwmon/corsair-cpro.c @@ -215,10 +215,11 @@ static int set_target(struct ccp_device *ccp, int channel, long val) int ret; val = clamp_val(val, 0, 0xFFFF); - ccp->target[channel] = val; mutex_lock(&ccp->mutex); ret = send_usb_cmd(ccp, CTL_SET_FAN_TARGET, channel, val >> 8, val); + if (!ret) + ccp->target[channel] = val; mutex_unlock(&ccp->mutex); return ret; From cf8f81babd2e96087dedbb74985bc1781c98f73d Mon Sep 17 00:00:00 2001 From: Jiale Yao Date: Wed, 21 Oct 2026 20:04:07 +0800 Subject: [PATCH 03/41] hwmon: (aquacomputer_d5next) reject short status reports The HID raw_event callback runs before HID core validates the received report length. aqc_raw_event() reads device-specific status fields at fixed offsets, including multi-byte values, without first ensuring that the received buffer contains every field. A truncated report can therefore cause out-of-bounds reads and update hwmon state with unrelated data. The required length differs between supported devices and cannot be derived from the HID report descriptor. Define the minimum length needed by each asynchronous status format, select it while configuring the device, and reject reports that do not contain the last field accessed by that format. Keep the published length zero until probe configuration is complete, which also excludes legacy devices whose status is retrieved through feature reports. Suggested-by: Guenter Roeck Fixes: 0e35f63f7f4e ("hwmon: add driver for Aquacomputer D5 Next") Fixes: 19692f17cd13 ("hwmon: (aquacomputer_d5next) Add support for Aquacomputer Aquastream XT") Cc: stable@vger.kernel.org Signed-off-by: Jiale Yao Link: https://patch.msgid.link/20261021120408.48495-1-yaojiale02@163.com Signed-off-by: Guenter Roeck --- drivers/hwmon/aquacomputer_d5next.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/drivers/hwmon/aquacomputer_d5next.c b/drivers/hwmon/aquacomputer_d5next.c index 1ca70e726298..341cce4347e2 100644 --- a/drivers/hwmon/aquacomputer_d5next.c +++ b/drivers/hwmon/aquacomputer_d5next.c @@ -132,6 +132,7 @@ static u8 aquaero_secondary_ctrl_report[] = { #define AQUAERO_FAN_CURRENT_OFFSET 0x06 #define AQUAERO_FAN_POWER_OFFSET 0x08 #define AQUAERO_FAN_SPEED_OFFSET 0x00 +#define AQUAERO_MIN_STATUS_REPORT_SIZE (0x18B + AQUAERO_FAN_POWER_OFFSET + sizeof(u16)) static u16 aquaero_sensor_fan_offsets[] = { 0x167, 0x173, 0x17f, 0x18B }; /* Control report offsets for the Aquaero fan controllers */ @@ -155,6 +156,8 @@ static u16 aquaero_ctrl_fan_offsets[] = { 0x20c, 0x220, 0x234, 0x248 }; #define D5NEXT_5V_VOLTAGE 0x39 #define D5NEXT_12V_VOLTAGE 0x37 #define D5NEXT_VIRTUAL_SENSORS_START 0x3f +#define D5NEXT_MIN_STATUS_REPORT_SIZE (D5NEXT_PUMP_OFFSET + AQC_FAN_SPEED_OFFSET + \ + sizeof(u16)) static u16 d5next_sensor_fan_offsets[] = { D5NEXT_PUMP_OFFSET, D5NEXT_FAN_OFFSET }; /* Control report offsets for the D5 Next pump */ @@ -179,11 +182,14 @@ static u16 d5next_ctrl_fan_offsets[] = { 0x97, 0x42 }; /* Pump and fan speed (fr #define AQUASTREAMULT_FAN_CURRENT_OFFSET 0x00 #define AQUASTREAMULT_FAN_POWER_OFFSET 0x04 #define AQUASTREAMULT_FAN_SPEED_OFFSET 0x06 +#define AQUASTREAMULT_MIN_STATUS_REPORT_SIZE (AQUASTREAMULT_PRESSURE_OFFSET + sizeof(u16)) static u16 aquastreamult_sensor_fan_offsets[] = { AQUASTREAMULT_FAN_OFFSET }; /* Spec and sensor report offset for the Farbwerk RGB controller */ #define FARBWERK_NUM_SENSORS 4 #define FARBWERK_SENSOR_START 0x2f +#define FARBWERK_MIN_STATUS_REPORT_SIZE (FARBWERK_SENSOR_START + \ + FARBWERK_NUM_SENSORS * AQC_SENSOR_SIZE) /* Specs of the Farbwerk 360 RGB controller */ #define FARBWERK360_NUM_SENSORS 4 @@ -193,6 +199,8 @@ static u16 aquastreamult_sensor_fan_offsets[] = { AQUASTREAMULT_FAN_OFFSET }; /* Sensor report offsets for the Farbwerk 360 */ #define FARBWERK360_SENSOR_START 0x32 #define FARBWERK360_VIRTUAL_SENSORS_START 0x3a +#define FARBWERK360_MIN_STATUS_REPORT_SIZE (FARBWERK360_VIRTUAL_SENSORS_START + \ + FARBWERK360_NUM_VIRTUAL_SENSORS * AQC_SENSOR_SIZE) /* Control report offsets for the Farbwerk 360 */ #define FARBWERK360_TEMP_CTRL_OFFSET 0x8 @@ -209,6 +217,7 @@ static u16 aquastreamult_sensor_fan_offsets[] = { AQUASTREAMULT_FAN_OFFSET }; #define OCTO_SENSOR_START 0x3D #define OCTO_VIRTUAL_SENSORS_START 0x45 #define OCTO_FLOW_SENSOR_OFFSET 0x7B +#define OCTO_MIN_STATUS_REPORT_SIZE (0xD8 + AQC_FAN_SPEED_OFFSET + sizeof(u16)) static u16 octo_sensor_fan_offsets[] = { 0x7D, 0x8A, 0x97, 0xA4, 0xB1, 0xBE, 0xCB, 0xD8 }; /* Control report offsets for the Octo */ @@ -229,6 +238,7 @@ static u16 octo_ctrl_fan_offsets[] = { 0x5B, 0xB0, 0x105, 0x15A, 0x1AF, 0x204, 0 #define QUADRO_SENSOR_START 0x34 #define QUADRO_VIRTUAL_SENSORS_START 0x3c #define QUADRO_FLOW_SENSOR_OFFSET 0x6e +#define QUADRO_MIN_STATUS_REPORT_SIZE (0x97 + AQC_FAN_SPEED_OFFSET + sizeof(u16)) static u16 quadro_sensor_fan_offsets[] = { 0x70, 0x7D, 0x8A, 0x97 }; /* Control report offsets for the Quadro */ @@ -248,6 +258,7 @@ static u16 quadro_ctrl_fan_offsets[] = { 0x37, 0x8c, 0xe1, 0x136 }; /* Fan speed #define HIGHFLOWNEXT_CONDUCTIVITY 95 #define HIGHFLOWNEXT_5V_VOLTAGE 97 #define HIGHFLOWNEXT_5V_VOLTAGE_USB 99 +#define HIGHFLOWNEXT_MIN_STATUS_REPORT_SIZE (HIGHFLOWNEXT_5V_VOLTAGE_USB + sizeof(u16)) /* Specs of the Leakshield */ #define LEAKSHIELD_NUM_SENSORS 2 @@ -263,6 +274,7 @@ static u16 quadro_ctrl_fan_offsets[] = { 0x37, 0x8c, 0xe1, 0x136 }; /* Fan speed #define LEAKSHIELD_FLOW_IN 111 #define LEAKSHIELD_RESERVOIR_VOLUME 313 #define LEAKSHIELD_RESERVOIR_FILLED 311 +#define LEAKSHIELD_MIN_STATUS_REPORT_SIZE (LEAKSHIELD_RESERVOIR_VOLUME + sizeof(u16)) /* Specs of the Aquastream XT pump */ #define AQUASTREAMXT_SERIAL_START 0x3a @@ -554,6 +566,7 @@ struct aqc_data { const char *name; int status_report_id; /* Used for legacy devices, report is stored in buffer */ + u16 min_report_size; int ctrl_report_id; int secondary_ctrl_report_id; int secondary_ctrl_report_size; @@ -1333,6 +1346,8 @@ static int aqc_raw_event(struct hid_device *hdev, struct hid_report *report, u8 return 0; priv = hid_get_drvdata(hdev); + if (!priv->min_report_size || size < priv->min_report_size) + return 0; /* Info provided with every report */ priv->serial_number[0] = get_unaligned_be16(data + priv->serial_number_start_offset); @@ -1518,6 +1533,7 @@ static void aqc_debugfs_init(struct aqc_data *priv) static int aqc_probe(struct hid_device *hdev, const struct hid_device_id *id) { struct aqc_data *priv; + u16 min_report_size = 0; int ret; priv = devm_kzalloc(&hdev->dev, sizeof(*priv), GFP_KERNEL); @@ -1585,6 +1601,7 @@ static int aqc_probe(struct hid_device *hdev, const struct hid_device_id *id) priv->power_label = label_fan_power; priv->voltage_label = label_fan_voltage; priv->current_label = label_fan_current; + min_report_size = AQUAERO_MIN_STATUS_REPORT_SIZE; break; case USB_PRODUCT_ID_D5NEXT: priv->kind = d5next; @@ -1610,6 +1627,7 @@ static int aqc_probe(struct hid_device *hdev, const struct hid_device_id *id) priv->power_label = label_d5next_power; priv->voltage_label = label_d5next_voltages; priv->current_label = label_d5next_current; + min_report_size = D5NEXT_MIN_STATUS_REPORT_SIZE; break; case USB_PRODUCT_ID_FARBWERK: priv->kind = farbwerk; @@ -1620,6 +1638,7 @@ static int aqc_probe(struct hid_device *hdev, const struct hid_device_id *id) priv->temp_sensor_start_offset = FARBWERK_SENSOR_START; priv->temp_label = label_temp_sensors; + min_report_size = FARBWERK_MIN_STATUS_REPORT_SIZE; break; case USB_PRODUCT_ID_FARBWERK360: priv->kind = farbwerk360; @@ -1636,6 +1655,7 @@ static int aqc_probe(struct hid_device *hdev, const struct hid_device_id *id) priv->temp_label = label_temp_sensors; priv->virtual_temp_label = label_virtual_temp_sensors; + min_report_size = FARBWERK360_MIN_STATUS_REPORT_SIZE; break; case USB_PRODUCT_ID_OCTO: priv->kind = octo; @@ -1665,6 +1685,7 @@ static int aqc_probe(struct hid_device *hdev, const struct hid_device_id *id) priv->power_label = label_fan_power; priv->voltage_label = label_fan_voltage; priv->current_label = label_fan_current; + min_report_size = OCTO_MIN_STATUS_REPORT_SIZE; break; case USB_PRODUCT_ID_QUADRO: priv->kind = quadro; @@ -1694,6 +1715,7 @@ static int aqc_probe(struct hid_device *hdev, const struct hid_device_id *id) priv->power_label = label_fan_power; priv->voltage_label = label_fan_voltage; priv->current_label = label_fan_current; + min_report_size = QUADRO_MIN_STATUS_REPORT_SIZE; break; case USB_PRODUCT_ID_HIGHFLOWNEXT: priv->kind = highflownext; @@ -1711,6 +1733,7 @@ static int aqc_probe(struct hid_device *hdev, const struct hid_device_id *id) priv->speed_label = label_highflownext_fan_speed; priv->power_label = label_highflownext_power; priv->voltage_label = label_highflownext_voltage; + min_report_size = HIGHFLOWNEXT_MIN_STATUS_REPORT_SIZE; break; case USB_PRODUCT_ID_LEAKSHIELD: /* @@ -1730,6 +1753,7 @@ static int aqc_probe(struct hid_device *hdev, const struct hid_device_id *id) priv->temp_label = label_leakshield_temp_sensors; priv->speed_label = label_leakshield_fan_speed; + min_report_size = LEAKSHIELD_MIN_STATUS_REPORT_SIZE; break; case USB_PRODUCT_ID_AQUASTREAMXT: priv->kind = aquastreamxt; @@ -1760,6 +1784,7 @@ static int aqc_probe(struct hid_device *hdev, const struct hid_device_id *id) priv->power_label = label_aquastreamult_power; priv->voltage_label = label_aquastreamult_voltages; priv->current_label = label_aquastreamult_current; + min_report_size = AQUASTREAMULT_MIN_STATUS_REPORT_SIZE; break; case USB_PRODUCT_ID_POWERADJUST3: priv->kind = poweradjust3; @@ -1833,6 +1858,8 @@ static int aqc_probe(struct hid_device *hdev, const struct hid_device_id *id) break; } + priv->min_report_size = min_report_size; + if (priv->buffer_size != 0) { priv->checksum_start = 0x01; priv->checksum_length = priv->buffer_size - 3; From d4f272230f0b18b59fbbd1c4b5ef357aba01a4d2 Mon Sep 17 00:00:00 2001 From: Vincent Jardin Date: Thu, 1 Oct 2026 23:47:39 +0200 Subject: [PATCH 04/41] hwmon: (pmbus/core) Register the thermal sensors with a 0-based index Since commit 3aa74796cfd0 ("hwmon: (pmbus) Register with thermal for PSC_TEMPERATURE") pmbus_core registers each temperature input with the thermal core using its hwmon index as the sensor id. That index starts at 1, so temp1 is registered as sensor 1. The thermal core expects a 0-based id, which is what the hwmon core passes for the drivers that set HWMON_C_REGISTER_TZ: thermal_of matches a thermal-sensors phandle without cells as id 0, so a PMBus device with a single temperature input cannot use #thermal-sensor-cells = <0>. Cc: stable+noautosel@kernel.org # changes the DT-visible sensor id, no in-tree user Fixes: 3aa74796cfd0 ("hwmon: (pmbus) Register with thermal for PSC_TEMPERATURE") Signed-off-by: Vincent Jardin Link: https://patch.msgid.link/20261001-for-upstream-dt-mpq8785-thermal-sensor-cells-v3-1-bfafba9fcc95@free.fr Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/pmbus_core.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c index d69f1bde1795..af13408c62f9 100644 --- a/drivers/hwmon/pmbus/pmbus_core.c +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -1524,7 +1524,12 @@ static int pmbus_thermal_add_sensor(struct pmbus_data *pmbus_data, tdata->sensor = sensor; tdata->pmbus_data = pmbus_data; - tzd = devm_thermal_of_zone_register(dev, index, tdata, + /* + * The PMBus core numbers its temperature sensors from 1, as the + * hwmon tempN attributes, while the thermal core expects a 0-based + * sensor index. + */ + tzd = devm_thermal_of_zone_register(dev, index - 1, tdata, &pmbus_thermal_ops); /* * If CONFIG_THERMAL_OF is disabled, this returns -ENODEV, From 743b4481b6112d8ac11c165628daa8e0f0ecf78c Mon Sep 17 00:00:00 2001 From: Zac Lanzon Date: Mon, 28 Sep 2026 07:41:05 -0400 Subject: [PATCH 05/41] hwmon: (asus_rog_ryujin) Fix pump control source on the Ryujin III The RYUJIN III pump has a 4-pin cable for a motherboard fan header in addition to USB. Byte 2 of the set cooler duty command selects the pump control source: 0 hands the pump to its internal controller, which follows the PWM of the header its 4-pin cable is plugged into, and 1 makes it follow the pump duty in byte 3. Byte 3 of the cooler duty report returns the current source. The driver fills byte 2 with duty_channel, which is 1 for the RYUJIN III, and a pwm2 write re-sends the reported pump duty with the new fan duty. So writing only the internal fan duty takes the pump away from its internal controller. Measured on a RYUJIN III 360 (firmware AURJ2-S750-0108) with the pump cable on AIO_PUMP and the header PWM set through nct6775, after loading the driver with the pump on its internal controller (pump rpm): header 50% header 80% source no write yet 2160 3090 0 pwm2 = 40%, before this patch 1740 1740 1 pwm2 = 40%, with this patch 2130 3090 0 Send the reported source with pwm2 writes. The fan duty applies with either source (40%: 1545 to 1590 rpm, 30%: 870 rpm). A pwm2 write sent right after a pwm1 write that took the pump over, before the report could show the change, left the pump on USB and applied its fan duty (2 of 2 trials, and 7 of 7 with raw commands up to 0.9 s after). pwm1 writes keep selecting source 1. The cooler reports the pump duty of the last write, whichever source it selected, and 40% after power-on. Byte 9 of the status report follows the duty the pump runs at. Measured from power-on (PSU switched off) once per driver, with the header at 100%: before this patch with this patch source duty rpm source duty rpm no write yet 0 100 3570 0 100 3570 pwm2 = 40% 1 100 3570 0 100 3570 pwm1 = 40%, the reported duty 1 100 3570 1 40 1710 pwm1 = 60% 1 60 2430 pwm1 = 40% 1 40 1725 So a pwm1 write of the reported duty did not reach the pump, while other duties applied in one write, also when taking the pump from its internal controller (60%: 2460 rpm). When a pwm1 write requests the reported duty, write a duty 1% away first (below at 100%), then the requested one. Sent back to back, the two writes left the pump at the requested duty in all 19 trials, 9 with raw commands and 10 through this driver. Tested on the RYUJIN III 360 only. The EXTREME, EVA and WHITE EDITION use the same rog_ryujin_iii_info and send the same command, and Edward Blair's analysis of the WHITE EDITION firmware shows the same pump control source. The RYUJIN II, whose duty_channel is 0, is unchanged. Fixes: cd258aca0dc4 ("hwmon: (asus_rog_ryujin) Add ROG Ryujin III support") Closes: https://github.com/aleksamagicka/asus_rog_ryujin-hwmon/issues/12 Suggested-by: Edward Blair Cc: stable@vger.kernel.org Signed-off-by: Zac Lanzon Link: https://patch.msgid.link/7ca8fe6cd6b4125813613bd4187ff6cdda72e4b6.1790512964.git.lanzonza@gmail.com [groeck: Dropped obsolete 'Assisted-by: tag] Signed-off-by: Guenter Roeck --- Documentation/hwmon/asus_rog_ryujin.rst | 6 +++ drivers/hwmon/asus_rog_ryujin.c | 54 ++++++++++++++++++++++++- 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/Documentation/hwmon/asus_rog_ryujin.rst b/Documentation/hwmon/asus_rog_ryujin.rst index 3ebab230e8ca..fe072e94d87b 100644 --- a/Documentation/hwmon/asus_rog_ryujin.rst +++ b/Documentation/hwmon/asus_rog_ryujin.rst @@ -34,6 +34,12 @@ Usage notes As these are USB HIDs, the driver can be loaded automatically by the kernel and supports hot swapping. +The pump of the RYUJIN III also has a 4-pin cable for a motherboard fan header. +The cooler either hands the pump to its internal controller, which follows the +PWM of the header its 4-pin cable is plugged into, or runs it at the pump duty +set over USB. Writing pwm1 selects the duty set over USB. Writing pwm2 sets +only the internal fan duty and leaves the pump control as it is. + Sysfs entries ------------- diff --git a/drivers/hwmon/asus_rog_ryujin.c b/drivers/hwmon/asus_rog_ryujin.c index 5ced69e94ead..ba188e867934 100644 --- a/drivers/hwmon/asus_rog_ryujin.c +++ b/drivers/hwmon/asus_rog_ryujin.c @@ -50,6 +50,7 @@ static const struct rog_ryujin_device_info rog_ryujin_iii_info = { #define MAX_REPORT_LENGTH 65 /* Cooler duty report offsets */ +#define RYUJIN_PUMP_SOURCE 3 #define RYUJIN_PUMP_DUTY 4 #define RYUJIN_INTERNAL_FAN_DUTY 5 @@ -70,10 +71,15 @@ static const u8 get_cooler_duty_cmd[] = { RYUJIN_CMD_PREFIX, 0x9A }; static const u8 get_controller_speed_cmd[] = { RYUJIN_CMD_PREFIX, 0xA0 }; static const u8 get_controller_duty_cmd[] = { RYUJIN_CMD_PREFIX, 0xA1 }; +#define RYUJIN_SET_COOLER_CHANNEL_OFFSET 2 #define RYUJIN_SET_COOLER_PUMP_DUTY_OFFSET 3 #define RYUJIN_SET_COOLER_FAN_DUTY_OFFSET 4 static const u8 set_cooler_duty_cmd[] = { RYUJIN_CMD_PREFIX, 0x1A, 0x00, 0x00, 0x00 }; +/* Pump control sources in the set cooler duty command and the cooler duty report */ +#define RYUJIN_SOURCE_HEADER 0 +#define RYUJIN_SOURCE_USB 1 + #define RYUJIN_SET_CONTROLLER_FAN_DUTY_OFFSET 4 static const u8 set_controller_duty_cmd[] = { RYUJIN_CMD_PREFIX, 0x21, 0x00, 0x00, 0x00 }; @@ -117,6 +123,7 @@ struct rog_ryujin_data { s32 temp_input[1]; u16 speed_input[6]; /* Pump, internal fan and four controller fan speeds in RPM */ u8 duty_input[3]; /* Pump, internal fan and controller fan duty in PWM */ + u8 pump_source; /* Pump control source from the cooler duty report */ u8 *buffer; unsigned long updated; /* jiffies */ @@ -306,6 +313,44 @@ static int rog_ryujin_read_string(struct device *dev, enum hwmon_sensor_types ty return 0; } +/* + * On devices with a non-zero duty_channel (the RYUJIN III), the channel byte of + * the set cooler duty command selects the pump control source. + * RYUJIN_SOURCE_HEADER hands the pump to its internal controller, which follows + * the PWM of the header its 4-pin cable is plugged into. RYUJIN_SOURCE_USB + * makes the pump follow the pump duty in the command. The cooler duty report + * returns the current source. + * + * A fan duty write sends the reported source back, so that it leaves the pump + * control source as it is. A pump duty write selects USB. The cooler does not + * pass on a pump duty equal to the one it reports, and after power-on it + * reports 40% while a pump handed over at that duty runs at full speed. So + * when a pump duty write asks for the reported duty, write a duty 1% away + * first. + */ +static int rog_ryujin_select_pump_source(struct rog_ryujin_data *priv, u8 *set_cmd, + int channel) +{ + u8 duty = set_cmd[RYUJIN_SET_COOLER_PUMP_DUTY_OFFSET]; + int ret; + + if (channel == 1) { + set_cmd[RYUJIN_SET_COOLER_CHANNEL_OFFSET] = priv->pump_source; + return 0; + } + + set_cmd[RYUJIN_SET_COOLER_CHANNEL_OFFSET] = RYUJIN_SOURCE_USB; + + if (duty != rog_ryujin_pwm_to_percent(priv->duty_input[0])) + return 0; + + set_cmd[RYUJIN_SET_COOLER_PUMP_DUTY_OFFSET] = duty < 100 ? duty + 1 : duty - 1; + ret = rog_ryujin_execute_cmd(priv, set_cmd, SET_CMD_LENGTH, &priv->cooler_duty_set); + set_cmd[RYUJIN_SET_COOLER_PUMP_DUTY_OFFSET] = duty; + + return ret; +} + static int rog_ryujin_write_fixed_duty(struct rog_ryujin_data *priv, int channel, int val) { u8 set_cmd[SET_CMD_LENGTH]; @@ -323,7 +368,7 @@ static int rog_ryujin_write_fixed_duty(struct rog_ryujin_data *priv, int channel return ret; memcpy(set_cmd, set_cooler_duty_cmd, SET_CMD_LENGTH); - set_cmd[2] = priv->info->duty_channel; + set_cmd[RYUJIN_SET_COOLER_CHANNEL_OFFSET] = priv->info->duty_channel; /* Cooler duties are set as 0-100% */ val = rog_ryujin_pwm_to_percent(val); @@ -340,6 +385,12 @@ static int rog_ryujin_write_fixed_duty(struct rog_ryujin_data *priv, int channel set_cmd[RYUJIN_SET_COOLER_FAN_DUTY_OFFSET] = val; } + if (priv->info->duty_channel) { + ret = rog_ryujin_select_pump_source(priv, set_cmd, channel); + if (ret < 0) + return ret; + } + return rog_ryujin_execute_cmd(priv, set_cmd, SET_CMD_LENGTH, &priv->cooler_duty_set); } else { /* @@ -482,6 +533,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo goto unlock; } read_cooler_duty: + priv->pump_source = data[RYUJIN_PUMP_SOURCE]; priv->duty_input[0] = rog_ryujin_percent_to_pwm(data[RYUJIN_PUMP_DUTY]); priv->duty_input[1] = rog_ryujin_percent_to_pwm(data[RYUJIN_INTERNAL_FAN_DUTY]); From d1f60971fbf85c243ab6c78f6535fbc88fa640ce Mon Sep 17 00:00:00 2001 From: Alexei Solcanu Date: Sat, 26 Sep 2026 16:11:20 -0400 Subject: [PATCH 06/41] hwmon: (gpd-fan) Reset both fans to automatic on duo boards On boards using gpd_duo_drvdata (GPD Duo, GPD Win 5), writing pwm1_enable=2 only returns the first fan to EC control. The second fan keeps its last manual duty cycle, e.g. full speed after pwm1=255, so a fan control tool that exits leaves it running at that speed. gpd_duo_write_pwm() writes both fan registers (pwm_write and pwm_write + 1), but switching to automatic only clears pwm_write. Also clear pwm_write + 1, so both fans return to EC control. Tested on a GPD Win 5 (G1618-05). Not tested on the GPD Duo. Fixes: 0ab88e239439 ("hwmon: add GPD devices sensor driver") Cc: stable@vger.kernel.org Signed-off-by: Alexei Solcanu Reviewed-by: Antheas Kapenekakis Link: https://patch.msgid.link/20260926-gpd-fan-duo-auto-v2-1-0a2ea41ee3b5@pm.me [groeck: Dropped obsolete 'Assisted-by: tag] Signed-off-by: Guenter Roeck --- drivers/hwmon/gpd-fan.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hwmon/gpd-fan.c b/drivers/hwmon/gpd-fan.c index ed212615b2fe..b3b75f99f588 100644 --- a/drivers/hwmon/gpd-fan.c +++ b/drivers/hwmon/gpd-fan.c @@ -444,6 +444,7 @@ static void gpd_duo_set_pwm_enable(struct gpd_fan_data *data, enum FAN_PWM_ENABL break; case AUTOMATIC: gpd_ecram_write(data->drvdata, data->drvdata->pwm_write, 0); + gpd_ecram_write(data->drvdata, data->drvdata->pwm_write + 1, 0); break; } } From d0a03e1a2d9bd53b94fe6dff7a1a8058d25be82d Mon Sep 17 00:00:00 2001 From: Javier Carrasco Date: Wed, 14 Oct 2026 19:34:00 +0200 Subject: [PATCH 07/41] hwmon: chipcap2: fix command error diagnostics The command response field only contains bits 1:0, while the error flags are stored in bits 2:5. The existing diagnostic checks test the extracted response field, so they can never report an error flag. Test the full status byte for the error flags, and check the configuration-load flag instead of RAM parity for the last diagnostic. Fixes: 3af350929e75 ("hwmon: Add support for Amphenol ChipCap 2") Cc: stable@vger.kernel.org Signed-off-by: Javier Carrasco Link: https://patch.msgid.link/20261014-chipcap2_locks-v3-1-27ebfc2c5b9e@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/chipcap2.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/hwmon/chipcap2.c b/drivers/hwmon/chipcap2.c index 9bef767b589e..855057892852 100644 --- a/drivers/hwmon/chipcap2.c +++ b/drivers/hwmon/chipcap2.c @@ -167,13 +167,13 @@ static int cc2_cmd_response_diagnostic(struct device *dev, u8 status) case CC2_RESPONSE_BUSY: return -EBUSY; case CC2_RESPONSE_NACK: - if (resp & CC2_ERR_CORR_EEPROM) + if (status & CC2_ERR_CORR_EEPROM) dev_dbg(dev, "Command failed: corrected EEPROM\n"); - if (resp & CC2_ERR_UNCORR_EEPROM) + if (status & CC2_ERR_UNCORR_EEPROM) dev_dbg(dev, "Command failed: uncorrected EEPROM\n"); - if (resp & CC2_ERR_RAM_PARITY) + if (status & CC2_ERR_RAM_PARITY) dev_dbg(dev, "Command failed: RAM parity\n"); - if (resp & CC2_ERR_RAM_PARITY) + if (status & CC2_ERR_CONFIG_LOAD) dev_dbg(dev, "Command failed: configuration error\n"); return -ENODATA; default: From 4c0e65a4ed665c416ccb1524c58a646a580e491f Mon Sep 17 00:00:00 2001 From: Javier Carrasco Date: Wed, 14 Oct 2026 19:34:01 +0200 Subject: [PATCH 08/41] hwmon: chipcap2: fix off-by-one in command mode retry loop i == CC2_CM_RETRIES can never be true inside the loop, since it only runs while i < CC2_CM_RETRIES. Use CC2_CM_RETRIES - 1 so the last failed retry breaks out immediately instead of doing a useless disable + 80 ms power-cycle delay before the loop condition ends it anyway. Fixes: 3af350929e75 ("hwmon: Add support for Amphenol ChipCap 2") Cc: stable@vger.kernel.org Signed-off-by: Javier Carrasco Link: https://patch.msgid.link/20261014-chipcap2_locks-v3-2-27ebfc2c5b9e@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/chipcap2.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/chipcap2.c b/drivers/hwmon/chipcap2.c index 855057892852..8102b6851d3d 100644 --- a/drivers/hwmon/chipcap2.c +++ b/drivers/hwmon/chipcap2.c @@ -226,7 +226,7 @@ static int cc2_command_mode_start(struct cc2_data *data) 2 * CC2_RESP_START_CM_US); } ret = cc2_read_command_status(data->client); - if (ret != -ETIMEDOUT || i == CC2_CM_RETRIES) + if (ret != -ETIMEDOUT || i == CC2_CM_RETRIES - 1) break; /* command window missed, prepare for a retry */ From 36b07ddfe20b0fe06a8bc32c5e09974876fb5e42 Mon Sep 17 00:00:00 2001 From: Javier Carrasco Date: Wed, 14 Oct 2026 19:34:02 +0200 Subject: [PATCH 09/41] hwmon: chipcap2: fix be16 handling The driver currently only works correctly on little-endian architectures when writing 16-bit register values. Use i2c_smbus_write_word_swapped() for writes and get_unaligned_be16() for reads to correctly handle the ChipCap 2's big-endian data format on all architectures. Fixes: 3af350929e75 ("hwmon: Add support for Amphenol ChipCap 2") Cc: stable@vger.kernel.org Signed-off-by: Javier Carrasco Link: https://patch.msgid.link/20261014-chipcap2_locks-v3-3-27ebfc2c5b9e@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/chipcap2.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/hwmon/chipcap2.c b/drivers/hwmon/chipcap2.c index 8102b6851d3d..aec4adf98658 100644 --- a/drivers/hwmon/chipcap2.c +++ b/drivers/hwmon/chipcap2.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -260,8 +261,7 @@ static int cc2_write_reg(struct cc2_data *data, u8 reg, u16 val) if (ret < 0) goto disable; - cpu_to_be16s(&val); - ret = i2c_smbus_write_word_data(data->client, reg, val); + ret = i2c_smbus_write_word_swapped(data->client, reg, val); if (ret < 0) goto disable; @@ -311,7 +311,7 @@ static int cc2_read_reg(struct cc2_data *data, u8 reg, u16 *val) if (ret != CC2_EEPROM_DATA_LEN) return ret < 0 ? ret : -EIO; - *val = be16_to_cpup((__be16 *)&buf[1]); + *val = get_unaligned_be16(&buf[1]); return cc2_read_command_status(data->client); } @@ -351,10 +351,10 @@ static int cc2_data_fetch(struct i2c_client *client, switch (type) { case hwmon_humidity: - *val = cc2_rh_convert(be16_to_cpup((__be16 *)&data[0])); + *val = cc2_rh_convert(get_unaligned_be16(&data[0])); break; case hwmon_temp: - *val = cc2_temp_convert(be16_to_cpup((__be16 *)&data[2])); + *val = cc2_temp_convert(get_unaligned_be16(&data[2])); break; default: return -EINVAL; From b5a1412618f93b594f924cf4f3b4bb6b5dc49d41 Mon Sep 17 00:00:00 2001 From: Javier Carrasco Date: Wed, 14 Oct 2026 19:34:03 +0200 Subject: [PATCH 10/41] hwmon: chipcap2: defer probe when IRQs are unavailable This device can work with or without interrupts, but if they are provided and still not available, but if -EPROBE_DEFER is read, its value must be propagated to enable probe retries. Fixes: 3af350929e75 ("hwmon: Add support for Amphenol ChipCap 2") Cc: stable@vger.kernel.org Signed-off-by: Javier Carrasco Link: https://patch.msgid.link/20261014-chipcap2_locks-v3-4-27ebfc2c5b9e@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/chipcap2.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/hwmon/chipcap2.c b/drivers/hwmon/chipcap2.c index aec4adf98658..37dbf6a76fbc 100644 --- a/drivers/hwmon/chipcap2.c +++ b/drivers/hwmon/chipcap2.c @@ -636,6 +636,8 @@ static int cc2_request_ready_irq(struct cc2_data *data, struct device *dev) int ret = 0; data->irq_ready = fwnode_irq_get_byname(dev_fwnode(dev), "ready"); + if (data->irq_ready == -EPROBE_DEFER) + return data->irq_ready; if (data->irq_ready > 0) { init_completion(&data->complete); ret = devm_request_threaded_irq(dev, data->irq_ready, NULL, @@ -653,6 +655,8 @@ static int cc2_request_alarm_irqs(struct cc2_data *data, struct device *dev) int ret = 0; data->irq_low = fwnode_irq_get_byname(dev_fwnode(dev), "low"); + if (data->irq_low == -EPROBE_DEFER) + return data->irq_low; if (data->irq_low > 0) { ret = devm_request_threaded_irq(dev, data->irq_low, NULL, cc2_low_interrupt, @@ -666,6 +670,8 @@ static int cc2_request_alarm_irqs(struct cc2_data *data, struct device *dev) } data->irq_high = fwnode_irq_get_byname(dev_fwnode(dev), "high"); + if (data->irq_high == -EPROBE_DEFER) + return data->irq_high; if (data->irq_high > 0) { ret = devm_request_threaded_irq(dev, data->irq_high, NULL, cc2_high_interrupt, From d2a66c515d7730fd453485ffb306c412f1f46d72 Mon Sep 17 00:00:00 2001 From: Javier Carrasco Date: Wed, 14 Oct 2026 19:34:04 +0200 Subject: [PATCH 11/41] hwmon: chipcap2: fix IRQ teardown ordering The threaded IRQs are currently requested before the hwmon device is registered. Since devres releases resources in reverse order, the hwmon device is unregistered before the IRQs are freed during driver teardown. An IRQ may therefore still run after the hwmon device has been released and access the stale data->hwmon pointer. Separate IRQ discovery from IRQ registration so that the IRQ numbers and alarm visibility flags are initialized before registering the hwmon device while the IRQs are released before the hwmon device during devres cleanup. Fixes: 3af350929e75 ("hwmon: Add support for Amphenol ChipCap 2") Cc: stable@vger.kernel.org Signed-off-by: Javier Carrasco Link: https://patch.msgid.link/20261014-chipcap2_locks-v3-5-27ebfc2c5b9e@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/chipcap2.c | 54 ++++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/drivers/hwmon/chipcap2.c b/drivers/hwmon/chipcap2.c index 37dbf6a76fbc..21dc6db27c40 100644 --- a/drivers/hwmon/chipcap2.c +++ b/drivers/hwmon/chipcap2.c @@ -631,32 +631,47 @@ static int cc2_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, } } -static int cc2_request_ready_irq(struct cc2_data *data, struct device *dev) +static int cc2_get_irqs(struct cc2_data *data, struct device *dev) { - int ret = 0; - data->irq_ready = fwnode_irq_get_byname(dev_fwnode(dev), "ready"); if (data->irq_ready == -EPROBE_DEFER) return data->irq_ready; - if (data->irq_ready > 0) { + if (data->irq_ready > 0) init_completion(&data->complete); + + data->irq_low = fwnode_irq_get_byname(dev_fwnode(dev), "low"); + if (data->irq_low == -EPROBE_DEFER) + return data->irq_low; + if (data->irq_low > 0) + data->rh_alarm.low_alarm_visible = true; + + data->irq_high = fwnode_irq_get_byname(dev_fwnode(dev), "high"); + if (data->irq_high == -EPROBE_DEFER) + return data->irq_high; + if (data->irq_high > 0) + data->rh_alarm.high_alarm_visible = true; + + return 0; +} + +static int cc2_request_data_ready(struct cc2_data *data, struct device *dev) +{ + int ret = 0; + + if (data->irq_ready > 0) ret = devm_request_threaded_irq(dev, data->irq_ready, NULL, cc2_ready_interrupt, IRQF_ONESHOT | IRQF_TRIGGER_RISING, dev_name(dev), data); - } return ret; } -static int cc2_request_alarm_irqs(struct cc2_data *data, struct device *dev) +static int cc2_request_alarms(struct cc2_data *data, struct device *dev) { int ret = 0; - data->irq_low = fwnode_irq_get_byname(dev_fwnode(dev), "low"); - if (data->irq_low == -EPROBE_DEFER) - return data->irq_low; if (data->irq_low > 0) { ret = devm_request_threaded_irq(dev, data->irq_low, NULL, cc2_low_interrupt, @@ -665,13 +680,8 @@ static int cc2_request_alarm_irqs(struct cc2_data *data, struct device *dev) dev_name(dev), data); if (ret) return ret; - - data->rh_alarm.low_alarm_visible = true; } - data->irq_high = fwnode_irq_get_byname(dev_fwnode(dev), "high"); - if (data->irq_high == -EPROBE_DEFER) - return data->irq_high; if (data->irq_high > 0) { ret = devm_request_threaded_irq(dev, data->irq_high, NULL, cc2_high_interrupt, @@ -680,8 +690,6 @@ static int cc2_request_alarm_irqs(struct cc2_data *data, struct device *dev) dev_name(dev), data); if (ret) return ret; - - data->rh_alarm.high_alarm_visible = true; } return ret; @@ -732,14 +740,14 @@ static int cc2_probe(struct i2c_client *client) device_property_read_string(dev, "label", &data->label); - ret = cc2_request_ready_irq(data, dev); + ret = cc2_get_irqs(data, dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to get IRQs\n"); + + ret = cc2_request_data_ready(data, dev); if (ret) return dev_err_probe(dev, ret, "Failed to request ready irq\n"); - ret = cc2_request_alarm_irqs(data, dev); - if (ret) - return dev_err_probe(dev, ret, "Failed to request alarm irqs\n"); - data->hwmon = devm_hwmon_device_register_with_info(dev, client->name, data, &cc2_chip_info, NULL); @@ -747,6 +755,10 @@ static int cc2_probe(struct i2c_client *client) return dev_err_probe(dev, PTR_ERR(data->hwmon), "Failed to register hwmon device\n"); + ret = cc2_request_alarms(data, dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to request alarm irqs\n"); + return 0; } From 4724ac5556ce63f01a3f5d61fc7fcc20f4ddef3f Mon Sep 17 00:00:00 2001 From: Javier Carrasco Date: Wed, 14 Oct 2026 19:34:05 +0200 Subject: [PATCH 12/41] hwmon: chipcap2: disable device after hwmon teardown cc2_remove() disables the device while the device-managed hwmon device is still registered. A concurrent sysfs callback can then enable the device after it has been disabled during removal. Register a device-managed disable action before registering the hwmon device, so devres unregisters the hwmon device before disabling the physical device. Fixes: 3af350929e75 ("hwmon: Add support for Amphenol ChipCap 2") Cc: stable@vger.kernel.org Signed-off-by: Javier Carrasco Link: https://patch.msgid.link/20261014-chipcap2_locks-v3-6-27ebfc2c5b9e@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/chipcap2.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/drivers/hwmon/chipcap2.c b/drivers/hwmon/chipcap2.c index 21dc6db27c40..654f6420da7a 100644 --- a/drivers/hwmon/chipcap2.c +++ b/drivers/hwmon/chipcap2.c @@ -152,6 +152,11 @@ static void cc2_disable(struct cc2_data *data) } } +static void cc2_disable_action(void *data) +{ + cc2_disable(data); +} + static int cc2_cmd_response_diagnostic(struct device *dev, u8 status) { int resp; @@ -744,6 +749,10 @@ static int cc2_probe(struct i2c_client *client) if (ret) return dev_err_probe(dev, ret, "Failed to get IRQs\n"); + ret = devm_add_action_or_reset(dev, cc2_disable_action, data); + if (ret) + return ret; + ret = cc2_request_data_ready(data, dev); if (ret) return dev_err_probe(dev, ret, "Failed to request ready irq\n"); @@ -762,13 +771,6 @@ static int cc2_probe(struct i2c_client *client) return 0; } -static void cc2_remove(struct i2c_client *client) -{ - struct cc2_data *data = i2c_get_clientdata(client); - - cc2_disable(data); -} - static const struct i2c_device_id cc2_id[] = { { .name = "cc2d23" }, { .name = "cc2d23s" }, @@ -801,7 +803,6 @@ static struct i2c_driver cc2_driver = { .of_match_table = cc2_of_match, }, .probe = cc2_probe, - .remove = cc2_remove, .id_table = cc2_id, }; module_i2c_driver(cc2_driver); From d1ce827a9cc11ffdfec77b7fec9b73a8020a308d Mon Sep 17 00:00:00 2001 From: Javier Carrasco Date: Wed, 14 Oct 2026 19:34:06 +0200 Subject: [PATCH 13/41] hwmon: chipcap2: enable IRQ processing when regulator is already enabled cc2_enable() returns early when the regulator is already enabled. This can happen if a previous regulator_disable() failed or if the regulator was configured as always-on. In this case, process_irqs remains false, causing the ready interrupt to be ignored. Subsequent operations waiting for the ready completion then time out. Set process_irqs before returning when the regulator is already enabled, so that the driver continues to process ready interrupts. Fixes: 3af350929e75 ("hwmon: Add support for Amphenol ChipCap 2") Cc: stable@vger.kernel.org Signed-off-by: Javier Carrasco Link: https://patch.msgid.link/20261014-chipcap2_locks-v3-7-27ebfc2c5b9e@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/chipcap2.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/chipcap2.c b/drivers/hwmon/chipcap2.c index 654f6420da7a..837b1830f2e4 100644 --- a/drivers/hwmon/chipcap2.c +++ b/drivers/hwmon/chipcap2.c @@ -120,8 +120,10 @@ static int cc2_enable(struct cc2_data *data) int ret; /* exclusive regulator, check in case a disable failed */ - if (regulator_is_enabled(data->regulator)) + if (regulator_is_enabled(data->regulator)) { + data->process_irqs = true; return 0; + } /* clear any pending completion */ try_wait_for_completion(&data->complete); From 378add65fd7d352de9fe440d167fde5bf511b090 Mon Sep 17 00:00:00 2001 From: Javier Carrasco Date: Wed, 14 Oct 2026 19:34:07 +0200 Subject: [PATCH 14/41] hwmon: chipcap2: balance regulator state The current implementation does not guarantee that the calls to regulator_enable() and regulator_disable() are balanced. regulator_is_enabled() might return a negative value, and an always-on attribute in the DT might break the symmetry anyway. Add a regulator_enabled flag to keep balance within this regulator consumer. Fixes: 3af350929e75 ("hwmon: Add support for Amphenol ChipCap 2") Cc: stable@vger.kernel.org Signed-off-by: Javier Carrasco Link: https://patch.msgid.link/20261014-chipcap2_locks-v3-8-27ebfc2c5b9e@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/chipcap2.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/drivers/hwmon/chipcap2.c b/drivers/hwmon/chipcap2.c index 837b1830f2e4..952789fdb9db 100644 --- a/drivers/hwmon/chipcap2.c +++ b/drivers/hwmon/chipcap2.c @@ -91,6 +91,7 @@ struct cc2_data { int irq_low; int irq_high; bool process_irqs; + bool regulator_enabled; }; /* %RH as a per cent mille from a register value */ @@ -120,7 +121,7 @@ static int cc2_enable(struct cc2_data *data) int ret; /* exclusive regulator, check in case a disable failed */ - if (regulator_is_enabled(data->regulator)) { + if (data->regulator_enabled) { data->process_irqs = true; return 0; } @@ -132,6 +133,8 @@ static int cc2_enable(struct cc2_data *data) if (ret < 0) return ret; + data->regulator_enabled = true; + usleep_range(CC2_STARTUP_TIME_US, CC2_STARTUP_TIME_US + 125); data->process_irqs = true; @@ -147,11 +150,16 @@ static void cc2_disable(struct cc2_data *data) data->process_irqs = false; /* exclusive regulator, check in case an enable failed */ - if (regulator_is_enabled(data->regulator)) { - err = regulator_disable(data->regulator); - if (err) - dev_dbg(&data->client->dev, "Failed to disable device"); + if (!data->regulator_enabled) + return; + + err = regulator_disable(data->regulator); + if (err) { + dev_dbg(&data->client->dev, "Failed to disable device"); + return; } + + data->regulator_enabled = false; } static void cc2_disable_action(void *data) From 22aecf164e1c0c849808c342c65322d14f45176f Mon Sep 17 00:00:00 2001 From: Javier Carrasco Date: Wed, 14 Oct 2026 19:34:08 +0200 Subject: [PATCH 15/41] hwmon: chipcap2: do not discard pending alarms on disable cc2_disable() stops processing interrupts before the device is powered down to ignore the alarms caused by voltage toggling. The handler of an alarm raised by the last measurement might not have run at that point, and the alarm is discarded when it finally does. The sysfs read that triggered the measurement can then report no alarm although the device raised one. Wait for the alarm handlers that have already been raised before the interrupt processing is stopped. This covers the case where the handler thread has not been scheduled yet; an alarm that has not reached the interrupt core at that point is discarded. Fixes: 3af350929e75 ("hwmon: Add support for Amphenol ChipCap 2") Cc: stable@vger.kernel.org Signed-off-by: Javier Carrasco Link: https://patch.msgid.link/20261014-chipcap2_locks-v3-9-27ebfc2c5b9e@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/chipcap2.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/hwmon/chipcap2.c b/drivers/hwmon/chipcap2.c index 952789fdb9db..eba6469940ab 100644 --- a/drivers/hwmon/chipcap2.c +++ b/drivers/hwmon/chipcap2.c @@ -146,6 +146,13 @@ static void cc2_disable(struct cc2_data *data) { int err; + /* let the alarms already raised by the last measurement be processed */ + if (data->irq_low > 0) + synchronize_irq(data->irq_low); + + if (data->irq_high > 0) + synchronize_irq(data->irq_high); + /* ignore alarms triggered by voltage toggling when powering up */ data->process_irqs = false; From bed5f0c075eb99442fca6a61c009a32752eceda2 Mon Sep 17 00:00:00 2001 From: Javier Carrasco Date: Wed, 14 Oct 2026 19:34:09 +0200 Subject: [PATCH 16/41] hwmon: chipcap2: serialize access to the alarm indicators low_alarm and high_alarm are accessed from the threaded interrupt handlers and from sysfs without any locking, which can lead to lost alarms. Add a mutex to serialize the access to the alarm indicators and to process_irqs, which the handlers use to decide whether an interrupt belongs to an ongoing operation. Fixes: 3af350929e75 ("hwmon: Add support for Amphenol ChipCap 2") Cc: stable@vger.kernel.org Signed-off-by: Javier Carrasco Link: https://patch.msgid.link/20261014-chipcap2_locks-v3-10-27ebfc2c5b9e@gmail.com [groeck: Dropped obsolete 'Assisted-by: tag] Signed-off-by: Guenter Roeck --- drivers/hwmon/chipcap2.c | 58 +++++++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/drivers/hwmon/chipcap2.c b/drivers/hwmon/chipcap2.c index eba6469940ab..b3b725cc7224 100644 --- a/drivers/hwmon/chipcap2.c +++ b/drivers/hwmon/chipcap2.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -91,6 +92,8 @@ struct cc2_data { int irq_low; int irq_high; bool process_irqs; + /* protects process_irqs and the alarm indicators */ + struct mutex irq_lock; bool regulator_enabled; }; @@ -122,7 +125,8 @@ static int cc2_enable(struct cc2_data *data) /* exclusive regulator, check in case a disable failed */ if (data->regulator_enabled) { - data->process_irqs = true; + scoped_guard(mutex, &data->irq_lock) + data->process_irqs = true; return 0; } @@ -137,7 +141,8 @@ static int cc2_enable(struct cc2_data *data) usleep_range(CC2_STARTUP_TIME_US, CC2_STARTUP_TIME_US + 125); - data->process_irqs = true; + scoped_guard(mutex, &data->irq_lock) + data->process_irqs = true; return 0; } @@ -154,7 +159,8 @@ static void cc2_disable(struct cc2_data *data) synchronize_irq(data->irq_high); /* ignore alarms triggered by voltage toggling when powering up */ - data->process_irqs = false; + scoped_guard(mutex, &data->irq_lock) + data->process_irqs = false; /* exclusive regulator, check in case an enable failed */ if (!data->regulator_enabled) @@ -514,10 +520,12 @@ static irqreturn_t cc2_low_interrupt(int irq, void *data) { struct cc2_data *cc2 = data; - if (cc2->process_irqs) { - hwmon_notify_event(cc2->hwmon, hwmon_humidity, - hwmon_humidity_min_alarm, 0); - cc2->rh_alarm.low_alarm = true; + scoped_guard(mutex, &cc2->irq_lock) { + if (cc2->process_irqs) { + hwmon_notify_event(cc2->hwmon, hwmon_humidity, + hwmon_humidity_min_alarm, 0); + cc2->rh_alarm.low_alarm = true; + } } return IRQ_HANDLED; @@ -527,10 +535,12 @@ static irqreturn_t cc2_high_interrupt(int irq, void *data) { struct cc2_data *cc2 = data; - if (cc2->process_irqs) { - hwmon_notify_event(cc2->hwmon, hwmon_humidity, - hwmon_humidity_max_alarm, 0); - cc2->rh_alarm.high_alarm = true; + scoped_guard(mutex, &cc2->irq_lock) { + if (cc2->process_irqs) { + hwmon_notify_event(cc2->hwmon, hwmon_humidity, + hwmon_humidity_max_alarm, 0); + cc2->rh_alarm.high_alarm = true; + } } return IRQ_HANDLED; @@ -546,11 +556,13 @@ static int cc2_humidity_min_alarm_status(struct cc2_data *data, long *val) if (ret < 0) return ret; - if (data->rh_alarm.low_alarm) { - *val = (measurement < min_hyst) ? 1 : 0; - data->rh_alarm.low_alarm = *val; - } else { - *val = 0; + scoped_guard(mutex, &data->irq_lock) { + if (data->rh_alarm.low_alarm) { + *val = (measurement < min_hyst) ? 1 : 0; + data->rh_alarm.low_alarm = *val; + } else { + *val = 0; + } } return 0; @@ -566,11 +578,13 @@ static int cc2_humidity_max_alarm_status(struct cc2_data *data, long *val) if (ret < 0) return ret; - if (data->rh_alarm.high_alarm) { - *val = (measurement > max_hyst) ? 1 : 0; - data->rh_alarm.high_alarm = *val; - } else { - *val = 0; + scoped_guard(mutex, &data->irq_lock) { + if (data->rh_alarm.high_alarm) { + *val = (measurement > max_hyst) ? 1 : 0; + data->rh_alarm.high_alarm = *val; + } else { + *val = 0; + } } return 0; @@ -753,6 +767,8 @@ static int cc2_probe(struct i2c_client *client) i2c_set_clientdata(client, data); + mutex_init(&data->irq_lock); + data->client = client; data->regulator = devm_regulator_get_exclusive(dev, "vdd"); From 8c02722be2caa6604a837b81faeaba78250eda4a Mon Sep 17 00:00:00 2001 From: Javier Carrasco Date: Wed, 14 Oct 2026 19:34:10 +0200 Subject: [PATCH 17/41] hwmon: chipcap2: synchronize ready IRQ state The ready IRQ handler can race with reinit_completion() in the enable path. In particular, a handler from an earlier operation can complete the completion after it is reset for a new operation. Use irq_lock to serialize access to the completion and process_irqs. Disable IRQ processing and synchronize any running handler before resetting the completion for a new operation. Fixes: 3af350929e75 ("hwmon: Add support for Amphenol ChipCap 2") Cc: stable@vger.kernel.org Signed-off-by: Javier Carrasco Link: https://patch.msgid.link/20261014-chipcap2_locks-v3-11-27ebfc2c5b9e@gmail.com [groeck: Dropped obsolete 'Assisted-by: tag] Signed-off-by: Guenter Roeck --- drivers/hwmon/chipcap2.c | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/drivers/hwmon/chipcap2.c b/drivers/hwmon/chipcap2.c index b3b725cc7224..00f62a33d463 100644 --- a/drivers/hwmon/chipcap2.c +++ b/drivers/hwmon/chipcap2.c @@ -123,6 +123,16 @@ static int cc2_enable(struct cc2_data *data) { int ret; + if (data->irq_ready > 0) { + scoped_guard(mutex, &data->irq_lock) + data->process_irqs = false; + + synchronize_irq(data->irq_ready); + + scoped_guard(mutex, &data->irq_lock) + reinit_completion(&data->complete); + } + /* exclusive regulator, check in case a disable failed */ if (data->regulator_enabled) { scoped_guard(mutex, &data->irq_lock) @@ -130,9 +140,6 @@ static int cc2_enable(struct cc2_data *data) return 0; } - /* clear any pending completion */ - try_wait_for_completion(&data->complete); - ret = regulator_enable(data->regulator); if (ret < 0) return ret; @@ -162,6 +169,9 @@ static void cc2_disable(struct cc2_data *data) scoped_guard(mutex, &data->irq_lock) data->process_irqs = false; + if (data->irq_ready > 0) + synchronize_irq(data->irq_ready); + /* exclusive regulator, check in case an enable failed */ if (!data->regulator_enabled) return; @@ -510,8 +520,10 @@ static irqreturn_t cc2_ready_interrupt(int irq, void *data) { struct cc2_data *cc2 = data; - if (cc2->process_irqs) - complete(&cc2->complete); + scoped_guard(mutex, &cc2->irq_lock) { + if (cc2->process_irqs) + complete(&cc2->complete); + } return IRQ_HANDLED; } From b7743c6dbe3ab94045164ef298f65cfb9b0b450a Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Thu, 29 Oct 2026 23:57:16 -0700 Subject: [PATCH 18/41] hwmon: (aquacomputer_d5next) Check maxcollection when probing AQUAERO devices When a USB HID device matching USB_PRODUCT_ID_AQUAERO presents a malformed report descriptor, an out-of-bounds array access occurs in aqc_probe() due to an unchecked index into the parsed collections array. During device initialization, the driver distinguishes between the multiple logical interfaces exposed by the Aquaero device by evaluating the type of the second element in the collection array. The driver unconditionally accesses hdev->collection[1] without verifying that hdev->maxcollection is greater than 1. Because the collection array is dynamically allocated by the HID core based on the number of collections found in the report descriptor, a crafted device exposing fewer than two collections causes an immediate heap out-of-bounds read or NULL pointer dereference. This leads to a kernel panic during the device probe phase. Add the missing check to fix the problem. Fixes: 2c55211104b4 ("hwmon: (aquacomputer_d5next) Support sensors for Aquacomputer Aquaero") Cc: stable@vger.kernel.org Cc: Aleksa Savic Cc: Jack Doan Signed-off-by: Guenter Roeck --- drivers/hwmon/aquacomputer_d5next.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/aquacomputer_d5next.c b/drivers/hwmon/aquacomputer_d5next.c index 341cce4347e2..6565731b0e71 100644 --- a/drivers/hwmon/aquacomputer_d5next.c +++ b/drivers/hwmon/aquacomputer_d5next.c @@ -1570,7 +1570,7 @@ static int aqc_probe(struct hid_device *hdev, const struct hid_device_id *id) * they present. The two other devices have the type of the second element in * their respective collections set to 1, while the real device has it set to 0. */ - if (hdev->collection[1].type != 0) { + if (hdev->maxcollection < 2 || hdev->collection[1].type != 0) { ret = -ENODEV; goto fail_and_close; } From 59d586c9e47d1205563bc9b5a5d443adc3ba5bc5 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Thu, 29 Oct 2026 23:49:04 -0700 Subject: [PATCH 19/41] hwmon: (asus_wmi_sensors) Avoid global struct mutation In the asus_wmi_sensors driver, device-managed memory allocated in asus_wmi_configure_sensor_setup() is assigned to a global static structure, leading to a potential use-after-free when the device is unbound. The driver defines a single global asus_wmi_chip_info structure to register with the hwmon core. During probe, it dynamically allocates the .info channel array using devm_kcalloc() and assigns it directly to this global structure. When a device is unbound, its device-managed memory is automatically freed, leaving asus_wmi_chip_info.info as a dangling pointer. Furthermore, if multiple WMI devices are probed sequentially, each probe overwrites the same global pointer, forcing all registered hwmon instances to reference the channel information of the most recently probed device. While it is unlikely if not impossible that multiple WMI devices of the same kind exist, modifying a global static data structure is undesirable. Remove the static data structure and allocate it as part of struct asus_wmi_sensors to avoid the potential problem. Fixes: 548820e21ce1 ("hwmon: (asus_wmi_sensors) Support X370 Asus WMI.") Cc: stable@vger.kernel.org Cc: Ed Brindley Cc: Denis Pauk Signed-off-by: Guenter Roeck --- drivers/hwmon/asus_wmi_sensors.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/drivers/hwmon/asus_wmi_sensors.c b/drivers/hwmon/asus_wmi_sensors.c index 621e3506972d..0feab3f62496 100644 --- a/drivers/hwmon/asus_wmi_sensors.c +++ b/drivers/hwmon/asus_wmi_sensors.c @@ -164,6 +164,7 @@ struct asus_wmi_wmi_info { struct asus_wmi_sensors { struct asus_wmi_wmi_info wmi; + struct hwmon_chip_info chip_info; /* lock access to internal cache */ struct mutex lock; }; @@ -496,11 +497,6 @@ static const struct hwmon_ops asus_wmi_hwmon_ops = { .read_string = asus_wmi_hwmon_read_string, }; -static struct hwmon_chip_info asus_wmi_chip_info = { - .ops = &asus_wmi_hwmon_ops, - .info = NULL, -}; - static int asus_wmi_configure_sensor_setup(struct device *dev, struct asus_wmi_sensors *sensor_data) { @@ -508,7 +504,7 @@ static int asus_wmi_configure_sensor_setup(struct device *dev, struct hwmon_channel_info *asus_wmi_hwmon_chan; int nr_count[hwmon_max] = {}, nr_types = 0; struct asus_wmi_sensor_info *temp_sensor; - const struct hwmon_chip_info *chip_info; + struct hwmon_chip_info *chip_info; enum hwmon_sensor_types type; struct device *hwdev; int i, idx; @@ -549,8 +545,9 @@ static int asus_wmi_configure_sensor_setup(struct device *dev, if (!ptr_asus_wmi_ci) return -ENOMEM; - asus_wmi_chip_info.info = ptr_asus_wmi_ci; - chip_info = &asus_wmi_chip_info; + chip_info = &sensor_data->chip_info; + chip_info->ops = &asus_wmi_hwmon_ops; + chip_info->info = ptr_asus_wmi_ci; sensor_data->wmi.info_by_id = devm_kcalloc(dev, sensor_data->wmi.sensor_count, sizeof(*sensor_data->wmi.info_by_id), From dc992efeace99e479aec7a8f0c49a9493c729606 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 30 Oct 2026 08:41:11 -0700 Subject: [PATCH 20/41] hwmon: (hp-wmi-sensors) Avoid global struct mutation In the HP WMI sensors driver, make_chip_info() assigns a device-managed memory allocation to a static global structure, resulting in a use-after-free when the device unbinds. During hp_wmi_sensors_probe(), make_chip_info() dynamically allocates a hwmon_channel_info array using devm_kcalloc() and assigns it to the static global hp_wmi_chip_info.info pointer. If multiple instances probe sequentially or concurrently, the shared global pointer is overwritten by each new device. When any device unbinds, its devm-managed memory is automatically freed, leaving the global hp_wmi_chip_info struct and any other active device instances with a dangling pointer. Subsequent accesses by the hwmon core (such as thermal zone reads) will dereference this freed memory, causing a use-after-free. While this is not supposed to happen in the real world (there should only be one instance of the driver in any system), modifying global structures is undesirable and should be avoided whenever possible. Allocate struct hwmon_chip_info as part of struct hp_wmi_sensors instead and initialize it in make_chip_info() to avoid mutating the global structure. Fixes: 23902f98f8d4 ("hwmon: add HP WMI Sensors driver") Cc: stable@vger.kernel.org Cc: James Seo Acked-by: James Seo --- drivers/hwmon/hp-wmi-sensors.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/drivers/hwmon/hp-wmi-sensors.c b/drivers/hwmon/hp-wmi-sensors.c index d8bcfeb8d5b2..a859752c8387 100644 --- a/drivers/hwmon/hp-wmi-sensors.c +++ b/drivers/hwmon/hp-wmi-sensors.c @@ -390,6 +390,7 @@ struct hp_wmi_info { */ struct hp_wmi_sensors { struct wmi_device *wdev; + struct hwmon_chip_info chip_info; struct hp_wmi_info **info_map[hwmon_max]; u8 channel_count[hwmon_max]; bool has_intrusion; @@ -1531,11 +1532,6 @@ static const struct hwmon_ops hp_wmi_hwmon_ops = { .write = hp_wmi_hwmon_write, }; -static struct hwmon_chip_info hp_wmi_chip_info = { - .ops = &hp_wmi_hwmon_ops, - .info = NULL, -}; - static struct hp_wmi_info *match_fan_event(struct hp_wmi_sensors *state, const char *event_description) { @@ -1962,7 +1958,8 @@ static int make_chip_info(struct hp_wmi_sensors *state, bool has_events) if (!ptr_channel_info) return -ENOMEM; - hp_wmi_chip_info.info = ptr_channel_info; + state->chip_info.ops = &hp_wmi_hwmon_ops; + state->chip_info.info = ptr_channel_info; for (type = hwmon_chip; type < hwmon_max; type++) { count = channel_count[type]; @@ -2053,7 +2050,7 @@ static int hp_wmi_sensors_init(struct hp_wmi_sensors *state) return err; hwdev = devm_hwmon_device_register_with_info(dev, "hp_wmi_sensors", - state, &hp_wmi_chip_info, + state, &state->chip_info, NULL); return PTR_ERR_OR_ZERO(hwdev); } From 185039b1c0389b70ec52a6d3e3c1494e2d0e23da Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 30 Oct 2026 10:31:20 -0700 Subject: [PATCH 21/41] hwmon: (hp-wmi-sensors) Fix unbounded loops in scale_numeric_sensor() In scale_numeric_sensor() in the hp-wmi-sensors hwmon driver, an unbounded loop occurs if the ACPI WMI implementation provides a highly anomalous or malformed UnitModifier value for a numeric sensor. The UnitModifier property is extracted from a WMI ACPI object without bounds checking and cast to a signed 32-bit integer. This value directly controls the iteration count of two scaling loops. If the provided unit_modifier is extremely negative (e.g., INT_MIN), the first loop executes billions of times before reaching target_modifier. Conversely, if unit_modifier is extremely large (e.g., INT_MAX) and the current sensor reading (val) is exactly 0, the second loop's overflow safeguard (val > LONG_MAX / 10) never evaluates to true, resulting in billions of unnecessary iterations. This prolonged execution causes severe CPU stalls and thread soft lockups during driver initialization or whenever a user-space process reads the sensor attribute via sysfs, resulting in a denial of service on the affected CPU core. Fix the problem in the first loop by exiting it as soon as val is zero. Fix the problem with the second loop by only entering it if val is not zero. Also add a check for val < LONG_MIN / 10 and exit the loop if that is the case. Fixes: 23902f98f8d4 ("hwmon: add HP WMI Sensors driver") Cc: stable@vger.kernel.org Cc: James Seo Acked-by: James Seo Signed-off-by: Guenter Roeck --- drivers/hwmon/hp-wmi-sensors.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/drivers/hwmon/hp-wmi-sensors.c b/drivers/hwmon/hp-wmi-sensors.c index a859752c8387..1953d672edaf 100644 --- a/drivers/hwmon/hp-wmi-sensors.c +++ b/drivers/hwmon/hp-wmi-sensors.c @@ -739,15 +739,21 @@ static long scale_numeric_sensor(const struct hp_wmi_numeric_sensor *nsensor) val = current_reading; - for (; unit_modifier < target_modifier; unit_modifier++) + for (; val && unit_modifier < target_modifier; unit_modifier++) val = DIV_ROUND_CLOSEST(val, 10); - for (; unit_modifier > target_modifier; unit_modifier--) { - if (val > LONG_MAX / 10) { - val = LONG_MAX; - break; + if (val) { + for (; unit_modifier > target_modifier; unit_modifier--) { + if (val > LONG_MAX / 10) { + val = LONG_MAX; + break; + } + if (val < LONG_MIN / 10) { + val = LONG_MIN; + break; + } + val *= 10; } - val *= 10; } if (sensor_type == HP_WMI_TYPE_TEMPERATURE) { From 5b51ff0887b9f88d042b92dd07fcdda4132f0ebc Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 30 Oct 2026 10:40:36 -0700 Subject: [PATCH 22/41] hwmon: (lm87) Fix drvdata type confusion in vrm_show() and vrm_store() When an LM87 or ADM1024 device is probed with VID support enabled, reading from or writing to the vrm sysfs attribute triggers a driver data type confusion in vrm_show() and vrm_store() in drivers/hwmon/lm87.c. In lm87_probe(), the driver stores the struct lm87_data pointer on the I2C client via i2c_set_clientdata() and passes the struct i2c_client pointer as the drvdata argument to devm_hwmon_device_register_with_groups(), which attaches the attribute groups to the registered hwmon device with client as its driver_data. Unlike the other sysfs callbacks that retrieve client via dev_get_drvdata() and then obtain data via i2c_get_clientdata(), vrm_show() and vrm_store() assign dev_get_drvdata(dev) directly to struct lm87_data *. Dereferencing data->vrm therefore accesses the struct i2c_client instance at offsetof(struct lm87_data, vrm), causing vrm_show() to leak an unrelated byte from struct i2c_client to userspace and vrm_store() to corrupt a byte within struct i2c_client while failing to update the actual VRM value. Fix the problem by assigning the return value from dev_get_drvdata(dev) to struct i2c_client *client and using i2c_get_clientdata() to get the actual client data. Fixes: 00a0c905ab4b ("hwmon: (lm87) Use hwmon to create the sysfs groups") Cc: stable@vger.kernel.org Signed-off-by: Guenter Roeck --- drivers/hwmon/lm87.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/lm87.c b/drivers/hwmon/lm87.c index c09074f44708..bb4e15b66355 100644 --- a/drivers/hwmon/lm87.c +++ b/drivers/hwmon/lm87.c @@ -606,13 +606,16 @@ static DEVICE_ATTR_RO(cpu0_vid); static ssize_t vrm_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct lm87_data *data = dev_get_drvdata(dev); + struct i2c_client *client = dev_get_drvdata(dev); + struct lm87_data *data = i2c_get_clientdata(client); + return sprintf(buf, "%d\n", data->vrm); } static ssize_t vrm_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - struct lm87_data *data = dev_get_drvdata(dev); + struct i2c_client *client = dev_get_drvdata(dev); + struct lm87_data *data = i2c_get_clientdata(client); unsigned long val; int err; From c4a01c2a80b203ab5f80885f6994346fe217f19c Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 30 Oct 2026 07:44:57 -0700 Subject: [PATCH 23/41] hwmon: (nct6683) Fix incorrect struct cast in show_pwm() and store_pwm() On big-endian architectures, reading or writing the pwm[1-8] sysfs attributes in the nct6683 hardware monitoring driver always redirects to channel 0 due to an incorrect struct downcast in show_pwm() and store_pwm(). The driver defines the PWM sysfs attributes using SENSOR_TEMPLATE(), which initializes them as struct sensor_device_attribute (where the index is a 32-bit int). However, the sysfs callbacks show_pwm() and store_pwm() incorrectly downcast the attribute to a struct sensor_device_attribute_2 (where the index is an 8-bit u8). Because both structs start with struct device_attribute, container_of() resolves to the same base address. On big-endian architectures, reading the 8-bit index field from the memory layout of a 32-bit int fetches the most significant byte (which is 0), rather than the intended channel number. This type mismatch causes all PWM reads to return data for channel 0, and any writes intended to configure other channels will silently overwrite the duty cycle of PWM channel 0 instead. Fix the type casts in show_pwm() and store_pwm() to solve the problem. Fixes: 41082d66bfd6 ("hwmon: Driver for NCT6683D") Cc: stable@vger.kernel.org Signed-off-by: Guenter Roeck --- drivers/hwmon/nct6683.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/nct6683.c b/drivers/hwmon/nct6683.c index e1c36c95affb..f34cb62305be 100644 --- a/drivers/hwmon/nct6683.c +++ b/drivers/hwmon/nct6683.c @@ -920,7 +920,7 @@ static ssize_t show_pwm(struct device *dev, struct device_attribute *attr, char *buf) { struct nct6683_data *data = nct6683_update_device(dev); - struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); int index = sattr->index; return sprintf(buf, "%d\n", data->pwm[index]); @@ -930,7 +930,7 @@ static ssize_t store_pwm(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); struct nct6683_data *data = dev_get_drvdata(dev); int index = sattr->index; unsigned long val; From 61bc4514b8e99e9733c04de028885f6fc906d628 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 30 Oct 2026 11:22:58 -0700 Subject: [PATCH 24/41] hwmon: (peci/dimmtemp) Fix devres leak and UAF on concurrent unbind When a peci_dimmtemp device is unbound concurrently with its delayed initialization work (detect_work), newly allocated devm resources and the hwmon device are leaked, leading to a use-after-free when reading the lingering hwmon sysfs attributes. In peci_dimmtemp_probe(), detect_work is registered for cancellation via devm_delayed_work_autocancel(). During driver detach, devres_release_all() first moves all existing entries from dev->devres_head onto a local todo list before invoking their release callbacks in reverse order. If create_dimm_temp_info_delayed() is already running when devm_delayed_work_drop() calls cancel_delayed_work_sync(), create_dimm_temp_info() can allocate dimmtemp_label strings via devm_kzalloc()/devm_kasprintf() and register the hwmon device via devm_hwmon_device_register_with_info(), appending new devres entries to the now-empty dev->devres_head. Once the work finishes, devres_release_all() frees priv and priv->name from the local todo list without re-evaluating dev->devres_head, leaving the late devres allocations and registered hwmon device behind with a dangling priv pointer. Cancel detect_work synchronously in a driver .remove() callback instead of relying on devm_delayed_work_autocancel() so that the work is fully drained before devres_release_all() detaches dev->devres_head, allowing any devm resources registered by the delayed work to be released in proper reverse order. Also only schedule detect_work when check_populated_dimms() returns -EAGAIN so that probe failure paths do not leave delayed work pending. Fixes: 73bc1b885dae ("hwmon: peci: Add dimmtemp driver") Cc: stable@vger.kernel.org Cc: Iwona Winiarska Reviewed-by: Iwona Winiarska Signed-off-by: Guenter Roeck --- drivers/hwmon/peci/dimmtemp.c | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/drivers/hwmon/peci/dimmtemp.c b/drivers/hwmon/peci/dimmtemp.c index bd3e8715dfec..617a4cc0f65c 100644 --- a/drivers/hwmon/peci/dimmtemp.c +++ b/drivers/hwmon/peci/dimmtemp.c @@ -4,7 +4,6 @@ #include #include #include -#include #include #include #include @@ -326,11 +325,13 @@ static int create_dimm_temp_info(struct peci_dimmtemp *priv) if (ret == -ENODEV) { dev_dbg(priv->dev, "No DIMMs found\n"); return 0; - } else if (ret) { + } + if (ret == -EAGAIN) { schedule_delayed_work(&priv->detect_work, DIMM_MASK_CHECK_DELAY_JIFFIES); dev_dbg(priv->dev, "Deferred populating DIMM temp info\n"); - return ret; } + if (ret) + return ret; channels = priv->gen_info->chan_rank_max * priv->gen_info->dimm_idx_max; @@ -398,10 +399,8 @@ static int peci_dimmtemp_probe(struct auxiliary_device *adev, const struct auxil "Unexpected PECI revision %#x, some features may be unavailable\n", peci_dev->info.peci_revision); - ret = devm_delayed_work_autocancel(priv->dev, &priv->detect_work, - create_dimm_temp_info_delayed); - if (ret) - return ret; + auxiliary_set_drvdata(adev, priv); + INIT_DELAYED_WORK(&priv->detect_work, create_dimm_temp_info_delayed); ret = create_dimm_temp_info(priv); if (ret && ret != -EAGAIN) { @@ -412,6 +411,13 @@ static int peci_dimmtemp_probe(struct auxiliary_device *adev, const struct auxil return 0; } +static void peci_dimmtemp_remove(struct auxiliary_device *adev) +{ + struct peci_dimmtemp *priv = auxiliary_get_drvdata(adev); + + cancel_delayed_work_sync(&priv->detect_work); +} + static int read_thresholds_hsx(struct peci_dimmtemp *priv, int dimm_order, int chan_rank, u32 *data) { @@ -665,6 +671,7 @@ MODULE_DEVICE_TABLE(auxiliary, peci_dimmtemp_ids); static struct auxiliary_driver peci_dimmtemp_driver = { .probe = peci_dimmtemp_probe, + .remove = peci_dimmtemp_remove, .id_table = peci_dimmtemp_ids, }; From a5802c587da97a4ff85c04aad6043fa26640fc9b Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Thu, 29 Oct 2026 23:23:58 -0700 Subject: [PATCH 25/41] hwmon: (pmbus/q54sj108a2) Add missing NULL pointer check When CONFIG_DEBUG_FS is enabled and per-client PMBus debugfs directory creation fails during pmbus_do_probe(), q54sj108a2_probe() passes the NULL pointer returned by pmbus_get_debugfs_dir() to debugfs_create_dir() without validation, creating its debugfs directory at the debugfs root instead of under the client directory. If pmbus_init_debugfs() fails to create the client directory, data->debugfs is left as NULL or an ERR_PTR, which pmbus_get_debugfs_dir() converts to NULL. Passing a NULL parent to debugfs_create_dir() places q54sj108a2_dir in the root of the debugfs filesystem and populates it with attribute files whose private data pointers reference psu->debugfs_entries. Because this root-level directory is outside the client debugfs hierarchy, PMBus core cleanup does not remove it on device unbind, leaking the debugfs entries and causing a use-after-free of the devm-allocated struct q54sj108a2_data if the orphaned files are accessed via q54sj108a2_debugfs_read() or q54sj108a2_debugfs_write() after device removal. Fix the problem by adding the missing NULL pointer check. Fixes: d014538aa385 ("hwmon: (pmbus) Driver for Delta power supplies Q54SJ108A2") Cc: stable@vger.kernel.org Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/q54sj108a2.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/hwmon/pmbus/q54sj108a2.c b/drivers/hwmon/pmbus/q54sj108a2.c index ecac405d7536..b018a611d9b6 100644 --- a/drivers/hwmon/pmbus/q54sj108a2.c +++ b/drivers/hwmon/pmbus/q54sj108a2.c @@ -351,6 +351,8 @@ static int q54sj108a2_probe(struct i2c_client *client) psu->client = client; debugfs = pmbus_get_debugfs_dir(client); + if (!debugfs) + return 0; q54sj108a2_dir = debugfs_create_dir(client->name, debugfs); From 11dfbd835b17b75f7cc7a4a969f33ff702b1b081 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Thu, 29 Oct 2026 23:34:04 -0700 Subject: [PATCH 26/41] hwmon: (pmbus) Check return value from pmbus_thermal_add_sensor() During PMBus device probe, if registering a temperature sensor with the thermal subsystem fails, pmbus_add_sensor() silently ignores the error and returns success. When a valid temperature sensor is detected (class PSC_TEMPERATURE and type "input"), pmbus_add_sensor() invokes pmbus_thermal_add_sensor() to hook it into the kernel's thermal framework. However, the return value of this call is completely discarded. If thermal registration fails, such as from an -ENOMEM allocation failure or an invalid device tree configuration like a malformed #thermal-sensor-cells property, no error is propagated. Because the hardware monitor probe falsely succeeds despite the failure to register the thermal zone, the system is left without thermal mitigation for the affected PMBus device. This silently blinds the system to actual PMIC temperatures, preventing the execution of necessary cooling mechanisms or emergency shutdowns and risking localized overheating. Pass the return value from pmbus_thermal_add_sensor() to the calling code to fix the problem. To enable passing the actual error, change the error return from pmbus_add_sensor() from NULL to ERR_PTR() and extract the error from in the calling code. Fixes: 3aa74796cfd0 ("hwmon: (pmbus) Register with thermal for PSC_TEMPERATURE") Cc: stable@vger.kernel.org Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/pmbus_core.c | 42 ++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c index af13408c62f9..9bccbf41c884 100644 --- a/drivers/hwmon/pmbus/pmbus_core.c +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -1551,10 +1551,11 @@ static struct pmbus_sensor *pmbus_add_sensor(struct pmbus_data *data, { struct sensor_device_attribute *a; struct pmbus_sensor *sensor; + int err; sensor = devm_kzalloc(data->dev, sizeof(*sensor), GFP_KERNEL); if (!sensor) - return NULL; + return ERR_PTR(-ENOMEM); a = &sensor->attribute; if (type) @@ -1579,14 +1580,17 @@ static struct pmbus_sensor *pmbus_add_sensor(struct pmbus_data *data, pmbus_set_sensor, -1); if (pmbus_add_attribute(data, &a->dev_attr.attr)) - return NULL; + return ERR_PTR(-ENOMEM); sensor->next = data->sensors; data->sensors = sensor; /* temperature sensors with _input values are registered with thermal */ - if (class == PSC_TEMPERATURE && strcmp(type, "input") == 0) - pmbus_thermal_add_sensor(data, sensor, seq); + if (class == PSC_TEMPERATURE && strcmp(type, "input") == 0) { + err = pmbus_thermal_add_sensor(data, sensor, seq); + if (err) + return ERR_PTR(err); + } return sensor; } @@ -1687,8 +1691,8 @@ static int pmbus_add_limit_attrs(struct i2c_client *client, page, 0xff, l->reg, attr->class, attr->update || l->update, l->readonly, l->writeonly, true); - if (!curr) - return -ENOMEM; + if (IS_ERR(curr)) + return PTR_ERR(curr); if (l->sbit && (info->func[page] & attr->sfunc)) { ret = pmbus_add_boolean(data, name, l->alarm, index, @@ -1727,8 +1731,8 @@ static int pmbus_add_sensor_attrs_one(struct i2c_client *client, } base = pmbus_add_sensor(data, name, "input", index, page, phase, attr->reg, attr->class, true, true, false, true); - if (!base) - return -ENOMEM; + if (IS_ERR(base)) + return PTR_ERR(base); /* No limit and alarm attributes for phase specific sensors */ if (attr->sfunc && phase == 0xff) { ret = pmbus_add_limit_attrs(client, data, info, name, @@ -2401,8 +2405,8 @@ static int pmbus_add_fan_ctrl(struct i2c_client *client, 0xff, PMBUS_VIRT_FAN_TARGET_1 + id, PSC_FAN, false, false, false, true); - if (!sensor) - return -ENOMEM; + if (IS_ERR(sensor)) + return PTR_ERR(sensor); if (!((data->info->func[page] & PMBUS_HAVE_PWM12) || (data->info->func[page] & PMBUS_HAVE_PWM34))) @@ -2412,15 +2416,15 @@ static int pmbus_add_fan_ctrl(struct i2c_client *client, 0xff, PMBUS_VIRT_PWM_1 + id, PSC_PWM, false, false, false, true); - if (!sensor) - return -ENOMEM; + if (IS_ERR(sensor)) + return PTR_ERR(sensor); sensor = pmbus_add_sensor(data, "pwm", "enable", index, page, 0xff, PMBUS_VIRT_PWM_ENABLE_1 + id, PSC_PWM, true, false, false, false); - if (!sensor) - return -ENOMEM; + if (IS_ERR(sensor)) + return PTR_ERR(sensor); return 0; } @@ -2437,6 +2441,7 @@ static int pmbus_add_fan_attributes(struct i2c_client *client, int f; for (f = 0; f < ARRAY_SIZE(pmbus_fan_registers); f++) { + struct pmbus_sensor *sensor; int regval; if (!(info->func[page] & pmbus_fan_flags[f])) @@ -2457,10 +2462,11 @@ static int pmbus_add_fan_attributes(struct i2c_client *client, (!(regval & (PB_FAN_1_INSTALLED >> ((f & 1) * 4))))) continue; - if (pmbus_add_sensor(data, "fan", "input", index, - page, 0xff, pmbus_fan_registers[f], - PSC_FAN, true, true, false, true) == NULL) - return -ENOMEM; + sensor = pmbus_add_sensor(data, "fan", "input", index, + page, 0xff, pmbus_fan_registers[f], + PSC_FAN, true, true, false, true); + if (IS_ERR(sensor)) + return PTR_ERR(sensor); /* Fan control */ if (pmbus_check_word_register(client, page, From 9aa6cc563e881c3cad96ea36926cadceee151f32 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 30 Oct 2026 07:34:41 -0700 Subject: [PATCH 27/41] hwmon: (pmbus) Release devres allocated memory after devm_thermal_of_zone_register error In pmbus_thermal_add_sensor(), a devres memory leak occurs when a PMBus temperature sensor is initialized but no thermal zone is configured for it. The function allocates a pmbus_thermal_data structure using devm_kzalloc(). It then attempts to register the sensor with the thermal subsystem via devm_thermal_of_zone_register(). If this registration returns -ENODEV (e.g., when no thermal zone is mapped in the Device Tree or CONFIG_THERMAL_OF is disabled), the function intentionally catches the error and returns 0 to allow device probing to continue. Because devm_kfree() is not called on the -ENODEV path, the unused tdata structure remains allocated on the device's managed resource list. Since most PMBus temperature sensors are not attached to thermal zones, this results in an accumulation of unused objects that needlessly consume kernel memory for the lifetime of the PMBus device. Add the missing call to devm_kfree() to fix the problem. Fixes: 3aa74796cfd0 ("hwmon: (pmbus) Register with thermal for PSC_TEMPERATURE") Cc: stable@vger.kernel.org Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/pmbus_core.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c index 9bccbf41c884..574d9879bdcf 100644 --- a/drivers/hwmon/pmbus/pmbus_core.c +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -1535,8 +1535,10 @@ static int pmbus_thermal_add_sensor(struct pmbus_data *pmbus_data, * If CONFIG_THERMAL_OF is disabled, this returns -ENODEV, * so ignore that error but forward any other error. */ - if (IS_ERR(tzd) && (PTR_ERR(tzd) != -ENODEV)) + if (IS_ERR(tzd) && (PTR_ERR(tzd) != -ENODEV)) { + devm_kfree(dev, tdata); return PTR_ERR(tzd); + } return 0; } From 4fc79634af8cdf734e0a633923ba4817e30d29ad Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 30 Oct 2026 09:22:52 -0700 Subject: [PATCH 28/41] hwmon: (pmbus) Fix return value check from thermal zone registration The PMBus core used to call devm_thermal_zone_of_sensor_register() to register thermal zones. Commit 613ed3f676092 ("hwmon: pm_bus: core: Switch to new of thermal API") changed this to call devm_thermal_of_zone_register() instead. The code subsequently checks if the call failed because the thermal subsystem is not configured. devm_thermal_zone_of_sensor_register() used to return -ENODEV in this case. devm_thermal_of_zone_register() returns -ENOTSUPP. The code currently still checks for -ENOVEV. This results in driver instantiation failures if the thermal subsystem is disabled. Check for -ENOTSUPP to fix the problem. Keep checking for -ENODEV because this indicates that no thermal zone is configured for the sensor, which is acceptable for the driver and must not cause a registration failure. Fixes: 613ed3f676092 ("hwmon: pm_bus: core: Switch to new of thermal API") Cc: stable@vger.kernel.org Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/pmbus_core.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c index 574d9879bdcf..8fa379d04cd1 100644 --- a/drivers/hwmon/pmbus/pmbus_core.c +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -1532,10 +1532,11 @@ static int pmbus_thermal_add_sensor(struct pmbus_data *pmbus_data, tzd = devm_thermal_of_zone_register(dev, index - 1, tdata, &pmbus_thermal_ops); /* - * If CONFIG_THERMAL_OF is disabled, this returns -ENODEV, - * so ignore that error but forward any other error. + * If CONFIG_THERMAL_OF is disabled, devm_thermal_of_zone_register() + * returns -ENOTSUPP. If no thermal zone is configured for the sensor, + * it returns -ENODEV. Ignore those errors but forward any other error. */ - if (IS_ERR(tzd) && (PTR_ERR(tzd) != -ENODEV)) { + if (IS_ERR(tzd) && PTR_ERR(tzd) != -ENODEV && PTR_ERR(tzd) != -ENOTSUPP) { devm_kfree(dev, tdata); return PTR_ERR(tzd); } From 3897309ede38d7f3ff34c4c4583b1dd345666988 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 30 Oct 2026 18:59:26 -0700 Subject: [PATCH 29/41] hwmon: (lm75) Fix setting polarity mask for chips with 16-bit config register When probing a 16-bit compatible temperature sensor configured with the "ti,alert-polarity-active-high" device property, lm75_generic_probe() writes to a reserved bit instead of the alert polarity bit due to an incorrect bit shift in the 16-bit polarity mask. On 16-bit sensors, the polarity bit is physically located at bit 2 of the MSByte. When accessed via i2c_smbus_read_word_data() or i2c_smbus_write_word_data(), the SMBus protocol maps the first received byte (the physical MSByte) to the lower 8 bits of the integer representation. Consequently, the polarity bit is consistently mapped to bit 2, identically to 8-bit sensors. However, the LM75_ALERT_POLARITY_HIGH_16_BIT macro applies an 8-bit left shift, erroneously targeting bit 10. This causes the driver to silently corrupt a reserved bit in the configuration register while leaving the active-high alert polarity unconfigured, breaking the polarity feature. Use BIT(2) as polarity mask in the configuration register for all chips to fix the problem. Reported-by: Sashiko Fixes: 1be4001d9b9b ("hwmon: (lm75) Support active-high alert polarity") Cc: stable@vger.kernel.org Signed-off-by: Guenter Roeck --- drivers/hwmon/lm75.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/drivers/hwmon/lm75.c b/drivers/hwmon/lm75.c index 2d2d752aeac9..917ac250a648 100644 --- a/drivers/hwmon/lm75.c +++ b/drivers/hwmon/lm75.c @@ -123,8 +123,7 @@ struct lm75_data { static const u8 lm75_sample_set_masks[] = { 0 << 5, 1 << 5, 2 << 5, 3 << 5 }; -#define LM75_ALERT_POLARITY_HIGH_8_BIT (BIT(2)) -#define LM75_ALERT_POLARITY_HIGH_16_BIT (BIT(2) << 8) +#define LM75_ALERT_POLARITY_HIGH_BIT BIT(2) #define LM75_SAMPLE_CLEAR_MASK (3 << 5) /* The structure below stores the configuration values of the supported devices. @@ -770,11 +769,7 @@ static int lm75_generic_probe(struct device *dev, const char *name, data->orig_conf = status; /* Enforce polarity active-low (default) or active-high (devicetree) */ - if (!data->params->config_reg_16bits) - pol_mask = LM75_ALERT_POLARITY_HIGH_8_BIT; - else - pol_mask = LM75_ALERT_POLARITY_HIGH_16_BIT; - + pol_mask = LM75_ALERT_POLARITY_HIGH_BIT; clr_mask = data->params->clr_mask | pol_mask; set_mask = data->params->set_mask & ~pol_mask; if (device_property_read_bool(dev, "ti,alert-polarity-active-high")) From 7cd60538073e465310c94b2a2f0ea8d2f03768ef Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 30 Oct 2026 20:02:08 -0700 Subject: [PATCH 30/41] hwmon: (tmp108) Fix initial conversion delay and wait until ready MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In tmp108_common_probe(), the driver only adds the 30 ms conversion delay (TMP108_CONVERSION_TIME_MS) to ready_time if the sensor was previously in TMP108_MODE_SHUTDOWN. However, the TMP108 hardware defaults to continuous conversion mode (TMP108_MODE_CONTINUOUS) upon power-on reset, including immediately after enabling the vcc supply regulator during probe, while the temperature register retains its power-on reset value of 0x0000 (0°C) until the first conversion completes. Because the initial mode is not shutdown, the delay is bypassed and early reads via thermal zone registration or sysfs before the first conversion finishes return an uninitialized 0°C reading. In addition, returning -EAGAIN from tmp108_read() while waiting for a conversion to complete after probe or resume can trigger an unnecessary tight retry loop in callers or fail initial reads. Unconditionally set ready_time to TMP108_CONVERSION_TIME_MS ahead in tmp108_common_probe(), track ready_time as ktime_t, and wait for the remaining conversion time in tmp108_read() using usleep_range() instead of returning -EAGAIN. Reported-by: Sashiko Fixes: 66e1c9171339 ("hwmon: Add Texas Instruments TMP108 temperature sensor driver.") Cc: stable@vger.kernel.org Signed-off-by: Guenter Roeck --- drivers/hwmon/tmp108.c | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/drivers/hwmon/tmp108.c b/drivers/hwmon/tmp108.c index 91d20a1ce112..862b944d3e60 100644 --- a/drivers/hwmon/tmp108.c +++ b/drivers/hwmon/tmp108.c @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include #include #include @@ -78,7 +78,7 @@ struct tmp108 { struct regmap *regmap; u16 orig_config; - u64 ready_time; + ktime_t ready_time; const struct tmp108_params *params; }; @@ -121,6 +121,7 @@ static int tmp108_read(struct device *dev, enum hwmon_sensor_types type, struct tmp108 *tmp108 = dev_get_drvdata(dev); unsigned int regval; int err, hyst; + s64 delay; if (type == hwmon_chip) { if (attr == hwmon_chip_update_interval) { @@ -137,12 +138,10 @@ static int tmp108_read(struct device *dev, enum hwmon_sensor_types type, switch (attr) { case hwmon_temp_input: - /* Is it too early to return a conversion ? */ - if (time_before64(get_jiffies_64(), tmp108->ready_time)) { - dev_dbg(dev, "%s: Conversion not ready yet..\n", - __func__); - return -EAGAIN; - } + /* Wait if it is too early to return a conversion */ + delay = ktime_us_delta(tmp108->ready_time, ktime_get()); + if (delay > 0) + usleep_range(delay, delay + 1000); err = regmap_read(tmp108->regmap, TMP108_REG_TEMP, ®val); if (err < 0) return err; @@ -477,11 +476,8 @@ static int tmp108_common_probe(struct device *dev, struct regmap *regmap, char * return err; } - tmp108->ready_time = get_jiffies_64(); - if ((tmp108->orig_config & TMP108_CONF_MODE_MASK) == - TMP108_MODE_SHUTDOWN) - tmp108->ready_time += - msecs_to_jiffies(TMP108_CONVERSION_TIME_MS); + tmp108->ready_time = ktime_add_ms(ktime_get(), + TMP108_CONVERSION_TIME_MS); err = devm_add_action_or_reset(dev, tmp108_restore_config, tmp108); if (err) { @@ -528,8 +524,8 @@ static int tmp108_resume(struct device *dev) err = regmap_update_bits(tmp108->regmap, TMP108_REG_CONF, TMP108_CONF_MODE_MASK, TMP108_MODE_CONTINUOUS); - tmp108->ready_time = get_jiffies_64() + - msecs_to_jiffies(TMP108_CONVERSION_TIME_MS); + tmp108->ready_time = ktime_add_ms(ktime_get(), + TMP108_CONVERSION_TIME_MS); return err; } From f324179e6ccd05dfdc8e98fba2e9d50aea8e7ea2 Mon Sep 17 00:00:00 2001 From: "Baek Kyu, Kim" Date: Sat, 31 Oct 2026 18:50:39 +0900 Subject: [PATCH 31/41] hwmon: (gpio-fan) Update the speed cache before dropping the PM reference set_fan_speed() writes the GPIO control value before dropping the runtime PM reference when stopping the fan. pm_runtime_put_sync() decrements the usage count even when suspending the device fails, but the error return currently leaves speed_index at the previous running speed. A request for that same running speed then returns success without changing the GPIOs or taking a runtime PM reference. Retrying the stop instead drops a reference the driver no longer owns and can trigger a runtime PM usage count underflow warning. Update speed_index after writing the GPIOs and before dropping the PM reference. The initial same-speed check already excludes a stopped fan, so a zero target is sufficient to identify the transition that needs a PM put. Preserve the PM error return and the CONFIG_PM=n exception. This keeps the cached control state and PM reference ownership consistent without restoring running GPIOs after a possibly partial supply failure. It does not clear persistent runtime PM errors or claim that a failing regulator has successfully powered off. Fixes: 0d01110e6356 ("hwmon: (gpio-fan) Add regulator support") Cc: stable@vger.kernel.org Signed-off-by: Baek Kyu, Kim Link: https://patch.msgid.link/20261031095039.11683-1-pocj8ur4in@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/gpio-fan.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/hwmon/gpio-fan.c b/drivers/hwmon/gpio-fan.c index 3f78375eeb44..08b58b49be5b 100644 --- a/drivers/hwmon/gpio-fan.c +++ b/drivers/hwmon/gpio-fan.c @@ -150,7 +150,10 @@ static int set_fan_speed(struct gpio_fan_data *fan_data, int speed_index) __set_fan_ctrl(fan_data, fan_data->speed[speed_index].ctrl_val); - if (fan_data->speed_index > 0 && speed_index == 0) { + /* The GPIOs have changed, and a PM put drops the reference on error. */ + fan_data->speed_index = speed_index; + + if (speed_index == 0) { int ret; ret = pm_runtime_put_sync(fan_data->dev); @@ -158,8 +161,6 @@ static int set_fan_speed(struct gpio_fan_data *fan_data, int speed_index) return ret; } - fan_data->speed_index = speed_index; - return 0; } From 0f9c7ed3b5009a037e5533eac03a0d4d45ecf1f0 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sat, 31 Oct 2026 03:08:08 +0000 Subject: [PATCH 32/41] hwmon: (coretemp) Check for NULL platform device in hotplug callbacks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit coretemp_get_pdev() returns NULL when topology_logical_die_id(cpu) is negative (such as -ENODEV or -ERANGE from topology_get_logical_id() on a firmware/hardware APIC ID mismatch) or exceeds max_zones (for example, under asymmetric or anomalous ACPI die topologies where topology_max_packages() * topology_max_dies_per_package() is smaller than the logical die count). Commit 6d03bbff456b ("hwmon: (coretemp) Simplify platform device handling") pre-allocated zone_devices[0 .. max_zones - 1] during module init and dropped the NULL checks on pdev in coretemp_cpu_online() and coretemp_cpu_offline(), passing pdev unconditionally to platform_get_drvdata(). When coretemp_get_pdev() returns NULL, platform_get_drvdata(pdev) dereferences &pdev->dev and triggers a kernel NULL pointer dereference. Check for a NULL pdev and return 0 in both coretemp_cpu_online() and coretemp_cpu_offline() before calling platform_get_drvdata(). Returning 0 in coretemp_cpu_online() ignores the unmapped CPU without blocking CPU bringup or tearing down valid thermal zones on other dies. Reported-by: Sashiko Fixes: 6d03bbff456b ("hwmon: (coretemp) Simplify platform device handling") Cc: stable@vger.kernel.org Tested-by: Asaf Nuri Bertizlioğlu Signed-off-by: Guenter Roeck --- drivers/hwmon/coretemp.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/hwmon/coretemp.c b/drivers/hwmon/coretemp.c index 3836beade59f..eb74ecd12310 100644 --- a/drivers/hwmon/coretemp.c +++ b/drivers/hwmon/coretemp.c @@ -695,6 +695,9 @@ static int coretemp_cpu_online(unsigned int cpu) if (!housekeeping_cpu(cpu, HK_TYPE_MISC)) return 0; + if (!pdev) + return 0; + pdata = platform_get_drvdata(pdev); if (!pdata->hwmon_dev) { struct device *hwmon; @@ -746,6 +749,9 @@ static int coretemp_cpu_offline(unsigned int cpu) return 0; /* If the physical CPU device does not exist, just return */ + if (!pdev) + return 0; + pd = platform_get_drvdata(pdev); if (!pd->hwmon_dev) return 0; From 95638f1f05ced75a82e83f610cd01b636d2a21b1 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sat, 31 Oct 2026 03:08:32 +0000 Subject: [PATCH 33/41] hwmon: (coretemp) Set nr_cores only after core_data allocation succeeds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In init_temp_data(), pdata->nr_cores is populated from topology_num_cores_per_package() before allocating pdata->core_data. If that allocation fails, init_temp_data() returns NULL with pdata->core_data still NULL and pdata->nr_cores set to a positive value. coretemp_add_core() logs the error without failing coretemp_cpu_online(), so the CPU still comes online with pdata->hwmon_dev registered. When the CPU is later taken offline (or the module is unloaded), coretemp_cpu_offline() calls get_temp_data(pd, cpu), which loops up to pdata->nr_cores and dereferences the NULL pdata->core_data pointer. Store the core count in a local variable and assign pdata->nr_cores only after pdata->core_data is allocated, preserving the invariant that pdata->nr_cores is zero whenever pdata->core_data is NULL. Reported-by: Sashiko Fixes: 1a793caf6f69 ("hwmon: (coretemp) Use dynamic allocated memory for core temp_data") Cc: stable@vger.kernel.org Tested-by: Asaf Nuri Bertizlioğlu Signed-off-by: Guenter Roeck --- drivers/hwmon/coretemp.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/hwmon/coretemp.c b/drivers/hwmon/coretemp.c index eb74ecd12310..7fced0e2abeb 100644 --- a/drivers/hwmon/coretemp.c +++ b/drivers/hwmon/coretemp.c @@ -491,11 +491,12 @@ init_temp_data(struct platform_data *pdata, unsigned int cpu, int pkg_flag) struct temp_data *tdata; if (!pdata->core_data) { - pdata->nr_cores = topology_num_cores_per_package(); - pdata->core_data = kzalloc_objs(struct temp_data *, - pdata->nr_cores); + int nr_cores = topology_num_cores_per_package(); + + pdata->core_data = kzalloc_objs(struct temp_data *, nr_cores); if (!pdata->core_data) return NULL; + pdata->nr_cores = nr_cores; } tdata = kzalloc_obj(struct temp_data); From 9a6aa085c94c7f2817c4d50a7264e6fb51286f40 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sat, 31 Oct 2026 03:09:14 +0000 Subject: [PATCH 34/41] hwmon: (coretemp) Fix 32-bit jiffies wraparound in show_temp() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit show_temp() rate-limits MSR reads by storing tdata->last_updated as an unsigned long and comparing it against jiffies using time_after(jiffies, tdata->last_updated + HZ). time_after() relies on signed long subtraction, which only produces the expected result while the two timestamps are within LONG_MAX jiffies of each other. On 32-bit kernels, if tempX_input is not read for more than LONG_MAX jiffies (~24.85 days at HZ=1000), the signed difference flips sign and time_after() evaluates to false. Because tdata->last_updated is only refreshed when time_after() succeeds, subsequent reads remain blocked from refreshing for another ~24.85 days until jiffies wraps past tdata->last_updated + HZ again, returning either a stale cached temperature or -ENODATA if the sensor had never been read since initialization. Store tdata->last_updated as a u64 and use get_jiffies_64() with time_after64(), which does not wrap in any practical uptime. Reported-by: Sashiko Fixes: bebe467823c0 ("hwmon: New coretemp driver") Cc: stable@vger.kernel.org Tested-by: Asaf Nuri Bertizlioğlu Signed-off-by: Guenter Roeck --- drivers/hwmon/coretemp.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/drivers/hwmon/coretemp.c b/drivers/hwmon/coretemp.c index 7fced0e2abeb..96d57fe12de6 100644 --- a/drivers/hwmon/coretemp.c +++ b/drivers/hwmon/coretemp.c @@ -76,7 +76,7 @@ struct temp_data { int temp; bool temp_valid; int tjmax; - unsigned long last_updated; + u64 last_updated; unsigned int cpu; int index; u32 cpu_core_id; @@ -407,7 +407,7 @@ static ssize_t show_temp(struct device *dev, mutex_lock(&tdata->update_lock); /* Check whether the time interval has elapsed */ - if (time_after(jiffies, tdata->last_updated + HZ)) { + if (time_after64(get_jiffies_64(), tdata->last_updated + HZ)) { tjmax = get_tjmax(tdata, dev); rdmsrq_on_cpu(tdata->cpu, tdata->status_reg, &val.q); /* @@ -417,7 +417,7 @@ static ssize_t show_temp(struct device *dev, */ tdata->temp_valid = (val.l >> 31) & 1; tdata->temp = tjmax - ((val.l >> 16) & 0xff) * 1000; - tdata->last_updated = jiffies; + tdata->last_updated = get_jiffies_64(); } valid = tdata->temp_valid; @@ -521,11 +521,8 @@ init_temp_data(struct platform_data *pdata, unsigned int cpu, int pkg_flag) tdata->cpu = cpu; tdata->cpu_core_id = topology_core_id(cpu); tdata->attr_size = MAX_CORE_ATTRS; - /* - * A zero timestamp does not look stale on 32-bit, where jiffies - * starts just short of wrapping. Backdate it instead. - */ - tdata->last_updated = jiffies - HZ - 1; + /* Backdate to force a refresh on the first read */ + tdata->last_updated = get_jiffies_64() - HZ - 1; mutex_init(&tdata->update_lock); return tdata; } From 1a30ea0be623b7443edb6adc79e07632418cefdc Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sat, 31 Oct 2026 18:42:37 -0700 Subject: [PATCH 35/41] hwmon: (sht4x) Convert update_interval to jiffies in pending data check In sht4x_read_values(), the check for whether a pending post-heater measurement is still fresh compares get_jiffies_64() against data->heating_complete + data->update_interval. However, data->heating_complete is stored in jiffies whereas data->update_interval is stored in milliseconds. Whenever HZ != 1000, adding data->update_interval directly without msecs_to_jiffies() mixes incompatible time units. For example, with the default 2000 ms update_interval, the validity window is inflated to 8 seconds at HZ=250 and 20 seconds at HZ=100 instead of 2 seconds. A read within that extended window skips triggering a fresh measurement and instead returns and caches the stale post-heating measurement left in the sensor's output buffer. Convert data->update_interval to jiffies using msecs_to_jiffies() when computing the pending measurement expiration deadline, matching the next_update calculation right below it. Reported-by: Sashiko Fixes: 0eed6fc3d2b9 ("hwmon: (sht4x): add heater support") Cc: stable@vger.kernel.org Signed-off-by: Guenter Roeck --- drivers/hwmon/sht4x.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/sht4x.c b/drivers/hwmon/sht4x.c index 8372b4fc902e..020a523bf863 100644 --- a/drivers/hwmon/sht4x.c +++ b/drivers/hwmon/sht4x.c @@ -111,7 +111,9 @@ static int sht4x_read_values(struct sht4x_data *data) } if (data->data_pending && - time_before64(get_jiffies_64(), data->heating_complete + data->update_interval)) { + time_before64(get_jiffies_64(), + data->heating_complete + + msecs_to_jiffies(data->update_interval))) { data->data_pending = false; } else { next_update = data->last_updated + From c221091f7e30c64280e4cbfa27dc4c05edc96d0c Mon Sep 17 00:00:00 2001 From: Vas Zayarskiy Date: Sun, 27 Sep 2026 22:13:12 +0300 Subject: [PATCH 36/41] hwmon: (aquacomputer_d5next) Avoid truncating scaled sensor readings High Flow Next power is reported in watts. Multiplying the 16-bit value by one million can overflow both the signed intermediate and the u32 cache. Store power in long, perform the conversion in u64, and clamp to LONG_MAX. The other existing power conversions fit in a signed 32-bit value. Return -ENODATA for unavailable power instead of exposing the stored error as a reading. The power cache is updated by HID reports without the hwmon read mutex. Read it once with READ_ONCE() and use that snapshot for both the availability check and the returned value. Mark the corresponding updates with WRITE_ONCE() so that the shared accesses are explicit. Widen the shared current cache to u32. The Aquastream XT conversion to milliamperes can exceed 65535 mA; clamp its negative conversion results to zero before assigning to the unsigned cache. This also permits devices with scaled aggregate currents to store milliamperes directly. Fixes: aed80bb91de8 ("hwmon: (aquacomputer_d5next) Add support for Aquacomputer High Flow Next") Cc: stable@vger.kernel.org Signed-off-by: Vas Zayarskiy Link: https://patch.msgid.link/20260927191316.4137752-2-contact@vasily.cc [groeck: Dropped obsolete 'Assisted-by: tag] Signed-off-by: Guenter Roeck --- drivers/hwmon/aquacomputer_d5next.c | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/drivers/hwmon/aquacomputer_d5next.c b/drivers/hwmon/aquacomputer_d5next.c index 6565731b0e71..3d3786c82931 100644 --- a/drivers/hwmon/aquacomputer_d5next.c +++ b/drivers/hwmon/aquacomputer_d5next.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -612,9 +613,9 @@ struct aqc_data { u32 speed_input_min[1]; u32 speed_input_target[1]; u32 speed_input_max[1]; - u32 power_input[8]; + long power_input[8]; u16 voltage_input[8]; - u16 current_input[8]; + u32 current_input[8]; /* Label values */ const char *const *temp_label; @@ -989,7 +990,7 @@ static int aqc_legacy_read(struct aqc_data *priv) /* Calculation derived from linear regression */ sensor_value = get_unaligned_le16(priv->buffer + AQUASTREAMXT_PUMP_CURR_OFFSET); - priv->current_input[0] = DIV_ROUND_CLOSEST(sensor_value * 176, 100) - 52; + priv->current_input[0] = max(DIV_ROUND_CLOSEST(sensor_value * 176, 100) - 52, 0); sensor_value = get_unaligned_le16(priv->buffer + AQUASTREAMXT_PUMP_VOLTAGE_OFFSET); priv->voltage_input[0] = DIV_ROUND_CLOSEST(sensor_value * 1000, 61); @@ -1019,6 +1020,7 @@ static int aqc_legacy_read(struct aqc_data *priv) static int aqc_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *val) { + long power; int ret; struct aqc_data *priv = dev_get_drvdata(dev); @@ -1083,7 +1085,10 @@ static int aqc_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, } break; case hwmon_power: - *val = priv->power_input[channel]; + power = READ_ONCE(priv->power_input[channel]); + if (power == -ENODATA) + return -ENODATA; + *val = power; break; case hwmon_pwm: switch (priv->kind) { @@ -1383,9 +1388,9 @@ static int aqc_raw_event(struct hid_device *hdev, struct hid_report *report, u8 priv->speed_input[i] = get_unaligned_be16(data + priv->fan_sensor_offsets[i] + priv->fan_structure->speed); - priv->power_input[i] = - get_unaligned_be16(data + priv->fan_sensor_offsets[i] + - priv->fan_structure->power) * 10000; + WRITE_ONCE(priv->power_input[i], + get_unaligned_be16(data + priv->fan_sensor_offsets[i] + + priv->fan_structure->power) * 10000); priv->voltage_input[i] = get_unaligned_be16(data + priv->fan_sensor_offsets[i] + priv->fan_structure->voltage) * 10; @@ -1425,7 +1430,8 @@ static int aqc_raw_event(struct hid_device *hdev, struct hid_report *report, u8 priv->speed_input[2] = get_unaligned_be16(data + AQUASTREAMULT_PRESSURE_OFFSET); priv->speed_input[3] = get_unaligned_be16(data + AQUASTREAMULT_FLOW_SENSOR_OFFSET); - priv->power_input[1] = get_unaligned_be16(data + AQUASTREAMULT_PUMP_POWER) * 10000; + WRITE_ONCE(priv->power_input[1], + get_unaligned_be16(data + AQUASTREAMULT_PUMP_POWER) * 10000); priv->voltage_input[1] = get_unaligned_be16(data + AQUASTREAMULT_PUMP_VOLTAGE) * 10; @@ -1438,10 +1444,11 @@ static int aqc_raw_event(struct hid_device *hdev, struct hid_report *report, u8 case highflownext: /* If external temp sensor is not connected, its power reading is also N/A */ if (priv->temp_input[1] == -ENODATA) - priv->power_input[0] = -ENODATA; + WRITE_ONCE(priv->power_input[0], -ENODATA); else - priv->power_input[0] = - get_unaligned_be16(data + HIGHFLOWNEXT_POWER) * 1000000; + WRITE_ONCE(priv->power_input[0], + min_t(u64, get_unaligned_be16(data + HIGHFLOWNEXT_POWER) * + 1000000ULL, LONG_MAX)); priv->voltage_input[0] = get_unaligned_be16(data + HIGHFLOWNEXT_5V_VOLTAGE) * 10; priv->voltage_input[1] = From 321dcb9ae937c4c537020d12a0ef0cace5157dcc Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 30 Oct 2026 20:33:12 -0700 Subject: [PATCH 37/41] hwmon: (acpi_power_meter) Fix missing locking in sysfs show callbacks and resume During system resume, acpi_power_meter_resume() calls free_capabilities() and read_capabilities() without acquiring resource->lock. Because ACPI notifications run on the non-freezable kacpi_notify_wq workqueue, a concurrent METER_NOTIFY_CONFIG notification during resume can race with acpi_power_meter_resume() and trigger a double-free or use-after-free on the model_number, serial_number, and oem_info strings as well as concurrently overwrite resource->caps. When CONFIG_SUSPEND_FREEZER is disabled, concurrent sysfs readers can also dereference the freed capability strings mid-update. In addition, none of the extra sysfs attribute show callbacks acquire res->lock: - power1_average_min_show() and power1_average_max_show() read the 64-bit res->trip[] entries twice without locking while set_trip() modifies them under res->lock (including temporarily storing a candidate value before evaluating _PTP and rolling it back on failure). On 32-bit architectures this exposes torn 64-bit reads, and on all architectures it exposes uncommitted trip values and a TOCTOU race between the < 0 check and multiplication (emitting "-1000" instead of "unknown" if set_trip() rolls back to -1). - power1_model_number_show(), power1_oem_info_show(), power1_serial_number_show(), power1_cap_hyst_show(), power1_accuracy_show(), and power1_is_battery_show() read capability strings and 64-bit capability fields without synchronization against resume updates. Acquire resource->lock using guard(mutex) in acpi_power_meter_resume() and in all extra sysfs show callbacks. Also return -ENODATA in the capability string show callbacks if a string pointer is NULL after a failed capability re-read. Reported-by: Sashiko Fixes: de584afa5e18 ("hwmon driver for ACPI 4.0 power meters") Fixes: 16746ce8adfe ("hwmon: (acpi_power_meter) Replace the deprecated hwmon_device_register") Signed-off-by: Guenter Roeck --- drivers/hwmon/acpi_power_meter.c | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/acpi_power_meter.c b/drivers/hwmon/acpi_power_meter.c index 3891962f03db..38d6a1e56ffc 100644 --- a/drivers/hwmon/acpi_power_meter.c +++ b/drivers/hwmon/acpi_power_meter.c @@ -584,6 +584,8 @@ static ssize_t power1_average_min_show(struct device *dev, { struct acpi_power_meter_resource *res = dev_get_drvdata(dev); + guard(mutex)(&res->lock); + if (res->trip[POWER_METER_TRIP_AVERAGE_MIN_IDX] < 0) return sysfs_emit(buf, "unknown\n"); @@ -596,6 +598,8 @@ static ssize_t power1_average_max_show(struct device *dev, { struct acpi_power_meter_resource *res = dev_get_drvdata(dev); + guard(mutex)(&res->lock); + if (res->trip[POWER_METER_TRIP_AVERAGE_MAX_IDX] < 0) return sysfs_emit(buf, "unknown\n"); @@ -608,6 +612,8 @@ static ssize_t power1_cap_hyst_show(struct device *dev, { struct acpi_power_meter_resource *res = dev_get_drvdata(dev); + guard(mutex)(&res->lock); + if (res->caps.hysteresis == UNKNOWN_HYSTERESIS) return sysfs_emit(buf, "unknown\n"); @@ -619,8 +625,11 @@ static ssize_t power1_accuracy_show(struct device *dev, char *buf) { struct acpi_power_meter_resource *res = dev_get_drvdata(dev); - unsigned int acc = res->caps.accuracy; + unsigned int acc; + guard(mutex)(&res->lock); + + acc = res->caps.accuracy; return sysfs_emit(buf, "%u.%u%%\n", acc / 1000, acc % 1000); } @@ -630,6 +639,8 @@ static ssize_t power1_is_battery_show(struct device *dev, { struct acpi_power_meter_resource *res = dev_get_drvdata(dev); + guard(mutex)(&res->lock); + return sysfs_emit(buf, "%u\n", res->caps.flags & POWER_METER_IS_BATTERY ? 1 : 0); } @@ -640,6 +651,11 @@ static ssize_t power1_model_number_show(struct device *dev, { struct acpi_power_meter_resource *res = dev_get_drvdata(dev); + guard(mutex)(&res->lock); + + if (!res->model_number) + return -ENODATA; + return sysfs_emit(buf, "%s\n", res->model_number); } @@ -649,6 +665,11 @@ static ssize_t power1_oem_info_show(struct device *dev, { struct acpi_power_meter_resource *res = dev_get_drvdata(dev); + guard(mutex)(&res->lock); + + if (!res->oem_info) + return -ENODATA; + return sysfs_emit(buf, "%s\n", res->oem_info); } @@ -658,6 +679,11 @@ static ssize_t power1_serial_number_show(struct device *dev, { struct acpi_power_meter_resource *res = dev_get_drvdata(dev); + guard(mutex)(&res->lock); + + if (!res->serial_number) + return -ENODATA; + return sysfs_emit(buf, "%s\n", res->serial_number); } @@ -984,6 +1010,8 @@ static int acpi_power_meter_resume(struct device *dev) { struct acpi_power_meter_resource *resource = dev_get_drvdata(dev); + guard(mutex)(&resource->lock); + free_capabilities(resource); read_capabilities(resource); From 847ec34ec2ad5a317f1aa254781dded9e7b5faa9 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sat, 31 Oct 2026 09:08:12 -0700 Subject: [PATCH 38/41] hwmon: (acpi_power_meter) Prevent spurious power alarm when power is unknown According to the ACPI specification (Section 10.4.3 _PMM), if an error occurs while obtaining the power meter reading or if the value is not available, _PMM returns an integer with all bits set (U32_MAX or U64_MAX). In get_power_alarm_state(), update_meter() stores this unknown sentinel value in resource->power and returns 0. Unlike the hwmon_power_average handler in power_meter_read(), get_power_alarm_state() does not check resource->power against UNKNOWN_POWER_32 or UNKNOWN_POWER_64 before comparing resource->power > resource->cap. Because the sentinel is a large unsigned integer, the comparison evaluates to true for any valid cap, falsely reporting an active power alarm to userspace. Furthermore, get_power_alarm_state() writes resource->power > resource->cap back into resource->power_alarm, re-latching polled software comparisons as if a hardware METER_NOTIFY_CAPPING event had occurred and causing the next power1_alarm read to falsely report an alarm even after power drops below cap or becomes unknown. In addition, alarm attributes should not return error codes when the alarm status cannot be determined; an inconclusive alarm state should report 0 (no alarm, no error). Convert get_power_alarm_state() to return a boolean alarm state. Consume and clear any latched hardware capping notification in resource->power_alarm without re-latching software comparisons, and return false (0, no alarm) when no notification was latched and resource->power is unknown or updating the meter or cap reading fails. Reported-by: Sashiko Fixes: de584afa5e18 ("hwmon driver for ACPI 4.0 power meters") Fixes: 7532e68f5d8f ("hwmon: (acpi_power_meter) Fix uninitialized variables") Signed-off-by: Guenter Roeck --- drivers/hwmon/acpi_power_meter.c | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/drivers/hwmon/acpi_power_meter.c b/drivers/hwmon/acpi_power_meter.c index 38d6a1e56ffc..eceb3d278199 100644 --- a/drivers/hwmon/acpi_power_meter.c +++ b/drivers/hwmon/acpi_power_meter.c @@ -377,28 +377,29 @@ static int set_avg_interval(struct acpi_power_meter_resource *resource, return 0; } -static int get_power_alarm_state(struct acpi_power_meter_resource *resource, - long *val) +static bool get_power_alarm_state(struct acpi_power_meter_resource *resource) { + bool alarm = false; int ret; + if (resource->caps.flags & POWER_METER_CAN_NOTIFY) + alarm = resource->power_alarm; + resource->power_alarm = false; + ret = update_meter(resource); - if (ret) - return ret; + if (ret || + resource->power == UNKNOWN_POWER_32 || + resource->power == UNKNOWN_POWER_64) + return alarm; /* need to update cap if not to support the notification. */ if (!(resource->caps.flags & POWER_METER_CAN_NOTIFY)) { ret = update_cap(resource); if (ret) - return ret; - resource->power_alarm = resource->power > resource->cap; - *val = resource->power_alarm; - } else { - *val = resource->power_alarm || resource->power > resource->cap; - resource->power_alarm = resource->power > resource->cap; + return false; } - return 0; + return alarm || resource->power > resource->cap; } static umode_t power_meter_is_visible(const void *data, @@ -481,9 +482,7 @@ static int power_meter_read(struct device *dev, enum hwmon_sensor_types type, *val = res->caps.max_cap * 1000; break; case hwmon_power_alarm: - ret = get_power_alarm_state(res, val); - if (ret) - return ret; + *val = get_power_alarm_state(res); break; case hwmon_power_cap: ret = update_cap(res); From fe23bad2156f1d5f0df8ae4a79ee590b79efc13a Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sat, 31 Oct 2026 09:22:24 -0700 Subject: [PATCH 39/41] hwmon: (acpi_power_meter) Check for 32-bit and 64-bit unknown ACPI values According to the ACPI specification (Section 10.4), ACPI Power Meter objects (_PMC capability fields, _PMM, _GHL, _PAI) report unknown or unavailable integer values using an integer with all bits set, which can be either U32_MAX (0xffffffff) or U64_MAX (0xffffffffffffffff) depending on the DSDT revision. Currently, only hwmon_power_average checks both 32-bit and 64-bit unknown sentinels: - power1_cap_hyst_show() only compares res->caps.hysteresis against the 32-bit UNKNOWN_HYSTERESIS (0xffffffff), so a 64-bit U64_MAX sentinel bypasses the check, overflows when multiplied by 1000, and emits a bogus number instead of "unknown". - power1_accuracy_show() does not check for unknown sentinels at all, formatting 0xffffffff as "4294967.295%". - power_meter_read() does not check min_avg_interval, max_avg_interval, avg_interval, min_cap, max_cap, or cap for unknown sentinels, exposing scaled or overflowed values (or negative values when cast to signed long) to userspace instead of returning -ENODATA. - get_power_alarm_state() does not check whether resource->cap is unknown before comparing resource->power > resource->cap. - update_meter() passes an unknown caps.sampling_time directly to msecs_to_jiffies(), caching sensor readings for weeks on 64-bit builds. Introduce acpi_power_meter_is_unknown() to check for both U32_MAX and U64_MAX, and use it across sysfs attribute handlers and meter updates: - Return -ENODATA in power_meter_read() when numeric power, interval, or cap attributes report an unknown value. - Emit "unknown" in power1_cap_hyst_show() and power1_accuracy_show() when hysteresis or accuracy is unknown. - Avoid comparing against an unknown cap in get_power_alarm_state(), returning 0 (no alarm, no error) unless a hardware capping notification was latched. - Skip the sampling_time cache window in update_meter() when sampling_time is unknown. Reported-by: Sashiko Fixes: de584afa5e18 ("hwmon driver for ACPI 4.0 power meters") Signed-off-by: Guenter Roeck --- drivers/hwmon/acpi_power_meter.c | 49 ++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/drivers/hwmon/acpi_power_meter.c b/drivers/hwmon/acpi_power_meter.c index eceb3d278199..300acf1998ce 100644 --- a/drivers/hwmon/acpi_power_meter.c +++ b/drivers/hwmon/acpi_power_meter.c @@ -30,13 +30,6 @@ #define POWER_METER_CAN_CAP (1 << 2) #define POWER_METER_CAN_NOTIFY (1 << 3) #define POWER_METER_IS_BATTERY (1 << 8) -#define UNKNOWN_HYSTERESIS 0xFFFFFFFF -/* - * ACPI integers can either be 32-bit or 64-bit wide, so - * we have to check for both values. - */ -#define UNKNOWN_POWER_32 U32_MAX -#define UNKNOWN_POWER_64 U64_MAX #define METER_NOTIFY_CONFIG 0x80 #define METER_NOTIFY_TRIP 0x81 @@ -54,6 +47,15 @@ static bool force_cap_on; static DEFINE_MUTEX(acpi_notify_lock); +/* + * ACPI integers can either be 32-bit or 64-bit wide, so + * we have to check for both values. + */ +static bool acpi_power_meter_is_unknown(u64 val) +{ + return val == U32_MAX || val == U64_MAX; +} + static int can_cap_in_hardware(void) { return force_cap_on || cap_in_hardware; @@ -179,9 +181,10 @@ static int update_meter(struct acpi_power_meter_resource *resource) acpi_status status; unsigned long local_jiffies = jiffies; - if (time_before(local_jiffies, resource->sensors_last_updated + + if (!acpi_power_meter_is_unknown(resource->caps.sampling_time) && + time_before(local_jiffies, resource->sensors_last_updated + msecs_to_jiffies(resource->caps.sampling_time)) && - resource->sensors_valid) + resource->sensors_valid) return 0; status = acpi_evaluate_integer(resource->acpi_dev->handle, "_PMM", @@ -387,9 +390,7 @@ static bool get_power_alarm_state(struct acpi_power_meter_resource *resource) resource->power_alarm = false; ret = update_meter(resource); - if (ret || - resource->power == UNKNOWN_POWER_32 || - resource->power == UNKNOWN_POWER_64) + if (ret || acpi_power_meter_is_unknown(resource->power)) return alarm; /* need to update cap if not to support the notification. */ @@ -399,6 +400,9 @@ static bool get_power_alarm_state(struct acpi_power_meter_resource *resource) return false; } + if (acpi_power_meter_is_unknown(resource->cap)) + return alarm; + return alarm || resource->power > resource->cap; } @@ -459,26 +463,36 @@ static int power_meter_read(struct device *dev, enum hwmon_sensor_types type, ret = update_meter(res); if (ret) return ret; - if (res->power == UNKNOWN_POWER_32 || res->power == UNKNOWN_POWER_64) + if (acpi_power_meter_is_unknown(res->power)) return -ENODATA; *val = res->power * 1000; break; case hwmon_power_average_interval_min: + if (acpi_power_meter_is_unknown(res->caps.min_avg_interval)) + return -ENODATA; *val = res->caps.min_avg_interval; break; case hwmon_power_average_interval_max: + if (acpi_power_meter_is_unknown(res->caps.max_avg_interval)) + return -ENODATA; *val = res->caps.max_avg_interval; break; case hwmon_power_average_interval: ret = update_avg_interval(res); if (ret) return ret; - *val = (res)->avg_interval; + if (acpi_power_meter_is_unknown(res->avg_interval)) + return -ENODATA; + *val = res->avg_interval; break; case hwmon_power_cap_min: + if (acpi_power_meter_is_unknown(res->caps.min_cap)) + return -ENODATA; *val = res->caps.min_cap * 1000; break; case hwmon_power_cap_max: + if (acpi_power_meter_is_unknown(res->caps.max_cap)) + return -ENODATA; *val = res->caps.max_cap * 1000; break; case hwmon_power_alarm: @@ -488,6 +502,8 @@ static int power_meter_read(struct device *dev, enum hwmon_sensor_types type, ret = update_cap(res); if (ret) return ret; + if (acpi_power_meter_is_unknown(res->cap)) + return -ENODATA; *val = res->cap * 1000; break; default: @@ -613,7 +629,7 @@ static ssize_t power1_cap_hyst_show(struct device *dev, guard(mutex)(&res->lock); - if (res->caps.hysteresis == UNKNOWN_HYSTERESIS) + if (acpi_power_meter_is_unknown(res->caps.hysteresis)) return sysfs_emit(buf, "unknown\n"); return sysfs_emit(buf, "%llu\n", res->caps.hysteresis * 1000); @@ -628,6 +644,9 @@ static ssize_t power1_accuracy_show(struct device *dev, guard(mutex)(&res->lock); + if (acpi_power_meter_is_unknown(res->caps.accuracy)) + return sysfs_emit(buf, "unknown\n"); + acc = res->caps.accuracy; return sysfs_emit(buf, "%u.%u%%\n", acc / 1000, acc % 1000); } From e6b96b76d7074ae64f0d37d880794c71d3ce277e Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 3 Nov 2026 10:26:15 -0800 Subject: [PATCH 40/41] hwmon: (hp-wmi-sensors) Serialize alarm flag read-and-clear with state->lock In hp_wmi_hwmon_read(), temperature and fan alarm flags (info->alarm) are evaluated and cleared without acquiring state->lock, while only the hwmon subsystem lock is held by the hwmon core. Meanwhile, the asynchronous WMI event handler hp_wmi_notify() sets info->alarm under state->lock. If hp_wmi_notify() sets info->alarm between the read and the subsequent clear in hp_wmi_hwmon_read(), the newly reported fan stall or critical temperature alarm is overwritten with false and permanently lost. The driver-local state->lock cannot be replaced with the hwmon subsystem lock (hwmon_lock()): - When no connected hwmon-supported sensors are present (!count), the driver operates in debugfs-only mode without registering a hwmon device, while debugfs attributes (fungible_show()) still require state->lock to serialize WMI queries and sensor state updates. - Even when sensors are connected, add_event_handler() must subscribe to WMI events before make_chip_info() and devm_hwmon_device_register_with_info() because visibility of alarm and intrusion attributes depends on whether event registration succeeded. As a result, hp_wmi_notify() can run before the hwmon device exists during probe and after devm unregisters and frees the hwmon device during removal. Acquire state->lock in hp_wmi_hwmon_read() around reading and clearing info->alarm to prevent losing WMI alarm events. Fixes: 23902f98f8d4 ("hwmon: add HP WMI Sensors driver") Cc: James Seo Signed-off-by: Guenter Roeck --- drivers/hwmon/hp-wmi-sensors.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/hwmon/hp-wmi-sensors.c b/drivers/hwmon/hp-wmi-sensors.c index 1953d672edaf..1a4955dbddd0 100644 --- a/drivers/hwmon/hp-wmi-sensors.c +++ b/drivers/hwmon/hp-wmi-sensors.c @@ -1480,8 +1480,10 @@ static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, if ((type == hwmon_temp && attr == hwmon_temp_alarm) || (type == hwmon_fan && attr == hwmon_fan_alarm)) { + mutex_lock(&state->lock); *out_val = info->alarm ? 1 : 0; info->alarm = false; + mutex_unlock(&state->lock); return 0; } From e395bbf3ddf1b3175fd3bddaead63a2909b33694 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 3 Nov 2026 19:45:08 -0800 Subject: [PATCH 41/41] hwmon: (asus_rog_ryujin) Fix timeout when reading 0% duty cycle The device uses the same zero-duty report format for both legitimate 0% duty GET responses and SET confirmation ACKs on cooler and controller duty channels. To distinguish them, rog_ryujin_raw_event() checks !completion_done() on cooler_duty_set and controller_duty_set, assuming this is only true while a SET command is awaiting confirmation. However, init_completion() initializes the completion count to zero, so !completion_done() evaluates to true from probe until the first SET command completes. Likewise, if rog_ryujin_execute_cmd() fails to send a command, times out, or is interrupted after reinit_completion(), the completion remains uncompleted. When a valid 0% duty GET response then arrives, rog_ryujin_raw_event() consumes it as a SET confirmation and leaves the pending *_duty_received completion uncompleted, causing rog_ryujin_execute_cmd() to wait 1.5 seconds and fail with -ETIMEDOUT. Mark all completions as done with complete_all() after initialization in rog_ryujin_probe(), and complete status_completion on error in rog_ryujin_execute_cmd(), so !completion_done() is only true while a command is actively awaiting a response. Fixes: ed3e03790c5c ("hwmon: Add driver for ASUS ROG RYUJIN II 360 AIO cooler") Cc: Aleksa Savic Signed-off-by: Guenter Roeck --- drivers/hwmon/asus_rog_ryujin.c | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/drivers/hwmon/asus_rog_ryujin.c b/drivers/hwmon/asus_rog_ryujin.c index ba188e867934..111e0099fd9c 100644 --- a/drivers/hwmon/asus_rog_ryujin.c +++ b/drivers/hwmon/asus_rog_ryujin.c @@ -207,16 +207,23 @@ static int rog_ryujin_execute_cmd(struct rog_ryujin_data *priv, const u8 *cmd, i /* Send command for getting data */ ret = rog_ryujin_write_expanded(priv, cmd, cmd_length); if (ret < 0) - return ret; + goto out_complete; ret = wait_for_completion_interruptible_timeout(status_completion, msecs_to_jiffies(STATUS_VALIDITY)); if (ret == 0) - return -ETIMEDOUT; - else if (ret < 0) - return ret; + ret = -ETIMEDOUT; + if (ret < 0) + goto out_complete; return 0; + +out_complete: + spin_lock_irqsave(&priv->status_report_request_lock, flags); + if (!completion_done(status_completion)) + complete_all(status_completion); + spin_unlock_irqrestore(&priv->status_report_request_lock, flags); + return ret; } static int rog_ryujin_get_status(struct rog_ryujin_data *priv) @@ -630,6 +637,18 @@ static int rog_ryujin_probe(struct hid_device *hdev, const struct hid_device_id init_completion(&priv->cooler_duty_set); init_completion(&priv->controller_duty_set); + /* + * Start completions in the completed state so rog_ryujin_raw_event() + * only sees !completion_done() while rog_ryujin_execute_cmd() is + * awaiting a response. + */ + complete_all(&priv->cooler_status_received); + complete_all(&priv->controller_status_received); + complete_all(&priv->cooler_duty_received); + complete_all(&priv->controller_duty_received); + complete_all(&priv->cooler_duty_set); + complete_all(&priv->controller_duty_set); + priv->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, "rog_ryujin", priv, &rog_ryujin_chip_info, NULL); if (IS_ERR(priv->hwmon_dev)) {