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