Commit c56ce956 authored by Thomas Hellström's avatar Thomas Hellström

drm/i915 Implement LMEM backup and restore for suspend / resume

Just evict unpinned objects to system. For pinned LMEM objects,
make a backup system object and blit the contents to that.

Backup is performed in three steps,
1: Opportunistically evict evictable objects using the gpu blitter.
2: After gt idle, evict evictable objects using the gpu blitter. This will
be modified in an upcoming patch to backup pinned objects that are not used
by the blitter itself.
3: Backup remaining pinned objects using memcpy.

Also move uC suspend to after 2) to make sure we have a functional GuC
during 2) if using GuC submission.

v2:
- Major refactor to make sure gem_exec_suspend@hang-SX subtests work, and
  suspend / resume works with a slightly modified GuC submission enabling
  patch series.

v3:
- Fix a potential use-after-free (Matthew Auld)
- Use i915_gem_object_create_shmem() instead of
  i915_gem_object_create_region (Matthew Auld)
- Minor simplifications (Matthew Auld)
- Fix up kerneldoc for i195_ttm_restore_region().
- Final lmem_suspend() call moved to i915_gem_backup_suspend from
  i915_gem_suspend_late, since the latter gets called at driver unload
  and we don't unnecessarily want to run it at that time.

v4:
- Interface change of ttm- & lmem suspend / resume functions to use
  flags rather than bools. (Matthew Auld)
