diff options
4 files changed, 960 insertions, 0 deletions
diff --git a/package/kernel/linux/modules/hwmon.mk b/package/kernel/linux/modules/hwmon.mk index 73b448022d..1f4e4558a9 100644 --- a/package/kernel/linux/modules/hwmon.mk +++ b/package/kernel/linux/modules/hwmon.mk @@ -77,6 +77,21 @@ endef $(eval $(call KernelPackage,hwmon-adt7475)) +define KernelPackage/hwmon-drivetemp + TITLE:=Hard disk drives with temperature sensor + KCONFIG:=CONFIG_SENSORS_DRIVETEMP + FILES:=$(LINUX_DIR)/drivers/hwmon/drivetemp.ko + AUTOLOAD:=$(call AutoLoad,60,drivetemp) + $(call AddDepends/hwmon,+kmod-ata-core +kmod-scsi-core) +endef + +define KernelPackage/hwmon-drivetemp/description + Kernel module for Hard disk drives with temperature sensor +endef + +$(eval $(call KernelPackage,hwmon-drivetemp)) + + define KernelPackage/hwmon-gpiofan TITLE:=Generic GPIO FAN support KCONFIG:=CONFIG_SENSORS_GPIO_FAN diff --git a/target/linux/generic/backport-5.4/800-v5.5-scsi-core-Add-sysfs-attributes-for-VPD-pages-0h-and-.patch b/target/linux/generic/backport-5.4/800-v5.5-scsi-core-Add-sysfs-attributes-for-VPD-pages-0h-and-.patch new file mode 100644 index 0000000000..dc89b279e5 --- /dev/null +++ b/target/linux/generic/backport-5.4/800-v5.5-scsi-core-Add-sysfs-attributes-for-VPD-pages-0h-and-.patch @@ -0,0 +1,122 @@ +From d188b0675b21d5a6ca27b3e741381813983f4719 Mon Sep 17 00:00:00 2001 +From: Ryan Attard <ryanattard@ryanattard.info> +Date: Thu, 26 Sep 2019 11:22:17 -0500 +Subject: [PATCH] scsi: core: Add sysfs attributes for VPD pages 0h and 89h + +Add sysfs attributes for the ATA information page and Supported VPD Pages +page. + +Link: https://lore.kernel.org/r/20190926162216.56591-1-ryanattard@ryanattard.info +Signed-off-by: Ryan Attard <ryanattard@ryanattard.info> +Reviewed-by: Bart Van Assche <bvanassche@acm.org> +Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com> +--- + drivers/scsi/scsi.c | 4 ++++ + drivers/scsi/scsi_sysfs.c | 19 +++++++++++++++++++ + include/scsi/scsi_device.h | 2 ++ + 3 files changed, 25 insertions(+) + +--- a/drivers/scsi/scsi.c ++++ b/drivers/scsi/scsi.c +@@ -465,10 +465,14 @@ void scsi_attach_vpd(struct scsi_device + return; + + for (i = 4; i < vpd_buf->len; i++) { ++ if (vpd_buf->data[i] == 0x0) ++ scsi_update_vpd_page(sdev, 0x0, &sdev->vpd_pg0); + if (vpd_buf->data[i] == 0x80) + scsi_update_vpd_page(sdev, 0x80, &sdev->vpd_pg80); + if (vpd_buf->data[i] == 0x83) + scsi_update_vpd_page(sdev, 0x83, &sdev->vpd_pg83); ++ if (vpd_buf->data[i] == 0x89) ++ scsi_update_vpd_page(sdev, 0x89, &sdev->vpd_pg89); + } + kfree(vpd_buf); + } +--- a/drivers/scsi/scsi_sysfs.c ++++ b/drivers/scsi/scsi_sysfs.c +@@ -437,6 +437,7 @@ static void scsi_device_dev_release_user + struct device *parent; + struct list_head *this, *tmp; + struct scsi_vpd *vpd_pg80 = NULL, *vpd_pg83 = NULL; ++ struct scsi_vpd *vpd_pg0 = NULL, *vpd_pg89 = NULL; + unsigned long flags; + + sdev = container_of(work, struct scsi_device, ew.work); +@@ -466,16 +467,24 @@ static void scsi_device_dev_release_user + sdev->request_queue = NULL; + + mutex_lock(&sdev->inquiry_mutex); ++ rcu_swap_protected(sdev->vpd_pg0, vpd_pg0, ++ lockdep_is_held(&sdev->inquiry_mutex)); + rcu_swap_protected(sdev->vpd_pg80, vpd_pg80, + lockdep_is_held(&sdev->inquiry_mutex)); + rcu_swap_protected(sdev->vpd_pg83, vpd_pg83, + lockdep_is_held(&sdev->inquiry_mutex)); ++ rcu_swap_protected(sdev->vpd_pg89, vpd_pg89, ++ lockdep_is_held(&sdev->inquiry_mutex)); + mutex_unlock(&sdev->inquiry_mutex); + ++ if (vpd_pg0) ++ kfree_rcu(vpd_pg0, rcu); + if (vpd_pg83) + kfree_rcu(vpd_pg83, rcu); + if (vpd_pg80) + kfree_rcu(vpd_pg80, rcu); ++ if (vpd_pg89) ++ kfree_rcu(vpd_pg89, rcu); + kfree(sdev->inquiry); + kfree(sdev); + +@@ -868,6 +877,8 @@ static struct bin_attribute dev_attr_vpd + + sdev_vpd_pg_attr(pg83); + sdev_vpd_pg_attr(pg80); ++sdev_vpd_pg_attr(pg89); ++sdev_vpd_pg_attr(pg0); + + static ssize_t show_inquiry(struct file *filep, struct kobject *kobj, + struct bin_attribute *bin_attr, +@@ -1200,12 +1211,18 @@ static umode_t scsi_sdev_bin_attr_is_vis + struct scsi_device *sdev = to_scsi_device(dev); + + ++ if (attr == &dev_attr_vpd_pg0 && !sdev->vpd_pg0) ++ return 0; ++ + if (attr == &dev_attr_vpd_pg80 && !sdev->vpd_pg80) + return 0; + + if (attr == &dev_attr_vpd_pg83 && !sdev->vpd_pg83) + return 0; + ++ if (attr == &dev_attr_vpd_pg89 && !sdev->vpd_pg89) ++ return 0; ++ + return S_IRUGO; + } + +@@ -1248,8 +1265,10 @@ static struct attribute *scsi_sdev_attrs + }; + + static struct bin_attribute *scsi_sdev_bin_attrs[] = { ++ &dev_attr_vpd_pg0, + &dev_attr_vpd_pg83, + &dev_attr_vpd_pg80, ++ &dev_attr_vpd_pg89, + &dev_attr_inquiry, + NULL + }; +--- a/include/scsi/scsi_device.h ++++ b/include/scsi/scsi_device.h +@@ -140,8 +140,10 @@ struct scsi_device { + const char * rev; /* ... "nullnullnullnull" before scan */ + + #define SCSI_VPD_PG_LEN 255 ++ struct scsi_vpd __rcu *vpd_pg0; + struct scsi_vpd __rcu *vpd_pg83; + struct scsi_vpd __rcu *vpd_pg80; ++ struct scsi_vpd __rcu *vpd_pg89; + unsigned char current_tag; /* current tag */ + struct scsi_target *sdev_target; /* used only for single_lun */ + diff --git a/target/linux/generic/backport-5.4/801-v5.5-hwmon-Driver-for-disk-and-solid-state-drives-with-te.patch b/target/linux/generic/backport-5.4/801-v5.5-hwmon-Driver-for-disk-and-solid-state-drives-with-te.patch new file mode 100644 index 0000000000..32a629772f --- /dev/null +++ b/target/linux/generic/backport-5.4/801-v5.5-hwmon-Driver-for-disk-and-solid-state-drives-with-te.patch @@ -0,0 +1,737 @@ +From 5b46903d8bf372e563bf2150d46b87fff197a109 Mon Sep 17 00:00:00 2001 +From: Guenter Roeck <linux@roeck-us.net> +Date: Thu, 28 Nov 2019 21:34:40 -0800 +Subject: [PATCH] hwmon: Driver for disk and solid state drives with + temperature sensors +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Reading the temperature of ATA drives has been supported for years +by userspace tools such as smarttools or hddtemp. The downside of +such tools is that they need to run with super-user privilege, that +the temperatures are not reported by standard tools such as 'sensors' +or 'libsensors', and that drive temperatures are not available for use +in the kernel's thermal subsystem. + +This driver solves this problem by adding support for reading the +temperature of ATA drives from the kernel using the hwmon API and +by adding a temperature zone for each drive. + +With this driver, the hard disk temperature can be read using the +unprivileged 'sensors' application: + +$ sensors drivetemp-scsi-1-0 +drivetemp-scsi-1-0 +Adapter: SCSI adapter +temp1: +23.0°C + +or directly from sysfs: + +$ grep . /sys/class/hwmon/hwmon9/{name,temp1_input} +/sys/class/hwmon/hwmon9/name:drivetemp +/sys/class/hwmon/hwmon9/temp1_input:23000 + +If the drive supports SCT transport and reports temperature limits, +those are reported as well. + +drivetemp-scsi-0-0 +Adapter: SCSI adapter +temp1: +27.0°C (low = +0.0°C, high = +60.0°C) + (crit low = -41.0°C, crit = +85.0°C) + (lowest = +23.0°C, highest = +34.0°C) + +The driver attempts to use SCT Command Transport to read the drive +temperature. If the SCT Command Transport feature set is not available, +or if it does not report the drive temperature, drive temperatures may +be readable through SMART attributes. Since SMART attributes are not well +defined, this method is only used as fallback mechanism. + +Cc: Chris Healy <cphealy@gmail.com> +Cc: Linus Walleij <linus.walleij@linaro.org> +Cc: Martin K. Petersen <martin.petersen@oracle.com> +Cc: Bart Van Assche <bvanassche@acm.org> +Reviewed-by: Linus Walleij <linus.walleij@linaro.org> +Tested-by: Linus Walleij <linus.walleij@linaro.org> +Signed-off-by: Guenter Roeck <linux@roeck-us.net> +--- + Documentation/hwmon/drivetemp.rst | 52 +++ + Documentation/hwmon/index.rst | 1 + + drivers/hwmon/Kconfig | 10 + + drivers/hwmon/Makefile | 1 + + drivers/hwmon/drivetemp.c | 574 ++++++++++++++++++++++++++++++ + 5 files changed, 638 insertions(+) + create mode 100644 Documentation/hwmon/drivetemp.rst + create mode 100644 drivers/hwmon/drivetemp.c + +--- /dev/null ++++ b/Documentation/hwmon/drivetemp.rst +@@ -0,0 +1,52 @@ ++.. SPDX-License-Identifier: GPL-2.0 ++ ++Kernel driver drivetemp ++======================= ++ ++ ++References ++---------- ++ ++ANS T13/1699-D ++Information technology - AT Attachment 8 - ATA/ATAPI Command Set (ATA8-ACS) ++ ++ANS Project T10/BSR INCITS 513 ++Information technology - SCSI Primary Commands - 4 (SPC-4) ++ ++ANS Project INCITS 557 ++Information technology - SCSI / ATA Translation - 5 (SAT-5) ++ ++ ++Description ++----------- ++ ++This driver supports reporting the temperature of disk and solid state ++drives with temperature sensors. ++ ++If supported, it uses the ATA SCT Command Transport feature to read ++the current drive temperature and, if available, temperature limits ++as well as historic minimum and maximum temperatures. If SCT Command ++Transport is not supported, the driver uses SMART attributes to read ++the drive temperature. ++ ++ ++Sysfs entries ++------------- ++ ++Only the temp1_input attribute is always available. Other attributes are ++available only if reported by the drive. All temperatures are reported in ++milli-degrees Celsius. ++ ++======================= ===================================================== ++temp1_input Current drive temperature ++temp1_lcrit Minimum temperature limit. Operating the device below ++ this temperature may cause physical damage to the ++ device. ++temp1_min Minimum recommended continuous operating limit ++temp1_max Maximum recommended continuous operating temperature ++temp1_crit Maximum temperature limit. Operating the device above ++ this temperature may cause physical damage to the ++ device. ++temp1_lowest Minimum temperature seen this power cycle ++temp1_highest Maximum temperature seen this power cycle ++======================= ===================================================== +--- a/Documentation/hwmon/index.rst ++++ b/Documentation/hwmon/index.rst +@@ -45,6 +45,7 @@ Hardware Monitoring Kernel Drivers + da9052 + da9055 + dme1737 ++ drivetemp + ds1621 + ds620 + emc1403 +--- a/drivers/hwmon/Kconfig ++++ b/drivers/hwmon/Kconfig +@@ -385,6 +385,16 @@ config SENSORS_ATXP1 + This driver can also be built as a module. If so, the module + will be called atxp1. + ++config SENSORS_DRIVETEMP ++ tristate "Hard disk drives with temperature sensors" ++ depends on SCSI && ATA ++ help ++ If you say yes you get support for the temperature sensor on ++ hard disk drives. ++ ++ This driver can also be built as a module. If so, the module ++ will be called satatemp. ++ + config SENSORS_DS620 + tristate "Dallas Semiconductor DS620" + depends on I2C +--- a/drivers/hwmon/Makefile ++++ b/drivers/hwmon/Makefile +@@ -56,6 +56,7 @@ obj-$(CONFIG_SENSORS_DA9052_ADC)+= da905 + obj-$(CONFIG_SENSORS_DA9055)+= da9055-hwmon.o + obj-$(CONFIG_SENSORS_DELL_SMM) += dell-smm-hwmon.o + obj-$(CONFIG_SENSORS_DME1737) += dme1737.o ++obj-$(CONFIG_SENSORS_DRIVETEMP) += drivetemp.o + obj-$(CONFIG_SENSORS_DS620) += ds620.o + obj-$(CONFIG_SENSORS_DS1621) += ds1621.o + obj-$(CONFIG_SENSORS_EMC1403) += emc1403.o +--- /dev/null ++++ b/drivers/hwmon/drivetemp.c +@@ -0,0 +1,574 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Hwmon client for disk and solid state drives with temperature sensors ++ * Copyright (C) 2019 Zodiac Inflight Innovations ++ * ++ * With input from: ++ * Hwmon client for S.M.A.R.T. hard disk drives with temperature sensors. ++ * (C) 2018 Linus Walleij ++ * ++ * hwmon: Driver for SCSI/ATA temperature sensors ++ * by Constantin Baranov <const@mimas.ru>, submitted September 2009 ++ * ++ * This drive supports reporting the temperatire of SATA drives. It can be ++ * easily extended to report the temperature of SCSI drives. ++ * ++ * The primary means to read drive temperatures and temperature limits ++ * for ATA drives is the SCT Command Transport feature set as specified in ++ * ATA8-ACS. ++ * It can be used to read the current drive temperature, temperature limits, ++ * and historic minimum and maximum temperatures. The SCT Command Transport ++ * feature set is documented in "AT Attachment 8 - ATA/ATAPI Command Set ++ * (ATA8-ACS)". ++ * ++ * If the SCT Command Transport feature set is not available, drive temperatures ++ * may be readable through SMART attributes. Since SMART attributes are not well ++ * defined, this method is only used as fallback mechanism. ++ * ++ * There are three SMART attributes which may report drive temperatures. ++ * Those are defined as follows (from ++ * http://www.cropel.com/library/smart-attribute-list.aspx). ++ * ++ * 190 Temperature Temperature, monitored by a sensor somewhere inside ++ * the drive. Raw value typicaly holds the actual ++ * temperature (hexadecimal) in its rightmost two digits. ++ * ++ * 194 Temperature Temperature, monitored by a sensor somewhere inside ++ * the drive. Raw value typicaly holds the actual ++ * temperature (hexadecimal) in its rightmost two digits. ++ * ++ * 231 Temperature Temperature, monitored by a sensor somewhere inside ++ * the drive. Raw value typicaly holds the actual ++ * temperature (hexadecimal) in its rightmost two digits. ++ * ++ * Wikipedia defines attributes a bit differently. ++ * ++ * 190 Temperature Value is equal to (100-temp. °C), allowing manufacturer ++ * Difference or to set a minimum threshold which corresponds to a ++ * Airflow maximum temperature. This also follows the convention of ++ * Temperature 100 being a best-case value and lower values being ++ * undesirable. However, some older drives may instead ++ * report raw Temperature (identical to 0xC2) or ++ * Temperature minus 50 here. ++ * 194 Temperature or Indicates the device temperature, if the appropriate ++ * Temperature sensor is fitted. Lowest byte of the raw value contains ++ * Celsius the exact temperature value (Celsius degrees). ++ * 231 Life Left Indicates the approximate SSD life left, in terms of ++ * (SSDs) or program/erase cycles or available reserved blocks. ++ * Temperature A normalized value of 100 represents a new drive, with ++ * a threshold value at 10 indicating a need for ++ * replacement. A value of 0 may mean that the drive is ++ * operating in read-only mode to allow data recovery. ++ * Previously (pre-2010) occasionally used for Drive ++ * Temperature (more typically reported at 0xC2). ++ * ++ * Common denominator is that the first raw byte reports the temperature ++ * in degrees C on almost all drives. Some drives may report a fractional ++ * temperature in the second raw byte. ++ * ++ * Known exceptions (from libatasmart): ++ * - SAMSUNG SV0412H and SAMSUNG SV1204H) report the temperature in 10th ++ * degrees C in the first two raw bytes. ++ * - A few Maxtor drives report an unknown or bad value in attribute 194. ++ * - Certain Apple SSD drives report an unknown value in attribute 190. ++ * Only certain firmware versions are affected. ++ * ++ * Those exceptions affect older ATA drives and are currently ignored. ++ * Also, the second raw byte (possibly reporting the fractional temperature) ++ * is currently ignored. ++ * ++ * Many drives also report temperature limits in additional SMART data raw ++ * bytes. The format of those is not well defined and varies widely. ++ * The driver does not currently attempt to report those limits. ++ * ++ * According to data in smartmontools, attribute 231 is rarely used to report ++ * drive temperatures. At the same time, several drives report SSD life left ++ * in attribute 231, but do not support temperature sensors. For this reason, ++ * attribute 231 is currently ignored. ++ * ++ * Following above definitions, temperatures are reported as follows. ++ * If SCT Command Transport is supported, it is used to read the ++ * temperature and, if available, temperature limits. ++ * - Otherwise, if SMART attribute 194 is supported, it is used to read ++ * the temperature. ++ * - Otherwise, if SMART attribute 190 is supported, it is used to read ++ * the temperature. ++ */ ++ ++#include <linux/ata.h> ++#include <linux/bits.h> ++#include <linux/device.h> ++#include <linux/hwmon.h> ++#include <linux/kernel.h> ++#include <linux/list.h> ++#include <linux/module.h> ++#include <linux/mutex.h> ++#include <scsi/scsi_cmnd.h> ++#include <scsi/scsi_device.h> ++#include <scsi/scsi_driver.h> ++#include <scsi/scsi_proto.h> ++ ++struct drivetemp_data { ++ struct list_head list; /* list of instantiated devices */ ++ struct mutex lock; /* protect data buffer accesses */ ++ struct scsi_device *sdev; /* SCSI device */ ++ struct device *dev; /* instantiating device */ ++ struct device *hwdev; /* hardware monitoring device */ ++ u8 smartdata[ATA_SECT_SIZE]; /* local buffer */ ++ int (*get_temp)(struct drivetemp_data *st, u32 attr, long *val); ++ bool have_temp_lowest; /* lowest temp in SCT status */ ++ bool have_temp_highest; /* highest temp in SCT status */ ++ bool have_temp_min; /* have min temp */ ++ bool have_temp_max; /* have max temp */ ++ bool have_temp_lcrit; /* have lower critical limit */ ++ bool have_temp_crit; /* have critical limit */ ++ int temp_min; /* min temp */ ++ int temp_max; /* max temp */ ++ int temp_lcrit; /* lower critical limit */ ++ int temp_crit; /* critical limit */ ++}; ++ ++static LIST_HEAD(drivetemp_devlist); ++ ++#define ATA_MAX_SMART_ATTRS 30 ++#define SMART_TEMP_PROP_190 190 ++#define SMART_TEMP_PROP_194 194 ++ ++#define SCT_STATUS_REQ_ADDR 0xe0 ++#define SCT_STATUS_VERSION_LOW 0 /* log byte offsets */ ++#define SCT_STATUS_VERSION_HIGH 1 ++#define SCT_STATUS_TEMP 200 ++#define SCT_STATUS_TEMP_LOWEST 201 ++#define SCT_STATUS_TEMP_HIGHEST 202 ++#define SCT_READ_LOG_ADDR 0xe1 ++#define SMART_READ_LOG 0xd5 ++#define SMART_WRITE_LOG 0xd6 ++ ++#define INVALID_TEMP 0x80 ++ ++#define temp_is_valid(temp) ((temp) != INVALID_TEMP) ++#define temp_from_sct(temp) (((s8)(temp)) * 1000) ++ ++static inline bool ata_id_smart_supported(u16 *id) ++{ ++ return id[ATA_ID_COMMAND_SET_1] & BIT(0); ++} ++ ++static inline bool ata_id_smart_enabled(u16 *id) ++{ ++ return id[ATA_ID_CFS_ENABLE_1] & BIT(0); ++} ++ ++static int drivetemp_scsi_command(struct drivetemp_data *st, ++ u8 ata_command, u8 feature, ++ u8 lba_low, u8 lba_mid, u8 lba_high) ++{ ++ u8 scsi_cmd[MAX_COMMAND_SIZE]; ++ int data_dir; ++ ++ memset(scsi_cmd, 0, sizeof(scsi_cmd)); ++ scsi_cmd[0] = ATA_16; ++ if (ata_command == ATA_CMD_SMART && feature == SMART_WRITE_LOG) { ++ scsi_cmd[1] = (5 << 1); /* PIO Data-out */ ++ /* ++ * No off.line or cc, write to dev, block count in sector count ++ * field. ++ */ ++ scsi_cmd[2] = 0x06; ++ data_dir = DMA_TO_DEVICE; ++ } else { ++ scsi_cmd[1] = (4 << 1); /* PIO Data-in */ ++ /* ++ * No off.line or cc, read from dev, block count in sector count ++ * field. ++ */ ++ scsi_cmd[2] = 0x0e; ++ data_dir = DMA_FROM_DEVICE; ++ } ++ scsi_cmd[4] = feature; ++ scsi_cmd[6] = 1; /* 1 sector */ ++ scsi_cmd[8] = lba_low; ++ scsi_cmd[10] = lba_mid; ++ scsi_cmd[12] = lba_high; ++ scsi_cmd[14] = ata_command; ++ ++ return scsi_execute_req(st->sdev, scsi_cmd, data_dir, ++ st->smartdata, ATA_SECT_SIZE, NULL, HZ, 5, ++ NULL); ++} ++ ++static int drivetemp_ata_command(struct drivetemp_data *st, u8 feature, ++ u8 select) ++{ ++ return drivetemp_scsi_command(st, ATA_CMD_SMART, feature, select, ++ ATA_SMART_LBAM_PASS, ATA_SMART_LBAH_PASS); ++} ++ ++static int drivetemp_get_smarttemp(struct drivetemp_data *st, u32 attr, ++ long *temp) ++{ ++ u8 *buf = st->smartdata; ++ bool have_temp = false; ++ u8 temp_raw; ++ u8 csum; ++ int err; ++ int i; ++ ++ err = drivetemp_ata_command(st, ATA_SMART_READ_VALUES, 0); ++ if (err) ++ return err; ++ ++ /* Checksum the read value table */ ++ csum = 0; ++ for (i = 0; i < ATA_SECT_SIZE; i++) ++ csum += buf[i]; ++ if (csum) { ++ dev_dbg(&st->sdev->sdev_gendev, ++ "checksum error reading SMART values\n"); ++ return -EIO; ++ } ++ ++ for (i = 0; i < ATA_MAX_SMART_ATTRS; i++) { ++ u8 *attr = buf + i * 12; ++ int id = attr[2]; ++ ++ if (!id) ++ continue; ++ ++ if (id == SMART_TEMP_PROP_190) { ++ temp_raw = attr[7]; ++ have_temp = true; ++ } ++ if (id == SMART_TEMP_PROP_194) { ++ temp_raw = attr[7]; ++ have_temp = true; ++ break; ++ } ++ } ++ ++ if (have_temp) { ++ *temp = temp_raw * 1000; ++ return 0; ++ } ++ ++ return -ENXIO; ++} ++ ++static int drivetemp_get_scttemp(struct drivetemp_data *st, u32 attr, long *val) ++{ ++ u8 *buf = st->smartdata; ++ int err; ++ ++ err = drivetemp_ata_command(st, SMART_READ_LOG, SCT_STATUS_REQ_ADDR); ++ if (err) ++ return err; ++ switch (attr) { ++ case hwmon_temp_input: ++ *val = temp_from_sct(buf[SCT_STATUS_TEMP]); ++ break; ++ case hwmon_temp_lowest: ++ *val = temp_from_sct(buf[SCT_STATUS_TEMP_LOWEST]); ++ break; ++ case hwmon_temp_highest: ++ *val = temp_from_sct(buf[SCT_STATUS_TEMP_HIGHEST]); ++ break; ++ default: ++ err = -EINVAL; ++ break; ++ } ++ return err; ++} ++ ++static int drivetemp_identify_sata(struct drivetemp_data *st) ++{ ++ struct scsi_device *sdev = st->sdev; ++ u8 *buf = st->smartdata; ++ struct scsi_vpd *vpd; ++ bool is_ata, is_sata; ++ bool have_sct_data_table; ++ bool have_sct_temp; ++ bool have_smart; ++ bool have_sct; ++ u16 *ata_id; ++ u16 version; ++ long temp; ++ int err; ++ ++ /* SCSI-ATA Translation present? */ ++ rcu_read_lock(); ++ vpd = rcu_dereference(sdev->vpd_pg89); ++ ++ /* ++ * Verify that ATA IDENTIFY DEVICE data is included in ATA Information ++ * VPD and that the drive implements the SATA protocol. ++ */ ++ if (!vpd || vpd->len < 572 || vpd->data[56] != ATA_CMD_ID_ATA || ++ vpd->data[36] != 0x34) { ++ rcu_read_unlock(); ++ return -ENODEV; ++ } ++ ata_id = (u16 *)&vpd->data[60]; ++ is_ata = ata_id_is_ata(ata_id); ++ is_sata = ata_id_is_sata(ata_id); ++ have_sct = ata_id_sct_supported(ata_id); ++ have_sct_data_table = ata_id_sct_data_tables(ata_id); ++ have_smart = ata_id_smart_supported(ata_id) && ++ ata_id_smart_enabled(ata_id); ++ ++ rcu_read_unlock(); ++ ++ /* bail out if this is not a SATA device */ ++ if (!is_ata || !is_sata) ++ return -ENODEV; ++ if (!have_sct) ++ goto skip_sct; ++ ++ err = drivetemp_ata_command(st, SMART_READ_LOG, SCT_STATUS_REQ_ADDR); ++ if (err) ++ goto skip_sct; ++ ++ version = (buf[SCT_STATUS_VERSION_HIGH] << 8) | ++ buf[SCT_STATUS_VERSION_LOW]; ++ if (version != 2 && version != 3) ++ goto skip_sct; ++ ++ have_sct_temp = temp_is_valid(buf[SCT_STATUS_TEMP]); ++ if (!have_sct_temp) ++ goto skip_sct; ++ ++ st->have_temp_lowest = temp_is_valid(buf[SCT_STATUS_TEMP_LOWEST]); ++ st->have_temp_highest = temp_is_valid(buf[SCT_STATUS_TEMP_HIGHEST]); ++ ++ if (!have_sct_data_table) ++ goto skip_sct; ++ ++ /* Request and read temperature history table */ ++ memset(buf, '\0', sizeof(st->smartdata)); ++ buf[0] = 5; /* data table command */ ++ buf[2] = 1; /* read table */ ++ buf[4] = 2; /* temperature history table */ ++ ++ err = drivetemp_ata_command(st, SMART_WRITE_LOG, SCT_STATUS_REQ_ADDR); ++ if (err) ++ goto skip_sct_data; ++ ++ err = drivetemp_ata_command(st, SMART_READ_LOG, SCT_READ_LOG_ADDR); ++ if (err) ++ goto skip_sct_data; ++ ++ /* ++ * Temperature limits per AT Attachment 8 - ++ * ATA/ATAPI Command Set (ATA8-ACS) ++ */ ++ st->have_temp_max = temp_is_valid(buf[6]); ++ st->have_temp_crit = temp_is_valid(buf[7]); ++ st->have_temp_min = temp_is_valid(buf[8]); ++ st->have_temp_lcrit = temp_is_valid(buf[9]); ++ ++ st->temp_max = temp_from_sct(buf[6]); ++ st->temp_crit = temp_from_sct(buf[7]); ++ st->temp_min = temp_from_sct(buf[8]); ++ st->temp_lcrit = temp_from_sct(buf[9]); ++ ++skip_sct_data: ++ if (have_sct_temp) { ++ st->get_temp = drivetemp_get_scttemp; ++ return 0; ++ } ++skip_sct: ++ if (!have_smart) ++ return -ENODEV; ++ st->get_temp = drivetemp_get_smarttemp; ++ return drivetemp_get_smarttemp(st, hwmon_temp_input, &temp); ++} ++ ++static int drivetemp_identify(struct drivetemp_data *st) ++{ ++ struct scsi_device *sdev = st->sdev; ++ ++ /* Bail out immediately if there is no inquiry data */ ++ if (!sdev->inquiry || sdev->inquiry_len < 16) ++ return -ENODEV; ++ ++ /* Disk device? */ ++ if (sdev->type != TYPE_DISK && sdev->type != TYPE_ZBC) ++ return -ENODEV; ++ ++ return drivetemp_identify_sata(st); ++} ++ ++static int drivetemp_read(struct device *dev, enum hwmon_sensor_types type, ++ u32 attr, int channel, long *val) ++{ ++ struct drivetemp_data *st = dev_get_drvdata(dev); ++ int err = 0; ++ ++ if (type != hwmon_temp) ++ return -EINVAL; ++ ++ switch (attr) { ++ case hwmon_temp_input: ++ case hwmon_temp_lowest: ++ case hwmon_temp_highest: ++ mutex_lock(&st->lock); ++ err = st->get_temp(st, attr, val); ++ mutex_unlock(&st->lock); ++ break; ++ case hwmon_temp_lcrit: ++ *val = st->temp_lcrit; ++ break; ++ case hwmon_temp_min: ++ *val = st->temp_min; ++ break; ++ case hwmon_temp_max: ++ *val = st->temp_max; ++ break; ++ case hwmon_temp_crit: ++ *val = st->temp_crit; ++ break; ++ default: ++ err = -EINVAL; ++ break; ++ } ++ return err; ++} ++ ++static umode_t drivetemp_is_visible(const void *data, ++ enum hwmon_sensor_types type, ++ u32 attr, int channel) ++{ ++ const struct drivetemp_data *st = data; ++ ++ switch (type) { ++ case hwmon_temp: ++ switch (attr) { ++ case hwmon_temp_input: ++ return 0444; ++ case hwmon_temp_lowest: ++ if (st->have_temp_lowest) ++ return 0444; ++ break; ++ case hwmon_temp_highest: ++ if (st->have_temp_highest) ++ return 0444; ++ break; ++ case hwmon_temp_min: ++ if (st->have_temp_min) ++ return 0444; ++ break; ++ case hwmon_temp_max: ++ if (st->have_temp_max) ++ return 0444; ++ break; ++ case hwmon_temp_lcrit: ++ if (st->have_temp_lcrit) ++ return 0444; ++ break; ++ case hwmon_temp_crit: ++ if (st->have_temp_crit) ++ return 0444; ++ break; ++ default: ++ break; ++ } ++ break; ++ default: ++ break; ++ } ++ return 0; ++} ++ ++static const struct hwmon_channel_info *drivetemp_info[] = { ++ HWMON_CHANNEL_INFO(chip, ++ HWMON_C_REGISTER_TZ), ++ HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | ++ HWMON_T_LOWEST | HWMON_T_HIGHEST | ++ HWMON_T_MIN | HWMON_T_MAX | ++ HWMON_T_LCRIT | HWMON_T_CRIT), ++ NULL ++}; ++ ++static const struct hwmon_ops drivetemp_ops = { ++ .is_visible = drivetemp_is_visible, ++ .read = drivetemp_read, ++}; ++ ++static const struct hwmon_chip_info drivetemp_chip_info = { ++ .ops = &drivetemp_ops, ++ .info = drivetemp_info, ++}; ++ ++/* ++ * The device argument points to sdev->sdev_dev. Its parent is ++ * sdev->sdev_gendev, which we can use to get the scsi_device pointer. ++ */ ++static int drivetemp_add(struct device *dev, struct class_interface *intf) ++{ ++ struct scsi_device *sdev = to_scsi_device(dev->parent); ++ struct drivetemp_data *st; ++ int err; ++ ++ st = kzalloc(sizeof(*st), GFP_KERNEL); ++ if (!st) ++ return -ENOMEM; ++ ++ st->sdev = sdev; ++ st->dev = dev; ++ mutex_init(&st->lock); ++ ++ if (drivetemp_identify(st)) { ++ err = -ENODEV; ++ goto abort; ++ } ++ ++ st->hwdev = hwmon_device_register_with_info(dev->parent, "drivetemp", ++ st, &drivetemp_chip_info, ++ NULL); ++ if (IS_ERR(st->hwdev)) { ++ err = PTR_ERR(st->hwdev); ++ goto abort; ++ } ++ ++ list_add(&st->list, &drivetemp_devlist); ++ return 0; ++ ++abort: ++ kfree(st); ++ return err; ++} ++ ++static void drivetemp_remove(struct device *dev, struct class_interface *intf) ++{ ++ struct drivetemp_data *st, *tmp; ++ ++ list_for_each_entry_safe(st, tmp, &drivetemp_devlist, list) { ++ if (st->dev == dev) { ++ list_del(&st->list); ++ hwmon_device_unregister(st->hwdev); ++ kfree(st); ++ break; ++ } ++ } ++} ++ ++static struct class_interface drivetemp_interface = { ++ .add_dev = drivetemp_add, ++ .remove_dev = drivetemp_remove, ++}; ++ ++static int __init drivetemp_init(void) ++{ ++ return scsi_register_interface(&drivetemp_interface); ++} ++ ++static void __exit drivetemp_exit(void) ++{ ++ scsi_unregister_interface(&drivetemp_interface); ++} ++ ++module_init(drivetemp_init); ++module_exit(drivetemp_exit); ++ ++MODULE_AUTHOR("Guenter Roeck <linus@roeck-us.net>"); ++MODULE_DESCRIPTION("Hard drive temperature monitor"); ++MODULE_LICENSE("GPL"); diff --git a/target/linux/generic/pending-5.4/820-libata-Assign-OF-node-to-the-SCSI-device.patch b/target/linux/generic/pending-5.4/820-libata-Assign-OF-node-to-the-SCSI-device.patch new file mode 100644 index 0000000000..9e72313ec9 --- /dev/null +++ b/target/linux/generic/pending-5.4/820-libata-Assign-OF-node-to-the-SCSI-device.patch @@ -0,0 +1,86 @@ +From 43a93893eb33e996836b99fb3e1f7300c0132a51 Mon Sep 17 00:00:00 2001 +From: Linus Walleij <linus.walleij@linaro.org> +Date: Tue, 31 Dec 2019 18:15:33 +0100 +Subject: [PATCH 5/7] libata: Assign OF node to the SCSI device + +When we spawn a SCSI device from an ATA device in libata-scsi +the SCSI device had no relation to the device tree. + +The DT binding allows us to define port nodes under a +PATA (IDE) or SATA host controller, so we can have proper device +nodes for these devices. + +If OF is enabled, walk the children of the host controller node +to see if there is a valid device tree node to assign. The reg +is used to match to ID 0 for the master device and ID 1 for the +slave device. + +The corresponding device tree bindings have been accepted by +the device tree maintainers. + +Cc: Chris Healy <cphealy@gmail.com> +Cc: Martin K. Petersen <martin.petersen@oracle.com> +Cc: Bart Van Assche <bvanassche@acm.org> +Cc: Guenter Roeck <linux@roeck-us.net> +Signed-off-by: Linus Walleij <linus.walleij@linaro.org> +--- +ChangeLog v1->v2: +- Use dev_dbg() for the debug print +- return immediately after finding a matching OF node +--- + drivers/ata/libata-scsi.c | 30 ++++++++++++++++++++++++++++++ + 1 file changed, 30 insertions(+) + +--- a/drivers/ata/libata-scsi.c ++++ b/drivers/ata/libata-scsi.c +@@ -35,6 +35,7 @@ + #include <linux/suspend.h> + #include <asm/unaligned.h> + #include <linux/ioprio.h> ++#include <linux/of.h> + + #include "libata.h" + #include "libata-transport.h" +@@ -4573,6 +4574,34 @@ int ata_scsi_add_hosts(struct ata_host * + return rc; + } + ++#ifdef CONFIG_OF ++static void ata_scsi_assign_ofnode(struct ata_device *dev, struct ata_port *ap) ++{ ++ struct scsi_device *sdev = dev->sdev; ++ struct device *d = ap->host->dev; ++ struct device_node *np = d->of_node; ++ struct device_node *child; ++ ++ for_each_available_child_of_node(np, child) { ++ int ret; ++ u32 val; ++ ++ ret = of_property_read_u32(child, "reg", &val); ++ if (ret) ++ continue; ++ if (val == dev->devno) { ++ dev_dbg(d, "found matching device node\n"); ++ sdev->sdev_gendev.of_node = child; ++ return; ++ } ++ } ++} ++#else ++static void ata_scsi_assign_ofnode(struct ata_device *dev, struct ata_port *ap) ++{ ++} ++#endif ++ + void ata_scsi_scan_host(struct ata_port *ap, int sync) + { + int tries = 5; +@@ -4598,6 +4627,7 @@ void ata_scsi_scan_host(struct ata_port + NULL); + if (!IS_ERR(sdev)) { + dev->sdev = sdev; ++ ata_scsi_assign_ofnode(dev, ap); + scsi_device_put(sdev); + } else { + dev->sdev = NULL; |