Commit ba9aeba0 authored by Rafael J. Wysocki's avatar Rafael J. Wysocki
Browse files

Merge branches 'acpi-tad', 'acpi-fan', 'acpi-dptf' and 'acpi-tools'

Merge updates of the ACPI time and alarm device (TAD) driver, ACPI fan
driver, ACPI DPTF code and an ACPI utility update for 6.19-rc1:

 - Improve runtime PM in the ACPI time and alarm device (TAD) driver
   using guard macros and rearrange code related to runtime PM in
   acpi_tad_remove() (Rafael Wysocki)

 - Add support for Microsoft fan extensions to the ACPI fan driver along
   with notification support and work around a 64-bit firmware bug in
   that driver (Armin Wolf)

 - Use ACPI_FREE() to free ACPI buffer in the ACPI DPTF code (Kaushlendra
   Kumar)

 - Fix a memory leak and a resource leak in the ACPI pfrut utility (Malaya
   Kumar Rout)

* acpi-tad:
  ACPI: TAD: Improve runtime PM using guard macros
  ACPI: TAD: Rearrange runtime PM operations in acpi_tad_remove()

* acpi-fan:
  ACPI: fan: Add support for Microsoft fan extensions
  ACPI: fan: Add hwmon notification support
  ACPI: fan: Add basic notification support
  ACPI: fan: Workaround for 64-bit firmware bug

* acpi-dptf:
  ACPI: DPTF: Use ACPI_FREE() for ACPI buffer deallocation

* acpi-tools:
  ACPI: tools: pfrut: fix memory leak and resource leak in pfrut.c
Loading
Loading
Loading
Loading
+39 −34
Original line number Diff line number Diff line
@@ -90,19 +90,18 @@ static int acpi_tad_set_real_time(struct device *dev, struct acpi_tad_rt *rt)
	args[0].buffer.pointer = (u8 *)rt;
	args[0].buffer.length = sizeof(*rt);

	pm_runtime_get_sync(dev);
	ACQUIRE(pm_runtime_active_try, pm)(dev);
	if (ACQUIRE_ERR(pm_runtime_active_try, &pm))
		return -ENXIO;

	status = acpi_evaluate_integer(handle, "_SRT", &arg_list, &retval);

	pm_runtime_put_sync(dev);

	if (ACPI_FAILURE(status) || retval)
		return -EIO;

	return 0;
}

static int acpi_tad_get_real_time(struct device *dev, struct acpi_tad_rt *rt)
static int acpi_tad_evaluate_grt(struct device *dev, struct acpi_tad_rt *rt)
{
	acpi_handle handle = ACPI_HANDLE(dev);
	struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER };
@@ -111,12 +110,7 @@ static int acpi_tad_get_real_time(struct device *dev, struct acpi_tad_rt *rt)
	acpi_status status;
	int ret = -EIO;

	pm_runtime_get_sync(dev);

	status = acpi_evaluate_object(handle, "_GRT", NULL, &output);

	pm_runtime_put_sync(dev);

	if (ACPI_FAILURE(status))
		goto out_free;

@@ -139,6 +133,21 @@ static int acpi_tad_get_real_time(struct device *dev, struct acpi_tad_rt *rt)
	return ret;
}

static int acpi_tad_get_real_time(struct device *dev, struct acpi_tad_rt *rt)
{
	int ret;

	ACQUIRE(pm_runtime_active_try, pm)(dev);
	if (ACQUIRE_ERR(pm_runtime_active_try, &pm))
		return -ENXIO;

	ret = acpi_tad_evaluate_grt(dev, rt);
	if (ret)
		return ret;

	return 0;
}

