| 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 | |