Commit f4fb8596 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'thermal-5.19-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm

Pull thermal control updates from Rafael Wysocki:
 "These add a thermal library and thermal tools to wrap the netlink
  interface into event-based callbacks, improve overheat condition
  handling during suspend-to-idle on Intel SoCs, add some new hardware
  support, fix bugs and clean up code.

  Specifics:

   - Add thermal library and thermal tools to encapsulate the netlink
     into event based callbacks (Daniel Lezcano, Jiapeng Chong).

   - Improve overheat condition handling during suspend-to-idle in the
     Intel PCH thermal driver (Zhang Rui).

   - Use local ops instead of global ops in devfreq_cooling (Kant Fan).

   - Clean up _OSC handling in int340x (Davidlohr Bueso).

   - Switch hisi_termal from CONFIG_PM_SLEEP guards to pm_sleep_ptr()
     (Hesham Almatary).

   - Add new k3 j72xx bangdap driver and the corresponding bindings
     (Keerthy).

   - Fix missing of_node_put() in the SC iMX driver at probe time
     (Miaoqian Lin).

   - Fix memory leak in __thermal_cooling_device_register()
     when device_register() fails by calling
     thermal_cooling_device_destroy_sysfs() (Yang Yingliang).

   - Add sc8180x and sc8280xp compatible string in the DT bindings and
     lMH support for QCom tsens driver (Bjorn Andersson).

   - Fix OTP Calibration Register values conforming to the documentation
     on RZ/G2L and bindings documentation for RZ/G2UL (Biju Das).

   - Fix type in kerneldoc description for __thermal_bind_params
     (Corentin Labbe).

   - Fix potential NULL dereference in sr_thermal_probe() on Broadcom
     platform (Zheng Yongjun).

   - Add change mode ops to the thermal-of sensor (Manaf Meethalavalappu
     Pallikunhi).

   - Fix non-negative value support by preventing the value to be clamp
     to zero (Stefan Wahren).

   - Add compatible string and DT bindings for MSM8960 tsens driver
     (Dmitry Baryshkov).

   - Add hwmon support for K3 driver (Massimiliano Minella).

   - Refactor and add multiple generations support for QCom ADC driver
     (Jishnu Prakash).

   - Use platform_get_irq_optional() to get the interrupt on RCar driver
     and document Document RZ/V2L bindings (Lad Prabhakar).

   - Remove NULL check after container_of() call from the Intel HFI
     thermal driver (Haowen Bai)"

* tag 'thermal-5.19-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm: (38 commits)
  thermal: intel: pch: improve the cooling delay log
  thermal: intel: pch: enhance overheat handling
  thermal: intel: pch: move cooling delay to suspend_noirq phase
  PM: wakeup: expose pm_wakeup_pending to modules
  thermal: k3_j72xx_bandgap: Add the bandgap driver support
  dt-bindings: thermal: k3-j72xx: Add VTM bindings documentation
  thermal/drivers/imx_sc_thermal: Fix refcount leak in imx_sc_thermal_probe
  thermal/core: Fix memory leak in __thermal_cooling_device_register()
  dt-bindings: thermal: tsens: Add sc8280xp compatible
  dt-bindings: thermal: lmh: Add Qualcomm sc8180x compatible
  thermal/drivers/qcom/lmh: Add sc8180x compatible
  thermal/drivers/rz2gl: Fix OTP Calibration Register values
  dt-bindings: thermal: rzg2l-thermal: Document RZ/G2UL bindings
  thermal: thermal_of: fix typo on __thermal_bind_params
  tools/thermal: remove unneeded semicolon
  tools/lib/thermal: remove unneeded semicolon
  thermal/drivers/broadcom: Fix potential NULL dereference in sr_thermal_probe
  tools/thermal: Add thermal daemon skeleton
  tools/thermal: Add a temperature capture tool
  tools/thermal: Add util library
  ...
