diff --git a/plugins/solidigm/solidigm-telemetry/config.c b/plugins/solidigm/solidigm-telemetry/config.c index aa8afa507e..3b27d27390 100644 --- a/plugins/solidigm/solidigm-telemetry/config.c +++ b/plugins/solidigm/solidigm-telemetry/config.c @@ -8,18 +8,20 @@ #include #include #include -#include #include #include "config.h" +#define NOT_FOUND "NOT_FOUND" + // max 16 bit unsigned integer number 65535 #define MAX_16BIT_NUM_AS_STRING_SIZE 6 #define OBJ_NAME_PREFIX "UID_" #define NLOG_OBJ_PREFIX OBJ_NAME_PREFIX "NLOG_" -static bool config_get_by_version(const struct json_object *obj, int version_major, - int version_minor, struct json_object **value) +static bool config_get_by_version(const struct json_object *obj, + int version_major, int version_minor, + struct json_object **value) { char str_key[MAX_16BIT_NUM_AS_STRING_SIZE]; char str_subkey[MAX_16BIT_NUM_AS_STRING_SIZE]; @@ -58,6 +60,7 @@ bool sldm_config_get_struct_by_key_version(const struct json_object *config, cha struct json_object **value) { struct json_object *token = NULL; + if (!json_object_object_get_ex(config, key, &token)) return false; if (!config_get_by_version(token, version_major, version_minor, value)) @@ -90,7 +93,7 @@ const char *solidigm_config_get_nlog_obj_name(const struct json_object *config, if (!json_object_object_get_ex(nlog_names, hex_header, &obj_name)) return NULL; name = json_object_get_string(obj_name); - if ((!name) || (strncmp(NLOG_OBJ_PREFIX, name, strlen(NLOG_OBJ_PREFIX)))) + if (!name || strncmp(NLOG_OBJ_PREFIX, name, strlen(NLOG_OBJ_PREFIX))) return NULL; return &name[strlen(OBJ_NAME_PREFIX)]; @@ -103,3 +106,99 @@ struct json_object *solidigm_config_get_nlog_formats(const struct json_object *c json_object_object_get_ex(config, "NLOG_FORMATS", &nlog_formats); return nlog_formats; } + +/* Enum value lookup helper functions */ +static const char *find_enum_value_in_member_list( + struct json_object *enum_member_list, + uint64_t value) +{ + if (!json_object_is_type(enum_member_list, json_type_array)) + return UNKNOWN_ENUM_VALUE; + + int enum_array_len = json_object_array_length(enum_member_list); + + for (int j = 0; j < enum_array_len; j++) { + struct json_object *enum_item = + json_object_array_get_idx(enum_member_list, j); + + if (!enum_item) + continue; + + json_object_object_foreach(enum_item, key, val) { + (void)key; /* Suppress unused variable warning */ + if (json_object_get_uint64(val) == value) + return key; + } + } + return UNKNOWN_ENUM_VALUE; +} + +static bool is_target_enum_field(struct json_object *member, + const char *enum_field_name) +{ + struct json_object *name_obj, *enum_obj; + + if (!json_object_object_get_ex(member, "name", &name_obj) || + !json_object_object_get_ex(member, "enum", &enum_obj)) + return false; + + const char *name = json_object_get_string(name_obj); + int is_enum = json_object_get_int(enum_obj); + + return (strcmp(name, enum_field_name) == 0 && is_enum == 1); +} + +static const char *search_enum_in_member(struct json_object *member, + const char *enum_field_name, + uint64_t value) +{ + if (!is_target_enum_field(member, enum_field_name)) + return NOT_FOUND; + + /* Found the enum field, look for the value in its memberList */ + struct json_object *enum_member_list; + + if (!json_object_object_get_ex(member, "memberList", &enum_member_list)) + return UNKNOWN_ENUM_VALUE; + + return find_enum_value_in_member_list(enum_member_list, value); +} + +const char *sldm_get_enum_label_by_value(struct json_object *struct_def, + const char *enum_field_name, + uint64_t value) +{ + struct json_object *member_list; + + if (!json_object_object_get_ex(struct_def, "memberList", &member_list)) + return UNKNOWN_ENUM_VALUE; + + if (!json_object_is_type(member_list, json_type_array)) + return UNKNOWN_ENUM_VALUE; + + int array_len = json_object_array_length(member_list); + + for (int i = 0; i < array_len; i++) { + struct json_object *member = + json_object_array_get_idx(member_list, i); + + if (!member) + continue; + + /* Try to find enum in this member */ + const char *result = search_enum_in_member(member, + enum_field_name, + value); + + if (strcmp(result, NOT_FOUND) != 0) + return result; /* Found it or determined it's unknown */ + + /* If this member has nested members, search recursively */ + result = sldm_get_enum_label_by_value(member, enum_field_name, + value); + if (strcmp(result, UNKNOWN_ENUM_VALUE) != 0) + return result; + } + + return UNKNOWN_ENUM_VALUE; +} diff --git a/plugins/solidigm/solidigm-telemetry/config.h b/plugins/solidigm/solidigm-telemetry/config.h index b12cee2236..781249cd1b 100644 --- a/plugins/solidigm/solidigm-telemetry/config.h +++ b/plugins/solidigm/solidigm-telemetry/config.h @@ -8,6 +8,7 @@ #include "util/json.h" #define STR_HEX32_SIZE sizeof("0x00000000") +#define UNKNOWN_ENUM_VALUE "UNKNOWN_ENUM_VALUE" bool sldm_config_get_struct_by_key_version(const struct json_object *config, char *key, int version_major, int version_minor, @@ -19,4 +20,7 @@ bool solidigm_config_get_struct_by_token_version(const struct json_object *obj, const char *solidigm_config_get_nlog_obj_name(const struct json_object *config, uint32_t token); struct json_object *solidigm_config_get_nlog_formats(const struct json_object *config); +const char *sldm_get_enum_label_by_value(struct json_object *struct_def, + const char *enum_field_name, + uint64_t value); diff --git a/plugins/solidigm/solidigm-telemetry/data-area.c b/plugins/solidigm/solidigm-telemetry/data-area.c index 1a7209e409..a36a0c79b0 100644 --- a/plugins/solidigm/solidigm-telemetry/data-area.c +++ b/plugins/solidigm/solidigm-telemetry/data-area.c @@ -12,6 +12,7 @@ #include "config.h" #include "nlog.h" #include "skht.h" +#include "side-trace.h" #include #define SIGNED_int_PREFIX "int" @@ -579,6 +580,40 @@ static void telemetry_log_data_area_toc_parse(const struct telemetry_log *tl, toc->items[i].ContentSizeBytes - header_offset, parsed_struct, toc_item); } + + // Check if this is a side trace object + struct json_object *obj_name = NULL; + + if (json_object_object_get_ex(toc_item, "objName", &obj_name)) { + const char *obj_name_str = + json_object_get_string(obj_name); + + if (obj_name_str && + (strstr(obj_name_str, "sideTrace") || + strstr(obj_name_str, "SideTrace") || + strstr(obj_name_str, "SIDETRACE"))) { + /* This is a side trace object, parse it when + * configuration is available. We want to + * replace array member fwSideTrace by parsed + * entries. + */ + struct json_object *first_dic = NULL; + + json_object_object_foreach(parsed_struct, key, + val) { + (void)key; + if (json_object_is_type(val, + json_type_object)) { + first_dic = val; + break; + } + } + sldm_parse_side_trace(tl, object_file_offset, + toc->items[i] + .ContentSizeBytes, + first_dic, toc_item); + } + } } } diff --git a/plugins/solidigm/solidigm-telemetry/meson.build b/plugins/solidigm/solidigm-telemetry/meson.build index dce6175223..9f2368614d 100644 --- a/plugins/solidigm/solidigm-telemetry/meson.build +++ b/plugins/solidigm/solidigm-telemetry/meson.build @@ -8,4 +8,5 @@ plugin_sources += [ 'plugins/solidigm/solidigm-telemetry/skht.c', 'plugins/solidigm/solidigm-telemetry/debug-info.c', 'plugins/solidigm/solidigm-telemetry/uart-log.c', + 'plugins/solidigm/solidigm-telemetry/side-trace.c', ] diff --git a/plugins/solidigm/solidigm-telemetry/side-trace.c b/plugins/solidigm/solidigm-telemetry/side-trace.c new file mode 100644 index 0000000000..c289e33d81 --- /dev/null +++ b/plugins/solidigm/solidigm-telemetry/side-trace.c @@ -0,0 +1,360 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (c) 2026 Solidigm. + * + * Author: leonardo.da.cunha@solidigm.com + */ + +#include "common.h" +#include "telemetry-log.h" +#include "side-trace.h" +#include "data-area.h" +#include "header.h" +#include "config.h" +#include +#include + +#define NUM_BITS_IN_BYTE 8 +#define SIDETRACE_BLOCK_SIZE 256 +#define SIDETRACE_PAYLOAD_OFFSET 32 +#define SIDETRACE_STACK_SIZE_MAX 224 +#define SIDETRACE_EMPTY_TOKEN 0xFFFF + +/* Side trace parsing context */ +struct side_trace_parser_context { + const struct telemetry_log *tl; + uint64_t file_offset; + uint32_t size_bytes; + struct json_object *output; + struct json_object *metadata; +}; + +static struct json_object *find_nested_object_with_field( + struct json_object *parent, + const char *field_name) +{ + struct json_object *nested_obj = NULL; + + json_object_object_foreach(parent, key, val) { + (void)key; /* Suppress unused variable warning */ + struct json_object *test_obj; + + if (json_object_object_get_ex(val, field_name, &test_obj)) { + nested_obj = val; + break; + } + } + + return nested_obj; +} + +static bool extract_header_fields(struct json_object *nested_obj, + uint8_t *major_rev, + uint8_t *minor_rev, + uint16_t *token_id) +{ + struct json_object *jobj; + bool found_any = false; + + if (json_object_object_get_ex(nested_obj, "majorRev", &jobj)) { + *major_rev = (uint8_t) json_object_get_uint64(jobj); + found_any = true; + } + + if (json_object_object_get_ex(nested_obj, "minorRev", &jobj)) { + *minor_rev = (uint8_t) json_object_get_uint64(jobj); + found_any = true; + } + + if (json_object_object_get_ex(nested_obj, "tokenId", &jobj)) { + *token_id = (uint16_t) json_object_get_uint64(jobj); + found_any = true; + } + + return found_any; +} + +static bool parse_header_structure(struct json_object *temp_obj, + uint8_t *major_rev, + uint8_t *minor_rev, + uint16_t *token_id, + uint8_t *payload_size) +{ + struct json_object *header_struct_obj, *common_obj; + + /* First navigate through the "trimmedSideTraceBufferEntryHeader" + * top-level key + */ + if (!json_object_object_get_ex(temp_obj, + "trimmedSideTraceBufferEntryHeader", + &header_struct_obj)) + return false; + + /* Look for the "common" substructure within the header structure */ + if (json_object_object_get_ex(header_struct_obj, "common", + &common_obj)) { + /* Search within "common" for the nested structure that + * contains our fields + */ + struct json_object *nested_obj = + find_nested_object_with_field(common_obj, "majorRev"); + + if (nested_obj) + extract_header_fields(nested_obj, major_rev, + minor_rev, token_id); + } + + /* Also look for payload size field dynamically */ + struct json_object *payload_size_obj; + + if (json_object_object_get_ex(header_struct_obj, "payloadSize", + &payload_size_obj)) + *payload_size = (uint8_t) + json_object_get_uint64(payload_size_obj); + + return true; +} + +static int parse_side_trace_entry(struct side_trace_parser_context *ctx, + uint64_t offset_bytes, int entry_count, + struct json_object *entries_array) +{ + const uint8_t *data_ptr; + struct json_object *entry_obj, *header_obj; + struct json_object *structure_definition = NULL; + bool has_struct = false; + uint8_t major_rev = 0, minor_rev = 0; + uint16_t token_id = 0; + /* Store header_struct for token enum lookup */ + struct json_object *token_header_struct = NULL; + int ret = 0; + uint8_t payload_size = 0; /* Initialize payload size */ + uint32_t header_size = 0; /* Initialize header size */ + + if (offset_bytes + SIDETRACE_BLOCK_SIZE > ctx->tl->log_size) { + SOLIDIGM_LOG_WARNING( + "Side trace entry %d exceeds log size", + entry_count); + return -1; + } + + data_ptr = (const uint8_t *)ctx->tl->log + ctx->file_offset + + offset_bytes; + + /* First try to get the header structure to read basic header info */ + struct json_object *header_struct = NULL; + bool has_header = sldm_config_get_struct_by_key_version( + ctx->tl->configuration, + "trimmedSideTraceBufferEntryHeader", + 8, 8, &header_struct); + + /* Calculate header size from struct definition's sizeBit field */ + if (has_header && header_struct) { + struct json_object *size_bit_obj; + + if (json_object_object_get_ex(header_struct, "sizeBit", + &size_bit_obj)) + header_size = (uint32_t) + json_object_get_uint64(size_bit_obj) / + NUM_BITS_IN_BYTE; + /* Store header_struct for token enum lookup */ + token_header_struct = header_struct; + + /* Use dynamic parsing to extract header fields */ + struct json_object *temp_obj = json_object_new_object(); + int parse_ret = sldm_telemetry_structure_parse( + ctx->tl, header_struct, + NUM_BITS_IN_BYTE * + (ctx->file_offset + + offset_bytes), + temp_obj, NULL); + + if (parse_ret == 0) + parse_header_structure(temp_obj, &major_rev, + &minor_rev, &token_id, + &payload_size); + + json_object_put(temp_obj); + } + + if (token_id == SIDETRACE_EMPTY_TOKEN) { + /* Skip empty entries */ + return 0; + } + + /* Check if token_id is valid by attempting enum lookup */ + const char *token_name = "UNKNOWN_TOKEN"; + + if (token_header_struct) { + token_name = sldm_get_enum_label_by_value( + token_header_struct, + "tokenId", token_id); + if (strncmp(token_name, UNKNOWN_ENUM_VALUE, + strlen(UNKNOWN_ENUM_VALUE)) == 0) { + /* Invalid token, stop processing */ + return 1; /* Signal end of valid entries */ + } + } + + entry_obj = json_object_new_object(); + + /* Add entry metadata */ + json_object_add_value_uint(entry_obj, "entryNumber", entry_count); + json_object_add_value_uint64(entry_obj, "offsetBytes", offset_bytes); + + /* Use already discovered token name */ + json_object_object_add(entry_obj, "tokenName", + json_object_new_string(token_name)); + + /* Determine which structure definition to use based on major + * revision + */ + if (major_rev >= 128) { + /* Trimmed entry - use preparsed header structure */ + json_object_object_add(entry_obj, "entryType", + json_object_new_string("trimmed")); + if (has_header && header_struct) { + structure_definition = header_struct; + has_struct = true; + } + } else { + /* Full entry */ + json_object_object_add(entry_obj, "entryType", + json_object_new_string("full")); + has_struct = sldm_config_get_struct_by_key_version( + ctx->tl->configuration, + "sideTraceBufferEntry", + 6, 0, &structure_definition); + } + + if (has_struct && structure_definition) { + /* Parse using dynamic structure definition */ + header_obj = json_object_new_object(); + json_object_object_add(entry_obj, "parsedData", header_obj); + + ret = sldm_telemetry_structure_parse( + ctx->tl, structure_definition, + NUM_BITS_IN_BYTE * + (ctx->file_offset + + offset_bytes), + header_obj, NULL); + if (ret < 0) + SOLIDIGM_LOG_WARNING( + "Failed to parse side trace structure for entry %d", + entry_count); + + /* Add raw payload data array for additional analysis */ + if (major_rev >= 128) { + /* For trimmed entries, add payload data */ + if (header_size > 0 && + (offset_bytes + header_size) < ctx->size_bytes) { + /* payload starts after header */ + uint32_t payload_start = header_size; + + if (payload_size > 0 && + (offset_bytes + payload_start + + payload_size) <= ctx->size_bytes) { + struct json_object *payload_obj = + json_object_new_object(); + + json_object_object_add(entry_obj, + "payload", + payload_obj); + + struct json_object *raw_array = + json_create_array(); + + json_object_object_add(payload_obj, + "rawDataArray", + raw_array); + + for (uint8_t i = 0; i < payload_size; + i++) { + json_object_array_add( + raw_array, + json_object_new_uint64( + data_ptr[ + payload_start + + i])); + } + } + } + } + json_object_array_add(entries_array, entry_obj); + } else + json_object_put(entry_obj); + + return ret; +} + +int sldm_parse_side_trace(const struct telemetry_log *tl, + uint64_t file_offset, + uint32_t size_bytes, + struct json_object *output, + struct json_object *metadata) +{ + struct side_trace_parser_context ctx = { + .tl = tl, + .file_offset = file_offset, + .size_bytes = size_bytes, + .output = output, + .metadata = metadata + }; + + uint64_t offset_bytes = 0; + int entry_count = 0; + int total_entries = 0; + + /* Boundary check */ + if (tl->log_size < (file_offset + size_bytes)) { + const char *name = ""; + int media_bank = -1; + struct json_object *jobj; + + if (json_object_object_get_ex(metadata, "objName", &jobj)) + name = json_object_get_string(jobj); + if (json_object_object_get_ex(metadata, "mediaBankId", &jobj)) + media_bank = json_object_get_int(jobj); + + SOLIDIGM_LOG_WARNING("%s:%d do not fit this log dump.", + name, media_bank); + return -1; + } + + /* Add parsing metadata */ + json_object_object_add(metadata, "parseType", + json_object_new_string("sideTrace")); + json_object_add_value_uint(metadata, "fileSizeBytes", size_bytes); + json_object_add_value_uint(metadata, "blockSize", SIDETRACE_BLOCK_SIZE); + + /* Create entries array */ + struct json_object *entries_array = json_create_array(); + + /* Parse side trace entries in 256-byte blocks */ + while (offset_bytes < size_bytes) { + int ret = parse_side_trace_entry(&ctx, offset_bytes, + entry_count, entries_array); + + if (ret < 0) { + /* Error occurred */ + SOLIDIGM_LOG_WARNING( + "Error parsing side trace entry %d", + entry_count); + break; + } else if (ret > 0) { + /* End of valid entries */ + break; + } + entry_count++; + + offset_bytes += SIDETRACE_BLOCK_SIZE; + } + // Replace fwSideTrace only if we have entries + total_entries = json_object_array_length(entries_array); + if (total_entries > 0) { + json_object_object_add(output, "fwSideTrace", entries_array); + json_object_add_value_uint(metadata, "totalEntriesParsed", + total_entries); + } + return 0; +} diff --git a/plugins/solidigm/solidigm-telemetry/side-trace.h b/plugins/solidigm/solidigm-telemetry/side-trace.h new file mode 100644 index 0000000000..67d341f9db --- /dev/null +++ b/plugins/solidigm/solidigm-telemetry/side-trace.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2026 Solidigm. + * + * Author: leonardo.da.cunha@solidigm.com + */ + +#include "telemetry-log.h" + +int sldm_parse_side_trace(const struct telemetry_log *tl, + uint64_t file_offset, + uint32_t size_bytes, + struct json_object *output, + struct json_object *metadata); +