1254885Sdumbbell/* radeon_mem.c -- Simple GART/fb memory manager for radeon -*- linux-c -*- */
2254885Sdumbbell/*
3254885Sdumbbell * Copyright (C) The Weather Channel, Inc.  2002.  All Rights Reserved.
4254885Sdumbbell *
5254885Sdumbbell * The Weather Channel (TM) funded Tungsten Graphics to develop the
6254885Sdumbbell * initial release of the Radeon 8500 driver under the XFree86 license.
7254885Sdumbbell * This notice must be preserved.
8254885Sdumbbell *
9254885Sdumbbell * Permission is hereby granted, free of charge, to any person obtaining a
10254885Sdumbbell * copy of this software and associated documentation files (the "Software"),
11254885Sdumbbell * to deal in the Software without restriction, including without limitation
12254885Sdumbbell * the rights to use, copy, modify, merge, publish, distribute, sublicense,
13254885Sdumbbell * and/or sell copies of the Software, and to permit persons to whom the
14254885Sdumbbell * Software is furnished to do so, subject to the following conditions:
15254885Sdumbbell *
16254885Sdumbbell * The above copyright notice and this permission notice (including the next
17254885Sdumbbell * paragraph) shall be included in all copies or substantial portions of the
18254885Sdumbbell * Software.
19254885Sdumbbell *
20254885Sdumbbell * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21254885Sdumbbell * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22254885Sdumbbell * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
23254885Sdumbbell * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
24254885Sdumbbell * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
25254885Sdumbbell * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26254885Sdumbbell * DEALINGS IN THE SOFTWARE.
27254885Sdumbbell *
28254885Sdumbbell * Authors:
29254885Sdumbbell *    Keith Whitwell <keith@tungstengraphics.com>
30254885Sdumbbell */
31254885Sdumbbell
32254885Sdumbbell#include <sys/cdefs.h>
33254885Sdumbbell__FBSDID("$FreeBSD$");
34254885Sdumbbell
35254885Sdumbbell#include <dev/drm2/drmP.h>
36254885Sdumbbell#include <dev/drm2/radeon/radeon_drm.h>
37254885Sdumbbell#include "radeon_drv.h"
38254885Sdumbbell
39254885Sdumbbell/* Very simple allocator for GART memory, working on a static range
40254885Sdumbbell * already mapped into each client's address space.
41254885Sdumbbell */
42254885Sdumbbell
43254885Sdumbbellstatic struct mem_block *split_block(struct mem_block *p, int start, int size,
44254885Sdumbbell				     struct drm_file *file_priv)
45254885Sdumbbell{
46254885Sdumbbell	/* Maybe cut off the start of an existing block */
47254885Sdumbbell	if (start > p->start) {
48254885Sdumbbell		struct mem_block *newblock = malloc(sizeof(*newblock),
49254885Sdumbbell						     DRM_MEM_DRIVER, M_WAITOK);
50254885Sdumbbell		if (!newblock)
51254885Sdumbbell			goto out;
52254885Sdumbbell		newblock->start = start;
53254885Sdumbbell		newblock->size = p->size - (start - p->start);
54254885Sdumbbell		newblock->file_priv = NULL;
55254885Sdumbbell		newblock->next = p->next;
56254885Sdumbbell		newblock->prev = p;
57254885Sdumbbell		p->next->prev = newblock;
58254885Sdumbbell		p->next = newblock;
59254885Sdumbbell		p->size -= newblock->size;
60254885Sdumbbell		p = newblock;
61254885Sdumbbell	}
62254885Sdumbbell
63254885Sdumbbell	/* Maybe cut off the end of an existing block */
64254885Sdumbbell	if (size < p->size) {
65254885Sdumbbell		struct mem_block *newblock = malloc(sizeof(*newblock),
66254885Sdumbbell						     DRM_MEM_DRIVER, M_WAITOK);
67254885Sdumbbell		if (!newblock)
68254885Sdumbbell			goto out;
69254885Sdumbbell		newblock->start = start + size;
70254885Sdumbbell		newblock->size = p->size - size;
71254885Sdumbbell		newblock->file_priv = NULL;
72254885Sdumbbell		newblock->next = p->next;
73254885Sdumbbell		newblock->prev = p;
74254885Sdumbbell		p->next->prev = newblock;
75254885Sdumbbell		p->next = newblock;
76254885Sdumbbell		p->size = size;
77254885Sdumbbell	}
78254885Sdumbbell
79254885Sdumbbell      out:
80254885Sdumbbell	/* Our block is in the middle */
81254885Sdumbbell	p->file_priv = file_priv;
82254885Sdumbbell	return p;
83254885Sdumbbell}
84254885Sdumbbell
85254885Sdumbbellstatic struct mem_block *alloc_block(struct mem_block *heap, int size,
86254885Sdumbbell				     int align2, struct drm_file *file_priv)
87254885Sdumbbell{
88254885Sdumbbell	struct mem_block *p;
89254885Sdumbbell	int mask = (1 << align2) - 1;
90254885Sdumbbell
91254885Sdumbbell	list_for_each(p, heap) {
92254885Sdumbbell		int start = (p->start + mask) & ~mask;
93254885Sdumbbell		if (p->file_priv == NULL && start + size <= p->start + p->size)
94254885Sdumbbell			return split_block(p, start, size, file_priv);
95254885Sdumbbell	}
96254885Sdumbbell
97254885Sdumbbell	return NULL;
98254885Sdumbbell}
99254885Sdumbbell
100254885Sdumbbellstatic struct mem_block *find_block(struct mem_block *heap, int start)
101254885Sdumbbell{
102254885Sdumbbell	struct mem_block *p;
103254885Sdumbbell
104254885Sdumbbell	list_for_each(p, heap)
105254885Sdumbbell	    if (p->start == start)
106254885Sdumbbell		return p;
107254885Sdumbbell
108254885Sdumbbell	return NULL;
109254885Sdumbbell}
110254885Sdumbbell
111254885Sdumbbellstatic void free_block(struct mem_block *p)
112254885Sdumbbell{
113254885Sdumbbell	p->file_priv = NULL;
114254885Sdumbbell
115254885Sdumbbell	/* Assumes a single contiguous range.  Needs a special file_priv in
116254885Sdumbbell	 * 'heap' to stop it being subsumed.
117254885Sdumbbell	 */
118254885Sdumbbell	if (p->next->file_priv == NULL) {
119254885Sdumbbell		struct mem_block *q = p->next;
120254885Sdumbbell		p->size += q->size;
121254885Sdumbbell		p->next = q->next;
122254885Sdumbbell		p->next->prev = p;
123254885Sdumbbell		free(q, DRM_MEM_DRIVER);
124254885Sdumbbell	}
125254885Sdumbbell
126254885Sdumbbell	if (p->prev->file_priv == NULL) {
127254885Sdumbbell		struct mem_block *q = p->prev;
128254885Sdumbbell		q->size += p->size;
129254885Sdumbbell		q->next = p->next;
130254885Sdumbbell		q->next->prev = q;
131254885Sdumbbell		free(p, DRM_MEM_DRIVER);
132254885Sdumbbell	}
133254885Sdumbbell}
134254885Sdumbbell
135254885Sdumbbell/* Initialize.  How to check for an uninitialized heap?
136254885Sdumbbell */
137254885Sdumbbellstatic int init_heap(struct mem_block **heap, int start, int size)
138254885Sdumbbell{
139254885Sdumbbell	struct mem_block *blocks = malloc(sizeof(*blocks),
140254885Sdumbbell	    DRM_MEM_DRIVER, M_WAITOK);
141254885Sdumbbell
142254885Sdumbbell	if (!blocks)
143254885Sdumbbell		return -ENOMEM;
144254885Sdumbbell
145254885Sdumbbell	*heap = malloc(sizeof(**heap), DRM_MEM_DRIVER, M_ZERO | M_WAITOK);
146254885Sdumbbell	if (!*heap) {
147254885Sdumbbell		free(blocks, DRM_MEM_DRIVER);
148254885Sdumbbell		return -ENOMEM;
149254885Sdumbbell	}
150254885Sdumbbell
151254885Sdumbbell	blocks->start = start;
152254885Sdumbbell	blocks->size = size;
153254885Sdumbbell	blocks->file_priv = NULL;
154254885Sdumbbell	blocks->next = blocks->prev = *heap;
155254885Sdumbbell
156254885Sdumbbell	(*heap)->file_priv = (struct drm_file *) - 1;
157254885Sdumbbell	(*heap)->next = (*heap)->prev = blocks;
158254885Sdumbbell	return 0;
159254885Sdumbbell}
160254885Sdumbbell
161254885Sdumbbell/* Free all blocks associated with the releasing file.
162254885Sdumbbell */
163254885Sdumbbellvoid radeon_mem_release(struct drm_file *file_priv, struct mem_block *heap)
164254885Sdumbbell{
165254885Sdumbbell	struct mem_block *p;
166254885Sdumbbell
167254885Sdumbbell	if (!heap || !heap->next)
168254885Sdumbbell		return;
169254885Sdumbbell
170254885Sdumbbell	list_for_each(p, heap) {
171254885Sdumbbell		if (p->file_priv == file_priv)
172254885Sdumbbell			p->file_priv = NULL;
173254885Sdumbbell	}
174254885Sdumbbell
175254885Sdumbbell	/* Assumes a single contiguous range.  Needs a special file_priv in
176254885Sdumbbell	 * 'heap' to stop it being subsumed.
177254885Sdumbbell	 */
178254885Sdumbbell	list_for_each(p, heap) {
179254885Sdumbbell		while (p->file_priv == NULL && p->next->file_priv == NULL) {
180254885Sdumbbell			struct mem_block *q = p->next;
181254885Sdumbbell			p->size += q->size;
182254885Sdumbbell			p->next = q->next;
183254885Sdumbbell			p->next->prev = p;
184254885Sdumbbell			free(q, DRM_MEM_DRIVER);
185254885Sdumbbell		}
186254885Sdumbbell	}
187254885Sdumbbell}
188254885Sdumbbell
189254885Sdumbbell/* Shutdown.
190254885Sdumbbell */
191254885Sdumbbellvoid radeon_mem_takedown(struct mem_block **heap)
192254885Sdumbbell{
193254885Sdumbbell	struct mem_block *p;
194254885Sdumbbell
195254885Sdumbbell	if (!*heap)
196254885Sdumbbell		return;
197254885Sdumbbell
198254885Sdumbbell	for (p = (*heap)->next; p != *heap;) {
199254885Sdumbbell		struct mem_block *q = p;
200254885Sdumbbell		p = p->next;
201254885Sdumbbell		free(q, DRM_MEM_DRIVER);
202254885Sdumbbell	}
203254885Sdumbbell
204254885Sdumbbell	free(*heap, DRM_MEM_DRIVER);
205254885Sdumbbell	*heap = NULL;
206254885Sdumbbell}
207254885Sdumbbell
208254885Sdumbbell/* IOCTL HANDLERS */
209254885Sdumbbell
210254885Sdumbbellstatic struct mem_block **get_heap(drm_radeon_private_t * dev_priv, int region)
211254885Sdumbbell{
212254885Sdumbbell	switch (region) {
213254885Sdumbbell	case RADEON_MEM_REGION_GART:
214254885Sdumbbell		return &dev_priv->gart_heap;
215254885Sdumbbell	case RADEON_MEM_REGION_FB:
216254885Sdumbbell		return &dev_priv->fb_heap;
217254885Sdumbbell	default:
218254885Sdumbbell		return NULL;
219254885Sdumbbell	}
220254885Sdumbbell}
221254885Sdumbbell
222254885Sdumbbellint radeon_mem_alloc(struct drm_device *dev, void *data, struct drm_file *file_priv)
223254885Sdumbbell{
224254885Sdumbbell	drm_radeon_private_t *dev_priv = dev->dev_private;
225254885Sdumbbell	drm_radeon_mem_alloc_t *alloc = data;
226254885Sdumbbell	struct mem_block *block, **heap;
227254885Sdumbbell
228254885Sdumbbell	if (!dev_priv) {
229254885Sdumbbell		DRM_ERROR("called with no initialization\n");
230254885Sdumbbell		return -EINVAL;
231254885Sdumbbell	}
232254885Sdumbbell
233254885Sdumbbell	heap = get_heap(dev_priv, alloc->region);
234254885Sdumbbell	if (!heap || !*heap)
235254885Sdumbbell		return -EFAULT;
236254885Sdumbbell
237254885Sdumbbell	/* Make things easier on ourselves: all allocations at least
238254885Sdumbbell	 * 4k aligned.
239254885Sdumbbell	 */
240254885Sdumbbell	if (alloc->alignment < 12)
241254885Sdumbbell		alloc->alignment = 12;
242254885Sdumbbell
243254885Sdumbbell	block = alloc_block(*heap, alloc->size, alloc->alignment, file_priv);
244254885Sdumbbell
245254885Sdumbbell	if (!block)
246254885Sdumbbell		return -ENOMEM;
247254885Sdumbbell
248254885Sdumbbell	if (DRM_COPY_TO_USER(alloc->region_offset, &block->start,
249254885Sdumbbell			     sizeof(int))) {
250254885Sdumbbell		DRM_ERROR("copy_to_user\n");
251254885Sdumbbell		return -EFAULT;
252254885Sdumbbell	}
253254885Sdumbbell
254254885Sdumbbell	return 0;
255254885Sdumbbell}
256254885Sdumbbell
257254885Sdumbbellint radeon_mem_free(struct drm_device *dev, void *data, struct drm_file *file_priv)
258254885Sdumbbell{
259254885Sdumbbell	drm_radeon_private_t *dev_priv = dev->dev_private;
260254885Sdumbbell	drm_radeon_mem_free_t *memfree = data;
261254885Sdumbbell	struct mem_block *block, **heap;
262254885Sdumbbell
263254885Sdumbbell	if (!dev_priv) {
264254885Sdumbbell		DRM_ERROR("called with no initialization\n");
265254885Sdumbbell		return -EINVAL;
266254885Sdumbbell	}
267254885Sdumbbell
268254885Sdumbbell	heap = get_heap(dev_priv, memfree->region);
269254885Sdumbbell	if (!heap || !*heap)
270254885Sdumbbell		return -EFAULT;
271254885Sdumbbell
272254885Sdumbbell	block = find_block(*heap, memfree->region_offset);
273254885Sdumbbell	if (!block)
274254885Sdumbbell		return -EFAULT;
275254885Sdumbbell
276254885Sdumbbell	if (block->file_priv != file_priv)
277254885Sdumbbell		return -EPERM;
278254885Sdumbbell
279254885Sdumbbell	free_block(block);
280254885Sdumbbell	return 0;
281254885Sdumbbell}
282254885Sdumbbell
283254885Sdumbbellint radeon_mem_init_heap(struct drm_device *dev, void *data, struct drm_file *file_priv)
284254885Sdumbbell{
285254885Sdumbbell	drm_radeon_private_t *dev_priv = dev->dev_private;
286254885Sdumbbell	drm_radeon_mem_init_heap_t *initheap = data;
287254885Sdumbbell	struct mem_block **heap;
288254885Sdumbbell
289254885Sdumbbell	if (!dev_priv) {
290254885Sdumbbell		DRM_ERROR("called with no initialization\n");
291254885Sdumbbell		return -EINVAL;
292254885Sdumbbell	}
293254885Sdumbbell
294254885Sdumbbell	heap = get_heap(dev_priv, initheap->region);
295254885Sdumbbell	if (!heap)
296254885Sdumbbell		return -EFAULT;
297254885Sdumbbell
298254885Sdumbbell	if (*heap) {
299254885Sdumbbell		DRM_ERROR("heap already initialized?");
300254885Sdumbbell		return -EFAULT;
301254885Sdumbbell	}
302254885Sdumbbell
303254885Sdumbbell	return init_heap(heap, initheap->start, initheap->size);
304254885Sdumbbell}
305