Re: [RFC 08/15] drm/xe: Implement xe_access_counter_queue_work

Matthew Brost
2026-04-02T22:06:30Z

On Wed, Mar 18, 2026 at 01:14:49PM +0530, Himal Prasad Ghimiray wrote:
> Implement worker function to dequeue and process access counter
> notifications and migrate bo based vma to vram and rebind it.
>
> Signed-off-by: Himal Prasad Ghimiray <himal.prasad.ghimiray@intel.com>

Again apoligize for streams of replies but one question.

> ---
> drivers/gpu/drm/xe/xe_access_counter.c | 139 ++++++++++++++++++++++++-
> 1 file changed, 138 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/gpu/drm/xe/xe_access_counter.c b/drivers/gpu/drm/xe/xe_access_counter.c
> index a2ce9dc45d05..9eb9917d8da7 100644
> --- a/drivers/gpu/drm/xe/xe_access_counter.c
> +++ b/drivers/gpu/drm/xe/xe_access_counter.c
> @@ -11,7 +11,10 @@
> #include "xe_access_counter.h"
> #include "xe_access_counter_types.h"
> #include "xe_device.h"
> +#include "xe_gt_printk.h"
> +#include "xe_hw_engine.h"
> #include "xe_usm_queue.h"
> +#include "xe_vm.h"
>
> /**
> * DOC: Xe access counters
> @@ -33,9 +36,143 @@ static int xe_access_counter_entry_size(void)
> return roundup_pow_of_two(sizeof(struct xe_access_counter));
> }
>
> +static int xe_access_counter_sub_granularity_in_byte(int val)
> +{
> + return xe_access_counter_granularity_in_byte(val) / 32;
> +}
> +
> +static struct xe_vma *xe_access_counter_get_vma(struct xe_vm *vm,
> + struct xe_access_counter *ac)
> +{
> + u64 page_va;
> +
> + if (ac->consumer.granularity != XE_ACCESS_COUNTER_GRANULARITY_128K) {
> + page_va = ac->consumer.va_range_base;
> + } else {
> + page_va = ac->consumer.va_range_base +
> + (ffs(ac->consumer.sub_granularity) - 1) *
> + xe_access_counter_sub_granularity_in_byte(ac->consumer.granularity);
> + }
> +
> + return xe_vm_find_overlapping_vma(vm, page_va, SZ_4K);

I can’t find any documentation on how the granularity works—do you have
a bspec link? Or, if there’s an internal document, could you send me a
message with a link? Without documentation, this may be a dumb
question...

Can the granularity ever indicate that VA + length is the hot region to
migrate? If so, wouldn’t it be possible for multiple VMAs or ranges to
need migration? In that case, we should handle it—probably as an
iterative process, re-queuing work items to migrate a single VMA/range
in order to unblock higher-priority page faults that may be occurring.

Matt

> +}
> +
> +static void xe_access_counter_print(struct xe_access_counter *ac)
> +{
> + xe_gt_dbg(ac->gt, "\n\tASID: %d\n"
> + "\tVA Range Base: 0x%08x%08x\n"
> + "\tCounter Type: %d\n"
> + "\tGranularity: %d\n"
> + "\tSub-Granularity: 0x%08x\n"
> + "\tEngineClass: %d %s\n"
> + "\tEngineInstance: %d\n",
> + ac->consumer.xe3.asid,
> + upper_32_bits(ac->consumer.va_range_base),
> + lower_32_bits(ac->consumer.va_range_base),
> + ac->consumer.counter_type,
> + ac->consumer.granularity,
> + ac->consumer.sub_granularity,
> + ac->consumer.xe3.engine_class,
> + xe_hw_engine_class_to_str(ac->consumer.xe3.engine_class),
> + ac->consumer.xe3.engine_instance);
> +}
> +
> +static int xe_access_counter_service(struct xe_access_counter *ac)
> +{
> + struct xe_gt *gt = ac->gt;
> + struct xe_device *xe = gt_to_xe(gt);
> + struct xe_tile *tile = gt_to_tile(gt);
> + struct xe_validation_ctx ctx;
> + struct drm_exec exec;
> + struct dma_fence *fence;
> + struct xe_vm *vm;
> + struct xe_vma *vma;
> + int err = 0;
> +
> + if (ac->consumer.counter_type > XE_ACCESS_COUNTER_TYPE_NOTIFY)
> + return -EINVAL;
> +
> + vm = xe_device_asid_to_fault_vm(xe, ac->consumer.xe3.asid);
> + if (IS_ERR(vm))
> + return PTR_ERR(vm);
> +
> + down_write(&vm->lock);
> +
> + if (xe_vm_is_closed(vm)) {
> + err = -ENOENT;
> + goto unlock_vm;
> + }
> + /* Lookup VMA */
> + vma = xe_access_counter_get_vma(vm, ac);
> + if (!vma) {
> + err = -EINVAL;
> + goto unlock_vm;
> + }
> +
> + /* TODO: Handle svm vma's */
> + if (xe_vma_has_no_bo(vma))
> + goto unlock_vm;
> +
> + /* Lock VM and BOs dma-resv */
> + xe_validation_ctx_init(&ctx, &vm->xe->val, &exec, (struct xe_val_flags) {});
> + drm_exec_until_all_locked(&exec) {
> + err = xe_vma_lock_and_validate(&exec, vma, tile->mem.vram, true);
> + drm_exec_retry_on_contention(&exec);
> + xe_validation_retry_on_oom(&ctx, &err);
> + if (err)
> + break;
> +
> + xe_vm_set_validation_exec(vm, &exec);
> + fence = xe_vma_rebind(vm, vma, BIT(tile->id));
> + xe_vm_set_validation_exec(vm, NULL);
> + if (IS_ERR(fence))
> + err = PTR_ERR(fence);
> + }
> +
> + if (!err && !IS_ERR(fence)) {
> + dma_fence_wait(fence, false);
> + dma_fence_put(fence);
> + }
> +
> + xe_validation_ctx_fini(&ctx);
> +
> +unlock_vm:
> + up_write(&vm->lock);
> + xe_vm_put(vm);
> +
> + return err;
> +}
> +
> static void xe_access_counter_queue_work_func(struct work_struct *w)
> {
> - /* TODO: Implement */
> + struct xe_usm_queue *ac_queue =
> + container_of(w, typeof(*ac_queue), worker);
> + struct xe_access_counter ac = {};
> + unsigned long threshold;
> +
> +#define USM_QUEUE_MAX_RUNTIME_MS 20
> + threshold = jiffies + msecs_to_jiffies(USM_QUEUE_MAX_RUNTIME_MS);
> +
> + while (xe_usm_queue_pop(ac_queue, &ac, xe_access_counter_entry_size())) {
> + int err;
> +
> + if (!ac.gt) /* Access counter squashed during reset */
> + continue;
> +
> + err = xe_access_counter_service(&ac);
> + if (err) {
> + xe_access_counter_print(&ac);
> + xe_gt_dbg(ac.gt, "Access counter handling: Unsuccessful %pe\n",
> + ERR_PTR(err));
> + }
> +
> + if (time_after(jiffies, threshold) &&
> + ac_queue->tail != ac_queue->head) {
> + queue_work(gt_to_xe(ac.gt)->usm.pf_wq, w);
> + break;
> + }
> + }
> +#undef USM_QUEUE_MAX_RUNTIME_MS
> }
>
> static int xe_access_counter_queue_init(struct xe_device *xe,
> --
> 2.34.1
>