/** ** Proll (PROM replacement) ** iommu.c: Functions for DVMA management. ** Copyright 1999 Pete Zaitcev ** This code is licensed under GNU General Public License. **/ #include "config.h" #include "libopenbios/bindings.h" #include "libopenbios/ofmem.h" #include "drivers/drivers.h" #include "iommu.h" #include "arch/sparc32/ofmem_sparc32.h" #include "arch/sparc32/asi.h" #include "arch/sparc32/pgtsrmmu.h" #ifdef CONFIG_DEBUG_IOMMU #define DPRINTF(fmt, args...) \ do { printk(fmt , ##args); } while (0) #else #define DPRINTF(fmt, args...) #endif /* * IOMMU parameters */ struct iommu { struct iommu_regs *regs; unsigned int *page_table; unsigned long plow; /* Base bus address */ unsigned long pphys; /* Base phys address */ }; static struct iommu ciommu; static void iommu_invalidate(struct iommu_regs *iregs) { iregs->tlbflush = 0; } /* * XXX This is a problematic interface. We alloc _memory_ which is uncached. * So if we ever reuse allocations somebody is going to get uncached pages. * Returned address is always aligned by page. * BTW, we were not going to give away anonymous storage, were we not? */ void * dvma_alloc(int size) { void *va; unsigned int pa, iova; unsigned int npages; unsigned int mva, mpa; unsigned int i; unsigned int *iopte; struct iommu *t = &ciommu; npages = (size + (PAGE_SIZE-1)) / PAGE_SIZE; iova = (unsigned int)mem_alloc(&cdvmem, npages * PAGE_SIZE, PAGE_SIZE); if (iova == 0) return NULL; pa = t->pphys + (iova - t->plow); va = (void *)pa2va((unsigned long)pa); /* * Change page attributes in MMU to uncached. */ mva = (unsigned int) va; mpa = (unsigned int) pa; ofmem_arch_map_pages(mpa, mva, npages * PAGE_SIZE, ofmem_arch_io_translation_mode(mpa)); /* * Map into IOMMU page table. */ mpa = (unsigned int) pa; iopte = &t->page_table[(iova - t->plow) / PAGE_SIZE]; for (i = 0; i < npages; i++) { *iopte++ = MKIOPTE(mpa); mpa += PAGE_SIZE; } return va; } void dvma_sync(unsigned char *va, int size) { /* Synchronise the VA address region after DMA */ unsigned long virt = pointer2cell(va); unsigned long page; for (page = (unsigned long)virt; page < virt + size; page += PAGE_SIZE) { srmmu_flush_tlb_page(page); } } unsigned int dvma_map_in(unsigned char *va) { /* Convert from VA to IOVA */ unsigned int pa, iova; struct iommu *t = &ciommu; pa = va2pa((unsigned int)va); iova = t->plow + (pa - t->pphys); return iova; } #define DVMA_SIZE 0x4000 /* * Initialize IOMMU * This looks like initialization of CPU MMU but * the routine is higher in food chain. */ static struct iommu_regs * iommu_init(struct iommu *t, uint64_t base) { unsigned int *ptab, pva; int ptsize; #ifdef CONFIG_DEBUG_IOMMU unsigned int impl, vers; #endif unsigned int tmp; struct iommu_regs *regs; int ret; unsigned long vasize; regs = (struct iommu_regs *)ofmem_map_io(base, IOMMU_REGS); if (regs == NULL) { DPRINTF("Cannot map IOMMU\n"); for (;;) { } } t->regs = regs; #ifdef CONFIG_DEBUG_IOMMU impl = (regs->control & IOMMU_CTRL_IMPL) >> 28; vers = (regs->control & IOMMU_CTRL_VERS) >> 24; #endif tmp = regs->control; tmp &= ~(IOMMU_CTRL_RNGE); tmp |= (IOMMU_RNGE_32MB | IOMMU_CTRL_ENAB); t->plow = 0xfe000000; /* End - 32 MB */ /* Size of VA region that we manage */ vasize = 0x2000000; /* 32 MB */ regs->control = tmp; iommu_invalidate(regs); /* Allocate IOMMU page table */ /* Tremendous alignment causes great waste... */ ptsize = (vasize / PAGE_SIZE) * sizeof(int); ret = ofmem_posix_memalign((void *)&ptab, ptsize, ptsize); if (ret != 0) { DPRINTF("Cannot allocate IOMMU table [0x%x]\n", ptsize); for (;;) { } } t->page_table = ptab; /* flush_cache_all(); */ /** flush_tlb_all(); **/ tmp = (unsigned int)va2pa((unsigned long)ptab); regs->base = tmp >> 4; iommu_invalidate(regs); DPRINTF("IOMMU: impl %d vers %d page table at 0x%p (pa 0x%x) of size %d bytes\n", impl, vers, t->page_table, tmp, ptsize); mem_init(&cdvmem, (char*)t->plow, (char *)(t->plow + DVMA_SIZE)); ret = ofmem_posix_memalign((void *)&pva, DVMA_SIZE, PAGE_SIZE); if (ret != 0) { DPRINTF("Cannot allocate IOMMU phys size [0x%x]\n", DVMA_SIZE); for (;;) { } } t->pphys = va2pa(pva); return regs; } /* ( addr.lo addr.hi size -- virt ) */ static void ob_iommu_map_in(void) { phys_addr_t phys; ucell size, virt; size = POP(); phys = POP(); phys = (phys << 32) + POP(); virt = ofmem_map_io(phys, size); PUSH(virt); } /* ( virt size ) */ static void ob_iommu_map_out(void) { ucell size = POP(); ucell virt = POP(); ofmem_release_io(virt, size); } static void ob_iommu_dma_alloc(void) { call_parent_method("dma-alloc"); } static void ob_iommu_dma_free(void) { call_parent_method("dma-free"); } static void ob_iommu_dma_map_in(void) { call_parent_method("dma-map-in"); } static void ob_iommu_dma_map_out(void) { call_parent_method("dma-map-out"); } static void ob_iommu_dma_sync(void) { call_parent_method("dma-sync"); } void ob_init_iommu(uint64_t base) { struct iommu_regs *regs; regs = iommu_init(&ciommu, base); push_str("/iommu"); fword("find-device"); PUSH((unsigned long)regs); fword("encode-int"); push_str("address"); fword("property"); PUSH(base >> 32); fword("encode-int"); PUSH(base & 0xffffffff); fword("encode-int"); fword("encode+"); PUSH(IOMMU_REGS); fword("encode-int"); fword("encode+"); push_str("reg"); fword("property"); bind_func("map-in", ob_iommu_map_in); bind_func("map-out", ob_iommu_map_out); bind_func("dma-alloc", ob_iommu_dma_alloc); bind_func("dma-free", ob_iommu_dma_free); bind_func("dma-map-in", ob_iommu_dma_map_in); bind_func("dma-map-out", ob_iommu_dma_map_out); bind_func("dma-sync", ob_iommu_dma_sync); }