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 * 27 */ 28 29 /** @file ati_pcigart.c 30 * Implementation of ATI's PCIGART, which provides an aperture in card virtual 31 * address space with addresses remapped to system memory. 32 */ 33 34 #include "drmP.h" 35 36 #define ATI_PCIGART_PAGE_SIZE 4096 /* PCI GART page size */ 37 #define ATI_PCIGART_PAGE_MASK (~(ATI_PCIGART_PAGE_SIZE-1)) 38 39 #define ATI_PCIE_WRITE 0x4 40 #define ATI_PCIE_READ 0x8 41 42 #if defined(__FreeBSD__) 43 static void 44 drm_ati_alloc_pcigart_table_cb(void *arg, bus_dma_segment_t *segs, 45 int nsegs, int error) 46 { 47 struct drm_dma_handle *dmah = arg; 48 49 if (error != 0) 50 return; 51 52 DRM_KASSERT(nsegs == 1, 53 ("drm_ati_alloc_pcigart_table_cb: bad dma segment count")); 54 55 dmah->busaddr = segs[0].ds_addr; 56 } 57 #endif 58 59 static int 60 drm_ati_alloc_pcigart_table(struct drm_device *dev, 61 struct drm_ati_pcigart_info *gart_info) 62 { 63 struct drm_dma_handle *dmah; 64 int flags, ret; 65 #if defined(__NetBSD__) 66 int nsegs; 67 #endif 68 69 dmah = malloc(sizeof(struct drm_dma_handle), DRM_MEM_DMA, 70 M_ZERO | M_NOWAIT); 71 if (dmah == NULL) 72 return ENOMEM; 73 74 #if defined(__FreeBSD__) 75 DRM_UNLOCK(); 76 ret = bus_dma_tag_create(NULL, PAGE_SIZE, 0, /* tag, align, boundary */ 77 gart_info->table_mask, BUS_SPACE_MAXADDR, /* lowaddr, highaddr */ 78 NULL, NULL, /* filtfunc, filtfuncargs */ 79 gart_info->table_size, 1, /* maxsize, nsegs */ 80 gart_info->table_size, /* maxsegsize */ 81 BUS_DMA_ALLOCNOW, NULL, NULL, /* flags, lockfunc, lockfuncargs */ 82 &dmah->tag); 83 if (ret != 0) { 84 free(dmah, DRM_MEM_DMA); 85 return ENOMEM; 86 } 87 88 flags = BUS_DMA_NOWAIT | BUS_DMA_ZERO; 89 if (gart_info->gart_reg_if == DRM_ATI_GART_IGP) 90 flags |= BUS_DMA_NOCACHE; 91 92 ret = bus_dmamem_alloc(dmah->tag, &dmah->vaddr, flags, &dmah->map); 93 if (ret != 0) { 94 bus_dma_tag_destroy(dmah->tag); 95 free(dmah, DRM_MEM_DMA); 96 return ENOMEM; 97 } 98 DRM_LOCK(); 99 100 ret = bus_dmamap_load(dmah->tag, dmah->map, dmah->vaddr, 101 gart_info->table_size, drm_ati_alloc_pcigart_table_cb, dmah, 0); 102 if (ret != 0) { 103 bus_dmamem_free(dmah->tag, dmah->vaddr, dmah->map); 104 bus_dma_tag_destroy(dmah->tag); 105 free(dmah, DRM_MEM_DMA); 106 return ENOMEM; 107 } 108 109 #elif defined(__NetBSD__) 110 dmah->tag = dev->pa.pa_dmat; 111 112 flags = BUS_DMA_NOWAIT; 113 if (gart_info->gart_reg_if == DRM_ATI_GART_IGP) 114 flags |= BUS_DMA_NOCACHE; 115 116 ret = bus_dmamem_alloc(dmah->tag, gart_info->table_size, PAGE_SIZE, 117 0, dmah->segs, 1, &nsegs, flags); 118 if (ret != 0) { 119 printf("drm: unable to allocate %zu bytes of DMA, error %d\n", 120 (size_t)gart_info->table_size, ret); 121 dmah->tag = NULL; 122 free(dmah, DRM_MEM_DMA); 123 return ENOMEM; 124 } 125 if (nsegs != 1) { 126 printf("drm: bad segment count\n"); 127 bus_dmamem_free(dmah->tag, dmah->segs, 1); 128 dmah->tag = NULL; 129 free(dmah, DRM_MEM_DMA); 130 return ENOMEM; 131 } 132 133 ret = bus_dmamem_map(dmah->tag, dmah->segs, nsegs, 134 gart_info->table_size, &dmah->vaddr, 135 flags | BUS_DMA_COHERENT); 136 if (ret != 0) { 137 printf("drm: Unable to map DMA, error %d\n", ret); 138 bus_dmamem_free(dmah->tag, dmah->segs, 1); 139 dmah->tag = NULL; 140 free(dmah, DRM_MEM_DMA); 141 return ENOMEM; 142 } 143 144 ret = bus_dmamap_create(dmah->tag, gart_info->table_size, 1, 145 gart_info->table_size, 0, 146 BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW, &dmah->map); 147 if (ret != 0) { 148 printf("drm: Unable to create DMA map, error %d\n", ret); 149 bus_dmamem_unmap(dmah->tag, dmah->vaddr, gart_info->table_size); 150 bus_dmamem_free(dmah->tag, dmah->segs, 1); 151 dmah->tag = NULL; 152 free(dmah, DRM_MEM_DMA); 153 return ENOMEM; 154 } 155 156 ret = bus_dmamap_load(dmah->tag, dmah->map, dmah->vaddr, 157 gart_info->table_size, NULL, BUS_DMA_NOWAIT); 158 if (ret != 0) { 159 printf("drm: Unable to load DMA map, error %d\n", ret); 160 bus_dmamap_destroy(dmah->tag, dmah->map); 161 bus_dmamem_unmap(dmah->tag, dmah->vaddr, gart_info->table_size); 162 bus_dmamem_free(dmah->tag, dmah->segs, 1); 163 dmah->tag = NULL; 164 free(dmah, DRM_MEM_DMA); 165 return ENOMEM; 166 } 167 dmah->busaddr = dmah->map->dm_segs[0].ds_addr; 168 dmah->size = gart_info->table_size; 169 dmah->nsegs = 1; 170 #if 0 171 /* 172 * Mirror here FreeBSD doing BUS_DMA_ZERO. 173 * But I see this same memset() is done in drm_ati_pcigart_init(), 174 * so maybe this is not needed. 175 */ 176 memset(dmah->vaddr, 0, gart_info->table_size); 177 #endif 178 #endif 179 180 dev->sg->dmah = dmah; 181 182 return 0; 183 } 184 185 static void 186 drm_ati_free_pcigart_table(struct drm_device *dev, 187 struct drm_ati_pcigart_info *gart_info) 188 { 189 struct drm_dma_handle *dmah = dev->sg->dmah; 190 191 #if defined(__FreeBSD__) 192 bus_dmamem_free(dmah->tag, dmah->vaddr, dmah->map); 193 bus_dma_tag_destroy(dmah->tag); 194 #elif defined(__NetBSD__) 195 bus_dmamap_unload(dmah->tag, dmah->map); 196 bus_dmamap_destroy(dmah->tag, dmah->map); 197 bus_dmamem_unmap(dmah->tag, dmah->vaddr, dmah->size); 198 bus_dmamem_free(dmah->tag, dmah->segs, 1); 199 dmah->tag = NULL; 200 #endif 201 free(dmah, DRM_MEM_DMA); 202 dev->sg->dmah = NULL; 203 } 204 205 int 206 drm_ati_pcigart_cleanup(struct drm_device *dev, 207 struct drm_ati_pcigart_info *gart_info) 208 { 209 /* we need to support large memory configurations */ 210 if (dev->sg == NULL) { 211 DRM_ERROR("no scatter/gather memory!\n"); 212 return 0; 213 } 214 215 if (gart_info->bus_addr) { 216 if (gart_info->gart_table_location == DRM_ATI_GART_MAIN) { 217 gart_info->bus_addr = 0; 218 if (dev->sg->dmah) 219 drm_ati_free_pcigart_table(dev, gart_info); 220 } 221 } 222 223 return 1; 224 } 225 226 int 227 drm_ati_pcigart_init(struct drm_device *dev, 228 struct drm_ati_pcigart_info *gart_info) 229 { 230 void *address = NULL; 231 unsigned long pages; 232 u32 *pci_gart, page_base; 233 dma_addr_t bus_address = 0; 234 dma_addr_t entry_addr; 235 int i, j, ret = 0; 236 int max_pages; 237 238 /* we need to support large memory configurations */ 239 if (dev->sg == NULL) { 240 DRM_ERROR("no scatter/gather memory!\n"); 241 goto done; 242 } 243 244 if (gart_info->gart_table_location == DRM_ATI_GART_MAIN) { 245 DRM_DEBUG("PCI: no table in VRAM: using normal RAM\n"); 246 247 ret = drm_ati_alloc_pcigart_table(dev, gart_info); 248 if (ret) { 249 DRM_ERROR("cannot allocate PCI GART page!\n"); 250 goto done; 251 } 252 253 address = (void *)dev->sg->dmah->vaddr; 254 bus_address = dev->sg->dmah->busaddr; 255 } else { 256 address = gart_info->addr; 257 bus_address = gart_info->bus_addr; 258 DRM_DEBUG("PCI: Gart Table: VRAM %08X mapped at %08lX\n", 259 (unsigned int)bus_address, (unsigned long)address); 260 } 261 262 pci_gart = (u32 *) address; 263 264 max_pages = (gart_info->table_size / sizeof(u32)); 265 pages = (dev->sg->pages <= max_pages) 266 ? dev->sg->pages : max_pages; 267 268 memset(pci_gart, 0, max_pages * sizeof(u32)); 269 270 DRM_KASSERT(PAGE_SIZE >= ATI_PCIGART_PAGE_SIZE, ("page size too small")); 271 272 for (i = 0; i < pages; i++) { 273 entry_addr = dev->sg->busaddr[i]; 274 for (j = 0; j < (PAGE_SIZE / ATI_PCIGART_PAGE_SIZE); j++) { 275 page_base = (u32) entry_addr & ATI_PCIGART_PAGE_MASK; 276 switch(gart_info->gart_reg_if) { 277 case DRM_ATI_GART_IGP: 278 page_base |= 279 (upper_32_bits(entry_addr) & 0xff) << 4; 280 page_base |= 0xc; 281 break; 282 case DRM_ATI_GART_PCIE: 283 page_base >>= 8; 284 page_base |= 285 (upper_32_bits(entry_addr) & 0xff) << 24; 286 page_base |= ATI_PCIE_READ | ATI_PCIE_WRITE; 287 break; 288 default: 289 case DRM_ATI_GART_PCI: 290 break; 291 } 292 *pci_gart = cpu_to_le32(page_base); 293 pci_gart++; 294 entry_addr += ATI_PCIGART_PAGE_SIZE; 295 } 296 } 297 298 ret = 1; 299 300 done: 301 gart_info->addr = address; 302 gart_info->bus_addr = bus_address; 303 return ret; 304 } 305