mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 10:40:15 +02:00 
			
		
		
		
	drm/sched: Add scheduler unit testing infrastructure and some basic tests
Implement a mock scheduler backend and add some basic test to exercise the core scheduler code paths. Mock backend (kind of like a very simple mock GPU) can either process jobs by tests manually advancing the "timeline" job at a time, or alternatively jobs can be configured with a time duration in which case they get completed asynchronously from the unit test code. Core scheduler classes are subclassed to support this mock implementation. The tests added are just a few simple submission patterns. Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@igalia.com> Suggested-by: Philipp Stanner <phasta@kernel.org> Cc: Christian König <christian.koenig@amd.com> Cc: Danilo Krummrich <dakr@kernel.org> Cc: Matthew Brost <matthew.brost@intel.com> Cc: Philipp Stanner <phasta@kernel.org> Acked-by: Christian König <christian.koenig@amd.com> Signed-off-by: Philipp Stanner <phasta@kernel.org> Link: https://patchwork.freedesktop.org/patch/msgid/20250324092633.49746-3-tvrtko.ursulin@igalia.com
This commit is contained in:
		
							parent
							
								
									8e623137f1
								
							
						
					
					
						commit
						5a99350794
					
				
					 7 changed files with 809 additions and 0 deletions
				
			
		| 
						 | 
					@ -100,5 +100,17 @@ config DRM_TTM_KUNIT_TEST
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	  If in doubt, say "N".
 | 
						  If in doubt, say "N".
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					config DRM_SCHED_KUNIT_TEST
 | 
				
			||||||
 | 
						tristate "KUnit tests for the DRM scheduler" if !KUNIT_ALL_TESTS
 | 
				
			||||||
 | 
						select DRM_SCHED
 | 
				
			||||||
 | 
						depends on DRM && KUNIT
 | 
				
			||||||
 | 
						default KUNIT_ALL_TESTS
 | 
				
			||||||
 | 
						help
 | 
				
			||||||
 | 
						  Choose this option to build unit tests for the DRM scheduler.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						  Recommended for driver developers only.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						  If in doubt, say "N".
 | 
				
			||||||
 | 
					
 | 
				
			||||||
config DRM_EXPORT_FOR_TESTS
 | 
					config DRM_EXPORT_FOR_TESTS
 | 
				
			||||||
	bool
 | 
						bool
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										12
									
								
								drivers/gpu/drm/scheduler/.kunitconfig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								drivers/gpu/drm/scheduler/.kunitconfig
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,12 @@
 | 
				
			||||||
 | 
					CONFIG_KUNIT=y
 | 
				
			||||||
 | 
					CONFIG_DRM=y
 | 
				
			||||||
 | 
					CONFIG_DRM_SCHED_KUNIT_TEST=y
 | 
				
			||||||
 | 
					CONFIG_EXPERT=y
 | 
				
			||||||
 | 
					CONFIG_DEBUG_SPINLOCK=y
 | 
				
			||||||
 | 
					CONFIG_DEBUG_MUTEXES=y
 | 
				
			||||||
 | 
					CONFIG_DEBUG_ATOMIC_SLEEP=y
 | 
				
			||||||
 | 
					CONFIG_LOCK_DEBUGGING_SUPPORT=y
 | 
				
			||||||
 | 
					CONFIG_PROVE_LOCKING=y
 | 
				
			||||||
 | 
					CONFIG_LOCKDEP=y
 | 
				
			||||||
 | 
					CONFIG_DEBUG_LOCKDEP=y
 | 
				
			||||||
 | 
					CONFIG_DEBUG_LIST=y
 | 
				
			||||||
| 
						 | 
					@ -23,3 +23,5 @@
 | 
				
			||||||
gpu-sched-y := sched_main.o sched_fence.o sched_entity.o
 | 
					gpu-sched-y := sched_main.o sched_fence.o sched_entity.o
 | 
				
			||||||
 | 
					
 | 
				
			||||||
