[PATCH v9 03/18] drm/amdgpu: implement SVM attribute tree and helper functions
Huang Rui <[email protected]> Tue, 4 Aug 2026 17:42:29 +0800
| Newsgroups | org.freedesktop.lists.amd-gfx,org.freedesktop.lists.dri-devel |
|---|---|
| Message-ID | <[email protected]> |
From: Honglei Huang <[email protected]> Add amdgpu_svm_attr.h with the SVM attribute types and tree infrastructure together with the helper implementation in amdgpu_svm_attr.c that uses them. The header is squashed into the implementation patch so it does not stand alone as declarations without an implementation. amdgpu_svm_attr.h: - Internal flag bitmask definitions mapped from UAPI attr types - PTE_FLAG_MASK and MAPPING_FLAG_MASK for change detection - struct amdgpu_svm_attrs: user set attribute range - struct amdgpu_svm_attr_range: interval tree node with attrs - struct amdgpu_svm_attr_tree: mutex protected RB tree for store and search - enum amdgpu_svm_attr_change_trigger: change flags of user attribute changes amdgpu_svm_attr.c: - Default attribute initialization: amdgpu_svm_attr_set_default - Device memory and VRAM preference helpers - VMA validity checker: amdgpu_svm_check_vma - Attribute equality comparison: attr_equal - Interval tree CRUD operations: find, get_bounds, alloc, insert, and remove - attr_set_interval helper for range boundary updates Signed-off-by: Honglei Huang <[email protected]> --- drivers/gpu/drm/amd/amdgpu/amdgpu_svm_attr.c | 235 +++++++++++++++++++ drivers/gpu/drm/amd/amdgpu/amdgpu_svm_attr.h | 183 +++++++++++++++ 2 files changed, 418 insertions(+) create mode 100644 drivers/gpu/drm/amd/amdgpu/amdgpu_svm_attr.c create mode 100644 drivers/gpu/drm/amd/amdgpu/amdgpu_svm_attr.h diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_svm_attr.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_svm_attr.c new file mode 100644 index 0000000000000..9d3519776c9c8 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_svm_attr.c @@ -0,0 +1,235 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * Copyright 2026 Advanced Micro Devices, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "amdgpu_svm.h" +#include "amdgpu_svm_attr.h" + +#include <linux/err.h> +#include <linux/errno.h> +#include <linux/gfp.h> +#include <linux/lockdep.h> +#include <linux/minmax.h> +#include <linux/mm.h> +#include <linux/slab.h> + +struct attr_set_ctx { + struct amdgpu_svm_attrs old_attrs; + struct amdgpu_svm_attrs new_attrs; + unsigned long start_page; + unsigned long last_page; +}; + +struct attr_get_ctx { + int32_t preferred_loc; + int32_t prefetch_loc; + enum amdgpu_ioctl_svm_access access; + uint32_t granularity; + uint32_t flags_and; + bool has_range; +}; + +bool amdgpu_svm_attr_prefer_vram(const struct amdgpu_svm_attrs *attrs) +{ + if (attrs->preferred_loc != AMDGPU_SVM_LOCATION_UNDEFINED && + attrs->preferred_loc != AMDGPU_SVM_LOCATION_SYSMEM) + return true; + + if (attrs->prefetch_loc != AMDGPU_SVM_LOCATION_UNDEFINED && + attrs->prefetch_loc != AMDGPU_SVM_LOCATION_SYSMEM) + return true; + + return false; +} + +struct vm_area_struct *amdgpu_svm_check_vma(struct mm_struct *mm, + unsigned long addr) +{ + const unsigned long unsupported_vm_flags = VM_IO | VM_PFNMAP | + VM_MIXEDMAP; + struct vm_area_struct *vma = vma_lookup(mm, addr); + + if (!vma) + return ERR_PTR(-EFAULT); + + if (vma->vm_flags & unsupported_vm_flags) + return ERR_PTR(-EOPNOTSUPP); + + return vma; +} + +static void attr_set_interval(struct amdgpu_svm_attr_range *range, + unsigned long start_page, + unsigned long last_page) +{ + range->it_node.start = start_page; + range->it_node.last = last_page; +} + +void amdgpu_svm_attr_set_default(struct amdgpu_svm *svm, + struct amdgpu_svm_attrs *attrs) +{ + attrs->preferred_loc = AMDGPU_SVM_LOCATION_UNDEFINED; + attrs->prefetch_loc = AMDGPU_SVM_LOCATION_UNDEFINED; + attrs->granularity = svm->default_granularity; + attrs->flags = AMDGPU_SVM_ATTR_BIT_HOST_ACCESS | AMDGPU_SVM_ATTR_BIT_COHERENT; + attrs->access = svm->xnack_enabled ? + AMDGPU_SVM_ACCESS_ALLOW_MIGRATE : AMDGPU_SVM_ACCESS_INACCESSIBLE; +} + +struct amdgpu_svm_attr_range * +amdgpu_svm_attr_find_locked(struct amdgpu_svm_attr_tree *attr_tree, + unsigned long page) +{ + struct interval_tree_node *node; + + node = interval_tree_iter_first(&attr_tree->tree, page, page); + if (node) + return container_of(node, struct amdgpu_svm_attr_range, it_node); + + return NULL; +} + +/** + * amdgpu_svm_attr_get_bounds_locked() - Find attributes or surrounding bounds + * @attr_tree: attribute tree to search. + * @page: page index to look up. + * @start_page: out, first page of the returned range or gap. + * @last_page: out, last page of the returned range or gap. + * + * If @page is covered by an attribute range, return that range and report its + * bounds. Otherwise return NULL and fill @start_page/@last_page with the bounds + * of the default attribute gap around @page, clamped by neighboring explicit + * ranges or [0, ULONG_MAX] when none. + * + * Return: the covering range, or NULL if @page falls in a gap. + */ +struct amdgpu_svm_attr_range * +amdgpu_svm_attr_get_bounds_locked(struct amdgpu_svm_attr_tree *attr_tree, + unsigned long page, + unsigned long *start_page, + unsigned long *last_page) +{ + struct amdgpu_svm_attr_range *attr_range; + struct interval_tree_node *node; + struct rb_node *rb; + + attr_range = amdgpu_svm_attr_find_locked(attr_tree, page); + if (attr_range) { + *start_page = amdgpu_svm_attr_start_page(attr_range); + *last_page = amdgpu_svm_attr_last_page(attr_range); + return attr_range; + } + + *start_page = 0; + *last_page = ULONG_MAX; + + if (page == ULONG_MAX) + return NULL; + + node = interval_tree_iter_first(&attr_tree->tree, page + 1, ULONG_MAX); + if (node) { + if (node->start > page) + *last_page = node->start - 1; + + rb = rb_prev(&node->rb); + if (rb) { + node = container_of(rb, struct interval_tree_node, rb); + if (node->last < page) + *start_page = node->last + 1; + } + } else { + rb = rb_last(&attr_tree->tree.rb_root); + + if (rb) { + node = container_of(rb, struct interval_tree_node, rb); + if (node->last < page) + *start_page = node->last + 1; + } + } + + return NULL; +} + +static bool attr_equal(const struct amdgpu_svm_attrs *a, + const struct amdgpu_svm_attrs *b) +{ + return a->flags == b->flags && + a->preferred_loc == b->preferred_loc && + a->prefetch_loc == b->prefetch_loc && + a->granularity == b->granularity && + a->access == b->access; +} + +struct amdgpu_svm_attr_range * +amdgpu_svm_attr_range_alloc(unsigned long start_page, + unsigned long last_page, + const struct amdgpu_svm_attrs *attrs) +{ + struct amdgpu_svm_attr_range *range; + + range = kzalloc(sizeof(*range), GFP_KERNEL); + if (!range) + return NULL; + + INIT_LIST_HEAD(&range->list); + attr_set_interval(range, start_page, last_page); + range->attrs = *attrs; + return range; +} + +void amdgpu_svm_attr_range_insert_locked(struct amdgpu_svm_attr_tree *attr_tree, + struct amdgpu_svm_attr_range *range) +{ + struct interval_tree_node *node; + struct amdgpu_svm_attr_range *next; + + lockdep_assert_held(&attr_tree->lock); + + /* + * Keep @range_list ordered by start page: insert before the first range + * starting at or after @range, then add to the interval tree. + */ + node = interval_tree_iter_first(&attr_tree->tree, amdgpu_svm_attr_start_page(range), + ULONG_MAX); + if (node) { + next = container_of(node, struct amdgpu_svm_attr_range, it_node); + list_add_tail(&range->list, &next->list); + } else { + list_add_tail(&range->list, &attr_tree->range_list); + } + + interval_tree_insert(&range->it_node, &attr_tree->tree); +} + +static void attr_remove_range_locked(struct amdgpu_svm_attr_tree *attr_tree, + struct amdgpu_svm_attr_range *range, + bool free_range) +{ + lockdep_assert_held(&attr_tree->lock); + + interval_tree_remove(&range->it_node, &attr_tree->tree); + list_del_init(&range->list); + if (free_range) + kfree(range); +} diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_svm_attr.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_svm_attr.h new file mode 100644 index 0000000000000..0f712536a5dc1 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_svm_attr.h @@ -0,0 +1,183 @@ +/* SPDX-License-Identifier: GPL-2.0 OR MIT */ +/* + * Copyright 2026 Advanced Micro Devices, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef __AMDGPU_SVM_ATTR_H__ +#define __AMDGPU_SVM_ATTR_H__ + +#include <drm/amdgpu_drm.h> +#include <linux/interval_tree.h> +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/rbtree.h> +#include <linux/types.h> + +/* Internal SVM attribute bitmask flags mapped from UAPI ioctl definitions */ +#define AMDGPU_SVM_ATTR_BIT_HOST_ACCESS (1u << 0) +#define AMDGPU_SVM_ATTR_BIT_COHERENT (1u << 1) +#define AMDGPU_SVM_ATTR_BIT_EXT_COHERENT (1u << 2) +#define AMDGPU_SVM_ATTR_BIT_HIVE_LOCAL (1u << 3) +#define AMDGPU_SVM_ATTR_BIT_GPU_RO (1u << 4) +#define AMDGPU_SVM_ATTR_BIT_GPU_EXEC (1u << 5) +#define AMDGPU_SVM_ATTR_BIT_GPU_READ_MOSTLY (1u << 6) + +#define AMDGPU_SVM_PTE_FLAG_MASK \ + (AMDGPU_SVM_ATTR_BIT_COHERENT | AMDGPU_SVM_ATTR_BIT_EXT_COHERENT | \ + AMDGPU_SVM_ATTR_BIT_GPU_RO | AMDGPU_SVM_ATTR_BIT_GPU_EXEC) + +#define AMDGPU_SVM_MAPPING_FLAG_MASK \ + (AMDGPU_SVM_ATTR_BIT_HOST_ACCESS | AMDGPU_SVM_ATTR_BIT_HIVE_LOCAL | \ + AMDGPU_SVM_ATTR_BIT_GPU_READ_MOSTLY) + +/** + * struct amdgpu_svm_attrs - SVM attributes for an address range + * @preferred_loc: Preferred backing location, using AMDGPU_SVM_LOCATION_*. + * @prefetch_loc: Target location for prefetch requests, using + * AMDGPU_SVM_LOCATION_*. + * @flags: Internal AMDGPU_SVM_ATTR_BIT_* flags mapped from the UAPI. + * @granularity: Mapping granularity encoded as a page order. + * @access: CPU/GPU access policy from the SVM UAPI. + */ +struct amdgpu_svm_attrs { + int32_t preferred_loc; + int32_t prefetch_loc; + uint32_t flags; + uint32_t granularity; + enum amdgpu_ioctl_svm_access access; +}; + +/** + * struct amdgpu_svm_attr_range - a range of user attributes + * @it_node: interval tree node keyed by [start, last] page index. + * @list: links the range into amdgpu_svm_attr_tree.range_list in address order. + * @attrs: the attributes applied to this range. + */ +struct amdgpu_svm_attr_range { + struct interval_tree_node it_node; + struct list_head list; + struct amdgpu_svm_attrs attrs; +}; + +static inline unsigned long +amdgpu_svm_attr_start_page(const struct amdgpu_svm_attr_range *range) +{ + return range->it_node.start; +} + +static inline unsigned long +amdgpu_svm_attr_last_page(const struct amdgpu_svm_attr_range *range) +{ + return range->it_node.last; +} + +static inline unsigned long +amdgpu_svm_attr_start(const struct amdgpu_svm_attr_range *range) +{ + return range->it_node.start << PAGE_SHIFT; +} + +static inline unsigned long +amdgpu_svm_attr_end(const struct amdgpu_svm_attr_range *range) +{ + return (range->it_node.last + 1) << PAGE_SHIFT; +} + +struct amdgpu_svm; +struct mm_struct; +struct vm_area_struct; + +static inline bool +amdgpu_svm_attr_has_access(enum amdgpu_ioctl_svm_access access) +{ + return access == AMDGPU_SVM_ACCESS_ALLOW_MIGRATE || + access == AMDGPU_SVM_ACCESS_IN_PLACE; +} + +/** + * struct amdgpu_svm_attr_tree - per-SVM store of user attribute ranges + * @lock: protects @tree and @range_list. + * @tree: interval tree of struct amdgpu_svm_attr_range for fast lookup. + * @range_list: address-ordered list of the same ranges, kept in sync with + * @tree to allow ordered traversal during modification. + * @svm: back pointer to the owning SVM instance. + */ +struct amdgpu_svm_attr_tree { + struct mutex lock; + struct rb_root_cached tree; + struct list_head range_list; + struct amdgpu_svm *svm; +}; + +/** + * enum amdgpu_svm_attr_change_trigger - effects caused by an attribute change + * @AMDGPU_SVM_ATTR_TRIGGER_ACCESS_CHANGE: Access policy changed. + * @AMDGPU_SVM_ATTR_TRIGGER_PTE_FLAG_CHANGE: GPU PTE permission/cache bits changed. + * @AMDGPU_SVM_ATTR_TRIGGER_MAPPING_FLAG_CHANGE: Mapping policy bits changed. + * @AMDGPU_SVM_ATTR_TRIGGER_LOCATION_CHANGE: Preferred or prefetch location changed. + * @AMDGPU_SVM_ATTR_TRIGGER_GRANULARITY_CHANGE: Range granularity changed. + * @AMDGPU_SVM_ATTR_TRIGGER_PREFETCH: New attributes request a VRAM prefetch. + * + * Bitmask describing what an attribute update touched. It drives whether the + * existing GPU mappings must be invalidated and/or remapped. + */ +enum amdgpu_svm_attr_change_trigger { + AMDGPU_SVM_ATTR_TRIGGER_ACCESS_CHANGE = (1U << 0), + AMDGPU_SVM_ATTR_TRIGGER_PTE_FLAG_CHANGE = (1U << 1), + AMDGPU_SVM_ATTR_TRIGGER_MAPPING_FLAG_CHANGE = (1U << 2), + AMDGPU_SVM_ATTR_TRIGGER_LOCATION_CHANGE = (1U << 3), + AMDGPU_SVM_ATTR_TRIGGER_GRANULARITY_CHANGE = (1U << 4), + AMDGPU_SVM_ATTR_TRIGGER_PREFETCH = (1U << 5), +}; + +/* + * Attribute changes that require invalidating existing GPU mappings. + * A granularity-only change does not, so it is intentionally excluded. + */ +#define AMDGPU_SVM_ATTR_TRIGGER_NEED_INVALIDATE \ + (AMDGPU_SVM_ATTR_TRIGGER_ACCESS_CHANGE | \ + AMDGPU_SVM_ATTR_TRIGGER_PTE_FLAG_CHANGE | \ + AMDGPU_SVM_ATTR_TRIGGER_MAPPING_FLAG_CHANGE | \ + AMDGPU_SVM_ATTR_TRIGGER_LOCATION_CHANGE) + +struct amdgpu_svm_attr_range * +amdgpu_svm_attr_find_locked(struct amdgpu_svm_attr_tree *attr_tree, + unsigned long page); +struct amdgpu_svm_attr_range * +amdgpu_svm_attr_get_bounds_locked(struct amdgpu_svm_attr_tree *attr_tree, + unsigned long page, + unsigned long *start_page, + unsigned long *last_page); +void amdgpu_svm_attr_set_default(struct amdgpu_svm *svm, + struct amdgpu_svm_attrs *attrs); + +struct amdgpu_svm_attr_range * +amdgpu_svm_attr_range_alloc(unsigned long start_page, + unsigned long last_page, + const struct amdgpu_svm_attrs *attrs); +void amdgpu_svm_attr_range_insert_locked(struct amdgpu_svm_attr_tree *attr_tree, + struct amdgpu_svm_attr_range *range); +bool amdgpu_svm_attr_prefer_vram(const struct amdgpu_svm_attrs *attrs); +struct vm_area_struct *amdgpu_svm_check_vma(struct mm_struct *mm, + unsigned long addr); + +#endif /* __AMDGPU_SVM_ATTR_H__ */ -- 2.53.0