mbox series

[0/3] drm/msm: Improved devfreq tuning

Message ID 20210722222145.1759900-1-robdclark@gmail.com
Headers show
Series drm/msm: Improved devfreq tuning | expand

Message

Rob Clark July 22, 2021, 10:21 p.m. UTC
From: Rob Clark <robdclark@chromium.org>

This is the outcome of trying to fix some bad gpu freq behavior seen in
some use-cases, in particular mobile games that throttle themselves to
30fps.  With the existing tuning, we'd end up spending most of the time
that we should be running fast at a low freq, and most of the idle time
at a high freq.

First two patches are prep, 3/3 is the interesting bit.  See the patch
description in 3/3 for more details.

Rob Clark (3):
  drm/msm: Split out devfreq handling
  drm/msm: Split out get_freq() helper
  drm/msm: Devfreq tuning

 drivers/gpu/drm/msm/Makefile          |   1 +
 drivers/gpu/drm/msm/adreno/a6xx_gpu.c |   4 +-
 drivers/gpu/drm/msm/msm_gpu.c         | 124 ++--------------
 drivers/gpu/drm/msm/msm_gpu.h         |  27 +++-
 drivers/gpu/drm/msm/msm_gpu_devfreq.c | 203 ++++++++++++++++++++++++++
 5 files changed, 238 insertions(+), 121 deletions(-)
 create mode 100644 drivers/gpu/drm/msm/msm_gpu_devfreq.c

Comments

Akhil P Oommen July 23, 2021, 6:51 a.m. UTC | #1
On 7/23/2021 3:51 AM, Rob Clark wrote:
> From: Rob Clark <robdclark@chromium.org>

> 

> This adds a few things to try and make frequency scaling better match

> the workload:

> 

> 1) Longer polling interval to avoid whip-lashing between too-high and

>     too-low frequencies in certain workloads, like mobile games which

>     throttle themselves to 30fps.

> 

>     Previously our polling interval was short enough to let things

>     ramp down to minimum freq in the "off" frame, but long enough to

>     not react quickly enough when rendering started on the next frame,

>     leading to uneven frame times.  (Ie. rather than a consistent 33ms

>     it would alternate between 16/33/48ms.)

> 

> 2) Awareness of when the GPU is active vs idle.  Since we know when

>     the GPU is active vs idle, we can clamp the frequency down to the

>     minimum while it is idle.  (If it is idle for long enough, then

>     the autosuspend delay will eventually kick in and power down the

>     GPU.)

> 

>     Since devfreq has no knowledge of powered-but-idle, this takes a

>     small bit of trickery to maintain a "fake" frequency while idle.

>     This, combined with the longer polling period allows devfreq to

>     arrive at a reasonable "active" frequency, while still clamping

>     to minimum freq when idle to reduce power draw.

> 

> 3) Boost.  Because simple_ondemand needs to see a certain threshold

>     of busyness to ramp up, we could end up needing multiple polling

>     cycles before it reacts appropriately on interactive workloads

>     (ex. scrolling a web page after reading for some time), on top

>     of the already lengthened polling interval, when we see a idle

>     to active transition after a period of idle time we boost the

>     frequency that we return to.

> 

> Signed-off-by: Rob Clark <robdclark@chromium.org>

> ---

>   drivers/gpu/drm/msm/msm_gpu.c         |  8 +++

>   drivers/gpu/drm/msm/msm_gpu.h         |  9 ++++

>   drivers/gpu/drm/msm/msm_gpu_devfreq.c | 73 ++++++++++++++++++++++++++-

>   3 files changed, 89 insertions(+), 1 deletion(-)

> 

> diff --git a/drivers/gpu/drm/msm/msm_gpu.c b/drivers/gpu/drm/msm/msm_gpu.c

> index 70d8610b1b73..68d2df590054 100644

> --- a/drivers/gpu/drm/msm/msm_gpu.c

> +++ b/drivers/gpu/drm/msm/msm_gpu.c

> @@ -667,6 +667,10 @@ static void retire_submit(struct msm_gpu *gpu, struct msm_ringbuffer *ring,

>   	list_del(&submit->node);

>   	spin_unlock(&ring->submit_lock);

>   

> +	/* Update devfreq on transition from active->idle: */

> +	if (atomic_dec_return(&gpu->active_submits) == 0)

This will race with the submit path. To avoid that, this test and the 
msm_devfreq_idle should be under the same lock. Same applies for the 
submit path.

-Akhil
> +		msm_devfreq_idle(gpu);

> +

>   	msm_gem_submit_put(submit);

>   }

>   

> @@ -747,6 +751,10 @@ void msm_gpu_submit(struct msm_gpu *gpu, struct msm_gem_submit *submit)

>   	list_add_tail(&submit->node, &ring->submits);

>   	spin_unlock(&ring->submit_lock);

>   

> +	/* Update devfreq on transition from idle->active: */

> +	if (atomic_inc_return(&gpu->active_submits) == 1)

> +		msm_devfreq_active(gpu);

> +

>   	gpu->funcs->submit(gpu, submit);

>   	priv->lastctx = submit->queue->ctx;

>   

> diff --git a/drivers/gpu/drm/msm/msm_gpu.h b/drivers/gpu/drm/msm/msm_gpu.h

> index ada15e28f251..e14edda3d778 100644

> --- a/drivers/gpu/drm/msm/msm_gpu.h

> +++ b/drivers/gpu/drm/msm/msm_gpu.h

