xref: /illumos-gate/usr/src/tools/cpcgen/cpcgen.c (revision e3ae4b35)
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 *
cpcgen_map_lookup(const char * path)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
cpcgen_parse_model(char * fsr,uint_t * family,uint_t * model,uint_t * nstepp,uint_t * steppings)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 *
cpcgen_read_datafile(const char * datadir,const char * file)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 *
cpcgen_use_arch(const char * path,cpc_type_t type,const char * platform)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
cpcgen_determine_vendor(const char * datadir)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 			    mappath);
628 		}
629 
630 		cpcgen_mode = CPCGEN_MODE_AMD;
631 	}
632 
633 	free(mappath);
634 }
635 
636 /*
637  * Read in all the data files that exist for AMD.
638  *
639  * Our family names for AMD systems are based on the family and type so a given
640  * name will look like f17h_<core>_core.json.
641  */
642 static void
cpcgen_read_amd(const char * datadir,const char * platform)643 cpcgen_read_amd(const char *datadir, const char *platform)
644 {
645 	DIR *dir;
646 	struct dirent *d;
647 	const char *suffix = ".json";
648 	const size_t slen = strlen(suffix);
649 
650 	if ((dir = opendir(datadir)) == NULL) {
651 		err(EXIT_FAILURE, "failed to open directory %s", datadir);
652 	}
653 
654 	while ((d = readdir(dir)) != NULL) {
655 		char *name, *c;
656 		cpc_map_t *map;
657 		nvlist_t *parsed;
658 
659 		if ((name = strdup(d->d_name)) == NULL) {
660 			errx(EXIT_FAILURE, "ran out of memory duplicating "
661 			    "name %s", d->d_name);
662 		}
663 		c = strstr(name, suffix);
664 
665 		if (c == NULL) {
666 			free(name);
667 			continue;
668 		}
669 
670 		/*
671 		 * Chop off the .json. Next, make sure we have both _ present.
672 		 */
673 		if (*(c + slen) != '\0') {
674 			free(name);
675 			continue;
676 		}
677 		*c = '\0';
678 
679 		c = strchr(name, '_');
680 		if (c == NULL) {
681 			errx(EXIT_FAILURE, "unexpected AMD JSON file name: %s",
682 			    d->d_name);
683 		}
684 
685 		c++;
686 		c = strchr(c, '_');
687 		if (c == NULL) {
688 			errx(EXIT_FAILURE, "unexpected AMD JSON file name: %s",
689 			    d->d_name);
690 		}
691 		*c = '\0';
692 		c++;
693 		if (strcmp(c, "core") != 0) {
694 			errx(EXIT_FAILURE, "unexpected AMD JSON file name: %s",
695 			    d->d_name);
696 		}
697 
698 		if (platform != NULL && strcmp(platform, name) != 0) {
699 			free(name);
700 			continue;
701 		}
702 
703 		if ((map = calloc(1, sizeof (cpc_map_t))) == NULL) {
704 			err(EXIT_FAILURE, "failed to allocate space for cpc "
705 			    "file");
706 		}
707 
708 		parsed = cpcgen_read_datafile(datadir, d->d_name);
709 		if ((map->cmap_path = strdup(d->d_name)) == NULL) {
710 			err(EXIT_FAILURE, "failed to duplicate path string");
711 		}
712 		map->cmap_type = CPC_FILE_CORE;
713 		map->cmap_data = parsed;
714 		map->cmap_name = name;
715 		map->cmap_procs = NULL;
716 
717 		map->cmap_next = cpcgen_maps;
718 		cpcgen_maps = map;
719 	}
720 }
721 
722 /*
723  * Read in the mapfile.csv that is used to map between processor families and
724  * parse this. Each line has a comma separated value.
725  */
726 static void
cpcgen_read_intel(const char * datadir,const char * platform)727 cpcgen_read_intel(const char *datadir, const char *platform)
728 {
729 	FILE *map;
730 	char *mappath, *last;
731 	char *data = NULL;
732 	size_t datalen = 0;
733 	uint_t lineno;
734 
735 	if (asprintf(&mappath, "%s/%s", datadir, cpcgen_intel_mapfile) == -1) {
736 		err(EXIT_FAILURE, "failed to construct path to mapfile");
737 	}
738 
739 	if ((map = fopen(mappath, "r")) == NULL) {
740 		err(EXIT_FAILURE, "failed to open data mapfile %s", mappath);
741 	}
742 
743 	lineno = 0;
744 	while (getline(&data, &datalen, map) != -1) {
745 		char *fstr, *path, *tstr;
746 		const char *name;
747 		uint_t family, model, nsteps;
748 		uint_t steppings[CPROC_MAX_STEPPINGS];
749 
750 		cpc_type_t type;
751 		cpc_map_t *map;
752 		cpc_proc_t *proc;
753 
754 		/*
755 		 * The first line contains the header:
756 		 * Family-model,Version,Filename,EventType
757 		 */
758 		lineno++;
759 		if (lineno == 1) {
760 			continue;
761 		}
762 
763 		if ((fstr = strtok_r(data, ",", &last)) == NULL ||
764 		    strtok_r(NULL, ",", &last) == NULL ||
765 		    (path = strtok_r(NULL, ",", &last)) == NULL ||
766 		    (tstr = strtok_r(NULL, "\n", &last)) == NULL) {
767 			errx(EXIT_FAILURE, "failed to parse mapfile line "
768 			    "%u in %s", lineno, mappath);
769 		}
770 
771 		cpcgen_parse_model(fstr, &family, &model, &nsteps, steppings);
772 
773 		if (strcmp(tstr, "core") == 0) {
774 			type = CPC_FILE_CORE;
775 		} else if (strcmp(tstr, "offcore") == 0) {
776 			type = CPC_FILE_OFF_CORE;
777 		} else if (strcmp(tstr, "uncore") == 0) {
778 			type = CPC_FILE_UNCORE;
779 		} else if (strcmp(tstr, "fp_arith_inst") == 0) {
780 			type = CPC_FILE_FP_MATH;
781 		} else if (strcmp(tstr, "uncore experimental") == 0) {
782 			type = CPC_FILE_UNCORE_EXP;
783 		} else {
784 			errx(EXIT_FAILURE, "unknown file type \"%s\" on line "
785 			    "%u", tstr, lineno);
786 		}
787 
788 		if ((name = cpcgen_use_arch(path, type, platform)) == NULL)
789 			continue;
790 
791 		if ((map = cpcgen_map_lookup(path)) == NULL) {
792 			nvlist_t *parsed;
793 
794 			parsed = cpcgen_read_datafile(datadir, path);
795 
796 			if ((map = calloc(1, sizeof (cpc_map_t))) == NULL) {
797 				err(EXIT_FAILURE, "failed to allocate space "
798 				    "for cpc file");
799 			}
800 
801 			if ((map->cmap_path = strdup(path)) == NULL) {
802 				err(EXIT_FAILURE, "failed to duplicate path "
803 				    "string");
804 			}
805 
806 			map->cmap_type = type;
807 			map->cmap_data = parsed;
808 			map->cmap_name = name;
809 			map->cmap_procs = NULL;
810 
811 			map->cmap_next = cpcgen_maps;
812 			cpcgen_maps = map;
813 		}
814 
815 		if ((proc = calloc(1, sizeof (cpc_proc_t))) == NULL) {
816 			err(EXIT_FAILURE, "failed to allocate memory for "
817 			    "family and model tracking");
818 		}
819 
820 		proc->cproc_family = family;
821 		proc->cproc_model = model;
822 		proc->cproc_nsteps = nsteps;
823 		if (nsteps > 0) {
824 			bcopy(steppings, proc->cproc_steppings,
825 			    sizeof (steppings));
826 		}
827 		proc->cproc_next = map->cmap_procs;
828 		map->cmap_procs = proc;
829 	}
830 
831 	if (errno != 0 || ferror(map)) {
832 		err(EXIT_FAILURE, "failed to read %s", mappath);
833 	}
834 
835 	if (fclose(map) == EOF) {
836 		err(EXIT_FAILURE, "failed to close %s", mappath);
837 	}
838 	free(data);
839 	free(mappath);
840 }
841 
842 static char *
cpcgen_manual_intel_name(cpc_map_t * map)843 cpcgen_manual_intel_name(cpc_map_t *map)
844 {
845 	char *name;
846 
847 	if (asprintf(&name, "%s_events.3cpc", map->cmap_name) == -1) {
848 		warn("failed to assemble manual page name for %s",
849 		    map->cmap_path);
850 		return (NULL);
851 	}
852 
853 	return (name);
854 }
855 
856 static boolean_t
cpcgen_manual_intel_file_before(FILE * f,cpc_map_t * map)857 cpcgen_manual_intel_file_before(FILE *f, cpc_map_t *map)
858 {
859 	size_t i;
860 	char *upper;
861 	cpc_proc_t *proc;
862 
863 	if ((upper = strdup(map->cmap_name)) == NULL) {
864 		warn("failed to duplicate manual name for %s", map->cmap_name);
865 		return (B_FALSE);
866 	}
867 
868 	for (i = 0; upper[i] != '\0'; i++) {
869 		upper[i] = toupper(upper[i]);
870 	}
871 
872 	if (fprintf(f, cpcgen_manual_intel_intel_header, map->cmap_path, upper,
873 	    map->cmap_name) == -1) {
874 		warn("failed to write out manual header for %s",
875 		    map->cmap_name);
876 		free(upper);
877 		return (B_FALSE);
878 	}
879 	free(upper);
880 
881 	for (proc = map->cmap_procs; proc != NULL; proc = proc->cproc_next) {
882 		if (proc->cproc_nsteps > 0) {
883 			uint_t step;
884 
885 			for (step = 0; step < proc->cproc_nsteps; step++) {
886 				if (fprintf(f, ".It\n.Sy Family 0x%x, Model "
887 				    "0x%x, Stepping 0x%x\n",
888 				    proc->cproc_family, proc->cproc_model,
889 				    proc->cproc_steppings[step]) == -1) {
890 					warn("failed to write out model "
891 					    "information for %s",
892 					    map->cmap_name);
893 					return (B_FALSE);
894 				}
895 			}
896 		} else {
897 			if (fprintf(f, ".It\n.Sy Family 0x%x, Model 0x%x\n",
898 			    proc->cproc_family, proc->cproc_model) == -1) {
899 				warn("failed to write out model information "
900 				    "for %s", map->cmap_name);
901 				return (B_FALSE);
902 			}
903 		}
904 	}
905 
906 	if (fprintf(f, cpcgen_manual_intel_data) == -1) {
907 		warn("failed to write out manual header for %s",
908 		    map->cmap_name);
909 		return (B_FALSE);
910 	}
911 
912 	return (B_TRUE);
913 }
914 
915 static boolean_t
cpcgen_manual_intel_file_after(FILE * f,cpc_map_t * map)916 cpcgen_manual_intel_file_after(FILE *f, cpc_map_t *map)
917 {
918 	if (fprintf(f, cpcgen_manual_intel_trailer) == -1) {
919 		warn("failed to write out manual header for %s",
920 		    map->cmap_name);
921 		return (B_FALSE);
922 	}
923 
924 	return (B_TRUE);
925 }
926 
927 static boolean_t
cpcgen_manual_intel_event(FILE * f,nvlist_t * nvl,const char * path,uint32_t ent)928 cpcgen_manual_intel_event(FILE *f, nvlist_t *nvl, const char *path,
929     uint32_t ent)
930 {
931 	char *event, *lname, *brief = NULL, *public = NULL, *errata = NULL;
932 	size_t i;
933 
934 	if (nvlist_lookup_string(nvl, "EventName", &event) != 0) {
935 		warnx("Found event without 'EventName' property "
936 		    "in %s, entry %u", path, ent);
937 		return (B_FALSE);
938 	}
939 
940 	/*
941 	 * Intel uses capital names. CPC historically uses lower case names.
942 	 */
943 	if ((lname = strdup(event)) == NULL) {
944 		err(EXIT_FAILURE, "failed to duplicate event name %s", event);
945 	}
946 	for (i = 0; lname[i] != '\0'; i++) {
947 		lname[i] = tolower(event[i]);
948 	}
949 
950 	/*
951 	 * Try to get the other event fields, but if they're not there, don't
952 	 * worry about it.
953 	 */
954 	(void) nvlist_lookup_string(nvl, "BriefDescription", &brief);
955 	(void) nvlist_lookup_string(nvl, "PublicDescription", &public);
956 	(void) nvlist_lookup_string(nvl, "Errata", &errata);
957 	if (errata != NULL && (strcmp(errata, "0") == 0 ||
958 	    strcmp(errata, "null") == 0)) {
959 		errata = NULL;
960 	}
961 
962 	if (fprintf(f, ".It Sy %s\n", lname) == -1) {
963 		warn("failed to write out event entry %s", event);
964 		free(lname);
965 		return (B_FALSE);
966 	}
967 
968 	if (public != NULL) {
969 		if (fprintf(f, "%s\n", public) == -1) {
970 			warn("failed to write out event entry %s", event);
971 			free(lname);
972 			return (B_FALSE);
973 		}
974 	} else if (brief != NULL) {
975 		if (fprintf(f, "%s\n", brief) == -1) {
976 			warn("failed to write out event entry %s", event);
977 			free(lname);
978 			return (B_FALSE);
979 		}
980 	}
981 
982 	if (errata != NULL) {
983 		if (fprintf(f, ".Pp\nThe following errata may apply to this: "
984 		    "%s\n", errata) == -1) {
985 
986 			warn("failed to write out event entry %s", event);
987 			free(lname);
988 			return (B_FALSE);
989 		}
990 	}
991 
992 	free(lname);
993 	return (B_TRUE);
994 }
995 
996 static char *
cpcgen_cfile_intel_name(cpc_map_t * map)997 cpcgen_cfile_intel_name(cpc_map_t *map)
998 {
999 	char *name;
1000 
1001 	if (asprintf(&name, "core_pcbe_%s.c", map->cmap_name) == -1) {
1002 		warn("failed to assemble file name for %s", map->cmap_path);
1003 		return (NULL);
1004 	}
1005 
1006 	return (name);
1007 }
1008 
1009 static boolean_t
cpcgen_cfile_intel_before(FILE * f,cpc_map_t * map)1010 cpcgen_cfile_intel_before(FILE *f, cpc_map_t *map)
1011 {
1012 	if (fprintf(f, cpcgen_cfile_intel_header, map->cmap_path) == -1) {
1013 		warn("failed to write header to temporary file for %s",
1014 		    map->cmap_path);
1015 		return (B_FALSE);
1016 	}
1017 
1018 	if (fprintf(f, cpcgen_cfile_intel_table_start, map->cmap_name) == -1) {
1019 		warn("failed to write header to temporary file for %s",
1020 		    map->cmap_path);
1021 		return (B_FALSE);
1022 	}
1023 
1024 	return (B_TRUE);
1025 }
1026 
1027 static boolean_t
cpcgen_cfile_intel_after(FILE * f,cpc_map_t * map)1028 cpcgen_cfile_intel_after(FILE *f, cpc_map_t *map)
1029 {
1030 	if (fprintf(f, cpcgen_cfile_intel_table_end) == -1) {
1031 		warn("failed to write footer to temporary file for %s",
1032 		    map->cmap_path);
1033 		return (B_FALSE);
1034 	}
1035 
1036 	return (B_TRUE);
1037 }
1038 
1039 static boolean_t
cpcgen_cfile_intel_event(FILE * f,nvlist_t * nvl,const char * path,uint_t ent)1040 cpcgen_cfile_intel_event(FILE *f, nvlist_t *nvl, const char *path, uint_t ent)
1041 {
1042 	char *ecode, *umask, *name, *counter, *lname, *cmask;
1043 	size_t i;
1044 
1045 	if (nvlist_lookup_string(nvl, "EventName", &name) != 0) {
1046 		warnx("Found event without 'EventName' property "
1047 		    "in %s, entry %u", path, ent);
1048 		return (B_FALSE);
1049 	}
1050 
1051 	if (nvlist_lookup_string(nvl, "EventCode", &ecode) != 0 ||
1052 	    nvlist_lookup_string(nvl, "UMask", &umask) != 0 ||
1053 	    nvlist_lookup_string(nvl, "Counter", &counter) != 0) {
1054 		warnx("event %s (index %u) from %s, missing "
1055 		    "required properties for C file translation",
1056 		    name, ent, path);
1057 		return (B_FALSE);
1058 	}
1059 
1060 	/*
1061 	 * While we could try and parse the counters manually, just do this the
1062 	 * max power way for now based on all possible values.
1063 	 */
1064 	if (strcmp(counter, "0") == 0 || strcmp(counter, "0,") == 0) {
1065 		cmask = "C0";
1066 	} else if (strcmp(counter, "1") == 0) {
1067 		cmask = "C1";
1068 	} else if (strcmp(counter, "2") == 0) {
1069 		cmask = "C2";
1070 	} else if (strcmp(counter, "3") == 0) {
1071 		cmask = "C3";
1072 	} else if (strcmp(counter, "0,1") == 0) {
1073 		cmask = "C0|C1";
1074 	} else if (strcmp(counter, "0,1,2") == 0) {
1075 		cmask = "C0|C1|C2";
1076 	} else if (strcmp(counter, "0,1,2,3") == 0) {
1077 		cmask = "C0|C1|C2|C3";
1078 	} else if (strcmp(counter, "0,1,2,3,4,5,6,7") == 0) {
1079 		/*
1080 		 * We don't support the larger number of counters on some
1081 		 * platforms right now, so just truncate it to the supported
1082 		 * set.
1083 		 */
1084 		cmask = "C0|C1|C2|C3";
1085 	} else if (strcmp(counter, "0,2,3") == 0) {
1086 		cmask = "C0|C2|C3";
1087 	} else if (strcmp(counter, "1,2,3") == 0) {
1088 		cmask = "C1|C2|C3";
1089 	} else if (strcmp(counter, "2,3") == 0) {
1090 		cmask = "C2|C3";
1091 	} else {
1092 		warnx("event %s (index %u) from %s, has unknown "
1093 		    "counter value \"%s\"", name, ent, path, counter);
1094 		return (B_FALSE);
1095 	}
1096 
1097 
1098 	/*
1099 	 * Intel uses capital names. CPC historically uses lower case names.
1100 	 */
1101 	if ((lname = strdup(name)) == NULL) {
1102 		err(EXIT_FAILURE, "failed to duplicate event name %s", name);
1103 	}
1104 	for (i = 0; lname[i] != '\0'; i++) {
1105 		lname[i] = tolower(name[i]);
1106 	}
1107 
1108 	if (fprintf(f, "\t{ %s, %s, %s, \"%s\" },\n", ecode, umask, cmask,
1109 	    lname) == -1) {
1110 		warn("failed to write out entry %s from %s", name, path);
1111 		free(lname);
1112 		return (B_FALSE);
1113 	}
1114 
1115 	free(lname);
1116 
1117 	/*
1118 	 * Check if we have any PAPI aliases.
1119 	 */
1120 	for (i = 0; cpcgen_intel_papi_map[i].cpapi_intc != NULL; i++) {
1121 		if (strcmp(name, cpcgen_intel_papi_map[i].cpapi_intc) != 0)
1122 			continue;
1123 
1124 		if (fprintf(f, "\t{ %s, %s, %s, \"%s\" },\n", ecode, umask,
1125 		    cmask, cpcgen_intel_papi_map[i].cpapi_papi) == -1) {
1126 			warn("failed to write out entry %s from %s", name,
1127 			    path);
1128 			return (B_FALSE);
1129 		}
1130 	}
1131 
1132 	return (B_TRUE);
1133 }
1134 
1135 static boolean_t
cpcgen_generate_map(FILE * f,cpc_map_t * map,boolean_t start)1136 cpcgen_generate_map(FILE *f, cpc_map_t *map, boolean_t start)
1137 {
1138 	cpc_proc_t *p;
1139 
1140 	if (fprintf(f, "\t%sif (", start ? "" : "} else ") == -1) {
1141 		return (B_FALSE);
1142 	}
1143 
1144 	for (p = map->cmap_procs; p != NULL; p = p->cproc_next) {
1145 		/*
1146 		 * Make sure the line is padded so the generated C code looks
1147 		 * like reasonable C style.
1148 		 */
1149 		if (p != map->cmap_procs) {
1150 			if (fputs("\t    ", f) == -1) {
1151 				return (B_FALSE);
1152 			}
1153 		}
1154 
1155 		if (p->cproc_nsteps > 0) {
1156 			uint_t i;
1157 
1158 			if (fprintf(f, "(model == 0x%x &&\n\t    (",
1159 			    p->cproc_model) == -1) {
1160 				return (B_FALSE);
1161 			}
1162 
1163 			for (i = 0; i < p->cproc_nsteps; i++) {
1164 				if (fprintf(f, "stepping == 0x%x%s",
1165 				    p->cproc_steppings[i],
1166 				    i + 1 != p->cproc_nsteps ?
1167 				    " ||\n\t    " : "") == -1) {
1168 					return (B_FALSE);
1169 				}
1170 			}
1171 
1172 			if (fputs("))", f) == -1) {
1173 				return (B_FALSE);
1174 			}
1175 		} else if (fprintf(f, "model == 0x%x", p->cproc_model) == -1) {
1176 			return (B_FALSE);
1177 		}
1178 
1179 		if (fprintf(f, "%s\n",
1180 		    p->cproc_next != NULL ? " ||" : ") {") == -1) {
1181 			return (B_FALSE);
1182 		}
1183 	}
1184 
1185 	if (fprintf(f, "\t\t\treturn (pcbe_core_events_%s);\n",
1186 	    map->cmap_name) == -1) {
1187 		return (B_FALSE);
1188 	}
1189 
1190 	return (B_TRUE);
1191 }
1192 
1193 /*
1194  * This is a wrapper around unlinkat that makes sure that we don't clobber
1195  * errno, which is used for properly printing out error messages below.
1196  */
1197 static void
cpcgen_remove_tmpfile(int dirfd,const char * path)1198 cpcgen_remove_tmpfile(int dirfd, const char *path)
1199 {
1200 	int e = errno;
1201 	(void) unlinkat(dirfd, path, 0);
1202 	errno = e;
1203 }
1204 
1205 /*
1206  * Generate a header file that declares all of these arrays and provide a map
1207  * for models to the corresponding table to use.
1208  */
1209 static void
cpcgen_common_intel_files(int dirfd)1210 cpcgen_common_intel_files(int dirfd)
1211 {
1212 	const char *fname = "core_pcbe_cpcgen.h";
1213 	char *tmpname;
1214 	int fd;
1215 	FILE *f;
1216 	cpc_map_t *map;
1217 
1218 	if (asprintf(&tmpname, ".%s.%d", fname, getpid()) == -1) {
1219 		err(EXIT_FAILURE, "failed to construct temporary file name");
1220 	}
1221 
1222 	if ((fd = openat(dirfd, tmpname, O_RDWR | O_CREAT, 0644)) < 0) {
1223 		err(EXIT_FAILURE, "failed to create temporary file %s",
1224 		    tmpname);
1225 	}
1226 
1227 	if ((f = fdopen(fd, "w")) == NULL) {
1228 		cpcgen_remove_tmpfile(dirfd, tmpname);
1229 		err(EXIT_FAILURE, "failed to fdopen temporary file");
1230 	}
1231 
1232 	if (fprintf(f, cpcgen_cfile_intel_header, cpcgen_intel_mapfile) == -1) {
1233 		cpcgen_remove_tmpfile(dirfd, tmpname);
1234 		errx(EXIT_FAILURE, "failed to write header to temporary file "
1235 		    "for %s", fname);
1236 	}
1237 
1238 	if (fprintf(f, "#ifndef _CORE_PCBE_CPCGEN_H\n"
1239 	    "#define\t_CORE_PCBE_CPCGEN_H\n"
1240 	    "\n"
1241 	    "#ifdef __cplusplus\n"
1242 	    "extern \"C\" {\n"
1243 	    "#endif\n"
1244 	    "\n"
1245 	    "extern const struct events_table_t *core_cpcgen_table(uint_t, "
1246 	    "uint_t);\n"
1247 	    "\n") == -1) {
1248 		cpcgen_remove_tmpfile(dirfd, tmpname);
1249 		errx(EXIT_FAILURE, "failed to write header to "
1250 		    "temporary file for %s", fname);
1251 	}
1252 
1253 	for (map = cpcgen_maps; map != NULL; map = map->cmap_next) {
1254 		if (fprintf(f, "extern const struct events_table_t "
1255 		    "pcbe_core_events_%s[];\n", map->cmap_name) == -1) {
1256 			cpcgen_remove_tmpfile(dirfd, tmpname);
1257 			errx(EXIT_FAILURE, "failed to write entry to "
1258 			    "temporary file for %s", fname);
1259 		}
1260 	}
1261 
1262 	if (fprintf(f, "\n"
1263 	    "#ifdef __cplusplus\n"
1264 	    "}\n"
1265 	    "#endif\n"
1266 	    "\n"
1267 	    "#endif /* _CORE_PCBE_CPCGEN_H */\n") == -1) {
1268 		cpcgen_remove_tmpfile(dirfd, tmpname);
1269 		errx(EXIT_FAILURE, "failed to write header to "
1270 		    "temporary file for %s", fname);
1271 	}
1272 
1273 	if (fflush(f) != 0 || fclose(f) != 0) {
1274 		cpcgen_remove_tmpfile(dirfd, tmpname);
1275 		err(EXIT_FAILURE, "failed to flush and close temporary file");
1276 	}
1277 
1278 	if (renameat(dirfd, tmpname, dirfd, fname) != 0) {
1279 		err(EXIT_FAILURE, "failed to rename temporary file %s",
1280 		    tmpname);
1281 	}
1282 
1283 	free(tmpname);
1284 
1285 	/* Now again for the .c file. */
1286 	fname = "core_pcbe_cpcgen.c";
1287 	if (asprintf(&tmpname, ".%s.%d", fname, getpid()) == -1) {
1288 		err(EXIT_FAILURE, "failed to construct temporary file name");
1289 	}
1290 
1291 	if ((fd = openat(dirfd, tmpname, O_RDWR | O_CREAT, 0644)) < 0) {
1292 		err(EXIT_FAILURE, "failed to create temporary file %s",
1293 		    tmpname);
1294 	}
1295 
1296 	if ((f = fdopen(fd, "w")) == NULL) {
1297 		cpcgen_remove_tmpfile(dirfd, tmpname);
1298 		err(EXIT_FAILURE, "failed to fdopen temporary file");
1299 	}
1300 
1301 	if (fprintf(f, cpcgen_cfile_intel_header, cpcgen_intel_mapfile) == -1) {
1302 		cpcgen_remove_tmpfile(dirfd, tmpname);
1303 		errx(EXIT_FAILURE, "failed to write header to temporary file "
1304 		    "for %s", fname);
1305 	}
1306 
1307 	if (fprintf(f, "#include <core_pcbe_table.h>\n"
1308 	    "#include <sys/null.h>\n"
1309 	    "#include \"core_pcbe_cpcgen.h\"\n"
1310 	    "\n"
1311 	    "const struct events_table_t *\n"
1312 	    "core_cpcgen_table(uint_t model, uint_t stepping)\n"
1313 	    "{\n") == -1) {
1314 		cpcgen_remove_tmpfile(dirfd, tmpname);
1315 		errx(EXIT_FAILURE, "failed to write header to "
1316 		    "temporary file for %s", fname);
1317 	}
1318 
1319 	for (map = cpcgen_maps; map != NULL; map = map->cmap_next) {
1320 		if (!cpcgen_generate_map(f, map, map == cpcgen_maps)) {
1321 			cpcgen_remove_tmpfile(dirfd, tmpname);
1322 			errx(EXIT_FAILURE, "failed to write to temporary "
1323 			    "file for %s", fname);
1324 		}
1325 	}
1326 
1327 	if (fprintf(f, "\t} else {\n"
1328 	    "\t\t\treturn (NULL);\n"
1329 	    "\t}\n"
1330 	    "}\n") == -1) {
1331 		cpcgen_remove_tmpfile(dirfd, tmpname);
1332 		errx(EXIT_FAILURE, "failed to write header to "
1333 		    "temporary file for %s", fname);
1334 	}
1335 
1336 	if (fflush(f) != 0 || fclose(f) != 0) {
1337 		cpcgen_remove_tmpfile(dirfd, tmpname);
1338 		err(EXIT_FAILURE, "failed to flush and close temporary file");
1339 	}
1340 
1341 	if (renameat(dirfd, tmpname, dirfd, fname) != 0) {
1342 		err(EXIT_FAILURE, "failed to rename temporary file %s",
1343 		    tmpname);
1344 	}
1345 
1346 	free(tmpname);
1347 }
1348 
1349 /*
1350  * Look at a rule to determine whether or not we should consider including it or
1351  * not. At this point we've already filtered things such that we only get core
1352  * events.
1353  *
1354  * To consider an entry, we currently apply the following criteria:
1355  *
1356  * - The MSRIndex and MSRValue are zero. Programming additional MSRs is no
1357  *   supported right now.
1358  * - TakenAlone is non-zero, which means that it cannot run at the same time as
1359  *   another field.
1360  * - Offcore is one, indicating that it is off the core and we need to figure
1361  *   out if we can support this.
1362  * - If the counter is fixed, don't use it for now. "32"-"35" is another name
1363  *    for the fixed counters.
1364  * - If more than one value is specified in the EventCode or UMask values
1365  */
1366 static boolean_t
cpcgen_skip_intel_entry(nvlist_t * nvl,const char * path,uint_t ent)1367 cpcgen_skip_intel_entry(nvlist_t *nvl, const char *path, uint_t ent)
1368 {
1369 	char *event, *msridx, *msrval, *taken, *offcore, *counter;
1370 	char *ecode, *umask;
1371 
1372 	/*
1373 	 * Require EventName, it's kind of useless without that.
1374 	 */
1375 	if (nvlist_lookup_string(nvl, "EventName", &event) != 0) {
1376 		errx(EXIT_FAILURE, "Found event without 'EventName' property "
1377 		    "in %s, entry %u", path, ent);
1378 	}
1379 
1380 	/*
1381 	 * If we can't find an expected value, whine about it.
1382 	 */
1383 	if (nvlist_lookup_string(nvl, "MSRIndex", &msridx) != 0 ||
1384 	    nvlist_lookup_string(nvl, "MSRValue", &msrval) != 0 ||
1385 	    nvlist_lookup_string(nvl, "Counter", &counter) != 0 ||
1386 	    nvlist_lookup_string(nvl, "EventCode", &ecode) != 0 ||
1387 	    nvlist_lookup_string(nvl, "UMask", &umask) != 0 ||
1388 	    nvlist_lookup_string(nvl, "Offcore", &offcore) != 0) {
1389 		warnx("Skipping event %s (index %u) from %s, missing required "
1390 		    "property", event, ent, path);
1391 		return (B_TRUE);
1392 	}
1393 
1394 	/*
1395 	 * MSRIndex and MSRvalue comes as either "0" or "0x00".
1396 	 */
1397 	if ((strcmp(msridx, "0") != 0 && strcmp(msridx, "0x00") != 0) ||
1398 	    (strcmp(msrval, "0") != 0 && strcmp(msridx, "0x00") != 0) ||
1399 	    strcmp(offcore, "0") != 0 || strchr(ecode, ',') != NULL ||
1400 	    strchr(umask, ',') != NULL) {
1401 		return (B_TRUE);
1402 	}
1403 
1404 	/*
1405 	 * Unfortunately, not everything actually has "TakenAlone". If it
1406 	 * doesn't, we assume that it doesn't have to be.
1407 	 */
1408 	if (nvlist_lookup_string(nvl, "TakenAlone", &taken) == 0 &&
1409 	    strcmp(taken, "0") != 0) {
1410 		return (B_TRUE);
1411 	}
1412 
1413 
1414 	if (strncasecmp(counter, "fixed", strlen("fixed")) == 0)
1415 		return (B_TRUE);
1416 	if (strcmp(counter, "32") == 0 || strcmp(counter, "33") == 0 ||
1417 	    strcmp(counter, "34") == 0 || strcmp(counter, "35") == 0)
1418 		return (B_TRUE);
1419 
1420 	return (B_FALSE);
1421 }
1422 static char *
cpcgen_manual_amd_name(cpc_map_t * map)1423 cpcgen_manual_amd_name(cpc_map_t *map)
1424 {
1425 	char *name;
1426 
1427 	if (asprintf(&name, "amd_%s_events.3cpc", map->cmap_name) == -1) {
1428 		warn("failed to assemble file name for %s", map->cmap_path);
1429 		return (NULL);
1430 	}
1431 
1432 	return (name);
1433 }
1434 
1435 static boolean_t
cpcgen_manual_amd_file_before(FILE * f,cpc_map_t * map)1436 cpcgen_manual_amd_file_before(FILE *f, cpc_map_t *map)
1437 {
1438 	size_t i;
1439 	char *upper, *desc, *c;
1440 
1441 	if ((upper = strdup(map->cmap_name)) == NULL) {
1442 		warn("failed to duplicate manual name for %s", map->cmap_name);
1443 		return (B_FALSE);
1444 	}
1445 
1446 	if ((desc = strdup(map->cmap_name + 1)) == NULL) {
1447 		warn("failed to duplicate manual name for %s", map->cmap_name);
1448 		free(upper);
1449 		return (B_FALSE);
1450 	}
1451 
1452 	for (i = 0; upper[i] != '\0'; i++) {
1453 		upper[i] = toupper(upper[i]);
1454 	}
1455 
1456 	c = strchr(desc, '_');
1457 	if (c != NULL) {
1458 		*c = ' ';
1459 		c++;
1460 		*c = toupper(*c);
1461 	}
1462 
1463 	if (fprintf(f, cpcgen_manual_amd_header, map->cmap_path, upper,
1464 	    map->cmap_name, desc, desc) == -1) {
1465 		warn("failed to write out manual header for %s",
1466 		    map->cmap_name);
1467 		free(upper);
1468 		free(desc);
1469 		return (B_FALSE);
1470 	}
1471 
1472 	free(upper);
1473 	free(desc);
1474 	return (B_TRUE);
1475 }
1476 
1477 static boolean_t
cpcgen_manual_amd_file_after(FILE * f,cpc_map_t * map)1478 cpcgen_manual_amd_file_after(FILE *f, cpc_map_t *map)
1479 {
1480 	if (fprintf(f, cpcgen_manual_amd_trailer) == -1) {
1481 		warn("failed to write out manual header for %s",
1482 		    map->cmap_name);
1483 		return (B_FALSE);
1484 	}
1485 
1486 	return (B_TRUE);
1487 }
1488 
1489 static boolean_t
cpcgen_manual_amd_event(FILE * f,nvlist_t * nvl,const char * path,uint32_t ent)1490 cpcgen_manual_amd_event(FILE *f, nvlist_t *nvl, const char *path, uint32_t ent)
1491 {
1492 	char *name, *mnemonic = NULL, *summary = NULL, *desc = NULL;
1493 	char *umode;
1494 	nvlist_t *units = NULL;
1495 	uint32_t i, length;
1496 
1497 	if (nvlist_lookup_string(nvl, "name", &name) != 0) {
1498 		warnx("Found event without 'name' property in %s, entry %u",
1499 		    path, ent);
1500 		return (B_FALSE);
1501 	}
1502 
1503 	if (nvlist_lookup_string(nvl, "mnemonic", &mnemonic) != 0 ||
1504 	    nvlist_lookup_string(nvl, "summary", &summary) != 0) {
1505 		warnx("event %s in %s, entry %u, missing required fields",
1506 		    name, path, ent);
1507 		return (B_FALSE);
1508 	}
1509 
1510 	/*
1511 	 * Allow the other fields to be missing.
1512 	 */
1513 	(void) nvlist_lookup_string(nvl, "description", &desc);
1514 	(void) nvlist_lookup_nvlist(nvl, "units", &units);
1515 
1516 	if (fprintf(f, ".It Sy %s\n", name) == -1) {
1517 		warn("failed to write out event entry %s", name);
1518 	}
1519 
1520 	if (fprintf(f, ".Sy %s -\n"
1521 	    "%s\n", mnemonic, summary) == -1) {
1522 		warn("failed to write out event entry %s", name);
1523 		return (B_FALSE);
1524 	}
1525 
1526 	if (desc != NULL) {
1527 		if (fprintf(f, ".Pp\n%s\n", desc) == -1) {
1528 			warn("failed to write out event entry %s", name);
1529 			return (B_FALSE);
1530 		}
1531 	}
1532 
1533 	if (units == NULL)
1534 		return (B_TRUE);
1535 
1536 	/*
1537 	 * Skip units we don't know how to handle.
1538 	 */
1539 	if (nvlist_lookup_string(nvl, "unit_mode", &umode) == 0) {
1540 		return (B_TRUE);
1541 	}
1542 
1543 	if (fprintf(f, ".Pp\n"
1544 	    "This event has the following units which may be used\n"
1545 	    "to modify the behavior of the event:\n"
1546 	    ".Bl -tag -width Sy\n") == -1) {
1547 		warn("failed to write out event entry %s", name);
1548 		return (B_FALSE);
1549 	}
1550 
1551 	if (nvlist_lookup_uint32(units, "length", &length) != 0) {
1552 		warnx("found units array, but could not look up length "
1553 		    "property for events %s (index %u) in file %s",
1554 		    name, ent, path);
1555 		return (B_FALSE);
1556 	}
1557 
1558 	for (i = 0; i < length; i++) {
1559 		nvlist_t *uvl;
1560 		char num[64];
1561 		char *uname, *udesc = NULL;
1562 
1563 		(void) snprintf(num, sizeof (num), "%u", i);
1564 		if (nvlist_lookup_nvlist(units, num, &uvl) != 0) {
1565 			warnx("failed to look up unit %u for event %s (index "
1566 			    "%u) in file %s", i, name, ent, path);
1567 			return (B_FALSE);
1568 		}
1569 
1570 		if (nvlist_lookup_string(uvl, "name", &uname) != 0) {
1571 			warnx("failed to find required members for unit array "
1572 			    "entry %u of event %s (index %u) from file %s",
1573 			    i, name, ent, path);
1574 			return (B_FALSE);
1575 		}
1576 		(void) nvlist_lookup_string(uvl, "description", &udesc);
1577 		if (fprintf(f, ".It Sy %s\n", uname) == -1) {
1578 			warn("failed to write out event entry %s", name);
1579 			return (B_FALSE);
1580 		}
1581 
1582 		if (udesc != NULL) {
1583 			if (fprintf(f, "%s\n", udesc) == -1) {
1584 				warn("failed to write out event entry %s",
1585 				    name);
1586 				return (B_FALSE);
1587 			}
1588 		}
1589 	}
1590 
1591 	if (fprintf(f, ".El\n") == -1) {
1592 		warn("failed to write out event entry %s",
1593 		    name);
1594 		return (B_FALSE);
1595 	}
1596 
1597 	return (B_TRUE);
1598 }
1599 
1600 static char *
cpcgen_cfile_amd_name(cpc_map_t * map)1601 cpcgen_cfile_amd_name(cpc_map_t *map)
1602 {
1603 	char *name;
1604 
1605 	if (asprintf(&name, "opteron_pcbe_%s.c", map->cmap_name) == -1) {
1606 		warn("failed to assemble file name for %s", map->cmap_path);
1607 		return (NULL);
1608 	}
1609 
1610 	return (name);
1611 }
1612 
1613 /*
1614  * Generate a header file that can be used to synthesize the data events we care
1615  * about.
1616  */
1617 static void
cpcgen_common_amd_files(int dirfd)1618 cpcgen_common_amd_files(int dirfd)
1619 {
1620 	const char *fname = "opteron_pcbe_cpcgen.h";
1621 	char *tmpname;
1622 	int fd;
1623 	FILE *f;
1624 	cpc_map_t *map;
1625 
1626 	if (asprintf(&tmpname, ".%s.%d", fname, getpid()) == -1) {
1627 		err(EXIT_FAILURE, "failed to construct temporary file name");
1628 	}
1629 
1630 	if ((fd = openat(dirfd, tmpname, O_RDWR | O_CREAT, 0644)) < 0) {
1631 		err(EXIT_FAILURE, "failed to create temporary file %s",
1632 		    tmpname);
1633 	}
1634 
1635 	if ((f = fdopen(fd, "w")) == NULL) {
1636 		cpcgen_remove_tmpfile(dirfd, tmpname);
1637 		err(EXIT_FAILURE, "failed to fdopen temporary file");
1638 	}
1639 
1640 	if (fprintf(f, cpcgen_cfile_cddl_header) == -1) {
1641 		cpcgen_remove_tmpfile(dirfd, tmpname);
1642 		err(EXIT_FAILURE, "failed to write header to "
1643 		    "temporary file for %s", fname);
1644 	}
1645 
1646 	if (fprintf(f, "#ifndef _OPTERON_PCBE_CPCGEN_H\n"
1647 	    "#define\t_OPTERON_PCBE_CPCGEN_H\n"
1648 	    "\n"
1649 	    "#ifdef __cplusplus\n"
1650 	    "extern \"C\" {\n"
1651 	    "#endif\n"
1652 	    "\n") == -1) {
1653 		cpcgen_remove_tmpfile(dirfd, tmpname);
1654 		err(EXIT_FAILURE, "failed to write header to "
1655 		    "temporary file for %s", fname);
1656 	}
1657 
1658 	for (map = cpcgen_maps; map != NULL; map = map->cmap_next) {
1659 		if (fprintf(f, "extern const amd_event_t "
1660 		    "opteron_pcbe_%s_events[];\n", map->cmap_name) == -1) {
1661 			cpcgen_remove_tmpfile(dirfd, tmpname);
1662 			err(EXIT_FAILURE, "failed to write header to "
1663 			    "temporary file for %s", fname);
1664 		}
1665 	}
1666 
1667 	if (fprintf(f, "\n"
1668 	    "#ifdef __cplusplus\n"
1669 	    "}\n"
1670 	    "#endif\n"
1671 	    "\n"
1672 	    "#endif /* _OPTERON_PCBE_CPCGEN_H */\n") == -1) {
1673 		cpcgen_remove_tmpfile(dirfd, tmpname);
1674 		err(EXIT_FAILURE, "failed to write header to "
1675 		    "temporary file for %s", fname);
1676 	}
1677 
1678 
1679 
1680 	if (fflush(f) != 0 || fclose(f) != 0) {
1681 		cpcgen_remove_tmpfile(dirfd, tmpname);
1682 		err(EXIT_FAILURE, "failed to flush and close temporary file");
1683 	}
1684 
1685 	if (renameat(dirfd, tmpname, dirfd, fname) != 0) {
1686 		err(EXIT_FAILURE, "failed to rename temporary file %s",
1687 		    tmpname);
1688 	}
1689 
1690 	free(tmpname);
1691 }
1692 
1693 static boolean_t
cpcgen_cfile_amd_before(FILE * f,cpc_map_t * map)1694 cpcgen_cfile_amd_before(FILE *f, cpc_map_t *map)
1695 {
1696 	if (fprintf(f, cpcgen_cfile_amd_header, map->cmap_name) == -1) {
1697 		warn("failed to write header to temporary file for %s",
1698 		    map->cmap_path);
1699 		return (B_FALSE);
1700 	}
1701 
1702 	if (fprintf(f, cpcgen_cfile_amd_table_start, map->cmap_name) == -1) {
1703 		warn("failed to write header to temporary file for %s",
1704 		    map->cmap_path);
1705 		return (B_FALSE);
1706 	}
1707 
1708 
1709 	return (B_TRUE);
1710 }
1711 
1712 static boolean_t
cpcgen_cfile_amd_after(FILE * f,cpc_map_t * map)1713 cpcgen_cfile_amd_after(FILE *f, cpc_map_t *map)
1714 {
1715 	if (fprintf(f, cpcgen_cfile_amd_table_end) == -1) {
1716 		warn("failed to write footer to temporary file for %s",
1717 		    map->cmap_path);
1718 		return (B_FALSE);
1719 	}
1720 
1721 	return (B_TRUE);
1722 }
1723 
1724 static boolean_t
cpcgen_cfile_amd_event(FILE * f,nvlist_t * nvl,const char * path,uint_t ent)1725 cpcgen_cfile_amd_event(FILE *f, nvlist_t *nvl, const char *path, uint_t ent)
1726 {
1727 	char *name, *code, *umode;
1728 	uint32_t i, length;
1729 	nvlist_t *units;
1730 
1731 	if (nvlist_lookup_string(nvl, "name", &name) != 0) {
1732 		warnx("Found event without 'name' property in %s, entry %u",
1733 		    path, ent);
1734 		return (B_FALSE);
1735 	}
1736 
1737 	if (nvlist_lookup_string(nvl, "code", &code) != 0) {
1738 		warnx("event %s (index %u) from %s missing required properties "
1739 		    "for C translation", name, ent, path);
1740 		return (B_FALSE);
1741 	}
1742 
1743 	if (fprintf(f, "\t{ \"%s\", %s, 0 },\n", name, code) == -1) {
1744 		warn("failed to write out entry %s from %s", name, path);
1745 		return (B_FALSE);
1746 	}
1747 
1748 	/*
1749 	 * The 'units' array is optional. If the rule has a specific 'unit_mode'
1750 	 * indicating how the units should be combined, skip that. We don't know
1751 	 * how to properly process that right now.
1752 	 */
1753 	if (nvlist_lookup_nvlist(nvl, "units", &units) != 0) {
1754 		return (B_TRUE);
1755 	}
1756 
1757 	if (nvlist_lookup_string(nvl, "unit_mode", &umode) == 0) {
1758 		return (B_TRUE);
1759 	}
1760 
1761 	if (nvlist_lookup_uint32(units, "length", &length) != 0) {
1762 		warnx("found units array, but could not look up length "
1763 		    "property for events %s (index %u) in file %s",
1764 		    name, ent, path);
1765 		return (B_FALSE);
1766 	}
1767 
1768 	for (i = 0; i < length; i++) {
1769 		nvlist_t *uvl;
1770 		char num[64];
1771 		char *uname, *urw;
1772 		int32_t bit;
1773 
1774 		(void) snprintf(num, sizeof (num), "%u", i);
1775 		if (nvlist_lookup_nvlist(units, num, &uvl) != 0) {
1776 			warnx("failed to look up unit %u for event %s (index "
1777 			    "%u) in file %s", i, name, ent, path);
1778 			return (B_FALSE);
1779 		}
1780 
1781 		if (nvlist_lookup_string(uvl, "name", &uname) != 0 ||
1782 		    nvlist_lookup_string(uvl, "rw", &urw) != 0 ||
1783 		    nvlist_lookup_int32(uvl, "bit", &bit) != 0) {
1784 			warnx("failed to find required members for unit array "
1785 			    "entry %u of event %s (index %u) from file %s",
1786 			    i, name, ent, path);
1787 			dump_nvlist(uvl, 0);
1788 			return (B_FALSE);
1789 		}
1790 
1791 		if (bit < 0 || bit > 31) {
1792 			warnx("event %s (index %u) from file %s has invalid "
1793 			    "bit value: %d; skipping", name, ent, path, bit);
1794 			continue;
1795 		}
1796 
1797 		if (strcasecmp(urw, "Read-write") != 0)
1798 			continue;
1799 
1800 		if (fprintf(f, "\t{ \"%s.%s\", %s, 0x%x },\n", name, uname,
1801 		    code, 1U << bit) == -1) {
1802 			warn("failed to write out entry %s from %s", name,
1803 			    path);
1804 			return (B_FALSE);
1805 		}
1806 	}
1807 
1808 	return (B_TRUE);
1809 }
1810 
1811 /*
1812  * For each processor family, generate a data file that contains all of the
1813  * events that we support. Also generate a header that can be included that
1814  * declares all of the tables.
1815  */
1816 static void
cpcgen_gen(int dirfd)1817 cpcgen_gen(int dirfd)
1818 {
1819 	cpc_map_t *map = cpcgen_maps;
1820 
1821 	if (map == NULL) {
1822 		errx(EXIT_FAILURE, "no platforms found or matched");
1823 	}
1824 
1825 	for (map = cpcgen_maps; map != NULL; map = map->cmap_next) {
1826 		int fd, ret;
1827 		FILE *f;
1828 		char *tmpname, *name;
1829 		uint32_t length, i;
1830 
1831 		if ((name = cpcgen_ops.cgen_op_name(map)) == NULL) {
1832 			exit(EXIT_FAILURE);
1833 		}
1834 
1835 		if (asprintf(&tmpname, ".%s.%d", name, getpid()) == -1) {
1836 			err(EXIT_FAILURE, "failed to construct temporary file "
1837 			    "name");
1838 		}
1839 
1840 		if ((fd = openat(dirfd, tmpname, O_RDWR | O_CREAT, 0444)) < 0) {
1841 			err(EXIT_FAILURE, "failed to create temporary file %s",
1842 			    tmpname);
1843 		}
1844 
1845 		if ((f = fdopen(fd, "w")) == NULL) {
1846 			cpcgen_remove_tmpfile(dirfd, tmpname);
1847 			err(EXIT_FAILURE, "failed to fdopen temporary file");
1848 		}
1849 
1850 		if (!cpcgen_ops.cgen_op_file_before(f, map)) {
1851 			cpcgen_remove_tmpfile(dirfd, tmpname);
1852 			exit(EXIT_FAILURE);
1853 		}
1854 
1855 		/*
1856 		 * Iterate over array contents.
1857 		 */
1858 		if ((ret = nvlist_lookup_uint32(map->cmap_data, "length",
1859 		    &length)) != 0) {
1860 			errx(EXIT_FAILURE, "failed to look up length property "
1861 			    "in parsed data for %s: %s", map->cmap_path,
1862 			    strerror(ret));
1863 		}
1864 
1865 		for (i = 0; i < length; i++) {
1866 			nvlist_t *nvl;
1867 			char num[64];
1868 
1869 			(void) snprintf(num, sizeof (num), "%u", i);
1870 			if ((ret = nvlist_lookup_nvlist(map->cmap_data,
1871 			    num, &nvl)) != 0) {
1872 				cpcgen_remove_tmpfile(dirfd, tmpname);
1873 				errx(EXIT_FAILURE, "failed to look up array "
1874 				    "entry %u in parsed data for %s: %s", i,
1875 				    map->cmap_path, strerror(ret));
1876 			}
1877 
1878 			if (cpcgen_ops.cgen_op_skip != NULL &&
1879 			    cpcgen_ops.cgen_op_skip(nvl, map->cmap_path, i)) {
1880 				continue;
1881 			}
1882 
1883 			if (!cpcgen_ops.cgen_op_event(f, nvl, map->cmap_path,
1884 			    i)) {
1885 				cpcgen_remove_tmpfile(dirfd, tmpname);
1886 				exit(EXIT_FAILURE);
1887 			}
1888 		}
1889 
1890 		if (!cpcgen_ops.cgen_op_file_after(f, map)) {
1891 			cpcgen_remove_tmpfile(dirfd, tmpname);
1892 			exit(EXIT_FAILURE);
1893 		}
1894 
1895 		if (fflush(f) != 0 || fclose(f) != 0) {
1896 			cpcgen_remove_tmpfile(dirfd, tmpname);
1897 			err(EXIT_FAILURE, "failed to flush and close "
1898 			    "temporary file");
1899 		}
1900 
1901 		if (renameat(dirfd, tmpname, dirfd, name) != 0) {
1902 			err(EXIT_FAILURE, "failed to rename temporary file %s",
1903 			    tmpname);
1904 		}
1905 
1906 		free(name);
1907 		free(tmpname);
1908 	}
1909 }
1910 
1911 static void
cpcgen_usage(const char * fmt,...)1912 cpcgen_usage(const char *fmt, ...)
1913 {
1914 	if (fmt != NULL) {
1915 		va_list ap;
1916 
1917 		(void) fprintf(stderr, "%s: ", cpcgen_progname);
1918 		va_start(ap, fmt);
1919 		(void) vfprintf(stderr, fmt, ap);
1920 		va_end(ap);
1921 	}
1922 
1923 	(void) fprintf(stderr, "Usage: %s -a|-p platform -c|-H|-m -d datadir "
1924 	    "-o outdir\n"
1925 	    "\n"
1926 	    "\t-a  generate data for all platforms\n"
1927 	    "\t-c  generate C file for CPC\n"
1928 	    "\t-d  specify the directory containt perfmon data\n"
1929 	    "\t-H  generate header file and common files\n"
1930 	    "\t-m  generate manual pages for CPC data\n"
1931 	    "\t-o  output files in directory outdir\n"
1932 	    "\t-p  generate data for a specified platform\n",
1933 	    cpcgen_progname);
1934 }
1935 
1936 int
main(int argc,char * argv[])1937 main(int argc, char *argv[])
1938 {
1939 	int c, outdirfd;
1940 	boolean_t do_mpage = B_FALSE, do_cfile = B_FALSE, do_header = B_FALSE,
1941 	    do_all = B_FALSE;
1942 	const char *datadir = NULL, *outdir = NULL, *platform = NULL;
1943 	uint_t count = 0;
1944 
1945 	cpcgen_progname = basename(argv[0]);
1946 
1947 	while ((c = getopt(argc, argv, ":acd:hHmo:p:")) != -1) {
1948 		switch (c) {
1949 		case 'a':
1950 			do_all = B_TRUE;
1951 			break;
1952 		case 'c':
1953 			do_cfile = B_TRUE;
1954 			break;
1955 		case 'd':
1956 			datadir = optarg;
1957 			break;
1958 		case 'm':
1959 			do_mpage = B_TRUE;
1960 			break;
1961 		case 'H':
1962 			do_header = B_TRUE;
1963 			break;
1964 		case 'o':
1965 			outdir = optarg;
1966 			break;
1967 		case 'p':
1968 			platform = optarg;
1969 			break;
1970 		case ':':
1971 			cpcgen_usage("Option -%c requires an operand\n",
1972 			    optopt);
1973 			return (2);
1974 		case '?':
1975 			cpcgen_usage("Unknown option: -%c\n", optopt);
1976 			return (2);
1977 		case 'h':
1978 		default:
1979 			cpcgen_usage(NULL);
1980 			return (2);
1981 		}
1982 	}
1983 
1984 	count = 0;
1985 	if (do_mpage)
1986 		count++;
1987 	if (do_cfile)
1988 		count++;
1989 	if (do_header)
1990 		count++;
1991 	if (count > 1) {
1992 		cpcgen_usage("Only one of -c, -h, and -m may be specified\n");
1993 		return (2);
1994 	} else if (count == 0) {
1995 		cpcgen_usage("One of -c, -h, and -m is required\n");
1996 		return (2);
1997 	}
1998 
1999 	count = 0;
2000 	if (do_all)
2001 		count++;
2002 	if (platform != NULL)
2003 		count++;
2004 	if (count > 1) {
2005 		cpcgen_usage("Only one of -a and -p may be specified\n");
2006 		return (2);
2007 	} else if (count == 0) {
2008 		cpcgen_usage("One of -a and -p is required\n");
2009 		return (2);
2010 	}
2011 
2012 	if (outdir == NULL) {
2013 		cpcgen_usage("Missing required output directory (-o)\n");
2014 		return (2);
2015 	}
2016 
2017 	if ((outdirfd = open(outdir, O_RDONLY)) < 0) {
2018 		err(EXIT_FAILURE, "failed to open output directory %s", outdir);
2019 	}
2020 
2021 	if (datadir == NULL) {
2022 		cpcgen_usage("Missing required data directory (-d)\n");
2023 		return (2);
2024 	}
2025 
2026 	cpcgen_determine_vendor(datadir);
2027 
2028 	switch (cpcgen_mode) {
2029 	case CPCGEN_MODE_INTEL:
2030 		cpcgen_ops.cgen_op_gather = cpcgen_read_intel;
2031 		cpcgen_ops.cgen_op_common = cpcgen_common_intel_files;
2032 		cpcgen_ops.cgen_op_skip = cpcgen_skip_intel_entry;
2033 		if (do_mpage) {
2034 			cpcgen_ops.cgen_op_name = cpcgen_manual_intel_name;
2035 			cpcgen_ops.cgen_op_file_before =
2036 			    cpcgen_manual_intel_file_before;
2037 			cpcgen_ops.cgen_op_file_after =
2038 			    cpcgen_manual_intel_file_after;
2039 			cpcgen_ops.cgen_op_event = cpcgen_manual_intel_event;
2040 		} else {
2041 			cpcgen_ops.cgen_op_name = cpcgen_cfile_intel_name;
2042 			cpcgen_ops.cgen_op_file_before =
2043 			    cpcgen_cfile_intel_before;
2044 			cpcgen_ops.cgen_op_file_after =
2045 			    cpcgen_cfile_intel_after;
2046 			cpcgen_ops.cgen_op_event = cpcgen_cfile_intel_event;
2047 		}
2048 		break;
2049 	case CPCGEN_MODE_AMD:
2050 		cpcgen_ops.cgen_op_gather = cpcgen_read_amd;
2051 		cpcgen_ops.cgen_op_common = cpcgen_common_amd_files;
2052 		cpcgen_ops.cgen_op_skip = NULL;
2053 		if (do_mpage) {
2054 			cpcgen_ops.cgen_op_name = cpcgen_manual_amd_name;
2055 			cpcgen_ops.cgen_op_file_before =
2056 			    cpcgen_manual_amd_file_before;
2057 			cpcgen_ops.cgen_op_file_after =
2058 			    cpcgen_manual_amd_file_after;
2059 			cpcgen_ops.cgen_op_event = cpcgen_manual_amd_event;
2060 		} else {
2061 			cpcgen_ops.cgen_op_name = cpcgen_cfile_amd_name;
2062 			cpcgen_ops.cgen_op_file_before =
2063 			    cpcgen_cfile_amd_before;
2064 			cpcgen_ops.cgen_op_file_after = cpcgen_cfile_amd_after;
2065 			cpcgen_ops.cgen_op_event = cpcgen_cfile_amd_event;
2066 
2067 		}
2068 		break;
2069 	default:
2070 		errx(EXIT_FAILURE, "failed to determine if operating on AMD or "
2071 		    "Intel");
2072 		break;
2073 	}
2074 
2075 	cpcgen_ops.cgen_op_gather(datadir, platform);
2076 
2077 	if (do_header) {
2078 		cpcgen_ops.cgen_op_common(outdirfd);
2079 		return (0);
2080 	}
2081 
2082 	cpcgen_gen(outdirfd);
2083 
2084 	return (0);
2085 }
2086