diff options
Diffstat (limited to 'drivers/gpu/drm/vc4/vc4_gem.c')
| -rw-r--r-- | drivers/gpu/drm/vc4/vc4_gem.c | 170 | 
1 files changed, 157 insertions, 13 deletions
| diff --git a/drivers/gpu/drm/vc4/vc4_gem.c b/drivers/gpu/drm/vc4/vc4_gem.c index d0c6bfb68c4e..638540943c61 100644 --- a/drivers/gpu/drm/vc4/vc4_gem.c +++ b/drivers/gpu/drm/vc4/vc4_gem.c @@ -188,11 +188,22 @@ vc4_save_hang_state(struct drm_device *dev)  			continue;  		for (j = 0; j < exec[i]->bo_count; j++) { +			bo = to_vc4_bo(&exec[i]->bo[j]->base); + +			/* Retain BOs just in case they were marked purgeable. +			 * This prevents the BO from being purged before +			 * someone had a chance to dump the hang state. +			 */ +			WARN_ON(!refcount_read(&bo->usecnt)); +			refcount_inc(&bo->usecnt);  			drm_gem_object_get(&exec[i]->bo[j]->base);  			kernel_state->bo[j + prev_idx] = &exec[i]->bo[j]->base;  		}  		list_for_each_entry(bo, &exec[i]->unref_list, unref_head) { +			/* No need to retain BOs coming from the ->unref_list +			 * because they are naturally unpurgeable. +			 */  			drm_gem_object_get(&bo->base.base);  			kernel_state->bo[j + prev_idx] = &bo->base.base;  			j++; @@ -233,6 +244,26 @@ vc4_save_hang_state(struct drm_device *dev)  	state->fdbgs = V3D_READ(V3D_FDBGS);  	state->errstat = V3D_READ(V3D_ERRSTAT); +	/* We need to turn purgeable BOs into unpurgeable ones so that +	 * userspace has a chance to dump the hang state before the kernel +	 * decides to purge those BOs. +	 * Note that BO consistency at dump time cannot be guaranteed. For +	 * example, if the owner of these BOs decides to re-use them or mark +	 * them purgeable again there's nothing we can do to prevent it. +	 */ +	for (i = 0; i < kernel_state->user_state.bo_count; i++) { +		struct vc4_bo *bo = to_vc4_bo(kernel_state->bo[i]); + +		if (bo->madv == __VC4_MADV_NOTSUPP) +			continue; + +		mutex_lock(&bo->madv_lock); +		if (!WARN_ON(bo->madv == __VC4_MADV_PURGED)) +			bo->madv = VC4_MADV_WILLNEED; +		refcount_dec(&bo->usecnt); +		mutex_unlock(&bo->madv_lock); +	} +  	spin_lock_irqsave(&vc4->job_lock, irqflags);  	if (vc4->hang_state) {  		spin_unlock_irqrestore(&vc4->job_lock, irqflags); @@ -281,10 +312,10 @@ vc4_reset_work(struct work_struct *work)  }  static void -vc4_hangcheck_elapsed(unsigned long data) +vc4_hangcheck_elapsed(struct timer_list *t)  { -	struct drm_device *dev = (struct drm_device *)data; -	struct vc4_dev *vc4 = to_vc4_dev(dev); +	struct vc4_dev *vc4 = from_timer(vc4, t, hangcheck.timer); +	struct drm_device *dev = vc4->dev;  	uint32_t ct0ca, ct1ca;  	unsigned long irqflags;  	struct vc4_exec_info *bin_exec, *render_exec; @@ -639,9 +670,6 @@ vc4_queue_submit(struct drm_device *dev, struct vc4_exec_info *exec,   * The command validator needs to reference BOs by their index within   * the submitted job's BO list.  This does the validation of the job's   * BO list and reference counting for the lifetime of the job. - * - * Note that this function doesn't need to unreference the BOs on - * failure, because that will happen at vc4_complete_exec() time.   */  static int  vc4_cl_lookup_bos(struct drm_device *dev, @@ -693,16 +721,47 @@ vc4_cl_lookup_bos(struct drm_device *dev,  			DRM_DEBUG("Failed to look up GEM BO %d: %d\n",  				  i, handles[i]);  			ret = -EINVAL; -			spin_unlock(&file_priv->table_lock); -			goto fail; +			break;  		} +  		drm_gem_object_get(bo);  		exec->bo[i] = (struct drm_gem_cma_object *)bo;  	}  	spin_unlock(&file_priv->table_lock); +	if (ret) +		goto fail_put_bo; + +	for (i = 0; i < exec->bo_count; i++) { +		ret = vc4_bo_inc_usecnt(to_vc4_bo(&exec->bo[i]->base)); +		if (ret) +			goto fail_dec_usecnt; +	} + +	kvfree(handles); +	return 0; + +fail_dec_usecnt: +	/* Decrease usecnt on acquired objects. +	 * We cannot rely on  vc4_complete_exec() to release resources here, +	 * because vc4_complete_exec() has no information about which BO has +	 * had its ->usecnt incremented. +	 * To make things easier we just free everything explicitly and set +	 * exec->bo to NULL so that vc4_complete_exec() skips the 'BO release' +	 * step. +	 */ +	for (i-- ; i >= 0; i--) +		vc4_bo_dec_usecnt(to_vc4_bo(&exec->bo[i]->base)); + +fail_put_bo: +	/* Release any reference to acquired objects. */ +	for (i = 0; i < exec->bo_count && exec->bo[i]; i++) +		drm_gem_object_put_unlocked(&exec->bo[i]->base); +  fail:  	kvfree(handles); +	kvfree(exec->bo); +	exec->bo = NULL;  	return ret;  } @@ -829,12 +888,18 @@ vc4_complete_exec(struct drm_device *dev, struct vc4_exec_info *exec)  	/* If we got force-completed because of GPU reset rather than  	 * through our IRQ handler, signal the fence now.  	 */ -	if (exec->fence) +	if (exec->fence) {  		dma_fence_signal(exec->fence); +		dma_fence_put(exec->fence); +	}  	if (exec->bo) { -		for (i = 0; i < exec->bo_count; i++) +		for (i = 0; i < exec->bo_count; i++) { +			struct vc4_bo *bo = to_vc4_bo(&exec->bo[i]->base); + +			vc4_bo_dec_usecnt(bo);  			drm_gem_object_put_unlocked(&exec->bo[i]->base); +		}  		kvfree(exec->bo);  	} @@ -1091,13 +1156,14 @@ vc4_gem_init(struct drm_device *dev)  	spin_lock_init(&vc4->job_lock);  	INIT_WORK(&vc4->hangcheck.reset_work, vc4_reset_work); -	setup_timer(&vc4->hangcheck.timer, -		    vc4_hangcheck_elapsed, -		    (unsigned long)dev); +	timer_setup(&vc4->hangcheck.timer, vc4_hangcheck_elapsed, 0);  	INIT_WORK(&vc4->job_done_work, vc4_job_done_work);  	mutex_init(&vc4->power_lock); + +	INIT_LIST_HEAD(&vc4->purgeable.list); +	mutex_init(&vc4->purgeable.lock);  }  void @@ -1121,3 +1187,81 @@ vc4_gem_destroy(struct drm_device *dev)  	if (vc4->hang_state)  		vc4_free_hang_state(dev, vc4->hang_state);  } + +int vc4_gem_madvise_ioctl(struct drm_device *dev, void *data, +			  struct drm_file *file_priv) +{ +	struct drm_vc4_gem_madvise *args = data; +	struct drm_gem_object *gem_obj; +	struct vc4_bo *bo; +	int ret; + +	switch (args->madv) { +	case VC4_MADV_DONTNEED: +	case VC4_MADV_WILLNEED: +		break; +	default: +		return -EINVAL; +	} + +	if (args->pad != 0) +		return -EINVAL; + +	gem_obj = drm_gem_object_lookup(file_priv, args->handle); +	if (!gem_obj) { +		DRM_DEBUG("Failed to look up GEM BO %d\n", args->handle); +		return -ENOENT; +	} + +	bo = to_vc4_bo(gem_obj); + +	/* Only BOs exposed to userspace can be purged. */ +	if (bo->madv == __VC4_MADV_NOTSUPP) { +		DRM_DEBUG("madvise not supported on this BO\n"); +		ret = -EINVAL; +		goto out_put_gem; +	} + +	/* Not sure it's safe to purge imported BOs. Let's just assume it's +	 * not until proven otherwise. +	 */ +	if (gem_obj->import_attach) { +		DRM_DEBUG("madvise not supported on imported BOs\n"); +		ret = -EINVAL; +		goto out_put_gem; +	} + +	mutex_lock(&bo->madv_lock); + +	if (args->madv == VC4_MADV_DONTNEED && bo->madv == VC4_MADV_WILLNEED && +	    !refcount_read(&bo->usecnt)) { +		/* If the BO is about to be marked as purgeable, is not used +		 * and is not already purgeable or purged, add it to the +		 * purgeable list. +		 */ +		vc4_bo_add_to_purgeable_pool(bo); +	} else if (args->madv == VC4_MADV_WILLNEED && +		   bo->madv == VC4_MADV_DONTNEED && +		   !refcount_read(&bo->usecnt)) { +		/* The BO has not been purged yet, just remove it from +		 * the purgeable list. +		 */ +		vc4_bo_remove_from_purgeable_pool(bo); +	} + +	/* Save the purged state. */ +	args->retained = bo->madv != __VC4_MADV_PURGED; + +	/* Update internal madv state only if the bo was not purged. */ +	if (bo->madv != __VC4_MADV_PURGED) +		bo->madv = args->madv; + +	mutex_unlock(&bo->madv_lock); + +	ret = 0; + +out_put_gem: +	drm_gem_object_put_unlocked(gem_obj); + +	return ret; +} |