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