Thermal control updates for 6.13-rc1

- Add support for thermal thresholds that can be added and removed from
    user space via netlink along with a related library update (Daniel
    Lezcano).
 
  - Fix thermal zone initialization, suspend/resume and exit
    synchronization issues (Rafael Wysocki).
 
  - Rearrange locking in the thermal core to use guards (Rafael Wysocki).
 
  - Make the code handling thermal zone temperature updates use sorted
    lists of trip points to reduce the number of trip points table walks
    in the thermal core (Rafael Wysocki).
 
  - Fix and clean up the thermal testing facility code (Rafael Wysocki).
 
  - Fix a Power Allocator thermal governor issue (ZhengShaobo).
 -----BEGIN PGP SIGNATURE-----
 
 iQJGBAABCAAwFiEE4fcc61cGeeHD/fCwgsRv/nhiVHEFAmc3sPYSHHJqd0Byand5
 c29ja2kubmV0AAoJEILEb/54YlRxk7oQAKW9vjEPQvQvdkm86cI/mUnfMPMop6eF
 hKKwXUO5XHx+HqW1B8pQ9YPjAA8bcj0p9Ey1jn1cqXXOkjECU8V6tZZwIvSqLZdu
 xg2Jxtq/Hma269oxxQ6dmcUFHSLFWnPExpXfX9kbXjAZUOaoZ0I/Q8jEENQhZb50
 puewxt/T8zj6sZ0X9Skwd3nI2kVP4q95In+Ed/EPxnmJdrRTsyDg4Q38Et2KuxqO
 Trp++JMKEOJzOor65WelQylKVuIJM+88Gv4FMEPa8aX/mgCEZqn2RUguMLKWSmhq
 DUJUgWgxfF65vHM1yqbfwuYSgN7kSkYUgLuvwn7vSPS/NctwD5MPNL8sVJeieMOn
 IV6NZ1gWzjd0vK8ZQgUZi5NxrI5mMHK/9cgKesJ55jT9XwTNX922bF6dRUd4eT7j
 iKhQDajoyUZ/Oro4IN3OnwZ8dAPc9CREjaKFQ5duqiHFWa3I29zU2k/xgrIqGCNI
 Ko+uxY2PxxNvNQ2CR3iDck2pY24DvN2oALL9mLZekHaTKLLd7woo44D6p1pr0fbq
 E1y77uoFEAnbrW1v+9taSBYGyTRrNz0e4fPOwrqHcZ/O8wNBAQW+RKjzEOsk7Qj7
 rrh7XvBzM5aJGW1fcXTa2P581BhitD72i2/EXpxV3tH3JMZbqQxW3xGMG3GuW7QU
 RP0hnSpbiIWn
 =Pb3D
 -----END PGP SIGNATURE-----

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

Pull thermal control updates from Rafael Wysocki:
 "These are thermal core changes, including the addition of support for
  temperature thresholds that can be set from user space, fixes related
  to thermal zone initialization, suspend/resume and exit, locking
  rework and rearrangement of the code handling thermal zone temperature
  updates.

  Specifics:

   - Add support for thermal thresholds that can be added and removed
     from user space via netlink along with a related library update
     (Daniel Lezcano)

   - Fix thermal zone initialization, suspend/resume and exit
     synchronization issues (Rafael Wysocki)

   - Rearrange locking in the thermal core to use guards (Rafael
     Wysocki)

   - Make the code handling thermal zone temperature updates use sorted
     lists of trip points to reduce the number of trip points table
     walks in the thermal core (Rafael Wysocki)

   - Fix and clean up the thermal testing facility code (Rafael Wysocki)

   - Fix a Power Allocator thermal governor issue (ZhengShaobo)"

* tag 'thermal-6.13-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm: (45 commits)
  thermal: testing: Initialize some variables annoteded with _free()
  thermal: testing: Use DEFINE_FREE() and __free() to simplify code
  thermal: testing: Simplify tt_get_tt_zone()
  thermal: gov_power_allocator: Granted power set to max when nobody request power
  thermal: core: Relocate thermal zone initialization routine
  thermal: core: Use trip lists for trip crossing detection
  thermal: core: Eliminate thermal_zone_trip_down()
  thermal: core: Relocate functions that update trip points
  thermal: core: Move some trip processing to thermal_trip_crossed()
  thermal: core: Pass trip descriptor to thermal_trip_crossed()
  thermal: core: Rearrange __thermal_zone_device_update()
  thermal: core: Prepare for moving trips between sorted lists
  thermal: core: Rename trip list node in struct thermal_trip_desc
  thermal: core: Build sorted lists instead of sorting them later
  thermal/lib: Fix memory leak on error in thermal_genl_auto()
  thermal: thresholds: Fix thermal lock annotation issue
  tools/thermal/thermal-engine: Take into account the thresholds API
  tools/lib/thermal: Add the threshold netlink ABI
  tools/lib/thermal: Make more generic the command encoding function
  thermal: netlink: Add the commands and the events for the thresholds
  ...
This commit is contained in:
Linus Torvalds 2024-11-19 11:15:40 -08:00
commit cd7fa3e1b0
26 changed files with 1656 additions and 739 deletions

View File

@ -6,6 +6,7 @@ CFLAGS_thermal_core.o := -I$(src)
obj-$(CONFIG_THERMAL) += thermal_sys.o
thermal_sys-y += thermal_core.o thermal_sysfs.o
thermal_sys-y += thermal_trip.o thermal_helpers.o
thermal_sys-y += thermal_thresholds.o
# netlink interface to manage the thermal framework
thermal_sys-$(CONFIG_THERMAL_NETLINK) += thermal_netlink.o

View File

@ -30,9 +30,7 @@ static void bang_bang_set_instance_target(struct thermal_instance *instance,
dev_dbg(&instance->cdev->device, "target=%ld\n", instance->target);
mutex_lock(&instance->cdev->lock);
__thermal_cdev_update(instance->cdev);
mutex_unlock(&instance->cdev->lock);
thermal_cdev_update_nocheck(instance->cdev);
}
/**
@ -67,6 +65,7 @@ static void bang_bang_control(struct thermal_zone_device *tz,
const struct thermal_trip *trip,
bool crossed_up)
{
const struct thermal_trip_desc *td = trip_to_trip_desc(trip);
struct thermal_instance *instance;
lockdep_assert_held(&tz->lock);
@ -75,11 +74,9 @@ static void bang_bang_control(struct thermal_zone_device *tz,
thermal_zone_trip_id(tz, trip), trip->temperature,
tz->temperature, trip->hysteresis);
list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
if (instance->trip == trip)
list_for_each_entry(instance, &td->thermal_instances, trip_node)
bang_bang_set_instance_target(instance, crossed_up);
}
}
static void bang_bang_manage(struct thermal_zone_device *tz)
{
@ -104,8 +101,8 @@ static void bang_bang_manage(struct thermal_zone_device *tz)
* to the thermal zone temperature and the trip point threshold.
*/
turn_on = tz->temperature >= td->threshold;
list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
if (!instance->initialized && instance->trip == trip)
list_for_each_entry(instance, &td->thermal_instances, trip_node) {
if (!instance->initialized)
bang_bang_set_instance_target(instance, turn_on);
}
}

View File

@ -44,7 +44,7 @@ static int get_trip_level(struct thermal_zone_device *tz)
/**
* fair_share_throttle - throttles devices associated with the given zone
* @tz: thermal_zone_device
* @trip: trip point
* @td: trip point descriptor
* @trip_level: number of trips crossed by the zone temperature
*
* Throttling Logic: This uses three parameters to calculate the new
@ -61,29 +61,23 @@ static int get_trip_level(struct thermal_zone_device *tz)
* new_state of cooling device = P3 * P2 * P1
*/
static void fair_share_throttle(struct thermal_zone_device *tz,
const struct thermal_trip *trip,
const struct thermal_trip_desc *td,
int trip_level)
{
struct thermal_instance *instance;
int total_weight = 0;
int nr_instances = 0;
list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
if (instance->trip != trip)
continue;
list_for_each_entry(instance, &td->thermal_instances, trip_node) {
total_weight += instance->weight;
nr_instances++;
}
list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
list_for_each_entry(instance, &td->thermal_instances, trip_node) {
struct thermal_cooling_device *cdev = instance->cdev;
u64 dividend;
u32 divisor;
if (instance->trip != trip)
continue;
dividend = trip_level;
dividend *= cdev->max_state;
divisor = tz->num_trips;
@ -95,9 +89,7 @@ static void fair_share_throttle(struct thermal_zone_device *tz,
}
instance->target = div_u64(dividend, divisor);
mutex_lock(&cdev->lock);
__thermal_cdev_update(cdev);
mutex_unlock(&cdev->lock);
thermal_cdev_update_nocheck(cdev);
}
}
@ -116,7 +108,7 @@ static void fair_share_manage(struct thermal_zone_device *tz)
trip->type == THERMAL_TRIP_HOT)
continue;
fair_share_throttle(tz, trip, trip_level);
fair_share_throttle(tz, td, trip_level);
}
}

View File

