1/* -*- mode: C; c-basic-offset: 3; -*- */
2
3#include <setjmp.h>
4#include <signal.h>
5#include <stdio.h>
6#include <stdlib.h>
7#include <string.h>
8#include <ctype.h>     // isspace
9#include <fcntl.h>     // open
10#include <unistd.h>    // lseek
11#include <sys/stat.h>  // S_IRUSR
12
13// This file determines s390x features a processor supports.
14//
15// We return:
16// - 0 if the machine provides the asked-for feature and the cpu
17//     model, if specified, matches the machine
18// - 1 the machine does not provide the asked-for feature or the
19//     cpu model, if specified, does not match the machine
20// - 2 if the asked-for feature isn't recognised (this will be the case for
21//     any feature if run on a non-s390x machine).
22// - 2 for an unknown cpu model in /proc/cpu_info
23// - 3 if there was a usage error (it also prints an error message).
24//
25// USAGE:
26//
27//    s390x_features <feature> [<machine-model>]
28//
29// The machine_model is optional and it can be something like:
30//
31//   z9        -- Host needs to be a z9 (and nothing else)
32//   z9:       -- Host needs to be a z9 or any later model
33//   :z9       -- Host needs to be a model up to and including z9
34//   z900:z9   -- Host needs to be at least a z900 and at most a z9.
35//                Any model in between is OK, too.
36
37jmp_buf env;
38
39#if defined(VGA_s390x)
40
41void handle_sigill(int signum)
42{
43   longjmp(env, 1);
44}
45
46unsigned long long stfle(void)
47{
48
49   unsigned long long ret;
50
51   signal(SIGILL, handle_sigill);
52   if (setjmp(env)) {
53      /* stfle not available: assume no facilities */
54      return 0;
55   } else {
56      asm volatile("lghi 0, 0\n"
57                   ".insn s,0xb2b00000,%0\n" /* stfle */
58      : "=Q" (ret)::"0", "cc");
59      return ret;
60   }
61}
62
63
64/* Read /proc/cpuinfo. Look for lines like these
65
66      processor 0: version = FF,  identification = 0117C9,  machine = 2064
67
68   and return the machine model or NULL on error.
69   Adapted from function VG_(get_machine_model) in coregrind/m_machine.c */
70
71typedef struct {
72   const char *cpuinfo_name;
73   const char *real_name;
74} model_info;
75
76/* Array needs to be sorted chronologically. Oldest to newest */
77model_info models[] = {
78   { "2064", "z900"   },
79   { "2066", "z800"   },
80   { "2084", "z990"   },
81   { "2086", "z890"   },
82   { "2094", "z9-ec"  },
83   { "2096", "z9-bc"  },
84   { "2097", "z10-ec" },
85   { "2098", "z10-bc" },
86   { "2817", "z196"   },
87};
88
89
90/* Locate a machine model by name. Name can be either the cpuinfo
91   name or the external name. */
92static model_info *locate_model(const char *name)
93{
94   model_info *p;
95
96   /* Try cpuinfo name first */
97   for (p = models; p != models + sizeof models / sizeof models[0]; ++p) {
98      if (strcmp(p->cpuinfo_name, name) == 0) return p;  // found it
99   }
100
101   /* Now try external name */
102   for (p = models; p != models + sizeof models / sizeof models[0]; ++p) {
103      if (strcmp(p->real_name, name) == 0) return p;  // found it
104   }
105
106   return NULL;
107}
108
109
110static model_info *get_host(void)
111{
112   int    n, fh;
113   size_t num_bytes, file_buf_size;
114   char  *p, *m, *model_name, *file_buf;
115   model_info *model;
116
117   /* Slurp contents of /proc/cpuinfo into FILE_BUF */
118   //fh = open("/proc/cpuinfo", O_RDONLY, S_IRUSR);
119   fh = open("/proc/cpuinfo", O_RDONLY, S_IRUSR);
120   if (fh < 0) return NULL;
121
122   /* Determine the size of /proc/cpuinfo.
123      Work around broken-ness in /proc file system implementation.
124      fstat returns a zero size for /proc/cpuinfo although it is
125      claimed to be a regular file. */
126   num_bytes = 0;
127   file_buf_size = 1000;
128   file_buf = malloc(file_buf_size + 1);
129
130   while (42) {
131      n = read(fh, file_buf, file_buf_size);
132      if (n < 0) break;
133
134      num_bytes += n;
135      if (n < file_buf_size) break;  /* reached EOF */
136   }
137
138   if (n < 0) num_bytes = 0;   /* read error; ignore contents */
139
140   if (num_bytes > file_buf_size) {
141      free(file_buf);
142      lseek(fh, 0, SEEK_SET);
143      file_buf = malloc(num_bytes + 1);
144      n = read(fh, file_buf, num_bytes);
145      if (n < 0) num_bytes = 0;
146   }
147
148   file_buf[num_bytes] = '\0';
149   close(fh);
150
151   /* Parse file */
152   model = models + sizeof models / sizeof models[0];
153   for (p = file_buf; *p; ++p) {
154      /* Beginning of line */
155      if (strncmp(p, "processor", sizeof "processor" - 1 ) != 0) continue;
156
157      m = strstr(p, "machine");
158      if (m == NULL) continue;
159
160      p = m + sizeof "machine" - 1;
161      while (isspace(*p) || *p == '=') {
162         if (*p == '\n') goto next_line;
163         ++p;
164      }
165
166      model_name = p;
167      for (n = 0; n < sizeof models / sizeof models[0]; ++n) {
168         model_info *mm = models + n;
169         size_t len = strlen(mm->cpuinfo_name);
170         if (strncmp(mm->cpuinfo_name, model_name, len) == 0 &&
171             isspace(model_name[len])) {
172            /* In case there are different CPUs in this cluster return the
173               one with the dewest capabilities ("oldest" model). */
174            if (mm < model) model = mm;
175            p = model_name + len;
176            break;
177         }
178      }
179      /* Skip until end-of-line */
180      while (*p != '\n')
181         ++p;
182   next_line: ;
183   }
184
185   free(file_buf);
186
187   if (model == models + sizeof models / sizeof models[0]) return NULL;
188
189   return model;
190}
191
192static int go(char *feature, char *cpu)
193{
194   unsigned long long facilities;
195   unsigned long long match;
196   model_info *host, *from, *to, *p;
197   char *colon;
198
199   facilities = stfle();
200
201   if        (strcmp(feature, "s390x-zarch") == 0 ) {
202     match = (facilities & (1ULL << 62) && (facilities & (1ULL << 61)));
203   } else if (strcmp(feature, "s390x-n3") == 0 ) {
204     match = (facilities & (1ULL << 63));
205   } else if (strcmp(feature, "s390x-stfle") == 0 ) {
206     match = (facilities & (1ULL << 56));
207   } else if (strcmp(feature, "s390x-ldisp") == 0 ) {
208     match = (facilities & (1ULL << 45) && (facilities & (1ULL << 44)));
209   } else if (strcmp(feature, "s390x-eimm") == 0 ) {
210     match = (facilities & (1ULL << 42));
211   } else if (strcmp(feature, "s390x-stckf") == 0 ) {
212     match = (facilities & (1ULL << 38));
213   } else if (strcmp(feature, "s390x-genins") == 0 ) {
214     match = (facilities & (1ULL << 29));
215   } else if (strcmp(feature, "s390x-exrl") == 0 ) {
216     match = (facilities & (1ULL << 28));
217   } else {
218     return 2;          // Unrecognised feature.
219   }
220
221   if (match == 0) return 1;   // facility not provided
222
223   /* Host provides facility. If no CPU was specified, we're done. */
224   if (cpu == NULL) return 0;
225
226   host = get_host();
227   if (host == NULL) return 2;  // unknown model
228
229   //   printf("host = %s (%s)\n", host->cpuinfo_name, host->real_name);
230
231   /* Determine interval of models in which to search for HOST. */
232   from = to = NULL;
233   colon = strchr(cpu, ':');
234
235   if (colon == NULL) {
236      // match exact
237      from = to = locate_model(cpu);
238   } else if (colon == cpu) {
239      // :NAME  match machines up to and including CPU
240      from = models;
241      to   = locate_model(cpu + 1);
242   } else if (colon[1] == '\0') {
243      // NAME:  match machines beginning with CPU or later
244      *colon = '\0';
245      from = locate_model(cpu);
246      to   = models + sizeof models / sizeof models[0] - 1;
247      *colon = ':';
248   } else {
249      // NAME:NAME  match machines in interval
250      *colon = '\0';
251      from = locate_model(cpu);
252      to   = locate_model(colon + 1);
253      *colon = ':';
254   }
255
256   if (from == NULL || to == NULL || from > to) {
257      fprintf(stderr, "invalid cpu specification '%s'\n", cpu);
258      return 3;
259   }
260
261#if 0
262   printf("from  %s (%s)  to  %s (%s)\n", from->cpuinfo_name, from->real_name,
263          to->cpuinfo_name, to->real_name);
264#endif
265
266   /* Search for HOST. */
267   for (p = from; p <= to; ++p) {
268      if (p == host) return 0;
269   }
270
271   return 1; // host does not match CPU specification
272}
273
274#else
275
276static int go(char *feature, char *cpu)
277{
278   return 2;      // Feature not recognised (non-s390x machine!)
279}
280
281#endif
282
283
284//---------------------------------------------------------------------------
285// main
286//---------------------------------------------------------------------------
287int main(int argc, char **argv)
288{
289   int rc;
290
291   if (argc < 2 || argc > 3) {
292      fprintf( stderr, "usage: s390x_features <feature> [<machine-model>]\n" );
293      exit(3);                // Usage error.
294   }
295
296   rc = go(argv[1], argv[2]);
297
298   //   printf("rc = %d\n", rc);
299
300   return rc;
301}
302