parents 09583dfe bbb544f3
......@@ -18,6 +18,7 @@ description:
properties:
compatible:
enum:
- qcom,sc8180x-lmh
- qcom,sdm845-lmh
- qcom,sm8150-lmh
......
......@@ -10,7 +10,9 @@ maintainers:
properties:
compatible:
const: qcom,spmi-adc-tm5
enum:
- qcom,spmi-adc-tm5
- qcom,spmi-adc-tm5-gen2
reg:
maxItems: 1
......@@ -33,6 +35,7 @@ properties:
qcom,avg-samples:
$ref: /schemas/types.yaml#/definitions/uint32
description: Number of samples to be used for measurement.
Not applicable for Gen2 ADC_TM peripheral.
enum:
- 1
- 2
......@@ -45,6 +48,7 @@ properties:
$ref: /schemas/types.yaml#/definitions/uint32
description: This parameter is used to decrease ADC sampling rate.
Quicker measurements can be made by reducing decimation ratio.
Not applicable for Gen2 ADC_TM peripheral.
enum:
- 250
- 420
......@@ -93,6 +97,29 @@ patternProperties:
- const: 1
- enum: [ 1, 3, 4, 6, 20, 8, 10 ]
qcom,avg-samples:
$ref: /schemas/types.yaml#/definitions/uint32
description: Number of samples to be used for measurement.
This property in child node is applicable only for Gen2 ADC_TM peripheral.
enum:
- 1
- 2
- 4
- 8
- 16
default: 1
qcom,decimation:
$ref: /schemas/types.yaml#/definitions/uint32
description: This parameter is used to decrease ADC sampling rate.
Quicker measurements can be made by reducing decimation ratio.
This property in child node is applicable only for Gen2 ADC_TM peripheral.
enum:
- 85
- 340
- 1360
default: 1360
required:
- reg
- io-channels
......@@ -100,6 +127,31 @@ patternProperties:
additionalProperties:
false
allOf:
- if:
properties:
compatible:
contains:
const: qcom,spmi-adc-tm5
then:
patternProperties:
"^([-a-z0-9]*)@[0-7]$":
properties:
qcom,decimation: false
qcom,avg-samples: false
- if:
properties:
compatible:
contains:
const: qcom,spmi-adc-tm5-gen2
then:
properties:
qcom,avg-samples: false
qcom,decimation: false
required:
- compatible
- reg
......@@ -124,7 +176,7 @@ examples:
#size-cells = <0>;
#io-channel-cells = <1>;
/* Other propreties are omitted */
/* Other properties are omitted */
conn-therm@4f {
reg = <ADC5_AMUX_THM3_100K_PU>;
qcom,ratiometric;
......@@ -148,4 +200,58 @@ examples:
};
};
};
- |
#include <dt-bindings/iio/qcom,spmi-adc7-pmk8350.h>
#include <dt-bindings/iio/qcom,spmi-adc7-pm8350.h>
#include <dt-bindings/interrupt-controller/irq.h>
spmi_bus {
#address-cells = <1>;
#size-cells = <0>;
pmk8350_vadc: adc@3100 {
reg = <0x3100>;
compatible = "qcom,spmi-adc7";
#address-cells = <1>;
#size-cells = <0>;
#io-channel-cells = <1>;
/* Other properties are omitted */
xo-therm@44 {
reg = <PMK8350_ADC7_AMUX_THM1_100K_PU>;
qcom,ratiometric;
qcom,hw-settle-time = <200>;
};
conn-therm@47 {
reg = <PM8350_ADC7_AMUX_THM4_100K_PU>;
qcom,ratiometric;
qcom,hw-settle-time = <200>;
};
};
pmk8350_adc_tm: adc-tm@3400 {
compatible = "qcom,spmi-adc-tm5-gen2";
reg = <0x3400>;
interrupts = <0x0 0x34 0x0 IRQ_TYPE_EDGE_RISING>;
#thermal-sensor-cells = <1>;
#address-cells = <1>;
#size-cells = <0>;
pmk8350-xo-therm@0 {
reg = <0>;
io-channels = <&pmk8350_vadc PMK8350_ADC7_AMUX_THM1_100K_PU>;
qcom,decimation = <340>;
qcom,ratiometric;
qcom,hw-settle-time-us = <200>;
};
conn-therm@1 {
reg = <1>;
io-channels = <&pmk8350_vadc PM8350_ADC7_AMUX_THM4_100K_PU>;
qcom,avg-samples = <2>;
qcom,ratiometric;
qcom,hw-settle-time-us = <200>;
};
};
};
...
......@@ -19,10 +19,11 @@ description: |
properties:
compatible:
oneOf:
- description: msm9860 TSENS based
- description: msm8960 TSENS based
items:
- enum:
- qcom,ipq8064-tsens
- qcom,msm8960-tsens
- description: v0.1 of TSENS
items:
......@@ -49,6 +50,7 @@ properties:
- qcom,sc7180-tsens
- qcom,sc7280-tsens
- qcom,sc8180x-tsens
- qcom,sc8280xp-tsens
- qcom,sdm630-tsens
- qcom,sdm845-tsens
- qcom,sm8150-tsens
......@@ -116,6 +118,7 @@ allOf:
- qcom,ipq8064-tsens
- qcom,mdm9607-tsens
- qcom,msm8916-tsens
- qcom,msm8960-tsens
- qcom,msm8974-tsens
- qcom,msm8976-tsens
- qcom,qcs404-tsens
......
......@@ -17,7 +17,9 @@ properties:
compatible:
items:
- enum:
- renesas,r9a07g043-tsu # RZ/G2UL
- renesas,r9a07g044-tsu # RZ/G2{L,LC}
- renesas,r9a07g054-tsu # RZ/V2L
- const: renesas,rzg2l-tsu
reg:
......
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/thermal/ti,j72xx-thermal.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Texas Instruments J72XX VTM (DTS) binding
maintainers:
- Keerthy <j-keerthy@ti.com>
properties:
compatible:
enum:
- ti,j721e-vtm
- ti,j7200-vtm
reg:
items:
- description: VTM cfg1 register space
- description: VTM cfg2 register space
- description: VTM efuse register space
power-domains:
maxItems: 1
"#thermal-sensor-cells":
const: 1
required:
- compatible
- reg
- power-domains
- "#thermal-sensor-cells"
additionalProperties: false
examples:
- |
#include <dt-bindings/soc/ti,sci_pm_domain.h>
wkup_vtm0: thermal-sensor@42040000 {
compatible = "ti,j721e-vtm";
reg = <0x42040000 0x350>,
<0x42050000 0x350>,
<0x43000300 0x10>;
power-domains = <&k3_pds 154 TI_SCI_PD_EXCLUSIVE>;
#thermal-sensor-cells = <1>;
};
mpu_thermal: mpu-thermal {
polling-delay-passive = <250>; /* milliseconds */
polling-delay = <500>; /* milliseconds */
thermal-sensors = <&wkup_vtm0 0>;
trips {
mpu_crit: mpu-crit {
temperature = <125000>; /* milliCelsius */
hysteresis = <2000>; /* milliCelsius */
type = "critical";
};
};
};
...
......@@ -19589,6 +19589,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
......
......@@ -930,6 +930,7 @@ bool pm_wakeup_pending(void)
return ret || atomic_read(&pm_abort_suspend) > 0;
}
EXPORT_SYMBOL_GPL(pm_wakeup_pending);
void pm_system_wakeup(void)
{
......
......@@ -677,6 +677,17 @@ u16 qcom_adc_tm5_temp_volt_scale(unsigned int prescale_ratio,
}
EXPORT_SYMBOL(qcom_adc_tm5_temp_volt_scale);
u16 qcom_adc_tm5_gen2_temp_res_scale(int temp)
{
int64_t resistance;
resistance = qcom_vadc_map_temp_voltage(adcmap7_100k,
ARRAY_SIZE(adcmap7_100k), temp);
return div64_s64(resistance * RATIO_MAX_ADC7, resistance + R_PU_100K);
}
EXPORT_SYMBOL(qcom_adc_tm5_gen2_temp_res_scale);
int qcom_adc5_hw_scale(enum vadc_scale_fn_type scaletype,
unsigned int prescale_ratio,
const struct adc5_data *data,
......
......@@ -28,7 +28,7 @@ thermal_sys-$(CONFIG_CPU_IDLE_THERMAL) += cpuidle_cooling.o
# devfreq cooling
thermal_sys-$(CONFIG_DEVFREQ_THERMAL) += devfreq_cooling.o
obj-$(CONFIG_K3_THERMAL) += k3_bandgap.o
obj-$(CONFIG_K3_THERMAL) += k3_bandgap.o k3_j72xx_bandgap.o
# platform thermal drivers
obj-y += broadcom/
obj-$(CONFIG_THERMAL_MMIO) += thermal_mmio.o
......
......@@ -38,7 +38,6 @@ static int bcm2711_get_temp(void *data, int *temp)
int offset = thermal_zone_get_offset(priv->thermal);
u32 val;
int ret;
long t;
ret = regmap_read(priv->regmap, AVS_RO_TEMP_STATUS, &val);
if (ret)
......@@ -50,9 +49,7 @@ static int bcm2711_get_temp(void *data, int *temp)
val &= AVS_RO_TEMP_STATUS_DATA_MSK;
/* Convert a HW code to a temperature reading (millidegree celsius) */
t = slope * val + offset;
*temp = t < 0 ? 0 : t;
*temp = slope * val + offset;
return 0;
}
......
......@@ -60,6 +60,9 @@ static int sr_thermal_probe(struct platform_device *pdev)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
return -ENOENT;
sr_thermal->regs = (void __iomem *)devm_memremap(&pdev->dev, res->start,
resource_size(res),
MEMREMAP_WB);
......
......@@ -359,22 +359,29 @@ of_devfreq_cooling_register_power(struct device_node *np, struct devfreq *df,
struct device *dev = df->dev.parent;
struct devfreq_cooling_device *dfc;
struct em_perf_domain *em;
struct thermal_cooling_device_ops *ops;
char *name;
int err, num_opps;
dfc = kzalloc(sizeof(*dfc), GFP_KERNEL);
if (!dfc)
ops = kmemdup(&devfreq_cooling_ops, sizeof(*ops), GFP_KERNEL);
if (!ops)
return ERR_PTR(-ENOMEM);
dfc = kzalloc(sizeof(*dfc), GFP_KERNEL);
if (!dfc) {
err = -ENOMEM;
goto free_ops;
}
dfc->devfreq = df;
em = em_pd_get(dev);
if (em && !em_is_artificial(em)) {
dfc->em_pd = em;
devfreq_cooling_ops.get_requested_power =
ops->get_requested_power =
devfreq_cooling_get_requested_power;
devfreq_cooling_ops.state2power = devfreq_cooling_state2power;
devfreq_cooling_ops.power2state = devfreq_cooling_power2state;
ops->state2power = devfreq_cooling_state2power;
ops->power2state = devfreq_cooling_power2state;
dfc->power_ops = dfc_power;
......@@ -409,8 +416,7 @@ of_devfreq_cooling_register_power(struct device_node *np, struct devfreq *df,
if (!name)
goto remove_qos_req;
cdev = thermal_of_cooling_device_register(np, name, dfc,
&devfreq_cooling_ops);
cdev = thermal_of_cooling_device_register(np, name, dfc, ops);
kfree(name);
if (IS_ERR(cdev)) {
......@@ -431,6 +437,8 @@ of_devfreq_cooling_register_power(struct device_node *np, struct devfreq *df,
kfree(dfc->freq_table);
free_dfc:
kfree(dfc);
free_ops:
kfree(ops);
return ERR_PTR(err);
}
......@@ -512,11 +520,13 @@ EXPORT_SYMBOL_GPL(devfreq_cooling_em_register);
void devfreq_cooling_unregister(struct thermal_cooling_device *cdev)
{
struct devfreq_cooling_device *dfc;
const struct thermal_cooling_device_ops *ops;
struct device *dev;
if (IS_ERR_OR_NULL(cdev))
return;
ops = cdev->ops;
dfc = cdev->devdata;
dev = dfc->devfreq->dev.parent;
......@@ -527,5 +537,6 @@ void devfreq_cooling_unregister(struct thermal_cooling_device *cdev)
kfree(dfc->freq_table);
kfree(dfc);
kfree(ops);
}
EXPORT_SYMBOL_GPL(devfreq_cooling_unregister);
......@@ -629,7 +629,6 @@ static int hisi_thermal_remove(struct platform_device *pdev)
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int hisi_thermal_suspend(struct device *dev)
{
struct hisi_thermal_data *data = dev_get_drvdata(dev);
......@@ -651,15 +650,14 @@ static int hisi_thermal_resume(struct device *dev)
return ret;
}
#endif
static SIMPLE_DEV_PM_OPS(hisi_thermal_pm_ops,
static DEFINE_SIMPLE_DEV_PM_OPS(hisi_thermal_pm_ops,
hisi_thermal_suspend, hisi_thermal_resume);
static struct platform_driver hisi_thermal_driver = {
.driver = {
.name = "hisi_thermal",
.pm = &hisi_thermal_pm_ops,
.pm = pm_sleep_ptr(&hisi_thermal_pm_ops),
.of_match_table = of_hisi_thermal_match,
},
.probe = hisi_thermal_probe,
......
......@@ -94,8 +94,8 @@ static int imx_sc_thermal_probe(struct platform_device *pdev)
sensor = devm_kzalloc(&pdev->dev, sizeof(*sensor), GFP_KERNEL);
if (!sensor) {
of_node_put(child);
of_node_put(sensor_np);
return -ENOMEM;
ret = -ENOMEM;
goto put_node;
}
ret = thermal_zone_of_get_sensor_id(child,
......@@ -124,7 +124,9 @@ static int imx_sc_thermal_probe(struct platform_device *pdev)
dev_warn(&pdev->dev, "failed to add hwmon sysfs attributes\n");
}
put_node:
of_node_put(sensor_np);
of_node_put(np);
return ret;
}
......
......@@ -169,28 +169,25 @@ static int int3400_thermal_run_osc(acpi_handle handle, char *uuid_str, int *enab
acpi_status status;
int result = 0;
struct acpi_osc_context context = {
.uuid_str = NULL,
.uuid_str = uuid_str,
.rev = 1,
.cap.length = 8,
.cap.pointer = buf,
};
context.uuid_str = uuid_str;
buf[OSC_QUERY_DWORD] = 0;
buf[OSC_SUPPORT_DWORD] = *enable;
context.cap.pointer = buf;
status = acpi_run_osc(handle, &context);
if (ACPI_SUCCESS(status)) {
ret = *((u32 *)(context.ret.pointer + 4));
if (ret != *enable)
result = -EPERM;
kfree(context.ret.pointer);
} else
result = -EPERM;
kfree(context.ret.pointer);
return result;
}
......@@ -524,21 +521,18 @@ static void int3400_setup_gddv(struct int3400_thermal_priv *priv)
obj = buffer.pointer;
if (obj->type != ACPI_TYPE_PACKAGE || obj->package.count != 1
|| obj->package.elements[0].type != ACPI_TYPE_BUFFER) {
kfree(buffer.pointer);
return;
}
|| obj->package.elements[0].type != ACPI_TYPE_BUFFER)
goto out_free;
priv->data_vault = kmemdup(obj->package.elements[0].buffer.pointer,
obj->package.elements[0].buffer.length,
GFP_KERNEL);
if (!priv->data_vault) {
kfree(buffer.pointer);
return;
}
if (!priv->data_vault)
goto out_free;
bin_attr_data_vault.private = priv->data_vault;
bin_attr_data_vault.size = obj->package.elements[0].buffer.length;
out_free:
kfree(buffer.pointer);
}
......
......@@ -243,8 +243,6 @@ static void hfi_update_work_fn(struct work_struct *work)
hfi_instance = container_of(to_delayed_work(work), struct hfi_instance,
update_work);
if (!hfi_instance)
return;
update_capabilities(hfi_instance);
}
......
......@@ -70,8 +70,8 @@ static unsigned int delay_timeout = 100;
module_param(delay_timeout, int, 0644);
MODULE_PARM_DESC(delay_timeout, "amount of time delay for each iteration.");
/* Number of iterations for cooling delay, 10 counts by default for now */
static unsigned int delay_cnt = 10;
/* Number of iterations for cooling delay, 600 counts by default for now */
static unsigned int delay_cnt = 600;
module_param(delay_cnt, int, 0644);
MODULE_PARM_DESC(delay_cnt, "total number of iterations for time delay.");
......@@ -193,10 +193,11 @@ static int pch_wpt_get_temp(struct pch_thermal_device *ptd, int *temp)
return 0;
}
/* Cool the PCH when it's overheat in .suspend_noirq phase */
static int pch_wpt_suspend(struct pch_thermal_device *ptd)
{
u8 tsel;
u8 pch_delay_cnt = 1;
int pch_delay_cnt = 0;
u16 pch_thr_temp, pch_cur_temp;
/* Shutdown the thermal sensor if it is not enabled by BIOS */
......@@ -232,26 +233,38 @@ static int pch_wpt_suspend(struct pch_thermal_device *ptd)
* temperature stays above threshold, notify the warning message
* which helps to indentify the reason why S0ix entry was rejected.
*/
while (pch_delay_cnt <= delay_cnt) {
if (pch_cur_temp <= pch_thr_temp)
while (pch_delay_cnt < delay_cnt) {
if (pch_cur_temp < pch_thr_temp)
break;
dev_warn(&ptd->pdev->dev,
if (pm_wakeup_pending()) {
dev_warn(&ptd->pdev->dev, "Wakeup event detected, abort cooling\n");
return 0;
}
pch_delay_cnt++;
dev_dbg(&ptd->pdev->dev,
"CPU-PCH current temp [%dC] higher than the threshold temp [%dC], sleep %d times for %d ms duration\n",
pch_cur_temp, pch_thr_temp, pch_delay_cnt, delay_timeout);
msleep(delay_timeout);
/* Read the PCH current temperature for next cycle. */
pch_cur_temp = GET_PCH_TEMP(WPT_TEMP_TSR & readw(ptd->hw_base + WPT_TEMP));
pch_delay_cnt++;
}
if (pch_cur_temp > pch_thr_temp)
if (pch_cur_temp >= pch_thr_temp)
dev_warn(&ptd->pdev->dev,
"CPU-PCH is hot [%dC] even after delay, continue to suspend. S0ix might fail\n",
pch_cur_temp);
else
dev_info(&ptd->pdev->dev,
"CPU-PCH is cool [%dC], continue to suspend\n", pch_cur_temp);
"CPU-PCH is hot [%dC] after %d ms delay. S0ix might fail\n",
pch_cur_temp, pch_delay_cnt * delay_timeout);
else {
if (pch_delay_cnt)
dev_info(&ptd->pdev->dev,
"CPU-PCH is cool [%dC] after %d ms delay\n",
pch_cur_temp, pch_delay_cnt * delay_timeout);
else
dev_info(&ptd->pdev->dev,
"CPU-PCH is cool [%dC]\n",
pch_cur_temp);
}
return 0;
}
......@@ -455,7 +468,7 @@ static void intel_pch_thermal_remove(struct pci_dev *pdev)
pci_disable_device(pdev);
}
static int intel_pch_thermal_suspend(struct device *device)
static int intel_pch_thermal_suspend_noirq(struct device *device)
{
struct pch_thermal_device *ptd = dev_get_drvdata(device);
......@@ -495,7 +508,7 @@ static const struct pci_device_id intel_pch_thermal_id[] = {
MODULE_DEVICE_TABLE(pci, intel_pch_thermal_id);
static const struct dev_pm_ops intel_pch_pm_ops = {
.suspend = intel_pch_thermal_suspend,
.suspend_noirq = intel_pch_thermal_suspend_noirq,
.resume = intel_pch_thermal_resume,
};
......
......@@ -16,6 +16,8 @@
#include <linux/thermal.h>
#include <linux/types.h>
#include "thermal_hwmon.h"
#define K3_VTM_DEVINFO_PWR0_OFFSET 0x4
#define K3_VTM_DEVINFO_PWR0_TEMPSENS_CT_MASK 0xf0
#define K3_VTM_TMPSENS0_CTRL_OFFSET 0x80
......@@ -219,6 +221,9 @@ static int k3_bandgap_probe(struct platform_device *pdev)
ret = PTR_ERR(data[id].tzd);
goto err_alloc;
}
if (devm_thermal_add_hwmon_sysfs(data[id].tzd))
dev_warn(dev, "Failed to add hwmon sysfs attributes\n");
}
platform_set_drvdata(pdev, bgp);
......
This diff is collapsed.
......@@ -220,6 +220,7 @@ static int lmh_probe(struct platform_device *pdev)
}
static const struct of_device_id lmh_table[] = {
{ .compatible = "qcom,sc8180x-lmh", },
{ .compatible = "qcom,sdm845-lmh", .data = (void *)LMH_ENABLE_ALGOS},
{ .compatible = "qcom,sm8150-lmh", },
{}
......
This diff is collapsed.
......@@ -979,6 +979,9 @@ static const struct of_device_id tsens_table[] = {
}, {
.compatible = "qcom,msm8939-tsens",
.data = &data_8939,
}, {
.compatible = "qcom,msm8960-tsens",
.data = &data_8960,
}, {
.compatible = "qcom,msm8974-tsens",
.data = &data_8974,
......
......@@ -445,7 +445,7 @@ static int rcar_thermal_probe(struct platform_device *pdev)
struct rcar_thermal_common *common;
struct rcar_thermal_priv *priv;
struct device *dev = &pdev->dev;
struct resource *res, *irq;
struct resource *res;
const struct rcar_thermal_chip *chip = of_device_get_match_data(dev);
int mres = 0;
int i;
......@@ -467,9 +467,16 @@ static int rcar_thermal_probe(struct platform_device *pdev)
pm_runtime_get_sync(dev);
for (i = 0; i < chip->nirqs; i++) {
irq = platform_get_resource(pdev, IORESOURCE_IRQ, i);
if (!irq)
continue;
int irq;
ret = platform_get_irq_optional(pdev, i);
if (ret < 0 && ret != -ENXIO)
goto error_unregister;
if (ret > 0)
irq = ret;
else
break;
if (!common->base) {
/*
* platform has IRQ support.
......@@ -487,7 +494,7 @@ static int rcar_thermal_probe(struct platform_device *pdev)
idle = 0; /* polling delay is not needed */
}
ret = devm_request_irq(dev, irq->start, rcar_thermal_irq,
ret = devm_request_irq(dev, irq, rcar_thermal_irq,
IRQF_SHARED, dev_name(dev), common);
if (ret) {
dev_err(dev, "irq request failed\n ");
......
......@@ -32,6 +32,8 @@
#define TSU_SS 0x10
#define OTPTSUTRIM_REG(n) (0x18 + ((n) * 0x4))
#define OTPTSUTRIM_EN_MASK BIT(31)
#define OTPTSUTRIM_MASK GENMASK(11, 0)
/* Sensor Mode Register(TSU_SM) */
#define TSU_SM_EN_TS BIT(0)
......@@ -183,11 +185,15 @@ static int rzg2l_thermal_probe(struct platform_device *pdev)
pm_runtime_get_sync(dev);
priv->calib0 = rzg2l_thermal_read(priv, OTPTSUTRIM_REG(0));
if (!priv->calib0)
if (priv->calib0 & OTPTSUTRIM_EN_MASK)
priv->calib0 &= OTPTSUTRIM_MASK;
else
priv->calib0 = SW_CALIB0_VAL;
priv->calib1 = rzg2l_thermal_read(priv, OTPTSUTRIM_REG(1));
if (!priv->calib1)
if (priv->calib1 & OTPTSUTRIM_EN_MASK)
priv->calib1 &= OTPTSUTRIM_MASK;
else
priv->calib1 = SW_CALIB1_VAL;
platform_set_drvdata(pdev, priv);
......
......@@ -947,6 +947,7 @@ __thermal_cooling_device_register(struct device_node *np,
return cdev;
out_kfree_type:
thermal_cooling_device_destroy_sysfs(cdev);
kfree(cdev->type);
put_device(&cdev->device);
cdev = NULL;
......
......@@ -35,7 +35,7 @@ struct __thermal_cooling_bind_param {
};
/**
* struct __thermal_bind_param - a match between trip and cooling device
* struct __thermal_bind_params - a match between trip and cooling device
* @tcbp: a pointer to an array of cooling devices
* @count: number of elements in array
* @trip_id: the trip point index
......@@ -203,6 +203,14 @@ static int of_thermal_get_trend(struct thermal_zone_device *tz, int trip,
return data->ops->get_trend(data->sensor_data, trip, trend);
}
static int of_thermal_change_mode(struct thermal_zone_device *tz,
enum thermal_device_mode mode)
{
struct __thermal_zone *data = tz->devdata;
return data->ops->change_mode(data->sensor_data, mode);
}
static int of_thermal_bind(struct thermal_zone_device *thermal,
struct thermal_cooling_device *cdev)
{
......@@ -408,6 +416,9 @@ thermal_zone_of_add_sensor(struct device_node *zone,
if (ops->set_emul_temp)
tzd->ops->set_emul_temp = of_thermal_set_emul_temp;
if (ops->change_mode)
tzd->ops->change_mode = of_thermal_change_mode;
mutex_unlock(&tzd->lock);
return tzd;
......@@ -569,6 +580,7 @@ void thermal_zone_of_sensor_unregister(struct device *dev,
tzd->ops->get_temp = NULL;
tzd->ops->get_trend = NULL;
tzd->ops->set_emul_temp = NULL;
tzd->ops->change_mode = NULL;
tz->ops = NULL;
tz->sensor_data = NULL;
......
......@@ -152,6 +152,8 @@ int qcom_adc5_hw_scale(enum vadc_scale_fn_type scaletype,
u16 qcom_adc_tm5_temp_volt_scale(unsigned int prescale_ratio,
u32 full_scale_code_volt, int temp);
u16 qcom_adc_tm5_gen2_temp_res_scale(int temp);
int qcom_adc5_prescaling_from_dt(u32 num, u32 den);
int qcom_adc5_hw_settle_time_from_dt(u32 value, const unsigned int *hw_settle);
......
......@@ -299,6 +299,8 @@ struct thermal_zone_params {
* temperature.
* @set_trip_temp: a pointer to a function that sets the trip temperature on
* hardware.
* @change_mode: a pointer to a function that notifies the thermal zone
* mode change.
*/
struct thermal_zone_of_device_ops {
int (*get_temp)(void *, int *);
......@@ -306,6 +308,7 @@ struct thermal_zone_of_device_ops {
int (*set_trips)(void *, int, int);
int (*set_emul_temp)(void *, int);
int (*set_trip_temp)(void *, int, int);
int (*change_mode) (void *, enum thermal_device_mode);
};
/* Function declarations */
......
......@@ -32,6 +32,9 @@ help:
@echo ' bootconfig - boot config tool'
@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'
@echo ' usb - USB testing tools'
......@@ -89,12 +92,21 @@ 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/$@)
tmon: FORCE
$(call descend,thermal/$@)
thermometer: FORCE
$(call descend,thermal/$@)
thermal-engine: FORCE thermal
$(call descend,thermal/$@)
freefall: FORCE
$(call descend,laptop/$@)
......@@ -105,7 +117,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 thermometer thermal-engine
acpi_install:
$(call descend,power/$(@:_install=),install)
......@@ -119,12 +131,21 @@ 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)
tmon_install:
$(call descend,thermal/$(@:_install=),install)
thermometer_install:
$(call descend,thermal/$(@:_install=),install)
thermal-engine_install:
$(call descend,thermal/$(@:_install=),install)
freefall_install:
$(call descend,laptop/$(@:_install=),install)
......@@ -137,7 +158,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 thermal-engine_install
acpi_clean:
$(call descend,power/acpi,clean)
......@@ -164,9 +185,18 @@ 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)
thermometer_clean:
$(call descend,thermal/thermometer,clean)
thermal-engine_clean:
$(call descend,thermal/thermal-engine,clean)
tmon_clean:
$(call descend,thermal/tmon,clean)
......@@ -181,6 +211,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 thermometer_clean thermal-engine_clean
.PHONY: FORCE
libthermal.so*
libthermal.pc
libthermal-y += commands.o
libthermal-y += events.o
libthermal-y += thermal_nl.o
libthermal-y += sampling.o
libthermal-y += thermal.o
# 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
// SPDX-License-Identifier: LGPL-2.1+
// Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org>
#define _GNU_SOURCE
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <thermal.h>
#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);
}
if (__tz)
__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);
}
if (__cdev)
__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);
}
if (__tt)
__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;
}
// SPDX-License-Identifier: LGPL-2.1+
// Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org>
#include <linux/netlink.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <thermal.h>
#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;
}
/* SPDX-License-Identifier: LGPL-2.1+ */
/* Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org> */
#ifndef __LIBTHERMAL_H
#define __LIBTHERMAL_H
#include <linux/thermal.h>
#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
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:
*;
};
# 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
// SPDX-License-Identifier: LGPL-2.1+
// Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <thermal.h>
#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;
}
// SPDX-License-Identifier: LGPL-2.1+
// Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org>
#include <stdio.h>
#include <thermal.h>
#include "thermal_nl.h"
int for_each_thermal_cdev(struct thermal_cdev *cdev, cb_tc_t cb, void *arg)
{
int i, ret = 0;
if (!cdev)
return 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;
if (!tt)
return 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;
if (!tz)
return 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 (!tz || !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 (!tz || 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;
}
// SPDX-License-Identifier: LGPL-2.1+
// Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <thermal.h>
#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;
}
/* SPDX-License-Identifier: LGPL-2.1+ */
/* Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org> */
#ifndef __THERMAL_H
#define __THERMAL_H
#include <netlink/netlink.h>
#include <netlink/genl/genl.h>
#include <netlink/genl/mngt.h>
#include <netlink/genl/ctrl.h>
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 */
libthermal_tools-y += mainloop.o
libthermal_tools-y += log.o
libthermal_tools-y += uptimeofday.o
# 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
# 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
// SPDX-License-Identifier: LGPL-2.1+
// Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <syslog.h>
#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();
}
/* SPDX-License-Identifier: LGPL-2.1+ */
/* Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org> */
#ifndef __THERMAL_TOOLS_LOG_H
#define __THERMAL_TOOLS_LOG_H
#include <syslog.h>
#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
// SPDX-License-Identifier: LGPL-2.1+
// Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <sys/epoll.h>
#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);
}
/* SPDX-License-Identifier: LGPL-2.1+ */
/* Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org> */
#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
/* SPDX-License-Identifier: LGPL-2.1+ */
/* Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org> */
#ifndef __THERMAL_TOOLS
#define __THERMAL_TOOLS
#include "log.h"
#include "mainloop.h"
#include "uptimeofday.h"
#endif
// SPDX-License-Identifier: LGPL-2.1+
// Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org>
#include <stdio.h>
#include <sys/time.h>
#include <linux/sysinfo.h>
#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;
}
/* SPDX-License-Identifier: LGPL-2.1+ */
/* Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org> */
#ifndef __THERMAL_TOOLS_UPTIMEOFDAY_H
#define __THERMAL_TOOLS_UPTIMEOFDAY_H
#include <sys/sysinfo.h>
#include <sys/time.h>
int uptimeofday_init(void);
unsigned long getuptimeofday_ms(void);
struct timespec msec_to_timespec(int msec);
#endif
thermal-engine-y += thermal-engine.o
# 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
// SPDX-License-Identifier: GPL-2.0-only
/*
* Thermal monitoring tool based on the thermal netlink events.
*
* Copyright (C) 2022 Linaro Ltd.
*
* Author: Daniel Lezcano <daniel.lezcano@kernel.org>
*/
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <libgen.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <syslog.h>
#include <sys/epoll.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <thermal.h>
#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 usage(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\n");
printf("\t-l <level>, --loglevel <level>\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':
usage(basename(argv[0]));
break;
default: /* '?' */
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)) {
ERROR("Usage: %s --help\n", argv[0]);
return THERMAL_ENGINE_OPTION_ERROR;
}
if (options.daemonize && daemon(0, 0)) {
ERROR("Failed to daemonize: %p\n");
return THERMAL_ENGINE_DAEMON_ERROR;
}
if (log_init(options.loglevel, basename(argv[0]), options.logopt)) {
ERROR("Failed to initialize logging facility\n");
return THERMAL_ENGINE_LOG_ERROR;
}
td.th = thermal_init(&ops);
if (!td.th) {
ERROR("Failed to initialize the thermal library\n");
return THERMAL_ENGINE_THERMAL_ERROR;
}
td.tz = thermal_zone_discover(td.th);
if (!td.tz) {
ERROR("No thermal zone available\n");
return THERMAL_ENGINE_THERMAL_ERROR;
}
for_each_thermal_zone(td.tz, show_tz, td.th);
if (mainloop_init()) {
ERROR("Failed to initialize the mainloop\n");
return THERMAL_ENGINE_MAINLOOP_ERROR;
}
if (mainloop_add(thermal_events_fd(td.th), thermal_event, &td)) {
ERROR("Failed to setup the mainloop\n");
return THERMAL_ENGINE_MAINLOOP_ERROR;
}
INFO("Waiting for thermal events ...\n");
if (mainloop(-1)) {
ERROR("Mainloop failed\n");
return THERMAL_ENGINE_MAINLOOP_ERROR;
}
return THERMAL_ENGINE_SUCCESS;
}
thermometer-y += thermometer.o
# 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)
.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 <dir>, --output <dir>\fP option defines the output directory to put the
sampling files
.PP
The \fB-c <config>, --config <config>\fP option specifies the configuration file to use
.PP
The \fB-d <seconds>, --duration <seconds>\fP option specifies the duration of the capture
.PP
The \fB-l <loglevel>, --loglevel <loglevel>\fP option sets the loglevel [DEBUG,INFO,NOTICE,WARN,ERROR]
.PP
The \fB-p <string>, --postfix <string>\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 <daniel.lezcano@kernel.org>
This diff is collapsed.
thermal-zones = (
{ name = "cpu[0-9]-thermal";
polling = 100; }
)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment