]> git.ipfire.org Git - thirdparty/util-linux.git/blob - sys-utils/lscpu-dmi.c
libfdisk: (gpt) add functionality to move backup header
[thirdparty/util-linux.git] / sys-utils / lscpu-dmi.c
1 /*
2 * lscpu-dmi - Module to parse SMBIOS information
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it would be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 *
18 * Code originally taken from the dmidecode utility and slightly rewritten
19 * to suite the needs of lscpu
20 */
21 #include <errno.h>
22 #include <stdlib.h>
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #include <fcntl.h>
26 #include <unistd.h>
27 #include <string.h>
28 #include <stdio.h>
29
30 #include "lscpu.h"
31
32 #define _PATH_SYS_DMI "/sys/firmware/dmi/tables/DMI"
33
34 #define WORD(x) (uint16_t)(*(const uint16_t *)(x))
35 #define DWORD(x) (uint32_t)(*(const uint32_t *)(x))
36
37 struct dmi_header
38 {
39 uint8_t type;
40 uint8_t length;
41 uint16_t handle;
42 uint8_t *data;
43 };
44
45 static void *get_mem_chunk(size_t base, size_t len, const char *devmem)
46 {
47 void *p = NULL;
48 int fd;
49
50 if ((fd = open(devmem, O_RDONLY)) < 0)
51 return NULL;
52
53 if (!(p = malloc(len)))
54 goto nothing;
55 if (lseek(fd, base, SEEK_SET) == -1)
56 goto nothing;
57 if (read_all(fd, p, len) == -1)
58 goto nothing;
59
60 close(fd);
61 return p;
62
63 nothing:
64 free(p);
65 close(fd);
66 return NULL;
67 }
68
69 static void to_dmi_header(struct dmi_header *h, uint8_t *data)
70 {
71 h->type = data[0];
72 h->length = data[1];
73 memcpy(&h->handle, data + 2, sizeof(h->handle));
74 h->data = data;
75 }
76
77 static char *dmi_string(const struct dmi_header *dm, uint8_t s)
78 {
79 char *bp = (char *)dm->data;
80
81 if (s == 0)
82 return NULL;
83
84 bp += dm->length;
85 while (s > 1 && *bp)
86 {
87 bp += strlen(bp);
88 bp++;
89 s--;
90 }
91
92 if (!*bp)
93 return NULL;
94
95 return bp;
96 }
97
98 static int hypervisor_from_dmi_table(uint32_t base, uint16_t len,
99 uint16_t num, const char *devmem)
100 {
101 uint8_t *buf;
102 uint8_t *data;
103 int i = 0;
104 char *vendor = NULL;
105 char *product = NULL;
106 char *manufacturer = NULL;
107 int rc = HYPER_NONE;
108
109 data = buf = get_mem_chunk(base, len, devmem);
110 if (!buf)
111 goto done;
112
113 /* 4 is the length of an SMBIOS structure header */
114 while (i < num && data + 4 <= buf + len) {
115 uint8_t *next;
116 struct dmi_header h;
117
118 to_dmi_header(&h, data);
119
120 /*
121 * If a short entry is found (less than 4 bytes), not only it
122 * is invalid, but we cannot reliably locate the next entry.
123 * Better stop at this point.
124 */
125 if (h.length < 4)
126 goto done;
127
128 /* look for the next handle */
129 next = data + h.length;
130 while (next - buf + 1 < len && (next[0] != 0 || next[1] != 0))
131 next++;
132 next += 2;
133 switch (h.type) {
134 case 0:
135 vendor = dmi_string(&h, data[0x04]);
136 break;
137 case 1:
138 manufacturer = dmi_string(&h, data[0x04]);
139 product = dmi_string(&h, data[0x05]);
140 break;
141 default:
142 break;
143 }
144
145 data = next;
146 i++;
147 }
148 if (manufacturer && !strcmp(manufacturer, "innotek GmbH"))
149 rc = HYPER_INNOTEK;
150 else if (manufacturer && strstr(manufacturer, "HITACHI") &&
151 product && strstr(product, "LPAR"))
152 rc = HYPER_HITACHI;
153 else if (vendor && !strcmp(vendor, "Parallels"))
154 rc = HYPER_PARALLELS;
155 done:
156 free(buf);
157 return rc;
158 }
159
160 static int checksum(const uint8_t *buf, size_t len)
161 {
162 uint8_t sum = 0;
163 size_t a;
164
165 for (a = 0; a < len; a++)
166 sum += buf[a];
167 return (sum == 0);
168 }
169
170 #if defined(__x86_64__) || defined(__i386__)
171 static int hypervisor_decode_legacy(uint8_t *buf, const char *devmem)
172 {
173 if (!checksum(buf, 0x0F))
174 return -1;
175
176 return hypervisor_from_dmi_table(DWORD(buf + 0x08), WORD(buf + 0x06),
177 WORD(buf + 0x0C),
178 devmem);
179 }
180 #endif
181
182 static int hypervisor_decode_smbios(uint8_t *buf, const char *devmem)
183 {
184 if (!checksum(buf, buf[0x05])
185 || memcmp(buf + 0x10, "_DMI_", 5) != 0
186 || !checksum(buf + 0x10, 0x0F))
187 return -1;
188
189 return hypervisor_from_dmi_table(DWORD(buf + 0x18), WORD(buf + 0x16),
190 WORD(buf + 0x1C),
191 devmem);
192 }
193
194 /*
195 * Probe for EFI interface
196 */
197 #define EFI_NOT_FOUND (-1)
198 #define EFI_NO_SMBIOS (-2)
199 static int address_from_efi(size_t *address)
200 {
201 FILE *tab;
202 char linebuf[64];
203 int ret;
204
205 *address = 0; /* Prevent compiler warning */
206
207 /*
208 * Linux up to 2.6.6: /proc/efi/systab
209 * Linux 2.6.7 and up: /sys/firmware/efi/systab
210 */
211 if (!(tab = fopen("/sys/firmware/efi/systab", "r")) &&
212 !(tab = fopen("/proc/efi/systab", "r")))
213 return EFI_NOT_FOUND; /* No EFI interface */
214
215 ret = EFI_NO_SMBIOS;
216 while ((fgets(linebuf, sizeof(linebuf) - 1, tab)) != NULL) {
217 char *addrp = strchr(linebuf, '=');
218 if (!addrp)
219 continue;
220 *(addrp++) = '\0';
221 if (strcmp(linebuf, "SMBIOS") == 0) {
222 *address = strtoul(addrp, NULL, 0);
223 ret = 0;
224 break;
225 }
226 }
227
228 fclose(tab);
229 return ret;
230 }
231
232 static int read_hypervisor_dmi_from_devmem(void)
233 {
234 int rc = HYPER_NONE;
235 uint8_t *buf = NULL;
236 size_t fp = 0;
237
238 /* First try EFI (ia64, Intel-based Mac) */
239 switch (address_from_efi(&fp)) {
240 case EFI_NOT_FOUND:
241 goto memory_scan;
242 case EFI_NO_SMBIOS:
243 goto done;
244 }
245
246 buf = get_mem_chunk(fp, 0x20, _PATH_DEV_MEM);
247 if (!buf)
248 goto done;
249
250 rc = hypervisor_decode_smbios(buf, _PATH_DEV_MEM);
251 if (rc >= HYPER_NONE)
252 goto done;
253
254 free(buf);
255 buf = NULL;
256 memory_scan:
257 #if defined(__x86_64__) || defined(__i386__)
258 /* Fallback to memory scan (x86, x86_64) */
259 buf = get_mem_chunk(0xF0000, 0x10000, _PATH_DEV_MEM);
260 if (!buf)
261 goto done;
262
263 for (fp = 0; fp <= 0xFFF0; fp += 16) {
264 if (memcmp(buf + fp, "_SM_", 4) == 0 && fp <= 0xFFE0) {
265 rc = hypervisor_decode_smbios(buf + fp, _PATH_DEV_MEM);
266 if (rc < 0)
267 fp += 16;
268
269 } else if (memcmp(buf + fp, "_DMI_", 5) == 0)
270 rc = hypervisor_decode_legacy(buf + fp, _PATH_DEV_MEM);
271
272 if (rc >= HYPER_NONE)
273 break;
274 }
275 #endif
276 done:
277 free(buf);
278 return rc;
279 }
280
281 static int read_hypervisor_dmi_from_sysfw(void)
282 {
283 static char const sys_fw_dmi_tables[] = _PATH_SYS_DMI;
284 struct stat st;
285
286 if (stat(sys_fw_dmi_tables, &st))
287 return -1;
288
289 return hypervisor_from_dmi_table(0, st.st_size, st.st_size / 4,
290 sys_fw_dmi_tables);
291 }
292
293 int read_hypervisor_dmi(void)
294 {
295 int rc;
296
297 if (sizeof(uint8_t) != 1
298 || sizeof(uint16_t) != 2
299 || sizeof(uint32_t) != 4
300 || '\0' != 0)
301 return HYPER_NONE;
302
303 /* -1 : no DMI in /sys,
304 * 0 : DMI exist, nothing detected (HYPER_NONE)
305 * >0 : hypervisor detected
306 */
307 rc = read_hypervisor_dmi_from_sysfw();
308 if (rc < 0)
309 rc = read_hypervisor_dmi_from_devmem();
310
311 return rc < 0 ? HYPER_NONE : rc;
312 }