/* Copyright (c) 2010, Code Aurora Forum. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ /* Architecture-specific VCM functions */ #include #include #include #include #define MRC(reg, processor, op1, crn, crm, op2) \ __asm__ __volatile__ ( \ " mrc " #processor "," #op1 ", %0," #crn "," #crm "," #op2 " \n" \ : "=r" (reg)) #define RCP15_PRRR(reg) MRC(reg, p15, 0, c10, c2, 0) #define RCP15_NMRR(reg) MRC(reg, p15, 0, c10, c2, 1) /* Local type attributes (not the same as VCM) */ #define ARM_MT_NORMAL 2 #define ARM_MT_STRONGLYORDERED 0 #define ARM_MT_DEVICE 1 #define ARM_CP_NONCACHED 0 #define ARM_CP_WB_WA 1 #define ARM_CP_WB_NWA 3 #define ARM_CP_WT_NWA 2 #define smmu_err(a, ...) \ pr_err("ERROR %s %i " a, __func__, __LINE__, ##__VA_ARGS__) #define FL_OFFSET(va) (((va) & 0xFFF00000) >> 20) #define SL_OFFSET(va) (((va) & 0xFF000) >> 12) int vcm_driver_tex_class[4]; static int find_tex_class(int icp, int ocp, int mt, int nos) { int i = 0; unsigned int prrr = 0; unsigned int nmrr = 0; int c_icp, c_ocp, c_mt, c_nos; RCP15_PRRR(prrr); RCP15_NMRR(nmrr); /* There are only 8 classes on this architecture */ /* If they add more classes, registers will VASTLY change */ for (i = 0; i < 8; i++) { c_nos = prrr & (1 << (i + 24)) ? 1 : 0; c_mt = (prrr & (3 << (i * 2))) >> (i * 2); c_icp = (nmrr & (3 << (i * 2))) >> (i * 2); c_ocp = (nmrr & (3 << (i * 2 + 16))) >> (i * 2 + 16); if (icp == c_icp && ocp == c_ocp && c_mt == mt && c_nos == nos) return i; } smmu_err("Could not find TEX class for ICP=%d, OCP=%d, MT=%d, NOS=%d\n", icp, ocp, mt, nos); /* In reality, we may want to remove this panic. Some classes just */ /* will not be available, and will fail in smmu_set_attr */ panic("SMMU: Could not determine TEX attribute mapping.\n"); return -1; } int vcm_setup_tex_classes(void) { unsigned int cpu_prrr; unsigned int cpu_nmrr; if (!(get_cr() & CR_TRE)) /* No TRE? */ panic("TEX remap not enabled, but the SMMU driver needs it!\n"); RCP15_PRRR(cpu_prrr); RCP15_NMRR(cpu_nmrr); vcm_driver_tex_class[VCM_DEV_ATTR_NONCACHED] = find_tex_class(ARM_CP_NONCACHED, ARM_CP_NONCACHED, ARM_MT_NORMAL, 1); vcm_driver_tex_class[VCM_DEV_ATTR_CACHED_WB_WA] = find_tex_class(ARM_CP_WB_WA, ARM_CP_WB_WA, ARM_MT_NORMAL, 1); vcm_driver_tex_class[VCM_DEV_ATTR_CACHED_WB_NWA] = find_tex_class(ARM_CP_WB_NWA, ARM_CP_WB_NWA, ARM_MT_NORMAL, 1); vcm_driver_tex_class[VCM_DEV_ATTR_CACHED_WT] = find_tex_class(ARM_CP_WT_NWA, ARM_CP_WT_NWA, ARM_MT_NORMAL, 1); #ifdef DEBUG_TEX printk(KERN_INFO "VCM driver debug: Using TEX classes: %d %d %d %d\n", vcm_driver_tex_class[VCM_DEV_ATTR_NONCACHED], vcm_driver_tex_class[VCM_DEV_ATTR_CACHED_WB_WA], vcm_driver_tex_class[VCM_DEV_ATTR_CACHED_WB_NWA], vcm_driver_tex_class[VCM_DEV_ATTR_CACHED_WT]); #endif return 0; } int set_arm7_pte_attr(unsigned long pt_base, unsigned long va, unsigned long len, unsigned int attr) { unsigned long *fl_table = NULL; unsigned long *fl_pte = NULL; unsigned long fl_offset = 0; unsigned long *sl_table = NULL; unsigned long *sl_pte = NULL; unsigned long sl_offset = 0; int i; int sh = 0; int class = 0; /* Alignment */ if (va & (len-1)) { smmu_err("misaligned va: %p\n", (void *) va); goto fail; } if (attr > 7) { smmu_err("bad attribute: %d\n", attr); goto fail; } sh = (attr & VCM_DEV_ATTR_SH) ? 1 : 0; class = vcm_driver_tex_class[attr & 0x03]; if (class > 7 || class < 0) { /* Bad class */ smmu_err("bad tex class: %d\n", class); goto fail; } if (len != SZ_16M && len != SZ_1M && len != SZ_64K && len != SZ_4K) { smmu_err("bad size: %lu\n", len); goto fail; } fl_table = (unsigned long *) pt_base; if (!fl_table) { smmu_err("null page table\n"); goto fail; } fl_offset = FL_OFFSET(va); /* Upper 12 bits */ fl_pte = fl_table + fl_offset; /* int pointers, 4 bytes */ if (*fl_pte == 0) { /* Nothing there! */ smmu_err("first level pte is 0\n"); goto fail; } /* Supersection attributes */ if (len == SZ_16M) { for (i = 0; i < 16; i++) { /* Clear the old bits */ *(fl_pte+i) &= ~(PMD_SECT_S | PMD_SECT_CACHEABLE | PMD_SECT_BUFFERABLE | PMD_SECT_TEX(1)); /* Assign new class and S bit */ *(fl_pte+i) |= sh ? PMD_SECT_S : 0; *(fl_pte+i) |= class & 0x01 ? PMD_SECT_BUFFERABLE : 0; *(fl_pte+i) |= class & 0x02 ? PMD_SECT_CACHEABLE : 0; *(fl_pte+i) |= class & 0x04 ? PMD_SECT_TEX(1) : 0; } } else if (len == SZ_1M) { /* Clear the old bits */ *(fl_pte) &= ~(PMD_SECT_S | PMD_SECT_CACHEABLE | PMD_SECT_BUFFERABLE | PMD_SECT_TEX(1)); /* Assign new class and S bit */ *(fl_pte) |= sh ? PMD_SECT_S : 0; *(fl_pte) |= class & 0x01 ? PMD_SECT_BUFFERABLE : 0; *(fl_pte) |= class & 0x02 ? PMD_SECT_CACHEABLE : 0; *(fl_pte) |= class & 0x04 ? PMD_SECT_TEX(1) : 0; } sl_table = (unsigned long *) __va(((*fl_pte) & 0xFFFFFC00)); sl_offset = SL_OFFSET(va); sl_pte = sl_table + sl_offset; if (len == SZ_64K) { for (i = 0; i < 16; i++) { /* Clear the old bits */ *(sl_pte+i) &= ~(PTE_EXT_SHARED | PTE_CACHEABLE | PTE_BUFFERABLE | PTE_EXT_TEX(1)); /* Assign new class and S bit */ *(sl_pte+i) |= sh ? PTE_EXT_SHARED : 0; *(sl_pte+i) |= class & 0x01 ? PTE_BUFFERABLE : 0; *(sl_pte+i) |= class & 0x02 ? PTE_CACHEABLE : 0; *(sl_pte+i) |= class & 0x04 ? PTE_EXT_TEX(1) : 0; } } else if (len == SZ_4K) { /* Clear the old bits */ *(sl_pte) &= ~(PTE_EXT_SHARED | PTE_CACHEABLE | PTE_BUFFERABLE | PTE_EXT_TEX(1)); /* Assign new class and S bit */ *(sl_pte) |= sh ? PTE_EXT_SHARED : 0; *(sl_pte) |= class & 0x01 ? PTE_BUFFERABLE : 0; *(sl_pte) |= class & 0x02 ? PTE_CACHEABLE : 0; *(sl_pte) |= class & 0x04 ? PTE_EXT_TEX(1) : 0; } mb(); return 0; fail: return 1; } int cpu_set_attr(unsigned long va, unsigned long len, unsigned int attr) { int ret; pgd_t *pgd = init_mm.pgd; if (!pgd) { smmu_err("null pgd\n"); goto fail; } ret = set_arm7_pte_attr((unsigned long)pgd, va, len, attr); if (ret != 0) { smmu_err("could not set attribute: \ pgd=%p, va=%p, len=%lu, attr=%d\n", (void *) pgd, (void *) va, len, attr); goto fail; } dmb(); flush_tlb_all(); return 0; fail: return -1; }