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