#include <drm/drm_managed.h>
#include "xe_device_types.h"
#include "xe_force_wake.h"
#include "xe_gt_stats.h"
#include "xe_gt_types.h"
#include "xe_guc_ct.h"
#include "xe_guc_tlb_inval.h"
#include "xe_mmio.h"
#include "xe_pm.h"
#include "xe_tlb_inval.h"
#include "xe_trace.h"
#define FENCE_STACK_BIT DMA_FENCE_FLAG_USER_BITS
static void xe_tlb_inval_fence_fini(struct xe_tlb_inval_fence *fence)
{
if (WARN_ON_ONCE(!fence->tlb_inval))
return;
xe_pm_runtime_put(fence->tlb_inval->xe);
fence->tlb_inval = NULL;
}
static void
xe_tlb_inval_fence_signal(struct xe_tlb_inval_fence *fence)
{
struct xe_tlb_inval *tlb_inval = fence->tlb_inval;
bool stack = test_bit(FENCE_STACK_BIT, &fence->base.flags);
lockdep_assert_held(&fence->tlb_inval->pending_lock);
list_del(&fence->link);
if (list_empty(&tlb_inval->pending_fences))
cancel_delayed_work(&tlb_inval->fence_tdr);
trace_xe_tlb_inval_fence_signal(fence->tlb_inval->xe, fence);
xe_tlb_inval_fence_fini(fence);
dma_fence_signal(&fence->base);
if (!stack)
dma_fence_put(&fence->base);
}
static void
xe_tlb_inval_fence_signal_unlocked(struct xe_tlb_inval_fence *fence)
{
struct xe_tlb_inval *tlb_inval = fence->tlb_inval;
spin_lock_irq(&tlb_inval->pending_lock);
xe_tlb_inval_fence_signal(fence);
spin_unlock_irq(&tlb_inval->pending_lock);
}
static void xe_tlb_inval_fence_timeout(struct work_struct *work)
{
struct xe_tlb_inval *tlb_inval = container_of(work, struct xe_tlb_inval,
fence_tdr.work);
struct xe_device *xe = tlb_inval->xe;
struct xe_tlb_inval_fence *fence, *next;
long timeout_delay = tlb_inval->ops->timeout_delay(tlb_inval);
tlb_inval->ops->flush(tlb_inval);
spin_lock_irq(&tlb_inval->pending_lock);
list_for_each_entry_safe(fence, next,
&tlb_inval->pending_fences, link) {
s64 since_inval_ms = ktime_ms_delta(ktime_get(),
fence->inval_time);
if (msecs_to_jiffies(since_inval_ms) < timeout_delay)
break;
trace_xe_tlb_inval_fence_timeout(xe, fence);
drm_err(&xe->drm,
"TLB invalidation fence timeout, seqno=%d recv=%d",
fence->seqno, tlb_inval->seqno_recv);
fence->base.error = -ETIME;
xe_tlb_inval_fence_signal(fence);
}
if (!list_empty(&tlb_inval->pending_fences))
queue_delayed_work(tlb_inval->timeout_wq, &tlb_inval->fence_tdr,
timeout_delay);
spin_unlock_irq(&tlb_inval->pending_lock);
}
static void tlb_inval_fini(struct drm_device *drm, void *arg)
{
struct xe_tlb_inval *tlb_inval = arg;
xe_tlb_inval_reset(tlb_inval);
}
static void primelockdep(struct xe_tlb_inval *tlb_inval)
{
if (!IS_ENABLED(CONFIG_LOCKDEP))
return;
fs_reclaim_acquire(GFP_KERNEL);
might_lock(&tlb_inval->seqno_lock);
fs_reclaim_release(GFP_KERNEL);
}
int xe_gt_tlb_inval_init_early(struct xe_gt *gt)
{
struct xe_device *xe = gt_to_xe(gt);
struct xe_tlb_inval *tlb_inval = >->tlb_inval;
int err;
tlb_inval->xe = xe;
tlb_inval->seqno = 1;
INIT_LIST_HEAD(&tlb_inval->pending_fences);
spin_lock_init(&tlb_inval->pending_lock);
spin_lock_init(&tlb_inval->lock);
INIT_DELAYED_WORK(&tlb_inval->fence_tdr, xe_tlb_inval_fence_timeout);
err = drmm_mutex_init(&xe->drm, &tlb_inval->seqno_lock);
if (err)
return err;
primelockdep(tlb_inval);
tlb_inval->job_wq = drmm_alloc_ordered_workqueue(&xe->drm,
"gt-tbl-inval-job-wq",
WQ_MEM_RECLAIM);
if (IS_ERR(tlb_inval->job_wq))
return PTR_ERR(tlb_inval->job_wq);
tlb_inval->timeout_wq = gt->ordered_wq;
if (IS_ERR(tlb_inval->timeout_wq))
return PTR_ERR(tlb_inval->timeout_wq);
xe_guc_tlb_inval_init_early(>->uc.guc, tlb_inval);
return drmm_add_action_or_reset(&xe->drm, tlb_inval_fini, tlb_inval);
}
void xe_tlb_inval_reset(struct xe_tlb_inval *tlb_inval)
{
struct xe_tlb_inval_fence *fence, *next;
int pending_seqno;
if (!tlb_inval->ops->initialized(tlb_inval))
return;
mutex_lock(&tlb_inval->seqno_lock);
spin_lock_irq(&tlb_inval->pending_lock);
cancel_delayed_work(&tlb_inval->fence_tdr);
if (tlb_inval->seqno == 1)
pending_seqno = TLB_INVALIDATION_SEQNO_MAX - 1;
else
pending_seqno = tlb_inval->seqno - 1;
WRITE_ONCE(tlb_inval->seqno_recv, pending_seqno);
list_for_each_entry_safe(fence, next,
&tlb_inval->pending_fences, link)
xe_tlb_inval_fence_signal(fence);
spin_unlock_irq(&tlb_inval->pending_lock);
mutex_unlock(&tlb_inval->seqno_lock);
}
static void xe_tlb_inval_reset_timeout(struct xe_tlb_inval *tlb_inval)
{
lockdep_assert_held(&tlb_inval->pending_lock);
mod_delayed_work(system_wq, &tlb_inval->fence_tdr,
tlb_inval->ops->timeout_delay(tlb_inval));
}
static bool xe_tlb_inval_seqno_past(struct xe_tlb_inval *tlb_inval, int seqno)
{
int seqno_recv = READ_ONCE(tlb_inval->seqno_recv);
lockdep_assert_held(&tlb_inval->pending_lock);
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 void xe_tlb_inval_fence_prep(struct xe_tlb_inval_fence *fence)
{
struct xe_tlb_inval *tlb_inval = fence->tlb_inval;
fence->seqno = tlb_inval->seqno;
trace_xe_tlb_inval_fence_send(tlb_inval->xe, fence);
spin_lock_irq(&tlb_inval->pending_lock);
fence->inval_time = ktime_get();
list_add_tail(&fence->link, &tlb_inval->pending_fences);
if (list_is_singular(&tlb_inval->pending_fences))
queue_delayed_work(tlb_inval->timeout_wq, &tlb_inval->fence_tdr,
tlb_inval->ops->timeout_delay(tlb_inval));
spin_unlock_irq(&tlb_inval->pending_lock);
tlb_inval->seqno = (tlb_inval->seqno + 1) %
TLB_INVALIDATION_SEQNO_MAX;
if (!tlb_inval->seqno)
tlb_inval->seqno = 1;
}
#define xe_tlb_inval_issue(__tlb_inval, __fence, op, args...) \
({ \
int __ret; \
\
xe_assert((__tlb_inval)->xe, (__tlb_inval)->ops); \
xe_assert((__tlb_inval)->xe, (__fence)); \
\
mutex_lock(&(__tlb_inval)->seqno_lock); \
xe_tlb_inval_fence_prep((__fence)); \
__ret = op((__tlb_inval), (__fence)->seqno, ##args); \
if (__ret < 0) \
xe_tlb_inval_fence_signal_unlocked((__fence)); \
mutex_unlock(&(__tlb_inval)->seqno_lock); \
\
__ret == -ECANCELED ? 0 : __ret; \
})
int xe_tlb_inval_all(struct xe_tlb_inval *tlb_inval,
struct xe_tlb_inval_fence *fence)
{
return xe_tlb_inval_issue(tlb_inval, fence, tlb_inval->ops->all);
}
int xe_tlb_inval_ggtt(struct xe_tlb_inval *tlb_inval)
{
struct xe_tlb_inval_fence fence, *fence_ptr = &fence;
int ret;
xe_tlb_inval_fence_init(tlb_inval, fence_ptr, true);
ret = xe_tlb_inval_issue(tlb_inval, fence_ptr, tlb_inval->ops->ggtt);
xe_tlb_inval_fence_wait(fence_ptr);
return ret;
}
int xe_tlb_inval_range(struct xe_tlb_inval *tlb_inval,
struct xe_tlb_inval_fence *fence, u64 start, u64 end,
u32 asid, struct drm_suballoc *prl_sa)
{
return xe_tlb_inval_issue(tlb_inval, fence, tlb_inval->ops->ppgtt,
start, end, asid, prl_sa);
}
void xe_tlb_inval_vm(struct xe_tlb_inval *tlb_inval, struct xe_vm *vm)
{
struct xe_tlb_inval_fence fence;
u64 range = 1ull << vm->xe->info.va_bits;
xe_tlb_inval_fence_init(tlb_inval, &fence, true);
xe_tlb_inval_range(tlb_inval, &fence, 0, range, vm->usm.asid, NULL);
xe_tlb_inval_fence_wait(&fence);
}
void xe_tlb_inval_done_handler(struct xe_tlb_inval *tlb_inval, int seqno)
{
struct xe_device *xe = tlb_inval->xe;
struct xe_tlb_inval_fence *fence, *next;
unsigned long flags;
spin_lock_irqsave(&tlb_inval->pending_lock, flags);
if (seqno == TLB_INVALIDATION_SEQNO_INVALID) {
xe_tlb_inval_reset_timeout(tlb_inval);
spin_unlock_irqrestore(&tlb_inval->pending_lock, flags);
return;
}
if (xe_tlb_inval_seqno_past(tlb_inval, seqno)) {
spin_unlock_irqrestore(&tlb_inval->pending_lock, flags);
return;
}
WRITE_ONCE(tlb_inval->seqno_recv, seqno);
list_for_each_entry_safe(fence, next,
&tlb_inval->pending_fences, link) {
trace_xe_tlb_inval_fence_recv(xe, fence);
if (!xe_tlb_inval_seqno_past(tlb_inval, fence->seqno))
break;
xe_tlb_inval_fence_signal(fence);
}
if (!list_empty(&tlb_inval->pending_fences))
mod_delayed_work(tlb_inval->timeout_wq,
&tlb_inval->fence_tdr,
tlb_inval->ops->timeout_delay(tlb_inval));
else
cancel_delayed_work(&tlb_inval->fence_tdr);
spin_unlock_irqrestore(&tlb_inval->pending_lock, flags);
}
static const char *
xe_inval_fence_get_driver_name(struct dma_fence *dma_fence)
{
return "xe";
}
static const char *
xe_inval_fence_get_timeline_name(struct dma_fence *dma_fence)
{
return "tlb_inval_fence";
}
static const struct dma_fence_ops inval_fence_ops = {
.get_driver_name = xe_inval_fence_get_driver_name,
.get_timeline_name = xe_inval_fence_get_timeline_name,
};
void xe_tlb_inval_fence_init(struct xe_tlb_inval *tlb_inval,
struct xe_tlb_inval_fence *fence,
bool stack)
{
xe_pm_runtime_get_noresume(tlb_inval->xe);
spin_lock_irq(&tlb_inval->lock);
dma_fence_init(&fence->base, &inval_fence_ops, &tlb_inval->lock,
dma_fence_context_alloc(1), 1);
spin_unlock_irq(&tlb_inval->lock);
INIT_LIST_HEAD(&fence->link);
if (stack)
set_bit(FENCE_STACK_BIT, &fence->base.flags);
else
dma_fence_get(&fence->base);
fence->tlb_inval = tlb_inval;
}
bool xe_tlb_inval_idle(struct xe_tlb_inval *tlb_inval)
{
lockdep_assert_held(&tlb_inval->seqno_lock);
guard(spinlock_irq)(&tlb_inval->pending_lock);
return list_is_singular(&tlb_inval->pending_fences);
}
void xe_tlb_inval_batch_wait(struct xe_tlb_inval_batch *batch)
{
struct xe_tlb_inval_fence *fence = &batch->fence[0];
unsigned int i;
for (i = 0; i < batch->num_fences; ++i)
xe_tlb_inval_fence_wait(fence++);
batch->num_fences = 0;
}
int xe_tlb_inval_range_tilemask_submit(struct xe_device *xe, u32 asid,
u64 start, u64 end, u8 tile_mask,
struct xe_tlb_inval_batch *batch)
{
struct xe_tlb_inval_fence *fence = &batch->fence[0];
struct xe_tile *tile;
u32 fence_id = 0;
u8 id;
int err = 0;
batch->num_fences = 0;
if (!tile_mask)
return 0;
for_each_tile(tile, xe, id) {
if (!(tile_mask & BIT(id)))
continue;
xe_tlb_inval_fence_init(&tile->primary_gt->tlb_inval,
&fence[fence_id], true);
err = xe_tlb_inval_range(&tile->primary_gt->tlb_inval,
&fence[fence_id], start, end,
asid, NULL);
if (err)
goto wait;
++fence_id;
if (!tile->media_gt)
continue;
xe_tlb_inval_fence_init(&tile->media_gt->tlb_inval,
&fence[fence_id], true);
err = xe_tlb_inval_range(&tile->media_gt->tlb_inval,
&fence[fence_id], start, end,
asid, NULL);
if (err)
goto wait;
++fence_id;
}
wait:
batch->num_fences = fence_id;
if (err)
xe_tlb_inval_batch_wait(batch);
return err;
}