diff mbox series

[UPDATE] ACPI / fan: Display fan performance state information

Message ID 20191205012309.23868-1-srinivas.pandruvada@linux.intel.com (mailing list archive)
State Changes Requested, archived
Headers show
Series [UPDATE] ACPI / fan: Display fan performance state information | expand

Commit Message

srinivas pandruvada Dec. 5, 2019, 1:23 a.m. UTC
When _FPS object indicates support of variable speed fan, thermal cooling
devices for fan shows max performance state count using attribute
"max_state" greater than or equal to 1.

But the thermal cooling device doesn't display properties of each
performance state. This is not enough for smart fan control user space
software, which also considers speed, power and noise level.

This change presents fan performance states attributes under acpi
device for the fan. This will be under:
/sys/bus/acpi/devices/devices/INT3404:00
or
/sys/bus/platform/devices/PNP0C0B:00.

For more information refer to:
Documentation/acpi/fan_performance_states.txt

Signed-off-by: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
Tested-by: Sumeet Pawnikar <sumeet.r.pawnikar@intel.com>
---
updated version:
	Removed one unnecessary platoform_device* conversion from
	acpi_device* in the function argument.

 Documentation/acpi/fan_performance_states.txt |  39 +++++++
 drivers/acpi/fan.c                            | 106 ++++++++++++++++--
 2 files changed, 138 insertions(+), 7 deletions(-)
 create mode 100644 Documentation/acpi/fan_performance_states.txt

Comments

Rafael J. Wysocki Dec. 12, 2019, 9:58 p.m. UTC | #1
On Thursday, December 5, 2019 2:23:09 AM CET Srinivas Pandruvada wrote:
> When _FPS object indicates support of variable speed fan, thermal cooling
> devices for fan shows max performance state count using attribute
> "max_state" greater than or equal to 1.
> 
> But the thermal cooling device doesn't display properties of each
> performance state. This is not enough for smart fan control user space
> software, which also considers speed, power and noise level.
> 
> This change presents fan performance states attributes under acpi
> device for the fan. This will be under:
> /sys/bus/acpi/devices/devices/INT3404:00
> or
> /sys/bus/platform/devices/PNP0C0B:00.
> 
> For more information refer to:
> Documentation/acpi/fan_performance_states.txt
> 
> Signed-off-by: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
> Tested-by: Sumeet Pawnikar <sumeet.r.pawnikar@intel.com>
> ---
> updated version:
> 	Removed one unnecessary platoform_device* conversion from
> 	acpi_device* in the function argument.
> 
>  Documentation/acpi/fan_performance_states.txt |  39 +++++++

Documentation/acpi/ is not there any more.

It looks like this could go into admin-guide/acpi/ in the RST format.

>  drivers/acpi/fan.c                            | 106 ++++++++++++++++--
>  2 files changed, 138 insertions(+), 7 deletions(-)
>  create mode 100644 Documentation/acpi/fan_performance_states.txt
> 
> diff --git a/Documentation/acpi/fan_performance_states.txt b/Documentation/acpi/fan_performance_states.txt
> new file mode 100644
> index 000000000000..06fd8eb002f2
> --- /dev/null
> +++ b/Documentation/acpi/fan_performance_states.txt
> @@ -0,0 +1,39 @@
> +When the optional object _FPS is present for ACPI devices PNP0C0B or INT3404,
> +additional attributes are displayed under acpi device object. These attributes
> +display information about each performance state.
> +
> +For example
> +$ ls /sys/bus/acpi/devices/INT3404\:00
> +description        fan_perf_state_11  fan_perf_state_5  fan_perf_state_9  physical_node  uevent
> +fan_perf_state_0   fan_perf_state_2   fan_perf_state_6  hid               power          uid
> +fan_perf_state_1   fan_perf_state_3   fan_perf_state_7  modalias          status         wakeup
> +fan_perf_state_10  fan_perf_state_4   fan_perf_state_8  path              subsystem

This looks kind of messy IMO.

I would drop the "fan_perf_" prefix and the underline between "state" and the
number, so they become "state0" through "state11".

There shouldn't be any confusion related to that.