- Completely drop the i915_gem_backup_suspend change (Matthew Auld)
Signed-off-by: default avatarThomas Hellström <thomas.hellstrom@linux.intel.com>
Reviewed-by: default avatarMatthew Auld <matthew.auld@intel.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20210922062527.865433-5-thomas.hellstrom@linux.intel.com
parent 81387fc4
...@@ -154,6 +154,7 @@ gem-y += \ ...@@ -154,6 +154,7 @@ gem-y += \
gem/i915_gem_throttle.o \ gem/i915_gem_throttle.o \
gem/i915_gem_tiling.o \ gem/i915_gem_tiling.o \
gem/i915_gem_ttm.o \ gem/i915_gem_ttm.o \
gem/i915_gem_ttm_pm.o \
gem/i915_gem_userptr.o \ gem/i915_gem_userptr.o \
gem/i915_gem_wait.o \ gem/i915_gem_wait.o \
gem/i915_gemfs.o gem/i915_gemfs.o
......
...@@ -534,6 +534,7 @@ struct drm_i915_gem_object { ...@@ -534,6 +534,7 @@ struct drm_i915_gem_object {
struct { struct {
struct sg_table *cached_io_st; struct sg_table *cached_io_st;
struct i915_gem_object_page_iter get_io_page; struct i915_gem_object_page_iter get_io_page;
struct drm_i915_gem_object *backup;
bool created:1; bool created:1;
} ttm; } ttm;
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
*/ */
#include "gem/i915_gem_pm.h" #include "gem/i915_gem_pm.h"
#include "gem/i915_gem_ttm_pm.h"
#include "gt/intel_gt.h" #include "gt/intel_gt.h"
#include "gt/intel_gt_pm.h" #include "gt/intel_gt_pm.h"
#include "gt/intel_gt_requests.h" #include "gt/intel_gt_requests.h"
...@@ -39,6 +40,84 @@ void i915_gem_suspend(struct drm_i915_private *i915) ...@@ -39,6 +40,84 @@ void i915_gem_suspend(struct drm_i915_private *i915)
i915_gem_drain_freed_objects(i915); i915_gem_drain_freed_objects(i915);
} }
static int lmem_restore(struct drm_i915_private *i915, u32 flags)
{
struct intel_memory_region *mr;
int ret = 0, id;
for_each_memory_region(mr, i915, id) {
if (mr->type == INTEL_MEMORY_LOCAL) {
ret = i915_ttm_restore_region(mr, flags);
if (ret)
break;
}
}
return ret;
}
static int lmem_suspend(struct drm_i915_private *i915, u32 flags)
{
struct intel_memory_region *mr;
int ret = 0, id;
for_each_memory_region(mr, i915, id) {
if (mr->type == INTEL_MEMORY_LOCAL) {
ret = i915_ttm_backup_region(mr, flags);
if (ret)
break;
}
}
return ret;
}
static void lmem_recover(struct drm_i915_private *i915)
{
struct intel_memory_region *mr;
int id;
for_each_memory_region(mr, i915, id)
if (mr->type == INTEL_MEMORY_LOCAL)
i915_ttm_recover_region(mr);
}
int i915_gem_backup_suspend(struct drm_i915_private *i915)
{
int ret;
/* Opportunistically try to evict unpinned objects */
ret = lmem_suspend(i915, I915_TTM_BACKUP_ALLOW_GPU);
if (ret)
goto out_recover;
i915_gem_suspend(i915);
/*
* More objects may have become unpinned as requests were
* retired. Now try to evict again. The gt may be wedged here
* in which case we automatically fall back to memcpy.
*/
ret = lmem_suspend(i915, I915_TTM_BACKUP_ALLOW_GPU);
if (ret)
goto out_recover;
/*
* Remaining objects are backed up using memcpy once we've stopped
* using the migrate context.
*/
ret = lmem_suspend(i915, I915_TTM_BACKUP_PINNED);
if (ret)
goto out_recover;
return 0;
out_recover:
lmem_recover(i915);
return ret;
}
void i915_gem_suspend_late(struct drm_i915_private *i915) void i915_gem_suspend_late(struct drm_i915_private *i915)
{ {
struct drm_i915_gem_object *obj; struct drm_i915_gem_object *obj;
...@@ -128,12 +207,20 @@ int i915_gem_freeze_late(struct drm_i915_private *i915) ...@@ -128,12 +207,20 @@ int i915_gem_freeze_late(struct drm_i915_private *i915)
void i915_gem_resume(struct drm_i915_private *i915) void i915_gem_resume(struct drm_i915_private *i915)
{ {
int ret;
GEM_TRACE("%s\n", dev_name(i915->drm.dev)); GEM_TRACE("%s\n", dev_name(i915->drm.dev));
ret = lmem_restore(i915, 0);
GEM_WARN_ON(ret);
/* /*
* As we didn't flush the kernel context before suspend, we cannot * As we didn't flush the kernel context before suspend, we cannot
* guarantee that the context image is complete. So let's just reset * guarantee that the context image is complete. So let's just reset
* it and start again. * it and start again.
*/ */
intel_gt_resume(&i915->gt); intel_gt_resume(&i915->gt);
ret = lmem_restore(i915, I915_TTM_BACKUP_ALLOW_GPU);
GEM_WARN_ON(ret);
} }
...@@ -18,6 +18,7 @@ void i915_gem_idle_work_handler(struct work_struct *work); ...@@ -18,6 +18,7 @@ void i915_gem_idle_work_handler(struct work_struct *work);
void i915_gem_suspend(struct drm_i915_private *i915); void i915_gem_suspend(struct drm_i915_private *i915);
void i915_gem_suspend_late(struct drm_i915_private *i915); void i915_gem_suspend_late(struct drm_i915_private *i915);
int i915_gem_backup_suspend(struct drm_i915_private *i915);
int i915_gem_freeze(struct drm_i915_private *i915); int i915_gem_freeze(struct drm_i915_private *i915);
int i915_gem_freeze_late(struct drm_i915_private *i915); int i915_gem_freeze_late(struct drm_i915_private *i915);
......
...@@ -10,18 +10,16 @@ ...@@ -10,18 +10,16 @@
#include "intel_memory_region.h" #include "intel_memory_region.h"
#include "intel_region_ttm.h" #include "intel_region_ttm.h"
#include "gem/i915_gem_mman.h"
#include "gem/i915_gem_object.h" #include "gem/i915_gem_object.h"
#include "gem/i915_gem_region.h" #include "gem/i915_gem_region.h"
#include "gem/i915_gem_ttm.h" #include "gem/i915_gem_ttm.h"
#include "gem/i915_gem_mman.h" #include "gem/i915_gem_ttm_pm.h"
#include "gt/intel_migrate.h"
#include "gt/intel_engine_pm.h"
#define I915_PL_LMEM0 TTM_PL_PRIV #include "gt/intel_engine_pm.h"
#define I915_PL_SYSTEM TTM_PL_SYSTEM #include "gt/intel_gt.h"
#define I915_PL_STOLEN TTM_PL_VRAM #include "gt/intel_migrate.h"
#define I915_PL_GGTT TTM_PL_TT
#define I915_TTM_PRIO_PURGE 0 #define I915_TTM_PRIO_PURGE 0
#define I915_TTM_PRIO_NO_PAGES 1 #define I915_TTM_PRIO_NO_PAGES 1
...@@ -64,6 +62,20 @@ static struct ttm_placement i915_sys_placement = { ...@@ -64,6 +62,20 @@ static struct ttm_placement i915_sys_placement = {
.busy_placement = &sys_placement_flags, .busy_placement = &sys_placement_flags,
}; };
/**
* i915_ttm_sys_placement - Return the struct ttm_placement to be
* used for an object in system memory.
*
* Rather than making the struct extern, use this
* function.
*
* Return: A pointer to a static variable for sys placement.
*/
struct ttm_placement *i915_ttm_sys_placement(void)
{
return &i915_sys_placement;
}
static int i915_ttm_err_to_gem(int err) static int i915_ttm_err_to_gem(int err)
{ {
/* Fastpath */ /* Fastpath */
...@@ -443,7 +455,7 @@ static int i915_ttm_accel_move(struct ttm_buffer_object *bo, ...@@ -443,7 +455,7 @@ static int i915_ttm_accel_move(struct ttm_buffer_object *bo,
enum i915_cache_level src_level, dst_level; enum i915_cache_level src_level, dst_level;
int ret; int ret;
if (!i915->gt.migrate.context) if (!i915->gt.migrate.context || intel_gt_is_wedged(&i915->gt))
return -EINVAL; return -EINVAL;
dst_level = i915_ttm_cache_level(i915, dst_mem, dst_ttm); dst_level = i915_ttm_cache_level(i915, dst_mem, dst_ttm);
...@@ -887,6 +899,8 @@ void i915_ttm_bo_destroy(struct ttm_buffer_object *bo) ...@@ -887,6 +899,8 @@ void i915_ttm_bo_destroy(struct ttm_buffer_object *bo)
{ {
struct drm_i915_gem_object *obj = i915_ttm_to_gem(bo); struct drm_i915_gem_object *obj = i915_ttm_to_gem(bo);
i915_ttm_backup_free(obj);
/* This releases all gem object bindings to the backend. */ /* This releases all gem object bindings to the backend. */
__i915_gem_free_object(obj); __i915_gem_free_object(obj);
......
...@@ -50,4 +50,14 @@ int __i915_gem_ttm_object_init(struct intel_memory_region *mem, ...@@ -50,4 +50,14 @@ int __i915_gem_ttm_object_init(struct intel_memory_region *mem,
int i915_gem_obj_copy_ttm(struct drm_i915_gem_object *dst, int i915_gem_obj_copy_ttm(struct drm_i915_gem_object *dst,
struct drm_i915_gem_object *src, struct drm_i915_gem_object *src,
bool allow_accel, bool intr); bool allow_accel, bool intr);
/* Internal I915 TTM declarations and definitions below. */
#define I915_PL_LMEM0 TTM_PL_PRIV
#define I915_PL_SYSTEM TTM_PL_SYSTEM
#define I915_PL_STOLEN TTM_PL_VRAM
#define I915_PL_GGTT TTM_PL_TT
struct ttm_placement *i915_ttm_sys_placement(void);
#endif #endif
// SPDX-License-Identifier: MIT
/*
* Copyright © 2021 Intel Corporation
*/
#include <drm/ttm/ttm_placement.h>
#include <drm/ttm/ttm_tt.h>
#include "i915_drv.h"
#include "intel_memory_region.h"
#include "intel_region_ttm.h"
#include "gem/i915_gem_region.h"
#include "gem/i915_gem_ttm.h"
#include "gem/i915_gem_ttm_pm.h"
/**
* i915_ttm_backup_free - Free any backup attached to this object
* @obj: The object whose backup is to be freed.
*/
void i915_ttm_backup_free(struct drm_i915_gem_object *obj)
{
if (obj->ttm.backup) {
i915_gem_object_put(obj->ttm.backup);
obj->ttm.backup = NULL;
}
}
/**
* struct i915_gem_ttm_pm_apply - Apply-to-region subclass for restore
* @base: The i915_gem_apply_to_region we derive from.
* @allow_gpu: Whether using the gpu blitter is allowed.
* @backup_pinned: On backup, backup also pinned objects.
*/
struct i915_gem_ttm_pm_apply {
struct i915_gem_apply_to_region base;
bool allow_gpu : 1;
bool backup_pinned : 1;
};
static int i915_ttm_backup(struct i915_gem_apply_to_region *apply,
struct drm_i915_gem_object *obj)
{
struct i915_gem_ttm_pm_apply *pm_apply =
container_of(apply, typeof(*pm_apply), base);
struct ttm_buffer_object *bo = i915_gem_to_ttm(obj);
struct ttm_buffer_object *backup_bo;
struct drm_i915_private *i915 =
container_of(bo->bdev, typeof(*i915), bdev);
struct drm_i915_gem_object *backup;
struct ttm_operation_ctx ctx = {};
int err = 0;
if (bo->resource->mem_type == I915_PL_SYSTEM || obj->ttm.backup)
return 0;
if (pm_apply->allow_gpu && i915_gem_object_evictable(obj))
return ttm_bo_validate(bo, i915_ttm_sys_placement(), &ctx);
if (!pm_apply->backup_pinned)
return 0;
backup = i915_gem_object_create_shmem(i915, obj->base.size);
if (IS_ERR(backup))
return PTR_ERR(backup);
err = i915_gem_object_lock(backup, apply->ww);
if (err)
goto out_no_lock;
backup_bo = i915_gem_to_ttm(backup);
err = ttm_tt_populate(backup_bo->bdev, backup_bo->ttm, &ctx);
if (err)
goto out_no_populate;
err = i915_gem_obj_copy_ttm(backup, obj, pm_apply->allow_gpu, false);
GEM_WARN_ON(err);
obj->ttm.backup = backup;
return 0;
out_no_populate:
i915_gem_ww_unlock_single(backup);
out_no_lock:
i915_gem_object_put(backup);
return err;
}
static int i915_ttm_recover(struct i915_gem_apply_to_region *apply,
struct drm_i915_gem_object *obj)
{
i915_ttm_backup_free(obj);
return 0;
}
/**
* i915_ttm_recover_region - Free the backup of all objects of a region
* @mr: The memory region
*
* Checks all objects of a region if there is backup attached and if so
* frees that backup. Typically this is called to recover after a partially
* performed backup.
*/
void i915_ttm_recover_region(struct intel_memory_region *mr)
{
static const struct i915_gem_apply_to_region_ops recover_ops = {
.process_obj = i915_ttm_recover,
};
struct i915_gem_apply_to_region apply = {.ops = &recover_ops};
int ret;
ret = i915_gem_process_region(mr, &apply);
GEM_WARN_ON(ret);
}
/**
* i915_ttm_backup_region - Back up all objects of a region to smem.
* @mr: The memory region
* @allow_gpu: Whether to allow the gpu blitter for this backup.
* @backup_pinned: Backup also pinned objects.
*
* Loops over all objects of a region and either evicts them if they are
* evictable or backs them up using a backup object if they are pinned.
*
* Return: Zero on success. Negative error code on error.
*/
int i915_ttm_backup_region(struct intel_memory_region *mr, u32 flags)
{
static const struct i915_gem_apply_to_region_ops backup_ops = {
.process_obj = i915_ttm_backup,
};
struct i915_gem_ttm_pm_apply pm_apply = {
.base = {.ops = &backup_ops},
.allow_gpu = flags & I915_TTM_BACKUP_ALLOW_GPU,
.backup_pinned = flags & I915_TTM_BACKUP_PINNED,
};
return i915_gem_process_region(mr, &pm_apply.base);
}
static int i915_ttm_restore(struct i915_gem_apply_to_region *apply,
struct drm_i915_gem_object *obj)
{
struct i915_gem_ttm_pm_apply *pm_apply =
container_of(apply, typeof(*pm_apply), base);
struct drm_i915_gem_object *backup = obj->ttm.backup;
struct ttm_buffer_object *backup_bo = i915_gem_to_ttm(backup);
struct ttm_operation_ctx ctx = {};
int err;
if (!backup)
return 0;
if (!pm_apply->allow_gpu && (obj->flags & I915_BO_ALLOC_USER))
return 0;
err = i915_gem_object_lock(backup, apply->ww);
if (err)
return err;
/* Content may have been swapped. */
err = ttm_tt_populate(backup_bo->bdev, backup_bo->ttm, &ctx);
if (!err) {
err = i915_gem_obj_copy_ttm(obj, backup, pm_apply->allow_gpu,
false);
GEM_WARN_ON(err);
obj->ttm.backup = NULL;
err = 0;
}
i915_gem_ww_unlock_single(backup);
if (!err)
i915_gem_object_put(backup);
return err;
}
/**
* i915_ttm_restore_region - Restore backed-up objects of a region from smem.
* @mr: The memory region
* @allow_gpu: Whether to allow the gpu blitter to recover.
*
* Loops over all objects of a region and if they are backed-up, restores
* them from smem.
*
* Return: Zero on success. Negative error code on error.
*/
int i915_ttm_restore_region(struct intel_memory_region *mr, u32 flags)
{
static const struct i915_gem_apply_to_region_ops restore_ops = {
.process_obj = i915_ttm_restore,
};
struct i915_gem_ttm_pm_apply pm_apply = {
.base = {.ops = &restore_ops},
.allow_gpu = flags & I915_TTM_BACKUP_ALLOW_GPU,
};
return i915_gem_process_region(mr, &pm_apply.base);
}
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2021 Intel Corporation
*/
#ifndef _I915_GEM_TTM_PM_H_
#define _I915_GEM_TTM_PM_H_
#include <linux/types.h>
struct intel_memory_region;
struct drm_i915_gem_object;
#define I915_TTM_BACKUP_ALLOW_GPU BIT(0)
#define I915_TTM_BACKUP_PINNED BIT(1)
int i915_ttm_backup_region(struct intel_memory_region *mr, u32 flags);
void i915_ttm_recover_region(struct intel_memory_region *mr);
int i915_ttm_restore_region(struct intel_memory_region *mr, u32 flags);
/* Internal I915 TTM functions below. */
void i915_ttm_backup_free(struct drm_i915_gem_object *obj);
#endif
...@@ -297,8 +297,6 @@ void intel_gt_suspend_prepare(struct intel_gt *gt) ...@@ -297,8 +297,6 @@ void intel_gt_suspend_prepare(struct intel_gt *gt)
{ {
user_forcewake(gt, true); user_forcewake(gt, true);
wait_for_suspend(gt); wait_for_suspend(gt);
intel_uc_suspend(&gt->uc);
} }
static suspend_state_t pm_suspend_target(void) static suspend_state_t pm_suspend_target(void)
...@@ -322,6 +320,8 @@ void intel_gt_suspend_late(struct intel_gt *gt) ...@@ -322,6 +320,8 @@ void intel_gt_suspend_late(struct intel_gt *gt)
GEM_BUG_ON(gt->awake); GEM_BUG_ON(gt->awake);
intel_uc_suspend(&gt->uc);
/* /*
* On disabling the device, we want to turn off HW access to memory * On disabling the device, we want to turn off HW access to memory
* that we no longer own. * that we no longer own.
......
...@@ -1094,9 +1094,7 @@ static int i915_drm_prepare(struct drm_device *dev) ...@@ -1094,9 +1094,7 @@ static int i915_drm_prepare(struct drm_device *dev)
* split out that work and pull it forward so that after point, * split out that work and pull it forward so that after point,
* the GPU is not woken again. * the GPU is not woken again.
*/ */
i915_gem_suspend(i915); return i915_gem_backup_suspend(i915);
return 0;
} }
static int i915_drm_suspend(struct drm_device *dev) static int i915_drm_suspend(struct drm_device *dev)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment