linux/drivers/gpu/drm/i915/i915_scatterlist.c
<<
>>
Prefs
   1/*
   2 * SPDX-License-Identifier: MIT
   3 *
   4 * Copyright © 2016 Intel Corporation
   5 */
   6
   7#include "i915_scatterlist.h"
   8
   9#include "i915_buddy.h"
  10#include "i915_ttm_buddy_manager.h"
  11
  12#include <drm/drm_mm.h>
  13
  14#include <linux/slab.h>
  15
  16bool i915_sg_trim(struct sg_table *orig_st)
  17{
  18        struct sg_table new_st;
  19        struct scatterlist *sg, *new_sg;
  20        unsigned int i;
  21
  22        if (orig_st->nents == orig_st->orig_nents)
  23                return false;
  24
  25        if (sg_alloc_table(&new_st, orig_st->nents, GFP_KERNEL | __GFP_NOWARN))
  26                return false;
  27
  28        new_sg = new_st.sgl;
  29        for_each_sg(orig_st->sgl, sg, orig_st->nents, i) {
  30                sg_set_page(new_sg, sg_page(sg), sg->length, 0);
  31                sg_dma_address(new_sg) = sg_dma_address(sg);
  32                sg_dma_len(new_sg) = sg_dma_len(sg);
  33
  34                new_sg = sg_next(new_sg);
  35        }
  36        GEM_BUG_ON(new_sg); /* Should walk exactly nents and hit the end */
  37
  38        sg_free_table(orig_st);
  39
  40        *orig_st = new_st;
  41        return true;
  42}
  43
  44/**
  45 * i915_sg_from_mm_node - Create an sg_table from a struct drm_mm_node
  46 * @node: The drm_mm_node.
  47 * @region_start: An offset to add to the dma addresses of the sg list.
  48 *
  49 * Create a struct sg_table, initializing it from a struct drm_mm_node,
  50 * taking a maximum segment length into account, splitting into segments
  51 * if necessary.
  52 *
  53 * Return: A pointer to a kmalloced struct sg_table on success, negative
  54 * error code cast to an error pointer on failure.
  55 */
  56struct sg_table *i915_sg_from_mm_node(const struct drm_mm_node *node,
  57                                      u64 region_start)
  58{
  59        const u64 max_segment = SZ_1G; /* Do we have a limit on this? */
  60        u64 segment_pages = max_segment >> PAGE_SHIFT;
  61        u64 block_size, offset, prev_end;
  62        struct sg_table *st;
  63        struct scatterlist *sg;
  64
  65        st = kmalloc(sizeof(*st), GFP_KERNEL);
  66        if (!st)
  67                return ERR_PTR(-ENOMEM);
  68
  69        if (sg_alloc_table(st, DIV_ROUND_UP(node->size, segment_pages),
  70                           GFP_KERNEL)) {
  71                kfree(st);
  72                return ERR_PTR(-ENOMEM);
  73        }
  74
  75        sg = st->sgl;
  76        st->nents = 0;
  77        prev_end = (resource_size_t)-1;
  78        block_size = node->size << PAGE_SHIFT;
  79        offset = node->start << PAGE_SHIFT;
  80
  81        while (block_size) {
  82                u64 len;
  83
  84                if (offset != prev_end || sg->length >= max_segment) {
  85                        if (st->nents)
  86                                sg = __sg_next(sg);
  87
  88                        sg_dma_address(sg) = region_start + offset;
  89                        sg_dma_len(sg) = 0;
  90                        sg->length = 0;
  91                        st->nents++;
  92                }
  93
  94                len = min(block_size, max_segment - sg->length);
  95                sg->length += len;
  96                sg_dma_len(sg) += len;
  97
  98                offset += len;
  99                block_size -= len;
 100
 101                prev_end = offset;
 102        }
 103
 104        sg_mark_end(sg);
 105        i915_sg_trim(st);
 106
 107        return st;
 108}
 109
 110/**
 111 * i915_sg_from_buddy_resource - Create an sg_table from a struct
 112 * i915_buddy_block list
 113 * @res: The struct i915_ttm_buddy_resource.
 114 * @region_start: An offset to add to the dma addresses of the sg list.
 115 *
 116 * Create a struct sg_table, initializing it from struct i915_buddy_block list,
 117 * taking a maximum segment length into account, splitting into segments
 118 * if necessary.
 119 *
 120 * Return: A pointer to a kmalloced struct sg_table on success, negative
 121 * error code cast to an error pointer on failure.
 122 */
 123struct sg_table *i915_sg_from_buddy_resource(struct ttm_resource *res,
 124                                             u64 region_start)
 125{
 126        struct i915_ttm_buddy_resource *bman_res = to_ttm_buddy_resource(res);
 127        const u64 size = res->num_pages << PAGE_SHIFT;
 128        const u64 max_segment = rounddown(UINT_MAX, PAGE_SIZE);
 129        struct i915_buddy_mm *mm = bman_res->mm;
 130        struct list_head *blocks = &bman_res->blocks;
 131        struct i915_buddy_block *block;
 132        struct scatterlist *sg;
 133        struct sg_table *st;
 134        resource_size_t prev_end;
 135
 136        GEM_BUG_ON(list_empty(blocks));
 137
 138        st = kmalloc(sizeof(*st), GFP_KERNEL);
 139        if (!st)
 140                return ERR_PTR(-ENOMEM);
 141
 142        if (sg_alloc_table(st, res->num_pages, GFP_KERNEL)) {
 143                kfree(st);
 144                return ERR_PTR(-ENOMEM);
 145        }
 146
 147        sg = st->sgl;
 148        st->nents = 0;
 149        prev_end = (resource_size_t)-1;
 150
 151        list_for_each_entry(block, blocks, link) {
 152                u64 block_size, offset;
 153
 154                block_size = min_t(u64, size, i915_buddy_block_size(mm, block));
 155                offset = i915_buddy_block_offset(block);
 156
 157                while (block_size) {
 158                        u64 len;
 159
 160                        if (offset != prev_end || sg->length >= max_segment) {
 161                                if (st->nents)
 162                                        sg = __sg_next(sg);
 163
 164                                sg_dma_address(sg) = region_start + offset;
 165                                sg_dma_len(sg) = 0;
 166                                sg->length = 0;
 167                                st->nents++;
 168                        }
 169
 170                        len = min(block_size, max_segment - sg->length);
 171                        sg->length += len;
 172                        sg_dma_len(sg) += len;
 173
 174                        offset += len;
 175                        block_size -= len;
 176
 177                        prev_end = offset;
 178                }
 179        }
 180
 181        sg_mark_end(sg);
 182        i915_sg_trim(st);
 183
 184        return st;
 185}
 186
 187#if IS_ENABLED(CONFIG_DRM_I915_SELFTEST)
 188#include "selftests/scatterlist.c"
 189#endif
 190