| /* Copyright (c) 2010, The Linux Foundation. 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 <linux/kernel.h> |
| #include <linux/vcm_mm.h> |
| |
| #include <asm/pgtable-hwdef.h> |
| #include <asm/tlbflush.h> |
| |
| #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; |
| } |