diff options
| -rw-r--r-- | drivers/acpi/thermal.c | 10 | ||||
| -rw-r--r-- | drivers/thermal/Kconfig | 7 | ||||
| -rw-r--r-- | drivers/thermal/cpu_cooling.c | 4 | ||||
| -rw-r--r-- | drivers/thermal/intel_powerclamp.c | 29 | ||||
| -rw-r--r-- | drivers/thermal/thermal_core.c | 10 | ||||
| -rw-r--r-- | tools/Makefile | 15 | ||||
| -rw-r--r-- | tools/thermal/tmon/Makefile | 47 | ||||
| -rw-r--r-- | tools/thermal/tmon/README | 50 | ||||
| -rw-r--r-- | tools/thermal/tmon/pid.c | 131 | ||||
| -rw-r--r-- | tools/thermal/tmon/sysfs.c | 596 | ||||
| -rw-r--r-- | tools/thermal/tmon/tmon.8 | 142 | ||||
| -rw-r--r-- | tools/thermal/tmon/tmon.c | 352 | ||||
| -rw-r--r-- | tools/thermal/tmon/tmon.h | 204 | ||||
| -rw-r--r-- | tools/thermal/tmon/tui.c | 638 |
14 files changed, 2216 insertions, 19 deletions
diff --git a/drivers/acpi/thermal.c b/drivers/acpi/thermal.c index e600b5dbfcb6..0d9f46b5ae6d 100644 --- a/drivers/acpi/thermal.c +++ b/drivers/acpi/thermal.c | |||
| @@ -514,10 +514,9 @@ static void acpi_thermal_check(void *data) | |||
| 514 | { | 514 | { |
| 515 | struct acpi_thermal *tz = data; | 515 | struct acpi_thermal *tz = data; |
| 516 | 516 | ||
| 517 | if (!tz->tz_enabled) { | 517 | if (!tz->tz_enabled) |
| 518 | pr_warn("thermal zone is disabled \n"); | ||
| 519 | return; | 518 | return; |
| 520 | } | 519 | |
| 521 | thermal_zone_device_update(tz->thermal_zone); | 520 | thermal_zone_device_update(tz->thermal_zone); |
| 522 | } | 521 | } |
| 523 | 522 | ||
| @@ -569,9 +568,10 @@ static int thermal_set_mode(struct thermal_zone_device *thermal, | |||
| 569 | */ | 568 | */ |
| 570 | if (mode == THERMAL_DEVICE_ENABLED) | 569 | if (mode == THERMAL_DEVICE_ENABLED) |
| 571 | enable = 1; | 570 | enable = 1; |
| 572 | else if (mode == THERMAL_DEVICE_DISABLED) | 571 | else if (mode == THERMAL_DEVICE_DISABLED) { |
| 573 | enable = 0; | 572 | enable = 0; |
| 574 | else | 573 | pr_warn("thermal zone will be disabled\n"); |
| 574 | } else | ||
| 575 | return -EINVAL; | 575 | return -EINVAL; |
| 576 | 576 | ||
| 577 | if (enable != tz->tz_enabled) { | 577 | if (enable != tz->tz_enabled) { |
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index 5ef596765060..f35a1f75b15b 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig | |||
| @@ -56,7 +56,7 @@ config THERMAL_DEFAULT_GOV_USER_SPACE | |||
| 56 | select THERMAL_GOV_USER_SPACE | 56 | select THERMAL_GOV_USER_SPACE |
| 57 | help | 57 | help |
| 58 | Select this if you want to let the user space manage the | 58 | Select this if you want to let the user space manage the |
| 59 | lpatform thermals. | 59 | platform thermals. |
| 60 | 60 | ||
| 61 | endchoice | 61 | endchoice |
| 62 | 62 | ||
| @@ -69,6 +69,7 @@ config THERMAL_GOV_STEP_WISE | |||
| 69 | bool "Step_wise thermal governor" | 69 | bool "Step_wise thermal governor" |
| 70 | help | 70 | help |
| 71 | Enable this to manage platform thermals using a simple linear | 71 | Enable this to manage platform thermals using a simple linear |
| 72 | governor. | ||
| 72 | 73 | ||
| 73 | config THERMAL_GOV_USER_SPACE | 74 | config THERMAL_GOV_USER_SPACE |
| 74 | bool "User_space thermal governor" | 75 | bool "User_space thermal governor" |
| @@ -116,14 +117,14 @@ config SPEAR_THERMAL | |||
| 116 | depends on OF | 117 | depends on OF |
| 117 | help | 118 | help |
| 118 | Enable this to plug the SPEAr thermal sensor driver into the Linux | 119 | Enable this to plug the SPEAr thermal sensor driver into the Linux |
| 119 | thermal framework | 120 | thermal framework. |
| 120 | 121 | ||
| 121 | config RCAR_THERMAL | 122 | config RCAR_THERMAL |
| 122 | tristate "Renesas R-Car thermal driver" | 123 | tristate "Renesas R-Car thermal driver" |
| 123 | depends on ARCH_SHMOBILE | 124 | depends on ARCH_SHMOBILE |
| 124 | help | 125 | help |
| 125 | Enable this to plug the R-Car thermal sensor driver into the Linux | 126 | Enable this to plug the R-Car thermal sensor driver into the Linux |
| 126 | thermal framework | 127 | thermal framework. |
| 127 | 128 | ||
| 128 | config KIRKWOOD_THERMAL | 129 | config KIRKWOOD_THERMAL |
| 129 | tristate "Temperature sensor on Marvell Kirkwood SoCs" | 130 | tristate "Temperature sensor on Marvell Kirkwood SoCs" |
diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c index d17902886c3f..02a46f23d14c 100644 --- a/drivers/thermal/cpu_cooling.c +++ b/drivers/thermal/cpu_cooling.c | |||
| @@ -469,10 +469,10 @@ cpufreq_cooling_register(const struct cpumask *clip_cpus) | |||
| 469 | 469 | ||
| 470 | cool_dev = thermal_cooling_device_register(dev_name, cpufreq_dev, | 470 | cool_dev = thermal_cooling_device_register(dev_name, cpufreq_dev, |
| 471 | &cpufreq_cooling_ops); | 471 | &cpufreq_cooling_ops); |
| 472 | if (!cool_dev) { | 472 | if (IS_ERR(cool_dev)) { |
| 473 | release_idr(&cpufreq_idr, cpufreq_dev->id); | 473 | release_idr(&cpufreq_idr, cpufreq_dev->id); |
| 474 | kfree(cpufreq_dev); | 474 | kfree(cpufreq_dev); |
| 475 | return ERR_PTR(-EINVAL); | 475 | return cool_dev; |
| 476 | } | 476 | } |
| 477 | cpufreq_dev->cool_dev = cool_dev; | 477 | cpufreq_dev->cool_dev = cool_dev; |
| 478 | cpufreq_dev->cpufreq_state = 0; | 478 | cpufreq_dev->cpufreq_state = 0; |
diff --git a/drivers/thermal/intel_powerclamp.c b/drivers/thermal/intel_powerclamp.c index b40b37cd25e0..8f181b3f842b 100644 --- a/drivers/thermal/intel_powerclamp.c +++ b/drivers/thermal/intel_powerclamp.c | |||
| @@ -675,6 +675,11 @@ static const struct x86_cpu_id intel_powerclamp_ids[] = { | |||
| 675 | { X86_VENDOR_INTEL, 6, 0x2e}, | 675 | { X86_VENDOR_INTEL, 6, 0x2e}, |
| 676 | { X86_VENDOR_INTEL, 6, 0x2f}, | 676 | { X86_VENDOR_INTEL, 6, 0x2f}, |
| 677 | { X86_VENDOR_INTEL, 6, 0x3a}, | 677 | { X86_VENDOR_INTEL, 6, 0x3a}, |
| 678 | { X86_VENDOR_INTEL, 6, 0x3c}, | ||
| 679 | { X86_VENDOR_INTEL, 6, 0x3e}, | ||
| 680 | { X86_VENDOR_INTEL, 6, 0x3f}, | ||
| 681 | { X86_VENDOR_INTEL, 6, 0x45}, | ||
| 682 | { X86_VENDOR_INTEL, 6, 0x46}, | ||
| 678 | {} | 683 | {} |
| 679 | }; | 684 | }; |
| 680 | MODULE_DEVICE_TABLE(x86cpu, intel_powerclamp_ids); | 685 | MODULE_DEVICE_TABLE(x86cpu, intel_powerclamp_ids); |
| @@ -758,21 +763,39 @@ static int powerclamp_init(void) | |||
| 758 | /* probe cpu features and ids here */ | 763 | /* probe cpu features and ids here */ |
| 759 | retval = powerclamp_probe(); | 764 | retval = powerclamp_probe(); |
| 760 | if (retval) | 765 | if (retval) |
| 761 | return retval; | 766 | goto exit_free; |
| 767 | |||
| 762 | /* set default limit, maybe adjusted during runtime based on feedback */ | 768 | /* set default limit, maybe adjusted during runtime based on feedback */ |
| 763 | window_size = 2; | 769 | window_size = 2; |
| 764 | register_hotcpu_notifier(&powerclamp_cpu_notifier); | 770 | register_hotcpu_notifier(&powerclamp_cpu_notifier); |
| 771 | |||
| 765 | powerclamp_thread = alloc_percpu(struct task_struct *); | 772 | powerclamp_thread = alloc_percpu(struct task_struct *); |
| 773 | if (!powerclamp_thread) { | ||
| 774 | retval = -ENOMEM; | ||
| 775 | goto exit_unregister; | ||
| 776 | } | ||
| 777 | |||
| 766 | cooling_dev = thermal_cooling_device_register("intel_powerclamp", NULL, | 778 | cooling_dev = thermal_cooling_device_register("intel_powerclamp", NULL, |
| 767 | &powerclamp_cooling_ops); | 779 | &powerclamp_cooling_ops); |
| 768 | if (IS_ERR(cooling_dev)) | 780 | if (IS_ERR(cooling_dev)) { |
| 769 | return -ENODEV; | 781 | retval = -ENODEV; |
| 782 | goto exit_free_thread; | ||
| 783 | } | ||
| 770 | 784 | ||
| 771 | if (!duration) | 785 | if (!duration) |
| 772 | duration = jiffies_to_msecs(DEFAULT_DURATION_JIFFIES); | 786 | duration = jiffies_to_msecs(DEFAULT_DURATION_JIFFIES); |
| 787 | |||
| 773 | powerclamp_create_debug_files(); | 788 | powerclamp_create_debug_files(); |
| 774 | 789 | ||
| 775 | return 0; | 790 | return 0; |
| 791 | |||
| 792 | exit_free_thread: | ||
| 793 | free_percpu(powerclamp_thread); | ||
| 794 | exit_unregister: | ||
| 795 | unregister_hotcpu_notifier(&powerclamp_cpu_notifier); | ||
| 796 | exit_free: | ||
| 797 | kfree(cpu_clamping_mask); | ||
| 798 | return retval; | ||
| 776 | } | 799 | } |
| 777 | module_init(powerclamp_init); | 800 | module_init(powerclamp_init); |
| 778 | 801 | ||
diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c index 4962a6aaf295..03a567199bbe 100644 --- a/drivers/thermal/thermal_core.c +++ b/drivers/thermal/thermal_core.c | |||
| @@ -247,10 +247,11 @@ static void bind_cdev(struct thermal_cooling_device *cdev) | |||
| 247 | if (!pos->tzp && !pos->ops->bind) | 247 | if (!pos->tzp && !pos->ops->bind) |
| 248 | continue; | 248 | continue; |
| 249 | 249 | ||
| 250 | if (!pos->tzp && pos->ops->bind) { | 250 | if (pos->ops->bind) { |
| 251 | ret = pos->ops->bind(pos, cdev); | 251 | ret = pos->ops->bind(pos, cdev); |
| 252 | if (ret) | 252 | if (ret) |
| 253 | print_bind_err_msg(pos, cdev, ret); | 253 | print_bind_err_msg(pos, cdev, ret); |
| 254 | continue; | ||
| 254 | } | 255 | } |
| 255 | 256 | ||
| 256 | tzp = pos->tzp; | 257 | tzp = pos->tzp; |
| @@ -282,8 +283,8 @@ static void bind_tz(struct thermal_zone_device *tz) | |||
| 282 | 283 | ||
| 283 | mutex_lock(&thermal_list_lock); | 284 | mutex_lock(&thermal_list_lock); |
| 284 | 285 | ||
| 285 | /* If there is no platform data, try to use ops->bind */ | 286 | /* If there is ops->bind, try to use ops->bind */ |
| 286 | if (!tzp && tz->ops->bind) { | 287 | if (tz->ops->bind) { |
| 287 | list_for_each_entry(pos, &thermal_cdev_list, node) { | 288 | list_for_each_entry(pos, &thermal_cdev_list, node) { |
| 288 | ret = tz->ops->bind(tz, pos); | 289 | ret = tz->ops->bind(tz, pos); |
| 289 | if (ret) | 290 | if (ret) |
| @@ -1038,7 +1039,8 @@ static void thermal_release(struct device *dev) | |||
| 1038 | sizeof("thermal_zone") - 1)) { | 1039 | sizeof("thermal_zone") - 1)) { |
| 1039 | tz = to_thermal_zone(dev); | 1040 | tz = to_thermal_zone(dev); |
| 1040 | kfree(tz); | 1041 | kfree(tz); |
| 1041 | } else { | 1042 | } else if(!strncmp(dev_name(dev), "cooling_device", |
| 1043 | sizeof("cooling_device") - 1)){ | ||
| 1042 | cdev = to_cooling_device(dev); | 1044 | cdev = to_cooling_device(dev); |
| 1043 | kfree(cdev); | 1045 | kfree(cdev); |
| 1044 | } | 1046 | } |
diff --git a/tools/Makefile b/tools/Makefile index 41067f304215..a9b02008443c 100644 --- a/tools/Makefile +++ b/tools/Makefile | |||
| @@ -15,6 +15,7 @@ help: | |||
| 15 | @echo ' net - misc networking tools' | 15 | @echo ' net - misc networking tools' |
| 16 | @echo ' vm - misc vm tools' | 16 | @echo ' vm - misc vm tools' |
| 17 | @echo ' x86_energy_perf_policy - Intel energy policy tool' | 17 | @echo ' x86_energy_perf_policy - Intel energy policy tool' |
| 18 | @echo ' tmon - thermal monitoring and tuning tool' | ||
| 18 | @echo '' | 19 | @echo '' |
| 19 | @echo 'You can do:' | 20 | @echo 'You can do:' |
| 20 | @echo ' $$ make -C tools/ <tool>_install' | 21 | @echo ' $$ make -C tools/ <tool>_install' |
| @@ -50,6 +51,9 @@ selftests: FORCE | |||
| 50 | turbostat x86_energy_perf_policy: FORCE | 51 | turbostat x86_energy_perf_policy: FORCE |
| 51 | $(call descend,power/x86/$@) | 52 | $(call descend,power/x86/$@) |
| 52 | 53 | ||
| 54 | tmon: FORCE | ||
| 55 | $(call descend,thermal/$@) | ||
| 56 | |||
| 53 | cpupower_install: | 57 | cpupower_install: |
| 54 | $(call descend,power/$(@:_install=),install) | 58 | $(call descend,power/$(@:_install=),install) |
| 55 | 59 | ||
| @@ -62,9 +66,13 @@ selftests_install: | |||
| 62 | turbostat_install x86_energy_perf_policy_install: | 66 | turbostat_install x86_energy_perf_policy_install: |
| 63 | $(call descend,power/x86/$(@:_install=),install) | 67 | $(call descend,power/x86/$(@:_install=),install) |
| 64 | 68 | ||
| 69 | tmon_install: | ||
| 70 | $(call descend,thermal/$(@:_install=),install) | ||
| 71 | |||
| 65 | install: cgroup_install cpupower_install firewire_install lguest_install \ | 72 | install: cgroup_install cpupower_install firewire_install lguest_install \ |
| 66 | perf_install selftests_install turbostat_install usb_install \ | 73 | perf_install selftests_install turbostat_install usb_install \ |
| 67 | virtio_install vm_install net_install x86_energy_perf_policy_install | 74 | virtio_install vm_install net_install x86_energy_perf_policy_install \ |
| 75 | tmon | ||
| 68 | 76 | ||
| 69 | cpupower_clean: | 77 | cpupower_clean: |
| 70 | $(call descend,power/cpupower,clean) | 78 | $(call descend,power/cpupower,clean) |
| @@ -84,8 +92,11 @@ selftests_clean: | |||
| 84 | turbostat_clean x86_energy_perf_policy_clean: | 92 | turbostat_clean x86_energy_perf_policy_clean: |
| 85 | $(call descend,power/x86/$(@:_clean=),clean) | 93 | $(call descend,power/x86/$(@:_clean=),clean) |
| 86 | 94 | ||
| 95 | tmon_clean: | ||
| 96 | $(call descend,thermal/tmon,clean) | ||
| 97 | |||
| 87 | clean: cgroup_clean cpupower_clean firewire_clean lguest_clean perf_clean \ | 98 | clean: cgroup_clean cpupower_clean firewire_clean lguest_clean perf_clean \ |
| 88 | selftests_clean turbostat_clean usb_clean virtio_clean \ | 99 | selftests_clean turbostat_clean usb_clean virtio_clean \ |
| 89 | vm_clean net_clean x86_energy_perf_policy_clean | 100 | vm_clean net_clean x86_energy_perf_policy_clean tmon_clean |
| 90 | 101 | ||
| 91 | .PHONY: FORCE | 102 | .PHONY: FORCE |
diff --git a/tools/thermal/tmon/Makefile b/tools/thermal/tmon/Makefile new file mode 100644 index 000000000000..447321104ec0 --- /dev/null +++ b/tools/thermal/tmon/Makefile | |||
| @@ -0,0 +1,47 @@ | |||
| 1 | VERSION = 1.0 | ||
| 2 | |||
| 3 | BINDIR=usr/bin | ||
| 4 | WARNFLAGS=-Wall -Wshadow -W -Wformat -Wimplicit-function-declaration -Wimplicit-int | ||
| 5 | CFLAGS= -O1 ${WARNFLAGS} -fstack-protector | ||
| 6 | CC=gcc | ||
| 7 | |||
| 8 | CFLAGS+=-D VERSION=\"$(VERSION)\" | ||
| 9 | LDFLAGS+= | ||
| 10 | TARGET=tmon | ||
| 11 | |||
| 12 | INSTALL_PROGRAM=install -m 755 -p | ||
| 13 | DEL_FILE=rm -f | ||
| 14 | |||
| 15 | INSTALL_CONFIGFILE=install -m 644 -p | ||
| 16 | CONFIG_FILE= | ||
| 17 | CONFIG_PATH= | ||
| 18 | |||
| 19 | |||
| 20 | OBJS = tmon.o tui.o sysfs.o pid.o | ||
| 21 | OBJS += | ||
| 22 | |||
| 23 | tmon: $(OBJS) Makefile tmon.h | ||
| 24 | $(CC) ${CFLAGS} $(LDFLAGS) $(OBJS) -o $(TARGET) -lm -lpanel -lncursesw -lpthread | ||
| 25 | |||
| 26 | valgrind: tmon | ||
| 27 | sudo valgrind -v --track-origins=yes --tool=memcheck --leak-check=yes --show-reachable=yes --num-callers=20 --track-fds=yes ./$(TARGET) 1> /dev/null | ||
| 28 | |||
| 29 | install: | ||
| 30 | - mkdir -p $(INSTALL_ROOT)/$(BINDIR) | ||
| 31 | - $(INSTALL_PROGRAM) "$(TARGET)" "$(INSTALL_ROOT)/$(BINDIR)/$(TARGET)" | ||
| 32 | - mkdir -p $(INSTALL_ROOT)/$(CONFIG_PATH) | ||
| 33 | - $(INSTALL_CONFIGFILE) "$(CONFIG_FILE)" "$(INSTALL_ROOT)/$(CONFIG_PATH)" | ||
| 34 | |||
| 35 | uninstall: | ||
| 36 | $(DEL_FILE) "$(INSTALL_ROOT)/$(BINDIR)/$(TARGET)" | ||
| 37 | $(CONFIG_FILE) "$(CONFIG_PATH)" | ||
| 38 | |||
| 39 | |||
| 40 | clean: | ||
| 41 | find . -name "*.o" | xargs $(DEL_FILE) | ||
| 42 | rm -f $(TARGET) | ||
| 43 | |||
| 44 | dist: | ||
| 45 | git tag v$(VERSION) | ||
| 46 | git archive --format=tar --prefix="$(TARGET)-$(VERSION)/" v$(VERSION) | \ | ||
| 47 | gzip > $(TARGET)-$(VERSION).tar.gz | ||
diff --git a/tools/thermal/tmon/README b/tools/thermal/tmon/README new file mode 100644 index 000000000000..457949897a8e --- /dev/null +++ b/tools/thermal/tmon/README | |||
| @@ -0,0 +1,50 @@ | |||
| 1 | TMON - A Monitoring and Testing Tool for Linux kernel thermal subsystem | ||
| 2 | |||
| 3 | Why TMON? | ||
| 4 | ========== | ||
| 5 | Increasingly, Linux is running on thermally constrained devices. The simple | ||
| 6 | thermal relationship between processor and fan has become past for modern | ||
| 7 | computers. | ||
| 8 | |||
| 9 | As hardware vendors cope with the thermal constraints on their products, more | ||
| 10 | and more sensors are added, new cooling capabilities are introduced. The | ||
| 11 | complexity of the thermal relationship can grow exponentially among cooling | ||
| 12 | devices, zones, sensors, and trip points. They can also change dynamically. | ||
| 13 | |||
| 14 | To expose such relationship to the userspace, Linux generic thermal layer | ||
| 15 | introduced sysfs entry at /sys/class/thermal with a matrix of symbolic | ||
| 16 | links, trip point bindings, and device instances. To traverse such | ||
| 17 | matrix by hand is not a trivial task. Testing is also difficult in that | ||
| 18 | thermal conditions are often exception cases that hard to reach in | ||
| 19 | normal operations. | ||
| 20 | |||
| 21 | TMON is conceived as a tool to help visualize, tune, and test the | ||
| 22 | complex thermal subsystem. | ||
| 23 | |||
| 24 | Files | ||
| 25 | ===== | ||
| 26 | tmon.c : main function for set up and configurations. | ||
| 27 | tui.c : handles ncurses based user interface | ||
| 28 | sysfs.c : access to the generic thermal sysfs | ||
| 29 | pid.c : a proportional-integral-derivative (PID) controller | ||
| 30 | that can be used for thermal relationship training. | ||
| 31 | |||
| 32 | Requirements | ||
| 33 | ============ | ||
| 34 | Depends on ncurses | ||
| 35 | |||
| 36 | Build | ||
| 37 | ========= | ||
| 38 | $ make | ||
| 39 | $ sudo ./tmon -h | ||
| 40 | Usage: tmon [OPTION...] | ||
| 41 | -c, --control cooling device in control | ||
| 42 | -d, --daemon run as daemon, no TUI | ||
| 43 | -l, --log log data to /var/tmp/tmon.log | ||
| 44 | -h, --help show this help message | ||
| 45 | -t, --time-interval set time interval for sampling | ||
| 46 | -v, --version show version | ||
| 47 | -g, --debug debug message in syslog | ||
| 48 | |||
| 49 | 1. For monitoring only: | ||
| 50 | $ sudo ./tmon | ||
diff --git a/tools/thermal/tmon/pid.c b/tools/thermal/tmon/pid.c new file mode 100644 index 000000000000..fd7e9e9d6f4a --- /dev/null +++ b/tools/thermal/tmon/pid.c | |||
| @@ -0,0 +1,131 @@ | |||
| 1 | /* | ||
| 2 | * pid.c PID controller for testing cooling devices | ||
| 3 | * | ||
| 4 | * | ||
| 5 | * | ||
| 6 | * Copyright (C) 2012 Intel Corporation. All rights reserved. | ||
| 7 | * | ||
| 8 | * This program is free software; you can redistribute it and/or | ||
| 9 | * modify it under the terms of the GNU General Public License version | ||
| 10 | * 2 or later as published by the Free Software Foundation. | ||
| 11 | * | ||
| 12 | * This program is distributed in the hope that it will be useful, | ||
| 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 15 | * GNU General Public License for more details. | ||
| 16 | * | ||
| 17 | * Author Name Jacob Pan <jacob.jun.pan@linux.intel.com> | ||
| 18 | * | ||
| 19 | */ | ||
| 20 | |||
| 21 | #include <unistd.h> | ||
| 22 | #include <stdio.h> | ||
| 23 | #include <stdlib.h> | ||
| 24 | #include <string.h> | ||
| 25 | #include <stdint.h> | ||
| 26 | #include <sys/types.h> | ||
| 27 | #include <dirent.h> | ||
| 28 | #include <libintl.h> | ||
| 29 | #include <ctype.h> | ||
| 30 | #include <assert.h> | ||
| 31 | #include <time.h> | ||
| 32 | #include <limits.h> | ||
| 33 | #include <math.h> | ||
| 34 | #include <sys/stat.h> | ||
| 35 | #include <syslog.h> | ||
| 36 | |||
| 37 | #include "tmon.h" | ||
| 38 | |||
| 39 | /************************************************************************** | ||
| 40 | * PID (Proportional-Integral-Derivative) controller is commonly used in | ||
| 41 | * linear control system, consider the the process. | ||
| 42 | * G(s) = U(s)/E(s) | ||
| 43 | * kp = proportional gain | ||
| 44 | * ki = integral gain | ||
| 45 | * kd = derivative gain | ||
| 46 | * Ts | ||
| 47 | * We use type C Alan Bradley equation which takes set point off the | ||
| 48 | * output dependency in P and D term. | ||
| 49 | * | ||
| 50 | * y[k] = y[k-1] - kp*(x[k] - x[k-1]) + Ki*Ts*e[k] - Kd*(x[k] | ||
| 51 | * - 2*x[k-1]+x[k-2])/Ts | ||
| 52 | * | ||
| 53 | * | ||
| 54 | ***********************************************************************/ | ||
| 55 | struct pid_params p_param; | ||
| 56 | /* cached data from previous loop */ | ||
| 57 | static double xk_1, xk_2; /* input temperature x[k-#] */ | ||
| 58 | |||
| 59 | /* | ||
| 60 | * TODO: make PID parameters tuned automatically, | ||
| 61 | * 1. use CPU burn to produce open loop unit step response | ||
| 62 | * 2. calculate PID based on Ziegler-Nichols rule | ||
| 63 | * | ||
| 64 | * add a flag for tuning PID | ||
| 65 | */ | ||
| 66 | int init_thermal_controller(void) | ||
| 67 | { | ||
| 68 | int ret = 0; | ||
| 69 | |||
| 70 | /* init pid params */ | ||
| 71 | p_param.ts = ticktime; | ||
| 72 | /* TODO: get it from TUI tuning tab */ | ||
| 73 | p_param.kp = .36; | ||
| 74 | p_param.ki = 5.0; | ||
| 75 | p_param.kd = 0.19; | ||
| 76 | |||
| 77 | p_param.t_target = target_temp_user; | ||
| 78 | |||
| 79 | return ret; | ||
| 80 | } | ||
| 81 | |||
| 82 | void controller_reset(void) | ||
| 83 | { | ||
| 84 | /* TODO: relax control data when not over thermal limit */ | ||
| 85 | syslog(LOG_DEBUG, "TC inactive, relax p-state\n"); | ||
| 86 | p_param.y_k = 0.0; | ||
| 87 | xk_1 = 0.0; | ||
| 88 | xk_2 = 0.0; | ||
| 89 | set_ctrl_state(0); | ||
| 90 | } | ||
| 91 | |||
| 92 | /* To be called at time interval Ts. Type C PID controller. | ||
| 93 | * y[k] = y[k-1] - kp*(x[k] - x[k-1]) + Ki*Ts*e[k] - Kd*(x[k] | ||
| 94 | * - 2*x[k-1]+x[k-2])/Ts | ||
| 95 | * TODO: add low pass filter for D term | ||
| 96 | */ | ||
| 97 | #define GUARD_BAND (2) | ||
| 98 | void controller_handler(const double xk, double *yk) | ||
| 99 | { | ||
| 100 | double ek; | ||
| 101 | double p_term, i_term, d_term; | ||
| 102 | |||
| 103 | ek = p_param.t_target - xk; /* error */ | ||
| 104 | if (ek >= 3.0) { | ||
| 105 | syslog(LOG_DEBUG, "PID: %3.1f Below set point %3.1f, stop\n", | ||
| 106 | xk, p_param.t_target); | ||
| 107 | controller_reset(); | ||
| 108 | *yk = 0.0; | ||
| 109 | return; | ||
| 110 | } | ||
| 111 | /* compute intermediate PID terms */ | ||
| 112 | p_term = -p_param.kp * (xk - xk_1); | ||
| 113 | i_term = p_param.kp * p_param.ki * p_param.ts * ek; | ||
| 114 | d_term = -p_param.kp * p_param.kd * (xk - 2 * xk_1 + xk_2) / p_param.ts; | ||
| 115 | /* compute output */ | ||
| 116 | *yk += p_term + i_term + d_term; | ||
| 117 | /* update sample data */ | ||
| 118 | xk_1 = xk; | ||
| 119 | xk_2 = xk_1; | ||
| 120 | |||
| 121 | /* clamp output adjustment range */ | ||
| 122 | if (*yk < -LIMIT_HIGH) | ||
| 123 | *yk = -LIMIT_HIGH; | ||
| 124 | else if (*yk > -LIMIT_LOW) | ||
| 125 | *yk = -LIMIT_LOW; | ||
| 126 | |||
| 127 | p_param.y_k = *yk; | ||
| 128 | |||
| 129 | set_ctrl_state(lround(fabs(p_param.y_k))); | ||
| 130 | |||
| 131 | } | ||
diff --git a/tools/thermal/tmon/sysfs.c b/tools/thermal/tmon/sysfs.c new file mode 100644 index 000000000000..dfe454855cd2 --- /dev/null +++ b/tools/thermal/tmon/sysfs.c | |||
| @@ -0,0 +1,596 @@ | |||
| 1 | /* | ||
| 2 | * sysfs.c sysfs ABI access functions for TMON program | ||
| 3 | * | ||
| 4 | * Copyright (C) 2013 Intel Corporation. All rights reserved. | ||
| 5 | * | ||
| 6 | * This program is free software; you can redistribute it and/or | ||
| 7 | * modify it under the terms of the GNU General Public License version | ||
| 8 | * 2 or later as published by the Free Software Foundation. | ||
| 9 | * | ||
| 10 | * This program is distributed in the hope that it will be useful, | ||
| 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 13 | * GNU General Public License for more details. | ||
| 14 | * | ||
| 15 | * Author: Jacob Pan <jacob.jun.pan@linux.intel.com> | ||
| 16 | * | ||
| 17 | */ | ||
| 18 | #include <unistd.h> | ||
| 19 | #include <stdio.h> | ||
| 20 | #include <stdlib.h> | ||
| 21 | #include <string.h> | ||
| 22 | #include <stdint.h> | ||
| 23 | #include <dirent.h> | ||
| 24 | #include <libintl.h> | ||
| 25 | #include <ctype.h> | ||
| 26 | #include <time.h> | ||
| 27 | #include <syslog.h> | ||
| 28 | #include <sys/time.h> | ||
| 29 | #include <errno.h> | ||
| 30 | |||
| 31 | #include "tmon.h" | ||
| 32 | |||
| 33 | struct tmon_platform_data ptdata; | ||
| 34 | const char *trip_type_name[] = { | ||
| 35 | "critical", | ||
| 36 | "hot", | ||
| 37 | "passive", | ||
| 38 | "active", | ||
| 39 | }; | ||
| 40 | |||
| 41 | int sysfs_set_ulong(char *path, char *filename, unsigned long val) | ||
| 42 | { | ||
| 43 | FILE *fd; | ||
| 44 | int ret = -1; | ||
| 45 | char filepath[256]; | ||
| 46 | |||
| 47 | snprintf(filepath, 256, "%s/%s", path, filename); | ||
| 48 | |||
| 49 | fd = fopen(filepath, "w"); | ||
| 50 | if (!fd) { | ||
| 51 | syslog(LOG_ERR, "Err: open %s: %s\n", __func__, filepath); | ||
| 52 | return ret; | ||
| 53 | } | ||
| 54 | ret = fprintf(fd, "%lu", val); | ||
| 55 | fclose(fd); | ||
| 56 | |||
| 57 | return 0; | ||
| 58 | } | ||
| 59 | |||
| 60 | /* history of thermal data, used for control algo */ | ||
| 61 | #define NR_THERMAL_RECORDS 3 | ||
| 62 | struct thermal_data_record trec[NR_THERMAL_RECORDS]; | ||
| 63 | int cur_thermal_record; /* index to the trec array */ | ||
| 64 | |||
| 65 | static int sysfs_get_ulong(char *path, char *filename, unsigned long *p_ulong) | ||
| 66 | { | ||
| 67 | FILE *fd; | ||
| 68 | int ret = -1; | ||
| 69 | char filepath[256]; | ||
| 70 | |||
| 71 | snprintf(filepath, 256, "%s/%s", path, filename); | ||
| 72 | |||
| 73 | fd = fopen(filepath, "r"); | ||
| 74 | if (!fd) { | ||
| 75 | syslog(LOG_ERR, "Err: open %s: %s\n", __func__, filepath); | ||
| 76 | return ret; | ||
| 77 | } | ||
| 78 | ret = fscanf(fd, "%lu", p_ulong); | ||
| 79 | fclose(fd); | ||
| 80 | |||
| 81 | return 0; | ||
| 82 | } | ||
| 83 | |||
| 84 | static int sysfs_get_string(char *path, char *filename, char *str) | ||
| 85 | { | ||
| 86 | FILE *fd; | ||
| 87 | int ret = -1; | ||
| 88 | char filepath[256]; | ||
| 89 | |||
| 90 | snprintf(filepath, 256, "%s/%s", path, filename); | ||
| 91 | |||
| 92 | fd = fopen(filepath, "r"); | ||
| 93 | if (!fd) { | ||
| 94 | syslog(LOG_ERR, "Err: open %s: %s\n", __func__, filepath); | ||
| 95 | return ret; | ||
| 96 | } | ||
| 97 | ret = fscanf(fd, "%256s", str); | ||
| 98 | fclose(fd); | ||
| 99 | |||
| 100 | return ret; | ||
| 101 | } | ||
| 102 | |||
| 103 | /* get states of the cooling device instance */ | ||
| 104 | static int probe_cdev(struct cdev_info *cdi, char *path) | ||
| 105 | { | ||
| 106 | sysfs_get_string(path, "type", cdi->type); | ||
| 107 | sysfs_get_ulong(path, "max_state", &cdi->max_state); | ||
| 108 | sysfs_get_ulong(path, "cur_state", &cdi->cur_state); | ||
| 109 | |||
| 110 | syslog(LOG_INFO, "%s: %s: type %s, max %lu, curr %lu inst %d\n", | ||
| 111 | __func__, path, | ||
| 112 | cdi->type, cdi->max_state, cdi->cur_state, cdi->instance); | ||
| 113 | |||
| 114 | return 0; | ||
| 115 | } | ||
| 116 | |||
| 117 | static int str_to_trip_type(char *name) | ||
| 118 | { | ||
| 119 | int i; | ||
| 120 | |||
| 121 | for (i = 0; i < NR_THERMAL_TRIP_TYPE; i++) { | ||
| 122 | if (!strcmp(name, trip_type_name[i])) | ||
| 123 | return i; | ||
| 124 | } | ||
| 125 | |||
| 126 | return -ENOENT; | ||
| 127 | } | ||
| 128 | |||
| 129 | /* scan and fill in trip point info for a thermal zone and trip point id */ | ||
| 130 | static int get_trip_point_data(char *tz_path, int tzid, int tpid) | ||
| 131 | { | ||
| 132 | char filename[256]; | ||
| 133 | char temp_str[256]; | ||
| 134 | int trip_type; | ||
| 135 | |||
| 136 | if (tpid >= MAX_NR_TRIP) | ||
| 137 | return -EINVAL; | ||
| 138 | /* check trip point type */ | ||
| 139 | snprintf(filename, sizeof(filename), "trip_point_%d_type", tpid); | ||
| 140 | sysfs_get_string(tz_path, filename, temp_str); | ||
| 141 | trip_type = str_to_trip_type(temp_str); | ||
| 142 | if (trip_type < 0) { | ||
| 143 | syslog(LOG_ERR, "%s:%s no matching type\n", __func__, temp_str); | ||
| 144 | return -ENOENT; | ||
| 145 | } | ||
| 146 | ptdata.tzi[tzid].tp[tpid].type = trip_type; | ||
| 147 | syslog(LOG_INFO, "%s:tz:%d tp:%d:type:%s type id %d\n", __func__, tzid, | ||
| 148 | tpid, temp_str, trip_type); | ||
| 149 | |||
| 150 | /* TODO: check attribute */ | ||
| 151 | |||
| 152 | return 0; | ||
| 153 | } | ||
| 154 | |||
| 155 | /* return instance id for file format such as trip_point_4_temp */ | ||
| 156 | static int get_instance_id(char *name, int pos, int skip) | ||
| 157 | { | ||
| 158 | char *ch; | ||
| 159 | int i = 0; | ||
| 160 | |||
| 161 | ch = strtok(name, "_"); | ||
| 162 | while (ch != NULL) { | ||
| 163 | ++i; | ||
| 164 | syslog(LOG_INFO, "%s:%s:%s:%d", __func__, name, ch, i); | ||
| 165 | ch = strtok(NULL, "_"); | ||
| 166 | if (pos == i) | ||
| 167 | return atol(ch + skip); | ||
| 168 | } | ||
| 169 | |||
| 170 | return -1; | ||
| 171 | } | ||
| 172 | |||
| 173 | /* Find trip point info of a thermal zone */ | ||
| 174 | static int find_tzone_tp(char *tz_name, char *d_name, struct tz_info *tzi, | ||
| 175 | int tz_id) | ||
| 176 | { | ||
| 177 | int tp_id; | ||
| 178 | unsigned long temp_ulong; | ||
| 179 | |||
| 180 | if (strstr(d_name, "trip_point") && | ||
| 181 | strstr(d_name, "temp")) { | ||
| 182 | /* check if trip point temp is non-zero | ||
| 183 | * ignore 0/invalid trip points | ||
| 184 | */ | ||
| 185 | sysfs_get_ulong(tz_name, d_name, &temp_ulong); | ||
| 186 | if (temp_ulong < MAX_TEMP_KC) { | ||
| 187 | tzi->nr_trip_pts++; | ||
| 188 | /* found a valid trip point */ | ||
| 189 | tp_id = get_instance_id(d_name, 2, 0); | ||
| 190 | syslog(LOG_DEBUG, "tzone %s trip %d temp %lu tpnode %s", | ||
| 191 | tz_name, tp_id, temp_ulong, d_name); | ||
| 192 | if (tp_id < 0 || tp_id >= MAX_NR_TRIP) { | ||
| 193 | syslog(LOG_ERR, "Failed to find TP inst %s\n", | ||
| 194 | d_name); | ||
| 195 | return -1; | ||
| 196 | } | ||
| 197 | get_trip_point_data(tz_name, tz_id, tp_id); | ||
| 198 | tzi->tp[tp_id].temp = temp_ulong; | ||
| 199 | } | ||
| 200 | } | ||
| 201 | |||
| 202 | return 0; | ||
| 203 | } | ||
| 204 | |||
| 205 | /* check cooling devices for binding info. */ | ||
| 206 | static int find_tzone_cdev(struct dirent *nl, char *tz_name, | ||
| 207 | struct tz_info *tzi, int tz_id, int cid) | ||
| 208 | { | ||
| 209 | unsigned long trip_instance = 0; | ||
| 210 | char cdev_name_linked[256]; | ||
| 211 | char cdev_name[256]; | ||
| 212 | char cdev_trip_name[256]; | ||
| 213 | int cdev_id; | ||
| 214 | |||
| 215 | if (nl->d_type == DT_LNK) { | ||
| 216 | syslog(LOG_DEBUG, "TZ%d: cdev: %s cid %d\n", tz_id, nl->d_name, | ||
| 217 | cid); | ||
| 218 | tzi->nr_cdev++; | ||
| 219 | if (tzi->nr_cdev > ptdata.nr_cooling_dev) { | ||
| 220 | syslog(LOG_ERR, "Err: Too many cdev? %d\n", | ||
| 221 | tzi->nr_cdev); | ||
| 222 | return -EINVAL; | ||
| 223 | } | ||
| 224 | /* find the link to real cooling device record binding */ | ||
| 225 | snprintf(cdev_name, 256, "%s/%s", tz_name, nl->d_name); | ||
| 226 | memset(cdev_name_linked, 0, sizeof(cdev_name_linked)); | ||
| 227 | if (readlink(cdev_name, cdev_name_linked, | ||
| 228 | sizeof(cdev_name_linked) - 1) != -1) { | ||
| 229 | cdev_id = get_instance_id(cdev_name_linked, 1, | ||
| 230 | sizeof("device") - 1); | ||
| 231 | syslog(LOG_DEBUG, "cdev %s linked to %s : %d\n", | ||
| 232 | cdev_name, cdev_name_linked, cdev_id); | ||
| 233 | tzi->cdev_binding |= (1 << cdev_id); | ||
| 234 | |||
| 235 | /* find the trip point in which the cdev is binded to | ||
| 236 | * in this tzone | ||
| 237 | */ | ||
| 238 | snprintf(cdev_trip_name, 256, "%s%s", nl->d_name, | ||
| 239 | "_trip_point"); | ||
| 240 | sysfs_get_ulong(tz_name, cdev_trip_name, | ||
| 241 | &trip_instance); | ||
| 242 | /* validate trip point range, e.g. trip could return -1 | ||
| 243 | * when passive is enabled | ||
| 244 | */ | ||
| 245 | if (trip_instance > MAX_NR_TRIP) | ||
| 246 | trip_instance = 0; | ||
| 247 | tzi->trip_binding[cdev_id] |= 1 << trip_instance; | ||
| 248 | syslog(LOG_DEBUG, "cdev %s -> trip:%lu: 0x%lx %d\n", | ||
| 249 | cdev_name, trip_instance, | ||
| 250 | tzi->trip_binding[cdev_id], | ||
| 251 | cdev_id); | ||
| 252 | |||
| 253 | |||
| 254 | } | ||
| 255 | return 0; | ||
| 256 | } | ||
| 257 | |||
| 258 | return -ENODEV; | ||
| 259 | } | ||
| 260 | |||
| 261 | |||
| 262 | |||
| 263 | /***************************************************************************** | ||
| 264 | * Before calling scan_tzones, thermal sysfs must be probed to determine | ||
| 265 | * the number of thermal zones and cooling devices. | ||
| 266 | * We loop through each thermal zone and fill in tz_info struct, i.e. | ||
| 267 | * ptdata.tzi[] | ||
| 268 | root@jacob-chiefriver:~# tree -d /sys/class/thermal/thermal_zone0 | ||
| 269 | /sys/class/thermal/thermal_zone0 | ||
| 270 | |-- cdev0 -> ../cooling_device4 | ||
| 271 | |-- cdev1 -> ../cooling_device3 | ||
| 272 | |-- cdev10 -> ../cooling_device7 | ||
| 273 | |-- cdev11 -> ../cooling_device6 | ||
| 274 | |-- cdev12 -> ../cooling_device5 | ||
| 275 | |-- cdev2 -> ../cooling_device2 | ||
| 276 | |-- cdev3 -> ../cooling_device1 | ||
| 277 | |-- cdev4 -> ../cooling_device0 | ||
| 278 | |-- cdev5 -> ../cooling_device12 | ||
| 279 | |-- cdev6 -> ../cooling_device11 | ||
| 280 | |-- cdev7 -> ../cooling_device10 | ||
| 281 | |-- cdev8 -> ../cooling_device9 | ||
| 282 | |-- cdev9 -> ../cooling_device8 | ||
| 283 | |-- device -> ../../../LNXSYSTM:00/device:62/LNXTHERM:00 | ||
| 284 | |-- power | ||
| 285 | `-- subsystem -> ../../../../class/thermal | ||
| 286 | *****************************************************************************/ | ||
| 287 | static int scan_tzones(void) | ||
| 288 | { | ||
| 289 | DIR *dir; | ||
| 290 | struct dirent **namelist; | ||
| 291 | char tz_name[256]; | ||
| 292 | int i, j, n, k = 0; | ||
| 293 | |||
| 294 | if (!ptdata.nr_tz_sensor) | ||
| 295 | return -1; | ||
| 296 | |||
| 297 | for (i = 0; i <= ptdata.max_tz_instance; i++) { | ||
| 298 | memset(tz_name, 0, sizeof(tz_name)); | ||
| 299 | snprintf(tz_name, 256, "%s/%s%d", THERMAL_SYSFS, TZONE, i); | ||
| 300 | |||
| 301 | dir = opendir(tz_name); | ||
| 302 | if (!dir) { | ||
| 303 | syslog(LOG_INFO, "Thermal zone %s skipped\n", tz_name); | ||
| 304 | continue; | ||
| 305 | } | ||
| 306 | /* keep track of valid tzones */ | ||
| 307 | n = scandir(tz_name, &namelist, 0, alphasort); | ||
| 308 | if (n < 0) | ||
| 309 | syslog(LOG_ERR, "scandir failed in %s", tz_name); | ||
| 310 | else { | ||
| 311 | sysfs_get_string(tz_name, "type", ptdata.tzi[k].type); | ||
| 312 | ptdata.tzi[k].instance = i; | ||
| 313 | /* detect trip points and cdev attached to this tzone */ | ||
| 314 | j = 0; /* index for cdev */ | ||
| 315 | ptdata.tzi[k].nr_cdev = 0; | ||
| 316 | ptdata.tzi[k].nr_trip_pts = 0; | ||
| 317 | while (n--) { | ||
| 318 | char *temp_str; | ||
| 319 | |||
| 320 | if (find_tzone_tp(tz_name, namelist[n]->d_name, | ||
| 321 | &ptdata.tzi[k], k)) | ||
| 322 | break; | ||
| 323 | temp_str = strstr(namelist[n]->d_name, "cdev"); | ||
| 324 | if (!temp_str) { | ||
| 325 | free(namelist[n]); | ||
| 326 | continue; | ||
| 327 | } | ||
| 328 | if (!find_tzone_cdev(namelist[n], tz_name, | ||
| 329 | &ptdata.tzi[k], i, j)) | ||
| 330 | j++; /* increment cdev index */ | ||
| 331 | free(namelist[n]); | ||
| 332 | } | ||
| 333 | free(namelist); | ||
| 334 | } | ||
| 335 | /*TODO: reverse trip points */ | ||
| 336 | closedir(dir); | ||
| 337 | syslog(LOG_INFO, "TZ %d has %d cdev\n", i, | ||
| 338 | ptdata.tzi[k].nr_cdev); | ||
| 339 | k++; | ||
| 340 | } | ||
| 341 | |||
| 342 | return 0; | ||
| 343 | } | ||
| 344 | |||
| 345 | static int scan_cdevs(void) | ||
| 346 | { | ||
| 347 | DIR *dir; | ||
| 348 | struct dirent **namelist; | ||
| 349 | char cdev_name[256]; | ||
| 350 | int i, n, k = 0; | ||
| 351 | |||
| 352 | if (!ptdata.nr_cooling_dev) { | ||
| 353 | fprintf(stderr, "No cooling devices found\n"); | ||
| 354 | return 0; | ||
| 355 | } | ||
| 356 | for (i = 0; i <= ptdata.max_cdev_instance; i++) { | ||
| 357 | memset(cdev_name, 0, sizeof(cdev_name)); | ||
| 358 | snprintf(cdev_name, 256, "%s/%s%d", THERMAL_SYSFS, CDEV, i); | ||
| 359 | |||
| 360 | dir = opendir(cdev_name); | ||
| 361 | if (!dir) { | ||
| 362 | syslog(LOG_INFO, "Cooling dev %s skipped\n", cdev_name); | ||
| 363 | /* there is a gap in cooling device id, check again | ||
| 364 | * for the same index. | ||
| 365 | */ | ||
| 366 | continue; | ||
| 367 | } | ||
| 368 | |||
| 369 | n = scandir(cdev_name, &namelist, 0, alphasort); | ||
| 370 | if (n < 0) | ||
| 371 | syslog(LOG_ERR, "scandir failed in %s", cdev_name); | ||
| 372 | else { | ||
| 373 | sysfs_get_string(cdev_name, "type", ptdata.cdi[k].type); | ||
| 374 | ptdata.cdi[k].instance = i; | ||
| 375 | if (strstr(ptdata.cdi[k].type, ctrl_cdev)) { | ||
| 376 | ptdata.cdi[k].flag |= CDEV_FLAG_IN_CONTROL; | ||
| 377 | syslog(LOG_DEBUG, "control cdev id %d\n", i); | ||
| 378 | } | ||
| 379 | while (n--) | ||
| 380 | free(namelist[n]); | ||
| 381 | free(namelist); | ||
| 382 | } | ||
| 383 | closedir(dir); | ||
| 384 | k++; | ||
| 385 | } | ||
| 386 | return 0; | ||
| 387 | } | ||
| 388 | |||
| 389 | |||
| 390 | int probe_thermal_sysfs(void) | ||
| 391 | { | ||
| 392 | DIR *dir; | ||
| 393 | struct dirent **namelist; | ||
| 394 | int n; | ||
| 395 | |||
| 396 | dir = opendir(THERMAL_SYSFS); | ||
| 397 | if (!dir) { | ||
| 398 | fprintf(stderr, "\nNo thermal sysfs, exit\n"); | ||
| 399 | return -1; | ||
| 400 | } | ||
| 401 | n = scandir(THERMAL_SYSFS, &namelist, 0, alphasort); | ||
| 402 | if (n < 0) | ||
| 403 | syslog(LOG_ERR, "scandir failed in thermal sysfs"); | ||
| 404 | else { | ||
| 405 | /* detect number of thermal zones and cooling devices */ | ||
| 406 | while (n--) { | ||
| 407 | int inst; | ||
| 408 | |||
| 409 | if (strstr(namelist[n]->d_name, CDEV)) { | ||
| 410 | inst = get_instance_id(namelist[n]->d_name, 1, | ||
| 411 | sizeof("device") - 1); | ||
| 412 | /* keep track of the max cooling device since | ||
| 413 | * there may be gaps. | ||
| 414 | */ | ||
| 415 | if (inst > ptdata.max_cdev_instance) | ||
| 416 | ptdata.max_cdev_instance = inst; | ||
| 417 | |||
| 418 | syslog(LOG_DEBUG, "found cdev: %s %d %d\n", | ||
| 419 | namelist[n]->d_name, | ||
| 420 | ptdata.nr_cooling_dev, | ||
| 421 | ptdata.max_cdev_instance); | ||
| 422 | ptdata.nr_cooling_dev++; | ||
| 423 | } else if (strstr(namelist[n]->d_name, TZONE)) { | ||
| 424 | inst = get_instance_id(namelist[n]->d_name, 1, | ||
| 425 | sizeof("zone") - 1); | ||
| 426 | if (inst > ptdata.max_tz_instance) | ||
| 427 | ptdata.max_tz_instance = inst; | ||
| 428 | |||
| 429 | syslog(LOG_DEBUG, "found tzone: %s %d %d\n", | ||
| 430 | namelist[n]->d_name, | ||
| 431 | ptdata.nr_tz_sensor, | ||
| 432 | ptdata.max_tz_instance); | ||
| 433 | ptdata.nr_tz_sensor++; | ||
| 434 | } | ||
| 435 | free(namelist[n]); | ||
| 436 | } | ||
| 437 | free(namelist); | ||
| 438 | } | ||
| 439 | syslog(LOG_INFO, "found %d tzone(s), %d cdev(s), target zone %d\n", | ||
| 440 | ptdata.nr_tz_sensor, ptdata.nr_cooling_dev, | ||
| 441 | target_thermal_zone); | ||
| 442 | closedir(dir); | ||
| 443 | |||
| 444 | if (!ptdata.nr_tz_sensor) { | ||
| 445 | fprintf(stderr, "\nNo thermal zones found, exit\n\n"); | ||
| 446 | return -1; | ||
| 447 | } | ||
| 448 | |||
| 449 | ptdata.tzi = calloc(sizeof(struct tz_info), ptdata.max_tz_instance+1); | ||
| 450 | if (!ptdata.tzi) { | ||
| 451 | fprintf(stderr, "Err: allocate tz_info\n"); | ||
| 452 | return -1; | ||
| 453 | } | ||
| 454 | |||
| 455 | /* we still show thermal zone information if there is no cdev */ | ||
| 456 | if (ptdata.nr_cooling_dev) { | ||
| 457 | ptdata.cdi = calloc(sizeof(struct cdev_info), | ||
| 458 | ptdata.max_cdev_instance + 1); | ||
| 459 | if (!ptdata.cdi) { | ||
| 460 | free(ptdata.tzi); | ||
| 461 | fprintf(stderr, "Err: allocate cdev_info\n"); | ||
| 462 | return -1; | ||
| 463 | } | ||
| 464 | } | ||
| 465 | |||
| 466 | /* now probe tzones */ | ||
| 467 | if (scan_tzones()) | ||
| 468 | return -1; | ||
| 469 | if (scan_cdevs()) | ||
| 470 | return -1; | ||
| 471 | return 0; | ||
| 472 | } | ||
| 473 | |||
| 474 | /* convert sysfs zone instance to zone array index */ | ||
| 475 | int zone_instance_to_index(int zone_inst) | ||
| 476 | { | ||
| 477 | int i; | ||
| 478 | |||
| 479 | for (i = 0; i < ptdata.nr_tz_sensor; i++) | ||
| 480 | if (ptdata.tzi[i].instance == zone_inst) | ||
| 481 | return i; | ||
| 482 | return -ENOENT; | ||
| 483 | } | ||
| 484 | |||
| 485 | /* read temperature of all thermal zones */ | ||
| 486 | int update_thermal_data() | ||
| 487 | { | ||
| 488 | int i; | ||
| 489 | char tz_name[256]; | ||
| 490 | static unsigned long samples; | ||
| 491 | |||
| 492 | if (!ptdata.nr_tz_sensor) { | ||
| 493 | syslog(LOG_ERR, "No thermal zones found!\n"); | ||
| 494 | return -1; | ||
| 495 | } | ||
| 496 | |||
| 497 | /* circular buffer for keeping historic data */ | ||
| 498 | if (cur_thermal_record >= NR_THERMAL_RECORDS) | ||
| 499 | cur_thermal_record = 0; | ||
| 500 | gettimeofday(&trec[cur_thermal_record].tv, NULL); | ||
| 501 | if (tmon_log) { | ||
| 502 | fprintf(tmon_log, "%lu ", ++samples); | ||
| 503 | fprintf(tmon_log, "%3.1f ", p_param.t_target); | ||
| 504 | } | ||
| 505 | for (i = 0; i < ptdata.nr_tz_sensor; i++) { | ||
| 506 | memset(tz_name, 0, sizeof(tz_name)); | ||
| 507 | snprintf(tz_name, 256, "%s/%s%d", THERMAL_SYSFS, TZONE, | ||
| 508 | ptdata.tzi[i].instance); | ||
| 509 | sysfs_get_ulong(tz_name, "temp", | ||
| 510 | &trec[cur_thermal_record].temp[i]); | ||
| 511 | if (tmon_log) | ||
| 512 | fprintf(tmon_log, "%lu ", | ||
| 513 | trec[cur_thermal_record].temp[i]/1000); | ||
| 514 | } | ||
| 515 | for (i = 0; i < ptdata.nr_cooling_dev; i++) { | ||
| 516 | char cdev_name[256]; | ||
| 517 | unsigned long val; | ||
| 518 | |||
| 519 | snprintf(cdev_name, 256, "%s/%s%d", THERMAL_SYSFS, CDEV, | ||
| 520 | ptdata.cdi[i].instance); | ||
| 521 | probe_cdev(&ptdata.cdi[i], cdev_name); | ||
| 522 | val = ptdata.cdi[i].cur_state; | ||
| 523 | if (val > 1000000) | ||
| 524 | val = 0; | ||
| 525 | if (tmon_log) | ||
| 526 | fprintf(tmon_log, "%lu ", val); | ||
| 527 | } | ||
| 528 | |||
| 529 | if (tmon_log) { | ||
| 530 | fprintf(tmon_log, "\n"); | ||
| 531 | fflush(tmon_log); | ||
| 532 | } | ||
| 533 | |||
| 534 | return 0; | ||
| 535 | } | ||
| 536 | |||
| 537 | void set_ctrl_state(unsigned long state) | ||
| 538 | { | ||
| 539 | char ctrl_cdev_path[256]; | ||
| 540 | int i; | ||
| 541 | unsigned long cdev_state; | ||
| 542 | |||
| 543 | if (no_control) | ||
| 544 | return; | ||
| 545 | /* set all ctrl cdev to the same state */ | ||
| 546 | for (i = 0; i < ptdata.nr_cooling_dev; i++) { | ||
| 547 | if (ptdata.cdi[i].flag & CDEV_FLAG_IN_CONTROL) { | ||
| 548 | if (ptdata.cdi[i].max_state < 10) { | ||
| 549 | strcpy(ctrl_cdev, "None."); | ||
| 550 | return; | ||
| 551 | } | ||
| 552 | /* scale to percentage of max_state */ | ||
| 553 | cdev_state = state * ptdata.cdi[i].max_state/100; | ||
| 554 | syslog(LOG_DEBUG, | ||
| 555 | "ctrl cdev %d set state %lu scaled to %lu\n", | ||
| 556 | ptdata.cdi[i].instance, state, cdev_state); | ||
| 557 | snprintf(ctrl_cdev_path, 256, "%s/%s%d", THERMAL_SYSFS, | ||
| 558 | CDEV, ptdata.cdi[i].instance); | ||
| 559 | syslog(LOG_DEBUG, "ctrl cdev path %s", ctrl_cdev_path); | ||
| 560 | sysfs_set_ulong(ctrl_cdev_path, "cur_state", | ||
| 561 | cdev_state); | ||
| 562 | } | ||
| 563 | } | ||
| 564 | } | ||
| 565 | |||
| 566 | void get_ctrl_state(unsigned long *state) | ||
| 567 | { | ||
| 568 | char ctrl_cdev_path[256]; | ||
| 569 | int ctrl_cdev_id = -1; | ||
| 570 | int i; | ||
| 571 | |||
| 572 | /* TODO: take average of all ctrl types. also consider change based on | ||
| 573 | * uevent. Take the first reading for now. | ||
| 574 | */ | ||
| 575 | for (i = 0; i < ptdata.nr_cooling_dev; i++) { | ||
| 576 | if (ptdata.cdi[i].flag & CDEV_FLAG_IN_CONTROL) { | ||
| 577 | ctrl_cdev_id = ptdata.cdi[i].instance; | ||
| 578 | syslog(LOG_INFO, "ctrl cdev %d get state\n", | ||
| 579 | ptdata.cdi[i].instance); | ||
| 580 | break; | ||
| 581 | } | ||
| 582 | } | ||
| 583 | if (ctrl_cdev_id == -1) { | ||
| 584 | *state = 0; | ||
| 585 | return; | ||
| 586 | } | ||
| 587 | snprintf(ctrl_cdev_path, 256, "%s/%s%d", THERMAL_SYSFS, | ||
| 588 | CDEV, ctrl_cdev_id); | ||
| 589 | sysfs_get_ulong(ctrl_cdev_path, "cur_state", state); | ||
| 590 | } | ||
| 591 | |||
| 592 | void free_thermal_data(void) | ||
| 593 | { | ||
| 594 | free(ptdata.tzi); | ||
| 595 | free(ptdata.cdi); | ||
| 596 | } | ||
diff --git a/tools/thermal/tmon/tmon.8 b/tools/thermal/tmon/tmon.8 new file mode 100644 index 000000000000..0be727cb9892 --- /dev/null +++ b/tools/thermal/tmon/tmon.8 | |||
| @@ -0,0 +1,142 @@ | |||
| 1 | .TH TMON 8 | ||
| 2 | .SH NAME | ||
| 3 | \fBtmon\fP - A monitoring and testing tool for Linux kernel thermal subsystem | ||
| 4 | |||
| 5 | .SH SYNOPSIS | ||
| 6 | .ft B | ||
| 7 | .B tmon | ||
| 8 | .RB [ Options ] | ||
| 9 | .br | ||
| 10 | .SH DESCRIPTION | ||
| 11 | \fBtmon \fP can be used to visualize thermal relationship and | ||
| 12 | real-time thermal data; tune | ||
| 13 | and test cooling devices and sensors; collect thermal data for offline | ||
| 14 | analysis and plot. \fBtmon\fP must be run as root in order to control device | ||
| 15 | states via sysfs. | ||
| 16 | .PP | ||
| 17 | \fBFunctions\fP | ||
| 18 | .PP | ||
| 19 | .nf | ||
| 20 | 1. Thermal relationships: | ||
| 21 | - show thermal zone information | ||
| 22 | - show cooling device information | ||
| 23 | - show trip point binding within each thermal zone | ||
| 24 | - show trip point and cooling device instance bindings | ||
| 25 | .PP | ||
| 26 | 2. Real time data display | ||
| 27 | - show temperature of all thermal zones w.r.t. its trip points and types | ||
| 28 | - show states of all cooling devices | ||
| 29 | .PP | ||
| 30 | 3. Thermal relationship learning and device tuning | ||
| 31 | - with a built-in Proportional Integral Derivative (\fBPID\fP) | ||
| 32 | controller, user can pair a cooling device to a thermal sensor for | ||
| 33 | testing the effectiveness and learn about the thermal distance between the two | ||
| 34 | - allow manual control of cooling device states and target temperature | ||
| 35 | .PP | ||
| 36 | 4. Data logging in /var/tmp/tmon.log | ||
| 37 | - contains thermal configuration data, i.e. cooling device, thermal | ||
| 38 | zones, and trip points. Can be used for data collection in remote | ||
| 39 | debugging. | ||
| 40 | - log real-time thermal data into space separated format that can be | ||
| 41 | directly consumed by plotting tools such as Rscript. | ||
| 42 | |||
| 43 | .SS Options | ||
| 44 | .PP | ||
| 45 | The \fB-c --control\fP option sets a cooling device type to control temperature | ||
| 46 | of a thermal zone | ||
| 47 | .PP | ||
| 48 | The \fB-d --daemon\fP option runs \fBtmon \fP as daemon without user interface | ||
| 49 | .PP | ||
| 50 | The \fB-g --debug\fP option allow debug messages to be stored in syslog | ||
| 51 | .PP | ||
| 52 | The \fB-h --help\fP option shows help message | ||
| 53 | .PP | ||
| 54 | The \fB-l --log\fP option write data to /var/tmp/tmon.log | ||
| 55 | .PP | ||
| 56 | The \fB-t --time-interval\fP option sets the polling interval in seconds | ||
| 57 | .PP | ||
| 58 | The \fB-v --version\fP option shows the version of \fBtmon \fP | ||
| 59 | .PP | ||
| 60 | The \fB-z --zone\fP option sets the target therma zone instance to be controlled | ||
| 61 | .PP | ||
| 62 | |||
| 63 | .SH FIELD DESCRIPTIONS | ||
| 64 | .nf | ||
| 65 | .PP | ||
| 66 | \fBP \fP passive cooling trip point type | ||
| 67 | \fBA \fP active cooling trip point type (fan) | ||
| 68 | \fBC \fP critical trip point type | ||
| 69 | \fBA \fP hot trip point type | ||
| 70 | \fBkp \fP proportional gain of \fBPID\fP controller | ||
| 71 | \fBki \fP integral gain of \fBPID\fP controller | ||
| 72 | \fBkd \fP derivative gain of \fBPID\fP controller | ||
| 73 | |||
| 74 | .SH REQUIREMENT | ||
| 75 | Build depends on ncurses | ||
| 76 | .PP | ||
| 77 | Runtime depends on window size large enough to show the number of | ||
| 78 | devices found on the system. | ||
| 79 | |||
| 80 | .PP | ||
| 81 | |||
| 82 | .SH INTERACTIVE COMMANDS | ||
| 83 | .pp | ||
| 84 | .nf | ||
| 85 | \fBCtrl-C, q/Q\fP stops \fBtmon\fP | ||
| 86 | \fBTAB\fP shows tuning pop up panel, choose a letter to modify | ||
| 87 | |||
| 88 | .SH EXAMPLES | ||
| 89 | Without any parameters, tmon is in monitoring only mode and refresh | ||
| 90 | screen every 1 second. | ||
| 91 | .PP | ||
| 92 | 1. For monitoring only: | ||
| 93 | .nf | ||
| 94 | $ sudo ./tmon | ||
| 95 | |||
| 96 | 2. Use Processor cooling device to control thermal zone 0 at default 65C. | ||
| 97 | $ sudo ./tmon -c Processor -z 0 | ||
| 98 | |||
| 99 | 3. Use intel_powerclamp(idle injection) cooling device to control thermal zone 1 | ||
| 100 | $ sudo ./tmon -c intel_powerclamp -z 1 | ||
| 101 | |||
| 102 | 4. Turn on debug and collect data log at /var/tmp/tmon.log | ||
| 103 | $ sudo ./tmon -g -l | ||
| 104 | |||
| 105 | For example, the log below shows PID controller was adjusting current states | ||
| 106 | for all cooling devices with "Processor" type such that thermal zone 0 | ||
| 107 | can stay below 65 dC. | ||
| 108 | |||
| 109 | #---------- THERMAL DATA LOG STARTED ----------- | ||
| 110 | Samples TargetTemp acpitz0 acpitz1 Fan0 Fan1 Fan2 Fan3 Fan4 Fan5 | ||
| 111 | Fan6 Fan7 Fan8 Fan9 Processor10 Processor11 Processor12 Processor13 | ||
| 112 | 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 | ||
| 113 | 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 | ||
| 114 | 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 | ||
| 115 | 5 65.0 52 52 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 0 | ||
| 116 | 6 65.0 53 65 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 0 | ||
| 117 | 7 65.0 68 70 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 0 | ||
| 118 | 8 65.0 68 68 0 0 0 0 0 0 0 0 0 0 5 5 5 5 6 0 | ||
| 119 | 9 65.0 68 68 0 0 0 0 0 0 0 0 0 0 6 6 6 6 6 0 | ||
| 120 | 10 65.0 67 67 0 0 0 0 0 0 0 0 0 0 7 7 7 7 6 0 | ||
| 121 | 11 65.0 67 67 0 0 0 0 0 0 0 0 0 0 8 8 8 8 6 0 | ||
| 122 | 12 65.0 67 67 0 0 0 0 0 0 0 0 0 0 8 8 8 8 6 0 | ||
| 123 | 13 65.0 67 67 0 0 0 0 0 0 0 0 0 0 9 9 9 9 6 0 | ||
| 124 | 14 65.0 66 66 0 0 0 0 0 0 0 0 0 0 10 10 10 10 6 0 | ||
| 125 | 15 65.0 66 67 0 0 0 0 0 0 0 0 0 0 10 10 10 10 6 0 | ||
| 126 | 16 65.0 66 66 0 0 0 0 0 0 0 0 0 0 11 11 11 11 6 0 | ||
| 127 | 17 65.0 66 66 0 0 0 0 0 0 0 0 0 0 11 11 11 11 6 0 | ||
| 128 | 18 65.0 64 61 0 0 0 0 0 0 0 0 0 0 11 11 11 11 6 0 | ||
| 129 | 19 65.0 60 59 0 0 0 0 0 0 0 0 0 0 12 12 12 12 6 0 | ||
| 130 | |||
| 131 | Data can be read directly into an array by an example R-script below: | ||
| 132 | |||
| 133 | #!/usr/bin/Rscript | ||
| 134 | tdata <- read.table("/var/tmp/tmon.log", header=T, comment.char="#") | ||
| 135 | attach(tdata) | ||
| 136 | jpeg("tmon.jpg") | ||
| 137 | X11() | ||
| 138 | g_range <- range(0, intel_powerclamp15, TargetTemp, acpitz0) | ||
| 139 | plot( Samples, intel_powerclamp15, col="blue", ylim=g_range, axes=FALSE, ann=FALSE) | ||
| 140 | par(new=TRUE) | ||
| 141 | lines(TargetTemp, type="o", pch=22, lty=2, col="red") | ||
| 142 | dev.off() | ||
diff --git a/tools/thermal/tmon/tmon.c b/tools/thermal/tmon/tmon.c new file mode 100644 index 000000000000..b30f531173e4 --- /dev/null +++ b/tools/thermal/tmon/tmon.c | |||
| @@ -0,0 +1,352 @@ | |||
| 1 | /* | ||
| 2 | * tmon.c Thermal Monitor (TMON) main function and entry point | ||
| 3 | * | ||
| 4 | * Copyright (C) 2012 Intel Corporation. All rights reserved. | ||
| 5 | * | ||
| 6 | * This program is free software; you can redistribute it and/or | ||
| 7 | * modify it under the terms of the GNU General Public License version | ||
| 8 | * 2 or later as published by the Free Software Foundation. | ||
| 9 | * | ||
| 10 | * This program is distributed in the hope that it will be useful, | ||
| 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 13 | * GNU General Public License for more details. | ||
| 14 | * | ||
| 15 | * Author: Jacob Pan <jacob.jun.pan@linux.intel.com> | ||
| 16 | * | ||
| 17 | */ | ||
| 18 | |||
| 19 | #include <getopt.h> | ||
| 20 | #include <unistd.h> | ||
| 21 | #include <stdio.h> | ||
| 22 | #include <stdlib.h> | ||
| 23 | #include <string.h> | ||
| 24 | #include <sys/types.h> | ||
| 25 | #include <sys/stat.h> | ||
| 26 | #include <ncurses.h> | ||
| 27 | #include <ctype.h> | ||
| 28 | #include <time.h> | ||
| 29 | #include <signal.h> | ||
| 30 | #include <limits.h> | ||
| 31 | #include <sys/time.h> | ||
| 32 | #include <pthread.h> | ||
| 33 | #include <math.h> | ||
| 34 | #include <stdarg.h> | ||
| 35 | #include <syslog.h> | ||
| 36 | |||
| 37 | #include "tmon.h" | ||
| 38 | |||
| 39 | unsigned long ticktime = 1; /* seconds */ | ||
| 40 | unsigned long no_control = 1; /* monitoring only or use cooling device for | ||
| 41 | * temperature control. | ||
| 42 | */ | ||
| 43 | double time_elapsed = 0.0; | ||
| 44 | unsigned long target_temp_user = 65; /* can be select by tui later */ | ||
| 45 | int dialogue_on; | ||
| 46 | int tmon_exit; | ||
| 47 | static short daemon_mode; | ||
| 48 | static int logging; /* for recording thermal data to a file */ | ||
| 49 | static int debug_on; | ||
| 50 | FILE *tmon_log; | ||
| 51 | /*cooling device used for the PID controller */ | ||
| 52 | char ctrl_cdev[CDEV_NAME_SIZE] = "None"; | ||
| 53 | int target_thermal_zone; /* user selected target zone instance */ | ||
| 54 | static void start_daemon_mode(void); | ||
| 55 | |||
| 56 | pthread_t event_tid; | ||
| 57 | pthread_mutex_t input_lock; | ||
| 58 | void usage() | ||
| 59 | { | ||
| 60 | printf("Usage: tmon [OPTION...]\n"); | ||
| 61 | printf(" -c, --control cooling device in control\n"); | ||
| 62 | printf(" -d, --daemon run as daemon, no TUI\n"); | ||
| 63 | printf(" -g, --debug debug message in syslog\n"); | ||
| 64 | printf(" -h, --help show this help message\n"); | ||
| 65 | printf(" -l, --log log data to /var/tmp/tmon.log\n"); | ||
| 66 | printf(" -t, --time-interval sampling time interval, > 1 sec.\n"); | ||
| 67 | printf(" -v, --version show version\n"); | ||
| 68 | printf(" -z, --zone target thermal zone id\n"); | ||
| 69 | |||
| 70 | exit(0); | ||
| 71 | } | ||
| 72 | |||
| 73 | void version() | ||
| 74 | { | ||
| 75 | printf("TMON version %s\n", VERSION); | ||
| 76 | exit(EXIT_SUCCESS); | ||
| 77 | } | ||
| 78 | |||
| 79 | static void tmon_cleanup(void) | ||
| 80 | { | ||
| 81 | |||
| 82 | syslog(LOG_INFO, "TMON exit cleanup\n"); | ||
| 83 | fflush(stdout); | ||
| 84 | refresh(); | ||
| 85 | if (tmon_log) | ||
| 86 | fclose(tmon_log); | ||
| 87 | if (event_tid) { | ||
| 88 | pthread_mutex_lock(&input_lock); | ||
| 89 | pthread_cancel(event_tid); | ||
| 90 | pthread_mutex_unlock(&input_lock); | ||
| 91 | pthread_mutex_destroy(&input_lock); | ||
| 92 | } | ||
| 93 | closelog(); | ||
| 94 | /* relax control knobs, undo throttling */ | ||
| 95 | set_ctrl_state(0); | ||
| 96 | |||
| 97 | keypad(stdscr, FALSE); | ||
| 98 | echo(); | ||
| 99 | nocbreak(); | ||
| 100 | close_windows(); | ||
| 101 | endwin(); | ||
| 102 | free_thermal_data(); | ||
| 103 | |||
| 104 | exit(1); | ||
| 105 | } | ||
| 106 | |||
| 107 | |||
| 108 | static void tmon_sig_handler(int sig) | ||
| 109 | { | ||
| 110 | syslog(LOG_INFO, "TMON caught signal %d\n", sig); | ||
| 111 | refresh(); | ||
| 112 | switch (sig) { | ||
| 113 | case SIGTERM: | ||
| 114 | printf("sigterm, exit and clean up\n"); | ||
| 115 | fflush(stdout); | ||
| 116 | break; | ||
| 117 | case SIGKILL: | ||
| 118 | printf("sigkill, exit and clean up\n"); | ||
| 119 | fflush(stdout); | ||
| 120 | break; | ||
| 121 | case SIGINT: | ||
| 122 | printf("ctrl-c, exit and clean up\n"); | ||
| 123 | fflush(stdout); | ||
| 124 | break; | ||
| 125 | default: | ||
| 126 | break; | ||
| 127 | } | ||
| 128 | tmon_exit = true; | ||
| 129 | } | ||
| 130 | |||
| 131 | |||
| 132 | static void start_syslog(void) | ||
| 133 | { | ||
| 134 | if (debug_on) | ||
| 135 | setlogmask(LOG_UPTO(LOG_DEBUG)); | ||
| 136 | else | ||
| 137 | setlogmask(LOG_UPTO(LOG_ERR)); | ||
| 138 | openlog("tmon.log", LOG_CONS | LOG_PID | LOG_NDELAY, LOG_LOCAL0); | ||
| 139 | syslog(LOG_NOTICE, "TMON started by User %d", getuid()); | ||
| 140 | } | ||
| 141 | |||
| 142 | static void prepare_logging(void) | ||
| 143 | { | ||
| 144 | int i; | ||
| 145 | |||
| 146 | if (!logging) | ||
| 147 | return; | ||
| 148 | /* open local data log file */ | ||
| 149 | tmon_log = fopen(TMON_LOG_FILE, "w+"); | ||
| 150 | if (!tmon_log) { | ||
| 151 | syslog(LOG_ERR, "failed to open log file %s\n", TMON_LOG_FILE); | ||
| 152 | return; | ||
| 153 | } | ||
| 154 | |||
| 155 | fprintf(tmon_log, "#----------- THERMAL SYSTEM CONFIG -------------\n"); | ||
| 156 | for (i = 0; i < ptdata.nr_tz_sensor; i++) { | ||
| 157 | char binding_str[33]; /* size of long + 1 */ | ||
| 158 | int j; | ||
| 159 | |||
| 160 | memset(binding_str, 0, sizeof(binding_str)); | ||
| 161 | for (j = 0; j < 32; j++) | ||
| 162 | binding_str[j] = (ptdata.tzi[i].cdev_binding & 1<<j) ? | ||
| 163 | '1' : '0'; | ||
| 164 | |||
| 165 | fprintf(tmon_log, "#thermal zone %s%02d cdevs binding: %32s\n", | ||
| 166 | ptdata.tzi[i].type, | ||
| 167 | ptdata.tzi[i].instance, | ||
| 168 | binding_str); | ||
| 169 | for (j = 0; j < ptdata.tzi[i].nr_trip_pts; j++) { | ||
| 170 | fprintf(tmon_log, "#\tTP%02d type:%s, temp:%lu\n", j, | ||
| 171 | trip_type_name[ptdata.tzi[i].tp[j].type], | ||
| 172 | ptdata.tzi[i].tp[j].temp); | ||
| 173 | } | ||
| 174 | |||
| 175 | } | ||
| 176 | |||
| 177 | for (i = 0; i < ptdata.nr_cooling_dev; i++) | ||
| 178 | fprintf(tmon_log, "#cooling devices%02d: %s\n", | ||
| 179 | i, ptdata.cdi[i].type); | ||
| 180 | |||
| 181 | fprintf(tmon_log, "#---------- THERMAL DATA LOG STARTED -----------\n"); | ||
| 182 | fprintf(tmon_log, "Samples TargetTemp "); | ||
| 183 | for (i = 0; i < ptdata.nr_tz_sensor; i++) { | ||
| 184 | fprintf(tmon_log, "%s%d ", ptdata.tzi[i].type, | ||
| 185 | ptdata.tzi[i].instance); | ||
| 186 | } | ||
| 187 | for (i = 0; i < ptdata.nr_cooling_dev; i++) | ||
| 188 | fprintf(tmon_log, "%s%d ", ptdata.cdi[i].type, | ||
| 189 | ptdata.cdi[i].instance); | ||
| 190 | |||
| 191 | fprintf(tmon_log, "\n"); | ||
| 192 | } | ||
| 193 | |||
| 194 | static struct option opts[] = { | ||
| 195 | { "control", 1, NULL, 'c' }, | ||
| 196 | { "daemon", 0, NULL, 'd' }, | ||
| 197 | { "time-interval", 1, NULL, 't' }, | ||
| 198 | { "log", 0, NULL, 'l' }, | ||
| 199 | { "help", 0, NULL, 'h' }, | ||
| 200 | { "version", 0, NULL, 'v' }, | ||
| 201 | { "debug", 0, NULL, 'g' }, | ||
| 202 | { 0, 0, NULL, 0 } | ||
| 203 | }; | ||
| 204 | |||
| 205 | |||
| 206 | int main(int argc, char **argv) | ||
| 207 | { | ||
| 208 | int err = 0; | ||
| 209 | int id2 = 0, c; | ||
| 210 | double yk = 0.0; /* controller output */ | ||
| 211 | int target_tz_index; | ||
| 212 | |||
| 213 | if (geteuid() != 0) { | ||
| 214 | printf("TMON needs to be run as root\n"); | ||
| 215 | exit(EXIT_FAILURE); | ||
| 216 | } | ||
| 217 | |||
| 218 | while ((c = getopt_long(argc, argv, "c:dlht:vgz:", opts, &id2)) != -1) { | ||
| 219 | switch (c) { | ||
| 220 | case 'c': | ||
| 221 | no_control = 0; | ||
| 222 | strncpy(ctrl_cdev, optarg, CDEV_NAME_SIZE); | ||
| 223 | break; | ||
| 224 | case 'd': | ||
| 225 | start_daemon_mode(); | ||
| 226 | printf("Run TMON in daemon mode\n"); | ||
| 227 | break; | ||
| 228 | case 't': | ||
| 229 | ticktime = strtod(optarg, NULL); | ||
| 230 | if (ticktime < 1) | ||
| 231 | ticktime = 1; | ||
| 232 | break; | ||
| 233 | case 'l': | ||
| 234 | printf("Logging data to /var/tmp/tmon.log\n"); | ||
| 235 | logging = 1; | ||
| 236 | break; | ||
| 237 | case 'h': | ||
| 238 | usage(); | ||
| 239 | break; | ||
| 240 | case 'v': | ||
| 241 | version(); | ||
| 242 | break; | ||
| 243 | case 'g': | ||
| 244 | debug_on = 1; | ||
| 245 | break; | ||
| 246 | case 'z': | ||
| 247 | target_thermal_zone = strtod(optarg, NULL); | ||
| 248 | break; | ||
| 249 | default: | ||
| 250 | break; | ||
| 251 | } | ||
| 252 | } | ||
| 253 | if (pthread_mutex_init(&input_lock, NULL) != 0) { | ||
| 254 | fprintf(stderr, "\n mutex init failed, exit\n"); | ||
| 255 | return 1; | ||
| 256 | } | ||
| 257 | start_syslog(); | ||
| 258 | if (signal(SIGINT, tmon_sig_handler) == SIG_ERR) | ||
| 259 | syslog(LOG_DEBUG, "Cannot handle SIGINT\n"); | ||
| 260 | if (signal(SIGTERM, tmon_sig_handler) == SIG_ERR) | ||
| 261 | syslog(LOG_DEBUG, "Cannot handle SIGINT\n"); | ||
| 262 | |||
| 263 | if (probe_thermal_sysfs()) { | ||
| 264 | pthread_mutex_destroy(&input_lock); | ||
| 265 | closelog(); | ||
| 266 | return -1; | ||
| 267 | } | ||
| 268 | initialize_curses(); | ||
| 269 | setup_windows(); | ||
| 270 | signal(SIGWINCH, resize_handler); | ||
| 271 | show_title_bar(); | ||
| 272 | show_sensors_w(); | ||
| 273 | show_cooling_device(); | ||
| 274 | update_thermal_data(); | ||
| 275 | show_data_w(); | ||
| 276 | prepare_logging(); | ||
| 277 | init_thermal_controller(); | ||
| 278 | |||
| 279 | nodelay(stdscr, TRUE); | ||
| 280 | err = pthread_create(&event_tid, NULL, &handle_tui_events, NULL); | ||
| 281 | if (err != 0) { | ||
| 282 | printf("\ncan't create thread :[%s]", strerror(err)); | ||
| 283 | tmon_cleanup(); | ||
| 284 | exit(EXIT_FAILURE); | ||
| 285 | } | ||
| 286 | |||
| 287 | /* validate range of user selected target zone, default to the first | ||
| 288 | * instance if out of range | ||
| 289 | */ | ||
| 290 | target_tz_index = zone_instance_to_index(target_thermal_zone); | ||
| 291 | if (target_tz_index < 0) { | ||
| 292 | target_thermal_zone = ptdata.tzi[0].instance; | ||
| 293 | syslog(LOG_ERR, "target zone is not found, default to %d\n", | ||
| 294 | target_thermal_zone); | ||
| 295 | } | ||
| 296 | while (1) { | ||
| 297 | sleep(ticktime); | ||
| 298 | show_title_bar(); | ||
| 299 | show_sensors_w(); | ||
| 300 | update_thermal_data(); | ||
| 301 | if (!dialogue_on) { | ||
| 302 | show_data_w(); | ||
| 303 | show_cooling_device(); | ||
| 304 | } | ||
| 305 | cur_thermal_record++; | ||
| 306 | time_elapsed += ticktime; | ||
| 307 | controller_handler(trec[0].temp[target_tz_index] / 1000, | ||
| 308 | &yk); | ||
| 309 | trec[0].pid_out_pct = yk; | ||
| 310 | if (!dialogue_on) | ||
| 311 | show_control_w(); | ||
| 312 | if (tmon_exit) | ||
| 313 | break; | ||
| 314 | } | ||
| 315 | tmon_cleanup(); | ||
| 316 | return 0; | ||
| 317 | } | ||
| 318 | |||
| 319 | static void start_daemon_mode() | ||
| 320 | { | ||
| 321 | daemon_mode = 1; | ||
| 322 | /* fork */ | ||
| 323 | pid_t sid, pid = fork(); | ||
| 324 | if (pid < 0) { | ||
| 325 | exit(EXIT_FAILURE); | ||
| 326 | } else if (pid > 0) | ||
| 327 | /* kill parent */ | ||
| 328 | exit(EXIT_SUCCESS); | ||
| 329 | |||
| 330 | /* disable TUI, it may not be necessary, but saves some resource */ | ||
| 331 | disable_tui(); | ||
| 332 | |||
| 333 | /* change the file mode mask */ | ||
| 334 | umask(0); | ||
| 335 | |||
| 336 | /* new SID for the daemon process */ | ||
| 337 | sid = setsid(); | ||
| 338 | if (sid < 0) | ||
| 339 | exit(EXIT_FAILURE); | ||
| 340 | |||
| 341 | /* change working directory */ | ||
| 342 | if ((chdir("/")) < 0) | ||
| 343 | exit(EXIT_FAILURE); | ||
| 344 | |||
| 345 | |||
| 346 | sleep(10); | ||
| 347 | |||
| 348 | close(STDIN_FILENO); | ||
| 349 | close(STDOUT_FILENO); | ||
| 350 | close(STDERR_FILENO); | ||
| 351 | |||
| 352 | } | ||
diff --git a/tools/thermal/tmon/tmon.h b/tools/thermal/tmon/tmon.h new file mode 100644 index 000000000000..9e3c49c547ac --- /dev/null +++ b/tools/thermal/tmon/tmon.h | |||
| @@ -0,0 +1,204 @@ | |||
| 1 | /* | ||
| 2 | * tmon.h contains data structures and constants used by TMON | ||
| 3 | * | ||
| 4 | * Copyright (C) 2012 Intel Corporation. All rights reserved. | ||
| 5 | * | ||
| 6 | * This program is free software; you can redistribute it and/or | ||
| 7 | * modify it under the terms of the GNU General Public License version | ||
| 8 | * 2 or later as published by the Free Software Foundation. | ||
| 9 | * | ||
| 10 | * This program is distributed in the hope that it will be useful, | ||
| 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 13 | * GNU General Public License for more details. | ||
| 14 | * | ||
| 15 | * Author Name Jacob Pan <jacob.jun.pan@linux.intel.com> | ||
| 16 | * | ||
| 17 | */ | ||
| 18 | |||
| 19 | #ifndef TMON_H | ||
| 20 | #define TMON_H | ||
| 21 | |||
| 22 | #define MAX_DISP_TEMP 125 | ||
| 23 | #define MAX_CTRL_TEMP 105 | ||
| 24 | #define MIN_CTRL_TEMP 40 | ||
| 25 | #define MAX_NR_TZONE 16 | ||
| 26 | #define MAX_NR_CDEV 32 | ||
| 27 | #define MAX_NR_TRIP 16 | ||
| 28 | #define MAX_NR_CDEV_TRIP 12 /* number of cooling devices that can bind | ||
| 29 | * to a thermal zone trip. | ||
| 30 | */ | ||
| 31 | #define MAX_TEMP_KC 140000 | ||
| 32 | /* starting char position to draw sensor data, such as tz names | ||
| 33 | * trip point list, etc. | ||
| 34 | */ | ||
| 35 | #define DATA_LEFT_ALIGN 10 | ||
| 36 | #define NR_LINES_TZDATA 1 | ||
| 37 | #define TMON_LOG_FILE "/var/tmp/tmon.log" | ||
| 38 | |||
| 39 | extern unsigned long ticktime; | ||
| 40 | extern double time_elapsed; | ||
| 41 | extern unsigned long target_temp_user; | ||
| 42 | extern int dialogue_on; | ||
| 43 | extern char ctrl_cdev[]; | ||
| 44 | extern pthread_mutex_t input_lock; | ||
| 45 | extern int tmon_exit; | ||
| 46 | extern int target_thermal_zone; | ||
| 47 | /* use fixed size record to simplify data processing and transfer | ||
| 48 | * TBD: more info to be added, e.g. programmable trip point data. | ||
| 49 | */ | ||
| 50 | struct thermal_data_record { | ||
| 51 | struct timeval tv; | ||
| 52 | unsigned long temp[MAX_NR_TZONE]; | ||
| 53 | double pid_out_pct; | ||
| 54 | }; | ||
| 55 | |||
| 56 | struct cdev_info { | ||
| 57 | char type[64]; | ||
| 58 | int instance; | ||
| 59 | unsigned long max_state; | ||
| 60 | unsigned long cur_state; | ||
| 61 | unsigned long flag; | ||
| 62 | }; | ||
| 63 | |||
| 64 | enum trip_type { | ||
| 65 | THERMAL_TRIP_CRITICAL, | ||
| 66 | THERMAL_TRIP_HOT, | ||
| 67 | THERMAL_TRIP_PASSIVE, | ||
| 68 | THERMAL_TRIP_ACTIVE, | ||
| 69 | NR_THERMAL_TRIP_TYPE, | ||
| 70 | }; | ||
| 71 | |||
| 72 | struct trip_point { | ||
| 73 | enum trip_type type; | ||
| 74 | unsigned long temp; | ||
| 75 | unsigned long hysteresis; | ||
| 76 | int attribute; /* programmability etc. */ | ||
| 77 | }; | ||
| 78 | |||
| 79 | /* thermal zone configuration information, binding with cooling devices could | ||
| 80 | * change at runtime. | ||
| 81 | */ | ||
| 82 | struct tz_info { | ||
| 83 | char type[256]; /* e.g. acpitz */ | ||
| 84 | int instance; | ||
| 85 | int passive; /* active zone has passive node to force passive mode */ | ||
| 86 | int nr_cdev; /* number of cooling device binded */ | ||
| 87 | int nr_trip_pts; | ||
| 88 | struct trip_point tp[MAX_NR_TRIP]; | ||
| 89 | unsigned long cdev_binding; /* bitmap for attached cdevs */ | ||
| 90 | /* cdev bind trip points, allow one cdev bind to multiple trips */ | ||
| 91 | unsigned long trip_binding[MAX_NR_CDEV]; | ||
| 92 | }; | ||
| 93 | |||
| 94 | struct tmon_platform_data { | ||
| 95 | int nr_tz_sensor; | ||
| 96 | int nr_cooling_dev; | ||
| 97 | /* keep track of instance ids since there might be gaps */ | ||
| 98 | int max_tz_instance; | ||
| 99 | int max_cdev_instance; | ||
| 100 | struct tz_info *tzi; | ||
| 101 | struct cdev_info *cdi; | ||
| 102 | }; | ||
| 103 | |||
| 104 | struct control_ops { | ||
| 105 | void (*set_ratio)(unsigned long ratio); | ||
| 106 | unsigned long (*get_ratio)(unsigned long ratio); | ||
| 107 | |||
| 108 | }; | ||
| 109 | |||
| 110 | enum cdev_types { | ||
| 111 | CDEV_TYPE_PROC, | ||
| 112 | CDEV_TYPE_FAN, | ||
| 113 | CDEV_TYPE_MEM, | ||
| 114 | CDEV_TYPE_NR, | ||
| 115 | }; | ||
| 116 | |||
| 117 | /* REVISIT: the idea is to group sensors if possible, e.g. on intel mid | ||
| 118 | * we have "skin0", "skin1", "sys", "msicdie" | ||
| 119 | * on DPTF enabled systems, we might have PCH, TSKN, TAMB, etc. | ||
| 120 | */ | ||
| 121 | enum tzone_types { | ||
| 122 | TZONE_TYPE_ACPI, | ||
| 123 | TZONE_TYPE_PCH, | ||
| 124 | TZONE_TYPE_NR, | ||
| 125 | }; | ||
| 126 | |||
| 127 | /* limit the output of PID controller adjustment */ | ||
| 128 | #define LIMIT_HIGH (95) | ||
| 129 | #define LIMIT_LOW (2) | ||
| 130 | |||
| 131 | struct pid_params { | ||
| 132 | double kp; /* Controller gain from Dialog Box */ | ||
| 133 | double ki; /* Time-constant for I action from Dialog Box */ | ||
| 134 | double kd; /* Time-constant for D action from Dialog Box */ | ||
| 135 | double ts; | ||
| 136 | double k_lpf; | ||
| 137 | |||
| 138 | double t_target; | ||
| 139 | double y_k; | ||
| 140 | }; | ||
| 141 | |||
| 142 | extern int init_thermal_controller(void); | ||
| 143 | extern void controller_handler(const double xk, double *yk); | ||
| 144 | |||
| 145 | extern struct tmon_platform_data ptdata; | ||
| 146 | extern struct pid_params p_param; | ||
| 147 | |||
| 148 | extern FILE *tmon_log; | ||
| 149 | extern int cur_thermal_record; /* index to the trec array */ | ||
| 150 | extern struct thermal_data_record trec[]; | ||
| 151 | extern const char *trip_type_name[]; | ||
| 152 | extern unsigned long no_control; | ||
| 153 | |||
| 154 | extern void initialize_curses(void); | ||
| 155 | extern void show_controller_stats(char *line); | ||
| 156 | extern void show_title_bar(void); | ||
| 157 | extern void setup_windows(void); | ||
| 158 | extern void disable_tui(void); | ||
| 159 | extern void show_sensors_w(void); | ||
| 160 | extern void show_data_w(void); | ||
| 161 | extern void write_status_bar(int x, char *line); | ||
| 162 | extern void show_control_w(); | ||
| 163 | |||
| 164 | extern void show_cooling_device(void); | ||
| 165 | extern void show_dialogue(void); | ||
| 166 | extern int update_thermal_data(void); | ||
| 167 | |||
| 168 | extern int probe_thermal_sysfs(void); | ||
| 169 | extern void free_thermal_data(void); | ||
| 170 | extern void resize_handler(int sig); | ||
| 171 | extern void set_ctrl_state(unsigned long state); | ||
| 172 | extern void get_ctrl_state(unsigned long *state); | ||
| 173 | extern void *handle_tui_events(void *arg); | ||
| 174 | extern int sysfs_set_ulong(char *path, char *filename, unsigned long val); | ||
| 175 | extern int zone_instance_to_index(int zone_inst); | ||
| 176 | extern void close_windows(void); | ||
| 177 | |||
| 178 | #define PT_COLOR_DEFAULT 1 | ||
| 179 | #define PT_COLOR_HEADER_BAR 2 | ||
| 180 | #define PT_COLOR_ERROR 3 | ||
| 181 | #define PT_COLOR_RED 4 | ||
| 182 | #define PT_COLOR_YELLOW 5 | ||
| 183 | #define PT_COLOR_GREEN 6 | ||
| 184 | #define PT_COLOR_BRIGHT 7 | ||
| 185 | #define PT_COLOR_BLUE 8 | ||
| 186 | |||
| 187 | /* each thermal zone uses 12 chars, 8 for name, 2 for instance, 2 space | ||
| 188 | * also used to list trip points in forms of AAAC, which represents | ||
| 189 | * A: Active | ||
| 190 | * C: Critical | ||
| 191 | */ | ||
| 192 | #define TZONE_RECORD_SIZE 12 | ||
| 193 | #define TZ_LEFT_ALIGN 32 | ||
| 194 | #define CDEV_NAME_SIZE 20 | ||
| 195 | #define CDEV_FLAG_IN_CONTROL (1 << 0) | ||
| 196 | |||
| 197 | /* dialogue box starts */ | ||
| 198 | #define DIAG_X 48 | ||
| 199 | #define DIAG_Y 8 | ||
| 200 | #define THERMAL_SYSFS "/sys/class/thermal" | ||
| 201 | #define CDEV "cooling_device" | ||
| 202 | #define TZONE "thermal_zone" | ||
| 203 | #define TDATA_LEFT 16 | ||
| 204 | #endif /* TMON_H */ | ||
diff --git a/tools/thermal/tmon/tui.c b/tools/thermal/tmon/tui.c new file mode 100644 index 000000000000..89f8ef0e15c8 --- /dev/null +++ b/tools/thermal/tmon/tui.c | |||
| @@ -0,0 +1,638 @@ | |||
| 1 | /* | ||
| 2 | * tui.c ncurses text user interface for TMON program | ||
| 3 | * | ||
| 4 | * Copyright (C) 2013 Intel Corporation. All rights reserved. | ||
| 5 | * | ||
| 6 | * This program is free software; you can redistribute it and/or | ||
| 7 | * modify it under the terms of the GNU General Public License version | ||
| 8 | * 2 or later as published by the Free Software Foundation. | ||
| 9 | * | ||
| 10 | * This program is distributed in the hope that it will be useful, | ||
| 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 13 | * GNU General Public License for more details. | ||
| 14 | * | ||
| 15 | * Author: Jacob Pan <jacob.jun.pan@linux.intel.com> | ||
| 16 | * | ||
| 17 | */ | ||
| 18 | |||
| 19 | #include <unistd.h> | ||
| 20 | #include <stdio.h> | ||
| 21 | #include <stdlib.h> | ||
| 22 | #include <string.h> | ||
| 23 | #include <stdint.h> | ||
| 24 | #include <ncurses.h> | ||
| 25 | #include <time.h> | ||
| 26 | #include <syslog.h> | ||
| 27 | #include <panel.h> | ||
| 28 | #include <pthread.h> | ||
| 29 | #include <signal.h> | ||
| 30 | |||
| 31 | #include "tmon.h" | ||
| 32 | |||
| 33 | static PANEL *data_panel; | ||
| 34 | static PANEL *dialogue_panel; | ||
| 35 | static PANEL *top; | ||
| 36 | |||
| 37 | static WINDOW *title_bar_window; | ||
| 38 | static WINDOW *tz_sensor_window; | ||
| 39 | static WINDOW *cooling_device_window; | ||
| 40 | static WINDOW *control_window; | ||
| 41 | static WINDOW *status_bar_window; | ||
| 42 | static WINDOW *thermal_data_window; | ||
| 43 | static WINDOW *dialogue_window; | ||
| 44 | |||
| 45 | char status_bar_slots[10][40]; | ||
| 46 | static void draw_hbar(WINDOW *win, int y, int start, int len, | ||
| 47 | unsigned long pattern, bool end); | ||
| 48 | |||
| 49 | static int maxx, maxy; | ||
| 50 | static int maxwidth = 200; | ||
| 51 | |||
| 52 | #define TITLE_BAR_HIGHT 1 | ||
| 53 | #define SENSOR_WIN_HIGHT 4 /* one row for tz name, one for trip points */ | ||
| 54 | |||
| 55 | |||
| 56 | /* daemon mode flag (set by startup parameter -d) */ | ||
| 57 | static int tui_disabled; | ||
| 58 | |||
| 59 | static void close_panel(PANEL *p) | ||
| 60 | { | ||
| 61 | if (p) { | ||
| 62 | del_panel(p); | ||
| 63 | p = NULL; | ||
| 64 | } | ||
| 65 | } | ||
| 66 | |||
| 67 | static void close_window(WINDOW *win) | ||
| 68 | { | ||
| 69 | if (win) { | ||
| 70 | delwin(win); | ||
| 71 | win = NULL; | ||
| 72 | } | ||
| 73 | } | ||
| 74 | |||
| 75 | void close_windows(void) | ||
| 76 | { | ||
| 77 | if (tui_disabled) | ||
| 78 | return; | ||
| 79 | /* must delete panels before their attached windows */ | ||
| 80 | if (dialogue_window) | ||
| 81 | close_panel(dialogue_panel); | ||
| 82 | if (cooling_device_window) | ||
| 83 | close_panel(data_panel); | ||
| 84 | |||
| 85 | close_window(title_bar_window); | ||
| 86 | close_window(tz_sensor_window); | ||
| 87 | close_window(status_bar_window); | ||
| 88 | close_window(cooling_device_window); | ||
| 89 | close_window(control_window); | ||
| 90 | close_window(thermal_data_window); | ||
| 91 | close_window(dialogue_window); | ||
| 92 | |||
| 93 | } | ||
| 94 | |||
| 95 | void write_status_bar(int x, char *line) | ||
| 96 | { | ||
| 97 | mvwprintw(status_bar_window, 0, x, "%s", line); | ||
| 98 | wrefresh(status_bar_window); | ||
| 99 | } | ||
| 100 | |||
| 101 | void setup_windows(void) | ||
| 102 | { | ||
| 103 | int y_begin = 1; | ||
| 104 | |||
| 105 | if (tui_disabled) | ||
| 106 | return; | ||
| 107 | |||
| 108 | getmaxyx(stdscr, maxy, maxx); | ||
| 109 | resizeterm(maxy, maxx); | ||
| 110 | |||
| 111 | title_bar_window = subwin(stdscr, TITLE_BAR_HIGHT, maxx, 0, 0); | ||
| 112 | y_begin += TITLE_BAR_HIGHT; | ||
| 113 | |||
| 114 | tz_sensor_window = subwin(stdscr, SENSOR_WIN_HIGHT, maxx, y_begin, 0); | ||
| 115 | y_begin += SENSOR_WIN_HIGHT; | ||
| 116 | |||
| 117 | cooling_device_window = subwin(stdscr, ptdata.nr_cooling_dev + 3, maxx, | ||
| 118 | y_begin, 0); | ||
| 119 | y_begin += ptdata.nr_cooling_dev + 3; /* 2 lines for border */ | ||
| 120 | /* two lines to show borders, one line per tz show trip point position | ||
| 121 | * and value. | ||
| 122 | * dialogue window is a pop-up, when needed it lays on top of cdev win | ||
| 123 | */ | ||
| 124 | |||
| 125 | dialogue_window = subwin(stdscr, ptdata.nr_cooling_dev+5, maxx-50, | ||
| 126 | DIAG_Y, DIAG_X); | ||
| 127 | |||
| 128 | thermal_data_window = subwin(stdscr, ptdata.nr_tz_sensor * | ||
| 129 | NR_LINES_TZDATA + 3, maxx, y_begin, 0); | ||
| 130 | y_begin += ptdata.nr_tz_sensor * NR_LINES_TZDATA + 3; | ||
| 131 | control_window = subwin(stdscr, 4, maxx, y_begin, 0); | ||
| 132 | |||
| 133 | scrollok(cooling_device_window, TRUE); | ||
| 134 | maxwidth = maxx - 18; | ||
| 135 | status_bar_window = subwin(stdscr, 1, maxx, maxy-1, 0); | ||
| 136 | |||
| 137 | strcpy(status_bar_slots[0], " Ctrl-c - Quit "); | ||
| 138 | strcpy(status_bar_slots[1], " TAB - Tuning "); | ||
| 139 | wmove(status_bar_window, 1, 30); | ||
| 140 | |||
| 141 | /* prepare panels for dialogue, if panel already created then we must | ||
| 142 | * be doing resizing, so just replace windows with new ones, old ones | ||
| 143 | * should have been deleted by close_window | ||
| 144 | */ | ||
| 145 | data_panel = new_panel(cooling_device_window); | ||
| 146 | if (!data_panel) | ||
| 147 | syslog(LOG_DEBUG, "No data panel\n"); | ||
| 148 | else { | ||
| 149 | if (dialogue_window) { | ||
| 150 | dialogue_panel = new_panel(dialogue_window); | ||
| 151 | if (!dialogue_panel) | ||
| 152 | syslog(LOG_DEBUG, "No dialogue panel\n"); | ||
| 153 | else { | ||
| 154 | /* Set up the user pointer to the next panel*/ | ||
| 155 | set_panel_userptr(data_panel, dialogue_panel); | ||
| 156 | set_panel_userptr(dialogue_panel, data_panel); | ||
| 157 | top = data_panel; | ||
| 158 | } | ||
| 159 | } else | ||
| 160 | syslog(LOG_INFO, "no dialogue win, term too small\n"); | ||
| 161 | } | ||
| 162 | doupdate(); | ||
| 163 | werase(stdscr); | ||
| 164 | refresh(); | ||
| 165 | } | ||
| 166 | |||
| 167 | void resize_handler(int sig) | ||
| 168 | { | ||
| 169 | /* start over when term gets resized, but first we clean up */ | ||
| 170 | close_windows(); | ||
| 171 | endwin(); | ||
| 172 | refresh(); | ||
| 173 | clear(); | ||
| 174 | getmaxyx(stdscr, maxy, maxx); /* get the new screen size */ | ||
| 175 | setup_windows(); | ||
| 176 | /* rate limit */ | ||
| 177 | sleep(1); | ||
| 178 | syslog(LOG_DEBUG, "SIG %d, term resized to %d x %d\n", | ||
| 179 | sig, maxy, maxx); | ||
| 180 | signal(SIGWINCH, resize_handler); | ||
| 181 | } | ||
| 182 | |||
| 183 | const char cdev_title[] = " COOLING DEVICES "; | ||
| 184 | void show_cooling_device(void) | ||
| 185 | { | ||
| 186 | int i, j, x, y = 0; | ||
| 187 | |||
| 188 | if (tui_disabled || !cooling_device_window) | ||
| 189 | return; | ||
| 190 | |||
| 191 | werase(cooling_device_window); | ||
| 192 | wattron(cooling_device_window, A_BOLD); | ||
| 193 | mvwprintw(cooling_device_window, 1, 1, | ||
| 194 | "ID Cooling Dev Cur Max Thermal Zone Binding"); | ||
| 195 | wattroff(cooling_device_window, A_BOLD); | ||
| 196 | for (j = 0; j < ptdata.nr_cooling_dev; j++) { | ||
| 197 | /* draw cooling device list on the left in the order of | ||
| 198 | * cooling device instances. skip unused idr. | ||
| 199 | */ | ||
| 200 | mvwprintw(cooling_device_window, j + 2, 1, | ||
| 201 | "%02d %12.12s%6d %6d", | ||
| 202 | ptdata.cdi[j].instance, | ||
| 203 | ptdata.cdi[j].type, | ||
| 204 | ptdata.cdi[j].cur_state, | ||
| 205 | ptdata.cdi[j].max_state); | ||
| 206 | } | ||
| 207 | |||
| 208 | /* show cdev binding, y is the global cooling device instance */ | ||
| 209 | for (i = 0; i < ptdata.nr_tz_sensor; i++) { | ||
| 210 | int tz_inst = ptdata.tzi[i].instance; | ||
| 211 | for (j = 0; j < ptdata.nr_cooling_dev; j++) { | ||
| 212 | int cdev_inst; | ||
| 213 | y = j; | ||
| 214 | x = tz_inst * TZONE_RECORD_SIZE + TZ_LEFT_ALIGN; | ||
| 215 | |||
| 216 | draw_hbar(cooling_device_window, y+2, x, | ||
| 217 | TZONE_RECORD_SIZE-1, ACS_VLINE, false); | ||
| 218 | |||
| 219 | /* draw a column of spaces to separate thermal zones */ | ||
| 220 | mvwprintw(cooling_device_window, y+2, x-1, " "); | ||
| 221 | if (ptdata.tzi[i].cdev_binding) { | ||
| 222 | cdev_inst = ptdata.cdi[j].instance; | ||
| 223 | unsigned long trip_binding = | ||
| 224 | ptdata.tzi[i].trip_binding[cdev_inst]; | ||
| 225 | int k = 0; /* per zone trip point id that | ||
| 226 | * binded to this cdev, one to | ||
| 227 | * many possible based on the | ||
| 228 | * binding bitmask. | ||
| 229 | */ | ||
| 230 | syslog(LOG_DEBUG, | ||
| 231 | "bind tz%d cdev%d tp%lx %d cdev%lx\n", | ||
| 232 | i, j, trip_binding, y, | ||
| 233 | ptdata.tzi[i].cdev_binding); | ||
| 234 | /* draw each trip binding for the cdev */ | ||
| 235 | while (trip_binding >>= 1) { | ||
| 236 | k++; | ||
| 237 | if (!(trip_binding & 1)) | ||
| 238 | continue; | ||
| 239 | /* draw '*' to show binding */ | ||
| 240 | mvwprintw(cooling_device_window, | ||
| 241 | y + 2, | ||
| 242 | x + ptdata.tzi[i].nr_trip_pts - | ||
| 243 | k - 1, "*"); | ||
| 244 | } | ||
| 245 | } | ||
| 246 | } | ||
| 247 | } | ||
| 248 | /* draw border after data so that border will not be messed up | ||
| 249 | * even there is not enough space for all the data to be shown | ||
| 250 | */ | ||
| 251 | wborder(cooling_device_window, 0, 0, 0, 0, 0, 0, 0, 0); | ||
| 252 | wattron(cooling_device_window, A_BOLD); | ||
| 253 | mvwprintw(cooling_device_window, 0, maxx/2 - sizeof(cdev_title), | ||
| 254 | cdev_title); | ||
| 255 | wattroff(cooling_device_window, A_BOLD); | ||
| 256 | |||
| 257 | wrefresh(cooling_device_window); | ||
| 258 | } | ||
| 259 | |||
| 260 | const char DIAG_TITLE[] = "[ TUNABLES ]"; | ||
| 261 | #define DIAG_DEV_ROWS 5 | ||
| 262 | void show_dialogue(void) | ||
| 263 | { | ||
| 264 | int j, x = 0, y = 0; | ||
| 265 | WINDOW *w = dialogue_window; | ||
| 266 | |||
| 267 | if (tui_disabled || !w) | ||
| 268 | return; | ||
| 269 | |||
| 270 | werase(w); | ||
| 271 | box(w, 0, 0); | ||
| 272 | mvwprintw(w, 0, maxx/4, DIAG_TITLE); | ||
| 273 | /* list all the available tunables */ | ||
| 274 | for (j = 0; j <= ptdata.nr_cooling_dev; j++) { | ||
| 275 | y = j % DIAG_DEV_ROWS; | ||
| 276 | if (y == 0 && j != 0) | ||
| 277 | x += 20; | ||
| 278 | if (j == ptdata.nr_cooling_dev) | ||
| 279 | /* save last choice for target temp */ | ||
| 280 | mvwprintw(w, y+1, x+1, "%C-%.12s", 'A'+j, "Set Temp"); | ||
| 281 | else | ||
| 282 | mvwprintw(w, y+1, x+1, "%C-%.10s-%2d", 'A'+j, | ||
| 283 | ptdata.cdi[j].type, ptdata.cdi[j].instance); | ||
| 284 | } | ||
| 285 | wattron(w, A_BOLD); | ||
| 286 | mvwprintw(w, DIAG_DEV_ROWS+1, 1, "Enter Choice [A-Z]?"); | ||
| 287 | wattroff(w, A_BOLD); | ||
| 288 | /* y size of dialogue win is nr cdev + 5, so print legend | ||
| 289 | * at the bottom line | ||
| 290 | */ | ||
| 291 | mvwprintw(w, ptdata.nr_cooling_dev+3, 1, | ||
| 292 | "Legend: A=Active, P=Passive, C=Critical"); | ||
| 293 | |||
| 294 | wrefresh(dialogue_window); | ||
| 295 | } | ||
| 296 | |||
| 297 | void write_dialogue_win(char *buf, int y, int x) | ||
| 298 | { | ||
| 299 | WINDOW *w = dialogue_window; | ||
| 300 | |||
| 301 | mvwprintw(w, y, x, "%s", buf); | ||
| 302 | } | ||
| 303 | |||
| 304 | const char control_title[] = " CONTROLS "; | ||
| 305 | void show_control_w(void) | ||
| 306 | { | ||
| 307 | unsigned long state; | ||
| 308 | |||
| 309 | get_ctrl_state(&state); | ||
| 310 | |||
| 311 | if (tui_disabled || !control_window) | ||
| 312 | return; | ||
| 313 | |||
| 314 | werase(control_window); | ||
| 315 | mvwprintw(control_window, 1, 1, | ||
| 316 | "PID gain: kp=%2.2f ki=%2.2f kd=%2.2f Output %2.2f", | ||
| 317 | p_param.kp, p_param.ki, p_param.kd, p_param.y_k); | ||
| 318 | |||
| 319 | mvwprintw(control_window, 2, 1, | ||
| 320 | "Target Temp: %2.1fC, Zone: %d, Control Device: %.12s", | ||
| 321 | p_param.t_target, target_thermal_zone, ctrl_cdev); | ||
| 322 | |||
| 323 | /* draw border last such that everything is within boundary */ | ||
| 324 | wborder(control_window, 0, 0, 0, 0, 0, 0, 0, 0); | ||
| 325 | wattron(control_window, A_BOLD); | ||
| 326 | mvwprintw(control_window, 0, maxx/2 - sizeof(control_title), | ||
| 327 | control_title); | ||
| 328 | wattroff(control_window, A_BOLD); | ||
| 329 | |||
| 330 | wrefresh(control_window); | ||
| 331 | } | ||
| 332 | |||
| 333 | void initialize_curses(void) | ||
| 334 | { | ||
| 335 | if (tui_disabled) | ||
| 336 | return; | ||
| 337 | |||
| 338 | initscr(); | ||
| 339 | start_color(); | ||
| 340 | keypad(stdscr, TRUE); /* enable keyboard mapping */ | ||
| 341 | nonl(); /* tell curses not to do NL->CR/NL on output */ | ||
| 342 | cbreak(); /* take input chars one at a time */ | ||
| 343 | noecho(); /* dont echo input */ | ||
| 344 | curs_set(0); /* turn off cursor */ | ||
| 345 | use_default_colors(); | ||
| 346 | |||
| 347 | init_pair(PT_COLOR_DEFAULT, COLOR_WHITE, COLOR_BLACK); | ||
| 348 | init_pair(PT_COLOR_HEADER_BAR, COLOR_BLACK, COLOR_WHITE); | ||
| 349 | init_pair(PT_COLOR_ERROR, COLOR_BLACK, COLOR_RED); | ||
| 350 | init_pair(PT_COLOR_RED, COLOR_WHITE, COLOR_RED); | ||
| 351 | init_pair(PT_COLOR_YELLOW, COLOR_WHITE, COLOR_YELLOW); | ||
| 352 | init_pair(PT_COLOR_GREEN, COLOR_WHITE, COLOR_GREEN); | ||
| 353 | init_pair(PT_COLOR_BLUE, COLOR_WHITE, COLOR_BLUE); | ||
| 354 | init_pair(PT_COLOR_BRIGHT, COLOR_WHITE, COLOR_BLACK); | ||
| 355 | |||
| 356 | } | ||
| 357 | |||
| 358 | void show_title_bar(void) | ||
| 359 | { | ||
| 360 | int i; | ||
| 361 | int x = 0; | ||
| 362 | |||
| 363 | if (tui_disabled || !title_bar_window) | ||
| 364 | return; | ||
| 365 | |||
| 366 | wattrset(title_bar_window, COLOR_PAIR(PT_COLOR_HEADER_BAR)); | ||
| 367 | wbkgd(title_bar_window, COLOR_PAIR(PT_COLOR_HEADER_BAR)); | ||
| 368 | werase(title_bar_window); | ||
| 369 | |||
| 370 | mvwprintw(title_bar_window, 0, 0, | ||
| 371 | " TMON v%s", VERSION); | ||
| 372 | |||
| 373 | wrefresh(title_bar_window); | ||
| 374 | |||
| 375 | werase(status_bar_window); | ||
| 376 | |||
| 377 | for (i = 0; i < 10; i++) { | ||
| 378 | if (strlen(status_bar_slots[i]) == 0) | ||
| 379 | continue; | ||
| 380 | wattron(status_bar_window, A_REVERSE); | ||
| 381 | mvwprintw(status_bar_window, 0, x, "%s", status_bar_slots[i]); | ||
| 382 | wattroff(status_bar_window, A_REVERSE); | ||
| 383 | x += strlen(status_bar_slots[i]) + 1; | ||
| 384 | } | ||
| 385 | wrefresh(status_bar_window); | ||
| 386 | } | ||
| 387 | |||
| 388 | static void handle_input_val(int ch) | ||
| 389 | { | ||
| 390 | char buf[32]; | ||
| 391 | int val; | ||
| 392 | char path[256]; | ||
| 393 | WINDOW *w = dialogue_window; | ||
| 394 | |||
| 395 | echo(); | ||
| 396 | keypad(w, TRUE); | ||
| 397 | wgetnstr(w, buf, 31); | ||
| 398 | val = atoi(buf); | ||
| 399 | |||
| 400 | if (ch == ptdata.nr_cooling_dev) { | ||
| 401 | snprintf(buf, 31, "Invalid Temp %d! %d-%d", val, | ||
| 402 | MIN_CTRL_TEMP, MAX_CTRL_TEMP); | ||
| 403 | if (val < MIN_CTRL_TEMP || val > MAX_CTRL_TEMP) | ||
| 404 | write_status_bar(40, buf); | ||
| 405 | else { | ||
| 406 | p_param.t_target = val; | ||
| 407 | snprintf(buf, 31, "Set New Target Temp %d", val); | ||
| 408 | write_status_bar(40, buf); | ||
| 409 | } | ||
| 410 | } else { | ||
| 411 | snprintf(path, 256, "%s/%s%d", THERMAL_SYSFS, | ||
| 412 | CDEV, ptdata.cdi[ch].instance); | ||
| 413 | sysfs_set_ulong(path, "cur_state", val); | ||
| 414 | } | ||
| 415 | noecho(); | ||
| 416 | dialogue_on = 0; | ||
| 417 | show_data_w(); | ||
| 418 | show_control_w(); | ||
| 419 | |||
| 420 | top = (PANEL *)panel_userptr(top); | ||
| 421 | top_panel(top); | ||
| 422 | } | ||
| 423 | |||
| 424 | static void handle_input_choice(int ch) | ||
| 425 | { | ||
| 426 | char buf[48]; | ||
| 427 | int base = 0; | ||
| 428 | int cdev_id = 0; | ||
| 429 | |||
| 430 | if ((ch >= 'A' && ch <= 'A' + ptdata.nr_cooling_dev) || | ||
| 431 | (ch >= 'a' && ch <= 'a' + ptdata.nr_cooling_dev)) { | ||
| 432 | base = (ch < 'a') ? 'A' : 'a'; | ||
| 433 | cdev_id = ch - base; | ||
| 434 | if (ptdata.nr_cooling_dev == cdev_id) | ||
| 435 | snprintf(buf, sizeof(buf), "New Target Temp:"); | ||
| 436 | else | ||
| 437 | snprintf(buf, sizeof(buf), "New Value for %.10s-%2d: ", | ||
| 438 | ptdata.cdi[cdev_id].type, | ||
| 439 | ptdata.cdi[cdev_id].instance); | ||
| 440 | write_dialogue_win(buf, DIAG_DEV_ROWS+2, 2); | ||
| 441 | handle_input_val(cdev_id); | ||
| 442 | } else { | ||
| 443 | snprintf(buf, sizeof(buf), "Invalid selection %d", ch); | ||
| 444 | write_dialogue_win(buf, 8, 2); | ||
| 445 | } | ||
| 446 | } | ||
| 447 | |||
| 448 | void *handle_tui_events(void *arg) | ||
| 449 | { | ||
| 450 | int ch; | ||
| 451 | |||
| 452 | keypad(cooling_device_window, TRUE); | ||
| 453 | while ((ch = wgetch(cooling_device_window)) != EOF) { | ||
| 454 | if (tmon_exit) | ||
| 455 | break; | ||
| 456 | /* when term size is too small, no dialogue panels are set. | ||
| 457 | * we need to filter out such cases. | ||
| 458 | */ | ||
| 459 | if (!data_panel || !dialogue_panel || | ||
| 460 | !cooling_device_window || | ||
| 461 | !dialogue_window) { | ||
| 462 | |||
| 463 | continue; | ||
| 464 | } | ||
| 465 | pthread_mutex_lock(&input_lock); | ||
| 466 | if (dialogue_on) { | ||
| 467 | handle_input_choice(ch); | ||
| 468 | /* top panel filter */ | ||
| 469 | if (ch == 'q' || ch == 'Q') | ||
| 470 | ch = 0; | ||
| 471 | } | ||
| 472 | switch (ch) { | ||
| 473 | case KEY_LEFT: | ||
| 474 | box(cooling_device_window, 10, 0); | ||
| 475 | break; | ||
| 476 | case 9: /* TAB */ | ||
| 477 | top = (PANEL *)panel_userptr(top); | ||
| 478 | top_panel(top); | ||
| 479 | if (top == dialogue_panel) { | ||
| 480 | dialogue_on = 1; | ||
| 481 | show_dialogue(); | ||
| 482 | } else { | ||
| 483 | dialogue_on = 0; | ||
| 484 | /* force refresh */ | ||
| 485 | show_data_w(); | ||
| 486 | show_control_w(); | ||
| 487 | } | ||
| 488 | break; | ||
| 489 | case 'q': | ||
| 490 | case 'Q': | ||
| 491 | tmon_exit = 1; | ||
| 492 | break; | ||
| 493 | } | ||
| 494 | update_panels(); | ||
| 495 | doupdate(); | ||
| 496 | pthread_mutex_unlock(&input_lock); | ||
| 497 | } | ||
| 498 | |||
| 499 | if (arg) | ||
| 500 | *(int *)arg = 0; /* make gcc happy */ | ||
| 501 | |||
| 502 | return NULL; | ||
| 503 | } | ||
| 504 | |||
| 505 | /* draw a horizontal bar in given pattern */ | ||
| 506 | static void draw_hbar(WINDOW *win, int y, int start, int len, unsigned long ptn, | ||
| 507 | bool end) | ||
| 508 | { | ||
| 509 | mvwaddch(win, y, start, ptn); | ||
| 510 | whline(win, ptn, len); | ||
| 511 | if (end) | ||
| 512 | mvwaddch(win, y, MAX_DISP_TEMP+TDATA_LEFT, ']'); | ||
| 513 | } | ||
| 514 | |||
| 515 | static char trip_type_to_char(int type) | ||
| 516 | { | ||
| 517 | switch (type) { | ||
| 518 | case THERMAL_TRIP_CRITICAL: return 'C'; | ||
| 519 | case THERMAL_TRIP_HOT: return 'H'; | ||
| 520 | case THERMAL_TRIP_PASSIVE: return 'P'; | ||
| 521 | case THERMAL_TRIP_ACTIVE: return 'A'; | ||
| 522 | default: | ||
| 523 | return '?'; | ||
| 524 | } | ||
| 525 | } | ||
| 526 | |||
| 527 | /* fill a string with trip point type and value in one line | ||
| 528 | * e.g. P(56) C(106) | ||
| 529 | * maintain the distance one degree per char | ||
| 530 | */ | ||
| 531 | static void draw_tp_line(int tz, int y) | ||
| 532 | { | ||
| 533 | int j; | ||
| 534 | int x; | ||
| 535 | |||
| 536 | for (j = 0; j < ptdata.tzi[tz].nr_trip_pts; j++) { | ||
| 537 | x = ptdata.tzi[tz].tp[j].temp / 1000; | ||
| 538 | mvwprintw(thermal_data_window, y + 0, x + TDATA_LEFT, | ||
| 539 | "%c%d", trip_type_to_char(ptdata.tzi[tz].tp[j].type), | ||
| 540 | x); | ||
| 541 | syslog(LOG_INFO, "%s:tz %d tp %d temp = %lu\n", __func__, | ||
| 542 | tz, j, ptdata.tzi[tz].tp[j].temp); | ||
| 543 | } | ||
| 544 | } | ||
| 545 | |||
| 546 | const char data_win_title[] = " THERMAL DATA "; | ||
| 547 | void show_data_w(void) | ||
| 548 | { | ||
| 549 | int i; | ||
| 550 | |||
| 551 | |||
| 552 | if (tui_disabled || !thermal_data_window) | ||
| 553 | return; | ||
| 554 | |||
| 555 | werase(thermal_data_window); | ||
| 556 | wattron(thermal_data_window, A_BOLD); | ||
| 557 | mvwprintw(thermal_data_window, 0, maxx/2 - sizeof(data_win_title), | ||
| 558 | data_win_title); | ||
| 559 | wattroff(thermal_data_window, A_BOLD); | ||
| 560 | /* draw a line as ruler */ | ||
| 561 | for (i = 10; i < MAX_DISP_TEMP; i += 10) | ||
| 562 | mvwprintw(thermal_data_window, 1, i+TDATA_LEFT, "%2d", i); | ||
| 563 | |||
| 564 | for (i = 0; i < ptdata.nr_tz_sensor; i++) { | ||
| 565 | int temp = trec[cur_thermal_record].temp[i] / 1000; | ||
| 566 | int y = 0; | ||
| 567 | |||
| 568 | y = i * NR_LINES_TZDATA + 2; | ||
| 569 | /* y at tz temp data line */ | ||
| 570 | mvwprintw(thermal_data_window, y, 1, "%6.6s%2d:[%3d][", | ||
| 571 | ptdata.tzi[i].type, | ||
| 572 | ptdata.tzi[i].instance, temp); | ||
| 573 | draw_hbar(thermal_data_window, y, TDATA_LEFT, temp, ACS_RARROW, | ||
| 574 | true); | ||
| 575 | draw_tp_line(i, y); | ||
| 576 | } | ||
| 577 | wborder(thermal_data_window, 0, 0, 0, 0, 0, 0, 0, 0); | ||
| 578 | wrefresh(thermal_data_window); | ||
| 579 | } | ||
| 580 | |||
| 581 | const char tz_title[] = "THERMAL ZONES(SENSORS)"; | ||
| 582 | |||
| 583 | void show_sensors_w(void) | ||
| 584 | { | ||
| 585 | int i, j; | ||
| 586 | char buffer[512]; | ||
| 587 | |||
| 588 | if (tui_disabled || !tz_sensor_window) | ||
| 589 | return; | ||
| 590 | |||
| 591 | werase(tz_sensor_window); | ||
| 592 | |||
| 593 | memset(buffer, 0, sizeof(buffer)); | ||
| 594 | wattron(tz_sensor_window, A_BOLD); | ||
| 595 | mvwprintw(tz_sensor_window, 1, 1, "Thermal Zones:"); | ||
| 596 | wattroff(tz_sensor_window, A_BOLD); | ||
| 597 | |||
| 598 | mvwprintw(tz_sensor_window, 1, TZ_LEFT_ALIGN, "%s", buffer); | ||
| 599 | /* fill trip points for each tzone */ | ||
| 600 | wattron(tz_sensor_window, A_BOLD); | ||
| 601 | mvwprintw(tz_sensor_window, 2, 1, "Trip Points:"); | ||
| 602 | wattroff(tz_sensor_window, A_BOLD); | ||
| 603 | |||
| 604 | /* draw trip point from low to high for each tz */ | ||
| 605 | for (i = 0; i < ptdata.nr_tz_sensor; i++) { | ||
| 606 | int inst = ptdata.tzi[i].instance; | ||
| 607 | |||
| 608 | mvwprintw(tz_sensor_window, 1, | ||
| 609 | TZ_LEFT_ALIGN+TZONE_RECORD_SIZE * inst, "%.9s%02d", | ||
| 610 | ptdata.tzi[i].type, ptdata.tzi[i].instance); | ||
| 611 | for (j = ptdata.tzi[i].nr_trip_pts - 1; j >= 0; j--) { | ||
| 612 | /* loop through all trip points */ | ||
| 613 | char type; | ||
| 614 | int tp_pos; | ||
| 615 | /* reverse the order here since trips are sorted | ||
| 616 | * in ascending order in terms of temperature. | ||
| 617 | */ | ||
| 618 | tp_pos = ptdata.tzi[i].nr_trip_pts - j - 1; | ||
| 619 | |||
| 620 | type = trip_type_to_char(ptdata.tzi[i].tp[j].type); | ||
| 621 | mvwaddch(tz_sensor_window, 2, | ||
| 622 | inst * TZONE_RECORD_SIZE + TZ_LEFT_ALIGN + | ||
| 623 | tp_pos, type); | ||
| 624 | syslog(LOG_DEBUG, "draw tz %d tp %d ch:%c\n", | ||
| 625 | inst, j, type); | ||
| 626 | } | ||
| 627 | } | ||
| 628 | wborder(tz_sensor_window, 0, 0, 0, 0, 0, 0, 0, 0); | ||
| 629 | wattron(tz_sensor_window, A_BOLD); | ||
| 630 | mvwprintw(tz_sensor_window, 0, maxx/2 - sizeof(tz_title), tz_title); | ||
| 631 | wattroff(tz_sensor_window, A_BOLD); | ||
| 632 | wrefresh(tz_sensor_window); | ||
| 633 | } | ||
| 634 | |||
| 635 | void disable_tui(void) | ||
| 636 | { | ||
| 637 | tui_disabled = 1; | ||
| 638 | } | ||
