dma.c 6.0 KB
Newer Older
L
Linus Torvalds 已提交
1 2 3 4 5 6 7 8 9
/*
 * DMA region bookkeeping routines
 *
 * Copyright (C) 2002 Maas Digital LLC
 *
 * This code is licensed under the GPL.  See the file COPYING in the root
 * directory of the kernel sources for details.
 */

10
#include <linux/mm.h>
L
Linus Torvalds 已提交
11
#include <linux/module.h>
12
#include <linux/pci.h>
L
Linus Torvalds 已提交
13
#include <linux/slab.h>
14 15 16
#include <linux/vmalloc.h>
#include <asm/scatterlist.h>

L
Linus Torvalds 已提交
17 18 19 20 21 22 23 24 25 26 27 28
#include "dma.h"

/* dma_prog_region */

void dma_prog_region_init(struct dma_prog_region *prog)
{
	prog->kvirt = NULL;
	prog->dev = NULL;
	prog->n_pages = 0;
	prog->bus_addr = 0;
}

29 30
int dma_prog_region_alloc(struct dma_prog_region *prog, unsigned long n_bytes,
			  struct pci_dev *dev)
L
Linus Torvalds 已提交
31 32 33 34 35 36 37 38
{
	/* round up to page size */
	n_bytes = PAGE_ALIGN(n_bytes);

	prog->n_pages = n_bytes >> PAGE_SHIFT;

	prog->kvirt = pci_alloc_consistent(dev, n_bytes, &prog->bus_addr);
	if (!prog->kvirt) {
39 40
		printk(KERN_ERR
		       "dma_prog_region_alloc: pci_alloc_consistent() failed\n");
L
Linus Torvalds 已提交
41 42 43 44 45 46 47 48 49 50 51 52
		dma_prog_region_free(prog);
		return -ENOMEM;
	}

	prog->dev = dev;

	return 0;
}

void dma_prog_region_free(struct dma_prog_region *prog)
{
	if (prog->kvirt) {
53 54
		pci_free_consistent(prog->dev, prog->n_pages << PAGE_SHIFT,
				    prog->kvirt, prog->bus_addr);
L
Linus Torvalds 已提交
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
	}

	prog->kvirt = NULL;
	prog->dev = NULL;
	prog->n_pages = 0;
	prog->bus_addr = 0;
}

/* dma_region */

void dma_region_init(struct dma_region *dma)
{
	dma->kvirt = NULL;
	dma->dev = NULL;
	dma->n_pages = 0;
	dma->n_dma_pages = 0;
	dma->sglist = NULL;
}

74 75
int dma_region_alloc(struct dma_region *dma, unsigned long n_bytes,
		     struct pci_dev *dev, int direction)
L
Linus Torvalds 已提交
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
{
	unsigned int i;

	/* round up to page size */
	n_bytes = PAGE_ALIGN(n_bytes);

	dma->n_pages = n_bytes >> PAGE_SHIFT;

	dma->kvirt = vmalloc_32(n_bytes);
	if (!dma->kvirt) {
		printk(KERN_ERR "dma_region_alloc: vmalloc_32() failed\n");
		goto err;
	}

	/* Clear the ram out, no junk to the user */
	memset(dma->kvirt, 0, n_bytes);

	/* allocate scatter/gather list */
	dma->sglist = vmalloc(dma->n_pages * sizeof(*dma->sglist));
	if (!dma->sglist) {
		printk(KERN_ERR "dma_region_alloc: vmalloc(sglist) failed\n");
		goto err;
	}

	/* just to be safe - this will become unnecessary once sglist->address goes away */
	memset(dma->sglist, 0, dma->n_pages * sizeof(*dma->sglist));

	/* fill scatter/gather list with pages */
	for (i = 0; i < dma->n_pages; i++) {
105 106
		unsigned long va =
		    (unsigned long)dma->kvirt + (i << PAGE_SHIFT);
L
Linus Torvalds 已提交
107 108 109 110 111 112

		dma->sglist[i].page = vmalloc_to_page((void *)va);
		dma->sglist[i].length = PAGE_SIZE;
	}

	/* map sglist to the IOMMU */
113 114
	dma->n_dma_pages =
	    pci_map_sg(dev, dma->sglist, dma->n_pages, direction);
L
Linus Torvalds 已提交
115 116 117 118 119 120 121 122 123 124 125

	if (dma->n_dma_pages == 0) {
		printk(KERN_ERR "dma_region_alloc: pci_map_sg() failed\n");
		goto err;
	}

	dma->dev = dev;
	dma->direction = direction;

	return 0;

126
      err:
L
Linus Torvalds 已提交
127 128 129 130 131 132 133
	dma_region_free(dma);
	return -ENOMEM;
}

void dma_region_free(struct dma_region *dma)
{
	if (dma->n_dma_pages) {
134 135
		pci_unmap_sg(dma->dev, dma->sglist, dma->n_pages,
			     dma->direction);
L
Linus Torvalds 已提交
136 137 138 139 140 141 142 143 144 145 146 147 148 149
		dma->n_dma_pages = 0;
		dma->dev = NULL;
	}

	vfree(dma->sglist);
	dma->sglist = NULL;

	vfree(dma->kvirt);
	dma->kvirt = NULL;
	dma->n_pages = 0;
}

