1 /*
2  * Copyright © 2015-2018 Intel
3  * Copyright © 2015-2020 Inria.  All rights reserved.
4  * See COPYING in top-level directory.
5  */
6 
7 #include "private/autogen/config.h"
8 
9 #include <stdio.h>
10 #include <stdint.h>
11 #include <dirent.h>
12 #include <errno.h>
13 #include <fcntl.h>
14 #ifdef HAVE_UNISTD_H
15 #include <unistd.h>
16 #endif
17 #include <stdlib.h>
18 #include <string.h>
19 #include <sys/stat.h>
20 #include <sys/types.h>
21 
22 #define KERNEL_SMBIOS_SYSFS "/sys/firmware/dmi/entries"
23 
24 /* official strings, found at least in Intel S7200AP boards (also used by Cray) and some SuperMicro boards */
25 #define KNL_INTEL_GROUP_STRING "Group: Knights Landing Information"
26 #define KNM_INTEL_GROUP_STRING "Group: Knights Mill Information"
27 /* unexpected strings, found at least in Dell C6320p BIOS <=1.4.1 */
28 #define KNL_DELL_GROUP_STRING "Knights Landing Association"
29 
30 static const char *allowed_group_strings[] =
31 {
32     KNL_INTEL_GROUP_STRING,
33     KNM_INTEL_GROUP_STRING,
34     KNL_DELL_GROUP_STRING
35 };
36 
37 /* Header is common part of all SMBIOS entries */
38 struct smbios_header
39 {
40     uint8_t type;
41     uint8_t length;
42     uint16_t handle;
43 };
44 
45 struct smbios_group
46 {
47     uint8_t group_name;
48 };
49 
50 /* This structrures are padded by compiler
51  * So we hardcode size of the struct and use it
52  * instead of sizeof() */
53 #define GROUP_ENTRY_SIZE 3
54 struct smbios_group_entry
55 {
56     uint8_t type;
57     uint16_t handle;
58 };
59 
60 /* KNL header is similar as SMBIOS header
61  * decided to add it for readability */
62 #define SMBIOS_KNL_HEADER_SIZE 7
63 struct knl_smbios_header
64 {
65     uint8_t type;
66     uint8_t length;
67     uint16_t handle;
68     uint16_t member_id;
69     uint8_t member_name;
70 };
71 
72 /* general info data */
73 #define SMBIOS_KNL_GENERAL_INFO_SIZE 5
74 struct knl_general_info
75 {
76     uint8_t supp_cluster_mode;
77     uint8_t cluster_mode;
78     uint8_t supp_memory_mode;
79     uint8_t memory_mode;
80     uint8_t cache_info;
81 };
82 
83 /* memory info */
84 #define SMBIOS_KNL_EDC_INFO_SIZE 9
85 struct knl_edc_info
86 {
87     uint8_t mcdram_present;
88     uint8_t mcdram_enabled;
89     uint8_t allowed_channels;
90     uint8_t reserved[4];
91     uint8_t mcdram_info_size;
92     uint8_t mcdram_info_count;
93 };
94 
95 /* mcdram controller structure */
96 struct knl_mcdram_info {
97     uint32_t status;
98     uint8_t controller;
99     uint8_t channel;
100     uint16_t size64MB;
101     uint8_t product_revision;
102     uint8_t fwmajor_revision;
103     uint8_t fwminor_revision;
104     uint8_t fwpatch_revision;
105 };
106 
107 /* internal data */
108 struct parser_data
109 {
110     uint64_t mcdram_regular;
111     uint64_t mcdram_cache;
112     int cluster_mode;
113     int memory_mode;
114     int cache_info;
115     int type_count;
116     int knl_types[64];
117 };
118 
119 enum cluster_mode
120 {
121     QUADRANT = 1,
122     HEMISPHERE = 2,
123     SNC4 = 4,
124     SNC2 = 8,
125     ALL2ALL = 16
126 };
127 
128 enum memory_mode
129 {
130     CACHE = 1,
131     FLAT = 2,
132     HYBRID = 4
133 };
134 
135 enum hybrid_cache
136 {
137     H25 = 1,
138     H50 = 2,
139     H100 = 4 /* Incorrect but possible value */
140 };
141 
get_file_buffer(const char * file,char * buffer,int size)142 static int get_file_buffer(const char *file, char *buffer, int size)
143 {
144     FILE *f;
145 
146     printf("  File = %s\n", file);
147 
148     if (!buffer) {
149         fprintf(stderr, "Unable to allocate buffer\n");
150         return 0;
151     }
152 
153     f = fopen(file, "rb");
154     if (!f) {
155         fprintf(stderr, "Unable to open %s (%s)\n", file, strerror(errno));
156         return 0;
157     }
158 
159     size = fread(buffer, 1, size, f);
160     if (size == 0) {
161         fprintf(stderr, "Unable to read file\n");
162         fclose(f);
163         return 0;
164     }
165     printf("    Read %d bytes\n", size);
166 
167     fclose(f);
168     return size;
169 }
170 
check_entry(struct smbios_header * h,const char * end,const char * query)171 static int check_entry(struct smbios_header *h, const char *end, const char *query)
172 {
173     char *group_strings = (char*)h + h->length;
174     do {
175         int len = strlen(group_strings);
176         /* SMBIOS string entries end with "\0\0"
177          * if length is 0 break and return
178          * */
179         if (len == 0)
180             break;
181 
182         printf("  Looking for \"%s\" in group string \"%s\"\n", query, group_strings);
183         if (!strncmp(group_strings, query, len))
184             return 1;
185 
186         group_strings += len;
187     } while(group_strings < end);
188 
189     return 0;
190 }
191 
is_phi_group(struct smbios_header * h,const char * end)192 static int is_phi_group(struct smbios_header *h, const char *end)
193 {
194     unsigned i;
195     if (h->type != 14) {
196         fprintf(stderr, "SMBIOS table is not group table\n");
197         return 0;
198     }
199 
200     for (i = 0; i < sizeof(allowed_group_strings)/sizeof(char*); i++) {
201         if (check_entry(h, end, allowed_group_strings[i]))
202             return 1;
203     }
204 
205     return 0;
206 }
207 
208 #define KNL_MEMBER_ID_GENERAL 0x1
209 #define KNL_MEMBER_ID_EDC 0x2
210 
211 #define PATH_SIZE 512
212 #define SMBIOS_FILE_BUF_SIZE 4096
213 #define KNL_MCDRAM_SIZE (16ULL*1024*1024*1024)
214 
process_smbios_group(const char * input_fsroot,char * dir_name,struct parser_data * data)215 static int process_smbios_group(const char *input_fsroot, char *dir_name, struct parser_data *data)
216 {
217     char path[PATH_SIZE];
218     char file_buf[SMBIOS_FILE_BUF_SIZE];
219     struct smbios_header *h;
220     char *p;
221     char *end;
222     int size;
223     int i;
224 
225     snprintf(path, PATH_SIZE-1, "%s/" KERNEL_SMBIOS_SYSFS "/%s/raw", input_fsroot, dir_name);
226     path[PATH_SIZE-1] = 0;
227 
228     size = get_file_buffer(path, file_buf, SMBIOS_FILE_BUF_SIZE);
229     if (!size) {
230         fprintf(stderr, "Unable to read raw table file\n");
231         return -1;
232     }
233 
234     h = (struct smbios_header*)file_buf;
235     end = file_buf+size;
236     if (!is_phi_group(h, end)) {
237         printf("  Failed to find Phi group\n");
238         fprintf(stderr, "SMBIOS table does not contain Xeon Phi entries\n");
239         return -1;
240     }
241     printf("  Found Phi group\n");
242 
243     p = file_buf + sizeof(struct smbios_header) + sizeof(struct smbios_group);
244     if ((char*)p >= end) {
245         fprintf(stderr, "SMBIOS table does not have entries\n");
246         return -1;
247     }
248 
249     end = file_buf+h->length;
250 
251     i = 0;
252     for (; p < end; i++, p+=3) {
253         struct smbios_group_entry *e = (struct smbios_group_entry*)p;
254         data->knl_types[i] = e->type;
255         printf("    Found Xeon Phi type = %d\n", e->type);
256     }
257 
258     data->type_count = i;
259     return 0;
260 }
261 
process_knl_entry(const char * input_fsroot,char * dir_name,struct parser_data * data)262 static int process_knl_entry(const char *input_fsroot, char *dir_name, struct parser_data *data)
263 {
264     char path[PATH_SIZE];
265     char file_buf[SMBIOS_FILE_BUF_SIZE];
266     char *end;
267     int size;
268     struct knl_smbios_header *h;
269 
270     snprintf(path, PATH_SIZE-1, "%s/" KERNEL_SMBIOS_SYSFS "/%s/raw", input_fsroot, dir_name);
271     path[PATH_SIZE-1] = 0;
272 
273     size = get_file_buffer(path, file_buf, SMBIOS_FILE_BUF_SIZE);
274     if (!size) {
275         fprintf(stderr, "Unable to read raw table file\n");
276         return -1;
277     }
278 
279     end = file_buf+size;
280     h = (struct knl_smbios_header*)file_buf;
281     if (h->member_id & KNL_MEMBER_ID_GENERAL) {
282         struct knl_general_info *info =
283             (struct knl_general_info*) (file_buf+SMBIOS_KNL_HEADER_SIZE);
284         printf("  Getting general Xeon Phi info\n");
285         data->cluster_mode = info->cluster_mode;
286         data->memory_mode = info->memory_mode;
287         data->cache_info = info->cache_info;
288     } else if (h->member_id & KNL_MEMBER_ID_EDC) {
289         struct knl_edc_info *info = (struct knl_edc_info*)(file_buf+SMBIOS_KNL_HEADER_SIZE);
290         if (info->mcdram_present && info->mcdram_enabled) {
291             struct knl_mcdram_info *mi = (struct knl_mcdram_info*)(info + 1);
292             /* we use always smbios size not struct size
293              * as it can change in future.*/
294             int struct_size = info->mcdram_info_size;
295             int i = 0;
296 
297             if (0 == struct_size) {
298                 printf("  MCDRAM info size is set to 0, falling back to known size\n");
299                 struct_size = sizeof(*mi);
300             }
301             printf("  Getting Xeon Phi MCDRAM info. Count=%d struct size=%d\n",
302                    (int)info->mcdram_info_count, struct_size);
303             for ( ; i < info->mcdram_info_count; i++) {
304                 if ((char*)mi >= end) {
305                     fprintf(stderr, "SMBIOS Xeon Phi entry is too small\n");
306                     return -1;
307                 }
308                 printf("  MCDRAM controller %d\n", mi->controller);
309                 if (mi->status & 0x1) {
310                     printf("  Controller fused\n");
311                 } else {
312                     data->mcdram_regular += mi->size64MB;
313                     printf("  Size = %d MB\n", (int)mi->size64MB*64);
314                 }
315                 mi = (struct knl_mcdram_info*)(((char*)mi)+struct_size);
316             }
317             /* convert to bytes  */
318             printf("  Total MCDRAM %llu MB\n", (long long unsigned int)data->mcdram_regular*64);
319             data->mcdram_regular *= 64*1024*1024;
320             /*
321              * BIOS can expose some MCRAM controllers as fused
322              * When this happens we hardcode MCDRAM size to 16 GB
323              */
324             if (data->mcdram_regular != KNL_MCDRAM_SIZE) {
325                 fprintf(stderr, "Not all MCDRAM is exposed in DMI. Please contact BIOS vendor\n");
326                 data->mcdram_regular = KNL_MCDRAM_SIZE;
327             }
328 
329         } else {
330             data->mcdram_regular = 0;
331             data->mcdram_cache = 0;
332         }
333 
334     } else {
335         /* We skip unknown table */
336         fprintf(stderr, "Ignoring unknown SMBIOS entry type=%x\n", h->member_id);
337     }
338 
339     return 0;
340 }
get_memory_mode_str(int memory_mode,int hybrid_cache_size)341 static const char* get_memory_mode_str(int memory_mode, int hybrid_cache_size)
342 {
343     switch (memory_mode) {
344         case CACHE: return "Cache";
345         case FLAT: return "Flat";
346         case HYBRID:
347             if (hybrid_cache_size == H25) {
348                 return "Hybrid25";
349             } else if (hybrid_cache_size == H50) {
350                 return "Hybrid50";
351             }
352             return "Unknown";
353         default:
354             return "Unknown";
355     }
356 }
357 
get_cluster_mode_str(int cluster_mode)358 static const char* get_cluster_mode_str(int cluster_mode)
359 {
360     switch (cluster_mode) {
361         case QUADRANT: return "Quadrant";
362         case HEMISPHERE: return "Hemisphere";
363         case ALL2ALL: return "All2All";
364         case SNC2: return "SNC2";
365         case SNC4: return "SNC4";
366         default:
367             return "Unknown";
368     }
369 }
370 
print_result(struct parser_data * data,const char * out_file)371 static int print_result(struct parser_data *data, const char *out_file)
372 {
373     int node_count = 0;
374     int fd;
375     FILE *f;
376 
377     switch (data->cluster_mode) {
378         case QUADRANT:
379             node_count = 1;
380             break;
381         case HEMISPHERE:
382             node_count = 1;
383             break;
384         case ALL2ALL:
385             node_count = 1;
386             break;
387         case SNC2:
388             node_count = 2;
389             break;
390         case SNC4:
391             node_count = 4;
392             break;
393         default:
394             fprintf(stderr, "Incorrect cluster mode %d\n", data->cluster_mode);
395             return -1;
396     }
397 
398     switch (data->memory_mode) {
399         case CACHE:
400             data->mcdram_cache = data->mcdram_regular;
401             data->mcdram_regular = 0;
402             break;
403         case FLAT:
404             data->mcdram_cache = 0;
405             break;
406         case HYBRID:
407             if (data->cache_info == H25) {
408                 data->mcdram_cache = data->mcdram_regular/4;
409             } else if (data->cache_info == H50) {
410                 data->mcdram_cache = data->mcdram_regular/2;
411             } else if (data->cache_info == H100) {
412                 data->mcdram_cache = data->mcdram_regular;
413             } else {
414                 fprintf(stderr, "SMBIOS reserved cache info value %d\n", data->cache_info);
415                 return -1;
416             }
417             data->mcdram_regular -= data->mcdram_cache;
418             break;
419         default:
420             fprintf(stderr, "Incorrect memory mode %d\n", data->memory_mode);
421             return -1;
422     }
423 
424     printf("  Cluster Mode: %s Memory Mode: %s\n",
425             get_cluster_mode_str(data->cluster_mode),
426             get_memory_mode_str(data->memory_mode, data->cache_info));
427     printf("  MCDRAM total = %llu bytes, cache = %llu bytes\n",
428            (long long unsigned int)data->mcdram_regular,
429            (long long unsigned int)data->mcdram_cache);
430     data->mcdram_regular /= node_count;
431     data->mcdram_cache /= node_count;
432     printf("  MCDRAM total = %llu bytes, cache = %llu bytes per node\n",
433            (long long unsigned int)data->mcdram_regular,
434            (long long unsigned int)data->mcdram_cache);
435 
436     /* Now we can start printing stuff */
437     /* use open+fdopen so that we can specify the file creation mode */
438     fd = open(out_file, O_WRONLY|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
439     if (fd < 0) {
440         fprintf(stderr, "Unable to open file `%s' (%s).\n", out_file, strerror(errno));
441         return -1;
442     }
443     f = fdopen(fd, "w");
444     if (!f) {
445         fprintf(stderr, "Unable to fdopen file `%s' (%s).\n", out_file, strerror(errno));
446         close(fd);
447         return -1;
448     }
449 
450     fprintf(f, "version: 2\n");
451     /* We cache is equal for node */
452     fprintf(f, "cache_size: %llu\n",
453                 (long long unsigned int)data->mcdram_cache);
454     fprintf(f, "associativity: 1\n");// direct-mapped cache
455     fprintf(f, "inclusiveness: 1\n");// inclusive cache
456     fprintf(f, "line_size: 64\n");
457     fprintf(f, "cluster_mode: %s\n", get_cluster_mode_str(data->cluster_mode));
458     fprintf(f, "memory_mode: %s\n", get_memory_mode_str(data->memory_mode, data->cache_info));
459     fflush(f);
460     fclose(f);
461     close(fd);
462     return 0;
463 }
464 
465 /**
466 * Seeks SMBIOS sysfs for entry with type
467 */
468 int hwloc_dump_hwdata_knl_smbios(const char *input_fsroot, const char *outfile);
469 
hwloc_dump_hwdata_knl_smbios(const char * input_fsroot,const char * outfile)470 int hwloc_dump_hwdata_knl_smbios(const char *input_fsroot, const char *outfile)
471 {
472     DIR *d;
473     int i;
474     struct dirent *dir;
475     struct parser_data data;
476     char path[PATH_SIZE];
477     int err;
478 
479     memset(&data, 0, sizeof(data));
480 
481     printf("Dumping Xeon Phi SMBIOS Memory-Side Cache information:\n");
482 
483     snprintf(path, PATH_SIZE-1, "%s/" KERNEL_SMBIOS_SYSFS, input_fsroot);
484     path[PATH_SIZE-1] = 0;
485 
486     d = opendir(path);
487     if (!d) {
488         fprintf(stderr, "Unable to open dmi-sysfs dir: %s", path);
489         return -1;
490     }
491 
492     /* process Xeon Phi entries
493      * start with group (type 14, dash os to omit 140 types) then find SMBIOS types for
494      * Knights Landing mcdram indofrmation
495      */
496     while ((dir = readdir(d))) {
497         if (strncmp("14-", dir->d_name, 3) == 0) {
498             err = process_smbios_group(input_fsroot, dir->d_name, &data);
499             if (err < 0) {
500                 closedir(d);
501                 return err;
502             }
503         }
504     }
505 
506     if (!data.type_count) {
507       fprintf (stderr, "  Couldn't find any Xeon Phi information.\n");
508       closedir(d);
509       return -1;
510     }
511 
512     /* We probably have Xeon Phi type identifiers here */
513     for (i = 0; i < data.type_count; i++) {
514         char tab[16] = {0};
515         int l = snprintf(tab, sizeof(tab)-1, "%d-", data.knl_types[i]);
516         printf("\n");
517         printf ("  Seeking dir ̀`%s' %d\n", tab, l);
518         rewinddir(d);
519         while ((dir = readdir(d))) {
520             if (strncmp(dir->d_name, tab, l) == 0) {
521                 err = process_knl_entry(input_fsroot, dir->d_name, &data);
522                 if (err < 0) {
523                     closedir(d);
524                     return err;
525                 }
526             }
527         }
528     }
529 
530     closedir(d);
531 
532     return print_result(&data, outfile);
533 }
534