dma.c 6.16 KB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
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>
Linus Torvalds's avatar
Linus Torvalds committed
11
#include <linux/module.h>
12
13
#include <linux/pci.h>
#include <linux/vmalloc.h>
14
#include <linux/scatterlist.h>
15

Linus Torvalds's avatar
Linus Torvalds committed
16
17
18
19
20
21
22
23
24
25
26
27
#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;
}

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

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

/* dma_region */

64
65
66
/**
 * dma_region_init - clear out all fields but do not allocate anything
 */
Linus Torvalds's avatar
Linus Torvalds committed
67
68
69
70
71
72
73
74
75
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;
}

76
77
78
/**
 * dma_region_alloc - allocate the buffer and map it to the IOMMU
 */
79
80
int dma_region_alloc(struct dma_region *dma, unsigned long n_bytes,
		     struct pci_dev *dev, int direction)
Linus Torvalds's avatar
Linus Torvalds committed
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;
	}

105
	sg_init_table(dma->sglist, dma->n_pages);
Linus Torvalds's avatar
Linus Torvalds committed
106
107
108

	/* fill scatter/gather list with pages */
	for (i = 0; i < dma->n_pages; i++) {
109
110
		unsigned long va =
		    (unsigned long)dma->kvirt + (i << PAGE_SHIFT);
Linus Torvalds's avatar
Linus Torvalds committed
111

112
113
		sg_set_page(&dma->sglist[i], vmalloc_to_page((void *)va),
				PAGE_SIZE, 0);
Linus Torvalds's avatar
Linus Torvalds committed
114
115
116
	}

	/* map sglist to the IOMMU */
117
118
	dma->n_dma_pages =
	    pci_map_sg(dev, dma->sglist, dma->n_pages, direction);
Linus Torvalds's avatar
Linus Torvalds committed
119
120
121
122
123
124
125
126
127
128
129

	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;

130
      err:
Linus Torvalds's avatar
Linus Torvalds committed
131
132
133
134
	dma_region_free(dma);
	return -ENOMEM;
}

135
136
137
/**
 * dma_region_free - unmap and free the buffer
 */
