linux/drivers/gpu/drm/msm/msm_perf.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * Copyright (C) 2013 Red Hat
   4 * Author: Rob Clark <robdclark@gmail.com>
   5 */
   6
   7/* For profiling, userspace can:
   8 *
   9 *   tail -f /sys/kernel/debug/dri/<minor>/gpu
  10 *
  11 * This will enable performance counters/profiling to track the busy time
  12 * and any gpu specific performance counters that are supported.
  13 */
  14
  15#ifdef CONFIG_DEBUG_FS
  16
  17#include <linux/debugfs.h>
  18#include <linux/uaccess.h>
  19
  20#include <drm/drm_file.h>
  21
  22#include "msm_drv.h"
  23#include "msm_gpu.h"
  24
  25struct msm_perf_state {
  26        struct drm_device *dev;
  27
  28        bool open;
  29        int cnt;
  30        struct mutex read_lock;
  31
  32        char buf[256];
  33        int buftot, bufpos;
  34
  35        unsigned long next_jiffies;
  36};
  37
  38#define SAMPLE_TIME (HZ/4)
  39
  40/* wait for next sample time: */
  41static int wait_sample(struct msm_perf_state *perf)
  42{
  43        unsigned long start_jiffies = jiffies;
  44
  45        if (time_after(perf->next_jiffies, start_jiffies)) {
  46                unsigned long remaining_jiffies =
  47                        perf->next_jiffies - start_jiffies;
  48                int ret = schedule_timeout_interruptible(remaining_jiffies);
  49                if (ret > 0) {
  50                        /* interrupted */
  51                        return -ERESTARTSYS;
  52                }
  53        }
  54        perf->next_jiffies += SAMPLE_TIME;
  55        return 0;
  56}
  57
  58static int refill_buf(struct msm_perf_state *perf)
  59{
  60        struct msm_drm_private *priv = perf->dev->dev_private;
  61        struct msm_gpu *gpu = priv->gpu;
  62        char *ptr = perf->buf;
  63        int rem = sizeof(perf->buf);
  64        int i, n;
  65
  66        if ((perf->cnt++ % 32) == 0) {
  67                /* Header line: */
  68                n = snprintf(ptr, rem, "%%BUSY");
  69                ptr += n;
  70                rem -= n;
  71
  72                for (i = 0; i < gpu->num_perfcntrs; i++) {
  73                        const struct msm_gpu_perfcntr *perfcntr = &gpu->perfcntrs[i];
  74                        n = snprintf(ptr, rem, "\t%s", perfcntr->name);
  75                        ptr += n;
  76                        rem -= n;
  77                }
  78        } else {
  79                /* Sample line: */
  80                uint32_t activetime = 0, totaltime = 0;
  81                uint32_t cntrs[5];
  82                uint32_t val;
  83                int ret;
  84
  85                /* sleep until next sample time: */
  86                ret = wait_sample(perf);
  87                if (ret)
  88                        return ret;
  89
  90                ret = msm_gpu_perfcntr_sample(gpu, &activetime, &totaltime,
  91                                ARRAY_SIZE(cntrs), cntrs);
  92                if (ret < 0)
  93                        return ret;
  94
  95                val = totaltime ? 1000 * activetime / totaltime : 0;
  96                n = snprintf(ptr, rem, "%3d.%d%%", val / 10, val % 10);
  97                ptr += n;
  98                rem -= n;
  99
 100                for (i = 0; i < ret; i++) {
 101                        /* cycle counters (I think).. convert to MHz.. */
 102                        val = cntrs[i] / 10000;
 103                        n = snprintf(ptr, rem, "\t%5d.%02d",
 104                                        val / 100, val % 100);
 105                        ptr += n;
 106                        rem -= n;
 107                }
 108        }
 109
 110        n = snprintf(ptr, rem, "\n");
 111        ptr += n;
 112        rem -= n;
 113
 114        perf->bufpos = 0;
 115        perf->buftot = ptr - perf->buf;
 116
 117        return 0;
 118}
 119
 120static ssize_t perf_read(struct file *file, char __user *buf,
 121                size_t sz, loff_t *ppos)
 122{
 123        struct msm_perf_state *perf = file->private_data;
 124        int n = 0, ret = 0;
 125
 126        mutex_lock(&perf->read_lock);
 127
 128        if (perf->bufpos >= perf->buftot) {
 129                ret = refill_buf(perf);
 130                if (ret)
 131                        goto out;
 132        }
 133
 134        n = min((int)sz, perf->buftot - perf->bufpos);
 135        if (copy_to_user(buf, &perf->buf[perf->bufpos], n)) {
 136                ret = -EFAULT;
 137                goto out;
 138        }
 139
 140        perf->bufpos += n;
 141        *ppos += n;
 142
 143out:
 144        mutex_unlock(&perf->read_lock);
 145        if (ret)
 146                return ret;
 147        return n;
 148}
 149
 150static int perf_open(struct inode *inode, struct file *file)
 151{
 152        struct msm_perf_state *perf = inode->i_private;
 153        struct drm_device *dev = perf->dev;
 154        struct msm_drm_private *priv = dev->dev_private;
 155        struct msm_gpu *gpu = priv->gpu;
 156        int ret = 0;
 157
 158        mutex_lock(&dev->struct_mutex);
 159
 160        if (perf->open || !gpu) {
 161                ret = -EBUSY;
 162                goto out;
 163        }
 164
 165        file->private_data = perf;
 166        perf->open = true;
 167        perf->cnt = 0;
 168        perf->buftot = 0;
 169        perf->bufpos = 0;
 170        msm_gpu_perfcntr_start(gpu);
 171        perf->next_jiffies = jiffies + SAMPLE_TIME;
 172
 173out:
 174        mutex_unlock(&dev->struct_mutex);
 175        return ret;
 176}
 177
 178static int perf_release(struct inode *inode, struct file *file)
 179{
 180        struct msm_perf_state *perf = inode->i_private;
 181        struct msm_drm_private *priv = perf->dev->dev_private;
 182        msm_gpu_perfcntr_stop(priv->gpu);
 183        perf->open = false;
 184        return 0;
 185}
 186
 187
 188static const struct file_operations perf_debugfs_fops = {
 189        .owner = THIS_MODULE,
 190        .open = perf_open,
 191        .read = perf_read,
 192        .llseek = no_llseek,
 193        .release = perf_release,
 194};
 195
 196int msm_perf_debugfs_init(struct drm_minor *minor)
 197{
 198        struct msm_drm_private *priv = minor->dev->dev_private;
 199        struct msm_perf_state *perf;
 200
 201        /* only create on first minor: */
 202        if (priv->perf)
 203                return 0;
 204
 205        perf = kzalloc(sizeof(*perf), GFP_KERNEL);
 206        if (!perf)
 207                return -ENOMEM;
 208
 209        perf->dev = minor->dev;
 210
 211        mutex_init(&perf->read_lock);
 212        priv->perf = perf;
 213
 214        debugfs_create_file("perf", S_IFREG | S_IRUGO, minor->debugfs_root,
 215                            perf, &perf_debugfs_fops);
 216        return 0;
 217}
 218
 219void msm_perf_debugfs_cleanup(struct msm_drm_private *priv)
 220{
 221        struct msm_perf_state *perf = priv->perf;
 222
 223        if (!perf)
 224                return;
 225
 226        priv->perf = NULL;
 227
 228        mutex_destroy(&perf->read_lock);
 229
 230        kfree(perf);
 231}
 232
 233#endif
 234