Merge branch 'for_paulus' of master.kernel.org:/pub/scm/linux/kernel/git/galak/powerpc
[linux-2.6] / arch / sparc64 / kernel / iommu_common.c
1 /* $Id: iommu_common.c,v 1.9 2001/12/17 07:05:09 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
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
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) (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
134 out:
135         *__sg = sg;
136         *__iopte = iopte;
137         return nents;
138 }
139
140 static 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
172 void 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
195 unsigned 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 }