diff options
| -rw-r--r-- | drivers/gpu/drm/msm/Kconfig | 2 | ||||
| -rw-r--r-- | drivers/gpu/drm/msm/Makefile | 2 | ||||
| -rw-r--r-- | drivers/gpu/drm/msm/adreno/a3xx_gpu.c | 20 | ||||
| -rw-r--r-- | drivers/gpu/drm/msm/hdmi/hdmi_connector.c | 52 | ||||
| -rw-r--r-- | drivers/gpu/drm/msm/mdp/mdp4/mdp4_crtc.c | 2 | ||||
| -rw-r--r-- | drivers/gpu/drm/msm/mdp/mdp5/mdp5_crtc.c | 2 | ||||
| -rw-r--r-- | drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.c | 22 | ||||
| -rw-r--r-- | drivers/gpu/drm/msm/mdp/mdp5/mdp5_plane.c | 5 | ||||
| -rw-r--r-- | drivers/gpu/drm/msm/msm_drv.c | 47 | ||||
| -rw-r--r-- | drivers/gpu/drm/msm/msm_drv.h | 17 | ||||
| -rw-r--r-- | drivers/gpu/drm/msm/msm_gem.h | 1 | ||||
| -rw-r--r-- | drivers/gpu/drm/msm/msm_gem_submit.c | 1 | ||||
| -rw-r--r-- | drivers/gpu/drm/msm/msm_gpu.c | 107 | ||||
| -rw-r--r-- | drivers/gpu/drm/msm/msm_gpu.h | 31 | ||||
| -rw-r--r-- | drivers/gpu/drm/msm/msm_perf.c | 275 | ||||
| -rw-r--r-- | drivers/gpu/drm/msm/msm_rd.c | 337 | 
16 files changed, 884 insertions, 39 deletions
diff --git a/drivers/gpu/drm/msm/Kconfig b/drivers/gpu/drm/msm/Kconfig index b6984971ce0c..f12388967856 100644 --- a/drivers/gpu/drm/msm/Kconfig +++ b/drivers/gpu/drm/msm/Kconfig @@ -3,7 +3,7 @@ config DRM_MSM  	tristate "MSM DRM"  	depends on DRM  	depends on MSM_IOMMU -	depends on ARCH_MSM8960 || (ARM && COMPILE_TEST) +	depends on ARCH_QCOM || (ARM && COMPILE_TEST)  	select DRM_KMS_HELPER  	select SHMEM  	select TMPFS diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile index 5e1e6b0cd8ac..93ca49c8df44 100644 --- a/drivers/gpu/drm/msm/Makefile +++ b/drivers/gpu/drm/msm/Makefile @@ -34,6 +34,8 @@ msm-y := \  	msm_gem_submit.o \  	msm_gpu.o \  	msm_iommu.o \ +	msm_perf.o \ +	msm_rd.o \  	msm_ringbuffer.o  msm-$(CONFIG_DRM_MSM_FBDEV) += msm_fbdev.o diff --git a/drivers/gpu/drm/msm/adreno/a3xx_gpu.c b/drivers/gpu/drm/msm/adreno/a3xx_gpu.c index f20fbde5dc49..942e09d898a8 100644 --- a/drivers/gpu/drm/msm/adreno/a3xx_gpu.c +++ b/drivers/gpu/drm/msm/adreno/a3xx_gpu.c @@ -207,11 +207,11 @@ static int a3xx_hw_init(struct msm_gpu *gpu)  	/* Turn on performance counters: */  	gpu_write(gpu, REG_A3XX_RBBM_PERFCTR_CTL, 0x01); -	/* Set SP perfcounter 7 to count SP_FS_FULL_ALU_INSTRUCTIONS -	 * we will use this to augment our hang detection: -	 */ -	gpu_write(gpu, REG_A3XX_SP_PERFCOUNTER7_SELECT, -			SP_FS_FULL_ALU_INSTRUCTIONS); +	/* Enable the perfcntrs that we use.. */ +	for (i = 0; i < gpu->num_perfcntrs; i++) { +		const struct msm_gpu_perfcntr *perfcntr = &gpu->perfcntrs[i]; +		gpu_write(gpu, perfcntr->select_reg, perfcntr->select_val); +	}  	gpu_write(gpu, REG_A3XX_RBBM_INT_0_MASK, A3XX_INT0_MASK); @@ -465,6 +465,13 @@ static const struct adreno_gpu_funcs funcs = {  	},  }; +static const struct msm_gpu_perfcntr perfcntrs[] = { +	{ REG_A3XX_SP_PERFCOUNTER6_SELECT, REG_A3XX_RBBM_PERFCTR_SP_6_LO, +			SP_ALU_ACTIVE_CYCLES, "ALUACTIVE" }, +	{ REG_A3XX_SP_PERFCOUNTER7_SELECT, REG_A3XX_RBBM_PERFCTR_SP_7_LO, +			SP_FS_FULL_ALU_INSTRUCTIONS, "ALUFULL" }, +}; +  struct msm_gpu *a3xx_gpu_init(struct drm_device *dev)  {  	struct a3xx_gpu *a3xx_gpu = NULL; @@ -504,6 +511,9 @@ struct msm_gpu *a3xx_gpu_init(struct drm_device *dev)  	DBG("fast_rate=%u, slow_rate=%u, bus_freq=%u",  			gpu->fast_rate, gpu->slow_rate, gpu->bus_freq); +	gpu->perfcntrs = perfcntrs; +	gpu->num_perfcntrs = ARRAY_SIZE(perfcntrs); +  	ret = adreno_gpu_init(dev, pdev, adreno_gpu, &funcs, config->rev);  	if (ret)  		goto fail; diff --git a/drivers/gpu/drm/msm/hdmi/hdmi_connector.c b/drivers/gpu/drm/msm/hdmi/hdmi_connector.c index 7dedfdd12075..e56a6196867c 100644 --- a/drivers/gpu/drm/msm/hdmi/hdmi_connector.c +++ b/drivers/gpu/drm/msm/hdmi/hdmi_connector.c @@ -247,36 +247,49 @@ void hdmi_connector_irq(struct drm_connector *connector)  	}  } +static enum drm_connector_status detect_reg(struct hdmi *hdmi) +{ +	uint32_t hpd_int_status = hdmi_read(hdmi, REG_HDMI_HPD_INT_STATUS); +	return (hpd_int_status & HDMI_HPD_INT_STATUS_CABLE_DETECTED) ? +			connector_status_connected : connector_status_disconnected; +} + +static enum drm_connector_status detect_gpio(struct hdmi *hdmi) +{ +	const struct hdmi_platform_config *config = hdmi->config; +	return gpio_get_value(config->hpd_gpio) ? +			connector_status_connected : +			connector_status_disconnected; +} +  static enum drm_connector_status hdmi_connector_detect(  		struct drm_connector *connector, bool force)  {  	struct hdmi_connector *hdmi_connector = to_hdmi_connector(connector);  	struct hdmi *hdmi = hdmi_connector->hdmi; -	const struct hdmi_platform_config *config = hdmi->config; -	uint32_t hpd_int_status; +	enum drm_connector_status stat_gpio, stat_reg;  	int retry = 20; -	hpd_int_status = hdmi_read(hdmi, REG_HDMI_HPD_INT_STATUS); +	do { +		stat_gpio = detect_gpio(hdmi); +		stat_reg  = detect_reg(hdmi); -	/* sense seems to in some cases be momentarily de-asserted, don't -	 * let that trick us into thinking the monitor is gone: -	 */ -	while (retry-- && !(hpd_int_status & HDMI_HPD_INT_STATUS_CABLE_DETECTED)) { -		/* hdmi debounce logic seems to get stuck sometimes, -		 * read directly the gpio to get a second opinion: -		 */ -		if (gpio_get_value(config->hpd_gpio)) { -			DBG("gpio tells us we are connected!"); -			hpd_int_status |= HDMI_HPD_INT_STATUS_CABLE_DETECTED; +		if (stat_gpio == stat_reg)  			break; -		} +  		mdelay(10); -		hpd_int_status = hdmi_read(hdmi, REG_HDMI_HPD_INT_STATUS); -		DBG("status=%08x", hpd_int_status); +	} while (--retry); + +	/* the status we get from reading gpio seems to be more reliable, +	 * so trust that one the most if we didn't manage to get hdmi and +	 * gpio status to agree: +	 */ +	if (stat_gpio != stat_reg) { +		DBG("HDMI_HPD_INT_STATUS tells us: %d", stat_reg); +		DBG("hpd gpio tells us: %d", stat_gpio);  	} -	return (hpd_int_status & HDMI_HPD_INT_STATUS_CABLE_DETECTED) ? -			connector_status_connected : connector_status_disconnected; +	return stat_gpio;  }  static void hdmi_connector_destroy(struct drm_connector *connector) @@ -389,7 +402,8 @@ struct drm_connector *hdmi_connector_init(struct hdmi *hdmi)  			DRM_MODE_CONNECTOR_HDMIA);  	drm_connector_helper_add(connector, &hdmi_connector_helper_funcs); -	connector->polled = DRM_CONNECTOR_POLL_HPD; +	connector->polled = DRM_CONNECTOR_POLL_CONNECT | +			DRM_CONNECTOR_POLL_DISCONNECT;  	connector->interlace_allowed = 1;  	connector->doublescan_allowed = 0; diff --git a/drivers/gpu/drm/msm/mdp/mdp4/mdp4_crtc.c b/drivers/gpu/drm/msm/mdp/mdp4/mdp4_crtc.c index ef9957dbac94..74cebb51e8c2 100644 --- a/drivers/gpu/drm/msm/mdp/mdp4/mdp4_crtc.c +++ b/drivers/gpu/drm/msm/mdp/mdp4/mdp4_crtc.c @@ -217,8 +217,6 @@ static void mdp4_crtc_destroy(struct drm_crtc *crtc)  {  	struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc); -	mdp4_crtc->plane->funcs->destroy(mdp4_crtc->plane); -  	drm_crtc_cleanup(crtc);  	drm_flip_work_cleanup(&mdp4_crtc->unref_fb_work);  	drm_flip_work_cleanup(&mdp4_crtc->unref_cursor_work); diff --git a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_crtc.c b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_crtc.c index 6ea10bdb6e8f..ebe2e60f3ab1 100644 --- a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_crtc.c +++ b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_crtc.c @@ -195,8 +195,6 @@ static void mdp5_crtc_destroy(struct drm_crtc *crtc)  {  	struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc); -	mdp5_crtc->plane->funcs->destroy(mdp5_crtc->plane); -  	drm_crtc_cleanup(crtc);  	drm_flip_work_cleanup(&mdp5_crtc->unref_fb_work); diff --git a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.c b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.c index ee8446c1b5f6..42caf7fcb0b9 100644 --- a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.c +++ b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.c @@ -280,12 +280,22 @@ struct msm_kms *mdp5_kms_init(struct drm_device *dev)  		goto fail;  	} -	ret = get_clk(pdev, &mdp5_kms->axi_clk, "bus_clk") || -			get_clk(pdev, &mdp5_kms->ahb_clk, "iface_clk") || -			get_clk(pdev, &mdp5_kms->src_clk, "core_clk_src") || -			get_clk(pdev, &mdp5_kms->core_clk, "core_clk") || -			get_clk(pdev, &mdp5_kms->lut_clk, "lut_clk") || -			get_clk(pdev, &mdp5_kms->vsync_clk, "vsync_clk"); +	ret = get_clk(pdev, &mdp5_kms->axi_clk, "bus_clk"); +	if (ret) +		goto fail; +	ret = get_clk(pdev, &mdp5_kms->ahb_clk, "iface_clk"); +	if (ret) +		goto fail; +	ret = get_clk(pdev, &mdp5_kms->src_clk, "core_clk_src"); +	if (ret) +		goto fail; +	ret = get_clk(pdev, &mdp5_kms->core_clk, "core_clk"); +	if (ret) +		goto fail; +	ret = get_clk(pdev, &mdp5_kms->lut_clk, "lut_clk"); +	if (ret) +		goto fail; +	ret = get_clk(pdev, &mdp5_kms->vsync_clk, "vsync_clk");  	if (ret)  		goto fail; diff --git a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_plane.c b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_plane.c index 47f7bbb9c15a..f3daec4412ad 100644 --- a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_plane.c +++ b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_plane.c @@ -85,8 +85,11 @@ static int mdp5_plane_disable(struct drm_plane *plane)  static void mdp5_plane_destroy(struct drm_plane *plane)  {  	struct mdp5_plane *mdp5_plane = to_mdp5_plane(plane); +	struct msm_drm_private *priv = plane->dev->dev_private; + +	if (priv->kms) +		mdp5_plane_disable(plane); -	mdp5_plane_disable(plane);  	drm_plane_cleanup(plane);  	kfree(mdp5_plane); diff --git a/drivers/gpu/drm/msm/msm_drv.c b/drivers/gpu/drm/msm/msm_drv.c index 50ec1bed5820..c071aacf2752 100644 --- a/drivers/gpu/drm/msm/msm_drv.c +++ b/drivers/gpu/drm/msm/msm_drv.c @@ -220,7 +220,7 @@ static int msm_load(struct drm_device *dev, unsigned long flags)  		 * is bogus, but non-null if allocation succeeded:  		 */  		p = dma_alloc_attrs(dev->dev, size, -				&priv->vram.paddr, 0, &attrs); +				&priv->vram.paddr, GFP_KERNEL, &attrs);  		if (!p) {  			dev_err(dev->dev, "failed to allocate VRAM\n");  			priv->vram.paddr = 0; @@ -299,6 +299,10 @@ static int msm_load(struct drm_device *dev, unsigned long flags)  	priv->fbdev = msm_fbdev_init(dev);  #endif +	ret = msm_debugfs_late_init(dev); +	if (ret) +		goto fail; +  	drm_kms_helper_poll_init(dev);  	return 0; @@ -531,6 +535,41 @@ static struct drm_info_list msm_debugfs_list[] = {  		{ "fb", show_locked, 0, msm_fb_show },  }; +static int late_init_minor(struct drm_minor *minor) +{ +	int ret; + +	if (!minor) +		return 0; + +	ret = msm_rd_debugfs_init(minor); +	if (ret) { +		dev_err(minor->dev->dev, "could not install rd debugfs\n"); +		return ret; +	} + +	ret = msm_perf_debugfs_init(minor); +	if (ret) { +		dev_err(minor->dev->dev, "could not install perf debugfs\n"); +		return ret; +	} + +	return 0; +} + +int msm_debugfs_late_init(struct drm_device *dev) +{ +	int ret; +	ret = late_init_minor(dev->primary); +	if (ret) +		return ret; +	ret = late_init_minor(dev->render); +	if (ret) +		return ret; +	ret = late_init_minor(dev->control); +	return ret; +} +  static int msm_debugfs_init(struct drm_minor *minor)  {  	struct drm_device *dev = minor->dev; @@ -545,13 +584,17 @@ static int msm_debugfs_init(struct drm_minor *minor)  		return ret;  	} -	return ret; +	return 0;  }  static void msm_debugfs_cleanup(struct drm_minor *minor)  {  	drm_debugfs_remove_files(msm_debugfs_list,  			ARRAY_SIZE(msm_debugfs_list), minor); +	if (!minor->dev->dev_private) +		return; +	msm_rd_debugfs_cleanup(minor); +	msm_perf_debugfs_cleanup(minor);  }  #endif diff --git a/drivers/gpu/drm/msm/msm_drv.h b/drivers/gpu/drm/msm/msm_drv.h index 9d10ee0b5aac..8a2c5fd0893e 100644 --- a/drivers/gpu/drm/msm/msm_drv.h +++ b/drivers/gpu/drm/msm/msm_drv.h @@ -33,7 +33,7 @@  #include <asm/sizes.h> -#if defined(CONFIG_COMPILE_TEST) && !defined(CONFIG_ARCH_MSM) +#if defined(CONFIG_COMPILE_TEST) && !defined(CONFIG_ARCH_QCOM)  /* stubs we need for compile-test: */  static inline struct device *msm_iommu_get_ctx(const char *ctx_name)  { @@ -55,6 +55,9 @@ static inline struct device *msm_iommu_get_ctx(const char *ctx_name)  struct msm_kms;  struct msm_gpu;  struct msm_mmu; +struct msm_rd_state; +struct msm_perf_state; +struct msm_gem_submit;  #define NUM_DOMAINS 2    /* one for KMS, then one per gpu core (?) */ @@ -82,6 +85,9 @@ struct msm_drm_private {  	uint32_t next_fence, completed_fence;  	wait_queue_head_t fence_event; +	struct msm_rd_state *rd; +	struct msm_perf_state *perf; +  	/* list of GEM objects: */  	struct list_head inactive_list; @@ -204,6 +210,15 @@ void __exit hdmi_unregister(void);  void msm_gem_describe(struct drm_gem_object *obj, struct seq_file *m);  void msm_gem_describe_objects(struct list_head *list, struct seq_file *m);  void msm_framebuffer_describe(struct drm_framebuffer *fb, struct seq_file *m); +int msm_debugfs_late_init(struct drm_device *dev); +int msm_rd_debugfs_init(struct drm_minor *minor); +void msm_rd_debugfs_cleanup(struct drm_minor *minor); +void msm_rd_dump_submit(struct msm_gem_submit *submit); +int msm_perf_debugfs_init(struct drm_minor *minor); +void msm_perf_debugfs_cleanup(struct drm_minor *minor); +#else +static inline int msm_debugfs_late_init(struct drm_device *dev) { return 0; } +static inline void msm_rd_dump_submit(struct msm_gem_submit *submit) {}  #endif  void __iomem *msm_ioremap(struct platform_device *pdev, const char *name, diff --git a/drivers/gpu/drm/msm/msm_gem.h b/drivers/gpu/drm/msm/msm_gem.h index 3246bb46c4f2..bfb052688f8e 100644 --- a/drivers/gpu/drm/msm/msm_gem.h +++ b/drivers/gpu/drm/msm/msm_gem.h @@ -90,6 +90,7 @@ struct msm_gem_submit {  		uint32_t type;  		uint32_t size;  /* in dwords */  		uint32_t iova; +		uint32_t idx;   /* cmdstream buffer idx in bos[] */  	} cmd[MAX_CMDS];  	struct {  		uint32_t flags; diff --git a/drivers/gpu/drm/msm/msm_gem_submit.c b/drivers/gpu/drm/msm/msm_gem_submit.c index 1f1f4cffdaed..cd0554f68316 100644 --- a/drivers/gpu/drm/msm/msm_gem_submit.c +++ b/drivers/gpu/drm/msm/msm_gem_submit.c @@ -402,6 +402,7 @@ int msm_ioctl_gem_submit(struct drm_device *dev, void *data,  		submit->cmd[i].type = submit_cmd.type;  		submit->cmd[i].size = submit_cmd.size / 4;  		submit->cmd[i].iova = iova + submit_cmd.submit_offset; +		submit->cmd[i].idx  = submit_cmd.submit_idx;  		if (submit->valid)  			continue; diff --git a/drivers/gpu/drm/msm/msm_gpu.c b/drivers/gpu/drm/msm/msm_gpu.c index 3e667ca1f2b9..c6322197db8c 100644 --- a/drivers/gpu/drm/msm/msm_gpu.c +++ b/drivers/gpu/drm/msm/msm_gpu.c @@ -320,6 +320,101 @@ static void hangcheck_handler(unsigned long data)  }  /* + * Performance Counters: + */ + +/* called under perf_lock */ +static int update_hw_cntrs(struct msm_gpu *gpu, uint32_t ncntrs, uint32_t *cntrs) +{ +	uint32_t current_cntrs[ARRAY_SIZE(gpu->last_cntrs)]; +	int i, n = min(ncntrs, gpu->num_perfcntrs); + +	/* read current values: */ +	for (i = 0; i < gpu->num_perfcntrs; i++) +		current_cntrs[i] = gpu_read(gpu, gpu->perfcntrs[i].sample_reg); + +	/* update cntrs: */ +	for (i = 0; i < n; i++) +		cntrs[i] = current_cntrs[i] - gpu->last_cntrs[i]; + +	/* save current values: */ +	for (i = 0; i < gpu->num_perfcntrs; i++) +		gpu->last_cntrs[i] = current_cntrs[i]; + +	return n; +} + +static void update_sw_cntrs(struct msm_gpu *gpu) +{ +	ktime_t time; +	uint32_t elapsed; +	unsigned long flags; + +	spin_lock_irqsave(&gpu->perf_lock, flags); +	if (!gpu->perfcntr_active) +		goto out; + +	time = ktime_get(); +	elapsed = ktime_to_us(ktime_sub(time, gpu->last_sample.time)); + +	gpu->totaltime += elapsed; +	if (gpu->last_sample.active) +		gpu->activetime += elapsed; + +	gpu->last_sample.active = msm_gpu_active(gpu); +	gpu->last_sample.time = time; + +out: +	spin_unlock_irqrestore(&gpu->perf_lock, flags); +} + +void msm_gpu_perfcntr_start(struct msm_gpu *gpu) +{ +	unsigned long flags; + +	spin_lock_irqsave(&gpu->perf_lock, flags); +	/* we could dynamically enable/disable perfcntr registers too.. */ +	gpu->last_sample.active = msm_gpu_active(gpu); +	gpu->last_sample.time = ktime_get(); +	gpu->activetime = gpu->totaltime = 0; +	gpu->perfcntr_active = true; +	update_hw_cntrs(gpu, 0, NULL); +	spin_unlock_irqrestore(&gpu->perf_lock, flags); +} + +void msm_gpu_perfcntr_stop(struct msm_gpu *gpu) +{ +	gpu->perfcntr_active = false; +} + +/* returns -errno or # of cntrs sampled */ +int msm_gpu_perfcntr_sample(struct msm_gpu *gpu, uint32_t *activetime, +		uint32_t *totaltime, uint32_t ncntrs, uint32_t *cntrs) +{ +	unsigned long flags; +	int ret; + +	spin_lock_irqsave(&gpu->perf_lock, flags); + +	if (!gpu->perfcntr_active) { +		ret = -EINVAL; +		goto out; +	} + +	*activetime = gpu->activetime; +	*totaltime = gpu->totaltime; + +	gpu->activetime = gpu->totaltime = 0; + +	ret = update_hw_cntrs(gpu, ncntrs, cntrs); + +out: +	spin_unlock_irqrestore(&gpu->perf_lock, flags); + +	return ret; +} + +/*   * Cmdstream submission/retirement:   */ @@ -361,6 +456,7 @@ void msm_gpu_retire(struct msm_gpu *gpu)  {  	struct msm_drm_private *priv = gpu->dev->dev_private;  	queue_work(priv->wq, &gpu->retire_work); +	update_sw_cntrs(gpu);  }  /* add bo's to gpu's ring, and kick gpu: */ @@ -377,6 +473,12 @@ int msm_gpu_submit(struct msm_gpu *gpu, struct msm_gem_submit *submit,  	inactive_cancel(gpu); +	msm_rd_dump_submit(submit); + +	gpu->submitted_fence = submit->fence; + +	update_sw_cntrs(gpu); +  	ret = gpu->funcs->submit(gpu, submit, ctx);  	priv->lastctx = ctx; @@ -429,6 +531,9 @@ int msm_gpu_init(struct drm_device *drm, struct platform_device *pdev,  	struct iommu_domain *iommu;  	int i, ret; +	if (WARN_ON(gpu->num_perfcntrs > ARRAY_SIZE(gpu->last_cntrs))) +		gpu->num_perfcntrs = ARRAY_SIZE(gpu->last_cntrs); +  	gpu->dev = drm;  	gpu->funcs = funcs;  	gpu->name = name; @@ -444,6 +549,8 @@ int msm_gpu_init(struct drm_device *drm, struct platform_device *pdev,  	setup_timer(&gpu->hangcheck_timer, hangcheck_handler,  			(unsigned long)gpu); +	spin_lock_init(&gpu->perf_lock); +  	BUG_ON(ARRAY_SIZE(clk_names) != ARRAY_SIZE(gpu->grp_clks));  	/* Map registers: */ diff --git a/drivers/gpu/drm/msm/msm_gpu.h b/drivers/gpu/drm/msm/msm_gpu.h index fad27008922f..9b579b792840 100644 --- a/drivers/gpu/drm/msm/msm_gpu.h +++ b/drivers/gpu/drm/msm/msm_gpu.h @@ -25,6 +25,7 @@  #include "msm_ringbuffer.h"  struct msm_gem_submit; +struct msm_gpu_perfcntr;  /* So far, with hardware that I've seen to date, we can have:   *  + zero, one, or two z180 2d cores @@ -64,6 +65,18 @@ struct msm_gpu {  	struct drm_device *dev;  	const struct msm_gpu_funcs *funcs; +	/* performance counters (hw & sw): */ +	spinlock_t perf_lock; +	bool perfcntr_active; +	struct { +		bool active; +		ktime_t time; +	} last_sample; +	uint32_t totaltime, activetime;    /* sw counters */ +	uint32_t last_cntrs[5];            /* hw counters */ +	const struct msm_gpu_perfcntr *perfcntrs; +	uint32_t num_perfcntrs; +  	struct msm_ringbuffer *rb;  	uint32_t rb_iova; @@ -113,6 +126,19 @@ static inline bool msm_gpu_active(struct msm_gpu *gpu)  	return gpu->submitted_fence > gpu->funcs->last_fence(gpu);  } +/* Perf-Counters: + * The select_reg and select_val are just there for the benefit of the child + * class that actually enables the perf counter..  but msm_gpu base class + * will handle sampling/displaying the counters. + */ + +struct msm_gpu_perfcntr { +	uint32_t select_reg; +	uint32_t sample_reg; +	uint32_t select_val; +	const char *name; +}; +  static inline void gpu_write(struct msm_gpu *gpu, u32 reg, u32 data)  {  	msm_writel(data, gpu->mmio + (reg << 2)); @@ -126,6 +152,11 @@ static inline u32 gpu_read(struct msm_gpu *gpu, u32 reg)  int msm_gpu_pm_suspend(struct msm_gpu *gpu);  int msm_gpu_pm_resume(struct msm_gpu *gpu); +void msm_gpu_perfcntr_start(struct msm_gpu *gpu); +void msm_gpu_perfcntr_stop(struct msm_gpu *gpu); +int msm_gpu_perfcntr_sample(struct msm_gpu *gpu, uint32_t *activetime, +		uint32_t *totaltime, uint32_t ncntrs, uint32_t *cntrs); +  void msm_gpu_retire(struct msm_gpu *gpu);  int msm_gpu_submit(struct msm_gpu *gpu, struct msm_gem_submit *submit,  		struct msm_file_private *ctx); diff --git a/drivers/gpu/drm/msm/msm_perf.c b/drivers/gpu/drm/msm/msm_perf.c new file mode 100644 index 000000000000..830857c47c86 --- /dev/null +++ b/drivers/gpu/drm/msm/msm_perf.c @@ -0,0 +1,275 @@ +/* + * Copyright (C) 2013 Red Hat + * Author: Rob Clark <robdclark@gmail.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program.  If not, see <http://www.gnu.org/licenses/>. + */ + +/* For profiling, userspace can: + * + *   tail -f /sys/kernel/debug/dri/<minor>/gpu + * + * This will enable performance counters/profiling to track the busy time + * and any gpu specific performance counters that are supported. + */ + +#ifdef CONFIG_DEBUG_FS + +#include <linux/debugfs.h> + +#include "msm_drv.h" +#include "msm_gpu.h" + +struct msm_perf_state { +	struct drm_device *dev; + +	bool open; +	int cnt; +	struct mutex read_lock; + +	char buf[256]; +	int buftot, bufpos; + +	unsigned long next_jiffies; + +	struct dentry *ent; +	struct drm_info_node *node; +}; + +#define SAMPLE_TIME (HZ/4) + +/* wait for next sample time: */ +static int wait_sample(struct msm_perf_state *perf) +{ +	unsigned long start_jiffies = jiffies; + +	if (time_after(perf->next_jiffies, start_jiffies)) { +		unsigned long remaining_jiffies = +			perf->next_jiffies - start_jiffies; +		int ret = schedule_timeout_interruptible(remaining_jiffies); +		if (ret > 0) { +			/* interrupted */ +			return -ERESTARTSYS; +		} +	} +	perf->next_jiffies += SAMPLE_TIME; +	return 0; +} + +static int refill_buf(struct msm_perf_state *perf) +{ +	struct msm_drm_private *priv = perf->dev->dev_private; +	struct msm_gpu *gpu = priv->gpu; +	char *ptr = perf->buf; +	int rem = sizeof(perf->buf); +	int i, n; + +	if ((perf->cnt++ % 32) == 0) { +		/* Header line: */ +		n = snprintf(ptr, rem, "%%BUSY"); +		ptr += n; +		rem -= n; + +		for (i = 0; i < gpu->num_perfcntrs; i++) { +			const struct msm_gpu_perfcntr *perfcntr = &gpu->perfcntrs[i]; +			n = snprintf(ptr, rem, "\t%s", perfcntr->name); +			ptr += n; +			rem -= n; +		} +	} else { +		/* Sample line: */ +		uint32_t activetime = 0, totaltime = 0; +		uint32_t cntrs[5]; +		uint32_t val; +		int ret; + +		/* sleep until next sample time: */ +		ret = wait_sample(perf); +		if (ret) +			return ret; + +		ret = msm_gpu_perfcntr_sample(gpu, &activetime, &totaltime, +				ARRAY_SIZE(cntrs), cntrs); +		if (ret < 0) +			return ret; + +		val = totaltime ? 1000 * activetime / totaltime : 0; +		n = snprintf(ptr, rem, "%3d.%d%%", val / 10, val % 10); +		ptr += n; +		rem -= n; + +		for (i = 0; i < ret; i++) { +			/* cycle counters (I think).. convert to MHz.. */ +			val = cntrs[i] / 10000; +			n = snprintf(ptr, rem, "\t%5d.%02d", +					val / 100, val % 100); +			ptr += n; +			rem -= n; +		} +	} + +	n = snprintf(ptr, rem, "\n"); +	ptr += n; +	rem -= n; + +	perf->bufpos = 0; +	perf->buftot = ptr - perf->buf; + +	return 0; +} + +static ssize_t perf_read(struct file *file, char __user *buf, +		size_t sz, loff_t *ppos) +{ +	struct msm_perf_state *perf = file->private_data; +	int n = 0, ret; + +	mutex_lock(&perf->read_lock); + +	if (perf->bufpos >= perf->buftot) { +		ret = refill_buf(perf); +		if (ret) +			goto out; +	} + +	n = min((int)sz, perf->buftot - perf->bufpos); +	ret = copy_to_user(buf, &perf->buf[perf->bufpos], n); +	if (ret) +		goto out; + +	perf->bufpos += n; +	*ppos += n; + +out: +	mutex_unlock(&perf->read_lock); +	if (ret) +		return ret; +	return n; +} + +static int perf_open(struct inode *inode, struct file *file) +{ +	struct msm_perf_state *perf = inode->i_private; +	struct drm_device *dev = perf->dev; +	struct msm_drm_private *priv = dev->dev_private; +	struct msm_gpu *gpu = priv->gpu; +	int ret = 0; + +	mutex_lock(&dev->struct_mutex); + +	if (perf->open || !gpu) { +		ret = -EBUSY; +		goto out; +	} + +	file->private_data = perf; +	perf->open = true; +	perf->cnt = 0; +	perf->buftot = 0; +	perf->bufpos = 0; +	msm_gpu_perfcntr_start(gpu); +	perf->next_jiffies = jiffies + SAMPLE_TIME; + +out: +	mutex_unlock(&dev->struct_mutex); +	return ret; +} + +static int perf_release(struct inode *inode, struct file *file) +{ +	struct msm_perf_state *perf = inode->i_private; +	struct msm_drm_private *priv = perf->dev->dev_private; +	msm_gpu_perfcntr_stop(priv->gpu); +	perf->open = false; +	return 0; +} + + +static const struct file_operations perf_debugfs_fops = { +	.owner = THIS_MODULE, +	.open = perf_open, +	.read = perf_read, +	.llseek = no_llseek, +	.release = perf_release, +}; + +int msm_perf_debugfs_init(struct drm_minor *minor) +{ +	struct msm_drm_private *priv = minor->dev->dev_private; +	struct msm_perf_state *perf; + +	/* only create on first minor: */ +	if (priv->perf) +		return 0; + +	perf = kzalloc(sizeof(*perf), GFP_KERNEL); +	if (!perf) +		return -ENOMEM; + +	perf->dev = minor->dev; + +	mutex_init(&perf->read_lock); +	priv->perf = perf; + +	perf->node = kzalloc(sizeof(*perf->node), GFP_KERNEL); +	if (!perf->node) +		goto fail; + +	perf->ent = debugfs_create_file("perf", S_IFREG | S_IRUGO, +			minor->debugfs_root, perf, &perf_debugfs_fops); +	if (!perf->ent) { +		DRM_ERROR("Cannot create /sys/kernel/debug/dri/%s/perf\n", +				minor->debugfs_root->d_name.name); +		goto fail; +	} + +	perf->node->minor = minor; +	perf->node->dent  = perf->ent; +	perf->node->info_ent = NULL; + +	mutex_lock(&minor->debugfs_lock); +	list_add(&perf->node->list, &minor->debugfs_list); +	mutex_unlock(&minor->debugfs_lock); + +	return 0; + +fail: +	msm_perf_debugfs_cleanup(minor); +	return -1; +} + +void msm_perf_debugfs_cleanup(struct drm_minor *minor) +{ +	struct msm_drm_private *priv = minor->dev->dev_private; +	struct msm_perf_state *perf = priv->perf; + +	if (!perf) +		return; + +	priv->perf = NULL; + +	debugfs_remove(perf->ent); + +	if (perf->node) { +		mutex_lock(&minor->debugfs_lock); +		list_del(&perf->node->list); +		mutex_unlock(&minor->debugfs_lock); +		kfree(perf->node); +	} + +	mutex_destroy(&perf->read_lock); + +	kfree(perf); +} + +#endif diff --git a/drivers/gpu/drm/msm/msm_rd.c b/drivers/gpu/drm/msm/msm_rd.c new file mode 100644 index 000000000000..9a78c48817c6 --- /dev/null +++ b/drivers/gpu/drm/msm/msm_rd.c @@ -0,0 +1,337 @@ +/* + * Copyright (C) 2013 Red Hat + * Author: Rob Clark <robdclark@gmail.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program.  If not, see <http://www.gnu.org/licenses/>. + */ + +/* For debugging crashes, userspace can: + * + *   tail -f /sys/kernel/debug/dri/<minor>/rd > logfile.rd + * + * To log the cmdstream in a format that is understood by freedreno/cffdump + * utility.  By comparing the last successfully completed fence #, to the + * cmdstream for the next fence, you can narrow down which process and submit + * caused the gpu crash/lockup. + * + * This bypasses drm_debugfs_create_files() mainly because we need to use + * our own fops for a bit more control.  In particular, we don't want to + * do anything if userspace doesn't have the debugfs file open. + */ + +#ifdef CONFIG_DEBUG_FS + +#include <linux/kfifo.h> +#include <linux/debugfs.h> +#include <linux/circ_buf.h> +#include <linux/wait.h> + +#include "msm_drv.h" +#include "msm_gpu.h" +#include "msm_gem.h" + +enum rd_sect_type { +	RD_NONE, +	RD_TEST,       /* ascii text */ +	RD_CMD,        /* ascii text */ +	RD_GPUADDR,    /* u32 gpuaddr, u32 size */ +	RD_CONTEXT,    /* raw dump */ +	RD_CMDSTREAM,  /* raw dump */ +	RD_CMDSTREAM_ADDR, /* gpu addr of cmdstream */ +	RD_PARAM,      /* u32 param_type, u32 param_val, u32 bitlen */ +	RD_FLUSH,      /* empty, clear previous params */ +	RD_PROGRAM,    /* shader program, raw dump */ +	RD_VERT_SHADER, +	RD_FRAG_SHADER, +	RD_BUFFER_CONTENTS, +	RD_GPU_ID, +}; + +#define BUF_SZ 512  /* should be power of 2 */ + +/* space used: */ +#define circ_count(circ) \ +	(CIRC_CNT((circ)->head, (circ)->tail, BUF_SZ)) +#define circ_count_to_end(circ) \ +	(CIRC_CNT_TO_END((circ)->head, (circ)->tail, BUF_SZ)) +/* space available: */ +#define circ_space(circ) \ +	(CIRC_SPACE((circ)->head, (circ)->tail, BUF_SZ)) +#define circ_space_to_end(circ) \ +	(CIRC_SPACE_TO_END((circ)->head, (circ)->tail, BUF_SZ)) + +struct msm_rd_state { +	struct drm_device *dev; + +	bool open; + +	struct dentry *ent; +	struct drm_info_node *node; + +	/* current submit to read out: */ +	struct msm_gem_submit *submit; + +	/* fifo access is synchronized on the producer side by +	 * struct_mutex held by submit code (otherwise we could +	 * end up w/ cmds logged in different order than they +	 * were executed).  And read_lock synchronizes the reads +	 */ +	struct mutex read_lock; + +	wait_queue_head_t fifo_event; +	struct circ_buf fifo; + +	char buf[BUF_SZ]; +}; + +static void rd_write(struct msm_rd_state *rd, const void *buf, int sz) +{ +	struct circ_buf *fifo = &rd->fifo; +	const char *ptr = buf; + +	while (sz > 0) { +		char *fptr = &fifo->buf[fifo->head]; +		int n; + +		wait_event(rd->fifo_event, circ_space(&rd->fifo) > 0); + +		n = min(sz, circ_space_to_end(&rd->fifo)); +		memcpy(fptr, ptr, n); + +		fifo->head = (fifo->head + n) & (BUF_SZ - 1); +		sz  -= n; +		ptr += n; + +		wake_up_all(&rd->fifo_event); +	} +} + +static void rd_write_section(struct msm_rd_state *rd, +		enum rd_sect_type type, const void *buf, int sz) +{ +	rd_write(rd, &type, 4); +	rd_write(rd, &sz, 4); +	rd_write(rd, buf, sz); +} + +static ssize_t rd_read(struct file *file, char __user *buf, +		size_t sz, loff_t *ppos) +{ +	struct msm_rd_state *rd = file->private_data; +	struct circ_buf *fifo = &rd->fifo; +	const char *fptr = &fifo->buf[fifo->tail]; +	int n = 0, ret = 0; + +	mutex_lock(&rd->read_lock); + +	ret = wait_event_interruptible(rd->fifo_event, +			circ_count(&rd->fifo) > 0); +	if (ret) +		goto out; + +	n = min_t(int, sz, circ_count_to_end(&rd->fifo)); +	ret = copy_to_user(buf, fptr, n); +	if (ret) +		goto out; + +	fifo->tail = (fifo->tail + n) & (BUF_SZ - 1); +	*ppos += n; + +	wake_up_all(&rd->fifo_event); + +out: +	mutex_unlock(&rd->read_lock); +	if (ret) +		return ret; +	return n; +} + +static int rd_open(struct inode *inode, struct file *file) +{ +	struct msm_rd_state *rd = inode->i_private; +	struct drm_device *dev = rd->dev; +	struct msm_drm_private *priv = dev->dev_private; +	struct msm_gpu *gpu = priv->gpu; +	uint64_t val; +	uint32_t gpu_id; +	int ret = 0; + +	mutex_lock(&dev->struct_mutex); + +	if (rd->open || !gpu) { +		ret = -EBUSY; +		goto out; +	} + +	file->private_data = rd; +	rd->open = true; + +	/* the parsing tools need to know gpu-id to know which +	 * register database to load. +	 */ +	gpu->funcs->get_param(gpu, MSM_PARAM_GPU_ID, &val); +	gpu_id = val; + +	rd_write_section(rd, RD_GPU_ID, &gpu_id, sizeof(gpu_id)); + +out: +	mutex_unlock(&dev->struct_mutex); +	return ret; +} + +static int rd_release(struct inode *inode, struct file *file) +{ +	struct msm_rd_state *rd = inode->i_private; +	rd->open = false; +	return 0; +} + + +static const struct file_operations rd_debugfs_fops = { +	.owner = THIS_MODULE, +	.open = rd_open, +	.read = rd_read, +	.llseek = no_llseek, +	.release = rd_release, +}; + +int msm_rd_debugfs_init(struct drm_minor *minor) +{ +	struct msm_drm_private *priv = minor->dev->dev_private; +	struct msm_rd_state *rd; + +	/* only create on first minor: */ +	if (priv->rd) +		return 0; + +	rd = kzalloc(sizeof(*rd), GFP_KERNEL); +	if (!rd) +		return -ENOMEM; + +	rd->dev = minor->dev; +	rd->fifo.buf = rd->buf; + +	mutex_init(&rd->read_lock); +	priv->rd = rd; + +	init_waitqueue_head(&rd->fifo_event); + +	rd->node = kzalloc(sizeof(*rd->node), GFP_KERNEL); +	if (!rd->node) +		goto fail; + +	rd->ent = debugfs_create_file("rd", S_IFREG | S_IRUGO, +			minor->debugfs_root, rd, &rd_debugfs_fops); +	if (!rd->ent) { +		DRM_ERROR("Cannot create /sys/kernel/debug/dri/%s/rd\n", +				minor->debugfs_root->d_name.name); +		goto fail; +	} + +	rd->node->minor = minor; +	rd->node->dent  = rd->ent; +	rd->node->info_ent = NULL; + +	mutex_lock(&minor->debugfs_lock); +	list_add(&rd->node->list, &minor->debugfs_list); +	mutex_unlock(&minor->debugfs_lock); + +	return 0; + +fail: +	msm_rd_debugfs_cleanup(minor); +	return -1; +} + +void msm_rd_debugfs_cleanup(struct drm_minor *minor) +{ +	struct msm_drm_private *priv = minor->dev->dev_private; +	struct msm_rd_state *rd = priv->rd; + +	if (!rd) +		return; + +	priv->rd = NULL; + +	debugfs_remove(rd->ent); + +	if (rd->node) { +		mutex_lock(&minor->debugfs_lock); +		list_del(&rd->node->list); +		mutex_unlock(&minor->debugfs_lock); +		kfree(rd->node); +	} + +	mutex_destroy(&rd->read_lock); + +	kfree(rd); +} + +/* called under struct_mutex */ +void msm_rd_dump_submit(struct msm_gem_submit *submit) +{ +	struct drm_device *dev = submit->dev; +	struct msm_drm_private *priv = dev->dev_private; +	struct msm_rd_state *rd = priv->rd; +	char msg[128]; +	int i, n; + +	if (!rd->open) +		return; + +	/* writing into fifo is serialized by caller, and +	 * rd->read_lock is used to serialize the reads +	 */ +	WARN_ON(!mutex_is_locked(&dev->struct_mutex)); + +	n = snprintf(msg, sizeof(msg), "%.*s/%d: fence=%u", +			TASK_COMM_LEN, current->comm, task_pid_nr(current), +			submit->fence); + +	rd_write_section(rd, RD_CMD, msg, ALIGN(n, 4)); + +	/* could be nice to have an option (module-param?) to snapshot +	 * all the bo's associated with the submit.  Handy to see vtx +	 * buffers, etc.  For now just the cmdstream bo's is enough. +	 */ + +	for (i = 0; i < submit->nr_cmds; i++) { +		uint32_t idx  = submit->cmd[i].idx; +		uint32_t iova = submit->cmd[i].iova; +		uint32_t szd  = submit->cmd[i].size; /* in dwords */ +		struct msm_gem_object *obj = submit->bos[idx].obj; +		const char *buf = msm_gem_vaddr_locked(&obj->base); + +		buf += iova - submit->bos[idx].iova; + +		rd_write_section(rd, RD_GPUADDR, +				(uint32_t[2]){ iova, szd * 4 }, 8); +		rd_write_section(rd, RD_BUFFER_CONTENTS, +				buf, szd * 4); + +		switch (submit->cmd[i].type) { +		case MSM_SUBMIT_CMD_IB_TARGET_BUF: +			/* ignore IB-targets, we've logged the buffer, the +			 * parser tool will follow the IB based on the logged +			 * buffer/gpuaddr, so nothing more to do. +			 */ +			break; +		case MSM_SUBMIT_CMD_CTX_RESTORE_BUF: +		case MSM_SUBMIT_CMD_BUF: +			rd_write_section(rd, RD_CMDSTREAM_ADDR, +					(uint32_t[2]){ iova, szd }, 8); +			break; +		} +	} +} +#endif  | 
