18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: MIT 28c2ecf20Sopenharmony_ci 38c2ecf20Sopenharmony_ci#include <uapi/linux/sched/types.h> 48c2ecf20Sopenharmony_ci 58c2ecf20Sopenharmony_ci#include <drm/drm_print.h> 68c2ecf20Sopenharmony_ci#include <drm/drm_vblank.h> 78c2ecf20Sopenharmony_ci#include <drm/drm_vblank_work.h> 88c2ecf20Sopenharmony_ci#include <drm/drm_crtc.h> 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci#include "drm_internal.h" 118c2ecf20Sopenharmony_ci 128c2ecf20Sopenharmony_ci/** 138c2ecf20Sopenharmony_ci * DOC: vblank works 148c2ecf20Sopenharmony_ci * 158c2ecf20Sopenharmony_ci * Many DRM drivers need to program hardware in a time-sensitive manner, many 168c2ecf20Sopenharmony_ci * times with a deadline of starting and finishing within a certain region of 178c2ecf20Sopenharmony_ci * the scanout. Most of the time the safest way to accomplish this is to 188c2ecf20Sopenharmony_ci * simply do said time-sensitive programming in the driver's IRQ handler, 198c2ecf20Sopenharmony_ci * which allows drivers to avoid being preempted during these critical 208c2ecf20Sopenharmony_ci * regions. Or even better, the hardware may even handle applying such 218c2ecf20Sopenharmony_ci * time-critical programming independently of the CPU. 228c2ecf20Sopenharmony_ci * 238c2ecf20Sopenharmony_ci * While there's a decent amount of hardware that's designed so that the CPU 248c2ecf20Sopenharmony_ci * doesn't need to be concerned with extremely time-sensitive programming, 258c2ecf20Sopenharmony_ci * there's a few situations where it can't be helped. Some unforgiving 268c2ecf20Sopenharmony_ci * hardware may require that certain time-sensitive programming be handled 278c2ecf20Sopenharmony_ci * completely by the CPU, and said programming may even take too long to 288c2ecf20Sopenharmony_ci * handle in an IRQ handler. Another such situation would be where the driver 298c2ecf20Sopenharmony_ci * needs to perform a task that needs to complete within a specific scanout 308c2ecf20Sopenharmony_ci * period, but might possibly block and thus cannot be handled in an IRQ 318c2ecf20Sopenharmony_ci * context. Both of these situations can't be solved perfectly in Linux since 328c2ecf20Sopenharmony_ci * we're not a realtime kernel, and thus the scheduler may cause us to miss 338c2ecf20Sopenharmony_ci * our deadline if it decides to preempt us. But for some drivers, it's good 348c2ecf20Sopenharmony_ci * enough if we can lower our chance of being preempted to an absolute 358c2ecf20Sopenharmony_ci * minimum. 368c2ecf20Sopenharmony_ci * 378c2ecf20Sopenharmony_ci * This is where &drm_vblank_work comes in. &drm_vblank_work provides a simple 388c2ecf20Sopenharmony_ci * generic delayed work implementation which delays work execution until a 398c2ecf20Sopenharmony_ci * particular vblank has passed, and then executes the work at realtime 408c2ecf20Sopenharmony_ci * priority. This provides the best possible chance at performing 418c2ecf20Sopenharmony_ci * time-sensitive hardware programming on time, even when the system is under 428c2ecf20Sopenharmony_ci * heavy load. &drm_vblank_work also supports rescheduling, so that self 438c2ecf20Sopenharmony_ci * re-arming work items can be easily implemented. 448c2ecf20Sopenharmony_ci */ 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_civoid drm_handle_vblank_works(struct drm_vblank_crtc *vblank) 478c2ecf20Sopenharmony_ci{ 488c2ecf20Sopenharmony_ci struct drm_vblank_work *work, *next; 498c2ecf20Sopenharmony_ci u64 count = atomic64_read(&vblank->count); 508c2ecf20Sopenharmony_ci bool wake = false; 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_ci assert_spin_locked(&vblank->dev->event_lock); 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_ci list_for_each_entry_safe(work, next, &vblank->pending_work, node) { 558c2ecf20Sopenharmony_ci if (!drm_vblank_passed(count, work->count)) 568c2ecf20Sopenharmony_ci continue; 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_ci list_del_init(&work->node); 598c2ecf20Sopenharmony_ci drm_vblank_put(vblank->dev, vblank->pipe); 608c2ecf20Sopenharmony_ci kthread_queue_work(vblank->worker, &work->base); 618c2ecf20Sopenharmony_ci wake = true; 628c2ecf20Sopenharmony_ci } 638c2ecf20Sopenharmony_ci if (wake) 648c2ecf20Sopenharmony_ci wake_up_all(&vblank->work_wait_queue); 658c2ecf20Sopenharmony_ci} 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci/* Handle cancelling any pending vblank work items and drop respective vblank 688c2ecf20Sopenharmony_ci * references in response to vblank interrupts being disabled. 698c2ecf20Sopenharmony_ci */ 708c2ecf20Sopenharmony_civoid drm_vblank_cancel_pending_works(struct drm_vblank_crtc *vblank) 718c2ecf20Sopenharmony_ci{ 728c2ecf20Sopenharmony_ci struct drm_vblank_work *work, *next; 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci assert_spin_locked(&vblank->dev->event_lock); 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci list_for_each_entry_safe(work, next, &vblank->pending_work, node) { 778c2ecf20Sopenharmony_ci list_del_init(&work->node); 788c2ecf20Sopenharmony_ci drm_vblank_put(vblank->dev, vblank->pipe); 798c2ecf20Sopenharmony_ci } 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci wake_up_all(&vblank->work_wait_queue); 828c2ecf20Sopenharmony_ci} 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ci/** 858c2ecf20Sopenharmony_ci * drm_vblank_work_schedule - schedule a vblank work 868c2ecf20Sopenharmony_ci * @work: vblank work to schedule 878c2ecf20Sopenharmony_ci * @count: target vblank count 888c2ecf20Sopenharmony_ci * @nextonmiss: defer until the next vblank if target vblank was missed 898c2ecf20Sopenharmony_ci * 908c2ecf20Sopenharmony_ci * Schedule @work for execution once the crtc vblank count reaches @count. 918c2ecf20Sopenharmony_ci * 928c2ecf20Sopenharmony_ci * If the crtc vblank count has already reached @count and @nextonmiss is 938c2ecf20Sopenharmony_ci * %false the work starts to execute immediately. 948c2ecf20Sopenharmony_ci * 958c2ecf20Sopenharmony_ci * If the crtc vblank count has already reached @count and @nextonmiss is 968c2ecf20Sopenharmony_ci * %true the work is deferred until the next vblank (as if @count has been 978c2ecf20Sopenharmony_ci * specified as crtc vblank count + 1). 988c2ecf20Sopenharmony_ci * 998c2ecf20Sopenharmony_ci * If @work is already scheduled, this function will reschedule said work 1008c2ecf20Sopenharmony_ci * using the new @count. This can be used for self-rearming work items. 1018c2ecf20Sopenharmony_ci * 1028c2ecf20Sopenharmony_ci * Returns: 1038c2ecf20Sopenharmony_ci * %1 if @work was successfully (re)scheduled, %0 if it was either already 1048c2ecf20Sopenharmony_ci * scheduled or cancelled, or a negative error code on failure. 1058c2ecf20Sopenharmony_ci */ 1068c2ecf20Sopenharmony_ciint drm_vblank_work_schedule(struct drm_vblank_work *work, 1078c2ecf20Sopenharmony_ci u64 count, bool nextonmiss) 1088c2ecf20Sopenharmony_ci{ 1098c2ecf20Sopenharmony_ci struct drm_vblank_crtc *vblank = work->vblank; 1108c2ecf20Sopenharmony_ci struct drm_device *dev = vblank->dev; 1118c2ecf20Sopenharmony_ci u64 cur_vbl; 1128c2ecf20Sopenharmony_ci unsigned long irqflags; 1138c2ecf20Sopenharmony_ci bool passed, inmodeset, rescheduling = false, wake = false; 1148c2ecf20Sopenharmony_ci int ret = 0; 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_ci spin_lock_irqsave(&dev->event_lock, irqflags); 1178c2ecf20Sopenharmony_ci if (work->cancelling) 1188c2ecf20Sopenharmony_ci goto out; 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci spin_lock(&dev->vbl_lock); 1218c2ecf20Sopenharmony_ci inmodeset = vblank->inmodeset; 1228c2ecf20Sopenharmony_ci spin_unlock(&dev->vbl_lock); 1238c2ecf20Sopenharmony_ci if (inmodeset) 1248c2ecf20Sopenharmony_ci goto out; 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci if (list_empty(&work->node)) { 1278c2ecf20Sopenharmony_ci ret = drm_vblank_get(dev, vblank->pipe); 1288c2ecf20Sopenharmony_ci if (ret < 0) 1298c2ecf20Sopenharmony_ci goto out; 1308c2ecf20Sopenharmony_ci } else if (work->count == count) { 1318c2ecf20Sopenharmony_ci /* Already scheduled w/ same vbl count */ 1328c2ecf20Sopenharmony_ci goto out; 1338c2ecf20Sopenharmony_ci } else { 1348c2ecf20Sopenharmony_ci rescheduling = true; 1358c2ecf20Sopenharmony_ci } 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_ci work->count = count; 1388c2ecf20Sopenharmony_ci cur_vbl = drm_vblank_count(dev, vblank->pipe); 1398c2ecf20Sopenharmony_ci passed = drm_vblank_passed(cur_vbl, count); 1408c2ecf20Sopenharmony_ci if (passed) 1418c2ecf20Sopenharmony_ci drm_dbg_core(dev, 1428c2ecf20Sopenharmony_ci "crtc %d vblank %llu already passed (current %llu)\n", 1438c2ecf20Sopenharmony_ci vblank->pipe, count, cur_vbl); 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_ci if (!nextonmiss && passed) { 1468c2ecf20Sopenharmony_ci drm_vblank_put(dev, vblank->pipe); 1478c2ecf20Sopenharmony_ci ret = kthread_queue_work(vblank->worker, &work->base); 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_ci if (rescheduling) { 1508c2ecf20Sopenharmony_ci list_del_init(&work->node); 1518c2ecf20Sopenharmony_ci wake = true; 1528c2ecf20Sopenharmony_ci } 1538c2ecf20Sopenharmony_ci } else { 1548c2ecf20Sopenharmony_ci if (!rescheduling) 1558c2ecf20Sopenharmony_ci list_add_tail(&work->node, &vblank->pending_work); 1568c2ecf20Sopenharmony_ci ret = true; 1578c2ecf20Sopenharmony_ci } 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ciout: 1608c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&dev->event_lock, irqflags); 1618c2ecf20Sopenharmony_ci if (wake) 1628c2ecf20Sopenharmony_ci wake_up_all(&vblank->work_wait_queue); 1638c2ecf20Sopenharmony_ci return ret; 1648c2ecf20Sopenharmony_ci} 1658c2ecf20Sopenharmony_ciEXPORT_SYMBOL(drm_vblank_work_schedule); 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_ci/** 1688c2ecf20Sopenharmony_ci * drm_vblank_work_cancel_sync - cancel a vblank work and wait for it to 1698c2ecf20Sopenharmony_ci * finish executing 1708c2ecf20Sopenharmony_ci * @work: vblank work to cancel 1718c2ecf20Sopenharmony_ci * 1728c2ecf20Sopenharmony_ci * Cancel an already scheduled vblank work and wait for its 1738c2ecf20Sopenharmony_ci * execution to finish. 1748c2ecf20Sopenharmony_ci * 1758c2ecf20Sopenharmony_ci * On return, @work is guaranteed to no longer be scheduled or running, even 1768c2ecf20Sopenharmony_ci * if it's self-arming. 1778c2ecf20Sopenharmony_ci * 1788c2ecf20Sopenharmony_ci * Returns: 1798c2ecf20Sopenharmony_ci * %True if the work was cancelled before it started to execute, %false 1808c2ecf20Sopenharmony_ci * otherwise. 1818c2ecf20Sopenharmony_ci */ 1828c2ecf20Sopenharmony_cibool drm_vblank_work_cancel_sync(struct drm_vblank_work *work) 1838c2ecf20Sopenharmony_ci{ 1848c2ecf20Sopenharmony_ci struct drm_vblank_crtc *vblank = work->vblank; 1858c2ecf20Sopenharmony_ci struct drm_device *dev = vblank->dev; 1868c2ecf20Sopenharmony_ci bool ret = false; 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_ci spin_lock_irq(&dev->event_lock); 1898c2ecf20Sopenharmony_ci if (!list_empty(&work->node)) { 1908c2ecf20Sopenharmony_ci list_del_init(&work->node); 1918c2ecf20Sopenharmony_ci drm_vblank_put(vblank->dev, vblank->pipe); 1928c2ecf20Sopenharmony_ci ret = true; 1938c2ecf20Sopenharmony_ci } 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_ci work->cancelling++; 1968c2ecf20Sopenharmony_ci spin_unlock_irq(&dev->event_lock); 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_ci wake_up_all(&vblank->work_wait_queue); 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_ci if (kthread_cancel_work_sync(&work->base)) 2018c2ecf20Sopenharmony_ci ret = true; 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_ci spin_lock_irq(&dev->event_lock); 2048c2ecf20Sopenharmony_ci work->cancelling--; 2058c2ecf20Sopenharmony_ci spin_unlock_irq(&dev->event_lock); 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci return ret; 2088c2ecf20Sopenharmony_ci} 2098c2ecf20Sopenharmony_ciEXPORT_SYMBOL(drm_vblank_work_cancel_sync); 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_ci/** 2128c2ecf20Sopenharmony_ci * drm_vblank_work_flush - wait for a scheduled vblank work to finish 2138c2ecf20Sopenharmony_ci * executing 2148c2ecf20Sopenharmony_ci * @work: vblank work to flush 2158c2ecf20Sopenharmony_ci * 2168c2ecf20Sopenharmony_ci * Wait until @work has finished executing once. 2178c2ecf20Sopenharmony_ci */ 2188c2ecf20Sopenharmony_civoid drm_vblank_work_flush(struct drm_vblank_work *work) 2198c2ecf20Sopenharmony_ci{ 2208c2ecf20Sopenharmony_ci struct drm_vblank_crtc *vblank = work->vblank; 2218c2ecf20Sopenharmony_ci struct drm_device *dev = vblank->dev; 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_ci spin_lock_irq(&dev->event_lock); 2248c2ecf20Sopenharmony_ci wait_event_lock_irq(vblank->work_wait_queue, list_empty(&work->node), 2258c2ecf20Sopenharmony_ci dev->event_lock); 2268c2ecf20Sopenharmony_ci spin_unlock_irq(&dev->event_lock); 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_ci kthread_flush_work(&work->base); 2298c2ecf20Sopenharmony_ci} 2308c2ecf20Sopenharmony_ciEXPORT_SYMBOL(drm_vblank_work_flush); 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ci/** 2338c2ecf20Sopenharmony_ci * drm_vblank_work_init - initialize a vblank work item 2348c2ecf20Sopenharmony_ci * @work: vblank work item 2358c2ecf20Sopenharmony_ci * @crtc: CRTC whose vblank will trigger the work execution 2368c2ecf20Sopenharmony_ci * @func: work function to be executed 2378c2ecf20Sopenharmony_ci * 2388c2ecf20Sopenharmony_ci * Initialize a vblank work item for a specific crtc. 2398c2ecf20Sopenharmony_ci */ 2408c2ecf20Sopenharmony_civoid drm_vblank_work_init(struct drm_vblank_work *work, struct drm_crtc *crtc, 2418c2ecf20Sopenharmony_ci void (*func)(struct kthread_work *work)) 2428c2ecf20Sopenharmony_ci{ 2438c2ecf20Sopenharmony_ci kthread_init_work(&work->base, func); 2448c2ecf20Sopenharmony_ci INIT_LIST_HEAD(&work->node); 2458c2ecf20Sopenharmony_ci work->vblank = &crtc->dev->vblank[drm_crtc_index(crtc)]; 2468c2ecf20Sopenharmony_ci} 2478c2ecf20Sopenharmony_ciEXPORT_SYMBOL(drm_vblank_work_init); 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ciint drm_vblank_worker_init(struct drm_vblank_crtc *vblank) 2508c2ecf20Sopenharmony_ci{ 2518c2ecf20Sopenharmony_ci struct kthread_worker *worker; 2528c2ecf20Sopenharmony_ci 2538c2ecf20Sopenharmony_ci INIT_LIST_HEAD(&vblank->pending_work); 2548c2ecf20Sopenharmony_ci init_waitqueue_head(&vblank->work_wait_queue); 2558c2ecf20Sopenharmony_ci worker = kthread_create_worker(0, "card%d-crtc%d", 2568c2ecf20Sopenharmony_ci vblank->dev->primary->index, 2578c2ecf20Sopenharmony_ci vblank->pipe); 2588c2ecf20Sopenharmony_ci if (IS_ERR(worker)) 2598c2ecf20Sopenharmony_ci return PTR_ERR(worker); 2608c2ecf20Sopenharmony_ci 2618c2ecf20Sopenharmony_ci vblank->worker = worker; 2628c2ecf20Sopenharmony_ci 2638c2ecf20Sopenharmony_ci sched_set_fifo(worker->task); 2648c2ecf20Sopenharmony_ci return 0; 2658c2ecf20Sopenharmony_ci} 266