Linus Torvalds's avatar
Linus Torvalds committed
138
139
140
void dma_region_free(struct dma_region *dma)
{
	if (dma->n_dma_pages) {
141
142
		pci_unmap_sg(dma->dev, dma->sglist, dma->n_pages,
			     dma->direction);
Linus Torvalds's avatar
Linus Torvalds committed
143
144
145
146
147
148
149
150
151
152
153
154
155
156
		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 */
157
static inline int dma_region_find(struct dma_region *dma, unsigned long offset,
158
				  unsigned int start, unsigned long *rem)
Linus Torvalds's avatar
Linus Torvalds committed
159
160
161
162
{
	int i;
	unsigned long off = offset;

163
	for (i = start; i < dma->n_dma_pages; i++) {
Linus Torvalds's avatar
Linus Torvalds committed
164
165
166
167
168
169
170
171
172
173
174
175
176
		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;
}

177
178
179
180
181
182
/**
 * dma_region_offset_to_bus - get bus address of an offset within a DMA region
 *
 * Returns the DMA bus address of the byte with the given @offset relative to
 * the beginning of the @dma.
 */
183
184
dma_addr_t dma_region_offset_to_bus(struct dma_region * dma,
				    unsigned long offset)
Linus Torvalds's avatar
Linus Torvalds committed
185
{
Ben Collins's avatar
Ben Collins committed
186
	unsigned long rem = 0;
Linus Torvalds's avatar
Linus Torvalds committed
187

188
	struct scatterlist *sg =
189
	    &dma->sglist[dma_region_find(dma, offset, 0, &rem)];
Linus Torvalds's avatar
Linus Torvalds committed
190
191
192
	return sg_dma_address(sg) + rem;
}

193
194
195
/**
 * dma_region_sync_for_cpu - sync the CPU's view of the buffer
 */
196
197
void dma_region_sync_for_cpu(struct dma_region *dma, unsigned long offset,
			     unsigned long len)
Linus Torvalds's avatar
Linus Torvalds committed
198
199
{
	int first, last;
200
	unsigned long rem = 0;
Linus Torvalds's avatar
Linus Torvalds committed
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);
Linus Torvalds's avatar
Linus Torvalds committed
207

208
209
	pci_dma_sync_sg_for_cpu(dma->dev, &dma->sglist[first], last - first + 1,
				dma->direction);
Linus Torvalds's avatar
Linus Torvalds committed
210
211
}

212
213
214
/**
 * dma_region_sync_for_device - sync the IO bus' view of the buffer
 */
215
216
void dma_region_sync_for_device(struct dma_region *dma, unsigned long offset,
				unsigned long len)
Linus Torvalds's avatar
Linus Torvalds committed
217
218
{
	int first, last;
219
	unsigned long rem = 0;
Linus Torvalds's avatar
Linus Torvalds committed
220
221
222
223

	if (!len)
		len = 1;

224
225
	first = dma_region_find(dma, offset, 0, &rem);
	last = dma_region_find(dma, rem + len - 1, first, &rem);
Linus Torvalds's avatar
Linus Torvalds committed
226

227
228
	pci_dma_sync_sg_for_device(dma->dev, &dma->sglist[first],
				   last - first + 1, dma->direction);
Linus Torvalds's avatar
Linus Torvalds committed
229
230
231
232
}

#ifdef CONFIG_MMU

Nick Piggin's avatar
Nick Piggin committed
233
static int dma_region_pagefault(struct vm_area_struct *vma,
234
				struct vm_fault *vmf)
Linus Torvalds's avatar
Linus Torvalds committed
235
{
Nick Piggin's avatar
Nick Piggin committed
236
	struct dma_region *dma = (struct dma_region *)vma->vm_private_data;
Linus Torvalds's avatar
Linus Torvalds committed
237
238

	if (!dma->kvirt)
239
		return VM_FAULT_SIGBUS;
Nick Piggin's avatar
Nick Piggin committed
240
241

	if (vmf->pgoff >= dma->n_pages)
242
		return VM_FAULT_SIGBUS;
Nick Piggin's avatar
Nick Piggin committed
243

244
	vmf->page = vmalloc_to_page(dma->kvirt + (vmf->pgoff << PAGE_SHIFT));
Nick Piggin's avatar
Nick Piggin committed
245
246
	get_page(vmf->page);
	return 0;
Linus Torvalds's avatar
Linus Torvalds committed
247
248
}

249
static const struct vm_operations_struct dma_region_vm_ops = {
Nick Piggin's avatar
Nick Piggin committed
250
	.fault = dma_region_pagefault,
Linus Torvalds's avatar
Linus Torvalds committed
251
252
};

253
254
255
/**
 * dma_region_mmap - map the buffer into a user space process
 */
256
257
int dma_region_mmap(struct dma_region *dma, struct file *file,
		    struct vm_area_struct *vma)
Linus Torvalds's avatar
Linus Torvalds committed
258
259
260
261
262
263
{
	unsigned long size;

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

Nick Piggin's avatar
Nick Piggin committed
264
	/* must be page-aligned (XXX: comment is wrong, we could allow pgoff) */
Linus Torvalds's avatar
Linus Torvalds committed
265
266
267
268
269
270
271
272
273
274
275
	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;
276
	vma->vm_flags |= VM_RESERVED | VM_ALWAYSDUMP;
Linus Torvalds's avatar
Linus Torvalds committed
277
278
279
280

	return 0;
}

281
#else				/* CONFIG_MMU */
Linus Torvalds's avatar
Linus Torvalds committed
282

283
284
int dma_region_mmap(struct dma_region *dma, struct file *file,
		    struct vm_area_struct *vma)
Linus Torvalds's avatar
Linus Torvalds committed
285
286
287
288
{
	return -EINVAL;
}

289
#endif				/* CONFIG_MMU */