mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2026-08-09 06:14:34 +02:00
occ_active(false) and occ_shutdown() unregister sysfs-backed devices while occ->lock is held. hwmon_device_unregister() and sysfs_remove_group() can wait for active sysfs callbacks to drain, and those callbacks can enter the OCC update path and try to take occ->lock again. That gives the unregister paths the lock ordering occ->lock -> sysfs callback drain, while a callback has the opposite edge sysfs callback -> occ->lock. This issue was found by our static analysis tool and then manually reviewed against the current tree. The grounded PoC kept the real unregister and callback carrier: occ_shutdown() hwmon_device_unregister() occ_show_temp_1() occ_update_response() Lockdep reported the circular dependency with occ_shutdown() already holding the OCC mutex and hwmon_device_unregister() waiting on the sysfs side: WARNING: possible circular locking dependency detected ... (sysfs_lock) ... at: hwmon_device_unregister+0x12/0x30 [vuln_msv] ... (&test_occ.lock) ... at: occ_shutdown.constprop.0+0xe/0x40 [vuln_msv] occ_update_response.isra.0+0xb/0x20 [vuln_msv] occ_show_temp_1.constprop.0.isra.0+0x23/0x40 [vuln_msv] *** DEADLOCK *** Serialize hwmon registration and removal with a separate hwmon_lock. Under that lock, detach occ->hwmon and update occ->active while occ->lock is held so concurrent OCC state changes still see a stable state, then drop occ->lock before calling hwmon_device_unregister(). Remove the driver sysfs group before taking occ->lock in occ_shutdown(), so draining the driver attributes cannot wait while the OCC mutex is held. Also make OCC update callbacks return -ENODEV after deactivation, so callbacks that already passed sysfs active protection do not poll the hardware after teardown has detached the hwmon device. Fixes:849b0156d9("hwmon: (occ) Delay hwmon registration until user request") Fixes:ac6888ac5a("hwmon: (occ) Lock mutex in shutdown to prevent race with occ_active") Cc: stable@vger.kernel.org Signed-off-by: Runyu Xiao <runyu.xiao@seu.edu.cn> Link: https://lore.kernel.org/r/20260619015938.494464-1-runyu.xiao@seu.edu.cn Signed-off-by: Guenter Roeck <linux@roeck-us.net>
138 lines
3.1 KiB
C
138 lines
3.1 KiB
C
/* SPDX-License-Identifier: GPL-2.0+ */
|
|
/* Copyright IBM Corp 2019 */
|
|
|
|
#ifndef OCC_COMMON_H
|
|
#define OCC_COMMON_H
|
|
|
|
#include <linux/hwmon-sysfs.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/sysfs.h>
|
|
|
|
struct device;
|
|
|
|
#define OCC_RESP_DATA_BYTES 4089
|
|
|
|
/*
|
|
* Same response format for all OCC versions.
|
|
* Allocate the largest possible response.
|
|
*/
|
|
struct occ_response {
|
|
u8 seq_no;
|
|
u8 cmd_type;
|
|
u8 return_status;
|
|
__be16 data_length;
|
|
u8 data[OCC_RESP_DATA_BYTES];
|
|
__be16 checksum;
|
|
} __packed;
|
|
|
|
struct occ_sensor_data_block_header {
|
|
u8 eye_catcher[4];
|
|
u8 reserved;
|
|
u8 sensor_format;
|
|
u8 sensor_length;
|
|
u8 num_sensors;
|
|
} __packed;
|
|
|
|
struct occ_sensor_data_block {
|
|
struct occ_sensor_data_block_header header;
|
|
u32 data;
|
|
} __packed;
|
|
|
|
struct occ_poll_response_header {
|
|
u8 status;
|
|
u8 ext_status;
|
|
u8 occs_present;
|
|
u8 config_data;
|
|
u8 occ_state;
|
|
u8 mode;
|
|
u8 ips_status;
|
|
u8 error_log_id;
|
|
__be32 error_log_start_address;
|
|
__be16 error_log_length;
|
|
u16 reserved;
|
|
u8 occ_code_level[16];
|
|
u8 eye_catcher[6];
|
|
u8 num_sensor_data_blocks;
|
|
u8 sensor_data_block_header_version;
|
|
} __packed;
|
|
|
|
struct occ_poll_response {
|
|
struct occ_poll_response_header header;
|
|
struct occ_sensor_data_block block;
|
|
} __packed;
|
|
|
|
struct occ_sensor {
|
|
u8 num_sensors;
|
|
u8 version;
|
|
void *data; /* pointer to sensor data start within response */
|
|
};
|
|
|
|
/*
|
|
* OCC only provides one sensor data block of each type, but any number of
|
|
* sensors within that block.
|
|
*/
|
|
struct occ_sensors {
|
|
struct occ_sensor temp;
|
|
struct occ_sensor freq;
|
|
struct occ_sensor power;
|
|
struct occ_sensor caps;
|
|
struct occ_sensor extended;
|
|
};
|
|
|
|
/*
|
|
* Use our own attribute struct so we can dynamically allocate space for the
|
|
* name.
|
|
*/
|
|
struct occ_attribute {
|
|
char name[32];
|
|
struct sensor_device_attribute_2 sensor;
|
|
};
|
|
|
|
struct occ {
|
|
struct device *bus_dev;
|
|
|
|
struct occ_response resp;
|
|
struct occ_sensors sensors;
|
|
|
|
int powr_sample_time_us; /* average power sample time */
|
|
u8 poll_cmd_data; /* to perform OCC poll command */
|
|
int (*send_cmd)(struct occ *occ, u8 *cmd, size_t len, void *resp,
|
|
size_t resp_len);
|
|
|
|
unsigned long next_update;
|
|
struct mutex lock; /* lock OCC access */
|
|
struct mutex hwmon_lock; /* serialize hwmon registration/removal */
|
|
|
|
struct device *hwmon;
|
|
struct occ_attribute *attrs;
|
|
struct attribute_group group;
|
|
const struct attribute_group *groups[2];
|
|
|
|
bool active;
|
|
int error; /* final transfer error after retry */
|
|
int last_error; /* latest transfer error */
|
|
unsigned int error_count; /* number of xfr errors observed */
|
|
unsigned long last_safe; /* time OCC entered "safe" state */
|
|
|
|
/*
|
|
* Store the previous state data for comparison in order to notify
|
|
* sysfs readers of state changes.
|
|
*/
|
|
int prev_error;
|
|
u8 prev_stat;
|
|
u8 prev_ext_stat;
|
|
u8 prev_occs_present;
|
|
u8 prev_ips_status;
|
|
u8 prev_mode;
|
|
};
|
|
|
|
int occ_active(struct occ *occ, bool active);
|
|
int occ_setup(struct occ *occ);
|
|
int occ_setup_sysfs(struct occ *occ);
|
|
void occ_shutdown(struct occ *occ);
|
|
void occ_shutdown_sysfs(struct occ *occ);
|
|
void occ_sysfs_poll_done(struct occ *occ);
|
|
int occ_update_response(struct occ *occ);
|
|
|
|
#endif /* OCC_COMMON_H */
|