summaryrefslogtreecommitdiffstats
path: root/drivers/firmware/efi/libstub/arm64.c
blob: 8aad8c49d43f18e0903e055ecfbf38740c957fa0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2013, 2014 Linaro Ltd;  <roy.franz@linaro.org>
 *
 * This file implements the EFI boot stub for the arm64 kernel.
 * Adapted from ARM version by Mark Salter <msalter@redhat.com>
 */


#include <linux/efi.h>
#include <asm/efi.h>
#include <asm/memory.h>
#include <asm/sysreg.h>

#include "efistub.h"

static bool system_needs_vamap(void)
{
	const struct efi_smbios_type4_record *record;
	const u32 __aligned(1) *socid;
	const u8 *version;

	/*
	 * Ampere eMAG, Altra, and Altra Max machines crash in SetTime() if
	 * SetVirtualAddressMap() has not been called prior. Most Altra systems
	 * can be identified by the SMCCC soc ID, which is conveniently exposed
	 * via the type 4 SMBIOS records. Otherwise, test the processor version
	 * field. eMAG systems all appear to have the processor version field
	 * set to "eMAG".
	 */
	record = (struct efi_smbios_type4_record *)efi_get_smbios_record(4);
	if (!record)
		return false;

	socid = (u32 *)record->processor_id;
	switch (*socid & 0xffff000f) {
		static char const altra[] = "Ampere(TM) Altra(TM) Processor";
		static char const emag[] = "eMAG";

	default:
		version = efi_get_smbios_string(&record->header, 4,
						processor_version);
		if (!version || (strncmp(version, altra, sizeof(altra) - 1) &&
				 strncmp(version, emag, sizeof(emag) - 1)))
			break;

		fallthrough;

	case 0x0a160001:	// Altra
	case 0x0a160002:	// Altra Max
		efi_warn("Working around broken SetVirtualAddressMap()\n");
		return true;
	}

	return false;
}

efi_status_t check_platform_features(void)
{
	u64 tg;

	/*
	 * If we have 48 bits of VA space for TTBR0 mappings, we can map the
	 * UEFI runtime regions 1:1 and so calling SetVirtualAddressMap() is
	 * unnecessary.
	 */
	if (VA_BITS_MIN >= 48 && !system_needs_vamap())
		efi_novamap = true;

	/* UEFI mandates support for 4 KB granularity, no need to check */
	if (IS_ENABLED(CONFIG_ARM64_4K_PAGES))
		return EFI_SUCCESS;

	tg = (read_cpuid(ID_AA64MMFR0_EL1) >> ID_AA64MMFR0_EL1_TGRAN_SHIFT) & 0xf;
	if (tg < ID_AA64MMFR0_EL1_TGRAN_SUPPORTED_MIN || tg > ID_AA64MMFR0_EL1_TGRAN_SUPPORTED_MAX) {
		if (IS_ENABLED(CONFIG_ARM64_64K_PAGES))
			efi_err("This 64 KB granular kernel is not supported by your CPU\n");
		else
			efi_err("This 16 KB granular kernel is not supported by your CPU\n");
		return EFI_UNSUPPORTED;
	}
	return EFI_SUCCESS;
}

#ifdef CONFIG_ARM64_WORKAROUND_CLEAN_CACHE
#define DCTYPE	"civac"
#else
#define DCTYPE	"cvau"
#endif

void efi_cache_sync_image(unsigned long image_base,
			  unsigned long alloc_size,
			  unsigned long code_size)
{
	u32 ctr = read_cpuid_effective_cachetype();
	u64 lsize = 4 << cpuid_feature_extract_unsigned_field(ctr,
						CTR_EL0_DminLine_SHIFT);

	/* only perform the cache maintenance if needed for I/D coherency */
	if (!(ctr & BIT(CTR_EL0_IDC_SHIFT))) {
		do {
			asm("dc " DCTYPE ", %0" :: "r"(image_base));
			image_base += lsize;
			code_size -= lsize;
		} while (code_size >= lsize);
	}

	asm("ic ialluis");
	dsb(ish);
	isb();
}

unsigned long __weak primary_entry_offset(void)
{
	/*
	 * By default, we can invoke the kernel via the branch instruction in
	 * the image header, so offset #0. This will be overridden by the EFI
	 * stub build that is linked into the core kernel, as in that case, the
	 * image header may not have been loaded into memory, or may be mapped
	 * with non-executable permissions.
	 */
       return 0;
}

void __noreturn efi_enter_kernel(unsigned long entrypoint,
				 unsigned long fdt_addr,
				 unsigned long fdt_size)
{
	void (* __noreturn enter_kernel)(u64, u64, u64, u64);

	enter_kernel = (void *)entrypoint + primary_entry_offset();
	enter_kernel(fdt_addr, 0, 0, 0);
}