1 /* $Id: iommu_common.c,v 1.6.2.1 2001/12/11 22:47:27 davem 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
verify_lengths(struct scatterlist * sg,int nents,int npages)15 static 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
verify_one_map(struct scatterlist * dma_sg,struct scatterlist ** __sg,int nents,iopte_t ** __iopte)58 static 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) (sg->address ?
70 sg->address :
71 page_address(sg->page) + sg->offset);
72 while (dlen > 0) {
73 unsigned long paddr;
74
75 /* SG and DMA_SG must begin at the same sub-page boundary. */
76 if ((sgaddr & ~IO_PAGE_MASK) != (daddr & ~IO_PAGE_MASK)) {
77 printk("verify_one_map: Wrong start offset "
78 "sg[%08lx] dma[%08x]\n",
79 sgaddr, daddr);
80 nents = -1;
81 goto out;
82 }
83
84 /* Verify the IOPTE points to the right page. */
85 paddr = iopte_val(*iopte) & IOPTE_PAGE;
86 if ((paddr + PAGE_OFFSET) != (sgaddr & IO_PAGE_MASK)) {
87 printk("verify_one_map: IOPTE[%08lx] maps the "
88 "wrong page, should be [%08lx]\n",
89 iopte_val(*iopte), (sgaddr & IO_PAGE_MASK) - PAGE_OFFSET);
90 nents = -1;
91 goto out;
92 }
93
94 /* If this SG crosses a page, adjust to that next page
95 * boundary and loop.
96 */
97 if ((sgaddr & IO_PAGE_MASK) ^ ((sgaddr + sglen - 1) & IO_PAGE_MASK)) {
98 unsigned long next_page, diff;
99
100 next_page = (sgaddr + IO_PAGE_SIZE) & IO_PAGE_MASK;
101 diff = next_page - sgaddr;
102 sgaddr += diff;
103 daddr += diff;
104 sglen -= diff;
105 dlen -= diff;
106 if (dlen > 0)
107 iopte++;
108 continue;
109 }
110
111 /* SG wholly consumed within this page. */
112 daddr += sglen;
113 dlen -= sglen;
114
115 if (dlen > 0 && ((daddr & ~IO_PAGE_MASK) == 0))
116 iopte++;
117
118 sg++;
119 if (--nents <= 0)
120 break;
121 sgaddr = (unsigned long) (sg->address ?
122 sg->address :
123 page_address(sg->page) + sg->offset);
124 sglen = sg->length;
125 }
126 if (dlen < 0) {
127 /* Transfer overrun, big problems. */
128 printk("verify_one_map: Transfer overrun by %d bytes.\n",
129 -dlen);
130 nents = -1;
131 } else {
132 /* Advance to next dma_sg implies that the next iopte will
133 * begin it.
134 */
135 iopte++;
136 }
137
138 out:
139 *__sg = sg;
140 *__iopte = iopte;
141 return nents;
142 }
143
verify_maps(struct scatterlist * sg,int nents,iopte_t * iopte)144 static int verify_maps(struct scatterlist *sg, int nents, iopte_t *iopte)
145 {
146 struct scatterlist *dma_sg = sg;
147 struct scatterlist *orig_dma_sg = dma_sg;
148 int orig_nents = nents;
149
150 for (;;) {
151 nents = verify_one_map(dma_sg, &sg, nents, &iopte);
152 if (nents <= 0)
153 break;
154 dma_sg++;
155 if (dma_sg->dma_length == 0)
156 break;
157 }
158
159 if (nents > 0) {
160 printk("verify_maps: dma maps consumed by some sgs remain (%d)\n",
161 nents);
162 return -1;
163 }
164
165 if (nents < 0) {
166 printk("verify_maps: Error, messed up mappings, "
167 "at sg %d dma_sg %d\n",
168 (int) (orig_nents + nents), (int) (dma_sg - orig_dma_sg));
169 return -1;
170 }
171
172 /* This test passes... */
173 return 0;
174 }
175
verify_sglist(struct scatterlist * sg,int nents,iopte_t * iopte,int npages)176 void verify_sglist(struct scatterlist *sg, int nents, iopte_t *iopte, int npages)
177 {
178 if (verify_lengths(sg, nents, npages) < 0 ||
179 verify_maps(sg, nents, iopte) < 0) {
180 int i;
181
182 printk("verify_sglist: Crap, messed up mappings, dumping, iodma at ");
183 printk("%016lx.\n", sg->dma_address & IO_PAGE_MASK);
184
185 for (i = 0; i < nents; i++) {
186 printk("sg(%d): address(%p) length(%x) "
187 "dma_address[%016lx] dma_length[%016lx]\n",
188 i,
189 sg[i].address, sg[i].length,
190 sg[i].dma_address, sg[i].dma_length);
191 }
192 }
193
194 /* Seems to be ok */
195 }
196 #endif
197
prepare_sg(struct scatterlist * sg,int nents)198 unsigned long prepare_sg(struct scatterlist *sg, int nents)
199 {
200 struct scatterlist *dma_sg = sg;
201 unsigned long prev;
202 u32 dent_addr, dent_len;
203
204 prev = (unsigned long) (sg->address ?
205 sg->address :
206 page_address(sg->page) + sg->offset);
207 prev += (unsigned long) (dent_len = sg->length);
208 dent_addr = (u32) ((unsigned long)(sg->address ?
209 sg->address :
210 page_address(sg->page) + sg->offset)
211 & (IO_PAGE_SIZE - 1UL));
212 while (--nents) {
213 unsigned long addr;
214
215 sg++;
216 addr = (unsigned long) (sg->address ?
217 sg->address :
218 page_address(sg->page) + sg->offset);
219 if (! VCONTIG(prev, addr)) {
220 dma_sg->dma_address = dent_addr;
221 dma_sg->dma_length = dent_len;
222 dma_sg++;
223
224 dent_addr = ((dent_addr +
225 dent_len +
226 (IO_PAGE_SIZE - 1UL)) >> IO_PAGE_SHIFT);
227 dent_addr <<= IO_PAGE_SHIFT;
228 dent_addr += addr & (IO_PAGE_SIZE - 1UL);
229 dent_len = 0;
230 }
231 dent_len += sg->length;
232 prev = addr + sg->length;
233 }
234 dma_sg->dma_address = dent_addr;
235 dma_sg->dma_length = dent_len;
236
237 return ((unsigned long) dent_addr +
238 (unsigned long) dent_len +
239 (IO_PAGE_SIZE - 1UL)) >> IO_PAGE_SHIFT;
240 }
241