1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343
|
// 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.external,
&xe->pinned.late.external, 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)
{
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;
xe_ggtt_map_bo_unlocked(tile->mem.ggtt, bo);
}
}
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);
}
|