diff options
Diffstat (limited to 'tools')
36 files changed, 2434 insertions, 117 deletions
diff --git a/tools/Makefile b/tools/Makefile index 41067f30421..a9b02008443 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -15,6 +15,7 @@ help: @echo ' net - misc networking tools' @echo ' vm - misc vm tools' @echo ' x86_energy_perf_policy - Intel energy policy tool' + @echo ' tmon - thermal monitoring and tuning tool' @echo '' @echo 'You can do:' @echo ' $$ make -C tools/ <tool>_install' @@ -50,6 +51,9 @@ selftests: FORCE turbostat x86_energy_perf_policy: FORCE $(call descend,power/x86/$@) +tmon: FORCE + $(call descend,thermal/$@) + cpupower_install: $(call descend,power/$(@:_install=),install) @@ -62,9 +66,13 @@ selftests_install: turbostat_install x86_energy_perf_policy_install: $(call descend,power/x86/$(@:_install=),install) +tmon_install: + $(call descend,thermal/$(@:_install=),install) + install: cgroup_install cpupower_install firewire_install lguest_install \ perf_install selftests_install turbostat_install usb_install \ - virtio_install vm_install net_install x86_energy_perf_policy_install + virtio_install vm_install net_install x86_energy_perf_policy_install \ + tmon cpupower_clean: $(call descend,power/cpupower,clean) @@ -84,8 +92,11 @@ selftests_clean: turbostat_clean x86_energy_perf_policy_clean: $(call descend,power/x86/$(@:_clean=),clean) +tmon_clean: + $(call descend,thermal/tmon,clean) + clean: cgroup_clean cpupower_clean firewire_clean lguest_clean perf_clean \ selftests_clean turbostat_clean usb_clean virtio_clean \ - vm_clean net_clean x86_energy_perf_policy_clean + vm_clean net_clean x86_energy_perf_policy_clean tmon_clean .PHONY: FORCE diff --git a/tools/lib/lk/debugfs.c b/tools/lib/lk/debugfs.c index 099e7cd022e..7c434796235 100644 --- a/tools/lib/lk/debugfs.c +++ b/tools/lib/lk/debugfs.c @@ -5,7 +5,6 @@ #include <stdbool.h> #include <sys/vfs.h> #include <sys/mount.h> -#include <linux/magic.h> #include <linux/kernel.h> #include "debugfs.h" diff --git a/tools/perf/Makefile b/tools/perf/Makefile index 3a0ff7fb71b..64c043b7a43 100644 --- a/tools/perf/Makefile +++ b/tools/perf/Makefile @@ -770,6 +770,7 @@ check: $(OUTPUT)common-cmds.h install-bin: all $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(bindir_SQ)' $(INSTALL) $(OUTPUT)perf '$(DESTDIR_SQ)$(bindir_SQ)' + $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(perfexec_instdir_SQ)' $(INSTALL) $(OUTPUT)perf-archive -t '$(DESTDIR_SQ)$(perfexec_instdir_SQ)' ifndef NO_LIBPERL $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/scripts/perl/Perf-Trace-Util/lib/Perf/Trace' diff --git a/tools/perf/arch/x86/util/tsc.c b/tools/perf/arch/x86/util/tsc.c index 9570c2b0f83..b2519e49424 100644 --- a/tools/perf/arch/x86/util/tsc.c +++ b/tools/perf/arch/x86/util/tsc.c @@ -32,7 +32,7 @@ u64 tsc_to_perf_time(u64 cyc, struct perf_tsc_conversion *tc) int perf_read_tsc_conversion(const struct perf_event_mmap_page *pc, struct perf_tsc_conversion *tc) { - bool cap_usr_time_zero; + bool cap_user_time_zero; u32 seq; int i = 0; @@ -42,7 +42,7 @@ int perf_read_tsc_conversion(const struct perf_event_mmap_page *pc, tc->time_mult = pc->time_mult; tc->time_shift = pc->time_shift; tc->time_zero = pc->time_zero; - cap_usr_time_zero = pc->cap_usr_time_zero; + cap_user_time_zero = pc->cap_user_time_zero; rmb(); if (pc->lock == seq && !(seq & 1)) break; @@ -52,7 +52,7 @@ int perf_read_tsc_conversion(const struct perf_event_mmap_page *pc, } } - if (!cap_usr_time_zero) + if (!cap_user_time_zero) return -EOPNOTSUPP; return 0; diff --git a/tools/perf/builtin-inject.c b/tools/perf/builtin-inject.c index 423875c999b..afe377b2884 100644 --- a/tools/perf/builtin-inject.c +++ b/tools/perf/builtin-inject.c @@ -321,8 +321,6 @@ found: return perf_event__repipe(tool, event_sw, &sample_sw, machine); } -extern volatile int session_done; - static void sig_handler(int sig __maybe_unused) { session_done = 1; diff --git a/tools/perf/builtin-kmem.c b/tools/perf/builtin-kmem.c index c2dff9cb1f2..9b5f077fee5 100644 --- a/tools/perf/builtin-kmem.c +++ b/tools/perf/builtin-kmem.c @@ -101,7 +101,7 @@ static int setup_cpunode_map(void) dir1 = opendir(PATH_SYS_NODE); if (!dir1) - return -1; + return 0; while ((dent1 = readdir(dir1)) != NULL) { if (dent1->d_type != DT_DIR || diff --git a/tools/perf/builtin-report.c b/tools/perf/builtin-report.c index 8e50d8d7741..72eae7498c0 100644 --- a/tools/perf/builtin-report.c +++ b/tools/perf/builtin-report.c @@ -401,8 +401,6 @@ static int perf_report__setup_sample_type(struct perf_report *rep) return 0; } -extern volatile int session_done; - static void sig_handler(int sig __maybe_unused) { session_done = 1; @@ -568,6 +566,9 @@ static int __cmd_report(struct perf_report *rep) } } + if (session_done()) + return 0; + if (nr_samples == 0) { ui__error("The %s file has no samples!\n", session->filename); return 0; diff --git a/tools/perf/builtin-script.c b/tools/perf/builtin-script.c index 7f31a3ded1b..9c333ff3dfe 100644 --- a/tools/perf/builtin-script.c +++ b/tools/perf/builtin-script.c @@ -553,8 +553,6 @@ static struct perf_tool perf_script = { .ordering_requires_timestamps = true, }; -extern volatile int session_done; - static void sig_handler(int sig __maybe_unused) { session_done = 1; diff --git a/tools/perf/builtin-stat.c b/tools/perf/builtin-stat.c index f686d5ff594..5098f144b92 100644 --- a/tools/perf/builtin-stat.c +++ b/tools/perf/builtin-stat.c @@ -457,6 +457,7 @@ static int __run_perf_stat(int argc, const char **argv) perror("failed to prepare workload"); return -1; } + child_pid = evsel_list->workload.pid; } if (group) diff --git a/tools/perf/builtin-trace.c b/tools/perf/builtin-trace.c index f5aa6375e3e..71aa3e35406 100644 --- a/tools/perf/builtin-trace.c +++ b/tools/perf/builtin-trace.c @@ -16,6 +16,23 @@ #include <sys/mman.h> #include <linux/futex.h> +/* For older distros: */ +#ifndef MAP_STACK +# define MAP_STACK 0x20000 +#endif + +#ifndef MADV_HWPOISON +# define MADV_HWPOISON 100 +#endif + +#ifndef MADV_MERGEABLE +# define MADV_MERGEABLE 12 +#endif + +#ifndef MADV_UNMERGEABLE +# define MADV_UNMERGEABLE 13 +#endif + static size_t syscall_arg__scnprintf_hex(char *bf, size_t size, unsigned long arg, u8 arg_idx __maybe_unused, @@ -1038,6 +1055,7 @@ static int trace__replay(struct trace *trace) trace->tool.sample = trace__process_sample; trace->tool.mmap = perf_event__process_mmap; + trace->tool.mmap2 = perf_event__process_mmap2; trace->tool.comm = perf_event__process_comm; trace->tool.exit = perf_event__process_exit; trace->tool.fork = perf_event__process_fork; diff --git a/tools/perf/config/Makefile b/tools/perf/config/Makefile index 214e17e97e5..5f6f9b3271b 100644 --- a/tools/perf/config/Makefile +++ b/tools/perf/config/Makefile @@ -87,7 +87,7 @@ CFLAGS += -Wall CFLAGS += -Wextra CFLAGS += -std=gnu99 -EXTLIBS = -lelf -lpthread -lrt -lm +EXTLIBS = -lelf -lpthread -lrt -lm -ldl ifeq ($(call try-cc,$(SOURCE_HELLO),$(CFLAGS) -Werror -fstack-protector-all,-fstack-protector-all),y) CFLAGS += -fstack-protector-all @@ -180,6 +180,9 @@ FLAGS_LIBELF=$(CFLAGS) $(LDFLAGS) $(EXTLIBS) ifeq ($(call try-cc,$(SOURCE_ELF_MMAP),$(FLAGS_LIBELF),-DLIBELF_MMAP),y) CFLAGS += -DLIBELF_MMAP endif +ifeq ($(call try-cc,$(SOURCE_ELF_GETPHDRNUM),$(FLAGS_LIBELF),-DHAVE_ELF_GETPHDRNUM),y) + CFLAGS += -DHAVE_ELF_GETPHDRNUM +endif # include ARCH specific config -include $(src-perf)/arch/$(ARCH)/Makefile diff --git a/tools/perf/config/feature-tests.mak b/tools/perf/config/feature-tests.mak index 708fb8e9822..f79305739ec 100644 --- a/tools/perf/config/feature-tests.mak +++ b/tools/perf/config/feature-tests.mak @@ -61,6 +61,15 @@ int main(void) } endef +define SOURCE_ELF_GETPHDRNUM +#include <libelf.h> +int main(void) +{ + size_t dst; + return elf_getphdrnum(0, &dst); +} +endef + ifndef NO_SLANG define SOURCE_SLANG #include <slang.h> @@ -210,6 +219,7 @@ define SOURCE_LIBAUDIT int main(void) { + printf(\"error message: %s\", audit_errno_to_name(0)); return audit_open(); } endef diff --git a/tools/perf/util/annotate.c b/tools/perf/util/annotate.c index bfc5a27597d..7eae5488ece 100644 --- a/tools/perf/util/annotate.c +++ b/tools/perf/util/annotate.c @@ -809,7 +809,7 @@ static int symbol__parse_objdump_line(struct symbol *sym, struct map *map, end = map__rip_2objdump(map, sym->end); offset = line_ip - start; - if (offset < 0 || (u64)line_ip > end) + if ((u64)line_ip < start || (u64)line_ip > end) offset = -1; else parsed_line = tmp2 + 1; diff --git a/tools/perf/util/dwarf-aux.c b/tools/perf/util/dwarf-aux.c index 3e5f5430a28..7defd77105d 100644 --- a/tools/perf/util/dwarf-aux.c +++ b/tools/perf/util/dwarf-aux.c @@ -263,6 +263,21 @@ bool die_is_signed_type(Dwarf_Die *tp_die) } /** + * die_is_func_def - Ensure that this DIE is a subprogram and definition + * @dw_die: a DIE + * + * Ensure that this DIE is a subprogram and NOT a declaration. This + * returns true if @dw_die is a function definition. + **/ +bool die_is_func_def(Dwarf_Die *dw_die) +{ + Dwarf_Attribute attr; + + return (dwarf_tag(dw_die) == DW_TAG_subprogram && + dwarf_attr(dw_die, DW_AT_declaration, &attr) == NULL); +} + +/** * die_get_data_member_location - Get the data-member offset * @mb_die: a DIE of a member of a data structure * @offs: The offset of the member in the data structure @@ -392,6 +407,10 @@ static int __die_search_func_cb(Dwarf_Die *fn_die, void *data) { struct __addr_die_search_param *ad = data; + /* + * Since a declaration entry doesn't has given pc, this always returns + * function definition entry. + */ if (dwarf_tag(fn_die) == DW_TAG_subprogram && dwarf_haspc(fn_die, ad->addr)) { memcpy(ad->die_mem, fn_die, sizeof(Dwarf_Die)); @@ -407,7 +426,7 @@ static int __die_search_func_cb(Dwarf_Die *fn_die, void *data) * @die_mem: a buffer for result DIE * * Search a non-inlined function DIE which includes @addr. Stores the - * DIE to @die_mem and returns it if found. Returns NULl if failed. + * DIE to @die_mem and returns it if found. Returns NULL if failed. */ Dwarf_Die *die_find_realfunc(Dwarf_Die *cu_die, Dwarf_Addr addr, Dwarf_Die *die_mem) @@ -435,15 +454,32 @@ static int __die_find_inline_cb(Dwarf_Die *die_mem, void *data) } /** + * die_find_top_inlinefunc - Search the top inlined function at given address + * @sp_die: a subprogram DIE which including @addr + * @addr: target address + * @die_mem: a buffer for result DIE + * + * Search an inlined function DIE which includes @addr. Stores the + * DIE to @die_mem and returns it if found. Returns NULL if failed. + * Even if several inlined functions are expanded recursively, this + * doesn't trace it down, and returns the topmost one. + */ +Dwarf_Die *die_find_top_inlinefunc(Dwarf_Die *sp_die, Dwarf_Addr addr, + Dwarf_Die *die_mem) +{ + return die_find_child(sp_die, __die_find_inline_cb, &addr, die_mem); +} + +/** * die_find_inlinefunc - Search an inlined function at given address - * @cu_die: a CU DIE which including @addr + * @sp_die: a subprogram DIE which including @addr * @addr: target address * @die_mem: a buffer for result DIE * * Search an inlined function DIE which includes @addr. Stores the - * DIE to @die_mem and returns it if found. Returns NULl if failed. + * DIE to @die_mem and returns it if found. Returns NULL if failed. * If several inlined functions are expanded recursively, this trace - * it and returns deepest one. + * it down and returns deepest one. */ Dwarf_Die *die_find_inlinefunc(Dwarf_Die *sp_die, Dwarf_Addr addr, Dwarf_Die *die_mem) diff --git a/tools/perf/util/dwarf-aux.h b/tools/perf/util/dwarf-aux.h index 6ce1717784b..b4fe90c6cb2 100644 --- a/tools/perf/util/dwarf-aux.h +++ b/tools/perf/util/dwarf-aux.h @@ -38,6 +38,9 @@ extern int cu_find_lineinfo(Dwarf_Die *cudie, unsigned long addr, extern int cu_walk_functions_at(Dwarf_Die *cu_die, Dwarf_Addr addr, int (*callback)(Dwarf_Die *, void *), void *data); +/* Ensure that this DIE is a subprogram and definition (not declaration) */ +extern bool die_is_func_def(Dwarf_Die *dw_die); + /* Compare diename and tname */ extern bool die_compare_name(Dwarf_Die *dw_die, const char *tname); @@ -76,7 +79,11 @@ extern Dwarf_Die *die_find_child(Dwarf_Die *rt_die, extern Dwarf_Die *die_find_realfunc(Dwarf_Die *cu_die, Dwarf_Addr addr, Dwarf_Die *die_mem); -/* Search an inlined function including given address */ +/* Search the top inlined function including given address */ +extern Dwarf_Die *die_find_top_inlinefunc(Dwarf_Die *sp_die, Dwarf_Addr addr, + Dwarf_Die *die_mem); + +/* Search the deepest inlined function including given address */ extern Dwarf_Die *die_find_inlinefunc(Dwarf_Die *sp_die, Dwarf_Addr addr, Dwarf_Die *die_mem); diff --git a/tools/perf/util/event.c b/tools/perf/util/event.c index 9b393e7dca6..63df031fc9c 100644 --- a/tools/perf/util/event.c +++ b/tools/perf/util/event.c @@ -187,7 +187,7 @@ static int perf_event__synthesize_mmap_events(struct perf_tool *tool, return -1; } - event->header.type = PERF_RECORD_MMAP2; + event->header.type = PERF_RECORD_MMAP; /* * Just like the kernel, see __perf_event_mmap in kernel/perf_event.c */ @@ -198,7 +198,6 @@ static int perf_event__synthesize_mmap_events(struct perf_tool *tool, char prot[5]; char execname[PATH_MAX]; char anonstr[] = "//anon"; - unsigned int ino; size_t size; ssize_t n; @@ -209,13 +208,10 @@ static 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->mmap2.start, &event->mmap2.len, prot, - &event->mmap2.pgoff, &event->mmap2.maj, - &event->mmap2.min, - &ino, execname); - - event->mmap2.ino = (u64)ino; + n = sscanf(bf, "%"PRIx64"-%"PRIx64" %s %"PRIx64" %*x:%*x %*u %s\n", + &event->mmap.start, &event->mmap.len, prot, + &event->mmap.pgoff, + execname); if (n != 8) continue; @@ -227,15 +223,15 @@ static int perf_event__synthesize_mmap_events(struct perf_tool *tool, strcpy(execname, anonstr); size = strlen(execname) + 1; - memcpy(event->mmap2.filename, execname, size); + memcpy(event->mmap.filename, execname, size); size = PERF_ALIGN(size, sizeof(u64)); - 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; + 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; if (process(tool, event, &synth_sample, machine) != 0) { rc = -1; diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c index 0ce9febf1ba..9f1ef9bee2d 100644 --- a/tools/perf/util/evsel.c +++ b/tools/perf/util/evsel.c @@ -678,7 +678,6 @@ void perf_evsel__config(struct perf_evsel *evsel, attr->sample_type |= PERF_SAMPLE_WEIGHT; attr->mmap = track; - attr->mmap2 = track && !perf_missing_features.mmap2; attr->comm = track; /* diff --git a/tools/perf/util/header.c b/tools/perf/util/header.c index 26441d0e571..c3e5a3b817a 100644 --- a/tools/perf/util/header.c +++ b/tools/perf/util/header.c @@ -199,9 +199,11 @@ static int write_buildid(char *name, size_t name_len, u8 *build_id, return write_padded(fd, name, name_len + 1, len); } -static int __dsos__write_buildid_table(struct list_head *head, pid_t pid, - u16 misc, int fd) +static int __dsos__write_buildid_table(struct list_head *head, + struct machine *machine, + pid_t pid, u16 misc, int fd) { + char nm[PATH_MAX]; struct dso *pos; dsos__for_each_with_build_id(pos, head) { @@ -215,6 +217,10 @@ static int __dsos__write_buildid_table(struct list_head *head, pid_t pid, if (is_vdso_map(pos->short_name)) { name = (char *) VDSO__MAP_NAME; name_len = sizeof(VDSO__MAP_NAME) + 1; + } else if (dso__is_kcore(pos)) { + machine__mmap_name(machine, nm, sizeof(nm)); + name = nm; + name_len = strlen(nm) + 1; } else { name = pos->long_name; name_len = pos->long_name_len + 1; @@ -240,10 +246,10 @@ static int machine__write_buildid_table(struct machine *machine, int fd) umisc = PERF_RECORD_MISC_GUEST_USER; } - err = __dsos__write_buildid_table(&machine->kernel_dsos, machine->pid, - kmisc, fd); + err = __dsos__write_buildid_table(&machine->kernel_dsos, machine, + machine->pid, kmisc, fd); if (err == 0) - err = __dsos__write_buildid_table(&machine->user_dsos, + err = __dsos__write_buildid_table(&machine->user_dsos, machine, machine->pid, umisc, fd); return err; } @@ -375,23 +381,31 @@ out_free: return err; } -static int dso__cache_build_id(struct dso *dso, const char *debugdir) +static int dso__cache_build_id(struct dso *dso, struct machine *machine, + const char *debugdir) { bool is_kallsyms = dso->kernel && dso->long_name[0] != '/'; bool is_vdso = is_vdso_map(dso->short_name); + char *name = dso->long_name; + char nm[PATH_MAX]; - return build_id_cache__add_b(dso->build_id, sizeof(dso->build_id), - dso->long_name, debugdir, - is_kallsyms, is_vdso); + if (dso__is_kcore(dso)) { + is_kallsyms = true; + machine__mmap_name(machine, nm, sizeof(nm)); + name = nm; + } + return build_id_cache__add_b(dso->build_id, sizeof(dso->build_id), name, + debugdir, is_kallsyms, is_vdso); } -static int __dsos__cache_build_ids(struct list_head *head, const char *debugdir) +static int __dsos__cache_build_ids(struct list_head *head, + struct machine *machine, const char *debugdir) { struct dso *pos; int err = 0; dsos__for_each_with_build_id(pos, head) - if (dso__cache_build_id(pos, debugdir)) + if (dso__cache_build_id(pos, machine, debugdir)) err = -1; return err; @@ -399,8 +413,9 @@ static int __dsos__cache_build_ids(struct list_head *head, const char *debugdir) static int machine__cache_build_ids(struct machine *machine, const char *debugdir) { - int ret = __dsos__cache_build_ids(&machine->kernel_dsos, debugdir); - ret |= __dsos__cache_build_ids(&machine->user_dsos, debugdir); + int ret = __dsos__cache_build_ids(&machine->kernel_dsos, machine, + debugdir); + ret |= __dsos__cache_build_ids(&machine->user_dsos, machine, debugdir); return ret; } @@ -2753,6 +2768,18 @@ int perf_session__read_header(struct perf_session *session) if (perf_file_header__read(&f_header, header, fd) < 0) return -EINVAL; + /* + * Sanity check that perf.data was written cleanly; data size is + * initialized to 0 and updated only if the on_exit function is run. + * If data size is still 0 then the file contains only partial + * information. Just warn user and process it as much as it can. + */ + if (f_header.data.size == 0) { + pr_warning("WARNING: The %s file's data size field is 0 which is unexpected.\n" + "Was the 'perf record' command properly terminated?\n", + session->filename); + } + nr_attrs = f_header.attrs.size / f_header.attr_size; lseek(fd, f_header.attrs.offset, SEEK_SET); diff --git a/tools/perf/util/hist.c b/tools/perf/util/hist.c index 46a0d35a05e..9ff6cf3e9a9 100644 --- a/tools/perf/util/hist.c +++ b/tools/perf/util/hist.c @@ -611,6 +611,8 @@ void hists__collapse_resort(struct hists *hists) next = rb_first(root); while (next) { + if (session_done()) + break; n = rb_entry(next, struct hist_entry, rb_node_in); next = rb_next(&n->rb_node_in); diff --git a/tools/perf/util/machine.c b/tools/perf/util/machine.c index 933d14f287c..6188d2876a7 100644 --- a/tools/perf/util/machine.c +++ b/tools/perf/util/machine.c @@ -792,7 +792,7 @@ static int machine__create_modules(struct machine *machine) modules = path; } - if (symbol__restricted_filename(path, "/proc/modules")) + if (symbol__restricted_filename(modules, "/proc/modules")) return -1; file = fopen(modules, "r"); diff --git a/tools/perf/util/probe-finder.c b/tools/perf/util/probe-finder.c index be0329394d5..f0692737ebf 100644 --- a/tools/perf/util/probe-finder.c +++ b/tools/perf/util/probe-finder.c @@ -118,7 +118,6 @@ static const Dwfl_Callbacks offline_callbacks = { static int debuginfo__init_offline_dwarf(struct debuginfo *self, const char *path) { - Dwfl_Module *mod; int fd; fd = open(path, O_RDONLY); @@ -129,11 +128,11 @@ static int debuginfo__init_offline_dwarf(struct debuginfo *self, if (!self->dwfl) goto error; - mod = dwfl_report_offline(self->dwfl, "", "", fd); - if (!mod) + self->mod = dwfl_report_offline(self->dwfl, "", "", fd); + if (!self->mod) goto error; - self->dbg = dwfl_module_getdwarf(mod, &self->bias); + self->dbg = dwfl_module_getdwarf(self->mod, &self->bias); if (!self->dbg) goto error; @@ -676,37 +675,42 @@ static int find_variable(Dwarf_Die *sc_die, struct probe_finder *pf) } /* Convert subprogram DIE to trace point */ -static int convert_to_trace_point(Dwarf_Die *sp_die, Dwarf_Addr paddr, - bool retprobe, struct probe_trace_point *tp) +static int convert_to_trace_point(Dwarf_Die *sp_die, Dwfl_Module *mod, + Dwarf_Addr paddr, bool retprobe, + struct probe_trace_point *tp) { Dwarf_Addr eaddr, highaddr; - const char *name; - - /* Copy the name of probe point */ - name = dwarf_diename(sp_die); - if (name) { - if (dwarf_entrypc(sp_die, &eaddr) != 0) { - pr_warning("Failed to get entry address of %s\n", - dwarf_diename(sp_die)); - return -ENOENT; - } - if (dwarf_highpc(sp_die, &highaddr) != 0) { - pr_warning("Failed to get end address of %s\n", - dwarf_diename(sp_die)); - return -ENOENT; - } - if (paddr > highaddr) { - pr_warning("Offset specified is greater than size of %s\n", - dwarf_diename(sp_die)); - return -EINVAL; - } - tp->symbol = strdup(name); - if (tp->symbol == NULL) - return -ENOMEM; - tp->offset = (unsigned long)(paddr - eaddr); - } else - /* This function has no name. */ - tp->offset = (unsigned long)paddr; + GElf_Sym sym; + const char *symbol; + + /* Verify the address is correct */ + if (dwarf_entrypc(sp_die, &eaddr) != 0) { + pr_warning("Failed to get entry address of %s\n", + dwarf_diename(sp_die)); + return -ENOENT; + } + if (dwarf_highpc(sp_die, &highaddr) != 0) { + pr_warning("Failed to get end address of %s\n", + dwarf_diename(sp_die)); + return -ENOENT; + } + if (paddr > highaddr) { + pr_warning("Offset specified is greater than size of %s\n", + dwarf_diename(sp_die)); + return -EINVAL; + } + + /* Get an appropriate symbol from symtab */ + symbol = dwfl_module_addrsym(mod, paddr, &sym, NULL); + if (!symbol) { + pr_warning("Failed to find symbol at 0x%lx\n", + (unsigned long)paddr); + return -ENOENT; + } + tp->offset = (unsigned long)(paddr - sym.st_value); + tp->symbol = strdup(symbol); + if (!tp->symbol) + return -ENOMEM; /* Return probe must be on the head of a subprogram */ if (retprobe) { @@ -734,7 +738,7 @@ static int call_probe_finder(Dwarf_Die *sc_die, struct probe_finder *pf) } /* If not a real subprogram, find a real one */ - if (dwarf_tag(sc_die) != DW_TAG_subprogram) { + if (!die_is_func_def(sc_die)) { if (!die_find_realfunc(&pf->cu_die, pf->addr, &pf->sp_die)) { pr_warning("Failed to find probe point in any " "functions.\n"); @@ -980,12 +984,10 @@ static int probe_point_search_cb(Dwarf_Die *sp_die, void *data) struct dwarf_callback_param *param = data; struct probe_finder *pf = param->data; struct perf_probe_point *pp = &pf->pev->point; - Dwarf_Attribute attr; /* Check tag and diename */ - if (dwarf_tag(sp_die) != DW_TAG_subprogram || - !die_compare_name(sp_die, pp->function) || - dwarf_attr(sp_die, DW_AT_declaration, &attr)) + if (!die_is_func_def(sp_die) || + !die_compare_name(sp_die, pp->function)) return DWARF_CB_OK; /* Check declared file */ @@ -1151,7 +1153,7 @@ static int add_probe_trace_event(Dwarf_Die *sc_die, struct probe_finder *pf) tev = &tf->tevs[tf->ntevs++]; /* Trace point should be converted from subprogram DIE */ - ret = convert_to_trace_point(&pf->sp_die, pf->addr, + ret = convert_to_trace_point(&pf->sp_die, tf->mod, pf->addr, pf->pev->point.retprobe, &tev->point); if (ret < 0) return ret; @@ -1183,7 +1185,7 @@ int debuginfo__find_trace_events(struct debuginfo *self, { struct trace_event_finder tf = { .pf = {.pev = pev, .callback = add_probe_trace_event}, - .max_tevs = max_tevs}; + .mod = self->mod, .max_tevs = max_tevs}; int ret; /* Allocate result tevs array */ @@ -1252,7 +1254,7 @@ static int add_available_vars(Dwarf_Die *sc_die, struct probe_finder *pf) vl = &af->vls[af->nvls++]; /* Trace point should be converted from subprogram DIE */ - ret = convert_to_trace_point(&pf->sp_die, pf->addr, + ret = convert_to_trace_point(&pf->sp_die, af->mod, pf->addr, pf->pev->point.retprobe, &vl->point); if (ret < 0) return ret; @@ -1291,6 +1293,7 @@ int debuginfo__find_available_vars_at(struct debuginfo *self, { struct available_var_finder af = { .pf = {.pev = pev, .callback = add_available_vars}, + .mod = self->mod, .max_vls = max_vls, .externs = externs}; int ret; @@ -1324,8 +1327,8 @@ int debuginfo__find_probe_point(struct debuginfo *self, unsigned long addr, struct perf_probe_point *ppt) { Dwarf_Die cudie, spdie, indie; - Dwarf_Addr _addr, baseaddr; - const char *fname = NULL, *func = NULL, *tmp; + Dwarf_Addr _addr = 0, baseaddr = 0; + const char *fname = NULL, *func = NULL, *basefunc = NULL, *tmp; int baseline = 0, lineno = 0, ret = 0; /* Adjust address with bias */ @@ -1346,27 +1349,36 @@ int debuginfo__find_probe_point(struct debuginfo *self, unsigned long addr, /* Find a corresponding function (name, baseline and baseaddr) */ if (die_find_realfunc(&cudie, (Dwarf_Addr)addr, &spdie)) { /* Get function entry information */ - tmp = dwarf_diename(&spdie); - if (!tmp || + func = basefunc = dwarf_diename(&spdie); + if (!func || dwarf_entrypc(&spdie, &baseaddr) != 0 || - dwarf_decl_line(&spdie, &baseline) != 0) + dwarf_decl_line(&spdie, &baseline) != 0) { + lineno = 0; goto post; - func = tmp; + } - if (addr == (unsigned long)baseaddr) + fname = dwarf_decl_file(&spdie); + if (addr == (unsigned long)baseaddr) { /* Function entry - Relative line number is 0 */ lineno = baseline; - else if (die_find_inlinefunc(&spdie, (Dwarf_Addr)addr, - &indie)) { + goto post; + } + + /* Track down the inline functions step by step */ + while (die_find_top_inlinefunc(&spdie, (Dwarf_Addr)addr, + &indie)) { + /* There is an inline function */ if (dwarf_entrypc(&indie, &_addr) == 0 && - _addr == addr) + _addr == addr) { /* * addr is at an inline function entry. * In this case, lineno should be the call-site - * line number. + * line number. (overwrite lineinfo) */ lineno = die_get_call_lineno(&indie); - else { + fname = die_get_call_file(&indie); + break; + } else { /* * addr is in an inline function body. * Since lineno points one of the lines @@ -1374,19 +1386,27 @@ int debuginfo__find_probe_point(struct debuginfo *self, unsigned long addr, * be the entry line of the inline function. */ tmp = dwarf_diename(&indie); - if (tmp && - dwarf_decl_line(&spdie, &baseline) == 0) - func = tmp; + if (!tmp || + dwarf_decl_line(&indie, &baseline) != 0) + break; + func = tmp; + spdie = indie; } } + /* Verify the lineno and baseline are in a same file */ + tmp = dwarf_decl_file(&spdie); + if (!tmp || strcmp(tmp, fname) != 0) + lineno = 0; } post: /* Make a relative line number or an offset */ if (lineno) ppt->line = lineno - baseline; - else if (func) + else if (basefunc) { ppt->offset = addr - (unsigned long)baseaddr; + func = basefunc; + } /* Duplicate strings */ if (func) { @@ -1474,7 +1494,7 @@ static int line_range_inline_cb(Dwarf_Die *in_die, void *data) return 0; } -/* Search function from function name */ +/* Search function definition from function name */ static int line_range_search_cb(Dwarf_Die *sp_die, void *data) { struct dwarf_callback_param *param = data; @@ -1485,7 +1505,7 @@ static int line_range_search_cb(Dwarf_Die *sp_die, void *data) if (lr->file && strtailcmp(lr->file, dwarf_decl_file(sp_die))) return DWARF_CB_OK; - if (dwarf_tag(sp_die) == DW_TAG_subprogram && + if (die_is_func_def(sp_die) && die_compare_name(sp_die, lr->function)) { lf->fname = dwarf_decl_file(sp_die); dwarf_decl_line(sp_die, &lr->offset); diff --git a/tools/perf/util/probe-finder.h b/tools/perf/util/probe-finder.h index 17e94d0c36f..3b7d6301896 100644 --- a/tools/perf/util/probe-finder.h +++ b/tools/perf/util/probe-finder.h @@ -23,6 +23,7 @@ static inline int is_c_varname(const char *name) /* debug information structure */ struct debuginfo { Dwarf *dbg; + Dwfl_Module *mod; Dwfl *dwfl; Dwarf_Addr bias; }; @@ -77,6 +78,7 @@ struct probe_finder { struct trace_event_finder { struct probe_finder pf; + Dwfl_Module *mod; /* For solving symbols */ struct probe_trace_event *tevs; /* Found trace events */ int ntevs; /* Number of trace events */ int max_tevs; /* Max number of trace events */ @@ -84,6 +86,7 @@ struct trace_event_finder { struct available_var_finder { struct probe_finder pf; + Dwfl_Module *mod; /* For solving symbols */ struct variable_list *vls; /* Found variable lists */ int nvls; /* Number of variable lists */ int max_vls; /* Max no. of variable lists */ diff --git a/tools/perf/util/scripting-engines/trace-event-perl.c b/tools/perf/util/scripting-engines/trace-event-perl.c index a85e4ae5f3a..c0c9795c4f0 100644 --- a/tools/perf/util/scripting-engines/trace-event-perl.c +++ b/tools/perf/util/scripting-engines/trace-event-perl.c @@ -282,7 +282,7 @@ static void perl_process_tracepoint(union perf_event *perf_event __maybe_unused, event = find_cache_event(evsel); if (!event) - die("ug! no event found for type %" PRIu64, evsel->attr.config); + die("ug! no event found for type %" PRIu64, (u64)evsel->attr.config); pid = raw_field_value(event, "common_pid", data); diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c index 51f5edf2a6d..568b750c01f 100644 --- a/tools/perf/util/session.c +++ b/tools/perf/util/session.c @@ -256,6 +256,8 @@ void perf_tool__fill_defaults(struct perf_tool *tool) tool->sample = process_event_sample_stub; if (tool->mmap == NULL) tool->mmap = process_event_stub; + if (tool->mmap2 == NULL) + tool->mmap2 = process_event_stub; if (tool->comm == NULL) tool->comm = process_event_stub; if (tool->fork == NULL) @@ -531,6 +533,9 @@ static int flush_sample_queue(struct perf_session *s, return 0; list_for_each_entry_safe(iter, tmp, head, list) { + if (session_done()) + return 0; + if (iter->timestamp > limit) break; @@ -1160,7 +1165,6 @@ static void perf_session__warn_about_errors(const struct perf_session *session, } } -#define session_done() (*(volatile int *)(&session_done)) volatile int session_done; static int __perf_session__process_pipe_events(struct perf_session *self, @@ -1308,7 +1312,7 @@ int __perf_session__process_events(struct perf_session *session, file_offset = page_offset; head = data_offset - page_offset; - if (data_offset + data_size < file_size) + if (data_size && (data_offset + data_size < file_size)) file_size = data_offset + data_size; progress_next = file_size / 16; @@ -1372,10 +1376,13 @@ more: "Processing events..."); } + err = 0; + if (session_done()) + goto out_err; + if (file_pos < file_size) goto more; - err = 0; /* do the final flush for ordered samples */ session->ordered_samples.next_flush = ULLONG_MAX; err = flush_sample_queue(session, tool); diff --git a/tools/perf/util/session.h b/tools/perf/util/session.h index 3aa75fb2225..04bf7373a7e 100644 --- a/tools/perf/util/session.h +++ b/tools/perf/util/session.h @@ -124,4 +124,8 @@ int __perf_session__set_tracepoints_handlers(struct perf_session *session, #define perf_session__set_tracepoints_handlers(session, array) \ __perf_session__set_tracepoints_handlers(session, array, ARRAY_SIZE(array)) + +extern volatile int session_done; + +#define session_done() (*(volatile int *)(&session_done)) #endif /* __PERF_SESSION_H */ diff --git a/tools/perf/util/symbol-elf.c b/tools/perf/util/symbol-elf.c index a7b9ab55738..a9c829be521 100644 --- a/tools/perf/util/symbol-elf.c +++ b/tools/perf/util/symbol-elf.c @@ -8,6 +8,22 @@ #include "symbol.h" #include "debug.h" +#ifndef HAVE_ELF_GETPHDRNUM +static int elf_getphdrnum(Elf *elf, size_t *dst) +{ + GElf_Ehdr gehdr; + GElf_Ehdr *ehdr; + + ehdr = gelf_getehdr(elf, &gehdr); + if (!ehdr) + return -1; + + *dst = ehdr->e_phnum; + + return 0; +} +#endif + #ifndef NT_GNU_BUILD_ID #define NT_GNU_BUILD_ID 3 #endif diff --git a/tools/perf/util/trace-event-parse.c b/tools/perf/util/trace-event-parse.c index fe7a27d67d2..e9e1c03f927 100644 --- a/tools/perf/util/trace-event-parse.c +++ b/tools/perf/util/trace-event-parse.c @@ -186,7 +186,7 @@ void parse_proc_kallsyms(struct pevent *pevent, char *next = NULL; char *addr_str; char *mod; - char *fmt; + char *fmt = NULL; line = strtok_r(file, "\n", &next); while (line) { diff --git a/tools/testing/selftests/timers/posix_timers.c b/tools/testing/selftests/timers/posix_timers.c index 4fa655d68a8..41bd85559d4 100644 --- a/tools/testing/selftests/timers/posix_timers.c +++ b/tools/testing/selftests/timers/posix_timers.c @@ -151,7 +151,7 @@ static int check_timer_create(int which) fflush(stdout); done = 0; - timer_create(which, NULL, &id); + err = timer_create(which, NULL, &id); if (err < 0) { perror("Can't create timer\n"); return -1; diff --git a/tools/thermal/tmon/Makefile b/tools/thermal/tmon/Makefile new file mode 100644 index 00000000000..447321104ec --- /dev/null +++ b/tools/thermal/tmon/Makefile @@ -0,0 +1,47 @@ +VERSION = 1.0 + +BINDIR=usr/bin +WARNFLAGS=-Wall -Wshadow -W -Wformat -Wimplicit-function-declaration -Wimplicit-int +CFLAGS= -O1 ${WARNFLAGS} -fstack-protector +CC=gcc + +CFLAGS+=-D VERSION=\"$(VERSION)\" +LDFLAGS+= +TARGET=tmon + +INSTALL_PROGRAM=install -m 755 -p +DEL_FILE=rm -f + +INSTALL_CONFIGFILE=install -m 644 -p +CONFIG_FILE= +CONFIG_PATH= + + +OBJS = tmon.o tui.o sysfs.o pid.o +OBJS += + +tmon: $(OBJS) Makefile tmon.h + $(CC) ${CFLAGS} $(LDFLAGS) $(OBJS) -o $(TARGET) -lm -lpanel -lncursesw -lpthread + +valgrind: tmon + sudo valgrind -v --track-origins=yes --tool=memcheck --leak-check=yes --show-reachable=yes --num-callers=20 --track-fds=yes ./$(TARGET) 1> /dev/null + +install: + - mkdir -p $(INSTALL_ROOT)/$(BINDIR) + - $(INSTALL_PROGRAM) "$(TARGET)" "$(INSTALL_ROOT)/$(BINDIR)/$(TARGET)" + - mkdir -p $(INSTALL_ROOT)/$(CONFIG_PATH) + - $(INSTALL_CONFIGFILE) "$(CONFIG_FILE)" "$(INSTALL_ROOT)/$(CONFIG_PATH)" + +uninstall: + $(DEL_FILE) "$(INSTALL_ROOT)/$(BINDIR)/$(TARGET)" + $(CONFIG_FILE) "$(CONFIG_PATH)" + + +clean: + find . -name "*.o" | xargs $(DEL_FILE) + rm -f $(TARGET) + +dist: + git tag v$(VERSION) + git archive --format=tar --prefix="$(TARGET)-$(VERSION)/" v$(VERSION) | \ + gzip > $(TARGET)-$(VERSION).tar.gz diff --git a/tools/thermal/tmon/README b/tools/thermal/tmon/README new file mode 100644 index 00000000000..457949897a8 --- /dev/null +++ b/tools/thermal/tmon/README @@ -0,0 +1,50 @@ +TMON - A Monitoring and Testing Tool for Linux kernel thermal subsystem + +Why TMON? +========== +Increasingly, Linux is running on thermally constrained devices. The simple +thermal relationship between processor and fan has become past for modern +computers. + +As hardware vendors cope with the thermal constraints on their products, more +and more sensors are added, new cooling capabilities are introduced. The +complexity of the thermal relationship can grow exponentially among cooling +devices, zones, sensors, and trip points. They can also change dynamically. + +To expose such relationship to the userspace, Linux generic thermal layer +introduced sysfs entry at /sys/class/thermal with a matrix of symbolic +links, trip point bindings, and device instances. To traverse such +matrix by hand is not a trivial task. Testing is also difficult in that +thermal conditions are often exception cases that hard to reach in +normal operations. + +TMON is conceived as a tool to help visualize, tune, and test the +complex thermal subsystem. + +Files +===== + tmon.c : main function for set up and configurations. + tui.c : handles ncurses based user interface + sysfs.c : access to the generic thermal sysfs + pid.c : a proportional-integral-derivative (PID) controller + that can be used for thermal relationship training. + +Requirements +============ +Depends on ncurses + +Build +========= +$ make +$ sudo ./tmon -h +Usage: tmon [OPTION...] + -c, --control cooling device in control + -d, --daemon run as daemon, no TUI + -l, --log log data to /var/tmp/tmon.log + -h, --help show this help message + -t, --time-interval set time interval for sampling + -v, --version show version + -g, --debug debug message in syslog + +1. For monitoring only: +$ sudo ./tmon diff --git a/tools/thermal/tmon/pid.c b/tools/thermal/tmon/pid.c new file mode 100644 index 00000000000..fd7e9e9d6f4 --- /dev/null +++ b/tools/thermal/tmon/pid.c @@ -0,0 +1,131 @@ +/* + * pid.c PID controller for testing cooling devices + * + * + * + * Copyright (C) 2012 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 or later as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Author Name Jacob Pan <jacob.jun.pan@linux.intel.com> + * + */ + +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <sys/types.h> +#include <dirent.h> +#include <libintl.h> +#include <ctype.h> +#include <assert.h> +#include <time.h> +#include <limits.h> +#include <math.h> +#include <sys/stat.h> +#include <syslog.h> + +#include "tmon.h" + +/************************************************************************** + * PID (Proportional-Integral-Derivative) controller is commonly used in + * linear control system, consider the the process. + * G(s) = U(s)/E(s) + * kp = proportional gain + * ki = integral gain + * kd = derivative gain + * Ts + * We use type C Alan Bradley equation which takes set point off the + * output dependency in P and D term. + * + * y[k] = y[k-1] - kp*(x[k] - x[k-1]) + Ki*Ts*e[k] - Kd*(x[k] + * - 2*x[k-1]+x[k-2])/Ts + * + * + ***********************************************************************/ +struct pid_params p_param; +/* cached data from previous loop */ +static double xk_1, xk_2; /* input temperature x[k-#] */ + +/* + * TODO: make PID parameters tuned automatically, + * 1. use CPU burn to produce open loop unit step response + * 2. calculate PID based on Ziegler-Nichols rule + * + * add a flag for tuning PID + */ +int init_thermal_controller(void) +{ + int ret = 0; + + /* init pid params */ + p_param.ts = ticktime; + /* TODO: get it from TUI tuning tab */ + p_param.kp = .36; + p_param.ki = 5.0; + p_param.kd = 0.19; + + p_param.t_target = target_temp_user; + + return ret; +} + +void controller_reset(void) +{ + /* TODO: relax control data when not over thermal limit */ + syslog(LOG_DEBUG, "TC inactive, relax p-state\n"); + p_param.y_k = 0.0; + xk_1 = 0.0; + xk_2 = 0.0; + set_ctrl_state(0); +} + +/* To be called at time interval Ts. Type C PID controller. + * y[k] = y[k-1] - kp*(x[k] - x[k-1]) + Ki*Ts*e[k] - Kd*(x[k] + * - 2*x[k-1]+x[k-2])/Ts + * TODO: add low pass filter for D term + */ +#define GUARD_BAND (2) +void controller_handler(const double xk, double *yk) +{ + double ek; + double p_term, i_term, d_term; + + ek = p_param.t_target - xk; /* error */ + if (ek >= 3.0) { + syslog(LOG_DEBUG, "PID: %3.1f Below set point %3.1f, stop\n", + xk, p_param.t_target); + controller_reset(); + *yk = 0.0; + return; + } + /* compute intermediate PID terms */ + p_term = -p_param.kp * (xk - xk_1); + i_term = p_param.kp * p_param.ki * p_param.ts * ek; + d_term = -p_param.kp * p_param.kd * (xk - 2 * xk_1 + xk_2) / p_param.ts; + /* compute output */ + *yk += p_term + i_term + d_term; + /* update sample data */ + xk_1 = xk; + xk_2 = xk_1; + + /* clamp output adjustment range */ + if (*yk < -LIMIT_HIGH) + *yk = -LIMIT_HIGH; + else if (*yk > -LIMIT_LOW) + *yk = -LIMIT_LOW; + + p_param.y_k = *yk; + + set_ctrl_state(lround(fabs(p_param.y_k))); + +} diff --git a/tools/thermal/tmon/sysfs.c b/tools/thermal/tmon/sysfs.c new file mode 100644 index 00000000000..dfe454855cd --- /dev/null +++ b/tools/thermal/tmon/sysfs.c @@ -0,0 +1,596 @@ +/* + * sysfs.c sysfs ABI access functions for TMON program + * + * Copyright (C) 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 or later as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Author: Jacob Pan <jacob.jun.pan@linux.intel.com> + * + */ +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <dirent.h> +#include <libintl.h> +#include <ctype.h> +#include <time.h> +#include <syslog.h> +#include <sys/time.h> +#include <errno.h> + +#include "tmon.h" + +struct tmon_platform_data ptdata; +const char *trip_type_name[] = { + "critical", + "hot", + "passive", + "active", +}; + +int sysfs_set_ulong(char *path, char *filename, unsigned long val) +{ + FILE *fd; + int ret = -1; + char filepath[256]; + + snprintf(filepath, 256, "%s/%s", path, filename); + + fd = fopen(filepath, "w"); + if (!fd) { + syslog(LOG_ERR, "Err: open %s: %s\n", __func__, filepath); + return ret; + } + ret = fprintf(fd, "%lu", val); + fclose(fd); + + return 0; +} + +/* history of thermal data, used for control algo */ +#define NR_THERMAL_RECORDS 3 +struct thermal_data_record trec[NR_THERMAL_RECORDS]; +int cur_thermal_record; /* index to the trec array */ + +static int sysfs_get_ulong(char *path, char *filename, unsigned long *p_ulong) +{ + FILE *fd; + int ret = -1; + char filepath[256]; + + snprintf(filepath, 256, "%s/%s", path, filename); + + fd = fopen(filepath, "r"); + if (!fd) { + syslog(LOG_ERR, "Err: open %s: %s\n", __func__, filepath); + return ret; + } + ret = fscanf(fd, "%lu", p_ulong); + fclose(fd); + + return 0; +} + +static int sysfs_get_string(char *path, char *filename, char *str) +{ + FILE *fd; + int ret = -1; + char filepath[256]; + + snprintf(filepath, 256, "%s/%s", path, filename); + + fd = fopen(filepath, "r"); + if (!fd) { + syslog(LOG_ERR, "Err: open %s: %s\n", __func__, filepath); + return ret; + } + ret = fscanf(fd, "%256s", str); + fclose(fd); + + return ret; +} + +/* get states of the cooling device instance */ +static int probe_cdev(struct cdev_info *cdi, char *path) +{ + sysfs_get_string(path, "type", cdi->type); + sysfs_get_ulong(path, "max_state", &cdi->max_state); + sysfs_get_ulong(path, "cur_state", &cdi->cur_state); + + syslog(LOG_INFO, "%s: %s: type %s, max %lu, curr %lu inst %d\n", + __func__, path, + cdi->type, cdi->max_state, cdi->cur_state, cdi->instance); + + return 0; +} + +static int str_to_trip_type(char *name) +{ + int i; + + for (i = 0; i < NR_THERMAL_TRIP_TYPE; i++) { + if (!strcmp(name, trip_type_name[i])) + return i; + } + + return -ENOENT; +} + +/* scan and fill in trip point info for a thermal zone and trip point id */ +static int get_trip_point_data(char *tz_path, int tzid, int tpid) +{ + char filename[256]; + char temp_str[256]; + int trip_type; + + if (tpid >= MAX_NR_TRIP) + return -EINVAL; + /* check trip point type */ + snprintf(filename, sizeof(filename), "trip_point_%d_type", tpid); + sysfs_get_string(tz_path, filename, temp_str); + trip_type = str_to_trip_type(temp_str); + if (trip_type < 0) { + syslog(LOG_ERR, "%s:%s no matching type\n", __func__, temp_str); + return -ENOENT; + } + ptdata.tzi[tzid].tp[tpid].type = trip_type; + syslog(LOG_INFO, "%s:tz:%d tp:%d:type:%s type id %d\n", __func__, tzid, + tpid, temp_str, trip_type); + + /* TODO: check attribute */ + + return 0; +} + +/* return instance id for file format such as trip_point_4_temp */ +static int get_instance_id(char *name, int pos, int skip) +{ + char *ch; + int i = 0; + + ch = strtok(name, "_"); + while (ch != NULL) { + ++i; + syslog(LOG_INFO, "%s:%s:%s:%d", __func__, name, ch, i); + ch = strtok(NULL, "_"); + if (pos == i) + return atol(ch + skip); + } + + return -1; +} + +/* Find trip point info of a thermal zone */ +static int find_tzone_tp(char *tz_name, char *d_name, struct tz_info *tzi, + int tz_id) +{ + int tp_id; + unsigned long temp_ulong; + + if (strstr(d_name, "trip_point") && + strstr(d_name, "temp")) { + /* check if trip point temp is non-zero + * ignore 0/invalid trip points + */ + sysfs_get_ulong(tz_name, d_name, &temp_ulong); + if (temp_ulong < MAX_TEMP_KC) { + tzi->nr_trip_pts++; + /* found a valid trip point */ + tp_id = get_instance_id(d_name, 2, 0); + syslog(LOG_DEBUG, "tzone %s trip %d temp %lu tpnode %s", + tz_name, tp_id, temp_ulong, d_name); + if (tp_id < 0 || tp_id >= MAX_NR_TRIP) { + syslog(LOG_ERR, "Failed to find TP inst %s\n", + d_name); + return -1; + } + get_trip_point_data(tz_name, tz_id, tp_id); + tzi->tp[tp_id].temp = temp_ulong; + } + } + + return 0; +} + +/* check cooling devices for binding info. */ +static int find_tzone_cdev(struct dirent *nl, char *tz_name, + struct tz_info *tzi, int tz_id, int cid) +{ + unsigned long trip_instance = 0; + char cdev_name_linked[256]; + char cdev_name[256]; + char cdev_trip_name[256]; + int cdev_id; + + if (nl->d_type == DT_LNK) { + syslog(LOG_DEBUG, "TZ%d: cdev: %s cid %d\n", tz_id, nl->d_name, + cid); + tzi->nr_cdev++; + if (tzi->nr_cdev > ptdata.nr_cooling_dev) { + syslog(LOG_ERR, "Err: Too many cdev? %d\n", + tzi->nr_cdev); + return -EINVAL; + } + /* find the link to real cooling device record binding */ + snprintf(cdev_name, 256, "%s/%s", tz_name, nl->d_name); + memset(cdev_name_linked, 0, sizeof(cdev_name_linked)); + if (readlink(cdev_name, cdev_name_linked, + sizeof(cdev_name_linked) - 1) != -1) { + cdev_id = get_instance_id(cdev_name_linked, 1, + sizeof("device") - 1); + syslog(LOG_DEBUG, "cdev %s linked to %s : %d\n", + cdev_name, cdev_name_linked, cdev_id); + tzi->cdev_binding |= (1 << cdev_id); + + /* find the trip point in which the cdev is binded to + * in this tzone + */ + snprintf(cdev_trip_name, 256, "%s%s", nl->d_name, + "_trip_point"); + sysfs_get_ulong(tz_name, cdev_trip_name, + &trip_instance); + /* validate trip point range, e.g. trip could return -1 + * when passive is enabled + */ + if (trip_instance > MAX_NR_TRIP) + trip_instance = 0; + tzi->trip_binding[cdev_id] |= 1 << trip_instance; + syslog(LOG_DEBUG, "cdev %s -> trip:%lu: 0x%lx %d\n", + cdev_name, trip_instance, + tzi->trip_binding[cdev_id], + cdev_id); + + + } + return 0; + } + + return -ENODEV; +} + + + +/***************************************************************************** + * Before calling scan_tzones, thermal sysfs must be probed to determine + * the number of thermal zones and cooling devices. + * We loop through each thermal zone and fill in tz_info struct, i.e. + * ptdata.tzi[] +root@jacob-chiefriver:~# tree -d /sys/class/thermal/thermal_zone0 +/sys/class/thermal/thermal_zone0 +|-- cdev0 -> ../cooling_device4 +|-- cdev1 -> ../cooling_device3 +|-- cdev10 -> ../cooling_device7 +|-- cdev11 -> ../cooling_device6 +|-- cdev12 -> ../cooling_device5 +|-- cdev2 -> ../cooling_device2 +|-- cdev3 -> ../cooling_device1 +|-- cdev4 -> ../cooling_device0 +|-- cdev5 -> ../cooling_device12 +|-- cdev6 -> ../cooling_device11 +|-- cdev7 -> ../cooling_device10 +|-- cdev8 -> ../cooling_device9 +|-- cdev9 -> ../cooling_device8 +|-- device -> ../../../LNXSYSTM:00/device:62/LNXTHERM:00 +|-- power +`-- subsystem -> ../../../../class/thermal +*****************************************************************************/ +static int scan_tzones(void) +{ + DIR *dir; + struct dirent **namelist; + char tz_name[256]; + int i, j, n, k = 0; + + if (!ptdata.nr_tz_sensor) + return -1; + + for (i = 0; i <= ptdata.max_tz_instance; i++) { + memset(tz_name, 0, sizeof(tz_name)); + snprintf(tz_name, 256, "%s/%s%d", THERMAL_SYSFS, TZONE, i); + + dir = opendir(tz_name); + if (!dir) { + syslog(LOG_INFO, "Thermal zone %s skipped\n", tz_name); + continue; + } + /* keep track of valid tzones */ + n = scandir(tz_name, &namelist, 0, alphasort); + if (n < 0) + syslog(LOG_ERR, "scandir failed in %s", tz_name); + else { + sysfs_get_string(tz_name, "type", ptdata.tzi[k].type); + ptdata.tzi[k].instance = i; + /* detect trip points and cdev attached to this tzone */ + j = 0; /* index for cdev */ + ptdata.tzi[k].nr_cdev = 0; + ptdata.tzi[k].nr_trip_pts = 0; + while (n--) { + char *temp_str; + + if (find_tzone_tp(tz_name, namelist[n]->d_name, + &ptdata.tzi[k], k)) + break; + temp_str = strstr(namelist[n]->d_name, "cdev"); + if (!temp_str) { + free(namelist[n]); + continue; + } + if (!find_tzone_cdev(namelist[n], tz_name, + &ptdata.tzi[k], i, j)) + j++; /* increment cdev index */ + free(namelist[n]); + } + free(namelist); + } + /*TODO: reverse trip points */ + closedir(dir); + syslog(LOG_INFO, "TZ %d has %d cdev\n", i, + ptdata.tzi[k].nr_cdev); + k++; + } + + return 0; +} + +static int scan_cdevs(void) +{ + DIR *dir; + struct dirent **namelist; + char cdev_name[256]; + int i, n, k = 0; + + if (!ptdata.nr_cooling_dev) { + fprintf(stderr, "No cooling devices found\n"); + return 0; + } + for (i = 0; i <= ptdata.max_cdev_instance; i++) { + memset(cdev_name, 0, sizeof(cdev_name)); + snprintf(cdev_name, 256, "%s/%s%d", THERMAL_SYSFS, CDEV, i); + + dir = opendir(cdev_name); + if (!dir) { + syslog(LOG_INFO, "Cooling dev %s skipped\n", cdev_name); + /* there is a gap in cooling device id, check again + * for the same index. + */ + continue; + } + + n = scandir(cdev_name, &namelist, 0, alphasort); + if (n < 0) + syslog(LOG_ERR, "scandir failed in %s", cdev_name); + else { + sysfs_get_string(cdev_name, "type", ptdata.cdi[k].type); + ptdata.cdi[k].instance = i; + if (strstr(ptdata.cdi[k].type, ctrl_cdev)) { + ptdata.cdi[k].flag |= CDEV_FLAG_IN_CONTROL; + syslog(LOG_DEBUG, "control cdev id %d\n", i); + } + while (n--) + free(namelist[n]); + free(namelist); + } + closedir(dir); + k++; + } + return 0; +} + + +int probe_thermal_sysfs(void) +{ + DIR *dir; + struct dirent **namelist; + int n; + + dir = opendir(THERMAL_SYSFS); + if (!dir) { + fprintf(stderr, "\nNo thermal sysfs, exit\n"); + return -1; + } + n = scandir(THERMAL_SYSFS, &namelist, 0, alphasort); + if (n < 0) + syslog(LOG_ERR, "scandir failed in thermal sysfs"); + else { + /* detect number of thermal zones and cooling devices */ + while (n--) { + int inst; + + if (strstr(namelist[n]->d_name, CDEV)) { + inst = get_instance_id(namelist[n]->d_name, 1, + sizeof("device") - 1); + /* keep track of the max cooling device since + * there may be gaps. + */ + if (inst > ptdata.max_cdev_instance) + ptdata.max_cdev_instance = inst; + + syslog(LOG_DEBUG, "found cdev: %s %d %d\n", + namelist[n]->d_name, + ptdata.nr_cooling_dev, + ptdata.max_cdev_instance); + ptdata.nr_cooling_dev++; + } else if (strstr(namelist[n]->d_name, TZONE)) { + inst = get_instance_id(namelist[n]->d_name, 1, + sizeof("zone") - 1); + if (inst > ptdata.max_tz_instance) + ptdata.max_tz_instance = inst; + + syslog(LOG_DEBUG, "found tzone: %s %d %d\n", + namelist[n]->d_name, + ptdata.nr_tz_sensor, + ptdata.max_tz_instance); + ptdata.nr_tz_sensor++; + } + free(namelist[n]); + } + free(namelist); + } + syslog(LOG_INFO, "found %d tzone(s), %d cdev(s), target zone %d\n", + ptdata.nr_tz_sensor, ptdata.nr_cooling_dev, + target_thermal_zone); + closedir(dir); + + if (!ptdata.nr_tz_sensor) { + fprintf(stderr, "\nNo thermal zones found, exit\n\n"); + return -1; + } + + ptdata.tzi = calloc(sizeof(struct tz_info), ptdata.max_tz_instance+1); + if (!ptdata.tzi) { + fprintf(stderr, "Err: allocate tz_info\n"); + return -1; + } + + /* we still show thermal zone information if there is no cdev */ + if (ptdata.nr_cooling_dev) { + ptdata.cdi = calloc(sizeof(struct cdev_info), + ptdata.max_cdev_instance + 1); + if (!ptdata.cdi) { + free(ptdata.tzi); + fprintf(stderr, "Err: allocate cdev_info\n"); + return -1; + } + } + + /* now probe tzones */ + if (scan_tzones()) + return -1; + if (scan_cdevs()) + return -1; + return 0; +} + +/* convert sysfs zone instance to zone array index */ +int zone_instance_to_index(int zone_inst) +{ + int i; + + for (i = 0; i < ptdata.nr_tz_sensor; i++) + if (ptdata.tzi[i].instance == zone_inst) + return i; + return -ENOENT; +} + +/* read temperature of all thermal zones */ +int update_thermal_data() +{ + int i; + char tz_name[256]; + static unsigned long samples; + + if (!ptdata.nr_tz_sensor) { + syslog(LOG_ERR, "No thermal zones found!\n"); + return -1; + } + + /* circular buffer for keeping historic data */ + if (cur_thermal_record >= NR_THERMAL_RECORDS) + cur_thermal_record = 0; + gettimeofday(&trec[cur_thermal_record].tv, NULL); + if (tmon_log) { + fprintf(tmon_log, "%lu ", ++samples); + fprintf(tmon_log, "%3.1f ", p_param.t_target); + } + for (i = 0; i < ptdata.nr_tz_sensor; i++) { + memset(tz_name, 0, sizeof(tz_name)); + snprintf(tz_name, 256, "%s/%s%d", THERMAL_SYSFS, TZONE, + ptdata.tzi[i].instance); + sysfs_get_ulong(tz_name, "temp", + &trec[cur_thermal_record].temp[i]); + if (tmon_log) + fprintf(tmon_log, "%lu ", + trec[cur_thermal_record].temp[i]/1000); + } + for (i = 0; i < ptdata.nr_cooling_dev; i++) { + char cdev_name[256]; + unsigned long val; + + snprintf(cdev_name, 256, "%s/%s%d", THERMAL_SYSFS, CDEV, + ptdata.cdi[i].instance); + probe_cdev(&ptdata.cdi[i], cdev_name); + val = ptdata.cdi[i].cur_state; + if (val > 1000000) + val = 0; + if (tmon_log) + fprintf(tmon_log, "%lu ", val); + } + + if (tmon_log) { + fprintf(tmon_log, "\n"); + fflush(tmon_log); + } + + return 0; +} + +void set_ctrl_state(unsigned long state) +{ + char ctrl_cdev_path[256]; + int i; + unsigned long cdev_state; + + if (no_control) + return; + /* set all ctrl cdev to the same state */ + for (i = 0; i < ptdata.nr_cooling_dev; i++) { + if (ptdata.cdi[i].flag & CDEV_FLAG_IN_CONTROL) { + if (ptdata.cdi[i].max_state < 10) { + strcpy(ctrl_cdev, "None."); + return; + } + /* scale to percentage of max_state */ + cdev_state = state * ptdata.cdi[i].max_state/100; + syslog(LOG_DEBUG, + "ctrl cdev %d set state %lu scaled to %lu\n", + ptdata.cdi[i].instance, state, cdev_state); + snprintf(ctrl_cdev_path, 256, "%s/%s%d", THERMAL_SYSFS, + CDEV, ptdata.cdi[i].instance); + syslog(LOG_DEBUG, "ctrl cdev path %s", ctrl_cdev_path); + sysfs_set_ulong(ctrl_cdev_path, "cur_state", + cdev_state); + } + } +} + +void get_ctrl_state(unsigned long *state) +{ + char ctrl_cdev_path[256]; + int ctrl_cdev_id = -1; + int i; + + /* TODO: take average of all ctrl types. also consider change based on + * uevent. Take the first reading for now. + */ + for (i = 0; i < ptdata.nr_cooling_dev; i++) { + if (ptdata.cdi[i].flag & CDEV_FLAG_IN_CONTROL) { + ctrl_cdev_id = ptdata.cdi[i].instance; + syslog(LOG_INFO, "ctrl cdev %d get state\n", + ptdata.cdi[i].instance); + break; + } + } + if (ctrl_cdev_id == -1) { + *state = 0; + return; + } + snprintf(ctrl_cdev_path, 256, "%s/%s%d", THERMAL_SYSFS, + CDEV, ctrl_cdev_id); + sysfs_get_ulong(ctrl_cdev_path, "cur_state", state); +} + +void free_thermal_data(void) +{ + free(ptdata.tzi); + free(ptdata.cdi); +} diff --git a/tools/thermal/tmon/tmon.8 b/tools/thermal/tmon/tmon.8 new file mode 100644 index 00000000000..0be727cb989 --- /dev/null +++ b/tools/thermal/tmon/tmon.8 @@ -0,0 +1,142 @@ +.TH TMON 8 +.SH NAME +\fBtmon\fP - A monitoring and testing tool for Linux kernel thermal subsystem + +.SH SYNOPSIS +.ft B +.B tmon +.RB [ Options ] +.br +.SH DESCRIPTION +\fBtmon \fP can be used to visualize thermal relationship and +real-time thermal data; tune +and test cooling devices and sensors; collect thermal data for offline +analysis and plot. \fBtmon\fP must be run as root in order to control device +states via sysfs. +.PP +\fBFunctions\fP +.PP +.nf +1. Thermal relationships: +- show thermal zone information +- show cooling device information +- show trip point binding within each thermal zone +- show trip point and cooling device instance bindings +.PP +2. Real time data display +- show temperature of all thermal zones w.r.t. its trip points and types +- show states of all cooling devices +.PP +3. Thermal relationship learning and device tuning +- with a built-in Proportional Integral Derivative (\fBPID\fP) +controller, user can pair a cooling device to a thermal sensor for +testing the effectiveness and learn about the thermal distance between the two +- allow manual control of cooling device states and target temperature +.PP +4. Data logging in /var/tmp/tmon.log +- contains thermal configuration data, i.e. cooling device, thermal + zones, and trip points. Can be used for data collection in remote + debugging. +- log real-time thermal data into space separated format that can be + directly consumed by plotting tools such as Rscript. + +.SS Options +.PP +The \fB-c --control\fP option sets a cooling device type to control temperature +of a thermal zone +.PP +The \fB-d --daemon\fP option runs \fBtmon \fP as daemon without user interface +.PP +The \fB-g --debug\fP option allow debug messages to be stored in syslog +.PP +The \fB-h --help\fP option shows help message +.PP +The \fB-l --log\fP option write data to /var/tmp/tmon.log +.PP +The \fB-t --time-interval\fP option sets the polling interval in seconds +.PP +The \fB-v --version\fP option shows the version of \fBtmon \fP +.PP +The \fB-z --zone\fP option sets the target therma zone instance to be controlled +.PP + +.SH FIELD DESCRIPTIONS +.nf +.PP +\fBP \fP passive cooling trip point type +\fBA \fP active cooling trip point type (fan) +\fBC \fP critical trip point type +\fBA \fP hot trip point type +\fBkp \fP proportional gain of \fBPID\fP controller +\fBki \fP integral gain of \fBPID\fP controller +\fBkd \fP derivative gain of \fBPID\fP controller + +.SH REQUIREMENT +Build depends on ncurses +.PP +Runtime depends on window size large enough to show the number of +devices found on the system. + +.PP + +.SH INTERACTIVE COMMANDS +.pp +.nf +\fBCtrl-C, q/Q\fP stops \fBtmon\fP +\fBTAB\fP shows tuning pop up panel, choose a letter to modify + +.SH EXAMPLES +Without any parameters, tmon is in monitoring only mode and refresh +screen every 1 second. +.PP +1. For monitoring only: +.nf +$ sudo ./tmon + +2. Use Processor cooling device to control thermal zone 0 at default 65C. +$ sudo ./tmon -c Processor -z 0 + +3. Use intel_powerclamp(idle injection) cooling device to control thermal zone 1 +$ sudo ./tmon -c intel_powerclamp -z 1 + +4. Turn on debug and collect data log at /var/tmp/tmon.log +$ sudo ./tmon -g -l + +For example, the log below shows PID controller was adjusting current states +for all cooling devices with "Processor" type such that thermal zone 0 +can stay below 65 dC. + +#---------- THERMAL DATA LOG STARTED ----------- +Samples TargetTemp acpitz0 acpitz1 Fan0 Fan1 Fan2 Fan3 Fan4 Fan5 +Fan6 Fan7 Fan8 Fan9 Processor10 Processor11 Processor12 Processor13 +LCD14 intel_powerclamp15 1 65.0 65 65 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 0 2 +65.0 66 65 0 0 0 0 0 0 0 0 0 0 4 4 4 4 6 0 3 65.0 60 54 0 0 0 0 0 0 0 0 +0 0 4 4 4 4 6 0 4 65.0 53 53 0 0 0 0 0 0 0 0 0 0 4 4 4 4 6 0 +5 65.0 52 52 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 0 +6 65.0 53 65 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 0 +7 65.0 68 70 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 0 +8 65.0 68 68 0 0 0 0 0 0 0 0 0 0 5 5 5 5 6 0 +9 65.0 68 68 0 0 0 0 0 0 0 0 0 0 6 6 6 6 6 0 +10 65.0 67 67 0 0 0 0 0 0 0 0 0 0 7 7 7 7 6 0 +11 65.0 67 67 0 0 0 0 0 0 0 0 0 0 8 8 8 8 6 0 +12 65.0 67 67 0 0 0 0 0 0 0 0 0 0 8 8 8 8 6 0 +13 65.0 67 67 0 0 0 0 0 0 0 0 0 0 9 9 9 9 6 0 +14 65.0 66 66 0 0 0 0 0 0 0 0 0 0 10 10 10 10 6 0 +15 65.0 66 67 0 0 0 0 0 0 0 0 0 0 10 10 10 10 6 0 +16 65.0 66 66 0 0 0 0 0 0 0 0 0 0 11 11 11 11 6 0 +17 65.0 66 66 0 0 0 0 0 0 0 0 0 0 11 11 11 11 6 0 +18 65.0 64 61 0 0 0 0 0 0 0 0 0 0 11 11 11 11 6 0 +19 65.0 60 59 0 0 0 0 0 0 0 0 0 0 12 12 12 12 6 0 + +Data can be read directly into an array by an example R-script below: + +#!/usr/bin/Rscript +tdata <- read.table("/var/tmp/tmon.log", header=T, comment.char="#") +attach(tdata) +jpeg("tmon.jpg") +X11() +g_range <- range(0, intel_powerclamp15, TargetTemp, acpitz0) +plot( Samples, intel_powerclamp15, col="blue", ylim=g_range, axes=FALSE, ann=FALSE) +par(new=TRUE) +lines(TargetTemp, type="o", pch=22, lty=2, col="red") +dev.off() diff --git a/tools/thermal/tmon/tmon.c b/tools/thermal/tmon/tmon.c new file mode 100644 index 00000000000..b30f531173e --- /dev/null +++ b/tools/thermal/tmon/tmon.c @@ -0,0 +1,352 @@ +/* + * tmon.c Thermal Monitor (TMON) main function and entry point + * + * Copyright (C) 2012 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 or later as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Author: Jacob Pan <jacob.jun.pan@linux.intel.com> + * + */ + +#include <getopt.h> +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <ncurses.h> +#include <ctype.h> +#include <time.h> +#include <signal.h> +#include <limits.h> +#include <sys/time.h> +#include <pthread.h> +#include <math.h> +#include <stdarg.h> +#include <syslog.h> + +#include "tmon.h" + +unsigned long ticktime = 1; /* seconds */ +unsigned long no_control = 1; /* monitoring only or use cooling device for + * temperature control. + */ +double time_elapsed = 0.0; +unsigned long target_temp_user = 65; /* can be select by tui later */ +int dialogue_on; +int tmon_exit; +static short daemon_mode; +static int logging; /* for recording thermal data to a file */ +static int debug_on; +FILE *tmon_log; +/*cooling device used for the PID controller */ +char ctrl_cdev[CDEV_NAME_SIZE] = "None"; +int target_thermal_zone; /* user selected target zone instance */ +static void start_daemon_mode(void); + +pthread_t event_tid; +pthread_mutex_t input_lock; +void usage() +{ + printf("Usage: tmon [OPTION...]\n"); + printf(" -c, --control cooling device in control\n"); + printf(" -d, --daemon run as daemon, no TUI\n"); + printf(" -g, --debug debug message in syslog\n"); + printf(" -h, --help show this help message\n"); + printf(" -l, --log log data to /var/tmp/tmon.log\n"); + printf(" -t, --time-interval sampling time interval, > 1 sec.\n"); + printf(" -v, --version show version\n"); + printf(" -z, --zone target thermal zone id\n"); + + exit(0); +} + +void version() +{ + printf("TMON version %s\n", VERSION); + exit(EXIT_SUCCESS); +} + +static void tmon_cleanup(void) +{ + + syslog(LOG_INFO, "TMON exit cleanup\n"); + fflush(stdout); + refresh(); + if (tmon_log) + fclose(tmon_log); + if (event_tid) { + pthread_mutex_lock(&input_lock); + pthread_cancel(event_tid); + pthread_mutex_unlock(&input_lock); + pthread_mutex_destroy(&input_lock); + } + closelog(); + /* relax control knobs, undo throttling */ + set_ctrl_state(0); + + keypad(stdscr, FALSE); + echo(); + nocbreak(); + close_windows(); + endwin(); + free_thermal_data(); + + exit(1); +} + + +static void tmon_sig_handler(int sig) +{ + syslog(LOG_INFO, "TMON caught signal %d\n", sig); + refresh(); + switch (sig) { + case SIGTERM: + printf("sigterm, exit and clean up\n"); + fflush(stdout); + break; + case SIGKILL: + printf("sigkill, exit and clean up\n"); + fflush(stdout); + break; + case SIGINT: + printf("ctrl-c, exit and clean up\n"); + fflush(stdout); + break; + default: + break; + } + tmon_exit = true; +} + + +static void start_syslog(void) +{ + if (debug_on) + setlogmask(LOG_UPTO(LOG_DEBUG)); + else + setlogmask(LOG_UPTO(LOG_ERR)); + openlog("tmon.log", LOG_CONS | LOG_PID | LOG_NDELAY, LOG_LOCAL0); + syslog(LOG_NOTICE, "TMON started by User %d", getuid()); +} + +static void prepare_logging(void) +{ + int i; + + if (!logging) + return; + /* open local data log file */ + tmon_log = fopen(TMON_LOG_FILE, "w+"); + if (!tmon_log) { + syslog(LOG_ERR, "failed to open log file %s\n", TMON_LOG_FILE); + return; + } + + fprintf(tmon_log, "#----------- THERMAL SYSTEM CONFIG -------------\n"); + for (i = 0; i < ptdata.nr_tz_sensor; i++) { + char binding_str[33]; /* size of long + 1 */ + int j; + + memset(binding_str, 0, sizeof(binding_str)); + for (j = 0; j < 32; j++) + binding_str[j] = (ptdata.tzi[i].cdev_binding & 1<<j) ? + '1' : '0'; + + fprintf(tmon_log, "#thermal zone %s%02d cdevs binding: %32s\n", + ptdata.tzi[i].type, + ptdata.tzi[i].instance, + binding_str); + for (j = 0; j < ptdata.tzi[i].nr_trip_pts; j++) { + fprintf(tmon_log, "#\tTP%02d type:%s, temp:%lu\n", j, + trip_type_name[ptdata.tzi[i].tp[j].type], + ptdata.tzi[i].tp[j].temp); + } + + } + + for (i = 0; i < ptdata.nr_cooling_dev; i++) + fprintf(tmon_log, "#cooling devices%02d: %s\n", + i, ptdata.cdi[i].type); + + fprintf(tmon_log, "#---------- THERMAL DATA LOG STARTED -----------\n"); + fprintf(tmon_log, "Samples TargetTemp "); + for (i = 0; i < ptdata.nr_tz_sensor; i++) { + fprintf(tmon_log, "%s%d ", ptdata.tzi[i].type, + ptdata.tzi[i].instance); + } + for (i = 0; i < ptdata.nr_cooling_dev; i++) + fprintf(tmon_log, "%s%d ", ptdata.cdi[i].type, + ptdata.cdi[i].instance); + + fprintf(tmon_log, "\n"); +} + +static struct option opts[] = { + { "control", 1, NULL, 'c' }, + { "daemon", 0, NULL, 'd' }, + { "time-interval", 1, NULL, 't' }, + { "log", 0, NULL, 'l' }, + { "help", 0, NULL, 'h' }, + { "version", 0, NULL, 'v' }, + { "debug", 0, NULL, 'g' }, + { 0, 0, NULL, 0 } +}; + + +int main(int argc, char **argv) +{ + int err = 0; + int id2 = 0, c; + double yk = 0.0; /* controller output */ + int target_tz_index; + + if (geteuid() != 0) { + printf("TMON needs to be run as root\n"); + exit(EXIT_FAILURE); + } + + while ((c = getopt_long(argc, argv, "c:dlht:vgz:", opts, &id2)) != -1) { + switch (c) { + case 'c': + no_control = 0; + strncpy(ctrl_cdev, optarg, CDEV_NAME_SIZE); + break; + case 'd': + start_daemon_mode(); + printf("Run TMON in daemon mode\n"); + break; + case 't': + ticktime = strtod(optarg, NULL); + if (ticktime < 1) + ticktime = 1; + break; + case 'l': + printf("Logging data to /var/tmp/tmon.log\n"); + logging = 1; + break; + case 'h': + usage(); + break; + case 'v': + version(); + break; + case 'g': + debug_on = 1; + break; + case 'z': + target_thermal_zone = strtod(optarg, NULL); + break; + default: + break; + } + } + if (pthread_mutex_init(&input_lock, NULL) != 0) { + fprintf(stderr, "\n mutex init failed, exit\n"); + return 1; + } + start_syslog(); + if (signal(SIGINT, tmon_sig_handler) == SIG_ERR) + syslog(LOG_DEBUG, "Cannot handle SIGINT\n"); + if (signal(SIGTERM, tmon_sig_handler) == SIG_ERR) + syslog(LOG_DEBUG, "Cannot handle SIGINT\n"); + + if (probe_thermal_sysfs()) { + pthread_mutex_destroy(&input_lock); + closelog(); + return -1; + } + initialize_curses(); + setup_windows(); + signal(SIGWINCH, resize_handler); + show_title_bar(); + show_sensors_w(); + show_cooling_device(); + update_thermal_data(); + show_data_w(); + prepare_logging(); + init_thermal_controller(); + + nodelay(stdscr, TRUE); + err = pthread_create(&event_tid, NULL, &handle_tui_events, NULL); + if (err != 0) { + printf("\ncan't create thread :[%s]", strerror(err)); + tmon_cleanup(); + exit(EXIT_FAILURE); + } + + /* validate range of user selected target zone, default to the first + * instance if out of range + */ + target_tz_index = zone_instance_to_index(target_thermal_zone); + if (target_tz_index < 0) { + target_thermal_zone = ptdata.tzi[0].instance; + syslog(LOG_ERR, "target zone is not found, default to %d\n", + target_thermal_zone); + } + while (1) { + sleep(ticktime); + show_title_bar(); + show_sensors_w(); + update_thermal_data(); + if (!dialogue_on) { + show_data_w(); + show_cooling_device(); + } + cur_thermal_record++; + time_elapsed += ticktime; + controller_handler(trec[0].temp[target_tz_index] / 1000, + &yk); + trec[0].pid_out_pct = yk; + if (!dialogue_on) + show_control_w(); + if (tmon_exit) + break; + } + tmon_cleanup(); + return 0; +} + +static void start_daemon_mode() +{ + daemon_mode = 1; + /* fork */ + pid_t sid, pid = fork(); + if (pid < 0) { + exit(EXIT_FAILURE); + } else if (pid > 0) + /* kill parent */ + exit(EXIT_SUCCESS); + + /* disable TUI, it may not be necessary, but saves some resource */ + disable_tui(); + + /* change the file mode mask */ + umask(0); + + /* new SID for the daemon process */ + sid = setsid(); + if (sid < 0) + exit(EXIT_FAILURE); + + /* change working directory */ + if ((chdir("/")) < 0) + exit(EXIT_FAILURE); + + + sleep(10); + + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + +} diff --git a/tools/thermal/tmon/tmon.h b/tools/thermal/tmon/tmon.h new file mode 100644 index 00000000000..9e3c49c547a --- /dev/null +++ b/tools/thermal/tmon/tmon.h @@ -0,0 +1,204 @@ +/* + * tmon.h contains data structures and constants used by TMON + * + * Copyright (C) 2012 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 or later as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Author Name Jacob Pan <jacob.jun.pan@linux.intel.com> + * + */ + +#ifndef TMON_H +#define TMON_H + +#define MAX_DISP_TEMP 125 +#define MAX_CTRL_TEMP 105 +#define MIN_CTRL_TEMP 40 +#define MAX_NR_TZONE 16 +#define MAX_NR_CDEV 32 +#define MAX_NR_TRIP 16 +#define MAX_NR_CDEV_TRIP 12 /* number of cooling devices that can bind + * to a thermal zone trip. + */ +#define MAX_TEMP_KC 140000 +/* starting char position to draw sensor data, such as tz names + * trip point list, etc. + */ +#define DATA_LEFT_ALIGN 10 +#define NR_LINES_TZDATA 1 +#define TMON_LOG_FILE "/var/tmp/tmon.log" + +extern unsigned long ticktime; +extern double time_elapsed; +extern unsigned long target_temp_user; +extern int dialogue_on; +extern char ctrl_cdev[]; +extern pthread_mutex_t input_lock; +extern int tmon_exit; +extern int target_thermal_zone; +/* use fixed size record to simplify data processing and transfer + * TBD: more info to be added, e.g. programmable trip point data. +*/ +struct thermal_data_record { + struct timeval tv; + unsigned long temp[MAX_NR_TZONE]; + double pid_out_pct; +}; + +struct cdev_info { + char type[64]; + int instance; + unsigned long max_state; + unsigned long cur_state; + unsigned long flag; +}; + +enum trip_type { + THERMAL_TRIP_CRITICAL, + THERMAL_TRIP_HOT, + THERMAL_TRIP_PASSIVE, + THERMAL_TRIP_ACTIVE, + NR_THERMAL_TRIP_TYPE, +}; + +struct trip_point { + enum trip_type type; + unsigned long temp; + unsigned long hysteresis; + int attribute; /* programmability etc. */ +}; + +/* thermal zone configuration information, binding with cooling devices could + * change at runtime. + */ +struct tz_info { + char type[256]; /* e.g. acpitz */ + int instance; + int passive; /* active zone has passive node to force passive mode */ + int nr_cdev; /* number of cooling device binded */ + int nr_trip_pts; + struct trip_point tp[MAX_NR_TRIP]; + unsigned long cdev_binding; /* bitmap for attached cdevs */ + /* cdev bind trip points, allow one cdev bind to multiple trips */ + unsigned long trip_binding[MAX_NR_CDEV]; +}; + +struct tmon_platform_data { + int nr_tz_sensor; + int nr_cooling_dev; + /* keep track of instance ids since there might be gaps */ + int max_tz_instance; + int max_cdev_instance; + struct tz_info *tzi; + struct cdev_info *cdi; +}; + +struct control_ops { + void (*set_ratio)(unsigned long ratio); + unsigned long (*get_ratio)(unsigned long ratio); + +}; + +enum cdev_types { + CDEV_TYPE_PROC, + CDEV_TYPE_FAN, + CDEV_TYPE_MEM, + CDEV_TYPE_NR, +}; + +/* REVISIT: the idea is to group sensors if possible, e.g. on intel mid + * we have "skin0", "skin1", "sys", "msicdie" + * on DPTF enabled systems, we might have PCH, TSKN, TAMB, etc. + */ +enum tzone_types { + TZONE_TYPE_ACPI, + TZONE_TYPE_PCH, + TZONE_TYPE_NR, +}; + +/* limit the output of PID controller adjustment */ +#define LIMIT_HIGH (95) +#define LIMIT_LOW (2) + +struct pid_params { + double kp; /* Controller gain from Dialog Box */ + double ki; /* Time-constant for I action from Dialog Box */ + double kd; /* Time-constant for D action from Dialog Box */ + double ts; + double k_lpf; + + double t_target; + double y_k; +}; + +extern int init_thermal_controller(void); +extern void controller_handler(const double xk, double *yk); + +extern struct tmon_platform_data ptdata; +extern struct pid_params p_param; + +extern FILE *tmon_log; +extern int cur_thermal_record; /* index to the trec array */ +extern struct thermal_data_record trec[]; +extern const char *trip_type_name[]; +extern unsigned long no_control; + +extern void initialize_curses(void); +extern void show_controller_stats(char *line); +extern void show_title_bar(void); +extern void setup_windows(void); +extern void disable_tui(void); +extern void show_sensors_w(void); +extern void show_data_w(void); +extern void write_status_bar(int x, char *line); +extern void show_control_w(); + +extern void show_cooling_device(void); +extern void show_dialogue(void); +extern int update_thermal_data(void); + +extern int probe_thermal_sysfs(void); +extern void free_thermal_data(void); +extern void resize_handler(int sig); +extern void set_ctrl_state(unsigned long state); +extern void get_ctrl_state(unsigned long *state); +extern void *handle_tui_events(void *arg); +extern int sysfs_set_ulong(char *path, char *filename, unsigned long val); +extern int zone_instance_to_index(int zone_inst); +extern void close_windows(void); + +#define PT_COLOR_DEFAULT 1 +#define PT_COLOR_HEADER_BAR 2 +#define PT_COLOR_ERROR 3 +#define PT_COLOR_RED 4 +#define PT_COLOR_YELLOW 5 +#define PT_COLOR_GREEN 6 +#define PT_COLOR_BRIGHT 7 +#define PT_COLOR_BLUE 8 + +/* each thermal zone uses 12 chars, 8 for name, 2 for instance, 2 space + * also used to list trip points in forms of AAAC, which represents + * A: Active + * C: Critical + */ +#define TZONE_RECORD_SIZE 12 +#define TZ_LEFT_ALIGN 32 +#define CDEV_NAME_SIZE 20 +#define CDEV_FLAG_IN_CONTROL (1 << 0) + +/* dialogue box starts */ +#define DIAG_X 48 +#define DIAG_Y 8 +#define THERMAL_SYSFS "/sys/class/thermal" +#define CDEV "cooling_device" +#define TZONE "thermal_zone" +#define TDATA_LEFT 16 +#endif /* TMON_H */ diff --git a/tools/thermal/tmon/tui.c b/tools/thermal/tmon/tui.c new file mode 100644 index 00000000000..89f8ef0e15c --- /dev/null +++ b/tools/thermal/tmon/tui.c @@ -0,0 +1,638 @@ +/* + * tui.c ncurses text user interface for TMON program + * + * Copyright (C) 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 or later as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Author: Jacob Pan <jacob.jun.pan@linux.intel.com> + * + */ + +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <ncurses.h> +#include <time.h> +#include <syslog.h> +#include <panel.h> +#include <pthread.h> +#include <signal.h> + +#include "tmon.h" + +static PANEL *data_panel; +static PANEL *dialogue_panel; +static PANEL *top; + +static WINDOW *title_bar_window; +static WINDOW *tz_sensor_window; +static WINDOW *cooling_device_window; +static WINDOW *control_window; +static WINDOW *status_bar_window; +static WINDOW *thermal_data_window; +static WINDOW *dialogue_window; + +char status_bar_slots[10][40]; +static void draw_hbar(WINDOW *win, int y, int start, int len, + unsigned long pattern, bool end); + +static int maxx, maxy; +static int maxwidth = 200; + +#define TITLE_BAR_HIGHT 1 +#define SENSOR_WIN_HIGHT 4 /* one row for tz name, one for trip points */ + + +/* daemon mode flag (set by startup parameter -d) */ +static int tui_disabled; + +static void close_panel(PANEL *p) +{ + if (p) { + del_panel(p); + p = NULL; + } +} + +static void close_window(WINDOW *win) +{ + if (win) { + delwin(win); + win = NULL; + } +} + +void close_windows(void) +{ + if (tui_disabled) + return; + /* must delete panels before their attached windows */ + if (dialogue_window) + close_panel(dialogue_panel); + if (cooling_device_window) + close_panel(data_panel); + + close_window(title_bar_window); + close_window(tz_sensor_window); + close_window(status_bar_window); + close_window(cooling_device_window); + close_window(control_window); + close_window(thermal_data_window); + close_window(dialogue_window); + +} + +void write_status_bar(int x, char *line) +{ + mvwprintw(status_bar_window, 0, x, "%s", line); + wrefresh(status_bar_window); +} + +void setup_windows(void) +{ + int y_begin = 1; + + if (tui_disabled) + return; + + getmaxyx(stdscr, maxy, maxx); + resizeterm(maxy, maxx); + + title_bar_window = subwin(stdscr, TITLE_BAR_HIGHT, maxx, 0, 0); + y_begin += TITLE_BAR_HIGHT; + + tz_sensor_window = subwin(stdscr, SENSOR_WIN_HIGHT, maxx, y_begin, 0); + y_begin += SENSOR_WIN_HIGHT; + + cooling_device_window = subwin(stdscr, ptdata.nr_cooling_dev + 3, maxx, + y_begin, 0); + y_begin += ptdata.nr_cooling_dev + 3; /* 2 lines for border */ + /* two lines to show borders, one line per tz show trip point position + * and value. + * dialogue window is a pop-up, when needed it lays on top of cdev win + */ + + dialogue_window = subwin(stdscr, ptdata.nr_cooling_dev+5, maxx-50, + DIAG_Y, DIAG_X); + + thermal_data_window = subwin(stdscr, ptdata.nr_tz_sensor * + NR_LINES_TZDATA + 3, maxx, y_begin, 0); + y_begin += ptdata.nr_tz_sensor * NR_LINES_TZDATA + 3; + control_window = subwin(stdscr, 4, maxx, y_begin, 0); + + scrollok(cooling_device_window, TRUE); + maxwidth = maxx - 18; + status_bar_window = subwin(stdscr, 1, maxx, maxy-1, 0); + + strcpy(status_bar_slots[0], " Ctrl-c - Quit "); + strcpy(status_bar_slots[1], " TAB - Tuning "); + wmove(status_bar_window, 1, 30); + + /* prepare panels for dialogue, if panel already created then we must + * be doing resizing, so just replace windows with new ones, old ones + * should have been deleted by close_window + */ + data_panel = new_panel(cooling_device_window); + if (!data_panel) + syslog(LOG_DEBUG, "No data panel\n"); + else { + if (dialogue_window) { + dialogue_panel = new_panel(dialogue_window); + if (!dialogue_panel) + syslog(LOG_DEBUG, "No dialogue panel\n"); + else { + /* Set up the user pointer to the next panel*/ + set_panel_userptr(data_panel, dialogue_panel); + set_panel_userptr(dialogue_panel, data_panel); + top = data_panel; + } + } else + syslog(LOG_INFO, "no dialogue win, term too small\n"); + } + doupdate(); + werase(stdscr); + refresh(); +} + +void resize_handler(int sig) +{ + /* start over when term gets resized, but first we clean up */ + close_windows(); + endwin(); + refresh(); + clear(); + getmaxyx(stdscr, maxy, maxx); /* get the new screen size */ + setup_windows(); + /* rate limit */ + sleep(1); + syslog(LOG_DEBUG, "SIG %d, term resized to %d x %d\n", + sig, maxy, maxx); + signal(SIGWINCH, resize_handler); +} + +const char cdev_title[] = " COOLING DEVICES "; +void show_cooling_device(void) +{ + int i, j, x, y = 0; + + if (tui_disabled || !cooling_device_window) + return; + + werase(cooling_device_window); + wattron(cooling_device_window, A_BOLD); + mvwprintw(cooling_device_window, 1, 1, + "ID Cooling Dev Cur Max Thermal Zone Binding"); + wattroff(cooling_device_window, A_BOLD); + for (j = 0; j < ptdata.nr_cooling_dev; j++) { + /* draw cooling device list on the left in the order of + * cooling device instances. skip unused idr. + */ + mvwprintw(cooling_device_window, j + 2, 1, + "%02d %12.12s%6d %6d", + ptdata.cdi[j].instance, + ptdata.cdi[j].type, + ptdata.cdi[j].cur_state, + ptdata.cdi[j].max_state); + } + + /* show cdev binding, y is the global cooling device instance */ + for (i = 0; i < ptdata.nr_tz_sensor; i++) { + int tz_inst = ptdata.tzi[i].instance; + for (j = 0; j < ptdata.nr_cooling_dev; j++) { + int cdev_inst; + y = j; + x = tz_inst * TZONE_RECORD_SIZE + TZ_LEFT_ALIGN; + + draw_hbar(cooling_device_window, y+2, x, + TZONE_RECORD_SIZE-1, ACS_VLINE, false); + + /* draw a column of spaces to separate thermal zones */ + mvwprintw(cooling_device_window, y+2, x-1, " "); + if (ptdata.tzi[i].cdev_binding) { + cdev_inst = ptdata.cdi[j].instance; + unsigned long trip_binding = + ptdata.tzi[i].trip_binding[cdev_inst]; + int k = 0; /* per zone trip point id that + * binded to this cdev, one to + * many possible based on the + * binding bitmask. + */ + syslog(LOG_DEBUG, + "bind tz%d cdev%d tp%lx %d cdev%lx\n", + i, j, trip_binding, y, + ptdata.tzi[i].cdev_binding); + /* draw each trip binding for the cdev */ + while (trip_binding >>= 1) { + k++; + if (!(trip_binding & 1)) + continue; + /* draw '*' to show binding */ + mvwprintw(cooling_device_window, + y + 2, + x + ptdata.tzi[i].nr_trip_pts - + k - 1, "*"); + } + } + } + } + /* draw border after data so that border will not be messed up + * even there is not enough space for all the data to be shown + */ + wborder(cooling_device_window, 0, 0, 0, 0, 0, 0, 0, 0); + wattron(cooling_device_window, A_BOLD); + mvwprintw(cooling_device_window, 0, maxx/2 - sizeof(cdev_title), + cdev_title); + wattroff(cooling_device_window, A_BOLD); + + wrefresh(cooling_device_window); +} + +const char DIAG_TITLE[] = "[ TUNABLES ]"; +#define DIAG_DEV_ROWS 5 +void show_dialogue(void) +{ + int j, x = 0, y = 0; + WINDOW *w = dialogue_window; + + if (tui_disabled || !w) + return; + + werase(w); + box(w, 0, 0); + mvwprintw(w, 0, maxx/4, DIAG_TITLE); + /* list all the available tunables */ + for (j = 0; j <= ptdata.nr_cooling_dev; j++) { + y = j % DIAG_DEV_ROWS; + if (y == 0 && j != 0) + x += 20; + if (j == ptdata.nr_cooling_dev) + /* save last choice for target temp */ + mvwprintw(w, y+1, x+1, "%C-%.12s", 'A'+j, "Set Temp"); + else + mvwprintw(w, y+1, x+1, "%C-%.10s-%2d", 'A'+j, + ptdata.cdi[j].type, ptdata.cdi[j].instance); + } + wattron(w, A_BOLD); + mvwprintw(w, DIAG_DEV_ROWS+1, 1, "Enter Choice [A-Z]?"); + wattroff(w, A_BOLD); + /* y size of dialogue win is nr cdev + 5, so print legend + * at the bottom line + */ + mvwprintw(w, ptdata.nr_cooling_dev+3, 1, + "Legend: A=Active, P=Passive, C=Critical"); + + wrefresh(dialogue_window); +} + +void write_dialogue_win(char *buf, int y, int x) +{ + WINDOW *w = dialogue_window; + + mvwprintw(w, y, x, "%s", buf); +} + +const char control_title[] = " CONTROLS "; +void show_control_w(void) +{ + unsigned long state; + + get_ctrl_state(&state); + + if (tui_disabled || !control_window) + return; + + werase(control_window); + mvwprintw(control_window, 1, 1, + "PID gain: kp=%2.2f ki=%2.2f kd=%2.2f Output %2.2f", + p_param.kp, p_param.ki, p_param.kd, p_param.y_k); + + mvwprintw(control_window, 2, 1, + "Target Temp: %2.1fC, Zone: %d, Control Device: %.12s", + p_param.t_target, target_thermal_zone, ctrl_cdev); + + /* draw border last such that everything is within boundary */ + wborder(control_window, 0, 0, 0, 0, 0, 0, 0, 0); + wattron(control_window, A_BOLD); + mvwprintw(control_window, 0, maxx/2 - sizeof(control_title), + control_title); + wattroff(control_window, A_BOLD); + + wrefresh(control_window); +} + +void initialize_curses(void) +{ + if (tui_disabled) + return; + + initscr(); + start_color(); + keypad(stdscr, TRUE); /* enable keyboard mapping */ + nonl(); /* tell curses not to do NL->CR/NL on output */ + cbreak(); /* take input chars one at a time */ + noecho(); /* dont echo input */ + curs_set(0); /* turn off cursor */ + use_default_colors(); + + init_pair(PT_COLOR_DEFAULT, COLOR_WHITE, COLOR_BLACK); + init_pair(PT_COLOR_HEADER_BAR, COLOR_BLACK, COLOR_WHITE); + init_pair(PT_COLOR_ERROR, COLOR_BLACK, COLOR_RED); + init_pair(PT_COLOR_RED, COLOR_WHITE, COLOR_RED); + init_pair(PT_COLOR_YELLOW, COLOR_WHITE, COLOR_YELLOW); + init_pair(PT_COLOR_GREEN, COLOR_WHITE, COLOR_GREEN); + init_pair(PT_COLOR_BLUE, COLOR_WHITE, COLOR_BLUE); + init_pair(PT_COLOR_BRIGHT, COLOR_WHITE, COLOR_BLACK); + +} + +void show_title_bar(void) +{ + int i; + int x = 0; + + if (tui_disabled || !title_bar_window) + return; + + wattrset(title_bar_window, COLOR_PAIR(PT_COLOR_HEADER_BAR)); + wbkgd(title_bar_window, COLOR_PAIR(PT_COLOR_HEADER_BAR)); + werase(title_bar_window); + + mvwprintw(title_bar_window, 0, 0, + " TMON v%s", VERSION); + + wrefresh(title_bar_window); + + werase(status_bar_window); + + for (i = 0; i < 10; i++) { + if (strlen(status_bar_slots[i]) == 0) + continue; + wattron(status_bar_window, A_REVERSE); + mvwprintw(status_bar_window, 0, x, "%s", status_bar_slots[i]); + wattroff(status_bar_window, A_REVERSE); + x += strlen(status_bar_slots[i]) + 1; + } + wrefresh(status_bar_window); +} + +static void handle_input_val(int ch) +{ + char buf[32]; + int val; + char path[256]; + WINDOW *w = dialogue_window; + + echo(); + keypad(w, TRUE); + wgetnstr(w, buf, 31); + val = atoi(buf); + + if (ch == ptdata.nr_cooling_dev) { + snprintf(buf, 31, "Invalid Temp %d! %d-%d", val, + MIN_CTRL_TEMP, MAX_CTRL_TEMP); + if (val < MIN_CTRL_TEMP || val > MAX_CTRL_TEMP) + write_status_bar(40, buf); + else { + p_param.t_target = val; + snprintf(buf, 31, "Set New Target Temp %d", val); + write_status_bar(40, buf); + } + } else { + snprintf(path, 256, "%s/%s%d", THERMAL_SYSFS, + CDEV, ptdata.cdi[ch].instance); + sysfs_set_ulong(path, "cur_state", val); + } + noecho(); + dialogue_on = 0; + show_data_w(); + show_control_w(); + + top = (PANEL *)panel_userptr(top); + top_panel(top); +} + +static void handle_input_choice(int ch) +{ + char buf[48]; + int base = 0; + int cdev_id = 0; + + if ((ch >= 'A' && ch <= 'A' + ptdata.nr_cooling_dev) || + (ch >= 'a' && ch <= 'a' + ptdata.nr_cooling_dev)) { + base = (ch < 'a') ? 'A' : 'a'; + cdev_id = ch - base; + if (ptdata.nr_cooling_dev == cdev_id) + snprintf(buf, sizeof(buf), "New Target Temp:"); + else + snprintf(buf, sizeof(buf), "New Value for %.10s-%2d: ", + ptdata.cdi[cdev_id].type, + ptdata.cdi[cdev_id].instance); + write_dialogue_win(buf, DIAG_DEV_ROWS+2, 2); + handle_input_val(cdev_id); + } else { + snprintf(buf, sizeof(buf), "Invalid selection %d", ch); + write_dialogue_win(buf, 8, 2); + } +} + +void *handle_tui_events(void *arg) +{ + int ch; + + keypad(cooling_device_window, TRUE); + while ((ch = wgetch(cooling_device_window)) != EOF) { + if (tmon_exit) + break; + /* when term size is too small, no dialogue panels are set. + * we need to filter out such cases. + */ + if (!data_panel || !dialogue_panel || + !cooling_device_window || + !dialogue_window) { + + continue; + } + pthread_mutex_lock(&input_lock); + if (dialogue_on) { + handle_input_choice(ch); + /* top panel filter */ + if (ch == 'q' || ch == 'Q') + ch = 0; + } + switch (ch) { + case KEY_LEFT: + box(cooling_device_window, 10, 0); + break; + case 9: /* TAB */ + top = (PANEL *)panel_userptr(top); + top_panel(top); + if (top == dialogue_panel) { + dialogue_on = 1; + show_dialogue(); + } else { + dialogue_on = 0; + /* force refresh */ + show_data_w(); + show_control_w(); + } + break; + case 'q': + case 'Q': + tmon_exit = 1; + break; + } + update_panels(); + doupdate(); + pthread_mutex_unlock(&input_lock); + } + + if (arg) + *(int *)arg = 0; /* make gcc happy */ + + return NULL; +} + +/* draw a horizontal bar in given pattern */ +static void draw_hbar(WINDOW *win, int y, int start, int len, unsigned long ptn, + bool end) +{ + mvwaddch(win, y, start, ptn); + whline(win, ptn, len); + if (end) + mvwaddch(win, y, MAX_DISP_TEMP+TDATA_LEFT, ']'); +} + +static char trip_type_to_char(int type) +{ + switch (type) { + case THERMAL_TRIP_CRITICAL: return 'C'; + case THERMAL_TRIP_HOT: return 'H'; + case THERMAL_TRIP_PASSIVE: return 'P'; + case THERMAL_TRIP_ACTIVE: return 'A'; + default: + return '?'; + } +} + +/* fill a string with trip point type and value in one line + * e.g. P(56) C(106) + * maintain the distance one degree per char + */ +static void draw_tp_line(int tz, int y) +{ + int j; + int x; + + for (j = 0; j < ptdata.tzi[tz].nr_trip_pts; j++) { + x = ptdata.tzi[tz].tp[j].temp / 1000; + mvwprintw(thermal_data_window, y + 0, x + TDATA_LEFT, + "%c%d", trip_type_to_char(ptdata.tzi[tz].tp[j].type), + x); + syslog(LOG_INFO, "%s:tz %d tp %d temp = %lu\n", __func__, + tz, j, ptdata.tzi[tz].tp[j].temp); + } +} + +const char data_win_title[] = " THERMAL DATA "; +void show_data_w(void) +{ + int i; + + + if (tui_disabled || !thermal_data_window) + return; + + werase(thermal_data_window); + wattron(thermal_data_window, A_BOLD); + mvwprintw(thermal_data_window, 0, maxx/2 - sizeof(data_win_title), + data_win_title); + wattroff(thermal_data_window, A_BOLD); + /* draw a line as ruler */ + for (i = 10; i < MAX_DISP_TEMP; i += 10) + mvwprintw(thermal_data_window, 1, i+TDATA_LEFT, "%2d", i); + + for (i = 0; i < ptdata.nr_tz_sensor; i++) { + int temp = trec[cur_thermal_record].temp[i] / 1000; + int y = 0; + + y = i * NR_LINES_TZDATA + 2; + /* y at tz temp data line */ + mvwprintw(thermal_data_window, y, 1, "%6.6s%2d:[%3d][", + ptdata.tzi[i].type, + ptdata.tzi[i].instance, temp); + draw_hbar(thermal_data_window, y, TDATA_LEFT, temp, ACS_RARROW, + true); + draw_tp_line(i, y); + } + wborder(thermal_data_window, 0, 0, 0, 0, 0, 0, 0, 0); + wrefresh(thermal_data_window); +} + +const char tz_title[] = "THERMAL ZONES(SENSORS)"; + +void show_sensors_w(void) +{ + int i, j; + char buffer[512]; + + if (tui_disabled || !tz_sensor_window) + return; + + werase(tz_sensor_window); + + memset(buffer, 0, sizeof(buffer)); + wattron(tz_sensor_window, A_BOLD); + mvwprintw(tz_sensor_window, 1, 1, "Thermal Zones:"); + wattroff(tz_sensor_window, A_BOLD); + + mvwprintw(tz_sensor_window, 1, TZ_LEFT_ALIGN, "%s", buffer); + /* fill trip points for each tzone */ + wattron(tz_sensor_window, A_BOLD); + mvwprintw(tz_sensor_window, 2, 1, "Trip Points:"); + wattroff(tz_sensor_window, A_BOLD); + + /* draw trip point from low to high for each tz */ + for (i = 0; i < ptdata.nr_tz_sensor; i++) { + int inst = ptdata.tzi[i].instance; + + mvwprintw(tz_sensor_window, 1, + TZ_LEFT_ALIGN+TZONE_RECORD_SIZE * inst, "%.9s%02d", + ptdata.tzi[i].type, ptdata.tzi[i].instance); + for (j = ptdata.tzi[i].nr_trip_pts - 1; j >= 0; j--) { + /* loop through all trip points */ + char type; + int tp_pos; + /* reverse the order here since trips are sorted + * in ascending order in terms of temperature. + */ + tp_pos = ptdata.tzi[i].nr_trip_pts - j - 1; + + type = trip_type_to_char(ptdata.tzi[i].tp[j].type); + mvwaddch(tz_sensor_window, 2, + inst * TZONE_RECORD_SIZE + TZ_LEFT_ALIGN + + tp_pos, type); + syslog(LOG_DEBUG, "draw tz %d tp %d ch:%c\n", + inst, j, type); + } + } + wborder(tz_sensor_window, 0, 0, 0, 0, 0, 0, 0, 0); + wattron(tz_sensor_window, A_BOLD); + mvwprintw(tz_sensor_window, 0, maxx/2 - sizeof(tz_title), tz_title); + wattroff(tz_sensor_window, A_BOLD); + wrefresh(tz_sensor_window); +} + +void disable_tui(void) +{ + tui_disabled = 1; +} |