tlb_invalidation.fence_tdr.work);
struct xe_gt_tlb_invalidation_fence *fence, *next;
- mutex_lock(>->uc.guc.ct.lock);
+ spin_lock_irq(>->tlb_invalidation.pending_lock);
list_for_each_entry_safe(fence, next,
>->tlb_invalidation.pending_fences, link) {
s64 since_inval_ms = ktime_ms_delta(ktime_get(),
queue_delayed_work(system_wq,
>->tlb_invalidation.fence_tdr,
TLB_TIMEOUT);
- mutex_unlock(>->uc.guc.ct.lock);
+ spin_unlock_irq(>->tlb_invalidation.pending_lock);
}
/**
{
gt->tlb_invalidation.seqno = 1;
INIT_LIST_HEAD(>->tlb_invalidation.pending_fences);
+ spin_lock_init(>->tlb_invalidation.pending_lock);
spin_lock_init(>->tlb_invalidation.lock);
gt->tlb_invalidation.fence_context = dma_fence_context_alloc(1);
INIT_DELAYED_WORK(>->tlb_invalidation.fence_tdr,
}
static void
-invalidation_fence_signal(struct xe_gt_tlb_invalidation_fence *fence)
+__invalidation_fence_signal(struct xe_gt_tlb_invalidation_fence *fence)
{
trace_xe_gt_tlb_invalidation_fence_signal(fence);
- list_del(&fence->link);
dma_fence_signal(&fence->base);
dma_fence_put(&fence->base);
}
+static void
+invalidation_fence_signal(struct xe_gt_tlb_invalidation_fence *fence)
+{
+ list_del(&fence->link);
+ __invalidation_fence_signal(fence);
+}
+
/**
* xe_gt_tlb_invalidation_reset - Initialize GT TLB invalidation reset
* @gt: graphics tile
*/
mutex_lock(>->uc.guc.ct.lock);
+ spin_lock_irq(>->tlb_invalidation.pending_lock);
cancel_delayed_work(>->tlb_invalidation.fence_tdr);
/*
* We might have various kworkers waiting for TLB flushes to complete
list_for_each_entry_safe(fence, next,
>->tlb_invalidation.pending_fences, link)
invalidation_fence_signal(fence);
+ spin_unlock_irq(>->tlb_invalidation.pending_lock);
mutex_unlock(>->uc.guc.ct.lock);
}
+static bool tlb_invalidation_seqno_past(struct xe_gt *gt, int seqno)
+{
+ int seqno_recv = READ_ONCE(gt->tlb_invalidation.seqno_recv);
+
+ if (seqno - seqno_recv < -(TLB_INVALIDATION_SEQNO_MAX / 2))
+ return false;
+
+ if (seqno - seqno_recv > (TLB_INVALIDATION_SEQNO_MAX / 2))
+ return true;
+
+ return seqno_recv >= seqno;
+}
+
static int send_tlb_invalidation(struct xe_guc *guc,
struct xe_gt_tlb_invalidation_fence *fence,
u32 *action, int len)
struct xe_gt *gt = guc_to_gt(guc);
int seqno;
int ret;
- bool queue_work;
/*
* XXX: The seqno algorithm relies on TLB invalidation being processed
mutex_lock(&guc->ct.lock);
seqno = gt->tlb_invalidation.seqno;
if (fence) {
- queue_work = list_empty(>->tlb_invalidation.pending_fences);
fence->seqno = seqno;
- list_add_tail(&fence->link,
- >->tlb_invalidation.pending_fences);
trace_xe_gt_tlb_invalidation_fence_send(fence);
}
action[1] = seqno;
ret = xe_guc_ct_send_locked(&guc->ct, action, len,
G2H_LEN_DW_TLB_INVALIDATE, 1);
if (!ret && fence) {
- fence->invalidation_time = ktime_get();
- if (queue_work)
- queue_delayed_work(system_wq,
- >->tlb_invalidation.fence_tdr,
- TLB_TIMEOUT);
+ spin_lock_irq(>->tlb_invalidation.pending_lock);
+ /*
+ * We haven't actually published the TLB fence as per
+ * pending_fences, but in theory our seqno could have already
+ * been written as we acquired the pending_lock. In such a case
+ * we can just go ahead and signal the fence here.
+ */
+ if (tlb_invalidation_seqno_past(gt, seqno)) {
+ __invalidation_fence_signal(fence);
+ } else {
+ fence->invalidation_time = ktime_get();
+ list_add_tail(&fence->link,
+ >->tlb_invalidation.pending_fences);
+
+ if (list_is_singular(>->tlb_invalidation.pending_fences))
+ queue_delayed_work(system_wq,
+ >->tlb_invalidation.fence_tdr,
+ TLB_TIMEOUT);
+ }
+ spin_unlock_irq(>->tlb_invalidation.pending_lock);
+ } else if (ret < 0 && fence) {
+ __invalidation_fence_signal(fence);
}
if (!ret) {
gt->tlb_invalidation.seqno = (gt->tlb_invalidation.seqno + 1) %
gt->tlb_invalidation.seqno = 1;
ret = seqno;
}
- if (ret < 0 && fence)
- invalidation_fence_signal(fence);
mutex_unlock(&guc->ct.lock);
return ret;
return ret;
}
-static bool tlb_invalidation_seqno_past(struct xe_gt *gt, int seqno)
-{
- int seqno_recv = READ_ONCE(gt->tlb_invalidation.seqno_recv);
-
- if (seqno - seqno_recv < -(TLB_INVALIDATION_SEQNO_MAX / 2))
- return false;
-
- if (seqno - seqno_recv > (TLB_INVALIDATION_SEQNO_MAX / 2))
- return true;
-
- return seqno_recv >= seqno;
-}
-
/**
* xe_gt_tlb_invalidation_wait - Wait for TLB to complete
* @gt: graphics tile
int xe_guc_tlb_invalidation_done_handler(struct xe_guc *guc, u32 *msg, u32 len)
{
struct xe_gt *gt = guc_to_gt(guc);
- struct xe_gt_tlb_invalidation_fence *fence;
- int expected_seqno;
-
- lockdep_assert_held(&guc->ct.lock);
+ struct xe_gt_tlb_invalidation_fence *fence, *next;
+ unsigned long flags;
if (unlikely(len != 1))
return -EPROTO;
- /* Sanity check on seqno */
- expected_seqno = (gt->tlb_invalidation.seqno_recv + 1) %
- TLB_INVALIDATION_SEQNO_MAX;
- if (!expected_seqno)
- expected_seqno = 1;
- if (drm_WARN_ON(>_to_xe(gt)->drm, expected_seqno != msg[0])) {
- drm_err(>_to_xe(gt)->drm, "TLB expected_seqno(%d) != msg(%u)\n",
- expected_seqno, msg[0]);
+ /*
+ * This can also be run both directly from the IRQ handler and also in
+ * process_g2h_msg(). Only one may process any individual CT message,
+ * however the order they are processed here could result in skipping a
+ * seqno. To handle that we just process all the seqnos from the last
+ * seqno_recv up to and including the one in msg[0]. The delta should be
+ * very small so there shouldn't be much of pending_fences we actually
+ * need to iterate over here.
+ *
+ * From GuC POV we expect the seqnos to always appear in-order, so if we
+ * see something later in the timeline we can be sure that anything
+ * appearing earlier has already signalled, just that we have yet to
+ * officially process the CT message like if racing against
+ * process_g2h_msg().
+ */
+ spin_lock_irqsave(>->tlb_invalidation.pending_lock, flags);
+ if (tlb_invalidation_seqno_past(gt, msg[0])) {
+ spin_unlock_irqrestore(>->tlb_invalidation.pending_lock, flags);
+ return 0;
}
/*
WRITE_ONCE(gt->tlb_invalidation.seqno_recv, msg[0]);
wake_up_all(&guc->ct.wq);
- fence = list_first_entry_or_null(>->tlb_invalidation.pending_fences,
- typeof(*fence), link);
- if (fence)
+ list_for_each_entry_safe(fence, next,
+ >->tlb_invalidation.pending_fences, link) {
trace_xe_gt_tlb_invalidation_fence_recv(fence);
- if (fence && tlb_invalidation_seqno_past(gt, fence->seqno)) {
+
+ if (!tlb_invalidation_seqno_past(gt, fence->seqno))
+ break;
+
invalidation_fence_signal(fence);
- if (!list_empty(>->tlb_invalidation.pending_fences))
- mod_delayed_work(system_wq,
- >->tlb_invalidation.fence_tdr,
- TLB_TIMEOUT);
- else
- cancel_delayed_work(>->tlb_invalidation.fence_tdr);
}
+ if (!list_empty(>->tlb_invalidation.pending_fences))
+ mod_delayed_work(system_wq,
+ >->tlb_invalidation.fence_tdr,
+ TLB_TIMEOUT);
+ else
+ cancel_delayed_work(>->tlb_invalidation.fence_tdr);
+
+ spin_unlock_irqrestore(>->tlb_invalidation.pending_lock, flags);
+
return 0;
}