| /* |
| * SPDX-License-Identifier: MIT |
| * |
| * Copyright © 2019 Intel Corporation |
| */ |
| |
| #include "i915_drv.h" /* for_each_engine() */ |
| #include "i915_request.h" |
| #include "intel_gt.h" |
| #include "intel_gt_pm.h" |
| #include "intel_gt_requests.h" |
| #include "intel_timeline.h" |
| |
| static void retire_requests(struct intel_timeline *tl) |
| { |
| struct i915_request *rq, *rn; |
| |
| list_for_each_entry_safe(rq, rn, &tl->requests, link) |
| if (!i915_request_retire(rq)) |
| break; |
| } |
| |
| static void flush_submission(struct intel_gt *gt) |
| { |
| struct intel_engine_cs *engine; |
| enum intel_engine_id id; |
| |
| for_each_engine(engine, gt, id) |
| intel_engine_flush_submission(engine); |
| } |
| |
| long intel_gt_retire_requests_timeout(struct intel_gt *gt, long timeout) |
| { |
| struct intel_gt_timelines *timelines = >->timelines; |
| struct intel_timeline *tl, *tn; |
| unsigned long active_count = 0; |
| unsigned long flags; |
| bool interruptible; |
| LIST_HEAD(free); |
| |
| interruptible = true; |
| if (unlikely(timeout < 0)) |
| timeout = -timeout, interruptible = false; |
| |
| flush_submission(gt); /* kick the ksoftirqd tasklets */ |
| |
| spin_lock_irqsave(&timelines->lock, flags); |
| list_for_each_entry_safe(tl, tn, &timelines->active_list, link) { |
| if (!mutex_trylock(&tl->mutex)) { |
| active_count++; /* report busy to caller, try again? */ |
| continue; |
| } |
| |
| intel_timeline_get(tl); |
| GEM_BUG_ON(!tl->active_count); |
| tl->active_count++; /* pin the list element */ |
| spin_unlock_irqrestore(&timelines->lock, flags); |
| |
| if (timeout > 0) { |
| struct dma_fence *fence; |
| |
| fence = i915_active_fence_get(&tl->last_request); |
| if (fence) { |
| timeout = dma_fence_wait_timeout(fence, |
| interruptible, |
| timeout); |
| dma_fence_put(fence); |
| } |
| } |
| |
| retire_requests(tl); |
| |
| spin_lock_irqsave(&timelines->lock, flags); |
| |
| /* Resume iteration after dropping lock */ |
| list_safe_reset_next(tl, tn, link); |
| if (!--tl->active_count) |
| list_del(&tl->link); |
| else |
| active_count += !!rcu_access_pointer(tl->last_request.fence); |
| |
| mutex_unlock(&tl->mutex); |
| |
| /* Defer the final release to after the spinlock */ |
| if (refcount_dec_and_test(&tl->kref.refcount)) { |
| GEM_BUG_ON(tl->active_count); |
| list_add(&tl->link, &free); |
| } |
| } |
| spin_unlock_irqrestore(&timelines->lock, flags); |
| |
| list_for_each_entry_safe(tl, tn, &free, link) |
| __intel_timeline_free(&tl->kref); |
| |
| return active_count ? timeout : 0; |
| } |
| |
| int intel_gt_wait_for_idle(struct intel_gt *gt, long timeout) |
| { |
| /* If the device is asleep, we have no requests outstanding */ |
| if (!intel_gt_pm_is_awake(gt)) |
| return 0; |
| |
| while ((timeout = intel_gt_retire_requests_timeout(gt, timeout)) > 0) { |
| cond_resched(); |
| if (signal_pending(current)) |
| return -EINTR; |
| } |
| |
| return timeout; |
| } |
| |
| static void retire_work_handler(struct work_struct *work) |
| { |
| struct intel_gt *gt = |
| container_of(work, typeof(*gt), requests.retire_work.work); |
| |
| intel_gt_retire_requests(gt); |
| schedule_delayed_work(>->requests.retire_work, |
| round_jiffies_up_relative(HZ)); |
| } |
| |
| void intel_gt_init_requests(struct intel_gt *gt) |
| { |
| INIT_DELAYED_WORK(>->requests.retire_work, retire_work_handler); |
| } |
| |
| void intel_gt_park_requests(struct intel_gt *gt) |
| { |
| cancel_delayed_work(>->requests.retire_work); |
| } |
| |
| void intel_gt_unpark_requests(struct intel_gt *gt) |
| { |
| schedule_delayed_work(>->requests.retire_work, |
| round_jiffies_up_relative(HZ)); |
| } |