• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #include <stdbool.h>
2 #include <stdint.h>
3 #include <stdlib.h>
4 #include <stddef.h>
5 #include <string.h>
6 
7 #include <linux/api.h>
8 #include <x86/linux/api.h>
9 #include <cpuinfo/log.h>
10 
11 /*
12  * Size, in chars, of the on-stack buffer used for parsing lines of /proc/cpuinfo.
13  * This is also the limit on the length of a single line.
14  */
15 #define BUFFER_SIZE 2048
16 
17 
parse_processor_number(const char * processor_start,const char * processor_end)18 static uint32_t parse_processor_number(
19 	const char* processor_start,
20 	const char* processor_end)
21 {
22 	const size_t processor_length = (size_t) (processor_end - processor_start);
23 
24 	if (processor_length == 0) {
25 		cpuinfo_log_warning("Processor number in /proc/cpuinfo is ignored: string is empty");
26 		return 0;
27 	}
28 
29 	uint32_t processor_number = 0;
30 	for (const char* digit_ptr = processor_start; digit_ptr != processor_end; digit_ptr++) {
31 		const uint32_t digit = (uint32_t) (*digit_ptr - '0');
32 		if (digit > 10) {
33 			cpuinfo_log_warning("non-decimal suffix %.*s in /proc/cpuinfo processor number is ignored",
34 				(int) (processor_end - digit_ptr), digit_ptr);
35 			break;
36 		}
37 
38 		processor_number = processor_number * 10 + digit;
39 	}
40 
41 	return processor_number;
42 }
43 
44 /*
45  * Decode APIC ID reported by Linux kernel for x86/x86-64 architecture.
46  * Example of APIC ID reported in /proc/cpuinfo:
47  *
48  *		apicid		: 2
49  */
parse_apic_id(const char * apic_start,const char * apic_end,struct cpuinfo_x86_linux_processor processor[restrict static1])50 static void parse_apic_id(
51 	const char* apic_start,
52 	const char* apic_end,
53 	struct cpuinfo_x86_linux_processor processor[restrict static 1])
54 {
55 	uint32_t apic_id = 0;
56 	for (const char* digit_ptr = apic_start; digit_ptr != apic_end; digit_ptr++) {
57 		const uint32_t digit = *digit_ptr - '0';
58 		if (digit >= 10) {
59 			cpuinfo_log_warning("APIC ID %.*s in /proc/cpuinfo is ignored due to unexpected non-digit character '%c' at offset %zu",
60 				(int) (apic_end - apic_start), apic_start,
61 				*digit_ptr, (size_t) (digit_ptr - apic_start));
62 			return;
63 		}
64 
65 		apic_id = apic_id * 10 + digit;
66 	}
67 
68 	processor->apic_id = apic_id;
69 	processor->flags |= CPUINFO_LINUX_FLAG_APIC_ID;
70 }
71 
72 struct proc_cpuinfo_parser_state {
73 	uint32_t processor_index;
74 	uint32_t max_processors_count;
75 	struct cpuinfo_x86_linux_processor* processors;
76 	struct cpuinfo_x86_linux_processor dummy_processor;
77 };
78 
79 /*
80  *	Decode a single line of /proc/cpuinfo information.
81  *	Lines have format <words-with-spaces>[ ]*:[ ]<space-separated words>
82  */
parse_line(const char * line_start,const char * line_end,struct proc_cpuinfo_parser_state state[restrict static1],uint64_t line_number)83 static bool parse_line(
84 	const char* line_start,
85 	const char* line_end,
86 	struct proc_cpuinfo_parser_state state[restrict static 1],
87 	uint64_t line_number)
88 {
89 	/* Empty line. Skip. */
90 	if (line_start == line_end) {
91 		return true;
92 	}
93 
94 	/* Search for ':' on the line. */
95 	const char* separator = line_start;
96 	for (; separator != line_end; separator++) {
97 		if (*separator == ':') {
98 			break;
99 		}
100 	}
101 	/* Skip line if no ':' separator was found. */
102 	if (separator == line_end) {
103 		cpuinfo_log_info("Line %.*s in /proc/cpuinfo is ignored: key/value separator ':' not found",
104 			(int) (line_end - line_start), line_start);
105 		return true;
106 	}
107 
108 	/* Skip trailing spaces in key part. */
109 	const char* key_end = separator;
110 	for (; key_end != line_start; key_end--) {
111 		if (key_end[-1] != ' ' && key_end[-1] != '\t') {
112 			break;
113 		}
114 	}
115 	/* Skip line if key contains nothing but spaces. */
116 	if (key_end == line_start) {
117 		cpuinfo_log_info("Line %.*s in /proc/cpuinfo is ignored: key contains only spaces",
118 			(int) (line_end - line_start), line_start);
119 		return true;
120 	}
121 
122 	/* Skip leading spaces in value part. */
123 	const char* value_start = separator + 1;
124 	for (; value_start != line_end; value_start++) {
125 		if (*value_start != ' ') {
126 			break;
127 		}
128 	}
129 	/* Value part contains nothing but spaces. Skip line. */
130 	if (value_start == line_end) {
131 		cpuinfo_log_info("Line %.*s in /proc/cpuinfo is ignored: value contains only spaces",
132 			(int) (line_end - line_start), line_start);
133 		return true;
134 	}
135 
136 	/* Skip trailing spaces in value part (if any) */
137 	const char* value_end = line_end;
138 	for (; value_end != value_start; value_end--) {
139 		if (value_end[-1] != ' ') {
140 			break;
141 		}
142 	}
143 
144 	const uint32_t processor_index      = state->processor_index;
145 	const uint32_t max_processors_count = state->max_processors_count;
146 	struct cpuinfo_x86_linux_processor* processors = state->processors;
147 	struct cpuinfo_x86_linux_processor* processor  = &state->dummy_processor;
148 	if (processor_index < max_processors_count) {
149 		processor = &processors[processor_index];
150 	}
151 
152 	const size_t key_length = key_end - line_start;
153 	switch (key_length) {
154 		case 6:
155 			if (memcmp(line_start, "apicid", key_length) == 0) {
156 				parse_apic_id(value_start, value_end, processor);
157 			} else {
158 				goto unknown;
159 			}
160 			break;
161 		case 9:
162 			if (memcmp(line_start, "processor", key_length) == 0) {
163 				const uint32_t new_processor_index = parse_processor_number(value_start, value_end);
164 				if (new_processor_index < processor_index) {
165 					/* Strange: decreasing processor number */
166 					cpuinfo_log_warning(
167 						"unexpectedly low processor number %"PRIu32" following processor %"PRIu32" in /proc/cpuinfo",
168 						new_processor_index, processor_index);
169 				} else if (new_processor_index > processor_index + 1) {
170 					/* Strange, but common: skipped processor $(processor_index + 1) */
171 					cpuinfo_log_info(
172 						"unexpectedly high processor number %"PRIu32" following processor %"PRIu32" in /proc/cpuinfo",
173 						new_processor_index, processor_index);
174 				}
175 				if (new_processor_index >= max_processors_count) {
176 					/* Log and ignore processor */
177 					cpuinfo_log_warning("processor %"PRIu32" in /proc/cpuinfo is ignored: index exceeds system limit %"PRIu32,
178 						new_processor_index, max_processors_count - 1);
179 				} else {
180 					processors[new_processor_index].flags |= CPUINFO_LINUX_FLAG_PROC_CPUINFO;
181 				}
182 				state->processor_index = new_processor_index;
183 				return true;
184 			} else {
185 				goto unknown;
186 			}
187 			break;
188 		default:
189 		unknown:
190 			cpuinfo_log_debug("unknown /proc/cpuinfo key: %.*s", (int) key_length, line_start);
191 
192 	}
193 	return true;
194 }
195 
cpuinfo_x86_linux_parse_proc_cpuinfo(uint32_t max_processors_count,struct cpuinfo_x86_linux_processor processors[restrict static max_processors_count])196 bool cpuinfo_x86_linux_parse_proc_cpuinfo(
197 	uint32_t max_processors_count,
198 	struct cpuinfo_x86_linux_processor processors[restrict static max_processors_count])
199 {
200 	struct proc_cpuinfo_parser_state state = {
201 		.processor_index = 0,
202 		.max_processors_count = max_processors_count,
203 		.processors = processors,
204 	};
205 	return cpuinfo_linux_parse_multiline_file("/proc/cpuinfo", BUFFER_SIZE,
206 		(cpuinfo_line_callback) parse_line, &state);
207 }
208