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