727 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Copyright 2020 Linaro Limited4 *5 * Author: Daniel Lezcano <daniel.lezcano@linaro.org>6 *7 * Generic netlink for thermal management framework8 */9#include <linux/module.h>10#include <linux/notifier.h>11#include <linux/kernel.h>12#include <net/genetlink.h>13#include <uapi/linux/thermal.h>14 15#include "thermal_core.h"16 17static const struct genl_multicast_group thermal_genl_mcgrps[] = {18 [THERMAL_GENL_SAMPLING_GROUP] = { .name = THERMAL_GENL_SAMPLING_GROUP_NAME, },19 [THERMAL_GENL_EVENT_GROUP] = { .name = THERMAL_GENL_EVENT_GROUP_NAME, },20};21 22static const struct nla_policy thermal_genl_policy[THERMAL_GENL_ATTR_MAX + 1] = {23 /* Thermal zone */24 [THERMAL_GENL_ATTR_TZ] = { .type = NLA_NESTED },25 [THERMAL_GENL_ATTR_TZ_ID] = { .type = NLA_U32 },26 [THERMAL_GENL_ATTR_TZ_TEMP] = { .type = NLA_U32 },27 [THERMAL_GENL_ATTR_TZ_TRIP] = { .type = NLA_NESTED },28 [THERMAL_GENL_ATTR_TZ_TRIP_ID] = { .type = NLA_U32 },29 [THERMAL_GENL_ATTR_TZ_TRIP_TEMP] = { .type = NLA_U32 },30 [THERMAL_GENL_ATTR_TZ_TRIP_TYPE] = { .type = NLA_U32 },31 [THERMAL_GENL_ATTR_TZ_TRIP_HYST] = { .type = NLA_U32 },32 [THERMAL_GENL_ATTR_TZ_MODE] = { .type = NLA_U32 },33 [THERMAL_GENL_ATTR_TZ_CDEV_WEIGHT] = { .type = NLA_U32 },34 [THERMAL_GENL_ATTR_TZ_NAME] = { .type = NLA_STRING,35 .len = THERMAL_NAME_LENGTH },36 /* Governor(s) */37 [THERMAL_GENL_ATTR_TZ_GOV] = { .type = NLA_NESTED },38 [THERMAL_GENL_ATTR_TZ_GOV_NAME] = { .type = NLA_STRING,39 .len = THERMAL_NAME_LENGTH },40 /* Cooling devices */41 [THERMAL_GENL_ATTR_CDEV] = { .type = NLA_NESTED },42 [THERMAL_GENL_ATTR_CDEV_ID] = { .type = NLA_U32 },43 [THERMAL_GENL_ATTR_CDEV_CUR_STATE] = { .type = NLA_U32 },44 [THERMAL_GENL_ATTR_CDEV_MAX_STATE] = { .type = NLA_U32 },45 [THERMAL_GENL_ATTR_CDEV_NAME] = { .type = NLA_STRING,46 .len = THERMAL_NAME_LENGTH },47 /* CPU capabilities */48 [THERMAL_GENL_ATTR_CPU_CAPABILITY] = { .type = NLA_NESTED },49 [THERMAL_GENL_ATTR_CPU_CAPABILITY_ID] = { .type = NLA_U32 },50 [THERMAL_GENL_ATTR_CPU_CAPABILITY_PERFORMANCE] = { .type = NLA_U32 },51 [THERMAL_GENL_ATTR_CPU_CAPABILITY_EFFICIENCY] = { .type = NLA_U32 },52};53 54struct param {55 struct nlattr **attrs;56 struct sk_buff *msg;57 const char *name;58 int tz_id;59 int cdev_id;60 int trip_id;61 int trip_temp;62 int trip_type;63 int trip_hyst;64 int temp;65 int cdev_state;66 int cdev_max_state;67 struct thermal_genl_cpu_caps *cpu_capabilities;68 int cpu_capabilities_count;69};70 71typedef int (*cb_t)(struct param *);72 73static struct genl_family thermal_genl_family;74static BLOCKING_NOTIFIER_HEAD(thermal_genl_chain);75 76static int thermal_group_has_listeners(enum thermal_genl_multicast_groups group)77{78 return genl_has_listeners(&thermal_genl_family, &init_net, group);79}80 81/************************** Sampling encoding *******************************/82 83int thermal_genl_sampling_temp(int id, int temp)84{85 struct sk_buff *skb;86 void *hdr;87 88 if (!thermal_group_has_listeners(THERMAL_GENL_SAMPLING_GROUP))89 return 0;90 91 skb = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);92 if (!skb)93 return -ENOMEM;94 95 hdr = genlmsg_put(skb, 0, 0, &thermal_genl_family, 0,96 THERMAL_GENL_SAMPLING_TEMP);97 if (!hdr)98 goto out_free;99 100 if (nla_put_u32(skb, THERMAL_GENL_ATTR_TZ_ID, id))101 goto out_cancel;102 103 if (nla_put_u32(skb, THERMAL_GENL_ATTR_TZ_TEMP, temp))104 goto out_cancel;105 106 genlmsg_end(skb, hdr);107 108 genlmsg_multicast(&thermal_genl_family, skb, 0, THERMAL_GENL_SAMPLING_GROUP, GFP_KERNEL);109 110 return 0;111out_cancel:112 genlmsg_cancel(skb, hdr);113out_free:114 nlmsg_free(skb);115 116 return -EMSGSIZE;117}118 119/**************************** Event encoding *********************************/120 121static int thermal_genl_event_tz_create(struct param *p)122{123 if (nla_put_u32(p->msg, THERMAL_GENL_ATTR_TZ_ID, p->tz_id) ||124 nla_put_string(p->msg, THERMAL_GENL_ATTR_TZ_NAME, p->name))125 return -EMSGSIZE;126 127 return 0;128}129 130static int thermal_genl_event_tz(struct param *p)131{132 if (nla_put_u32(p->msg, THERMAL_GENL_ATTR_TZ_ID, p->tz_id))133 return -EMSGSIZE;134 135 return 0;136}137 138static int thermal_genl_event_tz_trip_up(struct param *p)139{140 if (nla_put_u32(p->msg, THERMAL_GENL_ATTR_TZ_ID, p->tz_id) ||141 nla_put_u32(p->msg, THERMAL_GENL_ATTR_TZ_TRIP_ID, p->trip_id) ||142 nla_put_u32(p->msg, THERMAL_GENL_ATTR_TZ_TEMP, p->temp))143 return -EMSGSIZE;144 145 return 0;146}147 148static int thermal_genl_event_tz_trip_change(struct param *p)149{150 if (nla_put_u32(p->msg, THERMAL_GENL_ATTR_TZ_ID, p->tz_id) ||151 nla_put_u32(p->msg, THERMAL_GENL_ATTR_TZ_TRIP_ID, p->trip_id) ||152 nla_put_u32(p->msg, THERMAL_GENL_ATTR_TZ_TRIP_TYPE, p->trip_type) ||153 nla_put_u32(p->msg, THERMAL_GENL_ATTR_TZ_TRIP_TEMP, p->trip_temp) ||154 nla_put_u32(p->msg, THERMAL_GENL_ATTR_TZ_TRIP_HYST, p->trip_hyst))155 return -EMSGSIZE;156 157 return 0;158}159 160static int thermal_genl_event_cdev_add(struct param *p)161{162 if (nla_put_string(p->msg, THERMAL_GENL_ATTR_CDEV_NAME,163 p->name) ||164 nla_put_u32(p->msg, THERMAL_GENL_ATTR_CDEV_ID,165 p->cdev_id) ||166 nla_put_u32(p->msg, THERMAL_GENL_ATTR_CDEV_MAX_STATE,167 p->cdev_max_state))168 return -EMSGSIZE;169 170 return 0;171}172 173static int thermal_genl_event_cdev_delete(struct param *p)174{175 if (nla_put_u32(p->msg, THERMAL_GENL_ATTR_CDEV_ID, p->cdev_id))176 return -EMSGSIZE;177 178 return 0;179}180 181static int thermal_genl_event_cdev_state_update(struct param *p)182{183 if (nla_put_u32(p->msg, THERMAL_GENL_ATTR_CDEV_ID,184 p->cdev_id) ||185 nla_put_u32(p->msg, THERMAL_GENL_ATTR_CDEV_CUR_STATE,186 p->cdev_state))187 return -EMSGSIZE;188 189 return 0;190}191 192static int thermal_genl_event_gov_change(struct param *p)193{194 if (nla_put_u32(p->msg, THERMAL_GENL_ATTR_TZ_ID, p->tz_id) ||195 nla_put_string(p->msg, THERMAL_GENL_ATTR_GOV_NAME, p->name))196 return -EMSGSIZE;197 198 return 0;199}200 201static int thermal_genl_event_cpu_capability_change(struct param *p)202{203 struct thermal_genl_cpu_caps *cpu_cap = p->cpu_capabilities;204 struct sk_buff *msg = p->msg;205 struct nlattr *start_cap;206 int i;207 208 start_cap = nla_nest_start(msg, THERMAL_GENL_ATTR_CPU_CAPABILITY);209 if (!start_cap)210 return -EMSGSIZE;211 212 for (i = 0; i < p->cpu_capabilities_count; ++i) {213 if (nla_put_u32(msg, THERMAL_GENL_ATTR_CPU_CAPABILITY_ID,214 cpu_cap->cpu))215 goto out_cancel_nest;216 217 if (nla_put_u32(msg, THERMAL_GENL_ATTR_CPU_CAPABILITY_PERFORMANCE,218 cpu_cap->performance))219 goto out_cancel_nest;220 221 if (nla_put_u32(msg, THERMAL_GENL_ATTR_CPU_CAPABILITY_EFFICIENCY,222 cpu_cap->efficiency))223 goto out_cancel_nest;224 225 ++cpu_cap;226 }227 228 nla_nest_end(msg, start_cap);229 230 return 0;231out_cancel_nest:232 nla_nest_cancel(msg, start_cap);233 234 return -EMSGSIZE;235}236 237int thermal_genl_event_tz_delete(struct param *p)238 __attribute__((alias("thermal_genl_event_tz")));239 240int thermal_genl_event_tz_enable(struct param *p)241 __attribute__((alias("thermal_genl_event_tz")));242 243int thermal_genl_event_tz_disable(struct param *p)244 __attribute__((alias("thermal_genl_event_tz")));245 246int thermal_genl_event_tz_trip_down(struct param *p)247 __attribute__((alias("thermal_genl_event_tz_trip_up")));248 249static cb_t event_cb[] = {250 [THERMAL_GENL_EVENT_TZ_CREATE] = thermal_genl_event_tz_create,251 [THERMAL_GENL_EVENT_TZ_DELETE] = thermal_genl_event_tz_delete,252 [THERMAL_GENL_EVENT_TZ_ENABLE] = thermal_genl_event_tz_enable,253 [THERMAL_GENL_EVENT_TZ_DISABLE] = thermal_genl_event_tz_disable,254 [THERMAL_GENL_EVENT_TZ_TRIP_UP] = thermal_genl_event_tz_trip_up,255 [THERMAL_GENL_EVENT_TZ_TRIP_DOWN] = thermal_genl_event_tz_trip_down,256 [THERMAL_GENL_EVENT_TZ_TRIP_CHANGE] = thermal_genl_event_tz_trip_change,257 [THERMAL_GENL_EVENT_CDEV_ADD] = thermal_genl_event_cdev_add,258 [THERMAL_GENL_EVENT_CDEV_DELETE] = thermal_genl_event_cdev_delete,259 [THERMAL_GENL_EVENT_CDEV_STATE_UPDATE] = thermal_genl_event_cdev_state_update,260 [THERMAL_GENL_EVENT_TZ_GOV_CHANGE] = thermal_genl_event_gov_change,261 [THERMAL_GENL_EVENT_CPU_CAPABILITY_CHANGE] = thermal_genl_event_cpu_capability_change,262};263 264/*265 * Generic netlink event encoding266 */267static int thermal_genl_send_event(enum thermal_genl_event event,268 struct param *p)269{270 struct sk_buff *msg;271 int ret = -EMSGSIZE;272 void *hdr;273 274 if (!thermal_group_has_listeners(THERMAL_GENL_EVENT_GROUP))275 return 0;276 277 msg = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);278 if (!msg)279 return -ENOMEM;280 p->msg = msg;281 282 hdr = genlmsg_put(msg, 0, 0, &thermal_genl_family, 0, event);283 if (!hdr)284 goto out_free_msg;285 286 ret = event_cb[event](p);287 if (ret)288 goto out_cancel_msg;289 290 genlmsg_end(msg, hdr);291 292 genlmsg_multicast(&thermal_genl_family, msg, 0, THERMAL_GENL_EVENT_GROUP, GFP_KERNEL);293 294 return 0;295 296out_cancel_msg:297 genlmsg_cancel(msg, hdr);298out_free_msg:299 nlmsg_free(msg);300 301 return ret;302}303 304int thermal_notify_tz_create(const struct thermal_zone_device *tz)305{306 struct param p = { .tz_id = tz->id, .name = tz->type };307 308 return thermal_genl_send_event(THERMAL_GENL_EVENT_TZ_CREATE, &p);309}310 311int thermal_notify_tz_delete(const struct thermal_zone_device *tz)312{313 struct param p = { .tz_id = tz->id };314 315 return thermal_genl_send_event(THERMAL_GENL_EVENT_TZ_DELETE, &p);316}317 318int thermal_notify_tz_enable(const struct thermal_zone_device *tz)319{320 struct param p = { .tz_id = tz->id };321 322 return thermal_genl_send_event(THERMAL_GENL_EVENT_TZ_ENABLE, &p);323}324 325int thermal_notify_tz_disable(const struct thermal_zone_device *tz)326{327 struct param p = { .tz_id = tz->id };328 329 return thermal_genl_send_event(THERMAL_GENL_EVENT_TZ_DISABLE, &p);330}331 332int thermal_notify_tz_trip_down(const struct thermal_zone_device *tz,333 const struct thermal_trip *trip)334{335 struct param p = { .tz_id = tz->id,336 .trip_id = thermal_zone_trip_id(tz, trip),337 .temp = tz->temperature };338 339 return thermal_genl_send_event(THERMAL_GENL_EVENT_TZ_TRIP_DOWN, &p);340}341 342int thermal_notify_tz_trip_up(const struct thermal_zone_device *tz,343 const struct thermal_trip *trip)344{345 struct param p = { .tz_id = tz->id,346 .trip_id = thermal_zone_trip_id(tz, trip),347 .temp = tz->temperature };348 349 return thermal_genl_send_event(THERMAL_GENL_EVENT_TZ_TRIP_UP, &p);350}351 352int thermal_notify_tz_trip_change(const struct thermal_zone_device *tz,353 const struct thermal_trip *trip)354{355 struct param p = { .tz_id = tz->id,356 .trip_id = thermal_zone_trip_id(tz, trip),357 .trip_type = trip->type,358 .trip_temp = trip->temperature,359 .trip_hyst = trip->hysteresis };360 361 return thermal_genl_send_event(THERMAL_GENL_EVENT_TZ_TRIP_CHANGE, &p);362}363 364int thermal_notify_cdev_state_update(const struct thermal_cooling_device *cdev,365 int state)366{367 struct param p = { .cdev_id = cdev->id, .cdev_state = state };368 369 return thermal_genl_send_event(THERMAL_GENL_EVENT_CDEV_STATE_UPDATE, &p);370}371 372int thermal_notify_cdev_add(const struct thermal_cooling_device *cdev)373{374 struct param p = { .cdev_id = cdev->id, .name = cdev->type,375 .cdev_max_state = cdev->max_state };376 377 return thermal_genl_send_event(THERMAL_GENL_EVENT_CDEV_ADD, &p);378}379 380int thermal_notify_cdev_delete(const struct thermal_cooling_device *cdev)381{382 struct param p = { .cdev_id = cdev->id };383 384 return thermal_genl_send_event(THERMAL_GENL_EVENT_CDEV_DELETE, &p);385}386 387int thermal_notify_tz_gov_change(const struct thermal_zone_device *tz,388 const char *name)389{390 struct param p = { .tz_id = tz->id, .name = name };391 392 return thermal_genl_send_event(THERMAL_GENL_EVENT_TZ_GOV_CHANGE, &p);393}394 395int thermal_genl_cpu_capability_event(int count,396 struct thermal_genl_cpu_caps *caps)397{398 struct param p = { .cpu_capabilities_count = count, .cpu_capabilities = caps };399 400 return thermal_genl_send_event(THERMAL_GENL_EVENT_CPU_CAPABILITY_CHANGE, &p);401}402EXPORT_SYMBOL_GPL(thermal_genl_cpu_capability_event);403 404/*************************** Command encoding ********************************/405 406static int __thermal_genl_cmd_tz_get_id(struct thermal_zone_device *tz,407 void *data)408{409 struct sk_buff *msg = data;410 411 if (nla_put_u32(msg, THERMAL_GENL_ATTR_TZ_ID, tz->id) ||412 nla_put_string(msg, THERMAL_GENL_ATTR_TZ_NAME, tz->type))413 return -EMSGSIZE;414 415 return 0;416}417 418static int thermal_genl_cmd_tz_get_id(struct param *p)419{420 struct sk_buff *msg = p->msg;421 struct nlattr *start_tz;422 int ret;423 424 start_tz = nla_nest_start(msg, THERMAL_GENL_ATTR_TZ);425 if (!start_tz)426 return -EMSGSIZE;427 428 ret = for_each_thermal_zone(__thermal_genl_cmd_tz_get_id, msg);429 if (ret)430 goto out_cancel_nest;431 432 nla_nest_end(msg, start_tz);433 434 return 0;435 436out_cancel_nest:437 nla_nest_cancel(msg, start_tz);438 439 return ret;440}441 442static int thermal_genl_cmd_tz_get_trip(struct param *p)443{444 struct sk_buff *msg = p->msg;445 const struct thermal_trip_desc *td;446 struct nlattr *start_trip;447 int id;448 449 if (!p->attrs[THERMAL_GENL_ATTR_TZ_ID])450 return -EINVAL;451 452 id = nla_get_u32(p->attrs[THERMAL_GENL_ATTR_TZ_ID]);453 454 CLASS(thermal_zone_get_by_id, tz)(id);455 if (!tz)456 return -EINVAL;457 458 start_trip = nla_nest_start(msg, THERMAL_GENL_ATTR_TZ_TRIP);459 if (!start_trip)460 return -EMSGSIZE;461 462 mutex_lock(&tz->lock);463 464 for_each_trip_desc(tz, td) {465 const struct thermal_trip *trip = &td->trip;466 467 if (nla_put_u32(msg, THERMAL_GENL_ATTR_TZ_TRIP_ID,468 thermal_zone_trip_id(tz, trip)) ||469 nla_put_u32(msg, THERMAL_GENL_ATTR_TZ_TRIP_TYPE, trip->type) ||470 nla_put_u32(msg, THERMAL_GENL_ATTR_TZ_TRIP_TEMP, trip->temperature) ||471 nla_put_u32(msg, THERMAL_GENL_ATTR_TZ_TRIP_HYST, trip->hysteresis))472 goto out_cancel_nest;473 }474 475 mutex_unlock(&tz->lock);476 477 nla_nest_end(msg, start_trip);478 479 return 0;480 481out_cancel_nest:482 mutex_unlock(&tz->lock);483 484 return -EMSGSIZE;485}486 487static int thermal_genl_cmd_tz_get_temp(struct param *p)488{489 struct sk_buff *msg = p->msg;490 int temp, ret, id;491 492 if (!p->attrs[THERMAL_GENL_ATTR_TZ_ID])493 return -EINVAL;494 495 id = nla_get_u32(p->attrs[THERMAL_GENL_ATTR_TZ_ID]);496 497 CLASS(thermal_zone_get_by_id, tz)(id);498 if (!tz)499 return -EINVAL;500 501 ret = thermal_zone_get_temp(tz, &temp);502 if (ret)503 return ret;504 505 if (nla_put_u32(msg, THERMAL_GENL_ATTR_TZ_ID, id) ||506 nla_put_u32(msg, THERMAL_GENL_ATTR_TZ_TEMP, temp))507 return -EMSGSIZE;508 509 return 0;510}511 512static int thermal_genl_cmd_tz_get_gov(struct param *p)513{514 struct sk_buff *msg = p->msg;515 int id, ret = 0;516 517 if (!p->attrs[THERMAL_GENL_ATTR_TZ_ID])518 return -EINVAL;519 520 id = nla_get_u32(p->attrs[THERMAL_GENL_ATTR_TZ_ID]);521 522 CLASS(thermal_zone_get_by_id, tz)(id);523 if (!tz)524 return -EINVAL;525 526 mutex_lock(&tz->lock);527 528 if (nla_put_u32(msg, THERMAL_GENL_ATTR_TZ_ID, id) ||529 nla_put_string(msg, THERMAL_GENL_ATTR_TZ_GOV_NAME,530 tz->governor->name))531 ret = -EMSGSIZE;532 533 mutex_unlock(&tz->lock);534 535 return ret;536}537 538static int __thermal_genl_cmd_cdev_get(struct thermal_cooling_device *cdev,539 void *data)540{541 struct sk_buff *msg = data;542 543 if (nla_put_u32(msg, THERMAL_GENL_ATTR_CDEV_ID, cdev->id))544 return -EMSGSIZE;545 546 if (nla_put_string(msg, THERMAL_GENL_ATTR_CDEV_NAME, cdev->type))547 return -EMSGSIZE;548 549 return 0;550}551 552static int thermal_genl_cmd_cdev_get(struct param *p)553{554 struct sk_buff *msg = p->msg;555 struct nlattr *start_cdev;556 int ret;557 558 start_cdev = nla_nest_start(msg, THERMAL_GENL_ATTR_CDEV);559 if (!start_cdev)560 return -EMSGSIZE;561 562 ret = for_each_thermal_cooling_device(__thermal_genl_cmd_cdev_get, msg);563 if (ret)564 goto out_cancel_nest;565 566 nla_nest_end(msg, start_cdev);567 568 return 0;569out_cancel_nest:570 nla_nest_cancel(msg, start_cdev);571 572 return ret;573}574 575static cb_t cmd_cb[] = {576 [THERMAL_GENL_CMD_TZ_GET_ID] = thermal_genl_cmd_tz_get_id,577 [THERMAL_GENL_CMD_TZ_GET_TRIP] = thermal_genl_cmd_tz_get_trip,578 [THERMAL_GENL_CMD_TZ_GET_TEMP] = thermal_genl_cmd_tz_get_temp,579 [THERMAL_GENL_CMD_TZ_GET_GOV] = thermal_genl_cmd_tz_get_gov,580 [THERMAL_GENL_CMD_CDEV_GET] = thermal_genl_cmd_cdev_get,581};582 583static int thermal_genl_cmd_dumpit(struct sk_buff *skb,584 struct netlink_callback *cb)585{586 struct param p = { .msg = skb };587 const struct genl_dumpit_info *info = genl_dumpit_info(cb);588 int cmd = info->op.cmd;589 int ret;590 void *hdr;591 592 hdr = genlmsg_put(skb, 0, 0, &thermal_genl_family, 0, cmd);593 if (!hdr)594 return -EMSGSIZE;595 596 ret = cmd_cb[cmd](&p);597 if (ret)598 goto out_cancel_msg;599 600 genlmsg_end(skb, hdr);601 602 return 0;603 604out_cancel_msg:605 genlmsg_cancel(skb, hdr);606 607 return ret;608}609 610static int thermal_genl_cmd_doit(struct sk_buff *skb,611 struct genl_info *info)612{613 struct param p = { .attrs = info->attrs };614 struct sk_buff *msg;615 void *hdr;616 int cmd = info->genlhdr->cmd;617 int ret = -EMSGSIZE;618 619 msg = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);620 if (!msg)621 return -ENOMEM;622 p.msg = msg;623 624 hdr = genlmsg_put_reply(msg, info, &thermal_genl_family, 0, cmd);625 if (!hdr)626 goto out_free_msg;627 628 ret = cmd_cb[cmd](&p);629 if (ret)630 goto out_cancel_msg;631 632 genlmsg_end(msg, hdr);633 634 return genlmsg_reply(msg, info);635 636out_cancel_msg:637 genlmsg_cancel(msg, hdr);638out_free_msg:639 nlmsg_free(msg);640 641 return ret;642}643 644static int thermal_genl_bind(int mcgrp)645{646 struct thermal_genl_notify n = { .mcgrp = mcgrp };647 648 if (WARN_ON_ONCE(mcgrp > THERMAL_GENL_MAX_GROUP))649 return -EINVAL;650 651 blocking_notifier_call_chain(&thermal_genl_chain, THERMAL_NOTIFY_BIND, &n);652 return 0;653}654 655static void thermal_genl_unbind(int mcgrp)656{657 struct thermal_genl_notify n = { .mcgrp = mcgrp };658 659 if (WARN_ON_ONCE(mcgrp > THERMAL_GENL_MAX_GROUP))660 return;661 662 blocking_notifier_call_chain(&thermal_genl_chain, THERMAL_NOTIFY_UNBIND, &n);663}664 665static const struct genl_small_ops thermal_genl_ops[] = {666 {667 .cmd = THERMAL_GENL_CMD_TZ_GET_ID,668 .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,669 .dumpit = thermal_genl_cmd_dumpit,670 },671 {672 .cmd = THERMAL_GENL_CMD_TZ_GET_TRIP,673 .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,674 .doit = thermal_genl_cmd_doit,675 },676 {677 .cmd = THERMAL_GENL_CMD_TZ_GET_TEMP,678 .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,679 .doit = thermal_genl_cmd_doit,680 },681 {682 .cmd = THERMAL_GENL_CMD_TZ_GET_GOV,683 .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,684 .doit = thermal_genl_cmd_doit,685 },686 {687 .cmd = THERMAL_GENL_CMD_CDEV_GET,688 .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,689 .dumpit = thermal_genl_cmd_dumpit,690 },691};692 693static struct genl_family thermal_genl_family __ro_after_init = {694 .hdrsize = 0,695 .name = THERMAL_GENL_FAMILY_NAME,696 .version = THERMAL_GENL_VERSION,697 .maxattr = THERMAL_GENL_ATTR_MAX,698 .policy = thermal_genl_policy,699 .bind = thermal_genl_bind,700 .unbind = thermal_genl_unbind,701 .small_ops = thermal_genl_ops,702 .n_small_ops = ARRAY_SIZE(thermal_genl_ops),703 .resv_start_op = THERMAL_GENL_CMD_CDEV_GET + 1,704 .mcgrps = thermal_genl_mcgrps,705 .n_mcgrps = ARRAY_SIZE(thermal_genl_mcgrps),706};707 708int thermal_genl_register_notifier(struct notifier_block *nb)709{710 return blocking_notifier_chain_register(&thermal_genl_chain, nb);711}712 713int thermal_genl_unregister_notifier(struct notifier_block *nb)714{715 return blocking_notifier_chain_unregister(&thermal_genl_chain, nb);716}717 718int __init thermal_netlink_init(void)719{720 return genl_register_family(&thermal_genl_family);721}722 723void __init thermal_netlink_exit(void)724{725 genl_unregister_family(&thermal_genl_family);726}727