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 } 1291