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