| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Intel Uncore Frequency Control: Common code implementation |
| * Copyright (c) 2022, Intel Corporation. |
| * All rights reserved. |
| * |
| */ |
| #include <linux/cpu.h> |
| #include <linux/module.h> |
| #include "uncore-frequency-common.h" |
| |
| /* Mutex to control all mutual exclusions */ |
| static DEFINE_MUTEX(uncore_lock); |
| /* Root of the all uncore sysfs kobjs */ |
| static struct kobject *uncore_root_kobj; |
| /* uncore instance count */ |
| static int uncore_instance_count; |
| |
| static DEFINE_IDA(intel_uncore_ida); |
| |
| /* callbacks for actual HW read/write */ |
| static int (*uncore_read)(struct uncore_data *data, unsigned int *min, unsigned int *max); |
| static int (*uncore_write)(struct uncore_data *data, unsigned int input, unsigned int min_max); |
| static int (*uncore_read_freq)(struct uncore_data *data, unsigned int *freq); |
| |
| static ssize_t show_domain_id(struct kobject *kobj, struct kobj_attribute *attr, char *buf) |
| { |
| struct uncore_data *data = container_of(attr, struct uncore_data, domain_id_kobj_attr); |
| |
| return sprintf(buf, "%u\n", data->domain_id); |
| } |
| |
| static ssize_t show_fabric_cluster_id(struct kobject *kobj, struct kobj_attribute *attr, char *buf) |
| { |
| struct uncore_data *data = container_of(attr, struct uncore_data, fabric_cluster_id_kobj_attr); |
| |
| return sprintf(buf, "%u\n", data->cluster_id); |
| } |
| |
| static ssize_t show_package_id(struct kobject *kobj, struct kobj_attribute *attr, char *buf) |
| { |
| struct uncore_data *data = container_of(attr, struct uncore_data, package_id_kobj_attr); |
| |
| return sprintf(buf, "%u\n", data->package_id); |
| } |
| |
| static ssize_t show_min_max_freq_khz(struct uncore_data *data, |
| char *buf, int min_max) |
| { |
| unsigned int min, max; |
| int ret; |
| |
| mutex_lock(&uncore_lock); |
| ret = uncore_read(data, &min, &max); |
| mutex_unlock(&uncore_lock); |
| if (ret) |
| return ret; |
| |
| if (min_max) |
| return sprintf(buf, "%u\n", max); |
| |
| return sprintf(buf, "%u\n", min); |
| } |
| |
| static ssize_t store_min_max_freq_khz(struct uncore_data *data, |
| const char *buf, ssize_t count, |
| int min_max) |
| { |
| unsigned int input; |
| int ret; |
| |
| if (kstrtouint(buf, 10, &input)) |
| return -EINVAL; |
| |
| mutex_lock(&uncore_lock); |
| ret = uncore_write(data, input, min_max); |
| mutex_unlock(&uncore_lock); |
| |
| if (ret) |
| return ret; |
| |
| return count; |
| } |
| |
| static ssize_t show_perf_status_freq_khz(struct uncore_data *data, char *buf) |
| { |
| unsigned int freq; |
| int ret; |
| |
| mutex_lock(&uncore_lock); |
| ret = uncore_read_freq(data, &freq); |
| mutex_unlock(&uncore_lock); |
| if (ret) |
| return ret; |
| |
| return sprintf(buf, "%u\n", freq); |
| } |
| |
| #define store_uncore_min_max(name, min_max) \ |
| static ssize_t store_##name(struct kobject *kobj, \ |
| struct kobj_attribute *attr, \ |
| const char *buf, size_t count) \ |
| { \ |
| struct uncore_data *data = container_of(attr, struct uncore_data, name##_kobj_attr);\ |
| \ |
| return store_min_max_freq_khz(data, buf, count, \ |
| min_max); \ |
| } |
| |
| #define show_uncore_min_max(name, min_max) \ |
| static ssize_t show_##name(struct kobject *kobj, \ |
| struct kobj_attribute *attr, char *buf)\ |
| { \ |
| struct uncore_data *data = container_of(attr, struct uncore_data, name##_kobj_attr);\ |
| \ |
| return show_min_max_freq_khz(data, buf, min_max); \ |
| } |
| |
| #define show_uncore_perf_status(name) \ |
| static ssize_t show_##name(struct kobject *kobj, \ |
| struct kobj_attribute *attr, char *buf)\ |
| { \ |
| struct uncore_data *data = container_of(attr, struct uncore_data, name##_kobj_attr);\ |
| \ |
| return show_perf_status_freq_khz(data, buf); \ |
| } |
| |
| store_uncore_min_max(min_freq_khz, 0); |
| store_uncore_min_max(max_freq_khz, 1); |
| |
| show_uncore_min_max(min_freq_khz, 0); |
| show_uncore_min_max(max_freq_khz, 1); |
| |
| show_uncore_perf_status(current_freq_khz); |
| |
| #define show_uncore_data(member_name) \ |
| static ssize_t show_##member_name(struct kobject *kobj, \ |
| struct kobj_attribute *attr, char *buf)\ |
| { \ |
| struct uncore_data *data = container_of(attr, struct uncore_data,\ |
| member_name##_kobj_attr);\ |
| \ |
| return sysfs_emit(buf, "%u\n", \ |
| data->member_name); \ |
| } \ |
| |
| show_uncore_data(initial_min_freq_khz); |
| show_uncore_data(initial_max_freq_khz); |
| |
| #define init_attribute_rw(_name) \ |
| do { \ |
| sysfs_attr_init(&data->_name##_kobj_attr.attr); \ |
| data->_name##_kobj_attr.show = show_##_name; \ |
| data->_name##_kobj_attr.store = store_##_name; \ |
| data->_name##_kobj_attr.attr.name = #_name; \ |
| data->_name##_kobj_attr.attr.mode = 0644; \ |
| } while (0) |
| |
| #define init_attribute_ro(_name) \ |
| do { \ |
| sysfs_attr_init(&data->_name##_kobj_attr.attr); \ |
| data->_name##_kobj_attr.show = show_##_name; \ |
| data->_name##_kobj_attr.store = NULL; \ |
| data->_name##_kobj_attr.attr.name = #_name; \ |
| data->_name##_kobj_attr.attr.mode = 0444; \ |
| } while (0) |
| |
| #define init_attribute_root_ro(_name) \ |
| do { \ |
| sysfs_attr_init(&data->_name##_kobj_attr.attr); \ |
| data->_name##_kobj_attr.show = show_##_name; \ |
| data->_name##_kobj_attr.store = NULL; \ |
| data->_name##_kobj_attr.attr.name = #_name; \ |
| data->_name##_kobj_attr.attr.mode = 0400; \ |
| } while (0) |
| |
| static int create_attr_group(struct uncore_data *data, char *name) |
| { |
| int ret, freq, index = 0; |
| |
| init_attribute_rw(max_freq_khz); |
| init_attribute_rw(min_freq_khz); |
| init_attribute_ro(initial_min_freq_khz); |
| init_attribute_ro(initial_max_freq_khz); |
| init_attribute_root_ro(current_freq_khz); |
| |
| if (data->domain_id != UNCORE_DOMAIN_ID_INVALID) { |
| init_attribute_root_ro(domain_id); |
| data->uncore_attrs[index++] = &data->domain_id_kobj_attr.attr; |
| init_attribute_root_ro(fabric_cluster_id); |
| data->uncore_attrs[index++] = &data->fabric_cluster_id_kobj_attr.attr; |
| init_attribute_root_ro(package_id); |
| data->uncore_attrs[index++] = &data->package_id_kobj_attr.attr; |
| } |
| |
| data->uncore_attrs[index++] = &data->max_freq_khz_kobj_attr.attr; |
| data->uncore_attrs[index++] = &data->min_freq_khz_kobj_attr.attr; |
| data->uncore_attrs[index++] = &data->initial_min_freq_khz_kobj_attr.attr; |
| data->uncore_attrs[index++] = &data->initial_max_freq_khz_kobj_attr.attr; |
| |
| ret = uncore_read_freq(data, &freq); |
| if (!ret) |
| data->uncore_attrs[index++] = &data->current_freq_khz_kobj_attr.attr; |
| |
| data->uncore_attrs[index] = NULL; |
| |
| data->uncore_attr_group.name = name; |
| data->uncore_attr_group.attrs = data->uncore_attrs; |
| ret = sysfs_create_group(uncore_root_kobj, &data->uncore_attr_group); |
| |
| return ret; |
| } |
| |
| static void delete_attr_group(struct uncore_data *data, char *name) |
| { |
| sysfs_remove_group(uncore_root_kobj, &data->uncore_attr_group); |
| } |
| |
| int uncore_freq_add_entry(struct uncore_data *data, int cpu) |
| { |
| int ret = 0; |
| |
| mutex_lock(&uncore_lock); |
| if (data->valid) { |
| /* control cpu changed */ |
| data->control_cpu = cpu; |
| goto uncore_unlock; |
| } |
| |
| if (data->domain_id != UNCORE_DOMAIN_ID_INVALID) { |
| ret = ida_alloc(&intel_uncore_ida, GFP_KERNEL); |
| if (ret < 0) |
| goto uncore_unlock; |
| |
| data->instance_id = ret; |
| sprintf(data->name, "uncore%02d", ret); |
| } else { |
| sprintf(data->name, "package_%02d_die_%02d", data->package_id, data->die_id); |
| } |
| |
| uncore_read(data, &data->initial_min_freq_khz, &data->initial_max_freq_khz); |
| |
| ret = create_attr_group(data, data->name); |
| if (ret) { |
| if (data->domain_id != UNCORE_DOMAIN_ID_INVALID) |
| ida_free(&intel_uncore_ida, data->instance_id); |
| } else { |
| data->control_cpu = cpu; |
| data->valid = true; |
| } |
| |
| uncore_unlock: |
| mutex_unlock(&uncore_lock); |
| |
| return ret; |
| } |
| EXPORT_SYMBOL_NS_GPL(uncore_freq_add_entry, INTEL_UNCORE_FREQUENCY); |
| |
| void uncore_freq_remove_die_entry(struct uncore_data *data) |
| { |
| mutex_lock(&uncore_lock); |
| delete_attr_group(data, data->name); |
| data->control_cpu = -1; |
| data->valid = false; |
| if (data->domain_id != UNCORE_DOMAIN_ID_INVALID) |
| ida_free(&intel_uncore_ida, data->instance_id); |
| |
| mutex_unlock(&uncore_lock); |
| } |
| EXPORT_SYMBOL_NS_GPL(uncore_freq_remove_die_entry, INTEL_UNCORE_FREQUENCY); |
| |
| int uncore_freq_common_init(int (*read_control_freq)(struct uncore_data *data, unsigned int *min, unsigned int *max), |
| int (*write_control_freq)(struct uncore_data *data, unsigned int input, unsigned int set_max), |
| int (*read_freq)(struct uncore_data *data, unsigned int *freq)) |
| { |
| mutex_lock(&uncore_lock); |
| |
| uncore_read = read_control_freq; |
| uncore_write = write_control_freq; |
| uncore_read_freq = read_freq; |
| |
| if (!uncore_root_kobj) { |
| struct device *dev_root = bus_get_dev_root(&cpu_subsys); |
| |
| if (dev_root) { |
| uncore_root_kobj = kobject_create_and_add("intel_uncore_frequency", |
| &dev_root->kobj); |
| put_device(dev_root); |
| } |
| } |
| if (uncore_root_kobj) |
| ++uncore_instance_count; |
| mutex_unlock(&uncore_lock); |
| |
| return uncore_root_kobj ? 0 : -ENOMEM; |
| } |
| EXPORT_SYMBOL_NS_GPL(uncore_freq_common_init, INTEL_UNCORE_FREQUENCY); |
| |
| void uncore_freq_common_exit(void) |
| { |
| mutex_lock(&uncore_lock); |
| --uncore_instance_count; |
| if (!uncore_instance_count) { |
| kobject_put(uncore_root_kobj); |
| uncore_root_kobj = NULL; |
| } |
| mutex_unlock(&uncore_lock); |
| } |
| EXPORT_SYMBOL_NS_GPL(uncore_freq_common_exit, INTEL_UNCORE_FREQUENCY); |
| |
| |
| MODULE_LICENSE("GPL v2"); |
| MODULE_DESCRIPTION("Intel Uncore Frequency Common Module"); |