> @@ -84,6 +84,10 @@ struct msm_gpu_devfreq {

>   	struct devfreq *devfreq;

>   	u64 busy_cycles;

>   	ktime_t time;

> +

> +	/* Time and freq of last transition to idle: */

> +	ktime_t idle_time;

> +	unsigned long idle_freq;

>   };

>   

>   struct msm_gpu {

> @@ -115,6 +119,9 @@ struct msm_gpu {

>   	 */

>   	struct list_head active_list;

>   

> +	/* number of in-flight submits: */

> +	atomic_t active_submits;

> +

>   	/* does gpu need hw_init? */

>   	bool needs_hw_init;

>   

> @@ -384,6 +391,8 @@ void msm_devfreq_init(struct msm_gpu *gpu);

>   void msm_devfreq_cleanup(struct msm_gpu *gpu);

>   void msm_devfreq_resume(struct msm_gpu *gpu);

>   void msm_devfreq_suspend(struct msm_gpu *gpu);

> +void msm_devfreq_active(struct msm_gpu *gpu);

> +void msm_devfreq_idle(struct msm_gpu *gpu);

>   

>   int msm_gpu_hw_init(struct msm_gpu *gpu);

>   

> diff --git a/drivers/gpu/drm/msm/msm_gpu_devfreq.c b/drivers/gpu/drm/msm/msm_gpu_devfreq.c

> index 2e24a97be624..0a1ee20296a2 100644

> --- a/drivers/gpu/drm/msm/msm_gpu_devfreq.c

> +++ b/drivers/gpu/drm/msm/msm_gpu_devfreq.c

> @@ -22,6 +22,15 @@ static int msm_devfreq_target(struct device *dev, unsigned long *freq,

>   

>   	opp = devfreq_recommended_opp(dev, freq, flags);

>   

> +	/*

> +	 * If the GPU is idle, devfreq is not aware, so just ignore

> +	 * it's requests

> +	 */

> +	if (gpu->devfreq.idle_freq) {

> +		gpu->devfreq.idle_freq = *freq;

> +		return 0;

> +	}

> +

>   	if (IS_ERR(opp))

>   		return PTR_ERR(opp);

>   

> @@ -39,6 +48,9 @@ static int msm_devfreq_target(struct device *dev, unsigned long *freq,

>   

>   static unsigned long get_freq(struct msm_gpu *gpu)

>   {

> +	if (gpu->devfreq.idle_freq)

> +		return gpu->devfreq.idle_freq;

> +

>   	if (gpu->funcs->gpu_get_freq)

>   		return gpu->funcs->gpu_get_freq(gpu);

>   

> @@ -69,7 +81,8 @@ static int msm_devfreq_get_cur_freq(struct device *dev, unsigned long *freq)

>   }

>   

>   static struct devfreq_dev_profile msm_devfreq_profile = {

> -	.polling_ms = 10,

> +	.timer = DEVFREQ_TIMER_DELAYED,

> +	.polling_ms = 50,

>   	.target = msm_devfreq_target,

>   	.get_dev_status = msm_devfreq_get_dev_status,

>   	.get_cur_freq = msm_devfreq_get_cur_freq,

> @@ -130,3 +143,61 @@ void msm_devfreq_suspend(struct msm_gpu *gpu)

>   {

>   	devfreq_suspend_device(gpu->devfreq.devfreq);

>   }

> +

> +void msm_devfreq_active(struct msm_gpu *gpu)

> +{

> +	struct msm_gpu_devfreq *df = &gpu->devfreq;

> +	struct devfreq_dev_status status;

> +	unsigned int idle_time;

> +	unsigned long target_freq = df->idle_freq;

> +

> +	/*

> +	 * Hold devfreq lock to synchronize with get_dev_status()/

> +	 * target() callbacks

> +	 */

> +	mutex_lock(&df->devfreq->lock);

> +

> +	idle_time = ktime_to_ms(ktime_sub(ktime_get(), df->idle_time));

> +

> +	/*

> +	 * If we've been idle for a significant fraction of a polling

> +	 * interval, then we won't meet the threshold of busyness for

> +	 * the governor to ramp up the freq.. so give some boost

> +	 */

> +	if (idle_time > msm_devfreq_profile.polling_ms/2) {

> +		target_freq *= 2;

> +	}

> +

> +	df->idle_freq = 0;

> +

> +	msm_devfreq_target(&gpu->pdev->dev, &target_freq, 0);

> +

> +	/*

> +	 * Reset the polling interval so we aren't inconsistent

> +	 * about freq vs busy/total cycles

> +	 */

> +	msm_devfreq_get_dev_status(&gpu->pdev->dev, &status);

> +

> +	mutex_unlock(&df->devfreq->lock);

> +}

> +

> +void msm_devfreq_idle(struct msm_gpu *gpu)

> +{

> +	struct msm_gpu_devfreq *df = &gpu->devfreq;

> +	unsigned long idle_freq, target_freq = 0;

> +

> +	/*

> +	 * Hold devfreq lock to synchronize with get_dev_status()/

> +	 * target() callbacks

> +	 */

> +	mutex_lock(&df->devfreq->lock);

> +

> +	idle_freq = get_freq(gpu);

> +

> +	msm_devfreq_target(&gpu->pdev->dev, &target_freq, 0);

> +

> +	df->idle_time = ktime_get();

> +	df->idle_freq = idle_freq;

> +

> +	mutex_unlock(&df->devfreq->lock);

> +}

>