grub/grub-core/kern/arm/cache.c
Marc Zyngier 6a34fdb76a arm: Fix 32-bit ARM handling of the CTR register
When booting on an ARMv8 core that implements either CTR.IDC or CTR.DIC
(indicating that some of the cache maintenance operations can be
removed when dealing with I/D-cache coherency, GRUB dies with a
"Unsupported cache type 0x........" message.

This is pretty likely to happen when running in a virtual machine
hosted on an arm64 machine (I've triggered it on a system built around
a bunch of Cortex-A55 cores, which implements CTR.IDC).

It turns out that the way GRUB deals with the CTR register is a bit
harsh for anything from ARMv7 onwards. The layout of the register is
backward compatible, meaning that nothing that gets added is allowed to
break earlier behaviour. In this case, ignoring IDC is completely fine,
and only results in unnecessary cache maintenance.

We can thus avoid being paranoid, and align the 32bit behaviour with
its 64bit equivalent.

This patch has the added benefit that it gets rid of a (gnu-specific)
case range too.

Signed-off-by: Marc Zyngier <maz@kernel.org>
Reviewed-by: Leif Lindholm <leif@nuviainc.com>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
2020-05-25 15:02:51 +02:00

311 lines
8.1 KiB
C

#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;
}
}