1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Extract CPU cache information and expose them via sysfs. |
4 | * |
5 | * Copyright IBM Corp. 2012 |
6 | */ |
7 | |
8 | #include <linux/seq_file.h> |
9 | #include <linux/cpu.h> |
10 | #include <linux/cacheinfo.h> |
11 | #include <asm/facility.h> |
12 | |
13 | enum { |
14 | CACHE_SCOPE_NOTEXISTS, |
15 | CACHE_SCOPE_PRIVATE, |
16 | CACHE_SCOPE_SHARED, |
17 | CACHE_SCOPE_RESERVED, |
18 | }; |
19 | |
20 | enum { |
21 | CTYPE_SEPARATE, |
22 | CTYPE_DATA, |
23 | CTYPE_INSTRUCTION, |
24 | CTYPE_UNIFIED, |
25 | }; |
26 | |
27 | enum { |
28 | , |
29 | , |
30 | , |
31 | , |
32 | }; |
33 | |
34 | enum { |
35 | CACHE_TI_UNIFIED = 0, |
36 | CACHE_TI_DATA = 0, |
37 | CACHE_TI_INSTRUCTION, |
38 | }; |
39 | |
40 | struct cache_info { |
41 | unsigned char : 4; |
42 | unsigned char scope : 2; |
43 | unsigned char type : 2; |
44 | }; |
45 | |
46 | #define CACHE_MAX_LEVEL 8 |
47 | union cache_topology { |
48 | struct cache_info ci[CACHE_MAX_LEVEL]; |
49 | unsigned long raw; |
50 | }; |
51 | |
52 | static const char * const cache_type_string[] = { |
53 | "" , |
54 | "Instruction" , |
55 | "Data" , |
56 | "" , |
57 | "Unified" , |
58 | }; |
59 | |
60 | static const enum cache_type cache_type_map[] = { |
61 | [CTYPE_SEPARATE] = CACHE_TYPE_SEPARATE, |
62 | [CTYPE_DATA] = CACHE_TYPE_DATA, |
63 | [CTYPE_INSTRUCTION] = CACHE_TYPE_INST, |
64 | [CTYPE_UNIFIED] = CACHE_TYPE_UNIFIED, |
65 | }; |
66 | |
67 | void show_cacheinfo(struct seq_file *m) |
68 | { |
69 | struct cpu_cacheinfo *this_cpu_ci; |
70 | struct cacheinfo *cache; |
71 | int idx; |
72 | |
73 | this_cpu_ci = get_cpu_cacheinfo(cpumask_any(cpu_online_mask)); |
74 | for (idx = 0; idx < this_cpu_ci->num_leaves; idx++) { |
75 | cache = this_cpu_ci->info_list + idx; |
76 | seq_printf(m, fmt: "cache%-11d: " , idx); |
77 | seq_printf(m, fmt: "level=%d " , cache->level); |
78 | seq_printf(m, fmt: "type=%s " , cache_type_string[cache->type]); |
79 | seq_printf(m, fmt: "scope=%s " , |
80 | cache->disable_sysfs ? "Shared" : "Private" ); |
81 | seq_printf(m, fmt: "size=%dK " , cache->size >> 10); |
82 | seq_printf(m, fmt: "line_size=%u " , cache->coherency_line_size); |
83 | seq_printf(m, fmt: "associativity=%d" , cache->ways_of_associativity); |
84 | seq_puts(m, s: "\n" ); |
85 | } |
86 | } |
87 | |
88 | static inline enum cache_type get_cache_type(struct cache_info *ci, int level) |
89 | { |
90 | if (level >= CACHE_MAX_LEVEL) |
91 | return CACHE_TYPE_NOCACHE; |
92 | ci += level; |
93 | if (ci->scope != CACHE_SCOPE_SHARED && ci->scope != CACHE_SCOPE_PRIVATE) |
94 | return CACHE_TYPE_NOCACHE; |
95 | return cache_type_map[ci->type]; |
96 | } |
97 | |
98 | static inline unsigned long ecag(int ai, int li, int ti) |
99 | { |
100 | return __ecag(ECAG_CACHE_ATTRIBUTE, ai << 4 | li << 1 | ti); |
101 | } |
102 | |
103 | static void ci_leaf_init(struct cacheinfo *this_leaf, int private, |
104 | enum cache_type type, unsigned int level, int cpu) |
105 | { |
106 | int ti, num_sets; |
107 | |
108 | if (type == CACHE_TYPE_INST) |
109 | ti = CACHE_TI_INSTRUCTION; |
110 | else |
111 | ti = CACHE_TI_UNIFIED; |
112 | this_leaf->level = level + 1; |
113 | this_leaf->type = type; |
114 | this_leaf->coherency_line_size = ecag(ai: EXTRACT_LINE_SIZE, li: level, ti); |
115 | this_leaf->ways_of_associativity = ecag(ai: EXTRACT_ASSOCIATIVITY, li: level, ti); |
116 | this_leaf->size = ecag(ai: EXTRACT_SIZE, li: level, ti); |
117 | num_sets = this_leaf->size / this_leaf->coherency_line_size; |
118 | num_sets /= this_leaf->ways_of_associativity; |
119 | this_leaf->number_of_sets = num_sets; |
120 | cpumask_set_cpu(cpu, dstp: &this_leaf->shared_cpu_map); |
121 | if (!private) |
122 | this_leaf->disable_sysfs = true; |
123 | } |
124 | |
125 | int init_cache_level(unsigned int cpu) |
126 | { |
127 | struct cpu_cacheinfo *this_cpu_ci = get_cpu_cacheinfo(cpu); |
128 | unsigned int level = 0, leaves = 0; |
129 | union cache_topology ct; |
130 | enum cache_type ctype; |
131 | |
132 | if (!this_cpu_ci) |
133 | return -EINVAL; |
134 | ct.raw = ecag(ai: EXTRACT_TOPOLOGY, li: 0, ti: 0); |
135 | do { |
136 | ctype = get_cache_type(ci: &ct.ci[0], level); |
137 | if (ctype == CACHE_TYPE_NOCACHE) |
138 | break; |
139 | /* Separate instruction and data caches */ |
140 | leaves += (ctype == CACHE_TYPE_SEPARATE) ? 2 : 1; |
141 | } while (++level < CACHE_MAX_LEVEL); |
142 | this_cpu_ci->num_levels = level; |
143 | this_cpu_ci->num_leaves = leaves; |
144 | return 0; |
145 | } |
146 | |
147 | int populate_cache_leaves(unsigned int cpu) |
148 | { |
149 | struct cpu_cacheinfo *this_cpu_ci = get_cpu_cacheinfo(cpu); |
150 | struct cacheinfo *this_leaf = this_cpu_ci->info_list; |
151 | unsigned int level, idx, pvt; |
152 | union cache_topology ct; |
153 | enum cache_type ctype; |
154 | |
155 | ct.raw = ecag(ai: EXTRACT_TOPOLOGY, li: 0, ti: 0); |
156 | for (idx = 0, level = 0; level < this_cpu_ci->num_levels && |
157 | idx < this_cpu_ci->num_leaves; idx++, level++) { |
158 | if (!this_leaf) |
159 | return -EINVAL; |
160 | pvt = (ct.ci[level].scope == CACHE_SCOPE_PRIVATE) ? 1 : 0; |
161 | ctype = get_cache_type(ci: &ct.ci[0], level); |
162 | if (ctype == CACHE_TYPE_SEPARATE) { |
163 | ci_leaf_init(this_leaf: this_leaf++, private: pvt, type: CACHE_TYPE_DATA, level, cpu); |
164 | ci_leaf_init(this_leaf: this_leaf++, private: pvt, type: CACHE_TYPE_INST, level, cpu); |
165 | } else { |
166 | ci_leaf_init(this_leaf: this_leaf++, private: pvt, type: ctype, level, cpu); |
167 | } |
168 | } |
169 | this_cpu_ci->cpu_map_populated = true; |
170 | return 0; |
171 | } |
172 | |