[PATCH v3 2/5] w1_therm: adding sysfs entry to check device power

From: Akira Shimahara
Date: Wed Apr 29 2020 - 09:32:23 EST


Patch for enhacement of w1_therm module.
Adding ext_power sysfs entry (RO). Return the power status of the device:
- 0: device parasite powered
- 1: device externally powered
- xx: xx is kernel error

Creating Documentation/ABI/testing/sysfs-driver-w1_therm for the old
driver sysfs and this new entry.

Signed-off-by: Akira Shimahara <akira215corp@xxxxxxxxx>
---
.../ABI/testing/sysfs-driver-w1_therm | 29 ++++++
drivers/w1/slaves/w1_therm.c | 93 ++++++++++++++++++-
drivers/w1/slaves/w1_therm.h | 44 ++++++++-
3 files changed, 163 insertions(+), 3 deletions(-)
create mode 100644 Documentation/ABI/testing/sysfs-driver-w1_therm

diff --git a/Documentation/ABI/testing/sysfs-driver-w1_therm b/Documentation/ABI/testing/sysfs-driver-w1_therm
new file mode 100644
index 0000000..9aaf625
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-driver-w1_therm
@@ -0,0 +1,29 @@
+What: /sys/bus/w1/devices/.../ext_power
+Date: Apr 2020
+Contact: Akira Shimahara <akira215corp@xxxxxxxxx>
+Description:
+ (RO) return the power status by asking the device
+ * `0`: device parasite powered
+ * `1`: device externally powered
+ * `-xx`: xx is kernel error when reading power status
+Users: any user space application which wants to communicate with
+ w1_term device
+
+
+What: /sys/bus/w1/devices/.../w1_slave
+Date: Apr 2020
+Contact: Akira Shimahara <akira215corp@xxxxxxxxx>
+Description:
+ (RW) return the temperature in 1/1000 degC.
+ *read*: return 2 lines with the hexa output data sent on the
+ bus, return the CRC check and temperature in 1/1000 degC
+ *write* :
+ * `0` : save the 2 or 3 bytes to the device EEPROM
+ (i.e. TH, TL and config register)
+ * `9..12` : set the device resolution in RAM
+ (if supported)
+ * Anything else: do nothing
+ refer to Documentation/w1/slaves/w1_therm.rst for detailed
+ information.
+Users: any user space application which wants to communicate with
+ w1_term device
\ No newline at end of file
diff --git a/drivers/w1/slaves/w1_therm.c b/drivers/w1/slaves/w1_therm.c
index 6245950..a530853 100644
--- a/drivers/w1/slaves/w1_therm.c
+++ b/drivers/w1/slaves/w1_therm.c
@@ -39,12 +39,14 @@ module_param_named(strong_pullup, w1_strong_pullup, int, 0);

static struct attribute *w1_therm_attrs[] = {
&dev_attr_w1_slave.attr,
+ &dev_attr_ext_power.attr,
NULL,
};

static struct attribute *w1_ds28ea00_attrs[] = {
&dev_attr_w1_slave.attr,
&dev_attr_w1_seq.attr,
+ &dev_attr_ext_power.attr,
NULL,
};

@@ -294,6 +296,26 @@ static inline int w1_DS18S20_convert_temp(u8 rom[9])
return t;
}

+/*------------------------ Helpers Functions----------------------------*/
+
+static inline bool bus_mutex_lock(struct mutex *lock)
+{
+ int max_trying = W1_THERM_MAX_TRY;
+ /* try to acquire the mutex, if not, sleep retry_delay before retry) */
+ while (mutex_lock_interruptible(lock) != 0 && max_trying > 0) {
+ unsigned long sleep_rem;
+
+ sleep_rem = msleep_interruptible(W1_THERM_RETRY_DELAY);
+ if (!sleep_rem)
+ max_trying--;
+ }
+
+ if (!max_trying)
+ return false; /* Didn't acquire the bus mutex */
+
+ return true;
+}
+
/*-------------------------Interface Functions------------------------------*/
static int w1_therm_add_slave(struct w1_slave *sl)
{
@@ -302,6 +324,16 @@ static int w1_therm_add_slave(struct w1_slave *sl)
if (!sl->family_data)
return -ENOMEM;
atomic_set(THERM_REFCNT(sl->family_data), 1);
+
+ /* Getting the power mode of the device {external, parasite}*/
+ SLAVE_POWERMODE(sl) = read_powermode(sl);
+
+ if (SLAVE_POWERMODE(sl) < 0) {
+ /* no error returned as device has been added */
+ dev_warn(&sl->dev,
+ "%s: Device has been added, but power_mode may be corrupted. err=%d\n",
+ __func__, SLAVE_POWERMODE(sl));
+ }
return 0;
}

@@ -512,6 +544,43 @@ error:
return ret;
}

+static int read_powermode(struct w1_slave *sl)
+{
+ struct w1_master *dev_master = sl->master;
+ int max_trying = W1_THERM_MAX_TRY;
+ int ret = -ENODEV;
+
+ if (!sl->family_data)
+ goto error;
+
+ /* prevent the slave from going away in sleep */
+ atomic_inc(THERM_REFCNT(sl->family_data));
+
+ if (!bus_mutex_lock(&dev_master->bus_mutex)) {
+ ret = -EAGAIN; // Didn't acquire the mutex
+ goto dec_refcnt;
+ }
+
+ while ((max_trying--) && (ret < 0)) {
+ /* safe version to select slave */
+ if (!reset_select_slave(sl)) {
+ w1_write_8(dev_master, W1_READ_PSUPPLY);
+ /* Read only one bit,
+ * 1 is externally powered,
+ * 0 is parasite powered
+ */
+ ret = w1_touch_bit(dev_master, 1);
+ /* ret should be either 1 either 0 */
+ }
+ }
+ mutex_unlock(&dev_master->bus_mutex);
+
+dec_refcnt:
+ atomic_dec(THERM_REFCNT(sl->family_data));
+error:
+ return ret;
+}
+
/*------------------------Interface sysfs--------------------------*/

