diff options
author | Will Song <jinpengsong@google.com> | 2024-02-12 19:31:00 -0800 |
---|---|---|
committer | Will Song <jinpengsong@google.com> | 2024-03-04 19:09:06 +0000 |
commit | 927893a91c4cad4564869c9cd341dbc231191ea7 (patch) | |
tree | 5aea3045baeeede6f7c7aa460f9b922415505907 | |
parent | 72e0de316c0f37f1d585b5b093d1665ad49de83f (diff) | |
download | gs-927893a91c4cad4564869c9cd341dbc231191ea7.tar.gz |
drivers: performance: Add tick-driven memlat gov.
Adds the tick driven memory latency governor. The new
memlat governor is serviced by a kernel thread awoken
on interval from the arch_timer tick and opportunistically
from the sugov. Performance counters and cpu information
are gathered from the gs_perf_mon.
Test: Flash + Perfetto on Burncycles + gb5
Bug: 325274590
Bug: 262894231
Change-Id: I950845fe8dad41b35259866b09471d17aacd76db
Signed-off-by: Will Song <jinpengsong@google.com>
-rw-r--r-- | drivers/performance/lat_governors/gs_governor_memlat.c | 425 | ||||
-rw-r--r-- | drivers/performance/lat_governors/gs_governor_memlat.h | 34 | ||||
-rw-r--r-- | drivers/performance/lat_governors/gs_governor_utils.c | 182 | ||||
-rw-r--r-- | drivers/performance/lat_governors/gs_governor_utils.h | 140 |
4 files changed, 781 insertions, 0 deletions
diff --git a/drivers/performance/lat_governors/gs_governor_memlat.c b/drivers/performance/lat_governors/gs_governor_memlat.c new file mode 100644 index 000000000..c3a598a04 --- /dev/null +++ b/drivers/performance/lat_governors/gs_governor_memlat.c @@ -0,0 +1,425 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2024 Google LLC. + * + * Memory Latency Governor Main Module. + */ +#define pr_fmt(fmt) "gs_governor_memlat: " fmt + +#include <dt-bindings/soc/google/zuma-devfreq.h> +#include <governor.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <performance/gs_perf_mon/gs_perf_mon.h> +#include <soc/google/exynos-devfreq.h> +#include <trace/events/power.h> + +#include "gs_governor_memlat.h" +#include "gs_governor_utils.h" + +/** + * struct memlat_data - Node containing memlat's global data. + * @gov_is_on: Governor's active state. + * @devfreq_data: Pointer to the memlat's devfreq. + * @attr_grp: Tuneable governor parameters exposed to userspace. + * @num_cpu_clusters: Number of CPU clusters the governor will service. + * @cpu_configs_arr: Configurations for each cluster's latency vote. + */ +struct memlat_data { + bool gov_is_on; + struct exynos_devfreq_data *devfreq_data; + struct attribute_group *attr_grp; + int num_cpu_clusters; + struct cluster_config *cpu_configs_arr; +}; + +static void update_memlat_gov(struct gs_cpu_perf_data *data, void* private_data); + +/* Global monitor client used to get callbacks when gs_perf_mon data is updated. */ +static struct gs_perf_mon_client memlat_perf_client = { + .client_callback = update_memlat_gov, + .name = "memlat" +}; + +/* Memlat datastructure holding memlat governor configurations and metadata. */ +static struct memlat_data memlat_node; + +/* Macro Expansions for sysfs nodes.*/ +make_cluster_attr(memlat_node, stall_floor); +make_cluster_attr(memlat_node, ratio_ceil); +make_cluster_attr(memlat_node, cpuidle_state_depth_threshold); + +/* These sysfs emitters also depend on the macro expansions from above. */ +static struct attribute *memlat_dev_attr[] = { + &dev_attr_memlat_node_stall_floor.attr, + &dev_attr_memlat_node_ratio_ceil.attr, + &dev_attr_memlat_node_cpuidle_state_depth_threshold.attr, + NULL, +}; + +/* Sysfs files to expose. */ +static struct attribute_group memlat_dev_attr_group = { + .name = "memlat_attr", + .attrs = memlat_dev_attr, +}; + +/** + * update_memlat_gov - Callback function from the perf monitor to service + * the memlat governor. + * + * Input: + * @data: Performance Data from the monitor. + * @private_data: Unused. + * +*/ +static void update_memlat_gov(struct gs_cpu_perf_data *data, void* private_data) +{ + struct devfreq *df; + int err; + + df = memlat_node.devfreq_data->devfreq; + mutex_lock(&df->lock); + df->governor_data = data; + err = update_devfreq(df); + if (err) + dev_err(&df->dev, "Memlat update failed: %d\n", err); + df->governor_data = NULL; + mutex_unlock(&df->lock); +} + +/** + * gov_start - Starts the governor. + * + * This is invoked in devfreq_add_governor during probe. + * + * Input: + * @df: The devfreq to start. +*/ +static int gov_start(struct devfreq *df) +{ + int ret = gs_perf_mon_add_client(&memlat_perf_client); + if (ret) + goto err_start; + memlat_node.gov_is_on = true; + + ret = sysfs_create_group(&df->dev.kobj, memlat_node.attr_grp); + if (ret) + goto err_sysfs; + return 0; + +err_sysfs: + memlat_node.gov_is_on = false; + gs_perf_mon_remove_client(&memlat_perf_client); +err_start: + return ret; +} + +/** + * gov_suspend - Pauses the governor. + * + * This is invoked when the entering system suspends. + * + * Input: + * @df: The devfreq to suspend. +*/ +static int gov_suspend(struct devfreq *df) +{ + unsigned long prev_freq; + prev_freq = df->previous_freq; + memlat_node.gov_is_on = false; + + mutex_lock(&df->lock); + update_devfreq(df); + mutex_unlock(&df->lock); + gs_perf_mon_remove_client(&memlat_perf_client); + + return 0; +} + +/** + * gov_resume - Restarts the governor. + * + * Input: + * @df: The devfreq to resume. +*/ +static int gov_resume(struct devfreq *df) +{ + int ret = gs_perf_mon_add_client(&memlat_perf_client); + if (ret) + return ret; + + memlat_node.gov_is_on = true; + + mutex_lock(&df->lock); + update_devfreq(df); + mutex_unlock(&df->lock); + + return 0; +} + +/** + * gov_stop - Stops the governor. + * + * This is invoked by devfreq_remove_governor. + * + * Input: + * @df: The devfreq to stop. +*/ +static void gov_stop(struct devfreq *df) +{ + memlat_node.gov_is_on = false; + gs_perf_mon_remove_client(&memlat_perf_client); + + mutex_lock(&df->lock); + update_devfreq(df); + mutex_unlock(&df->lock); + + sysfs_remove_group(&df->dev.kobj, memlat_node.attr_grp); +} + +/** + * gs_governor_memlat_ev_handler - Handles governor + * Calls start/stop/resume/suspend events. + * + * Inputs: + * @df: The devfreq to signal. + * @event: Start/Stop/Suspend/Resume. + * @data: Unused. + * + * Return: Non-zero on error. Notice this also returns + * 0 if event is not found. +*/ +int gs_governor_memlat_ev_handler(struct devfreq *df, unsigned int event, void *data) +{ + int ret; + + /* Check if the governor exists. */ + if (!df) { + pr_err("Undefined devfreq for Memory Latency governor\n"); + return -ENODEV; + } + + switch (event) { + case DEVFREQ_GOV_START: + ret = gov_start(df); + if (ret) + return ret; + + dev_dbg(df->dev.parent, "Enabled Memory Latency governor\n"); + break; + + case DEVFREQ_GOV_STOP: + gov_stop(df); + dev_dbg(df->dev.parent, "Disabled Memory Latency governor\n"); + break; + + case DEVFREQ_GOV_SUSPEND: + ret = gov_suspend(df); + if (ret) { + dev_err(df->dev.parent, "Unable to suspend Memory Latency governor (%d)\n", ret); + return ret; + } + + dev_dbg(df->dev.parent, "Suspended Memory Latency governor\n"); + break; + + case DEVFREQ_GOV_RESUME: + ret = gov_resume(df); + if (ret) { + dev_err(df->dev.parent, "Unable to resume Memory Latency governor (%d)\n", ret); + return ret; + } + + dev_dbg(df->dev.parent, "Resumed Memory Latency governor\n"); + break; + } + return 0; +} +EXPORT_SYMBOL(gs_governor_memlat_ev_handler); + +/** + * gs_governor_memlat_get_freq - Calculates memlat freq votes desired by each CPU cluster. + * + * This function determines the memlat target frequency. + * + * Input: + * @df: The devfreq we are deciding a vote for. + * @freq: Where to store the computed frequency. +*/ +int gs_governor_memlat_get_freq(struct devfreq *df, unsigned long *freq) +{ + int cpu; + int cluster_idx; + struct cluster_config *cluster; + unsigned long max_freq = 0; + char trace_name[] = { 'c', 'p', 'u', '0', 'm', 'i', 'f', '\0' }; + + /* Retrieving the CPU data array from the devfreq governor_data. */ + struct gs_cpu_perf_data *cpu_perf_data_arr = df->governor_data; + + /* If the memlat governor is not active. Reset our vote to minimum. */ + if (!memlat_node.gov_is_on) { + *freq = 0; + goto trace_out; + } + + /* If monitor data is not supplied. Maintain current vote. */ + if (!cpu_perf_data_arr) + goto trace_out; + + /* For each cluster, we make a frequency decision. */ + for (cluster_idx = 0; cluster_idx < memlat_node.num_cpu_clusters; cluster_idx++) { + cluster = &memlat_node.cpu_configs_arr[cluster_idx]; + for_each_cpu(cpu, &cluster->cpus) { + unsigned long ratio, mem_stall_pct, mem_stall_floor, ratio_ceil; + unsigned long l3_cachemiss, mem_stall, cyc, last_delta_us, inst; + unsigned long mif_freq = 0, effective_cpu_freq_khz; + bool memlat_cpuidle_state_aware; + enum gs_perf_cpu_idle_state memlat_configured_idle_depth_threshold; + struct gs_cpu_perf_data *cpu_data = &cpu_perf_data_arr[cpu]; + trace_name[3] = '0' + cpu; + + /* Check if the cpu monitor is up. */ + if (!cpu_data->cpu_mon_on) + goto early_exit; + + l3_cachemiss = cpu_data->perf_ev_last_delta[PERF_L3_CACHE_MISS_IDX]; + mem_stall = cpu_data->perf_ev_last_delta[PERF_STALL_BACKEND_MEM_IDX]; + cyc = cpu_data->perf_ev_last_delta[PERF_CYCLE_IDX]; + inst = cpu_data->perf_ev_last_delta[PERF_INST_IDX]; + last_delta_us = cpu_data->time_delta_us; + + ratio_ceil = cluster->ratio_ceil; + mem_stall_floor = cluster->stall_floor; + memlat_cpuidle_state_aware = cluster->cpuidle_state_aware; + memlat_configured_idle_depth_threshold = cluster->cpuidle_state_depth_threshold; + + /* Compute threshold data. */ + if (l3_cachemiss != 0) + ratio = inst / l3_cachemiss; + else + ratio = inst; + + mem_stall_pct = mult_frac(10000, mem_stall, cyc); + effective_cpu_freq_khz = MHZ_TO_KHZ * cyc / last_delta_us; + + if (memlat_cpuidle_state_aware && cpu_data->cpu_idle_state >= memlat_configured_idle_depth_threshold) + goto early_exit; // Zeroing vote for sufficiently idle CPUs. + + /* If we pass the threshold, use the latency table. */ + if (ratio <= ratio_ceil && mem_stall_pct >= mem_stall_floor) { + mif_freq = gs_governor_core_to_dev_freq(cluster->latency_freq_table, + effective_cpu_freq_khz); + if (mif_freq > max_freq) + max_freq = mif_freq; + } + early_exit: + /* Leave a trace for the cluster desired MIF frequency. */ + trace_clock_set_rate(trace_name, mif_freq, cpu); + } + } + + /* We vote on the max score across all cpus. */ + *freq = max_freq; + +trace_out: + trace_clock_set_rate("Memlat Governor", *freq, raw_smp_processor_id()); + return 0; +} +EXPORT_SYMBOL(gs_governor_memlat_get_freq); + +/** + * gs_memlat_populate_governor - Parses memlat governor data from an input device tree node. + * + * Inputs: + * @dev: The memlat governor's underlying device. + * @governor_node: The tree node contanin governor data. + * + * Returns: Non-zero on error. +*/ +static int gs_memlat_populate_governor(struct device *dev, struct device_node *governor_node) +{ + struct device_node *cluster_node = NULL; + struct cluster_config *cluster; + int cluster_idx; + int ret = 0; + + memlat_node.num_cpu_clusters = of_get_child_count(governor_node); + + /* Allocate a container for clusters. */ + memlat_node.cpu_configs_arr = devm_kzalloc( + dev, sizeof(struct cluster_config) * memlat_node.num_cpu_clusters, GFP_KERNEL); + if (!memlat_node.cpu_configs_arr) { + dev_err(dev, "no mem for cluster_config\n"); + return -ENOMEM; + } + + /* Populate the Components. */ + cluster_idx = 0; + while ((cluster_node = of_get_next_child(governor_node, cluster_node)) != NULL) { + cluster = &memlat_node.cpu_configs_arr[cluster_idx]; + if ((ret = populate_cluster_config(dev, cluster_node, cluster))) + return ret; + + /* Increment pointer. */ + cluster_idx += 1; + } + + return 0; +} + +/** + * gs_memlat_governor_initialize - Initializes the dsulat governor from a DT Node. + * + * Inputs: + * @governor_node: The tree node contanin governor data. + * @data: The devfreq data to update frequencies. + * + * Returns: Non-zero on error. +*/ +int gs_memlat_governor_initialize(struct device_node *governor_node, + struct exynos_devfreq_data *data) +{ + int ret = 0; + struct device *dev = data->dev; + + /* Configure memlat governor. */ + ret = gs_memlat_populate_governor(dev, governor_node); + if (ret) + return ret; + + memlat_node.attr_grp = &memlat_dev_attr_group; + memlat_node.devfreq_data = data; + return 0; + +} +EXPORT_SYMBOL(gs_memlat_governor_initialize); + +/* We hold the struct for the governor here. */ +static struct devfreq_governor gs_governor_memlat = { + .name = "gs_memlat", + .get_target_freq = gs_governor_memlat_get_freq, + .event_handler = gs_governor_memlat_ev_handler, +}; + +/* Adds this governor to a devfreq.*/ +int gs_memlat_governor_register(void) +{ + return devfreq_add_governor(&gs_governor_memlat); +} +EXPORT_SYMBOL(gs_memlat_governor_register); + +/* Remove this governor to a devfreq.*/ +void gs_memlat_governor_unregister(void) +{ + devfreq_remove_governor(&gs_governor_memlat); +} +EXPORT_SYMBOL(gs_memlat_governor_unregister); + +MODULE_AUTHOR("Will Song <jinpengsong@google.com>"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Google Source Memlat Governor");
\ No newline at end of file diff --git a/drivers/performance/lat_governors/gs_governor_memlat.h b/drivers/performance/lat_governors/gs_governor_memlat.h new file mode 100644 index 000000000..2bb577988 --- /dev/null +++ b/drivers/performance/lat_governors/gs_governor_memlat.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2024 Google, Inc. All rights reserved. + */ + +#ifndef _GS_GOVERNOR_MEMLAT_H +#define _GS_GOVERNOR_MEMLAT_H + +#include <linux/kernel.h> +#include <linux/devfreq.h> + +/** + * gs_memlat_governor_register - Adds the governor to the devfreq system. +*/ +int gs_memlat_governor_register(void); + +/** + * gs_memlat_governor_unregister - Removes governor from the devfreq system. +*/ +void gs_memlat_governor_unregister(void); + +/** + * gs_memlat_governor_initialize - Parse and init memlat governor data. + * + * Inputs: + * @governor_node: The device tree node containing memlat data. + * @data: Devfreq data for governor. + * + * Returns: Non-zero on error. +*/ +int gs_memlat_governor_initialize(struct device_node *governor_node, + struct exynos_devfreq_data *data); + +#endif /* _GS_GOVERNOR_MEMLAT_H */ diff --git a/drivers/performance/lat_governors/gs_governor_utils.c b/drivers/performance/lat_governors/gs_governor_utils.c new file mode 100644 index 000000000..3ae6390ac --- /dev/null +++ b/drivers/performance/lat_governors/gs_governor_utils.c @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2024 Google LLC. + * + * A Utility File for Shared Functions Between + * Google Governors. + */ +#define pr_fmt(fmt) "gs_governor_utils: " fmt + +#include <linux/cpu.h> +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/of.h> +#include <performance/gs_perf_mon/gs_perf_mon.h> + +#include "gs_governor_utils.h" + +#define NUM_FREQ_TABLE_COLS 2 + +long gs_governor_core_to_dev_freq(struct gs_governor_core_dev_map *map, unsigned long input_freq) +{ + unsigned long freq = 0; + + if (!map) + goto out; + + /* + * We use the target frequency for the highest node with a frequency + * *less or equal* to the current frequency. I.e. the table entries + * should each represent new explicit thresholds for frequencies exceeding + * the specified values. + */ + while (map->core_khz && map->core_khz <= input_freq) { + freq = map->target_freq; + map++; + } + +out: + pr_debug("freq: %lu -> dev: %lu\n", input_freq, freq); + return freq; +} +EXPORT_SYMBOL(gs_governor_core_to_dev_freq); + +struct gs_governor_core_dev_map * +gs_governor_init_core_dev_map(struct device *dev, struct device_node *of_node, char *prop_name) +{ + int len, nf, i, j; + u32 data; + struct gs_governor_core_dev_map *tbl; + int ret; + + if (!of_node) + of_node = dev->of_node; + + if (!of_find_property(of_node, prop_name, &len)) + return NULL; + + len /= sizeof(data); + + if (len % NUM_FREQ_TABLE_COLS || len == 0) + return NULL; + + nf = len / NUM_FREQ_TABLE_COLS; + tbl = devm_kzalloc(dev, (nf + 1) * sizeof(*tbl), GFP_KERNEL); + if (!tbl) + return NULL; + + for (i = 0; i < nf; i++) { + j = 2*i; + ret = of_property_read_u32_index(of_node, prop_name, j, &data); + if (ret) + return NULL; + + tbl[i].core_khz = data; + + ret = of_property_read_u32_index(of_node, prop_name, j + 1, &data); + if (ret) + return NULL; + + tbl[i].target_freq = data; + pr_debug("Entry%d CPU:%u, Dev:%u\n", i, tbl[i].core_khz, tbl[i].target_freq); + } + + /* This is a sentinel marking the end of the array. */ + tbl[i].core_khz = 0; + + return tbl; +} +EXPORT_SYMBOL(gs_governor_init_core_dev_map); + +/** + * parse_device_node_cpumask - General parser for cpu masks. + * + * Inputs: + * @np: Pointer to device tree node containing mask. + * @mask: Container for resulting cpu_mask. + * @cpu_list_name: Name of the cpu mask attribute. + * + * Returns: Non-zero on error. +*/ +static int parse_device_node_cpumask(struct device_node *np, cpumask_t *mask, char *cpu_list_name) +{ + struct device_node *dev_phandle; + struct device *cpu_dev; + int cpu, i = 0; + int ret = -ENOENT; + + dev_phandle = of_parse_phandle(np, cpu_list_name, i++); + while (dev_phandle) { + for_each_possible_cpu(cpu) { + cpu_dev = get_cpu_device(cpu); + if (cpu_dev && cpu_dev->of_node == dev_phandle) { + cpumask_set_cpu(cpu, mask); + ret = 0; + break; + } + } + dev_phandle = of_parse_phandle(np, cpu_list_name, i++); + } + return ret; +} + +int populate_cluster_config(struct device *dev, struct device_node *cluster_node, + struct cluster_config *cluster) +{ + /* Retrieve the list of CPUs in the current cluster. */ + int ret = 0; + ret = parse_device_node_cpumask(cluster_node, &cluster->cpus, "cpulist"); + if (ret) { + dev_err(dev, "Can't parse cpu_list.\n"); + return -EINVAL; + } + + ret = of_property_read_u32(cluster_node, "ratio_ceil", &(cluster->ratio_ceil)); + if (ret) { + dev_err(dev, "ratio_ceil unspecified.\n"); + return -EINVAL; + } + + ret = of_property_read_u32(cluster_node, "stall_floor", &(cluster->stall_floor)); + if (ret) { + dev_err(dev, "stall_floor unspecified.\n"); + return -EINVAL; + } + + /* + * Idle Aware Config. Expected to be 1 for C1 idle or 2 for C2 idle. + */ + if (of_property_read_u32(cluster_node, "cpuidle_state_depth_threshold", &cluster->cpuidle_state_depth_threshold)) { + dev_dbg(dev, "Config idle_aware mising. Defaulting to no idle awareness.\n"); + cluster->cpuidle_state_aware = false; + } + + if (cluster->cpuidle_state_depth_threshold == PERF_CPU_IDLE_C1 || cluster->cpuidle_state_depth_threshold == PERF_CPU_IDLE_C2) + cluster->cpuidle_state_aware = true; + else + cluster->cpuidle_state_aware = false; + + /* Parsing latency_freq_table. */ + cluster->latency_freq_table = + gs_governor_init_core_dev_map(dev, cluster_node, "core-dev-table-latency"); + if (!cluster->latency_freq_table) { + dev_err(dev, "Couldn't find the core-dev-table-latency! Aborting probe!\n"); + return -EINVAL; + } + + /* Parsing base_freq_table. */ + cluster->base_freq_table = + gs_governor_init_core_dev_map(dev, cluster_node, "core-dev-table-base"); + if (!cluster->base_freq_table) { + dev_err(dev, + "Couldn't find the core-dev-table-base! Defaulting to latency-table.\n"); + cluster->base_freq_table = cluster->latency_freq_table; + } + return ret; +} +EXPORT_SYMBOL(populate_cluster_config); + +MODULE_AUTHOR("Will Song <jinpengsong@google.com>"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Google Source Governor Utilities");
\ No newline at end of file diff --git a/drivers/performance/lat_governors/gs_governor_utils.h b/drivers/performance/lat_governors/gs_governor_utils.h new file mode 100644 index 000000000..34936ab33 --- /dev/null +++ b/drivers/performance/lat_governors/gs_governor_utils.h @@ -0,0 +1,140 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2024 Google, Inc. + */ + +#ifndef _GS_GOVERNOR_UTILS_H +#define _GS_GOVERNOR_UTILS_H + +#include <linux/kernel.h> +#include <linux/devfreq.h> +#include <performance/gs_perf_mon/gs_perf_mon.h> + +/* Conversion parameter */ +#define MHZ_TO_KHZ 1000 + +/** + * struct gs_governor_core_dev_map - Maps cpu frequency to desired device frequency. + * @core_khz: CPU frequency. + * @target_freq: The corresponding device frequency. + */ +struct gs_governor_core_dev_map { + unsigned int core_khz; + unsigned int target_freq; +}; + +/** + * struct cluster_config - Per cluster decision information. + * @name: Component name (ex. MIF-CL0). + * @cpus: Which cpus this component considers. + * + * @ratio_ceil: Ratio of performance metrics (e.g. instructions + * executed / memory accesses) below which we consider + * the CPU may be stalled by the target device. + * + * @stall_floor: Ratio of performance metrics (e.g. memory stalls / CPU cycles) + * above which we consider the CPU may be stalled by the + * target device. + * + * @cpuidle_state_aware: Whether to consider CPU idle for device frequency calculation. + * + * @base_freq_table: Votes applied on behalf of the CPU cluster if it is not + * determined to be currently stalling. + * + * @latency_freq_table: Votes applied on behalf of the CPU cluster if it is determined + * to be currently stalling. (This should be a more aggressive table + * than base_freq_table). + */ +struct cluster_config { + const char *name; + cpumask_t cpus; + unsigned int ratio_ceil; + unsigned int stall_floor; + enum gs_perf_cpu_idle_state cpuidle_state_depth_threshold; + bool cpuidle_state_aware; + struct gs_governor_core_dev_map *base_freq_table; + struct gs_governor_core_dev_map *latency_freq_table; +}; + +/** + * gs_governor_core_to_dev_freq - Utility to look up in frequency maps. + * + * Inputs: + * @map: The frequency table to lookup. + * @input_freq: The input lookup frequency in khz. + * + * Returns: Device frequency in khz. + */ +long gs_governor_core_to_dev_freq(struct gs_governor_core_dev_map *map, unsigned long input_freq); + +/** + * gs_governor_init_core_dev_map - populate the frequency map + * + * Inputs: + * @dev: Device the map belongs to. + * @of_node: The device tree node containing the map. + * @prop_name: The name of the frequency table in the device tree node. + * + * Returns: Device allocated frequency map. +*/ +struct gs_governor_core_dev_map * +gs_governor_init_core_dev_map(struct device *dev, struct device_node *of_node, char *prop_name); + +/** + * populate_cluster_config - Initialize a cluster_config from an input device tree node. + * + * Inputs: + * @dev: Device this component is for. + * @cluster_node: The device tree node containing latgov configs. + * @cluster: Container for the latgov configs. + * + * Returns: Non-zero on error. +*/ +int populate_cluster_config(struct device *dev, struct device_node *cluster_node, + struct cluster_config *cluster); + +/**************************************************************** + * SYSFS * + ****************************************************************/ +#define make_cluster_attr(_node_name, _name) \ + static ssize_t _node_name##_##_name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ + { \ + int res = 0; \ + int cluster_idx; \ + for (cluster_idx = 0; cluster_idx < _node_name.num_cpu_clusters; cluster_idx++) { \ + res += sysfs_emit_at(buf, res, "%u\n", \ + _node_name.cpu_configs_arr[cluster_idx]._name); \ + } \ + return res; \ + } \ + \ + static ssize_t _node_name##_##_name##_store( \ + struct device *dev, struct device_attribute *attr, const char *buf, size_t count) \ + { \ + int ret = 0; \ + int cluster_idx; \ + char *token; \ + char *token_bookmark; \ + unsigned int val; \ + char *internal_buffer = \ + kstrndup(buf, 10 * _node_name.num_cpu_clusters, GFP_KERNEL); \ + if (!internal_buffer) \ + return -ENOMEM; \ + token_bookmark = internal_buffer; \ + for (cluster_idx = 0; cluster_idx < _node_name.num_cpu_clusters; cluster_idx++) { \ + token = strsep(&token_bookmark, " "); \ + if (!token) \ + goto free_out; \ + ret = kstrtouint(token, 0, &val); \ + if (ret) \ + goto free_out; \ + _node_name.cpu_configs_arr[cluster_idx]._name = val; \ + } \ + free_out: \ + kfree(internal_buffer); \ + return count; \ + } \ + static DEVICE_ATTR_RW(_node_name##_##_name); + +#endif /* _GS_GOVERNOR_UTILS_H */
\ No newline at end of file |