hwmon: Serialize accesses in hwmon core
authorGuenter Roeck <linux@roeck-us.net>
Thu, 5 Jun 2025 23:23:57 +0000 (16:23 -0700)
committerGuenter Roeck <linux@roeck-us.net>
Mon, 8 Sep 2025 13:24:50 +0000 (06:24 -0700)
Implement locking in the hardware monitoring core for drivers using
the _with_info() API functions.

Most hardware monitoring drivers need to support locking to protect
against parallel accesses from userspace. With older API functions, such
locking had to be implemented in the driver code since sysfs attributes
were created by the driver. However, the _with_info() API creates sysfs
attributes in the hardware monitoring core. This makes it easy to move
the locking primitives into that code. This has the benefit of simplifying
driver code while at the same time reducing the risk of incomplete of bad
locking implementations in hardware monitoring drivers.

While this means that all accesses are forced to be synchronized, this
has little if any practical impact since accesses are expected to be low
frequency and are typically synchronized from userspace anyway since
only a single process is accessing the data. On top of that, many drivers
use regmap, which also has its own locking scheme and already serializes
accesses.

Signed-off-by: Guenter Roeck <linux@roeck-us.net>
Documentation/hwmon/hwmon-kernel-api.rst
drivers/hwmon/hwmon.c
include/linux/hwmon.h

index 037b69c..1d7f139 100644 (file)
@@ -42,6 +42,9 @@ register/unregister functions::
 
   char *devm_hwmon_sanitize_name(struct device *dev, const char *name);
 
+  void hwmon_lock(struct device *dev);
+  void hwmon_unlock(struct device *dev);
+
 hwmon_device_register_with_info registers a hardware monitoring device.
 It creates the standard sysfs attributes in the hardware monitoring core,
 letting the driver focus on reading from and writing to the chip instead
@@ -79,6 +82,13 @@ devm_hwmon_sanitize_name is the resource managed version of
 hwmon_sanitize_name; the memory will be freed automatically on device
 removal.
 
+When using ``[devm_]hwmon_device_register_with_info()`` to register the
+hardware monitoring device, accesses using the associated access functions
+are serialised by the hardware monitoring core. If a driver needs locking
+for other functions such as interrupt handlers or for attributes which are
+fully implemented in the driver, hwmon_lock() and hwmon_unlock() can be used
+to ensure that calls to those functions are serialized.
+
 Using devm_hwmon_device_register_with_info()
 --------------------------------------------
 
index 2e17f3a..0b4bdcd 100644 (file)
@@ -19,6 +19,7 @@
 #include <linux/kstrtox.h>
 #include <linux/list.h>
 #include <linux/module.h>
+#include <linux/mutex.h>
 #include <linux/pci.h>
 #include <linux/property.h>
 #include <linux/slab.h>