static ssize_t w1_slave_show(struct device *device,
@@ -565,13 +634,35 @@ static ssize_t w1_slave_store(struct device *device,
ret = w1_therm_families[i].eeprom(device);
else
ret = w1_therm_families[i].precision(device,
- val);
+ val);
break;
}
}
return ret ? : size;
}

+static ssize_t ext_power_show(struct device *device,
+ struct device_attribute *attr, char *buf)
+{
+ struct w1_slave *sl = dev_to_w1_slave(device);
+
+ if (!sl->family_data) {
+ dev_info(device,
+ "%s: Device not supported by the driver\n", __func__);
+ return 0; /* No device family */
+ }
+
+ /* Getting the power mode of the device {external, parasite}*/
+ SLAVE_POWERMODE(sl) = read_powermode(sl);
+
+ if (SLAVE_POWERMODE(sl) < 0) {
+ dev_dbg(device,
+ "%s: Power_mode may be corrupted. err=%d\n",
+ __func__, SLAVE_POWERMODE(sl));
+ }
+ return sprintf(buf, "%d\n", SLAVE_POWERMODE(sl));
+}
+
#if IS_REACHABLE(CONFIG_HWMON)
static int w1_read_temp(struct device *device, u32 attr, int channel,
long *val)
diff --git a/drivers/w1/slaves/w1_therm.h b/drivers/w1/slaves/w1_therm.h
index b73af0b..2f975a4 100644
--- a/drivers/w1/slaves/w1_therm.h
+++ b/drivers/w1/slaves/w1_therm.h
@@ -25,6 +25,12 @@
#include <linux/mutex.h>
#include <linux/w1.h>

+/*----------------------------------Defines---------------------------------*/
+/* Nb of try for an operation */
+#define W1_THERM_MAX_TRY 5
+
+/* ms delay to retry bus mutex */
+#define W1_THERM_RETRY_DELAY 20
/*----------------------------------Structs---------------------------------*/

/**
@@ -47,10 +53,15 @@ struct w1_therm_family_converter {
* struct w1_therm_family_data
* @param rom data
* @param refcnt ref count
+ * @param external_powered
+ * 1 device powered externally,
+ * 0 device parasite powered,
+ * -x error or undefined
*/
struct w1_therm_family_data {
uint8_t rom[9];
atomic_t refcnt;
+ int external_powered;
};

/**
@@ -80,11 +91,24 @@ static inline int w1_DS18B20_convert_temp(u8 rom[9]);
static inline int w1_DS18S20_convert_temp(u8 rom[9]);

/*-------------------------------Macros--------------------------------------*/
+/* return the power mode of the sl slave : 1-ext, 0-parasite, <0 unknown
+ * always test family data existence before
+ */
+#define SLAVE_POWERMODE(sl) \
+ (((struct w1_therm_family_data *)(sl->family_data))->external_powered)

/* return the address of the refcnt in the family data */
#define THERM_REFCNT(family_data) \
(&((struct w1_therm_family_data *)family_data)->refcnt)

+/*-------------------------- Helpers Functions------------------------------*/
+
+/** bus_mutex_lock() get the mutex & retry
+ * @param lock w1 bus mutex to get
+ * @return value true is mutex is acquired and lock, false otherwise
+ */
+static inline bool bus_mutex_lock(struct mutex *lock);
+
/*---------------------------Hardware Functions-----------------------------*/

/**
@@ -107,7 +131,14 @@ static int reset_select_slave(struct w1_slave *sl);
*/
static ssize_t read_therm(struct device *device,
struct w1_slave *sl, struct therm_info *info);
-
+/** read_powermode()
+ * @brief ask the device to get its power mode {external, parasite}
+ * @param sl slave to be interrogated
+ * @return 0 parasite powered device
+ * 1 externally powered device
+ * <0 kernel error code
+ */
+static int read_powermode(struct w1_slave *sl);
/*----------------------------Interface sysfs-------------------------------*/

/** @brief A callback function to output the temperature Old way
@@ -127,11 +158,20 @@ static ssize_t w1_slave_store(struct device *device,

static ssize_t w1_seq_show(struct device *device,
struct device_attribute *attr, char *buf);
-
+/** @brief A callback function to output the power mode of the device
+ * Once done, it is stored in the sl->family_data to avoid doing the test
+ * during data read
+ * @return 0 : device parasite powered
+ * 1 : device externally powered
+ * -xx : xx is kernel error code
+ */
+static ssize_t ext_power_show(struct device *device,
+ struct device_attribute *attr, char *buf);
/*-----------------------------Attributes declarations----------------------*/

static DEVICE_ATTR_RW(w1_slave);
static DEVICE_ATTR_RO(w1_seq);
+static DEVICE_ATTR_RO(ext_power);

/*--------------------------Interface Functions-----------------------------*/

--
2.25.4