|
@@ -0,0 +1,449 @@
|
|
|
+/*
|
|
|
+ * Copyright © 2015-2016 Intel Corporation
|
|
|
+ *
|
|
|
+ * Permission is hereby granted, free of charge, to any person obtaining a
|
|
|
+ * copy of this software and associated documentation files (the "Software"),
|
|
|
+ * to deal in the Software without restriction, including without limitation
|
|
|
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
|
+ * and/or sell copies of the Software, and to permit persons to whom the
|
|
|
+ * Software is furnished to do so, subject to the following conditions:
|
|
|
+ *
|
|
|
+ * The above copyright notice and this permission notice (including the next
|
|
|
+ * paragraph) shall be included in all copies or substantial portions of the
|
|
|
+ * Software.
|
|
|
+ *
|
|
|
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
|
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
|
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
|
+ * IN THE SOFTWARE.
|
|
|
+ *
|
|
|
+ * Authors:
|
|
|
+ * Robert Bragg <robert@sixbynine.org>
|
|
|
+ */
|
|
|
+
|
|
|
+#include <linux/anon_inodes.h>
|
|
|
+
|
|
|
+#include "i915_drv.h"
|
|
|
+
|
|
|
+struct perf_open_properties {
|
|
|
+ u32 sample_flags;
|
|
|
+
|
|
|
+ u64 single_context:1;
|
|
|
+ u64 ctx_handle;
|
|
|
+};
|
|
|
+
|
|
|
+static ssize_t i915_perf_read_locked(struct i915_perf_stream *stream,
|
|
|
+ struct file *file,
|
|
|
+ char __user *buf,
|
|
|
+ size_t count,
|
|
|
+ loff_t *ppos)
|
|
|
+{
|
|
|
+ /* Note we keep the offset (aka bytes read) separate from any
|
|
|
+ * error status so that the final check for whether we return
|
|
|
+ * the bytes read with a higher precedence than any error (see
|
|
|
+ * comment below) doesn't need to be handled/duplicated in
|
|
|
+ * stream->ops->read() implementations.
|
|
|
+ */
|
|
|
+ size_t offset = 0;
|
|
|
+ int ret = stream->ops->read(stream, buf, count, &offset);
|
|
|
+
|
|
|
+ /* If we've successfully copied any data then reporting that
|
|
|
+ * takes precedence over any internal error status, so the
|
|
|
+ * data isn't lost.
|
|
|
+ *
|
|
|
+ * For example ret will be -ENOSPC whenever there is more
|
|
|
+ * buffered data than can be copied to userspace, but that's
|
|
|
+ * only interesting if we weren't able to copy some data
|
|
|
+ * because it implies the userspace buffer is too small to
|
|
|
+ * receive a single record (and we never split records).
|
|
|
+ *
|
|
|
+ * Another case with ret == -EFAULT is more of a grey area
|
|
|
+ * since it would seem like bad form for userspace to ask us
|
|
|
+ * to overrun its buffer, but the user knows best:
|
|
|
+ *
|
|
|
+ * http://yarchive.net/comp/linux/partial_reads_writes.html
|
|
|
+ */
|
|
|
+ return offset ?: (ret ?: -EAGAIN);
|
|
|
+}
|
|
|
+
|
|
|
+static ssize_t i915_perf_read(struct file *file,
|
|
|
+ char __user *buf,
|
|
|
+ size_t count,
|
|
|
+ loff_t *ppos)
|
|
|
+{
|
|
|
+ struct i915_perf_stream *stream = file->private_data;
|
|
|
+ struct drm_i915_private *dev_priv = stream->dev_priv;
|
|
|
+ ssize_t ret;
|
|
|
+
|
|
|
+ if (!(file->f_flags & O_NONBLOCK)) {
|
|
|
+ /* Allow false positives from stream->ops->wait_unlocked.
|
|
|
+ */
|
|
|
+ do {
|
|
|
+ ret = stream->ops->wait_unlocked(stream);
|
|
|
+ if (ret)
|
|
|
+ return ret;
|
|
|
+
|
|
|
+ mutex_lock(&dev_priv->perf.lock);
|
|
|
+ ret = i915_perf_read_locked(stream, file,
|
|
|
+ buf, count, ppos);
|
|
|
+ mutex_unlock(&dev_priv->perf.lock);
|
|
|
+ } while (ret == -EAGAIN);
|
|
|
+ } else {
|
|
|
+ mutex_lock(&dev_priv->perf.lock);
|
|
|
+ ret = i915_perf_read_locked(stream, file, buf, count, ppos);
|
|
|
+ mutex_unlock(&dev_priv->perf.lock);
|
|
|
+ }
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+static unsigned int i915_perf_poll_locked(struct i915_perf_stream *stream,
|
|
|
+ struct file *file,
|
|
|
+ poll_table *wait)
|
|
|
+{
|
|
|
+ unsigned int streams = 0;
|
|
|
+
|
|
|
+ stream->ops->poll_wait(stream, file, wait);
|
|
|
+
|
|
|
+ if (stream->ops->can_read(stream))
|
|
|
+ streams |= POLLIN;
|
|
|
+
|
|
|
+ return streams;
|
|
|
+}
|
|
|
+
|
|
|
+static unsigned int i915_perf_poll(struct file *file, poll_table *wait)
|
|
|
+{
|
|
|
+ struct i915_perf_stream *stream = file->private_data;
|
|
|
+ struct drm_i915_private *dev_priv = stream->dev_priv;
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ mutex_lock(&dev_priv->perf.lock);
|
|
|
+ ret = i915_perf_poll_locked(stream, file, wait);
|
|
|
+ mutex_unlock(&dev_priv->perf.lock);
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+static void i915_perf_enable_locked(struct i915_perf_stream *stream)
|
|
|
+{
|
|
|
+ if (stream->enabled)
|
|
|
+ return;
|
|
|
+
|
|
|
+ /* Allow stream->ops->enable() to refer to this */
|
|
|
+ stream->enabled = true;
|
|
|
+
|
|
|
+ if (stream->ops->enable)
|
|
|
+ stream->ops->enable(stream);
|
|
|
+}
|
|
|
+
|
|
|
+static void i915_perf_disable_locked(struct i915_perf_stream *stream)
|
|
|
+{
|
|
|
+ if (!stream->enabled)
|
|
|
+ return;
|
|
|
+
|
|
|
+ /* Allow stream->ops->disable() to refer to this */
|
|
|
+ stream->enabled = false;
|
|
|
+
|
|
|
+ if (stream->ops->disable)
|
|
|
+ stream->ops->disable(stream);
|
|
|
+}
|
|
|
+
|
|
|
+static long i915_perf_ioctl_locked(struct i915_perf_stream *stream,
|
|
|
+ unsigned int cmd,
|
|
|
+ unsigned long arg)
|
|
|
+{
|
|
|
+ switch (cmd) {
|
|
|
+ case I915_PERF_IOCTL_ENABLE:
|
|
|
+ i915_perf_enable_locked(stream);
|
|
|
+ return 0;
|
|
|
+ case I915_PERF_IOCTL_DISABLE:
|
|
|
+ i915_perf_disable_locked(stream);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ return -EINVAL;
|
|
|
+}
|
|
|
+
|
|
|
+static long i915_perf_ioctl(struct file *file,
|
|
|
+ unsigned int cmd,
|
|
|
+ unsigned long arg)
|
|
|
+{
|
|
|
+ struct i915_perf_stream *stream = file->private_data;
|
|
|
+ struct drm_i915_private *dev_priv = stream->dev_priv;
|
|
|
+ long ret;
|
|
|
+
|
|
|
+ mutex_lock(&dev_priv->perf.lock);
|
|
|
+ ret = i915_perf_ioctl_locked(stream, cmd, arg);
|
|
|
+ mutex_unlock(&dev_priv->perf.lock);
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+static void i915_perf_destroy_locked(struct i915_perf_stream *stream)
|
|
|
+{
|
|
|
+ struct drm_i915_private *dev_priv = stream->dev_priv;
|
|
|
+
|
|
|
+ if (stream->enabled)
|
|
|
+ i915_perf_disable_locked(stream);
|
|
|
+
|
|
|
+ if (stream->ops->destroy)
|
|
|
+ stream->ops->destroy(stream);
|
|
|
+
|
|
|
+ list_del(&stream->link);
|
|
|
+
|
|
|
+ if (stream->ctx) {
|
|
|
+ mutex_lock(&dev_priv->drm.struct_mutex);
|
|
|
+ i915_gem_context_put(stream->ctx);
|
|
|
+ mutex_unlock(&dev_priv->drm.struct_mutex);
|
|
|
+ }
|
|
|
+
|
|
|
+ kfree(stream);
|
|
|
+}
|
|
|
+
|
|
|
+static int i915_perf_release(struct inode *inode, struct file *file)
|
|
|
+{
|
|
|
+ struct i915_perf_stream *stream = file->private_data;
|
|
|
+ struct drm_i915_private *dev_priv = stream->dev_priv;
|
|
|
+
|
|
|
+ mutex_lock(&dev_priv->perf.lock);
|
|
|
+ i915_perf_destroy_locked(stream);
|
|
|
+ mutex_unlock(&dev_priv->perf.lock);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+static const struct file_operations fops = {
|
|
|
+ .owner = THIS_MODULE,
|
|
|
+ .llseek = no_llseek,
|
|
|
+ .release = i915_perf_release,
|
|
|
+ .poll = i915_perf_poll,
|
|
|
+ .read = i915_perf_read,
|
|
|
+ .unlocked_ioctl = i915_perf_ioctl,
|
|
|
+};
|
|
|
+
|
|
|
+
|
|
|
+static struct i915_gem_context *
|
|
|
+lookup_context(struct drm_i915_private *dev_priv,
|
|
|
+ struct drm_i915_file_private *file_priv,
|
|
|
+ u32 ctx_user_handle)
|
|
|
+{
|
|
|
+ struct i915_gem_context *ctx;
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ ret = i915_mutex_lock_interruptible(&dev_priv->drm);
|
|
|
+ if (ret)
|
|
|
+ return ERR_PTR(ret);
|
|
|
+
|
|
|
+ ctx = i915_gem_context_lookup(file_priv, ctx_user_handle);
|
|
|
+ if (!IS_ERR(ctx))
|
|
|
+ i915_gem_context_get(ctx);
|
|
|
+
|
|
|
+ mutex_unlock(&dev_priv->drm.struct_mutex);
|
|
|
+
|
|
|
+ return ctx;
|
|
|
+}
|
|
|
+
|
|
|
+static int
|
|
|
+i915_perf_open_ioctl_locked(struct drm_i915_private *dev_priv,
|
|
|
+ struct drm_i915_perf_open_param *param,
|
|
|
+ struct perf_open_properties *props,
|
|
|
+ struct drm_file *file)
|
|
|
+{
|
|
|
+ struct i915_gem_context *specific_ctx = NULL;
|
|
|
+ struct i915_perf_stream *stream = NULL;
|
|
|
+ unsigned long f_flags = 0;
|
|
|
+ int stream_fd;
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ if (props->single_context) {
|
|
|
+ u32 ctx_handle = props->ctx_handle;
|
|
|
+ struct drm_i915_file_private *file_priv = file->driver_priv;
|
|
|
+
|
|
|
+ specific_ctx = lookup_context(dev_priv, file_priv, ctx_handle);
|
|
|
+ if (IS_ERR(specific_ctx)) {
|
|
|
+ ret = PTR_ERR(specific_ctx);
|
|
|
+ if (ret != -EINTR)
|
|
|
+ DRM_ERROR("Failed to look up context with ID %u for opening perf stream\n",
|
|
|
+ ctx_handle);
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!specific_ctx && !capable(CAP_SYS_ADMIN)) {
|
|
|
+ DRM_ERROR("Insufficient privileges to open system-wide i915 perf stream\n");
|
|
|
+ ret = -EACCES;
|
|
|
+ goto err_ctx;
|
|
|
+ }
|
|
|
+
|
|
|
+ stream = kzalloc(sizeof(*stream), GFP_KERNEL);
|
|
|
+ if (!stream) {
|
|
|
+ ret = -ENOMEM;
|
|
|
+ goto err_ctx;
|
|
|
+ }
|
|
|
+
|
|
|
+ stream->sample_flags = props->sample_flags;
|
|
|
+ stream->dev_priv = dev_priv;
|
|
|
+ stream->ctx = specific_ctx;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * TODO: support sampling something
|
|
|
+ *
|
|
|
+ * For now this is as far as we can go.
|
|
|
+ */
|
|
|
+ DRM_ERROR("Unsupported i915 perf stream configuration\n");
|
|
|
+ ret = -EINVAL;
|
|
|
+ goto err_alloc;
|
|
|
+
|
|
|
+ list_add(&stream->link, &dev_priv->perf.streams);
|
|
|
+
|
|
|
+ if (param->flags & I915_PERF_FLAG_FD_CLOEXEC)
|
|
|
+ f_flags |= O_CLOEXEC;
|
|
|
+ if (param->flags & I915_PERF_FLAG_FD_NONBLOCK)
|
|
|
+ f_flags |= O_NONBLOCK;
|
|
|
+
|
|
|
+ stream_fd = anon_inode_getfd("[i915_perf]", &fops, stream, f_flags);
|
|
|
+ if (stream_fd < 0) {
|
|
|
+ ret = stream_fd;
|
|
|
+ goto err_open;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!(param->flags & I915_PERF_FLAG_DISABLED))
|
|
|
+ i915_perf_enable_locked(stream);
|
|
|
+
|
|
|
+ return stream_fd;
|
|
|
+
|
|
|
+err_open:
|
|
|
+ list_del(&stream->link);
|
|
|
+ if (stream->ops->destroy)
|
|
|
+ stream->ops->destroy(stream);
|
|
|
+err_alloc:
|
|
|
+ kfree(stream);
|
|
|
+err_ctx:
|
|
|
+ if (specific_ctx) {
|
|
|
+ mutex_lock(&dev_priv->drm.struct_mutex);
|
|
|
+ i915_gem_context_put(specific_ctx);
|
|
|
+ mutex_unlock(&dev_priv->drm.struct_mutex);
|
|
|
+ }
|
|
|
+err:
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+/* Note we copy the properties from userspace outside of the i915 perf
|
|
|
+ * mutex to avoid an awkward lockdep with mmap_sem.
|
|
|
+ *
|
|
|
+ * Note this function only validates properties in isolation it doesn't
|
|
|
+ * validate that the combination of properties makes sense or that all
|
|
|
+ * properties necessary for a particular kind of stream have been set.
|
|
|
+ */
|
|
|
+static int read_properties_unlocked(struct drm_i915_private *dev_priv,
|
|
|
+ u64 __user *uprops,
|
|
|
+ u32 n_props,
|
|
|
+ struct perf_open_properties *props)
|
|
|
+{
|
|
|
+ u64 __user *uprop = uprops;
|
|
|
+ int i;
|
|
|
+
|
|
|
+ memset(props, 0, sizeof(struct perf_open_properties));
|
|
|
+
|
|
|
+ if (!n_props) {
|
|
|
+ DRM_ERROR("No i915 perf properties given");
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Considering that ID = 0 is reserved and assuming that we don't
|
|
|
+ * (currently) expect any configurations to ever specify duplicate
|
|
|
+ * values for a particular property ID then the last _PROP_MAX value is
|
|
|
+ * one greater than the maximum number of properties we expect to get
|
|
|
+ * from userspace.
|
|
|
+ */
|
|
|
+ if (n_props >= DRM_I915_PERF_PROP_MAX) {
|
|
|
+ DRM_ERROR("More i915 perf properties specified than exist");
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ for (i = 0; i < n_props; i++) {
|
|
|
+ u64 id, value;
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ ret = get_user(id, uprop);
|
|
|
+ if (ret)
|
|
|
+ return ret;
|
|
|
+
|
|
|
+ ret = get_user(value, uprop + 1);
|
|
|
+ if (ret)
|
|
|
+ return ret;
|
|
|
+
|
|
|
+ switch ((enum drm_i915_perf_property_id)id) {
|
|
|
+ case DRM_I915_PERF_PROP_CTX_HANDLE:
|
|
|
+ props->single_context = 1;
|
|
|
+ props->ctx_handle = value;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ MISSING_CASE(id);
|
|
|
+ DRM_ERROR("Unknown i915 perf property ID");
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ uprop += 2;
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+int i915_perf_open_ioctl(struct drm_device *dev, void *data,
|
|
|
+ struct drm_file *file)
|
|
|
+{
|
|
|
+ struct drm_i915_private *dev_priv = dev->dev_private;
|
|
|
+ struct drm_i915_perf_open_param *param = data;
|
|
|
+ struct perf_open_properties props;
|
|
|
+ u32 known_open_flags;
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ if (!dev_priv->perf.initialized) {
|
|
|
+ DRM_ERROR("i915 perf interface not available for this system");
|
|
|
+ return -ENOTSUPP;
|
|
|
+ }
|
|
|
+
|
|
|
+ known_open_flags = I915_PERF_FLAG_FD_CLOEXEC |
|
|
|
+ I915_PERF_FLAG_FD_NONBLOCK |
|
|
|
+ I915_PERF_FLAG_DISABLED;
|
|
|
+ if (param->flags & ~known_open_flags) {
|
|
|
+ DRM_ERROR("Unknown drm_i915_perf_open_param flag\n");
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ ret = read_properties_unlocked(dev_priv,
|
|
|
+ u64_to_user_ptr(param->properties_ptr),
|
|
|
+ param->num_properties,
|
|
|
+ &props);
|
|
|
+ if (ret)
|
|
|
+ return ret;
|
|
|
+
|
|
|
+ mutex_lock(&dev_priv->perf.lock);
|
|
|
+ ret = i915_perf_open_ioctl_locked(dev_priv, param, &props, file);
|
|
|
+ mutex_unlock(&dev_priv->perf.lock);
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+void i915_perf_init(struct drm_i915_private *dev_priv)
|
|
|
+{
|
|
|
+ INIT_LIST_HEAD(&dev_priv->perf.streams);
|
|
|
+ mutex_init(&dev_priv->perf.lock);
|
|
|
+
|
|
|
+ dev_priv->perf.initialized = true;
|
|
|
+}
|
|
|
+
|
|
|
+void i915_perf_fini(struct drm_i915_private *dev_priv)
|
|
|
+{
|
|
|
+ if (!dev_priv->perf.initialized)
|
|
|
+ return;
|
|
|
+
|
|
|
+ /* Currently nothing to clean up */
|
|
|
+
|
|
|
+ dev_priv->perf.initialized = false;
|
|
|
+}
|