xref: /illumos-gate/usr/src/tools/cpcgen/cpcgen.c (revision d0e58ef5)
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 program transforms Intel perfmon and AMD PMC data files into C files and
18  * manual pages.
19  */
20 
21 #include <stdio.h>
22 #include <stdarg.h>
23 #include <unistd.h>
24 #include <err.h>
25 #include <libgen.h>
26 #include <libnvpair.h>
27 #include <strings.h>
28 #include <errno.h>
29 #include <limits.h>
30 #include <sys/mman.h>
31 #include <sys/param.h>
32 #include <assert.h>
33 #include <ctype.h>
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <fcntl.h>
37 #include <dirent.h>
38 
39 #include <json_nvlist.h>
40 
41 #define	EXIT_USAGE	2
42 #define	CPROC_MAX_STEPPINGS	16
43 
44 typedef enum {
45 	CPCGEN_MODE_UNKNOWN = 0,
46 	CPCGEN_MODE_INTEL,
47 	CPCGEN_MODE_AMD
48 } cpc_mode_t;
49 
50 typedef struct cpc_proc {
51 	struct cpc_proc *cproc_next;
52 	uint_t		cproc_family;
53 	uint_t		cproc_model;
54 	uint_t		cproc_nsteps;
55 	uint_t		cproc_steppings[CPROC_MAX_STEPPINGS];
56 } cpc_proc_t;
57 
58 typedef enum cpc_file_type {
59 	CPC_FILE_CORE		= 1 << 0,
60 	CPC_FILE_OFF_CORE	= 1 << 1,
61 	CPC_FILE_UNCORE		= 1 << 2,
62 	CPC_FILE_FP_MATH	= 1 << 3,
63 	CPC_FILE_UNCORE_EXP	= 1 << 4
64 } cpc_type_t;
65 
66 typedef struct cpc_map {
67 	struct cpc_map	*cmap_next;
68 	cpc_type_t	cmap_type;
69 	nvlist_t	*cmap_data;
70 	char		*cmap_path;
71 	const char	*cmap_name;
72 	cpc_proc_t	*cmap_procs;
73 } cpc_map_t;
74 
75 typedef struct cpc_whitelist {
76 	const char	*cwhite_short;
77 	const char	*cwhite_human;
78 	uint_t		cwhite_mask;
79 } cpc_whitelist_t;
80 
81 /*
82  * List of architectures that we support generating this data for. This is done
83  * so that processors that illumos doesn't support or run on aren't generated
84  * (generally the Xeon Phi).
85  */
86 static cpc_whitelist_t cpcgen_intel_whitelist[] = {
87 	/* Nehalem */
88 	{ "NHM-EP", "nhm_ep", CPC_FILE_CORE },
89 	{ "NHM-EX", "nhm_ex", CPC_FILE_CORE },
90 	/* Westmere */
91 	{ "WSM-EP-DP", "wsm_ep_dp", CPC_FILE_CORE },
92 	{ "WSM-EP-SP", "wsm_ep_sp", CPC_FILE_CORE },
93 	{ "WSM-EX", "wsm_ex", CPC_FILE_CORE },
94 	/* Sandy Bridge */
95 	{ "SNB", "snb", CPC_FILE_CORE },
96 	{ "JKT", "jkt", CPC_FILE_CORE },
97 	/* Ivy Bridge */
98 	{ "IVB", "ivb", CPC_FILE_CORE },
99 	{ "IVT", "ivt", CPC_FILE_CORE },
100 	/* Haswell */
101 	{ "HSW", "hsw", CPC_FILE_CORE },
102 	{ "HSX", "hsx", CPC_FILE_CORE },
103 	/* Broadwell */
104 	{ "BDW", "bdw", CPC_FILE_CORE },
105 	{ "BDW-DE", "bdw_de", CPC_FILE_CORE },
106 	{ "BDX", "bdx", CPC_FILE_CORE },
107 	/* Skylake */
108 	{ "SKL", "skl", CPC_FILE_CORE },
109 	{ "SKX", "skx", CPC_FILE_CORE },
110 	/* Cascade Lake */
111 	{ "CLX", "clx", CPC_FILE_CORE },
112 	/* Atom */
113 	{ "BNL", "bnl", CPC_FILE_CORE },
114 	{ "SLM", "slm", CPC_FILE_CORE },
115 	{ "GLM", "glm", CPC_FILE_CORE },
116 	{ "GLP", "glp", CPC_FILE_CORE },
117 	{ NULL }
118 };
119 
120 typedef struct cpc_papi {
121 	const char	*cpapi_intc;
122 	const char	*cpapi_papi;
123 } cpc_papi_t;
124 
125 /*
126  * This table maps events with an Intel specific name to the corresponding PAPI
127  * name. There may be multiple Intel events which map to the same PAPI event.
128  * This is usually because different processors have different names for an
129  * event. We use the title as opposed to the event codes because those can
130  * change somewhat arbitrarily between processor generations.
131  */
132 static cpc_papi_t cpcgen_intel_papi_map[] = {
133 	{ "CPU_CLK_UNHALTED.THREAD_P", "PAPI_tot_cyc" },
134 	{ "INST_RETIRED.ANY_P", "PAPI_tot_ins" },
135 	{ "BR_INST_RETIRED.ALL_BRANCHES", "PAPI_br_ins" },
136 	{ "BR_MISP_RETIRED.ALL_BRANCHES", "PAPI_br_msp" },
137 	{ "BR_INST_RETIRED.CONDITIONAL", "PAPI_br_cn" },
138 	{ "CYCLE_ACTIVITY.CYCLES_L1D_MISS", "PAPI_l1_dcm" },
139 	{ "L1I.HITS", "PAPI_l1_ich" },
140 	{ "ICACHE.HIT", "PAPI_l1_ich" },
141 	{ "L1I.MISS", "PAPI_L1_icm" },
142 	{ "ICACHE.MISSES", "PAPI_l1_icm" },
143 	{ "L1I.READS", "PAPI_l1_ica" },
144 	{ "ICACHE.ACCESSES", "PAPI_l1_ica" },
145 	{ "L1I.READS", "PAPI_l1_icr" },
146 	{ "ICACHE.ACCESSES", "PAPI_l1_icr" },
147 	{ "L2_RQSTS.CODE_RD_MISS", "PAPI_l2_icm" },
148 	{ "L2_RQSTS.MISS", "PAPI_l2_tcm" },
149 	{ "ITLB_MISSES.MISS_CAUSES_A_WALK", "PAPI_tlb_im" },
150 	{ "DTLB_LOAD_MISSES.MISS_CAUSES_A_WALK", "PAPI_tlb_dm" },
151 	{ "PAGE_WALKS.D_SIDE_WALKS", "PAPI_tlb_dm" },
152 	{ "PAGE_WALKS.I_SIDE_WALKS", "PAPI_tlb_im" },
153 	{ "PAGE_WALKS.WALKS", "PAPI_tlb_tl" },
154 	{ "INST_QUEUE_WRITES", "PAPI_tot_iis" },
155 	{ "MEM_INST_RETIRED.STORES" "PAPI_sr_ins" },
156 	{ "MEM_INST_RETIRED.LOADS" "PAPI_ld_ins" },
157 	{ NULL, NULL }
158 };
159 
160 typedef struct cpcgen_ops {
161 	void (*cgen_op_gather)(const char *, const char *);
162 	void (*cgen_op_common)(int);
163 	char *(*cgen_op_name)(cpc_map_t *);
164 	boolean_t (*cgen_op_skip)(nvlist_t *, const char *, uint_t);
165 	boolean_t (*cgen_op_file_before)(FILE *, cpc_map_t *);
166 	boolean_t (*cgen_op_file_after)(FILE *, cpc_map_t *);
167 	boolean_t (*cgen_op_event)(FILE *, nvlist_t *, const char *, uint32_t);
168 } cpcgen_ops_t;
169 
170 static cpcgen_ops_t cpcgen_ops;
171 static const char *cpcgen_intel_mapfile = "/mapfile.csv";
172 static const char *cpcgen_progname;
173 static cpc_map_t *cpcgen_maps;
174 static cpc_mode_t cpcgen_mode = CPCGEN_MODE_UNKNOWN;
175 
176 /*
177  * Constants used for generating data.
178  */
179 /* BEGIN CSTYLED */
180 static const char *cpcgen_cfile_intel_header = ""
181 "/*\n"
182 " *  Copyright (c) 2018, Intel Corporation\n"
183 " *  Copyright (c) 2018, Joyent, Inc\n"
184 " *  All rights reserved.\n"
185 " *\n"
186 " *  Redistribution and use in source and binary forms, with or without\n"
187 " *  modification, are permitted provided that the following conditions are met:\n"
188 " * \n"
189 " *   1. Redistributions of source code must retain the above copyright notice,\n"
190 " *      this list of conditions and the following disclaimer.\n"
191 " * \n"
192 " *   2. Redistributions in binary form must reproduce the above copyright \n"
193 " *      notice, this list of conditions and the following disclaimer in the\n"
194 " *      documentation and/or other materials provided with the distribution.\n"
195 " * \n"
196 " *   3. Neither the name of the Intel Corporation nor the names of its \n"
197 " *      contributors may be used to endorse or promote products derived from\n"
198 " *      this software without specific prior written permission.\n"
199 " *\n"
200 " *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n"
201 " *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n"
202 " *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n"
203 " *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE\n"
204 " *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n"
205 " *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n"
206 " *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n"
207 " *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n"
208 " *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n"
209 " *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n"
210 " *  POSSIBILITY OF SUCH DAMAGE.\n"
211 " *\n"
212 " * This file was automatically generated by cpcgen from the data file\n"
213 " * data/perfmon%s\n"
214 " *\n"
215 " * Do not modify this file. Your changes will be lost!\n"
216 " */\n"
217 "\n";
218 /* END CSTYLED */
219 
220 static const char *cpcgen_cfile_intel_table_start = ""
221 "#include <core_pcbe_table.h>\n"
222 "\n"
223 "const struct events_table_t pcbe_core_events_%s[] = {\n";
224 
225 static const char *cpcgen_cfile_intel_table_end = ""
226 "\t{ NT_END, 0, 0, \"\" }\n"
227 "};\n";
228 
229 /* BEGIN CSTYLED */
230 static const char *cpcgen_manual_intel_intel_header = ""
231 ".\\\" Copyright (c) 2018, Intel Corporation \n"
232 ".\\\" Copyright (c) 2018, Joyent, Inc.\n"
233 ".\\\" All rights reserved.\n"
234 ".\\\"\n"
235 ".\\\" Redistribution and use in source and binary forms, with or without \n"
236 ".\\\" modification, are permitted provided that the following conditions are met:\n"
237 ".\\\"\n"
238 ".\\\"  1. Redistributions of source code must retain the above copyright notice,\n"
239 ".\\\"     this list of conditions and the following disclaimer.\n"
240 ".\\\"\n"
241 ".\\\"  2. Redistributions in binary form must reproduce the above copyright\n"
242 ".\\\"     notice, this list of conditions and the following disclaimer in the\n"
243 ".\\\"     documentation and/or other materials provided with the distribution.\n"
244 ".\\\"\n"
245 ".\\\"  3. Neither the name of the Intel Corporation nor the names of its\n"
246 ".\\\"     contributors may be used to endorse or promote products derived from\n"
247 ".\\\"     this software without specific prior written permission.\n"
248 ".\\\"\n"
249 ".\\\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n"
250 ".\\\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n"
251 ".\\\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n"
252 ".\\\" ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE\n"
253 ".\\\" LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n"
254 ".\\\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n"
255 ".\\\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n"
256 ".\\\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n"
257 ".\\\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n"
258 ".\\\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n"
259 ".\\\" POSSIBILITY OF SUCH DAMAGE.\n"
260 ".\\\"\n"
261 ".\\\" This file was automatically generated by cpcgen from the data file\n"
262 ".\\\" data/perfmon%s\n"
263 ".\\\"\n"
264 ".\\\" Do not modify this file. Your changes will be lost!\n"
265 ".\\\"\n"
266 ".\\\" We would like to thank Intel for providing the perfmon data for use in\n"
267 ".\\\" our manual pages.\n"
268 ".Dd June 18, 2018\n"
269 ".Dt %s_EVENTS 3CPC\n"
270 ".Os\n"
271 ".Sh NAME\n"
272 ".Nm %s_events\n"
273 ".Nd processor model specific performance counter events\n"
274 ".Sh DESCRIPTION\n"
275 "This manual page describes events specific to the following Intel CPU\n"
276 "models and is derived from Intel's perfmon data.\n"
277 "For more information, please consult the Intel Software Developer's Manual "
278 "or Intel's perfmon website.\n"
279 ".Pp\n"
280 "CPU models described by this document:\n"
281 ".Bl -bullet\n";
282 /* END CSTYLED */
283 
284 static const char *cpcgen_manual_intel_data = ""
285 ".El\n"
286 ".Pp\n"
287 "The following events are supported:\n"
288 ".Bl -tag -width Sy\n";
289 
290 static const char *cpcgen_manual_intel_trailer = ""
291 ".El\n"
292 ".Sh SEE ALSO\n"
293 ".Xr cpc 3CPC\n"
294 ".Pp\n"
295 ".Lk https://download.01.org/perfmon/index/";
296 
297 static const char *cpcgen_cfile_cddl_header = ""
298 "/*\n"
299 " * This file and its contents are supplied under the terms of the\n"
300 " * Common Development and Distribution License (\"CDDL\"), version 1.0.\n"
301 " * You may only use this file in accordance with the terms of version\n"
302 " * 1.0 of the CDDL.\n"
303 " *\n"
304 " * A full copy of the text of the CDDL should have accompanied this\n"
305 " * source.  A copy of the CDDL is also available via the Internet at\n"
306 " * http://www.illumos.org/license/CDDL.\n"
307 " */\n"
308 "\n"
309 "/*\n"
310 " * Copyright 2019 Joyent, Inc\n"
311 " */\n"
312 "\n"
313 "/*\n"
314 " * This file was automatically generated by cpcgen.\n"
315 " */\n"
316 "\n"
317 "/*\n"
318 " * Do not modify this file. Your changes will be lost!\n"
319 " */\n"
320 "\n";
321 
322 static const char *cpcgen_manual_amd_header = ""
323 ".\\\" This file was automatically generated by cpcgen from the data file\n"
324 ".\\\" data/amdpmc/%s\n"
325 ".\\\"\n"
326 ".\\\" Do not modify this file. Your changes will be lost!\n"
327 ".\\\"\n"
328 ".\\\" We would like to thank AMD for providing the PMC data for use in\n"
329 ".\\\" our manual pages.\n"
330 ".Dd March 25, 2019\n"
331 ".Dt AMD_%s_EVENTS 3CPC\n"
332 ".Os\n"
333 ".Sh NAME\n"
334 ".Nm amd_%s_events\n"
335 ".Nd AMD family %s processor performance monitoring events\n"
336 ".Sh DESCRIPTION\n"
337 "This manual page describes events specfic to AMD family %s processors.\n"
338 "For more information, please consult the appropriate AMD BIOS and Kernel\n"
339 "Developer's guide or Open-Source Register Reference manual.\n"
340 ".Pp\n"
341 "Each of the events listed below includes the AMD mnemonic which matches\n"
342 "the name found in the AMD manual and a brief summary of the event.\n"
343 "If available, a more detailed description of the event follows and then\n"
344 "any additional unit values that modify the event.\n"
345 "Each unit can be combined to create a new event in the system by placing\n"
346 "the '.' character between the event name and the unit name.\n"
347 ".Pp\n"
348 "The following events are supported:\n"
349 ".Bl -tag -width Sy\n";
350 
351 static const char *cpcgen_manual_amd_trailer = ""
352 ".El\n"
353 ".Sh SEE ALSO\n"
354 ".Xr cpc 3CPC\n";
355 
356 static const char *cpcgen_cfile_amd_header = ""
357 "/*\n"
358 " * This file was automatically generated by cpcgen from the data file\n"
359 " * data/perfmon%s\n"
360 " *\n"
361 " * Do not modify this file. Your changes will be lost!\n"
362 " */\n"
363 "\n";
364 
365 static const char *cpcgen_cfile_amd_table_start = ""
366 "#include <opteron_pcbe_table.h>\n"
367 "#include <sys/null.h>\n"
368 "\n"
369 "const amd_event_t opteron_pcbe_%s_events[] = {\n";
370 
371 static const char *cpcgen_cfile_amd_table_end = ""
372 "\t{ NULL, 0, 0 }\n"
373 "};\n";
374 
375 static cpc_map_t *
376 cpcgen_map_lookup(const char *path)
377 {
378 	cpc_map_t *m;
379 
380 	for (m = cpcgen_maps; m != NULL; m = m->cmap_next) {
381 		if (strcmp(path, m->cmap_path) == 0) {
382 			return (m);
383 		}
384 	}
385 
386 	return (NULL);
387 }
388 
389 /*
390  * Parse a string of the form 'GenuineIntel-6-2E' and get out the family and
391  * model.
392  */
393 static void
394 cpcgen_parse_model(char *fsr, uint_t *family, uint_t *model, uint_t *nstepp,
395     uint_t *steppings)
396 {
397 	const char *bstr = "GenuineIntel";
398 	const char *brand, *fam, *mod, *step;
399 	char *last;
400 	long l;
401 	uint_t nstep = 0;
402 
403 	/*
404 	 * Tokeninze the string. There may be an optional stepping portion,
405 	 * which has a range of steppings enclosed by '[' and ']' characters.
406 	 * While the other parts are required, the stepping may be missing.
407 	 */
408 	if ((brand = strtok_r(fsr, "-", &last)) == NULL ||
409 	    (fam = strtok_r(NULL, "-", &last)) == NULL ||
410 	    (mod = strtok_r(NULL, "-", &last)) == NULL) {
411 		errx(EXIT_FAILURE, "failed to parse processor id \"%s\"", fsr);
412 	}
413 	step = strtok_r(NULL, "-", &last);
414 
415 	if (strcmp(bstr, brand) != 0) {
416 		errx(EXIT_FAILURE, "brand string \"%s\" did not match \"%s\"",
417 		    brand, bstr);
418 	}
419 
420 	errno = 0;
421 	l = strtol(fam, &last, 16);
422 	if (errno != 0 || l < 0 || l > UINT_MAX || *last != '\0') {
423 		errx(EXIT_FAILURE, "failed to parse family \"%s\"", fam);
424 	}
425 	*family = (uint_t)l;
426 
427 	l = strtol(mod, &last, 16);
428 	if (errno != 0 || l < 0 || l > UINT_MAX || *last != '\0') {
429 		errx(EXIT_FAILURE, "failed to parse model \"%s\"", mod);
430 	}
431 	*model = (uint_t)l;
432 
433 	if (step == NULL) {
434 		*nstepp = 0;
435 		return;
436 	}
437 
438 	if (*step != '[' || ((last = strrchr(step, ']')) == NULL)) {
439 		errx(EXIT_FAILURE, "failed to parse stepping \"%s\": missing "
440 		    "stepping range brackets", step);
441 	}
442 	step++;
443 	*last = '\0';
444 	while (*step != '\0') {
445 		if (!isxdigit(*step)) {
446 			errx(EXIT_FAILURE, "failed to parse stepping: invalid "
447 			    "stepping identifier '0x%x'", *step);
448 		}
449 
450 		if (nstep >= CPROC_MAX_STEPPINGS) {
451 			errx(EXIT_FAILURE, "failed to parse stepping: "
452 			    "encountered too many steppings");
453 		}
454 
455 		switch (*step) {
456 		case '0':
457 			steppings[nstep] = 0x0;
458 			break;
459 		case '1':
460 			steppings[nstep] = 0x1;
461 			break;
462 		case '2':
463 			steppings[nstep] = 0x2;
464 			break;
465 		case '3':
466 			steppings[nstep] = 0x3;
467 			break;
468 		case '4':
469 			steppings[nstep] = 0x4;
470 			break;
471 		case '5':
472 			steppings[nstep] = 0x5;
473 			break;
474 		case '6':
475 			steppings[nstep] = 0x6;
476 			break;
477 		case '7':
478 			steppings[nstep] = 0x7;
479 			break;
480 		case '8':
481 			steppings[nstep] = 0x8;
482 			break;
483 		case '9':
484 			steppings[nstep] = 0x9;
485 			break;
486 		case 'a':
487 		case 'A':
488 			steppings[nstep] = 0xa;
489 			break;
490 		case 'b':
491 		case 'B':
492 			steppings[nstep] = 0xb;
493 			break;
494 		case 'c':
495 		case 'C':
496 			steppings[nstep] = 0xc;
497 			break;
498 		case 'd':
499 		case 'D':
500 			steppings[nstep] = 0xd;
501 			break;
502 		case 'e':
503 		case 'E':
504 			steppings[nstep] = 0xe;
505 			break;
506 		case 'f':
507 		case 'F':
508 			steppings[nstep] = 0xf;
509 			break;
510 		default:
511 			errx(EXIT_FAILURE, "encountered non-hex stepping "
512 			    "character: '%c'", *step);
513 		}
514 		nstep++;
515 		step++;
516 	}
517 
518 	*nstepp = nstep;
519 }
520 
521 static nvlist_t *
522 cpcgen_read_datafile(const char *datadir, const char *file)
523 {
524 	int fd;
525 	char *path;
526 	struct stat st;
527 	void *map;
528 	nvlist_t *nvl;
529 	nvlist_parse_json_error_t jerr;
530 
531 	if (asprintf(&path, "%s/%s", datadir, file) == -1) {
532 		err(EXIT_FAILURE, "failed to construct path to data file %s",
533 		    file);
534 	}
535 
536 	if ((fd = open(path, O_RDONLY)) < 0) {
537 		err(EXIT_FAILURE, "failed to open data file %s", path);
538 	}
539 
540 	if (fstat(fd, &st) != 0) {
541 		err(EXIT_FAILURE, "failed to stat %s", path);
542 	}
543 
544 	if ((map = mmap(NULL, st.st_size, PROT_READ | PROT_WRITE, MAP_PRIVATE,
545 	    fd, 0)) == MAP_FAILED) {
546 		err(EXIT_FAILURE, "failed to mmap %s", path);
547 	}
548 
549 	if (nvlist_parse_json(map, st.st_size, &nvl, NVJSON_FORCE_INTEGER,
550 	    &jerr) != 0) {
551 		errx(EXIT_FAILURE, "failed to parse file %s at pos %ld: %s",
552 		    path, jerr.nje_pos, jerr.nje_message);
553 	}
554 
555 	if (munmap(map, st.st_size) != 0) {
556 		err(EXIT_FAILURE, "failed to munmap %s", path);
557 	}
558 
559 	if (close(fd) != 0) {
560 		err(EXIT_FAILURE, "failed to close data file %s", path);
561 	}
562 	free(path);
563 
564 	return (nvl);
565 }
566 
567 /*
568  * Check the whitelist to see if we should use this model.
569  */
570 static const char *
571 cpcgen_use_arch(const char *path, cpc_type_t type, const char *platform)
572 {
573 	const char *slash;
574 	size_t len;
575 	uint_t i;
576 
577 	if (*path != '/') {
578 		errx(EXIT_FAILURE, "invalid path in mapfile: \"%s\": missing "
579 		    "leading '/'", path);
580 	}
581 	if ((slash = strchr(path + 1, '/')) == NULL) {
582 		errx(EXIT_FAILURE, "invalid path in mapfile: \"%s\": missing "
583 		    "second '/'", path);
584 	}
585 	/* Account for the last '/' character. */
586 	len = slash - path - 1;
587 	assert(len > 0);
588 
589 	for (i = 0; cpcgen_intel_whitelist[i].cwhite_short != NULL; i++) {
590 		if (platform != NULL && strcasecmp(platform,
591 		    cpcgen_intel_whitelist[i].cwhite_short) != 0)
592 			continue;
593 		if (strncmp(path + 1, cpcgen_intel_whitelist[i].cwhite_short,
594 		    len) == 0 &&
595 		    (cpcgen_intel_whitelist[i].cwhite_mask & type) == type) {
596 			return (cpcgen_intel_whitelist[i].cwhite_human);
597 		}
598 	}
599 
600 	return (NULL);
601 }
602 
603 /*
604  * Determine which CPU Vendor we're transmuting data from.
605  */
606 static void
607 cpcgen_determine_vendor(const char *datadir)
608 {
609 	char *mappath;
610 	struct stat st;
611 
612 	if (asprintf(&mappath, "%s/%s", datadir, cpcgen_intel_mapfile) == -1) {
613 		err(EXIT_FAILURE, "failed to construct path to mapfile");
614 	}
615 
616 	if (stat(mappath, &st) == 0) {
617 		cpcgen_mode = CPCGEN_MODE_INTEL;
618 	} else {
619 		if (errno != ENOENT) {
620 			err(EXIT_FAILURE, "stat(2) of %s failed unexpectedly");
621 		}
622 
623 		cpcgen_mode = CPCGEN_MODE_AMD;
624 	}
625 
626 	free(mappath);
627 }
628 
629 /*
630  * Read in all the data files that exist for AMD.
631  *
632  * Our family names for AMD systems are based on the family and type so a given
633  * name will look like f17h_core.json.
634  */
635 static void
636 cpcgen_read_amd(const char *datadir, const char *platform)
637 {
638 	DIR *dir;
639 	struct dirent *d;
640 	const char *suffix = ".json";
641 	const size_t slen = strlen(suffix);
642 
643 	if ((dir = opendir(datadir)) == NULL) {
644 		err(EXIT_FAILURE, "failed to open directory %s", datadir);
645 	}
646 
647 	while ((d = readdir(dir)) != NULL) {
648 		char *name, *c;
649 		cpc_map_t *map;
650 		nvlist_t *parsed;
651 
652 		if ((name = strdup(d->d_name)) == NULL) {
653 			errx(EXIT_FAILURE, "ran out of memory duplicating "
654 			    "name %s", d->d_name);
655 		}
656 		c = strstr(name, suffix);
657 
658 		if (c == NULL) {
659 			free(name);
660 			continue;
661 		}
662 
663 		if (*(c + slen) != '\0') {
664 			free(name);
665 			continue;
666 		}
667 
668 		*c = '\0';
669 		c = strchr(name, '_');
670 		if (c == NULL) {
671 			free(name);
672 			continue;
673 		}
674 		*c = '\0';
675 		c++;
676 		if (strcmp(c, "core") != 0) {
677 			errx(EXIT_FAILURE, "unexpected AMD JSON file name: %s",
678 			    d->d_name);
679 		}
680 
681 		if (platform != NULL && strcmp(platform, name) != 0) {
682 			free(name);
683 			continue;
684 		}
685 
686 		if ((map = calloc(1, sizeof (cpc_map_t))) == NULL) {
687 			err(EXIT_FAILURE, "failed to allocate space for cpc "
688 			    "file");
689 		}
690 
691 		parsed = cpcgen_read_datafile(datadir, d->d_name);
692 		if ((map->cmap_path = strdup(d->d_name)) == NULL) {
693 			err(EXIT_FAILURE, "failed to duplicate path string");
694 		}
695 		map->cmap_type = CPC_FILE_CORE;
696 		map->cmap_data = parsed;
697 		map->cmap_name = name;
698 		map->cmap_procs = NULL;
699 
700 		map->cmap_next = cpcgen_maps;
701 		cpcgen_maps = map;
702 	}
703 }
704 
705 /*
706  * Read in the mapfile.csv that is used to map between processor families and
707  * parse this. Each line has a comma separated value.
708  */
709 static void
710 cpcgen_read_intel(const char *datadir, const char *platform)
711 {
712 	FILE *map;
713 	char *mappath, *last;
714 	char *data = NULL;
715 	size_t datalen = 0;
716 	uint_t lineno;
717 
718 	if (asprintf(&mappath, "%s/%s", datadir, cpcgen_intel_mapfile) == -1) {
719 		err(EXIT_FAILURE, "failed to construct path to mapfile");
720 	}
721 
722 	if ((map = fopen(mappath, "r")) == NULL) {
723 		err(EXIT_FAILURE, "failed to open data mapfile %s", mappath);
724 	}
725 
726 	lineno = 0;
727 	while (getline(&data, &datalen, map) != -1) {
728 		char *fstr, *path, *tstr;
729 		const char *name;
730 		uint_t family, model, nsteps;
731 		uint_t steppings[CPROC_MAX_STEPPINGS];
732 
733 		cpc_type_t type;
734 		cpc_map_t *map;
735 		cpc_proc_t *proc;
736 
737 		/*
738 		 * The first line contains the header:
739 		 * Family-model,Version,Filename,EventType
740 		 */
741 		lineno++;
742 		if (lineno == 1) {
743 			continue;
744 		}
745 
746 		if ((fstr = strtok_r(data, ",", &last)) == NULL ||
747 		    strtok_r(NULL, ",", &last) == NULL ||
748 		    (path = strtok_r(NULL, ",", &last)) == NULL ||
749 		    (tstr = strtok_r(NULL, "\n", &last)) == NULL) {
750 			errx(EXIT_FAILURE, "failed to parse mapfile line "
751 			    "%u in %s", lineno, mappath);
752 		}
753 
754 		cpcgen_parse_model(fstr, &family, &model, &nsteps, steppings);
755 
756 		if (strcmp(tstr, "core") == 0) {
757 			type = CPC_FILE_CORE;
758 		} else if (strcmp(tstr, "offcore") == 0) {
759 			type = CPC_FILE_OFF_CORE;
760 		} else if (strcmp(tstr, "uncore") == 0) {
761 			type = CPC_FILE_UNCORE;
762 		} else if (strcmp(tstr, "fp_arith_inst") == 0) {
763 			type = CPC_FILE_FP_MATH;
764 		} else if (strcmp(tstr, "uncore experimental") == 0) {
765 			type = CPC_FILE_UNCORE_EXP;
766 		} else {
767 			errx(EXIT_FAILURE, "unknown file type \"%s\" on line "
768 			    "%u", tstr, lineno);
769 		}
770 
771 		if ((name = cpcgen_use_arch(path, type, platform)) == NULL)
772 			continue;
773 
774 		if ((map = cpcgen_map_lookup(path)) == NULL) {
775 			nvlist_t *parsed;
776 
777 			parsed = cpcgen_read_datafile(datadir, path);
778 
779 			if ((map = calloc(1, sizeof (cpc_map_t))) == NULL) {
780 				err(EXIT_FAILURE, "failed to allocate space "
781 				    "for cpc file");
782 			}
783 
784 			if ((map->cmap_path = strdup(path)) == NULL) {
785 				err(EXIT_FAILURE, "failed to duplicate path "
786 				    "string");
787 			}
788 
789 			map->cmap_type = type;
790 			map->cmap_data = parsed;
791 			map->cmap_name = name;
792 			map->cmap_procs = NULL;
793 
794 			map->cmap_next = cpcgen_maps;
795 			cpcgen_maps = map;
796 		}
797 
798 		if ((proc = calloc(1, sizeof (cpc_proc_t))) == NULL) {
799 			err(EXIT_FAILURE, "failed to allocate memory for "
800 			    "family and model tracking");
801 		}
802 
803 		proc->cproc_family = family;
804 		proc->cproc_model = model;
805 		proc->cproc_nsteps = nsteps;
806 		if (nsteps > 0) {
807 			bcopy(steppings, proc->cproc_steppings,
808 			    sizeof (steppings));
809 		}
810 		proc->cproc_next = map->cmap_procs;
811 		map->cmap_procs = proc;
812 	}
813 
814 	if (errno != 0 || ferror(map)) {
815 		err(EXIT_FAILURE, "failed to read %s", mappath);
816 	}
817 
818 	if (fclose(map) == EOF) {
819 		err(EXIT_FAILURE, "failed to close %s", mappath);
820 	}
821 	free(data);
822 	free(mappath);
823 }
824 
825 static char *
826 cpcgen_manual_intel_name(cpc_map_t *map)
827 {
828 	char *name;
829 
830 	if (asprintf(&name, "%s_events.3cpc", map->cmap_name) == -1) {
831 		warn("failed to assemble manual page name for %s",
832 		    map->cmap_path);
833 		return (NULL);
834 	}
835 
836 	return (name);
837 }
838 
839 static boolean_t
840 cpcgen_manual_intel_file_before(FILE *f, cpc_map_t *map)
841 {
842 	size_t i;
843 	char *upper;
844 	cpc_proc_t *proc;
845 
846 	if ((upper = strdup(map->cmap_name)) == NULL) {
847 		warn("failed to duplicate manual name for %s", map->cmap_name);
848 		return (B_FALSE);
849 	}
850 
851 	for (i = 0; upper[i] != '\0'; i++) {
852 		upper[i] = toupper(upper[i]);
853 	}
854 
855 	if (fprintf(f, cpcgen_manual_intel_intel_header, map->cmap_path, upper,
856 	    map->cmap_name) == -1) {
857 		warn("failed to write out manual header for %s",
858 		    map->cmap_name);
859 		free(upper);
860 		return (B_FALSE);
861 	}
862 	free(upper);
863 
864 	for (proc = map->cmap_procs; proc != NULL; proc = proc->cproc_next) {
865 		if (proc->cproc_nsteps > 0) {
866 			uint_t step;
867 
868 			for (step = 0; step < proc->cproc_nsteps; step++) {
869 				if (fprintf(f, ".It\n.Sy Family 0x%x, Model "
870 				    "0x%x, Stepping 0x%x\n",
871 				    proc->cproc_family, proc->cproc_model,
872 				    proc->cproc_steppings[step]) == -1) {
873 					warn("failed to write out model "
874 					    "information for %s",
875 					    map->cmap_name);
876 					return (B_FALSE);
877 				}
878 			}
879 		} else {
880 			if (fprintf(f, ".It\n.Sy Family 0x%x, Model 0x%x\n",
881 			    proc->cproc_family, proc->cproc_model) == -1) {
882 				warn("failed to write out model information "
883 				    "for %s", map->cmap_name);
884 				return (B_FALSE);
885 			}
886 		}
887 	}
888 
889 	if (fprintf(f, cpcgen_manual_intel_data) == -1) {
890 		warn("failed to write out manual header for %s",
891 		    map->cmap_name);
892 		return (B_FALSE);
893 	}
894 
895 	free(upper);
896 	return (B_TRUE);
897 }
898 
899 static boolean_t
900 cpcgen_manual_intel_file_after(FILE *f, cpc_map_t *map)
901 {
902 	if (fprintf(f, cpcgen_manual_intel_trailer) == -1) {
903 		warn("failed to write out manual header for %s",
904 		    map->cmap_name);
905 		return (B_FALSE);
906 	}
907 
908 	return (B_TRUE);
909 }
910 
911 static boolean_t
912 cpcgen_manual_intel_event(FILE *f, nvlist_t *nvl, const char *path,
913     uint32_t ent)
914 {
915 	char *event, *lname, *brief = NULL, *public = NULL, *errata = NULL;
916 	size_t i;
917 
918 	if (nvlist_lookup_string(nvl, "EventName", &event) != 0) {
919 		warnx("Found event without 'EventName' property "
920 		    "in %s, entry %u", path, ent);
921 		return (B_FALSE);
922 	}
923 
924 	/*
925 	 * Intel uses capital names. CPC historically uses lower case names.
926 	 */
927 	if ((lname = strdup(event)) == NULL) {
928 		err(EXIT_FAILURE, "failed to duplicate event name %s", event);
929 	}
930 	for (i = 0; lname[i] != '\0'; i++) {
931 		lname[i] = tolower(event[i]);
932 	}
933 
934 	/*
935 	 * Try to get the other event fields, but if they're not there, don't
936 	 * worry about it.
937 	 */
938 	(void) nvlist_lookup_string(nvl, "BriefDescription", &brief);
939 	(void) nvlist_lookup_string(nvl, "PublicDescription", &public);
940 	(void) nvlist_lookup_string(nvl, "Errata", &errata);
941 	if (errata != NULL && (strcmp(errata, "0") == 0 ||
942 	    strcmp(errata, "null") == 0)) {
943 		errata = NULL;
944 	}
945 
946 	if (fprintf(f, ".It Sy %s\n", lname) == -1) {
947 		warn("failed to write out event entry %s", event);
948 		free(lname);
949 		return (B_FALSE);
950 	}
951 
952 	if (public != NULL) {
953 		if (fprintf(f, "%s\n", public) == -1) {
954 			warn("failed to write out event entry %s", event);
955 			free(lname);
956 			return (B_FALSE);
957 		}
958 	} else if (brief != NULL) {
959 		if (fprintf(f, "%s\n", brief) == -1) {
960 			warn("failed to write out event entry %s", event);
961 			free(lname);
962 			return (B_FALSE);
963 		}
964 	}
965 
966 	if (errata != NULL) {
967 		if (fprintf(f, ".Pp\nThe following errata may apply to this: "
968 		    "%s\n", errata) == -1) {
969 
970 			warn("failed to write out event entry %s", event);
971 			free(lname);
972 			return (B_FALSE);
973 		}
974 	}
975 
976 	free(lname);
977 	return (B_TRUE);
978 }
979 
980 static char *
981 cpcgen_cfile_intel_name(cpc_map_t *map)
982 {
983 	char *name;
984 
985 	if (asprintf(&name, "core_pcbe_%s.c", map->cmap_name) == -1) {
986 		warn("failed to assemble file name for %s", map->cmap_path);
987 		return (NULL);
988 	}
989 
990 	return (name);
991 }
992 
993 static boolean_t
994 cpcgen_cfile_intel_before(FILE *f, cpc_map_t *map)
995 {
996 	if (fprintf(f, cpcgen_cfile_intel_header, map->cmap_path) == -1) {
997 		warn("failed to write header to temporary file for %s",
998 		    map->cmap_path);
999 		return (B_FALSE);
1000 	}
1001 
1002 	if (fprintf(f, cpcgen_cfile_intel_table_start, map->cmap_name) == -1) {
1003 		warn("failed to write header to temporary file for %s",
1004 		    map->cmap_path);
1005 		return (B_FALSE);
1006 	}
1007 
1008 	return (B_TRUE);
1009 }
1010 
1011 static boolean_t
1012 cpcgen_cfile_intel_after(FILE *f, cpc_map_t *map)
1013 {
1014 	if (fprintf(f, cpcgen_cfile_intel_table_end) == -1) {
1015 		warn("failed to write footer to temporary file for %s",
1016 		    map->cmap_path);
1017 		return (B_FALSE);
1018 	}
1019 
1020 	return (B_TRUE);
1021 }
1022 
1023 static boolean_t
1024 cpcgen_cfile_intel_event(FILE *f, nvlist_t *nvl, const char *path, uint_t ent)
1025 {
1026 	char *ecode, *umask, *name, *counter, *lname, *cmask;
1027 	size_t i;
1028 
1029 	if (nvlist_lookup_string(nvl, "EventName", &name) != 0) {
1030 		warnx("Found event without 'EventName' property "
1031 		    "in %s, entry %u", path, ent);
1032 		return (B_FALSE);
1033 	}
1034 
1035 	if (nvlist_lookup_string(nvl, "EventCode", &ecode) != 0 ||
1036 	    nvlist_lookup_string(nvl, "UMask", &umask) != 0 ||
1037 	    nvlist_lookup_string(nvl, "Counter", &counter) != 0) {
1038 		warnx("event %s (index %u) from %s, missing "
1039 		    "required properties for C file translation",
1040 		    name, ent, path);
1041 		return (B_FALSE);
1042 	}
1043 
1044 	/*
1045 	 * While we could try and parse the counters manually, just do this the
1046 	 * max power way for now based on all possible values.
1047 	 */
1048 	if (strcmp(counter, "0") == 0 || strcmp(counter, "0,") == 0) {
1049 		cmask = "C0";
1050 	} else if (strcmp(counter, "1") == 0) {
1051 		cmask = "C1";
1052 	} else if (strcmp(counter, "2") == 0) {
1053 		cmask = "C2";
1054 	} else if (strcmp(counter, "3") == 0) {
1055 		cmask = "C3";
1056 	} else if (strcmp(counter, "0,1") == 0) {
1057 		cmask = "C0|C1";
1058 	} else if (strcmp(counter, "0,1,2") == 0) {
1059 		cmask = "C0|C1|C2";
1060 	} else if (strcmp(counter, "0,1,2,3") == 0) {
1061 		cmask = "C0|C1|C2|C3";
1062 	} else if (strcmp(counter, "0,2,3") == 0) {
1063 		cmask = "C0|C2|C3";
1064 	} else if (strcmp(counter, "1,2,3") == 0) {
1065 		cmask = "C1|C2|C3";
1066 	} else if (strcmp(counter, "2,3") == 0) {
1067 		cmask = "C2|C3";
1068 	} else {
1069 		warnx("event %s (index %u) from %s, has unknown "
1070 		    "counter value \"%s\"", name, ent, path, counter);
1071 		return (B_FALSE);
1072 	}
1073 
1074 
1075 	/*
1076 	 * Intel uses capital names. CPC historically uses lower case names.
1077 	 */
1078 	if ((lname = strdup(name)) == NULL) {
1079 		err(EXIT_FAILURE, "failed to duplicate event name %s", name);
1080 	}
1081 	for (i = 0; lname[i] != '\0'; i++) {
1082 		lname[i] = tolower(name[i]);
1083 	}
1084 
1085 	if (fprintf(f, "\t{ %s, %s, %s, \"%s\" },\n", ecode, umask, cmask,
1086 	    lname) == -1) {
1087 		warn("failed to write out entry %s from %s", name, path);
1088 		free(lname);
1089 		return (B_FALSE);
1090 	}
1091 
1092 	free(lname);
1093 
1094 	/*
1095 	 * Check if we have any PAPI aliases.
1096 	 */
1097 	for (i = 0; cpcgen_intel_papi_map[i].cpapi_intc != NULL; i++) {
1098 		if (strcmp(name, cpcgen_intel_papi_map[i].cpapi_intc) != 0)
1099 			continue;
1100 
1101 		if (fprintf(f, "\t{ %s, %s, %s, \"%s\" },\n", ecode, umask,
1102 		    cmask, cpcgen_intel_papi_map[i].cpapi_papi) == -1) {
1103 			warn("failed to write out entry %s from %s", name,
1104 			    path);
1105 			return (B_FALSE);
1106 		}
1107 	}
1108 
1109 	return (B_TRUE);
1110 }
1111 
1112 static boolean_t
1113 cpcgen_generate_map(FILE *f, cpc_map_t *map, boolean_t start)
1114 {
1115 	cpc_proc_t *p;
1116 
1117 	if (fprintf(f, "\t%sif (", start ? "" : "} else ") == -1) {
1118 		return (B_FALSE);
1119 	}
1120 
1121 	for (p = map->cmap_procs; p != NULL; p = p->cproc_next) {
1122 		/*
1123 		 * Make sure the line is padded so the generated C code looks
1124 		 * like reasonable C style.
1125 		 */
1126 		if (p != map->cmap_procs) {
1127 			if (fputs("\t    ", f) == -1) {
1128 				return (B_FALSE);
1129 			}
1130 		}
1131 
1132 		if (p->cproc_nsteps > 0) {
1133 			uint_t i;
1134 
1135 			if (fprintf(f, "(model == 0x%x &&\n\t    (",
1136 			    p->cproc_model) == -1) {
1137 				return (B_FALSE);
1138 			}
1139 
1140 			for (i = 0; i < p->cproc_nsteps; i++) {
1141 				if (fprintf(f, "stepping == 0x%x%s",
1142 				    p->cproc_steppings[i],
1143 				    i + 1 != p->cproc_nsteps ?
1144 				    " ||\n\t    " : "") == -1) {
1145 					return (B_FALSE);
1146 				}
1147 			}
1148 
1149 			if (fputs("))", f) == -1) {
1150 				return (B_FALSE);
1151 			}
1152 		} else if (fprintf(f, "model == 0x%x", p->cproc_model) == -1) {
1153 			return (B_FALSE);
1154 		}
1155 
1156 		if (fprintf(f, "%s\n",
1157 		    p->cproc_next != NULL ? " ||" : ") {") == -1) {
1158 			return (B_FALSE);
1159 		}
1160 	}
1161 
1162 	if (fprintf(f, "\t\t\treturn (pcbe_core_events_%s);\n",
1163 	    map->cmap_name) == -1) {
1164 		return (B_FALSE);
1165 	}
1166 
1167 	return (B_TRUE);
1168 }
1169 
1170 /*
1171  * This is a wrapper around unlinkat that makes sure that we don't clobber
1172  * errno, which is used for properly printing out error messages below.
1173  */
1174 static void
1175 cpcgen_remove_tmpfile(int dirfd, const char *path)
1176 {
1177 	int e = errno;
1178 	(void) unlinkat(dirfd, path, 0);
1179 	errno = e;
1180 }
1181 
1182 /*
1183  * Generate a header file that declares all of these arrays and provide a map
1184  * for models to the corresponding table to use.
1185  */
1186 static void
1187 cpcgen_common_intel_files(int dirfd)
1188 {
1189 	const char *fname = "core_pcbe_cpcgen.h";
1190 	char *tmpname;
1191 	int fd;
1192 	FILE *f;
1193 	cpc_map_t *map;
1194 
1195 	if (asprintf(&tmpname, ".%s.%d", fname, getpid()) == -1) {
1196 		err(EXIT_FAILURE, "failed to construct temporary file name");
1197 	}
1198 
1199 	if ((fd = openat(dirfd, tmpname, O_RDWR | O_CREAT, 0644)) < 0) {
1200 		err(EXIT_FAILURE, "failed to create temporary file %s",
1201 		    tmpname);
1202 	}
1203 
1204 	if ((f = fdopen(fd, "w")) == NULL) {
1205 		cpcgen_remove_tmpfile(dirfd, tmpname);
1206 		err(EXIT_FAILURE, "failed to fdopen temporary file");
1207 	}
1208 
1209 	if (fprintf(f, cpcgen_cfile_intel_header, cpcgen_intel_mapfile) == -1) {
1210 		cpcgen_remove_tmpfile(dirfd, tmpname);
1211 		errx(EXIT_FAILURE, "failed to write header to temporary file "
1212 		    "for %s", fname);
1213 	}
1214 
1215 	if (fprintf(f, "#ifndef _CORE_PCBE_CPCGEN_H\n"
1216 	    "#define\t_CORE_PCBE_CPCGEN_H\n"
1217 	    "\n"
1218 	    "#ifdef __cplusplus\n"
1219 	    "extern \"C\" {\n"
1220 	    "#endif\n"
1221 	    "\n"
1222 	    "extern const struct events_table_t *core_cpcgen_table(uint_t, "
1223 	    "uint_t);\n"
1224 	    "\n") == -1) {
1225 		cpcgen_remove_tmpfile(dirfd, tmpname);
1226 		errx(EXIT_FAILURE, "failed to write header to "
1227 		    "temporary file for %s", fname);
1228 	}
1229 
1230 	for (map = cpcgen_maps; map != NULL; map = map->cmap_next) {
1231 		if (fprintf(f, "extern const struct events_table_t "
1232 		    "pcbe_core_events_%s[];\n", map->cmap_name) == -1) {
1233 			cpcgen_remove_tmpfile(dirfd, tmpname);
1234 			errx(EXIT_FAILURE, "failed to write entry to "
1235 			    "temporary file for %s", fname);
1236 		}
1237 	}
1238 
1239 	if (fprintf(f, "\n"
1240 	    "#ifdef __cplusplus\n"
1241 	    "}\n"
1242 	    "#endif\n"
1243 	    "\n"
1244 	    "#endif /* _CORE_PCBE_CPCGEN_H */\n") == -1) {
1245 		cpcgen_remove_tmpfile(dirfd, tmpname);
1246 		errx(EXIT_FAILURE, "failed to write header to "
1247 		    "temporary file for %s", fname);
1248 	}
1249 
1250 	if (fflush(f) != 0 || fclose(f) != 0) {
1251 		cpcgen_remove_tmpfile(dirfd, tmpname);
1252 		err(EXIT_FAILURE, "failed to flush and close temporary file");
1253 	}
1254 
1255 	if (renameat(dirfd, tmpname, dirfd, fname) != 0) {
1256 		err(EXIT_FAILURE, "failed to rename temporary file %s",
1257 		    tmpname);
1258 	}
1259 
1260 	free(tmpname);
1261 
1262 	/* Now again for the .c file. */
1263 	fname = "core_pcbe_cpcgen.c";
1264 	if (asprintf(&tmpname, ".%s.%d", fname, getpid()) == -1) {
1265 		err(EXIT_FAILURE, "failed to construct temporary file name");
1266 	}
1267 
1268 	if ((fd = openat(dirfd, tmpname, O_RDWR | O_CREAT, 0644)) < 0) {
1269 		err(EXIT_FAILURE, "failed to create temporary file %s",
1270 		    tmpname);
1271 	}
1272 
1273 	if ((f = fdopen(fd, "w")) == NULL) {
1274 		cpcgen_remove_tmpfile(dirfd, tmpname);
1275 		err(EXIT_FAILURE, "failed to fdopen temporary file");
1276 	}
1277 
1278 	if (fprintf(f, cpcgen_cfile_intel_header, cpcgen_intel_mapfile) == -1) {
1279 		cpcgen_remove_tmpfile(dirfd, tmpname);
1280 		errx(EXIT_FAILURE, "failed to write header to temporary file "
1281 		    "for %s", fname);
1282 	}
1283 
1284 	if (fprintf(f, "#include <core_pcbe_table.h>\n"
1285 	    "#include <sys/null.h>\n"
1286 	    "#include \"core_pcbe_cpcgen.h\"\n"
1287 	    "\n"
1288 	    "const struct events_table_t *\n"
1289 	    "core_cpcgen_table(uint_t model, uint_t stepping)\n"
1290 	    "{\n") == -1) {
1291 		cpcgen_remove_tmpfile(dirfd, tmpname);
1292 		errx(EXIT_FAILURE, "failed to write header to "
1293 		    "temporary file for %s", fname);
1294 	}
1295 
1296 	for (map = cpcgen_maps; map != NULL; map = map->cmap_next) {
1297 		if (!cpcgen_generate_map(f, map, map == cpcgen_maps)) {
1298 			cpcgen_remove_tmpfile(dirfd, tmpname);
1299 			errx(EXIT_FAILURE, "failed to write to temporary "
1300 			    "file for %s", fname);
1301 		}
1302 	}
1303 
1304 	if (fprintf(f, "\t} else {\n"
1305 	    "\t\t\treturn (NULL);\n"
1306 	    "\t}\n"
1307 	    "}\n") == -1) {
1308 		cpcgen_remove_tmpfile(dirfd, tmpname);
1309 		errx(EXIT_FAILURE, "failed to write header to "
1310 		    "temporary file for %s", fname);
1311 	}
1312 
1313 	if (fflush(f) != 0 || fclose(f) != 0) {
1314 		cpcgen_remove_tmpfile(dirfd, tmpname);
1315 		err(EXIT_FAILURE, "failed to flush and close temporary file");
1316 	}
1317 
1318 	if (renameat(dirfd, tmpname, dirfd, fname) != 0) {
1319 		err(EXIT_FAILURE, "failed to rename temporary file %s",
1320 		    tmpname);
1321 	}
1322 
1323 	free(tmpname);
1324 }
1325 
1326 /*
1327  * Look at a rule to determine whether or not we should consider including it or
1328  * not. At this point we've already filtered things such that we only get core
1329  * events.
1330  *
1331  * To consider an entry, we currently apply the following criteria:
1332  *
1333  * - The MSRIndex and MSRValue are zero. Programming additional MSRs is no
1334  *   supported right now.
1335  * - TakenAlone is non-zero, which means that it cannot run at the same time as
1336  *   another field.
1337  * - Offcore is one, indicating that it is off the core and we need to figure
1338  *   out if we can support this.
1339  * - If the counter is fixed, don't use it for now.
1340  * - If more than one value is specified in the EventCode or UMask values
1341  */
1342 static boolean_t
1343 cpcgen_skip_intel_entry(nvlist_t *nvl, const char *path, uint_t ent)
1344 {
1345 	char *event, *msridx, *msrval, *taken, *offcore, *counter;
1346 	char *ecode, *umask;
1347 
1348 	/*
1349 	 * Require EventName, it's kind of useless without that.
1350 	 */
1351 	if (nvlist_lookup_string(nvl, "EventName", &event) != 0) {
1352 		errx(EXIT_FAILURE, "Found event without 'EventName' property "
1353 		    "in %s, entry %u", path, ent);
1354 	}
1355 
1356 	/*
1357 	 * If we can't find an expected value, whine about it.
1358 	 */
1359 	if (nvlist_lookup_string(nvl, "MSRIndex", &msridx) != 0 ||
1360 	    nvlist_lookup_string(nvl, "MSRValue", &msrval) != 0 ||
1361 	    nvlist_lookup_string(nvl, "Counter", &counter) != 0 ||
1362 	    nvlist_lookup_string(nvl, "EventCode", &ecode) != 0 ||
1363 	    nvlist_lookup_string(nvl, "UMask", &umask) != 0 ||
1364 	    nvlist_lookup_string(nvl, "Offcore", &offcore) != 0) {
1365 		warnx("Skipping event %s (index %u) from %s, missing required "
1366 		    "property", event, ent, path);
1367 		return (B_TRUE);
1368 	}
1369 
1370 	/*
1371 	 * MSRIndex and MSRvalue comes as either "0" or "0x00".
1372 	 */
1373 	if ((strcmp(msridx, "0") != 0 && strcmp(msridx, "0x00") != 0) ||
1374 	    (strcmp(msrval, "0") != 0 && strcmp(msridx, "0x00") != 0) ||
1375 	    strcmp(offcore, "0") != 0 || strchr(ecode, ',') != NULL ||
1376 	    strchr(umask, ',') != NULL) {
1377 		return (B_TRUE);
1378 	}
1379 
1380 	/*
1381 	 * Unfortunately, not everything actually has "TakenAlone". If it
1382 	 * doesn't, we assume that it doesn't have to be.
1383 	 */
1384 	if (nvlist_lookup_string(nvl, "TakenAlone", &taken) == 0 &&
1385 	    strcmp(taken, "0") != 0) {
1386 		return (B_TRUE);
1387 	}
1388 
1389 
1390 	if (strncasecmp(counter, "fixed", strlen("fixed")) == 0)
1391 		return (B_TRUE);
1392 
1393 	return (B_FALSE);
1394 }
1395 static char *
1396 cpcgen_manual_amd_name(cpc_map_t *map)
1397 {
1398 	char *name;
1399 
1400 	if (asprintf(&name, "amd_%s_events.3cpc", map->cmap_name) == -1) {
1401 		warn("failed to assemble file name for %s", map->cmap_path);
1402 		return (NULL);
1403 	}
1404 
1405 	return (name);
1406 }
1407 
1408 static boolean_t
1409 cpcgen_manual_amd_file_before(FILE *f, cpc_map_t *map)
1410 {
1411 	size_t i;
1412 	char *upper;
1413 	const char *family;
1414 
1415 	if ((upper = strdup(map->cmap_name)) == NULL) {
1416 		warn("failed to duplicate manual name for %s", map->cmap_name);
1417 		return (B_FALSE);
1418 	}
1419 
1420 	for (i = 0; upper[i] != '\0'; i++) {
1421 		upper[i] = toupper(upper[i]);
1422 	}
1423 
1424 	family = map->cmap_name + 1;
1425 
1426 	if (fprintf(f, cpcgen_manual_amd_header, map->cmap_path, upper,
1427 	    family, family, family) == -1) {
1428 		warn("failed to write out manual header for %s",
1429 		    map->cmap_name);
1430 		free(upper);
1431 		return (B_FALSE);
1432 	}
1433 
1434 	free(upper);
1435 	return (B_TRUE);
1436 }
1437 
1438 static boolean_t
1439 cpcgen_manual_amd_file_after(FILE *f, cpc_map_t *map)
1440 {
1441 	if (fprintf(f, cpcgen_manual_amd_trailer) == -1) {
1442 		warn("failed to write out manual header for %s",
1443 		    map->cmap_name);
1444 		return (B_FALSE);
1445 	}
1446 
1447 	return (B_TRUE);
1448 }
1449 
1450 static boolean_t
1451 cpcgen_manual_amd_event(FILE *f, nvlist_t *nvl, const char *path, uint32_t ent)
1452 {
1453 	char *name, *mnemonic = NULL, *summary = NULL, *desc = NULL;
1454 	char *umode;
1455 	nvlist_t *units = NULL;
1456 	uint32_t i, length;
1457 
1458 	if (nvlist_lookup_string(nvl, "name", &name) != 0) {
1459 		warnx("Found event without 'name' property in %s, entry %u",
1460 		    path, ent);
1461 		return (B_FALSE);
1462 	}
1463 
1464 	if (nvlist_lookup_string(nvl, "mnemonic", &mnemonic) != 0 ||
1465 	    nvlist_lookup_string(nvl, "summary", &summary) != 0) {
1466 		warnx("event %s in %s, entry %u, missing required fields",
1467 		    name, path, ent);
1468 		return (B_FALSE);
1469 	}
1470 
1471 	/*
1472 	 * Allow the other fields to be missing.
1473 	 */
1474 	(void) nvlist_lookup_string(nvl, "description", &desc);
1475 	(void) nvlist_lookup_nvlist(nvl, "units", &units);
1476 
1477 	if (fprintf(f, ".It Sy %s\n", name) == -1) {
1478 		warn("failed to write out event entry %s", name);
1479 	}
1480 
1481 	if (fprintf(f, ".Sy %s -\n"
1482 	    "%s\n", mnemonic, summary) == -1) {
1483 		warn("failed to write out event entry %s", name);
1484 		return (B_FALSE);
1485 	}
1486 
1487 	if (desc != NULL) {
1488 		if (fprintf(f, ".Pp\n%s\n", desc) == -1) {
1489 			warn("failed to write out event entry %s", name);
1490 			return (B_FALSE);
1491 		}
1492 	}
1493 
1494 	if (units == NULL)
1495 		return (B_TRUE);
1496 
1497 	/*
1498 	 * Skip units we don't know how to handle.
1499 	 */
1500 	if (nvlist_lookup_string(nvl, "unit_mode", &umode) == 0) {
1501 		return (B_TRUE);
1502 	}
1503 
1504 	if (fprintf(f, ".Pp\n"
1505 	    "This event has the following units which may be used\n"
1506 	    "to modify the behavior of the event:\n"
1507 	    ".Bl -tag -width Sy\n") == -1) {
1508 		warn("failed to write out event entry %s", name);
1509 		return (B_FALSE);
1510 	}
1511 
1512 	if (nvlist_lookup_uint32(units, "length", &length) != 0) {
1513 		warnx("found units array, but could not look up length "
1514 		    "property for events %s (index %u) in file %s",
1515 		    name, ent, path);
1516 		return (B_FALSE);
1517 	}
1518 
1519 	for (i = 0; i < length; i++) {
1520 		nvlist_t *uvl;
1521 		char num[64];
1522 		char *uname, *udesc = NULL;
1523 
1524 		(void) snprintf(num, sizeof (num), "%u", i);
1525 		if (nvlist_lookup_nvlist(units, num, &uvl) != 0) {
1526 			warnx("failed to look up unit %u for event %s (index "
1527 			    "%u) in file %s", i, name, ent, path);
1528 			return (B_FALSE);
1529 		}
1530 
1531 		if (nvlist_lookup_string(uvl, "name", &uname) != 0) {
1532 			warnx("failed to find required members for unit array "
1533 			    "entry %u of event %s (index %u) from file %s",
1534 			    i, name, ent, path);
1535 			return (B_FALSE);
1536 		}
1537 		(void) nvlist_lookup_string(uvl, "description", &udesc);
1538 		if (fprintf(f, ".It Sy %s\n", uname) == -1) {
1539 			warn("failed to write out event entry %s", name);
1540 			return (B_FALSE);
1541 		}
1542 
1543 		if (udesc != NULL) {
1544 			if (fprintf(f, "%s\n", udesc) == -1) {
1545 				warn("failed to write out event entry %s",
1546 				    name);
1547 				return (B_FALSE);
1548 			}
1549 		}
1550 	}
1551 
1552 	if (fprintf(f, ".El\n") == -1) {
1553 		warn("failed to write out event entry %s",
1554 		    name);
1555 		return (B_FALSE);
1556 	}
1557 
1558 	return (B_TRUE);
1559 }
1560 
1561 static char *
1562 cpcgen_cfile_amd_name(cpc_map_t *map)
1563 {
1564 	char *name;
1565 
1566 	if (asprintf(&name, "opteron_pcbe_%s.c", map->cmap_name) == -1) {
1567 		warn("failed to assemble file name for %s", map->cmap_path);
1568 		return (NULL);
1569 	}
1570 
1571 	return (name);
1572 }
1573 
1574 /*
1575  * Generate a header file that can be used to synthesize the data events we care
1576  * about.
1577  */
1578 static void
1579 cpcgen_common_amd_files(int dirfd)
1580 {
1581 	const char *fname = "opteron_pcbe_cpcgen.h";
1582 	char *tmpname;
1583 	int fd;
1584 	FILE *f;
1585 	cpc_map_t *map;
1586 
1587 	if (asprintf(&tmpname, ".%s.%d", fname, getpid()) == -1) {
1588 		err(EXIT_FAILURE, "failed to construct temporary file name");
1589 	}
1590 
1591 	if ((fd = openat(dirfd, tmpname, O_RDWR | O_CREAT, 0644)) < 0) {
1592 		err(EXIT_FAILURE, "failed to create temporary file %s",
1593 		    tmpname);
1594 	}
1595 
1596 	if ((f = fdopen(fd, "w")) == NULL) {
1597 		cpcgen_remove_tmpfile(dirfd, tmpname);
1598 		err(EXIT_FAILURE, "failed to fdopen temporary file");
1599 	}
1600 
1601 	if (fprintf(f, cpcgen_cfile_cddl_header) == -1) {
1602 		cpcgen_remove_tmpfile(dirfd, tmpname);
1603 		err(EXIT_FAILURE, "failed to write header to "
1604 		    "temporary file for %s", fname);
1605 	}
1606 
1607 	if (fprintf(f, "#ifndef _OPTERON_PCBE_CPCGEN_H\n"
1608 	    "#define\t_OPTERON_PCBE_CPCGEN_H\n"
1609 	    "\n"
1610 	    "#ifdef __cplusplus\n"
1611 	    "extern \"C\" {\n"
1612 	    "#endif\n"
1613 	    "\n") == -1) {
1614 		cpcgen_remove_tmpfile(dirfd, tmpname);
1615 		err(EXIT_FAILURE, "failed to write header to "
1616 		    "temporary file for %s", fname);
1617 	}
1618 
1619 	for (map = cpcgen_maps; map != NULL; map = map->cmap_next) {
1620 		if (fprintf(f, "extern const amd_event_t "
1621 		    "opteron_pcbe_%s_events[];\n", map->cmap_name) == -1) {
1622 			cpcgen_remove_tmpfile(dirfd, tmpname);
1623 			err(EXIT_FAILURE, "failed to write header to "
1624 			    "temporary file for %s", fname);
1625 		}
1626 	}
1627 
1628 	if (fprintf(f, "\n"
1629 	    "#ifdef __cplusplus\n"
1630 	    "}\n"
1631 	    "#endif\n"
1632 	    "\n"
1633 	    "#endif /* _OPTERON_PCBE_CPCGEN_H */\n") == -1) {
1634 		cpcgen_remove_tmpfile(dirfd, tmpname);
1635 		err(EXIT_FAILURE, "failed to write header to "
1636 		    "temporary file for %s", fname);
1637 	}
1638 
1639 
1640 
1641 	if (fflush(f) != 0 || fclose(f) != 0) {
1642 		cpcgen_remove_tmpfile(dirfd, tmpname);
1643 		err(EXIT_FAILURE, "failed to flush and close temporary file");
1644 	}
1645 
1646 	if (renameat(dirfd, tmpname, dirfd, fname) != 0) {
1647 		err(EXIT_FAILURE, "failed to rename temporary file %s",
1648 		    tmpname);
1649 	}
1650 
1651 	free(tmpname);
1652 }
1653 
1654 static boolean_t
1655 cpcgen_cfile_amd_before(FILE *f, cpc_map_t *map)
1656 {
1657 	if (fprintf(f, cpcgen_cfile_amd_header, map->cmap_name) == -1) {
1658 		warn("failed to write header to temporary file for %s",
1659 		    map->cmap_path);
1660 		return (B_FALSE);
1661 	}
1662 
1663 	if (fprintf(f, cpcgen_cfile_amd_table_start, map->cmap_name) == -1) {
1664 		warn("failed to write header to temporary file for %s",
1665 		    map->cmap_path);
1666 		return (B_FALSE);
1667 	}
1668 
1669 
1670 	return (B_TRUE);
1671 }
1672 
1673 static boolean_t
1674 cpcgen_cfile_amd_after(FILE *f, cpc_map_t *map)
1675 {
1676 	if (fprintf(f, cpcgen_cfile_amd_table_end) == -1) {
1677 		warn("failed to write footer to temporary file for %s",
1678 		    map->cmap_path);
1679 		return (B_FALSE);
1680 	}
1681 
1682 	return (B_TRUE);
1683 }
1684 
1685 static boolean_t
1686 cpcgen_cfile_amd_event(FILE *f, nvlist_t *nvl, const char *path, uint_t ent)
1687 {
1688 	char *name, *code, *umode;
1689 	uint32_t i, length;
1690 	nvlist_t *units;
1691 
1692 	if (nvlist_lookup_string(nvl, "name", &name) != 0) {
1693 		warnx("Found event without 'name' property in %s, entry %u",
1694 		    path, ent);
1695 		return (B_FALSE);
1696 	}
1697 
1698 	if (nvlist_lookup_string(nvl, "code", &code) != 0) {
1699 		warnx("event %s (index %u) from %s missing required properties "
1700 		    "for C translation", name, path, ent);
1701 		return (B_FALSE);
1702 	}
1703 
1704 	if (fprintf(f, "\t{ \"%s\", %s, 0 },\n", name, code) == -1) {
1705 		warn("failed to write out entry %s from %s", name, path);
1706 		return (B_FALSE);
1707 	}
1708 
1709 	/*
1710 	 * The 'units' array is optional. If the rule has a specific 'unit_mode'
1711 	 * indicating how the units should be combined, skip that. We don't know
1712 	 * how to properly process that right now.
1713 	 */
1714 	if (nvlist_lookup_nvlist(nvl, "units", &units) != 0) {
1715 		return (B_TRUE);
1716 	}
1717 
1718 	if (nvlist_lookup_string(nvl, "unit_mode", &umode) == 0) {
1719 		return (B_TRUE);
1720 	}
1721 
1722 	if (nvlist_lookup_uint32(units, "length", &length) != 0) {
1723 		warnx("found units array, but could not look up length "
1724 		    "property for events %s (index %u) in file %s",
1725 		    name, ent, path);
1726 		return (B_FALSE);
1727 	}
1728 
1729 	for (i = 0; i < length; i++) {
1730 		nvlist_t *uvl;
1731 		char num[64];
1732 		char *uname, *urw;
1733 		int32_t bit;
1734 
1735 		(void) snprintf(num, sizeof (num), "%u", i);
1736 		if (nvlist_lookup_nvlist(units, num, &uvl) != 0) {
1737 			warnx("failed to look up unit %u for event %s (index "
1738 			    "%u) in file %s", i, name, ent, path);
1739 			return (B_FALSE);
1740 		}
1741 
1742 		if (nvlist_lookup_string(uvl, "name", &uname) != 0 ||
1743 		    nvlist_lookup_string(uvl, "rw", &urw) != 0 ||
1744 		    nvlist_lookup_int32(uvl, "bit", &bit) != 0) {
1745 			warnx("failed to find required members for unit array "
1746 			    "entry %u of event %s (index %u) from file %s",
1747 			    i, name, ent, path);
1748 			dump_nvlist(uvl, 0);
1749 			return (B_FALSE);
1750 		}
1751 
1752 		if (bit < 0 || bit > 31) {
1753 			warnx("event %s (index %u) from file %s has invalid "
1754 			    "bit value: %d; skipping", name, ent, path, bit);
1755 			continue;
1756 		}
1757 
1758 		if (strcasecmp(urw, "Read-write") != 0)
1759 			continue;
1760 
1761 		if (fprintf(f, "\t{ \"%s.%s\", %s, 0x%x },\n", name, uname,
1762 		    code, 1U << bit) == -1) {
1763 			warn("failed to write out entry %s from %s", name,
1764 			    path);
1765 			return (B_FALSE);
1766 		}
1767 	}
1768 
1769 	return (B_TRUE);
1770 }
1771 
1772 /*
1773  * For each processor family, generate a data file that contains all of the
1774  * events that we support. Also generate a header that can be included that
1775  * declares all of the tables.
1776  */
1777 static void
1778 cpcgen_gen(int dirfd)
1779 {
1780 	cpc_map_t *map = cpcgen_maps;
1781 
1782 	if (map == NULL) {
1783 		errx(EXIT_FAILURE, "no platforms found or matched");
1784 	}
1785 
1786 	for (map = cpcgen_maps; map != NULL; map = map->cmap_next) {
1787 		int fd, ret;
1788 		FILE *f;
1789 		char *tmpname, *name;
1790 		uint32_t length, i;
1791 
1792 		if ((name = cpcgen_ops.cgen_op_name(map)) == NULL) {
1793 			exit(EXIT_FAILURE);
1794 		}
1795 
1796 		if (asprintf(&tmpname, ".%s.%d", name, getpid()) == -1) {
1797 			err(EXIT_FAILURE, "failed to construct temporary file "
1798 			    "name");
1799 		}
1800 
1801 		if ((fd = openat(dirfd, tmpname, O_RDWR | O_CREAT, 0444)) < 0) {
1802 			err(EXIT_FAILURE, "failed to create temporary file %s",
1803 			    tmpname);
1804 		}
1805 
1806 		if ((f = fdopen(fd, "w")) == NULL) {
1807 			cpcgen_remove_tmpfile(dirfd, tmpname);
1808 			err(EXIT_FAILURE, "failed to fdopen temporary file");
1809 		}
1810 
1811 		if (!cpcgen_ops.cgen_op_file_before(f, map)) {
1812 			cpcgen_remove_tmpfile(dirfd, tmpname);
1813 			exit(EXIT_FAILURE);
1814 		}
1815 
1816 		/*
1817 		 * Iterate over array contents.
1818 		 */
1819 		if ((ret = nvlist_lookup_uint32(map->cmap_data, "length",
1820 		    &length)) != 0) {
1821 			errx(EXIT_FAILURE, "failed to look up length property "
1822 			    "in parsed data for %s: %s", map->cmap_path,
1823 			    strerror(ret));
1824 		}
1825 
1826 		for (i = 0; i < length; i++) {
1827 			nvlist_t *nvl;
1828 			char num[64];
1829 
1830 			(void) snprintf(num, sizeof (num), "%u", i);
1831 			if ((ret = nvlist_lookup_nvlist(map->cmap_data,
1832 			    num, &nvl)) != 0) {
1833 				cpcgen_remove_tmpfile(dirfd, tmpname);
1834 				errx(EXIT_FAILURE, "failed to look up array "
1835 				    "entry %u in parsed data for %s: %s", i,
1836 				    map->cmap_path, strerror(ret));
1837 			}
1838 
1839 			if (cpcgen_ops.cgen_op_skip != NULL &&
1840 			    cpcgen_ops.cgen_op_skip(nvl, map->cmap_path, i)) {
1841 				continue;
1842 			}
1843 
1844 			if (!cpcgen_ops.cgen_op_event(f, nvl, map->cmap_path,
1845 			    i)) {
1846 				cpcgen_remove_tmpfile(dirfd, tmpname);
1847 				exit(EXIT_FAILURE);
1848 			}
1849 		}
1850 
1851 		if (!cpcgen_ops.cgen_op_file_after(f, map)) {
1852 			cpcgen_remove_tmpfile(dirfd, tmpname);
1853 			exit(EXIT_FAILURE);
1854 		}
1855 
1856 		if (fflush(f) != 0 || fclose(f) != 0) {
1857 			cpcgen_remove_tmpfile(dirfd, tmpname);
1858 			err(EXIT_FAILURE, "failed to flush and close "
1859 			    "temporary file");
1860 		}
1861 
1862 		if (renameat(dirfd, tmpname, dirfd, name) != 0) {
1863 			err(EXIT_FAILURE, "failed to rename temporary file %s",
1864 			    tmpname);
1865 		}
1866 
1867 		free(name);
1868 		free(tmpname);
1869 	}
1870 }
1871 
1872 static void
1873 cpcgen_usage(const char *fmt, ...)
1874 {
1875 	if (fmt != NULL) {
1876 		va_list ap;
1877 
1878 		(void) fprintf(stderr, "%s: ", cpcgen_progname);
1879 		va_start(ap, fmt);
1880 		(void) vfprintf(stderr, fmt, ap);
1881 		va_end(ap);
1882 	}
1883 
1884 	(void) fprintf(stderr, "Usage: %s -a|-p platform -c|-H|-m -d datadir "
1885 	    "-o outdir\n"
1886 	    "\n"
1887 	    "\t-a  generate data for all platforms\n"
1888 	    "\t-c  generate C file for CPC\n"
1889 	    "\t-d  specify the directory containt perfmon data\n"
1890 	    "\t-H  generate header file and common files\n"
1891 	    "\t-m  generate manual pages for CPC data\n"
1892 	    "\t-o  output files in directory outdir\n"
1893 	    "\t-p  generate data for a specified platform\n",
1894 	    cpcgen_progname);
1895 }
1896 
1897 int
1898 main(int argc, char *argv[])
1899 {
1900 	int c, outdirfd;
1901 	boolean_t do_mpage = B_FALSE, do_cfile = B_FALSE, do_header = B_FALSE,
1902 	    do_all = B_FALSE;
1903 	const char *datadir = NULL, *outdir = NULL, *platform = NULL;
1904 	uint_t count = 0;
1905 
1906 	cpcgen_progname = basename(argv[0]);
1907 
1908 	while ((c = getopt(argc, argv, ":acd:hHmo:p:")) != -1) {
1909 		switch (c) {
1910 		case 'a':
1911 			do_all = B_TRUE;
1912 			break;
1913 		case 'c':
1914 			do_cfile = B_TRUE;
1915 			break;
1916 		case 'd':
1917 			datadir = optarg;
1918 			break;
1919 		case 'm':
1920 			do_mpage = B_TRUE;
1921 			break;
1922 		case 'H':
1923 			do_header = B_TRUE;
1924 			break;
1925 		case 'o':
1926 			outdir = optarg;
1927 			break;
1928 		case 'p':
1929 			platform = optarg;
1930 			break;
1931 		case ':':
1932 			cpcgen_usage("Option -%c requires an operand\n",
1933 			    optopt);
1934 			return (2);
1935 		case '?':
1936 			cpcgen_usage("Unknown option: -%c\n", optopt);
1937 			return (2);
1938 		case 'h':
1939 		default:
1940 			cpcgen_usage(NULL);
1941 			return (2);
1942 		}
1943 	}
1944 
1945 	count = 0;
1946 	if (do_mpage)
1947 		count++;
1948 	if (do_cfile)
1949 		count++;
1950 	if (do_header)
1951 		count++;
1952 	if (count > 1) {
1953 		cpcgen_usage("Only one of -c, -h, and -m may be specified\n");
1954 		return (2);
1955 	} else if (count == 0) {
1956 		cpcgen_usage("One of -c, -h, and -m is required\n");
1957 		return (2);
1958 	}
1959 
1960 	count = 0;
1961 	if (do_all)
1962 		count++;
1963 	if (platform != NULL)
1964 		count++;
1965 	if (count > 1) {
1966 		cpcgen_usage("Only one of -a and -p may be specified\n");
1967 		return (2);
1968 	} else if (count == 0) {
1969 		cpcgen_usage("One of -a and -p is required\n");
1970 		return (2);
1971 	}
1972 
1973 	if (outdir == NULL) {
1974 		cpcgen_usage("Missing required output directory (-o)\n");
1975 		return (2);
1976 	}
1977 
1978 	if ((outdirfd = open(outdir, O_RDONLY)) < 0) {
1979 		err(EXIT_FAILURE, "failed to open output directory %s", outdir);
1980 	}
1981 
1982 	if (datadir == NULL) {
1983 		cpcgen_usage("Missing required data directory (-d)\n");
1984 		return (2);
1985 	}
1986 
1987 	cpcgen_determine_vendor(datadir);
1988 
1989 	switch (cpcgen_mode) {
1990 	case CPCGEN_MODE_INTEL:
1991 		cpcgen_ops.cgen_op_gather = cpcgen_read_intel;
1992 		cpcgen_ops.cgen_op_common = cpcgen_common_intel_files;
1993 		cpcgen_ops.cgen_op_skip = cpcgen_skip_intel_entry;
1994 		if (do_mpage) {
1995 			cpcgen_ops.cgen_op_name = cpcgen_manual_intel_name;
1996 			cpcgen_ops.cgen_op_file_before =
1997 			    cpcgen_manual_intel_file_before;
1998 			cpcgen_ops.cgen_op_file_after =
1999 			    cpcgen_manual_intel_file_after;
2000 			cpcgen_ops.cgen_op_event = cpcgen_manual_intel_event;
2001 		} else {
2002 			cpcgen_ops.cgen_op_name = cpcgen_cfile_intel_name;
2003 			cpcgen_ops.cgen_op_file_before =
2004 			    cpcgen_cfile_intel_before;
2005 			cpcgen_ops.cgen_op_file_after =
2006 			    cpcgen_cfile_intel_after;
2007 			cpcgen_ops.cgen_op_event = cpcgen_cfile_intel_event;
2008 		}
2009 		break;
2010 	case CPCGEN_MODE_AMD:
2011 		cpcgen_ops.cgen_op_gather = cpcgen_read_amd;
2012 		cpcgen_ops.cgen_op_common = cpcgen_common_amd_files;
2013 		cpcgen_ops.cgen_op_skip = NULL;
2014 		if (do_mpage) {
2015 			cpcgen_ops.cgen_op_name = cpcgen_manual_amd_name;
2016 			cpcgen_ops.cgen_op_file_before =
2017 			    cpcgen_manual_amd_file_before;
2018 			cpcgen_ops.cgen_op_file_after =
2019 			    cpcgen_manual_amd_file_after;
2020 			cpcgen_ops.cgen_op_event = cpcgen_manual_amd_event;
2021 		} else {
2022 			cpcgen_ops.cgen_op_name = cpcgen_cfile_amd_name;
2023 			cpcgen_ops.cgen_op_file_before =
2024 			    cpcgen_cfile_amd_before;
2025 			cpcgen_ops.cgen_op_file_after = cpcgen_cfile_amd_after;
2026 			cpcgen_ops.cgen_op_event = cpcgen_cfile_amd_event;
2027 
2028 		}
2029 		break;
2030 	default:
2031 		errx(EXIT_FAILURE, "failed to determine if operating on AMD or "
2032 		    "Intel");
2033 		break;
2034 	}
2035 
2036 	cpcgen_ops.cgen_op_gather(datadir, platform);
2037 
2038 	if (do_header) {
2039 		cpcgen_ops.cgen_op_common(outdirfd);
2040 		return (0);
2041 	}
2042 
2043 	cpcgen_gen(outdirfd);
2044 
2045 	return (0);
2046 }
2047