#include "xe_ggtt.h"
#include <kunit/visibility.h>
#include <linux/fault-inject.h>
#include <linux/io-64-nonatomic-lo-hi.h>
#include <linux/sizes.h>
#include <drm/drm_drv.h>
#include <drm/drm_managed.h>
#include <drm/intel/i915_drm.h>
#include <generated/xe_wa_oob.h>
#include "regs/xe_gt_regs.h"
#include "regs/xe_gtt_defs.h"
#include "regs/xe_regs.h"
#include "xe_assert.h"
#include "xe_bo.h"
#include "xe_gt_printk.h"
#include "xe_gt_types.h"
#include "xe_map.h"
#include "xe_mmio.h"
#include "xe_pm.h"
#include "xe_res_cursor.h"
#include "xe_sriov.h"
#include "xe_tile_printk.h"
#include "xe_tile_sriov_vf.h"
#include "xe_tlb_inval.h"
#include "xe_wa.h"
#include "xe_wopcm.h"
struct xe_ggtt_node {
struct xe_ggtt *ggtt;
struct drm_mm_node base;
struct work_struct delayed_removal_work;
bool invalidate_on_remove;
};
static u64 xelp_ggtt_pte_flags(struct xe_bo *bo, u16 pat_index)
{
u64 pte = XE_PAGE_PRESENT;
if (xe_bo_is_vram(bo) || xe_bo_is_stolen_devmem(bo))
pte |= XE_GGTT_PTE_DM;
return pte;
}
static u64 xelpg_ggtt_pte_flags(struct xe_bo *bo, u16 pat_index)
{
struct xe_device *xe = xe_bo_device(bo);
u64 pte;
pte = xelp_ggtt_pte_flags(bo, pat_index);
xe_assert(xe, pat_index <= 3);
if (pat_index & BIT(0))
pte |= XELPG_GGTT_PTE_PAT0;
if (pat_index & BIT(1))
pte |= XELPG_GGTT_PTE_PAT1;
return pte;
}
static unsigned int probe_gsm_size(struct pci_dev *pdev)
{
u16 gmch_ctl, ggms;
pci_read_config_word(pdev, SNB_GMCH_CTRL, &gmch_ctl);
ggms = (gmch_ctl >> BDW_GMCH_GGMS_SHIFT) & BDW_GMCH_GGMS_MASK;
return ggms ? SZ_1M << ggms : 0;
}
static void ggtt_update_access_counter(struct xe_ggtt *ggtt)
{
struct xe_tile *tile = ggtt->tile;
struct xe_gt *affected_gt;
u32 max_gtt_writes;
if (tile->primary_gt && XE_GT_WA(tile->primary_gt, 22019338487)) {
affected_gt = tile->primary_gt;
max_gtt_writes = 1100;
xe_tile_assert(tile, IS_DGFX(tile_to_xe(tile)));
} else {
affected_gt = tile->media_gt;
max_gtt_writes = 63;
xe_tile_assert(tile, !IS_DGFX(tile_to_xe(tile)));
}
lockdep_assert_held(&ggtt->lock);
if ((++ggtt->access_count % max_gtt_writes) == 0) {
xe_mmio_write32(&affected_gt->mmio, GMD_ID, 0x0);
ggtt->access_count = 0;
}
}
u64 xe_ggtt_start(struct xe_ggtt *ggtt)
{
return ggtt->start;
}
u64 xe_ggtt_size(struct xe_ggtt *ggtt)
{
return ggtt->size;
}
static void xe_ggtt_set_pte(struct xe_ggtt *ggtt, u64 addr, u64 pte)
{
xe_tile_assert(ggtt->tile, !(addr & XE_PTE_MASK));
xe_tile_assert(ggtt->tile, addr < ggtt->start + ggtt->size);
writeq(pte, &ggtt->gsm[addr >> XE_PTE_SHIFT]);
}
static void xe_ggtt_set_pte_and_flush(struct xe_ggtt *ggtt, u64 addr, u64 pte)
{
xe_ggtt_set_pte(ggtt, addr, pte);
ggtt_update_access_counter(ggtt);
}
static u64 xe_ggtt_get_pte(struct xe_ggtt *ggtt, u64 addr)
{
xe_tile_assert(ggtt->tile, !(addr & XE_PTE_MASK));
xe_tile_assert(ggtt->tile, addr < ggtt->size);
return readq(&ggtt->gsm[addr >> XE_PTE_SHIFT]);
}
static void xe_ggtt_clear(struct xe_ggtt *ggtt, u64 start, u64 size)
{
u16 pat_index = tile_to_xe(ggtt->tile)->pat.idx[XE_CACHE_WB];
u64 end = start + size - 1;
u64 scratch_pte;
xe_tile_assert(ggtt->tile, start < end);
if (ggtt->scratch)
scratch_pte = xe_bo_addr(ggtt->scratch, 0, XE_PAGE_SIZE) |
ggtt->pt_ops->pte_encode_flags(ggtt->scratch,
pat_index);
else
scratch_pte = 0;
while (start < end) {
ggtt->pt_ops->ggtt_set_pte(ggtt, start, scratch_pte);
start += XE_PAGE_SIZE;
}
}
static void primelockdep(struct xe_ggtt *ggtt)
{
if (!IS_ENABLED(CONFIG_LOCKDEP))
return;
fs_reclaim_acquire(GFP_KERNEL);
might_lock(&ggtt->lock);
fs_reclaim_release(GFP_KERNEL);
}
struct xe_ggtt *xe_ggtt_alloc(struct xe_tile *tile)
{
struct xe_device *xe = tile_to_xe(tile);
struct xe_ggtt *ggtt;
ggtt = drmm_kzalloc(&xe->drm, sizeof(*ggtt), GFP_KERNEL);
if (!ggtt)
return NULL;
if (drmm_mutex_init(&xe->drm, &ggtt->lock))
return NULL;
primelockdep(ggtt);
ggtt->tile = tile;
return ggtt;
}
static void ggtt_fini_early(struct drm_device *drm, void *arg)
{
struct xe_ggtt *ggtt = arg;
destroy_workqueue(ggtt->wq);
drm_mm_takedown(&ggtt->mm);
}
static void ggtt_fini(void *arg)
{
struct xe_ggtt *ggtt = arg;
ggtt->scratch = NULL;
}
#ifdef CONFIG_LOCKDEP
void xe_ggtt_might_lock(struct xe_ggtt *ggtt)
{
might_lock(&ggtt->lock);
}
#endif
static const struct xe_ggtt_pt_ops xelp_pt_ops = {
.pte_encode_flags = xelp_ggtt_pte_flags,
.ggtt_set_pte = xe_ggtt_set_pte,
.ggtt_get_pte = xe_ggtt_get_pte,
};
static const struct xe_ggtt_pt_ops xelpg_pt_ops = {
.pte_encode_flags = xelpg_ggtt_pte_flags,
.ggtt_set_pte = xe_ggtt_set_pte,
.ggtt_get_pte = xe_ggtt_get_pte,
};
static const struct xe_ggtt_pt_ops xelpg_pt_wa_ops = {
.pte_encode_flags = xelpg_ggtt_pte_flags,
.ggtt_set_pte = xe_ggtt_set_pte_and_flush,
.ggtt_get_pte = xe_ggtt_get_pte,
};
static void __xe_ggtt_init_early(struct xe_ggtt *ggtt, u64 start, u64 size)
{
ggtt->start = start;
ggtt->size = size;
drm_mm_init(&ggtt->mm, start, size);
}
int xe_ggtt_init_kunit(struct xe_ggtt *ggtt, u32 start, u32 size)
{
__xe_ggtt_init_early(ggtt, start, size);
return 0;
}
EXPORT_SYMBOL_IF_KUNIT(xe_ggtt_init_kunit);
static void dev_fini_ggtt(void *arg)
{
struct xe_ggtt *ggtt = arg;
scoped_guard(mutex, &ggtt->lock)
ggtt->flags &= ~XE_GGTT_FLAGS_ONLINE;
drain_workqueue(ggtt->wq);
}
int xe_ggtt_init_early(struct xe_ggtt *ggtt)
{
struct xe_device *xe = tile_to_xe(ggtt->tile);
struct pci_dev *pdev = to_pci_dev(xe->drm.dev);
unsigned int gsm_size;
u64 ggtt_start, wopcm = xe_wopcm_size(xe), ggtt_size;
int err;
if (!IS_SRIOV_VF(xe)) {
if (GRAPHICS_VERx100(xe) >= 1250)
gsm_size = SZ_8M;
else
gsm_size = probe_gsm_size(pdev);
if (gsm_size == 0) {
xe_tile_err(ggtt->tile, "Hardware reported no preallocated GSM\n");
return -ENOMEM;
}
ggtt_start = wopcm;
ggtt_size = (gsm_size / 8) * (u64)XE_PAGE_SIZE - ggtt_start;
} else {
ggtt_start = wopcm;
ggtt_size = SZ_4G - ggtt_start;
}
ggtt->gsm = ggtt->tile->mmio.regs + SZ_8M;
if (IS_DGFX(xe) && xe->info.vram_flags & XE_VRAM_FLAGS_NEED64K)
ggtt->flags |= XE_GGTT_FLAGS_64K;
if (ggtt_size + ggtt_start > GUC_GGTT_TOP)
ggtt_size = GUC_GGTT_TOP - ggtt_start;
if (GRAPHICS_VERx100(xe) >= 1270)
ggtt->pt_ops =
(ggtt->tile->media_gt && XE_GT_WA(ggtt->tile->media_gt, 22019338487)) ||
(ggtt->tile->primary_gt && XE_GT_WA(ggtt->tile->primary_gt, 22019338487)) ?
&xelpg_pt_wa_ops : &xelpg_pt_ops;
else
ggtt->pt_ops = &xelp_pt_ops;
ggtt->wq = alloc_workqueue("xe-ggtt-wq", WQ_MEM_RECLAIM, 0);
if (!ggtt->wq)
return -ENOMEM;
__xe_ggtt_init_early(ggtt, ggtt_start, ggtt_size);
err = drmm_add_action_or_reset(&xe->drm, ggtt_fini_early, ggtt);
if (err)
return err;
ggtt->flags |= XE_GGTT_FLAGS_ONLINE;
err = devm_add_action_or_reset(xe->drm.dev, dev_fini_ggtt, ggtt);
if (err)
return err;
if (IS_SRIOV_VF(xe)) {
err = xe_tile_sriov_vf_prepare_ggtt(ggtt->tile);
if (err)
return err;
}
return 0;
}
ALLOW_ERROR_INJECTION(xe_ggtt_init_early, ERRNO);
static void xe_ggtt_invalidate(struct xe_ggtt *ggtt);
static void xe_ggtt_initial_clear(struct xe_ggtt *ggtt)
{
struct drm_mm_node *hole;
u64 start, end;
mutex_lock(&ggtt->lock);
drm_mm_for_each_hole(hole, &ggtt->mm, start, end)
xe_ggtt_clear(ggtt, start, end - start);
xe_ggtt_invalidate(ggtt);
mutex_unlock(&ggtt->lock);
}
static void ggtt_node_remove(struct xe_ggtt_node *node)
{
struct xe_ggtt *ggtt = node->ggtt;
bool bound;
mutex_lock(&ggtt->lock);
bound = ggtt->flags & XE_GGTT_FLAGS_ONLINE;
if (bound)
xe_ggtt_clear(ggtt, node->base.start, node->base.size);
drm_mm_remove_node(&node->base);
node->base.size = 0;
mutex_unlock(&ggtt->lock);
if (!bound)
goto free_node;
if (node->invalidate_on_remove)
xe_ggtt_invalidate(ggtt);
free_node:
xe_ggtt_node_fini(node);
}
static void ggtt_node_remove_work_func(struct work_struct *work)
{
struct xe_ggtt_node *node = container_of(work, typeof(*node),
delayed_removal_work);
struct xe_device *xe = tile_to_xe(node->ggtt->tile);
guard(xe_pm_runtime)(xe);
ggtt_node_remove(node);
}
void xe_ggtt_node_remove(struct xe_ggtt_node *node, bool invalidate)
{
struct xe_ggtt *ggtt;
struct xe_device *xe;
if (!node || !node->ggtt)
return;
ggtt = node->ggtt;
xe = tile_to_xe(ggtt->tile);
node->invalidate_on_remove = invalidate;
if (xe_pm_runtime_get_if_active(xe)) {
ggtt_node_remove(node);
xe_pm_runtime_put(xe);
} else {
queue_work(ggtt->wq, &node->delayed_removal_work);
}
}
int xe_ggtt_init(struct xe_ggtt *ggtt)
{
struct xe_device *xe = tile_to_xe(ggtt->tile);
unsigned int flags;
int err;
flags = 0;
if (ggtt->flags & XE_GGTT_FLAGS_64K)
flags |= XE_BO_FLAG_SYSTEM;
else
flags |= XE_BO_FLAG_VRAM_IF_DGFX(ggtt->tile);
ggtt->scratch = xe_managed_bo_create_pin_map(xe, ggtt->tile, XE_PAGE_SIZE, flags);
if (IS_ERR(ggtt->scratch)) {
err = PTR_ERR(ggtt->scratch);
goto err;
}
xe_map_memset(xe, &ggtt->scratch->vmap, 0, 0, xe_bo_size(ggtt->scratch));
xe_ggtt_initial_clear(ggtt);
return devm_add_action_or_reset(xe->drm.dev, ggtt_fini, ggtt);
err:
ggtt->scratch = NULL;
return err;
}
static void ggtt_invalidate_gt_tlb(struct xe_gt *gt)
{
int err;
if (!gt)
return;
err = xe_tlb_inval_ggtt(>->tlb_inval);
xe_gt_WARN(gt, err, "Failed to invalidate GGTT (%pe)", ERR_PTR(err));
}
static void xe_ggtt_invalidate(struct xe_ggtt *ggtt)
{
struct xe_device *xe = tile_to_xe(ggtt->tile);
xe_mmio_read32(xe_root_tile_mmio(xe), VF_CAP_REG);
ggtt_invalidate_gt_tlb(ggtt->tile->primary_gt);
ggtt_invalidate_gt_tlb(ggtt->tile->media_gt);
}
static void xe_ggtt_dump_node(struct xe_ggtt *ggtt,
const struct drm_mm_node *node, const char *description)
{
char buf[10];
if (IS_ENABLED(CONFIG_DRM_XE_DEBUG)) {
string_get_size(node->size, 1, STRING_UNITS_2, buf, sizeof(buf));
xe_tile_dbg(ggtt->tile, "GGTT %#llx-%#llx (%s) %s\n",
node->start, node->start + node->size, buf, description);
}
}
int xe_ggtt_node_insert_balloon_locked(struct xe_ggtt_node *node, u64 start, u64 end)
{
struct xe_ggtt *ggtt = node->ggtt;
int err;
xe_tile_assert(ggtt->tile, start < end);
xe_tile_assert(ggtt->tile, IS_ALIGNED(start, XE_PAGE_SIZE));
xe_tile_assert(ggtt->tile, IS_ALIGNED(end, XE_PAGE_SIZE));
xe_tile_assert(ggtt->tile, !drm_mm_node_allocated(&node->base));
lockdep_assert_held(&ggtt->lock);
node->base.color = 0;
node->base.start = start;
node->base.size = end - start;
err = drm_mm_reserve_node(&ggtt->mm, &node->base);
if (xe_tile_WARN(ggtt->tile, err, "Failed to balloon GGTT %#llx-%#llx (%pe)\n",
node->base.start, node->base.start + node->base.size, ERR_PTR(err)))
return err;
xe_ggtt_dump_node(ggtt, &node->base, "balloon");
return 0;
}
void xe_ggtt_node_remove_balloon_locked(struct xe_ggtt_node *node)
{
if (!xe_ggtt_node_allocated(node))
return;
lockdep_assert_held(&node->ggtt->lock);
xe_ggtt_dump_node(node->ggtt, &node->base, "remove-balloon");
drm_mm_remove_node(&node->base);
}
static void xe_ggtt_assert_fit(struct xe_ggtt *ggtt, u64 start, u64 size)
{
struct xe_tile *tile = ggtt->tile;
xe_tile_assert(tile, start >= ggtt->start);
xe_tile_assert(tile, start + size <= ggtt->start + ggtt->size);
}
void xe_ggtt_shift_nodes_locked(struct xe_ggtt *ggtt, s64 shift)
{
struct xe_tile *tile __maybe_unused = ggtt->tile;
struct drm_mm_node *node, *tmpn;
LIST_HEAD(temp_list_head);
lockdep_assert_held(&ggtt->lock);
if (IS_ENABLED(CONFIG_DRM_XE_DEBUG))
drm_mm_for_each_node_safe(node, tmpn, &ggtt->mm)
xe_ggtt_assert_fit(ggtt, node->start + shift, node->size);
drm_mm_for_each_node_safe(node, tmpn, &ggtt->mm) {
drm_mm_remove_node(node);
list_add(&node->node_list, &temp_list_head);
}
list_for_each_entry_safe(node, tmpn, &temp_list_head, node_list) {
list_del(&node->node_list);
node->start += shift;
drm_mm_reserve_node(&ggtt->mm, node);
xe_tile_assert(tile, drm_mm_node_allocated(node));
}
}
static int xe_ggtt_node_insert_locked(struct xe_ggtt_node *node,
u32 size, u32 align, u32 mm_flags)
{
return drm_mm_insert_node_generic(&node->ggtt->mm, &node->base, size, align, 0,
mm_flags);
}
int xe_ggtt_node_insert(struct xe_ggtt_node *node, u32 size, u32 align)
{
int ret;
if (!node || !node->ggtt)
return -ENOENT;
mutex_lock(&node->ggtt->lock);
ret = xe_ggtt_node_insert_locked(node, size, align,
DRM_MM_INSERT_HIGH);
mutex_unlock(&node->ggtt->lock);
return ret;
}
struct xe_ggtt_node *xe_ggtt_node_init(struct xe_ggtt *ggtt)
{
struct xe_ggtt_node *node = kzalloc_obj(*node, GFP_NOFS);
if (!node)
return ERR_PTR(-ENOMEM);
INIT_WORK(&node->delayed_removal_work, ggtt_node_remove_work_func);
node->ggtt = ggtt;
return node;
}
void xe_ggtt_node_fini(struct xe_ggtt_node *node)
{
kfree(node);
}
bool xe_ggtt_node_allocated(const struct xe_ggtt_node *node)
{
if (!node || !node->ggtt)
return false;
return drm_mm_node_allocated(&node->base);
}
size_t xe_ggtt_node_pt_size(const struct xe_ggtt_node *node)
{
if (!node)
return 0;
return node->base.size / XE_PAGE_SIZE * sizeof(u64);
}
static void xe_ggtt_map_bo(struct xe_ggtt *ggtt, struct xe_ggtt_node *node,
struct xe_bo *bo, u64 pte)
{
u64 start, end;
struct xe_res_cursor cur;
if (XE_WARN_ON(!node))
return;
start = node->base.start;
end = start + xe_bo_size(bo);
if (!xe_bo_is_vram(bo) && !xe_bo_is_stolen(bo)) {
xe_assert(xe_bo_device(bo), bo->ttm.ttm);
for (xe_res_first_sg(xe_bo_sg(bo), 0, xe_bo_size(bo), &cur);
cur.remaining; xe_res_next(&cur, XE_PAGE_SIZE))
ggtt->pt_ops->ggtt_set_pte(ggtt, end - cur.remaining,
pte | xe_res_dma(&cur));
} else {
pte |= vram_region_gpu_offset(bo->ttm.resource);
for (xe_res_first(bo->ttm.resource, 0, xe_bo_size(bo), &cur);
cur.remaining; xe_res_next(&cur, XE_PAGE_SIZE))
ggtt->pt_ops->ggtt_set_pte(ggtt, end - cur.remaining,
pte + cur.start);
}
}
void xe_ggtt_map_bo_unlocked(struct xe_ggtt *ggtt, struct xe_bo *bo)
{
u16 cache_mode = bo->flags & XE_BO_FLAG_NEEDS_UC ? XE_CACHE_NONE : XE_CACHE_WB;
u16 pat_index = tile_to_xe(ggtt->tile)->pat.idx[cache_mode];
u64 pte;
mutex_lock(&ggtt->lock);
pte = ggtt->pt_ops->pte_encode_flags(bo, pat_index);
xe_ggtt_map_bo(ggtt, bo->ggtt_node[ggtt->tile->id], bo, pte);
mutex_unlock(&ggtt->lock);
}
struct xe_ggtt_node *xe_ggtt_node_insert_transform(struct xe_ggtt *ggtt,
struct xe_bo *bo, u64 pte_flags,
u64 size, u32 align,
xe_ggtt_transform_cb transform, void *arg)
{
struct xe_ggtt_node *node;
int ret;
node = xe_ggtt_node_init(ggtt);
if (IS_ERR(node))
return ERR_CAST(node);
if (mutex_lock_interruptible(&ggtt->lock) < 0) {
ret = -ERESTARTSYS;
goto err;
}
ret = xe_ggtt_node_insert_locked(node, size, align, 0);
if (ret)
goto err_unlock;
if (transform)
transform(ggtt, node, pte_flags, ggtt->pt_ops->ggtt_set_pte, arg);
else
xe_ggtt_map_bo(ggtt, node, bo, pte_flags);
mutex_unlock(&ggtt->lock);
return node;
err_unlock:
mutex_unlock(&ggtt->lock);
err:
xe_ggtt_node_fini(node);
return ERR_PTR(ret);
}
static int __xe_ggtt_insert_bo_at(struct xe_ggtt *ggtt, struct xe_bo *bo,
u64 start, u64 end, struct drm_exec *exec)
{
u64 alignment = bo->min_align > 0 ? bo->min_align : XE_PAGE_SIZE;
u8 tile_id = ggtt->tile->id;
int err;
if (xe_bo_is_vram(bo) && ggtt->flags & XE_GGTT_FLAGS_64K)
alignment = SZ_64K;
if (XE_WARN_ON(bo->ggtt_node[tile_id])) {
xe_tile_assert(ggtt->tile, bo->ggtt_node[tile_id]->base.size == xe_bo_size(bo));
return 0;
}
err = xe_bo_validate(bo, NULL, false, exec);
if (err)
return err;
xe_pm_runtime_get_noresume(tile_to_xe(ggtt->tile));
bo->ggtt_node[tile_id] = xe_ggtt_node_init(ggtt);
if (IS_ERR(bo->ggtt_node[tile_id])) {
err = PTR_ERR(bo->ggtt_node[tile_id]);
bo->ggtt_node[tile_id] = NULL;
goto out;
}
mutex_lock(&ggtt->lock);
err = drm_mm_insert_node_in_range(&ggtt->mm, &bo->ggtt_node[tile_id]->base,
xe_bo_size(bo), alignment, 0, start, end, 0);
if (err) {
xe_ggtt_node_fini(bo->ggtt_node[tile_id]);
bo->ggtt_node[tile_id] = NULL;
} else {
u16 cache_mode = bo->flags & XE_BO_FLAG_NEEDS_UC ? XE_CACHE_NONE : XE_CACHE_WB;
u16 pat_index = tile_to_xe(ggtt->tile)->pat.idx[cache_mode];
u64 pte = ggtt->pt_ops->pte_encode_flags(bo, pat_index);
xe_ggtt_map_bo(ggtt, bo->ggtt_node[tile_id], bo, pte);
}
mutex_unlock(&ggtt->lock);
if (!err && bo->flags & XE_BO_FLAG_GGTT_INVALIDATE)
xe_ggtt_invalidate(ggtt);
out:
xe_pm_runtime_put(tile_to_xe(ggtt->tile));
return err;
}
int xe_ggtt_insert_bo_at(struct xe_ggtt *ggtt, struct xe_bo *bo,
u64 start, u64 end, struct drm_exec *exec)
{
return __xe_ggtt_insert_bo_at(ggtt, bo, start, end, exec);
}
int xe_ggtt_insert_bo(struct xe_ggtt *ggtt, struct xe_bo *bo,
struct drm_exec *exec)
{
return __xe_ggtt_insert_bo_at(ggtt, bo, 0, U64_MAX, exec);
}
void xe_ggtt_remove_bo(struct xe_ggtt *ggtt, struct xe_bo *bo)
{
u8 tile_id = ggtt->tile->id;
if (XE_WARN_ON(!bo->ggtt_node[tile_id]))
return;
xe_tile_assert(ggtt->tile, bo->ggtt_node[tile_id]->base.size == xe_bo_size(bo));
xe_ggtt_node_remove(bo->ggtt_node[tile_id],
bo->flags & XE_BO_FLAG_GGTT_INVALIDATE);
}
u64 xe_ggtt_largest_hole(struct xe_ggtt *ggtt, u64 alignment, u64 *spare)
{
const struct drm_mm *mm = &ggtt->mm;
const struct drm_mm_node *entry;
u64 hole_start, hole_end, hole_size;
u64 max_hole = 0;
mutex_lock(&ggtt->lock);
drm_mm_for_each_hole(entry, mm, hole_start, hole_end) {
hole_start = max(hole_start, ggtt->start);
hole_start = ALIGN(hole_start, alignment);
hole_end = ALIGN_DOWN(hole_end, alignment);
if (hole_start >= hole_end)
continue;
hole_size = hole_end - hole_start;
if (spare)
*spare -= min3(*spare, hole_size, max_hole);
max_hole = max(max_hole, hole_size);
}
mutex_unlock(&ggtt->lock);
return max_hole;
}
#ifdef CONFIG_PCI_IOV
static u64 xe_encode_vfid_pte(u16 vfid)
{
return FIELD_PREP(GGTT_PTE_VFID, vfid) | XE_PAGE_PRESENT;
}
static void xe_ggtt_assign_locked(struct xe_ggtt *ggtt, const struct drm_mm_node *node, u16 vfid)
{
u64 start = node->start;
u64 size = node->size;
u64 end = start + size - 1;
u64 pte = xe_encode_vfid_pte(vfid);
lockdep_assert_held(&ggtt->lock);
if (!drm_mm_node_allocated(node))
return;
while (start < end) {
ggtt->pt_ops->ggtt_set_pte(ggtt, start, pte);
start += XE_PAGE_SIZE;
}
xe_ggtt_invalidate(ggtt);
}
void xe_ggtt_assign(const struct xe_ggtt_node *node, u16 vfid)
{
mutex_lock(&node->ggtt->lock);
xe_ggtt_assign_locked(node->ggtt, &node->base, vfid);
mutex_unlock(&node->ggtt->lock);
}
int xe_ggtt_node_save(struct xe_ggtt_node *node, void *dst, size_t size, u16 vfid)
{
struct xe_ggtt *ggtt;
u64 start, end;
u64 *buf = dst;
u64 pte;
if (!node)
return -ENOENT;
guard(mutex)(&node->ggtt->lock);
if (xe_ggtt_node_pt_size(node) != size)
return -EINVAL;
ggtt = node->ggtt;
start = node->base.start;
end = start + node->base.size - 1;
while (start < end) {
pte = ggtt->pt_ops->ggtt_get_pte(ggtt, start);
if (vfid != u64_get_bits(pte, GGTT_PTE_VFID))
return -EPERM;
*buf++ = u64_replace_bits(pte, 0, GGTT_PTE_VFID);
start += XE_PAGE_SIZE;
}
return 0;
}
int xe_ggtt_node_load(struct xe_ggtt_node *node, const void *src, size_t size, u16 vfid)
{
u64 vfid_pte = xe_encode_vfid_pte(vfid);
const u64 *buf = src;
struct xe_ggtt *ggtt;
u64 start, end;
if (!node)
return -ENOENT;
guard(mutex)(&node->ggtt->lock);
if (xe_ggtt_node_pt_size(node) != size)
return -EINVAL;
ggtt = node->ggtt;
start = node->base.start;
end = start + node->base.size - 1;
while (start < end) {
vfid_pte = u64_replace_bits(*buf++, vfid, GGTT_PTE_VFID);
ggtt->pt_ops->ggtt_set_pte(ggtt, start, vfid_pte);
start += XE_PAGE_SIZE;
}
xe_ggtt_invalidate(ggtt);
return 0;
}
#endif
int xe_ggtt_dump(struct xe_ggtt *ggtt, struct drm_printer *p)
{
int err;
err = mutex_lock_interruptible(&ggtt->lock);
if (err)
return err;
drm_mm_print(&ggtt->mm, p);
mutex_unlock(&ggtt->lock);
return err;
}
u64 xe_ggtt_print_holes(struct xe_ggtt *ggtt, u64 alignment, struct drm_printer *p)
{
const struct drm_mm *mm = &ggtt->mm;
const struct drm_mm_node *entry;
u64 hole_start, hole_end, hole_size;
u64 total = 0;
char buf[10];
mutex_lock(&ggtt->lock);
drm_mm_for_each_hole(entry, mm, hole_start, hole_end) {
hole_start = max(hole_start, ggtt->start);
hole_start = ALIGN(hole_start, alignment);
hole_end = ALIGN_DOWN(hole_end, alignment);
if (hole_start >= hole_end)
continue;
hole_size = hole_end - hole_start;
total += hole_size;
string_get_size(hole_size, 1, STRING_UNITS_2, buf, sizeof(buf));
drm_printf(p, "range:\t%#llx-%#llx\t(%s)\n",
hole_start, hole_end - 1, buf);
}
mutex_unlock(&ggtt->lock);
return total;
}
u64 xe_ggtt_encode_pte_flags(struct xe_ggtt *ggtt,
struct xe_bo *bo, u16 pat_index)
{
return ggtt->pt_ops->pte_encode_flags(bo, pat_index);
}
u64 xe_ggtt_read_pte(struct xe_ggtt *ggtt, u64 offset)
{
return ioread64(ggtt->gsm + (offset / XE_PAGE_SIZE));
}
u64 xe_ggtt_node_addr(const struct xe_ggtt_node *node)
{
return node->base.start;
}
u64 xe_ggtt_node_size(const struct xe_ggtt_node *node)
{
return node->base.size;
}