mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 02:30:34 +02:00 
			
		
		
		
	drm/msm: return the average load over the polling period
simple_ondemand interacts poorly with clamp_to_idle.  It only looks at
the load since the last get_dev_status call, while it should really look
at the load over polling_ms.  When clamp_to_idle true, it almost always
picks the lowest frequency on active because the gpu is idle between
msm_devfreq_idle/msm_devfreq_active.
This logic could potentially be moved into devfreq core.
Fixes: 7c0ffcd40b ("drm/msm/gpu: Respect PM QoS constraints")
Signed-off-by: Chia-I Wu <olvaffe@gmail.com>
Cc: Rob Clark <robdclark@chromium.org>
Link: https://lore.kernel.org/r/20220416003314.59211-3-olvaffe@gmail.com
Signed-off-by: Rob Clark <robdclark@chromium.org>
			
			
This commit is contained in:
		
							parent
							
								
									15c411980b
								
							
						
					
					
						commit
						78f815c1cf
					
				
					 2 changed files with 62 additions and 1 deletions
				
			
		| 
						 | 
					@ -9,6 +9,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <linux/adreno-smmu-priv.h>
 | 
					#include <linux/adreno-smmu-priv.h>
 | 
				
			||||||
#include <linux/clk.h>
 | 
					#include <linux/clk.h>
 | 
				
			||||||
 | 
					#include <linux/devfreq.h>
 | 
				
			||||||
#include <linux/interconnect.h>
 | 
					#include <linux/interconnect.h>
 | 
				
			||||||
#include <linux/pm_opp.h>
 | 
					#include <linux/pm_opp.h>
 | 
				
			||||||
#include <linux/regulator/consumer.h>
 | 
					#include <linux/regulator/consumer.h>
 | 
				
			||||||
| 
						 | 
					@ -118,6 +119,8 @@ struct msm_gpu_devfreq {
 | 
				
			||||||
	/** idle_time: Time of last transition to idle: */
 | 
						/** idle_time: Time of last transition to idle: */
 | 
				
			||||||
	ktime_t idle_time;
 | 
						ktime_t idle_time;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct devfreq_dev_status average_status;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * idle_work:
 | 
						 * idle_work:
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,6 +9,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <linux/devfreq.h>
 | 
					#include <linux/devfreq.h>
 | 
				
			||||||
#include <linux/devfreq_cooling.h>
 | 
					#include <linux/devfreq_cooling.h>
 | 
				
			||||||
 | 
					#include <linux/math64.h>
 | 
				
			||||||
#include <linux/units.h>
 | 
					#include <linux/units.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
| 
						 | 
					@ -75,12 +76,69 @@ static void get_raw_dev_status(struct msm_gpu *gpu,
 | 
				
			||||||
	status->busy_time = busy_time;
 | 
						status->busy_time = busy_time;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void update_average_dev_status(struct msm_gpu *gpu,
 | 
				
			||||||
 | 
							const struct devfreq_dev_status *raw)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct msm_gpu_devfreq *df = &gpu->devfreq;
 | 
				
			||||||
 | 
						const u32 polling_ms = df->devfreq->profile->polling_ms;
 | 
				
			||||||
 | 
						const u32 max_history_ms = polling_ms * 11 / 10;
 | 
				
			||||||
 | 
						struct devfreq_dev_status *avg = &df->average_status;
 | 
				
			||||||
 | 
						u64 avg_freq;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* simple_ondemand governor interacts poorly with gpu->clamp_to_idle.
 | 
				
			||||||
 | 
						 * When we enforce the constraint on idle, it calls get_dev_status
 | 
				
			||||||
 | 
						 * which would normally reset the stats.  When we remove the
 | 
				
			||||||
 | 
						 * constraint on active, it calls get_dev_status again where busy_time
 | 
				
			||||||
 | 
						 * would be 0.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * To remedy this, we always return the average load over the past
 | 
				
			||||||
 | 
						 * polling_ms.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* raw is longer than polling_ms or avg has no history */
 | 
				
			||||||
 | 
						if (div_u64(raw->total_time, USEC_PER_MSEC) >= polling_ms ||
 | 
				
			||||||
 | 
						    !avg->total_time) {
 | 
				
			||||||
 | 
							*avg = *raw;
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Truncate the oldest history first.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * Because we keep the history with a single devfreq_dev_status,
 | 
				
			||||||
 | 
						 * rather than a list of devfreq_dev_status, we have to assume freq
 | 
				
			||||||
 | 
						 * and load are the same over avg->total_time.  We can scale down
 | 
				
			||||||
 | 
						 * avg->busy_time and avg->total_time by the same factor to drop
 | 
				
			||||||
 | 
						 * history.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						if (div_u64(avg->total_time + raw->total_time, USEC_PER_MSEC) >=
 | 
				
			||||||
 | 
								max_history_ms) {
 | 
				
			||||||
 | 
							const u32 new_total_time = polling_ms * USEC_PER_MSEC -
 | 
				
			||||||
 | 
								raw->total_time;
 | 
				
			||||||
 | 
							avg->busy_time = div_u64(
 | 
				
			||||||
 | 
									mul_u32_u32(avg->busy_time, new_total_time),
 | 
				
			||||||
 | 
									avg->total_time);
 | 
				
			||||||
 | 
							avg->total_time = new_total_time;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* compute the average freq over avg->total_time + raw->total_time */
 | 
				
			||||||
 | 
						avg_freq = mul_u32_u32(avg->current_frequency, avg->total_time);
 | 
				
			||||||
 | 
						avg_freq += mul_u32_u32(raw->current_frequency, raw->total_time);
 | 
				
			||||||
 | 
						do_div(avg_freq, avg->total_time + raw->total_time);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						avg->current_frequency = avg_freq;
 | 
				
			||||||
 | 
						avg->busy_time += raw->busy_time;
 | 
				
			||||||
 | 
						avg->total_time += raw->total_time;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int msm_devfreq_get_dev_status(struct device *dev,
 | 
					static int msm_devfreq_get_dev_status(struct device *dev,
 | 
				
			||||||
		struct devfreq_dev_status *status)
 | 
							struct devfreq_dev_status *status)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct msm_gpu *gpu = dev_to_gpu(dev);
 | 
						struct msm_gpu *gpu = dev_to_gpu(dev);
 | 
				
			||||||
 | 
						struct devfreq_dev_status raw;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	get_raw_dev_status(gpu, status);
 | 
						get_raw_dev_status(gpu, &raw);
 | 
				
			||||||
 | 
						update_average_dev_status(gpu, &raw);
 | 
				
			||||||
 | 
						*status = gpu->devfreq.average_status;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return 0;
 | 
						return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue