blob: 5c5070534409aa22492e1cdea8575bdfd6e16b70 [file] [log] [blame]
// 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, closest_freq_idx, idx = 0;
uint32_t cur_freq, closest_freq = 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;
break;
}
}
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--) {
cur_freq = ustats->ops->get_default_dvfs_freq(i, ustats->data);
if (dvfs_freq == cur_freq)
return i;
if (dvfs_freq > cur_freq && closest_freq < cur_freq) {
closest_freq = cur_freq;
closest_freq_idx = i;
}
}
if (closest_freq)
return closest_freq_idx;
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;
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);
return scnprintf(buf, PAGE_SIZE, "%d\n", val);
}
/* 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, i;
ustats->ops->update_usage_kci(ustats->data);
/*
* We need to decide @subcomponent after calling `update_usage_kci` because IP kernel
* drivers may want to change the version of @ustats to lower one if the firmware doesn't
* support a higher version.
*/
subcomponent = ustats->version >= GCIP_USAGE_STATS_V2 ? attr->subcomponent : 0;
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, i;
ustats->ops->update_usage_kci(ustats->data);
/*
* We need to decide @subcomponent after calling `update_usage_kci` because IP kernel
* drivers may want to change the version of @ustats to lower one if the firmware doesn't
* support a higher version.
*/
subcomponent = ustats->version >= GCIP_USAGE_STATS_V2 ? attr->subcomponent : 0;
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);
}
written += scnprintf(buf + written, PAGE_SIZE - written, "\n");
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);
}
}