summaryrefslogtreecommitdiff
path: root/usr/src/tools
diff options
context:
space:
mode:
authorRobert Mustacchi <rm@joyent.com>2018-06-15 03:44:35 +0000
committerRobert Mustacchi <rm@joyent.com>2018-08-17 22:06:17 +0000
commit2f4638792d45e909736547e2aeac02422e486ccd (patch)
tree5de835825ffb822f58ed97b9eb3e12987a2adcb8 /usr/src/tools
parentd4571eb653823d0e8936b35502fc38b7af9f6f04 (diff)
downloadillumos-joyent-2f4638792d45e909736547e2aeac02422e486ccd.tar.gz
OS-7033 Autogenerate Intel pcbe values from perfmon data
Reviewed by: Jason King <jbk@joyent.com> Reviewed by: Jerry Jelinek <jerry.jelinek@joyent.com> Approved by: Patrick Mooney <patrick.mooney@joyent.com>
Diffstat (limited to 'usr/src/tools')
-rw-r--r--usr/src/tools/Makefile1
-rw-r--r--usr/src/tools/cpcgen/Makefile44
-rw-r--r--usr/src/tools/cpcgen/cpcgen.c1287
3 files changed, 1332 insertions, 0 deletions
diff --git a/usr/src/tools/Makefile b/usr/src/tools/Makefile
index b3ce7df21b..d9796e6c05 100644
--- a/usr/src/tools/Makefile
+++ b/usr/src/tools/Makefile
@@ -73,6 +73,7 @@ sparc_SUBDIRS= \
i386_SUBDIRS= \
aw \
+ cpcgen \
elfextract \
mbh_patch \
btxld
diff --git a/usr/src/tools/cpcgen/Makefile b/usr/src/tools/cpcgen/Makefile
new file mode 100644
index 0000000000..dc50c0b1e0
--- /dev/null
+++ b/usr/src/tools/cpcgen/Makefile
@@ -0,0 +1,44 @@
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source. A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright (c) 2018, Joyent, Inc.
+#
+
+PROG = cpcgen
+OBJS = cpcgen.o json_nvlist.o custr.o
+
+include ../Makefile.tools
+
+LDLIBS += -lnvpair
+CPPFLAGS += -I$(SRC)/lib/json_nvlist/
+
+all: $(PROG)
+
+install: all .WAIT $(ROOTONBLDMACHPROG)
+
+$(PROG): $(OBJS)
+ $(LINK.c) -o $@ $(OBJS) $(LDLIBS)
+ $(POST_PROCESS)
+
+%.o: %.c
+ $(COMPILE.c) $<
+
+%.o: $(SRC)/lib/json_nvlist/%.c
+ $(COMPILE.c) $<
+
+%.o: $(SRC)/lib/libcmdutils/common/%.c
+ $(COMPILE.c) $<
+
+clean:
+ $(RM) $(OBJS)
+
+include ../Makefile.targ
diff --git a/usr/src/tools/cpcgen/cpcgen.c b/usr/src/tools/cpcgen/cpcgen.c
new file mode 100644
index 0000000000..daa9c255f9
--- /dev/null
+++ b/usr/src/tools/cpcgen/cpcgen.c
@@ -0,0 +1,1287 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright (c) 2018, Joyent, Inc.
+ */
+
+/*
+ * This file transforms the perfmon data files into C files and manual pages.
+ */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <err.h>
+#include <libgen.h>
+#include <libnvpair.h>
+#include <strings.h>
+#include <errno.h>
+#include <limits.h>
+#include <sys/mman.h>
+#include <sys/param.h>
+#include <assert.h>
+#include <ctype.h>
+
+#include <json_nvlist.h>
+
+#define EXIT_USAGE 2
+
+
+typedef struct cpc_proc {
+ struct cpc_proc *cproc_next;
+ uint_t cproc_family;
+ uint_t cproc_model;
+} cpc_proc_t;
+
+typedef enum cpc_file_type {
+ CPC_FILE_CORE = 1 << 0,
+ CPC_FILE_OFF_CORE = 1 << 1,
+ CPC_FILE_UNCORE = 1 << 2,
+ CPC_FILE_FP_MATH = 1 << 3,
+ CPC_FILE_UNCORE_EXP = 1 << 4
+} cpc_type_t;
+
+typedef struct cpc_map {
+ struct cpc_map *cmap_next;
+ cpc_type_t cmap_type;
+ nvlist_t *cmap_data;
+ char *cmap_path;
+ const char *cmap_name;
+ cpc_proc_t *cmap_procs;
+} cpc_map_t;
+
+typedef struct cpc_whitelist {
+ const char *cwhite_short;
+ const char *cwhite_human;
+ uint_t cwhite_mask;
+} cpc_whitelist_t;
+
+/*
+ * List of architectures that we support generating this data for. This is done
+ * so that processors that illumos doesn't support or run on aren't generated
+ * (generally the Xeon Phi).
+ */
+static cpc_whitelist_t cpcgen_whitelist[] = {
+ /* Nehalem */
+ { "NHM-EP", "nhm_ep", CPC_FILE_CORE },
+ { "NHM-EX", "nhm_ex", CPC_FILE_CORE },
+ /* Westmere */
+ { "WSM-EP-DP", "wsm_ep_dp", CPC_FILE_CORE },
+ { "WSM-EP-SP", "wsm_ep_sp", CPC_FILE_CORE },
+ { "WSM-EX", "wsm_ex", CPC_FILE_CORE },
+ /* Sandy Bridge */
+ { "SNB", "snb", CPC_FILE_CORE },
+ { "JKT", "jkt", CPC_FILE_CORE },
+ /* Ivy Bridge */
+ { "IVB", "ivb", CPC_FILE_CORE },
+ { "IVT", "ivt", CPC_FILE_CORE },
+ /* Haswell */
+ { "HSW", "hsw", CPC_FILE_CORE },
+ { "HSX", "hsx", CPC_FILE_CORE },
+ /* Broadwell */
+ { "BDW", "bdw", CPC_FILE_CORE },
+ { "BDW-DE", "bdw_de", CPC_FILE_CORE },
+ { "BDX", "bdx", CPC_FILE_CORE },
+ /* Skylake */
+ { "SKL", "skl", CPC_FILE_CORE },
+ { "SKX", "skx", CPC_FILE_CORE },
+ /* Atom */
+ { "BNL", "bnl", CPC_FILE_CORE },
+ { "SLM", "slm", CPC_FILE_CORE },
+ { "GLM", "glm", CPC_FILE_CORE },
+ { "GLP", "glp", CPC_FILE_CORE },
+ { NULL }
+};
+
+typedef struct cpc_papi {
+ const char *cpapi_intc;
+ const char *cpapi_papi;
+} cpc_papi_t;
+
+/*
+ * This table maps events with an Intel specific name to the corresponding PAPI
+ * name. There may be multiple INtel events which map to the same PAPI event.
+ * This is usually because different processors have different names for an
+ * event. We use the title as opposed to the event codes because those can
+ * change somewhat arbitrarily between processor generations.
+ */
+static cpc_papi_t cpcgen_papi_map[] = {
+ { "CPU_CLK_UNHALTED.THREAD_P", "PAPI_tot_cyc" },
+ { "INST_RETIRED.ANY_P", "PAPI_tot_ins" },
+ { "BR_INST_RETIRED.ALL_BRANCHES", "PAPI_br_ins" },
+ { "BR_MISP_RETIRED.ALL_BRANCHES", "PAPI_br_msp" },
+ { "BR_INST_RETIRED.CONDITIONAL", "PAPI_br_cn" },
+ { "CYCLE_ACTIVITY.CYCLES_L1D_MISS", "PAPI_l1_dcm" },
+ { "L1I.HITS", "PAPI_l1_ich" },
+ { "ICACHE.HIT", "PAPI_l1_ich" },
+ { "L1I.MISS", "PAPI_L1_icm" },
+ { "ICACHE.MISSES", "PAPI_l1_icm" },
+ { "L1I.READS", "PAPI_l1_ica" },
+ { "ICACHE.ACCESSES", "PAPI_l1_ica" },
+ { "L1I.READS", "PAPI_l1_icr" },
+ { "ICACHE.ACCESSES", "PAPI_l1_icr" },
+ { "L2_RQSTS.CODE_RD_MISS", "PAPI_l2_icm" },
+ { "L2_RQSTS.MISS", "PAPI_l2_tcm" },
+ { "ITLB_MISSES.MISS_CAUSES_A_WALK", "PAPI_tlb_im" },
+ { "DTLB_LOAD_MISSES.MISS_CAUSES_A_WALK", "PAPI_tlb_dm" },
+ { "PAGE_WALKS.D_SIDE_WALKS", "PAPI_tlb_dm" },
+ { "PAGE_WALKS.I_SIDE_WALKS", "PAPI_tlb_im" },
+ { "PAGE_WALKS.WALKS", "PAPI_tlb_tl" },
+ { "INST_QUEUE_WRITES", "PAPI_tot_iis" },
+ { "MEM_INST_RETIRED.STORES" "PAPI_sr_ins" },
+ { "MEM_INST_RETIRED.LOADS" "PAPI_ld_ins" },
+ { NULL, NULL }
+};
+
+typedef struct cpcgen_ops {
+ char *(*cgen_op_name)(cpc_map_t *);
+ boolean_t (*cgen_op_file_before)(FILE *, cpc_map_t *);
+ boolean_t (*cgen_op_file_after)(FILE *, cpc_map_t *);
+ boolean_t (*cgen_op_event)(FILE *, nvlist_t *, const char *, uint32_t);
+} cpcgen_ops_t;
+
+static cpcgen_ops_t cpcgen_ops;
+static const char *cpcgen_mapfile = "/mapfile.csv";
+static const char *cpcgen_progname;
+static cpc_map_t *cpcgen_maps;
+
+/*
+ * Constants used for generating data.
+ */
+/* BEGIN CSTYLED */
+static const char *cpcgen_cfile_header = ""
+"/*\n"
+" * Copyright (c) 2018, Intel Corporation\n"
+" * Copyright (c) 2018, Joyent, Inc\n"
+" * All rights reserved.\n"
+" *\n"
+" * Redistribution and use in source and binary forms, with or without\n"
+" * modification, are permitted provided that the following conditions are met:\n"
+" * \n"
+" * 1. Redistributions of source code must retain the above copyright notice,\n"
+" * this list of conditions and the following disclaimer.\n"
+" * \n"
+" * 2. Redistributions in binary form must reproduce the above copyright \n"
+" * notice, this list of conditions and the following disclaimer in the\n"
+" * documentation and/or other materials provided with the distribution.\n"
+" * \n"
+" * 3. Neither the name of the Intel Corporation nor the names of its \n"
+" * contributors may be used to endorse or promote products derived from\n"
+" * this software without specific prior written permission.\n"
+" *\n"
+" * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n"
+" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n"
+" * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n"
+" * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE\n"
+" * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n"
+" * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n"
+" * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n"
+" * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n"
+" * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n"
+" * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n"
+" * POSSIBILITY OF SUCH DAMAGE.\n"
+" *\n"
+" * This file was automatically generated by cpcgen from the data file\n"
+" * data/perfmon%s\n"
+" *\n"
+" * Do not modify this file. Your changes will be lost!\n"
+" */\n"
+"\n";
+/* END CSTYLED */
+
+static const char *cpcgen_cfile_table_start = ""
+"#include <core_pcbe_table.h>\n"
+"\n"
+"const struct events_table_t pcbe_core_events_%s[] = {\n";
+
+static const char *cpcgen_cfile_table_end = ""
+"\t{ NT_END, 0, 0, \"\" }\n"
+"};\n";
+
+/* BEGIN CSTYLED */
+static const char *cpcgen_manual_header = ""
+".\\\" Copyright (c) 2018, Intel Corporation \n"
+".\\\" Copyright (c) 2018, Joyent, Inc.\n"
+".\\\" All rights reserved.\n"
+".\\\"\n"
+".\\\" Redistribution and use in source and binary forms, with or without \n"
+".\\\" modification, are permitted provided that the following conditions are met:\n"
+".\\\"\n"
+".\\\" 1. Redistributions of source code must retain the above copyright notice,\n"
+".\\\" this list of conditions and the following disclaimer.\n"
+".\\\"\n"
+".\\\" 2. Redistributions in binary form must reproduce the above copyright\n"
+".\\\" notice, this list of conditions and the following disclaimer in the\n"
+".\\\" documentation and/or other materials provided with the distribution.\n"
+".\\\"\n"
+".\\\" 3. Neither the name of the Intel Corporation nor the names of its\n"
+".\\\" contributors may be used to endorse or promote products derived from\n"
+".\\\" this software without specific prior written permission.\n"
+".\\\"\n"
+".\\\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n"
+".\\\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n"
+".\\\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n"
+".\\\" ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE\n"
+".\\\" LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n"
+".\\\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n"
+".\\\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n"
+".\\\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n"
+".\\\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n"
+".\\\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n"
+".\\\" POSSIBILITY OF SUCH DAMAGE.\n"
+".\\\"\n"
+".\\\" This file was automatically generated by cpcgen from the data file\n"
+".\\\" data/perfmon%s\n"
+".\\\"\n"
+".\\\" Do not modify this file. Your changes will be lost!\n"
+".\\\"\n"
+".\\\" We would like to thank Intel for providing the perfmon data for use in\n"
+".\\\" our manual pages.\n"
+".Dd June 18, 2018\n"
+".Dt %s_EVENTS 3CPC\n"
+".Os\n"
+".Sh NAME\n"
+".Nm %s_events\n"
+".Nd processor model specific performance counter events\n"
+".Sh DESCRIPTION\n"
+"This manual page describes events specific to the following Intel CPU\n"
+"models and is derived from Intel's perfmon data.\n"
+"For more information, please consult the Intel Software Developer's Manual "
+"or Intel's perfmon website.\n"
+".Pp\n"
+"CPU models described by this document:\n"
+".Bl -bullet\n";
+/* END CSTYLED */
+
+static const char *cpcgen_manual_data = ""
+".El\n"
+".Pp\n"
+"The following events are supported:\n"
+".Bl -tag -width Sy\n";
+
+static const char *cpcgen_manual_trailer = ""
+".El\n"
+".Sh SEE ALSO\n"
+".Xr cpc 3CPC\n"
+".Pp\n"
+".Lk https://download.01.org/perfmon/index/";
+
+static cpc_map_t *
+cpcgen_map_lookup(const char *path)
+{
+ cpc_map_t *m;
+
+ for (m = cpcgen_maps; m != NULL; m = m->cmap_next) {
+ if (strcmp(path, m->cmap_path) == 0) {
+ return (m);
+ }
+ }
+
+ return (NULL);
+}
+
+/*
+ * Parse a string of the form 'GenuineIntel-6-2E' and get out the family and
+ * model.
+ */
+static void
+cpcgen_parse_model(char *fsr, uint_t *family, uint_t *model)
+{
+ const char *bstr = "GenuineIntel";
+ const char *brand, *fam, *mod;
+ char *last;
+ long l;
+
+ if ((brand = strtok_r(fsr, "-", &last)) == NULL ||
+ (fam = strtok_r(NULL, "-", &last)) == NULL ||
+ (mod = strtok_r(NULL, "-", &last)) == NULL) {
+ errx(EXIT_FAILURE, "failed to parse processor id \"%s\"", fsr);
+ }
+
+ if (strcmp(bstr, brand) != 0) {
+ errx(EXIT_FAILURE, "brand string \"%s\" did not match \"%s\"",
+ brand, bstr);
+ }
+
+ errno = 0;
+ l = strtol(fam, &last, 16);
+ if (errno != 0 || l < 0 || l > UINT_MAX || *last != '\0') {
+ errx(EXIT_FAILURE, "failed to parse family \"%s\"", fam);
+ }
+ *family = (uint_t)l;
+
+ l = strtol(mod, &last, 16);
+ if (errno != 0 || l < 0 || l > UINT_MAX || *last != '\0') {
+ errx(EXIT_FAILURE, "failed to parse model \"%s\"", mod);
+ }
+ *model = (uint_t)l;
+}
+
+static nvlist_t *
+cpcgen_read_datafile(const char *datadir, const char *file)
+{
+ int fd;
+ char *path;
+ struct stat st;
+ void *map;
+ nvlist_t *nvl;
+ nvlist_parse_json_error_t jerr;
+
+ if (asprintf(&path, "%s/%s", datadir, file) == -1) {
+ err(EXIT_FAILURE, "failed to construct path to data file %s",
+ file);
+ }
+
+ if ((fd = open(path, O_RDONLY)) < 0) {
+ err(EXIT_FAILURE, "failed to open data file %s", path);
+ }
+
+ if (fstat(fd, &st) != 0) {
+ err(EXIT_FAILURE, "failed to stat %s", path);
+ }
+
+ if ((map = mmap(NULL, st.st_size, PROT_READ | PROT_WRITE, MAP_PRIVATE,
+ fd, 0)) == MAP_FAILED) {
+ err(EXIT_FAILURE, "failed to mmap %s", path);
+ }
+
+ if (nvlist_parse_json(map, st.st_size, &nvl, NVJSON_FORCE_INTEGER,
+ &jerr) != 0) {
+ errx(EXIT_FAILURE, "failed to parse file %s at pos %ld: %s",
+ path, jerr.nje_pos, jerr.nje_message);
+ }
+
+ if (munmap(map, st.st_size) != 0) {
+ err(EXIT_FAILURE, "failed to munmap %s", path);
+ }
+
+ if (close(fd) != 0) {
+ err(EXIT_FAILURE, "failed to close data file %s", path);
+ }
+ free(path);
+
+ return (nvl);
+}
+
+/*
+ * Check the whitelist to see if we should use this model.
+ */
+static const char *
+cpcgen_use_arch(const char *path, cpc_type_t type, const char *platform)
+{
+ const char *slash;
+ size_t len;
+ uint_t i;
+
+ if (*path != '/') {
+ errx(EXIT_FAILURE, "invalid path in mapfile: \"%s\": missing "
+ "leading '/'", path);
+ }
+ if ((slash = strchr(path + 1, '/')) == NULL) {
+ errx(EXIT_FAILURE, "invalid path in mapfile: \"%s\": missing "
+ "second '/'", path);
+ }
+ /* Account for the last '/' character. */
+ len = slash - path - 1;
+ assert(len > 0);
+
+ for (i = 0; cpcgen_whitelist[i].cwhite_short != NULL; i++) {
+ if (platform != NULL && strcasecmp(platform,
+ cpcgen_whitelist[i].cwhite_short) != 0)
+ continue;
+ if (strncmp(path + 1, cpcgen_whitelist[i].cwhite_short,
+ len) == 0 &&
+ (cpcgen_whitelist[i].cwhite_mask & type) == type) {
+ return (cpcgen_whitelist[i].cwhite_human);
+ }
+ }
+
+ return (NULL);
+}
+
+/*
+ * Read in the mapfile.csv that is used to map between processor families and
+ * parse this. Each line has a comma separated value.
+ */
+static void
+cpcgen_read_mapfile(const char *datadir, const char *platform)
+{
+ FILE *map;
+ char *mappath, *last;
+ char *data = NULL;
+ size_t datalen = 0;
+ uint_t lineno;
+
+ if (asprintf(&mappath, "%s/%s", datadir, cpcgen_mapfile) == -1) {
+ err(EXIT_FAILURE, "failed to construct path to mapfile");
+ }
+
+ if ((map = fopen(mappath, "r")) == NULL) {
+ err(EXIT_FAILURE, "failed to open data mapfile %s", mappath);
+ }
+
+ lineno = 0;
+ while (getline(&data, &datalen, map) != -1) {
+ char *fstr, *path, *tstr;
+ const char *name;
+ uint_t family, model;
+ cpc_type_t type;
+ cpc_map_t *map;
+ cpc_proc_t *proc;
+
+ /*
+ * The first line contains the header:
+ * Family-model,Version,Filename,EventType
+ */
+ lineno++;
+ if (lineno == 1) {
+ continue;
+ }
+
+ if ((fstr = strtok_r(data, ",", &last)) == NULL ||
+ strtok_r(NULL, ",", &last) == NULL ||
+ (path = strtok_r(NULL, ",", &last)) == NULL ||
+ (tstr = strtok_r(NULL, "\n", &last)) == NULL) {
+ errx(EXIT_FAILURE, "failed to parse mapfile line "
+ "%u in %s", lineno, mappath);
+ }
+
+ cpcgen_parse_model(fstr, &family, &model);
+
+ if (strcmp(tstr, "core") == 0) {
+ type = CPC_FILE_CORE;
+ } else if (strcmp(tstr, "offcore") == 0) {
+ type = CPC_FILE_OFF_CORE;
+ } else if (strcmp(tstr, "uncore") == 0) {
+ type = CPC_FILE_UNCORE;
+ } else if (strcmp(tstr, "fp_arith_inst") == 0) {
+ type = CPC_FILE_FP_MATH;
+ } else if (strcmp(tstr, "uncore experimental") == 0) {
+ type = CPC_FILE_UNCORE_EXP;
+ } else {
+ errx(EXIT_FAILURE, "unknown file type \"%s\" on line "
+ "%u", tstr, lineno);
+ }
+
+ if ((name = cpcgen_use_arch(path, type, platform)) == NULL)
+ continue;
+
+ if ((map = cpcgen_map_lookup(path)) == NULL) {
+ nvlist_t *parsed;
+
+ parsed = cpcgen_read_datafile(datadir, path);
+
+ if ((map = calloc(1, sizeof (cpc_map_t))) == NULL) {
+ err(EXIT_FAILURE, "failed to allocate space "
+ "for cpc file");
+ }
+
+ if ((map->cmap_path = strdup(path)) == NULL) {
+ err(EXIT_FAILURE, "failed to duplicate path "
+ "string");
+ }
+
+ map->cmap_type = type;
+ map->cmap_data = parsed;
+ map->cmap_next = cpcgen_maps;
+ map->cmap_name = name;
+ cpcgen_maps = map;
+ }
+
+ if ((proc = malloc(sizeof (cpc_proc_t))) == NULL) {
+ err(EXIT_FAILURE, "failed to allocate memory for "
+ "family and model tracking");
+ }
+
+ proc->cproc_family = family;
+ proc->cproc_model = model;
+ proc->cproc_next = map->cmap_procs;
+ map->cmap_procs = proc;
+ }
+
+ if (errno != 0 || ferror(map)) {
+ err(EXIT_FAILURE, "failed to read %s", mappath);
+ }
+
+ if (fclose(map) == EOF) {
+ err(EXIT_FAILURE, "failed to close %s", mappath);
+ }
+ free(data);
+ free(mappath);
+}
+
+static char *
+cpcgen_manual_name(cpc_map_t *map)
+{
+ char *name;
+
+ if (asprintf(&name, "%s_events.3cpc", map->cmap_name) == -1) {
+ warn("failed to assemble manual page name for %s",
+ map->cmap_path);
+ return (NULL);
+ }
+
+ return (name);
+}
+
+static boolean_t
+cpcgen_manual_file_before(FILE *f, cpc_map_t *map)
+{
+ size_t i;
+ char *upper;
+ cpc_proc_t *proc;
+
+ if ((upper = strdup(map->cmap_name)) == NULL) {
+ warn("failed to duplicate manual name for %s", map->cmap_name);
+ return (B_FALSE);
+ }
+
+ for (i = 0; upper[i] != '\0'; i++) {
+ upper[i] = toupper(upper[i]);
+ }
+
+ if (fprintf(f, cpcgen_manual_header, map->cmap_path, upper,
+ map->cmap_name) == -1) {
+ warn("failed to write out manual header for %s",
+ map->cmap_name);
+ free(upper);
+ return (B_FALSE);
+ }
+
+ for (proc = map->cmap_procs; proc != NULL; proc = proc->cproc_next) {
+ if (fprintf(f, ".It\n.Sy Family 0x%x, Model 0x%x\n",
+ proc->cproc_family, proc->cproc_model) == -1) {
+ warn("failed to write out model information for %s",
+ map->cmap_name);
+ free(upper);
+ return (B_FALSE);
+ }
+ }
+
+ if (fprintf(f, cpcgen_manual_data, map->cmap_path, upper,
+ map->cmap_name) == -1) {
+ warn("failed to write out manual header for %s",
+ map->cmap_name);
+ free(upper);
+ return (B_FALSE);
+ }
+
+ free(upper);
+ return (B_TRUE);
+}
+
+static boolean_t
+cpcgen_manual_file_after(FILE *f, cpc_map_t *map)
+{
+ if (fprintf(f, cpcgen_manual_trailer) == -1) {
+ warn("failed to write out manual header for %s",
+ map->cmap_name);
+ return (B_FALSE);
+ }
+
+ return (B_TRUE);
+}
+
+static boolean_t
+cpcgen_manual_event(FILE *f, nvlist_t *nvl, const char *path, uint32_t ent)
+{
+ char *event, *lname, *brief = NULL, *public = NULL, *errata = NULL;
+ size_t i;
+
+ if (nvlist_lookup_string(nvl, "EventName", &event) != 0) {
+ warnx("Found event without 'EventName' property "
+ "in %s, entry %u", path, ent);
+ return (B_FALSE);
+ }
+
+ /*
+ * Intel uses capital names. CPC historically uses lower case names.
+ */
+ if ((lname = strdup(event)) == NULL) {
+ err(EXIT_FAILURE, "failed to duplicate event name %s", event);
+ }
+ for (i = 0; lname[i] != '\0'; i++) {
+ lname[i] = tolower(event[i]);
+ }
+
+ /*
+ * Try to get the other event fields, but if they're not there, don't
+ * worry about it.
+ */
+ (void) nvlist_lookup_string(nvl, "BriefDescription", &brief);
+ (void) nvlist_lookup_string(nvl, "PublicDescription", &public);
+ (void) nvlist_lookup_string(nvl, "Errata", &errata);
+ if (errata != NULL && (strcmp(errata, "0") == 0 ||
+ strcmp(errata, "null") == 0)) {
+ errata = NULL;
+ }
+
+ if (fprintf(f, ".It Sy %s\n", lname) == -1) {
+ warn("failed to write out probe entry %s", event);
+ free(lname);
+ return (B_FALSE);
+ }
+
+ if (public != NULL) {
+ if (fprintf(f, "%s\n", public) == -1) {
+ warn("failed to write out probe entry %s", event);
+ free(lname);
+ return (B_FALSE);
+ }
+ } else if (brief != NULL) {
+ if (fprintf(f, "%s\n", brief) == -1) {
+ warn("failed to write out probe entry %s", event);
+ free(lname);
+ return (B_FALSE);
+ }
+ }
+
+ if (errata != NULL) {
+ if (fprintf(f, ".Pp\nThe following errata may apply to this: "
+ "%s\n", errata) == -1) {
+
+ warn("failed to write out probe entry %s", event);
+ free(lname);
+ return (B_FALSE);
+ }
+ }
+
+ free(lname);
+ return (B_TRUE);
+}
+
+static char *
+cpcgen_cfile_name(cpc_map_t *map)
+{
+ char *name;
+
+ if (asprintf(&name, "core_pcbe_%s.c", map->cmap_name) == -1) {
+ warn("failed to assemble file name for %s", map->cmap_path);
+ return (NULL);
+ }
+
+ return (name);
+}
+
+static boolean_t
+cpcgen_cfile_file_before(FILE *f, cpc_map_t *map)
+{
+ if (fprintf(f, cpcgen_cfile_header, map->cmap_path) == -1) {
+ warn("failed to write header to temporary file for %s",
+ map->cmap_path);
+ return (B_FALSE);
+ }
+
+ if (fprintf(f, cpcgen_cfile_table_start, map->cmap_name) == -1) {
+ warn("failed to write header to temporary file for %s",
+ map->cmap_path);
+ return (B_FALSE);
+ }
+
+ return (B_TRUE);
+}
+
+static boolean_t
+cpcgen_cfile_file_after(FILE *f, cpc_map_t *map)
+{
+ if (fprintf(f, cpcgen_cfile_table_end) == -1) {
+ warn("failed to write footer to temporary file for %s",
+ map->cmap_path);
+ return (B_FALSE);
+ }
+
+ return (B_TRUE);
+}
+
+static boolean_t
+cpcgen_cfile_event(FILE *f, nvlist_t *nvl, const char *path, uint_t ent)
+{
+ char *ecode, *umask, *name, *counter, *lname, *cmask;
+ size_t i;
+
+ if (nvlist_lookup_string(nvl, "EventName", &name) != 0) {
+ warnx("Found event without 'EventName' property "
+ "in %s, entry %u", path, ent);
+ return (B_FALSE);
+ }
+
+ if (nvlist_lookup_string(nvl, "EventCode", &ecode) != 0 ||
+ nvlist_lookup_string(nvl, "UMask", &umask) != 0 ||
+ nvlist_lookup_string(nvl, "Counter", &counter) != 0) {
+ warnx("event %s (index %u) from %s, missing "
+ "required properties for C file translation",
+ name, ent, path);
+ return (B_FALSE);
+ }
+
+ /*
+ * While we could try and parse the counters manually, just do this the
+ * max power way for now based on all possible values.
+ */
+ if (strcmp(counter, "0") == 0 || strcmp(counter, "0,") == 0) {
+ cmask = "C0";
+ } else if (strcmp(counter, "1") == 0) {
+ cmask = "C1";
+ } else if (strcmp(counter, "2") == 0) {
+ cmask = "C2";
+ } else if (strcmp(counter, "3") == 0) {
+ cmask = "C3";
+ } else if (strcmp(counter, "0,1") == 0) {
+ cmask = "C0|C1";
+ } else if (strcmp(counter, "0,1,2") == 0) {
+ cmask = "C0|C1|C2";
+ } else if (strcmp(counter, "0,1,2,3") == 0) {
+ cmask = "C0|C1|C2|C3";
+ } else if (strcmp(counter, "0,2,3") == 0) {
+ cmask = "C0|C2|C3";
+ } else if (strcmp(counter, "1,2,3") == 0) {
+ cmask = "C1|C2|C3";
+ } else if (strcmp(counter, "2,3") == 0) {
+ cmask = "C2|C3";
+ } else {
+ warnx("event %s (index %u) from %s, has unknown "
+ "counter value \"%s\"", name, ent, path, counter);
+ return (B_FALSE);
+ }
+
+
+ /*
+ * Intel uses capital names. CPC historically uses lower case names.
+ */
+ if ((lname = strdup(name)) == NULL) {
+ err(EXIT_FAILURE, "failed to duplicate event name %s", name);
+ }
+ for (i = 0; lname[i] != '\0'; i++) {
+ lname[i] = tolower(name[i]);
+ }
+
+ if (fprintf(f, "\t{ %s, %s, %s, \"%s\" },\n", ecode, umask, cmask,
+ lname) == -1) {
+ warn("failed to write out entry %s from %s", name, path);
+ free(lname);
+ return (B_FALSE);
+ }
+
+ free(lname);
+
+ /*
+ * Check if we have any PAPI aliases.
+ */
+ for (i = 0; cpcgen_papi_map[i].cpapi_intc != NULL; i++) {
+ if (strcmp(name, cpcgen_papi_map[i].cpapi_intc) != 0)
+ continue;
+
+ if (fprintf(f, "\t{ %s, %s, %s, \"%s\" },\n", ecode, umask,
+ cmask, cpcgen_papi_map[i].cpapi_papi) == -1) {
+ warn("failed to write out entry %s from %s", name,
+ path);
+ return (B_FALSE);
+ }
+ }
+
+ return (B_TRUE);
+}
+
+/*
+ * Generate a header file that declares all of these arrays and provide a map
+ * for models to the corresponding table to use.
+ */
+static void
+cpcgen_common_files(int dirfd)
+{
+ const char *fname = "core_pcbe_cpcgen.h";
+ char *tmpname;
+ int fd;
+ FILE *f;
+ cpc_map_t *map;
+
+ if (asprintf(&tmpname, ".%s.%d", fname, getpid()) == -1) {
+ err(EXIT_FAILURE, "failed to construct temporary file name");
+ }
+
+ if ((fd = openat(dirfd, tmpname, O_RDWR | O_CREAT, 0644)) < 0) {
+ err(EXIT_FAILURE, "failed to create temporary file %s",
+ tmpname);
+ }
+
+ if ((f = fdopen(fd, "w")) == NULL) {
+ int e = errno;
+ (void) unlinkat(dirfd, tmpname, 0);
+ errno = e;
+ err(EXIT_FAILURE, "failed to fdopen temporary file");
+ }
+
+ if (fprintf(f, cpcgen_cfile_header, cpcgen_mapfile) == -1) {
+ int e = errno;
+ (void) unlinkat(dirfd, tmpname, 0);
+ errno = e;
+ errx(EXIT_FAILURE, "failed to write header to temporary file "
+ "for %s", fname);
+ }
+
+ if (fprintf(f, "#ifndef _CORE_PCBE_CPCGEN_H\n"
+ "#define\t_CORE_PCBE_CPCGEN_H\n"
+ "\n"
+ "#ifdef __cplusplus\n"
+ "extern \"C\" {\n"
+ "#endif\n"
+ "\n"
+ "extern const struct events_table_t *core_cpcgen_table(uint_t);\n"
+ "\n") == -1) {
+ int e = errno;
+ (void) unlinkat(dirfd, tmpname, 0);
+ errno = e;
+ errx(EXIT_FAILURE, "failed to write header to "
+ "temporary file for %s", fname);
+ }
+
+ for (map = cpcgen_maps; map != NULL; map = map->cmap_next) {
+ if (fprintf(f, "extern const struct events_table_t "
+ "pcbe_core_events_%s[];\n", map->cmap_name) == -1) {
+ int e = errno;
+ (void) unlinkat(dirfd, tmpname, 0);
+ errno = e;
+ errx(EXIT_FAILURE, "failed to write entry to "
+ "temporary file for %s", fname);
+ }
+ }
+
+ if (fprintf(f, "\n"
+ "#ifdef __cplusplus\n"
+ "}\n"
+ "#endif\n"
+ "\n"
+ "#endif /* _CORE_PCBE_CPCGEN_H */\n") == -1) {
+ int e = errno;
+ (void) unlinkat(dirfd, tmpname, 0);
+ errno = e;
+ errx(EXIT_FAILURE, "failed to write header to "
+ "temporary file for %s", fname);
+ }
+
+ if (fflush(f) != 0 || fclose(f) != 0) {
+ int e = errno;
+ (void) unlinkat(dirfd, tmpname, 0);
+ errno = e;
+ err(EXIT_FAILURE, "failed to flush and close temporary file");
+ }
+
+ if (renameat(dirfd, tmpname, dirfd, fname) != 0) {
+ err(EXIT_FAILURE, "failed to rename temporary file %s",
+ tmpname);
+ }
+
+ free(tmpname);
+
+ /* Now again for the .c file. */
+ fname = "core_pcbe_cpcgen.c";
+ if (asprintf(&tmpname, ".%s.%d", fname, getpid()) == -1) {
+ err(EXIT_FAILURE, "failed to construct temporary file name");
+ }
+
+ if ((fd = openat(dirfd, tmpname, O_RDWR | O_CREAT, 0644)) < 0) {
+ err(EXIT_FAILURE, "failed to create temporary file %s",
+ tmpname);
+ }
+
+ if ((f = fdopen(fd, "w")) == NULL) {
+ int e = errno;
+ (void) unlinkat(dirfd, tmpname, 0);
+ errno = e;
+ err(EXIT_FAILURE, "failed to fdopen temporary file");
+ }
+
+ if (fprintf(f, cpcgen_cfile_header, cpcgen_mapfile) == -1) {
+ int e = errno;
+ (void) unlinkat(dirfd, tmpname, 0);
+ errno = e;
+ errx(EXIT_FAILURE, "failed to write header to temporary file "
+ "for %s", fname);
+ }
+
+ if (fprintf(f, "#include <core_pcbe_table.h>\n"
+ "#include <sys/null.h>\n"
+ "#include \"core_pcbe_cpcgen.h\"\n"
+ "\n"
+ "const struct events_table_t *\n"
+ "core_cpcgen_table(uint_t model)\n"
+ "{\n"
+ "\tswitch (model) {\n") == -1) {
+ int e = errno;
+ (void) unlinkat(dirfd, tmpname, 0);
+ errno = e;
+ errx(EXIT_FAILURE, "failed to write header to "
+ "temporary file for %s", fname);
+ }
+
+ for (map = cpcgen_maps; map != NULL; map = map->cmap_next) {
+ cpc_proc_t *p;
+ for (p = map->cmap_procs; p != NULL; p = p->cproc_next) {
+ assert(p->cproc_family == 6);
+ if (fprintf(f, "\t\tcase 0x%x:\n", p->cproc_model) ==
+ -1) {
+ int e = errno;
+ (void) unlinkat(dirfd, tmpname, 0);
+ errno = e;
+ errx(EXIT_FAILURE, "failed to write header to "
+ "temporary file for %s", fname);
+ }
+ }
+ if (fprintf(f, "\t\t\treturn (pcbe_core_events_%s);\n",
+ map->cmap_name) == -1) {
+ int e = errno;
+ (void) unlinkat(dirfd, tmpname, 0);
+ errno = e;
+ errx(EXIT_FAILURE, "failed to write entry to "
+ "temporary file for %s", fname);
+ }
+ }
+
+ if (fprintf(f, "\t\tdefault:\n"
+ "\t\t\treturn (NULL);\n"
+ "\t}\n"
+ "}\n") == -1) {
+ int e = errno;
+ (void) unlinkat(dirfd, tmpname, 0);
+ errno = e;
+ errx(EXIT_FAILURE, "failed to write header to "
+ "temporary file for %s", fname);
+ }
+
+ if (fflush(f) != 0 || fclose(f) != 0) {
+ int e = errno;
+ (void) unlinkat(dirfd, tmpname, 0);
+ errno = e;
+ err(EXIT_FAILURE, "failed to flush and close temporary file");
+ }
+
+ if (renameat(dirfd, tmpname, dirfd, fname) != 0) {
+ err(EXIT_FAILURE, "failed to rename temporary file %s",
+ tmpname);
+ }
+
+ free(tmpname);
+}
+
+/*
+ * Look at a rule to determine whether or not we should consider including it or
+ * not. At this point we've already filtered things such that we only get core
+ * events.
+ *
+ * To consider an entry, we currently apply the following criteria:
+ *
+ * - The MSRIndex and MSRValue are zero. Programming additional MSRs is no
+ * supported right now.
+ * - TakenAlone is non-zero, which means that it cannot run at the same time as
+ * another field.
+ * - Offcore is one, indicating that it is off the core and we need to figure
+ * out if we can support this.
+ * - If the counter is fixed, don't use it for now.
+ * - If more than one value is specified in the EventCode or UMask values
+ */
+static boolean_t
+cpcgen_skip_entry(nvlist_t *nvl, const char *path, uint_t ent)
+{
+ char *event, *msridx, *msrval, *taken, *offcore, *counter;
+ char *ecode, *umask;
+
+ /*
+ * Require EventName, it's kind of useless without that.
+ */
+ if (nvlist_lookup_string(nvl, "EventName", &event) != 0) {
+ errx(EXIT_FAILURE, "Found event without 'EventName' property "
+ "in %s, entry %u", path, ent);
+ }
+
+ /*
+ * If we can't find an expected value, whine about it.
+ */
+ if (nvlist_lookup_string(nvl, "MSRIndex", &msridx) != 0 ||
+ nvlist_lookup_string(nvl, "MSRValue", &msrval) != 0 ||
+ nvlist_lookup_string(nvl, "Counter", &counter) != 0 ||
+ nvlist_lookup_string(nvl, "EventCode", &ecode) != 0 ||
+ nvlist_lookup_string(nvl, "UMask", &umask) != 0 ||
+ nvlist_lookup_string(nvl, "Offcore", &offcore) != 0) {
+ warnx("Skipping event %s (index %u) from %s, missing required "
+ "property", event, ent, path);
+ return (B_TRUE);
+ }
+
+ /*
+ * MSRIndex and MSRvalue comes as either "0" or "0x00".
+ */
+ if ((strcmp(msridx, "0") != 0 && strcmp(msridx, "0x00") != 0) ||
+ (strcmp(msrval, "0") != 0 && strcmp(msridx, "0x00") != 0) ||
+ strcmp(offcore, "0") != 0 || strchr(ecode, ',') != NULL ||
+ strchr(umask, ',') != NULL) {
+ return (B_TRUE);
+ }
+
+ /*
+ * Unfortunately, not everything actually has "TakenAlone". If it
+ * doesn't, we assume that it doesn't have to be.
+ */
+ if (nvlist_lookup_string(nvl, "TakenAlone", &taken) == 0 &&
+ strcmp(taken, "0") != 0) {
+ return (B_TRUE);
+ }
+
+
+ if (strncasecmp(counter, "fixed", strlen("fixed")) == 0)
+ return (B_TRUE);
+
+ return (B_FALSE);
+}
+
+/*
+ * For each processor family, generate a data file that contains all of the
+ * events that we support. Also generate a header that can be included that
+ * declares all of the tables.
+ */
+static void
+cpcgen_gen(int dirfd)
+{
+ cpc_map_t *map = cpcgen_maps;
+
+ if (map == NULL) {
+ errx(EXIT_FAILURE, "no platforms found or matched");
+ }
+
+ for (map = cpcgen_maps; map != NULL; map = map->cmap_next) {
+ int fd, ret;
+ FILE *f;
+ char *tmpname, *name;
+ uint32_t length, i;
+
+ if ((name = cpcgen_ops.cgen_op_name(map)) == NULL) {
+ exit(EXIT_FAILURE);
+ }
+
+ if (asprintf(&tmpname, ".%s.%d", name, getpid()) == -1) {
+ err(EXIT_FAILURE, "failed to construct temporary file "
+ "name");
+ }
+
+ if ((fd = openat(dirfd, tmpname, O_RDWR | O_CREAT, 0644)) < 0) {
+ err(EXIT_FAILURE, "failed to create temporary file %s",
+ tmpname);
+ }
+
+ if ((f = fdopen(fd, "w")) == NULL) {
+ int e = errno;
+ (void) unlinkat(dirfd, tmpname, 0);
+ errno = e;
+ err(EXIT_FAILURE, "failed to fdopen temporary file");
+ }
+
+ if (!cpcgen_ops.cgen_op_file_before(f, map)) {
+ (void) unlinkat(dirfd, tmpname, 0);
+ exit(EXIT_FAILURE);
+ }
+
+ /*
+ * Iterate over array contents.
+ */
+ if ((ret = nvlist_lookup_uint32(map->cmap_data, "length",
+ &length)) != 0) {
+ errx(EXIT_FAILURE, "failed to look up length property "
+ "in parsed data for %s: %s", map->cmap_path,
+ strerror(ret));
+ }
+
+ for (i = 0; i < length; i++) {
+ nvlist_t *nvl;
+ char num[64];
+
+ (void) snprintf(num, sizeof (num), "%u", i);
+ if ((ret = nvlist_lookup_nvlist(map->cmap_data,
+ num, &nvl)) != 0) {
+ errx(EXIT_FAILURE, "failed to look up array "
+ "entry %u in parsed data for %s: %s", i,
+ map->cmap_path, strerror(ret));
+ }
+
+ if (cpcgen_skip_entry(nvl, map->cmap_path, i))
+ continue;
+
+ if (!cpcgen_ops.cgen_op_event(f, nvl, map->cmap_path,
+ i)) {
+ (void) unlinkat(dirfd, tmpname, 0);
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ if (!cpcgen_ops.cgen_op_file_after(f, map)) {
+ (void) unlinkat(dirfd, tmpname, 0);
+ exit(EXIT_FAILURE);
+ }
+
+ if (fflush(f) != 0 || fclose(f) != 0) {
+ int e = errno;
+ (void) unlinkat(dirfd, tmpname, 0);
+ errno = e;
+ err(EXIT_FAILURE, "failed to flush and close "
+ "temporary file");
+ }
+
+ if (renameat(dirfd, tmpname, dirfd, name) != 0) {
+ err(EXIT_FAILURE, "failed to rename temporary file %s",
+ tmpname);
+ }
+
+ free(name);
+ free(tmpname);
+ }
+}
+
+static void
+cpcgen_usage(const char *fmt, ...)
+{
+ if (fmt != NULL) {
+ va_list ap;
+
+ (void) fprintf(stderr, "%s: ", cpcgen_progname);
+ va_start(ap, fmt);
+ (void) vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ }
+
+ (void) fprintf(stderr, "Usage: %s -a|-p platform -c|-H|-m -d datadir "
+ "-o outdir\n"
+ "\n"
+ "\t-a generate data for all platforms\n"
+ "\t-c generate C file for CPC\n"
+ "\t-d specify the directory containt perfmon data\n"
+ "\t-h generate header file and common files\n"
+ "\t-m generate manual pages for CPC data\n"
+ "\t-o outut files in directory outdir\n"
+ "\t-p generate data for a specified platform\n",
+ cpcgen_progname);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int c, outdirfd;
+ boolean_t do_mpage = B_FALSE, do_cfile = B_FALSE, do_header = B_FALSE,
+ do_all = B_FALSE;
+ const char *datadir = NULL, *outdir = NULL, *platform = NULL;
+ uint_t count = 0;
+
+ cpcgen_progname = basename(argv[0]);
+
+ while ((c = getopt(argc, argv, ":acd:hHmo:p:")) != -1) {
+ switch (c) {
+ case 'a':
+ do_all = B_TRUE;
+ break;
+ case 'c':
+ do_cfile = B_TRUE;
+ break;
+ case 'd':
+ datadir = optarg;
+ break;
+ case 'm':
+ do_mpage = B_TRUE;
+ break;
+ case 'H':
+ do_header = B_TRUE;
+ break;
+ case 'o':
+ outdir = optarg;
+ break;
+ case 'p':
+ platform = optarg;
+ break;
+ case ':':
+ cpcgen_usage("Option -%c requires an operand\n",
+ optopt);
+ return (2);
+ case '?':
+ cpcgen_usage("Unknown option: -%c\n", optopt);
+ return (2);
+ case 'h':
+ default:
+ cpcgen_usage(NULL);
+ return (2);
+ }
+ }
+
+ count = 0;
+ if (do_mpage)
+ count++;
+ if (do_cfile)
+ count++;
+ if (do_header)
+ count++;
+ if (count > 1) {
+ cpcgen_usage("Only one of -c, -h, and -m may be specified\n");
+ return (2);
+ } else if (count == 0) {
+ cpcgen_usage("One of -c, -h, and -m is required\n");
+ return (2);
+ }
+
+ count = 0;
+ if (do_all)
+ count++;
+ if (platform != NULL)
+ count++;
+ if (count > 1) {
+ cpcgen_usage("Only one of -a and -p may be specified\n");
+ return (2);
+ } else if (count == 0) {
+ cpcgen_usage("One of -a and -p is required\n");
+ return (2);
+ }
+
+
+ if (outdir == NULL) {
+ cpcgen_usage("Missing required output directory (-o)\n");
+ return (2);
+ }
+
+ if ((outdirfd = open(outdir, O_RDONLY)) < 0) {
+ err(EXIT_FAILURE, "failed to open output directory %s", outdir);
+ }
+
+ if (datadir == NULL) {
+ cpcgen_usage("Missing required data directory (-d)\n");
+ return (2);
+ }
+
+ cpcgen_read_mapfile(datadir, platform);
+
+ if (do_header) {
+ cpcgen_common_files(outdirfd);
+ return (0);
+ }
+
+ if (do_mpage) {
+ cpcgen_ops.cgen_op_name = cpcgen_manual_name;
+ cpcgen_ops.cgen_op_file_before = cpcgen_manual_file_before;
+ cpcgen_ops.cgen_op_file_after = cpcgen_manual_file_after;
+ cpcgen_ops.cgen_op_event = cpcgen_manual_event;
+ }
+
+ if (do_cfile) {
+ cpcgen_ops.cgen_op_name = cpcgen_cfile_name;
+ cpcgen_ops.cgen_op_file_before = cpcgen_cfile_file_before;
+ cpcgen_ops.cgen_op_file_after = cpcgen_cfile_file_after;
+ cpcgen_ops.cgen_op_event = cpcgen_cfile_event;
+ }
+
+
+ cpcgen_gen(outdirfd);
+
+ return (0);
+}