| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * x86 CPU caches detection and configuration | |
| * | |
| * Previous changes | |
| * - Venkatesh Pallipadi: Cache identification through CPUID(0x4) | |
| * - Ashok Raj <ashok.raj@intel.com>: Work with CPU hotplug infrastructure | |
| * - Andi Kleen / Andreas Herrmann: CPUID(0x4) emulation on AMD | |
| */ | |
| #include <linux/cacheinfo.h> | |
| #include <linux/cpu.h> | |
| #include <linux/cpuhotplug.h> | |
| #include <linux/stop_machine.h> | |
| #include <asm/amd/nb.h> | |
| #include <asm/cacheinfo.h> | |
| #include <asm/cpufeature.h> | |
| #include <asm/cpuid/api.h> | |
| #include <asm/mtrr.h> | |
| #include <asm/smp.h> | |
| #include <asm/tlbflush.h> | |
| #include "cpu.h" | |
| /* Shared last level cache maps */ | |
| DEFINE_PER_CPU_READ_MOSTLY(cpumask_var_t, cpu_llc_shared_map); | |
| /* Shared L2 cache maps */ | |
| DEFINE_PER_CPU_READ_MOSTLY(cpumask_var_t, cpu_l2c_shared_map); | |
| static cpumask_var_t cpu_cacheinfo_mask; | |
| /* Kernel controls MTRR and/or PAT MSRs. */ | |
| unsigned int memory_caching_control __ro_after_init; | |
| enum _cache_type { | |
| CTYPE_NULL = 0, | |
| CTYPE_DATA = 1, | |
| CTYPE_INST = 2, | |
| CTYPE_UNIFIED = 3 | |
| }; | |
| union _cpuid4_leaf_eax { | |
| struct { | |
| enum _cache_type type :5; | |
| unsigned int level :3; | |
| unsigned int is_self_initializing :1; | |
| unsigned int is_fully_associative :1; | |
| unsigned int reserved :4; | |
| unsigned int num_threads_sharing :12; | |
| unsigned int num_cores_on_die :6; | |
| } split; | |
| u32 full; | |
| }; | |
| union _cpuid4_leaf_ebx { | |
| struct { | |
| unsigned int coherency_line_size :12; | |
| unsigned int physical_line_partition :10; | |
| unsigned int ways_of_associativity :10; | |
| } split; | |
| u32 full; | |
| }; | |
| union _cpuid4_leaf_ecx { | |
| struct { | |
| unsigned int number_of_sets :32; | |
| } split; | |
| u32 full; | |
| }; | |
| struct _cpuid4_info { | |
| union _cpuid4_leaf_eax eax; | |
| union _cpuid4_leaf_ebx ebx; | |
| union _cpuid4_leaf_ecx ecx; | |
| unsigned int id; | |
| unsigned long size; | |
| }; | |
| /* Map CPUID(0x4) EAX.cache_type to <linux/cacheinfo.h> types */ | |
| static const enum cache_type cache_type_map[] = { | |
| [CTYPE_NULL] = CACHE_TYPE_NOCACHE, | |
| [CTYPE_DATA] = CACHE_TYPE_DATA, | |
| [CTYPE_INST] = CACHE_TYPE_INST, | |
| [CTYPE_UNIFIED] = CACHE_TYPE_UNIFIED, | |
| }; | |
| /* | |
| * Fallback AMD CPUID(0x4) emulation | |
| * AMD CPUs with TOPOEXT can just use CPUID(0x8000001d) | |
| * | |
| * @AMD_L2_L3_INVALID_ASSOC: cache info for the respective L2/L3 cache should | |
| * be determined from CPUID(0x8000001d) instead of CPUID(0x80000006). | |
| */ | |
| #define AMD_CPUID4_FULLY_ASSOCIATIVE 0xffff | |
| #define AMD_L2_L3_INVALID_ASSOC 0x9 | |
| union l1_cache { | |
| struct { | |
| unsigned line_size :8; | |
| unsigned lines_per_tag :8; | |
| unsigned assoc :8; | |
| unsigned size_in_kb :8; | |
| }; | |
| unsigned int val; | |
| }; | |
| union l2_cache { | |
| struct { | |
| unsigned line_size :8; | |
| unsigned lines_per_tag :4; | |
| unsigned assoc :4; | |
| unsigned size_in_kb :16; | |
| }; | |
| unsigned int val; | |
| }; | |
| union l3_cache { | |
| struct { | |
| unsigned line_size :8; | |
| unsigned lines_per_tag :4; | |
| unsigned assoc :4; | |
| unsigned res :2; | |
| unsigned size_encoded :14; | |
| }; | |
| unsigned int val; | |
| }; | |
| /* L2/L3 associativity mapping */ | |
| static const unsigned short assocs[] = { | |
| [1] = 1, | |
| [2] = 2, | |
| [3] = 3, | |
| [4] = 4, | |
| [5] = 6, | |
| [6] = 8, | |
| [8] = 16, | |
| [0xa] = 32, | |
| [0xb] = 48, | |
| [0xc] = 64, | |
| [0xd] = 96, | |
| [0xe] = 128, | |
| [0xf] = AMD_CPUID4_FULLY_ASSOCIATIVE | |
| }; | |
| static const unsigned char levels[] = { 1, 1, 2, 3 }; | |
| static const unsigned char types[] = { 1, 2, 3, 3 }; | |
| static void legacy_amd_cpuid4(int index, union _cpuid4_leaf_eax *eax, | |
| union _cpuid4_leaf_ebx *ebx, union _cpuid4_leaf_ecx *ecx) | |
| { | |
| unsigned int dummy, line_size, lines_per_tag, assoc, size_in_kb; | |
| union l1_cache l1i, l1d, *l1; | |
| union l2_cache l2; | |
| union l3_cache l3; | |
| eax->full = 0; | |
| ebx->full = 0; | |
| ecx->full = 0; | |
| cpuid(0x80000005, &dummy, &dummy, &l1d.val, &l1i.val); | |
| cpuid(0x80000006, &dummy, &dummy, &l2.val, &l3.val); | |
| l1 = &l1d; | |
| switch (index) { | |
| case 1: | |
| l1 = &l1i; | |
| fallthrough; | |
| case 0: | |
| if (!l1->val) | |
| return; | |
| assoc = (l1->assoc == 0xff) ? AMD_CPUID4_FULLY_ASSOCIATIVE : l1->assoc; | |
| line_size = l1->line_size; | |
| lines_per_tag = l1->lines_per_tag; | |
| size_in_kb = l1->size_in_kb; | |
| break; | |
| case 2: | |
| if (!l2.assoc || l2.assoc == AMD_L2_L3_INVALID_ASSOC) | |
| return; | |
| /* Use x86_cache_size as it might have K7 errata fixes */ | |
| assoc = assocs[l2.assoc]; | |
| line_size = l2.line_size; | |
| lines_per_tag = l2.lines_per_tag; | |
| size_in_kb = __this_cpu_read(cpu_info.x86_cache_size); | |
| break; | |
| case 3: | |
| if (!l3.assoc || l3.assoc == AMD_L2_L3_INVALID_ASSOC) | |
| return; | |
| assoc = assocs[l3.assoc]; | |
| line_size = l3.line_size; | |
| lines_per_tag = l3.lines_per_tag; | |
| size_in_kb = l3.size_encoded * 512; | |
| if (boot_cpu_has(X86_FEATURE_AMD_DCM)) { | |
| size_in_kb = size_in_kb >> 1; | |
| assoc = assoc >> 1; | |
| } | |
| break; | |
| default: | |
| return; | |
| } | |
| eax->split.is_self_initializing = 1; | |
| eax->split.type = types[index]; | |
| eax->split.level = levels[index]; | |
| eax->split.num_threads_sharing = 0; | |
| eax->split.num_cores_on_die = topology_num_cores_per_package(); | |
| if (assoc == AMD_CPUID4_FULLY_ASSOCIATIVE) | |
| eax->split.is_fully_associative = 1; | |
| ebx->split.coherency_line_size = line_size - 1; | |
| ebx->split.ways_of_associativity = assoc - 1; | |
| ebx->split.physical_line_partition = lines_per_tag - 1; | |
| ecx->split.number_of_sets = (size_in_kb * 1024) / line_size / | |
| (ebx->split.ways_of_associativity + 1) - 1; | |
| } | |
| static int cpuid4_info_fill_done(struct _cpuid4_info *id4, union _cpuid4_leaf_eax eax, | |
| union _cpuid4_leaf_ebx ebx, union _cpuid4_leaf_ecx ecx) | |
| { | |
| if (eax.split.type == CTYPE_NULL) | |
| return -EIO; | |
| id4->eax = eax; | |
| id4->ebx = ebx; | |
| id4->ecx = ecx; | |
| id4->size = (ecx.split.number_of_sets + 1) * | |
| (ebx.split.coherency_line_size + 1) * | |
| (ebx.split.physical_line_partition + 1) * | |
| (ebx.split.ways_of_associativity + 1); | |
| return 0; | |
| } | |
| static int amd_fill_cpuid4_info(int index, struct _cpuid4_info *id4) | |
| { | |
| union _cpuid4_leaf_eax eax; | |
| union _cpuid4_leaf_ebx ebx; | |
| union _cpuid4_leaf_ecx ecx; | |
| u32 ignored; | |
| if (boot_cpu_has(X86_FEATURE_TOPOEXT) || boot_cpu_data.x86_vendor == X86_VENDOR_HYGON) | |
| cpuid_count(0x8000001d, index, &eax.full, &ebx.full, &ecx.full, &ignored); | |
| else | |
| legacy_amd_cpuid4(index, &eax, &ebx, &ecx); | |
| return cpuid4_info_fill_done(id4, eax, ebx, ecx); | |
| } | |
| static int intel_fill_cpuid4_info(int index, struct _cpuid4_info *id4) | |
| { | |
| union _cpuid4_leaf_eax eax; | |
| union _cpuid4_leaf_ebx ebx; | |
| union _cpuid4_leaf_ecx ecx; | |
| u32 ignored; | |
| cpuid_count(4, index, &eax.full, &ebx.full, &ecx.full, &ignored); | |
| return cpuid4_info_fill_done(id4, eax, ebx, ecx); | |
| } | |
| static int fill_cpuid4_info(int index, struct _cpuid4_info *id4) | |
| { | |
| u8 cpu_vendor = boot_cpu_data.x86_vendor; | |
| return (cpu_vendor == X86_VENDOR_AMD || cpu_vendor == X86_VENDOR_HYGON) ? | |
| amd_fill_cpuid4_info(index, id4) : | |
| intel_fill_cpuid4_info(index, id4); | |
| } | |
| static int find_num_cache_leaves(struct cpuinfo_x86 *c) | |
| { | |
| unsigned int eax, ebx, ecx, edx, op; | |
| union _cpuid4_leaf_eax cache_eax; | |
| int i = -1; | |
| /* Do a CPUID(op) loop to calculate num_cache_leaves */ | |
| op = (c->x86_vendor == X86_VENDOR_AMD || c->x86_vendor == X86_VENDOR_HYGON) ? 0x8000001d : 4; | |
| do { | |
| ++i; | |
| cpuid_count(op, i, &eax, &ebx, &ecx, &edx); | |
| cache_eax.full = eax; | |
| } while (cache_eax.split.type != CTYPE_NULL); | |
| return i; | |
| } | |
| /* | |
| * The max shared threads number comes from CPUID(0x4) EAX[25-14] with input | |
| * ECX as cache index. Then right shift apicid by the number's order to get | |
| * cache id for this cache node. | |
| */ | |
| static unsigned int get_cache_id(u32 apicid, const struct _cpuid4_info *id4) | |
| { | |
| unsigned long num_threads_sharing; | |
| int index_msb; | |
| num_threads_sharing = 1 + id4->eax.split.num_threads_sharing; | |
| index_msb = get_count_order(num_threads_sharing); | |
| return apicid >> index_msb; | |
| } | |
| /* | |
| * AMD/Hygon CPUs may have multiple LLCs if L3 caches exist. | |
| */ | |
| void cacheinfo_amd_init_llc_id(struct cpuinfo_x86 *c, u16 die_id) | |
| { | |
| if (!cpuid_amd_hygon_has_l3_cache()) | |
| return; | |
| if (c->x86 < 0x17) { | |
| /* Pre-Zen: LLC is at the node level */ | |
| c->topo.llc_id = die_id; | |
| } else if (c->x86 == 0x17 && c->x86_model <= 0x1F) { | |
| /* | |
| * Family 17h up to 1F models: LLC is at the core | |
| * complex level. Core complex ID is ApicId[3]. | |
| */ | |
| c->topo.llc_id = c->topo.apicid >> 3; | |
| } else { | |
| /* | |
| * Newer families: LLC ID is calculated from the number | |
| * of threads sharing the L3 cache. | |
| */ | |
| u32 llc_index = find_num_cache_leaves(c) - 1; | |
| struct _cpuid4_info id4 = {}; | |
| if (!amd_fill_cpuid4_info(llc_index, &id4)) | |
| c->topo.llc_id = get_cache_id(c->topo.apicid, &id4); | |
| } | |
| } | |
| void cacheinfo_hygon_init_llc_id(struct cpuinfo_x86 *c) | |
| { | |
| if (!cpuid_amd_hygon_has_l3_cache()) | |
| return; | |
| /* | |
| * Hygons are similar to AMD Family 17h up to 1F models: LLC is | |
| * at the core complex level. Core complex ID is ApicId[3]. | |
| */ | |
| c->topo.llc_id = c->topo.apicid >> 3; | |
| } | |
| void init_amd_cacheinfo(struct cpuinfo_x86 *c) | |
| { | |
| struct cpu_cacheinfo *ci = get_cpu_cacheinfo(c->cpu_index); | |
| if (boot_cpu_has(X86_FEATURE_TOPOEXT)) | |
| ci->num_leaves = find_num_cache_leaves(c); | |
| else if (c->extended_cpuid_level >= 0x80000006) | |
| ci->num_leaves = (cpuid_edx(0x80000006) & 0xf000) ? 4 : 3; | |
| } | |
| void init_hygon_cacheinfo(struct cpuinfo_x86 *c) | |
| { | |
| struct cpu_cacheinfo *ci = get_cpu_cacheinfo(c->cpu_index); | |
| ci->num_leaves = find_num_cache_leaves(c); | |
| } | |
| static void intel_cacheinfo_done(struct cpuinfo_x86 *c, unsigned int l3, | |
| unsigned int l2, unsigned int l1i, unsigned int l1d) | |
| { | |
| /* | |
| * If llc_id is still unset, then cpuid_level < 4, which implies | |
| * that the only possibility left is SMT. Since CPUID(0x2) doesn't | |
| * specify any shared caches and SMT shares all caches, we can | |
| * unconditionally set LLC ID to the package ID so that all | |
| * threads share it. | |
| */ | |
| if (c->topo.llc_id == BAD_APICID) | |
| c->topo.llc_id = c->topo.pkg_id; | |
| c->x86_cache_size = l3 ? l3 : (l2 ? l2 : l1i + l1d); | |
| if (!l2) | |
| cpu_detect_cache_sizes(c); | |
| } | |
| /* | |
| * Legacy Intel CPUID(0x2) path if CPUID(0x4) is not available. | |
| */ | |
| static void intel_cacheinfo_0x2(struct cpuinfo_x86 *c) | |
| { | |
| unsigned int l1i = 0, l1d = 0, l2 = 0, l3 = 0; | |
| const struct leaf_0x2_table *desc; | |
| union leaf_0x2_regs regs; | |
| u8 *ptr; | |
| if (c->cpuid_level < 2) | |
| return; | |
| cpuid_leaf_0x2(®s); | |
| for_each_cpuid_0x2_desc(regs, ptr, desc) { | |
| switch (desc->c_type) { | |
| case CACHE_L1_INST: l1i += desc->c_size; break; | |
| case CACHE_L1_DATA: l1d += desc->c_size; break; | |
| case CACHE_L2: l2 += desc->c_size; break; | |
| case CACHE_L3: l3 += desc->c_size; break; | |
| } | |
| } | |
| intel_cacheinfo_done(c, l3, l2, l1i, l1d); | |
| } | |
| static unsigned int calc_cache_topo_id(struct cpuinfo_x86 *c, const struct _cpuid4_info *id4) | |
| { | |
| unsigned int num_threads_sharing; | |
| int index_msb; | |
| num_threads_sharing = 1 + id4->eax.split.num_threads_sharing; | |
| index_msb = get_count_order(num_threads_sharing); | |
| return c->topo.apicid & ~((1 << index_msb) - 1); | |
| } | |
| static bool intel_cacheinfo_0x4(struct cpuinfo_x86 *c) | |
| { | |
| struct cpu_cacheinfo *ci = get_cpu_cacheinfo(c->cpu_index); | |
| unsigned int l2_id = BAD_APICID, l3_id = BAD_APICID; | |
| unsigned int l1d = 0, l1i = 0, l2 = 0, l3 = 0; | |
| if (c->cpuid_level < 4) | |
| return false; | |
| /* | |
| * There should be at least one leaf. A non-zero value means | |
| * that the number of leaves has been previously initialized. | |
| */ | |
| if (!ci->num_leaves) | |
| ci->num_leaves = find_num_cache_leaves(c); | |
| if (!ci->num_leaves) | |
| return false; | |
| for (int i = 0; i < ci->num_leaves; i++) { | |
| struct _cpuid4_info id4 = {}; | |
| int ret; | |
| ret = intel_fill_cpuid4_info(i, &id4); | |
| if (ret < 0) | |
| continue; | |
| switch (id4.eax.split.level) { | |
| case 1: | |
| if (id4.eax.split.type == CTYPE_DATA) | |
| l1d = id4.size / 1024; | |
| else if (id4.eax.split.type == CTYPE_INST) | |
| l1i = id4.size / 1024; | |
| break; | |
| case 2: | |
| l2 = id4.size / 1024; | |
| l2_id = calc_cache_topo_id(c, &id4); | |
| break; | |
| case 3: | |
| l3 = id4.size / 1024; | |
| l3_id = calc_cache_topo_id(c, &id4); | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| c->topo.l2c_id = l2_id; | |
| c->topo.llc_id = (l3_id == BAD_APICID) ? l2_id : l3_id; | |
| intel_cacheinfo_done(c, l3, l2, l1i, l1d); | |
| return true; | |
| } | |
| void init_intel_cacheinfo(struct cpuinfo_x86 *c) | |
| { | |
| /* Don't use CPUID(0x2) if CPUID(0x4) is supported. */ | |
| if (intel_cacheinfo_0x4(c)) | |
| return; | |
| intel_cacheinfo_0x2(c); | |
| } | |
| /* | |
| * <linux/cacheinfo.h> shared_cpu_map setup, AMD/Hygon | |
| */ | |
| static int __cache_amd_cpumap_setup(unsigned int cpu, int index, | |
| const struct _cpuid4_info *id4) | |
| { | |
| struct cpu_cacheinfo *this_cpu_ci; | |
| struct cacheinfo *ci; | |
| int i, sibling; | |
| /* | |
| * For L3, always use the pre-calculated cpu_llc_shared_mask | |
| * to derive shared_cpu_map. | |
| */ | |
| if (index == 3) { | |
| for_each_cpu(i, cpu_llc_shared_mask(cpu)) { | |
| this_cpu_ci = get_cpu_cacheinfo(i); | |
| if (!this_cpu_ci->info_list) | |
| continue; | |
| ci = this_cpu_ci->info_list + index; | |
| for_each_cpu(sibling, cpu_llc_shared_mask(cpu)) { | |
| if (!cpu_online(sibling)) | |
| continue; | |
| cpumask_set_cpu(sibling, &ci->shared_cpu_map); | |
| } | |
| } | |
| } else if (boot_cpu_has(X86_FEATURE_TOPOEXT)) { | |
| unsigned int apicid, nshared, first, last; | |
| nshared = id4->eax.split.num_threads_sharing + 1; | |
| apicid = cpu_data(cpu).topo.apicid; | |
| first = apicid - (apicid % nshared); | |
| last = first + nshared - 1; | |
| for_each_online_cpu(i) { | |
| this_cpu_ci = get_cpu_cacheinfo(i); | |
| if (!this_cpu_ci->info_list) | |
| continue; | |
| apicid = cpu_data(i).topo.apicid; | |
| if ((apicid < first) || (apicid > last)) | |
| continue; | |
| ci = this_cpu_ci->info_list + index; | |
| for_each_online_cpu(sibling) { | |
| apicid = cpu_data(sibling).topo.apicid; | |
| if ((apicid < first) || (apicid > last)) | |
| continue; | |
| cpumask_set_cpu(sibling, &ci->shared_cpu_map); | |
| } | |
| } | |
| } else | |
| return 0; | |
| return 1; | |
| } | |
| /* | |
| * <linux/cacheinfo.h> shared_cpu_map setup, Intel + fallback AMD/Hygon | |
| */ | |
| static void __cache_cpumap_setup(unsigned int cpu, int index, | |
| const struct _cpuid4_info *id4) | |
| { | |
| struct cpu_cacheinfo *this_cpu_ci = get_cpu_cacheinfo(cpu); | |
| struct cpuinfo_x86 *c = &cpu_data(cpu); | |
| struct cacheinfo *ci, *sibling_ci; | |
| unsigned long num_threads_sharing; | |
| int index_msb, i; | |
| if (c->x86_vendor == X86_VENDOR_AMD || c->x86_vendor == X86_VENDOR_HYGON) { | |
| if (__cache_amd_cpumap_setup(cpu, index, id4)) | |
| return; | |
| } | |
| ci = this_cpu_ci->info_list + index; | |
| num_threads_sharing = 1 + id4->eax.split.num_threads_sharing; | |
| cpumask_set_cpu(cpu, &ci->shared_cpu_map); | |
| if (num_threads_sharing == 1) | |
| return; | |
| index_msb = get_count_order(num_threads_sharing); | |
| for_each_online_cpu(i) | |
| if (cpu_data(i).topo.apicid >> index_msb == c->topo.apicid >> index_msb) { | |
| struct cpu_cacheinfo *sib_cpu_ci = get_cpu_cacheinfo(i); | |
| /* Skip if itself or no cacheinfo */ | |
| if (i == cpu || !sib_cpu_ci->info_list) | |
| continue; | |
| sibling_ci = sib_cpu_ci->info_list + index; | |
| cpumask_set_cpu(i, &ci->shared_cpu_map); | |
| cpumask_set_cpu(cpu, &sibling_ci->shared_cpu_map); | |
| } | |
| } | |
| static void ci_info_init(struct cacheinfo *ci, const struct _cpuid4_info *id4, | |
| struct amd_northbridge *nb) | |
| { | |
| ci->id = id4->id; | |
| ci->attributes = CACHE_ID; | |
| ci->level = id4->eax.split.level; | |
| ci->type = cache_type_map[id4->eax.split.type]; | |
| ci->coherency_line_size = id4->ebx.split.coherency_line_size + 1; | |
| ci->ways_of_associativity = id4->ebx.split.ways_of_associativity + 1; | |
| ci->size = id4->size; | |
| ci->number_of_sets = id4->ecx.split.number_of_sets + 1; | |
| ci->physical_line_partition = id4->ebx.split.physical_line_partition + 1; | |
| ci->priv = nb; | |
| } | |
| int init_cache_level(unsigned int cpu) | |
| { | |
| struct cpu_cacheinfo *ci = get_cpu_cacheinfo(cpu); | |
| /* There should be at least one leaf. */ | |
| if (!ci->num_leaves) | |
| return -ENOENT; | |
| return 0; | |
| } | |
| int populate_cache_leaves(unsigned int cpu) | |
| { | |
| struct cpu_cacheinfo *this_cpu_ci = get_cpu_cacheinfo(cpu); | |
| struct cacheinfo *ci = this_cpu_ci->info_list; | |
| u8 cpu_vendor = boot_cpu_data.x86_vendor; | |
| u32 apicid = cpu_data(cpu).topo.apicid; | |
| struct amd_northbridge *nb = NULL; | |
| struct _cpuid4_info id4 = {}; | |
| int idx, ret; | |
| for (idx = 0; idx < this_cpu_ci->num_leaves; idx++) { | |
| ret = fill_cpuid4_info(idx, &id4); | |
| if (ret) | |
| return ret; | |
| id4.id = get_cache_id(apicid, &id4); | |
| if (cpu_vendor == X86_VENDOR_AMD || cpu_vendor == X86_VENDOR_HYGON) | |
| nb = amd_init_l3_cache(idx); | |
| ci_info_init(ci++, &id4, nb); | |
| __cache_cpumap_setup(cpu, idx, &id4); | |
| } | |
| this_cpu_ci->cpu_map_populated = true; | |
| return 0; | |
| } | |
| /* | |
| * Disable and enable caches. Needed for changing MTRRs and the PAT MSR. | |
| * | |
| * Since we are disabling the cache don't allow any interrupts, | |
| * they would run extremely slow and would only increase the pain. | |
| * | |
| * The caller must ensure that local interrupts are disabled and | |
| * are reenabled after cache_enable() has been called. | |
| */ | |
| static unsigned long saved_cr4; | |
| static DEFINE_RAW_SPINLOCK(cache_disable_lock); | |
| /* | |
| * Cache flushing is the most time-consuming step when programming the | |
| * MTRRs. On many Intel CPUs without known erratas, it can be skipped | |
| * if the CPU declares cache self-snooping support. | |
| */ | |
| static void maybe_flush_caches(void) | |
| { | |
| if (!cpu_feature_enabled(X86_FEATURE_SELFSNOOP)) | |
| wbinvd(); | |
| } | |
| void cache_disable(void) __acquires(cache_disable_lock) | |
| { | |
| unsigned long cr0; | |
| /* | |
| * This is not ideal since the cache is only flushed/disabled | |
| * for this CPU while the MTRRs are changed, but changing this | |
| * requires more invasive changes to the way the kernel boots. | |
| */ | |
| raw_spin_lock(&cache_disable_lock); | |
| /* Enter the no-fill (CD=1, NW=0) cache mode and flush caches. */ | |
| cr0 = read_cr0() | X86_CR0_CD; | |
| write_cr0(cr0); | |
| maybe_flush_caches(); | |
| /* Save value of CR4 and clear Page Global Enable (bit 7) */ | |
| if (cpu_feature_enabled(X86_FEATURE_PGE)) { | |
| saved_cr4 = __read_cr4(); | |
| __write_cr4(saved_cr4 & ~X86_CR4_PGE); | |
| } | |
| /* Flush all TLBs via a mov %cr3, %reg; mov %reg, %cr3 */ | |
| count_vm_tlb_event(NR_TLB_LOCAL_FLUSH_ALL); | |
| flush_tlb_local(); | |
| if (cpu_feature_enabled(X86_FEATURE_MTRR)) | |
| mtrr_disable(); | |
| maybe_flush_caches(); | |
| } | |
| void cache_enable(void) __releases(cache_disable_lock) | |
| { | |
| /* Flush TLBs (no need to flush caches - they are disabled) */ | |
| count_vm_tlb_event(NR_TLB_LOCAL_FLUSH_ALL); | |
| flush_tlb_local(); | |
| if (cpu_feature_enabled(X86_FEATURE_MTRR)) | |
| mtrr_enable(); | |
| /* Enable caches */ | |
| write_cr0(read_cr0() & ~X86_CR0_CD); | |
| /* Restore value of CR4 */ | |
| if (cpu_feature_enabled(X86_FEATURE_PGE)) | |
| __write_cr4(saved_cr4); | |
| raw_spin_unlock(&cache_disable_lock); | |
| } | |
| static void cache_cpu_init(void) | |
| { | |
| unsigned long flags; | |
| local_irq_save(flags); | |
| if (memory_caching_control & CACHE_MTRR) { | |
| cache_disable(); | |
| mtrr_generic_set_state(); | |
| cache_enable(); | |
| } | |
| if (memory_caching_control & CACHE_PAT) | |
| pat_cpu_init(); | |
| local_irq_restore(flags); | |
| } | |
| static bool cache_aps_delayed_init = true; | |
| void set_cache_aps_delayed_init(bool val) | |
| { | |
| cache_aps_delayed_init = val; | |
| } | |
| bool get_cache_aps_delayed_init(void) | |
| { | |
| return cache_aps_delayed_init; | |
| } | |
| static int cache_rendezvous_handler(void *unused) | |
| { | |
| if (get_cache_aps_delayed_init() || !cpu_online(smp_processor_id())) | |
| cache_cpu_init(); | |
| return 0; | |
| } | |
| void __init cache_bp_init(void) | |
| { | |
| mtrr_bp_init(); | |
| pat_bp_init(); | |
| if (memory_caching_control) | |
| cache_cpu_init(); | |
| } | |
| void cache_bp_restore(void) | |
| { | |
| if (memory_caching_control) | |
| cache_cpu_init(); | |
| } | |
| static int cache_ap_online(unsigned int cpu) | |
| { | |
| cpumask_set_cpu(cpu, cpu_cacheinfo_mask); | |
| if (!memory_caching_control || get_cache_aps_delayed_init()) | |
| return 0; | |
| /* | |
| * Ideally we should hold mtrr_mutex here to avoid MTRR entries | |
| * changed, but this routine will be called in CPU boot time, | |
| * holding the lock breaks it. | |
| * | |
| * This routine is called in two cases: | |
| * | |
| * 1. very early time of software resume, when there absolutely | |
| * isn't MTRR entry changes; | |
| * | |
| * 2. CPU hotadd time. We let mtrr_add/del_page hold cpuhotplug | |
| * lock to prevent MTRR entry changes | |
| */ | |
| stop_machine_from_inactive_cpu(cache_rendezvous_handler, NULL, | |
| cpu_cacheinfo_mask); | |
| return 0; | |
| } | |
| static int cache_ap_offline(unsigned int cpu) | |
| { | |
| cpumask_clear_cpu(cpu, cpu_cacheinfo_mask); | |
| return 0; | |
| } | |
| /* | |
| * Delayed cache initialization for all AP's | |
| */ | |
| void cache_aps_init(void) | |
| { | |
| if (!memory_caching_control || !get_cache_aps_delayed_init()) | |
| return; | |
| stop_machine(cache_rendezvous_handler, NULL, cpu_online_mask); | |
| set_cache_aps_delayed_init(false); | |
| } | |
| static int __init cache_ap_register(void) | |
| { | |
| zalloc_cpumask_var(&cpu_cacheinfo_mask, GFP_KERNEL); | |
| cpumask_set_cpu(smp_processor_id(), cpu_cacheinfo_mask); | |
| cpuhp_setup_state_nocalls(CPUHP_AP_CACHECTRL_STARTING, | |
| "x86/cachectrl:starting", | |
| cache_ap_online, cache_ap_offline); | |
| return 0; | |
| } | |
| early_initcall(cache_ap_register); |