xref: /linux/tools/lib/thermal/commands.c (revision a26267248628760d02e7d03534afc59dae0c9b40)
1 // SPDX-License-Identifier: LGPL-2.1+
2 // Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org>
3 #define _GNU_SOURCE
4 #include <errno.h>
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <unistd.h>
8 #include <limits.h>
9 
10 #include <thermal.h>
11 #include "thermal_nl.h"
12 
13 static struct nla_policy thermal_genl_policy[THERMAL_GENL_ATTR_MAX + 1] = {
14 	/* Thermal zone */
15 	[THERMAL_GENL_ATTR_TZ]                  = { .type = NLA_NESTED },
16 	[THERMAL_GENL_ATTR_TZ_ID]               = { .type = NLA_U32 },
17 	[THERMAL_GENL_ATTR_TZ_TEMP]             = { .type = NLA_U32 },
18 	[THERMAL_GENL_ATTR_TZ_TRIP]             = { .type = NLA_NESTED },
19 	[THERMAL_GENL_ATTR_TZ_TRIP_ID]          = { .type = NLA_U32 },
20 	[THERMAL_GENL_ATTR_TZ_TRIP_TEMP]        = { .type = NLA_U32 },
21 	[THERMAL_GENL_ATTR_TZ_TRIP_TYPE]        = { .type = NLA_U32 },
22 	[THERMAL_GENL_ATTR_TZ_TRIP_HYST]        = { .type = NLA_U32 },
23 	[THERMAL_GENL_ATTR_TZ_MODE]             = { .type = NLA_U32 },
24 	[THERMAL_GENL_ATTR_TZ_CDEV_WEIGHT]      = { .type = NLA_U32 },
25 	[THERMAL_GENL_ATTR_TZ_NAME]             = { .type = NLA_STRING },
26 
27 	/* Governor(s) */
28 	[THERMAL_GENL_ATTR_TZ_GOV]              = { .type = NLA_NESTED },
29 	[THERMAL_GENL_ATTR_TZ_GOV_NAME]         = { .type = NLA_STRING },
30 
31 	/* Cooling devices */
32 	[THERMAL_GENL_ATTR_CDEV]                = { .type = NLA_NESTED },
33 	[THERMAL_GENL_ATTR_CDEV_ID]             = { .type = NLA_U32 },
34 	[THERMAL_GENL_ATTR_CDEV_CUR_STATE]      = { .type = NLA_U32 },
35 	[THERMAL_GENL_ATTR_CDEV_MAX_STATE]      = { .type = NLA_U32 },
36 	[THERMAL_GENL_ATTR_CDEV_NAME]           = { .type = NLA_STRING },
37 
38         /* Thresholds */
39         [THERMAL_GENL_ATTR_THRESHOLD]      	= { .type = NLA_NESTED },
40         [THERMAL_GENL_ATTR_THRESHOLD_TEMP]      = { .type = NLA_U32 },
41         [THERMAL_GENL_ATTR_THRESHOLD_DIRECTION] = { .type = NLA_U32 },
42 };
43 
44 static int parse_tz_get(struct genl_info *info, struct thermal_zone **tz)
45 {
46 	struct nlattr *attr;
47 	struct thermal_zone *__tz = NULL;
48 	size_t size = 0;
49 	int rem;
50 
51 	nla_for_each_nested(attr, info->attrs[THERMAL_GENL_ATTR_TZ], rem) {
52 
53 		if (nla_type(attr) == THERMAL_GENL_ATTR_TZ_ID) {
54 
55 			size++;
56 
57 			__tz = realloc(__tz, sizeof(*__tz) * (size + 2));
58 			if (!__tz)
59 				return THERMAL_ERROR;
60 
61 			__tz[size - 1].id = nla_get_u32(attr);
62 		}
63 
64 
65 		if (nla_type(attr) == THERMAL_GENL_ATTR_TZ_NAME)
66 			nla_strlcpy(__tz[size - 1].name, attr,
67 				    THERMAL_NAME_LENGTH);
68 	}
69 
70 	if (__tz)
71 		__tz[size].id = -1;
72 
73 	*tz = __tz;
74 
75 	return THERMAL_SUCCESS;
76 }
77 
78 static int parse_cdev_get(struct genl_info *info, struct thermal_cdev **cdev)
79 {
80 	struct nlattr *attr;
81 	struct thermal_cdev *__cdev = NULL;
82 	size_t size = 0;
83 	int rem;
84 
85 	nla_for_each_nested(attr, info->attrs[THERMAL_GENL_ATTR_CDEV], rem) {
86 
87 		if (nla_type(attr) == THERMAL_GENL_ATTR_CDEV_ID) {
88 
89 			size++;
90 
91 			__cdev = realloc(__cdev, sizeof(*__cdev) * (size + 2));
92 			if (!__cdev)
93 				return THERMAL_ERROR;
94 
95 			__cdev[size - 1].id = nla_get_u32(attr);
96 		}
97 
98 		if (nla_type(attr) == THERMAL_GENL_ATTR_CDEV_NAME) {
99 			nla_strlcpy(__cdev[size - 1].name, attr,
100 				    THERMAL_NAME_LENGTH);
101 		}
102 
103 		if (nla_type(attr) == THERMAL_GENL_ATTR_CDEV_CUR_STATE)
104 			__cdev[size - 1].cur_state = nla_get_u32(attr);
105 
106 		if (nla_type(attr) == THERMAL_GENL_ATTR_CDEV_MAX_STATE)
107 			__cdev[size - 1].max_state = nla_get_u32(attr);
108 	}
109 
110 	if (__cdev)
111 		__cdev[size].id = -1;
112 
113 	*cdev = __cdev;
114 
115 	return THERMAL_SUCCESS;
116 }
117 
118 static int parse_tz_get_trip(struct genl_info *info, struct thermal_zone *tz)
119 {
120 	struct nlattr *attr;
121 	struct thermal_trip *__tt = NULL;
122 	size_t size = 0;
123 	int rem;
124 
125 	nla_for_each_nested(attr, info->attrs[THERMAL_GENL_ATTR_TZ_TRIP], rem) {
126 
127 		if (nla_type(attr) == THERMAL_GENL_ATTR_TZ_TRIP_ID) {
128 
129 			size++;
130 
131 			__tt = realloc(__tt, sizeof(*__tt) * (size + 2));
132 			if (!__tt)
133 				return THERMAL_ERROR;
134 
135 			__tt[size - 1].id = nla_get_u32(attr);
136 		}
137 
138 		if (nla_type(attr) == THERMAL_GENL_ATTR_TZ_TRIP_TYPE)
139 			__tt[size - 1].type = nla_get_u32(attr);
140 
141 		if (nla_type(attr) == THERMAL_GENL_ATTR_TZ_TRIP_TEMP)
142 			__tt[size - 1].temp = nla_get_u32(attr);
143 
144 		if (nla_type(attr) == THERMAL_GENL_ATTR_TZ_TRIP_HYST)
145 			__tt[size - 1].hyst = nla_get_u32(attr);
146 	}
147 
148 	if (__tt)
149 		__tt[size].id = -1;
150 
151 	tz->trip = __tt;
152 
153 	return THERMAL_SUCCESS;
154 }
155 
156 static int parse_tz_get_temp(struct genl_info *info, struct thermal_zone *tz)
157 {
158 	int id = -1;
159 
160 	if (info->attrs[THERMAL_GENL_ATTR_TZ_ID])
161 		id = nla_get_u32(info->attrs[THERMAL_GENL_ATTR_TZ_ID]);
162 
163 	if (tz->id != id)
164 		return THERMAL_ERROR;
165 
166 	if (info->attrs[THERMAL_GENL_ATTR_TZ_TEMP])
167 		tz->temp = nla_get_u32(info->attrs[THERMAL_GENL_ATTR_TZ_TEMP]);
168 
169 	return THERMAL_SUCCESS;
170 }
171 
172 static int parse_tz_get_gov(struct genl_info *info, struct thermal_zone *tz)
173 {
174 	int id = -1;
175 
176 	if (info->attrs[THERMAL_GENL_ATTR_TZ_ID])
177 		id = nla_get_u32(info->attrs[THERMAL_GENL_ATTR_TZ_ID]);
178 
179 	if (tz->id != id)
180 		return THERMAL_ERROR;
181 
182 	if (info->attrs[THERMAL_GENL_ATTR_TZ_GOV_NAME]) {
183 		nla_strlcpy(tz->governor,
184 			    info->attrs[THERMAL_GENL_ATTR_TZ_GOV_NAME],
185 			    THERMAL_NAME_LENGTH);
186 	}
187 
188 	return THERMAL_SUCCESS;
189 }
190 
191 static int parse_threshold_get(struct genl_info *info, struct thermal_zone *tz)
192 {
193 	struct nlattr *attr;
194 	struct thermal_threshold *__tt = NULL;
195 	size_t size = 0;
196 	int rem;
197 
198 	/*
199 	 * The size contains the size of the array and we want to
200 	 * access the last element, size - 1.
201 	 *
202 	 * The variable size is initialized to zero but it will be
203 	 * then incremented by the first if() statement. The message
204 	 * attributes are ordered, so the first if() statement will be
205 	 * always called before the second one. If it happens that is
206 	 * not the case, then it is a kernel bug.
207 	 */
208 	nla_for_each_nested(attr, info->attrs[THERMAL_GENL_ATTR_THRESHOLD], rem) {
209 
210 		if (nla_type(attr) == THERMAL_GENL_ATTR_THRESHOLD_TEMP) {
211 
212 			size++;
213 
214 			__tt = realloc(__tt, sizeof(*__tt) * (size + 2));
215 			if (!__tt)
216 				return THERMAL_ERROR;
217 
218 			__tt[size - 1].temperature = nla_get_u32(attr);
219 		}
220 
221 		if (nla_type(attr) == THERMAL_GENL_ATTR_THRESHOLD_DIRECTION)
222 			__tt[size - 1].direction = nla_get_u32(attr);
223 	}
224 
225 	if (__tt)
226 		__tt[size].temperature = INT_MAX;
227 
228 	tz->thresholds = __tt;
229 
230 	return THERMAL_SUCCESS;
231 }
232 
233 static int handle_netlink(struct nl_cache_ops *unused,
234 			  struct genl_cmd *cmd,
235 			  struct genl_info *info, void *arg)
236 {
237 	int ret;
238 
239 	switch (cmd->c_id) {
240 
241 	case THERMAL_GENL_CMD_TZ_GET_ID:
242 		ret = parse_tz_get(info, arg);
243 		break;
244 
245 	case THERMAL_GENL_CMD_CDEV_GET:
246 		ret = parse_cdev_get(info, arg);
247 		break;
248 
249 	case THERMAL_GENL_CMD_TZ_GET_TEMP:
250 		ret = parse_tz_get_temp(info, arg);
251 		break;
252 
253 	case THERMAL_GENL_CMD_TZ_GET_TRIP:
254 		ret = parse_tz_get_trip(info, arg);
255 		break;
256 
257 	case THERMAL_GENL_CMD_TZ_GET_GOV:
258 		ret = parse_tz_get_gov(info, arg);
259 		break;
260 
261 	case THERMAL_GENL_CMD_THRESHOLD_GET:
262 		ret = parse_threshold_get(info, arg);
263 		break;
264 
265 	default:
266 		return THERMAL_ERROR;
267 	}
268 
269 	return ret;
270 }
271 
272 static struct genl_cmd thermal_cmds[] = {
273 	{
274 		.c_id		= THERMAL_GENL_CMD_TZ_GET_ID,
275 		.c_name		= (char *)"List thermal zones",
276 		.c_msg_parser	= handle_netlink,
277 		.c_maxattr	= THERMAL_GENL_ATTR_MAX,
278 		.c_attr_policy	= thermal_genl_policy,
279 	},
280 	{
281 		.c_id		= THERMAL_GENL_CMD_TZ_GET_GOV,
282 		.c_name		= (char *)"Get governor",
283 		.c_msg_parser	= handle_netlink,
284 		.c_maxattr	= THERMAL_GENL_ATTR_MAX,
285 		.c_attr_policy	= thermal_genl_policy,
286 	},
287 	{
288 		.c_id		= THERMAL_GENL_CMD_TZ_GET_TEMP,
289 		.c_name		= (char *)"Get thermal zone temperature",
290 		.c_msg_parser	= handle_netlink,
291 		.c_maxattr	= THERMAL_GENL_ATTR_MAX,
292 		.c_attr_policy	= thermal_genl_policy,
293 	},
294 	{
295 		.c_id		= THERMAL_GENL_CMD_TZ_GET_TRIP,
296 		.c_name		= (char *)"Get thermal zone trip points",
297 		.c_msg_parser	= handle_netlink,
298 		.c_maxattr	= THERMAL_GENL_ATTR_MAX,
299 		.c_attr_policy	= thermal_genl_policy,
300 	},
301 	{
302 		.c_id		= THERMAL_GENL_CMD_CDEV_GET,
303 		.c_name		= (char *)"Get cooling devices",
304 		.c_msg_parser	= handle_netlink,
305 		.c_maxattr	= THERMAL_GENL_ATTR_MAX,
306 		.c_attr_policy	= thermal_genl_policy,
307 	},
308         {
309                 .c_id           = THERMAL_GENL_CMD_THRESHOLD_GET,
310                 .c_name         = (char *)"Get thresholds list",
311                 .c_msg_parser   = handle_netlink,
312                 .c_maxattr      = THERMAL_GENL_ATTR_MAX,
313                 .c_attr_policy  = thermal_genl_policy,
314         },
315         {
316                 .c_id           = THERMAL_GENL_CMD_THRESHOLD_ADD,
317                 .c_name         = (char *)"Add a threshold",
318                 .c_msg_parser   = handle_netlink,
319                 .c_maxattr      = THERMAL_GENL_ATTR_MAX,
320                 .c_attr_policy  = thermal_genl_policy,
321         },
322         {
323                 .c_id           = THERMAL_GENL_CMD_THRESHOLD_DELETE,
324                 .c_name         = (char *)"Delete a threshold",
325                 .c_msg_parser   = handle_netlink,
326                 .c_maxattr      = THERMAL_GENL_ATTR_MAX,
327                 .c_attr_policy  = thermal_genl_policy,
328         },
329         {
330                 .c_id           = THERMAL_GENL_CMD_THRESHOLD_FLUSH,
331                 .c_name         = (char *)"Flush the thresholds",
332                 .c_msg_parser   = handle_netlink,
333                 .c_maxattr      = THERMAL_GENL_ATTR_MAX,
334                 .c_attr_policy  = thermal_genl_policy,
335         },
336 };
337 
338 static struct genl_ops thermal_cmd_ops = {
339 	.o_name		= (char *)"thermal",
340 	.o_cmds		= thermal_cmds,
341 	.o_ncmds	= ARRAY_SIZE(thermal_cmds),
342 };
343 
344 struct cmd_param {
345 	int tz_id;
346 	int temp;
347 	int direction;
348 };
349 
350 typedef int (*cmd_cb_t)(struct nl_msg *, struct cmd_param *);
351 
352 static int thermal_genl_tz_id_encode(struct nl_msg *msg, struct cmd_param *p)
353 {
354 	if (nla_put_u32(msg, THERMAL_GENL_ATTR_TZ_ID, p->tz_id))
355 		return -1;
356 
357 	return 0;
358 }
359 
360 static int thermal_genl_threshold_encode(struct nl_msg *msg, struct cmd_param *p)
361 {
362 	if (thermal_genl_tz_id_encode(msg, p))
363 		return -1;
364 
365 	if (nla_put_u32(msg, THERMAL_GENL_ATTR_THRESHOLD_TEMP, p->temp))
366 		return -1;
367 
368 	if (nla_put_u32(msg, THERMAL_GENL_ATTR_THRESHOLD_DIRECTION, p->direction))
369 		return -1;
370 
371 	return 0;
372 }
373 
374 static thermal_error_t thermal_genl_auto(struct thermal_handler *th, cmd_cb_t cmd_cb,
375 					 struct cmd_param *param,
376 					 int cmd, int flags, void *arg)
377 {
378 	struct nl_msg *msg;
379 	void *hdr;
380 
381 	msg = nlmsg_alloc();
382 	if (!msg)
383 		return THERMAL_ERROR;
384 
385 	hdr = genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, thermal_cmd_ops.o_id,
386 			  0, flags, cmd, THERMAL_GENL_VERSION);
387 	if (!hdr)
388 		return THERMAL_ERROR;
389 
390 	if (cmd_cb && cmd_cb(msg, param))
391 		return THERMAL_ERROR;
392 
393 	if (nl_send_msg(th->sk_cmd, th->cb_cmd, msg, genl_handle_msg, arg))
394 		return THERMAL_ERROR;
395 
396 	nlmsg_free(msg);
397 
398 	return THERMAL_SUCCESS;
399 }
400 
401 thermal_error_t thermal_cmd_get_tz(struct thermal_handler *th, struct thermal_zone **tz)
402 {
403 	return thermal_genl_auto(th, NULL, NULL, THERMAL_GENL_CMD_TZ_GET_ID,
404 				 NLM_F_DUMP | NLM_F_ACK, tz);
405 }
406 
407 thermal_error_t thermal_cmd_get_cdev(struct thermal_handler *th, struct thermal_cdev **tc)
408 {
409 	return thermal_genl_auto(th, NULL, NULL, THERMAL_GENL_CMD_CDEV_GET,
410 				 NLM_F_DUMP | NLM_F_ACK, tc);
411 }
412 
413 thermal_error_t thermal_cmd_get_trip(struct thermal_handler *th, struct thermal_zone *tz)
414 {
415 	struct cmd_param p = { .tz_id = tz->id };
416 
417 	return thermal_genl_auto(th, thermal_genl_tz_id_encode, &p,
418 				 THERMAL_GENL_CMD_TZ_GET_TRIP, 0, tz);
419 }
420 
421 thermal_error_t thermal_cmd_get_governor(struct thermal_handler *th, struct thermal_zone *tz)
422 {
423 	struct cmd_param p = { .tz_id = tz->id };
424 
425 	return thermal_genl_auto(th, thermal_genl_tz_id_encode, &p,
426 				 THERMAL_GENL_CMD_TZ_GET_GOV, 0, tz);
427 }
428 
429 thermal_error_t thermal_cmd_get_temp(struct thermal_handler *th, struct thermal_zone *tz)
430 {
431 	struct cmd_param p = { .tz_id = tz->id };
432 
433 	return thermal_genl_auto(th, thermal_genl_tz_id_encode, &p,
434 				 THERMAL_GENL_CMD_TZ_GET_TEMP, 0, tz);
435 }
436 
437 thermal_error_t thermal_cmd_threshold_get(struct thermal_handler *th,
438                                           struct thermal_zone *tz)
439 {
440 	struct cmd_param p = { .tz_id = tz->id };
441 
442         return thermal_genl_auto(th, thermal_genl_tz_id_encode, &p,
443 				 THERMAL_GENL_CMD_THRESHOLD_GET, 0, tz);
444 }
445 
446 thermal_error_t thermal_cmd_threshold_add(struct thermal_handler *th,
447                                           struct thermal_zone *tz,
448                                           int temperature,
449                                           int direction)
450 {
451 	struct cmd_param p = { .tz_id = tz->id, .temp = temperature, .direction = direction };
452 
453         return thermal_genl_auto(th, thermal_genl_threshold_encode, &p,
454 				 THERMAL_GENL_CMD_THRESHOLD_ADD, 0, tz);
455 }
456 
457 thermal_error_t thermal_cmd_threshold_delete(struct thermal_handler *th,
458                                              struct thermal_zone *tz,
459                                              int temperature,
460                                              int direction)
461 {
462 	struct cmd_param p = { .tz_id = tz->id, .temp = temperature, .direction = direction };
463 
464         return thermal_genl_auto(th, thermal_genl_threshold_encode, &p,
465 				 THERMAL_GENL_CMD_THRESHOLD_DELETE, 0, tz);
466 }
467 
468 thermal_error_t thermal_cmd_threshold_flush(struct thermal_handler *th,
469                                             struct thermal_zone *tz)
470 {
471 	struct cmd_param p = { .tz_id = tz->id };
472 
473         return thermal_genl_auto(th, thermal_genl_tz_id_encode, &p,
474 				 THERMAL_GENL_CMD_THRESHOLD_FLUSH, 0, tz);
475 }
476 
477 thermal_error_t thermal_cmd_exit(struct thermal_handler *th)
478 {
479 	if (genl_unregister_family(&thermal_cmd_ops))
480 		return THERMAL_ERROR;
481 
482 	nl_thermal_disconnect(th->sk_cmd, th->cb_cmd);
483 
484 	return THERMAL_SUCCESS;
485 }
486 
487 thermal_error_t thermal_cmd_init(struct thermal_handler *th)
488 {
489 	int ret;
490 	int family;
491 
492 	if (nl_thermal_connect(&th->sk_cmd, &th->cb_cmd))
493 		return THERMAL_ERROR;
494 
495 	ret = genl_register_family(&thermal_cmd_ops);
496 	if (ret)
497 		return THERMAL_ERROR;
498 
499 	ret = genl_ops_resolve(th->sk_cmd, &thermal_cmd_ops);
500 	if (ret)
501 		return THERMAL_ERROR;
502 
503 	family = genl_ctrl_resolve(th->sk_cmd, "nlctrl");
504 	if (family != GENL_ID_CTRL)
505 		return THERMAL_ERROR;
506 
507 	return THERMAL_SUCCESS;
508 }
509