2019-11-06 06:35:18 -08:00
// SPDX-License-Identifier: GPL-2.0
/*
* NVM Express hardware monitoring support
* Copyright ( c ) 2019 , Guenter Roeck
*/
# include <linux/hwmon.h>
2020-01-30 22:15:53 -08:00
# include <linux/units.h>
2019-11-06 06:35:18 -08:00
# include <asm/unaligned.h>
# include "nvme.h"
struct nvme_hwmon_data {
struct nvme_ctrl * ctrl ;
struct nvme_smart_log log ;
struct mutex read_lock ;
} ;
2019-11-15 00:40:00 +09:00
static int nvme_get_temp_thresh ( struct nvme_ctrl * ctrl , int sensor , bool under ,
long * temp )
{
unsigned int threshold = sensor < < NVME_TEMP_THRESH_SELECT_SHIFT ;
u32 status ;
int ret ;
if ( under )
threshold | = NVME_TEMP_THRESH_TYPE_UNDER ;
ret = nvme_get_features ( ctrl , NVME_FEAT_TEMP_THRESH , threshold , NULL , 0 ,
& status ) ;
if ( ret > 0 )
return - EIO ;
if ( ret < 0 )
return ret ;
2020-01-30 22:15:53 -08:00
* temp = kelvin_to_millicelsius ( status & NVME_TEMP_THRESH_MASK ) ;
2019-11-15 00:40:00 +09:00
return 0 ;
}
static int nvme_set_temp_thresh ( struct nvme_ctrl * ctrl , int sensor , bool under ,
long temp )
{
unsigned int threshold = sensor < < NVME_TEMP_THRESH_SELECT_SHIFT ;
int ret ;
2020-01-30 22:15:53 -08:00
temp = millicelsius_to_kelvin ( temp ) ;
2019-11-15 00:40:00 +09:00
threshold | = clamp_val ( temp , 0 , NVME_TEMP_THRESH_MASK ) ;
if ( under )
threshold | = NVME_TEMP_THRESH_TYPE_UNDER ;
ret = nvme_set_features ( ctrl , NVME_FEAT_TEMP_THRESH , threshold , NULL , 0 ,
NULL ) ;
if ( ret > 0 )
return - EIO ;
return ret ;
}
2019-11-06 06:35:18 -08:00
static int nvme_hwmon_get_smart_log ( struct nvme_hwmon_data * data )
{
2020-09-17 08:50:25 -07:00
return nvme_get_log ( data - > ctrl , NVME_NSID_ALL , NVME_LOG_SMART , 0 ,
2020-06-29 12:06:40 -07:00
NVME_CSI_NVM , & data - > log , sizeof ( data - > log ) , 0 ) ;
2019-11-06 06:35:18 -08:00
}
static int nvme_hwmon_read ( struct device * dev , enum hwmon_sensor_types type ,
u32 attr , int channel , long * val )
{
struct nvme_hwmon_data * data = dev_get_drvdata ( dev ) ;
struct nvme_smart_log * log = & data - > log ;
int temp ;
int err ;
/*
* First handle attributes which don ' t require us to read
* the smart log .
*/
switch ( attr ) {
case hwmon_temp_max :
2019-11-15 00:40:00 +09:00
return nvme_get_temp_thresh ( data - > ctrl , channel , false , val ) ;
case hwmon_temp_min :
return nvme_get_temp_thresh ( data - > ctrl , channel , true , val ) ;
2019-11-06 06:35:18 -08:00
case hwmon_temp_crit :
2020-01-30 22:15:53 -08:00
* val = kelvin_to_millicelsius ( data - > ctrl - > cctemp ) ;
2019-11-06 06:35:18 -08:00
return 0 ;
default :
break ;
}
mutex_lock ( & data - > read_lock ) ;
err = nvme_hwmon_get_smart_log ( data ) ;
if ( err )
goto unlock ;
switch ( attr ) {
case hwmon_temp_input :
if ( ! channel )
temp = get_unaligned_le16 ( log - > temperature ) ;
else
temp = le16_to_cpu ( log - > temp_sensor [ channel - 1 ] ) ;
2020-01-30 22:15:53 -08:00
* val = kelvin_to_millicelsius ( temp ) ;
2019-11-06 06:35:18 -08:00
break ;
case hwmon_temp_alarm :
* val = ! ! ( log - > critical_warning & NVME_SMART_CRIT_TEMPERATURE ) ;
break ;
default :
err = - EOPNOTSUPP ;
break ;
}
unlock :
mutex_unlock ( & data - > read_lock ) ;
return err ;
}
2019-11-15 00:40:00 +09:00
static int nvme_hwmon_write ( struct device * dev , enum hwmon_sensor_types type ,
u32 attr , int channel , long val )
{
struct nvme_hwmon_data * data = dev_get_drvdata ( dev ) ;
switch ( attr ) {
case hwmon_temp_max :
return nvme_set_temp_thresh ( data - > ctrl , channel , false , val ) ;
case hwmon_temp_min :
return nvme_set_temp_thresh ( data - > ctrl , channel , true , val ) ;
default :
break ;
}
return - EOPNOTSUPP ;
}
2019-11-06 06:35:18 -08:00
static const char * const nvme_hwmon_sensor_names [ ] = {
" Composite " ,
" Sensor 1 " ,
" Sensor 2 " ,
" Sensor 3 " ,
" Sensor 4 " ,
" Sensor 5 " ,
" Sensor 6 " ,
" Sensor 7 " ,
" Sensor 8 " ,
} ;
static int nvme_hwmon_read_string ( struct device * dev ,
enum hwmon_sensor_types type , u32 attr ,
int channel , const char * * str )
{
* str = nvme_hwmon_sensor_names [ channel ] ;
return 0 ;
}
static umode_t nvme_hwmon_is_visible ( const void * _data ,
enum hwmon_sensor_types type ,
u32 attr , int channel )
{
const struct nvme_hwmon_data * data = _data ;
switch ( attr ) {
case hwmon_temp_crit :
if ( ! channel & & data - > ctrl - > cctemp )
return 0444 ;
break ;
case hwmon_temp_max :
2019-11-15 00:40:00 +09:00
case hwmon_temp_min :
if ( ( ! channel & & data - > ctrl - > wctemp ) | |
2019-11-15 00:40:01 +09:00
( channel & & data - > log . temp_sensor [ channel - 1 ] ) ) {
if ( data - > ctrl - > quirks &
NVME_QUIRK_NO_TEMP_THRESH_CHANGE )
return 0444 ;
2019-11-15 00:40:00 +09:00
return 0644 ;
2019-11-15 00:40:01 +09:00
}
2019-11-06 06:35:18 -08:00
break ;
case hwmon_temp_alarm :
if ( ! channel )
return 0444 ;
break ;
case hwmon_temp_input :
case hwmon_temp_label :
if ( ! channel | | data - > log . temp_sensor [ channel - 1 ] )
return 0444 ;
break ;
default :
break ;
}
return 0 ;
}
static const struct hwmon_channel_info * nvme_hwmon_info [ ] = {
HWMON_CHANNEL_INFO ( chip , HWMON_C_REGISTER_TZ ) ,
HWMON_CHANNEL_INFO ( temp ,
2019-11-15 00:40:00 +09:00
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN |
HWMON_T_CRIT | HWMON_T_LABEL | HWMON_T_ALARM ,
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN |
HWMON_T_LABEL ,
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN |
HWMON_T_LABEL ,
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN |
HWMON_T_LABEL ,
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN |
HWMON_T_LABEL ,
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN |
HWMON_T_LABEL ,
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN |
HWMON_T_LABEL ,
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN |
HWMON_T_LABEL ,
HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN |
HWMON_T_LABEL ) ,
2019-11-06 06:35:18 -08:00
NULL
} ;
static const struct hwmon_ops nvme_hwmon_ops = {
. is_visible = nvme_hwmon_is_visible ,
. read = nvme_hwmon_read ,
. read_string = nvme_hwmon_read_string ,
2019-11-15 00:40:00 +09:00
. write = nvme_hwmon_write ,
2019-11-06 06:35:18 -08:00
} ;
static const struct hwmon_chip_info nvme_hwmon_chip_info = {
. ops = & nvme_hwmon_ops ,
. info = nvme_hwmon_info ,
} ;
2020-09-17 08:50:25 -07:00
int nvme_hwmon_init ( struct nvme_ctrl * ctrl )
2019-11-06 06:35:18 -08:00
{
2021-01-19 07:43:18 +01:00
struct device * dev = ctrl - > device ;
2019-11-06 06:35:18 -08:00
struct nvme_hwmon_data * data ;
struct device * hwmon ;
int err ;
2021-01-19 07:43:18 +01:00
data = kzalloc ( sizeof ( * data ) , GFP_KERNEL ) ;
2019-11-06 06:35:18 -08:00
if ( ! data )
2020-09-17 08:50:25 -07:00
return 0 ;
2019-11-06 06:35:18 -08:00
data - > ctrl = ctrl ;
mutex_init ( & data - > read_lock ) ;
err = nvme_hwmon_get_smart_log ( data ) ;
if ( err ) {
2021-01-19 07:43:18 +01:00
dev_warn ( dev , " Failed to read smart log (error %d) \n " , err ) ;
kfree ( data ) ;
2020-09-17 08:50:25 -07:00
return err ;
2019-11-06 06:35:18 -08:00
}
2021-01-19 07:43:18 +01:00
hwmon = hwmon_device_register_with_info ( dev , " nvme " ,
data , & nvme_hwmon_chip_info ,
NULL ) ;
2019-11-06 06:35:18 -08:00
if ( IS_ERR ( hwmon ) ) {
dev_warn ( dev , " Failed to instantiate hwmon device \n " ) ;
2021-01-19 07:43:18 +01:00
kfree ( data ) ;
2021-02-12 10:30:15 +01:00
return PTR_ERR ( hwmon ) ;
2019-11-06 06:35:18 -08:00
}
2021-01-19 07:43:18 +01:00
ctrl - > hwmon_device = hwmon ;
2020-09-17 08:50:25 -07:00
return 0 ;
2019-11-06 06:35:18 -08:00
}
2021-01-19 07:43:18 +01:00
void nvme_hwmon_exit ( struct nvme_ctrl * ctrl )
{
if ( ctrl - > hwmon_device ) {
struct nvme_hwmon_data * data =
dev_get_drvdata ( ctrl - > hwmon_device ) ;
hwmon_device_unregister ( ctrl - > hwmon_device ) ;
ctrl - > hwmon_device = NULL ;
kfree ( data ) ;
}
}