Adds base i915 perf infrastructure for Gen performance metrics. This adds a DRM_IOCTL_I915_PERF_OPEN ioctl that takes an array of uint64 properties to configure a stream of metrics and returns a new fd usable with standard VFS system calls including read() to read typed and sized records; ioctl() to enable or disable capture and poll() to wait for data. A stream is opened something like: uint64_t properties[] = { /* Single context sampling */ DRM_I915_PERF_PROP_CTX_HANDLE, ctx_handle, /* Include OA reports in samples */ DRM_I915_PERF_PROP_SAMPLE_OA, true, /* OA unit configuration */ DRM_I915_PERF_PROP_OA_METRICS_SET, metrics_set_id, DRM_I915_PERF_PROP_OA_FORMAT, report_format, DRM_I915_PERF_PROP_OA_EXPONENT, period_exponent, }; struct drm_i915_perf_open_param parm = { .flags = I915_PERF_FLAG_FD_CLOEXEC | I915_PERF_FLAG_FD_NONBLOCK | I915_PERF_FLAG_DISABLED, .properties_ptr = (uint64_t)properties, .num_properties = sizeof(properties) / 16, }; int fd = drmIoctl(drm_fd, DRM_IOCTL_I915_PERF_OPEN, ¶m); Records read all start with a common { type, size } header with DRM_I915_PERF_RECORD_SAMPLE being of most interest. Sample records contain an extensible number of fields and it's the DRM_I915_PERF_PROP_SAMPLE_xyz properties given when opening that determine what's included in every sample. No specific streams are supported yet so any attempt to open a stream will return an error. v2: use i915_gem_context_get() - Chris Wilson v3: update read() interface to avoid passing state struct - Chris Wilson fix some rebase fallout, with i915-perf init/deinit v4: s/DRM_IORW/DRM_IOW/ - Emil Velikov Signed-off-by: Robert Bragg <robert@sixbynine.org> Reviewed-by: Matthew Auld <matthew.auld@intel.com> Reviewed-by: Sourab Gupta <sourab.gupta@intel.com> Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch> Link: http://patchwork.freedesktop.org/patch/msgid/20161107194957.3385-2-robert@sixbynine.orgtirimbino
parent
bc1d53c647
commit
eec688e142
@ -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; |
||||
} |
Loading…
Reference in new issue