// SPDX-License-Identifier: GPL-2.0 /* * Intel Uncore Frequency Control: Common code implementation * Copyright (c) 2022, Intel Corporation. * All rights reserved. * */ #include #include #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");