// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) 2022 Solidigm. * * Author: leonardo.da.cunha@solidigm.com */ #include #include #include #include #include #include "common.h" #include "nvme.h" #include "libnvme.h" #include "plugin.h" #include "nvme-print.h" #include "solidigm-telemetry.h" #include "solidigm-telemetry/telemetry-log.h" #include "solidigm-telemetry/cod.h" #include "solidigm-telemetry/header.h" #include "solidigm-telemetry/config.h" #include "solidigm-telemetry/data-area.h" #include "solidigm-util.h" static int read_file2buffer(char *file_name, char **buffer, size_t *length) { FILE *fd = fopen(file_name, "rb"); if (!fd) return -errno; fseek(fd, 0, SEEK_END); size_t length_bytes = ftell(fd); fseek(fd, 0, SEEK_SET); *buffer = malloc(length_bytes); if (!*buffer) { fclose(fd); return -errno; } *length = fread(*buffer, 1, length_bytes, fd); fclose(fd); return 0; } struct config { __u32 host_gen; bool ctrl_init; int data_area; char *cfg_file; char *binary_file; }; static void cleanup_json_object(struct json_object **jobj_ptr) { json_free_object(*jobj_ptr); *jobj_ptr = NULL; } int solidigm_get_telemetry_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) { const char *desc = "Parse Solidigm Telemetry log"; const char *hgen = "Controls when to generate new host initiated report. Default value '1' generates new host initiated report, value '0' causes retrieval of existing log."; const char *cgen = "Gather report generated by the controller."; const char *dgen = "Pick which telemetry data area to report. Default is 3 to fetch areas 1-3. Valid options are 1, 2, 3, 4."; const char *cfile = "JSON configuration file"; const char *sfile = "binary file containing log dump"; bool has_binary_file = false; _cleanup_nvme_dev_ struct nvme_dev *dev = NULL; _cleanup_free_ struct nvme_telemetry_log *tlog = NULL; __attribute__((cleanup(cleanup_json_object))) struct json_object *configuration = NULL; __attribute__((cleanup(cleanup_json_object))) struct json_object *root = json_create_object(); struct telemetry_log tl = { .root = root, }; struct config cfg = { .host_gen = 1, .ctrl_init = false, }; OPT_ARGS(opts) = { OPT_UINT("host-generate", 'g', &cfg.host_gen, hgen), OPT_FLAG("controller-init", 'c', &cfg.ctrl_init, cgen), OPT_UINT("data-area", 'd', &cfg.data_area, dgen), OPT_FILE("config-file", 'j', &cfg.cfg_file, cfile), OPT_FILE("source-file", 's', &cfg.binary_file, sfile), OPT_INCR("verbose", 'v', &nvme_cfg.verbose, verbose), OPT_END() }; int err = argconfig_parse(argc, argv, desc, opts); if (err) { nvme_show_status(err); return err; } /* When not selected on the command line, get minimum data area required */ if (!argconfig_parse_seen(opts, "data-area")) cfg.data_area = argconfig_parse_seen(opts, "config-file") ? 3 : 1; has_binary_file = argconfig_parse_seen(opts, "source-file"); if (has_binary_file) { // If a binary file is provided, we don't want to open a device. // GNU getopt() permutes the contents of argv as it scans, // so that eventually all the nonoptions are at the end. if (argc > optind) { errno = EINVAL; err = -errno; nvme_show_status(err); return err; } err = read_file2buffer(cfg.binary_file, (char **)&tlog, &tl.log_size); } else { err = parse_and_open(&dev, argc, argv, desc, opts); } if (err) { nvme_show_status(err); return err; } if (cfg.host_gen > 1) { SOLIDIGM_LOG_WARNING("Invalid host-generate value '%d'", cfg.host_gen); err = -EINVAL; nvme_show_status(err); return err; } if (argconfig_parse_seen(opts, "config-file")) { _cleanup_free_ char *conf_str = NULL; size_t length = 0; err = read_file2buffer(cfg.cfg_file, &conf_str, &length); if (err) { nvme_show_status(err); return err; } struct json_tokener *jstok = json_tokener_new(); configuration = json_tokener_parse_ex(jstok, conf_str, length); if (jstok->err != json_tokener_success) { SOLIDIGM_LOG_WARNING("Parsing error on JSON configuration file %s: %s (at offset %d)", cfg.cfg_file, json_tokener_error_desc(jstok->err), jstok->char_offset); json_tokener_free(jstok); err = EINVAL; return err; } json_tokener_free(jstok); tl.configuration = configuration; } if (!has_binary_file) { size_t max_data_tx; size_t power2; __u8 mdts = 0; err = nvme_get_telemetry_max(dev_fd(dev), NULL, &max_data_tx); if (err < 0) { SOLIDIGM_LOG_WARNING("identify_ctrl: %s", nvme_strerror(errno)); return err; } else if (err > 0) { nvme_show_status(err); SOLIDIGM_LOG_WARNING("Failed to acquire identify ctrl %d!", err); return err; } power2 = max_data_tx / NVME_LOG_PAGE_PDU_SIZE; while (power2 && !(1 & power2)) { power2 >>= 1; mdts++; } err = sldgm_dynamic_telemetry(dev_fd(dev), cfg.host_gen, cfg.ctrl_init, true, mdts, cfg.data_area, &tlog, &tl.log_size); if (err < 0) { SOLIDIGM_LOG_WARNING("get-telemetry-log: %s", nvme_strerror(errno)); return err; } else if (err > 0) { nvme_show_status(err); SOLIDIGM_LOG_WARNING("Failed to acquire telemetry log %d!", err); return err; } } tl.log = tlog; solidigm_telemetry_log_data_areas_parse(&tl, cfg.data_area); json_print_object(tl.root, NULL); printf("\n"); return err; }