1 // SPDX-License-Identifier: GPL-2.0
3 * Copyright 2020 Linaro Limited
5 * Author: Daniel Lezcano <daniel.lezcano@linaro.org>
7 * Generic netlink for thermal management framework
9 #include <linux/module.h>
10 #include <linux/kernel.h>
11 #include <net/genetlink.h>
12 #include <uapi/linux/thermal.h>
14 #include "thermal_core.h"
16 enum thermal_genl_multicast_groups
{
17 THERMAL_GENL_SAMPLING_GROUP
= 0,
18 THERMAL_GENL_EVENT_GROUP
= 1,
21 static const struct genl_multicast_group thermal_genl_mcgrps
[] = {
22 [THERMAL_GENL_SAMPLING_GROUP
] = { .name
= THERMAL_GENL_SAMPLING_GROUP_NAME
, },
23 [THERMAL_GENL_EVENT_GROUP
] = { .name
= THERMAL_GENL_EVENT_GROUP_NAME
, },
26 static const struct nla_policy thermal_genl_policy
[THERMAL_GENL_ATTR_MAX
+ 1] = {
28 [THERMAL_GENL_ATTR_TZ
] = { .type
= NLA_NESTED
},
29 [THERMAL_GENL_ATTR_TZ_ID
] = { .type
= NLA_U32
},
30 [THERMAL_GENL_ATTR_TZ_TEMP
] = { .type
= NLA_U32
},
31 [THERMAL_GENL_ATTR_TZ_TRIP
] = { .type
= NLA_NESTED
},
32 [THERMAL_GENL_ATTR_TZ_TRIP_ID
] = { .type
= NLA_U32
},
33 [THERMAL_GENL_ATTR_TZ_TRIP_TEMP
] = { .type
= NLA_U32
},
34 [THERMAL_GENL_ATTR_TZ_TRIP_TYPE
] = { .type
= NLA_U32
},
35 [THERMAL_GENL_ATTR_TZ_TRIP_HYST
] = { .type
= NLA_U32
},
36 [THERMAL_GENL_ATTR_TZ_MODE
] = { .type
= NLA_U32
},
37 [THERMAL_GENL_ATTR_TZ_CDEV_WEIGHT
] = { .type
= NLA_U32
},
38 [THERMAL_GENL_ATTR_TZ_NAME
] = { .type
= NLA_STRING
,
39 .len
= THERMAL_NAME_LENGTH
},
41 [THERMAL_GENL_ATTR_TZ_GOV
] = { .type
= NLA_NESTED
},
42 [THERMAL_GENL_ATTR_TZ_GOV_NAME
] = { .type
= NLA_STRING
,
43 .len
= THERMAL_NAME_LENGTH
},
45 [THERMAL_GENL_ATTR_CDEV
] = { .type
= NLA_NESTED
},
46 [THERMAL_GENL_ATTR_CDEV_ID
] = { .type
= NLA_U32
},
47 [THERMAL_GENL_ATTR_CDEV_CUR_STATE
] = { .type
= NLA_U32
},
48 [THERMAL_GENL_ATTR_CDEV_MAX_STATE
] = { .type
= NLA_U32
},
49 [THERMAL_GENL_ATTR_CDEV_NAME
] = { .type
= NLA_STRING
,
50 .len
= THERMAL_NAME_LENGTH
},
51 /* CPU capabilities */
52 [THERMAL_GENL_ATTR_CPU_CAPABILITY
] = { .type
= NLA_NESTED
},
53 [THERMAL_GENL_ATTR_CPU_CAPABILITY_ID
] = { .type
= NLA_U32
},
54 [THERMAL_GENL_ATTR_CPU_CAPABILITY_PERFORMANCE
] = { .type
= NLA_U32
},
55 [THERMAL_GENL_ATTR_CPU_CAPABILITY_EFFICIENCY
] = { .type
= NLA_U32
},
59 struct nlattr
**attrs
;
71 struct thermal_genl_cpu_caps
*cpu_capabilities
;
72 int cpu_capabilities_count
;
75 typedef int (*cb_t
)(struct param
*);
77 static struct genl_family thermal_gnl_family
;
79 static int thermal_group_has_listeners(enum thermal_genl_multicast_groups group
)
81 return genl_has_listeners(&thermal_gnl_family
, &init_net
, group
);
84 /************************** Sampling encoding *******************************/
86 int thermal_genl_sampling_temp(int id
, int temp
)
91 if (!thermal_group_has_listeners(THERMAL_GENL_SAMPLING_GROUP
))
94 skb
= genlmsg_new(NLMSG_GOODSIZE
, GFP_KERNEL
);
98 hdr
= genlmsg_put(skb
, 0, 0, &thermal_gnl_family
, 0,
99 THERMAL_GENL_SAMPLING_TEMP
);
103 if (nla_put_u32(skb
, THERMAL_GENL_ATTR_TZ_ID
, id
))
106 if (nla_put_u32(skb
, THERMAL_GENL_ATTR_TZ_TEMP
, temp
))
109 genlmsg_end(skb
, hdr
);
111 genlmsg_multicast(&thermal_gnl_family
, skb
, 0, THERMAL_GENL_SAMPLING_GROUP
, GFP_KERNEL
);
115 genlmsg_cancel(skb
, hdr
);
122 /**************************** Event encoding *********************************/
124 static int thermal_genl_event_tz_create(struct param
*p
)
126 if (nla_put_u32(p
->msg
, THERMAL_GENL_ATTR_TZ_ID
, p
->tz_id
) ||
127 nla_put_string(p
->msg
, THERMAL_GENL_ATTR_TZ_NAME
, p
->name
))
133 static int thermal_genl_event_tz(struct param
*p
)
135 if (nla_put_u32(p
->msg
, THERMAL_GENL_ATTR_TZ_ID
, p
->tz_id
))
141 static int thermal_genl_event_tz_trip_up(struct param
*p
)
143 if (nla_put_u32(p
->msg
, THERMAL_GENL_ATTR_TZ_ID
, p
->tz_id
) ||
144 nla_put_u32(p
->msg
, THERMAL_GENL_ATTR_TZ_TRIP_ID
, p
->trip_id
) ||
145 nla_put_u32(p
->msg
, THERMAL_GENL_ATTR_TZ_TEMP
, p
->temp
))
151 static int thermal_genl_event_tz_trip_change(struct param
*p
)
153 if (nla_put_u32(p
->msg
, THERMAL_GENL_ATTR_TZ_ID
, p
->tz_id
) ||
154 nla_put_u32(p
->msg
, THERMAL_GENL_ATTR_TZ_TRIP_ID
, p
->trip_id
) ||
155 nla_put_u32(p
->msg
, THERMAL_GENL_ATTR_TZ_TRIP_TYPE
, p
->trip_type
) ||
156 nla_put_u32(p
->msg
, THERMAL_GENL_ATTR_TZ_TRIP_TEMP
, p
->trip_temp
) ||
157 nla_put_u32(p
->msg
, THERMAL_GENL_ATTR_TZ_TRIP_HYST
, p
->trip_hyst
))
163 static int thermal_genl_event_cdev_add(struct param
*p
)
165 if (nla_put_string(p
->msg
, THERMAL_GENL_ATTR_CDEV_NAME
,
167 nla_put_u32(p
->msg
, THERMAL_GENL_ATTR_CDEV_ID
,
169 nla_put_u32(p
->msg
, THERMAL_GENL_ATTR_CDEV_MAX_STATE
,
176 static int thermal_genl_event_cdev_delete(struct param
*p
)
178 if (nla_put_u32(p
->msg
, THERMAL_GENL_ATTR_CDEV_ID
, p
->cdev_id
))
184 static int thermal_genl_event_cdev_state_update(struct param
*p
)
186 if (nla_put_u32(p
->msg
, THERMAL_GENL_ATTR_CDEV_ID
,
188 nla_put_u32(p
->msg
, THERMAL_GENL_ATTR_CDEV_CUR_STATE
,
195 static int thermal_genl_event_gov_change(struct param
*p
)
197 if (nla_put_u32(p
->msg
, THERMAL_GENL_ATTR_TZ_ID
, p
->tz_id
) ||
198 nla_put_string(p
->msg
, THERMAL_GENL_ATTR_GOV_NAME
, p
->name
))
204 static int thermal_genl_event_cpu_capability_change(struct param
*p
)
206 struct thermal_genl_cpu_caps
*cpu_cap
= p
->cpu_capabilities
;
207 struct sk_buff
*msg
= p
->msg
;
208 struct nlattr
*start_cap
;
211 start_cap
= nla_nest_start(msg
, THERMAL_GENL_ATTR_CPU_CAPABILITY
);
215 for (i
= 0; i
< p
->cpu_capabilities_count
; ++i
) {
216 if (nla_put_u32(msg
, THERMAL_GENL_ATTR_CPU_CAPABILITY_ID
,
218 goto out_cancel_nest
;
220 if (nla_put_u32(msg
, THERMAL_GENL_ATTR_CPU_CAPABILITY_PERFORMANCE
,
221 cpu_cap
->performance
))
222 goto out_cancel_nest
;
224 if (nla_put_u32(msg
, THERMAL_GENL_ATTR_CPU_CAPABILITY_EFFICIENCY
,
225 cpu_cap
->efficiency
))
226 goto out_cancel_nest
;
231 nla_nest_end(msg
, start_cap
);
235 nla_nest_cancel(msg
, start_cap
);
240 int thermal_genl_event_tz_delete(struct param
*p
)
241 __attribute__((alias("thermal_genl_event_tz")));
243 int thermal_genl_event_tz_enable(struct param
*p
)
244 __attribute__((alias("thermal_genl_event_tz")));
246 int thermal_genl_event_tz_disable(struct param
*p
)
247 __attribute__((alias("thermal_genl_event_tz")));
249 int thermal_genl_event_tz_trip_down(struct param
*p
)
250 __attribute__((alias("thermal_genl_event_tz_trip_up")));
252 static cb_t event_cb
[] = {
253 [THERMAL_GENL_EVENT_TZ_CREATE
] = thermal_genl_event_tz_create
,
254 [THERMAL_GENL_EVENT_TZ_DELETE
] = thermal_genl_event_tz_delete
,
255 [THERMAL_GENL_EVENT_TZ_ENABLE
] = thermal_genl_event_tz_enable
,
256 [THERMAL_GENL_EVENT_TZ_DISABLE
] = thermal_genl_event_tz_disable
,
257 [THERMAL_GENL_EVENT_TZ_TRIP_UP
] = thermal_genl_event_tz_trip_up
,
258 [THERMAL_GENL_EVENT_TZ_TRIP_DOWN
] = thermal_genl_event_tz_trip_down
,
259 [THERMAL_GENL_EVENT_TZ_TRIP_CHANGE
] = thermal_genl_event_tz_trip_change
,
260 [THERMAL_GENL_EVENT_CDEV_ADD
] = thermal_genl_event_cdev_add
,
261 [THERMAL_GENL_EVENT_CDEV_DELETE
] = thermal_genl_event_cdev_delete
,
262 [THERMAL_GENL_EVENT_CDEV_STATE_UPDATE
] = thermal_genl_event_cdev_state_update
,
263 [THERMAL_GENL_EVENT_TZ_GOV_CHANGE
] = thermal_genl_event_gov_change
,
264 [THERMAL_GENL_EVENT_CPU_CAPABILITY_CHANGE
] = thermal_genl_event_cpu_capability_change
,
268 * Generic netlink event encoding
270 static int thermal_genl_send_event(enum thermal_genl_event event
,
277 if (!thermal_group_has_listeners(THERMAL_GENL_EVENT_GROUP
))
280 msg
= genlmsg_new(NLMSG_GOODSIZE
, GFP_KERNEL
);
285 hdr
= genlmsg_put(msg
, 0, 0, &thermal_gnl_family
, 0, event
);
289 ret
= event_cb
[event
](p
);
293 genlmsg_end(msg
, hdr
);
295 genlmsg_multicast(&thermal_gnl_family
, msg
, 0, THERMAL_GENL_EVENT_GROUP
, GFP_KERNEL
);
300 genlmsg_cancel(msg
, hdr
);
307 int thermal_notify_tz_create(const struct thermal_zone_device
*tz
)
309 struct param p
= { .tz_id
= tz
->id
, .name
= tz
->type
};
311 return thermal_genl_send_event(THERMAL_GENL_EVENT_TZ_CREATE
, &p
);
314 int thermal_notify_tz_delete(const struct thermal_zone_device
*tz
)
316 struct param p
= { .tz_id
= tz
->id
};
318 return thermal_genl_send_event(THERMAL_GENL_EVENT_TZ_DELETE
, &p
);
321 int thermal_notify_tz_enable(const struct thermal_zone_device
*tz
)
323 struct param p
= { .tz_id
= tz
->id
};
325 return thermal_genl_send_event(THERMAL_GENL_EVENT_TZ_ENABLE
, &p
);
328 int thermal_notify_tz_disable(const struct thermal_zone_device
*tz
)
330 struct param p
= { .tz_id
= tz
->id
};
332 return thermal_genl_send_event(THERMAL_GENL_EVENT_TZ_DISABLE
, &p
);
335 int thermal_notify_tz_trip_down(const struct thermal_zone_device
*tz
,
336 const struct thermal_trip
*trip
)
338 struct param p
= { .tz_id
= tz
->id
,
339 .trip_id
= thermal_zone_trip_id(tz
, trip
),
340 .temp
= tz
->temperature
};
342 return thermal_genl_send_event(THERMAL_GENL_EVENT_TZ_TRIP_DOWN
, &p
);
345 int thermal_notify_tz_trip_up(const struct thermal_zone_device
*tz
,
346 const struct thermal_trip
*trip
)
348 struct param p
= { .tz_id
= tz
->id
,
349 .trip_id
= thermal_zone_trip_id(tz
, trip
),
350 .temp
= tz
->temperature
};
352 return thermal_genl_send_event(THERMAL_GENL_EVENT_TZ_TRIP_UP
, &p
);
355 int thermal_notify_tz_trip_change(const struct thermal_zone_device
*tz
,
356 const struct thermal_trip
*trip
)
358 struct param p
= { .tz_id
= tz
->id
,
359 .trip_id
= thermal_zone_trip_id(tz
, trip
),
360 .trip_type
= trip
->type
,
361 .trip_temp
= trip
->temperature
,
362 .trip_hyst
= trip
->hysteresis
};
364 return thermal_genl_send_event(THERMAL_GENL_EVENT_TZ_TRIP_CHANGE
, &p
);
367 int thermal_notify_cdev_state_update(int cdev_id
, int cdev_state
)
369 struct param p
= { .cdev_id
= cdev_id
, .cdev_state
= cdev_state
};
371 return thermal_genl_send_event(THERMAL_GENL_EVENT_CDEV_STATE_UPDATE
, &p
);
374 int thermal_notify_cdev_add(int cdev_id
, const char *name
, int cdev_max_state
)
376 struct param p
= { .cdev_id
= cdev_id
, .name
= name
,
377 .cdev_max_state
= cdev_max_state
};
379 return thermal_genl_send_event(THERMAL_GENL_EVENT_CDEV_ADD
, &p
);
382 int thermal_notify_cdev_delete(int cdev_id
)
384 struct param p
= { .cdev_id
= cdev_id
};
386 return thermal_genl_send_event(THERMAL_GENL_EVENT_CDEV_DELETE
, &p
);
389 int thermal_notify_tz_gov_change(const struct thermal_zone_device
*tz
,
392 struct param p
= { .tz_id
= tz
->id
, .name
= name
};
394 return thermal_genl_send_event(THERMAL_GENL_EVENT_TZ_GOV_CHANGE
, &p
);
397 int thermal_genl_cpu_capability_event(int count
,
398 struct thermal_genl_cpu_caps
*caps
)
400 struct param p
= { .cpu_capabilities_count
= count
, .cpu_capabilities
= caps
};
402 return thermal_genl_send_event(THERMAL_GENL_EVENT_CPU_CAPABILITY_CHANGE
, &p
);
404 EXPORT_SYMBOL_GPL(thermal_genl_cpu_capability_event
);
406 /*************************** Command encoding ********************************/
408 static int __thermal_genl_cmd_tz_get_id(struct thermal_zone_device
*tz
,
411 struct sk_buff
*msg
= data
;
413 if (nla_put_u32(msg
, THERMAL_GENL_ATTR_TZ_ID
, tz
->id
) ||
414 nla_put_string(msg
, THERMAL_GENL_ATTR_TZ_NAME
, tz
->type
))
420 static int thermal_genl_cmd_tz_get_id(struct param
*p
)
422 struct sk_buff
*msg
= p
->msg
;
423 struct nlattr
*start_tz
;
426 start_tz
= nla_nest_start(msg
, THERMAL_GENL_ATTR_TZ
);
430 ret
= for_each_thermal_zone(__thermal_genl_cmd_tz_get_id
, msg
);
432 goto out_cancel_nest
;
434 nla_nest_end(msg
, start_tz
);
439 nla_nest_cancel(msg
, start_tz
);
444 static int thermal_genl_cmd_tz_get_trip(struct param
*p
)
446 struct sk_buff
*msg
= p
->msg
;
447 const struct thermal_trip
*trip
;
448 struct thermal_zone_device
*tz
;
449 struct nlattr
*start_trip
;
452 if (!p
->attrs
[THERMAL_GENL_ATTR_TZ_ID
])
455 id
= nla_get_u32(p
->attrs
[THERMAL_GENL_ATTR_TZ_ID
]);
457 tz
= thermal_zone_get_by_id(id
);
461 start_trip
= nla_nest_start(msg
, THERMAL_GENL_ATTR_TZ_TRIP
);
465 mutex_lock(&tz
->lock
);
467 for_each_trip(tz
, trip
) {
468 if (nla_put_u32(msg
, THERMAL_GENL_ATTR_TZ_TRIP_ID
,
469 thermal_zone_trip_id(tz
, trip
)) ||
470 nla_put_u32(msg
, THERMAL_GENL_ATTR_TZ_TRIP_TYPE
, trip
->type
) ||
471 nla_put_u32(msg
, THERMAL_GENL_ATTR_TZ_TRIP_TEMP
, trip
->temperature
) ||
472 nla_put_u32(msg
, THERMAL_GENL_ATTR_TZ_TRIP_HYST
, trip
->hysteresis
))
473 goto out_cancel_nest
;
476 mutex_unlock(&tz
->lock
);
478 nla_nest_end(msg
, start_trip
);
483 mutex_unlock(&tz
->lock
);
488 static int thermal_genl_cmd_tz_get_temp(struct param
*p
)
490 struct sk_buff
*msg
= p
->msg
;
491 struct thermal_zone_device
*tz
;
494 if (!p
->attrs
[THERMAL_GENL_ATTR_TZ_ID
])
497 id
= nla_get_u32(p
->attrs
[THERMAL_GENL_ATTR_TZ_ID
]);
499 tz
= thermal_zone_get_by_id(id
);
503 ret
= thermal_zone_get_temp(tz
, &temp
);
507 if (nla_put_u32(msg
, THERMAL_GENL_ATTR_TZ_ID
, id
) ||
508 nla_put_u32(msg
, THERMAL_GENL_ATTR_TZ_TEMP
, temp
))
514 static int thermal_genl_cmd_tz_get_gov(struct param
*p
)
516 struct sk_buff
*msg
= p
->msg
;
517 struct thermal_zone_device
*tz
;
520 if (!p
->attrs
[THERMAL_GENL_ATTR_TZ_ID
])
523 id
= nla_get_u32(p
->attrs
[THERMAL_GENL_ATTR_TZ_ID
]);
525 tz
= thermal_zone_get_by_id(id
);
529 mutex_lock(&tz
->lock
);
531 if (nla_put_u32(msg
, THERMAL_GENL_ATTR_TZ_ID
, id
) ||
532 nla_put_string(msg
, THERMAL_GENL_ATTR_TZ_GOV_NAME
,
536 mutex_unlock(&tz
->lock
);
541 static int __thermal_genl_cmd_cdev_get(struct thermal_cooling_device
*cdev
,
544 struct sk_buff
*msg
= data
;
546 if (nla_put_u32(msg
, THERMAL_GENL_ATTR_CDEV_ID
, cdev
->id
))
549 if (nla_put_string(msg
, THERMAL_GENL_ATTR_CDEV_NAME
, cdev
->type
))
555 static int thermal_genl_cmd_cdev_get(struct param
*p
)
557 struct sk_buff
*msg
= p
->msg
;
558 struct nlattr
*start_cdev
;
561 start_cdev
= nla_nest_start(msg
, THERMAL_GENL_ATTR_CDEV
);
565 ret
= for_each_thermal_cooling_device(__thermal_genl_cmd_cdev_get
, msg
);
567 goto out_cancel_nest
;
569 nla_nest_end(msg
, start_cdev
);
573 nla_nest_cancel(msg
, start_cdev
);
578 static cb_t cmd_cb
[] = {
579 [THERMAL_GENL_CMD_TZ_GET_ID
] = thermal_genl_cmd_tz_get_id
,
580 [THERMAL_GENL_CMD_TZ_GET_TRIP
] = thermal_genl_cmd_tz_get_trip
,
581 [THERMAL_GENL_CMD_TZ_GET_TEMP
] = thermal_genl_cmd_tz_get_temp
,
582 [THERMAL_GENL_CMD_TZ_GET_GOV
] = thermal_genl_cmd_tz_get_gov
,
583 [THERMAL_GENL_CMD_CDEV_GET
] = thermal_genl_cmd_cdev_get
,
586 static int thermal_genl_cmd_dumpit(struct sk_buff
*skb
,
587 struct netlink_callback
*cb
)
589 struct param p
= { .msg
= skb
};
590 const struct genl_dumpit_info
*info
= genl_dumpit_info(cb
);
591 int cmd
= info
->op
.cmd
;
595 hdr
= genlmsg_put(skb
, 0, 0, &thermal_gnl_family
, 0, cmd
);
599 ret
= cmd_cb
[cmd
](&p
);
603 genlmsg_end(skb
, hdr
);
608 genlmsg_cancel(skb
, hdr
);
613 static int thermal_genl_cmd_doit(struct sk_buff
*skb
,
614 struct genl_info
*info
)
616 struct param p
= { .attrs
= info
->attrs
};
619 int cmd
= info
->genlhdr
->cmd
;
622 msg
= genlmsg_new(NLMSG_GOODSIZE
, GFP_KERNEL
);
627 hdr
= genlmsg_put_reply(msg
, info
, &thermal_gnl_family
, 0, cmd
);
631 ret
= cmd_cb
[cmd
](&p
);
635 genlmsg_end(msg
, hdr
);
637 return genlmsg_reply(msg
, info
);
640 genlmsg_cancel(msg
, hdr
);
647 static const struct genl_small_ops thermal_genl_ops
[] = {
649 .cmd
= THERMAL_GENL_CMD_TZ_GET_ID
,
650 .validate
= GENL_DONT_VALIDATE_STRICT
| GENL_DONT_VALIDATE_DUMP
,
651 .dumpit
= thermal_genl_cmd_dumpit
,
654 .cmd
= THERMAL_GENL_CMD_TZ_GET_TRIP
,
655 .validate
= GENL_DONT_VALIDATE_STRICT
| GENL_DONT_VALIDATE_DUMP
,
656 .doit
= thermal_genl_cmd_doit
,
659 .cmd
= THERMAL_GENL_CMD_TZ_GET_TEMP
,
660 .validate
= GENL_DONT_VALIDATE_STRICT
| GENL_DONT_VALIDATE_DUMP
,
661 .doit
= thermal_genl_cmd_doit
,
664 .cmd
= THERMAL_GENL_CMD_TZ_GET_GOV
,
665 .validate
= GENL_DONT_VALIDATE_STRICT
| GENL_DONT_VALIDATE_DUMP
,
666 .doit
= thermal_genl_cmd_doit
,
669 .cmd
= THERMAL_GENL_CMD_CDEV_GET
,
670 .validate
= GENL_DONT_VALIDATE_STRICT
| GENL_DONT_VALIDATE_DUMP
,
671 .dumpit
= thermal_genl_cmd_dumpit
,
675 static struct genl_family thermal_gnl_family __ro_after_init
= {
677 .name
= THERMAL_GENL_FAMILY_NAME
,
678 .version
= THERMAL_GENL_VERSION
,
679 .maxattr
= THERMAL_GENL_ATTR_MAX
,
680 .policy
= thermal_genl_policy
,
681 .small_ops
= thermal_genl_ops
,
682 .n_small_ops
= ARRAY_SIZE(thermal_genl_ops
),
683 .resv_start_op
= THERMAL_GENL_CMD_CDEV_GET
+ 1,
684 .mcgrps
= thermal_genl_mcgrps
,
685 .n_mcgrps
= ARRAY_SIZE(thermal_genl_mcgrps
),
688 int __init
thermal_netlink_init(void)
690 return genl_register_family(&thermal_gnl_family
);
693 void __init
thermal_netlink_exit(void)
695 genl_unregister_family(&thermal_gnl_family
);