mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Adrian Larumbe <adrian.larumbe@collabora.com>
To: Boris Brezillon <boris.brezillon@collabora.com>
Cc: Steven Price <steven.price@arm.com>,
	Liviu Dudau <liviu.dudau@arm.com>,
	 Chris Diamand <chris.diamand@arm.com>,
	Akash Goel <akash.goel@arm.com>,
	 Maarten Lankhorst <maarten.lankhorst@linux.intel.com>,
	Maxime Ripard <mripard@kernel.org>,
	 Thomas Zimmermann <tzimmermann@suse.de>,
	David Airlie <airlied@gmail.com>,
	 Simona Vetter <simona@ffwll.ch>,
	dri-devel@lists.freedesktop.org, linux-kernel@vger.kernel.org
Subject: Re: [PATCH v4 08/18] drm/panthor: Split panthor_vm
Date: Fri, 11 Sep 2026 23:55:13 +0100	[thread overview]
Message-ID: <aqSGVCOJvJhxeVVA@sobremesa> (raw)
In-Reply-To: <20260826-panthor-unplug-fixes-v4-8-982cc8f4234b@collabora.com>

On 26.08.2026 16:56, Boris Brezillon wrote:
> The way things are currently defined makes the cleanup procedure harder
> because the panthor_vm object cleanup happens after drm_gpuvm_fini() has
> been called, and sometimes we need a drm_gpuvm to undo things.
> This has been worked around by things like the panthor_vm_unmap_range()
> call in panthor_vm_destroy(), but there are still situations where this
> is problematic, like the show_each_vm() where we walk a list of VM and
> call drm_debugfs_gpuva_info() on each, with the risk of hitting an object
> that had drm_gpuvm_fini() called on it already.
> 
> There's more of these tricky situations to come when we get to making
> the unplug logic more robust, so let's address the problem ahead of it
> and split the panthor_vm object in two:
> 
> - panthor_as: this is the object embedding drm_gpuvm and more
>   generally dealing with page table updates/residency
> - panthor_vm: this is the user-visible object wrapping around
>   panthor_as. Among other things, it contains the scheduler for
>   the bind queue and the drm_mm tree for kernel BO allocation.
>   This object owns a drm_gpuvm ref.
> 
> With this in place, we can do the cleanup steps that need a valid
> drm_gpuvm object in panthor_vm_release(), and the rest is cleaned up
> in panthor_as_free().
> 
> Note that there's a bunch of s/as[_nr]/slot/ variable/argument renames
> to clear the confusion between the AS slot number and the newly
> introduced panthor_as object.
> 
> Signed-off-by: Boris Brezillon <boris.brezillon@collabora.com>
> ---
>  drivers/gpu/drm/panthor/panthor_mmu.c | 1085 ++++++++++++++++++---------------
>  1 file changed, 580 insertions(+), 505 deletions(-)
> 
> diff --git a/drivers/gpu/drm/panthor/panthor_mmu.c b/drivers/gpu/drm/panthor/panthor_mmu.c
> index 44a44602b8ba..933cb820926d 100644
> --- a/drivers/gpu/drm/panthor/panthor_mmu.c
> +++ b/drivers/gpu/drm/panthor/panthor_mmu.c
> @@ -41,14 +41,14 @@
>  
>  #define MAX_AS_SLOTS			32
>  
> -struct panthor_vm;
> +struct panthor_as;
>  
>  /**
>   * struct panthor_as_slot - Address space slot
>   */
>  struct panthor_as_slot {
> -	/** @vm: VM bound to this slot. NULL is no VM is bound. */
> -	struct panthor_vm *vm;
> +	/** @as: AS bound to this slot. NULL if no AS is bound. */
> +	struct panthor_as *as;
>  };
>  
>  /**
> @@ -87,19 +87,19 @@ struct panthor_mmu {
>  		/** @as.faulty_mask: Bitmask encoding the faulty slots. */
>  		unsigned long faulty_mask;
>  
> -		/** @as.slots: VMs currently bound to the AS slots. */
> +		/** @as.slots: AS currently bound to the AS slots. */
>  		struct panthor_as_slot slots[MAX_AS_SLOTS];
>  
>  		/**
> -		 * @as.lru_list: List of least recently used VMs.
> +		 * @as.lru_list: List of least recently used AS.
>  		 *
> -		 * We use this list to pick a VM to evict when all slots are
> +		 * We use this list to pick an AS to evict when all slots are
>  		 * used.
>  		 *
> -		 * There should be no more active VMs than there are AS slots,
> -		 * so this LRU is just here to keep VMs bound until there's
> -		 * a need to release a slot, thus avoid unnecessary TLB/cache
> -		 * flushes.
> +		 * There should be no more active AS than there are AS slots,
> +		 * so this LRU is just here to keep page tables bound until
> +		 * there's a need to release a slot, thus avoiding unnecessary
> +		 * TLB/cache flushes.
>  		 */
>  		struct list_head lru_list;
>  	} as;
> @@ -163,9 +163,9 @@ struct panthor_vma {
>  };
>  
>  /**
> - * struct panthor_vm_op_ctx - VM operation context
> + * struct panthor_as_op_ctx - AS operation context
>   *
> - * With VM operations potentially taking place in a dma-signaling path, we
> + * With AS operations potentially taking place in a dma-signaling path, we
>   * need to make sure everything that might require resource allocation is
>   * pre-allocated upfront. This is what this operation context is far.
>   *
> @@ -173,7 +173,7 @@ struct panthor_vma {
>   * asynchronously, and let the VM_BIND scheduler process the next VM_BIND
>   * request.
>   */
> -struct panthor_vm_op_ctx {
> +struct panthor_as_op_ctx {
>  	/** @rsvd_page_tables: Pages reserved for the MMU page table update. */
>  	struct {
>  		/** @rsvd_page_tables.count: Number of pages reserved. */
> @@ -225,13 +225,130 @@ struct panthor_vm_op_ctx {
>  	} map;
>  };
>  
> +/**
> + * struct panthor_as - Used to managed a GPU address space.
> + */
> +struct panthor_as {
> +	/**
> +	 * @base: Inherit from drm_gpuvm.
> +	 *
> +	 * We delegate all the VA management to the common drm_gpuvm framework
> +	 * and only implement hooks to update the MMU page table.
> +	 */
> +	struct drm_gpuvm base;
> +
> +	/** @memattr: Value to program to the AS_MEMATTR register. */
> +	u64 memattr;
> +
> +	/** @pt: Page table fields. */
> +	struct {
> +		/** @pt.ops: Page table ops. */
> +		struct io_pgtable_ops *ops;
> +
> +		/** @pt.root: Page table root. */
> +		void *root;
> +	} pt;
> +
> +	/**
> +	 * @op_lock: Lock used to serialize operations on the AS.
> +	 *
> +	 * The serialization of jobs queued to the VM_BIND queue is already
> +	 * taken care of by drm_sched, but we need to serialize synchronous
> +	 * and asynchronous VM_BIND request. This is what this lock is for.
> +	 */
> +	struct mutex op_lock;
> +
> +	/**
> +	 * @op_ctx: The context attached to the currently executing operation.
> +	 *
> +	 * NULL when no operation is in progress.
> +	 */
> +	struct panthor_as_op_ctx *op_ctx;
> +
> +	/** @active_cnt: Number of active users of this address space. */
> +	refcount_t active_cnt;
> +
> +	/** @hw_slot: Hardware slot related fields. */
> +	struct {
> +		/**
> +		 * @hw_slot.id: ID of the slot this AS is bound to.
> +		 *
> +		 * A value of -1 means the AS is inactive/not bound.
> +		 */
> +		int id;
> +
> +		/**
> +		 * @hw_slot.lru_node: Used to insert the AS in panthor_mmu::as::lru_list.
> +		 *
> +		 * Active ASs should not be inserted in the LRU list.
> +		 */
> +		struct list_head lru_node;
> +	} hw_slot;
> +
> +	/**
> +	 * @unusable: True if the AS has turned unusable because something
> +	 * bad happened during an asynchronous request.
> +	 *
> +	 * We don't try to recover from such failures, because this implies
> +	 * informing userspace about the specific operation that failed, and
> +	 * hoping the userspace driver can replay things from there. This all
> +	 * sounds very complicated for little gain.
> +	 *
> +	 * Instead, we should just flag the AS as unusable, and fail any
> +	 * further request targeting this AS.
> +	 *
> +	 * We also provide a way to query an AS state, so userspace can
> +	 * destroy it and create a new one.
> +	 *
> +	 * As an analogy, this would be mapped to a VK_ERROR_DEVICE_LOST
> +	 * situation, where the logical device needs to be re-created.
> +	 */
> +	bool unusable;
> +
> +	/**
> +	 * @unhandled_fault: Unhandled fault happened.
> +	 *
> +	 * This should be reported to the scheduler, and the queue/group be
> +	 * flagged as faulty as a result.
> +	 */
> +	bool unhandled_fault;
> +
> +	/** @locked_region: Information about the currently locked region currently. */
> +	struct {
> +		/** @locked_region.start: Start of the locked region. */
> +		u64 start;
> +
> +		/** @locked_region.size: Size of the locked region. */
> +		u64 size;
> +	} locked_region;
> +
> +	/** @reclaim: Fields related to BO reclaim. */
> +	struct {
> +		/** @reclaim.lru: LRU of BOs that are only mapped to this AS. */
> +		struct drm_gem_lru lru;
> +
> +		/**
> +		 * @reclaim.lru_node: Node used to insert the AS in
> +		 * panthor_device::reclaim::vms.
> +		 */
> +		struct list_head lru_node;
> +
> +		/**
> +		 * @skip: Skip reclaim on this AS.
> +		 *
> +		 * This is set to true when the AS is about to be
> +		 * released to ensure this VM won't participate to reclaim.
> +		 * The VM should go away pretty soon and release its
> +		 * resources anyway.
> +		 */
> +		bool skip;
> +	} reclaim;
> +};
> +
>  /**
>   * struct panthor_vm - VM object
>   *
>   * A VM is an object representing a GPU (or MCU) virtual address space.
> - * It embeds the MMU page table for this address space, a tree containing
> - * all the virtual mappings of GEM objects, and other things needed to manage
> - * the VM.
>   *
>   * Except for the MCU VM, which is managed by the kernel, all other VMs are
>   * created by userspace and mostly managed by userspace, using the
> @@ -243,13 +360,11 @@ struct panthor_vm_op_ctx {
>   * by default).
>   */
>  struct panthor_vm {
> -	/**
> -	 * @base: Inherit from drm_gpuvm.
> -	 *
> -	 * We delegate all the VA management to the common drm_gpuvm framework
> -	 * and only implement hooks to update the MMU page table.
> -	 */
> -	struct drm_gpuvm base;
> +	/** @refcount: VM refcount. */
> +	struct kref refcount;
> +
> +	/** @as: VM address space. */
> +	struct panthor_as *as;
>  
>  	/**
>  	 * @sched: Scheduler used for asynchronous VM_BIND request.
> @@ -266,34 +381,6 @@ struct panthor_vm {
>  	 */
>  	struct drm_sched_entity entity;
>  
> -	/** @ptdev: Device. */
> -	struct panthor_device *ptdev;
> -
> -	/** @memattr: Value to program to the AS_MEMATTR register. */
> -	u64 memattr;
> -
> -	/** @pgtbl_ops: Page table operations. */
> -	struct io_pgtable_ops *pgtbl_ops;
> -
> -	/** @root_page_table: Stores the root page table pointer. */
> -	void *root_page_table;
> -
> -	/**
> -	 * @op_lock: Lock used to serialize operations on a VM.
> -	 *
> -	 * The serialization of jobs queued to the VM_BIND queue is already
> -	 * taken care of by drm_sched, but we need to serialize synchronous
> -	 * and asynchronous VM_BIND request. This is what this lock is for.
> -	 */
> -	struct mutex op_lock;
> -
> -	/**
> -	 * @op_ctx: The context attached to the currently executing VM operation.
> -	 *
> -	 * NULL when no operation is in progress.
> -	 */
> -	struct panthor_vm_op_ctx *op_ctx;
> -
>  	/**
>  	 * @mm: Memory management object representing the auto-VA/kernel-VA.
>  	 *
> @@ -323,26 +410,6 @@ struct panthor_vm {
>  	/** @user_va_range: Upper boundary of VAs VM users can map objects against. */
>  	u64 user_va_range;
>  
> -	/** @as: Address space related fields. */
> -	struct {
> -		/**
> -		 * @as.id: ID of the address space this VM is bound to.
> -		 *
> -		 * A value of -1 means the VM is inactive/not bound.
> -		 */
> -		int id;
> -
> -		/** @as.active_cnt: Number of active users of this VM. */
> -		refcount_t active_cnt;
> -
> -		/**
> -		 * @as.lru_node: Used to instead the VM in the panthor_mmu::as::lru_list.
> -		 *
> -		 * Active VMs should not be inserted in the LRU list.
> -		 */
> -		struct list_head lru_node;
> -	} as;
> -
>  	/**
>  	 * @heaps: Tiler heap related fields.
>  	 */
> @@ -371,55 +438,6 @@ struct panthor_vm {
>  	 */
>  	bool destroyed;
>  
> -	/**
> -	 * @unusable: True if the VM has turned unusable because something
> -	 * bad happened during an asynchronous request.
> -	 *
> -	 * We don't try to recover from such failures, because this implies
> -	 * informing userspace about the specific operation that failed, and
> -	 * hoping the userspace driver can replay things from there. This all
> -	 * sounds very complicated for little gain.
> -	 *
> -	 * Instead, we should just flag the VM as unusable, and fail any
> -	 * further request targeting this VM.
> -	 *
> -	 * We also provide a way to query a VM state, so userspace can destroy
> -	 * it and create a new one.
> -	 *
> -	 * As an analogy, this would be mapped to a VK_ERROR_DEVICE_LOST
> -	 * situation, where the logical device needs to be re-created.
> -	 */
> -	bool unusable;
> -
> -	/**
> -	 * @unhandled_fault: Unhandled fault happened.
> -	 *
> -	 * This should be reported to the scheduler, and the queue/group be
> -	 * flagged as faulty as a result.
> -	 */
> -	bool unhandled_fault;
> -
> -	/** @locked_region: Information about the currently locked region currently. */
> -	struct {
> -		/** @locked_region.start: Start of the locked region. */
> -		u64 start;
> -
> -		/** @locked_region.size: Size of the locked region. */
> -		u64 size;
> -	} locked_region;
> -
> -	/** @reclaim: Fields related to BO reclaim. */
> -	struct {
> -		/** @reclaim.lru: LRU of BOs that are only mapped to this VM. */
> -		struct drm_gem_lru lru;
> -
> -		/**
> -		 * @reclaim.lru_node: Node used to insert the VM in
> -		 * panthor_device::reclaim::vms.
> -		 */
> -		struct list_head lru_node;
> -	} reclaim;
> -
>  	/**
>  	 * @dummy: Dummy object used for sparse mappings.
>  	 *
> @@ -447,7 +465,7 @@ struct panthor_vm_bind_job {
>  	struct panthor_vm *vm;
>  
>  	/** @ctx: Operation context. */
> -	struct panthor_vm_op_ctx ctx;
> +	struct panthor_as_op_ctx ctx;
>  };
>  
>  /**
> @@ -466,36 +484,37 @@ struct panthor_vm_bind_job {
>   */
>  static void *alloc_pt(void *cookie, size_t size, gfp_t gfp)
>  {
> -	struct panthor_vm *vm = cookie;
> +	struct panthor_as *as = cookie;
> +	struct panthor_as_op_ctx *op_ctx = as->op_ctx;
> +	struct drm_device *ddev = as->base.drm;
>  	void *page;
>  
>  	/* Allocation of the root page table happening during init. */
> -	if (unlikely(!vm->root_page_table)) {
> +	if (unlikely(!as->pt.root)) {
> +		struct device *dev = drm_dev_dma_dev(ddev);
>  		struct page *p;
>  
> -		drm_WARN_ON(&vm->ptdev->base, vm->op_ctx);
> -		p = alloc_pages_node(dev_to_node(vm->ptdev->base.dev),
> -				     gfp | __GFP_ZERO, get_order(size));
> +		drm_WARN_ON(ddev, op_ctx);
> +		p = alloc_pages_node(dev_to_node(dev), gfp | __GFP_ZERO, get_order(size));
>  		page = p ? page_address(p) : NULL;
> -		vm->root_page_table = page;
> +		as->pt.root = page;
>  		return page;
>  	}
>  
>  	/* We're not supposed to have anything bigger than 4k here, because we picked a
>  	 * 4k granule size at init time.
>  	 */
> -	if (drm_WARN_ON(&vm->ptdev->base, size != SZ_4K))
> +	if (drm_WARN_ON(ddev, size != SZ_4K))
>  		return NULL;
>  
>  	/* We must have some op_ctx attached to the VM and it must have at least one
>  	 * free page.
>  	 */
> -	if (drm_WARN_ON(&vm->ptdev->base, !vm->op_ctx) ||
> -	    drm_WARN_ON(&vm->ptdev->base,
> -			vm->op_ctx->rsvd_page_tables.ptr >= vm->op_ctx->rsvd_page_tables.count))
> +	if (drm_WARN_ON(ddev, !op_ctx) ||
> +	    drm_WARN_ON(ddev, op_ctx->rsvd_page_tables.ptr >= op_ctx->rsvd_page_tables.count))
>  		return NULL;
>  
> -	page = vm->op_ctx->rsvd_page_tables.pages[vm->op_ctx->rsvd_page_tables.ptr++];
> +	page = op_ctx->rsvd_page_tables.pages[op_ctx->rsvd_page_tables.ptr++];
>  	memset(page, 0, SZ_4K);
>  
>  	/* Page table entries don't use virtual addresses, which trips out
> @@ -518,22 +537,23 @@ static void *alloc_pt(void *cookie, size_t size, gfp_t gfp)
>   */
>  static void free_pt(void *cookie, void *data, size_t size)
>  {
> -	struct panthor_vm *vm = cookie;
> +	struct panthor_as *as = cookie;
> +	struct panthor_device *ptdev = container_of(as->base.drm, struct panthor_device, base);
>  
> -	if (unlikely(vm->root_page_table == data)) {
> +	if (unlikely(as->pt.root == data)) {
>  		free_pages((unsigned long)data, get_order(size));
> -		vm->root_page_table = NULL;
> +		as->pt.root = NULL;
>  		return;
>  	}
>  
> -	if (drm_WARN_ON(&vm->ptdev->base, size != SZ_4K))
> +	if (drm_WARN_ON(&ptdev->base, size != SZ_4K))
>  		return;
>  
>  	/* Return the page to the pt_cache. */
> -	kmem_cache_free(vm->ptdev->mmu->pt_cache, data);
> +	kmem_cache_free(ptdev->mmu->pt_cache, data);
>  }
>  
> -static int wait_ready(struct panthor_device *ptdev, u32 as_nr)
> +static int wait_ready(struct panthor_device *ptdev, u32 slot)
>  {
>  	struct panthor_mmu *mmu = ptdev->mmu;
>  	int ret;
> @@ -542,7 +562,7 @@ static int wait_ready(struct panthor_device *ptdev, u32 as_nr)
>  	/* Wait for the MMU status to indicate there is no active command, in
>  	 * case one is pending.
>  	 */
> -	ret = gpu_read_relaxed_poll_timeout_atomic(mmu->iomem, AS_STATUS(as_nr), val,
> +	ret = gpu_read_relaxed_poll_timeout_atomic(mmu->iomem, AS_STATUS(slot), val,
>  						   !(val & AS_STATUS_AS_ACTIVE), 10, 100000);
>  
>  	if (ret) {
> @@ -553,15 +573,15 @@ static int wait_ready(struct panthor_device *ptdev, u32 as_nr)
>  	return ret;
>  }
>  
> -static int as_send_cmd_and_wait(struct panthor_device *ptdev, u32 as_nr, u32 cmd)
> +static int as_send_cmd_and_wait(struct panthor_device *ptdev, u32 slot, u32 cmd)
>  {
>  	int status;
>  
>  	/* write AS_COMMAND when MMU is ready to accept another command */
> -	status = wait_ready(ptdev, as_nr);
> +	status = wait_ready(ptdev, slot);
>  	if (!status) {
> -		gpu_write(ptdev->mmu->iomem, AS_COMMAND(as_nr), cmd);
> -		status = wait_ready(ptdev, as_nr);
> +		gpu_write(ptdev->mmu->iomem, AS_COMMAND(slot), cmd);
> +		status = wait_ready(ptdev, slot);
>  	}
>  
>  	return status;
> @@ -596,37 +616,37 @@ static u64 pack_region_range(struct panthor_device *ptdev, u64 *region_start, u6
>  	return region_width | *region_start;
>  }
>  
> -static u32 panthor_mmu_as_fault_mask(struct panthor_device *ptdev, u32 as)
> +static u32 panthor_mmu_as_fault_mask(struct panthor_device *ptdev, u32 slot)
>  {
> -	return BIT(as);
> +	return BIT(slot);
>  }
>  
> -static int panthor_mmu_as_enable(struct panthor_device *ptdev, u32 as_nr,
> +static int panthor_mmu_as_enable(struct panthor_device *ptdev, u32 slot,
>  				 u64 transtab, u64 transcfg, u64 memattr)
>  {
>  	struct panthor_mmu *mmu = ptdev->mmu;
>  
>  	panthor_irq_enable_events(&ptdev->mmu->irq,
> -				  panthor_mmu_as_fault_mask(ptdev, as_nr));
> +				  panthor_mmu_as_fault_mask(ptdev, slot));
>  
> -	gpu_write64(mmu->iomem, AS_TRANSTAB(as_nr), transtab);
> -	gpu_write64(mmu->iomem, AS_MEMATTR(as_nr), memattr);
> -	gpu_write64(mmu->iomem, AS_TRANSCFG(as_nr), transcfg);
> +	gpu_write64(mmu->iomem, AS_TRANSTAB(slot), transtab);
> +	gpu_write64(mmu->iomem, AS_MEMATTR(slot), memattr);
> +	gpu_write64(mmu->iomem, AS_TRANSCFG(slot), transcfg);
>  
> -	return as_send_cmd_and_wait(ptdev, as_nr, AS_COMMAND_UPDATE);
> +	return as_send_cmd_and_wait(ptdev, slot, AS_COMMAND_UPDATE);
>  }
>  
> -static int panthor_mmu_as_disable(struct panthor_device *ptdev, u32 as_nr,
> +static int panthor_mmu_as_disable(struct panthor_device *ptdev, u32 slot,
>  				  bool recycle_slot)
>  {
>  	struct panthor_mmu *mmu = ptdev->mmu;
> -	struct panthor_vm *vm = ptdev->mmu->as.slots[as_nr].vm;
> +	struct panthor_as *as = ptdev->mmu->as.slots[slot].as;
>  	int ret;
>  
>  	lockdep_assert_held(&ptdev->mmu->as.slots_lock);
>  
>  	panthor_irq_disable_events(&ptdev->mmu->irq,
> -				   panthor_mmu_as_fault_mask(ptdev, as_nr));
> +				   panthor_mmu_as_fault_mask(ptdev, slot));
>  
>  	/* Flush+invalidate RW caches, invalidate RO ones. */
>  	ret = panthor_gpu_flush_caches(ptdev, CACHE_CLEAN | CACHE_INV,
> @@ -634,9 +654,9 @@ static int panthor_mmu_as_disable(struct panthor_device *ptdev, u32 as_nr,
>  	if (ret)
>  		return ret;
>  
> -	if (vm && vm->locked_region.size) {
> +	if (as && as->locked_region.size) {
>  		/* Unlock the region if there's a lock pending. */
> -		ret = as_send_cmd_and_wait(ptdev, vm->as.id, AS_COMMAND_UNLOCK);
> +		ret = as_send_cmd_and_wait(ptdev, slot, AS_COMMAND_UNLOCK);
>  		if (ret)
>  			return ret;
>  	}
> @@ -647,11 +667,11 @@ static int panthor_mmu_as_disable(struct panthor_device *ptdev, u32 as_nr,
>  	if (recycle_slot)
>  		return 0;
>  
> -	gpu_write64(mmu->iomem, AS_TRANSTAB(as_nr), 0);
> -	gpu_write64(mmu->iomem, AS_MEMATTR(as_nr), 0);
> -	gpu_write64(mmu->iomem, AS_TRANSCFG(as_nr), AS_TRANSCFG_ADRMODE_UNMAPPED);
> +	gpu_write64(mmu->iomem, AS_TRANSTAB(slot), 0);
> +	gpu_write64(mmu->iomem, AS_MEMATTR(slot), 0);
> +	gpu_write64(mmu->iomem, AS_TRANSCFG(slot), AS_TRANSCFG_ADRMODE_UNMAPPED);
>  
> -	return as_send_cmd_and_wait(ptdev, as_nr, AS_COMMAND_UPDATE);
> +	return as_send_cmd_and_wait(ptdev, slot, AS_COMMAND_UPDATE);
>  }
>  
>  static u32 panthor_mmu_fault_mask(struct panthor_device *ptdev, u32 value)
> @@ -668,7 +688,7 @@ static u32 panthor_mmu_fault_mask(struct panthor_device *ptdev, u32 value)
>   */
>  bool panthor_vm_has_unhandled_faults(struct panthor_vm *vm)
>  {
> -	return vm->unhandled_fault;
> +	return vm->as->unhandled_fault;
>  }
>  
>  /**
> @@ -679,23 +699,23 @@ bool panthor_vm_has_unhandled_faults(struct panthor_vm *vm)
>   */
>  bool panthor_vm_is_unusable(struct panthor_vm *vm)
>  {
> -	return vm->unusable;
> +	return vm->as->unusable;
>  }
>  
> -static void panthor_vm_release_as_locked(struct panthor_vm *vm)
> +static void panthor_as_release_hw_slot_locked(struct panthor_as *as)
>  {
> -	struct panthor_device *ptdev = vm->ptdev;
> +	struct panthor_device *ptdev = container_of(as->base.drm, struct panthor_device, base);
>  
>  	lockdep_assert_held(&ptdev->mmu->as.slots_lock);
>  
> -	if (drm_WARN_ON(&ptdev->base, vm->as.id < 0))
> +	if (drm_WARN_ON(&ptdev->base, as->hw_slot.id < 0))
>  		return;
>  
> -	ptdev->mmu->as.slots[vm->as.id].vm = NULL;
> -	clear_bit(vm->as.id, &ptdev->mmu->as.alloc_mask);
> -	refcount_set(&vm->as.active_cnt, 0);
> -	list_del_init(&vm->as.lru_node);
> -	vm->as.id = -1;
> +	ptdev->mmu->as.slots[as->hw_slot.id].as = NULL;
> +	clear_bit(as->hw_slot.id, &ptdev->mmu->as.alloc_mask);
> +	refcount_set(&as->active_cnt, 0);
> +	list_del_init(&as->hw_slot.lru_node);
> +	as->hw_slot.id = -1;
>  }
>  
>  /**
> @@ -708,17 +728,18 @@ static void panthor_vm_release_as_locked(struct panthor_vm *vm)
>   */
>  int panthor_vm_active(struct panthor_vm *vm)
>  {
> -	struct panthor_device *ptdev = vm->ptdev;
> +	struct panthor_as *as = vm->as;
> +	struct panthor_device *ptdev = container_of(as->base.drm, struct panthor_device, base);
>  	u32 va_bits = GPU_MMU_FEATURES_VA_BITS(ptdev->gpu_info.mmu_features);
> -	struct io_pgtable_cfg *cfg = &io_pgtable_ops_to_pgtable(vm->pgtbl_ops)->cfg;
> -	int ret = 0, as, cookie;
> +	struct io_pgtable_cfg *cfg = &io_pgtable_ops_to_pgtable(as->pt.ops)->cfg;
> +	int ret = 0, slot, cookie;
>  	u64 transtab, transcfg;
>  	u32 fault_mask;
>  
>  	if (!drm_dev_enter(&ptdev->base, &cookie))
>  		return -ENODEV;
>  
> -	if (refcount_inc_not_zero(&vm->as.active_cnt))
> +	if (refcount_inc_not_zero(&as->active_cnt))
>  		goto out_dev_exit;
>  
>  	/* As soon as active is called, we place the VM at the end of the VM LRU.
> @@ -727,25 +748,25 @@ int panthor_vm_active(struct panthor_vm *vm)
>  	 * that's an acceptable trade-off.
>  	 */
>  	mutex_lock(&ptdev->base.gem_lru_mutex);
> -	if (vm->reclaim.lru.count)
> -		list_move_tail(&vm->reclaim.lru_node, &ptdev->reclaim.vms);
> +	if (as->reclaim.lru.count && !as->reclaim.skip)
> +		list_move_tail(&as->reclaim.lru_node, &ptdev->reclaim.vms);
>  	mutex_unlock(&ptdev->base.gem_lru_mutex);
>  
>  	/* Make sure we don't race with lock/unlock_region() calls
>  	 * happening around VM bind operations.
>  	 */
> -	mutex_lock(&vm->op_lock);
> +	mutex_lock(&as->op_lock);
>  	mutex_lock(&ptdev->mmu->as.slots_lock);
>  
> -	if (refcount_inc_not_zero(&vm->as.active_cnt))
> +	if (refcount_inc_not_zero(&as->active_cnt))
>  		goto out_unlock;
>  
> -	as = vm->as.id;
> -	if (as >= 0) {
> +	slot = as->hw_slot.id;
> +	if (slot >= 0) {
>  		/* Unhandled pagefault on this AS, the MMU was disabled. We need to
>  		 * re-enable the MMU after clearing+unmasking the AS interrupts.
>  		 */
> -		if (ptdev->mmu->as.faulty_mask & panthor_mmu_as_fault_mask(ptdev, as))
> +		if (ptdev->mmu->as.faulty_mask & panthor_mmu_as_fault_mask(ptdev, slot))
>  			goto out_enable_as;
>  
>  		goto out_make_active;
> @@ -754,36 +775,36 @@ int panthor_vm_active(struct panthor_vm *vm)
>  	/* Check for a free AS */
>  	if (vm->for_mcu) {
>  		drm_WARN_ON(&ptdev->base, ptdev->mmu->as.alloc_mask & BIT(0));
> -		as = 0;
> +		slot = 0;
>  	} else {
> -		as = ffz(ptdev->mmu->as.alloc_mask | BIT(0));
> +		slot = ffz(ptdev->mmu->as.alloc_mask | BIT(0));
>  	}
>  
> -	if (!(BIT(as) & ptdev->gpu_info.as_present)) {
> -		struct panthor_vm *lru_vm;
> +	if (!(BIT(slot) & ptdev->gpu_info.as_present)) {
> +		struct panthor_as *lru_as;
>  
> -		lru_vm = list_first_entry_or_null(&ptdev->mmu->as.lru_list,
> -						  struct panthor_vm,
> -						  as.lru_node);
> -		if (drm_WARN_ON(&ptdev->base, !lru_vm)) {
> +		lru_as = list_first_entry_or_null(&ptdev->mmu->as.lru_list,
> +						  struct panthor_as,
> +						  hw_slot.lru_node);
> +		if (drm_WARN_ON(&ptdev->base, !lru_as)) {
>  			ret = -EBUSY;
>  			goto out_unlock;
>  		}
>  
> -		drm_WARN_ON(&ptdev->base, refcount_read(&lru_vm->as.active_cnt));
> -		as = lru_vm->as.id;
> +		drm_WARN_ON(&ptdev->base, refcount_read(&lru_as->active_cnt));
> +		slot = lru_as->hw_slot.id;
>  
> -		ret = panthor_mmu_as_disable(ptdev, as, true);
> +		ret = panthor_mmu_as_disable(ptdev, slot, true);
>  		if (ret)
>  			goto out_unlock;
>  
> -		panthor_vm_release_as_locked(lru_vm);
> +		panthor_as_release_hw_slot_locked(lru_as);
>  	}
>  
>  	/* Assign the free or reclaimed AS to the FD */
> -	vm->as.id = as;
> -	set_bit(as, &ptdev->mmu->as.alloc_mask);
> -	ptdev->mmu->as.slots[as].vm = vm;
> +	as->hw_slot.id = slot;
> +	set_bit(slot, &ptdev->mmu->as.alloc_mask);
> +	ptdev->mmu->as.slots[slot].as = as;
>  
>  out_enable_as:
>  	transtab = cfg->arm_lpae_s1_cfg.ttbr;
> @@ -795,12 +816,12 @@ int panthor_vm_active(struct panthor_vm *vm)
>  		transcfg |= AS_TRANSCFG_PTW_SH_OS;
>  
>  	/* If the VM is re-activated, we clear the fault. */
> -	vm->unhandled_fault = false;
> +	as->unhandled_fault = false;
>  
>  	/* Unhandled pagefault on this AS, clear the fault and enable the AS,
>  	 * which re-enables interrupts.
>  	 */
> -	fault_mask = panthor_mmu_as_fault_mask(ptdev, as);
> +	fault_mask = panthor_mmu_as_fault_mask(ptdev, slot);
>  	if (ptdev->mmu->as.faulty_mask & fault_mask) {
>  		gpu_write(ptdev->mmu->irq.iomem, INT_CLEAR, fault_mask);
>  		ptdev->mmu->as.faulty_mask &= ~fault_mask;
> @@ -809,18 +830,18 @@ int panthor_vm_active(struct panthor_vm *vm)
>  	/* The VM update is guarded by ::op_lock, which we take at the beginning
>  	 * of this function, so we don't expect any locked region here.
>  	 */
> -	drm_WARN_ON(&vm->ptdev->base, vm->locked_region.size > 0);
> -	ret = panthor_mmu_as_enable(vm->ptdev, vm->as.id, transtab, transcfg, vm->memattr);
> +	drm_WARN_ON(&ptdev->base, as->locked_region.size > 0);
> +	ret = panthor_mmu_as_enable(ptdev, as->hw_slot.id, transtab, transcfg, as->memattr);
>  
>  out_make_active:
>  	if (!ret) {
> -		refcount_set(&vm->as.active_cnt, 1);
> -		list_del_init(&vm->as.lru_node);
> +		refcount_set(&as->active_cnt, 1);
> +		list_del_init(&as->hw_slot.lru_node);
>  	}
>  
>  out_unlock:
>  	mutex_unlock(&ptdev->mmu->as.slots_lock);
> -	mutex_unlock(&vm->op_lock);
> +	mutex_unlock(&as->op_lock);
>  
>  out_dev_exit:
>  	drm_dev_exit(cookie);
> @@ -842,21 +863,22 @@ int panthor_vm_active(struct panthor_vm *vm)
>   */
>  void panthor_vm_idle(struct panthor_vm *vm)
>  {
> -	struct panthor_device *ptdev = vm->ptdev;
> +	struct panthor_as *as = vm->as;
> +	struct panthor_device *ptdev = container_of(as->base.drm, struct panthor_device, base);
>  
> -	if (!refcount_dec_and_mutex_lock(&vm->as.active_cnt, &ptdev->mmu->as.slots_lock))
> +	if (!refcount_dec_and_mutex_lock(&as->active_cnt, &ptdev->mmu->as.slots_lock))
>  		return;
>  
> -	if (!drm_WARN_ON(&ptdev->base, vm->as.id == -1 || !list_empty(&vm->as.lru_node)))
> -		list_add_tail(&vm->as.lru_node, &ptdev->mmu->as.lru_list);
> +	if (!drm_WARN_ON(&ptdev->base, as->hw_slot.id == -1 || !list_empty(&as->hw_slot.lru_node)))
> +		list_add_tail(&as->hw_slot.lru_node, &ptdev->mmu->as.lru_list);
>  
> -	refcount_set(&vm->as.active_cnt, 0);
> +	refcount_set(&as->active_cnt, 0);
>  	mutex_unlock(&ptdev->mmu->as.slots_lock);
>  }
>  
>  u32 panthor_vm_page_size(struct panthor_vm *vm)
>  {
> -	const struct io_pgtable *pgt = io_pgtable_ops_to_pgtable(vm->pgtbl_ops);
> +	const struct io_pgtable *pgt = io_pgtable_ops_to_pgtable(vm->as->pt.ops);
>  	u32 pg_shift = ffs(pgt->cfg.pgsize_bitmap) - 1;
>  
>  	return 1u << pg_shift;
> @@ -880,7 +902,7 @@ static void panthor_vm_start(struct panthor_vm *vm)
>   */
>  int panthor_vm_as(struct panthor_vm *vm)
>  {
> -	return vm->as.id;
> +	return vm->as->hw_slot.id;
>  }
>  
>  static size_t get_pgsize(u64 addr, size_t size, size_t *count)
> @@ -904,43 +926,43 @@ static size_t get_pgsize(u64 addr, size_t size, size_t *count)
>  	return SZ_2M;
>  }
>  
> -static void panthor_vm_declare_unusable(struct panthor_vm *vm)
> +static void panthor_as_declare_unusable(struct panthor_as *as)
>  {
> -	struct panthor_device *ptdev = vm->ptdev;
> +	struct panthor_device *ptdev = container_of(as->base.drm, struct panthor_device, base);
>  	int cookie;
>  
> -	if (vm->unusable)
> +	if (as->unusable)
>  		return;
>  
> -	vm->unusable = true;
> +	as->unusable = true;
>  	mutex_lock(&ptdev->mmu->as.slots_lock);
> -	if (vm->as.id >= 0 && drm_dev_enter(&ptdev->base, &cookie)) {
> -		panthor_mmu_as_disable(ptdev, vm->as.id, false);
> +	if (as->hw_slot.id >= 0 && drm_dev_enter(&ptdev->base, &cookie)) {
> +		panthor_mmu_as_disable(ptdev, as->hw_slot.id, false);
>  		drm_dev_exit(cookie);
>  	}
>  	mutex_unlock(&ptdev->mmu->as.slots_lock);
>  }
>  
> -static void panthor_vm_unmap_pages(struct panthor_vm *vm, u64 iova, u64 size)
> +static void panthor_as_unmap_pages(struct panthor_as *as, u64 iova, u64 size)
>  {
> -	struct panthor_device *ptdev = vm->ptdev;
> -	struct io_pgtable_ops *ops = vm->pgtbl_ops;
> +	struct drm_device *ddev = as->base.drm;
> +	struct io_pgtable_ops *ops = as->pt.ops;
>  	u64 start_iova = iova;
>  	u64 offset = 0;
>  
>  	if (!size)
>  		return;
>  
> -	drm_WARN_ON(&ptdev->base,
> -		    (iova < vm->locked_region.start) ||
> -		    (iova + size > vm->locked_region.start + vm->locked_region.size));
> +	drm_WARN_ON(ddev,
> +		    (iova < as->locked_region.start) ||
> +		    (iova + size > as->locked_region.start + as->locked_region.size));
>  
>  	while (offset < size) {
>  		size_t unmapped_sz = 0, pgcount;
>  		size_t pgsize = get_pgsize(iova + offset, size - offset, &pgcount);
>  
>  		unmapped_sz = ops->unmap_pages(ops, iova + offset, pgsize, pgcount, NULL);
> -		if (drm_WARN_ON_ONCE(&ptdev->base, unmapped_sz != pgsize * pgcount)) {
> +		if (drm_WARN_ON_ONCE(ddev, unmapped_sz != pgsize * pgcount)) {
>  			/* Gracefully handle sparsely unmapped regions to avoid leaving
>  			 * page table pages behind when the drm_gpuvm and VM page table
>  			 * are out-of-sync. This is not supposed to happen, hence the
> @@ -954,33 +976,32 @@ static void panthor_vm_unmap_pages(struct panthor_vm *vm, u64 iova, u64 size)
>  			 * so flag the VM unusable to make sure it's not going
>  			 * to be used anymore.
>  			 */
> -			panthor_vm_declare_unusable(vm);
> +			panthor_as_declare_unusable(as);
>  
>  			/* If we don't make progress, we're screwed. That also means
>  			 * something else prevents us from unmapping the region, but
>  			 * there's not much we can do here: time for debugging.
>  			 */
> -			if (drm_WARN_ON_ONCE(&ptdev->base, !unmapped_sz))
> +			if (drm_WARN_ON_ONCE(ddev, !unmapped_sz))
>  				return;
>  		}
>  
> -		drm_dbg(&ptdev->base,
> -			"unmap: as=%d, iova=0x%llx, sz=%llu, va=0x%llx, pgcnt=%zu, pgsz=%zu",
> -			vm->as.id, start_iova, size, iova + offset,
> -			unmapped_sz / pgsize, pgsize);
> +		drm_dbg(ddev,
> +			"unmap: iova=0x%llx, sz=%llu, va=0x%llx, pgcnt=%zu, pgsz=%zu",
> +			start_iova, size, iova + offset, unmapped_sz / pgsize, pgsize);
>  
>  		offset += unmapped_sz;
>  	}
>  }
>  
>  static int
> -panthor_vm_map_pages(struct panthor_vm *vm, u64 iova, int prot,
> +panthor_as_map_pages(struct panthor_as *as, u64 iova, int prot,
>  		     struct sg_table *sgt, u64 offset, u64 size)
>  {
> -	struct panthor_device *ptdev = vm->ptdev;
> +	struct drm_device *ddev = as->base.drm;
>  	unsigned int count;
>  	struct scatterlist *sgl;
> -	struct io_pgtable_ops *ops = vm->pgtbl_ops;
> +	struct io_pgtable_ops *ops = as->pt.ops;
>  	u64 start_iova = iova;
>  	u64 start_size = size;
>  	int ret;
> @@ -988,9 +1009,9 @@ panthor_vm_map_pages(struct panthor_vm *vm, u64 iova, int prot,
>  	if (!size)
>  		return 0;
>  
> -	drm_WARN_ON(&ptdev->base,
> -		    (iova < vm->locked_region.start) ||
> -		    (iova + size > vm->locked_region.start + vm->locked_region.size));
> +	drm_WARN_ON(ddev,
> +		    (iova < as->locked_region.start) ||
> +		    (iova + size > as->locked_region.start + as->locked_region.size));
>  
>  	for_each_sgtable_dma_sg(sgt, sgl, count) {
>  		dma_addr_t paddr = sg_dma_address(sgl);
> @@ -1013,10 +1034,9 @@ panthor_vm_map_pages(struct panthor_vm *vm, u64 iova, int prot,
>  			ret = ops->map_pages(ops, iova, paddr, pgsize, pgcount, prot,
>  					     GFP_KERNEL, &mapped);
>  
> -			drm_dbg(&ptdev->base,
> -				"map: as=%d, iova=0x%llx, sz=%llu, va=0x%llx, pa=%pad, pgcnt=%zu, pgsz=%zu",
> -				vm->as.id, start_iova, start_size, iova, &paddr,
> -				mapped / pgsize, pgsize);
> +			drm_dbg(ddev,
> +				"map: iova=0x%llx, sz=%llu, va=0x%llx, pa=%pad, pgcnt=%zu, pgsz=%zu",
> +				start_iova, start_size, iova, &paddr, mapped / pgsize, pgsize);
>  
>  			iova += mapped;
>  			paddr += mapped;
> @@ -1027,12 +1047,12 @@ panthor_vm_map_pages(struct panthor_vm *vm, u64 iova, int prot,
>  				ret = -ENOMEM;
>  
>  			/* If something fails, we stop there, and flag the VM unusable. */
> -			if (drm_WARN_ON_ONCE(&ptdev->base, ret)) {
> +			if (drm_WARN_ON_ONCE(ddev, ret)) {
>  				/* Unmap what we've already mapped to avoid leaving page
>  				 * table pages behind.
>  				 */
> -				panthor_vm_unmap_pages(vm, start_iova, iova - start_iova);
> -				panthor_vm_declare_unusable(vm);
> +				panthor_as_unmap_pages(as, start_iova, iova - start_iova);
> +				panthor_as_declare_unusable(as);
>  				return ret;
>  			}
>  		}
> @@ -1047,8 +1067,8 @@ panthor_vm_map_pages(struct panthor_vm *vm, u64 iova, int prot,
>  }
>  
>  static int
> -panthor_vm_map_sparse(struct panthor_vm *vm, u64 iova, int prot,
> -		      struct sg_table *sgt, u64 size)
> +panthor_as_map_sparse(struct panthor_as *as, u64 iova,
> +		      int prot, struct sg_table *sgt, u64 size)
>  {
>  	u64 mapped = 0;
>  	int ret;
> @@ -1057,10 +1077,10 @@ panthor_vm_map_sparse(struct panthor_vm *vm, u64 iova, int prot,
>  		u64 addr = iova + mapped;
>  		u32 chunk_size = min(size - mapped, SZ_2M - (addr & (SZ_2M - 1)));
>  
> -		ret = panthor_vm_map_pages(vm, addr, prot, sgt,
> +		ret = panthor_as_map_pages(as, addr, prot, sgt,
>  					   addr % SZ_2M, chunk_size);
>  		if (ret) {
> -			panthor_vm_unmap_pages(vm, iova, mapped);
> +			panthor_as_unmap_pages(as, iova, mapped);
>  			return ret;
>  		}
>  
> @@ -1162,9 +1182,10 @@ static void panthor_vm_bo_free(struct drm_gpuvm_bo *vm_bo)
>  	kfree(vm_bo);
>  }
>  
> -static void panthor_vm_cleanup_op_ctx(struct panthor_vm_op_ctx *op_ctx,
> -				      struct panthor_vm *vm)
> +static void panthor_as_cleanup_op_ctx(struct panthor_as_op_ctx *op_ctx,
> +				      struct panthor_as *as)
>  {
> +	struct panthor_device *ptdev = container_of(as->base.drm, struct panthor_device, base);
>  	u32 remaining_pt_count = op_ctx->rsvd_page_tables.count -
>  				 op_ctx->rsvd_page_tables.ptr;
>  	u32 op_type = op_ctx->flags & DRM_PANTHOR_VM_BIND_OP_TYPE_MASK;
> @@ -1179,7 +1200,7 @@ static void panthor_vm_cleanup_op_ctx(struct panthor_vm_op_ctx *op_ctx,
>  				     !op_ctx->map.bo;
>  
>  	if (remaining_pt_count) {
> -		kmem_cache_free_bulk(vm->ptdev->mmu->pt_cache,
> +		kmem_cache_free_bulk(ptdev->mmu->pt_cache,
>  				     remaining_pt_count,
>  				     op_ctx->rsvd_page_tables.pages +
>  				     op_ctx->rsvd_page_tables.ptr);
> @@ -1199,11 +1220,11 @@ static void panthor_vm_cleanup_op_ctx(struct panthor_vm_op_ctx *op_ctx,
>  		kfree(op_ctx->preallocated_vmas[i]);
>  
>  	if (!skip_deferred_cleanup)
> -		drm_gpuvm_bo_deferred_cleanup(&vm->base);
> +		drm_gpuvm_bo_deferred_cleanup(&as->base);
>  }
>  
>  static void
> -panthor_vm_op_ctx_return_vma(struct panthor_vm_op_ctx *op_ctx,
> +panthor_as_op_ctx_return_vma(struct panthor_as_op_ctx *op_ctx,
>  			     struct panthor_vma *vma)
>  {
>  	for (u32 i = 0; i < ARRAY_SIZE(op_ctx->preallocated_vmas); i++) {
> @@ -1217,7 +1238,7 @@ panthor_vm_op_ctx_return_vma(struct panthor_vm_op_ctx *op_ctx,
>  }
>  
>  static struct panthor_vma *
> -panthor_vm_op_ctx_get_vma(struct panthor_vm_op_ctx *op_ctx)
> +panthor_as_op_ctx_get_vma(struct panthor_as_op_ctx *op_ctx)
>  {
>  	for (u32 i = 0; i < ARRAY_SIZE(op_ctx->preallocated_vmas); i++) {
>  		struct panthor_vma *vma = op_ctx->preallocated_vmas[i];
> @@ -1232,7 +1253,7 @@ panthor_vm_op_ctx_get_vma(struct panthor_vm_op_ctx *op_ctx)
>  }
>  
>  static int
> -panthor_vm_op_ctx_prealloc_vmas(struct panthor_vm_op_ctx *op_ctx)
> +panthor_as_op_ctx_prealloc_vmas(struct panthor_as_op_ctx *op_ctx)
>  {
>  	u32 vma_count;
>  
> @@ -1271,7 +1292,7 @@ panthor_vm_op_ctx_prealloc_vmas(struct panthor_vm_op_ctx *op_ctx)
>  	return 0;
>  }
>  
> -static void panthor_vm_init_op_ctx(struct panthor_vm_op_ctx *op_ctx,
> +static void panthor_vm_init_op_ctx(struct panthor_as_op_ctx *op_ctx,
>  				   u64 size, u64 va, u32 flags)
>  {
>  	memset(op_ctx, 0, sizeof(*op_ctx));
> @@ -1280,8 +1301,8 @@ static void panthor_vm_init_op_ctx(struct panthor_vm_op_ctx *op_ctx,
>  	op_ctx->va.addr = va;
>  }
>  
> -static int panthor_vm_op_ctx_prealloc_pts(struct panthor_device *ptdev,
> -					  struct panthor_vm_op_ctx *op_ctx)
> +static int panthor_as_op_ctx_prealloc_pts(struct panthor_device *ptdev,
> +					  struct panthor_as_op_ctx *op_ctx)
>  {
>  	u64 size = op_ctx->va.range;
>  	u64 va = op_ctx->va.addr;
> @@ -1317,11 +1338,12 @@ static int panthor_vm_op_ctx_prealloc_pts(struct panthor_device *ptdev,
>  	 DRM_PANTHOR_VM_BIND_OP_MAP_SPARSE | \
>  	 DRM_PANTHOR_VM_BIND_OP_TYPE_MASK)
>  
> -static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx,
> -					 struct panthor_vm *vm,
> +static int panthor_as_prepare_map_op_ctx(struct panthor_as_op_ctx *op_ctx,
> +					 struct panthor_as *as,
>  					 struct panthor_gem_object *bo,
>  					 const struct drm_panthor_vm_bind_op *op)
>  {
> +	struct panthor_device *ptdev = container_of(as->base.drm, struct panthor_device, base);
>  	bool is_sparse = op->flags & DRM_PANTHOR_VM_BIND_OP_MAP_SPARSE;
>  	struct drm_gpuvm_bo *preallocated_vm_bo;
>  	struct sg_table *sgt = NULL;
> @@ -1353,12 +1375,12 @@ static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx,
>  
>  	/* If the BO has an exclusive VM attached, it can't be mapped to other VMs. */
>  	if (bo->exclusive_vm_root_gem &&
> -	    bo->exclusive_vm_root_gem != panthor_vm_root_gem(vm))
> +	    bo->exclusive_vm_root_gem != as->base.r_obj)
>  		return -EINVAL;
>  
>  	panthor_vm_init_op_ctx(op_ctx, op->size, op->va, op->flags);
>  
> -	ret = panthor_vm_op_ctx_prealloc_vmas(op_ctx);
> +	ret = panthor_as_op_ctx_prealloc_vmas(op_ctx);
>  	if (ret)
>  		goto err_cleanup;
>  
> @@ -1378,7 +1400,7 @@ static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx,
>  		goto err_cleanup;
>  	}
>  
> -	preallocated_vm_bo = drm_gpuvm_bo_create(&vm->base, &bo->base);
> +	preallocated_vm_bo = drm_gpuvm_bo_create(&as->base, &bo->base);
>  	if (!preallocated_vm_bo) {
>  		ret = -ENOMEM;
>  		goto err_cleanup;
> @@ -1387,15 +1409,15 @@ static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx,
>  	op_ctx->map.vm_bo = drm_gpuvm_bo_obtain_prealloc(preallocated_vm_bo);
>  	op_ctx->map.bo_offset = op->bo_offset;
>  
> -	ret = panthor_vm_op_ctx_prealloc_pts(vm->ptdev, op_ctx);
> +	ret = panthor_as_op_ctx_prealloc_pts(ptdev, op_ctx);
>  	if (ret)
>  		goto err_cleanup;
>  
>  	/* Insert BO into the extobj list last, when we know nothing can fail. */
> -	if (bo->base.resv != panthor_vm_resv(vm)) {
> -		dma_resv_lock(panthor_vm_resv(vm), NULL);
> +	if (bo->base.resv != drm_gpuvm_resv(&as->base)) {
> +		dma_resv_lock(drm_gpuvm_resv(&as->base), NULL);
>  		drm_gpuvm_bo_extobj_add(op_ctx->map.vm_bo);
> -		dma_resv_unlock(panthor_vm_resv(vm));
> +		dma_resv_unlock(drm_gpuvm_resv(&as->base));
>  	}
>  
>  	/* And finally update the BO state. */
> @@ -1408,14 +1430,15 @@ static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx,
>  	return 0;
>  
>  err_cleanup:
> -	panthor_vm_cleanup_op_ctx(op_ctx, vm);
> +	panthor_as_cleanup_op_ctx(op_ctx, as);
>  	return ret;
>  }
>  
> -static int panthor_vm_prepare_unmap_op_ctx(struct panthor_vm_op_ctx *op_ctx,
> -					   struct panthor_vm *vm,
> +static int panthor_as_prepare_unmap_op_ctx(struct panthor_as_op_ctx *op_ctx,
> +					   struct panthor_as *as,
>  					   u64 va, u64 size)
>  {
> +	struct panthor_device *ptdev = container_of(as->base.drm, struct panthor_device, base);
>  	u32 pt_count = 0;
>  	int ret;
>  
> @@ -1434,7 +1457,7 @@ static int panthor_vm_prepare_unmap_op_ctx(struct panthor_vm_op_ctx *op_ctx,
>  	    ALIGN(va + size, SZ_2M) != ALIGN(va, SZ_2M))
>  		pt_count++;
>  
> -	ret = panthor_vm_op_ctx_prealloc_vmas(op_ctx);
> +	ret = panthor_as_op_ctx_prealloc_vmas(op_ctx);
>  	if (ret)
>  		goto err_cleanup;
>  
> @@ -1446,7 +1469,7 @@ static int panthor_vm_prepare_unmap_op_ctx(struct panthor_vm_op_ctx *op_ctx,
>  			goto err_cleanup;
>  		}
>  
> -		if (!kmem_cache_alloc_bulk(vm->ptdev->mmu->pt_cache,
> +		if (!kmem_cache_alloc_bulk(ptdev->mmu->pt_cache,
>  					   GFP_KERNEL, pt_count,
>  					   op_ctx->rsvd_page_tables.pages)) {
>  			ret = -ENOMEM;
> @@ -1458,12 +1481,12 @@ static int panthor_vm_prepare_unmap_op_ctx(struct panthor_vm_op_ctx *op_ctx,
>  	return 0;
>  
>  err_cleanup:
> -	panthor_vm_cleanup_op_ctx(op_ctx, vm);
> +	panthor_as_cleanup_op_ctx(op_ctx, as);
>  	return ret;
>  }
>  
>  static void
> -panthor_vm_prepare_sync_only_op_ctx(struct panthor_vm_op_ctx *op_ctx)
> +panthor_as_prepare_sync_only_op_ctx(struct panthor_as_op_ctx *op_ctx)
>  {
>  	memset(op_ctx, 0, sizeof(*op_ctx));
>  	op_ctx->flags = DRM_PANTHOR_VM_BIND_OP_TYPE_SYNC_ONLY;
> @@ -1491,8 +1514,8 @@ panthor_vm_get_bo_for_va(struct panthor_vm *vm, u64 va, u64 *bo_offset)
>  	struct panthor_vma *vma;
>  
>  	/* Take the VM lock to prevent concurrent map/unmap operations. */
> -	mutex_lock(&vm->op_lock);
> -	gpuva = drm_gpuva_find_first(&vm->base, va, 1);
> +	mutex_lock(&vm->as->op_lock);
> +	gpuva = drm_gpuva_find_first(&vm->as->base, va, 1);
>  	vma = gpuva ? container_of(gpuva, struct panthor_vma, base) : NULL;
>  	if (vma && vma->base.gem.obj) {
>  		drm_gem_object_get(vma->base.gem.obj);
> @@ -1501,7 +1524,7 @@ panthor_vm_get_bo_for_va(struct panthor_vm *vm, u64 va, u64 *bo_offset)
>  			vma->base.gem.offset + (va - vma->base.va.addr) :
>  			va & (SZ_2M - 1);
>  	}
> -	mutex_unlock(&vm->op_lock);
> +	mutex_unlock(&vm->as->op_lock);
>  
>  	return bo;
>  }
> @@ -1621,22 +1644,24 @@ int panthor_vm_pool_create_vm(struct panthor_device *ptdev,
>  
>  static void panthor_vm_destroy(struct panthor_vm *vm)
>  {
> +	struct panthor_as *as;
> +	struct panthor_device *ptdev;
> +
>  	if (!vm)
>  		return;
>  
> +	as = vm->as;
> +	ptdev = container_of(as->base.drm, struct panthor_device, base);
>  	vm->destroyed = true;
>  
>  	/* Tell scheduler to stop all GPU work related to this VM */
> -	if (refcount_read(&vm->as.active_cnt) > 0)
> -		panthor_sched_prepare_for_vm_destruction(vm->ptdev);
> +	if (refcount_read(&as->active_cnt) > 0)
> +		panthor_sched_prepare_for_vm_destruction(ptdev);
>  
>  	mutex_lock(&vm->heaps.lock);
>  	panthor_heap_pool_destroy(vm->heaps.pool);
>  	vm->heaps.pool = NULL;
>  	mutex_unlock(&vm->heaps.lock);
> -
> -	drm_WARN_ON(&vm->ptdev->base,
> -		    panthor_vm_unmap_range(vm, vm->base.mm_start, vm->base.mm_range));
>  	panthor_vm_put(vm);
>  }
>  
> @@ -1776,17 +1801,18 @@ static const char *access_type_name(struct panthor_device *ptdev,
>  	}
>  }
>  
> -static int panthor_vm_lock_region(struct panthor_vm *vm, u64 start, u64 size)
> +static int panthor_as_lock_region(struct panthor_as *as, u64 start, u64 size)
>  {
> -	struct panthor_device *ptdev = vm->ptdev;
> +	struct drm_device *ddev = as->base.drm;
> +	struct panthor_device *ptdev = container_of(ddev, struct panthor_device, base);
>  	int ret = 0;
>  
> -	/* sm_step_remap() can call panthor_vm_lock_region() to account for
> +	/* sm_step_remap() can call panthor_as_lock_region() to account for
>  	 * the wider unmap needed when doing a partial huge page unamp. We
>  	 * need to ignore the lock if it's already part of the locked region.
>  	 */
> -	if (start >= vm->locked_region.start &&
> -	    start + size <= vm->locked_region.start + vm->locked_region.size)
> +	if (start >= as->locked_region.start &&
> +	    start + size <= as->locked_region.start + as->locked_region.size)
>  		return 0;
>  
>  	/* sm_step_remap() may need a locked region that isn't a strict superset
> @@ -1797,42 +1823,42 @@ static int panthor_vm_lock_region(struct panthor_vm *vm, u64 start, u64 size)
>  	 * boundaries in a remap operation can only shift up or down respectively,
>  	 * but never otherwise.
>  	 */
> -	if (vm->locked_region.size) {
> -		u64 end = max(vm->locked_region.start + vm->locked_region.size,
> +	if (as->locked_region.size) {
> +		u64 end = max(as->locked_region.start + as->locked_region.size,
>  			      start + size);
>  
> -		drm_WARN_ON_ONCE(&vm->ptdev->base, (start + size <= vm->locked_region.start) ||
> -				 (start >= vm->locked_region.start + vm->locked_region.size));
> +		drm_WARN_ON_ONCE(ddev, (start + size <= as->locked_region.start) ||
> +				 (start >= as->locked_region.start + as->locked_region.size));
>  
> -		start = min(start, vm->locked_region.start);
> +		start = min(start, as->locked_region.start);
>  		size = end - start;
>  	}
>  
>  	mutex_lock(&ptdev->mmu->as.slots_lock);
> -	if (vm->as.id >= 0 && size) {
> +	if (as->hw_slot.id >= 0 && size) {
>  		/* Lock the region that needs to be updated */
> -		gpu_write64(ptdev->mmu->iomem, AS_LOCKADDR(vm->as.id),
> +		gpu_write64(ptdev->mmu->iomem, AS_LOCKADDR(as->hw_slot.id),
>  			    pack_region_range(ptdev, &start, &size));
>  
>  		/* If the lock succeeded, update the locked_region info. */
> -		ret = as_send_cmd_and_wait(ptdev, vm->as.id, AS_COMMAND_LOCK);
> +		ret = as_send_cmd_and_wait(ptdev, as->hw_slot.id, AS_COMMAND_LOCK);
>  	}
>  
>  	if (!ret) {
> -		vm->locked_region.start = start;
> -		vm->locked_region.size = size;
> +		as->locked_region.start = start;
> +		as->locked_region.size = size;
>  	}
>  	mutex_unlock(&ptdev->mmu->as.slots_lock);
>  
>  	return ret;
>  }
>  
> -static void panthor_vm_unlock_region(struct panthor_vm *vm)
> +static void panthor_as_unlock_region(struct panthor_as *as)
>  {
> -	struct panthor_device *ptdev = vm->ptdev;
> +	struct panthor_device *ptdev = container_of(as->base.drm, struct panthor_device, base);
>  
>  	mutex_lock(&ptdev->mmu->as.slots_lock);
> -	if (vm->as.id >= 0) {
> +	if (as->hw_slot.id >= 0) {
>  		int ret;
>  
>  		/* flush+invalidate RW caches and invalidate RO ones.
> @@ -1845,7 +1871,7 @@ static void panthor_vm_unlock_region(struct panthor_vm *vm)
>  
>  		/* Unlock the region if the flush is effective. */
>  		if (!ret)
> -			ret = as_send_cmd_and_wait(ptdev, vm->as.id, AS_COMMAND_UNLOCK);
> +			ret = as_send_cmd_and_wait(ptdev, as->hw_slot.id, AS_COMMAND_UNLOCK);
>  
>  		/* If we fail to flush or unlock the region, schedule a GPU reset
>  		 * to unblock the situation.
> @@ -1853,8 +1879,8 @@ static void panthor_vm_unlock_region(struct panthor_vm *vm)
>  		if (ret)
>  			panthor_device_schedule_reset(ptdev);
>  	}
> -	vm->locked_region.start = 0;
> -	vm->locked_region.size = 0;
> +	as->locked_region.start = 0;
> +	as->locked_region.size = 0;
>  	mutex_unlock(&ptdev->mmu->as.slots_lock);
>  }
>  
> @@ -1908,8 +1934,8 @@ static void panthor_mmu_irq_handler(struct panthor_irq *pirq, u32 status)
>  		 */
>  		gpu_write(mmu->irq.iomem, INT_CLEAR, mask);
>  
> -		if (ptdev->mmu->as.slots[as].vm)
> -			ptdev->mmu->as.slots[as].vm->unhandled_fault = true;
> +		if (ptdev->mmu->as.slots[as].as)
> +			ptdev->mmu->as.slots[as].as->unhandled_fault = true;
>  
>  		/* Disable the MMU to kill jobs on this AS. */
>  		panthor_mmu_as_disable(ptdev, as, false);
> @@ -1942,12 +1968,12 @@ void panthor_mmu_suspend(struct panthor_device *ptdev)
>  {
>  	mutex_lock(&ptdev->mmu->as.slots_lock);
>  	for (u32 i = 0; i < ARRAY_SIZE(ptdev->mmu->as.slots); i++) {
> -		struct panthor_vm *vm = ptdev->mmu->as.slots[i].vm;
> +		struct panthor_as *as = ptdev->mmu->as.slots[i].as;
>  
> -		if (vm) {
> +		if (as) {
>  			drm_WARN_ON(&ptdev->base,
>  				    panthor_mmu_as_disable(ptdev, i, false));
> -			panthor_vm_release_as_locked(vm);
> +			panthor_as_release_hw_slot_locked(as);
>  		}
>  	}
>  	mutex_unlock(&ptdev->mmu->as.slots_lock);
> @@ -2017,10 +2043,10 @@ void panthor_mmu_post_reset(struct panthor_device *ptdev)
>  	ptdev->mmu->as.faulty_mask = 0;
>  
>  	for (u32 i = 0; i < ARRAY_SIZE(ptdev->mmu->as.slots); i++) {
> -		struct panthor_vm *vm = ptdev->mmu->as.slots[i].vm;
> +		struct panthor_as *as = ptdev->mmu->as.slots[i].as;
>  
> -		if (vm)
> -			panthor_vm_release_as_locked(vm);
> +		if (as)
> +			panthor_as_release_hw_slot_locked(as);
>  	}
>  
>  	mutex_unlock(&ptdev->mmu->as.slots_lock);
> @@ -2036,15 +2062,24 @@ void panthor_mmu_post_reset(struct panthor_device *ptdev)
>  	mutex_unlock(&ptdev->mmu->vm.lock);
>  }
>  
> -static void panthor_vm_free(struct drm_gpuvm *gpuvm)
> +static void panthor_vm_release(struct kref *kref)
>  {
> -	struct panthor_vm *vm = container_of(gpuvm, struct panthor_vm, base);
> -	struct panthor_device *ptdev = vm->ptdev;
> +	struct panthor_vm *vm = container_of(kref, struct panthor_vm, refcount);
> +	struct panthor_as *as = vm->as;
> +	struct panthor_device *ptdev = container_of(as->base.drm, struct panthor_device, base);
>  
> +	/* Make sure the page table behind this VM doesn't participate in reclaim
> +	 * after that point, since we're about to release everything anyway.
> +	 */
>  	mutex_lock(&ptdev->base.gem_lru_mutex);
> -	list_del_init(&vm->reclaim.lru_node);
> +	as->reclaim.skip = true;
> +	list_del_init(&as->reclaim.lru_node);
>  	mutex_unlock(&ptdev->base.gem_lru_mutex);
>  
> +	/* Unmap everything in case some BOs were still mapped. */
> +	drm_WARN_ON(&ptdev->base,
> +		    panthor_vm_unmap_range(vm, as->base.mm_start, as->base.mm_range));
> +
>  	mutex_lock(&vm->heaps.lock);
>  	if (drm_WARN_ON(&ptdev->base, vm->heaps.pool))
>  		panthor_heap_pool_destroy(vm->heaps.pool);
> @@ -2065,29 +2100,26 @@ static void panthor_vm_free(struct drm_gpuvm *gpuvm)
>  	drm_sched_entity_destroy(&vm->entity);
>  	drm_sched_fini(&vm->sched);
>  
> -	mutex_lock(&vm->op_lock);
> +	mutex_lock(&vm->as->op_lock);
>  	mutex_lock(&ptdev->mmu->as.slots_lock);
> -	if (vm->as.id >= 0) {
> +	if (as->hw_slot.id >= 0) {
>  		int cookie;
>  
>  		if (drm_dev_enter(&ptdev->base, &cookie)) {
> -			panthor_mmu_as_disable(ptdev, vm->as.id, false);
> +			panthor_mmu_as_disable(ptdev, as->hw_slot.id, false);
>  			drm_dev_exit(cookie);
>  		}
>  
> -		ptdev->mmu->as.slots[vm->as.id].vm = NULL;
> -		clear_bit(vm->as.id, &ptdev->mmu->as.alloc_mask);
> -		list_del(&vm->as.lru_node);
> +		panthor_as_release_hw_slot_locked(as);
>  	}
>  	mutex_unlock(&ptdev->mmu->as.slots_lock);
> -	mutex_unlock(&vm->op_lock);
> -
> -	free_io_pgtable_ops(vm->pgtbl_ops);
> +	mutex_unlock(&vm->as->op_lock);
>  
>  	if (vm->dummy)
>  		drm_gem_object_put(&vm->dummy->base);
>  
>  	drm_mm_takedown(&vm->mm);
> +	drm_gpuvm_put(&as->base);
>  	kfree(vm);
>  }
>  
> @@ -2097,7 +2129,8 @@ static void panthor_vm_free(struct drm_gpuvm *gpuvm)
>   */
>  void panthor_vm_put(struct panthor_vm *vm)
>  {
> -	drm_gpuvm_put(vm ? &vm->base : NULL);
> +	if (vm)
> +		kref_put(&vm->refcount, panthor_vm_release);
>  }
>  
>  /**
> @@ -2109,7 +2142,7 @@ void panthor_vm_put(struct panthor_vm *vm)
>  struct panthor_vm *panthor_vm_get(struct panthor_vm *vm)
>  {
>  	if (vm)
> -		drm_gpuvm_get(&vm->base);
> +		kref_get(&vm->refcount);
>  
>  	return vm;
>  }
> @@ -2130,6 +2163,8 @@ struct panthor_vm *panthor_vm_get(struct panthor_vm *vm)
>   */
>  struct panthor_heap_pool *panthor_vm_get_heap_pool(struct panthor_vm *vm, bool create)
>  {
> +	struct panthor_device *ptdev = container_of(vm->as->base.drm,
> +						    struct panthor_device, base);
>  	struct panthor_heap_pool *pool;
>  
>  	mutex_lock(&vm->heaps.lock);
> @@ -2137,7 +2172,7 @@ struct panthor_heap_pool *panthor_vm_get_heap_pool(struct panthor_vm *vm, bool c
>  		if (vm->destroyed)
>  			pool = ERR_PTR(-EINVAL);
>  		else
> -			pool = panthor_heap_pool_create(vm->ptdev, vm);
> +			pool = panthor_heap_pool_create(ptdev, vm);
>  
>  		if (!IS_ERR(pool))
>  			vm->heaps.pool = panthor_heap_pool_get(pool);
> @@ -2172,7 +2207,7 @@ void panthor_vm_heaps_sizes(struct panthor_file *pfile, struct drm_memory_stats
>  	xa_for_each(&pfile->vms->xa, i, vm) {
>  		size_t size = panthor_heap_pool_size(vm->heaps.pool);
>  		stats->resident += size;
> -		if (vm->as.id >= 0)
> +		if (vm->as->hw_slot.id >= 0)
>  			stats->active += size;
>  	}
>  	xa_unlock(&pfile->vms->xa);
> @@ -2220,8 +2255,7 @@ static u64 mair_to_memattr(u64 mair, bool coherent)
>  	return memattr;
>  }
>  
> -static void panthor_vma_link(struct panthor_vm *vm,
> -			     struct panthor_vma *vma,
> +static void panthor_vma_link(struct panthor_vma *vma,
>  			     struct drm_gpuvm_bo *vm_bo)
>  {
>  	struct panthor_gem_object *bo = to_panthor_bo(vma->base.gem.obj);
> @@ -2257,25 +2291,25 @@ panthor_fix_sparse_map_offset(struct drm_gpuva_op_map *op, u32 flags)
>  }
>  
>  static int
> -panthor_vm_exec_map_op(struct panthor_vm *vm, u32 flags,
> +panthor_as_exec_map_op(struct panthor_as *as, u32 flags,
>  		       const struct drm_gpuva_op_map *op)
>  {
>  	struct panthor_gem_object *bo = to_panthor_bo(op->gem.obj);
>  	int prot = flags_to_prot(flags);
>  
>  	if (flags & DRM_PANTHOR_VM_BIND_OP_MAP_SPARSE)
> -		return panthor_vm_map_sparse(vm, op->va.addr, prot,
> +		return panthor_as_map_sparse(as, op->va.addr, prot,
>  					     bo->dmap.sgt, op->va.range);
>  
> -	return panthor_vm_map_pages(vm, op->va.addr, prot, bo->dmap.sgt,
> +	return panthor_as_map_pages(as, op->va.addr, prot, bo->dmap.sgt,
>  				    op->gem.offset, op->va.range);
>  }
>  
>  static int panthor_gpuva_sm_step_map(struct drm_gpuva_op *op, void *priv)
>  {
> -	struct panthor_vm *vm = priv;
> -	struct panthor_vm_op_ctx *op_ctx = vm->op_ctx;
> -	struct panthor_vma *vma = panthor_vm_op_ctx_get_vma(op_ctx);
> +	struct panthor_as *as = priv;
> +	struct panthor_as_op_ctx *op_ctx = as->op_ctx;
> +	struct panthor_vma *vma = panthor_as_op_ctx_get_vma(op_ctx);
>  	int ret;
>  
>  	if (!vma)
> @@ -2284,14 +2318,14 @@ static int panthor_gpuva_sm_step_map(struct drm_gpuva_op *op, void *priv)
>  	panthor_vma_init(vma, op_ctx->flags & PANTHOR_VM_MAP_FLAGS);
>  	panthor_fix_sparse_map_offset(&op->map, vma->flags);
>  
> -	ret = panthor_vm_exec_map_op(vm, vma->flags, &op->map);
> +	ret = panthor_as_exec_map_op(as, vma->flags, &op->map);
>  	if (ret) {
> -		panthor_vm_op_ctx_return_vma(op_ctx, vma);
> +		panthor_as_op_ctx_return_vma(op_ctx, vma);
>  		return ret;
>  	}
>  
> -	drm_gpuva_map(&vm->base, &vma->base, &op->map);
> -	panthor_vma_link(vm, vma, op_ctx->map.vm_bo);
> +	drm_gpuva_map(&as->base, &vma->base, &op->map);
> +	panthor_vma_link(vma, op_ctx->map.vm_bo);
>  
>  	drm_gpuvm_bo_put_deferred(op_ctx->map.vm_bo);
>  	op_ctx->map.vm_bo = NULL;
> @@ -2352,8 +2386,8 @@ static int panthor_gpuva_sm_step_remap(struct drm_gpuva_op *op,
>  				       void *priv)
>  {
>  	struct panthor_vma *unmap_vma = container_of(op->remap.unmap->va, struct panthor_vma, base);
> -	struct panthor_vm *vm = priv;
> -	struct panthor_vm_op_ctx *op_ctx = vm->op_ctx;
> +	struct panthor_as *as = priv;
> +	struct panthor_as_op_ctx *op_ctx = as->op_ctx;
>  	struct panthor_vma *prev_vma = NULL, *next_vma = NULL;
>  	u64 unmap_start, unmap_range;
>  	int ret;
> @@ -2379,8 +2413,8 @@ static int panthor_gpuva_sm_step_remap(struct drm_gpuva_op *op,
>  		 * atomicity. panthor_vm_lock_region() bails out early if the new region
>  		 * is already part of the locked region, so no need to do this check here.
>  		 */
> -		panthor_vm_lock_region(vm, unmap_start, unmap_range);
> -		panthor_vm_unmap_pages(vm, unmap_start, unmap_range);
> +		panthor_as_lock_region(as, unmap_start, unmap_range);
> +		panthor_as_unmap_pages(as, unmap_start, unmap_range);
>  	}
>  
>  	if (op->remap.prev) {
> @@ -2396,12 +2430,12 @@ static int panthor_gpuva_sm_step_remap(struct drm_gpuva_op *op,
>  			};
>  			panthor_fix_sparse_map_offset(&map_op, unmap_vma->flags);
>  
> -			ret = panthor_vm_exec_map_op(vm, unmap_vma->flags, &map_op);
> +			ret = panthor_as_exec_map_op(as, unmap_vma->flags, &map_op);
>  			if (ret)
>  				return ret;
>  		}
>  
> -		prev_vma = panthor_vm_op_ctx_get_vma(op_ctx);
> +		prev_vma = panthor_as_op_ctx_get_vma(op_ctx);
>  		panthor_vma_init(prev_vma, unmap_vma->flags);
>  		prev_vma->evicted = unmap_vma->evicted;
>  	}
> @@ -2419,12 +2453,12 @@ static int panthor_gpuva_sm_step_remap(struct drm_gpuva_op *op,
>  			};
>  			panthor_fix_sparse_map_offset(&map_op, unmap_vma->flags);
>  
> -			ret = panthor_vm_exec_map_op(vm, unmap_vma->flags, &map_op);
> +			ret = panthor_as_exec_map_op(as, unmap_vma->flags, &map_op);
>  			if (ret)
>  				return ret;
>  		}
>  
> -		next_vma = panthor_vm_op_ctx_get_vma(op_ctx);
> +		next_vma = panthor_as_op_ctx_get_vma(op_ctx);
>  		panthor_vma_init(next_vma, unmap_vma->flags);
>  		next_vma->evicted = unmap_vma->evicted;
>  	}
> @@ -2439,11 +2473,11 @@ static int panthor_gpuva_sm_step_remap(struct drm_gpuva_op *op,
>  		 * owned by the old mapping which will be released when this
>  		 * mapping is destroyed, we need to grab a ref here.
>  		 */
> -		panthor_vma_link(vm, prev_vma, op->remap.unmap->va->vm_bo);
> +		panthor_vma_link(prev_vma, op->remap.unmap->va->vm_bo);
>  	}
>  
>  	if (next_vma) {
> -		panthor_vma_link(vm, next_vma, op->remap.unmap->va->vm_bo);
> +		panthor_vma_link(next_vma, op->remap.unmap->va->vm_bo);
>  	}
>  
>  	panthor_vma_unlink(unmap_vma);
> @@ -2454,10 +2488,10 @@ static int panthor_gpuva_sm_step_unmap(struct drm_gpuva_op *op,
>  				       void *priv)
>  {
>  	struct panthor_vma *unmap_vma = container_of(op->unmap.va, struct panthor_vma, base);
> -	struct panthor_vm *vm = priv;
> +	struct panthor_as *as = priv;
>  
>  	if (!unmap_vma->evicted) {
> -		panthor_vm_unmap_pages(vm, unmap_vma->base.va.addr,
> +		panthor_as_unmap_pages(as, unmap_vma->base.va.addr,
>  				       unmap_vma->base.va.range);
>  	}
>  
> @@ -2469,7 +2503,7 @@ static int panthor_gpuva_sm_step_unmap(struct drm_gpuva_op *op,
>  void panthor_vm_update_bo_reclaim_lru_locked(struct panthor_gem_object *bo)
>  {
>  	struct panthor_device *ptdev = container_of(bo->base.dev, struct panthor_device, base);
> -	struct panthor_vm *vm = NULL;
> +	struct panthor_as *as = NULL;
>  	struct drm_gpuvm_bo *vm_bo;
>  
>  	dma_resv_assert_held(bo->base.resv);
> @@ -2482,27 +2516,34 @@ void panthor_vm_update_bo_reclaim_lru_locked(struct panthor_gem_object *bo)
>  		/* We're only supposed to have one non-evicted vm_bo in the list if we get
>  		 * there.
>  		 */
> -		drm_WARN_ON(&ptdev->base, vm);
> -		vm = container_of(vm_bo->vm, struct panthor_vm, base);
> +		drm_WARN_ON(&ptdev->base, as);
> +		as = container_of(vm_bo->vm, struct panthor_as, base);
>  
>  		mutex_lock(&ptdev->base.gem_lru_mutex);
> -		drm_gem_lru_move_tail_locked(&vm->reclaim.lru, &bo->base);
> -		if (list_empty(&vm->reclaim.lru_node))
> -			list_move(&vm->reclaim.lru_node, &ptdev->reclaim.vms);
> +		drm_gem_lru_move_tail_locked(&as->reclaim.lru, &bo->base);
> +		if (list_empty(&as->reclaim.lru_node) && !as->reclaim.skip)
> +			list_move(&as->reclaim.lru_node, &ptdev->reclaim.vms);
>  		mutex_unlock(&ptdev->base.gem_lru_mutex);
>  	}
>  }
>  
>  int panthor_vm_evict_bo_mappings_locked(struct panthor_gem_object *bo)
>  {
> +	struct panthor_device *ptdev = container_of(bo->base.dev, struct panthor_device, base);
>  	struct drm_gpuvm_bo *vm_bo;
>  	int ret = 0;
>  
>  	drm_gem_for_each_gpuvm_bo(vm_bo, &bo->base) {
> -		struct panthor_vm *vm = container_of(vm_bo->vm, struct panthor_vm, base);
> +		struct panthor_as *as = container_of(vm_bo->vm, struct panthor_as, base);
>  		struct drm_gpuva *va;
>  
> -		if (!mutex_trylock(&vm->op_lock))
> +		scoped_guard(mutex, &ptdev->base.gem_lru_mutex) {
> +			/* The VM is going away, skip this BO and get back to it later. */
> +			if (!as->reclaim.skip)
> +				return -EAGAIN;
> +		}
> +
> +		if (!mutex_trylock(&as->op_lock))
>  			return -EDEADLK;
>  
>  		/* It can be that the vm_bo was already evicted but a new
> @@ -2531,16 +2572,16 @@ int panthor_vm_evict_bo_mappings_locked(struct panthor_gem_object *bo)
>  			 * will be validated, causing all its evicted VMAs to be repopulated
>  			 * before the job runs. So no GPU fault expected.
>  			 */
> -			ret = panthor_vm_lock_region(vm, va->va.addr, va->va.range);
> +			ret = panthor_as_lock_region(as, va->va.addr, va->va.range);
>  			if (ret)
>  				break;
>  
> -			panthor_vm_unmap_pages(vm, va->va.addr, va->va.range);
> -			panthor_vm_unlock_region(vm);
> +			panthor_as_unmap_pages(as, va->va.addr, va->va.range);
> +			panthor_as_unlock_region(as);
>  			vma->evicted = true;
>  		}
>  
> -		mutex_unlock(&vm->op_lock);
> +		mutex_unlock(&as->op_lock);
>  
>  		if (ret)
>  			break;
> @@ -2550,14 +2591,14 @@ int panthor_vm_evict_bo_mappings_locked(struct panthor_gem_object *bo)
>  }
>  
>  static struct panthor_vma *select_evicted_vma(struct drm_gpuvm_bo *vm_bo,
> -					      struct panthor_vm_op_ctx *op_ctx)
> +					      struct panthor_as_op_ctx *op_ctx)
>  {
> -	struct panthor_vm *vm = container_of(vm_bo->vm, struct panthor_vm, base);
> +	struct panthor_as *as = container_of(vm_bo->vm, struct panthor_as, base);
>  	struct panthor_vma *first_evicted_vma = NULL;
>  	struct drm_gpuva *va;
>  
>  	/* Take op_lock to protect against va insertion/removal. */
> -	mutex_lock(&vm->op_lock);
> +	mutex_lock(&as->op_lock);
>  	drm_gpuvm_bo_for_each_va(va, vm_bo) {
>  		struct panthor_vma *vma = container_of(va, struct panthor_vma, base);
>  
> @@ -2568,22 +2609,23 @@ static struct panthor_vma *select_evicted_vma(struct drm_gpuvm_bo *vm_bo,
>  			break;
>  		}
>  	}
> -	mutex_unlock(&vm->op_lock);
> +	mutex_unlock(&as->op_lock);
>  
>  	return first_evicted_vma;
>  }
>  
>  static int remap_evicted_vma(struct drm_gpuvm_bo *vm_bo,
>  			     struct panthor_vma *evicted_vma,
> -			     struct panthor_vm_op_ctx *op_ctx)
> +			     struct panthor_as_op_ctx *op_ctx)
>  {
> -	struct panthor_vm *vm = container_of(vm_bo->vm, struct panthor_vm, base);
> +	struct panthor_as *as = container_of(vm_bo->vm, struct panthor_as, base);
> +	struct panthor_device *ptdev = container_of(as->base.drm, struct panthor_device, base);
>  	struct panthor_gem_object *bo = to_panthor_bo(vm_bo->obj);
>  	struct drm_gpuva *va;
>  	bool found = false;
>  	int ret;
>  
> -	ret = panthor_vm_op_ctx_prealloc_pts(vm->ptdev, op_ctx);
> +	ret = panthor_as_op_ctx_prealloc_pts(ptdev, op_ctx);
>  	if (ret)
>  		goto out_cleanup;
>  
> @@ -2592,7 +2634,7 @@ static int remap_evicted_vma(struct drm_gpuvm_bo *vm_bo,
>  	 * to release it so we can allocate PTs, because this very same lock
>  	 * is taken in a DMA-signalling path.
>  	 */
> -	mutex_lock(&vm->op_lock);
> +	mutex_lock(&as->op_lock);
>  	drm_gpuvm_bo_for_each_va(va, vm_bo) {
>  		struct panthor_vma *vma = container_of(va, struct panthor_vma, base);
>  
> @@ -2612,8 +2654,8 @@ static int remap_evicted_vma(struct drm_gpuvm_bo *vm_bo,
>  	}
>  
>  	if (found) {
> -		vm->op_ctx = op_ctx;
> -		ret = panthor_vm_lock_region(vm, evicted_vma->base.va.addr,
> +		as->op_ctx = op_ctx;
> +		ret = panthor_as_lock_region(as, evicted_vma->base.va.addr,
>  					     evicted_vma->base.va.range);
>  		if (!ret) {
>  			struct drm_gpuva_op_map map_op = {
> @@ -2622,34 +2664,37 @@ static int remap_evicted_vma(struct drm_gpuvm_bo *vm_bo,
>  				.gem.obj = &bo->base,
>  				.gem.offset = evicted_vma->base.gem.offset,
>  			};
> -			if (evicted_vma->flags & DRM_PANTHOR_VM_BIND_OP_MAP_SPARSE)
> -				drm_WARN_ON_ONCE(&vm->ptdev->base, map_op.gem.offset !=
> -						 (map_op.va.addr & (SZ_2M - 1)));
> +			if (evicted_vma->flags & DRM_PANTHOR_VM_BIND_OP_MAP_SPARSE) {
> +				u64 expected_offset = map_op.va.addr & (SZ_2M - 1);
>  
> -			ret = panthor_vm_exec_map_op(vm, evicted_vma->flags, &map_op);
> +				drm_WARN_ON_ONCE(as->base.drm,
> +						 map_op.gem.offset != expected_offset);
> +			}
> +
> +			ret = panthor_as_exec_map_op(as, evicted_vma->flags, &map_op);
>  			if (!ret)
>  				evicted_vma->evicted = false;
>  
> -			panthor_vm_unlock_region(vm);
> +			panthor_as_unlock_region(as);
>  		}
>  
> -		vm->op_ctx = NULL;
> +		as->op_ctx = NULL;
>  	}
>  
> -	mutex_unlock(&vm->op_lock);
> +	mutex_unlock(&as->op_lock);
>  
>  out_cleanup:
> -	panthor_vm_cleanup_op_ctx(op_ctx, vm);
> +	panthor_as_cleanup_op_ctx(op_ctx, as);
>  	return ret;
>  }
>  
>  static int panthor_vm_restore_vmas(struct drm_gpuvm_bo *vm_bo)
>  {
> -	struct panthor_vm *vm = container_of(vm_bo->vm, struct panthor_vm, base);
> +	struct panthor_as *as = container_of(vm_bo->vm, struct panthor_as, base);
>  	struct panthor_gem_object *bo = to_panthor_bo(vm_bo->obj);
> -	struct panthor_vm_op_ctx op_ctx;
> +	struct panthor_as_op_ctx op_ctx;
>  
> -	if (drm_WARN_ON_ONCE(&vm->ptdev->base, !bo->dmap.sgt))
> +	if (drm_WARN_ON_ONCE(as->base.drm, !bo->dmap.sgt))
>  		return -EINVAL;
>  
>  	for (struct panthor_vma *vma = select_evicted_vma(vm_bo, &op_ctx);
> @@ -2685,8 +2730,19 @@ static int panthor_vm_bo_validate(struct drm_gpuvm_bo *vm_bo,
>  	return 0;
>  }
>  
> +static void panthor_as_free(struct drm_gpuvm *gpuvm)
> +{
> +	struct panthor_as *as = container_of(gpuvm, struct panthor_as, base);
> +
> +	if (as->pt.ops)
> +		free_io_pgtable_ops(as->pt.ops);
> +
> +	mutex_destroy(&as->op_lock);
> +	kfree(as);

Just one little thing here. Just like we do in other release/free functionsd, maybe you could
drm_WARN over AS list nodes that aren't yet empty.

> +}
> +
>  static const struct drm_gpuvm_ops panthor_gpuvm_ops = {
> -	.vm_free = panthor_vm_free,
> +	.vm_free = panthor_as_free,
>  	.vm_bo_free = panthor_vm_bo_free,
>  	.sm_step_map = panthor_gpuva_sm_step_map,
>  	.sm_step_remap = panthor_gpuva_sm_step_remap,
> @@ -2702,7 +2758,7 @@ static const struct drm_gpuvm_ops panthor_gpuvm_ops = {
>   */
>  struct dma_resv *panthor_vm_resv(struct panthor_vm *vm)
>  {
> -	return drm_gpuvm_resv(&vm->base);
> +	return drm_gpuvm_resv(&vm->as->base);
>  }
>  
>  struct drm_gem_object *panthor_vm_root_gem(struct panthor_vm *vm)
> @@ -2710,12 +2766,12 @@ struct drm_gem_object *panthor_vm_root_gem(struct panthor_vm *vm)
>  	if (!vm)
>  		return NULL;
>  
> -	return vm->base.r_obj;
> +	return vm->as->base.r_obj;
>  }
>  
> -static int
> -panthor_vm_exec_op(struct panthor_vm *vm, struct panthor_vm_op_ctx *op,
> -		   bool flag_vm_unusable_on_failure)
> +static int panthor_as_exec_op(struct panthor_as *as,
> +			      struct panthor_as_op_ctx *op,
> +			      bool flag_vm_unusable_on_failure)
>  {
>  	u32 op_type = op->flags & DRM_PANTHOR_VM_BIND_OP_TYPE_MASK;
>  	int ret;
> @@ -2723,10 +2779,10 @@ panthor_vm_exec_op(struct panthor_vm *vm, struct panthor_vm_op_ctx *op,
>  	if (op_type == DRM_PANTHOR_VM_BIND_OP_TYPE_SYNC_ONLY)
>  		return 0;
>  
> -	mutex_lock(&vm->op_lock);
> -	vm->op_ctx = op;
> +	mutex_lock(&as->op_lock);
> +	as->op_ctx = op;
>  
> -	ret = panthor_vm_lock_region(vm, op->va.addr, op->va.range);
> +	ret = panthor_as_lock_region(as, op->va.addr, op->va.range);
>  	if (ret)
>  		goto out;
>  
> @@ -2739,17 +2795,17 @@ panthor_vm_exec_op(struct panthor_vm *vm, struct panthor_vm_op_ctx *op,
>  			.map.gem.offset = op->map.bo_offset,
>  		};
>  
> -		if (vm->unusable) {
> +		if (as->unusable) {
>  			ret = -EINVAL;
>  			break;
>  		}
>  
> -		ret = drm_gpuvm_sm_map(&vm->base, vm, &map_req);
> +		ret = drm_gpuvm_sm_map(&as->base, as, &map_req);
>  		break;
>  	}
>  
>  	case DRM_PANTHOR_VM_BIND_OP_TYPE_UNMAP:
> -		ret = drm_gpuvm_sm_unmap(&vm->base, vm, op->va.addr, op->va.range);
> +		ret = drm_gpuvm_sm_unmap(&as->base, as, op->va.addr, op->va.range);
>  		break;
>  
>  	default:
> @@ -2757,14 +2813,14 @@ panthor_vm_exec_op(struct panthor_vm *vm, struct panthor_vm_op_ctx *op,
>  		break;
>  	}
>  
> -	panthor_vm_unlock_region(vm);
> +	panthor_as_unlock_region(as);
>  
>  out:
>  	if (ret && flag_vm_unusable_on_failure)
> -		panthor_vm_declare_unusable(vm);
> +		panthor_as_declare_unusable(as);
>  
> -	vm->op_ctx = NULL;
> -	mutex_unlock(&vm->op_lock);
> +	as->op_ctx = NULL;
> +	mutex_unlock(&as->op_lock);
>  
>  	return ret;
>  }
> @@ -2782,7 +2838,7 @@ panthor_vm_bind_run_job(struct drm_sched_job *sched_job)
>  	 * to be destroyed and recreated.
>  	 */
>  	cookie = dma_fence_begin_signalling();
> -	ret = panthor_vm_exec_op(job->vm, &job->ctx, true);
> +	ret = panthor_as_exec_op(job->vm->as, &job->ctx, true);
>  	dma_fence_end_signalling(cookie);
>  
>  	return ret ? ERR_PTR(ret) : NULL;
> @@ -2795,7 +2851,7 @@ static void panthor_vm_bind_job_release(struct kref *kref)
>  	if (job->base.s_fence)
>  		drm_sched_job_cleanup(&job->base);
>  
> -	panthor_vm_cleanup_op_ctx(&job->ctx, job->vm);
> +	panthor_as_cleanup_op_ctx(&job->ctx, job->vm->as);
>  	panthor_vm_put(job->vm);
>  	kfree(job);
>  }
> @@ -2818,13 +2874,15 @@ panthor_vm_bind_free_job(struct drm_sched_job *sched_job)
>  {
>  	struct panthor_vm_bind_job *job =
>  		container_of(sched_job, struct panthor_vm_bind_job, base);
> +	struct panthor_device *ptdev =
> +		 container_of(job->vm->as->base.drm, struct panthor_device, base);
>  
>  	drm_sched_job_cleanup(sched_job);
>  
>  	/* Do the heavy cleanups asynchronously, so we're out of the
>  	 * dma-signaling path and can acquire dma-resv locks safely.
>  	 */
> -	queue_work(job->vm->ptdev->cleanup_wq, &job->cleanup_op_ctx_work);
> +	queue_work(ptdev->cleanup_wq, &job->cleanup_op_ctx_work);
>  }
>  
>  static enum drm_gpu_sched_stat
> @@ -2840,6 +2898,62 @@ static const struct drm_sched_backend_ops panthor_vm_bind_ops = {
>  	.timedout_job = panthor_vm_bind_timedout_job,
>  };
>  
> +static struct panthor_as *
> +panthor_as_create(struct panthor_device *ptdev, const char *name,
> +		  u64 min_va, u64 va_range)
> +{
> +	struct io_pgtable_cfg as_cfg = {
> +		.pgsize_bitmap	= ptdev->mmu_info.page_size_bitmap,
> +		.ias		= GPU_MMU_FEATURES_VA_BITS(ptdev->gpu_info.mmu_features),
> +		.oas		= GPU_MMU_FEATURES_PA_BITS(ptdev->gpu_info.mmu_features),
> +		.coherent_walk	= ptdev->coherent,
> +		.tlb		= &mmu_tlb_ops,
> +		.iommu_dev	= drm_dev_dma_dev(&ptdev->base),
> +		.alloc		= alloc_pt,
> +		.free		= free_pt,
> +	};
> +	struct drm_gem_object *dummy_gem;
> +	struct panthor_as *as;
> +	u64 mair;
> +
> +	/* We allocate a dummy GEM for the VM. */
> +	dummy_gem = drm_gpuvm_resv_object_alloc(&ptdev->base);
> +	if (!dummy_gem)
> +		return ERR_PTR(-ENOMEM);
> +
> +	as = kzalloc_obj(*as);
> +	if (!as) {
> +		drm_gem_object_put(dummy_gem);
> +		return ERR_PTR(-ENOMEM);
> +	}
> +
> +	mutex_init(&as->op_lock);
> +	drm_gem_lru_init(&as->reclaim.lru);
> +	INIT_LIST_HEAD(&as->reclaim.lru_node);
> +	INIT_LIST_HEAD(&as->hw_slot.lru_node);
> +	as->hw_slot.id = -1;
> +	refcount_set(&as->active_cnt, 0);
> +
> +	/* We intentionally leave the reserved range to zero, because we want kernel VMAs
> +	 * to be handled the same way user VMAs are.
> +	 */
> +	drm_gpuvm_init(&as->base, name,
> +		       DRM_GPUVM_RESV_PROTECTED | DRM_GPUVM_IMMEDIATE_MODE,
> +		       &ptdev->base, dummy_gem, min_va, va_range, 0, 0,
> +		       &panthor_gpuvm_ops);
> +	drm_gem_object_put(dummy_gem);
> +
> +	as->pt.ops = alloc_io_pgtable_ops(ARM_64_LPAE_S1, &as_cfg, as);
> +	if (!as->pt.ops) {
> +		drm_gpuvm_put(&as->base);
> +		return ERR_PTR(-EINVAL);
> +	}
> +
> +	mair = io_pgtable_ops_to_pgtable(as->pt.ops)->cfg.arm_lpae_s1_cfg.mair;
> +	as->memattr = mair_to_memattr(mair, ptdev->coherent);
> +	return as;
> +}
> +
>  /**
>   * panthor_vm_create() - Create a VM
>   * @ptdev: Device.
> @@ -2857,9 +2971,8 @@ panthor_vm_create(struct panthor_device *ptdev, bool for_mcu,
>  		  u64 auto_kernel_va_start, u64 auto_kernel_va_size)
>  {
>  	u32 va_bits = GPU_MMU_FEATURES_VA_BITS(ptdev->gpu_info.mmu_features);
> -	u32 pa_bits = GPU_MMU_FEATURES_PA_BITS(ptdev->gpu_info.mmu_features);
> +	const char *name = for_mcu ? "panthor-MCU-VM" : "panthor-GPU-VM";
>  	u64 full_va_range = 1ull << va_bits;
> -	struct drm_gem_object *dummy_gem;
>  	struct drm_gpu_scheduler *sched;
>  	const struct drm_sched_init_args sched_args = {
>  		.ops = &panthor_vm_bind_ops,
> @@ -2870,27 +2983,11 @@ panthor_vm_create(struct panthor_device *ptdev, bool for_mcu,
>  		.name = "panthor-vm-bind",
>  		.dev = ptdev->base.dev,
>  	};
> -	struct io_pgtable_cfg pgtbl_cfg;
> -	u64 mair, min_va, va_range;
> +	struct panthor_as *as;
>  	struct panthor_vm *vm;
> +	u64 min_va, va_range;
>  	int ret;
>  
> -	vm = kzalloc_obj(*vm);
> -	if (!vm)
> -		return ERR_PTR(-ENOMEM);
> -
> -	/* We allocate a dummy GEM for the VM. */
> -	dummy_gem = drm_gpuvm_resv_object_alloc(&ptdev->base);
> -	if (!dummy_gem) {
> -		ret = -ENOMEM;
> -		goto err_free_vm;
> -	}
> -
> -	mutex_init(&vm->heaps.lock);
> -	vm->for_mcu = for_mcu;
> -	vm->ptdev = ptdev;
> -	mutex_init(&vm->op_lock);
> -
>  	if (for_mcu) {
>  		/* CSF MCU is a cortex M7, and can only address 4G */
>  		min_va = 0;
> @@ -2900,49 +2997,35 @@ panthor_vm_create(struct panthor_device *ptdev, bool for_mcu,
>  		va_range = full_va_range;
>  	}
>  
> +	as = panthor_as_create(ptdev, name, min_va, va_range);
> +	if (IS_ERR(as))
> +		return ERR_CAST(as);
> +
> +	vm = kzalloc_obj(*vm);
> +	if (!vm) {
> +		ret = -ENOMEM;
> +		goto err_put_as;
> +	}
> +
>  	vm->user_va_range = kernel_va_start;
> +	vm->as = as;
> +	mutex_init(&vm->heaps.lock);
> +	vm->for_mcu = for_mcu;
>  
>  	mutex_init(&vm->mm_lock);
>  	drm_mm_init(&vm->mm, kernel_va_start, kernel_va_size);
>  	vm->kernel_auto_va.start = auto_kernel_va_start;
>  	vm->kernel_auto_va.end = vm->kernel_auto_va.start + auto_kernel_va_size - 1;
>  
> -	drm_gem_lru_init(&vm->reclaim.lru);
> -	INIT_LIST_HEAD(&vm->reclaim.lru_node);
> -	INIT_LIST_HEAD(&vm->node);
> -	INIT_LIST_HEAD(&vm->as.lru_node);
> -	vm->as.id = -1;
> -	refcount_set(&vm->as.active_cnt, 0);
> -
> -	pgtbl_cfg = (struct io_pgtable_cfg) {
> -		.pgsize_bitmap	= ptdev->mmu_info.page_size_bitmap,
> -		.ias		= va_bits,
> -		.oas		= pa_bits,
> -		.coherent_walk	= ptdev->coherent,
> -		.tlb		= &mmu_tlb_ops,
> -		.iommu_dev	= ptdev->base.dev,
> -		.alloc		= alloc_pt,
> -		.free		= free_pt,
> -	};
> -
> -	vm->pgtbl_ops = alloc_io_pgtable_ops(ARM_64_LPAE_S1, &pgtbl_cfg, vm);
> -	if (!vm->pgtbl_ops) {
> -		ret = -EINVAL;
> -		goto err_mm_takedown;
> -	}
> -
>  	ret = drm_sched_init(&vm->sched, &sched_args);
>  	if (ret)
> -		goto err_free_io_pgtable;
> +		goto err_free_vm;
>  
>  	sched = &vm->sched;
>  	ret = drm_sched_entity_init(&vm->entity, 0, &sched, 1, NULL);
>  	if (ret)
>  		goto err_sched_fini;
>  
> -	mair = io_pgtable_ops_to_pgtable(vm->pgtbl_ops)->cfg.arm_lpae_s1_cfg.mair;
> -	vm->memattr = mair_to_memattr(mair, ptdev->coherent);
> -
>  	mutex_lock(&ptdev->mmu->vm.lock);
>  	list_add_tail(&vm->node, &ptdev->mmu->vm.list);
>  
> @@ -2951,28 +3034,20 @@ panthor_vm_create(struct panthor_device *ptdev, bool for_mcu,
>  		panthor_vm_stop(vm);
>  	mutex_unlock(&ptdev->mmu->vm.lock);
>  
> -	/* We intentionally leave the reserved range to zero, because we want kernel VMAs
> -	 * to be handled the same way user VMAs are.
> -	 */
> -	drm_gpuvm_init(&vm->base, for_mcu ? "panthor-MCU-VM" : "panthor-GPU-VM",
> -		       DRM_GPUVM_RESV_PROTECTED | DRM_GPUVM_IMMEDIATE_MODE,
> -		       &ptdev->base, dummy_gem, min_va, va_range, 0, 0,
> -		       &panthor_gpuvm_ops);
> -	drm_gem_object_put(dummy_gem);
> +	kref_init(&vm->refcount);
>  	return vm;
>  
>  err_sched_fini:
>  	drm_sched_fini(&vm->sched);
>  
> -err_free_io_pgtable:
> -	free_io_pgtable_ops(vm->pgtbl_ops);
> -
> -err_mm_takedown:
> -	drm_mm_takedown(&vm->mm);
> -	drm_gem_object_put(dummy_gem);
> -
>  err_free_vm:
> +	drm_mm_takedown(&vm->mm);
> +	mutex_destroy(&vm->mm_lock);
> +	mutex_destroy(&vm->heaps.lock);
>  	kfree(vm);
> +
> +err_put_as:
> +	drm_gpuvm_put(&as->base);
>  	return ERR_PTR(ret);
>  }
>  
> @@ -2980,7 +3055,7 @@ static int
>  panthor_vm_bind_prepare_op_ctx(struct drm_file *file,
>  			       struct panthor_vm *vm,
>  			       const struct drm_panthor_vm_bind_op *op,
> -			       struct panthor_vm_op_ctx *op_ctx)
> +			       struct panthor_as_op_ctx *op_ctx)
>  {
>  	ssize_t vm_pgsz = panthor_vm_page_size(vm);
>  	struct drm_gem_object *gem;
> @@ -3003,7 +3078,7 @@ panthor_vm_bind_prepare_op_ctx(struct drm_file *file,
>  			drm_gem_object_get(&vm->dummy->base);
>  		}
>  
> -		ret = panthor_vm_prepare_map_op_ctx(op_ctx, vm,
> +		ret = panthor_as_prepare_map_op_ctx(op_ctx, vm->as,
>  						    gem ? to_panthor_bo(gem) : NULL,
>  						    op);
>  		drm_gem_object_put(gem);
> @@ -3016,7 +3091,7 @@ panthor_vm_bind_prepare_op_ctx(struct drm_file *file,
>  		if (op->bo_handle || op->bo_offset)
>  			return -EINVAL;
>  
> -		return panthor_vm_prepare_unmap_op_ctx(op_ctx, vm, op->va, op->size);
> +		return panthor_as_prepare_unmap_op_ctx(op_ctx, vm->as, op->va, op->size);
>  
>  	case DRM_PANTHOR_VM_BIND_OP_TYPE_SYNC_ONLY:
>  		if (op->flags & ~DRM_PANTHOR_VM_BIND_OP_TYPE_MASK)
> @@ -3031,7 +3106,7 @@ panthor_vm_bind_prepare_op_ctx(struct drm_file *file,
>  		if (!op->syncs.count)
>  			return -EINVAL;
>  
> -		panthor_vm_prepare_sync_only_op_ctx(op_ctx);
> +		panthor_as_prepare_sync_only_op_ctx(op_ctx);
>  		return 0;
>  
>  	default:
> @@ -3066,7 +3141,7 @@ panthor_vm_bind_job_create(struct drm_file *file,
>  	if (!vm)
>  		return ERR_PTR(-EINVAL);
>  
> -	if (vm->destroyed || vm->unusable)
> +	if (vm->destroyed || vm->as->unusable)
>  		return ERR_PTR(-EINVAL);
>  
>  	job = kzalloc_obj(*job);
> @@ -3112,7 +3187,7 @@ int panthor_vm_bind_job_prepare_resvs(struct drm_exec *exec,
>  	int ret;
>  
>  	/* Acquire the VM lock an reserve a slot for this VM bind job. */
> -	ret = drm_gpuvm_prepare_vm(&job->vm->base, exec, 1);
> +	ret = drm_gpuvm_prepare_vm(&job->vm->as->base, exec, 1);
>  	if (ret)
>  		return ret;
>  
> @@ -3137,7 +3212,7 @@ void panthor_vm_bind_job_update_resvs(struct drm_exec *exec,
>  	struct panthor_vm_bind_job *job = container_of(sched_job, struct panthor_vm_bind_job, base);
>  
>  	/* Explicit sync => we just register our job finished fence as bookkeep. */
> -	drm_gpuvm_resv_add_fence(&job->vm->base, exec,
> +	drm_gpuvm_resv_add_fence(&job->vm->as->base, exec,
>  				 &sched_job->s_fence->finished,
>  				 DMA_RESV_USAGE_BOOKKEEP,
>  				 DMA_RESV_USAGE_BOOKKEEP);
> @@ -3148,7 +3223,7 @@ void panthor_vm_update_resvs(struct panthor_vm *vm, struct drm_exec *exec,
>  			     enum dma_resv_usage private_usage,
>  			     enum dma_resv_usage extobj_usage)
>  {
> -	drm_gpuvm_resv_add_fence(&vm->base, exec, fence, private_usage, extobj_usage);
> +	drm_gpuvm_resv_add_fence(&vm->as->base, exec, fence, private_usage, extobj_usage);
>  }
>  
>  /**
> @@ -3163,7 +3238,7 @@ int panthor_vm_bind_exec_sync_op(struct drm_file *file,
>  				 struct panthor_vm *vm,
>  				 struct drm_panthor_vm_bind_op *op)
>  {
> -	struct panthor_vm_op_ctx op_ctx;
> +	struct panthor_as_op_ctx op_ctx;
>  	int ret;
>  
>  	/* No sync objects allowed on synchronous operations. */
> @@ -3177,8 +3252,8 @@ int panthor_vm_bind_exec_sync_op(struct drm_file *file,
>  	if (ret)
>  		return ret;
>  
> -	ret = panthor_vm_exec_op(vm, &op_ctx, false);
> -	panthor_vm_cleanup_op_ctx(&op_ctx, vm);
> +	ret = panthor_as_exec_op(vm->as, &op_ctx, false);
> +	panthor_as_cleanup_op_ctx(&op_ctx, vm->as);
>  
>  	return ret;
>  }
> @@ -3207,18 +3282,18 @@ int panthor_vm_map_bo_range(struct panthor_vm *vm, struct panthor_gem_object *bo
>  		.va = va,
>  		.flags = flags,
>  	};
> -	struct panthor_vm_op_ctx op_ctx;
> +	struct panthor_as_op_ctx op_ctx;
>  	int ret;
>  
> -	if (drm_WARN_ON(&vm->ptdev->base, flags & DRM_PANTHOR_VM_BIND_OP_MAP_SPARSE))
> +	if (drm_WARN_ON(vm->as->base.drm, flags & DRM_PANTHOR_VM_BIND_OP_MAP_SPARSE))
>  		return -EINVAL;
>  
> -	ret = panthor_vm_prepare_map_op_ctx(&op_ctx, vm, bo, &op);
> +	ret = panthor_as_prepare_map_op_ctx(&op_ctx, vm->as, bo, &op);
>  	if (ret)
>  		return ret;
>  
> -	ret = panthor_vm_exec_op(vm, &op_ctx, false);
> -	panthor_vm_cleanup_op_ctx(&op_ctx, vm);
> +	ret = panthor_as_exec_op(vm->as, &op_ctx, false);
> +	panthor_as_cleanup_op_ctx(&op_ctx, vm->as);
>  
>  	return ret;
>  }
> @@ -3236,15 +3311,15 @@ int panthor_vm_map_bo_range(struct panthor_vm *vm, struct panthor_gem_object *bo
>   */
>  int panthor_vm_unmap_range(struct panthor_vm *vm, u64 va, u64 size)
>  {
> -	struct panthor_vm_op_ctx op_ctx;
> +	struct panthor_as_op_ctx op_ctx;
>  	int ret;
>  
> -	ret = panthor_vm_prepare_unmap_op_ctx(&op_ctx, vm, va, size);
> +	ret = panthor_as_prepare_unmap_op_ctx(&op_ctx, vm->as, va, size);
>  	if (ret)
>  		return ret;
>  
> -	ret = panthor_vm_exec_op(vm, &op_ctx, false);
> -	panthor_vm_cleanup_op_ctx(&op_ctx, vm);
> +	ret = panthor_as_exec_op(vm->as, &op_ctx, false);
> +	panthor_as_cleanup_op_ctx(&op_ctx, vm->as);
>  
>  	return ret;
>  }
> @@ -3268,15 +3343,15 @@ int panthor_vm_prepare_mapped_bos_resvs(struct drm_exec *exec, struct panthor_vm
>  	int ret;
>  
>  	/* Acquire the VM lock and reserve a slot for this GPU job. */
> -	ret = drm_gpuvm_prepare_vm(&vm->base, exec, slot_count);
> +	ret = drm_gpuvm_prepare_vm(&vm->as->base, exec, slot_count);
>  	if (ret)
>  		return ret;
>  
> -	ret = drm_gpuvm_prepare_objects(&vm->base, exec, slot_count);
> +	ret = drm_gpuvm_prepare_objects(&vm->as->base, exec, slot_count);
>  	if (ret)
>  		return ret;
>  
> -	return drm_gpuvm_validate(&vm->base, exec);
> +	return drm_gpuvm_validate(&vm->as->base, exec);
>  }
>  
>  unsigned long
> @@ -3293,21 +3368,21 @@ panthor_mmu_reclaim_priv_bos(struct panthor_device *ptdev,
>  	list_splice_init(&ptdev->reclaim.vms, &vms);
>  
>  	while (freed < nr_to_scan) {
> -		struct panthor_vm *vm;
> +		struct panthor_as *as;
>  
> -		vm = list_first_entry_or_null(&vms, typeof(*vm),
> +		as = list_first_entry_or_null(&vms, typeof(*as),
>  					      reclaim.lru_node);
> -		if (!vm)
> +		if (!as)
>  			break;
>  
> -		if (!kref_get_unless_zero(&vm->base.kref)) {
> -			list_del_init(&vm->reclaim.lru_node);
> +		if (!kref_get_unless_zero(&as->base.kref)) {
> +			list_del_init(&as->reclaim.lru_node);
>  			continue;
>  		}
>  
>  		mutex_unlock(&ptdev->base.gem_lru_mutex);
>  
> -		freed += drm_gem_lru_scan(&ptdev->base, &vm->reclaim.lru,
> +		freed += drm_gem_lru_scan(&ptdev->base, &as->reclaim.lru,
>  					  nr_to_scan - freed,
>  					  remaining, shrink, NULL);
>  
> @@ -3316,20 +3391,20 @@ panthor_mmu_reclaim_priv_bos(struct panthor_device *ptdev,
>  		/* If the VM is still in the temporary list, remove it so we
>  		 * can proceed with the next VM.
>  		 */
> -		if (vm == list_first_entry_or_null(&vms, typeof(*vm), reclaim.lru_node)) {
> -			list_del_init(&vm->reclaim.lru_node);
> +		if (as == list_first_entry_or_null(&vms, typeof(*as), reclaim.lru_node)) {
> +			list_del_init(&as->reclaim.lru_node);
>  
>  			/* Keep the VM around if there are still things to
>  			 * reclaim, so we can preserve the LRU order when
>  			 * re-inserting in ptdev->reclaim.vms at the end.
>  			 */
> -			if (vm->reclaim.lru.count > 0)
> -				list_add_tail(&vm->reclaim.lru_node, &remaining_vms);
> +			if (as->reclaim.lru.count > 0)
> +				list_add_tail(&as->reclaim.lru_node, &remaining_vms);
>  		}
>  
>  		mutex_unlock(&ptdev->base.gem_lru_mutex);
>  
> -		panthor_vm_put(vm);
> +		drm_gpuvm_put(&as->base);
>  
>  		mutex_lock(&ptdev->base.gem_lru_mutex);
>  	}
> @@ -3361,12 +3436,12 @@ void panthor_mmu_unplug(struct panthor_device *ptdev)
>  
>  	mutex_lock(&ptdev->mmu->as.slots_lock);
>  	for (u32 i = 0; i < ARRAY_SIZE(ptdev->mmu->as.slots); i++) {
> -		struct panthor_vm *vm = ptdev->mmu->as.slots[i].vm;
> +		struct panthor_as *as = ptdev->mmu->as.slots[i].as;
>  
> -		if (vm) {
> +		if (as) {
>  			drm_WARN_ON(&ptdev->base,
>  				    panthor_mmu_as_disable(ptdev, i, false));
> -			panthor_vm_release_as_locked(vm);
> +			panthor_as_release_hw_slot_locked(as);
>  		}
>  	}
>  	mutex_unlock(&ptdev->mmu->as.slots_lock);
> @@ -3470,9 +3545,9 @@ static int show_vm_gpuvas(struct panthor_vm *vm, struct seq_file *m)
>  {
>  	int ret;
>  
> -	mutex_lock(&vm->op_lock);
> -	ret = drm_debugfs_gpuva_info(m, &vm->base);
> -	mutex_unlock(&vm->op_lock);
> +	mutex_lock(&vm->as->op_lock);
> +	ret = drm_debugfs_gpuva_info(m, &vm->as->base);
> +	mutex_unlock(&vm->as->op_lock);
>  
>  	return ret;
>  }
> 
> -- 
> 2.55.0


Adrian Larumbe

  parent reply	other threads:[~2026-09-11 22:55 UTC|newest]

Thread overview: 46+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-26 14:55 [PATCH v4 00/18] drm/panthor: Fix the unplug logic Boris Brezillon
2026-08-26 14:56 ` [PATCH v4 01/18] drm/panthor: Disable reset work before unplug Boris Brezillon
2026-08-27 13:00   ` Liviu Dudau
2026-09-10  1:12   ` Adrian Larumbe
2026-08-26 14:56 ` [PATCH v4 02/18] drm/panthor: Revisit the reset logic to avoid reset request loss Boris Brezillon
2026-08-27 15:04   ` Liviu Dudau
2026-09-10  1:13   ` Adrian Larumbe
2026-08-26 14:56 ` [PATCH v4 03/18] drm/panthor: Make panthor_device::pm::state non-atomic Boris Brezillon
2026-08-27 15:12   ` Liviu Dudau
2026-09-10  1:13   ` Adrian Larumbe
2026-08-26 14:56 ` [PATCH v4 04/18] drm/panthor: Flush the cleanup_wq in the unplug path Boris Brezillon
2026-08-27 15:14   ` Liviu Dudau
2026-09-10  1:14   ` Adrian Larumbe
2026-08-26 14:56 ` [PATCH v4 05/18] drm/panthor: Make the page table cache and cleanup workqueue device-local Boris Brezillon
2026-08-27 15:20   ` Liviu Dudau
2026-09-10  1:14   ` Adrian Larumbe
2026-08-26 14:56 ` [PATCH v4 06/18] drm/panthor: Drop unused vm argument passed to panthor_vm_prepare_sync_only_op_ctx() Boris Brezillon
2026-08-27 15:21   ` Liviu Dudau
2026-09-10  1:15   ` Adrian Larumbe
2026-08-26 14:56 ` [PATCH v4 07/18] drm/panthor: Move the debugfs initialization to panthor_device.c Boris Brezillon
2026-09-10  1:18   ` Adrian Larumbe
2026-08-26 14:56 ` [PATCH v4 08/18] drm/panthor: Split panthor_vm Boris Brezillon
2026-09-11  3:37   ` Adrian Larumbe
2026-09-11  9:48     ` Boris Brezillon
2026-09-11 22:55   ` Adrian Larumbe [this message]
2026-08-26 14:56 ` [PATCH v4 09/18] drm/panthor: Add fine-grained restrictions on VMs Boris Brezillon
2026-09-11  3:38   ` Adrian Larumbe
2026-08-26 14:56 ` [PATCH v4 10/18] drm/panthor: Check AS state before disabling Boris Brezillon
2026-09-11  3:38   ` Adrian Larumbe
2026-08-26 14:56 ` [PATCH v4 11/18] drm/panthor: Don't pre-allocate VMAs or page tables when preparing a full VM unmap Boris Brezillon
2026-09-11  3:38   ` Adrian Larumbe
2026-08-26 14:56 ` [PATCH v4 12/18] drm/panthor: Let l2_power_off return errors and force users to check it Boris Brezillon
2026-09-11  3:39   ` Adrian Larumbe
2026-08-26 14:56 ` [PATCH v4 13/18] drm/panthor: Complain if the SOFT_RESET fails Boris Brezillon
2026-09-11  3:40   ` Adrian Larumbe
2026-09-11  9:54     ` Boris Brezillon
2026-08-26 14:56 ` [PATCH v4 14/18] drm/panthor: Make the VM cleanup path more robust against UAF Boris Brezillon
2026-09-11 19:15   ` Adrian Larumbe
2026-08-26 14:56 ` [PATCH v4 15/18] drm/panthor: Track user owned VMs Boris Brezillon
2026-09-11 19:17   ` Adrian Larumbe
2026-08-26 14:56 ` [PATCH v4 16/18] drm/panthor: Track user owned groups Boris Brezillon
2026-09-11 19:17   ` Adrian Larumbe
2026-08-26 14:56 ` [PATCH v4 17/18] drm/panthor: Fix the unplug logic Boris Brezillon
2026-09-11 22:44   ` Adrian Larumbe
2026-08-26 14:56 ` [PATCH v4 18/18] drm/panthor: Add debugfs knobs to simulate reset failures Boris Brezillon
2026-09-11 19:18   ` Adrian Larumbe

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=aqSGVCOJvJhxeVVA@sobremesa \
    --to=adrian.larumbe@collabora.com \
    --cc=airlied@gmail.com \
    --cc=akash.goel@arm.com \
    --cc=boris.brezillon@collabora.com \
    --cc=chris.diamand@arm.com \
    --cc=dri-devel@lists.freedesktop.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=liviu.dudau@arm.com \
    --cc=maarten.lankhorst@linux.intel.com \
    --cc=mripard@kernel.org \
    --cc=simona@ffwll.ch \
    --cc=steven.price@arm.com \
    --cc=tzimmermann@suse.de \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®