// SPDX-License-Identifier: MIT /* * Copyright © 2022 Intel Corporation */ #include "xe_bo_evict.h" #include "xe_bo.h" #include "xe_device.h" #include "xe_ggtt.h" #include "xe_tile.h" typedef int (*xe_pinned_fn)(struct xe_bo *bo); static int xe_bo_apply_to_pinned(struct xe_device *xe, struct list_head *pinned_list, struct list_head *new_list, const xe_pinned_fn pinned_fn) { LIST_HEAD(still_in_list); struct xe_bo *bo; int ret = 0; spin_lock(&xe->pinned.lock); while (!ret) { bo = list_first_entry_or_null(pinned_list, typeof(*bo), pinned_link); if (!bo) break; xe_bo_get(bo); list_move_tail(&bo->pinned_link, &still_in_list); spin_unlock(&xe->pinned.lock); ret = pinned_fn(bo); if (ret && pinned_list != new_list) { spin_lock(&xe->pinned.lock); /* * We might no longer be pinned, since PM notifier can * call this. If the pinned link is now empty, keep it * that way. */ if (!list_empty(&bo->pinned_link)) list_move(&bo->pinned_link, pinned_list); spin_unlock(&xe->pinned.lock); } xe_bo_put(bo); spin_lock(&xe->pinned.lock); } list_splice_tail(&still_in_list, new_list); spin_unlock(&xe->pinned.lock); return ret; } /** * xe_bo_notifier_prepare_all_pinned() - Pre-allocate the backing pages for all * pinned VRAM objects which need to be saved. * @xe: xe device * * Should be called from PM notifier when preparing for s3/s4. * * Return: 0 on success, negative error code on error. */ int xe_bo_notifier_prepare_all_pinned(struct xe_device *xe) { int ret; ret = xe_bo_apply_to_pinned(xe, &xe->pinned.early.kernel_bo_present, &xe->pinned.early.kernel_bo_present, xe_bo_notifier_prepare_pinned); if (!ret) ret = xe_bo_apply_to_pinned(xe, &xe->pinned.late.kernel_bo_present, &xe->pinned.late.kernel_bo_present, xe_bo_notifier_prepare_pinned); return ret; } /** * xe_bo_notifier_unprepare_all_pinned() - Remove the backing pages for all * pinned VRAM objects which have been restored. * @xe: xe device * * Should be called from PM notifier after exiting s3/s4 (either on success or * failure). */ void xe_bo_notifier_unprepare_all_pinned(struct xe_device *xe) { (void)xe_bo_apply_to_pinned(xe, &xe->pinned.early.kernel_bo_present, &xe->pinned.early.kernel_bo_present, xe_bo_notifier_unprepare_pinned); (void)xe_bo_apply_to_pinned(xe, &xe->pinned.late.kernel_bo_present, &xe->pinned.late.kernel_bo_present, xe_bo_notifier_unprepare_pinned); } /** * xe_bo_evict_all_user - evict all non-pinned user BOs from VRAM * @xe: xe device * * Evict non-pinned user BOs (via GPU). * * Evict == move VRAM BOs to temporary (typically system) memory. */ int xe_bo_evict_all_user(struct xe_device *xe) { struct ttm_device *bdev = &xe->ttm; u32 mem_type; int ret; /* User memory */ for (mem_type = XE_PL_TT; mem_type <= XE_PL_VRAM1; ++mem_type) { struct ttm_resource_manager *man = ttm_manager_type(bdev, mem_type); /* * On igpu platforms with flat CCS we need to ensure we save and restore any CCS * state since this state lives inside graphics stolen memory which doesn't survive * hibernation. * * This can be further improved by only evicting objects that we know have actually * used a compression enabled PAT index. */ if (mem_type == XE_PL_TT && (IS_DGFX(xe) || !xe_device_has_flat_ccs(xe))) continue; if (man) { ret = ttm_resource_manager_evict_all(bdev, man); if (ret) return ret; } } return 0; } /** * xe_bo_evict_all - evict all BOs from VRAM * @xe: xe device * * Evict non-pinned user BOs first (via GPU), evict pinned external BOs next * (via GPU), wait for evictions, and finally evict pinned kernel BOs via CPU. * All eviction magic done via TTM calls. * * Evict == move VRAM BOs to temporary (typically system) memory. * * This function should be called before the device goes into a suspend state * where the VRAM loses power. */ int xe_bo_evict_all(struct xe_device *xe) { struct xe_tile *tile; u8 id; int ret; ret = xe_bo_evict_all_user(xe); if (ret) return ret; ret = xe_bo_apply_to_pinned(xe, &xe->pinned.late.kernel_bo_present, &xe->pinned.late.evicted, xe_bo_evict_pinned); if (!ret) ret = xe_bo_apply_to_pinned(xe, &xe->pinned.late.kernel_bo_present, &xe->pinned.late.evicted, xe_bo_evict_pinned); /* * Wait for all user BO to be evicted as those evictions depend on the * memory moved below. */ for_each_tile(tile, xe, id) xe_tile_migrate_wait(tile); if (ret) return ret; return xe_bo_apply_to_pinned(xe, &xe->pinned.early.kernel_bo_present, &xe->pinned.early.evicted, xe_bo_evict_pinned); } static int xe_bo_restore_and_map_ggtt(struct xe_bo *bo) { struct xe_device *xe = xe_bo_device(bo); int ret; ret = xe_bo_restore_pinned(bo); if (ret) return ret; if (bo->flags & XE_BO_FLAG_GGTT) { struct xe_tile *tile; u8 id; for_each_tile(tile, xe_bo_device(bo), id) { if (tile != bo->tile && !(bo->flags & XE_BO_FLAG_GGTTx(tile))) continue; mutex_lock(&tile->mem.ggtt->lock); xe_ggtt_map_bo(tile->mem.ggtt, bo); mutex_unlock(&tile->mem.ggtt->lock); } } /* * We expect validate to trigger a move VRAM and our move code * should setup the iosys map. */ xe_assert(xe, !(bo->flags & XE_BO_FLAG_PINNED_LATE_RESTORE) || !iosys_map_is_null(&bo->vmap)); return 0; } /** * xe_bo_restore_early - restore early phase kernel BOs to VRAM * * @xe: xe device * * Move kernel BOs from temporary (typically system) memory to VRAM via CPU. All * moves done via TTM calls. * * This function should be called early, before trying to init the GT, on device * resume. */ int xe_bo_restore_early(struct xe_device *xe) { return xe_bo_apply_to_pinned(xe, &xe->pinned.early.evicted, &xe->pinned.early.kernel_bo_present, xe_bo_restore_and_map_ggtt); } /** * xe_bo_restore_late - restore pinned late phase BOs * * @xe: xe device * * Move pinned user and kernel BOs which can use blitter from temporary * (typically system) memory to VRAM. All moves done via TTM calls. * * This function should be called late, after GT init, on device resume. */ int xe_bo_restore_late(struct xe_device *xe) { struct xe_tile *tile; int ret, id; ret = xe_bo_apply_to_pinned(xe, &xe->pinned.late.evicted, &xe->pinned.late.kernel_bo_present, xe_bo_restore_and_map_ggtt); for_each_tile(tile, xe, id) xe_tile_migrate_wait(tile); if (ret) return ret; if (!IS_DGFX(xe)) return 0; /* Pinned user memory in VRAM should be validated on resume */ ret = xe_bo_apply_to_pinned(xe, &xe->pinned.late.external, &xe->pinned.late.external, xe_bo_restore_pinned); /* Wait for restore to complete */ for_each_tile(tile, xe, id) xe_tile_migrate_wait(tile); return ret; } static void xe_bo_pci_dev_remove_pinned(struct xe_device *xe) { struct xe_tile *tile; unsigned int id; (void)xe_bo_apply_to_pinned(xe, &xe->pinned.late.external, &xe->pinned.late.external, xe_bo_dma_unmap_pinned); for_each_tile(tile, xe, id) xe_tile_migrate_wait(tile); } /** * xe_bo_pci_dev_remove_all() - Handle bos when the pci_device is about to be removed * @xe: The xe device. * * On pci_device removal we need to drop all dma mappings and move * the data of exported bos out to system. This includes SVM bos and * exported dma-buf bos. This is done by evicting all bos, but * the evict placement in xe_evict_flags() is chosen such that all * bos except those mentioned are purged, and thus their memory * is released. * * For pinned bos, we're unmapping dma. */ void xe_bo_pci_dev_remove_all(struct xe_device *xe) { unsigned int mem_type; /* * Move pagemap bos and exported dma-buf to system, and * purge everything else. */ for (mem_type = XE_PL_VRAM1; mem_type >= XE_PL_TT; --mem_type) { struct ttm_resource_manager *man = ttm_manager_type(&xe->ttm, mem_type); if (man) { int ret = ttm_resource_manager_evict_all(&xe->ttm, man); drm_WARN_ON(&xe->drm, ret); } } xe_bo_pci_dev_remove_pinned(xe); } static void xe_bo_pinned_fini(void *arg) { struct xe_device *xe = arg; (void)xe_bo_apply_to_pinned(xe, &xe->pinned.late.kernel_bo_present, &xe->pinned.late.kernel_bo_present, xe_bo_dma_unmap_pinned); (void)xe_bo_apply_to_pinned(xe, &xe->pinned.early.kernel_bo_present, &xe->pinned.early.kernel_bo_present, xe_bo_dma_unmap_pinned); } /** * xe_bo_pinned_init() - Initialize pinned bo tracking * @xe: The xe device. * * Initializes the lists and locks required for pinned bo * tracking and registers a callback to dma-unmap * any remaining pinned bos on pci device removal. * * Return: %0 on success, negative error code on error. */ int xe_bo_pinned_init(struct xe_device *xe) { spin_lock_init(&xe->pinned.lock); INIT_LIST_HEAD(&xe->pinned.early.kernel_bo_present); INIT_LIST_HEAD(&xe->pinned.early.evicted); INIT_LIST_HEAD(&xe->pinned.late.kernel_bo_present); INIT_LIST_HEAD(&xe->pinned.late.evicted); INIT_LIST_HEAD(&xe->pinned.late.external); return devm_add_action_or_reset(xe->drm.dev, xe_bo_pinned_fini, xe); }