isadma.c revision 1.24 1 /* $NetBSD: isadma.c,v 1.24 1997/05/28 20:02:39 mycroft Exp $ */
2
3 #include <sys/param.h>
4 #include <sys/systm.h>
5 #include <sys/file.h>
6 #include <sys/buf.h>
7 #include <sys/syslog.h>
8 #include <sys/malloc.h>
9 #include <sys/proc.h>
10 #include <sys/uio.h>
11
12 #include <vm/vm.h>
13
14 #include <machine/pio.h>
15
16 #include <dev/isa/isareg.h>
17 #include <dev/isa/isadmavar.h>
18 #include <dev/isa/isadmareg.h>
19
20 /* region of physical memory known to be contiguous */
21 vm_offset_t isaphysmem;
22 static caddr_t dma_bounce[8]; /* XXX */
23 static char dma_bounced[8]; /* XXX */
24 #define MAXDMASZ 512 /* XXX */
25 static u_int8_t dma_finished;
26 static vm_size_t dma_length[8];
27
28 /* high byte of address is stored in this port for i-th dma channel */
29 static int dmapageport[2][4] = {
30 {0x87, 0x83, 0x81, 0x82},
31 {0x8f, 0x8b, 0x89, 0x8a}
32 };
33
34 static u_int8_t dmamode[4] = {
35 DMA37MD_READ | DMA37MD_SINGLE,
36 DMA37MD_WRITE | DMA37MD_SINGLE,
37 DMA37MD_READ | DMA37MD_LOOP,
38 DMA37MD_WRITE | DMA37MD_LOOP
39 };
40
41 int isa_dmarangecheck __P((vm_offset_t, u_long, int));
42 caddr_t isa_allocphysmem __P((caddr_t, unsigned, void (*)(void)));
43 void isa_freephysmem __P((caddr_t, unsigned));
44
45 inline void
46 isa_dmaunmask(chan)
47 int chan;
48 {
49 int ochan = chan & 3;
50
51 #ifdef ISADMA_DEBUG
52 if (chan < 0 || chan > 7)
53 panic("isa_dmacascade: impossible request");
54 #endif
55
56 /* set dma channel mode, and set dma channel mode */
57 if ((chan & 4) == 0)
58 outb(DMA1_SMSK, ochan | DMA37SM_CLEAR);
59 else
60 outb(DMA2_SMSK, ochan | DMA37SM_CLEAR);
61 }
62
63 inline void
64 isa_dmamask(chan)
65 int chan;
66 {
67 int ochan = chan & 3;
68
69 #ifdef ISADMA_DEBUG
70 if (chan < 0 || chan > 7)
71 panic("isa_dmacascade: impossible request");
72 #endif
73
74 /* set dma channel mode, and set dma channel mode */
75 if ((chan & 4) == 0) {
76 outb(DMA1_SMSK, ochan | DMA37SM_SET);
77 outb(DMA1_FFC, 0);
78 } else {
79 outb(DMA2_SMSK, ochan | DMA37SM_SET);
80 outb(DMA2_FFC, 0);
81 }
82 }
83
84 /*
85 * isa_dmacascade(): program 8237 DMA controller channel to accept
86 * external dma control by a board.
87 */
88 void
89 isa_dmacascade(chan)
90 int chan;
91 {
92 int ochan = chan & 3;
93
94 #ifdef ISADMA_DEBUG
95 if (chan < 0 || chan > 7)
96 panic("isa_dmacascade: impossible request");
97 #endif
98
99 /* set dma channel mode, and set dma channel mode */
100 if ((chan & 4) == 0)
101 outb(DMA1_MODE, ochan | DMA37MD_CASCADE);
102 else
103 outb(DMA2_MODE, ochan | DMA37MD_CASCADE);
104
105 isa_dmaunmask(chan);
106 }
107
108 /*
109 * isa_dmastart(): program 8237 DMA controller channel, avoid page alignment
110 * problems by using a bounce buffer.
111 */
112 void
113 isa_dmastart(flags, addr, nbytes, chan)
114 int flags;
115 caddr_t addr;
116 vm_size_t nbytes;
117 int chan;
118 {
119 vm_offset_t phys;
120 int waport;
121 caddr_t newaddr;
122 int ochan = chan & 3;
123
124 #ifdef ISADMA_DEBUG
125 if (chan < 0 || chan > 7 ||
126 ((chan & 4) ? (nbytes >= (1<<17) || nbytes & 1 || (u_int)addr & 1) :
127 (nbytes >= (1<<16))))
128 panic("isa_dmastart: impossible request");
129 #endif
130
131 if (isa_dmarangecheck((vm_offset_t) addr, nbytes, chan)) {
132 if (dma_bounce[chan] == 0)
133 dma_bounce[chan] =
134 /*(caddr_t)malloc(MAXDMASZ, M_TEMP, M_WAITOK);*/
135 (caddr_t) isaphysmem + NBPG*chan;
136 dma_bounced[chan] = 1;
137 newaddr = dma_bounce[chan];
138 *(int *) newaddr = 0; /* XXX */
139 /* copy bounce buffer on write */
140 if ((flags & DMAMODE_READ) == 0)
141 bcopy(addr, newaddr, nbytes);
142 addr = newaddr;
143 }
144
145 dma_length[chan] = nbytes;
146
147 /* translate to physical */
148 phys = pmap_extract(pmap_kernel(), (vm_offset_t)addr);
149
150 isa_dmamask(chan);
151 dma_finished &= ~(1 << chan);
152
153 if ((chan & 4) == 0) {
154 /* set dma channel mode */
155 outb(DMA1_MODE, ochan | dmamode[flags]);
156
157 /* send start address */
158 waport = DMA1_CHN(ochan);
159 outb(dmapageport[0][ochan], phys>>16);
160 outb(waport, phys);
161 outb(waport, phys>>8);
162
163 /* send count */
164 outb(waport + 1, --nbytes);
165 outb(waport + 1, nbytes>>8);
166 } else {
167 /* set dma channel mode */
168 outb(DMA2_MODE, ochan | dmamode[flags]);
169
170 /* send start address */
171 waport = DMA2_CHN(ochan);
172 outb(dmapageport[1][ochan], phys>>16);
173 phys >>= 1;
174 outb(waport, phys);
175 outb(waport, phys>>8);
176
177 /* send count */
178 nbytes >>= 1;
179 outb(waport + 2, --nbytes);
180 outb(waport + 2, nbytes>>8);
181 }
182
183 isa_dmaunmask(chan);
184 }
185
186 void
187 isa_dmaabort(chan)
188 int chan;
189 {
190
191 #ifdef ISADMA_DEBUG
192 if (chan < 0 || chan > 7)
193 panic("isa_dmaabort: impossible request");
194 #endif
195
196 isa_dmamask(chan);
197
198 dma_bounced[chan] = 0;
199 }
200
201 vm_size_t
202 isa_dmacount(chan)
203 int chan;
204 {
205 int waport;
206 vm_size_t nbytes;
207 int ochan = chan & 3;
208
209 #ifdef ISADMA_DEBUG
210 if (chan < 0 || chan > 7)
211 panic("isa_dmacount: impossible request");
212 #endif
213
214 isa_dmamask(chan);
215
216 /*
217 * We have to shift the byte count by 1. If we're in auto-initialize
218 * mode, the count may have wrapped around to the initial value. We
219 * can't use the TC bit to check for this case, so instead we compare
220 * against the original byte count.
221 * If we're not in auto-initialize mode, then the count will wrap to
222 * -1, so we also handle that case.
223 */
224 if ((chan & 4) == 0) {
225 waport = DMA1_CHN(ochan);
226 nbytes = inb(waport + 1) + 1;
227 nbytes += inb(waport + 1) << 8;
228 nbytes &= 0xffff;
229 } else {
230 waport = DMA2_CHN(ochan);
231 nbytes = inb(waport + 2) + 1;
232 nbytes += inb(waport + 2) << 8;
233 nbytes <<= 1;
234 nbytes &= 0x1ffff;
235 }
236
237 if (nbytes == dma_length[chan])
238 nbytes = 0;
239
240 isa_dmaunmask(chan);
241 return (nbytes);
242 }
243
244 int
245 isa_dmafinished(chan)
246 int chan;
247 {
248
249 #ifdef ISADMA_DEBUG
250 if (chan < 0 || chan > 7)
251 panic("isa_dmafinished: impossible request");
252 #endif
253
254 /* check that the terminal count was reached */
255 if ((chan & 4) == 0)
256 dma_finished |= inb(DMA1_SR) & 0x0f;
257 else
258 dma_finished |= (inb(DMA2_SR) & 0x0f) << 4;
259
260 return ((dma_finished & (1 << chan)) != 0);
261 }
262
263 void
264 isa_dmadone(flags, addr, nbytes, chan)
265 int flags;
266 caddr_t addr;
267 vm_size_t nbytes;
268 int chan;
269 {
270
271 #ifdef ISADMA_DEBUG
272 if (chan < 0 || chan > 7)
273 panic("isa_dmadone: impossible request");
274 #endif
275
276 isa_dmamask(chan);
277
278 if (!isa_dmafinished(chan))
279 printf("isa_dmadone: channel %d not finished\n", chan);
280
281 /* copy bounce buffer on read */
282 if (dma_bounced[chan]) {
283 bcopy(dma_bounce[chan], addr, nbytes);
284 dma_bounced[chan] = 0;
285 }
286 }
287
288 /*
289 * Check for problems with the address range of a DMA transfer
290 * (non-contiguous physical pages, outside of bus address space,
291 * crossing DMA page boundaries).
292 * Return true if special handling needed.
293 */
294 int
295 isa_dmarangecheck(va, length, chan)
296 vm_offset_t va;
297 u_long length;
298 int chan;
299 {
300 vm_offset_t phys, priorpage = 0, endva;
301 u_int dma_pgmsk = (chan & 4) ? ~(128*1024-1) : ~(64*1024-1);
302
303 endva = round_page(va + length);
304 for (; va < endva ; va += NBPG) {
305 phys = trunc_page(pmap_extract(pmap_kernel(), va));
306 if (phys == 0)
307 panic("isa_dmacheck: no physical page present");
308 if (phys >= (1<<24))
309 return 1;
310 if (priorpage) {
311 if (priorpage + NBPG != phys)
312 return 1;
313 /* check if crossing a DMA page boundary */
314 if ((priorpage ^ phys) & dma_pgmsk)
315 return 1;
316 }
317 priorpage = phys;
318 }
319 return 0;
320 }
321
322 /* head of queue waiting for physmem to become available */
323 struct buf isa_physmemq;
324
325 /* blocked waiting for resource to become free for exclusive use */
326 static isaphysmemflag;
327 /* if waited for and call requested when free (B_CALL) */
328 static void (*isaphysmemunblock) __P((void)); /* needs to be a list */
329
330 /*
331 * Allocate contiguous physical memory for transfer, returning
332 * a *virtual* address to region. May block waiting for resource.
333 * (assumed to be called at splbio())
334 */
335 caddr_t
336 isa_allocphysmem(ca, length, func)
337 caddr_t ca;
338 unsigned length;
339 void (*func) __P((void));
340 {
341
342 isaphysmemunblock = func;
343 while (isaphysmemflag & B_BUSY) {
344 isaphysmemflag |= B_WANTED;
345 sleep((caddr_t)&isaphysmemflag, PRIBIO);
346 }
347 isaphysmemflag |= B_BUSY;
348
349 return((caddr_t)isaphysmem);
350 }
351
352 /*
353 * Free contiguous physical memory used for transfer.
354 * (assumed to be called at splbio())
355 */
356 void
357 isa_freephysmem(va, length)
358 caddr_t va;
359 unsigned length;
360 {
361
362 isaphysmemflag &= ~B_BUSY;
363 if (isaphysmemflag & B_WANTED) {
364 isaphysmemflag &= B_WANTED;
365 wakeup((caddr_t)&isaphysmemflag);
366 if (isaphysmemunblock)
367 (*isaphysmemunblock)();
368 }
369 }
370