/* find the scatterlist index and remaining offset corresponding to a
   given offset from the beginning of the buffer */
150
static inline int dma_region_find(struct dma_region *dma, unsigned long offset,
151
				  unsigned int start, unsigned long *rem)
L
Linus Torvalds 已提交
152 153 154 155
{
	int i;
	unsigned long off = offset;

156
	for (i = start; i < dma->n_dma_pages; i++) {
L
Linus Torvalds 已提交
157 158 159 160 161 162 163 164 165 166 167 168 169
		if (off < sg_dma_len(&dma->sglist[i])) {
			*rem = off;
			break;
		}

		off -= sg_dma_len(&dma->sglist[i]);
	}

	BUG_ON(i >= dma->n_dma_pages);

	return i;
}

170 171
dma_addr_t dma_region_offset_to_bus(struct dma_region * dma,
				    unsigned long offset)
L
Linus Torvalds 已提交
172
{
B
Ben Collins 已提交
173
	unsigned long rem = 0;
L
Linus Torvalds 已提交
174

175
	struct scatterlist *sg =
176
	    &dma->sglist[dma_region_find(dma, offset, 0, &rem)];
L
Linus Torvalds 已提交
177 178 179
	return sg_dma_address(sg) + rem;
}

180 181
void dma_region_sync_for_cpu(struct dma_region *dma, unsigned long offset,
			     unsigned long len)
L
Linus Torvalds 已提交
182 183
{
	int first, last;
184
	unsigned long rem = 0;
L
Linus Torvalds 已提交
185 186 187 188

	if (!len)
		len = 1;

189 190
	first = dma_region_find(dma, offset, 0, &rem);
	last = dma_region_find(dma, rem + len - 1, first, &rem);
L
Linus Torvalds 已提交
191

192 193
	pci_dma_sync_sg_for_cpu(dma->dev, &dma->sglist[first], last - first + 1,
				dma->direction);
L
Linus Torvalds 已提交
194 195
}

196 197
void dma_region_sync_for_device(struct dma_region *dma, unsigned long offset,
				unsigned long len)
L
Linus Torvalds 已提交
198 199
{
	int first, last;
200
	unsigned long rem = 0;
L
Linus Torvalds 已提交
201 202 203 204

	if (!len)
		len = 1;

205 206
	first = dma_region_find(dma, offset, 0, &rem);
	last = dma_region_find(dma, rem + len - 1, first, &rem);
L
Linus Torvalds 已提交
207

208 209
	pci_dma_sync_sg_for_device(dma->dev, &dma->sglist[first],
				   last - first + 1, dma->direction);
L
Linus Torvalds 已提交
210 211 212 213 214 215
}

#ifdef CONFIG_MMU

/* nopage() handler for mmap access */

216 217
static struct page *dma_region_pagefault(struct vm_area_struct *area,
					 unsigned long address, int *type)
L
Linus Torvalds 已提交
218 219 220 221 222
{
	unsigned long offset;
	unsigned long kernel_virt_addr;
	struct page *ret = NOPAGE_SIGBUS;

223
	struct dma_region *dma = (struct dma_region *)area->vm_private_data;
L
Linus Torvalds 已提交
224 225 226 227

	if (!dma->kvirt)
		goto out;

228 229 230
	if ((address < (unsigned long)area->vm_start) ||
	    (address >
	     (unsigned long)area->vm_start + (dma->n_pages << PAGE_SHIFT)))
L
Linus Torvalds 已提交
231 232 233 234 235
		goto out;

	if (type)
		*type = VM_FAULT_MINOR;
	offset = address - area->vm_start;
236 237
	kernel_virt_addr = (unsigned long)dma->kvirt + offset;
	ret = vmalloc_to_page((void *)kernel_virt_addr);
L
Linus Torvalds 已提交
238
	get_page(ret);
239
      out:
L
Linus Torvalds 已提交
240 241 242 243
	return ret;
}

static struct vm_operations_struct dma_region_vm_ops = {
244
	.nopage = dma_region_pagefault,
L
Linus Torvalds 已提交
245 246
};

247 248
int dma_region_mmap(struct dma_region *dma, struct file *file,
		    struct vm_area_struct *vma)
L
Linus Torvalds 已提交
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271
{
	unsigned long size;

	if (!dma->kvirt)
		return -EINVAL;

	/* must be page-aligned */
	if (vma->vm_pgoff != 0)
		return -EINVAL;

	/* check the length */
	size = vma->vm_end - vma->vm_start;
	if (size > (dma->n_pages << PAGE_SHIFT))
		return -EINVAL;

	vma->vm_ops = &dma_region_vm_ops;
	vma->vm_private_data = dma;
	vma->vm_file = file;
	vma->vm_flags |= VM_RESERVED;

	return 0;
}

272
#else				/* CONFIG_MMU */
L
Linus Torvalds 已提交
273

274 275
int dma_region_mmap(struct dma_region *dma, struct file *file,
		    struct vm_area_struct *vma)
L
Linus Torvalds 已提交
276 277 278 279
{
	return -EINVAL;
}

280
#endif				/* CONFIG_MMU */