diff --git a/plugins/sandisk/sandisk-nvme.c b/plugins/sandisk/sandisk-nvme.c index b6c092611e..d888788a94 100644 --- a/plugins/sandisk/sandisk-nvme.c +++ b/plugins/sandisk/sandisk-nvme.c @@ -28,6 +28,11 @@ #include "sandisk-utils.h" #include "plugins/wdc/wdc-nvme-cmds.h" +static __u8 ocp_C2_guid[SNDK_GUID_LENGTH] = { + 0x6D, 0x79, 0x9A, 0x76, 0xB4, 0xDA, 0xF6, 0xA3, + 0xE2, 0x4D, 0xB2, 0x8A, 0xAC, 0xF3, 0x1C, 0xD1 +}; + static int sndk_do_cap_telemetry_log(struct nvme_dev *dev, const char *file, __u32 bs, int type, int data_area) { @@ -547,9 +552,7 @@ static int sndk_drive_resize(int argc, char **argv, capabilities = sndk_get_drive_capabilities(r, dev); ret = sndk_get_pci_ids(r, dev, &device_id, &vendor_id); - if (((capabilities & SNDK_DRIVE_CAP_RESIZE) == SNDK_DRIVE_CAP_RESIZE) && - ((device_id == SNDK_NVME_SN861_DEV_ID_U2) || - (device_id == SNDK_NVME_SN861_DEV_ID_E3S))) { + if ((capabilities & SNDK_DRIVE_CAP_RESIZE_SN861) == SNDK_DRIVE_CAP_RESIZE_SN861) { ret = sndk_do_sn861_drive_resize(dev, cfg.size, &result); if (!ret) { @@ -571,18 +574,399 @@ static int sndk_drive_resize(int argc, char **argv, return ret; } +static void sndk_print_fw_act_history_log_normal(__u8 *data, int num_entries) +{ + int i, j; + char previous_fw[9]; + char new_fw[9]; + char commit_action_bin[8]; + char time_str[100]; + __u16 oldestEntryIdx = 0, entryIdx = 0; + uint64_t timestamp; + int fw_vers_len = 0; + const char *null_fw = "--------"; + + memset((void *)time_str, '\0', 100); + + if (data[0] == SNDK_NVME_GET_FW_ACT_HISTORY_C2_LOG_ID) { + printf(" Firmware Activate History Log\n"); + printf(" Power Cycle "); + printf("Previous New\n"); + printf(" Entry Timestamp Count "); + printf("Firmware Firmware Slot Action Result\n"); + printf(" ----- ----------------- ----------------- "); + printf("--------- --------- ----- ------ -------\n"); + + struct sndk_fw_act_history_log_format_c2 *fw_act_hist_log = + (struct sndk_fw_act_history_log_format_c2 *)(data); + + oldestEntryIdx = SNDK_MAX_NUM_ACT_HIST_ENTRIES; + if (num_entries == SNDK_MAX_NUM_ACT_HIST_ENTRIES) { + /* find lowest/oldest entry */ + for (i = 0; i < num_entries; i++) { + j = (i+1 == SNDK_MAX_NUM_ACT_HIST_ENTRIES) ? 0 : i+1; + if (le16_to_cpu( + fw_act_hist_log->entry[i].fw_act_hist_entries) > + le16_to_cpu( + fw_act_hist_log->entry[j].fw_act_hist_entries)) { + oldestEntryIdx = j; + break; + } + } + } + if (oldestEntryIdx == SNDK_MAX_NUM_ACT_HIST_ENTRIES) + entryIdx = 0; + else + entryIdx = oldestEntryIdx; + + for (i = 0; i < num_entries; i++) { + memset((void *)previous_fw, 0, 9); + memset((void *)new_fw, 0, 9); + memset((void *)commit_action_bin, 0, 8); + + memcpy(previous_fw, + (char *)& + (fw_act_hist_log->entry[entryIdx].previous_fw_version), + 8); + fw_vers_len = strlen((char *) + &(fw_act_hist_log->entry[entryIdx].current_fw_version)); + if (fw_vers_len > 1) + memcpy(new_fw, + (char *)& + (fw_act_hist_log->entry[entryIdx].current_fw_version), + 8); + else + memcpy(new_fw, null_fw, 8); + + printf("%5"PRIu16"", + (uint16_t)le16_to_cpu( + fw_act_hist_log->entry[entryIdx].fw_act_hist_entries)); + + timestamp = (0x0000FFFFFFFFFFFF & + le64_to_cpu( + fw_act_hist_log->entry[entryIdx].timestamp)); + printf(" "); + printf("%16"PRIu64"", timestamp); + printf(" "); + + printf("%16"PRIu64"", + (uint64_t)le64_to_cpu( + fw_act_hist_log->entry[entryIdx].power_cycle_count)); + printf(" "); + printf("%s", (char *)previous_fw); + printf(" "); + printf("%s", (char *)new_fw); + printf(" "); + printf("%2"PRIu8"", + (uint8_t)fw_act_hist_log->entry[entryIdx].slot_number); + printf(" "); + sndk_get_commit_action_bin( + fw_act_hist_log->entry[entryIdx].commit_action_type, + (char *)&commit_action_bin); + printf(" %s", (char *)commit_action_bin); + printf(" "); + if (!le16_to_cpu(fw_act_hist_log->entry[entryIdx].result)) + printf("pass"); + else + printf("fail #%d", + (uint16_t)le16_to_cpu( + fw_act_hist_log->entry[entryIdx].result)); + printf("\n"); + + entryIdx++; + if (entryIdx >= SNDK_MAX_NUM_ACT_HIST_ENTRIES) + entryIdx = 0; + } + } else + fprintf(stderr, "ERROR: SNDK: %s: Unknown log page\n", __func__); +} + +static void sndk_print_fw_act_history_log_json(__u8 *data, int num_entries) +{ + struct json_object *root = json_create_object(); + int i, j; + char previous_fw[9]; + char new_fw[9]; + char commit_action_bin[8]; + char fail_str[32]; + char time_str[100]; + char ext_time_str[20]; + uint64_t timestamp; + int fw_vers_len = 0; + + memset((void *)previous_fw, 0, 9); + memset((void *)new_fw, 0, 9); + memset((void *)commit_action_bin, 0, 8); + memset((void *)time_str, '\0', 100); + memset((void *)ext_time_str, 0, 20); + memset((void *)fail_str, 0, 11); + char *null_fw = "--------"; + __u16 oldestEntryIdx = 0, entryIdx = 0; + + if (data[0] == SNDK_NVME_GET_FW_ACT_HISTORY_C2_LOG_ID) { + struct sndk_fw_act_history_log_format_c2 *fw_act_hist_log = + (struct sndk_fw_act_history_log_format_c2 *)(data); + + oldestEntryIdx = SNDK_MAX_NUM_ACT_HIST_ENTRIES; + if (num_entries == SNDK_MAX_NUM_ACT_HIST_ENTRIES) { + /* find lowest/oldest entry */ + for (i = 0; i < num_entries; i++) { + j = (i+1 == SNDK_MAX_NUM_ACT_HIST_ENTRIES) ? 0 : i+1; + if (le16_to_cpu( + fw_act_hist_log->entry[i].fw_act_hist_entries) > + le16_to_cpu( + fw_act_hist_log->entry[j].fw_act_hist_entries)) { + oldestEntryIdx = j; + break; + } + } + } + if (oldestEntryIdx == SNDK_MAX_NUM_ACT_HIST_ENTRIES) + entryIdx = 0; + else + entryIdx = oldestEntryIdx; + + for (i = 0; i < num_entries; i++) { + memcpy(previous_fw, + (char *)& + (fw_act_hist_log->entry[entryIdx].previous_fw_version), + 8); + fw_vers_len = strlen((char *) + &(fw_act_hist_log->entry[entryIdx].current_fw_version)); + if (fw_vers_len > 1) + memcpy(new_fw, + (char *)& + (fw_act_hist_log->entry[entryIdx].current_fw_version), + 8); + else + memcpy(new_fw, null_fw, 8); + + json_object_add_value_int(root, "Entry", + le16_to_cpu(fw_act_hist_log->entry[entryIdx].fw_act_hist_entries)); + + timestamp = (0x0000FFFFFFFFFFFF & + le64_to_cpu( + fw_act_hist_log->entry[entryIdx].timestamp)); + json_object_add_value_uint64(root, "Timestamp", timestamp); + + json_object_add_value_int(root, "Power Cycle Count", + le64_to_cpu( + fw_act_hist_log->entry[entryIdx].power_cycle_count)); + json_object_add_value_string(root, "Previous Firmware", + previous_fw); + json_object_add_value_string(root, "New Firmware", + new_fw); + json_object_add_value_int(root, "Slot", + fw_act_hist_log->entry[entryIdx].slot_number); + + sndk_get_commit_action_bin( + fw_act_hist_log->entry[entryIdx].commit_action_type, + (char *)&commit_action_bin); + json_object_add_value_string(root, "Action", commit_action_bin); + + if (!le16_to_cpu(fw_act_hist_log->entry[entryIdx].result)) { + json_object_add_value_string(root, "Result", "pass"); + } else { + sprintf((char *)fail_str, "fail #%d", + (int)(le16_to_cpu( + fw_act_hist_log->entry[entryIdx].result))); + json_object_add_value_string(root, "Result", fail_str); + } + + json_print_object(root, NULL); + printf("\n"); + + entryIdx++; + if (entryIdx >= SNDK_MAX_NUM_ACT_HIST_ENTRIES) + entryIdx = 0; + } + } else + fprintf(stderr, "ERROR: SNDK: %s: Unknown log page\n", __func__); + + json_free_object(root); +} + +static int sndk_print_fw_act_history_log(__u8 *data, int num_entries, int fmt) +{ + if (!data) { + fprintf(stderr, "ERROR: SNDK: Invalid buffer in print_fw act_history_log\n"); + return -1; + } + + switch (fmt) { + case NORMAL: + sndk_print_fw_act_history_log_normal(data, num_entries); + break; + case JSON: + sndk_print_fw_act_history_log_json(data, num_entries); + break; + } + return 0; +} + +static int sndk_get_fw_act_history_C2(nvme_root_t r, struct nvme_dev *dev, + char *format) +{ + struct sndk_fw_act_history_log_format_c2 *fw_act_history_log; + __u32 tot_entries = 0, num_entries = 0; + nvme_print_flags_t fmt; + __u8 *data; + int ret; + bool c2GuidMatch = false; + + if (!sndk_check_device(r, dev)) + return -1; + + ret = validate_output_format(format, &fmt); + if (ret < 0) { + fprintf(stderr, "ERROR: SNDK: invalid output format\n"); + return ret; + } + + data = (__u8 *)malloc(sizeof(__u8) * SNDK_FW_ACT_HISTORY_C2_LOG_BUF_LEN); + if (!data) { + fprintf(stderr, "ERROR: SNDK: malloc: %s\n", strerror(errno)); + return -1; + } + + memset(data, 0, sizeof(__u8) * SNDK_FW_ACT_HISTORY_C2_LOG_BUF_LEN); + + ret = nvme_get_log_simple(dev_fd(dev), + SNDK_NVME_GET_FW_ACT_HISTORY_C2_LOG_ID, + SNDK_FW_ACT_HISTORY_C2_LOG_BUF_LEN, data); + + if (strcmp(format, "json")) + nvme_show_status(ret); + + if (!ret) { + /* Get the log page data and verify the GUID */ + fw_act_history_log = (struct sndk_fw_act_history_log_format_c2 *)(data); + + c2GuidMatch = !memcmp(ocp_C2_guid, + fw_act_history_log->log_page_guid, + SNDK_GUID_LENGTH); + + if (c2GuidMatch) { + /* parse the data */ + tot_entries = le32_to_cpu(fw_act_history_log->num_entries); + + if (tot_entries > 0) { + num_entries = (tot_entries < SNDK_MAX_NUM_ACT_HIST_ENTRIES) ? + tot_entries : SNDK_MAX_NUM_ACT_HIST_ENTRIES; + ret = sndk_print_fw_act_history_log(data, num_entries, + fmt); + } else { + fprintf(stderr, "INFO: SNDK: No entries found.\n"); + ret = 0; + } + } else { + fprintf(stderr, "ERROR: SNDK: Invalid C2 log page GUID\n"); + ret = -1; + } + } else { + fprintf(stderr, "ERROR: SNDK: Unable to read FW Activate History Log Page data\n"); + ret = -1; + } + + free(data); + return ret; +} + + static int sndk_vs_fw_activate_history(int argc, char **argv, struct command *command, struct plugin *plugin) { - return run_wdc_vs_fw_activate_history(argc, argv, command, plugin); + const char *desc = "Retrieve FW activate history table."; + __u64 capabilities = 0; + struct nvme_dev *dev; + nvme_root_t r; + int ret = -1; + + struct config { + char *output_format; + }; + + struct config cfg = { + .output_format = "normal", + }; + + OPT_ARGS(opts) = { + OPT_FMT("output-format", 'o', &cfg.output_format, "Output Format: normal|json"), + OPT_END() + }; + + ret = parse_and_open(&dev, argc, argv, desc, opts); + if (ret) + return ret; + + r = nvme_scan(NULL); + capabilities = sndk_get_drive_capabilities(r, dev); + + if (capabilities & SNDK_DRIVE_CAP_FW_ACTIVATE_HISTORY_C2) { + ret = sndk_get_fw_act_history_C2(r, dev, cfg.output_format); + + if (ret) { + fprintf(stderr, "ERROR: SNDK: Failure reading the FW "); + fprintf(stderr, "Activate History, ret = %d\n", ret); + } + } else + /* Fall back to the wdc plugin command */ + ret = run_wdc_vs_fw_activate_history(argc, argv, command, plugin); + + nvme_free_tree(r); + dev_close(dev); + return ret; +} + +static int sndk_do_clear_fw_activate_history_fid(int fd) +{ + int ret = -1; + __u32 result; + __u32 value = 1 << 31; /* Bit 31 - Clear Firmware Update History Log */ + + ret = nvme_set_features_simple(fd, SNDK_NVME_CLEAR_FW_ACT_HIST_VU_FID, 0, value, + false, &result); + + nvme_show_status(ret); + return ret; } static int sndk_clear_fw_activate_history(int argc, char **argv, struct command *command, struct plugin *plugin) { - return run_wdc_clear_fw_activate_history(argc, argv, command, plugin); + const char *desc = "Clear FW activate history table."; + __u64 capabilities = 0; + struct nvme_dev *dev; + nvme_root_t r; + int ret; + + OPT_ARGS(opts) = { + OPT_END() + }; + + ret = parse_and_open(&dev, argc, argv, desc, opts); + if (ret) + return ret; + + r = nvme_scan(NULL); + capabilities = sndk_get_drive_capabilities(r, dev); + + if (capabilities & SNDK_DRIVE_CAP_VU_FID_CLEAR_FW_ACT_HISTORY) { + ret = sndk_do_clear_fw_activate_history_fid(dev_fd(dev)); + + if (ret) { + fprintf(stderr, "ERROR: SNDK: Failure clearing the FW "); + fprintf(stderr, "Activate History, ret = %d\n", ret); + } + } else + /* Fall back to the wdc plugin command */ + ret = run_wdc_clear_fw_activate_history(argc, argv, command, plugin); + + nvme_free_tree(r); + dev_close(dev); + return ret; } static int sndk_vs_telemetry_controller_option(int argc, char **argv, @@ -668,7 +1052,7 @@ static int sndk_capabilities(int argc, char **argv, printf("clear-assert-dump : %s\n", capabilities & SNDK_DRIVE_CAP_CLEAR_ASSERT ? "Supported" : "Not Supported"); printf("drive-resize : %s\n", - capabilities & SNDK_DRIVE_CAP_RESIZE ? "Supported" : "Not Supported"); + capabilities & SNDK_DRIVE_CAP_RESIZE_MASK ? "Supported" : "Not Supported"); printf("vs-fw-activate-history : %s\n", capabilities & SNDK_DRIVE_CAP_FW_ACTIVATE_HISTORY_MASK ? "Supported" : "Not Supported"); diff --git a/plugins/sandisk/sandisk-nvme.h b/plugins/sandisk/sandisk-nvme.h index b724f30f8a..b428f0d41b 100644 --- a/plugins/sandisk/sandisk-nvme.h +++ b/plugins/sandisk/sandisk-nvme.h @@ -5,7 +5,7 @@ #if !defined(SANDISK_NVME) || defined(CMD_HEADER_MULTI_READ) #define SANDISK_NVME -#define SANDISK_PLUGIN_VERSION "3.0.5" +#define SANDISK_PLUGIN_VERSION "3.0.6" #include "cmd.h" PLUGIN(NAME("sndk", "Sandisk vendor specific extensions", SANDISK_PLUGIN_VERSION), diff --git a/plugins/sandisk/sandisk-utils.c b/plugins/sandisk/sandisk-utils.c index ad32fa9be0..4d2270b9e8 100644 --- a/plugins/sandisk/sandisk-utils.c +++ b/plugins/sandisk/sandisk-utils.c @@ -11,12 +11,24 @@ #include #include #include +#include "common.h" #include "nvme.h" #include "libnvme.h" #include "nvme-print.h" #include "sandisk-utils.h" #include "plugins/wdc/wdc-nvme-cmds.h" +/* WDC UUID value */ +static const __u8 WDC_UUID[NVME_UUID_LEN] = { + 0x2d, 0xb9, 0x8c, 0x52, 0x0c, 0x4c, 0x5a, 0x15, + 0xab, 0xe6, 0x33, 0x29, 0x9a, 0x70, 0xdf, 0xd0 +}; + +/* Sandisk UUID value */ +static const __u8 SNDK_UUID[NVME_UUID_LEN] = { + 0xde, 0x87, 0xd1, 0xeb, 0x72, 0xc5, 0x58, 0x0b, + 0xad, 0xd8, 0x3c, 0x29, 0xd1, 0x23, 0x7c, 0x70 +}; int sndk_get_pci_ids(nvme_root_t r, struct nvme_dev *dev, uint32_t *device_id, uint32_t *vendor_id) @@ -131,6 +143,390 @@ bool sndk_check_device(nvme_root_t r, struct nvme_dev *dev) return supported; } +void sndk_get_commit_action_bin(__u8 commit_action_type, char *action_bin) +{ + switch (commit_action_type) { + case 0: + strcpy(action_bin, "000b"); + break; + case 1: + strcpy(action_bin, "001b"); + break; + case 2: + strcpy(action_bin, "010b"); + break; + case 3: + strcpy(action_bin, "011b"); + break; + case 4: + strcpy(action_bin, "100b"); + break; + case 5: + strcpy(action_bin, "101b"); + break; + case 6: + strcpy(action_bin, "110b"); + break; + case 7: + strcpy(action_bin, "111b"); + break; + default: + strcpy(action_bin, "INVALID"); + } +} + +bool sndk_parse_dev_mng_log_entry(void *data, + __u32 entry_id, + struct sndk_c2_log_subpage_header **log_entry) +{ + __u32 remaining_len = 0; + __u32 log_length = 0; + __u32 log_entry_size = 0; + __u32 log_entry_id = 0; + __u32 offset = 0; + bool found = false; + struct sndk_c2_log_subpage_header *p_next_log_entry = NULL; + struct sndk_c2_log_page_header *hdr_ptr = (struct sndk_c2_log_page_header *)data; + + log_length = le32_to_cpu(hdr_ptr->length); + /* Ensure log data is large enough for common header */ + if (log_length < sizeof(struct sndk_c2_log_page_header)) { + fprintf(stderr, + "ERROR: %s: log smaller than header. log_len: 0x%x HdrSize: %"PRIxPTR"\n", + __func__, log_length, sizeof(struct sndk_c2_log_page_header)); + return found; + } + + /* Get pointer to first log Entry */ + offset = sizeof(struct sndk_c2_log_page_header); + p_next_log_entry = (struct sndk_c2_log_subpage_header *)(((__u8 *)data) + offset); + remaining_len = log_length - offset; + + if (!log_entry) { + fprintf(stderr, "ERROR: SNDK - %s: No log entry pointer.\n", __func__); + return found; + } + *log_entry = NULL; + + /* Proceed only if there is at least enough data to read an entry header */ + while (remaining_len >= sizeof(struct sndk_c2_log_subpage_header)) { + /* Get size of the next entry */ + log_entry_size = le32_to_cpu(p_next_log_entry->length); + log_entry_id = le32_to_cpu(p_next_log_entry->entry_id); + + /* + * If log entry size is 0 or the log entry goes past the end + * of the data, we must be at the end of the data + */ + if (!log_entry_size || log_entry_size > remaining_len) { + fprintf(stderr, "ERROR: SNDK: %s: Detected unaligned end of the data. ", + __func__); + fprintf(stderr, "Data Offset: 0x%x Entry Size: 0x%x, ", + offset, log_entry_size); + fprintf(stderr, "Remaining Log Length: 0x%x Entry Id: 0x%x\n", + remaining_len, log_entry_id); + + /* Force the loop to end */ + remaining_len = 0; + } else if (!log_entry_id || log_entry_id > 200) { + /* Invalid entry - fail the search */ + fprintf(stderr, "ERROR: SNDK: %s: Invalid entry found at offset: 0x%x ", + __func__, offset); + fprintf(stderr, "Entry Size: 0x%x, Remaining Log Length: 0x%x ", + log_entry_size, remaining_len); + fprintf(stderr, "Entry Id: 0x%x\n", log_entry_id); + + /* Force the loop to end */ + remaining_len = 0; + } else { + if (log_entry_id == entry_id) { + found = true; + *log_entry = p_next_log_entry; + remaining_len = 0; + } else { + remaining_len -= log_entry_size; + } + + if (remaining_len > 0) { + /* Increment the offset counter */ + offset += log_entry_size; + + /* Get the next entry */ + p_next_log_entry = + (struct sndk_c2_log_subpage_header *)(((__u8 *)data) + offset); + } + } + } + + return found; +} + +bool sndk_nvme_parse_dev_status_log_entry(void *log_data, + __u32 entry_id, + __u32 *ret_data) +{ + struct sndk_c2_log_subpage_header *entry_data = NULL; + + if (sndk_parse_dev_mng_log_entry(log_data, entry_id, &entry_data)) { + if (entry_data) { + *ret_data = le32_to_cpu(entry_data->data); + return true; + } + } + + *ret_data = 0; + return false; +} + +bool sndk_nvme_parse_dev_status_log_str(void *log_data, + __u32 entry_id, + char *ret_data, + __u32 *ret_data_len) +{ + struct sndk_c2_log_subpage_header *entry_data = NULL; + struct sndk_c2_cbs_data *entry_str_data = NULL; + + if (sndk_parse_dev_mng_log_entry(log_data, entry_id, &entry_data)) { + if (entry_data) { + entry_str_data = (struct sndk_c2_cbs_data *)&entry_data->data; + memcpy(ret_data, + (void *)&entry_str_data->data, + le32_to_cpu(entry_str_data->length)); + *ret_data_len = le32_to_cpu(entry_str_data->length); + return true; + } + } + + *ret_data = 0; + *ret_data_len = 0; + return false; +} + + +bool sndk_get_dev_mgment_data(nvme_root_t r, struct nvme_dev *dev, + void **data) +{ + bool found = false; + __u32 device_id = 0, vendor_id = 0; + int uuid_index = 0; + struct nvme_id_uuid_list uuid_list; + + *data = NULL; + + /* The sndk_get_pci_ids function could fail when drives are connected + * via a PCIe switch. Therefore, the return code is intentionally + * being ignored. The device_id and vendor_id variables have been + * initialized to 0 so the code can continue on without issue for + * both cases: sndk_get_pci_ids successful or failed. + */ + sndk_get_pci_ids(r, dev, &device_id, &vendor_id); + + memset(&uuid_list, 0, sizeof(struct nvme_id_uuid_list)); + if (!nvme_get_uuid_list(dev_fd(dev), &uuid_list)) { + /* check for the Sandisk UUID first */ + uuid_index = nvme_uuid_find(&uuid_list, SNDK_UUID); + + if (uuid_index < 0) { + /* The Sandisk UUID is not found; + * check for the WDC UUID second. + */ + uuid_index = nvme_uuid_find(&uuid_list, WDC_UUID); + } + + if (uuid_index >= 0) + found = sndk_get_dev_mgmt_log_page_data(dev, data, uuid_index); + else { + fprintf(stderr, "%s: UUID lists are supported but a matching ", + __func__); + fprintf(stderr, "uuid was not found\n"); + } + } else { + /* UUID lists are not supported, Default to uuid-index 0 */ + fprintf(stderr, "INFO: SNDK: %s: UUID Lists not supported\n", + __func__); + uuid_index = 0; + found = sndk_get_dev_mgmt_log_page_data(dev, data, uuid_index); + } + + return found; +} + +bool sndk_validate_dev_mng_log(void *data) +{ + __u32 remaining_len = 0; + __u32 log_length = 0; + __u32 log_entry_size = 0; + __u32 log_entry_id = 0; + __u32 offset = 0; + bool valid_log = false; + struct sndk_c2_log_subpage_header *p_next_log_entry = NULL; + struct sndk_c2_log_page_header *hdr_ptr = (struct sndk_c2_log_page_header *)data; + + log_length = le32_to_cpu(hdr_ptr->length); + /* Ensure log data is large enough for common header */ + if (log_length < sizeof(struct sndk_c2_log_page_header)) { + fprintf(stderr, + "ERROR: %s: log smaller than header. log_len: 0x%x HdrSize: %"PRIxPTR"\n", + __func__, log_length, sizeof(struct sndk_c2_log_page_header)); + return valid_log; + } + + /* Get pointer to first log Entry */ + offset = sizeof(struct sndk_c2_log_page_header); + p_next_log_entry = (struct sndk_c2_log_subpage_header *)(((__u8 *)data) + offset); + remaining_len = log_length - offset; + + /* Proceed only if there is at least enough data to read an entry header */ + while (remaining_len >= sizeof(struct sndk_c2_log_subpage_header)) { + /* Get size of the next entry */ + log_entry_size = le32_to_cpu(p_next_log_entry->length); + log_entry_id = le32_to_cpu(p_next_log_entry->entry_id); + /* + * If log entry size is 0 or the log entry goes past the end + * of the data, we must be at the end of the data + */ + if (!log_entry_size || log_entry_size > remaining_len) { + fprintf(stderr, "ERROR: SNDK: %s: Detected unaligned end of the data. ", + __func__); + fprintf(stderr, "Data Offset: 0x%x Entry Size: 0x%x, ", + offset, log_entry_size); + fprintf(stderr, "Remaining Log Length: 0x%x Entry Id: 0x%x\n", + remaining_len, log_entry_id); + + /* Force the loop to end */ + remaining_len = 0; + } else if (!log_entry_id || log_entry_id > 200) { + /* Invalid entry - fail the search */ + fprintf(stderr, "ERROR: SNDK: %s: Invalid entry found at offset: 0x%x ", + __func__, offset); + fprintf(stderr, "Entry Size: 0x%x, Remaining Log Length: 0x%x ", + log_entry_size, remaining_len); + fprintf(stderr, "Entry Id: 0x%x\n", log_entry_id); + + /* Force the loop to end */ + remaining_len = 0; + valid_log = false; + } else { + /* A valid log has at least one entry and no invalid entries */ + valid_log = true; + remaining_len -= log_entry_size; + if (remaining_len > 0) { + /* Increment the offset counter */ + offset += log_entry_size; + /* Get the next entry */ + p_next_log_entry = + (struct sndk_c2_log_subpage_header *)(((__u8 *)data) + offset); + } + } + } + + return valid_log; +} + +bool sndk_get_dev_mgmt_log_page_data(struct nvme_dev *dev, + void **log_data, + __u8 uuid_ix) +{ + void *data; + struct sndk_c2_log_page_header *hdr_ptr; + __u32 length = 0; + int ret = 0; + bool valid = false; + + data = (__u8 *)malloc(sizeof(__u8) * SNDK_DEV_MGMNT_LOG_PAGE_LEN); + if (!data) { + fprintf(stderr, "ERROR: SNDK: malloc: %s\n", strerror(errno)); + return false; + } + + memset(data, 0, sizeof(__u8) * SNDK_DEV_MGMNT_LOG_PAGE_LEN); + + /* get the log page length */ + struct nvme_get_log_args args_len = { + .args_size = sizeof(args_len), + .fd = dev_fd(dev), + .lid = SNDK_NVME_GET_DEV_MGMNT_LOG_PAGE_ID, + .nsid = 0xFFFFFFFF, + .lpo = 0, + .lsp = NVME_LOG_LSP_NONE, + .lsi = 0, + .rae = false, + .uuidx = uuid_ix, + .csi = NVME_CSI_NVM, + .ot = false, + .len = SNDK_DEV_MGMNT_LOG_PAGE_LEN, + .log = data, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = NULL, + }; + ret = nvme_get_log(&args_len); + if (ret) { + fprintf(stderr, + "ERROR: SNDK: Unable to get 0x%x Log Page with uuid %d, ret = 0x%x\n", + SNDK_NVME_GET_DEV_MGMNT_LOG_PAGE_ID, uuid_ix, ret); + goto end; + } + + hdr_ptr = (struct sndk_c2_log_page_header *)data; + length = le32_to_cpu(hdr_ptr->length); + + if (length > SNDK_DEV_MGMNT_LOG_PAGE_LEN) { + /* Log page buffer too small for actual data */ + free(data); + data = calloc(length, sizeof(__u8)); + if (!data) { + fprintf(stderr, "ERROR: SNDK: malloc: %s\n", strerror(errno)); + goto end; + } + + /* get the log page data with the increased length */ + struct nvme_get_log_args args_data = { + .args_size = sizeof(args_data), + .fd = dev_fd(dev), + .lid = SNDK_NVME_GET_DEV_MGMNT_LOG_PAGE_ID, + .nsid = 0xFFFFFFFF, + .lpo = 0, + .lsp = NVME_LOG_LSP_NONE, + .lsi = 0, + .rae = false, + .uuidx = uuid_ix, + .csi = NVME_CSI_NVM, + .ot = false, + .len = length, + .log = data, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = NULL, + }; + ret = nvme_get_log(&args_data); + + if (ret) { + fprintf(stderr, + "ERROR: SNDK: Unable to read 0x%x Log with uuid %d, ret = 0x%x\n", + SNDK_NVME_GET_DEV_MGMNT_LOG_PAGE_ID, uuid_ix, ret); + goto end; + } + } + + valid = sndk_validate_dev_mng_log(data); + if (valid) { + /* Ensure size of log data matches length in log header */ + *log_data = calloc(length, sizeof(__u8)); + if (!*log_data) { + fprintf(stderr, "ERROR: SNDK: calloc: %s\n", strerror(errno)); + valid = false; + goto end; + } + memcpy((void *)*log_data, data, length); + } else { + fprintf(stderr, "ERROR: SNDK: C2 log page not found with uuid index %d\n", + uuid_ix); + } + +end: + free(data); + return valid; +} + __u64 sndk_get_drive_capabilities(nvme_root_t r, struct nvme_dev *dev) { __u64 capabilities = 0; @@ -173,6 +569,22 @@ __u64 sndk_get_drive_capabilities(nvme_root_t r, struct nvme_dev *dev) break; case SNDK_NVME_SN861_DEV_ID_E1S: + capabilities |= (SNDK_DRIVE_CAP_C0_LOG_PAGE | + SNDK_DRIVE_CAP_C3_LOG_PAGE | + SNDK_DRIVE_CAP_CA_LOG_PAGE | + SNDK_DRIVE_CAP_OCP_C4_LOG_PAGE | + SNDK_DRIVE_CAP_OCP_C5_LOG_PAGE | + SNDK_DRIVE_CAP_INTERNAL_LOG | + SNDK_DRIVE_CAP_FW_ACTIVATE_HISTORY_C2 | + SNDK_DRIVE_CAP_VU_FID_CLEAR_PCIE | + SNDK_DRIVE_CAP_VU_FID_CLEAR_FW_ACT_HISTORY | + SNDK_DRIVE_CAP_INFO | + SNDK_DRIVE_CAP_CLOUD_SSD_VERSION | + SNDK_DRIVE_CAP_LOG_PAGE_DIR | + SNDK_DRIVE_CAP_DRIVE_STATUS | + SNDK_DRIVE_CAP_SET_LATENCY_MONITOR); + break; + case SNDK_NVME_SN861_DEV_ID_U2: case SNDK_NVME_SN861_DEV_ID_E3S: capabilities |= (SNDK_DRIVE_CAP_C0_LOG_PAGE | @@ -188,7 +600,7 @@ __u64 sndk_get_drive_capabilities(nvme_root_t r, struct nvme_dev *dev) SNDK_DRIVE_CAP_CLOUD_SSD_VERSION | SNDK_DRIVE_CAP_LOG_PAGE_DIR | SNDK_DRIVE_CAP_DRIVE_STATUS | - SNDK_DRIVE_CAP_RESIZE | + SNDK_DRIVE_CAP_RESIZE_SN861 | SNDK_DRIVE_CAP_SET_LATENCY_MONITOR); break; @@ -228,7 +640,14 @@ __u64 sndk_get_enc_drive_capabilities(nvme_root_t r, int ret; uint32_t read_vendor_id; __u64 capabilities = 0; - __u32 cust_id; + __u32 cust_id, market_name_len, + drive_form_factor = 0; + char marketing_name[64]; + void *dev_mng_log = NULL; + int uuid_index = 0; + struct nvme_id_uuid_list uuid_list; + + memset(marketing_name, 0, 64); ret = sndk_get_vendor_id(dev, &read_vendor_id); if (ret < 0) @@ -241,47 +660,115 @@ __u64 sndk_get_enc_drive_capabilities(nvme_root_t r, SNDK_DRIVE_CAP_CLEAR_ASSERT | SNDK_DRIVE_CAP_RESIZE); + /* Check for the Sandisk or WDC UUID index */ + memset(&uuid_list, 0, sizeof(struct nvme_id_uuid_list)); + if (!nvme_get_uuid_list(dev_fd(dev), &uuid_list)) { + /* check for the Sandisk UUID first */ + uuid_index = nvme_uuid_find(&uuid_list, SNDK_UUID); + + if (uuid_index < 0) + /* The Sandisk UUID is not found; + * check for the WDC UUID second. + */ + uuid_index = nvme_uuid_find(&uuid_list, WDC_UUID); + } else { + /* UUID Lists not supported, Use default uuid index - 0 */ + fprintf(stderr, "INFO: SNDK: %s: UUID Lists not supported\n", + __func__); + uuid_index = 0; + } + + /* verify the 0xC2 Device Manageability log page is supported */ + if (run_wdc_nvme_check_supported_log_page(r, dev, + SNDK_NVME_GET_DEV_MGMNT_LOG_PAGE_ID, + uuid_index) == false) { + fprintf(stderr, "ERROR: SNDK: 0xC2 Log Page not supported, "); + fprintf(stderr, "uuid_index: %d\n", uuid_index); + ret = -1; + goto out; + } + + if (!sndk_get_dev_mgment_data(r, dev, &dev_mng_log)) { + fprintf(stderr, "ERROR: SNDK: 0xC2 Log Page not found\n"); + ret = -1; + goto out; + } + + /* Get the customer ID */ + if (!sndk_nvme_parse_dev_status_log_entry(dev_mng_log, + SNDK_C2_CUSTOMER_ID_ID, + (void *)&cust_id)) + fprintf(stderr, "ERROR: SNDK: Get Customer FW ID Failed\n"); + + /* Get the marketing name */ + if (!sndk_nvme_parse_dev_status_log_str(dev_mng_log, + SNDK_C2_MARKETING_NAME_ID, + (char *)marketing_name, + &market_name_len)) + fprintf(stderr, "ERROR: SNDK: Get Marketing Name Failed\n"); + + /* Get the drive form factor */ + if (!sndk_nvme_parse_dev_status_log_entry(dev_mng_log, + SNDK_C2_FORM_FACTOR, + (void *)&drive_form_factor)) + fprintf(stderr, "ERROR: SNDK: Getting Form Factor Failed\n"); + + /* verify the 0xC3 log page is supported */ if (run_wdc_nvme_check_supported_log_page(r, dev, - SNDK_LATENCY_MON_LOG_ID)) + SNDK_LATENCY_MON_LOG_ID, 0)) capabilities |= SNDK_DRIVE_CAP_C3_LOG_PAGE; /* verify the 0xCB log page is supported */ if (run_wdc_nvme_check_supported_log_page(r, dev, - SNDK_NVME_GET_FW_ACT_HISTORY_LOG_ID)) + SNDK_NVME_GET_FW_ACT_HISTORY_LOG_ID, 0)) capabilities |= SNDK_DRIVE_CAP_FW_ACTIVATE_HISTORY; /* verify the 0xCA log page is supported */ if (run_wdc_nvme_check_supported_log_page(r, dev, - SNDK_NVME_GET_DEVICE_INFO_LOG_ID)) + SNDK_NVME_GET_DEVICE_INFO_LOG_ID, 0)) capabilities |= SNDK_DRIVE_CAP_CA_LOG_PAGE; /* verify the 0xD0 log page is supported */ if (run_wdc_nvme_check_supported_log_page(r, dev, - SNDK_NVME_GET_VU_SMART_LOG_ID)) + SNDK_NVME_GET_VU_SMART_LOG_ID, 0)) capabilities |= SNDK_DRIVE_CAP_D0_LOG_PAGE; - cust_id = run_wdc_get_fw_cust_id(r, dev); - if (cust_id == SNDK_INVALID_CUSTOMER_ID) { - fprintf(stderr, "%s: ERROR: SNDK: invalid customer id\n", __func__); - return -1; - } - if ((cust_id == SNDK_CUSTOMER_ID_0x1004) || (cust_id == SNDK_CUSTOMER_ID_0x1008) || (cust_id == SNDK_CUSTOMER_ID_0x1005) || (cust_id == SNDK_CUSTOMER_ID_0x1304)) - capabilities |= (SNDK_DRIVE_CAP_VU_FID_CLEAR_FW_ACT_HISTORY | + /* Set capabilities for OCP compliant drives */ + capabilities |= (SNDK_DRIVE_CAP_FW_ACTIVATE_HISTORY_C2 | + SNDK_DRIVE_CAP_VU_FID_CLEAR_FW_ACT_HISTORY | SNDK_DRIVE_CAP_VU_FID_CLEAR_PCIE); - else + else if ((!strncmp(marketing_name, SNDK_SN861_MARKETING_NAME_1, market_name_len)) || + (!strncmp(marketing_name, SNDK_SN861_MARKETING_NAME_2, market_name_len))) { + /* Set capabilities for OCP compliant drives */ + capabilities |= (SNDK_DRIVE_CAP_FW_ACTIVATE_HISTORY_C2 | + SNDK_DRIVE_CAP_VU_FID_CLEAR_FW_ACT_HISTORY | + SNDK_DRIVE_CAP_VU_FID_CLEAR_PCIE); + + if ((drive_form_factor == SNDK_C2_FORM_FACTOR_SFF_U2) || + (drive_form_factor == SNDK_C2_FORM_FACTOR_EDSFF_E3S)) + capabilities |= SNDK_DRIVE_CAP_RESIZE_SN861; + else + capabilities &= ~SNDK_DRIVE_CAP_RESIZE; + } else { capabilities |= (SNDK_DRIVE_CAP_CLEAR_FW_ACT_HISTORY | - SNDK_DRIVE_CAP_CLEAR_PCIE); + SNDK_DRIVE_CAP_CLEAR_PCIE); + /* if the 0xCB log page is supported */ + if (run_wdc_nvme_check_supported_log_page(r, dev, + SNDK_NVME_GET_FW_ACT_HISTORY_LOG_ID, 0)) + capabilities |= SNDK_DRIVE_CAP_FW_ACTIVATE_HISTORY; + } break; default: capabilities = 0; } +out: return capabilities; } diff --git a/plugins/sandisk/sandisk-utils.h b/plugins/sandisk/sandisk-utils.h index 78a102cce1..dba355b8cc 100644 --- a/plugins/sandisk/sandisk-utils.h +++ b/plugins/sandisk/sandisk-utils.h @@ -108,7 +108,7 @@ #define SNDK_DRIVE_CAP_CLEAR_PCIE 0x0000000000000080 #define SNDK_DRIVE_CAP_RESIZE 0x0000000000000100 #define SNDK_DRIVE_CAP_NAND_STATS 0x0000000000000200 -#define SNDK_DRIVE_CAP_RESERVED2 0x0000000000000400 +#define SNDK_DRIVE_CAP_RESIZE_SN861 0x0000000000000400 #define SNDK_DRIVE_CAP_RESERVED3 0x0000000000000800 #define SNDK_DRIVE_CAP_RESERVED4 0x0000000000001000 #define SNDK_DRIVE_CAP_FW_ACTIVATE_HISTORY 0x0000000000002000 @@ -141,6 +141,7 @@ #define SNDK_DRIVE_CAP_DEVICE_WAF 0x0000010000000000 #define SNDK_DRIVE_CAP_SET_LATENCY_MONITOR 0x0000020000000000 #define SNDK_DRIVE_CAP_UDUI 0x0000040000000000 + /* Any new capability flags should be added to the WDC plugin */ #define SNDK_DRIVE_CAP_SMART_LOG_MASK (SNDK_DRIVE_CAP_C0_LOG_PAGE | \ @@ -159,6 +160,8 @@ SNDK_DRIVE_CAP_FW_ACTIVATE_HISTORY_C2) #define SNDK_DRIVE_CAP_CLEAR_FW_ACT_HISTORY_MASK (SNDK_DRIVE_CAP_CLEAR_FW_ACT_HISTORY | \ SNDK_DRIVE_CAP_VU_FID_CLEAR_FW_ACT_HISTORY) +#define SNDK_DRIVE_CAP_RESIZE_MASK (SNDK_DRIVE_CAP_RESIZE | \ + SNDK_DRIVE_CAP_RESIZE_SN861) /* Vendor defined Log Page IDs */ #define SNDK_NVME_GET_SMART_CLOUD_ATTR_LOG_ID 0xC0 @@ -173,8 +176,32 @@ #define SNDK_NVME_GET_FW_ACT_HISTORY_LOG_ID 0xCB #define SNDK_NVME_GET_VU_SMART_LOG_ID 0xD0 +#define SNDK_NVME_GET_DEV_MGMNT_LOG_PAGE_ID 0xC2 +#define SNDK_DEV_MGMNT_LOG_PAGE_LEN 0x1000 + +#define SNDK_C2_MARKETING_NAME_ID 0x07 +#define SNDK_C2_LOG_PAGES_SUPPORTED_ID 0x08 +#define SNDK_C2_FORM_FACTOR 0x0A +#define SNDK_C2_CUSTOMER_ID_ID 0x15 + +#define SNDK_SN861_MARKETING_NAME_1 "Ultrastar DC SN861" +#define SNDK_SN861_MARKETING_NAME_2 "ULTRASTAR DC SN861" + +#define SNDK_C2_FORM_FACTOR_UNKNOWN 0x00000000 +#define SNDK_C2_FORM_FACTOR_SFF_U2 0x00000001 +#define SNDK_C2_FORM_FACTOR_HHHL 0x00000002 +#define SNDK_C2_FORM_FACTOR_MEZZANINE 0x00000003 +#define SNDK_C2_FORM_FACTOR_M2 0x00000004 +#define SNDK_C2_FORM_FACTOR_EDSFF_E1L 0x00000005 +#define SNDK_C2_FORM_FACTOR_EDSFF_E1S 0x00000006 +#define SNDK_C2_FORM_FACTOR_SFF_U3 0x00000007 +#define SNDK_C2_FORM_FACTOR_EDSFF_E3L 0x00000008 +#define SNDK_C2_FORM_FACTOR_EDSFF_E3S 0x00000009 +#define SNDK_C2_FORM_FACTOR_EDSFF_E2 0x0000000A + /* Vendor defined Feature IDs */ -#define SNDK_VU_DISABLE_CNTLR_TELEMETRY_OPTION_FEATURE_ID 0xD2 +#define SNDK_NVME_CLEAR_FW_ACT_HIST_VU_FID 0xC1 +#define SNDK_VU_DISABLE_CNTLR_TELEMETRY_OPTION_FEATURE_ID 0xD2 /* Customer ID's */ #define SNDK_CUSTOMER_ID_GN 0x0001 @@ -191,12 +218,16 @@ #define SNDK_NVME_CAP_UDUI_OPCODE 0xFA /* Telemtery types for vs-internal-log command */ -#define SNDK_TELEMETRY_TYPE_NONE 0x0 -#define SNDK_TELEMETRY_TYPE_HOST 0x1 -#define SNDK_TELEMETRY_TYPE_CONTROLLER 0x2 +#define SNDK_TELEMETRY_TYPE_NONE 0x0 +#define SNDK_TELEMETRY_TYPE_HOST 0x1 +#define SNDK_TELEMETRY_TYPE_CONTROLLER 0x2 /* Misc */ -#define SNDK_MAX_PATH_LEN 256 +#define SNDK_MAX_PATH_LEN 256 +#define SNDK_GUID_LENGTH 16 + +#define SNDK_MAX_NUM_ACT_HIST_ENTRIES 20 +#define SNDK_FW_ACT_HISTORY_C2_LOG_BUF_LEN 0x1000 struct SNDK_UtilsTimeInfo { unsigned int year; @@ -211,6 +242,49 @@ struct SNDK_UtilsTimeInfo { int zone; /* Zone value like +530 or -300 */ }; +/* Additional C2 Log Page */ +struct sndk_c2_log_page_header { + __le32 length; + __le32 version; +}; + +struct sndk_c2_log_subpage_header { + __le32 length; + __le32 entry_id; + __le32 data; +}; + +struct sndk_c2_cbs_data { + __le32 length; + __u8 data[]; +}; + +struct __packed sndk_fw_act_history_log_entry_c2 { + __u8 entry_version_num; + __u8 entry_len; + __le16 reserved; + __le16 fw_act_hist_entries; + __le64 timestamp; + __u8 reserved2[8]; + __le64 power_cycle_count; + __le64 previous_fw_version; + __le64 current_fw_version; + __u8 slot_number; + __u8 commit_action_type; + __le16 result; + __u8 reserved3[14]; +}; + +struct __packed sndk_fw_act_history_log_format_c2 { + __u8 log_identifier; + __u8 reserved[3]; + __le32 num_entries; + struct sndk_fw_act_history_log_entry_c2 entry[SNDK_MAX_NUM_ACT_HIST_ENTRIES]; + __u8 reserved2[2790]; + __le16 log_page_version; + __u8 log_page_guid[SNDK_GUID_LENGTH]; +}; + int sndk_get_pci_ids(nvme_root_t r, struct nvme_dev *dev, uint32_t *device_id, @@ -222,6 +296,32 @@ int sndk_get_vendor_id(struct nvme_dev *dev, bool sndk_check_device(nvme_root_t r, struct nvme_dev *dev); +void sndk_get_commit_action_bin(__u8 commit_action_type, + char *action_bin); + +bool sndk_parse_dev_mng_log_entry(void *data, + __u32 entry_id, + struct sndk_c2_log_subpage_header **log_entry); + +bool sndk_nvme_parse_dev_status_log_entry(void *log_data, + __u32 entry_id, + __u32 *ret_data); + +bool sndk_nvme_parse_dev_status_log_str(void *log_data, + __u32 entry_id, + char *ret_data, + __u32 *ret_data_len); + +bool sndk_get_dev_mgment_data(nvme_root_t r, + struct nvme_dev *dev, + void **data); + +bool sndk_validate_dev_mng_log(void *data); + +bool sndk_get_dev_mgmt_log_page_data(struct nvme_dev *dev, + void **log_data, + __u8 uuid_ix); + __u64 sndk_get_drive_capabilities(nvme_root_t r, struct nvme_dev *dev); diff --git a/plugins/wdc/wdc-nvme-cmds.h b/plugins/wdc/wdc-nvme-cmds.h index 2dea9b06c2..dc37a2da34 100644 --- a/plugins/wdc/wdc-nvme-cmds.h +++ b/plugins/wdc/wdc-nvme-cmds.h @@ -119,7 +119,8 @@ int run_wdc_cu_smart_log(int argc, char **argv, bool run_wdc_nvme_check_supported_log_page(nvme_root_t r, struct nvme_dev *dev, - __u8 log_id); + __u8 log_id, + __u8 uuid_index); __u32 run_wdc_get_fw_cust_id(nvme_root_t r, struct nvme_dev *dev); diff --git a/plugins/wdc/wdc-nvme.c b/plugins/wdc/wdc-nvme.c index f66d3f452a..43bf793bdb 100644 --- a/plugins/wdc/wdc-nvme.c +++ b/plugins/wdc/wdc-nvme.c @@ -13082,12 +13082,13 @@ __u32 run_wdc_get_fw_cust_id(nvme_root_t r, struct nvme_dev *dev) bool run_wdc_nvme_check_supported_log_page(nvme_root_t r, struct nvme_dev *dev, - __u8 log_id) + __u8 log_id, + __u8 uuid_index) { return wdc_nvme_check_supported_log_page(r, dev, log_id, - 0); + uuid_index); } __u64 run_wdc_get_drive_capabilities(nvme_root_t r, struct nvme_dev *dev) diff --git a/subprojects/libnvme.wrap b/subprojects/libnvme.wrap index 5897e970e6..4e13aacc3d 100644 --- a/subprojects/libnvme.wrap +++ b/subprojects/libnvme.wrap @@ -1,6 +1,6 @@ [wrap-git] url = https://github.com/linux-nvme/libnvme.git -revision = c804c2fc999b91ce8cca1a05cb4c059c451a2ddb +revision = 6ff20a2d535e6c28b4d529b9bbbb3f7b51c5d2aa [provide] libnvme = libnvme_dep