@@ -36,6 +37,7 @@ struct hwmon_device {
        const char *label;
        struct device dev;
        const struct hwmon_chip_info *chip;
+       struct mutex lock;
        struct list_head tzdata;
        struct attribute_group group;
        const struct attribute_group **groups;
@@ -165,6 +167,8 @@ static int hwmon_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
        int ret;
        long t;
 
+       guard(mutex)(&hwdev->lock);
+
        ret = hwdev->chip->ops->read(tdata->dev, hwmon_temp, hwmon_temp_input,
                                     tdata->index, &t);
        if (ret < 0)
@@ -193,6 +197,8 @@ static int hwmon_thermal_set_trips(struct thermal_zone_device *tz, int low, int
        if (!info[i])
                return 0;
 
+       guard(mutex)(&hwdev->lock);
+
        if (info[i]->config[tdata->index] & HWMON_T_MIN) {
                err = chip->ops->write(tdata->dev, hwmon_temp,
                                       hwmon_temp_min, tdata->index, low);
@@ -330,8 +336,6 @@ static int hwmon_attr_base(enum hwmon_sensor_types type)
  * attached to an i2c client device.
  */
 
-static DEFINE_MUTEX(hwmon_pec_mutex);
-
 static int hwmon_match_device(struct device *dev, const void *data)
 {
        return dev->class == &hwmon_class;
@@ -362,17 +366,16 @@ static ssize_t pec_store(struct device *dev, struct device_attribute *devattr,
        if (!hdev)
                return -ENODEV;
 
-       mutex_lock(&hwmon_pec_mutex);
-
        /*
         * If there is no write function, we assume that chip specific
         * handling is not required.
         */
        hwdev = to_hwmon_device(hdev);
+       guard(mutex)(&hwdev->lock);
        if (hwdev->chip->ops->write) {
                err = hwdev->chip->ops->write(hdev, hwmon_chip, hwmon_chip_pec, 0, val);
                if (err && err != -EOPNOTSUPP)
-                       goto unlock;
+                       goto put;
        }
 
        if (!val)
@@ -381,8 +384,7 @@ static ssize_t pec_store(struct device *dev, struct device_attribute *devattr,
                client->flags |= I2C_CLIENT_PEC;
 
        err = count;
-unlock:
-       mutex_unlock(&hwmon_pec_mutex);
+put:
        put_device(hdev);
 
        return err;
@@ -426,10 +428,13 @@ static ssize_t hwmon_attr_show(struct device *dev,
                               struct device_attribute *devattr, char *buf)
 {
        struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr);
+       struct hwmon_device *hwdev = to_hwmon_device(dev);
        s64 val64;
        long val;
        int ret;
 
+       guard(mutex)(&hwdev->lock);
+
        ret = hattr->ops->read(dev, hattr->type, hattr->attr, hattr->index,
                               (hattr->type == hwmon_energy64) ? (long *)&val64 : &val);
        if (ret < 0)
@@ -449,10 +454,13 @@ static ssize_t hwmon_attr_show_string(struct device *dev,
                                      char *buf)
 {
        struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr);
+       struct hwmon_device *hwdev = to_hwmon_device(dev);
        enum hwmon_sensor_types type = hattr->type;
        const char *s;
        int ret;
 
+       guard(mutex)(&hwdev->lock);
+
        ret = hattr->ops->read_string(dev, hattr->type, hattr->attr,
                                      hattr->index, &s);
        if (ret < 0)
@@ -469,6 +477,7 @@ static ssize_t hwmon_attr_store(struct device *dev,
                                const char *buf, size_t count)
 {
        struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr);
+       struct hwmon_device *hwdev = to_hwmon_device(dev);
        long val;
        int ret;
 
@@ -476,6 +485,8 @@ static ssize_t hwmon_attr_store(struct device *dev,
        if (ret < 0)
                return ret;
 
+       guard(mutex)(&hwdev->lock);
+
        ret = hattr->ops->write(dev, hattr->type, hattr->attr, hattr->index,
                                val);
        if (ret < 0)
@@ -791,6 +802,22 @@ int hwmon_notify_event(struct device *dev, enum hwmon_sensor_types type,
 }
 EXPORT_SYMBOL_GPL(hwmon_notify_event);
 
+void hwmon_lock(struct device *dev)
+{
+       struct hwmon_device *hwdev = to_hwmon_device(dev);
+
+       mutex_lock(&hwdev->lock);
+}
+EXPORT_SYMBOL_GPL(hwmon_lock);
+
+void hwmon_unlock(struct device *dev)
+{
+       struct hwmon_device *hwdev = to_hwmon_device(dev);
+
+       mutex_unlock(&hwdev->lock);
+}
+EXPORT_SYMBOL_GPL(hwmon_unlock);
+
 static int hwmon_num_channel_attrs(const struct hwmon_channel_info *info)
 {
        int i, n;
@@ -951,6 +978,7 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata,
                tdev = tdev->parent;
        hdev->of_node = tdev ? tdev->of_node : NULL;
        hwdev->chip = chip;
+       mutex_init(&hwdev->lock);
        dev_set_drvdata(hdev, drvdata);
        dev_set_name(hdev, HWMON_ID_FORMAT, id);
        err = device_register(hdev);
index 886fc90..301a83a 100644 (file)
@@ -492,6 +492,9 @@ int hwmon_notify_event(struct device *dev, enum hwmon_sensor_types type,
 char *hwmon_sanitize_name(const char *name);
 char *devm_hwmon_sanitize_name(struct device *dev, const char *name);
 
+void hwmon_lock(struct device *dev);
+void hwmon_unlock(struct device *dev);
+
 /**
  * hwmon_is_bad_char - Is the char invalid in a hwmon name
  * @ch: the char to be considered