Subject: zcryptstats: Add new zcryptstats tool
From: Ingo Franzki <ifranzki@linux.ibm.com>

Summary:     s390-tools: Add zcryptstats tool
Description: The zcryptstats tool displays usage statistics of
             IBM Crypto Express adapters. It obtains cryptographic
             performance measurement data periodically and displays
             the data for each cryptographic device for each
             interval. A cryptographic device can be either a card
             device or queue device (APQN).
Upstream-ID: 7b4a05e5a36fd36ba1866047f6b4814f127ec846
Problem-ID:  SEC1709

Upstream-Description:

             zcryptstats: Add new zcryptstats tool

             The zcryptstats tool displays usage statistics of IBM Crypto Express
             adapters. It obtains cryptographic performance measurement data
             periodically and displays the data for each cryptographic device for
             each interval. A cryptographic device can be either a card device or
             a queue device (APQN).

             Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
             Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>


Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
---
 zconf/zcrypt/Makefile      |    6 
 zconf/zcrypt/zcryptstats.c | 2418 +++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 2422 insertions(+), 2 deletions(-)

--- a/zconf/zcrypt/Makefile
+++ b/zconf/zcrypt/Makefile
@@ -1,24 +1,26 @@
 include ../../common.mak
 
-all: chzcrypt lszcrypt zcryptctl
+all: chzcrypt lszcrypt zcryptctl zcryptstats
 
 libs = $(rootdir)/libutil/libutil.a
 
 chzcrypt: chzcrypt.o misc.o $(libs)
 lszcrypt: lszcrypt.o misc.o $(libs)
 zcryptctl:  zcryptctl.o misc.o $(libs)
