| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Copyright 2020 Linaro Limited | |
| * | |
| * Author: Daniel Lezcano <daniel.lezcano@linaro.org> | |
| * | |
| * Generic netlink for thermal management framework | |
| */ | |
| #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> | |
| #include "thermal_core.h" | |
| static const struct genl_multicast_group thermal_genl_mcgrps[] = { | |
| [THERMAL_GENL_SAMPLING_GROUP] = { .name = THERMAL_GENL_SAMPLING_GROUP_NAME, }, | |
| [THERMAL_GENL_EVENT_GROUP] = { .name = THERMAL_GENL_EVENT_GROUP_NAME, }, | |
| }; | |
| static const 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, | |
| .len = THERMAL_NAME_LENGTH }, | |
| /* Governor(s) */ | |
| [THERMAL_GENL_ATTR_TZ_GOV] = { .type = NLA_NESTED }, | |
| [THERMAL_GENL_ATTR_TZ_GOV_NAME] = { .type = NLA_STRING, | |
| .len = THERMAL_NAME_LENGTH }, | |
| /* 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, | |
| .len = THERMAL_NAME_LENGTH }, | |
| /* CPU capabilities */ | |
| [THERMAL_GENL_ATTR_CPU_CAPABILITY] = { .type = NLA_NESTED }, | |
| [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 { | |
| struct nlattr **attrs; | |
| struct sk_buff *msg; | |
| const char *name; | |
| int tz_id; | |
| int cdev_id; | |
| int trip_id; | |
| int trip_temp; | |
| 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; | |
| int cpu_capabilities_count; | |
| }; | |
| typedef int (*cb_t)(struct param *); | |
| static struct genl_family thermal_genl_family; | |
| static BLOCKING_NOTIFIER_HEAD(thermal_genl_chain); | |
| static int thermal_group_has_listeners(enum thermal_genl_multicast_groups group) | |
| { | |
| return genl_has_listeners(&thermal_genl_family, &init_net, group); | |
| } | |
| /************************** Sampling encoding *******************************/ | |
| int thermal_genl_sampling_temp(int id, int temp) | |
| { | |
| struct sk_buff *skb; | |
| void *hdr; | |
| if (!thermal_group_has_listeners(THERMAL_GENL_SAMPLING_GROUP)) | |
| return 0; | |
| skb = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); | |
| if (!skb) | |
| return -ENOMEM; | |
| hdr = genlmsg_put(skb, 0, 0, &thermal_genl_family, 0, | |
| THERMAL_GENL_SAMPLING_TEMP); | |
| if (!hdr) | |
| goto out_free; | |
| if (nla_put_u32(skb, THERMAL_GENL_ATTR_TZ_ID, id)) | |
| goto out_cancel; | |
| if (nla_put_u32(skb, THERMAL_GENL_ATTR_TZ_TEMP, temp)) | |
| goto out_cancel; | |
| genlmsg_end(skb, hdr); | |
| genlmsg_multicast(&thermal_genl_family, skb, 0, THERMAL_GENL_SAMPLING_GROUP, GFP_KERNEL); | |
| return 0; | |
| out_cancel: | |
| genlmsg_cancel(skb, hdr); | |
| out_free: | |
| nlmsg_free(skb); | |
| return -EMSGSIZE; | |
| } | |
| /**************************** Event encoding *********************************/ | |
| static int thermal_genl_event_tz_create(struct param *p) | |
| { | |
| if (nla_put_u32(p->msg, THERMAL_GENL_ATTR_TZ_ID, p->tz_id) || | |
| nla_put_string(p->msg, THERMAL_GENL_ATTR_TZ_NAME, p->name)) | |
| return -EMSGSIZE; | |
| return 0; | |
| } | |
| static int thermal_genl_event_tz(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_tz_trip_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_TRIP_ID, p->trip_id) || | |
| nla_put_u32(p->msg, THERMAL_GENL_ATTR_TZ_TEMP, p->temp)) | |
| return -EMSGSIZE; | |
| return 0; | |
| } | |
| static int thermal_genl_event_tz_trip_change(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_TRIP_ID, p->trip_id) || | |
| nla_put_u32(p->msg, THERMAL_GENL_ATTR_TZ_TRIP_TYPE, p->trip_type) || | |
| nla_put_u32(p->msg, THERMAL_GENL_ATTR_TZ_TRIP_TEMP, p->trip_temp) || | |
| nla_put_u32(p->msg, THERMAL_GENL_ATTR_TZ_TRIP_HYST, p->trip_hyst)) | |
| return -EMSGSIZE; | |
| return 0; | |
| } | |
| static int thermal_genl_event_cdev_add(struct param *p) | |
| { | |
| if (nla_put_string(p->msg, THERMAL_GENL_ATTR_CDEV_NAME, | |
| p->name) || | |
| nla_put_u32(p->msg, THERMAL_GENL_ATTR_CDEV_ID, | |
| p->cdev_id) || | |
| nla_put_u32(p->msg, THERMAL_GENL_ATTR_CDEV_MAX_STATE, | |
| p->cdev_max_state)) | |
| return -EMSGSIZE; | |
| return 0; | |
| } | |
| static int thermal_genl_event_cdev_delete(struct param *p) | |
| { | |
| if (nla_put_u32(p->msg, THERMAL_GENL_ATTR_CDEV_ID, p->cdev_id)) | |
| return -EMSGSIZE; | |
| return 0; | |
| } | |
| static int thermal_genl_event_cdev_state_update(struct param *p) | |
| { | |
| if (nla_put_u32(p->msg, THERMAL_GENL_ATTR_CDEV_ID, | |
| p->cdev_id) || | |
| nla_put_u32(p->msg, THERMAL_GENL_ATTR_CDEV_CUR_STATE, | |
| p->cdev_state)) | |
| return -EMSGSIZE; | |
| return 0; | |
| } | |
| static int thermal_genl_event_gov_change(struct param *p) | |
| { | |
| if (nla_put_u32(p->msg, THERMAL_GENL_ATTR_TZ_ID, p->tz_id) || | |
| nla_put_string(p->msg, THERMAL_GENL_ATTR_GOV_NAME, p->name)) | |
| return -EMSGSIZE; | |
| return 0; | |
| } | |
| static int thermal_genl_event_cpu_capability_change(struct param *p) | |
| { | |
| struct thermal_genl_cpu_caps *cpu_cap = p->cpu_capabilities; | |
| struct sk_buff *msg = p->msg; | |
| struct nlattr *start_cap; | |
| int i; | |
| start_cap = nla_nest_start(msg, THERMAL_GENL_ATTR_CPU_CAPABILITY); | |
| if (!start_cap) | |
| return -EMSGSIZE; | |
| for (i = 0; i < p->cpu_capabilities_count; ++i) { | |
| if (nla_put_u32(msg, THERMAL_GENL_ATTR_CPU_CAPABILITY_ID, | |
| cpu_cap->cpu)) | |
| goto out_cancel_nest; | |
| if (nla_put_u32(msg, THERMAL_GENL_ATTR_CPU_CAPABILITY_PERFORMANCE, | |
| cpu_cap->performance)) | |
| goto out_cancel_nest; | |
| if (nla_put_u32(msg, THERMAL_GENL_ATTR_CPU_CAPABILITY_EFFICIENCY, | |
| cpu_cap->efficiency)) | |
| goto out_cancel_nest; | |
| ++cpu_cap; | |
| } | |
| nla_nest_end(msg, start_cap); | |
| return 0; | |
| out_cancel_nest: | |
| nla_nest_cancel(msg, start_cap); | |
| 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"))); | |
| int thermal_genl_event_tz_enable(struct param *p) | |
| __attribute__((alias("thermal_genl_event_tz"))); | |
| int thermal_genl_event_tz_disable(struct param *p) | |
| __attribute__((alias("thermal_genl_event_tz"))); | |
| 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, | |
| [THERMAL_GENL_EVENT_TZ_ENABLE] = thermal_genl_event_tz_enable, | |
| [THERMAL_GENL_EVENT_TZ_DISABLE] = thermal_genl_event_tz_disable, | |
| [THERMAL_GENL_EVENT_TZ_TRIP_UP] = thermal_genl_event_tz_trip_up, | |
| [THERMAL_GENL_EVENT_TZ_TRIP_DOWN] = thermal_genl_event_tz_trip_down, | |
| [THERMAL_GENL_EVENT_TZ_TRIP_CHANGE] = thermal_genl_event_tz_trip_change, | |
| [THERMAL_GENL_EVENT_CDEV_ADD] = thermal_genl_event_cdev_add, | |
| [THERMAL_GENL_EVENT_CDEV_DELETE] = thermal_genl_event_cdev_delete, | |
| [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, | |
| }; | |
| /* | |
| * Generic netlink event encoding | |
| */ | |
| static int thermal_genl_send_event(enum thermal_genl_event event, | |
| struct param *p) | |
| { | |
| struct sk_buff *msg; | |
| int ret = -EMSGSIZE; | |
| void *hdr; | |
| if (!thermal_group_has_listeners(THERMAL_GENL_EVENT_GROUP)) | |
| return 0; | |
| msg = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); | |
| if (!msg) | |
| return -ENOMEM; | |
| p->msg = msg; | |
| hdr = genlmsg_put(msg, 0, 0, &thermal_genl_family, 0, event); | |
| if (!hdr) | |
| goto out_free_msg; | |
| ret = event_cb[event](p); | |
| if (ret) | |
| goto out_cancel_msg; | |
| genlmsg_end(msg, hdr); | |
| genlmsg_multicast(&thermal_genl_family, msg, 0, THERMAL_GENL_EVENT_GROUP, GFP_KERNEL); | |
| return 0; | |
| out_cancel_msg: | |
| genlmsg_cancel(msg, hdr); | |
| out_free_msg: | |
| nlmsg_free(msg); | |
| return ret; | |
| } | |
| int thermal_notify_tz_create(const struct thermal_zone_device *tz) | |
| { | |
| struct param p = { .tz_id = tz->id, .name = tz->type }; | |
| return thermal_genl_send_event(THERMAL_GENL_EVENT_TZ_CREATE, &p); | |
| } | |
| int thermal_notify_tz_delete(const struct thermal_zone_device *tz) | |
| { | |
| struct param p = { .tz_id = tz->id }; | |
| return thermal_genl_send_event(THERMAL_GENL_EVENT_TZ_DELETE, &p); | |
| } | |
| int thermal_notify_tz_enable(const struct thermal_zone_device *tz) | |
| { | |
| struct param p = { .tz_id = tz->id }; | |
| return thermal_genl_send_event(THERMAL_GENL_EVENT_TZ_ENABLE, &p); | |
| } | |
| int thermal_notify_tz_disable(const struct thermal_zone_device *tz) | |
| { | |
| struct param p = { .tz_id = tz->id }; | |
| return thermal_genl_send_event(THERMAL_GENL_EVENT_TZ_DISABLE, &p); | |
| } | |
| int thermal_notify_tz_trip_down(const struct thermal_zone_device *tz, | |
| const struct thermal_trip *trip) | |
| { | |
| struct param p = { .tz_id = tz->id, | |
| .trip_id = thermal_zone_trip_id(tz, trip), | |
| .temp = tz->temperature }; | |
| return thermal_genl_send_event(THERMAL_GENL_EVENT_TZ_TRIP_DOWN, &p); | |
| } | |
| int thermal_notify_tz_trip_up(const struct thermal_zone_device *tz, | |
| const struct thermal_trip *trip) | |
| { | |
| struct param p = { .tz_id = tz->id, | |
| .trip_id = thermal_zone_trip_id(tz, trip), | |
| .temp = tz->temperature }; | |
| return thermal_genl_send_event(THERMAL_GENL_EVENT_TZ_TRIP_UP, &p); | |
| } | |
| int thermal_notify_tz_trip_change(const struct thermal_zone_device *tz, | |
| const struct thermal_trip *trip) | |
| { | |
| struct param p = { .tz_id = tz->id, | |
| .trip_id = thermal_zone_trip_id(tz, trip), | |
| .trip_type = trip->type, | |
| .trip_temp = trip->temperature, | |
| .trip_hyst = trip->hysteresis }; | |
| return thermal_genl_send_event(THERMAL_GENL_EVENT_TZ_TRIP_CHANGE, &p); | |
| } | |
| int thermal_notify_cdev_state_update(const struct thermal_cooling_device *cdev, | |
| int state) | |
| { | |
| struct param p = { .cdev_id = cdev->id, .cdev_state = state }; | |
| return thermal_genl_send_event(THERMAL_GENL_EVENT_CDEV_STATE_UPDATE, &p); | |
| } | |
| int thermal_notify_cdev_add(const struct thermal_cooling_device *cdev) | |
| { | |
| struct param p = { .cdev_id = cdev->id, .name = cdev->type, | |
| .cdev_max_state = cdev->max_state }; | |
| return thermal_genl_send_event(THERMAL_GENL_EVENT_CDEV_ADD, &p); | |
| } | |
| int thermal_notify_cdev_delete(const struct thermal_cooling_device *cdev) | |
| { | |
| struct param p = { .cdev_id = cdev->id }; | |
| return thermal_genl_send_event(THERMAL_GENL_EVENT_CDEV_DELETE, &p); | |
| } | |
| int thermal_notify_tz_gov_change(const struct thermal_zone_device *tz, | |
| const char *name) | |
| { | |
| struct param p = { .tz_id = tz->id, .name = name }; | |
| return thermal_genl_send_event(THERMAL_GENL_EVENT_TZ_GOV_CHANGE, &p); | |
| } | |
| int thermal_genl_cpu_capability_event(int count, | |
| struct thermal_genl_cpu_caps *caps) | |
| { | |
| struct param p = { .cpu_capabilities_count = count, .cpu_capabilities = caps }; | |
| return thermal_genl_send_event(THERMAL_GENL_EVENT_CPU_CAPABILITY_CHANGE, &p); | |
| } | |
| 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, | |
| void *data) | |
| { | |
| struct sk_buff *msg = data; | |
| if (nla_put_u32(msg, THERMAL_GENL_ATTR_TZ_ID, tz->id) || | |
| nla_put_string(msg, THERMAL_GENL_ATTR_TZ_NAME, tz->type)) | |
| return -EMSGSIZE; | |
| return 0; | |
| } | |
| static int thermal_genl_cmd_tz_get_id(struct param *p) | |
| { | |
| struct sk_buff *msg = p->msg; | |
| struct nlattr *start_tz; | |
| int ret; | |
| start_tz = nla_nest_start(msg, THERMAL_GENL_ATTR_TZ); | |
| if (!start_tz) | |
| return -EMSGSIZE; | |
| ret = for_each_thermal_zone(__thermal_genl_cmd_tz_get_id, msg); | |
| if (ret) | |
| goto out_cancel_nest; | |
| nla_nest_end(msg, start_tz); | |
| return 0; | |
| out_cancel_nest: | |
| nla_nest_cancel(msg, start_tz); | |
| return ret; | |
| } | |
| static int thermal_genl_cmd_tz_get_trip(struct param *p) | |
| { | |
| struct sk_buff *msg = p->msg; | |
| const struct thermal_trip_desc *td; | |
| struct nlattr *start_trip; | |
| int id; | |
| 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_TZ_TRIP); | |
| if (!start_trip) | |
| return -EMSGSIZE; | |
| guard(thermal_zone)(tz); | |
| for_each_trip_desc(tz, td) { | |
| const struct thermal_trip *trip = &td->trip; | |
| if (nla_put_u32(msg, THERMAL_GENL_ATTR_TZ_TRIP_ID, | |
| thermal_zone_trip_id(tz, trip)) || | |
| 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)) | |
| return -EMSGSIZE; | |
| } | |
| nla_nest_end(msg, start_trip); | |
| return 0; | |
| } | |
| static int thermal_genl_cmd_tz_get_temp(struct param *p) | |
| { | |
| struct sk_buff *msg = p->msg; | |
| int temp, ret, id; | |
| 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; | |
| ret = thermal_zone_get_temp(tz, &temp); | |
| if (ret) | |
| return ret; | |
| if (nla_put_u32(msg, THERMAL_GENL_ATTR_TZ_ID, id) || | |
| nla_put_u32(msg, THERMAL_GENL_ATTR_TZ_TEMP, temp)) | |
| return -EMSGSIZE; | |
| return 0; | |
| } | |
| static int thermal_genl_cmd_tz_get_gov(struct param *p) | |
| { | |
| struct sk_buff *msg = p->msg; | |
| int id; | |
| 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); | |
| if (nla_put_u32(msg, THERMAL_GENL_ATTR_TZ_ID, id) || | |
| nla_put_string(msg, THERMAL_GENL_ATTR_TZ_GOV_NAME, | |
| tz->governor->name)) | |
| return -EMSGSIZE; | |
| return 0; | |
| } | |
| static int __thermal_genl_cmd_cdev_get(struct thermal_cooling_device *cdev, | |
| void *data) | |
| { | |
| struct sk_buff *msg = data; | |
| if (nla_put_u32(msg, THERMAL_GENL_ATTR_CDEV_ID, cdev->id)) | |
| return -EMSGSIZE; | |
| if (nla_put_string(msg, THERMAL_GENL_ATTR_CDEV_NAME, cdev->type)) | |
| return -EMSGSIZE; | |
| return 0; | |
| } | |
| static int thermal_genl_cmd_cdev_get(struct param *p) | |
| { | |
| struct sk_buff *msg = p->msg; | |
| struct nlattr *start_cdev; | |
| int ret; | |
| start_cdev = nla_nest_start(msg, THERMAL_GENL_ATTR_CDEV); | |
| if (!start_cdev) | |
| return -EMSGSIZE; | |
| ret = for_each_thermal_cooling_device(__thermal_genl_cmd_cdev_get, msg); | |
| if (ret) | |
| goto out_cancel_nest; | |
| nla_nest_end(msg, start_cdev); | |
| return 0; | |
| out_cancel_nest: | |
| nla_nest_cancel(msg, start_cdev); | |
| 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, | |
| struct netlink_callback *cb) | |
| { | |
| struct param p = { .msg = skb }; | |
| const struct genl_dumpit_info *info = genl_dumpit_info(cb); | |
| int cmd = info->op.cmd; | |
| int ret; | |
| void *hdr; | |
| hdr = genlmsg_put(skb, 0, 0, &thermal_genl_family, 0, cmd); | |
| if (!hdr) | |
| return -EMSGSIZE; | |
| ret = cmd_cb[cmd](&p); | |
| if (ret) | |
| goto out_cancel_msg; | |
| genlmsg_end(skb, hdr); | |
| return 0; | |
| out_cancel_msg: | |
| genlmsg_cancel(skb, hdr); | |
| return ret; | |
| } | |
| static int thermal_genl_cmd_doit(struct sk_buff *skb, | |
| struct genl_info *info) | |
| { | |
| struct param p = { .attrs = info->attrs }; | |
| struct sk_buff *msg; | |
| void *hdr; | |
| int cmd = info->genlhdr->cmd; | |
| int ret = -EMSGSIZE; | |
| msg = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); | |
| if (!msg) | |
| return -ENOMEM; | |
| p.msg = msg; | |
| hdr = genlmsg_put_reply(msg, info, &thermal_genl_family, 0, cmd); | |
| if (!hdr) | |
| goto out_free_msg; | |
| ret = cmd_cb[cmd](&p); | |
| if (ret) | |
| goto out_cancel_msg; | |
| genlmsg_end(msg, hdr); | |
| return genlmsg_reply(msg, info); | |
| out_cancel_msg: | |
| genlmsg_cancel(msg, hdr); | |
| out_free_msg: | |
| nlmsg_free(msg); | |
| return ret; | |
| } | |
| static int thermal_genl_bind(int mcgrp) | |
| { | |
| struct thermal_genl_notify n = { .mcgrp = mcgrp }; | |
| if (WARN_ON_ONCE(mcgrp > THERMAL_GENL_MAX_GROUP)) | |
| return -EINVAL; | |
| blocking_notifier_call_chain(&thermal_genl_chain, THERMAL_NOTIFY_BIND, &n); | |
| return 0; | |
| } | |
| static void thermal_genl_unbind(int mcgrp) | |
| { | |
| struct thermal_genl_notify n = { .mcgrp = mcgrp }; | |
| if (WARN_ON_ONCE(mcgrp > THERMAL_GENL_MAX_GROUP)) | |
| return; | |
| blocking_notifier_call_chain(&thermal_genl_chain, THERMAL_NOTIFY_UNBIND, &n); | |
| } | |
| static const struct genl_small_ops thermal_genl_ops[] = { | |
| { | |
| .cmd = THERMAL_GENL_CMD_TZ_GET_ID, | |
| .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, | |
| .dumpit = thermal_genl_cmd_dumpit, | |
| }, | |
| { | |
| .cmd = THERMAL_GENL_CMD_TZ_GET_TRIP, | |
| .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, | |
| .doit = thermal_genl_cmd_doit, | |
| }, | |
| { | |
| .cmd = THERMAL_GENL_CMD_TZ_GET_TEMP, | |
| .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, | |
| .doit = thermal_genl_cmd_doit, | |
| }, | |
| { | |
| .cmd = THERMAL_GENL_CMD_TZ_GET_GOV, | |
| .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, | |
| .doit = thermal_genl_cmd_doit, | |
| }, | |
| { | |
| .cmd = THERMAL_GENL_CMD_CDEV_GET, | |
| .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 = { | |
| .hdrsize = 0, | |
| .name = THERMAL_GENL_FAMILY_NAME, | |
| .version = THERMAL_GENL_VERSION, | |
| .maxattr = THERMAL_GENL_ATTR_MAX, | |
| .policy = thermal_genl_policy, | |
| .bind = thermal_genl_bind, | |
| .unbind = thermal_genl_unbind, | |
| .small_ops = thermal_genl_ops, | |
| .n_small_ops = ARRAY_SIZE(thermal_genl_ops), | |
| .resv_start_op = __THERMAL_GENL_CMD_MAX, | |
| .mcgrps = thermal_genl_mcgrps, | |
| .n_mcgrps = ARRAY_SIZE(thermal_genl_mcgrps), | |
| }; | |
| int thermal_genl_register_notifier(struct notifier_block *nb) | |
| { | |
| return blocking_notifier_chain_register(&thermal_genl_chain, nb); | |
| } | |
| int thermal_genl_unregister_notifier(struct notifier_block *nb) | |
| { | |
| return blocking_notifier_chain_unregister(&thermal_genl_chain, nb); | |
| } | |
| int __init thermal_netlink_init(void) | |
| { | |
| return genl_register_family(&thermal_genl_family); | |
| } | |
| void __init thermal_netlink_exit(void) | |
| { | |
| genl_unregister_family(&thermal_genl_family); | |
| } |