188278ca2SAdrian Bunk /* 21da177e4SLinus Torvalds * io-unit.c: IO-UNIT specific routines for memory management. 31da177e4SLinus Torvalds * 41da177e4SLinus Torvalds * Copyright (C) 1997,1998 Jakub Jelinek (jj@sunsite.mff.cuni.cz) 51da177e4SLinus Torvalds */ 61da177e4SLinus Torvalds 71da177e4SLinus Torvalds #include <linux/kernel.h> 81da177e4SLinus Torvalds #include <linux/init.h> 91da177e4SLinus Torvalds #include <linux/slab.h> 101da177e4SLinus Torvalds #include <linux/spinlock.h> 111da177e4SLinus Torvalds #include <linux/mm.h> 121da177e4SLinus Torvalds #include <linux/highmem.h> /* pte_offset_map => kmap_atomic */ 131da177e4SLinus Torvalds #include <linux/bitops.h> 140912a5dbSJens Axboe #include <linux/scatterlist.h> 159dc69230SDavid S. Miller #include <linux/of.h> 169dc69230SDavid S. Miller #include <linux/of_device.h> 171da177e4SLinus Torvalds 181da177e4SLinus Torvalds #include <asm/pgalloc.h> 191da177e4SLinus Torvalds #include <asm/pgtable.h> 201da177e4SLinus Torvalds #include <asm/io.h> 211da177e4SLinus Torvalds #include <asm/io-unit.h> 221da177e4SLinus Torvalds #include <asm/mxcc.h> 231da177e4SLinus Torvalds #include <asm/cacheflush.h> 241da177e4SLinus Torvalds #include <asm/tlbflush.h> 251da177e4SLinus Torvalds #include <asm/dma.h> 26d4accd60SDavid S. Miller #include <asm/oplib.h> 271da177e4SLinus Torvalds 281da177e4SLinus Torvalds /* #define IOUNIT_DEBUG */ 291da177e4SLinus Torvalds #ifdef IOUNIT_DEBUG 301da177e4SLinus Torvalds #define IOD(x) printk(x) 311da177e4SLinus Torvalds #else 321da177e4SLinus Torvalds #define IOD(x) do { } while (0) 331da177e4SLinus Torvalds #endif 341da177e4SLinus Torvalds 351da177e4SLinus Torvalds #define IOPERM (IOUPTE_CACHE | IOUPTE_WRITE | IOUPTE_VALID) 361da177e4SLinus Torvalds #define MKIOPTE(phys) __iopte((((phys)>>4) & IOUPTE_PAGE) | IOPERM) 371da177e4SLinus Torvalds 38cd4cd730SGrant Likely static void __init iounit_iommu_init(struct platform_device *op) 391da177e4SLinus Torvalds { 401da177e4SLinus Torvalds struct iounit_struct *iounit; 41e0039348SDavid S. Miller iopte_t *xpt, *xptend; 421da177e4SLinus Torvalds 43c80892d1SYan Burman iounit = kzalloc(sizeof(struct iounit_struct), GFP_ATOMIC); 44d4accd60SDavid S. Miller if (!iounit) { 45d4accd60SDavid S. Miller prom_printf("SUN4D: Cannot alloc iounit, halting.\n"); 46d4accd60SDavid S. Miller prom_halt(); 47d4accd60SDavid S. Miller } 481da177e4SLinus Torvalds 491da177e4SLinus Torvalds iounit->limit[0] = IOUNIT_BMAP1_START; 501da177e4SLinus Torvalds iounit->limit[1] = IOUNIT_BMAP2_START; 511da177e4SLinus Torvalds iounit->limit[2] = IOUNIT_BMAPM_START; 521da177e4SLinus Torvalds iounit->limit[3] = IOUNIT_BMAPM_END; 531da177e4SLinus Torvalds iounit->rotor[1] = IOUNIT_BMAP2_START; 541da177e4SLinus Torvalds iounit->rotor[2] = IOUNIT_BMAPM_START; 551da177e4SLinus Torvalds 56e0039348SDavid S. Miller xpt = of_ioremap(&op->resource[2], 0, PAGE_SIZE * 16, "XPT"); 57e0039348SDavid S. Miller if (!xpt) { 58e0039348SDavid S. Miller prom_printf("SUN4D: Cannot map External Page Table."); 59e0039348SDavid S. Miller prom_halt(); 601da177e4SLinus Torvalds } 611da177e4SLinus Torvalds 62e0039348SDavid S. Miller op->dev.archdata.iommu = iounit; 631da177e4SLinus Torvalds iounit->page_table = xpt; 642f72ba43SRaymond Burns spin_lock_init(&iounit->lock); 651da177e4SLinus Torvalds 661da177e4SLinus Torvalds for (xptend = iounit->page_table + (16 * PAGE_SIZE) / sizeof(iopte_t); 671da177e4SLinus Torvalds xpt < xptend;) 681da177e4SLinus Torvalds iopte_val(*xpt++) = 0; 691da177e4SLinus Torvalds } 701da177e4SLinus Torvalds 71046e26a8SDavid S. Miller static int __init iounit_init(void) 72046e26a8SDavid S. Miller { 73046e26a8SDavid S. Miller extern void sun4d_init_sbi_irq(void); 74046e26a8SDavid S. Miller struct device_node *dp; 75046e26a8SDavid S. Miller 76046e26a8SDavid S. Miller for_each_node_by_name(dp, "sbi") { 77cd4cd730SGrant Likely struct platform_device *op = of_find_device_by_node(dp); 78046e26a8SDavid S. Miller 79046e26a8SDavid S. Miller iounit_iommu_init(op); 80046e26a8SDavid S. Miller of_propagate_archdata(op); 81046e26a8SDavid S. Miller } 82046e26a8SDavid S. Miller 83046e26a8SDavid S. Miller sun4d_init_sbi_irq(); 84046e26a8SDavid S. Miller 85046e26a8SDavid S. Miller return 0; 86046e26a8SDavid S. Miller } 87046e26a8SDavid S. Miller 88046e26a8SDavid S. Miller subsys_initcall(iounit_init); 89046e26a8SDavid S. Miller 901da177e4SLinus Torvalds /* One has to hold iounit->lock to call this */ 911da177e4SLinus Torvalds static unsigned long iounit_get_area(struct iounit_struct *iounit, unsigned long vaddr, int size) 921da177e4SLinus Torvalds { 931da177e4SLinus Torvalds int i, j, k, npages; 941da177e4SLinus Torvalds unsigned long rotor, scan, limit; 951da177e4SLinus Torvalds iopte_t iopte; 961da177e4SLinus Torvalds 971da177e4SLinus Torvalds npages = ((vaddr & ~PAGE_MASK) + size + (PAGE_SIZE-1)) >> PAGE_SHIFT; 981da177e4SLinus Torvalds 991da177e4SLinus Torvalds /* A tiny bit of magic ingredience :) */ 1001da177e4SLinus Torvalds switch (npages) { 1011da177e4SLinus Torvalds case 1: i = 0x0231; break; 1021da177e4SLinus Torvalds case 2: i = 0x0132; break; 1031da177e4SLinus Torvalds default: i = 0x0213; break; 1041da177e4SLinus Torvalds } 1051da177e4SLinus Torvalds 1061da177e4SLinus Torvalds IOD(("iounit_get_area(%08lx,%d[%d])=", vaddr, size, npages)); 1071da177e4SLinus Torvalds 1081da177e4SLinus Torvalds next: j = (i & 15); 1091da177e4SLinus Torvalds rotor = iounit->rotor[j - 1]; 1101da177e4SLinus Torvalds limit = iounit->limit[j]; 1111da177e4SLinus Torvalds scan = rotor; 1121da177e4SLinus Torvalds nexti: scan = find_next_zero_bit(iounit->bmap, limit, scan); 1131da177e4SLinus Torvalds if (scan + npages > limit) { 1141da177e4SLinus Torvalds if (limit != rotor) { 1151da177e4SLinus Torvalds limit = rotor; 1161da177e4SLinus Torvalds scan = iounit->limit[j - 1]; 1171da177e4SLinus Torvalds goto nexti; 1181da177e4SLinus Torvalds } 1191da177e4SLinus Torvalds i >>= 4; 1201da177e4SLinus Torvalds if (!(i & 15)) 1211da177e4SLinus Torvalds panic("iounit_get_area: Couldn't find free iopte slots for (%08lx,%d)\n", vaddr, size); 1221da177e4SLinus Torvalds goto next; 1231da177e4SLinus Torvalds } 1241da177e4SLinus Torvalds for (k = 1, scan++; k < npages; k++) 1251da177e4SLinus Torvalds if (test_bit(scan++, iounit->bmap)) 1261da177e4SLinus Torvalds goto nexti; 1271da177e4SLinus Torvalds iounit->rotor[j - 1] = (scan < limit) ? scan : iounit->limit[j - 1]; 1281da177e4SLinus Torvalds scan -= npages; 1291da177e4SLinus Torvalds iopte = MKIOPTE(__pa(vaddr & PAGE_MASK)); 1301da177e4SLinus Torvalds vaddr = IOUNIT_DMA_BASE + (scan << PAGE_SHIFT) + (vaddr & ~PAGE_MASK); 1311da177e4SLinus Torvalds for (k = 0; k < npages; k++, iopte = __iopte(iopte_val(iopte) + 0x100), scan++) { 1321da177e4SLinus Torvalds set_bit(scan, iounit->bmap); 1331da177e4SLinus Torvalds iounit->page_table[scan] = iopte; 1341da177e4SLinus Torvalds } 1351da177e4SLinus Torvalds IOD(("%08lx\n", vaddr)); 1361da177e4SLinus Torvalds return vaddr; 1371da177e4SLinus Torvalds } 1381da177e4SLinus Torvalds 139260489faSDavid S. Miller static __u32 iounit_get_scsi_one(struct device *dev, char *vaddr, unsigned long len) 1401da177e4SLinus Torvalds { 141260489faSDavid S. Miller struct iounit_struct *iounit = dev->archdata.iommu; 1421da177e4SLinus Torvalds unsigned long ret, flags; 1431da177e4SLinus Torvalds 1441da177e4SLinus Torvalds spin_lock_irqsave(&iounit->lock, flags); 1451da177e4SLinus Torvalds ret = iounit_get_area(iounit, (unsigned long)vaddr, len); 1461da177e4SLinus Torvalds spin_unlock_irqrestore(&iounit->lock, flags); 1471da177e4SLinus Torvalds return ret; 1481da177e4SLinus Torvalds } 1491da177e4SLinus Torvalds 150260489faSDavid S. Miller static void iounit_get_scsi_sgl(struct device *dev, struct scatterlist *sg, int sz) 1511da177e4SLinus Torvalds { 152260489faSDavid S. Miller struct iounit_struct *iounit = dev->archdata.iommu; 1531da177e4SLinus Torvalds unsigned long flags; 1541da177e4SLinus Torvalds 1551da177e4SLinus Torvalds /* FIXME: Cache some resolved pages - often several sg entries are to the same page */ 1561da177e4SLinus Torvalds spin_lock_irqsave(&iounit->lock, flags); 1571da177e4SLinus Torvalds while (sz != 0) { 1581da177e4SLinus Torvalds --sz; 159aa83a26aSRobert Reif sg->dma_address = iounit_get_area(iounit, (unsigned long) sg_virt(sg), sg->length); 160aa83a26aSRobert Reif sg->dma_length = sg->length; 1610912a5dbSJens Axboe sg = sg_next(sg); 1621da177e4SLinus Torvalds } 1631da177e4SLinus Torvalds spin_unlock_irqrestore(&iounit->lock, flags); 1641da177e4SLinus Torvalds } 1651da177e4SLinus Torvalds 166260489faSDavid S. Miller static void iounit_release_scsi_one(struct device *dev, __u32 vaddr, unsigned long len) 1671da177e4SLinus Torvalds { 168260489faSDavid S. Miller struct iounit_struct *iounit = dev->archdata.iommu; 1691da177e4SLinus Torvalds unsigned long flags; 1701da177e4SLinus Torvalds 1711da177e4SLinus Torvalds spin_lock_irqsave(&iounit->lock, flags); 1721da177e4SLinus Torvalds len = ((vaddr & ~PAGE_MASK) + len + (PAGE_SIZE-1)) >> PAGE_SHIFT; 1731da177e4SLinus Torvalds vaddr = (vaddr - IOUNIT_DMA_BASE) >> PAGE_SHIFT; 1741da177e4SLinus Torvalds IOD(("iounit_release %08lx-%08lx\n", (long)vaddr, (long)len+vaddr)); 1751da177e4SLinus Torvalds for (len += vaddr; vaddr < len; vaddr++) 1761da177e4SLinus Torvalds clear_bit(vaddr, iounit->bmap); 1771da177e4SLinus Torvalds spin_unlock_irqrestore(&iounit->lock, flags); 1781da177e4SLinus Torvalds } 1791da177e4SLinus Torvalds 180260489faSDavid S. Miller static void iounit_release_scsi_sgl(struct device *dev, struct scatterlist *sg, int sz) 1811da177e4SLinus Torvalds { 182260489faSDavid S. Miller struct iounit_struct *iounit = dev->archdata.iommu; 1831da177e4SLinus Torvalds unsigned long flags; 1841da177e4SLinus Torvalds unsigned long vaddr, len; 1851da177e4SLinus Torvalds 1861da177e4SLinus Torvalds spin_lock_irqsave(&iounit->lock, flags); 1871da177e4SLinus Torvalds while (sz != 0) { 1881da177e4SLinus Torvalds --sz; 189aa83a26aSRobert Reif len = ((sg->dma_address & ~PAGE_MASK) + sg->length + (PAGE_SIZE-1)) >> PAGE_SHIFT; 190aa83a26aSRobert Reif vaddr = (sg->dma_address - IOUNIT_DMA_BASE) >> PAGE_SHIFT; 1911da177e4SLinus Torvalds IOD(("iounit_release %08lx-%08lx\n", (long)vaddr, (long)len+vaddr)); 1921da177e4SLinus Torvalds for (len += vaddr; vaddr < len; vaddr++) 1931da177e4SLinus Torvalds clear_bit(vaddr, iounit->bmap); 1940912a5dbSJens Axboe sg = sg_next(sg); 1951da177e4SLinus Torvalds } 1961da177e4SLinus Torvalds spin_unlock_irqrestore(&iounit->lock, flags); 1971da177e4SLinus Torvalds } 1981da177e4SLinus Torvalds 1991da177e4SLinus Torvalds #ifdef CONFIG_SBUS 200d894d964SDavid S. Miller static int iounit_map_dma_area(struct device *dev, dma_addr_t *pba, unsigned long va, unsigned long addr, int len) 2011da177e4SLinus Torvalds { 2024b1c5df2SDavid S. Miller struct iounit_struct *iounit = dev->archdata.iommu; 2031da177e4SLinus Torvalds unsigned long page, end; 2041da177e4SLinus Torvalds pgprot_t dvma_prot; 2051da177e4SLinus Torvalds iopte_t *iopte; 2061da177e4SLinus Torvalds 2071da177e4SLinus Torvalds *pba = addr; 2081da177e4SLinus Torvalds 2091da177e4SLinus Torvalds dvma_prot = __pgprot(SRMMU_CACHE | SRMMU_ET_PTE | SRMMU_PRIV); 2101da177e4SLinus Torvalds end = PAGE_ALIGN((addr + len)); 2111da177e4SLinus Torvalds while(addr < end) { 2121da177e4SLinus Torvalds page = va; 2131da177e4SLinus Torvalds { 2141da177e4SLinus Torvalds pgd_t *pgdp; 2151da177e4SLinus Torvalds pmd_t *pmdp; 2161da177e4SLinus Torvalds pte_t *ptep; 2171da177e4SLinus Torvalds long i; 2181da177e4SLinus Torvalds 2191da177e4SLinus Torvalds pgdp = pgd_offset(&init_mm, addr); 2201da177e4SLinus Torvalds pmdp = pmd_offset(pgdp, addr); 2211da177e4SLinus Torvalds ptep = pte_offset_map(pmdp, addr); 2221da177e4SLinus Torvalds 2231da177e4SLinus Torvalds set_pte(ptep, mk_pte(virt_to_page(page), dvma_prot)); 2241da177e4SLinus Torvalds 2251da177e4SLinus Torvalds i = ((addr - IOUNIT_DMA_BASE) >> PAGE_SHIFT); 2261da177e4SLinus Torvalds 2271da177e4SLinus Torvalds iopte = (iopte_t *)(iounit->page_table + i); 2281da177e4SLinus Torvalds *iopte = MKIOPTE(__pa(page)); 2291da177e4SLinus Torvalds } 2301da177e4SLinus Torvalds addr += PAGE_SIZE; 2311da177e4SLinus Torvalds va += PAGE_SIZE; 2321da177e4SLinus Torvalds } 2331da177e4SLinus Torvalds flush_cache_all(); 2341da177e4SLinus Torvalds flush_tlb_all(); 2351da177e4SLinus Torvalds 2361da177e4SLinus Torvalds return 0; 2371da177e4SLinus Torvalds } 2381da177e4SLinus Torvalds 2394b1c5df2SDavid S. Miller static void iounit_unmap_dma_area(struct device *dev, unsigned long addr, int len) 2401da177e4SLinus Torvalds { 2411da177e4SLinus Torvalds /* XXX Somebody please fill this in */ 2421da177e4SLinus Torvalds } 2431da177e4SLinus Torvalds #endif 2441da177e4SLinus Torvalds 245d894d964SDavid S. Miller static const struct sparc32_dma_ops iounit_dma_ops = { 246d894d964SDavid S. Miller .get_scsi_one = iounit_get_scsi_one, 247d894d964SDavid S. Miller .get_scsi_sgl = iounit_get_scsi_sgl, 248d894d964SDavid S. Miller .release_scsi_one = iounit_release_scsi_one, 249d894d964SDavid S. Miller .release_scsi_sgl = iounit_release_scsi_sgl, 250d894d964SDavid S. Miller #ifdef CONFIG_SBUS 251d894d964SDavid S. Miller .map_dma_area = iounit_map_dma_area, 252d894d964SDavid S. Miller .unmap_dma_area = iounit_unmap_dma_area, 253d894d964SDavid S. Miller #endif 254d894d964SDavid S. Miller }; 255d894d964SDavid S. Miller 2561da177e4SLinus Torvalds void __init ld_mmu_iounit(void) 2571da177e4SLinus Torvalds { 258d894d964SDavid S. Miller sparc32_dma_ops = &iounit_dma_ops; 2591da177e4SLinus Torvalds } 260