]> www.infradead.org Git - users/sagi/nvme-cli.git/commitdiff
wdc: rework log retrieval and parsing
authorBrandon Paupore <brandon.paupore@wdc.com>
Mon, 25 Nov 2024 17:11:31 +0000 (11:11 -0600)
committerDaniel Wagner <wagi@monom.org>
Mon, 2 Dec 2024 11:30:19 +0000 (12:30 +0100)
We should only need to retrieve the log page once, rather than once per
field to be parsed. This also fixes some endianness issues when handling
the parsed data entries.

Signed-off-by: Brandon Paupore <brandon.paupore@wdc.com>
plugins/wdc/wdc-nvme.c
plugins/wdc/wdc-nvme.h

index 04d5dac112595e7cdbc11a8759b9ef39dbbc7fc0..f1e12c48b5249da8437d28165922bf80440e44ea 100644 (file)
@@ -2181,6 +2181,163 @@ static int wdc_create_log_file(char *file, __u8 *drive_log_data,
        return 0;
 }
 
+bool wdc_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 wdc_c2_log_subpage_header *p_next_log_entry = NULL;
+       struct wdc_c2_log_page_header *hdr_ptr = (struct wdc_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 wdc_c2_log_page_header)) {
+               fprintf(stderr,
+                   "ERROR: %s: log smaller than header. log_len: 0x%x  HdrSize: %"PRIxPTR"\n",
+                   __func__, log_length, sizeof(struct wdc_c2_log_page_header));
+               return valid_log;
+       }
+
+       /* Get pointer to first log Entry */
+       offset = sizeof(struct wdc_c2_log_page_header);
+       p_next_log_entry = (struct wdc_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 wdc_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: WDC: %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: WDC: %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 wdc_c2_log_subpage_header *)(((__u8 *)data) + offset);
+                       }
+               }
+       }
+
+       return valid_log;
+}
+
+bool wdc_parse_dev_mng_log_entry(void *data, __u32 entry_id,
+                                struct wdc_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 wdc_c2_log_subpage_header *p_next_log_entry = NULL;
+       struct wdc_c2_log_page_header *hdr_ptr = (struct wdc_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 wdc_c2_log_page_header)) {
+               fprintf(stderr,
+                   "ERROR: %s: log smaller than header. log_len: 0x%x  HdrSize: %"PRIxPTR"\n",
+                   __func__, log_length, sizeof(struct wdc_c2_log_page_header));
+               return found;
+       }
+
+       /* Get pointer to first log Entry */
+       offset = sizeof(struct wdc_c2_log_page_header);
+       p_next_log_entry = (struct wdc_c2_log_subpage_header *)(((__u8 *)data) + offset);
+       remaining_len = log_length - offset;
+
+       if (!log_entry) {
+               fprintf(stderr, "ERROR: WDC - %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 wdc_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: WDC: %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: WDC: %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 wdc_c2_log_subpage_header *)(((__u8 *)data) + offset);
+                       }
+               }
+       }
+
+       return found;
+}
+
 bool wdc_get_dev_mng_log_entry(__u32 log_length, __u32 entry_id,
                               struct wdc_c2_log_page_header *p_log_hdr,
                               struct wdc_c2_log_subpage_header **p_p_found_log_entry)
