/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 " "('') or a queue device ID (." "'). To filter all devices by domain, " "provide '.'. 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; }