diff options
Diffstat (limited to 'sys/dev/drm2/i915/i915_gem.c')
-rw-r--r-- | sys/dev/drm2/i915/i915_gem.c | 4242 |
1 files changed, 2170 insertions, 2072 deletions
diff --git a/sys/dev/drm2/i915/i915_gem.c b/sys/dev/drm2/i915/i915_gem.c index 356ccc5..3a59623 100644 --- a/sys/dev/drm2/i915/i915_gem.c +++ b/sys/dev/drm2/i915/i915_gem.c @@ -1,4 +1,4 @@ -/*- +/* * Copyright © 2008 Intel Corporation * * Permission is hereby granted, free of charge, to any person obtaining a @@ -60,6 +60,7 @@ __FBSDID("$FreeBSD$"); #include <dev/drm2/i915/i915_drv.h> #include <dev/drm2/i915/intel_drv.h> #include <dev/drm2/i915/intel_ringbuffer.h> + #include <sys/resourcevar.h> #include <sys/sched.h> #include <sys/sf_buf.h> @@ -69,38 +70,40 @@ __FBSDID("$FreeBSD$"); #include <machine/md_var.h> -static void i915_gem_object_flush_cpu_write_domain( - struct drm_i915_gem_object *obj); -static uint32_t i915_gem_get_gtt_size(struct drm_device *dev, uint32_t size, - int tiling_mode); -static uint32_t i915_gem_get_gtt_alignment(struct drm_device *dev, - uint32_t size, int tiling_mode); -static int i915_gem_object_bind_to_gtt(struct drm_i915_gem_object *obj, - unsigned alignment, bool map_and_fenceable); -static int i915_gem_object_get_pages_gtt(struct drm_i915_gem_object *obj, - int flags); -static void i915_gem_object_put_pages_gtt(struct drm_i915_gem_object *obj); -static void i915_gem_object_put_pages_range(struct drm_i915_gem_object *obj, - off_t start, off_t end); +#define __user +#define __force +#define __iomem +#define __must_check +#define to_user_ptr(x) ((void *)(uintptr_t)(x)) +#define offset_in_page(x) ((x) & PAGE_MASK) +#define page_to_phys(x) VM_PAGE_TO_PHYS(x) + +static void i915_gem_object_flush_gtt_write_domain(struct drm_i915_gem_object *obj); +static void i915_gem_object_flush_cpu_write_domain(struct drm_i915_gem_object *obj); +static __must_check int i915_gem_object_bind_to_gtt(struct drm_i915_gem_object *obj, + unsigned alignment, + bool map_and_fenceable); +static int i915_gem_phys_pwrite(struct drm_device *dev, + struct drm_i915_gem_object *obj, + struct drm_i915_gem_pwrite *args, + struct drm_file *file); + +static void i915_gem_write_fence(struct drm_device *dev, int reg, + struct drm_i915_gem_object *obj); +static void i915_gem_object_update_fence(struct drm_i915_gem_object *obj, + struct drm_i915_fence_reg *fence, + bool enable); + +static void i915_gem_lowmem(void *arg); +static void i915_gem_object_truncate(struct drm_i915_gem_object *obj); + static int i915_gem_object_get_pages_range(struct drm_i915_gem_object *obj, off_t start, off_t end); -static void i915_gem_object_finish_gtt(struct drm_i915_gem_object *obj); -static void i915_gem_object_truncate(struct drm_i915_gem_object *obj); -static int i915_gem_object_is_purgeable(struct drm_i915_gem_object *obj); -static bool i915_gem_object_is_inactive(struct drm_i915_gem_object *obj); -static int i915_gem_object_needs_bit17_swizzle(struct drm_i915_gem_object *obj); +static void i915_gem_object_put_pages_range(struct drm_i915_gem_object *obj, + off_t start, off_t end); + static vm_page_t i915_gem_wire_page(vm_object_t object, vm_pindex_t pindex, bool *fresh); -static void i915_gem_process_flushing_list(struct intel_ring_buffer *ring, - uint32_t flush_domains); -static void i915_gem_reset_fences(struct drm_device *dev); -static void i915_gem_retire_task_handler(void *arg, int pending); -static void i915_gem_lowmem(void *arg); -static void i915_gem_write_fence(struct drm_device *dev, int reg, - struct drm_i915_gem_object *obj); -static int __wait_seqno(struct intel_ring_buffer *ring, u32 seqno, - bool interruptible); -static int i915_gem_check_olr(struct intel_ring_buffer *ring, u32 seqno); MALLOC_DEFINE(DRM_I915_GEM, "i915gem", "Allocations from i915 gem"); long i915_gem_wired_pages_cnt; @@ -131,18 +134,17 @@ static inline void i915_gem_object_fence_lost(struct drm_i915_gem_object *obj) obj->fence_reg = I915_FENCE_REG_NONE; } -static void -i915_gem_info_add_obj(struct drm_i915_private *dev_priv, size_t size) +/* some bookkeeping */ +static void i915_gem_info_add_obj(struct drm_i915_private *dev_priv, + size_t size) { - dev_priv->mm.object_count++; dev_priv->mm.object_memory += size; } -static void -i915_gem_info_remove_obj(struct drm_i915_private *dev_priv, size_t size) +static void i915_gem_info_remove_obj(struct drm_i915_private *dev_priv, + size_t size) { - dev_priv->mm.object_count--; dev_priv->mm.object_memory -= size; } @@ -150,10 +152,9 @@ i915_gem_info_remove_obj(struct drm_i915_private *dev_priv, size_t size) static int i915_gem_wait_for_error(struct drm_device *dev) { - struct drm_i915_private *dev_priv; + struct drm_i915_private *dev_priv = dev->dev_private; int ret; - dev_priv = dev->dev_private; if (!atomic_load_acq_int(&dev_priv->mm.wedged)) return (0); @@ -171,150 +172,60 @@ i915_gem_wait_for_error(struct drm_device *dev) mtx_unlock(&dev_priv->error_completion_lock); if (atomic_load_acq_int(&dev_priv->mm.wedged)) { + /* GPU is hung, bump the completion count to account for + * the token we just consumed so that we never hit zero and + * end up waiting upon a subsequent completion event that + * will never happen. + */ mtx_lock(&dev_priv->error_completion_lock); dev_priv->error_completion++; mtx_unlock(&dev_priv->error_completion_lock); } - return (0); + return 0; } -int -i915_mutex_lock_interruptible(struct drm_device *dev) +int i915_mutex_lock_interruptible(struct drm_device *dev) { - struct drm_i915_private *dev_priv; int ret; - dev_priv = dev->dev_private; ret = i915_gem_wait_for_error(dev); - if (ret != 0) - return (ret); + if (ret) + return ret; /* * interruptible shall it be. might indeed be if dev_lock is * changed to sx */ - ret = sx_xlock_sig(&dev->dev_struct_lock); - if (ret != 0) - return (-ret); - - return (0); -} - - -void -i915_gem_free_object(struct drm_gem_object *gem_obj) -{ - struct drm_i915_gem_object *obj = to_intel_bo(gem_obj); - struct drm_device *dev; - drm_i915_private_t *dev_priv; - - dev = obj->base.dev; - dev_priv = dev->dev_private; - - CTR1(KTR_DRM, "object_destroy_tail %p", obj); - - if (obj->phys_obj) - i915_gem_detach_phys_object(dev, obj); - - obj->pin_count = 0; - if (i915_gem_object_unbind(obj) == -ERESTARTSYS) { - bool was_interruptible; - - was_interruptible = dev_priv->mm.interruptible; - dev_priv->mm.interruptible = false; - - if (i915_gem_object_unbind(obj)) - printf("i915_gem_free_object: unbind\n"); - - dev_priv->mm.interruptible = was_interruptible; - } - - drm_gem_free_mmap_offset(&obj->base); - drm_gem_object_release(&obj->base); - i915_gem_info_remove_obj(dev_priv, obj->base.size); - - free(obj->bit_17, DRM_I915_GEM); - free(obj, DRM_I915_GEM); -} - -static void -init_ring_lists(struct intel_ring_buffer *ring) -{ + ret = -sx_xlock_sig(&dev->dev_struct_lock); + if (ret) + return ret; - INIT_LIST_HEAD(&ring->active_list); - INIT_LIST_HEAD(&ring->request_list); - INIT_LIST_HEAD(&ring->gpu_write_list); + return 0; } -void -i915_gem_load(struct drm_device *dev) +static inline bool +i915_gem_object_is_inactive(struct drm_i915_gem_object *obj) { - drm_i915_private_t *dev_priv; - int i; - - dev_priv = dev->dev_private; - - INIT_LIST_HEAD(&dev_priv->mm.active_list); - INIT_LIST_HEAD(&dev_priv->mm.flushing_list); - INIT_LIST_HEAD(&dev_priv->mm.inactive_list); - INIT_LIST_HEAD(&dev_priv->mm.fence_list); - INIT_LIST_HEAD(&dev_priv->mm.gtt_list); - for (i = 0; i < I915_NUM_RINGS; i++) - init_ring_lists(&dev_priv->rings[i]); - for (i = 0; i < I915_MAX_NUM_FENCES; i++) - INIT_LIST_HEAD(&dev_priv->fence_regs[i].lru_list); - TIMEOUT_TASK_INIT(dev_priv->tq, &dev_priv->mm.retire_task, 0, - i915_gem_retire_task_handler, dev_priv); - dev_priv->error_completion = 0; - - /* On GEN3 we really need to make sure the ARB C3 LP bit is set */ - if (IS_GEN3(dev)) { - I915_WRITE(MI_ARB_STATE, - _MASKED_BIT_ENABLE(MI_ARB_C3_LP_WRITE_ENABLE)); - } - - dev_priv->relative_constants_mode = I915_EXEC_CONSTANTS_REL_GENERAL; - - /* Old X drivers will take 0-2 for front, back, depth buffers */ - if (!drm_core_check_feature(dev, DRIVER_MODESET)) - dev_priv->fence_reg_start = 3; - - if (INTEL_INFO(dev)->gen >= 4 || IS_I945G(dev) || IS_I945GM(dev) || - IS_G33(dev)) - dev_priv->num_fence_regs = 16; - else - dev_priv->num_fence_regs = 8; - - /* Initialize fence registers to zero */ - i915_gem_reset_fences(dev); - - i915_gem_detect_bit_6_swizzle(dev); - dev_priv->mm.interruptible = true; - - dev_priv->mm.i915_lowmem = EVENTHANDLER_REGISTER(vm_lowmem, - i915_gem_lowmem, dev, EVENTHANDLER_PRI_ANY); + return !obj->active; } int i915_gem_init_ioctl(struct drm_device *dev, void *data, - struct drm_file *file) + struct drm_file *file) { - struct drm_i915_gem_init *args; - drm_i915_private_t *dev_priv; - int error; + struct drm_i915_gem_init *args = data; + drm_i915_private_t *dev_priv = dev->dev_private; + int ret; if (drm_core_check_feature(dev, DRIVER_MODESET)) return -ENODEV; - dev_priv = dev->dev_private; - args = data; - if (args->gtt_start >= args->gtt_end || (args->gtt_end | args->gtt_start) & (PAGE_SIZE - 1)) - return (-EINVAL); + return -EINVAL; if (mtx_initialized(&dev_priv->mm.gtt_space.unused_lock)) - return (-EBUSY); + return -EBUSY; /* GEM with user mode setting was never supported on ilk and later. */ if (INTEL_INFO(dev)->gen >= 5) @@ -325,254 +236,22 @@ i915_gem_init_ioctl(struct drm_device *dev, void *data, * against. */ DRM_LOCK(dev); - error = i915_gem_init_global_gtt(dev, args->gtt_start, - args->gtt_end, args->gtt_end); - DRM_UNLOCK(dev); - return (error); -} - -int -i915_gem_idle(struct drm_device *dev) -{ - drm_i915_private_t *dev_priv; - int ret; - - DRM_LOCK(dev); - - dev_priv = dev->dev_private; - if (dev_priv->mm.suspended) { - DRM_UNLOCK(dev); - return (0); - } - - ret = i915_gpu_idle(dev); - if (ret != 0) { - DRM_UNLOCK(dev); - return (ret); - } - i915_gem_retire_requests(dev); - - /* Under UMS, be paranoid and evict. */ - if (!drm_core_check_feature(dev, DRIVER_MODESET)) { - ret = i915_gem_evict_everything(dev, false); - if (ret != 0) { - DRM_UNLOCK(dev); - return ret; - } - } - - i915_gem_reset_fences(dev); - - /* Hack! Don't let anybody do execbuf while we don't control the chip. - * We need to replace this with a semaphore, or something. - * And not confound mm.suspended! - */ - dev_priv->mm.suspended = 1; - callout_stop(&dev_priv->hangcheck_timer); - - i915_kernel_lost_context(dev); - i915_gem_cleanup_ringbuffer(dev); - - DRM_UNLOCK(dev); - - /* Cancel the retire work handler, which should be idle now. */ - taskqueue_cancel_timeout(dev_priv->tq, &dev_priv->mm.retire_task, NULL); - return (ret); -} - -void -i915_gem_init_swizzling(struct drm_device *dev) -{ - drm_i915_private_t *dev_priv; - - dev_priv = dev->dev_private; - - if (INTEL_INFO(dev)->gen < 5 || - dev_priv->mm.bit_6_swizzle_x == I915_BIT_6_SWIZZLE_NONE) - return; - - I915_WRITE(DISP_ARB_CTL, I915_READ(DISP_ARB_CTL) | - DISP_TILE_SURFACE_SWIZZLING); - - if (IS_GEN5(dev)) - return; - - - I915_WRITE(TILECTL, I915_READ(TILECTL) | TILECTL_SWZCTL); - if (IS_GEN6(dev)) - I915_WRITE(ARB_MODE, _MASKED_BIT_ENABLE(ARB_MODE_SWIZZLE_SNB)); - else - I915_WRITE(ARB_MODE, _MASKED_BIT_ENABLE(ARB_MODE_SWIZZLE_IVB)); -} - -void i915_gem_init_ppgtt(struct drm_device *dev) -{ - drm_i915_private_t *dev_priv; - struct i915_hw_ppgtt *ppgtt; - uint32_t pd_offset, pd_entry; - vm_paddr_t pt_addr; - struct intel_ring_buffer *ring; - u_int first_pd_entry_in_global_pt, i; - - dev_priv = dev->dev_private; - ppgtt = dev_priv->mm.aliasing_ppgtt; - if (ppgtt == NULL) - return; - - first_pd_entry_in_global_pt = 512 * 1024 - I915_PPGTT_PD_ENTRIES; - for (i = 0; i < ppgtt->num_pd_entries; i++) { - pt_addr = VM_PAGE_TO_PHYS(ppgtt->pt_pages[i]); - pd_entry = GEN6_PDE_ADDR_ENCODE(pt_addr); - pd_entry |= GEN6_PDE_VALID; - intel_gtt_write(first_pd_entry_in_global_pt + i, pd_entry); - } - intel_gtt_read_pte(first_pd_entry_in_global_pt); - - pd_offset = ppgtt->pd_offset; - pd_offset /= 64; /* in cachelines, */ - pd_offset <<= 16; - - if (INTEL_INFO(dev)->gen == 6) { - uint32_t ecochk, gab_ctl, ecobits; - - ecobits = I915_READ(GAC_ECO_BITS); - I915_WRITE(GAC_ECO_BITS, ecobits | ECOBITS_PPGTT_CACHE64B); - - gab_ctl = I915_READ(GAB_CTL); - I915_WRITE(GAB_CTL, gab_ctl | GAB_CTL_CONT_AFTER_PAGEFAULT); - - ecochk = I915_READ(GAM_ECOCHK); - I915_WRITE(GAM_ECOCHK, ecochk | ECOCHK_SNB_BIT | - ECOCHK_PPGTT_CACHE64B); - I915_WRITE(GFX_MODE, _MASKED_BIT_ENABLE(GFX_PPGTT_ENABLE)); - } else if (INTEL_INFO(dev)->gen >= 7) { - I915_WRITE(GAM_ECOCHK, ECOCHK_PPGTT_CACHE64B); - /* GFX_MODE is per-ring on gen7+ */ - } - - for_each_ring(ring, dev_priv, i) { - if (INTEL_INFO(dev)->gen >= 7) - I915_WRITE(RING_MODE_GEN7(ring), - _MASKED_BIT_ENABLE(GFX_PPGTT_ENABLE)); - - I915_WRITE(RING_PP_DIR_DCLV(ring), PP_DIR_DCLV_2G); - I915_WRITE(RING_PP_DIR_BASE(ring), pd_offset); - } -} - -int -i915_gem_init_hw(struct drm_device *dev) -{ - drm_i915_private_t *dev_priv; - int ret; - - dev_priv = dev->dev_private; - - i915_gem_init_swizzling(dev); - - ret = intel_init_render_ring_buffer(dev); - if (ret != 0) - return (ret); - - if (HAS_BSD(dev)) { - ret = intel_init_bsd_ring_buffer(dev); - if (ret != 0) - goto cleanup_render_ring; - } - - if (HAS_BLT(dev)) { - ret = intel_init_blt_ring_buffer(dev); - if (ret != 0) - goto cleanup_bsd_ring; - } - - dev_priv->next_seqno = 1; - i915_gem_context_init(dev); - i915_gem_init_ppgtt(dev); - return (0); - -cleanup_bsd_ring: - intel_cleanup_ring_buffer(&dev_priv->rings[VCS]); -cleanup_render_ring: - intel_cleanup_ring_buffer(&dev_priv->rings[RCS]); - return (ret); -} - -static bool -intel_enable_ppgtt(struct drm_device *dev) -{ - if (i915_enable_ppgtt >= 0) - return i915_enable_ppgtt; - - /* Disable ppgtt on SNB if VT-d is on. */ - if (INTEL_INFO(dev)->gen == 6 && intel_iommu_enabled) - return false; - - return true; -} - -int i915_gem_init(struct drm_device *dev) -{ - struct drm_i915_private *dev_priv = dev->dev_private; - unsigned long gtt_size, mappable_size; - int ret; - - gtt_size = dev_priv->mm.gtt.gtt_total_entries << PAGE_SHIFT; - mappable_size = dev_priv->mm.gtt.gtt_mappable_entries << PAGE_SHIFT; - - DRM_LOCK(dev); - if (intel_enable_ppgtt(dev) && HAS_ALIASING_PPGTT(dev)) { - /* PPGTT pdes are stolen from global gtt ptes, so shrink the - * aperture accordingly when using aliasing ppgtt. */ - gtt_size -= I915_PPGTT_PD_ENTRIES*PAGE_SIZE; - - i915_gem_init_global_gtt(dev, 0, mappable_size, gtt_size); - - ret = i915_gem_init_aliasing_ppgtt(dev); - if (ret) { - DRM_UNLOCK(dev); - return ret; - } - } else { - /* Let GEM Manage all of the aperture. - * - * However, leave one page at the end still bound to the scratch - * page. There are a number of places where the hardware - * apparently prefetches past the end of the object, and we've - * seen multiple hangs with the GPU head pointer stuck in a - * batchbuffer bound at the last page of the aperture. One page - * should be enough to keep any prefetching inside of the - * aperture. - */ - i915_gem_init_global_gtt(dev, 0, mappable_size, - gtt_size); - } - - ret = i915_gem_init_hw(dev); + ret = i915_gem_init_global_gtt(dev, args->gtt_start, + args->gtt_end, args->gtt_end); DRM_UNLOCK(dev); - if (ret != 0) { - i915_gem_cleanup_aliasing_ppgtt(dev); - return (ret); - } - /* Allow hardware batchbuffers unless told otherwise, but not for KMS. */ - if (!drm_core_check_feature(dev, DRIVER_MODESET)) - dev_priv->dri1.allow_batchbuffer = 1; - return 0; + return ret; } int i915_gem_get_aperture_ioctl(struct drm_device *dev, void *data, - struct drm_file *file) + struct drm_file *file) { - struct drm_i915_private *dev_priv; - struct drm_i915_gem_get_aperture *args; + struct drm_i915_private *dev_priv = dev->dev_private; + struct drm_i915_gem_get_aperture *args = data; struct drm_i915_gem_object *obj; size_t pinned; - dev_priv = dev->dev_private; - args = data; - pinned = 0; DRM_LOCK(dev); list_for_each_entry(obj, &dev_priv->mm.gtt_list, gtt_list) @@ -583,418 +262,84 @@ i915_gem_get_aperture_ioctl(struct drm_device *dev, void *data, args->aper_size = dev_priv->mm.gtt_total; args->aper_available_size = args->aper_size - pinned; - return (0); -} - -int -i915_gem_object_pin(struct drm_i915_gem_object *obj, uint32_t alignment, - bool map_and_fenceable) -{ - int ret; - - if (obj->pin_count == DRM_I915_GEM_OBJECT_MAX_PIN_COUNT) - return (-EBUSY); - - if (obj->gtt_space != NULL) { - if ((alignment && obj->gtt_offset & (alignment - 1)) || - (map_and_fenceable && !obj->map_and_fenceable)) { - DRM_DEBUG("bo is already pinned with incorrect alignment:" - " offset=%x, req.alignment=%x, req.map_and_fenceable=%d," - " obj->map_and_fenceable=%d\n", - obj->gtt_offset, alignment, - map_and_fenceable, - obj->map_and_fenceable); - ret = i915_gem_object_unbind(obj); - if (ret != 0) - return (ret); - } - } - - if (obj->gtt_space == NULL) { - ret = i915_gem_object_bind_to_gtt(obj, alignment, - map_and_fenceable); - if (ret) - return (ret); - } - - if (!obj->has_global_gtt_mapping && map_and_fenceable) - i915_gem_gtt_bind_object(obj, obj->cache_level); - - obj->pin_count++; - obj->pin_mappable |= map_and_fenceable; - return 0; } -void -i915_gem_object_unpin(struct drm_i915_gem_object *obj) -{ - - KASSERT(obj->pin_count != 0, ("zero pin count")); - KASSERT(obj->gtt_space != NULL, ("No gtt mapping")); - - if (--obj->pin_count == 0) - obj->pin_mappable = false; -} - -int -i915_gem_pin_ioctl(struct drm_device *dev, void *data, - struct drm_file *file) -{ - struct drm_i915_gem_pin *args; - struct drm_i915_gem_object *obj; - struct drm_gem_object *gobj; - int ret; - - args = data; - - ret = i915_mutex_lock_interruptible(dev); - if (ret != 0) - return ret; - - gobj = drm_gem_object_lookup(dev, file, args->handle); - if (gobj == NULL) { - ret = -ENOENT; - goto unlock; - } - obj = to_intel_bo(gobj); - - if (obj->madv != I915_MADV_WILLNEED) { - DRM_ERROR("Attempting to pin a purgeable buffer\n"); - ret = -EINVAL; - goto out; - } - - if (obj->pin_filp != NULL && obj->pin_filp != file) { - DRM_ERROR("Already pinned in i915_gem_pin_ioctl(): %d\n", - args->handle); - ret = -EINVAL; - goto out; - } - - obj->user_pin_count++; - obj->pin_filp = file; - if (obj->user_pin_count == 1) { - ret = i915_gem_object_pin(obj, args->alignment, true); - if (ret != 0) - goto out; - } - - /* XXX - flush the CPU caches for pinned objects - * as the X server doesn't manage domains yet - */ - i915_gem_object_flush_cpu_write_domain(obj); - args->offset = obj->gtt_offset; -out: - drm_gem_object_unreference(&obj->base); -unlock: - DRM_UNLOCK(dev); - return (ret); -} - -int -i915_gem_unpin_ioctl(struct drm_device *dev, void *data, - struct drm_file *file) -{ - struct drm_i915_gem_pin *args; - struct drm_i915_gem_object *obj; - int ret; - - args = data; - ret = i915_mutex_lock_interruptible(dev); - if (ret != 0) - return (ret); - - obj = to_intel_bo(drm_gem_object_lookup(dev, file, args->handle)); - if (&obj->base == NULL) { - ret = -ENOENT; - goto unlock; - } - - if (obj->pin_filp != file) { - DRM_ERROR("Not pinned by caller in i915_gem_pin_ioctl(): %d\n", - args->handle); - ret = -EINVAL; - goto out; - } - obj->user_pin_count--; - if (obj->user_pin_count == 0) { - obj->pin_filp = NULL; - i915_gem_object_unpin(obj); - } - -out: - drm_gem_object_unreference(&obj->base); -unlock: - DRM_UNLOCK(dev); - return (ret); -} - -int -i915_gem_busy_ioctl(struct drm_device *dev, void *data, - struct drm_file *file) -{ - struct drm_i915_gem_busy *args; - struct drm_i915_gem_object *obj; - int ret; - - args = data; - - ret = i915_mutex_lock_interruptible(dev); - if (ret != 0) - return ret; - - obj = to_intel_bo(drm_gem_object_lookup(dev, file, args->handle)); - if (&obj->base == NULL) { - ret = -ENOENT; - goto unlock; - } - - args->busy = obj->active; - if (args->busy) { - if (obj->base.write_domain & I915_GEM_GPU_DOMAINS) { - ret = i915_gem_flush_ring(obj->ring, - 0, obj->base.write_domain); - } else { - ret = i915_gem_check_olr(obj->ring, - obj->last_rendering_seqno); - } - - i915_gem_retire_requests_ring(obj->ring); - args->busy = obj->active; - } - - drm_gem_object_unreference(&obj->base); -unlock: - DRM_UNLOCK(dev); - return (ret); -} - static int -i915_gem_ring_throttle(struct drm_device *dev, struct drm_file *file) -{ - struct drm_i915_private *dev_priv; - struct drm_i915_file_private *file_priv; - unsigned long recent_enough; - struct drm_i915_gem_request *request; - struct intel_ring_buffer *ring; - u32 seqno; - int ret; - - dev_priv = dev->dev_private; - if (atomic_load_acq_int(&dev_priv->mm.wedged)) - return (-EIO); - - file_priv = file->driver_priv; - recent_enough = ticks - (20 * hz / 1000); - ring = NULL; - seqno = 0; - - mtx_lock(&file_priv->mm.lck); - list_for_each_entry(request, &file_priv->mm.request_list, client_list) { - if (time_after_eq(request->emitted_jiffies, recent_enough)) - break; - ring = request->ring; - seqno = request->seqno; - } - mtx_unlock(&file_priv->mm.lck); - if (seqno == 0) - return (0); - - ret = __wait_seqno(ring, seqno, true); - if (ret == 0) - taskqueue_enqueue_timeout(dev_priv->tq, - &dev_priv->mm.retire_task, 0); - - return (ret); -} - -int -i915_gem_throttle_ioctl(struct drm_device *dev, void *data, - struct drm_file *file_priv) -{ - - return (i915_gem_ring_throttle(dev, file_priv)); -} - -int -i915_gem_madvise_ioctl(struct drm_device *dev, void *data, - struct drm_file *file_priv) -{ - struct drm_i915_gem_madvise *args; - struct drm_i915_gem_object *obj; - int ret; - - args = data; - switch (args->madv) { - case I915_MADV_DONTNEED: - case I915_MADV_WILLNEED: - break; - default: - return (-EINVAL); - } - - ret = i915_mutex_lock_interruptible(dev); - if (ret != 0) - return (ret); - - obj = to_intel_bo(drm_gem_object_lookup(dev, file_priv, args->handle)); - if (&obj->base == NULL) { - ret = -ENOENT; - goto unlock; - } - - if (obj->pin_count != 0) { - ret = -EINVAL; - goto out; - } - - if (obj->madv != I915_MADV_PURGED_INTERNAL) - obj->madv = args->madv; - if (i915_gem_object_is_purgeable(obj) && obj->gtt_space == NULL) - i915_gem_object_truncate(obj); - args->retained = obj->madv != I915_MADV_PURGED_INTERNAL; - -out: - drm_gem_object_unreference(&obj->base); -unlock: - DRM_UNLOCK(dev); - return (ret); -} - -void -i915_gem_cleanup_ringbuffer(struct drm_device *dev) -{ - drm_i915_private_t *dev_priv; - struct intel_ring_buffer *ring; - int i; - - dev_priv = dev->dev_private; - for_each_ring(ring, dev_priv, i) - intel_cleanup_ring_buffer(ring); -} - -int -i915_gem_entervt_ioctl(struct drm_device *dev, void *data, - struct drm_file *file_priv) -{ - drm_i915_private_t *dev_priv; - int ret; - - if (drm_core_check_feature(dev, DRIVER_MODESET)) - return (0); - dev_priv = dev->dev_private; - if (atomic_load_acq_int(&dev_priv->mm.wedged) != 0) { - DRM_ERROR("Reenabling wedged hardware, good luck\n"); - atomic_store_rel_int(&dev_priv->mm.wedged, 0); - } - - DRM_LOCK(dev); - dev_priv->mm.suspended = 0; - - ret = i915_gem_init_hw(dev); - if (ret != 0) { - DRM_UNLOCK(dev); - return (ret); - } - - KASSERT(list_empty(&dev_priv->mm.active_list), ("active list")); - KASSERT(list_empty(&dev_priv->mm.flushing_list), ("flushing list")); - KASSERT(list_empty(&dev_priv->mm.inactive_list), ("inactive list")); - DRM_UNLOCK(dev); - - ret = drm_irq_install(dev); - if (ret) - goto cleanup_ringbuffer; - - return (0); - -cleanup_ringbuffer: - DRM_LOCK(dev); - i915_gem_cleanup_ringbuffer(dev); - dev_priv->mm.suspended = 1; - DRM_UNLOCK(dev); - - return (ret); -} - -int -i915_gem_leavevt_ioctl(struct drm_device *dev, void *data, - struct drm_file *file_priv) -{ - - if (drm_core_check_feature(dev, DRIVER_MODESET)) - return 0; - - drm_irq_uninstall(dev); - return (i915_gem_idle(dev)); -} - -int -i915_gem_create(struct drm_file *file, struct drm_device *dev, uint64_t size, - uint32_t *handle_p) +i915_gem_create(struct drm_file *file, + struct drm_device *dev, + uint64_t size, + uint32_t *handle_p) { struct drm_i915_gem_object *obj; - uint32_t handle; int ret; + u32 handle; size = roundup(size, PAGE_SIZE); if (size == 0) - return (-EINVAL); + return -EINVAL; + /* Allocate the new object */ obj = i915_gem_alloc_object(dev, size); if (obj == NULL) - return (-ENOMEM); + return -ENOMEM; ret = drm_gem_handle_create(file, &obj->base, &handle); - if (ret != 0) { + if (ret) { drm_gem_object_release(&obj->base); i915_gem_info_remove_obj(dev->dev_private, obj->base.size); free(obj, DRM_I915_GEM); - return (ret); + return ret; } /* drop reference from allocate - handle holds it now */ drm_gem_object_unreference(&obj->base); CTR2(KTR_DRM, "object_create %p %x", obj, size); + *handle_p = handle; - return (0); + return 0; } int -i915_gem_dumb_create(struct drm_file *file, struct drm_device *dev, - struct drm_mode_create_dumb *args) +i915_gem_dumb_create(struct drm_file *file, + struct drm_device *dev, + struct drm_mode_create_dumb *args) { - /* have to work out size/pitch and return them */ args->pitch = roundup2(args->width * ((args->bpp + 7) / 8), 64); args->size = args->pitch * args->height; - return (i915_gem_create(file, dev, args->size, &args->handle)); + return i915_gem_create(file, dev, + args->size, &args->handle); } -int -i915_gem_dumb_destroy(struct drm_file *file, struct drm_device *dev, - uint32_t handle) +int i915_gem_dumb_destroy(struct drm_file *file, + struct drm_device *dev, + uint32_t handle) { - - return (drm_gem_handle_delete(file, handle)); + return drm_gem_handle_delete(file, handle); } +/** + * Creates a new mm object and returns a handle to it. + */ int i915_gem_create_ioctl(struct drm_device *dev, void *data, - struct drm_file *file) + struct drm_file *file) { struct drm_i915_gem_create *args = data; - return (i915_gem_create(file, dev, args->size, &args->handle)); + return i915_gem_create(file, dev, + args->size, &args->handle); +} + +static int i915_gem_object_needs_bit17_swizzle(struct drm_i915_gem_object *obj) +{ + drm_i915_private_t *dev_priv = obj->base.dev->dev_private; + + return dev_priv->mm.bit_6_swizzle_x == I915_BIT_6_SWIZZLE_9_10_17 && + obj->tiling_mode != I915_TILING_NONE; } -#define __user -#define __force -#define __iomem -#define to_user_ptr(x) ((void *)(uintptr_t)(x)) -#define offset_in_page(x) ((x) & PAGE_MASK) -#define page_to_phys(x) VM_PAGE_TO_PHYS(x) static inline int __copy_to_user_inatomic(void __user *to, const void *from, unsigned n) { @@ -1120,33 +465,6 @@ __copy_from_user_swizzled(char *gpu_vaddr, int gpu_offset, return 0; } -static int -i915_gem_phys_pwrite(struct drm_device *dev, - struct drm_i915_gem_object *obj, - struct drm_i915_gem_pwrite *args, - struct drm_file *file_priv) -{ - void *vaddr = (char *)obj->phys_obj->handle->vaddr + args->offset; - char __user *user_data = to_user_ptr(args->data_ptr); - - if (__copy_from_user_inatomic_nocache(vaddr, user_data, args->size)) { - unsigned long unwritten; - - /* The physical object once assigned is fixed for the lifetime - * of the obj, so we can safely drop the lock and continue - * to access vaddr. - */ - DRM_UNLOCK(dev); - unwritten = copy_from_user(vaddr, user_data, args->size); - DRM_LOCK(dev); - if (unwritten) - return -EFAULT; - } - - i915_gem_chipset_flush(dev); - return 0; -} - /* Per-page copy function for the shmem pread fastpath. * Flushes invalid cachelines before reading the target if * needs_clflush is set. */ @@ -1411,7 +729,7 @@ fast_user_write(struct drm_device *dev, vaddr_atomic = pmap_mapdev_attr(dev->agp->base + page_base, length, PAT_WRITE_COMBINING); /* We can use the cpu mem copy function because this is X86. */ - vaddr = (char *)vaddr_atomic + page_offset; + vaddr = (char __force*)vaddr_atomic + page_offset; unwritten = __copy_from_user_inatomic_nocache(vaddr, user_data, length); pmap_unmapdev((vm_offset_t)vaddr_atomic, length); @@ -1435,7 +753,7 @@ i915_gem_gtt_pwrite_fast(struct drm_device *dev, ret = i915_gem_object_pin(obj, 0, true); /* XXXKIB ret = i915_gem_obj_ggtt_pin(obj, 0, true, true); */ - if (ret != 0) + if (ret) goto out; ret = i915_gem_object_set_to_gtt_domain(obj, true); @@ -1678,7 +996,7 @@ out: if (!needs_clflush_after && obj->base.write_domain != I915_GEM_DOMAIN_CPU) { i915_gem_clflush_object(obj); - i915_gem_chipset_flush(dev); + i915_gem_chipset_flush(dev); } } @@ -1775,35 +1093,167 @@ unlock: DRM_UNLOCK(dev); return ret; } -#undef __user -#undef __force -#undef __iomem -#undef to_user_ptr -#undef offset_in_page -#undef page_to_phys + +static int +i915_gem_check_wedge(struct drm_i915_private *dev_priv) +{ + DRM_LOCK_ASSERT(dev_priv->dev); + + if (atomic_load_acq_int(&dev_priv->mm.wedged) != 0) { + bool recovery_complete; + + /* Give the error handler a chance to run. */ + mtx_lock(&dev_priv->error_completion_lock); + recovery_complete = (&dev_priv->error_completion) > 0; + mtx_unlock(&dev_priv->error_completion_lock); + + return (recovery_complete ? -EIO : -EAGAIN); + } + + return 0; +} + +/* + * Compare seqno against outstanding lazy request. Emit a request if they are + * equal. + */ +static int +i915_gem_check_olr(struct intel_ring_buffer *ring, u32 seqno) +{ + int ret; + + DRM_LOCK_ASSERT(ring->dev); + + ret = 0; + if (seqno == ring->outstanding_lazy_request) { + struct drm_i915_gem_request *request; + + request = malloc(sizeof(*request), DRM_I915_GEM, + M_WAITOK | M_ZERO); + + ret = i915_add_request(ring, NULL, request); + if (ret != 0) { + free(request, DRM_I915_GEM); + return ret; + } + + MPASS(seqno == request->seqno); + } + return ret; +} + +static int __wait_seqno(struct intel_ring_buffer *ring, u32 seqno, + bool interruptible) +{ + drm_i915_private_t *dev_priv = ring->dev->dev_private; + int ret = 0, flags; + + if (i915_seqno_passed(ring->get_seqno(ring), seqno)) + return 0; + + CTR2(KTR_DRM, "request_wait_begin %s %d", ring->name, seqno); + + mtx_lock(&dev_priv->irq_lock); + if (!ring->irq_get(ring)) { + mtx_unlock(&dev_priv->irq_lock); + return -ENODEV; + } + + flags = interruptible ? PCATCH : 0; + while (!i915_seqno_passed(ring->get_seqno(ring), seqno) + && !atomic_load_acq_int(&dev_priv->mm.wedged) && + ret == 0) { + ret = -msleep(ring, &dev_priv->irq_lock, flags, "915gwr", 0); + if (ret == -ERESTART) + ret = -ERESTARTSYS; + } + ring->irq_put(ring); + mtx_unlock(&dev_priv->irq_lock); + + CTR3(KTR_DRM, "request_wait_end %s %d %d", ring->name, seqno, ret); + + return ret; +} + +/** + * Waits for a sequence number to be signaled, and cleans up the + * request and object lists appropriately for that event. + */ +int +i915_wait_request(struct intel_ring_buffer *ring, uint32_t seqno) +{ + struct drm_device *dev = ring->dev; + struct drm_i915_private *dev_priv = dev->dev_private; + int ret; + + KASSERT(seqno != 0, ("Zero seqno")); + + ret = i915_gem_check_wedge(dev_priv); + if (ret) + return ret; + + ret = i915_gem_check_olr(ring, seqno); + if (ret) + return ret; + + ret = __wait_seqno(ring, seqno, dev_priv->mm.interruptible); + if (atomic_load_acq_int(&dev_priv->mm.wedged)) + ret = -EAGAIN; + + return ret; +} + +/** + * Ensures that all rendering to the object has completed and the object is + * safe to unbind from the GTT or access from the CPU. + */ +static __must_check int +i915_gem_object_wait_rendering(struct drm_i915_gem_object *obj) +{ + int ret; + + KASSERT((obj->base.write_domain & I915_GEM_GPU_DOMAINS) == 0, + ("In GPU write domain")); + + CTR5(KTR_DRM, "object_wait_rendering %p %s %x %d %d", obj, + obj->ring != NULL ? obj->ring->name : "none", obj->gtt_offset, + obj->active, obj->last_rendering_seqno); + if (obj->active) { + ret = i915_wait_request(obj->ring, obj->last_rendering_seqno); + if (ret != 0) + return (ret); + i915_gem_retire_requests_ring(obj->ring); + } + + return 0; +} int i915_gem_set_domain_ioctl(struct drm_device *dev, void *data, - struct drm_file *file) + struct drm_file *file) { - struct drm_i915_gem_set_domain *args; + struct drm_i915_gem_set_domain *args = data; struct drm_i915_gem_object *obj; - uint32_t read_domains; - uint32_t write_domain; + uint32_t read_domains = args->read_domains; + uint32_t write_domain = args->write_domain; int ret; - args = data; - read_domains = args->read_domains; - write_domain = args->write_domain; + /* Only handle setting domains to types used by the CPU. */ + if (write_domain & I915_GEM_GPU_DOMAINS) + return -EINVAL; - if ((write_domain & I915_GEM_GPU_DOMAINS) != 0 || - (read_domains & I915_GEM_GPU_DOMAINS) != 0 || - (write_domain != 0 && read_domains != write_domain)) - return (-EINVAL); + if (read_domains & I915_GEM_GPU_DOMAINS) + return -EINVAL; + + /* Having something in the write domain implies it's in the read + * domain, and only that read domain. Enforce that in the request. + */ + if (write_domain != 0 && read_domains != write_domain) + return -EINVAL; ret = i915_mutex_lock_interruptible(dev); - if (ret != 0) - return (ret); + if (ret) + return ret; obj = to_intel_bo(drm_gem_object_lookup(dev, file, args->handle)); if (&obj->base == NULL) { @@ -1811,50 +1261,68 @@ i915_gem_set_domain_ioctl(struct drm_device *dev, void *data, goto unlock; } - if ((read_domains & I915_GEM_DOMAIN_GTT) != 0) { + if (read_domains & I915_GEM_DOMAIN_GTT) { ret = i915_gem_object_set_to_gtt_domain(obj, write_domain != 0); + + /* Silently promote "you're not bound, there was nothing to do" + * to success, since the client was just asking us to + * make sure everything was done. + */ if (ret == -EINVAL) ret = 0; - } else + } else { ret = i915_gem_object_set_to_cpu_domain(obj, write_domain != 0); + } drm_gem_object_unreference(&obj->base); unlock: DRM_UNLOCK(dev); - return (ret); + return ret; } +/** + * Called when user space has done writes to this buffer + */ int i915_gem_sw_finish_ioctl(struct drm_device *dev, void *data, - struct drm_file *file) + struct drm_file *file) { - struct drm_i915_gem_sw_finish *args; + struct drm_i915_gem_sw_finish *args = data; struct drm_i915_gem_object *obj; - int ret; - - args = data; + int ret = 0; ret = i915_mutex_lock_interruptible(dev); - if (ret != 0) - return (ret); + if (ret) + return ret; + obj = to_intel_bo(drm_gem_object_lookup(dev, file, args->handle)); if (&obj->base == NULL) { ret = -ENOENT; goto unlock; } - if (obj->pin_count != 0) + + /* Pinned buffers may be scanout, so flush the cache */ + if (obj->pin_count) i915_gem_object_flush_cpu_write_domain(obj); + drm_gem_object_unreference(&obj->base); unlock: DRM_UNLOCK(dev); - return (ret); + return ret; } +/** + * Maps the contents of an object, returning the address it is mapped + * into. + * + * While the mapping holds a reference on the contents of the object, it doesn't + * imply a ref on the object itself. + */ int i915_gem_mmap_ioctl(struct drm_device *dev, void *data, - struct drm_file *file) + struct drm_file *file) { - struct drm_i915_gem_mmap *args; + struct drm_i915_gem_mmap *args = data; struct drm_gem_object *obj; struct proc *p; vm_map_t map; @@ -1862,11 +1330,10 @@ i915_gem_mmap_ioctl(struct drm_device *dev, void *data, vm_size_t size; int error, rv; - args = data; - obj = drm_gem_object_lookup(dev, file, args->handle); if (obj == NULL) - return (-ENOENT); + return -ENOENT; + error = 0; if (args->size == 0) goto out; @@ -1906,6 +1373,23 @@ i915_gem_pager_ctor(void *handle, vm_ooffset_t size, vm_prot_t prot, return (0); } +/** + * i915_gem_fault - fault a page into the GTT + * vma: VMA in question + * vmf: fault info + * + * The fault handler is set up by drm_gem_mmap() when a object is GTT mapped + * from userspace. The fault handler takes care of binding the object to + * the GTT (if needed), allocating and programming a fence register (again, + * only if needed based on whether the old reg is still valid or the object + * is tiled) and inserting a new PTE into the faulting process. + * + * Note that the faulting process may involve evicting existing objects + * from the GTT and/or fence registers to make room. So performance may + * suffer if the GTT working set is large or there are few fence registers + * left. + */ + int i915_intr_pf; static int @@ -1916,7 +1400,7 @@ i915_gem_pager_fault(vm_object_t vm_obj, vm_ooffset_t offset, int prot, struct drm_i915_gem_object *obj; struct drm_device *dev; drm_i915_private_t *dev_priv; - vm_page_t m, oldm; + vm_page_t page, oldpage; int cause, ret; bool write; @@ -1943,17 +1427,17 @@ i915_gem_pager_fault(vm_object_t vm_obj, vm_ooffset_t offset, int prot, * progress. */ if (*mres != NULL) { - oldm = *mres; - vm_page_lock(oldm); - vm_page_remove(oldm); - vm_page_unlock(oldm); + oldpage = *mres; + vm_page_lock(oldpage); + vm_page_remove(oldpage); + vm_page_unlock(oldpage); *mres = NULL; } else - oldm = NULL; + oldpage = NULL; VM_OBJECT_WUNLOCK(vm_obj); retry: cause = ret = 0; - m = NULL; + page = NULL; if (i915_intr_pf) { ret = i915_mutex_lock_interruptible(dev); @@ -1970,13 +1454,13 @@ retry: * mapping for the page. Recheck. */ VM_OBJECT_WLOCK(vm_obj); - m = vm_page_lookup(vm_obj, OFF_TO_IDX(offset)); - if (m != NULL) { - if (vm_page_busied(m)) { + page = vm_page_lookup(vm_obj, OFF_TO_IDX(offset)); + if (page != NULL) { + if (vm_page_busied(page)) { DRM_UNLOCK(dev); - vm_page_lock(m); + vm_page_lock(page); VM_OBJECT_WUNLOCK(vm_obj); - vm_page_busy_sleep(m, "915pee"); + vm_page_busy_sleep(page, "915pee"); goto retry; } goto have_page; @@ -2019,45 +1503,45 @@ retry: obj->fault_mappable = true; VM_OBJECT_WLOCK(vm_obj); - m = PHYS_TO_VM_PAGE(dev->agp->base + obj->gtt_offset + offset); - KASSERT((m->flags & PG_FICTITIOUS) != 0, + page = PHYS_TO_VM_PAGE(dev->agp->base + obj->gtt_offset + offset); + KASSERT((page->flags & PG_FICTITIOUS) != 0, ("physical address %#jx not fictitious", (uintmax_t)(dev->agp->base + obj->gtt_offset + offset))); - if (m == NULL) { + if (page == NULL) { VM_OBJECT_WUNLOCK(vm_obj); cause = 60; ret = -EFAULT; goto unlock; } - KASSERT((m->flags & PG_FICTITIOUS) != 0, - ("not fictitious %p", m)); - KASSERT(m->wire_count == 1, ("wire_count not 1 %p", m)); + KASSERT((page->flags & PG_FICTITIOUS) != 0, + ("not fictitious %p", page)); + KASSERT(page->wire_count == 1, ("wire_count not 1 %p", page)); - if (vm_page_busied(m)) { + if (vm_page_busied(page)) { DRM_UNLOCK(dev); - vm_page_lock(m); + vm_page_lock(page); VM_OBJECT_WUNLOCK(vm_obj); - vm_page_busy_sleep(m, "915pbs"); + vm_page_busy_sleep(page, "915pbs"); goto retry; } - if (vm_page_insert(m, vm_obj, OFF_TO_IDX(offset))) { + if (vm_page_insert(page, vm_obj, OFF_TO_IDX(offset))) { DRM_UNLOCK(dev); VM_OBJECT_WUNLOCK(vm_obj); VM_WAIT; goto retry; } - m->valid = VM_PAGE_BITS_ALL; + page->valid = VM_PAGE_BITS_ALL; have_page: - *mres = m; - vm_page_xbusy(m); + *mres = page; + vm_page_xbusy(page); CTR4(KTR_DRM, "fault %p %jx %x phys %x", gem_obj, offset, prot, - m->phys_addr); + page->phys_addr); DRM_UNLOCK(dev); - if (oldm != NULL) { - vm_page_lock(oldm); - vm_page_free(oldm); - vm_page_unlock(oldm); + if (oldpage != NULL) { + vm_page_lock(oldpage); + vm_page_free(oldpage); + vm_page_unlock(oldpage); } vm_object_pip_wakeup(vm_obj); return (VM_PAGER_OK); @@ -2099,416 +1583,51 @@ struct cdev_pager_ops i915_gem_pager_ops = { .cdev_pg_dtor = i915_gem_pager_dtor }; -int -i915_gem_mmap_gtt(struct drm_file *file, struct drm_device *dev, - uint32_t handle, uint64_t *offset) -{ - struct drm_i915_private *dev_priv; - struct drm_i915_gem_object *obj; - int ret; - - dev_priv = dev->dev_private; - - ret = i915_mutex_lock_interruptible(dev); - if (ret != 0) - return (ret); - - obj = to_intel_bo(drm_gem_object_lookup(dev, file, handle)); - if (&obj->base == NULL) { - ret = -ENOENT; - goto unlock; - } - - if (obj->base.size > dev_priv->mm.gtt_mappable_end) { - ret = -E2BIG; - goto out; - } - - if (obj->madv != I915_MADV_WILLNEED) { - DRM_ERROR("Attempting to mmap a purgeable buffer\n"); - ret = -EINVAL; - goto out; - } - - ret = drm_gem_create_mmap_offset(&obj->base); - if (ret != 0) - goto out; - - *offset = DRM_GEM_MAPPING_OFF(obj->base.map_list.key) | - DRM_GEM_MAPPING_KEY; -out: - drm_gem_object_unreference(&obj->base); -unlock: - DRM_UNLOCK(dev); - return (ret); -} - -int -i915_gem_mmap_gtt_ioctl(struct drm_device *dev, void *data, - struct drm_file *file) -{ - struct drm_i915_private *dev_priv; - struct drm_i915_gem_mmap_gtt *args; - - dev_priv = dev->dev_private; - args = data; - - return (i915_gem_mmap_gtt(file, dev, args->handle, &args->offset)); -} - -struct drm_i915_gem_object * -i915_gem_alloc_object(struct drm_device *dev, size_t size) -{ - struct drm_i915_private *dev_priv; - struct drm_i915_gem_object *obj; - - dev_priv = dev->dev_private; - - obj = malloc(sizeof(*obj), DRM_I915_GEM, M_WAITOK | M_ZERO); - - if (drm_gem_object_init(dev, &obj->base, size) != 0) { - free(obj, DRM_I915_GEM); - return (NULL); - } - - obj->base.write_domain = I915_GEM_DOMAIN_CPU; - obj->base.read_domains = I915_GEM_DOMAIN_CPU; - - if (HAS_LLC(dev)) - obj->cache_level = I915_CACHE_LLC; - else - obj->cache_level = I915_CACHE_NONE; - obj->base.driver_private = NULL; - obj->fence_reg = I915_FENCE_REG_NONE; - INIT_LIST_HEAD(&obj->mm_list); - INIT_LIST_HEAD(&obj->gtt_list); - INIT_LIST_HEAD(&obj->ring_list); - INIT_LIST_HEAD(&obj->exec_list); - INIT_LIST_HEAD(&obj->gpu_write_list); - obj->madv = I915_MADV_WILLNEED; - /* Avoid an unnecessary call to unbind on the first bind. */ - obj->map_and_fenceable = true; - - i915_gem_info_add_obj(dev_priv, size); - - return (obj); -} - +/** + * i915_gem_release_mmap - remove physical page mappings + * @obj: obj in question + * + * Preserve the reservation of the mmapping with the DRM core code, but + * relinquish ownership of the pages back to the system. + * + * It is vital that we remove the page mapping if we have mapped a tiled + * object through the GTT and then lose the fence register due to + * resource pressure. Similarly if the object has been moved out of the + * aperture, than pages mapped into userspace must be revoked. Removing the + * mapping will then trigger a page fault on the next user access, allowing + * fixup by i915_gem_fault(). + */ void -i915_gem_clflush_object(struct drm_i915_gem_object *obj) -{ - - /* If we don't have a page list set up, then we're not pinned - * to GPU, and we can ignore the cache flush because it'll happen - * again at bind time. - */ - if (obj->pages == NULL) - return; - - /* If the GPU is snooping the contents of the CPU cache, - * we do not need to manually clear the CPU cache lines. However, - * the caches are only snooped when the render cache is - * flushed/invalidated. As we always have to emit invalidations - * and flushes when moving into and out of the RENDER domain, correct - * snooping behaviour occurs naturally as the result of our domain - * tracking. - */ - if (obj->cache_level != I915_CACHE_NONE) - return; - - CTR1(KTR_DRM, "object_clflush %p", obj); - drm_clflush_pages(obj->pages, obj->base.size / PAGE_SIZE); -} - -static void -i915_gem_object_flush_cpu_write_domain(struct drm_i915_gem_object *obj) -{ - uint32_t old_write_domain; - - if (obj->base.write_domain != I915_GEM_DOMAIN_CPU) - return; - - i915_gem_clflush_object(obj); - intel_gtt_chipset_flush(); - old_write_domain = obj->base.write_domain; - obj->base.write_domain = 0; - - CTR3(KTR_DRM, "object_change_domain flush_cpu_write %p %x %x", obj, - obj->base.read_domains, old_write_domain); -} - -static int -i915_gem_object_flush_gpu_write_domain(struct drm_i915_gem_object *obj) -{ - - if ((obj->base.write_domain & I915_GEM_GPU_DOMAINS) == 0) - return (0); - return (i915_gem_flush_ring(obj->ring, 0, obj->base.write_domain)); -} - -static void -i915_gem_object_flush_gtt_write_domain(struct drm_i915_gem_object *obj) +i915_gem_release_mmap(struct drm_i915_gem_object *obj) { - uint32_t old_write_domain; + vm_object_t devobj; + vm_page_t page; + int i, page_count; - if (obj->base.write_domain != I915_GEM_DOMAIN_GTT) + if (!obj->fault_mappable) return; - wmb(); - - old_write_domain = obj->base.write_domain; - obj->base.write_domain = 0; - - CTR3(KTR_DRM, "object_change_domain flush gtt_write %p %x %x", obj, - obj->base.read_domains, old_write_domain); -} - -int -i915_gem_object_set_to_gtt_domain(struct drm_i915_gem_object *obj, bool write) -{ - drm_i915_private_t *dev_priv = obj->base.dev->dev_private; - uint32_t old_write_domain, old_read_domains; - int ret; - - if (obj->gtt_space == NULL) - return (-EINVAL); - - if (obj->base.write_domain == I915_GEM_DOMAIN_GTT) - return 0; - - ret = i915_gem_object_flush_gpu_write_domain(obj); - if (ret != 0) - return (ret); - - if (obj->pending_gpu_write || write) { - ret = i915_gem_object_wait_rendering(obj); - if (ret != 0) - return (ret); - } - - i915_gem_object_flush_cpu_write_domain(obj); - - old_write_domain = obj->base.write_domain; - old_read_domains = obj->base.read_domains; - - KASSERT((obj->base.write_domain & ~I915_GEM_DOMAIN_GTT) == 0, - ("In GTT write domain")); - obj->base.read_domains |= I915_GEM_DOMAIN_GTT; - if (write) { - obj->base.read_domains = I915_GEM_DOMAIN_GTT; - obj->base.write_domain = I915_GEM_DOMAIN_GTT; - obj->dirty = 1; - } - - /* And bump the LRU for this access */ - if (i915_gem_object_is_inactive(obj)) - list_move_tail(&obj->mm_list, &dev_priv->mm.inactive_list); - - CTR3(KTR_DRM, "object_change_domain set_to_gtt %p %x %x", obj, - old_read_domains, old_write_domain); - return (0); -} - -int -i915_gem_object_set_cache_level(struct drm_i915_gem_object *obj, - enum i915_cache_level cache_level) -{ - struct drm_device *dev; - drm_i915_private_t *dev_priv; - int ret; - - if (obj->cache_level == cache_level) - return 0; - - if (obj->pin_count) { - DRM_DEBUG("can not change the cache level of pinned objects\n"); - return (-EBUSY); - } - - dev = obj->base.dev; - dev_priv = dev->dev_private; - if (obj->gtt_space) { - ret = i915_gem_object_finish_gpu(obj); - if (ret != 0) - return (ret); - - i915_gem_object_finish_gtt(obj); + CTR3(KTR_DRM, "release_mmap %p %x %x", obj, obj->gtt_offset, + OFF_TO_IDX(obj->base.size)); + devobj = cdev_pager_lookup(obj); + if (devobj != NULL) { + page_count = OFF_TO_IDX(obj->base.size); - /* Before SandyBridge, you could not use tiling or fence - * registers with snooped memory, so relinquish any fences - * currently pointing to our region in the aperture. - */ - if (INTEL_INFO(obj->base.dev)->gen < 6) { - ret = i915_gem_object_put_fence(obj); - if (ret != 0) - return (ret); + VM_OBJECT_WLOCK(devobj); +retry: + for (i = 0; i < page_count; i++) { + page = vm_page_lookup(devobj, i); + if (page == NULL) + continue; + if (vm_page_sleep_if_busy(page, "915unm")) + goto retry; + cdev_pager_free_page(devobj, page); } - - if (obj->has_global_gtt_mapping) - i915_gem_gtt_bind_object(obj, cache_level); - if (obj->has_aliasing_ppgtt_mapping) - i915_ppgtt_bind_object(dev_priv->mm.aliasing_ppgtt, - obj, cache_level); - } - - if (cache_level == I915_CACHE_NONE) { - u32 old_read_domains, old_write_domain; - - /* If we're coming from LLC cached, then we haven't - * actually been tracking whether the data is in the - * CPU cache or not, since we only allow one bit set - * in obj->write_domain and have been skipping the clflushes. - * Just set it to the CPU cache for now. - */ - KASSERT((obj->base.write_domain & ~I915_GEM_DOMAIN_CPU) == 0, - ("obj %p in CPU write domain", obj)); - KASSERT((obj->base.read_domains & ~I915_GEM_DOMAIN_CPU) == 0, - ("obj %p in CPU read domain", obj)); - - old_read_domains = obj->base.read_domains; - old_write_domain = obj->base.write_domain; - - obj->base.read_domains = I915_GEM_DOMAIN_CPU; - obj->base.write_domain = I915_GEM_DOMAIN_CPU; - - CTR3(KTR_DRM, "object_change_domain set_cache_level %p %x %x", - obj, old_read_domains, old_write_domain); - } - - obj->cache_level = cache_level; - return (0); -} - -static bool is_pin_display(struct drm_i915_gem_object *obj) -{ - /* There are 3 sources that pin objects: - * 1. The display engine (scanouts, sprites, cursors); - * 2. Reservations for execbuffer; - * 3. The user. - * - * We can ignore reservations as we hold the struct_mutex and - * are only called outside of the reservation path. The user - * can only increment pin_count once, and so if after - * subtracting the potential reference by the user, any pin_count - * remains, it must be due to another use by the display engine. - */ - return obj->pin_count - !!obj->user_pin_count; -} - -int -i915_gem_object_pin_to_display_plane(struct drm_i915_gem_object *obj, - u32 alignment, struct intel_ring_buffer *pipelined) -{ - u32 old_read_domains, old_write_domain; - int ret; - - ret = i915_gem_object_flush_gpu_write_domain(obj); - if (ret != 0) - return (ret); - - if (pipelined != obj->ring) { - ret = i915_gem_object_sync(obj, pipelined); - if (ret) - return (ret); - } - - obj->pin_display = true; - ret = i915_gem_object_set_cache_level(obj, I915_CACHE_NONE); - if (ret != 0) - goto err_unpin_display; - - ret = i915_gem_object_pin(obj, alignment, true); - if (ret != 0) - goto err_unpin_display; - - i915_gem_object_flush_cpu_write_domain(obj); - - old_write_domain = obj->base.write_domain; - old_read_domains = obj->base.read_domains; - - KASSERT((obj->base.write_domain & ~I915_GEM_DOMAIN_GTT) == 0, - ("obj %p in GTT write domain", obj)); - obj->base.read_domains |= I915_GEM_DOMAIN_GTT; - - CTR3(KTR_DRM, "object_change_domain pin_to_display_plan %p %x %x", - obj, old_read_domains, obj->base.write_domain); - return (0); - -err_unpin_display: - obj->pin_display = is_pin_display(obj); - return ret; -} - -void -i915_gem_object_unpin_from_display_plane(struct drm_i915_gem_object *obj) -{ - i915_gem_object_unpin(obj); - obj->pin_display = is_pin_display(obj); -} - -int -i915_gem_object_finish_gpu(struct drm_i915_gem_object *obj) -{ - int ret; - - if ((obj->base.read_domains & I915_GEM_GPU_DOMAINS) == 0) - return (0); - - if (obj->base.write_domain & I915_GEM_GPU_DOMAINS) { - ret = i915_gem_flush_ring(obj->ring, 0, obj->base.write_domain); - if (ret != 0) - return (ret); - } - - ret = i915_gem_object_wait_rendering(obj); - if (ret != 0) - return (ret); - - obj->base.read_domains &= ~I915_GEM_GPU_DOMAINS; - - return (0); -} - -int -i915_gem_object_set_to_cpu_domain(struct drm_i915_gem_object *obj, bool write) -{ - uint32_t old_write_domain, old_read_domains; - int ret; - - if (obj->base.write_domain == I915_GEM_DOMAIN_CPU) - return 0; - - ret = i915_gem_object_flush_gpu_write_domain(obj); - if (ret != 0) - return (ret); - - if (write || obj->pending_gpu_write) { - ret = i915_gem_object_wait_rendering(obj); - if (ret != 0) - return (ret); - } - - i915_gem_object_flush_gtt_write_domain(obj); - - old_write_domain = obj->base.write_domain; - old_read_domains = obj->base.read_domains; - - if ((obj->base.read_domains & I915_GEM_DOMAIN_CPU) == 0) { - i915_gem_clflush_object(obj); - obj->base.read_domains |= I915_GEM_DOMAIN_CPU; - } - - KASSERT((obj->base.write_domain & ~I915_GEM_DOMAIN_CPU) == 0, - ("In cpu write domain")); - - if (write) { - obj->base.read_domains = I915_GEM_DOMAIN_CPU; - obj->base.write_domain = I915_GEM_DOMAIN_CPU; + VM_OBJECT_WUNLOCK(devobj); + vm_object_deallocate(devobj); } - CTR3(KTR_DRM, "object_change_domain set_to_cpu %p %x %x", obj, - old_read_domains, old_write_domain); - return (0); + obj->fault_mappable = false; } static uint32_t @@ -2518,7 +1637,7 @@ i915_gem_get_gtt_size(struct drm_device *dev, uint32_t size, int tiling_mode) if (INTEL_INFO(dev)->gen >= 4 || tiling_mode == I915_TILING_NONE) - return (size); + return size; /* Previous chips need a power-of-two fence region when tiling */ if (INTEL_INFO(dev)->gen == 3) @@ -2529,7 +1648,7 @@ i915_gem_get_gtt_size(struct drm_device *dev, uint32_t size, int tiling_mode) while (gtt_size < size) gtt_size <<= 1; - return (gtt_size); + return gtt_size; } /** @@ -2540,382 +1659,163 @@ i915_gem_get_gtt_size(struct drm_device *dev, uint32_t size, int tiling_mode) * potential fence register mapping. */ static uint32_t -i915_gem_get_gtt_alignment(struct drm_device *dev, uint32_t size, - int tiling_mode) +i915_gem_get_gtt_alignment(struct drm_device *dev, + uint32_t size, + int tiling_mode) { - /* * Minimum alignment is 4k (GTT page size), but might be greater * if a fence register is needed for the object. */ if (INTEL_INFO(dev)->gen >= 4 || tiling_mode == I915_TILING_NONE) - return (4096); + return 4096; /* * Previous chips need to be aligned to the size of the smallest * fence register that can contain the object. */ - return (i915_gem_get_gtt_size(dev, size, tiling_mode)); + return i915_gem_get_gtt_size(dev, size, tiling_mode); } +/** + * i915_gem_get_unfenced_gtt_alignment - return required GTT alignment for an + * unfenced object + * @dev: the device + * @size: size of the object + * @tiling_mode: tiling mode of the object + * + * Return the required GTT alignment for an object, only taking into account + * unfenced tiled surface requirements. + */ uint32_t -i915_gem_get_unfenced_gtt_alignment(struct drm_device *dev, uint32_t size, - int tiling_mode) +i915_gem_get_unfenced_gtt_alignment(struct drm_device *dev, + uint32_t size, + int tiling_mode) { - - if (tiling_mode == I915_TILING_NONE) - return (4096); - /* * Minimum alignment is 4k (GTT page size) for sane hw. */ - if (INTEL_INFO(dev)->gen >= 4 || IS_G33(dev)) - return (4096); + if (INTEL_INFO(dev)->gen >= 4 || IS_G33(dev) || + tiling_mode == I915_TILING_NONE) + return 4096; - /* - * Previous hardware however needs to be aligned to a power-of-two + /* Previous hardware however needs to be aligned to a power-of-two * tile height. The simplest method for determining this is to reuse * the power-of-tile object size. - */ - return (i915_gem_get_gtt_size(dev, size, tiling_mode)); -} - -static int -i915_gem_object_bind_to_gtt(struct drm_i915_gem_object *obj, - unsigned alignment, bool map_and_fenceable) -{ - struct drm_device *dev; - struct drm_i915_private *dev_priv; - struct drm_mm_node *free_space; - uint32_t size, fence_size, fence_alignment, unfenced_alignment; - bool mappable, fenceable; - int ret; - - dev = obj->base.dev; - dev_priv = dev->dev_private; - - if (obj->madv != I915_MADV_WILLNEED) { - DRM_ERROR("Attempting to bind a purgeable object\n"); - return (-EINVAL); - } - - fence_size = i915_gem_get_gtt_size(dev, obj->base.size, - obj->tiling_mode); - fence_alignment = i915_gem_get_gtt_alignment(dev, obj->base.size, - obj->tiling_mode); - unfenced_alignment = i915_gem_get_unfenced_gtt_alignment(dev, - obj->base.size, obj->tiling_mode); - if (alignment == 0) - alignment = map_and_fenceable ? fence_alignment : - unfenced_alignment; - if (map_and_fenceable && (alignment & (fence_alignment - 1)) != 0) { - DRM_ERROR("Invalid object alignment requested %u\n", alignment); - return (-EINVAL); - } - - size = map_and_fenceable ? fence_size : obj->base.size; - - /* If the object is bigger than the entire aperture, reject it early - * before evicting everything in a vain attempt to find space. */ - if (obj->base.size > (map_and_fenceable ? - dev_priv->mm.gtt_mappable_end : dev_priv->mm.gtt_total)) { - DRM_ERROR( -"Attempting to bind an object larger than the aperture\n"); - return (-E2BIG); - } - - search_free: - if (map_and_fenceable) - free_space = drm_mm_search_free_in_range( - &dev_priv->mm.gtt_space, size, alignment, 0, - dev_priv->mm.gtt_mappable_end, 0); - else - free_space = drm_mm_search_free(&dev_priv->mm.gtt_space, - size, alignment, 0); - if (free_space != NULL) { - if (map_and_fenceable) - obj->gtt_space = drm_mm_get_block_range_generic( - free_space, size, alignment, 0, 0, - dev_priv->mm.gtt_mappable_end, 1); - else - obj->gtt_space = drm_mm_get_block_generic(free_space, - size, alignment, 0, 1); - } - if (obj->gtt_space == NULL) { - ret = i915_gem_evict_something(dev, size, alignment, - map_and_fenceable); - if (ret != 0) - return (ret); - goto search_free; - } - ret = i915_gem_object_get_pages_gtt(obj, 0); - if (ret != 0) { - drm_mm_put_block(obj->gtt_space); - obj->gtt_space = NULL; - /* - * i915_gem_object_get_pages_gtt() cannot return - * ENOMEM, since we use vm_page_grab(). - */ - return (ret); - } - - ret = i915_gem_gtt_prepare_object(obj); - if (ret != 0) { - i915_gem_object_put_pages_gtt(obj); - drm_mm_put_block(obj->gtt_space); - obj->gtt_space = NULL; - if (i915_gem_evict_everything(dev, false)) - return (ret); - goto search_free; - } - - if (!dev_priv->mm.aliasing_ppgtt) - i915_gem_gtt_bind_object(obj, obj->cache_level); - - list_add_tail(&obj->gtt_list, &dev_priv->mm.gtt_list); - list_add_tail(&obj->mm_list, &dev_priv->mm.inactive_list); - - KASSERT((obj->base.read_domains & I915_GEM_GPU_DOMAINS) == 0, - ("Object in gpu read domain")); - KASSERT((obj->base.write_domain & I915_GEM_GPU_DOMAINS) == 0, - ("Object in gpu write domain")); - - obj->gtt_offset = obj->gtt_space->start; - - fenceable = - obj->gtt_space->size == fence_size && - (obj->gtt_space->start & (fence_alignment - 1)) == 0; - - mappable = - obj->gtt_offset + obj->base.size <= dev_priv->mm.gtt_mappable_end; - obj->map_and_fenceable = mappable && fenceable; - - CTR4(KTR_DRM, "object_bind %p %x %x %d", obj, obj->gtt_offset, - obj->base.size, map_and_fenceable); - return (0); + return i915_gem_get_gtt_size(dev, size, tiling_mode); } int -i915_gem_object_sync(struct drm_i915_gem_object *obj, - struct intel_ring_buffer *to) +i915_gem_mmap_gtt(struct drm_file *file, + struct drm_device *dev, + uint32_t handle, + uint64_t *offset) { - struct intel_ring_buffer *from = obj->ring; - u32 seqno; - int ret, idx; - - if (from == NULL || to == from) - return 0; - - if (to == NULL || !i915_semaphore_is_enabled(obj->base.dev)) - return i915_gem_object_wait_rendering(obj); - - idx = intel_ring_sync_index(from, to); - - seqno = obj->last_rendering_seqno; - if (seqno <= from->sync_seqno[idx]) - return 0; + struct drm_i915_private *dev_priv = dev->dev_private; + struct drm_i915_gem_object *obj; + int ret; - if (seqno == from->outstanding_lazy_request) { - struct drm_i915_gem_request *request; + ret = i915_mutex_lock_interruptible(dev); + if (ret) + return ret; - request = malloc(sizeof(*request), DRM_I915_GEM, - M_WAITOK | M_ZERO); - ret = i915_add_request(from, NULL, request); - if (ret) { - free(request, DRM_I915_GEM); - return ret; - } - seqno = request->seqno; + obj = to_intel_bo(drm_gem_object_lookup(dev, file, handle)); + if (&obj->base == NULL) { + ret = -ENOENT; + goto unlock; } - - ret = to->sync_to(to, from, seqno); - if (!ret) - from->sync_seqno[idx] = seqno; - - return ret; -} - -static void i915_gem_object_finish_gtt(struct drm_i915_gem_object *obj) -{ - u32 old_write_domain, old_read_domains; - - /* Act a barrier for all accesses through the GTT */ - mb(); - - /* Force a pagefault for domain tracking on next user access */ - i915_gem_release_mmap(obj); - - if ((obj->base.read_domains & I915_GEM_DOMAIN_GTT) == 0) - return; - - old_read_domains = obj->base.read_domains; - old_write_domain = obj->base.write_domain; - - obj->base.read_domains &= ~I915_GEM_DOMAIN_GTT; - obj->base.write_domain &= ~I915_GEM_DOMAIN_GTT; - - CTR3(KTR_DRM, "object_change_domain finish gtt %p %x %x", - obj, old_read_domains, old_write_domain); -} - -int -i915_gem_object_unbind(struct drm_i915_gem_object *obj) -{ - drm_i915_private_t *dev_priv; - int ret; - - dev_priv = obj->base.dev->dev_private; - ret = 0; - if (obj->gtt_space == NULL) - return (0); - if (obj->pin_count != 0) { - DRM_ERROR("Attempting to unbind pinned buffer\n"); - return (-EINVAL); + if (obj->base.size > dev_priv->mm.gtt_mappable_end) { + ret = -E2BIG; + goto out; } - ret = i915_gem_object_finish_gpu(obj); - if (ret == -ERESTARTSYS || ret == -EINTR) - return (ret); - - i915_gem_object_finish_gtt(obj); - - if (ret == 0) - ret = i915_gem_object_set_to_cpu_domain(obj, 1); - if (ret == -ERESTARTSYS || ret == -EINTR) - return (ret); - if (ret != 0) { - i915_gem_clflush_object(obj); - obj->base.read_domains = obj->base.write_domain = - I915_GEM_DOMAIN_CPU; + if (obj->madv != I915_MADV_WILLNEED) { + DRM_ERROR("Attempting to mmap a purgeable buffer\n"); + ret = -EINVAL; + goto out; } - ret = i915_gem_object_put_fence(obj); + ret = drm_gem_create_mmap_offset(&obj->base); if (ret) - return (ret); - - if (obj->has_global_gtt_mapping) - i915_gem_gtt_unbind_object(obj); - if (obj->has_aliasing_ppgtt_mapping) { - i915_ppgtt_unbind_object(dev_priv->mm.aliasing_ppgtt, obj); - obj->has_aliasing_ppgtt_mapping = 0; - } - i915_gem_gtt_finish_object(obj); - - i915_gem_object_put_pages_gtt(obj); - - list_del_init(&obj->gtt_list); - list_del_init(&obj->mm_list); - obj->map_and_fenceable = true; - - drm_mm_put_block(obj->gtt_space); - obj->gtt_space = NULL; - obj->gtt_offset = 0; + goto out; - if (i915_gem_object_is_purgeable(obj)) - i915_gem_object_truncate(obj); - CTR1(KTR_DRM, "object_unbind %p", obj); + *offset = DRM_GEM_MAPPING_OFF(obj->base.map_list.key) | + DRM_GEM_MAPPING_KEY; - return (ret); +out: + drm_gem_object_unreference(&obj->base); +unlock: + DRM_UNLOCK(dev); + return ret; } -static void -i915_gem_object_put_pages_range_locked(struct drm_i915_gem_object *obj, - vm_pindex_t si, vm_pindex_t ei) +/** + * i915_gem_mmap_gtt_ioctl - prepare an object for GTT mmap'ing + * @dev: DRM device + * @data: GTT mapping ioctl data + * @file: GEM object info + * + * Simply returns the fake offset to userspace so it can mmap it. + * The mmap call will end up in drm_gem_mmap(), which will set things + * up so we can get faults in the handler above. + * + * The fault handler will take care of binding the object into the GTT + * (since it may have been evicted to make room for something), allocating + * a fence register, and mapping the appropriate aperture address into + * userspace. + */ +int +i915_gem_mmap_gtt_ioctl(struct drm_device *dev, void *data, + struct drm_file *file) { - vm_object_t vm_obj; - vm_page_t m; - vm_pindex_t i; + struct drm_i915_gem_mmap_gtt *args = data; - vm_obj = obj->base.vm_obj; - VM_OBJECT_ASSERT_LOCKED(vm_obj); - for (i = si, m = vm_page_lookup(vm_obj, i); i < ei; - m = vm_page_next(m), i++) { - KASSERT(m->pindex == i, ("pindex %jx %jx", - (uintmax_t)m->pindex, (uintmax_t)i)); - vm_page_lock(m); - vm_page_unwire(m, PQ_INACTIVE); - if (m->wire_count == 0) - atomic_add_long(&i915_gem_wired_pages_cnt, -1); - vm_page_unlock(m); - } + return i915_gem_mmap_gtt(file, dev, args->handle, &args->offset); } +/* Immediately discard the backing storage */ static void -i915_gem_object_put_pages_range(struct drm_i915_gem_object *obj, - off_t start, off_t end) +i915_gem_object_truncate(struct drm_i915_gem_object *obj) { vm_object_t vm_obj; vm_obj = obj->base.vm_obj; VM_OBJECT_WLOCK(vm_obj); - i915_gem_object_put_pages_range_locked(obj, - OFF_TO_IDX(trunc_page(start)), OFF_TO_IDX(round_page(end))); + vm_object_page_remove(vm_obj, 0, 0, false); VM_OBJECT_WUNLOCK(vm_obj); + drm_gem_free_mmap_offset(&obj->base); + obj->madv = I915_MADV_PURGED_INTERNAL; } -static int -i915_gem_object_get_pages_range(struct drm_i915_gem_object *obj, - off_t start, off_t end) +static inline int +i915_gem_object_is_purgeable(struct drm_i915_gem_object *obj) { - vm_object_t vm_obj; - vm_page_t m; - vm_pindex_t si, ei, i; - bool need_swizzle, fresh; - - need_swizzle = i915_gem_object_needs_bit17_swizzle(obj) != 0; - vm_obj = obj->base.vm_obj; - si = OFF_TO_IDX(trunc_page(start)); - ei = OFF_TO_IDX(round_page(end)); - VM_OBJECT_WLOCK(vm_obj); - for (i = si; i < ei; i++) { - m = i915_gem_wire_page(vm_obj, i, &fresh); - if (m == NULL) - goto failed; - if (need_swizzle && fresh) - i915_gem_object_do_bit_17_swizzle_page(obj, m); - } - VM_OBJECT_WUNLOCK(vm_obj); - return (0); -failed: - i915_gem_object_put_pages_range_locked(obj, si, i); - VM_OBJECT_WUNLOCK(vm_obj); - return (-EIO); + return obj->madv == I915_MADV_DONTNEED; } -static int -i915_gem_object_get_pages_gtt(struct drm_i915_gem_object *obj, - int flags) +static void +i915_gem_object_put_pages_range_locked(struct drm_i915_gem_object *obj, + vm_pindex_t si, vm_pindex_t ei) { - struct drm_device *dev; vm_object_t vm_obj; - vm_page_t m; - vm_pindex_t i, page_count; - int res; + vm_page_t page; + vm_pindex_t i; - dev = obj->base.dev; - KASSERT(obj->pages == NULL, ("Obj already has pages")); - page_count = OFF_TO_IDX(obj->base.size); - obj->pages = malloc(page_count * sizeof(vm_page_t), DRM_I915_GEM, - M_WAITOK); - res = i915_gem_object_get_pages_range(obj, 0, obj->base.size); - if (res != 0) { - free(obj->pages, DRM_I915_GEM); - obj->pages = NULL; - return (res); - } vm_obj = obj->base.vm_obj; - VM_OBJECT_WLOCK(vm_obj); - for (i = 0, m = vm_page_lookup(vm_obj, 0); i < page_count; - i++, m = vm_page_next(m)) { - KASSERT(m->pindex == i, ("pindex %jx %jx", - (uintmax_t)m->pindex, (uintmax_t)i)); - obj->pages[i] = m; + VM_OBJECT_ASSERT_LOCKED(vm_obj); + for (i = si, page = vm_page_lookup(vm_obj, i); i < ei; + page = vm_page_next(page), i++) { + KASSERT(page->pindex == i, ("pindex %jx %jx", + (uintmax_t)page->pindex, (uintmax_t)i)); + vm_page_lock(page); + vm_page_unwire(page, PQ_INACTIVE); + if (page->wire_count == 0) + atomic_add_long(&i915_gem_wired_pages_cnt, -1); + vm_page_unlock(page); } - VM_OBJECT_WUNLOCK(vm_obj); - return (0); } #define GEM_PARANOID_CHECK_GTT 0 @@ -2946,9 +1846,22 @@ i915_gem_assert_pages_not_mapped(struct drm_device *dev, vm_page_t *ma, #endif static void +i915_gem_object_put_pages_range(struct drm_i915_gem_object *obj, + off_t start, off_t end) +{ + vm_object_t vm_obj; + + vm_obj = obj->base.vm_obj; + VM_OBJECT_WLOCK(vm_obj); + i915_gem_object_put_pages_range_locked(obj, + OFF_TO_IDX(trunc_page(start)), OFF_TO_IDX(round_page(end))); + VM_OBJECT_WUNLOCK(vm_obj); +} + +static void i915_gem_object_put_pages_gtt(struct drm_i915_gem_object *obj) { - vm_page_t m; + vm_page_t page; int page_count, i; KASSERT(obj->madv != I915_MADV_PURGED_INTERNAL, ("Purged object")); @@ -2963,14 +1876,14 @@ i915_gem_object_put_pages_gtt(struct drm_i915_gem_object *obj) i915_gem_assert_pages_not_mapped(obj->base.dev, obj->pages, page_count); #endif for (i = 0; i < page_count; i++) { - m = obj->pages[i]; + page = obj->pages[i]; if (obj->dirty) - vm_page_dirty(m); + vm_page_dirty(page); if (obj->madv == I915_MADV_WILLNEED) - vm_page_reference(m); - vm_page_lock(m); + vm_page_reference(page); + vm_page_lock(page); vm_page_unwire(obj->pages[i], PQ_ACTIVE); - vm_page_unlock(m); + vm_page_unlock(page); atomic_add_long(&i915_gem_wired_pages_cnt, -1); } VM_OBJECT_WUNLOCK(obj->base.vm_obj); @@ -2979,69 +1892,139 @@ i915_gem_object_put_pages_gtt(struct drm_i915_gem_object *obj) obj->pages = NULL; } -void -i915_gem_release_mmap(struct drm_i915_gem_object *obj) +static int +i915_gpu_is_active(struct drm_device *dev) { - vm_object_t devobj; - vm_page_t m; - int i, page_count; + drm_i915_private_t *dev_priv = dev->dev_private; - if (!obj->fault_mappable) + return (!list_empty(&dev_priv->mm.flushing_list) || + !list_empty(&dev_priv->mm.active_list)); +} + +static void +i915_gem_lowmem(void *arg) +{ + struct drm_device *dev; + struct drm_i915_private *dev_priv; + struct drm_i915_gem_object *obj, *next; + int cnt, cnt_fail, cnt_total; + + dev = arg; + dev_priv = dev->dev_private; + + if (!sx_try_xlock(&dev->dev_struct_lock)) return; - CTR3(KTR_DRM, "release_mmap %p %x %x", obj, obj->gtt_offset, - OFF_TO_IDX(obj->base.size)); - devobj = cdev_pager_lookup(obj); - if (devobj != NULL) { - page_count = OFF_TO_IDX(obj->base.size); + CTR0(KTR_DRM, "gem_lowmem"); - VM_OBJECT_WLOCK(devobj); -retry: - for (i = 0; i < page_count; i++) { - m = vm_page_lookup(devobj, i); - if (m == NULL) - continue; - if (vm_page_sleep_if_busy(m, "915unm")) - goto retry; - cdev_pager_free_page(devobj, m); - } - VM_OBJECT_WUNLOCK(devobj); - vm_object_deallocate(devobj); +rescan: + /* first scan for clean buffers */ + i915_gem_retire_requests(dev); + + cnt_total = cnt_fail = cnt = 0; + + list_for_each_entry_safe(obj, next, &dev_priv->mm.inactive_list, + mm_list) { + if (i915_gem_object_is_purgeable(obj)) { + if (i915_gem_object_unbind(obj) != 0) + cnt_total++; + } else + cnt_total++; } - obj->fault_mappable = false; + /* second pass, evict/count anything still on the inactive list */ + list_for_each_entry_safe(obj, next, &dev_priv->mm.inactive_list, + mm_list) { + if (i915_gem_object_unbind(obj) == 0) + cnt++; + else + cnt_fail++; + } + + if (cnt_fail > cnt_total / 100 && i915_gpu_is_active(dev)) { + /* + * We are desperate for pages, so as a last resort, wait + * for the GPU to finish and discard whatever we can. + * This has a dramatic impact to reduce the number of + * OOM-killer events whilst running the GPU aggressively. + */ + if (i915_gpu_idle(dev) == 0) + goto rescan; + } + DRM_UNLOCK(dev); } -int -i915_gem_object_wait_rendering(struct drm_i915_gem_object *obj) +static int +i915_gem_object_get_pages_range(struct drm_i915_gem_object *obj, + off_t start, off_t end) { - int ret; + vm_object_t vm_obj; + vm_page_t page; + vm_pindex_t si, ei, i; + bool need_swizzle, fresh; - KASSERT((obj->base.write_domain & I915_GEM_GPU_DOMAINS) == 0, - ("In GPU write domain")); + need_swizzle = i915_gem_object_needs_bit17_swizzle(obj) != 0; + vm_obj = obj->base.vm_obj; + si = OFF_TO_IDX(trunc_page(start)); + ei = OFF_TO_IDX(round_page(end)); + VM_OBJECT_WLOCK(vm_obj); + for (i = si; i < ei; i++) { + page = i915_gem_wire_page(vm_obj, i, &fresh); + if (page == NULL) + goto failed; + if (need_swizzle && fresh) + i915_gem_object_do_bit_17_swizzle_page(obj, page); + } + VM_OBJECT_WUNLOCK(vm_obj); + return (0); +failed: + i915_gem_object_put_pages_range_locked(obj, si, i); + VM_OBJECT_WUNLOCK(vm_obj); + return (-EIO); +} - CTR5(KTR_DRM, "object_wait_rendering %p %s %x %d %d", obj, - obj->ring != NULL ? obj->ring->name : "none", obj->gtt_offset, - obj->active, obj->last_rendering_seqno); - if (obj->active) { - ret = i915_wait_request(obj->ring, obj->last_rendering_seqno); - if (ret != 0) - return (ret); - i915_gem_retire_requests_ring(obj->ring); +static int +i915_gem_object_get_pages_gtt(struct drm_i915_gem_object *obj, + int flags) +{ + vm_object_t vm_obj; + vm_page_t page; + vm_pindex_t i, page_count; + int res; + + KASSERT(obj->pages == NULL, ("Obj already has pages")); + + page_count = OFF_TO_IDX(obj->base.size); + obj->pages = malloc(page_count * sizeof(vm_page_t), DRM_I915_GEM, + M_WAITOK); + res = i915_gem_object_get_pages_range(obj, 0, obj->base.size); + if (res != 0) { + free(obj->pages, DRM_I915_GEM); + obj->pages = NULL; + return (res); + } + vm_obj = obj->base.vm_obj; + VM_OBJECT_WLOCK(vm_obj); + for (i = 0, page = vm_page_lookup(vm_obj, 0); i < page_count; + i++, page = vm_page_next(page)) { + KASSERT(page->pindex == i, ("pindex %jx %jx", + (uintmax_t)page->pindex, (uintmax_t)i)); + obj->pages[i] = page; } + VM_OBJECT_WUNLOCK(vm_obj); return (0); } void i915_gem_object_move_to_active(struct drm_i915_gem_object *obj, - struct intel_ring_buffer *ring, uint32_t seqno) + struct intel_ring_buffer *ring, uint32_t seqno) { struct drm_device *dev = obj->base.dev; struct drm_i915_private *dev_priv = dev->dev_private; struct drm_i915_fence_reg *reg; - obj->ring = ring; KASSERT(ring != NULL, ("NULL ring")); + obj->ring = ring; /* Add a reference if we're newly entering the active list. */ if (!obj->active) { @@ -3112,262 +2095,6 @@ i915_gem_object_move_to_inactive(struct drm_i915_gem_object *obj) #endif } -static void -i915_gem_object_truncate(struct drm_i915_gem_object *obj) -{ - vm_object_t vm_obj; - - vm_obj = obj->base.vm_obj; - VM_OBJECT_WLOCK(vm_obj); - vm_object_page_remove(vm_obj, 0, 0, false); - VM_OBJECT_WUNLOCK(vm_obj); - drm_gem_free_mmap_offset(&obj->base); - obj->madv = I915_MADV_PURGED_INTERNAL; -} - -static inline int -i915_gem_object_is_purgeable(struct drm_i915_gem_object *obj) -{ - - return (obj->madv == I915_MADV_DONTNEED); -} - -static void -i915_gem_process_flushing_list(struct intel_ring_buffer *ring, - uint32_t flush_domains) -{ - struct drm_i915_gem_object *obj, *next; - uint32_t old_write_domain; - - list_for_each_entry_safe(obj, next, &ring->gpu_write_list, - gpu_write_list) { - if (obj->base.write_domain & flush_domains) { - old_write_domain = obj->base.write_domain; - obj->base.write_domain = 0; - list_del_init(&obj->gpu_write_list); - i915_gem_object_move_to_active(obj, ring, - i915_gem_next_request_seqno(ring)); - - CTR3(KTR_DRM, "object_change_domain process_flush %p %x %x", - obj, obj->base.read_domains, old_write_domain); - } - } -} - -static int -i915_gem_object_needs_bit17_swizzle(struct drm_i915_gem_object *obj) -{ - drm_i915_private_t *dev_priv; - - dev_priv = obj->base.dev->dev_private; - return (dev_priv->mm.bit_6_swizzle_x == I915_BIT_6_SWIZZLE_9_10_17 && - obj->tiling_mode != I915_TILING_NONE); -} - -static vm_page_t -i915_gem_wire_page(vm_object_t object, vm_pindex_t pindex, bool *fresh) -{ - vm_page_t m; - int rv; - - VM_OBJECT_ASSERT_WLOCKED(object); - m = vm_page_grab(object, pindex, VM_ALLOC_NORMAL); - if (m->valid != VM_PAGE_BITS_ALL) { - if (vm_pager_has_page(object, pindex, NULL, NULL)) { - rv = vm_pager_get_pages(object, &m, 1, 0); - if (rv != VM_PAGER_OK) { - vm_page_lock(m); - vm_page_free(m); - vm_page_unlock(m); - return (NULL); - } - if (fresh != NULL) - *fresh = true; - } else { - pmap_zero_page(m); - m->valid = VM_PAGE_BITS_ALL; - m->dirty = 0; - if (fresh != NULL) - *fresh = false; - } - } else if (fresh != NULL) { - *fresh = false; - } - vm_page_lock(m); - vm_page_wire(m); - vm_page_unlock(m); - vm_page_xunbusy(m); - atomic_add_long(&i915_gem_wired_pages_cnt, 1); - return (m); -} - -int -i915_gem_flush_ring(struct intel_ring_buffer *ring, uint32_t invalidate_domains, - uint32_t flush_domains) -{ - int ret; - - if (((invalidate_domains | flush_domains) & I915_GEM_GPU_DOMAINS) == 0) - return 0; - - CTR3(KTR_DRM, "ring_flush %s %x %x", ring->name, invalidate_domains, - flush_domains); - ret = ring->flush(ring, invalidate_domains, flush_domains); - if (ret) - return ret; - - if (flush_domains & I915_GEM_GPU_DOMAINS) - i915_gem_process_flushing_list(ring, flush_domains); - return 0; -} - -static int -i915_ring_idle(struct intel_ring_buffer *ring) -{ - int ret; - - if (list_empty(&ring->gpu_write_list) && list_empty(&ring->active_list)) - return 0; - - if (!list_empty(&ring->gpu_write_list)) { - ret = i915_gem_flush_ring(ring, I915_GEM_GPU_DOMAINS, - I915_GEM_GPU_DOMAINS); - if (ret != 0) - return ret; - } - - return (i915_wait_request(ring, i915_gem_next_request_seqno(ring))); -} - -int -i915_gpu_idle(struct drm_device *dev) -{ - drm_i915_private_t *dev_priv = dev->dev_private; - struct intel_ring_buffer *ring; - int ret, i; - - /* Flush everything onto the inactive list. */ - for_each_ring(ring, dev_priv, i) { - ret = i915_switch_context(ring, NULL, DEFAULT_CONTEXT_ID); - if (ret) - return ret; - - ret = i915_ring_idle(ring); - if (ret) - return ret; - - /* Is the device fubar? */ - if (!list_empty(&ring->gpu_write_list)) - return -EBUSY; - } - - return 0; -} - -static int -i915_gem_check_wedge(struct drm_i915_private *dev_priv) -{ - DRM_LOCK_ASSERT(dev_priv->dev); - - if (atomic_load_acq_int(&dev_priv->mm.wedged) != 0) { - bool recovery_complete; - /* Give the error handler a chance to run. */ - mtx_lock(&dev_priv->error_completion_lock); - recovery_complete = (&dev_priv->error_completion) > 0; - mtx_unlock(&dev_priv->error_completion_lock); - return (recovery_complete ? -EIO : -EAGAIN); - } - - return 0; -} - -/* - * Compare seqno against outstanding lazy request. Emit a request if they are - * equal. - */ -static int -i915_gem_check_olr(struct intel_ring_buffer *ring, u32 seqno) -{ - int ret = 0; - - DRM_LOCK_ASSERT(ring->dev); - - if (seqno == ring->outstanding_lazy_request) { - struct drm_i915_gem_request *request; - - request = malloc(sizeof(*request), DRM_I915_GEM, - M_WAITOK | M_ZERO); - - ret = i915_add_request(ring, NULL, request); - if (ret != 0) { - free(request, DRM_I915_GEM); - return (ret); - } - - MPASS(seqno == request->seqno); - } - return ret; -} - -static int __wait_seqno(struct intel_ring_buffer *ring, u32 seqno, - bool interruptible) -{ - drm_i915_private_t *dev_priv = ring->dev->dev_private; - int ret = 0, flags; - - if (i915_seqno_passed(ring->get_seqno(ring), seqno)) - return 0; - - CTR2(KTR_DRM, "request_wait_begin %s %d", ring->name, seqno); - - mtx_lock(&dev_priv->irq_lock); - if (!ring->irq_get(ring)) { - mtx_unlock(&dev_priv->irq_lock); - return (-ENODEV); - } - - flags = interruptible ? PCATCH : 0; - while (!i915_seqno_passed(ring->get_seqno(ring), seqno) - && !atomic_load_acq_int(&dev_priv->mm.wedged) && - ret == 0) { - ret = -msleep(ring, &dev_priv->irq_lock, flags, "915gwr", 0); - if (ret == -ERESTART) - ret = -ERESTARTSYS; - } - ring->irq_put(ring); - mtx_unlock(&dev_priv->irq_lock); - - CTR3(KTR_DRM, "request_wait_end %s %d %d", ring->name, seqno, ret); - - return ret; -} - -int -i915_wait_request(struct intel_ring_buffer *ring, uint32_t seqno) -{ - drm_i915_private_t *dev_priv; - int ret; - - KASSERT(seqno != 0, ("Zero seqno")); - - dev_priv = ring->dev->dev_private; - ret = 0; - - ret = i915_gem_check_wedge(dev_priv); - if (ret) - return ret; - - ret = i915_gem_check_olr(ring, seqno); - if (ret) - return ret; - - ret = __wait_seqno(ring, seqno, dev_priv->mm.interruptible); - if (atomic_load_acq_int(&dev_priv->mm.wedged)) - ret = -EAGAIN; - - return (ret); -} - static u32 i915_gem_get_seqno(struct drm_device *dev) { @@ -3391,10 +2118,11 @@ i915_gem_next_request_seqno(struct intel_ring_buffer *ring) } int -i915_add_request(struct intel_ring_buffer *ring, struct drm_file *file, - struct drm_i915_gem_request *request) +i915_add_request(struct intel_ring_buffer *ring, + struct drm_file *file, + struct drm_i915_gem_request *request) { - drm_i915_private_t *dev_priv; + drm_i915_private_t *dev_priv = ring->dev->dev_private; struct drm_i915_file_private *file_priv; uint32_t seqno; u32 request_ring_position; @@ -3403,7 +2131,6 @@ i915_add_request(struct intel_ring_buffer *ring, struct drm_file *file, KASSERT(request != NULL, ("NULL request in add")); DRM_LOCK_ASSERT(ring->dev); - dev_priv = ring->dev->dev_private; seqno = i915_gem_next_request_seqno(ring); request_ring_position = intel_ring_get_tail(ring); @@ -3421,13 +2148,13 @@ i915_add_request(struct intel_ring_buffer *ring, struct drm_file *file, was_empty = list_empty(&ring->request_list); list_add_tail(&request->list, &ring->request_list); - if (file != NULL) { + if (file) { file_priv = file->driver_priv; mtx_lock(&file_priv->mm.lck); request->file_priv = file_priv; list_add_tail(&request->client_list, - &file_priv->mm.request_list); + &file_priv->mm.request_list); mtx_unlock(&file_priv->mm.lck); } @@ -3442,7 +2169,8 @@ i915_add_request(struct intel_ring_buffer *ring, struct drm_file *file, taskqueue_enqueue_timeout(dev_priv->tq, &dev_priv->mm.retire_task, hz); } - return (0); + + return 0; } static inline void @@ -3456,41 +2184,16 @@ i915_gem_request_remove_from_client(struct drm_i915_gem_request *request) DRM_LOCK_ASSERT(request->ring->dev); mtx_lock(&file_priv->mm.lck); - if (request->file_priv != NULL) { + if (request->file_priv) { list_del(&request->client_list); request->file_priv = NULL; } mtx_unlock(&file_priv->mm.lck); } -void -i915_gem_release(struct drm_device *dev, struct drm_file *file) -{ - struct drm_i915_file_private *file_priv; - struct drm_i915_gem_request *request; - - file_priv = file->driver_priv; - - /* Clean up our request list when the client is going away, so that - * later retire_requests won't dereference our soon-to-be-gone - * file_priv. - */ - mtx_lock(&file_priv->mm.lck); - while (!list_empty(&file_priv->mm.request_list)) { - request = list_first_entry(&file_priv->mm.request_list, - struct drm_i915_gem_request, - client_list); - list_del(&request->client_list); - request->file_priv = NULL; - } - mtx_unlock(&file_priv->mm.lck); -} - -static void -i915_gem_reset_ring_lists(struct drm_i915_private *dev_priv, - struct intel_ring_buffer *ring) +static void i915_gem_reset_ring_lists(struct drm_i915_private *dev_priv, + struct intel_ring_buffer *ring) { - if (ring->dev != NULL) DRM_LOCK_ASSERT(ring->dev); @@ -3498,7 +2201,8 @@ i915_gem_reset_ring_lists(struct drm_i915_private *dev_priv, struct drm_i915_gem_request *request; request = list_first_entry(&ring->request_list, - struct drm_i915_gem_request, list); + struct drm_i915_gem_request, + list); list_del(&request->list); i915_gem_request_remove_from_client(request); @@ -3509,7 +2213,8 @@ i915_gem_reset_ring_lists(struct drm_i915_private *dev_priv, struct drm_i915_gem_object *obj; obj = list_first_entry(&ring->active_list, - struct drm_i915_gem_object, ring_list); + struct drm_i915_gem_object, + ring_list); obj->base.write_domain = 0; list_del_init(&obj->gpu_write_list); @@ -3517,8 +2222,7 @@ i915_gem_reset_ring_lists(struct drm_i915_private *dev_priv, } } -static void -i915_gem_reset_fences(struct drm_device *dev) +static void i915_gem_reset_fences(struct drm_device *dev) { struct drm_i915_private *dev_priv = dev->dev_private; int i; @@ -3539,8 +2243,7 @@ i915_gem_reset_fences(struct drm_device *dev) INIT_LIST_HEAD(&dev_priv->mm.fence_list); } -void -i915_gem_reset(struct drm_device *dev) +void i915_gem_reset(struct drm_device *dev) { struct drm_i915_private *dev_priv = dev->dev_private; struct drm_i915_gem_object *obj; @@ -3653,6 +2356,270 @@ i915_gem_retire_requests(struct drm_device *dev) i915_gem_retire_requests_ring(ring); } +static void +i915_gem_process_flushing_list(struct intel_ring_buffer *ring, + uint32_t flush_domains) +{ + struct drm_i915_gem_object *obj, *next; + uint32_t old_write_domain; + + list_for_each_entry_safe(obj, next, &ring->gpu_write_list, + gpu_write_list) { + if (obj->base.write_domain & flush_domains) { + old_write_domain = obj->base.write_domain; + obj->base.write_domain = 0; + list_del_init(&obj->gpu_write_list); + i915_gem_object_move_to_active(obj, ring, + i915_gem_next_request_seqno(ring)); + + CTR3(KTR_DRM, "object_change_domain process_flush %p %x %x", + obj, obj->base.read_domains, old_write_domain); + } + } +} + +int +i915_gem_flush_ring(struct intel_ring_buffer *ring, uint32_t invalidate_domains, + uint32_t flush_domains) +{ + int ret; + + if (((invalidate_domains | flush_domains) & I915_GEM_GPU_DOMAINS) == 0) + return 0; + + CTR3(KTR_DRM, "ring_flush %s %x %x", ring->name, invalidate_domains, + flush_domains); + ret = ring->flush(ring, invalidate_domains, flush_domains); + if (ret) + return ret; + + if (flush_domains & I915_GEM_GPU_DOMAINS) + i915_gem_process_flushing_list(ring, flush_domains); + return 0; +} + +static void +i915_gem_retire_task_handler(void *arg, int pending) +{ + drm_i915_private_t *dev_priv; + struct drm_device *dev; + struct intel_ring_buffer *ring; + bool idle; + int i; + + dev_priv = arg; + dev = dev_priv->dev; + + /* Come back later if the device is busy... */ + if (!sx_try_xlock(&dev->dev_struct_lock)) { + taskqueue_enqueue_timeout(dev_priv->tq, + &dev_priv->mm.retire_task, hz); + return; + } + + CTR0(KTR_DRM, "retire_task"); + + i915_gem_retire_requests(dev); + + /* Send a periodic flush down the ring so we don't hold onto GEM + * objects indefinitely. + */ + idle = true; + for_each_ring(ring, dev_priv, i) { + struct intel_ring_buffer *ring = &dev_priv->rings[i]; + + if (!list_empty(&ring->gpu_write_list)) { + struct drm_i915_gem_request *request; + int ret; + + ret = i915_gem_flush_ring(ring, + 0, I915_GEM_GPU_DOMAINS); + request = malloc(sizeof(*request), DRM_I915_GEM, + M_WAITOK | M_ZERO); + if (ret || request == NULL || + i915_add_request(ring, NULL, request)) + free(request, DRM_I915_GEM); + } + + idle &= list_empty(&ring->request_list); + } + + if (!dev_priv->mm.suspended && !idle) + taskqueue_enqueue_timeout(dev_priv->tq, + &dev_priv->mm.retire_task, hz); + + DRM_UNLOCK(dev); +} + +int +i915_gem_object_sync(struct drm_i915_gem_object *obj, + struct intel_ring_buffer *to) +{ + struct intel_ring_buffer *from = obj->ring; + u32 seqno; + int ret, idx; + + if (from == NULL || to == from) + return 0; + + if (to == NULL || !i915_semaphore_is_enabled(obj->base.dev)) + return i915_gem_object_wait_rendering(obj); + + idx = intel_ring_sync_index(from, to); + + seqno = obj->last_rendering_seqno; + if (seqno <= from->sync_seqno[idx]) + return 0; + + if (seqno == from->outstanding_lazy_request) { + struct drm_i915_gem_request *request; + + request = malloc(sizeof(*request), DRM_I915_GEM, + M_WAITOK | M_ZERO); + ret = i915_add_request(from, NULL, request); + if (ret) { + free(request, DRM_I915_GEM); + return ret; + } + seqno = request->seqno; + } + + + ret = to->sync_to(to, from, seqno); + if (!ret) + from->sync_seqno[idx] = seqno; + + return ret; +} + +static void i915_gem_object_finish_gtt(struct drm_i915_gem_object *obj) +{ + u32 old_write_domain, old_read_domains; + + /* Act a barrier for all accesses through the GTT */ + mb(); + + /* Force a pagefault for domain tracking on next user access */ + i915_gem_release_mmap(obj); + + if ((obj->base.read_domains & I915_GEM_DOMAIN_GTT) == 0) + return; + + old_read_domains = obj->base.read_domains; + old_write_domain = obj->base.write_domain; + + obj->base.read_domains &= ~I915_GEM_DOMAIN_GTT; + obj->base.write_domain &= ~I915_GEM_DOMAIN_GTT; + + CTR3(KTR_DRM, "object_change_domain finish gtt %p %x %x", + obj, old_read_domains, old_write_domain); +} + +/** + * Unbinds an object from the GTT aperture. + */ +int +i915_gem_object_unbind(struct drm_i915_gem_object *obj) +{ + drm_i915_private_t *dev_priv = obj->base.dev->dev_private; + int ret = 0; + + if (obj->gtt_space == NULL) + return 0; + + if (obj->pin_count) { + DRM_ERROR("Attempting to unbind pinned buffer\n"); + return -EINVAL; + } + + ret = i915_gem_object_finish_gpu(obj); + if (ret == -ERESTARTSYS || ret == -EINTR) + return ret; + + i915_gem_object_finish_gtt(obj); + + if (ret == 0) + ret = i915_gem_object_set_to_cpu_domain(obj, 1); + if (ret == -ERESTARTSYS || ret == -EINTR) + return ret; + if (ret != 0) { + i915_gem_clflush_object(obj); + obj->base.read_domains = obj->base.write_domain = + I915_GEM_DOMAIN_CPU; + } + + /* release the fence reg _after_ flushing */ + ret = i915_gem_object_put_fence(obj); + if (ret) + return ret; + + if (obj->has_global_gtt_mapping) + i915_gem_gtt_unbind_object(obj); + if (obj->has_aliasing_ppgtt_mapping) { + i915_ppgtt_unbind_object(dev_priv->mm.aliasing_ppgtt, obj); + obj->has_aliasing_ppgtt_mapping = 0; + } + i915_gem_gtt_finish_object(obj); + + i915_gem_object_put_pages_gtt(obj); + + list_del_init(&obj->gtt_list); + list_del_init(&obj->mm_list); + obj->map_and_fenceable = true; + + drm_mm_put_block(obj->gtt_space); + obj->gtt_space = NULL; + obj->gtt_offset = 0; + + if (i915_gem_object_is_purgeable(obj)) + i915_gem_object_truncate(obj); + CTR1(KTR_DRM, "object_unbind %p", obj); + + return ret; +} + +static int +i915_ring_idle(struct intel_ring_buffer *ring) +{ + int ret; + + if (list_empty(&ring->gpu_write_list) && list_empty(&ring->active_list)) + return 0; + + if (!list_empty(&ring->gpu_write_list)) { + ret = i915_gem_flush_ring(ring, I915_GEM_GPU_DOMAINS, + I915_GEM_GPU_DOMAINS); + if (ret != 0) + return ret; + } + + return (i915_wait_request(ring, i915_gem_next_request_seqno(ring))); +} + +int i915_gpu_idle(struct drm_device *dev) +{ + drm_i915_private_t *dev_priv = dev->dev_private; + struct intel_ring_buffer *ring; + int ret, i; + + /* Flush everything onto the inactive list. */ + for_each_ring(ring, dev_priv, i) { + ret = i915_switch_context(ring, NULL, DEFAULT_CONTEXT_ID); + if (ret) + return ret; + + ret = i915_ring_idle(ring); + if (ret) + return ret; + + /* Is the device fubar? */ + if (!list_empty(&ring->gpu_write_list)) + return -EBUSY; + } + + return 0; +} + static void sandybridge_write_fence_reg(struct drm_device *dev, int reg, struct drm_i915_gem_object *obj) { @@ -3803,13 +2770,14 @@ static void i915_gem_object_update_fence(struct drm_i915_gem_object *obj, struct drm_i915_fence_reg *fence, bool enable) { - struct drm_i915_private *dev_priv = obj->base.dev->dev_private; - int reg = fence_number(dev_priv, fence); + struct drm_device *dev = obj->base.dev; + struct drm_i915_private *dev_priv = dev->dev_private; + int fence_reg = fence_number(dev_priv, fence); - i915_gem_write_fence(obj->base.dev, reg, enable ? obj : NULL); + i915_gem_write_fence(dev, fence_reg, enable ? obj : NULL); if (enable) { - obj->fence_reg = reg; + obj->fence_reg = fence_reg; fence->obj = obj; list_move_tail(&fence->lru_list, &dev_priv->mm.fence_list); } else { @@ -3906,6 +2874,20 @@ i915_find_fence_reg(struct drm_device *dev) return NULL; } +/** + * i915_gem_object_get_fence - set up fencing for an object + * @obj: object to map through a fence reg + * + * When mapping objects through the GTT, userspace wants to be able to write + * to them without having to worry about swizzling if the object is tiled. + * This function walks the fence regs looking for a free one for @obj, + * stealing one if it can't find any. + * + * It then sets up the reg based on the object's properties: address, pitch + * and tiling format. + * + * For an untiled surface, this removes any existing fence. + */ int i915_gem_object_get_fence(struct drm_i915_gem_object *obj) { @@ -3924,8 +2906,7 @@ i915_gem_object_get_fence(struct drm_i915_gem_object *obj) return ret; } - ret = 0; - + /* Just update our place in the LRU if our fence is getting reused. */ if (obj->fence_reg != I915_FENCE_REG_NONE) { reg = &dev_priv->fence_regs[obj->fence_reg]; if (!obj->fence_dirty) { @@ -3956,72 +2937,1102 @@ i915_gem_object_get_fence(struct drm_i915_gem_object *obj) return 0; } -int -i915_gem_init_object(struct drm_gem_object *obj) +/** + * Finds free space in the GTT aperture and binds the object there. + */ +static int +i915_gem_object_bind_to_gtt(struct drm_i915_gem_object *obj, + unsigned alignment, + bool map_and_fenceable) { + struct drm_device *dev = obj->base.dev; + drm_i915_private_t *dev_priv = dev->dev_private; + struct drm_mm_node *free_space; + u32 size, fence_size, fence_alignment, unfenced_alignment; + bool mappable, fenceable; + int ret; - printf("i915_gem_init_object called\n"); - return (0); + if (obj->madv != I915_MADV_WILLNEED) { + DRM_ERROR("Attempting to bind a purgeable object\n"); + return -EINVAL; + } + + fence_size = i915_gem_get_gtt_size(dev, + obj->base.size, + obj->tiling_mode); + fence_alignment = i915_gem_get_gtt_alignment(dev, + obj->base.size, + obj->tiling_mode); + unfenced_alignment = + i915_gem_get_unfenced_gtt_alignment(dev, + obj->base.size, + obj->tiling_mode); + + if (alignment == 0) + alignment = map_and_fenceable ? fence_alignment : + unfenced_alignment; + if (map_and_fenceable && alignment & (fence_alignment - 1)) { + DRM_ERROR("Invalid object alignment requested %u\n", alignment); + return -EINVAL; + } + + size = map_and_fenceable ? fence_size : obj->base.size; + + /* If the object is bigger than the entire aperture, reject it early + * before evicting everything in a vain attempt to find space. + */ + if (obj->base.size > + (map_and_fenceable ? dev_priv->mm.gtt_mappable_end : dev_priv->mm.gtt_total)) { + DRM_ERROR("Attempting to bind an object larger than the aperture\n"); + return -E2BIG; + } + + search_free: + if (map_and_fenceable) + free_space = drm_mm_search_free_in_range( + &dev_priv->mm.gtt_space, size, alignment, 0, + dev_priv->mm.gtt_mappable_end, 0); + else + free_space = drm_mm_search_free(&dev_priv->mm.gtt_space, + size, alignment, 0); + if (free_space != NULL) { + if (map_and_fenceable) + obj->gtt_space = drm_mm_get_block_range_generic( + free_space, size, alignment, 0, 0, + dev_priv->mm.gtt_mappable_end, 1); + else + obj->gtt_space = drm_mm_get_block_generic(free_space, + size, alignment, 0, 1); + } + if (obj->gtt_space == NULL) { + ret = i915_gem_evict_something(dev, size, alignment, + map_and_fenceable); + if (ret != 0) + return ret; + goto search_free; + } + ret = i915_gem_object_get_pages_gtt(obj, 0); + if (ret) { + drm_mm_put_block(obj->gtt_space); + obj->gtt_space = NULL; + /* + * i915_gem_object_get_pages_gtt() cannot return + * ENOMEM, since we use vm_page_grab(). + */ + return ret; + } + + ret = i915_gem_gtt_prepare_object(obj); + if (ret) { + i915_gem_object_put_pages_gtt(obj); + drm_mm_put_block(obj->gtt_space); + obj->gtt_space = NULL; + if (i915_gem_evict_everything(dev, false)) + return ret; + goto search_free; + } + + if (!dev_priv->mm.aliasing_ppgtt) + i915_gem_gtt_bind_object(obj, obj->cache_level); + + list_add_tail(&obj->gtt_list, &dev_priv->mm.gtt_list); + list_add_tail(&obj->mm_list, &dev_priv->mm.inactive_list); + + KASSERT((obj->base.read_domains & I915_GEM_GPU_DOMAINS) == 0, + ("Object in gpu read domain")); + KASSERT((obj->base.write_domain & I915_GEM_GPU_DOMAINS) == 0, + ("Object in gpu write domain")); + + obj->gtt_offset = obj->gtt_space->start; + + fenceable = + obj->gtt_space->size == fence_size && + (obj->gtt_space->start & (fence_alignment - 1)) == 0; + + mappable = + obj->gtt_offset + obj->base.size <= dev_priv->mm.gtt_mappable_end; + + obj->map_and_fenceable = mappable && fenceable; + + CTR4(KTR_DRM, "object_bind %p %x %x %d", obj, obj->gtt_offset, + obj->base.size, map_and_fenceable); + return 0; } -static bool -i915_gem_object_is_inactive(struct drm_i915_gem_object *obj) +void +i915_gem_clflush_object(struct drm_i915_gem_object *obj) { + /* If we don't have a page list set up, then we're not pinned + * to GPU, and we can ignore the cache flush because it'll happen + * again at bind time. + */ + if (obj->pages == NULL) + return; - return !obj->active; + /* If the GPU is snooping the contents of the CPU cache, + * we do not need to manually clear the CPU cache lines. However, + * the caches are only snooped when the render cache is + * flushed/invalidated. As we always have to emit invalidations + * and flushes when moving into and out of the RENDER domain, correct + * snooping behaviour occurs naturally as the result of our domain + * tracking. + */ + if (obj->cache_level != I915_CACHE_NONE) + return; + + CTR1(KTR_DRM, "object_clflush %p", obj); + + drm_clflush_pages(obj->pages, obj->base.size / PAGE_SIZE); } +/** Flushes the GTT write domain for the object if it's dirty. */ static void -i915_gem_retire_task_handler(void *arg, int pending) +i915_gem_object_flush_gtt_write_domain(struct drm_i915_gem_object *obj) { - drm_i915_private_t *dev_priv; - struct drm_device *dev; - struct intel_ring_buffer *ring; - bool idle; - int i; + uint32_t old_write_domain; - dev_priv = arg; - dev = dev_priv->dev; + if (obj->base.write_domain != I915_GEM_DOMAIN_GTT) + return; - /* Come back later if the device is busy... */ - if (!sx_try_xlock(&dev->dev_struct_lock)) { - taskqueue_enqueue_timeout(dev_priv->tq, - &dev_priv->mm.retire_task, hz); + /* No actual flushing is required for the GTT write domain. Writes + * to it immediately go to main memory as far as we know, so there's + * no chipset flush. It also doesn't land in render cache. + * + * However, we do have to enforce the order so that all writes through + * the GTT land before any writes to the device, such as updates to + * the GATT itself. + */ + wmb(); + + old_write_domain = obj->base.write_domain; + obj->base.write_domain = 0; + + CTR3(KTR_DRM, "object_change_domain flush gtt_write %p %x %x", obj, + obj->base.read_domains, old_write_domain); +} + +/** Flushes the CPU write domain for the object if it's dirty. */ +static void +i915_gem_object_flush_cpu_write_domain(struct drm_i915_gem_object *obj) +{ + uint32_t old_write_domain; + + if (obj->base.write_domain != I915_GEM_DOMAIN_CPU) return; + + i915_gem_clflush_object(obj); + intel_gtt_chipset_flush(); + old_write_domain = obj->base.write_domain; + obj->base.write_domain = 0; + + CTR3(KTR_DRM, "object_change_domain flush_cpu_write %p %x %x", obj, + obj->base.read_domains, old_write_domain); +} + +static int +i915_gem_object_flush_gpu_write_domain(struct drm_i915_gem_object *obj) +{ + + if ((obj->base.write_domain & I915_GEM_GPU_DOMAINS) == 0) + return (0); + return (i915_gem_flush_ring(obj->ring, 0, obj->base.write_domain)); +} + +/** + * Moves a single object to the GTT read, and possibly write domain. + * + * This function returns when the move is complete, including waiting on + * flushes to occur. + */ +int +i915_gem_object_set_to_gtt_domain(struct drm_i915_gem_object *obj, bool write) +{ + drm_i915_private_t *dev_priv = obj->base.dev->dev_private; + uint32_t old_write_domain, old_read_domains; + int ret; + + /* Not valid to be called on unbound objects. */ + if (obj->gtt_space == NULL) + return -EINVAL; + + if (obj->base.write_domain == I915_GEM_DOMAIN_GTT) + return 0; + + ret = i915_gem_object_flush_gpu_write_domain(obj); + if (ret) + return ret; + + if (obj->pending_gpu_write || write) { + ret = i915_gem_object_wait_rendering(obj); + if (ret) + return (ret); } - CTR0(KTR_DRM, "retire_task"); + i915_gem_object_flush_cpu_write_domain(obj); - i915_gem_retire_requests(dev); + old_write_domain = obj->base.write_domain; + old_read_domains = obj->base.read_domains; - /* Send a periodic flush down the ring so we don't hold onto GEM - * objects indefinitely. + /* It should now be out of any other write domains, and we can update + * the domain values for our changes. */ - idle = true; - for_each_ring(ring, dev_priv, i) { - struct intel_ring_buffer *ring = &dev_priv->rings[i]; + KASSERT((obj->base.write_domain & ~I915_GEM_DOMAIN_GTT) == 0, + ("In GTT write domain")); + obj->base.read_domains |= I915_GEM_DOMAIN_GTT; + if (write) { + obj->base.read_domains = I915_GEM_DOMAIN_GTT; + obj->base.write_domain = I915_GEM_DOMAIN_GTT; + obj->dirty = 1; + } - if (!list_empty(&ring->gpu_write_list)) { - struct drm_i915_gem_request *request; - int ret; + CTR3(KTR_DRM, "object_change_domain set_to_gtt %p %x %x", obj, + old_read_domains, old_write_domain); - ret = i915_gem_flush_ring(ring, - 0, I915_GEM_GPU_DOMAINS); - request = malloc(sizeof(*request), DRM_I915_GEM, - M_WAITOK | M_ZERO); - if (ret || request == NULL || - i915_add_request(ring, NULL, request)) - free(request, DRM_I915_GEM); + /* And bump the LRU for this access */ + if (i915_gem_object_is_inactive(obj)) + list_move_tail(&obj->mm_list, &dev_priv->mm.inactive_list); + + return 0; +} + +int i915_gem_object_set_cache_level(struct drm_i915_gem_object *obj, + enum i915_cache_level cache_level) +{ + struct drm_device *dev = obj->base.dev; + drm_i915_private_t *dev_priv = dev->dev_private; + int ret; + + if (obj->cache_level == cache_level) + return 0; + + if (obj->pin_count) { + DRM_DEBUG("can not change the cache level of pinned objects\n"); + return -EBUSY; + } + + if (obj->gtt_space) { + ret = i915_gem_object_finish_gpu(obj); + if (ret) + return ret; + + i915_gem_object_finish_gtt(obj); + + /* Before SandyBridge, you could not use tiling or fence + * registers with snooped memory, so relinquish any fences + * currently pointing to our region in the aperture. + */ + if (INTEL_INFO(obj->base.dev)->gen < 6) { + ret = i915_gem_object_put_fence(obj); + if (ret) + return ret; } - idle &= list_empty(&ring->request_list); + if (obj->has_global_gtt_mapping) + i915_gem_gtt_bind_object(obj, cache_level); + if (obj->has_aliasing_ppgtt_mapping) + i915_ppgtt_bind_object(dev_priv->mm.aliasing_ppgtt, + obj, cache_level); } - if (!dev_priv->mm.suspended && !idle) + if (cache_level == I915_CACHE_NONE) { + u32 old_read_domains, old_write_domain; + + /* If we're coming from LLC cached, then we haven't + * actually been tracking whether the data is in the + * CPU cache or not, since we only allow one bit set + * in obj->write_domain and have been skipping the clflushes. + * Just set it to the CPU cache for now. + */ + KASSERT((obj->base.write_domain & ~I915_GEM_DOMAIN_CPU) == 0, + ("obj %p in CPU write domain", obj)); + KASSERT((obj->base.read_domains & ~I915_GEM_DOMAIN_CPU) == 0, + ("obj %p in CPU read domain", obj)); + + old_read_domains = obj->base.read_domains; + old_write_domain = obj->base.write_domain; + + obj->base.read_domains = I915_GEM_DOMAIN_CPU; + obj->base.write_domain = I915_GEM_DOMAIN_CPU; + + CTR3(KTR_DRM, "object_change_domain set_cache_level %p %x %x", + obj, old_read_domains, old_write_domain); + } + + obj->cache_level = cache_level; + return 0; +} + +static bool is_pin_display(struct drm_i915_gem_object *obj) +{ + /* There are 3 sources that pin objects: + * 1. The display engine (scanouts, sprites, cursors); + * 2. Reservations for execbuffer; + * 3. The user. + * + * We can ignore reservations as we hold the struct_mutex and + * are only called outside of the reservation path. The user + * can only increment pin_count once, and so if after + * subtracting the potential reference by the user, any pin_count + * remains, it must be due to another use by the display engine. + */ + return obj->pin_count - !!obj->user_pin_count; +} + +int +i915_gem_object_pin_to_display_plane(struct drm_i915_gem_object *obj, + u32 alignment, + struct intel_ring_buffer *pipelined) +{ + u32 old_read_domains, old_write_domain; + int ret; + + ret = i915_gem_object_flush_gpu_write_domain(obj); + if (ret) + return ret; + + if (pipelined != obj->ring) { + ret = i915_gem_object_sync(obj, pipelined); + if (ret) + return ret; + } + + /* Mark the pin_display early so that we account for the + * display coherency whilst setting up the cache domains. + */ + obj->pin_display = true; + + /* The display engine is not coherent with the LLC cache on gen6. As + * a result, we make sure that the pinning that is about to occur is + * done with uncached PTEs. This is lowest common denominator for all + * chipsets. + * + * However for gen6+, we could do better by using the GFDT bit instead + * of uncaching, which would allow us to flush all the LLC-cached data + * with that bit in the PTE to main memory with just one PIPE_CONTROL. + */ + ret = i915_gem_object_set_cache_level(obj, I915_CACHE_NONE); + if (ret) + goto err_unpin_display; + + /* As the user may map the buffer once pinned in the display plane + * (e.g. libkms for the bootup splash), we have to ensure that we + * always use map_and_fenceable for all scanout buffers. + */ + ret = i915_gem_object_pin(obj, alignment, true); + if (ret) + goto err_unpin_display; + + i915_gem_object_flush_cpu_write_domain(obj); + + old_write_domain = obj->base.write_domain; + old_read_domains = obj->base.read_domains; + + KASSERT((obj->base.write_domain & ~I915_GEM_DOMAIN_GTT) == 0, + ("obj %p in GTT write domain", obj)); + obj->base.read_domains |= I915_GEM_DOMAIN_GTT; + + CTR3(KTR_DRM, "object_change_domain pin_to_display_plan %p %x %x", + obj, old_read_domains, obj->base.write_domain); + + return 0; + +err_unpin_display: + obj->pin_display = is_pin_display(obj); + return ret; +} + +void +i915_gem_object_unpin_from_display_plane(struct drm_i915_gem_object *obj) +{ + i915_gem_object_unpin(obj); + obj->pin_display = is_pin_display(obj); +} + +int +i915_gem_object_finish_gpu(struct drm_i915_gem_object *obj) +{ + int ret; + + if ((obj->base.read_domains & I915_GEM_GPU_DOMAINS) == 0) + return 0; + + if (obj->base.write_domain & I915_GEM_GPU_DOMAINS) { + ret = i915_gem_flush_ring(obj->ring, 0, obj->base.write_domain); + if (ret) + return ret; + } + + ret = i915_gem_object_wait_rendering(obj); + if (ret) + return ret; + + /* Ensure that we invalidate the GPU's caches and TLBs. */ + obj->base.read_domains &= ~I915_GEM_GPU_DOMAINS; + return 0; +} + +/** + * Moves a single object to the CPU read, and possibly write domain. + * + * This function returns when the move is complete, including waiting on + * flushes to occur. + */ +int +i915_gem_object_set_to_cpu_domain(struct drm_i915_gem_object *obj, bool write) +{ + uint32_t old_write_domain, old_read_domains; + int ret; + + if (obj->base.write_domain == I915_GEM_DOMAIN_CPU) + return 0; + + ret = i915_gem_object_flush_gpu_write_domain(obj); + if (ret) + return ret; + + if (write || obj->pending_gpu_write) { + ret = i915_gem_object_wait_rendering(obj); + if (ret) + return ret; + } + + i915_gem_object_flush_gtt_write_domain(obj); + + old_write_domain = obj->base.write_domain; + old_read_domains = obj->base.read_domains; + + /* Flush the CPU cache if it's still invalid. */ + if ((obj->base.read_domains & I915_GEM_DOMAIN_CPU) == 0) { + i915_gem_clflush_object(obj); + + obj->base.read_domains |= I915_GEM_DOMAIN_CPU; + } + + /* It should now be out of any other write domains, and we can update + * the domain values for our changes. + */ + KASSERT((obj->base.write_domain & ~I915_GEM_DOMAIN_CPU) == 0, + ("In cpu write domain")); + + /* If we're writing through the CPU, then the GPU read domains will + * need to be invalidated at next use. + */ + if (write) { + obj->base.read_domains = I915_GEM_DOMAIN_CPU; + obj->base.write_domain = I915_GEM_DOMAIN_CPU; + } + + CTR3(KTR_DRM, "object_change_domain set_to_cpu %p %x %x", obj, + old_read_domains, old_write_domain); + + return 0; +} + +/* Throttle our rendering by waiting until the ring has completed our requests + * emitted over 20 msec ago. + * + * Note that if we were to use the current jiffies each time around the loop, + * we wouldn't escape the function with any frames outstanding if the time to + * render a frame was over 20ms. + * + * This should get us reasonable parallelism between CPU and GPU but also + * relatively low latency when blocking on a particular request to finish. + */ +static int +i915_gem_ring_throttle(struct drm_device *dev, struct drm_file *file) +{ + struct drm_i915_private *dev_priv = dev->dev_private; + struct drm_i915_file_private *file_priv = file->driver_priv; + unsigned long recent_enough = ticks - (20 * hz / 1000); + struct drm_i915_gem_request *request; + struct intel_ring_buffer *ring = NULL; + u32 seqno = 0; + int ret; + + if (atomic_load_acq_int(&dev_priv->mm.wedged)) + return -EIO; + + mtx_lock(&file_priv->mm.lck); + list_for_each_entry(request, &file_priv->mm.request_list, client_list) { + if (time_after_eq(request->emitted_jiffies, recent_enough)) + break; + ring = request->ring; + seqno = request->seqno; + } + mtx_unlock(&file_priv->mm.lck); + if (seqno == 0) + return 0; + + ret = __wait_seqno(ring, seqno, true); + if (ret == 0) taskqueue_enqueue_timeout(dev_priv->tq, - &dev_priv->mm.retire_task, hz); + &dev_priv->mm.retire_task, 0); + + return ret; +} + +int +i915_gem_object_pin(struct drm_i915_gem_object *obj, + uint32_t alignment, + bool map_and_fenceable) +{ + int ret; + + if (obj->pin_count == DRM_I915_GEM_OBJECT_MAX_PIN_COUNT) + return -EBUSY; + + if (obj->gtt_space != NULL) { + if ((alignment && obj->gtt_offset & (alignment - 1)) || + (map_and_fenceable && !obj->map_and_fenceable)) { + DRM_DEBUG("bo is already pinned with incorrect alignment:" + " offset=%x, req.alignment=%x, req.map_and_fenceable=%d," + " obj->map_and_fenceable=%d\n", + obj->gtt_offset, alignment, + map_and_fenceable, + obj->map_and_fenceable); + ret = i915_gem_object_unbind(obj); + if (ret) + return ret; + } + } + + if (obj->gtt_space == NULL) { + ret = i915_gem_object_bind_to_gtt(obj, alignment, + map_and_fenceable); + if (ret) + return ret; + } + + if (!obj->has_global_gtt_mapping && map_and_fenceable) + i915_gem_gtt_bind_object(obj, obj->cache_level); + + obj->pin_count++; + obj->pin_mappable |= map_and_fenceable; + + return 0; +} + +void +i915_gem_object_unpin(struct drm_i915_gem_object *obj) +{ + + KASSERT(obj->pin_count != 0, ("zero pin count")); + KASSERT(obj->gtt_space != NULL, ("No gtt mapping")); + + if (--obj->pin_count == 0) + obj->pin_mappable = false; +} + +int +i915_gem_pin_ioctl(struct drm_device *dev, void *data, + struct drm_file *file) +{ + struct drm_i915_gem_pin *args = data; + struct drm_i915_gem_object *obj; + struct drm_gem_object *gobj; + int ret; + + ret = i915_mutex_lock_interruptible(dev); + if (ret) + return ret; + + gobj = drm_gem_object_lookup(dev, file, args->handle); + if (gobj == NULL) { + ret = -ENOENT; + goto unlock; + } + obj = to_intel_bo(gobj); + + if (obj->madv != I915_MADV_WILLNEED) { + DRM_ERROR("Attempting to pin a purgeable buffer\n"); + ret = -EINVAL; + goto out; + } + + if (obj->pin_filp != NULL && obj->pin_filp != file) { + DRM_ERROR("Already pinned in i915_gem_pin_ioctl(): %d\n", + args->handle); + ret = -EINVAL; + goto out; + } + + obj->user_pin_count++; + obj->pin_filp = file; + if (obj->user_pin_count == 1) { + ret = i915_gem_object_pin(obj, args->alignment, true); + if (ret) + goto out; + } + + /* XXX - flush the CPU caches for pinned objects + * as the X server doesn't manage domains yet + */ + i915_gem_object_flush_cpu_write_domain(obj); + args->offset = obj->gtt_offset; +out: + drm_gem_object_unreference(&obj->base); +unlock: + DRM_UNLOCK(dev); + return ret; +} + +int +i915_gem_unpin_ioctl(struct drm_device *dev, void *data, + struct drm_file *file) +{ + struct drm_i915_gem_pin *args = data; + struct drm_i915_gem_object *obj; + int ret; + + ret = i915_mutex_lock_interruptible(dev); + if (ret) + return ret; + + obj = to_intel_bo(drm_gem_object_lookup(dev, file, args->handle)); + if (&obj->base == NULL) { + ret = -ENOENT; + goto unlock; + } + + if (obj->pin_filp != file) { + DRM_ERROR("Not pinned by caller in i915_gem_pin_ioctl(): %d\n", + args->handle); + ret = -EINVAL; + goto out; + } + obj->user_pin_count--; + if (obj->user_pin_count == 0) { + obj->pin_filp = NULL; + i915_gem_object_unpin(obj); + } + +out: + drm_gem_object_unreference(&obj->base); +unlock: + DRM_UNLOCK(dev); + return ret; +} + +int +i915_gem_busy_ioctl(struct drm_device *dev, void *data, + struct drm_file *file) +{ + struct drm_i915_gem_busy *args = data; + struct drm_i915_gem_object *obj; + int ret; + + ret = i915_mutex_lock_interruptible(dev); + if (ret) + return ret; + + obj = to_intel_bo(drm_gem_object_lookup(dev, file, args->handle)); + if (&obj->base == NULL) { + ret = -ENOENT; + goto unlock; + } + + args->busy = obj->active; + if (args->busy) { + if (obj->base.write_domain & I915_GEM_GPU_DOMAINS) { + ret = i915_gem_flush_ring(obj->ring, + 0, obj->base.write_domain); + } else { + ret = i915_gem_check_olr(obj->ring, + obj->last_rendering_seqno); + } + + i915_gem_retire_requests_ring(obj->ring); + args->busy = obj->active; + } + + drm_gem_object_unreference(&obj->base); +unlock: + DRM_UNLOCK(dev); + return ret; +} + +int +i915_gem_throttle_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + return i915_gem_ring_throttle(dev, file_priv); +} + +int +i915_gem_madvise_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_i915_gem_madvise *args = data; + struct drm_i915_gem_object *obj; + int ret; + + switch (args->madv) { + case I915_MADV_DONTNEED: + case I915_MADV_WILLNEED: + break; + default: + return -EINVAL; + } + + ret = i915_mutex_lock_interruptible(dev); + if (ret) + return ret; + + obj = to_intel_bo(drm_gem_object_lookup(dev, file_priv, args->handle)); + if (&obj->base == NULL) { + ret = -ENOENT; + goto unlock; + } + + if (obj->pin_count) { + ret = -EINVAL; + goto out; + } + + if (obj->madv != I915_MADV_PURGED_INTERNAL) + obj->madv = args->madv; + + /* if the object is no longer attached, discard its backing storage */ + if (i915_gem_object_is_purgeable(obj) && obj->gtt_space == NULL) + i915_gem_object_truncate(obj); + + args->retained = obj->madv != I915_MADV_PURGED_INTERNAL; +out: + drm_gem_object_unreference(&obj->base); +unlock: DRM_UNLOCK(dev); + return ret; +} + +struct drm_i915_gem_object *i915_gem_alloc_object(struct drm_device *dev, + size_t size) +{ + struct drm_i915_private *dev_priv; + struct drm_i915_gem_object *obj; + + dev_priv = dev->dev_private; + + obj = malloc(sizeof(*obj), DRM_I915_GEM, M_WAITOK | M_ZERO); + + if (drm_gem_object_init(dev, &obj->base, size) != 0) { + free(obj, DRM_I915_GEM); + return NULL; + } + + obj->base.write_domain = I915_GEM_DOMAIN_CPU; + obj->base.read_domains = I915_GEM_DOMAIN_CPU; + + if (HAS_LLC(dev)) { + /* On some devices, we can have the GPU use the LLC (the CPU + * cache) for about a 10% performance improvement + * compared to uncached. Graphics requests other than + * display scanout are coherent with the CPU in + * accessing this cache. This means in this mode we + * don't need to clflush on the CPU side, and on the + * GPU side we only need to flush internal caches to + * get data visible to the CPU. + * + * However, we maintain the display planes as UC, and so + * need to rebind when first used as such. + */ + obj->cache_level = I915_CACHE_LLC; + } else + obj->cache_level = I915_CACHE_NONE; + obj->base.driver_private = NULL; + obj->fence_reg = I915_FENCE_REG_NONE; + INIT_LIST_HEAD(&obj->mm_list); + INIT_LIST_HEAD(&obj->gtt_list); + INIT_LIST_HEAD(&obj->ring_list); + INIT_LIST_HEAD(&obj->exec_list); + INIT_LIST_HEAD(&obj->gpu_write_list); + obj->madv = I915_MADV_WILLNEED; + /* Avoid an unnecessary call to unbind on the first bind. */ + obj->map_and_fenceable = true; + + i915_gem_info_add_obj(dev_priv, size); + + return obj; +} + +int i915_gem_init_object(struct drm_gem_object *obj) +{ + printf("i915_gem_init_object called\n"); + + return 0; +} + +void i915_gem_free_object(struct drm_gem_object *gem_obj) +{ + struct drm_i915_gem_object *obj = to_intel_bo(gem_obj); + struct drm_device *dev = obj->base.dev; + drm_i915_private_t *dev_priv = dev->dev_private; + + CTR1(KTR_DRM, "object_destroy_tail %p", obj); + + if (obj->phys_obj) + i915_gem_detach_phys_object(dev, obj); + + obj->pin_count = 0; + if (i915_gem_object_unbind(obj) == -ERESTARTSYS) { + bool was_interruptible; + + was_interruptible = dev_priv->mm.interruptible; + dev_priv->mm.interruptible = false; + + if (i915_gem_object_unbind(obj)) + printf("i915_gem_free_object: unbind\n"); + + dev_priv->mm.interruptible = was_interruptible; + } + + drm_gem_free_mmap_offset(&obj->base); + drm_gem_object_release(&obj->base); + i915_gem_info_remove_obj(dev_priv, obj->base.size); + + free(obj->bit_17, DRM_I915_GEM); + free(obj, DRM_I915_GEM); +} + +int +i915_gem_idle(struct drm_device *dev) +{ + drm_i915_private_t *dev_priv = dev->dev_private; + int ret; + + DRM_LOCK(dev); + + if (dev_priv->mm.suspended) { + DRM_UNLOCK(dev); + return 0; + } + + ret = i915_gpu_idle(dev); + if (ret) { + DRM_UNLOCK(dev); + return ret; + } + i915_gem_retire_requests(dev); + + /* Under UMS, be paranoid and evict. */ + if (!drm_core_check_feature(dev, DRIVER_MODESET)) { + ret = i915_gem_evict_everything(dev, false); + if (ret) { + DRM_UNLOCK(dev); + return ret; + } + } + + i915_gem_reset_fences(dev); + + /* Hack! Don't let anybody do execbuf while we don't control the chip. + * We need to replace this with a semaphore, or something. + * And not confound mm.suspended! + */ + dev_priv->mm.suspended = 1; + callout_stop(&dev_priv->hangcheck_timer); + + i915_kernel_lost_context(dev); + i915_gem_cleanup_ringbuffer(dev); + + DRM_UNLOCK(dev); + + /* Cancel the retire work handler, which should be idle now. */ + taskqueue_cancel_timeout(dev_priv->tq, &dev_priv->mm.retire_task, NULL); + + return ret; +} + +void i915_gem_init_swizzling(struct drm_device *dev) +{ + drm_i915_private_t *dev_priv = dev->dev_private; + + if (INTEL_INFO(dev)->gen < 5 || + dev_priv->mm.bit_6_swizzle_x == I915_BIT_6_SWIZZLE_NONE) + return; + + I915_WRITE(DISP_ARB_CTL, I915_READ(DISP_ARB_CTL) | + DISP_TILE_SURFACE_SWIZZLING); + + if (IS_GEN5(dev)) + return; + + I915_WRITE(TILECTL, I915_READ(TILECTL) | TILECTL_SWZCTL); + if (IS_GEN6(dev)) + I915_WRITE(ARB_MODE, _MASKED_BIT_ENABLE(ARB_MODE_SWIZZLE_SNB)); + else + I915_WRITE(ARB_MODE, _MASKED_BIT_ENABLE(ARB_MODE_SWIZZLE_IVB)); +} + +int +i915_gem_init_hw(struct drm_device *dev) +{ + drm_i915_private_t *dev_priv = dev->dev_private; + int ret; + + i915_gem_init_swizzling(dev); + + ret = intel_init_render_ring_buffer(dev); + if (ret) + return ret; + + if (HAS_BSD(dev)) { + ret = intel_init_bsd_ring_buffer(dev); + if (ret) + goto cleanup_render_ring; + } + + if (HAS_BLT(dev)) { + ret = intel_init_blt_ring_buffer(dev); + if (ret) + goto cleanup_bsd_ring; + } + + dev_priv->next_seqno = 1; + + /* + * XXX: There was some w/a described somewhere suggesting loading + * contexts before PPGTT. + */ + i915_gem_context_init(dev); + i915_gem_init_ppgtt(dev); + + return 0; + +cleanup_bsd_ring: + intel_cleanup_ring_buffer(&dev_priv->rings[VCS]); +cleanup_render_ring: + intel_cleanup_ring_buffer(&dev_priv->rings[RCS]); + return ret; +} + +static bool +intel_enable_ppgtt(struct drm_device *dev) +{ + if (i915_enable_ppgtt >= 0) + return i915_enable_ppgtt; + + /* Disable ppgtt on SNB if VT-d is on. */ + if (INTEL_INFO(dev)->gen == 6 && intel_iommu_enabled) + return false; + + return true; +} + +int i915_gem_init(struct drm_device *dev) +{ + struct drm_i915_private *dev_priv = dev->dev_private; + unsigned long gtt_size, mappable_size; + int ret; + + gtt_size = dev_priv->mm.gtt.gtt_total_entries << PAGE_SHIFT; + mappable_size = dev_priv->mm.gtt.gtt_mappable_entries << PAGE_SHIFT; + + DRM_LOCK(dev); + if (intel_enable_ppgtt(dev) && HAS_ALIASING_PPGTT(dev)) { + /* PPGTT pdes are stolen from global gtt ptes, so shrink the + * aperture accordingly when using aliasing ppgtt. */ + gtt_size -= I915_PPGTT_PD_ENTRIES*PAGE_SIZE; + + i915_gem_init_global_gtt(dev, 0, mappable_size, gtt_size); + + ret = i915_gem_init_aliasing_ppgtt(dev); + if (ret) { + DRM_UNLOCK(dev); + return ret; + } + } else { + /* Let GEM Manage all of the aperture. + * + * However, leave one page at the end still bound to the scratch + * page. There are a number of places where the hardware + * apparently prefetches past the end of the object, and we've + * seen multiple hangs with the GPU head pointer stuck in a + * batchbuffer bound at the last page of the aperture. One page + * should be enough to keep any prefetching inside of the + * aperture. + */ + i915_gem_init_global_gtt(dev, 0, mappable_size, + gtt_size); + } + + ret = i915_gem_init_hw(dev); + DRM_UNLOCK(dev); + if (ret) { + i915_gem_cleanup_aliasing_ppgtt(dev); + return ret; + } + + /* Allow hardware batchbuffers unless told otherwise, but not for KMS. */ + if (!drm_core_check_feature(dev, DRIVER_MODESET)) + dev_priv->dri1.allow_batchbuffer = 1; + return 0; +} + +void +i915_gem_cleanup_ringbuffer(struct drm_device *dev) +{ + drm_i915_private_t *dev_priv = dev->dev_private; + struct intel_ring_buffer *ring; + int i; + + for_each_ring(ring, dev_priv, i) + intel_cleanup_ring_buffer(ring); +} + +int +i915_gem_entervt_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + drm_i915_private_t *dev_priv = dev->dev_private; + int ret; + + if (drm_core_check_feature(dev, DRIVER_MODESET)) + return 0; + + if (atomic_load_acq_int(&dev_priv->mm.wedged) != 0) { + DRM_ERROR("Reenabling wedged hardware, good luck\n"); + atomic_store_rel_int(&dev_priv->mm.wedged, 0); + } + + DRM_LOCK(dev); + dev_priv->mm.suspended = 0; + + ret = i915_gem_init_hw(dev); + if (ret != 0) { + DRM_UNLOCK(dev); + return ret; + } + + KASSERT(list_empty(&dev_priv->mm.active_list), ("active list")); + KASSERT(list_empty(&dev_priv->mm.flushing_list), ("flushing list")); + KASSERT(list_empty(&dev_priv->mm.inactive_list), ("inactive list")); + DRM_UNLOCK(dev); + + ret = drm_irq_install(dev); + if (ret) + goto cleanup_ringbuffer; + + return 0; + +cleanup_ringbuffer: + DRM_LOCK(dev); + i915_gem_cleanup_ringbuffer(dev); + dev_priv->mm.suspended = 1; + DRM_UNLOCK(dev); + + return ret; +} + +int +i915_gem_leavevt_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + if (drm_core_check_feature(dev, DRIVER_MODESET)) + return 0; + + drm_irq_uninstall(dev); + return i915_gem_idle(dev); } void @@ -4033,64 +4044,127 @@ i915_gem_lastclose(struct drm_device *dev) return; ret = i915_gem_idle(dev); - if (ret != 0) + if (ret) DRM_ERROR("failed to idle hardware: %d\n", ret); } -static int -i915_gem_init_phys_object(struct drm_device *dev, int id, int size, int align) +static void +init_ring_lists(struct intel_ring_buffer *ring) { - drm_i915_private_t *dev_priv; + INIT_LIST_HEAD(&ring->active_list); + INIT_LIST_HEAD(&ring->request_list); + INIT_LIST_HEAD(&ring->gpu_write_list); +} + +void +i915_gem_load(struct drm_device *dev) +{ + int i; + drm_i915_private_t *dev_priv = dev->dev_private; + + INIT_LIST_HEAD(&dev_priv->mm.active_list); + INIT_LIST_HEAD(&dev_priv->mm.flushing_list); + INIT_LIST_HEAD(&dev_priv->mm.inactive_list); + INIT_LIST_HEAD(&dev_priv->mm.fence_list); + INIT_LIST_HEAD(&dev_priv->mm.gtt_list); + for (i = 0; i < I915_NUM_RINGS; i++) + init_ring_lists(&dev_priv->rings[i]); + for (i = 0; i < I915_MAX_NUM_FENCES; i++) + INIT_LIST_HEAD(&dev_priv->fence_regs[i].lru_list); + TIMEOUT_TASK_INIT(dev_priv->tq, &dev_priv->mm.retire_task, 0, + i915_gem_retire_task_handler, dev_priv); + dev_priv->error_completion = 0; + + /* On GEN3 we really need to make sure the ARB C3 LP bit is set */ + if (IS_GEN3(dev)) { + I915_WRITE(MI_ARB_STATE, + _MASKED_BIT_ENABLE(MI_ARB_C3_LP_WRITE_ENABLE)); + } + + dev_priv->relative_constants_mode = I915_EXEC_CONSTANTS_REL_GENERAL; + + /* Old X drivers will take 0-2 for front, back, depth buffers */ + if (!drm_core_check_feature(dev, DRIVER_MODESET)) + dev_priv->fence_reg_start = 3; + + if (INTEL_INFO(dev)->gen >= 4 || IS_I945G(dev) || IS_I945GM(dev) || IS_G33(dev)) + dev_priv->num_fence_regs = 16; + else + dev_priv->num_fence_regs = 8; + + /* Initialize fence registers to zero */ + i915_gem_reset_fences(dev); + + i915_gem_detect_bit_6_swizzle(dev); + dev_priv->mm.interruptible = true; + + dev_priv->mm.i915_lowmem = EVENTHANDLER_REGISTER(vm_lowmem, + i915_gem_lowmem, dev, EVENTHANDLER_PRI_ANY); +} + +void +i915_gem_unload(struct drm_device *dev) +{ + struct drm_i915_private *dev_priv; + + dev_priv = dev->dev_private; + EVENTHANDLER_DEREGISTER(vm_lowmem, dev_priv->mm.i915_lowmem); +} + +/* + * Create a physically contiguous memory object for this object + * e.g. for cursor + overlay regs + */ +static int i915_gem_init_phys_object(struct drm_device *dev, + int id, int size, int align) +{ + drm_i915_private_t *dev_priv = dev->dev_private; struct drm_i915_gem_phys_object *phys_obj; int ret; - dev_priv = dev->dev_private; - if (dev_priv->mm.phys_objs[id - 1] != NULL || size == 0) - return (0); + if (dev_priv->mm.phys_objs[id - 1] || !size) + return 0; - phys_obj = malloc(sizeof(struct drm_i915_gem_phys_object), DRM_I915_GEM, - M_WAITOK | M_ZERO); + phys_obj = malloc(sizeof(struct drm_i915_gem_phys_object), + DRM_I915_GEM, M_WAITOK | M_ZERO); phys_obj->id = id; phys_obj->handle = drm_pci_alloc(dev, size, align, BUS_SPACE_MAXADDR); - if (phys_obj->handle == NULL) { + if (!phys_obj->handle) { ret = -ENOMEM; - goto free_obj; + goto kfree_obj; } pmap_change_attr((vm_offset_t)phys_obj->handle->vaddr, size / PAGE_SIZE, PAT_WRITE_COMBINING); dev_priv->mm.phys_objs[id - 1] = phys_obj; - return (0); - -free_obj: + return 0; +kfree_obj: free(phys_obj, DRM_I915_GEM); - return (ret); + return ret; } -static void -i915_gem_free_phys_object(struct drm_device *dev, int id) +static void i915_gem_free_phys_object(struct drm_device *dev, int id) { - drm_i915_private_t *dev_priv; + drm_i915_private_t *dev_priv = dev->dev_private; struct drm_i915_gem_phys_object *phys_obj; - dev_priv = dev->dev_private; - if (dev_priv->mm.phys_objs[id - 1] == NULL) + if (!dev_priv->mm.phys_objs[id - 1]) return; phys_obj = dev_priv->mm.phys_objs[id - 1]; - if (phys_obj->cur_obj != NULL) + if (phys_obj->cur_obj) { i915_gem_detach_phys_object(dev, phys_obj->cur_obj); + } drm_pci_free(dev, phys_obj->handle); free(phys_obj, DRM_I915_GEM); dev_priv->mm.phys_objs[id - 1] = NULL; } -void -i915_gem_free_all_phys_object(struct drm_device *dev) +void i915_gem_free_all_phys_object(struct drm_device *dev) { int i; @@ -4098,41 +4172,40 @@ i915_gem_free_all_phys_object(struct drm_device *dev) i915_gem_free_phys_object(dev, i); } -void -i915_gem_detach_phys_object(struct drm_device *dev, - struct drm_i915_gem_object *obj) +void i915_gem_detach_phys_object(struct drm_device *dev, + struct drm_i915_gem_object *obj) { - vm_page_t m; + vm_page_t page; struct sf_buf *sf; char *vaddr, *dst; int i, page_count; - if (obj->phys_obj == NULL) + if (!obj->phys_obj) return; vaddr = obj->phys_obj->handle->vaddr; page_count = obj->base.size / PAGE_SIZE; VM_OBJECT_WLOCK(obj->base.vm_obj); for (i = 0; i < page_count; i++) { - m = i915_gem_wire_page(obj->base.vm_obj, i, NULL); - if (m == NULL) + page = i915_gem_wire_page(obj->base.vm_obj, i, NULL); + if (page == NULL) continue; /* XXX */ VM_OBJECT_WUNLOCK(obj->base.vm_obj); - sf = sf_buf_alloc(m, 0); + sf = sf_buf_alloc(page, 0); if (sf != NULL) { dst = (char *)sf_buf_kva(sf); memcpy(dst, vaddr + IDX_TO_OFF(i), PAGE_SIZE); sf_buf_free(sf); } - drm_clflush_pages(&m, 1); + drm_clflush_pages(&page, 1); VM_OBJECT_WLOCK(obj->base.vm_obj); - vm_page_reference(m); - vm_page_lock(m); - vm_page_dirty(m); - vm_page_unwire(m, PQ_INACTIVE); - vm_page_unlock(m); + vm_page_reference(page); + vm_page_lock(page); + vm_page_dirty(page); + vm_page_unwire(page, PQ_INACTIVE); + vm_page_unlock(page); atomic_add_long(&i915_gem_wired_pages_cnt, -1); } VM_OBJECT_WUNLOCK(obj->base.vm_obj); @@ -4144,30 +4217,35 @@ i915_gem_detach_phys_object(struct drm_device *dev, int i915_gem_attach_phys_object(struct drm_device *dev, - struct drm_i915_gem_object *obj, int id, int align) + struct drm_i915_gem_object *obj, + int id, + int align) { - drm_i915_private_t *dev_priv; - vm_page_t m; + drm_i915_private_t *dev_priv = dev->dev_private; + vm_page_t page; struct sf_buf *sf; char *dst, *src; - int i, page_count, ret; + int ret = 0; + int page_count; + int i; if (id > I915_MAX_PHYS_OBJECT) - return (-EINVAL); + return -EINVAL; - if (obj->phys_obj != NULL) { + if (obj->phys_obj) { if (obj->phys_obj->id == id) - return (0); + return 0; i915_gem_detach_phys_object(dev, obj); } - dev_priv = dev->dev_private; - if (dev_priv->mm.phys_objs[id - 1] == NULL) { - ret = i915_gem_init_phys_object(dev, id, obj->base.size, align); - if (ret != 0) { + /* create a new object */ + if (!dev_priv->mm.phys_objs[id - 1]) { + ret = i915_gem_init_phys_object(dev, id, + obj->base.size, align); + if (ret) { DRM_ERROR("failed to init phys object %d size: %zu\n", id, obj->base.size); - return (ret); + return ret; } } @@ -4178,15 +4256,14 @@ i915_gem_attach_phys_object(struct drm_device *dev, page_count = obj->base.size / PAGE_SIZE; VM_OBJECT_WLOCK(obj->base.vm_obj); - ret = 0; for (i = 0; i < page_count; i++) { - m = i915_gem_wire_page(obj->base.vm_obj, i, NULL); - if (m == NULL) { + page = i915_gem_wire_page(obj->base.vm_obj, i, NULL); + if (page == NULL) { ret = -EIO; break; } VM_OBJECT_WUNLOCK(obj->base.vm_obj); - sf = sf_buf_alloc(m, 0); + sf = sf_buf_alloc(page, 0); src = (char *)sf_buf_kva(sf); dst = (char *)obj->phys_obj->handle->vaddr + IDX_TO_OFF(i); memcpy(dst, src, PAGE_SIZE); @@ -4194,85 +4271,106 @@ i915_gem_attach_phys_object(struct drm_device *dev, VM_OBJECT_WLOCK(obj->base.vm_obj); - vm_page_reference(m); - vm_page_lock(m); - vm_page_unwire(m, PQ_INACTIVE); - vm_page_unlock(m); + vm_page_reference(page); + vm_page_lock(page); + vm_page_unwire(page, PQ_INACTIVE); + vm_page_unlock(page); atomic_add_long(&i915_gem_wired_pages_cnt, -1); } VM_OBJECT_WUNLOCK(obj->base.vm_obj); - return (ret); + return ret; } static int -i915_gpu_is_active(struct drm_device *dev) -{ - drm_i915_private_t *dev_priv; - - dev_priv = dev->dev_private; - return (!list_empty(&dev_priv->mm.flushing_list) || - !list_empty(&dev_priv->mm.active_list)); -} - -static void -i915_gem_lowmem(void *arg) +i915_gem_phys_pwrite(struct drm_device *dev, + struct drm_i915_gem_object *obj, + struct drm_i915_gem_pwrite *args, + struct drm_file *file_priv) { - struct drm_device *dev; - struct drm_i915_private *dev_priv; - struct drm_i915_gem_object *obj, *next; - int cnt, cnt_fail, cnt_total; - - dev = arg; - dev_priv = dev->dev_private; - - if (!sx_try_xlock(&dev->dev_struct_lock)) - return; + void *vaddr = (char *)obj->phys_obj->handle->vaddr + args->offset; + char __user *user_data = to_user_ptr(args->data_ptr); - CTR0(KTR_DRM, "gem_lowmem"); + if (__copy_from_user_inatomic_nocache(vaddr, user_data, args->size)) { + unsigned long unwritten; -rescan: - /* first scan for clean buffers */ - i915_gem_retire_requests(dev); + /* The physical object once assigned is fixed for the lifetime + * of the obj, so we can safely drop the lock and continue + * to access vaddr. + */ + DRM_UNLOCK(dev); + unwritten = copy_from_user(vaddr, user_data, args->size); + DRM_LOCK(dev); + if (unwritten) + return -EFAULT; + } - cnt_total = cnt_fail = cnt = 0; + i915_gem_chipset_flush(dev); + return 0; +} - list_for_each_entry_safe(obj, next, &dev_priv->mm.inactive_list, - mm_list) { - if (i915_gem_object_is_purgeable(obj)) { - if (i915_gem_object_unbind(obj) != 0) - cnt_total++; - } else - cnt_total++; - } +void i915_gem_release(struct drm_device *dev, struct drm_file *file) +{ + struct drm_i915_file_private *file_priv = file->driver_priv; - /* second pass, evict/count anything still on the inactive list */ - list_for_each_entry_safe(obj, next, &dev_priv->mm.inactive_list, - mm_list) { - if (i915_gem_object_unbind(obj) == 0) - cnt++; - else - cnt_fail++; - } + /* Clean up our request list when the client is going away, so that + * later retire_requests won't dereference our soon-to-be-gone + * file_priv. + */ + mtx_lock(&file_priv->mm.lck); + while (!list_empty(&file_priv->mm.request_list)) { + struct drm_i915_gem_request *request; - if (cnt_fail > cnt_total / 100 && i915_gpu_is_active(dev)) { - /* - * We are desperate for pages, so as a last resort, wait - * for the GPU to finish and discard whatever we can. - * This has a dramatic impact to reduce the number of - * OOM-killer events whilst running the GPU aggressively. - */ - if (i915_gpu_idle(dev) == 0) - goto rescan; + request = list_first_entry(&file_priv->mm.request_list, + struct drm_i915_gem_request, + client_list); + list_del(&request->client_list); + request->file_priv = NULL; } - DRM_UNLOCK(dev); + mtx_unlock(&file_priv->mm.lck); } -void -i915_gem_unload(struct drm_device *dev) +static vm_page_t +i915_gem_wire_page(vm_object_t object, vm_pindex_t pindex, bool *fresh) { - struct drm_i915_private *dev_priv; + vm_page_t page; + int rv; - dev_priv = dev->dev_private; - EVENTHANDLER_DEREGISTER(vm_lowmem, dev_priv->mm.i915_lowmem); + VM_OBJECT_ASSERT_WLOCKED(object); + page = vm_page_grab(object, pindex, VM_ALLOC_NORMAL); + if (page->valid != VM_PAGE_BITS_ALL) { + if (vm_pager_has_page(object, pindex, NULL, NULL)) { + rv = vm_pager_get_pages(object, &page, 1, 0); + if (rv != VM_PAGER_OK) { + vm_page_lock(page); + vm_page_free(page); + vm_page_unlock(page); + return (NULL); + } + if (fresh != NULL) + *fresh = true; + } else { + pmap_zero_page(page); + page->valid = VM_PAGE_BITS_ALL; + page->dirty = 0; + if (fresh != NULL) + *fresh = false; + } + } else if (fresh != NULL) { + *fresh = false; + } + vm_page_lock(page); + vm_page_wire(page); + vm_page_unlock(page); + vm_page_xunbusy(page); + atomic_add_long(&i915_gem_wired_pages_cnt, 1); + return (page); } + +#undef __user +#undef __force +#undef __iomem +#undef __must_check +#undef to_user_ptr +#undef offset_in_page +#undef page_to_phys |