From patchwork Wed Mar 30 10:04:41 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Lezcano X-Patchwork-Id: 555219 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id DD321C433EF for ; Wed, 30 Mar 2022 10:05:13 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S245140AbiC3KG4 (ORCPT ); Wed, 30 Mar 2022 06:06:56 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:39306 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S245128AbiC3KGz (ORCPT ); Wed, 30 Mar 2022 06:06:55 -0400 Received: from mail-wr1-x42f.google.com (mail-wr1-x42f.google.com [IPv6:2a00:1450:4864:20::42f]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id F1420DFFA8 for ; Wed, 30 Mar 2022 03:05:08 -0700 (PDT) Received: by mail-wr1-x42f.google.com with SMTP id i4so9670098wrb.5 for ; Wed, 30 Mar 2022 03:05:08 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=X0cVlNPtVwnSw5gZ4pa9tLmY4mqZGnFS2d9FtzXI5Mw=; b=v62g3hqUY+W6WRvyKh4/6XLTxFWo+D9A7wPKo9NDMkbR4J6OTPUpQYmVabPOpsY8QN kRXAyLVTsdEwr9asT7iCXbeQ96FYgj2j0pWqs5JC/81gnf+NdbDmxkjulJYwLE4fAq0m Pvx3zpRZsqga5XCdIr6cw6C5fRlh5hOJsA3qoXPcvX67+glonnm3ICzDPkWIqfclxhLz 5qE1sxv2dQFghcYmti2ZsBKwdVq09Yn7hgL4dnWmdDvr8+8hVHOb6qVYpQLowgx5IzWU qj/hL06Gf7X8tTL3NDolpFVUPcykn0d5GbbFdgYPWstEASu51aHUdE//RWimxZSVpUHv 2Exw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=X0cVlNPtVwnSw5gZ4pa9tLmY4mqZGnFS2d9FtzXI5Mw=; b=yobSLYEl+ZWV6hqnw1p5D3/VRsX5+CESNn7S3PJC9Nwg6ENgS3m6XNfiEnDmjPYn42 TQNu83+6p0+54b2+EGbBW3mgP9CYFxaFoUH6UXsR+Sxaz0RoPlV2GDa2mUe8g4VdI/nI F8RXg6HGZyZZ77BkcaA3oB8b7UWnDoXv2Hm/C//EruBsohdOTs2AB6zMpu+4eQoeCJRh dsCUUxQc4XQvIDh8t2IZelHrKAvpM1+PPW8Fk4vjKY/W+kDFT+yogM+h/NphC0RoLrc2 qxa6WGpI75Lf3v1k1ZhX7AK6Or1OwG0w8rlVgxo1BFzGz4I3NiG5Wf1rDuSfXfCNKh/6 Ai/Q== X-Gm-Message-State: AOAM532mSprO2XxTN85cQyZ7ezRSEOM0jSLSjhMMLxwGfmcIOZAUxwV5 n7d5DEWR5eCTzKhHK9m39QqIzg== X-Google-Smtp-Source: ABdhPJwH24hPLHbH8z1duANBrwZa9loErloqxRSFFFsn1XXVVFb0HY6B/ZssziZEpoOa0Cz+hKJm/w== X-Received: by 2002:a5d:498b:0:b0:203:e5d5:622c with SMTP id r11-20020a5d498b000000b00203e5d5622cmr35943781wrq.153.1648634707078; Wed, 30 Mar 2022 03:05:07 -0700 (PDT) Received: from localhost.localdomain ([2a01:e34:ed2f:f020:8f92:3217:a3c6:3ee8]) by smtp.gmail.com with ESMTPSA id p8-20020a5d4e08000000b002054b5437f2sm16542743wrt.115.2022.03.30.03.05.04 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 30 Mar 2022 03:05:06 -0700 (PDT) From: Daniel Lezcano To: daniel.lezcano@linaro.org, rafael@kernel.org Cc: srinivas.pandruvada@linux.intel.com, linux-kernel@vger.kernel.org, linux-pm@vger.kernel.org, Amit Kucheria , Zhang Rui , Mauro Carvalho Chehab , Rob Herring , Hans de Goede , "David S. Miller" , Krzysztof Kozlowski , Lukas Bulwahn , Jonathan Cameron , William Breathitt Gray , Sasha Levin Subject: [PATCH v2 1/4] tools/lib/thermal: Add a thermal library Date: Wed, 30 Mar 2022 12:04:41 +0200 Message-Id: <20220330100444.3846661-2-daniel.lezcano@linaro.org> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220330100444.3846661-1-daniel.lezcano@linaro.org> References: <20220330100444.3846661-1-daniel.lezcano@linaro.org> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-pm@vger.kernel.org The thermal framework implements a netlink notification mechanism to be used by the userspace to have a thermal configuration discovery, trip point changes or violation, cooling device changes notifications, etc... This library provides a level of abstraction for the thermal netlink notification allowing the userspace to connect to the notification mechanism more easily. The library is callback oriented. Signed-off-by: Daniel Lezcano --- MAINTAINERS | 1 + tools/Makefile | 14 +- tools/lib/thermal/.gitignore | 2 + tools/lib/thermal/Build | 5 + tools/lib/thermal/Makefile | 165 +++++++++++ tools/lib/thermal/commands.c | 349 +++++++++++++++++++++++ tools/lib/thermal/events.c | 164 +++++++++++ tools/lib/thermal/include/thermal.h | 142 +++++++++ tools/lib/thermal/libthermal.map | 25 ++ tools/lib/thermal/libthermal.pc.template | 12 + tools/lib/thermal/sampling.c | 75 +++++ tools/lib/thermal/thermal.c | 126 ++++++++ tools/lib/thermal/thermal_nl.c | 215 ++++++++++++++ tools/lib/thermal/thermal_nl.h | 46 +++ 14 files changed, 1339 insertions(+), 2 deletions(-) create mode 100644 tools/lib/thermal/.gitignore create mode 100644 tools/lib/thermal/Build create mode 100644 tools/lib/thermal/Makefile create mode 100644 tools/lib/thermal/commands.c create mode 100644 tools/lib/thermal/events.c create mode 100644 tools/lib/thermal/include/thermal.h create mode 100644 tools/lib/thermal/libthermal.map create mode 100644 tools/lib/thermal/libthermal.pc.template create mode 100644 tools/lib/thermal/sampling.c create mode 100644 tools/lib/thermal/thermal.c create mode 100644 tools/lib/thermal/thermal_nl.c create mode 100644 tools/lib/thermal/thermal_nl.h diff --git a/MAINTAINERS b/MAINTAINERS index 3bcb6f13617f..07fb2d6e2810 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -19129,6 +19129,7 @@ F: drivers/thermal/ F: include/linux/cpu_cooling.h F: include/linux/thermal.h F: include/uapi/linux/thermal.h +F: tools/lib/thermal/ F: tools/thermal/ THERMAL DRIVER FOR AMLOGIC SOCS diff --git a/tools/Makefile b/tools/Makefile index db2f7b8ebed5..c253cbd27c06 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -31,6 +31,7 @@ help: @echo ' bootconfig - boot config tool' @echo ' spi - spi tools' @echo ' tmon - thermal monitoring and tuning tool' + @echo ' thermal - thermal library' @echo ' tracing - misc tracing tools' @echo ' turbostat - Intel CPU idle stats and freq reporting tool' @echo ' usb - USB testing tools' @@ -85,6 +86,9 @@ perf: FORCE selftests: FORCE $(call descend,testing/$@) +thermal: FORCE + $(call descend,lib/$@) + turbostat x86_energy_perf_policy intel-speed-select: FORCE $(call descend,power/x86/$@) @@ -101,7 +105,7 @@ all: acpi cgroup counter cpupower gpio hv firewire \ perf selftests bootconfig spi turbostat usb \ virtio vm bpf x86_energy_perf_policy \ tmon freefall iio objtool kvm_stat wmi \ - pci debugging tracing + pci debugging tracing thermal acpi_install: $(call descend,power/$(@:_install=),install) @@ -115,6 +119,9 @@ cgroup_install counter_install firewire_install gpio_install hv_install iio_inst selftests_install: $(call descend,testing/$(@:_install=),install) +thermal_install: + $(call descend,lib/$(@:_install=),install) + turbostat_install x86_energy_perf_policy_install intel-speed-select_install: $(call descend,power/x86/$(@:_install=),install) @@ -160,6 +167,9 @@ perf_clean: selftests_clean: $(call descend,testing/$(@:_clean=),clean) +thermal_clean: + $(call descend,lib/thermal,clean) + turbostat_clean x86_energy_perf_policy_clean intel-speed-select_clean: $(call descend,power/x86/$(@:_clean=),clean) @@ -177,6 +187,6 @@ clean: acpi_clean cgroup_clean counter_clean cpupower_clean hv_clean firewire_cl vm_clean bpf_clean iio_clean x86_energy_perf_policy_clean tmon_clean \ freefall_clean build_clean libbpf_clean libsubcmd_clean \ gpio_clean objtool_clean leds_clean wmi_clean pci_clean firmware_clean debugging_clean \ - intel-speed-select_clean tracing_clean + intel-speed-select_clean tracing_clean thermal_clean .PHONY: FORCE diff --git a/tools/lib/thermal/.gitignore b/tools/lib/thermal/.gitignore new file mode 100644 index 000000000000..5d2aeda80fea --- /dev/null +++ b/tools/lib/thermal/.gitignore @@ -0,0 +1,2 @@ +libthermal.so* +libthermal.pc diff --git a/tools/lib/thermal/Build b/tools/lib/thermal/Build new file mode 100644 index 000000000000..4a892d9e24f9 --- /dev/null +++ b/tools/lib/thermal/Build @@ -0,0 +1,5 @@ +libthermal-y += commands.o +libthermal-y += events.o +libthermal-y += thermal_nl.o +libthermal-y += sampling.o +libthermal-y += thermal.o diff --git a/tools/lib/thermal/Makefile b/tools/lib/thermal/Makefile new file mode 100644 index 000000000000..2d0d255fd0e1 --- /dev/null +++ b/tools/lib/thermal/Makefile @@ -0,0 +1,165 @@ +# SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) +# Most of this file is copied from tools/lib/perf/Makefile + +LIBTHERMAL_VERSION = 0 +LIBTHERMAL_PATCHLEVEL = 0 +LIBTHERMAL_EXTRAVERSION = 1 + +MAKEFLAGS += --no-print-directory + +ifeq ($(srctree),) +srctree := $(patsubst %/,%,$(dir $(CURDIR))) +srctree := $(patsubst %/,%,$(dir $(srctree))) +srctree := $(patsubst %/,%,$(dir $(srctree))) +# $(info Determined 'srctree' to be $(srctree)) +endif + +INSTALL = install + +# Use DESTDIR for installing into a different root directory. +# This is useful for building a package. The program will be +# installed in this directory as if it was the root directory. +# Then the build tool can move it later. +DESTDIR ?= +DESTDIR_SQ = '$(subst ','\'',$(DESTDIR))' + +include $(srctree)/tools/scripts/Makefile.include +include $(srctree)/tools/scripts/Makefile.arch + +ifeq ($(LP64), 1) + libdir_relative = lib64 +else + libdir_relative = lib +endif + +prefix ?= +libdir = $(prefix)/$(libdir_relative) + +# Shell quotes +libdir_SQ = $(subst ','\'',$(libdir)) +libdir_relative_SQ = $(subst ','\'',$(libdir_relative)) + +ifeq ("$(origin V)", "command line") + VERBOSE = $(V) +endif +ifndef VERBOSE + VERBOSE = 0 +endif + +ifeq ($(VERBOSE),1) + Q = +else + Q = @ +endif + +# Set compile option CFLAGS +ifdef EXTRA_CFLAGS + CFLAGS := $(EXTRA_CFLAGS) +else + CFLAGS := -g -Wall +endif + +INCLUDES = \ +-I/usr/include/libnl3 \ +-I$(srctree)/tools/lib/thermal/include \ +-I$(srctree)/tools/lib/ \ +-I$(srctree)/tools/include \ +-I$(srctree)/tools/arch/$(SRCARCH)/include/ \ +-I$(srctree)/tools/arch/$(SRCARCH)/include/uapi \ +-I$(srctree)/tools/include/uapi + +# Append required CFLAGS +override CFLAGS += $(EXTRA_WARNINGS) +override CFLAGS += -Werror -Wall +override CFLAGS += -fPIC +override CFLAGS += $(INCLUDES) +override CFLAGS += -fvisibility=hidden +override CFGLAS += -Wl,-L. +override CFGLAS += -Wl,-lthermal + +all: + +export srctree OUTPUT CC LD CFLAGS V +export DESTDIR DESTDIR_SQ + +include $(srctree)/tools/build/Makefile.include + +VERSION_SCRIPT := libthermal.map + +PATCHLEVEL = $(LIBTHERMAL_PATCHLEVEL) +EXTRAVERSION = $(LIBTHERMAL_EXTRAVERSION) +VERSION = $(LIBTHERMAL_VERSION).$(LIBTHERMAL_PATCHLEVEL).$(LIBTHERMAL_EXTRAVERSION) + +LIBTHERMAL_SO := $(OUTPUT)libthermal.so.$(VERSION) +LIBTHERMAL_A := $(OUTPUT)libthermal.a +LIBTHERMAL_IN := $(OUTPUT)libthermal-in.o +LIBTHERMAL_PC := $(OUTPUT)libthermal.pc +LIBTHERMAL_ALL := $(LIBTHERMAL_A) $(OUTPUT)libthermal.so* + +THERMAL_UAPI := include/uapi/linux/thermal.h + +$(THERMAL_UAPI): FORCE + ln -sf $(srctree)/$@ $(srctree)/tools/$@ + +$(LIBTHERMAL_IN): FORCE + $(Q)$(MAKE) $(build)=libthermal + +$(LIBTHERMAL_A): $(LIBTHERMAL_IN) + $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(LIBTHERMAL_IN) + +$(LIBTHERMAL_SO): $(LIBTHERMAL_IN) + $(QUIET_LINK)$(CC) --shared -Wl,-soname,libthermal.so \ + -Wl,--version-script=$(VERSION_SCRIPT) $^ -o $@ + @ln -sf $(@F) $(OUTPUT)libthermal.so + @ln -sf $(@F) $(OUTPUT)libthermal.so.$(LIBTHERMAL_VERSION) + + +libs: $(THERMAL_UAPI) $(LIBTHERMAL_A) $(LIBTHERMAL_SO) $(LIBTHERMAL_PC) + +all: fixdep + $(Q)$(MAKE) libs + +clean: + $(call QUIET_CLEAN, libthermal) $(RM) $(LIBTHERMAL_A) \ + *.o *~ *.a *.so *.so.$(VERSION) *.so.$(LIBTHERMAL_VERSION) .*.d .*.cmd LIBTHERMAL-CFLAGS $(LIBTHERMAL_PC) + +$(LIBTHERMAL_PC): + $(QUIET_GEN)sed -e "s|@PREFIX@|$(prefix)|" \ + -e "s|@LIBDIR@|$(libdir_SQ)|" \ + -e "s|@VERSION@|$(VERSION)|" \ + < libthermal.pc.template > $@ + +define do_install_mkdir + if [ ! -d '$(DESTDIR_SQ)$1' ]; then \ + $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$1'; \ + fi +endef + +define do_install + if [ ! -d '$(DESTDIR_SQ)$2' ]; then \ + $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$2'; \ + fi; \ + $(INSTALL) $1 $(if $3,-m $3,) '$(DESTDIR_SQ)$2' +endef + +install_lib: libs + $(call QUIET_INSTALL, $(LIBTHERMAL_ALL)) \ + $(call do_install_mkdir,$(libdir_SQ)); \ + cp -fpR $(LIBTHERMAL_ALL) $(DESTDIR)$(libdir_SQ) + +install_headers: + $(call QUIET_INSTALL, headers) \ + $(call do_install,include/thermal.h,$(prefix)/include/thermal,644); \ + +install_pkgconfig: $(LIBTHERMAL_PC) + $(call QUIET_INSTALL, $(LIBTHERMAL_PC)) \ + $(call do_install,$(LIBTHERMAL_PC),$(libdir_SQ)/pkgconfig,644) + +install_doc: + $(Q)$(MAKE) -C Documentation install-man install-html install-examples + +install: install_lib install_headers install_pkgconfig + +FORCE: + +.PHONY: all install clean FORCE diff --git a/tools/lib/thermal/commands.c b/tools/lib/thermal/commands.c new file mode 100644 index 000000000000..539083780107 --- /dev/null +++ b/tools/lib/thermal/commands.c @@ -0,0 +1,349 @@ +// SPDX-License-Identifier: LGPL-2.1+ +// Copyright (C) 2022, Linaro Ltd - Daniel Lezcano +#define _GNU_SOURCE +#include +#include +#include +#include + +#include +#include "thermal_nl.h" + +static struct nla_policy thermal_genl_policy[THERMAL_GENL_ATTR_MAX + 1] = { + /* Thermal zone */ + [THERMAL_GENL_ATTR_TZ] = { .type = NLA_NESTED }, + [THERMAL_GENL_ATTR_TZ_ID] = { .type = NLA_U32 }, + [THERMAL_GENL_ATTR_TZ_TEMP] = { .type = NLA_U32 }, + [THERMAL_GENL_ATTR_TZ_TRIP] = { .type = NLA_NESTED }, + [THERMAL_GENL_ATTR_TZ_TRIP_ID] = { .type = NLA_U32 }, + [THERMAL_GENL_ATTR_TZ_TRIP_TEMP] = { .type = NLA_U32 }, + [THERMAL_GENL_ATTR_TZ_TRIP_TYPE] = { .type = NLA_U32 }, + [THERMAL_GENL_ATTR_TZ_TRIP_HYST] = { .type = NLA_U32 }, + [THERMAL_GENL_ATTR_TZ_MODE] = { .type = NLA_U32 }, + [THERMAL_GENL_ATTR_TZ_CDEV_WEIGHT] = { .type = NLA_U32 }, + [THERMAL_GENL_ATTR_TZ_NAME] = { .type = NLA_STRING }, + + /* Governor(s) */ + [THERMAL_GENL_ATTR_TZ_GOV] = { .type = NLA_NESTED }, + [THERMAL_GENL_ATTR_TZ_GOV_NAME] = { .type = NLA_STRING }, + + /* Cooling devices */ + [THERMAL_GENL_ATTR_CDEV] = { .type = NLA_NESTED }, + [THERMAL_GENL_ATTR_CDEV_ID] = { .type = NLA_U32 }, + [THERMAL_GENL_ATTR_CDEV_CUR_STATE] = { .type = NLA_U32 }, + [THERMAL_GENL_ATTR_CDEV_MAX_STATE] = { .type = NLA_U32 }, + [THERMAL_GENL_ATTR_CDEV_NAME] = { .type = NLA_STRING }, +}; + +static int parse_tz_get(struct genl_info *info, struct thermal_zone **tz) +{ + struct nlattr *attr; + struct thermal_zone *__tz = NULL; + size_t size = 0; + int rem; + + nla_for_each_nested(attr, info->attrs[THERMAL_GENL_ATTR_TZ], rem) { + + if (nla_type(attr) == THERMAL_GENL_ATTR_TZ_ID) { + + size++; + + __tz = realloc(__tz, sizeof(*__tz) * (size + 2)); + if (!__tz) + return THERMAL_ERROR; + + __tz[size - 1].id = nla_get_u32(attr); + } + + + if (nla_type(attr) == THERMAL_GENL_ATTR_TZ_NAME) + nla_strlcpy(__tz[size - 1].name, attr, + THERMAL_NAME_LENGTH); + } + + /* + * We end the array of thermal zones + */ + __tz[size].id = -1; + + *tz = __tz; + + return THERMAL_SUCCESS; +} + +static int parse_cdev_get(struct genl_info *info, struct thermal_cdev **cdev) +{ + struct nlattr *attr; + struct thermal_cdev *__cdev = NULL; + size_t size = 0; + int rem; + + nla_for_each_nested(attr, info->attrs[THERMAL_GENL_ATTR_CDEV], rem) { + + if (nla_type(attr) == THERMAL_GENL_ATTR_CDEV_ID) { + + size++; + + __cdev = realloc(__cdev, sizeof(*__cdev) * (size + 2)); + if (!__cdev) + return THERMAL_ERROR; + + __cdev[size - 1].id = nla_get_u32(attr); + } + + if (nla_type(attr) == THERMAL_GENL_ATTR_CDEV_NAME) { + nla_strlcpy(__cdev[size - 1].name, attr, + THERMAL_NAME_LENGTH); + } + + if (nla_type(attr) == THERMAL_GENL_ATTR_CDEV_CUR_STATE) + __cdev[size - 1].cur_state = nla_get_u32(attr); + + if (nla_type(attr) == THERMAL_GENL_ATTR_CDEV_MAX_STATE) + __cdev[size - 1].max_state = nla_get_u32(attr); + } + + __cdev[size].id = -1; + + *cdev = __cdev; + + return THERMAL_SUCCESS; +} + +static int parse_tz_get_trip(struct genl_info *info, struct thermal_zone *tz) +{ + struct nlattr *attr; + struct thermal_trip *__tt = NULL; + size_t size = 0; + int rem; + + nla_for_each_nested(attr, info->attrs[THERMAL_GENL_ATTR_TZ_TRIP], rem) { + + if (nla_type(attr) == THERMAL_GENL_ATTR_TZ_TRIP_ID) { + + size++; + + __tt = realloc(__tt, sizeof(*__tt) * (size + 2)); + if (!__tt) + return THERMAL_ERROR; + + __tt[size - 1].id = nla_get_u32(attr); + } + + if (nla_type(attr) == THERMAL_GENL_ATTR_TZ_TRIP_TYPE) + __tt[size - 1].type = nla_get_u32(attr); + + if (nla_type(attr) == THERMAL_GENL_ATTR_TZ_TRIP_TEMP) + __tt[size - 1].temp = nla_get_u32(attr); + + if (nla_type(attr) == THERMAL_GENL_ATTR_TZ_TRIP_HYST) + __tt[size - 1].hyst = nla_get_u32(attr); + } + + __tt[size].id = -1; + + tz->trip = __tt; + + return THERMAL_SUCCESS; +} + +static int parse_tz_get_temp(struct genl_info *info, struct thermal_zone *tz) +{ + int id = -1; + + if (info->attrs[THERMAL_GENL_ATTR_TZ_ID]) + id = nla_get_u32(info->attrs[THERMAL_GENL_ATTR_TZ_ID]); + + if (tz->id != id) + return THERMAL_ERROR; + + if (info->attrs[THERMAL_GENL_ATTR_TZ_TEMP]) + tz->temp = nla_get_u32(info->attrs[THERMAL_GENL_ATTR_TZ_TEMP]); + + return THERMAL_SUCCESS; +} + +static int parse_tz_get_gov(struct genl_info *info, struct thermal_zone *tz) +{ + int id = -1; + + if (info->attrs[THERMAL_GENL_ATTR_TZ_ID]) + id = nla_get_u32(info->attrs[THERMAL_GENL_ATTR_TZ_ID]); + + if (tz->id != id) + return THERMAL_ERROR; + + if (info->attrs[THERMAL_GENL_ATTR_TZ_GOV_NAME]) { + nla_strlcpy(tz->governor, + info->attrs[THERMAL_GENL_ATTR_TZ_GOV_NAME], + THERMAL_NAME_LENGTH); + } + + return THERMAL_SUCCESS; +} + +static int handle_netlink(struct nl_cache_ops *unused, + struct genl_cmd *cmd, + struct genl_info *info, void *arg) +{ + int ret; + + switch (cmd->c_id) { + + case THERMAL_GENL_CMD_TZ_GET_ID: + ret = parse_tz_get(info, arg); + break; + + case THERMAL_GENL_CMD_CDEV_GET: + ret = parse_cdev_get(info, arg); + break; + + case THERMAL_GENL_CMD_TZ_GET_TEMP: + ret = parse_tz_get_temp(info, arg); + break; + + case THERMAL_GENL_CMD_TZ_GET_TRIP: + ret = parse_tz_get_trip(info, arg); + break; + + case THERMAL_GENL_CMD_TZ_GET_GOV: + ret = parse_tz_get_gov(info, arg); + break; + + default: + return THERMAL_ERROR; + }; + + return ret; +} + +static struct genl_cmd thermal_cmds[] = { + { + .c_id = THERMAL_GENL_CMD_TZ_GET_ID, + .c_name = (char *)"List thermal zones", + .c_msg_parser = handle_netlink, + .c_maxattr = THERMAL_GENL_ATTR_MAX, + .c_attr_policy = thermal_genl_policy, + }, + { + .c_id = THERMAL_GENL_CMD_TZ_GET_GOV, + .c_name = (char *)"Get governor", + .c_msg_parser = handle_netlink, + .c_maxattr = THERMAL_GENL_ATTR_MAX, + .c_attr_policy = thermal_genl_policy, + }, + { + .c_id = THERMAL_GENL_CMD_TZ_GET_TEMP, + .c_name = (char *)"Get thermal zone temperature", + .c_msg_parser = handle_netlink, + .c_maxattr = THERMAL_GENL_ATTR_MAX, + .c_attr_policy = thermal_genl_policy, + }, + { + .c_id = THERMAL_GENL_CMD_TZ_GET_TRIP, + .c_name = (char *)"Get thermal zone trip points", + .c_msg_parser = handle_netlink, + .c_maxattr = THERMAL_GENL_ATTR_MAX, + .c_attr_policy = thermal_genl_policy, + }, + { + .c_id = THERMAL_GENL_CMD_CDEV_GET, + .c_name = (char *)"Get cooling devices", + .c_msg_parser = handle_netlink, + .c_maxattr = THERMAL_GENL_ATTR_MAX, + .c_attr_policy = thermal_genl_policy, + }, +}; + +static struct genl_ops thermal_cmd_ops = { + .o_name = (char *)"thermal", + .o_cmds = thermal_cmds, + .o_ncmds = ARRAY_SIZE(thermal_cmds), +}; + +static thermal_error_t thermal_genl_auto(struct thermal_handler *th, int id, int cmd, + int flags, void *arg) +{ + struct nl_msg *msg; + void *hdr; + + msg = nlmsg_alloc(); + if (!msg) + return THERMAL_ERROR; + + hdr = genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, thermal_cmd_ops.o_id, + 0, flags, cmd, THERMAL_GENL_VERSION); + if (!hdr) + return THERMAL_ERROR; + + if (id >= 0 && nla_put_u32(msg, THERMAL_GENL_ATTR_TZ_ID, id)) + return THERMAL_ERROR; + + if (nl_send_msg(th->sk_cmd, th->cb_cmd, msg, genl_handle_msg, arg)) + return THERMAL_ERROR; + + nlmsg_free(msg); + + return THERMAL_SUCCESS; +} + +thermal_error_t thermal_cmd_get_tz(struct thermal_handler *th, struct thermal_zone **tz) +{ + return thermal_genl_auto(th, -1, THERMAL_GENL_CMD_TZ_GET_ID, + NLM_F_DUMP | NLM_F_ACK, tz); +} + +thermal_error_t thermal_cmd_get_cdev(struct thermal_handler *th, struct thermal_cdev **tc) +{ + return thermal_genl_auto(th, -1, THERMAL_GENL_CMD_CDEV_GET, + NLM_F_DUMP | NLM_F_ACK, tc); +} + +thermal_error_t thermal_cmd_get_trip(struct thermal_handler *th, struct thermal_zone *tz) +{ + return thermal_genl_auto(th, tz->id, THERMAL_GENL_CMD_TZ_GET_TRIP, + 0, tz); +} + +thermal_error_t thermal_cmd_get_governor(struct thermal_handler *th, struct thermal_zone *tz) +{ + return thermal_genl_auto(th, tz->id, THERMAL_GENL_CMD_TZ_GET_GOV, 0, tz); +} + +thermal_error_t thermal_cmd_get_temp(struct thermal_handler *th, struct thermal_zone *tz) +{ + return thermal_genl_auto(th, tz->id, THERMAL_GENL_CMD_TZ_GET_TEMP, 0, tz); +} + +thermal_error_t thermal_cmd_exit(struct thermal_handler *th) +{ + if (genl_unregister_family(&thermal_cmd_ops)) + return THERMAL_ERROR; + + nl_thermal_disconnect(th->sk_cmd, th->cb_cmd); + + return THERMAL_SUCCESS; +} + +thermal_error_t thermal_cmd_init(struct thermal_handler *th) +{ + int ret; + int family; + + if (nl_thermal_connect(&th->sk_cmd, &th->cb_cmd)) + return THERMAL_ERROR; + + ret = genl_register_family(&thermal_cmd_ops); + if (ret) + return THERMAL_ERROR; + + ret = genl_ops_resolve(th->sk_cmd, &thermal_cmd_ops); + if (ret) + return THERMAL_ERROR; + + family = genl_ctrl_resolve(th->sk_cmd, "nlctrl"); + if (family != GENL_ID_CTRL) + return THERMAL_ERROR; + + return THERMAL_SUCCESS; +} diff --git a/tools/lib/thermal/events.c b/tools/lib/thermal/events.c new file mode 100644 index 000000000000..a7a55d1a0c4c --- /dev/null +++ b/tools/lib/thermal/events.c @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: LGPL-2.1+ +// Copyright (C) 2022, Linaro Ltd - Daniel Lezcano +#include +#include +#include +#include + + +#include +#include "thermal_nl.h" + +/* + * Optimization: fill this array to tell which event we do want to pay + * attention to. That happens at init time with the ops + * structure. Each ops will enable the event and the general handler + * will be able to discard the event if there is not ops associated + * with it. + */ +static int enabled_ops[__THERMAL_GENL_EVENT_MAX]; + +static int handle_thermal_event(struct nl_msg *n, void *arg) +{ + struct nlmsghdr *nlh = nlmsg_hdr(n); + struct genlmsghdr *genlhdr = genlmsg_hdr(nlh); + struct nlattr *attrs[THERMAL_GENL_ATTR_MAX + 1]; + struct thermal_handler_param *thp = arg; + struct thermal_events_ops *ops = &thp->th->ops->events; + + genlmsg_parse(nlh, 0, attrs, THERMAL_GENL_ATTR_MAX, NULL); + + arg = thp->arg; + + /* + * This is an event we don't care of, bail out. + */ + if (!enabled_ops[genlhdr->cmd]) + return THERMAL_SUCCESS; + + switch (genlhdr->cmd) { + + case THERMAL_GENL_EVENT_TZ_CREATE: + return ops->tz_create(nla_get_string(attrs[THERMAL_GENL_ATTR_TZ_NAME]), + nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]), arg); + + case THERMAL_GENL_EVENT_TZ_DELETE: + return ops->tz_delete(nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]), arg); + + case THERMAL_GENL_EVENT_TZ_ENABLE: + return ops->tz_enable(nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]), arg); + + case THERMAL_GENL_EVENT_TZ_DISABLE: + return ops->tz_disable(nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]), arg); + + case THERMAL_GENL_EVENT_TZ_TRIP_CHANGE: + return ops->trip_change(nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]), + nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_ID]), + nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_TYPE]), + nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_TEMP]), + nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_HYST]), arg); + + case THERMAL_GENL_EVENT_TZ_TRIP_ADD: + return ops->trip_add(nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]), + nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_ID]), + nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_TYPE]), + nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_TEMP]), + nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_HYST]), arg); + + case THERMAL_GENL_EVENT_TZ_TRIP_DELETE: + return ops->trip_delete(nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]), + nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_ID]), arg); + + case THERMAL_GENL_EVENT_TZ_TRIP_UP: + return ops->trip_high(nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]), + nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_ID]), + nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TEMP]), arg); + + case THERMAL_GENL_EVENT_TZ_TRIP_DOWN: + return ops->trip_low(nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]), + nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_ID]), + nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TEMP]), arg); + + case THERMAL_GENL_EVENT_CDEV_ADD: + return ops->cdev_add(nla_get_string(attrs[THERMAL_GENL_ATTR_CDEV_NAME]), + nla_get_u32(attrs[THERMAL_GENL_ATTR_CDEV_ID]), + nla_get_u32(attrs[THERMAL_GENL_ATTR_CDEV_MAX_STATE]), arg); + + case THERMAL_GENL_EVENT_CDEV_DELETE: + return ops->cdev_delete(nla_get_u32(attrs[THERMAL_GENL_ATTR_CDEV_ID]), arg); + + case THERMAL_GENL_EVENT_CDEV_STATE_UPDATE: + return ops->cdev_update(nla_get_u32(attrs[THERMAL_GENL_ATTR_CDEV_ID]), + nla_get_u32(attrs[THERMAL_GENL_ATTR_CDEV_CUR_STATE]), arg); + + case THERMAL_GENL_EVENT_TZ_GOV_CHANGE: + return ops->gov_change(nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]), + nla_get_string(attrs[THERMAL_GENL_ATTR_GOV_NAME]), arg); + default: + return -1; + } +} + +static void thermal_events_ops_init(struct thermal_events_ops *ops) +{ + enabled_ops[THERMAL_GENL_EVENT_TZ_CREATE] = !!ops->tz_create; + enabled_ops[THERMAL_GENL_EVENT_TZ_DELETE] = !!ops->tz_delete; + enabled_ops[THERMAL_GENL_EVENT_TZ_DISABLE] = !!ops->tz_disable; + enabled_ops[THERMAL_GENL_EVENT_TZ_ENABLE] = !!ops->tz_enable; + enabled_ops[THERMAL_GENL_EVENT_TZ_TRIP_UP] = !!ops->trip_high; + enabled_ops[THERMAL_GENL_EVENT_TZ_TRIP_DOWN] = !!ops->trip_low; + enabled_ops[THERMAL_GENL_EVENT_TZ_TRIP_CHANGE] = !!ops->trip_change; + enabled_ops[THERMAL_GENL_EVENT_TZ_TRIP_ADD] = !!ops->trip_add; + enabled_ops[THERMAL_GENL_EVENT_TZ_TRIP_DELETE] = !!ops->trip_delete; + enabled_ops[THERMAL_GENL_EVENT_CDEV_ADD] = !!ops->cdev_add; + enabled_ops[THERMAL_GENL_EVENT_CDEV_DELETE] = !!ops->cdev_delete; + enabled_ops[THERMAL_GENL_EVENT_CDEV_STATE_UPDATE] = !!ops->cdev_update; + enabled_ops[THERMAL_GENL_EVENT_TZ_GOV_CHANGE] = !!ops->gov_change; +} + +thermal_error_t thermal_events_handle(struct thermal_handler *th, void *arg) +{ + struct thermal_handler_param thp = { .th = th, .arg = arg }; + + if (!th) + return THERMAL_ERROR; + + if (nl_cb_set(th->cb_event, NL_CB_VALID, NL_CB_CUSTOM, + handle_thermal_event, &thp)) + return THERMAL_ERROR; + + return nl_recvmsgs(th->sk_event, th->cb_event); +} + +int thermal_events_fd(struct thermal_handler *th) +{ + if (!th) + return -1; + + return nl_socket_get_fd(th->sk_event); +} + +thermal_error_t thermal_events_exit(struct thermal_handler *th) +{ + if (nl_unsubscribe_thermal(th->sk_event, th->cb_event, + THERMAL_GENL_EVENT_GROUP_NAME)) + return THERMAL_ERROR; + + nl_thermal_disconnect(th->sk_event, th->cb_event); + + return THERMAL_SUCCESS; +} + +thermal_error_t thermal_events_init(struct thermal_handler *th) +{ + thermal_events_ops_init(&th->ops->events); + + if (nl_thermal_connect(&th->sk_event, &th->cb_event)) + return THERMAL_ERROR; + + if (nl_subscribe_thermal(th->sk_event, th->cb_event, + THERMAL_GENL_EVENT_GROUP_NAME)) + return THERMAL_ERROR; + + return THERMAL_SUCCESS; +} diff --git a/tools/lib/thermal/include/thermal.h b/tools/lib/thermal/include/thermal.h new file mode 100644 index 000000000000..1abc560602cf --- /dev/null +++ b/tools/lib/thermal/include/thermal.h @@ -0,0 +1,142 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +/* Copyright (C) 2022, Linaro Ltd - Daniel Lezcano */ +#ifndef __LIBTHERMAL_H +#define __LIBTHERMAL_H + +#include + +#ifndef LIBTHERMAL_API +#define LIBTHERMAL_API __attribute__((visibility("default"))) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +struct thermal_sampling_ops { + int (*tz_temp)(int tz_id, int temp, void *arg); +}; + +struct thermal_events_ops { + int (*tz_create)(const char *name, int tz_id, void *arg); + int (*tz_delete)(int tz_id, void *arg); + int (*tz_enable)(int tz_id, void *arg); + int (*tz_disable)(int tz_id, void *arg); + int (*trip_high)(int tz_id, int trip_id, int temp, void *arg); + int (*trip_low)(int tz_id, int trip_id, int temp, void *arg); + int (*trip_add)(int tz_id, int trip_id, int type, int temp, int hyst, void *arg); + int (*trip_change)(int tz_id, int trip_id, int type, int temp, int hyst, void *arg); + int (*trip_delete)(int tz_id, int trip_id, void *arg); + int (*cdev_add)(const char *name, int cdev_id, int max_state, void *arg); + int (*cdev_delete)(int cdev_id, void *arg); + int (*cdev_update)(int cdev_id, int cur_state, void *arg); + int (*gov_change)(int tz_id, const char *gov_name, void *arg); +}; + +struct thermal_ops { + struct thermal_sampling_ops sampling; + struct thermal_events_ops events; +}; + +struct thermal_trip { + int id; + int type; + int temp; + int hyst; +}; + +struct thermal_zone { + int id; + int temp; + char name[THERMAL_NAME_LENGTH]; + char governor[THERMAL_NAME_LENGTH]; + struct thermal_trip *trip; +}; + +struct thermal_cdev { + int id; + char name[THERMAL_NAME_LENGTH]; + int max_state; + int min_state; + int cur_state; +}; + +typedef enum { + THERMAL_ERROR = -1, + THERMAL_SUCCESS = 0, +} thermal_error_t; + +struct thermal_handler; + +typedef int (*cb_tz_t)(struct thermal_zone *, void *); + +typedef int (*cb_tt_t)(struct thermal_trip *, void *); + +typedef int (*cb_tc_t)(struct thermal_cdev *, void *); + +LIBTHERMAL_API int for_each_thermal_zone(struct thermal_zone *tz, cb_tz_t cb, void *arg); + +LIBTHERMAL_API int for_each_thermal_trip(struct thermal_trip *tt, cb_tt_t cb, void *arg); + +LIBTHERMAL_API int for_each_thermal_cdev(struct thermal_cdev *cdev, cb_tc_t cb, void *arg); + +LIBTHERMAL_API struct thermal_zone *thermal_zone_find_by_name(struct thermal_zone *tz, + const char *name); + +LIBTHERMAL_API struct thermal_zone *thermal_zone_find_by_id(struct thermal_zone *tz, int id); + +LIBTHERMAL_API struct thermal_zone *thermal_zone_discover(struct thermal_handler *th); + +LIBTHERMAL_API struct thermal_handler *thermal_init(struct thermal_ops *ops); + +LIBTHERMAL_API void thermal_exit(struct thermal_handler *th); + +/* + * Netlink thermal events + */ +LIBTHERMAL_API thermal_error_t thermal_events_exit(struct thermal_handler *th); + +LIBTHERMAL_API thermal_error_t thermal_events_init(struct thermal_handler *th); + +LIBTHERMAL_API thermal_error_t thermal_events_handle(struct thermal_handler *th, void *arg); + +LIBTHERMAL_API int thermal_events_fd(struct thermal_handler *th); + +/* + * Netlink thermal commands + */ +LIBTHERMAL_API thermal_error_t thermal_cmd_exit(struct thermal_handler *th); + +LIBTHERMAL_API thermal_error_t thermal_cmd_init(struct thermal_handler *th); + +LIBTHERMAL_API thermal_error_t thermal_cmd_get_tz(struct thermal_handler *th, + struct thermal_zone **tz); + +LIBTHERMAL_API thermal_error_t thermal_cmd_get_cdev(struct thermal_handler *th, + struct thermal_cdev **tc); + +LIBTHERMAL_API thermal_error_t thermal_cmd_get_trip(struct thermal_handler *th, + struct thermal_zone *tz); + +LIBTHERMAL_API thermal_error_t thermal_cmd_get_governor(struct thermal_handler *th, + struct thermal_zone *tz); + +LIBTHERMAL_API thermal_error_t thermal_cmd_get_temp(struct thermal_handler *th, + struct thermal_zone *tz); + +/* + * Netlink thermal samples + */ +LIBTHERMAL_API thermal_error_t thermal_sampling_exit(struct thermal_handler *th); + +LIBTHERMAL_API thermal_error_t thermal_sampling_init(struct thermal_handler *th); + +LIBTHERMAL_API thermal_error_t thermal_sampling_handle(struct thermal_handler *th, void *arg); + +LIBTHERMAL_API int thermal_sampling_fd(struct thermal_handler *th); + +#endif /* __LIBTHERMAL_H */ + +#ifdef __cplusplus +} +#endif diff --git a/tools/lib/thermal/libthermal.map b/tools/lib/thermal/libthermal.map new file mode 100644 index 000000000000..d5e77738c7a4 --- /dev/null +++ b/tools/lib/thermal/libthermal.map @@ -0,0 +1,25 @@ +LIBTHERMAL_0.0.1 { + global: + thermal_init; + for_each_thermal_zone; + for_each_thermal_trip; + for_each_thermal_cdev; + thermal_zone_find_by_name; + thermal_zone_find_by_id; + thermal_zone_discover; + thermal_init; + thermal_events_init; + thermal_events_handle; + thermal_events_fd; + thermal_cmd_init; + thermal_cmd_get_tz; + thermal_cmd_get_cdev; + thermal_cmd_get_trip; + thermal_cmd_get_governor; + thermal_cmd_get_temp; + thermal_sampling_init; + thermal_sampling_handle; + thermal_sampling_fd; +local: + *; +}; diff --git a/tools/lib/thermal/libthermal.pc.template b/tools/lib/thermal/libthermal.pc.template new file mode 100644 index 000000000000..6f3769731b59 --- /dev/null +++ b/tools/lib/thermal/libthermal.pc.template @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) + +prefix=@PREFIX@ +libdir=@LIBDIR@ +includedir=${prefix}/include + +Name: libthermal +Description: thermal library +Requires: libnl-3.0 libnl-genl-3.0 +Version: @VERSION@ +Libs: -L${libdir} -lnl-genl-3 -lnl-3 +Cflags: -I${includedir} -I{include}/libnl3 diff --git a/tools/lib/thermal/sampling.c b/tools/lib/thermal/sampling.c new file mode 100644 index 000000000000..ee818f4e9654 --- /dev/null +++ b/tools/lib/thermal/sampling.c @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: LGPL-2.1+ +// Copyright (C) 2022, Linaro Ltd - Daniel Lezcano +#include +#include +#include +#include + +#include +#include "thermal_nl.h" + +static int handle_thermal_sample(struct nl_msg *n, void *arg) +{ + struct nlmsghdr *nlh = nlmsg_hdr(n); + struct genlmsghdr *genlhdr = genlmsg_hdr(nlh); + struct nlattr *attrs[THERMAL_GENL_ATTR_MAX + 1]; + struct thermal_handler_param *thp = arg; + struct thermal_handler *th = thp->th; + + genlmsg_parse(nlh, 0, attrs, THERMAL_GENL_ATTR_MAX, NULL); + + switch (genlhdr->cmd) { + + case THERMAL_GENL_SAMPLING_TEMP: + return th->ops->sampling.tz_temp( + nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]), + nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TEMP]), arg); + default: + return THERMAL_ERROR; + } +} + +thermal_error_t thermal_sampling_handle(struct thermal_handler *th, void *arg) +{ + struct thermal_handler_param thp = { .th = th, .arg = arg }; + + if (!th) + return THERMAL_ERROR; + + if (nl_cb_set(th->cb_sampling, NL_CB_VALID, NL_CB_CUSTOM, + handle_thermal_sample, &thp)) + return THERMAL_ERROR; + + return nl_recvmsgs(th->sk_sampling, th->cb_sampling); +} + +int thermal_sampling_fd(struct thermal_handler *th) +{ + if (!th) + return -1; + + return nl_socket_get_fd(th->sk_sampling); +} + +thermal_error_t thermal_sampling_exit(struct thermal_handler *th) +{ + if (nl_unsubscribe_thermal(th->sk_sampling, th->cb_sampling, + THERMAL_GENL_EVENT_GROUP_NAME)) + return THERMAL_ERROR; + + nl_thermal_disconnect(th->sk_sampling, th->cb_sampling); + + return THERMAL_SUCCESS; +} + +thermal_error_t thermal_sampling_init(struct thermal_handler *th) +{ + if (nl_thermal_connect(&th->sk_sampling, &th->cb_sampling)) + return THERMAL_ERROR; + + if (nl_subscribe_thermal(th->sk_sampling, th->cb_sampling, + THERMAL_GENL_SAMPLING_GROUP_NAME)) + return THERMAL_ERROR; + + return THERMAL_SUCCESS; +} diff --git a/tools/lib/thermal/thermal.c b/tools/lib/thermal/thermal.c new file mode 100644 index 000000000000..a80b967ce334 --- /dev/null +++ b/tools/lib/thermal/thermal.c @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: LGPL-2.1+ +// Copyright (C) 2022, Linaro Ltd - Daniel Lezcano +#include +#include + +#include "thermal_nl.h" + +int for_each_thermal_cdev(struct thermal_cdev *cdev, cb_tc_t cb, void *arg) +{ + int i, ret = 0; + + for (i = 0; cdev[i].id != -1; i++) + ret |= cb(&cdev[i], arg); + + return ret; +} + +int for_each_thermal_trip(struct thermal_trip *tt, cb_tt_t cb, void *arg) +{ + int i, ret = 0; + + for (i = 0; tt[i].id != -1; i++) + ret |= cb(&tt[i], arg); + + return ret; +} + +int for_each_thermal_zone(struct thermal_zone *tz, cb_tz_t cb, void *arg) +{ + int i, ret = 0; + + for (i = 0; tz[i].id != -1; i++) + ret |= cb(&tz[i], arg); + + return ret; +} + +struct thermal_zone *thermal_zone_find_by_name(struct thermal_zone *tz, + const char *name) +{ + int i; + + if (!name) + return NULL; + + for (i = 0; tz[i].id != -1; i++) { + if (!strcmp(tz[i].name, name)) + return &tz[i]; + } + + return NULL; +} + +struct thermal_zone *thermal_zone_find_by_id(struct thermal_zone *tz, int id) +{ + int i; + + if (id < 0) + return NULL; + + for (i = 0; tz[i].id != -1; i++) { + if (tz[i].id == id) + return &tz[i]; + } + + return NULL; +} + +static int __thermal_zone_discover(struct thermal_zone *tz, void *th) +{ + if (thermal_cmd_get_trip(th, tz) < 0) + return -1; + + if (thermal_cmd_get_governor(th, tz)) + return -1; + + return 0; +} + +struct thermal_zone *thermal_zone_discover(struct thermal_handler *th) +{ + struct thermal_zone *tz; + + if (thermal_cmd_get_tz(th, &tz) < 0) + return NULL; + + if (for_each_thermal_zone(tz, __thermal_zone_discover, th)) + return NULL; + + return tz; +} + +void thermal_exit(struct thermal_handler *th) +{ + thermal_cmd_exit(th); + thermal_events_exit(th); + thermal_sampling_exit(th); + + free(th); +} + +struct thermal_handler *thermal_init(struct thermal_ops *ops) +{ + struct thermal_handler *th; + + th = malloc(sizeof(*th)); + if (!th) + return NULL; + th->ops = ops; + + if (thermal_events_init(th)) + goto out_free; + + if (thermal_sampling_init(th)) + goto out_free; + + if (thermal_cmd_init(th)) + goto out_free; + + return th; + +out_free: + free(th); + + return NULL; +} diff --git a/tools/lib/thermal/thermal_nl.c b/tools/lib/thermal/thermal_nl.c new file mode 100644 index 000000000000..b05cf9569858 --- /dev/null +++ b/tools/lib/thermal/thermal_nl.c @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: LGPL-2.1+ +// Copyright (C) 2022, Linaro Ltd - Daniel Lezcano +#include +#include +#include +#include + +#include +#include "thermal_nl.h" + +struct handler_args { + const char *group; + int id; +}; + +static __thread int err; +static __thread int done; + +static int nl_seq_check_handler(struct nl_msg *msg, void *arg) +{ + return NL_OK; +} + +static int nl_error_handler(struct sockaddr_nl *nla, struct nlmsgerr *nl_err, + void *arg) +{ + int *ret = arg; + + if (ret) + *ret = nl_err->error; + + return NL_STOP; +} + +static int nl_finish_handler(struct nl_msg *msg, void *arg) +{ + int *ret = arg; + + if (ret) + *ret = 1; + + return NL_OK; +} + +static int nl_ack_handler(struct nl_msg *msg, void *arg) +{ + int *ret = arg; + + if (ret) + *ret = 1; + + return NL_OK; +} + +int nl_send_msg(struct nl_sock *sock, struct nl_cb *cb, struct nl_msg *msg, + int (*rx_handler)(struct nl_msg *, void *), void *data) +{ + if (!rx_handler) + return THERMAL_ERROR; + + err = nl_send_auto_complete(sock, msg); + if (err < 0) + return err; + + nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, rx_handler, data); + + err = done = 0; + + while (err == 0 && done == 0) + nl_recvmsgs(sock, cb); + + return err; +} + +static int nl_family_handler(struct nl_msg *msg, void *arg) +{ + struct handler_args *grp = arg; + struct nlattr *tb[CTRL_ATTR_MAX + 1]; + struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); + struct nlattr *mcgrp; + int rem_mcgrp; + + nla_parse(tb, CTRL_ATTR_MAX, genlmsg_attrdata(gnlh, 0), + genlmsg_attrlen(gnlh, 0), NULL); + + if (!tb[CTRL_ATTR_MCAST_GROUPS]) + return THERMAL_ERROR; + + nla_for_each_nested(mcgrp, tb[CTRL_ATTR_MCAST_GROUPS], rem_mcgrp) { + + struct nlattr *tb_mcgrp[CTRL_ATTR_MCAST_GRP_MAX + 1]; + + nla_parse(tb_mcgrp, CTRL_ATTR_MCAST_GRP_MAX, + nla_data(mcgrp), nla_len(mcgrp), NULL); + + if (!tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME] || + !tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID]) + continue; + + if (strncmp(nla_data(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME]), + grp->group, + nla_len(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME]))) + continue; + + grp->id = nla_get_u32(tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID]); + + break; + } + + return THERMAL_SUCCESS; +} + +static int nl_get_multicast_id(struct nl_sock *sock, struct nl_cb *cb, + const char *family, const char *group) +{ + struct nl_msg *msg; + int ret = 0, ctrlid; + struct handler_args grp = { + .group = group, + .id = -ENOENT, + }; + + msg = nlmsg_alloc(); + if (!msg) + return THERMAL_ERROR; + + ctrlid = genl_ctrl_resolve(sock, "nlctrl"); + + genlmsg_put(msg, 0, 0, ctrlid, 0, 0, CTRL_CMD_GETFAMILY, 0); + + nla_put_string(msg, CTRL_ATTR_FAMILY_NAME, family); + + ret = nl_send_msg(sock, cb, msg, nl_family_handler, &grp); + if (ret) + goto nla_put_failure; + + ret = grp.id; + +nla_put_failure: + nlmsg_free(msg); + return ret; +} + +int nl_thermal_connect(struct nl_sock **nl_sock, struct nl_cb **nl_cb) +{ + struct nl_cb *cb; + struct nl_sock *sock; + + cb = nl_cb_alloc(NL_CB_DEFAULT); + if (!cb) + return THERMAL_ERROR; + + sock = nl_socket_alloc(); + if (!sock) + goto out_cb_free; + + if (genl_connect(sock)) + goto out_socket_free; + + if (nl_cb_err(cb, NL_CB_CUSTOM, nl_error_handler, &err) || + nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, nl_finish_handler, &done) || + nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, nl_ack_handler, &done) || + nl_cb_set(cb, NL_CB_SEQ_CHECK, NL_CB_CUSTOM, nl_seq_check_handler, &done)) + return THERMAL_ERROR; + + *nl_sock = sock; + *nl_cb = cb; + + return THERMAL_SUCCESS; + +out_socket_free: + nl_socket_free(sock); +out_cb_free: + nl_cb_put(cb); + return THERMAL_ERROR; +} + +void nl_thermal_disconnect(struct nl_sock *nl_sock, struct nl_cb *nl_cb) +{ + nl_close(nl_sock); + nl_socket_free(nl_sock); + nl_cb_put(nl_cb); +} + +int nl_unsubscribe_thermal(struct nl_sock *nl_sock, struct nl_cb *nl_cb, + const char *group) +{ + int mcid; + + mcid = nl_get_multicast_id(nl_sock, nl_cb, THERMAL_GENL_FAMILY_NAME, + group); + if (mcid < 0) + return THERMAL_ERROR; + + if (nl_socket_drop_membership(nl_sock, mcid)) + return THERMAL_ERROR; + + return THERMAL_SUCCESS; +} + +int nl_subscribe_thermal(struct nl_sock *nl_sock, struct nl_cb *nl_cb, + const char *group) +{ + int mcid; + + mcid = nl_get_multicast_id(nl_sock, nl_cb, THERMAL_GENL_FAMILY_NAME, + group); + if (mcid < 0) + return THERMAL_ERROR; + + if (nl_socket_add_membership(nl_sock, mcid)) + return THERMAL_ERROR; + + return THERMAL_SUCCESS; +} diff --git a/tools/lib/thermal/thermal_nl.h b/tools/lib/thermal/thermal_nl.h new file mode 100644 index 000000000000..ddf635642f07 --- /dev/null +++ b/tools/lib/thermal/thermal_nl.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +/* Copyright (C) 2022, Linaro Ltd - Daniel Lezcano */ +#ifndef __THERMAL_H +#define __THERMAL_H + +#include +#include +#include +#include + +struct thermal_handler { + int done; + int error; + struct thermal_ops *ops; + struct nl_msg *msg; + struct nl_sock *sk_event; + struct nl_sock *sk_sampling; + struct nl_sock *sk_cmd; + struct nl_cb *cb_cmd; + struct nl_cb *cb_event; + struct nl_cb *cb_sampling; +}; + +struct thermal_handler_param { + struct thermal_handler *th; + void *arg; +}; + +/* + * Low level netlink + */ +extern int nl_subscribe_thermal(struct nl_sock *nl_sock, struct nl_cb *nl_cb, + const char *group); + +extern int nl_unsubscribe_thermal(struct nl_sock *nl_sock, struct nl_cb *nl_cb, + const char *group); + +extern int nl_thermal_connect(struct nl_sock **nl_sock, struct nl_cb **nl_cb); + +extern void nl_thermal_disconnect(struct nl_sock *nl_sock, struct nl_cb *nl_cb); + +extern int nl_send_msg(struct nl_sock *sock, struct nl_cb *nl_cb, struct nl_msg *msg, + int (*rx_handler)(struct nl_msg *, void *), + void *data); + +#endif /* __THERMAL_H */ From patchwork Wed Mar 30 10:04:42 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Lezcano X-Patchwork-Id: 555469 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 89A6AC433F5 for ; Wed, 30 Mar 2022 10:05:26 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S244218AbiC3KHI (ORCPT ); Wed, 30 Mar 2022 06:07:08 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:40120 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S245154AbiC3KHG (ORCPT ); Wed, 30 Mar 2022 06:07:06 -0400 Received: from mail-wr1-x432.google.com (mail-wr1-x432.google.com [IPv6:2a00:1450:4864:20::432]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 37852E542D for ; Wed, 30 Mar 2022 03:05:20 -0700 (PDT) Received: by mail-wr1-x432.google.com with SMTP id u16so28509371wru.4 for ; Wed, 30 Mar 2022 03:05:20 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=RmWlTRAAbGJbrPtoirJxNyXFgPG9ShUU4hqz7EbJJqY=; b=uX9thH517i+xzTFy71NIXJUZwZd0cgA6KTdHu/XW1XxN8d4QUEEenoKjN2+wFYFV7y rClBxhK0AUZdf96vfOG3fbiVRkVDnXYgldOIIx9huxeYgIVTIgZsfcANxSxQnVLIf0D4 0wFzDNpHFzoRDz/C24LW3rtwps1feUjFYPLMteGYxspkaMcvPyb0VxRYpex/yi8D0amu NFL2IrKEE0I39qBhOFydXw75EFCWni7lJUaWUpoUfvB+/I7OtLD+ghzJa84Kn6pOMPcQ q/5TZSzUbu4Mr1hCVG7aDBXc/DCEs3lraDgQCt+tcZyBVm63VyUa0GSHGqLFsMnCllTf w6ZA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=RmWlTRAAbGJbrPtoirJxNyXFgPG9ShUU4hqz7EbJJqY=; b=w4+NiHzxUo2jMe3yAXYk6iDftNw46ToIgoU463agqyzuSm9sSurLqpYkYOXdoQPvbg wqt8IjX2D67SMETuWW4CPiNHfclLTcK75SfBx7oAmgOKQcqb7k+jaKN1rUzmQ0jvbBAt k3LNgkgmwt2TvFB2av8kMtdKChD0bZIbYg0sOrXIxMj+Nn4ZziGFlNfGhftfvslNxpqY D7HnpCXiMaFTLR8AcZV4A+ZMea/qenZVxIacWJxb029aQoeWAZybcQf+O3Yc6hHYcQLN F0cVrcc/hRTJCRvcAHhZ+93oNM+bOTbkyThGXdcave6nrNIt5LJ1K0C1KGxfbuB822ul bLoA== X-Gm-Message-State: AOAM530Yibsv8pqyyGki0IvvbzeuOIQOTgAsZkZs8WZr/Po1tJf49uG8 JbLLTvswVmEjj1bsr4W8N81Lww== X-Google-Smtp-Source: ABdhPJxYvO4npAxP3BEy1xMp6RRkcTDJEcgzOaqYpnTT+dnoX4hD0MGGt36gQrt/p5X7tP9SmFpGaA== X-Received: by 2002:a05:6000:1862:b0:204:e417:9cf8 with SMTP id d2-20020a056000186200b00204e4179cf8mr36413657wri.593.1648634718516; Wed, 30 Mar 2022 03:05:18 -0700 (PDT) Received: from localhost.localdomain ([2a01:e34:ed2f:f020:8f92:3217:a3c6:3ee8]) by smtp.gmail.com with ESMTPSA id p8-20020a5d4e08000000b002054b5437f2sm16542743wrt.115.2022.03.30.03.05.17 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 30 Mar 2022 03:05:17 -0700 (PDT) From: Daniel Lezcano To: daniel.lezcano@linaro.org, rafael@kernel.org Cc: srinivas.pandruvada@linux.intel.com, linux-kernel@vger.kernel.org, linux-pm@vger.kernel.org, Amit Kucheria , Zhang Rui Subject: [PATCH v2 2/4] tools/thermal: Add util library Date: Wed, 30 Mar 2022 12:04:42 +0200 Message-Id: <20220330100444.3846661-3-daniel.lezcano@linaro.org> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220330100444.3846661-1-daniel.lezcano@linaro.org> References: <20220330100444.3846661-1-daniel.lezcano@linaro.org> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-pm@vger.kernel.org The next changes will provide a couple of tools using some common functions provided by this library. It provides basic wrappers for: - mainloop - logging - timestamp Signed-off-by: Daniel Lezcano --- tools/thermal/lib/Build | 3 + tools/thermal/lib/Makefile | 158 ++++++++++++++++++ .../thermal/lib/libthermal_tools.pc.template | 12 ++ tools/thermal/lib/log.c | 77 +++++++++ tools/thermal/lib/log.h | 31 ++++ tools/thermal/lib/mainloop.c | 120 +++++++++++++ tools/thermal/lib/mainloop.h | 15 ++ tools/thermal/lib/thermal-tools.h | 10 ++ tools/thermal/lib/uptimeofday.c | 40 +++++ tools/thermal/lib/uptimeofday.h | 12 ++ 10 files changed, 478 insertions(+) create mode 100644 tools/thermal/lib/Build create mode 100644 tools/thermal/lib/Makefile create mode 100644 tools/thermal/lib/libthermal_tools.pc.template create mode 100644 tools/thermal/lib/log.c create mode 100644 tools/thermal/lib/log.h create mode 100644 tools/thermal/lib/mainloop.c create mode 100644 tools/thermal/lib/mainloop.h create mode 100644 tools/thermal/lib/thermal-tools.h create mode 100644 tools/thermal/lib/uptimeofday.c create mode 100644 tools/thermal/lib/uptimeofday.h diff --git a/tools/thermal/lib/Build b/tools/thermal/lib/Build new file mode 100644 index 000000000000..06f22760a272 --- /dev/null +++ b/tools/thermal/lib/Build @@ -0,0 +1,3 @@ +libthermal_tools-y += mainloop.o +libthermal_tools-y += log.o +libthermal_tools-y += uptimeofday.o diff --git a/tools/thermal/lib/Makefile b/tools/thermal/lib/Makefile new file mode 100644 index 000000000000..82db451935c5 --- /dev/null +++ b/tools/thermal/lib/Makefile @@ -0,0 +1,158 @@ +# SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) +# Most of this file is copied from tools/lib/perf/Makefile + +LIBTHERMAL_TOOLS_VERSION = 0 +LIBTHERMAL_TOOLS_PATCHLEVEL = 0 +LIBTHERMAL_TOOLS_EXTRAVERSION = 1 + +MAKEFLAGS += --no-print-directory + +ifeq ($(srctree),) +srctree := $(patsubst %/,%,$(dir $(CURDIR))) +srctree := $(patsubst %/,%,$(dir $(srctree))) +srctree := $(patsubst %/,%,$(dir $(srctree))) +# $(info Determined 'srctree' to be $(srctree)) +endif + +INSTALL = install + +# Use DESTDIR for installing into a different root directory. +# This is useful for building a package. The program will be +# installed in this directory as if it was the root directory. +# Then the build tool can move it later. +DESTDIR ?= +DESTDIR_SQ = '$(subst ','\'',$(DESTDIR))' + +include $(srctree)/tools/scripts/Makefile.include +include $(srctree)/tools/scripts/Makefile.arch + +ifeq ($(LP64), 1) + libdir_relative = lib64 +else + libdir_relative = lib +endif + +prefix ?= +libdir = $(prefix)/$(libdir_relative) + +# Shell quotes +libdir_SQ = $(subst ','\'',$(libdir)) +libdir_relative_SQ = $(subst ','\'',$(libdir_relative)) + +ifeq ("$(origin V)", "command line") + VERBOSE = $(V) +endif +ifndef VERBOSE + VERBOSE = 0 +endif + +ifeq ($(VERBOSE),1) + Q = +else + Q = @ +endif + +# Set compile option CFLAGS +ifdef EXTRA_CFLAGS + CFLAGS := $(EXTRA_CFLAGS) +else + CFLAGS := -g -Wall +endif + +INCLUDES = \ +-I/usr/include/libnl3 \ +-I$(srctree)/tools/lib/thermal/include \ +-I$(srctree)/tools/lib/ \ +-I$(srctree)/tools/include \ +-I$(srctree)/tools/arch/$(SRCARCH)/include/ \ +-I$(srctree)/tools/arch/$(SRCARCH)/include/uapi \ +-I$(srctree)/tools/include/uapi + +# Append required CFLAGS +override CFLAGS += $(EXTRA_WARNINGS) +override CFLAGS += -Werror -Wall +override CFLAGS += -fPIC +override CFLAGS += $(INCLUDES) +override CFGLAS += -Wl,-L. +override CFGLAS += -Wl,-lthermal + +all: + +export srctree OUTPUT CC LD CFLAGS V +export DESTDIR DESTDIR_SQ + +include $(srctree)/tools/build/Makefile.include + +PATCHLEVEL = $(LIBTHERMAL_TOOLS_PATCHLEVEL) +EXTRAVERSION = $(LIBTHERMAL_TOOLS_EXTRAVERSION) +VERSION = $(LIBTHERMAL_TOOLS_VERSION).$(LIBTHERMAL_TOOLS_PATCHLEVEL).$(LIBTHERMAL_TOOLS_EXTRAVERSION) + +LIBTHERMAL_TOOLS_SO := $(OUTPUT)libthermal_tools.so.$(VERSION) +LIBTHERMAL_TOOLS_A := $(OUTPUT)libthermal_tools.a +LIBTHERMAL_TOOLS_IN := $(OUTPUT)libthermal_tools-in.o +LIBTHERMAL_TOOLS_PC := $(OUTPUT)libthermal_tools.pc + +LIBTHERMAL_TOOLS_ALL := $(LIBTHERMAL_TOOLS_A) $(OUTPUT)libthermal_tools.so* + +$(LIBTHERMAL_TOOLS_IN): FORCE + $(Q)$(MAKE) $(build)=libthermal_tools + +$(LIBTHERMAL_TOOLS_A): $(LIBTHERMAL_TOOLS_IN) + $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(LIBTHERMAL_TOOLS_IN) + +$(LIBTHERMAL_TOOLS_SO): $(LIBTHERMAL_TOOLS_IN) + $(QUIET_LINK)$(CC) --shared -Wl,-soname,libthermal_tools.so $^ -o $@ + @ln -sf $(@F) $(OUTPUT)libthermal_tools.so + @ln -sf $(@F) $(OUTPUT)libthermal_tools.so.$(LIBTHERMAL_TOOLS_VERSION) + + +libs: $(LIBTHERMAL_TOOLS_A) $(LIBTHERMAL_TOOLS_SO) $(LIBTHERMAL_TOOLS_PC) + +all: fixdep + $(Q)$(MAKE) libs + +clean: + $(call QUIET_CLEAN, libthermal_tools) $(RM) $(LIBTHERMAL_TOOLS_A) \ + *.o *~ *.a *.so *.so.$(VERSION) *.so.$(LIBTHERMAL_TOOLS_VERSION) .*.d .*.cmd LIBTHERMAL_TOOLS-CFLAGS $(LIBTHERMAL_TOOLS_PC) + +$(LIBTHERMAL_TOOLS_PC): + $(QUIET_GEN)sed -e "s|@PREFIX@|$(prefix)|" \ + -e "s|@LIBDIR@|$(libdir_SQ)|" \ + -e "s|@VERSION@|$(VERSION)|" \ + < libthermal_tools.pc.template > $@ + +define do_install_mkdir + if [ ! -d '$(DESTDIR_SQ)$1' ]; then \ + $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$1'; \ + fi +endef + +define do_install + if [ ! -d '$(DESTDIR_SQ)$2' ]; then \ + $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$2'; \ + fi; \ + $(INSTALL) $1 $(if $3,-m $3,) '$(DESTDIR_SQ)$2' +endef + +install_lib: libs + $(call QUIET_INSTALL, $(LIBTHERMAL_TOOLS_ALL)) \ + $(call do_install_mkdir,$(libdir_SQ)); \ + cp -fpR $(LIBTHERMAL_TOOLS_ALL) $(DESTDIR)$(libdir_SQ) + +install_headers: + $(call QUIET_INSTALL, headers) \ + $(call do_install,include/thermal.h,$(prefix)/include/thermal,644); \ + +install_pkgconfig: $(LIBTHERMAL_TOOLS_PC) + $(call QUIET_INSTALL, $(LIBTHERMAL_TOOLS_PC)) \ + $(call do_install,$(LIBTHERMAL_TOOLS_PC),$(libdir_SQ)/pkgconfig,644) + +install_doc: + $(Q)$(MAKE) -C Documentation install-man install-html install-examples + +#install: install_lib install_headers install_pkgconfig install_doc +install: install_lib install_headers install_pkgconfig + +FORCE: + +.PHONY: all install clean FORCE diff --git a/tools/thermal/lib/libthermal_tools.pc.template b/tools/thermal/lib/libthermal_tools.pc.template new file mode 100644 index 000000000000..6f3769731b59 --- /dev/null +++ b/tools/thermal/lib/libthermal_tools.pc.template @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) + +prefix=@PREFIX@ +libdir=@LIBDIR@ +includedir=${prefix}/include + +Name: libthermal +Description: thermal library +Requires: libnl-3.0 libnl-genl-3.0 +Version: @VERSION@ +Libs: -L${libdir} -lnl-genl-3 -lnl-3 +Cflags: -I${includedir} -I{include}/libnl3 diff --git a/tools/thermal/lib/log.c b/tools/thermal/lib/log.c new file mode 100644 index 000000000000..597d6e7f7858 --- /dev/null +++ b/tools/thermal/lib/log.c @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: LGPL-2.1+ +// Copyright (C) 2022, Linaro Ltd - Daniel Lezcano +#include +#include +#include +#include +#include "log.h" + +static const char *__ident = "unknown"; +static int __options; + +static const char * const loglvl[] = { + [LOG_DEBUG] = "DEBUG", + [LOG_INFO] = "INFO", + [LOG_NOTICE] = "NOTICE", + [LOG_WARNING] = "WARN", + [LOG_ERR] = "ERROR", + [LOG_CRIT] = "CRITICAL", + [LOG_ALERT] = "ALERT", + [LOG_EMERG] = "EMERG", +}; + +int log_str2level(const char *lvl) +{ + int i; + + for (i = 0; i < sizeof(loglvl) / sizeof(loglvl[LOG_DEBUG]); i++) + if (!strcmp(lvl, loglvl[i])) + return i; + + return LOG_DEBUG; +} + +extern void logit(int level, const char *format, ...) +{ + va_list args; + + va_start(args, format); + + if (__options & TO_SYSLOG) + vsyslog(level, format, args); + + if (__options & TO_STDERR) + vfprintf(stderr, format, args); + + if (__options & TO_STDOUT) + vfprintf(stdout, format, args); + + va_end(args); +} + +int log_init(int level, const char *ident, int options) +{ + if (!options) + return -1; + + if (level > LOG_DEBUG) + return -1; + + if (!ident) + return -1; + + __ident = ident; + __options = options; + + if (options & TO_SYSLOG) { + openlog(__ident, options | LOG_NDELAY, LOG_USER); + setlogmask(LOG_UPTO(level)); + } + + return 0; +} + +void log_exit(void) +{ + closelog(); +} diff --git a/tools/thermal/lib/log.h b/tools/thermal/lib/log.h new file mode 100644 index 000000000000..be8ab5144938 --- /dev/null +++ b/tools/thermal/lib/log.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +/* Copyright (C) 2022, Linaro Ltd - Daniel Lezcano */ +#ifndef __THERMAL_TOOLS_LOG_H +#define __THERMAL_TOOLS_LOG_H + +#include + +#ifndef __maybe_unused +#define __maybe_unused __attribute__((__unused__)) +#endif + +#define TO_SYSLOG 0x1 +#define TO_STDOUT 0x2 +#define TO_STDERR 0x4 + +extern void logit(int level, const char *format, ...); + +#define DEBUG(fmt, ...) logit(LOG_DEBUG, "%s:%d: " fmt, __func__, __LINE__, ##__VA_ARGS__) +#define INFO(fmt, ...) logit(LOG_INFO, fmt, ##__VA_ARGS__) +#define NOTICE(fmt, ...) logit(LOG_NOTICE, fmt, ##__VA_ARGS__) +#define WARN(fmt, ...) logit(LOG_WARNING, fmt, ##__VA_ARGS__) +#define ERROR(fmt, ...) logit(LOG_ERR, fmt, ##__VA_ARGS__) +#define CRITICAL(fmt, ...) logit(LOG_CRIT, fmt, ##__VA_ARGS__) +#define ALERT(fmt, ...) logit(LOG_ALERT, fmt, ##__VA_ARGS__) +#define EMERG(fmt, ...) logit(LOG_EMERG, fmt, ##__VA_ARGS__) + +int log_init(int level, const char *ident, int options); +int log_str2level(const char *lvl); +void log_exit(void); + +#endif diff --git a/tools/thermal/lib/mainloop.c b/tools/thermal/lib/mainloop.c new file mode 100644 index 000000000000..94cbbcbd1c14 --- /dev/null +++ b/tools/thermal/lib/mainloop.c @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: LGPL-2.1+ +// Copyright (C) 2022, Linaro Ltd - Daniel Lezcano +#include +#include +#include +#include +#include +#include "mainloop.h" +#include "log.h" + +static int epfd = -1; +static unsigned short nrhandler; +static sig_atomic_t exit_mainloop; + +struct mainloop_data { + mainloop_callback_t cb; + void *data; + int fd; +}; + +static struct mainloop_data **mds; + +#define MAX_EVENTS 10 + +int mainloop(unsigned int timeout) +{ + int i, nfds; + struct epoll_event events[MAX_EVENTS]; + struct mainloop_data *md; + + if (epfd < 0) + return -1; + + for (;;) { + + nfds = epoll_wait(epfd, events, MAX_EVENTS, timeout); + + if (exit_mainloop || !nfds) + return 0; + + if (nfds < 0) { + if (errno == EINTR) + continue; + return -1; + } + + for (i = 0; i < nfds; i++) { + md = events[i].data.ptr; + + if (md->cb(md->fd, md->data) > 0) + return 0; + } + } +} + +int mainloop_add(int fd, mainloop_callback_t cb, void *data) +{ + struct epoll_event ev = { + .events = EPOLLIN, + }; + + struct mainloop_data *md; + + if (fd >= nrhandler) { + mds = realloc(mds, sizeof(*mds) * (fd + 1)); + if (!mds) + return -1; + nrhandler = fd + 1; + } + + md = malloc(sizeof(*md)); + if (!md) + return -1; + + md->data = data; + md->cb = cb; + md->fd = fd; + + mds[fd] = md; + ev.data.ptr = md; + + if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev) < 0) { + free(md); + return -1; + } + + return 0; +} + +int mainloop_del(int fd) +{ + if (fd >= nrhandler) + return -1; + + if (epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL) < 0) + return -1; + + free(mds[fd]); + + return 0; +} + +int mainloop_init(void) +{ + epfd = epoll_create(2); + if (epfd < 0) + return -1; + + return 0; +} + +void mainloop_exit(void) +{ + exit_mainloop = 1; +} + +void mainloop_fini(void) +{ + close(epfd); +} diff --git a/tools/thermal/lib/mainloop.h b/tools/thermal/lib/mainloop.h new file mode 100644 index 000000000000..89b61e89d905 --- /dev/null +++ b/tools/thermal/lib/mainloop.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +/* Copyright (C) 2022, Linaro Ltd - Daniel Lezcano */ +#ifndef __THERMAL_TOOLS_MAINLOOP_H +#define __THERMAL_TOOLS_MAINLOOP_H + +typedef int (*mainloop_callback_t)(int fd, void *data); + +extern int mainloop(unsigned int timeout); +extern int mainloop_add(int fd, mainloop_callback_t cb, void *data); +extern int mainloop_del(int fd); +extern void mainloop_exit(void); +extern int mainloop_init(void); +extern void mainloop_fini(void); + +#endif diff --git a/tools/thermal/lib/thermal-tools.h b/tools/thermal/lib/thermal-tools.h new file mode 100644 index 000000000000..f43939a468a3 --- /dev/null +++ b/tools/thermal/lib/thermal-tools.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +/* Copyright (C) 2022, Linaro Ltd - Daniel Lezcano */ +#ifndef __THERMAL_TOOLS +#define __THERMAL_TOOLS + +#include "log.h" +#include "mainloop.h" +#include "uptimeofday.h" + +#endif diff --git a/tools/thermal/lib/uptimeofday.c b/tools/thermal/lib/uptimeofday.c new file mode 100644 index 000000000000..dacb02956a68 --- /dev/null +++ b/tools/thermal/lib/uptimeofday.c @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: LGPL-2.1+ +// Copyright (C) 2022, Linaro Ltd - Daniel Lezcano +#include +#include +#include +#include "thermal-tools.h" + +static unsigned long __offset; +static struct timeval __tv; + +int uptimeofday_init(void) +{ + struct sysinfo info; + + if (sysinfo(&info)) + return -1; + + gettimeofday(&__tv, NULL); + + __offset = __tv.tv_sec - info.uptime; + + return 0; +} + +unsigned long getuptimeofday_ms(void) +{ + gettimeofday(&__tv, NULL); + + return ((__tv.tv_sec - __offset) * 1000) + (__tv.tv_usec / 1000); +} + +struct timespec msec_to_timespec(int msec) +{ + struct timespec tv = { + .tv_sec = (msec / 1000), + .tv_nsec = (msec % 1000) * 1000000, + }; + + return tv; +} diff --git a/tools/thermal/lib/uptimeofday.h b/tools/thermal/lib/uptimeofday.h new file mode 100644 index 000000000000..c0da5de41325 --- /dev/null +++ b/tools/thermal/lib/uptimeofday.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +/* Copyright (C) 2022, Linaro Ltd - Daniel Lezcano */ +#ifndef __THERMAL_TOOLS_UPTIMEOFDAY_H +#define __THERMAL_TOOLS_UPTIMEOFDAY_H +#include +#include + +int uptimeofday_init(void); +unsigned long getuptimeofday_ms(void); +struct timespec msec_to_timespec(int msec); + +#endif From patchwork Wed Mar 30 10:04:43 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Daniel Lezcano X-Patchwork-Id: 555218 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 10537C433EF for ; Wed, 30 Mar 2022 10:05:38 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S245157AbiC3KHU (ORCPT ); Wed, 30 Mar 2022 06:07:20 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:41152 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S245168AbiC3KHT (ORCPT ); Wed, 30 Mar 2022 06:07:19 -0400 Received: from mail-wr1-x431.google.com (mail-wr1-x431.google.com [IPv6:2a00:1450:4864:20::431]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id D8880E9C9C for ; Wed, 30 Mar 2022 03:05:27 -0700 (PDT) Received: by mail-wr1-x431.google.com with SMTP id b19so28471846wrh.11 for ; Wed, 30 Mar 2022 03:05:27 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=WBMBrrF+3DeGd5Qv19hdc8TZo5LddleFrKa225rKdeY=; b=mQlCez5JnZKNz0wqCAAtkzZdUJtSvOkWuyUhdkhH3ZoqzhXka/V6KZDHleJgUuFsZT tLAJAedTqt7JnGPLUaJhmOOQnUHuzvJrKdsfGYQzKfPCJjqv+3wsPeM/0lEdN1SDAUL9 WlQtg2zTY2//rMGjzj70TwcyGfhmUSHITRgrZGeIcEOaK7IFhLTvqkl/PsUEKkV6NueH 1YqhIDmkjlTCuWDNH2xUixdyYkANmCcMK7SereHazGu7nfni25VYT2iB7dY1IEGTT4IE krtJPvL3H5G1sucwglNi5rEhbGbM5gVRe1q8yoNi7fTpJk+I4jiLTI7kM4HNURGWsdwO fwWQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=WBMBrrF+3DeGd5Qv19hdc8TZo5LddleFrKa225rKdeY=; b=XyosOYJKQwdd51R03Hqe4RWy3sv7UnNBVUCzsHOBV9F9t3IqE1hDvqoyX20WWCCmeK +pX4EaYrmkOqMXbQZmw0hYEGeZBDFHVigdEt6NlvftQIMgciK1mouBJUa9nEO+IW6Uct aBKaLMZKMmvWx12hAsJUunD7hApV18lgknD0LG7SfcTDf4f4R0C2491GZdrb3WrnmqCz m/nlouMlLMFNkd1/685NQH5UVN7GW2IhC5Xec8p3GgUVBe4RrxpxfXkD/Eh9xOZVj9AE dQvwm7hrgVtvGHwxSqUe1F+M9hkYS9W+TmBG9Hdag+wGIH0bBYsERNhBFhzqQOG44Vu5 j6Rg== X-Gm-Message-State: AOAM53375UvFpL4TVskLuxh8Mlk8Px67XFOOUWH3sOZZ0vGf8L7IFL3B 0FmZRaBC0C/o8VVzgbYeAFojuQ== X-Google-Smtp-Source: ABdhPJyeI927hDXDpa2Qe0RhXe/0azmG0//95pcqn7RkyA9gtZCxFqPGOz2F6G9xEYUzdmklZ0P0YQ== X-Received: by 2002:a5d:588c:0:b0:205:7f17:3901 with SMTP id n12-20020a5d588c000000b002057f173901mr35321202wrf.359.1648634726103; Wed, 30 Mar 2022 03:05:26 -0700 (PDT) Received: from localhost.localdomain ([2a01:e34:ed2f:f020:8f92:3217:a3c6:3ee8]) by smtp.gmail.com with ESMTPSA id p8-20020a5d4e08000000b002054b5437f2sm16542743wrt.115.2022.03.30.03.05.24 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 30 Mar 2022 03:05:25 -0700 (PDT) From: Daniel Lezcano To: daniel.lezcano@linaro.org, rafael@kernel.org Cc: srinivas.pandruvada@linux.intel.com, linux-kernel@vger.kernel.org, linux-pm@vger.kernel.org, Amit Kucheria , Zhang Rui , Sasha Levin , William Breathitt Gray , Jonathan Cameron Subject: [PATCH v2 3/4] tools/thermal: A temperature capture tool Date: Wed, 30 Mar 2022 12:04:43 +0200 Message-Id: <20220330100444.3846661-4-daniel.lezcano@linaro.org> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220330100444.3846661-1-daniel.lezcano@linaro.org> References: <20220330100444.3846661-1-daniel.lezcano@linaro.org> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-pm@vger.kernel.org The 'thermometer' tool allows to capture the temperature of a set of thermal zones defined in a configuration file at a specified rate. It is designed to have the lowest possible overhead. It will write the captured temperature per thermal zone per file so making easier to write a gnuplot script. Signed-off-by: Daniel Lezcano --- tools/Makefile | 16 +- tools/thermal/thermometer/Build | 2 + tools/thermal/thermometer/Makefile | 26 + tools/thermal/thermometer/thermometer.8 | 93 ++++ tools/thermal/thermometer/thermometer.c | 558 +++++++++++++++++++++ tools/thermal/thermometer/thermometer.conf | 5 + 6 files changed, 697 insertions(+), 3 deletions(-) create mode 100644 tools/thermal/thermometer/Build create mode 100644 tools/thermal/thermometer/Makefile create mode 100644 tools/thermal/thermometer/thermometer.8 create mode 100644 tools/thermal/thermometer/thermometer.c create mode 100644 tools/thermal/thermometer/thermometer.conf diff --git a/tools/Makefile b/tools/Makefile index c253cbd27c06..78615f8cb463 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -31,6 +31,7 @@ help: @echo ' bootconfig - boot config tool' @echo ' spi - spi tools' @echo ' tmon - thermal monitoring and tuning tool' + @echo ' thermometer - temperature capture tool' @echo ' thermal - thermal library' @echo ' tracing - misc tracing tools' @echo ' turbostat - Intel CPU idle stats and freq reporting tool' @@ -95,6 +96,9 @@ turbostat x86_energy_perf_policy intel-speed-select: FORCE tmon: FORCE $(call descend,thermal/$@) +thermometer: FORCE + $(call descend,thermal/$@) + freefall: FORCE $(call descend,laptop/$@) @@ -105,7 +109,7 @@ all: acpi cgroup counter cpupower gpio hv firewire \ perf selftests bootconfig spi turbostat usb \ virtio vm bpf x86_energy_perf_policy \ tmon freefall iio objtool kvm_stat wmi \ - pci debugging tracing thermal + pci debugging tracing thermal thermometer acpi_install: $(call descend,power/$(@:_install=),install) @@ -128,6 +132,9 @@ turbostat_install x86_energy_perf_policy_install intel-speed-select_install: tmon_install: $(call descend,thermal/$(@:_install=),install) +thermometer_install: + $(call descend,thermal/$(@:_install=),install) + freefall_install: $(call descend,laptop/$(@:_install=),install) @@ -140,7 +147,7 @@ install: acpi_install cgroup_install counter_install cpupower_install gpio_insta virtio_install vm_install bpf_install x86_energy_perf_policy_install \ tmon_install freefall_install objtool_install kvm_stat_install \ wmi_install pci_install debugging_install intel-speed-select_install \ - tracing_install + tracing_install thermometer_install acpi_clean: $(call descend,power/acpi,clean) @@ -173,6 +180,9 @@ thermal_clean: turbostat_clean x86_energy_perf_policy_clean intel-speed-select_clean: $(call descend,power/x86/$(@:_clean=),clean) +thermometer_clean: + $(call descend,thermal/thermometer,clean) + tmon_clean: $(call descend,thermal/tmon,clean) @@ -187,6 +197,6 @@ clean: acpi_clean cgroup_clean counter_clean cpupower_clean hv_clean firewire_cl vm_clean bpf_clean iio_clean x86_energy_perf_policy_clean tmon_clean \ freefall_clean build_clean libbpf_clean libsubcmd_clean \ gpio_clean objtool_clean leds_clean wmi_clean pci_clean firmware_clean debugging_clean \ - intel-speed-select_clean tracing_clean thermal_clean + intel-speed-select_clean tracing_clean thermal_clean thermometer_clean .PHONY: FORCE diff --git a/tools/thermal/thermometer/Build b/tools/thermal/thermometer/Build new file mode 100644 index 000000000000..2531dda4acdd --- /dev/null +++ b/tools/thermal/thermometer/Build @@ -0,0 +1,2 @@ +thermometer-y += thermometer.o + diff --git a/tools/thermal/thermometer/Makefile b/tools/thermal/thermometer/Makefile new file mode 100644 index 000000000000..d8f8bc82fe3b --- /dev/null +++ b/tools/thermal/thermometer/Makefile @@ -0,0 +1,26 @@ +# SPDX-License-Identifier: GPL-2.0 +# Makefile for cgroup tools + +ifeq ($(srctree),) +srctree := $(patsubst %/,%,$(dir $(CURDIR))) +srctree := $(patsubst %/,%,$(dir $(srctree))) +srctree := $(patsubst %/,%,$(dir $(srctree))) +# $(info Determined 'srctree' to be $(srctree)) +endif + +CFLAGS = -Wall -Wextra +CFLAGS += -I$(srctree)/tools/thermal/lib + +LDFLAGS = -L$(srctree)/tools/thermal/lib +LDFLAGS += -lthermal_tools +LDFLAGS += -lconfig + +VERSION = 0.0.1 +TARGET=thermometer + +all: $(TARGET) +%: %.c + $(CC) $(CFLAGS) -D VERSION=\"$(VERSION)\" -o $@ $^ $(LDFLAGS) + +clean: + $(RM) $(TARGET) diff --git a/tools/thermal/thermometer/thermometer.8 b/tools/thermal/thermometer/thermometer.8 new file mode 100644 index 000000000000..5242220c12be --- /dev/null +++ b/tools/thermal/thermometer/thermometer.8 @@ -0,0 +1,93 @@ +.TH THERMOMETER 8 +# SPDX-License-Identifier: GPL-2.0 +.SH NAME +\fBthermometer\fP - A thermal profiling tool + +.SH SYNOPSIS +.ft B +.B thermometer +.RB [ options ] +.RB [ command ] +.br +.SH DESCRIPTION +\fBthermometer \fP captures the thermal zones temperature at a +specified sampling period. It is optimized to reduce as much as +possible the overhead while doing the temperature acquisition in order +to prevent disrupting the running application we may want to profile. + +This low overhead also allows a high rate sampling for the temperature +which could be necessary to spot overshots and undershots. + +If no configuration file is specified, then all the thermal zones will +be monitored at 4Hz, so every 250ms. A configuration file specifies +the thermal zone names and the desired sampling period. A thermal zone +name can be a regular expression to specify a group of thermal zone. + +The sampling of the different thermal zones will be written into +separate files with the thermal zone name. It is possible to specify a +postfix to identify them for example for a specific scenario. The +output directory can be specified in addition. + +Without any parameters, \fBthermometer \fP captures all the thermal +zone temperatures every 250ms and write to the current directory the +captured files postfixed with the current date. + +If a running \fBduration\fP is specified or a \fBcommand\fP, the +capture ends at the end of the duration if the command did not +finished before. The \fBduration\fP can be specified alone as well as +the \fBcommand\fP. If none is specified, the capture will continue +indefinitively until interrupted by \fBSIGINT\fP or \fBSIGQUIT\fP. +.PP + +.SS Options +.PP +The \fB-h, --help\fP option shows a short usage help +.PP +The \fB-o , --output \fP option defines the output directory to put the +sampling files +.PP +The \fB-c , --config \fP option specifies the configuration file to use +.PP +The \fB-d , --duration \fP option specifies the duration of the capture +.PP +The \fB-l , --loglevel \fP option sets the loglevel [DEBUG,INFO,NOTICE,WARN,ERROR] +.PP +The \fB-p , --postfix \fP option appends \fBstring\fP at the end of the capture filenames +.PP +The \fB-s, --syslog\fP option sets the output to syslog, default is \fBstdout\fP +.PP +The \fB-w, --overwrite\fP overwrites the output files if they exist +.PP + +.PP + +.SS "Exit status:" +.TP +0 +if OK, +.TP +1 +Error with the options specified as parameters +.TP +2 +Error when configuring the logging facility +.TP +3 +Error when configuring the time +.TP +4 +Error in the initialization routine +.TP +5 +Error during the runtime + +.SH Capture file format + +Every file contains two columns. The first one is the uptime timestamp +in order to find a point in time since the system started up if there +is any thermal event. The second one is the temperature in milli +degree. The first line contains the label of each column. + +.SH AUTHOR +Daniel Lezcano + diff --git a/tools/thermal/thermometer/thermometer.c b/tools/thermal/thermometer/thermometer.c new file mode 100644 index 000000000000..cb30a4a039cb --- /dev/null +++ b/tools/thermal/thermometer/thermometer.c @@ -0,0 +1,558 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (C) 2022, Linaro Ltd - Daniel Lezcano +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "thermal-tools.h" + +#define CLASS_THERMAL "/sys/class/thermal" + +enum { + THERMOMETER_SUCCESS = 0, + THERMOMETER_OPTION_ERROR, + THERMOMETER_LOG_ERROR, + THERMOMETER_CONFIG_ERROR, + THERMOMETER_TIME_ERROR, + THERMOMETER_INIT_ERROR, + THERMOMETER_RUNTIME_ERROR +}; + +struct options { + int loglvl; + int logopt; + int overwrite; + int duration; + const char *config; + char postfix[PATH_MAX]; + char output[PATH_MAX]; +}; + +struct tz_regex { + regex_t regex; + int polling; +}; + +struct configuration { + struct tz_regex *tz_regex; + int nr_tz_regex; + +}; + +struct tz { + FILE *file_out; + int fd_temp; + int fd_timer; + int polling; + const char *name; +}; + +struct thermometer { + struct tz *tz; + int nr_tz; +}; + +static struct tz_regex *configuration_tz_match(const char *expr, + struct configuration *config) +{ + int i; + + for (i = 0; i < config->nr_tz_regex; i++) { + + if (!regexec(&config->tz_regex[i].regex, expr, 0, NULL, 0)) + return &config->tz_regex[i]; + } + + return NULL; +} + +static int configuration_default_init(struct configuration *config) +{ + config->tz_regex = realloc(config->tz_regex, sizeof(*config->tz_regex) * + (config->nr_tz_regex + 1)); + + if (regcomp(&config->tz_regex[config->nr_tz_regex].regex, ".*", + REG_NOSUB | REG_EXTENDED)) { + ERROR("Invalid regular expression\n"); + return -1; + } + + config->tz_regex[config->nr_tz_regex].polling = 250; + config->nr_tz_regex = 1; + + return 0; +} + +static int configuration_init(const char *path, struct configuration *config) +{ + config_t cfg; + + config_setting_t *tz; + int i, length; + + if (path && access(path, F_OK)) { + ERROR("'%s' is not accessible\n", path); + return -1; + } + + if (!path && !config->nr_tz_regex) { + INFO("No thermal zones configured, using wildcard for all of them\n"); + return configuration_default_init(config); + } + + config_init(&cfg); + + if (!config_read_file(&cfg, path)) { + ERROR("Failed to parse %s:%d - %s\n", config_error_file(&cfg), + config_error_line(&cfg), config_error_text(&cfg)); + + return -1; + } + + tz = config_lookup(&cfg, "thermal-zones"); + if (!tz) { + ERROR("No thermal zone configured to be monitored\n"); + return -1; + } + + length = config_setting_length(tz); + + INFO("Found %d thermal zone(s) regular expression\n", length); + + for (i = 0; i < length; i++) { + + config_setting_t *node; + const char *name; + int polling; + + node = config_setting_get_elem(tz, i); + if (!node) { + ERROR("Missing node name '%d'\n", i); + return -1; + }; + + if (!config_setting_lookup_string(node, "name", &name)) { + ERROR("Thermal zone name not found\n"); + return -1; + } + + if (!config_setting_lookup_int(node, "polling", &polling)) { + ERROR("Polling value not found"); + return -1; + } + + config->tz_regex = realloc(config->tz_regex, sizeof(*config->tz_regex) * + (config->nr_tz_regex + 1)); + + if (regcomp(&config->tz_regex[config->nr_tz_regex].regex, name, + REG_NOSUB | REG_EXTENDED)) { + ERROR("Invalid regular expression '%s'\n", name); + continue; + } + + config->tz_regex[config->nr_tz_regex].polling = polling; + config->nr_tz_regex++; + + INFO("Thermal zone regular expression '%s' with polling %d\n", + name, polling); + } + + return 0; +} + +static void help(const char *cmd) +{ + printf("%s Version: %s\n", cmd, VERSION); + printf("Usage: %s [options]\n", cmd); + printf("\t-h, --help\t\tthis help\n"); + printf("\t-o, --output \toutput directory for temperature capture\n"); + printf("\t-c, --config \tconfiguration file\n"); + printf("\t-d, --duration \tcapture duration\n"); + printf("\t-l, --loglevel \tlog level: "); + printf("DEBUG, INFO, NOTICE, WARN, ERROR\n"); + printf("\t-p, --postfix \tpostfix to be happened at the end of the files\n"); + printf("\t-s, --syslog\t\toutput to syslog\n"); + printf("\t-w, --overwrite\t\toverwrite the temperature capture files if they exist\n"); + printf("\n"); + exit(0); +} + +static int options_init(int argc, char *argv[], struct options *options) +{ + int opt; + time_t now = time(NULL); + + struct option long_options[] = { + { "help", no_argument, NULL, 'h' }, + { "config", required_argument, NULL, 'c' }, + { "duration", required_argument, NULL, 'd' }, + { "loglevel", required_argument, NULL, 'l' }, + { "postfix", required_argument, NULL, 'p' }, + { "output", required_argument, NULL, 'o' }, + { "syslog", required_argument, NULL, 's' }, + { "overwrite", no_argument, NULL, 'w' }, + { 0, 0, 0, 0 } + }; + + strftime(options->postfix, sizeof(options->postfix), + "-%Y-%m-%d_%H:%M:%S", gmtime(&now)); + + while (1) { + + int optindex = 0; + + opt = getopt_long(argc, argv, "ho:c:d:l:p:sw", long_options, &optindex); + if (opt == -1) + break; + + switch (opt) { + case 'c': + options->config = optarg; + break; + case 'd': + options->duration = atoi(optarg) * 1000; + break; + case 'l': + options->loglvl = log_str2level(optarg); + break; + case 'h': + help(basename(argv[0])); + break; + case 'p': + strcpy(options->postfix, optarg); + break; + case 'o': + strcpy(options->output, optarg); + break; + case 's': + options->logopt = TO_SYSLOG; + break; + case 'w': + options->overwrite = 1; + break; + default: /* '?' */ + ERROR("Usage: %s --help\n", argv[0]); + return -1; + } + } + + return 0; +} + +static int thermometer_add_tz(const char *path, const char *name, int polling, + struct thermometer *thermometer) +{ + int fd; + char tz_path[PATH_MAX]; + + sprintf(tz_path, CLASS_THERMAL"/%s/temp", path); + + fd = open(tz_path, O_RDONLY); + if (fd < 0) { + ERROR("Failed to open '%s': %m\n", tz_path); + return -1; + } + + thermometer->tz = realloc(thermometer->tz, + sizeof(*thermometer->tz) * (thermometer->nr_tz + 1)); + if (!thermometer->tz) { + ERROR("Failed to allocate thermometer->tz\n"); + return -1; + } + + thermometer->tz[thermometer->nr_tz].fd_temp = fd; + thermometer->tz[thermometer->nr_tz].name = strdup(name); + thermometer->tz[thermometer->nr_tz].polling = polling; + thermometer->nr_tz++; + + INFO("Added thermal zone '%s->%s (polling:%d)'\n", path, name, polling); + + return 0; +} + +static int thermometer_init(struct configuration *config, + struct thermometer *thermometer) +{ + DIR *dir; + struct dirent *dirent; + struct tz_regex *tz_regex; + const char *tz_dirname = "thermal_zone"; + + if (mainloop_init()) { + ERROR("Failed to start mainloop\n"); + return -1; + } + + dir = opendir(CLASS_THERMAL); + if (!dir) { + ERROR("failed to open '%s'\n", CLASS_THERMAL); + return -1; + } + + while ((dirent = readdir(dir))) { + char tz_type[THERMAL_NAME_LENGTH]; + char tz_path[PATH_MAX]; + FILE *tz_file; + + if (strncmp(dirent->d_name, tz_dirname, strlen(tz_dirname))) + continue; + + sprintf(tz_path, CLASS_THERMAL"/%s/type", dirent->d_name); + + tz_file = fopen(tz_path, "r"); + if (!tz_file) { + ERROR("Failed to open '%s': %m", tz_path); + continue; + } + + fscanf(tz_file, "%s", tz_type); + + fclose(tz_file); + + tz_regex = configuration_tz_match(tz_type, config); + if (!tz_regex) + continue; + + if (thermometer_add_tz(dirent->d_name, tz_type, + tz_regex->polling, thermometer)) + continue; + } + + closedir(dir); + + return 0; +} + +static int timer_temperature_callback(int fd, void *arg) +{ + struct tz *tz = arg; + char buf[16] = { 0 }; + + pread(tz->fd_temp, buf, sizeof(buf), 0); + + fprintf(tz->file_out, "%ld %s", getuptimeofday_ms(), buf); + + read(fd, buf, sizeof(buf)); + + return 0; +} + +static int thermometer_start(struct thermometer *thermometer, + struct options *options) +{ + struct itimerspec timer_it = { 0 }; + char *path; + FILE *f; + int i; + + INFO("Capturing %d thermal zone(s) temperature...\n", thermometer->nr_tz); + + if (access(options->output, F_OK) && mkdir(options->output, 0700)) { + ERROR("Failed to create directory '%s'\n", options->output); + return -1; + } + + for (i = 0; i < thermometer->nr_tz; i++) { + + asprintf(&path, "%s/%s%s", options->output, + thermometer->tz[i].name, options->postfix); + + if (!options->overwrite && !access(path, F_OK)) { + ERROR("'%s' already exists\n", path); + return -1; + } + + f = fopen(path, "w"); + if (!f) { + ERROR("Failed to create '%s':%m\n", path); + return -1; + } + + fprintf(f, "timestamp(ms) %s(°mC)\n", thermometer->tz[i].name); + + thermometer->tz[i].file_out = f; + + DEBUG("Created '%s' file for thermal zone '%s'\n", path, thermometer->tz[i].name); + + /* + * Create polling timer + */ + thermometer->tz[i].fd_timer = timerfd_create(CLOCK_MONOTONIC, 0); + if (thermometer->tz[i].fd_timer < 0) { + ERROR("Failed to create timer for '%s': %m\n", + thermometer->tz[i].name); + return -1; + } + + DEBUG("Watching '%s' every %d ms\n", + thermometer->tz[i].name, thermometer->tz[i].polling); + + timer_it.it_interval = timer_it.it_value = + msec_to_timespec(thermometer->tz[i].polling); + + if (timerfd_settime(thermometer->tz[i].fd_timer, 0, + &timer_it, NULL) < 0) + return -1; + + if (mainloop_add(thermometer->tz[i].fd_timer, + timer_temperature_callback, + &thermometer->tz[i])) + return -1; + } + + return 0; +} + +static int thermometer_execute(int argc, char *argv[], char *const envp[]) +{ + pid_t pid; + + if (!argc) + return 0; + + pid = fork(); + if (pid < 0) { + ERROR("Failed to fork process: %m"); + return -1; + } + + if (!pid) { + execvpe(argv[0], argv, envp); + exit(1); + } + + return 0; +} + +static int exit_mainloop(__maybe_unused int fd, __maybe_unused void *arg) +{ + mainloop_exit(); + + return 0; +} + +static int thermometer_wait(struct options *options) +{ + int fd; + sigset_t mask; + + /* + * If there is a duration specified, we will exit the mainloop + * and gracefully close all the files which will flush the + * file system cache + */ + if (options->duration) { + struct itimerspec timer_it = { 0 }; + + timer_it.it_value = msec_to_timespec(options->duration); + + fd = timerfd_create(CLOCK_MONOTONIC, 0); + if (fd < 0) { + ERROR("Failed to create duration timer: %m\n"); + return -1; + } + + if (timerfd_settime(fd, 0, &timer_it, NULL)) { + ERROR("Failed to set timer time: %m\n"); + return -1; + } + + if (mainloop_add(fd, exit_mainloop, NULL)) { + ERROR("Failed to set timer exit mainloop callback\n"); + return -1; + } + } + + /* + * We want to catch any keyboard interrupt, as well as child + * signals if any in order to exit properly + */ + sigemptyset(&mask); + sigaddset(&mask, SIGINT); + sigaddset(&mask, SIGQUIT); + sigaddset(&mask, SIGCHLD); + + if (sigprocmask(SIG_BLOCK, &mask, NULL)) { + ERROR("Failed to set sigprocmask: %m\n"); + return -1; + } + + fd = signalfd(-1, &mask, 0); + if (fd < 0) { + ERROR("Failed to set the signalfd: %m\n"); + return -1; + } + + if (mainloop_add(fd, exit_mainloop, NULL)) { + ERROR("Failed to set timer exit mainloop callback\n"); + return -1; + } + + return mainloop(-1); +} + +static int thermometer_stop(struct thermometer *thermometer) +{ + int i; + + INFO("Closing/flushing output files\n"); + + for (i = 0; i < thermometer->nr_tz; i++) + fclose(thermometer->tz[i].file_out); + + return 0; +} + +int main(int argc, char *argv[], char *const envp[]) +{ + struct options options = { + .loglvl = LOG_DEBUG, + .logopt = TO_STDOUT, + .output = ".", + }; + struct configuration config = { 0 }; + struct thermometer thermometer = { 0 }; + + if (options_init(argc, argv, &options)) + return THERMOMETER_OPTION_ERROR; + + if (log_init(options.loglvl, argv[0], options.logopt)) + return THERMOMETER_LOG_ERROR; + + if (configuration_init(options.config, &config)) + return THERMOMETER_CONFIG_ERROR; + + if (uptimeofday_init()) + return THERMOMETER_TIME_ERROR; + + if (thermometer_init(&config, &thermometer)) + return THERMOMETER_INIT_ERROR; + + if (thermometer_start(&thermometer, &options)) + return THERMOMETER_RUNTIME_ERROR; + + if (thermometer_execute(argc - optind, &argv[optind], envp)) + return THERMOMETER_RUNTIME_ERROR; + + if (thermometer_wait(&options)) + return THERMOMETER_RUNTIME_ERROR; + + if (thermometer_stop(&thermometer)) + return THERMOMETER_RUNTIME_ERROR; + + return THERMOMETER_SUCCESS; +} diff --git a/tools/thermal/thermometer/thermometer.conf b/tools/thermal/thermometer/thermometer.conf new file mode 100644 index 000000000000..456aa269c1b7 --- /dev/null +++ b/tools/thermal/thermometer/thermometer.conf @@ -0,0 +1,5 @@ + +thermal-zones = ( + { name = "cluster1-thermal"; + polling = 100; } + ) From patchwork Wed Mar 30 10:04:44 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Daniel Lezcano X-Patchwork-Id: 555468 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id AED4EC433F5 for ; Wed, 30 Mar 2022 10:05:39 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S245168AbiC3KHV (ORCPT ); Wed, 30 Mar 2022 06:07:21 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:41194 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S245182AbiC3KHU (ORCPT ); Wed, 30 Mar 2022 06:07:20 -0400 Received: from mail-wr1-x42f.google.com (mail-wr1-x42f.google.com [IPv6:2a00:1450:4864:20::42f]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id BFA85ED9E7 for ; Wed, 30 Mar 2022 03:05:33 -0700 (PDT) Received: by mail-wr1-x42f.google.com with SMTP id u3so28538542wrg.3 for ; Wed, 30 Mar 2022 03:05:33 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=PGQlj9TEsb6f3Qz+wlAvnxY/j23ohMghNrtlMbgqvGc=; b=YmYQ48smNP0yRiczg1vJ2Z0dGEXhbEhzJqvF+sKabr78HkFjZD1Cn+YVGJUqK6d7/l pIQtiW/cT3XRigXiHrKsQBvt1FyUVajkN3G+6fKbkzzfKF0jBlVejXQarSg3djdIkcKg hMNJfPcqLicmS/vn51oDWe41GXrVhgamd0ld0n+EhSAqdx6/11trYILMUsIggXk8NroE cDraBzO0RWTlaibFoDir9ZehlScMVxRfI64GWpmThY5TBoMQ/s+RABbZtpoKCu6K6OC7 3CraZOU9AMu+csfIs7bP9wTQNrKWT7BWGVUiWXlFUk541olalL29nFL34lDd58L+8as/ /nYA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=PGQlj9TEsb6f3Qz+wlAvnxY/j23ohMghNrtlMbgqvGc=; b=1s+Kzeeub/NOTtU7B7D4KSh0ncCA/1spiWtTSarK2HuJZbwAqujrpl40Gh5RIk5Mhi 2+CnOmbfUR2dividtqxvI/PIfBNAO2BNgtr+FcCIpf8rfs9yKmFx2kmUU2AKsXeDBu83 IGAg8jnr7/MTdKnsrMpEC/sZGOwEI4H99NzQ7nBq2Awt2wDj36ensKeKNeKnkAuLVlL7 H/MBUO0lHjzfIrv0Cb+F2wVEDPJQGAHMp6LLdZRF2jIcInQ/ElUclnIhO+2X48lQev4Y CtlvuSpFLFSu4RXjV427TWkLluR2KJ0YBEe4Kwt8SZotEOLXk1NPyI3skgIx44+Mrp/F Sp5A== X-Gm-Message-State: AOAM533c10CGdwbWBUNhxtqRi3A43olum0c9tC4sdIRAW8aR+zLWIfny DRD5ImOWMN9hjym/UbJXrSejIQ== X-Google-Smtp-Source: ABdhPJwPQTgKhU5pPsl0td3R4tZUBHdwuc8zohS2IhF+KaLZ39qP0ErrK2uISxdTZ7cOwBcsWGqBRg== X-Received: by 2002:a5d:6d8b:0:b0:203:e242:5e38 with SMTP id l11-20020a5d6d8b000000b00203e2425e38mr36036111wrs.105.1648634732037; Wed, 30 Mar 2022 03:05:32 -0700 (PDT) Received: from localhost.localdomain ([2a01:e34:ed2f:f020:8f92:3217:a3c6:3ee8]) by smtp.gmail.com with ESMTPSA id p8-20020a5d4e08000000b002054b5437f2sm16542743wrt.115.2022.03.30.03.05.30 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 30 Mar 2022 03:05:31 -0700 (PDT) From: Daniel Lezcano To: daniel.lezcano@linaro.org, rafael@kernel.org Cc: srinivas.pandruvada@linux.intel.com, linux-kernel@vger.kernel.org, linux-pm@vger.kernel.org, Amit Kucheria , Zhang Rui , Sasha Levin , Jonathan Cameron , William Breathitt Gray Subject: [PATCH v2 4/4] tools/thermal: Add thermal daemon skeleton Date: Wed, 30 Mar 2022 12:04:44 +0200 Message-Id: <20220330100444.3846661-5-daniel.lezcano@linaro.org> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220330100444.3846661-1-daniel.lezcano@linaro.org> References: <20220330100444.3846661-1-daniel.lezcano@linaro.org> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-pm@vger.kernel.org This change provides a simple daemon skeleton. It is an example of how to use the thermal library which wraps all the complex code related to the netlink and transforms it into a callback oriented code. The goal of this skeleton is to give a base brick for anyone interested in writing its own thermal engine or as an example to rely on to write its own thermal monitoring implementation. In the future, it will evolve with more features and hopefully more logic. Signed-off-by: Daniel Lezcano --- tools/Makefile | 16 +- tools/thermal/thermal-engine/Build | 2 + tools/thermal/thermal-engine/Makefile | 28 ++ tools/thermal/thermal-engine/thermal-engine.c | 326 ++++++++++++++++++ 4 files changed, 369 insertions(+), 3 deletions(-) create mode 100644 tools/thermal/thermal-engine/Build create mode 100644 tools/thermal/thermal-engine/Makefile create mode 100644 tools/thermal/thermal-engine/thermal-engine.c diff --git a/tools/Makefile b/tools/Makefile index 78615f8cb463..b71cf39d3c08 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -32,6 +32,7 @@ help: @echo ' spi - spi tools' @echo ' tmon - thermal monitoring and tuning tool' @echo ' thermometer - temperature capture tool' + @echo ' thermal-engine - thermal monitoring tool' @echo ' thermal - thermal library' @echo ' tracing - misc tracing tools' @echo ' turbostat - Intel CPU idle stats and freq reporting tool' @@ -99,6 +100,9 @@ tmon: FORCE thermometer: FORCE $(call descend,thermal/$@) +thermal-engine: FORCE thermal + $(call descend,thermal/$@) + freefall: FORCE $(call descend,laptop/$@) @@ -109,7 +113,7 @@ all: acpi cgroup counter cpupower gpio hv firewire \ perf selftests bootconfig spi turbostat usb \ virtio vm bpf x86_energy_perf_policy \ tmon freefall iio objtool kvm_stat wmi \ - pci debugging tracing thermal thermometer + pci debugging tracing thermal thermometer thermal-engine acpi_install: $(call descend,power/$(@:_install=),install) @@ -135,6 +139,9 @@ tmon_install: thermometer_install: $(call descend,thermal/$(@:_install=),install) +thermal-engine_install: + $(call descend,thermal/$(@:_install=),install) + freefall_install: $(call descend,laptop/$(@:_install=),install) @@ -147,7 +154,7 @@ install: acpi_install cgroup_install counter_install cpupower_install gpio_insta virtio_install vm_install bpf_install x86_energy_perf_policy_install \ tmon_install freefall_install objtool_install kvm_stat_install \ wmi_install pci_install debugging_install intel-speed-select_install \ - tracing_install thermometer_install + tracing_install thermometer_install thermal-engine_install acpi_clean: $(call descend,power/acpi,clean) @@ -183,6 +190,9 @@ turbostat_clean x86_energy_perf_policy_clean intel-speed-select_clean: thermometer_clean: $(call descend,thermal/thermometer,clean) +thermal-engine_clean: + $(call descend,thermal/thermal-engine,clean) + tmon_clean: $(call descend,thermal/tmon,clean) @@ -197,6 +207,6 @@ clean: acpi_clean cgroup_clean counter_clean cpupower_clean hv_clean firewire_cl vm_clean bpf_clean iio_clean x86_energy_perf_policy_clean tmon_clean \ freefall_clean build_clean libbpf_clean libsubcmd_clean \ gpio_clean objtool_clean leds_clean wmi_clean pci_clean firmware_clean debugging_clean \ - intel-speed-select_clean tracing_clean thermal_clean thermometer_clean + intel-speed-select_clean tracing_clean thermal_clean thermometer_clean thermal-engine_clean .PHONY: FORCE diff --git a/tools/thermal/thermal-engine/Build b/tools/thermal/thermal-engine/Build new file mode 100644 index 000000000000..0c8b65248c80 --- /dev/null +++ b/tools/thermal/thermal-engine/Build @@ -0,0 +1,2 @@ +thermal-engine-y += thermal-engine.o + diff --git a/tools/thermal/thermal-engine/Makefile b/tools/thermal/thermal-engine/Makefile new file mode 100644 index 000000000000..6bd05ff89485 --- /dev/null +++ b/tools/thermal/thermal-engine/Makefile @@ -0,0 +1,28 @@ +# SPDX-License-Identifier: GPL-2.0 +# Makefile for thermal tools + +ifeq ($(srctree),) +srctree := $(patsubst %/,%,$(dir $(CURDIR))) +srctree := $(patsubst %/,%,$(dir $(srctree))) +srctree := $(patsubst %/,%,$(dir $(srctree))) +# $(info Determined 'srctree' to be $(srctree)) +endif + +CFLAGS = -Wall -Wextra +CFLAGS += -I$(srctree)/tools/thermal/lib +CFLAGS += -I$(srctree)/tools/lib/thermal/include + +LDFLAGS = -L$(srctree)/tools/thermal/lib +LDFLAGS += -L$(srctree)/tools/lib/thermal +LDFLAGS += -lthermal_tools +LDFLAGS += -lthermal +LDFLAGS += -lconfig +LDFLAGS += -lnl-genl-3 -lnl-3 + +VERSION = 0.0.1 + +all: thermal-engine +%: %.c + $(CC) $(CFLAGS) -D VERSION=\"$(VERSION)\" -o $@ $^ $(LDFLAGS) +clean: + $(RM) thermal-engine diff --git a/tools/thermal/thermal-engine/thermal-engine.c b/tools/thermal/thermal-engine/thermal-engine.c new file mode 100644 index 000000000000..525520049aa2 --- /dev/null +++ b/tools/thermal/thermal-engine/thermal-engine.c @@ -0,0 +1,326 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Thermal monitoring tool based on the thermal netlink events. + * + * Copyright (C) 2022 Linaro Ltd. + * + * Author: Daniel Lezcano + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include "thermal-tools.h" + +struct options { + int loglevel; + int logopt; + int interactive; + int daemonize; +}; + +struct thermal_data { + struct thermal_zone *tz; + struct thermal_handler *th; +}; + +static int show_trip(struct thermal_trip *tt, __maybe_unused void *arg) +{ + INFO("trip id=%d, type=%d, temp=%d, hyst=%d\n", + tt->id, tt->type, tt->temp, tt->hyst); + + return 0; +} + +static int show_temp(struct thermal_zone *tz, __maybe_unused void *arg) +{ + thermal_cmd_get_temp(arg, tz); + + INFO("temperature: %d\n", tz->temp); + + return 0; +} + +static int show_governor(struct thermal_zone *tz, __maybe_unused void *arg) +{ + thermal_cmd_get_governor(arg, tz); + + INFO("governor: '%s'\n", tz->governor); + + return 0; +} + +static int show_tz(struct thermal_zone *tz, __maybe_unused void *arg) +{ + INFO("thermal zone '%s', id=%d\n", tz->name, tz->id); + + for_each_thermal_trip(tz->trip, show_trip, NULL); + + show_temp(tz, arg); + + show_governor(tz, arg); + + return 0; +} + +static int tz_create(const char *name, int tz_id, __maybe_unused void *arg) +{ + INFO("Thermal zone '%s'/%d created\n", name, tz_id); + + return 0; +} + +static int tz_delete(int tz_id, __maybe_unused void *arg) +{ + INFO("Thermal zone %d deleted\n", tz_id); + + return 0; +} + +static int tz_disable(int tz_id, void *arg) +{ + struct thermal_data *td = arg; + struct thermal_zone *tz = thermal_zone_find_by_id(td->tz, tz_id); + + INFO("Thermal zone %d ('%s') disabled\n", tz_id, tz->name); + + return 0; +} + +static int tz_enable(int tz_id, void *arg) +{ + struct thermal_data *td = arg; + struct thermal_zone *tz = thermal_zone_find_by_id(td->tz, tz_id); + + INFO("Thermal zone %d ('%s') enabled\n", tz_id, tz->name); + + return 0; +} + +static int trip_high(int tz_id, int trip_id, int temp, void *arg) +{ + struct thermal_data *td = arg; + struct thermal_zone *tz = thermal_zone_find_by_id(td->tz, tz_id); + + INFO("Thermal zone %d ('%s'): trip point %d crossed way up with %d °C\n", + tz_id, tz->name, trip_id, temp); + + return 0; +} + +static int trip_low(int tz_id, int trip_id, int temp, void *arg) +{ + struct thermal_data *td = arg; + struct thermal_zone *tz = thermal_zone_find_by_id(td->tz, tz_id); + + INFO("Thermal zone %d ('%s'): trip point %d crossed way down with %d °C\n", + tz_id, tz->name, trip_id, temp); + + return 0; +} + +static int trip_add(int tz_id, int trip_id, int type, int temp, int hyst, __maybe_unused void *arg) +{ + INFO("Trip point added %d: id=%d, type=%d, temp=%d, hyst=%d\n", + tz_id, trip_id, type, temp, hyst); + + return 0; +} + +static int trip_delete(int tz_id, int trip_id, __maybe_unused void *arg) +{ + INFO("Trip point deleted %d: id=%d\n", tz_id, trip_id); + + return 0; +} + +static int trip_change(int tz_id, int trip_id, int type, int temp, + int hyst, __maybe_unused void *arg) +{ + struct thermal_data *td = arg; + struct thermal_zone *tz = thermal_zone_find_by_id(td->tz, tz_id); + + INFO("Trip point changed %d: id=%d, type=%d, temp=%d, hyst=%d\n", + tz_id, trip_id, type, temp, hyst); + + tz->trip[trip_id].type = type; + tz->trip[trip_id].temp = temp; + tz->trip[trip_id].hyst = hyst; + + return 0; +} + +static int cdev_add(const char *name, int cdev_id, int max_state, __maybe_unused void *arg) +{ + INFO("Cooling device '%s'/%d (max state=%d) added\n", name, cdev_id, max_state); + + return 0; +} + +static int cdev_delete(int cdev_id, __maybe_unused void *arg) +{ + INFO("Cooling device %d deleted", cdev_id); + + return 0; +} + +static int cdev_update(int cdev_id, int cur_state, __maybe_unused void *arg) +{ + INFO("cdev:%d state:%d\n", cdev_id, cur_state); + + return 0; +} + +static int gov_change(int tz_id, const char *name, __maybe_unused void *arg) +{ + struct thermal_data *td = arg; + struct thermal_zone *tz = thermal_zone_find_by_id(td->tz, tz_id); + + INFO("%s: governor changed %s -> %s\n", tz->name, tz->governor, name); + + strcpy(tz->governor, name); + + return 0; +} + +static struct thermal_ops ops = { + .events.tz_create = tz_create, + .events.tz_delete = tz_delete, + .events.tz_disable = tz_disable, + .events.tz_enable = tz_enable, + .events.trip_high = trip_high, + .events.trip_low = trip_low, + .events.trip_add = trip_add, + .events.trip_delete = trip_delete, + .events.trip_change = trip_change, + .events.cdev_add = cdev_add, + .events.cdev_delete = cdev_delete, + .events.cdev_update = cdev_update, + .events.gov_change = gov_change +}; + +static int thermal_event(__maybe_unused int fd, __maybe_unused void *arg) +{ + struct thermal_data *td = arg; + + return thermal_events_handle(td->th, td); +} + +static void help(const char *cmd) +{ + printf("%s : A thermal monitoring engine based on notifications\n", cmd); + printf("Usage: %s [options]\n", cmd); + printf("\t-h, --help\t\tthis help\n"); + printf("\t-d, --daemonize\tcapture duration\n"); + printf("\t-l , --loglevel \tlog level: "); + printf("DEBUG, INFO, NOTICE, WARN, ERROR\n"); + printf("\t-s, --syslog\t\toutput to syslog\n"); + printf("\n"); + exit(0); +} + +static int options_init(int argc, char *argv[], struct options *options) +{ + int opt; + + struct option long_options[] = { + { "help", no_argument, NULL, 'h' }, + { "daemonize", no_argument, NULL, 'd' }, + { "syslog", no_argument, NULL, 's' }, + { "loglevel", required_argument, NULL, 'l' }, + { 0, 0, 0, 0 } + }; + + while (1) { + + int optindex = 0; + + opt = getopt_long(argc, argv, "l:dhs", long_options, &optindex); + if (opt == -1) + break; + + switch (opt) { + case 'l': + options->loglevel = log_str2level(optarg); + break; + case 'd': + options->daemonize = 1; + break; + case 's': + options->logopt = TO_SYSLOG; + break; + case 'h': + help(basename(argv[0])); + break; + default: /* '?' */ + ERROR("Usage: %s --help\n", argv[0]); + return -1; + } + } + + return 0; +} + +enum { + THERMAL_ENGINE_SUCCESS = 0, + THERMAL_ENGINE_OPTION_ERROR, + THERMAL_ENGINE_DAEMON_ERROR, + THERMAL_ENGINE_LOG_ERROR, + THERMAL_ENGINE_THERMAL_ERROR, + THERMAL_ENGINE_MAINLOOP_ERROR, +}; + +int main(int argc, char *argv[]) +{ + struct thermal_data td; + struct options options = { + .loglevel = LOG_INFO, + .logopt = TO_STDOUT, + }; + + if (options_init(argc, argv, &options)) + return THERMAL_ENGINE_OPTION_ERROR; + + if (options.daemonize && daemon(0, 0)) + return THERMAL_ENGINE_DAEMON_ERROR; + + if (log_init(options.loglevel, basename(argv[0]), options.logopt)) + return THERMAL_ENGINE_LOG_ERROR; + + td.th = thermal_init(&ops); + if (!td.th) + return THERMAL_ENGINE_THERMAL_ERROR; + + td.tz = thermal_zone_discover(td.th); + if (!td.tz) + return THERMAL_ENGINE_THERMAL_ERROR; + + for_each_thermal_zone(td.tz, show_tz, td.th); + + if (mainloop_init()) + return THERMAL_ENGINE_MAINLOOP_ERROR; + + if (mainloop_add(thermal_events_fd(td.th), thermal_event, &td)) + return THERMAL_ENGINE_MAINLOOP_ERROR; + + INFO("Waiting for thermal events ...\n"); + + if (mainloop(-1)) + return THERMAL_ENGINE_MAINLOOP_ERROR; + + return THERMAL_ENGINE_SUCCESS; +}