1 /*
   2  * This file and its contents are supplied under the terms of the
   3  * Common Development and Distribution License ("CDDL"), version 1.0.
   4  * You may only use this file in accordance with the terms of version
   5  * 1.0 of the CDDL.
   6  *
   7  * A full copy of the text of the CDDL should have accompanied this
   8  * source.  A copy of the CDDL is also available via the Internet at
   9  * http://www.illumos.org/license/CDDL.
  10  */
  11 
  12 /*
  13  * Copyright (c) 2019, Joyent, Inc.
  14  */
  15 
  16 /*
  17  * This file transforms the perfmon data files into C files and manual pages.
  18  */
  19 
  20 #include <stdio.h>
  21 #include <stdarg.h>
  22 #include <unistd.h>
  23 #include <err.h>
  24 #include <libgen.h>
  25 #include <libnvpair.h>
  26 #include <strings.h>
  27 #include <errno.h>
  28 #include <limits.h>
  29 #include <sys/mman.h>
  30 #include <sys/param.h>
  31 #include <assert.h>
  32 #include <ctype.h>
  33 #include <sys/types.h>
  34 #include <sys/stat.h>
  35 #include <fcntl.h>
  36 
  37 #include <json_nvlist.h>
  38 
  39 #define EXIT_USAGE      2
  40 
  41 
  42 typedef struct cpc_proc {
  43         struct cpc_proc *cproc_next;
  44         uint_t          cproc_family;
  45         uint_t          cproc_model;
  46 } cpc_proc_t;
  47 
  48 typedef enum cpc_file_type {
  49         CPC_FILE_CORE           = 1 << 0,
  50         CPC_FILE_OFF_CORE       = 1 << 1,
  51         CPC_FILE_UNCORE         = 1 << 2,
  52         CPC_FILE_FP_MATH        = 1 << 3,
  53         CPC_FILE_UNCORE_EXP     = 1 << 4
  54 } cpc_type_t;
  55 
  56 typedef struct cpc_map {
  57         struct cpc_map  *cmap_next;
  58         cpc_type_t      cmap_type;
  59         nvlist_t        *cmap_data;
  60         char            *cmap_path;
  61         const char      *cmap_name;
  62         cpc_proc_t      *cmap_procs;
  63 } cpc_map_t;
  64 
  65 typedef struct cpc_whitelist {
  66         const char      *cwhite_short;
  67         const char      *cwhite_human;
  68         uint_t          cwhite_mask;
  69 } cpc_whitelist_t;
  70 
  71 /*
  72  * List of architectures that we support generating this data for. This is done
  73  * so that processors that illumos doesn't support or run on aren't generated
  74  * (generally the Xeon Phi).
  75  */
  76 static cpc_whitelist_t cpcgen_whitelist[] = {
  77         /* Nehalem */
  78         { "NHM-EP", "nhm_ep", CPC_FILE_CORE },
  79         { "NHM-EX", "nhm_ex", CPC_FILE_CORE },
  80         /* Westmere */
  81         { "WSM-EP-DP", "wsm_ep_dp", CPC_FILE_CORE },
  82         { "WSM-EP-SP", "wsm_ep_sp", CPC_FILE_CORE },
  83         { "WSM-EX", "wsm_ex", CPC_FILE_CORE },
  84         /* Sandy Bridge */
  85         { "SNB", "snb", CPC_FILE_CORE },
  86         { "JKT", "jkt", CPC_FILE_CORE },
  87         /* Ivy Bridge */
  88         { "IVB", "ivb", CPC_FILE_CORE },
  89         { "IVT", "ivt", CPC_FILE_CORE },
  90         /* Haswell */
  91         { "HSW", "hsw", CPC_FILE_CORE },
  92         { "HSX", "hsx", CPC_FILE_CORE },
  93         /* Broadwell */
  94         { "BDW", "bdw", CPC_FILE_CORE },
  95         { "BDW-DE", "bdw_de", CPC_FILE_CORE },
  96         { "BDX", "bdx", CPC_FILE_CORE },
  97         /* Skylake */
  98         { "SKL", "skl", CPC_FILE_CORE },
  99         { "SKX", "skx", CPC_FILE_CORE },
 100         /* Atom */
 101         { "BNL", "bnl", CPC_FILE_CORE },
 102         { "SLM", "slm", CPC_FILE_CORE },
 103         { "GLM", "glm", CPC_FILE_CORE },
 104         { "GLP", "glp", CPC_FILE_CORE },
 105         { NULL }
 106 };
 107 
 108 typedef struct cpc_papi {
 109         const char      *cpapi_intc;
 110         const char      *cpapi_papi;
 111 } cpc_papi_t;
 112 
 113 /*
 114  * This table maps events with an Intel specific name to the corresponding PAPI
 115  * name. There may be multiple INtel events which map to the same PAPI event.
 116  * This is usually because different processors have different names for an
 117  * event. We use the title as opposed to the event codes because those can
 118  * change somewhat arbitrarily between processor generations.
 119  */
 120 static cpc_papi_t cpcgen_papi_map[] = {
 121         { "CPU_CLK_UNHALTED.THREAD_P", "PAPI_tot_cyc" },
 122         { "INST_RETIRED.ANY_P", "PAPI_tot_ins" },
 123         { "BR_INST_RETIRED.ALL_BRANCHES", "PAPI_br_ins" },
 124         { "BR_MISP_RETIRED.ALL_BRANCHES", "PAPI_br_msp" },
 125         { "BR_INST_RETIRED.CONDITIONAL", "PAPI_br_cn" },
 126         { "CYCLE_ACTIVITY.CYCLES_L1D_MISS", "PAPI_l1_dcm" },
 127         { "L1I.HITS", "PAPI_l1_ich" },
 128         { "ICACHE.HIT", "PAPI_l1_ich" },
 129         { "L1I.MISS", "PAPI_L1_icm" },
 130         { "ICACHE.MISSES", "PAPI_l1_icm" },
 131         { "L1I.READS", "PAPI_l1_ica" },
 132         { "ICACHE.ACCESSES", "PAPI_l1_ica" },
 133         { "L1I.READS", "PAPI_l1_icr" },
 134         { "ICACHE.ACCESSES", "PAPI_l1_icr" },
 135         { "L2_RQSTS.CODE_RD_MISS", "PAPI_l2_icm" },
 136         { "L2_RQSTS.MISS", "PAPI_l2_tcm" },
 137         { "ITLB_MISSES.MISS_CAUSES_A_WALK", "PAPI_tlb_im" },
 138         { "DTLB_LOAD_MISSES.MISS_CAUSES_A_WALK", "PAPI_tlb_dm" },
 139         { "PAGE_WALKS.D_SIDE_WALKS", "PAPI_tlb_dm" },
 140         { "PAGE_WALKS.I_SIDE_WALKS", "PAPI_tlb_im" },
 141         { "PAGE_WALKS.WALKS", "PAPI_tlb_tl" },
 142         { "INST_QUEUE_WRITES", "PAPI_tot_iis" },
 143         { "MEM_INST_RETIRED.STORES" "PAPI_sr_ins" },
 144         { "MEM_INST_RETIRED.LOADS" "PAPI_ld_ins" },
 145         { NULL, NULL }
 146 };
 147 
 148 typedef struct cpcgen_ops {
 149         char *(*cgen_op_name)(cpc_map_t *);
 150         boolean_t (*cgen_op_file_before)(FILE *, cpc_map_t *);
 151         boolean_t (*cgen_op_file_after)(FILE *, cpc_map_t *);
 152         boolean_t (*cgen_op_event)(FILE *, nvlist_t *, const char *, uint32_t);
 153 } cpcgen_ops_t;
 154 
 155 static cpcgen_ops_t cpcgen_ops;
 156 static const char *cpcgen_mapfile = "/mapfile.csv";
 157 static const char *cpcgen_progname;
 158 static cpc_map_t *cpcgen_maps;
 159 
 160 /*
 161  * Constants used for generating data.
 162  */
 163 /* BEGIN CSTYLED */
 164 static const char *cpcgen_cfile_header = ""
 165 "/*\n"
 166 " *  Copyright (c) 2018, Intel Corporation\n"
 167 " *  Copyright (c) 2018, Joyent, Inc\n"
 168 " *  All rights reserved.\n"
 169 " *\n"
 170 " *  Redistribution and use in source and binary forms, with or without\n"
 171 " *  modification, are permitted provided that the following conditions are met:\n"
 172 " * \n"
 173 " *   1. Redistributions of source code must retain the above copyright notice,\n"
 174 " *      this list of conditions and the following disclaimer.\n"
 175 " * \n"
 176 " *   2. Redistributions in binary form must reproduce the above copyright \n"
 177 " *      notice, this list of conditions and the following disclaimer in the\n"
 178 " *      documentation and/or other materials provided with the distribution.\n"
 179 " * \n"
 180 " *   3. Neither the name of the Intel Corporation nor the names of its \n"
 181 " *      contributors may be used to endorse or promote products derived from\n"
 182 " *      this software without specific prior written permission.\n"
 183 " *\n"
 184 " *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n"
 185 " *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n"
 186 " *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n"
 187 " *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE\n"
 188 " *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n"
 189 " *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n"
 190 " *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n"
 191 " *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n"
 192 " *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n"
 193 " *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n"
 194 " *  POSSIBILITY OF SUCH DAMAGE.\n"
 195 " *\n"
 196 " * This file was automatically generated by cpcgen from the data file\n"
 197 " * data/perfmon%s\n"
 198 " *\n"
 199 " * Do not modify this file. Your changes will be lost!\n"
 200 " */\n"
 201 "\n";
 202 /* END CSTYLED */
 203 
 204 static const char *cpcgen_cfile_table_start = ""
 205 "#include <core_pcbe_table.h>\n"
 206 "\n"
 207 "const struct events_table_t pcbe_core_events_%s[] = {\n";
 208 
 209 static const char *cpcgen_cfile_table_end = ""
 210 "\t{ NT_END, 0, 0, \"\" }\n"
 211 "};\n";
 212 
 213 /* BEGIN CSTYLED */
 214 static const char *cpcgen_manual_header = ""
 215 ".\\\" Copyright (c) 2018, Intel Corporation \n"
 216 ".\\\" Copyright (c) 2018, Joyent, Inc.\n"
 217 ".\\\" All rights reserved.\n"
 218 ".\\\"\n"
 219 ".\\\" Redistribution and use in source and binary forms, with or without \n"
 220 ".\\\" modification, are permitted provided that the following conditions are met:\n"
 221 ".\\\"\n"
 222 ".\\\"  1. Redistributions of source code must retain the above copyright notice,\n"
 223 ".\\\"     this list of conditions and the following disclaimer.\n"
 224 ".\\\"\n"
 225 ".\\\"  2. Redistributions in binary form must reproduce the above copyright\n"
 226 ".\\\"     notice, this list of conditions and the following disclaimer in the\n"
 227 ".\\\"     documentation and/or other materials provided with the distribution.\n"
 228 ".\\\"\n"
 229 ".\\\"  3. Neither the name of the Intel Corporation nor the names of its\n"
 230 ".\\\"     contributors may be used to endorse or promote products derived from\n"
 231 ".\\\"     this software without specific prior written permission.\n"
 232 ".\\\"\n"
 233 ".\\\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n"
 234 ".\\\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n"
 235 ".\\\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n"
 236 ".\\\" ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE\n"
 237 ".\\\" LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n"
 238 ".\\\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n"
 239 ".\\\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n"
 240 ".\\\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n"
 241 ".\\\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n"
 242 ".\\\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n"
 243 ".\\\" POSSIBILITY OF SUCH DAMAGE.\n"
 244 ".\\\"\n"
 245 ".\\\" This file was automatically generated by cpcgen from the data file\n"
 246 ".\\\" data/perfmon%s\n"
 247 ".\\\"\n"
 248 ".\\\" Do not modify this file. Your changes will be lost!\n"
 249 ".\\\"\n"
 250 ".\\\" We would like to thank Intel for providing the perfmon data for use in\n"
 251 ".\\\" our manual pages.\n"
 252 ".Dd June 18, 2018\n"
 253 ".Dt %s_EVENTS 3CPC\n"
 254 ".Os\n"
 255 ".Sh NAME\n"
 256 ".Nm %s_events\n"
 257 ".Nd processor model specific performance counter events\n"
 258 ".Sh DESCRIPTION\n"
 259 "This manual page describes events specific to the following Intel CPU\n"
 260 "models and is derived from Intel's perfmon data.\n"
 261 "For more information, please consult the Intel Software Developer's Manual "
 262 "or Intel's perfmon website.\n"
 263 ".Pp\n"
 264 "CPU models described by this document:\n"
 265 ".Bl -bullet\n";
 266 /* END CSTYLED */
 267 
 268 static const char *cpcgen_manual_data = ""
 269 ".El\n"
 270 ".Pp\n"
 271 "The following events are supported:\n"
 272 ".Bl -tag -width Sy\n";
 273 
 274 static const char *cpcgen_manual_trailer = ""
 275 ".El\n"
 276 ".Sh SEE ALSO\n"
 277 ".Xr cpc 3CPC\n"
 278 ".Pp\n"
 279 ".Lk https://download.01.org/perfmon/index/";
 280 
 281 static cpc_map_t *
 282 cpcgen_map_lookup(const char *path)
 283 {
 284         cpc_map_t *m;
 285 
 286         for (m = cpcgen_maps; m != NULL; m = m->cmap_next) {
 287                 if (strcmp(path, m->cmap_path) == 0) {
 288                         return (m);
 289                 }
 290         }
 291 
 292         return (NULL);
 293 }
 294 
 295 /*
 296  * Parse a string of the form 'GenuineIntel-6-2E' and get out the family and
 297  * model.
 298  */
 299 static void
 300 cpcgen_parse_model(char *fsr, uint_t *family, uint_t *model)
 301 {
 302         const char *bstr = "GenuineIntel";
 303         const char *brand, *fam, *mod;
 304         char *last;
 305         long l;
 306 
 307         if ((brand = strtok_r(fsr, "-", &last)) == NULL ||
 308             (fam = strtok_r(NULL, "-", &last)) == NULL ||
 309             (mod = strtok_r(NULL, "-", &last)) == NULL) {
 310                 errx(EXIT_FAILURE, "failed to parse processor id \"%s\"", fsr);
 311         }
 312 
 313         if (strcmp(bstr, brand) != 0) {
 314                 errx(EXIT_FAILURE, "brand string \"%s\" did not match \"%s\"",
 315                     brand, bstr);
 316         }
 317 
 318         errno = 0;
 319         l = strtol(fam, &last, 16);
 320         if (errno != 0 || l < 0 || l >= INT_MAX || *last != '\0') {
 321                 errx(EXIT_FAILURE, "failed to parse family \"%s\"", fam);
 322         }
 323         *family = (uint_t)l;
 324 
 325         l = strtol(mod, &last, 16);
 326         if (errno != 0 || l < 0 || l >= INT_MAX || *last != '\0') {
 327                 errx(EXIT_FAILURE, "failed to parse model \"%s\"", mod);
 328         }
 329         *model = (uint_t)l;
 330 }
 331 
 332 static nvlist_t *
 333 cpcgen_read_datafile(const char *datadir, const char *file)
 334 {
 335         int fd;
 336         char *path;
 337         struct stat st;
 338         void *map;
 339         nvlist_t *nvl;
 340         nvlist_parse_json_error_t jerr;
 341 
 342         if (asprintf(&path, "%s/%s", datadir, file) == -1) {
 343                 err(EXIT_FAILURE, "failed to construct path to data file %s",
 344                     file);
 345         }
 346 
 347         if ((fd = open(path, O_RDONLY)) < 0) {
 348                 err(EXIT_FAILURE, "failed to open data file %s", path);
 349         }
 350 
 351         if (fstat(fd, &st) != 0) {
 352                 err(EXIT_FAILURE, "failed to stat %s", path);
 353         }
 354 
 355         if ((map = mmap(NULL, st.st_size, PROT_READ | PROT_WRITE, MAP_PRIVATE,
 356             fd, 0)) == MAP_FAILED) {
 357                 err(EXIT_FAILURE, "failed to mmap %s", path);
 358         }
 359 
 360         if (nvlist_parse_json(map, st.st_size, &nvl, NVJSON_FORCE_INTEGER,
 361             &jerr) != 0) {
 362                 errx(EXIT_FAILURE, "failed to parse file %s at pos %ld: %s",
 363                     path, jerr.nje_pos, jerr.nje_message);
 364         }
 365 
 366         if (munmap(map, st.st_size) != 0) {
 367                 err(EXIT_FAILURE, "failed to munmap %s", path);
 368         }
 369 
 370         if (close(fd) != 0) {
 371                 err(EXIT_FAILURE, "failed to close data file %s", path);
 372         }
 373         free(path);
 374 
 375         return (nvl);
 376 }
 377 
 378 /*
 379  * Check the whitelist to see if we should use this model.
 380  */
 381 static const char *
 382 cpcgen_use_arch(const char *path, cpc_type_t type, const char *platform)
 383 {
 384         const char *slash;
 385         size_t len;
 386         uint_t i;
 387 
 388         if (*path != '/') {
 389                 errx(EXIT_FAILURE, "invalid path in mapfile: \"%s\": missing "
 390                     "leading '/'", path);
 391         }
 392         if ((slash = strchr(path + 1, '/')) == NULL) {
 393                 errx(EXIT_FAILURE, "invalid path in mapfile: \"%s\": missing "
 394                     "second '/'", path);
 395         }
 396         /* Account for the last '/' character. */
 397         len = slash - path - 1;
 398         assert(len > 0);
 399 
 400         for (i = 0; cpcgen_whitelist[i].cwhite_short != NULL; i++) {
 401                 if (platform != NULL && strcasecmp(platform,
 402                     cpcgen_whitelist[i].cwhite_short) != 0)
 403                         continue;
 404                 if (strncmp(path + 1, cpcgen_whitelist[i].cwhite_short,
 405                     len) == 0 &&
 406                     (cpcgen_whitelist[i].cwhite_mask & type) == type) {
 407                         return (cpcgen_whitelist[i].cwhite_human);
 408                 }
 409         }
 410 
 411         return (NULL);
 412 }
 413 
 414 /*
 415  * Read in the mapfile.csv that is used to map between processor families and
 416  * parse this. Each line has a comma separated value.
 417  */
 418 static void
 419 cpcgen_read_mapfile(const char *datadir, const char *platform)
 420 {
 421         FILE *map;
 422         char *mappath, *last;
 423         char *data = NULL;
 424         size_t datalen = 0;
 425         uint_t lineno;
 426 
 427         if (asprintf(&mappath, "%s/%s", datadir, cpcgen_mapfile) == -1) {
 428                 err(EXIT_FAILURE, "failed to construct path to mapfile");
 429         }
 430 
 431         if ((map = fopen(mappath, "r")) == NULL) {
 432                 err(EXIT_FAILURE, "failed to open data mapfile %s", mappath);
 433         }
 434 
 435         lineno = 0;
 436         while (getline(&data, &datalen, map) != -1) {
 437                 char *fstr, *path, *tstr;
 438                 const char *name;
 439                 uint_t family, model;
 440                 cpc_type_t type;
 441                 cpc_map_t *map;
 442                 cpc_proc_t *proc;
 443 
 444                 /*
 445                  * The first line contains the header:
 446                  * Family-model,Version,Filename,EventType
 447                  */
 448                 lineno++;
 449                 if (lineno == 1) {
 450                         continue;
 451                 }
 452 
 453                 if ((fstr = strtok_r(data, ",", &last)) == NULL ||
 454                     strtok_r(NULL, ",", &last) == NULL ||
 455                     (path = strtok_r(NULL, ",", &last)) == NULL ||
 456                     (tstr = strtok_r(NULL, "\n", &last)) == NULL) {
 457                         errx(EXIT_FAILURE, "failed to parse mapfile line "
 458                             "%u in %s", lineno, mappath);
 459                 }
 460 
 461                 cpcgen_parse_model(fstr, &family, &model);
 462 
 463                 if (strcmp(tstr, "core") == 0) {
 464                         type = CPC_FILE_CORE;
 465                 } else if (strcmp(tstr, "offcore") == 0) {
 466                         type = CPC_FILE_OFF_CORE;
 467                 } else if (strcmp(tstr, "uncore") == 0) {
 468                         type = CPC_FILE_UNCORE;
 469                 } else if (strcmp(tstr, "fp_arith_inst") == 0) {
 470                         type = CPC_FILE_FP_MATH;
 471                 } else if (strcmp(tstr, "uncore experimental") == 0) {
 472                         type = CPC_FILE_UNCORE_EXP;
 473                 } else {
 474                         errx(EXIT_FAILURE, "unknown file type \"%s\" on line "
 475                             "%u", tstr, lineno);
 476                 }
 477 
 478                 if ((name = cpcgen_use_arch(path, type, platform)) == NULL)
 479                         continue;
 480 
 481                 if ((map = cpcgen_map_lookup(path)) == NULL) {
 482                         nvlist_t *parsed;
 483 
 484                         parsed = cpcgen_read_datafile(datadir, path);
 485 
 486                         if ((map = calloc(1, sizeof (cpc_map_t))) == NULL) {
 487                                 err(EXIT_FAILURE, "failed to allocate space "
 488                                     "for cpc file");
 489                         }
 490 
 491                         if ((map->cmap_path = strdup(path)) == NULL) {
 492                                 err(EXIT_FAILURE, "failed to duplicate path "
 493                                     "string");
 494                         }
 495 
 496                         map->cmap_type = type;
 497                         map->cmap_data = parsed;
 498                         map->cmap_next = cpcgen_maps;
 499                         map->cmap_name = name;
 500                         cpcgen_maps = map;
 501                 }
 502 
 503                 if ((proc = malloc(sizeof (cpc_proc_t))) == NULL) {
 504                         err(EXIT_FAILURE, "failed to allocate memory for "
 505                             "family and model tracking");
 506                 }
 507 
 508                 proc->cproc_family = family;
 509                 proc->cproc_model = model;
 510                 proc->cproc_next = map->cmap_procs;
 511                 map->cmap_procs = proc;
 512         }
 513 
 514         if (errno != 0 || ferror(map)) {
 515                 err(EXIT_FAILURE, "failed to read %s", mappath);
 516         }
 517 
 518         if (fclose(map) == EOF) {
 519                 err(EXIT_FAILURE, "failed to close %s", mappath);
 520         }
 521         free(data);
 522         free(mappath);
 523 }
 524 
 525 static char *
 526 cpcgen_manual_name(cpc_map_t *map)
 527 {
 528         char *name;
 529 
 530         if (asprintf(&name, "%s_events.3cpc", map->cmap_name) == -1) {
 531                 warn("failed to assemble manual page name for %s",
 532                     map->cmap_path);
 533                 return (NULL);
 534         }
 535 
 536         return (name);
 537 }
 538 
 539 static boolean_t
 540 cpcgen_manual_file_before(FILE *f, cpc_map_t *map)
 541 {
 542         size_t i;
 543         char *upper;
 544         cpc_proc_t *proc;
 545 
 546         if ((upper = strdup(map->cmap_name)) == NULL) {
 547                 warn("failed to duplicate manual name for %s", map->cmap_name);
 548                 return (B_FALSE);
 549         }
 550 
 551         for (i = 0; upper[i] != '\0'; i++) {
 552                 upper[i] = toupper(upper[i]);
 553         }
 554 
 555         if (fprintf(f, cpcgen_manual_header, map->cmap_path, upper,
 556             map->cmap_name) == -1) {
 557                 warn("failed to write out manual header for %s",
 558                     map->cmap_name);
 559                 free(upper);
 560                 return (B_FALSE);
 561         }
 562 
 563         for (proc = map->cmap_procs; proc != NULL; proc = proc->cproc_next) {
 564                 if (fprintf(f, ".It\n.Sy Family 0x%x, Model 0x%x\n",
 565                     proc->cproc_family, proc->cproc_model) == -1) {
 566                         warn("failed to write out model information for %s",
 567                             map->cmap_name);
 568                         free(upper);
 569                         return (B_FALSE);
 570                 }
 571         }
 572 
 573         if (fprintf(f, cpcgen_manual_data, map->cmap_path, upper,
 574             map->cmap_name) == -1) {
 575                 warn("failed to write out manual header for %s",
 576                     map->cmap_name);
 577                 free(upper);
 578                 return (B_FALSE);
 579         }
 580 
 581         free(upper);
 582         return (B_TRUE);
 583 }
 584 
 585 static boolean_t
 586 cpcgen_manual_file_after(FILE *f, cpc_map_t *map)
 587 {
 588         if (fprintf(f, cpcgen_manual_trailer) == -1) {
 589                 warn("failed to write out manual header for %s",
 590                     map->cmap_name);
 591                 return (B_FALSE);
 592         }
 593 
 594         return (B_TRUE);
 595 }
 596 
 597 static boolean_t
 598 cpcgen_manual_event(FILE *f, nvlist_t *nvl, const char *path, uint32_t ent)
 599 {
 600         char *event, *lname, *brief = NULL, *public = NULL, *errata = NULL;
 601         size_t i;
 602 
 603         if (nvlist_lookup_string(nvl, "EventName", &event) != 0) {
 604                 warnx("Found event without 'EventName' property "
 605                     "in %s, entry %u", path, ent);
 606                 return (B_FALSE);
 607         }
 608 
 609         /*
 610          * Intel uses capital names. CPC historically uses lower case names.
 611          */
 612         if ((lname = strdup(event)) == NULL) {
 613                 err(EXIT_FAILURE, "failed to duplicate event name %s", event);
 614         }
 615         for (i = 0; lname[i] != '\0'; i++) {
 616                 lname[i] = tolower(event[i]);
 617         }
 618 
 619         /*
 620          * Try to get the other event fields, but if they're not there, don't
 621          * worry about it.
 622          */
 623         (void) nvlist_lookup_string(nvl, "BriefDescription", &brief);
 624         (void) nvlist_lookup_string(nvl, "PublicDescription", &public);
 625         (void) nvlist_lookup_string(nvl, "Errata", &errata);
 626         if (errata != NULL && (strcmp(errata, "0") == 0 ||
 627             strcmp(errata, "null") == 0)) {
 628                 errata = NULL;
 629         }
 630 
 631         if (fprintf(f, ".It Sy %s\n", lname) == -1) {
 632                 warn("failed to write out probe entry %s", event);
 633                 free(lname);
 634                 return (B_FALSE);
 635         }
 636 
 637         if (public != NULL) {
 638                 if (fprintf(f, "%s\n", public) == -1) {
 639                         warn("failed to write out probe entry %s", event);
 640                         free(lname);
 641                         return (B_FALSE);
 642                 }
 643         } else if (brief != NULL) {
 644                 if (fprintf(f, "%s\n", brief) == -1) {
 645                         warn("failed to write out probe entry %s", event);
 646                         free(lname);
 647                         return (B_FALSE);
 648                 }
 649         }
 650 
 651         if (errata != NULL) {
 652                 if (fprintf(f, ".Pp\nThe following errata may apply to this: "
 653                     "%s\n", errata) == -1) {
 654 
 655                         warn("failed to write out probe entry %s", event);
 656                         free(lname);
 657                         return (B_FALSE);
 658                 }
 659         }
 660 
 661         free(lname);
 662         return (B_TRUE);
 663 }
 664 
 665 static char *
 666 cpcgen_cfile_name(cpc_map_t *map)
 667 {
 668         char *name;
 669 
 670         if (asprintf(&name, "core_pcbe_%s.c", map->cmap_name) == -1) {
 671                 warn("failed to assemble file name for %s", map->cmap_path);
 672                 return (NULL);
 673         }
 674 
 675         return (name);
 676 }
 677 
 678 static boolean_t
 679 cpcgen_cfile_file_before(FILE *f, cpc_map_t *map)
 680 {
 681         if (fprintf(f, cpcgen_cfile_header, map->cmap_path) == -1) {
 682                 warn("failed to write header to temporary file for %s",
 683                     map->cmap_path);
 684                 return (B_FALSE);
 685         }
 686 
 687         if (fprintf(f, cpcgen_cfile_table_start, map->cmap_name) == -1) {
 688                 warn("failed to write header to temporary file for %s",
 689                     map->cmap_path);
 690                 return (B_FALSE);
 691         }
 692 
 693         return (B_TRUE);
 694 }
 695 
 696 static boolean_t
 697 cpcgen_cfile_file_after(FILE *f, cpc_map_t *map)
 698 {
 699         if (fprintf(f, cpcgen_cfile_table_end) == -1) {
 700                 warn("failed to write footer to temporary file for %s",
 701                     map->cmap_path);
 702                 return (B_FALSE);
 703         }
 704 
 705         return (B_TRUE);
 706 }
 707 
 708 static boolean_t
 709 cpcgen_cfile_event(FILE *f, nvlist_t *nvl, const char *path, uint_t ent)
 710 {
 711         char *ecode, *umask, *name, *counter, *lname, *cmask;
 712         size_t i;
 713 
 714         if (nvlist_lookup_string(nvl, "EventName", &name) != 0) {
 715                 warnx("Found event without 'EventName' property "
 716                     "in %s, entry %u", path, ent);
 717                 return (B_FALSE);
 718         }
 719 
 720         if (nvlist_lookup_string(nvl, "EventCode", &ecode) != 0 ||
 721             nvlist_lookup_string(nvl, "UMask", &umask) != 0 ||
 722             nvlist_lookup_string(nvl, "Counter", &counter) != 0) {
 723                 warnx("event %s (index %u) from %s, missing "
 724                     "required properties for C file translation",
 725                     name, ent, path);
 726                 return (B_FALSE);
 727         }
 728 
 729         /*
 730          * While we could try and parse the counters manually, just do this the
 731          * max power way for now based on all possible values.
 732          */
 733         if (strcmp(counter, "0") == 0 || strcmp(counter, "0,") == 0) {
 734                 cmask = "C0";
 735         } else if (strcmp(counter, "1") == 0) {
 736                 cmask = "C1";
 737         } else if (strcmp(counter, "2") == 0) {
 738                 cmask = "C2";
 739         } else if (strcmp(counter, "3") == 0) {
 740                 cmask = "C3";
 741         } else if (strcmp(counter, "0,1") == 0) {
 742                 cmask = "C0|C1";
 743         } else if (strcmp(counter, "0,1,2") == 0) {
 744                 cmask = "C0|C1|C2";
 745         } else if (strcmp(counter, "0,1,2,3") == 0) {
 746                 cmask = "C0|C1|C2|C3";
 747         } else if (strcmp(counter, "0,2,3") == 0) {
 748                 cmask = "C0|C2|C3";
 749         } else if (strcmp(counter, "1,2,3") == 0) {
 750                 cmask = "C1|C2|C3";
 751         } else if (strcmp(counter, "2,3") == 0) {
 752                 cmask = "C2|C3";
 753         } else {
 754                 warnx("event %s (index %u) from %s, has unknown "
 755                     "counter value \"%s\"", name, ent, path, counter);
 756                 return (B_FALSE);
 757         }
 758 
 759 
 760         /*
 761          * Intel uses capital names. CPC historically uses lower case names.
 762          */
 763         if ((lname = strdup(name)) == NULL) {
 764                 err(EXIT_FAILURE, "failed to duplicate event name %s", name);
 765         }
 766         for (i = 0; lname[i] != '\0'; i++) {
 767                 lname[i] = tolower(name[i]);
 768         }
 769 
 770         if (fprintf(f, "\t{ %s, %s, %s, \"%s\" },\n", ecode, umask, cmask,
 771             lname) == -1) {
 772                 warn("failed to write out entry %s from %s", name, path);
 773                 free(lname);
 774                 return (B_FALSE);
 775         }
 776 
 777         free(lname);
 778 
 779         /*
 780          * Check if we have any PAPI aliases.
 781          */
 782         for (i = 0; cpcgen_papi_map[i].cpapi_intc != NULL; i++) {
 783                 if (strcmp(name, cpcgen_papi_map[i].cpapi_intc) != 0)
 784                         continue;
 785 
 786                 if (fprintf(f, "\t{ %s, %s, %s, \"%s\" },\n", ecode, umask,
 787                     cmask, cpcgen_papi_map[i].cpapi_papi) == -1) {
 788                         warn("failed to write out entry %s from %s", name,
 789                             path);
 790                         return (B_FALSE);
 791                 }
 792         }
 793 
 794         return (B_TRUE);
 795 }
 796 
 797 /*
 798  * Generate a header file that declares all of these arrays and provide a map
 799  * for models to the corresponding table to use.
 800  */
 801 static void
 802 cpcgen_common_files(int dirfd)
 803 {
 804         const char *fname = "core_pcbe_cpcgen.h";
 805         char *tmpname;
 806         int fd;
 807         FILE *f;
 808         cpc_map_t *map;
 809 
 810         if (asprintf(&tmpname, ".%s.%d", fname, getpid()) == -1) {
 811                 err(EXIT_FAILURE, "failed to construct temporary file name");
 812         }
 813 
 814         if ((fd = openat(dirfd, tmpname, O_RDWR | O_CREAT, 0644)) < 0) {
 815                 err(EXIT_FAILURE, "failed to create temporary file %s",
 816                     tmpname);
 817         }
 818 
 819         if ((f = fdopen(fd, "w")) == NULL) {
 820                 int e = errno;
 821                 (void) unlinkat(dirfd, tmpname, 0);
 822                 errno = e;
 823                 err(EXIT_FAILURE, "failed to fdopen temporary file");
 824         }
 825 
 826         if (fprintf(f, cpcgen_cfile_header, cpcgen_mapfile) == -1) {
 827                 int e = errno;
 828                 (void) unlinkat(dirfd, tmpname, 0);
 829                 errno = e;
 830                 errx(EXIT_FAILURE, "failed to write header to temporary file "
 831                     "for %s", fname);
 832         }
 833 
 834         if (fprintf(f, "#ifndef _CORE_PCBE_CPCGEN_H\n"
 835             "#define\t_CORE_PCBE_CPCGEN_H\n"
 836             "\n"
 837             "#ifdef __cplusplus\n"
 838             "extern \"C\" {\n"
 839             "#endif\n"
 840             "\n"
 841             "extern const struct events_table_t *core_cpcgen_table(uint_t);\n"
 842             "\n") == -1) {
 843                 int e = errno;
 844                 (void) unlinkat(dirfd, tmpname, 0);
 845                 errno = e;
 846                 errx(EXIT_FAILURE, "failed to write header to "
 847                     "temporary file for %s", fname);
 848         }
 849 
 850         for (map = cpcgen_maps; map != NULL; map = map->cmap_next) {
 851                 if (fprintf(f, "extern const struct events_table_t "
 852                     "pcbe_core_events_%s[];\n", map->cmap_name) == -1) {
 853                         int e = errno;
 854                         (void) unlinkat(dirfd, tmpname, 0);
 855                         errno = e;
 856                         errx(EXIT_FAILURE, "failed to write entry to "
 857                             "temporary file for %s", fname);
 858                 }
 859         }
 860 
 861         if (fprintf(f, "\n"
 862             "#ifdef __cplusplus\n"
 863             "}\n"
 864             "#endif\n"
 865             "\n"
 866             "#endif /* _CORE_PCBE_CPCGEN_H */\n") == -1) {
 867                 int e = errno;
 868                 (void) unlinkat(dirfd, tmpname, 0);
 869                 errno = e;
 870                 errx(EXIT_FAILURE, "failed to write header to "
 871                     "temporary file for %s", fname);
 872         }
 873 
 874         if (fflush(f) != 0 || fclose(f) != 0) {
 875                 int e = errno;
 876                 (void) unlinkat(dirfd, tmpname, 0);
 877                 errno = e;
 878                 err(EXIT_FAILURE, "failed to flush and close temporary file");
 879         }
 880 
 881         if (renameat(dirfd, tmpname, dirfd, fname) != 0) {
 882                 err(EXIT_FAILURE, "failed to rename temporary file %s",
 883                     tmpname);
 884         }
 885 
 886         free(tmpname);
 887 
 888         /* Now again for the .c file. */
 889         fname = "core_pcbe_cpcgen.c";
 890         if (asprintf(&tmpname, ".%s.%d", fname, getpid()) == -1) {
 891                 err(EXIT_FAILURE, "failed to construct temporary file name");
 892         }
 893 
 894         if ((fd = openat(dirfd, tmpname, O_RDWR | O_CREAT, 0644)) < 0) {
 895                 err(EXIT_FAILURE, "failed to create temporary file %s",
 896                     tmpname);
 897         }
 898 
 899         if ((f = fdopen(fd, "w")) == NULL) {
 900                 int e = errno;
 901                 (void) unlinkat(dirfd, tmpname, 0);
 902                 errno = e;
 903                 err(EXIT_FAILURE, "failed to fdopen temporary file");
 904         }
 905 
 906         if (fprintf(f, cpcgen_cfile_header, cpcgen_mapfile) == -1) {
 907                 int e = errno;
 908                 (void) unlinkat(dirfd, tmpname, 0);
 909                 errno = e;
 910                 errx(EXIT_FAILURE, "failed to write header to temporary file "
 911                     "for %s", fname);
 912         }
 913 
 914         if (fprintf(f, "#include <core_pcbe_table.h>\n"
 915             "#include <sys/null.h>\n"
 916             "#include \"core_pcbe_cpcgen.h\"\n"
 917             "\n"
 918             "const struct events_table_t *\n"
 919             "core_cpcgen_table(uint_t model)\n"
 920             "{\n"
 921             "\tswitch (model) {\n") == -1) {
 922                 int e = errno;
 923                 (void) unlinkat(dirfd, tmpname, 0);
 924                 errno = e;
 925                 errx(EXIT_FAILURE, "failed to write header to "
 926                     "temporary file for %s", fname);
 927         }
 928 
 929         for (map = cpcgen_maps; map != NULL; map = map->cmap_next) {
 930                 cpc_proc_t *p;
 931                 for (p = map->cmap_procs; p != NULL; p = p->cproc_next) {
 932                         assert(p->cproc_family == 6);
 933                         if (fprintf(f, "\t\tcase 0x%x:\n", p->cproc_model) ==
 934                             -1) {
 935                                 int e = errno;
 936                                 (void) unlinkat(dirfd, tmpname, 0);
 937                                 errno = e;
 938                                 errx(EXIT_FAILURE, "failed to write header to "
 939                                     "temporary file for %s", fname);
 940                         }
 941                 }
 942                 if (fprintf(f, "\t\t\treturn (pcbe_core_events_%s);\n",
 943                     map->cmap_name) == -1) {
 944                         int e = errno;
 945                         (void) unlinkat(dirfd, tmpname, 0);
 946                         errno = e;
 947                         errx(EXIT_FAILURE, "failed to write entry to "
 948                             "temporary file for %s", fname);
 949                 }
 950         }
 951 
 952         if (fprintf(f, "\t\tdefault:\n"
 953             "\t\t\treturn (NULL);\n"
 954             "\t}\n"
 955             "}\n") == -1) {
 956                 int e = errno;
 957                 (void) unlinkat(dirfd, tmpname, 0);
 958                 errno = e;
 959                 errx(EXIT_FAILURE, "failed to write header to "
 960                     "temporary file for %s", fname);
 961         }
 962 
 963         if (fflush(f) != 0 || fclose(f) != 0) {
 964                 int e = errno;
 965                 (void) unlinkat(dirfd, tmpname, 0);
 966                 errno = e;
 967                 err(EXIT_FAILURE, "failed to flush and close temporary file");
 968         }
 969 
 970         if (renameat(dirfd, tmpname, dirfd, fname) != 0) {
 971                 err(EXIT_FAILURE, "failed to rename temporary file %s",
 972                     tmpname);
 973         }
 974 
 975         free(tmpname);
 976 }
 977 
 978 /*
 979  * Look at a rule to determine whether or not we should consider including it or
 980  * not. At this point we've already filtered things such that we only get core
 981  * events.
 982  *
 983  * To consider an entry, we currently apply the following criteria:
 984  *
 985  * - The MSRIndex and MSRValue are zero. Programming additional MSRs is no
 986  *   supported right now.
 987  * - TakenAlone is non-zero, which means that it cannot run at the same time as
 988  *   another field.
 989  * - Offcore is one, indicating that it is off the core and we need to figure
 990  *   out if we can support this.
 991  * - If the counter is fixed, don't use it for now.
 992  * - If more than one value is specified in the EventCode or UMask values
 993  */
 994 static boolean_t
 995 cpcgen_skip_entry(nvlist_t *nvl, const char *path, uint_t ent)
 996 {
 997         char *event, *msridx, *msrval, *taken, *offcore, *counter;
 998         char *ecode, *umask;
 999 
1000         /*
1001          * Require EventName, it's kind of useless without that.
1002          */
1003         if (nvlist_lookup_string(nvl, "EventName", &event) != 0) {
1004                 errx(EXIT_FAILURE, "Found event without 'EventName' property "
1005                     "in %s, entry %u", path, ent);
1006         }
1007 
1008         /*
1009          * If we can't find an expected value, whine about it.
1010          */
1011         if (nvlist_lookup_string(nvl, "MSRIndex", &msridx) != 0 ||
1012             nvlist_lookup_string(nvl, "MSRValue", &msrval) != 0 ||
1013             nvlist_lookup_string(nvl, "Counter", &counter) != 0 ||
1014             nvlist_lookup_string(nvl, "EventCode", &ecode) != 0 ||
1015             nvlist_lookup_string(nvl, "UMask", &umask) != 0 ||
1016             nvlist_lookup_string(nvl, "Offcore", &offcore) != 0) {
1017                 warnx("Skipping event %s (index %u) from %s, missing required "
1018                     "property", event, ent, path);
1019                 return (B_TRUE);
1020         }
1021 
1022         /*
1023          * MSRIndex and MSRvalue comes as either "0" or "0x00".
1024          */
1025         if ((strcmp(msridx, "0") != 0 && strcmp(msridx, "0x00") != 0) ||
1026             (strcmp(msrval, "0") != 0 && strcmp(msridx, "0x00") != 0) ||
1027             strcmp(offcore, "0") != 0 || strchr(ecode, ',') != NULL ||
1028             strchr(umask, ',') != NULL) {
1029                 return (B_TRUE);
1030         }
1031 
1032         /*
1033          * Unfortunately, not everything actually has "TakenAlone". If it
1034          * doesn't, we assume that it doesn't have to be.
1035          */
1036         if (nvlist_lookup_string(nvl, "TakenAlone", &taken) == 0 &&
1037             strcmp(taken, "0") != 0) {
1038                 return (B_TRUE);
1039         }
1040 
1041 
1042         if (strncasecmp(counter, "fixed", strlen("fixed")) == 0)
1043                 return (B_TRUE);
1044 
1045         return (B_FALSE);
1046 }
1047 
1048 /*
1049  * For each processor family, generate a data file that contains all of the
1050  * events that we support. Also generate a header that can be included that
1051  * declares all of the tables.
1052  */
1053 static void
1054 cpcgen_gen(int dirfd)
1055 {
1056         cpc_map_t *map = cpcgen_maps;
1057 
1058         if (map == NULL) {
1059                 errx(EXIT_FAILURE, "no platforms found or matched");
1060         }
1061 
1062         for (map = cpcgen_maps; map != NULL; map = map->cmap_next) {
1063                 int fd, ret;
1064                 FILE *f;
1065                 char *tmpname, *name;
1066                 uint32_t length, i;
1067 
1068                 if ((name = cpcgen_ops.cgen_op_name(map)) == NULL) {
1069                         exit(EXIT_FAILURE);
1070                 }
1071 
1072                 if (asprintf(&tmpname, ".%s.%d", name, getpid()) == -1) {
1073                         err(EXIT_FAILURE, "failed to construct temporary file "
1074                             "name");
1075                 }
1076 
1077                 if ((fd = openat(dirfd, tmpname, O_RDWR | O_CREAT, 0444)) < 0) {
1078                         err(EXIT_FAILURE, "failed to create temporary file %s",
1079                             tmpname);
1080                 }
1081 
1082                 if ((f = fdopen(fd, "w")) == NULL) {
1083                         int e = errno;
1084                         (void) unlinkat(dirfd, tmpname, 0);
1085                         errno = e;
1086                         err(EXIT_FAILURE, "failed to fdopen temporary file");
1087                 }
1088 
1089                 if (!cpcgen_ops.cgen_op_file_before(f, map)) {
1090                         (void) unlinkat(dirfd, tmpname, 0);
1091                         exit(EXIT_FAILURE);
1092                 }
1093 
1094                 /*
1095                  * Iterate over array contents.
1096                  */
1097                 if ((ret = nvlist_lookup_uint32(map->cmap_data, "length",
1098                     &length)) != 0) {
1099                         errx(EXIT_FAILURE, "failed to look up length property "
1100                             "in parsed data for %s: %s", map->cmap_path,
1101                             strerror(ret));
1102                 }
1103 
1104                 for (i = 0; i < length; i++) {
1105                         nvlist_t *nvl;
1106                         char num[64];
1107 
1108                         (void) snprintf(num, sizeof (num), "%u", i);
1109                         if ((ret = nvlist_lookup_nvlist(map->cmap_data,
1110                             num, &nvl)) != 0) {
1111                                 errx(EXIT_FAILURE, "failed to look up array "
1112                                     "entry %u in parsed data for %s: %s", i,
1113                                     map->cmap_path, strerror(ret));
1114                         }
1115 
1116                         if (cpcgen_skip_entry(nvl, map->cmap_path, i))
1117                                 continue;
1118 
1119                         if (!cpcgen_ops.cgen_op_event(f, nvl, map->cmap_path,
1120                             i)) {
1121                                 (void) unlinkat(dirfd, tmpname, 0);
1122                                 exit(EXIT_FAILURE);
1123                         }
1124                 }
1125 
1126                 if (!cpcgen_ops.cgen_op_file_after(f, map)) {
1127                         (void) unlinkat(dirfd, tmpname, 0);
1128                         exit(EXIT_FAILURE);
1129                 }
1130 
1131                 if (fflush(f) != 0 || fclose(f) != 0) {
1132                         int e = errno;
1133                         (void) unlinkat(dirfd, tmpname, 0);
1134                         errno = e;
1135                         err(EXIT_FAILURE, "failed to flush and close "
1136                             "temporary file");
1137                 }
1138 
1139                 if (renameat(dirfd, tmpname, dirfd, name) != 0) {
1140                         err(EXIT_FAILURE, "failed to rename temporary file %s",
1141                             tmpname);
1142                 }
1143 
1144                 free(name);
1145                 free(tmpname);
1146         }
1147 }
1148 
1149 static void
1150 cpcgen_usage(const char *fmt, ...)
1151 {
1152         if (fmt != NULL) {
1153                 va_list ap;
1154 
1155                 (void) fprintf(stderr, "%s: ", cpcgen_progname);
1156                 va_start(ap, fmt);
1157                 (void) vfprintf(stderr, fmt, ap);
1158                 va_end(ap);
1159         }
1160 
1161         (void) fprintf(stderr, "Usage: %s -a|-p platform -c|-H|-m -d datadir "
1162             "-o outdir\n"
1163             "\n"
1164             "\t-a  generate data for all platforms\n"
1165             "\t-c  generate C file for CPC\n"
1166             "\t-d  specify the directory containt perfmon data\n"
1167             "\t-h  generate header file and common files\n"
1168             "\t-m  generate manual pages for CPC data\n"
1169             "\t-o  outut files in directory outdir\n"
1170             "\t-p  generate data for a specified platform\n",
1171             cpcgen_progname);
1172 }
1173 
1174 int
1175 main(int argc, char *argv[])
1176 {
1177         int c, outdirfd;
1178         boolean_t do_mpage = B_FALSE, do_cfile = B_FALSE, do_header = B_FALSE,
1179             do_all = B_FALSE;
1180         const char *datadir = NULL, *outdir = NULL, *platform = NULL;
1181         uint_t count = 0;
1182 
1183         cpcgen_progname = basename(argv[0]);
1184 
1185         while ((c = getopt(argc, argv, ":acd:hHmo:p:")) != -1) {
1186                 switch (c) {
1187                 case 'a':
1188                         do_all = B_TRUE;
1189                         break;
1190                 case 'c':
1191                         do_cfile = B_TRUE;
1192                         break;
1193                 case 'd':
1194                         datadir = optarg;
1195                         break;
1196                 case 'm':
1197                         do_mpage = B_TRUE;
1198                         break;
1199                 case 'H':
1200                         do_header = B_TRUE;
1201                         break;
1202                 case 'o':
1203                         outdir = optarg;
1204                         break;
1205                 case 'p':
1206                         platform = optarg;
1207                         break;
1208                 case ':':
1209                         cpcgen_usage("Option -%c requires an operand\n",
1210                             optopt);
1211                         return (2);
1212                 case '?':
1213                         cpcgen_usage("Unknown option: -%c\n", optopt);
1214                         return (2);
1215                 case 'h':
1216                 default:
1217                         cpcgen_usage(NULL);
1218                         return (2);
1219                 }
1220         }
1221 
1222         count = 0;
1223         if (do_mpage)
1224                 count++;
1225         if (do_cfile)
1226                 count++;
1227         if (do_header)
1228                 count++;
1229         if (count > 1) {
1230                 cpcgen_usage("Only one of -c, -h, and -m may be specified\n");
1231                 return (2);
1232         } else if (count == 0) {
1233                 cpcgen_usage("One of -c, -h, and -m is required\n");
1234                 return (2);
1235         }
1236 
1237         count = 0;
1238         if (do_all)
1239                 count++;
1240         if (platform != NULL)
1241                 count++;
1242         if (count > 1) {
1243                 cpcgen_usage("Only one of -a and -p may be specified\n");
1244                 return (2);
1245         } else if (count == 0) {
1246                 cpcgen_usage("One of -a and -p is required\n");
1247                 return (2);
1248         }
1249 
1250 
1251         if (outdir == NULL) {
1252                 cpcgen_usage("Missing required output directory (-o)\n");
1253                 return (2);
1254         }
1255 
1256         if ((outdirfd = open(outdir, O_RDONLY)) < 0) {
1257                 err(EXIT_FAILURE, "failed to open output directory %s", outdir);
1258         }
1259 
1260         if (datadir == NULL) {
1261                 cpcgen_usage("Missing required data directory (-d)\n");
1262                 return (2);
1263         }
1264 
1265         cpcgen_read_mapfile(datadir, platform);
1266 
1267         if (do_header) {
1268                 cpcgen_common_files(outdirfd);
1269                 return (0);
1270         }
1271 
1272         if (do_mpage) {
1273                 cpcgen_ops.cgen_op_name = cpcgen_manual_name;
1274                 cpcgen_ops.cgen_op_file_before = cpcgen_manual_file_before;
1275                 cpcgen_ops.cgen_op_file_after = cpcgen_manual_file_after;
1276                 cpcgen_ops.cgen_op_event = cpcgen_manual_event;
1277         }
1278 
1279         if (do_cfile) {
1280                 cpcgen_ops.cgen_op_name = cpcgen_cfile_name;
1281                 cpcgen_ops.cgen_op_file_before = cpcgen_cfile_file_before;
1282                 cpcgen_ops.cgen_op_file_after = cpcgen_cfile_file_after;
1283                 cpcgen_ops.cgen_op_event = cpcgen_cfile_event;
1284         }
1285 
1286 
1287         cpcgen_gen(outdirfd);
1288 
1289         return (0);
1290 }