@@ -111,6 +111,15 @@ config DRM_I915_KMS
the driver to bind to PCI devices, which precludes loading things
like intelfb.
+config DRM_I915_TRACE
+ bool "i915 trace"
+ depends on DRM_I915
+ select GENERIC_TRACER
+ help
+ Choose this option if you want to enable event tracing in the
+ i915 driver. This is used to identify performance problems
+ within the driver and applications. If unsure, say N.
+
endchoice
config DRM_MGA
@@ -28,7 +28,8 @@ i915-y := i915_drv.o i915_dma.o i915_irq.o i915_mem.o \
dvo_tfp410.o \
dvo_sil164.o
-i915-$(CONFIG_ACPI) += i915_opregion.o
-i915-$(CONFIG_COMPAT) += i915_ioc32.o
+i915-$(CONFIG_ACPI) += i915_opregion.o
+i915-$(CONFIG_COMPAT) += i915_ioc32.o
+i915-$(CONFIG_DRM_I915_TRACE) += i915_trace.o
-obj-$(CONFIG_DRM_I915) += i915.o
+obj-$(CONFIG_DRM_I915) += i915.o
@@ -32,6 +32,7 @@
#include "intel_drv.h"
#include "i915_drm.h"
#include "i915_drv.h"
+#include "i915_trace.h"
#define I915_DRV "i915_drv"
@@ -1261,6 +1262,8 @@ int i915_driver_load(struct drm_device *dev, unsigned long flags)
if (!IS_IGDNG(dev))
intel_opregion_init(dev, 0);
+ i915_trace_init(dev);
+
return 0;
out_workqueue_free:
@@ -1278,6 +1281,8 @@ int i915_driver_unload(struct drm_device *dev)
{
struct drm_i915_private *dev_priv = dev->dev_private;
+ i915_trace_cleanup(dev);
+
destroy_workqueue(dev_priv->wq);
io_mapping_free(dev_priv->mm.gtt_mapping);
@@ -149,6 +149,8 @@ struct drm_i915_error_state {
struct timeval time;
};
+struct i915_trace;
+
typedef struct drm_i915_private {
struct drm_device *dev;
@@ -439,6 +441,8 @@ typedef struct drm_i915_private {
struct drm_i915_gem_phys_object *phys_objs[I915_MAX_PHYS_OBJECT];
} mm;
struct sdvo_device_mapping sdvo_mappings[2];
+
+ struct i915_trace *trace;
} drm_i915_private_t;
/** driver private structure attached to each drm_gem_object */
@@ -734,6 +738,15 @@ extern int i915_restore_state(struct drm_device *dev);
extern int i915_save_state(struct drm_device *dev);
extern int i915_restore_state(struct drm_device *dev);
+#if CONFIG_DRM_I915_TRACE
+/* i915_trace.c */
+extern int i915_trace_init(struct drm_device *dev);
+extern void i915_trace_cleanup(struct drm_device *dev);
+#else
+static inline int i915_trace_init(struct drm_device *dev) { return 0 }
+static inline void i915_trace_cleanup(struct drm_device *dev) { }
+#endif
+
#ifdef CONFIG_ACPI
/* i915_opregion.c */
extern int intel_opregion_init(struct drm_device *dev, int resume);
new file mode 100644
@@ -0,0 +1,559 @@
+/*
+ * Copyright © 2009 Chris Wilson
+ *
+ * Tracing infrastructure for i915 performance monitoring
+ *
+ * See intel-gpu-tools/trace
+ */
+
+#include <linux/ctype.h>
+#include <linux/debugfs.h>
+#include <linux/ring_buffer.h>
+
+#include "drm/drmP.h"
+#include "drm/i915_drm.h"
+#include "i915_drv.h"
+
+#define CREATE_TRACE_POINTS
+#include "i915_trace.h"
+
+#define RING_BUFFER_SIZE (16*4096)
+
+struct i915_trace {
+ struct kref kref;
+ struct drm_device *dev;
+ struct dentry *dentry;
+ struct ring_buffer *ring_buffer;
+ atomic_t event_count;
+
+ unsigned long flags;
+ wait_queue_head_t wait;
+ struct delayed_work work;
+};
+
+enum {
+ TRACE_ACTIVE_FLAG,
+ TRACE_PROBED_FLAG,
+ TRACE_IRQ_FLAG
+};
+
+static void i915_trace_kref_release(struct kref *kref);
+
+static void
+i915_trace_header(struct drm_device *dev)
+{
+ drm_i915_private_t *dev_priv = dev->dev_private;
+ struct i915_trace *trace = dev_priv->trace;
+ struct i915_trace_event event;
+
+ event.time = ktime_to_ns(ktime_get());
+ event.minor = dev->primary->index;
+ event.seqno = I915_TRACE_MAGIC;
+ event.id = I915_TRACE_HEADER;
+ event.arg1 = I915_TRACE_VERSION;
+ event.arg2 = sizeof(event);
+
+ ring_buffer_write(trace->ring_buffer, sizeof(event), &event);
+}
+
+/* objects */
+static void
+i915_trace_object(struct drm_device *dev,
+ int event_id,
+ struct drm_gem_object *obj,
+ int arg1,
+ int arg2)
+{
+ drm_i915_private_t *dev_priv = dev->dev_private;
+ struct i915_trace *trace = dev_priv->trace;
+ struct i915_trace_event event;
+
+ if (!test_bit(TRACE_ACTIVE_FLAG, &trace->flags))
+ return;
+
+ event.time = ktime_to_ns(ktime_get());
+ event.minor = dev->primary->index;
+ event.obj = (u64) (uintptr_t) obj;
+ event.id = event_id;
+ event.arg1 = arg1;
+ event.arg2 = arg2;
+
+ if (ring_buffer_write(trace->ring_buffer, sizeof(event), &event) == 0 &&
+ atomic_add_negative(-1, &trace->event_count))
+ wake_up_interruptible(&trace->wait);
+ queue_delayed_work(dev_priv->wq, &trace->work, HZ);
+}
+
+static void
+i915_trace_gem_object_create(struct drm_gem_object *obj)
+{
+ i915_trace_object(obj->dev,
+ I915_TRACE_OBJECT_CREATE, obj, obj->size, 0);
+}
+
+static void
+i915_trace_gem_object_bind(struct drm_gem_object *obj, u32 gtt_offset)
+{
+ i915_trace_object(obj->dev,
+ I915_TRACE_OBJECT_BIND, obj, gtt_offset, 0);
+}
+
+static void
+i915_trace_gem_object_clflush(struct drm_gem_object *obj)
+{
+ i915_trace_object(obj->dev,
+ I915_TRACE_OBJECT_CLFLUSH, obj, 0, 0);
+}
+
+static void
+i915_trace_gem_object_change_domain(struct drm_gem_object *obj,
+ uint32_t old_read_domains,
+ uint32_t old_write_domain)
+{
+ /* Would prefer to filter these out at source */
+ if (old_read_domains == obj->read_domains &&
+ old_write_domain == obj->write_domain)
+ return;
+
+ i915_trace_object(obj->dev,
+ I915_TRACE_OBJECT_CHANGE_DOMAIN, obj,
+ obj->read_domains | (old_read_domains << 16),
+ obj->write_domain | (old_write_domain << 16));
+}
+
+static void
+i915_trace_gem_object_get_fence(struct drm_gem_object *obj,
+ int fence,
+ int tiling_mode)
+{
+ i915_trace_object(obj->dev,
+ I915_TRACE_OBJECT_GET_FENCE, obj,
+ fence, tiling_mode);
+}
+
+static void
+i915_trace_gem_object_unbind(struct drm_gem_object *obj)
+{
+ i915_trace_object(obj->dev,
+ I915_TRACE_OBJECT_UNBIND, obj, 0, 0);
+}
+
+static void
+i915_trace_gem_object_destroy(struct drm_gem_object *obj)
+{
+ i915_trace_object(obj->dev,
+ I915_TRACE_OBJECT_DESTROY, obj, 0, 0);
+}
+
+/* requests */
+static void
+i915_trace_request(struct drm_device *dev,
+ int event_id, u32 seqno,
+ u32 arg1, u32 arg2)
+{
+ drm_i915_private_t *dev_priv = dev->dev_private;
+ struct i915_trace *trace = dev_priv->trace;
+ struct i915_trace_event event;
+
+ if (!test_bit(TRACE_ACTIVE_FLAG, &trace->flags))
+ return;
+
+ event.time = ktime_to_ns(ktime_get());
+ event.minor = dev->primary->index;
+ event.seqno = seqno;
+ event.id = event_id;
+ event.arg1 = arg1;
+ event.arg2 = arg2;
+
+ if (ring_buffer_write(trace->ring_buffer, sizeof(event), &event) == 0 &&
+ atomic_add_negative(-1, &trace->event_count))
+ wake_up_interruptible(&trace->wait);
+ queue_delayed_work(dev_priv->wq, &trace->work, HZ);
+}
+
+static void
+i915_trace_gem_request_submit(struct drm_device *dev, u32 seqno)
+{
+ i915_trace_request(dev, I915_TRACE_REQUEST_SUBMIT, seqno, 0, 0);
+}
+
+static void
+i915_trace_gem_request_flush(struct drm_device *dev, u32 seqno,
+ u32 flush_domains, u32 invalidate_domains)
+{
+ i915_trace_request(dev, I915_TRACE_REQUEST_FLUSH, seqno,
+ flush_domains, invalidate_domains);
+}
+
+static void
+i915_trace_gem_request_complete(struct drm_device *dev, u32 seqno)
+{
+ i915_trace_request(dev, I915_TRACE_REQUEST_COMPLETE, seqno, 0, 0);
+}
+
+static void
+i915_trace_gem_request_retire(struct drm_device *dev, u32 seqno)
+{
+ i915_trace_request(dev, I915_TRACE_REQUEST_RETIRE, seqno, 0, 0);
+}
+
+static void
+i915_trace_gem_request_wait_begin(struct drm_device *dev, u32 seqno)
+{
+ i915_trace_request(dev, I915_TRACE_REQUEST_WAIT_BEGIN, seqno, 0, 0);
+
+}
+
+static void
+i915_trace_gem_request_wait_end(struct drm_device *dev, u32 seqno)
+{
+ i915_trace_request(dev, I915_TRACE_REQUEST_WAIT_END, seqno, 0, 0);
+}
+
+/* debugfs interface */
+static DEFINE_SPINLOCK(probes_lock);
+static int probes_refcnt;
+
+static int
+i915_probes_register(void)
+{
+#define R(x) ret |= register_trace_i915_##x(i915_trace_##x)
+ int ret = 0;
+
+ spin_lock(&probes_lock);
+ if (probes_refcnt++ == 0) {
+ R(gem_object_create);
+ R(gem_object_bind);
+ R(gem_object_clflush);
+ R(gem_object_change_domain);
+ R(gem_object_get_fence);
+ R(gem_object_unbind);
+ R(gem_object_destroy);
+
+ R(gem_request_submit);
+ R(gem_request_flush);
+ R(gem_request_complete);
+ R(gem_request_retire);
+ R(gem_request_wait_begin);
+ R(gem_request_wait_end);
+ }
+ spin_unlock(&probes_lock);
+
+ return ret;
+#undef R
+}
+
+static void
+i915_probes_unregister(void)
+{
+#define U(x) unregister_trace_i915_##x(i915_trace_##x)
+ spin_lock(&probes_lock);
+ if (--probes_refcnt == 0) {
+ U(gem_object_create);
+ U(gem_object_bind);
+ U(gem_object_clflush);
+ U(gem_object_change_domain);
+ U(gem_object_get_fence);
+ U(gem_object_unbind);
+ U(gem_object_destroy);
+
+ U(gem_request_submit);
+ U(gem_request_flush);
+ U(gem_request_complete);
+ U(gem_request_retire);
+ U(gem_request_wait_begin);
+ U(gem_request_wait_end);
+
+ tracepoint_synchronize_unregister();
+ }
+ spin_unlock(&probes_lock);
+#undef U
+}
+
+static int
+i915_trace_open(struct inode *inode, struct file *filp)
+{
+ struct i915_trace *trace = inode->i_private;
+ int err;
+
+ kref_get (&trace->kref);
+ filp->private_data = trace;
+
+ if (test_and_set_bit_lock(TRACE_ACTIVE_FLAG, &trace->flags))
+ return -EBUSY;
+
+ if (!test_and_set_bit(TRACE_PROBED_FLAG, &trace->flags)) {
+ err = i915_probes_register();
+ if (err)
+ goto fail;
+ }
+
+ if (!test_and_set_bit(TRACE_IRQ_FLAG, &trace->flags))
+ i915_user_irq_get(trace->dev);
+
+ ring_buffer_reset(trace->ring_buffer);
+ atomic_set(&trace->event_count,
+ PAGE_SIZE / sizeof(struct i915_trace_event));
+ i915_trace_header(trace->dev);
+
+ return 0;
+
+fail:
+ __clear_bit_unlock(TRACE_ACTIVE_FLAG, &trace->flags);
+ return err;
+}
+
+static struct ring_buffer_event *
+_ring_buffer_consume_next(struct ring_buffer *ring_buffer, u64 *ts_out)
+{
+ int cpu, next_cpu = -1;
+ struct ring_buffer_event *event;
+ u64 next_ts = (u64) - 1, ts;
+
+ for_each_possible_cpu(cpu) {
+ event = ring_buffer_peek(ring_buffer, cpu, &ts);
+ if (event == NULL)
+ continue;
+
+ if (ts < next_ts) {
+ next_cpu = cpu;
+ next_ts = ts;
+ }
+ }
+
+ if (next_cpu < 0)
+ return NULL;
+
+ return ring_buffer_consume(ring_buffer, next_cpu, ts_out);
+}
+
+static ssize_t
+i915_trace_read(struct file *filp, char __user *ubuf,
+ size_t max, loff_t *ppos)
+{
+ struct i915_trace *trace = filp->private_data;
+ u64 ts;
+ size_t copied;
+
+ /* ignore partial reads */
+ if (*ppos || max < sizeof(struct i915_trace_event))
+ return -EINVAL;
+ if (trace->dev == NULL)
+ return 0;
+
+ wait_event_interruptible(trace->wait,
+ !ring_buffer_empty(trace->ring_buffer));
+
+ if (signal_pending(current))
+ return -EINTR;
+
+ copied = 0;
+ do {
+ struct ring_buffer_event *rb_event;
+
+ rb_event = _ring_buffer_consume_next(trace->ring_buffer, &ts);
+ if (rb_event == NULL)
+ break;
+
+ if (copy_to_user(ubuf + copied,
+ ring_buffer_event_data(rb_event),
+ sizeof(struct i915_trace_event)))
+ return -EFAULT;
+
+ copied += sizeof(struct i915_trace_event);
+ } while (copied + sizeof(struct i915_trace_event) <= max);
+ atomic_add(copied / sizeof(struct i915_trace_event),
+ &trace->event_count);
+
+ return copied;
+}
+
+static ssize_t
+i915_trace_write(struct file *filp, const char __user *ubuf,
+ size_t cnt, loff_t *ppos)
+{
+ size_t read = 0;
+ int i, set = 1;
+ ssize_t ret;
+ char buf[128];
+ char *event;
+ char ch;
+
+ if (!cnt || cnt < 0)
+ return 0;
+
+ ret = get_user(ch, ubuf++);
+ if (ret)
+ return ret;
+ read++;
+ cnt--;
+
+ /* skip white space */
+ while (cnt && isspace(ch)) {
+ ret = get_user(ch, ubuf++);
+ if (ret)
+ return ret;
+
+ read++;
+ cnt--;
+ }
+ if (cnt == 0) {
+ filp->f_pos += read;
+ return read;
+ }
+
+ i = 0;
+ while (cnt && !isspace(ch)) {
+ if (!i && ch == '!')
+ set = 0;
+ else
+ buf[i++] = ch;
+
+ ret = get_user(ch, ubuf++);
+ if (ret)
+ return ret;
+
+ read++;
+ cnt--;
+
+ if (i == sizeof (buf) - 1)
+ break;
+ }
+ buf[i] = 0;
+
+ event = buf;
+ if (i == 0 || (i == 1 && buf[0] == '*'))
+ event = NULL;
+
+ ret = trace_set_clr_event(TRACE_SYSTEM_STRING, event, set);
+ if (ret)
+ return ret;
+
+ filp->f_pos += read;
+ return read;
+}
+
+static unsigned int
+i915_trace_poll(struct file *filp, struct poll_table_struct *wait)
+{
+ struct i915_trace *trace = filp->private_data;
+ unsigned int mask = POLLOUT | POLLWRNORM;
+
+ if (atomic_read(&trace->event_count) <= 0)
+ return mask | POLLIN | POLLRDNORM;
+
+ if (trace->dev == NULL)
+ return POLLHUP;
+
+ poll_wait(filp, &trace->wait, wait);
+
+ if (atomic_read(&trace->event_count) <= 0)
+ mask |= POLLIN | POLLRDNORM;
+
+ return mask;
+}
+
+static int
+i915_trace_release(struct inode *inode, struct file *filp)
+{
+ struct i915_trace *trace = filp->private_data;
+
+ if (test_and_clear_bit(TRACE_IRQ_FLAG, &trace->flags))
+ i915_user_irq_put(trace->dev);
+
+ __clear_bit_unlock(TRACE_ACTIVE_FLAG, &trace->flags);
+ kref_put(&trace->kref, i915_trace_kref_release);
+
+ return 0;
+}
+
+static const struct file_operations i915_trace_fops = {
+ .open = i915_trace_open,
+ .read = i915_trace_read,
+ .write = i915_trace_write,
+ .poll = i915_trace_poll,
+ .release = i915_trace_release,
+};
+
+static void
+i915_trace_kref_release(struct kref *kref)
+{
+ struct i915_trace *trace = container_of(kref, struct i915_trace, kref);
+
+ if (test_bit(TRACE_PROBED_FLAG, &trace->flags))
+ i915_probes_unregister();
+
+ ring_buffer_free(trace->ring_buffer);
+ kfree(trace);
+}
+
+static void
+i915_trace_flush(struct work_struct *work)
+{
+ struct i915_trace *trace = container_of(work,
+ struct i915_trace, work.work);
+ wake_up_interruptible(&trace->wait);
+}
+
+int
+i915_trace_init(struct drm_device *dev)
+{
+ drm_i915_private_t *dev_priv = dev->dev_private;
+ struct i915_trace *trace;
+
+ trace = kcalloc(1, sizeof(*trace), GFP_KERNEL);
+ if (trace == NULL)
+ return -ENOMEM;
+
+ kref_init(&trace->kref);
+ trace->dev = dev;
+ trace->flags = 0;
+
+ init_waitqueue_head(&trace->wait);
+ INIT_DELAYED_WORK(&trace->work, i915_trace_flush);
+
+ trace->ring_buffer = ring_buffer_alloc(RING_BUFFER_SIZE, 0);
+ if (trace->ring_buffer == NULL) {
+ kfree(trace);
+ return -ENOMEM;
+ }
+
+ trace->dentry = debugfs_create_file("i915_trace", S_IRUGO | S_IWUGO,
+ dev->primary->debugfs_root,
+ trace,
+ &i915_trace_fops);
+ if (IS_ERR(trace->dentry)) {
+ int err = PTR_ERR(trace->dentry);
+ ring_buffer_free(trace->ring_buffer);
+ kfree(trace);
+ return err;
+ }
+
+ dev_priv->trace = trace;
+
+ return 0;
+}
+
+void
+i915_trace_cleanup(struct drm_device *dev)
+{
+ drm_i915_private_t *dev_priv = dev->dev_private;
+ struct i915_trace *trace = dev_priv->trace;
+
+ if (trace == NULL)
+ return;
+
+ dev_priv->trace = NULL;
+
+ if (test_and_clear_bit(TRACE_IRQ_FLAG, &trace->flags))
+ i915_user_irq_put(trace->dev);
+
+ clear_bit(TRACE_ACTIVE_FLAG, &trace->flags);
+ debugfs_remove(trace->dentry);
+ trace->dev = NULL;
+
+ cancel_delayed_work_sync(&trace->work);
+ wake_up(&trace->wait);
+
+ kref_put(&trace->kref, i915_trace_kref_release);
+}
@@ -667,4 +667,40 @@ struct drm_i915_get_pipe_from_crtc_id {
__u32 pipe;
};
+/* i915 tracing interface */
+
+enum {
+ I915_TRACE_HEADER,
+
+ I915_TRACE_OBJECT_CREATE,
+ I915_TRACE_OBJECT_BIND,
+ I915_TRACE_OBJECT_CLFLUSH,
+ I915_TRACE_OBJECT_CHANGE_DOMAIN,
+ I915_TRACE_OBJECT_GET_FENCE,
+ I915_TRACE_OBJECT_UNBIND,
+ I915_TRACE_OBJECT_DESTROY,
+
+ I915_TRACE_REQUEST_SUBMIT,
+ I915_TRACE_REQUEST_FLUSH,
+ I915_TRACE_REQUEST_COMPLETE,
+ I915_TRACE_REQUEST_RETIRE,
+ I915_TRACE_REQUEST_WAIT_BEGIN,
+ I915_TRACE_REQUEST_WAIT_END,
+};
+
+#define I915_TRACE_VERSION 0
+#define I915_TRACE_MAGIC 0xdeadbeef
+
+struct i915_trace_event {
+ __u64 time;
+ __u32 minor;
+ __u32 id;
+ union {
+ __u64 obj;
+ __u32 seqno;
+ };
+ __u32 arg1;
+ __u32 arg2;
+};
+
#endif /* _I915_DRM_H_ */
Record all object and requests independent of ftrace and other system-wide tracers. Signed-off-by: Chris Wilson <chris@chris-wilson.co.uk> --- drivers/gpu/drm/Kconfig | 9 + drivers/gpu/drm/i915/Makefile | 7 +- drivers/gpu/drm/i915/i915_dma.c | 5 + drivers/gpu/drm/i915/i915_drv.h | 13 + drivers/gpu/drm/i915/i915_trace.c | 559 +++++++++++++++++++++++++++++++++++++ include/drm/i915_drm.h | 36 +++ 6 files changed, 626 insertions(+), 3 deletions(-) create mode 100644 drivers/gpu/drm/i915/i915_trace.c