summaryrefslogtreecommitdiff
path: root/drivers/edgetpu/gcip-kernel-driver/drivers/gcip/gcip-usage-stats.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/edgetpu/gcip-kernel-driver/drivers/gcip/gcip-usage-stats.c')
-rw-r--r--drivers/edgetpu/gcip-kernel-driver/drivers/gcip/gcip-usage-stats.c1075
1 files changed, 1075 insertions, 0 deletions
diff --git a/drivers/edgetpu/gcip-kernel-driver/drivers/gcip/gcip-usage-stats.c b/drivers/edgetpu/gcip-kernel-driver/drivers/gcip/gcip-usage-stats.c
new file mode 100644
index 0000000..88efbd9
--- /dev/null
+++ b/drivers/edgetpu/gcip-kernel-driver/drivers/gcip/gcip-usage-stats.c
@@ -0,0 +1,1075 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Interface of managing the usage stats of IPs.
+ *
+ * Copyright (C) 2023 Google LLC
+ */
+
+#include <linux/device.h>
+#include <linux/hashtable.h>
+#include <linux/kernel.h>
+#include <linux/lockdep.h>
+#include <linux/mutex.h>
+#include <linux/string.h>
+#include <linux/types.h>
+
+#include <gcip/gcip-usage-stats.h>
+
+typedef ssize_t (*show_t)(struct device *dev, struct device_attribute *dev_attr, char *buf);
+typedef ssize_t (*store_t)(struct device *dev, struct device_attribute *dev_attr, const char *buf,
+ size_t count);
+
+/*
+ * Show callback which simply redirects to the user defined one.
+ * If GCIP doesn't have its own implementation because we expect that it won't be used in the real
+ * cases or the user has their own implementation for the specific metric, this callback will
+ * be used for the show function of the device attribute.
+ */
+static ssize_t gcip_usage_stats_user_defined_show(struct device *dev,
+ struct device_attribute *dev_attr, char *buf)
+{
+ struct gcip_usage_stats_attr *attr =
+ container_of(dev_attr, struct gcip_usage_stats_attr, dev_attr);
+ ssize_t ret = 0;
+
+ if (attr->show)
+ ret = attr->show(dev, attr, buf, attr->ustats->data);
+
+ return ret;
+}
+
+/*
+ * Store callback which simply redirects to the user defined one.
+ * If GCIP doesn't have its own implementation because we expect that it won't be used in the real
+ * cases or the user has their own implementation for the specific metric, this callback will
+ * be used for the store function of the device attribute.
+ */
+static ssize_t gcip_usage_stats_user_defined_store(struct device *dev,
+ struct device_attribute *dev_attr,
+ const char *buf, size_t count)
+{
+ struct gcip_usage_stats_attr *attr =
+ container_of(dev_attr, struct gcip_usage_stats_attr, dev_attr);
+ ssize_t ret = 0;
+
+ if (attr->store)
+ ret = attr->store(dev, attr, buf, count, attr->ustats->data);
+
+ return ret;
+}
+
+/* Following functions are related to `CORE_USAGE` metrics. */
+
+/*
+ * Returns the core usage entry of @uid and @core_id from @ustats->core_usage_htable hash table.
+ * Caller must hold @ustats->usage_stats_lock.
+ */
+static struct gcip_usage_stats_core_usage_uid_entry *
+gcip_usage_stats_find_core_usage_entry_locked(int32_t uid, uint8_t core_id,
+ struct gcip_usage_stats *ustats)
+{
+ struct gcip_usage_stats_core_usage_uid_entry *uid_entry;
+
+ lockdep_assert_held(&ustats->usage_stats_lock);
+
+ hash_for_each_possible (ustats->core_usage_htable[core_id], uid_entry, node, uid) {
+ if (uid_entry->uid == uid)
+ return uid_entry;
+ }
+
+ return NULL;
+}
+
+/* Returns the 0-based index of @dvfs_freq from the frequency table. */
+static unsigned int gcip_usage_stats_find_dvfs_freq_index(struct gcip_usage_stats *ustats,
+ uint32_t dvfs_freq)
+{
+ int i, nums, idx = 0;
+
+ mutex_lock(&ustats->dvfs_freqs_lock);
+
+ /*
+ * Uses the frequency table, @ustats->dvfs_freqs, if the firmware has ever reported
+ * frequencies via `DVFS_FREQUENCY_INFO` metrics.
+ */
+ if (ustats->dvfs_freqs_num) {
+ for (i = ustats->dvfs_freqs_num - 1; i >= 0; i--) {
+ if (dvfs_freq == ustats->dvfs_freqs[i])
+ idx = i;
+ }
+
+ if (i < 0)
+ dev_warn(ustats->dev,
+ "Failed to find the freq among the ones sent from the FW, freq=%u",
+ dvfs_freq);
+
+ mutex_unlock(&ustats->dvfs_freqs_lock);
+ return idx;
+ }
+
+ mutex_unlock(&ustats->dvfs_freqs_lock);
+
+ /* Uses default one in case of the firmware has never reported supported frequencies. */
+ nums = ustats->ops->get_default_dvfs_freqs_num(ustats->data);
+ if (nums <= 0) {
+ dev_warn_once(ustats->dev, "The kernel driver doesn't have default DVFS freqs");
+ return 0;
+ }
+
+ for (i = nums - 1; i >= 0; i--) {
+ if (dvfs_freq >= ustats->ops->get_default_dvfs_freq(i, ustats->data))
+ return i;
+ }
+
+ dev_warn(ustats->dev,
+ "Failed to find the freq from the default ones of the kernel driver, freq=%u",
+ dvfs_freq);
+
+ return 0;
+}
+
+/*
+ * Updates the entry of @uid in the core usage hash table of @core_id.
+ * If there is no entry for @uid, it will create one and insert it into the table.
+ *
+ * Called when the FW sent `CORE_USAGE` metrics.
+ */
+static void gcip_usage_stats_update_core_usage(struct gcip_usage_stats *ustats,
+ struct gcip_usage_stats_core_usage *new,
+ int fw_metric_version)
+{
+ struct gcip_usage_stats_core_usage_uid_entry *uid_entry;
+ unsigned int state = gcip_usage_stats_find_dvfs_freq_index(ustats, new->operating_point);
+ uint8_t core_id = 0;
+
+ if (fw_metric_version >= GCIP_USAGE_STATS_V2)
+ core_id = new->core_id;
+
+ if (core_id >= ustats->subcomponents) {
+ dev_warn_once(ustats->dev,
+ "FW sent an invalid core_id for the core usage update, core_id=%u",
+ core_id);
+ return;
+ }
+
+ mutex_lock(&ustats->usage_stats_lock);
+
+ /* Finds the uid from @ustats->core_usage_htable first. */
+ uid_entry = gcip_usage_stats_find_core_usage_entry_locked(new->uid, core_id, ustats);
+ if (uid_entry) {
+ uid_entry->time_in_state[state] += new->control_core_duration;
+ mutex_unlock(&ustats->usage_stats_lock);
+ return;
+ }
+
+ dev_dbg(ustats->dev, "FW sent a new uid for the core usage update, uid=%d, core_id=%u",
+ new->uid, core_id);
+
+ /* Allocates an entry for this uid. */
+ uid_entry = devm_kzalloc(ustats->dev, sizeof(*uid_entry), GFP_KERNEL);
+ if (!uid_entry) {
+ dev_err(ustats->dev,
+ "Failed to allocate an entry of core usage hash table, uid=%d, core_id=%u",
+ new->uid, core_id);
+ mutex_unlock(&ustats->usage_stats_lock);
+ return;
+ }
+
+ uid_entry->uid = new->uid;
+ uid_entry->time_in_state[state] += new->control_core_duration;
+
+ /* Adds @uid_entry to the @ustats->core_usage_htable. */
+ hash_add(ustats->core_usage_htable[core_id], &uid_entry->node, new->uid);
+
+ mutex_unlock(&ustats->usage_stats_lock);
+}
+
+/* Releases all entries in the core usage hash table of @core_id. */
+static void gcip_usage_stats_free_core_usage_core_entries_locked(struct gcip_usage_stats *ustats,
+ uint8_t core_id)
+{
+ unsigned int bkt;
+ struct gcip_usage_stats_core_usage_uid_entry *uid_entry;
+ struct hlist_node *tmp;
+
+ lockdep_assert_held(&ustats->usage_stats_lock);
+
+ hash_for_each_safe (ustats->core_usage_htable[core_id], bkt, tmp, uid_entry, node) {
+ hash_del(&uid_entry->node);
+ devm_kfree(ustats->dev, uid_entry);
+ }
+}
+
+/* Releases all entries of all core usage hash tables. */
+static void gcip_usage_stats_free_core_usage_all_entries(struct gcip_usage_stats *ustats)
+{
+ int i;
+
+ mutex_lock(&ustats->usage_stats_lock);
+
+ for (i = 0; i < ustats->subcomponents; i++)
+ gcip_usage_stats_free_core_usage_core_entries_locked(ustats, i);
+
+ mutex_unlock(&ustats->usage_stats_lock);
+}
+
+/*
+ * Prints the core usage per uid in multiple arrays with the whitespace separation:
+ * <uid_0> <core_usage_0_1> <core_usage_0_2> ...
+ * <uid_1> <core_usage_1_1> <core_usage_1_2> ...
+ * ...
+ *
+ * Called when the runtime reads the device attribute.
+ */
+static ssize_t gcip_usage_stats_core_usage_show(struct device *dev,
+ struct device_attribute *dev_attr, char *buf)
+{
+ struct gcip_usage_stats_attr *attr =
+ container_of(dev_attr, struct gcip_usage_stats_attr, dev_attr);
+ struct gcip_usage_stats *ustats = attr->ustats;
+ struct gcip_usage_stats_core_usage_uid_entry *uid_entry;
+ int i, dvfs_freqs_num;
+ unsigned int bkt;
+ ssize_t written = 0;
+
+ ustats->ops->update_usage_kci(ustats->data);
+
+ mutex_lock(&ustats->dvfs_freqs_lock);
+
+ if (!ustats->dvfs_freqs_num)
+ dvfs_freqs_num = ustats->ops->get_default_dvfs_freqs_num(ustats->data);
+ else
+ dvfs_freqs_num = ustats->dvfs_freqs_num;
+
+ mutex_unlock(&ustats->dvfs_freqs_lock);
+ mutex_lock(&ustats->usage_stats_lock);
+
+ hash_for_each (ustats->core_usage_htable[attr->subcomponent], bkt, uid_entry, node) {
+ written += scnprintf(buf + written, PAGE_SIZE - written, "%d", uid_entry->uid);
+
+ for (i = 0; i < dvfs_freqs_num; i++)
+ written += scnprintf(buf + written, PAGE_SIZE - written, " %lld",
+ uid_entry->time_in_state[i]);
+
+ written += scnprintf(buf + written, PAGE_SIZE - written, "\n");
+ }
+
+ mutex_unlock(&ustats->usage_stats_lock);
+
+ return written;
+}
+
+/*
+ * Releases all the entries of the core usage hash table of the given core id, @attr->subcomponent.
+ *
+ * Called when the runtime writes the device attribute.
+ */
+static ssize_t gcip_usage_stats_core_usage_store(struct device *dev,
+ struct device_attribute *dev_attr, const char *buf,
+ size_t count)
+{
+ struct gcip_usage_stats_attr *attr =
+ container_of(dev_attr, struct gcip_usage_stats_attr, dev_attr);
+ struct gcip_usage_stats *ustats = attr->ustats;
+
+ mutex_lock(&ustats->usage_stats_lock);
+ gcip_usage_stats_free_core_usage_core_entries_locked(ustats, attr->subcomponent);
+ mutex_unlock(&ustats->usage_stats_lock);
+
+ return count;
+}
+
+/* Following functions are related to `COMPONENT_UTILIZATION` metrics. */
+
+/*
+ * Updates the utilization of components.
+ * The value of utilization must be [0, 100].
+ *
+ * Called when the FW sent `COMPONENT_UTILIZATION` metrics.
+ */
+static void
+gcip_usage_stats_update_component_utilization(struct gcip_usage_stats *ustats,
+ struct gcip_usage_stats_component_utilization *new,
+ uint16_t fw_metric_version)
+{
+ if (new->component < 0 ||
+ new->component >= GCIP_USAGE_STATS_COMPONENT_UTILIZATION_NUM_TYPES) {
+ dev_warn_once(ustats->dev, "FW sent an invalid component utilization type, type=%d",
+ new->component);
+ return;
+ }
+
+ if (new->utilization < 0 || new->utilization > 100) {
+ dev_warn_once(ustats->dev,
+ "FW sent an invalid component utilization value, type=%d, value=%d",
+ new->component, new->utilization);
+ return;
+ }
+
+ mutex_lock(&ustats->usage_stats_lock);
+ ustats->component_utilization[new->component] = new->utilization;
+ mutex_unlock(&ustats->usage_stats_lock);
+}
+
+/*
+ * Prints the utilization of the specific component.
+ * Note that this function also resets the utilization to 0. Therefore, we don't have a specific
+ * store function implementation for this stats. The `gcip_usage_stats_alloc_attrs` function will
+ * register the `gcip_usage_stats_user_defined_store` function as the store function if the kernel
+ * driver enables the write permission.
+ *
+ * Called when the runtime reads the device attribute.
+ */
+static ssize_t gcip_usage_stats_component_utilization_show(struct device *dev,
+ struct device_attribute *dev_attr,
+ char *buf)
+{
+ struct gcip_usage_stats_attr *attr =
+ container_of(dev_attr, struct gcip_usage_stats_attr, dev_attr);
+ struct gcip_usage_stats *ustats = attr->ustats;
+ int32_t val;
+ ssize_t written;
+
+ ustats->ops->update_usage_kci(ustats->data);
+
+ mutex_lock(&ustats->usage_stats_lock);
+
+ val = ustats->component_utilization[attr->type];
+ ustats->component_utilization[attr->type] = 0;
+
+ mutex_unlock(&ustats->usage_stats_lock);
+
+ written = scnprintf(buf, PAGE_SIZE, "%d\n", val);
+ if (written < 0)
+ return written;
+
+ return written;
+}
+
+/* Following functions are related to `COUNTER` metrics. */
+
+/*
+ * Updates the counter which represents monotonically increased occurrences such as workloads
+ * and preemptions.
+ *
+ * Called when the FW sent `COUNTER` metrics.
+ */
+static void gcip_usage_stats_update_counter(struct gcip_usage_stats *ustats,
+ struct gcip_usage_stats_counter *new,
+ uint16_t fw_metric_version)
+{
+ uint8_t component_id = 0;
+
+ if (new->type < 0 || new->type >= GCIP_USAGE_STATS_COUNTER_NUM_TYPES) {
+ dev_warn_once(ustats->dev, "FW sent an invalid counter type, type=%d", new->type);
+ return;
+ }
+
+ if (fw_metric_version >= GCIP_USAGE_STATS_V2)
+ component_id = new->component_id;
+
+ if (component_id >= ustats->subcomponents) {
+ dev_warn_once(
+ ustats->dev,
+ "FW sent an invalid component_id for the counter update, component_id=%d",
+ component_id);
+ return;
+ }
+
+ mutex_lock(&ustats->usage_stats_lock);
+ ustats->counter[component_id][new->type] += new->value;
+ mutex_unlock(&ustats->usage_stats_lock);
+}
+
+/*
+ * Prints the value(s) of counter.
+ * If the @attr->subcomponent is `GCIP_USAGE_STATS_ATTR_ALL_SUBCOMPONENTS`, it will print the
+ * counters of all subcomponents in one array with whitespace separation. Otherwise, it will print
+ * the counter of the specific subcomponent.
+ *
+ * Called when the runtime reads the device attribute.
+ */
+static ssize_t gcip_usage_stats_counter_show(struct device *dev, struct device_attribute *dev_attr,
+ char *buf)
+{
+ struct gcip_usage_stats_attr *attr =
+ container_of(dev_attr, struct gcip_usage_stats_attr, dev_attr);
+ struct gcip_usage_stats *ustats = attr->ustats;
+ ssize_t written = 0;
+ int subcomponent = ustats->version >= GCIP_USAGE_STATS_V2 ? attr->subcomponent : 0;
+ int i;
+
+ ustats->ops->update_usage_kci(ustats->data);
+
+ mutex_lock(&ustats->usage_stats_lock);
+
+ if (subcomponent == GCIP_USAGE_STATS_ATTR_ALL_SUBCOMPONENTS) {
+ for (i = 0; i < ustats->subcomponents; i++) {
+ /* Prints a blank only when @i is bigger than 0. */
+ written += scnprintf(buf + written, PAGE_SIZE - written, "%.*s%lld", i, " ",
+ ustats->counter[i][attr->type]);
+ }
+ } else {
+ written += scnprintf(buf + written, PAGE_SIZE - written, "%lld",
+ ustats->counter[subcomponent][attr->type]);
+ }
+
+ mutex_unlock(&ustats->usage_stats_lock);
+ written += scnprintf(buf + written, PAGE_SIZE - written, "\n");
+
+ return written;
+}
+
+/*
+ * Clears the value(s) of counter.
+ * As described in the show function, it will clears the counters of all subcomponents when
+ * @attr->subcomponent is `GCIP_USAGE_STATS_ATTR_ALL_SUBCOMPONENTS`. Otherwise, it will clear the
+ * counter of the specific subcomponent.
+ *
+ * Called when the runtime writes the device attribute.
+ */
+static ssize_t gcip_usage_stats_counter_store(struct device *dev, struct device_attribute *dev_attr,
+ const char *buf, size_t count)
+{
+ struct gcip_usage_stats_attr *attr =
+ container_of(dev_attr, struct gcip_usage_stats_attr, dev_attr);
+ struct gcip_usage_stats *ustats = attr->ustats;
+ int subcomponent = ustats->version >= GCIP_USAGE_STATS_V2 ? attr->subcomponent : 0;
+ int i;
+
+ mutex_lock(&ustats->usage_stats_lock);
+
+ if (subcomponent == GCIP_USAGE_STATS_ATTR_ALL_SUBCOMPONENTS) {
+ for (i = 0; i < ustats->subcomponents; i++)
+ ustats->counter[i][attr->type] = 0;
+ } else {
+ ustats->counter[subcomponent][attr->type] = 0;
+ }
+
+ mutex_unlock(&ustats->usage_stats_lock);
+
+ return count;
+}
+
+/* Following functions are related to `THREAD_STATISTICS` metrics. */
+
+/*
+ * Updates the max stack usage of the @new->thread_id type of thread.
+ *
+ * Called when the FW sent `THREAD_STATISTICS` metrics.
+ */
+static void gcip_usage_stats_update_thread_stats(struct gcip_usage_stats *ustats,
+ struct gcip_usage_stats_thread_stats *new,
+ uint16_t fw_metric_version)
+{
+ if (new->thread_id < 0 || new->thread_id >= GCIP_USAGE_STATS_THREAD_NUM_TYPES) {
+ dev_warn_once(ustats->dev, "FW sent an invalid thread_id, thread_id=%d",
+ new->thread_id);
+ return;
+ }
+
+ mutex_lock(&ustats->usage_stats_lock);
+
+ if (new->max_stack_usage_bytes > ustats->thread_max_stack_usage[new->thread_id])
+ ustats->thread_max_stack_usage[new->thread_id] = new->max_stack_usage_bytes;
+
+ mutex_unlock(&ustats->usage_stats_lock);
+}
+
+/*
+ * Prints the max stack usage of each thread with tab separation.
+ *
+ * Called when the runtime reads the device attribute.
+ */
+static ssize_t gcip_usage_stats_thread_stats_show(struct device *dev,
+ struct device_attribute *dev_attr, char *buf)
+{
+ struct gcip_usage_stats_attr *attr =
+ container_of(dev_attr, struct gcip_usage_stats_attr, dev_attr);
+ struct gcip_usage_stats *ustats = attr->ustats;
+ int i;
+ ssize_t written = 0;
+
+ ustats->ops->update_usage_kci(ustats->data);
+
+ mutex_lock(&ustats->usage_stats_lock);
+
+ for (i = 0; i < GCIP_USAGE_STATS_THREAD_NUM_TYPES; i++) {
+ if (!ustats->thread_max_stack_usage[i])
+ continue;
+ written += scnprintf(buf + written, PAGE_SIZE - written, "%u\t%u\n", i,
+ ustats->thread_max_stack_usage[i]);
+ }
+
+ mutex_unlock(&ustats->usage_stats_lock);
+
+ return written;
+}
+
+/*
+ * Clears the max usage of all threads to 0.
+ *
+ * Called when the runtime writes the device attribute.
+ */
+static ssize_t gcip_usage_stats_thread_stats_store(struct device *dev,
+ struct device_attribute *dev_attr,
+ const char *buf, size_t count)
+{
+ struct gcip_usage_stats_attr *attr =
+ container_of(dev_attr, struct gcip_usage_stats_attr, dev_attr);
+ struct gcip_usage_stats *ustats = attr->ustats;
+
+ mutex_lock(&ustats->usage_stats_lock);
+ memset(ustats->thread_max_stack_usage, 0, sizeof(ustats->thread_max_stack_usage));
+ mutex_unlock(&ustats->usage_stats_lock);
+ return count;
+}
+
+/* Following functions are related to `MAX_WATERMARK` metrics. */
+
+/*
+ * Updates the @new->type type of max watermark of @new->component_id of component.
+ *
+ * Called when the FW sent `MAX_WATERMARK` metrics.
+ */
+static void gcip_usage_stats_update_max_watermark(struct gcip_usage_stats *ustats,
+ struct gcip_usage_stats_max_watermark *new,
+ uint16_t fw_metric_version)
+{
+ uint8_t component_id = 0;
+
+ if (new->type < 0 || new->type >= GCIP_USAGE_STATS_MAX_WATERMARK_NUM_TYPES) {
+ dev_warn_once(ustats->dev, "FW sent an invalid max watermark type, type=%d",
+ new->type);
+ return;
+ }
+
+ if (fw_metric_version >= GCIP_USAGE_STATS_V2)
+ component_id = new->component_id;
+
+ if (component_id >= ustats->subcomponents) {
+ dev_warn_once(
+ ustats->dev,
+ "FW sent an invalid component_id for the max watermark update, component_id=%d",
+ component_id);
+ return;
+ }
+
+ mutex_lock(&ustats->usage_stats_lock);
+
+ if (new->value > ustats->max_watermark[component_id][new->type])
+ ustats->max_watermark[component_id][new->type] = new->value;
+
+ mutex_unlock(&ustats->usage_stats_lock);
+}
+
+/*
+ * Printe the value(s) of max watermark.
+ *
+ * If the @attr->subcomponent is `GCIP_USAGE_STATS_ATTR_ALL_SUBCOMPONENTS`, it will print the
+ * values of all subcomponents in one array with whitespace separation. Otherwise, it will print
+ * the value of the specific subcomponent.
+ *
+ * Called when the runtime reads the device attribute.
+ */
+static ssize_t gcip_usage_stats_max_watermark_show(struct device *dev,
+ struct device_attribute *dev_attr, char *buf)
+{
+ struct gcip_usage_stats_attr *attr =
+ container_of(dev_attr, struct gcip_usage_stats_attr, dev_attr);
+ struct gcip_usage_stats *ustats = attr->ustats;
+ ssize_t written = 0;
+ int subcomponent = ustats->version >= GCIP_USAGE_STATS_V2 ? attr->subcomponent : 0;
+ int i;
+
+ ustats->ops->update_usage_kci(ustats->data);
+
+ mutex_lock(&ustats->usage_stats_lock);
+
+ if (subcomponent == GCIP_USAGE_STATS_ATTR_ALL_SUBCOMPONENTS) {
+ for (i = 0; i < ustats->subcomponents; i++) {
+ /* Prints a blank only when @i is bigger than 0. */
+ written += scnprintf(buf + written, PAGE_SIZE - written, "%.*s%lld", i, " ",
+ ustats->max_watermark[i][attr->type]);
+ }
+ } else {
+ written += scnprintf(buf + written, PAGE_SIZE - written, "%lld",
+ ustats->max_watermark[subcomponent][attr->type]);
+ }
+
+ mutex_unlock(&ustats->usage_stats_lock);
+ written += scnprintf(buf + written, PAGE_SIZE - written, "\n");
+
+ return written;
+}
+
+/*
+ * Clears the value(s) of max watermark.
+ * As described in the show function, it will clears the values of all subcomponents when
+ * @attr->subcomponent is `GCIP_USAGE_STATS_ATTR_ALL_SUBCOMPONENTS`. Otherwise, it will clear the
+ * value of the specific subcomponent.
+ *
+ * Called when the runtime writes the device attribute.
+ */
+static ssize_t gcip_usage_stats_max_watermark_store(struct device *dev,
+ struct device_attribute *dev_attr,
+ const char *buf, size_t count)
+{
+ struct gcip_usage_stats_attr *attr =
+ container_of(dev_attr, struct gcip_usage_stats_attr, dev_attr);
+ struct gcip_usage_stats *ustats = attr->ustats;
+ int subcomponent = ustats->version >= GCIP_USAGE_STATS_V2 ? attr->subcomponent : 0;
+ int i;
+
+ mutex_lock(&ustats->usage_stats_lock);
+
+ if (subcomponent == GCIP_USAGE_STATS_ATTR_ALL_SUBCOMPONENTS) {
+ for (i = 0; i < ustats->subcomponents; i++)
+ ustats->max_watermark[i][attr->type] = 0;
+ } else {
+ ustats->max_watermark[subcomponent][attr->type] = 0;
+ }
+
+ mutex_unlock(&ustats->usage_stats_lock);
+
+ return count;
+}
+
+/* Following functions are related to `DVFS_FREQUENCY_INFO` metrics. */
+
+/*
+ * Updates the DVFS freq table with the ones sent by the FW.
+ *
+ * Until the runtime flushes them by writing the device attribute (i.e., the
+ * `gcip_usage_stats_dvfs_freqs_store` function is called), they will be used instead of the
+ * default freqs of the kernel driver.
+ *
+ * Called when the FW sent `DVFS_FREQUENCY_INFO` metrics.
+ */
+static void gcip_usage_stats_update_dvfs_freq_info(struct gcip_usage_stats *ustats,
+ struct gcip_usage_stats_dvfs_frequency_info *new,
+ uint16_t fw_metric_version)
+{
+ int i;
+
+ mutex_lock(&ustats->dvfs_freqs_lock);
+
+ for (i = 0; i < ustats->dvfs_freqs_num; i++)
+ if (ustats->dvfs_freqs[i] == new->supported_frequency)
+ goto out;
+
+ if (ustats->dvfs_freqs_num >= GCIP_USAGE_STATS_MAX_DVFS_FREQ_NUM) {
+ dev_warn(ustats->dev, "FW sent more DVFS freqs than the kernel driver can handle");
+ goto out;
+ }
+
+ ustats->dvfs_freqs[ustats->dvfs_freqs_num++] = new->supported_frequency;
+out:
+ mutex_unlock(&ustats->dvfs_freqs_lock);
+}
+
+/* Flushes the DVFS freqs from the FW by simply setting the number of freqs in the table to 0. */
+static void gcip_usage_stats_reset_dvfs_freqs(struct gcip_usage_stats *ustats)
+{
+ mutex_lock(&ustats->dvfs_freqs_lock);
+ ustats->dvfs_freqs_num = 0;
+ mutex_unlock(&ustats->dvfs_freqs_lock);
+}
+
+/*
+ * Prints the list of available DVFS freqs in an array with the whitespace separation.
+ *
+ * If the FW has sent `DVFS_FREQUENCY_INFO` metrics, they will be printed. Otherwise, the default
+ * ones from the kernel driver will be printed.
+ *
+ * Called when the runtime reads the device attribute.
+ */
+static ssize_t gcip_usage_stats_dvfs_freqs_show(struct device *dev,
+ struct device_attribute *dev_attr, char *buf)
+{
+ struct gcip_usage_stats_attr *attr =
+ container_of(dev_attr, struct gcip_usage_stats_attr, dev_attr);
+ struct gcip_usage_stats *ustats = attr->ustats;
+ int i, dvfs_freqs_num;
+ ssize_t written = 0;
+
+ ustats->ops->update_usage_kci(ustats->data);
+
+ mutex_lock(&ustats->dvfs_freqs_lock);
+
+ if (!ustats->dvfs_freqs_num) {
+ mutex_unlock(&ustats->dvfs_freqs_lock);
+ dvfs_freqs_num = ustats->ops->get_default_dvfs_freqs_num(ustats->data);
+ for (i = 0; i < dvfs_freqs_num; i++)
+ written += scnprintf(buf + written, PAGE_SIZE - written, "%.*s%d", i, " ",
+ ustats->ops->get_default_dvfs_freq(i, ustats->data));
+ } else {
+ dvfs_freqs_num = ustats->dvfs_freqs_num;
+ for (i = 0; i < dvfs_freqs_num; i++)
+ written += scnprintf(buf + written, PAGE_SIZE - written, "%.*s%u", i, " ",
+ ustats->dvfs_freqs[i]);
+ mutex_unlock(&ustats->dvfs_freqs_lock);
+ }
+
+ return written;
+}
+
+/*
+ * Flushes the DVFS freqs sent by the FW.
+ *
+ * Called when the runtime writes the device attribute.
+ */
+static ssize_t gcip_usage_stats_dvfs_freqs_store(struct device *dev,
+ struct device_attribute *dev_attr, const char *buf,
+ size_t count)
+{
+ struct gcip_usage_stats_attr *attr =
+ container_of(dev_attr, struct gcip_usage_stats_attr, dev_attr);
+
+ gcip_usage_stats_reset_dvfs_freqs(attr->ustats);
+
+ return count;
+}
+
+/* Parses header part of @buf. */
+static void *gcip_usage_stats_parse_header(struct gcip_usage_stats *ustats, void *buf,
+ uint32_t *num_metrics, uint32_t *metric_size,
+ uint16_t *fw_metric_version)
+{
+ struct gcip_usage_stats_header *header = buf;
+ struct gcip_usage_stats_header_v1 *header_v1 = buf;
+
+ if (ustats->version <= GCIP_USAGE_STATS_V1) {
+ *num_metrics = header_v1->num_metrics;
+ *metric_size = header_v1->metric_size;
+ *fw_metric_version = GCIP_USAGE_STATS_V1;
+ buf += sizeof(*header_v1);
+ } else {
+ *num_metrics = header->num_metrics;
+ *metric_size = header->metric_size;
+ *fw_metric_version = header->version;
+ buf += sizeof(*header);
+ }
+
+ return buf;
+}
+
+/*
+ * Fills out required information of device attribute such as show and store callbacks. Also,
+ * checks the validity of it.
+ */
+static int gcip_usage_stats_fill_attr(struct gcip_usage_stats *ustats,
+ struct gcip_usage_stats_attr *attr, const show_t show,
+ const store_t store)
+{
+ struct device_attribute *dev_attr = &attr->dev_attr;
+
+ /*
+ * CORE_USAGE metric doesn't support showing stats for all subcomponents in one device
+ * attribute. Because its printing format is complicated.
+ */
+ if (attr->metric == GCIP_USAGE_STATS_METRIC_TYPE_CORE_USAGE &&
+ attr->subcomponent == GCIP_USAGE_STATS_ATTR_ALL_SUBCOMPONENTS)
+ return -EINVAL;
+
+ /*
+ * For metrics which store stats per subcomponent, we have to check whether the caller set
+ * a proper subcomponent index.
+ */
+ if (attr->subcomponent != GCIP_USAGE_STATS_ATTR_ALL_SUBCOMPONENTS &&
+ (attr->subcomponent < 0 || attr->subcomponent >= ustats->subcomponents))
+ return -EINVAL;
+
+ switch (attr->metric) {
+ case GCIP_USAGE_STATS_METRIC_TYPE_COMPONENT_UTILIZATION:
+ if (attr->type >= GCIP_USAGE_STATS_COMPONENT_UTILIZATION_NUM_TYPES)
+ return -EINVAL;
+ break;
+ case GCIP_USAGE_STATS_METRIC_TYPE_COUNTER:
+ if (attr->type >= GCIP_USAGE_STATS_COUNTER_NUM_TYPES)
+ return -EINVAL;
+ break;
+ case GCIP_USAGE_STATS_METRIC_TYPE_MAX_WATERMARK:
+ if (attr->type >= GCIP_USAGE_STATS_MAX_WATERMARK_NUM_TYPES)
+ return -EINVAL;
+ break;
+ default:
+ break;
+ }
+
+ /*
+ * If the user defines its own show/store callbacks (@attr->show or @attr->store), use the
+ * functions which simply redirect to the user-defined callbacks.
+ */
+ if (attr->mode == GCIP_USAGE_STATS_MODE_RW || attr->mode == GCIP_USAGE_STATS_MODE_RO)
+ dev_attr->show = attr->show ? gcip_usage_stats_user_defined_show : show;
+ if (attr->mode == GCIP_USAGE_STATS_MODE_RW || attr->mode == GCIP_USAGE_STATS_MODE_WO)
+ dev_attr->store = attr->store ? gcip_usage_stats_user_defined_store : store;
+
+ attr->ustats = ustats;
+ dev_attr->attr.mode = attr->mode;
+ dev_attr->attr.name = attr->name;
+
+ return 0;
+}
+
+/*
+ * Allocates @ustats->attrs, a pointer array of `struct attribute`. Each pointers will indicate the
+ * `struct attribute` instances of the @args->attrs[]->dev_attr.attr.
+ * It will fill out show and store callbacks of each device attribute and the allocated
+ * @ustats->attr will be set to the @ustats->group.attr to register them.
+ */
+static int gcip_usage_stats_alloc_attrs(struct gcip_usage_stats *ustats,
+ const struct gcip_usage_stats_args *args)
+{
+ int i, ret;
+ struct gcip_usage_stats_attr *attr;
+
+ ustats->attrs =
+ devm_kcalloc(ustats->dev, args->num_attrs + 1, sizeof(*ustats->attrs), GFP_KERNEL);
+ if (!ustats->attrs)
+ return -ENOMEM;
+
+ /* TODO: fill @ustats->attrs according to the metrics. */
+ for (i = 0; i < args->num_attrs; i++) {
+ attr = args->attrs[i];
+
+ switch (attr->metric) {
+ case GCIP_USAGE_STATS_METRIC_TYPE_CORE_USAGE:
+ ret = gcip_usage_stats_fill_attr(ustats, attr,
+ gcip_usage_stats_core_usage_show,
+ gcip_usage_stats_core_usage_store);
+ break;
+ case GCIP_USAGE_STATS_METRIC_TYPE_COMPONENT_UTILIZATION:
+ ret = gcip_usage_stats_fill_attr(
+ ustats, attr, gcip_usage_stats_component_utilization_show,
+ gcip_usage_stats_user_defined_store);
+ break;
+ case GCIP_USAGE_STATS_METRIC_TYPE_COUNTER:
+ ret = gcip_usage_stats_fill_attr(ustats, attr,
+ gcip_usage_stats_counter_show,
+ gcip_usage_stats_counter_store);
+ break;
+ case GCIP_USAGE_STATS_METRIC_TYPE_THREAD_STATS:
+ ret = gcip_usage_stats_fill_attr(ustats, attr,
+ gcip_usage_stats_thread_stats_show,
+ gcip_usage_stats_thread_stats_store);
+ break;
+ case GCIP_USAGE_STATS_METRIC_TYPE_MAX_WATERMARK:
+ ret = gcip_usage_stats_fill_attr(ustats, attr,
+ gcip_usage_stats_max_watermark_show,
+ gcip_usage_stats_max_watermark_store);
+ break;
+ case GCIP_USAGE_STATS_METRIC_TYPE_DVFS_FREQUENCY_INFO:
+ ret = gcip_usage_stats_fill_attr(ustats, attr,
+ gcip_usage_stats_dvfs_freqs_show,
+ gcip_usage_stats_dvfs_freqs_store);
+ break;
+ default:
+ dev_warn(
+ ustats->dev,
+ "Invalid usage stats metric, use user defined callbacks (metric=%d)",
+ attr->metric);
+ ret = gcip_usage_stats_fill_attr(ustats, attr,
+ gcip_usage_stats_user_defined_show,
+ gcip_usage_stats_user_defined_store);
+ break;
+ }
+
+ if (ret) {
+ devm_kfree(ustats->dev, ustats->attrs);
+ return ret;
+ }
+
+ ustats->attrs[i] = &attr->dev_attr.attr;
+ }
+
+ ustats->attrs[args->num_attrs] = NULL;
+ ustats->group.attrs = ustats->attrs;
+
+ return 0;
+}
+
+/* Releases @ustats->attrs allocated by the `gcip_usage_stats_alloc_attrs` function. */
+static void gcip_usage_stats_free_attrs(struct gcip_usage_stats *ustats)
+{
+ devm_kfree(ustats->dev, ustats->attrs);
+}
+
+/* Allocates arrays which store the statistics per subcomponent. */
+static int gcip_usage_stats_alloc_stats(struct gcip_usage_stats *ustats)
+{
+ int i;
+
+ ustats->core_usage_htable = devm_kcalloc(ustats->dev, ustats->subcomponents,
+ sizeof(*ustats->core_usage_htable), GFP_KERNEL);
+ if (!ustats->core_usage_htable)
+ return -ENOMEM;
+
+ ustats->counter = devm_kcalloc(ustats->dev, ustats->subcomponents, sizeof(*ustats->counter),
+ GFP_KERNEL);
+ if (!ustats->counter)
+ goto err_free_core_usage_htable;
+
+ ustats->max_watermark = devm_kcalloc(ustats->dev, ustats->subcomponents,
+ sizeof(*ustats->max_watermark), GFP_KERNEL);
+ if (!ustats->max_watermark)
+ goto err_free_counter;
+
+ for (i = 0; i < ustats->subcomponents; i++)
+ hash_init(ustats->core_usage_htable[i]);
+
+ return 0;
+
+err_free_counter:
+ devm_kfree(ustats->dev, ustats->counter);
+err_free_core_usage_htable:
+ devm_kfree(ustats->dev, ustats->core_usage_htable);
+ return -ENOMEM;
+}
+
+/* Releases arrays which are allocated by the `gcip_usage_stats_alloc_stats` function. */
+static void gcip_usage_stats_free_stats(struct gcip_usage_stats *ustats)
+{
+ devm_kfree(ustats->dev, ustats->max_watermark);
+ devm_kfree(ustats->dev, ustats->counter);
+ devm_kfree(ustats->dev, ustats->core_usage_htable);
+}
+
+/* Sets operators to the @ustats from @args. */
+static int gcip_usage_stats_set_ops(struct gcip_usage_stats *ustats,
+ const struct gcip_usage_stats_args *args)
+{
+ if (!args->ops->update_usage_kci || !args->ops->get_default_dvfs_freqs_num ||
+ !args->ops->get_default_dvfs_freq)
+ return -EINVAL;
+
+ ustats->ops = args->ops;
+
+ return 0;
+}
+
+int gcip_usage_stats_init(struct gcip_usage_stats *ustats, const struct gcip_usage_stats_args *args)
+{
+ int ret;
+
+ if (args->version < GCIP_USAGE_STATS_V1 || args->version > GCIP_USAGE_STATS_V2)
+ return -EINVAL;
+
+ if (!args->dev)
+ return -EINVAL;
+
+ if (args->subcomponents < 1)
+ return -EINVAL;
+
+ ustats->version = args->version;
+ ustats->subcomponents = args->subcomponents;
+ ustats->dev = args->dev;
+ ustats->data = args->data;
+ mutex_init(&ustats->usage_stats_lock);
+ mutex_init(&ustats->dvfs_freqs_lock);
+ ustats->dvfs_freqs_num = 0;
+
+ ret = gcip_usage_stats_set_ops(ustats, args);
+ if (ret)
+ return ret;
+
+ ret = gcip_usage_stats_alloc_stats(ustats);
+ if (ret)
+ return ret;
+
+ ret = gcip_usage_stats_alloc_attrs(ustats, args);
+ if (ret)
+ goto err_free_stats;
+
+ ret = device_add_group(ustats->dev, &ustats->group);
+ if (ret)
+ goto err_free_attrs;
+
+ return 0;
+
+err_free_attrs:
+ gcip_usage_stats_free_attrs(ustats);
+err_free_stats:
+ gcip_usage_stats_free_stats(ustats);
+ return ret;
+}
+
+void gcip_usage_stats_exit(struct gcip_usage_stats *ustats)
+{
+ device_remove_group(ustats->dev, &ustats->group);
+ gcip_usage_stats_reset_dvfs_freqs(ustats);
+ gcip_usage_stats_free_core_usage_all_entries(ustats);
+ gcip_usage_stats_free_stats(ustats);
+ gcip_usage_stats_free_attrs(ustats);
+}
+
+void gcip_usage_stats_process_buffer(struct gcip_usage_stats *ustats, void *buf)
+{
+ struct gcip_usage_stats_metric *metric;
+ uint32_t num_metrics;
+ uint32_t metric_size;
+ /*
+ * Stores the version of metrics that the firmware is using.
+ * If the version of the firmware and the kernel driver are mismatching, we have to parse
+ * @buf according to the lower version.
+ */
+ uint16_t fw_metric_version;
+ int i;
+
+ metric = gcip_usage_stats_parse_header(ustats, buf, &num_metrics, &metric_size,
+ &fw_metric_version);
+
+ /* Firmware sent metrics which cannot be parsed. */
+ if (fw_metric_version == GCIP_USAGE_STATS_V1 &&
+ metric_size != GCIP_USAGE_STATS_METRIC_SIZE_V1) {
+ dev_err_once(ustats->dev,
+ "FW sent V1 metrics with invalid size (expected=%d, actual=%u)",
+ GCIP_USAGE_STATS_METRIC_SIZE_V1, metric_size);
+ return;
+ }
+
+ /* The metric version of the firmware is higher than the kernel driver. */
+ if (fw_metric_version >= GCIP_USAGE_STATS_VERSION_UPPER_BOUND ||
+ metric_size > sizeof(struct gcip_usage_stats_metric))
+ dev_warn_once(
+ ustats->dev,
+ "FW metrics are later version with unknown fields (expected=%zu, actual=%u, fw_metric_version=%u)",
+ sizeof(struct gcip_usage_stats_metric), metric_size, fw_metric_version);
+
+ for (i = 0; i < num_metrics; i++) {
+ switch (metric->type) {
+ case GCIP_USAGE_STATS_METRIC_TYPE_CORE_USAGE:
+ gcip_usage_stats_update_core_usage(ustats, &metric->core_usage,
+ fw_metric_version);
+ break;
+ case GCIP_USAGE_STATS_METRIC_TYPE_COMPONENT_UTILIZATION:
+ gcip_usage_stats_update_component_utilization(
+ ustats, &metric->component_utilization, fw_metric_version);
+ break;
+ case GCIP_USAGE_STATS_METRIC_TYPE_COUNTER:
+ gcip_usage_stats_update_counter(ustats, &metric->counter,
+ fw_metric_version);
+ break;
+ case GCIP_USAGE_STATS_METRIC_TYPE_THREAD_STATS:
+ gcip_usage_stats_update_thread_stats(ustats, &metric->thread_stats,
+ fw_metric_version);
+ break;
+ case GCIP_USAGE_STATS_METRIC_TYPE_MAX_WATERMARK:
+ gcip_usage_stats_update_max_watermark(ustats, &metric->max_watermark,
+ fw_metric_version);
+ break;
+ case GCIP_USAGE_STATS_METRIC_TYPE_DVFS_FREQUENCY_INFO:
+ gcip_usage_stats_update_dvfs_freq_info(ustats, &metric->dvfs_frequency_info,
+ fw_metric_version);
+ break;
+ default:
+ dev_warn(ustats->dev,
+ "Invalid usage stats metric, skip parsing it (type=%d)",
+ metric->type);
+ break;
+ }
+
+ metric = (struct gcip_usage_stats_metric *)((void *)metric + metric_size);
+ }
+}