#include <linux/dma-fence-array.h>
#include <linux/kthread.h>
#include <linux/dma-resv.h>
#include <linux/shmem_fs.h>
#include <linux/slab.h>
#include <linux/stop_machine.h>
#include <linux/swap.h>
#include <linux/pci.h>
#include <linux/dma-buf.h>
#include <linux/mman.h>
#include <drm/drm_cache.h>
#include <drm/drm_vma_manager.h>
#include "display/intel_display.h"
#include "display/intel_frontbuffer.h"
#include "gem/i915_gem_clflush.h"
#include "gem/i915_gem_context.h"
#include "gem/i915_gem_ioctls.h"
#include "gem/i915_gem_mman.h"
#include "gem/i915_gem_pm.h"
#include "gem/i915_gem_region.h"
#include "gem/i915_gem_userptr.h"
#include "gt/intel_engine_user.h"
#include "gt/intel_gt.h"
#include "gt/intel_gt_pm.h"
#include "gt/intel_workarounds.h"
#include "i915_drv.h"
#include "i915_file_private.h"
#include "i915_trace.h"
#include "i915_vgpu.h"
#include "intel_clock_gating.h"
static int
insert_mappable_node(struct i915_ggtt *ggtt, struct drm_mm_node *node, u32 size)
{
int err;
err = mutex_lock_interruptible(&ggtt->vm.mutex);
if (err)
return err;
memset(node, 0, sizeof(*node));
err = drm_mm_insert_node_in_range(&ggtt->vm.mm, node,
size, 0, I915_COLOR_UNEVICTABLE,
0, ggtt->mappable_end,
DRM_MM_INSERT_LOW);
mutex_unlock(&ggtt->vm.mutex);
return err;
}
static void
remove_mappable_node(struct i915_ggtt *ggtt, struct drm_mm_node *node)
{
mutex_lock(&ggtt->vm.mutex);
drm_mm_remove_node(node);
mutex_unlock(&ggtt->vm.mutex);
}
int
i915_gem_get_aperture_ioctl(struct drm_device *dev, void *data,
struct drm_file *file)
{
struct drm_i915_private *i915 = to_i915(dev);
struct i915_ggtt *ggtt = to_gt(i915)->ggtt;
struct drm_i915_gem_get_aperture *args = data;
struct i915_vma *vma;
u64 pinned;
if (mutex_lock_interruptible(&ggtt->vm.mutex))
return -EINTR;
pinned = ggtt->vm.reserved;
list_for_each_entry(vma, &ggtt->vm.bound_list, vm_link)
if (i915_vma_is_pinned(vma))
pinned += vma->node.size;
mutex_unlock(&ggtt->vm.mutex);
args->aper_size = ggtt->vm.total;
args->aper_available_size = args->aper_size - pinned;
return 0;
}
int i915_gem_object_unbind(struct drm_i915_gem_object *obj,
unsigned long flags)
{
struct intel_runtime_pm *rpm = &to_i915(obj->base.dev)->runtime_pm;
bool vm_trylock = !!(flags & I915_GEM_OBJECT_UNBIND_VM_TRYLOCK);
LIST_HEAD(still_in_list);
intel_wakeref_t wakeref;
struct i915_vma *vma;
int ret;
assert_object_held(obj);
if (list_empty(&obj->vma.list))
return 0;
wakeref = intel_runtime_pm_get(rpm);
try_again:
ret = 0;
spin_lock(&obj->vma.lock);
while (!ret && (vma = list_first_entry_or_null(&obj->vma.list,
struct i915_vma,
obj_link))) {
list_move_tail(&vma->obj_link, &still_in_list);
if (!i915_vma_is_bound(vma, I915_VMA_BIND_MASK))
continue;
if (flags & I915_GEM_OBJECT_UNBIND_TEST) {
ret = -EBUSY;
break;
}
ret = -EAGAIN;
if (!i915_vm_tryget(vma->vm))
break;
spin_unlock(&obj->vma.lock);
ret = -EBUSY;
if (flags & I915_GEM_OBJECT_UNBIND_ASYNC) {
assert_object_held(vma->obj);
ret = i915_vma_unbind_async(vma, vm_trylock);
}
if (ret == -EBUSY && (flags & I915_GEM_OBJECT_UNBIND_ACTIVE ||
!i915_vma_is_active(vma))) {
if (vm_trylock) {
if (mutex_trylock(&vma->vm->mutex)) {
ret = __i915_vma_unbind(vma);
mutex_unlock(&vma->vm->mutex);
}
} else {
ret = i915_vma_unbind(vma);
}
}
i915_vm_put(vma->vm);
spin_lock(&obj->vma.lock);
}
list_splice_init(&still_in_list, &obj->vma.list);
spin_unlock(&obj->vma.lock);
if (ret == -EAGAIN && flags & I915_GEM_OBJECT_UNBIND_BARRIER) {
rcu_barrier();
goto try_again;
}
intel_runtime_pm_put(rpm, wakeref);
return ret;
}
static int
shmem_pread(struct page *page, int offset, int len, char __user *user_data,
bool needs_clflush)
{
char *vaddr;
int ret;
vaddr = kmap(page);
if (needs_clflush)
drm_clflush_virt_range(vaddr + offset, len);
ret = __copy_to_user(user_data, vaddr + offset, len);
kunmap(page);
return ret ? -EFAULT : 0;
}
static int
i915_gem_shmem_pread(struct drm_i915_gem_object *obj,
struct drm_i915_gem_pread *args)
{
unsigned int needs_clflush;
char __user *user_data;
unsigned long offset;
pgoff_t idx;
u64 remain;
int ret;
ret = i915_gem_object_lock_interruptible(obj, NULL);
if (ret)
return ret;
ret = i915_gem_object_pin_pages(obj);
if (ret)
goto err_unlock;
ret = i915_gem_object_prepare_read(obj, &needs_clflush);
if (ret)
goto err_unpin;
i915_gem_object_finish_access(obj);
i915_gem_object_unlock(obj);
remain = args->size;
user_data = u64_to_user_ptr(args->data_ptr);
offset = offset_in_page(args->offset);
for (idx = args->offset >> PAGE_SHIFT; remain; idx++) {
struct page *page = i915_gem_object_get_page(obj, idx);
unsigned int length = min_t(u64, remain, PAGE_SIZE - offset);
ret = shmem_pread(page, offset, length, user_data,
needs_clflush);
if (ret)
break;
remain -= length;
user_data += length;
offset = 0;
}
i915_gem_object_unpin_pages(obj);
return ret;
err_unpin:
i915_gem_object_unpin_pages(obj);
err_unlock:
i915_gem_object_unlock(obj);
return ret;
}
static inline bool
gtt_user_read(struct io_mapping *mapping,
loff_t base, int offset,
char __user *user_data, int length)
{
void __iomem *vaddr;
unsigned long unwritten;
vaddr = io_mapping_map_atomic_wc(mapping, base);
unwritten = __copy_to_user_inatomic(user_data,
(void __force *)vaddr + offset,
length);
io_mapping_unmap_atomic(vaddr);
if (unwritten) {
vaddr = io_mapping_map_wc(mapping, base, PAGE_SIZE);
unwritten = copy_to_user(user_data,
(void __force *)vaddr + offset,
length);
io_mapping_unmap(vaddr);
}
return unwritten;
}
static struct i915_vma *i915_gem_gtt_prepare(struct drm_i915_gem_object *obj,
struct drm_mm_node *node,
bool write)
{
struct drm_i915_private *i915 = to_i915(obj->base.dev);
struct i915_ggtt *ggtt = to_gt(i915)->ggtt;
struct i915_vma *vma;
struct i915_gem_ww_ctx ww;
int ret;
i915_gem_ww_ctx_init(&ww, true);
retry:
vma = ERR_PTR(-ENODEV);
ret = i915_gem_object_lock(obj, &ww);
if (ret)
goto err_ww;
ret = i915_gem_object_set_to_gtt_domain(obj, write);
if (ret)
goto err_ww;
if (!i915_gem_object_is_tiled(obj))
vma = i915_gem_object_ggtt_pin_ww(obj, &ww, NULL, 0, 0,
PIN_MAPPABLE |
PIN_NONBLOCK |
PIN_NOEVICT);
if (vma == ERR_PTR(-EDEADLK)) {
ret = -EDEADLK;
goto err_ww;
} else if (!IS_ERR(vma)) {
node->start = i915_ggtt_offset(vma);
node->flags = 0;
} else {
ret = insert_mappable_node(ggtt, node, PAGE_SIZE);
if (ret)
goto err_ww;
GEM_BUG_ON(!drm_mm_node_allocated(node));
vma = NULL;
}
ret = i915_gem_object_pin_pages(obj);
if (ret) {
if (drm_mm_node_allocated(node)) {
ggtt->vm.clear_range(&ggtt->vm, node->start, node->size);
remove_mappable_node(ggtt, node);
} else {
i915_vma_unpin(vma);
}
}
err_ww:
if (ret == -EDEADLK) {
ret = i915_gem_ww_ctx_backoff(&ww);
if (!ret)
goto retry;
}
i915_gem_ww_ctx_fini(&ww);
return ret ? ERR_PTR(ret) : vma;
}
static void i915_gem_gtt_cleanup(struct drm_i915_gem_object *obj,
struct drm_mm_node *node,
struct i915_vma *vma)
{
struct drm_i915_private *i915 = to_i915(obj->base.dev);
struct i915_ggtt *ggtt = to_gt(i915)->ggtt;
i915_gem_object_unpin_pages(obj);
if (drm_mm_node_allocated(node)) {
ggtt->vm.clear_range(&ggtt->vm, node->start, node->size);
remove_mappable_node(ggtt, node);
} else {
i915_vma_unpin(vma);
}
}
static int
i915_gem_gtt_pread(struct drm_i915_gem_object *obj,
const struct drm_i915_gem_pread *args)
{
struct drm_i915_private *i915 = to_i915(obj->base.dev);
struct i915_ggtt *ggtt = to_gt(i915)->ggtt;
unsigned long remain, offset;
intel_wakeref_t wakeref;
struct drm_mm_node node;
void __user *user_data;
struct i915_vma *vma;
int ret = 0;
if (overflows_type(args->size, remain) ||
overflows_type(args->offset, offset))
return -EINVAL;
wakeref = intel_runtime_pm_get(&i915->runtime_pm);
vma = i915_gem_gtt_prepare(obj, &node, false);
if (IS_ERR(vma)) {
ret = PTR_ERR(vma);
goto out_rpm;
}
user_data = u64_to_user_ptr(args->data_ptr);
remain = args->size;
offset = args->offset;
while (remain > 0) {
u32 page_base = node.start;
unsigned page_offset = offset_in_page(offset);
unsigned page_length = PAGE_SIZE - page_offset;
page_length = remain < page_length ? remain : page_length;
if (drm_mm_node_allocated(&node)) {
ggtt->vm.insert_page(&ggtt->vm,
i915_gem_object_get_dma_address(obj,
offset >> PAGE_SHIFT),
node.start,
i915_gem_get_pat_index(i915,
I915_CACHE_NONE), 0);
} else {
page_base += offset & PAGE_MASK;
}
if (gtt_user_read(&ggtt->iomap, page_base, page_offset,
user_data, page_length)) {
ret = -EFAULT;
break;
}
remain -= page_length;
user_data += page_length;
offset += page_length;
}
i915_gem_gtt_cleanup(obj, &node, vma);
out_rpm:
intel_runtime_pm_put(&i915->runtime_pm, wakeref);
return ret;
}
int
i915_gem_pread_ioctl(struct drm_device *dev, void *data,
struct drm_file *file)
{
struct drm_i915_private *i915 = to_i915(dev);
struct drm_i915_gem_pread *args = data;
struct drm_i915_gem_object *obj;
int ret;
if (GRAPHICS_VER(i915) >= 12 && !IS_TIGERLAKE(i915))
return -EOPNOTSUPP;
if (args->size == 0)
return 0;
if (!access_ok(u64_to_user_ptr(args->data_ptr),
args->size))
return -EFAULT;
obj = i915_gem_object_lookup(file, args->handle);
if (!obj)
return -ENOENT;
if (range_overflows_t(u64, args->offset, args->size, obj->base.size)) {
ret = -EINVAL;
goto out;
}
trace_i915_gem_object_pread(obj, args->offset, args->size);
ret = -ENODEV;
if (obj->ops->pread)
ret = obj->ops->pread(obj, args);
if (ret != -ENODEV)
goto out;
ret = i915_gem_object_wait(obj,
I915_WAIT_INTERRUPTIBLE,
MAX_SCHEDULE_TIMEOUT);
if (ret)
goto out;
ret = i915_gem_shmem_pread(obj, args);
if (ret == -EFAULT || ret == -ENODEV)
ret = i915_gem_gtt_pread(obj, args);
out:
i915_gem_object_put(obj);
return ret;
}
static inline bool
ggtt_write(struct io_mapping *mapping,
loff_t base, int offset,
char __user *user_data, int length)
{
void __iomem *vaddr;
unsigned long unwritten;
vaddr = io_mapping_map_atomic_wc(mapping, base);
unwritten = __copy_from_user_inatomic_nocache((void __force *)vaddr + offset,
user_data, length);
io_mapping_unmap_atomic(vaddr);
if (unwritten) {
vaddr = io_mapping_map_wc(mapping, base, PAGE_SIZE);
unwritten = copy_from_user((void __force *)vaddr + offset,
user_data, length);
io_mapping_unmap(vaddr);
}
return unwritten;
}
static int
i915_gem_gtt_pwrite_fast(struct drm_i915_gem_object *obj,
const struct drm_i915_gem_pwrite *args)
{
struct drm_i915_private *i915 = to_i915(obj->base.dev);
struct i915_ggtt *ggtt = to_gt(i915)->ggtt;
struct intel_runtime_pm *rpm = &i915->runtime_pm;
unsigned long remain, offset;
intel_wakeref_t wakeref;
struct drm_mm_node node;
struct i915_vma *vma;
void __user *user_data;
int ret = 0;
if (overflows_type(args->size, remain) ||
overflows_type(args->offset, offset))
return -EINVAL;
if (i915_gem_object_has_struct_page(obj)) {
wakeref = intel_runtime_pm_get_if_in_use(rpm);
if (!wakeref)
return -EFAULT;
} else {
wakeref = intel_runtime_pm_get(rpm);
}
vma = i915_gem_gtt_prepare(obj, &node, true);
if (IS_ERR(vma)) {
ret = PTR_ERR(vma);
goto out_rpm;
}
i915_gem_object_invalidate_frontbuffer(obj, ORIGIN_CPU);
user_data = u64_to_user_ptr(args->data_ptr);
offset = args->offset;
remain = args->size;
while (remain) {
u32 page_base = node.start;
unsigned int page_offset = offset_in_page(offset);
unsigned int page_length = PAGE_SIZE - page_offset;
page_length = remain < page_length ? remain : page_length;
if (drm_mm_node_allocated(&node)) {
intel_gt_flush_ggtt_writes(ggtt->vm.gt);
ggtt->vm.insert_page(&ggtt->vm,
i915_gem_object_get_dma_address(obj,
offset >> PAGE_SHIFT),
node.start,
i915_gem_get_pat_index(i915,
I915_CACHE_NONE), 0);
wmb();
} else {
page_base += offset & PAGE_MASK;
}
if (ggtt_write(&ggtt->iomap, page_base, page_offset,
user_data, page_length)) {
ret = -EFAULT;
break;
}
remain -= page_length;
user_data += page_length;
offset += page_length;
}
intel_gt_flush_ggtt_writes(ggtt->vm.gt);
i915_gem_object_flush_frontbuffer(obj, ORIGIN_CPU);
i915_gem_gtt_cleanup(obj, &node, vma);
out_rpm:
intel_runtime_pm_put(rpm, wakeref);
return ret;
}
static int
shmem_pwrite(struct page *page, int offset, int len, char __user *user_data,
bool needs_clflush_before,
bool needs_clflush_after)
{
char *vaddr;
int ret;
vaddr = kmap(page);
if (needs_clflush_before)
drm_clflush_virt_range(vaddr + offset, len);
ret = __copy_from_user(vaddr + offset, user_data, len);
if (!ret && needs_clflush_after)
drm_clflush_virt_range(vaddr + offset, len);
kunmap(page);
return ret ? -EFAULT : 0;
}
static int
i915_gem_shmem_pwrite(struct drm_i915_gem_object *obj,
const struct drm_i915_gem_pwrite *args)
{
unsigned int partial_cacheline_write;
unsigned int needs_clflush;
void __user *user_data;
unsigned long offset;
pgoff_t idx;
u64 remain;
int ret;
ret = i915_gem_object_lock_interruptible(obj, NULL);
if (ret)
return ret;
ret = i915_gem_object_pin_pages(obj);
if (ret)
goto err_unlock;
ret = i915_gem_object_prepare_write(obj, &needs_clflush);
if (ret)
goto err_unpin;
i915_gem_object_finish_access(obj);
i915_gem_object_unlock(obj);
partial_cacheline_write = 0;
if (needs_clflush & CLFLUSH_BEFORE)
partial_cacheline_write = boot_cpu_data.x86_clflush_size - 1;
user_data = u64_to_user_ptr(args->data_ptr);
remain = args->size;
offset = offset_in_page(args->offset);
for (idx = args->offset >> PAGE_SHIFT; remain; idx++) {
struct page *page = i915_gem_object_get_page(obj, idx);
unsigned int length = min_t(u64, remain, PAGE_SIZE - offset);
ret = shmem_pwrite(page, offset, length, user_data,
(offset | length) & partial_cacheline_write,
needs_clflush & CLFLUSH_AFTER);
if (ret)
break;
remain -= length;
user_data += length;
offset = 0;
}
i915_gem_object_flush_frontbuffer(obj, ORIGIN_CPU);
i915_gem_object_unpin_pages(obj);
return ret;
err_unpin:
i915_gem_object_unpin_pages(obj);
err_unlock:
i915_gem_object_unlock(obj);
return ret;
}
int
i915_gem_pwrite_ioctl(struct drm_device *dev, void *data,
struct drm_file *file)
{
struct drm_i915_private *i915 = to_i915(dev);
struct drm_i915_gem_pwrite *args = data;
struct drm_i915_gem_object *obj;
int ret;
if (GRAPHICS_VER(i915) >= 12 && !IS_TIGERLAKE(i915))
return -EOPNOTSUPP;
if (args->size == 0)
return 0;
if (!access_ok(u64_to_user_ptr(args->data_ptr), args->size))
return -EFAULT;
obj = i915_gem_object_lookup(file, args->handle);
if (!obj)
return -ENOENT;
if (range_overflows_t(u64, args->offset, args->size, obj->base.size)) {
ret = -EINVAL;
goto err;
}
if (i915_gem_object_is_readonly(obj)) {
ret = -EINVAL;
goto err;
}
trace_i915_gem_object_pwrite(obj, args->offset, args->size);
ret = -ENODEV;
if (obj->ops->pwrite)
ret = obj->ops->pwrite(obj, args);
if (ret != -ENODEV)
goto err;
ret = i915_gem_object_wait(obj,
I915_WAIT_INTERRUPTIBLE |
I915_WAIT_ALL,
MAX_SCHEDULE_TIMEOUT);
if (ret)
goto err;
ret = -EFAULT;
if (!i915_gem_object_has_struct_page(obj) ||
i915_gem_cpu_write_needs_clflush(obj))
ret = i915_gem_gtt_pwrite_fast(obj, args);
if (ret == -EFAULT || ret == -ENOSPC) {
if (i915_gem_object_has_struct_page(obj))
ret = i915_gem_shmem_pwrite(obj, args);
}
err:
i915_gem_object_put(obj);
return ret;
}
int
i915_gem_sw_finish_ioctl(struct drm_device *dev, void *data,
struct drm_file *file)
{
struct drm_i915_gem_sw_finish *args = data;
struct drm_i915_gem_object *obj;
obj = i915_gem_object_lookup(file, args->handle);
if (!obj)
return -ENOENT;
i915_gem_object_flush_if_display(obj);
i915_gem_object_put(obj);
return 0;
}
void i915_gem_runtime_suspend(struct drm_i915_private *i915)
{
struct drm_i915_gem_object *obj, *on;
int i;
list_for_each_entry_safe(obj, on,
&to_gt(i915)->ggtt->userfault_list, userfault_link)
__i915_gem_object_release_mmap_gtt(obj);
list_for_each_entry_safe(obj, on,
&i915->runtime_pm.lmem_userfault_list, userfault_link)
i915_gem_object_runtime_pm_release_mmap_offset(obj);
for (i = 0; i < to_gt(i915)->ggtt->num_fences; i++) {
struct i915_fence_reg *reg = &to_gt(i915)->ggtt->fence_regs[i];
if (!reg->vma)
continue;
GEM_BUG_ON(i915_vma_has_userfault(reg->vma));
reg->dirty = true;
}
}
static void discard_ggtt_vma(struct i915_vma *vma)
{
struct drm_i915_gem_object *obj = vma->obj;
spin_lock(&obj->vma.lock);
if (!RB_EMPTY_NODE(&vma->obj_node)) {
rb_erase(&vma->obj_node, &obj->vma.tree);
RB_CLEAR_NODE(&vma->obj_node);
}
spin_unlock(&obj->vma.lock);
}
struct i915_vma *
i915_gem_object_ggtt_pin_ww(struct drm_i915_gem_object *obj,
struct i915_gem_ww_ctx *ww,
const struct i915_gtt_view *view,
u64 size, u64 alignment, u64 flags)
{
struct drm_i915_private *i915 = to_i915(obj->base.dev);
struct i915_ggtt *ggtt = to_gt(i915)->ggtt;
struct i915_vma *vma;
int ret;
GEM_WARN_ON(!ww);
if (flags & PIN_MAPPABLE &&
(!view || view->type == I915_GTT_VIEW_NORMAL)) {
if (obj->base.size > ggtt->mappable_end)
return ERR_PTR(-E2BIG);
if (flags & PIN_NONBLOCK &&
obj->base.size > ggtt->mappable_end / 2)
return ERR_PTR(-ENOSPC);
}
new_vma:
vma = i915_vma_instance(obj, &ggtt->vm, view);
if (IS_ERR(vma))
return vma;
if (i915_vma_misplaced(vma, size, alignment, flags)) {
if (flags & PIN_NONBLOCK) {
if (i915_vma_is_pinned(vma) || i915_vma_is_active(vma))
return ERR_PTR(-ENOSPC);
if (flags & PIN_MAPPABLE &&
(vma->fence_size > ggtt->mappable_end / 2 ||
!i915_vma_is_map_and_fenceable(vma)))
return ERR_PTR(-ENOSPC);
}
if (i915_vma_is_pinned(vma) || i915_vma_is_active(vma)) {
discard_ggtt_vma(vma);
goto new_vma;
}
ret = i915_vma_unbind(vma);
if (ret)
return ERR_PTR(ret);
}
ret = i915_vma_pin_ww(vma, ww, size, alignment, flags | PIN_GLOBAL);
if (ret)
return ERR_PTR(ret);
if (vma->fence && !i915_gem_object_is_tiled(obj)) {
mutex_lock(&ggtt->vm.mutex);
i915_vma_revoke_fence(vma);
mutex_unlock(&ggtt->vm.mutex);
}
ret = i915_vma_wait_for_bind(vma);
if (ret) {
i915_vma_unpin(vma);
return ERR_PTR(ret);
}
return vma;
}
struct i915_vma * __must_check
i915_gem_object_ggtt_pin(struct drm_i915_gem_object *obj,
const struct i915_gtt_view *view,
u64 size, u64 alignment, u64 flags)
{
struct i915_gem_ww_ctx ww;
struct i915_vma *ret;
int err;
for_i915_gem_ww(&ww, err, true) {
err = i915_gem_object_lock(obj, &ww);
if (err)
continue;
ret = i915_gem_object_ggtt_pin_ww(obj, &ww, view, size,
alignment, flags);
if (IS_ERR(ret))
err = PTR_ERR(ret);
}
return err ? ERR_PTR(err) : ret;
}
int
i915_gem_madvise_ioctl(struct drm_device *dev, void *data,
struct drm_file *file_priv)
{
struct drm_i915_private *i915 = to_i915(dev);
struct drm_i915_gem_madvise *args = data;
struct drm_i915_gem_object *obj;
int err;
switch (args->madv) {
case I915_MADV_DONTNEED:
case I915_MADV_WILLNEED:
break;
default:
return -EINVAL;
}
obj = i915_gem_object_lookup(file_priv, args->handle);
if (!obj)
return -ENOENT;
err = i915_gem_object_lock_interruptible(obj, NULL);
if (err)
goto out;
if (i915_gem_object_has_pages(obj) &&
i915_gem_object_is_tiled(obj) &&
i915->gem_quirks & GEM_QUIRK_PIN_SWIZZLED_PAGES) {
if (obj->mm.madv == I915_MADV_WILLNEED) {
GEM_BUG_ON(!i915_gem_object_has_tiling_quirk(obj));
i915_gem_object_clear_tiling_quirk(obj);
i915_gem_object_make_shrinkable(obj);
}
if (args->madv == I915_MADV_WILLNEED) {
GEM_BUG_ON(i915_gem_object_has_tiling_quirk(obj));
i915_gem_object_make_unshrinkable(obj);
i915_gem_object_set_tiling_quirk(obj);
}
}
if (obj->mm.madv != __I915_MADV_PURGED) {
obj->mm.madv = args->madv;
if (obj->ops->adjust_lru)
obj->ops->adjust_lru(obj);
}
if (i915_gem_object_has_pages(obj) ||
i915_gem_object_has_self_managed_shrink_list(obj)) {
unsigned long flags;
spin_lock_irqsave(&i915->mm.obj_lock, flags);
if (!list_empty(&obj->mm.link)) {
struct list_head *list;
if (obj->mm.madv != I915_MADV_WILLNEED)
list = &i915->mm.purge_list;
else
list = &i915->mm.shrink_list;
list_move_tail(&obj->mm.link, list);
}
spin_unlock_irqrestore(&i915->mm.obj_lock, flags);
}
if (obj->mm.madv == I915_MADV_DONTNEED &&
!i915_gem_object_has_pages(obj))
i915_gem_object_truncate(obj);
args->retained = obj->mm.madv != __I915_MADV_PURGED;
i915_gem_object_unlock(obj);
out:
i915_gem_object_put(obj);
return err;
}
void i915_gem_drain_freed_objects(struct drm_i915_private *i915)
{
while (atomic_read(&i915->mm.free_count)) {
flush_work(&i915->mm.free_work);
drain_workqueue(i915->bdev.wq);
rcu_barrier();
}
}
void i915_gem_drain_workqueue(struct drm_i915_private *i915)
{
int i;
for (i = 0; i < 3; i++) {
flush_workqueue(i915->wq);
rcu_barrier();
i915_gem_drain_freed_objects(i915);
}
drain_workqueue(i915->wq);
}
int i915_gem_init(struct drm_i915_private *dev_priv)
{
struct intel_gt *gt;
unsigned int i;
int ret;
BUILD_BUG_ON(I915_CACHE_NONE != 0 ||
I915_CACHE_LLC != 1 ||
I915_CACHE_L3_LLC != 2 ||
I915_CACHE_WT != 3 ||
I915_MAX_CACHE_LEVEL != 4);
if (intel_vgpu_active(dev_priv) && !intel_vgpu_has_huge_gtt(dev_priv))
RUNTIME_INFO(dev_priv)->page_sizes = I915_GTT_PAGE_SIZE_4K;
ret = i915_gem_init_userptr(dev_priv);
if (ret)
return ret;
for_each_gt(gt, dev_priv, i) {
intel_uc_fetch_firmwares(>->uc);
intel_wopcm_init(>->wopcm);
if (GRAPHICS_VER(dev_priv) >= 8)
setup_private_pat(gt);
}
ret = i915_init_ggtt(dev_priv);
if (ret) {
GEM_BUG_ON(ret == -EIO);
goto err_unlock;
}
intel_clock_gating_init(dev_priv);
for_each_gt(gt, dev_priv, i) {
ret = intel_gt_init(gt);
if (ret)
goto err_unlock;
}
intel_engines_driver_register(dev_priv);
return 0;
err_unlock:
i915_gem_drain_workqueue(dev_priv);
if (ret != -EIO) {
for_each_gt(gt, dev_priv, i) {
intel_gt_driver_remove(gt);
intel_gt_driver_release(gt);
intel_uc_cleanup_firmwares(>->uc);
}
}
if (ret == -EIO) {
for_each_gt(gt, dev_priv, i) {
if (!intel_gt_is_wedged(gt)) {
i915_probe_error(dev_priv,
"Failed to initialize GPU, declaring it wedged!\n");
intel_gt_set_wedged(gt);
}
}
ret = i915_ggtt_enable_hw(dev_priv);
i915_ggtt_resume(to_gt(dev_priv)->ggtt);
intel_clock_gating_init(dev_priv);
}
i915_gem_drain_freed_objects(dev_priv);
return ret;
}
void i915_gem_driver_register(struct drm_i915_private *i915)
{
i915_gem_driver_register__shrinker(i915);
}
void i915_gem_driver_unregister(struct drm_i915_private *i915)
{
i915_gem_driver_unregister__shrinker(i915);
}
void i915_gem_driver_remove(struct drm_i915_private *dev_priv)
{
struct intel_gt *gt;
unsigned int i;
i915_gem_suspend_late(dev_priv);
for_each_gt(gt, dev_priv, i)
intel_gt_driver_remove(gt);
dev_priv->uabi_engines = RB_ROOT;
i915_gem_drain_workqueue(dev_priv);
}
void i915_gem_driver_release(struct drm_i915_private *dev_priv)
{
struct intel_gt *gt;
unsigned int i;
for_each_gt(gt, dev_priv, i) {
intel_gt_driver_release(gt);
intel_uc_cleanup_firmwares(>->uc);
}
i915_gem_drain_workqueue(dev_priv);
drm_WARN_ON(&dev_priv->drm, !list_empty(&dev_priv->gem.contexts.list));
}
static void i915_gem_init__mm(struct drm_i915_private *i915)
{
spin_lock_init(&i915->mm.obj_lock);
init_llist_head(&i915->mm.free_list);
INIT_LIST_HEAD(&i915->mm.purge_list);
INIT_LIST_HEAD(&i915->mm.shrink_list);
i915_gem_init__objects(i915);
}
void i915_gem_init_early(struct drm_i915_private *dev_priv)
{
i915_gem_init__mm(dev_priv);
i915_gem_init__contexts(dev_priv);
spin_lock_init(&dev_priv->display.fb_tracking.lock);
}
void i915_gem_cleanup_early(struct drm_i915_private *dev_priv)
{
i915_gem_drain_workqueue(dev_priv);
GEM_BUG_ON(!llist_empty(&dev_priv->mm.free_list));
GEM_BUG_ON(atomic_read(&dev_priv->mm.free_count));
drm_WARN_ON(&dev_priv->drm, dev_priv->mm.shrink_count);
}
int i915_gem_open(struct drm_i915_private *i915, struct drm_file *file)
{
struct drm_i915_file_private *file_priv;
struct i915_drm_client *client;
int ret = -ENOMEM;
drm_dbg(&i915->drm, "\n");
file_priv = kzalloc(sizeof(*file_priv), GFP_KERNEL);
if (!file_priv)
goto err_alloc;
client = i915_drm_client_alloc();
if (!client)
goto err_client;
file->driver_priv = file_priv;
file_priv->i915 = i915;
file_priv->file = file;
file_priv->client = client;
file_priv->bsd_engine = -1;
file_priv->hang_timestamp = jiffies;
ret = i915_gem_context_open(i915, file);
if (ret)
goto err_context;
return 0;
err_context:
i915_drm_client_put(client);
err_client:
kfree(file_priv);
err_alloc:
return ret;
}
#if IS_ENABLED(CONFIG_DRM_I915_SELFTEST)
#include "selftests/mock_gem_device.c"
#include "selftests/i915_gem.c"
#endif