// SPDX-License-Identifier: MIT /* * Copyright © 2023 Intel Corporation */ #include #include #include "regs/xe_gt_regs.h" #include "xe_assert.h" #include "xe_bo.h" #include "xe_lmtt.h" #include "xe_map.h" #include "xe_mmio.h" #include "xe_res_cursor.h" #include "xe_sriov.h" #include "xe_sriov_printk.h" /** * DOC: Local Memory Translation Table * * The Local Memory Translation Table (LMTT) provides additional abstraction * when Virtual Function (VF) is accessing device Local Memory (VRAM). * * The Root LMTT Page Directory contains one entry for each VF. Entries are * indexed by the function number (1-based, index 0 is unused). * * See `Two-Level LMTT Structure`_ and `Multi-Level LMTT Structure`_. */ #define lmtt_assert(lmtt, condition) xe_tile_assert(lmtt_to_tile(lmtt), condition) #define lmtt_debug(lmtt, msg...) xe_sriov_dbg_verbose(lmtt_to_xe(lmtt), "LMTT: " msg) static bool xe_has_multi_level_lmtt(struct xe_device *xe) { return GRAPHICS_VERx100(xe) >= 1260; } static struct xe_tile *lmtt_to_tile(struct xe_lmtt *lmtt) { return container_of(lmtt, struct xe_tile, sriov.pf.lmtt); } static struct xe_device *lmtt_to_xe(struct xe_lmtt *lmtt) { return tile_to_xe(lmtt_to_tile(lmtt)); } static u64 lmtt_page_size(struct xe_lmtt *lmtt) { return BIT_ULL(lmtt->ops->lmtt_pte_shift(0)); } static struct xe_lmtt_pt *lmtt_pt_alloc(struct xe_lmtt *lmtt, unsigned int level) { unsigned int num_entries = level ? lmtt->ops->lmtt_pte_num(level) : 0; struct xe_lmtt_pt *pt; struct xe_bo *bo; int err; pt = kzalloc(struct_size(pt, entries, num_entries), GFP_KERNEL); if (!pt) { err = -ENOMEM; goto out; } bo = xe_bo_create_pin_map(lmtt_to_xe(lmtt), lmtt_to_tile(lmtt), NULL, PAGE_ALIGN(lmtt->ops->lmtt_pte_size(level) * lmtt->ops->lmtt_pte_num(level)), ttm_bo_type_kernel, XE_BO_FLAG_VRAM_IF_DGFX(lmtt_to_tile(lmtt)) | XE_BO_FLAG_NEEDS_64K | XE_BO_FLAG_PINNED); if (IS_ERR(bo)) { err = PTR_ERR(bo); goto out_free_pt; } lmtt_assert(lmtt, xe_bo_is_vram(bo)); pt->level = level; pt->bo = bo; return pt; out_free_pt: kfree(pt); out: return ERR_PTR(err); } static void lmtt_pt_free(struct xe_lmtt_pt *pt) { xe_bo_unpin_map_no_vm(pt->bo); kfree(pt); } static int lmtt_init_pd(struct xe_lmtt *lmtt) { struct xe_lmtt_pt *pd; lmtt_assert(lmtt, !lmtt->pd); lmtt_assert(lmtt, lmtt->ops->lmtt_root_pd_level()); pd = lmtt_pt_alloc(lmtt, lmtt->ops->lmtt_root_pd_level()); if (IS_ERR(pd)) return PTR_ERR(pd); lmtt->pd = pd; return 0; } static void lmtt_fini_pd(struct xe_lmtt *lmtt) { struct xe_lmtt_pt *pd = lmtt->pd; unsigned int num_entries = lmtt->ops->lmtt_pte_num(pd->level); unsigned int n = 0; /* make sure we don't leak */ for (n = 0; n < num_entries; n++) lmtt_assert(lmtt, !pd->entries[n]); lmtt->pd = NULL; lmtt_pt_free(pd); } static void fini_lmtt(struct drm_device *drm, void *arg) { struct xe_lmtt *lmtt = arg; lmtt_assert(lmtt, !(!!lmtt->ops ^ !!lmtt->pd)); if (!lmtt->pd) return; lmtt_fini_pd(lmtt); lmtt->ops = NULL; } /** * xe_lmtt_init - LMTT software initialization. * @lmtt: the &xe_lmtt to initialize * * The LMTT initialization requires two steps. * * The xe_lmtt_init() checks if LMTT is required on current device and selects * and initialize proper variant of the LMTT Root Directory. Currently supported * variants are `Two-Level LMTT Structure`_ and `Multi-Level LMTT Structure`_. * * In next step xe_lmtt_init_hw() will register this directory on the hardware. * * Notes: * The LMTT allocations are managed and will be implicitly released on driver unload. * This function shall be called only once and only when running as a PF driver. * Any LMTT initialization failure should block VFs enabling. * * Return: 0 on success or a negative error code on failure. */ int xe_lmtt_init(struct xe_lmtt *lmtt) { struct xe_device *xe = lmtt_to_xe(lmtt); int err; lmtt_assert(lmtt, IS_SRIOV_PF(xe)); lmtt_assert(lmtt, !lmtt->ops); if (!IS_DGFX(xe)) return 0; if (xe_has_multi_level_lmtt(xe)) lmtt->ops = &lmtt_ml_ops; else lmtt->ops = &lmtt_2l_ops; err = lmtt_init_pd(lmtt); if (unlikely(err)) goto fail; return drmm_add_action_or_reset(&xe->drm, fini_lmtt, lmtt); fail: lmtt->ops = NULL; return err; } static void lmtt_setup_dir_ptr(struct xe_lmtt *lmtt) { struct xe_tile *tile = lmtt_to_tile(lmtt); struct xe_device *xe = tile_to_xe(tile); dma_addr_t offset = xe_bo_main_addr(lmtt->pd->bo, XE_PAGE_SIZE); lmtt_debug(lmtt, "DIR offset %pad\n", &offset); lmtt_assert(lmtt, xe_bo_is_vram(lmtt->pd->bo)); lmtt_assert(lmtt, IS_ALIGNED(offset, SZ_64K)); xe_mmio_write32(tile->primary_gt, GRAPHICS_VER(xe) >= 20 ? XE2_LMEM_CFG : LMEM_CFG, LMEM_EN | REG_FIELD_PREP(LMTT_DIR_PTR, offset / SZ_64K)); } /** * xe_lmtt_init_hw - Perform LMTT hardware initialization. * @lmtt: the &xe_lmtt to initialize * * This function is a second step of the LMTT initialization. * This function registers LMTT Root Directory prepared in xe_lmtt_init(). * * This function shall be called after every hardware reset. * This function shall be called only when running as a PF driver. */ void xe_lmtt_init_hw(struct xe_lmtt *lmtt) { if (!lmtt->pd) return; lmtt_setup_dir_ptr(lmtt); } static void lmtt_write_pte(struct xe_lmtt *lmtt, struct xe_lmtt_pt *pt, u64 pte, unsigned int idx) { unsigned int level = pt->level; lmtt_assert(lmtt, idx <= lmtt->ops->lmtt_pte_num(level)); lmtt_debug(lmtt, "WRITE level=%u index=%u pte=%#llx\n", level, idx, pte); switch (lmtt->ops->lmtt_pte_size(level)) { case sizeof(u32): xe_map_wr(lmtt_to_xe(lmtt), &pt->bo->vmap, idx * sizeof(u32), u32, pte); break; case sizeof(u64): xe_map_wr(lmtt_to_xe(lmtt), &pt->bo->vmap, idx * sizeof(u64), u64, pte); break; default: lmtt_assert(lmtt, !!!"invalid pte size"); } } static void lmtt_destroy_pt(struct xe_lmtt *lmtt, struct xe_lmtt_pt *pd) { unsigned int num_entries = pd->level ? lmtt->ops->lmtt_pte_num(pd->level) : 0; struct xe_lmtt_pt *pt; unsigned int i; for (i = 0; i < num_entries; i++) { pt = pd->entries[i]; pd->entries[i] = NULL; if (!pt) continue; lmtt_destroy_pt(lmtt, pt); } lmtt_pt_free(pd); } static void lmtt_drop_pages(struct xe_lmtt *lmtt, unsigned int vfid) { struct xe_lmtt_pt *pd = lmtt->pd; struct xe_lmtt_pt *pt; pt = pd->entries[vfid]; pd->entries[vfid] = NULL; if (!pt) return; lmtt_write_pte(lmtt, pd, LMTT_PTE_INVALID, vfid); lmtt_assert(lmtt, pd->level > 0); lmtt_assert(lmtt, pt->level == pd->level - 1); lmtt_destroy_pt(lmtt, pt); } static int __lmtt_alloc_range(struct xe_lmtt *lmtt, struct xe_lmtt_pt *pd, u64 start, u64 end) { u64 pte_addr_shift = BIT_ULL(lmtt->ops->lmtt_pte_shift(pd->level)); u64 offset; int err; lmtt_assert(lmtt, pd->level > 0); offset = start; while (offset < end) { struct xe_lmtt_pt *pt; u64 next, pde, pt_addr; unsigned int idx; pt = lmtt_pt_alloc(lmtt, pd->level - 1); if (IS_ERR(pt)) return PTR_ERR(pt); pt_addr = xe_bo_main_addr(pt->bo, XE_PAGE_SIZE); idx = lmtt->ops->lmtt_pte_index(offset, pd->level); pde = lmtt->ops->lmtt_pte_encode(pt_addr, pd->level); lmtt_write_pte(lmtt, pd, pde, idx); pd->entries[idx] = pt; next = min(end, round_up(offset + 1, pte_addr_shift)); if (pt->level != 0) { err = __lmtt_alloc_range(lmtt, pt, offset, next); if (err) return err; } offset = next; } return 0; } static int lmtt_alloc_range(struct xe_lmtt *lmtt, unsigned int vfid, u64 start, u64 end) { struct xe_lmtt_pt *pd = lmtt->pd; struct xe_lmtt_pt *pt; u64 pt_addr; u64 pde; int err; lmtt_assert(lmtt, pd->level > 0); lmtt_assert(lmtt, vfid <= lmtt->ops->lmtt_pte_num(pd->level)); lmtt_assert(lmtt, IS_ALIGNED(start, lmtt_page_size(lmtt))); lmtt_assert(lmtt, IS_ALIGNED(end, lmtt_page_size(lmtt))); if (pd->entries[vfid]) return -ENOTEMPTY; pt = lmtt_pt_alloc(lmtt, pd->level - 1); if (IS_ERR(pt)) return PTR_ERR(pt); pt_addr = xe_bo_main_addr(pt->bo, XE_PAGE_SIZE); pde = lmtt->ops->lmtt_pte_encode(pt_addr, pd->level); lmtt_write_pte(lmtt, pd, pde, vfid); pd->entries[vfid] = pt; if (pt->level != 0) { err = __lmtt_alloc_range(lmtt, pt, start, end); if (err) goto out_free_pt; } return 0; out_free_pt: lmtt_pt_free(pt); return err; } static struct xe_lmtt_pt *lmtt_leaf_pt(struct xe_lmtt *lmtt, unsigned int vfid, u64 addr) { struct xe_lmtt_pt *pd = lmtt->pd; struct xe_lmtt_pt *pt; lmtt_assert(lmtt, vfid <= lmtt->ops->lmtt_pte_num(pd->level)); pt = pd->entries[vfid]; while (pt->level) { lmtt_assert(lmtt, lmtt->ops->lmtt_pte_index(addr, pt->level) <= lmtt->ops->lmtt_pte_num(pt->level)); pt = pt->entries[lmtt->ops->lmtt_pte_index(addr, pt->level)]; addr >>= lmtt->ops->lmtt_pte_shift(pt->level); } lmtt_assert(lmtt, lmtt->ops->lmtt_pte_index(addr, pt->level) <= lmtt->ops->lmtt_pte_num(pt->level)); lmtt_assert(lmtt, pt->level != pd->level); lmtt_assert(lmtt, pt->level == 0); return pt; } static void lmtt_insert_bo(struct xe_lmtt *lmtt, unsigned int vfid, struct xe_bo *bo, u64 start) { u64 page_size = lmtt_page_size(lmtt); struct xe_res_cursor cur; struct xe_lmtt_pt *pt; u64 addr, vram_offset; lmtt_assert(lmtt, IS_ALIGNED(start, page_size)); lmtt_assert(lmtt, IS_ALIGNED(bo->size, page_size)); lmtt_assert(lmtt, xe_bo_is_vram(bo)); vram_offset = vram_region_gpu_offset(bo->ttm.resource); xe_res_first(bo->ttm.resource, 0, bo->size, &cur); while (cur.remaining) { addr = xe_res_dma(&cur); addr += vram_offset; /* XXX */ pt = lmtt_leaf_pt(lmtt, vfid, start); lmtt_write_pte(lmtt, pt, lmtt->ops->lmtt_pte_encode(addr, 0), lmtt->ops->lmtt_pte_index(start, 0)); xe_res_next(&cur, page_size); start += page_size; } } /** * xe_lmtt_prepare_pages - Create VF's LMTT Page Tables. * @lmtt: the &xe_lmtt to update * @vfid: the VF identifier (1-based) * @range: top range of LMEM offset to be supported * * This function creates empty LMTT page tables for given VF to support * up to maximum #range LMEM offset. The LMTT page tables created by this * function must be released using xe_lmtt_drop_pages() function. * * Notes: * This function shall be called only after successful LMTT initialization. * See xe_lmtt_init(). * * Return: 0 on success or a negative error code on failure. */ int xe_lmtt_prepare_pages(struct xe_lmtt *lmtt, unsigned int vfid, u64 range) { lmtt_assert(lmtt, lmtt->pd); lmtt_assert(lmtt, vfid); return lmtt_alloc_range(lmtt, vfid, 0, range); } /** * xe_lmtt_populate_pages - Update VF's LMTT Page Table Entries. * @lmtt: the &xe_lmtt to update * @vfid: the VF identifier (1-based) * @bo: the buffer object with LMEM allocation to be mapped * @offset: the offset at which #bo should be mapped * * This function updates VF's LMTT entries to use given buffer object as a backstore. * * Notes: * This function shall be called only after successful preparation of the * VF's LMTT Page Tables. See xe_lmtt_prepare(). * * Return: 0 on success or a negative error code on failure. */ int xe_lmtt_populate_pages(struct xe_lmtt *lmtt, unsigned int vfid, struct xe_bo *bo, u64 offset) { lmtt_assert(lmtt, lmtt->pd); lmtt_assert(lmtt, vfid); lmtt_insert_bo(lmtt, vfid, bo, offset); return 0; } /** * xe_lmtt_drop_pages - Remove VF's LMTT Pages. * @lmtt: the &xe_lmtt to update * @vfid: the VF identifier (1-based) * * This function removes all LMTT Page Tables prepared by xe_lmtt_prepare_pages(). * * This function shall be called only after successful LMTT initialization. * See xe_lmtt_init(). */ void xe_lmtt_drop_pages(struct xe_lmtt *lmtt, unsigned int vfid) { lmtt_assert(lmtt, lmtt->pd); lmtt_assert(lmtt, vfid); lmtt_drop_pages(lmtt, vfid); } /** * xe_lmtt_estimate_pt_size - Estimate size of LMTT PT allocations. * @lmtt: the &xe_lmtt * @size: the size of the LMEM to be mapped over LMTT (including any offset) * * This function shall be called only by PF. * * Return: size of the PT allocation(s) needed to support given LMEM size. */ u64 xe_lmtt_estimate_pt_size(struct xe_lmtt *lmtt, u64 size) { unsigned int level = 0; u64 pt_size; lmtt_assert(lmtt, IS_SRIOV_PF(lmtt_to_xe(lmtt))); lmtt_assert(lmtt, IS_DGFX(lmtt_to_xe(lmtt))); lmtt_assert(lmtt, lmtt->ops); pt_size = PAGE_ALIGN(lmtt->ops->lmtt_pte_size(level) * lmtt->ops->lmtt_pte_num(level)); while (++level < lmtt->ops->lmtt_root_pd_level()) { pt_size *= lmtt->ops->lmtt_pte_index(size, level) + 1; pt_size += PAGE_ALIGN(lmtt->ops->lmtt_pte_size(level) * lmtt->ops->lmtt_pte_num(level)); } return pt_size; } #if IS_BUILTIN(CONFIG_DRM_XE_KUNIT_TEST) #include "tests/xe_lmtt_test.c" #endif