@@ -2188,9 +2345,10 @@ bool wdc_get_dev_mng_log_entry(__u32 log_length, __u32 entry_id,
        __u32 remaining_len = 0;
        __u32 log_entry_hdr_size = sizeof(struct wdc_c2_log_subpage_header) - 1;
        __u32 log_entry_size = 0;
+       __u32 log_entry_id = 0;
        __u32 size = 0;
        bool valid_log;
-       __u32 current_data_offset = 0;
+       __u32 offset = 0;
        struct wdc_c2_log_subpage_header *p_next_log_entry = NULL;
 
        if (!*p_p_found_log_entry) {
@@ -2210,8 +2368,8 @@ bool wdc_get_dev_mng_log_entry(__u32 log_length, __u32 entry_id,
 
        /* Get pointer to first log Entry */
        size = sizeof(struct wdc_c2_log_page_header);
-       current_data_offset = size;
-       p_next_log_entry = (struct wdc_c2_log_subpage_header *)((__u8 *)p_log_hdr + current_data_offset);
+       offset = size;
+       p_next_log_entry = (struct wdc_c2_log_subpage_header *)(((__u8 *)p_log_hdr) + offset);
        remaining_len = log_length - size;
        valid_log = false;
 
@@ -2225,7 +2383,8 @@ bool wdc_get_dev_mng_log_entry(__u32 log_length, __u32 entry_id,
        /* Proceed only if there is at least enough data to read an entry header */
        while (remaining_len >= log_entry_hdr_size) {
                /* Get size of the next entry */
-               log_entry_size = p_next_log_entry->length;
+               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
@@ -2235,19 +2394,19 @@ bool wdc_get_dev_mng_log_entry(__u32 log_length, __u32 entry_id,
                        fprintf(stderr, "ERROR: WDC: %s: Detected unaligned end of the data. ",
                                __func__);
                        fprintf(stderr, "Data Offset: 0x%x Entry Size: 0x%x, ",
-                               current_data_offset, log_entry_size);
+                               offset, log_entry_size);
                        fprintf(stderr, "Remaining Log Length: 0x%x Entry Id: 0x%x\n",
-                               remaining_len, p_next_log_entry->entry_id);
+                               remaining_len, log_entry_id);
 
                        /* Force the loop to end */
                        remaining_len = 0;
-               } else if (!p_next_log_entry->entry_id || p_next_log_entry->entry_id > 200) {
+               } else if (!log_entry_id || log_entry_id > 200) {
                        /* Invalid entry - fail the search */
                        fprintf(stderr, "ERROR: WDC: %s: Invalid entry found at offset: 0x%x ",
-                               __func__, current_data_offset);
+                               __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", p_next_log_entry->entry_id);
+                       fprintf(stderr, "Entry Id: 0x%x\n", log_entry_id);
 
                        /* Force the loop to end */
                        remaining_len = 0;
@@ -2258,7 +2417,7 @@ bool wdc_get_dev_mng_log_entry(__u32 log_length, __u32 entry_id,
                } else {
                        /* Structure must have at least one valid entry to be considered valid */
                        valid_log = true;
-                       if (p_next_log_entry->entry_id == entry_id)
+                       if (log_entry_id == entry_id)
                                /* A potential match. */
                                *p_p_found_log_entry = p_next_log_entry;
 
@@ -2266,10 +2425,11 @@ bool wdc_get_dev_mng_log_entry(__u32 log_length, __u32 entry_id,
 
                        if (remaining_len > 0) {
                                /* Increment the offset counter */
-                               current_data_offset += log_entry_size;
+                               offset += log_entry_size;
 
                                /* Get the next entry */
-                               p_next_log_entry = (struct wdc_c2_log_subpage_header *)(((__u8 *)p_log_hdr) + current_data_offset);
+                               p_next_log_entry =
+                               (struct wdc_c2_log_subpage_header *)(((__u8 *)p_log_hdr) + offset);
                        }
                }
        }
@@ -2277,6 +2437,109 @@ bool wdc_get_dev_mng_log_entry(__u32 log_length, __u32 entry_id,
        return valid_log;
 }
 
+static bool get_dev_mgmt_log_page_data(struct nvme_dev *dev, void **log_data,
+                                      __u8 uuid_ix)
+{
+       void *data;
+       struct wdc_c2_log_page_header *hdr_ptr;
+       __u32 length = 0;
+       int ret = 0;
+       bool valid = false;
+
+       data = (__u8 *)malloc(sizeof(__u8) * WDC_C2_LOG_BUF_LEN);
+       if (!data) {
+               fprintf(stderr, "ERROR: WDC: malloc: %s\n", strerror(errno));
+               return false;
+       }
+
+       memset(data, 0, sizeof(__u8) * WDC_C2_LOG_BUF_LEN);
+
+       /* get the log page length */
+       struct nvme_get_log_args args_len = {
+               .args_size      = sizeof(args_len),
+               .fd             = dev_fd(dev),
+               .lid            = WDC_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            = WDC_C2_LOG_BUF_LEN,
+               .log            = data,
+               .timeout        = NVME_DEFAULT_IOCTL_TIMEOUT,
+               .result         = NULL,
+       };
+       ret = nvme_get_log(&args_len);
+       if (ret) {
+               fprintf(stderr,
+                       "ERROR: WDC: Unable to get 0x%x Log Page with uuid %d, ret = 0x%x\n",
+                       WDC_NVME_GET_DEV_MGMNT_LOG_PAGE_ID, uuid_ix, ret);
+               goto end;
+       }
+
+       hdr_ptr = (struct wdc_c2_log_page_header *)data;
+       length = le32_to_cpu(hdr_ptr->length);
+
+       if (length > WDC_C2_LOG_BUF_LEN) {
+               /* Log page buffer too small for actual data */
+               free(data);
+               data = calloc(length, sizeof(__u8));
+               if (!data) {
+                       fprintf(stderr, "ERROR: WDC: 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            = WDC_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: WDC: Unable to read 0x%x Log with uuid %d, ret = 0x%x\n",
+                               WDC_NVME_GET_DEV_MGMNT_LOG_PAGE_ID, uuid_ix, ret);
+                       goto end;
+               }
+       }
+
+       valid = wdc_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: WDC: calloc: %s\n", strerror(errno));
+                       valid = false;
+                       goto end;
+               }
+               memcpy((void *)*log_data, data, length);
+       } else {
+               fprintf(stderr, "ERROR: WDC: C2 log page not found with uuid index %d\n",
+                       uuid_ix);
+       }
+
+end:
+       free(data);
+       return valid;
+}
+
 static bool get_dev_mgmt_log_page_lid_data(struct nvme_dev *dev,
        void **cbs_data,
        __u8 lid,
@@ -2387,6 +2650,78 @@ end:
        return found;
 }
 
+static bool get_dev_mgment_data(nvme_root_t r, struct nvme_dev *dev,
+                               void **data)
+{
+       bool found = false;
+       *data = NULL;
+       __u32 device_id = 0, vendor_id = 0;
+       bool uuid_present = false;
+       int index = 0, uuid_index = 0;
+       struct nvme_id_uuid_list uuid_list;
+
+       /* The wdc_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: wdc_get_pci_ids successful or failed.
+        */
+       wdc_get_pci_ids(r, dev, &device_id, &vendor_id);
+
+       typedef struct nvme_id_uuid_list_entry *uuid_list_entry;
+
+       memset(&uuid_list, 0, sizeof(struct nvme_id_uuid_list));
+       if (wdc_CheckUuidListSupport(dev, &uuid_list)) {
+               uuid_list_entry uuid_list_entry_ptr = (uuid_list_entry)&uuid_list.entry[0];
+
+               while (index <= NVME_ID_UUID_LIST_MAX &&
+                      !wdc_UuidEqual(uuid_list_entry_ptr, (uuid_list_entry)UUID_END)) {
+
+                       if (wdc_UuidEqual(uuid_list_entry_ptr,
+                                         (uuid_list_entry)WDC_UUID)) {
+                               uuid_present = true;
+                               break;
+                       } else if (wdc_UuidEqual(uuid_list_entry_ptr,
+                                                (uuid_list_entry)WDC_UUID_SN640_3) &&
+                                  wdc_is_sn640_3(device_id)) {
+                               uuid_present = true;
+                               break;
+                       }
+                       index++;
+                       uuid_list_entry_ptr = (uuid_list_entry)&uuid_list.entry[index];
+               }
+               if (uuid_present)
+                       uuid_index = index + 1;
+       }
+
+       if (uuid_present) {
+               /* use the uuid index found above */
+               found = get_dev_mgmt_log_page_data(dev, data, uuid_index);
+       } else {
+               if (!uuid_index && needs_c2_log_page_check(device_id)) {
+                       /* In certain devices that don't support UUID lists, there are multiple
+                        * definitions of the C2 logpage. In those cases, the code
+                        * needs to try two UUID indexes and use an identification algorithm
+                        * to determine which is returning the correct log page data.
+                        */
+                       uuid_index = 1;
+               }
+
+               found = get_dev_mgmt_log_page_data(dev, data, uuid_index);
+
+               if (!found) {
+                       /* not found with uuid = 1 try with uuid = 0 */
+                       uuid_index = 0;
+                       fprintf(stderr, "Not found, requesting log page with uuid_index %d\n",
+                                       uuid_index);
+
+                       found = get_dev_mgmt_log_page_data(dev, data, uuid_index);
+               }
+       }
+
+       return found;
+}
+
 static bool get_dev_mgment_cbs_data(nvme_root_t r, struct nvme_dev *dev,
                                __u8 log_id, void **cbs_data)
 {
@@ -2501,6 +2836,22 @@ static bool wdc_nvme_check_supported_log_page(nvme_root_t r, struct nvme_dev *de
        return found;
 }
 
+static bool wdc_nvme_parse_dev_status_log_entry(void *log_data, __u32 *ret_data,
+                                               __u32 entry_id)
+{
+       struct wdc_c2_log_subpage_header *entry_data = NULL;
+
+       if (wdc_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;
+}
+
 static bool wdc_nvme_get_dev_status_log_data(nvme_root_t r, struct nvme_dev *dev, __le32 *ret_data,
                                             __u8 log_id)
 {
@@ -8625,12 +8976,13 @@ static int wdc_drive_status(int argc, char **argv, struct command *command,
        struct nvme_dev *dev;
        int ret = 0;
        nvme_root_t r;
-       __le32 system_eol_state;
-       __le32 user_eol_state;
-       __le32 format_corrupt_reason = cpu_to_le32(0xFFFFFFFF);
-       __le32 eol_status;
-       __le32 assert_status = cpu_to_le32(0xFFFFFFFF);
-       __le32 thermal_status = cpu_to_le32(0xFFFFFFFF);
+       void *dev_mng_log = NULL;
+       __u32 system_eol_state;
+       __u32 user_eol_state;
+       __u32 format_corrupt_reason = 0xFFFFFFFF;
+       __u32 eol_status;
+       __u32 assert_status = 0xFFFFFFFF;
+       __u32 thermal_status = 0xFFFFFFFF;
        __u64 capabilities = 0;
 
        OPT_ARGS(opts) = {
@@ -8657,35 +9009,41 @@ static int wdc_drive_status(int argc, char **argv, struct command *command,
                goto out;
        }
 
+       if (!get_dev_mgment_data(r, dev, &dev_mng_log)) {
+               fprintf(stderr, "ERROR: WDC: 0xC2 Log Page not found\n");
+               ret = -1;
+               goto out;
+       }
+
        /* Get the assert dump present status */
-       if (!wdc_nvme_get_dev_status_log_data(r, dev, &assert_status,
+       if (!wdc_nvme_parse_dev_status_log_entry(dev_mng_log, &assert_status,
                        WDC_C2_ASSERT_DUMP_PRESENT_ID))
                fprintf(stderr, "ERROR: WDC: Get Assert Status Failed\n");
 
        /* Get the thermal throttling status */
-       if (!wdc_nvme_get_dev_status_log_data(r, dev, &thermal_status,
+       if (!wdc_nvme_parse_dev_status_log_entry(dev_mng_log, &thermal_status,
                        WDC_C2_THERMAL_THROTTLE_STATUS_ID))
                fprintf(stderr, "ERROR: WDC: Get Thermal Throttling Status Failed\n");
 
        /* Get EOL status */
-       if (!wdc_nvme_get_dev_status_log_data(r, dev, &eol_status,
+       if (!wdc_nvme_parse_dev_status_log_entry(dev_mng_log, &eol_status,
                        WDC_C2_USER_EOL_STATUS_ID)) {
                fprintf(stderr, "ERROR: WDC: Get User EOL Status Failed\n");
                eol_status = cpu_to_le32(-1);
        }
 
        /* Get Customer EOL state */
-       if (!wdc_nvme_get_dev_status_log_data(r, dev, &user_eol_state,
+       if (!wdc_nvme_parse_dev_status_log_entry(dev_mng_log, &user_eol_state,
                        WDC_C2_USER_EOL_STATE_ID))
                fprintf(stderr, "ERROR: WDC: Get User EOL State Failed\n");
 
        /* Get System EOL state*/
-       if (!wdc_nvme_get_dev_status_log_data(r, dev, &system_eol_state,
+       if (!wdc_nvme_parse_dev_status_log_entry(dev_mng_log, &system_eol_state,
                        WDC_C2_SYSTEM_EOL_STATE_ID))
                fprintf(stderr, "ERROR: WDC: Get System EOL State Failed\n");
 
        /* Get format corrupt reason*/
-       if (!wdc_nvme_get_dev_status_log_data(r, dev, &format_corrupt_reason,
+       if (!wdc_nvme_parse_dev_status_log_entry(dev_mng_log, &format_corrupt_reason,
                        WDC_C2_FORMAT_CORRUPT_REASON_ID))
                fprintf(stderr, "ERROR: WDC: Get Format Corrupt Reason Failed\n");
 
@@ -8732,6 +9090,7 @@ static int wdc_drive_status(int argc, char **argv, struct command *command,
        else
                printf("  Format Corrupt Reason:                Unknown : 0x%08x\n", le32_to_cpu(format_corrupt_reason));
 
+       free(dev_mng_log);
 out:
        nvme_free_tree(r);
        dev_close(dev);
index 46314a1c4ba02ceeea12b3868db950462fde3aed..ed7f9128e6356bcb64833288634d831996eb2c90 100644 (file)
@@ -5,7 +5,7 @@
 #if !defined(WDC_NVME) || defined(CMD_HEADER_MULTI_READ)
 #define WDC_NVME
 
-#define WDC_PLUGIN_VERSION   "2.11.2"
+#define WDC_PLUGIN_VERSION   "2.12.0"
 #include "cmd.h"
 
 PLUGIN(NAME("wdc", "Western Digital vendor specific extensions", WDC_PLUGIN_VERSION),