summaryrefslogtreecommitdiffstats
path: root/grub-core/kern/arm/cache.c
diff options
context:
space:
mode:
Diffstat (limited to 'grub-core/kern/arm/cache.c')
-rw-r--r--grub-core/kern/arm/cache.c311
1 files changed, 311 insertions, 0 deletions
diff --git a/grub-core/kern/arm/cache.c b/grub-core/kern/arm/cache.c
new file mode 100644
index 0000000..6c75193
--- /dev/null
+++ b/grub-core/kern/arm/cache.c
@@ -0,0 +1,311 @@
+#include <grub/dl.h>
+#include <grub/cache.h>
+#include <grub/arm/system.h>
+#ifdef GRUB_MACHINE_UBOOT
+#include <grub/uboot/uboot.h>
+#include <grub/uboot/api_public.h>
+#include <grub/mm.h>
+#endif
+
+/* This is only about cache architecture. It doesn't imply
+ the CPU architecture. */
+static enum
+ {
+ ARCH_UNKNOWN,
+ ARCH_ARMV5_WRITE_THROUGH,
+ ARCH_ARMV6,
+ ARCH_ARMV6_UNIFIED,
+ ARCH_ARMV7
+ } type = ARCH_UNKNOWN;
+
+static int is_v6_mmu;
+
+static grub_uint32_t grub_arch_cache_dlinesz;
+static grub_uint32_t grub_arch_cache_ilinesz;
+static grub_uint32_t grub_arch_cache_max_linesz;
+
+/* Prototypes for asm functions. */
+void grub_arm_clean_dcache_range_armv6 (grub_addr_t start, grub_addr_t end,
+ grub_addr_t dlinesz);
+void grub_arm_clean_dcache_range_armv7 (grub_addr_t start, grub_addr_t end,
+ grub_addr_t dlinesz);
+void grub_arm_clean_dcache_range_poc_armv7 (grub_addr_t start, grub_addr_t end,
+ grub_addr_t dlinesz);
+void grub_arm_invalidate_icache_range_armv6 (grub_addr_t start, grub_addr_t end,
+ grub_addr_t dlinesz);
+void grub_arm_invalidate_icache_range_armv7 (grub_addr_t start, grub_addr_t end,
+ grub_addr_t dlinesz);
+void grub_arm_disable_caches_mmu_armv6 (void);
+void grub_arm_disable_caches_mmu_armv7 (void);
+grub_uint32_t grub_arm_main_id (void);
+grub_uint32_t grub_arm_cache_type (void);
+
+static void
+probe_caches (void)
+{
+ grub_uint32_t main_id, cache_type;
+
+ /* Read main ID Register */
+ main_id = grub_arm_main_id ();
+
+ switch ((main_id >> 16) & 0xf)
+ {
+ case 0x3:
+ case 0x4:
+ case 0x5:
+ case 0x6:
+ is_v6_mmu = 0;
+ break;
+ case 0x7:
+ case 0xf:
+ is_v6_mmu = 1;
+ break;
+ default:
+ grub_fatal ("Unsupported ARM ID 0x%x", main_id);
+ }
+
+ /* Read Cache Type Register */
+ cache_type = grub_arm_cache_type ();
+
+ switch (cache_type >> 24)
+ {
+ case 0x00:
+ case 0x01:
+ grub_arch_cache_dlinesz = 8 << ((cache_type >> 12) & 3);
+ grub_arch_cache_ilinesz = 8 << (cache_type & 3);
+ type = ARCH_ARMV5_WRITE_THROUGH;
+ break;
+ case 0x04:
+ case 0x0a:
+ case 0x0c:
+ case 0x0e:
+ case 0x1c:
+ grub_arch_cache_dlinesz = 8 << ((cache_type >> 12) & 3);
+ grub_arch_cache_ilinesz = 8 << (cache_type & 3);
+ type = ARCH_ARMV6_UNIFIED;
+ break;
+ case 0x05:
+ case 0x0b:
+ case 0x0d:
+ case 0x0f:
+ case 0x1d:
+ grub_arch_cache_dlinesz = 8 << ((cache_type >> 12) & 3);
+ grub_arch_cache_ilinesz = 8 << (cache_type & 3);
+ type = ARCH_ARMV6;
+ break;
+ default:
+ /*
+ * The CTR register is pretty much unchanged from v7 onwards,
+ * and is guaranteed to be backward compatible (the IDC/DIC bits
+ * allow certain CMOs to be elided, but performing them is never
+ * wrong), hence handling it like its AArch64 equivalent.
+ */
+ grub_arch_cache_dlinesz = 4 << ((cache_type >> 16) & 0xf);
+ grub_arch_cache_ilinesz = 4 << (cache_type & 0xf);
+ type = ARCH_ARMV7;
+ }
+ if (grub_arch_cache_dlinesz > grub_arch_cache_ilinesz)
+ grub_arch_cache_max_linesz = grub_arch_cache_dlinesz;
+ else
+ grub_arch_cache_max_linesz = grub_arch_cache_ilinesz;
+}
+
+#ifdef GRUB_MACHINE_UBOOT
+
+static void subdivide (grub_uint32_t *table, grub_uint32_t *subtable,
+ grub_uint32_t addr)
+{
+ grub_uint32_t j;
+ addr = addr >> 20 << 20;
+ table[addr >> 20] = (grub_addr_t) subtable | 1;
+ for (j = 0; j < 256; j++)
+ subtable[j] = addr | (j << 12)
+ | (3 << 4) | (3 << 6) | (3 << 8) | (3 << 10)
+ | (0 << 3) | (1 << 2) | 2;
+}
+
+void
+grub_arm_enable_caches_mmu (void)
+{
+ grub_uint32_t *table;
+ grub_uint32_t i;
+ grub_uint32_t border_crossing = 0;
+ grub_uint32_t *subtable;
+ struct sys_info *si = grub_uboot_get_sys_info ();
+
+ if (!si || (si->mr_no == 0))
+ {
+ grub_printf ("couldn't get memory map, not enabling caches");
+ grub_errno = GRUB_ERR_NONE;
+ return;
+ }
+
+ if (type == ARCH_UNKNOWN)
+ probe_caches ();
+
+ for (i = 0; (signed) i < si->mr_no; i++)
+ {
+ if (si->mr[i].start & ((1 << 20) - 1))
+ border_crossing++;
+ if ((si->mr[i].start + si->mr[i].size) & ((1 << 20) - 1))
+ border_crossing++;
+ }
+
+ grub_printf ("%d crossers\n", border_crossing);
+
+ table = grub_memalign (1 << 14, (1 << 14) + (border_crossing << 10));
+ if (!table)
+ {
+ grub_printf ("couldn't allocate place for MMU table, not enabling caches");
+ grub_errno = GRUB_ERR_NONE;
+ return;
+ }
+
+ subtable = table + (1 << 12);
+ /* Map all unknown as device. */
+ for (i = 0; i < (1 << 12); i++)
+ table[i] = (i << 20) | (3 << 10) | (0 << 3) | (1 << 2) | 2;
+ /*
+ Device: TEX= 0, C=0, B=1
+ normal: TEX= 0, C=1, B=1
+ AP = 3
+ IMP = 0
+ Domain = 0
+*/
+
+ for (i = 0; (signed) i < si->mr_no; i++)
+ {
+ if (si->mr[i].start & ((1 << 20) - 1))
+ {
+ subdivide (table, subtable, si->mr[i].start);
+ subtable += (1 << 8);
+ }
+ if ((si->mr[i].start + si->mr[i].size) & ((1 << 20) - 1))
+ {
+ subdivide (table, subtable, si->mr[i].start + si->mr[i].size);
+ subtable += (1 << 8);
+ }
+ }
+
+ for (i = 0; (signed) i < si->mr_no; i++)
+ if ((si->mr[i].flags & MR_ATTR_MASK) == MR_ATTR_DRAM
+ || (si->mr[i].flags & MR_ATTR_MASK) == MR_ATTR_SRAM
+ || (si->mr[i].flags & MR_ATTR_MASK) == MR_ATTR_FLASH)
+ {
+ grub_uint32_t cur, end;
+ cur = si->mr[i].start;
+ end = si->mr[i].start + si->mr[i].size;
+ while (cur < end)
+ {
+ grub_uint32_t *st;
+ if ((table[cur >> 20] & 3) == 2)
+ {
+ cur = cur >> 20 << 20;
+ table[cur >> 20] = cur | (3 << 10) | (1 << 3) | (1 << 2) | 2;
+ cur += (1 << 20);
+ continue;
+ }
+ cur = cur >> 12 << 12;
+ st = (grub_uint32_t *) (table[cur >> 20] & ~0x3ff);
+ st[(cur >> 12) & 0xff] = cur | (3 << 4) | (3 << 6)
+ | (3 << 8) | (3 << 10)
+ | (1 << 3) | (1 << 2) | 2;
+ cur += (1 << 12);
+ }
+ }
+
+ grub_printf ("MMU tables generated\n");
+ if (is_v6_mmu)
+ grub_arm_clear_mmu_v6 ();
+
+ grub_printf ("enabling MMU\n");
+ grub_arm_enable_mmu (table);
+ grub_printf ("MMU enabled\n");
+}
+
+#endif
+
+void
+grub_arch_sync_caches (void *address, grub_size_t len)
+{
+ grub_addr_t start = (grub_addr_t) address;
+ grub_addr_t end = start + len;
+
+ if (type == ARCH_UNKNOWN)
+ probe_caches ();
+ start = ALIGN_DOWN (start, grub_arch_cache_max_linesz);
+ end = ALIGN_UP (end, grub_arch_cache_max_linesz);
+ switch (type)
+ {
+ case ARCH_ARMV6:
+ grub_arm_clean_dcache_range_armv6 (start, end, grub_arch_cache_dlinesz);
+ grub_arm_invalidate_icache_range_armv6 (start, end,
+ grub_arch_cache_ilinesz);
+ break;
+ case ARCH_ARMV7:
+ grub_arm_clean_dcache_range_armv7 (start, end, grub_arch_cache_dlinesz);
+ grub_arm_invalidate_icache_range_armv7 (start, end,
+ grub_arch_cache_ilinesz);
+ break;
+ /* Nothing to do. */
+ case ARCH_ARMV5_WRITE_THROUGH:
+ case ARCH_ARMV6_UNIFIED:
+ break;
+ /* Pacify GCC. */
+ case ARCH_UNKNOWN:
+ break;
+ }
+}
+
+void
+grub_arch_sync_dma_caches (volatile void *address, grub_size_t len)
+{
+ grub_addr_t start = (grub_addr_t) address;
+ grub_addr_t end = start + len;
+
+ if (type == ARCH_UNKNOWN)
+ probe_caches ();
+ start = ALIGN_DOWN (start, grub_arch_cache_max_linesz);
+ end = ALIGN_UP (end, grub_arch_cache_max_linesz);
+ switch (type)
+ {
+ case ARCH_ARMV6:
+ grub_arm_clean_dcache_range_armv6 (start, end, grub_arch_cache_dlinesz);
+ grub_arm_invalidate_icache_range_armv6 (start, end,
+ grub_arch_cache_ilinesz);
+ break;
+ case ARCH_ARMV5_WRITE_THROUGH:
+ case ARCH_ARMV6_UNIFIED:
+ grub_arm_clean_dcache_range_armv6 (start, end, grub_arch_cache_dlinesz);
+ break;
+ case ARCH_ARMV7:
+ grub_arm_clean_dcache_range_poc_armv7 (start, end, grub_arch_cache_dlinesz);
+ grub_arm_invalidate_icache_range_armv7 (start, end,
+ grub_arch_cache_ilinesz);
+ break;
+ /* Pacify GCC. */
+ case ARCH_UNKNOWN:
+ break;
+ }
+}
+
+void
+grub_arm_disable_caches_mmu (void)
+{
+ if (type == ARCH_UNKNOWN)
+ probe_caches ();
+ switch (type)
+ {
+ case ARCH_ARMV5_WRITE_THROUGH:
+ case ARCH_ARMV6_UNIFIED:
+ case ARCH_ARMV6:
+ grub_arm_disable_caches_mmu_armv6 ();
+ break;
+ case ARCH_ARMV7:
+ grub_arm_disable_caches_mmu_armv7 ();
+ break;
+ /* Pacify GCC. */
+ case ARCH_UNKNOWN:
+ break;
+ }
+}