[PATCH] drm/vgem: Added page prefaulting
Szymon Andrzejuk
s.andrzejuk at samsung.com
Thu Sep 5 12:44:05 UTC 2019
Thank you for the review Andrzej. I'll update the patch to v2 shortly, it should
cover all your comments.
FYI, I'll be on holiday until September 16 so I might not be able to respond in the
following days.
Regards,
Szymon
On 03.09.2019 15:19, Andrzej Hajda wrote:
> +CC: $(./script/get_maintainers.pl drivers/gpu/drm/vgem/vgem_drv.c)
>
>
> On 20.08.2019 08:58, Szymon Andrzejuk wrote:
>> Page fault handler inside vgem driver now preallocates pages in advance
>> when fault occurs for the first time. Pages can be allocated in
>> direction of increasing/decreasing addresses, depending on memory access
>> profile. In case of random access no preallocation occurs.
>>
>> Synthetic benchmark showed over 8x bandwidth increase when copying data
>> from mmapped vgem buffer with memcpy and ~160 times when accessing mapped
>> buffer sequentially. Compiled with gcc 8.2.0 with -O2 flag.
>> Unigine Heaven running on custom virgl vtest virtual GPU with vgem buffers
>> sees ~17% FPS increase.
>>
>> This performance increase only occurs when accessing vgem buffer mapped
>> using DRM_IOCTL_MODE_MAP_DUMB ioctl. When accessing buffer imported
>> from prime fd the vgem page fault handler is not invoked. It's advised
>> to use vector streaming copy instructions and avoid sequential accesses
>> in this case. Streaming copy brings the performance to be on par with
>> similar buffer allocated with memfd_create(2) syscall.
>>
>> Signed-off-by: Szymon Andrzejuk <s.andrzejuk at samsung.com>
>> ---
>> drivers/gpu/drm/vgem/vgem_drv.c | 177 ++++++++++++++++++++++++++------
>> 1 file changed, 143 insertions(+), 34 deletions(-)
>>
>> diff --git a/drivers/gpu/drm/vgem/vgem_drv.c b/drivers/gpu/drm/vgem/vgem_drv.c
>> index 11a8f99ba18c..739ba841e89c 100644
>> --- a/drivers/gpu/drm/vgem/vgem_drv.c
>> +++ b/drivers/gpu/drm/vgem/vgem_drv.c
>> @@ -34,6 +34,7 @@
>> #include <linux/ramfs.h>
>> #include <linux/shmem_fs.h>
>> #include <linux/dma-buf.h>
>> +#include <linux/pfn_t.h>
>> #include "vgem_drv.h"
>>
>> #define DRIVER_NAME "vgem"
>> @@ -50,8 +51,21 @@ static struct vgem_device {
>> static void vgem_gem_free_object(struct drm_gem_object *obj)
>> {
>> struct drm_vgem_gem_object *vgem_obj = to_vgem_bo(obj);
>> + int i;
>> +
>> + mutex_lock(&vgem_obj->pages_lock);
>> + if (vgem_obj->pages) {
>> + int num_pages = obj->size >> PAGE_SHIFT;
>> +
>> + for (i = 0; i < num_pages; i++) {
>> + if (vgem_obj->pages[i])
>> + put_page(vgem_obj->pages[i]);
>> + }
>> + kvfree(vgem_obj->pages);
>> + vgem_obj->pages = NULL;
>> + }
>> + mutex_unlock(&vgem_obj->pages_lock);
>>
>> - kvfree(vgem_obj->pages);
>> mutex_destroy(&vgem_obj->pages_lock);
>>
>> if (obj->import_attach)
>> @@ -61,6 +75,72 @@ static void vgem_gem_free_object(struct drm_gem_object *obj)
>> kfree(vgem_obj);
>> }
>>
>> +static int __vgem_alloc_page(struct page *page, struct vm_area_struct *vma,
>> + unsigned long vaddr, int page_num)
>
> unused page_num
>
>
>> +{
>> + unsigned long pfn;
>> + int insert_ret;
>> +
>> + pfn = page_to_pfn(page);
>> + insert_ret = vmf_insert_mixed(vma, vaddr, __pfn_to_pfn_t(pfn, PFN_DEV));
>> +
>> + if (insert_ret & VM_FAULT_ERROR)
>> + return VM_FAULT_ERROR;
>
> Is it OK to return mask? instead of insert_ret.
>
>
>> +
>> + return 0;
>> +}
>> +
>> +static int __vgem_read_mapping_page(struct drm_vgem_gem_object *obj,
>> + int page_num, struct page **page)
>> +{
>> + int ret;
>> + struct page *mapped_page;
>> +
>> + mapped_page = shmem_read_mapping_page(file_inode(obj->base.filp)->i_mapping,
>> + page_num);
>> + if (IS_ERR(page)) {
>> + switch (PTR_ERR(page)) {
>> + case -ENOSPC:
>> + case -ENOMEM:
>> + ret = VM_FAULT_OOM;
>> + break;
>> + case -EBUSY:
>> + ret = VM_FAULT_RETRY;
>> + break;
>> + case -EFAULT:
>> + case -EINVAL:
>> + ret = VM_FAULT_SIGBUS;
>> + break;
>> + default:
>> + WARN_ON(PTR_ERR(page));
>> + ret = VM_FAULT_SIGBUS;
>> + break;
>> + }
>> +
>> + return ret;
>> + }
>> +
>> + *page = mapped_page;
>> + return 0;
>> +}
>> +
>> +static int __vgem_prepare_single_page(struct drm_vgem_gem_object *obj,
>> + struct vm_area_struct *vma,
>> + int page_num, unsigned long vaddr)
>> +{
>> + int ret;
>> +
>> + ret = __vgem_read_mapping_page(obj, page_num, &obj->pages[page_num]);
>> + if (ret)
>> + return ret;
>> +
>> + ret = __vgem_alloc_page(obj->pages[page_num], vma, vaddr, page_num);
>> + if (ret)
>> + return ret;
>
> One can use return __vgem_alloc_page(...), up to you.
>
>
>> +
>> + return 0;
>> +}
>> +
>> static vm_fault_t vgem_gem_fault(struct vm_fault *vmf)
>> {
>> struct vm_area_struct *vma = vmf->vma;
>> @@ -70,6 +150,8 @@ static vm_fault_t vgem_gem_fault(struct vm_fault *vmf)
>> vm_fault_t ret = VM_FAULT_SIGBUS;
>> loff_t num_pages;
>> pgoff_t page_offset;
>> + int page_num, page_prep_ret;
>> + const int PREFAULT_PAGES = 8;
>> page_offset = (vaddr - vma->vm_start) >> PAGE_SHIFT;
>>
>> num_pages = DIV_ROUND_UP(obj->base.size, PAGE_SIZE);
>> @@ -77,41 +159,60 @@ static vm_fault_t vgem_gem_fault(struct vm_fault *vmf)
>> if (page_offset >= num_pages)
>> return VM_FAULT_SIGBUS;
>>
>> + ret = VM_FAULT_NOPAGE;
>> +
>> mutex_lock(&obj->pages_lock);
>> - if (obj->pages) {
>> - get_page(obj->pages[page_offset]);
>> - vmf->page = obj->pages[page_offset];
>> - ret = 0;
>> - }
>> - mutex_unlock(&obj->pages_lock);
>> - if (ret) {
>> - struct page *page;
>> -
>> - page = shmem_read_mapping_page(
>> - file_inode(obj->base.filp)->i_mapping,
>> - page_offset);
>> - if (!IS_ERR(page)) {
>> - vmf->page = page;
>> - ret = 0;
>> - } else switch (PTR_ERR(page)) {
>> - case -ENOSPC:
>> - case -ENOMEM:
>> - ret = VM_FAULT_OOM;
>> - break;
>> - case -EBUSY:
>> - ret = VM_FAULT_RETRY;
>> - break;
>> - case -EFAULT:
>> - case -EINVAL:
>> - ret = VM_FAULT_SIGBUS;
>> - break;
>> - default:
>> - WARN_ON(PTR_ERR(page));
>> - ret = VM_FAULT_SIGBUS;
>> - break;
>> +
>> + if (num_pages > 1) {
>> + bool forward = true;
>> + bool random = false;
>> +
>> + // Determine prefaulting direction. If adjacent pages are both
>> + // allocated/not allocated then we have random access.
>> + // Always try to prefault on first and last page.
>> + if (page_offset != 0 && page_offset != num_pages - 1) {
>> + struct page *next, *prev;
>> +
>> + next = obj->pages[page_offset + 1];
>> + prev = obj->pages[page_offset - 1];
>> + if (!((uintptr_t)next ^ (uintptr_t)prev))
>> + random = true;
>> + else if (!prev)
>> + forward = false;
>> + } else {
>> + forward = (page_offset == 0);
>> }
>
> Quite complicated, maybe sth like this:
>
> bool next, prev;
>
> next = obj->pages[page_offset + 1];
>
> prev = obj->pages[page_offset - 1];
>
> if (prev == next)
>
> random = true;
>
>
>>
>> + if (!random) {
>> + for (page_num = page_offset;
>> + forward ? page_num < page_offset + PREFAULT_PAGES && page_num < num_pages :
>> + page_offset - page_num < PREFAULT_PAGES && page_num >= 0;
>> + forward ? page_num++ : page_num--) {
>
> Again complicated, try pre-calculate boundaries and:
>
> start_page = ...;
>
> end_page = ...;
>
> step = forward ? 1 : -1;
>
> for (page_num = start_page; page_num < end_page; page_num += step)
>
> ...
>
>
>> + if (!obj->pages[page_num]) {
>> + page_prep_ret = __vgem_prepare_single_page(obj, vma, page_num, vaddr);
>> + if (page_prep_ret) {
>> + ret = page_prep_ret;
>> + break;
>> + }
>> + } else {
>> + // random access, exit loop
>> + break;
>> + }
>> +
>> + vaddr = vaddr + (forward ? 1 : -1) * PAGE_SIZE;
>
> vaddr += step * PAGE_SIZE;
>
>
> Regards
>
> Andrzej
>
>
>> + }
>> + } else {
>> + page_prep_ret = __vgem_prepare_single_page(obj, vma, page_offset, vaddr);
>> + if (page_prep_ret)
>> + ret = page_prep_ret;
>> + }
>> + } else {
>> + page_prep_ret = __vgem_prepare_single_page(obj, vma, page_offset, vaddr);
>> + if (page_prep_ret)
>> + ret = page_prep_ret;
>> }
>> +
>> + mutex_unlock(&obj->pages_lock);
>> return ret;
>> }
>>
>> @@ -182,7 +283,7 @@ static struct drm_gem_object *vgem_gem_create(struct drm_device *dev,
>> unsigned long size)
>> {
>> struct drm_vgem_gem_object *obj;
>> - int ret;
>> + int ret, num_pages;
>>
>> obj = __vgem_gem_create(dev, size);
>> if (IS_ERR(obj))
>> @@ -193,6 +294,13 @@ static struct drm_gem_object *vgem_gem_create(struct drm_device *dev,
>> if (ret)
>> return ERR_PTR(ret);
>>
>> + mutex_lock(&obj->pages_lock);
>> +
>> + num_pages = obj->base.size >> PAGE_SHIFT;
>> + obj->pages = kvcalloc(num_pages, sizeof(struct page *), GFP_KERNEL);
>> +
>> + mutex_unlock(&obj->pages_lock);
>> +
>> return &obj->base;
>> }
>>
>> @@ -262,7 +370,8 @@ static int vgem_mmap(struct file *filp, struct vm_area_struct *vma)
>> /* Keep the WC mmaping set by drm_gem_mmap() but our pages
>> * are ordinary and not special.
>> */
>> - vma->vm_flags = flags | VM_DONTEXPAND | VM_DONTDUMP;
>> + vma->vm_flags = flags | VM_DONTEXPAND | VM_DONTDUMP | VM_MIXEDMAP;
>> +
>> return 0;
>> }
>>
>>
>>
>> _______________________________________________
>> dri-devel mailing list
>> dri-devel at lists.freedesktop.org
>> https://lists.freedesktop.org/mailman/listinfo/dri-devel
>
>
>
More information about the dri-devel
mailing list