diff --git a/tools/logger/CMakeLists.txt b/tools/logger/CMakeLists.txt index 20ade4ad9737..4f1fc9047adf 100644 --- a/tools/logger/CMakeLists.txt +++ b/tools/logger/CMakeLists.txt @@ -3,6 +3,8 @@ add_executable(sof-logger logger.c convert.c + filter.c + misc.c ) target_compile_options(sof-logger PRIVATE diff --git a/tools/logger/convert.c b/tools/logger/convert.c index 2fe837a0da4e..019b0768d800 100644 --- a/tools/logger/convert.c +++ b/tools/logger/convert.c @@ -5,7 +5,6 @@ // Author: Bartosz Kokoszko // Artur Kloniecki -#include #include #include #include @@ -16,6 +15,8 @@ #include #include #include "convert.h" +#include "filter.h" +#include "misc.h" #define CEIL(a, b) ((a+b-1)/b) @@ -52,67 +53,6 @@ struct proc_ldc_entry { static const char *BAD_PTR_STR = ""; -char *vasprintf(const char *format, va_list args) -{ - va_list args_copy; - int size; - char localbuf[1]; - char *result; - - va_copy(args_copy, args); - size = vsnprintf(localbuf, 1, format, args_copy); - va_end(args_copy); - - result = calloc(1, size + 1); - if (result) - vsnprintf(result, size + 1, format, args); - return result; -} - -char *asprintf(const char *format, ...) -{ - va_list args; - char *result; - - va_start(args, format); - result = vasprintf(format, args); - va_end(args); - - return result; -} - -static void log_err(FILE *out_fd, const char *fmt, ...) -{ - static const char prefix[] = "error: "; - ssize_t needed_size; - va_list args, args_alloc; - char *buff; - - va_start(args, fmt); - - va_copy(args_alloc, args); - needed_size = vsnprintf(NULL, 0, fmt, args_alloc) + 1; - buff = malloc(needed_size); - va_end(args_alloc); - - if (buff) { - vsprintf(buff, fmt, args); - fprintf(stderr, "%s%s", prefix, buff); - - /* take care about out_fd validity and duplicated logging */ - if (out_fd && out_fd != stderr && out_fd != stdout) { - fprintf(out_fd, "%s%s", prefix, buff); - fflush(out_fd); - } - free(buff); - } else { - fprintf(stderr, "%s", prefix); - vfprintf(stderr, fmt, args); - } - - va_end(args); -} - char *format_uid_raw(const struct sof_uuid_entry *uid_entry, int use_colors, int name_first) { @@ -141,6 +81,25 @@ get_uuid_entry(const struct snd_sof_uids_header *uids_dict, uint32_t uid_ptr) uids_dict->base_address); } +/* + * Use uids dictionary content, to convert address of uuid `entry` from logger + * memory space to corresponding uuid key address used in firmware trace system + * (with base UUID_ENTRY_ELF_BASE, 0x1FFFA000 as usual). Function get_uuid_entry + * works in oppopsite direction. + */ +uint32_t get_uuid_key(const struct snd_sof_uids_header *uids_dict, + const struct sof_uuid_entry *entry) +{ + /* + * uids_dict->data_offset and uids_dict->base_address are both constants, + * related with given ldc file. + * Uuid address used in firmware, points unusable memory region, + * so its treated as key value. + */ + return (uintptr_t)entry - (uintptr_t)uids_dict - + uids_dict->data_offset + uids_dict->base_address; +} + const char *format_uid(const struct snd_sof_uids_header *uids_dict, uint32_t uid_ptr, int use_colors) @@ -715,8 +674,7 @@ static int dump_ldc_info(struct convert_config *config, while (remaining > 0) { name = format_uid_raw(&uid_ptr[cnt], 0, 0); - uid_addr = (uintptr_t)&uid_ptr[cnt] - (uintptr_t)uids_dict - - uids_dict->data_offset + uids_dict->base_address; + uid_addr = get_uuid_key(uids_dict, &uid_ptr[cnt]); fprintf(out_fd, "\t0x%lX %s\n", uid_addr, name); if (name) { @@ -806,5 +764,15 @@ int convert(struct convert_config *config) if (config->dump_ldc) return dump_ldc_info(config, &snd); + if (config->filter_config) { + ret = filter_update_firmware(config->uids_dict, + config->filter_config); + if (ret) { + log_err(config->out_fd, + "failed to apply trace filter, %d.\n", ret); + return ret; + } + } + return logger_read(config, &snd); } diff --git a/tools/logger/convert.h b/tools/logger/convert.h index 8a966bfe4348..4aeb550e2f53 100644 --- a/tools/logger/convert.h +++ b/tools/logger/convert.h @@ -13,6 +13,7 @@ #include #include #include +#include #define KNRM "\x1B[0m" #define KRED "\x1B[31m" @@ -29,6 +30,7 @@ struct convert_config { int trace; const char *ldc_file; FILE* ldc_fd; + char *filter_config; int input_std; int version_fw; char *version_file; @@ -42,4 +44,6 @@ struct convert_config { struct snd_sof_uids_header *uids_dict; }; +uint32_t get_uuid_key(const struct snd_sof_uids_header *uids_dict, + const struct sof_uuid_entry *entry); int convert(struct convert_config *config); diff --git a/tools/logger/filter.c b/tools/logger/filter.c new file mode 100644 index 000000000000..f1396d5e9759 --- /dev/null +++ b/tools/logger/filter.c @@ -0,0 +1,333 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright(c) 2020 Intel Corporation. All rights reserved. + * + * Author: Karol Trzcinski + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "convert.h" +#include "filter.h" +#include "misc.h" + +#define COMPONENTS_SEPARATOR ',' +#define COMPONENT_NAME_SCAN_STRING_LENGTH 32 + +/** map between log level given by user and enum value */ +static const struct { + const char name[16]; + int32_t log_level; +} log_level_dict[] = { + {"verbose", LOG_LEVEL_VERBOSE}, + {"debug", LOG_LEVEL_DEBUG}, + {"info", LOG_LEVEL_INFO}, + {"warning", LOG_LEVEL_WARNING}, + {"error", LOG_LEVEL_ERROR}, + {"critical", LOG_LEVEL_CRITICAL}, + {"v", LOG_LEVEL_VERBOSE}, + {"d", LOG_LEVEL_DEBUG}, + {"i", LOG_LEVEL_INFO}, + {"w", LOG_LEVEL_WARNING}, + {"e", LOG_LEVEL_ERROR}, + {"c", LOG_LEVEL_CRITICAL}, +}; + +struct filter_element { + struct list_item list; + int32_t uuid_id; /**< type id, or -1 when not important */ + int32_t comp_id; /**< component id or -1 when not important */ + int32_t pipe_id; /**< pipeline id or -1 when not important */ + int32_t log_level; /**< new log level value */ +}; + +/** + * Search for uuid entry with given component name in given uids dictionary + * @param uids_dict dictionary to search in + * @param name of uuid entry + * @return pointer to sof_uuid_entry with given name + */ +static struct sof_uuid_entry * +get_uuid_by_name(const struct snd_sof_uids_header *uids_dict, + const char *name) +{ + uintptr_t beg = (uintptr_t)uids_dict + uids_dict->data_offset; + uintptr_t end = beg + uids_dict->data_length; + struct sof_uuid_entry *ptr = (struct sof_uuid_entry *)beg; + + while ((uintptr_t)ptr < end) { + if (strcmp(name, ptr->name) == 0) + return ptr; + ++ptr; + } + return NULL; +} + +/** + * Parse log level name (from log_level_dict) to enum value. + * Take care about possible whitespace at the begin. + * @param value_start pointer to the begin of range to search + * @return enum value for given log level, or -1 for invalid value + */ +static int filter_parse_log_level(const char *value_start) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(log_level_dict); ++i) { + if (strstr(log_level_dict[i].name, value_start)) + return log_level_dict[i].log_level; + } + return -1; +} + +static char *filter_parse_component_name(const struct snd_sof_uids_header *uids_dict, + char *input_str, + struct filter_element *out) +{ + static char scan_format_string[COMPONENT_NAME_SCAN_STRING_LENGTH] = ""; + char comp_name[UUID_NAME_MAX_LEN]; + struct sof_uuid_entry *uuid_entry; + int ret; + + /* if component name is not specified, stay with default out->uuid_id value */ + if (input_str[0] == '*') + return &input_str[1]; + + /* + * Take care about buffer overflows when dealing with input from + * user, so scan no more than UUID_NAME_MAX_LEN bytes to + * `comp_name` variable. Only once initialise scan_format_string. + */ + if (strlen(scan_format_string) == 0) { + ret = snprintf(scan_format_string, sizeof(scan_format_string), + "%%%d[^0-9* ]s", UUID_NAME_MAX_LEN); + if (ret <= 0) + return NULL; + } + ret = sscanf(input_str, scan_format_string, comp_name); + if (ret <= 0) + return NULL; + + /* find component uuid key */ + uuid_entry = get_uuid_by_name(uids_dict, comp_name); + if (!uuid_entry) { + log_err(NULL, "unknown component name `%s`\n", comp_name); + return NULL; + } + out->uuid_id = get_uuid_key(uids_dict, uuid_entry); + return strstr(input_str, comp_name) + strlen(comp_name); +} + +/** + * Parse component definition from input_str. + * + * Possible input_str formats: + * `name pipe_id.comp_id` + * `name pipe_id.*` + * `name *` + * `name` + * `* pipe_id.comp_id` + * `* pipe_id.*` + * `*` + * Whitespace is possible at the begin, end and after `name`. + * `name` must refer to values from given UUID dictionary, + * (so name comes from DECLARE_SOF_UUID macro usage) + + * @param uids_dict dictionary with list of possible `name` values + * @param input_str formatted component definition + * @param out element where component definition should be saved + */ +static int filter_parse_component(const struct snd_sof_uids_header *uids_dict, + char *input_str, + struct filter_element *out) +{ + char *instance_info; + int ret; + + /* trim whitespaces, to easily check first and last char */ + input_str = trim(input_str); + + /* assign default values */ + out->uuid_id = 0; + out->pipe_id = -1; + out->comp_id = -1; + + /* parse component name and store pointer after component name, pointer to instance info */ + instance_info = filter_parse_component_name(uids_dict, input_str, out); + if (!instance_info) { + log_err(NULL, "component name parsing `%s`\n", + input_str); + return -EINVAL; + } + + /* if instance is not specified then stop parsing */ + instance_info = ltrim(instance_info); + if (instance_info[0] == '\0' || + (instance_info[0] == '*' && instance_info[1] == '\0')) { + return 0; + } + + /* now parse last part: `number.x` where x is a number or `*` */ + ret = sscanf(instance_info, "%d.%d", &out->pipe_id, &out->comp_id); + if (ret == 2) + return 0; + else if (ret != 1) + return -EINVAL; + + /* pipeline id parsed but component id is not a number */ + if (instance_info[strlen(instance_info) - 1] == '*') + return 0; + log_err(NULL, "Use * to specify each component on particular pipeline\n"); + return -EINVAL; +} + +/** + * Convert argument from -F flag to sof_ipc_dma_trace_filter_elem struct values. + * + * Possible log_level - see filter_parse_log_level() documentation + * Possible component - list of components separated by `COMPONENTS_SEPARATOR`, + * for single component definition description look at + * filter_parse_component() documentation + * + * Examples: + * `debug="pipe1"` - set debug log level for components from pipeline1 + * `d="pipe1, dai2.3"` - as above, but also for dai2.3 + * `error="FIR*"` - for each FIR component set log level to error + * + * @param dictionary uuid dictionary from ldc file + * @param input_str log level settings in format `log_level=component` + * @param out_list output list with filter_element elements + */ +static int filter_parse_entry(const struct snd_sof_uids_header *uids_dict, + char *input_str, struct list_item *out_list) +{ + struct filter_element *filter; + char *comp_fmt_end; + int32_t log_level; + char *comp_fmt; + int ret; + + /* + * split string on '=' char, left part describes log level, + * the right one is for component description. + */ + comp_fmt = strchr(input_str, '='); + if (!comp_fmt) { + log_err(NULL, "unable to find `=` in `%s`\n", input_str); + return -EINVAL; + } + *comp_fmt = 0; + ++comp_fmt; + + /* find correct log level in given conf string - string before `=` */ + log_level = filter_parse_log_level(input_str); + if (log_level < 0) { + log_err(NULL, "unable to parse log level from `%s`\n", + input_str); + return log_level; + } + + /* + * now parse list of components name and optional instance identifier, + * split string on `COMPONENTS_SEPARATOR` + */ + while (comp_fmt) { + filter = malloc(sizeof(struct filter_element)); + if (!filter) { + log_err(NULL, "unable to malloc memory\n"); + return -ENOMEM; + } + + comp_fmt_end = strchr(comp_fmt, COMPONENTS_SEPARATOR); + if (comp_fmt_end) + *comp_fmt_end = '\0'; + ret = filter_parse_component(uids_dict, comp_fmt, filter); + if (ret < 0) { + log_err(NULL, "unable to parse component from `%s`\n", + comp_fmt); + free(filter); + return ret; + } + filter->log_level = log_level; + + list_item_append(&filter->list, out_list); + comp_fmt = comp_fmt_end ? comp_fmt_end + 1 : 0; + } + + return 0; +} + +/** + * Parse `input_str` content and send it to FW via debugFS. + * + * `input_str` contain single filter definition element per line. + * Each line is parsed by `filter_parse_entry`, and saved in list. + * List of `sof_ipc_dma_trace_filter_elem` is writend to debugFS, + * and then send as IPC to FW (this action is implemented in driver). + * Each line in debugFS represents single IPC message. + * + * @param dictionary uuid dictionary from ldc file + * @param format log level settings in format `log_level=component` + */ +int filter_update_firmware(const struct snd_sof_uids_header *uids_dict, + char *input_str) +{ + struct filter_element *filter; + struct list_item filter_list; + struct list_item *list_elem; + struct list_item *list_temp; + char *line_end; + FILE *out_fd; + int ret = 0; + + list_init(&filter_list); + + /* parse `input_str` line by line */ + line_end = strchr(input_str, '\n'); + while (line_end) { + line_end[0] = '\0'; + ret = filter_parse_entry(uids_dict, input_str, &filter_list); + if (ret < 0) + goto err; + input_str = line_end + 1; + line_end = strchr(input_str, '\n'); + } + + /* write output to debugFS */ + out_fd = fopen(FILTER_KERNEL_PATH, "w"); + if (!out_fd) { + log_err(NULL, "Unable to open out file '%s'\n", + FILTER_KERNEL_PATH); + ret = -errno; + goto err; + } + + list_for_item(list_elem, &filter_list) { + filter = container_of(list_elem, struct filter_element, + list); + fprintf(out_fd, "%d %X %d %d;", filter->log_level, + filter->uuid_id, filter->pipe_id, filter->comp_id); + } + fprintf(stdout, "\n"); + +err: + if (out_fd) + fclose(out_fd); + + /* free each component from parsed element list */ + list_for_item_safe(list_elem, list_temp, &filter_list) { + filter = container_of(list_elem, struct filter_element, + list); + free(filter); + } + + return ret; +} diff --git a/tools/logger/filter.h b/tools/logger/filter.h new file mode 100644 index 000000000000..63da8f30a5da --- /dev/null +++ b/tools/logger/filter.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright(c) 2020 Intel Corporation. All rights reserved. + * + * Author: Karol Trzcinski + */ + +#ifndef __LOGGER_FILTER_H__ +#define __LOGGER_FILTER_H__ + +#define FILTER_KERNEL_PATH "/sys/kernel/debug/sof/filter" + +int filter_update_firmware(const struct snd_sof_uids_header *uids_dict, + char *input_str); + +#endif /* __LOGGER_FILTER_H__ */ diff --git a/tools/logger/logger.c b/tools/logger/logger.c index bf82b1954f52..0f5445dd181f 100644 --- a/tools/logger/logger.c +++ b/tools/logger/logger.c @@ -15,6 +15,7 @@ #include #include #include "convert.h" +#include "misc.h" #define APP_NAME "sof-logger" @@ -59,6 +60,8 @@ static void usage(void) APP_NAME); fprintf(stdout, "%s:\t -d *.ldc_file \t\tDump ldc_file information\n", APP_NAME); + fprintf(stdout, "%s:\t -F path\t\tUpdate trace filtering\n", + APP_NAME); exit(0); } @@ -140,9 +143,25 @@ static int configure_uart(const char *file, unsigned int baud) return ret < 0 ? -errno : fd; } +/* Concantenate `config->filter_config` with `input` + `\n` */ +static int append_filter_config(struct convert_config *config, const char *input) +{ + char *old_config = config->filter_config; + + /* filer_config can't be NULL for following steps */ + if (!old_config) + config->filter_config = asprintf(""); + + config->filter_config = asprintf("%s%s\n", config->filter_config, input); + free(old_config); + if (!config->filter_config) + return -ENOMEM; + return 0; +} + int main(int argc, char *argv[]) { - static const char optstring[] = "ho:i:l:ps:c:u:tev:rd:Lf:g"; + static const char optstring[] = "ho:i:l:ps:c:u:tev:rd:Lf:gF:"; struct convert_config config; unsigned int baud = 0; const char *snapshot_file = 0; @@ -167,6 +186,7 @@ int main(int argc, char *argv[]) config.dump_ldc = 0; config.hide_location = 0; config.time_precision = 6; + config.filter_config = NULL; while ((opt = getopt(argc, argv, optstring)) != -1) { switch (opt) { @@ -234,6 +254,11 @@ int main(int argc, char *argv[]) config.dump_ldc = 1; config.ldc_file = optarg; break; + case 'F': + ret = append_filter_config(&config, optarg); + if (ret < 0) + return ret; + break; case 'h': default: /* '?' */ usage(); @@ -309,6 +334,10 @@ int main(int argc, char *argv[]) ret = -convert(&config); out: + /* free memory */ + if (config.filter_config) + free(config.filter_config); + /* close files */ if (config.out_fd) fclose(config.out_fd); diff --git a/tools/logger/misc.c b/tools/logger/misc.c new file mode 100644 index 000000000000..d17e7fe4c5ce --- /dev/null +++ b/tools/logger/misc.c @@ -0,0 +1,99 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright(c) 2020 Intel Corporation. All rights reserved. + * + * Author: Karol Trzcinski + */ + +#include +#include +#include +#include +#include + +char *vasprintf(const char *format, va_list args) +{ + va_list args_copy; + int size; + char localbuf[1]; + char *result; + + va_copy(args_copy, args); + size = vsnprintf(localbuf, 1, format, args_copy); + va_end(args_copy); + + result = malloc(size + 1); + if (result) + vsnprintf(result, size + 1, format, args); + return result; +} + +char *asprintf(const char *format, ...) +{ + va_list args; + char *result; + + va_start(args, format); + result = vasprintf(format, args); + va_end(args); + + return result; +} + +void log_err(FILE *out_fd, const char *fmt, ...) +{ + static const char prefix[] = "error: "; + ssize_t needed_size; + va_list args, args_alloc; + char *buff; + + va_start(args, fmt); + + va_copy(args_alloc, args); + needed_size = vsnprintf(NULL, 0, fmt, args_alloc) + 1; + buff = malloc(needed_size); + va_end(args_alloc); + + if (buff) { + vsprintf(buff, fmt, args); + fprintf(stderr, "%s%s", prefix, buff); + + /* take care about out_fd validity and duplicated logging */ + if (out_fd && out_fd != stderr && out_fd != stdout) { + fprintf(out_fd, "%s%s", prefix, buff); + fflush(out_fd); + } + free(buff); + } else { + fprintf(stderr, "%s", prefix); + vfprintf(stderr, fmt, args); + } + + va_end(args); +} + +/* trim whitespaces from string begin */ +char *ltrim(char *s) +{ + while (isspace(*s)) + s++; + return s; +} + +/* trim whitespaces from string end */ +char *rtrim(char *s) +{ + char *ptr = s + strlen(s) - 1; + + while (ptr >= s && isspace(*ptr)) { + *ptr = '\0'; + --ptr; + } + return s; +} + +/* trim whitespaces from string begin and end*/ +char *trim(char *s) +{ + return ltrim(rtrim(s)); +} diff --git a/tools/logger/misc.h b/tools/logger/misc.h new file mode 100644 index 000000000000..5e30005584e7 --- /dev/null +++ b/tools/logger/misc.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright(c) 2020 Intel Corporation. All rights reserved. + * + * Author: Karol Trzcinski + */ + +#include +#include + +char *vasprintf(const char *format, va_list args); +char *asprintf(const char *format, ...); + +void log_err(FILE *out_fd, const char *fmt, ...); + +/* trim whitespaces from string begin */ +char *ltrim(char *s); + +/* trim whitespaces from string end */ +char *rtrim(char *s); + +/* trim whitespaces from string begin and end*/ +char *trim(char *s);