| /** |
| * @file op_events.c |
| * Details of PMC profiling events |
| * |
| * You can have silliness here. |
| * |
| * @remark Copyright 2002 OProfile authors |
| * @remark Read the file COPYING |
| * |
| * @author John Levon |
| * @author Philippe Elie |
| */ |
| |
| #include "op_events.h" |
| #include "op_libiberty.h" |
| #include "op_fileio.h" |
| #include "op_string.h" |
| #include "op_cpufreq.h" |
| #include "op_hw_specific.h" |
| #include "op_parse_event.h" |
| |
| #include <string.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <ctype.h> |
| |
| static LIST_HEAD(events_list); |
| static LIST_HEAD(um_list); |
| |
| static char const * filename; |
| static unsigned int line_nr; |
| |
| static void delete_event(struct op_event * event); |
| static void read_events(char const * file); |
| static void read_unit_masks(char const * file); |
| static void free_unit_mask(struct op_unit_mask * um); |
| |
| static char *build_fn(const char *cpu_name, const char *fn) |
| { |
| char *s; |
| static const char *dir; |
| if (dir == NULL) |
| dir = getenv("OPROFILE_EVENTS_DIR"); |
| if (dir == NULL) |
| dir = OP_DATADIR; |
| s = xmalloc(strlen(dir) + strlen(cpu_name) + strlen(fn) + 5); |
| sprintf(s, "%s/%s/%s", dir, cpu_name, fn); |
| return s; |
| } |
| |
| static void parse_error(char const * context) |
| { |
| fprintf(stderr, "oprofile: parse error in %s, line %u\n", |
| filename, line_nr); |
| fprintf(stderr, "%s\n", context); |
| exit(EXIT_FAILURE); |
| } |
| |
| |
| static int parse_int(char const * str) |
| { |
| int value; |
| if (sscanf(str, "%d", &value) != 1) |
| parse_error("expected decimal value"); |
| |
| return value; |
| } |
| |
| |
| static int parse_hex(char const * str) |
| { |
| int value; |
| /* 0x/0X to force the use of hexa notation for field intended to |
| be in hexadecimal */ |
| if (sscanf(str, "0x%x", &value) != 1 && |
| sscanf(str, "0X%x", &value) != 1) |
| parse_error("expected hexadecimal value"); |
| |
| return value; |
| } |
| |
| |
| static u64 parse_long_hex(char const * str) |
| { |
| u64 value; |
| if (sscanf(str, "%Lx", &value) != 1) |
| parse_error("expected long hexadecimal value"); |
| |
| fflush(stderr); |
| return value; |
| } |
| |
| static void include_um(const char *start, const char *end) |
| { |
| char *s; |
| char cpu[end - start + 1]; |
| int old_line_nr; |
| const char *old_filename; |
| |
| strncpy(cpu, start, end - start); |
| cpu[end - start] = 0; |
| s = build_fn(cpu, "unit_masks"); |
| old_line_nr = line_nr; |
| old_filename = filename; |
| read_unit_masks(s); |
| line_nr = old_line_nr; |
| filename = old_filename; |
| free(s); |
| } |
| |
| /* extra:cmask=12,inv,edge */ |
| unsigned parse_extra(const char *s) |
| { |
| unsigned v, w; |
| int o; |
| |
| v = 0; |
| while (*s) { |
| if (isspace(*s)) |
| break; |
| if (strisprefix(s, "edge")) { |
| v |= EXTRA_EDGE; |
| s += 4; |
| } else if (strisprefix(s, "inv")) { |
| v |= EXTRA_INV; |
| s += 3; |
| } else if (sscanf(s, "cmask=%x%n", &w, &o) >= 1) { |
| v |= (w & EXTRA_CMASK_MASK) << EXTRA_CMASK_SHIFT; |
| s += o; |
| } else { |
| parse_error("Illegal extra field modifier"); |
| } |
| if (*s == ',') |
| ++s; |
| } |
| return v; |
| } |
| |
| /* name:MESI type:bitmask default:0x0f */ |
| static void parse_um(struct op_unit_mask * um, char const * line) |
| { |
| int seen_name = 0; |
| int seen_type = 0; |
| int seen_default = 0; |
| char const * valueend = line + 1; |
| char const * tagend = line + 1; |
| char const * start = line; |
| |
| while (*valueend) { |
| valueend = skip_nonws(valueend); |
| |
| while (*tagend != ':' && *tagend) |
| ++tagend; |
| |
| if (valueend == tagend) |
| break; |
| |
| if (!*tagend) |
| parse_error("parse_um() expected :value"); |
| |
| ++tagend; |
| |
| if (strisprefix(start, "include")) { |
| if (seen_name + seen_type + seen_default > 0) |
| parse_error("include must be on its own"); |
| free_unit_mask(um); |
| include_um(tagend, valueend); |
| return; |
| } |
| |
| if (strisprefix(start, "name")) { |
| if (seen_name) |
| parse_error("duplicate name: tag"); |
| seen_name = 1; |
| um->name = op_xstrndup(tagend, valueend - tagend); |
| } else if (strisprefix(start, "type")) { |
| if (seen_type) |
| parse_error("duplicate type: tag"); |
| seen_type = 1; |
| if (strisprefix(tagend, "mandatory")) { |
| um->unit_type_mask = utm_mandatory; |
| } else if (strisprefix(tagend, "bitmask")) { |
| um->unit_type_mask = utm_bitmask; |
| } else if (strisprefix(tagend, "exclusive")) { |
| um->unit_type_mask = utm_exclusive; |
| } else { |
| parse_error("invalid unit mask type"); |
| } |
| } else if (strisprefix(start, "default")) { |
| if (seen_default) |
| parse_error("duplicate default: tag"); |
| seen_default = 1; |
| um->default_mask = parse_hex(tagend); |
| } else { |
| parse_error("invalid unit mask tag"); |
| } |
| |
| valueend = skip_ws(valueend); |
| tagend = valueend; |
| start = valueend; |
| } |
| |
| if (!um->name) |
| parse_error("Missing name for unit mask"); |
| if (!seen_type) |
| parse_error("Missing type for unit mask"); |
| } |
| |
| |
| /* \t0x08 (M)odified cache state */ |
| /* \t0x08 extra:inv,cmask=... (M)odified cache state */ |
| static void parse_um_entry(struct op_described_um * entry, char const * line) |
| { |
| char const * c = line; |
| |
| c = skip_ws(c); |
| entry->value = parse_hex(c); |
| c = skip_nonws(c); |
| |
| c = skip_ws(c); |
| if (strisprefix(c, "extra:")) { |
| c += 6; |
| entry->extra = parse_extra(c); |
| c = skip_nonws(c); |
| } else |
| entry->extra = 0; |
| |
| if (!*c) |
| parse_error("invalid unit mask entry"); |
| |
| c = skip_ws(c); |
| |
| if (!*c) |
| parse_error("invalid unit mask entry"); |
| |
| entry->desc = xstrdup(c); |
| } |
| |
| |
| static struct op_unit_mask * new_unit_mask(void) |
| { |
| struct op_unit_mask * um = xmalloc(sizeof(struct op_unit_mask)); |
| memset(um, '\0', sizeof(struct op_unit_mask)); |
| list_add_tail(&um->um_next, &um_list); |
| |
| return um; |
| } |
| |
| static void free_unit_mask(struct op_unit_mask * um) |
| { |
| list_del(&um->um_next); |
| free(um); |
| } |
| |
| /* |
| * name:zero type:mandatory default:0x0 |
| * \t0x0 No unit mask |
| */ |
| static void read_unit_masks(char const * file) |
| { |
| struct op_unit_mask * um = NULL; |
| char * line; |
| FILE * fp = fopen(file, "r"); |
| |
| if (!fp) { |
| fprintf(stderr, |
| "oprofile: could not open unit mask description file %s\n", file); |
| exit(EXIT_FAILURE); |
| } |
| |
| filename = file; |
| line_nr = 1; |
| |
| line = op_get_line(fp); |
| |
| while (line) { |
| if (empty_line(line) || comment_line(line)) |
| goto next; |
| |
| if (line[0] != '\t') { |
| um = new_unit_mask(); |
| parse_um(um, line); |
| } else { |
| if (!um) |
| parse_error("no unit mask name line"); |
| if (um->num >= MAX_UNIT_MASK) |
| parse_error("oprofile: maximum unit mask entries exceeded"); |
| |
| parse_um_entry(&um->um[um->num], line); |
| ++(um->num); |
| } |
| |
| next: |
| free(line); |
| line = op_get_line(fp); |
| ++line_nr; |
| } |
| |
| fclose(fp); |
| } |
| |
| |
| static u32 parse_counter_mask(char const * str) |
| { |
| u32 mask = 0; |
| char const * numstart = str; |
| |
| while (*numstart) { |
| mask |= 1 << parse_int(numstart); |
| |
| while (*numstart && *numstart != ',') |
| ++numstart; |
| /* skip , unless we reach eos */ |
| if (*numstart) |
| ++numstart; |
| |
| numstart = skip_ws(numstart); |
| } |
| |
| return mask; |
| } |
| |
| static struct op_unit_mask * try_find_um(char const * value) |
| { |
| struct list_head * pos; |
| |
| list_for_each(pos, &um_list) { |
| struct op_unit_mask * um = list_entry(pos, struct op_unit_mask, um_next); |
| if (strcmp(value, um->name) == 0) { |
| um->used = 1; |
| return um; |
| } |
| } |
| return NULL; |
| } |
| |
| static struct op_unit_mask * find_um(char const * value) |
| { |
| struct op_unit_mask * um = try_find_um(value); |
| if (um) |
| return um; |
| fprintf(stderr, "oprofile: could not find unit mask %s\n", value); |
| exit(EXIT_FAILURE); |
| } |
| |
| /* um:a,b,c,d merge multiple unit masks */ |
| static struct op_unit_mask * merge_um(char * value) |
| { |
| int num; |
| char *s; |
| struct op_unit_mask *new, *um; |
| enum unit_mask_type type = -1U; |
| |
| um = try_find_um(value); |
| if (um) |
| return um; |
| |
| new = new_unit_mask(); |
| new->name = xstrdup(value); |
| new->used = 1; |
| num = 0; |
| while ((s = strsep(&value, ",")) != NULL) { |
| unsigned c; |
| um = find_um(s); |
| if (type == -1U) |
| type = um->unit_type_mask; |
| if (um->unit_type_mask != type) |
| parse_error("combined unit mask must be all the same types"); |
| if (type != utm_bitmask && type != utm_exclusive) |
| parse_error("combined unit mask must be all bitmasks or exclusive"); |
| new->default_mask |= um->default_mask; |
| new->num += um->num; |
| if (new->num > MAX_UNIT_MASK) |
| parse_error("too many members in combined unit mask"); |
| for (c = 0; c < um->num; c++, num++) { |
| new->um[num] = um->um[c]; |
| new->um[num].desc = xstrdup(new->um[num].desc); |
| } |
| } |
| if (type == -1U) |
| parse_error("Empty unit mask"); |
| new->unit_type_mask = type; |
| return new; |
| } |
| |
| /* parse either a "tag:value" or a ": trailing description string" */ |
| static int next_token(char const ** cp, char ** name, char ** value) |
| { |
| size_t tag_len; |
| size_t val_len; |
| char const * c = *cp; |
| char const * end; |
| char const * colon; |
| |
| c = skip_ws(c); |
| end = colon = c; |
| end = skip_nonws(end); |
| |
| colon = strchr(colon, ':'); |
| |
| if (!colon) { |
| if (*c) |
| parse_error("next_token(): garbage at end of line"); |
| return 0; |
| } |
| |
| if (colon >= end) |
| parse_error("next_token() expected ':'"); |
| |
| tag_len = colon - c; |
| val_len = end - (colon + 1); |
| |
| if (!tag_len) { |
| /* : trailing description */ |
| end = skip_ws(end); |
| *name = xstrdup("desc"); |
| *value = xstrdup(end); |
| end += strlen(end); |
| } else { |
| /* tag:value */ |
| *name = op_xstrndup(c, tag_len); |
| *value = op_xstrndup(colon + 1, val_len); |
| end = skip_ws(end); |
| } |
| |
| *cp = end; |
| return 1; |
| } |
| |
| static void include_events (char *value) |
| { |
| char * event_file; |
| const char *old_filename; |
| int old_line_nr; |
| |
| event_file = build_fn(value, "events"); |
| old_line_nr = line_nr; |
| old_filename = filename; |
| read_events(event_file); |
| line_nr = old_line_nr; |
| filename = old_filename; |
| free(event_file); |
| } |
| |
| static struct op_event * new_event(void) |
| { |
| struct op_event * event = xmalloc(sizeof(struct op_event)); |
| memset(event, '\0', sizeof(struct op_event)); |
| list_add_tail(&event->event_next, &events_list); |
| |
| return event; |
| } |
| |
| static void free_event(struct op_event * event) |
| { |
| list_del(&event->event_next); |
| free(event); |
| } |
| |
| /* event:0x00 counters:0 um:zero minimum:4096 name:ISSUES : Total issues */ |
| /* event:0x00 ext:xxxxxx um:zero minimum:4096 name:ISSUES : Total issues */ |
| static void read_events(char const * file) |
| { |
| struct op_event * event = NULL; |
| char * line; |
| char * name; |
| char * value; |
| char const * c; |
| int seen_event, seen_counters, seen_um, seen_minimum, seen_name, seen_ext; |
| FILE * fp = fopen(file, "r"); |
| int tags; |
| |
| if (!fp) { |
| fprintf(stderr, "oprofile: could not open event description file %s\n", file); |
| exit(EXIT_FAILURE); |
| } |
| |
| filename = file; |
| line_nr = 1; |
| |
| line = op_get_line(fp); |
| |
| while (line) { |
| if (empty_line(line) || comment_line(line)) |
| goto next; |
| |
| tags = 0; |
| seen_name = 0; |
| seen_event = 0; |
| seen_counters = 0; |
| seen_ext = 0; |
| seen_um = 0; |
| seen_minimum = 0; |
| event = new_event(); |
| event->filter = -1; |
| event->ext = NULL; |
| |
| c = line; |
| while (next_token(&c, &name, &value)) { |
| if (strcmp(name, "name") == 0) { |
| if (seen_name) |
| parse_error("duplicate name: tag"); |
| seen_name = 1; |
| if (strchr(value, '/') != NULL) |
| parse_error("invalid event name"); |
| if (strchr(value, '.') != NULL) |
| parse_error("invalid event name"); |
| event->name = value; |
| } else if (strcmp(name, "event") == 0) { |
| if (seen_event) |
| parse_error("duplicate event: tag"); |
| seen_event = 1; |
| event->val = parse_hex(value); |
| free(value); |
| } else if (strcmp(name, "counters") == 0) { |
| if (seen_counters) |
| parse_error("duplicate counters: tag"); |
| seen_counters = 1; |
| if (!strcmp(value, "cpuid")) |
| event->counter_mask = arch_get_counter_mask(); |
| else |
| event->counter_mask = parse_counter_mask(value); |
| free(value); |
| } else if (strcmp(name, "ext") == 0) { |
| if (seen_ext) |
| parse_error("duplicate ext: tag"); |
| seen_ext = 1; |
| event->ext = value; |
| } else if (strcmp(name, "um") == 0) { |
| if (seen_um) |
| parse_error("duplicate um: tag"); |
| seen_um = 1; |
| if (strchr(value, ',')) |
| event->unit = merge_um(value); |
| else |
| event->unit = find_um(value); |
| free(value); |
| } else if (strcmp(name, "minimum") == 0) { |
| if (seen_minimum) |
| parse_error("duplicate minimum: tag"); |
| seen_minimum = 1; |
| event->min_count = parse_int(value); |
| free(value); |
| } else if (strcmp(name, "desc") == 0) { |
| event->desc = value; |
| } else if (strcmp(name, "filter") == 0) { |
| event->filter = parse_int(value); |
| free(value); |
| } else if (strcmp(name, "include") == 0) { |
| if (tags > 0) |
| parse_error("tags before include:"); |
| free_event(event); |
| include_events(value); |
| free(value); |
| c = skip_ws(c); |
| if (*c != '\0' && *c != '#') |
| parse_error("non whitespace after include:"); |
| } else { |
| parse_error("unknown tag"); |
| } |
| tags++; |
| |
| free(name); |
| } |
| next: |
| free(line); |
| line = op_get_line(fp); |
| ++line_nr; |
| } |
| |
| fclose(fp); |
| } |
| |
| |
| /* usefull for make check */ |
| static int check_unit_mask(struct op_unit_mask const * um, |
| char const * cpu_name) |
| { |
| u32 i; |
| int err = 0; |
| |
| if (!um->used) { |
| fprintf(stderr, "um %s is not used\n", um->name); |
| err = EXIT_FAILURE; |
| } |
| |
| if (um->unit_type_mask == utm_mandatory && um->num != 1) { |
| fprintf(stderr, "mandatory um %s doesn't contain exactly one " |
| "entry (%s)\n", um->name, cpu_name); |
| err = EXIT_FAILURE; |
| } else if (um->unit_type_mask == utm_bitmask) { |
| u32 default_mask = um->default_mask; |
| for (i = 0; i < um->num; ++i) |
| default_mask &= ~um->um[i].value; |
| |
| if (default_mask) { |
| fprintf(stderr, "um %s default mask is not valid " |
| "(%s)\n", um->name, cpu_name); |
| err = EXIT_FAILURE; |
| } |
| } else { |
| for (i = 0; i < um->num; ++i) { |
| if (um->default_mask == um->um[i].value) |
| break; |
| } |
| |
| if (i == um->num) { |
| fprintf(stderr, "exclusive um %s default value is not " |
| "valid (%s)\n", um->name, cpu_name); |
| err = EXIT_FAILURE; |
| } |
| } |
| return err; |
| } |
| |
| static void arch_filter_events(op_cpu cpu_type) |
| { |
| struct list_head * pos, * pos2; |
| unsigned filter = arch_get_filter(cpu_type); |
| if (!filter) |
| return; |
| list_for_each_safe (pos, pos2, &events_list) { |
| struct op_event * event = list_entry(pos, struct op_event, event_next); |
| if (event->filter >= 0 && ((1U << event->filter) & filter)) |
| delete_event(event); |
| } |
| } |
| |
| static void load_events_name(const char *cpu_name) |
| { |
| char * event_file; |
| char * um_file; |
| |
| event_file = build_fn(cpu_name, "events"); |
| um_file = build_fn(cpu_name, "unit_masks"); |
| |
| read_unit_masks(um_file); |
| read_events(event_file); |
| |
| free(um_file); |
| free(event_file); |
| } |
| |
| static void load_events(op_cpu cpu_type) |
| { |
| const char * cpu_name = op_get_cpu_name(cpu_type); |
| struct list_head * pos; |
| int err = 0; |
| |
| if (!list_empty(&events_list)) |
| return; |
| |
| load_events_name(cpu_name); |
| |
| arch_filter_events(cpu_type); |
| |
| /* sanity check: all unit mask must be used */ |
| list_for_each(pos, &um_list) { |
| struct op_unit_mask * um = list_entry(pos, struct op_unit_mask, um_next); |
| err |= check_unit_mask(um, cpu_name); |
| } |
| if (err) |
| exit(err); |
| } |
| |
| struct list_head * op_events(op_cpu cpu_type) |
| { |
| load_events(cpu_type); |
| arch_filter_events(cpu_type); |
| return &events_list; |
| } |
| |
| |
| static void delete_unit_mask(struct op_unit_mask * unit) |
| { |
| u32 cur; |
| for (cur = 0 ; cur < unit->num ; ++cur) { |
| if (unit->um[cur].desc) |
| free(unit->um[cur].desc); |
| } |
| |
| if (unit->name) |
| free(unit->name); |
| |
| list_del(&unit->um_next); |
| free(unit); |
| } |
| |
| |
| static void delete_event(struct op_event * event) |
| { |
| if (event->name) |
| free(event->name); |
| if (event->desc) |
| free(event->desc); |
| |
| list_del(&event->event_next); |
| free(event); |
| } |
| |
| |
| void op_free_events(void) |
| { |
| struct list_head * pos, * pos2; |
| list_for_each_safe(pos, pos2, &events_list) { |
| struct op_event * event = list_entry(pos, struct op_event, event_next); |
| delete_event(event); |
| } |
| |
| list_for_each_safe(pos, pos2, &um_list) { |
| struct op_unit_mask * unit = list_entry(pos, struct op_unit_mask, um_next); |
| delete_unit_mask(unit); |
| } |
| } |
| |
| /* There can be actually multiple events here, so this is not quite correct */ |
| static struct op_event * find_event_any(u32 nr) |
| { |
| struct list_head * pos; |
| |
| list_for_each(pos, &events_list) { |
| struct op_event * event = list_entry(pos, struct op_event, event_next); |
| if (event->val == nr) |
| return event; |
| } |
| |
| return NULL; |
| } |
| |
| static struct op_event * find_event_um(u32 nr, u32 um) |
| { |
| struct list_head * pos; |
| unsigned int i; |
| |
| list_for_each(pos, &events_list) { |
| struct op_event * event = list_entry(pos, struct op_event, event_next); |
| if (event->val == nr) { |
| for (i = 0; i < event->unit->num; i++) { |
| if (event->unit->um[i].value == um) |
| return event; |
| } |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static FILE * open_event_mapping_file(char const * cpu_name) |
| { |
| char * ev_map_file; |
| char * dir; |
| dir = getenv("OPROFILE_EVENTS_DIR"); |
| if (dir == NULL) |
| dir = OP_DATADIR; |
| |
| ev_map_file = xmalloc(strlen(dir) + strlen("/") + strlen(cpu_name) + |
| strlen("/") + + strlen("event_mappings") + 1); |
| strcpy(ev_map_file, dir); |
| strcat(ev_map_file, "/"); |
| |
| strcat(ev_map_file, cpu_name); |
| strcat(ev_map_file, "/"); |
| strcat(ev_map_file, "event_mappings"); |
| filename = ev_map_file; |
| return (fopen(ev_map_file, "r")); |
| } |
| |
| |
| /** |
| * This function is PPC64-specific. |
| */ |
| static char const * get_mapping(u32 nr, FILE * fp) |
| { |
| char * line; |
| char * name; |
| char * value; |
| char const * c; |
| char * map = NULL; |
| int seen_event = 0, seen_mmcr0 = 0, seen_mmcr1 = 0, seen_mmcra = 0; |
| u32 mmcr0 = 0; |
| u64 mmcr1 = 0; |
| u32 mmcra = 0; |
| int event_found = 0; |
| |
| line_nr = 1; |
| line = op_get_line(fp); |
| while (line && !event_found) { |
| if (empty_line(line) || comment_line(line)) |
| goto next; |
| |
| seen_event = 0; |
| seen_mmcr0 = 0; |
| seen_mmcr1 = 0; |
| seen_mmcra = 0; |
| mmcr0 = 0; |
| mmcr1 = 0; |
| mmcra = 0; |
| |
| c = line; |
| while (next_token(&c, &name, &value)) { |
| if (strcmp(name, "event") == 0) { |
| u32 evt; |
| if (seen_event) |
| parse_error("duplicate event tag"); |
| seen_event = 1; |
| evt = parse_hex(value); |
| if (evt == nr) |
| event_found = 1; |
| free(value); |
| } else if (strcmp(name, "mmcr0") == 0) { |
| if (seen_mmcr0) |
| parse_error("duplicate mmcr0 tag"); |
| seen_mmcr0 = 1; |
| mmcr0 = parse_hex(value); |
| free(value); |
| } else if (strcmp(name, "mmcr1") == 0) { |
| if (seen_mmcr1) |
| parse_error("duplicate mmcr1: tag"); |
| seen_mmcr1 = 1; |
| mmcr1 = parse_long_hex(value); |
| free(value); |
| } else if (strcmp(name, "mmcra") == 0) { |
| if (seen_mmcra) |
| parse_error("duplicate mmcra: tag"); |
| seen_mmcra = 1; |
| mmcra = parse_hex(value); |
| free(value); |
| } else { |
| parse_error("unknown tag"); |
| } |
| |
| free(name); |
| } |
| next: |
| free(line); |
| line = op_get_line(fp); |
| ++line_nr; |
| } |
| if (event_found) { |
| if (!seen_mmcr0 || !seen_mmcr1 || !seen_mmcra) { |
| fprintf(stderr, "Error: Missing information in line %d of event mapping file %s\n", line_nr, filename); |
| exit(EXIT_FAILURE); |
| } |
| map = xmalloc(70); |
| snprintf(map, 70, "mmcr0:%u mmcr1:%Lu mmcra:%u", |
| mmcr0, mmcr1, mmcra); |
| } |
| |
| return map; |
| } |
| |
| |
| char const * find_mapping_for_event(u32 nr, op_cpu cpu_type) |
| { |
| char const * cpu_name = op_get_cpu_name(cpu_type); |
| FILE * fp = open_event_mapping_file(cpu_name); |
| char const * map = NULL; |
| switch (cpu_type) { |
| case CPU_PPC64_PA6T: |
| case CPU_PPC64_970: |
| case CPU_PPC64_970MP: |
| case CPU_PPC64_POWER4: |
| case CPU_PPC64_POWER5: |
| case CPU_PPC64_POWER5p: |
| case CPU_PPC64_POWER5pp: |
| case CPU_PPC64_POWER6: |
| case CPU_PPC64_POWER7: |
| case CPU_PPC64_IBM_COMPAT_V1: |
| if (!fp) { |
| fprintf(stderr, "oprofile: could not open event mapping file %s\n", filename); |
| exit(EXIT_FAILURE); |
| } else { |
| map = get_mapping(nr, fp); |
| } |
| break; |
| default: |
| break; |
| } |
| |
| if (fp) |
| fclose(fp); |
| |
| return map; |
| } |
| |
| static int match_event(int i, struct op_event *event, unsigned um) |
| { |
| unsigned v = event->unit->um[i].value; |
| |
| switch (event->unit->unit_type_mask) { |
| case utm_exclusive: |
| case utm_mandatory: |
| return v == um; |
| |
| case utm_bitmask: |
| return (v & um) || (!v && v == 0); |
| } |
| |
| abort(); |
| } |
| |
| struct op_event * find_event_by_name(char const * name, unsigned um, int um_valid) |
| { |
| struct list_head * pos; |
| |
| list_for_each(pos, &events_list) { |
| struct op_event * event = list_entry(pos, struct op_event, event_next); |
| if (strcmp(event->name, name) == 0) { |
| if (um_valid) { |
| unsigned i; |
| |
| for (i = 0; i < event->unit->num; i++) |
| if (match_event(i, event, um)) |
| return event; |
| continue; |
| } |
| return event; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| |
| static struct op_event * find_next_event(struct op_event * e) |
| { |
| struct list_head * n; |
| |
| for (n = e->event_next.next; n != &events_list; n = n->next) { |
| struct op_event * ne = list_entry(n, struct op_event, event_next); |
| if (!strcmp(e->name, ne->name)) |
| return ne; |
| } |
| return NULL; |
| } |
| |
| struct op_event * op_find_event(op_cpu cpu_type, u32 nr, u32 um) |
| { |
| struct op_event * event; |
| |
| load_events(cpu_type); |
| |
| event = find_event_um(nr, um); |
| |
| return event; |
| } |
| |
| struct op_event * op_find_event_any(op_cpu cpu_type, u32 nr) |
| { |
| load_events(cpu_type); |
| |
| return find_event_any(nr); |
| } |
| |
| int op_check_events(int ctr, u32 nr, u32 um, op_cpu cpu_type) |
| { |
| int ret = OP_INVALID_EVENT; |
| size_t i; |
| u32 ctr_mask = 1 << ctr; |
| struct list_head * pos; |
| |
| load_events(cpu_type); |
| |
| list_for_each(pos, &events_list) { |
| struct op_event * event = list_entry(pos, struct op_event, event_next); |
| if (event->val != nr) |
| continue; |
| |
| ret = OP_OK_EVENT; |
| |
| if ((event->counter_mask & ctr_mask) == 0) |
| ret |= OP_INVALID_COUNTER; |
| |
| if (event->unit->unit_type_mask == utm_bitmask) { |
| for (i = 0; i < event->unit->num; ++i) |
| um &= ~(event->unit->um[i].value); |
| |
| if (um) |
| ret |= OP_INVALID_UM; |
| |
| } else { |
| for (i = 0; i < event->unit->num; ++i) { |
| if (event->unit->um[i].value == um) |
| break; |
| } |
| |
| if (i == event->unit->num) |
| ret |= OP_INVALID_UM; |
| |
| } |
| |
| if (ret == OP_OK_EVENT) |
| return ret; |
| } |
| |
| return ret; |
| } |
| |
| |
| void op_default_event(op_cpu cpu_type, struct op_default_event_descr * descr) |
| { |
| descr->name = ""; |
| descr->um = 0x0; |
| /* A fixed value of CPU cycles; this should ensure good |
| * granulity even on faster CPUs, though it will generate more |
| * interrupts. |
| */ |
| descr->count = 100000; |
| |
| switch (cpu_type) { |
| case CPU_PPRO: |
| case CPU_PII: |
| case CPU_PIII: |
| case CPU_P6_MOBILE: |
| case CPU_CORE: |
| case CPU_CORE_2: |
| case CPU_ATHLON: |
| case CPU_HAMMER: |
| case CPU_FAMILY10: |
| case CPU_ARCH_PERFMON: |
| case CPU_FAMILY11H: |
| case CPU_ATOM: |
| case CPU_CORE_I7: |
| case CPU_NEHALEM: |
| case CPU_WESTMERE: |
| case CPU_SANDYBRIDGE: |
| case CPU_MIPS_LOONGSON2: |
| case CPU_FAMILY12H: |
| case CPU_FAMILY14H: |
| case CPU_FAMILY15H: |
| descr->name = "CPU_CLK_UNHALTED"; |
| break; |
| |
| case CPU_RTC: |
| descr->name = "RTC_INTERRUPTS"; |
| descr->count = 1024; |
| break; |
| |
| case CPU_P4: |
| case CPU_P4_HT2: |
| descr->name = "GLOBAL_POWER_EVENTS"; |
| descr->um = 0x1; |
| break; |
| |
| case CPU_IA64: |
| case CPU_IA64_1: |
| case CPU_IA64_2: |
| descr->count = 1000000; |
| descr->name = "CPU_CYCLES"; |
| break; |
| |
| case CPU_AXP_EV4: |
| case CPU_AXP_EV5: |
| case CPU_AXP_PCA56: |
| case CPU_AXP_EV6: |
| case CPU_AXP_EV67: |
| descr->name = "CYCLES"; |
| break; |
| |
| // we could possibly use the CCNT |
| case CPU_ARM_XSCALE1: |
| case CPU_ARM_XSCALE2: |
| case CPU_ARM_MPCORE: |
| case CPU_ARM_V6: |
| case CPU_ARM_V7: |
| case CPU_ARM_V7_CA9: |
| case CPU_AVR32: |
| case CPU_ARM_SCORPION: |
| case CPU_ARM_SCORPIONMP: |
| descr->name = "CPU_CYCLES"; |
| break; |
| |
| case CPU_PPC64_PA6T: |
| case CPU_PPC64_970: |
| case CPU_PPC64_970MP: |
| case CPU_PPC_7450: |
| case CPU_PPC64_POWER4: |
| case CPU_PPC64_POWER5: |
| case CPU_PPC64_POWER6: |
| case CPU_PPC64_POWER5p: |
| case CPU_PPC64_POWER5pp: |
| case CPU_PPC64_CELL: |
| case CPU_PPC64_POWER7: |
| case CPU_PPC64_IBM_COMPAT_V1: |
| descr->name = "CYCLES"; |
| break; |
| |
| case CPU_MIPS_20K: |
| descr->name = "CYCLES"; |
| break; |
| |
| case CPU_MIPS_24K: |
| case CPU_MIPS_34K: |
| case CPU_MIPS_74K: |
| case CPU_MIPS_1004K: |
| descr->name = "INSTRUCTIONS"; |
| break; |
| |
| case CPU_MIPS_5K: |
| case CPU_MIPS_25K: |
| descr->name = "CYCLES"; |
| break; |
| |
| case CPU_MIPS_R10000: |
| case CPU_MIPS_R12000: |
| descr->name = "INSTRUCTIONS_GRADUATED"; |
| break; |
| |
| case CPU_MIPS_RM7000: |
| case CPU_MIPS_RM9000: |
| descr->name = "INSTRUCTIONS_ISSUED"; |
| break; |
| |
| case CPU_MIPS_SB1: |
| descr->name = "INSN_SURVIVED_STAGE7"; |
| break; |
| |
| case CPU_MIPS_VR5432: |
| case CPU_MIPS_VR5500: |
| descr->name = "INSTRUCTIONS_EXECUTED"; |
| break; |
| |
| case CPU_PPC_E500: |
| case CPU_PPC_E500_2: |
| case CPU_PPC_E300: |
| descr->name = "CPU_CLK"; |
| break; |
| |
| // don't use default, if someone add a cpu he wants a compiler |
| // warning if he forgets to handle it here. |
| case CPU_TIMER_INT: |
| case CPU_NO_GOOD: |
| case MAX_CPU_TYPE: |
| break; |
| } |
| } |
| |
| static void extra_check(struct op_event *e, u32 unit_mask) |
| { |
| unsigned i; |
| int found = 0; |
| |
| for (i = 0; i < e->unit->num; i++) |
| if (e->unit->um[i].value == unit_mask) |
| found++; |
| if (found > 1) { |
| fprintf(stderr, |
| "Named unit masks not allowed for events without 'extra:' values.\n" |
| "Please specify the numerical value for the unit mask. See 'opcontrol'" |
| " man page for more info.\n"); |
| exit(EXIT_FAILURE); |
| } |
| } |
| |
| static void another_extra_check(struct op_event *e, char *name, unsigned w) |
| { |
| int found; |
| unsigned i; |
| |
| if (!e->unit->um[w].extra) { |
| fprintf(stderr, |
| "Named unit mask (%s) not allowed for event without 'extra:' values.\n" |
| "Please specify the numerical value for the unit mask. See 'opcontrol'" |
| " man page for more info.\n", name); |
| exit(EXIT_FAILURE); |
| } |
| |
| found = 0; |
| for (i = 0; i < e->unit->num; i++) { |
| int len = strcspn(e->unit->um[i].desc, " \t"); |
| if (!strncmp(name, e->unit->um[i].desc, len) && |
| name[len] == '\0') |
| found++; |
| } |
| if (found > 1) { |
| fprintf(stderr, |
| "Unit mask name `%s' not unique. Sorry please use a numerical unit mask\n", name); |
| exit(EXIT_FAILURE); |
| } |
| } |
| |
| static void do_resolve_unit_mask(struct op_event *e, struct parsed_event *pe, |
| u32 *extra) |
| { |
| unsigned i; |
| int found; |
| |
| for (;;) { |
| if (pe->unit_mask_name == NULL) { |
| int fi = 0; |
| unsigned um = pe->unit_mask; |
| int had_unit_mask = pe->unit_mask_valid; |
| |
| found = 0; |
| for (i = 0; i < e->unit->num; i++) { |
| if (pe->unit_mask_valid && |
| e->unit->um[i].value == um) { |
| if (found++ == 0) |
| fi = i; |
| } |
| if (!pe->unit_mask_valid && |
| e->unit->um[i].value == e->unit->default_mask) { |
| pe->unit_mask_valid = 1; |
| pe->unit_mask = e->unit->default_mask; |
| break; |
| } |
| } |
| if (found > 1 && had_unit_mask) { |
| fprintf(stderr, |
| "Non unique numerical unit mask.\n" |
| "Please specify the unit mask using the first word of the description\n"); |
| exit(EXIT_FAILURE); |
| } |
| extra_check(e, pe->unit_mask); |
| if (i == e->unit->num) { |
| e = find_next_event(e); |
| if (e != NULL) |
| continue; |
| } else { |
| if (extra) |
| *extra = e->unit->um[i].extra; |
| } |
| return; |
| } |
| for (i = 0; i < e->unit->num; i++) { |
| int len = strcspn(e->unit->um[i].desc, " \t"); |
| if (!strncmp(pe->unit_mask_name, e->unit->um[i].desc, |
| len) && pe->unit_mask_name[len] == '\0') |
| break; |
| } |
| if (i == e->unit->num) { |
| e = find_next_event(e); |
| if (e != NULL) |
| continue; |
| fprintf(stderr, "Cannot find unit mask %s for %s\n", |
| pe->unit_mask_name, pe->name); |
| exit(EXIT_FAILURE); |
| } |
| another_extra_check(e, pe->unit_mask_name, i); |
| pe->unit_mask_valid = 1; |
| pe->unit_mask = e->unit->um[i].value; |
| if (extra) |
| *extra = e->unit->um[i].extra; |
| return; |
| } |
| } |
| |
| void op_resolve_unit_mask(struct parsed_event *pe, u32 *extra) |
| { |
| struct op_event *e; |
| |
| e = find_event_by_name(pe->name, 0, 0); |
| if (!e) { |
| fprintf(stderr, "Cannot find event %s\n", pe->name); |
| exit(EXIT_FAILURE); |
| } |
| return do_resolve_unit_mask(e, pe, extra); |
| } |