dma.c 5.9 KB
Newer Older
L
Linus Torvalds 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
/*
 * 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.
 */

#include <linux/module.h>
#include <linux/vmalloc.h>
#include <linux/slab.h>
#include <linux/mm.h>
#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;
}

26 27
int dma_prog_region_alloc(struct dma_prog_region *prog, unsigned long n_bytes,
			  struct pci_dev *dev)
L
Linus Torvalds 已提交
28 29 30 31 32 33 34 35
{
	/* 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) {
36 37
		printk(KERN_ERR
		       "dma_prog_region_alloc: pci_alloc_consistent() failed\n");
L
Linus Torvalds 已提交
38 39 40 41 42 43 44 45 46 47 48 49
		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) {
50 51
		pci_free_consistent(prog->dev, prog->n_pages << PAGE_SHIFT,
				    prog->kvirt, prog->bus_addr);
L
Linus Torvalds 已提交
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
	}

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

71 72
int dma_region_alloc(struct dma_region *dma, unsigned long n_bytes,
		     struct pci_dev *dev, int direction)
L
Linus Torvalds 已提交
73 74 75 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
{
	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++) {
102 103
		unsigned long va =
		    (unsigned long)dma->kvirt + (i << PAGE_SHIFT);
L
Linus Torvalds 已提交
104 105 106 107 108 109

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

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

	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;

123
      err:
L
Linus Torvalds 已提交
124 125 126 127 128 129 130
	dma_region_free(dma);
	return -ENOMEM;
}

void dma_region_free(struct dma_region *dma)
{
	if (dma->n_dma_pages) {
131 132
		pci_unmap_sg(dma->dev, dma->sglist, dma->n_pages,
			     dma->direction);
L
Linus Torvalds 已提交
133 134 135 136 137 138 139 140 141 142 143 144 145 146
		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 */
147 148
static inline int dma_region_find(struct dma_region *dma, unsigned long offset,
				  unsigned long *rem)
L
Linus Torvalds 已提交
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
{
	int i;
	unsigned long off = offset;

	for (i = 0; i < dma->n_dma_pages; i++) {
		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;
}

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

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

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

	if (!len)
		len = 1;

	first = dma_region_find(dma, offset, &rem);
	last = dma_region_find(dma, offset + len - 1, &rem);

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

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

	if (!len)
		len = 1;

	first = dma_region_find(dma, offset, &rem);
	last = dma_region_find(dma, offset + len - 1, &rem);

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

#ifdef CONFIG_MMU

/* nopage() handler for mmap access */

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

220
	struct dma_region *dma = (struct dma_region *)area->vm_private_data;
L
Linus Torvalds 已提交
221 222 223 224

	if (!dma->kvirt)
		goto out;

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

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

static struct vm_operations_struct dma_region_vm_ops = {
241
	.nopage = dma_region_pagefault,
L
Linus Torvalds 已提交
242 243
};

244 245
int dma_region_mmap(struct dma_region *dma, struct file *file,
		    struct vm_area_struct *vma)
L
Linus Torvalds 已提交
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268
{
	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;
}

269
#else				/* CONFIG_MMU */
L
Linus Torvalds 已提交
270

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

277
#endif				/* CONFIG_MMU */