static char *acpi_tad_rt_next_field(char *s, int *val)
{
	char *p;
@@ -266,12 +275,11 @@ static int acpi_tad_wake_set(struct device *dev, char *method, u32 timer_id,
	args[0].integer.value = timer_id;
	args[1].integer.value = value;

	pm_runtime_get_sync(dev);
	ACQUIRE(pm_runtime_active_try, pm)(dev);
	if (ACQUIRE_ERR(pm_runtime_active_try, &pm))
		return -ENXIO;

	status = acpi_evaluate_integer(handle, method, &arg_list, &retval);

	pm_runtime_put_sync(dev);

	if (ACPI_FAILURE(status) || retval)
		return -EIO;

@@ -314,12 +322,11 @@ static ssize_t acpi_tad_wake_read(struct device *dev, char *buf, char *method,

	args[0].integer.value = timer_id;

	pm_runtime_get_sync(dev);
	ACQUIRE(pm_runtime_active_try, pm)(dev);
	if (ACQUIRE_ERR(pm_runtime_active_try, &pm))
		return -ENXIO;

	status = acpi_evaluate_integer(handle, method, &arg_list, &retval);

	pm_runtime_put_sync(dev);

	if (ACPI_FAILURE(status))
		return -EIO;

@@ -370,12 +377,11 @@ static int acpi_tad_clear_status(struct device *dev, u32 timer_id)

	args[0].integer.value = timer_id;

	pm_runtime_get_sync(dev);
	ACQUIRE(pm_runtime_active_try, pm)(dev);
	if (ACQUIRE_ERR(pm_runtime_active_try, &pm))
		return -ENXIO;

	status = acpi_evaluate_integer(handle, "_CWS", &arg_list, &retval);

	pm_runtime_put_sync(dev);

	if (ACPI_FAILURE(status) || retval)
		return -EIO;

@@ -411,12 +417,11 @@ static ssize_t acpi_tad_status_read(struct device *dev, char *buf, u32 timer_id)

	args[0].integer.value = timer_id;

	pm_runtime_get_sync(dev);
	ACQUIRE(pm_runtime_active_try, pm)(dev);
	if (ACQUIRE_ERR(pm_runtime_active_try, &pm))
		return -ENXIO;

	status = acpi_evaluate_integer(handle, "_GWS", &arg_list, &retval);

	pm_runtime_put_sync(dev);

	if (ACPI_FAILURE(status))
		return -EIO;

@@ -563,8 +568,6 @@ static void acpi_tad_remove(struct platform_device *pdev)

	device_init_wakeup(dev, false);

	pm_runtime_get_sync(dev);

	if (dd->capabilities & ACPI_TAD_RT)
		sysfs_remove_group(&dev->kobj, &acpi_tad_time_attr_group);

@@ -573,14 +576,16 @@ static void acpi_tad_remove(struct platform_device *pdev)

	sysfs_remove_group(&dev->kobj, &acpi_tad_attr_group);

	scoped_guard(pm_runtime_noresume, dev) {
		acpi_tad_disable_timer(dev, ACPI_TAD_AC_TIMER);
		acpi_tad_clear_status(dev, ACPI_TAD_AC_TIMER);
		if (dd->capabilities & ACPI_TAD_DC_WAKE) {
			acpi_tad_disable_timer(dev, ACPI_TAD_DC_TIMER);
			acpi_tad_clear_status(dev, ACPI_TAD_DC_TIMER);
		}
	}

	pm_runtime_put_sync(dev);
	pm_runtime_suspend(dev);
	pm_runtime_disable(dev);
	acpi_remove_cmos_rtc_space_handler(handle);
}
+1 −1
Original line number Diff line number Diff line
@@ -41,7 +41,7 @@ static int pch_fivr_read(acpi_handle handle, char *method, struct pch_fivr_resp
	ret = 0;

release_buffer:
	kfree(buffer.pointer);
	ACPI_FREE(buffer.pointer);
	return ret;
}

+40 −0
Original line number Diff line number Diff line
@@ -11,6 +11,7 @@
#define _ACPI_FAN_H_

#include <linux/kconfig.h>
#include <linux/limits.h>

#define ACPI_FAN_DEVICE_IDS	\
	{"INT3404", }, /* Fan */ \
@@ -55,19 +56,58 @@ struct acpi_fan {
	struct acpi_fan_fif fif;
	struct acpi_fan_fps *fps;
	int fps_count;
	/* A value of 0 means that trippoint-related functions are not supported */
	u32 fan_trip_granularity;
#if IS_REACHABLE(CONFIG_HWMON)
	struct device *hdev;
#endif
	struct thermal_cooling_device *cdev;
	struct device_attribute fst_speed;
	struct device_attribute fine_grain_control;
};

/**
 * acpi_fan_speed_valid - Check if fan speed value is valid
 * @speeed: Speed value returned by the ACPI firmware
 *
 * Check if the fan speed value returned by the ACPI firmware is valid. This function is
 * necessary as ACPI firmware implementations can return 0xFFFFFFFF to signal that the
 * ACPI fan does not support speed reporting. Additionally, some buggy ACPI firmware
 * implementations return a value larger than the 32-bit integer value defined by
 * the ACPI specification when using placeholder values. Such invalid values are also
 * detected by this function.
 *
 * Returns: True if the fan speed value is valid, false otherwise.
 */
static inline bool acpi_fan_speed_valid(u64 speed)
{
	return speed < U32_MAX;
}

/**
 * acpi_fan_power_valid - Check if fan power value is valid
 * @power: Power value returned by the ACPI firmware
 *
 * Check if the fan power value returned by the ACPI firmware is valid.
 * See acpi_fan_speed_valid() for details.
 *
 * Returns: True if the fan power value is valid, false otherwise.
 */
static inline bool acpi_fan_power_valid(u64 power)
{
	return power < U32_MAX;
}

int acpi_fan_get_fst(acpi_handle handle, struct acpi_fan_fst *fst);
int acpi_fan_create_attributes(struct acpi_device *device);
void acpi_fan_delete_attributes(struct acpi_device *device);

#if IS_REACHABLE(CONFIG_HWMON)
int devm_acpi_fan_create_hwmon(struct device *dev);
void acpi_fan_notify_hwmon(struct device *dev);
#else
static inline int devm_acpi_fan_create_hwmon(struct device *dev) { return 0; };
static inline void acpi_fan_notify_hwmon(struct device *dev) { };
#endif

#endif
+222 −1
Original line number Diff line number Diff line
@@ -7,11 +7,16 @@
 *  Copyright (C) 2022 Intel Corporation. All rights reserved.
 */

#include <linux/bits.h>
#include <linux/kernel.h>
#include <linux/limits.h>
#include <linux/math.h>
#include <linux/math64.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/uuid.h>
#include <linux/thermal.h>
#include <linux/acpi.h>
#include <linux/platform_device.h>
@@ -19,6 +24,26 @@

#include "fan.h"

#define ACPI_FAN_NOTIFY_STATE_CHANGED	0x80

/*
 * Defined inside the "Fan Noise Signal" section at
 * https://learn.microsoft.com/en-us/windows-hardware/design/device-experiences/design-guide.
 */
static const guid_t acpi_fan_microsoft_guid = GUID_INIT(0xA7611840, 0x99FE, 0x41AE, 0xA4, 0x88,
							0x35, 0xC7, 0x59, 0x26, 0xC8, 0xEB);
#define ACPI_FAN_DSM_GET_TRIP_POINT_GRANULARITY 1
#define ACPI_FAN_DSM_SET_TRIP_POINTS		2
#define ACPI_FAN_DSM_GET_OPERATING_RANGES	3

/*
 * Ensures that fans with a very low trip point granularity
 * do not send too many notifications.
 */
static uint min_trip_distance = 100;
module_param(min_trip_distance, uint, 0);
MODULE_PARM_DESC(min_trip_distance, "Minimum distance between fan speed trip points in RPM");

static const struct acpi_device_id fan_device_ids[] = {
	ACPI_FAN_DEVICE_IDS,
	{"", 0},
@@ -308,6 +333,182 @@ static int acpi_fan_get_fps(struct acpi_device *device)
	return status;
}

static int acpi_fan_dsm_init(struct device *dev)
{
	union acpi_object dummy = {
		.package = {
			.type = ACPI_TYPE_PACKAGE,
			.count = 0,
			.elements = NULL,
		},
	};
	struct acpi_fan *fan = dev_get_drvdata(dev);
	union acpi_object *obj;
	int ret = 0;

	if (!acpi_check_dsm(fan->handle, &acpi_fan_microsoft_guid, 0,
			    BIT(ACPI_FAN_DSM_GET_TRIP_POINT_GRANULARITY) |
			    BIT(ACPI_FAN_DSM_SET_TRIP_POINTS)))
		return 0;

	dev_info(dev, "Using Microsoft fan extensions\n");

	obj = acpi_evaluate_dsm_typed(fan->handle, &acpi_fan_microsoft_guid, 0,
				      ACPI_FAN_DSM_GET_TRIP_POINT_GRANULARITY, &dummy,
				      ACPI_TYPE_INTEGER);
	if (!obj)
		return -EIO;

	if (obj->integer.value > U32_MAX)
		ret = -EOVERFLOW;
	else
		fan->fan_trip_granularity = obj->integer.value;

	kfree(obj);

	return ret;
}

static int acpi_fan_dsm_set_trip_points(struct device *dev, u64 upper, u64 lower)
{
	union acpi_object args[2] = {
		{
			.integer = {
				.type = ACPI_TYPE_INTEGER,
				.value = lower,
			},
		},
		{
			.integer = {
				.type = ACPI_TYPE_INTEGER,
				.value = upper,
			},
		},
	};
	struct acpi_fan *fan = dev_get_drvdata(dev);
	union acpi_object in = {
		.package = {
			.type = ACPI_TYPE_PACKAGE,
			.count = ARRAY_SIZE(args),
			.elements = args,
		},
	};
	union acpi_object *obj;

	obj = acpi_evaluate_dsm(fan->handle, &acpi_fan_microsoft_guid, 0,
				ACPI_FAN_DSM_SET_TRIP_POINTS, &in);
	kfree(obj);

	return 0;
}

static int acpi_fan_dsm_start(struct device *dev)
{
	struct acpi_fan *fan = dev_get_drvdata(dev);
	int ret;

	if (!fan->fan_trip_granularity)
		return 0;

	/*
	 * Some firmware implementations only update the values returned by the
	 * _FST control method when a notification is received. This usually
	 * works with Microsoft Windows as setting up trip points will keep
	 * triggering said notifications, but will cause issues when using _FST
	 * without the Microsoft-specific trip point extension.
	 *
	 * Because of this, an initial notification needs to be triggered to
	 * start the cycle of trip points updates. This is achieved by setting
	 * the trip points sequencially to two separate ranges. As by the
	 * Microsoft specification the firmware should trigger a notification
	 * immediately if the fan speed is outside the trip point range. This
	 * _should_ result in at least one notification as both ranges do not
	 * overlap, meaning that the current fan speed needs to be outside at
	 * least one range.
	 */
	ret = acpi_fan_dsm_set_trip_points(dev, fan->fan_trip_granularity, 0);
	if (ret < 0)
		return ret;

	return acpi_fan_dsm_set_trip_points(dev, fan->fan_trip_granularity * 3,
					    fan->fan_trip_granularity * 2);
}

static int acpi_fan_dsm_update_trips_points(struct device *dev, struct acpi_fan_fst *fst)
{
	struct acpi_fan *fan = dev_get_drvdata(dev);
	u64 upper, lower;

	if (!fan->fan_trip_granularity)
		return 0;

	if (!acpi_fan_speed_valid(fst->speed))
		return -EINVAL;

	upper = roundup_u64(fst->speed + min_trip_distance, fan->fan_trip_granularity);
	if (fst->speed <= min_trip_distance) {
		lower = 0;
	} else {
		/*
		 * Valid fan speed values cannot be larger than 32 bit, so
		 * we can safely assume that no overflow will happen here.
		 */
		lower = rounddown((u32)fst->speed - min_trip_distance, fan->fan_trip_granularity);
	}

	return acpi_fan_dsm_set_trip_points(dev, upper, lower);
}

static void acpi_fan_notify_handler(acpi_handle handle, u32 event, void *context)
{
	struct device *dev = context;
	struct acpi_fan_fst fst;
	int ret;

	switch (event) {
	case ACPI_FAN_NOTIFY_STATE_CHANGED:
		/*
		 * The ACPI specification says that we must evaluate _FST when we
		 * receive an ACPI event indicating that the fan state has changed.
		 */
		ret = acpi_fan_get_fst(handle, &fst);
		if (ret < 0) {
			dev_err(dev, "Error retrieving current fan status: %d\n", ret);
		} else {
			ret = acpi_fan_dsm_update_trips_points(dev, &fst);
			if (ret < 0)
				dev_err(dev, "Failed to update trip points: %d\n", ret);
		}

		acpi_fan_notify_hwmon(dev);
		acpi_bus_generate_netlink_event("fan", dev_name(dev), event, 0);
		break;
	default:
		dev_dbg(dev, "Unsupported ACPI notification 0x%x\n", event);
		break;
	}
}

static void acpi_fan_notify_remove(void *data)
{
	struct acpi_fan *fan = data;

	acpi_remove_notify_handler(fan->handle, ACPI_DEVICE_NOTIFY, acpi_fan_notify_handler);
}

static int devm_acpi_fan_notify_init(struct device *dev)
{
	struct acpi_fan *fan = dev_get_drvdata(dev);
	acpi_status status;

	status = acpi_install_notify_handler(fan->handle, ACPI_DEVICE_NOTIFY,
					     acpi_fan_notify_handler, dev);
	if (ACPI_FAILURE(status))
		return -EIO;

	return devm_add_action_or_reset(dev, acpi_fan_notify_remove, fan);
}

static int acpi_fan_probe(struct platform_device *pdev)
{
	int result = 0;
@@ -347,10 +548,24 @@ static int acpi_fan_probe(struct platform_device *pdev)
	}

	if (fan->has_fst) {
		result = acpi_fan_dsm_init(&pdev->dev);
		if (result)
			return result;

		result = devm_acpi_fan_create_hwmon(&pdev->dev);
		if (result)
			return result;

		result = devm_acpi_fan_notify_init(&pdev->dev);
		if (result)
			return result;

		result = acpi_fan_dsm_start(&pdev->dev);
		if (result) {
			dev_err(&pdev->dev, "Failed to start Microsoft fan extensions\n");
			return result;
		}

		result = acpi_fan_create_attributes(device);
		if (result)
			return result;
@@ -436,8 +651,14 @@ static int acpi_fan_suspend(struct device *dev)

static int acpi_fan_resume(struct device *dev)
{
	int result;
	struct acpi_fan *fan = dev_get_drvdata(dev);
	int result;

	if (fan->has_fst) {
		result = acpi_fan_dsm_start(dev);
		if (result)
			dev_err(dev, "Failed to start Microsoft fan extensions: %d\n", result);
	}

	if (fan->acpi4)
		return 0;
+14 −11
Original line number Diff line number Diff line
@@ -15,10 +15,6 @@

#include "fan.h"

/* Returned when the ACPI fan does not support speed reporting */
#define FAN_SPEED_UNAVAILABLE	U32_MAX
#define FAN_POWER_UNAVAILABLE	U32_MAX

static struct acpi_fan_fps *acpi_fan_get_current_fps(struct acpi_fan *fan, u64 control)
{
	unsigned int i;
@@ -77,7 +73,7 @@ static umode_t acpi_fan_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_
			 * when the associated attribute should not be created.
			 */
			for (i = 0; i < fan->fps_count; i++) {
				if (fan->fps[i].power != FAN_POWER_UNAVAILABLE)
				if (acpi_fan_power_valid(fan->fps[i].power))
					return 0444;
			}

@@ -106,7 +102,7 @@ static int acpi_fan_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
	case hwmon_fan:
		switch (attr) {
		case hwmon_fan_input:
			if (fst.speed == FAN_SPEED_UNAVAILABLE)
			if (!acpi_fan_speed_valid(fst.speed))
				return -ENODEV;

			if (fst.speed > LONG_MAX)
@@ -134,7 +130,7 @@ static int acpi_fan_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
			if (!fps)
				return -EIO;

			if (fps->power == FAN_POWER_UNAVAILABLE)
			if (!acpi_fan_power_valid(fps->power))
				return -ENODEV;

			if (fps->power > LONG_MAX / MICROWATT_PER_MILLIWATT)
@@ -166,12 +162,19 @@ static const struct hwmon_chip_info acpi_fan_hwmon_chip_info = {
	.info = acpi_fan_hwmon_info,
};

void acpi_fan_notify_hwmon(struct device *dev)
{
	struct acpi_fan *fan = dev_get_drvdata(dev);

	hwmon_notify_event(fan->hdev, hwmon_fan, hwmon_fan_input, 0);
}

int devm_acpi_fan_create_hwmon(struct device *dev)
{
	struct acpi_fan *fan = dev_get_drvdata(dev);
	struct device *hdev;

	hdev = devm_hwmon_device_register_with_info(dev, "acpi_fan", fan, &acpi_fan_hwmon_chip_info,
						    NULL);
	return PTR_ERR_OR_ZERO(hdev);
	fan->hdev = devm_hwmon_device_register_with_info(dev, "acpi_fan", fan,
							 &acpi_fan_hwmon_chip_info, NULL);

	return PTR_ERR_OR_ZERO(fan->hdev);
}
Loading