Home | History | Annotate | Line # | Download | only in bsd-core
      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 (at) 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 
    306 MODULE(MODULE_CLASS_MISC, ati_pcigart, "drm");
    307 
    308 static int
    309 ati_pcigart_modcmd(modcmd_t cmd, void *priv)
    310 {
    311 	if (cmd == MODULE_CMD_INIT || cmd == MODULE_CMD_FINI)
    312 		return 0;
    313 	return ENOTTY;
    314 }
    315