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