diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 39b393d..1a138d6 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -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
diff --git a/drivers/gpu/drm/i915/Makefile b/drivers/gpu/drm/i915/Makefile
index 30d6b99..3141553 100644
--- a/drivers/gpu/drm/i915/Makefile
+++ b/drivers/gpu/drm/i915/Makefile
@@ -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
diff --git a/drivers/gpu/drm/i915/i915_dma.c b/drivers/gpu/drm/i915/i915_dma.c
index 50d1f78..718ee5e 100644
--- a/drivers/gpu/drm/i915/i915_dma.c
+++ b/drivers/gpu/drm/i915/i915_dma.c
@@ -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);
diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h
index 7537f57..2f0ca4f 100644
--- a/drivers/gpu/drm/i915/i915_drv.h
+++ b/drivers/gpu/drm/i915/i915_drv.h
@@ -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);
diff --git a/drivers/gpu/drm/i915/i915_trace.c b/drivers/gpu/drm/i915/i915_trace.c
new file mode 100644
index 0000000..31ef892
--- /dev/null
+++ b/drivers/gpu/drm/i915/i915_trace.c
@@ -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);
+}
diff --git a/include/drm/i915_drm.h b/include/drm/i915_drm.h
index 8e1e925..8c716dc 100644
--- a/include/drm/i915_drm.h
+++ b/include/drm/i915_drm.h
@@ -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_ */
