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