[Intel-gfx] [PATCH] RFC drm/i915: Expose a PMU interface for perf queries

Chris Wilson chris at chris-wilson.co.uk
Tue Aug 20 22:17:03 CEST 2013


The first goal is to be able to measure GPU (and invidual ring) busyness
without having to poll registers from userspace. (Which not only incurs
holding the forcewake lock indefinitely, perturbing the system, but also
runs the risk of hanging the machine.) As an alternative we can use the
perf event counter interface to sample the ring registers periodically
and send those results to userspace.

To be able to do so, we need to export the two symbols from
kernel/events/core.c to register and unregister a PMU device.

Signed-off-by: Chris Wilson <chris at chris-wilson.co.uk>
---
 drivers/gpu/drm/i915/Makefile    |   1 +
 drivers/gpu/drm/i915/i915_dma.c  |   4 +
 drivers/gpu/drm/i915/i915_drv.h  |   7 +
 drivers/gpu/drm/i915/i915_perf.c | 294 +++++++++++++++++++++++++++++++++++++++
 kernel/events/core.c             |   2 +
 5 files changed, 308 insertions(+)
 create mode 100644 drivers/gpu/drm/i915/i915_perf.c

diff --git a/drivers/gpu/drm/i915/Makefile b/drivers/gpu/drm/i915/Makefile
index 2369cfe..955ec40 100644
--- a/drivers/gpu/drm/i915/Makefile
+++ b/drivers/gpu/drm/i915/Makefile
@@ -17,6 +17,7 @@ i915-y := i915_drv.o i915_dma.o i915_irq.o \
 	  i915_gem_stolen.o \
 	  i915_gem_tiling.o \
 	  i915_gem_userptr.o \
+	  i915_perf.o \
 	  i915_sysfs.o \
 	  i915_trace_points.o \
 	  i915_ums.o \
diff --git a/drivers/gpu/drm/i915/i915_dma.c b/drivers/gpu/drm/i915/i915_dma.c
index 7d0b73b..e02ba63 100644
--- a/drivers/gpu/drm/i915/i915_dma.c
+++ b/drivers/gpu/drm/i915/i915_dma.c
@@ -1656,6 +1656,8 @@ int i915_driver_load(struct drm_device *dev, unsigned long flags)
 	if (IS_GEN5(dev))
 		intel_gpu_ips_init(dev_priv);
 
+	i915_perf_register(dev);
+
 	return 0;
 
 out_power_well:
@@ -1695,6 +1697,8 @@ int i915_driver_unload(struct drm_device *dev)
 	struct drm_i915_private *dev_priv = dev->dev_private;
 	int ret;
 
