]>
Commit | Line | Data |
---|---|---|
fb2627ce OO |
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 "c.h" | |
31 | #include "pathnames.h" | |
32 | #include "all-io.h" | |
33 | #include "lscpu.h" | |
34 | ||
73be2127 KZ |
35 | #define _PATH_SYS_DMI "/sys/firmware/dmi/tables/DMI" |
36 | ||
fb2627ce OO |
37 | #define WORD(x) (uint16_t)(*(const uint16_t *)(x)) |
38 | #define DWORD(x) (uint32_t)(*(const uint32_t *)(x)) | |
39 | ||
40 | struct dmi_header | |
41 | { | |
42 | uint8_t type; | |
43 | uint8_t length; | |
44 | uint16_t handle; | |
45 | uint8_t *data; | |
46 | }; | |
47 | ||
48 | static int checksum(const uint8_t *buf, size_t len) | |
49 | { | |
50 | uint8_t sum = 0; | |
51 | size_t a; | |
52 | ||
53 | for (a = 0; a < len; a++) | |
54 | sum += buf[a]; | |
55 | return (sum == 0); | |
56 | } | |
57 | ||
58 | static void *get_mem_chunk(size_t base, size_t len, const char *devmem) | |
59 | { | |
dbbf8395 | 60 | void *p = NULL; |
fb2627ce OO |
61 | int fd; |
62 | ||
dbbf8395 | 63 | if ((fd = open(devmem, O_RDONLY)) < 0) |
fb2627ce | 64 | return NULL; |
dbbf8395 KZ |
65 | |
66 | if (!(p = malloc(len))) | |
67 | goto nothing; | |
68 | if (lseek(fd, base, SEEK_SET) == -1) | |
69 | goto nothing; | |
70 | if (read_all(fd, p, len) == -1) | |
71 | goto nothing; | |
fb2627ce OO |
72 | |
73 | close(fd); | |
74 | return p; | |
dbbf8395 KZ |
75 | |
76 | nothing: | |
77 | free(p); | |
78 | close(fd); | |
79 | return NULL; | |
fb2627ce OO |
80 | } |
81 | ||
82 | static void to_dmi_header(struct dmi_header *h, uint8_t *data) | |
83 | { | |
84 | h->type = data[0]; | |
85 | h->length = data[1]; | |
86 | h->handle = WORD(data + 2); | |
87 | h->data = data; | |
88 | } | |
89 | ||
90 | static char *dmi_string(const struct dmi_header *dm, uint8_t s) | |
91 | { | |
92 | char *bp = (char *)dm->data; | |
93 | ||
94 | if (s == 0) | |
95 | return NULL; | |
96 | ||
97 | bp += dm->length; | |
98 | while (s > 1 && *bp) | |
99 | { | |
100 | bp += strlen(bp); | |
101 | bp++; | |
102 | s--; | |
103 | } | |
104 | ||
105 | if (!*bp) | |
106 | return NULL; | |
107 | ||
108 | return bp; | |
109 | } | |
110 | ||
111 | static int hypervisor_from_dmi_table(uint32_t base, uint16_t len, | |
112 | uint16_t num, const char *devmem) | |
113 | { | |
dbbf8395 | 114 | uint8_t *buf; |
fb2627ce OO |
115 | uint8_t *data; |
116 | int i = 0; | |
117 | char *vendor = NULL; | |
118 | char *product = NULL; | |
119 | char *manufacturer = NULL; | |
dbbf8395 | 120 | int rc = HYPER_NONE; |
fb2627ce | 121 | |
dbbf8395 KZ |
122 | data = buf = get_mem_chunk(base, len, devmem); |
123 | if (!buf) | |
124 | goto done; | |
fb2627ce OO |
125 | |
126 | /* 4 is the length of an SMBIOS structure header */ | |
127 | while (i < num && data + 4 <= buf + len) { | |
128 | uint8_t *next; | |
129 | struct dmi_header h; | |
130 | ||
131 | to_dmi_header(&h, data); | |
132 | ||
133 | /* | |
134 | * If a short entry is found (less than 4 bytes), not only it | |
135 | * is invalid, but we cannot reliably locate the next entry. | |
136 | * Better stop at this point. | |
137 | */ | |
dbbf8395 KZ |
138 | if (h.length < 4) |
139 | goto done; | |
fb2627ce OO |
140 | |
141 | /* look for the next handle */ | |
142 | next = data + h.length; | |
143 | while (next - buf + 1 < len && (next[0] != 0 || next[1] != 0)) | |
144 | next++; | |
145 | next += 2; | |
146 | switch (h.type) { | |
147 | case 0: | |
148 | vendor = dmi_string(&h, data[0x04]); | |
149 | break; | |
150 | case 1: | |
151 | manufacturer = dmi_string(&h, data[0x04]); | |
152 | product = dmi_string(&h, data[0x05]); | |
153 | break; | |
154 | default: | |
155 | break; | |
156 | } | |
157 | ||
158 | data = next; | |
159 | i++; | |
160 | } | |
bbff0890 | 161 | if (manufacturer && !strcmp(manufacturer, "innotek GmbH")) |
dbbf8395 | 162 | rc = HYPER_INNOTEK; |
bbff0890 KZ |
163 | else if (manufacturer && strstr(manufacturer, "HITACHI") && |
164 | product && strstr(product, "LPAR")) | |
dbbf8395 | 165 | rc = HYPER_HITACHI; |
73f2bec5 | 166 | else if (vendor && !strcmp(vendor, "Parallels")) |
dbbf8395 KZ |
167 | rc = HYPER_PARALLELS; |
168 | done: | |
fb2627ce | 169 | free(buf); |
dbbf8395 | 170 | return rc; |
fb2627ce OO |
171 | } |
172 | ||
60cb2c37 | 173 | #if defined(__x86_64__) || defined(__i386__) |
fb2627ce OO |
174 | static int hypervisor_decode_legacy(uint8_t *buf, const char *devmem) |
175 | { | |
176 | if (!checksum(buf, 0x0F)) | |
c972852b | 177 | return -1; |
fb2627ce OO |
178 | |
179 | return hypervisor_from_dmi_table(DWORD(buf + 0x08), WORD(buf + 0x06), | |
180 | WORD(buf + 0x0C), | |
181 | devmem); | |
182 | } | |
60cb2c37 | 183 | #endif |
fb2627ce OO |
184 | |
185 | static int hypervisor_decode_smbios(uint8_t *buf, const char *devmem) | |
186 | { | |
187 | if (!checksum(buf, buf[0x05]) | |
188 | || memcmp(buf + 0x10, "_DMI_", 5) != 0 | |
189 | || !checksum(buf + 0x10, 0x0F)) | |
190 | return -1; | |
191 | ||
192 | return hypervisor_from_dmi_table(DWORD(buf + 0x18), WORD(buf + 0x16), | |
193 | WORD(buf + 0x1C), | |
194 | devmem); | |
195 | } | |
196 | ||
92a6392c AB |
197 | static int hypervisor_decode_sysfw(void) |
198 | { | |
73be2127 | 199 | static char const sys_fw_dmi_tables[] = _PATH_SYS_DMI; |
92a6392c AB |
200 | struct stat st; |
201 | ||
202 | if (stat(sys_fw_dmi_tables, &st)) | |
203 | return -1; | |
204 | ||
205 | return hypervisor_from_dmi_table(0, st.st_size, st.st_size / 4, | |
206 | sys_fw_dmi_tables); | |
207 | } | |
208 | ||
fb2627ce OO |
209 | /* |
210 | * Probe for EFI interface | |
211 | */ | |
212 | #define EFI_NOT_FOUND (-1) | |
213 | #define EFI_NO_SMBIOS (-2) | |
214 | static int address_from_efi(size_t *address) | |
215 | { | |
216 | FILE *tab; | |
217 | char linebuf[64]; | |
218 | int ret; | |
219 | ||
220 | *address = 0; /* Prevent compiler warning */ | |
221 | ||
222 | /* | |
223 | * Linux up to 2.6.6: /proc/efi/systab | |
224 | * Linux 2.6.7 and up: /sys/firmware/efi/systab | |
225 | */ | |
226 | if (!(tab = fopen("/sys/firmware/efi/systab", "r")) && | |
227 | !(tab = fopen("/proc/efi/systab", "r"))) | |
228 | return EFI_NOT_FOUND; /* No EFI interface */ | |
229 | ||
230 | ret = EFI_NO_SMBIOS; | |
231 | while ((fgets(linebuf, sizeof(linebuf) - 1, tab)) != NULL) { | |
232 | char *addrp = strchr(linebuf, '='); | |
6d707c1d KZ |
233 | if (!addrp) |
234 | continue; | |
fb2627ce OO |
235 | *(addrp++) = '\0'; |
236 | if (strcmp(linebuf, "SMBIOS") == 0) { | |
237 | *address = strtoul(addrp, NULL, 0); | |
238 | ret = 0; | |
239 | break; | |
240 | } | |
241 | } | |
242 | ||
243 | fclose(tab); | |
244 | return ret; | |
245 | } | |
246 | ||
247 | int read_hypervisor_dmi(void) | |
248 | { | |
dbbf8395 | 249 | int rc = HYPER_NONE; |
fb2627ce OO |
250 | uint8_t *buf = NULL; |
251 | size_t fp = 0; | |
252 | ||
253 | if (sizeof(uint8_t) != 1 | |
254 | || sizeof(uint16_t) != 2 | |
255 | || sizeof(uint32_t) != 4 | |
256 | || '\0' != 0) | |
c972852b | 257 | goto done; |
fb2627ce | 258 | |
c972852b KZ |
259 | /* -1 : no DMI in /sys, |
260 | * 0 : DMI exist, nothing detected (HYPER_NONE) | |
261 | * >0 : hypervisor detected | |
262 | */ | |
92a6392c | 263 | rc = hypervisor_decode_sysfw(); |
c972852b KZ |
264 | if (rc >= HYPER_NONE) |
265 | goto done; | |
92a6392c | 266 | |
fb2627ce OO |
267 | /* First try EFI (ia64, Intel-based Mac) */ |
268 | switch (address_from_efi(&fp)) { | |
269 | case EFI_NOT_FOUND: | |
270 | goto memory_scan; | |
271 | case EFI_NO_SMBIOS: | |
dbbf8395 | 272 | goto done; |
fb2627ce OO |
273 | } |
274 | ||
275 | buf = get_mem_chunk(fp, 0x20, _PATH_DEV_MEM); | |
276 | if (!buf) | |
dbbf8395 | 277 | goto done; |
fb2627ce | 278 | |
dbbf8395 | 279 | rc = hypervisor_decode_smbios(buf, _PATH_DEV_MEM); |
c972852b | 280 | if (rc >= HYPER_NONE) |
fb2627ce | 281 | goto done; |
c972852b | 282 | |
dbbf8395 | 283 | free(buf); |
4e6604e5 | 284 | buf = NULL; |
fb2627ce | 285 | memory_scan: |
6f7234f6 | 286 | #if defined(__x86_64__) || defined(__i386__) |
fb2627ce OO |
287 | /* Fallback to memory scan (x86, x86_64) */ |
288 | buf = get_mem_chunk(0xF0000, 0x10000, _PATH_DEV_MEM); | |
289 | if (!buf) | |
dbbf8395 | 290 | goto done; |
fb2627ce OO |
291 | |
292 | for (fp = 0; fp <= 0xFFF0; fp += 16) { | |
293 | if (memcmp(buf + fp, "_SM_", 4) == 0 && fp <= 0xFFE0) { | |
dbbf8395 | 294 | rc = hypervisor_decode_smbios(buf + fp, _PATH_DEV_MEM); |
c972852b | 295 | if (rc < 0) |
fb2627ce OO |
296 | fp += 16; |
297 | ||
298 | } else if (memcmp(buf + fp, "_DMI_", 5) == 0) | |
dbbf8395 KZ |
299 | rc = hypervisor_decode_legacy(buf + fp, _PATH_DEV_MEM); |
300 | ||
c972852b | 301 | if (rc >= HYPER_NONE) |
dbbf8395 | 302 | break; |
fb2627ce | 303 | } |
6f7234f6 | 304 | #endif |
fb2627ce OO |
305 | done: |
306 | free(buf); | |
c972852b | 307 | return rc < 0 ? HYPER_NONE : rc; |
fb2627ce | 308 | } |