xref: /illumos-gate/usr/src/tools/cpcgen/cpcgen.c (revision 43449cdc)
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 		}
628 
629 		cpcgen_mode = CPCGEN_MODE_AMD;
630 	}
631 
632 	free(mappath);
633 }
634 
635 /*
636  * Read in all the data files that exist for AMD.
637  *
638  * Our family names for AMD systems are based on the family and type so a given
639  * name will look like f17h_<core>_core.json.
640  */
641 static void
cpcgen_read_amd(const char * datadir,const char * platform)642 cpcgen_read_amd(const char *datadir, const char *platform)
643 {
644 	DIR *dir;
645 	struct dirent *d;
646 	const char *suffix = ".json";
647 	const size_t slen = strlen(suffix);
648 
649 	if ((dir = opendir(datadir)) == NULL) {
650 		err(EXIT_FAILURE, "failed to open directory %s", datadir);
651 	}
652 
653 	while ((d = readdir(dir)) != NULL) {
654 		char *name, *c;
655 		cpc_map_t *map;
656 		nvlist_t *parsed;
657 
658 		if ((name = strdup(d->d_name)) == NULL) {
659 			errx(EXIT_FAILURE, "ran out of memory duplicating "
660 			    "name %s", d->d_name);
661 		}
662 		c = strstr(name, suffix);
663 
664 		if (c == NULL) {
665 			free(name);
666 			continue;
667 		}
668 
669 		/*
670 		 * Chop off the .json. Next, make sure we have both _ present.
671 		 */
672 		if (*(c + slen) != '\0') {
673 			free(name);
674 			continue;
675 		}
676 		*c = '\0';
677 
678 		c = strchr(name, '_');
679 		if (c == NULL) {
680 			errx(EXIT_FAILURE, "unexpected AMD JSON file name: %s",
681 			    d->d_name);
682 		}
683 
684 		c++;
685 		c = strchr(c, '_');
686 		if (c == NULL) {
687 			errx(EXIT_FAILURE, "unexpected AMD JSON file name: %s",
688 			    d->d_name);
689 		}
690 		*c = '\0';
691 		c++;
692 		if (strcmp(c, "core") != 0) {
693 			errx(EXIT_FAILURE, "unexpected AMD JSON file name: %s",
694 			    d->d_name);
695 		}
696 
697 		if (platform != NULL && strcmp(platform, name) != 0) {
698 			free(name);
699 			continue;
700 		}
701 
702 		if ((map = calloc(1, sizeof (cpc_map_t))) == NULL) {
703 			err(EXIT_FAILURE, "failed to allocate space for cpc "
704 			    "file");
705 		}
706 
707 		parsed = cpcgen_read_datafile(datadir, d->d_name);
708 		if ((map->cmap_path = strdup(d->d_name)) == NULL) {
709 			err(EXIT_FAILURE, "failed to duplicate path string");
710 		}
711 		map->cmap_type = CPC_FILE_CORE;
712 		map->cmap_data = parsed;
713 		map->cmap_name = name;
714 		map->cmap_procs = NULL;
715 
716 		map->cmap_next = cpcgen_maps;
717 		cpcgen_maps = map;
718 	}
719 }
720 
721 /*
722  * Read in the mapfile.csv that is used to map between processor families and
723  * parse this. Each line has a comma separated value.
724  */
725 static void
cpcgen_read_intel(const char * datadir,const char * platform)726 cpcgen_read_intel(const char *datadir, const char *platform)
727 {
728 	FILE *map;
729 	char *mappath, *last;
730 	char *data = NULL;
731 	size_t datalen = 0;
732 	uint_t lineno;
733 
734 	if (asprintf(&mappath, "%s/%s", datadir, cpcgen_intel_mapfile) == -1) {
735 		err(EXIT_FAILURE, "failed to construct path to mapfile");
736 	}
737 
738 	if ((map = fopen(mappath, "r")) == NULL) {
739 		err(EXIT_FAILURE, "failed to open data mapfile %s", mappath);
740 	}
741 
742 	lineno = 0;
743 	while (getline(&data, &datalen, map) != -1) {
744 		char *fstr, *path, *tstr;
745 		const char *name;
746 		uint_t family, model, nsteps;
747 		uint_t steppings[CPROC_MAX_STEPPINGS];
748 
749 		cpc_type_t type;
750 		cpc_map_t *map;
751 		cpc_proc_t *proc;
752 
753 		/*
754 		 * The first line contains the header:
755 		 * Family-model,Version,Filename,EventType
756 		 */
757 		lineno++;
758 		if (lineno == 1) {
759 			continue;
760 		}
761 
762 		if ((fstr = strtok_r(data, ",", &last)) == NULL ||
763 		    strtok_r(NULL, ",", &last) == NULL ||
764 		    (path = strtok_r(NULL, ",", &last)) == NULL ||
765 		    (tstr = strtok_r(NULL, "\n", &last)) == NULL) {
766 			errx(EXIT_FAILURE, "failed to parse mapfile line "
767 			    "%u in %s", lineno, mappath);
768 		}
769 
770 		cpcgen_parse_model(fstr, &family, &model, &nsteps, steppings);
771 
772 		if (strcmp(tstr, "core") == 0) {
773 			type = CPC_FILE_CORE;
774 		} else if (strcmp(tstr, "offcore") == 0) {
775 			type = CPC_FILE_OFF_CORE;
776 		} else if (strcmp(tstr, "uncore") == 0) {
777 			type = CPC_FILE_UNCORE;
778 		} else if (strcmp(tstr, "fp_arith_inst") == 0) {
779 			type = CPC_FILE_FP_MATH;
780 		} else if (strcmp(tstr, "uncore experimental") == 0) {
781 			type = CPC_FILE_UNCORE_EXP;
782 		} else {
783 			errx(EXIT_FAILURE, "unknown file type \"%s\" on line "
784 			    "%u", tstr, lineno);
785 		}
786 
787 		if ((name = cpcgen_use_arch(path, type, platform)) == NULL)
788 			continue;
789 
790 		if ((map = cpcgen_map_lookup(path)) == NULL) {
791 			nvlist_t *parsed;
792 
793 			parsed = cpcgen_read_datafile(datadir, path);
794 
795 			if ((map = calloc(1, sizeof (cpc_map_t))) == NULL) {
796 				err(EXIT_FAILURE, "failed to allocate space "
797 				    "for cpc file");
798 			}
799 
800 			if ((map->cmap_path = strdup(path)) == NULL) {
801 				err(EXIT_FAILURE, "failed to duplicate path "
802 				    "string");
803 			}
804 
805 			map->cmap_type = type;
806 			map->cmap_data = parsed;
807 			map->cmap_name = name;
808 			map->cmap_procs = NULL;
809 
810 			map->cmap_next = cpcgen_maps;
811 			cpcgen_maps = map;
812 		}
813 
814 		if ((proc = calloc(1, sizeof (cpc_proc_t))) == NULL) {
815 			err(EXIT_FAILURE, "failed to allocate memory for "
816 			    "family and model tracking");
817 		}
818 
819 		proc->cproc_family = family;
820 		proc->cproc_model = model;
821 		proc->cproc_nsteps = nsteps;
822 		if (nsteps > 0) {
823 			bcopy(steppings, proc->cproc_steppings,
824 			    sizeof (steppings));
825 		}
826 		proc->cproc_next = map->cmap_procs;
827 		map->cmap_procs = proc;
828 	}
829 
830 	if (errno != 0 || ferror(map)) {
831 		err(EXIT_FAILURE, "failed to read %s", mappath);
832 	}
833 
834 	if (fclose(map) == EOF) {
835 		err(EXIT_FAILURE, "failed to close %s", mappath);
836 	}
837 	free(data);
838 	free(mappath);
839 }
840 
841 static char *
cpcgen_manual_intel_name(cpc_map_t * map)842 cpcgen_manual_intel_name(cpc_map_t *map)
843 {
844 	char *name;
845 
846 	if (asprintf(&name, "%s_events.3cpc", map->cmap_name) == -1) {
847 		warn("failed to assemble manual page name for %s",
848 		    map->cmap_path);
849 		return (NULL);
850 	}
851 
852 	return (name);
853 }
854 
855 static boolean_t
cpcgen_manual_intel_file_before(FILE * f,cpc_map_t * map)856 cpcgen_manual_intel_file_before(FILE *f, cpc_map_t *map)
857 {
858 	size_t i;
859 	char *upper;
860 	cpc_proc_t *proc;
861 
862 	if ((upper = strdup(map->cmap_name)) == NULL) {
863 		warn("failed to duplicate manual name for %s", map->cmap_name);
864 		return (B_FALSE);
865 	}
866 
867 	for (i = 0; upper[i] != '\0'; i++) {
868 		upper[i] = toupper(upper[i]);
869 	}
870 
871 	if (fprintf(f, cpcgen_manual_intel_intel_header, map->cmap_path, upper,
872 	    map->cmap_name) == -1) {
873 		warn("failed to write out manual header for %s",
874 		    map->cmap_name);
875 		free(upper);
876 		return (B_FALSE);
877 	}
878 	free(upper);
879 
880 	for (proc = map->cmap_procs; proc != NULL; proc = proc->cproc_next) {
881 		if (proc->cproc_nsteps > 0) {
882 			uint_t step;
883 
884 			for (step = 0; step < proc->cproc_nsteps; step++) {
885 				if (fprintf(f, ".It\n.Sy Family 0x%x, Model "
886 				    "0x%x, Stepping 0x%x\n",
887 				    proc->cproc_family, proc->cproc_model,
888 				    proc->cproc_steppings[step]) == -1) {
889 					warn("failed to write out model "
890 					    "information for %s",
891 					    map->cmap_name);
892 					return (B_FALSE);
893 				}
894 			}
895 		} else {
896 			if (fprintf(f, ".It\n.Sy Family 0x%x, Model 0x%x\n",
897 			    proc->cproc_family, proc->cproc_model) == -1) {
898 				warn("failed to write out model information "
899 				    "for %s", map->cmap_name);
900 				return (B_FALSE);
901 			}
902 		}
903 	}
904 
905 	if (fprintf(f, cpcgen_manual_intel_data) == -1) {
906 		warn("failed to write out manual header for %s",
907 		    map->cmap_name);
908 		return (B_FALSE);
909 	}
910 
911 	return (B_TRUE);
912 }
913 
914 static boolean_t
cpcgen_manual_intel_file_after(FILE * f,cpc_map_t * map)915 cpcgen_manual_intel_file_after(FILE *f, cpc_map_t *map)
916 {
917 	if (fprintf(f, cpcgen_manual_intel_trailer) == -1) {
918 		warn("failed to write out manual header for %s",
919 		    map->cmap_name);
920 		return (B_FALSE);
921 	}
922 
923 	return (B_TRUE);
924 }
925 
926 static boolean_t
cpcgen_manual_intel_event(FILE * f,nvlist_t * nvl,const char * path,uint32_t ent)927 cpcgen_manual_intel_event(FILE *f, nvlist_t *nvl, const char *path,
928     uint32_t ent)
929 {
930 	char *event, *lname, *brief = NULL, *public = NULL, *errata = NULL;
931 	size_t i;
932 
933 	if (nvlist_lookup_string(nvl, "EventName", &event) != 0) {
934 		warnx("Found event without 'EventName' property "
935 		    "in %s, entry %u", path, ent);
936 		return (B_FALSE);
937 	}
938 
939 	/*
940 	 * Intel uses capital names. CPC historically uses lower case names.
941 	 */
942 	if ((lname = strdup(event)) == NULL) {
943 		err(EXIT_FAILURE, "failed to duplicate event name %s", event);
944 	}
945 	for (i = 0; lname[i] != '\0'; i++) {
946 		lname[i] = tolower(event[i]);
947 	}
948 
949 	/*
950 	 * Try to get the other event fields, but if they're not there, don't
951 	 * worry about it.
952 	 */
953 	(void) nvlist_lookup_string(nvl, "BriefDescription", &brief);
954 	(void) nvlist_lookup_string(nvl, "PublicDescription", &public);
955 	(void) nvlist_lookup_string(nvl, "Errata", &errata);
956 	if (errata != NULL && (strcmp(errata, "0") == 0 ||
957 	    strcmp(errata, "null") == 0)) {
958 		errata = NULL;
959 	}
960 
961 	if (fprintf(f, ".It Sy %s\n", lname) == -1) {
962 		warn("failed to write out event entry %s", event);
963 		free(lname);
964 		return (B_FALSE);
965 	}
966 
967 	if (public != NULL) {
968 		if (fprintf(f, "%s\n", public) == -1) {
969 			warn("failed to write out event entry %s", event);
970 			free(lname);
971 			return (B_FALSE);
972 		}
973 	} else if (brief != NULL) {
974 		if (fprintf(f, "%s\n", brief) == -1) {
975 			warn("failed to write out event entry %s", event);
976 			free(lname);
977 			return (B_FALSE);
978 		}
979 	}
980 
981 	if (errata != NULL) {
982 		if (fprintf(f, ".Pp\nThe following errata may apply to this: "
983 		    "%s\n", errata) == -1) {
984 
985 			warn("failed to write out event entry %s", event);
986 			free(lname);
987 			return (B_FALSE);
988 		}
989 	}
990 
991 	free(lname);
992 	return (B_TRUE);
993 }
994 
995 static char *
cpcgen_cfile_intel_name(cpc_map_t * map)996 cpcgen_cfile_intel_name(cpc_map_t *map)
997 {
998 	char *name;
999 
1000 	if (asprintf(&name, "core_pcbe_%s.c", map->cmap_name) == -1) {
1001 		warn("failed to assemble file name for %s", map->cmap_path);
1002 		return (NULL);
1003 	}
1004 
1005 	return (name);
1006 }
1007 
1008 static boolean_t
cpcgen_cfile_intel_before(FILE * f,cpc_map_t * map)1009 cpcgen_cfile_intel_before(FILE *f, cpc_map_t *map)
1010 {
1011 	if (fprintf(f, cpcgen_cfile_intel_header, map->cmap_path) == -1) {
1012 		warn("failed to write header to temporary file for %s",
1013 		    map->cmap_path);
1014 		return (B_FALSE);
1015 	}
1016 
1017 	if (fprintf(f, cpcgen_cfile_intel_table_start, map->cmap_name) == -1) {
1018 		warn("failed to write header to temporary file for %s",
1019 		    map->cmap_path);
1020 		return (B_FALSE);
1021 	}
1022 
1023 	return (B_TRUE);
1024 }
1025 
1026 static boolean_t
cpcgen_cfile_intel_after(FILE * f,cpc_map_t * map)1027 cpcgen_cfile_intel_after(FILE *f, cpc_map_t *map)
1028 {
1029 	if (fprintf(f, cpcgen_cfile_intel_table_end) == -1) {
1030 		warn("failed to write footer to temporary file for %s",
1031 		    map->cmap_path);
1032 		return (B_FALSE);
1033 	}
1034 
1035 	return (B_TRUE);
1036 }
1037 
1038 static boolean_t
cpcgen_cfile_intel_event(FILE * f,nvlist_t * nvl,const char * path,uint_t ent)1039 cpcgen_cfile_intel_event(FILE *f, nvlist_t *nvl, const char *path, uint_t ent)
1040 {
1041 	char *ecode, *umask, *name, *counter, *lname, *cmask;
1042 	size_t i;
1043 
1044 	if (nvlist_lookup_string(nvl, "EventName", &name) != 0) {
1045 		warnx("Found event without 'EventName' property "
1046 		    "in %s, entry %u", path, ent);
1047 		return (B_FALSE);
1048 	}
1049 
1050 	if (nvlist_lookup_string(nvl, "EventCode", &ecode) != 0 ||
1051 	    nvlist_lookup_string(nvl, "UMask", &umask) != 0 ||
1052 	    nvlist_lookup_string(nvl, "Counter", &counter) != 0) {
1053 		warnx("event %s (index %u) from %s, missing "
1054 		    "required properties for C file translation",
1055 		    name, ent, path);
1056 		return (B_FALSE);
1057 	}
1058 
1059 	/*
1060 	 * While we could try and parse the counters manually, just do this the
1061 	 * max power way for now based on all possible values.
1062 	 */
1063 	if (strcmp(counter, "0") == 0 || strcmp(counter, "0,") == 0) {
1064 		cmask = "C0";
1065 	} else if (strcmp(counter, "1") == 0) {
1066 		cmask = "C1";
1067 	} else if (strcmp(counter, "2") == 0) {
1068 		cmask = "C2";
1069 	} else if (strcmp(counter, "3") == 0) {
1070 		cmask = "C3";
1071 	} else if (strcmp(counter, "0,1") == 0) {
1072 		cmask = "C0|C1";
1073 	} else if (strcmp(counter, "0,1,2") == 0) {
1074 		cmask = "C0|C1|C2";
1075 	} else if (strcmp(counter, "0,1,2,3") == 0) {
1076 		cmask = "C0|C1|C2|C3";
1077 	} else if (strcmp(counter, "0,1,2,3,4,5,6,7") == 0) {
1078 		/*
1079 		 * We don't support the larger number of counters on some
1080 		 * platforms right now, so just truncate it to the supported
1081 		 * set.
1082 		 */
1083 		cmask = "C0|C1|C2|C3";
1084 	} else if (strcmp(counter, "0,2,3") == 0) {
1085 		cmask = "C0|C2|C3";
1086 	} else if (strcmp(counter, "1,2,3") == 0) {
1087 		cmask = "C1|C2|C3";
1088 	} else if (strcmp(counter, "2,3") == 0) {
1089 		cmask = "C2|C3";
1090 	} else {
1091 		warnx("event %s (index %u) from %s, has unknown "
1092 		    "counter value \"%s\"", name, ent, path, counter);
1093 		return (B_FALSE);
1094 	}
1095 
1096 
1097 	/*
1098 	 * Intel uses capital names. CPC historically uses lower case names.
1099 	 */
1100 	if ((lname = strdup(name)) == NULL) {
1101 		err(EXIT_FAILURE, "failed to duplicate event name %s", name);
1102 	}
1103 	for (i = 0; lname[i] != '\0'; i++) {
1104 		lname[i] = tolower(name[i]);
1105 	}
1106 
1107 	if (fprintf(f, "\t{ %s, %s, %s, \"%s\" },\n", ecode, umask, cmask,
1108 	    lname) == -1) {
1109 		warn("failed to write out entry %s from %s", name, path);
1110 		free(lname);
1111 		return (B_FALSE);
1112 	}
1113 
1114 	free(lname);
1115 
1116 	/*
1117 	 * Check if we have any PAPI aliases.
1118 	 */
1119 	for (i = 0; cpcgen_intel_papi_map[i].cpapi_intc != NULL; i++) {
1120 		if (strcmp(name, cpcgen_intel_papi_map[i].cpapi_intc) != 0)
1121 			continue;
1122 
1123 		if (fprintf(f, "\t{ %s, %s, %s, \"%s\" },\n", ecode, umask,
1124 		    cmask, cpcgen_intel_papi_map[i].cpapi_papi) == -1) {
1125 			warn("failed to write out entry %s from %s", name,
1126 			    path);
1127 			return (B_FALSE);
1128 		}
1129 	}
1130 
1131 	return (B_TRUE);
1132 }
1133 
1134 static boolean_t
cpcgen_generate_map(FILE * f,cpc_map_t * map,boolean_t start)1135 cpcgen_generate_map(FILE *f, cpc_map_t *map, boolean_t start)
1136 {
1137 	cpc_proc_t *p;
1138 
1139 	if (fprintf(f, "\t%sif (", start ? "" : "} else ") == -1) {
1140 		return (B_FALSE);
1141 	}
1142 
1143 	for (p = map->cmap_procs; p != NULL; p = p->cproc_next) {
1144 		/*
1145 		 * Make sure the line is padded so the generated C code looks
1146 		 * like reasonable C style.
1147 		 */
1148 		if (p != map->cmap_procs) {
1149 			if (fputs("\t    ", f) == -1) {
1150 				return (B_FALSE);
1151 			}
1152 		}
1153 
1154 		if (p->cproc_nsteps > 0) {
1155 			uint_t i;
1156 
1157 			if (fprintf(f, "(model == 0x%x &&\n\t    (",
1158 			    p->cproc_model) == -1) {
1159 				return (B_FALSE);
1160 			}
1161 
1162 			for (i = 0; i < p->cproc_nsteps; i++) {
1163 				if (fprintf(f, "stepping == 0x%x%s",
1164 				    p->cproc_steppings[i],
1165 				    i + 1 != p->cproc_nsteps ?
1166 				    " ||\n\t    " : "") == -1) {
1167 					return (B_FALSE);
1168 				}
1169 			}
1170 
1171 			if (fputs("))", f) == -1) {
1172 				return (B_FALSE);
1173 			}
1174 		} else if (fprintf(f, "model == 0x%x", p->cproc_model) == -1) {
1175 			return (B_FALSE);
1176 		}
1177 
1178 		if (fprintf(f, "%s\n",
1179 		    p->cproc_next != NULL ? " ||" : ") {") == -1) {
1180 			return (B_FALSE);
1181 		}
1182 	}
1183 
1184 	if (fprintf(f, "\t\t\treturn (pcbe_core_events_%s);\n",
1185 	    map->cmap_name) == -1) {
1186 		return (B_FALSE);
1187 	}
1188 
1189 	return (B_TRUE);
1190 }
1191 
1192 /*
1193  * This is a wrapper around unlinkat that makes sure that we don't clobber
1194  * errno, which is used for properly printing out error messages below.
1195  */
1196 static void
cpcgen_remove_tmpfile(int dirfd,const char * path)1197 cpcgen_remove_tmpfile(int dirfd, const char *path)
1198 {
1199 	int e = errno;
1200 	(void) unlinkat(dirfd, path, 0);
1201 	errno = e;
1202 }
1203 
1204 /*
1205  * Generate a header file that declares all of these arrays and provide a map
1206  * for models to the corresponding table to use.
1207  */
1208 static void
cpcgen_common_intel_files(int dirfd)1209 cpcgen_common_intel_files(int dirfd)
1210 {
1211 	const char *fname = "core_pcbe_cpcgen.h";
1212 	char *tmpname;
1213 	int fd;
1214 	FILE *f;
1215 	cpc_map_t *map;
1216 
1217 	if (asprintf(&tmpname, ".%s.%d", fname, getpid()) == -1) {
1218 		err(EXIT_FAILURE, "failed to construct temporary file name");
1219 	}
1220 
1221 	if ((fd = openat(dirfd, tmpname, O_RDWR | O_CREAT, 0644)) < 0) {
1222 		err(EXIT_FAILURE, "failed to create temporary file %s",
1223 		    tmpname);
1224 	}
1225 
1226 	if ((f = fdopen(fd, "w")) == NULL) {
1227 		cpcgen_remove_tmpfile(dirfd, tmpname);
1228 		err(EXIT_FAILURE, "failed to fdopen temporary file");
1229 	}
1230 
1231 	if (fprintf(f, cpcgen_cfile_intel_header, cpcgen_intel_mapfile) == -1) {
1232 		cpcgen_remove_tmpfile(dirfd, tmpname);
1233 		errx(EXIT_FAILURE, "failed to write header to temporary file "
1234 		    "for %s", fname);
1235 	}
1236 
1237 	if (fprintf(f, "#ifndef _CORE_PCBE_CPCGEN_H\n"
1238 	    "#define\t_CORE_PCBE_CPCGEN_H\n"
1239 	    "\n"
1240 	    "#ifdef __cplusplus\n"
1241 	    "extern \"C\" {\n"
1242 	    "#endif\n"
1243 	    "\n"
1244 	    "extern const struct events_table_t *core_cpcgen_table(uint_t, "
1245 	    "uint_t);\n"
1246 	    "\n") == -1) {
1247 		cpcgen_remove_tmpfile(dirfd, tmpname);
1248 		errx(EXIT_FAILURE, "failed to write header to "
1249 		    "temporary file for %s", fname);
1250 	}
1251 
1252 	for (map = cpcgen_maps; map != NULL; map = map->cmap_next) {
1253 		if (fprintf(f, "extern const struct events_table_t "
1254 		    "pcbe_core_events_%s[];\n", map->cmap_name) == -1) {
1255 			cpcgen_remove_tmpfile(dirfd, tmpname);
1256 			errx(EXIT_FAILURE, "failed to write entry to "
1257 			    "temporary file for %s", fname);
1258 		}
1259 	}
1260 
1261 	if (fprintf(f, "\n"
1262 	    "#ifdef __cplusplus\n"
1263 	    "}\n"
1264 	    "#endif\n"
1265 	    "\n"
1266 	    "#endif /* _CORE_PCBE_CPCGEN_H */\n") == -1) {
1267 		cpcgen_remove_tmpfile(dirfd, tmpname);
1268 		errx(EXIT_FAILURE, "failed to write header to "
1269 		    "temporary file for %s", fname);
1270 	}
1271 
1272 	if (fflush(f) != 0 || fclose(f) != 0) {
1273 		cpcgen_remove_tmpfile(dirfd, tmpname);
1274 		err(EXIT_FAILURE, "failed to flush and close temporary file");
1275 	}
1276 
1277 	if (renameat(dirfd, tmpname, dirfd, fname) != 0) {
1278 		err(EXIT_FAILURE, "failed to rename temporary file %s",
1279 		    tmpname);
1280 	}
1281 
1282 	free(tmpname);
1283 
1284 	/* Now again for the .c file. */
1285 	fname = "core_pcbe_cpcgen.c";
1286 	if (asprintf(&tmpname, ".%s.%d", fname, getpid()) == -1) {
1287 		err(EXIT_FAILURE, "failed to construct temporary file name");
1288 	}
1289 
1290 	if ((fd = openat(dirfd, tmpname, O_RDWR | O_CREAT, 0644)) < 0) {
1291 		err(EXIT_FAILURE, "failed to create temporary file %s",
1292 		    tmpname);
1293 	}
1294 
1295 	if ((f = fdopen(fd, "w")) == NULL) {
1296 		cpcgen_remove_tmpfile(dirfd, tmpname);
1297 		err(EXIT_FAILURE, "failed to fdopen temporary file");
1298 	}
1299 
1300 	if (fprintf(f, cpcgen_cfile_intel_header, cpcgen_intel_mapfile) == -1) {
1301 		cpcgen_remove_tmpfile(dirfd, tmpname);
1302 		errx(EXIT_FAILURE, "failed to write header to temporary file "
1303 		    "for %s", fname);
1304 	}
1305 
1306 	if (fprintf(f, "#include <core_pcbe_table.h>\n"
1307 	    "#include <sys/null.h>\n"
1308 	    "#include \"core_pcbe_cpcgen.h\"\n"
1309 	    "\n"
1310 	    "const struct events_table_t *\n"
1311 	    "core_cpcgen_table(uint_t model, uint_t stepping)\n"
1312 	    "{\n") == -1) {
1313 		cpcgen_remove_tmpfile(dirfd, tmpname);
1314 		errx(EXIT_FAILURE, "failed to write header to "
1315 		    "temporary file for %s", fname);
1316 	}
1317 
1318 	for (map = cpcgen_maps; map != NULL; map = map->cmap_next) {
1319 		if (!cpcgen_generate_map(f, map, map == cpcgen_maps)) {
1320 			cpcgen_remove_tmpfile(dirfd, tmpname);
1321 			errx(EXIT_FAILURE, "failed to write to temporary "
1322 			    "file for %s", fname);
1323 		}
1324 	}
1325 
1326 	if (fprintf(f, "\t} else {\n"
1327 	    "\t\t\treturn (NULL);\n"
1328 	    "\t}\n"
1329 	    "}\n") == -1) {
1330 		cpcgen_remove_tmpfile(dirfd, tmpname);
1331 		errx(EXIT_FAILURE, "failed to write header to "
1332 		    "temporary file for %s", fname);
1333 	}
1334 
1335 	if (fflush(f) != 0 || fclose(f) != 0) {
1336 		cpcgen_remove_tmpfile(dirfd, tmpname);
1337 		err(EXIT_FAILURE, "failed to flush and close temporary file");
1338 	}
1339 
1340 	if (renameat(dirfd, tmpname, dirfd, fname) != 0) {
1341 		err(EXIT_FAILURE, "failed to rename temporary file %s",
1342 		    tmpname);
1343 	}
1344 
1345 	free(tmpname);
1346 }
1347 
1348 /*
1349  * Look at a rule to determine whether or not we should consider including it or
1350  * not. At this point we've already filtered things such that we only get core
1351  * events.
1352  *
1353  * To consider an entry, we currently apply the following criteria:
1354  *
1355  * - The MSRIndex and MSRValue are zero. Programming additional MSRs is no
1356  *   supported right now.
1357  * - TakenAlone is non-zero, which means that it cannot run at the same time as
1358  *   another field.
1359  * - Offcore is one, indicating that it is off the core and we need to figure
1360  *   out if we can support this.
1361  * - If the counter is fixed, don't use it for now. "32"-"35" is another name
1362  *    for the fixed counters.
1363  * - If more than one value is specified in the EventCode or UMask values
1364  */
1365 static boolean_t
cpcgen_skip_intel_entry(nvlist_t * nvl,const char * path,uint_t ent)1366 cpcgen_skip_intel_entry(nvlist_t *nvl, const char *path, uint_t ent)
1367 {
1368 	char *event, *msridx, *msrval, *taken, *offcore, *counter;
1369 	char *ecode, *umask;
1370 
1371 	/*
1372 	 * Require EventName, it's kind of useless without that.
1373 	 */
1374 	if (nvlist_lookup_string(nvl, "EventName", &event) != 0) {
1375 		errx(EXIT_FAILURE, "Found event without 'EventName' property "
1376 		    "in %s, entry %u", path, ent);
1377 	}
1378 
1379 	/*
1380 	 * If we can't find an expected value, whine about it.
1381 	 */
1382 	if (nvlist_lookup_string(nvl, "MSRIndex", &msridx) != 0 ||
1383 	    nvlist_lookup_string(nvl, "MSRValue", &msrval) != 0 ||
1384 	    nvlist_lookup_string(nvl, "Counter", &counter) != 0 ||
1385 	    nvlist_lookup_string(nvl, "EventCode", &ecode) != 0 ||
1386 	    nvlist_lookup_string(nvl, "UMask", &umask) != 0 ||
1387 	    nvlist_lookup_string(nvl, "Offcore", &offcore) != 0) {
1388 		warnx("Skipping event %s (index %u) from %s, missing required "
1389 		    "property", event, ent, path);
1390 		return (B_TRUE);
1391 	}
1392 
1393 	/*
1394 	 * MSRIndex and MSRvalue comes as either "0" or "0x00".
1395 	 */
1396 	if ((strcmp(msridx, "0") != 0 && strcmp(msridx, "0x00") != 0) ||
1397 	    (strcmp(msrval, "0") != 0 && strcmp(msridx, "0x00") != 0) ||
1398 	    strcmp(offcore, "0") != 0 || strchr(ecode, ',') != NULL ||
1399 	    strchr(umask, ',') != NULL) {
1400 		return (B_TRUE);
1401 	}
1402 
1403 	/*
1404 	 * Unfortunately, not everything actually has "TakenAlone". If it
1405 	 * doesn't, we assume that it doesn't have to be.
1406 	 */
1407 	if (nvlist_lookup_string(nvl, "TakenAlone", &taken) == 0 &&
1408 	    strcmp(taken, "0") != 0) {
1409 		return (B_TRUE);
1410 	}
1411 
1412 
1413 	if (strncasecmp(counter, "fixed", strlen("fixed")) == 0)
1414 		return (B_TRUE);
1415 	if (strcmp(counter, "32") == 0 || strcmp(counter, "33") == 0 ||
1416 	    strcmp(counter, "34") == 0 || strcmp(counter, "35") == 0)
1417 		return (B_TRUE);
1418 
1419 	return (B_FALSE);
1420 }
1421 static char *
cpcgen_manual_amd_name(cpc_map_t * map)1422 cpcgen_manual_amd_name(cpc_map_t *map)
1423 {
1424 	char *name;
1425 
1426 	if (asprintf(&name, "amd_%s_events.3cpc", map->cmap_name) == -1) {
1427 		warn("failed to assemble file name for %s", map->cmap_path);
1428 		return (NULL);
1429 	}
1430 
1431 	return (name);
1432 }
1433 
1434 static boolean_t
cpcgen_manual_amd_file_before(FILE * f,cpc_map_t * map)1435 cpcgen_manual_amd_file_before(FILE *f, cpc_map_t *map)
1436 {
1437 	size_t i;
1438 	char *upper, *desc, *c;
1439 
1440 	if ((upper = strdup(map->cmap_name)) == NULL) {
1441 		warn("failed to duplicate manual name for %s", map->cmap_name);
1442 		return (B_FALSE);
1443 	}
1444 
1445 	if ((desc = strdup(map->cmap_name)) == NULL) {
1446 		warn("failed to duplicate manual name for %s", map->cmap_name);
1447 		free(upper);
1448 		return (B_FALSE);
1449 	}
1450 
1451 	for (i = 0; upper[i] != '\0'; i++) {
1452 		upper[i] = toupper(upper[i]);
1453 	}
1454 
1455 	desc++;
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, path, ent);
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