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_locked(&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 1); 122 KUNIT_ASSERT_EQ(test, ret, 0); 123 124 job->test = test; 125 126 init_completion(&job->done); 127 INIT_LIST_HEAD(&job->link); 128 hrtimer_setup(&job->timer, drm_mock_sched_job_signal_timer, 129 CLOCK_MONOTONIC, HRTIMER_MODE_ABS); 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 &sched->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 /* 204 * Normally, drivers would take appropriate measures in this callback, such as 205 * killing the entity the faulty job is associated with, resetting the hardware 206 * and / or resubmitting non-faulty jobs. 207 * 208 * For the mock scheduler, there are no hardware rings to be resetted nor jobs 209 * to be resubmitted. Thus, this function merely ensures that 210 * a) timedout fences get signaled properly and removed from the pending list 211 * b) the mock scheduler framework gets informed about the timeout via a flag 212 * c) The drm_sched_job, not longer needed, gets freed 213 */ 214 static enum drm_gpu_sched_stat 215 mock_sched_timedout_job(struct drm_sched_job *sched_job) 216 { 217 struct drm_mock_scheduler *sched = drm_sched_to_mock_sched(sched_job->sched); 218 struct drm_mock_sched_job *job = drm_sched_job_to_mock_job(sched_job); 219 unsigned long flags; 220 221 spin_lock_irqsave(&sched->lock, flags); 222 if (!dma_fence_is_signaled_locked(&job->hw_fence)) { 223 list_del(&job->link); 224 job->flags |= DRM_MOCK_SCHED_JOB_TIMEDOUT; 225 dma_fence_set_error(&job->hw_fence, -ETIMEDOUT); 226 dma_fence_signal_locked(&job->hw_fence); 227 } 228 spin_unlock_irqrestore(&sched->lock, flags); 229 230 dma_fence_put(&job->hw_fence); 231 drm_sched_job_cleanup(sched_job); 232 /* Mock job itself is freed by the kunit framework. */ 233 234 return DRM_GPU_SCHED_STAT_NOMINAL; 235 } 236 237 static void mock_sched_free_job(struct drm_sched_job *sched_job) 238 { 239 struct drm_mock_scheduler *sched = 240 drm_sched_to_mock_sched(sched_job->sched); 241 struct drm_mock_sched_job *job = drm_sched_job_to_mock_job(sched_job); 242 unsigned long flags; 243 244 /* Remove from the scheduler done list. */ 245 spin_lock_irqsave(&sched->lock, flags); 246 list_del(&job->link); 247 spin_unlock_irqrestore(&sched->lock, flags); 248 dma_fence_put(&job->hw_fence); 249 250 drm_sched_job_cleanup(sched_job); 251 252 /* Mock job itself is freed by the kunit framework. */ 253 } 254 255 static const struct drm_sched_backend_ops drm_mock_scheduler_ops = { 256 .run_job = mock_sched_run_job, 257 .timedout_job = mock_sched_timedout_job, 258 .free_job = mock_sched_free_job 259 }; 260 261 /** 262 * drm_mock_sched_new - Create a new mock scheduler 263 * 264 * @test: KUnit test owning the job 265 * @timeout: Job timeout to set 266 * 267 * Returns: New mock scheduler with allocation managed by the test 268 */ 269 struct drm_mock_scheduler *drm_mock_sched_new(struct kunit *test, long timeout) 270 { 271 struct drm_sched_init_args args = { 272 .ops = &drm_mock_scheduler_ops, 273 .num_rqs = DRM_SCHED_PRIORITY_COUNT, 274 .credit_limit = U32_MAX, 275 .hang_limit = 1, 276 .timeout = timeout, 277 .name = "drm-mock-scheduler", 278 }; 279 struct drm_mock_scheduler *sched; 280 int ret; 281 282 sched = kunit_kzalloc(test, sizeof(*sched), GFP_KERNEL); 283 KUNIT_ASSERT_NOT_NULL(test, sched); 284 285 ret = drm_sched_init(&sched->base, &args); 286 KUNIT_ASSERT_EQ(test, ret, 0); 287 288 sched->test = test; 289 sched->hw_timeline.context = dma_fence_context_alloc(1); 290 atomic_set(&sched->hw_timeline.next_seqno, 0); 291 INIT_LIST_HEAD(&sched->job_list); 292 INIT_LIST_HEAD(&sched->done_list); 293 spin_lock_init(&sched->lock); 294 295 return sched; 296 } 297 298 /** 299 * drm_mock_sched_fini - Destroys a mock scheduler 300 * 301 * @sched: Scheduler to destroy 302 * 303 * To be used from the test cases once done with the scheduler. 304 */ 305 void drm_mock_sched_fini(struct drm_mock_scheduler *sched) 306 { 307 struct drm_mock_sched_job *job, *next; 308 unsigned long flags; 309 LIST_HEAD(list); 310 311 drm_sched_wqueue_stop(&sched->base); 312 313 /* Force complete all unfinished jobs. */ 314 spin_lock_irqsave(&sched->lock, flags); 315 list_for_each_entry_safe(job, next, &sched->job_list, link) 316 list_move_tail(&job->link, &list); 317 spin_unlock_irqrestore(&sched->lock, flags); 318 319 list_for_each_entry(job, &list, link) 320 hrtimer_cancel(&job->timer); 321 322 spin_lock_irqsave(&sched->lock, flags); 323 list_for_each_entry_safe(job, next, &list, link) 324 drm_mock_sched_job_complete(job); 325 spin_unlock_irqrestore(&sched->lock, flags); 326 327 /* 328 * Free completed jobs and jobs not yet processed by the DRM scheduler 329 * free worker. 330 */ 331 spin_lock_irqsave(&sched->lock, flags); 332 list_for_each_entry_safe(job, next, &sched->done_list, link) 333 list_move_tail(&job->link, &list); 334 spin_unlock_irqrestore(&sched->lock, flags); 335 336 list_for_each_entry_safe(job, next, &list, link) 337 mock_sched_free_job(&job->base); 338 339 drm_sched_fini(&sched->base); 340 } 341 342 /** 343 * drm_mock_sched_advance - Advances the mock scheduler timeline 344 * 345 * @sched: Scheduler timeline to advance 346 * @num: By how many jobs to advance 347 * 348 * Advancing the scheduler timeline by a number of seqnos will trigger 349 * signalling of the hardware fences and unlinking the jobs from the internal 350 * scheduler tracking. 351 * 352 * This can be used from test cases which want complete control of the simulated 353 * job execution timing. For example submitting one job with no set duration 354 * would never complete it before test cases advances the timeline by one. 355 */ 356 unsigned int drm_mock_sched_advance(struct drm_mock_scheduler *sched, 357 unsigned int num) 358 { 359 struct drm_mock_sched_job *job, *next; 360 unsigned int found = 0; 361 unsigned long flags; 362 LIST_HEAD(signal); 363 364 spin_lock_irqsave(&sched->lock, flags); 365 if (WARN_ON_ONCE(sched->hw_timeline.cur_seqno + num < 366 sched->hw_timeline.cur_seqno)) 367 goto unlock; 368 sched->hw_timeline.cur_seqno += num; 369 list_for_each_entry_safe(job, next, &sched->job_list, link) { 370 if (sched->hw_timeline.cur_seqno < job->hw_fence.seqno) 371 break; 372 373 drm_mock_sched_job_complete(job); 374 found++; 375 } 376 unlock: 377 spin_unlock_irqrestore(&sched->lock, flags); 378 379 return found; 380 } 381 382 MODULE_DESCRIPTION("DRM mock scheduler and tests"); 383 MODULE_LICENSE("GPL"); 384