+zcryptstats:  zcryptstats.o $(libs)
 
 install: all
 	$(INSTALL) -d -m 755 $(DESTDIR)$(BINDIR)
 	$(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 chzcrypt $(DESTDIR)$(BINDIR)
 	$(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 lszcrypt $(DESTDIR)$(BINDIR)
 	$(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 zcryptctl $(DESTDIR)$(BINDIR)
+	$(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 zcryptstats $(DESTDIR)$(BINDIR)
 	$(INSTALL) -d -m 755 $(DESTDIR)$(MANDIR)/man8
 	$(INSTALL) -m 644 -c chzcrypt.8 $(DESTDIR)$(MANDIR)/man8
 	$(INSTALL) -m 644 -c lszcrypt.8 $(DESTDIR)$(MANDIR)/man8
 	$(INSTALL) -m 644 -c zcryptctl.8 $(DESTDIR)$(MANDIR)/man8
 
 clean:
-	rm -f *.o chzcrypt lszcrypt zcryptctl
+	rm -f *.o chzcrypt lszcrypt zcryptctl zcryptstats
 
 .PHONY: all install clean
--- /dev/null
+++ b/zconf/zcrypt/zcryptstats.c
@@ -0,0 +1,2418 @@
+/*
+ * zcryptstats - Show usage statistics of IBM Crypto Express adapters
+ *
+ * Copyright IBM Corp. 2019
+ *
+ * s390-tools is free software; you can redistribute it and/or modify
+ * it under the terms of the MIT license. See LICENSE for details.
+ */
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <locale.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <signal.h>
+#include <time.h>
+#include <asm/chsc.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/utsname.h>
+
+#include "lib/util_base.h"
+#include "lib/util_file.h"
+#include "lib/util_libc.h"
+#include "lib/util_opt.h"
+#include "lib/util_path.h"
+#include "lib/util_prg.h"
+#include "lib/util_rec.h"
+#include "lib/util_scandir.h"
+#include "lib/zt_common.h"
+
+#ifndef offsetof
+	#define offsetof(type, member) ((size_t) &((type *)0)->member)
+#endif
+#ifndef offsetofend
+	#define offsetofend(type, member) \
+		(offsetof(type, member) + sizeof(((type *)0)->member))
+#endif
+
+#define SYSFS_DEVICES_AP_PATH		"devices/ap"
+#define SYSFS_DEVICES_CARD		"devices/ap/card%02x"
+#define SYSFS_DEVICES_APQN		"devices/ap/card%02x/%02x.%04x"
+#define SYSFS_DEVICES_CARD_ONLINE	"devices/ap/card%02x/online"
+#define SYSFS_DEVICES_APQN_ONLINE	"devices/ap/card%02x/%02x.%04x/online"
+#define CHSC_DEVICE			"/dev/chsc"
+#define NUM_CARDS_OLD			64
+#define NUM_CARDS			256
+#define NUM_DOMAINS			256
+
+#define MASK_WORD_BITS			(sizeof(uint32_t) * 8)
+#define MASK_WORD_NO(n)			((n) / MASK_WORD_BITS)
+#define MASK_BIT_IN_WORD(n)		((n) % MASK_WORD_BITS)
+#define MASK_BIT(n)			(0x80000000 >> MASK_BIT_IN_WORD(n))
+
+struct chsc_apdn {
+	uint8_t ap_index;
+	uint8_t domain_index;
+} __packed;
+
+struct chsc_scdmd_request {
+	struct chsc_header header;
+	struct chsc_apdn first_drid;
+	struct chsc_apdn last_drid;
+	uint32_t s:1;
+	uint32_t reserved1:31;
+	uint32_t reserved2;
+	uint32_t apsm[8];
+	uint32_t dsm[8];
+} __packed;
+
+struct chsc_scdmd_response {
+	struct chsc_header header;
+	uint32_t reserved1;
+	uint16_t p:1;
+	uint16_t reserved2:15;
+	struct chsc_apdn crid;
+	uint32_t reserved3;
+} __packed;
+
+struct chsc_scdmd_area {
+	struct chsc_scdmd_request request;
+	struct chsc_scdmd_response response;
+	uint8_t response_data[CHSC_SIZE - sizeof(struct chsc_scdmd_request) -
+			 sizeof(struct chsc_scdmd_response)];
+} __packed;
+
+struct chsc_scmd_request {
+	struct chsc_header header;
+	uint8_t reserved1;
+	uint8_t zeros1:6;
+	uint8_t one:1;
+	uint8_t zero:1;
+	uint8_t fcs;
+	uint8_t lcs;
+	uint32_t reserved2;
+	uint32_t reserved3;
+} __packed;
+
+struct chsc_scmd_response {
+	struct chsc_header header;
+	uint32_t reserved1;
+	uint32_t p:1;
+	uint32_t reserved2:31;
+	uint32_t reserved3;
+} __packed;
+
+struct chsc_scmd_area {
+	struct chsc_scmd_request request;
+	struct chsc_scmd_response response;
+	uint8_t response_data[CHSC_SIZE - sizeof(struct chsc_scmd_request) -
+			 sizeof(struct chsc_scmd_response)];
+} __packed;
+
+struct chsc_cmb_header {
+	uint8_t reserved1;
+	uint8_t ct; /* AP_DEVICE_TYPE_xxx values */
+	uint8_t format;
+	uint8_t ax;
+	float s;
+	uint32_t v;
+	uint8_t dx;
+	uint8_t mt;
+	uint16_t l4;
+} __packed;
+
+struct chsc_cmb_entry {
+	u64 t;
+	u64 c;
+} __packed;
+
+struct chsc_cmb_area {
+	struct chsc_cmb_header header;
+	struct chsc_cmb_entry entries[32];
+} __packed;
+
+#define CRYPTO_TYPE_PCICC	3
+#define CRYPTO_TYPE_PCICA	4
+#define CRYPTO_TYPE_PCIXCC	5
+#define CRYPTO_TYPE_CEX2A	6
+#define CRYPTO_TYPE_CEX2C	7
+#define CRYPTO_TYPE_CEX3A	8
+#define CRYPTO_TYPE_CEX3C	9
+#define CRYPTO_TYPE_CEX4S	10
+#define CRYPTO_TYPE_CEX5S	11
+#define CRYPTO_TYPE_CEX6S	12
+
+#define CRYPTO_TYPE_TOLERATION	CRYPTO_TYPE_CEX6S
+
+struct crypto_counter {
+	const char *name;
+	bool is_totals;
+};
+
+struct crypto_mode {
+	const char *name;
+	char indicatior_char;
+	unsigned int num_counters;
+	const struct crypto_counter *counters;
+};
+
+struct crypto_type {
+	const char *name;
+	unsigned int num_modes;
+	const struct crypto_mode *modes;
+};
+
+#define NUM_COPROC_COUNTERS	2
+const struct crypto_counter counter_coproc[NUM_COPROC_COUNTERS] = {
+		{ .name = "All", .is_totals = true },
+		{ .name = "RSA Key-gen" },
+};
+
+#define NUM_ACCEL_COUNTERS	6
+const struct crypto_counter counter_accel[NUM_ACCEL_COUNTERS] = {
+		{ .name = "RSA 1024 ME" },
+		{ .name = "RSA 2048 ME" },
+		{ .name = "RSA 1024 CRT" },
+		{ .name = "RSA 2048 CRT" },
+		{ .name = "RSA 4096 ME" },
+		{ .name = "RSA 4096 CTR" },
+};
+
+#define NUM_EP11_COUNTERS	5
+const struct crypto_counter counter_ep11[NUM_EP11_COUNTERS] = {
+		{ .name = "Asym. Slow" },
+		{ .name = "Asym. Fast" },
+		{ .name = "Symm. Partial" },
+		{ .name = "Symm. Complete" },
+		{ .name = "Asym. Key-gen" },
+};
+
+#define NUM_PCICA_COUNTERS	20
+const struct crypto_counter counter_pcica[NUM_PCICA_COUNTERS] = {
+		{ .name = "RSA 1024 ME (E0)" },
+		{ .name = "RSA 2048 ME (E0)" },
+		{ .name = "RSA 1024 CRT (E0)" },
+		{ .name = "RSA 2048 CRT (E0)" },
+		{ .name = "RSA 1024 ME (E1)" },
+		{ .name = "RSA 2048 ME (E1)" },
+		{ .name = "RSA 1024 CRT (E1)" },
+		{ .name = "RSA 2048 CRT (E1)" },
+		{ .name = "RSA 1024 ME (E2)" },
+		{ .name = "RSA 2048 ME (E2)" },
+		{ .name = "RSA 1024 CRT (E2)" },
+		{ .name = "RSA 2048 CRT (E2)" },
+		{ .name = "RSA 1024 ME (E3)" },
+		{ .name = "RSA 2048 ME (E3)" },
+		{ .name = "RSA 1024 CRT (E3)" },
+		{ .name = "RSA 2048 CRT (E3)" },
+		{ .name = "RSA 1024 ME (E4)" },
+		{ .name = "RSA 2048 ME (E4)" },
+		{ .name = "RSA 1024 CRT (E4)" },
+		{ .name = "RSA 2048 CRT (E4)" },
+};
+
+#define NUM_COPROC_MODES	1
+const struct crypto_mode mode_coproc[1] = {
+		{ .num_counters = NUM_COPROC_COUNTERS,
+		  .counters = counter_coproc},
+};
+
+#define NUM_ACCEL_MODES		1
+const struct crypto_mode mode_accel[1] = {
+		{ .num_counters = NUM_ACCEL_COUNTERS,
+		  .counters = counter_accel },
+};
+
+#define NUM_PCICA_MODES		1
+const struct crypto_mode mode_pcica[1] = {
+		{ .num_counters = NUM_PCICA_COUNTERS,
+		  .counters = counter_pcica },
+};
+
+#define NUM_CEX456_MODES	11
+const struct crypto_mode mode_cex456[NUM_CEX456_MODES] = {
+		{ 0 },
+		{ 0 },
+		{ 0 },
+		{ 0 },
+		{ 0 },
+		{ 0 },
+		{ 0 },
+		{ 0 },
+		{ .name  = "Accelerator", .indicatior_char = 'A',
+		  .num_counters = NUM_ACCEL_COUNTERS,
+		  .counters = counter_accel },
+		{ .name  = "CCA co-processor", .indicatior_char = 'C',
+		  .num_counters = NUM_COPROC_COUNTERS,
+		  .counters = counter_coproc},
+		{ .name  = "EP11 co-processor", .indicatior_char = 'P',
+		  .num_counters = NUM_EP11_COUNTERS,
+		  .counters = counter_ep11 },
+};
+
+#define NUM_CRYPTO_TYPES	13
+const struct crypto_type crypto_types[NUM_CRYPTO_TYPES] = {
+		{ 0 },
+		{ 0 },
+		{ 0 },
+		{ .name = "PCICC", .num_modes = NUM_COPROC_MODES,
+		  .modes = mode_coproc },
+		{ .name = "PCICA", .num_modes = NUM_PCICA_MODES,
+		  .modes = mode_pcica },
+		{ .name = "PCIXCC", .num_modes = NUM_COPROC_MODES,
+		  .modes = mode_coproc},
+		{ .name = "CEX2A", .num_modes = NUM_ACCEL_MODES,
+		  .modes = mode_accel },
+		{ .name = "CEX2C", .num_modes = NUM_COPROC_MODES,
+		  .modes = mode_coproc },
+		{ .name = "CEX3A", .num_modes = NUM_ACCEL_MODES,
+		  .modes = mode_accel },
+		{ .name = "CEX3C", .num_modes = NUM_COPROC_MODES,
+		  .modes = mode_coproc },
+		{ .name = "CEX4", .num_modes = NUM_CEX456_MODES,
+		  .modes = mode_cex456 },
+		{ .name = "CEX5", .num_modes = NUM_CEX456_MODES,
+		  .modes = mode_cex456 },
+		{ .name = "CEX6", .num_modes = NUM_CEX456_MODES,
+		  .modes = mode_cex456 },
+};
+
+
+struct type_mapping {
+	uint8_t from_type;
+	uint8_t from_mode;
+	uint8_t to_type;
+	uint8_t to_mode;
+	struct type_mapping *next;
+};
+
+struct device_selection {
+	int card;
+	int domain; /* -1 if not specified */
+	struct device_selection *next;
+};
+
+struct interval_data {
+	bool current_valid;
+	bool previous_valid;
+	struct chsc_cmb_area current;
+	struct chsc_cmb_area previous;
+};
+
+struct card_data {
+	struct interval_data data;
+	struct interval_data *domains[NUM_DOMAINS];
+};
+
+struct interval_values {
+	u64 count;
+	double rate;
+	double utilization;
+	double duration;
+};
+
+struct print_func {
+	int (*print_initialize)(void);
+	int (*print_terminate)(void);
+	int (*print_header)(void);
+	int (*print_footer)(void);
+	int (*print_interval_header)(unsigned long interval_count,
+				     const char *timestamp);
+	int (*print_interval_footer)(void);
+	int (*print_device_header)(bool is_apqn, uint8_t card, uint8_t domain,
+				   const char *type, const char *timestamp);
+	int (*print_device_footer)(void);
+	int (*print_counter_data)(bool is_apqn, uint8_t card, uint8_t domain,
+				  const char *type, const char *timestamp,
+				  const char *name,
+				  struct interval_values *vals);
+	int (*print_counter_separator)(void);
+};
+
+#define pr_call(func)	g.print_funcs->func == NULL ? 0 : g.print_funcs->func
+
+static int default_print_initialize(void);
+static int default_print_terminate(void);
+static int default_print_header(void);
+static int default_print_footer(void);
+static int default_print_interval_header(unsigned long interval_count,
+					 const char *timestamp);
+static int default_print_device_header(bool is_apqn, uint8_t card,
+				       uint8_t domain, const char *type,
+				       const char *timestamp);
+static int default_print_device_footer(void);
+static int default_print_counter_data(bool is_apqn, uint8_t card,
+				      uint8_t domain, const char *type,
+				      const char *timestamp, const char *name,
+				      struct interval_values *vals);
+static int default_print_counter_separator(void);
+
+static const struct print_func default_print = {
+	.print_initialize = default_print_initialize,
+	.print_terminate = default_print_terminate,
+	.print_header = default_print_header,
+	.print_footer = default_print_footer,
+	.print_interval_header = default_print_interval_header,
+	.print_device_header = default_print_device_header,
+	.print_device_footer = default_print_device_footer,
+	.print_counter_data = default_print_counter_data,
+	.print_counter_separator = default_print_counter_separator,
+};
+
+static int json_print_initialize(void);
+static int json_print_header(void);
+static int json_print_footer(void);
+static int json_print_interval_header(unsigned long interval_count,
+				      const char *timestamp);
+static int json_print_interval_footer(void);
+static int json_print_device_header(bool is_apqn, uint8_t card,
+				    uint8_t domain, const char *type,
+				    const char *timestamp);
+static int json_print_device_footer(void);
+static int json_print_counter_data(bool is_apqn, uint8_t card, uint8_t domain,
+				   const char *type, const char *timestamp,
+				   const char *name,
+				   struct interval_values *vals);
+
+static const struct print_func json_print = {
+	.print_initialize = json_print_initialize,
+	.print_header = json_print_header,
+	.print_footer = json_print_footer,
+	.print_interval_header = json_print_interval_header,
+	.print_interval_footer = json_print_interval_footer,
+	.print_device_header = json_print_device_header,
+	.print_device_footer = json_print_device_footer,
+	.print_counter_data = json_print_counter_data,
+};
+
+
+static int table_print_initialize(void);
+static int table_print_terminate(void);
+static int table_print_header(void);
+static int table_print_interval_footer(void);
+static int table_print_counter_data(bool is_apqn, uint8_t card, uint8_t domain,
+				    const char *type, const char *timestamp,
+				    const char *name,
+				    struct interval_values *vals);
+
+static const struct print_func table_print = {
+	.print_initialize = table_print_initialize,
+	.print_terminate = table_print_terminate,
+	.print_header = table_print_header,
+	.print_interval_footer = table_print_interval_footer,
+	.print_counter_data = table_print_counter_data,
+};
+
+static int csv_print_initialize(void);
+static int csv_print_terminate(void);
+static int csv_print_header(void);
+static int csv_print_counter_data(bool is_apqn, uint8_t card, uint8_t domain,
+				    const char *type, const char *timestamp,
+				    const char *name,
+				    struct interval_values *vals);
+
+static const struct print_func csv_print = {
+	.print_initialize = csv_print_initialize,
+	.print_terminate = csv_print_terminate,
+	.print_header = csv_print_header,
+	.print_counter_data = csv_print_counter_data,
+};
+
+/*
+ * Program configuration
+ */
+const struct util_prg prg = {
+	.desc = "Display usage statistics of IBM Crypto Express adapters",
+	.args = "[DEVICE_IDS]",
+	.copyright_vec = {
+		{
+			.owner = "IBM Corp.",
+			.pub_first = 2019,
+			.pub_last = 2019,
+		},
+		UTIL_PRG_COPYRIGHT_END
+	}
+};
+
+/*
+ * Global variables for program options
+ */
+static struct zcryptstats_globals {
+	long interval;
+	unsigned long count;
+	bool no_totals;
+	bool only_totals;
+	bool no_apqn;
+	char *map_type;
+	char **device_ids;
+	bool all;
+	bool only_online;
+	bool verbose;
+	int chsc_fd;
+	uint8_t max_card_used;
+	uint32_t card_mask[8];
+	uint8_t min_card;
+	uint8_t max_card;
+	uint32_t domain_mask[8];
+	uint8_t min_domain;
+	uint8_t max_domain;
+	struct device_selection *dev_selection;
+	struct type_mapping *type_mapping;
+	struct card_data *cards[NUM_CARDS];
+	const struct print_func *print_funcs;
+	struct util_rec *device_rec;
+	struct util_rec *counter_rec;
+	bool first_device;
+	bool first_counter;
+} g = {
+	.interval = 10,
+	.chsc_fd = -1,
+	.print_funcs = &default_print,
+};
+
+
+static volatile bool quit;
+
+/*
+ * Configuration of command line options
+ */
+static struct util_opt opt_vec[] = {
+	/***********************************************************/
+	{
+		.flags = UTIL_OPT_FLAG_SECTION,
+		.desc = "OPTIONS",
+	},
+	{
+		.option = {"interval", required_argument, NULL, 'i'},
+		.argument = "INTERVAL",
+		.desc = "Specifies the interval time in seconds. If omitted, a "
+			"default interval of 10 seconds is used",
+	},
+	{
+		.option = {"count", required_argument, NULL, 'c'},
+		.argument = "COUNT",
+		.desc = "Specifies  the  number of reports that are generated "
+			"at INTERVAL seconds apart. If omitted, reports are "
+			"generated continuously, until stopped with control-C",
+	},
+	{
+		.option = {"output", required_argument, NULL, 'o'},
+		.argument = "JSON|TABLE|CSV",
+		.desc = "Displays the statistics in the specified format. If "
+			"this option is omitted, a comprehensive report is "
+			"displayed. Supported output formats are: JSON, TABLE, "
+			"CSV. With TABLE and CSV the display of the individual "
+			"counters are omitted, and only the totals are "
+			"displayed. CSV and TABLE output formats imply option "
+			"--only-totals",
+	},
+	{
+		.option = {"no-totals", 0, NULL, 't'},
+		.desc = "Excludes the totals of all counters of a card "
+			"device or queue device (APQN). It can not be "
+			"specified together with option --only-totals or "
+			"option --output TABLE|CSV",
+	},
+	{
+		.option = {"only-totals", 0, NULL, 'T'},
+		.desc = "Displays only the totals of all counters of a card "
+			"device or a queue device (APQN), but not the "
+			"individual counters. This option is implied with "
+			"option --output TABLE|CSV",
+	},
+	{
+		.option = {"no-apqn", 0, NULL, 'a'},
+		.desc = "Displays only the counters of the card device, but "
+			"omits the counters of the queue device (APQN). If the "
+			"system does not support obtaining cryptographic "
+			"performance measurement data on the queue devices, "
+			"then this option is implied",
+	},
+	{
+		.option = {"map-type", required_argument, NULL, 'M'},
+		.argument = "MAPPING",
+		.desc = "Maps unknown cryptographic device types and modes to "
+			"known types and modes. This option should only be "
+			"used when new, so far unknown cryptographic devices "
+			"are found. You can then map them to known devices and "
+			"modes, provided that the new cryptographic devices "
+			"report the same counters as the known cryptographic "
+			"device to which it is mapped. The mapping "
+			"specification consists of a comma-separated list of "
+			"FROM-TYPE:FROM-MODE=TO-TYPE:TO-MODE specifications. "
+			"The type and mode values must be specified in decimal "
+			"notation",
+	},
+	{
+		.option = {"all", 0, NULL, 'A'},
+		.desc = "Displays all cards devices and queue devices (APQNs), "
+			"not only those that are available to the Linux "
+			"system. Using this option additional cryptographic "
+			"devices that are available in the CEC, but not "
+			"available to the Linux system are also monitored. "
+			"This option can not be specified together with option "
+			"--only-online",
+	},
+	{
+		.option = {"only-online", 0, NULL, 'O'},
+		.desc = "Displays only online cards devices and queue devices "
+			"(APQNs). This option can not be specified together "
+			"with option --all"
+	},
+	{
+		.option = {"verbose", 0, NULL, 'V'},
+		.desc = "Prints additional information messages during "
+			"processing",
+	},
+	UTIL_OPT_HELP,
+	UTIL_OPT_VERSION,
+	UTIL_OPT_END
+};
+
+#define pr_verbose(fmt...)	do { \
+					if (g.verbose) \
+						warnx(fmt); \
+				} while (0)
+
+/*
+ * Describe adapter ids
+ */
+static void print_adapter_id_help(void)
+{
+	printf("\n");
+	printf("DEVICE_IDS\n");
+	util_print_indented("  List of cryptographic device IDs separated by "
+			    "blanks for which statistics are displayed. "
+			    "DEVICE_ID can either be a card device ID "
+			    "('<card-id>') or a queue device ID (<card-id>."
+			    "<domain-id>'). To filter all devices by domain, "
+			    "provide '.<domain-id>'. If no IDs are given, "
+			    "statistics are displayed for all available "
+			    "devices.", 2);
+	printf("\n");
+	printf("EXAMPLE:\n");
+	util_print_indented("  Display statistics for all cryptographic "
+			    "devices with card ID '02.", 2);
+	printf("  # zcryptstats 02\n");
+	printf("\n");
+	util_print_indented("  Display statistics for cryptographic devices "
+			    "with card ID '02' and domain ID '0005'.", 2);
+	printf("  # zcryptstats 02.0005\n");
+	printf("\n");
+}
+
+static struct type_mapping *find_type_mapping(uint8_t from_type,
+					      uint8_t from_mode)
+{
+	struct type_mapping *map = g.type_mapping;
+
+	while (map != NULL) {
+		if (map->from_type == from_type && map->from_mode == from_mode)
+			return map;
+		map = map->next;
+	}
+	return NULL;
+}
+
+/*
+ * Get the name of the card for a crypto type and mode.
+ * Note: This function might return the address of a static string variable.
+ * It is only valid until this function is called again.
+ */
+static const char *get_card_name(uint8_t type, uint8_t mode)
+{
+	const struct crypto_type *ct;
+	const struct crypto_mode *m;
+	static char temp_name[250];
+	struct type_mapping *map;
+
+	map = find_type_mapping(type, mode);
+	if (map != NULL) {
+		type = map->to_type;
+		mode = map->to_mode;
+	} else if (type >= NUM_CRYPTO_TYPES) {
+		type = CRYPTO_TYPE_TOLERATION;
+	}
+
+	if (type >= NUM_CRYPTO_TYPES)
+		return "UNKNOWN ADAPTER TYPE";
+
+	ct = &crypto_types[type];
+	if (ct->name == NULL || ct->modes == NULL || ct->num_modes == 0)
+		return "UNKNOWN ADAPTER TYPE";
+
+	if (mode >= ct->num_modes)
+		return ct->name;
+
+	m = &ct->modes[mode];
+	snprintf(temp_name, sizeof(temp_name) - 1, "%s%c (%s)", ct->name,
+		 m->indicatior_char, m->name != NULL ? m->name : "");
+	return temp_name;
+}
+
+/*
+ * Get the name of a counter for a crypto type, mode and index.
+ * Note: This function might return the address of a static string variable.
+ * It is only valid until this function is called again.
+ */
+static const char *get_counter_name(uint8_t type, uint8_t mode, uint8_t index)
+{
+	const struct crypto_type *ct;
+	const struct crypto_mode *m;
+	static char temp_name[250];
+	struct type_mapping *map;
+
+	map = find_type_mapping(type, mode);
+	if (map != NULL) {
+		type = map->to_type;
+		mode = map->to_mode;
+	} else if (type >= NUM_CRYPTO_TYPES) {
+		type = CRYPTO_TYPE_TOLERATION;
+	}
+
+	if (type >= NUM_CRYPTO_TYPES)
+		goto generic;
+
+	ct = &crypto_types[type];
+	if (ct->name == NULL || ct->modes == NULL || ct->num_modes == 0)
+		goto generic;
+
+	if (mode >= ct->num_modes)
+		goto generic;
+
+	m = &ct->modes[mode];
+	if (m->counters == NULL || m->num_counters == 0)
+		goto generic;
+
+	if (index >= m->num_counters)
+		goto generic;
+
+	return m->counters[index].name;
+
+generic:
+	snprintf(temp_name, sizeof(temp_name) - 1, "COUNTER %u", index);
+	return temp_name;
+}
+
+/*
+ * Returns true if a counter for a crypto type, mode and index represents the
+ * total number of operations.
+ */
+static bool is_counter_totals(uint8_t type, uint8_t mode, uint8_t index)
+{
+	const struct crypto_type *ct;
+	const struct crypto_mode *m;
+	struct type_mapping *map;
+
+	map = find_type_mapping(type, mode);
+	if (map != NULL) {
+		type = map->to_type;
+		mode = map->to_mode;
+	} else if (type >= NUM_CRYPTO_TYPES) {
+		type = CRYPTO_TYPE_TOLERATION;
+	}
+
+	if (type >= NUM_CRYPTO_TYPES)
+		return false;
+
+	ct = &crypto_types[type];
+	if (ct->name == NULL || ct->modes == NULL || ct->num_modes == 0)
+		return false;
+
+	if (mode >= ct->num_modes)
+		return false;
+
+	m = &ct->modes[mode];
+	if (m->counters == NULL || m->num_counters == 0)
+		return false;
+
+	if (index >= m->num_counters)
+		return false;
+
+	return m->counters[index].is_totals;
+}
+
+/*
+ * Returns true if the card is available
+ */
+static bool is_card_available(uint8_t card)
+{
+	char *path;
+	bool ret;
+
+	path = util_path_sysfs(SYSFS_DEVICES_CARD, card);
+	ret = util_path_is_dir(path);
+	free(path);
+
+	return ret;
+}
+
+/*
+ * Returns true if the APQN is available
+ */
+static bool is_apqn_available(uint8_t card, uint8_t domain)
+{
+	char *path;
+	bool ret;
+
+	path = util_path_sysfs(SYSFS_DEVICES_APQN, card, card, domain);
+	ret = util_path_is_dir(path);
+	free(path);
+
+	return ret;
+}
+
+/*
+ * Returns true if the card is online
+ */
+static bool is_card_online(uint8_t card)
+{
+	unsigned long online;
+	char *path;
+	int rc;
+
+	path = util_path_sysfs(SYSFS_DEVICES_CARD_ONLINE, card);
+	rc = util_file_read_ul(&online, 10, path);
+	free(path);
+
+	return rc == 0 && online != 0;
+}
+
+/*
+ * Returns true if the APQN is online
+ */
+static bool is_apqn_online(uint8_t card, uint8_t domain)
+{
+	unsigned long online;
+	char *path;
+	int rc;
+
+	path = util_path_sysfs(SYSFS_DEVICES_APQN_ONLINE, card, card, domain);
+	rc = util_file_read_ul(&online, 10, path);
+	free(path);
+
+	if (rc != 0)
+		return false;
+
+	return online != 0;
+}
+
+/*
+ * Updates the APQNs data with data for the current interval.
+ */
+static void update_apqn_data(uint8_t card, uint8_t domain,
+			     struct chsc_cmb_area *cmb, size_t cmb_len)
+{
+	struct card_data *cd = g.cards[card];
+	struct interval_data *dd;
+
+	if (cd == NULL) {
+		cd = util_malloc(sizeof(struct card_data));
+		memset(cd, 0, sizeof(struct card_data));
+		g.cards[card] = cd;
+
+		pr_verbose("Card %02x added", card);
+	}
+
+	dd = cd->domains[domain];
+	if (dd == NULL) {
+		dd = util_malloc(sizeof(struct interval_data));
+		memset(dd, 0, sizeof(struct interval_data));
+		cd->domains[domain] = dd;
+
+		pr_verbose("APQN %02x.%04x added", card, domain);
+	} else {
+		if (!dd->current_valid) {
+			dd->previous = dd->current;
+			dd->previous_valid = true;
+		}
+	}
+
+	memset(&dd->current, 0, sizeof(struct chsc_cmb_area));
+	memcpy(&dd->current, cmb, cmb_len);
+	dd->current_valid = true;
+}
+
+/*
+ * Updates the card's data with data for the current interval.
+ */
+static void update_card_data(uint8_t card, struct chsc_cmb_area *cmb,
+			     size_t cmb_len)
+{
+	struct card_data *cd = g.cards[card];
+
+	if (cd == NULL) {
+		cd = util_malloc(sizeof(struct card_data));
+		memset(cd, 0, sizeof(struct card_data));
+		g.cards[card] = cd;
+
+		pr_verbose("Card %02x added", card);
+	} else {
+		if (!cd->data.current_valid) {
+			cd->data.previous = cd->data.current;
+			cd->data.previous_valid = true;
+		}
+	};
+
+	memset(&cd->data.current, 0, sizeof(struct chsc_cmb_area));
+	memcpy(&cd->data.current, cmb, cmb_len);
+	cd->data.current_valid = true;
+}
+
+/*
+ * Frees the interval data of a card
+ */
+static void free_card_data(struct card_data *cd)
+{
+	struct interval_data *dd;
+	int domain;
+
+	if (cd == NULL)
+		return;
+
+	for (domain = 0; domain < NUM_DOMAINS; domain++) {
+		dd = cd->domains[domain];
+		if (dd == NULL)
+			continue;
+
+		free(dd);
+		cd->domains[domain] = NULL;
+	}
+
+	free(cd);
+}
+
+/*
+ * Frees the interval data
+ */
+static void free_interval_data(void)
+{
+	struct card_data *cd;
+	int card;
+
+	for (card = 0; card < NUM_CARDS; card++) {
+		cd = g.cards[card];
+		if (cd == NULL)
+			continue;
+
+		free_card_data(cd);
+		g.cards[card] = NULL;
+	}
+}
+
+/*
+ * Returns the highest card index used by the system.
+ * If there is a card with an index > 0x3f, then the returned number is 0xff,
+ * else 0x3f is returned.
+ */
+static int get_max_card_index(uint8_t *max_index)
+{
+	struct dirent **dev_vec = NULL;
+	int i, count, card, rc = 0;
+	char *path;
+
+	path = util_path_sysfs(SYSFS_DEVICES_AP_PATH);
+	if (!util_path_is_dir(path)) {
+		warnx("Crypto device driver is not available");
+		rc = -ENODEV;
+		goto out;
+	}
+
+	count = util_scandir(&dev_vec, NULL, path, "card[0-9a-fA-F]+");
+	if (count < 1) {
+		warnx("No crypto card devices found");
+		rc = -ENODEV;
+		goto out;
+	}
+
+	*max_index = NUM_CARDS_OLD - 1;
+	for (i = 0; i < count; i++) {
+		if (sscanf(dev_vec[i]->d_name, "card%x", &card) != 1)
+			continue;
+		if (card >= NUM_CARDS_OLD)
+			*max_index = NUM_CARDS - 1;
+	}
+
+	pr_verbose("Max card index used: %u", *max_index);
+
+out:
+	free(path);
+	if (dev_vec != NULL)
+		free(dev_vec);
+	return rc;
+}
+
+/*
+ * Returns the size of the CMB. The size is either contained in l4 field
+ * of the cmb, or a fix length dependent on the crypto type, if l4 is zero.
+ */
+static size_t get_cmb_length(struct chsc_cmb_area *cmb)
+{
+	size_t len = cmb->header.l4;
+
+	if (len != 0)
+		return len;
+
+	switch (cmb->header.ct) {
+	case CRYPTO_TYPE_PCICC:
+	case CRYPTO_TYPE_PCIXCC:
+	case CRYPTO_TYPE_CEX2C:
+	case CRYPTO_TYPE_CEX3C:
+		return 64;
+	case CRYPTO_TYPE_PCICA:
+		return 336;
+	case CRYPTO_TYPE_CEX2A:
+	case CRYPTO_TYPE_CEX3A:
+		return 80;
+	default:
+		warnx("Zero length value in CMB");
+		return 0;
+	}
+}
+
+/*
+ * Return true if the device is in the device selection list and mask
+ */
+static bool filter_device(uint8_t card, uint8_t domain, bool is_apqn)
+{
+	struct device_selection *dev;
+	bool found;
+
+	/* Check for selection mask */
+	if ((g.card_mask[MASK_WORD_NO(card)] &
+				MASK_BIT(card)) == 0) {
+		pr_verbose("Skipping card %02x (mask)", card);
+		return false;
+	}
+	if (is_apqn) {
+		if ((g.domain_mask[MASK_WORD_NO(domain)] &
+					MASK_BIT(domain)) == 0) {
+			pr_verbose("Skipping APQN %02x.%04x (mask)", card,
+				   domain);
+			return false;
+		}
+	}
+
+	/* Check for device selection list */
+	if (g.dev_selection != NULL) {
+		dev = g.dev_selection;
+		found = false;
+		while (dev != NULL) {
+			if (is_apqn == false) {
+				/* Its a card */
+				if (card == (dev->card >= 0 ? dev->card :
+						card)) {
+					found = true;
+					break;
+				}
+			} else {
+				/* Its an APQN */
+				if (card == (dev->card >= 0 ? dev->card :
+						card) &&
+				    domain == (dev->domain >= 0 ? dev->domain :
+						domain)) {
+					found = true;
+					break;
+				}
+			}
+
+			dev = dev->next;
+		}
+
+		if (!found) {
+			if (is_apqn)
+				pr_verbose("Skipping APQN %02x.%04x "
+					   "(selection)", card, domain);
+			else
+				pr_verbose("Skipping card %02x (selection)",
+					   card);
+			return false;
+		}
+	}
+
+	if (g.all)
+		return true;
+
+	/* Check if card/APQN is available in the system (SYSFS) */
+	if (!is_card_available(card)) {
+		pr_verbose("Skipping card %02x (not available)", card);
+		return false;
+	}
+	if (is_apqn && !is_apqn_available(card, domain)) {
+		pr_verbose("Skipping APQN %02x.%04x (not available)",
+			   card, domain);
+		return false;
+	}
+
+	if (g.only_online) {
+		/* Check if card/APQN is online */
+		if (!is_card_online(card)) {
+			pr_verbose("Skipping card %02x (not online)", card);
+			return false;
+		}
+		if (is_apqn && !is_apqn_online(card, domain)) {
+			pr_verbose("Skipping APQN %02x.%04x (not online)",
+				   card, domain);
+			return false;
+		}
+	}
+
+	return true;
+}
+
+/*
+ * Process a crypto measurement block.
+ * Passes back the actual length of the CMB processed and its card number.
+ * Returns -ENODEV when the CMB is skipped.
+ */
+static int process_cmb(struct chsc_cmb_area *cmb, size_t size, size_t *length,
+		       uint8_t *card)
+{
+	size_t len;
+
+	len = get_cmb_length(cmb);
+	if (len == 0)
+		return -EINVAL;
+
+	if (len > size) {
+		warnx("Length value in CMB exceeds size of CMB");
+		return -EINVAL;
+	}
+
+	if (card != NULL)
+		*card = cmb->header.ax;
+	if (length != NULL)
+		*length = len;
+
+	if (filter_device(cmb->header.ax, cmb->header.dx,
+			  cmb->header.format == 1) == false)
+		return -ENODEV;
+
+	if (cmb->header.format == 1)
+		update_apqn_data(cmb->header.ax, cmb->header.dx, cmb, len);
+	else
+		update_card_data(cmb->header.ax, cmb, len);
+
+	return 0;
+}
+
+/*
+ * Translate the CHSC response code to an error (0 or negative errno)
+ */
+static int chsc_error_from_response(int response)
+{
+	if (response != 0x0001)
+		pr_verbose("CHSC Response code: %04x", response);
+
+	switch (response) {
+	case 0x0001:
+		return 0;
+	case 0x0002:
+		return -EOPNOTSUPP;
+	case 0x0003:
+	case 0x0006:
+	case 0x0007:
+	case 0x0008:
+	case 0x000a:
+	case 0x0103:
+	case 0x0104:
+		return -EINVAL;
+	case 0x0004:
+		return -EOPNOTSUPP;
+	case 0x000b:
+		return -EBUSY;
+	case 0x0102:
+		return -ENOMEM;
+	case 0x0105:
+		return -EACCES;
+	case 0x0100:
+	case 0x0107:
+		return -ENODEV;
+	default:
+		return -EIO;
+	}
+}
+
+/*
+ * Process the APQN measurement data and extract the CMBs
+ */
+static int process_apqn_measurement_data(struct chsc_scdmd_area *scdmd_area)
+{
+	size_t size = scdmd_area->response.header.length -
+				sizeof(struct chsc_scdmd_response);
+	size_t len, ofs = 0;
+	int rc;
+
+	while (ofs < size) {
+		rc = process_cmb((struct chsc_cmb_area *)
+					&scdmd_area->response_data[ofs],
+				 size - ofs, &len, NULL);
+		if (rc != 0 && rc != -ENODEV)
+			return rc;
+		ofs += len;
+
+	}
+
+	return 0;
+}
+
+/*
+ * Get Crypto Measurement data on the APQN level
+ */
+static int get_apqn_measurement_data(uint8_t card)
+{
+	struct chsc_scdmd_area scdmd_area;
+	int rc;
+
+	memset(&scdmd_area, 0, sizeof(scdmd_area));
+	do {
+		scdmd_area.request.header.code = 0x102d;
+		scdmd_area.request.header.length =
+				sizeof(struct chsc_scdmd_request);
+		scdmd_area.request.first_drid.ap_index = card;
+		scdmd_area.request.first_drid.domain_index = g.min_domain;
+		scdmd_area.request.last_drid.ap_index = card;
+		scdmd_area.request.last_drid.domain_index = g.max_domain;
+		scdmd_area.request.s = 1;
+		scdmd_area.request.apsm[MASK_WORD_NO(card)] |= MASK_BIT(card);
+		memcpy(scdmd_area.request.dsm, g.domain_mask,
+				sizeof(scdmd_area.request.dsm));
+
+		rc = ioctl(g.chsc_fd, CHSC_START_SYNC, &scdmd_area);
+		if (rc != 0) {
+			rc = -errno;
+			warnx("Failed to get APQN measurement data for card "
+			      "%02x: %s", card, strerror(errno));
+			break;
+		}
+
+		rc = chsc_error_from_response(scdmd_area.response.header.code);
+		if (rc != 0) {
+			if (rc != -EOPNOTSUPP && rc != -ENODEV) {
+				warnx("Failed to get APQN crypto measurement "
+				      "data for card %02x: %s", card,
+				      strerror(-rc));
+			} else {
+				pr_verbose("Failed to get APQN crypto "
+					   "measurement data for card %02x: %s",
+					   card, strerror(-rc));
+				/*
+				 * ignore return code other than -EOPNOTSUPP
+				 * and -ENODEV
+				 */
+				rc = 0;
+			}
+			break;
+		}
+
+		rc = process_apqn_measurement_data(&scdmd_area);
+		if (rc != 0)
+			break;
+
+		if (scdmd_area.response.p)
+			scdmd_area.request.first_drid =
+					scdmd_area.response.crid;
+	} while (scdmd_area.response.p);
+
+	return rc;
+}
+
+/*
+ * Process the card measurement data and extract the CMBs
+ */
+static int process_card_measurement_data(struct chsc_scmd_area *scmd_area,
+					 uint8_t *last_card)
+{
+	size_t size = scmd_area->response.header.length -
+				sizeof(struct chsc_scmd_response);
+	size_t len, ofs = 0;
+	int rc;
+
+	while (ofs < size) {
+		rc = process_cmb((struct chsc_cmb_area *)
+					&scmd_area->response_data[ofs],
+					size - ofs, &len, last_card);
+		if (rc != 0 && rc != -ENODEV)
+			return rc;
+		ofs += len;
+
+		if (rc == -ENODEV)
+			continue;
+
+		if (!g.no_apqn) {
+			rc = get_apqn_measurement_data(*last_card);
+			if (rc != 0)
+				return rc;
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * Get Crypto Measurement data on the card level
+ */
+static int get_card_measurement_data(void)
+{
+	struct chsc_scmd_area scmd_area;
+	uint8_t last_card = 0;
+	int rc;
+
+	memset(&scmd_area, 0, sizeof(scmd_area));
+	do {
+		scmd_area.request.header.code = 0x102e;
+		scmd_area.request.header.length =
+				sizeof(struct chsc_scmd_request);
+		scmd_area.request.one = 1;
+		scmd_area.request.fcs = g.min_card;
+		scmd_area.request.lcs = g.max_card;
+
+		rc = ioctl(g.chsc_fd, CHSC_START_SYNC, &scmd_area);
+		if (rc != 0) {
+			rc = -errno;
+			warnx("Failed to get card measurement data: %s",
+			      strerror(errno));
+			break;
+		}
+
+		rc = chsc_error_from_response(scmd_area.response.header.code);
+		if (rc != 0) {
+			warnx("Failed to get card crypto measurement data: %s",
+			      strerror(-rc));
+			break;
+		}
+
+		rc = process_card_measurement_data(&scmd_area, &last_card);
+		if (rc != 0)
+			break;
+
+		if (scmd_area.response.p)
+			scmd_area.request.fcs = last_card + 1;
+	} while (scmd_area.response.p && last_card < g.max_card);
+
+	return rc;
+}
+
+/*
+ * Signal handler for SIGALRM
+ */
+static void alarm_handler(int UNUSED(sig))
+{
+	if (!quit)
+		alarm(g.interval);
+}
+
+/*
+ * Signal handler for SIGINT and SIGTERM
+ */
+static void int_handler(int UNUSED(sig))
+{
+	quit = true;
+	raise(SIGALRM);
+}
+
+/*
+ * Calculates the time difference between tv1 and tv2 in seconds
+ */
+static float time_diff(struct timeval *tv1, struct timeval *tv2)
+{
+	struct timeval tv_diff;
+
+	tv_diff.tv_sec = tv2->tv_sec - tv1->tv_sec;
+	tv_diff.tv_usec = tv2->tv_usec - tv1->tv_usec;
+	if (tv_diff.tv_usec < 0) {
+		tv_diff.tv_sec--;
+		tv_diff.tv_usec += 1000000;
+	}
+
+	return (float)tv_diff.tv_sec +  (float)(0.000001 * tv_diff.tv_usec);
+}
+
+/*
+ * Initialize the default print format
+ */
+static int default_print_initialize(void)
+{
+	g.device_rec = util_rec_new_wide("-");
+	util_rec_def(g.device_rec, "device", UTIL_REC_ALIGN_LEFT, 7,
+		     "DEVICE");
+	util_rec_def(g.device_rec, "kind", UTIL_REC_ALIGN_LEFT, 5, "");
+	util_rec_def(g.device_rec, "type", UTIL_REC_ALIGN_LEFT, 33,
+		     "TYPE");
+	util_rec_def(g.device_rec, "time", UTIL_REC_ALIGN_LEFT, 20,
+			     "TIMESTAMP");
+
+	g.counter_rec = util_rec_new_wide("-");
+	util_rec_def(g.counter_rec, "name", UTIL_REC_ALIGN_LEFT, 18,
+		     "COUNTER");
+	util_rec_def(g.counter_rec, "ops", UTIL_REC_ALIGN_RIGHT, 10,
+		     "OPS");
+	util_rec_def(g.counter_rec, "rate", UTIL_REC_ALIGN_RIGHT, 12,
+		     "RATE");
+	util_rec_def(g.counter_rec, "utilization", UTIL_REC_ALIGN_RIGHT,
+		     12, "UTILIZATION");
+	util_rec_def(g.counter_rec, "duration", UTIL_REC_ALIGN_RIGHT,
+		     15, "AVG.DURATION");
+
+	return 0;
+}
+
+/*
+ * Terminate the default print format
+ */
+static int default_print_terminate(void)
+{
+	util_rec_free(g.counter_rec);
+	util_rec_free(g.device_rec);
+
+	return 0;
+}
+
+/*
+ * Print the header lines for the default print format
+ */
+static int default_print_header(void)
+{
+	char timestamp[64];
+	struct utsname un;
+	struct tm *tm;
+	time_t t;
+
+	time(&t);
+	tm = localtime(&t);
+	strftime(timestamp, sizeof(timestamp), "%x", tm);
+
+	if (uname(&un) != 0)
+		return -errno;
+
+	printf("%s %s (%s) \t%s\t%s\n\n", un.sysname, un.release,
+		 un.nodename, timestamp, un.machine);
+
+	return 0;
+}
+
+/*
+ * Print the footer lines for the default print format
+ */
+static int default_print_footer(void)
+{
+	printf("\n");
+	return 0;
+}
+
+/*
+ * Print the interval header lines for the default print format
+ */
+static int default_print_interval_header(unsigned long interval_count,
+					 const char *timestamp)
+{
+
+	printf("*****************************************************"
+	       "***************\n");
+	printf("TIME: %s\t\tINTERVAL: %lu\n\n", timestamp, interval_count);
+	return 0;
+}
+
+/*
+ * Prints the separator lines in front of a device for the default print format
+ */
+static int default_print_device_header(bool is_apqn, uint8_t card,
+				       uint8_t domain, const char *type,
+				       const char *timestamp)
+{
+	if (is_apqn)
+		util_rec_set(g.device_rec, "device", "%02x.%04x", card,
+			    domain);
+	else
+		util_rec_set(g.device_rec, "device", "%02x", card);
+	util_rec_set(g.device_rec, "kind", "%s",
+		     is_apqn ? "APQN" : "CARD");
+	util_rec_set(g.device_rec, "type", "%s", type);
+	util_rec_set(g.device_rec, "time", "%s", timestamp);
+
+	util_rec_print_hdr(g.device_rec);
+	util_rec_print(g.device_rec);
+	printf("\n");
+
+	util_rec_set_indent(g.counter_rec, is_apqn ? 8 : 4);
+	util_rec_print_hdr(g.counter_rec);
+
+	return 0;
+}
+
+/*
+ * Prints the separator lines after a device for the default print format
+ */
+static int default_print_device_footer(void)
+{
+
+	printf("\n");
+	return 0;
+}
+
+
+/**
+ * Prints the counter data for the default print format
+ */
+static int default_print_counter_data(bool UNUSED(is_apqn),
+				      uint8_t UNUSED(card),
+				      uint8_t UNUSED(domain),
+				      const char *UNUSED(type),
+				      const char *UNUSED(timestamp),
+				      const char *name,
+				      struct interval_values *vals)
+{
+	util_rec_set(g.counter_rec, "name", "%s", name);
+	util_rec_set(g.counter_rec, "ops", "%llu", vals->count);
+	util_rec_set(g.counter_rec, "rate", "%.2f", vals->rate);
+	util_rec_set(g.counter_rec, "utilization", "%.2f %%",
+			vals->utilization * 100);
+	if (vals->duration >= 1)
+		util_rec_set(g.counter_rec, "duration", "%.3f sec ",
+			     vals->duration);
+	else if (vals->duration >= 0.001)
+		util_rec_set(g.counter_rec, "duration", "%.3f msec",
+			     vals->duration * 1000);
+	else
+		util_rec_set(g.counter_rec, "duration", "%.3f usec",
+			     vals->duration * 1000000);
+	util_rec_print(g.counter_rec);
+
+	return 0;
+}
+
+/*
+ * Prints a separator between the counter lines and the totals line for the
+ * default print format
+ */
+static int default_print_counter_separator(void)
+{
+	util_rec_print_separator(g.counter_rec);
+	return 0;
+}
+
+/*
+ * Initialize the JSON print format
+ */
+static int json_print_initialize(void)
+{
+	/* Use a decimal point to make JSON code compliant with RFC7159 */
+	setlocale(LC_NUMERIC, "C");
+	return 0;
+}
+
+/*
+ * Print the header lines for the JSON print format
+ */
+static int json_print_header(void)
+{
+	char timestamp[64];
+	struct utsname un;
+	struct tm *tm;
+	time_t t;
+
+	time(&t);
+	tm = localtime(&t);
+	strftime(timestamp, sizeof(timestamp), "%x", tm);
+
+	if (uname(&un) != 0)
+		return -errno;
+
+	printf("{\"zcryptstats\": {\n");
+	printf("\t\"host\": {\n");
+	printf("\t\t\"nodename\": \"%s\",\n", un.nodename);
+	printf("\t\t\"sysname\": \"%s\",\n", un.sysname);
+	printf("\t\t\"release\": \"%s\",\n", un.release);
+	printf("\t\t\"machine\": \"%s\",\n", un.machine);
+	printf("\t\t\"date\": \"%s\",\n", timestamp);
+	printf("\t\t\"statistics\": [\n");
+
+	return 0;
+}
+
+/*
+ * Print the footer lines for the JSON print format
+ */
+static int json_print_footer(void)
+{
+	printf("\n\t\t]\n");
+	printf("\t}\n");
+	printf("}}\n");
+
+	return 0;
+}
+
+/*
+ * Print the interval header lines for the JSON print format
+ */
+static int json_print_interval_header(unsigned long interval_count,
+				      const char *timestamp)
+{
+	if (interval_count > 1)
+		printf(",\n");
+	printf("\t\t\t{\n");
+	printf("\t\t\t\t\"interval\": %lu, \"timestamp\": \"%s\","
+	       " \"devices\": [\n", interval_count, timestamp);
+
+	return 0;
+}
+
+/*
+ * Print the interval footer lines for the JSON print format
+ */
+static int json_print_interval_footer(void)
+{
+	if (!g.first_device)
+		printf("\n");
+	printf("\t\t\t\t]\n");
+	printf("\t\t\t}");
+
+	return 0;
+}
+
+/*
+ * Prints the separator lines in front of a device for the JSON print format
+ */
+static int json_print_device_header(bool is_apqn, uint8_t card,
+				    uint8_t domain, const char *type,
+				    const char *UNUSED(timestamp))
+{
+	if (!g.first_device)
+		printf(",\n");
+	printf("\t\t\t\t\t{");
+	if (is_apqn)
+		printf("\"device\": \"%02x.%04x\"", card,
+		       domain);
+	else
+		printf("\"device\": \"%02x\"", card);
+	printf(", \"type\": \"%s\",\n", type);
+	printf("\t\t\t\t\t \"counters\": [\n");
+
+	return 0;
+}
+
+/*
+ * Prints the separator lines after a device for the JSON print format
+ */
+static int json_print_device_footer(void)
+{
+	if (!g.first_counter)
+		printf("\n");
+	printf("\t\t\t\t\t ]}");
+
+	return 0;
+}
+
+
+/**
+ * Prints the counter data for the JSON print format
+ */
+static int json_print_counter_data(bool UNUSED(is_apqn),
+				   uint8_t UNUSED(card),
+				   uint8_t UNUSED(domain),
+				   const char *UNUSED(type),
+				   const char *UNUSED(timestamp),
+				   const char *name,
+				   struct interval_values *vals)
+{
+	if (!g.first_counter)
+		printf(",\n");
+	printf("\t\t\t\t\t\t{\"counter\": \"%s\", \"ops\": %llu, "
+	       "\"rate\": %.2f, \"utilization\": %.2f, \"duration\": %.9f}",
+	       name, vals->count, vals->rate, vals->utilization * 100,
+	       vals->duration);
+
+	return 0;
+}
+
+static int table_print_initialize(void)
+{
+	g.counter_rec = util_rec_new_wide("-");
+	util_rec_def(g.counter_rec, "time", UTIL_REC_ALIGN_LEFT, 20,
+			     "TIMESTAMP");
+	util_rec_def(g.counter_rec, "device", UTIL_REC_ALIGN_LEFT, 7,
+		     "DEVICE");
+	util_rec_def(g.counter_rec, "ops", UTIL_REC_ALIGN_RIGHT, 10,
+		     "OPS");
+	util_rec_def(g.counter_rec, "rate", UTIL_REC_ALIGN_RIGHT, 12,
+		     "RATE");
+	util_rec_def(g.counter_rec, "utilization", UTIL_REC_ALIGN_RIGHT,
+		     12, "UTILIZATION");
+	util_rec_def(g.counter_rec, "duration", UTIL_REC_ALIGN_RIGHT,
+		     15, "AVG.DURATION");
+	return 0;
+}
+
+static int table_print_terminate(void)
+{
+	util_rec_free(g.counter_rec);
+	return 0;
+}
+
+static int table_print_header(void)
+{
+	int rc;
+
+	rc = default_print_header();
+	if (rc != 0)
+		return rc;
+
+	util_rec_print_hdr(g.counter_rec);
+	return 0;
+}
+
+static int table_print_interval_footer(void)
+{
+	util_rec_print_separator(g.counter_rec);
+	return 0;
+}
+
+static int table_print_counter_data(bool is_apqn, uint8_t card, uint8_t domain,
+				    const char *UNUSED(type),
+				    const char *timestamp,
+				    const char *UNUSED(name),
+				    struct interval_values *vals)
+{
+	if (is_apqn)
+		util_rec_set(g.counter_rec, "device", "%02x.%04x", card,
+			    domain);
+	else
+		util_rec_set(g.counter_rec, "device", "%02x", card);
+	util_rec_set(g.counter_rec, "time", "%s", timestamp);
+
+	util_rec_set(g.counter_rec, "ops", "%llu", vals->count);
+	util_rec_set(g.counter_rec, "rate", "%.2f", vals->rate);
+	util_rec_set(g.counter_rec, "utilization", "%.2f %%",
+		     vals->utilization * 100);
+	if (vals->duration >= 1)
+		util_rec_set(g.counter_rec, "duration", "%.3f sec ",
+			     vals->duration);
+	else if (vals->duration >= 0.001)
+		util_rec_set(g.counter_rec, "duration", "%.3f msec",
+			     vals->duration * 1000);
+	else
+		util_rec_set(g.counter_rec, "duration", "%.3f usec",
+			     vals->duration * 1000000);
+
+	util_rec_print(g.counter_rec);
+
+	return 0;
+}
+
+static int csv_print_initialize(void)
+{
+	/* Use a decimal point to not conflict with the colon separator char */
+	setlocale(LC_NUMERIC, "C");
+
+	g.counter_rec = util_rec_new_csv(",");
+	util_rec_def(g.counter_rec, "time", UTIL_REC_ALIGN_LEFT, 20,
+			     "TIMESTAMP");
+	util_rec_def(g.counter_rec, "device", UTIL_REC_ALIGN_LEFT, 7,
+		     "DEVICE");
+	util_rec_def(g.counter_rec, "ops", UTIL_REC_ALIGN_RIGHT, 10,
+		     "OPS");
+	util_rec_def(g.counter_rec, "rate", UTIL_REC_ALIGN_RIGHT, 12,
+		     "RATE");
+	util_rec_def(g.counter_rec, "utilization", UTIL_REC_ALIGN_RIGHT,
+		     12, "UTILIZATION");
+	util_rec_def(g.counter_rec, "duration", UTIL_REC_ALIGN_RIGHT,
+		     15, "AVG.DURATION");
+	return 0;
+}
+
+static int csv_print_terminate(void)
+{
+	util_rec_free(g.counter_rec);
+	return 0;
+}
+
+static int csv_print_header(void)
+{
+	util_rec_print_hdr(g.counter_rec);
+	return 0;
+}
+
+
+static int csv_print_counter_data(bool is_apqn, uint8_t card, uint8_t domain,
+				  const char *UNUSED(type),
+				  const char *timestamp,
+				  const char *UNUSED(name),
+				  struct interval_values *vals)
+{
+	if (is_apqn)
+		util_rec_set(g.counter_rec, "device", "%02x.%04x", card,
+			    domain);
+	else
+		util_rec_set(g.counter_rec, "device", "%02x", card);
+	util_rec_set(g.counter_rec, "time", "%s", timestamp);
+	util_rec_set(g.counter_rec, "ops", "%llu", vals->count);
+	util_rec_set(g.counter_rec, "rate", "%.2f", vals->rate);
+	util_rec_set(g.counter_rec, "utilization", "%.2f %%",
+		     vals->utilization * 100);
+	util_rec_set(g.counter_rec, "duration", "%.9f", vals->duration);
+
+	util_rec_print(g.counter_rec);
+
+	return 0;
+}
+
+/*
+ * Calculates number of ops, utilization, duration and rate of an
+ * interval from the timer values, scale and interval time.
+ */
+static void calc_interval_values(struct chsc_cmb_entry *current,
+				 struct chsc_cmb_entry *previous,
+				 float scale,
+				 float interval_time,
+				 struct interval_values *result)
+{
+	u64 tdiff;
+
+	tdiff = current->t - previous->t;
+	result->count = current->c - previous->c;
+	result->utilization = (double)(tdiff) * scale / interval_time;
+	if (result->count > 0)
+		result->duration = (double)(tdiff) * scale / result->count;
+	else
+		result->duration = 0;
+	result->rate = (double)result->count / interval_time;
+}
+
+/*
+ * Print the measurement data of an interval
+ */
+static int print_interval_data(struct interval_data *data,
+			       const char *timestamp, float interval_time)
+{
+	struct chsc_cmb_entry total_current;
+	struct chsc_cmb_entry total_previous;
+	struct interval_values vals;
+	const char *type, *counter;
+	uint32_t mask = 0x80000000;
+	bool totals_found = false;
+	size_t len;
+	int i, rc;
+
+	len = get_cmb_length(&data->current);
+	type = get_card_name(data->current.header.ct, data->current.header.mt);
+
+	rc = pr_call(print_device_header)(data->current.header.format == 1,
+					  data->current.header.ax,
+					  data->current.header.dx, type,
+					  timestamp);
+	if (rc != 0)
+		return rc;
+
+	memset(&total_current, 0, sizeof(total_current));
+	memset(&total_previous, 0, sizeof(total_previous));
+
+	g.first_counter = true;
+
+	for (i = 0; i < 32 &&
+	     offsetofend(struct chsc_cmb_area, entries[i]) <= len; i++) {
+		if (data->current.header.v & mask) {
+			if (is_counter_totals(data->current.header.ct,
+					      data->current.header.mt, i)) {
+				total_current.t = data->current.entries[i].t;
+				total_current.c = data->current.entries[i].c;
+				total_previous.t = data->previous.entries[i].t;
+				total_previous.c = data->previous.entries[i].c;
+				totals_found = true;
+			} else if (!totals_found) {
+				total_current.t += data->current.entries[i].t;
+				total_current.c += data->current.entries[i].c;
+				total_previous.t += data->previous.entries[i].t;
+				total_previous.c += data->previous.entries[i].c;
+			}
+
+			if (g.only_totals)
+				continue;
+
+			calc_interval_values(&data->current.entries[i],
+					     &data->previous.entries[i],
+					     data->current.header.s,
+					     interval_time,
+					     &vals);
+
+			counter = get_counter_name(data->current.header.ct,
+						   data->current.header.mt, i);
+
+			rc = pr_call(print_counter_data)(
+					data->current.header.format == 1,
+					data->current.header.ax,
+					data->current.header.dx, type,
+					timestamp, counter, &vals);
+			if (rc != 0)
+				break;
+
+			g.first_counter = false;
+		}
+		mask >>= 1;
+	}
+
+	if (!g.no_totals) {
+		rc = pr_call(print_counter_separator)();
+		if (rc != 0)
+			return rc;
+
+		calc_interval_values(&total_current, &total_previous,
+				     data->current.header.s, interval_time,
+				     &vals);
+
+		rc = pr_call(print_counter_data)(
+				data->current.header.format == 1,
+				data->current.header.ax,
+				data->current.header.dx, type,
+				timestamp, "Total", &vals);
+		if (rc != 0)
+			return rc;
+	}
+
+	rc = pr_call(print_device_footer)();
+	if (rc != 0)
+		return rc;
+
+	return 0;
+}
+
+/*
+ * Print the measured data
+ */
+static int print_measurement_data(unsigned long interval_count,
+				  float interval_time, const char *timestamp)
+{
+	bool header_printed = false;
+	struct interval_data *dd;
+	struct card_data *cd;
+	int card, domain, rc;
+
+	g.first_device = true;
+	for (card = 0; card < NUM_CARDS; card++) {
+		cd = g.cards[card];
+		if (cd == NULL)
+			continue;
+
+		if (!cd->data.current_valid) {
+			/* Not update in last interval -> free it */
+			free_card_data(cd);
+			g.cards[card] = NULL;
+
+			pr_verbose("Card %02x removed", card);
+			continue;
+		}
+
+		if (cd->data.previous_valid) {
+			if (memcmp(&cd->data.current.header,
+				   &cd->data.previous.header,
+				   sizeof(struct chsc_cmb_header)) != 0) {
+				free_card_data(cd);
+				g.cards[card] = NULL;
+
+				pr_verbose("CMB header mismatch, card %02x "
+					   "removed", card);
+				continue;
+			}
+
+			if (!header_printed) {
+				rc = pr_call(print_interval_header)(
+						interval_count, timestamp);
+				if (rc != 0)
+					return rc;
+				header_printed = true;
+			}
+
+			rc = print_interval_data(&cd->data, timestamp,
+						 interval_time);
+			if (rc != 0)
+				return rc;
+
+			g.first_device = false;
+		}
+
+		cd->data.current_valid = false;
+
+		for (domain = 0; domain < NUM_DOMAINS; domain++) {
+			dd = cd->domains[domain];
+			if (dd == NULL)
+				continue;
+
+			if (!dd->current_valid) {
+				/* Not update in last interval -> free it */
+				free(dd);
+				cd->domains[domain] = NULL;
+
+				pr_verbose("APQN %02x.%04x removed", card,
+					   domain);
+			}
+
+			if (dd->previous_valid) {
+				if (memcmp(&dd->current.header,
+					   &dd->previous.header,
+					   sizeof(struct chsc_cmb_header))) {
+					free(dd);
+					cd->domains[domain] = NULL;
+
+					pr_verbose("CMB header mismatch, APQN "
+						   "%02x.%04x removed", card,
+						   domain);
+					continue;
+				}
+
+				rc = print_interval_data(dd, timestamp,
+							 interval_time);
+				if (rc != 0)
+					return rc;
+			}
+
+			dd->current_valid = false;
+		}
+	}
+
+	if (header_printed) {
+		rc = pr_call(print_interval_footer)();
+		if (rc != 0)
+			return rc;
+	} else if (interval_count > 0) {
+		pr_verbose("No data was reported in this interval");
+		warnx("Failed to get card crypto measurement data: %s",
+		      strerror(ENODEV));
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+/*
+ * Perform the measurement in intervals
+ */
+static int perform_measurement(void)
+{
+	struct timeval tv_current, tv_previous;
+	struct sigaction alrm_act, int_act;
+	unsigned long interval_count = 0;
+	float interval_time;
+	char timestamp[64];
+	struct tm *tm;
+	int rc;
+
+	/* Set a handler for SIGINT/SIGTERM */
+	memset(&int_act, 0, sizeof(int_act));
+	int_act.sa_handler = int_handler;
+	sigaction(SIGINT, &int_act, NULL);
+	sigaction(SIGTERM, &int_act, NULL);
+
+	/* Set a handler for SIGALRM */
+	memset(&alrm_act, 0, sizeof(alrm_act));
+	alrm_act.sa_handler = alarm_handler;
+	sigaction(SIGALRM, &alrm_act, NULL);
+
+	rc = pr_call(print_initialize)();
+	if (rc != 0)
+		return rc;
+
+	rc = pr_call(print_header)();
+	if (rc != 0)
+		return 0;
+
+	alarm(g.interval);
+
+	memset(&tv_current, 0, sizeof(tv_current));
+	while (!quit) {
+		pr_verbose("Interval %lu", interval_count);
+
+		tv_previous = tv_current;
+		rc = gettimeofday(&tv_current, NULL);
+		if (rc != 0)
+			break;
+
+		tm = localtime(&tv_current.tv_sec);
+		if (tm == NULL)
+			break;
+		strftime(timestamp, sizeof(timestamp), "%x %X", tm);
+		interval_time = time_diff(&tv_previous, &tv_current);
+
+		rc = get_card_measurement_data();
+		if (rc != 0)
+			break;
+
+		rc = print_measurement_data(interval_count, interval_time,
+					    timestamp);
+		if (rc != 0)
+			break;
+
+		if (g.count > 0 && interval_count >= g.count) {
+			pr_verbose("Interval limit reached");
+			break;
+		}
+		interval_count++;
+
+		if (quit)
+			break;
+
+		pause();
+	}
+
+	if (quit)
+		pr_verbose("Measurement stopped by user");
+
+	alarm(0);
+	memset(&alrm_act, 0, sizeof(alrm_act));
+	alrm_act.sa_handler = SIG_DFL;
+	sigaction(SIGALRM, &alrm_act, NULL);
+
+	rc = pr_call(print_footer)();
+	if (rc != 0)
+		return 0;
+
+	rc = pr_call(print_terminate)();
+	if (rc != 0)
+		return rc;
+
+	return 0;
+}
+
+/*
+ * Parse the type mapping specification:
+ * TYPE:MODE=TYPE:MODE[,TYPE:MODE=TYPE:MODE[,...]]
+ */
+static int parse_type_mapping(char *mapping)
+{
+	unsigned int from_type, to_type, from_mode, to_mode;
+	struct type_mapping *map;
+	char *tok;
+
+	tok = strtok(mapping, ",");
+	while (tok != NULL) {
+		if (sscanf(tok, "%u:%u=%u:%u", &from_type, &from_mode,
+			   &to_type, &to_mode) != 4) {
+			warnx("Invalid type mapping: %s", tok);
+			return -EINVAL;
+		}
+
+		pr_verbose("from_type: %u from_mode: %u to_type: %u "
+			   "to_mode: %u", from_type, from_mode, to_type,
+			   to_mode);
+
+		if (from_type < NUM_CRYPTO_TYPES &&
+		    crypto_types[from_type].name != NULL &&
+		    from_mode < crypto_types[from_type].num_modes &&
+		    crypto_types[from_type].modes[from_mode].counters != NULL) {
+			warnx("Cannot map a known type/mode to another "
+			      "type/mode: %s", tok);
+			return -EINVAL;
+		}
+
+		if (to_type >= NUM_CRYPTO_TYPES ||
+		    crypto_types[to_type].name == NULL ||
+		    to_mode >= crypto_types[to_type].num_modes ||
+		    crypto_types[to_type].modes[to_mode].counters == NULL) {
+			warnx("Cannot map a type/mode to an unknown "
+			      "type/mode: %s", tok);
+			return -EINVAL;
+		}
+
+		map = util_malloc(sizeof(struct type_mapping));
+		map->from_type = from_type;
+		map->from_mode = from_mode;
+		map->to_type = to_type;
+		map->to_mode = to_mode;
+
+		map->next = g.type_mapping;
+		g.type_mapping = map;
+
+		tok = strtok(NULL, ",");
+	}
+
+	return 0;
+}
+
+static void free_type_mapping(void)
+{
+	struct type_mapping *map = g.type_mapping;
+	struct type_mapping *next;
+
+	while (map != NULL) {
+		next = map->next;
+		free(map);
+		map = next;
+	}
+}
+
+static int add_device_selection(const char *device_id)
+{
+	int card = -1, domain = -1;
+	struct device_selection *dev;
+
+	pr_verbose("device_id: '%s'", device_id);
+
+	/* check for 'card[.domain]' specification */
+	if (sscanf(device_id, "%x.%x", &card, &domain) >= 1) {
+		pr_verbose("card: %d domain: %d", card, domain);
+		if (card < 0 || card > g.max_card_used) {
+			warnx("Invalid card specified: %s", device_id);
+			return -EINVAL;
+		}
+		g.card_mask[MASK_WORD_NO(card)] |= MASK_BIT(card);
+		g.min_card = MIN(g.min_card, card);
+		g.max_card = MAX(g.max_card, card);
+
+		if (domain >= 0) {
+			if (domain > NUM_DOMAINS) {
+				warnx("Invalid domain specified: %s",
+				      device_id);
+				return -EINVAL;
+			}
+			g.domain_mask[MASK_WORD_NO(domain)] |=
+					MASK_BIT(domain);
+			g.min_domain = MIN(g.max_domain, domain);
+			g.max_domain = MAX(g.max_domain, domain);
+		} else {
+			memset(g.domain_mask, 0xff, sizeof(g.domain_mask));
+			g.min_domain = 0;
+			g.max_domain = NUM_DOMAINS - 1;
+		}
+
+		dev = util_malloc(sizeof(struct device_selection));
+		dev->card = card;
+		dev->domain = domain;
+		dev->next = g.dev_selection;
+		g.dev_selection = dev;
+
+		return 0;
+	}
+	/* check for '.domain' specification */
+	if (device_id[0] == '.' &&
+	    sscanf(device_id + 1, "%x", &domain) == 1) {
+		pr_verbose("domain: %d", domain);
+		if (domain < 0 || domain > NUM_DOMAINS) {
+			warnx("Invalid domain specified: %s", device_id);
+			return -EINVAL;
+		}
+
+		g.domain_mask[MASK_WORD_NO(domain)] |= MASK_BIT(domain);
+		g.min_domain = MIN(g.max_domain, domain);
+		g.max_domain = MAX(g.max_domain, domain);
+		memset(g.card_mask, 0xff, sizeof(g.card_mask));
+		g.min_card = 0;
+		g.max_card = g.max_card_used;
+
+		dev = util_malloc(sizeof(struct device_selection));
+		dev->card = -1;
+		dev->domain = domain;
+		dev->next = g.dev_selection;
+		g.dev_selection = dev;
+
+		return 0;
+	}
+
+	warnx("Invalid device ID specified: %s", device_id);
+	return -EINVAL;
+}
+
+/*
+ * Frees the device selection list
+ */
+static void free_device_selection(void)
+{
+	struct device_selection *dev = g.dev_selection;
+	struct device_selection *next;
+
+	while (dev != NULL) {
+		next = dev->next;
+		free(dev);
+
+		dev = next;
+	}
+}
+
+/*
+ * Build the AP and domain selection mask from the specified device ID's.
+ */
+static int parse_device_selection(void)
+{
+	int i, rc;
+
+	g.min_card = NUM_CARDS - 1;
+	g.max_card = 0;
+	g.min_domain = NUM_DOMAINS - 1;
+	g.max_domain = 0;
+
+	for (i = 0; g.device_ids[i] != NULL; i++) {
+		rc = add_device_selection(g.device_ids[i]);
+		if (rc != 0)
+			return rc;
+	}
+
+	if (i == 0) {
+		/* No device-IDs specified */
+		memset(g.card_mask, 0xff, sizeof(g.card_mask));
+		g.min_card = 0;
+		g.max_card = g.max_card_used;
+		memset(g.domain_mask, 0xff, sizeof(g.domain_mask));
+		g.min_domain = 0;
+		g.max_domain = NUM_DOMAINS - 1;
+	}
+
+	pr_verbose("Min card: %u, max card: %u", g.min_card, g.max_card);
+	pr_verbose("Min domain: %u, max domain: %u", g.min_domain,
+		   g.max_domain);
+
+	return 0;
+}
+
+/*
+ * Entry point
+ */
+int main(int argc, char *argv[])
+{
+	char *endp;
+	int c, rc;
+
+	util_prg_init(&prg);
+	util_opt_init(opt_vec, NULL);
+
+	while (1) {
+		c = util_opt_getopt_long(argc, argv);
+		if (c == -1)
+			break;
+		switch (c) {
+		case 'i':
+			g.interval = strtoll(optarg, &endp, 0);
+			if (*optarg == '\0' || *endp != '\0' ||
+			    g.interval <= 0 ||
+			    (g.interval == LLONG_MAX && errno == ERANGE)) {
+				warnx("Invalid value for '--interval'|"
+				      "'-i': '%s'", optarg);
+				util_prg_print_parse_error();
+				return EXIT_FAILURE;
+			}
+			break;
+		case 'c':
+			g.count = strtoull(optarg, &endp, 0);
+			if (*optarg == '\0' || *endp != '\0' || g.count == 0 ||
+			    (g.count == LLONG_MAX && errno == ERANGE)) {
+				warnx("Invalid value for '--count'|"
+				      "'-c': '%s'", optarg);
+				util_prg_print_parse_error();
+				return EXIT_FAILURE;
+			}
+			break;
+		case 'o':
+			if (strcasecmp(optarg, "JSON") == 0) {
+				g.print_funcs = &json_print;
+			} else if (strcasecmp(optarg, "TABLE") == 0) {
+				g.only_totals = true;
+				g.print_funcs = &table_print;
+			} else if (strcasecmp(optarg, "CSV") == 0) {
+				g.only_totals = true;
+				g.print_funcs = &csv_print;
+			} else {
+				warnx("Invalid value for '--output'|"
+				      "'-o': '%s'", optarg);
+				util_prg_print_parse_error();
+				return EXIT_FAILURE;
+			}
+			break;
+		case 't':
+			g.no_totals = true;
+			break;
+		case 'T':
+			g.only_totals = true;
+			break;
+		case 'a':
+			g.no_apqn = true;
+			break;
+		case 'M':
+			g.map_type = optarg;
+			break;
+		case 'A':
+			g.all = true;
+			break;
+		case 'O':
+			g.only_online = true;
+			break;
+		case 'V':
+			g.verbose = true;
+			break;
+		case 'h':
+			util_prg_print_help();
+			util_opt_print_help();
+			print_adapter_id_help();
+			return EXIT_SUCCESS;
+		case 'v':
+			util_prg_print_version();
+			return EXIT_SUCCESS;
+		default:
+			util_opt_print_parse_error(c, argv);
+			return EXIT_FAILURE;
+		}
+	}
+
+	/* remaining positional args are device IDs */
+	g.device_ids = &argv[optind];
+
+	if (g.only_totals && g.no_totals) {
+		warnx("Either --no-totals or --only-totals can be specified, "
+		      "but not both");
+		return EXIT_FAILURE;
+	}
+
+	if (g.only_online && g.all) {
+		warnx("Either --only-online or --all can be specified, "
+		      "but not both");
+		return EXIT_FAILURE;
+	}
+
+	pr_verbose("Interval: %ld Count: %ld", g.interval, g.count);
+
+	rc = get_max_card_index(&g.max_card_used);
+	if (rc != 0) {
+		rc = EXIT_FAILURE;
+		goto out;
+	}
+
+	rc = parse_device_selection();
+	if (rc != 0) {
+		rc = EXIT_FAILURE;
+		goto out;
+	}
+
+	if (g.map_type != NULL) {
+		rc = parse_type_mapping(g.map_type);
+		if (rc != 0) {
+			rc = EXIT_FAILURE;
+			goto out;
+		}
+	}
+
+	g.chsc_fd = open(CHSC_DEVICE, O_RDWR);
+	if (g.chsc_fd < 0) {
+		warnx("File '%s:' %s", CHSC_DEVICE, strerror(errno));
+		return EXIT_FAILURE;
+	}
+	pr_verbose("Device '%s' has been opened successfully", CHSC_DEVICE);
+
+	/* Don't buffer data if redirected to a pipe */
+	setbuf(stdout, NULL);
+
+	rc = perform_measurement();
+	if (rc != 0) {
+		rc = EXIT_FAILURE;
+		goto out;
+	}
+out:
+	if (g.chsc_fd >= 0)
+		close(g.chsc_fd);
+	free_device_selection();
+	free_type_mapping();
+	free_interval_data();
+
+	return rc;
+}
+