> +
> +Each fan_perf_state_* contains the information about the fields for each state as
> +defined by the ACPI specification.
> +
> +For example
> +$ grep . /sys/bus/acpi/devices/INT3404\:00/fan_perf_state_1/*
> +/sys/bus/acpi/devices/INT3404:00/fan_perf_state_1/control_percent:25
> +/sys/bus/acpi/devices/INT3404:00/fan_perf_state_1/noise_level_mdb:12500
> +/sys/bus/acpi/devices/INT3404:00/fan_perf_state_1/power_mw:1250
> +/sys/bus/acpi/devices/INT3404:00/fan_perf_state_1/speed_rpm:3200
> +/sys/bus/acpi/devices/INT3404:00/fan_perf_state_1/trip_point_index:invalid

Alternatively, the states could be represented as individual files each
in the

control_percent:noise_level_mdb:power_mw:speed_rpm:trip_point_index

format (ie. colon-separated list of values).  Then you'd not need to
use the new ktype etc.

> +
> +Fields
> +control_percent: Indicates the value to be used to set the fan speed to a
> +specific level using the _FSL object. The value here is from 0-100 percent.
> +
> +noise_level_mdb: Indicates the audible noise emitted by the fan. By the
> +specification the value represents the noise in 10ths of decibels. Here
> +it is multiplied with 100 to present in milli-db, to avoid loss of
> +precision. When not populated, "not-defined" is displayed.
> +
> +speed_rpm: Indicates the speed of the fan in revolutions per minute.
> +
> +power_mw: Indicates the power consumption in milliwatts. When not populated,
> +"not-defined" is displayed.
> +
> +trip_point_index: The active cooling trip point number that corresponds to this
> +performance state. The range is from 0-9. For any other values, "invalid" is
> +be displayed.
> diff --git a/drivers/acpi/fan.c b/drivers/acpi/fan.c
> index 816b0803f7fb..f5e9f67e6a5f 100644
> --- a/drivers/acpi/fan.c
> +++ b/drivers/acpi/fan.c
> @@ -50,6 +50,7 @@ struct acpi_fan_fps {
>  	u64 speed;
>  	u64 noise_level;
>  	u64 power;
> +	struct kobject kobj;
>  };
>  
>  struct acpi_fan_fif {
> @@ -265,6 +266,64 @@ static int acpi_fan_speed_cmp(const void *a, const void *b)
>  	return fps1->speed - fps2->speed;
>  }
>  
> +#define to_fps_state(k) container_of(k, struct acpi_fan_fps, kobj)
> +
> +#define DEFINE_ONE_FPS_ATTR_RO(_name)\
> +	static struct kobj_attribute _name =\
> +		__ATTR(_name, 0444, show_##_name, NULL)
> +
> +#define FPS_INFO_SHOW(name, object, multiplier)\
> +static ssize_t show_##name(struct kobject *kobj,\
> +			   struct kobj_attribute *attr,\
> +			   char *buf)\
> +{\
> +	struct acpi_fan_fps *fps = to_fps_state(kobj);\
> +\
> +	if (fps->object == 0xFFFFFFFF)\
> +		return sprintf(buf, "not-defined\n");\
> +\
> +	return sprintf(buf, "%llu\n", multiplier * fps->object);\
> +}
> +
> +FPS_INFO_SHOW(control_percent, control, 1)
> +FPS_INFO_SHOW(speed_rpm, speed, 1)
> +FPS_INFO_SHOW(power_mw, power, 1)
> +FPS_INFO_SHOW(noise_level_mdb, noise_level, 100)
> +
> +static ssize_t show_trip_point_index(struct kobject *kobj,
> +				     struct kobj_attribute *attr,
> +				     char *buf)
> +{
> +	struct acpi_fan_fps *fps = to_fps_state(kobj);
> +
> +	if (fps->trip_point > 9)
> +		return sprintf(buf, "invalid\n");
> +
> +	return sprintf(buf, "%llu\n", fps->trip_point);
> +}
> +
> +DEFINE_ONE_FPS_ATTR_RO(control_percent);
> +DEFINE_ONE_FPS_ATTR_RO(trip_point_index);
> +DEFINE_ONE_FPS_ATTR_RO(speed_rpm);
> +DEFINE_ONE_FPS_ATTR_RO(noise_level_mdb);
> +DEFINE_ONE_FPS_ATTR_RO(power_mw);
> +
> +static struct attribute *fps_attrs[] = {
> +	&control_percent.attr,
> +	&trip_point_index.attr,
> +	&speed_rpm.attr,
> +	&noise_level_mdb.attr,
> +	&power_mw.attr,
> +	NULL
> +};
> +
> +static struct kobj_type fps_ktype = {
> +	.sysfs_ops = &kobj_sysfs_ops,
> +	.default_attrs = fps_attrs,
> +};
> +
> +#define ACPI_FPS_NAME_LEN	20
> +
>  static int acpi_fan_get_fps(struct acpi_device *device)
>  {
>  	struct acpi_fan *fan = acpi_driver_data(device);
> @@ -295,12 +354,13 @@ static int acpi_fan_get_fps(struct acpi_device *device)
>  	}
>  	for (i = 0; i < fan->fps_count; i++) {
>  		struct acpi_buffer format = { sizeof("NNNNN"), "NNNNN" };
> -		struct acpi_buffer fps = { sizeof(fan->fps[i]), &fan->fps[i] };
> +		struct acpi_buffer fps = { offsetof(struct acpi_fan_fps, kobj),
> +						&fan->fps[i] };
>  		status = acpi_extract_package(&obj->package.elements[i + 1],
>  					      &format, &fps);
>  		if (ACPI_FAILURE(status)) {
>  			dev_err(&device->dev, "Invalid _FPS element\n");
> -			break;
> +			goto err;
>  		}
>  	}
>  
> @@ -308,6 +368,21 @@ static int acpi_fan_get_fps(struct acpi_device *device)
>  	sort(fan->fps, fan->fps_count, sizeof(*fan->fps),
>  	     acpi_fan_speed_cmp, NULL);
>  
> +	for (i = 0; i < fan->fps_count; ++i) {
> +		char name[ACPI_FPS_NAME_LEN];
> +
> +		snprintf(name, ACPI_FPS_NAME_LEN, "fan_perf_state_%d", i);
> +		status = kobject_init_and_add(&fan->fps[i].kobj, &fps_ktype,
> +					      &device->dev.kobj, name);
> +		if (status) {
> +			int j;
> +
> +			for (j = 0; j < i; ++j)
> +				kobject_put(&fan->fps[j].kobj);
> +			break;
> +		}
> +	}
> +
>  err:
>  	kfree(obj);
>  	return status;
> @@ -331,13 +406,13 @@ static int acpi_fan_probe(struct platform_device *pdev)
>  
>  	if (acpi_fan_is_acpi4(device)) {
>  		if (acpi_fan_get_fif(device) || acpi_fan_get_fps(device))
> -			goto end;
> +			goto err_end;
>  		fan->acpi4 = true;
>  	} else {
>  		result = acpi_device_update_power(device, NULL);
>  		if (result) {
>  			dev_err(&device->dev, "Failed to set initial power state\n");
> -			goto end;
> +			goto err_end;
>  		}
>  	}
>  
> @@ -350,7 +425,7 @@ static int acpi_fan_probe(struct platform_device *pdev)
>  						&fan_cooling_ops);
>  	if (IS_ERR(cdev)) {
>  		result = PTR_ERR(cdev);
> -		goto end;
> +		goto err_end;
>  	}
>  
>  	dev_dbg(&pdev->dev, "registered as cooling_device%d\n", cdev->id);
> @@ -365,10 +440,21 @@ static int acpi_fan_probe(struct platform_device *pdev)
>  	result = sysfs_create_link(&cdev->device.kobj,
>  				   &pdev->dev.kobj,
>  				   "device");
> -	if (result)
> +	if (result) {
>  		dev_err(&pdev->dev, "Failed to create sysfs link 'device'\n");
> +		goto err_end;
> +	}
> +
> +	return 0;
> +
> +err_end:
> +	if (fan->acpi4) {
> +		int i;
> +
> +		for (i = 0; i < fan->fps_count; ++i)
> +			kobject_put(&fan->fps[i].kobj);
> +	}
>  
> -end:
>  	return result;
>  }
>  
> @@ -376,6 +462,12 @@ static int acpi_fan_remove(struct platform_device *pdev)
>  {
>  	struct acpi_fan *fan = platform_get_drvdata(pdev);
>  
> +	if (fan->acpi4) {
> +		int i;
> +
> +		for (i = 0; i < fan->fps_count; ++i)
> +			kobject_put(&fan->fps[i].kobj);
> +	}
>  	sysfs_remove_link(&pdev->dev.kobj, "thermal_cooling");
>  	sysfs_remove_link(&fan->cdev->device.kobj, "device");
>  	thermal_cooling_device_unregister(fan->cdev);
>
diff mbox series

Patch

diff --git a/Documentation/acpi/fan_performance_states.txt b/Documentation/acpi/fan_performance_states.txt
new file mode 100644
index 000000000000..06fd8eb002f2
--- /dev/null
+++ b/Documentation/acpi/fan_performance_states.txt
@@ -0,0 +1,39 @@ 
+When the optional object _FPS is present for ACPI devices PNP0C0B or INT3404,
+additional attributes are displayed under acpi device object. These attributes
+display information about each performance state.
+
+For example
+$ ls /sys/bus/acpi/devices/INT3404\:00
+description        fan_perf_state_11  fan_perf_state_5  fan_perf_state_9  physical_node  uevent
+fan_perf_state_0   fan_perf_state_2   fan_perf_state_6  hid               power          uid
+fan_perf_state_1   fan_perf_state_3   fan_perf_state_7  modalias          status         wakeup
+fan_perf_state_10  fan_perf_state_4   fan_perf_state_8  path              subsystem
+
+Each fan_perf_state_* contains the information about the fields for each state as
+defined by the ACPI specification.
+
+For example
+$ grep . /sys/bus/acpi/devices/INT3404\:00/fan_perf_state_1/*
+/sys/bus/acpi/devices/INT3404:00/fan_perf_state_1/control_percent:25
+/sys/bus/acpi/devices/INT3404:00/fan_perf_state_1/noise_level_mdb:12500
+/sys/bus/acpi/devices/INT3404:00/fan_perf_state_1/power_mw:1250
+/sys/bus/acpi/devices/INT3404:00/fan_perf_state_1/speed_rpm:3200
+/sys/bus/acpi/devices/INT3404:00/fan_perf_state_1/trip_point_index:invalid
+
+Fields
+control_percent: Indicates the value to be used to set the fan speed to a
+specific level using the _FSL object. The value here is from 0-100 percent.
+
+noise_level_mdb: Indicates the audible noise emitted by the fan. By the
+specification the value represents the noise in 10ths of decibels. Here
+it is multiplied with 100 to present in milli-db, to avoid loss of
+precision. When not populated, "not-defined" is displayed.
+
+speed_rpm: Indicates the speed of the fan in revolutions per minute.
+
+power_mw: Indicates the power consumption in milliwatts. When not populated,
+"not-defined" is displayed.
+
+trip_point_index: The active cooling trip point number that corresponds to this
+performance state. The range is from 0-9. For any other values, "invalid" is
+be displayed.
diff --git a/drivers/acpi/fan.c b/drivers/acpi/fan.c
index 816b0803f7fb..f5e9f67e6a5f 100644
--- a/drivers/acpi/fan.c
+++ b/drivers/acpi/fan.c
@@ -50,6 +50,7 @@  struct acpi_fan_fps {
 	u64 speed;
 	u64 noise_level;
 	u64 power;
+	struct kobject kobj;
 };
 
 struct acpi_fan_fif {
@@ -265,6 +266,64 @@  static int acpi_fan_speed_cmp(const void *a, const void *b)
 	return fps1->speed - fps2->speed;
 }
 
+#define to_fps_state(k) container_of(k, struct acpi_fan_fps, kobj)
+
+#define DEFINE_ONE_FPS_ATTR_RO(_name)\
+	static struct kobj_attribute _name =\
+		__ATTR(_name, 0444, show_##_name, NULL)
+
+#define FPS_INFO_SHOW(name, object, multiplier)\
+static ssize_t show_##name(struct kobject *kobj,\
+			   struct kobj_attribute *attr,\
+			   char *buf)\
+{\
+	struct acpi_fan_fps *fps = to_fps_state(kobj);\
+\
+	if (fps->object == 0xFFFFFFFF)\
+		return sprintf(buf, "not-defined\n");\
+\
+	return sprintf(buf, "%llu\n", multiplier * fps->object);\
+}
+
+FPS_INFO_SHOW(control_percent, control, 1)
+FPS_INFO_SHOW(speed_rpm, speed, 1)
+FPS_INFO_SHOW(power_mw, power, 1)
+FPS_INFO_SHOW(noise_level_mdb, noise_level, 100)
+
+static ssize_t show_trip_point_index(struct kobject *kobj,
+				     struct kobj_attribute *attr,
+				     char *buf)
+{
+	struct acpi_fan_fps *fps = to_fps_state(kobj);
+
+	if (fps->trip_point > 9)
+		return sprintf(buf, "invalid\n");
+
+	return sprintf(buf, "%llu\n", fps->trip_point);
+}
+
+DEFINE_ONE_FPS_ATTR_RO(control_percent);
+DEFINE_ONE_FPS_ATTR_RO(trip_point_index);
+DEFINE_ONE_FPS_ATTR_RO(speed_rpm);
+DEFINE_ONE_FPS_ATTR_RO(noise_level_mdb);
+DEFINE_ONE_FPS_ATTR_RO(power_mw);
+
+static struct attribute *fps_attrs[] = {
+	&control_percent.attr,
+	&trip_point_index.attr,
+	&speed_rpm.attr,
+	&noise_level_mdb.attr,
+	&power_mw.attr,
+	NULL
+};
+
+static struct kobj_type fps_ktype = {
+	.sysfs_ops = &kobj_sysfs_ops,
+	.default_attrs = fps_attrs,
+};
+
+#define ACPI_FPS_NAME_LEN	20
+
 static int acpi_fan_get_fps(struct acpi_device *device)
 {
 	struct acpi_fan *fan = acpi_driver_data(device);
@@ -295,12 +354,13 @@  static int acpi_fan_get_fps(struct acpi_device *device)
 	}
 	for (i = 0; i < fan->fps_count; i++) {
 		struct acpi_buffer format = { sizeof("NNNNN"), "NNNNN" };
-		struct acpi_buffer fps = { sizeof(fan->fps[i]), &fan->fps[i] };
+		struct acpi_buffer fps = { offsetof(struct acpi_fan_fps, kobj),
+						&fan->fps[i] };
 		status = acpi_extract_package(&obj->package.elements[i + 1],
 					      &format, &fps);
 		if (ACPI_FAILURE(status)) {
 			dev_err(&device->dev, "Invalid _FPS element\n");
-			break;
+			goto err;
 		}
 	}
 
@@ -308,6 +368,21 @@  static int acpi_fan_get_fps(struct acpi_device *device)
 	sort(fan->fps, fan->fps_count, sizeof(*fan->fps),
 	     acpi_fan_speed_cmp, NULL);
 
+	for (i = 0; i < fan->fps_count; ++i) {
+		char name[ACPI_FPS_NAME_LEN];
+
+		snprintf(name, ACPI_FPS_NAME_LEN, "fan_perf_state_%d", i);
+		status = kobject_init_and_add(&fan->fps[i].kobj, &fps_ktype,
+					      &device->dev.kobj, name);
+		if (status) {
+			int j;
+
+			for (j = 0; j < i; ++j)
+				kobject_put(&fan->fps[j].kobj);
+			break;
+		}
+	}
+
 err:
 	kfree(obj);
 	return status;
@@ -331,13 +406,13 @@  static int acpi_fan_probe(struct platform_device *pdev)
 
 	if (acpi_fan_is_acpi4(device)) {
 		if (acpi_fan_get_fif(device) || acpi_fan_get_fps(device))
-			goto end;
+			goto err_end;
 		fan->acpi4 = true;
 	} else {
 		result = acpi_device_update_power(device, NULL);
 		if (result) {
 			dev_err(&device->dev, "Failed to set initial power state\n");
-			goto end;
+			goto err_end;
 		}
 	}
 
@@ -350,7 +425,7 @@  static int acpi_fan_probe(struct platform_device *pdev)
 						&fan_cooling_ops);
 	if (IS_ERR(cdev)) {
 		result = PTR_ERR(cdev);
-		goto end;
+		goto err_end;
 	}
 
 	dev_dbg(&pdev->dev, "registered as cooling_device%d\n", cdev->id);
@@ -365,10 +440,21 @@  static int acpi_fan_probe(struct platform_device *pdev)
 	result = sysfs_create_link(&cdev->device.kobj,
 				   &pdev->dev.kobj,
 				   "device");
-	if (result)
+	if (result) {
 		dev_err(&pdev->dev, "Failed to create sysfs link 'device'\n");
+		goto err_end;
+	}
+
+	return 0;
+
+err_end:
+	if (fan->acpi4) {
+		int i;
+
+		for (i = 0; i < fan->fps_count; ++i)
+			kobject_put(&fan->fps[i].kobj);
+	}
 
-end:
 	return result;
 }
 
@@ -376,6 +462,12 @@  static int acpi_fan_remove(struct platform_device *pdev)
 {
 	struct acpi_fan *fan = platform_get_drvdata(pdev);
 
+	if (fan->acpi4) {
+		int i;
+
+		for (i = 0; i < fan->fps_count; ++i)
+			kobject_put(&fan->fps[i].kobj);
+	}
 	sysfs_remove_link(&pdev->dev.kobj, "thermal_cooling");
 	sysfs_remove_link(&fan->cdev->device.kobj, "device");
 	thermal_cooling_device_unregister(fan->cdev);