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@xxxxxxx> --- drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 2 + drivers/gpu/drm/amd/pm/amdgpu_pm.c | 264 ++++++++++++++++++++- drivers/gpu/drm/amd/pm/inc/amdgpu_dpm.h | 2 + 3 files changed, 266 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index 533daba2accb..7aae2801b36e 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -3623,6 +3623,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, diff --git a/drivers/gpu/drm/amd/pm/amdgpu_pm.c b/drivers/gpu/drm/amd/pm/amdgpu_pm.c index b0ef3d8b0ecf..beb3303fc832 100644 --- a/drivers/gpu/drm/amd/pm/amdgpu_pm.c +++ b/drivers/gpu/drm/amd/pm/amdgpu_pm.c @@ -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 }; -int amdgpu_pm_sysfs_init(struct amdgpu_device *adev) +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) +{ 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); diff --git a/drivers/gpu/drm/amd/pm/inc/amdgpu_dpm.h b/drivers/gpu/drm/amd/pm/inc/amdgpu_dpm.h index 42172b00be66..4cab6a2efb63 100644 --- a/drivers/gpu/drm/amd/pm/inc/amdgpu_dpm.h +++ b/drivers/gpu/drm/amd/pm/inc/amdgpu_dpm.h @@ -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, -- 2.34.1