@ -97,11 +97,9 @@ struct power_allocator_params {
struct power_actor *power;
};
static bool power_actor_is_valid(struct power_allocator_params *params,
struct thermal_instance *instance)
static bool power_actor_is_valid(struct thermal_instance *instance)
{
return (instance->trip == params->trip_max &&
cdev_is_power_actor(instance->cdev));
return cdev_is_power_actor(instance->cdev);
}
/**
@ -118,13 +116,14 @@ static bool power_actor_is_valid(struct power_allocator_params *params,
static u32 estimate_sustainable_power(struct thermal_zone_device *tz)
{
struct power_allocator_params *params = tz->governor_data;
const struct thermal_trip_desc *td = trip_to_trip_desc(params->trip_max);
struct thermal_cooling_device *cdev;
struct thermal_instance *instance;
u32 sustainable_power = 0;
u32 min_power;
list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
if (!power_actor_is_valid(params, instance))
list_for_each_entry(instance, &td->thermal_instances, trip_node) {
if (!power_actor_is_valid(instance))
continue;
cdev = instance->cdev;
@ -323,9 +322,8 @@ power_actor_set_power(struct thermal_cooling_device *cdev,
return ret;
instance->target = clamp_val(state, instance->lower, instance->upper);
mutex_lock(&cdev->lock);
__thermal_cdev_update(cdev);
mutex_unlock(&cdev->lock);
thermal_cdev_update_nocheck(cdev);
return 0;
}
@ -356,11 +354,19 @@ static void divvy_up_power(struct power_actor *power, int num_actors,
u32 extra_power = 0;
int i;
if (!total_req_power) {
/*
* Prevent division by 0 if none of the actors request power.
* Nobody requested anything, just give everybody
* the maximum power
*/
if (!total_req_power)
total_req_power = 1;
for (i = 0; i < num_actors; i++) {
struct power_actor *pa = &power[i];
pa->granted_power = pa->max_power;
}
return;
}
for (i = 0; i < num_actors; i++) {
struct power_actor *pa = &power[i];
@ -400,6 +406,7 @@ static void divvy_up_power(struct power_actor *power, int num_actors,
static void allocate_power(struct thermal_zone_device *tz, int control_temp)
{
struct power_allocator_params *params = tz->governor_data;
const struct thermal_trip_desc *td = trip_to_trip_desc(params->trip_max);
unsigned int num_actors = params->num_actors;
struct power_actor *power = params->power;
struct thermal_cooling_device *cdev;
@ -417,10 +424,10 @@ static void allocate_power(struct thermal_zone_device *tz, int control_temp)
/* Clean all buffers for new power estimations */
memset(power, 0, params->buffer_size);
list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
list_for_each_entry(instance, &td->thermal_instances, trip_node) {
struct power_actor *pa = &power[i];
if (!power_actor_is_valid(params, instance))
if (!power_actor_is_valid(instance))
continue;
cdev = instance->cdev;
@ -454,10 +461,10 @@ static void allocate_power(struct thermal_zone_device *tz, int control_temp)
power_range);
i = 0;
list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
list_for_each_entry(instance, &td->thermal_instances, trip_node) {
struct power_actor *pa = &power[i];
if (!power_actor_is_valid(params, instance))
if (!power_actor_is_valid(instance))
continue;
power_actor_set_power(instance->cdev, instance,
@ -538,29 +545,29 @@ static void reset_pid_controller(struct power_allocator_params *params)
static void allow_maximum_power(struct thermal_zone_device *tz)
{
struct power_allocator_params *params = tz->governor_data;
const struct thermal_trip_desc *td = trip_to_trip_desc(params->trip_max);
struct thermal_cooling_device *cdev;
struct thermal_instance *instance;
u32 req_power;
list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
if (!power_actor_is_valid(params, instance))
list_for_each_entry(instance, &td->thermal_instances, trip_node) {
if (!power_actor_is_valid(instance))
continue;
cdev = instance->cdev;
instance->target = 0;
mutex_lock(&cdev->lock);
scoped_guard(cooling_dev, cdev) {
/*
* Call for updating the cooling devices local stats and avoid
* periods of dozen of seconds when those have not been
* maintained.
* Call for updating the cooling devices local stats and
* avoid periods of dozen of seconds when those have not
* been maintained.
*/
cdev->ops->get_requested_power(cdev, &req_power);
if (params->update_cdevs)
__thermal_cdev_update(cdev);
mutex_unlock(&cdev->lock);
}
}
}
@ -581,13 +588,11 @@ static void allow_maximum_power(struct thermal_zone_device *tz)
static int check_power_actors(struct thermal_zone_device *tz,
struct power_allocator_params *params)
{
const struct thermal_trip_desc *td = trip_to_trip_desc(params->trip_max);
struct thermal_instance *instance;
int ret = 0;
list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
if (instance->trip != params->trip_max)
continue;
list_for_each_entry(instance, &td->thermal_instances, trip_node) {
if (!cdev_is_power_actor(instance->cdev)) {
dev_warn(&tz->device, "power_allocator: %s is not a power actor\n",
instance->cdev->type);
@ -635,14 +640,15 @@ static void power_allocator_update_tz(struct thermal_zone_device *tz,
enum thermal_notify_event reason)
{
struct power_allocator_params *params = tz->governor_data;
const struct thermal_trip_desc *td = trip_to_trip_desc(params->trip_max);
struct thermal_instance *instance;
int num_actors = 0;
switch (reason) {
case THERMAL_TZ_BIND_CDEV:
case THERMAL_TZ_UNBIND_CDEV:
list_for_each_entry(instance, &tz->thermal_instances, tz_node)
if (power_actor_is_valid(params, instance))
list_for_each_entry(instance, &td->thermal_instances, trip_node)
if (power_actor_is_valid(instance))
num_actors++;
if (num_actors == params->num_actors)
@ -652,8 +658,8 @@ static void power_allocator_update_tz(struct thermal_zone_device *tz,
break;
case THERMAL_INSTANCE_WEIGHT_CHANGED:
params->total_weight = 0;
list_for_each_entry(instance, &tz->thermal_instances, tz_node)
if (power_actor_is_valid(params, instance))
list_for_each_entry(instance, &td->thermal_instances, trip_node)
if (power_actor_is_valid(instance))
params->total_weight += instance->weight;
break;
default:

View File

@ -66,9 +66,10 @@ static unsigned long get_target_state(struct thermal_instance *instance,
}
static void thermal_zone_trip_update(struct thermal_zone_device *tz,
const struct thermal_trip *trip,
const struct thermal_trip_desc *td,
int trip_threshold)
{
const struct thermal_trip *trip = &td->trip;
enum thermal_trend trend = get_tz_trend(tz, trip);
int trip_id = thermal_zone_trip_id(tz, trip);
struct thermal_instance *instance;
@ -82,12 +83,9 @@ static void thermal_zone_trip_update(struct thermal_zone_device *tz,
dev_dbg(&tz->device, "Trip%d[type=%d,temp=%d]:trend=%d,throttle=%d\n",
trip_id, trip->type, trip_threshold, trend, throttle);
list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
list_for_each_entry(instance, &td->thermal_instances, trip_node) {
int old_target;
if (instance->trip != trip)
continue;
old_target = instance->target;
instance->target = get_target_state(instance, trend, throttle);
@ -99,9 +97,9 @@ static void thermal_zone_trip_update(struct thermal_zone_device *tz,
instance->initialized = true;
mutex_lock(&instance->cdev->lock);
scoped_guard(cooling_dev, instance->cdev) {
instance->cdev->updated = false; /* cdev needs update */
mutex_unlock(&instance->cdev->lock);
}
}
}
@ -127,12 +125,14 @@ static void step_wise_manage(struct thermal_zone_device *tz)
trip->type == THERMAL_TRIP_HOT)
continue;
thermal_zone_trip_update(tz, trip, td->threshold);
thermal_zone_trip_update(tz, td, td->threshold);
}
list_for_each_entry(instance, &tz->thermal_instances, tz_node)
for_each_trip_desc(tz, td) {
list_for_each_entry(instance, &td->thermal_instances, trip_node)
thermal_cdev_update(instance->cdev);
}
}
static struct thermal_governor thermal_gov_step_wise = {
.name = "step_wise",

View File

@ -185,7 +185,7 @@ static void tt_add_tz_work_fn(struct work_struct *work)
int tt_add_tz(void)
{
struct tt_thermal_zone *tt_zone __free(kfree);
struct tt_work *tt_work __free(kfree);
struct tt_work *tt_work __free(kfree) = NULL;
int ret;
tt_zone = kzalloc(sizeof(*tt_zone), GFP_KERNEL);
@ -237,7 +237,7 @@ static void tt_zone_unregister_tz(struct tt_thermal_zone *tt_zone)
int tt_del_tz(const char *arg)
{
struct tt_work *tt_work __free(kfree);
struct tt_work *tt_work __free(kfree) = NULL;
struct tt_thermal_zone *tt_zone, *aux;
int ret;
int id;
@ -288,20 +288,15 @@ static struct tt_thermal_zone *tt_get_tt_zone(const char *arg)
guard(mutex)(&tt_thermal_zones_lock);
ret = -EINVAL;
list_for_each_entry(tt_zone, &tt_thermal_zones, list_node) {
if (tt_zone->id == id) {
tt_zone->refcount++;
ret = 0;
break;
}
}
if (ret)
return ERR_PTR(ret);
return tt_zone;
}
}
return ERR_PTR(-EINVAL);
}
static void tt_put_tt_zone(struct tt_thermal_zone *tt_zone)
{
@ -310,6 +305,9 @@ static void tt_put_tt_zone(struct tt_thermal_zone *tt_zone)
tt_zone->refcount--;
}
DEFINE_FREE(put_tt_zone, struct tt_thermal_zone *,
if (!IS_ERR_OR_NULL(_T)) tt_put_tt_zone(_T))
static void tt_zone_add_trip_work_fn(struct work_struct *work)
{
struct tt_work *tt_work = tt_work_of_work(work);
@ -332,9 +330,9 @@ static void tt_zone_add_trip_work_fn(struct work_struct *work)
int tt_zone_add_trip(const char *arg)
{
struct tt_thermal_zone *tt_zone __free(put_tt_zone) = NULL;
struct tt_trip *tt_trip __free(kfree) = NULL;
struct tt_work *tt_work __free(kfree);
struct tt_trip *tt_trip __free(kfree);
struct tt_thermal_zone *tt_zone;
int id;
tt_work = kzalloc(sizeof(*tt_work), GFP_KERNEL);
@ -350,10 +348,8 @@ int tt_zone_add_trip(const char *arg)
return PTR_ERR(tt_zone);
id = ida_alloc(&tt_zone->ida, GFP_KERNEL);
if (id < 0) {
tt_put_tt_zone(tt_zone);
if (id < 0)
return id;
}
tt_trip->trip.type = THERMAL_TRIP_ACTIVE;
tt_trip->trip.temperature = THERMAL_TEMP_INVALID;
@ -366,7 +362,7 @@ int tt_zone_add_trip(const char *arg)
tt_zone->num_trips++;
INIT_WORK(&tt_work->work, tt_zone_add_trip_work_fn);
tt_work->tt_zone = tt_zone;
tt_work->tt_zone = no_free_ptr(tt_zone);
tt_work->tt_trip = no_free_ptr(tt_trip);
schedule_work(&(no_free_ptr(tt_work)->work));
@ -391,7 +387,7 @@ static struct thermal_zone_device_ops tt_zone_ops = {
static int tt_zone_register_tz(struct tt_thermal_zone *tt_zone)
{
struct thermal_trip *trips __free(kfree);
struct thermal_trip *trips __free(kfree) = NULL;
struct thermal_zone_device *tz;
struct tt_trip *tt_trip;
int i;
@ -425,23 +421,18 @@ static int tt_zone_register_tz(struct tt_thermal_zone *tt_zone)
int tt_zone_reg(const char *arg)
{
struct tt_thermal_zone *tt_zone;
int ret;
struct tt_thermal_zone *tt_zone __free(put_tt_zone);
tt_zone = tt_get_tt_zone(arg);
if (IS_ERR(tt_zone))
return PTR_ERR(tt_zone);
ret = tt_zone_register_tz(tt_zone);
tt_put_tt_zone(tt_zone);
return ret;
return tt_zone_register_tz(tt_zone);
}
int tt_zone_unreg(const char *arg)
{
struct tt_thermal_zone *tt_zone;
struct tt_thermal_zone *tt_zone __free(put_tt_zone);
tt_zone = tt_get_tt_zone(arg);
if (IS_ERR(tt_zone))
@ -449,8 +440,6 @@ int tt_zone_unreg(const char *arg)
tt_zone_unregister_tz(tt_zone);
tt_put_tt_zone(tt_zone);
return 0;
}

File diff suppressed because it is too large Load Diff

View File

@ -9,10 +9,12 @@
#ifndef __THERMAL_CORE_H__
#define __THERMAL_CORE_H__
#include <linux/cleanup.h>
#include <linux/device.h>
#include <linux/thermal.h>
#include "thermal_netlink.h"
#include "thermal_thresholds.h"
#include "thermal_debugfs.h"
struct thermal_attr {
@ -29,8 +31,8 @@ struct thermal_trip_attrs {
struct thermal_trip_desc {
struct thermal_trip trip;
struct thermal_trip_attrs trip_attrs;
struct list_head notify_list_node;
int notify_temp;
struct list_head list_node;
struct list_head thermal_instances;
int threshold;
};
@ -61,6 +63,13 @@ struct thermal_governor {
struct list_head governor_list;
};
#define TZ_STATE_FLAG_SUSPENDED BIT(0)
#define TZ_STATE_FLAG_RESUMING BIT(1)
#define TZ_STATE_FLAG_INIT BIT(2)
#define TZ_STATE_FLAG_EXIT BIT(3)
#define TZ_STATE_READY 0
/**
* struct thermal_zone_device - structure for a thermal zone
* @id: unique id number for each thermal zone
@ -68,6 +77,9 @@ struct thermal_governor {
* @device: &struct device for this thermal zone
* @removal: removal completion
* @resume: resume completion
* @trips_high: trips above the current zone temperature
* @trips_reached: trips below or at the current zone temperature
* @trips_invalid: trips with invalid temperature
* @mode: current mode of this thermal zone
* @devdata: private pointer for device private data
* @num_trips: number of trip points the thermal zone supports
@ -88,20 +100,17 @@ struct thermal_governor {
trip point.
* @prev_high_trip: the above current temperature if you've crossed a
passive trip point.
* @need_update: if equals 1, thermal_zone_device_update needs to be invoked.
* @ops: operations this &thermal_zone_device supports
* @tzp: thermal zone parameters
* @governor: pointer to the governor for this thermal zone
* @governor_data: private pointer for governor data
* @thermal_instances: list of &struct thermal_instance of this thermal zone
* @ida: &struct ida to generate unique id for this zone's cooling
* devices
* @lock: lock to protect thermal_instances list
* @node: node in thermal_tz_list (in thermal_core.c)
* @poll_queue: delayed work for polling
* @notify_event: Last notification event
* @suspended: thermal zone suspend indicator
* @resuming: indicates whether or not thermal zone resume is in progress
* @state: current state of the thermal zone
* @trips: array of struct thermal_trip objects
*/
struct thermal_zone_device {
@ -111,6 +120,9 @@ struct thermal_zone_device {
struct completion removal;
struct completion resume;
struct attribute_group trips_attribute_group;
struct list_head trips_high;
struct list_head trips_reached;
struct list_head trips_invalid;
enum thermal_device_mode mode;
void *devdata;
int num_trips;
@ -123,25 +135,29 @@ struct thermal_zone_device {
int passive;
int prev_low_trip;
int prev_high_trip;
atomic_t need_update;
struct thermal_zone_device_ops ops;
struct thermal_zone_params *tzp;
struct thermal_governor *governor;
void *governor_data;
struct list_head thermal_instances;
struct ida ida;
struct mutex lock;
struct list_head node;
struct delayed_work poll_queue;
enum thermal_notify_event notify_event;
bool suspended;
bool resuming;
u8 state;
#ifdef CONFIG_THERMAL_DEBUGFS
struct thermal_debugfs *debugfs;
#endif
struct list_head user_thresholds;
struct thermal_trip_desc trips[] __counted_by(num_trips);
};
DEFINE_GUARD(thermal_zone, struct thermal_zone_device *, mutex_lock(&_T->lock),
mutex_unlock(&_T->lock))
DEFINE_GUARD(thermal_zone_reverse, struct thermal_zone_device *,
mutex_unlock(&_T->lock), mutex_lock(&_T->lock))
/* Initial thermal zone temperature. */
#define THERMAL_TEMP_INIT INT_MIN
@ -204,6 +220,7 @@ static inline bool cdev_is_power_actor(struct thermal_cooling_device *cdev)
}
void thermal_cdev_update(struct thermal_cooling_device *);
void thermal_cdev_update_nocheck(struct thermal_cooling_device *cdev);
void __thermal_cdev_update(struct thermal_cooling_device *cdev);
int get_tz_trend(struct thermal_zone_device *tz, const struct thermal_trip *trip);
@ -226,7 +243,7 @@ struct thermal_instance {
struct device_attribute attr;
char weight_attr_name[THERMAL_NAME_LENGTH];
struct device_attribute weight_attr;
struct list_head tz_node; /* node in tz->thermal_instances */
struct list_head trip_node; /* node in trip->thermal_instances */
struct list_head cdev_node; /* node in cdev->thermal_instances */
unsigned int weight; /* The weight of the cooling device */
bool upper_no_limit;
@ -261,8 +278,6 @@ void thermal_zone_set_trips(struct thermal_zone_device *tz, int low, int high);
int thermal_zone_trip_id(const struct thermal_zone_device *tz,
const struct thermal_trip *trip);
int __thermal_zone_get_temp(struct thermal_zone_device *tz, int *temp);
void thermal_zone_trip_down(struct thermal_zone_device *tz,
const struct thermal_trip *trip);
void thermal_zone_set_trip_hyst(struct thermal_zone_device *tz,
struct thermal_trip *trip, int hyst);

View File

@ -516,6 +516,19 @@ void thermal_debug_cdev_add(struct thermal_cooling_device *cdev, int state)
cdev->debugfs = thermal_dbg;
}
static struct thermal_debugfs *thermal_debug_cdev_clear(struct thermal_cooling_device *cdev)
{
struct thermal_debugfs *thermal_dbg;
guard(cooling_dev)(cdev);
thermal_dbg = cdev->debugfs;
if (thermal_dbg)
cdev->debugfs = NULL;
return thermal_dbg;
}
/**
* thermal_debug_cdev_remove - Remove a cooling device debugfs entry
*
@ -527,17 +540,9 @@ void thermal_debug_cdev_remove(struct thermal_cooling_device *cdev)
{
struct thermal_debugfs *thermal_dbg;
mutex_lock(&cdev->lock);
thermal_dbg = cdev->debugfs;
if (!thermal_dbg) {
mutex_unlock(&cdev->lock);
thermal_dbg = thermal_debug_cdev_clear(cdev);
if (!thermal_dbg)
return;
}
cdev->debugfs = NULL;
mutex_unlock(&cdev->lock);
mutex_lock(&thermal_dbg->lock);
@ -885,6 +890,19 @@ void thermal_debug_tz_add(struct thermal_zone_device *tz)
tz->debugfs = thermal_dbg;
}
static struct thermal_debugfs *thermal_debug_tz_clear(struct thermal_zone_device *tz)
{
struct thermal_debugfs *thermal_dbg;
guard(thermal_zone)(tz);
thermal_dbg = tz->debugfs;
if (thermal_dbg)
tz->debugfs = NULL;
return thermal_dbg;
}
void thermal_debug_tz_remove(struct thermal_zone_device *tz)
{
struct thermal_debugfs *thermal_dbg;
@ -892,17 +910,9 @@ void thermal_debug_tz_remove(struct thermal_zone_device *tz)
struct tz_debugfs *tz_dbg;
int *trips_crossed;
mutex_lock(&tz->lock);
thermal_dbg = tz->debugfs;
if (!thermal_dbg) {
mutex_unlock(&tz->lock);
thermal_dbg = thermal_debug_tz_clear(tz);
if (!thermal_dbg)
return;
}
tz->debugfs = NULL;
mutex_unlock(&tz->lock);
tz_dbg = &thermal_dbg->tz_dbg;

View File

@ -43,10 +43,11 @@ static bool thermal_instance_present(struct thermal_zone_device *tz,
struct thermal_cooling_device *cdev,
const struct thermal_trip *trip)
{
const struct thermal_trip_desc *td = trip_to_trip_desc(trip);
struct thermal_instance *ti;
list_for_each_entry(ti, &tz->thermal_instances, tz_node) {
if (ti->trip == trip && ti->cdev == cdev)
list_for_each_entry(ti, &td->thermal_instances, trip_node) {
if (ti->cdev == cdev)
return true;
}
@ -57,17 +58,10 @@ bool thermal_trip_is_bound_to_cdev(struct thermal_zone_device *tz,
const struct thermal_trip *trip,
struct thermal_cooling_device *cdev)
{
bool ret;
guard(thermal_zone)(tz);
guard(cooling_dev)(cdev);
mutex_lock(&tz->lock);
mutex_lock(&cdev->lock);
ret = thermal_instance_present(tz, cdev, trip);
mutex_unlock(&cdev->lock);
mutex_unlock(&tz->lock);
return ret;
return thermal_instance_present(tz, cdev, trip);
}
EXPORT_SYMBOL_GPL(thermal_trip_is_bound_to_cdev);
@ -137,19 +131,14 @@ int thermal_zone_get_temp(struct thermal_zone_device *tz, int *temp)
if (IS_ERR_OR_NULL(tz))
return -EINVAL;
mutex_lock(&tz->lock);
guard(thermal_zone)(tz);
if (!tz->ops.get_temp) {
ret = -EINVAL;
goto unlock;
}
if (!tz->ops.get_temp)
return -EINVAL;
ret = __thermal_zone_get_temp(tz, temp);
if (!ret && *temp <= THERMAL_TEMP_INVALID)
ret = -ENODATA;
unlock:
mutex_unlock(&tz->lock);
return -ENODATA;
return ret;
}
@ -201,12 +190,23 @@ void __thermal_cdev_update(struct thermal_cooling_device *cdev)
*/
void thermal_cdev_update(struct thermal_cooling_device *cdev)
{
mutex_lock(&cdev->lock);
guard(cooling_dev)(cdev);
if (!cdev->updated) {
__thermal_cdev_update(cdev);
cdev->updated = true;
}
mutex_unlock(&cdev->lock);
}
/**
* thermal_cdev_update_nocheck() - Unconditionally update cooling device state
* @cdev: Target cooling device.
*/
void thermal_cdev_update_nocheck(struct thermal_cooling_device *cdev)
{
guard(cooling_dev)(cdev);
__thermal_cdev_update(cdev);
}
/**

View File

@ -78,12 +78,9 @@ temp_crit_show(struct device *dev, struct device_attribute *attr, char *buf)
int temperature;
int ret;
mutex_lock(&tz->lock);
guard(thermal_zone)(tz);
ret = tz->ops.get_crit_temp(tz, &temperature);
mutex_unlock(&tz->lock);
if (ret)
return ret;

View File

@ -9,6 +9,7 @@
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/kernel.h>
#include <net/sock.h>
#include <net/genetlink.h>
#include <uapi/linux/thermal.h>
@ -49,6 +50,11 @@ static const struct nla_policy thermal_genl_policy[THERMAL_GENL_ATTR_MAX + 1] =
[THERMAL_GENL_ATTR_CPU_CAPABILITY_ID] = { .type = NLA_U32 },
[THERMAL_GENL_ATTR_CPU_CAPABILITY_PERFORMANCE] = { .type = NLA_U32 },
[THERMAL_GENL_ATTR_CPU_CAPABILITY_EFFICIENCY] = { .type = NLA_U32 },
/* Thresholds */
[THERMAL_GENL_ATTR_THRESHOLD] = { .type = NLA_NESTED },
[THERMAL_GENL_ATTR_THRESHOLD_TEMP] = { .type = NLA_U32 },
[THERMAL_GENL_ATTR_THRESHOLD_DIRECTION] = { .type = NLA_U32 },
};
struct param {
@ -62,6 +68,8 @@ struct param {
int trip_type;
int trip_hyst;
int temp;
int prev_temp;
int direction;
int cdev_state;
int cdev_max_state;
struct thermal_genl_cpu_caps *cpu_capabilities;
@ -234,6 +242,34 @@ out_cancel_nest:
return -EMSGSIZE;
}
static int thermal_genl_event_threshold_add(struct param *p)
{
if (nla_put_u32(p->msg, THERMAL_GENL_ATTR_TZ_ID, p->tz_id) ||
nla_put_u32(p->msg, THERMAL_GENL_ATTR_THRESHOLD_TEMP, p->temp) ||
nla_put_u32(p->msg, THERMAL_GENL_ATTR_THRESHOLD_DIRECTION, p->direction))
return -EMSGSIZE;
return 0;
}
static int thermal_genl_event_threshold_flush(struct param *p)
{
if (nla_put_u32(p->msg, THERMAL_GENL_ATTR_TZ_ID, p->tz_id))
return -EMSGSIZE;
return 0;
}
static int thermal_genl_event_threshold_up(struct param *p)
{
if (nla_put_u32(p->msg, THERMAL_GENL_ATTR_TZ_ID, p->tz_id) ||
nla_put_u32(p->msg, THERMAL_GENL_ATTR_TZ_PREV_TEMP, p->prev_temp) ||
nla_put_u32(p->msg, THERMAL_GENL_ATTR_TZ_TEMP, p->temp))
return -EMSGSIZE;
return 0;
}
int thermal_genl_event_tz_delete(struct param *p)
__attribute__((alias("thermal_genl_event_tz")));
@ -246,6 +282,12 @@ int thermal_genl_event_tz_disable(struct param *p)
int thermal_genl_event_tz_trip_down(struct param *p)
__attribute__((alias("thermal_genl_event_tz_trip_up")));
int thermal_genl_event_threshold_delete(struct param *p)
__attribute__((alias("thermal_genl_event_threshold_add")));
int thermal_genl_event_threshold_down(struct param *p)
__attribute__((alias("thermal_genl_event_threshold_up")));
static cb_t event_cb[] = {
[THERMAL_GENL_EVENT_TZ_CREATE] = thermal_genl_event_tz_create,
[THERMAL_GENL_EVENT_TZ_DELETE] = thermal_genl_event_tz_delete,
@ -259,6 +301,11 @@ static cb_t event_cb[] = {
[THERMAL_GENL_EVENT_CDEV_STATE_UPDATE] = thermal_genl_event_cdev_state_update,
[THERMAL_GENL_EVENT_TZ_GOV_CHANGE] = thermal_genl_event_gov_change,
[THERMAL_GENL_EVENT_CPU_CAPABILITY_CHANGE] = thermal_genl_event_cpu_capability_change,
[THERMAL_GENL_EVENT_THRESHOLD_ADD] = thermal_genl_event_threshold_add,
[THERMAL_GENL_EVENT_THRESHOLD_DELETE] = thermal_genl_event_threshold_delete,
[THERMAL_GENL_EVENT_THRESHOLD_FLUSH] = thermal_genl_event_threshold_flush,
[THERMAL_GENL_EVENT_THRESHOLD_DOWN] = thermal_genl_event_threshold_down,
[THERMAL_GENL_EVENT_THRESHOLD_UP] = thermal_genl_event_threshold_up,
};
/*
@ -401,6 +448,43 @@ int thermal_genl_cpu_capability_event(int count,
}
EXPORT_SYMBOL_GPL(thermal_genl_cpu_capability_event);
int thermal_notify_threshold_add(const struct thermal_zone_device *tz,
int temperature, int direction)
{
struct param p = { .tz_id = tz->id, .temp = temperature, .direction = direction };
return thermal_genl_send_event(THERMAL_GENL_EVENT_THRESHOLD_ADD, &p);
}
int thermal_notify_threshold_delete(const struct thermal_zone_device *tz,
int temperature, int direction)
{
struct param p = { .tz_id = tz->id, .temp = temperature, .direction = direction };
return thermal_genl_send_event(THERMAL_GENL_EVENT_THRESHOLD_DELETE, &p);
}
int thermal_notify_threshold_flush(const struct thermal_zone_device *tz)
{
struct param p = { .tz_id = tz->id };
return thermal_genl_send_event(THERMAL_GENL_EVENT_THRESHOLD_FLUSH, &p);
}
int thermal_notify_threshold_down(const struct thermal_zone_device *tz)
{
struct param p = { .tz_id = tz->id, .temp = tz->temperature, .prev_temp = tz->last_temperature };
return thermal_genl_send_event(THERMAL_GENL_EVENT_THRESHOLD_DOWN, &p);
}
int thermal_notify_threshold_up(const struct thermal_zone_device *tz)
{
struct param p = { .tz_id = tz->id, .temp = tz->temperature, .prev_temp = tz->last_temperature };
return thermal_genl_send_event(THERMAL_GENL_EVENT_THRESHOLD_UP, &p);
}
/*************************** Command encoding ********************************/
static int __thermal_genl_cmd_tz_get_id(struct thermal_zone_device *tz,
@ -459,7 +543,7 @@ static int thermal_genl_cmd_tz_get_trip(struct param *p)
if (!start_trip)
return -EMSGSIZE;
mutex_lock(&tz->lock);
guard(thermal_zone)(tz);
for_each_trip_desc(tz, td) {
const struct thermal_trip *trip = &td->trip;
@ -469,19 +553,12 @@ static int thermal_genl_cmd_tz_get_trip(struct param *p)
nla_put_u32(msg, THERMAL_GENL_ATTR_TZ_TRIP_TYPE, trip->type) ||
nla_put_u32(msg, THERMAL_GENL_ATTR_TZ_TRIP_TEMP, trip->temperature) ||
nla_put_u32(msg, THERMAL_GENL_ATTR_TZ_TRIP_HYST, trip->hysteresis))
goto out_cancel_nest;
return -EMSGSIZE;
}
mutex_unlock(&tz->lock);
nla_nest_end(msg, start_trip);
return 0;
out_cancel_nest:
mutex_unlock(&tz->lock);
return -EMSGSIZE;
}
static int thermal_genl_cmd_tz_get_temp(struct param *p)
@ -512,7 +589,7 @@ static int thermal_genl_cmd_tz_get_temp(struct param *p)
static int thermal_genl_cmd_tz_get_gov(struct param *p)
{
struct sk_buff *msg = p->msg;
int id, ret = 0;
int id;
if (!p->attrs[THERMAL_GENL_ATTR_TZ_ID])
return -EINVAL;
@ -523,16 +600,14 @@ static int thermal_genl_cmd_tz_get_gov(struct param *p)
if (!tz)
return -EINVAL;
mutex_lock(&tz->lock);
guard(thermal_zone)(tz);
if (nla_put_u32(msg, THERMAL_GENL_ATTR_TZ_ID, id) ||
nla_put_string(msg, THERMAL_GENL_ATTR_TZ_GOV_NAME,
tz->governor->name))
ret = -EMSGSIZE;
return -EMSGSIZE;
mutex_unlock(&tz->lock);
return ret;
return 0;
}
static int __thermal_genl_cmd_cdev_get(struct thermal_cooling_device *cdev,
@ -572,12 +647,128 @@ out_cancel_nest:
return ret;
}
static int __thermal_genl_cmd_threshold_get(struct user_threshold *threshold, void *arg)
{
struct sk_buff *msg = arg;
if (nla_put_u32(msg, THERMAL_GENL_ATTR_THRESHOLD_TEMP, threshold->temperature) ||
nla_put_u32(msg, THERMAL_GENL_ATTR_THRESHOLD_DIRECTION, threshold->direction))
return -1;
return 0;
}
static int thermal_genl_cmd_threshold_get(struct param *p)
{
struct sk_buff *msg = p->msg;
struct nlattr *start_trip;
int id, ret;
if (!p->attrs[THERMAL_GENL_ATTR_TZ_ID])
return -EINVAL;
id = nla_get_u32(p->attrs[THERMAL_GENL_ATTR_TZ_ID]);
CLASS(thermal_zone_get_by_id, tz)(id);
if (!tz)
return -EINVAL;
start_trip = nla_nest_start(msg, THERMAL_GENL_ATTR_THRESHOLD);
if (!start_trip)
return -EMSGSIZE;
ret = thermal_thresholds_for_each(tz, __thermal_genl_cmd_threshold_get, msg);
if (ret)
return -EMSGSIZE;
nla_nest_end(msg, start_trip);
return 0;
}
static int thermal_genl_cmd_threshold_add(struct param *p)
{
int id, temp, direction;
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
if (!p->attrs[THERMAL_GENL_ATTR_TZ_ID] ||
!p->attrs[THERMAL_GENL_ATTR_THRESHOLD_TEMP] ||
!p->attrs[THERMAL_GENL_ATTR_THRESHOLD_DIRECTION])
return -EINVAL;
id = nla_get_u32(p->attrs[THERMAL_GENL_ATTR_TZ_ID]);
temp = nla_get_u32(p->attrs[THERMAL_GENL_ATTR_THRESHOLD_TEMP]);
direction = nla_get_u32(p->attrs[THERMAL_GENL_ATTR_THRESHOLD_DIRECTION]);
CLASS(thermal_zone_get_by_id, tz)(id);
if (!tz)
return -EINVAL;
guard(thermal_zone)(tz);
return thermal_thresholds_add(tz, temp, direction);
}
static int thermal_genl_cmd_threshold_delete(struct param *p)
{
int id, temp, direction;
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
if (!p->attrs[THERMAL_GENL_ATTR_TZ_ID] ||
!p->attrs[THERMAL_GENL_ATTR_THRESHOLD_TEMP] ||
!p->attrs[THERMAL_GENL_ATTR_THRESHOLD_DIRECTION])
return -EINVAL;
id = nla_get_u32(p->attrs[THERMAL_GENL_ATTR_TZ_ID]);
temp = nla_get_u32(p->attrs[THERMAL_GENL_ATTR_THRESHOLD_TEMP]);
direction = nla_get_u32(p->attrs[THERMAL_GENL_ATTR_THRESHOLD_DIRECTION]);
CLASS(thermal_zone_get_by_id, tz)(id);
if (!tz)
return -EINVAL;
guard(thermal_zone)(tz);
return thermal_thresholds_delete(tz, temp, direction);
}
static int thermal_genl_cmd_threshold_flush(struct param *p)
{
int id;
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
if (!p->attrs[THERMAL_GENL_ATTR_TZ_ID])
return -EINVAL;
id = nla_get_u32(p->attrs[THERMAL_GENL_ATTR_TZ_ID]);
CLASS(thermal_zone_get_by_id, tz)(id);
if (!tz)
return -EINVAL;
guard(thermal_zone)(tz);
thermal_thresholds_flush(tz);
return 0;
}
static cb_t cmd_cb[] = {
[THERMAL_GENL_CMD_TZ_GET_ID] = thermal_genl_cmd_tz_get_id,
[THERMAL_GENL_CMD_TZ_GET_TRIP] = thermal_genl_cmd_tz_get_trip,
[THERMAL_GENL_CMD_TZ_GET_TEMP] = thermal_genl_cmd_tz_get_temp,
[THERMAL_GENL_CMD_TZ_GET_GOV] = thermal_genl_cmd_tz_get_gov,
[THERMAL_GENL_CMD_CDEV_GET] = thermal_genl_cmd_cdev_get,
[THERMAL_GENL_CMD_THRESHOLD_GET] = thermal_genl_cmd_threshold_get,
[THERMAL_GENL_CMD_THRESHOLD_ADD] = thermal_genl_cmd_threshold_add,
[THERMAL_GENL_CMD_THRESHOLD_DELETE] = thermal_genl_cmd_threshold_delete,
[THERMAL_GENL_CMD_THRESHOLD_FLUSH] = thermal_genl_cmd_threshold_flush,
};
static int thermal_genl_cmd_dumpit(struct sk_buff *skb,
@ -688,6 +879,26 @@ static const struct genl_small_ops thermal_genl_ops[] = {
.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
.dumpit = thermal_genl_cmd_dumpit,
},
{
.cmd = THERMAL_GENL_CMD_THRESHOLD_GET,
.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
.doit = thermal_genl_cmd_doit,
},
{
.cmd = THERMAL_GENL_CMD_THRESHOLD_ADD,
.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
.doit = thermal_genl_cmd_doit,
},
{
.cmd = THERMAL_GENL_CMD_THRESHOLD_DELETE,
.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
.doit = thermal_genl_cmd_doit,
},
{
.cmd = THERMAL_GENL_CMD_THRESHOLD_FLUSH,
.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
.doit = thermal_genl_cmd_doit,
},
};
static struct genl_family thermal_genl_family __ro_after_init = {
@ -700,7 +911,7 @@ static struct genl_family thermal_genl_family __ro_after_init = {
.unbind = thermal_genl_unbind,
.small_ops = thermal_genl_ops,
.n_small_ops = ARRAY_SIZE(thermal_genl_ops),
.resv_start_op = THERMAL_GENL_CMD_CDEV_GET + 1,
.resv_start_op = __THERMAL_GENL_CMD_MAX,
.mcgrps = thermal_genl_mcgrps,
.n_mcgrps = ARRAY_SIZE(thermal_genl_mcgrps),
};

View File

@ -53,6 +53,13 @@ int thermal_notify_tz_gov_change(const struct thermal_zone_device *tz,
int thermal_genl_sampling_temp(int id, int temp);
int thermal_genl_cpu_capability_event(int count,
struct thermal_genl_cpu_caps *caps);
int thermal_notify_threshold_add(const struct thermal_zone_device *tz,
int temperature, int direction);
int thermal_notify_threshold_delete(const struct thermal_zone_device *tz,
int temperature, int direction);
int thermal_notify_threshold_flush(const struct thermal_zone_device *tz);
int thermal_notify_threshold_down(const struct thermal_zone_device *tz);
int thermal_notify_threshold_up(const struct thermal_zone_device *tz);
#else
static inline int thermal_netlink_init(void)
{
@ -139,6 +146,33 @@ static inline int thermal_genl_cpu_capability_event(int count, struct thermal_ge
return 0;
}
static inline int thermal_notify_threshold_add(const struct thermal_zone_device *tz,
int temperature, int direction)
{
return 0;
}
static inline int thermal_notify_threshold_delete(const struct thermal_zone_device *tz,
int temperature, int direction)
{
return 0;
}
static inline int thermal_notify_threshold_flush(const struct thermal_zone_device *tz)
{
return 0;
}
static inline int thermal_notify_threshold_down(const struct thermal_zone_device *tz)
{
return 0;
}
static inline int thermal_notify_threshold_up(const struct thermal_zone_device *tz)
{
return 0;
}
static inline void __init thermal_netlink_exit(void) {}
#endif /* CONFIG_THERMAL_NETLINK */

View File

@ -50,13 +50,13 @@ static ssize_t
mode_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct thermal_zone_device *tz = to_thermal_zone(dev);
int enabled;
mutex_lock(&tz->lock);
enabled = tz->mode == THERMAL_DEVICE_ENABLED;
mutex_unlock(&tz->lock);
guard(thermal_zone)(tz);
return sprintf(buf, "%s\n", enabled ? "enabled" : "disabled");
if (tz->mode == THERMAL_DEVICE_ENABLED)
return sprintf(buf, "enabled\n");
return sprintf(buf, "disabled\n");
}
static ssize_t
@ -103,38 +103,34 @@ trip_point_temp_store(struct device *dev, struct device_attribute *attr,
{
struct thermal_trip *trip = thermal_trip_of_attr(attr, temp);
struct thermal_zone_device *tz = to_thermal_zone(dev);
int ret, temp;
int temp;
ret = kstrtoint(buf, 10, &temp);
if (ret)
if (kstrtoint(buf, 10, &temp))
return -EINVAL;
mutex_lock(&tz->lock);
guard(thermal_zone)(tz);
if (temp == trip->temperature)
goto unlock;
return count;
/* Arrange the condition to avoid integer overflows. */
if (temp != THERMAL_TEMP_INVALID &&
temp <= trip->hysteresis + THERMAL_TEMP_INVALID) {
ret = -EINVAL;
goto unlock;
}
temp <= trip->hysteresis + THERMAL_TEMP_INVALID)
return -EINVAL;
if (tz->ops.set_trip_temp) {
int ret;
ret = tz->ops.set_trip_temp(tz, trip, temp);
if (ret)
goto unlock;
return ret;
}
thermal_zone_set_trip_temp(tz, trip, temp);
__thermal_zone_device_update(tz, THERMAL_TRIP_CHANGED);
unlock:
mutex_unlock(&tz->lock);
return ret ? ret : count;
return count;
}
static ssize_t
@ -152,16 +148,15 @@ trip_point_hyst_store(struct device *dev, struct device_attribute *attr,
{
struct thermal_trip *trip = thermal_trip_of_attr(attr, hyst);
struct thermal_zone_device *tz = to_thermal_zone(dev);
int ret, hyst;
int hyst;
ret = kstrtoint(buf, 10, &hyst);
if (ret || hyst < 0)
if (kstrtoint(buf, 10, &hyst) || hyst < 0)
return -EINVAL;
mutex_lock(&tz->lock);
guard(thermal_zone)(tz);
if (hyst == trip->hysteresis)
goto unlock;
return count;
/*
* Allow the hysteresis to be updated when the temperature is invalid
@ -171,22 +166,17 @@ trip_point_hyst_store(struct device *dev, struct device_attribute *attr,
*/
if (trip->temperature == THERMAL_TEMP_INVALID) {
WRITE_ONCE(trip->hysteresis, hyst);
goto unlock;
return count;
}
if (trip->temperature - hyst <= THERMAL_TEMP_INVALID) {
ret = -EINVAL;
goto unlock;
}
if (trip->temperature - hyst <= THERMAL_TEMP_INVALID)
return -EINVAL;
thermal_zone_set_trip_hyst(tz, trip, hyst);
__thermal_zone_device_update(tz, THERMAL_TRIP_CHANGED);
unlock:
mutex_unlock(&tz->lock);
return ret ? ret : count;
return count;
}
static ssize_t
@ -236,25 +226,26 @@ emul_temp_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct thermal_zone_device *tz = to_thermal_zone(dev);
int ret = 0;
int temperature;
if (kstrtoint(buf, 10, &temperature))
return -EINVAL;
mutex_lock(&tz->lock);
guard(thermal_zone)(tz);
if (tz->ops.set_emul_temp) {
int ret;
if (!tz->ops.set_emul_temp)
tz->emul_temperature = temperature;
else
ret = tz->ops.set_emul_temp(tz, temperature);
if (ret)
return ret;
} else {
tz->emul_temperature = temperature;
}
if (!ret)
__thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
mutex_unlock(&tz->lock);
return ret ? ret : count;
return count;
}
static DEVICE_ATTR_WO(emul_temp);
#endif
@ -553,14 +544,15 @@ cur_state_store(struct device *dev, struct device_attribute *attr,
if (state > cdev->max_state)
return -EINVAL;
mutex_lock(&cdev->lock);
guard(cooling_dev)(cdev);
result = cdev->ops->set_cur_state(cdev, state);
if (!result)
if (result)
return result;
thermal_cooling_device_stats_update(cdev, state);
mutex_unlock(&cdev->lock);
return result ? result : count;
return count;
}
static struct device_attribute
@ -634,21 +626,18 @@ static ssize_t total_trans_show(struct device *dev,
{
struct thermal_cooling_device *cdev = to_cooling_device(dev);
struct cooling_dev_stats *stats;
int ret = 0;
int ret;
mutex_lock(&cdev->lock);
guard(cooling_dev)(cdev);
stats = cdev->stats;
if (!stats)
goto unlock;
return 0;
spin_lock(&stats->lock);
ret = sprintf(buf, "%u\n", stats->total_trans);
spin_unlock(&stats->lock);
unlock:
mutex_unlock(&cdev->lock);
return ret;
}
@ -661,11 +650,11 @@ time_in_state_ms_show(struct device *dev, struct device_attribute *attr,
ssize_t len = 0;
int i;
mutex_lock(&cdev->lock);
guard(cooling_dev)(cdev);
stats = cdev->stats;
if (!stats)
goto unlock;
return 0;
spin_lock(&stats->lock);
@ -677,9 +666,6 @@ time_in_state_ms_show(struct device *dev, struct device_attribute *attr,
}
spin_unlock(&stats->lock);
unlock:
mutex_unlock(&cdev->lock);
return len;
}
@ -691,11 +677,11 @@ reset_store(struct device *dev, struct device_attribute *attr, const char *buf,
struct cooling_dev_stats *stats;
int i, states;
mutex_lock(&cdev->lock);
guard(cooling_dev)(cdev);
stats = cdev->stats;
if (!stats)
goto unlock;
return count;
states = cdev->max_state + 1;
@ -711,9 +697,6 @@ reset_store(struct device *dev, struct device_attribute *attr, const char *buf,
spin_unlock(&stats->lock);
unlock:
mutex_unlock(&cdev->lock);
return count;
}
@ -725,13 +708,11 @@ static ssize_t trans_table_show(struct device *dev,
ssize_t len = 0;
int i, j;
mutex_lock(&cdev->lock);
guard(cooling_dev)(cdev);
stats = cdev->stats;
if (!stats) {
len = -ENODATA;
goto unlock;
}
if (!stats)
return -ENODATA;
len += snprintf(buf + len, PAGE_SIZE - len, " From : To\n");
len += snprintf(buf + len, PAGE_SIZE - len, " : ");
@ -740,10 +721,8 @@ static ssize_t trans_table_show(struct device *dev,
break;
len += snprintf(buf + len, PAGE_SIZE - len, "state%2u ", i);
}
if (len >= PAGE_SIZE) {
len = PAGE_SIZE;
goto unlock;
}
if (len >= PAGE_SIZE)
return PAGE_SIZE;
len += snprintf(buf + len, PAGE_SIZE - len, "\n");
@ -769,9 +748,6 @@ static ssize_t trans_table_show(struct device *dev,
len = -EFBIG;
}
unlock:
mutex_unlock(&cdev->lock);
return len;
}
@ -894,13 +870,11 @@ ssize_t weight_store(struct device *dev, struct device_attribute *attr,
instance = container_of(attr, struct thermal_instance, weight_attr);
/* Don't race with governors using the 'weight' value */
mutex_lock(&tz->lock);
guard(thermal_zone)(tz);
instance->weight = weight;
thermal_governor_update_tz(tz, THERMAL_INSTANCE_WEIGHT_CHANGED);
mutex_unlock(&tz->lock);
return count;
}

View File

@ -0,0 +1,240 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2024 Linaro Limited
*
* Author: Daniel Lezcano <daniel.lezcano@linaro.org>
*
* Thermal thresholds
*/
#include <linux/list.h>
#include <linux/list_sort.h>
#include <linux/slab.h>
#include "thermal_core.h"
#include "thermal_thresholds.h"
int thermal_thresholds_init(struct thermal_zone_device *tz)
{
INIT_LIST_HEAD(&tz->user_thresholds);
return 0;
}
static void __thermal_thresholds_flush(struct thermal_zone_device *tz)
{
struct list_head *thresholds = &tz->user_thresholds;
struct user_threshold *entry, *tmp;
list_for_each_entry_safe(entry, tmp, thresholds, list_node) {
list_del(&entry->list_node);
kfree(entry);
}
}
void thermal_thresholds_flush(struct thermal_zone_device *tz)
{
lockdep_assert_held(&tz->lock);
__thermal_thresholds_flush(tz);
thermal_notify_threshold_flush(tz);
__thermal_zone_device_update(tz, THERMAL_TZ_FLUSH_THRESHOLDS);
}
void thermal_thresholds_exit(struct thermal_zone_device *tz)
{
__thermal_thresholds_flush(tz);
}
static int __thermal_thresholds_cmp(void *data,
const struct list_head *l1,
const struct list_head *l2)
{
struct user_threshold *t1 = container_of(l1, struct user_threshold, list_node);
struct user_threshold *t2 = container_of(l2, struct user_threshold, list_node);
return t1->temperature - t2->temperature;
}
static struct user_threshold *__thermal_thresholds_find(const struct list_head *thresholds,
int temperature)
{
struct user_threshold *t;
list_for_each_entry(t, thresholds, list_node)
if (t->temperature == temperature)
return t;
return NULL;
}
static bool __thermal_threshold_is_crossed(struct user_threshold *threshold, int temperature,
int last_temperature, int direction,
int *low, int *high)
{
if (temperature >= threshold->temperature) {
if (threshold->temperature > *low &&
THERMAL_THRESHOLD_WAY_DOWN & threshold->direction)
*low = threshold->temperature;
if (last_temperature < threshold->temperature &&
threshold->direction & direction)
return true;
} else {
if (threshold->temperature < *high && THERMAL_THRESHOLD_WAY_UP
& threshold->direction)
*high = threshold->temperature;
if (last_temperature >= threshold->temperature &&
threshold->direction & direction)
return true;
}
return false;
}
static bool thermal_thresholds_handle_raising(struct list_head *thresholds, int temperature,
int last_temperature, int *low, int *high)
{
struct user_threshold *t;
list_for_each_entry(t, thresholds, list_node) {
if (__thermal_threshold_is_crossed(t, temperature, last_temperature,
THERMAL_THRESHOLD_WAY_UP, low, high))
return true;
}
return false;
}
static bool thermal_thresholds_handle_dropping(struct list_head *thresholds, int temperature,
int last_temperature, int *low, int *high)
{
struct user_threshold *t;
list_for_each_entry_reverse(t, thresholds, list_node) {
if (__thermal_threshold_is_crossed(t, temperature, last_temperature,
THERMAL_THRESHOLD_WAY_DOWN, low, high))
return true;
}
return false;
}
void thermal_thresholds_handle(struct thermal_zone_device *tz, int *low, int *high)
{
struct list_head *thresholds = &tz->user_thresholds;
int temperature = tz->temperature;
int last_temperature = tz->last_temperature;
lockdep_assert_held(&tz->lock);
/*
* We need a second update in order to detect a threshold being crossed
*/
if (last_temperature == THERMAL_TEMP_INVALID)
return;
/*
* The temperature is stable, so obviously we can not have
* crossed a threshold.
*/
if (last_temperature == temperature)
return;
/*
* Since last update the temperature:
* - increased : thresholds are crossed the way up
* - decreased : thresholds are crossed the way down
*/
if (temperature > last_temperature) {
if (thermal_thresholds_handle_raising(thresholds, temperature,
last_temperature, low, high))
thermal_notify_threshold_up(tz);
} else {
if (thermal_thresholds_handle_dropping(thresholds, temperature,
last_temperature, low, high))
thermal_notify_threshold_down(tz);
}
}
int thermal_thresholds_add(struct thermal_zone_device *tz,
int temperature, int direction)
{
struct list_head *thresholds = &tz->user_thresholds;
struct user_threshold *t;
lockdep_assert_held(&tz->lock);
t = __thermal_thresholds_find(thresholds, temperature);
if (t) {
if (t->direction == direction)
return -EEXIST;
t->direction |= direction;
} else {
t = kmalloc(sizeof(*t), GFP_KERNEL);
if (!t)
return -ENOMEM;
INIT_LIST_HEAD(&t->list_node);
t->temperature = temperature;
t->direction = direction;
list_add(&t->list_node, thresholds);
list_sort(NULL, thresholds, __thermal_thresholds_cmp);
}
thermal_notify_threshold_add(tz, temperature, direction);
__thermal_zone_device_update(tz, THERMAL_TZ_ADD_THRESHOLD);
return 0;
}
int thermal_thresholds_delete(struct thermal_zone_device *tz,
int temperature, int direction)
{
struct list_head *thresholds = &tz->user_thresholds;
struct user_threshold *t;
lockdep_assert_held(&tz->lock);
t = __thermal_thresholds_find(thresholds, temperature);
if (!t)
return -ENOENT;
if (t->direction == direction) {
list_del(&t->list_node);
kfree(t);
} else {
t->direction &= ~direction;
}
thermal_notify_threshold_delete(tz, temperature, direction);
__thermal_zone_device_update(tz, THERMAL_TZ_DEL_THRESHOLD);
return 0;
}
int thermal_thresholds_for_each(struct thermal_zone_device *tz,
int (*cb)(struct user_threshold *, void *arg), void *arg)
{
struct list_head *thresholds = &tz->user_thresholds;
struct user_threshold *entry;
int ret;
guard(thermal_zone)(tz);
list_for_each_entry(entry, thresholds, list_node) {
ret = cb(entry, arg);
if (ret)
return ret;
}
return 0;
}

View File

@ -0,0 +1,19 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __THERMAL_THRESHOLDS_H__
#define __THERMAL_THRESHOLDS_H__
struct user_threshold {
struct list_head list_node;
int temperature;
int direction;
};
int thermal_thresholds_init(struct thermal_zone_device *tz);
void thermal_thresholds_exit(struct thermal_zone_device *tz);
void thermal_thresholds_handle(struct thermal_zone_device *tz, int *low, int *high);
void thermal_thresholds_flush(struct thermal_zone_device *tz);
int thermal_thresholds_add(struct thermal_zone_device *tz, int temperature, int direction);
int thermal_thresholds_delete(struct thermal_zone_device *tz, int temperature, int direction);
int thermal_thresholds_for_each(struct thermal_zone_device *tz,
int (*cb)(struct user_threshold *, void *arg), void *arg);
#endif

View File

@ -45,13 +45,9 @@ int thermal_zone_for_each_trip(struct thermal_zone_device *tz,
int (*cb)(struct thermal_trip *, void *),
void *data)
{
int ret;
guard(thermal_zone)(tz);
mutex_lock(&tz->lock);
ret = for_each_thermal_trip(tz, cb, data);
mutex_unlock(&tz->lock);
return ret;
return for_each_thermal_trip(tz, cb, data);
}
EXPORT_SYMBOL_GPL(thermal_zone_for_each_trip);
@ -92,43 +88,3 @@ int thermal_zone_trip_id(const struct thermal_zone_device *tz,
*/
return trip_to_trip_desc(trip) - tz->trips;
}
void thermal_zone_set_trip_hyst(struct thermal_zone_device *tz,
struct thermal_trip *trip, int hyst)
{
WRITE_ONCE(trip->hysteresis, hyst);
thermal_notify_tz_trip_change(tz, trip);
}
void thermal_zone_set_trip_temp(struct thermal_zone_device *tz,
struct thermal_trip *trip, int temp)
{
if (trip->temperature == temp)
return;
WRITE_ONCE(trip->temperature, temp);
thermal_notify_tz_trip_change(tz, trip);
if (temp == THERMAL_TEMP_INVALID) {
struct thermal_trip_desc *td = trip_to_trip_desc(trip);
if (tz->temperature >= td->threshold) {
/*
* The trip has been crossed on the way up, so some
* adjustments are needed to compensate for the lack
* of it going forward.
*/
if (trip->type == THERMAL_TRIP_PASSIVE) {
tz->passive--;
WARN_ON_ONCE(tz->passive < 0);
}
thermal_zone_trip_down(tz, trip);
}
/*
* Invalidate the threshold to avoid triggering a spurious
* trip crossing notification when the trip becomes valid.
*/
td->threshold = INT_MAX;
}
}
EXPORT_SYMBOL_GPL(thermal_zone_set_trip_temp);

View File

@ -56,6 +56,9 @@ enum thermal_notify_event {
THERMAL_TZ_UNBIND_CDEV, /* Cooling dev is unbind from the thermal zone */
THERMAL_INSTANCE_WEIGHT_CHANGED, /* Thermal instance weight changed */
THERMAL_TZ_RESUME, /* Thermal zone is resuming after system sleep */
THERMAL_TZ_ADD_THRESHOLD, /* Threshold added */
THERMAL_TZ_DEL_THRESHOLD, /* Threshold deleted */
THERMAL_TZ_FLUSH_THRESHOLDS, /* All thresholds deleted */
};
/**
@ -137,6 +140,9 @@ struct thermal_cooling_device {
#endif
};
DEFINE_GUARD(cooling_dev, struct thermal_cooling_device *, mutex_lock(&_T->lock),
mutex_unlock(&_T->lock))
/* Structure to define Thermal Zone parameters */
struct thermal_zone_params {
const char *governor_name;

View File

@ -3,6 +3,8 @@
#define _UAPI_LINUX_THERMAL_H
#define THERMAL_NAME_LENGTH 20
#define THERMAL_THRESHOLD_WAY_UP BIT(0)
#define THERMAL_THRESHOLD_WAY_DOWN BIT(1)
enum thermal_device_mode {
THERMAL_DEVICE_DISABLED = 0,
@ -18,7 +20,7 @@ enum thermal_trip_type {
/* Adding event notification support elements */
#define THERMAL_GENL_FAMILY_NAME "thermal"
#define THERMAL_GENL_VERSION 0x01
#define THERMAL_GENL_VERSION 0x02
#define THERMAL_GENL_SAMPLING_GROUP_NAME "sampling"
#define THERMAL_GENL_EVENT_GROUP_NAME "event"
@ -28,6 +30,7 @@ enum thermal_genl_attr {
THERMAL_GENL_ATTR_TZ,
THERMAL_GENL_ATTR_TZ_ID,
THERMAL_GENL_ATTR_TZ_TEMP,
THERMAL_GENL_ATTR_TZ_PREV_TEMP,
THERMAL_GENL_ATTR_TZ_TRIP,
THERMAL_GENL_ATTR_TZ_TRIP_ID,
THERMAL_GENL_ATTR_TZ_TRIP_TYPE,
@ -48,6 +51,9 @@ enum thermal_genl_attr {
THERMAL_GENL_ATTR_CPU_CAPABILITY_ID,
THERMAL_GENL_ATTR_CPU_CAPABILITY_PERFORMANCE,
THERMAL_GENL_ATTR_CPU_CAPABILITY_EFFICIENCY,
THERMAL_GENL_ATTR_THRESHOLD,
THERMAL_GENL_ATTR_THRESHOLD_TEMP,
THERMAL_GENL_ATTR_THRESHOLD_DIRECTION,
__THERMAL_GENL_ATTR_MAX,
};
#define THERMAL_GENL_ATTR_MAX (__THERMAL_GENL_ATTR_MAX - 1)
@ -75,6 +81,11 @@ enum thermal_genl_event {
THERMAL_GENL_EVENT_CDEV_STATE_UPDATE, /* Cdev state updated */
THERMAL_GENL_EVENT_TZ_GOV_CHANGE, /* Governor policy changed */
THERMAL_GENL_EVENT_CPU_CAPABILITY_CHANGE, /* CPU capability changed */
THERMAL_GENL_EVENT_THRESHOLD_ADD, /* A thresold has been added */
THERMAL_GENL_EVENT_THRESHOLD_DELETE, /* A thresold has been deleted */
THERMAL_GENL_EVENT_THRESHOLD_FLUSH, /* All thresolds have been deleted */
THERMAL_GENL_EVENT_THRESHOLD_UP, /* A thresold has been crossed the way up */
THERMAL_GENL_EVENT_THRESHOLD_DOWN, /* A thresold has been crossed the way down */
__THERMAL_GENL_EVENT_MAX,
};
#define THERMAL_GENL_EVENT_MAX (__THERMAL_GENL_EVENT_MAX - 1)
@ -88,6 +99,10 @@ enum thermal_genl_cmd {
THERMAL_GENL_CMD_TZ_GET_GOV, /* Get the thermal zone governor */
THERMAL_GENL_CMD_TZ_GET_MODE, /* Get the thermal zone mode */
THERMAL_GENL_CMD_CDEV_GET, /* List of cdev id */
THERMAL_GENL_CMD_THRESHOLD_GET, /* List of thresholds */
THERMAL_GENL_CMD_THRESHOLD_ADD, /* Add a threshold */
THERMAL_GENL_CMD_THRESHOLD_DELETE, /* Delete a threshold */
THERMAL_GENL_CMD_THRESHOLD_FLUSH, /* Flush all the thresholds */
__THERMAL_GENL_CMD_MAX,
};
#define THERMAL_GENL_CMD_MAX (__THERMAL_GENL_CMD_MAX - 1)

View File

@ -5,6 +5,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <limits.h>
#include <thermal.h>
#include "thermal_nl.h"
@ -33,6 +34,11 @@ static struct nla_policy thermal_genl_policy[THERMAL_GENL_ATTR_MAX + 1] = {
[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 },
/* Thresholds */
[THERMAL_GENL_ATTR_THRESHOLD] = { .type = NLA_NESTED },
[THERMAL_GENL_ATTR_THRESHOLD_TEMP] = { .type = NLA_U32 },
[THERMAL_GENL_ATTR_THRESHOLD_DIRECTION] = { .type = NLA_U32 },
};
static int parse_tz_get(struct genl_info *info, struct thermal_zone **tz)
@ -182,6 +188,48 @@ static int parse_tz_get_gov(struct genl_info *info, struct thermal_zone *tz)
return THERMAL_SUCCESS;
}
static int parse_threshold_get(struct genl_info *info, struct thermal_zone *tz)
{
struct nlattr *attr;
struct thermal_threshold *__tt = NULL;
size_t size = 0;
int rem;
/*
* The size contains the size of the array and we want to
* access the last element, size - 1.
*
* The variable size is initialized to zero but it will be
* then incremented by the first if() statement. The message
* attributes are ordered, so the first if() statement will be
* always called before the second one. If it happens that is
* not the case, then it is a kernel bug.
*/
nla_for_each_nested(attr, info->attrs[THERMAL_GENL_ATTR_THRESHOLD], rem) {
if (nla_type(attr) == THERMAL_GENL_ATTR_THRESHOLD_TEMP) {
size++;
__tt = realloc(__tt, sizeof(*__tt) * (size + 2));
if (!__tt)
return THERMAL_ERROR;
__tt[size - 1].temperature = nla_get_u32(attr);
}
if (nla_type(attr) == THERMAL_GENL_ATTR_THRESHOLD_DIRECTION)
__tt[size - 1].direction = nla_get_u32(attr);
}
if (__tt)
__tt[size].temperature = INT_MAX;
tz->thresholds = __tt;
return THERMAL_SUCCESS;
}
static int handle_netlink(struct nl_cache_ops *unused,
struct genl_cmd *cmd,
struct genl_info *info, void *arg)
@ -210,6 +258,10 @@ static int handle_netlink(struct nl_cache_ops *unused,
ret = parse_tz_get_gov(info, arg);
break;
case THERMAL_GENL_CMD_THRESHOLD_GET:
ret = parse_threshold_get(info, arg);
break;
default:
return THERMAL_ERROR;
}
@ -253,6 +305,34 @@ static struct genl_cmd thermal_cmds[] = {
.c_maxattr = THERMAL_GENL_ATTR_MAX,
.c_attr_policy = thermal_genl_policy,
},
{
.c_id = THERMAL_GENL_CMD_THRESHOLD_GET,
.c_name = (char *)"Get thresholds list",
.c_msg_parser = handle_netlink,
.c_maxattr = THERMAL_GENL_ATTR_MAX,
.c_attr_policy = thermal_genl_policy,
},
{
.c_id = THERMAL_GENL_CMD_THRESHOLD_ADD,
.c_name = (char *)"Add a threshold",
.c_msg_parser = handle_netlink,
.c_maxattr = THERMAL_GENL_ATTR_MAX,
.c_attr_policy = thermal_genl_policy,
},
{
.c_id = THERMAL_GENL_CMD_THRESHOLD_DELETE,
.c_name = (char *)"Delete a threshold",
.c_msg_parser = handle_netlink,
.c_maxattr = THERMAL_GENL_ATTR_MAX,
.c_attr_policy = thermal_genl_policy,
},
{
.c_id = THERMAL_GENL_CMD_THRESHOLD_FLUSH,
.c_name = (char *)"Flush the thresholds",
.c_msg_parser = handle_netlink,
.c_maxattr = THERMAL_GENL_ATTR_MAX,
.c_attr_policy = thermal_genl_policy,
},
};
static struct genl_ops thermal_cmd_ops = {
@ -261,9 +341,41 @@ static struct genl_ops thermal_cmd_ops = {
.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 cmd_param {
int tz_id;
int temp;
int direction;
};
typedef int (*cmd_cb_t)(struct nl_msg *, struct cmd_param *);
static int thermal_genl_tz_id_encode(struct nl_msg *msg, struct cmd_param *p)
{
if (nla_put_u32(msg, THERMAL_GENL_ATTR_TZ_ID, p->tz_id))
return -1;
return 0;
}
static int thermal_genl_threshold_encode(struct nl_msg *msg, struct cmd_param *p)
{
if (thermal_genl_tz_id_encode(msg, p))
return -1;
if (nla_put_u32(msg, THERMAL_GENL_ATTR_THRESHOLD_TEMP, p->temp))
return -1;
if (nla_put_u32(msg, THERMAL_GENL_ATTR_THRESHOLD_DIRECTION, p->direction))
return -1;
return 0;
}
static thermal_error_t thermal_genl_auto(struct thermal_handler *th, cmd_cb_t cmd_cb,
struct cmd_param *param,
int cmd, int flags, void *arg)
{
thermal_error_t ret = THERMAL_ERROR;
struct nl_msg *msg;
void *hdr;
@ -274,45 +386,95 @@ static thermal_error_t thermal_genl_auto(struct thermal_handler *th, int id, int
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;
goto out;
if (id >= 0 && nla_put_u32(msg, THERMAL_GENL_ATTR_TZ_ID, id))
return THERMAL_ERROR;
if (cmd_cb && cmd_cb(msg, param))
goto out;
if (nl_send_msg(th->sk_cmd, th->cb_cmd, msg, genl_handle_msg, arg))
return THERMAL_ERROR;
goto out;
ret = THERMAL_SUCCESS;
out:
nlmsg_free(msg);
return THERMAL_SUCCESS;
return ret;
}
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,
return thermal_genl_auto(th, NULL, NULL, 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,
return thermal_genl_auto(th, NULL, NULL, 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);
struct cmd_param p = { .tz_id = tz->id };
return thermal_genl_auto(th, thermal_genl_tz_id_encode, &p,
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);
struct cmd_param p = { .tz_id = tz->id };
return thermal_genl_auto(th, thermal_genl_tz_id_encode, &p,
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);
struct cmd_param p = { .tz_id = tz->id };
return thermal_genl_auto(th, thermal_genl_tz_id_encode, &p,
THERMAL_GENL_CMD_TZ_GET_TEMP, 0, tz);
}
thermal_error_t thermal_cmd_threshold_get(struct thermal_handler *th,
struct thermal_zone *tz)
{
struct cmd_param p = { .tz_id = tz->id };
return thermal_genl_auto(th, thermal_genl_tz_id_encode, &p,
THERMAL_GENL_CMD_THRESHOLD_GET, 0, tz);
}
thermal_error_t thermal_cmd_threshold_add(struct thermal_handler *th,
struct thermal_zone *tz,
int temperature,
int direction)
{
struct cmd_param p = { .tz_id = tz->id, .temp = temperature, .direction = direction };
return thermal_genl_auto(th, thermal_genl_threshold_encode, &p,
THERMAL_GENL_CMD_THRESHOLD_ADD, 0, tz);
}
thermal_error_t thermal_cmd_threshold_delete(struct thermal_handler *th,
struct thermal_zone *tz,
int temperature,
int direction)
{
struct cmd_param p = { .tz_id = tz->id, .temp = temperature, .direction = direction };
return thermal_genl_auto(th, thermal_genl_threshold_encode, &p,
THERMAL_GENL_CMD_THRESHOLD_DELETE, 0, tz);
}
thermal_error_t thermal_cmd_threshold_flush(struct thermal_handler *th,
struct thermal_zone *tz)
{
struct cmd_param p = { .tz_id = tz->id };
return thermal_genl_auto(th, thermal_genl_tz_id_encode, &p,
THERMAL_GENL_CMD_THRESHOLD_FLUSH, 0, tz);
}
thermal_error_t thermal_cmd_exit(struct thermal_handler *th)

View File

@ -94,6 +94,30 @@ static int handle_thermal_event(struct nl_msg *n, void *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);
case THERMAL_GENL_EVENT_THRESHOLD_ADD:
return ops->threshold_add(nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]),
nla_get_u32(attrs[THERMAL_GENL_ATTR_THRESHOLD_TEMP]),
nla_get_u32(attrs[THERMAL_GENL_ATTR_THRESHOLD_DIRECTION]), arg);
case THERMAL_GENL_EVENT_THRESHOLD_DELETE:
return ops->threshold_delete(nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]),
nla_get_u32(attrs[THERMAL_GENL_ATTR_THRESHOLD_TEMP]),
nla_get_u32(attrs[THERMAL_GENL_ATTR_THRESHOLD_DIRECTION]), arg);
case THERMAL_GENL_EVENT_THRESHOLD_FLUSH:
return ops->threshold_flush(nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]), arg);
case THERMAL_GENL_EVENT_THRESHOLD_UP:
return ops->threshold_up(nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]),
nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TEMP]),
nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_PREV_TEMP]), arg);
case THERMAL_GENL_EVENT_THRESHOLD_DOWN:
return ops->threshold_down(nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]),
nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TEMP]),
nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_PREV_TEMP]), arg);
default:
return -1;
}
@ -114,6 +138,11 @@ static void thermal_events_ops_init(struct thermal_events_ops *ops)
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;
enabled_ops[THERMAL_GENL_EVENT_THRESHOLD_ADD] = !!ops->threshold_add;
enabled_ops[THERMAL_GENL_EVENT_THRESHOLD_DELETE] = !!ops->threshold_delete;
enabled_ops[THERMAL_GENL_EVENT_THRESHOLD_FLUSH] = !!ops->threshold_flush;
enabled_ops[THERMAL_GENL_EVENT_THRESHOLD_UP] = !!ops->threshold_up;
enabled_ops[THERMAL_GENL_EVENT_THRESHOLD_DOWN] = !!ops->threshold_down;
}
thermal_error_t thermal_events_handle(struct thermal_handler *th, void *arg)

View File

@ -4,11 +4,20 @@
#define __LIBTHERMAL_H
#include <linux/thermal.h>
#include <sys/types.h>
#ifndef LIBTHERMAL_API
#define LIBTHERMAL_API __attribute__((visibility("default")))
#endif
#ifndef THERMAL_THRESHOLD_WAY_UP
#define THERMAL_THRESHOLD_WAY_UP 0x1
#endif
#ifndef THERMAL_THRESHOLD_WAY_DOWN
#define THERMAL_THRESHOLD_WAY_DOWN 0x2
#endif
#ifdef __cplusplus
extern "C" {
#endif
@ -31,6 +40,11 @@ struct thermal_events_ops {
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);
int (*threshold_add)(int tz_id, int temperature, int direction, void *arg);
int (*threshold_delete)(int tz_id, int temperature, int direction, void *arg);
int (*threshold_flush)(int tz_id, void *arg);
int (*threshold_up)(int tz_id, int temp, int prev_temp, void *arg);
int (*threshold_down)(int tz_id, int temp, int prev_temp, void *arg);
};
struct thermal_ops {
@ -45,12 +59,18 @@ struct thermal_trip {
int hyst;
};
struct thermal_threshold {
int temperature;
int direction;
};
struct thermal_zone {
int id;
int temp;
char name[THERMAL_NAME_LENGTH];
char governor[THERMAL_NAME_LENGTH];
struct thermal_trip *trip;
struct thermal_threshold *thresholds;
};
struct thermal_cdev {
@ -74,12 +94,16 @@ typedef int (*cb_tt_t)(struct thermal_trip *, void *);
typedef int (*cb_tc_t)(struct thermal_cdev *, void *);
typedef int (*cb_th_t)(struct thermal_threshold *, 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 int for_each_thermal_threshold(struct thermal_threshold *th, cb_th_t cb, void *arg);
LIBTHERMAL_API struct thermal_zone *thermal_zone_find_by_name(struct thermal_zone *tz,
const char *name);
@ -124,6 +148,22 @@ LIBTHERMAL_API thermal_error_t thermal_cmd_get_governor(struct thermal_handler *
LIBTHERMAL_API thermal_error_t thermal_cmd_get_temp(struct thermal_handler *th,
struct thermal_zone *tz);
LIBTHERMAL_API thermal_error_t thermal_cmd_threshold_get(struct thermal_handler *th,
struct thermal_zone *tz);
LIBTHERMAL_API thermal_error_t thermal_cmd_threshold_add(struct thermal_handler *th,
struct thermal_zone *tz,
int temperature,
int direction);
LIBTHERMAL_API thermal_error_t thermal_cmd_threshold_delete(struct thermal_handler *th,
struct thermal_zone *tz,
int temperature,
int direction);
LIBTHERMAL_API thermal_error_t thermal_cmd_threshold_flush(struct thermal_handler *th,
struct thermal_zone *tz);
/*
* Netlink thermal samples
*/

View File

@ -4,6 +4,7 @@ LIBTHERMAL_0.0.1 {
for_each_thermal_zone;
for_each_thermal_trip;
for_each_thermal_cdev;
for_each_thermal_threshold;
thermal_zone_find_by_name;
thermal_zone_find_by_id;
thermal_zone_discover;
@ -17,6 +18,10 @@ LIBTHERMAL_0.0.1 {
thermal_cmd_get_trip;
thermal_cmd_get_governor;
thermal_cmd_get_temp;
thermal_cmd_threshold_get;
thermal_cmd_threshold_add;
thermal_cmd_threshold_delete;
thermal_cmd_threshold_flush;
thermal_sampling_init;
thermal_sampling_handle;
thermal_sampling_fd;

View File

@ -1,10 +1,24 @@
// SPDX-License-Identifier: LGPL-2.1+
// Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org>
#include <stdio.h>
#include <limits.h>
#include <thermal.h>
#include "thermal_nl.h"
int for_each_thermal_threshold(struct thermal_threshold *th, cb_th_t cb, void *arg)
{
int i, ret = 0;
if (!th)
return 0;
for (i = 0; th[i].temperature != INT_MAX; i++)
ret |= cb(&th[i], arg);
return ret;
}
int for_each_thermal_cdev(struct thermal_cdev *cdev, cb_tc_t cb, void *arg)
{
int i, ret = 0;
@ -80,6 +94,9 @@ static int __thermal_zone_discover(struct thermal_zone *tz, void *th)
if (thermal_cmd_get_trip(th, tz) < 0)
return -1;
if (thermal_cmd_threshold_get(th, tz))
return -1;
if (thermal_cmd_get_governor(th, tz))
return -1;

View File

@ -3,7 +3,7 @@
LIBTHERMAL_TOOLS_VERSION = 0
LIBTHERMAL_TOOLS_PATCHLEVEL = 0
LIBTHERMAL_TOOLS_EXTRAVERSION = 1
LIBTHERMAL_TOOLS_EXTRAVERSION = 2
MAKEFLAGS += --no-print-directory

View File

@ -38,6 +38,14 @@ struct thermal_data {
struct thermal_handler *th;
};
static int show_threshold(struct thermal_threshold *th, __maybe_unused void *arg)
{
INFO("threshold temp=%d, direction=%d\n",
th->temperature, th->direction);
return 0;
}
static int show_trip(struct thermal_trip *tt, __maybe_unused void *arg)
{
INFO("trip id=%d, type=%d, temp=%d, hyst=%d\n",
@ -70,6 +78,8 @@ static int show_tz(struct thermal_zone *tz, __maybe_unused void *arg)
for_each_thermal_trip(tz->trip, show_trip, NULL);
for_each_thermal_threshold(tz->thresholds, show_threshold, NULL);
show_temp(tz, arg);
show_governor(tz, arg);
@ -77,6 +87,30 @@ static int show_tz(struct thermal_zone *tz, __maybe_unused void *arg)
return 0;
}
static int set_threshold(struct thermal_zone *tz, __maybe_unused void *arg)
{
struct thermal_handler *th = arg;
int thresholds[] = { 43000, 65000, 49000, 55000, 57000 };
size_t i;
INFO("Setting threshold for thermal zone '%s', id=%d\n", tz->name, tz->id);
if (thermal_cmd_threshold_flush(th, tz)) {
ERROR("Failed to flush all previous thresholds\n");
return -1;
}
for (i = 0; i < sizeof(thresholds) / sizeof(thresholds[0]); i++)
if (thermal_cmd_threshold_add(th, tz, thresholds[i],
THERMAL_THRESHOLD_WAY_UP |
THERMAL_THRESHOLD_WAY_DOWN)) {
ERROR("Failed to set threshold\n");
return -1;
}
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);
@ -197,6 +231,43 @@ static int gov_change(int tz_id, const char *name, __maybe_unused void *arg)
return 0;
}
static int threshold_add(int tz_id, int temp, int direction, __maybe_unused void *arg)
{
INFO("Threshold added tz_id=%d: temp=%d, direction=%d\n", tz_id, temp, direction);
return 0;
}
static int threshold_delete(int tz_id, int temp, int direction, __maybe_unused void *arg)
{
INFO("Threshold deleted tz_id=%d: temp=%d, direction=%d\n", tz_id, temp, direction);
return 0;
}
static int threshold_flush(int tz_id, __maybe_unused void *arg)
{
INFO("Thresholds flushed tz_id=%d\n", tz_id);
return 0;
}
static int threshold_up(int tz_id, int temp, int prev_temp, __maybe_unused void *arg)
{
INFO("Threshold crossed way up tz_id=%d: temp=%d, prev_temp=%d\n",
tz_id, temp, prev_temp);
return 0;
}
static int threshold_down(int tz_id, int temp, int prev_temp, __maybe_unused void *arg)
{
INFO("Threshold crossed way down tz_id=%d: temp=%d, prev_temp=%d\n",
tz_id, temp, prev_temp);
return 0;
}
static struct thermal_ops ops = {
.events.tz_create = tz_create,
.events.tz_delete = tz_delete,
@ -210,7 +281,12 @@ static struct thermal_ops ops = {
.events.cdev_add = cdev_add,
.events.cdev_delete = cdev_delete,
.events.cdev_update = cdev_update,
.events.gov_change = gov_change
.events.gov_change = gov_change,
.events.threshold_add = threshold_add,
.events.threshold_delete = threshold_delete,
.events.threshold_flush = threshold_flush,
.events.threshold_up = threshold_up,
.events.threshold_down = threshold_down,
};
static int thermal_event(__maybe_unused int fd, __maybe_unused void *arg)
@ -280,6 +356,7 @@ enum {
THERMAL_ENGINE_DAEMON_ERROR,
THERMAL_ENGINE_LOG_ERROR,
THERMAL_ENGINE_THERMAL_ERROR,
THERMAL_ENGINE_THRESHOLD_ERROR,
THERMAL_ENGINE_MAINLOOP_ERROR,
};
@ -318,6 +395,8 @@ int main(int argc, char *argv[])
return THERMAL_ENGINE_THERMAL_ERROR;
}
for_each_thermal_zone(td.tz, set_threshold, td.th);
for_each_thermal_zone(td.tz, show_tz, td.th);
if (mainloop_init()) {