[Intel-gfx] [RFC] drm/i915: Add variable gem object size support to i915

Daniel Vetter daniel at ffwll.ch
Mon May 12 18:19:11 CEST 2014


On Fri, May 09, 2014 at 02:18:54PM -0700, Volkin, Bradley D wrote:
> On Mon, Apr 28, 2014 at 08:01:29AM -0700, arun.siluvery at linux.intel.com wrote:
> > From: "Siluvery, Arun" <arun.siluvery at intel.com>
> > 
> > This patch adds support to have gem objects of variable size.
> > The size of the gem object obj->size is always constant and this fact
> > is tightly coupled in the driver; this implementation allows to vary
> > its effective size using an interface similar to fallocate().
> > 
> > A new ioctl() is introduced to mark a range as scratch/usable.
> > Once marked as scratch, associated backing store is released and the
> > region is filled with scratch pages. The region can also be unmarked
> > at a later point in which case new backing pages are created.
> > The range can be anywhere within the object space, it can have multiple
> > ranges possibly overlapping forming a large contiguous range.
> > 
> > There is only one single scratch page and Kernel allows to write to this
> > page; userspace need to keep track of scratch page range otherwise any
> > subsequent writes to these pages will overwrite previous content.
> > 
> > This feature is useful where the exact size of the object is not clear
> > at the time of its creation, in such case we usually create an object
> > with more than the required size but end up using it partially.
> > In devices where there are tight memory constraints it would be useful
> > to release that additional space which is currently unused. Using this
> > interface the region can be simply marked as scratch which releases
> > its backing store thus reducing the memory pressure on the kernel.
> > 
> > Many thanks to Daniel, ChrisW, Tvrtko, Bob for the idea and feedback
> > on this implementation.
> > 
> > v2: fix holes in error handling and use consistent data types (Tvrtko)
> >  - If page allocation fails simply return error; do not try to invoke
> >    shrinker to free backing store.
> >  - Release new pages created by us in case of error during page allocation
> >    or sg_table update.
> >  - Use 64-bit data types for start and length values to avoid truncation.
> > 
> > Change-Id: Id3339be95dbb6b5c69c39d751986c40ec0ccdaf8
> > Signed-off-by: Siluvery, Arun <arun.siluvery at intel.com>
> > ---
> > 
> > Please let me know if I need to submit this as PATCH instead of RFC.
> > Since this is RFC I have included all changes as a single patch.
> 
> Hi Arun,
> 
> For a change of this size, one patch seems fine to me. I think RFC vs.
> PATCH is more a comment of what state you think the patch is in and
> what level of feedback you're looking for.
> 
> > 
> >  drivers/gpu/drm/i915/i915_dma.c |   1 +
> >  drivers/gpu/drm/i915/i915_drv.h |   2 +
> >  drivers/gpu/drm/i915/i915_gem.c | 205 ++++++++++++++++++++++++++++++++++++++++
> >  include/uapi/drm/i915_drm.h     |  31 ++++++
> >  4 files changed, 239 insertions(+)
> > 
> > diff --git a/drivers/gpu/drm/i915/i915_dma.c b/drivers/gpu/drm/i915/i915_dma.c
> > index 31c499f..3dd4b1a 100644
> > --- a/drivers/gpu/drm/i915/i915_dma.c
> > +++ b/drivers/gpu/drm/i915/i915_dma.c
> > @@ -2000,6 +2000,7 @@ const struct drm_ioctl_desc i915_ioctls[] = {
> >  	DRM_IOCTL_DEF_DRV(I915_GET_RESET_STATS, i915_get_reset_stats_ioctl, DRM_UNLOCKED|DRM_RENDER_ALLOW),
> >  	DRM_IOCTL_DEF_DRV(I915_GEM_USERPTR, i915_gem_userptr_ioctl, \
> >  						DRM_UNLOCKED|DRM_RENDER_ALLOW),
> > +	DRM_IOCTL_DEF_DRV(I915_GEM_FALLOCATE, i915_gem_fallocate_ioctl, DRM_UNLOCKED|DRM_RENDER_ALLOW),
> >  	DRM_IOCTL_DEF_DRV(I915_SET_PLANE_180_ROTATION, \
> >  		i915_set_plane_180_rotation, DRM_AUTH | DRM_UNLOCKED),
> >  	DRM_IOCTL_DEF_DRV(I915_ENABLE_PLANE_RESERVED_REG_BIT_2,
> > diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h
> > index 4069800..1f30fb6 100644
> > --- a/drivers/gpu/drm/i915/i915_drv.h
> > +++ b/drivers/gpu/drm/i915/i915_drv.h
> > @@ -2210,6 +2210,8 @@ int i915_gem_get_tiling(struct drm_device *dev, void *data,
> >  int i915_gem_init_userptr(struct drm_device *dev);
> >  int i915_gem_userptr_ioctl(struct drm_device *dev, void *data,
> >  				struct drm_file *file);
> > +int i915_gem_fallocate_ioctl(struct drm_device *dev, void *data,
> > +				struct drm_file *file);
> >  int i915_gem_get_aperture_ioctl(struct drm_device *dev, void *data,
> >  				struct drm_file *file_priv);
> >  int i915_gem_wait_ioctl(struct drm_device *dev, void *data,
> > diff --git a/drivers/gpu/drm/i915/i915_gem.c b/drivers/gpu/drm/i915/i915_gem.c
> > index 6153e01..a0188ee 100644
> > --- a/drivers/gpu/drm/i915/i915_gem.c
> > +++ b/drivers/gpu/drm/i915/i915_gem.c
> > @@ -317,6 +317,211 @@ i915_gem_create_ioctl(struct drm_device *dev, void *data,
> >  			       args->size, &args->handle);
> >  }
> >  
> > +static int i915_gem_obj_fallocate(struct drm_i915_gem_object *obj,
> > +				  bool mark_scratch, uint64_t start,
> > +				  uint64_t length)
> > +{
> > +	int i, j;
> > +	int ret;
> > +	uint32_t start_page, end_page;
> > +	uint32_t page_count;
> > +	gfp_t gfp;
> > +	bool update_sg_table = false;
> > +	unsigned long scratch_pfn;
> > +	struct page *scratch;
> > +	struct page **pages;
> > +	struct sg_table *sg = NULL;
> > +	struct sg_page_iter sg_iter;
> > +	struct address_space *mapping;
> > +	struct drm_i915_private *dev_priv;
> > +
> > +	dev_priv = obj->base.dev->dev_private;
> > +	start_page = start >> PAGE_SHIFT;
> > +	end_page = (start + length) >> PAGE_SHIFT;
> > +	page_count = obj->base.size >> PAGE_SHIFT;
> > +
> > +	pages = drm_malloc_ab(page_count, sizeof(*pages));
> > +	if (pages == NULL)
> > +		return -ENOMEM;
> > +
> > +	i = 0;
> > +	for_each_sg_page(obj->pages->sgl, &sg_iter, obj->pages->nents, 0) {
> > +		pages[i] = sg_page_iter_page(&sg_iter);
> > +		i++;
> > +	}
> > +
> > +	mapping = file_inode(obj->base.filp)->i_mapping;
> > +	gfp = mapping_gfp_mask(mapping);
> > +	gfp |= __GFP_NORETRY | __GFP_NOWARN | __GFP_NO_KSWAPD;
> > +	gfp &= ~(__GFP_IO | __GFP_WAIT);
> > +	scratch = dev_priv->gtt.base.scratch.page;
> > +	scratch_pfn = page_to_pfn(scratch);
> > +
> > +	if (mark_scratch) {
> > +		/* invalidate page range */
> > +		for (i = start_page; i < end_page; ++i) {
> > +			if (scratch_pfn == page_to_pfn(pages[i]))
> > +				continue;
> > +
> > +			update_sg_table = true;
> > +			drm_clflush_pages((pages + i), 1);
> > +			if (obj->dirty)
> > +				set_page_dirty(pages[i]);
> > +			page_cache_release(pages[i]);
> > +			pages[i] = scratch;
> > +		}
> > +	} else {
> > +		struct page *page;
> > +		/* allocate new pages */
> > +		for (i = start_page; i < end_page; ++i) {
> > +			if (page_to_pfn(pages[i]) != scratch_pfn)
> > +				continue;
> > +
> > +			page = shmem_read_mapping_page_gfp(mapping, i, gfp);
> > +			if (IS_ERR(page)) {
> > +				ret = PTR_ERR(page);
> > +				goto err_pages;
> > +			}
> > +
> > +			update_sg_table = true;
> > +			pages[i] = page;
> > +		}
> > +	}
> > +
> > +	if (update_sg_table == false) {
> > +		ret = 0;
> > +		goto out;
> > +	}
> > +
> > +	/**
> > +	 * easier to replace the existing sg_table with
> > +	 * the new one instead of modifying it
> > +	 */
> > +	sg = kmalloc(sizeof(struct sg_table), GFP_KERNEL);
> > +	if (!sg) {
> > +		ret = -ENOMEM;
> > +		goto err_pages;
> > +	}
> > +	ret = sg_alloc_table_from_pages(sg, pages, page_count, 0,
> > +					page_count << PAGE_SHIFT, gfp);
> > +	if (ret)
> > +		goto err_alloc_table;
> > +
> > +	sg_free_table(obj->pages);
> > +	kfree(obj->pages);
> > +	obj->pages = sg;
> > +
> > +	return 0;
> > +
> > +err_alloc_table:
> > +	kfree(sg);
> > +err_pages:
> > +	/*
> > +	 * In case of an error we should keep the obj in its previous state.
> > +	 *
> > +	 * case 1: when replacing obj pages with scratch pages
> > +	 *   No action required because obj has all valid pages and
> > +	 *   we cannot release the scratch page as it is used in
> > +	 *   other places.
> > +	 *
> > +	 * case 2: when replacing scratch pages with real backing store
> > +	 *   In this case free only the new pages created by us
> > +	 */
> > +	if (!mark_scratch) {
> > +		for (j = start_page; j < i; ++j)
> > +			page_cache_release(pages[j]);
> > +	}
> > +out:
> > +	if (pages)
> > +		drm_free_large(pages);
> > +
> > +	return ret;
> > +}
> > +
> > +/**
> > + * Changes the effective size of an existing gem object.
> > + * The object size is always constant and this fact is tightly
> > + * coupled in the driver. This ioctl() allows user to define
> > + * certain ranges in the obj to be marked as usable/scratch
> > + * thus modifying the effective size of the object used.
> > + * mark_scratch: specified range of pages are replaced by scratch page.
> > + * umark_scratch: scratch pages are replaced by real backing store.
> > + */
> > +int i915_gem_fallocate_ioctl(struct drm_device *dev,
> > +			     void *data, struct drm_file *file)
> > +{
> > +	int ret;
> > +	bool mark_scratch = false;
> > +	uint64_t start, length;
> > +	struct i915_vma *vma;
> > +	struct drm_i915_private *dev_priv;
> > +	struct drm_i915_gem_object *obj;
> > +	struct i915_address_space *vm;
> > +	struct drm_i915_gem_fallocate *args = data;
> > +
> > +	if (!((args->mode & I915_GEM_FALLOC_MARK_SCRATCH) ^
> > +	       ((args->mode & I915_GEM_FALLOC_UNMARK_SCRATCH) >> 1)))
> > +		return -EINVAL;
> > +
> > +	obj = to_intel_bo(drm_gem_object_lookup(dev, file, args->handle));
> > +	if (&obj->base == NULL)
> > +		return -ENOENT;
> 
> Since the fallocate() code requires shmem backing, we need this check:
> 
> 	if (!obj->base.filp)
> 		return -EINVAL;
> 
> > +
> > +	start = roundup(args->start, PAGE_SIZE);
> > +	length = roundup(args->length, PAGE_SIZE);
> 
> Rounding up the start to avoid throwing away valid data on the start page
> makes sense to me. But shouldn't we then round the length down to avoid
> throwing away valid data on the end page, after the specified range?
> 
> Or we could just require page aligned start and length and return error
> otherwise; that's what the userptr ioctl does.

Imo just check for start | lenght & PAGE_MASK. Since you actually need to
round start + length, otherwise the userspace semantics are completely
nuts.

And of course the igt for this ioctl needs to have a testcase for this.
> 
> > +	if (length == 0
> > +	    || length > obj->base.size
> > +	    || start > obj->base.size
> > +	    || (start + length) > obj->base.size)
> > +		return -EINVAL;
> > +
> > +	dev_priv = dev->dev_private;
> > +	vm = &dev_priv->gtt.base;
> > +
> > +	ret = mutex_lock_interruptible(&dev->struct_mutex);
> 
> Should we use i915_mutex_lock_interruptible() here? I'm not entirely clear
> on when that's required vs. just mutex_lock_interruptible.

Use it always, but you _have_ to use it if you might end up blocking on
the gpu. Which the below code can do (through the unbind calls).
-Daniel

> 
> > +	if (ret)
> > +		return ret;
> > +
> > +	if (!i915_gem_obj_bound(obj, vm)) {
> > +		ret = i915_gem_object_bind_to_vm(obj, vm, 0, true, false);
> > +		if (ret)
> > +			goto unlock;
> > +
> > +		if (!dev_priv->mm.aliasing_ppgtt)
> > +			i915_gem_gtt_bind_object(obj, obj->cache_level);
> > +	}
> > +
> > +	drm_gem_object_reference(&obj->base);
> > +
> > +	vma = i915_gem_obj_to_vma(obj, vm);
> > +	if (!vma) {
> > +		ret = -EINVAL;
> > +		goto out;
> > +	}
> > +
> > +	ret = i915_vma_unbind(vma);
> > +	if (ret)
> > +		goto out;
> 
> Hmm, can you elaborate on the need for this section a bit? I don't
> really follow what we're doing here. I can see needing to unbind an
> object that is bound in order to change the page table entries. I
> guess I just don't understand the specific implementation.
> 
> For example, why do we need to bind an unbound object just to unbind
> it again? Should we even allow fallocate() on such an object? And we
> only bind/unbind from GGTT; what about PPGTTs?
> 
> > +
> > +	mark_scratch =
> > +		(args->mode & I915_GEM_FALLOC_MARK_SCRATCH) ? true : false;
> > +	ret = i915_gem_obj_fallocate(obj, mark_scratch, start, length);
> > +	if (ret) {
> > +		DRM_ERROR("fallocating specified obj range failed\n");
> > +		goto out;
> > +	}
> > +
> > +	ret = i915_gem_object_bind_to_vm(obj, vm, 0, true, false);
> > +	if (ret)
> > +		DRM_ERROR("object couldn't be bound after falloc\n");
> > +
> > +out:
> > +	drm_gem_object_unreference(&obj->base);
> > +unlock:
> > +	mutex_unlock(&dev->struct_mutex);
> > +	return ret;
> > +}
> > +
> >  static inline int
> >  __copy_to_user_swizzled(char __user *cpu_vaddr,
> >  			const char *gpu_vaddr, int gpu_offset,
> > diff --git a/include/uapi/drm/i915_drm.h b/include/uapi/drm/i915_drm.h
> > index aa8469e..0d63fc8 100644
> > --- a/include/uapi/drm/i915_drm.h
> > +++ b/include/uapi/drm/i915_drm.h
> > @@ -275,6 +275,7 @@ struct csc_coeff {
> >  #define DRM_I915_GET_RESET_STATS	0x32
> >  #define DRM_I915_SET_PLANE_ZORDER	0x33
> >  #define DRM_I915_GEM_USERPTR		0x34
> > +#define DRM_I915_GEM_FALLOCATE		0x35
> >  #define DRM_I915_SET_PLANE_180_ROTATION 0x36
> >  #define DRM_I915_ENABLE_PLANE_RESERVED_REG_BIT_2	0x37
> >  #define DRM_I915_SET_CSC		0x39
> > @@ -339,6 +340,9 @@ struct csc_coeff {
> >  #define DRM_IOCTL_I915_GEM_USERPTR \
> >  		DRM_IOWR (DRM_COMMAND_BASE + DRM_I915_GEM_USERPTR, \
> >  				struct drm_i915_gem_userptr)
> > +#define DRM_IOCTL_I915_GEM_FALLOCATE \
> > +		DRM_IOWR(DRM_COMMAND_BASE + DRM_I915_GEM_FALLOCATE, \
> > +				struct drm_i915_gem_fallocate)
> 
> We're not returning any data in the struct, so no need for DRM_IOWR.
> Just DRM_IOW should be fine.
> 
> >  #define DRM_IOCTL_I915_SET_PLANE_ALPHA		\
> >  			DRM_IOW(DRM_COMMAND_BASE + DRM_I915_SET_PLANE_ALPHA, \
> >  			struct drm_i915_set_plane_alpha)
> > @@ -523,6 +527,33 @@ struct drm_i915_gem_create {
> >  	__u32 pad;
> >  };
> >  
> > +struct drm_i915_gem_fallocate {
> > +	/**
> > +	 * Start position of the range
> > +	 *
> > +	 * If the given value is not page-aligned it will be rounded internally.
> > +	 */
> > +	__u64 start;
> > +	/**
> > +	 * Length of the range
> > +	 *
> > +	 * If the given value is not page-aligned it will be rounded internally.
> > +	 */
> > +	__u64 length;
> > +	/**
> > +	 * Mode applied to the range
> > +	 */
> > +	__u32 mode;
> > +#define I915_GEM_FALLOC_MARK_SCRATCH        0x01
> > +#define I915_GEM_FALLOC_UNMARK_SCRATCH      0x02
> > +	/**
> > +	 * Returned handle for the object.
> > +	 *
> > +	 * Object handles are nonzero.
> > +	 */
> 
> We're not actually returning the handle, it's only an input.
> 
> Thanks,
> Brad
> 
> > +	__u32 handle;
> > +};
> > +
> >  struct drm_i915_gem_pread {
> >  	/** Handle for the object being read. */
> >  	__u32 handle;
> > -- 
> > 1.9.2
> > 
> > _______________________________________________
> > Intel-gfx mailing list
> > Intel-gfx at lists.freedesktop.org
> > http://lists.freedesktop.org/mailman/listinfo/intel-gfx
> _______________________________________________
> Intel-gfx mailing list
> Intel-gfx at lists.freedesktop.org
> http://lists.freedesktop.org/mailman/listinfo/intel-gfx

-- 
Daniel Vetter
Software Engineer, Intel Corporation
+41 (0) 79 365 57 48 - http://blog.ffwll.ch



More information about the Intel-gfx mailing list