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