1/* $Id: iommu_common.c,v 1.1.1.1 2007/08/03 18:52:18 Exp $
2 * iommu_common.c: UltraSparc SBUS/PCI common iommu code.
3 *
4 * Copyright (C) 1999 David S. Miller (davem@redhat.com)
5 */
6
7#include "iommu_common.h"
8
9/* You are _strongly_ advised to enable the following debugging code
10 * any time you make changes to the sg code below, run it for a while
11 * with filesystems mounted read-only before buying the farm... -DaveM
12 */
13
14#ifdef VERIFY_SG
15static int verify_lengths(struct scatterlist *sg, int nents, int npages)
16{
17	int sg_len, dma_len;
18	int i, pgcount;
19
20	sg_len = 0;
21	for (i = 0; i < nents; i++)
22		sg_len += sg[i].length;
23
24	dma_len = 0;
25	for (i = 0; i < nents && sg[i].dma_length; i++)
26		dma_len += sg[i].dma_length;
27
28	if (sg_len != dma_len) {
29		printk("verify_lengths: Error, different, sg[%d] dma[%d]\n",
30		       sg_len, dma_len);
31		return -1;
32	}
33
34	pgcount = 0;
35	for (i = 0; i < nents && sg[i].dma_length; i++) {
36		unsigned long start, end;
37
38		start = sg[i].dma_address;
39		start = start & IO_PAGE_MASK;
40
41		end = sg[i].dma_address + sg[i].dma_length;
42		end = (end + (IO_PAGE_SIZE - 1)) & IO_PAGE_MASK;
43
44		pgcount += ((end - start) >> IO_PAGE_SHIFT);
45	}
46
47	if (pgcount != npages) {
48		printk("verify_lengths: Error, page count wrong, "
49		       "npages[%d] pgcount[%d]\n",
50		       npages, pgcount);
51		return -1;
52	}
53
54	/* This test passes... */
55	return 0;
56}
57
58static int verify_one_map(struct scatterlist *dma_sg, struct scatterlist **__sg, int nents, iopte_t **__iopte)
59{
60	struct scatterlist *sg = *__sg;
61	iopte_t *iopte = *__iopte;
62	u32 dlen = dma_sg->dma_length;
63	u32 daddr;
64	unsigned int sglen;
65	unsigned long sgaddr;
66
67	daddr = dma_sg->dma_address;
68	sglen = sg->length;
69	sgaddr = (unsigned long) (page_address(sg->page) + sg->offset);
70	while (dlen > 0) {
71		unsigned long paddr;
72
73		/* SG and DMA_SG must begin at the same sub-page boundary. */
74		if ((sgaddr & ~IO_PAGE_MASK) != (daddr & ~IO_PAGE_MASK)) {
75			printk("verify_one_map: Wrong start offset "
76			       "sg[%08lx] dma[%08x]\n",
77			       sgaddr, daddr);
78			nents = -1;
79			goto out;
80		}
81
82		/* Verify the IOPTE points to the right page. */
83		paddr = iopte_val(*iopte) & IOPTE_PAGE;
84		if ((paddr + PAGE_OFFSET) != (sgaddr & IO_PAGE_MASK)) {
85			printk("verify_one_map: IOPTE[%08lx] maps the "
86			       "wrong page, should be [%08lx]\n",
87			       iopte_val(*iopte), (sgaddr & IO_PAGE_MASK) - PAGE_OFFSET);
88			nents = -1;
89			goto out;
90		}
91
92		/* If this SG crosses a page, adjust to that next page
93		 * boundary and loop.
94		 */
95		if ((sgaddr & IO_PAGE_MASK) ^ ((sgaddr + sglen - 1) & IO_PAGE_MASK)) {
96			unsigned long next_page, diff;
97
98			next_page = (sgaddr + IO_PAGE_SIZE) & IO_PAGE_MASK;
99			diff = next_page - sgaddr;
100			sgaddr += diff;
101			daddr += diff;
102			sglen -= diff;
103			dlen -= diff;
104			if (dlen > 0)
105				iopte++;
106			continue;
107		}
108
109		/* SG wholly consumed within this page. */
110		daddr += sglen;
111		dlen -= sglen;
112
113		if (dlen > 0 && ((daddr & ~IO_PAGE_MASK) == 0))
114			iopte++;
115
116		sg++;
117		if (--nents <= 0)
118			break;
119		sgaddr = (unsigned long) (page_address(sg->page) + sg->offset);
120		sglen = sg->length;
121	}
122	if (dlen < 0) {
123		/* Transfer overrun, big problems. */
124		printk("verify_one_map: Transfer overrun by %d bytes.\n",
125		       -dlen);
126		nents = -1;
127	} else {
128		/* Advance to next dma_sg implies that the next iopte will
129		 * begin it.
130		 */
131		iopte++;
132	}
133
134out:
135	*__sg = sg;
136	*__iopte = iopte;
137	return nents;
138}
139
140static int verify_maps(struct scatterlist *sg, int nents, iopte_t *iopte)
141{
142	struct scatterlist *dma_sg = sg;
143	struct scatterlist *orig_dma_sg = dma_sg;
144	int orig_nents = nents;
145
146	for (;;) {
147		nents = verify_one_map(dma_sg, &sg, nents, &iopte);
148		if (nents <= 0)
149			break;
150		dma_sg++;
151		if (dma_sg->dma_length == 0)
152			break;
153	}
154
155	if (nents > 0) {
156		printk("verify_maps: dma maps consumed by some sgs remain (%d)\n",
157		       nents);
158		return -1;
159	}
160
161	if (nents < 0) {
162		printk("verify_maps: Error, messed up mappings, "
163		       "at sg %d dma_sg %d\n",
164		       (int) (orig_nents + nents), (int) (dma_sg - orig_dma_sg));
165		return -1;
166	}
167
168	/* This test passes... */
169	return 0;
170}
171
172void verify_sglist(struct scatterlist *sg, int nents, iopte_t *iopte, int npages)
173{
174	if (verify_lengths(sg, nents, npages) < 0 ||
175	    verify_maps(sg, nents, iopte) < 0) {
176		int i;
177
178		printk("verify_sglist: Crap, messed up mappings, dumping, iodma at ");
179		printk("%016lx.\n", sg->dma_address & IO_PAGE_MASK);
180
181		for (i = 0; i < nents; i++) {
182			printk("sg(%d): page_addr(%p) off(%x) length(%x) "
183			       "dma_address[%016lx] dma_length[%016lx]\n",
184			       i,
185			       page_address(sg[i].page), sg[i].offset,
186			       sg[i].length,
187			       sg[i].dma_address, sg[i].dma_length);
188		}
189	}
190
191	/* Seems to be ok */
192}
193#endif
194
195unsigned long prepare_sg(struct scatterlist *sg, int nents)
196{
197	struct scatterlist *dma_sg = sg;
198	unsigned long prev;
199	u32 dent_addr, dent_len;
200
201	prev  = (unsigned long) (page_address(sg->page) + sg->offset);
202	prev += (unsigned long) (dent_len = sg->length);
203	dent_addr = (u32) ((unsigned long)(page_address(sg->page) + sg->offset)
204			   & (IO_PAGE_SIZE - 1UL));
205	while (--nents) {
206		unsigned long addr;
207
208		sg++;
209		addr = (unsigned long) (page_address(sg->page) + sg->offset);
210		if (! VCONTIG(prev, addr)) {
211			dma_sg->dma_address = dent_addr;
212			dma_sg->dma_length = dent_len;
213			dma_sg++;
214
215			dent_addr = ((dent_addr +
216				      dent_len +
217				      (IO_PAGE_SIZE - 1UL)) >> IO_PAGE_SHIFT);
218			dent_addr <<= IO_PAGE_SHIFT;
219			dent_addr += addr & (IO_PAGE_SIZE - 1UL);
220			dent_len = 0;
221		}
222		dent_len += sg->length;
223		prev = addr + sg->length;
224	}
225	dma_sg->dma_address = dent_addr;
226	dma_sg->dma_length = dent_len;
227
228	return ((unsigned long) dent_addr +
229		(unsigned long) dent_len +
230		(IO_PAGE_SIZE - 1UL)) >> IO_PAGE_SHIFT;
231}
232