mirror of
https://github.com/torvalds/linux.git
synced 2026-05-24 15:12:13 +02:00
drm/amd/pm: introduce a new set of OD interfaces
There will be multiple interfaces(sysfs files) exposed with each representing a single OD functionality. And all those interface will be arranged in a tree liked hierarchy with the top dir as "gpu_od". Meanwhile all functionalities for the same component will be arranged under the same directory. Signed-off-by: Evan Quan <evan.quan@amd.com> Reviewed-by: Alex Deucher <alexander.deucher@amd.com> Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
This commit is contained in:
parent
2cd1f65d31
commit
3e38b634f9
|
|
@ -3624,6 +3624,8 @@ int amdgpu_device_init(struct amdgpu_device *adev,
|
|||
|
||||
INIT_LIST_HEAD(&adev->ras_list);
|
||||
|
||||
INIT_LIST_HEAD(&adev->pm.od_kobj_list);
|
||||
|
||||
INIT_DELAYED_WORK(&adev->delayed_init_work,
|
||||
amdgpu_device_delayed_init_work_handler);
|
||||
INIT_DELAYED_WORK(&adev->gfx.gfx_off_delay_work,
|
||||
|
|
|
|||
|
|
@ -35,6 +35,44 @@
|
|||
#include <linux/pm_runtime.h>
|
||||
#include <asm/processor.h>
|
||||
|
||||
#define MAX_NUM_OF_FEATURES_PER_SUBSET 8
|
||||
#define MAX_NUM_OF_SUBSETS 8
|
||||
|
||||
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;
|
||||
|
|
@ -3345,10 +3383,216 @@ static const struct attribute_group *hwmon_groups[] = {
|
|||
NULL
|
||||
};
|
||||
|
||||
static struct od_feature_set amdgpu_od_set;
|
||||
|
||||
static void od_kobj_release(struct kobject *kobj)
|
||||
{
|
||||
struct od_kobj *od_kobj = container_of(kobj, struct od_kobj, kobj);
|
||||
|
||||
kfree(od_kobj);
|
||||
}
|
||||
|
||||
static const struct kobj_type od_ktype = {
|
||||
.release = od_kobj_release,
|
||||
.sysfs_ops = &kobj_sysfs_ops,
|
||||
};
|
||||
|
||||
static void amdgpu_od_set_fini(struct amdgpu_device *adev)
|
||||
{
|
||||
struct od_kobj *container, *container_next;
|
||||
struct od_attribute *attribute, *attribute_next;
|
||||
|
||||
if (list_empty(&adev->pm.od_kobj_list))
|
||||
return;
|
||||
|
||||
list_for_each_entry_safe(container, container_next,
|
||||
&adev->pm.od_kobj_list, entry) {
|
||||
list_del(&container->entry);
|
||||
|
||||
list_for_each_entry_safe(attribute, attribute_next,
|
||||
&container->attribute, entry) {
|
||||
list_del(&attribute->entry);
|
||||
sysfs_remove_file(&container->kobj,
|
||||
&attribute->attribute.attr);
|
||||
kfree(attribute);
|
||||
}
|
||||
|
||||
kobject_put(&container->kobj);
|
||||
}
|
||||
}
|
||||
|
||||
static bool amdgpu_is_od_feature_supported(struct amdgpu_device *adev,
|
||||
struct od_feature_ops *feature_ops)
|
||||
{
|
||||
umode_t mode;
|
||||
|
||||
if (!feature_ops->is_visible)
|
||||
return false;
|
||||
|
||||
/*
|
||||
* If the feature has no user read and write mode set,
|
||||
* we can assume the feature is actually not supported.(?)
|
||||
* And the revelant sysfs interface should not be exposed.
|
||||
*/
|
||||
mode = feature_ops->is_visible(adev);
|
||||
if (mode & (S_IRUSR | S_IWUSR))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool amdgpu_od_is_self_contained(struct amdgpu_device *adev,
|
||||
struct od_feature_container *container)
|
||||
{
|
||||
int i;
|
||||
|
||||
/*
|
||||
* If there is no valid entry within the container, the container
|
||||
* is recognized as a self contained container. And the valid entry
|
||||
* here means it has a valid naming and it is visible/supported by
|
||||
* the ASIC.
|
||||
*/
|
||||
for (i = 0; i < ARRAY_SIZE(container->sub_feature); i++) {
|
||||
if (container->sub_feature[i].name &&
|
||||
amdgpu_is_od_feature_supported(adev,
|
||||
&container->sub_feature[i].ops))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int amdgpu_od_set_init(struct amdgpu_device *adev)
|
||||
{
|
||||
struct od_kobj *top_set, *sub_set;
|
||||
struct od_attribute *attribute;
|
||||
struct od_feature_container *container;
|
||||
struct od_feature_item *feature;
|
||||
int i, j;
|
||||
int ret;
|
||||
|
||||
/* Setup the top `gpu_od` directory which holds all other OD interfaces */
|
||||
top_set = kzalloc(sizeof(*top_set), GFP_KERNEL);
|
||||
if (!top_set)
|
||||
return -ENOMEM;
|
||||
list_add(&top_set->entry, &adev->pm.od_kobj_list);
|
||||
|
||||
ret = kobject_init_and_add(&top_set->kobj,
|
||||
&od_ktype,
|
||||
&adev->dev->kobj,
|
||||
"%s",
|
||||
"gpu_od");
|
||||
if (ret)
|
||||
goto err_out;
|
||||
INIT_LIST_HEAD(&top_set->attribute);
|
||||
top_set->priv = adev;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(amdgpu_od_set.containers); i++) {
|
||||
container = &amdgpu_od_set.containers[i];
|
||||
|
||||
if (!container->name)
|
||||
continue;
|
||||
|
||||
/*
|
||||
* If there is valid entries within the container, the container
|
||||
* will be presented as a sub directory and all its holding entries
|
||||
* will be presented as plain files under it.
|
||||
* While if there is no valid entry within the container, the container
|
||||
* itself will be presented as a plain file under top `gpu_od` directory.
|
||||
*/
|
||||
if (amdgpu_od_is_self_contained(adev, container)) {
|
||||
if (!amdgpu_is_od_feature_supported(adev,
|
||||
&container->ops))
|
||||
continue;
|
||||
|
||||
/*
|
||||
* The container is presented as a plain file under top `gpu_od`
|
||||
* directory.
|
||||
*/
|
||||
attribute = kzalloc(sizeof(*attribute), GFP_KERNEL);
|
||||
if (!attribute) {
|
||||
ret = -ENOMEM;
|
||||
goto err_out;
|
||||
}
|
||||
list_add(&attribute->entry, &top_set->attribute);
|
||||
|
||||
attribute->attribute.attr.mode =
|
||||
container->ops.is_visible(adev);
|
||||
attribute->attribute.attr.name = container->name;
|
||||
attribute->attribute.show =
|
||||
container->ops.show;
|
||||
attribute->attribute.store =
|
||||
container->ops.store;
|
||||
ret = sysfs_create_file(&top_set->kobj,
|
||||
&attribute->attribute.attr);
|
||||
if (ret)
|
||||
goto err_out;
|
||||
} else {
|
||||
/* The container is presented as a sub directory. */
|
||||
sub_set = kzalloc(sizeof(*sub_set), GFP_KERNEL);
|
||||
if (!sub_set) {
|
||||
ret = -ENOMEM;
|
||||
goto err_out;
|
||||
}
|
||||
list_add(&sub_set->entry, &adev->pm.od_kobj_list);
|
||||
|
||||
ret = kobject_init_and_add(&sub_set->kobj,
|
||||
&od_ktype,
|
||||
&top_set->kobj,
|
||||
"%s",
|
||||
container->name);
|
||||
if (ret)
|
||||
goto err_out;
|
||||
INIT_LIST_HEAD(&sub_set->attribute);
|
||||
sub_set->priv = adev;
|
||||
|
||||
for (j = 0; j < ARRAY_SIZE(container->sub_feature); j++) {
|
||||
feature = &container->sub_feature[j];
|
||||
if (!feature->name)
|
||||
continue;
|
||||
|
||||
if (!amdgpu_is_od_feature_supported(adev,
|
||||
&feature->ops))
|
||||
continue;
|
||||
|
||||
/*
|
||||
* With the container presented as a sub directory, the entry within
|
||||
* it is presented as a plain file under the sub directory.
|
||||
*/
|
||||
attribute = kzalloc(sizeof(*attribute), GFP_KERNEL);
|
||||
if (!attribute) {
|
||||
ret = -ENOMEM;
|
||||
goto err_out;
|
||||
}
|
||||
list_add(&attribute->entry, &sub_set->attribute);
|
||||
|
||||
attribute->attribute.attr.mode =
|
||||
feature->ops.is_visible(adev);
|
||||
attribute->attribute.attr.name = feature->name;
|
||||
attribute->attribute.show =
|
||||
feature->ops.show;
|
||||
attribute->attribute.store =
|
||||
feature->ops.store;
|
||||
ret = sysfs_create_file(&sub_set->kobj,
|
||||
&attribute->attribute.attr);
|
||||
if (ret)
|
||||
goto err_out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_out:
|
||||
amdgpu_od_set_fini(adev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int amdgpu_pm_sysfs_init(struct amdgpu_device *adev)
|
||||
{
|
||||
int ret;
|
||||
uint32_t mask = 0;
|
||||
int ret;
|
||||
|
||||
if (adev->pm.sysfs_initialized)
|
||||
return 0;
|
||||
|
|
@ -3387,15 +3631,31 @@ int amdgpu_pm_sysfs_init(struct amdgpu_device *adev)
|
|||
mask,
|
||||
&adev->pm.pm_attr_list);
|
||||
if (ret)
|
||||
return ret;
|
||||
goto err_out0;
|
||||
|
||||
if (amdgpu_dpm_is_overdrive_supported(adev)) {
|
||||
ret = amdgpu_od_set_init(adev);
|
||||
if (ret)
|
||||
goto err_out1;
|
||||
}
|
||||
|
||||
adev->pm.sysfs_initialized = true;
|
||||
|
||||
return 0;
|
||||
|
||||
err_out1:
|
||||
amdgpu_device_attr_remove_groups(adev, &adev->pm.pm_attr_list);
|
||||
err_out0:
|
||||
if (adev->pm.int_hwmon_dev)
|
||||
hwmon_device_unregister(adev->pm.int_hwmon_dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void amdgpu_pm_sysfs_fini(struct amdgpu_device *adev)
|
||||
{
|
||||
amdgpu_od_set_fini(adev);
|
||||
|
||||
if (adev->pm.int_hwmon_dev)
|
||||
hwmon_device_unregister(adev->pm.int_hwmon_dev);
|
||||
|
||||
|
|
|
|||
|
|
@ -366,6 +366,8 @@ struct amdgpu_pm {
|
|||
struct config_table_setting config_table;
|
||||
/* runtime mode */
|
||||
enum amdgpu_runpm_mode rpm_mode;
|
||||
|
||||
struct list_head od_kobj_list;
|
||||
};
|
||||
|
||||
int amdgpu_dpm_read_sensor(struct amdgpu_device *adev, enum amd_pp_sensors sensor,
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user