/* * 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 */ #include #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; }