/* $NetBSD: dma.c,v 1.10 1994/11/18 22:07:37 mycroft Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include /* region of physical memory known to be contiguous */ vm_offset_t isaphysmem; static caddr_t dma_bounce[8]; /* XXX */ static char bounced[8]; /* XXX */ #define MAXDMASZ 512 /* XXX */ /* high byte of address is stored in this port for i-th dma channel */ static int dmapageport[8] = { 0x87, 0x83, 0x81, 0x82, 0x8f, 0x8b, 0x89, 0x8a }; /* * isa_dmacascade(): program 8237 DMA controller channel to accept * external dma control by a board. */ void isa_dmacascade(chan) int chan; { #ifdef DIAGNOSTIC if (chan < 0 || chan > 7) panic("isa_dmacascade: impossible request"); #endif /* set dma channel mode, and set dma channel mode */ if ((chan & 4) == 0) { outb(DMA1_MODE, DMA37MD_CASCADE | chan); outb(DMA1_SMSK, chan); } else { outb(DMA2_MODE, DMA37MD_CASCADE | (chan & 3)); outb(DMA2_SMSK, chan & 3); } } /* * isa_dmastart(): program 8237 DMA controller channel, avoid page alignment * problems by using a bounce buffer. */ void isa_dmastart(flags, addr, nbytes, chan) int flags; caddr_t addr; vm_size_t nbytes; int chan; { vm_offset_t phys; int waport; caddr_t newaddr; #ifdef DIAGNOSTIC if (chan < 0 || chan > 7 || ((chan & 4) ? (nbytes >= (1<<17) || nbytes & 1 || (u_int)addr & 1) : (nbytes >= (1<<16)))) panic("isa_dmastart: impossible request"); #endif if (isa_dmarangecheck(addr, nbytes, chan)) { if (dma_bounce[chan] == 0) dma_bounce[chan] = /*(caddr_t)malloc(MAXDMASZ, M_TEMP, M_WAITOK);*/ (caddr_t) isaphysmem + NBPG*chan; bounced[chan] = 1; newaddr = dma_bounce[chan]; *(int *) newaddr = 0; /* XXX */ /* copy bounce buffer on write */ if ((flags & B_READ) == 0) bcopy(addr, newaddr, nbytes); addr = newaddr; } /* translate to physical */ phys = pmap_extract(kernel_pmap, (vm_offset_t)addr); if ((chan & 4) == 0) { /* * Program one of DMA channels 0..3. These are * byte mode channels. */ /* set dma channel mode, and reset address ff */ if (flags & B_READ) outb(DMA1_MODE, chan | DMA37MD_SINGLE | DMA37MD_WRITE); else outb(DMA1_MODE, chan | DMA37MD_SINGLE | DMA37MD_READ); outb(DMA1_FFC, 0); /* send start address */ waport = DMA1_CHN(chan); outb(waport, phys); outb(waport, phys>>8); outb(dmapageport[chan], phys>>16); /* send count */ outb(waport + 1, --nbytes); outb(waport + 1, nbytes>>8); /* unmask channel */ outb(DMA1_SMSK, chan | DMA37SM_CLEAR); } else { /* * Program one of DMA channels 4..7. These are * word mode channels. */ /* set dma channel mode, and reset address ff */ if (flags & B_READ) outb(DMA2_MODE, (chan & 3) | DMA37MD_SINGLE | DMA37MD_WRITE); else outb(DMA2_MODE, (chan & 3) | DMA37MD_SINGLE | DMA37MD_READ); outb(DMA2_FFC, 0); /* send start address */ waport = DMA2_CHN(chan & 3); outb(waport, phys>>1); outb(waport, phys>>9); outb(dmapageport[chan], phys>>16); /* send count */ nbytes >>= 1; outb(waport + 2, --nbytes); outb(waport + 2, nbytes>>8); /* unmask channel */ outb(DMA2_SMSK, (chan & 3) | DMA37SM_CLEAR); } } void isa_dmaabort(chan) int chan; { #ifdef DIAGNOSTIC if (chan < 0 || chan > 7) panic("isa_dmaabort: impossible request"); #endif bounced[chan] = 0; /* mask channel */ if ((chan & 4) == 0) outb(DMA1_SMSK, DMA37SM_SET | chan); else outb(DMA2_SMSK, DMA37SM_SET | (chan & 3)); } void isa_dmadone(flags, addr, nbytes, chan) int flags; caddr_t addr; vm_size_t nbytes; int chan; { u_char tc; #ifdef DIAGNOSTIC if (chan < 0 || chan > 7) panic("isa_dmadone: impossible request"); #endif /* check that the terminal count was reached */ if ((chan & 4) == 0) tc = inb(DMA1_SR) & (1 << chan); else tc = inb(DMA2_SR) & (1 << (chan & 3)); if (tc == 0) /* XXX probably should panic or something */ log(LOG_ERR, "dma channel %d not finished\n", chan); /* mask channel */ if ((chan & 4) == 0) outb(DMA1_SMSK, DMA37SM_SET | chan); else outb(DMA2_SMSK, DMA37SM_SET | (chan & 3)); /* copy bounce buffer on read */ if (bounced[chan]) { bcopy(dma_bounce[chan], addr, nbytes); bounced[chan] = 0; } } /* * Check for problems with the address range of a DMA transfer * (non-contiguous physical pages, outside of bus address space, * crossing DMA page boundaries). * Return true if special handling needed. */ int isa_dmarangecheck(va, length, chan) vm_offset_t va; u_long length; int chan; { vm_offset_t phys, priorpage = 0, endva; u_int dma_pgmsk = (chan & 4) ? ~(128*1024-1) : ~(64*1024-1); endva = round_page(va + length); for (; va < endva ; va += NBPG) { phys = trunc_page(pmap_extract(kernel_pmap, va)); if (phys == 0) panic("isa_dmacheck: no physical page present"); if (phys >= (1<<24)) return 1; if (priorpage) { if (priorpage + NBPG != phys) return 1; /* check if crossing a DMA page boundary */ if ((priorpage ^ phys) & dma_pgmsk) return 1; } priorpage = phys; } return 0; } /* head of queue waiting for physmem to become available */ struct buf isa_physmemq; /* blocked waiting for resource to become free for exclusive use */ static isaphysmemflag; /* if waited for and call requested when free (B_CALL) */ static void (*isaphysmemunblock)(); /* needs to be a list */ /* * Allocate contiguous physical memory for transfer, returning * a *virtual* address to region. May block waiting for resource. * (assumed to be called at splbio()) */ caddr_t isa_allocphysmem(caddr_t va, unsigned length, void (*func)()) { isaphysmemunblock = func; while (isaphysmemflag & B_BUSY) { isaphysmemflag |= B_WANTED; sleep((caddr_t)&isaphysmemflag, PRIBIO); } isaphysmemflag |= B_BUSY; return((caddr_t)isaphysmem); } /* * Free contiguous physical memory used for transfer. * (assumed to be called at splbio()) */ void isa_freephysmem(caddr_t va, unsigned length) { isaphysmemflag &= ~B_BUSY; if (isaphysmemflag & B_WANTED) { isaphysmemflag &= B_WANTED; wakeup((caddr_t)&isaphysmemflag); if (isaphysmemunblock) (*isaphysmemunblock)(); } }