root/drivers/iommu/intel/perf.c
// SPDX-License-Identifier: GPL-2.0
/*
 * perf.c - performance monitor
 *
 * Copyright (C) 2021 Intel Corporation
 *
 * Author: Lu Baolu <baolu.lu@linux.intel.com>
 *         Fenghua Yu <fenghua.yu@intel.com>
 */

#include <linux/spinlock.h>

#include "iommu.h"
#include "perf.h"

static DEFINE_SPINLOCK(latency_lock);

bool dmar_latency_enabled(struct intel_iommu *iommu, enum latency_type type)
{
        struct latency_statistic *lstat = iommu->perf_statistic;

        return lstat && lstat[type].enabled;
}

int dmar_latency_enable(struct intel_iommu *iommu, enum latency_type type)
{
        struct latency_statistic *lstat;
        unsigned long flags;
        int ret = -EBUSY;

        if (dmar_latency_enabled(iommu, type))
                return 0;

        spin_lock_irqsave(&latency_lock, flags);
        if (!iommu->perf_statistic) {
                iommu->perf_statistic = kzalloc_objs(*lstat, DMAR_LATENCY_NUM,
                                                     GFP_ATOMIC);
                if (!iommu->perf_statistic) {
                        ret = -ENOMEM;
                        goto unlock_out;
                }
        }

        lstat = iommu->perf_statistic;

        if (!lstat[type].enabled) {
                lstat[type].enabled = true;
                lstat[type].counter[COUNTS_MIN] = UINT_MAX;
                ret = 0;
        }
unlock_out:
        spin_unlock_irqrestore(&latency_lock, flags);

        return ret;
}

void dmar_latency_disable(struct intel_iommu *iommu, enum latency_type type)
{
        struct latency_statistic *lstat = iommu->perf_statistic;
        unsigned long flags;

        if (!dmar_latency_enabled(iommu, type))
                return;

        spin_lock_irqsave(&latency_lock, flags);
        memset(&lstat[type], 0, sizeof(*lstat) * DMAR_LATENCY_NUM);
        spin_unlock_irqrestore(&latency_lock, flags);
}

void dmar_latency_update(struct intel_iommu *iommu, enum latency_type type, u64 latency)
{
        struct latency_statistic *lstat = iommu->perf_statistic;
        unsigned long flags;
        u64 min, max;

        if (!dmar_latency_enabled(iommu, type))
                return;

        spin_lock_irqsave(&latency_lock, flags);
        if (latency < 100)
                lstat[type].counter[COUNTS_10e2]++;
        else if (latency < 1000)
                lstat[type].counter[COUNTS_10e3]++;
        else if (latency < 10000)
                lstat[type].counter[COUNTS_10e4]++;
        else if (latency < 100000)
                lstat[type].counter[COUNTS_10e5]++;
        else if (latency < 1000000)
                lstat[type].counter[COUNTS_10e6]++;
        else if (latency < 10000000)
                lstat[type].counter[COUNTS_10e7]++;
        else
                lstat[type].counter[COUNTS_10e8_plus]++;

        min = lstat[type].counter[COUNTS_MIN];
        max = lstat[type].counter[COUNTS_MAX];
        lstat[type].counter[COUNTS_MIN] = min_t(u64, min, latency);
        lstat[type].counter[COUNTS_MAX] = max_t(u64, max, latency);
        lstat[type].counter[COUNTS_SUM] += latency;
        lstat[type].samples++;
        spin_unlock_irqrestore(&latency_lock, flags);
}

static char *latency_counter_names[] = {
        "                  <0.1us",
        "   0.1us-1us", "    1us-10us", "  10us-100us",
        "   100us-1ms", "    1ms-10ms", "      >=10ms",
        "     min(us)", "     max(us)", " average(us)"
};

static char *latency_type_names[] = {
        "   inv_iotlb", "  inv_devtlb", "     inv_iec",
        "     svm_prq"
};

void dmar_latency_snapshot(struct intel_iommu *iommu, char *str, size_t size)
{
        struct latency_statistic *lstat = iommu->perf_statistic;
        unsigned long flags;
        int bytes = 0, i, j;

        memset(str, 0, size);

        for (i = 0; i < COUNTS_NUM; i++)
                bytes += scnprintf(str + bytes, size - bytes,
                                  "%s", latency_counter_names[i]);

        spin_lock_irqsave(&latency_lock, flags);
        for (i = 0; i < DMAR_LATENCY_NUM; i++) {
                if (!dmar_latency_enabled(iommu, i))
                        continue;

                bytes += scnprintf(str + bytes, size - bytes,
                                  "\n%s", latency_type_names[i]);

                for (j = 0; j < COUNTS_NUM; j++) {
                        u64 val = lstat[i].counter[j];

                        switch (j) {
                        case COUNTS_MIN:
                                if (val == UINT_MAX)
                                        val = 0;
                                else
                                        val = div_u64(val, 1000);
                                break;
                        case COUNTS_MAX:
                                val = div_u64(val, 1000);
                                break;
                        case COUNTS_SUM:
                                if (lstat[i].samples)
                                        val = div_u64(val, (lstat[i].samples * 1000));
                                else
                                        val = 0;
                                break;
                        default:
                                break;
                        }

                        bytes += scnprintf(str + bytes, size - bytes,
                                          "%12lld", val);
                }
        }
        spin_unlock_irqrestore(&latency_lock, flags);
}