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