Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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

authored by

Tvrtko Ursulin and committed by
Philipp Stanner
5a993507 8e623137

+809
+12
drivers/gpu/drm/Kconfig.debug
··· 100 100 101 101 If in doubt, say "N". 102 102 103 + config DRM_SCHED_KUNIT_TEST 104 + tristate "KUnit tests for the DRM scheduler" if !KUNIT_ALL_TESTS 105 + select DRM_SCHED 106 + depends on DRM && KUNIT 107 + default KUNIT_ALL_TESTS 108 + help 109 + Choose this option to build unit tests for the DRM scheduler. 110 + 111 + Recommended for driver developers only. 112 + 113 + If in doubt, say "N". 114 + 103 115 config DRM_EXPORT_FOR_TESTS 104 116 bool
+12
drivers/gpu/drm/scheduler/.kunitconfig
··· 1 + CONFIG_KUNIT=y 2 + CONFIG_DRM=y 3 + CONFIG_DRM_SCHED_KUNIT_TEST=y 4 + CONFIG_EXPERT=y 5 + CONFIG_DEBUG_SPINLOCK=y 6 + CONFIG_DEBUG_MUTEXES=y 7 + CONFIG_DEBUG_ATOMIC_SLEEP=y 8 + CONFIG_LOCK_DEBUGGING_SUPPORT=y 9 + CONFIG_PROVE_LOCKING=y 10 + CONFIG_LOCKDEP=y 11 + CONFIG_DEBUG_LOCKDEP=y 12 + CONFIG_DEBUG_LIST=y
+2
drivers/gpu/drm/scheduler/Makefile
··· 23 23 gpu-sched-y := sched_main.o sched_fence.o sched_entity.o 24 24 25 25 obj-$(CONFIG_DRM_SCHED) += gpu-sched.o 26 + 27 + obj-$(CONFIG_DRM_SCHED_KUNIT_TEST) += tests/
+7
drivers/gpu/drm/scheduler/tests/Makefile
··· 1 + # SPDX-License-Identifier: GPL-2.0 2 + 3 + drm-sched-tests-y := \ 4 + mock_scheduler.o \ 5 + tests_basic.o 6 + 7 + obj-$(CONFIG_DRM_SCHED_KUNIT_TEST) += drm-sched-tests.o
+354
drivers/gpu/drm/scheduler/tests/mock_scheduler.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* Copyright (c) 2025 Valve Corporation */ 3 + 4 + #include "sched_tests.h" 5 + 6 + /* 7 + * Here we implement the mock "GPU" (or the scheduler backend) which is used by 8 + * the DRM scheduler unit tests in order to exercise the core functionality. 9 + * 10 + * Test cases are implemented in a separate file. 11 + */ 12 + 13 + /** 14 + * drm_mock_sched_entity_new - Create a new mock scheduler entity 15 + * 16 + * @test: KUnit test owning the entity 17 + * @priority: Scheduling priority 18 + * @sched: Mock scheduler on which the entity can be scheduled 19 + * 20 + * Returns: New mock scheduler entity with allocation managed by the test 21 + */ 22 + struct drm_mock_sched_entity * 23 + drm_mock_sched_entity_new(struct kunit *test, 24 + enum drm_sched_priority priority, 25 + struct drm_mock_scheduler *sched) 26 + { 27 + struct drm_mock_sched_entity *entity; 28 + struct drm_gpu_scheduler *drm_sched; 29 + int ret; 30 + 31 + entity = kunit_kzalloc(test, sizeof(*entity), GFP_KERNEL); 32 + KUNIT_ASSERT_NOT_NULL(test, entity); 33 + 34 + drm_sched = &sched->base; 35 + ret = drm_sched_entity_init(&entity->base, 36 + priority, 37 + &drm_sched, 1, 38 + NULL); 39 + KUNIT_ASSERT_EQ(test, ret, 0); 40 + 41 + entity->test = test; 42 + 43 + return entity; 44 + } 45 + 46 + /** 47 + * drm_mock_sched_entity_free - Destroys a mock scheduler entity 48 + * 49 + * @entity: Entity to destroy 50 + * 51 + * To be used from the test cases once done with the entity. 52 + */ 53 + void drm_mock_sched_entity_free(struct drm_mock_sched_entity *entity) 54 + { 55 + drm_sched_entity_destroy(&entity->base); 56 + } 57 + 58 + static void drm_mock_sched_job_complete(struct drm_mock_sched_job *job) 59 + { 60 + struct drm_mock_scheduler *sched = 61 + drm_sched_to_mock_sched(job->base.sched); 62 + 63 + lockdep_assert_held(&sched->lock); 64 + 65 + job->flags |= DRM_MOCK_SCHED_JOB_DONE; 66 + list_move_tail(&job->link, &sched->done_list); 67 + dma_fence_signal(&job->hw_fence); 68 + complete(&job->done); 69 + } 70 + 71 + static enum hrtimer_restart 72 + drm_mock_sched_job_signal_timer(struct hrtimer *hrtimer) 73 + { 74 + struct drm_mock_sched_job *job = 75 + container_of(hrtimer, typeof(*job), timer); 76 + struct drm_mock_scheduler *sched = 77 + drm_sched_to_mock_sched(job->base.sched); 78 + struct drm_mock_sched_job *next; 79 + ktime_t now = ktime_get(); 80 + unsigned long flags; 81 + LIST_HEAD(signal); 82 + 83 + spin_lock_irqsave(&sched->lock, flags); 84 + list_for_each_entry_safe(job, next, &sched->job_list, link) { 85 + if (!job->duration_us) 86 + break; 87 + 88 + if (ktime_before(now, job->finish_at)) 89 + break; 90 + 91 + sched->hw_timeline.cur_seqno = job->hw_fence.seqno; 92 + drm_mock_sched_job_complete(job); 93 + } 94 + spin_unlock_irqrestore(&sched->lock, flags); 95 + 96 + return HRTIMER_NORESTART; 97 + } 98 + 99 + /** 100 + * drm_mock_sched_job_new - Create a new mock scheduler job 101 + * 102 + * @test: KUnit test owning the job 103 + * @entity: Scheduler entity of the job 104 + * 105 + * Returns: New mock scheduler job with allocation managed by the test 106 + */ 107 + struct drm_mock_sched_job * 108 + drm_mock_sched_job_new(struct kunit *test, 109 + struct drm_mock_sched_entity *entity) 110 + { 111 + struct drm_mock_sched_job *job; 112 + int ret; 113 + 114 + job = kunit_kzalloc(test, sizeof(*job), GFP_KERNEL); 115 + KUNIT_ASSERT_NOT_NULL(test, job); 116 + 117 + ret = drm_sched_job_init(&job->base, 118 + &entity->base, 119 + 1, 120 + NULL); 121 + KUNIT_ASSERT_EQ(test, ret, 0); 122 + 123 + job->test = test; 124 + 125 + init_completion(&job->done); 126 + spin_lock_init(&job->lock); 127 + INIT_LIST_HEAD(&job->link); 128 + hrtimer_init(&job->timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS); 129 + job->timer.function = drm_mock_sched_job_signal_timer; 130 + 131 + return job; 132 + } 133 + 134 + static const char *drm_mock_sched_hw_fence_driver_name(struct dma_fence *fence) 135 + { 136 + return "drm_mock_sched"; 137 + } 138 + 139 + static const char * 140 + drm_mock_sched_hw_fence_timeline_name(struct dma_fence *fence) 141 + { 142 + struct drm_mock_sched_job *job = 143 + container_of(fence, typeof(*job), hw_fence); 144 + 145 + return (const char *)job->base.sched->name; 146 + } 147 + 148 + static void drm_mock_sched_hw_fence_release(struct dma_fence *fence) 149 + { 150 + struct drm_mock_sched_job *job = 151 + container_of(fence, typeof(*job), hw_fence); 152 + 153 + hrtimer_cancel(&job->timer); 154 + 155 + /* Containing job is freed by the kunit framework */ 156 + } 157 + 158 + static const struct dma_fence_ops drm_mock_sched_hw_fence_ops = { 159 + .get_driver_name = drm_mock_sched_hw_fence_driver_name, 160 + .get_timeline_name = drm_mock_sched_hw_fence_timeline_name, 161 + .release = drm_mock_sched_hw_fence_release, 162 + }; 163 + 164 + static struct dma_fence *mock_sched_run_job(struct drm_sched_job *sched_job) 165 + { 166 + struct drm_mock_scheduler *sched = 167 + drm_sched_to_mock_sched(sched_job->sched); 168 + struct drm_mock_sched_job *job = drm_sched_job_to_mock_job(sched_job); 169 + 170 + dma_fence_init(&job->hw_fence, 171 + &drm_mock_sched_hw_fence_ops, 172 + &job->lock, 173 + sched->hw_timeline.context, 174 + atomic_inc_return(&sched->hw_timeline.next_seqno)); 175 + 176 + dma_fence_get(&job->hw_fence); /* Reference for the job_list */ 177 + 178 + spin_lock_irq(&sched->lock); 179 + if (job->duration_us) { 180 + ktime_t prev_finish_at = 0; 181 + 182 + if (!list_empty(&sched->job_list)) { 183 + struct drm_mock_sched_job *prev = 184 + list_last_entry(&sched->job_list, typeof(*prev), 185 + link); 186 + 187 + prev_finish_at = prev->finish_at; 188 + } 189 + 190 + if (!prev_finish_at) 191 + prev_finish_at = ktime_get(); 192 + 193 + job->finish_at = ktime_add_us(prev_finish_at, job->duration_us); 194 + } 195 + list_add_tail(&job->link, &sched->job_list); 196 + if (job->finish_at) 197 + hrtimer_start(&job->timer, job->finish_at, HRTIMER_MODE_ABS); 198 + spin_unlock_irq(&sched->lock); 199 + 200 + return &job->hw_fence; 201 + } 202 + 203 + static enum drm_gpu_sched_stat 204 + mock_sched_timedout_job(struct drm_sched_job *sched_job) 205 + { 206 + return DRM_GPU_SCHED_STAT_ENODEV; 207 + } 208 + 209 + static void mock_sched_free_job(struct drm_sched_job *sched_job) 210 + { 211 + struct drm_mock_scheduler *sched = 212 + drm_sched_to_mock_sched(sched_job->sched); 213 + struct drm_mock_sched_job *job = drm_sched_job_to_mock_job(sched_job); 214 + unsigned long flags; 215 + 216 + /* Remove from the scheduler done list. */ 217 + spin_lock_irqsave(&sched->lock, flags); 218 + list_del(&job->link); 219 + spin_unlock_irqrestore(&sched->lock, flags); 220 + dma_fence_put(&job->hw_fence); 221 + 222 + drm_sched_job_cleanup(sched_job); 223 + 224 + /* Mock job itself is freed by the kunit framework. */ 225 + } 226 + 227 + static const struct drm_sched_backend_ops drm_mock_scheduler_ops = { 228 + .run_job = mock_sched_run_job, 229 + .timedout_job = mock_sched_timedout_job, 230 + .free_job = mock_sched_free_job 231 + }; 232 + 233 + /** 234 + * drm_mock_sched_new - Create a new mock scheduler 235 + * 236 + * @test: KUnit test owning the job 237 + * 238 + * Returns: New mock scheduler with allocation managed by the test 239 + */ 240 + struct drm_mock_scheduler *drm_mock_sched_new(struct kunit *test) 241 + { 242 + struct drm_sched_init_args args = { 243 + .ops = &drm_mock_scheduler_ops, 244 + .num_rqs = DRM_SCHED_PRIORITY_COUNT, 245 + .credit_limit = U32_MAX, 246 + .hang_limit = 1, 247 + .timeout = MAX_SCHEDULE_TIMEOUT, 248 + .name = "drm-mock-scheduler", 249 + }; 250 + struct drm_mock_scheduler *sched; 251 + int ret; 252 + 253 + sched = kunit_kzalloc(test, sizeof(*sched), GFP_KERNEL); 254 + KUNIT_ASSERT_NOT_NULL(test, sched); 255 + 256 + ret = drm_sched_init(&sched->base, &args); 257 + KUNIT_ASSERT_EQ(test, ret, 0); 258 + 259 + sched->test = test; 260 + sched->hw_timeline.context = dma_fence_context_alloc(1); 261 + atomic_set(&sched->hw_timeline.next_seqno, 0); 262 + INIT_LIST_HEAD(&sched->job_list); 263 + INIT_LIST_HEAD(&sched->done_list); 264 + spin_lock_init(&sched->lock); 265 + 266 + return sched; 267 + } 268 + 269 + /** 270 + * drm_mock_sched_fini - Destroys a mock scheduler 271 + * 272 + * @sched: Scheduler to destroy 273 + * 274 + * To be used from the test cases once done with the scheduler. 275 + */ 276 + void drm_mock_sched_fini(struct drm_mock_scheduler *sched) 277 + { 278 + struct drm_mock_sched_job *job, *next; 279 + unsigned long flags; 280 + LIST_HEAD(list); 281 + 282 + drm_sched_wqueue_stop(&sched->base); 283 + 284 + /* Force complete all unfinished jobs. */ 285 + spin_lock_irqsave(&sched->lock, flags); 286 + list_for_each_entry_safe(job, next, &sched->job_list, link) 287 + list_move_tail(&job->link, &list); 288 + spin_unlock_irqrestore(&sched->lock, flags); 289 + 290 + list_for_each_entry(job, &list, link) 291 + hrtimer_cancel(&job->timer); 292 + 293 + spin_lock_irqsave(&sched->lock, flags); 294 + list_for_each_entry_safe(job, next, &list, link) 295 + drm_mock_sched_job_complete(job); 296 + spin_unlock_irqrestore(&sched->lock, flags); 297 + 298 + /* 299 + * Free completed jobs and jobs not yet processed by the DRM scheduler 300 + * free worker. 301 + */ 302 + spin_lock_irqsave(&sched->lock, flags); 303 + list_for_each_entry_safe(job, next, &sched->done_list, link) 304 + list_move_tail(&job->link, &list); 305 + spin_unlock_irqrestore(&sched->lock, flags); 306 + 307 + list_for_each_entry_safe(job, next, &list, link) 308 + mock_sched_free_job(&job->base); 309 + 310 + drm_sched_fini(&sched->base); 311 + } 312 + 313 + /** 314 + * drm_mock_sched_advance - Advances the mock scheduler timeline 315 + * 316 + * @sched: Scheduler timeline to advance 317 + * @num: By how many jobs to advance 318 + * 319 + * Advancing the scheduler timeline by a number of seqnos will trigger 320 + * signalling of the hardware fences and unlinking the jobs from the internal 321 + * scheduler tracking. 322 + * 323 + * This can be used from test cases which want complete control of the simulated 324 + * job execution timing. For example submitting one job with no set duration 325 + * would never complete it before test cases advances the timeline by one. 326 + */ 327 + unsigned int drm_mock_sched_advance(struct drm_mock_scheduler *sched, 328 + unsigned int num) 329 + { 330 + struct drm_mock_sched_job *job, *next; 331 + unsigned int found = 0; 332 + unsigned long flags; 333 + LIST_HEAD(signal); 334 + 335 + spin_lock_irqsave(&sched->lock, flags); 336 + if (WARN_ON_ONCE(sched->hw_timeline.cur_seqno + num < 337 + sched->hw_timeline.cur_seqno)) 338 + goto unlock; 339 + sched->hw_timeline.cur_seqno += num; 340 + list_for_each_entry_safe(job, next, &sched->job_list, link) { 341 + if (sched->hw_timeline.cur_seqno < job->hw_fence.seqno) 342 + break; 343 + 344 + drm_mock_sched_job_complete(job); 345 + found++; 346 + } 347 + unlock: 348 + spin_unlock_irqrestore(&sched->lock, flags); 349 + 350 + return found; 351 + } 352 + 353 + MODULE_DESCRIPTION("DRM mock scheduler and tests"); 354 + MODULE_LICENSE("GPL");
+224
drivers/gpu/drm/scheduler/tests/sched_tests.h
··· 1 + /* SPDX-License-Identifier: GPL-2.0 */ 2 + /* Copyright (c) 2025 Valve Corporation */ 3 + 4 + #ifndef _SCHED_TESTS_H_ 5 + #define _SCHED_TESTS_H_ 6 + 7 + #include <kunit/test.h> 8 + #include <linux/atomic.h> 9 + #include <linux/completion.h> 10 + #include <linux/dma-fence.h> 11 + #include <linux/hrtimer.h> 12 + #include <linux/ktime.h> 13 + #include <linux/list.h> 14 + #include <linux/atomic.h> 15 + #include <linux/mutex.h> 16 + #include <linux/types.h> 17 + 18 + #include <drm/gpu_scheduler.h> 19 + 20 + /* 21 + * DOC: Mock DRM scheduler data structures 22 + * 23 + * drm_mock_* data structures are used to implement a mock "GPU". 24 + * 25 + * They subclass the core DRM scheduler objects and add their data on top, which 26 + * enables tracking the submitted jobs and simulating their execution with the 27 + * attributes as specified by the test case. 28 + */ 29 + 30 + /** 31 + * struct drm_mock_scheduler - implements a trivial mock GPU execution engine 32 + * 33 + * @base: DRM scheduler base class 34 + * @test: Backpointer to owning the kunit test case 35 + * @lock: Lock to protect the simulated @hw_timeline, @job_list and @done_list 36 + * @job_list: List of jobs submitted to the mock GPU 37 + * @done_list: List of jobs completed by the mock GPU 38 + * @hw_timeline: Simulated hardware timeline has a @context, @next_seqno and 39 + * @cur_seqno for implementing a struct dma_fence signaling the 40 + * simulated job completion. 41 + * 42 + * Trivial mock GPU execution engine tracks submitted jobs and enables 43 + * completing them strictly in submission order. 44 + */ 45 + struct drm_mock_scheduler { 46 + struct drm_gpu_scheduler base; 47 + 48 + struct kunit *test; 49 + 50 + spinlock_t lock; 51 + struct list_head job_list; 52 + struct list_head done_list; 53 + 54 + struct { 55 + u64 context; 56 + atomic_t next_seqno; 57 + unsigned int cur_seqno; 58 + } hw_timeline; 59 + }; 60 + 61 + /** 62 + * struct drm_mock_sched_entity - implements a mock GPU sched entity 63 + * 64 + * @base: DRM scheduler entity base class 65 + * @test: Backpointer to owning the kunit test case 66 + * 67 + * Mock GPU sched entity is used by the test cases to submit jobs to the mock 68 + * scheduler. 69 + */ 70 + struct drm_mock_sched_entity { 71 + struct drm_sched_entity base; 72 + 73 + struct kunit *test; 74 + }; 75 + 76 + /** 77 + * struct drm_mock_sched_job - implements a mock GPU job 78 + * 79 + * @base: DRM sched job base class 80 + * @done: Completion signaling job completion. 81 + * @flags: Flags designating job state. 82 + * @link: List head element used by job tracking by the drm_mock_scheduler 83 + * @timer: Timer used for simulating job execution duration 84 + * @duration_us: Simulated job duration in micro seconds, or zero if in manual 85 + * timeline advance mode 86 + * @finish_at: Absolute time when the jobs with set duration will complete 87 + * @lock: Lock used for @hw_fence 88 + * @hw_fence: Fence returned to DRM scheduler as the hardware fence 89 + * @test: Backpointer to owning the kunit test case 90 + * 91 + * Mock GPU sched job is used by the test cases to submit jobs to the mock 92 + * scheduler. 93 + */ 94 + struct drm_mock_sched_job { 95 + struct drm_sched_job base; 96 + 97 + struct completion done; 98 + 99 + #define DRM_MOCK_SCHED_JOB_DONE 0x1 100 + unsigned long flags; 101 + 102 + struct list_head link; 103 + struct hrtimer timer; 104 + 105 + unsigned int duration_us; 106 + ktime_t finish_at; 107 + 108 + spinlock_t lock; 109 + struct dma_fence hw_fence; 110 + 111 + struct kunit *test; 112 + }; 113 + 114 + static inline struct drm_mock_scheduler * 115 + drm_sched_to_mock_sched(struct drm_gpu_scheduler *sched) 116 + { 117 + return container_of(sched, struct drm_mock_scheduler, base); 118 + }; 119 + 120 + static inline struct drm_mock_sched_entity * 121 + drm_sched_entity_to_mock_entity(struct drm_sched_entity *sched_entity) 122 + { 123 + return container_of(sched_entity, struct drm_mock_sched_entity, base); 124 + }; 125 + 126 + static inline struct drm_mock_sched_job * 127 + drm_sched_job_to_mock_job(struct drm_sched_job *sched_job) 128 + { 129 + return container_of(sched_job, struct drm_mock_sched_job, base); 130 + }; 131 + 132 + struct drm_mock_scheduler *drm_mock_sched_new(struct kunit *test); 133 + void drm_mock_sched_fini(struct drm_mock_scheduler *sched); 134 + unsigned int drm_mock_sched_advance(struct drm_mock_scheduler *sched, 135 + unsigned int num); 136 + 137 + struct drm_mock_sched_entity * 138 + drm_mock_sched_entity_new(struct kunit *test, 139 + enum drm_sched_priority priority, 140 + struct drm_mock_scheduler *sched); 141 + void drm_mock_sched_entity_free(struct drm_mock_sched_entity *entity); 142 + 143 + struct drm_mock_sched_job * 144 + drm_mock_sched_job_new(struct kunit *test, 145 + struct drm_mock_sched_entity *entity); 146 + 147 + /** 148 + * drm_mock_sched_job_submit - Arm and submit a job in one go 149 + * 150 + * @job: Job to arm and submit 151 + */ 152 + static inline void drm_mock_sched_job_submit(struct drm_mock_sched_job *job) 153 + { 154 + drm_sched_job_arm(&job->base); 155 + drm_sched_entity_push_job(&job->base); 156 + } 157 + 158 + /** 159 + * drm_mock_sched_job_set_duration_us - Set a job duration 160 + * 161 + * @job: Job to set the duration for 162 + * @duration_us: Duration in micro seconds 163 + * 164 + * Jobs with duration set will be automatically completed by the mock scheduler 165 + * as the timeline progresses, unless a job without a set duration is 166 + * encountered in the timelime in which case calling drm_mock_sched_advance() 167 + * will be required to bump the timeline. 168 + */ 169 + static inline void 170 + drm_mock_sched_job_set_duration_us(struct drm_mock_sched_job *job, 171 + unsigned int duration_us) 172 + { 173 + job->duration_us = duration_us; 174 + } 175 + 176 + /** 177 + * drm_mock_sched_job_is_finished - Check if a job is finished 178 + * 179 + * @job: Job to check 180 + * 181 + * Returns: true if finished 182 + */ 183 + static inline bool 184 + drm_mock_sched_job_is_finished(struct drm_mock_sched_job *job) 185 + { 186 + return job->flags & DRM_MOCK_SCHED_JOB_DONE; 187 + } 188 + 189 + /** 190 + * drm_mock_sched_job_wait_finished - Wait until a job is finished 191 + * 192 + * @job: Job to wait for 193 + * @timeout: Wait time in jiffies 194 + * 195 + * Returns: true if finished within the timeout provided, otherwise false 196 + */ 197 + static inline bool 198 + drm_mock_sched_job_wait_finished(struct drm_mock_sched_job *job, long timeout) 199 + { 200 + if (job->flags & DRM_MOCK_SCHED_JOB_DONE) 201 + return true; 202 + 203 + return wait_for_completion_timeout(&job->done, timeout) != 0; 204 + } 205 + 206 + /** 207 + * drm_mock_sched_job_wait_scheduled - Wait until a job is scheduled 208 + * 209 + * @job: Job to wait for 210 + * @timeout: Wait time in jiffies 211 + * 212 + * Returns: true if scheduled within the timeout provided, otherwise false 213 + */ 214 + static inline bool 215 + drm_mock_sched_job_wait_scheduled(struct drm_mock_sched_job *job, long timeout) 216 + { 217 + KUNIT_ASSERT_EQ(job->test, job->flags & DRM_MOCK_SCHED_JOB_DONE, 0); 218 + 219 + return dma_fence_wait_timeout(&job->base.s_fence->scheduled, 220 + false, 221 + timeout) != 0; 222 + } 223 + 224 + #endif
+198
drivers/gpu/drm/scheduler/tests/tests_basic.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* Copyright (c) 2025 Valve Corporation */ 3 + 4 + #include "sched_tests.h" 5 + 6 + /* 7 + * DRM scheduler basic tests should check the basic functional correctness of 8 + * the scheduler, including some very light smoke testing. More targeted tests, 9 + * for example focusing on testing specific bugs and other more complicated test 10 + * scenarios, should be implemented in separate source units. 11 + */ 12 + 13 + static int drm_sched_basic_init(struct kunit *test) 14 + { 15 + test->priv = drm_mock_sched_new(test); 16 + 17 + return 0; 18 + } 19 + 20 + static void drm_sched_basic_exit(struct kunit *test) 21 + { 22 + struct drm_mock_scheduler *sched = test->priv; 23 + 24 + drm_mock_sched_fini(sched); 25 + } 26 + 27 + static void drm_sched_basic_submit(struct kunit *test) 28 + { 29 + struct drm_mock_scheduler *sched = test->priv; 30 + struct drm_mock_sched_entity *entity; 31 + struct drm_mock_sched_job *job; 32 + unsigned int i; 33 + bool done; 34 + 35 + /* 36 + * Submit one job to the scheduler and verify that it gets scheduled 37 + * and completed only when the mock hw backend processes it. 38 + */ 39 + 40 + entity = drm_mock_sched_entity_new(test, 41 + DRM_SCHED_PRIORITY_NORMAL, 42 + sched); 43 + job = drm_mock_sched_job_new(test, entity); 44 + 45 + drm_mock_sched_job_submit(job); 46 + 47 + done = drm_mock_sched_job_wait_scheduled(job, HZ); 48 + KUNIT_ASSERT_TRUE(test, done); 49 + 50 + done = drm_mock_sched_job_wait_finished(job, HZ / 2); 51 + KUNIT_ASSERT_FALSE(test, done); 52 + 53 + i = drm_mock_sched_advance(sched, 1); 54 + KUNIT_ASSERT_EQ(test, i, 1); 55 + 56 + done = drm_mock_sched_job_wait_finished(job, HZ); 57 + KUNIT_ASSERT_TRUE(test, done); 58 + 59 + drm_mock_sched_entity_free(entity); 60 + } 61 + 62 + struct drm_sched_basic_params { 63 + const char *description; 64 + unsigned int queue_depth; 65 + unsigned int num_entities; 66 + unsigned int job_us; 67 + bool dep_chain; 68 + }; 69 + 70 + static const struct drm_sched_basic_params drm_sched_basic_cases[] = { 71 + { 72 + .description = "A queue of jobs in a single entity", 73 + .queue_depth = 100, 74 + .job_us = 1000, 75 + .num_entities = 1, 76 + }, 77 + { 78 + .description = "A chain of dependent jobs across multiple entities", 79 + .queue_depth = 100, 80 + .job_us = 1000, 81 + .num_entities = 1, 82 + .dep_chain = true, 83 + }, 84 + { 85 + .description = "Multiple independent job queues", 86 + .queue_depth = 100, 87 + .job_us = 1000, 88 + .num_entities = 4, 89 + }, 90 + { 91 + .description = "Multiple inter-dependent job queues", 92 + .queue_depth = 100, 93 + .job_us = 1000, 94 + .num_entities = 4, 95 + .dep_chain = true, 96 + }, 97 + }; 98 + 99 + static void 100 + drm_sched_basic_desc(const struct drm_sched_basic_params *params, char *desc) 101 + { 102 + strscpy(desc, params->description, KUNIT_PARAM_DESC_SIZE); 103 + } 104 + 105 + KUNIT_ARRAY_PARAM(drm_sched_basic, drm_sched_basic_cases, drm_sched_basic_desc); 106 + 107 + static void drm_sched_basic_test(struct kunit *test) 108 + { 109 + const struct drm_sched_basic_params *params = test->param_value; 110 + struct drm_mock_scheduler *sched = test->priv; 111 + struct drm_mock_sched_job *job, *prev = NULL; 112 + struct drm_mock_sched_entity **entity; 113 + unsigned int i, cur_ent = 0; 114 + bool done; 115 + 116 + entity = kunit_kcalloc(test, params->num_entities, sizeof(*entity), 117 + GFP_KERNEL); 118 + KUNIT_ASSERT_NOT_NULL(test, entity); 119 + 120 + for (i = 0; i < params->num_entities; i++) 121 + entity[i] = drm_mock_sched_entity_new(test, 122 + DRM_SCHED_PRIORITY_NORMAL, 123 + sched); 124 + 125 + for (i = 0; i < params->queue_depth; i++) { 126 + job = drm_mock_sched_job_new(test, entity[cur_ent++]); 127 + cur_ent %= params->num_entities; 128 + drm_mock_sched_job_set_duration_us(job, params->job_us); 129 + if (params->dep_chain && prev) 130 + drm_sched_job_add_dependency(&job->base, 131 + dma_fence_get(&prev->base.s_fence->finished)); 132 + drm_mock_sched_job_submit(job); 133 + prev = job; 134 + } 135 + 136 + done = drm_mock_sched_job_wait_finished(job, HZ); 137 + KUNIT_ASSERT_TRUE(test, done); 138 + 139 + for (i = 0; i < params->num_entities; i++) 140 + drm_mock_sched_entity_free(entity[i]); 141 + } 142 + 143 + static void drm_sched_basic_entity_cleanup(struct kunit *test) 144 + { 145 + struct drm_mock_sched_job *job, *mid, *prev = NULL; 146 + struct drm_mock_scheduler *sched = test->priv; 147 + struct drm_mock_sched_entity *entity[4]; 148 + const unsigned int qd = 100; 149 + unsigned int i, cur_ent = 0; 150 + bool done; 151 + 152 + /* 153 + * Submit a queue of jobs across different entities with an explicit 154 + * chain of dependencies between them and trigger entity cleanup while 155 + * the queue is still being processed. 156 + */ 157 + 158 + for (i = 0; i < ARRAY_SIZE(entity); i++) 159 + entity[i] = drm_mock_sched_entity_new(test, 160 + DRM_SCHED_PRIORITY_NORMAL, 161 + sched); 162 + 163 + for (i = 0; i < qd; i++) { 164 + job = drm_mock_sched_job_new(test, entity[cur_ent++]); 165 + cur_ent %= ARRAY_SIZE(entity); 166 + drm_mock_sched_job_set_duration_us(job, 1000); 167 + if (prev) 168 + drm_sched_job_add_dependency(&job->base, 169 + dma_fence_get(&prev->base.s_fence->finished)); 170 + drm_mock_sched_job_submit(job); 171 + if (i == qd / 2) 172 + mid = job; 173 + prev = job; 174 + } 175 + 176 + done = drm_mock_sched_job_wait_finished(mid, HZ); 177 + KUNIT_ASSERT_TRUE(test, done); 178 + 179 + /* Exit with half of the queue still pending to be executed. */ 180 + for (i = 0; i < ARRAY_SIZE(entity); i++) 181 + drm_mock_sched_entity_free(entity[i]); 182 + } 183 + 184 + static struct kunit_case drm_sched_basic_tests[] = { 185 + KUNIT_CASE(drm_sched_basic_submit), 186 + KUNIT_CASE_PARAM(drm_sched_basic_test, drm_sched_basic_gen_params), 187 + KUNIT_CASE(drm_sched_basic_entity_cleanup), 188 + {} 189 + }; 190 + 191 + static struct kunit_suite drm_sched_basic = { 192 + .name = "drm_sched_basic_tests", 193 + .init = drm_sched_basic_init, 194 + .exit = drm_sched_basic_exit, 195 + .test_cases = drm_sched_basic_tests, 196 + }; 197 + 198 + kunit_test_suite(drm_sched_basic);