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