+	i915_perf_unregister(dev);
+
 	intel_gpu_ips_teardown();
 
 	if (HAS_POWER_WELL(dev)) {
diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h
index 46105db..428469c 100644
--- a/drivers/gpu/drm/i915/i915_drv.h
+++ b/drivers/gpu/drm/i915/i915_drv.h
@@ -42,6 +42,7 @@
 #include <linux/backlight.h>
 #include <linux/intel-iommu.h>
 #include <linux/kref.h>
+#include <linux/perf_event.h>
 #include <linux/pm_qos.h>
 #include <linux/mmu_notifier.h>
 
@@ -1268,6 +1269,8 @@ typedef struct drm_i915_private {
 		uint16_t cur_latency[5];
 	} wm;
 
+	struct pmu pmu;
+
 	/* Old dri1 support infrastructure, beware the dragons ya fools entering
 	 * here! */
 	struct i915_dri1_state dri1;
@@ -2092,6 +2095,10 @@ void i915_destroy_error_state(struct drm_device *dev);
 void i915_get_extra_instdone(struct drm_device *dev, uint32_t *instdone);
 const char *i915_cache_level_str(int type);
 
+/* i915_perf.c */
+extern void i915_perf_register(struct drm_device *dev);
+extern void i915_perf_unregister(struct drm_device *dev);
+
 /* i915_suspend.c */
 extern int i915_save_state(struct drm_device *dev);
 extern int i915_restore_state(struct drm_device *dev);
diff --git a/drivers/gpu/drm/i915/i915_perf.c b/drivers/gpu/drm/i915/i915_perf.c
new file mode 100644
index 0000000..6a34daf
--- /dev/null
+++ b/drivers/gpu/drm/i915/i915_perf.c
@@ -0,0 +1,294 @@
+#include <linux/perf_event.h>
+
+#include "i915_drv.h"
+#include "intel_ringbuffer.h"
+
+#define I915_PERF_COUNT_RCS_BUSY 0
+#define I915_PERF_COUNT_RCS_WAIT 1
+#define I915_PERF_COUNT_RCS_SEMA 2
+
+#define I915_PERF_COUNT_VCS_BUSY 4
+#define I915_PERF_COUNT_VCS_WAIT 5
+#define I915_PERF_COUNT_VCS_SEMA 6
+
+#define I915_PERF_COUNT_BCS_BUSY 8
+#define I915_PERF_COUNT_BCS_WAIT 9
+#define I915_PERF_COUNT_BCS_SEMA 10
+
+#define I915_PERF_COUNT_VECS_BUSY 12
+#define I915_PERF_COUNT_VECS_WAIT 13
+#define I915_PERF_COUNT_VECS_SEMA 14
+
+struct i915_ring_event {
+	struct hrtimer hrtimer;
+	struct intel_ring_buffer *ring;
+};
+
+static enum hrtimer_restart ring_busy(struct hrtimer *hrtimer)
+{
+	struct perf_event *event =
+		container_of(hrtimer, struct perf_event, hw.hrtimer);
+	struct intel_ring_buffer *ring;
+	u64 period;
+
+	if (event->state != PERF_EVENT_STATE_ACTIVE)
+		return HRTIMER_NORESTART;
+
+	period = max_t(u64, 10000, event->hw.sample_period);
+
+	ring = ((struct i915_ring_event *)&event->hw)->ring;
+	if (!list_empty(&ring->request_list)) {
+		struct drm_i915_private *dev_priv = ring->dev->dev_private;
+
+		if (INTEL_INFO(ring->dev)->gen >= 6)
+			gen6_gt_force_wake_get(dev_priv);
+
+		if (I915_READ_HEAD(ring) != I915_READ_TAIL(ring))
+			local64_add(period, &event->count);
+
+		if (INTEL_INFO(ring->dev)->gen >= 6)
+			gen6_gt_force_wake_put(dev_priv);
+	}
+
+	hrtimer_forward_now(hrtimer, ns_to_ktime(period));
+
+	return HRTIMER_RESTART;
+}
+
+static enum hrtimer_restart ring_wait(struct hrtimer *hrtimer)
+{
+	struct perf_event *event =
+		container_of(hrtimer, struct perf_event, hw.hrtimer);
+	struct intel_ring_buffer *ring;
+	u64 period;
+
+	if (event->state != PERF_EVENT_STATE_ACTIVE)
+		return HRTIMER_NORESTART;
+
+	period = max_t(u64, 10000, event->hw.sample_period);
+
+	ring = ((struct i915_ring_event *)&event->hw)->ring;
+	if (!list_empty(&ring->request_list)) {
+		struct drm_i915_private *dev_priv = ring->dev->dev_private;
+		if (I915_READ_CTL(ring) & RING_WAIT)
+			local64_add(period, &event->count);
+	}
+
+	hrtimer_forward_now(hrtimer, ns_to_ktime(period));
+
+	return HRTIMER_RESTART;
+}
+
+static enum hrtimer_restart ring_sema(struct hrtimer *hrtimer)
+{
+	struct perf_event *event =
+		container_of(hrtimer, struct perf_event, hw.hrtimer);
+	struct intel_ring_buffer *ring;
+	u64 period;
+
+	if (event->state != PERF_EVENT_STATE_ACTIVE)
+		return HRTIMER_NORESTART;
+
+	period = max_t(u64, 10000, event->hw.sample_period);
+
+	ring = ((struct i915_ring_event *)&event->hw)->ring;
+	if (!list_empty(&ring->request_list)) {
+		struct drm_i915_private *dev_priv = ring->dev->dev_private;
+		if (I915_READ_CTL(ring) & RING_WAIT_SEMAPHORE)
+			local64_add(period, &event->count);
+	}
+
+	hrtimer_forward_now(hrtimer, ns_to_ktime(period));
+
+	return HRTIMER_RESTART;
+}
+
+static void i915_perf_event_destroy(struct perf_event *event)
+{
+	WARN_ON(event->parent);
+}
+
+static int ring_event_init(struct perf_event *event, int id,
+			   enum hrtimer_restart (*func)(struct hrtimer *hrtimer))
+{
+	struct drm_i915_private *i915 =
+		container_of(event->pmu, typeof(*i915), pmu);
+	struct hw_perf_event *hwc = &event->hw;
+	struct i915_ring_event *ring_event = (struct i915_ring_event *)hwc;
+	struct intel_ring_buffer *ring;
+
+	if (func == ring_wait && i915->info->gen < 3)
+		return -ENODEV;
+
+	if (func == ring_sema && i915->info->gen < 6)
+		return -ENODEV;
+
+	ring = &i915->ring[id];
+	if (ring->obj == NULL)
+		return -ENODEV;
+
+	ring_event->ring = ring;
+
+	hrtimer_init(&hwc->hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+	hwc->hrtimer.function = func;
+
+	hwc->sample_period = NSEC_PER_SEC / event->attr.sample_freq;
+	local64_set(&hwc->period_left, hwc->sample_period);
+	hwc->last_period = hwc->sample_period;
+
+	return 0;
+}
+
+static int i915_perf_event_init(struct perf_event *event)
+{
+	int ret;
+
+	if (event->attr.type != event->pmu->type)
+		return -ENOENT;
+
+	/* XXX ideally only want pid == -1 && cpu == -1 */
+
+	if (has_branch_stack(event))
+		return -EOPNOTSUPP;
+
+	if (event->attr.freq == 0)
+		return -EOPNOTSUPP;
+
+	switch (event->attr.config) {
+	case I915_PERF_COUNT_RCS_BUSY:
+		ret = ring_event_init(event, RCS, ring_busy);
+		break;
+	case I915_PERF_COUNT_VCS_BUSY:
+		ret = ring_event_init(event, VCS, ring_busy);
+		break;
+	case I915_PERF_COUNT_BCS_BUSY:
+		ret = ring_event_init(event, RCS, ring_busy);
+		break;
+	case I915_PERF_COUNT_VECS_BUSY:
+		ret = ring_event_init(event, VECS, ring_busy);
+		break;
+
+	case I915_PERF_COUNT_RCS_WAIT:
+		ret = ring_event_init(event, RCS, ring_wait);
+		break;
+	case I915_PERF_COUNT_VCS_WAIT:
+		ret = ring_event_init(event, VCS, ring_wait);
+		break;
+	case I915_PERF_COUNT_BCS_WAIT:
+		ret = ring_event_init(event, RCS, ring_wait);
+		break;
+	case I915_PERF_COUNT_VECS_WAIT:
+		ret = ring_event_init(event, VECS, ring_wait);
+		break;
+
+	case I915_PERF_COUNT_RCS_SEMA:
+		ret = ring_event_init(event, RCS, ring_sema);
+		break;
+	case I915_PERF_COUNT_VCS_SEMA:
+		ret = ring_event_init(event, VCS, ring_sema);
+		break;
+	case I915_PERF_COUNT_BCS_SEMA:
+		ret = ring_event_init(event, RCS, ring_sema);
+		break;
+	case I915_PERF_COUNT_VECS_SEMA:
+		ret = ring_event_init(event, VECS, ring_sema);
+		break;
+
+	default:
+		ret = -ENOENT;
+		break;
+	}
+	if (ret)
+		return ret;
+
+	if (!event->parent) {
+		event->destroy = i915_perf_event_destroy;
+	}
+
+	return 0;
+}
+
+static int i915_perf_event_add(struct perf_event *event, int flags)
+{
+	struct hw_perf_event *hwc = &event->hw;
+
+	hwc->last_period = hwc->sample_period;
+	hwc->state = !(flags & PERF_EF_START);
+
+	return 0;
+}
+
+static void i915_perf_event_del(struct perf_event *event, int flags)
+{
+}
+
+static void i915_perf_event_start(struct perf_event *event, int flags)
+{
+	struct hw_perf_event *hwc = &event->hw;
+	s64 period;
+
+	event->hw.state = 0;
+
+	period = local64_read(&hwc->period_left);
+	if (period) {
+		if (period < 0)
+			period = 10000;
+
+		local64_set(&hwc->period_left, 0);
+	} else {
+		period = max_t(u64, 10000, hwc->sample_period);
+	}
+	__hrtimer_start_range_ns(&hwc->hrtimer,
+				 ns_to_ktime(period), 0,
+				 HRTIMER_MODE_REL_PINNED, 0);
+}
+
+static void i915_perf_event_stop(struct perf_event *event, int flags)
+{
+	struct hw_perf_event *hwc = &event->hw;
+	ktime_t remaining;
+
+	event->hw.state = PERF_HES_STOPPED;
+
+	remaining = hrtimer_get_remaining(&hwc->hrtimer);
+	local64_set(&hwc->period_left, ktime_to_ns(remaining));
+
+	hrtimer_cancel(&hwc->hrtimer);
+}
+
+static void i915_perf_event_read(struct perf_event *event)
+{
+}
+
+static int i915_perf_event_event_idx(struct perf_event *event)
+{
+	return 0;
+}
+
+void i915_perf_register(struct drm_device *dev)
+{
+	struct drm_i915_private *i915 = to_i915(dev);
+
+	i915->pmu.task_ctx_nr	= perf_sw_context;
+	i915->pmu.event_init	= i915_perf_event_init;
+	i915->pmu.add		= i915_perf_event_add;
+	i915->pmu.del		= i915_perf_event_del;
+	i915->pmu.start		= i915_perf_event_start;
+	i915->pmu.stop		= i915_perf_event_stop;
+	i915->pmu.read		= i915_perf_event_read;
+	i915->pmu.event_idx	= i915_perf_event_event_idx;
+
+	if (perf_pmu_register(&i915->pmu, "i915", -1))
+		i915->pmu.event_init = NULL;
+}
+
+void i915_perf_unregister(struct drm_device *dev)
+{
+	struct drm_i915_private *i915 = to_i915(dev);
+
+	if (i915->pmu.event_init == NULL)
+		return;
+
+	perf_pmu_unregister(&i915->pmu);
+	i915->pmu.event_init = NULL;
+}
diff --git a/kernel/events/core.c b/kernel/events/core.c
index f86599e..cac360e 100644
--- a/kernel/events/core.c
+++ b/kernel/events/core.c
@@ -6384,6 +6384,7 @@ free_pdc:
 	free_percpu(pmu->pmu_disable_count);
 	goto unlock;
 }
+EXPORT_SYMBOL_GPL(perf_pmu_register);
 
 void perf_pmu_unregister(struct pmu *pmu)
 {
@@ -6405,6 +6406,7 @@ void perf_pmu_unregister(struct pmu *pmu)
 	put_device(pmu->dev);
 	free_pmu_context(pmu);
 }
+EXPORT_SYMBOL_GPL(perf_pmu_unregister);
 
 struct pmu *perf_init_event(struct perf_event *event)
 {
-- 
1.8.4.rc3




More information about the Intel-gfx mailing list