1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 *
4 * Generic netlink for energy model.
5 *
6 * Copyright (c) 2025 Valve Corporation.
7 * Author: Changwoo Min <changwoo@igalia.com>
8 */
9
10 #define pr_fmt(fmt) "energy_model: " fmt
11
12 #include <linux/energy_model.h>
13 #include <net/sock.h>
14 #include <net/genetlink.h>
15 #include <uapi/linux/dev_energymodel.h>
16
17 #include "em_netlink.h"
18 #include "em_netlink_autogen.h"
19
20 /*************************** Command encoding ********************************/
21 struct dump_ctx {
22 int idx;
23 int start;
24 struct sk_buff *skb;
25 struct netlink_callback *cb;
26 };
27
__em_nl_get_pd_size(struct em_perf_domain * pd,void * data)28 static int __em_nl_get_pd_size(struct em_perf_domain *pd, void *data)
29 {
30 int nr_cpus, msg_sz, cpus_sz;
31 int *tot_msg_sz = data;
32
33 nr_cpus = cpumask_weight(to_cpumask(pd->cpus));
34 cpus_sz = nla_total_size_64bit(sizeof(u64)) * nr_cpus;
35
36 msg_sz = nla_total_size(0) +
37 /* DEV_ENERGYMODEL_A_PERF_DOMAINS_PERF_DOMAIN */
38 nla_total_size(sizeof(u32)) +
39 /* DEV_ENERGYMODEL_A_PERF_DOMAIN_PERF_DOMAIN_ID */
40 nla_total_size_64bit(sizeof(u64)) +
41 /* DEV_ENERGYMODEL_A_PERF_DOMAIN_FLAGS */
42 nla_total_size(cpus_sz);
43 /* DEV_ENERGYMODEL_A_PERF_DOMAIN_CPUS */
44
45 *tot_msg_sz += nlmsg_total_size(genlmsg_msg_size(msg_sz));
46 return 0;
47 }
48
__em_nl_get_pd(struct em_perf_domain * pd,void * data)49 static int __em_nl_get_pd(struct em_perf_domain *pd, void *data)
50 {
51 struct sk_buff *msg = data;
52 struct cpumask *cpumask;
53 int cpu;
54
55 if (nla_put_u32(msg, DEV_ENERGYMODEL_A_PERF_DOMAIN_PERF_DOMAIN_ID,
56 pd->id))
57 goto out_cancel_nest;
58
59 if (nla_put_u64_64bit(msg, DEV_ENERGYMODEL_A_PERF_DOMAIN_FLAGS,
60 pd->flags, DEV_ENERGYMODEL_A_PERF_DOMAIN_PAD))
61 goto out_cancel_nest;
62
63 cpumask = to_cpumask(pd->cpus);
64 for_each_cpu(cpu, cpumask) {
65 if (nla_put_u64_64bit(msg, DEV_ENERGYMODEL_A_PERF_DOMAIN_CPUS,
66 cpu, DEV_ENERGYMODEL_A_PERF_DOMAIN_PAD))
67 goto out_cancel_nest;
68 }
69
70 return 0;
71
72 out_cancel_nest:
73 return -EMSGSIZE;
74 }
75
__em_nl_get_pd_for_dump(struct em_perf_domain * pd,void * data)76 static int __em_nl_get_pd_for_dump(struct em_perf_domain *pd, void *data)
77 {
78 const struct genl_info *info;
79 struct dump_ctx *ctx = data;
80 void *hdr;
81 int ret;
82
83 if (ctx->idx++ < ctx->start)
84 return 0;
85
86 info = genl_info_dump(ctx->cb);
87 hdr = genlmsg_iput(ctx->skb, info);
88 if (!hdr) {
89 genlmsg_cancel(ctx->skb, hdr);
90 return -EMSGSIZE;
91 }
92
93 ret = __em_nl_get_pd(pd, ctx->skb);
94 genlmsg_end(ctx->skb, hdr);
95 return ret;
96 }
97
dev_energymodel_nl_get_perf_domains_doit(struct sk_buff * skb,struct genl_info * info)98 int dev_energymodel_nl_get_perf_domains_doit(struct sk_buff *skb,
99 struct genl_info *info)
100 {
101 int id, ret = -EMSGSIZE, msg_sz = 0;
102 int cmd = info->genlhdr->cmd;
103 struct em_perf_domain *pd;
104 struct sk_buff *msg;
105 void *hdr;
106
107 if (!info->attrs[DEV_ENERGYMODEL_A_PERF_DOMAIN_PERF_DOMAIN_ID])
108 return -EINVAL;
109
110 id = nla_get_u32(info->attrs[DEV_ENERGYMODEL_A_PERF_DOMAIN_PERF_DOMAIN_ID]);
111 pd = em_perf_domain_get_by_id(id);
112
113 __em_nl_get_pd_size(pd, &msg_sz);
114 msg = genlmsg_new(msg_sz, GFP_KERNEL);
115 if (!msg)
116 return -ENOMEM;
117
118 hdr = genlmsg_put_reply(msg, info, &dev_energymodel_nl_family, 0, cmd);
119 if (!hdr)
120 goto out_free_msg;
121
122 ret = __em_nl_get_pd(pd, msg);
123 if (ret)
124 goto out_cancel_msg;
125 genlmsg_end(msg, hdr);
126
127 return genlmsg_reply(msg, info);
128
129 out_cancel_msg:
130 genlmsg_cancel(msg, hdr);
131 out_free_msg:
132 nlmsg_free(msg);
133 return ret;
134 }
135
dev_energymodel_nl_get_perf_domains_dumpit(struct sk_buff * skb,struct netlink_callback * cb)136 int dev_energymodel_nl_get_perf_domains_dumpit(struct sk_buff *skb,
137 struct netlink_callback *cb)
138 {
139 struct dump_ctx ctx = {
140 .idx = 0,
141 .start = cb->args[0],
142 .skb = skb,
143 .cb = cb,
144 };
145
146 return for_each_em_perf_domain(__em_nl_get_pd_for_dump, &ctx);
147 }
148
__em_nl_get_pd_table_id(struct nlattr ** attrs)149 static struct em_perf_domain *__em_nl_get_pd_table_id(struct nlattr **attrs)
150 {
151 struct em_perf_domain *pd;
152 int id;
153
154 if (!attrs[DEV_ENERGYMODEL_A_PERF_TABLE_PERF_DOMAIN_ID])
155 return NULL;
156
157 id = nla_get_u32(attrs[DEV_ENERGYMODEL_A_PERF_TABLE_PERF_DOMAIN_ID]);
158 pd = em_perf_domain_get_by_id(id);
159 return pd;
160 }
161
__em_nl_get_pd_table_size(const struct em_perf_domain * pd)162 static int __em_nl_get_pd_table_size(const struct em_perf_domain *pd)
163 {
164 int id_sz, ps_sz;
165
166 id_sz = nla_total_size(sizeof(u32));
167 /* DEV_ENERGYMODEL_A_PERF_TABLE_PERF_DOMAIN_ID */
168 ps_sz = nla_total_size(0) +
169 /* DEV_ENERGYMODEL_A_PERF_TABLE_PERF_STATE */
170 nla_total_size_64bit(sizeof(u64)) +
171 /* DEV_ENERGYMODEL_A_PERF_STATE_PERFORMANCE */
172 nla_total_size_64bit(sizeof(u64)) +
173 /* DEV_ENERGYMODEL_A_PERF_STATE_FREQUENCY */
174 nla_total_size_64bit(sizeof(u64)) +
175 /* DEV_ENERGYMODEL_A_PERF_STATE_POWER */
176 nla_total_size_64bit(sizeof(u64)) +
177 /* DEV_ENERGYMODEL_A_PERF_STATE_COST */
178 nla_total_size_64bit(sizeof(u64));
179 /* DEV_ENERGYMODEL_A_PERF_STATE_FLAGS */
180 ps_sz *= pd->nr_perf_states;
181
182 return nlmsg_total_size(genlmsg_msg_size(id_sz + ps_sz));
183 }
184
185 static
__em_nl_get_pd_table(struct sk_buff * msg,const struct em_perf_domain * pd)186 int __em_nl_get_pd_table(struct sk_buff *msg, const struct em_perf_domain *pd)
187 {
188 struct em_perf_state *table, *ps;
189 struct nlattr *entry;
190 int i;
191
192 if (nla_put_u32(msg, DEV_ENERGYMODEL_A_PERF_TABLE_PERF_DOMAIN_ID,
193 pd->id))
194 goto out_err;
195
196 rcu_read_lock();
197 table = em_perf_state_from_pd((struct em_perf_domain *)pd);
198
199 for (i = 0; i < pd->nr_perf_states; i++) {
200 ps = &table[i];
201
202 entry = nla_nest_start(msg,
203 DEV_ENERGYMODEL_A_PERF_TABLE_PERF_STATE);
204 if (!entry)
205 goto out_unlock_ps;
206
207 if (nla_put_u64_64bit(msg,
208 DEV_ENERGYMODEL_A_PERF_STATE_PERFORMANCE,
209 ps->performance,
210 DEV_ENERGYMODEL_A_PERF_STATE_PAD))
211 goto out_cancel_ps_nest;
212 if (nla_put_u64_64bit(msg,
213 DEV_ENERGYMODEL_A_PERF_STATE_FREQUENCY,
214 ps->frequency,
215 DEV_ENERGYMODEL_A_PERF_STATE_PAD))
216 goto out_cancel_ps_nest;
217 if (nla_put_u64_64bit(msg,
218 DEV_ENERGYMODEL_A_PERF_STATE_POWER,
219 ps->power,
220 DEV_ENERGYMODEL_A_PERF_STATE_PAD))
221 goto out_cancel_ps_nest;
222 if (nla_put_u64_64bit(msg,
223 DEV_ENERGYMODEL_A_PERF_STATE_COST,
224 ps->cost,
225 DEV_ENERGYMODEL_A_PERF_STATE_PAD))
226 goto out_cancel_ps_nest;
227 if (nla_put_u64_64bit(msg,
228 DEV_ENERGYMODEL_A_PERF_STATE_FLAGS,
229 ps->flags,
230 DEV_ENERGYMODEL_A_PERF_STATE_PAD))
231 goto out_cancel_ps_nest;
232
233 nla_nest_end(msg, entry);
234 }
235 rcu_read_unlock();
236 return 0;
237
238 out_cancel_ps_nest:
239 nla_nest_cancel(msg, entry);
240 out_unlock_ps:
241 rcu_read_unlock();
242 out_err:
243 return -EMSGSIZE;
244 }
245
dev_energymodel_nl_get_perf_table_doit(struct sk_buff * skb,struct genl_info * info)246 int dev_energymodel_nl_get_perf_table_doit(struct sk_buff *skb,
247 struct genl_info *info)
248 {
249 int cmd = info->genlhdr->cmd;
250 int msg_sz, ret = -EMSGSIZE;
251 struct em_perf_domain *pd;
252 struct sk_buff *msg;
253 void *hdr;
254
255 pd = __em_nl_get_pd_table_id(info->attrs);
256 if (!pd)
257 return -EINVAL;
258
259 msg_sz = __em_nl_get_pd_table_size(pd);
260
261 msg = genlmsg_new(msg_sz, GFP_KERNEL);
262 if (!msg)
263 return -ENOMEM;
264
265 hdr = genlmsg_put_reply(msg, info, &dev_energymodel_nl_family, 0, cmd);
266 if (!hdr)
267 goto out_free_msg;
268
269 ret = __em_nl_get_pd_table(msg, pd);
270 if (ret)
271 goto out_free_msg;
272
273 genlmsg_end(msg, hdr);
274 return genlmsg_reply(msg, info);
275
276 out_free_msg:
277 nlmsg_free(msg);
278 return ret;
279 }
280
281
282 /**************************** Event encoding *********************************/
__em_notify_pd_table(const struct em_perf_domain * pd,int ntf_type)283 static void __em_notify_pd_table(const struct em_perf_domain *pd, int ntf_type)
284 {
285 struct sk_buff *msg;
286 int msg_sz, ret = -EMSGSIZE;
287 void *hdr;
288
289 if (!genl_has_listeners(&dev_energymodel_nl_family, &init_net, DEV_ENERGYMODEL_NLGRP_EVENT))
290 return;
291
292 msg_sz = __em_nl_get_pd_table_size(pd);
293
294 msg = genlmsg_new(msg_sz, GFP_KERNEL);
295 if (!msg)
296 return;
297
298 hdr = genlmsg_put(msg, 0, 0, &dev_energymodel_nl_family, 0, ntf_type);
299 if (!hdr)
300 goto out_free_msg;
301
302 ret = __em_nl_get_pd_table(msg, pd);
303 if (ret)
304 goto out_free_msg;
305
306 genlmsg_end(msg, hdr);
307
308 genlmsg_multicast(&dev_energymodel_nl_family, msg, 0,
309 DEV_ENERGYMODEL_NLGRP_EVENT, GFP_KERNEL);
310
311 return;
312
313 out_free_msg:
314 nlmsg_free(msg);
315 }
316
em_notify_pd_created(const struct em_perf_domain * pd)317 void em_notify_pd_created(const struct em_perf_domain *pd)
318 {
319 __em_notify_pd_table(pd, DEV_ENERGYMODEL_CMD_PERF_DOMAIN_CREATED);
320 }
321
em_notify_pd_updated(const struct em_perf_domain * pd)322 void em_notify_pd_updated(const struct em_perf_domain *pd)
323 {
324 __em_notify_pd_table(pd, DEV_ENERGYMODEL_CMD_PERF_DOMAIN_UPDATED);
325 }
326
__em_notify_pd_deleted_size(const struct em_perf_domain * pd)327 static int __em_notify_pd_deleted_size(const struct em_perf_domain *pd)
328 {
329 int id_sz = nla_total_size(sizeof(u32)); /* DEV_ENERGYMODEL_A_PERF_TABLE_PERF_DOMAIN_ID */
330
331 return nlmsg_total_size(genlmsg_msg_size(id_sz));
332 }
333
em_notify_pd_deleted(const struct em_perf_domain * pd)334 void em_notify_pd_deleted(const struct em_perf_domain *pd)
335 {
336 struct sk_buff *msg;
337 void *hdr;
338 int msg_sz;
339
340 if (!genl_has_listeners(&dev_energymodel_nl_family, &init_net,
341 DEV_ENERGYMODEL_NLGRP_EVENT))
342 return;
343
344 msg_sz = __em_notify_pd_deleted_size(pd);
345
346 msg = genlmsg_new(msg_sz, GFP_KERNEL);
347 if (!msg)
348 return;
349
350 hdr = genlmsg_put(msg, 0, 0, &dev_energymodel_nl_family, 0,
351 DEV_ENERGYMODEL_CMD_PERF_DOMAIN_DELETED);
352 if (!hdr)
353 goto out_free_msg;
354
355 if (nla_put_u32(msg, DEV_ENERGYMODEL_A_PERF_TABLE_PERF_DOMAIN_ID,
356 pd->id))
357 goto out_free_msg;
358
359 genlmsg_end(msg, hdr);
360
361 genlmsg_multicast(&dev_energymodel_nl_family, msg, 0,
362 DEV_ENERGYMODEL_NLGRP_EVENT, GFP_KERNEL);
363
364 return;
365
366 out_free_msg:
367 nlmsg_free(msg);
368 }
369
370 /**************************** Initialization *********************************/
em_netlink_init(void)371 static int __init em_netlink_init(void)
372 {
373 return genl_register_family(&dev_energymodel_nl_family);
374 }
375 postcore_initcall(em_netlink_init);
376