1 | /* $NetBSD: drm_memory.c,v 1.10 2016/03/06 10:59:56 mlelstv Exp $ */ |
2 | |
3 | /*- |
4 | * Copyright (c) 2013 The NetBSD Foundation, Inc. |
5 | * All rights reserved. |
6 | * |
7 | * This code is derived from software contributed to The NetBSD Foundation |
8 | * by Taylor R. Campbell. |
9 | * |
10 | * Redistribution and use in source and binary forms, with or without |
11 | * modification, are permitted provided that the following conditions |
12 | * are met: |
13 | * 1. Redistributions of source code must retain the above copyright |
14 | * notice, this list of conditions and the following disclaimer. |
15 | * 2. Redistributions in binary form must reproduce the above copyright |
16 | * notice, this list of conditions and the following disclaimer in the |
17 | * documentation and/or other materials provided with the distribution. |
18 | * |
19 | * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS |
20 | * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
21 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
22 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS |
23 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
24 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
25 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
26 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
27 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
28 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
29 | * POSSIBILITY OF SUCH DAMAGE. |
30 | */ |
31 | |
32 | #include <sys/cdefs.h> |
33 | __KERNEL_RCSID(0, "$NetBSD: drm_memory.c,v 1.10 2016/03/06 10:59:56 mlelstv Exp $" ); |
34 | |
35 | #if defined(__i386__) || defined(__x86_64__) |
36 | |
37 | # ifdef _KERNEL_OPT |
38 | # include "agp.h" |
39 | # if NAGP > 0 |
40 | # include "agp_i810.h" |
41 | # else |
42 | # define NAGP_I810 0 |
43 | # endif |
44 | # include "genfb.h" |
45 | # else |
46 | # define NAGP_I810 1 |
47 | # define NGENFB 0 |
48 | # endif |
49 | |
50 | #else |
51 | |
52 | # ifdef _KERNEL_OPT |
53 | # define NAGP_I810 0 |
54 | # include "genfb.h" |
55 | # else |
56 | # define NAGP_I810 0 |
57 | # define NGENFB 0 |
58 | # endif |
59 | |
60 | #endif |
61 | |
62 | #include <sys/bus.h> |
63 | |
64 | #if NAGP_I810 > 0 |
65 | /* XXX include order botch -- shouldn't need to include pcivar.h */ |
66 | #include <dev/pci/pcivar.h> |
67 | #include <dev/pci/agpvar.h> |
68 | #endif |
69 | |
70 | #if NGENFB > 0 |
71 | #include <dev/wsfb/genfbvar.h> |
72 | #endif |
73 | |
74 | #include <drm/drmP.h> |
75 | |
76 | /* |
77 | * XXX drm_bus_borrow is a horrible kludge! |
78 | */ |
79 | static bool |
80 | drm_bus_borrow(bus_addr_t base, bus_size_t size, bus_space_handle_t *handlep) |
81 | { |
82 | |
83 | #if NAGP_I810 > 0 |
84 | if (agp_i810_borrow(base, size, handlep)) |
85 | return true; |
86 | #endif |
87 | |
88 | #if NGENFB > 0 |
89 | if (genfb_borrow(base, handlep)) |
90 | return true; |
91 | #endif |
92 | |
93 | return false; |
94 | } |
95 | |
96 | void |
97 | drm_core_ioremap(struct drm_local_map *map, struct drm_device *dev) |
98 | { |
99 | const bus_space_tag_t bst = dev->bst; |
100 | unsigned int unit; |
101 | |
102 | /* |
103 | * Search dev's bus maps for a match. |
104 | */ |
105 | for (unit = 0; unit < dev->bus_nmaps; unit++) { |
106 | struct drm_bus_map *const bm = &dev->bus_maps[unit]; |
107 | int flags = bm->bm_flags; |
108 | |
109 | /* Reject maps starting after the request. */ |
110 | if (map->offset < bm->bm_base) |
111 | continue; |
112 | |
113 | /* Reject maps smaller than the request. */ |
114 | if (bm->bm_size < map->size) |
115 | continue; |
116 | |
117 | /* Reject maps that the request doesn't fit in. */ |
118 | if ((bm->bm_size - map->size) < |
119 | (map->offset - bm->bm_base)) |
120 | continue; |
121 | |
122 | /* Ensure we can map the space into virtual memory. */ |
123 | if (!ISSET(flags, BUS_SPACE_MAP_LINEAR)) |
124 | continue; |
125 | |
126 | /* Reflect requested flags in the bus_space map. */ |
127 | if (ISSET(map->flags, _DRM_WRITE_COMBINING)) |
128 | flags |= BUS_SPACE_MAP_PREFETCHABLE; |
129 | |
130 | /* Map it. */ |
131 | if (bus_space_map(bst, map->offset, map->size, flags, |
132 | &map->lm_data.bus_space.bsh)) |
133 | break; |
134 | |
135 | map->lm_data.bus_space.bus_map = bm; |
136 | goto win; |
137 | } |
138 | |
139 | /* Couldn't map it. Try borrowing from someone else. */ |
140 | if (drm_bus_borrow(map->offset, map->size, |
141 | &map->lm_data.bus_space.bsh)) { |
142 | map->lm_data.bus_space.bus_map = NULL; |
143 | goto win; |
144 | } |
145 | |
146 | /* Failure! */ |
147 | return; |
148 | |
149 | win: map->lm_data.bus_space.bst = bst; |
150 | map->handle = bus_space_vaddr(bst, map->lm_data.bus_space.bsh); |
151 | } |
152 | |
153 | void |
154 | drm_core_ioremapfree(struct drm_local_map *map, struct drm_device *dev) |
155 | { |
156 | if (map->lm_data.bus_space.bus_map != NULL) { |
157 | bus_space_unmap(map->lm_data.bus_space.bst, |
158 | map->lm_data.bus_space.bsh, map->size); |
159 | map->lm_data.bus_space.bus_map = NULL; |
160 | map->handle = NULL; |
161 | } |
162 | } |
163 | |
164 | /* |
165 | * Allocate a drm dma handle, allocate memory fit for DMA, and map it. |
166 | * |
167 | * XXX This is called drm_pci_alloc for hysterical raisins; it is not |
168 | * specific to PCI. |
169 | * |
170 | * XXX For now, we use non-blocking allocations because this is called |
171 | * by ioctls with the drm global mutex held. |
172 | * |
173 | * XXX Error information is lost because this returns NULL on failure, |
174 | * not even an error embedded in a pointer. |
175 | */ |
176 | struct drm_dma_handle * |
177 | drm_pci_alloc(struct drm_device *dev, size_t size, size_t align) |
178 | { |
179 | int nsegs; |
180 | int error; |
181 | |
182 | /* |
183 | * Allocate a drm_dma_handle record. |
184 | */ |
185 | struct drm_dma_handle *const dmah = kmem_alloc(sizeof(*dmah), |
186 | KM_NOSLEEP); |
187 | if (dmah == NULL) { |
188 | error = -ENOMEM; |
189 | goto out; |
190 | } |
191 | dmah->dmah_tag = dev->dmat; |
192 | |
193 | /* |
194 | * Allocate the requested amount of DMA-safe memory. |
195 | */ |
196 | /* XXX errno NetBSD->Linux */ |
197 | error = -bus_dmamem_alloc(dmah->dmah_tag, size, align, 0, |
198 | &dmah->dmah_seg, 1, &nsegs, BUS_DMA_NOWAIT); |
199 | if (error) |
200 | goto fail0; |
201 | KASSERT(nsegs == 1); |
202 | |
203 | /* |
204 | * Map the DMA-safe memory into kernel virtual address space. |
205 | */ |
206 | /* XXX errno NetBSD->Linux */ |
207 | error = -bus_dmamem_map(dmah->dmah_tag, &dmah->dmah_seg, 1, size, |
208 | &dmah->vaddr, |
209 | (BUS_DMA_NOWAIT | BUS_DMA_COHERENT | BUS_DMA_NOCACHE)); |
210 | if (error) |
211 | goto fail1; |
212 | dmah->size = size; |
213 | |
214 | /* |
215 | * Create a map for DMA transfers. |
216 | */ |
217 | /* XXX errno NetBSD->Linux */ |
218 | error = -bus_dmamap_create(dmah->dmah_tag, size, 1, size, 0, |
219 | BUS_DMA_NOWAIT, &dmah->dmah_map); |
220 | if (error) |
221 | goto fail2; |
222 | |
223 | /* |
224 | * Load the kva buffer into the map for DMA transfers. |
225 | */ |
226 | /* XXX errno NetBSD->Linux */ |
227 | error = -bus_dmamap_load(dmah->dmah_tag, dmah->dmah_map, dmah->vaddr, |
228 | size, NULL, (BUS_DMA_NOWAIT | BUS_DMA_NOCACHE)); |
229 | if (error) |
230 | goto fail3; |
231 | |
232 | /* Record the bus address for convenient reference. */ |
233 | dmah->busaddr = dmah->dmah_map->dm_segs[0].ds_addr; |
234 | |
235 | /* Zero the DMA buffer. XXX Yikes! Is this necessary? */ |
236 | memset(dmah->vaddr, 0, size); |
237 | |
238 | /* Success! */ |
239 | return dmah; |
240 | |
241 | fail3: bus_dmamap_destroy(dmah->dmah_tag, dmah->dmah_map); |
242 | fail2: bus_dmamem_unmap(dmah->dmah_tag, dmah->vaddr, dmah->size); |
243 | fail1: bus_dmamem_free(dmah->dmah_tag, &dmah->dmah_seg, 1); |
244 | fail0: dmah->dmah_tag = NULL; /* XXX paranoia */ |
245 | kmem_free(dmah, sizeof(*dmah)); |
246 | out: DRM_DEBUG("drm_pci_alloc failed: %d\n" , error); |
247 | return NULL; |
248 | } |
249 | |
250 | /* |
251 | * Release the bus DMA mappings and memory in dmah, and deallocate it. |
252 | */ |
253 | void |
254 | drm_pci_free(struct drm_device *dev, struct drm_dma_handle *dmah) |
255 | { |
256 | |
257 | bus_dmamap_unload(dmah->dmah_tag, dmah->dmah_map); |
258 | bus_dmamap_destroy(dmah->dmah_tag, dmah->dmah_map); |
259 | bus_dmamem_unmap(dmah->dmah_tag, dmah->vaddr, dmah->size); |
260 | bus_dmamem_free(dmah->dmah_tag, &dmah->dmah_seg, 1); |
261 | dmah->dmah_tag = NULL; /* XXX paranoia */ |
262 | kmem_free(dmah, sizeof(*dmah)); |
263 | } |
264 | |
265 | /* |
266 | * Make sure the DMA-safe memory allocated for dev lies between |
267 | * min_addr and max_addr. Can be used multiple times to restrict the |
268 | * bounds further, but never to expand the bounds again. |
269 | * |
270 | * XXX Caller must guarantee nobody has used the tag yet, |
271 | * i.e. allocated any DMA memory. |
272 | */ |
273 | int |
274 | drm_limit_dma_space(struct drm_device *dev, resource_size_t min_addr, |
275 | resource_size_t max_addr) |
276 | { |
277 | int ret; |
278 | |
279 | KASSERT(min_addr <= max_addr); |
280 | |
281 | /* |
282 | * Limit it further if we have already limited it, and destroy |
283 | * the old subregion DMA tag. |
284 | */ |
285 | if (dev->dmat_subregion_p) { |
286 | min_addr = MAX(min_addr, dev->dmat_subregion_min); |
287 | max_addr = MIN(max_addr, dev->dmat_subregion_max); |
288 | bus_dmatag_destroy(dev->dmat); |
289 | } |
290 | |
291 | /* |
292 | * Create a DMA tag for a subregion from the bus's DMA tag. If |
293 | * that fails, restore dev->dmat to the whole region so that we |
294 | * need not worry about dev->dmat being uninitialized (not that |
295 | * the caller should try to allocate DMA-safe memory on failure |
296 | * anyway, but...paranoia). |
297 | */ |
298 | /* XXX errno NetBSD->Linux */ |
299 | ret = -bus_dmatag_subregion(dev->bus_dmat, min_addr, max_addr, |
300 | &dev->dmat, BUS_DMA_WAITOK); |
301 | if (ret) { |
302 | dev->dmat = dev->bus_dmat; |
303 | dev->dmat_subregion_p = false; |
304 | return ret; |
305 | } |
306 | |
307 | /* |
308 | * Remember that we have a subregion tag so that we know to |
309 | * destroy it later, and record the bounds in case we need to |
310 | * limit them again. |
311 | */ |
312 | dev->dmat_subregion_p = true; |
313 | dev->dmat_subregion_min = min_addr; |
314 | dev->dmat_subregion_max = max_addr; |
315 | |
316 | /* Success! */ |
317 | return 0; |
318 | } |
319 | |