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