| 
									
										
										
										
											2012-08-23 16:31:13 +02:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * Extract CPU cache information and expose them via sysfs. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *    Copyright IBM Corp. 2012 | 
					
						
							|  |  |  |  *    Author(s): Heiko Carstens <heiko.carstens@de.ibm.com> | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-08-29 14:12:20 +02:00
										 |  |  | #include <linux/seq_file.h>
 | 
					
						
							| 
									
										
										
										
											2012-08-23 16:31:13 +02:00
										 |  |  | #include <linux/cpu.h>
 | 
					
						
							| 
									
										
										
										
											2015-01-08 07:41:52 +00:00
										 |  |  | #include <linux/cacheinfo.h>
 | 
					
						
							| 
									
										
										
										
											2012-08-23 16:31:13 +02:00
										 |  |  | #include <asm/facility.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | enum { | 
					
						
							|  |  |  | 	CACHE_SCOPE_NOTEXISTS, | 
					
						
							|  |  |  | 	CACHE_SCOPE_PRIVATE, | 
					
						
							|  |  |  | 	CACHE_SCOPE_SHARED, | 
					
						
							|  |  |  | 	CACHE_SCOPE_RESERVED, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | enum { | 
					
						
							| 
									
										
										
										
											2015-01-08 07:41:52 +00:00
										 |  |  | 	CTYPE_SEPARATE, | 
					
						
							|  |  |  | 	CTYPE_DATA, | 
					
						
							|  |  |  | 	CTYPE_INSTRUCTION, | 
					
						
							|  |  |  | 	CTYPE_UNIFIED, | 
					
						
							| 
									
										
										
										
											2012-08-23 16:31:13 +02:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | enum { | 
					
						
							|  |  |  | 	EXTRACT_TOPOLOGY, | 
					
						
							|  |  |  | 	EXTRACT_LINE_SIZE, | 
					
						
							|  |  |  | 	EXTRACT_SIZE, | 
					
						
							|  |  |  | 	EXTRACT_ASSOCIATIVITY, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | enum { | 
					
						
							|  |  |  | 	CACHE_TI_UNIFIED = 0, | 
					
						
							| 
									
										
										
										
											2012-10-18 13:13:41 +02:00
										 |  |  | 	CACHE_TI_DATA = 0, | 
					
						
							|  |  |  | 	CACHE_TI_INSTRUCTION, | 
					
						
							| 
									
										
										
										
											2012-08-23 16:31:13 +02:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | struct cache_info { | 
					
						
							|  |  |  | 	unsigned char	    : 4; | 
					
						
							|  |  |  | 	unsigned char scope : 2; | 
					
						
							|  |  |  | 	unsigned char type  : 2; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #define CACHE_MAX_LEVEL 8
 | 
					
						
							|  |  |  | union cache_topology { | 
					
						
							|  |  |  | 	struct cache_info ci[CACHE_MAX_LEVEL]; | 
					
						
							|  |  |  | 	unsigned long long raw; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static const char * const cache_type_string[] = { | 
					
						
							| 
									
										
										
										
											2015-01-08 07:41:52 +00:00
										 |  |  | 	"", | 
					
						
							| 
									
										
										
										
											2012-08-23 16:31:13 +02:00
										 |  |  | 	"Instruction", | 
					
						
							| 
									
										
										
										
											2015-01-08 07:41:52 +00:00
										 |  |  | 	"Data", | 
					
						
							|  |  |  | 	"", | 
					
						
							| 
									
										
										
										
											2012-08-23 16:31:13 +02:00
										 |  |  | 	"Unified", | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-01-08 07:41:52 +00:00
										 |  |  | static const enum cache_type cache_type_map[] = { | 
					
						
							|  |  |  | 	[CTYPE_SEPARATE] = CACHE_TYPE_SEPARATE, | 
					
						
							|  |  |  | 	[CTYPE_DATA] = CACHE_TYPE_DATA, | 
					
						
							|  |  |  | 	[CTYPE_INSTRUCTION] = CACHE_TYPE_INST, | 
					
						
							|  |  |  | 	[CTYPE_UNIFIED] = CACHE_TYPE_UNIFIED, | 
					
						
							|  |  |  | }; | 
					
						
							| 
									
										
										
										
											2012-08-23 16:31:13 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-08-29 14:12:20 +02:00
										 |  |  | void show_cacheinfo(struct seq_file *m) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2015-02-09 12:54:16 +01:00
										 |  |  | 	struct cpu_cacheinfo *this_cpu_ci; | 
					
						
							| 
									
										
										
										
											2015-01-08 07:41:52 +00:00
										 |  |  | 	struct cacheinfo *cache; | 
					
						
							| 
									
										
										
										
											2015-02-09 12:54:16 +01:00
										 |  |  | 	int idx; | 
					
						
							| 
									
										
										
										
											2012-08-29 14:12:20 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-03-18 13:22:00 +01:00
										 |  |  | 	if (!test_facility(34)) | 
					
						
							|  |  |  | 		return; | 
					
						
							| 
									
										
										
										
											2015-02-09 12:54:16 +01:00
										 |  |  | 	get_online_cpus(); | 
					
						
							|  |  |  | 	this_cpu_ci = get_cpu_cacheinfo(cpumask_any(cpu_online_mask)); | 
					
						
							| 
									
										
										
										
											2015-01-08 07:41:52 +00:00
										 |  |  | 	for (idx = 0; idx < this_cpu_ci->num_leaves; idx++) { | 
					
						
							|  |  |  | 		cache = this_cpu_ci->info_list + idx; | 
					
						
							|  |  |  | 		seq_printf(m, "cache%-11d: ", idx); | 
					
						
							| 
									
										
										
										
											2012-08-29 14:12:20 +02:00
										 |  |  | 		seq_printf(m, "level=%d ", cache->level); | 
					
						
							|  |  |  | 		seq_printf(m, "type=%s ", cache_type_string[cache->type]); | 
					
						
							| 
									
										
										
										
											2015-01-08 07:41:52 +00:00
										 |  |  | 		seq_printf(m, "scope=%s ", | 
					
						
							|  |  |  | 			   cache->disable_sysfs ? "Shared" : "Private"); | 
					
						
							|  |  |  | 		seq_printf(m, "size=%dK ", cache->size >> 10); | 
					
						
							|  |  |  | 		seq_printf(m, "line_size=%u ", cache->coherency_line_size); | 
					
						
							|  |  |  | 		seq_printf(m, "associativity=%d", cache->ways_of_associativity); | 
					
						
							| 
									
										
										
										
											2012-08-29 14:12:20 +02:00
										 |  |  | 		seq_puts(m, "\n"); | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2015-02-09 12:54:16 +01:00
										 |  |  | 	put_online_cpus(); | 
					
						
							| 
									
										
										
										
											2012-08-29 14:12:20 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-01-08 07:41:52 +00:00
										 |  |  | static inline enum cache_type get_cache_type(struct cache_info *ci, int level) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	if (level >= CACHE_MAX_LEVEL) | 
					
						
							|  |  |  | 		return CACHE_TYPE_NOCACHE; | 
					
						
							|  |  |  | 	ci += level; | 
					
						
							|  |  |  | 	if (ci->scope != CACHE_SCOPE_SHARED && ci->scope != CACHE_SCOPE_PRIVATE) | 
					
						
							|  |  |  | 		return CACHE_TYPE_NOCACHE; | 
					
						
							|  |  |  | 	return cache_type_map[ci->type]; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-08-23 16:31:13 +02:00
										 |  |  | static inline unsigned long ecag(int ai, int li, int ti) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	unsigned long cmd, val; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	cmd = ai << 4 | li << 1 | ti; | 
					
						
							|  |  |  | 	asm volatile(".insn	rsy,0xeb000000004c,%0,0,0(%1)" /* ecag */ | 
					
						
							|  |  |  | 		     : "=d" (val) : "a" (cmd)); | 
					
						
							|  |  |  | 	return val; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-01-08 07:41:52 +00:00
										 |  |  | static void ci_leaf_init(struct cacheinfo *this_leaf, int private, | 
					
						
							| 
									
										
										
										
											2015-02-11 14:50:10 +01:00
										 |  |  | 			 enum cache_type type, unsigned int level, int cpu) | 
					
						
							| 
									
										
										
										
											2012-08-23 16:31:13 +02:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2015-01-08 07:41:52 +00:00
										 |  |  | 	int ti, num_sets; | 
					
						
							| 
									
										
										
										
											2012-08-23 16:31:13 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-01-08 07:41:52 +00:00
										 |  |  | 	if (type == CACHE_TYPE_INST) | 
					
						
							| 
									
										
										
										
											2012-10-18 13:13:41 +02:00
										 |  |  | 		ti = CACHE_TI_INSTRUCTION; | 
					
						
							|  |  |  | 	else | 
					
						
							|  |  |  | 		ti = CACHE_TI_UNIFIED; | 
					
						
							| 
									
										
										
										
											2015-01-08 07:41:52 +00:00
										 |  |  | 	this_leaf->level = level + 1; | 
					
						
							|  |  |  | 	this_leaf->type = type; | 
					
						
							|  |  |  | 	this_leaf->coherency_line_size = ecag(EXTRACT_LINE_SIZE, level, ti); | 
					
						
							| 
									
										
										
										
											2015-02-11 14:57:46 +01:00
										 |  |  | 	this_leaf->ways_of_associativity = ecag(EXTRACT_ASSOCIATIVITY, level, ti); | 
					
						
							| 
									
										
										
										
											2015-01-08 07:41:52 +00:00
										 |  |  | 	this_leaf->size = ecag(EXTRACT_SIZE, level, ti); | 
					
						
							|  |  |  | 	num_sets = this_leaf->size / this_leaf->coherency_line_size; | 
					
						
							|  |  |  | 	num_sets /= this_leaf->ways_of_associativity; | 
					
						
							|  |  |  | 	this_leaf->number_of_sets = num_sets; | 
					
						
							|  |  |  | 	cpumask_set_cpu(cpu, &this_leaf->shared_cpu_map); | 
					
						
							|  |  |  | 	if (!private) | 
					
						
							|  |  |  | 		this_leaf->disable_sysfs = true; | 
					
						
							| 
									
										
										
										
											2012-08-23 16:31:13 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-01-08 07:41:52 +00:00
										 |  |  | int init_cache_level(unsigned int cpu) | 
					
						
							| 
									
										
										
										
											2012-08-23 16:31:13 +02:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2015-01-08 07:41:52 +00:00
										 |  |  | 	struct cpu_cacheinfo *this_cpu_ci = get_cpu_cacheinfo(cpu); | 
					
						
							|  |  |  | 	unsigned int level = 0, leaves = 0; | 
					
						
							|  |  |  | 	union cache_topology ct; | 
					
						
							|  |  |  | 	enum cache_type ctype; | 
					
						
							| 
									
										
										
										
											2012-08-23 16:31:13 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
											  
											
												s390/cachinfo: add missing facility check to init_cache_level()
Stephen Powell reported the following crash on a z890 machine:
Kernel BUG at 00000000001219d0 [verbose debug info unavailable]
illegal operation: 0001 ilc:3 [#1] SMP
Krnl PSW : 0704e00180000000 00000000001219d0 (init_cache_level+0x38/0xe0)
	   R:0 T:1 IO:1 EX:1 Key:0 M:1 W:0 P:0 AS:3 CC:2 PM:0 EA:3
Krnl Code: 00000000001219c2: a7840056		brc	8,121a6e
	   00000000001219c6: a7190000		lghi	%r1,0
	  #00000000001219ca: eb101000004c	ecag	%r1,%r0,0(%r1)
	  >00000000001219d0: a7390000		lghi	%r3,0
	   00000000001219d4: e310f0a00024	stg	%r1,160(%r15)
	   00000000001219da: a7080000		lhi	%r0,0
	   00000000001219de: a7b9f000		lghi	%r11,-4096
	   00000000001219e2: c0a0002899d9	larl	%r10,634d94
Call Trace:
 [<0000000000478ee2>] detect_cache_attributes+0x2a/0x2b8
 [<000000000097c9b0>] cacheinfo_sysfs_init+0x60/0xc8
 [<00000000001001c0>] do_one_initcall+0x98/0x1c8
 [<000000000094fdc2>] kernel_init_freeable+0x212/0x2d8
 [<000000000062352e>] kernel_init+0x26/0x118
 [<000000000062fd2e>] kernel_thread_starter+0x6/0xc
The illegal operation was executed because of a missing facility check,
which should have made sure that the ECAG execution would only be executed
on machines which have the general-instructions-extension facility
installed.
Reported-and-tested-by: Stephen Powell <zlinuxman@wowway.com>
Cc: stable@vger.kernel.org # v4.0+
Signed-off-by: Heiko Carstens <heiko.carstens@de.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
											
										 
											2015-07-27 09:53:49 +02:00
										 |  |  | 	if (!test_facility(34)) | 
					
						
							|  |  |  | 		return -EOPNOTSUPP; | 
					
						
							| 
									
										
										
										
											2015-01-08 07:41:52 +00:00
										 |  |  | 	if (!this_cpu_ci) | 
					
						
							|  |  |  | 		return -EINVAL; | 
					
						
							|  |  |  | 	ct.raw = ecag(EXTRACT_TOPOLOGY, 0, 0); | 
					
						
							|  |  |  | 	do { | 
					
						
							|  |  |  | 		ctype = get_cache_type(&ct.ci[0], level); | 
					
						
							|  |  |  | 		if (ctype == CACHE_TYPE_NOCACHE) | 
					
						
							| 
									
										
										
										
											2012-08-29 14:12:20 +02:00
										 |  |  | 			break; | 
					
						
							| 
									
										
										
										
											2015-01-08 07:41:52 +00:00
										 |  |  | 		/* Separate instruction and data caches */ | 
					
						
							|  |  |  | 		leaves += (ctype == CACHE_TYPE_SEPARATE) ? 2 : 1; | 
					
						
							|  |  |  | 	} while (++level < CACHE_MAX_LEVEL); | 
					
						
							|  |  |  | 	this_cpu_ci->num_levels = level; | 
					
						
							|  |  |  | 	this_cpu_ci->num_leaves = leaves; | 
					
						
							|  |  |  | 	return 0; | 
					
						
							| 
									
										
										
										
											2012-08-23 16:31:13 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-01-08 07:41:52 +00:00
										 |  |  | int populate_cache_leaves(unsigned int cpu) | 
					
						
							| 
									
										
										
										
											2012-08-23 16:31:13 +02:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2015-02-11 14:57:46 +01:00
										 |  |  | 	struct cpu_cacheinfo *this_cpu_ci = get_cpu_cacheinfo(cpu); | 
					
						
							|  |  |  | 	struct cacheinfo *this_leaf = this_cpu_ci->info_list; | 
					
						
							| 
									
										
										
										
											2015-01-08 07:41:52 +00:00
										 |  |  | 	unsigned int level, idx, pvt; | 
					
						
							|  |  |  | 	union cache_topology ct; | 
					
						
							|  |  |  | 	enum cache_type ctype; | 
					
						
							| 
									
										
										
										
											2012-08-23 16:31:13 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-03-18 13:22:00 +01:00
										 |  |  | 	if (!test_facility(34)) | 
					
						
							|  |  |  | 		return -EOPNOTSUPP; | 
					
						
							| 
									
										
										
										
											2015-01-08 07:41:52 +00:00
										 |  |  | 	ct.raw = ecag(EXTRACT_TOPOLOGY, 0, 0); | 
					
						
							|  |  |  | 	for (idx = 0, level = 0; level < this_cpu_ci->num_levels && | 
					
						
							|  |  |  | 	     idx < this_cpu_ci->num_leaves; idx++, level++) { | 
					
						
							|  |  |  | 		if (!this_leaf) | 
					
						
							|  |  |  | 			return -EINVAL; | 
					
						
							|  |  |  | 		pvt = (ct.ci[level].scope == CACHE_SCOPE_PRIVATE) ? 1 : 0; | 
					
						
							|  |  |  | 		ctype = get_cache_type(&ct.ci[0], level); | 
					
						
							|  |  |  | 		if (ctype == CACHE_TYPE_SEPARATE) { | 
					
						
							| 
									
										
										
										
											2015-02-11 14:50:10 +01:00
										 |  |  | 			ci_leaf_init(this_leaf++, pvt, CACHE_TYPE_DATA, level, cpu); | 
					
						
							|  |  |  | 			ci_leaf_init(this_leaf++, pvt, CACHE_TYPE_INST, level, cpu); | 
					
						
							| 
									
										
										
										
											2015-01-08 07:41:52 +00:00
										 |  |  | 		} else { | 
					
						
							| 
									
										
										
										
											2015-02-11 14:50:10 +01:00
										 |  |  | 			ci_leaf_init(this_leaf++, pvt, ctype, level, cpu); | 
					
						
							| 
									
										
										
										
											2015-01-08 07:41:52 +00:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2012-08-23 16:31:13 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } |