[PATCH 1/3] drm/radeon: allocate page tables on demand v4

Alex Deucher alexdeucher at gmail.com
Mon Oct 8 09:24:57 PDT 2012


On Mon, Oct 8, 2012 at 11:55 AM, Christian König
<deathsimple at vodafone.de> wrote:
> Based on Dmitries work, but splitting the code into page
> directory and page table handling makes it far more
> readable and (hopefully) more reliable.
>
> Allocations of page tables are made from the SA on demand,
> that should still work fine since all page tables are of
> the same size.
>
> Also using the fact that allocations from the SA are mostly
> continuously (except for end of buffer wraps and under very
> high memory pressure) to group updates send to the chipset
> specific code into larger chunks.
>
> v3: mostly a rewrite of Dmitries previous patch.
> v4: fix some typos and coding style
>
> Signed-off-by: Dmitry Cherkasov <Dmitrii.Cherkasov at amd.com>
> Signed-off-by: Christian König <deathsimple at vodafone.de>
> Tested-by: Michel Dänzer <michel.daenzer at amd.com>

For the series:

Reviewed-by: Alex Deucher <alexander.deucher at amd.com>

> ---
>  drivers/gpu/drm/radeon/ni.c          |    2 +-
>  drivers/gpu/drm/radeon/radeon.h      |   11 +-
>  drivers/gpu/drm/radeon/radeon_gart.c |  322 ++++++++++++++++++++++++++--------
>  3 files changed, 262 insertions(+), 73 deletions(-)
>
> diff --git a/drivers/gpu/drm/radeon/ni.c b/drivers/gpu/drm/radeon/ni.c
> index 9a46f7d..48e2337 100644
> --- a/drivers/gpu/drm/radeon/ni.c
> +++ b/drivers/gpu/drm/radeon/ni.c
> @@ -1576,7 +1576,7 @@ void cayman_vm_flush(struct radeon_device *rdev, int ridx, struct radeon_vm *vm)
>         radeon_ring_write(ring, 0);
>
>         radeon_ring_write(ring, PACKET0(VM_CONTEXT0_PAGE_TABLE_END_ADDR + (vm->id << 2), 0));
> -       radeon_ring_write(ring, vm->last_pfn);
> +       radeon_ring_write(ring, rdev->vm_manager.max_pfn);
>
>         radeon_ring_write(ring, PACKET0(VM_CONTEXT0_PAGE_TABLE_BASE_ADDR + (vm->id << 2), 0));
>         radeon_ring_write(ring, vm->pd_gpu_addr >> 12);
> diff --git a/drivers/gpu/drm/radeon/radeon.h b/drivers/gpu/drm/radeon/radeon.h
> index b04c064..bc6b56b 100644
> --- a/drivers/gpu/drm/radeon/radeon.h
> +++ b/drivers/gpu/drm/radeon/radeon.h
> @@ -663,9 +663,14 @@ struct radeon_vm {
>         struct list_head                list;
>         struct list_head                va;
>         unsigned                        id;
> -       unsigned                        last_pfn;
> -       u64                             pd_gpu_addr;
> -       struct radeon_sa_bo             *sa_bo;
> +
> +       /* contains the page directory */
> +       struct radeon_sa_bo             *page_directory;
> +       uint64_t                        pd_gpu_addr;
> +
> +       /* array of page tables, one for each page directory entry */
> +       struct radeon_sa_bo             **page_tables;
> +
>         struct mutex                    mutex;
>         /* last fence for cs using this vm */
>         struct radeon_fence             *fence;
> diff --git a/drivers/gpu/drm/radeon/radeon_gart.c b/drivers/gpu/drm/radeon/radeon_gart.c
> index 753b7ca..b36b615 100644
> --- a/drivers/gpu/drm/radeon/radeon_gart.c
> +++ b/drivers/gpu/drm/radeon/radeon_gart.c
> @@ -423,6 +423,18 @@ void radeon_gart_fini(struct radeon_device *rdev)
>   */
>
>  /**
> + * radeon_vm_num_pde - return the number of page directory entries
> + *
> + * @rdev: radeon_device pointer
> + *
> + * Calculate the number of page directory entries (cayman+).
> + */
> +static unsigned radeon_vm_num_pdes(struct radeon_device *rdev)
> +{
> +       return rdev->vm_manager.max_pfn >> RADEON_VM_BLOCK_SIZE;
> +}
> +
> +/**
>   * radeon_vm_directory_size - returns the size of the page directory in bytes
>   *
>   * @rdev: radeon_device pointer
> @@ -431,7 +443,7 @@ void radeon_gart_fini(struct radeon_device *rdev)
>   */
>  static unsigned radeon_vm_directory_size(struct radeon_device *rdev)
>  {
> -       return (rdev->vm_manager.max_pfn >> RADEON_VM_BLOCK_SIZE) * 8;
> +       return RADEON_GPU_PAGE_ALIGN(radeon_vm_num_pdes(rdev) * 8);
>  }
>
>  /**
> @@ -451,11 +463,11 @@ int radeon_vm_manager_init(struct radeon_device *rdev)
>
>         if (!rdev->vm_manager.enabled) {
>                 /* allocate enough for 2 full VM pts */
> -               size = RADEON_GPU_PAGE_ALIGN(radeon_vm_directory_size(rdev));
> -               size += RADEON_GPU_PAGE_ALIGN(rdev->vm_manager.max_pfn * 8);
> +               size = radeon_vm_directory_size(rdev);
> +               size += rdev->vm_manager.max_pfn * 8;
>                 size *= 2;
>                 r = radeon_sa_bo_manager_init(rdev, &rdev->vm_manager.sa_manager,
> -                                             size,
> +                                             RADEON_GPU_PAGE_ALIGN(size),
>                                               RADEON_GEM_DOMAIN_VRAM);
>                 if (r) {
>                         dev_err(rdev->dev, "failed to allocate vm bo (%dKB)\n",
> @@ -476,7 +488,7 @@ int radeon_vm_manager_init(struct radeon_device *rdev)
>
>         /* restore page table */
>         list_for_each_entry(vm, &rdev->vm_manager.lru_vm, list) {
> -               if (vm->sa_bo == NULL)
> +               if (vm->page_directory == NULL)
>                         continue;
>
>                 list_for_each_entry(bo_va, &vm->va, vm_list) {
> @@ -500,16 +512,25 @@ static void radeon_vm_free_pt(struct radeon_device *rdev,
>                                     struct radeon_vm *vm)
>  {
>         struct radeon_bo_va *bo_va;
> +       int i;
>
> -       if (!vm->sa_bo)
> +       if (!vm->page_directory)
>                 return;
>
>         list_del_init(&vm->list);
> -       radeon_sa_bo_free(rdev, &vm->sa_bo, vm->fence);
> +       radeon_sa_bo_free(rdev, &vm->page_directory, vm->fence);
>
>         list_for_each_entry(bo_va, &vm->va, vm_list) {
>                 bo_va->valid = false;
>         }
> +
> +       if (vm->page_tables == NULL)
> +               return;
> +
> +       for (i = 0; i < radeon_vm_num_pdes(rdev); i++)
> +               radeon_sa_bo_free(rdev, &vm->page_tables[i], vm->fence);
> +
> +       kfree(vm->page_tables);
>  }
>
>  /**
> @@ -546,6 +567,35 @@ void radeon_vm_manager_fini(struct radeon_device *rdev)
>  }
>
>  /**
> + * radeon_vm_evict - evict page table to make room for new one
> + *
> + * @rdev: radeon_device pointer
> + * @vm: VM we want to allocate something for
> + *
> + * Evict a VM from the lru, making sure that it isn't @vm. (cayman+).
> + * Returns 0 for success, -ENOMEM for failure.
> + *
> + * Global and local mutex must be locked!
> + */
> +int radeon_vm_evict(struct radeon_device *rdev, struct radeon_vm *vm)
> +{
> +       struct radeon_vm *vm_evict;
> +
> +       if (list_empty(&rdev->vm_manager.lru_vm))
> +               return -ENOMEM;
> +
> +       vm_evict = list_first_entry(&rdev->vm_manager.lru_vm,
> +                                   struct radeon_vm, list);
> +       if (vm_evict == vm)
> +               return -ENOMEM;
> +
> +       mutex_lock(&vm_evict->mutex);
> +       radeon_vm_free_pt(rdev, vm_evict);
> +       mutex_unlock(&vm_evict->mutex);
> +       return 0;
> +}
> +
> +/**
>   * radeon_vm_alloc_pt - allocates a page table for a VM
>   *
>   * @rdev: radeon_device pointer
> @@ -559,20 +609,15 @@ void radeon_vm_manager_fini(struct radeon_device *rdev)
>   */
>  int radeon_vm_alloc_pt(struct radeon_device *rdev, struct radeon_vm *vm)
>  {
> -       struct radeon_vm *vm_evict;
> -       int r;
> +       unsigned pd_size, pts_size;
>         u64 *pd_addr;
> -       int tables_size;
> +       int r;
>
>         if (vm == NULL) {
>                 return -EINVAL;
>         }
>
> -       /* allocate enough to cover the current VM size */
> -       tables_size = RADEON_GPU_PAGE_ALIGN(radeon_vm_directory_size(rdev));
> -       tables_size += RADEON_GPU_PAGE_ALIGN(vm->last_pfn * 8);
> -
> -       if (vm->sa_bo != NULL) {
> +       if (vm->page_directory != NULL) {
>                 /* update lru */
>                 list_del_init(&vm->list);
>                 list_add_tail(&vm->list, &rdev->vm_manager.lru_vm);
> @@ -580,25 +625,34 @@ int radeon_vm_alloc_pt(struct radeon_device *rdev, struct radeon_vm *vm)
>         }
>
>  retry:
> -       r = radeon_sa_bo_new(rdev, &rdev->vm_manager.sa_manager, &vm->sa_bo,
> -                            tables_size, RADEON_GPU_PAGE_SIZE, false);
> +       pd_size = RADEON_GPU_PAGE_ALIGN(radeon_vm_directory_size(rdev));
> +       r = radeon_sa_bo_new(rdev, &rdev->vm_manager.sa_manager,
> +                            &vm->page_directory, pd_size,
> +                            RADEON_GPU_PAGE_SIZE, false);
>         if (r == -ENOMEM) {
> -               if (list_empty(&rdev->vm_manager.lru_vm)) {
> +               r = radeon_vm_evict(rdev, vm);
> +               if (r)
>                         return r;
> -               }
> -               vm_evict = list_first_entry(&rdev->vm_manager.lru_vm, struct radeon_vm, list);
> -               mutex_lock(&vm_evict->mutex);
> -               radeon_vm_free_pt(rdev, vm_evict);
> -               mutex_unlock(&vm_evict->mutex);
>                 goto retry;
>
>         } else if (r) {
>                 return r;
>         }
>
> -       pd_addr = radeon_sa_bo_cpu_addr(vm->sa_bo);
> -       vm->pd_gpu_addr = radeon_sa_bo_gpu_addr(vm->sa_bo);
> -       memset(pd_addr, 0, tables_size);
> +       vm->pd_gpu_addr = radeon_sa_bo_gpu_addr(vm->page_directory);
> +
> +       /* Initially clear the page directory */
> +       pd_addr = radeon_sa_bo_cpu_addr(vm->page_directory);
> +       memset(pd_addr, 0, pd_size);
> +
> +       pts_size = radeon_vm_num_pdes(rdev) * sizeof(struct radeon_sa_bo *);
> +       vm->page_tables = kzalloc(pts_size, GFP_KERNEL);
> +
> +       if (vm->page_tables == NULL) {
> +               DRM_ERROR("Cannot allocate memory for page table array\n");
> +               radeon_sa_bo_free(rdev, &vm->page_directory, vm->fence);
> +               return -ENOMEM;
> +       }
>
>         list_add_tail(&vm->list, &rdev->vm_manager.lru_vm);
>         return radeon_vm_bo_update_pte(rdev, vm, rdev->ring_tmp_bo.bo,
> @@ -793,20 +847,6 @@ int radeon_vm_bo_set_addr(struct radeon_device *rdev,
>         }
>
>         mutex_lock(&vm->mutex);
> -       if (last_pfn > vm->last_pfn) {
> -               /* release mutex and lock in right order */
> -               mutex_unlock(&vm->mutex);
> -               mutex_lock(&rdev->vm_manager.lock);
> -               mutex_lock(&vm->mutex);
> -               /* and check again */
> -               if (last_pfn > vm->last_pfn) {
> -                       /* grow va space 32M by 32M */
> -                       unsigned align = ((32 << 20) >> 12) - 1;
> -                       radeon_vm_free_pt(rdev, vm);
> -                       vm->last_pfn = (last_pfn + align) & ~align;
> -               }
> -               mutex_unlock(&rdev->vm_manager.lock);
> -       }
>         head = &vm->va;
>         last_offset = 0;
>         list_for_each_entry(tmp, &vm->va, vm_list) {
> @@ -865,6 +905,155 @@ uint64_t radeon_vm_map_gart(struct radeon_device *rdev, uint64_t addr)
>  }
>
>  /**
> + * radeon_vm_update_pdes - make sure that page directory is valid
> + *
> + * @rdev: radeon_device pointer
> + * @vm: requested vm
> + * @start: start of GPU address range
> + * @end: end of GPU address range
> + *
> + * Allocates new page tables if necessary
> + * and updates the page directory (cayman+).
> + * Returns 0 for success, error for failure.
> + *
> + * Global and local mutex must be locked!
> + */
> +static int radeon_vm_update_pdes(struct radeon_device *rdev,
> +                                struct radeon_vm *vm,
> +                                uint64_t start, uint64_t end)
> +{
> +       static const uint32_t incr = RADEON_VM_PTE_COUNT * 8;
> +
> +       uint64_t last_pde = ~0, last_pt = ~0;
> +       unsigned count = 0;
> +       uint64_t pt_idx;
> +       int r;
> +
> +       start = (start / RADEON_GPU_PAGE_SIZE) >> RADEON_VM_BLOCK_SIZE;
> +       end = (end / RADEON_GPU_PAGE_SIZE) >> RADEON_VM_BLOCK_SIZE;
> +
> +       /* walk over the address space and update the page directory */
> +       for (pt_idx = start; pt_idx <= end; ++pt_idx) {
> +               uint64_t pde, pt;
> +
> +               if (vm->page_tables[pt_idx])
> +                       continue;
> +
> +retry:
> +               r = radeon_sa_bo_new(rdev, &rdev->vm_manager.sa_manager,
> +                                    &vm->page_tables[pt_idx],
> +                                    RADEON_VM_PTE_COUNT * 8,
> +                                    RADEON_GPU_PAGE_SIZE, false);
> +
> +               if (r == -ENOMEM) {
> +                       r = radeon_vm_evict(rdev, vm);
> +                       if (r)
> +                               return r;
> +                       goto retry;
> +               } else if (r) {
> +                       return r;
> +               }
> +
> +               pde = vm->pd_gpu_addr + pt_idx * 8;
> +
> +               pt = radeon_sa_bo_gpu_addr(vm->page_tables[pt_idx]);
> +
> +               if (((last_pde + 8 * count) != pde) ||
> +                   ((last_pt + incr * count) != pt)) {
> +
> +                       if (count) {
> +                               radeon_asic_vm_set_page(rdev, last_pde,
> +                                                       last_pt, count, incr,
> +                                                       RADEON_VM_PAGE_VALID);
> +                       }
> +
> +                       count = 1;
> +                       last_pde = pde;
> +                       last_pt = pt;
> +               } else {
> +                       ++count;
> +               }
> +       }
> +
> +       if (count) {
> +               radeon_asic_vm_set_page(rdev, last_pde, last_pt, count,
> +                                       incr, RADEON_VM_PAGE_VALID);
> +
> +       }
> +
> +       return 0;
> +}
> +
> +/**
> + * radeon_vm_update_ptes - make sure that page tables are valid
> + *
> + * @rdev: radeon_device pointer
> + * @vm: requested vm
> + * @start: start of GPU address range
> + * @end: end of GPU address range
> + * @dst: destination address to map to
> + * @flags: mapping flags
> + *
> + * Update the page tables in the range @start - @end (cayman+).
> + *
> + * Global and local mutex must be locked!
> + */
> +static void radeon_vm_update_ptes(struct radeon_device *rdev,
> +                                 struct radeon_vm *vm,
> +                                 uint64_t start, uint64_t end,
> +                                 uint64_t dst, uint32_t flags)
> +{
> +       static const uint64_t mask = RADEON_VM_PTE_COUNT - 1;
> +
> +       uint64_t last_pte = ~0, last_dst = ~0;
> +       unsigned count = 0;
> +       uint64_t addr;
> +
> +       start = start / RADEON_GPU_PAGE_SIZE;
> +       end = end / RADEON_GPU_PAGE_SIZE;
> +
> +       /* walk over the address space and update the page tables */
> +       for (addr = start; addr < end; ) {
> +               uint64_t pt_idx = addr >> RADEON_VM_BLOCK_SIZE;
> +               unsigned nptes;
> +               uint64_t pte;
> +
> +               if ((addr & ~mask) == (end & ~mask))
> +                       nptes = end - addr;
> +               else
> +                       nptes = RADEON_VM_PTE_COUNT - (addr & mask);
> +
> +               pte = radeon_sa_bo_gpu_addr(vm->page_tables[pt_idx]);
> +               pte += (addr & mask) * 8;
> +
> +               if (((last_pte + 8 * count) != pte) ||
> +                   ((count + nptes) > 1 << 11)) {
> +
> +                       if (count) {
> +                               radeon_asic_vm_set_page(rdev, last_pte,
> +                                                       last_dst, count,
> +                                                       RADEON_GPU_PAGE_SIZE,
> +                                                       flags);
> +                       }
> +
> +                       count = nptes;
> +                       last_pte = pte;
> +                       last_dst = dst;
> +               } else {
> +                       count += nptes;
> +               }
> +
> +               addr += nptes;
> +               dst += nptes * RADEON_GPU_PAGE_SIZE;
> +       }
> +
> +       if (count) {
> +               radeon_asic_vm_set_page(rdev, last_pte, last_dst, count,
> +                                       RADEON_GPU_PAGE_SIZE, flags);
> +       }
> +}
> +
> +/**
>   * radeon_vm_bo_update_pte - map a bo into the vm page table
>   *
>   * @rdev: radeon_device pointer
> @@ -887,12 +1076,11 @@ int radeon_vm_bo_update_pte(struct radeon_device *rdev,
>         struct radeon_semaphore *sem = NULL;
>         struct radeon_bo_va *bo_va;
>         unsigned nptes, npdes, ndw;
> -       uint64_t pe, addr;
> -       uint64_t pfn;
> +       uint64_t addr;
>         int r;
>
>         /* nothing to do if vm isn't bound */
> -       if (vm->sa_bo == NULL)
> +       if (vm->page_directory == NULL)
>                 return 0;
>
>         bo_va = radeon_vm_bo_find(vm, bo);
> @@ -939,25 +1127,29 @@ int radeon_vm_bo_update_pte(struct radeon_device *rdev,
>                 }
>         }
>
> -       /* estimate number of dw needed */
> -       /* reserve space for 32-bit padding */
> -       ndw = 32;
> -
>         nptes = radeon_bo_ngpu_pages(bo);
>
> -       pfn = (bo_va->soffset / RADEON_GPU_PAGE_SIZE);
> +       /* assume two extra pdes in case the mapping overlaps the borders */
> +       npdes = (nptes >> RADEON_VM_BLOCK_SIZE) + 2;
> +
> +       /* estimate number of dw needed */
> +       /* semaphore, fence and padding */
> +       ndw = 32;
>
> -       /* handle cases where a bo spans several pdes  */
> -       npdes = (ALIGN(pfn + nptes, RADEON_VM_PTE_COUNT) -
> -                (pfn & ~(RADEON_VM_PTE_COUNT - 1))) >> RADEON_VM_BLOCK_SIZE;
> +       if (RADEON_VM_BLOCK_SIZE > 11)
> +               /* reserve space for one header for every 2k dwords */
> +               ndw += (nptes >> 11) * 3;
> +       else
> +               /* reserve space for one header for
> +                   every (1 << BLOCK_SIZE) entries */
> +               ndw += (nptes >> RADEON_VM_BLOCK_SIZE) * 3;
>
> -       /* reserve space for one header for every 2k dwords */
> -       ndw += (nptes >> 11) * 3;
>         /* reserve space for pte addresses */
>         ndw += nptes * 2;
>
>         /* reserve space for one header for every 2k dwords */
>         ndw += (npdes >> 11) * 3;
> +
>         /* reserve space for pde addresses */
>         ndw += npdes * 2;
>
> @@ -971,22 +1163,14 @@ int radeon_vm_bo_update_pte(struct radeon_device *rdev,
>                 radeon_fence_note_sync(vm->fence, ridx);
>         }
>
> -       /* update page table entries */
> -       pe = vm->pd_gpu_addr;
> -       pe += radeon_vm_directory_size(rdev);
> -       pe += (bo_va->soffset / RADEON_GPU_PAGE_SIZE) * 8;
> -
> -       radeon_asic_vm_set_page(rdev, pe, addr, nptes,
> -                               RADEON_GPU_PAGE_SIZE, bo_va->flags);
> -
> -       /* update page directory entries */
> -       addr = pe;
> -
> -       pe = vm->pd_gpu_addr;
> -       pe += ((bo_va->soffset / RADEON_GPU_PAGE_SIZE) >> RADEON_VM_BLOCK_SIZE) * 8;
> +       r = radeon_vm_update_pdes(rdev, vm, bo_va->soffset, bo_va->eoffset);
> +       if (r) {
> +               radeon_ring_unlock_undo(rdev, ring);
> +               return r;
> +       }
>
> -       radeon_asic_vm_set_page(rdev, pe, addr, npdes,
> -                               RADEON_VM_PTE_COUNT * 8, RADEON_VM_PAGE_VALID);
> +       radeon_vm_update_ptes(rdev, vm, bo_va->soffset, bo_va->eoffset,
> +                             addr, bo_va->flags);
>
>         radeon_fence_unref(&vm->fence);
>         r = radeon_fence_emit(rdev, &vm->fence, ridx);
> @@ -997,6 +1181,7 @@ int radeon_vm_bo_update_pte(struct radeon_device *rdev,
>         radeon_ring_unlock_commit(rdev, ring);
>         radeon_semaphore_free(rdev, &sem, vm->fence);
>         radeon_fence_unref(&vm->last_flush);
> +
>         return 0;
>  }
>
> @@ -1068,7 +1253,6 @@ int radeon_vm_init(struct radeon_device *rdev, struct radeon_vm *vm)
>
>         vm->id = 0;
>         vm->fence = NULL;
> -       vm->last_pfn = 0;
>         mutex_init(&vm->mutex);
>         INIT_LIST_HEAD(&vm->list);
>         INIT_LIST_HEAD(&vm->va);
> --
> 1.7.9.5
>


More information about the dri-devel mailing list