obj-$(CONFIG_DRM_SCHED) += gpu-sched.o
 | 
					obj-$(CONFIG_DRM_SCHED) += gpu-sched.o
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					obj-$(CONFIG_DRM_SCHED_KUNIT_TEST) += tests/
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										7
									
								
								drivers/gpu/drm/scheduler/tests/Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								drivers/gpu/drm/scheduler/tests/Makefile
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,7 @@
 | 
				
			||||||
 | 
					# SPDX-License-Identifier: GPL-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					drm-sched-tests-y := \
 | 
				
			||||||
 | 
					        mock_scheduler.o \
 | 
				
			||||||
 | 
					        tests_basic.o
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					obj-$(CONFIG_DRM_SCHED_KUNIT_TEST) += drm-sched-tests.o
 | 
				
			||||||
							
								
								
									
										354
									
								
								drivers/gpu/drm/scheduler/tests/mock_scheduler.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										354
									
								
								drivers/gpu/drm/scheduler/tests/mock_scheduler.c
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,354 @@
 | 
				
			||||||
 | 
					// SPDX-License-Identifier: GPL-2.0
 | 
				
			||||||
 | 
					/* Copyright (c) 2025 Valve Corporation */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "sched_tests.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Here we implement the mock "GPU" (or the scheduler backend) which is used by
 | 
				
			||||||
 | 
					 * the DRM scheduler unit tests in order to exercise the core functionality.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Test cases are implemented in a separate file.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * drm_mock_sched_entity_new - Create a new mock scheduler entity
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @test: KUnit test owning the entity
 | 
				
			||||||
 | 
					 * @priority: Scheduling priority
 | 
				
			||||||
 | 
					 * @sched: Mock scheduler on which the entity can be scheduled
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Returns: New mock scheduler entity with allocation managed by the test
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					struct drm_mock_sched_entity *
 | 
				
			||||||
 | 
					drm_mock_sched_entity_new(struct kunit *test,
 | 
				
			||||||
 | 
								  enum drm_sched_priority priority,
 | 
				
			||||||
 | 
								  struct drm_mock_scheduler *sched)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct drm_mock_sched_entity *entity;
 | 
				
			||||||
 | 
						struct drm_gpu_scheduler *drm_sched;
 | 
				
			||||||
 | 
						int ret;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						entity = kunit_kzalloc(test, sizeof(*entity), GFP_KERNEL);
 | 
				
			||||||
 | 
						KUNIT_ASSERT_NOT_NULL(test, entity);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						drm_sched = &sched->base;
 | 
				
			||||||
 | 
						ret = drm_sched_entity_init(&entity->base,
 | 
				
			||||||
 | 
									    priority,
 | 
				
			||||||
 | 
									    &drm_sched, 1,
 | 
				
			||||||
 | 
									    NULL);
 | 
				
			||||||
 | 
						KUNIT_ASSERT_EQ(test, ret, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						entity->test = test;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return entity;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * drm_mock_sched_entity_free - Destroys a mock scheduler entity
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @entity: Entity to destroy
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * To be used from the test cases once done with the entity.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					void drm_mock_sched_entity_free(struct drm_mock_sched_entity *entity)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						drm_sched_entity_destroy(&entity->base);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void drm_mock_sched_job_complete(struct drm_mock_sched_job *job)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct drm_mock_scheduler *sched =
 | 
				
			||||||
 | 
							drm_sched_to_mock_sched(job->base.sched);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						lockdep_assert_held(&sched->lock);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						job->flags |= DRM_MOCK_SCHED_JOB_DONE;
 | 
				
			||||||
 | 
						list_move_tail(&job->link, &sched->done_list);
 | 
				
			||||||
 | 
						dma_fence_signal(&job->hw_fence);
 | 
				
			||||||
 | 
						complete(&job->done);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static enum hrtimer_restart
 | 
				
			||||||
 | 
					drm_mock_sched_job_signal_timer(struct hrtimer *hrtimer)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct drm_mock_sched_job *job =
 | 
				
			||||||
 | 
							container_of(hrtimer, typeof(*job), timer);
 | 
				
			||||||
 | 
						struct drm_mock_scheduler *sched =
 | 
				
			||||||
 | 
							drm_sched_to_mock_sched(job->base.sched);
 | 
				
			||||||
 | 
						struct drm_mock_sched_job *next;
 | 
				
			||||||
 | 
						ktime_t now = ktime_get();
 | 
				
			||||||
 | 
						unsigned long flags;
 | 
				
			||||||
 | 
						LIST_HEAD(signal);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						spin_lock_irqsave(&sched->lock, flags);
 | 
				
			||||||
 | 
						list_for_each_entry_safe(job, next, &sched->job_list, link) {
 | 
				
			||||||
 | 
							if (!job->duration_us)
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (ktime_before(now, job->finish_at))
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							sched->hw_timeline.cur_seqno = job->hw_fence.seqno;
 | 
				
			||||||
 | 
							drm_mock_sched_job_complete(job);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						spin_unlock_irqrestore(&sched->lock, flags);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return HRTIMER_NORESTART;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * drm_mock_sched_job_new - Create a new mock scheduler job
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @test: KUnit test owning the job
 | 
				
			||||||
 | 
					 * @entity: Scheduler entity of the job
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Returns: New mock scheduler job with allocation managed by the test
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					struct drm_mock_sched_job *
 | 
				
			||||||
 | 
					drm_mock_sched_job_new(struct kunit *test,
 | 
				
			||||||
 | 
							       struct drm_mock_sched_entity *entity)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct drm_mock_sched_job *job;
 | 
				
			||||||
 | 
						int ret;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						job = kunit_kzalloc(test, sizeof(*job), GFP_KERNEL);
 | 
				
			||||||
 | 
						KUNIT_ASSERT_NOT_NULL(test, job);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ret = drm_sched_job_init(&job->base,
 | 
				
			||||||
 | 
									 &entity->base,
 | 
				
			||||||
 | 
									 1,
 | 
				
			||||||
 | 
									 NULL);
 | 
				
			||||||
 | 
						KUNIT_ASSERT_EQ(test, ret, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						job->test = test;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						init_completion(&job->done);
 | 
				
			||||||
 | 
						spin_lock_init(&job->lock);
 | 
				
			||||||
 | 
						INIT_LIST_HEAD(&job->link);
 | 
				
			||||||
 | 
						hrtimer_init(&job->timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS);
 | 
				
			||||||
 | 
						job->timer.function = drm_mock_sched_job_signal_timer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return job;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const char *drm_mock_sched_hw_fence_driver_name(struct dma_fence *fence)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						return "drm_mock_sched";
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const char *
 | 
				
			||||||
 | 
					drm_mock_sched_hw_fence_timeline_name(struct dma_fence *fence)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct drm_mock_sched_job *job =
 | 
				
			||||||
 | 
							container_of(fence, typeof(*job), hw_fence);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return (const char *)job->base.sched->name;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void drm_mock_sched_hw_fence_release(struct dma_fence *fence)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct drm_mock_sched_job *job =
 | 
				
			||||||
 | 
							container_of(fence, typeof(*job), hw_fence);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						hrtimer_cancel(&job->timer);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Containing job is freed by the kunit framework */
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const struct dma_fence_ops drm_mock_sched_hw_fence_ops = {
 | 
				
			||||||
 | 
						.get_driver_name = drm_mock_sched_hw_fence_driver_name,
 | 
				
			||||||
 | 
						.get_timeline_name = drm_mock_sched_hw_fence_timeline_name,
 | 
				
			||||||
 | 
						.release = drm_mock_sched_hw_fence_release,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static struct dma_fence *mock_sched_run_job(struct drm_sched_job *sched_job)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct drm_mock_scheduler *sched =
 | 
				
			||||||
 | 
							drm_sched_to_mock_sched(sched_job->sched);
 | 
				
			||||||
 | 
						struct drm_mock_sched_job *job = drm_sched_job_to_mock_job(sched_job);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						dma_fence_init(&job->hw_fence,
 | 
				
			||||||
 | 
							       &drm_mock_sched_hw_fence_ops,
 | 
				
			||||||
 | 
							       &job->lock,
 | 
				
			||||||
 | 
							       sched->hw_timeline.context,
 | 
				
			||||||
 | 
							       atomic_inc_return(&sched->hw_timeline.next_seqno));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						dma_fence_get(&job->hw_fence); /* Reference for the job_list */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						spin_lock_irq(&sched->lock);
 | 
				
			||||||
 | 
						if (job->duration_us) {
 | 
				
			||||||
 | 
							ktime_t prev_finish_at = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (!list_empty(&sched->job_list)) {
 | 
				
			||||||
 | 
								struct drm_mock_sched_job *prev =
 | 
				
			||||||
 | 
									list_last_entry(&sched->job_list, typeof(*prev),
 | 
				
			||||||
 | 
											link);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								prev_finish_at = prev->finish_at;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (!prev_finish_at)
 | 
				
			||||||
 | 
								prev_finish_at = ktime_get();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							job->finish_at = ktime_add_us(prev_finish_at, job->duration_us);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						list_add_tail(&job->link, &sched->job_list);
 | 
				
			||||||
 | 
						if (job->finish_at)
 | 
				
			||||||
 | 
							hrtimer_start(&job->timer, job->finish_at, HRTIMER_MODE_ABS);
 | 
				
			||||||
 | 
						spin_unlock_irq(&sched->lock);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return &job->hw_fence;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static enum drm_gpu_sched_stat
 | 
				
			||||||
 | 
					mock_sched_timedout_job(struct drm_sched_job *sched_job)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						return DRM_GPU_SCHED_STAT_ENODEV;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void mock_sched_free_job(struct drm_sched_job *sched_job)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct drm_mock_scheduler *sched =
 | 
				
			||||||
 | 
								drm_sched_to_mock_sched(sched_job->sched);
 | 
				
			||||||
 | 
						struct drm_mock_sched_job *job = drm_sched_job_to_mock_job(sched_job);
 | 
				
			||||||
 | 
						unsigned long flags;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Remove from the scheduler done list. */
 | 
				
			||||||
 | 
						spin_lock_irqsave(&sched->lock, flags);
 | 
				
			||||||
 | 
						list_del(&job->link);
 | 
				
			||||||
 | 
						spin_unlock_irqrestore(&sched->lock, flags);
 | 
				
			||||||
 | 
						dma_fence_put(&job->hw_fence);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						drm_sched_job_cleanup(sched_job);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Mock job itself is freed by the kunit framework. */
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const struct drm_sched_backend_ops drm_mock_scheduler_ops = {
 | 
				
			||||||
 | 
						.run_job = mock_sched_run_job,
 | 
				
			||||||
 | 
						.timedout_job = mock_sched_timedout_job,
 | 
				
			||||||
 | 
						.free_job = mock_sched_free_job
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * drm_mock_sched_new - Create a new mock scheduler
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @test: KUnit test owning the job
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Returns: New mock scheduler with allocation managed by the test
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					struct drm_mock_scheduler *drm_mock_sched_new(struct kunit *test)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct drm_sched_init_args args = {
 | 
				
			||||||
 | 
							.ops		= &drm_mock_scheduler_ops,
 | 
				
			||||||
 | 
							.num_rqs	= DRM_SCHED_PRIORITY_COUNT,
 | 
				
			||||||
 | 
							.credit_limit	= U32_MAX,
 | 
				
			||||||
 | 
							.hang_limit	= 1,
 | 
				
			||||||
 | 
							.timeout	= MAX_SCHEDULE_TIMEOUT,
 | 
				
			||||||
 | 
							.name		= "drm-mock-scheduler",
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						struct drm_mock_scheduler *sched;
 | 
				
			||||||
 | 
						int ret;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sched = kunit_kzalloc(test, sizeof(*sched), GFP_KERNEL);
 | 
				
			||||||
 | 
						KUNIT_ASSERT_NOT_NULL(test, sched);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ret = drm_sched_init(&sched->base, &args);
 | 
				
			||||||
 | 
						KUNIT_ASSERT_EQ(test, ret, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sched->test = test;
 | 
				
			||||||
 | 
						sched->hw_timeline.context = dma_fence_context_alloc(1);
 | 
				
			||||||
 | 
						atomic_set(&sched->hw_timeline.next_seqno, 0);
 | 
				
			||||||
 | 
						INIT_LIST_HEAD(&sched->job_list);
 | 
				
			||||||
 | 
						INIT_LIST_HEAD(&sched->done_list);
 | 
				
			||||||
 | 
						spin_lock_init(&sched->lock);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return sched;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * drm_mock_sched_fini - Destroys a mock scheduler
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @sched: Scheduler to destroy
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * To be used from the test cases once done with the scheduler.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					void drm_mock_sched_fini(struct drm_mock_scheduler *sched)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct drm_mock_sched_job *job, *next;
 | 
				
			||||||
 | 
						unsigned long flags;
 | 
				
			||||||
 | 
						LIST_HEAD(list);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						drm_sched_wqueue_stop(&sched->base);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Force complete all unfinished jobs. */
 | 
				
			||||||
 | 
						spin_lock_irqsave(&sched->lock, flags);
 | 
				
			||||||
 | 
						list_for_each_entry_safe(job, next, &sched->job_list, link)
 | 
				
			||||||
 | 
							list_move_tail(&job->link, &list);
 | 
				
			||||||
 | 
						spin_unlock_irqrestore(&sched->lock, flags);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						list_for_each_entry(job, &list, link)
 | 
				
			||||||
 | 
							hrtimer_cancel(&job->timer);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						spin_lock_irqsave(&sched->lock, flags);
 | 
				
			||||||
 | 
						list_for_each_entry_safe(job, next, &list, link)
 | 
				
			||||||
 | 
							drm_mock_sched_job_complete(job);
 | 
				
			||||||
 | 
						spin_unlock_irqrestore(&sched->lock, flags);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/*
 | 
				
			||||||
 | 
						 * Free completed jobs and jobs not yet processed by the DRM scheduler
 | 
				
			||||||
 | 
						 * free worker.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						spin_lock_irqsave(&sched->lock, flags);
 | 
				
			||||||
 | 
						list_for_each_entry_safe(job, next, &sched->done_list, link)
 | 
				
			||||||
 | 
							list_move_tail(&job->link, &list);
 | 
				
			||||||
 | 
						spin_unlock_irqrestore(&sched->lock, flags);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						list_for_each_entry_safe(job, next, &list, link)
 | 
				
			||||||
 | 
							mock_sched_free_job(&job->base);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						drm_sched_fini(&sched->base);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * drm_mock_sched_advance - Advances the mock scheduler timeline
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @sched: Scheduler timeline to advance
 | 
				
			||||||
 | 
					 * @num: By how many jobs to advance
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Advancing the scheduler timeline by a number of seqnos will trigger
 | 
				
			||||||
 | 
					 * signalling of the hardware fences and unlinking the jobs from the internal
 | 
				
			||||||
 | 
					 * scheduler tracking.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * This can be used from test cases which want complete control of the simulated
 | 
				
			||||||
 | 
					 * job execution timing. For example submitting one job with no set duration
 | 
				
			||||||
 | 
					 * would never complete it before test cases advances the timeline by one.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					unsigned int drm_mock_sched_advance(struct drm_mock_scheduler *sched,
 | 
				
			||||||
 | 
									    unsigned int num)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct drm_mock_sched_job *job, *next;
 | 
				
			||||||
 | 
						unsigned int found = 0;
 | 
				
			||||||
 | 
						unsigned long flags;
 | 
				
			||||||
 | 
						LIST_HEAD(signal);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						spin_lock_irqsave(&sched->lock, flags);
 | 
				
			||||||
 | 
						if (WARN_ON_ONCE(sched->hw_timeline.cur_seqno + num <
 | 
				
			||||||
 | 
								 sched->hw_timeline.cur_seqno))
 | 
				
			||||||
 | 
							goto unlock;
 | 
				
			||||||
 | 
						sched->hw_timeline.cur_seqno += num;
 | 
				
			||||||
 | 
						list_for_each_entry_safe(job, next, &sched->job_list, link) {
 | 
				
			||||||
 | 
							if (sched->hw_timeline.cur_seqno < job->hw_fence.seqno)
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							drm_mock_sched_job_complete(job);
 | 
				
			||||||
 | 
							found++;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					unlock:
 | 
				
			||||||
 | 
						spin_unlock_irqrestore(&sched->lock, flags);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return found;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MODULE_DESCRIPTION("DRM mock scheduler and tests");
 | 
				
			||||||
 | 
					MODULE_LICENSE("GPL");
 | 
				
			||||||
							
								
								
									
										224
									
								
								drivers/gpu/drm/scheduler/tests/sched_tests.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										224
									
								
								drivers/gpu/drm/scheduler/tests/sched_tests.h
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,224 @@
 | 
				
			||||||
 | 
					/* SPDX-License-Identifier: GPL-2.0 */
 | 
				
			||||||
 | 
					/* Copyright (c) 2025 Valve Corporation */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef _SCHED_TESTS_H_
 | 
				
			||||||
 | 
					#define _SCHED_TESTS_H_
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <kunit/test.h>
 | 
				
			||||||
 | 
					#include <linux/atomic.h>
 | 
				
			||||||
 | 
					#include <linux/completion.h>
 | 
				
			||||||
 | 
					#include <linux/dma-fence.h>
 | 
				
			||||||
 | 
					#include <linux/hrtimer.h>
 | 
				
			||||||
 | 
					#include <linux/ktime.h>
 | 
				
			||||||
 | 
					#include <linux/list.h>
 | 
				
			||||||
 | 
					#include <linux/atomic.h>
 | 
				
			||||||
 | 
					#include <linux/mutex.h>
 | 
				
			||||||
 | 
					#include <linux/types.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <drm/gpu_scheduler.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * DOC: Mock DRM scheduler data structures
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * drm_mock_* data structures are used to implement a mock "GPU".
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * They subclass the core DRM scheduler objects and add their data on top, which
 | 
				
			||||||
 | 
					 * enables tracking the submitted jobs and simulating their execution with the
 | 
				
			||||||
 | 
					 * attributes as specified by the test case.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * struct drm_mock_scheduler - implements a trivial mock GPU execution engine
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @base: DRM scheduler base class
 | 
				
			||||||
 | 
					 * @test: Backpointer to owning the kunit test case
 | 
				
			||||||
 | 
					 * @lock: Lock to protect the simulated @hw_timeline, @job_list and @done_list
 | 
				
			||||||
 | 
					 * @job_list: List of jobs submitted to the mock GPU
 | 
				
			||||||
 | 
					 * @done_list: List of jobs completed by the mock GPU
 | 
				
			||||||
 | 
					 * @hw_timeline: Simulated hardware timeline has a @context, @next_seqno and
 | 
				
			||||||
 | 
					 *		 @cur_seqno for implementing a struct dma_fence signaling the
 | 
				
			||||||
 | 
					 *		 simulated job completion.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Trivial mock GPU execution engine tracks submitted jobs and enables
 | 
				
			||||||
 | 
					 * completing them strictly in submission order.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					struct drm_mock_scheduler {
 | 
				
			||||||
 | 
						struct drm_gpu_scheduler base;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct kunit		*test;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						spinlock_t		lock;
 | 
				
			||||||
 | 
						struct list_head	job_list;
 | 
				
			||||||
 | 
						struct list_head	done_list;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct {
 | 
				
			||||||
 | 
							u64		context;
 | 
				
			||||||
 | 
							atomic_t	next_seqno;
 | 
				
			||||||
 | 
							unsigned int	cur_seqno;
 | 
				
			||||||
 | 
						} hw_timeline;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * struct drm_mock_sched_entity - implements a mock GPU sched entity
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @base: DRM scheduler entity base class
 | 
				
			||||||
 | 
					 * @test: Backpointer to owning the kunit test case
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Mock GPU sched entity is used by the test cases to submit jobs to the mock
 | 
				
			||||||
 | 
					 * scheduler.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					struct drm_mock_sched_entity {
 | 
				
			||||||
 | 
						struct drm_sched_entity base;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct kunit		*test;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * struct drm_mock_sched_job - implements a mock GPU job
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @base: DRM sched job base class
 | 
				
			||||||
 | 
					 * @done: Completion signaling job completion.
 | 
				
			||||||
 | 
					 * @flags: Flags designating job state.
 | 
				
			||||||
 | 
					 * @link: List head element used by job tracking by the drm_mock_scheduler
 | 
				
			||||||
 | 
					 * @timer: Timer used for simulating job execution duration
 | 
				
			||||||
 | 
					 * @duration_us: Simulated job duration in micro seconds, or zero if in manual
 | 
				
			||||||
 | 
					 *		 timeline advance mode
 | 
				
			||||||
 | 
					 * @finish_at: Absolute time when the jobs with set duration will complete
 | 
				
			||||||
 | 
					 * @lock: Lock used for @hw_fence
 | 
				
			||||||
 | 
					 * @hw_fence: Fence returned to DRM scheduler as the hardware fence
 | 
				
			||||||
 | 
					 * @test: Backpointer to owning the kunit test case
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Mock GPU sched job is used by the test cases to submit jobs to the mock
 | 
				
			||||||
 | 
					 * scheduler.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					struct drm_mock_sched_job {
 | 
				
			||||||
 | 
						struct drm_sched_job	base;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct completion	done;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define DRM_MOCK_SCHED_JOB_DONE		0x1
 | 
				
			||||||
 | 
						unsigned long		flags;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct list_head	link;
 | 
				
			||||||
 | 
						struct hrtimer		timer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						unsigned int		duration_us;
 | 
				
			||||||
 | 
						ktime_t			finish_at;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						spinlock_t		lock;
 | 
				
			||||||
 | 
						struct dma_fence	hw_fence;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct kunit		*test;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static inline struct drm_mock_scheduler *
 | 
				
			||||||
 | 
					drm_sched_to_mock_sched(struct drm_gpu_scheduler *sched)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						return container_of(sched, struct drm_mock_scheduler, base);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static inline struct drm_mock_sched_entity *
 | 
				
			||||||
 | 
					drm_sched_entity_to_mock_entity(struct drm_sched_entity *sched_entity)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						return container_of(sched_entity, struct drm_mock_sched_entity, base);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static inline struct drm_mock_sched_job *
 | 
				
			||||||
 | 
					drm_sched_job_to_mock_job(struct drm_sched_job *sched_job)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						return container_of(sched_job, struct drm_mock_sched_job, base);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct drm_mock_scheduler *drm_mock_sched_new(struct kunit *test);
 | 
				
			||||||
 | 
					void drm_mock_sched_fini(struct drm_mock_scheduler *sched);
 | 
				
			||||||
 | 
					unsigned int drm_mock_sched_advance(struct drm_mock_scheduler *sched,
 | 
				
			||||||
 | 
									    unsigned int num);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct drm_mock_sched_entity *
 | 
				
			||||||
 | 
					drm_mock_sched_entity_new(struct kunit *test,
 | 
				
			||||||
 | 
								  enum drm_sched_priority priority,
 | 
				
			||||||
 | 
								  struct drm_mock_scheduler *sched);
 | 
				
			||||||
 | 
					void drm_mock_sched_entity_free(struct drm_mock_sched_entity *entity);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct drm_mock_sched_job *
 | 
				
			||||||
 | 
					drm_mock_sched_job_new(struct kunit *test,
 | 
				
			||||||
 | 
							       struct drm_mock_sched_entity *entity);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * drm_mock_sched_job_submit - Arm and submit a job in one go
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @job: Job to arm and submit
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					static inline void drm_mock_sched_job_submit(struct drm_mock_sched_job *job)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						drm_sched_job_arm(&job->base);
 | 
				
			||||||
 | 
						drm_sched_entity_push_job(&job->base);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * drm_mock_sched_job_set_duration_us - Set a job duration
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @job: Job to set the duration for
 | 
				
			||||||
 | 
					 * @duration_us: Duration in micro seconds
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Jobs with duration set will be automatically completed by the mock scheduler
 | 
				
			||||||
 | 
					 * as the timeline progresses, unless a job without a set duration is
 | 
				
			||||||
 | 
					 * encountered in the timelime in which case calling drm_mock_sched_advance()
 | 
				
			||||||
 | 
					 * will be required to bump the timeline.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					static inline void
 | 
				
			||||||
 | 
					drm_mock_sched_job_set_duration_us(struct drm_mock_sched_job *job,
 | 
				
			||||||
 | 
									   unsigned int duration_us)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						job->duration_us = duration_us;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * drm_mock_sched_job_is_finished - Check if a job is finished
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @job: Job to check
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Returns: true if finished
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					static inline bool
 | 
				
			||||||
 | 
					drm_mock_sched_job_is_finished(struct drm_mock_sched_job *job)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						return job->flags & DRM_MOCK_SCHED_JOB_DONE;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * drm_mock_sched_job_wait_finished - Wait until a job is finished
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @job: Job to wait for
 | 
				
			||||||
 | 
					 * @timeout: Wait time in jiffies
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Returns: true if finished within the timeout provided, otherwise false
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					static inline bool
 | 
				
			||||||
 | 
					drm_mock_sched_job_wait_finished(struct drm_mock_sched_job *job, long timeout)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (job->flags & DRM_MOCK_SCHED_JOB_DONE)
 | 
				
			||||||
 | 
							return true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return wait_for_completion_timeout(&job->done, timeout) != 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * drm_mock_sched_job_wait_scheduled - Wait until a job is scheduled
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @job: Job to wait for
 | 
				
			||||||
 | 
					 * @timeout: Wait time in jiffies
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Returns: true if scheduled within the timeout provided, otherwise false
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					static inline bool
 | 
				
			||||||
 | 
					drm_mock_sched_job_wait_scheduled(struct drm_mock_sched_job *job, long timeout)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						KUNIT_ASSERT_EQ(job->test, job->flags & DRM_MOCK_SCHED_JOB_DONE, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return dma_fence_wait_timeout(&job->base.s_fence->scheduled,
 | 
				
			||||||
 | 
									      false,
 | 
				
			||||||
 | 
									      timeout) != 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
							
								
								
									
										198
									
								
								drivers/gpu/drm/scheduler/tests/tests_basic.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										198
									
								
								drivers/gpu/drm/scheduler/tests/tests_basic.c
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,198 @@
 | 
				
			||||||
 | 
					// SPDX-License-Identifier: GPL-2.0
 | 
				
			||||||
 | 
					/* Copyright (c) 2025 Valve Corporation */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "sched_tests.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * DRM scheduler basic tests should check the basic functional correctness of
 | 
				
			||||||
 | 
					 * the scheduler, including some very light smoke testing. More targeted tests,
 | 
				
			||||||
 | 
					 * for example focusing on testing specific bugs and other more complicated test
 | 
				
			||||||
 | 
					 * scenarios, should be implemented in separate source units.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int drm_sched_basic_init(struct kunit *test)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						test->priv = drm_mock_sched_new(test);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void drm_sched_basic_exit(struct kunit *test)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct drm_mock_scheduler *sched = test->priv;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						drm_mock_sched_fini(sched);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void drm_sched_basic_submit(struct kunit *test)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct drm_mock_scheduler *sched = test->priv;
 | 
				
			||||||
 | 
						struct drm_mock_sched_entity *entity;
 | 
				
			||||||
 | 
						struct drm_mock_sched_job *job;
 | 
				
			||||||
 | 
						unsigned int i;
 | 
				
			||||||
 | 
						bool done;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/*
 | 
				
			||||||
 | 
						 * Submit one job to the scheduler and verify that it gets scheduled
 | 
				
			||||||
 | 
						 * and completed only when the mock hw backend processes it.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						entity = drm_mock_sched_entity_new(test,
 | 
				
			||||||
 | 
										   DRM_SCHED_PRIORITY_NORMAL,
 | 
				
			||||||
 | 
										   sched);
 | 
				
			||||||
 | 
						job = drm_mock_sched_job_new(test, entity);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						drm_mock_sched_job_submit(job);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						done = drm_mock_sched_job_wait_scheduled(job, HZ);
 | 
				
			||||||
 | 
						KUNIT_ASSERT_TRUE(test, done);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						done = drm_mock_sched_job_wait_finished(job, HZ / 2);
 | 
				
			||||||
 | 
						KUNIT_ASSERT_FALSE(test, done);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						i = drm_mock_sched_advance(sched, 1);
 | 
				
			||||||
 | 
						KUNIT_ASSERT_EQ(test, i, 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						done = drm_mock_sched_job_wait_finished(job, HZ);
 | 
				
			||||||
 | 
						KUNIT_ASSERT_TRUE(test, done);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						drm_mock_sched_entity_free(entity);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct drm_sched_basic_params {
 | 
				
			||||||
 | 
						const char *description;
 | 
				
			||||||
 | 
						unsigned int queue_depth;
 | 
				
			||||||
 | 
						unsigned int num_entities;
 | 
				
			||||||
 | 
						unsigned int job_us;
 | 
				
			||||||
 | 
						bool dep_chain;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const struct drm_sched_basic_params drm_sched_basic_cases[] = {
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							.description = "A queue of jobs in a single entity",
 | 
				
			||||||
 | 
							.queue_depth = 100,
 | 
				
			||||||
 | 
							.job_us = 1000,
 | 
				
			||||||
 | 
							.num_entities = 1,
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							.description = "A chain of dependent jobs across multiple entities",
 | 
				
			||||||
 | 
							.queue_depth = 100,
 | 
				
			||||||
 | 
							.job_us = 1000,
 | 
				
			||||||
 | 
							.num_entities = 1,
 | 
				
			||||||
 | 
							.dep_chain = true,
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							.description = "Multiple independent job queues",
 | 
				
			||||||
 | 
							.queue_depth = 100,
 | 
				
			||||||
 | 
							.job_us = 1000,
 | 
				
			||||||
 | 
							.num_entities = 4,
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							.description = "Multiple inter-dependent job queues",
 | 
				
			||||||
 | 
							.queue_depth = 100,
 | 
				
			||||||
 | 
							.job_us = 1000,
 | 
				
			||||||
 | 
							.num_entities = 4,
 | 
				
			||||||
 | 
							.dep_chain = true,
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					drm_sched_basic_desc(const struct drm_sched_basic_params *params, char *desc)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						strscpy(desc, params->description, KUNIT_PARAM_DESC_SIZE);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					KUNIT_ARRAY_PARAM(drm_sched_basic, drm_sched_basic_cases, drm_sched_basic_desc);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void drm_sched_basic_test(struct kunit *test)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						const struct drm_sched_basic_params *params = test->param_value;
 | 
				
			||||||
 | 
						struct drm_mock_scheduler *sched = test->priv;
 | 
				
			||||||
 | 
						struct drm_mock_sched_job *job, *prev = NULL;
 | 
				
			||||||
 | 
						struct drm_mock_sched_entity **entity;
 | 
				
			||||||
 | 
						unsigned int i, cur_ent = 0;
 | 
				
			||||||
 | 
						bool done;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						entity = kunit_kcalloc(test, params->num_entities, sizeof(*entity),
 | 
				
			||||||
 | 
								       GFP_KERNEL);
 | 
				
			||||||
 | 
						KUNIT_ASSERT_NOT_NULL(test, entity);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (i = 0; i < params->num_entities; i++)
 | 
				
			||||||
 | 
							entity[i] = drm_mock_sched_entity_new(test,
 | 
				
			||||||
 | 
											      DRM_SCHED_PRIORITY_NORMAL,
 | 
				
			||||||
 | 
											      sched);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (i = 0; i < params->queue_depth; i++) {
 | 
				
			||||||
 | 
							job = drm_mock_sched_job_new(test, entity[cur_ent++]);
 | 
				
			||||||
 | 
							cur_ent %= params->num_entities;
 | 
				
			||||||
 | 
							drm_mock_sched_job_set_duration_us(job, params->job_us);
 | 
				
			||||||
 | 
							if (params->dep_chain && prev)
 | 
				
			||||||
 | 
								drm_sched_job_add_dependency(&job->base,
 | 
				
			||||||
 | 
											     dma_fence_get(&prev->base.s_fence->finished));
 | 
				
			||||||
 | 
							drm_mock_sched_job_submit(job);
 | 
				
			||||||
 | 
							prev = job;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						done = drm_mock_sched_job_wait_finished(job, HZ);
 | 
				
			||||||
 | 
						KUNIT_ASSERT_TRUE(test, done);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (i = 0; i < params->num_entities; i++)
 | 
				
			||||||
 | 
							drm_mock_sched_entity_free(entity[i]);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void drm_sched_basic_entity_cleanup(struct kunit *test)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct drm_mock_sched_job *job, *mid, *prev = NULL;
 | 
				
			||||||
 | 
						struct drm_mock_scheduler *sched = test->priv;
 | 
				
			||||||
 | 
						struct drm_mock_sched_entity *entity[4];
 | 
				
			||||||
 | 
						const unsigned int qd = 100;
 | 
				
			||||||
 | 
						unsigned int i, cur_ent = 0;
 | 
				
			||||||
 | 
						bool done;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/*
 | 
				
			||||||
 | 
						 * Submit a queue of jobs across different entities with an explicit
 | 
				
			||||||
 | 
						 * chain of dependencies between them and trigger entity cleanup while
 | 
				
			||||||
 | 
						 * the queue is still being processed.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (i = 0; i < ARRAY_SIZE(entity); i++)
 | 
				
			||||||
 | 
							entity[i] = drm_mock_sched_entity_new(test,
 | 
				
			||||||
 | 
											      DRM_SCHED_PRIORITY_NORMAL,
 | 
				
			||||||
 | 
											      sched);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (i = 0; i < qd; i++) {
 | 
				
			||||||
 | 
							job = drm_mock_sched_job_new(test, entity[cur_ent++]);
 | 
				
			||||||
 | 
							cur_ent %= ARRAY_SIZE(entity);
 | 
				
			||||||
 | 
							drm_mock_sched_job_set_duration_us(job, 1000);
 | 
				
			||||||
 | 
							if (prev)
 | 
				
			||||||
 | 
								drm_sched_job_add_dependency(&job->base,
 | 
				
			||||||
 | 
											     dma_fence_get(&prev->base.s_fence->finished));
 | 
				
			||||||
 | 
							drm_mock_sched_job_submit(job);
 | 
				
			||||||
 | 
							if (i == qd / 2)
 | 
				
			||||||
 | 
								mid = job;
 | 
				
			||||||
 | 
							prev = job;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						done = drm_mock_sched_job_wait_finished(mid, HZ);
 | 
				
			||||||
 | 
						KUNIT_ASSERT_TRUE(test, done);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Exit with half of the queue still pending to be executed. */
 | 
				
			||||||
 | 
						for (i = 0; i < ARRAY_SIZE(entity); i++)
 | 
				
			||||||
 | 
							drm_mock_sched_entity_free(entity[i]);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static struct kunit_case drm_sched_basic_tests[] = {
 | 
				
			||||||
 | 
						KUNIT_CASE(drm_sched_basic_submit),
 | 
				
			||||||
 | 
						KUNIT_CASE_PARAM(drm_sched_basic_test, drm_sched_basic_gen_params),
 | 
				
			||||||
 | 
						KUNIT_CASE(drm_sched_basic_entity_cleanup),
 | 
				
			||||||
 | 
						{}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static struct kunit_suite drm_sched_basic = {
 | 
				
			||||||
 | 
						.name = "drm_sched_basic_tests",
 | 
				
			||||||
 | 
						.init = drm_sched_basic_init,
 | 
				
			||||||
 | 
						.exit = drm_sched_basic_exit,
 | 
				
			||||||
 | 
						.test_cases = drm_sched_basic_tests,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					kunit_test_suite(drm_sched_basic);
 | 
				
			||||||
		Loading…
	
		Reference in a new issue