5119 lines · 144.4 KB · C
RawDownload
/* * Copyright 2017 Advanced Micro Devices, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * * Authors: Rafał Miłecki <zajec5@gmail.com> *          Alex Deucher <alexdeucher@gmail.com> */ #include "amdgpu.h"#include "amdgpu_drv.h"#include "amdgpu_pm.h"#include "amdgpu_dpm.h"#include "atom.h"#include <linux/pci.h>#include <linux/hwmon.h>#include <linux/hwmon-sysfs.h>#include <linux/nospec.h>#include <linux/pm_runtime.h>#include <linux/string_choices.h>#include <linux/units.h>#include <asm/processor.h> #define MAX_NUM_OF_FEATURES_PER_SUBSET		8#define MAX_NUM_OF_SUBSETS			8 #define DEVICE_ATTR_IS(_name)		(attr_id == device_attr_id__##_name) struct od_attribute {	struct kobj_attribute	attribute;	struct list_head	entry;}; struct od_kobj {	struct kobject		kobj;	struct list_head	entry;	struct list_head	attribute;	void			*priv;}; struct od_feature_ops {	umode_t (*is_visible)(struct amdgpu_device *adev);	ssize_t (*show)(struct kobject *kobj, struct kobj_attribute *attr,			char *buf);	ssize_t (*store)(struct kobject *kobj, struct kobj_attribute *attr,			 const char *buf, size_t count);}; struct od_feature_item {	const char		*name;	struct od_feature_ops	ops;}; struct od_feature_container {	char				*name;	struct od_feature_ops		ops;	struct od_feature_item		sub_feature[MAX_NUM_OF_FEATURES_PER_SUBSET];}; struct od_feature_set {	struct od_feature_container	containers[MAX_NUM_OF_SUBSETS];}; static const struct hwmon_temp_label {	enum PP_HWMON_TEMP channel;	const char *label;} temp_label[] = {	{PP_TEMP_EDGE, "edge"},	{PP_TEMP_JUNCTION, "junction"},	{PP_TEMP_MEM, "mem"},}; const char * const amdgpu_pp_profile_name[] = {	"BOOTUP_DEFAULT",	"3D_FULL_SCREEN",	"POWER_SAVING",	"VIDEO",	"VR",	"COMPUTE",	"CUSTOM",	"WINDOW_3D",	"CAPPED",	"UNCAPPED",}; static int amdgpu_pm_parse_long_params(char *str, long *params,				       uint32_t max_params,				       uint32_t *num_params){	const char delimiter[] = { ' ', '\n', '\0' };	uint32_t count = 0;	char *sub_str;	int ret; 	if (!params || !num_params)		return -EINVAL; 	while ((sub_str = strsep(&str, delimiter)) != NULL) {		if (strlen(sub_str) == 0)			continue;		if (count >= max_params)			return -EINVAL;		ret = kstrtol(sub_str, 0, &params[count]);		if (ret)			return -EINVAL;		count++;		if (!str)			break;		while (isspace(*str))			str++;	}	*num_params = count; 	return 0;} /** * amdgpu_pm_dev_state_check - Check if device can be accessed. * @adev: Target device. * @runpm: Check runpm status for suspend state checks. * * Checks the state of the @adev for access. Return 0 if the device is * accessible or a negative error code otherwise. */static int amdgpu_pm_dev_state_check(struct amdgpu_device *adev, bool runpm){	bool runpm_check = runpm ? adev->in_runpm : false;	bool full_init = (adev->init_lvl->level == AMDGPU_INIT_LEVEL_DEFAULT); 	if (amdgpu_in_reset(adev) || !full_init)		return -EBUSY; 	if (adev->in_suspend && !runpm_check)		return -EBUSY; 	return 0;} /** * amdgpu_pm_get_access - Check if device can be accessed, resume if needed. * @adev: Target device. * * Checks the state of the @adev for access. Use runtime pm API to resume if * needed. Return 0 if the device is accessible or a negative error code * otherwise. */static int amdgpu_pm_get_access(struct amdgpu_device *adev){	int ret; 	ret = amdgpu_pm_dev_state_check(adev, true);	if (ret)		return ret; 	return pm_runtime_resume_and_get(adev->dev);} /** * amdgpu_pm_get_access_if_active - Check if device is active for access. * @adev: Target device. * * Checks the state of the @adev for access. Use runtime pm API to determine * if device is active. Allow access only if device is active.Return 0 if the * device is accessible or a negative error code otherwise. */static int amdgpu_pm_get_access_if_active(struct amdgpu_device *adev){	int ret; 	/* Ignore runpm status. If device is in suspended state, deny access */	ret = amdgpu_pm_dev_state_check(adev, false);	if (ret)		return ret; 	/*	 * Allow only if device is active. If runpm is disabled also, as in	 * kernels without CONFIG_PM, allow access.	 */	ret = pm_runtime_get_if_active(adev->dev);	if (!ret)		return -EPERM; 	return 0;} /** * amdgpu_pm_put_access - Put to auto suspend mode after a device access. * @adev: Target device. * * Should be paired with amdgpu_pm_get_access* calls */static inline void amdgpu_pm_put_access(struct amdgpu_device *adev){	pm_runtime_put_autosuspend(adev->dev);} /** * DOC: power_dpm_state * * The power_dpm_state file is a legacy interface and is only provided for * backwards compatibility. The amdgpu driver provides a sysfs API for adjusting * certain power related parameters.  The file power_dpm_state is used for this. * It accepts the following arguments: * * - battery * * - balanced * * - performance * * battery * * On older GPUs, the vbios provided a special power state for battery * operation.  Selecting battery switched to this state.  This is no * longer provided on newer GPUs so the option does nothing in that case. * * balanced * * On older GPUs, the vbios provided a special power state for balanced * operation.  Selecting balanced switched to this state.  This is no * longer provided on newer GPUs so the option does nothing in that case. * * performance * * On older GPUs, the vbios provided a special power state for performance * operation.  Selecting performance switched to this state.  This is no * longer provided on newer GPUs so the option does nothing in that case. * */ static ssize_t amdgpu_get_power_dpm_state(struct device *dev,					  struct device_attribute *attr,					  char *buf){	struct drm_device *ddev = dev_get_drvdata(dev);	struct amdgpu_device *adev = drm_to_adev(ddev);	enum amd_pm_state_type pm;	int ret; 	ret = amdgpu_pm_get_access_if_active(adev);	if (ret)		return ret; 	amdgpu_dpm_get_current_power_state(adev, &pm); 	amdgpu_pm_put_access(adev); 	return sysfs_emit(buf, "%s\n",			  (pm == POWER_STATE_TYPE_BATTERY) ? "battery" :			  (pm == POWER_STATE_TYPE_BALANCED) ? "balanced" : "performance");} static ssize_t amdgpu_set_power_dpm_state(struct device *dev,					  struct device_attribute *attr,					  const char *buf,					  size_t count){	struct drm_device *ddev = dev_get_drvdata(dev);	struct amdgpu_device *adev = drm_to_adev(ddev);	enum amd_pm_state_type  state;	int ret; 	/* Reject empty/whitespace strings - fuzzing found this is not validated */	if (count == 0 || sysfs_streq(buf, ""))		return -EINVAL; 	if (sysfs_streq(buf, "battery"))		state = POWER_STATE_TYPE_BATTERY;	else if (sysfs_streq(buf, "balanced"))		state = POWER_STATE_TYPE_BALANCED;	else if (sysfs_streq(buf, "performance"))		state = POWER_STATE_TYPE_PERFORMANCE;	else		return -EINVAL; 	ret = amdgpu_pm_get_access(adev);	if (ret < 0)		return ret; 	amdgpu_dpm_set_power_state(adev, state); 	amdgpu_pm_put_access(adev); 	return count;}  /** * DOC: power_dpm_force_performance_level * * The amdgpu driver provides a sysfs API for adjusting certain power * related parameters.  The file power_dpm_force_performance_level is * used for this.  It accepts the following arguments: * * - auto * * - low * * - high * * - manual * * - profile_standard * * - profile_min_sclk * * - profile_min_mclk * * - profile_peak * * auto * * When auto is selected, the driver will attempt to dynamically select * the optimal power profile for current conditions in the driver. * * low * * When low is selected, the clocks are forced to the lowest power state. * * high * * When high is selected, the clocks are forced to the highest power state. * * manual * * When manual is selected, the user can manually adjust which power states * are enabled for each clock domain via the sysfs pp_dpm_mclk, pp_dpm_sclk, * and pp_dpm_pcie files and adjust the power state transition heuristics * via the pp_power_profile_mode sysfs file. * * profile_standard * profile_min_sclk * profile_min_mclk * profile_peak * * When the profiling modes are selected, clock and power gating are * disabled and the clocks are set for different profiling cases. This * mode is recommended for profiling specific work loads where you do * not want clock or power gating for clock fluctuation to interfere * with your results. profile_standard sets the clocks to a fixed clock * level which varies from asic to asic.  profile_min_sclk forces the sclk * to the lowest level.  profile_min_mclk forces the mclk to the lowest level. * profile_peak sets all clocks (mclk, sclk, pcie) to the highest levels. * */ static ssize_t amdgpu_get_power_dpm_force_performance_level(struct device *dev,							    struct device_attribute *attr,							    char *buf){	struct drm_device *ddev = dev_get_drvdata(dev);	struct amdgpu_device *adev = drm_to_adev(ddev);	enum amd_dpm_forced_level level = 0xff;	int ret; 	ret = amdgpu_pm_get_access_if_active(adev);	if (ret)		return ret; 	level = amdgpu_dpm_get_performance_level(adev); 	amdgpu_pm_put_access(adev); 	return sysfs_emit(buf, "%s\n",			  (level == AMD_DPM_FORCED_LEVEL_AUTO) ? "auto" :			  (level == AMD_DPM_FORCED_LEVEL_LOW) ? "low" :			  (level == AMD_DPM_FORCED_LEVEL_HIGH) ? "high" :			  (level == AMD_DPM_FORCED_LEVEL_MANUAL) ? "manual" :			  (level == AMD_DPM_FORCED_LEVEL_PROFILE_STANDARD) ? "profile_standard" :			  (level == AMD_DPM_FORCED_LEVEL_PROFILE_MIN_SCLK) ? "profile_min_sclk" :			  (level == AMD_DPM_FORCED_LEVEL_PROFILE_MIN_MCLK) ? "profile_min_mclk" :			  (level == AMD_DPM_FORCED_LEVEL_PROFILE_PEAK) ? "profile_peak" :			  (level == AMD_DPM_FORCED_LEVEL_PERF_DETERMINISM) ? "perf_determinism" :			  "unknown");} static ssize_t amdgpu_set_power_dpm_force_performance_level(struct device *dev,							    struct device_attribute *attr,							    const char *buf,							    size_t count){	struct drm_device *ddev = dev_get_drvdata(dev);	struct amdgpu_device *adev = drm_to_adev(ddev);	enum amd_dpm_forced_level level;	int ret = 0; 	/* Reject empty/whitespace strings - fuzzing found this is not validated */	if (count == 0 || sysfs_streq(buf, ""))		return -EINVAL; 	if (sysfs_streq(buf, "low"))		level = AMD_DPM_FORCED_LEVEL_LOW;	else if (sysfs_streq(buf, "high"))		level = AMD_DPM_FORCED_LEVEL_HIGH;	else if (sysfs_streq(buf, "auto"))		level = AMD_DPM_FORCED_LEVEL_AUTO;	else if (sysfs_streq(buf, "manual"))		level = AMD_DPM_FORCED_LEVEL_MANUAL;	else if (sysfs_streq(buf, "profile_exit"))		level = AMD_DPM_FORCED_LEVEL_PROFILE_EXIT;	else if (sysfs_streq(buf, "profile_standard"))		level = AMD_DPM_FORCED_LEVEL_PROFILE_STANDARD;	else if (sysfs_streq(buf, "profile_min_sclk"))		level = AMD_DPM_FORCED_LEVEL_PROFILE_MIN_SCLK;	else if (sysfs_streq(buf, "profile_min_mclk"))		level = AMD_DPM_FORCED_LEVEL_PROFILE_MIN_MCLK;	else if (sysfs_streq(buf, "profile_peak"))		level = AMD_DPM_FORCED_LEVEL_PROFILE_PEAK;	else if (sysfs_streq(buf, "perf_determinism"))		level = AMD_DPM_FORCED_LEVEL_PERF_DETERMINISM;	else		return -EINVAL; 	ret = amdgpu_pm_get_access(adev);	if (ret < 0)		return ret; 	mutex_lock(&adev->pm.stable_pstate_ctx_lock);	if (amdgpu_dpm_force_performance_level(adev, level)) {		amdgpu_pm_put_access(adev);		mutex_unlock(&adev->pm.stable_pstate_ctx_lock);		return -EINVAL;	}	/* override whatever a user ctx may have set */	adev->pm.stable_pstate_ctx = NULL;	mutex_unlock(&adev->pm.stable_pstate_ctx_lock); 	amdgpu_pm_put_access(adev); 	return count;} static ssize_t amdgpu_get_pp_num_states(struct device *dev,		struct device_attribute *attr,		char *buf){	struct drm_device *ddev = dev_get_drvdata(dev);	struct amdgpu_device *adev = drm_to_adev(ddev);	struct pp_states_info data;	uint32_t i;	int buf_len, ret; 	ret = amdgpu_pm_get_access_if_active(adev);	if (ret)		return ret; 	if (amdgpu_dpm_get_pp_num_states(adev, &data))		memset(&data, 0, sizeof(data)); 	amdgpu_pm_put_access(adev); 	buf_len = sysfs_emit(buf, "states: %d\n", data.nums);	for (i = 0; i < data.nums; i++)		buf_len += sysfs_emit_at(buf, buf_len, "%d %s\n", i,				(data.states[i] == POWER_STATE_TYPE_INTERNAL_BOOT) ? "boot" :				(data.states[i] == POWER_STATE_TYPE_BATTERY) ? "battery" :				(data.states[i] == POWER_STATE_TYPE_BALANCED) ? "balanced" :				(data.states[i] == POWER_STATE_TYPE_PERFORMANCE) ? "performance" : "default"); 	return buf_len;} static ssize_t amdgpu_get_pp_cur_state(struct device *dev,		struct device_attribute *attr,		char *buf){	struct drm_device *ddev = dev_get_drvdata(dev);	struct amdgpu_device *adev = drm_to_adev(ddev);	struct pp_states_info data = {0};	enum amd_pm_state_type pm = 0;	int i = 0, ret = 0; 	ret = amdgpu_pm_get_access_if_active(adev);	if (ret)		return ret; 	amdgpu_dpm_get_current_power_state(adev, &pm); 	ret = amdgpu_dpm_get_pp_num_states(adev, &data); 	amdgpu_pm_put_access(adev); 	if (ret)		return ret;

Lines 1–500 of 5119.