1 // SPDX-License-Identifier: GPL-2.0 2 /* Copyright (c) 2025 Valve Corporation */ 3 4 #include <kunit/device.h> 5 6 #include "sched_tests.h" 7 8 /* 9 * Here we implement the mock "GPU" (or the scheduler backend) which is used by 10 * the DRM scheduler unit tests in order to exercise the core functionality. 11 * 12 * Test cases are implemented in a separate file. 13 */ 14 15 /** 16 * drm_mock_sched_entity_new - Create a new mock scheduler entity 17 * 18 * @test: KUnit test owning the entity 19 * @priority: Scheduling priority 20 * @sched: Mock scheduler on which the entity can be scheduled 21 * 22 * Returns: New mock scheduler entity with allocation managed by the test 23 */ 24 struct drm_mock_sched_entity * 25 drm_mock_sched_entity_new(struct kunit *test, 26 enum drm_sched_priority priority, 27 struct drm_mock_scheduler *sched) 28 { 29 struct drm_mock_sched_entity *entity; 30 struct drm_gpu_scheduler *drm_sched; 31 int ret; 32 33 entity = kunit_kzalloc(test, sizeof(*entity), GFP_KERNEL); 34 KUNIT_ASSERT_NOT_NULL(test, entity); 35 36 drm_sched = &sched->base; 37 ret = drm_sched_entity_init(&entity->base, 38 priority, 39 &drm_sched, 1, 40 NULL); 41 KUNIT_ASSERT_EQ(test, ret, 0); 42 43 entity->test = test; 44 45 return entity; 46 } 47 48 /** 49 * drm_mock_sched_entity_free - Destroys a mock scheduler entity 50 * 51 * @entity: Entity to destroy 52 * 53 * To be used from the test cases once done with the entity. 54 */ 55 void drm_mock_sched_entity_free(struct drm_mock_sched_entity *entity) 56 { 57 drm_sched_entity_destroy(&entity->base); 58 } 59 60 static void drm_mock_sched_job_complete(struct drm_mock_sched_job *job) 61 { 62 struct drm_mock_scheduler *sched = 63 drm_sched_to_mock_sched(job->base.sched); 64 65 lockdep_assert_held(&sched->lock); 66 67 job->flags |= DRM_MOCK_SCHED_JOB_DONE; 68 list_del(&job->link); 69 dma_fence_signal_locked(&job->hw_fence); 70 complete(&job->done); 71 } 72 73 static enum hrtimer_restart 74 drm_mock_sched_job_signal_timer(struct hrtimer *hrtimer) 75 { 76 struct drm_mock_sched_job *job = 77 container_of(hrtimer, typeof(*job), timer); 78 struct drm_mock_scheduler *sched = 79 drm_sched_to_mock_sched(job->base.sched); 80 struct drm_mock_sched_job *next; 81 ktime_t now = ktime_get(); 82 unsigned long flags; 83 LIST_HEAD(signal); 84 85 spin_lock_irqsave(&sched->lock, flags); 86 list_for_each_entry_safe(job, next, &sched->job_list, link) { 87 if (!job->duration_us) 88 break; 89 90 if (ktime_before(now, job->finish_at)) 91 break; 92 93 sched->hw_timeline.cur_seqno = job->hw_fence.seqno; 94 drm_mock_sched_job_complete(job); 95 } 96 spin_unlock_irqrestore(&sched->lock, flags); 97 98 return HRTIMER_NORESTART; 99 } 100 101 /** 102 * drm_mock_sched_job_new - Create a new mock scheduler job 103 * 104 * @test: KUnit test owning the job 105 * @entity: Scheduler entity of the job 106 * 107 * Returns: New mock scheduler job with allocation managed by the test 108 */ 109 struct drm_mock_sched_job * 110 drm_mock_sched_job_new(struct kunit *test, 111 struct drm_mock_sched_entity *entity) 112 { 113 struct drm_mock_sched_job *job; 114 int ret; 115 116 job = kunit_kzalloc(test, sizeof(*job), GFP_KERNEL); 117 KUNIT_ASSERT_NOT_NULL(test, job); 118 119 ret = drm_sched_job_init(&job->base, 120 &entity->base, 121 1, 122 NULL, 123 1); 124 KUNIT_ASSERT_EQ(test, ret, 0); 125 126 job->test = test; 127 128 init_completion(&job->done); 129 INIT_LIST_HEAD(&job->link); 130 hrtimer_setup(&job->timer, drm_mock_sched_job_signal_timer, 131 CLOCK_MONOTONIC, HRTIMER_MODE_ABS); 132 133 return job; 134 } 135 136 static const char *drm_mock_sched_hw_fence_driver_name(struct dma_fence *fence) 137 { 138 return "drm_mock_sched"; 139 } 140 141 static const char * 142 drm_mock_sched_hw_fence_timeline_name(struct dma_fence *fence) 143 { 144 struct drm_mock_sched_job *job = 145 container_of(fence, typeof(*job), hw_fence); 146 147 return (const char *)job->base.sched->name; 148 } 149 150 static void drm_mock_sched_hw_fence_release(struct dma_fence *fence) 151 { 152 struct drm_mock_sched_job *job = 153 container_of(fence, typeof(*job), hw_fence); 154 155 hrtimer_cancel(&job->timer); 156 157 /* Containing job is freed by the kunit framework */ 158 } 159 160 static const struct dma_fence_ops drm_mock_sched_hw_fence_ops = { 161 .get_driver_name = drm_mock_sched_hw_fence_driver_name, 162 .get_timeline_name = drm_mock_sched_hw_fence_timeline_name, 163 .release = drm_mock_sched_hw_fence_release, 164 }; 165 166 static struct dma_fence *mock_sched_run_job(struct drm_sched_job *sched_job) 167 { 168 struct drm_mock_scheduler *sched = 169 drm_sched_to_mock_sched(sched_job->sched); 170 struct drm_mock_sched_job *job = drm_sched_job_to_mock_job(sched_job); 171 172 dma_fence_init(&job->hw_fence, 173 &drm_mock_sched_hw_fence_ops, 174 &sched->lock, 175 sched->hw_timeline.context, 176 atomic_inc_return(&sched->hw_timeline.next_seqno)); 177 178 dma_fence_get(&job->hw_fence); /* Reference for the job_list */ 179 180 spin_lock_irq(&sched->lock); 181 if (job->duration_us) { 182 ktime_t prev_finish_at = 0; 183 184 if (!list_empty(&sched->job_list)) { 185 struct drm_mock_sched_job *prev = 186 list_last_entry(&sched->job_list, typeof(*prev), 187 link); 188 189 prev_finish_at = prev->finish_at; 190 } 191 192 if (!prev_finish_at) 193 prev_finish_at = ktime_get(); 194 195 job->finish_at = ktime_add_us(prev_finish_at, job->duration_us); 196 } 197 list_add_tail(&job->link, &sched->job_list); 198 if (job->finish_at) 199 hrtimer_start(&job->timer, job->finish_at, HRTIMER_MODE_ABS); 200 spin_unlock_irq(&sched->lock); 201 202 return &job->hw_fence; 203 } 204 205 /* 206 * Normally, drivers would take appropriate measures in this callback, such as 207 * killing the entity the faulty job is associated with, resetting the hardware 208 * and / or resubmitting non-faulty jobs. 209 * 210 * For the mock scheduler, there are no hardware rings to be resetted nor jobs 211 * to be resubmitted. Thus, this function merely ensures that 212 * a) timedout fences get signaled properly and removed from the pending list 213 * b) the mock scheduler framework gets informed about the timeout via a flag 214 * c) The drm_sched_job, not longer needed, gets freed 215 */ 216 static enum drm_gpu_sched_stat 217 mock_sched_timedout_job(struct drm_sched_job *sched_job) 218 { 219 struct drm_mock_scheduler *sched = drm_sched_to_mock_sched(sched_job->sched); 220 struct drm_mock_sched_job *job = drm_sched_job_to_mock_job(sched_job); 221 unsigned long flags; 222 223 if (job->flags & DRM_MOCK_SCHED_JOB_DONT_RESET) { 224 job->flags |= DRM_MOCK_SCHED_JOB_RESET_SKIPPED; 225 return DRM_GPU_SCHED_STAT_NO_HANG; 226 } 227 228 spin_lock_irqsave(&sched->lock, flags); 229 if (!dma_fence_is_signaled_locked(&job->hw_fence)) { 230 list_del(&job->link); 231 job->flags |= DRM_MOCK_SCHED_JOB_TIMEDOUT; 232 dma_fence_set_error(&job->hw_fence, -ETIMEDOUT); 233 dma_fence_signal_locked(&job->hw_fence); 234 } 235 spin_unlock_irqrestore(&sched->lock, flags); 236 237 dma_fence_put(&job->hw_fence); 238 drm_sched_job_cleanup(sched_job); 239 /* Mock job itself is freed by the kunit framework. */ 240 241 return DRM_GPU_SCHED_STAT_RESET; 242 } 243 244 static void mock_sched_free_job(struct drm_sched_job *sched_job) 245 { 246 struct drm_mock_sched_job *job = drm_sched_job_to_mock_job(sched_job); 247 248 dma_fence_put(&job->hw_fence); 249 drm_sched_job_cleanup(sched_job); 250 251 /* Mock job itself is freed by the kunit framework. */ 252 } 253 254 static void mock_sched_cancel_job(struct drm_sched_job *sched_job) 255 { 256 struct drm_mock_scheduler *sched = drm_sched_to_mock_sched(sched_job->sched); 257 struct drm_mock_sched_job *job = drm_sched_job_to_mock_job(sched_job); 258 unsigned long flags; 259 260 hrtimer_cancel(&job->timer); 261 262 spin_lock_irqsave(&sched->lock, flags); 263 if (!dma_fence_is_signaled_locked(&job->hw_fence)) { 264 list_del(&job->link); 265 dma_fence_set_error(&job->hw_fence, -ECANCELED); 266 dma_fence_signal_locked(&job->hw_fence); 267 } 268 spin_unlock_irqrestore(&sched->lock, flags); 269 270 /* 271 * The GPU Scheduler will call drm_sched_backend_ops.free_job(), still. 272 * Mock job itself is freed by the kunit framework. 273 */ 274 } 275 276 static const struct drm_sched_backend_ops drm_mock_scheduler_ops = { 277 .run_job = mock_sched_run_job, 278 .timedout_job = mock_sched_timedout_job, 279 .free_job = mock_sched_free_job, 280 .cancel_job = mock_sched_cancel_job, 281 }; 282 283 /** 284 * drm_mock_sched_new - Create a new mock scheduler 285 * 286 * @test: KUnit test owning the job 287 * @timeout: Job timeout to set 288 * 289 * Returns: New mock scheduler with allocation managed by the test 290 */ 291 struct drm_mock_scheduler *drm_mock_sched_new(struct kunit *test, long timeout) 292 { 293 static unsigned int instance; 294 struct drm_sched_init_args args = { 295 .ops = &drm_mock_scheduler_ops, 296 .num_rqs = DRM_SCHED_PRIORITY_COUNT, 297 .credit_limit = U32_MAX, 298 .hang_limit = 1, 299 .timeout = timeout, 300 .name = "drm-mock-scheduler", 301 }; 302 struct drm_mock_scheduler *sched; 303 struct device *dev; 304 char name[64]; 305 int ret; 306 307 sched = kunit_kzalloc(test, sizeof(*sched), GFP_KERNEL); 308 KUNIT_ASSERT_NOT_NULL(test, sched); 309 310 snprintf(name, sizeof(name), "%s-%u", args.name, instance++); 311 dev = kunit_device_register(test, name); 312 KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); 313 314 args.dev = dev; 315 316 ret = drm_sched_init(&sched->base, &args); 317 KUNIT_ASSERT_EQ(test, ret, 0); 318 319 sched->test = test; 320 sched->hw_timeline.context = dma_fence_context_alloc(1); 321 atomic_set(&sched->hw_timeline.next_seqno, 0); 322 INIT_LIST_HEAD(&sched->job_list); 323 spin_lock_init(&sched->lock); 324 325 return sched; 326 } 327 328 /** 329 * drm_mock_sched_fini - Destroys a mock scheduler 330 * 331 * @sched: Scheduler to destroy 332 * 333 * To be used from the test cases once done with the scheduler. 334 */ 335 void drm_mock_sched_fini(struct drm_mock_scheduler *sched) 336 { 337 drm_sched_fini(&sched->base); 338 } 339 340 /** 341 * drm_mock_sched_advance - Advances the mock scheduler timeline 342 * 343 * @sched: Scheduler timeline to advance 344 * @num: By how many jobs to advance 345 * 346 * Advancing the scheduler timeline by a number of seqnos will trigger 347 * signalling of the hardware fences and unlinking the jobs from the internal 348 * scheduler tracking. 349 * 350 * This can be used from test cases which want complete control of the simulated 351 * job execution timing. For example submitting one job with no set duration 352 * would never complete it before test cases advances the timeline by one. 353 */ 354 unsigned int drm_mock_sched_advance(struct drm_mock_scheduler *sched, 355 unsigned int num) 356 { 357 struct drm_mock_sched_job *job, *next; 358 unsigned int found = 0; 359 unsigned long flags; 360 LIST_HEAD(signal); 361 362 spin_lock_irqsave(&sched->lock, flags); 363 if (WARN_ON_ONCE(sched->hw_timeline.cur_seqno + num < 364 sched->hw_timeline.cur_seqno)) 365 goto unlock; 366 sched->hw_timeline.cur_seqno += num; 367 list_for_each_entry_safe(job, next, &sched->job_list, link) { 368 if (sched->hw_timeline.cur_seqno < job->hw_fence.seqno) 369 break; 370 371 drm_mock_sched_job_complete(job); 372 found++; 373 } 374 unlock: 375 spin_unlock_irqrestore(&sched->lock, flags); 376 377 return found; 378 } 379 380 MODULE_DESCRIPTION("DRM mock scheduler and tests"); 381 MODULE_LICENSE("GPL"); 382