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