xref: /netbsd-src/sys/external/bsd/drm/dist/bsd-core/drm_scatter.c (revision 9b97857eeb7231eb5509ddf2a4328a2f98731f37)
1 /*-
2  * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
3  * All Rights Reserved.
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a
6  * copy of this software and associated documentation files (the "Software"),
7  * to deal in the Software without restriction, including without limitation
8  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9  * and/or sell copies of the Software, and to permit persons to whom the
10  * Software is furnished to do so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice (including the next
13  * paragraph) shall be included in all copies or substantial portions of the
14  * Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
19  * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20  * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21  * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22  * DEALINGS IN THE SOFTWARE.
23  *
24  * Authors:
25  *   Gareth Hughes <gareth@valinux.com>
26  *   Eric Anholt <anholt@FreeBSD.org>
27  *
28  */
29 
30 /** @file drm_scatter.c
31  * Allocation of memory for scatter-gather mappings by the graphics chip.
32  *
33  * The memory allocated here is then made into an aperture in the card
34  * by drm_ati_pcigart_init().
35  */
36 
37 #include "drmP.h"
38 
39 #if defined(__FreeBSD__)
40 static void drm_sg_alloc_cb(void *arg, bus_dma_segment_t *segs,
41 			    int nsegs, int error);
42 #endif
43 
44 int
drm_sg_alloc(struct drm_device * dev,struct drm_scatter_gather * request)45 drm_sg_alloc(struct drm_device *dev, struct drm_scatter_gather *request)
46 {
47 	struct drm_sg_mem *entry;
48 	struct drm_dma_handle *dmah;
49 	unsigned long pages;
50 	int ret;
51 #if defined(__NetBSD__)
52 	int nsegs, i, npage;
53 #endif
54 
55 	if (dev->sg)
56 		return EINVAL;
57 
58 	entry = malloc(sizeof(*entry), DRM_MEM_SGLISTS, M_WAITOK | M_ZERO);
59 	if (!entry)
60 		return ENOMEM;
61 
62 	pages = round_page(request->size) / PAGE_SIZE;
63 	DRM_DEBUG("sg size=%ld pages=%ld\n", request->size, pages);
64 
65 	entry->pages = pages;
66 
67 	entry->busaddr = malloc(pages * sizeof(*entry->busaddr), DRM_MEM_PAGES,
68 	    M_WAITOK | M_ZERO);
69 	if (!entry->busaddr) {
70 		free(entry, DRM_MEM_SGLISTS);
71 		return ENOMEM;
72 	}
73 
74 #if defined(__FreeBSD__)
75 	dmah = malloc(sizeof(struct drm_dma_handle), DRM_MEM_DMA,
76 	    M_ZERO | M_NOWAIT);
77 	if (dmah == NULL) {
78 		free(entry->busaddr, DRM_MEM_PAGES);
79 		free(entry, DRM_MEM_SGLISTS);
80 		return ENOMEM;
81 	}
82 
83 	ret = bus_dma_tag_create(NULL, PAGE_SIZE, 0, /* tag, align, boundary */
84 	    BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, /* lowaddr, highaddr */
85 	    NULL, NULL, /* filtfunc, filtfuncargs */
86 	    request->size, pages, /* maxsize, nsegs */
87 	    PAGE_SIZE, 0, /* maxsegsize, flags */
88 	    NULL, NULL, /* lockfunc, lockfuncargs */
89 	    &dmah->tag);
90 	if (ret != 0) {
91 		free(dmah, DRM_MEM_DMA);
92 		free(entry->busaddr, DRM_MEM_PAGES);
93 		free(entry, DRM_MEM_SGLISTS);
94 		return ENOMEM;
95 	}
96 
97 	ret = bus_dmamem_alloc(dmah->tag, &dmah->vaddr,
98 	    BUS_DMA_WAITOK | BUS_DMA_ZERO, &dmah->map);
99 	if (ret != 0) {
100 		bus_dma_tag_destroy(dmah->tag);
101 		free(dmah, DRM_MEM_DMA);
102 		free(entry->busaddr, DRM_MEM_PAGES);
103 		free(entry, DRM_MEM_SGLISTS);
104 		return ENOMEM;
105 	}
106 
107 	ret = bus_dmamap_load(dmah->tag, dmah->map, dmah->vaddr,
108 	    request->size, drm_sg_alloc_cb, entry,
109 	    BUS_DMA_NOWAIT | BUS_DMA_NOCACHE);
110 	if (ret != 0) {
111 		bus_dmamem_free(dmah->tag, dmah->vaddr, dmah->map);
112 		bus_dma_tag_destroy(dmah->tag);
113 		free(dmah, DRM_MEM_DMA);
114 		free(entry->busaddr, DRM_MEM_PAGES);
115 		free(entry, DRM_MEM_SGLISTS);
116 		return ENOMEM;
117 	}
118 #elif   defined(__NetBSD__)
119 	dmah = malloc(sizeof(struct drm_dma_handle) +
120 		      (pages - 1) * sizeof(bus_dma_segment_t),
121 		      DRM_MEM_DMA, M_ZERO | M_NOWAIT);
122 	if (dmah == NULL) {
123 		free(entry->busaddr, DRM_MEM_PAGES);
124 		free(entry, DRM_MEM_SGLISTS);
125 		return ENOMEM;
126 	}
127 
128 	dmah->tag = dev->pa.pa_dmat;
129 
130 	if ((ret = bus_dmamem_alloc(dmah->tag, request->size, PAGE_SIZE, 0,
131 				    dmah->segs, pages, &nsegs,
132 				    BUS_DMA_WAITOK)) != 0) {
133 		printf("drm: Unable to allocate %lu bytes of DMA, error %d\n",
134 		    request->size, ret);
135 		dmah->tag = NULL;
136 		free(dmah, DRM_MEM_DMA);
137 		free(entry->busaddr, DRM_MEM_PAGES);
138 		free(entry, DRM_MEM_SGLISTS);
139 		return ENOMEM;
140 	}
141 	DRM_DEBUG("nsegs = %d\n", nsegs);
142 	dmah->nsegs = nsegs;
143 	if ((ret = bus_dmamem_map(dmah->tag, dmah->segs, nsegs, request->size,
144 				  &dmah->vaddr, BUS_DMA_NOWAIT |
145 				  BUS_DMA_NOCACHE | BUS_DMA_COHERENT)) != 0) {
146 		printf("drm: Unable to map DMA, error %d\n", ret);
147 		bus_dmamem_free(dmah->tag, dmah->segs, dmah->nsegs);
148 		dmah->tag = NULL;
149 		free(dmah, DRM_MEM_DMA);
150 		free(entry->busaddr, DRM_MEM_PAGES);
151 		free(entry, DRM_MEM_SGLISTS);
152 		return ENOMEM;
153 	}
154 	if ((ret = bus_dmamap_create(dmah->tag, request->size, nsegs,
155                                      request->size, 0, BUS_DMA_NOWAIT,
156 				     &dmah->map)) != 0) {
157 		printf("drm: Unable to create DMA map, error %d\n", ret);
158 		bus_dmamem_unmap(dmah->tag, dmah->vaddr, request->size);
159 		bus_dmamem_free(dmah->tag, dmah->segs, nsegs);
160 		dmah->tag = NULL;
161 		free(dmah, DRM_MEM_DMA);
162 		free(entry->busaddr, DRM_MEM_PAGES);
163 		free(entry, DRM_MEM_SGLISTS);
164 		return ENOMEM;
165 	}
166 	if ((ret = bus_dmamap_load(dmah->tag, dmah->map, dmah->vaddr,
167 				   request->size, NULL,
168 				   BUS_DMA_NOWAIT | BUS_DMA_NOCACHE)) != 0) {
169 		printf("drm: Unable to load DMA map, error %d\n", ret);
170 		bus_dmamap_destroy(dmah->tag, dmah->map);
171 		bus_dmamem_unmap(dmah->tag, dmah->vaddr, request->size);
172 		bus_dmamem_free(dmah->tag, dmah->segs, dmah->nsegs);
173 		dmah->tag = NULL;
174 		free(dmah, DRM_MEM_DMA);
175 		return ENOMEM;
176 	}
177 	/*
178 	 * We are expected to return address for each page here.
179 	 * If bus_dmamem_alloc did not return each page in own segment
180 	 * (likely not), split them as if they were separate segments.
181 	 */
182 	for (i = 0, npage = 0 ; (i < nsegs) && (npage < pages) ; i++) {
183 		bus_addr_t base = dmah->map->dm_segs[i].ds_addr;
184 		bus_size_t offs;
185 
186 		for (offs = 0;
187 		     (offs + PAGE_SIZE <= dmah->map->dm_segs[i].ds_len) &&
188 		     (npage < pages);
189 		     offs += PAGE_SIZE)
190 			entry->busaddr[npage++] = base + offs;
191 	}
192 	KASSERT(i == nsegs);
193 	KASSERT(npage == pages);
194 	dmah->size = request->size;
195 	memset(dmah->vaddr, 0, request->size);
196 #endif
197 
198 	entry->sg_dmah = dmah;
199 	entry->handle = (unsigned long)dmah->vaddr;
200 
201 	DRM_DEBUG("sg alloc handle  = %08lx\n", entry->handle);
202 
203 	entry->virtual = (void *)entry->handle;
204 	request->handle = entry->handle;
205 
206 	DRM_LOCK();
207 	if (dev->sg) {
208 		DRM_UNLOCK();
209 		drm_sg_cleanup(entry);
210 		return EINVAL;
211 	}
212 	dev->sg = entry;
213 	DRM_UNLOCK();
214 
215 	return 0;
216 }
217 
218 #if defined(__FreeBSD__)
219 static void
drm_sg_alloc_cb(void * arg,bus_dma_segment_t * segs,int nsegs,int error)220 drm_sg_alloc_cb(void *arg, bus_dma_segment_t *segs, int nsegs, int error)
221 {
222 	struct drm_sg_mem *entry = arg;
223 	int i;
224 
225 	if (error != 0)
226 	    return;
227 
228 	for(i = 0 ; i < nsegs ; i++) {
229 		entry->busaddr[i] = segs[i].ds_addr;
230 	}
231 }
232 #endif
233 
234 int
drm_sg_alloc_ioctl(struct drm_device * dev,void * data,struct drm_file * file_priv)235 drm_sg_alloc_ioctl(struct drm_device *dev, void *data,
236 		   struct drm_file *file_priv)
237 {
238 	struct drm_scatter_gather *request = data;
239 
240 	DRM_DEBUG("\n");
241 
242 	return drm_sg_alloc(dev, request);
243 }
244 
245 void
drm_sg_cleanup(struct drm_sg_mem * entry)246 drm_sg_cleanup(struct drm_sg_mem *entry)
247 {
248 	struct drm_dma_handle *dmah = entry->sg_dmah;
249 
250 #if defined(__FreeBSD__)
251 	bus_dmamap_unload(dmah->tag, dmah->map);
252 	bus_dmamem_free(dmah->tag, dmah->vaddr, dmah->map);
253 	bus_dma_tag_destroy(dmah->tag);
254 #elif   defined(__NetBSD__)
255 	bus_dmamap_unload(dmah->tag, dmah->map);
256 	bus_dmamap_destroy(dmah->tag, dmah->map);
257 	bus_dmamem_unmap(dmah->tag, dmah->vaddr, dmah->size);
258 	bus_dmamem_free(dmah->tag, dmah->segs, dmah->nsegs);
259 	dmah->tag = NULL;
260 #endif
261 	free(dmah, DRM_MEM_DMA);
262 	free(entry->busaddr, DRM_MEM_PAGES);
263 	free(entry, DRM_MEM_SGLISTS);
264 }
265 
266 int
drm_sg_free(struct drm_device * dev,void * data,struct drm_file * file_priv)267 drm_sg_free(struct drm_device *dev, void *data, struct drm_file *file_priv)
268 {
269 	struct drm_scatter_gather *request = data;
270 	struct drm_sg_mem *entry;
271 
272 	DRM_LOCK();
273 	entry = dev->sg;
274 	dev->sg = NULL;
275 	DRM_UNLOCK();
276 
277 	if (!entry || entry->handle != request->handle)
278 		return EINVAL;
279 
280 	DRM_DEBUG("sg free virtual = 0x%lx\n", entry->handle);
281 
282 	drm_sg_cleanup(entry);
283 
284 	return 0;
285 }
286