diff --git a/drivers/scsi/mpt3sas/Makefile b/drivers/scsi/mpt3sas/Makefile index e76d994dbed3..18e2d87eb4a2 100644 --- a/drivers/scsi/mpt3sas/Makefile +++ b/drivers/scsi/mpt3sas/Makefile @@ -9,3 +9,5 @@ mpt3sas-y += mpt3sas_base.o \ mpt3sas_trigger_diag.o \ mpt3sas_warpdrive.o \ mpt3sas_debugfs.o \ + +mpt3sas-$(CONFIG_HWMON) += mpt3sas_hwmon.o diff --git a/drivers/scsi/mpt3sas/mpt3sas_base.h b/drivers/scsi/mpt3sas/mpt3sas_base.h index d4597d058705..47255bf9cdda 100644 --- a/drivers/scsi/mpt3sas/mpt3sas_base.h +++ b/drivers/scsi/mpt3sas/mpt3sas_base.h @@ -1629,6 +1629,9 @@ struct MPT3SAS_ADAPTER { u8 is_aero_ioc; struct dentry *debugfs_root; struct dentry *ioc_dump; +#if IS_REACHABLE(CONFIG_HWMON) + struct mpt3sas_hwmon *hwmon; +#endif PUT_SMID_IO_FP_HIP put_smid_scsi_io; PUT_SMID_IO_FP_HIP put_smid_fast_path; PUT_SMID_IO_FP_HIP put_smid_hi_priority; @@ -1904,6 +1907,9 @@ int mpt3sas_config_get_iounit_pg3(struct MPT3SAS_ADAPTER *ioc, Mpi2ConfigReply_t *mpi_reply, Mpi2IOUnitPage3_t *config_page, u16 sz); int mpt3sas_config_set_iounit_pg1(struct MPT3SAS_ADAPTER *ioc, Mpi2ConfigReply_t *mpi_reply, Mpi2IOUnitPage1_t *config_page); +int mpt3sas_config_get_iounit_pg7(struct MPT3SAS_ADAPTER *ioc, + Mpi2ConfigReply_t *mpi_reply, + Mpi2IOUnitPage7_t *config_page); int mpt3sas_config_get_iounit_pg8(struct MPT3SAS_ADAPTER *ioc, Mpi2ConfigReply_t *mpi_reply, Mpi2IOUnitPage8_t *config_page); int mpt3sas_config_get_sas_iounit_pg1(struct MPT3SAS_ADAPTER *ioc, @@ -2047,6 +2053,20 @@ void mpt3sas_destroy_debugfs(struct MPT3SAS_ADAPTER *ioc); void mpt3sas_init_debugfs(void); void mpt3sas_exit_debugfs(void); +#if IS_REACHABLE(CONFIG_HWMON) +int mpt3sas_hwmon_register(struct MPT3SAS_ADAPTER *ioc); +void mpt3sas_hwmon_unregister(struct MPT3SAS_ADAPTER *ioc); +#else +static inline int mpt3sas_hwmon_register(struct MPT3SAS_ADAPTER *ioc) +{ + return 0; +} + +static inline void mpt3sas_hwmon_unregister(struct MPT3SAS_ADAPTER *ioc) +{ +} +#endif + /** * _scsih_is_pcie_scsi_device - determines if device is an pcie scsi device * @device_info: bitfield providing information about the device. diff --git a/drivers/scsi/mpt3sas/mpt3sas_config.c b/drivers/scsi/mpt3sas/mpt3sas_config.c index 45ac853e1289..b0d5ef893600 100644 --- a/drivers/scsi/mpt3sas/mpt3sas_config.c +++ b/drivers/scsi/mpt3sas/mpt3sas_config.c @@ -991,6 +991,42 @@ mpt3sas_config_get_iounit_pg3(struct MPT3SAS_ADAPTER *ioc, return r; } +/** + * mpt3sas_config_get_iounit_pg7 - obtain iounit page 7 + * @ioc: per adapter object + * @mpi_reply: reply mf payload returned from firmware + * @config_page: contents of the config page + * Context: sleep. + * + * Return: 0 for success, non-zero for failure. + */ +int mpt3sas_config_get_iounit_pg7(struct MPT3SAS_ADAPTER *ioc, + Mpi2ConfigReply_t *mpi_reply, + Mpi2IOUnitPage7_t *config_page) +{ + Mpi2ConfigRequest_t mpi_request; + int r; + + memset(&mpi_request, 0, sizeof(Mpi2ConfigRequest_t)); + mpi_request.Function = MPI2_FUNCTION_CONFIG; + mpi_request.Action = MPI2_CONFIG_ACTION_PAGE_HEADER; + mpi_request.Header.PageType = MPI2_CONFIG_PAGETYPE_IO_UNIT; + mpi_request.Header.PageNumber = 7; + mpi_request.Header.PageVersion = MPI2_IOUNITPAGE7_PAGEVERSION; + ioc->build_zero_len_sge_mpi(ioc, &mpi_request.PageBufferSGE); + r = _config_request(ioc, &mpi_request, mpi_reply, + MPT3_CONFIG_PAGE_DEFAULT_TIMEOUT, NULL, 0); + if (r) + goto out; + + mpi_request.Action = MPI2_CONFIG_ACTION_PAGE_READ_CURRENT; + r = _config_request(ioc, &mpi_request, mpi_reply, + MPT3_CONFIG_PAGE_DEFAULT_TIMEOUT, + config_page, sizeof(*config_page)); + out: + return r; +} + /** * mpt3sas_config_get_iounit_pg8 - obtain iounit page 8 * @ioc: per adapter object diff --git a/drivers/scsi/mpt3sas/mpt3sas_hwmon.c b/drivers/scsi/mpt3sas/mpt3sas_hwmon.c new file mode 100644 index 000000000000..297a8efecadb --- /dev/null +++ b/drivers/scsi/mpt3sas/mpt3sas_hwmon.c @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Hardware monitoring (hwmon) support for the LSI / Broadcom mpt3sas + * SAS HBA driver. Exposes the IOC and board temperature sensors by + * reading MPI IO Unit Page 7. + */ + +#include +#include +#include + +#include "mpt3sas_base.h" + +struct mpt3sas_hwmon { + struct MPT3SAS_ADAPTER *ioc; + struct device *hwmon_dev; + bool ioc_present; + bool board_present; +}; + +/* + * Convert a (raw, units) reading to millidegrees Celsius. + * Returns -ENODATA when the sensor reports "not present" or + * unknown units. Temperature values are interpreted as signed + * two's-complement integers. + * + * The MPI2_IOUNITPAGE7_IOC_TEMP_* and MPI2_IOUNITPAGE7_BOARD_TEMP_* + * defines in mpi2_cnfg.h share the same values; the IOC ones are + * used for both channels. + */ +static int _hwmon_to_mdegc(s16 raw, u8 units, long *out) +{ + switch (units) { + case MPI2_IOUNITPAGE7_IOC_TEMP_CELSIUS: + *out = (long)raw * 1000; + return 0; + case MPI2_IOUNITPAGE7_IOC_TEMP_FAHRENHEIT: + /* (F - 32) * 5 / 9, expressed in milli-units */ + *out = ((long)raw - 32) * 5000 / 9; + return 0; + default: + return -ENODATA; + } +} + +static umode_t _hwmon_is_visible(const void *drvdata, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + const struct mpt3sas_hwmon *h = drvdata; + + if (type != hwmon_temp) + return 0; + if (attr != hwmon_temp_input && attr != hwmon_temp_label) + return 0; + if (channel == 0 && h->ioc_present) + return 0444; + if (channel == 1 && h->board_present) + return 0444; + return 0; +} + +static int _hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct mpt3sas_hwmon *h = dev_get_drvdata(dev); + Mpi2ConfigReply_t mpi_reply; + Mpi2IOUnitPage7_t page; + int r; + + if (type != hwmon_temp || attr != hwmon_temp_input) + return -EOPNOTSUPP; + + r = mpt3sas_config_get_iounit_pg7(h->ioc, &mpi_reply, &page); + if (r) + return r; + + if (channel == 0) + return _hwmon_to_mdegc((s16)le16_to_cpu(page.IOCTemperature), + page.IOCTemperatureUnits, val); + if (channel == 1) + return _hwmon_to_mdegc((s16)le16_to_cpu(page.BoardTemperature), + page.BoardTemperatureUnits, val); + return -EOPNOTSUPP; +} + +static const char * const mpt3sas_hwmon_temp_labels[] = { + "IOC", + "Board", +}; + +static int _hwmon_read_string(struct device *dev, + enum hwmon_sensor_types type, + u32 attr, int channel, const char **str) +{ + if (type != hwmon_temp || attr != hwmon_temp_label) + return -EOPNOTSUPP; + *str = mpt3sas_hwmon_temp_labels[channel]; + return 0; +} + +static const struct hwmon_channel_info * const mpt3sas_hwmon_info[] = { + HWMON_CHANNEL_INFO(temp, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL), + NULL, +}; + +static const struct hwmon_ops mpt3sas_hwmon_ops = { + .is_visible = _hwmon_is_visible, + .read = _hwmon_read, + .read_string = _hwmon_read_string, +}; + +static const struct hwmon_chip_info mpt3sas_hwmon_chip_info = { + .ops = &mpt3sas_hwmon_ops, + .info = mpt3sas_hwmon_info, +}; + +/** + * mpt3sas_hwmon_register - register an hwmon device for the IOC + * @ioc: per adapter object + * Context: sleep. + * + * Succeeds without registering when no temperature sensors are present, + * so cards without thermal monitoring do not expose an empty hwmon node. + * Paired with mpt3sas_hwmon_unregister() from the driver's remove path. + * + * Return: 0 for success, non-zero for failure. + */ +int mpt3sas_hwmon_register(struct MPT3SAS_ADAPTER *ioc) +{ + struct device *parent = &ioc->pdev->dev; + struct mpt3sas_hwmon *h; + struct device *hwdev; + Mpi2ConfigReply_t mpi_reply; + Mpi2IOUnitPage7_t page; + int r; + + r = mpt3sas_config_get_iounit_pg7(ioc, &mpi_reply, &page); + if (r) + return r; + + /* + * A page where both *TemperatureUnits are NOT_PRESENT covers + * two cases: cards that genuinely lack sensors, and firmware + * errors that left the page zero-filled (the accessor mirrors + * _config_request() behaviour). Either way: skip registration. + */ + if (page.IOCTemperatureUnits == MPI2_IOUNITPAGE7_IOC_TEMP_NOT_PRESENT && + page.BoardTemperatureUnits == MPI2_IOUNITPAGE7_BOARD_TEMP_NOT_PRESENT) + return 0; + + h = kzalloc_obj(*h); + if (!h) + return -ENOMEM; + + h->ioc = ioc; + h->ioc_present = page.IOCTemperatureUnits != MPI2_IOUNITPAGE7_IOC_TEMP_NOT_PRESENT; + h->board_present = page.BoardTemperatureUnits != MPI2_IOUNITPAGE7_BOARD_TEMP_NOT_PRESENT; + + hwdev = hwmon_device_register_with_info(parent, "mpt3sas", h, + &mpt3sas_hwmon_chip_info, + NULL); + if (IS_ERR(hwdev)) { + kfree(h); + return PTR_ERR(hwdev); + } + + h->hwmon_dev = hwdev; + ioc->hwmon = h; + return 0; +} + +/** + * mpt3sas_hwmon_unregister - tear down the hwmon device, if any + * @ioc: per adapter object + * + * Safe to call when registration was skipped (no sensors) or + * failed; in those cases ioc->hwmon is NULL and this is a no-op. + */ +void mpt3sas_hwmon_unregister(struct MPT3SAS_ADAPTER *ioc) +{ + struct mpt3sas_hwmon *h = ioc->hwmon; + + if (!h) + return; + hwmon_device_unregister(h->hwmon_dev); + kfree(h); + ioc->hwmon = NULL; +} diff --git a/drivers/scsi/mpt3sas/mpt3sas_scsih.c b/drivers/scsi/mpt3sas/mpt3sas_scsih.c index 12caffeed3a0..dea78688cc9b 100644 --- a/drivers/scsi/mpt3sas/mpt3sas_scsih.c +++ b/drivers/scsi/mpt3sas/mpt3sas_scsih.c @@ -12562,6 +12562,7 @@ static void scsih_remove(struct pci_dev *pdev) /* release all the volumes */ _scsih_ir_shutdown(ioc); mpt3sas_destroy_debugfs(ioc); + mpt3sas_hwmon_unregister(ioc); sas_remove_host(shost); list_for_each_entry_safe(raid_device, next, &ioc->raid_device_list, list) { @@ -13651,6 +13652,11 @@ _scsih_probe(struct pci_dev *pdev, const struct pci_device_id *id) } scsi_scan_host(shost); + + if (mpt3sas_hwmon_register(ioc)) + ioc_warn(ioc, + "hwmon registration failed; temperatures not exposed\n"); + mpt3sas_setup_debugfs(ioc); return 0; out_add_shost_fail: