xref: /openbsd-src/sys/dev/pci/drm/i915/i915_scatterlist.c (revision f005ef32267c16bdb134f0e9fa4477dbe07c263a)
1c349dbc7Sjsg /*
2c349dbc7Sjsg  * SPDX-License-Identifier: MIT
3c349dbc7Sjsg  *
4c349dbc7Sjsg  * Copyright © 2016 Intel Corporation
5c349dbc7Sjsg  */
6c349dbc7Sjsg 
7c349dbc7Sjsg #include "i915_scatterlist.h"
85ca02815Sjsg #include "i915_ttm_buddy_manager.h"
95ca02815Sjsg 
101bb76ff1Sjsg #include <drm/drm_buddy.h>
115ca02815Sjsg #include <drm/drm_mm.h>
125ca02815Sjsg 
135ca02815Sjsg #include <linux/slab.h>
145ca02815Sjsg 
i915_sg_trim(struct sg_table * orig_st)15c349dbc7Sjsg bool i915_sg_trim(struct sg_table *orig_st)
16c349dbc7Sjsg {
17c349dbc7Sjsg 	struct sg_table new_st;
18c349dbc7Sjsg 	struct scatterlist *sg, *new_sg;
19c349dbc7Sjsg 	unsigned int i;
20c349dbc7Sjsg 
21c349dbc7Sjsg 	if (orig_st->nents == orig_st->orig_nents)
22c349dbc7Sjsg 		return false;
23c349dbc7Sjsg 
24c349dbc7Sjsg 	if (sg_alloc_table(&new_st, orig_st->nents, GFP_KERNEL | __GFP_NOWARN))
25c349dbc7Sjsg 		return false;
26c349dbc7Sjsg 
27c349dbc7Sjsg 	new_sg = new_st.sgl;
28c349dbc7Sjsg 	for_each_sg(orig_st->sgl, sg, orig_st->nents, i) {
29c349dbc7Sjsg 		sg_set_page(new_sg, sg_page(sg), sg->length, 0);
30c349dbc7Sjsg 		sg_dma_address(new_sg) = sg_dma_address(sg);
31c349dbc7Sjsg 		sg_dma_len(new_sg) = sg_dma_len(sg);
32c349dbc7Sjsg 
33c349dbc7Sjsg 		new_sg = sg_next(new_sg);
34c349dbc7Sjsg 	}
35c349dbc7Sjsg 	GEM_BUG_ON(new_sg); /* Should walk exactly nents and hit the end */
36c349dbc7Sjsg 
37c349dbc7Sjsg 	sg_free_table(orig_st);
38c349dbc7Sjsg 
39c349dbc7Sjsg 	*orig_st = new_st;
40c349dbc7Sjsg 	return true;
41c349dbc7Sjsg }
42c349dbc7Sjsg 
i915_refct_sgt_release(struct kref * ref)431bb76ff1Sjsg static void i915_refct_sgt_release(struct kref *ref)
441bb76ff1Sjsg {
451bb76ff1Sjsg 	struct i915_refct_sgt *rsgt =
461bb76ff1Sjsg 		container_of(ref, typeof(*rsgt), kref);
471bb76ff1Sjsg 
481bb76ff1Sjsg 	sg_free_table(&rsgt->table);
491bb76ff1Sjsg 	kfree(rsgt);
501bb76ff1Sjsg }
511bb76ff1Sjsg 
521bb76ff1Sjsg static const struct i915_refct_sgt_ops rsgt_ops = {
531bb76ff1Sjsg 	.release = i915_refct_sgt_release
541bb76ff1Sjsg };
551bb76ff1Sjsg 
565ca02815Sjsg /**
571bb76ff1Sjsg  * i915_refct_sgt_init - Initialize a struct i915_refct_sgt with default ops
581bb76ff1Sjsg  * @rsgt: The struct i915_refct_sgt to initialize.
59*f005ef32Sjsg  * @size: The size of the underlying memory buffer.
601bb76ff1Sjsg  */
i915_refct_sgt_init(struct i915_refct_sgt * rsgt,size_t size)611bb76ff1Sjsg void i915_refct_sgt_init(struct i915_refct_sgt *rsgt, size_t size)
621bb76ff1Sjsg {
631bb76ff1Sjsg 	__i915_refct_sgt_init(rsgt, size, &rsgt_ops);
641bb76ff1Sjsg }
651bb76ff1Sjsg 
661bb76ff1Sjsg /**
671bb76ff1Sjsg  * i915_rsgt_from_mm_node - Create a refcounted sg_table from a struct
681bb76ff1Sjsg  * drm_mm_node
695ca02815Sjsg  * @node: The drm_mm_node.
705ca02815Sjsg  * @region_start: An offset to add to the dma addresses of the sg list.
711bb76ff1Sjsg  * @page_alignment: Required page alignment for each sg entry. Power of two.
725ca02815Sjsg  *
735ca02815Sjsg  * Create a struct sg_table, initializing it from a struct drm_mm_node,
745ca02815Sjsg  * taking a maximum segment length into account, splitting into segments
755ca02815Sjsg  * if necessary.
765ca02815Sjsg  *
771bb76ff1Sjsg  * Return: A pointer to a kmalloced struct i915_refct_sgt on success, negative
785ca02815Sjsg  * error code cast to an error pointer on failure.
795ca02815Sjsg  */
i915_rsgt_from_mm_node(const struct drm_mm_node * node,u64 region_start,u32 page_alignment)801bb76ff1Sjsg struct i915_refct_sgt *i915_rsgt_from_mm_node(const struct drm_mm_node *node,
811bb76ff1Sjsg 					      u64 region_start,
821bb76ff1Sjsg 					      u32 page_alignment)
835ca02815Sjsg {
841bb76ff1Sjsg 	const u32 max_segment = round_down(UINT_MAX, page_alignment);
851bb76ff1Sjsg 	const u32 segment_pages = max_segment >> PAGE_SHIFT;
865ca02815Sjsg 	u64 block_size, offset, prev_end;
871bb76ff1Sjsg 	struct i915_refct_sgt *rsgt;
885ca02815Sjsg 	struct sg_table *st;
895ca02815Sjsg 	struct scatterlist *sg;
905ca02815Sjsg 
911bb76ff1Sjsg 	GEM_BUG_ON(!max_segment);
921bb76ff1Sjsg 
931bb76ff1Sjsg 	rsgt = kmalloc(sizeof(*rsgt), GFP_KERNEL);
941bb76ff1Sjsg 	if (!rsgt)
955ca02815Sjsg 		return ERR_PTR(-ENOMEM);
965ca02815Sjsg 
971bb76ff1Sjsg 	i915_refct_sgt_init(rsgt, node->size << PAGE_SHIFT);
981bb76ff1Sjsg 	st = &rsgt->table;
99*f005ef32Sjsg 	/* restricted by sg_alloc_table */
100*f005ef32Sjsg 	if (WARN_ON(overflows_type(DIV_ROUND_UP_ULL(node->size, segment_pages),
101*f005ef32Sjsg 				   unsigned int))) {
102*f005ef32Sjsg 		i915_refct_sgt_put(rsgt);
103*f005ef32Sjsg 		return ERR_PTR(-E2BIG);
104*f005ef32Sjsg 	}
105*f005ef32Sjsg 
1061bb76ff1Sjsg 	if (sg_alloc_table(st, DIV_ROUND_UP_ULL(node->size, segment_pages),
1075ca02815Sjsg 			   GFP_KERNEL)) {
1081bb76ff1Sjsg 		i915_refct_sgt_put(rsgt);
1095ca02815Sjsg 		return ERR_PTR(-ENOMEM);
1105ca02815Sjsg 	}
1115ca02815Sjsg 
1125ca02815Sjsg 	sg = st->sgl;
1135ca02815Sjsg 	st->nents = 0;
1145ca02815Sjsg 	prev_end = (resource_size_t)-1;
1155ca02815Sjsg 	block_size = node->size << PAGE_SHIFT;
1165ca02815Sjsg 	offset = node->start << PAGE_SHIFT;
1175ca02815Sjsg 
1185ca02815Sjsg 	while (block_size) {
1195ca02815Sjsg 		u64 len;
1205ca02815Sjsg 
1215ca02815Sjsg 		if (offset != prev_end || sg->length >= max_segment) {
1225ca02815Sjsg 			if (st->nents)
1235ca02815Sjsg 				sg = __sg_next(sg);
1245ca02815Sjsg 
1255ca02815Sjsg 			sg_dma_address(sg) = region_start + offset;
1261bb76ff1Sjsg 			GEM_BUG_ON(!IS_ALIGNED(sg_dma_address(sg),
1271bb76ff1Sjsg 					       page_alignment));
1285ca02815Sjsg 			sg_dma_len(sg) = 0;
1295ca02815Sjsg 			sg->length = 0;
1305ca02815Sjsg 			st->nents++;
1315ca02815Sjsg 		}
1325ca02815Sjsg 
1331bb76ff1Sjsg 		len = min_t(u64, block_size, max_segment - sg->length);
1345ca02815Sjsg 		sg->length += len;
1355ca02815Sjsg 		sg_dma_len(sg) += len;
1365ca02815Sjsg 
1375ca02815Sjsg 		offset += len;
1385ca02815Sjsg 		block_size -= len;
1395ca02815Sjsg 
1405ca02815Sjsg 		prev_end = offset;
1415ca02815Sjsg 	}
1425ca02815Sjsg 
1435ca02815Sjsg 	sg_mark_end(sg);
1445ca02815Sjsg 	i915_sg_trim(st);
1455ca02815Sjsg 
1461bb76ff1Sjsg 	return rsgt;
1475ca02815Sjsg }
1485ca02815Sjsg 
1495ca02815Sjsg /**
1501bb76ff1Sjsg  * i915_rsgt_from_buddy_resource - Create a refcounted sg_table from a struct
1515ca02815Sjsg  * i915_buddy_block list
1525ca02815Sjsg  * @res: The struct i915_ttm_buddy_resource.
1535ca02815Sjsg  * @region_start: An offset to add to the dma addresses of the sg list.
1541bb76ff1Sjsg  * @page_alignment: Required page alignment for each sg entry. Power of two.
1555ca02815Sjsg  *
1565ca02815Sjsg  * Create a struct sg_table, initializing it from struct i915_buddy_block list,
1575ca02815Sjsg  * taking a maximum segment length into account, splitting into segments
1585ca02815Sjsg  * if necessary.
1595ca02815Sjsg  *
1601bb76ff1Sjsg  * Return: A pointer to a kmalloced struct i915_refct_sgts on success, negative
1615ca02815Sjsg  * error code cast to an error pointer on failure.
1625ca02815Sjsg  */
i915_rsgt_from_buddy_resource(struct ttm_resource * res,u64 region_start,u32 page_alignment)1631bb76ff1Sjsg struct i915_refct_sgt *i915_rsgt_from_buddy_resource(struct ttm_resource *res,
1641bb76ff1Sjsg 						     u64 region_start,
1651bb76ff1Sjsg 						     u32 page_alignment)
1665ca02815Sjsg {
1675ca02815Sjsg 	struct i915_ttm_buddy_resource *bman_res = to_ttm_buddy_resource(res);
168*f005ef32Sjsg 	const u64 size = res->size;
1691bb76ff1Sjsg 	const u32 max_segment = round_down(UINT_MAX, page_alignment);
1701bb76ff1Sjsg 	struct drm_buddy *mm = bman_res->mm;
1715ca02815Sjsg 	struct list_head *blocks = &bman_res->blocks;
1721bb76ff1Sjsg 	struct drm_buddy_block *block;
1731bb76ff1Sjsg 	struct i915_refct_sgt *rsgt;
1745ca02815Sjsg 	struct scatterlist *sg;
1755ca02815Sjsg 	struct sg_table *st;
1765ca02815Sjsg 	resource_size_t prev_end;
1775ca02815Sjsg 
1785ca02815Sjsg 	GEM_BUG_ON(list_empty(blocks));
1791bb76ff1Sjsg 	GEM_BUG_ON(!max_segment);
1805ca02815Sjsg 
1811bb76ff1Sjsg 	rsgt = kmalloc(sizeof(*rsgt), GFP_KERNEL);
1821bb76ff1Sjsg 	if (!rsgt)
1835ca02815Sjsg 		return ERR_PTR(-ENOMEM);
1845ca02815Sjsg 
1851bb76ff1Sjsg 	i915_refct_sgt_init(rsgt, size);
1861bb76ff1Sjsg 	st = &rsgt->table;
187*f005ef32Sjsg 	/* restricted by sg_alloc_table */
188*f005ef32Sjsg 	if (WARN_ON(overflows_type(PFN_UP(res->size), unsigned int))) {
189*f005ef32Sjsg 		i915_refct_sgt_put(rsgt);
190*f005ef32Sjsg 		return ERR_PTR(-E2BIG);
191*f005ef32Sjsg 	}
192*f005ef32Sjsg 
193*f005ef32Sjsg 	if (sg_alloc_table(st, PFN_UP(res->size), GFP_KERNEL)) {
1941bb76ff1Sjsg 		i915_refct_sgt_put(rsgt);
1955ca02815Sjsg 		return ERR_PTR(-ENOMEM);
1965ca02815Sjsg 	}
1975ca02815Sjsg 
1985ca02815Sjsg 	sg = st->sgl;
1995ca02815Sjsg 	st->nents = 0;
2005ca02815Sjsg 	prev_end = (resource_size_t)-1;
2015ca02815Sjsg 
2025ca02815Sjsg 	list_for_each_entry(block, blocks, link) {
2035ca02815Sjsg 		u64 block_size, offset;
2045ca02815Sjsg 
2051bb76ff1Sjsg 		block_size = min_t(u64, size, drm_buddy_block_size(mm, block));
2061bb76ff1Sjsg 		offset = drm_buddy_block_offset(block);
2075ca02815Sjsg 
2085ca02815Sjsg 		while (block_size) {
2095ca02815Sjsg 			u64 len;
2105ca02815Sjsg 
2115ca02815Sjsg 			if (offset != prev_end || sg->length >= max_segment) {
2125ca02815Sjsg 				if (st->nents)
2135ca02815Sjsg 					sg = __sg_next(sg);
2145ca02815Sjsg 
2155ca02815Sjsg 				sg_dma_address(sg) = region_start + offset;
2161bb76ff1Sjsg 				GEM_BUG_ON(!IS_ALIGNED(sg_dma_address(sg),
2171bb76ff1Sjsg 						       page_alignment));
2185ca02815Sjsg 				sg_dma_len(sg) = 0;
2195ca02815Sjsg 				sg->length = 0;
2205ca02815Sjsg 				st->nents++;
2215ca02815Sjsg 			}
2225ca02815Sjsg 
2231bb76ff1Sjsg 			len = min_t(u64, block_size, max_segment - sg->length);
2245ca02815Sjsg 			sg->length += len;
2255ca02815Sjsg 			sg_dma_len(sg) += len;
2265ca02815Sjsg 
2275ca02815Sjsg 			offset += len;
2285ca02815Sjsg 			block_size -= len;
2295ca02815Sjsg 
2305ca02815Sjsg 			prev_end = offset;
2315ca02815Sjsg 		}
2325ca02815Sjsg 	}
2335ca02815Sjsg 
2345ca02815Sjsg 	sg_mark_end(sg);
2355ca02815Sjsg 	i915_sg_trim(st);
2365ca02815Sjsg 
2371bb76ff1Sjsg 	return rsgt;
2385ca02815Sjsg }
2395ca02815Sjsg 
240c349dbc7Sjsg #if IS_ENABLED(CONFIG_DRM_I915_SELFTEST)
241c349dbc7Sjsg #include "selftests/scatterlist.c"
242c349dbc7Sjsg #endif
243