diff options
Diffstat (limited to 'tools')
148 files changed, 11325 insertions, 1329 deletions
diff --git a/tools/lib/api/fs/fs.c b/tools/lib/api/fs/fs.c index 5b5eb788996e..c1b49c36a951 100644 --- a/tools/lib/api/fs/fs.c +++ b/tools/lib/api/fs/fs.c @@ -1,8 +1,10 @@ /* TODO merge/factor in debugfs.c here */ +#include <ctype.h> #include <errno.h> #include <stdbool.h> #include <stdio.h> +#include <stdlib.h> #include <string.h> #include <sys/vfs.h> @@ -96,12 +98,51 @@ static bool fs__check_mounts(struct fs *fs) return false; } +static void mem_toupper(char *f, size_t len) +{ + while (len) { + *f = toupper(*f); + f++; + len--; + } +} + +/* + * Check for "NAME_PATH" environment variable to override fs location (for + * testing). This matches the recommendation in Documentation/sysfs-rules.txt + * for SYSFS_PATH. + */ +static bool fs__env_override(struct fs *fs) +{ + char *override_path; + size_t name_len = strlen(fs->name); + /* name + "_PATH" + '\0' */ + char upper_name[name_len + 5 + 1]; + memcpy(upper_name, fs->name, name_len); + mem_toupper(upper_name, name_len); + strcpy(&upper_name[name_len], "_PATH"); + + override_path = getenv(upper_name); + if (!override_path) + return false; + + fs->found = true; + strncpy(fs->path, override_path, sizeof(fs->path)); + return true; +} + static const char *fs__get_mountpoint(struct fs *fs) { + if (fs__env_override(fs)) + return fs->path; + if (fs__check_mounts(fs)) return fs->path; - return fs__read_mounts(fs) ? fs->path : NULL; + if (fs__read_mounts(fs)) + return fs->path; + + return NULL; } static const char *fs__mountpoint(int idx) diff --git a/tools/lib/traceevent/event-parse.c b/tools/lib/traceevent/event-parse.c index b83184f2d484..93825a17dcce 100644 --- a/tools/lib/traceevent/event-parse.c +++ b/tools/lib/traceevent/event-parse.c @@ -765,6 +765,9 @@ static void free_arg(struct print_arg *arg) case PRINT_BSTRING: free(arg->string.string); break; + case PRINT_BITMASK: + free(arg->bitmask.bitmask); + break; case PRINT_DYNAMIC_ARRAY: free(arg->dynarray.index); break; @@ -2268,6 +2271,7 @@ static int arg_num_eval(struct print_arg *arg, long long *val) case PRINT_FIELD ... PRINT_SYMBOL: case PRINT_STRING: case PRINT_BSTRING: + case PRINT_BITMASK: default: do_warning("invalid eval type %d", arg->type); ret = 0; @@ -2296,6 +2300,7 @@ static char *arg_eval (struct print_arg *arg) case PRINT_FIELD ... PRINT_SYMBOL: case PRINT_STRING: case PRINT_BSTRING: + case PRINT_BITMASK: default: do_warning("invalid eval type %d", arg->type); break; @@ -2683,6 +2688,35 @@ process_str(struct event_format *event __maybe_unused, struct print_arg *arg, return EVENT_ERROR; } +static enum event_type +process_bitmask(struct event_format *event __maybe_unused, struct print_arg *arg, + char **tok) +{ + enum event_type type; + char *token; + + if (read_expect_type(EVENT_ITEM, &token) < 0) + goto out_free; + + arg->type = PRINT_BITMASK; + arg->bitmask.bitmask = token; + arg->bitmask.offset = -1; + + if (read_expected(EVENT_DELIM, ")") < 0) + goto out_err; + + type = read_token(&token); + *tok = token; + + return type; + + out_free: + free_token(token); + out_err: + *tok = NULL; + return EVENT_ERROR; +} + static struct pevent_function_handler * find_func_handler(struct pevent *pevent, char *func_name) { @@ -2797,6 +2831,10 @@ process_function(struct event_format *event, struct print_arg *arg, free_token(token); return process_str(event, arg, tok); } + if (strcmp(token, "__get_bitmask") == 0) { + free_token(token); + return process_bitmask(event, arg, tok); + } if (strcmp(token, "__get_dynamic_array") == 0) { free_token(token); return process_dynamic_array(event, arg, tok); @@ -3324,6 +3362,7 @@ eval_num_arg(void *data, int size, struct event_format *event, struct print_arg return eval_type(val, arg, 0); case PRINT_STRING: case PRINT_BSTRING: + case PRINT_BITMASK: return 0; case PRINT_FUNC: { struct trace_seq s; @@ -3556,6 +3595,60 @@ static void print_str_to_seq(struct trace_seq *s, const char *format, trace_seq_printf(s, format, str); } +static void print_bitmask_to_seq(struct pevent *pevent, + struct trace_seq *s, const char *format, + int len_arg, const void *data, int size) +{ + int nr_bits = size * 8; + int str_size = (nr_bits + 3) / 4; + int len = 0; + char buf[3]; + char *str; + int index; + int i; + + /* + * The kernel likes to put in commas every 32 bits, we + * can do the same. + */ + str_size += (nr_bits - 1) / 32; + + str = malloc(str_size + 1); + if (!str) { + do_warning("%s: not enough memory!", __func__); + return; + } + str[str_size] = 0; + + /* Start out with -2 for the two chars per byte */ + for (i = str_size - 2; i >= 0; i -= 2) { + /* + * data points to a bit mask of size bytes. + * In the kernel, this is an array of long words, thus + * endianess is very important. + */ + if (pevent->file_bigendian) + index = size - (len + 1); + else + index = len; + + snprintf(buf, 3, "%02x", *((unsigned char *)data + index)); + memcpy(str + i, buf, 2); + len++; + if (!(len & 3) && i > 0) { + i--; + str[i] = ','; + } + } + + if (len_arg >= 0) + trace_seq_printf(s, format, len_arg, str); + else + trace_seq_printf(s, format, str); + + free(str); +} + static void print_str_arg(struct trace_seq *s, void *data, int size, struct event_format *event, const char *format, int len_arg, struct print_arg *arg) @@ -3691,6 +3784,23 @@ static void print_str_arg(struct trace_seq *s, void *data, int size, case PRINT_BSTRING: print_str_to_seq(s, format, len_arg, arg->string.string); break; + case PRINT_BITMASK: { + int bitmask_offset; + int bitmask_size; + + if (arg->bitmask.offset == -1) { + struct format_field *f; + + f = pevent_find_any_field(event, arg->bitmask.bitmask); + arg->bitmask.offset = f->offset; + } + bitmask_offset = data2host4(pevent, data + arg->bitmask.offset); + bitmask_size = bitmask_offset >> 16; + bitmask_offset &= 0xffff; + print_bitmask_to_seq(pevent, s, format, len_arg, + data + bitmask_offset, bitmask_size); + break; + } case PRINT_OP: /* * The only op for string should be ? : @@ -4822,6 +4932,9 @@ static void print_args(struct print_arg *args) case PRINT_BSTRING: printf("__get_str(%s)", args->string.string); break; + case PRINT_BITMASK: + printf("__get_bitmask(%s)", args->bitmask.bitmask); + break; case PRINT_TYPE: printf("(%s)", args->typecast.type); print_args(args->typecast.item); diff --git a/tools/lib/traceevent/event-parse.h b/tools/lib/traceevent/event-parse.h index feab94281634..7a3873ff9a4f 100644 --- a/tools/lib/traceevent/event-parse.h +++ b/tools/lib/traceevent/event-parse.h @@ -107,8 +107,8 @@ typedef int (*pevent_event_handler_func)(struct trace_seq *s, typedef int (*pevent_plugin_load_func)(struct pevent *pevent); typedef int (*pevent_plugin_unload_func)(struct pevent *pevent); -struct plugin_option { - struct plugin_option *next; +struct pevent_plugin_option { + struct pevent_plugin_option *next; void *handle; char *file; char *name; @@ -135,7 +135,7 @@ struct plugin_option { * PEVENT_PLUGIN_OPTIONS: (optional) * Plugin options that can be set before loading * - * struct plugin_option PEVENT_PLUGIN_OPTIONS[] = { + * struct pevent_plugin_option PEVENT_PLUGIN_OPTIONS[] = { * { * .name = "option-name", * .plugin_alias = "overide-file-name", (optional) @@ -208,6 +208,11 @@ struct print_arg_string { int offset; }; +struct print_arg_bitmask { + char *bitmask; + int offset; +}; + struct print_arg_field { char *name; struct format_field *field; @@ -274,6 +279,7 @@ enum print_arg_type { PRINT_DYNAMIC_ARRAY, PRINT_OP, PRINT_FUNC, + PRINT_BITMASK, }; struct print_arg { @@ -288,6 +294,7 @@ struct print_arg { struct print_arg_hex hex; struct print_arg_func func; struct print_arg_string string; + struct print_arg_bitmask bitmask; struct print_arg_op op; struct print_arg_dynarray dynarray; }; @@ -354,6 +361,8 @@ enum pevent_func_arg_type { enum pevent_flag { PEVENT_NSEC_OUTPUT = 1, /* output in NSECS */ + PEVENT_DISABLE_SYS_PLUGINS = 1 << 1, + PEVENT_DISABLE_PLUGINS = 1 << 2, }; #define PEVENT_ERRORS \ @@ -410,9 +419,19 @@ enum pevent_errno { struct plugin_list; +#define INVALID_PLUGIN_LIST_OPTION ((char **)((unsigned long)-1)) + struct plugin_list *traceevent_load_plugins(struct pevent *pevent); void traceevent_unload_plugins(struct plugin_list *plugin_list, struct pevent *pevent); +char **traceevent_plugin_list_options(void); +void traceevent_plugin_free_options_list(char **list); +int traceevent_plugin_add_options(const char *name, + struct pevent_plugin_option *options); +void traceevent_plugin_remove_options(struct pevent_plugin_option *options); +void traceevent_print_plugins(struct trace_seq *s, + const char *prefix, const char *suffix, + const struct plugin_list *list); struct cmdline; struct cmdline_list; diff --git a/tools/lib/traceevent/event-plugin.c b/tools/lib/traceevent/event-plugin.c index 0c8bf6780e4d..136162c03af1 100644 --- a/tools/lib/traceevent/event-plugin.c +++ b/tools/lib/traceevent/event-plugin.c @@ -18,6 +18,7 @@ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ +#include <stdio.h> #include <string.h> #include <dlfcn.h> #include <stdlib.h> @@ -30,12 +31,207 @@ #define LOCAL_PLUGIN_DIR ".traceevent/plugins" +static struct registered_plugin_options { + struct registered_plugin_options *next; + struct pevent_plugin_option *options; +} *registered_options; + +static struct trace_plugin_options { + struct trace_plugin_options *next; + char *plugin; + char *option; + char *value; +} *trace_plugin_options; + struct plugin_list { struct plugin_list *next; char *name; void *handle; }; +/** + * traceevent_plugin_list_options - get list of plugin options + * + * Returns an array of char strings that list the currently registered + * plugin options in the format of <plugin>:<option>. This list can be + * used by toggling the option. + * + * Returns NULL if there's no options registered. On error it returns + * INVALID_PLUGIN_LIST_OPTION + * + * Must be freed with traceevent_plugin_free_options_list(). + */ +char **traceevent_plugin_list_options(void) +{ + struct registered_plugin_options *reg; + struct pevent_plugin_option *op; + char **list = NULL; + char *name; + int count = 0; + + for (reg = registered_options; reg; reg = reg->next) { + for (op = reg->options; op->name; op++) { + char *alias = op->plugin_alias ? op->plugin_alias : op->file; + char **temp = list; + + name = malloc(strlen(op->name) + strlen(alias) + 2); + if (!name) + goto err; + + sprintf(name, "%s:%s", alias, op->name); + list = realloc(list, count + 2); + if (!list) { + list = temp; + free(name); + goto err; + } + list[count++] = name; + list[count] = NULL; + } + } + return list; + + err: + while (--count >= 0) + free(list[count]); + free(list); + + return INVALID_PLUGIN_LIST_OPTION; +} + +void traceevent_plugin_free_options_list(char **list) +{ + int i; + + if (!list) + return; + + if (list == INVALID_PLUGIN_LIST_OPTION) + return; + + for (i = 0; list[i]; i++) + free(list[i]); + + free(list); +} + +static int +update_option(const char *file, struct pevent_plugin_option *option) +{ + struct trace_plugin_options *op; + char *plugin; + + if (option->plugin_alias) { + plugin = strdup(option->plugin_alias); + if (!plugin) + return -1; + } else { + char *p; + plugin = strdup(file); + if (!plugin) + return -1; + p = strstr(plugin, "."); + if (p) + *p = '\0'; + } + + /* first look for named options */ + for (op = trace_plugin_options; op; op = op->next) { + if (!op->plugin) + continue; + if (strcmp(op->plugin, plugin) != 0) + continue; + if (strcmp(op->option, option->name) != 0) + continue; + + option->value = op->value; + option->set ^= 1; + goto out; + } + + /* first look for unnamed options */ + for (op = trace_plugin_options; op; op = op->next) { + if (op->plugin) + continue; + if (strcmp(op->option, option->name) != 0) + continue; + + option->value = op->value; + option->set ^= 1; + break; + } + + out: + free(plugin); + return 0; +} + +/** + * traceevent_plugin_add_options - Add a set of options by a plugin + * @name: The name of the plugin adding the options + * @options: The set of options being loaded + * + * Sets the options with the values that have been added by user. + */ +int traceevent_plugin_add_options(const char *name, + struct pevent_plugin_option *options) +{ + struct registered_plugin_options *reg; + + reg = malloc(sizeof(*reg)); + if (!reg) + return -1; + reg->next = registered_options; + reg->options = options; + registered_options = reg; + + while (options->name) { + update_option(name, options); + options++; + } + return 0; +} + +/** + * traceevent_plugin_remove_options - remove plugin options that were registered + * @options: Options to removed that were registered with traceevent_plugin_add_options + */ +void traceevent_plugin_remove_options(struct pevent_plugin_option *options) +{ + struct registered_plugin_options **last; + struct registered_plugin_options *reg; + + for (last = ®istered_options; *last; last = &(*last)->next) { + if ((*last)->options == options) { + reg = *last; + *last = reg->next; + free(reg); + return; + } + } +} + +/** + * traceevent_print_plugins - print out the list of plugins loaded + * @s: the trace_seq descripter to write to + * @prefix: The prefix string to add before listing the option name + * @suffix: The suffix string ot append after the option name + * @list: The list of plugins (usually returned by traceevent_load_plugins() + * + * Writes to the trace_seq @s the list of plugins (files) that is + * returned by traceevent_load_plugins(). Use @prefix and @suffix for formating: + * @prefix = " ", @suffix = "\n". + */ +void traceevent_print_plugins(struct trace_seq *s, + const char *prefix, const char *suffix, + const struct plugin_list *list) +{ + while (list) { + trace_seq_printf(s, "%s%s%s", prefix, list->name, suffix); + list = list->next; + } +} + static void load_plugin(struct pevent *pevent, const char *path, const char *file, void *data) @@ -148,12 +344,17 @@ load_plugins(struct pevent *pevent, const char *suffix, char *path; char *envdir; + if (pevent->flags & PEVENT_DISABLE_PLUGINS) + return; + /* * If a system plugin directory was defined, * check that first. */ #ifdef PLUGIN_DIR - load_plugins_dir(pevent, suffix, PLUGIN_DIR, load_plugin, data); + if (!(pevent->flags & PEVENT_DISABLE_SYS_PLUGINS)) + load_plugins_dir(pevent, suffix, PLUGIN_DIR, + load_plugin, data); #endif /* diff --git a/tools/lib/traceevent/plugin_function.c b/tools/lib/traceevent/plugin_function.c index 80ba4ff1fe84..a00ec190821a 100644 --- a/tools/lib/traceevent/plugin_function.c +++ b/tools/lib/traceevent/plugin_function.c @@ -33,6 +33,29 @@ static int cpus = -1; #define STK_BLK 10 +struct pevent_plugin_option plugin_options[] = +{ + { + .name = "parent", + .plugin_alias = "ftrace", + .description = + "Print parent of functions for function events", + }, + { + .name = "indent", + .plugin_alias = "ftrace", + .description = + "Try to show function call indents, based on parents", + .set = 1, + }, + { + .name = NULL, + } +}; + +static struct pevent_plugin_option *ftrace_parent = &plugin_options[0]; +static struct pevent_plugin_option *ftrace_indent = &plugin_options[1]; + static void add_child(struct func_stack *stack, const char *child, int pos) { int i; @@ -119,7 +142,8 @@ static int function_handler(struct trace_seq *s, struct pevent_record *record, parent = pevent_find_function(pevent, pfunction); - index = add_and_get_index(parent, func, record->cpu); + if (parent && ftrace_indent->set) + index = add_and_get_index(parent, func, record->cpu); trace_seq_printf(s, "%*s", index*3, ""); @@ -128,11 +152,13 @@ static int function_handler(struct trace_seq *s, struct pevent_record *record, else trace_seq_printf(s, "0x%llx", function); - trace_seq_printf(s, " <-- "); - if (parent) - trace_seq_printf(s, "%s", parent); - else - trace_seq_printf(s, "0x%llx", pfunction); + if (ftrace_parent->set) { + trace_seq_printf(s, " <-- "); + if (parent) + trace_seq_printf(s, "%s", parent); + else + trace_seq_printf(s, "0x%llx", pfunction); + } return 0; } @@ -141,6 +167,9 @@ int PEVENT_PLUGIN_LOADER(struct pevent *pevent) { pevent_register_event_handler(pevent, -1, "ftrace", "function", function_handler, NULL); + + traceevent_plugin_add_options("ftrace", plugin_options); + return 0; } @@ -157,6 +186,8 @@ void PEVENT_PLUGIN_UNLOADER(struct pevent *pevent) free(fstack[i].stack); } + traceevent_plugin_remove_options(plugin_options); + free(fstack); fstack = NULL; cpus = -1; diff --git a/tools/net/bpf_exp.l b/tools/net/bpf_exp.l index bf7be77ddd62..833a96611da6 100644 --- a/tools/net/bpf_exp.l +++ b/tools/net/bpf_exp.l @@ -92,6 +92,7 @@ extern void yyerror(const char *str); "#"?("cpu") { return K_CPU; } "#"?("vlan_tci") { return K_VLANT; } "#"?("vlan_pr") { return K_VLANP; } +"#"?("rand") { return K_RAND; } ":" { return ':'; } "," { return ','; } diff --git a/tools/net/bpf_exp.y b/tools/net/bpf_exp.y index d15efc989ef5..e6306c51c26f 100644 --- a/tools/net/bpf_exp.y +++ b/tools/net/bpf_exp.y @@ -56,7 +56,7 @@ static void bpf_set_jmp_label(char *label, enum jmp_type type); %token OP_LDXI %token K_PKT_LEN K_PROTO K_TYPE K_NLATTR K_NLATTR_NEST K_MARK K_QUEUE K_HATYPE -%token K_RXHASH K_CPU K_IFIDX K_VLANT K_VLANP K_POFF +%token K_RXHASH K_CPU K_IFIDX K_VLANT K_VLANP K_POFF K_RAND %token ':' ',' '[' ']' '(' ')' 'x' 'a' '+' 'M' '*' '&' '#' '%' @@ -164,6 +164,9 @@ ldb | OP_LDB K_POFF { bpf_set_curr_instr(BPF_LD | BPF_B | BPF_ABS, 0, 0, SKF_AD_OFF + SKF_AD_PAY_OFFSET); } + | OP_LDB K_RAND { + bpf_set_curr_instr(BPF_LD | BPF_B | BPF_ABS, 0, 0, + SKF_AD_OFF + SKF_AD_RANDOM); } ; ldh @@ -212,6 +215,9 @@ ldh | OP_LDH K_POFF { bpf_set_curr_instr(BPF_LD | BPF_H | BPF_ABS, 0, 0, SKF_AD_OFF + SKF_AD_PAY_OFFSET); } + | OP_LDH K_RAND { + bpf_set_curr_instr(BPF_LD | BPF_H | BPF_ABS, 0, 0, + SKF_AD_OFF + SKF_AD_RANDOM); } ; ldi @@ -265,6 +271,9 @@ ld | OP_LD K_POFF { bpf_set_curr_instr(BPF_LD | BPF_W | BPF_ABS, 0, 0, SKF_AD_OFF + SKF_AD_PAY_OFFSET); } + | OP_LD K_RAND { + bpf_set_curr_instr(BPF_LD | BPF_W | BPF_ABS, 0, 0, + SKF_AD_OFF + SKF_AD_RANDOM); } | OP_LD 'M' '[' number ']' { bpf_set_curr_instr(BPF_LD | BPF_MEM, 0, 0, $4); } | OP_LD '[' 'x' '+' number ']' { diff --git a/tools/net/bpf_jit_disasm.c b/tools/net/bpf_jit_disasm.c index cfe0cdcda3de..c5baf9c591b7 100644 --- a/tools/net/bpf_jit_disasm.c +++ b/tools/net/bpf_jit_disasm.c @@ -43,8 +43,7 @@ static void get_exec_path(char *tpath, size_t size) free(path); } -static void get_asm_insns(uint8_t *image, size_t len, unsigned long base, - int opcodes) +static void get_asm_insns(uint8_t *image, size_t len, int opcodes) { int count, i, pc = 0; char tpath[256]; @@ -107,13 +106,13 @@ static void put_klog_buff(char *buff) } static int get_last_jit_image(char *haystack, size_t hlen, - uint8_t *image, size_t ilen, - unsigned long *base) + uint8_t *image, size_t ilen) { char *ptr, *pptr, *tmp; off_t off = 0; int ret, flen, proglen, pass, ulen = 0; regmatch_t pmatch[1]; + unsigned long base; regex_t regex; if (hlen == 0) @@ -136,7 +135,7 @@ static int get_last_jit_image(char *haystack, size_t hlen, ptr = haystack + off - (pmatch[0].rm_eo - pmatch[0].rm_so); ret = sscanf(ptr, "flen=%d proglen=%d pass=%d image=%lx", - &flen, &proglen, &pass, base); + &flen, &proglen, &pass, &base); if (ret != 4) return 0; @@ -162,7 +161,7 @@ static int get_last_jit_image(char *haystack, size_t hlen, assert(ulen == proglen); printf("%d bytes emitted from JIT compiler (pass:%d, flen:%d)\n", proglen, pass, flen); - printf("%lx + <x>:\n", *base); + printf("%lx + <x>:\n", base); regfree(®ex); return ulen; @@ -172,8 +171,7 @@ int main(int argc, char **argv) { int len, klen, opcodes = 0; char *kbuff; - unsigned long base; - uint8_t image[4096]; + static uint8_t image[32768]; if (argc > 1) { if (!strncmp("-o", argv[argc - 1], 2)) { @@ -189,9 +187,9 @@ int main(int argc, char **argv) kbuff = get_klog_buff(&klen); - len = get_last_jit_image(kbuff, klen, image, sizeof(image), &base); - if (len > 0 && base > 0) - get_asm_insns(image, len, base, opcodes); + len = get_last_jit_image(kbuff, klen, image, sizeof(image)); + if (len > 0) + get_asm_insns(image, len, opcodes); put_klog_buff(kbuff); diff --git a/tools/perf/Documentation/perf-record.txt b/tools/perf/Documentation/perf-record.txt index c71b0f36d9e8..d460049cae8e 100644 --- a/tools/perf/Documentation/perf-record.txt +++ b/tools/perf/Documentation/perf-record.txt @@ -184,9 +184,10 @@ following filters are defined: - in_tx: only when the target is in a hardware transaction - no_tx: only when the target is not in a hardware transaction - abort_tx: only when the target is a hardware transaction abort + - cond: conditional branches + -The option requires at least one branch type among any, any_call, any_ret, ind_call. +The option requires at least one branch type among any, any_call, any_ret, ind_call, cond. The privilege levels may be omitted, in which case, the privilege levels of the associated event are applied to the branch filter. Both kernel (k) and hypervisor (hv) privilege levels are subject to permissions. When sampling on multiple events, branch stack sampling diff --git a/tools/perf/Documentation/perf-report.txt b/tools/perf/Documentation/perf-report.txt index a1b5185402d5..d2b59af62bc0 100644 --- a/tools/perf/Documentation/perf-report.txt +++ b/tools/perf/Documentation/perf-report.txt @@ -111,12 +111,28 @@ OPTIONS --fields=:: Specify output field - multiple keys can be specified in CSV format. Following fields are available: - overhead, overhead_sys, overhead_us, sample and period. + overhead, overhead_sys, overhead_us, overhead_children, sample and period. Also it can contain any sort key(s). By default, every sort keys not specified in -F will be appended automatically. + If --mem-mode option is used, following sort keys are also available + (incompatible with --branch-stack): + symbol_daddr, dso_daddr, locked, tlb, mem, snoop, dcacheline. + + - symbol_daddr: name of data symbol being executed on at the time of sample + - dso_daddr: name of library or module containing the data being executed + on at the time of sample + - locked: whether the bus was locked at the time of sample + - tlb: type of tlb access for the data at the time of sample + - mem: type of memory access for the data at the time of sample + - snoop: type of snoop (if any) for the data at the time of sample + - dcacheline: the cacheline the data address is on at the time of sample + + And default sort keys are changed to local_weight, mem, sym, dso, + symbol_daddr, dso_daddr, snoop, tlb, locked, see '--mem-mode'. + -p:: --parent=<regex>:: A regex filter to identify parent. The parent is a caller of this @@ -163,6 +179,11 @@ OPTIONS Default: fractal,0.5,callee,function. +--children:: + Accumulate callchain of children to parent entry so that then can + show up in the output. The output will have a new "Children" column + and will be sorted on the data. It requires callchains are recorded. + --max-stack:: Set the stack depth limit when parsing the callchain, anything beyond the specified depth will be ignored. This is a trade-off @@ -255,6 +276,13 @@ OPTIONS Demangle symbol names to human readable form. It's enabled by default, disable with --no-demangle. +--mem-mode:: + Use the data addresses of samples in addition to instruction addresses + to build the histograms. To generate meaningful output, the perf.data + file must have been obtained using perf record -d -W and using a + special event -e cpu/mem-loads/ or -e cpu/mem-stores/. See + 'perf mem' for simpler access. + --percent-limit:: Do not show entries which have an overhead under that percent. (Default: 0). diff --git a/tools/perf/Documentation/perf-timechart.txt b/tools/perf/Documentation/perf-timechart.txt index bc5990c33dc0..5e0f986dff38 100644 --- a/tools/perf/Documentation/perf-timechart.txt +++ b/tools/perf/Documentation/perf-timechart.txt @@ -43,27 +43,6 @@ TIMECHART OPTIONS --symfs=<directory>:: Look for files with symbols relative to this directory. - -EXAMPLES --------- - -$ perf timechart record git pull - - [ perf record: Woken up 13 times to write data ] - [ perf record: Captured and wrote 4.253 MB perf.data (~185801 samples) ] - -$ perf timechart - - Written 10.2 seconds of trace to output.svg. - -Record system-wide timechart: - - $ perf timechart record - - then generate timechart and highlight 'gcc' tasks: - - $ perf timechart --highlight gcc - -n:: --proc-num:: Print task info for at least given number of tasks. @@ -88,6 +67,26 @@ RECORD OPTIONS --callchain:: Do call-graph (stack chain/backtrace) recording +EXAMPLES +-------- + +$ perf timechart record git pull + + [ perf record: Woken up 13 times to write data ] + [ perf record: Captured and wrote 4.253 MB perf.data (~185801 samples) ] + +$ perf timechart + + Written 10.2 seconds of trace to output.svg. + +Record system-wide timechart: + + $ perf timechart record + + then generate timechart and highlight 'gcc' tasks: + + $ perf timechart --highlight gcc + SEE ALSO -------- linkperf:perf-record[1] diff --git a/tools/perf/Documentation/perf-top.txt b/tools/perf/Documentation/perf-top.txt index dcfa54c851e9..180ae02137a5 100644 --- a/tools/perf/Documentation/perf-top.txt +++ b/tools/perf/Documentation/perf-top.txt @@ -119,7 +119,7 @@ Default is to monitor all CPUS. --fields=:: Specify output field - multiple keys can be specified in CSV format. Following fields are available: - overhead, overhead_sys, overhead_us, sample and period. + overhead, overhead_sys, overhead_us, overhead_children, sample and period. Also it can contain any sort key(s). By default, every sort keys not specified in --field will be appended @@ -161,6 +161,12 @@ Default is to monitor all CPUS. Setup and enable call-graph (stack chain/backtrace) recording, implies -g. +--children:: + Accumulate callchain of children to parent entry so that then can + show up in the output. The output will have a new "Children" column + and will be sorted on the data. It requires -g/--call-graph option + enabled. + --max-stack:: Set the stack depth limit when parsing the callchain, anything beyond the specified depth will be ignored. This is a trade-off diff --git a/tools/perf/Makefile.perf b/tools/perf/Makefile.perf index 02f0a4dd1a80..9670a16fa577 100644 --- a/tools/perf/Makefile.perf +++ b/tools/perf/Makefile.perf @@ -400,6 +400,7 @@ LIB_OBJS += $(OUTPUT)tests/hists_common.o LIB_OBJS += $(OUTPUT)tests/hists_link.o LIB_OBJS += $(OUTPUT)tests/hists_filter.o LIB_OBJS += $(OUTPUT)tests/hists_output.o +LIB_OBJS += $(OUTPUT)tests/hists_cumulate.o LIB_OBJS += $(OUTPUT)tests/python-use.o LIB_OBJS += $(OUTPUT)tests/bp_signal.o LIB_OBJS += $(OUTPUT)tests/bp_signal_overflow.o @@ -788,8 +789,8 @@ help: @echo '' @echo 'Perf install targets:' @echo ' NOTE: documentation build requires asciidoc, xmlto packages to be installed' - @echo ' HINT: use "make prefix=<path> <install target>" to install to a particular' - @echo ' path like make prefix=/usr/local install install-doc' + @echo ' HINT: use "prefix" or "DESTDIR" to install to a particular' + @echo ' path like "make prefix=/usr/local install install-doc"' @echo ' install - install compiled binaries' @echo ' install-doc - install *all* documentation' @echo ' install-man - install manpage documentation' @@ -814,17 +815,20 @@ INSTALL_DOC_TARGETS += quick-install-doc quick-install-man quick-install-html $(DOC_TARGETS): $(QUIET_SUBDIR0)Documentation $(QUIET_SUBDIR1) $(@:doc=all) +TAG_FOLDERS= . ../lib/traceevent ../lib/api ../lib/symbol +TAG_FILES= ../../include/uapi/linux/perf_event.h + TAGS: - $(RM) TAGS - $(FIND) . -name '*.[hcS]' -print | xargs etags -a + $(QUIET_GEN)$(RM) TAGS; \ + $(FIND) $(TAG_FOLDERS) -name '*.[hcS]' -print | xargs etags -a $(TAG_FILES) tags: - $(RM) tags - $(FIND) . -name '*.[hcS]' -print | xargs ctags -a + $(QUIET_GEN)$(RM) tags; \ + $(FIND) $(TAG_FOLDERS) -name '*.[hcS]' -print | xargs ctags -a $(TAG_FILES) cscope: - $(RM) cscope* - $(FIND) . -name '*.[hcS]' -print | xargs cscope -b + $(QUIET_GEN)$(RM) cscope*; \ + $(FIND) $(TAG_FOLDERS) -name '*.[hcS]' -print | xargs cscope -b $(TAG_FILES) ### Detect prefix changes TRACK_CFLAGS = $(subst ','\'',$(CFLAGS)):\ diff --git a/tools/perf/builtin-annotate.c b/tools/perf/builtin-annotate.c index d30d2c2e2a7a..1ec429fef2be 100644 --- a/tools/perf/builtin-annotate.c +++ b/tools/perf/builtin-annotate.c @@ -65,12 +65,13 @@ static int perf_evsel__add_sample(struct perf_evsel *evsel, return 0; } - he = __hists__add_entry(&evsel->hists, al, NULL, NULL, NULL, 1, 1, 0); + he = __hists__add_entry(&evsel->hists, al, NULL, NULL, NULL, 1, 1, 0, + true); if (he == NULL) return -ENOMEM; ret = hist_entry__inc_addr_samples(he, evsel->idx, al->addr); - hists__inc_nr_events(&evsel->hists, PERF_RECORD_SAMPLE); + hists__inc_nr_samples(&evsel->hists, true); return ret; } diff --git a/tools/perf/builtin-diff.c b/tools/perf/builtin-diff.c index 8bff543acaab..9a5a035cb426 100644 --- a/tools/perf/builtin-diff.c +++ b/tools/perf/builtin-diff.c @@ -315,7 +315,7 @@ static int hists__add_entry(struct hists *hists, u64 weight, u64 transaction) { if (__hists__add_entry(hists, al, NULL, NULL, NULL, period, weight, - transaction) != NULL) + transaction, true) != NULL) return 0; return -ENOMEM; } diff --git a/tools/perf/builtin-inject.c b/tools/perf/builtin-inject.c index 6a3af0013d68..16c7c11ad06e 100644 --- a/tools/perf/builtin-inject.c +++ b/tools/perf/builtin-inject.c @@ -72,7 +72,7 @@ static int perf_event__repipe_attr(struct perf_tool *tool, if (ret) return ret; - if (&inject->output.is_pipe) + if (!inject->output.is_pipe) return 0; return perf_event__repipe_synth(tool, event); diff --git a/tools/perf/builtin-probe.c b/tools/perf/builtin-probe.c index cdcd4eb3a57d..c63fa2925075 100644 --- a/tools/perf/builtin-probe.c +++ b/tools/perf/builtin-probe.c @@ -288,6 +288,13 @@ static void cleanup_params(void) memset(¶ms, 0, sizeof(params)); } +static void pr_err_with_code(const char *msg, int err) +{ + pr_err("%s", msg); + pr_debug(" Reason: %s (Code: %d)", strerror(-err), err); + pr_err("\n"); +} + static int __cmd_probe(int argc, const char **argv, const char *prefix __maybe_unused) { @@ -379,7 +386,7 @@ __cmd_probe(int argc, const char **argv, const char *prefix __maybe_unused) } ret = parse_probe_event_argv(argc, argv); if (ret < 0) { - pr_err(" Error: Parse Error. (%d)\n", ret); + pr_err_with_code(" Error: Command Parse Error.", ret); return ret; } } @@ -419,8 +426,7 @@ __cmd_probe(int argc, const char **argv, const char *prefix __maybe_unused) } ret = show_perf_probe_events(); if (ret < 0) - pr_err(" Error: Failed to show event list. (%d)\n", - ret); + pr_err_with_code(" Error: Failed to show event list.", ret); return ret; } if (params.show_funcs) { @@ -445,8 +451,7 @@ __cmd_probe(int argc, const char **argv, const char *prefix __maybe_unused) strfilter__delete(params.filter); params.filter = NULL; if (ret < 0) - pr_err(" Error: Failed to show functions." - " (%d)\n", ret); + pr_err_with_code(" Error: Failed to show functions.", ret); return ret; } @@ -464,7 +469,7 @@ __cmd_probe(int argc, const char **argv, const char *prefix __maybe_unused) ret = show_line_range(¶ms.line_range, params.target); if (ret < 0) - pr_err(" Error: Failed to show lines. (%d)\n", ret); + pr_err_with_code(" Error: Failed to show lines.", ret); return ret; } if (params.show_vars) { @@ -485,7 +490,7 @@ __cmd_probe(int argc, const char **argv, const char *prefix __maybe_unused) strfilter__delete(params.filter); params.filter = NULL; if (ret < 0) - pr_err(" Error: Failed to show vars. (%d)\n", ret); + pr_err_with_code(" Error: Failed to show vars.", ret); return ret; } #endif @@ -493,7 +498,7 @@ __cmd_probe(int argc, const char **argv, const char *prefix __maybe_unused) if (params.dellist) { ret = del_perf_probe_events(params.dellist); if (ret < 0) { - pr_err(" Error: Failed to delete events. (%d)\n", ret); + pr_err_with_code(" Error: Failed to delete events.", ret); return ret; } } @@ -504,7 +509,7 @@ __cmd_probe(int argc, const char **argv, const char *prefix __maybe_unused) params.target, params.force_add); if (ret < 0) { - pr_err(" Error: Failed to add events. (%d)\n", ret); + pr_err_with_code(" Error: Failed to add events.", ret); return ret; } } diff --git a/tools/perf/builtin-record.c b/tools/perf/builtin-record.c index e4c85b8f46c2..378b85b731a7 100644 --- a/tools/perf/builtin-record.c +++ b/tools/perf/builtin-record.c @@ -454,7 +454,11 @@ static int __cmd_record(struct record *rec, int argc, const char **argv) if (done) break; err = poll(rec->evlist->pollfd, rec->evlist->nr_fds, -1); - if (err < 0 && errno == EINTR) + /* + * Propagate error, only if there's any. Ignore positive + * number of returned events and interrupt error. + */ + if (err > 0 || (err < 0 && errno == EINTR)) err = 0; waking++; } @@ -544,6 +548,7 @@ static const struct branch_mode branch_modes[] = { BRANCH_OPT("abort_tx", PERF_SAMPLE_BRANCH_ABORT_TX), BRANCH_OPT("in_tx", PERF_SAMPLE_BRANCH_IN_TX), BRANCH_OPT("no_tx", PERF_SAMPLE_BRANCH_NO_TX), + BRANCH_OPT("cond", PERF_SAMPLE_BRANCH_COND), BRANCH_END }; diff --git a/tools/perf/builtin-report.c b/tools/perf/builtin-report.c index bc0eec1ce4be..21d830bafff3 100644 --- a/tools/perf/builtin-report.c +++ b/tools/perf/builtin-report.c @@ -72,6 +72,10 @@ static int report__config(const char *var, const char *value, void *cb) rep->min_percent = strtof(value, NULL); return 0; } + if (!strcmp(var, "report.children")) { + symbol_conf.cumulate_callchain = perf_config_bool(var, value); + return 0; + } return perf_default_config(var, value, cb); } @@ -85,156 +89,52 @@ static void report__inc_stats(struct report *rep, struct hist_entry *he) */ if (he->stat.nr_events == 1) rep->nr_entries++; - - /* - * Only counts number of samples at this stage as it's more - * natural to do it here and non-sample events are also - * counted in perf_session_deliver_event(). The dump_trace - * requires this info is ready before going to the output tree. - */ - hists__inc_nr_events(he->hists, PERF_RECORD_SAMPLE); - if (!he->filtered) - he->hists->stats.nr_non_filtered_samples++; } -static int report__add_mem_hist_entry(struct report *rep, struct addr_location *al, - struct perf_sample *sample, struct perf_evsel *evsel) +static int hist_iter__report_callback(struct hist_entry_iter *iter, + struct addr_location *al, bool single, + void *arg) { - struct symbol *parent = NULL; - struct hist_entry *he; - struct mem_info *mi, *mx; - uint64_t cost; - int err = sample__resolve_callchain(sample, &parent, evsel, al, rep->max_stack); - - if (err) - return err; + int err = 0; + struct report *rep = arg; + struct hist_entry *he = iter->he; + struct perf_evsel *evsel = iter->evsel; + struct mem_info *mi; + struct branch_info *bi; - mi = sample__resolve_mem(sample, al); - if (!mi) - return -ENOMEM; + report__inc_stats(rep, he); - if (rep->hide_unresolved && !al->sym) + if (!ui__has_annotation()) return 0; - cost = sample->weight; - if (!cost) - cost = 1; - - /* - * must pass period=weight in order to get the correct - * sorting from hists__collapse_resort() which is solely - * based on periods. We want sorting be done on nr_events * weight - * and this is indirectly achieved by passing period=weight here - * and the he_stat__add_period() function. - */ - he = __hists__add_entry(&evsel->hists, al, parent, NULL, mi, - cost, cost, 0); - if (!he) - return -ENOMEM; - - if (ui__has_annotation()) { - err = hist_entry__inc_addr_samples(he, evsel->idx, al->addr); - if (err) - goto out; - - mx = he->mem_info; - err = addr_map_symbol__inc_samples(&mx->daddr, evsel->idx); + if (sort__mode == SORT_MODE__BRANCH) { + bi = he->branch_info; + err = addr_map_symbol__inc_samples(&bi->from, evsel->idx); if (err) goto out; - } - - report__inc_stats(rep, he); - - err = hist_entry__append_callchain(he, sample); -out: - return err; -} - -static int report__add_branch_hist_entry(struct report *rep, struct addr_location *al, - struct perf_sample *sample, struct perf_evsel *evsel) -{ - struct symbol *parent = NULL; - unsigned i; - struct hist_entry *he; - struct branch_info *bi, *bx; - int err = sample__resolve_callchain(sample, &parent, evsel, al, rep->max_stack); - if (err) - return err; - - bi = sample__resolve_bstack(sample, al); - if (!bi) - return -ENOMEM; - - for (i = 0; i < sample->branch_stack->nr; i++) { - if (rep->hide_unresolved && !(bi[i].from.sym && bi[i].to.sym)) - continue; + err = addr_map_symbol__inc_samples(&bi->to, evsel->idx); - err = -ENOMEM; - - /* overwrite the 'al' to branch-to info */ - al->map = bi[i].to.map; - al->sym = bi[i].to.sym; - al->addr = bi[i].to.addr; - /* - * The report shows the percentage of total branches captured - * and not events sampled. Thus we use a pseudo period of 1. - */ - he = __hists__add_entry(&evsel->hists, al, parent, &bi[i], NULL, - 1, 1, 0); - if (he) { - if (ui__has_annotation()) { - bx = he->branch_info; - err = addr_map_symbol__inc_samples(&bx->from, - evsel->idx); - if (err) - goto out; - - err = addr_map_symbol__inc_samples(&bx->to, - evsel->idx); - if (err) - goto out; - } - report__inc_stats(rep, he); - } else + } else if (rep->mem_mode) { + mi = he->mem_info; + err = addr_map_symbol__inc_samples(&mi->daddr, evsel->idx); + if (err) goto out; - } - err = 0; -out: - free(bi); - return err; -} - -static int report__add_hist_entry(struct report *rep, struct perf_evsel *evsel, - struct addr_location *al, struct perf_sample *sample) -{ - struct symbol *parent = NULL; - struct hist_entry *he; - int err = sample__resolve_callchain(sample, &parent, evsel, al, rep->max_stack); - - if (err) - return err; - he = __hists__add_entry(&evsel->hists, al, parent, NULL, NULL, - sample->period, sample->weight, - sample->transaction); - if (he == NULL) - return -ENOMEM; - - err = hist_entry__append_callchain(he, sample); - if (err) - goto out; - - if (ui__has_annotation()) err = hist_entry__inc_addr_samples(he, evsel->idx, al->addr); - report__inc_stats(rep, he); + } else if (symbol_conf.cumulate_callchain) { + if (single) + err = hist_entry__inc_addr_samples(he, evsel->idx, + al->addr); + } else { + err = hist_entry__inc_addr_samples(he, evsel->idx, al->addr); + } out: return err; } - static int process_sample_event(struct perf_tool *tool, union perf_event *event, struct perf_sample *sample, @@ -243,6 +143,10 @@ static int process_sample_event(struct perf_tool *tool, { struct report *rep = container_of(tool, struct report, tool); struct addr_location al; + struct hist_entry_iter iter = { + .hide_unresolved = rep->hide_unresolved, + .add_entry_cb = hist_iter__report_callback, + }; int ret; if (perf_event__preprocess_sample(event, machine, &al, sample) < 0) { @@ -257,22 +161,23 @@ static int process_sample_event(struct perf_tool *tool, if (rep->cpu_list && !test_bit(sample->cpu, rep->cpu_bitmap)) return 0; - if (sort__mode == SORT_MODE__BRANCH) { - ret = report__add_branch_hist_entry(rep, &al, sample, evsel); - if (ret < 0) - pr_debug("problem adding lbr entry, skipping event\n"); - } else if (rep->mem_mode == 1) { - ret = report__add_mem_hist_entry(rep, &al, sample, evsel); - if (ret < 0) - pr_debug("problem adding mem entry, skipping event\n"); - } else { - if (al.map != NULL) - al.map->dso->hit = 1; + if (sort__mode == SORT_MODE__BRANCH) + iter.ops = &hist_iter_branch; + else if (rep->mem_mode) + iter.ops = &hist_iter_mem; + else if (symbol_conf.cumulate_callchain) + iter.ops = &hist_iter_cumulative; + else + iter.ops = &hist_iter_normal; + + if (al.map != NULL) + al.map->dso->hit = 1; + + ret = hist_entry_iter__add(&iter, &al, evsel, sample, rep->max_stack, + rep); + if (ret < 0) + pr_debug("problem adding hist entry, skipping event\n"); - ret = report__add_hist_entry(rep, evsel, &al, sample); - if (ret < 0) - pr_debug("problem incrementing symbol period, skipping event\n"); - } return ret; } @@ -329,6 +234,14 @@ static int report__setup_sample_type(struct report *rep) } } + if (symbol_conf.cumulate_callchain) { + /* Silently ignore if callchain is missing */ + if (!(sample_type & PERF_SAMPLE_CALLCHAIN)) { + symbol_conf.cumulate_callchain = false; + perf_hpp__cancel_cumulate(); + } + } + if (sort__mode == SORT_MODE__BRANCH) { if (!is_pipe && !(sample_type & PERF_SAMPLE_BRANCH_STACK)) { @@ -712,6 +625,8 @@ int cmd_report(int argc, const char **argv, const char *prefix __maybe_unused) OPT_CALLBACK_DEFAULT('g', "call-graph", &report, "output_type,min_percent[,print_limit],call_order", "Display callchains using output_type (graph, flat, fractal, or none) , min percent threshold, optional print limit, callchain order, key (function or address). " "Default: fractal,0.5,callee,function", &report_parse_callchain_opt, callchain_default_opt), + OPT_BOOLEAN(0, "children", &symbol_conf.cumulate_callchain, + "Accumulate callchains of children and show total overhead as well"), OPT_INTEGER(0, "max-stack", &report.max_stack, "Set the maximum stack depth when parsing the callchain, " "anything beyond the specified depth will be ignored. " @@ -804,8 +719,10 @@ repeat: has_br_stack = perf_header__has_feat(&session->header, HEADER_BRANCH_STACK); - if (branch_mode == -1 && has_br_stack) + if (branch_mode == -1 && has_br_stack) { sort__mode = SORT_MODE__BRANCH; + symbol_conf.cumulate_callchain = false; + } if (report.mem_mode) { if (sort__mode == SORT_MODE__BRANCH) { @@ -813,6 +730,7 @@ repeat: goto error; } sort__mode = SORT_MODE__MEMORY; + symbol_conf.cumulate_callchain = false; } if (setup_sorting() < 0) { diff --git a/tools/perf/builtin-sched.c b/tools/perf/builtin-sched.c index d7176830b9b2..c38d06c04775 100644 --- a/tools/perf/builtin-sched.c +++ b/tools/perf/builtin-sched.c @@ -1428,7 +1428,7 @@ static int perf_sched__process_tracepoint_sample(struct perf_tool *tool __maybe_ int err = 0; evsel->hists.stats.total_period += sample->period; - hists__inc_nr_events(&evsel->hists, PERF_RECORD_SAMPLE); + hists__inc_nr_samples(&evsel->hists, true); if (evsel->handler != NULL) { tracepoint_handler f = evsel->handler; diff --git a/tools/perf/builtin-top.c b/tools/perf/builtin-top.c index 5b389ce4cd15..377971dc89a3 100644 --- a/tools/perf/builtin-top.c +++ b/tools/perf/builtin-top.c @@ -196,6 +196,12 @@ static void perf_top__record_precise_ip(struct perf_top *top, pthread_mutex_unlock(¬es->lock); + /* + * This function is now called with he->hists->lock held. + * Release it before going to sleep. + */ + pthread_mutex_unlock(&he->hists->lock); + if (err == -ERANGE && !he->ms.map->erange_warned) ui__warn_map_erange(he->ms.map, sym, ip); else if (err == -ENOMEM) { @@ -203,6 +209,8 @@ static void perf_top__record_precise_ip(struct perf_top *top, sym->name); sleep(1); } + + pthread_mutex_lock(&he->hists->lock); } static void perf_top__show_details(struct perf_top *top) @@ -238,27 +246,6 @@ out_unlock: pthread_mutex_unlock(¬es->lock); } -static struct hist_entry *perf_evsel__add_hist_entry(struct perf_evsel *evsel, - struct addr_location *al, - struct perf_sample *sample) -{ - struct hist_entry *he; - - pthread_mutex_lock(&evsel->hists.lock); - he = __hists__add_entry(&evsel->hists, al, NULL, NULL, NULL, - sample->period, sample->weight, - sample->transaction); - pthread_mutex_unlock(&evsel->hists.lock); - if (he == NULL) - return NULL; - - hists__inc_nr_events(&evsel->hists, PERF_RECORD_SAMPLE); - if (!he->filtered) - evsel->hists.stats.nr_non_filtered_samples++; - - return he; -} - static void perf_top__print_sym_table(struct perf_top *top) { char bf[160]; @@ -662,6 +649,26 @@ static int symbol_filter(struct map *map __maybe_unused, struct symbol *sym) return 0; } +static int hist_iter__top_callback(struct hist_entry_iter *iter, + struct addr_location *al, bool single, + void *arg) +{ + struct perf_top *top = arg; + struct hist_entry *he = iter->he; + struct perf_evsel *evsel = iter->evsel; + + if (sort__has_sym && single) { + u64 ip = al->addr; + + if (al->map) + ip = al->map->unmap_ip(al->map, ip); + + perf_top__record_precise_ip(top, he, evsel->idx, ip); + } + + return 0; +} + static void perf_event__process_sample(struct perf_tool *tool, const union perf_event *event, struct perf_evsel *evsel, @@ -669,8 +676,6 @@ static void perf_event__process_sample(struct perf_tool *tool, struct machine *machine) { struct perf_top *top = container_of(tool, struct perf_top, tool); - struct symbol *parent = NULL; - u64 ip = sample->ip; struct addr_location al; int err; @@ -745,25 +750,23 @@ static void perf_event__process_sample(struct perf_tool *tool, } if (al.sym == NULL || !al.sym->ignore) { - struct hist_entry *he; + struct hist_entry_iter iter = { + .add_entry_cb = hist_iter__top_callback, + }; - err = sample__resolve_callchain(sample, &parent, evsel, &al, - top->max_stack); - if (err) - return; + if (symbol_conf.cumulate_callchain) + iter.ops = &hist_iter_cumulative; + else + iter.ops = &hist_iter_normal; - he = perf_evsel__add_hist_entry(evsel, &al, sample); - if (he == NULL) { - pr_err("Problem incrementing symbol period, skipping event\n"); - return; - } + pthread_mutex_lock(&evsel->hists.lock); - err = hist_entry__append_callchain(he, sample); - if (err) - return; + err = hist_entry_iter__add(&iter, &al, evsel, sample, + top->max_stack, top); + if (err < 0) + pr_err("Problem incrementing symbol period, skipping event\n"); - if (sort__has_sym) - perf_top__record_precise_ip(top, he, evsel->idx, ip); + pthread_mutex_unlock(&evsel->hists.lock); } return; @@ -1001,6 +1004,10 @@ static int perf_top_config(const char *var, const char *value, void *cb) if (!strcmp(var, "top.call-graph")) return record_parse_callchain(value, &top->record_opts); + if (!strcmp(var, "top.children")) { + symbol_conf.cumulate_callchain = perf_config_bool(var, value); + return 0; + } return perf_default_config(var, value, cb); } @@ -1095,6 +1102,8 @@ int cmd_top(int argc, const char **argv, const char *prefix __maybe_unused) OPT_CALLBACK(0, "call-graph", &top.record_opts, "mode[,dump_size]", record_callchain_help, &parse_callchain_opt), + OPT_BOOLEAN(0, "children", &symbol_conf.cumulate_callchain, + "Accumulate callchains of children and show total overhead as well"), OPT_INTEGER(0, "max-stack", &top.max_stack, "Set the maximum stack depth when parsing the callchain. " "Default: " __stringify(PERF_MAX_STACK_DEPTH)), @@ -1200,6 +1209,11 @@ int cmd_top(int argc, const char **argv, const char *prefix __maybe_unused) top.sym_evsel = perf_evlist__first(top.evlist); + if (!symbol_conf.use_callchain) { + symbol_conf.cumulate_callchain = false; + perf_hpp__cancel_cumulate(); + } + symbol_conf.priv_size = sizeof(struct annotation); symbol_conf.try_vmlinux_path = (symbol_conf.vmlinux_name == NULL); diff --git a/tools/perf/config/Makefile b/tools/perf/config/Makefile index 729bbdf5cec7..f30ac5e5d271 100644 --- a/tools/perf/config/Makefile +++ b/tools/perf/config/Makefile @@ -299,7 +299,11 @@ else NO_LIBUNWIND := 1 NO_LIBDW_DWARF_UNWIND := 1 else - msg := $(error No gnu/libc-version.h found, please install glibc-dev[el]/glibc-static); + ifneq ($(filter s% -static%,$(LDFLAGS),),) + msg := $(error No static glibc found, please install glibc-static); + else + msg := $(error No gnu/libc-version.h found, please install glibc-dev[el]); + endif endif else ifndef NO_LIBDW_DWARF_UNWIND @@ -447,6 +451,7 @@ else ifneq ($(feature-libperl), 1) CFLAGS += -DNO_LIBPERL NO_LIBPERL := 1 + msg := $(warning Missing perl devel files. Disabling perl scripting support, consider installing perl-ExtUtils-Embed); else LDFLAGS += $(PERL_EMBED_LDFLAGS) EXTLIBS += $(PERL_EMBED_LIBADD) @@ -599,7 +604,7 @@ endif # Make the path relative to DESTDIR, not to prefix ifndef DESTDIR -prefix = $(HOME) +prefix ?= $(HOME) endif bindir_relative = bin bindir = $(prefix)/$(bindir_relative) diff --git a/tools/perf/perf.c b/tools/perf/perf.c index 431798a4110d..95c58fc15284 100644 --- a/tools/perf/perf.c +++ b/tools/perf/perf.c @@ -458,6 +458,7 @@ int main(int argc, const char **argv) /* The page_size is placed in util object. */ page_size = sysconf(_SC_PAGE_SIZE); + cacheline_size = sysconf(_SC_LEVEL1_DCACHE_LINESIZE); cmd = perf_extract_argv0_path(argv[0]); if (!cmd) @@ -481,14 +482,18 @@ int main(int argc, const char **argv) fprintf(stderr, "cannot handle %s internally", cmd); goto out; } -#ifdef HAVE_LIBAUDIT_SUPPORT if (!prefixcmp(cmd, "trace")) { +#ifdef HAVE_LIBAUDIT_SUPPORT set_buildid_dir(); setup_path(); argv[0] = "trace"; return cmd_trace(argc, argv, NULL); - } +#else + fprintf(stderr, + "trace command not available: missing audit-libs devel package at build time.\n"); + goto out; #endif + } /* Look for flags.. */ argv++; argc--; diff --git a/tools/perf/tests/builtin-test.c b/tools/perf/tests/builtin-test.c index 831f52cae197..6f8b01bc6033 100644 --- a/tools/perf/tests/builtin-test.c +++ b/tools/perf/tests/builtin-test.c @@ -3,6 +3,8 @@ * * Builtin regression testing command: ever growing number of sanity tests */ +#include <unistd.h> +#include <string.h> #include "builtin.h" #include "intlist.h" #include "tests.h" @@ -50,10 +52,18 @@ static struct test { .func = test__pmu, }, { - .desc = "Test dso data interface", + .desc = "Test dso data read", .func = test__dso_data, }, { + .desc = "Test dso data cache", + .func = test__dso_data_cache, + }, + { + .desc = "Test dso data reopen", + .func = test__dso_data_reopen, + }, + { .desc = "roundtrip evsel->name check", .func = test__perf_evsel__roundtrip_name_test, }, @@ -140,6 +150,10 @@ static struct test { .func = test__hists_output, }, { + .desc = "Test cumulation of child hist entries", + .func = test__hists_cumulate, + }, + { .func = NULL, }, }; @@ -168,6 +182,34 @@ static bool perf_test__matches(int curr, int argc, const char *argv[]) return false; } +static int run_test(struct test *test) +{ + int status, err = -1, child = fork(); + + if (child < 0) { + pr_err("failed to fork test: %s\n", strerror(errno)); + return -1; + } + + if (!child) { + pr_debug("test child forked, pid %d\n", getpid()); + err = test->func(); + exit(err); + } + + wait(&status); + + if (WIFEXITED(status)) { + err = WEXITSTATUS(status); + pr_debug("test child finished with %d\n", err); + } else if (WIFSIGNALED(status)) { + err = -1; + pr_debug("test child interrupted\n"); + } + + return err; +} + static int __cmd_test(int argc, const char *argv[], struct intlist *skiplist) { int i = 0; @@ -196,7 +238,7 @@ static int __cmd_test(int argc, const char *argv[], struct intlist *skiplist) } pr_debug("\n--- start ---\n"); - err = tests[curr].func(); + err = run_test(&tests[curr]); pr_debug("---- end ----\n%s:", tests[curr].desc); switch (err) { diff --git a/tools/perf/tests/dso-data.c b/tools/perf/tests/dso-data.c index 3e6cb171e3d3..630808cd7cc2 100644 --- a/tools/perf/tests/dso-data.c +++ b/tools/perf/tests/dso-data.c @@ -1,22 +1,27 @@ -#include "util.h" - #include <stdlib.h> #include <linux/types.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h> - +#include <sys/time.h> +#include <sys/resource.h> +#include <api/fs/fs.h> +#include "util.h" #include "machine.h" #include "symbol.h" #include "tests.h" static char *test_file(int size) { - static char buf_templ[] = "/tmp/test-XXXXXX"; +#define TEMPL "/tmp/perf-test-XXXXXX" + static char buf_templ[sizeof(TEMPL)]; char *templ = buf_templ; int fd, i; unsigned char *buf; + strcpy(buf_templ, TEMPL); +#undef TEMPL + fd = mkstemp(templ); if (fd < 0) { perror("mkstemp failed"); @@ -150,3 +155,204 @@ int test__dso_data(void) unlink(file); return 0; } + +static long open_files_cnt(void) +{ + char path[PATH_MAX]; + struct dirent *dent; + DIR *dir; + long nr = 0; + + scnprintf(path, PATH_MAX, "%s/self/fd", procfs__mountpoint()); + pr_debug("fd path: %s\n", path); + + dir = opendir(path); + TEST_ASSERT_VAL("failed to open fd directory", dir); + + while ((dent = readdir(dir)) != NULL) { + if (!strcmp(dent->d_name, ".") || + !strcmp(dent->d_name, "..")) + continue; + + nr++; + } + + closedir(dir); + return nr - 1; +} + +static struct dso **dsos; + +static int dsos__create(int cnt, int size) +{ + int i; + + dsos = malloc(sizeof(dsos) * cnt); + TEST_ASSERT_VAL("failed to alloc dsos array", dsos); + + for (i = 0; i < cnt; i++) { + char *file; + + file = test_file(size); + TEST_ASSERT_VAL("failed to get dso file", file); + + dsos[i] = dso__new(file); + TEST_ASSERT_VAL("failed to get dso", dsos[i]); + } + + return 0; +} + +static void dsos__delete(int cnt) +{ + int i; + + for (i = 0; i < cnt; i++) { + struct dso *dso = dsos[i]; + + unlink(dso->name); + dso__delete(dso); + } + + free(dsos); +} + +static int set_fd_limit(int n) +{ + struct rlimit rlim; + + if (getrlimit(RLIMIT_NOFILE, &rlim)) + return -1; + + pr_debug("file limit %ld, new %d\n", (long) rlim.rlim_cur, n); + + rlim.rlim_cur = n; + return setrlimit(RLIMIT_NOFILE, &rlim); +} + +int test__dso_data_cache(void) +{ + struct machine machine; + long nr_end, nr = open_files_cnt(); + int dso_cnt, limit, i, fd; + + memset(&machine, 0, sizeof(machine)); + + /* set as system limit */ + limit = nr * 4; + TEST_ASSERT_VAL("failed to set file limit", !set_fd_limit(limit)); + + /* and this is now our dso open FDs limit + 1 extra */ + dso_cnt = limit / 2 + 1; + TEST_ASSERT_VAL("failed to create dsos\n", + !dsos__create(dso_cnt, TEST_FILE_SIZE)); + + for (i = 0; i < (dso_cnt - 1); i++) { + struct dso *dso = dsos[i]; + + /* + * Open dsos via dso__data_fd or dso__data_read_offset. + * Both opens the data file and keep it open. + */ + if (i % 2) { + fd = dso__data_fd(dso, &machine); + TEST_ASSERT_VAL("failed to get fd", fd > 0); + } else { + #define BUFSIZE 10 + u8 buf[BUFSIZE]; + ssize_t n; + + n = dso__data_read_offset(dso, &machine, 0, buf, BUFSIZE); + TEST_ASSERT_VAL("failed to read dso", n == BUFSIZE); + } + } + + /* open +1 dso over the allowed limit */ + fd = dso__data_fd(dsos[i], &machine); + TEST_ASSERT_VAL("failed to get fd", fd > 0); + + /* should force the first one to be closed */ + TEST_ASSERT_VAL("failed to close dsos[0]", dsos[0]->data.fd == -1); + + /* cleanup everything */ + dsos__delete(dso_cnt); + + /* Make sure we did not leak any file descriptor. */ + nr_end = open_files_cnt(); + pr_debug("nr start %ld, nr stop %ld\n", nr, nr_end); + TEST_ASSERT_VAL("failed leadking files", nr == nr_end); + return 0; +} + +int test__dso_data_reopen(void) +{ + struct machine machine; + long nr_end, nr = open_files_cnt(); + int fd, fd_extra; + +#define dso_0 (dsos[0]) +#define dso_1 (dsos[1]) +#define dso_2 (dsos[2]) + + memset(&machine, 0, sizeof(machine)); + + /* + * Test scenario: + * - create 3 dso objects + * - set process file descriptor limit to current + * files count + 3 + * - test that the first dso gets closed when we + * reach the files count limit + */ + + /* Make sure we are able to open 3 fds anyway */ + TEST_ASSERT_VAL("failed to set file limit", + !set_fd_limit((nr + 3))); + + TEST_ASSERT_VAL("failed to create dsos\n", !dsos__create(3, TEST_FILE_SIZE)); + + /* open dso_0 */ + fd = dso__data_fd(dso_0, &machine); + TEST_ASSERT_VAL("failed to get fd", fd > 0); + + /* open dso_1 */ + fd = dso__data_fd(dso_1, &machine); + TEST_ASSERT_VAL("failed to get fd", fd > 0); + + /* + * open extra file descriptor and we just + * reached the files count limit + */ + fd_extra = open("/dev/null", O_RDONLY); + TEST_ASSERT_VAL("failed to open extra fd", fd_extra > 0); + + /* open dso_2 */ + fd = dso__data_fd(dso_2, &machine); + TEST_ASSERT_VAL("failed to get fd", fd > 0); + + /* + * dso_0 should get closed, because we reached + * the file descriptor limit + */ + TEST_ASSERT_VAL("failed to close dso_0", dso_0->data.fd == -1); + + /* open dso_0 */ + fd = dso__data_fd(dso_0, &machine); + TEST_ASSERT_VAL("failed to get fd", fd > 0); + + /* + * dso_1 should get closed, because we reached + * the file descriptor limit + */ + TEST_ASSERT_VAL("failed to close dso_1", dso_1->data.fd == -1); + + /* cleanup everything */ + close(fd_extra); + dsos__delete(3); + + /* Make sure we did not leak any file descriptor. */ + nr_end = open_files_cnt(); + pr_debug("nr start %ld, nr stop %ld\n", nr, nr_end); + TEST_ASSERT_VAL("failed leadking files", nr == nr_end); + return 0; +} diff --git a/tools/perf/tests/dwarf-unwind.c b/tools/perf/tests/dwarf-unwind.c index 108f0cd49f4e..96adb730b744 100644 --- a/tools/perf/tests/dwarf-unwind.c +++ b/tools/perf/tests/dwarf-unwind.c @@ -15,7 +15,7 @@ static int mmap_handler(struct perf_tool *tool __maybe_unused, struct perf_sample *sample __maybe_unused, struct machine *machine) { - return machine__process_mmap_event(machine, event, NULL); + return machine__process_mmap2_event(machine, event, NULL); } static int init_live_machine(struct machine *machine) diff --git a/tools/perf/tests/hists_common.c b/tools/perf/tests/hists_common.c index e4e01aadc3be..a62c09134516 100644 --- a/tools/perf/tests/hists_common.c +++ b/tools/perf/tests/hists_common.c @@ -12,9 +12,9 @@ static struct { u32 pid; const char *comm; } fake_threads[] = { - { 100, "perf" }, - { 200, "perf" }, - { 300, "bash" }, + { FAKE_PID_PERF1, "perf" }, + { FAKE_PID_PERF2, "perf" }, + { FAKE_PID_BASH, "bash" }, }; static struct { @@ -22,15 +22,15 @@ static struct { u64 start; const char *filename; } fake_mmap_info[] = { - { 100, 0x40000, "perf" }, - { 100, 0x50000, "libc" }, - { 100, 0xf0000, "[kernel]" }, - { 200, 0x40000, "perf" }, - { 200, 0x50000, "libc" }, - { 200, 0xf0000, "[kernel]" }, - { 300, 0x40000, "bash" }, - { 300, 0x50000, "libc" }, - { 300, 0xf0000, "[kernel]" }, + { FAKE_PID_PERF1, FAKE_MAP_PERF, "perf" }, + { FAKE_PID_PERF1, FAKE_MAP_LIBC, "libc" }, + { FAKE_PID_PERF1, FAKE_MAP_KERNEL, "[kernel]" }, + { FAKE_PID_PERF2, FAKE_MAP_PERF, "perf" }, + { FAKE_PID_PERF2, FAKE_MAP_LIBC, "libc" }, + { FAKE_PID_PERF2, FAKE_MAP_KERNEL, "[kernel]" }, + { FAKE_PID_BASH, FAKE_MAP_BASH, "bash" }, + { FAKE_PID_BASH, FAKE_MAP_LIBC, "libc" }, + { FAKE_PID_BASH, FAKE_MAP_KERNEL, "[kernel]" }, }; struct fake_sym { @@ -40,27 +40,30 @@ struct fake_sym { }; static struct fake_sym perf_syms[] = { - { 700, 100, "main" }, - { 800, 100, "run_command" }, - { 900, 100, "cmd_record" }, + { FAKE_SYM_OFFSET1, FAKE_SYM_LENGTH, "main" }, + { FAKE_SYM_OFFSET2, FAKE_SYM_LENGTH, "run_command" }, + { FAKE_SYM_OFFSET3, FAKE_SYM_LENGTH, "cmd_record" }, }; static struct fake_sym bash_syms[] = { - { 700, 100, "main" }, - { 800, 100, "xmalloc" }, - { 900, 100, "xfree" }, + { FAKE_SYM_OFFSET1, FAKE_SYM_LENGTH, "main" }, + { FAKE_SYM_OFFSET2, FAKE_SYM_LENGTH, "xmalloc" }, + { FAKE_SYM_OFFSET3, FAKE_SYM_LENGTH, "xfree" }, }; static struct fake_sym libc_syms[] = { { 700, 100, "malloc" }, { 800, 100, "free" }, { 900, 100, "realloc" }, + { FAKE_SYM_OFFSET1, FAKE_SYM_LENGTH, "malloc" }, + { FAKE_SYM_OFFSET2, FAKE_SYM_LENGTH, "free" }, + { FAKE_SYM_OFFSET3, FAKE_SYM_LENGTH, "realloc" }, }; static struct fake_sym kernel_syms[] = { - { 700, 100, "schedule" }, - { 800, 100, "page_fault" }, - { 900, 100, "sys_perf_event_open" }, + { FAKE_SYM_OFFSET1, FAKE_SYM_LENGTH, "schedule" }, + { FAKE_SYM_OFFSET2, FAKE_SYM_LENGTH, "page_fault" }, + { FAKE_SYM_OFFSET3, FAKE_SYM_LENGTH, "sys_perf_event_open" }, }; static struct { @@ -102,7 +105,7 @@ struct machine *setup_fake_machine(struct machines *machines) .pid = fake_mmap_info[i].pid, .tid = fake_mmap_info[i].pid, .start = fake_mmap_info[i].start, - .len = 0x1000ULL, + .len = FAKE_MAP_LENGTH, .pgoff = 0ULL, }, }; @@ -193,10 +196,11 @@ void print_hists_out(struct hists *hists) he = rb_entry(node, struct hist_entry, rb_node); if (!he->filtered) { - pr_info("%2d: entry: %8s:%5d [%-8s] %20s: period = %"PRIu64"\n", + pr_info("%2d: entry: %8s:%5d [%-8s] %20s: period = %"PRIu64"/%"PRIu64"\n", i, thread__comm_str(he->thread), he->thread->tid, he->ms.map->dso->short_name, - he->ms.sym->name, he->stat.period); + he->ms.sym->name, he->stat.period, + he->stat_acc ? he->stat_acc->period : 0); } i++; diff --git a/tools/perf/tests/hists_common.h b/tools/perf/tests/hists_common.h index 1415ae69d7b6..888254e8665c 100644 --- a/tools/perf/tests/hists_common.h +++ b/tools/perf/tests/hists_common.h @@ -4,6 +4,34 @@ struct machine; struct machines; +#define FAKE_PID_PERF1 100 +#define FAKE_PID_PERF2 200 +#define FAKE_PID_BASH 300 + +#define FAKE_MAP_PERF 0x400000 +#define FAKE_MAP_BASH 0x400000 +#define FAKE_MAP_LIBC 0x500000 +#define FAKE_MAP_KERNEL 0xf00000 +#define FAKE_MAP_LENGTH 0x100000 + +#define FAKE_SYM_OFFSET1 700 +#define FAKE_SYM_OFFSET2 800 +#define FAKE_SYM_OFFSET3 900 +#define FAKE_SYM_LENGTH 100 + +#define FAKE_IP_PERF_MAIN FAKE_MAP_PERF + FAKE_SYM_OFFSET1 +#define FAKE_IP_PERF_RUN_COMMAND FAKE_MAP_PERF + FAKE_SYM_OFFSET2 +#define FAKE_IP_PERF_CMD_RECORD FAKE_MAP_PERF + FAKE_SYM_OFFSET3 +#define FAKE_IP_BASH_MAIN FAKE_MAP_BASH + FAKE_SYM_OFFSET1 +#define FAKE_IP_BASH_XMALLOC FAKE_MAP_BASH + FAKE_SYM_OFFSET2 +#define FAKE_IP_BASH_XFREE FAKE_MAP_BASH + FAKE_SYM_OFFSET3 +#define FAKE_IP_LIBC_MALLOC FAKE_MAP_LIBC + FAKE_SYM_OFFSET1 +#define FAKE_IP_LIBC_FREE FAKE_MAP_LIBC + FAKE_SYM_OFFSET2 +#define FAKE_IP_LIBC_REALLOC FAKE_MAP_LIBC + FAKE_SYM_OFFSET3 +#define FAKE_IP_KERNEL_SCHEDULE FAKE_MAP_KERNEL + FAKE_SYM_OFFSET1 +#define FAKE_IP_KERNEL_PAGE_FAULT FAKE_MAP_KERNEL + FAKE_SYM_OFFSET2 +#define FAKE_IP_KERNEL_SYS_PERF_EVENT_OPEN FAKE_MAP_KERNEL + FAKE_SYM_OFFSET3 + /* * The setup_fake_machine() provides a test environment which consists * of 3 processes that have 3 mappings and in turn, have 3 symbols @@ -13,7 +41,7 @@ struct machines; * ............. ............. ................... * perf: 100 perf main * perf: 100 perf run_command - * perf: 100 perf comd_record + * perf: 100 perf cmd_record * perf: 100 libc malloc * perf: 100 libc free * perf: 100 libc realloc @@ -22,7 +50,7 @@ struct machines; * perf: 100 [kernel] sys_perf_event_open * perf: 200 perf main * perf: 200 perf run_command - * perf: 200 perf comd_record + * perf: 200 perf cmd_record * perf: 200 libc malloc * perf: 200 libc free * perf: 200 libc realloc diff --git a/tools/perf/tests/hists_cumulate.c b/tools/perf/tests/hists_cumulate.c new file mode 100644 index 000000000000..0ac240db2e24 --- /dev/null +++ b/tools/perf/tests/hists_cumulate.c @@ -0,0 +1,726 @@ +#include "perf.h" +#include "util/debug.h" +#include "util/symbol.h" +#include "util/sort.h" +#include "util/evsel.h" +#include "util/evlist.h" +#include "util/machine.h" +#include "util/thread.h" +#include "util/parse-events.h" +#include "tests/tests.h" +#include "tests/hists_common.h" + +struct sample { + u32 pid; + u64 ip; + struct thread *thread; + struct map *map; + struct symbol *sym; +}; + +/* For the numbers, see hists_common.c */ +static struct sample fake_samples[] = { + /* perf [kernel] schedule() */ + { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_KERNEL_SCHEDULE, }, + /* perf [perf] main() */ + { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_PERF_MAIN, }, + /* perf [perf] cmd_record() */ + { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_PERF_CMD_RECORD, }, + /* perf [libc] malloc() */ + { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_LIBC_MALLOC, }, + /* perf [libc] free() */ + { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_LIBC_FREE, }, + /* perf [perf] main() */ + { .pid = FAKE_PID_PERF2, .ip = FAKE_IP_PERF_MAIN, }, + /* perf [kernel] page_fault() */ + { .pid = FAKE_PID_PERF2, .ip = FAKE_IP_KERNEL_PAGE_FAULT, }, + /* bash [bash] main() */ + { .pid = FAKE_PID_BASH, .ip = FAKE_IP_BASH_MAIN, }, + /* bash [bash] xmalloc() */ + { .pid = FAKE_PID_BASH, .ip = FAKE_IP_BASH_XMALLOC, }, + /* bash [kernel] page_fault() */ + { .pid = FAKE_PID_BASH, .ip = FAKE_IP_KERNEL_PAGE_FAULT, }, +}; + +/* + * Will be casted to struct ip_callchain which has all 64 bit entries + * of nr and ips[]. + */ +static u64 fake_callchains[][10] = { + /* schedule => run_command => main */ + { 3, FAKE_IP_KERNEL_SCHEDULE, FAKE_IP_PERF_RUN_COMMAND, FAKE_IP_PERF_MAIN, }, + /* main */ + { 1, FAKE_IP_PERF_MAIN, }, + /* cmd_record => run_command => main */ + { 3, FAKE_IP_PERF_CMD_RECORD, FAKE_IP_PERF_RUN_COMMAND, FAKE_IP_PERF_MAIN, }, + /* malloc => cmd_record => run_command => main */ + { 4, FAKE_IP_LIBC_MALLOC, FAKE_IP_PERF_CMD_RECORD, FAKE_IP_PERF_RUN_COMMAND, + FAKE_IP_PERF_MAIN, }, + /* free => cmd_record => run_command => main */ + { 4, FAKE_IP_LIBC_FREE, FAKE_IP_PERF_CMD_RECORD, FAKE_IP_PERF_RUN_COMMAND, + FAKE_IP_PERF_MAIN, }, + /* main */ + { 1, FAKE_IP_PERF_MAIN, }, + /* page_fault => sys_perf_event_open => run_command => main */ + { 4, FAKE_IP_KERNEL_PAGE_FAULT, FAKE_IP_KERNEL_SYS_PERF_EVENT_OPEN, + FAKE_IP_PERF_RUN_COMMAND, FAKE_IP_PERF_MAIN, }, + /* main */ + { 1, FAKE_IP_BASH_MAIN, }, + /* xmalloc => malloc => xmalloc => malloc => xmalloc => main */ + { 6, FAKE_IP_BASH_XMALLOC, FAKE_IP_LIBC_MALLOC, FAKE_IP_BASH_XMALLOC, + FAKE_IP_LIBC_MALLOC, FAKE_IP_BASH_XMALLOC, FAKE_IP_BASH_MAIN, }, + /* page_fault => malloc => main */ + { 3, FAKE_IP_KERNEL_PAGE_FAULT, FAKE_IP_LIBC_MALLOC, FAKE_IP_BASH_MAIN, }, +}; + +static int add_hist_entries(struct hists *hists, struct machine *machine) +{ + struct addr_location al; + struct perf_evsel *evsel = hists_to_evsel(hists); + struct perf_sample sample = { .period = 1000, }; + size_t i; + + for (i = 0; i < ARRAY_SIZE(fake_samples); i++) { + const union perf_event event = { + .header = { + .misc = PERF_RECORD_MISC_USER, + }, + }; + struct hist_entry_iter iter = { + .hide_unresolved = false, + }; + + if (symbol_conf.cumulate_callchain) + iter.ops = &hist_iter_cumulative; + else + iter.ops = &hist_iter_normal; + + sample.pid = fake_samples[i].pid; + sample.tid = fake_samples[i].pid; + sample.ip = fake_samples[i].ip; + sample.callchain = (struct ip_callchain *)fake_callchains[i]; + + if (perf_event__preprocess_sample(&event, machine, &al, + &sample) < 0) + goto out; + + if (hist_entry_iter__add(&iter, &al, evsel, &sample, + PERF_MAX_STACK_DEPTH, NULL) < 0) + goto out; + + fake_samples[i].thread = al.thread; + fake_samples[i].map = al.map; + fake_samples[i].sym = al.sym; + } + + return TEST_OK; + +out: + pr_debug("Not enough memory for adding a hist entry\n"); + return TEST_FAIL; +} + +static void del_hist_entries(struct hists *hists) +{ + struct hist_entry *he; + struct rb_root *root_in; + struct rb_root *root_out; + struct rb_node *node; + + if (sort__need_collapse) + root_in = &hists->entries_collapsed; + else + root_in = hists->entries_in; + + root_out = &hists->entries; + + while (!RB_EMPTY_ROOT(root_out)) { + node = rb_first(root_out); + + he = rb_entry(node, struct hist_entry, rb_node); + rb_erase(node, root_out); + rb_erase(&he->rb_node_in, root_in); + hist_entry__free(he); + } +} + +typedef int (*test_fn_t)(struct perf_evsel *, struct machine *); + +#define COMM(he) (thread__comm_str(he->thread)) +#define DSO(he) (he->ms.map->dso->short_name) +#define SYM(he) (he->ms.sym->name) +#define CPU(he) (he->cpu) +#define PID(he) (he->thread->tid) +#define DEPTH(he) (he->callchain->max_depth) +#define CDSO(cl) (cl->ms.map->dso->short_name) +#define CSYM(cl) (cl->ms.sym->name) + +struct result { + u64 children; + u64 self; + const char *comm; + const char *dso; + const char *sym; +}; + +struct callchain_result { + u64 nr; + struct { + const char *dso; + const char *sym; + } node[10]; +}; + +static int do_test(struct hists *hists, struct result *expected, size_t nr_expected, + struct callchain_result *expected_callchain, size_t nr_callchain) +{ + char buf[32]; + size_t i, c; + struct hist_entry *he; + struct rb_root *root; + struct rb_node *node; + struct callchain_node *cnode; + struct callchain_list *clist; + + /* + * adding and deleting hist entries must be done outside of this + * function since TEST_ASSERT_VAL() returns in case of failure. + */ + hists__collapse_resort(hists, NULL); + hists__output_resort(hists); + + if (verbose > 2) { + pr_info("use callchain: %d, cumulate callchain: %d\n", + symbol_conf.use_callchain, + symbol_conf.cumulate_callchain); + print_hists_out(hists); + } + + root = &hists->entries; + for (node = rb_first(root), i = 0; + node && (he = rb_entry(node, struct hist_entry, rb_node)); + node = rb_next(node), i++) { + scnprintf(buf, sizeof(buf), "Invalid hist entry #%zd", i); + + TEST_ASSERT_VAL("Incorrect number of hist entry", + i < nr_expected); + TEST_ASSERT_VAL(buf, he->stat.period == expected[i].self && + !strcmp(COMM(he), expected[i].comm) && + !strcmp(DSO(he), expected[i].dso) && + !strcmp(SYM(he), expected[i].sym)); + + if (symbol_conf.cumulate_callchain) + TEST_ASSERT_VAL(buf, he->stat_acc->period == expected[i].children); + + if (!symbol_conf.use_callchain) + continue; + + /* check callchain entries */ + root = &he->callchain->node.rb_root; + cnode = rb_entry(rb_first(root), struct callchain_node, rb_node); + + c = 0; + list_for_each_entry(clist, &cnode->val, list) { + scnprintf(buf, sizeof(buf), "Invalid callchain entry #%zd/%zd", i, c); + + TEST_ASSERT_VAL("Incorrect number of callchain entry", + c < expected_callchain[i].nr); + TEST_ASSERT_VAL(buf, + !strcmp(CDSO(clist), expected_callchain[i].node[c].dso) && + !strcmp(CSYM(clist), expected_callchain[i].node[c].sym)); + c++; + } + /* TODO: handle multiple child nodes properly */ + TEST_ASSERT_VAL("Incorrect number of callchain entry", + c <= expected_callchain[i].nr); + } + TEST_ASSERT_VAL("Incorrect number of hist entry", + i == nr_expected); + TEST_ASSERT_VAL("Incorrect number of callchain entry", + !symbol_conf.use_callchain || nr_expected == nr_callchain); + return 0; +} + +/* NO callchain + NO children */ +static int test1(struct perf_evsel *evsel, struct machine *machine) +{ + int err; + struct hists *hists = &evsel->hists; + /* + * expected output: + * + * Overhead Command Shared Object Symbol + * ======== ======= ============= ============== + * 20.00% perf perf [.] main + * 10.00% bash [kernel] [k] page_fault + * 10.00% bash bash [.] main + * 10.00% bash bash [.] xmalloc + * 10.00% perf [kernel] [k] page_fault + * 10.00% perf [kernel] [k] schedule + * 10.00% perf libc [.] free + * 10.00% perf libc [.] malloc + * 10.00% perf perf [.] cmd_record + */ + struct result expected[] = { + { 0, 2000, "perf", "perf", "main" }, + { 0, 1000, "bash", "[kernel]", "page_fault" }, + { 0, 1000, "bash", "bash", "main" }, + { 0, 1000, "bash", "bash", "xmalloc" }, + { 0, 1000, "perf", "[kernel]", "page_fault" }, + { 0, 1000, "perf", "[kernel]", "schedule" }, + { 0, 1000, "perf", "libc", "free" }, + { 0, 1000, "perf", "libc", "malloc" }, + { 0, 1000, "perf", "perf", "cmd_record" }, + }; + + symbol_conf.use_callchain = false; + symbol_conf.cumulate_callchain = false; + + setup_sorting(); + callchain_register_param(&callchain_param); + + err = add_hist_entries(hists, machine); + if (err < 0) + goto out; + + err = do_test(hists, expected, ARRAY_SIZE(expected), NULL, 0); + +out: + del_hist_entries(hists); + reset_output_field(); + return err; +} + +/* callcain + NO children */ +static int test2(struct perf_evsel *evsel, struct machine *machine) +{ + int err; + struct hists *hists = &evsel->hists; + /* + * expected output: + * + * Overhead Command Shared Object Symbol + * ======== ======= ============= ============== + * 20.00% perf perf [.] main + * | + * --- main + * + * 10.00% bash [kernel] [k] page_fault + * | + * --- page_fault + * malloc + * main + * + * 10.00% bash bash [.] main + * | + * --- main + * + * 10.00% bash bash [.] xmalloc + * | + * --- xmalloc + * malloc + * xmalloc <--- NOTE: there's a cycle + * malloc + * xmalloc + * main + * + * 10.00% perf [kernel] [k] page_fault + * | + * --- page_fault + * sys_perf_event_open + * run_command + * main + * + * 10.00% perf [kernel] [k] schedule + * | + * --- schedule + * run_command + * main + * + * 10.00% perf libc [.] free + * | + * --- free + * cmd_record + * run_command + * main + * + * 10.00% perf libc [.] malloc + * | + * --- malloc + * cmd_record + * run_command + * main + * + * 10.00% perf perf [.] cmd_record + * | + * --- cmd_record + * run_command + * main + * + */ + struct result expected[] = { + { 0, 2000, "perf", "perf", "main" }, + { 0, 1000, "bash", "[kernel]", "page_fault" }, + { 0, 1000, "bash", "bash", "main" }, + { 0, 1000, "bash", "bash", "xmalloc" }, + { 0, 1000, "perf", "[kernel]", "page_fault" }, + { 0, 1000, "perf", "[kernel]", "schedule" }, + { 0, 1000, "perf", "libc", "free" }, + { 0, 1000, "perf", "libc", "malloc" }, + { 0, 1000, "perf", "perf", "cmd_record" }, + }; + struct callchain_result expected_callchain[] = { + { + 1, { { "perf", "main" }, }, + }, + { + 3, { { "[kernel]", "page_fault" }, + { "libc", "malloc" }, + { "bash", "main" }, }, + }, + { + 1, { { "bash", "main" }, }, + }, + { + 6, { { "bash", "xmalloc" }, + { "libc", "malloc" }, + { "bash", "xmalloc" }, + { "libc", "malloc" }, + { "bash", "xmalloc" }, + { "bash", "main" }, }, + }, + { + 4, { { "[kernel]", "page_fault" }, + { "[kernel]", "sys_perf_event_open" }, + { "perf", "run_command" }, + { "perf", "main" }, }, + }, + { + 3, { { "[kernel]", "schedule" }, + { "perf", "run_command" }, + { "perf", "main" }, }, + }, + { + 4, { { "libc", "free" }, + { "perf", "cmd_record" }, + { "perf", "run_command" }, + { "perf", "main" }, }, + }, + { + 4, { { "libc", "malloc" }, + { "perf", "cmd_record" }, + { "perf", "run_command" }, + { "perf", "main" }, }, + }, + { + 3, { { "perf", "cmd_record" }, + { "perf", "run_command" }, + { "perf", "main" }, }, + }, + }; + + symbol_conf.use_callchain = true; + symbol_conf.cumulate_callchain = false; + + setup_sorting(); + callchain_register_param(&callchain_param); + + err = add_hist_entries(hists, machine); + if (err < 0) + goto out; + + err = do_test(hists, expected, ARRAY_SIZE(expected), + expected_callchain, ARRAY_SIZE(expected_callchain)); + +out: + del_hist_entries(hists); + reset_output_field(); + return err; +} + +/* NO callchain + children */ +static int test3(struct perf_evsel *evsel, struct machine *machine) +{ + int err; + struct hists *hists = &evsel->hists; + /* + * expected output: + * + * Children Self Command Shared Object Symbol + * ======== ======== ======= ============= ======================= + * 70.00% 20.00% perf perf [.] main + * 50.00% 0.00% perf perf [.] run_command + * 30.00% 10.00% bash bash [.] main + * 30.00% 10.00% perf perf [.] cmd_record + * 20.00% 0.00% bash libc [.] malloc + * 10.00% 10.00% bash [kernel] [k] page_fault + * 10.00% 10.00% perf [kernel] [k] schedule + * 10.00% 0.00% perf [kernel] [k] sys_perf_event_open + * 10.00% 10.00% perf [kernel] [k] page_fault + * 10.00% 10.00% perf libc [.] free + * 10.00% 10.00% perf libc [.] malloc + * 10.00% 10.00% bash bash [.] xmalloc + */ + struct result expected[] = { + { 7000, 2000, "perf", "perf", "main" }, + { 5000, 0, "perf", "perf", "run_command" }, + { 3000, 1000, "bash", "bash", "main" }, + { 3000, 1000, "perf", "perf", "cmd_record" }, + { 2000, 0, "bash", "libc", "malloc" }, + { 1000, 1000, "bash", "[kernel]", "page_fault" }, + { 1000, 1000, "perf", "[kernel]", "schedule" }, + { 1000, 0, "perf", "[kernel]", "sys_perf_event_open" }, + { 1000, 1000, "perf", "[kernel]", "page_fault" }, + { 1000, 1000, "perf", "libc", "free" }, + { 1000, 1000, "perf", "libc", "malloc" }, + { 1000, 1000, "bash", "bash", "xmalloc" }, + }; + + symbol_conf.use_callchain = false; + symbol_conf.cumulate_callchain = true; + + setup_sorting(); + callchain_register_param(&callchain_param); + + err = add_hist_entries(hists, machine); + if (err < 0) + goto out; + + err = do_test(hists, expected, ARRAY_SIZE(expected), NULL, 0); + +out: + del_hist_entries(hists); + reset_output_field(); + return err; +} + +/* callchain + children */ +static int test4(struct perf_evsel *evsel, struct machine *machine) +{ + int err; + struct hists *hists = &evsel->hists; + /* + * expected output: + * + * Children Self Command Shared Object Symbol + * ======== ======== ======= ============= ======================= + * 70.00% 20.00% perf perf [.] main + * | + * --- main + * + * 50.00% 0.00% perf perf [.] run_command + * | + * --- run_command + * main + * + * 30.00% 10.00% bash bash [.] main + * | + * --- main + * + * 30.00% 10.00% perf perf [.] cmd_record + * | + * --- cmd_record + * run_command + * main + * + * 20.00% 0.00% bash libc [.] malloc + * | + * --- malloc + * | + * |--50.00%-- xmalloc + * | main + * --50.00%-- main + * + * 10.00% 10.00% bash [kernel] [k] page_fault + * | + * --- page_fault + * malloc + * main + * + * 10.00% 10.00% perf [kernel] [k] schedule + * | + * --- schedule + * run_command + * main + * + * 10.00% 0.00% perf [kernel] [k] sys_perf_event_open + * | + * --- sys_perf_event_open + * run_command + * main + * + * 10.00% 10.00% perf [kernel] [k] page_fault + * | + * --- page_fault + * sys_perf_event_open + * run_command + * main + * + * 10.00% 10.00% perf libc [.] free + * | + * --- free + * cmd_record + * run_command + * main + * + * 10.00% 10.00% perf libc [.] malloc + * | + * --- malloc + * cmd_record + * run_command + * main + * + * 10.00% 10.00% bash bash [.] xmalloc + * | + * --- xmalloc + * malloc + * xmalloc <--- NOTE: there's a cycle + * malloc + * xmalloc + * main + * + */ + struct result expected[] = { + { 7000, 2000, "perf", "perf", "main" }, + { 5000, 0, "perf", "perf", "run_command" }, + { 3000, 1000, "bash", "bash", "main" }, + { 3000, 1000, "perf", "perf", "cmd_record" }, + { 2000, 0, "bash", "libc", "malloc" }, + { 1000, 1000, "bash", "[kernel]", "page_fault" }, + { 1000, 1000, "perf", "[kernel]", "schedule" }, + { 1000, 0, "perf", "[kernel]", "sys_perf_event_open" }, + { 1000, 1000, "perf", "[kernel]", "page_fault" }, + { 1000, 1000, "perf", "libc", "free" }, + { 1000, 1000, "perf", "libc", "malloc" }, + { 1000, 1000, "bash", "bash", "xmalloc" }, + }; + struct callchain_result expected_callchain[] = { + { + 1, { { "perf", "main" }, }, + }, + { + 2, { { "perf", "run_command" }, + { "perf", "main" }, }, + }, + { + 1, { { "bash", "main" }, }, + }, + { + 3, { { "perf", "cmd_record" }, + { "perf", "run_command" }, + { "perf", "main" }, }, + }, + { + 4, { { "libc", "malloc" }, + { "bash", "xmalloc" }, + { "bash", "main" }, + { "bash", "main" }, }, + }, + { + 3, { { "[kernel]", "page_fault" }, + { "libc", "malloc" }, + { "bash", "main" }, }, + }, + { + 3, { { "[kernel]", "schedule" }, + { "perf", "run_command" }, + { "perf", "main" }, }, + }, + { + 3, { { "[kernel]", "sys_perf_event_open" }, + { "perf", "run_command" }, + { "perf", "main" }, }, + }, + { + 4, { { "[kernel]", "page_fault" }, + { "[kernel]", "sys_perf_event_open" }, + { "perf", "run_command" }, + { "perf", "main" }, }, + }, + { + 4, { { "libc", "free" }, + { "perf", "cmd_record" }, + { "perf", "run_command" }, + { "perf", "main" }, }, + }, + { + 4, { { "libc", "malloc" }, + { "perf", "cmd_record" }, + { "perf", "run_command" }, + { "perf", "main" }, }, + }, + { + 6, { { "bash", "xmalloc" }, + { "libc", "malloc" }, + { "bash", "xmalloc" }, + { "libc", "malloc" }, + { "bash", "xmalloc" }, + { "bash", "main" }, }, + }, + }; + + symbol_conf.use_callchain = true; + symbol_conf.cumulate_callchain = true; + + setup_sorting(); + callchain_register_param(&callchain_param); + + err = add_hist_entries(hists, machine); + if (err < 0) + goto out; + + err = do_test(hists, expected, ARRAY_SIZE(expected), + expected_callchain, ARRAY_SIZE(expected_callchain)); + +out: + del_hist_entries(hists); + reset_output_field(); + return err; +} + +int test__hists_cumulate(void) +{ + int err = TEST_FAIL; + struct machines machines; + struct machine *machine; + struct perf_evsel *evsel; + struct perf_evlist *evlist = perf_evlist__new(); + size_t i; + test_fn_t testcases[] = { + test1, + test2, + test3, + test4, + }; + + TEST_ASSERT_VAL("No memory", evlist); + + err = parse_events(evlist, "cpu-clock"); + if (err) + goto out; + + machines__init(&machines); + + /* setup threads/dso/map/symbols also */ + machine = setup_fake_machine(&machines); + if (!machine) + goto out; + + if (verbose > 1) + machine__fprintf(machine, stderr); + + evsel = perf_evlist__first(evlist); + + for (i = 0; i < ARRAY_SIZE(testcases); i++) { + err = testcases[i](evsel, machine); + if (err < 0) + break; + } + +out: + /* tear down everything */ + perf_evlist__delete(evlist); + machines__exit(&machines); + + return err; +} diff --git a/tools/perf/tests/hists_filter.c b/tools/perf/tests/hists_filter.c index c5ba924a3581..821f581fd930 100644 --- a/tools/perf/tests/hists_filter.c +++ b/tools/perf/tests/hists_filter.c @@ -21,33 +21,33 @@ struct sample { /* For the numbers, see hists_common.c */ static struct sample fake_samples[] = { /* perf [kernel] schedule() */ - { .pid = 100, .ip = 0xf0000 + 700, }, + { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_KERNEL_SCHEDULE, }, /* perf [perf] main() */ - { .pid = 100, .ip = 0x40000 + 700, }, + { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_PERF_MAIN, }, /* perf [libc] malloc() */ - { .pid = 100, .ip = 0x50000 + 700, }, + { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_LIBC_MALLOC, }, /* perf [perf] main() */ - { .pid = 200, .ip = 0x40000 + 700, }, /* will be merged */ + { .pid = FAKE_PID_PERF2, .ip = FAKE_IP_PERF_MAIN, }, /* will be merged */ /* perf [perf] cmd_record() */ - { .pid = 200, .ip = 0x40000 + 900, }, + { .pid = FAKE_PID_PERF2, .ip = FAKE_IP_PERF_CMD_RECORD, }, /* perf [kernel] page_fault() */ - { .pid = 200, .ip = 0xf0000 + 800, }, + { .pid = FAKE_PID_PERF2, .ip = FAKE_IP_KERNEL_PAGE_FAULT, }, /* bash [bash] main() */ - { .pid = 300, .ip = 0x40000 + 700, }, + { .pid = FAKE_PID_BASH, .ip = FAKE_IP_BASH_MAIN, }, /* bash [bash] xmalloc() */ - { .pid = 300, .ip = 0x40000 + 800, }, + { .pid = FAKE_PID_BASH, .ip = FAKE_IP_BASH_XMALLOC, }, /* bash [libc] malloc() */ - { .pid = 300, .ip = 0x50000 + 700, }, + { .pid = FAKE_PID_BASH, .ip = FAKE_IP_LIBC_MALLOC, }, /* bash [kernel] page_fault() */ - { .pid = 300, .ip = 0xf0000 + 800, }, + { .pid = FAKE_PID_BASH, .ip = FAKE_IP_KERNEL_PAGE_FAULT, }, }; -static int add_hist_entries(struct perf_evlist *evlist, struct machine *machine) +static int add_hist_entries(struct perf_evlist *evlist, + struct machine *machine __maybe_unused) { struct perf_evsel *evsel; struct addr_location al; - struct hist_entry *he; - struct perf_sample sample = { .cpu = 0, }; + struct perf_sample sample = { .period = 100, }; size_t i; /* @@ -62,6 +62,10 @@ static int add_hist_entries(struct perf_evlist *evlist, struct machine *machine) .misc = PERF_RECORD_MISC_USER, }, }; + struct hist_entry_iter iter = { + .ops = &hist_iter_normal, + .hide_unresolved = false, + }; /* make sure it has no filter at first */ evsel->hists.thread_filter = NULL; @@ -76,18 +80,13 @@ static int add_hist_entries(struct perf_evlist *evlist, struct machine *machine) &sample) < 0) goto out; - he = __hists__add_entry(&evsel->hists, &al, NULL, - NULL, NULL, 100, 1, 0); - if (he == NULL) + if (hist_entry_iter__add(&iter, &al, evsel, &sample, + PERF_MAX_STACK_DEPTH, NULL) < 0) goto out; fake_samples[i].thread = al.thread; fake_samples[i].map = al.map; fake_samples[i].sym = al.sym; - - hists__inc_nr_events(he->hists, PERF_RECORD_SAMPLE); - if (!he->filtered) - he->hists->stats.nr_non_filtered_samples++; } } diff --git a/tools/perf/tests/hists_link.c b/tools/perf/tests/hists_link.c index 5ffa2c3eb77d..d4b34b0f50a2 100644 --- a/tools/perf/tests/hists_link.c +++ b/tools/perf/tests/hists_link.c @@ -21,41 +21,41 @@ struct sample { /* For the numbers, see hists_common.c */ static struct sample fake_common_samples[] = { /* perf [kernel] schedule() */ - { .pid = 100, .ip = 0xf0000 + 700, }, + { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_KERNEL_SCHEDULE, }, /* perf [perf] main() */ - { .pid = 200, .ip = 0x40000 + 700, }, + { .pid = FAKE_PID_PERF2, .ip = FAKE_IP_PERF_MAIN, }, /* perf [perf] cmd_record() */ - { .pid = 200, .ip = 0x40000 + 900, }, + { .pid = FAKE_PID_PERF2, .ip = FAKE_IP_PERF_CMD_RECORD, }, /* bash [bash] xmalloc() */ - { .pid = 300, .ip = 0x40000 + 800, }, + { .pid = FAKE_PID_BASH, .ip = FAKE_IP_BASH_XMALLOC, }, /* bash [libc] malloc() */ - { .pid = 300, .ip = 0x50000 + 700, }, + { .pid = FAKE_PID_BASH, .ip = FAKE_IP_LIBC_MALLOC, }, }; static struct sample fake_samples[][5] = { { /* perf [perf] run_command() */ - { .pid = 100, .ip = 0x40000 + 800, }, + { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_PERF_RUN_COMMAND, }, /* perf [libc] malloc() */ - { .pid = 100, .ip = 0x50000 + 700, }, + { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_LIBC_MALLOC, }, /* perf [kernel] page_fault() */ - { .pid = 100, .ip = 0xf0000 + 800, }, + { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_KERNEL_PAGE_FAULT, }, /* perf [kernel] sys_perf_event_open() */ - { .pid = 200, .ip = 0xf0000 + 900, }, + { .pid = FAKE_PID_PERF2, .ip = FAKE_IP_KERNEL_SYS_PERF_EVENT_OPEN, }, /* bash [libc] free() */ - { .pid = 300, .ip = 0x50000 + 800, }, + { .pid = FAKE_PID_BASH, .ip = FAKE_IP_LIBC_FREE, }, }, { /* perf [libc] free() */ - { .pid = 200, .ip = 0x50000 + 800, }, + { .pid = FAKE_PID_PERF2, .ip = FAKE_IP_LIBC_FREE, }, /* bash [libc] malloc() */ - { .pid = 300, .ip = 0x50000 + 700, }, /* will be merged */ + { .pid = FAKE_PID_BASH, .ip = FAKE_IP_LIBC_MALLOC, }, /* will be merged */ /* bash [bash] xfee() */ - { .pid = 300, .ip = 0x40000 + 900, }, + { .pid = FAKE_PID_BASH, .ip = FAKE_IP_BASH_XFREE, }, /* bash [libc] realloc() */ - { .pid = 300, .ip = 0x50000 + 900, }, + { .pid = FAKE_PID_BASH, .ip = FAKE_IP_LIBC_REALLOC, }, /* bash [kernel] page_fault() */ - { .pid = 300, .ip = 0xf0000 + 800, }, + { .pid = FAKE_PID_BASH, .ip = FAKE_IP_KERNEL_PAGE_FAULT, }, }, }; @@ -64,7 +64,7 @@ static int add_hist_entries(struct perf_evlist *evlist, struct machine *machine) struct perf_evsel *evsel; struct addr_location al; struct hist_entry *he; - struct perf_sample sample = { .cpu = 0, }; + struct perf_sample sample = { .period = 1, }; size_t i = 0, k; /* @@ -88,7 +88,7 @@ static int add_hist_entries(struct perf_evlist *evlist, struct machine *machine) goto out; he = __hists__add_entry(&evsel->hists, &al, NULL, - NULL, NULL, 1, 1, 0); + NULL, NULL, 1, 1, 0, true); if (he == NULL) goto out; @@ -112,7 +112,7 @@ static int add_hist_entries(struct perf_evlist *evlist, struct machine *machine) goto out; he = __hists__add_entry(&evsel->hists, &al, NULL, - NULL, NULL, 1, 1, 0); + NULL, NULL, 1, 1, 0, true); if (he == NULL) goto out; diff --git a/tools/perf/tests/hists_output.c b/tools/perf/tests/hists_output.c index a16850551797..e3bbd6c54c1b 100644 --- a/tools/perf/tests/hists_output.c +++ b/tools/perf/tests/hists_output.c @@ -22,31 +22,31 @@ struct sample { /* For the numbers, see hists_common.c */ static struct sample fake_samples[] = { /* perf [kernel] schedule() */ - { .cpu = 0, .pid = 100, .ip = 0xf0000 + 700, }, + { .cpu = 0, .pid = FAKE_PID_PERF1, .ip = FAKE_IP_KERNEL_SCHEDULE, }, /* perf [perf] main() */ - { .cpu = 1, .pid = 100, .ip = 0x40000 + 700, }, + { .cpu = 1, .pid = FAKE_PID_PERF1, .ip = FAKE_IP_PERF_MAIN, }, /* perf [perf] cmd_record() */ - { .cpu = 1, .pid = 100, .ip = 0x40000 + 900, }, + { .cpu = 1, .pid = FAKE_PID_PERF1, .ip = FAKE_IP_PERF_CMD_RECORD, }, /* perf [libc] malloc() */ - { .cpu = 1, .pid = 100, .ip = 0x50000 + 700, }, + { .cpu = 1, .pid = FAKE_PID_PERF1, .ip = FAKE_IP_LIBC_MALLOC, }, /* perf [libc] free() */ - { .cpu = 2, .pid = 100, .ip = 0x50000 + 800, }, + { .cpu = 2, .pid = FAKE_PID_PERF1, .ip = FAKE_IP_LIBC_FREE, }, /* perf [perf] main() */ - { .cpu = 2, .pid = 200, .ip = 0x40000 + 700, }, + { .cpu = 2, .pid = FAKE_PID_PERF2, .ip = FAKE_IP_PERF_MAIN, }, /* perf [kernel] page_fault() */ - { .cpu = 2, .pid = 200, .ip = 0xf0000 + 800, }, + { .cpu = 2, .pid = FAKE_PID_PERF2, .ip = FAKE_IP_KERNEL_PAGE_FAULT, }, /* bash [bash] main() */ - { .cpu = 3, .pid = 300, .ip = 0x40000 + 700, }, + { .cpu = 3, .pid = FAKE_PID_BASH, .ip = FAKE_IP_BASH_MAIN, }, /* bash [bash] xmalloc() */ - { .cpu = 0, .pid = 300, .ip = 0x40000 + 800, }, + { .cpu = 0, .pid = FAKE_PID_BASH, .ip = FAKE_IP_BASH_XMALLOC, }, /* bash [kernel] page_fault() */ - { .cpu = 1, .pid = 300, .ip = 0xf0000 + 800, }, + { .cpu = 1, .pid = FAKE_PID_BASH, .ip = FAKE_IP_KERNEL_PAGE_FAULT, }, }; static int add_hist_entries(struct hists *hists, struct machine *machine) { struct addr_location al; - struct hist_entry *he; + struct perf_evsel *evsel = hists_to_evsel(hists); struct perf_sample sample = { .period = 100, }; size_t i; @@ -56,6 +56,10 @@ static int add_hist_entries(struct hists *hists, struct machine *machine) .misc = PERF_RECORD_MISC_USER, }, }; + struct hist_entry_iter iter = { + .ops = &hist_iter_normal, + .hide_unresolved = false, + }; sample.cpu = fake_samples[i].cpu; sample.pid = fake_samples[i].pid; @@ -66,9 +70,8 @@ static int add_hist_entries(struct hists *hists, struct machine *machine) &sample) < 0) goto out; - he = __hists__add_entry(hists, &al, NULL, NULL, NULL, - sample.period, 1, 0); - if (he == NULL) + if (hist_entry_iter__add(&iter, &al, evsel, &sample, + PERF_MAX_STACK_DEPTH, NULL) < 0) goto out; fake_samples[i].thread = al.thread; diff --git a/tools/perf/tests/make b/tools/perf/tests/make index 2f92d6e7ee00..69a71ff84e01 100644 --- a/tools/perf/tests/make +++ b/tools/perf/tests/make @@ -205,8 +205,7 @@ $(run): ( eval $$cmd ) >> $@ 2>&1; \ echo " test: $(call test,$@)" >> $@ 2>&1; \ $(call test,$@) && \ - rm -f $@ \ - rm -rf $$TMP_DEST + rm -rf $@ $$TMP_DEST || (cat $@ ; false) $(run_O): $(call clean) @@ -217,9 +216,7 @@ $(run_O): ( eval $$cmd ) >> $@ 2>&1 && \ echo " test: $(call test_O,$@)" >> $@ 2>&1; \ $(call test_O,$@) && \ - rm -f $@ && \ - rm -rf $$TMP_O \ - rm -rf $$TMP_DEST + rm -rf $@ $$TMP_O $$TMP_DEST || (cat $@ ; false) tarpkg: @cmd="$(PERF)/tests/perf-targz-src-pkg $(PERF)"; \ diff --git a/tools/perf/tests/tests.h b/tools/perf/tests/tests.h index d76c0e2e6635..ed64790a395f 100644 --- a/tools/perf/tests/tests.h +++ b/tools/perf/tests/tests.h @@ -28,6 +28,8 @@ int test__syscall_open_tp_fields(void); int test__pmu(void); int test__attr(void); int test__dso_data(void); +int test__dso_data_cache(void); +int test__dso_data_reopen(void); int test__parse_events(void); int test__hists_link(void); int test__python_use(void); @@ -45,6 +47,7 @@ int test__hists_filter(void); int test__mmap_thread_lookup(void); int test__thread_mg_share(void); int test__hists_output(void); +int test__hists_cumulate(void); #if defined(__x86_64__) || defined(__i386__) || defined(__arm__) #ifdef HAVE_DWARF_UNWIND_SUPPORT diff --git a/tools/perf/ui/browser.c b/tools/perf/ui/browser.c index d11541d4d7d7..3ccf6e14f89b 100644 --- a/tools/perf/ui/browser.c +++ b/tools/perf/ui/browser.c @@ -194,7 +194,7 @@ int ui_browser__warning(struct ui_browser *browser, int timeout, ui_helpline__vpush(format, args); va_end(args); } else { - while ((key == ui__question_window("Warning!", text, + while ((key = ui__question_window("Warning!", text, "Press any key...", timeout)) == K_RESIZE) ui_browser__handle_resize(browser); diff --git a/tools/perf/ui/browsers/hists.c b/tools/perf/ui/browsers/hists.c index 1c331b934ffc..52c03fbbba17 100644 --- a/tools/perf/ui/browsers/hists.c +++ b/tools/perf/ui/browsers/hists.c @@ -37,7 +37,6 @@ static int hists__browser_title(struct hists *hists, char *bf, size_t size, static void hist_browser__update_nr_entries(struct hist_browser *hb); static struct rb_node *hists__filter_entries(struct rb_node *nd, - struct hists *hists, float min_pcnt); static bool hist_browser__has_filter(struct hist_browser *hb) @@ -319,7 +318,7 @@ __hist_browser__set_folding(struct hist_browser *browser, bool unfold) struct hists *hists = browser->hists; for (nd = rb_first(&hists->entries); - (nd = hists__filter_entries(nd, hists, browser->min_pcnt)) != NULL; + (nd = hists__filter_entries(nd, browser->min_pcnt)) != NULL; nd = rb_next(nd)) { struct hist_entry *he = rb_entry(nd, struct hist_entry, rb_node); hist_entry__set_folding(he, unfold); @@ -651,13 +650,36 @@ hist_browser__hpp_color_##_type(struct perf_hpp_fmt *fmt __maybe_unused,\ __hpp__slsmg_color_printf, true); \ } +#define __HPP_COLOR_ACC_PERCENT_FN(_type, _field) \ +static u64 __hpp_get_acc_##_field(struct hist_entry *he) \ +{ \ + return he->stat_acc->_field; \ +} \ + \ +static int \ +hist_browser__hpp_color_##_type(struct perf_hpp_fmt *fmt __maybe_unused,\ + struct perf_hpp *hpp, \ + struct hist_entry *he) \ +{ \ + if (!symbol_conf.cumulate_callchain) { \ + int ret = scnprintf(hpp->buf, hpp->size, "%8s", "N/A"); \ + slsmg_printf("%s", hpp->buf); \ + \ + return ret; \ + } \ + return __hpp__fmt(hpp, he, __hpp_get_acc_##_field, " %6.2f%%", \ + __hpp__slsmg_color_printf, true); \ +} + __HPP_COLOR_PERCENT_FN(overhead, period) __HPP_COLOR_PERCENT_FN(overhead_sys, period_sys) __HPP_COLOR_PERCENT_FN(overhead_us, period_us) __HPP_COLOR_PERCENT_FN(overhead_guest_sys, period_guest_sys) __HPP_COLOR_PERCENT_FN(overhead_guest_us, period_guest_us) +__HPP_COLOR_ACC_PERCENT_FN(overhead_acc, period) #undef __HPP_COLOR_PERCENT_FN +#undef __HPP_COLOR_ACC_PERCENT_FN void hist_browser__init_hpp(void) { @@ -671,6 +693,8 @@ void hist_browser__init_hpp(void) hist_browser__hpp_color_overhead_guest_sys; perf_hpp__format[PERF_HPP__OVERHEAD_GUEST_US].color = hist_browser__hpp_color_overhead_guest_us; + perf_hpp__format[PERF_HPP__OVERHEAD_ACC].color = + hist_browser__hpp_color_overhead_acc; } static int hist_browser__show_entry(struct hist_browser *browser, @@ -783,15 +807,12 @@ static unsigned int hist_browser__refresh(struct ui_browser *browser) for (nd = browser->top; nd; nd = rb_next(nd)) { struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); - u64 total = hists__total_period(h->hists); - float percent = 0.0; + float percent; if (h->filtered) continue; - if (total) - percent = h->stat.period * 100.0 / total; - + percent = hist_entry__get_percent_limit(h); if (percent < hb->min_pcnt) continue; @@ -804,16 +825,11 @@ static unsigned int hist_browser__refresh(struct ui_browser *browser) } static struct rb_node *hists__filter_entries(struct rb_node *nd, - struct hists *hists, float min_pcnt) { while (nd != NULL) { struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); - u64 total = hists__total_period(hists); - float percent = 0.0; - - if (total) - percent = h->stat.period * 100.0 / total; + float percent = hist_entry__get_percent_limit(h); if (!h->filtered && percent >= min_pcnt) return nd; @@ -825,16 +841,11 @@ static struct rb_node *hists__filter_entries(struct rb_node *nd, } static struct rb_node *hists__filter_prev_entries(struct rb_node *nd, - struct hists *hists, float min_pcnt) { while (nd != NULL) { struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); - u64 total = hists__total_period(hists); - float percent = 0.0; - - if (total) - percent = h->stat.period * 100.0 / total; + float percent = hist_entry__get_percent_limit(h); if (!h->filtered && percent >= min_pcnt) return nd; @@ -863,14 +874,14 @@ static void ui_browser__hists_seek(struct ui_browser *browser, switch (whence) { case SEEK_SET: nd = hists__filter_entries(rb_first(browser->entries), - hb->hists, hb->min_pcnt); + hb->min_pcnt); break; case SEEK_CUR: nd = browser->top; goto do_offset; case SEEK_END: nd = hists__filter_prev_entries(rb_last(browser->entries), - hb->hists, hb->min_pcnt); + hb->min_pcnt); first = false; break; default: @@ -913,8 +924,7 @@ do_offset: break; } } - nd = hists__filter_entries(rb_next(nd), hb->hists, - hb->min_pcnt); + nd = hists__filter_entries(rb_next(nd), hb->min_pcnt); if (nd == NULL) break; --offset; @@ -947,7 +957,7 @@ do_offset: } } - nd = hists__filter_prev_entries(rb_prev(nd), hb->hists, + nd = hists__filter_prev_entries(rb_prev(nd), hb->min_pcnt); if (nd == NULL) break; @@ -1126,7 +1136,6 @@ static int hist_browser__fprintf_entry(struct hist_browser *browser, static int hist_browser__fprintf(struct hist_browser *browser, FILE *fp) { struct rb_node *nd = hists__filter_entries(rb_first(browser->b.entries), - browser->hists, browser->min_pcnt); int printed = 0; @@ -1134,8 +1143,7 @@ static int hist_browser__fprintf(struct hist_browser *browser, FILE *fp) struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); printed += hist_browser__fprintf_entry(browser, h, fp); - nd = hists__filter_entries(rb_next(nd), browser->hists, - browser->min_pcnt); + nd = hists__filter_entries(rb_next(nd), browser->min_pcnt); } return printed; @@ -1372,8 +1380,7 @@ static void hist_browser__update_nr_entries(struct hist_browser *hb) return; } - while ((nd = hists__filter_entries(nd, hb->hists, - hb->min_pcnt)) != NULL) { + while ((nd = hists__filter_entries(nd, hb->min_pcnt)) != NULL) { nr_entries++; nd = rb_next(nd); } @@ -1699,14 +1706,14 @@ zoom_dso: zoom_out_dso: ui_helpline__pop(); browser->hists->dso_filter = NULL; - sort_dso.elide = false; + perf_hpp__set_elide(HISTC_DSO, false); } else { if (dso == NULL) continue; ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s DSO\"", dso->kernel ? "the Kernel" : dso->short_name); browser->hists->dso_filter = dso; - sort_dso.elide = true; + perf_hpp__set_elide(HISTC_DSO, true); pstack__push(fstack, &browser->hists->dso_filter); } hists__filter_by_dso(hists); @@ -1718,13 +1725,13 @@ zoom_thread: zoom_out_thread: ui_helpline__pop(); browser->hists->thread_filter = NULL; - sort_thread.elide = false; + perf_hpp__set_elide(HISTC_THREAD, false); } else { ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s(%d) thread\"", thread->comm_set ? thread__comm_str(thread) : "", thread->tid); browser->hists->thread_filter = thread; - sort_thread.elide = true; + perf_hpp__set_elide(HISTC_THREAD, false); pstack__push(fstack, &browser->hists->thread_filter); } hists__filter_by_thread(hists); diff --git a/tools/perf/ui/gtk/hists.c b/tools/perf/ui/gtk/hists.c index 9d90683914d4..6ca60e482cdc 100644 --- a/tools/perf/ui/gtk/hists.c +++ b/tools/perf/ui/gtk/hists.c @@ -47,11 +47,26 @@ static int perf_gtk__hpp_color_##_type(struct perf_hpp_fmt *fmt __maybe_unused, __percent_color_snprintf, true); \ } +#define __HPP_COLOR_ACC_PERCENT_FN(_type, _field) \ +static u64 he_get_acc_##_field(struct hist_entry *he) \ +{ \ + return he->stat_acc->_field; \ +} \ + \ +static int perf_gtk__hpp_color_##_type(struct perf_hpp_fmt *fmt __maybe_unused, \ + struct perf_hpp *hpp, \ + struct hist_entry *he) \ +{ \ + return __hpp__fmt_acc(hpp, he, he_get_acc_##_field, " %6.2f%%", \ + __percent_color_snprintf, true); \ +} + __HPP_COLOR_PERCENT_FN(overhead, period) __HPP_COLOR_PERCENT_FN(overhead_sys, period_sys) __HPP_COLOR_PERCENT_FN(overhead_us, period_us) __HPP_COLOR_PERCENT_FN(overhead_guest_sys, period_guest_sys) __HPP_COLOR_PERCENT_FN(overhead_guest_us, period_guest_us) +__HPP_COLOR_ACC_PERCENT_FN(overhead_acc, period) #undef __HPP_COLOR_PERCENT_FN @@ -68,6 +83,8 @@ void perf_gtk__init_hpp(void) perf_gtk__hpp_color_overhead_guest_sys; perf_hpp__format[PERF_HPP__OVERHEAD_GUEST_US].color = perf_gtk__hpp_color_overhead_guest_us; + perf_hpp__format[PERF_HPP__OVERHEAD_ACC].color = + perf_gtk__hpp_color_overhead_acc; } static void callchain_list__sym_name(struct callchain_list *cl, @@ -181,6 +198,13 @@ static void perf_gtk__show_hists(GtkWidget *window, struct hists *hists, if (perf_hpp__should_skip(fmt)) continue; + /* + * XXX no way to determine where symcol column is.. + * Just use last column for now. + */ + if (perf_hpp__is_sort_entry(fmt)) + sym_col = col_idx; + fmt->header(fmt, &hpp, hists_to_evsel(hists)); gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), @@ -209,14 +233,12 @@ static void perf_gtk__show_hists(GtkWidget *window, struct hists *hists, struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); GtkTreeIter iter; u64 total = hists__total_period(h->hists); - float percent = 0.0; + float percent; if (h->filtered) continue; - if (total) - percent = h->stat.period * 100.0 / total; - + percent = hist_entry__get_percent_limit(h); if (percent < min_pcnt) continue; @@ -238,7 +260,8 @@ static void perf_gtk__show_hists(GtkWidget *window, struct hists *hists, if (symbol_conf.use_callchain && sort__has_sym) { if (callchain_param.mode == CHAIN_GRAPH_REL) - total = h->stat.period; + total = symbol_conf.cumulate_callchain ? + h->stat_acc->period : h->stat.period; perf_gtk__add_callchain(&h->sorted_chain, store, &iter, sym_col, total); diff --git a/tools/perf/ui/hist.c b/tools/perf/ui/hist.c index 4484f5bd1b14..498adb23c02e 100644 --- a/tools/perf/ui/hist.c +++ b/tools/perf/ui/hist.c @@ -104,6 +104,18 @@ int __hpp__fmt(struct perf_hpp *hpp, struct hist_entry *he, return ret; } +int __hpp__fmt_acc(struct perf_hpp *hpp, struct hist_entry *he, + hpp_field_fn get_field, const char *fmt, + hpp_snprint_fn print_fn, bool fmt_percent) +{ + if (!symbol_conf.cumulate_callchain) { + return snprintf(hpp->buf, hpp->size, "%*s", + fmt_percent ? 8 : 12, "N/A"); + } + + return __hpp__fmt(hpp, he, get_field, fmt, print_fn, fmt_percent); +} + static int field_cmp(u64 field_a, u64 field_b) { if (field_a > field_b) @@ -160,6 +172,24 @@ out: return ret; } +static int __hpp__sort_acc(struct hist_entry *a, struct hist_entry *b, + hpp_field_fn get_field) +{ + s64 ret = 0; + + if (symbol_conf.cumulate_callchain) { + /* + * Put caller above callee when they have equal period. + */ + ret = field_cmp(get_field(a), get_field(b)); + if (ret) + return ret; + + ret = b->callchain->max_depth - a->callchain->max_depth; + } + return ret; +} + #define __HPP_HEADER_FN(_type, _str, _min_width, _unit_width) \ static int hpp__header_##_type(struct perf_hpp_fmt *fmt __maybe_unused, \ struct perf_hpp *hpp, \ @@ -242,6 +272,34 @@ static int64_t hpp__sort_##_type(struct hist_entry *a, struct hist_entry *b) \ return __hpp__sort(a, b, he_get_##_field); \ } +#define __HPP_COLOR_ACC_PERCENT_FN(_type, _field) \ +static u64 he_get_acc_##_field(struct hist_entry *he) \ +{ \ + return he->stat_acc->_field; \ +} \ + \ +static int hpp__color_##_type(struct perf_hpp_fmt *fmt __maybe_unused, \ + struct perf_hpp *hpp, struct hist_entry *he) \ +{ \ + return __hpp__fmt_acc(hpp, he, he_get_acc_##_field, " %6.2f%%", \ + hpp_color_scnprintf, true); \ +} + +#define __HPP_ENTRY_ACC_PERCENT_FN(_type, _field) \ +static int hpp__entry_##_type(struct perf_hpp_fmt *_fmt __maybe_unused, \ + struct perf_hpp *hpp, struct hist_entry *he) \ +{ \ + const char *fmt = symbol_conf.field_sep ? " %.2f" : " %6.2f%%"; \ + return __hpp__fmt_acc(hpp, he, he_get_acc_##_field, fmt, \ + hpp_entry_scnprintf, true); \ +} + +#define __HPP_SORT_ACC_FN(_type, _field) \ +static int64_t hpp__sort_##_type(struct hist_entry *a, struct hist_entry *b) \ +{ \ + return __hpp__sort_acc(a, b, he_get_acc_##_field); \ +} + #define __HPP_ENTRY_RAW_FN(_type, _field) \ static u64 he_get_raw_##_field(struct hist_entry *he) \ { \ @@ -270,18 +328,27 @@ __HPP_COLOR_PERCENT_FN(_type, _field) \ __HPP_ENTRY_PERCENT_FN(_type, _field) \ __HPP_SORT_FN(_type, _field) +#define HPP_PERCENT_ACC_FNS(_type, _str, _field, _min_width, _unit_width)\ +__HPP_HEADER_FN(_type, _str, _min_width, _unit_width) \ +__HPP_WIDTH_FN(_type, _min_width, _unit_width) \ +__HPP_COLOR_ACC_PERCENT_FN(_type, _field) \ +__HPP_ENTRY_ACC_PERCENT_FN(_type, _field) \ +__HPP_SORT_ACC_FN(_type, _field) + #define HPP_RAW_FNS(_type, _str, _field, _min_width, _unit_width) \ __HPP_HEADER_FN(_type, _str, _min_width, _unit_width) \ __HPP_WIDTH_FN(_type, _min_width, _unit_width) \ __HPP_ENTRY_RAW_FN(_type, _field) \ __HPP_SORT_RAW_FN(_type, _field) +__HPP_HEADER_FN(overhead_self, "Self", 8, 8) HPP_PERCENT_FNS(overhead, "Overhead", period, 8, 8) HPP_PERCENT_FNS(overhead_sys, "sys", period_sys, 8, 8) HPP_PERCENT_FNS(overhead_us, "usr", period_us, 8, 8) HPP_PERCENT_FNS(overhead_guest_sys, "guest sys", period_guest_sys, 9, 8) HPP_PERCENT_FNS(overhead_guest_us, "guest usr", period_guest_us, 9, 8) +HPP_PERCENT_ACC_FNS(overhead_acc, "Children", period, 8, 8) HPP_RAW_FNS(samples, "Samples", nr_events, 12, 12) HPP_RAW_FNS(period, "Period", period, 12, 12) @@ -303,6 +370,17 @@ static int64_t hpp__nop_cmp(struct hist_entry *a __maybe_unused, .sort = hpp__sort_ ## _name, \ } +#define HPP__COLOR_ACC_PRINT_FNS(_name) \ + { \ + .header = hpp__header_ ## _name, \ + .width = hpp__width_ ## _name, \ + .color = hpp__color_ ## _name, \ + .entry = hpp__entry_ ## _name, \ + .cmp = hpp__nop_cmp, \ + .collapse = hpp__nop_cmp, \ + .sort = hpp__sort_ ## _name, \ + } + #define HPP__PRINT_FNS(_name) \ { \ .header = hpp__header_ ## _name, \ @@ -319,6 +397,7 @@ struct perf_hpp_fmt perf_hpp__format[] = { HPP__COLOR_PRINT_FNS(overhead_us), HPP__COLOR_PRINT_FNS(overhead_guest_sys), HPP__COLOR_PRINT_FNS(overhead_guest_us), + HPP__COLOR_ACC_PRINT_FNS(overhead_acc), HPP__PRINT_FNS(samples), HPP__PRINT_FNS(period) }; @@ -328,16 +407,23 @@ LIST_HEAD(perf_hpp__sort_list); #undef HPP__COLOR_PRINT_FNS +#undef HPP__COLOR_ACC_PRINT_FNS #undef HPP__PRINT_FNS #undef HPP_PERCENT_FNS +#undef HPP_PERCENT_ACC_FNS #undef HPP_RAW_FNS #undef __HPP_HEADER_FN #undef __HPP_WIDTH_FN #undef __HPP_COLOR_PERCENT_FN #undef __HPP_ENTRY_PERCENT_FN +#undef __HPP_COLOR_ACC_PERCENT_FN +#undef __HPP_ENTRY_ACC_PERCENT_FN #undef __HPP_ENTRY_RAW_FN +#undef __HPP_SORT_FN +#undef __HPP_SORT_ACC_FN +#undef __HPP_SORT_RAW_FN void perf_hpp__init(void) @@ -361,6 +447,13 @@ void perf_hpp__init(void) if (field_order) return; + if (symbol_conf.cumulate_callchain) { + perf_hpp__column_enable(PERF_HPP__OVERHEAD_ACC); + + perf_hpp__format[PERF_HPP__OVERHEAD].header = + hpp__header_overhead_self; + } + perf_hpp__column_enable(PERF_HPP__OVERHEAD); if (symbol_conf.show_cpu_utilization) { @@ -383,6 +476,12 @@ void perf_hpp__init(void) list = &perf_hpp__format[PERF_HPP__OVERHEAD].sort_list; if (list_empty(list)) list_add(list, &perf_hpp__sort_list); + + if (symbol_conf.cumulate_callchain) { + list = &perf_hpp__format[PERF_HPP__OVERHEAD_ACC].sort_list; + if (list_empty(list)) + list_add(list, &perf_hpp__sort_list); + } } void perf_hpp__column_register(struct perf_hpp_fmt *format) @@ -390,6 +489,11 @@ void perf_hpp__column_register(struct perf_hpp_fmt *format) list_add_tail(&format->list, &perf_hpp__list); } +void perf_hpp__column_unregister(struct perf_hpp_fmt *format) +{ + list_del(&format->list); +} + void perf_hpp__register_sort_field(struct perf_hpp_fmt *format) { list_add_tail(&format->sort_list, &perf_hpp__sort_list); @@ -401,6 +505,21 @@ void perf_hpp__column_enable(unsigned col) perf_hpp__column_register(&perf_hpp__format[col]); } +void perf_hpp__column_disable(unsigned col) +{ + BUG_ON(col >= PERF_HPP__MAX_INDEX); + perf_hpp__column_unregister(&perf_hpp__format[col]); +} + +void perf_hpp__cancel_cumulate(void) +{ + if (field_order) + return; + + perf_hpp__column_disable(PERF_HPP__OVERHEAD_ACC); + perf_hpp__format[PERF_HPP__OVERHEAD].header = hpp__header_overhead; +} + void perf_hpp__setup_output_field(void) { struct perf_hpp_fmt *fmt; diff --git a/tools/perf/ui/stdio/hist.c b/tools/perf/ui/stdio/hist.c index 9f57991025a9..90122abd3721 100644 --- a/tools/perf/ui/stdio/hist.c +++ b/tools/perf/ui/stdio/hist.c @@ -271,7 +271,9 @@ static size_t hist_entry_callchain__fprintf(struct hist_entry *he, { switch (callchain_param.mode) { case CHAIN_GRAPH_REL: - return callchain__fprintf_graph(fp, &he->sorted_chain, he->stat.period, + return callchain__fprintf_graph(fp, &he->sorted_chain, + symbol_conf.cumulate_callchain ? + he->stat_acc->period : he->stat.period, left_margin); break; case CHAIN_GRAPH_ABS: @@ -461,12 +463,12 @@ print_entries: for (nd = rb_first(&hists->entries); nd; nd = rb_next(nd)) { struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); - float percent = h->stat.period * 100.0 / - hists->stats.total_period; + float percent; if (h->filtered) continue; + percent = hist_entry__get_percent_limit(h); if (percent < min_pcnt) continue; diff --git a/tools/perf/util/callchain.c b/tools/perf/util/callchain.c index 9a42382b3921..48b6d3f50012 100644 --- a/tools/perf/util/callchain.c +++ b/tools/perf/util/callchain.c @@ -616,7 +616,8 @@ int sample__resolve_callchain(struct perf_sample *sample, struct symbol **parent if (sample->callchain == NULL) return 0; - if (symbol_conf.use_callchain || sort__has_parent) { + if (symbol_conf.use_callchain || symbol_conf.cumulate_callchain || + sort__has_parent) { return machine__resolve_callchain(al->machine, evsel, al->thread, sample, parent, al, max_stack); } @@ -629,3 +630,45 @@ int hist_entry__append_callchain(struct hist_entry *he, struct perf_sample *samp return 0; return callchain_append(he->callchain, &callchain_cursor, sample->period); } + +int fill_callchain_info(struct addr_location *al, struct callchain_cursor_node *node, + bool hide_unresolved) +{ + al->map = node->map; + al->sym = node->sym; + if (node->map) + al->addr = node->map->map_ip(node->map, node->ip); + else + al->addr = node->ip; + + if (al->sym == NULL) { + if (hide_unresolved) + return 0; + if (al->map == NULL) + goto out; + } + + if (al->map->groups == &al->machine->kmaps) { + if (machine__is_host(al->machine)) { + al->cpumode = PERF_RECORD_MISC_KERNEL; + al->level = 'k'; + } else { + al->cpumode = PERF_RECORD_MISC_GUEST_KERNEL; + al->level = 'g'; + } + } else { + if (machine__is_host(al->machine)) { + al->cpumode = PERF_RECORD_MISC_USER; + al->level = '.'; + } else if (perf_guest) { + al->cpumode = PERF_RECORD_MISC_GUEST_USER; + al->level = 'u'; + } else { + al->cpumode = PERF_RECORD_MISC_HYPERVISOR; + al->level = 'H'; + } + } + +out: + return 1; +} diff --git a/tools/perf/util/callchain.h b/tools/perf/util/callchain.h index bde2b0cc24cf..8f84423a75da 100644 --- a/tools/perf/util/callchain.h +++ b/tools/perf/util/callchain.h @@ -162,7 +162,18 @@ int sample__resolve_callchain(struct perf_sample *sample, struct symbol **parent struct perf_evsel *evsel, struct addr_location *al, int max_stack); int hist_entry__append_callchain(struct hist_entry *he, struct perf_sample *sample); +int fill_callchain_info(struct addr_location *al, struct callchain_cursor_node *node, + bool hide_unresolved); extern const char record_callchain_help[]; int parse_callchain_report_opt(const char *arg); + +static inline void callchain_cursor_snapshot(struct callchain_cursor *dest, + struct callchain_cursor *src) +{ + *dest = *src; + + dest->first = src->curr; + dest->nr -= src->pos; +} #endif /* __PERF_CALLCHAIN_H */ diff --git a/tools/perf/util/dso.c b/tools/perf/util/dso.c index 64453d63b971..819f10414f08 100644 --- a/tools/perf/util/dso.c +++ b/tools/perf/util/dso.c @@ -1,3 +1,6 @@ +#include <asm/bug.h> +#include <sys/time.h> +#include <sys/resource.h> #include "symbol.h" #include "dso.h" #include "machine.h" @@ -136,7 +139,48 @@ int dso__read_binary_type_filename(const struct dso *dso, return ret; } -static int open_dso(struct dso *dso, struct machine *machine) +/* + * Global list of open DSOs and the counter. + */ +static LIST_HEAD(dso__data_open); +static long dso__data_open_cnt; + +static void dso__list_add(struct dso *dso) +{ + list_add_tail(&dso->data.open_entry, &dso__data_open); + dso__data_open_cnt++; +} + +static void dso__list_del(struct dso *dso) +{ + list_del(&dso->data.open_entry); + WARN_ONCE(dso__data_open_cnt <= 0, + "DSO data fd counter out of bounds."); + dso__data_open_cnt--; +} + +static void close_first_dso(void); + +static int do_open(char *name) +{ + int fd; + + do { + fd = open(name, O_RDONLY); + if (fd >= 0) + return fd; + + pr_debug("dso open failed, mmap: %s\n", strerror(errno)); + if (!dso__data_open_cnt || errno != EMFILE) + break; + + close_first_dso(); + } while (1); + + return -1; +} + +static int __open_dso(struct dso *dso, struct machine *machine) { int fd; char *root_dir = (char *)""; @@ -154,11 +198,130 @@ static int open_dso(struct dso *dso, struct machine *machine) return -EINVAL; } - fd = open(name, O_RDONLY); + fd = do_open(name); free(name); return fd; } +static void check_data_close(void); + +/** + * dso_close - Open DSO data file + * @dso: dso object + * + * Open @dso's data file descriptor and updates + * list/count of open DSO objects. + */ +static int open_dso(struct dso *dso, struct machine *machine) +{ + int fd = __open_dso(dso, machine); + + if (fd > 0) { + dso__list_add(dso); + /* + * Check if we crossed the allowed number + * of opened DSOs and close one if needed. + */ + check_data_close(); + } + + return fd; +} + +static void close_data_fd(struct dso *dso) +{ + if (dso->data.fd >= 0) { + close(dso->data.fd); + dso->data.fd = -1; + dso->data.file_size = 0; + dso__list_del(dso); + } +} + +/** + * dso_close - Close DSO data file + * @dso: dso object + * + * Close @dso's data file descriptor and updates + * list/count of open DSO objects. + */ +static void close_dso(struct dso *dso) +{ + close_data_fd(dso); +} + +static void close_first_dso(void) +{ + struct dso *dso; + + dso = list_first_entry(&dso__data_open, struct dso, data.open_entry); + close_dso(dso); +} + +static rlim_t get_fd_limit(void) +{ + struct rlimit l; + rlim_t limit = 0; + + /* Allow half of the current open fd limit. */ + if (getrlimit(RLIMIT_NOFILE, &l) == 0) { + if (l.rlim_cur == RLIM_INFINITY) + limit = l.rlim_cur; + else + limit = l.rlim_cur / 2; + } else { + pr_err("failed to get fd limit\n"); + limit = 1; + } + + return limit; +} + +static bool may_cache_fd(void) +{ + static rlim_t limit; + + if (!limit) + limit = get_fd_limit(); + + if (limit == RLIM_INFINITY) + return true; + + return limit > (rlim_t) dso__data_open_cnt; +} + +/* + * Check and close LRU dso if we crossed allowed limit + * for opened dso file descriptors. The limit is half + * of the RLIMIT_NOFILE files opened. +*/ +static void check_data_close(void) +{ + bool cache_fd = may_cache_fd(); + + if (!cache_fd) + close_first_dso(); +} + +/** + * dso__data_close - Close DSO data file + * @dso: dso object + * + * External interface to close @dso's data file descriptor. + */ +void dso__data_close(struct dso *dso) +{ + close_dso(dso); +} + +/** + * dso__data_fd - Get dso's data file descriptor + * @dso: dso object + * @machine: machine object + * + * External interface to find dso's file, open it and + * returns file descriptor. + */ int dso__data_fd(struct dso *dso, struct machine *machine) { enum dso_binary_type binary_type_data[] = { @@ -168,8 +331,13 @@ int dso__data_fd(struct dso *dso, struct machine *machine) }; int i = 0; - if (dso->binary_type != DSO_BINARY_TYPE__NOT_FOUND) - return open_dso(dso, machine); + if (dso->data.fd >= 0) + return dso->data.fd; + + if (dso->binary_type != DSO_BINARY_TYPE__NOT_FOUND) { + dso->data.fd = open_dso(dso, machine); + return dso->data.fd; + } do { int fd; @@ -178,7 +346,7 @@ int dso__data_fd(struct dso *dso, struct machine *machine) fd = open_dso(dso, machine); if (fd >= 0) - return fd; + return dso->data.fd = fd; } while (dso->binary_type != DSO_BINARY_TYPE__NOT_FOUND); @@ -260,16 +428,10 @@ dso_cache__memcpy(struct dso_cache *cache, u64 offset, } static ssize_t -dso_cache__read(struct dso *dso, struct machine *machine, - u64 offset, u8 *data, ssize_t size) +dso_cache__read(struct dso *dso, u64 offset, u8 *data, ssize_t size) { struct dso_cache *cache; ssize_t ret; - int fd; - - fd = dso__data_fd(dso, machine); - if (fd < 0) - return -1; do { u64 cache_offset; @@ -283,16 +445,16 @@ dso_cache__read(struct dso *dso, struct machine *machine, cache_offset = offset & DSO__DATA_CACHE_MASK; ret = -EINVAL; - if (-1 == lseek(fd, cache_offset, SEEK_SET)) + if (-1 == lseek(dso->data.fd, cache_offset, SEEK_SET)) break; - ret = read(fd, cache->data, DSO__DATA_CACHE_SIZE); + ret = read(dso->data.fd, cache->data, DSO__DATA_CACHE_SIZE); if (ret <= 0) break; cache->offset = cache_offset; cache->size = ret; - dso_cache__insert(&dso->cache, cache); + dso_cache__insert(&dso->data.cache, cache); ret = dso_cache__memcpy(cache, offset, data, size); @@ -301,24 +463,27 @@ dso_cache__read(struct dso *dso, struct machine *machine, if (ret <= 0) free(cache); - close(fd); return ret; } -static ssize_t dso_cache_read(struct dso *dso, struct machine *machine, - u64 offset, u8 *data, ssize_t size) +static ssize_t dso_cache_read(struct dso *dso, u64 offset, + u8 *data, ssize_t size) { struct dso_cache *cache; - cache = dso_cache__find(&dso->cache, offset); + cache = dso_cache__find(&dso->data.cache, offset); if (cache) return dso_cache__memcpy(cache, offset, data, size); else - return dso_cache__read(dso, machine, offset, data, size); + return dso_cache__read(dso, offset, data, size); } -ssize_t dso__data_read_offset(struct dso *dso, struct machine *machine, - u64 offset, u8 *data, ssize_t size) +/* + * Reads and caches dso data DSO__DATA_CACHE_SIZE size chunks + * in the rb_tree. Any read to already cached data is served + * by cached data. + */ +static ssize_t cached_read(struct dso *dso, u64 offset, u8 *data, ssize_t size) { ssize_t r = 0; u8 *p = data; @@ -326,7 +491,7 @@ ssize_t dso__data_read_offset(struct dso *dso, struct machine *machine, do { ssize_t ret; - ret = dso_cache_read(dso, machine, offset, p, size); + ret = dso_cache_read(dso, offset, p, size); if (ret < 0) return ret; @@ -346,6 +511,67 @@ ssize_t dso__data_read_offset(struct dso *dso, struct machine *machine, return r; } +static int data_file_size(struct dso *dso) +{ + struct stat st; + + if (!dso->data.file_size) { + if (fstat(dso->data.fd, &st)) { + pr_err("dso mmap failed, fstat: %s\n", strerror(errno)); + return -1; + } + dso->data.file_size = st.st_size; + } + + return 0; +} + +static ssize_t data_read_offset(struct dso *dso, u64 offset, + u8 *data, ssize_t size) +{ + if (data_file_size(dso)) + return -1; + + /* Check the offset sanity. */ + if (offset > dso->data.file_size) + return -1; + + if (offset + size < offset) + return -1; + + return cached_read(dso, offset, data, size); +} + +/** + * dso__data_read_offset - Read data from dso file offset + * @dso: dso object + * @machine: machine object + * @offset: file offset + * @data: buffer to store data + * @size: size of the @data buffer + * + * External interface to read data from dso file offset. Open + * dso data file and use cached_read to get the data. + */ +ssize_t dso__data_read_offset(struct dso *dso, struct machine *machine, + u64 offset, u8 *data, ssize_t size) +{ + if (dso__data_fd(dso, machine) < 0) + return -1; + + return data_read_offset(dso, offset, data, size); +} + +/** + * dso__data_read_addr - Read data from dso address + * @dso: dso object + * @machine: machine object + * @add: virtual memory address + * @data: buffer to store data + * @size: size of the @data buffer + * + * External interface to read data from dso address. + */ ssize_t dso__data_read_addr(struct dso *dso, struct map *map, struct machine *machine, u64 addr, u8 *data, ssize_t size) @@ -473,7 +699,8 @@ struct dso *dso__new(const char *name) dso__set_short_name(dso, dso->name, false); for (i = 0; i < MAP__NR_TYPES; ++i) dso->symbols[i] = dso->symbol_names[i] = RB_ROOT; - dso->cache = RB_ROOT; + dso->data.cache = RB_ROOT; + dso->data.fd = -1; dso->symtab_type = DSO_BINARY_TYPE__NOT_FOUND; dso->binary_type = DSO_BINARY_TYPE__NOT_FOUND; dso->loaded = 0; @@ -485,6 +712,7 @@ struct dso *dso__new(const char *name) dso->kernel = DSO_TYPE_USER; dso->needs_swap = DSO_SWAP__UNSET; INIT_LIST_HEAD(&dso->node); + INIT_LIST_HEAD(&dso->data.open_entry); } return dso; @@ -506,7 +734,8 @@ void dso__delete(struct dso *dso) dso->long_name_allocated = false; } - dso_cache__free(&dso->cache); + dso__data_close(dso); + dso_cache__free(&dso->data.cache); dso__free_a2l(dso); zfree(&dso->symsrc_filename); free(dso); diff --git a/tools/perf/util/dso.h b/tools/perf/util/dso.h index 38efe95a7fdd..ad553ba257bf 100644 --- a/tools/perf/util/dso.h +++ b/tools/perf/util/dso.h @@ -76,7 +76,6 @@ struct dso { struct list_head node; struct rb_root symbols[MAP__NR_TYPES]; struct rb_root symbol_names[MAP__NR_TYPES]; - struct rb_root cache; void *a2l; char *symsrc_filename; unsigned int a2l_fails; @@ -99,6 +98,15 @@ struct dso { const char *long_name; u16 long_name_len; u16 short_name_len; + + /* dso data file */ + struct { + struct rb_root cache; + int fd; + size_t file_size; + struct list_head open_entry; + } data; + char name[0]; }; @@ -141,7 +149,47 @@ char dso__symtab_origin(const struct dso *dso); int dso__read_binary_type_filename(const struct dso *dso, enum dso_binary_type type, char *root_dir, char *filename, size_t size); +/* + * The dso__data_* external interface provides following functions: + * dso__data_fd + * dso__data_close + * dso__data_read_offset + * dso__data_read_addr + * + * Please refer to the dso.c object code for each function and + * arguments documentation. Following text tries to explain the + * dso file descriptor caching. + * + * The dso__data* interface allows caching of opened file descriptors + * to speed up the dso data accesses. The idea is to leave the file + * descriptor opened ideally for the whole life of the dso object. + * + * The current usage of the dso__data_* interface is as follows: + * + * Get DSO's fd: + * int fd = dso__data_fd(dso, machine); + * USE 'fd' SOMEHOW + * + * Read DSO's data: + * n = dso__data_read_offset(dso_0, &machine, 0, buf, BUFSIZE); + * n = dso__data_read_addr(dso_0, &machine, 0, buf, BUFSIZE); + * + * Eventually close DSO's fd: + * dso__data_close(dso); + * + * It is not necessary to close the DSO object data file. Each time new + * DSO data file is opened, the limit (RLIMIT_NOFILE/2) is checked. Once + * it is crossed, the oldest opened DSO object is closed. + * + * The dso__delete function calls close_dso function to ensure the + * data file descriptor gets closed/unmapped before the dso object + * is freed. + * + * TODO +*/ int dso__data_fd(struct dso *dso, struct machine *machine); +void dso__data_close(struct dso *dso); + ssize_t dso__data_read_offset(struct dso *dso, struct machine *machine, u64 offset, u8 *data, ssize_t size); ssize_t dso__data_read_addr(struct dso *dso, struct map *map, diff --git a/tools/perf/util/dwarf-aux.c b/tools/perf/util/dwarf-aux.c index 7defd77105d0..cc66c4049e09 100644 --- a/tools/perf/util/dwarf-aux.c +++ b/tools/perf/util/dwarf-aux.c @@ -747,14 +747,17 @@ struct __find_variable_param { static int __die_find_variable_cb(Dwarf_Die *die_mem, void *data) { struct __find_variable_param *fvp = data; + Dwarf_Attribute attr; int tag; tag = dwarf_tag(die_mem); if ((tag == DW_TAG_formal_parameter || tag == DW_TAG_variable) && - die_compare_name(die_mem, fvp->name)) + die_compare_name(die_mem, fvp->name) && + /* Does the DIE have location information or external instance? */ + (dwarf_attr(die_mem, DW_AT_external, &attr) || + dwarf_attr(die_mem, DW_AT_location, &attr))) return DIE_FIND_CB_END; - if (dwarf_haspc(die_mem, fvp->addr)) return DIE_FIND_CB_CONTINUE; else diff --git a/tools/perf/util/event.c b/tools/perf/util/event.c index 65795b835b39..d0281bdfa582 100644 --- a/tools/perf/util/event.c +++ b/tools/perf/util/event.c @@ -1,4 +1,5 @@ #include <linux/types.h> +#include <sys/mman.h> #include "event.h" #include "debug.h" #include "hist.h" @@ -178,13 +179,14 @@ int perf_event__synthesize_mmap_events(struct perf_tool *tool, return -1; } - event->header.type = PERF_RECORD_MMAP; + event->header.type = PERF_RECORD_MMAP2; while (1) { char bf[BUFSIZ]; char prot[5]; char execname[PATH_MAX]; char anonstr[] = "//anon"; + unsigned int ino; size_t size; ssize_t n; @@ -195,15 +197,20 @@ int perf_event__synthesize_mmap_events(struct perf_tool *tool, strcpy(execname, ""); /* 00400000-0040c000 r-xp 00000000 fd:01 41038 /bin/cat */ - n = sscanf(bf, "%"PRIx64"-%"PRIx64" %s %"PRIx64" %*x:%*x %*u %s\n", - &event->mmap.start, &event->mmap.len, prot, - &event->mmap.pgoff, - execname); + n = sscanf(bf, "%"PRIx64"-%"PRIx64" %s %"PRIx64" %x:%x %u %s\n", + &event->mmap2.start, &event->mmap2.len, prot, + &event->mmap2.pgoff, &event->mmap2.maj, + &event->mmap2.min, + &ino, execname); + /* * Anon maps don't have the execname. */ - if (n < 4) + if (n < 7) continue; + + event->mmap2.ino = (u64)ino; + /* * Just like the kernel, see __perf_event_mmap in kernel/perf_event.c */ @@ -212,6 +219,21 @@ int perf_event__synthesize_mmap_events(struct perf_tool *tool, else event->header.misc = PERF_RECORD_MISC_GUEST_USER; + /* map protection and flags bits */ + event->mmap2.prot = 0; + event->mmap2.flags = 0; + if (prot[0] == 'r') + event->mmap2.prot |= PROT_READ; + if (prot[1] == 'w') + event->mmap2.prot |= PROT_WRITE; + if (prot[2] == 'x') + event->mmap2.prot |= PROT_EXEC; + + if (prot[3] == 's') + event->mmap2.flags |= MAP_SHARED; + else + event->mmap2.flags |= MAP_PRIVATE; + if (prot[2] != 'x') { if (!mmap_data || prot[0] != 'r') continue; @@ -223,15 +245,15 @@ int perf_event__synthesize_mmap_events(struct perf_tool *tool, strcpy(execname, anonstr); size = strlen(execname) + 1; - memcpy(event->mmap.filename, execname, size); + memcpy(event->mmap2.filename, execname, size); size = PERF_ALIGN(size, sizeof(u64)); - event->mmap.len -= event->mmap.start; - event->mmap.header.size = (sizeof(event->mmap) - - (sizeof(event->mmap.filename) - size)); - memset(event->mmap.filename + size, 0, machine->id_hdr_size); - event->mmap.header.size += machine->id_hdr_size; - event->mmap.pid = tgid; - event->mmap.tid = pid; + event->mmap2.len -= event->mmap.start; + event->mmap2.header.size = (sizeof(event->mmap2) - + (sizeof(event->mmap2.filename) - size)); + memset(event->mmap2.filename + size, 0, machine->id_hdr_size); + event->mmap2.header.size += machine->id_hdr_size; + event->mmap2.pid = tgid; + event->mmap2.tid = pid; if (process(tool, event, &synth_sample, machine) != 0) { rc = -1; @@ -612,12 +634,15 @@ size_t perf_event__fprintf_mmap(union perf_event *event, FILE *fp) size_t perf_event__fprintf_mmap2(union perf_event *event, FILE *fp) { return fprintf(fp, " %d/%d: [%#" PRIx64 "(%#" PRIx64 ") @ %#" PRIx64 - " %02x:%02x %"PRIu64" %"PRIu64"]: %c %s\n", + " %02x:%02x %"PRIu64" %"PRIu64"]: %c%c%c%c %s\n", event->mmap2.pid, event->mmap2.tid, event->mmap2.start, event->mmap2.len, event->mmap2.pgoff, event->mmap2.maj, event->mmap2.min, event->mmap2.ino, event->mmap2.ino_generation, - (event->header.misc & PERF_RECORD_MISC_MMAP_DATA) ? 'r' : 'x', + (event->mmap2.prot & PROT_READ) ? 'r' : '-', + (event->mmap2.prot & PROT_WRITE) ? 'w' : '-', + (event->mmap2.prot & PROT_EXEC) ? 'x' : '-', + (event->mmap2.flags & MAP_SHARED) ? 's' : 'p', event->mmap2.filename); } diff --git a/tools/perf/util/event.h b/tools/perf/util/event.h index d970232cb270..e5dd40addb30 100644 --- a/tools/perf/util/event.h +++ b/tools/perf/util/event.h @@ -7,6 +7,7 @@ #include "../perf.h" #include "map.h" #include "build-id.h" +#include "perf_regs.h" struct mmap_event { struct perf_event_header header; @@ -27,6 +28,8 @@ struct mmap2_event { u32 min; u64 ino; u64 ino_generation; + u32 prot; + u32 flags; char filename[PATH_MAX]; }; @@ -87,6 +90,10 @@ struct regs_dump { u64 abi; u64 mask; u64 *regs; + + /* Cached values/mask filled by first register access. */ + u64 cache_regs[PERF_REGS_MAX]; + u64 cache_mask; }; struct stack_dump { diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c index 5c28d82b76c4..8606175fe1e8 100644 --- a/tools/perf/util/evsel.c +++ b/tools/perf/util/evsel.c @@ -589,10 +589,10 @@ void perf_evsel__config(struct perf_evsel *evsel, struct record_opts *opts) } /* - * We default some events to a 1 default interval. But keep + * We default some events to have a default interval. But keep * it a weak assumption overridable by the user. */ - if (!attr->sample_period || (opts->user_freq != UINT_MAX && + if (!attr->sample_period || (opts->user_freq != UINT_MAX || opts->user_interval != ULLONG_MAX)) { if (opts->freq) { perf_evsel__set_sample_bit(evsel, PERIOD); @@ -659,6 +659,7 @@ void perf_evsel__config(struct perf_evsel *evsel, struct record_opts *opts) perf_evsel__set_sample_bit(evsel, WEIGHT); attr->mmap = track; + attr->mmap2 = track && !perf_missing_features.mmap2; attr->comm = track; if (opts->sample_transaction) diff --git a/tools/perf/util/hist.c b/tools/perf/util/hist.c index b262b44b7a65..30df6187ee02 100644 --- a/tools/perf/util/hist.c +++ b/tools/perf/util/hist.c @@ -4,6 +4,7 @@ #include "session.h" #include "sort.h" #include "evsel.h" +#include "annotate.h" #include <math.h> static bool hists__filter_entry_by_dso(struct hists *hists, @@ -127,6 +128,8 @@ void hists__calc_col_len(struct hists *hists, struct hist_entry *h) + unresolved_col_width + 2; hists__new_col_len(hists, HISTC_MEM_DADDR_SYMBOL, symlen); + hists__new_col_len(hists, HISTC_MEM_DCACHELINE, + symlen + 1); } else { symlen = unresolved_col_width + 4 + 2; hists__new_col_len(hists, HISTC_MEM_DADDR_SYMBOL, @@ -231,6 +234,8 @@ static bool hists__decay_entry(struct hists *hists, struct hist_entry *he) return true; he_stat__decay(&he->stat); + if (symbol_conf.cumulate_callchain) + he_stat__decay(he->stat_acc); diff = prev_period - he->stat.period; @@ -276,14 +281,31 @@ void hists__decay_entries(struct hists *hists, bool zap_user, bool zap_kernel) * histogram, sorted on item, collects periods */ -static struct hist_entry *hist_entry__new(struct hist_entry *template) +static struct hist_entry *hist_entry__new(struct hist_entry *template, + bool sample_self) { - size_t callchain_size = symbol_conf.use_callchain ? sizeof(struct callchain_root) : 0; - struct hist_entry *he = zalloc(sizeof(*he) + callchain_size); + size_t callchain_size = 0; + struct hist_entry *he; + + if (symbol_conf.use_callchain || symbol_conf.cumulate_callchain) + callchain_size = sizeof(struct callchain_root); + + he = zalloc(sizeof(*he) + callchain_size); if (he != NULL) { *he = *template; + if (symbol_conf.cumulate_callchain) { + he->stat_acc = malloc(sizeof(he->stat)); + if (he->stat_acc == NULL) { + free(he); + return NULL; + } + memcpy(he->stat_acc, &he->stat, sizeof(he->stat)); + if (!sample_self) + memset(&he->stat, 0, sizeof(he->stat)); + } + if (he->ms.map) he->ms.map->referenced = true; @@ -295,6 +317,7 @@ static struct hist_entry *hist_entry__new(struct hist_entry *template) */ he->branch_info = malloc(sizeof(*he->branch_info)); if (he->branch_info == NULL) { + free(he->stat_acc); free(he); return NULL; } @@ -333,7 +356,8 @@ static u8 symbol__parent_filter(const struct symbol *parent) static struct hist_entry *add_hist_entry(struct hists *hists, struct hist_entry *entry, - struct addr_location *al) + struct addr_location *al, + bool sample_self) { struct rb_node **p; struct rb_node *parent = NULL; @@ -357,7 +381,10 @@ static struct hist_entry *add_hist_entry(struct hists *hists, cmp = hist_entry__cmp(he, entry); if (!cmp) { - he_stat__add_period(&he->stat, period, weight); + if (sample_self) + he_stat__add_period(&he->stat, period, weight); + if (symbol_conf.cumulate_callchain) + he_stat__add_period(he->stat_acc, period, weight); /* * This mem info was allocated from sample__resolve_mem @@ -385,14 +412,17 @@ static struct hist_entry *add_hist_entry(struct hists *hists, p = &(*p)->rb_right; } - he = hist_entry__new(entry); + he = hist_entry__new(entry, sample_self); if (!he) return NULL; rb_link_node(&he->rb_node_in, parent, p); rb_insert_color(&he->rb_node_in, hists->entries_in); out: - he_stat__add_cpumode_period(&he->stat, al->cpumode, period); + if (sample_self) + he_stat__add_cpumode_period(&he->stat, al->cpumode, period); + if (symbol_conf.cumulate_callchain) + he_stat__add_cpumode_period(he->stat_acc, al->cpumode, period); return he; } @@ -401,7 +431,8 @@ struct hist_entry *__hists__add_entry(struct hists *hists, struct symbol *sym_parent, struct branch_info *bi, struct mem_info *mi, - u64 period, u64 weight, u64 transaction) + u64 period, u64 weight, u64 transaction, + bool sample_self) { struct hist_entry entry = { .thread = al->thread, @@ -410,9 +441,10 @@ struct hist_entry *__hists__add_entry(struct hists *hists, .map = al->map, .sym = al->sym, }, - .cpu = al->cpu, - .ip = al->addr, - .level = al->level, + .cpu = al->cpu, + .cpumode = al->cpumode, + .ip = al->addr, + .level = al->level, .stat = { .nr_events = 1, .period = period, @@ -426,7 +458,429 @@ struct hist_entry *__hists__add_entry(struct hists *hists, .transaction = transaction, }; - return add_hist_entry(hists, &entry, al); + return add_hist_entry(hists, &entry, al, sample_self); +} + +static int +iter_next_nop_entry(struct hist_entry_iter *iter __maybe_unused, + struct addr_location *al __maybe_unused) +{ + return 0; +} + +static int +iter_add_next_nop_entry(struct hist_entry_iter *iter __maybe_unused, + struct addr_location *al __maybe_unused) +{ + return 0; +} + +static int +iter_prepare_mem_entry(struct hist_entry_iter *iter, struct addr_location *al) +{ + struct perf_sample *sample = iter->sample; + struct mem_info *mi; + + mi = sample__resolve_mem(sample, al); + if (mi == NULL) + return -ENOMEM; + + iter->priv = mi; + return 0; +} + +static int +iter_add_single_mem_entry(struct hist_entry_iter *iter, struct addr_location *al) +{ + u64 cost; + struct mem_info *mi = iter->priv; + struct hist_entry *he; + + if (mi == NULL) + return -EINVAL; + + cost = iter->sample->weight; + if (!cost) + cost = 1; + + /* + * must pass period=weight in order to get the correct + * sorting from hists__collapse_resort() which is solely + * based on periods. We want sorting be done on nr_events * weight + * and this is indirectly achieved by passing period=weight here + * and the he_stat__add_period() function. + */ + he = __hists__add_entry(&iter->evsel->hists, al, iter->parent, NULL, mi, + cost, cost, 0, true); + if (!he) + return -ENOMEM; + + iter->he = he; + return 0; +} + +static int +iter_finish_mem_entry(struct hist_entry_iter *iter, + struct addr_location *al __maybe_unused) +{ + struct perf_evsel *evsel = iter->evsel; + struct hist_entry *he = iter->he; + int err = -EINVAL; + + if (he == NULL) + goto out; + + hists__inc_nr_samples(&evsel->hists, he->filtered); + + err = hist_entry__append_callchain(he, iter->sample); + +out: + /* + * We don't need to free iter->priv (mem_info) here since + * the mem info was either already freed in add_hist_entry() or + * passed to a new hist entry by hist_entry__new(). + */ + iter->priv = NULL; + + iter->he = NULL; + return err; +} + +static int +iter_prepare_branch_entry(struct hist_entry_iter *iter, struct addr_location *al) +{ + struct branch_info *bi; + struct perf_sample *sample = iter->sample; + + bi = sample__resolve_bstack(sample, al); + if (!bi) + return -ENOMEM; + + iter->curr = 0; + iter->total = sample->branch_stack->nr; + + iter->priv = bi; + return 0; +} + +static int +iter_add_single_branch_entry(struct hist_entry_iter *iter __maybe_unused, + struct addr_location *al __maybe_unused) +{ + /* to avoid calling callback function */ + iter->he = NULL; + + return 0; +} + +static int +iter_next_branch_entry(struct hist_entry_iter *iter, struct addr_location *al) +{ + struct branch_info *bi = iter->priv; + int i = iter->curr; + + if (bi == NULL) + return 0; + + if (iter->curr >= iter->total) + return 0; + + al->map = bi[i].to.map; + al->sym = bi[i].to.sym; + al->addr = bi[i].to.addr; + return 1; +} + +static int +iter_add_next_branch_entry(struct hist_entry_iter *iter, struct addr_location *al) +{ + struct branch_info *bi; + struct perf_evsel *evsel = iter->evsel; + struct hist_entry *he = NULL; + int i = iter->curr; + int err = 0; + + bi = iter->priv; + + if (iter->hide_unresolved && !(bi[i].from.sym && bi[i].to.sym)) + goto out; + + /* + * The report shows the percentage of total branches captured + * and not events sampled. Thus we use a pseudo period of 1. + */ + he = __hists__add_entry(&evsel->hists, al, iter->parent, &bi[i], NULL, + 1, 1, 0, true); + if (he == NULL) + return -ENOMEM; + + hists__inc_nr_samples(&evsel->hists, he->filtered); + +out: + iter->he = he; + iter->curr++; + return err; +} + +static int +iter_finish_branch_entry(struct hist_entry_iter *iter, + struct addr_location *al __maybe_unused) +{ + zfree(&iter->priv); + iter->he = NULL; + + return iter->curr >= iter->total ? 0 : -1; +} + +static int +iter_prepare_normal_entry(struct hist_entry_iter *iter __maybe_unused, + struct addr_location *al __maybe_unused) +{ + return 0; +} + +static int +iter_add_single_normal_entry(struct hist_entry_iter *iter, struct addr_location *al) +{ + struct perf_evsel *evsel = iter->evsel; + struct perf_sample *sample = iter->sample; + struct hist_entry *he; + + he = __hists__add_entry(&evsel->hists, al, iter->parent, NULL, NULL, + sample->period, sample->weight, + sample->transaction, true); + if (he == NULL) + return -ENOMEM; + + iter->he = he; + return 0; +} + +static int +iter_finish_normal_entry(struct hist_entry_iter *iter, + struct addr_location *al __maybe_unused) +{ + struct hist_entry *he = iter->he; + struct perf_evsel *evsel = iter->evsel; + struct perf_sample *sample = iter->sample; + + if (he == NULL) + return 0; + + iter->he = NULL; + + hists__inc_nr_samples(&evsel->hists, he->filtered); + + return hist_entry__append_callchain(he, sample); +} + +static int +iter_prepare_cumulative_entry(struct hist_entry_iter *iter __maybe_unused, + struct addr_location *al __maybe_unused) +{ + struct hist_entry **he_cache; + + callchain_cursor_commit(&callchain_cursor); + + /* + * This is for detecting cycles or recursions so that they're + * cumulated only one time to prevent entries more than 100% + * overhead. + */ + he_cache = malloc(sizeof(*he_cache) * (PERF_MAX_STACK_DEPTH + 1)); + if (he_cache == NULL) + return -ENOMEM; + + iter->priv = he_cache; + iter->curr = 0; + + return 0; +} + +static int +iter_add_single_cumulative_entry(struct hist_entry_iter *iter, + struct addr_location *al) +{ + struct perf_evsel *evsel = iter->evsel; + struct perf_sample *sample = iter->sample; + struct hist_entry **he_cache = iter->priv; + struct hist_entry *he; + int err = 0; + + he = __hists__add_entry(&evsel->hists, al, iter->parent, NULL, NULL, + sample->period, sample->weight, + sample->transaction, true); + if (he == NULL) + return -ENOMEM; + + iter->he = he; + he_cache[iter->curr++] = he; + + callchain_append(he->callchain, &callchain_cursor, sample->period); + + /* + * We need to re-initialize the cursor since callchain_append() + * advanced the cursor to the end. + */ + callchain_cursor_commit(&callchain_cursor); + + hists__inc_nr_samples(&evsel->hists, he->filtered); + + return err; +} + +static int +iter_next_cumulative_entry(struct hist_entry_iter *iter, + struct addr_location *al) +{ + struct callchain_cursor_node *node; + + node = callchain_cursor_current(&callchain_cursor); + if (node == NULL) + return 0; + + return fill_callchain_info(al, node, iter->hide_unresolved); +} + +static int +iter_add_next_cumulative_entry(struct hist_entry_iter *iter, + struct addr_location *al) +{ + struct perf_evsel *evsel = iter->evsel; + struct perf_sample *sample = iter->sample; + struct hist_entry **he_cache = iter->priv; + struct hist_entry *he; + struct hist_entry he_tmp = { + .cpu = al->cpu, + .thread = al->thread, + .comm = thread__comm(al->thread), + .ip = al->addr, + .ms = { + .map = al->map, + .sym = al->sym, + }, + .parent = iter->parent, + }; + int i; + struct callchain_cursor cursor; + + callchain_cursor_snapshot(&cursor, &callchain_cursor); + + callchain_cursor_advance(&callchain_cursor); + + /* + * Check if there's duplicate entries in the callchain. + * It's possible that it has cycles or recursive calls. + */ + for (i = 0; i < iter->curr; i++) { + if (hist_entry__cmp(he_cache[i], &he_tmp) == 0) { + /* to avoid calling callback function */ + iter->he = NULL; + return 0; + } + } + + he = __hists__add_entry(&evsel->hists, al, iter->parent, NULL, NULL, + sample->period, sample->weight, + sample->transaction, false); + if (he == NULL) + return -ENOMEM; + + iter->he = he; + he_cache[iter->curr++] = he; + + callchain_append(he->callchain, &cursor, sample->period); + return 0; +} + +static int +iter_finish_cumulative_entry(struct hist_entry_iter *iter, + struct addr_location *al __maybe_unused) +{ + zfree(&iter->priv); + iter->he = NULL; + + return 0; +} + +const struct hist_iter_ops hist_iter_mem = { + .prepare_entry = iter_prepare_mem_entry, + .add_single_entry = iter_add_single_mem_entry, + .next_entry = iter_next_nop_entry, + .add_next_entry = iter_add_next_nop_entry, + .finish_entry = iter_finish_mem_entry, +}; + +const struct hist_iter_ops hist_iter_branch = { + .prepare_entry = iter_prepare_branch_entry, + .add_single_entry = iter_add_single_branch_entry, + .next_entry = iter_next_branch_entry, + .add_next_entry = iter_add_next_branch_entry, + .finish_entry = iter_finish_branch_entry, +}; + +const struct hist_iter_ops hist_iter_normal = { + .prepare_entry = iter_prepare_normal_entry, + .add_single_entry = iter_add_single_normal_entry, + .next_entry = iter_next_nop_entry, + .add_next_entry = iter_add_next_nop_entry, + .finish_entry = iter_finish_normal_entry, +}; + +const struct hist_iter_ops hist_iter_cumulative = { + .prepare_entry = iter_prepare_cumulative_entry, + .add_single_entry = iter_add_single_cumulative_entry, + .next_entry = iter_next_cumulative_entry, + .add_next_entry = iter_add_next_cumulative_entry, + .finish_entry = iter_finish_cumulative_entry, +}; + +int hist_entry_iter__add(struct hist_entry_iter *iter, struct addr_location *al, + struct perf_evsel *evsel, struct perf_sample *sample, + int max_stack_depth, void *arg) +{ + int err, err2; + + err = sample__resolve_callchain(sample, &iter->parent, evsel, al, + max_stack_depth); + if (err) + return err; + + iter->evsel = evsel; + iter->sample = sample; + + err = iter->ops->prepare_entry(iter, al); + if (err) + goto out; + + err = iter->ops->add_single_entry(iter, al); + if (err) + goto out; + + if (iter->he && iter->add_entry_cb) { + err = iter->add_entry_cb(iter, al, true, arg); + if (err) + goto out; + } + + while (iter->ops->next_entry(iter, al)) { + err = iter->ops->add_next_entry(iter, al); + if (err) + break; + + if (iter->he && iter->add_entry_cb) { + err = iter->add_entry_cb(iter, al, false, arg); + if (err) + goto out; + } + } + +out: + err2 = iter->ops->finish_entry(iter, al); + if (!err) + err = err2; + + return err; } int64_t @@ -469,6 +923,7 @@ void hist_entry__free(struct hist_entry *he) { zfree(&he->branch_info); zfree(&he->mem_info); + zfree(&he->stat_acc); free_srcline(he->srcline); free(he); } @@ -494,6 +949,8 @@ static bool hists__collapse_insert_entry(struct hists *hists __maybe_unused, if (!cmp) { he_stat__add_stat(&iter->stat, &he->stat); + if (symbol_conf.cumulate_callchain) + he_stat__add_stat(iter->stat_acc, he->stat_acc); if (symbol_conf.use_callchain) { callchain_cursor_reset(&callchain_cursor); @@ -800,6 +1257,13 @@ void hists__inc_nr_events(struct hists *hists, u32 type) events_stats__inc(&hists->stats, type); } +void hists__inc_nr_samples(struct hists *hists, bool filtered) +{ + events_stats__inc(&hists->stats, PERF_RECORD_SAMPLE); + if (!filtered) + hists->stats.nr_non_filtered_samples++; +} + static struct hist_entry *hists__add_dummy_entry(struct hists *hists, struct hist_entry *pair) { @@ -831,7 +1295,7 @@ static struct hist_entry *hists__add_dummy_entry(struct hists *hists, p = &(*p)->rb_right; } - he = hist_entry__new(pair); + he = hist_entry__new(pair, true); if (he) { memset(&he->stat, 0, sizeof(he->stat)); he->hists = hists; diff --git a/tools/perf/util/hist.h b/tools/perf/util/hist.h index a8418d19808d..742f49a85725 100644 --- a/tools/perf/util/hist.h +++ b/tools/perf/util/hist.h @@ -72,6 +72,7 @@ enum hist_column { HISTC_MEM_TLB, HISTC_MEM_LVL, HISTC_MEM_SNOOP, + HISTC_MEM_DCACHELINE, HISTC_TRANSACTION, HISTC_NR_COLS, /* Last entry */ }; @@ -96,12 +97,50 @@ struct hists { u16 col_len[HISTC_NR_COLS]; }; +struct hist_entry_iter; + +struct hist_iter_ops { + int (*prepare_entry)(struct hist_entry_iter *, struct addr_location *); + int (*add_single_entry)(struct hist_entry_iter *, struct addr_location *); + int (*next_entry)(struct hist_entry_iter *, struct addr_location *); + int (*add_next_entry)(struct hist_entry_iter *, struct addr_location *); + int (*finish_entry)(struct hist_entry_iter *, struct addr_location *); +}; + +struct hist_entry_iter { + int total; + int curr; + + bool hide_unresolved; + + struct perf_evsel *evsel; + struct perf_sample *sample; + struct hist_entry *he; + struct symbol *parent; + void *priv; + + const struct hist_iter_ops *ops; + /* user-defined callback function (optional) */ + int (*add_entry_cb)(struct hist_entry_iter *iter, + struct addr_location *al, bool single, void *arg); +}; + +extern const struct hist_iter_ops hist_iter_normal; +extern const struct hist_iter_ops hist_iter_branch; +extern const struct hist_iter_ops hist_iter_mem; +extern const struct hist_iter_ops hist_iter_cumulative; + struct hist_entry *__hists__add_entry(struct hists *hists, struct addr_location *al, struct symbol *parent, struct branch_info *bi, struct mem_info *mi, u64 period, - u64 weight, u64 transaction); + u64 weight, u64 transaction, + bool sample_self); +int hist_entry_iter__add(struct hist_entry_iter *iter, struct addr_location *al, + struct perf_evsel *evsel, struct perf_sample *sample, + int max_stack_depth, void *arg); + int64_t hist_entry__cmp(struct hist_entry *left, struct hist_entry *right); int64_t hist_entry__collapse(struct hist_entry *left, struct hist_entry *right); int hist_entry__transaction_len(void); @@ -119,6 +158,7 @@ u64 hists__total_period(struct hists *hists); void hists__reset_stats(struct hists *hists); void hists__inc_stats(struct hists *hists, struct hist_entry *h); void hists__inc_nr_events(struct hists *hists, u32 type); +void hists__inc_nr_samples(struct hists *hists, bool filtered); void events_stats__inc(struct events_stats *stats, u32 type); size_t events_stats__fprintf(struct events_stats *stats, FILE *fp); @@ -166,6 +206,7 @@ struct perf_hpp_fmt { struct list_head list; struct list_head sort_list; + bool elide; }; extern struct list_head perf_hpp__list; @@ -192,6 +233,7 @@ enum { PERF_HPP__OVERHEAD_US, PERF_HPP__OVERHEAD_GUEST_SYS, PERF_HPP__OVERHEAD_GUEST_US, + PERF_HPP__OVERHEAD_ACC, PERF_HPP__SAMPLES, PERF_HPP__PERIOD, @@ -200,7 +242,11 @@ enum { void perf_hpp__init(void); void perf_hpp__column_register(struct perf_hpp_fmt *format); +void perf_hpp__column_unregister(struct perf_hpp_fmt *format); void perf_hpp__column_enable(unsigned col); +void perf_hpp__column_disable(unsigned col); +void perf_hpp__cancel_cumulate(void); + void perf_hpp__register_sort_field(struct perf_hpp_fmt *format); void perf_hpp__setup_output_field(void); void perf_hpp__reset_output_field(void); @@ -208,7 +254,12 @@ void perf_hpp__append_sort_keys(void); bool perf_hpp__is_sort_entry(struct perf_hpp_fmt *format); bool perf_hpp__same_sort_entry(struct perf_hpp_fmt *a, struct perf_hpp_fmt *b); -bool perf_hpp__should_skip(struct perf_hpp_fmt *format); + +static inline bool perf_hpp__should_skip(struct perf_hpp_fmt *format) +{ + return format->elide; +} + void perf_hpp__reset_width(struct perf_hpp_fmt *fmt, struct hists *hists); typedef u64 (*hpp_field_fn)(struct hist_entry *he); @@ -218,6 +269,9 @@ typedef int (*hpp_snprint_fn)(struct perf_hpp *hpp, const char *fmt, ...); int __hpp__fmt(struct perf_hpp *hpp, struct hist_entry *he, hpp_field_fn get_field, const char *fmt, hpp_snprint_fn print_fn, bool fmt_percent); +int __hpp__fmt_acc(struct perf_hpp *hpp, struct hist_entry *he, + hpp_field_fn get_field, const char *fmt, + hpp_snprint_fn print_fn, bool fmt_percent); static inline void advance_hpp(struct perf_hpp *hpp, int inc) { diff --git a/tools/perf/util/machine.c b/tools/perf/util/machine.c index 7409ac8de51c..0e5fea95d596 100644 --- a/tools/perf/util/machine.c +++ b/tools/perf/util/machine.c @@ -1060,6 +1060,8 @@ int machine__process_mmap2_event(struct machine *machine, event->mmap2.pid, event->mmap2.maj, event->mmap2.min, event->mmap2.ino, event->mmap2.ino_generation, + event->mmap2.prot, + event->mmap2.flags, event->mmap2.filename, type); if (map == NULL) @@ -1105,7 +1107,7 @@ int machine__process_mmap_event(struct machine *machine, union perf_event *event map = map__new(&machine->user_dsos, event->mmap.start, event->mmap.len, event->mmap.pgoff, - event->mmap.pid, 0, 0, 0, 0, + event->mmap.pid, 0, 0, 0, 0, 0, 0, event->mmap.filename, type); diff --git a/tools/perf/util/map.c b/tools/perf/util/map.c index 8ccbb32eda25..25c571f4cba6 100644 --- a/tools/perf/util/map.c +++ b/tools/perf/util/map.c @@ -138,7 +138,7 @@ void map__init(struct map *map, enum map_type type, struct map *map__new(struct list_head *dsos__list, u64 start, u64 len, u64 pgoff, u32 pid, u32 d_maj, u32 d_min, u64 ino, - u64 ino_gen, char *filename, + u64 ino_gen, u32 prot, u32 flags, char *filename, enum map_type type) { struct map *map = malloc(sizeof(*map)); @@ -157,6 +157,8 @@ struct map *map__new(struct list_head *dsos__list, u64 start, u64 len, map->min = d_min; map->ino = ino; map->ino_generation = ino_gen; + map->prot = prot; + map->flags = flags; if ((anon || no_dso) && type == MAP__FUNCTION) { snprintf(newfilename, sizeof(newfilename), "/tmp/perf-%d.map", pid); diff --git a/tools/perf/util/map.h b/tools/perf/util/map.h index ae2d45110588..7758c72522ef 100644 --- a/tools/perf/util/map.h +++ b/tools/perf/util/map.h @@ -35,6 +35,8 @@ struct map { bool referenced; bool erange_warned; u32 priv; + u32 prot; + u32 flags; u64 pgoff; u64 reloc; u32 maj, min; /* only valid for MMAP2 record */ @@ -118,7 +120,7 @@ void map__init(struct map *map, enum map_type type, u64 start, u64 end, u64 pgoff, struct dso *dso); struct map *map__new(struct list_head *dsos__list, u64 start, u64 len, u64 pgoff, u32 pid, u32 d_maj, u32 d_min, u64 ino, - u64 ino_gen, + u64 ino_gen, u32 prot, u32 flags, char *filename, enum map_type type); struct map *map__new2(u64 start, struct dso *dso, enum map_type type); void map__delete(struct map *map); diff --git a/tools/perf/util/perf_regs.c b/tools/perf/util/perf_regs.c index a3539ef30b15..43168fb0d9a2 100644 --- a/tools/perf/util/perf_regs.c +++ b/tools/perf/util/perf_regs.c @@ -1,11 +1,15 @@ #include <errno.h> #include "perf_regs.h" +#include "event.h" int perf_reg_value(u64 *valp, struct regs_dump *regs, int id) { int i, idx = 0; u64 mask = regs->mask; + if (regs->cache_mask & (1 << id)) + goto out; + if (!(mask & (1 << id))) return -EINVAL; @@ -14,6 +18,10 @@ int perf_reg_value(u64 *valp, struct regs_dump *regs, int id) idx++; } - *valp = regs->regs[idx]; + regs->cache_mask |= (1 << id); + regs->cache_regs[id] = regs->regs[idx]; + +out: + *valp = regs->cache_regs[id]; return 0; } diff --git a/tools/perf/util/perf_regs.h b/tools/perf/util/perf_regs.h index 79c78f74e0cf..980dbf76bc98 100644 --- a/tools/perf/util/perf_regs.h +++ b/tools/perf/util/perf_regs.h @@ -2,7 +2,8 @@ #define __PERF_REGS_H #include <linux/types.h> -#include "event.h" + +struct regs_dump; #ifdef HAVE_PERF_REGS_SUPPORT #include <perf_regs.h> @@ -11,6 +12,7 @@ int perf_reg_value(u64 *valp, struct regs_dump *regs, int id); #else #define PERF_REGS_MASK 0 +#define PERF_REGS_MAX 0 static inline const char *perf_reg_name(int id __maybe_unused) { diff --git a/tools/perf/util/probe-event.c b/tools/perf/util/probe-event.c index 0d1542f33d87..9a0a1839a377 100644 --- a/tools/perf/util/probe-event.c +++ b/tools/perf/util/probe-event.c @@ -628,11 +628,11 @@ static int __show_line_range(struct line_range *lr, const char *module) ret = debuginfo__find_line_range(dinfo, lr); debuginfo__delete(dinfo); - if (ret == 0) { + if (ret == 0 || ret == -ENOENT) { pr_warning("Specified source line is not found.\n"); return -ENOENT; } else if (ret < 0) { - pr_warning("Debuginfo analysis failed. (%d)\n", ret); + pr_warning("Debuginfo analysis failed.\n"); return ret; } @@ -641,7 +641,7 @@ static int __show_line_range(struct line_range *lr, const char *module) ret = get_real_path(tmp, lr->comp_dir, &lr->path); free(tmp); /* Free old path */ if (ret < 0) { - pr_warning("Failed to find source file. (%d)\n", ret); + pr_warning("Failed to find source file path.\n"); return ret; } @@ -721,9 +721,14 @@ static int show_available_vars_at(struct debuginfo *dinfo, ret = debuginfo__find_available_vars_at(dinfo, pev, &vls, max_vls, externs); if (ret <= 0) { - pr_err("Failed to find variables at %s (%d)\n", buf, ret); + if (ret == 0 || ret == -ENOENT) { + pr_err("Failed to find the address of %s\n", buf); + ret = -ENOENT; + } else + pr_warning("Debuginfo analysis failed.\n"); goto end; } + /* Some variables are found */ fprintf(stdout, "Available variables at %s\n", buf); for (i = 0; i < ret; i++) { diff --git a/tools/perf/util/probe-finder.c b/tools/perf/util/probe-finder.c index 562762117639..98e304766416 100644 --- a/tools/perf/util/probe-finder.c +++ b/tools/perf/util/probe-finder.c @@ -511,12 +511,12 @@ static int convert_variable(Dwarf_Die *vr_die, struct probe_finder *pf) ret = convert_variable_location(vr_die, pf->addr, pf->fb_ops, &pf->sp_die, pf->tvar); - if (ret == -ENOENT) + if (ret == -ENOENT || ret == -EINVAL) pr_err("Failed to find the location of %s at this address.\n" " Perhaps, it has been optimized out.\n", pf->pvar->var); else if (ret == -ENOTSUP) pr_err("Sorry, we don't support this variable location yet.\n"); - else if (pf->pvar->field) { + else if (ret == 0 && pf->pvar->field) { ret = convert_variable_fields(vr_die, pf->pvar->var, pf->pvar->field, &pf->tvar->ref, &die_mem); @@ -573,14 +573,13 @@ static int find_variable(Dwarf_Die *sc_die, struct probe_finder *pf) if (!die_find_variable_at(sc_die, pf->pvar->var, pf->addr, &vr_die)) { /* Search again in global variables */ if (!die_find_variable_at(&pf->cu_die, pf->pvar->var, 0, &vr_die)) + pr_warning("Failed to find '%s' in this function.\n", + pf->pvar->var); ret = -ENOENT; } if (ret >= 0) ret = convert_variable(&vr_die, pf); - if (ret < 0) - pr_warning("Failed to find '%s' in this function.\n", - pf->pvar->var); return ret; } @@ -1281,7 +1280,11 @@ out: return ret; } -/* Find available variables at given probe point */ +/* + * Find available variables at given probe point + * Return the number of found probe points. Return 0 if there is no + * matched probe point. Return <0 if an error occurs. + */ int debuginfo__find_available_vars_at(struct debuginfo *dbg, struct perf_probe_event *pev, struct variable_list **vls, diff --git a/tools/perf/util/scripting-engines/trace-event-perl.c b/tools/perf/util/scripting-engines/trace-event-perl.c index e108207c5de0..af7da565a750 100644 --- a/tools/perf/util/scripting-engines/trace-event-perl.c +++ b/tools/perf/util/scripting-engines/trace-event-perl.c @@ -215,6 +215,7 @@ static void define_event_symbols(struct event_format *event, case PRINT_BSTRING: case PRINT_DYNAMIC_ARRAY: case PRINT_STRING: + case PRINT_BITMASK: break; case PRINT_TYPE: define_event_symbols(event, ev_name, args->typecast.item); diff --git a/tools/perf/util/scripting-engines/trace-event-python.c b/tools/perf/util/scripting-engines/trace-event-python.c index cd9774df3750..1c419321f707 100644 --- a/tools/perf/util/scripting-engines/trace-event-python.c +++ b/tools/perf/util/scripting-engines/trace-event-python.c @@ -197,6 +197,7 @@ static void define_event_symbols(struct event_format *event, case PRINT_BSTRING: case PRINT_DYNAMIC_ARRAY: case PRINT_FUNC: + case PRINT_BITMASK: /* we should warn... */ return; } @@ -622,6 +623,7 @@ static int python_generate_script(struct pevent *pevent, const char *outfile) fprintf(ofp, "%s=", f->name); if (f->flags & FIELD_IS_STRING || f->flags & FIELD_IS_FLAG || + f->flags & FIELD_IS_ARRAY || f->flags & FIELD_IS_SYMBOLIC) fprintf(ofp, "%%s"); else if (f->flags & FIELD_IS_SIGNED) diff --git a/tools/perf/util/sort.c b/tools/perf/util/sort.c index 901b9bece2ee..1ec57dd82284 100644 --- a/tools/perf/util/sort.c +++ b/tools/perf/util/sort.c @@ -1,3 +1,4 @@ +#include <sys/mman.h> #include "sort.h" #include "hist.h" #include "comm.h" @@ -784,6 +785,104 @@ static int hist_entry__snoop_snprintf(struct hist_entry *he, char *bf, return repsep_snprintf(bf, size, "%-*s", width, out); } +static inline u64 cl_address(u64 address) +{ + /* return the cacheline of the address */ + return (address & ~(cacheline_size - 1)); +} + +static int64_t +sort__dcacheline_cmp(struct hist_entry *left, struct hist_entry *right) +{ + u64 l, r; + struct map *l_map, *r_map; + + if (!left->mem_info) return -1; + if (!right->mem_info) return 1; + + /* group event types together */ + if (left->cpumode > right->cpumode) return -1; + if (left->cpumode < right->cpumode) return 1; + + l_map = left->mem_info->daddr.map; + r_map = right->mem_info->daddr.map; + + /* if both are NULL, jump to sort on al_addr instead */ + if (!l_map && !r_map) + goto addr; + + if (!l_map) return -1; + if (!r_map) return 1; + + if (l_map->maj > r_map->maj) return -1; + if (l_map->maj < r_map->maj) return 1; + + if (l_map->min > r_map->min) return -1; + if (l_map->min < r_map->min) return 1; + + if (l_map->ino > r_map->ino) return -1; + if (l_map->ino < r_map->ino) return 1; + + if (l_map->ino_generation > r_map->ino_generation) return -1; + if (l_map->ino_generation < r_map->ino_generation) return 1; + + /* + * Addresses with no major/minor numbers are assumed to be + * anonymous in userspace. Sort those on pid then address. + * + * The kernel and non-zero major/minor mapped areas are + * assumed to be unity mapped. Sort those on address. + */ + + if ((left->cpumode != PERF_RECORD_MISC_KERNEL) && + (!(l_map->flags & MAP_SHARED)) && + !l_map->maj && !l_map->min && !l_map->ino && + !l_map->ino_generation) { + /* userspace anonymous */ + + if (left->thread->pid_ > right->thread->pid_) return -1; + if (left->thread->pid_ < right->thread->pid_) return 1; + } + +addr: + /* al_addr does all the right addr - start + offset calculations */ + l = cl_address(left->mem_info->daddr.al_addr); + r = cl_address(right->mem_info->daddr.al_addr); + + if (l > r) return -1; + if (l < r) return 1; + + return 0; +} + +static int hist_entry__dcacheline_snprintf(struct hist_entry *he, char *bf, + size_t size, unsigned int width) +{ + + uint64_t addr = 0; + struct map *map = NULL; + struct symbol *sym = NULL; + char level = he->level; + + if (he->mem_info) { + addr = cl_address(he->mem_info->daddr.al_addr); + map = he->mem_info->daddr.map; + sym = he->mem_info->daddr.sym; + + /* print [s] for shared data mmaps */ + if ((he->cpumode != PERF_RECORD_MISC_KERNEL) && + map && (map->type == MAP__VARIABLE) && + (map->flags & MAP_SHARED) && + (map->maj || map->min || map->ino || + map->ino_generation)) + level = 's'; + else if (!map) + level = 'X'; + } + return _hist_entry__sym_snprintf(map, sym, addr, level, bf, size, + width); +} + struct sort_entry sort_mispredict = { .se_header = "Branch Mispredicted", .se_cmp = sort__mispredict_cmp, @@ -876,6 +975,13 @@ struct sort_entry sort_mem_snoop = { .se_width_idx = HISTC_MEM_SNOOP, }; +struct sort_entry sort_mem_dcacheline = { + .se_header = "Data Cacheline", + .se_cmp = sort__dcacheline_cmp, + .se_snprintf = hist_entry__dcacheline_snprintf, + .se_width_idx = HISTC_MEM_DCACHELINE, +}; + static int64_t sort__abort_cmp(struct hist_entry *left, struct hist_entry *right) { @@ -1043,6 +1149,7 @@ static struct sort_dimension memory_sort_dimensions[] = { DIM(SORT_MEM_TLB, "tlb", sort_mem_tlb), DIM(SORT_MEM_LVL, "mem", sort_mem_lvl), DIM(SORT_MEM_SNOOP, "snoop", sort_mem_snoop), + DIM(SORT_MEM_DCACHELINE, "dcacheline", sort_mem_dcacheline), }; #undef DIM @@ -1061,6 +1168,7 @@ static struct hpp_dimension hpp_sort_dimensions[] = { DIM(PERF_HPP__OVERHEAD_US, "overhead_us"), DIM(PERF_HPP__OVERHEAD_GUEST_SYS, "overhead_guest_sys"), DIM(PERF_HPP__OVERHEAD_GUEST_US, "overhead_guest_us"), + DIM(PERF_HPP__OVERHEAD_ACC, "overhead_children"), DIM(PERF_HPP__SAMPLES, "sample"), DIM(PERF_HPP__PERIOD, "period"), }; @@ -1156,6 +1264,7 @@ __sort_dimension__alloc_hpp(struct sort_dimension *sd) INIT_LIST_HEAD(&hse->hpp.list); INIT_LIST_HEAD(&hse->hpp.sort_list); + hse->hpp.elide = false; return hse; } @@ -1363,27 +1472,64 @@ static int __setup_sorting(void) return ret; } -bool perf_hpp__should_skip(struct perf_hpp_fmt *format) +void perf_hpp__set_elide(int idx, bool elide) { - if (perf_hpp__is_sort_entry(format)) { - struct hpp_sort_entry *hse; + struct perf_hpp_fmt *fmt; + struct hpp_sort_entry *hse; + + perf_hpp__for_each_format(fmt) { + if (!perf_hpp__is_sort_entry(fmt)) + continue; - hse = container_of(format, struct hpp_sort_entry, hpp); - return hse->se->elide; + hse = container_of(fmt, struct hpp_sort_entry, hpp); + if (hse->se->se_width_idx == idx) { + fmt->elide = elide; + break; + } } - return false; } -static void sort_entry__setup_elide(struct sort_entry *se, - struct strlist *list, - const char *list_name, FILE *fp) +static bool __get_elide(struct strlist *list, const char *list_name, FILE *fp) { if (list && strlist__nr_entries(list) == 1) { if (fp != NULL) fprintf(fp, "# %s: %s\n", list_name, strlist__entry(list, 0)->s); - se->elide = true; + return true; } + return false; +} + +static bool get_elide(int idx, FILE *output) +{ + switch (idx) { + case HISTC_SYMBOL: + return __get_elide(symbol_conf.sym_list, "symbol", output); + case HISTC_DSO: + return __get_elide(symbol_conf.dso_list, "dso", output); + case HISTC_COMM: + return __get_elide(symbol_conf.comm_list, "comm", output); + default: + break; + } + + if (sort__mode != SORT_MODE__BRANCH) + return false; + + switch (idx) { + case HISTC_SYMBOL_FROM: + return __get_elide(symbol_conf.sym_from_list, "sym_from", output); + case HISTC_SYMBOL_TO: + return __get_elide(symbol_conf.sym_to_list, "sym_to", output); + case HISTC_DSO_FROM: + return __get_elide(symbol_conf.dso_from_list, "dso_from", output); + case HISTC_DSO_TO: + return __get_elide(symbol_conf.dso_to_list, "dso_to", output); + default: + break; + } + + return false; } void sort__setup_elide(FILE *output) @@ -1391,39 +1537,12 @@ void sort__setup_elide(FILE *output) struct perf_hpp_fmt *fmt; struct hpp_sort_entry *hse; - sort_entry__setup_elide(&sort_dso, symbol_conf.dso_list, - "dso", output); - sort_entry__setup_elide(&sort_comm, symbol_conf.comm_list, - "comm", output); - sort_entry__setup_elide(&sort_sym, symbol_conf.sym_list, - "symbol", output); - - if (sort__mode == SORT_MODE__BRANCH) { - sort_entry__setup_elide(&sort_dso_from, - symbol_conf.dso_from_list, - "dso_from", output); - sort_entry__setup_elide(&sort_dso_to, - symbol_conf.dso_to_list, - "dso_to", output); - sort_entry__setup_elide(&sort_sym_from, - symbol_conf.sym_from_list, - "sym_from", output); - sort_entry__setup_elide(&sort_sym_to, - symbol_conf.sym_to_list, - "sym_to", output); - } else if (sort__mode == SORT_MODE__MEMORY) { - sort_entry__setup_elide(&sort_dso, symbol_conf.dso_list, - "symbol_daddr", output); - sort_entry__setup_elide(&sort_dso, symbol_conf.dso_list, - "dso_daddr", output); - sort_entry__setup_elide(&sort_dso, symbol_conf.dso_list, - "mem", output); - sort_entry__setup_elide(&sort_dso, symbol_conf.dso_list, - "local_weight", output); - sort_entry__setup_elide(&sort_dso, symbol_conf.dso_list, - "tlb", output); - sort_entry__setup_elide(&sort_dso, symbol_conf.dso_list, - "snoop", output); + perf_hpp__for_each_format(fmt) { + if (!perf_hpp__is_sort_entry(fmt)) + continue; + + hse = container_of(fmt, struct hpp_sort_entry, hpp); + fmt->elide = get_elide(hse->se->se_width_idx, output); } /* @@ -1434,8 +1553,7 @@ void sort__setup_elide(FILE *output) if (!perf_hpp__is_sort_entry(fmt)) continue; - hse = container_of(fmt, struct hpp_sort_entry, hpp); - if (!hse->se->elide) + if (!fmt->elide) return; } @@ -1443,8 +1561,7 @@ void sort__setup_elide(FILE *output) if (!perf_hpp__is_sort_entry(fmt)) continue; - hse = container_of(fmt, struct hpp_sort_entry, hpp); - hse->se->elide = false; + fmt->elide = false; } } @@ -1581,6 +1698,9 @@ void reset_output_field(void) sort__has_sym = 0; sort__has_dso = 0; + field_order = NULL; + sort_order = NULL; + reset_dimensions(); perf_hpp__reset_output_field(); } diff --git a/tools/perf/util/sort.h b/tools/perf/util/sort.h index 5f38d925e92f..041f0c9cea2b 100644 --- a/tools/perf/util/sort.h +++ b/tools/perf/util/sort.h @@ -20,7 +20,7 @@ #include "parse-options.h" #include "parse-events.h" - +#include "hist.h" #include "thread.h" extern regex_t parent_regex; @@ -82,12 +82,14 @@ struct hist_entry { struct list_head head; } pairs; struct he_stat stat; + struct he_stat *stat_acc; struct map_symbol ms; struct thread *thread; struct comm *comm; u64 ip; u64 transaction; s32 cpu; + u8 cpumode; struct hist_entry_diff diff; @@ -130,6 +132,21 @@ static inline void hist_entry__add_pair(struct hist_entry *pair, list_add_tail(&pair->pairs.node, &he->pairs.head); } +static inline float hist_entry__get_percent_limit(struct hist_entry *he) +{ + u64 period = he->stat.period; + u64 total_period = hists__total_period(he->hists); + + if (unlikely(total_period == 0)) + return 0; + + if (symbol_conf.cumulate_callchain) + period = he->stat_acc->period; + + return period * 100.0 / total_period; +} + + enum sort_mode { SORT_MODE__NORMAL, SORT_MODE__BRANCH, @@ -169,6 +186,7 @@ enum sort_type { SORT_MEM_TLB, SORT_MEM_LVL, SORT_MEM_SNOOP, + SORT_MEM_DCACHELINE, }; /* @@ -186,7 +204,6 @@ struct sort_entry { int (*se_snprintf)(struct hist_entry *he, char *bf, size_t size, unsigned int width); u8 se_width_idx; - bool elide; }; extern struct sort_entry sort_thread; @@ -197,6 +214,7 @@ int setup_output_field(void); void reset_output_field(void); extern int sort_dimension__add(const char *); void sort__setup_elide(FILE *fp); +void perf_hpp__set_elide(int idx, bool elide); int report_parse_ignore_callees_opt(const struct option *opt, const char *arg, int unset); diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c index 95e249779931..7b9096f29cdb 100644 --- a/tools/perf/util/symbol.c +++ b/tools/perf/util/symbol.c @@ -29,11 +29,12 @@ int vmlinux_path__nr_entries; char **vmlinux_path; struct symbol_conf symbol_conf = { - .use_modules = true, - .try_vmlinux_path = true, - .annotate_src = true, - .demangle = true, - .symfs = "", + .use_modules = true, + .try_vmlinux_path = true, + .annotate_src = true, + .demangle = true, + .cumulate_callchain = true, + .symfs = "", }; static enum dso_binary_type binary_type_symtab[] = { diff --git a/tools/perf/util/symbol.h b/tools/perf/util/symbol.h index 33ede53fa6b9..615c752dd767 100644 --- a/tools/perf/util/symbol.h +++ b/tools/perf/util/symbol.h @@ -109,6 +109,7 @@ struct symbol_conf { show_nr_samples, show_total_period, use_callchain, + cumulate_callchain, exclude_other, show_cpu_utilization, initialized, diff --git a/tools/perf/util/unwind-libunwind.c b/tools/perf/util/unwind-libunwind.c index bd5768d74f01..25578b98f5c5 100644 --- a/tools/perf/util/unwind-libunwind.c +++ b/tools/perf/util/unwind-libunwind.c @@ -250,7 +250,6 @@ static int read_unwind_spec_eh_frame(struct dso *dso, struct machine *machine, /* Check the .eh_frame section for unwinding info */ offset = elf_section_offset(fd, ".eh_frame_hdr"); - close(fd); if (offset) ret = unwind_spec_ehframe(dso, machine, offset, @@ -271,7 +270,6 @@ static int read_unwind_spec_debug_frame(struct dso *dso, /* Check the .debug_frame section for unwinding info */ *offset = elf_section_offset(fd, ".debug_frame"); - close(fd); if (*offset) return 0; diff --git a/tools/perf/util/util.c b/tools/perf/util/util.c index 7fff6be07f07..95aefa78bb07 100644 --- a/tools/perf/util/util.c +++ b/tools/perf/util/util.c @@ -17,6 +17,7 @@ * XXX We need to find a better place for these things... */ unsigned int page_size; +int cacheline_size; bool test_attr__enabled; diff --git a/tools/perf/util/util.h b/tools/perf/util/util.h index b03da44e94e4..66864364ccb4 100644 --- a/tools/perf/util/util.h +++ b/tools/perf/util/util.h @@ -304,6 +304,7 @@ char *rtrim(char *s); void dump_stack(void); extern unsigned int page_size; +extern int cacheline_size; void get_term_dimensions(struct winsize *ws); diff --git a/tools/power/acpi/Makefile b/tools/power/acpi/Makefile index c2c0f20067a5..e5a3c4be2a10 100644 --- a/tools/power/acpi/Makefile +++ b/tools/power/acpi/Makefile @@ -19,6 +19,8 @@ OUTDIR := $(shell cd $(OUTPUT) && /bin/pwd) $(if $(OUTDIR),, $(error output directory "$(OUTPUT)" does not exist)) endif +SUBDIRS = tools/ec + # --- CONFIGURATION BEGIN --- # Set the following to `true' to make a unstripped, unoptimized @@ -68,7 +70,8 @@ WARNINGS += $(call cc-supports,-Wstrict-prototypes) WARNINGS += $(call cc-supports,-Wdeclaration-after-statement) KERNEL_INCLUDE := ../../../include -CFLAGS += -D_LINUX -DDEFINE_ALTERNATE_TYPES -I$(KERNEL_INCLUDE) +ACPICA_INCLUDE := ../../../drivers/acpi/acpica +CFLAGS += -D_LINUX -I$(KERNEL_INCLUDE) -I$(ACPICA_INCLUDE) CFLAGS += $(WARNINGS) ifeq ($(strip $(V)),false) @@ -92,10 +95,29 @@ endif # --- ACPIDUMP BEGIN --- vpath %.c \ - tools/acpidump + ../../../drivers/acpi/acpica\ + tools/acpidump\ + common\ + os_specific/service_layers + +CFLAGS += -DACPI_DUMP_APP -Itools/acpidump DUMP_OBJS = \ - acpidump.o + apdump.o\ + apfiles.o\ + apmain.o\ + osunixdir.o\ + osunixmap.o\ + tbprint.o\ + tbxfroot.o\ + utbuffer.o\ + utexcep.o\ + utmath.o\ + utstring.o\ + utxferror.o\ + oslinuxtbl.o\ + cmfsize.o\ + getopt.o DUMP_OBJS := $(addprefix $(OUTPUT)tools/acpidump/,$(DUMP_OBJS)) diff --git a/tools/power/acpi/common/cmfsize.c b/tools/power/acpi/common/cmfsize.c new file mode 100644 index 000000000000..5140e5edae1f --- /dev/null +++ b/tools/power/acpi/common/cmfsize.c @@ -0,0 +1,101 @@ +/****************************************************************************** + * + * Module Name: cfsize - Common get file size function + * + *****************************************************************************/ + +/* + * Copyright (C) 2000 - 2014, Intel Corp. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * Alternatively, this software may be distributed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + * NO WARRANTY + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + */ + +#include <acpi/acpi.h> +#include "accommon.h" +#include "acapps.h" +#include <stdio.h> + +#define _COMPONENT ACPI_TOOLS +ACPI_MODULE_NAME("cmfsize") + +/******************************************************************************* + * + * FUNCTION: cm_get_file_size + * + * PARAMETERS: file - Open file descriptor + * + * RETURN: File Size. On error, -1 (ACPI_UINT32_MAX) + * + * DESCRIPTION: Get the size of a file. Uses seek-to-EOF. File must be open. + * Does not disturb the current file pointer. Uses perror for + * error messages. + * + ******************************************************************************/ +u32 cm_get_file_size(FILE * file) +{ + long file_size; + long current_offset; + + /* Save the current file pointer, seek to EOF to obtain file size */ + + current_offset = ftell(file); + if (current_offset < 0) { + goto offset_error; + } + + if (fseek(file, 0, SEEK_END)) { + goto seek_error; + } + + file_size = ftell(file); + if (file_size < 0) { + goto offset_error; + } + + /* Restore original file pointer */ + + if (fseek(file, current_offset, SEEK_SET)) { + goto seek_error; + } + + return ((u32)file_size); + +offset_error: + perror("Could not get file offset"); + return (ACPI_UINT32_MAX); + +seek_error: + perror("Could not seek file"); + return (ACPI_UINT32_MAX); +} diff --git a/tools/power/acpi/common/getopt.c b/tools/power/acpi/common/getopt.c new file mode 100644 index 000000000000..a302f52e4fd3 --- /dev/null +++ b/tools/power/acpi/common/getopt.c @@ -0,0 +1,239 @@ +/****************************************************************************** + * + * Module Name: getopt + * + *****************************************************************************/ + +/* + * Copyright (C) 2000 - 2014, Intel Corp. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * Alternatively, this software may be distributed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + * NO WARRANTY + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + */ + +/* + * ACPICA getopt() implementation + * + * Option strings: + * "f" - Option has no arguments + * "f:" - Option requires an argument + * "f^" - Option has optional single-char sub-options + * "f|" - Option has required single-char sub-options + */ + +#include <stdio.h> +#include <string.h> +#include <acpi/acpi.h> +#include "accommon.h" +#include "acapps.h" + +#define ACPI_OPTION_ERROR(msg, badchar) \ + if (acpi_gbl_opterr) {fprintf (stderr, "%s%c\n", msg, badchar);} + +int acpi_gbl_opterr = 1; +int acpi_gbl_optind = 1; +int acpi_gbl_sub_opt_char = 0; +char *acpi_gbl_optarg; + +static int current_char_ptr = 1; + +/******************************************************************************* + * + * FUNCTION: acpi_getopt_argument + * + * PARAMETERS: argc, argv - from main + * + * RETURN: 0 if an argument was found, -1 otherwise. Sets acpi_gbl_Optarg + * to point to the next argument. + * + * DESCRIPTION: Get the next argument. Used to obtain arguments for the + * two-character options after the original call to acpi_getopt. + * Note: Either the argument starts at the next character after + * the option, or it is pointed to by the next argv entry. + * (After call to acpi_getopt, we need to backup to the previous + * argv entry). + * + ******************************************************************************/ + +int acpi_getopt_argument(int argc, char **argv) +{ + acpi_gbl_optind--; + current_char_ptr++; + + if (argv[acpi_gbl_optind][(int)(current_char_ptr + 1)] != '\0') { + acpi_gbl_optarg = + &argv[acpi_gbl_optind++][(int)(current_char_ptr + 1)]; + } else if (++acpi_gbl_optind >= argc) { + ACPI_OPTION_ERROR("Option requires an argument: -", 'v'); + + current_char_ptr = 1; + return (-1); + } else { + acpi_gbl_optarg = argv[acpi_gbl_optind++]; + } + + current_char_ptr = 1; + return (0); +} + +/******************************************************************************* + * + * FUNCTION: acpi_getopt + * + * PARAMETERS: argc, argv - from main + * opts - options info list + * + * RETURN: Option character or EOF + * + * DESCRIPTION: Get the next option + * + ******************************************************************************/ + +int acpi_getopt(int argc, char **argv, char *opts) +{ + int current_char; + char *opts_ptr; + + if (current_char_ptr == 1) { + if (acpi_gbl_optind >= argc || + argv[acpi_gbl_optind][0] != '-' || + argv[acpi_gbl_optind][1] == '\0') { + return (EOF); + } else if (strcmp(argv[acpi_gbl_optind], "--") == 0) { + acpi_gbl_optind++; + return (EOF); + } + } + + /* Get the option */ + + current_char = argv[acpi_gbl_optind][current_char_ptr]; + + /* Make sure that the option is legal */ + + if (current_char == ':' || + (opts_ptr = strchr(opts, current_char)) == NULL) { + ACPI_OPTION_ERROR("Illegal option: -", current_char); + + if (argv[acpi_gbl_optind][++current_char_ptr] == '\0') { + acpi_gbl_optind++; + current_char_ptr = 1; + } + + return ('?'); + } + + /* Option requires an argument? */ + + if (*++opts_ptr == ':') { + if (argv[acpi_gbl_optind][(int)(current_char_ptr + 1)] != '\0') { + acpi_gbl_optarg = + &argv[acpi_gbl_optind++][(int) + (current_char_ptr + 1)]; + } else if (++acpi_gbl_optind >= argc) { + ACPI_OPTION_ERROR("Option requires an argument: -", + current_char); + + current_char_ptr = 1; + return ('?'); + } else { + acpi_gbl_optarg = argv[acpi_gbl_optind++]; + } + + current_char_ptr = 1; + } + + /* Option has an optional argument? */ + + else if (*opts_ptr == '+') { + if (argv[acpi_gbl_optind][(int)(current_char_ptr + 1)] != '\0') { + acpi_gbl_optarg = + &argv[acpi_gbl_optind++][(int) + (current_char_ptr + 1)]; + } else if (++acpi_gbl_optind >= argc) { + acpi_gbl_optarg = NULL; + } else { + acpi_gbl_optarg = argv[acpi_gbl_optind++]; + } + + current_char_ptr = 1; + } + + /* Option has optional single-char arguments? */ + + else if (*opts_ptr == '^') { + if (argv[acpi_gbl_optind][(int)(current_char_ptr + 1)] != '\0') { + acpi_gbl_optarg = + &argv[acpi_gbl_optind][(int)(current_char_ptr + 1)]; + } else { + acpi_gbl_optarg = "^"; + } + + acpi_gbl_sub_opt_char = acpi_gbl_optarg[0]; + acpi_gbl_optind++; + current_char_ptr = 1; + } + + /* Option has a required single-char argument? */ + + else if (*opts_ptr == '|') { + if (argv[acpi_gbl_optind][(int)(current_char_ptr + 1)] != '\0') { + acpi_gbl_optarg = + &argv[acpi_gbl_optind][(int)(current_char_ptr + 1)]; + } else { + ACPI_OPTION_ERROR + ("Option requires a single-character suboption: -", + current_char); + + current_char_ptr = 1; + return ('?'); + } + + acpi_gbl_sub_opt_char = acpi_gbl_optarg[0]; + acpi_gbl_optind++; + current_char_ptr = 1; + } + + /* Option with no arguments */ + + else { + if (argv[acpi_gbl_optind][++current_char_ptr] == '\0') { + current_char_ptr = 1; + acpi_gbl_optind++; + } + + acpi_gbl_optarg = NULL; + } + + return (current_char); +} diff --git a/tools/power/acpi/man/acpidump.8 b/tools/power/acpi/man/acpidump.8 index adfa99166e5e..38f095d86b52 100644 --- a/tools/power/acpi/man/acpidump.8 +++ b/tools/power/acpi/man/acpidump.8 @@ -1,18 +1,64 @@ .TH ACPIDUMP 8 .SH NAME -acpidump \- Dump system's ACPI tables to an ASCII file. +acpidump \- dump a system's ACPI tables to an ASCII file + .SH SYNOPSIS -.ft B -.B acpidump > acpidump.out +.B acpidump +.RI [ options ] +.br + .SH DESCRIPTION -\fBacpidump \fP dumps the systems ACPI tables to an ASCII file -appropriate for attaching to a bug report. +.B acpidump +dumps the systems ACPI tables to an ASCII file appropriate for +attaching to a bug report. Subsequently, they can be processed by utilities in the ACPICA package. -.SS Options -no options worth worrying about. -.PP -.SH EXAMPLE + +.SH OPTIONS +acpidump options are as follow: +.TP +.B Options +.TP +.B \-b +Dump tables to binary files +.TP +.B \-c +Dump customized tables +.TP +.B \-h \-? +This help message +.TP +.B \-o <File> +Redirect output to file +.TP +.B \-r <Address> +Dump tables from specified RSDP +.TP +.B \-s +Print table summaries only +.TP +.B \-v +Display version information +.TP +.B \-z +Verbose mode +.TP +.B Table Options +.TP +.B \-a <Address> +Get table via a physical address +.TP +.B \-f <BinaryFile> +Get table via a binary file +.TP +.B \-n <Signature> +Get table via a name/signature +.TP +Invocation without parameters dumps all available tables +.TP +Multiple mixed instances of -a, -f, and -n are supported + +.SH EXAMPLES .nf # acpidump > acpidump.out @@ -50,10 +96,25 @@ ACPICA: https://acpica.org/ .ta .nf /dev/mem +/sys/firmware/acpi/tables/* /sys/firmware/acpi/tables/dynamic/* +/sys/firmware/efi/systab .fi -.PP .SH AUTHOR -.nf -Written by Len Brown <len.brown@intel.com> +.TP +Original by: + Len Brown <len.brown@intel.com> +.TP +Written by: + Chao Guan <chao.guan@intel.com> +.TP +Updated by: + Bob Moore <robert.moore@intel.com> + Lv Zheng <lv.zheng@intel.com> + +.SH SEE ALSO +\&\fIacpixtract\fR\|(8), \fIiasl\fR\|(8). + +.SH COPYRIGHT +COPYRIGHT (c) 2013, Intel Corporation. diff --git a/tools/power/acpi/os_specific/service_layers/oslinuxtbl.c b/tools/power/acpi/os_specific/service_layers/oslinuxtbl.c new file mode 100644 index 000000000000..28c52008e854 --- /dev/null +++ b/tools/power/acpi/os_specific/service_layers/oslinuxtbl.c @@ -0,0 +1,1329 @@ +/****************************************************************************** + * + * Module Name: oslinuxtbl - Linux OSL for obtaining ACPI tables + * + *****************************************************************************/ + +/* + * Copyright (C) 2000 - 2014, Intel Corp. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * Alternatively, this software may be distributed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + * NO WARRANTY + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + */ + +#include "acpidump.h" + +#define _COMPONENT ACPI_OS_SERVICES +ACPI_MODULE_NAME("oslinuxtbl") + +#ifndef PATH_MAX +#define PATH_MAX 256 +#endif +/* List of information about obtained ACPI tables */ +typedef struct osl_table_info { + struct osl_table_info *next; + u32 instance; + char signature[ACPI_NAME_SIZE]; + +} osl_table_info; + +/* Local prototypes */ + +static acpi_status osl_table_initialize(void); + +static acpi_status +osl_table_name_from_file(char *filename, char *signature, u32 *instance); + +static acpi_status osl_add_table_to_list(char *signature, u32 instance); + +static acpi_status +osl_read_table_from_file(char *filename, + acpi_size file_offset, + char *signature, struct acpi_table_header **table); + +static acpi_status +osl_map_table(acpi_size address, + char *signature, struct acpi_table_header **table); + +static void osl_unmap_table(struct acpi_table_header *table); + +static acpi_physical_address osl_find_rsdp_via_efi(void); + +static acpi_status osl_load_rsdp(void); + +static acpi_status osl_list_customized_tables(char *directory); + +static acpi_status +osl_get_customized_table(char *pathname, + char *signature, + u32 instance, + struct acpi_table_header **table, + acpi_physical_address * address); + +static acpi_status osl_list_bios_tables(void); + +static acpi_status +osl_get_bios_table(char *signature, + u32 instance, + struct acpi_table_header **table, + acpi_physical_address * address); + +static acpi_status osl_get_last_status(acpi_status default_status); + +/* File locations */ + +#define DYNAMIC_TABLE_DIR "/sys/firmware/acpi/tables/dynamic" +#define STATIC_TABLE_DIR "/sys/firmware/acpi/tables" +#define EFI_SYSTAB "/sys/firmware/efi/systab" + +/* Should we get dynamically loaded SSDTs from DYNAMIC_TABLE_DIR? */ + +u8 gbl_dump_dynamic_tables = TRUE; + +/* Initialization flags */ + +u8 gbl_table_list_initialized = FALSE; + +/* Local copies of main ACPI tables */ + +struct acpi_table_rsdp gbl_rsdp; +struct acpi_table_fadt *gbl_fadt = NULL; +struct acpi_table_rsdt *gbl_rsdt = NULL; +struct acpi_table_xsdt *gbl_xsdt = NULL; + +/* Table addresses */ + +acpi_physical_address gbl_fadt_address = 0; +acpi_physical_address gbl_rsdp_address = 0; + +/* Revision of RSD PTR */ + +u8 gbl_revision = 0; + +struct osl_table_info *gbl_table_list_head = NULL; +u32 gbl_table_count = 0; + +/****************************************************************************** + * + * FUNCTION: osl_get_last_status + * + * PARAMETERS: default_status - Default error status to return + * + * RETURN: Status; Converted from errno. + * + * DESCRIPTION: Get last errno and conver it to acpi_status. + * + *****************************************************************************/ + +static acpi_status osl_get_last_status(acpi_status default_status) +{ + + switch (errno) { + case EACCES: + case EPERM: + + return (AE_ACCESS); + + case ENOENT: + + return (AE_NOT_FOUND); + + case ENOMEM: + + return (AE_NO_MEMORY); + + default: + + return (default_status); + } +} + +/****************************************************************************** + * + * FUNCTION: acpi_os_get_table_by_address + * + * PARAMETERS: address - Physical address of the ACPI table + * table - Where a pointer to the table is returned + * + * RETURN: Status; Table buffer is returned if AE_OK. + * AE_NOT_FOUND: A valid table was not found at the address + * + * DESCRIPTION: Get an ACPI table via a physical memory address. + * + *****************************************************************************/ + +acpi_status +acpi_os_get_table_by_address(acpi_physical_address address, + struct acpi_table_header ** table) +{ + u32 table_length; + struct acpi_table_header *mapped_table; + struct acpi_table_header *local_table = NULL; + acpi_status status = AE_OK; + + /* Get main ACPI tables from memory on first invocation of this function */ + + status = osl_table_initialize(); + if (ACPI_FAILURE(status)) { + return (status); + } + + /* Map the table and validate it */ + + status = osl_map_table(address, NULL, &mapped_table); + if (ACPI_FAILURE(status)) { + return (status); + } + + /* Copy table to local buffer and return it */ + + table_length = ap_get_table_length(mapped_table); + if (table_length == 0) { + status = AE_BAD_HEADER; + goto exit; + } + + local_table = calloc(1, table_length); + if (!local_table) { + status = AE_NO_MEMORY; + goto exit; + } + + ACPI_MEMCPY(local_table, mapped_table, table_length); + +exit: + osl_unmap_table(mapped_table); + *table = local_table; + return (status); +} + +/****************************************************************************** + * + * FUNCTION: acpi_os_get_table_by_name + * + * PARAMETERS: signature - ACPI Signature for desired table. Must be + * a null terminated 4-character string. + * instance - Multiple table support for SSDT/UEFI (0...n) + * Must be 0 for other tables. + * table - Where a pointer to the table is returned + * address - Where the table physical address is returned + * + * RETURN: Status; Table buffer and physical address returned if AE_OK. + * AE_LIMIT: Instance is beyond valid limit + * AE_NOT_FOUND: A table with the signature was not found + * + * NOTE: Assumes the input signature is uppercase. + * + *****************************************************************************/ + +acpi_status +acpi_os_get_table_by_name(char *signature, + u32 instance, + struct acpi_table_header ** table, + acpi_physical_address * address) +{ + acpi_status status; + + /* Get main ACPI tables from memory on first invocation of this function */ + + status = osl_table_initialize(); + if (ACPI_FAILURE(status)) { + return (status); + } + + /* Not a main ACPI table, attempt to extract it from the RSDT/XSDT */ + + if (!gbl_dump_customized_tables) { + + /* Attempt to get the table from the memory */ + + status = + osl_get_bios_table(signature, instance, table, address); + } else { + /* Attempt to get the table from the static directory */ + + status = osl_get_customized_table(STATIC_TABLE_DIR, signature, + instance, table, address); + } + + if (ACPI_FAILURE(status) && status == AE_LIMIT) { + if (gbl_dump_dynamic_tables) { + + /* Attempt to get a dynamic table */ + + status = + osl_get_customized_table(DYNAMIC_TABLE_DIR, + signature, instance, table, + address); + } + } + + return (status); +} + +/****************************************************************************** + * + * FUNCTION: osl_add_table_to_list + * + * PARAMETERS: signature - Table signature + * instance - Table instance + * + * RETURN: Status; Successfully added if AE_OK. + * AE_NO_MEMORY: Memory allocation error + * + * DESCRIPTION: Insert a table structure into OSL table list. + * + *****************************************************************************/ + +static acpi_status osl_add_table_to_list(char *signature, u32 instance) +{ + struct osl_table_info *new_info; + struct osl_table_info *next; + u32 next_instance = 0; + u8 found = FALSE; + + new_info = calloc(1, sizeof(struct osl_table_info)); + if (!new_info) { + return (AE_NO_MEMORY); + } + + ACPI_MOVE_NAME(new_info->signature, signature); + + if (!gbl_table_list_head) { + gbl_table_list_head = new_info; + } else { + next = gbl_table_list_head; + while (1) { + if (ACPI_COMPARE_NAME(next->signature, signature)) { + if (next->instance == instance) { + found = TRUE; + } + if (next->instance >= next_instance) { + next_instance = next->instance + 1; + } + } + + if (!next->next) { + break; + } + next = next->next; + } + next->next = new_info; + } + + if (found) { + if (instance) { + fprintf(stderr, + "%4.4s: Warning unmatched table instance %d, expected %d\n", + signature, instance, next_instance); + } + instance = next_instance; + } + + new_info->instance = instance; + gbl_table_count++; + + return (AE_OK); +} + +/****************************************************************************** + * + * FUNCTION: acpi_os_get_table_by_index + * + * PARAMETERS: index - Which table to get + * table - Where a pointer to the table is returned + * instance - Where a pointer to the table instance no. is + * returned + * address - Where the table physical address is returned + * + * RETURN: Status; Table buffer and physical address returned if AE_OK. + * AE_LIMIT: Index is beyond valid limit + * + * DESCRIPTION: Get an ACPI table via an index value (0 through n). Returns + * AE_LIMIT when an invalid index is reached. Index is not + * necessarily an index into the RSDT/XSDT. + * + *****************************************************************************/ + +acpi_status +acpi_os_get_table_by_index(u32 index, + struct acpi_table_header ** table, + u32 *instance, acpi_physical_address * address) +{ + struct osl_table_info *info; + acpi_status status; + u32 i; + + /* Get main ACPI tables from memory on first invocation of this function */ + + status = osl_table_initialize(); + if (ACPI_FAILURE(status)) { + return (status); + } + + /* Validate Index */ + + if (index >= gbl_table_count) { + return (AE_LIMIT); + } + + /* Point to the table list entry specified by the Index argument */ + + info = gbl_table_list_head; + for (i = 0; i < index; i++) { + info = info->next; + } + + /* Now we can just get the table via the signature */ + + status = acpi_os_get_table_by_name(info->signature, info->instance, + table, address); + + if (ACPI_SUCCESS(status)) { + *instance = info->instance; + } + return (status); +} + +/****************************************************************************** + * + * FUNCTION: osl_find_rsdp_via_efi + * + * PARAMETERS: None + * + * RETURN: RSDP address if found + * + * DESCRIPTION: Find RSDP address via EFI. + * + *****************************************************************************/ + +static acpi_physical_address osl_find_rsdp_via_efi(void) +{ + FILE *file; + char buffer[80]; + unsigned long address = 0; + + file = fopen(EFI_SYSTAB, "r"); + if (file) { + while (fgets(buffer, 80, file)) { + if (sscanf(buffer, "ACPI20=0x%lx", &address) == 1) { + break; + } + } + fclose(file); + } + + return ((acpi_physical_address) (address)); +} + +/****************************************************************************** + * + * FUNCTION: osl_load_rsdp + * + * PARAMETERS: None + * + * RETURN: Status + * + * DESCRIPTION: Scan and load RSDP. + * + *****************************************************************************/ + +static acpi_status osl_load_rsdp(void) +{ + struct acpi_table_header *mapped_table; + u8 *rsdp_address; + acpi_physical_address rsdp_base; + acpi_size rsdp_size; + + /* Get RSDP from memory */ + + rsdp_size = sizeof(struct acpi_table_rsdp); + if (gbl_rsdp_base) { + rsdp_base = gbl_rsdp_base; + } else { + rsdp_base = osl_find_rsdp_via_efi(); + } + + if (!rsdp_base) { + rsdp_base = ACPI_HI_RSDP_WINDOW_BASE; + rsdp_size = ACPI_HI_RSDP_WINDOW_SIZE; + } + + rsdp_address = acpi_os_map_memory(rsdp_base, rsdp_size); + if (!rsdp_address) { + return (osl_get_last_status(AE_BAD_ADDRESS)); + } + + /* Search low memory for the RSDP */ + + mapped_table = ACPI_CAST_PTR(struct acpi_table_header, + acpi_tb_scan_memory_for_rsdp(rsdp_address, + rsdp_size)); + if (!mapped_table) { + acpi_os_unmap_memory(rsdp_address, rsdp_size); + return (AE_NOT_FOUND); + } + + gbl_rsdp_address = + rsdp_base + (ACPI_CAST8(mapped_table) - rsdp_address); + + ACPI_MEMCPY(&gbl_rsdp, mapped_table, sizeof(struct acpi_table_rsdp)); + acpi_os_unmap_memory(rsdp_address, rsdp_size); + + return (AE_OK); +} + +/****************************************************************************** + * + * FUNCTION: osl_can_use_xsdt + * + * PARAMETERS: None + * + * RETURN: TRUE if XSDT is allowed to be used. + * + * DESCRIPTION: This function collects logic that can be used to determine if + * XSDT should be used instead of RSDT. + * + *****************************************************************************/ + +static u8 osl_can_use_xsdt(void) +{ + if (gbl_revision && !acpi_gbl_do_not_use_xsdt) { + return (TRUE); + } else { + return (FALSE); + } +} + +/****************************************************************************** + * + * FUNCTION: osl_table_initialize + * + * PARAMETERS: None + * + * RETURN: Status + * + * DESCRIPTION: Initialize ACPI table data. Get and store main ACPI tables to + * local variables. Main ACPI tables include RSDT, FADT, RSDT, + * and/or XSDT. + * + *****************************************************************************/ + +static acpi_status osl_table_initialize(void) +{ + acpi_status status; + acpi_physical_address address; + + if (gbl_table_list_initialized) { + return (AE_OK); + } + + /* Get RSDP from memory */ + + status = osl_load_rsdp(); + if (ACPI_FAILURE(status)) { + return (status); + } + + /* Get XSDT from memory */ + + if (gbl_rsdp.revision && !gbl_do_not_dump_xsdt) { + if (gbl_xsdt) { + free(gbl_xsdt); + gbl_xsdt = NULL; + } + + gbl_revision = 2; + status = osl_get_bios_table(ACPI_SIG_XSDT, 0, + ACPI_CAST_PTR(struct + acpi_table_header *, + &gbl_xsdt), &address); + if (ACPI_FAILURE(status)) { + return (status); + } + } + + /* Get RSDT from memory */ + + if (gbl_rsdp.rsdt_physical_address) { + if (gbl_rsdt) { + free(gbl_rsdt); + gbl_rsdt = NULL; + } + + status = osl_get_bios_table(ACPI_SIG_RSDT, 0, + ACPI_CAST_PTR(struct + acpi_table_header *, + &gbl_rsdt), &address); + if (ACPI_FAILURE(status)) { + return (status); + } + } + + /* Get FADT from memory */ + + if (gbl_fadt) { + free(gbl_fadt); + gbl_fadt = NULL; + } + + status = osl_get_bios_table(ACPI_SIG_FADT, 0, + ACPI_CAST_PTR(struct acpi_table_header *, + &gbl_fadt), + &gbl_fadt_address); + if (ACPI_FAILURE(status)) { + return (status); + } + + if (!gbl_dump_customized_tables) { + + /* Add mandatory tables to global table list first */ + + status = osl_add_table_to_list(ACPI_RSDP_NAME, 0); + if (ACPI_FAILURE(status)) { + return (status); + } + + status = osl_add_table_to_list(ACPI_SIG_RSDT, 0); + if (ACPI_FAILURE(status)) { + return (status); + } + + if (gbl_revision == 2) { + status = osl_add_table_to_list(ACPI_SIG_XSDT, 0); + if (ACPI_FAILURE(status)) { + return (status); + } + } + + status = osl_add_table_to_list(ACPI_SIG_DSDT, 0); + if (ACPI_FAILURE(status)) { + return (status); + } + + status = osl_add_table_to_list(ACPI_SIG_FACS, 0); + if (ACPI_FAILURE(status)) { + return (status); + } + + /* Add all tables found in the memory */ + + status = osl_list_bios_tables(); + if (ACPI_FAILURE(status)) { + return (status); + } + } else { + /* Add all tables found in the static directory */ + + status = osl_list_customized_tables(STATIC_TABLE_DIR); + if (ACPI_FAILURE(status)) { + return (status); + } + } + + if (gbl_dump_dynamic_tables) { + + /* Add all dynamically loaded tables in the dynamic directory */ + + status = osl_list_customized_tables(DYNAMIC_TABLE_DIR); + if (ACPI_FAILURE(status)) { + return (status); + } + } + + gbl_table_list_initialized = TRUE; + return (AE_OK); +} + +/****************************************************************************** + * + * FUNCTION: osl_list_bios_tables + * + * PARAMETERS: None + * + * RETURN: Status; Table list is initialized if AE_OK. + * + * DESCRIPTION: Add ACPI tables to the table list from memory. + * + * NOTE: This works on Linux as table customization does not modify the + * addresses stored in RSDP/RSDT/XSDT/FADT. + * + *****************************************************************************/ + +static acpi_status osl_list_bios_tables(void) +{ + struct acpi_table_header *mapped_table = NULL; + u8 *table_data; + u8 number_of_tables; + u8 item_size; + acpi_physical_address table_address = 0; + acpi_status status = AE_OK; + u32 i; + + if (osl_can_use_xsdt()) { + item_size = sizeof(u64); + table_data = + ACPI_CAST8(gbl_xsdt) + sizeof(struct acpi_table_header); + number_of_tables = + (u8)((gbl_xsdt->header.length - + sizeof(struct acpi_table_header)) + / item_size); + } else { /* Use RSDT if XSDT is not available */ + + item_size = sizeof(u32); + table_data = + ACPI_CAST8(gbl_rsdt) + sizeof(struct acpi_table_header); + number_of_tables = + (u8)((gbl_rsdt->header.length - + sizeof(struct acpi_table_header)) + / item_size); + } + + /* Search RSDT/XSDT for the requested table */ + + for (i = 0; i < number_of_tables; ++i, table_data += item_size) { + if (osl_can_use_xsdt()) { + table_address = + (acpi_physical_address) (*ACPI_CAST64(table_data)); + } else { + table_address = + (acpi_physical_address) (*ACPI_CAST32(table_data)); + } + + /* Skip NULL entries in RSDT/XSDT */ + + if (!table_address) { + continue; + } + + status = osl_map_table(table_address, NULL, &mapped_table); + if (ACPI_FAILURE(status)) { + return (status); + } + + osl_add_table_to_list(mapped_table->signature, 0); + osl_unmap_table(mapped_table); + } + + return (AE_OK); +} + +/****************************************************************************** + * + * FUNCTION: osl_get_bios_table + * + * PARAMETERS: signature - ACPI Signature for common table. Must be + * a null terminated 4-character string. + * instance - Multiple table support for SSDT/UEFI (0...n) + * Must be 0 for other tables. + * table - Where a pointer to the table is returned + * address - Where the table physical address is returned + * + * RETURN: Status; Table buffer and physical address returned if AE_OK. + * AE_LIMIT: Instance is beyond valid limit + * AE_NOT_FOUND: A table with the signature was not found + * + * DESCRIPTION: Get a BIOS provided ACPI table + * + * NOTE: Assumes the input signature is uppercase. + * + *****************************************************************************/ + +static acpi_status +osl_get_bios_table(char *signature, + u32 instance, + struct acpi_table_header **table, + acpi_physical_address * address) +{ + struct acpi_table_header *local_table = NULL; + struct acpi_table_header *mapped_table = NULL; + u8 *table_data; + u8 number_of_tables; + u8 item_size; + u32 current_instance = 0; + acpi_physical_address table_address = 0; + u32 table_length = 0; + acpi_status status = AE_OK; + u32 i; + + /* Handle special tables whose addresses are not in RSDT/XSDT */ + + if (ACPI_COMPARE_NAME(signature, ACPI_RSDP_NAME) || + ACPI_COMPARE_NAME(signature, ACPI_SIG_RSDT) || + ACPI_COMPARE_NAME(signature, ACPI_SIG_XSDT) || + ACPI_COMPARE_NAME(signature, ACPI_SIG_DSDT) || + ACPI_COMPARE_NAME(signature, ACPI_SIG_FACS)) { + if (instance > 0) { + return (AE_LIMIT); + } + + /* + * Get the appropriate address, either 32-bit or 64-bit. Be very + * careful about the FADT length and validate table addresses. + * Note: The 64-bit addresses have priority. + */ + if (ACPI_COMPARE_NAME(signature, ACPI_SIG_DSDT)) { + if ((gbl_fadt->header.length >= MIN_FADT_FOR_XDSDT) && + gbl_fadt->Xdsdt) { + table_address = + (acpi_physical_address) gbl_fadt->Xdsdt; + } else + if ((gbl_fadt->header.length >= MIN_FADT_FOR_DSDT) + && gbl_fadt->dsdt) { + table_address = + (acpi_physical_address) gbl_fadt->dsdt; + } + } else if (ACPI_COMPARE_NAME(signature, ACPI_SIG_FACS)) { + if ((gbl_fadt->header.length >= MIN_FADT_FOR_XFACS) && + gbl_fadt->Xfacs) { + table_address = + (acpi_physical_address) gbl_fadt->Xfacs; + } else + if ((gbl_fadt->header.length >= MIN_FADT_FOR_FACS) + && gbl_fadt->facs) { + table_address = + (acpi_physical_address) gbl_fadt->facs; + } + } else if (ACPI_COMPARE_NAME(signature, ACPI_SIG_XSDT)) { + if (!gbl_revision) { + return (AE_BAD_SIGNATURE); + } + table_address = + (acpi_physical_address) gbl_rsdp. + xsdt_physical_address; + } else if (ACPI_COMPARE_NAME(signature, ACPI_SIG_RSDT)) { + table_address = + (acpi_physical_address) gbl_rsdp. + rsdt_physical_address; + } else { + table_address = + (acpi_physical_address) gbl_rsdp_address; + signature = ACPI_SIG_RSDP; + } + + /* Now we can get the requested special table */ + + status = osl_map_table(table_address, signature, &mapped_table); + if (ACPI_FAILURE(status)) { + return (status); + } + + table_length = ap_get_table_length(mapped_table); + } else { /* Case for a normal ACPI table */ + + if (osl_can_use_xsdt()) { + item_size = sizeof(u64); + table_data = + ACPI_CAST8(gbl_xsdt) + + sizeof(struct acpi_table_header); + number_of_tables = + (u8)((gbl_xsdt->header.length - + sizeof(struct acpi_table_header)) + / item_size); + } else { /* Use RSDT if XSDT is not available */ + + item_size = sizeof(u32); + table_data = + ACPI_CAST8(gbl_rsdt) + + sizeof(struct acpi_table_header); + number_of_tables = + (u8)((gbl_rsdt->header.length - + sizeof(struct acpi_table_header)) + / item_size); + } + + /* Search RSDT/XSDT for the requested table */ + + for (i = 0; i < number_of_tables; ++i, table_data += item_size) { + if (osl_can_use_xsdt()) { + table_address = + (acpi_physical_address) (*ACPI_CAST64 + (table_data)); + } else { + table_address = + (acpi_physical_address) (*ACPI_CAST32 + (table_data)); + } + + /* Skip NULL entries in RSDT/XSDT */ + + if (!table_address) { + continue; + } + + status = + osl_map_table(table_address, NULL, &mapped_table); + if (ACPI_FAILURE(status)) { + return (status); + } + table_length = mapped_table->length; + + /* Does this table match the requested signature? */ + + if (!ACPI_COMPARE_NAME + (mapped_table->signature, signature)) { + osl_unmap_table(mapped_table); + mapped_table = NULL; + continue; + } + + /* Match table instance (for SSDT/UEFI tables) */ + + if (current_instance != instance) { + osl_unmap_table(mapped_table); + mapped_table = NULL; + current_instance++; + continue; + } + + break; + } + } + + if (!mapped_table) { + return (AE_LIMIT); + } + + if (table_length == 0) { + status = AE_BAD_HEADER; + goto exit; + } + + /* Copy table to local buffer and return it */ + + local_table = calloc(1, table_length); + if (!local_table) { + status = AE_NO_MEMORY; + goto exit; + } + + ACPI_MEMCPY(local_table, mapped_table, table_length); + *address = table_address; + *table = local_table; + +exit: + osl_unmap_table(mapped_table); + return (status); +} + +/****************************************************************************** + * + * FUNCTION: osl_list_customized_tables + * + * PARAMETERS: directory - Directory that contains the tables + * + * RETURN: Status; Table list is initialized if AE_OK. + * + * DESCRIPTION: Add ACPI tables to the table list from a directory. + * + *****************************************************************************/ + +static acpi_status osl_list_customized_tables(char *directory) +{ + void *table_dir; + u32 instance; + char temp_name[ACPI_NAME_SIZE]; + char *filename; + acpi_status status = AE_OK; + + /* Open the requested directory */ + + table_dir = acpi_os_open_directory(directory, "*", REQUEST_FILE_ONLY); + if (!table_dir) { + return (osl_get_last_status(AE_NOT_FOUND)); + } + + /* Examine all entries in this directory */ + + while ((filename = acpi_os_get_next_filename(table_dir))) { + + /* Extract table name and instance number */ + + status = + osl_table_name_from_file(filename, temp_name, &instance); + + /* Ignore meaningless files */ + + if (ACPI_FAILURE(status)) { + continue; + } + + /* Add new info node to global table list */ + + status = osl_add_table_to_list(temp_name, instance); + if (ACPI_FAILURE(status)) { + break; + } + } + + acpi_os_close_directory(table_dir); + return (status); +} + +/****************************************************************************** + * + * FUNCTION: osl_map_table + * + * PARAMETERS: address - Address of the table in memory + * signature - Optional ACPI Signature for desired table. + * Null terminated 4-character string. + * table - Where a pointer to the mapped table is + * returned + * + * RETURN: Status; Mapped table is returned if AE_OK. + * AE_NOT_FOUND: A valid table was not found at the address + * + * DESCRIPTION: Map entire ACPI table into caller's address space. + * + *****************************************************************************/ + +static acpi_status +osl_map_table(acpi_size address, + char *signature, struct acpi_table_header **table) +{ + struct acpi_table_header *mapped_table; + u32 length; + + if (!address) { + return (AE_BAD_ADDRESS); + } + + /* + * Map the header so we can get the table length. + * Use sizeof (struct acpi_table_header) as: + * 1. it is bigger than 24 to include RSDP->Length + * 2. it is smaller than sizeof (struct acpi_table_rsdp) + */ + mapped_table = + acpi_os_map_memory(address, sizeof(struct acpi_table_header)); + if (!mapped_table) { + fprintf(stderr, "Could not map table header at 0x%8.8X%8.8X\n", + ACPI_FORMAT_UINT64(address)); + return (osl_get_last_status(AE_BAD_ADDRESS)); + } + + /* If specified, signature must match */ + + if (signature) { + if (ACPI_VALIDATE_RSDP_SIG(signature)) { + if (!ACPI_VALIDATE_RSDP_SIG(mapped_table->signature)) { + acpi_os_unmap_memory(mapped_table, + sizeof(struct + acpi_table_header)); + return (AE_BAD_SIGNATURE); + } + } else + if (!ACPI_COMPARE_NAME(signature, mapped_table->signature)) + { + acpi_os_unmap_memory(mapped_table, + sizeof(struct acpi_table_header)); + return (AE_BAD_SIGNATURE); + } + } + + /* Map the entire table */ + + length = ap_get_table_length(mapped_table); + acpi_os_unmap_memory(mapped_table, sizeof(struct acpi_table_header)); + if (length == 0) { + return (AE_BAD_HEADER); + } + + mapped_table = acpi_os_map_memory(address, length); + if (!mapped_table) { + fprintf(stderr, + "Could not map table at 0x%8.8X%8.8X length %8.8X\n", + ACPI_FORMAT_UINT64(address), length); + return (osl_get_last_status(AE_INVALID_TABLE_LENGTH)); + } + + (void)ap_is_valid_checksum(mapped_table); + + *table = mapped_table; + return (AE_OK); +} + +/****************************************************************************** + * + * FUNCTION: osl_unmap_table + * + * PARAMETERS: table - A pointer to the mapped table + * + * RETURN: None + * + * DESCRIPTION: Unmap entire ACPI table. + * + *****************************************************************************/ + +static void osl_unmap_table(struct acpi_table_header *table) +{ + if (table) { + acpi_os_unmap_memory(table, ap_get_table_length(table)); + } +} + +/****************************************************************************** + * + * FUNCTION: osl_table_name_from_file + * + * PARAMETERS: filename - File that contains the desired table + * signature - Pointer to 4-character buffer to store + * extracted table signature. + * instance - Pointer to integer to store extracted + * table instance number. + * + * RETURN: Status; Table name is extracted if AE_OK. + * + * DESCRIPTION: Extract table signature and instance number from a table file + * name. + * + *****************************************************************************/ + +static acpi_status +osl_table_name_from_file(char *filename, char *signature, u32 *instance) +{ + + /* Ignore meaningless files */ + + if (strlen(filename) < ACPI_NAME_SIZE) { + return (AE_BAD_SIGNATURE); + } + + /* Extract instance number */ + + if (isdigit((int)filename[ACPI_NAME_SIZE])) { + sscanf(&filename[ACPI_NAME_SIZE], "%d", instance); + } else if (strlen(filename) != ACPI_NAME_SIZE) { + return (AE_BAD_SIGNATURE); + } else { + *instance = 0; + } + + /* Extract signature */ + + ACPI_MOVE_NAME(signature, filename); + return (AE_OK); +} + +/****************************************************************************** + * + * FUNCTION: osl_read_table_from_file + * + * PARAMETERS: filename - File that contains the desired table + * file_offset - Offset of the table in file + * signature - Optional ACPI Signature for desired table. + * A null terminated 4-character string. + * table - Where a pointer to the table is returned + * + * RETURN: Status; Table buffer is returned if AE_OK. + * + * DESCRIPTION: Read a ACPI table from a file. + * + *****************************************************************************/ + +static acpi_status +osl_read_table_from_file(char *filename, + acpi_size file_offset, + char *signature, struct acpi_table_header **table) +{ + FILE *table_file; + struct acpi_table_header header; + struct acpi_table_header *local_table = NULL; + u32 table_length; + s32 count; + acpi_status status = AE_OK; + + /* Open the file */ + + table_file = fopen(filename, "rb"); + if (table_file == NULL) { + fprintf(stderr, "Could not open table file: %s\n", filename); + return (osl_get_last_status(AE_NOT_FOUND)); + } + + fseek(table_file, file_offset, SEEK_SET); + + /* Read the Table header to get the table length */ + + count = fread(&header, 1, sizeof(struct acpi_table_header), table_file); + if (count != sizeof(struct acpi_table_header)) { + fprintf(stderr, "Could not read table header: %s\n", filename); + status = AE_BAD_HEADER; + goto exit; + } + + /* If signature is specified, it must match the table */ + + if (signature) { + if (ACPI_VALIDATE_RSDP_SIG(signature)) { + if (!ACPI_VALIDATE_RSDP_SIG(header.signature)) { + fprintf(stderr, + "Incorrect RSDP signature: found %8.8s\n", + header.signature); + status = AE_BAD_SIGNATURE; + goto exit; + } + } else if (!ACPI_COMPARE_NAME(signature, header.signature)) { + fprintf(stderr, + "Incorrect signature: Expecting %4.4s, found %4.4s\n", + signature, header.signature); + status = AE_BAD_SIGNATURE; + goto exit; + } + } + + table_length = ap_get_table_length(&header); + if (table_length == 0) { + status = AE_BAD_HEADER; + goto exit; + } + + /* Read the entire table into a local buffer */ + + local_table = calloc(1, table_length); + if (!local_table) { + fprintf(stderr, + "%4.4s: Could not allocate buffer for table of length %X\n", + header.signature, table_length); + status = AE_NO_MEMORY; + goto exit; + } + + fseek(table_file, file_offset, SEEK_SET); + + count = fread(local_table, 1, table_length, table_file); + if (count != table_length) { + fprintf(stderr, "%4.4s: Could not read table content\n", + header.signature); + status = AE_INVALID_TABLE_LENGTH; + goto exit; + } + + /* Validate checksum */ + + (void)ap_is_valid_checksum(local_table); + +exit: + fclose(table_file); + *table = local_table; + return (status); +} + +/****************************************************************************** + * + * FUNCTION: osl_get_customized_table + * + * PARAMETERS: pathname - Directory to find Linux customized table + * signature - ACPI Signature for desired table. Must be + * a null terminated 4-character string. + * instance - Multiple table support for SSDT/UEFI (0...n) + * Must be 0 for other tables. + * table - Where a pointer to the table is returned + * address - Where the table physical address is returned + * + * RETURN: Status; Table buffer is returned if AE_OK. + * AE_LIMIT: Instance is beyond valid limit + * AE_NOT_FOUND: A table with the signature was not found + * + * DESCRIPTION: Get an OS customized table. + * + *****************************************************************************/ + +static acpi_status +osl_get_customized_table(char *pathname, + char *signature, + u32 instance, + struct acpi_table_header **table, + acpi_physical_address * address) +{ + void *table_dir; + u32 current_instance = 0; + char temp_name[ACPI_NAME_SIZE]; + char table_filename[PATH_MAX]; + char *filename; + acpi_status status; + + /* Open the directory for customized tables */ + + table_dir = acpi_os_open_directory(pathname, "*", REQUEST_FILE_ONLY); + if (!table_dir) { + return (osl_get_last_status(AE_NOT_FOUND)); + } + + /* Attempt to find the table in the directory */ + + while ((filename = acpi_os_get_next_filename(table_dir))) { + + /* Ignore meaningless files */ + + if (!ACPI_COMPARE_NAME(filename, signature)) { + continue; + } + + /* Extract table name and instance number */ + + status = + osl_table_name_from_file(filename, temp_name, + ¤t_instance); + + /* Ignore meaningless files */ + + if (ACPI_FAILURE(status) || current_instance != instance) { + continue; + } + + /* Create the table pathname */ + + if (instance != 0) { + sprintf(table_filename, "%s/%4.4s%d", pathname, + temp_name, instance); + } else { + sprintf(table_filename, "%s/%4.4s", pathname, + temp_name); + } + break; + } + + acpi_os_close_directory(table_dir); + + if (!filename) { + return (AE_LIMIT); + } + + /* There is no physical address saved for customized tables, use zero */ + + *address = 0; + status = osl_read_table_from_file(table_filename, 0, NULL, table); + + return (status); +} diff --git a/tools/power/acpi/os_specific/service_layers/osunixdir.c b/tools/power/acpi/os_specific/service_layers/osunixdir.c new file mode 100644 index 000000000000..733f9e490fc4 --- /dev/null +++ b/tools/power/acpi/os_specific/service_layers/osunixdir.c @@ -0,0 +1,204 @@ +/****************************************************************************** + * + * Module Name: osunixdir - Unix directory access interfaces + * + *****************************************************************************/ + +/* + * Copyright (C) 2000 - 2014, Intel Corp. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * Alternatively, this software may be distributed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + * NO WARRANTY + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + */ + +#include <acpi/acpi.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <dirent.h> +#include <fnmatch.h> +#include <ctype.h> +#include <sys/stat.h> + +/* + * Allocated structure returned from os_open_directory + */ +typedef struct external_find_info { + char *dir_pathname; + DIR *dir_ptr; + char temp_buffer[256]; + char *wildcard_spec; + char requested_file_type; + +} external_find_info; + +/******************************************************************************* + * + * FUNCTION: acpi_os_open_directory + * + * PARAMETERS: dir_pathname - Full pathname to the directory + * wildcard_spec - string of the form "*.c", etc. + * + * RETURN: A directory "handle" to be used in subsequent search operations. + * NULL returned on failure. + * + * DESCRIPTION: Open a directory in preparation for a wildcard search + * + ******************************************************************************/ + +void *acpi_os_open_directory(char *dir_pathname, + char *wildcard_spec, char requested_file_type) +{ + struct external_find_info *external_info; + DIR *dir; + + /* Allocate the info struct that will be returned to the caller */ + + external_info = calloc(1, sizeof(struct external_find_info)); + if (!external_info) { + return (NULL); + } + + /* Get the directory stream */ + + dir = opendir(dir_pathname); + if (!dir) { + fprintf(stderr, "Cannot open directory - %s\n", dir_pathname); + free(external_info); + return (NULL); + } + + /* Save the info in the return structure */ + + external_info->wildcard_spec = wildcard_spec; + external_info->requested_file_type = requested_file_type; + external_info->dir_pathname = dir_pathname; + external_info->dir_ptr = dir; + return (external_info); +} + +/******************************************************************************* + * + * FUNCTION: acpi_os_get_next_filename + * + * PARAMETERS: dir_handle - Created via acpi_os_open_directory + * + * RETURN: Next filename matched. NULL if no more matches. + * + * DESCRIPTION: Get the next file in the directory that matches the wildcard + * specification. + * + ******************************************************************************/ + +char *acpi_os_get_next_filename(void *dir_handle) +{ + struct external_find_info *external_info = dir_handle; + struct dirent *dir_entry; + char *temp_str; + int str_len; + struct stat temp_stat; + int err; + + while ((dir_entry = readdir(external_info->dir_ptr))) { + if (!fnmatch + (external_info->wildcard_spec, dir_entry->d_name, 0)) { + if (dir_entry->d_name[0] == '.') { + continue; + } + + str_len = strlen(dir_entry->d_name) + + strlen(external_info->dir_pathname) + 2; + + temp_str = calloc(str_len, 1); + if (!temp_str) { + fprintf(stderr, + "Could not allocate buffer for temporary string\n"); + return (NULL); + } + + strcpy(temp_str, external_info->dir_pathname); + strcat(temp_str, "/"); + strcat(temp_str, dir_entry->d_name); + + err = stat(temp_str, &temp_stat); + if (err == -1) { + fprintf(stderr, + "Cannot stat file (should not happen) - %s\n", + temp_str); + free(temp_str); + return (NULL); + } + + free(temp_str); + + if ((S_ISDIR(temp_stat.st_mode) + && (external_info->requested_file_type == + REQUEST_DIR_ONLY)) + || ((!S_ISDIR(temp_stat.st_mode) + && external_info->requested_file_type == + REQUEST_FILE_ONLY))) { + + /* copy to a temp buffer because dir_entry struct is on the stack */ + + strcpy(external_info->temp_buffer, + dir_entry->d_name); + return (external_info->temp_buffer); + } + } + } + + return (NULL); +} + +/******************************************************************************* + * + * FUNCTION: acpi_os_close_directory + * + * PARAMETERS: dir_handle - Created via acpi_os_open_directory + * + * RETURN: None. + * + * DESCRIPTION: Close the open directory and cleanup. + * + ******************************************************************************/ + +void acpi_os_close_directory(void *dir_handle) +{ + struct external_find_info *external_info = dir_handle; + + /* Close the directory and free allocations */ + + closedir(external_info->dir_ptr); + free(dir_handle); +} diff --git a/tools/power/acpi/os_specific/service_layers/osunixmap.c b/tools/power/acpi/os_specific/service_layers/osunixmap.c new file mode 100644 index 000000000000..99b47b6194a3 --- /dev/null +++ b/tools/power/acpi/os_specific/service_layers/osunixmap.c @@ -0,0 +1,151 @@ +/****************************************************************************** + * + * Module Name: osunixmap - Unix OSL for file mappings + * + *****************************************************************************/ + +/* + * Copyright (C) 2000 - 2014, Intel Corp. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * Alternatively, this software may be distributed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + * NO WARRANTY + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + */ + +#include "acpidump.h" +#include <unistd.h> +#include <sys/mman.h> +#ifdef _free_BSD +#include <sys/param.h> +#endif + +#define _COMPONENT ACPI_OS_SERVICES +ACPI_MODULE_NAME("osunixmap") + +#ifndef O_BINARY +#define O_BINARY 0 +#endif +#ifdef _free_BSD +#define MMAP_FLAGS MAP_SHARED +#else +#define MMAP_FLAGS MAP_PRIVATE +#endif +#define SYSTEM_MEMORY "/dev/mem" +/******************************************************************************* + * + * FUNCTION: acpi_os_get_page_size + * + * PARAMETERS: None + * + * RETURN: Page size of the platform. + * + * DESCRIPTION: Obtain page size of the platform. + * + ******************************************************************************/ +static acpi_size acpi_os_get_page_size(void) +{ + +#ifdef PAGE_SIZE + return PAGE_SIZE; +#else + return sysconf(_SC_PAGESIZE); +#endif +} + +/****************************************************************************** + * + * FUNCTION: acpi_os_map_memory + * + * PARAMETERS: where - Physical address of memory to be mapped + * length - How much memory to map + * + * RETURN: Pointer to mapped memory. Null on error. + * + * DESCRIPTION: Map physical memory into local address space. + * + *****************************************************************************/ + +void *acpi_os_map_memory(acpi_physical_address where, acpi_size length) +{ + u8 *mapped_memory; + acpi_physical_address offset; + acpi_size page_size; + int fd; + + fd = open(SYSTEM_MEMORY, O_RDONLY | O_BINARY); + if (fd < 0) { + fprintf(stderr, "Cannot open %s\n", SYSTEM_MEMORY); + return (NULL); + } + + /* Align the offset to use mmap */ + + page_size = acpi_os_get_page_size(); + offset = where % page_size; + + /* Map the table header to get the length of the full table */ + + mapped_memory = mmap(NULL, (length + offset), PROT_READ, MMAP_FLAGS, + fd, (where - offset)); + if (mapped_memory == MAP_FAILED) { + fprintf(stderr, "Cannot map %s\n", SYSTEM_MEMORY); + close(fd); + return (NULL); + } + + close(fd); + return (ACPI_CAST8(mapped_memory + offset)); +} + +/****************************************************************************** + * + * FUNCTION: acpi_os_unmap_memory + * + * PARAMETERS: where - Logical address of memory to be unmapped + * length - How much memory to unmap + * + * RETURN: None. + * + * DESCRIPTION: Delete a previously created mapping. Where and Length must + * correspond to a previous mapping exactly. + * + *****************************************************************************/ + +void acpi_os_unmap_memory(void *where, acpi_size length) +{ + acpi_physical_address offset; + acpi_size page_size; + + page_size = acpi_os_get_page_size(); + offset = (acpi_physical_address) where % page_size; + munmap((u8 *)where - offset, (length + offset)); +} diff --git a/tools/power/acpi/tools/acpidump/acpidump.c b/tools/power/acpi/tools/acpidump/acpidump.c deleted file mode 100644 index a84553a0e0df..000000000000 --- a/tools/power/acpi/tools/acpidump/acpidump.c +++ /dev/null @@ -1,559 +0,0 @@ -/* - * (c) Alexey Starikovskiy, Intel, 2005-2006. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions, and the following disclaimer, - * without modification. - * 2. Redistributions in binary form must reproduce at minimum a disclaimer - * substantially similar to the "NO WARRANTY" disclaimer below - * ("Disclaimer") and any redistribution must be conditioned upon - * including a substantially similar Disclaimer requirement for further - * binary redistribution. - * 3. Neither the names of the above-listed copyright holders nor the names - * of any contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * Alternatively, this software may be distributed under the terms of the - * GNU General Public License ("GPL") version 2 as published by the Free - * Software Foundation. - * - * NO WARRANTY - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING - * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGES. - */ - -#ifdef DEFINE_ALTERNATE_TYPES -/* hack to enable building old application with new headers -lenb */ -#define acpi_fadt_descriptor acpi_table_fadt -#define acpi_rsdp_descriptor acpi_table_rsdp -#define DSDT_SIG ACPI_SIG_DSDT -#define FACS_SIG ACPI_SIG_FACS -#define FADT_SIG ACPI_SIG_FADT -#define xfirmware_ctrl Xfacs -#define firmware_ctrl facs - -typedef int s32; -typedef unsigned char u8; -typedef unsigned short u16; -typedef unsigned int u32; -typedef unsigned long long u64; -typedef long long s64; -#endif - -#include <sys/mman.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <fcntl.h> -#include <stdio.h> -#include <string.h> -#include <unistd.h> -#include <getopt.h> - -#include <dirent.h> - -#include <acpi/acconfig.h> -#include <acpi/platform/acenv.h> -#include <acpi/actypes.h> -#include <acpi/actbl.h> - -static inline u8 checksum(u8 * buffer, u32 length) -{ - u8 sum = 0, *i = buffer; - buffer += length; - for (; i < buffer; sum += *(i++)); - return sum; -} - -static unsigned long psz, addr, length; -static int print, connect, skip; -static u8 select_sig[4]; - -static unsigned long read_efi_systab( void ) -{ - char buffer[80]; - unsigned long addr; - FILE *f = fopen("/sys/firmware/efi/systab", "r"); - if (f) { - while (fgets(buffer, 80, f)) { - if (sscanf(buffer, "ACPI20=0x%lx", &addr) == 1) - return addr; - } - fclose(f); - } - return 0; -} - -static u8 *acpi_map_memory(unsigned long where, unsigned length) -{ - unsigned long offset; - u8 *there; - int fd = open("/dev/mem", O_RDONLY); - if (fd < 0) { - fprintf(stderr, "acpi_os_map_memory: cannot open /dev/mem\n"); - exit(1); - } - offset = where % psz; - there = mmap(NULL, length + offset, PROT_READ, MAP_PRIVATE, - fd, where - offset); - close(fd); - if (there == MAP_FAILED) return 0; - return (there + offset); -} - -static void acpi_unmap_memory(u8 * there, unsigned length) -{ - unsigned long offset = (unsigned long)there % psz; - munmap(there - offset, length + offset); -} - -static struct acpi_table_header *acpi_map_table(unsigned long where, char *sig) -{ - unsigned size; - struct acpi_table_header *tbl = (struct acpi_table_header *) - acpi_map_memory(where, sizeof(struct acpi_table_header)); - if (!tbl || (sig && memcmp(sig, tbl->signature, 4))) return 0; - size = tbl->length; - acpi_unmap_memory((u8 *) tbl, sizeof(struct acpi_table_header)); - return (struct acpi_table_header *)acpi_map_memory(where, size); -} - -static void acpi_unmap_table(struct acpi_table_header *tbl) -{ - acpi_unmap_memory((u8 *)tbl, tbl->length); -} - -static struct acpi_rsdp_descriptor *acpi_scan_for_rsdp(u8 *begin, u32 length) -{ - struct acpi_rsdp_descriptor *rsdp; - u8 *i, *end = begin + length; - /* Search from given start address for the requested length */ - for (i = begin; i < end; i += ACPI_RSDP_SCAN_STEP) { - /* The signature and checksum must both be correct */ - if (memcmp((char *)i, "RSD PTR ", 8)) continue; - rsdp = (struct acpi_rsdp_descriptor *)i; - /* Signature matches, check the appropriate checksum */ - if (!checksum((u8 *) rsdp, (rsdp->revision < 2) ? - ACPI_RSDP_CHECKSUM_LENGTH : - ACPI_RSDP_XCHECKSUM_LENGTH)) - /* Checksum valid, we have found a valid RSDP */ - return rsdp; - } - /* Searched entire block, no RSDP was found */ - return 0; -} - -/* - * Output data - */ -static void acpi_show_data(int fd, u8 * data, int size) -{ - char buffer[256]; - int len; - int i, remain = size; - while (remain > 0) { - len = snprintf(buffer, 256, " %04x:", size - remain); - for (i = 0; i < 16 && i < remain; i++) { - len += - snprintf(&buffer[len], 256 - len, " %02x", data[i]); - } - for (; i < 16; i++) { - len += snprintf(&buffer[len], 256 - len, " "); - } - len += snprintf(&buffer[len], 256 - len, " "); - for (i = 0; i < 16 && i < remain; i++) { - buffer[len++] = (isprint(data[i])) ? data[i] : '.'; - } - buffer[len++] = '\n'; - write(fd, buffer, len); - data += 16; - remain -= 16; - } -} - -/* - * Output ACPI table - */ -static void acpi_show_table(int fd, struct acpi_table_header *table, unsigned long addr) -{ - char buff[80]; - int len = snprintf(buff, 80, "%.4s @ %p\n", table->signature, (void *)addr); - write(fd, buff, len); - acpi_show_data(fd, (u8 *) table, table->length); - buff[0] = '\n'; - write(fd, buff, 1); -} - -static void write_table(int fd, struct acpi_table_header *tbl, unsigned long addr) -{ - static int select_done = 0; - if (!select_sig[0]) { - if (print) { - acpi_show_table(fd, tbl, addr); - } else { - write(fd, tbl, tbl->length); - } - } else if (!select_done && !memcmp(select_sig, tbl->signature, 4)) { - if (skip > 0) { - --skip; - return; - } - if (print) { - acpi_show_table(fd, tbl, addr); - } else { - write(fd, tbl, tbl->length); - } - select_done = 1; - } -} - -static void acpi_dump_FADT(int fd, struct acpi_table_header *tbl, unsigned long xaddr) { - struct acpi_fadt_descriptor x; - unsigned long addr; - size_t len = sizeof(struct acpi_fadt_descriptor); - if (len > tbl->length) len = tbl->length; - memcpy(&x, tbl, len); - x.header.length = len; - if (checksum((u8 *)tbl, len)) { - fprintf(stderr, "Wrong checksum for FADT!\n"); - } - if (x.header.length >= 148 && x.Xdsdt) { - addr = (unsigned long)x.Xdsdt; - if (connect) { - x.Xdsdt = lseek(fd, 0, SEEK_CUR); - } - } else if (x.header.length >= 44 && x.dsdt) { - addr = (unsigned long)x.dsdt; - if (connect) { - x.dsdt = lseek(fd, 0, SEEK_CUR); - } - } else { - fprintf(stderr, "No DSDT in FADT!\n"); - goto no_dsdt; - } - tbl = acpi_map_table(addr, DSDT_SIG); - if (!tbl) goto no_dsdt; - if (checksum((u8 *)tbl, tbl->length)) - fprintf(stderr, "Wrong checksum for DSDT!\n"); - write_table(fd, tbl, addr); - acpi_unmap_table(tbl); -no_dsdt: - if (x.header.length >= 140 && x.xfirmware_ctrl) { - addr = (unsigned long)x.xfirmware_ctrl; - if (connect) { - x.xfirmware_ctrl = lseek(fd, 0, SEEK_CUR); - } - } else if (x.header.length >= 40 && x.firmware_ctrl) { - addr = (unsigned long)x.firmware_ctrl; - if (connect) { - x.firmware_ctrl = lseek(fd, 0, SEEK_CUR); - } - } else { - fprintf(stderr, "No FACS in FADT!\n"); - goto no_facs; - } - tbl = acpi_map_table(addr, FACS_SIG); - if (!tbl) goto no_facs; - /* do not checksum FACS */ - write_table(fd, tbl, addr); - acpi_unmap_table(tbl); -no_facs: - write_table(fd, (struct acpi_table_header *)&x, xaddr); -} - -static int acpi_dump_SDT(int fd, struct acpi_rsdp_descriptor *rsdp) -{ - struct acpi_table_header *sdt, *tbl = 0; - int xsdt = 1, i, num; - char *offset; - unsigned long addr; - if (rsdp->revision > 1 && rsdp->xsdt_physical_address) { - tbl = acpi_map_table(rsdp->xsdt_physical_address, "XSDT"); - } - if (!tbl && rsdp->rsdt_physical_address) { - xsdt = 0; - tbl = acpi_map_table(rsdp->rsdt_physical_address, "RSDT"); - } - if (!tbl) return 0; - sdt = malloc(tbl->length); - memcpy(sdt, tbl, tbl->length); - acpi_unmap_table(tbl); - if (checksum((u8 *)sdt, sdt->length)) - fprintf(stderr, "Wrong checksum for %s!\n", (xsdt)?"XSDT":"RSDT"); - num = (sdt->length - sizeof(struct acpi_table_header))/((xsdt)?sizeof(u64):sizeof(u32)); - offset = (char *)sdt + sizeof(struct acpi_table_header); - for (i = 0; i < num; ++i, offset += ((xsdt) ? sizeof(u64) : sizeof(u32))) { - addr = (xsdt) ? (unsigned long)(*(u64 *)offset): - (unsigned long)(*(u32 *)offset); - if (!addr) continue; - tbl = acpi_map_table(addr, 0); - if (!tbl) continue; - if (!memcmp(tbl->signature, FADT_SIG, 4)) { - acpi_dump_FADT(fd, tbl, addr); - } else { - if (checksum((u8 *)tbl, tbl->length)) - fprintf(stderr, "Wrong checksum for generic table!\n"); - write_table(fd, tbl, addr); - } - acpi_unmap_table(tbl); - if (connect) { - if (xsdt) - (*(u64*)offset) = lseek(fd, 0, SEEK_CUR); - else - (*(u32*)offset) = lseek(fd, 0, SEEK_CUR); - } - } - if (xsdt) { - addr = (unsigned long)rsdp->xsdt_physical_address; - if (connect) { - rsdp->xsdt_physical_address = lseek(fd, 0, SEEK_CUR); - } - } else { - addr = (unsigned long)rsdp->rsdt_physical_address; - if (connect) { - rsdp->rsdt_physical_address = lseek(fd, 0, SEEK_CUR); - } - } - write_table(fd, sdt, addr); - free (sdt); - return 1; -} - -#define DYNAMIC_SSDT "/sys/firmware/acpi/tables/dynamic" - -static void acpi_dump_dynamic_SSDT(int fd) -{ - struct stat file_stat; - char filename[256], *ptr; - DIR *tabledir; - struct dirent *entry; - FILE *fp; - int count, readcount, length; - struct acpi_table_header table_header, *ptable; - - if (stat(DYNAMIC_SSDT, &file_stat) == -1) { - /* The directory doesn't exist */ - return; - } - tabledir = opendir(DYNAMIC_SSDT); - if(!tabledir){ - /*can't open the directory */ - return; - } - - while ((entry = readdir(tabledir)) != 0){ - /* skip the file of . /.. */ - if (entry->d_name[0] == '.') - continue; - - sprintf(filename, "%s/%s", DYNAMIC_SSDT, entry->d_name); - fp = fopen(filename, "r"); - if (fp == NULL) { - fprintf(stderr, "Can't open the file of %s\n", - filename); - continue; - } - /* Read the Table header to parse the table length */ - count = fread(&table_header, 1, sizeof(struct acpi_table_header), fp); - if (count < sizeof(table_header)) { - /* the length is lessn than ACPI table header. skip it */ - fclose(fp); - continue; - } - length = table_header.length; - ptr = malloc(table_header.length); - fseek(fp, 0, SEEK_SET); - readcount = 0; - while(!feof(fp) && readcount < length) { - count = fread(ptr + readcount, 1, 256, fp); - readcount += count; - } - fclose(fp); - ptable = (struct acpi_table_header *) ptr; - if (checksum((u8 *) ptable, ptable->length)) - fprintf(stderr, "Wrong checksum " - "for dynamic SSDT table!\n"); - write_table(fd, ptable, 0); - free(ptr); - } - closedir(tabledir); - return; -} - -static void usage(const char *progname) -{ - puts("Usage:"); - printf("%s [--addr 0x1234][--table DSDT][--output filename]" - "[--binary][--length 0x456][--help]\n", progname); - puts("\t--addr 0x1234 or -a 0x1234 -- look for tables at this physical address"); - puts("\t--table DSDT or -t DSDT -- only dump table with DSDT signature"); - puts("\t--output filename or -o filename -- redirect output from stdin to filename"); - puts("\t--binary or -b -- dump data in binary form rather than in hex-dump format"); - puts("\t--length 0x456 or -l 0x456 -- works only with --addr, dump physical memory" - "\n\t\tregion without trying to understand it's contents"); - puts("\t--skip 2 or -s 2 -- skip 2 tables of the given name and output only 3rd one"); - puts("\t--help or -h -- this help message"); - exit(0); -} - -static struct option long_options[] = { - {"addr", 1, 0, 0}, - {"table", 1, 0, 0}, - {"output", 1, 0, 0}, - {"binary", 0, 0, 0}, - {"length", 1, 0, 0}, - {"skip", 1, 0, 0}, - {"help", 0, 0, 0}, - {0, 0, 0, 0} -}; -int main(int argc, char **argv) -{ - int option_index, c, fd; - u8 *raw; - struct acpi_rsdp_descriptor rsdpx, *x = 0; - char *filename = 0; - char buff[80]; - memset(select_sig, 0, 4); - print = 1; - connect = 0; - addr = length = 0; - skip = 0; - while (1) { - option_index = 0; - c = getopt_long(argc, argv, "a:t:o:bl:s:h", - long_options, &option_index); - if (c == -1) - break; - - switch (c) { - case 0: - switch (option_index) { - case 0: - addr = strtoul(optarg, (char **)NULL, 16); - break; - case 1: - memcpy(select_sig, optarg, 4); - break; - case 2: - filename = optarg; - break; - case 3: - print = 0; - break; - case 4: - length = strtoul(optarg, (char **)NULL, 16); - break; - case 5: - skip = strtoul(optarg, (char **)NULL, 10); - break; - case 6: - usage(argv[0]); - exit(0); - } - break; - case 'a': - addr = strtoul(optarg, (char **)NULL, 16); - break; - case 't': - memcpy(select_sig, optarg, 4); - break; - case 'o': - filename = optarg; - break; - case 'b': - print = 0; - break; - case 'l': - length = strtoul(optarg, (char **)NULL, 16); - break; - case 's': - skip = strtoul(optarg, (char **)NULL, 10); - break; - case 'h': - usage(argv[0]); - exit(0); - default: - printf("Unknown option!\n"); - usage(argv[0]); - exit(0); - } - } - - fd = STDOUT_FILENO; - if (filename) { - fd = creat(filename, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); - if (fd < 0) - return fd; - } - - if (!select_sig[0] && !print) { - connect = 1; - } - - psz = sysconf(_SC_PAGESIZE); - if (length && addr) { - /* We know length and address, it means we just want a memory dump */ - if (!(raw = acpi_map_memory(addr, length))) - goto not_found; - write(fd, raw, length); - acpi_unmap_memory(raw, length); - close(fd); - return 0; - } - - length = sizeof(struct acpi_rsdp_descriptor); - if (!addr) { - addr = read_efi_systab(); - if (!addr) { - addr = ACPI_HI_RSDP_WINDOW_BASE; - length = ACPI_HI_RSDP_WINDOW_SIZE; - } - } - - if (!(raw = acpi_map_memory(addr, length)) || - !(x = acpi_scan_for_rsdp(raw, length))) - goto not_found; - - /* Find RSDP and print all found tables */ - memcpy(&rsdpx, x, sizeof(struct acpi_rsdp_descriptor)); - acpi_unmap_memory(raw, length); - if (connect) { - lseek(fd, sizeof(struct acpi_rsdp_descriptor), SEEK_SET); - } - if (!acpi_dump_SDT(fd, &rsdpx)) - goto not_found; - if (connect) { - lseek(fd, 0, SEEK_SET); - write(fd, x, (rsdpx.revision < 2) ? - ACPI_RSDP_CHECKSUM_LENGTH : ACPI_RSDP_XCHECKSUM_LENGTH); - } else if (!select_sig[0] || !memcmp("RSD PTR ", select_sig, 4)) { - addr += (long)x - (long)raw; - length = snprintf(buff, 80, "RSD PTR @ %p\n", (void *)addr); - write(fd, buff, length); - acpi_show_data(fd, (u8 *) & rsdpx, (rsdpx.revision < 2) ? - ACPI_RSDP_CHECKSUM_LENGTH : ACPI_RSDP_XCHECKSUM_LENGTH); - buff[0] = '\n'; - write(fd, buff, 1); - } - acpi_dump_dynamic_SSDT(fd); - close(fd); - return 0; -not_found: - close(fd); - fprintf(stderr, "ACPI tables were not found. If you know location " - "of RSD PTR table (from dmesg, etc), " - "supply it with either --addr or -a option\n"); - return 1; -} diff --git a/tools/power/acpi/tools/acpidump/acpidump.h b/tools/power/acpi/tools/acpidump/acpidump.h new file mode 100644 index 000000000000..46f519597fe5 --- /dev/null +++ b/tools/power/acpi/tools/acpidump/acpidump.h @@ -0,0 +1,130 @@ +/****************************************************************************** + * + * Module Name: acpidump.h - Include file for acpi_dump utility + * + *****************************************************************************/ + +/* + * Copyright (C) 2000 - 2014, Intel Corp. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * Alternatively, this software may be distributed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + * NO WARRANTY + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + */ + +/* + * Global variables. Defined in main.c only, externed in all other files + */ +#ifdef _DECLARE_GLOBALS +#define EXTERN +#define INIT_GLOBAL(a,b) a=b +#define DEFINE_ACPI_GLOBALS 1 +#else +#define EXTERN extern +#define INIT_GLOBAL(a,b) a +#endif + +#include <acpi/acpi.h> +#include "accommon.h" +#include "actables.h" + +#include <stdio.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/stat.h> + +/* Globals */ + +EXTERN u8 INIT_GLOBAL(gbl_summary_mode, FALSE); +EXTERN u8 INIT_GLOBAL(gbl_verbose_mode, FALSE); +EXTERN u8 INIT_GLOBAL(gbl_binary_mode, FALSE); +EXTERN u8 INIT_GLOBAL(gbl_dump_customized_tables, FALSE); +EXTERN u8 INIT_GLOBAL(gbl_do_not_dump_xsdt, FALSE); +EXTERN FILE INIT_GLOBAL(*gbl_output_file, NULL); +EXTERN char INIT_GLOBAL(*gbl_output_filename, NULL); +EXTERN u64 INIT_GLOBAL(gbl_rsdp_base, 0); + +/* Globals required for use with ACPICA modules */ + +#ifdef _DECLARE_GLOBALS +u8 acpi_gbl_integer_byte_width = 8; +#endif + +/* Action table used to defer requested options */ + +struct ap_dump_action { + char *argument; + u32 to_be_done; +}; + +#define AP_MAX_ACTIONS 32 + +#define AP_DUMP_ALL_TABLES 0 +#define AP_DUMP_TABLE_BY_ADDRESS 1 +#define AP_DUMP_TABLE_BY_NAME 2 +#define AP_DUMP_TABLE_BY_FILE 3 + +#define AP_MAX_ACPI_FILES 256 /* Prevent infinite loops */ + +/* Minimum FADT sizes for various table addresses */ + +#define MIN_FADT_FOR_DSDT (ACPI_FADT_OFFSET (dsdt) + sizeof (u32)) +#define MIN_FADT_FOR_FACS (ACPI_FADT_OFFSET (facs) + sizeof (u32)) +#define MIN_FADT_FOR_XDSDT (ACPI_FADT_OFFSET (Xdsdt) + sizeof (u64)) +#define MIN_FADT_FOR_XFACS (ACPI_FADT_OFFSET (Xfacs) + sizeof (u64)) + +/* + * apdump - Table get/dump routines + */ +int ap_dump_table_from_file(char *pathname); + +int ap_dump_table_by_name(char *signature); + +int ap_dump_table_by_address(char *ascii_address); + +int ap_dump_all_tables(void); + +u8 ap_is_valid_header(struct acpi_table_header *table); + +u8 ap_is_valid_checksum(struct acpi_table_header *table); + +u32 ap_get_table_length(struct acpi_table_header *table); + +/* + * apfiles - File I/O utilities + */ +int ap_open_output_file(char *pathname); + +int ap_write_to_binary_file(struct acpi_table_header *table, u32 instance); + +struct acpi_table_header *ap_get_table_from_file(char *pathname, + u32 *file_size); diff --git a/tools/power/acpi/tools/acpidump/apdump.c b/tools/power/acpi/tools/acpidump/apdump.c new file mode 100644 index 000000000000..3cac12378366 --- /dev/null +++ b/tools/power/acpi/tools/acpidump/apdump.c @@ -0,0 +1,451 @@ +/****************************************************************************** + * + * Module Name: apdump - Dump routines for ACPI tables (acpidump) + * + *****************************************************************************/ + +/* + * Copyright (C) 2000 - 2014, Intel Corp. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * Alternatively, this software may be distributed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + * NO WARRANTY + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + */ + +#include "acpidump.h" + +/* Local prototypes */ + +static int +ap_dump_table_buffer(struct acpi_table_header *table, + u32 instance, acpi_physical_address address); + +/****************************************************************************** + * + * FUNCTION: ap_is_valid_header + * + * PARAMETERS: table - Pointer to table to be validated + * + * RETURN: TRUE if the header appears to be valid. FALSE otherwise + * + * DESCRIPTION: Check for a valid ACPI table header + * + ******************************************************************************/ + +u8 ap_is_valid_header(struct acpi_table_header *table) +{ + + if (!ACPI_VALIDATE_RSDP_SIG(table->signature)) { + + /* Make sure signature is all ASCII and a valid ACPI name */ + + if (!acpi_ut_valid_acpi_name(table->signature)) { + fprintf(stderr, + "Table signature (0x%8.8X) is invalid\n", + *(u32 *)table->signature); + return (FALSE); + } + + /* Check for minimum table length */ + + if (table->length < sizeof(struct acpi_table_header)) { + fprintf(stderr, "Table length (0x%8.8X) is invalid\n", + table->length); + return (FALSE); + } + } + + return (TRUE); +} + +/****************************************************************************** + * + * FUNCTION: ap_is_valid_checksum + * + * PARAMETERS: table - Pointer to table to be validated + * + * RETURN: TRUE if the checksum appears to be valid. FALSE otherwise. + * + * DESCRIPTION: Check for a valid ACPI table checksum. + * + ******************************************************************************/ + +u8 ap_is_valid_checksum(struct acpi_table_header *table) +{ + acpi_status status; + struct acpi_table_rsdp *rsdp; + + if (ACPI_VALIDATE_RSDP_SIG(table->signature)) { + /* + * Checksum for RSDP. + * Note: Other checksums are computed during the table dump. + */ + rsdp = ACPI_CAST_PTR(struct acpi_table_rsdp, table); + status = acpi_tb_validate_rsdp(rsdp); + } else { + status = acpi_tb_verify_checksum(table, table->length); + } + + if (ACPI_FAILURE(status)) { + fprintf(stderr, "%4.4s: Warning: wrong checksum in table\n", + table->signature); + } + + return (AE_OK); +} + +/****************************************************************************** + * + * FUNCTION: ap_get_table_length + * + * PARAMETERS: table - Pointer to the table + * + * RETURN: Table length + * + * DESCRIPTION: Obtain table length according to table signature. + * + ******************************************************************************/ + +u32 ap_get_table_length(struct acpi_table_header *table) +{ + struct acpi_table_rsdp *rsdp; + + /* Check if table is valid */ + + if (!ap_is_valid_header(table)) { + return (0); + } + + if (ACPI_VALIDATE_RSDP_SIG(table->signature)) { + rsdp = ACPI_CAST_PTR(struct acpi_table_rsdp, table); + return (rsdp->length); + } + + /* Normal ACPI table */ + + return (table->length); +} + +/****************************************************************************** + * + * FUNCTION: ap_dump_table_buffer + * + * PARAMETERS: table - ACPI table to be dumped + * instance - ACPI table instance no. to be dumped + * address - Physical address of the table + * + * RETURN: None + * + * DESCRIPTION: Dump an ACPI table in standard ASCII hex format, with a + * header that is compatible with the acpi_xtract utility. + * + ******************************************************************************/ + +static int +ap_dump_table_buffer(struct acpi_table_header *table, + u32 instance, acpi_physical_address address) +{ + u32 table_length; + + table_length = ap_get_table_length(table); + + /* Print only the header if requested */ + + if (gbl_summary_mode) { + acpi_tb_print_table_header(address, table); + return (0); + } + + /* Dump to binary file if requested */ + + if (gbl_binary_mode) { + return (ap_write_to_binary_file(table, instance)); + } + + /* + * Dump the table with header for use with acpixtract utility. + * Note: simplest to just always emit a 64-bit address. acpi_xtract + * utility can handle this. + */ + printf("%4.4s @ 0x%8.8X%8.8X\n", table->signature, + ACPI_FORMAT_UINT64(address)); + + acpi_ut_dump_buffer(ACPI_CAST_PTR(u8, table), table_length, + DB_BYTE_DISPLAY, 0); + printf("\n"); + return (0); +} + +/****************************************************************************** + * + * FUNCTION: ap_dump_all_tables + * + * PARAMETERS: None + * + * RETURN: Status + * + * DESCRIPTION: Get all tables from the RSDT/XSDT (or at least all of the + * tables that we can possibly get). + * + ******************************************************************************/ + +int ap_dump_all_tables(void) +{ + struct acpi_table_header *table; + u32 instance = 0; + acpi_physical_address address; + acpi_status status; + int table_status; + u32 i; + + /* Get and dump all available ACPI tables */ + + for (i = 0; i < AP_MAX_ACPI_FILES; i++) { + status = + acpi_os_get_table_by_index(i, &table, &instance, &address); + if (ACPI_FAILURE(status)) { + + /* AE_LIMIT means that no more tables are available */ + + if (status == AE_LIMIT) { + return (0); + } else if (i == 0) { + fprintf(stderr, + "Could not get ACPI tables, %s\n", + acpi_format_exception(status)); + return (-1); + } else { + fprintf(stderr, + "Could not get ACPI table at index %u, %s\n", + i, acpi_format_exception(status)); + continue; + } + } + + table_status = ap_dump_table_buffer(table, instance, address); + free(table); + + if (table_status) { + break; + } + } + + /* Something seriously bad happened if the loop terminates here */ + + return (-1); +} + +/****************************************************************************** + * + * FUNCTION: ap_dump_table_by_address + * + * PARAMETERS: ascii_address - Address for requested ACPI table + * + * RETURN: Status + * + * DESCRIPTION: Get an ACPI table via a physical address and dump it. + * + ******************************************************************************/ + +int ap_dump_table_by_address(char *ascii_address) +{ + acpi_physical_address address; + struct acpi_table_header *table; + acpi_status status; + int table_status; + u64 long_address; + + /* Convert argument to an integer physical address */ + + status = acpi_ut_strtoul64(ascii_address, 0, &long_address); + if (ACPI_FAILURE(status)) { + fprintf(stderr, "%s: Could not convert to a physical address\n", + ascii_address); + return (-1); + } + + address = (acpi_physical_address) long_address; + status = acpi_os_get_table_by_address(address, &table); + if (ACPI_FAILURE(status)) { + fprintf(stderr, "Could not get table at 0x%8.8X%8.8X, %s\n", + ACPI_FORMAT_UINT64(address), + acpi_format_exception(status)); + return (-1); + } + + table_status = ap_dump_table_buffer(table, 0, address); + free(table); + return (table_status); +} + +/****************************************************************************** + * + * FUNCTION: ap_dump_table_by_name + * + * PARAMETERS: signature - Requested ACPI table signature + * + * RETURN: Status + * + * DESCRIPTION: Get an ACPI table via a signature and dump it. Handles + * multiple tables with the same signature (SSDTs). + * + ******************************************************************************/ + +int ap_dump_table_by_name(char *signature) +{ + char local_signature[ACPI_NAME_SIZE + 1]; + u32 instance; + struct acpi_table_header *table; + acpi_physical_address address; + acpi_status status; + int table_status; + + if (strlen(signature) != ACPI_NAME_SIZE) { + fprintf(stderr, + "Invalid table signature [%s]: must be exactly 4 characters\n", + signature); + return (-1); + } + + /* Table signatures are expected to be uppercase */ + + strcpy(local_signature, signature); + acpi_ut_strupr(local_signature); + + /* To be friendly, handle tables whose signatures do not match the name */ + + if (ACPI_COMPARE_NAME(local_signature, "FADT")) { + strcpy(local_signature, ACPI_SIG_FADT); + } else if (ACPI_COMPARE_NAME(local_signature, "MADT")) { + strcpy(local_signature, ACPI_SIG_MADT); + } + + /* Dump all instances of this signature (to handle multiple SSDTs) */ + + for (instance = 0; instance < AP_MAX_ACPI_FILES; instance++) { + status = acpi_os_get_table_by_name(local_signature, instance, + &table, &address); + if (ACPI_FAILURE(status)) { + + /* AE_LIMIT means that no more tables are available */ + + if (status == AE_LIMIT) { + return (0); + } + + fprintf(stderr, + "Could not get ACPI table with signature [%s], %s\n", + local_signature, acpi_format_exception(status)); + return (-1); + } + + table_status = ap_dump_table_buffer(table, instance, address); + free(table); + + if (table_status) { + break; + } + } + + /* Something seriously bad happened if the loop terminates here */ + + return (-1); +} + +/****************************************************************************** + * + * FUNCTION: ap_dump_table_from_file + * + * PARAMETERS: pathname - File containing the binary ACPI table + * + * RETURN: Status + * + * DESCRIPTION: Dump an ACPI table from a binary file + * + ******************************************************************************/ + +int ap_dump_table_from_file(char *pathname) +{ + struct acpi_table_header *table; + u32 file_size = 0; + int table_status = -1; + + /* Get the entire ACPI table from the file */ + + table = ap_get_table_from_file(pathname, &file_size); + if (!table) { + return (-1); + } + + /* File must be at least as long as the table length */ + + if (table->length > file_size) { + fprintf(stderr, + "Table length (0x%X) is too large for input file (0x%X) %s\n", + table->length, file_size, pathname); + goto exit; + } + + if (gbl_verbose_mode) { + fprintf(stderr, + "Input file: %s contains table [%4.4s], 0x%X (%u) bytes\n", + pathname, table->signature, file_size, file_size); + } + + table_status = ap_dump_table_buffer(table, 0, 0); + +exit: + free(table); + return (table_status); +} + +/****************************************************************************** + * + * FUNCTION: acpi_os* print functions + * + * DESCRIPTION: Used for linkage with ACPICA modules + * + ******************************************************************************/ + +void ACPI_INTERNAL_VAR_XFACE acpi_os_printf(const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + vfprintf(stdout, fmt, args); + va_end(args); +} + +void acpi_os_vprintf(const char *fmt, va_list args) +{ + vfprintf(stdout, fmt, args); +} diff --git a/tools/power/acpi/tools/acpidump/apfiles.c b/tools/power/acpi/tools/acpidump/apfiles.c new file mode 100644 index 000000000000..4488accc010b --- /dev/null +++ b/tools/power/acpi/tools/acpidump/apfiles.c @@ -0,0 +1,228 @@ +/****************************************************************************** + * + * Module Name: apfiles - File-related functions for acpidump utility + * + *****************************************************************************/ + +/* + * Copyright (C) 2000 - 2014, Intel Corp. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * Alternatively, this software may be distributed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + * NO WARRANTY + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + */ + +#include "acpidump.h" +#include "acapps.h" + +/****************************************************************************** + * + * FUNCTION: ap_open_output_file + * + * PARAMETERS: pathname - Output filename + * + * RETURN: Open file handle + * + * DESCRIPTION: Open a text output file for acpidump. Checks if file already + * exists. + * + ******************************************************************************/ + +int ap_open_output_file(char *pathname) +{ + struct stat stat_info; + FILE *file; + + /* If file exists, prompt for overwrite */ + + if (!stat(pathname, &stat_info)) { + fprintf(stderr, + "Target path already exists, overwrite? [y|n] "); + + if (getchar() != 'y') { + return (-1); + } + } + + /* Point stdout to the file */ + + file = freopen(pathname, "w", stdout); + if (!file) { + perror("Could not open output file"); + return (-1); + } + + /* Save the file and path */ + + gbl_output_file = file; + gbl_output_filename = pathname; + return (0); +} + +/****************************************************************************** + * + * FUNCTION: ap_write_to_binary_file + * + * PARAMETERS: table - ACPI table to be written + * instance - ACPI table instance no. to be written + * + * RETURN: Status + * + * DESCRIPTION: Write an ACPI table to a binary file. Builds the output + * filename from the table signature. + * + ******************************************************************************/ + +int ap_write_to_binary_file(struct acpi_table_header *table, u32 instance) +{ + char filename[ACPI_NAME_SIZE + 16]; + char instance_str[16]; + FILE *file; + size_t actual; + u32 table_length; + + /* Obtain table length */ + + table_length = ap_get_table_length(table); + + /* Construct lower-case filename from the table local signature */ + + if (ACPI_VALIDATE_RSDP_SIG(table->signature)) { + ACPI_MOVE_NAME(filename, ACPI_RSDP_NAME); + } else { + ACPI_MOVE_NAME(filename, table->signature); + } + filename[0] = (char)ACPI_TOLOWER(filename[0]); + filename[1] = (char)ACPI_TOLOWER(filename[1]); + filename[2] = (char)ACPI_TOLOWER(filename[2]); + filename[3] = (char)ACPI_TOLOWER(filename[3]); + filename[ACPI_NAME_SIZE] = 0; + + /* Handle multiple SSDts - create different filenames for each */ + + if (instance > 0) { + sprintf(instance_str, "%u", instance); + strcat(filename, instance_str); + } + + strcat(filename, ACPI_TABLE_FILE_SUFFIX); + + if (gbl_verbose_mode) { + fprintf(stderr, + "Writing [%4.4s] to binary file: %s 0x%X (%u) bytes\n", + table->signature, filename, table->length, + table->length); + } + + /* Open the file and dump the entire table in binary mode */ + + file = fopen(filename, "wb"); + if (!file) { + perror("Could not open output file"); + return (-1); + } + + actual = fwrite(table, 1, table_length, file); + if (actual != table_length) { + perror("Error writing binary output file"); + fclose(file); + return (-1); + } + + fclose(file); + return (0); +} + +/****************************************************************************** + * + * FUNCTION: ap_get_table_from_file + * + * PARAMETERS: pathname - File containing the binary ACPI table + * out_file_size - Where the file size is returned + * + * RETURN: Buffer containing the ACPI table. NULL on error. + * + * DESCRIPTION: Open a file and read it entirely into a new buffer + * + ******************************************************************************/ + +struct acpi_table_header *ap_get_table_from_file(char *pathname, + u32 *out_file_size) +{ + struct acpi_table_header *buffer = NULL; + FILE *file; + u32 file_size; + size_t actual; + + /* Must use binary mode */ + + file = fopen(pathname, "rb"); + if (!file) { + perror("Could not open input file"); + return (NULL); + } + + /* Need file size to allocate a buffer */ + + file_size = cm_get_file_size(file); + if (file_size == ACPI_UINT32_MAX) { + fprintf(stderr, + "Could not get input file size: %s\n", pathname); + goto cleanup; + } + + /* Allocate a buffer for the entire file */ + + buffer = calloc(1, file_size); + if (!buffer) { + fprintf(stderr, + "Could not allocate file buffer of size: %u\n", + file_size); + goto cleanup; + } + + /* Read the entire file */ + + actual = fread(buffer, 1, file_size, file); + if (actual != file_size) { + fprintf(stderr, "Could not read input file: %s\n", pathname); + free(buffer); + buffer = NULL; + goto cleanup; + } + + *out_file_size = file_size; + +cleanup: + fclose(file); + return (buffer); +} diff --git a/tools/power/acpi/tools/acpidump/apmain.c b/tools/power/acpi/tools/acpidump/apmain.c new file mode 100644 index 000000000000..51e8d638db18 --- /dev/null +++ b/tools/power/acpi/tools/acpidump/apmain.c @@ -0,0 +1,351 @@ +/****************************************************************************** + * + * Module Name: apmain - Main module for the acpidump utility + * + *****************************************************************************/ + +/* + * Copyright (C) 2000 - 2014, Intel Corp. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * Alternatively, this software may be distributed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + * NO WARRANTY + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + */ + +#define _DECLARE_GLOBALS +#include "acpidump.h" +#include "acapps.h" + +/* + * acpidump - A portable utility for obtaining system ACPI tables and dumping + * them in an ASCII hex format suitable for binary extraction via acpixtract. + * + * Obtaining the system ACPI tables is an OS-specific operation. + * + * This utility can be ported to any host operating system by providing a + * module containing system-specific versions of these interfaces: + * + * acpi_os_get_table_by_address + * acpi_os_get_table_by_index + * acpi_os_get_table_by_name + * + * See the ACPICA Reference Guide for the exact definitions of these + * interfaces. Also, see these ACPICA source code modules for example + * implementations: + * + * source/os_specific/service_layers/oswintbl.c + * source/os_specific/service_layers/oslinuxtbl.c + */ + +/* Local prototypes */ + +static void ap_display_usage(void); + +static int ap_do_options(int argc, char **argv); + +static void ap_insert_action(char *argument, u32 to_be_done); + +/* Table for deferred actions from command line options */ + +struct ap_dump_action action_table[AP_MAX_ACTIONS]; +u32 current_action = 0; + +#define AP_UTILITY_NAME "ACPI Binary Table Dump Utility" +#define AP_SUPPORTED_OPTIONS "?a:bcf:hn:o:r:svxz" + +/****************************************************************************** + * + * FUNCTION: ap_display_usage + * + * DESCRIPTION: Usage message for the acpi_dump utility + * + ******************************************************************************/ + +static void ap_display_usage(void) +{ + + ACPI_USAGE_HEADER("acpidump [options]"); + + ACPI_OPTION("-b", "Dump tables to binary files"); + ACPI_OPTION("-c", "Dump customized tables"); + ACPI_OPTION("-h -?", "This help message"); + ACPI_OPTION("-o <File>", "Redirect output to file"); + ACPI_OPTION("-r <Address>", "Dump tables from specified RSDP"); + ACPI_OPTION("-s", "Print table summaries only"); + ACPI_OPTION("-v", "Display version information"); + ACPI_OPTION("-z", "Verbose mode"); + + printf("\nTable Options:\n"); + + ACPI_OPTION("-a <Address>", "Get table via a physical address"); + ACPI_OPTION("-f <BinaryFile>", "Get table via a binary file"); + ACPI_OPTION("-n <Signature>", "Get table via a name/signature"); + ACPI_OPTION("-x", "Do not use but dump XSDT"); + ACPI_OPTION("-x -x", "Do not use or dump XSDT"); + + printf("\n" + "Invocation without parameters dumps all available tables\n" + "Multiple mixed instances of -a, -f, and -n are supported\n\n"); +} + +/****************************************************************************** + * + * FUNCTION: ap_insert_action + * + * PARAMETERS: argument - Pointer to the argument for this action + * to_be_done - What to do to process this action + * + * RETURN: None. Exits program if action table becomes full. + * + * DESCRIPTION: Add an action item to the action table + * + ******************************************************************************/ + +static void ap_insert_action(char *argument, u32 to_be_done) +{ + + /* Insert action and check for table overflow */ + + action_table[current_action].argument = argument; + action_table[current_action].to_be_done = to_be_done; + + current_action++; + if (current_action > AP_MAX_ACTIONS) { + fprintf(stderr, "Too many table options (max %u)\n", + AP_MAX_ACTIONS); + exit(-1); + } +} + +/****************************************************************************** + * + * FUNCTION: ap_do_options + * + * PARAMETERS: argc/argv - Standard argc/argv + * + * RETURN: Status + * + * DESCRIPTION: Command line option processing. The main actions for getting + * and dumping tables are deferred via the action table. + * + *****************************************************************************/ + +static int ap_do_options(int argc, char **argv) +{ + int j; + acpi_status status; + + /* Command line options */ + + while ((j = acpi_getopt(argc, argv, AP_SUPPORTED_OPTIONS)) != EOF) + switch (j) { + /* + * Global options + */ + case 'b': /* Dump all input tables to binary files */ + + gbl_binary_mode = TRUE; + continue; + + case 'c': /* Dump customized tables */ + + gbl_dump_customized_tables = TRUE; + continue; + + case 'h': + case '?': + + ap_display_usage(); + exit(0); + + case 'o': /* Redirect output to a single file */ + + if (ap_open_output_file(acpi_gbl_optarg)) { + exit(-1); + } + continue; + + case 'r': /* Dump tables from specified RSDP */ + + status = + acpi_ut_strtoul64(acpi_gbl_optarg, 0, + &gbl_rsdp_base); + if (ACPI_FAILURE(status)) { + fprintf(stderr, + "%s: Could not convert to a physical address\n", + acpi_gbl_optarg); + exit(-1); + } + continue; + + case 's': /* Print table summaries only */ + + gbl_summary_mode = TRUE; + continue; + + case 'x': /* Do not use XSDT */ + + if (!acpi_gbl_do_not_use_xsdt) { + acpi_gbl_do_not_use_xsdt = TRUE; + } else { + gbl_do_not_dump_xsdt = TRUE; + } + continue; + + case 'v': /* Revision/version */ + + printf(ACPI_COMMON_SIGNON(AP_UTILITY_NAME)); + exit(0); + + case 'z': /* Verbose mode */ + + gbl_verbose_mode = TRUE; + fprintf(stderr, ACPI_COMMON_SIGNON(AP_UTILITY_NAME)); + continue; + + /* + * Table options + */ + case 'a': /* Get table by physical address */ + + ap_insert_action(acpi_gbl_optarg, + AP_DUMP_TABLE_BY_ADDRESS); + break; + + case 'f': /* Get table from a file */ + + ap_insert_action(acpi_gbl_optarg, + AP_DUMP_TABLE_BY_FILE); + break; + + case 'n': /* Get table by input name (signature) */ + + ap_insert_action(acpi_gbl_optarg, + AP_DUMP_TABLE_BY_NAME); + break; + + default: + + ap_display_usage(); + exit(-1); + } + + /* If there are no actions, this means "get/dump all tables" */ + + if (current_action == 0) { + ap_insert_action(NULL, AP_DUMP_ALL_TABLES); + } + + return (0); +} + +/****************************************************************************** + * + * FUNCTION: main + * + * PARAMETERS: argc/argv - Standard argc/argv + * + * RETURN: Status + * + * DESCRIPTION: C main function for acpidump utility + * + ******************************************************************************/ + +int ACPI_SYSTEM_XFACE main(int argc, char *argv[]) +{ + int status = 0; + struct ap_dump_action *action; + u32 file_size; + u32 i; + + ACPI_DEBUG_INITIALIZE(); /* For debug version only */ + + /* Process command line options */ + + if (ap_do_options(argc, argv)) { + return (-1); + } + + /* Get/dump ACPI table(s) as requested */ + + for (i = 0; i < current_action; i++) { + action = &action_table[i]; + switch (action->to_be_done) { + case AP_DUMP_ALL_TABLES: + + status = ap_dump_all_tables(); + break; + + case AP_DUMP_TABLE_BY_ADDRESS: + + status = ap_dump_table_by_address(action->argument); + break; + + case AP_DUMP_TABLE_BY_NAME: + + status = ap_dump_table_by_name(action->argument); + break; + + case AP_DUMP_TABLE_BY_FILE: + + status = ap_dump_table_from_file(action->argument); + break; + + default: + + fprintf(stderr, + "Internal error, invalid action: 0x%X\n", + action->to_be_done); + return (-1); + } + + if (status) { + return (status); + } + } + + if (gbl_output_file) { + if (gbl_verbose_mode) { + + /* Summary for the output file */ + + file_size = cm_get_file_size(gbl_output_file); + fprintf(stderr, + "Output file %s contains 0x%X (%u) bytes\n\n", + gbl_output_filename, file_size, file_size); + } + + fclose(gbl_output_file); + } + + return (status); +} diff --git a/tools/power/acpi/tools/ec/Makefile b/tools/power/acpi/tools/ec/Makefile new file mode 100644 index 000000000000..b7b0b929bd32 --- /dev/null +++ b/tools/power/acpi/tools/ec/Makefile @@ -0,0 +1,22 @@ +ec_access: ec_access.o + $(ECHO) " LD " $@ + $(QUIET) $(LD) $(CFLAGS) $(LDFLAGS) $< -o $@ + $(QUIET) $(STRIPCMD) $@ + +%.o: %.c + $(ECHO) " CC " $@ + $(QUIET) $(CC) -c $(CFLAGS) -o $@ $< + +all: ec_access + +install: + $(INSTALL) -d $(DESTDIR)${sbindir} + $(INSTALL_PROGRAM) ec_access $(DESTDIR)${sbindir} + +uninstall: + - rm -f $(DESTDIR)${sbindir}/ec_access + +clean: + -rm -f $(OUTPUT)ec_access + +.PHONY: all install uninstall diff --git a/tools/power/acpi/tools/ec/ec_access.c b/tools/power/acpi/tools/ec/ec_access.c new file mode 100644 index 000000000000..6b8aaed44f2c --- /dev/null +++ b/tools/power/acpi/tools/ec/ec_access.c @@ -0,0 +1,238 @@ +/* + * ec_access.c + * + * Copyright (C) 2010 SUSE Linux Products GmbH + * Author: + * Thomas Renninger <trenn@suse.de> + * + * This work is licensed under the terms of the GNU GPL, version 2. + */ + +#include <fcntl.h> +#include <err.h> +#include <stdio.h> +#include <stdlib.h> +#include <libgen.h> +#include <unistd.h> +#include <getopt.h> +#include <stdint.h> +#include <sys/types.h> +#include <sys/stat.h> + + +#define EC_SPACE_SIZE 256 +#define SYSFS_PATH "/sys/kernel/debug/ec/ec0/io" + +/* TBD/Enhancements: + - Provide param for accessing different ECs (not supported by kernel yet) +*/ + +static int read_mode = -1; +static int sleep_time; +static int write_byte_offset = -1; +static int read_byte_offset = -1; +static uint8_t write_value = -1; + +void usage(char progname[], int exit_status) +{ + printf("Usage:\n"); + printf("1) %s -r [-s sleep]\n", basename(progname)); + printf("2) %s -b byte_offset\n", basename(progname)); + printf("3) %s -w byte_offset -v value\n\n", basename(progname)); + + puts("\t-r [-s sleep] : Dump EC registers"); + puts("\t If sleep is given, sleep x seconds,"); + puts("\t re-read EC registers and show changes"); + puts("\t-b offset : Read value at byte_offset (in hex)"); + puts("\t-w offset -v value : Write value at byte_offset"); + puts("\t-h : Print this help\n\n"); + puts("Offsets and values are in hexadecimal number sytem."); + puts("The offset and value must be between 0 and 0xff."); + exit(exit_status); +} + +void parse_opts(int argc, char *argv[]) +{ + int c; + + while ((c = getopt(argc, argv, "rs:b:w:v:h")) != -1) { + + switch (c) { + case 'r': + if (read_mode != -1) + usage(argv[0], EXIT_FAILURE); + read_mode = 1; + break; + case 's': + if (read_mode != -1 && read_mode != 1) + usage(argv[0], EXIT_FAILURE); + + sleep_time = atoi(optarg); + if (sleep_time <= 0) { + sleep_time = 0; + usage(argv[0], EXIT_FAILURE); + printf("Bad sleep time: %s\n", optarg); + } + break; + case 'b': + if (read_mode != -1) + usage(argv[0], EXIT_FAILURE); + read_mode = 1; + read_byte_offset = strtoul(optarg, NULL, 16); + break; + case 'w': + if (read_mode != -1) + usage(argv[0], EXIT_FAILURE); + read_mode = 0; + write_byte_offset = strtoul(optarg, NULL, 16); + break; + case 'v': + write_value = strtoul(optarg, NULL, 16); + break; + case 'h': + usage(argv[0], EXIT_SUCCESS); + default: + fprintf(stderr, "Unknown option!\n"); + usage(argv[0], EXIT_FAILURE); + } + } + if (read_mode == 0) { + if (write_byte_offset < 0 || + write_byte_offset >= EC_SPACE_SIZE) { + fprintf(stderr, "Wrong byte offset 0x%.2x, valid: " + "[0-0x%.2x]\n", + write_byte_offset, EC_SPACE_SIZE - 1); + usage(argv[0], EXIT_FAILURE); + } + if (write_value < 0 || + write_value >= 255) { + fprintf(stderr, "Wrong byte offset 0x%.2x, valid:" + "[0-0xff]\n", write_byte_offset); + usage(argv[0], EXIT_FAILURE); + } + } + if (read_mode == 1 && read_byte_offset != -1) { + if (read_byte_offset < -1 || + read_byte_offset >= EC_SPACE_SIZE) { + fprintf(stderr, "Wrong byte offset 0x%.2x, valid: " + "[0-0x%.2x]\n", + read_byte_offset, EC_SPACE_SIZE - 1); + usage(argv[0], EXIT_FAILURE); + } + } + /* Add additional parameter checks here */ +} + +void dump_ec(int fd) +{ + char buf[EC_SPACE_SIZE]; + char buf2[EC_SPACE_SIZE]; + int byte_off, bytes_read; + + bytes_read = read(fd, buf, EC_SPACE_SIZE); + + if (bytes_read == -1) + err(EXIT_FAILURE, "Could not read from %s\n", SYSFS_PATH); + + if (bytes_read != EC_SPACE_SIZE) + fprintf(stderr, "Could only read %d bytes\n", bytes_read); + + printf(" 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F"); + for (byte_off = 0; byte_off < bytes_read; byte_off++) { + if ((byte_off % 16) == 0) + printf("\n%.2X: ", byte_off); + printf(" %.2x ", (uint8_t)buf[byte_off]); + } + printf("\n"); + + if (!sleep_time) + return; + + printf("\n"); + lseek(fd, 0, SEEK_SET); + sleep(sleep_time); + + bytes_read = read(fd, buf2, EC_SPACE_SIZE); + + if (bytes_read == -1) + err(EXIT_FAILURE, "Could not read from %s\n", SYSFS_PATH); + + if (bytes_read != EC_SPACE_SIZE) + fprintf(stderr, "Could only read %d bytes\n", bytes_read); + + printf(" 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F"); + for (byte_off = 0; byte_off < bytes_read; byte_off++) { + if ((byte_off % 16) == 0) + printf("\n%.2X: ", byte_off); + + if (buf[byte_off] == buf2[byte_off]) + printf(" %.2x ", (uint8_t)buf2[byte_off]); + else + printf("*%.2x ", (uint8_t)buf2[byte_off]); + } + printf("\n"); +} + +void read_ec_val(int fd, int byte_offset) +{ + uint8_t buf; + int error; + + error = lseek(fd, byte_offset, SEEK_SET); + if (error != byte_offset) + err(EXIT_FAILURE, "Cannot set offset to 0x%.2x", byte_offset); + + error = read(fd, &buf, 1); + if (error != 1) + err(EXIT_FAILURE, "Could not read byte 0x%.2x from %s\n", + byte_offset, SYSFS_PATH); + printf("0x%.2x\n", buf); + return; +} + +void write_ec_val(int fd, int byte_offset, uint8_t value) +{ + int error; + + error = lseek(fd, byte_offset, SEEK_SET); + if (error != byte_offset) + err(EXIT_FAILURE, "Cannot set offset to 0x%.2x", byte_offset); + + error = write(fd, &value, 1); + if (error != 1) + err(EXIT_FAILURE, "Cannot write value 0x%.2x to offset 0x%.2x", + value, byte_offset); +} + +int main(int argc, char *argv[]) +{ + int file_mode = O_RDONLY; + int fd; + + parse_opts(argc, argv); + + if (read_mode == 0) + file_mode = O_WRONLY; + else if (read_mode == 1) + file_mode = O_RDONLY; + else + usage(argv[0], EXIT_FAILURE); + + fd = open(SYSFS_PATH, file_mode); + if (fd == -1) + err(EXIT_FAILURE, "%s", SYSFS_PATH); + + if (read_mode) + if (read_byte_offset == -1) + dump_ec(fd); + else if (read_byte_offset < 0 || + read_byte_offset >= EC_SPACE_SIZE) + usage(argv[0], EXIT_FAILURE); + else + read_ec_val(fd, read_byte_offset); + else + write_ec_val(fd, write_byte_offset, write_value); + close(fd); + + exit(EXIT_SUCCESS); +} diff --git a/tools/power/cpupower/Makefile b/tools/power/cpupower/Makefile index cbfec92af327..2e2ba2efa0d9 100644 --- a/tools/power/cpupower/Makefile +++ b/tools/power/cpupower/Makefile @@ -62,7 +62,7 @@ LIB_MAJ= 0.0.0 LIB_MIN= 0 PACKAGE = cpupower -PACKAGE_BUGREPORT = cpufreq@vger.kernel.org +PACKAGE_BUGREPORT = linux-pm@vger.kernel.org LANGUAGES = de fr it cs pt @@ -274,6 +274,8 @@ install-man: $(INSTALL_DATA) -D man/cpupower.1 $(DESTDIR)${mandir}/man1/cpupower.1 $(INSTALL_DATA) -D man/cpupower-frequency-set.1 $(DESTDIR)${mandir}/man1/cpupower-frequency-set.1 $(INSTALL_DATA) -D man/cpupower-frequency-info.1 $(DESTDIR)${mandir}/man1/cpupower-frequency-info.1 + $(INSTALL_DATA) -D man/cpupower-idle-set.1 $(DESTDIR)${mandir}/man1/cpupower-idle-set.1 + $(INSTALL_DATA) -D man/cpupower-idle-info.1 $(DESTDIR)${mandir}/man1/cpupower-idle-info.1 $(INSTALL_DATA) -D man/cpupower-set.1 $(DESTDIR)${mandir}/man1/cpupower-set.1 $(INSTALL_DATA) -D man/cpupower-info.1 $(DESTDIR)${mandir}/man1/cpupower-info.1 $(INSTALL_DATA) -D man/cpupower-monitor.1 $(DESTDIR)${mandir}/man1/cpupower-monitor.1 @@ -295,8 +297,12 @@ uninstall: - rm -f $(DESTDIR)${libdir}/libcpupower.* - rm -f $(DESTDIR)${includedir}/cpufreq.h - rm -f $(DESTDIR)${bindir}/utils/cpupower - - rm -f $(DESTDIR)${mandir}/man1/cpufreq-set.1 - - rm -f $(DESTDIR)${mandir}/man1/cpufreq-info.1 + - rm -f $(DESTDIR)${mandir}/man1/cpupower.1 + - rm -f $(DESTDIR)${mandir}/man1/cpupower-frequency-set.1 + - rm -f $(DESTDIR)${mandir}/man1/cpupower-frequency-info.1 + - rm -f $(DESTDIR)${mandir}/man1/cpupower-set.1 + - rm -f $(DESTDIR)${mandir}/man1/cpupower-info.1 + - rm -f $(DESTDIR)${mandir}/man1/cpupower-monitor.1 - for HLANG in $(LANGUAGES); do \ rm -f $(DESTDIR)${localedir}/$$HLANG/LC_MESSAGES/cpupower.mo; \ done; diff --git a/tools/power/cpupower/README b/tools/power/cpupower/README index fd9d4c0d6688..1c68f47663b2 100644 --- a/tools/power/cpupower/README +++ b/tools/power/cpupower/README @@ -1,6 +1,4 @@ -The cpufrequtils package (homepage: -http://www.kernel.org/pub/linux/utils/kernel/cpufreq/cpufrequtils.html ) -consists of the following elements: +The cpupower package consists of the following elements: requirements ------------ @@ -11,10 +9,10 @@ providing cpuid.h is needed. For both it's not explicitly checked for (yet). -libcpufreq +libcpupower ---------- -"libcpufreq" is a library which offers a unified access method for userspace +"libcpupower" is a library which offers a unified access method for userspace tools and programs to the cpufreq core and drivers in the Linux kernel. This allows for code reduction in userspace tools, a clean implementation of the interaction to the cpufreq core, and support for both the sysfs and proc @@ -28,22 +26,22 @@ make su make install -should suffice on most systems. It builds default libcpufreq, -cpufreq-set and cpufreq-info files and installs them in /usr/lib and -/usr/bin, respectively. If you want to set up the paths differently and/or -want to configure the package to your specific needs, you need to open -"Makefile" with an editor of your choice and edit the block marked -CONFIGURATION. +should suffice on most systems. It builds libcpupower to put in +/usr/lib; cpupower, cpufreq-bench_plot.sh to put in /usr/bin; and +cpufreq-bench to put in /usr/sbin. If you want to set up the paths +differently and/or want to configure the package to your specific +needs, you need to open "Makefile" with an editor of your choice and +edit the block marked CONFIGURATION. THANKS ------ Many thanks to Mattia Dongili who wrote the autotoolization and -libtoolization, the manpages and the italian language file for cpufrequtils; +libtoolization, the manpages and the italian language file for cpupower; to Dave Jones for his feedback and his dump_psb tool; to Bruno Ducrot for his powernow-k8-decode and intel_gsic tools as well as the french language file; and to various others commenting on the previous (pre-)releases of -cpufrequtils. +cpupower. Dominik Brodowski diff --git a/tools/power/cpupower/ToDo b/tools/power/cpupower/ToDo index 874b78b586ee..6e8b89f282e6 100644 --- a/tools/power/cpupower/ToDo +++ b/tools/power/cpupower/ToDo @@ -3,7 +3,6 @@ ToDos sorted by priority: - Use bitmask functions to parse CPU topology more robust (current implementation has issues on AMD) - Try to read out boost states and frequencies on Intel -- Adjust README - Somewhere saw the ability to read power consumption of RAM from HW on Intel SandyBridge -> another monitor? - Add another c1e debug idle monitor diff --git a/tools/power/cpupower/debug/kernel/cpufreq-test_tsc.c b/tools/power/cpupower/debug/kernel/cpufreq-test_tsc.c index 0f10b81e3322..5224ee5b392d 100644 --- a/tools/power/cpupower/debug/kernel/cpufreq-test_tsc.c +++ b/tools/power/cpupower/debug/kernel/cpufreq-test_tsc.c @@ -18,7 +18,7 @@ * 5.) if the third value, "diff_pmtmr", changes between 2. and 4., the * TSC-based delay routine on the Linux kernel does not correctly * handle the cpufreq transition. Please report this to - * cpufreq@vger.kernel.org + * linux-pm@vger.kernel.org */ #include <linux/kernel.h> diff --git a/tools/power/cpupower/man/cpupower-frequency-info.1 b/tools/power/cpupower/man/cpupower-frequency-info.1 index 4a1918ea8f9c..9c85a382e355 100644 --- a/tools/power/cpupower/man/cpupower-frequency-info.1 +++ b/tools/power/cpupower/man/cpupower-frequency-info.1 @@ -50,6 +50,9 @@ Prints out information like provided by the /proc/cpufreq interface in 2.4. and \fB\-m\fR \fB\-\-human\fR human\-readable output for the \-f, \-w, \-s and \-y parameters. .TP +\fB\-n\fR \fB\-\-no-rounding\fR +Output frequencies and latencies without rounding off values. +.TP .SH "REMARKS" .LP By default only values of core zero are displayed. How to display settings of diff --git a/tools/power/cpupower/man/cpupower-idle-set.1 b/tools/power/cpupower/man/cpupower-idle-set.1 index 6b1607272a5b..3e6799d7a79f 100644 --- a/tools/power/cpupower/man/cpupower-idle-set.1 +++ b/tools/power/cpupower/man/cpupower-idle-set.1 @@ -13,11 +13,17 @@ sleep states. This can be handy for power vs performance tuning. .SH "OPTIONS" .LP .TP -\fB\-d\fR \fB\-\-disable\fR +\fB\-d\fR \fB\-\-disable\fR <STATE_NO> Disable a specific processor sleep state. .TP -\fB\-e\fR \fB\-\-enable\fR +\fB\-e\fR \fB\-\-enable\fR <STATE_NO> Enable a specific processor sleep state. +.TP +\fB\-D\fR \fB\-\-disable-by-latency\fR <LATENCY> +Disable all idle states with a equal or higher latency than <LATENCY> +.TP +\fB\-E\fR \fB\-\-enable-all\fR +Enable all idle states if not enabled already. .SH "REMARKS" .LP diff --git a/tools/power/cpupower/man/cpupower-info.1 b/tools/power/cpupower/man/cpupower-info.1 index 58e21196f17f..340bcd0be7de 100644 --- a/tools/power/cpupower/man/cpupower-info.1 +++ b/tools/power/cpupower/man/cpupower-info.1 @@ -3,7 +3,7 @@ cpupower\-info \- Shows processor power related kernel or hardware configurations .SH SYNOPSIS .ft B -.B cpupower info [ \-b ] [ \-s ] [ \-m ] +.B cpupower info [ \-b ] .SH DESCRIPTION \fBcpupower info \fP shows kernel configurations or processor hardware diff --git a/tools/power/cpupower/man/cpupower-set.1 b/tools/power/cpupower/man/cpupower-set.1 index 9dbd536518ab..2bcc696f4496 100644 --- a/tools/power/cpupower/man/cpupower-set.1 +++ b/tools/power/cpupower/man/cpupower-set.1 @@ -3,7 +3,7 @@ cpupower\-set \- Set processor power related kernel or hardware configurations .SH SYNOPSIS .ft B -.B cpupower set [ \-b VAL ] [ \-s VAL ] [ \-m VAL ] +.B cpupower set [ \-b VAL ] .SH DESCRIPTION @@ -55,35 +55,6 @@ Use \fBcpupower -c all info -b\fP to verify. This options needs the msr kernel driver (CONFIG_X86_MSR) loaded. .RE -.PP -\-\-sched\-mc, \-m [ VAL ] -.RE -\-\-sched\-smt, \-s [ VAL ] -.RS 4 -\-\-sched\-mc utilizes cores in one processor package/socket first before -processes are scheduled to other processor packages/sockets. - -\-\-sched\-smt utilizes thread siblings of one processor core first before -processes are scheduled to other cores. - -The impact on power consumption and performance (positiv or negativ) heavily -depends on processor support for deep sleep states, frequency scaling and -frequency boost modes and their dependencies between other thread siblings -and processor cores. - -Taken over from kernel documentation: - -Adjust the kernel's multi-core scheduler support. - -Possible values are: -.RS 2 -0 - No power saving load balance (default value) - -1 - Fill one thread/core/package first for long running threads - -2 - Also bias task wakeups to semi-idle cpu package for power -savings -.RE .SH "SEE ALSO" cpupower-info(1), cpupower-monitor(1), powertop(1) diff --git a/tools/power/cpupower/utils/cpufreq-info.c b/tools/power/cpupower/utils/cpufreq-info.c index 28953c9a7bd5..b4b90a97662c 100644 --- a/tools/power/cpupower/utils/cpufreq-info.c +++ b/tools/power/cpupower/utils/cpufreq-info.c @@ -82,29 +82,42 @@ static void proc_cpufreq_output(void) } } +static int no_rounding; static void print_speed(unsigned long speed) { unsigned long tmp; - if (speed > 1000000) { - tmp = speed % 10000; - if (tmp >= 5000) - speed += 10000; - printf("%u.%02u GHz", ((unsigned int) speed/1000000), - ((unsigned int) (speed%1000000)/10000)); - } else if (speed > 100000) { - tmp = speed % 1000; - if (tmp >= 500) - speed += 1000; - printf("%u MHz", ((unsigned int) speed / 1000)); - } else if (speed > 1000) { - tmp = speed % 100; - if (tmp >= 50) - speed += 100; - printf("%u.%01u MHz", ((unsigned int) speed/1000), - ((unsigned int) (speed%1000)/100)); - } else - printf("%lu kHz", speed); + if (no_rounding) { + if (speed > 1000000) + printf("%u.%06u GHz", ((unsigned int) speed/1000000), + ((unsigned int) speed%1000000)); + else if (speed > 100000) + printf("%u MHz", (unsigned int) speed); + else if (speed > 1000) + printf("%u.%03u MHz", ((unsigned int) speed/1000), + (unsigned int) (speed%1000)); + else + printf("%lu kHz", speed); + } else { + if (speed > 1000000) { + tmp = speed%10000; + if (tmp >= 5000) + speed += 10000; + printf("%u.%02u GHz", ((unsigned int) speed/1000000), + ((unsigned int) (speed%1000000)/10000)); + } else if (speed > 100000) { + tmp = speed%1000; + if (tmp >= 500) + speed += 1000; + printf("%u MHz", ((unsigned int) speed/1000)); + } else if (speed > 1000) { + tmp = speed%100; + if (tmp >= 50) + speed += 100; + printf("%u.%01u MHz", ((unsigned int) speed/1000), + ((unsigned int) (speed%1000)/100)); + } + } return; } @@ -113,26 +126,38 @@ static void print_duration(unsigned long duration) { unsigned long tmp; - if (duration > 1000000) { - tmp = duration % 10000; - if (tmp >= 5000) - duration += 10000; - printf("%u.%02u ms", ((unsigned int) duration/1000000), - ((unsigned int) (duration%1000000)/10000)); - } else if (duration > 100000) { - tmp = duration % 1000; - if (tmp >= 500) - duration += 1000; - printf("%u us", ((unsigned int) duration / 1000)); - } else if (duration > 1000) { - tmp = duration % 100; - if (tmp >= 50) - duration += 100; - printf("%u.%01u us", ((unsigned int) duration/1000), - ((unsigned int) (duration%1000)/100)); - } else - printf("%lu ns", duration); - + if (no_rounding) { + if (duration > 1000000) + printf("%u.%06u ms", ((unsigned int) duration/1000000), + ((unsigned int) duration%1000000)); + else if (duration > 100000) + printf("%u us", ((unsigned int) duration/1000)); + else if (duration > 1000) + printf("%u.%03u us", ((unsigned int) duration/1000), + ((unsigned int) duration%1000)); + else + printf("%lu ns", duration); + } else { + if (duration > 1000000) { + tmp = duration%10000; + if (tmp >= 5000) + duration += 10000; + printf("%u.%02u ms", ((unsigned int) duration/1000000), + ((unsigned int) (duration%1000000)/10000)); + } else if (duration > 100000) { + tmp = duration%1000; + if (tmp >= 500) + duration += 1000; + printf("%u us", ((unsigned int) duration / 1000)); + } else if (duration > 1000) { + tmp = duration%100; + if (tmp >= 50) + duration += 100; + printf("%u.%01u us", ((unsigned int) duration/1000), + ((unsigned int) (duration%1000)/100)); + } else + printf("%lu ns", duration); + } return; } @@ -525,6 +550,7 @@ static struct option info_opts[] = { { .name = "latency", .has_arg = no_argument, .flag = NULL, .val = 'y'}, { .name = "proc", .has_arg = no_argument, .flag = NULL, .val = 'o'}, { .name = "human", .has_arg = no_argument, .flag = NULL, .val = 'm'}, + { .name = "no-rounding", .has_arg = no_argument, .flag = NULL, .val = 'n'}, { }, }; @@ -538,7 +564,8 @@ int cmd_freq_info(int argc, char **argv) int output_param = 0; do { - ret = getopt_long(argc, argv, "oefwldpgrasmyb", info_opts, NULL); + ret = getopt_long(argc, argv, "oefwldpgrasmybn", info_opts, + NULL); switch (ret) { case '?': output_param = '?'; @@ -575,6 +602,9 @@ int cmd_freq_info(int argc, char **argv) } human = 1; break; + case 'n': + no_rounding = 1; + break; default: fprintf(stderr, "invalid or unknown argument\n"); return EXIT_FAILURE; diff --git a/tools/power/cpupower/utils/cpuidle-set.c b/tools/power/cpupower/utils/cpuidle-set.c index c78141c5dfac..d45d8d775c02 100644 --- a/tools/power/cpupower/utils/cpuidle-set.c +++ b/tools/power/cpupower/utils/cpuidle-set.c @@ -13,8 +13,14 @@ #include "helpers/sysfs.h" static struct option info_opts[] = { - { .name = "disable", .has_arg = required_argument, .flag = NULL, .val = 'd'}, - { .name = "enable", .has_arg = required_argument, .flag = NULL, .val = 'e'}, + { .name = "disable", + .has_arg = required_argument, .flag = NULL, .val = 'd'}, + { .name = "enable", + .has_arg = required_argument, .flag = NULL, .val = 'e'}, + { .name = "disable-by-latency", + .has_arg = required_argument, .flag = NULL, .val = 'D'}, + { .name = "enable-all", + .has_arg = no_argument, .flag = NULL, .val = 'E'}, { }, }; @@ -23,11 +29,13 @@ int cmd_idle_set(int argc, char **argv) { extern char *optarg; extern int optind, opterr, optopt; - int ret = 0, cont = 1, param = 0, idlestate = 0; - unsigned int cpu = 0; + int ret = 0, cont = 1, param = 0, disabled; + unsigned long long latency = 0, state_latency; + unsigned int cpu = 0, idlestate = 0, idlestates = 0; + char *endptr; do { - ret = getopt_long(argc, argv, "d:e:", info_opts, NULL); + ret = getopt_long(argc, argv, "d:e:ED:", info_opts, NULL); if (ret == -1) break; switch (ret) { @@ -53,6 +61,27 @@ int cmd_idle_set(int argc, char **argv) param = ret; idlestate = atoi(optarg); break; + case 'D': + if (param) { + param = -1; + cont = 0; + break; + } + param = ret; + latency = strtoull(optarg, &endptr, 10); + if (*endptr != '\0') { + printf(_("Bad latency value: %s\n"), optarg); + exit(EXIT_FAILURE); + } + break; + case 'E': + if (param) { + param = -1; + cont = 0; + break; + } + param = ret; + break; case -1: cont = 0; break; @@ -79,8 +108,14 @@ int cmd_idle_set(int argc, char **argv) if (!bitmask_isbitset(cpus_chosen, cpu)) continue; - switch (param) { + if (sysfs_is_cpu_online(cpu) != 1) + continue; + + idlestates = sysfs_get_idlestate_count(cpu); + if (idlestates <= 0) + continue; + switch (param) { case 'd': ret = sysfs_idlestate_disable(cpu, idlestate, 1); if (ret == 0) @@ -107,6 +142,34 @@ int cmd_idle_set(int argc, char **argv) printf(_("Idlestate %u not enabled on CPU %u\n"), idlestate, cpu); break; + case 'D': + for (idlestate = 0; idlestate < idlestates; idlestate++) { + disabled = sysfs_is_idlestate_disabled + (cpu, idlestate); + state_latency = sysfs_get_idlestate_latency + (cpu, idlestate); + printf("CPU: %u - idlestate %u - state_latency: %llu - latency: %llu\n", + cpu, idlestate, state_latency, latency); + if (disabled == 1 || latency > state_latency) + continue; + ret = sysfs_idlestate_disable + (cpu, idlestate, 1); + if (ret == 0) + printf(_("Idlestate %u disabled on CPU %u\n"), idlestate, cpu); + } + break; + case 'E': + for (idlestate = 0; idlestate < idlestates; idlestate++) { + disabled = sysfs_is_idlestate_disabled + (cpu, idlestate); + if (disabled == 1) { + ret = sysfs_idlestate_disable + (cpu, idlestate, 0); + if (ret == 0) + printf(_("Idlestate %u enabled on CPU %u\n"), idlestate, cpu); + } + } + break; default: /* Not reachable with proper args checking */ printf(_("Invalid or unknown argument\n")); diff --git a/tools/power/cpupower/utils/cpupower-info.c b/tools/power/cpupower/utils/cpupower-info.c index 3f68632c28c7..136d979e9586 100644 --- a/tools/power/cpupower/utils/cpupower-info.c +++ b/tools/power/cpupower/utils/cpupower-info.c @@ -18,8 +18,6 @@ static struct option set_opts[] = { { .name = "perf-bias", .has_arg = optional_argument, .flag = NULL, .val = 'b'}, - { .name = "sched-mc", .has_arg = optional_argument, .flag = NULL, .val = 'm'}, - { .name = "sched-smt", .has_arg = optional_argument, .flag = NULL, .val = 's'}, { }, }; @@ -37,8 +35,6 @@ int cmd_info(int argc, char **argv) union { struct { - int sched_mc:1; - int sched_smt:1; int perf_bias:1; }; int params; @@ -49,23 +45,13 @@ int cmd_info(int argc, char **argv) textdomain(PACKAGE); /* parameter parsing */ - while ((ret = getopt_long(argc, argv, "msb", set_opts, NULL)) != -1) { + while ((ret = getopt_long(argc, argv, "b", set_opts, NULL)) != -1) { switch (ret) { case 'b': if (params.perf_bias) print_wrong_arg_exit(); params.perf_bias = 1; break; - case 'm': - if (params.sched_mc) - print_wrong_arg_exit(); - params.sched_mc = 1; - break; - case 's': - if (params.sched_smt) - print_wrong_arg_exit(); - params.sched_smt = 1; - break; default: print_wrong_arg_exit(); } @@ -78,25 +64,6 @@ int cmd_info(int argc, char **argv) if (bitmask_isallclear(cpus_chosen)) bitmask_setbit(cpus_chosen, 0); - if (params.sched_mc) { - ret = sysfs_get_sched("mc"); - printf(_("System's multi core scheduler setting: ")); - if (ret < 0) - /* if sysfs file is missing it's: errno == ENOENT */ - printf(_("not supported\n")); - else - printf("%d\n", ret); - } - if (params.sched_smt) { - ret = sysfs_get_sched("smt"); - printf(_("System's thread sibling scheduler setting: ")); - if (ret < 0) - /* if sysfs file is missing it's: errno == ENOENT */ - printf(_("not supported\n")); - else - printf("%d\n", ret); - } - /* Add more per cpu options here */ if (!params.perf_bias) return ret; @@ -125,11 +92,12 @@ int cmd_info(int argc, char **argv) if (params.perf_bias) { ret = msr_intel_get_perf_bias(cpu); if (ret < 0) { - printf(_("Could not read perf-bias value\n")); - break; + fprintf(stderr, + _("Could not read perf-bias value[%d]\n"), ret); + exit(EXIT_FAILURE); } else printf(_("perf-bias: %d\n"), ret); } } - return ret; + return 0; } diff --git a/tools/power/cpupower/utils/cpupower-set.c b/tools/power/cpupower/utils/cpupower-set.c index bcf1d2f0b791..573c75f8e3f5 100644 --- a/tools/power/cpupower/utils/cpupower-set.c +++ b/tools/power/cpupower/utils/cpupower-set.c @@ -19,8 +19,6 @@ static struct option set_opts[] = { { .name = "perf-bias", .has_arg = required_argument, .flag = NULL, .val = 'b'}, - { .name = "sched-mc", .has_arg = required_argument, .flag = NULL, .val = 'm'}, - { .name = "sched-smt", .has_arg = required_argument, .flag = NULL, .val = 's'}, { }, }; @@ -38,13 +36,11 @@ int cmd_set(int argc, char **argv) union { struct { - int sched_mc:1; - int sched_smt:1; int perf_bias:1; }; int params; } params; - int sched_mc = 0, sched_smt = 0, perf_bias = 0; + int perf_bias = 0; int ret = 0; setlocale(LC_ALL, ""); @@ -52,7 +48,7 @@ int cmd_set(int argc, char **argv) params.params = 0; /* parameter parsing */ - while ((ret = getopt_long(argc, argv, "m:s:b:", + while ((ret = getopt_long(argc, argv, "b:", set_opts, NULL)) != -1) { switch (ret) { case 'b': @@ -66,28 +62,6 @@ int cmd_set(int argc, char **argv) } params.perf_bias = 1; break; - case 'm': - if (params.sched_mc) - print_wrong_arg_exit(); - sched_mc = atoi(optarg); - if (sched_mc < 0 || sched_mc > 2) { - printf(_("--sched-mc param out " - "of range [0-%d]\n"), 2); - print_wrong_arg_exit(); - } - params.sched_mc = 1; - break; - case 's': - if (params.sched_smt) - print_wrong_arg_exit(); - sched_smt = atoi(optarg); - if (sched_smt < 0 || sched_smt > 2) { - printf(_("--sched-smt param out " - "of range [0-%d]\n"), 2); - print_wrong_arg_exit(); - } - params.sched_smt = 1; - break; default: print_wrong_arg_exit(); } @@ -96,19 +70,6 @@ int cmd_set(int argc, char **argv) if (!params.params) print_wrong_arg_exit(); - if (params.sched_mc) { - ret = sysfs_set_sched("mc", sched_mc); - if (ret) - fprintf(stderr, _("Error setting sched-mc %s\n"), - (ret == -ENODEV) ? "not supported" : ""); - } - if (params.sched_smt) { - ret = sysfs_set_sched("smt", sched_smt); - if (ret) - fprintf(stderr, _("Error setting sched-smt %s\n"), - (ret == -ENODEV) ? "not supported" : ""); - } - /* Default is: set all CPUs */ if (bitmask_isallclear(cpus_chosen)) bitmask_setall(cpus_chosen); diff --git a/tools/power/cpupower/utils/cpupower.c b/tools/power/cpupower/utils/cpupower.c index 7efc570ffbaa..7cdcf88659c7 100644 --- a/tools/power/cpupower/utils/cpupower.c +++ b/tools/power/cpupower/utils/cpupower.c @@ -12,6 +12,9 @@ #include <string.h> #include <unistd.h> #include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/utsname.h> #include "builtin.h" #include "helpers/helpers.h" @@ -169,6 +172,8 @@ int main(int argc, const char *argv[]) { const char *cmd; unsigned int i, ret; + struct stat statbuf; + struct utsname uts; cpus_chosen = bitmask_alloc(sysconf(_SC_NPROCESSORS_CONF)); @@ -195,6 +200,15 @@ int main(int argc, const char *argv[]) get_cpu_info(0, &cpupower_cpu_info); run_as_root = !getuid(); + if (run_as_root) { + ret = uname(&uts); + if (!ret && !strcmp(uts.machine, "x86_64") && + stat("/dev/cpu/0/msr", &statbuf) != 0) { + if (system("modprobe msr") == -1) + fprintf(stderr, _("MSR access not available.\n")); + } + } + for (i = 0; i < ARRAY_SIZE(commands); i++) { struct cmd_struct *p = commands + i; diff --git a/tools/power/x86/turbostat/turbostat.c b/tools/power/x86/turbostat/turbostat.c index 7c9d8e71eb9e..d0396af99fa0 100644 --- a/tools/power/x86/turbostat/turbostat.c +++ b/tools/power/x86/turbostat/turbostat.c @@ -1971,13 +1971,13 @@ int set_temperature_target(struct thread_data *t, struct core_data *c, struct pk if (get_msr(0, MSR_IA32_TEMPERATURE_TARGET, &msr)) goto guess; - target_c_local = (msr >> 16) & 0x7F; + target_c_local = (msr >> 16) & 0xFF; if (verbose) fprintf(stderr, "cpu%d: MSR_IA32_TEMPERATURE_TARGET: 0x%08llx (%d C)\n", cpu, msr, target_c_local); - if (target_c_local < 85 || target_c_local > 127) + if (!target_c_local) goto guess; tcc_activation_temp = target_c_local; diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index 32487ed18354..e66e710cc595 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -10,6 +10,7 @@ TARGETS += timers TARGETS += vm TARGETS += powerpc TARGETS += user +TARGETS += sysctl all: for TARGET in $(TARGETS); do \ diff --git a/tools/testing/selftests/cpu-hotplug/Makefile b/tools/testing/selftests/cpu-hotplug/Makefile index ae5faf9aade2..790c23a9db44 100644 --- a/tools/testing/selftests/cpu-hotplug/Makefile +++ b/tools/testing/selftests/cpu-hotplug/Makefile @@ -1,6 +1,6 @@ all: run_tests: - @/bin/sh ./on-off-test.sh || echo "cpu-hotplug selftests: [FAIL]" + @/bin/bash ./on-off-test.sh || echo "cpu-hotplug selftests: [FAIL]" clean: diff --git a/tools/testing/selftests/ipc/msgque.c b/tools/testing/selftests/ipc/msgque.c index aa290c0de6f5..552f0810bffb 100644 --- a/tools/testing/selftests/ipc/msgque.c +++ b/tools/testing/selftests/ipc/msgque.c @@ -193,6 +193,11 @@ int main(int argc, char **argv) int msg, pid, err; struct msgque_data msgque; + if (getuid() != 0) { + printf("Please run the test as root - Exiting.\n"); + exit(1); + } + msgque.key = ftok(argv[0], 822155650); if (msgque.key == -1) { printf("Can't make key\n"); diff --git a/tools/testing/selftests/memory-hotplug/Makefile b/tools/testing/selftests/memory-hotplug/Makefile index 350bfeda3aa8..058c76f5d102 100644 --- a/tools/testing/selftests/memory-hotplug/Makefile +++ b/tools/testing/selftests/memory-hotplug/Makefile @@ -1,6 +1,6 @@ all: run_tests: - @/bin/sh ./on-off-test.sh || echo "memory-hotplug selftests: [FAIL]" + @/bin/bash ./on-off-test.sh || echo "memory-hotplug selftests: [FAIL]" clean: diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile index 750512ba2c88..c7493b8f9b0e 100644 --- a/tools/testing/selftests/net/Makefile +++ b/tools/testing/selftests/net/Makefile @@ -14,6 +14,12 @@ all: $(NET_PROGS) run_tests: all @/bin/sh ./run_netsocktests || echo "sockettests: [FAIL]" @/bin/sh ./run_afpackettests || echo "afpackettests: [FAIL]" - + @if /sbin/modprobe test_bpf ; then \ + /sbin/rmmod test_bpf; \ + echo "test_bpf: ok"; \ + else \ + echo "test_bpf: [FAIL]"; \ + exit 1; \ + fi clean: $(RM) $(NET_PROGS) diff --git a/tools/testing/selftests/powerpc/Makefile b/tools/testing/selftests/powerpc/Makefile index 316194f26ff4..54833a791a44 100644 --- a/tools/testing/selftests/powerpc/Makefile +++ b/tools/testing/selftests/powerpc/Makefile @@ -13,7 +13,7 @@ CFLAGS := -Wall -O2 -flto -Wall -Werror -DGIT_VERSION='"$(GIT_VERSION)"' -I$(CUR export CC CFLAGS -TARGETS = pmu copyloops +TARGETS = pmu copyloops mm tm endif diff --git a/tools/testing/selftests/powerpc/copyloops/asm/ppc_asm.h b/tools/testing/selftests/powerpc/copyloops/asm/ppc_asm.h index ccd9c84c4e3f..d1dc37425510 100644 --- a/tools/testing/selftests/powerpc/copyloops/asm/ppc_asm.h +++ b/tools/testing/selftests/powerpc/copyloops/asm/ppc_asm.h @@ -46,12 +46,15 @@ #define R20 r20 #define R21 r21 #define R22 r22 +#define R29 r29 +#define R30 r30 +#define R31 r31 #define STACKFRAMESIZE 256 -#define STK_PARAM(i) (48 + ((i)-3)*8) #define STK_REG(i) (112 + ((i)-14)*8) #define _GLOBAL(A) FUNC_START(test_ ## A) +#define _GLOBAL_TOC(A) _GLOBAL(A) #define PPC_MTOCRF(A, B) mtocrf A, B diff --git a/tools/testing/selftests/powerpc/harness.c b/tools/testing/selftests/powerpc/harness.c index e80c42a584fe..8ebc58a09311 100644 --- a/tools/testing/selftests/powerpc/harness.c +++ b/tools/testing/selftests/powerpc/harness.c @@ -30,12 +30,15 @@ int run_test(int (test_function)(void), char *name) pid = fork(); if (pid == 0) { + setpgid(0, 0); exit(test_function()); } else if (pid == -1) { perror("fork"); return 1; } + setpgid(pid, pid); + /* Wake us up in timeout seconds */ alarm(TIMEOUT); terminated = false; @@ -50,17 +53,20 @@ wait: if (terminated) { printf("!! force killing %s\n", name); - kill(pid, SIGKILL); + kill(-pid, SIGKILL); return 1; } else { printf("!! killing %s\n", name); - kill(pid, SIGTERM); + kill(-pid, SIGTERM); terminated = true; alarm(KILL_TIMEOUT); goto wait; } } + /* Kill anything else in the process group that is still running */ + kill(-pid, SIGTERM); + if (WIFEXITED(status)) status = WEXITSTATUS(status); else { @@ -99,7 +105,10 @@ int test_harness(int (test_function)(void), char *name) rc = run_test(test_function, name); - test_finish(name, rc); + if (rc == MAGIC_SKIP_RETURN_VALUE) + test_skip(name); + else + test_finish(name, rc); return rc; } diff --git a/tools/testing/selftests/powerpc/mm/Makefile b/tools/testing/selftests/powerpc/mm/Makefile new file mode 100644 index 000000000000..357ccbd6bad9 --- /dev/null +++ b/tools/testing/selftests/powerpc/mm/Makefile @@ -0,0 +1,18 @@ +noarg: + $(MAKE) -C ../ + +PROGS := hugetlb_vs_thp_test + +all: $(PROGS) + +$(PROGS): ../harness.c + +run_tests: all + @-for PROG in $(PROGS); do \ + ./$$PROG; \ + done; + +clean: + rm -f $(PROGS) + +.PHONY: all run_tests clean diff --git a/tools/testing/selftests/powerpc/mm/hugetlb_vs_thp_test.c b/tools/testing/selftests/powerpc/mm/hugetlb_vs_thp_test.c new file mode 100644 index 000000000000..3d8e5b033e1d --- /dev/null +++ b/tools/testing/selftests/powerpc/mm/hugetlb_vs_thp_test.c @@ -0,0 +1,72 @@ +#include <stdio.h> +#include <sys/mman.h> +#include <unistd.h> + +#include "utils.h" + +/* This must match the huge page & THP size */ +#define SIZE (16 * 1024 * 1024) + +static int test_body(void) +{ + void *addr; + char *p; + + addr = (void *)0xa0000000; + + p = mmap(addr, SIZE, PROT_READ | PROT_WRITE, + MAP_HUGETLB | MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + if (p != MAP_FAILED) { + /* + * Typically the mmap will fail because no huge pages are + * allocated on the system. But if there are huge pages + * allocated the mmap will succeed. That's fine too, we just + * munmap here before continuing. + */ + munmap(addr, SIZE); + } + + p = mmap(addr, SIZE, PROT_READ | PROT_WRITE, + MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + if (p == MAP_FAILED) { + printf("Mapping failed @ %p\n", addr); + perror("mmap"); + return 1; + } + + /* + * Either a user or kernel access is sufficient to trigger the bug. + * A kernel access is easier to spot & debug, as it will trigger the + * softlockup or RCU stall detectors, and when the system is kicked + * into xmon we get a backtrace in the kernel. + * + * A good option is: + * getcwd(p, SIZE); + * + * For the purposes of this testcase it's preferable to spin in + * userspace, so the harness can kill us if we get stuck. That way we + * see a test failure rather than a dead system. + */ + *p = 0xf; + + munmap(addr, SIZE); + + return 0; +} + +static int test_main(void) +{ + int i; + + /* 10,000 because it's a "bunch", and completes reasonably quickly */ + for (i = 0; i < 10000; i++) + if (test_body()) + return 1; + + return 0; +} + +int main(void) +{ + return test_harness(test_main, "hugetlb_vs_thp"); +} diff --git a/tools/testing/selftests/powerpc/pmu/Makefile b/tools/testing/selftests/powerpc/pmu/Makefile index 7216f0091655..b9ff0db42c79 100644 --- a/tools/testing/selftests/powerpc/pmu/Makefile +++ b/tools/testing/selftests/powerpc/pmu/Makefile @@ -4,7 +4,7 @@ noarg: PROGS := count_instructions EXTRA_SOURCES := ../harness.c event.c -all: $(PROGS) +all: $(PROGS) sub_all $(PROGS): $(EXTRA_SOURCES) @@ -12,12 +12,30 @@ $(PROGS): $(EXTRA_SOURCES) count_instructions: loop.S count_instructions.c $(EXTRA_SOURCES) $(CC) $(CFLAGS) -m64 -o $@ $^ -run_tests: all +run_tests: all sub_run_tests @-for PROG in $(PROGS); do \ ./$$PROG; \ done; -clean: +clean: sub_clean rm -f $(PROGS) loop.o -.PHONY: all run_tests clean + +SUB_TARGETS = ebb + +sub_all: + @for TARGET in $(SUB_TARGETS); do \ + $(MAKE) -C $$TARGET all; \ + done; + +sub_run_tests: all + @for TARGET in $(SUB_TARGETS); do \ + $(MAKE) -C $$TARGET run_tests; \ + done; + +sub_clean: + @for TARGET in $(SUB_TARGETS); do \ + $(MAKE) -C $$TARGET clean; \ + done; + +.PHONY: all run_tests clean sub_all sub_run_tests sub_clean diff --git a/tools/testing/selftests/powerpc/pmu/ebb/Makefile b/tools/testing/selftests/powerpc/pmu/ebb/Makefile new file mode 100644 index 000000000000..edbba2affc2c --- /dev/null +++ b/tools/testing/selftests/powerpc/pmu/ebb/Makefile @@ -0,0 +1,32 @@ +noarg: + $(MAKE) -C ../../ + +# The EBB handler is 64-bit code and everything links against it +CFLAGS += -m64 + +PROGS := reg_access_test event_attributes_test cycles_test \ + cycles_with_freeze_test pmc56_overflow_test \ + ebb_vs_cpu_event_test cpu_event_vs_ebb_test \ + cpu_event_pinned_vs_ebb_test task_event_vs_ebb_test \ + task_event_pinned_vs_ebb_test multi_ebb_procs_test \ + multi_counter_test pmae_handling_test \ + close_clears_pmcc_test instruction_count_test \ + fork_cleanup_test ebb_on_child_test \ + ebb_on_willing_child_test back_to_back_ebbs_test \ + lost_exception_test no_handler_test + +all: $(PROGS) + +$(PROGS): ../../harness.c ../event.c ../lib.c ebb.c ebb_handler.S trace.c + +instruction_count_test: ../loop.S + +lost_exception_test: ../lib.c + +run_tests: all + @-for PROG in $(PROGS); do \ + ./$$PROG; \ + done; + +clean: + rm -f $(PROGS) diff --git a/tools/testing/selftests/powerpc/pmu/ebb/back_to_back_ebbs_test.c b/tools/testing/selftests/powerpc/pmu/ebb/back_to_back_ebbs_test.c new file mode 100644 index 000000000000..66ea765c0e72 --- /dev/null +++ b/tools/testing/selftests/powerpc/pmu/ebb/back_to_back_ebbs_test.c @@ -0,0 +1,106 @@ +/* + * Copyright 2014, Michael Ellerman, IBM Corp. + * Licensed under GPLv2. + */ + +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> + +#include "ebb.h" + + +#define NUMBER_OF_EBBS 50 + +/* + * Test that if we overflow the counter while in the EBB handler, we take + * another EBB on exiting from the handler. + * + * We do this by counting with a stupidly low sample period, causing us to + * overflow the PMU while we're still in the EBB handler, leading to another + * EBB. + * + * We get out of what would otherwise be an infinite loop by leaving the + * counter frozen once we've taken enough EBBs. + */ + +static void ebb_callee(void) +{ + uint64_t siar, val; + + val = mfspr(SPRN_BESCR); + if (!(val & BESCR_PMEO)) { + ebb_state.stats.spurious++; + goto out; + } + + ebb_state.stats.ebb_count++; + trace_log_counter(ebb_state.trace, ebb_state.stats.ebb_count); + + /* Resets the PMC */ + count_pmc(1, sample_period); + +out: + if (ebb_state.stats.ebb_count == NUMBER_OF_EBBS) + /* Reset but leave counters frozen */ + reset_ebb_with_clear_mask(MMCR0_PMAO); + else + /* Unfreezes */ + reset_ebb(); + + /* Do some stuff to chew some cycles and pop the counter */ + siar = mfspr(SPRN_SIAR); + trace_log_reg(ebb_state.trace, SPRN_SIAR, siar); + + val = mfspr(SPRN_PMC1); + trace_log_reg(ebb_state.trace, SPRN_PMC1, val); + + val = mfspr(SPRN_MMCR0); + trace_log_reg(ebb_state.trace, SPRN_MMCR0, val); +} + +int back_to_back_ebbs(void) +{ + struct event event; + + event_init_named(&event, 0x1001e, "cycles"); + event_leader_ebb_init(&event); + + event.attr.exclude_kernel = 1; + event.attr.exclude_hv = 1; + event.attr.exclude_idle = 1; + + FAIL_IF(event_open(&event)); + + setup_ebb_handler(ebb_callee); + + FAIL_IF(ebb_event_enable(&event)); + + sample_period = 5; + + ebb_freeze_pmcs(); + mtspr(SPRN_PMC1, pmc_sample_period(sample_period)); + ebb_global_enable(); + ebb_unfreeze_pmcs(); + + while (ebb_state.stats.ebb_count < NUMBER_OF_EBBS) + FAIL_IF(core_busy_loop()); + + ebb_global_disable(); + ebb_freeze_pmcs(); + + count_pmc(1, sample_period); + + dump_ebb_state(); + + event_close(&event); + + FAIL_IF(ebb_state.stats.ebb_count != NUMBER_OF_EBBS); + + return 0; +} + +int main(void) +{ + return test_harness(back_to_back_ebbs, "back_to_back_ebbs"); +} diff --git a/tools/testing/selftests/powerpc/pmu/ebb/close_clears_pmcc_test.c b/tools/testing/selftests/powerpc/pmu/ebb/close_clears_pmcc_test.c new file mode 100644 index 000000000000..0f0423dba18b --- /dev/null +++ b/tools/testing/selftests/powerpc/pmu/ebb/close_clears_pmcc_test.c @@ -0,0 +1,59 @@ +/* + * Copyright 2014, Michael Ellerman, IBM Corp. + * Licensed under GPLv2. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <setjmp.h> +#include <signal.h> + +#include "ebb.h" + + +/* + * Test that closing the EBB event clears MMCR0_PMCC, preventing further access + * by userspace to the PMU hardware. + */ + +int close_clears_pmcc(void) +{ + struct event event; + + event_init_named(&event, 0x1001e, "cycles"); + event_leader_ebb_init(&event); + + FAIL_IF(event_open(&event)); + + ebb_enable_pmc_counting(1); + setup_ebb_handler(standard_ebb_callee); + ebb_global_enable(); + FAIL_IF(ebb_event_enable(&event)); + + mtspr(SPRN_PMC1, pmc_sample_period(sample_period)); + + while (ebb_state.stats.ebb_count < 1) + FAIL_IF(core_busy_loop()); + + ebb_global_disable(); + event_close(&event); + + FAIL_IF(ebb_state.stats.ebb_count == 0); + + /* The real test is here, do we take a SIGILL when writing PMU regs now + * that we have closed the event. We expect that we will. */ + + FAIL_IF(catch_sigill(write_pmc1)); + + /* We should still be able to read EBB regs though */ + mfspr(SPRN_EBBHR); + mfspr(SPRN_EBBRR); + mfspr(SPRN_BESCR); + + return 0; +} + +int main(void) +{ + return test_harness(close_clears_pmcc, "close_clears_pmcc"); +} diff --git a/tools/testing/selftests/powerpc/pmu/ebb/cpu_event_pinned_vs_ebb_test.c b/tools/testing/selftests/powerpc/pmu/ebb/cpu_event_pinned_vs_ebb_test.c new file mode 100644 index 000000000000..d3ed64d5d6c0 --- /dev/null +++ b/tools/testing/selftests/powerpc/pmu/ebb/cpu_event_pinned_vs_ebb_test.c @@ -0,0 +1,93 @@ +/* + * Copyright 2014, Michael Ellerman, IBM Corp. + * Licensed under GPLv2. + */ + +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "ebb.h" + + +/* + * Tests a pinned cpu event vs an EBB - in that order. The pinned cpu event + * should remain and the EBB event should fail to enable. + */ + +static int setup_cpu_event(struct event *event, int cpu) +{ + event_init_named(event, 0x400FA, "PM_RUN_INST_CMPL"); + + event->attr.pinned = 1; + + event->attr.exclude_kernel = 1; + event->attr.exclude_hv = 1; + event->attr.exclude_idle = 1; + + SKIP_IF(require_paranoia_below(1)); + FAIL_IF(event_open_with_cpu(event, cpu)); + FAIL_IF(event_enable(event)); + + return 0; +} + +int cpu_event_pinned_vs_ebb(void) +{ + union pipe read_pipe, write_pipe; + struct event event; + int cpu, rc; + pid_t pid; + + cpu = pick_online_cpu(); + FAIL_IF(cpu < 0); + FAIL_IF(bind_to_cpu(cpu)); + + FAIL_IF(pipe(read_pipe.fds) == -1); + FAIL_IF(pipe(write_pipe.fds) == -1); + + pid = fork(); + if (pid == 0) { + /* NB order of pipes looks reversed */ + exit(ebb_child(write_pipe, read_pipe)); + } + + /* We setup the cpu event first */ + rc = setup_cpu_event(&event, cpu); + if (rc) { + kill_child_and_wait(pid); + return rc; + } + + /* Signal the child to install its EBB event and wait */ + if (sync_with_child(read_pipe, write_pipe)) + /* If it fails, wait for it to exit */ + goto wait; + + /* Signal the child to run */ + FAIL_IF(sync_with_child(read_pipe, write_pipe)); + +wait: + /* We expect it to fail to read the event */ + FAIL_IF(wait_for_child(pid) != 2); + + FAIL_IF(event_disable(&event)); + FAIL_IF(event_read(&event)); + + event_report(&event); + + /* The cpu event should have run */ + FAIL_IF(event.result.value == 0); + FAIL_IF(event.result.enabled != event.result.running); + + return 0; +} + +int main(void) +{ + return test_harness(cpu_event_pinned_vs_ebb, "cpu_event_pinned_vs_ebb"); +} diff --git a/tools/testing/selftests/powerpc/pmu/ebb/cpu_event_vs_ebb_test.c b/tools/testing/selftests/powerpc/pmu/ebb/cpu_event_vs_ebb_test.c new file mode 100644 index 000000000000..8b972c2aa392 --- /dev/null +++ b/tools/testing/selftests/powerpc/pmu/ebb/cpu_event_vs_ebb_test.c @@ -0,0 +1,89 @@ +/* + * Copyright 2014, Michael Ellerman, IBM Corp. + * Licensed under GPLv2. + */ + +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "ebb.h" + + +/* + * Tests a cpu event vs an EBB - in that order. The EBB should force the cpu + * event off the PMU. + */ + +static int setup_cpu_event(struct event *event, int cpu) +{ + event_init_named(event, 0x400FA, "PM_RUN_INST_CMPL"); + + event->attr.exclude_kernel = 1; + event->attr.exclude_hv = 1; + event->attr.exclude_idle = 1; + + SKIP_IF(require_paranoia_below(1)); + FAIL_IF(event_open_with_cpu(event, cpu)); + FAIL_IF(event_enable(event)); + + return 0; +} + +int cpu_event_vs_ebb(void) +{ + union pipe read_pipe, write_pipe; + struct event event; + int cpu, rc; + pid_t pid; + + cpu = pick_online_cpu(); + FAIL_IF(cpu < 0); + FAIL_IF(bind_to_cpu(cpu)); + + FAIL_IF(pipe(read_pipe.fds) == -1); + FAIL_IF(pipe(write_pipe.fds) == -1); + + pid = fork(); + if (pid == 0) { + /* NB order of pipes looks reversed */ + exit(ebb_child(write_pipe, read_pipe)); + } + + /* We setup the cpu event first */ + rc = setup_cpu_event(&event, cpu); + if (rc) { + kill_child_and_wait(pid); + return rc; + } + + /* Signal the child to install its EBB event and wait */ + if (sync_with_child(read_pipe, write_pipe)) + /* If it fails, wait for it to exit */ + goto wait; + + /* Signal the child to run */ + FAIL_IF(sync_with_child(read_pipe, write_pipe)); + +wait: + /* We expect the child to succeed */ + FAIL_IF(wait_for_child(pid)); + + FAIL_IF(event_disable(&event)); + FAIL_IF(event_read(&event)); + + event_report(&event); + + /* The cpu event may have run */ + + return 0; +} + +int main(void) +{ + return test_harness(cpu_event_vs_ebb, "cpu_event_vs_ebb"); +} diff --git a/tools/testing/selftests/powerpc/pmu/ebb/cycles_test.c b/tools/testing/selftests/powerpc/pmu/ebb/cycles_test.c new file mode 100644 index 000000000000..8590fc1bfc0d --- /dev/null +++ b/tools/testing/selftests/powerpc/pmu/ebb/cycles_test.c @@ -0,0 +1,58 @@ +/* + * Copyright 2014, Michael Ellerman, IBM Corp. + * Licensed under GPLv2. + */ + +#include <stdio.h> +#include <stdlib.h> + +#include "ebb.h" + + +/* + * Basic test that counts user cycles and takes EBBs. + */ +int cycles(void) +{ + struct event event; + + event_init_named(&event, 0x1001e, "cycles"); + event_leader_ebb_init(&event); + + event.attr.exclude_kernel = 1; + event.attr.exclude_hv = 1; + event.attr.exclude_idle = 1; + + FAIL_IF(event_open(&event)); + + ebb_enable_pmc_counting(1); + setup_ebb_handler(standard_ebb_callee); + ebb_global_enable(); + FAIL_IF(ebb_event_enable(&event)); + + mtspr(SPRN_PMC1, pmc_sample_period(sample_period)); + + while (ebb_state.stats.ebb_count < 10) { + FAIL_IF(core_busy_loop()); + FAIL_IF(ebb_check_mmcr0()); + } + + ebb_global_disable(); + ebb_freeze_pmcs(); + + count_pmc(1, sample_period); + + dump_ebb_state(); + + event_close(&event); + + FAIL_IF(ebb_state.stats.ebb_count == 0); + FAIL_IF(!ebb_check_count(1, sample_period, 100)); + + return 0; +} + +int main(void) +{ + return test_harness(cycles, "cycles"); +} diff --git a/tools/testing/selftests/powerpc/pmu/ebb/cycles_with_freeze_test.c b/tools/testing/selftests/powerpc/pmu/ebb/cycles_with_freeze_test.c new file mode 100644 index 000000000000..754b3f2008d3 --- /dev/null +++ b/tools/testing/selftests/powerpc/pmu/ebb/cycles_with_freeze_test.c @@ -0,0 +1,117 @@ +/* + * Copyright 2014, Michael Ellerman, IBM Corp. + * Licensed under GPLv2. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> + +#include "ebb.h" + + +/* + * Test of counting cycles while using MMCR0_FC (freeze counters) to only count + * parts of the code. This is complicated by the fact that FC is set by the + * hardware when the event overflows. We may take the EBB after we have set FC, + * so we have to be careful about whether we clear FC at the end of the EBB + * handler or not. + */ + +static bool counters_frozen = false; +static int ebbs_while_frozen = 0; + +static void ebb_callee(void) +{ + uint64_t mask, val; + + mask = MMCR0_PMAO | MMCR0_FC; + + val = mfspr(SPRN_BESCR); + if (!(val & BESCR_PMEO)) { + ebb_state.stats.spurious++; + goto out; + } + + ebb_state.stats.ebb_count++; + trace_log_counter(ebb_state.trace, ebb_state.stats.ebb_count); + + val = mfspr(SPRN_MMCR0); + trace_log_reg(ebb_state.trace, SPRN_MMCR0, val); + + if (counters_frozen) { + trace_log_string(ebb_state.trace, "frozen"); + ebbs_while_frozen++; + mask &= ~MMCR0_FC; + } + + count_pmc(1, sample_period); +out: + reset_ebb_with_clear_mask(mask); +} + +int cycles_with_freeze(void) +{ + struct event event; + uint64_t val; + bool fc_cleared; + + event_init_named(&event, 0x1001e, "cycles"); + event_leader_ebb_init(&event); + + event.attr.exclude_kernel = 1; + event.attr.exclude_hv = 1; + event.attr.exclude_idle = 1; + + FAIL_IF(event_open(&event)); + + setup_ebb_handler(ebb_callee); + ebb_global_enable(); + FAIL_IF(ebb_event_enable(&event)); + + mtspr(SPRN_PMC1, pmc_sample_period(sample_period)); + + fc_cleared = false; + + /* Make sure we loop until we take at least one EBB */ + while ((ebb_state.stats.ebb_count < 20 && !fc_cleared) || + ebb_state.stats.ebb_count < 1) + { + counters_frozen = false; + mb(); + mtspr(SPRN_MMCR0, mfspr(SPRN_MMCR0) & ~MMCR0_FC); + + FAIL_IF(core_busy_loop()); + + counters_frozen = true; + mb(); + mtspr(SPRN_MMCR0, mfspr(SPRN_MMCR0) | MMCR0_FC); + + val = mfspr(SPRN_MMCR0); + if (! (val & MMCR0_FC)) { + printf("Outside of loop, FC NOT set MMCR0 0x%lx\n", val); + fc_cleared = true; + } + } + + ebb_global_disable(); + ebb_freeze_pmcs(); + + count_pmc(1, sample_period); + + dump_ebb_state(); + + printf("EBBs while frozen %d\n", ebbs_while_frozen); + + event_close(&event); + + FAIL_IF(ebb_state.stats.ebb_count == 0); + FAIL_IF(fc_cleared); + + return 0; +} + +int main(void) +{ + return test_harness(cycles_with_freeze, "cycles_with_freeze"); +} diff --git a/tools/testing/selftests/powerpc/pmu/ebb/ebb.c b/tools/testing/selftests/powerpc/pmu/ebb/ebb.c new file mode 100644 index 000000000000..1b46be94b64c --- /dev/null +++ b/tools/testing/selftests/powerpc/pmu/ebb/ebb.c @@ -0,0 +1,727 @@ +/* + * Copyright 2014, Michael Ellerman, IBM Corp. + * Licensed under GPLv2. + */ + +#define _GNU_SOURCE /* For CPU_ZERO etc. */ + +#include <sched.h> +#include <sys/wait.h> +#include <setjmp.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> + +#include "trace.h" +#include "reg.h" +#include "ebb.h" + + +void (*ebb_user_func)(void); + +void ebb_hook(void) +{ + if (ebb_user_func) + ebb_user_func(); +} + +struct ebb_state ebb_state; + +u64 sample_period = 0x40000000ull; + +void reset_ebb_with_clear_mask(unsigned long mmcr0_clear_mask) +{ + u64 val; + + /* 2) clear MMCR0[PMAO] - docs say BESCR[PMEO] should do this */ + /* 3) set MMCR0[PMAE] - docs say BESCR[PME] should do this */ + val = mfspr(SPRN_MMCR0); + mtspr(SPRN_MMCR0, (val & ~mmcr0_clear_mask) | MMCR0_PMAE); + + /* 4) clear BESCR[PMEO] */ + mtspr(SPRN_BESCRR, BESCR_PMEO); + + /* 5) set BESCR[PME] */ + mtspr(SPRN_BESCRS, BESCR_PME); + + /* 6) rfebb 1 - done in our caller */ +} + +void reset_ebb(void) +{ + reset_ebb_with_clear_mask(MMCR0_PMAO | MMCR0_FC); +} + +/* Called outside of the EBB handler to check MMCR0 is sane */ +int ebb_check_mmcr0(void) +{ + u64 val; + + val = mfspr(SPRN_MMCR0); + if ((val & (MMCR0_FC | MMCR0_PMAO)) == MMCR0_FC) { + /* It's OK if we see FC & PMAO, but not FC by itself */ + printf("Outside of loop, only FC set 0x%llx\n", val); + return 1; + } + + return 0; +} + +bool ebb_check_count(int pmc, u64 sample_period, int fudge) +{ + u64 count, upper, lower; + + count = ebb_state.stats.pmc_count[PMC_INDEX(pmc)]; + + lower = ebb_state.stats.ebb_count * (sample_period - fudge); + + if (count < lower) { + printf("PMC%d count (0x%llx) below lower limit 0x%llx (-0x%llx)\n", + pmc, count, lower, lower - count); + return false; + } + + upper = ebb_state.stats.ebb_count * (sample_period + fudge); + + if (count > upper) { + printf("PMC%d count (0x%llx) above upper limit 0x%llx (+0x%llx)\n", + pmc, count, upper, count - upper); + return false; + } + + printf("PMC%d count (0x%llx) is between 0x%llx and 0x%llx delta +0x%llx/-0x%llx\n", + pmc, count, lower, upper, count - lower, upper - count); + + return true; +} + +void standard_ebb_callee(void) +{ + int found, i; + u64 val; + + val = mfspr(SPRN_BESCR); + if (!(val & BESCR_PMEO)) { + ebb_state.stats.spurious++; + goto out; + } + + ebb_state.stats.ebb_count++; + trace_log_counter(ebb_state.trace, ebb_state.stats.ebb_count); + + val = mfspr(SPRN_MMCR0); + trace_log_reg(ebb_state.trace, SPRN_MMCR0, val); + + found = 0; + for (i = 1; i <= 6; i++) { + if (ebb_state.pmc_enable[PMC_INDEX(i)]) + found += count_pmc(i, sample_period); + } + + if (!found) + ebb_state.stats.no_overflow++; + +out: + reset_ebb(); +} + +extern void ebb_handler(void); + +void setup_ebb_handler(void (*callee)(void)) +{ + u64 entry; + +#if defined(_CALL_ELF) && _CALL_ELF == 2 + entry = (u64)ebb_handler; +#else + struct opd + { + u64 entry; + u64 toc; + } *opd; + + opd = (struct opd *)ebb_handler; + entry = opd->entry; +#endif + printf("EBB Handler is at %#llx\n", entry); + + ebb_user_func = callee; + + /* Ensure ebb_user_func is set before we set the handler */ + mb(); + mtspr(SPRN_EBBHR, entry); + + /* Make sure the handler is set before we return */ + mb(); +} + +void clear_ebb_stats(void) +{ + memset(&ebb_state.stats, 0, sizeof(ebb_state.stats)); +} + +void dump_summary_ebb_state(void) +{ + printf("ebb_state:\n" \ + " ebb_count = %d\n" \ + " spurious = %d\n" \ + " negative = %d\n" \ + " no_overflow = %d\n" \ + " pmc[1] count = 0x%llx\n" \ + " pmc[2] count = 0x%llx\n" \ + " pmc[3] count = 0x%llx\n" \ + " pmc[4] count = 0x%llx\n" \ + " pmc[5] count = 0x%llx\n" \ + " pmc[6] count = 0x%llx\n", + ebb_state.stats.ebb_count, ebb_state.stats.spurious, + ebb_state.stats.negative, ebb_state.stats.no_overflow, + ebb_state.stats.pmc_count[0], ebb_state.stats.pmc_count[1], + ebb_state.stats.pmc_count[2], ebb_state.stats.pmc_count[3], + ebb_state.stats.pmc_count[4], ebb_state.stats.pmc_count[5]); +} + +static char *decode_mmcr0(u32 value) +{ + static char buf[16]; + + buf[0] = '\0'; + + if (value & (1 << 31)) + strcat(buf, "FC "); + if (value & (1 << 26)) + strcat(buf, "PMAE "); + if (value & (1 << 7)) + strcat(buf, "PMAO "); + + return buf; +} + +static char *decode_bescr(u64 value) +{ + static char buf[16]; + + buf[0] = '\0'; + + if (value & (1ull << 63)) + strcat(buf, "GE "); + if (value & (1ull << 32)) + strcat(buf, "PMAE "); + if (value & 1) + strcat(buf, "PMAO "); + + return buf; +} + +void dump_ebb_hw_state(void) +{ + u64 bescr; + u32 mmcr0; + + mmcr0 = mfspr(SPRN_MMCR0); + bescr = mfspr(SPRN_BESCR); + + printf("HW state:\n" \ + "MMCR0 0x%016x %s\n" \ + "EBBHR 0x%016lx\n" \ + "BESCR 0x%016llx %s\n" \ + "PMC1 0x%016lx\n" \ + "PMC2 0x%016lx\n" \ + "PMC3 0x%016lx\n" \ + "PMC4 0x%016lx\n" \ + "PMC5 0x%016lx\n" \ + "PMC6 0x%016lx\n" \ + "SIAR 0x%016lx\n", + mmcr0, decode_mmcr0(mmcr0), mfspr(SPRN_EBBHR), bescr, + decode_bescr(bescr), mfspr(SPRN_PMC1), mfspr(SPRN_PMC2), + mfspr(SPRN_PMC3), mfspr(SPRN_PMC4), mfspr(SPRN_PMC5), + mfspr(SPRN_PMC6), mfspr(SPRN_SIAR)); +} + +void dump_ebb_state(void) +{ + dump_summary_ebb_state(); + + dump_ebb_hw_state(); + + trace_buffer_print(ebb_state.trace); +} + +int count_pmc(int pmc, uint32_t sample_period) +{ + uint32_t start_value; + u64 val; + + /* 0) Read PMC */ + start_value = pmc_sample_period(sample_period); + + val = read_pmc(pmc); + if (val < start_value) + ebb_state.stats.negative++; + else + ebb_state.stats.pmc_count[PMC_INDEX(pmc)] += val - start_value; + + trace_log_reg(ebb_state.trace, SPRN_PMC1 + pmc - 1, val); + + /* 1) Reset PMC */ + write_pmc(pmc, start_value); + + /* Report if we overflowed */ + return val >= COUNTER_OVERFLOW; +} + +int ebb_event_enable(struct event *e) +{ + int rc; + + /* Ensure any SPR writes are ordered vs us */ + mb(); + + rc = ioctl(e->fd, PERF_EVENT_IOC_ENABLE); + if (rc) + return rc; + + rc = event_read(e); + + /* Ditto */ + mb(); + + return rc; +} + +void ebb_freeze_pmcs(void) +{ + mtspr(SPRN_MMCR0, mfspr(SPRN_MMCR0) | MMCR0_FC); + mb(); +} + +void ebb_unfreeze_pmcs(void) +{ + /* Unfreeze counters */ + mtspr(SPRN_MMCR0, mfspr(SPRN_MMCR0) & ~MMCR0_FC); + mb(); +} + +void ebb_global_enable(void) +{ + /* Enable EBBs globally and PMU EBBs */ + mtspr(SPRN_BESCR, 0x8000000100000000ull); + mb(); +} + +void ebb_global_disable(void) +{ + /* Disable EBBs & freeze counters, events are still scheduled */ + mtspr(SPRN_BESCRR, BESCR_PME); + mb(); +} + +void event_ebb_init(struct event *e) +{ + e->attr.config |= (1ull << 63); +} + +void event_bhrb_init(struct event *e, unsigned ifm) +{ + e->attr.config |= (1ull << 62) | ((u64)ifm << 60); +} + +void event_leader_ebb_init(struct event *e) +{ + event_ebb_init(e); + + e->attr.exclusive = 1; + e->attr.pinned = 1; +} + +int core_busy_loop(void) +{ + int rc; + + asm volatile ( + "li 3, 0x3030\n" + "std 3, -96(1)\n" + "li 4, 0x4040\n" + "std 4, -104(1)\n" + "li 5, 0x5050\n" + "std 5, -112(1)\n" + "li 6, 0x6060\n" + "std 6, -120(1)\n" + "li 7, 0x7070\n" + "std 7, -128(1)\n" + "li 8, 0x0808\n" + "std 8, -136(1)\n" + "li 9, 0x0909\n" + "std 9, -144(1)\n" + "li 10, 0x1010\n" + "std 10, -152(1)\n" + "li 11, 0x1111\n" + "std 11, -160(1)\n" + "li 14, 0x1414\n" + "std 14, -168(1)\n" + "li 15, 0x1515\n" + "std 15, -176(1)\n" + "li 16, 0x1616\n" + "std 16, -184(1)\n" + "li 17, 0x1717\n" + "std 17, -192(1)\n" + "li 18, 0x1818\n" + "std 18, -200(1)\n" + "li 19, 0x1919\n" + "std 19, -208(1)\n" + "li 20, 0x2020\n" + "std 20, -216(1)\n" + "li 21, 0x2121\n" + "std 21, -224(1)\n" + "li 22, 0x2222\n" + "std 22, -232(1)\n" + "li 23, 0x2323\n" + "std 23, -240(1)\n" + "li 24, 0x2424\n" + "std 24, -248(1)\n" + "li 25, 0x2525\n" + "std 25, -256(1)\n" + "li 26, 0x2626\n" + "std 26, -264(1)\n" + "li 27, 0x2727\n" + "std 27, -272(1)\n" + "li 28, 0x2828\n" + "std 28, -280(1)\n" + "li 29, 0x2929\n" + "std 29, -288(1)\n" + "li 30, 0x3030\n" + "li 31, 0x3131\n" + + "li 3, 0\n" + "0: " + "addi 3, 3, 1\n" + "cmpwi 3, 100\n" + "blt 0b\n" + + /* Return 1 (fail) unless we get through all the checks */ + "li 0, 1\n" + + /* Check none of our registers have been corrupted */ + "cmpwi 4, 0x4040\n" + "bne 1f\n" + "cmpwi 5, 0x5050\n" + "bne 1f\n" + "cmpwi 6, 0x6060\n" + "bne 1f\n" + "cmpwi 7, 0x7070\n" + "bne 1f\n" + "cmpwi 8, 0x0808\n" + "bne 1f\n" + "cmpwi 9, 0x0909\n" + "bne 1f\n" + "cmpwi 10, 0x1010\n" + "bne 1f\n" + "cmpwi 11, 0x1111\n" + "bne 1f\n" + "cmpwi 14, 0x1414\n" + "bne 1f\n" + "cmpwi 15, 0x1515\n" + "bne 1f\n" + "cmpwi 16, 0x1616\n" + "bne 1f\n" + "cmpwi 17, 0x1717\n" + "bne 1f\n" + "cmpwi 18, 0x1818\n" + "bne 1f\n" + "cmpwi 19, 0x1919\n" + "bne 1f\n" + "cmpwi 20, 0x2020\n" + "bne 1f\n" + "cmpwi 21, 0x2121\n" + "bne 1f\n" + "cmpwi 22, 0x2222\n" + "bne 1f\n" + "cmpwi 23, 0x2323\n" + "bne 1f\n" + "cmpwi 24, 0x2424\n" + "bne 1f\n" + "cmpwi 25, 0x2525\n" + "bne 1f\n" + "cmpwi 26, 0x2626\n" + "bne 1f\n" + "cmpwi 27, 0x2727\n" + "bne 1f\n" + "cmpwi 28, 0x2828\n" + "bne 1f\n" + "cmpwi 29, 0x2929\n" + "bne 1f\n" + "cmpwi 30, 0x3030\n" + "bne 1f\n" + "cmpwi 31, 0x3131\n" + "bne 1f\n" + + /* Load junk into all our registers before we reload them from the stack. */ + "li 3, 0xde\n" + "li 4, 0xad\n" + "li 5, 0xbe\n" + "li 6, 0xef\n" + "li 7, 0xde\n" + "li 8, 0xad\n" + "li 9, 0xbe\n" + "li 10, 0xef\n" + "li 11, 0xde\n" + "li 14, 0xad\n" + "li 15, 0xbe\n" + "li 16, 0xef\n" + "li 17, 0xde\n" + "li 18, 0xad\n" + "li 19, 0xbe\n" + "li 20, 0xef\n" + "li 21, 0xde\n" + "li 22, 0xad\n" + "li 23, 0xbe\n" + "li 24, 0xef\n" + "li 25, 0xde\n" + "li 26, 0xad\n" + "li 27, 0xbe\n" + "li 28, 0xef\n" + "li 29, 0xdd\n" + + "ld 3, -96(1)\n" + "cmpwi 3, 0x3030\n" + "bne 1f\n" + "ld 4, -104(1)\n" + "cmpwi 4, 0x4040\n" + "bne 1f\n" + "ld 5, -112(1)\n" + "cmpwi 5, 0x5050\n" + "bne 1f\n" + "ld 6, -120(1)\n" + "cmpwi 6, 0x6060\n" + "bne 1f\n" + "ld 7, -128(1)\n" + "cmpwi 7, 0x7070\n" + "bne 1f\n" + "ld 8, -136(1)\n" + "cmpwi 8, 0x0808\n" + "bne 1f\n" + "ld 9, -144(1)\n" + "cmpwi 9, 0x0909\n" + "bne 1f\n" + "ld 10, -152(1)\n" + "cmpwi 10, 0x1010\n" + "bne 1f\n" + "ld 11, -160(1)\n" + "cmpwi 11, 0x1111\n" + "bne 1f\n" + "ld 14, -168(1)\n" + "cmpwi 14, 0x1414\n" + "bne 1f\n" + "ld 15, -176(1)\n" + "cmpwi 15, 0x1515\n" + "bne 1f\n" + "ld 16, -184(1)\n" + "cmpwi 16, 0x1616\n" + "bne 1f\n" + "ld 17, -192(1)\n" + "cmpwi 17, 0x1717\n" + "bne 1f\n" + "ld 18, -200(1)\n" + "cmpwi 18, 0x1818\n" + "bne 1f\n" + "ld 19, -208(1)\n" + "cmpwi 19, 0x1919\n" + "bne 1f\n" + "ld 20, -216(1)\n" + "cmpwi 20, 0x2020\n" + "bne 1f\n" + "ld 21, -224(1)\n" + "cmpwi 21, 0x2121\n" + "bne 1f\n" + "ld 22, -232(1)\n" + "cmpwi 22, 0x2222\n" + "bne 1f\n" + "ld 23, -240(1)\n" + "cmpwi 23, 0x2323\n" + "bne 1f\n" + "ld 24, -248(1)\n" + "cmpwi 24, 0x2424\n" + "bne 1f\n" + "ld 25, -256(1)\n" + "cmpwi 25, 0x2525\n" + "bne 1f\n" + "ld 26, -264(1)\n" + "cmpwi 26, 0x2626\n" + "bne 1f\n" + "ld 27, -272(1)\n" + "cmpwi 27, 0x2727\n" + "bne 1f\n" + "ld 28, -280(1)\n" + "cmpwi 28, 0x2828\n" + "bne 1f\n" + "ld 29, -288(1)\n" + "cmpwi 29, 0x2929\n" + "bne 1f\n" + + /* Load 0 (success) to return */ + "li 0, 0\n" + + "1: mr %0, 0\n" + + : "=r" (rc) + : /* no inputs */ + : "3", "4", "5", "6", "7", "8", "9", "10", "11", "14", + "15", "16", "17", "18", "19", "20", "21", "22", "23", + "24", "25", "26", "27", "28", "29", "30", "31", + "memory" + ); + + return rc; +} + +int core_busy_loop_with_freeze(void) +{ + int rc; + + mtspr(SPRN_MMCR0, mfspr(SPRN_MMCR0) & ~MMCR0_FC); + rc = core_busy_loop(); + mtspr(SPRN_MMCR0, mfspr(SPRN_MMCR0) | MMCR0_FC); + + return rc; +} + +int ebb_child(union pipe read_pipe, union pipe write_pipe) +{ + struct event event; + uint64_t val; + + FAIL_IF(wait_for_parent(read_pipe)); + + event_init_named(&event, 0x1001e, "cycles"); + event_leader_ebb_init(&event); + + event.attr.exclude_kernel = 1; + event.attr.exclude_hv = 1; + event.attr.exclude_idle = 1; + + FAIL_IF(event_open(&event)); + + ebb_enable_pmc_counting(1); + setup_ebb_handler(standard_ebb_callee); + ebb_global_enable(); + + FAIL_IF(event_enable(&event)); + + if (event_read(&event)) { + /* + * Some tests expect to fail here, so don't report an error on + * this line, and return a distinguisable error code. Tell the + * parent an error happened. + */ + notify_parent_of_error(write_pipe); + return 2; + } + + mtspr(SPRN_PMC1, pmc_sample_period(sample_period)); + + FAIL_IF(notify_parent(write_pipe)); + FAIL_IF(wait_for_parent(read_pipe)); + FAIL_IF(notify_parent(write_pipe)); + + while (ebb_state.stats.ebb_count < 20) { + FAIL_IF(core_busy_loop()); + + /* To try and hit SIGILL case */ + val = mfspr(SPRN_MMCRA); + val |= mfspr(SPRN_MMCR2); + val |= mfspr(SPRN_MMCR0); + } + + ebb_global_disable(); + ebb_freeze_pmcs(); + + count_pmc(1, sample_period); + + dump_ebb_state(); + + event_close(&event); + + FAIL_IF(ebb_state.stats.ebb_count == 0); + + return 0; +} + +static jmp_buf setjmp_env; + +static void sigill_handler(int signal) +{ + printf("Took sigill\n"); + longjmp(setjmp_env, 1); +} + +static struct sigaction sigill_action = { + .sa_handler = sigill_handler, +}; + +int catch_sigill(void (*func)(void)) +{ + if (sigaction(SIGILL, &sigill_action, NULL)) { + perror("sigaction"); + return 1; + } + + if (setjmp(setjmp_env) == 0) { + func(); + return 1; + } + + return 0; +} + +void write_pmc1(void) +{ + mtspr(SPRN_PMC1, 0); +} + +void write_pmc(int pmc, u64 value) +{ + switch (pmc) { + case 1: mtspr(SPRN_PMC1, value); break; + case 2: mtspr(SPRN_PMC2, value); break; + case 3: mtspr(SPRN_PMC3, value); break; + case 4: mtspr(SPRN_PMC4, value); break; + case 5: mtspr(SPRN_PMC5, value); break; + case 6: mtspr(SPRN_PMC6, value); break; + } +} + +u64 read_pmc(int pmc) +{ + switch (pmc) { + case 1: return mfspr(SPRN_PMC1); + case 2: return mfspr(SPRN_PMC2); + case 3: return mfspr(SPRN_PMC3); + case 4: return mfspr(SPRN_PMC4); + case 5: return mfspr(SPRN_PMC5); + case 6: return mfspr(SPRN_PMC6); + } + + return 0; +} + +static void term_handler(int signal) +{ + dump_summary_ebb_state(); + dump_ebb_hw_state(); + abort(); +} + +struct sigaction term_action = { + .sa_handler = term_handler, +}; + +static void __attribute__((constructor)) ebb_init(void) +{ + clear_ebb_stats(); + + if (sigaction(SIGTERM, &term_action, NULL)) + perror("sigaction"); + + ebb_state.trace = trace_buffer_allocate(1 * 1024 * 1024); +} diff --git a/tools/testing/selftests/powerpc/pmu/ebb/ebb.h b/tools/testing/selftests/powerpc/pmu/ebb/ebb.h new file mode 100644 index 000000000000..e62bde05bf78 --- /dev/null +++ b/tools/testing/selftests/powerpc/pmu/ebb/ebb.h @@ -0,0 +1,78 @@ +/* + * Copyright 2014, Michael Ellerman, IBM Corp. + * Licensed under GPLv2. + */ + +#ifndef _SELFTESTS_POWERPC_PMU_EBB_EBB_H +#define _SELFTESTS_POWERPC_PMU_EBB_EBB_H + +#include "../event.h" +#include "../lib.h" +#include "trace.h" +#include "reg.h" + +#define PMC_INDEX(pmc) ((pmc)-1) + +#define NUM_PMC_VALUES 128 + +struct ebb_state +{ + struct { + u64 pmc_count[6]; + volatile int ebb_count; + int spurious; + int negative; + int no_overflow; + } stats; + + bool pmc_enable[6]; + struct trace_buffer *trace; +}; + +extern struct ebb_state ebb_state; + +#define COUNTER_OVERFLOW 0x80000000ull + +static inline uint32_t pmc_sample_period(uint32_t value) +{ + return COUNTER_OVERFLOW - value; +} + +static inline void ebb_enable_pmc_counting(int pmc) +{ + ebb_state.pmc_enable[PMC_INDEX(pmc)] = true; +} + +bool ebb_check_count(int pmc, u64 sample_period, int fudge); +void event_leader_ebb_init(struct event *e); +void event_ebb_init(struct event *e); +void event_bhrb_init(struct event *e, unsigned ifm); +void setup_ebb_handler(void (*callee)(void)); +void standard_ebb_callee(void); +int ebb_event_enable(struct event *e); +void ebb_global_enable(void); +void ebb_global_disable(void); +void ebb_freeze_pmcs(void); +void ebb_unfreeze_pmcs(void); +void event_ebb_init(struct event *e); +void event_leader_ebb_init(struct event *e); +int count_pmc(int pmc, uint32_t sample_period); +void dump_ebb_state(void); +void dump_summary_ebb_state(void); +void dump_ebb_hw_state(void); +void clear_ebb_stats(void); +void write_pmc(int pmc, u64 value); +u64 read_pmc(int pmc); +void reset_ebb_with_clear_mask(unsigned long mmcr0_clear_mask); +void reset_ebb(void); +int ebb_check_mmcr0(void); + +extern u64 sample_period; + +int core_busy_loop(void); +int core_busy_loop_with_freeze(void); +int ebb_child(union pipe read_pipe, union pipe write_pipe); +int catch_sigill(void (*func)(void)); +void write_pmc1(void); + +#endif /* _SELFTESTS_POWERPC_PMU_EBB_EBB_H */ diff --git a/tools/testing/selftests/powerpc/pmu/ebb/ebb_handler.S b/tools/testing/selftests/powerpc/pmu/ebb/ebb_handler.S new file mode 100644 index 000000000000..14274ea206e5 --- /dev/null +++ b/tools/testing/selftests/powerpc/pmu/ebb/ebb_handler.S @@ -0,0 +1,365 @@ +/* + * Copyright 2014, Michael Ellerman, IBM Corp. + * Licensed under GPLv2. + */ + +#include <ppc-asm.h> +#include "reg.h" + + +/* ppc-asm.h defines most of the reg aliases, but not r1/r2. */ +#define r1 1 +#define r2 2 + +#define RFEBB .long 0x4c000924 + +/* Stack layout: + * + * ^ + * User stack | + * Back chain ------+ <- r1 <-------+ + * ... | + * Red zone / ABI Gap | + * ... | + * vr63 <+ | + * vr0 | | + * VSCR | | + * FSCR | | + * r31 | Save area | + * r0 | | + * XER | | + * CTR | | + * LR | | + * CCR <+ | + * ... <+ | + * LR | Caller frame | + * CCR | | + * Back chain <+ <- updated r1 --------+ + * + */ + +#if defined(_CALL_ELF) && _CALL_ELF == 2 +#define ABIGAP 512 +#else +#define ABIGAP 288 +#endif + +#define NR_GPR 32 +#define NR_SPR 6 +#define NR_VSR 64 + +#define SAVE_AREA ((NR_GPR + NR_SPR) * 8 + (NR_VSR * 16)) +#define CALLER_FRAME 112 + +#define STACK_FRAME (ABIGAP + SAVE_AREA + CALLER_FRAME) + +#define CCR_SAVE (CALLER_FRAME) +#define LR_SAVE (CCR_SAVE + 8) +#define CTR_SAVE (LR_SAVE + 8) +#define XER_SAVE (CTR_SAVE + 8) +#define GPR_SAVE(n) (XER_SAVE + 8 + (8 * n)) +#define FSCR_SAVE (GPR_SAVE(31) + 8) +#define VSCR_SAVE (FSCR_SAVE + 8) +#define VSR_SAVE(n) (VSCR_SAVE + 8 + (16 * n)) + +#define SAVE_GPR(n) std n,GPR_SAVE(n)(r1) +#define REST_GPR(n) ld n,GPR_SAVE(n)(r1) +#define TRASH_GPR(n) lis n,0xaaaa + +#define SAVE_VSR(n, b) li b, VSR_SAVE(n); stxvd2x n,b,r1 +#define LOAD_VSR(n, b) li b, VSR_SAVE(n); lxvd2x n,b,r1 + +#define LOAD_REG_IMMEDIATE(reg,expr) \ + lis reg,(expr)@highest; \ + ori reg,reg,(expr)@higher; \ + rldicr reg,reg,32,31; \ + oris reg,reg,(expr)@h; \ + ori reg,reg,(expr)@l; + + +#if defined(_CALL_ELF) && _CALL_ELF == 2 +#define ENTRY_POINT(name) \ + .type FUNC_NAME(name),@function; \ + .globl FUNC_NAME(name); \ + FUNC_NAME(name): + +#define RESTORE_TOC(name) \ + /* Restore our TOC pointer using our entry point */ \ + LOAD_REG_IMMEDIATE(r12, name) \ +0: addis r2,r12,(.TOC.-0b)@ha; \ + addi r2,r2,(.TOC.-0b)@l; + +#else +#define ENTRY_POINT(name) FUNC_START(name) +#define RESTORE_TOC(name) \ + /* Restore our TOC pointer via our opd entry */ \ + LOAD_REG_IMMEDIATE(r2, name) \ + ld r2,8(r2); +#endif + + .text + +ENTRY_POINT(ebb_handler) + stdu r1,-STACK_FRAME(r1) + SAVE_GPR(0) + mflr r0 + std r0,LR_SAVE(r1) + mfcr r0 + std r0,CCR_SAVE(r1) + mfctr r0 + std r0,CTR_SAVE(r1) + mfxer r0 + std r0,XER_SAVE(r1) + SAVE_GPR(2) + SAVE_GPR(3) + SAVE_GPR(4) + SAVE_GPR(5) + SAVE_GPR(6) + SAVE_GPR(7) + SAVE_GPR(8) + SAVE_GPR(9) + SAVE_GPR(10) + SAVE_GPR(11) + SAVE_GPR(12) + SAVE_GPR(13) + SAVE_GPR(14) + SAVE_GPR(15) + SAVE_GPR(16) + SAVE_GPR(17) + SAVE_GPR(18) + SAVE_GPR(19) + SAVE_GPR(20) + SAVE_GPR(21) + SAVE_GPR(22) + SAVE_GPR(23) + SAVE_GPR(24) + SAVE_GPR(25) + SAVE_GPR(26) + SAVE_GPR(27) + SAVE_GPR(28) + SAVE_GPR(29) + SAVE_GPR(30) + SAVE_GPR(31) + SAVE_VSR(0, r3) + mffs f0 + stfd f0, FSCR_SAVE(r1) + mfvscr f0 + stfd f0, VSCR_SAVE(r1) + SAVE_VSR(1, r3) + SAVE_VSR(2, r3) + SAVE_VSR(3, r3) + SAVE_VSR(4, r3) + SAVE_VSR(5, r3) + SAVE_VSR(6, r3) + SAVE_VSR(7, r3) + SAVE_VSR(8, r3) + SAVE_VSR(9, r3) + SAVE_VSR(10, r3) + SAVE_VSR(11, r3) + SAVE_VSR(12, r3) + SAVE_VSR(13, r3) + SAVE_VSR(14, r3) + SAVE_VSR(15, r3) + SAVE_VSR(16, r3) + SAVE_VSR(17, r3) + SAVE_VSR(18, r3) + SAVE_VSR(19, r3) + SAVE_VSR(20, r3) + SAVE_VSR(21, r3) + SAVE_VSR(22, r3) + SAVE_VSR(23, r3) + SAVE_VSR(24, r3) + SAVE_VSR(25, r3) + SAVE_VSR(26, r3) + SAVE_VSR(27, r3) + SAVE_VSR(28, r3) + SAVE_VSR(29, r3) + SAVE_VSR(30, r3) + SAVE_VSR(31, r3) + SAVE_VSR(32, r3) + SAVE_VSR(33, r3) + SAVE_VSR(34, r3) + SAVE_VSR(35, r3) + SAVE_VSR(36, r3) + SAVE_VSR(37, r3) + SAVE_VSR(38, r3) + SAVE_VSR(39, r3) + SAVE_VSR(40, r3) + SAVE_VSR(41, r3) + SAVE_VSR(42, r3) + SAVE_VSR(43, r3) + SAVE_VSR(44, r3) + SAVE_VSR(45, r3) + SAVE_VSR(46, r3) + SAVE_VSR(47, r3) + SAVE_VSR(48, r3) + SAVE_VSR(49, r3) + SAVE_VSR(50, r3) + SAVE_VSR(51, r3) + SAVE_VSR(52, r3) + SAVE_VSR(53, r3) + SAVE_VSR(54, r3) + SAVE_VSR(55, r3) + SAVE_VSR(56, r3) + SAVE_VSR(57, r3) + SAVE_VSR(58, r3) + SAVE_VSR(59, r3) + SAVE_VSR(60, r3) + SAVE_VSR(61, r3) + SAVE_VSR(62, r3) + SAVE_VSR(63, r3) + + TRASH_GPR(2) + TRASH_GPR(3) + TRASH_GPR(4) + TRASH_GPR(5) + TRASH_GPR(6) + TRASH_GPR(7) + TRASH_GPR(8) + TRASH_GPR(9) + TRASH_GPR(10) + TRASH_GPR(11) + TRASH_GPR(12) + TRASH_GPR(14) + TRASH_GPR(15) + TRASH_GPR(16) + TRASH_GPR(17) + TRASH_GPR(18) + TRASH_GPR(19) + TRASH_GPR(20) + TRASH_GPR(21) + TRASH_GPR(22) + TRASH_GPR(23) + TRASH_GPR(24) + TRASH_GPR(25) + TRASH_GPR(26) + TRASH_GPR(27) + TRASH_GPR(28) + TRASH_GPR(29) + TRASH_GPR(30) + TRASH_GPR(31) + + RESTORE_TOC(ebb_handler) + + /* + * r13 is our TLS pointer. We leave whatever value was in there when the + * EBB fired. That seems to be OK because once set the TLS pointer is not + * changed - but presumably that could change in future. + */ + + bl ebb_hook + nop + + /* r2 may be changed here but we don't care */ + + lfd f0, FSCR_SAVE(r1) + mtfsf 0xff,f0 + lfd f0, VSCR_SAVE(r1) + mtvscr f0 + LOAD_VSR(0, r3) + LOAD_VSR(1, r3) + LOAD_VSR(2, r3) + LOAD_VSR(3, r3) + LOAD_VSR(4, r3) + LOAD_VSR(5, r3) + LOAD_VSR(6, r3) + LOAD_VSR(7, r3) + LOAD_VSR(8, r3) + LOAD_VSR(9, r3) + LOAD_VSR(10, r3) + LOAD_VSR(11, r3) + LOAD_VSR(12, r3) + LOAD_VSR(13, r3) + LOAD_VSR(14, r3) + LOAD_VSR(15, r3) + LOAD_VSR(16, r3) + LOAD_VSR(17, r3) + LOAD_VSR(18, r3) + LOAD_VSR(19, r3) + LOAD_VSR(20, r3) + LOAD_VSR(21, r3) + LOAD_VSR(22, r3) + LOAD_VSR(23, r3) + LOAD_VSR(24, r3) + LOAD_VSR(25, r3) + LOAD_VSR(26, r3) + LOAD_VSR(27, r3) + LOAD_VSR(28, r3) + LOAD_VSR(29, r3) + LOAD_VSR(30, r3) + LOAD_VSR(31, r3) + LOAD_VSR(32, r3) + LOAD_VSR(33, r3) + LOAD_VSR(34, r3) + LOAD_VSR(35, r3) + LOAD_VSR(36, r3) + LOAD_VSR(37, r3) + LOAD_VSR(38, r3) + LOAD_VSR(39, r3) + LOAD_VSR(40, r3) + LOAD_VSR(41, r3) + LOAD_VSR(42, r3) + LOAD_VSR(43, r3) + LOAD_VSR(44, r3) + LOAD_VSR(45, r3) + LOAD_VSR(46, r3) + LOAD_VSR(47, r3) + LOAD_VSR(48, r3) + LOAD_VSR(49, r3) + LOAD_VSR(50, r3) + LOAD_VSR(51, r3) + LOAD_VSR(52, r3) + LOAD_VSR(53, r3) + LOAD_VSR(54, r3) + LOAD_VSR(55, r3) + LOAD_VSR(56, r3) + LOAD_VSR(57, r3) + LOAD_VSR(58, r3) + LOAD_VSR(59, r3) + LOAD_VSR(60, r3) + LOAD_VSR(61, r3) + LOAD_VSR(62, r3) + LOAD_VSR(63, r3) + + ld r0,XER_SAVE(r1) + mtxer r0 + ld r0,CTR_SAVE(r1) + mtctr r0 + ld r0,LR_SAVE(r1) + mtlr r0 + ld r0,CCR_SAVE(r1) + mtcr r0 + REST_GPR(0) + REST_GPR(2) + REST_GPR(3) + REST_GPR(4) + REST_GPR(5) + REST_GPR(6) + REST_GPR(7) + REST_GPR(8) + REST_GPR(9) + REST_GPR(10) + REST_GPR(11) + REST_GPR(12) + REST_GPR(13) + REST_GPR(14) + REST_GPR(15) + REST_GPR(16) + REST_GPR(17) + REST_GPR(18) + REST_GPR(19) + REST_GPR(20) + REST_GPR(21) + REST_GPR(22) + REST_GPR(23) + REST_GPR(24) + REST_GPR(25) + REST_GPR(26) + REST_GPR(27) + REST_GPR(28) + REST_GPR(29) + REST_GPR(30) + REST_GPR(31) + addi r1,r1,STACK_FRAME + RFEBB +FUNC_END(ebb_handler) diff --git a/tools/testing/selftests/powerpc/pmu/ebb/ebb_on_child_test.c b/tools/testing/selftests/powerpc/pmu/ebb/ebb_on_child_test.c new file mode 100644 index 000000000000..c45f948148e1 --- /dev/null +++ b/tools/testing/selftests/powerpc/pmu/ebb/ebb_on_child_test.c @@ -0,0 +1,86 @@ +/* + * Copyright 2014, Michael Ellerman, IBM Corp. + * Licensed under GPLv2. + */ + +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "ebb.h" + + +/* + * Tests we can setup an EBB on our child. Nothing interesting happens, because + * even though the event is enabled and running the child hasn't enabled the + * actual delivery of the EBBs. + */ + +static int victim_child(union pipe read_pipe, union pipe write_pipe) +{ + int i; + + FAIL_IF(wait_for_parent(read_pipe)); + FAIL_IF(notify_parent(write_pipe)); + + /* Parent creates EBB event */ + + FAIL_IF(wait_for_parent(read_pipe)); + FAIL_IF(notify_parent(write_pipe)); + + /* Check the EBB is enabled by writing PMC1 */ + write_pmc1(); + + /* EBB event is enabled here */ + for (i = 0; i < 1000000; i++) ; + + return 0; +} + +int ebb_on_child(void) +{ + union pipe read_pipe, write_pipe; + struct event event; + pid_t pid; + + FAIL_IF(pipe(read_pipe.fds) == -1); + FAIL_IF(pipe(write_pipe.fds) == -1); + + pid = fork(); + if (pid == 0) { + /* NB order of pipes looks reversed */ + exit(victim_child(write_pipe, read_pipe)); + } + + FAIL_IF(sync_with_child(read_pipe, write_pipe)); + + /* Child is running now */ + + event_init_named(&event, 0x1001e, "cycles"); + event_leader_ebb_init(&event); + + event.attr.exclude_kernel = 1; + event.attr.exclude_hv = 1; + event.attr.exclude_idle = 1; + + FAIL_IF(event_open_with_pid(&event, pid)); + FAIL_IF(ebb_event_enable(&event)); + + FAIL_IF(sync_with_child(read_pipe, write_pipe)); + + /* Child should just exit happily */ + FAIL_IF(wait_for_child(pid)); + + event_close(&event); + + return 0; +} + +int main(void) +{ + return test_harness(ebb_on_child, "ebb_on_child"); +} diff --git a/tools/testing/selftests/powerpc/pmu/ebb/ebb_on_willing_child_test.c b/tools/testing/selftests/powerpc/pmu/ebb/ebb_on_willing_child_test.c new file mode 100644 index 000000000000..11acf1d55f8d --- /dev/null +++ b/tools/testing/selftests/powerpc/pmu/ebb/ebb_on_willing_child_test.c @@ -0,0 +1,92 @@ +/* + * Copyright 2014, Michael Ellerman, IBM Corp. + * Licensed under GPLv2. + */ + +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "ebb.h" + + +/* + * Tests we can setup an EBB on our child. The child expects this and enables + * EBBs, which are then delivered to the child, even though the event is + * created by the parent. + */ + +static int victim_child(union pipe read_pipe, union pipe write_pipe) +{ + FAIL_IF(wait_for_parent(read_pipe)); + + /* Setup our EBB handler, before the EBB event is created */ + ebb_enable_pmc_counting(1); + setup_ebb_handler(standard_ebb_callee); + ebb_global_enable(); + + FAIL_IF(notify_parent(write_pipe)); + + while (ebb_state.stats.ebb_count < 20) { + FAIL_IF(core_busy_loop()); + } + + ebb_global_disable(); + ebb_freeze_pmcs(); + + count_pmc(1, sample_period); + + dump_ebb_state(); + + FAIL_IF(ebb_state.stats.ebb_count == 0); + + return 0; +} + +/* Tests we can setup an EBB on our child - if it's expecting it */ +int ebb_on_willing_child(void) +{ + union pipe read_pipe, write_pipe; + struct event event; + pid_t pid; + + FAIL_IF(pipe(read_pipe.fds) == -1); + FAIL_IF(pipe(write_pipe.fds) == -1); + + pid = fork(); + if (pid == 0) { + /* NB order of pipes looks reversed */ + exit(victim_child(write_pipe, read_pipe)); + } + + /* Signal the child to setup its EBB handler */ + FAIL_IF(sync_with_child(read_pipe, write_pipe)); + + /* Child is running now */ + + event_init_named(&event, 0x1001e, "cycles"); + event_leader_ebb_init(&event); + + event.attr.exclude_kernel = 1; + event.attr.exclude_hv = 1; + event.attr.exclude_idle = 1; + + FAIL_IF(event_open_with_pid(&event, pid)); + FAIL_IF(ebb_event_enable(&event)); + + /* Child show now take EBBs and then exit */ + FAIL_IF(wait_for_child(pid)); + + event_close(&event); + + return 0; +} + +int main(void) +{ + return test_harness(ebb_on_willing_child, "ebb_on_willing_child"); +} diff --git a/tools/testing/selftests/powerpc/pmu/ebb/ebb_vs_cpu_event_test.c b/tools/testing/selftests/powerpc/pmu/ebb/ebb_vs_cpu_event_test.c new file mode 100644 index 000000000000..be4dd5a4e98e --- /dev/null +++ b/tools/testing/selftests/powerpc/pmu/ebb/ebb_vs_cpu_event_test.c @@ -0,0 +1,86 @@ +/* + * Copyright 2014, Michael Ellerman, IBM Corp. + * Licensed under GPLv2. + */ + +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "ebb.h" + + +/* + * Tests an EBB vs a cpu event - in that order. The EBB should force the cpu + * event off the PMU. + */ + +static int setup_cpu_event(struct event *event, int cpu) +{ + event_init_named(event, 0x400FA, "PM_RUN_INST_CMPL"); + + event->attr.exclude_kernel = 1; + event->attr.exclude_hv = 1; + event->attr.exclude_idle = 1; + + SKIP_IF(require_paranoia_below(1)); + FAIL_IF(event_open_with_cpu(event, cpu)); + FAIL_IF(event_enable(event)); + + return 0; +} + +int ebb_vs_cpu_event(void) +{ + union pipe read_pipe, write_pipe; + struct event event; + int cpu, rc; + pid_t pid; + + cpu = pick_online_cpu(); + FAIL_IF(cpu < 0); + FAIL_IF(bind_to_cpu(cpu)); + + FAIL_IF(pipe(read_pipe.fds) == -1); + FAIL_IF(pipe(write_pipe.fds) == -1); + + pid = fork(); + if (pid == 0) { + /* NB order of pipes looks reversed */ + exit(ebb_child(write_pipe, read_pipe)); + } + + /* Signal the child to install its EBB event and wait */ + FAIL_IF(sync_with_child(read_pipe, write_pipe)); + + /* Now try to install our CPU event */ + rc = setup_cpu_event(&event, cpu); + if (rc) { + kill_child_and_wait(pid); + return rc; + } + + /* Signal the child to run */ + FAIL_IF(sync_with_child(read_pipe, write_pipe)); + + /* .. and wait for it to complete */ + FAIL_IF(wait_for_child(pid)); + FAIL_IF(event_disable(&event)); + FAIL_IF(event_read(&event)); + + event_report(&event); + + /* The cpu event may have run, but we don't expect 100% */ + FAIL_IF(event.result.enabled >= event.result.running); + + return 0; +} + +int main(void) +{ + return test_harness(ebb_vs_cpu_event, "ebb_vs_cpu_event"); +} diff --git a/tools/testing/selftests/powerpc/pmu/ebb/event_attributes_test.c b/tools/testing/selftests/powerpc/pmu/ebb/event_attributes_test.c new file mode 100644 index 000000000000..7e78153f08eb --- /dev/null +++ b/tools/testing/selftests/powerpc/pmu/ebb/event_attributes_test.c @@ -0,0 +1,131 @@ +/* + * Copyright 2014, Michael Ellerman, IBM Corp. + * Licensed under GPLv2. + */ + +#include <stdio.h> +#include <stdlib.h> + +#include "ebb.h" + + +/* + * Test various attributes of the EBB event are enforced. + */ +int event_attributes(void) +{ + struct event event, leader; + + event_init(&event, 0x1001e); + event_leader_ebb_init(&event); + /* Expected to succeed */ + FAIL_IF(event_open(&event)); + event_close(&event); + + + event_init(&event, 0x001e); /* CYCLES - no PMC specified */ + event_leader_ebb_init(&event); + /* Expected to fail, no PMC specified */ + FAIL_IF(event_open(&event) == 0); + + + event_init(&event, 0x2001e); + event_leader_ebb_init(&event); + event.attr.exclusive = 0; + /* Expected to fail, not exclusive */ + FAIL_IF(event_open(&event) == 0); + + + event_init(&event, 0x3001e); + event_leader_ebb_init(&event); + event.attr.freq = 1; + /* Expected to fail, sets freq */ + FAIL_IF(event_open(&event) == 0); + + + event_init(&event, 0x4001e); + event_leader_ebb_init(&event); + event.attr.sample_period = 1; + /* Expected to fail, sets sample_period */ + FAIL_IF(event_open(&event) == 0); + + + event_init(&event, 0x1001e); + event_leader_ebb_init(&event); + event.attr.enable_on_exec = 1; + /* Expected to fail, sets enable_on_exec */ + FAIL_IF(event_open(&event) == 0); + + + event_init(&event, 0x1001e); + event_leader_ebb_init(&event); + event.attr.inherit = 1; + /* Expected to fail, sets inherit */ + FAIL_IF(event_open(&event) == 0); + + + event_init(&leader, 0x1001e); + event_leader_ebb_init(&leader); + FAIL_IF(event_open(&leader)); + + event_init(&event, 0x20002); + event_ebb_init(&event); + + /* Expected to succeed */ + FAIL_IF(event_open_with_group(&event, leader.fd)); + event_close(&leader); + event_close(&event); + + + event_init(&leader, 0x1001e); + event_leader_ebb_init(&leader); + FAIL_IF(event_open(&leader)); + + event_init(&event, 0x20002); + + /* Expected to fail, event doesn't request EBB, leader does */ + FAIL_IF(event_open_with_group(&event, leader.fd) == 0); + event_close(&leader); + + + event_init(&leader, 0x1001e); + event_leader_ebb_init(&leader); + /* Clear the EBB flag */ + leader.attr.config &= ~(1ull << 63); + + FAIL_IF(event_open(&leader)); + + event_init(&event, 0x20002); + event_ebb_init(&event); + + /* Expected to fail, leader doesn't request EBB */ + FAIL_IF(event_open_with_group(&event, leader.fd) == 0); + event_close(&leader); + + + event_init(&leader, 0x1001e); + event_leader_ebb_init(&leader); + leader.attr.exclusive = 0; + /* Expected to fail, leader isn't exclusive */ + FAIL_IF(event_open(&leader) == 0); + + + event_init(&leader, 0x1001e); + event_leader_ebb_init(&leader); + leader.attr.pinned = 0; + /* Expected to fail, leader isn't pinned */ + FAIL_IF(event_open(&leader) == 0); + + event_init(&event, 0x1001e); + event_leader_ebb_init(&event); + /* Expected to fail, not a task event */ + SKIP_IF(require_paranoia_below(1)); + FAIL_IF(event_open_with_cpu(&event, 0) == 0); + + return 0; +} + +int main(void) +{ + return test_harness(event_attributes, "event_attributes"); +} diff --git a/tools/testing/selftests/powerpc/pmu/ebb/fixed_instruction_loop.S b/tools/testing/selftests/powerpc/pmu/ebb/fixed_instruction_loop.S new file mode 100644 index 000000000000..b866a0581d32 --- /dev/null +++ b/tools/testing/selftests/powerpc/pmu/ebb/fixed_instruction_loop.S @@ -0,0 +1,43 @@ +/* + * Copyright 2014, Michael Ellerman, IBM Corp. + * Licensed under GPLv2. + */ + +#include <ppc-asm.h> + + .text + +FUNC_START(thirty_two_instruction_loop) + cmpwi r3,0 + beqlr + addi r4,r3,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 # 28 addi's + subi r3,r3,1 + b FUNC_NAME(thirty_two_instruction_loop) +FUNC_END(thirty_two_instruction_loop) diff --git a/tools/testing/selftests/powerpc/pmu/ebb/fork_cleanup_test.c b/tools/testing/selftests/powerpc/pmu/ebb/fork_cleanup_test.c new file mode 100644 index 000000000000..9e7af6e76622 --- /dev/null +++ b/tools/testing/selftests/powerpc/pmu/ebb/fork_cleanup_test.c @@ -0,0 +1,79 @@ +/* + * Copyright 2014, Michael Ellerman, IBM Corp. + * Licensed under GPLv2. + */ + +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> +#include <setjmp.h> +#include <signal.h> + +#include "ebb.h" + + +/* + * Test that a fork clears the PMU state of the child. eg. BESCR/EBBHR/EBBRR + * are cleared, and MMCR0_PMCC is reset, preventing the child from accessing + * the PMU. + */ + +static struct event event; + +static int child(void) +{ + /* Even though we have EBE=0 we can still see the EBB regs */ + FAIL_IF(mfspr(SPRN_BESCR) != 0); + FAIL_IF(mfspr(SPRN_EBBHR) != 0); + FAIL_IF(mfspr(SPRN_EBBRR) != 0); + + FAIL_IF(catch_sigill(write_pmc1)); + + /* We can still read from the event, though it is on our parent */ + FAIL_IF(event_read(&event)); + + return 0; +} + +/* Tests that fork clears EBB state */ +int fork_cleanup(void) +{ + pid_t pid; + + event_init_named(&event, 0x1001e, "cycles"); + event_leader_ebb_init(&event); + + FAIL_IF(event_open(&event)); + + ebb_enable_pmc_counting(1); + setup_ebb_handler(standard_ebb_callee); + ebb_global_enable(); + + FAIL_IF(ebb_event_enable(&event)); + + mtspr(SPRN_MMCR0, MMCR0_FC); + mtspr(SPRN_PMC1, pmc_sample_period(sample_period)); + + /* Don't need to actually take any EBBs */ + + pid = fork(); + if (pid == 0) + exit(child()); + + /* Child does the actual testing */ + FAIL_IF(wait_for_child(pid)); + + /* After fork */ + event_close(&event); + + return 0; +} + +int main(void) +{ + return test_harness(fork_cleanup, "fork_cleanup"); +} diff --git a/tools/testing/selftests/powerpc/pmu/ebb/instruction_count_test.c b/tools/testing/selftests/powerpc/pmu/ebb/instruction_count_test.c new file mode 100644 index 000000000000..f8190fa29592 --- /dev/null +++ b/tools/testing/selftests/powerpc/pmu/ebb/instruction_count_test.c @@ -0,0 +1,164 @@ +/* + * Copyright 2014, Michael Ellerman, IBM Corp. + * Licensed under GPLv2. + */ + +#define _GNU_SOURCE + +#include <stdio.h> +#include <stdbool.h> +#include <string.h> +#include <sys/prctl.h> + +#include "ebb.h" + + +/* + * Run a calibrated instruction loop and count instructions executed using + * EBBs. Make sure the counts look right. + */ + +extern void thirty_two_instruction_loop(uint64_t loops); + +static bool counters_frozen = true; + +static int do_count_loop(struct event *event, uint64_t instructions, + uint64_t overhead, bool report) +{ + int64_t difference, expected; + double percentage; + + clear_ebb_stats(); + + counters_frozen = false; + mb(); + mtspr(SPRN_MMCR0, mfspr(SPRN_MMCR0) & ~MMCR0_FC); + + thirty_two_instruction_loop(instructions >> 5); + + counters_frozen = true; + mb(); + mtspr(SPRN_MMCR0, mfspr(SPRN_MMCR0) | MMCR0_FC); + + count_pmc(4, sample_period); + + event->result.value = ebb_state.stats.pmc_count[4-1]; + expected = instructions + overhead; + difference = event->result.value - expected; + percentage = (double)difference / event->result.value * 100; + + if (report) { + printf("Looped for %lu instructions, overhead %lu\n", instructions, overhead); + printf("Expected %lu\n", expected); + printf("Actual %llu\n", event->result.value); + printf("Error %ld, %f%%\n", difference, percentage); + printf("Took %d EBBs\n", ebb_state.stats.ebb_count); + } + + if (difference < 0) + difference = -difference; + + /* Tolerate a difference of up to 0.0001 % */ + difference *= 10000 * 100; + if (difference / event->result.value) + return -1; + + return 0; +} + +/* Count how many instructions it takes to do a null loop */ +static uint64_t determine_overhead(struct event *event) +{ + uint64_t current, overhead; + int i; + + do_count_loop(event, 0, 0, false); + overhead = event->result.value; + + for (i = 0; i < 100; i++) { + do_count_loop(event, 0, 0, false); + current = event->result.value; + if (current < overhead) { + printf("Replacing overhead %lu with %lu\n", overhead, current); + overhead = current; + } + } + + return overhead; +} + +static void pmc4_ebb_callee(void) +{ + uint64_t val; + + val = mfspr(SPRN_BESCR); + if (!(val & BESCR_PMEO)) { + ebb_state.stats.spurious++; + goto out; + } + + ebb_state.stats.ebb_count++; + count_pmc(4, sample_period); +out: + if (counters_frozen) + reset_ebb_with_clear_mask(MMCR0_PMAO); + else + reset_ebb(); +} + +int instruction_count(void) +{ + struct event event; + uint64_t overhead; + + event_init_named(&event, 0x400FA, "PM_RUN_INST_CMPL"); + event_leader_ebb_init(&event); + event.attr.exclude_kernel = 1; + event.attr.exclude_hv = 1; + event.attr.exclude_idle = 1; + + FAIL_IF(event_open(&event)); + FAIL_IF(ebb_event_enable(&event)); + + sample_period = COUNTER_OVERFLOW; + + setup_ebb_handler(pmc4_ebb_callee); + mtspr(SPRN_MMCR0, mfspr(SPRN_MMCR0) & ~MMCR0_FC); + ebb_global_enable(); + + overhead = determine_overhead(&event); + printf("Overhead of null loop: %lu instructions\n", overhead); + + /* Run for 1M instructions */ + FAIL_IF(do_count_loop(&event, 0x100000, overhead, true)); + + /* Run for 10M instructions */ + FAIL_IF(do_count_loop(&event, 0xa00000, overhead, true)); + + /* Run for 100M instructions */ + FAIL_IF(do_count_loop(&event, 0x6400000, overhead, true)); + + /* Run for 1G instructions */ + FAIL_IF(do_count_loop(&event, 0x40000000, overhead, true)); + + /* Run for 16G instructions */ + FAIL_IF(do_count_loop(&event, 0x400000000, overhead, true)); + + /* Run for 64G instructions */ + FAIL_IF(do_count_loop(&event, 0x1000000000, overhead, true)); + + /* Run for 128G instructions */ + FAIL_IF(do_count_loop(&event, 0x2000000000, overhead, true)); + + ebb_global_disable(); + event_close(&event); + + printf("Finished OK\n"); + + return 0; +} + +int main(void) +{ + return test_harness(instruction_count, "instruction_count"); +} diff --git a/tools/testing/selftests/powerpc/pmu/ebb/lost_exception_test.c b/tools/testing/selftests/powerpc/pmu/ebb/lost_exception_test.c new file mode 100644 index 000000000000..0c9dd9b2e39d --- /dev/null +++ b/tools/testing/selftests/powerpc/pmu/ebb/lost_exception_test.c @@ -0,0 +1,100 @@ +/* + * Copyright 2014, Michael Ellerman, IBM Corp. + * Licensed under GPLv2. + */ + +#include <sched.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/mman.h> + +#include "ebb.h" + + +/* + * Test that tries to trigger CPU_FTR_PMAO_BUG. Which is a hardware defect + * where an exception triggers but we context switch before it is delivered and + * lose the exception. + */ + +static int test_body(void) +{ + int i, orig_period, max_period; + struct event event; + + /* We use PMC4 to make sure the kernel switches all counters correctly */ + event_init_named(&event, 0x40002, "instructions"); + event_leader_ebb_init(&event); + + event.attr.exclude_kernel = 1; + event.attr.exclude_hv = 1; + event.attr.exclude_idle = 1; + + FAIL_IF(event_open(&event)); + + ebb_enable_pmc_counting(4); + setup_ebb_handler(standard_ebb_callee); + ebb_global_enable(); + FAIL_IF(ebb_event_enable(&event)); + + /* + * We want a low sample period, but we also want to get out of the EBB + * handler without tripping up again. + * + * This value picked after much experimentation. + */ + orig_period = max_period = sample_period = 400; + + mtspr(SPRN_PMC4, pmc_sample_period(sample_period)); + + while (ebb_state.stats.ebb_count < 1000000) { + /* + * We are trying to get the EBB exception to race exactly with + * us entering the kernel to do the syscall. We then need the + * kernel to decide our timeslice is up and context switch to + * the other thread. When we come back our EBB will have been + * lost and we'll spin in this while loop forever. + */ + + for (i = 0; i < 100000; i++) + sched_yield(); + + /* Change the sample period slightly to try and hit the race */ + if (sample_period >= (orig_period + 200)) + sample_period = orig_period; + else + sample_period++; + + if (sample_period > max_period) + max_period = sample_period; + } + + ebb_freeze_pmcs(); + ebb_global_disable(); + + count_pmc(4, sample_period); + mtspr(SPRN_PMC4, 0xdead); + + dump_summary_ebb_state(); + dump_ebb_hw_state(); + + event_close(&event); + + FAIL_IF(ebb_state.stats.ebb_count == 0); + + /* We vary our sample period so we need extra fudge here */ + FAIL_IF(!ebb_check_count(4, orig_period, 2 * (max_period - orig_period))); + + return 0; +} + +static int lost_exception(void) +{ + return eat_cpu(test_body); +} + +int main(void) +{ + return test_harness(lost_exception, "lost_exception"); +} diff --git a/tools/testing/selftests/powerpc/pmu/ebb/multi_counter_test.c b/tools/testing/selftests/powerpc/pmu/ebb/multi_counter_test.c new file mode 100644 index 000000000000..67d78af3284c --- /dev/null +++ b/tools/testing/selftests/powerpc/pmu/ebb/multi_counter_test.c @@ -0,0 +1,91 @@ +/* + * Copyright 2014, Michael Ellerman, IBM Corp. + * Licensed under GPLv2. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <sys/ioctl.h> + +#include "ebb.h" + + +/* + * Test counting multiple events using EBBs. + */ +int multi_counter(void) +{ + struct event events[6]; + int i, group_fd; + + event_init_named(&events[0], 0x1001C, "PM_CMPLU_STALL_THRD"); + event_init_named(&events[1], 0x2D016, "PM_CMPLU_STALL_FXU"); + event_init_named(&events[2], 0x30006, "PM_CMPLU_STALL_OTHER_CMPL"); + event_init_named(&events[3], 0x4000A, "PM_CMPLU_STALL"); + event_init_named(&events[4], 0x600f4, "PM_RUN_CYC"); + event_init_named(&events[5], 0x500fa, "PM_RUN_INST_CMPL"); + + event_leader_ebb_init(&events[0]); + for (i = 1; i < 6; i++) + event_ebb_init(&events[i]); + + group_fd = -1; + for (i = 0; i < 6; i++) { + events[i].attr.exclude_kernel = 1; + events[i].attr.exclude_hv = 1; + events[i].attr.exclude_idle = 1; + + FAIL_IF(event_open_with_group(&events[i], group_fd)); + if (group_fd == -1) + group_fd = events[0].fd; + } + + ebb_enable_pmc_counting(1); + ebb_enable_pmc_counting(2); + ebb_enable_pmc_counting(3); + ebb_enable_pmc_counting(4); + ebb_enable_pmc_counting(5); + ebb_enable_pmc_counting(6); + setup_ebb_handler(standard_ebb_callee); + + FAIL_IF(ioctl(events[0].fd, PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP)); + FAIL_IF(event_read(&events[0])); + + ebb_global_enable(); + + mtspr(SPRN_PMC1, pmc_sample_period(sample_period)); + mtspr(SPRN_PMC2, pmc_sample_period(sample_period)); + mtspr(SPRN_PMC3, pmc_sample_period(sample_period)); + mtspr(SPRN_PMC4, pmc_sample_period(sample_period)); + mtspr(SPRN_PMC5, pmc_sample_period(sample_period)); + mtspr(SPRN_PMC6, pmc_sample_period(sample_period)); + + while (ebb_state.stats.ebb_count < 50) { + FAIL_IF(core_busy_loop()); + FAIL_IF(ebb_check_mmcr0()); + } + + ebb_global_disable(); + ebb_freeze_pmcs(); + + count_pmc(1, sample_period); + count_pmc(2, sample_period); + count_pmc(3, sample_period); + count_pmc(4, sample_period); + count_pmc(5, sample_period); + count_pmc(6, sample_period); + + dump_ebb_state(); + + for (i = 0; i < 6; i++) + event_close(&events[i]); + + FAIL_IF(ebb_state.stats.ebb_count == 0); + + return 0; +} + +int main(void) +{ + return test_harness(multi_counter, "multi_counter"); +} diff --git a/tools/testing/selftests/powerpc/pmu/ebb/multi_ebb_procs_test.c b/tools/testing/selftests/powerpc/pmu/ebb/multi_ebb_procs_test.c new file mode 100644 index 000000000000..b8dc371f9338 --- /dev/null +++ b/tools/testing/selftests/powerpc/pmu/ebb/multi_ebb_procs_test.c @@ -0,0 +1,109 @@ +/* + * Copyright 2014, Michael Ellerman, IBM Corp. + * Licensed under GPLv2. + */ + +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <signal.h> + +#include "ebb.h" + + +/* + * Test running multiple EBB using processes at once on a single CPU. They + * should all run happily without interfering with each other. + */ + +static bool child_should_exit; + +static void sigint_handler(int signal) +{ + child_should_exit = true; +} + +struct sigaction sigint_action = { + .sa_handler = sigint_handler, +}; + +static int cycles_child(void) +{ + struct event event; + + if (sigaction(SIGINT, &sigint_action, NULL)) { + perror("sigaction"); + return 1; + } + + event_init_named(&event, 0x1001e, "cycles"); + event_leader_ebb_init(&event); + + event.attr.exclude_kernel = 1; + event.attr.exclude_hv = 1; + event.attr.exclude_idle = 1; + + FAIL_IF(event_open(&event)); + + ebb_enable_pmc_counting(1); + setup_ebb_handler(standard_ebb_callee); + ebb_global_enable(); + + FAIL_IF(ebb_event_enable(&event)); + + mtspr(SPRN_PMC1, pmc_sample_period(sample_period)); + + while (!child_should_exit) { + FAIL_IF(core_busy_loop()); + FAIL_IF(ebb_check_mmcr0()); + } + + ebb_global_disable(); + ebb_freeze_pmcs(); + + count_pmc(1, sample_period); + + dump_summary_ebb_state(); + + event_close(&event); + + FAIL_IF(ebb_state.stats.ebb_count == 0); + + return 0; +} + +#define NR_CHILDREN 4 + +int multi_ebb_procs(void) +{ + pid_t pids[NR_CHILDREN]; + int cpu, rc, i; + + cpu = pick_online_cpu(); + FAIL_IF(cpu < 0); + FAIL_IF(bind_to_cpu(cpu)); + + for (i = 0; i < NR_CHILDREN; i++) { + pids[i] = fork(); + if (pids[i] == 0) + exit(cycles_child()); + } + + /* Have them all run for "a while" */ + sleep(10); + + rc = 0; + for (i = 0; i < NR_CHILDREN; i++) { + /* Tell them to stop */ + kill(pids[i], SIGINT); + /* And wait */ + rc |= wait_for_child(pids[i]); + } + + return rc; +} + +int main(void) +{ + return test_harness(multi_ebb_procs, "multi_ebb_procs"); +} diff --git a/tools/testing/selftests/powerpc/pmu/ebb/no_handler_test.c b/tools/testing/selftests/powerpc/pmu/ebb/no_handler_test.c new file mode 100644 index 000000000000..2f9bf8edfa60 --- /dev/null +++ b/tools/testing/selftests/powerpc/pmu/ebb/no_handler_test.c @@ -0,0 +1,61 @@ +/* + * Copyright 2014, Michael Ellerman, IBM Corp. + * Licensed under GPLv2. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <setjmp.h> +#include <signal.h> + +#include "ebb.h" + + +/* Test that things work sanely if we have no handler */ + +static int no_handler_test(void) +{ + struct event event; + u64 val; + int i; + + event_init_named(&event, 0x1001e, "cycles"); + event_leader_ebb_init(&event); + + event.attr.exclude_kernel = 1; + event.attr.exclude_hv = 1; + event.attr.exclude_idle = 1; + + FAIL_IF(event_open(&event)); + FAIL_IF(ebb_event_enable(&event)); + + val = mfspr(SPRN_EBBHR); + FAIL_IF(val != 0); + + /* Make sure it overflows quickly */ + sample_period = 1000; + mtspr(SPRN_PMC1, pmc_sample_period(sample_period)); + + /* Spin to make sure the event has time to overflow */ + for (i = 0; i < 1000; i++) + mb(); + + dump_ebb_state(); + + /* We expect to see the PMU frozen & PMAO set */ + val = mfspr(SPRN_MMCR0); + FAIL_IF(val != 0x0000000080000080); + + event_close(&event); + + dump_ebb_state(); + + /* The real test is that we never took an EBB at 0x0 */ + + return 0; +} + +int main(void) +{ + return test_harness(no_handler_test,"no_handler_test"); +} diff --git a/tools/testing/selftests/powerpc/pmu/ebb/pmae_handling_test.c b/tools/testing/selftests/powerpc/pmu/ebb/pmae_handling_test.c new file mode 100644 index 000000000000..986500fd2131 --- /dev/null +++ b/tools/testing/selftests/powerpc/pmu/ebb/pmae_handling_test.c @@ -0,0 +1,106 @@ +/* + * Copyright 2014, Michael Ellerman, IBM Corp. + * Licensed under GPLv2. + */ + +#include <sched.h> +#include <signal.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> + +#include "ebb.h" + + +/* + * Test that the kernel properly handles PMAE across context switches. + * + * We test this by calling into the kernel inside our EBB handler, where PMAE + * is clear. A cpu eater companion thread is running on the same CPU as us to + * encourage the scheduler to switch us. + * + * The kernel must make sure that when it context switches us back in, it + * honours the fact that we had PMAE clear. + * + * Observed to hit the failing case on the first EBB with a broken kernel. + */ + +static bool mmcr0_mismatch; +static uint64_t before, after; + +static void syscall_ebb_callee(void) +{ + uint64_t val; + + val = mfspr(SPRN_BESCR); + if (!(val & BESCR_PMEO)) { + ebb_state.stats.spurious++; + goto out; + } + + ebb_state.stats.ebb_count++; + count_pmc(1, sample_period); + + before = mfspr(SPRN_MMCR0); + + /* Try and get ourselves scheduled, to force a PMU context switch */ + sched_yield(); + + after = mfspr(SPRN_MMCR0); + if (before != after) + mmcr0_mismatch = true; + +out: + reset_ebb(); +} + +static int test_body(void) +{ + struct event event; + + event_init_named(&event, 0x1001e, "cycles"); + event_leader_ebb_init(&event); + + event.attr.exclude_kernel = 1; + event.attr.exclude_hv = 1; + event.attr.exclude_idle = 1; + + FAIL_IF(event_open(&event)); + + setup_ebb_handler(syscall_ebb_callee); + ebb_global_enable(); + + FAIL_IF(ebb_event_enable(&event)); + + mtspr(SPRN_PMC1, pmc_sample_period(sample_period)); + + while (ebb_state.stats.ebb_count < 20 && !mmcr0_mismatch) + FAIL_IF(core_busy_loop()); + + ebb_global_disable(); + ebb_freeze_pmcs(); + + count_pmc(1, sample_period); + + dump_ebb_state(); + + if (mmcr0_mismatch) + printf("Saw MMCR0 before 0x%lx after 0x%lx\n", before, after); + + event_close(&event); + + FAIL_IF(ebb_state.stats.ebb_count == 0); + FAIL_IF(mmcr0_mismatch); + + return 0; +} + +int pmae_handling(void) +{ + return eat_cpu(test_body); +} + +int main(void) +{ + return test_harness(pmae_handling, "pmae_handling"); +} diff --git a/tools/testing/selftests/powerpc/pmu/ebb/pmc56_overflow_test.c b/tools/testing/selftests/powerpc/pmu/ebb/pmc56_overflow_test.c new file mode 100644 index 000000000000..a503fa70c950 --- /dev/null +++ b/tools/testing/selftests/powerpc/pmu/ebb/pmc56_overflow_test.c @@ -0,0 +1,93 @@ +/* + * Copyright 2014, Michael Ellerman, IBM Corp. + * Licensed under GPLv2. + */ + +#include <stdio.h> +#include <stdlib.h> + +#include "ebb.h" + + +/* + * Test that PMC5 & 6 are frozen (ie. don't overflow) when they are not being + * used. Tests the MMCR0_FC56 logic in the kernel. + */ + +static int pmc56_overflowed; + +static void ebb_callee(void) +{ + uint64_t val; + + val = mfspr(SPRN_BESCR); + if (!(val & BESCR_PMEO)) { + ebb_state.stats.spurious++; + goto out; + } + + ebb_state.stats.ebb_count++; + count_pmc(2, sample_period); + + val = mfspr(SPRN_PMC5); + if (val >= COUNTER_OVERFLOW) + pmc56_overflowed++; + + count_pmc(5, COUNTER_OVERFLOW); + + val = mfspr(SPRN_PMC6); + if (val >= COUNTER_OVERFLOW) + pmc56_overflowed++; + + count_pmc(6, COUNTER_OVERFLOW); + +out: + reset_ebb(); +} + +int pmc56_overflow(void) +{ + struct event event; + + /* Use PMC2 so we set PMCjCE, which enables PMC5/6 */ + event_init(&event, 0x2001e); + event_leader_ebb_init(&event); + + event.attr.exclude_kernel = 1; + event.attr.exclude_hv = 1; + event.attr.exclude_idle = 1; + + FAIL_IF(event_open(&event)); + + setup_ebb_handler(ebb_callee); + ebb_global_enable(); + + FAIL_IF(ebb_event_enable(&event)); + + mtspr(SPRN_PMC1, pmc_sample_period(sample_period)); + mtspr(SPRN_PMC5, 0); + mtspr(SPRN_PMC6, 0); + + while (ebb_state.stats.ebb_count < 10) + FAIL_IF(core_busy_loop()); + + ebb_global_disable(); + ebb_freeze_pmcs(); + + count_pmc(2, sample_period); + + dump_ebb_state(); + + printf("PMC5/6 overflow %d\n", pmc56_overflowed); + + event_close(&event); + + FAIL_IF(ebb_state.stats.ebb_count == 0 || pmc56_overflowed != 0); + + return 0; +} + +int main(void) +{ + return test_harness(pmc56_overflow, "pmc56_overflow"); +} diff --git a/tools/testing/selftests/powerpc/pmu/ebb/reg.h b/tools/testing/selftests/powerpc/pmu/ebb/reg.h new file mode 100644 index 000000000000..5921b0dfe2e9 --- /dev/null +++ b/tools/testing/selftests/powerpc/pmu/ebb/reg.h @@ -0,0 +1,49 @@ +/* + * Copyright 2014, Michael Ellerman, IBM Corp. + * Licensed under GPLv2. + */ + +#ifndef _SELFTESTS_POWERPC_REG_H +#define _SELFTESTS_POWERPC_REG_H + +#define __stringify_1(x) #x +#define __stringify(x) __stringify_1(x) + +#define mfspr(rn) ({unsigned long rval; \ + asm volatile("mfspr %0," __stringify(rn) \ + : "=r" (rval)); rval; }) +#define mtspr(rn, v) asm volatile("mtspr " __stringify(rn) ",%0" : \ + : "r" ((unsigned long)(v)) \ + : "memory") + +#define mb() asm volatile("sync" : : : "memory"); + +#define SPRN_MMCR2 769 +#define SPRN_MMCRA 770 +#define SPRN_MMCR0 779 +#define MMCR0_PMAO 0x00000080 +#define MMCR0_PMAE 0x04000000 +#define MMCR0_FC 0x80000000 +#define SPRN_EBBHR 804 +#define SPRN_EBBRR 805 +#define SPRN_BESCR 806 /* Branch event status & control register */ +#define SPRN_BESCRS 800 /* Branch event status & control set (1 bits set to 1) */ +#define SPRN_BESCRSU 801 /* Branch event status & control set upper */ +#define SPRN_BESCRR 802 /* Branch event status & control REset (1 bits set to 0) */ +#define SPRN_BESCRRU 803 /* Branch event status & control REset upper */ + +#define BESCR_PMEO 0x1 /* PMU Event-based exception Occurred */ +#define BESCR_PME (0x1ul << 32) /* PMU Event-based exception Enable */ + +#define SPRN_PMC1 771 +#define SPRN_PMC2 772 +#define SPRN_PMC3 773 +#define SPRN_PMC4 774 +#define SPRN_PMC5 775 +#define SPRN_PMC6 776 + +#define SPRN_SIAR 780 +#define SPRN_SDAR 781 +#define SPRN_SIER 768 + +#endif /* _SELFTESTS_POWERPC_REG_H */ diff --git a/tools/testing/selftests/powerpc/pmu/ebb/reg_access_test.c b/tools/testing/selftests/powerpc/pmu/ebb/reg_access_test.c new file mode 100644 index 000000000000..0cae66f659a3 --- /dev/null +++ b/tools/testing/selftests/powerpc/pmu/ebb/reg_access_test.c @@ -0,0 +1,39 @@ +/* + * Copyright 2014, Michael Ellerman, IBM Corp. + * Licensed under GPLv2. + */ + +#include <stdio.h> +#include <stdlib.h> + +#include "ebb.h" +#include "reg.h" + + +/* + * Test basic access to the EBB regs, they should be user accessible with no + * kernel interaction required. + */ +int reg_access(void) +{ + uint64_t val, expected; + + expected = 0x8000000100000000ull; + mtspr(SPRN_BESCR, expected); + val = mfspr(SPRN_BESCR); + + FAIL_IF(val != expected); + + expected = 0x0000000001000000ull; + mtspr(SPRN_EBBHR, expected); + val = mfspr(SPRN_EBBHR); + + FAIL_IF(val != expected); + + return 0; +} + +int main(void) +{ + return test_harness(reg_access, "reg_access"); +} diff --git a/tools/testing/selftests/powerpc/pmu/ebb/task_event_pinned_vs_ebb_test.c b/tools/testing/selftests/powerpc/pmu/ebb/task_event_pinned_vs_ebb_test.c new file mode 100644 index 000000000000..d56607e4ffab --- /dev/null +++ b/tools/testing/selftests/powerpc/pmu/ebb/task_event_pinned_vs_ebb_test.c @@ -0,0 +1,91 @@ +/* + * Copyright 2014, Michael Ellerman, IBM Corp. + * Licensed under GPLv2. + */ + +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "ebb.h" + + +/* + * Tests a pinned per-task event vs an EBB - in that order. The pinned per-task + * event should prevent the EBB event from being enabled. + */ + +static int setup_child_event(struct event *event, pid_t child_pid) +{ + event_init_named(event, 0x400FA, "PM_RUN_INST_CMPL"); + + event->attr.pinned = 1; + + event->attr.exclude_kernel = 1; + event->attr.exclude_hv = 1; + event->attr.exclude_idle = 1; + + FAIL_IF(event_open_with_pid(event, child_pid)); + FAIL_IF(event_enable(event)); + + return 0; +} + +int task_event_pinned_vs_ebb(void) +{ + union pipe read_pipe, write_pipe; + struct event event; + pid_t pid; + int rc; + + FAIL_IF(pipe(read_pipe.fds) == -1); + FAIL_IF(pipe(write_pipe.fds) == -1); + + pid = fork(); + if (pid == 0) { + /* NB order of pipes looks reversed */ + exit(ebb_child(write_pipe, read_pipe)); + } + + /* We setup the task event first */ + rc = setup_child_event(&event, pid); + if (rc) { + kill_child_and_wait(pid); + return rc; + } + + /* Signal the child to install its EBB event and wait */ + if (sync_with_child(read_pipe, write_pipe)) + /* If it fails, wait for it to exit */ + goto wait; + + /* Signal the child to run */ + FAIL_IF(sync_with_child(read_pipe, write_pipe)); + +wait: + /* We expect it to fail to read the event */ + FAIL_IF(wait_for_child(pid) != 2); + FAIL_IF(event_disable(&event)); + FAIL_IF(event_read(&event)); + + event_report(&event); + + FAIL_IF(event.result.value == 0); + /* + * For reasons I don't understand enabled is usually just slightly + * lower than running. Would be good to confirm why. + */ + FAIL_IF(event.result.enabled == 0); + FAIL_IF(event.result.running == 0); + + return 0; +} + +int main(void) +{ + return test_harness(task_event_pinned_vs_ebb, "task_event_pinned_vs_ebb"); +} diff --git a/tools/testing/selftests/powerpc/pmu/ebb/task_event_vs_ebb_test.c b/tools/testing/selftests/powerpc/pmu/ebb/task_event_vs_ebb_test.c new file mode 100644 index 000000000000..eba32196dbbf --- /dev/null +++ b/tools/testing/selftests/powerpc/pmu/ebb/task_event_vs_ebb_test.c @@ -0,0 +1,83 @@ +/* + * Copyright 2014, Michael Ellerman, IBM Corp. + * Licensed under GPLv2. + */ + +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "ebb.h" + + +/* + * Tests a per-task event vs an EBB - in that order. The EBB should push the + * per-task event off the PMU. + */ + +static int setup_child_event(struct event *event, pid_t child_pid) +{ + event_init_named(event, 0x400FA, "PM_RUN_INST_CMPL"); + + event->attr.exclude_kernel = 1; + event->attr.exclude_hv = 1; + event->attr.exclude_idle = 1; + + FAIL_IF(event_open_with_pid(event, child_pid)); + FAIL_IF(event_enable(event)); + + return 0; +} + +int task_event_vs_ebb(void) +{ + union pipe read_pipe, write_pipe; + struct event event; + pid_t pid; + int rc; + + FAIL_IF(pipe(read_pipe.fds) == -1); + FAIL_IF(pipe(write_pipe.fds) == -1); + + pid = fork(); + if (pid == 0) { + /* NB order of pipes looks reversed */ + exit(ebb_child(write_pipe, read_pipe)); + } + + /* We setup the task event first */ + rc = setup_child_event(&event, pid); + if (rc) { + kill_child_and_wait(pid); + return rc; + } + + /* Signal the child to install its EBB event and wait */ + if (sync_with_child(read_pipe, write_pipe)) + /* If it fails, wait for it to exit */ + goto wait; + + /* Signal the child to run */ + FAIL_IF(sync_with_child(read_pipe, write_pipe)); + +wait: + /* The EBB event should push the task event off so the child should succeed */ + FAIL_IF(wait_for_child(pid)); + FAIL_IF(event_disable(&event)); + FAIL_IF(event_read(&event)); + + event_report(&event); + + /* The task event may have run, or not so we can't assert anything about it */ + + return 0; +} + +int main(void) +{ + return test_harness(task_event_vs_ebb, "task_event_vs_ebb"); +} diff --git a/tools/testing/selftests/powerpc/pmu/ebb/trace.c b/tools/testing/selftests/powerpc/pmu/ebb/trace.c new file mode 100644 index 000000000000..251e66ab2aa7 --- /dev/null +++ b/tools/testing/selftests/powerpc/pmu/ebb/trace.c @@ -0,0 +1,300 @@ +/* + * Copyright 2014, Michael Ellerman, IBM Corp. + * Licensed under GPLv2. + */ + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mman.h> + +#include "trace.h" + + +struct trace_buffer *trace_buffer_allocate(u64 size) +{ + struct trace_buffer *tb; + + if (size < sizeof(*tb)) { + fprintf(stderr, "Error: trace buffer too small\n"); + return NULL; + } + + tb = mmap(NULL, size, PROT_READ | PROT_WRITE, + MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + if (tb == MAP_FAILED) { + perror("mmap"); + return NULL; + } + + tb->size = size; + tb->tail = tb->data; + tb->overflow = false; + + return tb; +} + +static bool trace_check_bounds(struct trace_buffer *tb, void *p) +{ + return p < ((void *)tb + tb->size); +} + +static bool trace_check_alloc(struct trace_buffer *tb, void *p) +{ + /* + * If we ever overflowed don't allow any more input. This prevents us + * from dropping a large item and then later logging a small one. The + * buffer should just stop when overflow happened, not be patchy. If + * you're overflowing, make your buffer bigger. + */ + if (tb->overflow) + return false; + + if (!trace_check_bounds(tb, p)) { + tb->overflow = true; + return false; + } + + return true; +} + +static void *trace_alloc(struct trace_buffer *tb, int bytes) +{ + void *p, *newtail; + + p = tb->tail; + newtail = tb->tail + bytes; + if (!trace_check_alloc(tb, newtail)) + return NULL; + + tb->tail = newtail; + + return p; +} + +static struct trace_entry *trace_alloc_entry(struct trace_buffer *tb, int payload_size) +{ + struct trace_entry *e; + + e = trace_alloc(tb, sizeof(*e) + payload_size); + if (e) + e->length = payload_size; + + return e; +} + +int trace_log_reg(struct trace_buffer *tb, u64 reg, u64 value) +{ + struct trace_entry *e; + u64 *p; + + e = trace_alloc_entry(tb, sizeof(reg) + sizeof(value)); + if (!e) + return -ENOSPC; + + e->type = TRACE_TYPE_REG; + p = (u64 *)e->data; + *p++ = reg; + *p++ = value; + + return 0; +} + +int trace_log_counter(struct trace_buffer *tb, u64 value) +{ + struct trace_entry *e; + u64 *p; + + e = trace_alloc_entry(tb, sizeof(value)); + if (!e) + return -ENOSPC; + + e->type = TRACE_TYPE_COUNTER; + p = (u64 *)e->data; + *p++ = value; + + return 0; +} + +int trace_log_string(struct trace_buffer *tb, char *str) +{ + struct trace_entry *e; + char *p; + int len; + + len = strlen(str); + + /* We NULL terminate to make printing easier */ + e = trace_alloc_entry(tb, len + 1); + if (!e) + return -ENOSPC; + + e->type = TRACE_TYPE_STRING; + p = (char *)e->data; + memcpy(p, str, len); + p += len; + *p = '\0'; + + return 0; +} + +int trace_log_indent(struct trace_buffer *tb) +{ + struct trace_entry *e; + + e = trace_alloc_entry(tb, 0); + if (!e) + return -ENOSPC; + + e->type = TRACE_TYPE_INDENT; + + return 0; +} + +int trace_log_outdent(struct trace_buffer *tb) +{ + struct trace_entry *e; + + e = trace_alloc_entry(tb, 0); + if (!e) + return -ENOSPC; + + e->type = TRACE_TYPE_OUTDENT; + + return 0; +} + +static void trace_print_header(int seq, int prefix) +{ + printf("%*s[%d]: ", prefix, "", seq); +} + +static char *trace_decode_reg(int reg) +{ + switch (reg) { + case 769: return "SPRN_MMCR2"; break; + case 770: return "SPRN_MMCRA"; break; + case 779: return "SPRN_MMCR0"; break; + case 804: return "SPRN_EBBHR"; break; + case 805: return "SPRN_EBBRR"; break; + case 806: return "SPRN_BESCR"; break; + case 800: return "SPRN_BESCRS"; break; + case 801: return "SPRN_BESCRSU"; break; + case 802: return "SPRN_BESCRR"; break; + case 803: return "SPRN_BESCRRU"; break; + case 771: return "SPRN_PMC1"; break; + case 772: return "SPRN_PMC2"; break; + case 773: return "SPRN_PMC3"; break; + case 774: return "SPRN_PMC4"; break; + case 775: return "SPRN_PMC5"; break; + case 776: return "SPRN_PMC6"; break; + case 780: return "SPRN_SIAR"; break; + case 781: return "SPRN_SDAR"; break; + case 768: return "SPRN_SIER"; break; + } + + return NULL; +} + +static void trace_print_reg(struct trace_entry *e) +{ + u64 *p, *reg, *value; + char *name; + + p = (u64 *)e->data; + reg = p++; + value = p; + + name = trace_decode_reg(*reg); + if (name) + printf("register %-10s = 0x%016llx\n", name, *value); + else + printf("register %lld = 0x%016llx\n", *reg, *value); +} + +static void trace_print_counter(struct trace_entry *e) +{ + u64 *value; + + value = (u64 *)e->data; + printf("counter = %lld\n", *value); +} + +static void trace_print_string(struct trace_entry *e) +{ + char *str; + + str = (char *)e->data; + puts(str); +} + +#define BASE_PREFIX 2 +#define PREFIX_DELTA 8 + +static void trace_print_entry(struct trace_entry *e, int seq, int *prefix) +{ + switch (e->type) { + case TRACE_TYPE_REG: + trace_print_header(seq, *prefix); + trace_print_reg(e); + break; + case TRACE_TYPE_COUNTER: + trace_print_header(seq, *prefix); + trace_print_counter(e); + break; + case TRACE_TYPE_STRING: + trace_print_header(seq, *prefix); + trace_print_string(e); + break; + case TRACE_TYPE_INDENT: + trace_print_header(seq, *prefix); + puts("{"); + *prefix += PREFIX_DELTA; + break; + case TRACE_TYPE_OUTDENT: + *prefix -= PREFIX_DELTA; + if (*prefix < BASE_PREFIX) + *prefix = BASE_PREFIX; + trace_print_header(seq, *prefix); + puts("}"); + break; + default: + trace_print_header(seq, *prefix); + printf("entry @ %p type %d\n", e, e->type); + break; + } +} + +void trace_buffer_print(struct trace_buffer *tb) +{ + struct trace_entry *e; + int i, prefix; + void *p; + + printf("Trace buffer dump:\n"); + printf(" address %p \n", tb); + printf(" tail %p\n", tb->tail); + printf(" size %llu\n", tb->size); + printf(" overflow %s\n", tb->overflow ? "TRUE" : "false"); + printf(" Content:\n"); + + p = tb->data; + + i = 0; + prefix = BASE_PREFIX; + + while (trace_check_bounds(tb, p) && p < tb->tail) { + e = p; + + trace_print_entry(e, i, &prefix); + + i++; + p = (void *)e + sizeof(*e) + e->length; + } +} + +void trace_print_location(struct trace_buffer *tb) +{ + printf("Trace buffer 0x%llx bytes @ %p\n", tb->size, tb); +} diff --git a/tools/testing/selftests/powerpc/pmu/ebb/trace.h b/tools/testing/selftests/powerpc/pmu/ebb/trace.h new file mode 100644 index 000000000000..926458e28c8b --- /dev/null +++ b/tools/testing/selftests/powerpc/pmu/ebb/trace.h @@ -0,0 +1,41 @@ +/* + * Copyright 2014, Michael Ellerman, IBM Corp. + * Licensed under GPLv2. + */ + +#ifndef _SELFTESTS_POWERPC_PMU_EBB_TRACE_H +#define _SELFTESTS_POWERPC_PMU_EBB_TRACE_H + +#include "utils.h" + +#define TRACE_TYPE_REG 1 +#define TRACE_TYPE_COUNTER 2 +#define TRACE_TYPE_STRING 3 +#define TRACE_TYPE_INDENT 4 +#define TRACE_TYPE_OUTDENT 5 + +struct trace_entry +{ + u8 type; + u8 length; + u8 data[0]; +}; + +struct trace_buffer +{ + u64 size; + bool overflow; + void *tail; + u8 data[0]; +}; + +struct trace_buffer *trace_buffer_allocate(u64 size); +int trace_log_reg(struct trace_buffer *tb, u64 reg, u64 value); +int trace_log_counter(struct trace_buffer *tb, u64 value); +int trace_log_string(struct trace_buffer *tb, char *str); +int trace_log_indent(struct trace_buffer *tb); +int trace_log_outdent(struct trace_buffer *tb); +void trace_buffer_print(struct trace_buffer *tb); +void trace_print_location(struct trace_buffer *tb); + +#endif /* _SELFTESTS_POWERPC_PMU_EBB_TRACE_H */ diff --git a/tools/testing/selftests/powerpc/pmu/event.c b/tools/testing/selftests/powerpc/pmu/event.c index 2b2d11df2450..184b36807d48 100644 --- a/tools/testing/selftests/powerpc/pmu/event.c +++ b/tools/testing/selftests/powerpc/pmu/event.c @@ -39,7 +39,13 @@ void event_init_named(struct event *e, u64 config, char *name) event_init_opts(e, config, PERF_TYPE_RAW, name); } +void event_init(struct event *e, u64 config) +{ + event_init_opts(e, config, PERF_TYPE_RAW, "event"); +} + #define PERF_CURRENT_PID 0 +#define PERF_NO_PID -1 #define PERF_NO_CPU -1 #define PERF_NO_GROUP -1 @@ -59,6 +65,16 @@ int event_open_with_group(struct event *e, int group_fd) return event_open_with_options(e, PERF_CURRENT_PID, PERF_NO_CPU, group_fd); } +int event_open_with_pid(struct event *e, pid_t pid) +{ + return event_open_with_options(e, pid, PERF_NO_CPU, PERF_NO_GROUP); +} + +int event_open_with_cpu(struct event *e, int cpu) +{ + return event_open_with_options(e, PERF_NO_PID, cpu, PERF_NO_GROUP); +} + int event_open(struct event *e) { return event_open_with_options(e, PERF_CURRENT_PID, PERF_NO_CPU, PERF_NO_GROUP); @@ -69,6 +85,16 @@ void event_close(struct event *e) close(e->fd); } +int event_enable(struct event *e) +{ + return ioctl(e->fd, PERF_EVENT_IOC_ENABLE); +} + +int event_disable(struct event *e) +{ + return ioctl(e->fd, PERF_EVENT_IOC_DISABLE); +} + int event_reset(struct event *e) { return ioctl(e->fd, PERF_EVENT_IOC_RESET); diff --git a/tools/testing/selftests/powerpc/pmu/event.h b/tools/testing/selftests/powerpc/pmu/event.h index e6993192ff34..a0ea6b1eef73 100644 --- a/tools/testing/selftests/powerpc/pmu/event.h +++ b/tools/testing/selftests/powerpc/pmu/event.h @@ -29,8 +29,12 @@ void event_init_named(struct event *e, u64 config, char *name); void event_init_opts(struct event *e, u64 config, int type, char *name); int event_open_with_options(struct event *e, pid_t pid, int cpu, int group_fd); int event_open_with_group(struct event *e, int group_fd); +int event_open_with_pid(struct event *e, pid_t pid); +int event_open_with_cpu(struct event *e, int cpu); int event_open(struct event *e); void event_close(struct event *e); +int event_enable(struct event *e); +int event_disable(struct event *e); int event_reset(struct event *e); int event_read(struct event *e); void event_report_justified(struct event *e, int name_width, int result_width); diff --git a/tools/testing/selftests/powerpc/pmu/lib.c b/tools/testing/selftests/powerpc/pmu/lib.c new file mode 100644 index 000000000000..0f6a4731d546 --- /dev/null +++ b/tools/testing/selftests/powerpc/pmu/lib.c @@ -0,0 +1,252 @@ +/* + * Copyright 2014, Michael Ellerman, IBM Corp. + * Licensed under GPLv2. + */ + +#define _GNU_SOURCE /* For CPU_ZERO etc. */ + +#include <errno.h> +#include <sched.h> +#include <setjmp.h> +#include <stdlib.h> +#include <sys/wait.h> + +#include "utils.h" +#include "lib.h" + + +int pick_online_cpu(void) +{ + cpu_set_t mask; + int cpu; + + CPU_ZERO(&mask); + + if (sched_getaffinity(0, sizeof(mask), &mask)) { + perror("sched_getaffinity"); + return -1; + } + + /* We prefer a primary thread, but skip 0 */ + for (cpu = 8; cpu < CPU_SETSIZE; cpu += 8) + if (CPU_ISSET(cpu, &mask)) + return cpu; + + /* Search for anything, but in reverse */ + for (cpu = CPU_SETSIZE - 1; cpu >= 0; cpu--) + if (CPU_ISSET(cpu, &mask)) + return cpu; + + printf("No cpus in affinity mask?!\n"); + return -1; +} + +int bind_to_cpu(int cpu) +{ + cpu_set_t mask; + + printf("Binding to cpu %d\n", cpu); + + CPU_ZERO(&mask); + CPU_SET(cpu, &mask); + + return sched_setaffinity(0, sizeof(mask), &mask); +} + +#define PARENT_TOKEN 0xAA +#define CHILD_TOKEN 0x55 + +int sync_with_child(union pipe read_pipe, union pipe write_pipe) +{ + char c = PARENT_TOKEN; + + FAIL_IF(write(write_pipe.write_fd, &c, 1) != 1); + FAIL_IF(read(read_pipe.read_fd, &c, 1) != 1); + if (c != CHILD_TOKEN) /* sometimes expected */ + return 1; + + return 0; +} + +int wait_for_parent(union pipe read_pipe) +{ + char c; + + FAIL_IF(read(read_pipe.read_fd, &c, 1) != 1); + FAIL_IF(c != PARENT_TOKEN); + + return 0; +} + +int notify_parent(union pipe write_pipe) +{ + char c = CHILD_TOKEN; + + FAIL_IF(write(write_pipe.write_fd, &c, 1) != 1); + + return 0; +} + +int notify_parent_of_error(union pipe write_pipe) +{ + char c = ~CHILD_TOKEN; + + FAIL_IF(write(write_pipe.write_fd, &c, 1) != 1); + + return 0; +} + +int wait_for_child(pid_t child_pid) +{ + int rc; + + if (waitpid(child_pid, &rc, 0) == -1) { + perror("waitpid"); + return 1; + } + + if (WIFEXITED(rc)) + rc = WEXITSTATUS(rc); + else + rc = 1; /* Signal or other */ + + return rc; +} + +int kill_child_and_wait(pid_t child_pid) +{ + kill(child_pid, SIGTERM); + + return wait_for_child(child_pid); +} + +static int eat_cpu_child(union pipe read_pipe, union pipe write_pipe) +{ + volatile int i = 0; + + /* + * We are just here to eat cpu and die. So make sure we can be killed, + * and also don't do any custom SIGTERM handling. + */ + signal(SIGTERM, SIG_DFL); + + notify_parent(write_pipe); + wait_for_parent(read_pipe); + + /* Soak up cpu forever */ + while (1) i++; + + return 0; +} + +pid_t eat_cpu(int (test_function)(void)) +{ + union pipe read_pipe, write_pipe; + int cpu, rc; + pid_t pid; + + cpu = pick_online_cpu(); + FAIL_IF(cpu < 0); + FAIL_IF(bind_to_cpu(cpu)); + + if (pipe(read_pipe.fds) == -1) + return -1; + + if (pipe(write_pipe.fds) == -1) + return -1; + + pid = fork(); + if (pid == 0) + exit(eat_cpu_child(write_pipe, read_pipe)); + + if (sync_with_child(read_pipe, write_pipe)) { + rc = -1; + goto out; + } + + printf("main test running as pid %d\n", getpid()); + + rc = test_function(); +out: + kill(pid, SIGKILL); + + return rc; +} + +struct addr_range libc, vdso; + +int parse_proc_maps(void) +{ + char execute, name[128]; + uint64_t start, end; + FILE *f; + int rc; + + f = fopen("/proc/self/maps", "r"); + if (!f) { + perror("fopen"); + return -1; + } + + do { + /* This skips line with no executable which is what we want */ + rc = fscanf(f, "%lx-%lx %*c%*c%c%*c %*x %*d:%*d %*d %127s\n", + &start, &end, &execute, name); + if (rc <= 0) + break; + + if (execute != 'x') + continue; + + if (strstr(name, "libc")) { + libc.first = start; + libc.last = end - 1; + } else if (strstr(name, "[vdso]")) { + vdso.first = start; + vdso.last = end - 1; + } + } while(1); + + fclose(f); + + return 0; +} + +#define PARANOID_PATH "/proc/sys/kernel/perf_event_paranoid" + +bool require_paranoia_below(int level) +{ + unsigned long current; + char *end, buf[16]; + FILE *f; + int rc; + + rc = -1; + + f = fopen(PARANOID_PATH, "r"); + if (!f) { + perror("fopen"); + goto out; + } + + if (!fgets(buf, sizeof(buf), f)) { + printf("Couldn't read " PARANOID_PATH "?\n"); + goto out_close; + } + + current = strtoul(buf, &end, 10); + + if (end == buf) { + printf("Couldn't parse " PARANOID_PATH "?\n"); + goto out_close; + } + + if (current >= level) + goto out; + + rc = 0; +out_close: + fclose(f); +out: + return rc; +} diff --git a/tools/testing/selftests/powerpc/pmu/lib.h b/tools/testing/selftests/powerpc/pmu/lib.h new file mode 100644 index 000000000000..ca5d72ae3be6 --- /dev/null +++ b/tools/testing/selftests/powerpc/pmu/lib.h @@ -0,0 +1,41 @@ +/* + * Copyright 2014, Michael Ellerman, IBM Corp. + * Licensed under GPLv2. + */ + +#ifndef __SELFTESTS_POWERPC_PMU_LIB_H +#define __SELFTESTS_POWERPC_PMU_LIB_H + +#include <stdio.h> +#include <stdint.h> +#include <string.h> +#include <unistd.h> + +union pipe { + struct { + int read_fd; + int write_fd; + }; + int fds[2]; +}; + +extern int pick_online_cpu(void); +extern int bind_to_cpu(int cpu); +extern int kill_child_and_wait(pid_t child_pid); +extern int wait_for_child(pid_t child_pid); +extern int sync_with_child(union pipe read_pipe, union pipe write_pipe); +extern int wait_for_parent(union pipe read_pipe); +extern int notify_parent(union pipe write_pipe); +extern int notify_parent_of_error(union pipe write_pipe); +extern pid_t eat_cpu(int (test_function)(void)); +extern bool require_paranoia_below(int level); + +struct addr_range { + uint64_t first, last; +}; + +extern struct addr_range libc, vdso; + +int parse_proc_maps(void); + +#endif /* __SELFTESTS_POWERPC_PMU_LIB_H */ diff --git a/tools/testing/selftests/powerpc/pmu/loop.S b/tools/testing/selftests/powerpc/pmu/loop.S index 8820e3df1444..20c1f0876c47 100644 --- a/tools/testing/selftests/powerpc/pmu/loop.S +++ b/tools/testing/selftests/powerpc/pmu/loop.S @@ -3,44 +3,41 @@ * Licensed under GPLv2. */ +#include <ppc-asm.h> + .text - .global thirty_two_instruction_loop - .type .thirty_two_instruction_loop,@function - .section ".opd","aw",@progbits -thirty_two_instruction_loop: - .quad .thirty_two_instruction_loop, .TOC.@tocbase, 0 - .previous -.thirty_two_instruction_loop: - cmpwi %r3,0 +FUNC_START(thirty_two_instruction_loop) + cmpdi r3,0 beqlr - addi %r4,%r3,1 - addi %r4,%r4,1 - addi %r4,%r4,1 - addi %r4,%r4,1 - addi %r4,%r4,1 - addi %r4,%r4,1 - addi %r4,%r4,1 - addi %r4,%r4,1 - addi %r4,%r4,1 - addi %r4,%r4,1 - addi %r4,%r4,1 - addi %r4,%r4,1 - addi %r4,%r4,1 - addi %r4,%r4,1 - addi %r4,%r4,1 - addi %r4,%r4,1 - addi %r4,%r4,1 - addi %r4,%r4,1 - addi %r4,%r4,1 - addi %r4,%r4,1 - addi %r4,%r4,1 - addi %r4,%r4,1 - addi %r4,%r4,1 - addi %r4,%r4,1 - addi %r4,%r4,1 - addi %r4,%r4,1 - addi %r4,%r4,1 - addi %r4,%r4,1 # 28 addi's - subi %r3,%r3,1 - b .thirty_two_instruction_loop + addi r4,r3,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 + addi r4,r4,1 # 28 addi's + subi r3,r3,1 + b FUNC_NAME(thirty_two_instruction_loop) +FUNC_END(thirty_two_instruction_loop) diff --git a/tools/testing/selftests/powerpc/subunit.h b/tools/testing/selftests/powerpc/subunit.h index 98a22920792d..9c6c4e901ab6 100644 --- a/tools/testing/selftests/powerpc/subunit.h +++ b/tools/testing/selftests/powerpc/subunit.h @@ -26,6 +26,11 @@ static inline void test_error(char *name) printf("error: %s\n", name); } +static inline void test_skip(char *name) +{ + printf("skip: %s\n", name); +} + static inline void test_success(char *name) { printf("success: %s\n", name); diff --git a/tools/testing/selftests/powerpc/tm/Makefile b/tools/testing/selftests/powerpc/tm/Makefile new file mode 100644 index 000000000000..2cede239a074 --- /dev/null +++ b/tools/testing/selftests/powerpc/tm/Makefile @@ -0,0 +1,15 @@ +PROGS := tm-resched-dscr + +all: $(PROGS) + +$(PROGS): ../harness.c + +run_tests: all + @-for PROG in $(PROGS); do \ + ./$$PROG; \ + done; + +clean: + rm -f $(PROGS) *.o + +.PHONY: all run_tests clean diff --git a/tools/testing/selftests/powerpc/tm/tm-resched-dscr.c b/tools/testing/selftests/powerpc/tm/tm-resched-dscr.c new file mode 100644 index 000000000000..42d4c8caad81 --- /dev/null +++ b/tools/testing/selftests/powerpc/tm/tm-resched-dscr.c @@ -0,0 +1,98 @@ +/* Test context switching to see if the DSCR SPR is correctly preserved + * when within a transaction. + * + * Note: We assume that the DSCR has been left at the default value (0) + * for all CPUs. + * + * Method: + * + * Set a value into the DSCR. + * + * Start a transaction, and suspend it (*). + * + * Hard loop checking to see if the transaction has become doomed. + * + * Now that we *may* have been preempted, record the DSCR and TEXASR SPRS. + * + * If the abort was because of a context switch, check the DSCR value. + * Otherwise, try again. + * + * (*) If the transaction is not suspended we can't see the problem because + * the transaction abort handler will restore the DSCR to it's checkpointed + * value before we regain control. + */ + +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#include <asm/tm.h> + +#include "utils.h" + +#define TBEGIN ".long 0x7C00051D ;" +#define TEND ".long 0x7C00055D ;" +#define TCHECK ".long 0x7C00059C ;" +#define TSUSPEND ".long 0x7C0005DD ;" +#define TRESUME ".long 0x7C2005DD ;" +#define SPRN_TEXASR 0x82 +#define SPRN_DSCR 0x03 + +int test_body(void) +{ + uint64_t rv, dscr1 = 1, dscr2, texasr; + + printf("Check DSCR TM context switch: "); + fflush(stdout); + for (;;) { + rv = 1; + asm __volatile__ ( + /* set a known value into the DSCR */ + "ld 3, %[dscr1];" + "mtspr %[sprn_dscr], 3;" + + /* start and suspend a transaction */ + TBEGIN + "beq 1f;" + TSUSPEND + + /* hard loop until the transaction becomes doomed */ + "2: ;" + TCHECK + "bc 4, 0, 2b;" + + /* record DSCR and TEXASR */ + "mfspr 3, %[sprn_dscr];" + "std 3, %[dscr2];" + "mfspr 3, %[sprn_texasr];" + "std 3, %[texasr];" + + TRESUME + TEND + "li %[rv], 0;" + "1: ;" + : [rv]"=r"(rv), [dscr2]"=m"(dscr2), [texasr]"=m"(texasr) + : [dscr1]"m"(dscr1) + , [sprn_dscr]"i"(SPRN_DSCR), [sprn_texasr]"i"(SPRN_TEXASR) + : "memory", "r3" + ); + assert(rv); /* make sure the transaction aborted */ + if ((texasr >> 56) != TM_CAUSE_RESCHED) { + putchar('.'); + fflush(stdout); + continue; + } + if (dscr2 != dscr1) { + printf(" FAIL\n"); + return 1; + } else { + printf(" OK\n"); + return 0; + } + } +} + +int main(void) +{ + return test_harness(test_body, "tm_resched_dscr"); +} diff --git a/tools/testing/selftests/powerpc/utils.h b/tools/testing/selftests/powerpc/utils.h index 0de064406dab..a93777ae0684 100644 --- a/tools/testing/selftests/powerpc/utils.h +++ b/tools/testing/selftests/powerpc/utils.h @@ -31,6 +31,18 @@ do { \ } \ } while (0) +/* The test harness uses this, yes it's gross */ +#define MAGIC_SKIP_RETURN_VALUE 99 + +#define SKIP_IF(x) \ +do { \ + if ((x)) { \ + fprintf(stderr, \ + "[SKIP] Test skipped on line %d\n", __LINE__); \ + return MAGIC_SKIP_RETURN_VALUE; \ + } \ +} while (0) + #define _str(s) #s #define str(s) _str(s) diff --git a/tools/testing/selftests/sysctl/Makefile b/tools/testing/selftests/sysctl/Makefile new file mode 100644 index 000000000000..0a92adaf0865 --- /dev/null +++ b/tools/testing/selftests/sysctl/Makefile @@ -0,0 +1,19 @@ +# Makefile for sysctl selftests. +# Expects kernel.sysctl_writes_strict=1. + +# No binaries, but make sure arg-less "make" doesn't trigger "run_tests". +all: + +# Allow specific tests to be selected. +test_num: + @/bin/sh ./run_numerictests + +test_string: + @/bin/sh ./run_stringtests + +run_tests: all test_num test_string + +# Nothing to clean up. +clean: + +.PHONY: all run_tests clean test_num test_string diff --git a/tools/testing/selftests/sysctl/common_tests b/tools/testing/selftests/sysctl/common_tests new file mode 100644 index 000000000000..17d534b1b7b4 --- /dev/null +++ b/tools/testing/selftests/sysctl/common_tests @@ -0,0 +1,109 @@ +#!/bin/sh + +TEST_FILE=$(mktemp) + +echo "== Testing sysctl behavior against ${TARGET} ==" + +set_orig() +{ + echo "${ORIG}" > "${TARGET}" +} + +set_test() +{ + echo "${TEST_STR}" > "${TARGET}" +} + +verify() +{ + local seen + seen=$(cat "$1") + if [ "${seen}" != "${TEST_STR}" ]; then + return 1 + fi + return 0 +} + +trap 'set_orig; rm -f "${TEST_FILE}"' EXIT + +rc=0 + +echo -n "Writing test file ... " +echo "${TEST_STR}" > "${TEST_FILE}" +if ! verify "${TEST_FILE}"; then + echo "FAIL" >&2 + exit 1 +else + echo "ok" +fi + +echo -n "Checking sysctl is not set to test value ... " +if verify "${TARGET}"; then + echo "FAIL" >&2 + exit 1 +else + echo "ok" +fi + +echo -n "Writing sysctl from shell ... " +set_test +if ! verify "${TARGET}"; then + echo "FAIL" >&2 + exit 1 +else + echo "ok" +fi + +echo -n "Resetting sysctl to original value ... " +set_orig +if verify "${TARGET}"; then + echo "FAIL" >&2 + exit 1 +else + echo "ok" +fi + +# Now that we've validated the sanity of "set_test" and "set_orig", +# we can use those functions to set starting states before running +# specific behavioral tests. + +echo -n "Writing entire sysctl in single write ... " +set_orig +dd if="${TEST_FILE}" of="${TARGET}" bs=4096 2>/dev/null +if ! verify "${TARGET}"; then + echo "FAIL" >&2 + rc=1 +else + echo "ok" +fi + +echo -n "Writing middle of sysctl after synchronized seek ... " +set_test +dd if="${TEST_FILE}" of="${TARGET}" bs=1 seek=1 skip=1 2>/dev/null +if ! verify "${TARGET}"; then + echo "FAIL" >&2 + rc=1 +else + echo "ok" +fi + +echo -n "Writing beyond end of sysctl ... " +set_orig +dd if="${TEST_FILE}" of="${TARGET}" bs=20 seek=2 2>/dev/null +if verify "${TARGET}"; then + echo "FAIL" >&2 + rc=1 +else + echo "ok" +fi + +echo -n "Writing sysctl with multiple long writes ... " +set_orig +(perl -e 'print "A" x 50;'; echo "${TEST_STR}") | \ + dd of="${TARGET}" bs=50 2>/dev/null +if verify "${TARGET}"; then + echo "FAIL" >&2 + rc=1 +else + echo "ok" +fi diff --git a/tools/testing/selftests/sysctl/run_numerictests b/tools/testing/selftests/sysctl/run_numerictests new file mode 100644 index 000000000000..8510f93f2d14 --- /dev/null +++ b/tools/testing/selftests/sysctl/run_numerictests @@ -0,0 +1,10 @@ +#!/bin/sh + +SYSCTL="/proc/sys" +TARGET="${SYSCTL}/vm/swappiness" +ORIG=$(cat "${TARGET}") +TEST_STR=$(( $ORIG + 1 )) + +. ./common_tests + +exit $rc diff --git a/tools/testing/selftests/sysctl/run_stringtests b/tools/testing/selftests/sysctl/run_stringtests new file mode 100644 index 000000000000..90a9293d520c --- /dev/null +++ b/tools/testing/selftests/sysctl/run_stringtests @@ -0,0 +1,77 @@ +#!/bin/sh + +SYSCTL="/proc/sys" +TARGET="${SYSCTL}/kernel/domainname" +ORIG=$(cat "${TARGET}") +TEST_STR="Testing sysctl" + +. ./common_tests + +# Only string sysctls support seeking/appending. +MAXLEN=65 + +echo -n "Writing entire sysctl in short writes ... " +set_orig +dd if="${TEST_FILE}" of="${TARGET}" bs=1 2>/dev/null +if ! verify "${TARGET}"; then + echo "FAIL" >&2 + rc=1 +else + echo "ok" +fi + +echo -n "Writing middle of sysctl after unsynchronized seek ... " +set_test +dd if="${TEST_FILE}" of="${TARGET}" bs=1 seek=1 2>/dev/null +if verify "${TARGET}"; then + echo "FAIL" >&2 + rc=1 +else + echo "ok" +fi + +echo -n "Checking sysctl maxlen is at least $MAXLEN ... " +set_orig +perl -e 'print "A" x ('"${MAXLEN}"'-2), "B";' | \ + dd of="${TARGET}" bs="${MAXLEN}" 2>/dev/null +if ! grep -q B "${TARGET}"; then + echo "FAIL" >&2 + rc=1 +else + echo "ok" +fi + +echo -n "Checking sysctl keeps original string on overflow append ... " +set_orig +perl -e 'print "A" x ('"${MAXLEN}"'-1), "B";' | \ + dd of="${TARGET}" bs=$(( MAXLEN - 1 )) 2>/dev/null +if grep -q B "${TARGET}"; then + echo "FAIL" >&2 + rc=1 +else + echo "ok" +fi + +echo -n "Checking sysctl stays NULL terminated on write ... " +set_orig +perl -e 'print "A" x ('"${MAXLEN}"'-1), "B";' | \ + dd of="${TARGET}" bs="${MAXLEN}" 2>/dev/null +if grep -q B "${TARGET}"; then + echo "FAIL" >&2 + rc=1 +else + echo "ok" +fi + +echo -n "Checking sysctl stays NULL terminated on overwrite ... " +set_orig +perl -e 'print "A" x ('"${MAXLEN}"'-1), "BB";' | \ + dd of="${TARGET}" bs=$(( $MAXLEN + 1 )) 2>/dev/null +if grep -q B "${TARGET}"; then + echo "FAIL" >&2 + rc=1 +else + echo "ok" +fi + +exit $rc diff --git a/tools/usb/ffs-test.c b/tools/usb/ffs-test.c index fe1e66b6ef40..a87e99f37c52 100644 --- a/tools/usb/ffs-test.c +++ b/tools/usb/ffs-test.c @@ -116,8 +116,8 @@ static const struct { .header = { .magic = cpu_to_le32(FUNCTIONFS_DESCRIPTORS_MAGIC), .length = cpu_to_le32(sizeof descriptors), - .fs_count = 3, - .hs_count = 3, + .fs_count = cpu_to_le32(3), + .hs_count = cpu_to_le32(3), }, .fs_descs = { .intf = { diff --git a/tools/vm/page-types.c b/tools/vm/page-types.c index 05654f5e48d5..c4d6d2e20e0d 100644 --- a/tools/vm/page-types.c +++ b/tools/vm/page-types.c @@ -32,6 +32,8 @@ #include <assert.h> #include <ftw.h> #include <time.h> +#include <setjmp.h> +#include <signal.h> #include <sys/types.h> #include <sys/errno.h> #include <sys/fcntl.h> @@ -824,21 +826,38 @@ static void show_file(const char *name, const struct stat *st) atime, now - st->st_atime); } +static sigjmp_buf sigbus_jmp; + +static void * volatile sigbus_addr; + +static void sigbus_handler(int sig, siginfo_t *info, void *ucontex) +{ + (void)sig; + (void)ucontex; + sigbus_addr = info ? info->si_addr : NULL; + siglongjmp(sigbus_jmp, 1); +} + +static struct sigaction sigbus_action = { + .sa_sigaction = sigbus_handler, + .sa_flags = SA_SIGINFO, +}; + static void walk_file(const char *name, const struct stat *st) { uint8_t vec[PAGEMAP_BATCH]; uint64_t buf[PAGEMAP_BATCH], flags; unsigned long nr_pages, pfn, i; + off_t off, end = st->st_size; int fd; - off_t off; ssize_t len; void *ptr; int first = 1; fd = checked_open(name, O_RDONLY|O_NOATIME|O_NOFOLLOW); - for (off = 0; off < st->st_size; off += len) { - nr_pages = (st->st_size - off + page_size - 1) / page_size; + for (off = 0; off < end; off += len) { + nr_pages = (end - off + page_size - 1) / page_size; if (nr_pages > PAGEMAP_BATCH) nr_pages = PAGEMAP_BATCH; len = nr_pages * page_size; @@ -855,11 +874,19 @@ static void walk_file(const char *name, const struct stat *st) if (madvise(ptr, len, MADV_RANDOM)) fatal("madvice failed: %s", name); + if (sigsetjmp(sigbus_jmp, 1)) { + end = off + sigbus_addr ? sigbus_addr - ptr : 0; + fprintf(stderr, "got sigbus at offset %lld: %s\n", + (long long)end, name); + goto got_sigbus; + } + /* populate ptes */ for (i = 0; i < nr_pages ; i++) { if (vec[i] & 1) (void)*(volatile int *)(ptr + i * page_size); } +got_sigbus: /* turn off harvesting reference bits */ if (madvise(ptr, len, MADV_SEQUENTIAL)) @@ -910,6 +937,7 @@ static void walk_page_cache(void) kpageflags_fd = checked_open(PROC_KPAGEFLAGS, O_RDONLY); pagemap_fd = checked_open("/proc/self/pagemap", O_RDONLY); + sigaction(SIGBUS, &sigbus_action, NULL); if (stat(opt_file, &st)) fatal("stat failed: %s\n", opt_file); @@ -925,6 +953,7 @@ static void walk_page_cache(void) close(kpageflags_fd); close(pagemap_fd); + signal(SIGBUS, SIG_DFL); } static void parse_file(const char *name) |