diff mbox series

[v2] ACPI: thermal: Support for linking devices associated with the thermal zone

Message ID 20241113085634.7657-1-lihuisong@huawei.com
State New
Headers show
Series [v2] ACPI: thermal: Support for linking devices associated with the thermal zone | expand

Commit Message

Huisong Li Nov. 13, 2024, 8:56 a.m. UTC
There are many 'cdevX' files which link cooling devices under
'/sys/class/thermal/thermal_zoneX/'. These devices contain active cooling
devices and passive cooling devices. And user cann't directly know which
devices temperature is represented by the thermal zone.

However, ACPI spec provides a '_TZD' object which evaluates to a package
of device names. Each name corresponds to a device in the ACPI namespace
that is associated with the thermal zone. The temperature reported by the
thermal zone is roughly correspondent to that of each of the devices.

User can get all devices a thermal zone measured by the 'measures'
directory under the thermal zone device.

Signed-off-by: Huisong Li <lihuisong@huawei.com>
---
v2: fix commitlog based on Rafael's comment.
---
 drivers/acpi/thermal.c | 114 ++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 113 insertions(+), 1 deletion(-)
diff mbox series

Patch

diff --git a/drivers/acpi/thermal.c b/drivers/acpi/thermal.c
index 78db38c7076e..398195a5d42f 100644
--- a/drivers/acpi/thermal.c
+++ b/drivers/acpi/thermal.c
@@ -119,6 +119,9 @@  struct acpi_thermal {
 	struct work_struct thermal_check_work;
 	struct mutex thermal_check_lock;
 	refcount_t thermal_check_count;
+	int num_domain_devices;
+	struct acpi_device **domain_devices;
+	struct kobject *holders_dir;
 };
 
 /* --------------------------------------------------------------------------
@@ -589,6 +592,103 @@  static const struct thermal_zone_device_ops acpi_thermal_zone_ops = {
 	.critical = acpi_thermal_zone_device_critical,
 };
 
+static void acpi_thermal_remove_domain_devices(struct acpi_thermal *tz)
+{
+	int i;
+
+	if (!tz->num_domain_devices)
+		return;
+
+	for (i = 0; i < tz->num_domain_devices; i++) {
+		struct acpi_device *obj = tz->domain_devices[i];
+
+		if (!obj)
+			continue;
+
+		sysfs_remove_link(tz->holders_dir,
+				  kobject_name(&obj->dev.kobj));
+		acpi_dev_put(obj);
+	}
+
+	kfree(tz->domain_devices);
+	kobject_put(tz->holders_dir);
+	tz->num_domain_devices = 0;
+}
+
+static int acpi_thermal_read_domain_devices(struct acpi_thermal *tz)
+{
+	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+	union acpi_object *pss;
+	acpi_status status;
+	int ret = 0;
+	int i;
+
+	status = acpi_evaluate_object(tz->device->handle, "_TZD", NULL,
+				      &buffer);
+	if (ACPI_FAILURE(status)) {
+		acpi_evaluation_failure_warn(tz->device->handle, "_TZD",
+					     status);
+		return -ENODEV;
+	}
+
+	pss = buffer.pointer;
+	if (!pss ||
+	    pss->type != ACPI_TYPE_PACKAGE) {
+		dev_err(&tz->device->dev, "Thermal zone invalid _TZD data\n");
+		ret = -EFAULT;
+		goto end;
+	}
+
+	if (!pss->package.count)
+		goto end;
+
+	tz->domain_devices = kcalloc(pss->package.count,
+				     sizeof(struct acpi_device *), GFP_KERNEL);
+	if (!tz->domain_devices) {
+		ret = -ENOMEM;
+		goto end;
+	}
+
+	tz->holders_dir = kobject_create_and_add("measures",
+						 &tz->device->dev.kobj);
+	if (!tz->holders_dir) {
+		ret = -ENOMEM;
+		goto exit_free;
+	}
+
+	tz->num_domain_devices = pss->package.count;
+	for (i = 0; i < pss->package.count; i++) {
+		struct acpi_device *obj;
+		union acpi_object *element = &pss->package.elements[i];
+
+		/* Refuse non-references */
+		if (element->type != ACPI_TYPE_LOCAL_REFERENCE)
+			continue;
+
+		/* Create a symlink to domain objects */
+		obj = acpi_get_acpi_dev(element->reference.handle);
+		tz->domain_devices[i] = obj;
+		if (!obj)
+			continue;
+
+		ret = sysfs_create_link(tz->holders_dir, &obj->dev.kobj,
+					kobject_name(&obj->dev.kobj));
+		if (ret) {
+			acpi_dev_put(obj);
+			tz->domain_devices[i] = NULL;
+		}
+	}
+
+	ret = 0;
+	goto end;
+
+exit_free:
+	kfree(tz->domain_devices);
+end:
+	kfree(buffer.pointer);
+	return ret;
+}
+
 static int acpi_thermal_zone_sysfs_add(struct acpi_thermal *tz)
 {
 	struct device *tzdev = thermal_zone_device(tz->thermal_zone);
@@ -602,8 +702,19 @@  static int acpi_thermal_zone_sysfs_add(struct acpi_thermal *tz)
 	ret = sysfs_create_link(&tzdev->kobj,
 				   &tz->device->dev.kobj, "device");
 	if (ret)
-		sysfs_remove_link(&tz->device->dev.kobj, "thermal_zone");
+		goto remove_thermal_zone;
 
+	/* _TZD method is optional. */
+	ret = acpi_thermal_read_domain_devices(tz);
+	if (ret != -ENODEV)
+		goto remove_device;
+
+	return 0;
+
+remove_device:
+	sysfs_remove_link(&tz->device->dev.kobj, "device");
+remove_thermal_zone:
+	sysfs_remove_link(&tz->device->dev.kobj, "thermal_zone");
 	return ret;
 }
 
@@ -611,6 +722,7 @@  static void acpi_thermal_zone_sysfs_remove(struct acpi_thermal *tz)
 {
 	struct device *tzdev = thermal_zone_device(tz->thermal_zone);
 
+	acpi_thermal_remove_domain_devices(tz);
 	sysfs_remove_link(&tz->device->dev.kobj, "thermal_zone");
 	sysfs_remove_link(&tzdev->kobj, "device");
 }