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 <drm/drm_mm.h>
  10
  11#include <linux/slab.h>
  12
  13bool i915_sg_trim(struct sg_table *orig_st)
  14{
  15        struct sg_table new_st;
  16        struct scatterlist *sg, *new_sg;
  17        unsigned int i;
  18
  19        if (orig_st->nents == orig_st->orig_nents)
  20                return false;
  21
  22        if (sg_alloc_table(&new_st, orig_st->nents, GFP_KERNEL | __GFP_NOWARN))
  23                return false;
  24
  25        new_sg = new_st.sgl;
  26        for_each_sg(orig_st->sgl, sg, orig_st->nents, i) {
  27                sg_set_page(new_sg, sg_page(sg), sg->length, 0);
  28                sg_dma_address(new_sg) = sg_dma_address(sg);
  29                sg_dma_len(new_sg) = sg_dma_len(sg);
  30
  31                new_sg = sg_next(new_sg);
  32        }
  33        GEM_BUG_ON(new_sg); /* Should walk exactly nents and hit the end */
  34
  35        sg_free_table(orig_st);
  36
  37        *orig_st = new_st;
  38        return true;
  39}
  40
  41/**
  42 * i915_sg_from_mm_node - Create an sg_table from a struct drm_mm_node
  43 * @node: The drm_mm_node.
  44 * @region_start: An offset to add to the dma addresses of the sg list.
  45 *
  46 * Create a struct sg_table, initializing it from a struct drm_mm_node,
  47 * taking a maximum segment length into account, splitting into segments
  48 * if necessary.
  49 *
  50 * Return: A pointer to a kmalloced struct sg_table on success, negative
  51 * error code cast to an error pointer on failure.
  52 */
  53struct sg_table *i915_sg_from_mm_node(const struct drm_mm_node *node,
  54                                      u64 region_start)
  55{
  56        const u64 max_segment = SZ_1G; /* Do we have a limit on this? */
  57        u64 segment_pages = max_segment >> PAGE_SHIFT;
  58        u64 block_size, offset, prev_end;
  59        struct sg_table *st;
  60        struct scatterlist *sg;
  61
  62        st = kmalloc(sizeof(*st), GFP_KERNEL);
  63        if (!st)
  64                return ERR_PTR(-ENOMEM);
  65
  66        if (sg_alloc_table(st, DIV_ROUND_UP(node->size, segment_pages),
  67                           GFP_KERNEL)) {
  68                kfree(st);
  69                return ERR_PTR(-ENOMEM);
  70        }
  71
  72        sg = st->sgl;
  73        st->nents = 0;
  74        prev_end = (resource_size_t)-1;
  75        block_size = node->size << PAGE_SHIFT;
  76        offset = node->start << PAGE_SHIFT;
  77
  78        while (block_size) {
  79                u64 len;
  80
  81                if (offset != prev_end || sg->length >= max_segment) {
  82                        if (st->nents)
  83                                sg = __sg_next(sg);
  84
  85                        sg_dma_address(sg) = region_start + offset;
  86                        sg_dma_len(sg) = 0;
  87                        sg->length = 0;
  88                        st->nents++;
  89                }
  90
  91                len = min(block_size, max_segment - sg->length);
  92                sg->length += len;
  93                sg_dma_len(sg) += len;
  94
  95                offset += len;
  96                block_size -= len;
  97
  98                prev_end = offset;
  99        }
 100
 101        sg_mark_end(sg);
 102        i915_sg_trim(st);
 103
 104        return st;
 105}
 106
 107#if IS_ENABLED(CONFIG_DRM_I915_SELFTEST)
 108#include "selftests/scatterlist.c"
 109#endif
 110