162306a36Sopenharmony_ci// SPDX-License-Identifier: MIT 262306a36Sopenharmony_ci 362306a36Sopenharmony_ci#include <uapi/linux/sched/types.h> 462306a36Sopenharmony_ci 562306a36Sopenharmony_ci#include <drm/drm_print.h> 662306a36Sopenharmony_ci#include <drm/drm_vblank.h> 762306a36Sopenharmony_ci#include <drm/drm_vblank_work.h> 862306a36Sopenharmony_ci#include <drm/drm_crtc.h> 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include "drm_internal.h" 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci/** 1362306a36Sopenharmony_ci * DOC: vblank works 1462306a36Sopenharmony_ci * 1562306a36Sopenharmony_ci * Many DRM drivers need to program hardware in a time-sensitive manner, many 1662306a36Sopenharmony_ci * times with a deadline of starting and finishing within a certain region of 1762306a36Sopenharmony_ci * the scanout. Most of the time the safest way to accomplish this is to 1862306a36Sopenharmony_ci * simply do said time-sensitive programming in the driver's IRQ handler, 1962306a36Sopenharmony_ci * which allows drivers to avoid being preempted during these critical 2062306a36Sopenharmony_ci * regions. Or even better, the hardware may even handle applying such 2162306a36Sopenharmony_ci * time-critical programming independently of the CPU. 2262306a36Sopenharmony_ci * 2362306a36Sopenharmony_ci * While there's a decent amount of hardware that's designed so that the CPU 2462306a36Sopenharmony_ci * doesn't need to be concerned with extremely time-sensitive programming, 2562306a36Sopenharmony_ci * there's a few situations where it can't be helped. Some unforgiving 2662306a36Sopenharmony_ci * hardware may require that certain time-sensitive programming be handled 2762306a36Sopenharmony_ci * completely by the CPU, and said programming may even take too long to 2862306a36Sopenharmony_ci * handle in an IRQ handler. Another such situation would be where the driver 2962306a36Sopenharmony_ci * needs to perform a task that needs to complete within a specific scanout 3062306a36Sopenharmony_ci * period, but might possibly block and thus cannot be handled in an IRQ 3162306a36Sopenharmony_ci * context. Both of these situations can't be solved perfectly in Linux since 3262306a36Sopenharmony_ci * we're not a realtime kernel, and thus the scheduler may cause us to miss 3362306a36Sopenharmony_ci * our deadline if it decides to preempt us. But for some drivers, it's good 3462306a36Sopenharmony_ci * enough if we can lower our chance of being preempted to an absolute 3562306a36Sopenharmony_ci * minimum. 3662306a36Sopenharmony_ci * 3762306a36Sopenharmony_ci * This is where &drm_vblank_work comes in. &drm_vblank_work provides a simple 3862306a36Sopenharmony_ci * generic delayed work implementation which delays work execution until a 3962306a36Sopenharmony_ci * particular vblank has passed, and then executes the work at realtime 4062306a36Sopenharmony_ci * priority. This provides the best possible chance at performing 4162306a36Sopenharmony_ci * time-sensitive hardware programming on time, even when the system is under 4262306a36Sopenharmony_ci * heavy load. &drm_vblank_work also supports rescheduling, so that self 4362306a36Sopenharmony_ci * re-arming work items can be easily implemented. 4462306a36Sopenharmony_ci */ 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_civoid drm_handle_vblank_works(struct drm_vblank_crtc *vblank) 4762306a36Sopenharmony_ci{ 4862306a36Sopenharmony_ci struct drm_vblank_work *work, *next; 4962306a36Sopenharmony_ci u64 count = atomic64_read(&vblank->count); 5062306a36Sopenharmony_ci bool wake = false; 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci assert_spin_locked(&vblank->dev->event_lock); 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci list_for_each_entry_safe(work, next, &vblank->pending_work, node) { 5562306a36Sopenharmony_ci if (!drm_vblank_passed(count, work->count)) 5662306a36Sopenharmony_ci continue; 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci list_del_init(&work->node); 5962306a36Sopenharmony_ci drm_vblank_put(vblank->dev, vblank->pipe); 6062306a36Sopenharmony_ci kthread_queue_work(vblank->worker, &work->base); 6162306a36Sopenharmony_ci wake = true; 6262306a36Sopenharmony_ci } 6362306a36Sopenharmony_ci if (wake) 6462306a36Sopenharmony_ci wake_up_all(&vblank->work_wait_queue); 6562306a36Sopenharmony_ci} 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci/* Handle cancelling any pending vblank work items and drop respective vblank 6862306a36Sopenharmony_ci * references in response to vblank interrupts being disabled. 6962306a36Sopenharmony_ci */ 7062306a36Sopenharmony_civoid drm_vblank_cancel_pending_works(struct drm_vblank_crtc *vblank) 7162306a36Sopenharmony_ci{ 7262306a36Sopenharmony_ci struct drm_vblank_work *work, *next; 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci assert_spin_locked(&vblank->dev->event_lock); 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci list_for_each_entry_safe(work, next, &vblank->pending_work, node) { 7762306a36Sopenharmony_ci list_del_init(&work->node); 7862306a36Sopenharmony_ci drm_vblank_put(vblank->dev, vblank->pipe); 7962306a36Sopenharmony_ci } 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci wake_up_all(&vblank->work_wait_queue); 8262306a36Sopenharmony_ci} 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci/** 8562306a36Sopenharmony_ci * drm_vblank_work_schedule - schedule a vblank work 8662306a36Sopenharmony_ci * @work: vblank work to schedule 8762306a36Sopenharmony_ci * @count: target vblank count 8862306a36Sopenharmony_ci * @nextonmiss: defer until the next vblank if target vblank was missed 8962306a36Sopenharmony_ci * 9062306a36Sopenharmony_ci * Schedule @work for execution once the crtc vblank count reaches @count. 9162306a36Sopenharmony_ci * 9262306a36Sopenharmony_ci * If the crtc vblank count has already reached @count and @nextonmiss is 9362306a36Sopenharmony_ci * %false the work starts to execute immediately. 9462306a36Sopenharmony_ci * 9562306a36Sopenharmony_ci * If the crtc vblank count has already reached @count and @nextonmiss is 9662306a36Sopenharmony_ci * %true the work is deferred until the next vblank (as if @count has been 9762306a36Sopenharmony_ci * specified as crtc vblank count + 1). 9862306a36Sopenharmony_ci * 9962306a36Sopenharmony_ci * If @work is already scheduled, this function will reschedule said work 10062306a36Sopenharmony_ci * using the new @count. This can be used for self-rearming work items. 10162306a36Sopenharmony_ci * 10262306a36Sopenharmony_ci * Returns: 10362306a36Sopenharmony_ci * %1 if @work was successfully (re)scheduled, %0 if it was either already 10462306a36Sopenharmony_ci * scheduled or cancelled, or a negative error code on failure. 10562306a36Sopenharmony_ci */ 10662306a36Sopenharmony_ciint drm_vblank_work_schedule(struct drm_vblank_work *work, 10762306a36Sopenharmony_ci u64 count, bool nextonmiss) 10862306a36Sopenharmony_ci{ 10962306a36Sopenharmony_ci struct drm_vblank_crtc *vblank = work->vblank; 11062306a36Sopenharmony_ci struct drm_device *dev = vblank->dev; 11162306a36Sopenharmony_ci u64 cur_vbl; 11262306a36Sopenharmony_ci unsigned long irqflags; 11362306a36Sopenharmony_ci bool passed, inmodeset, rescheduling = false, wake = false; 11462306a36Sopenharmony_ci int ret = 0; 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci spin_lock_irqsave(&dev->event_lock, irqflags); 11762306a36Sopenharmony_ci if (work->cancelling) 11862306a36Sopenharmony_ci goto out; 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci spin_lock(&dev->vbl_lock); 12162306a36Sopenharmony_ci inmodeset = vblank->inmodeset; 12262306a36Sopenharmony_ci spin_unlock(&dev->vbl_lock); 12362306a36Sopenharmony_ci if (inmodeset) 12462306a36Sopenharmony_ci goto out; 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci if (list_empty(&work->node)) { 12762306a36Sopenharmony_ci ret = drm_vblank_get(dev, vblank->pipe); 12862306a36Sopenharmony_ci if (ret < 0) 12962306a36Sopenharmony_ci goto out; 13062306a36Sopenharmony_ci } else if (work->count == count) { 13162306a36Sopenharmony_ci /* Already scheduled w/ same vbl count */ 13262306a36Sopenharmony_ci goto out; 13362306a36Sopenharmony_ci } else { 13462306a36Sopenharmony_ci rescheduling = true; 13562306a36Sopenharmony_ci } 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci work->count = count; 13862306a36Sopenharmony_ci cur_vbl = drm_vblank_count(dev, vblank->pipe); 13962306a36Sopenharmony_ci passed = drm_vblank_passed(cur_vbl, count); 14062306a36Sopenharmony_ci if (passed) 14162306a36Sopenharmony_ci drm_dbg_core(dev, 14262306a36Sopenharmony_ci "crtc %d vblank %llu already passed (current %llu)\n", 14362306a36Sopenharmony_ci vblank->pipe, count, cur_vbl); 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci if (!nextonmiss && passed) { 14662306a36Sopenharmony_ci drm_vblank_put(dev, vblank->pipe); 14762306a36Sopenharmony_ci ret = kthread_queue_work(vblank->worker, &work->base); 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci if (rescheduling) { 15062306a36Sopenharmony_ci list_del_init(&work->node); 15162306a36Sopenharmony_ci wake = true; 15262306a36Sopenharmony_ci } 15362306a36Sopenharmony_ci } else { 15462306a36Sopenharmony_ci if (!rescheduling) 15562306a36Sopenharmony_ci list_add_tail(&work->node, &vblank->pending_work); 15662306a36Sopenharmony_ci ret = true; 15762306a36Sopenharmony_ci } 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ciout: 16062306a36Sopenharmony_ci spin_unlock_irqrestore(&dev->event_lock, irqflags); 16162306a36Sopenharmony_ci if (wake) 16262306a36Sopenharmony_ci wake_up_all(&vblank->work_wait_queue); 16362306a36Sopenharmony_ci return ret; 16462306a36Sopenharmony_ci} 16562306a36Sopenharmony_ciEXPORT_SYMBOL(drm_vblank_work_schedule); 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci/** 16862306a36Sopenharmony_ci * drm_vblank_work_cancel_sync - cancel a vblank work and wait for it to 16962306a36Sopenharmony_ci * finish executing 17062306a36Sopenharmony_ci * @work: vblank work to cancel 17162306a36Sopenharmony_ci * 17262306a36Sopenharmony_ci * Cancel an already scheduled vblank work and wait for its 17362306a36Sopenharmony_ci * execution to finish. 17462306a36Sopenharmony_ci * 17562306a36Sopenharmony_ci * On return, @work is guaranteed to no longer be scheduled or running, even 17662306a36Sopenharmony_ci * if it's self-arming. 17762306a36Sopenharmony_ci * 17862306a36Sopenharmony_ci * Returns: 17962306a36Sopenharmony_ci * %True if the work was cancelled before it started to execute, %false 18062306a36Sopenharmony_ci * otherwise. 18162306a36Sopenharmony_ci */ 18262306a36Sopenharmony_cibool drm_vblank_work_cancel_sync(struct drm_vblank_work *work) 18362306a36Sopenharmony_ci{ 18462306a36Sopenharmony_ci struct drm_vblank_crtc *vblank = work->vblank; 18562306a36Sopenharmony_ci struct drm_device *dev = vblank->dev; 18662306a36Sopenharmony_ci bool ret = false; 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci spin_lock_irq(&dev->event_lock); 18962306a36Sopenharmony_ci if (!list_empty(&work->node)) { 19062306a36Sopenharmony_ci list_del_init(&work->node); 19162306a36Sopenharmony_ci drm_vblank_put(vblank->dev, vblank->pipe); 19262306a36Sopenharmony_ci ret = true; 19362306a36Sopenharmony_ci } 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci work->cancelling++; 19662306a36Sopenharmony_ci spin_unlock_irq(&dev->event_lock); 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci wake_up_all(&vblank->work_wait_queue); 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ci if (kthread_cancel_work_sync(&work->base)) 20162306a36Sopenharmony_ci ret = true; 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ci spin_lock_irq(&dev->event_lock); 20462306a36Sopenharmony_ci work->cancelling--; 20562306a36Sopenharmony_ci spin_unlock_irq(&dev->event_lock); 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci return ret; 20862306a36Sopenharmony_ci} 20962306a36Sopenharmony_ciEXPORT_SYMBOL(drm_vblank_work_cancel_sync); 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci/** 21262306a36Sopenharmony_ci * drm_vblank_work_flush - wait for a scheduled vblank work to finish 21362306a36Sopenharmony_ci * executing 21462306a36Sopenharmony_ci * @work: vblank work to flush 21562306a36Sopenharmony_ci * 21662306a36Sopenharmony_ci * Wait until @work has finished executing once. 21762306a36Sopenharmony_ci */ 21862306a36Sopenharmony_civoid drm_vblank_work_flush(struct drm_vblank_work *work) 21962306a36Sopenharmony_ci{ 22062306a36Sopenharmony_ci struct drm_vblank_crtc *vblank = work->vblank; 22162306a36Sopenharmony_ci struct drm_device *dev = vblank->dev; 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_ci spin_lock_irq(&dev->event_lock); 22462306a36Sopenharmony_ci wait_event_lock_irq(vblank->work_wait_queue, list_empty(&work->node), 22562306a36Sopenharmony_ci dev->event_lock); 22662306a36Sopenharmony_ci spin_unlock_irq(&dev->event_lock); 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_ci kthread_flush_work(&work->base); 22962306a36Sopenharmony_ci} 23062306a36Sopenharmony_ciEXPORT_SYMBOL(drm_vblank_work_flush); 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci/** 23362306a36Sopenharmony_ci * drm_vblank_work_init - initialize a vblank work item 23462306a36Sopenharmony_ci * @work: vblank work item 23562306a36Sopenharmony_ci * @crtc: CRTC whose vblank will trigger the work execution 23662306a36Sopenharmony_ci * @func: work function to be executed 23762306a36Sopenharmony_ci * 23862306a36Sopenharmony_ci * Initialize a vblank work item for a specific crtc. 23962306a36Sopenharmony_ci */ 24062306a36Sopenharmony_civoid drm_vblank_work_init(struct drm_vblank_work *work, struct drm_crtc *crtc, 24162306a36Sopenharmony_ci void (*func)(struct kthread_work *work)) 24262306a36Sopenharmony_ci{ 24362306a36Sopenharmony_ci kthread_init_work(&work->base, func); 24462306a36Sopenharmony_ci INIT_LIST_HEAD(&work->node); 24562306a36Sopenharmony_ci work->vblank = &crtc->dev->vblank[drm_crtc_index(crtc)]; 24662306a36Sopenharmony_ci} 24762306a36Sopenharmony_ciEXPORT_SYMBOL(drm_vblank_work_init); 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ciint drm_vblank_worker_init(struct drm_vblank_crtc *vblank) 25062306a36Sopenharmony_ci{ 25162306a36Sopenharmony_ci struct kthread_worker *worker; 25262306a36Sopenharmony_ci 25362306a36Sopenharmony_ci INIT_LIST_HEAD(&vblank->pending_work); 25462306a36Sopenharmony_ci init_waitqueue_head(&vblank->work_wait_queue); 25562306a36Sopenharmony_ci worker = kthread_create_worker(0, "card%d-crtc%d", 25662306a36Sopenharmony_ci vblank->dev->primary->index, 25762306a36Sopenharmony_ci vblank->pipe); 25862306a36Sopenharmony_ci if (IS_ERR(worker)) 25962306a36Sopenharmony_ci return PTR_ERR(worker); 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_ci vblank->worker = worker; 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_ci sched_set_fifo(worker->task); 26462306a36Sopenharmony_ci return 0; 26562306a36Sopenharmony_ci} 266