summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWill Song <jinpengsong@google.com>2024-02-12 19:31:00 -0800
committerWill Song <jinpengsong@google.com>2024-03-04 19:09:06 +0000
commit927893a91c4cad4564869c9cd341dbc231191ea7 (patch)
tree5aea3045baeeede6f7c7aa460f9b922415505907
parent72e0de316c0f37f1d585b5b093d1665ad49de83f (diff)
downloadgs-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.c425
-rw-r--r--drivers/performance/lat_governors/gs_governor_memlat.h34
-rw-r--r--drivers/performance/lat_governors/gs_governor_utils.c182
-rw-r--r--drivers/performance/lat_governors/gs_governor_utils.h140
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