Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 | /* * SPDX-License-Identifier: MIT * * Copyright © 2016 Intel Corporation */ #include "i915_scatterlist.h" #include "i915_ttm_buddy_manager.h" #include <drm/drm_buddy.h> #include <drm/drm_mm.h> #include <linux/slab.h> bool i915_sg_trim(struct sg_table *orig_st) { struct sg_table new_st; struct scatterlist *sg, *new_sg; unsigned int i; if (orig_st->nents == orig_st->orig_nents) return false; if (sg_alloc_table(&new_st, orig_st->nents, GFP_KERNEL | __GFP_NOWARN)) return false; new_sg = new_st.sgl; for_each_sg(orig_st->sgl, sg, orig_st->nents, i) { sg_set_page(new_sg, sg_page(sg), sg->length, 0); sg_dma_address(new_sg) = sg_dma_address(sg); sg_dma_len(new_sg) = sg_dma_len(sg); new_sg = sg_next(new_sg); } GEM_BUG_ON(new_sg); /* Should walk exactly nents and hit the end */ sg_free_table(orig_st); *orig_st = new_st; return true; } static void i915_refct_sgt_release(struct kref *ref) { struct i915_refct_sgt *rsgt = container_of(ref, typeof(*rsgt), kref); sg_free_table(&rsgt->table); kfree(rsgt); } static const struct i915_refct_sgt_ops rsgt_ops = { .release = i915_refct_sgt_release }; /** * i915_refct_sgt_init - Initialize a struct i915_refct_sgt with default ops * @rsgt: The struct i915_refct_sgt to initialize. * size: The size of the underlying memory buffer. */ void i915_refct_sgt_init(struct i915_refct_sgt *rsgt, size_t size) { __i915_refct_sgt_init(rsgt, size, &rsgt_ops); } /** * i915_rsgt_from_mm_node - Create a refcounted sg_table from a struct * drm_mm_node * @node: The drm_mm_node. * @region_start: An offset to add to the dma addresses of the sg list. * @page_alignment: Required page alignment for each sg entry. Power of two. * * Create a struct sg_table, initializing it from a struct drm_mm_node, * taking a maximum segment length into account, splitting into segments * if necessary. * * Return: A pointer to a kmalloced struct i915_refct_sgt on success, negative * error code cast to an error pointer on failure. */ struct i915_refct_sgt *i915_rsgt_from_mm_node(const struct drm_mm_node *node, u64 region_start, u32 page_alignment) { const u32 max_segment = round_down(UINT_MAX, page_alignment); const u32 segment_pages = max_segment >> PAGE_SHIFT; u64 block_size, offset, prev_end; struct i915_refct_sgt *rsgt; struct sg_table *st; struct scatterlist *sg; GEM_BUG_ON(!max_segment); rsgt = kmalloc(sizeof(*rsgt), GFP_KERNEL); if (!rsgt) return ERR_PTR(-ENOMEM); i915_refct_sgt_init(rsgt, node->size << PAGE_SHIFT); st = &rsgt->table; if (sg_alloc_table(st, DIV_ROUND_UP_ULL(node->size, segment_pages), GFP_KERNEL)) { i915_refct_sgt_put(rsgt); return ERR_PTR(-ENOMEM); } sg = st->sgl; st->nents = 0; prev_end = (resource_size_t)-1; block_size = node->size << PAGE_SHIFT; offset = node->start << PAGE_SHIFT; while (block_size) { u64 len; if (offset != prev_end || sg->length >= max_segment) { if (st->nents) sg = __sg_next(sg); sg_dma_address(sg) = region_start + offset; GEM_BUG_ON(!IS_ALIGNED(sg_dma_address(sg), page_alignment)); sg_dma_len(sg) = 0; sg->length = 0; st->nents++; } len = min_t(u64, block_size, max_segment - sg->length); sg->length += len; sg_dma_len(sg) += len; offset += len; block_size -= len; prev_end = offset; } sg_mark_end(sg); i915_sg_trim(st); return rsgt; } /** * i915_rsgt_from_buddy_resource - Create a refcounted sg_table from a struct * i915_buddy_block list * @res: The struct i915_ttm_buddy_resource. * @region_start: An offset to add to the dma addresses of the sg list. * @page_alignment: Required page alignment for each sg entry. Power of two. * * Create a struct sg_table, initializing it from struct i915_buddy_block list, * taking a maximum segment length into account, splitting into segments * if necessary. * * Return: A pointer to a kmalloced struct i915_refct_sgts on success, negative * error code cast to an error pointer on failure. */ struct i915_refct_sgt *i915_rsgt_from_buddy_resource(struct ttm_resource *res, u64 region_start, u32 page_alignment) { struct i915_ttm_buddy_resource *bman_res = to_ttm_buddy_resource(res); const u64 size = res->num_pages << PAGE_SHIFT; const u32 max_segment = round_down(UINT_MAX, page_alignment); struct drm_buddy *mm = bman_res->mm; struct list_head *blocks = &bman_res->blocks; struct drm_buddy_block *block; struct i915_refct_sgt *rsgt; struct scatterlist *sg; struct sg_table *st; resource_size_t prev_end; GEM_BUG_ON(list_empty(blocks)); GEM_BUG_ON(!max_segment); rsgt = kmalloc(sizeof(*rsgt), GFP_KERNEL); if (!rsgt) return ERR_PTR(-ENOMEM); i915_refct_sgt_init(rsgt, size); st = &rsgt->table; if (sg_alloc_table(st, res->num_pages, GFP_KERNEL)) { i915_refct_sgt_put(rsgt); return ERR_PTR(-ENOMEM); } sg = st->sgl; st->nents = 0; prev_end = (resource_size_t)-1; list_for_each_entry(block, blocks, link) { u64 block_size, offset; block_size = min_t(u64, size, drm_buddy_block_size(mm, block)); offset = drm_buddy_block_offset(block); while (block_size) { u64 len; if (offset != prev_end || sg->length >= max_segment) { if (st->nents) sg = __sg_next(sg); sg_dma_address(sg) = region_start + offset; GEM_BUG_ON(!IS_ALIGNED(sg_dma_address(sg), page_alignment)); sg_dma_len(sg) = 0; sg->length = 0; st->nents++; } len = min_t(u64, block_size, max_segment - sg->length); sg->length += len; sg_dma_len(sg) += len; offset += len; block_size -= len; prev_end = offset; } } sg_mark_end(sg); i915_sg_trim(st); return rsgt; } #if IS_ENABLED(CONFIG_DRM_I915_SELFTEST) #include "selftests/scatterlist.c" #endif |