xref: /linux/kernel/power/em_netlink.c (revision 638757c9c9e5a823671367150a9f48e93d115b48)
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/energy_model.h>
16 
17 #include "em_netlink.h"
18 #include "em_netlink_autogen.h"
19 
20 #define EM_A_PD_CPUS_LEN		256
21 
22 /*************************** Command encoding ********************************/
23 static int __em_nl_get_pd_size(struct em_perf_domain *pd, void *data)
24 {
25 	char cpus_buf[EM_A_PD_CPUS_LEN];
26 	int *tot_msg_sz = data;
27 	int msg_sz, cpus_sz;
28 
29 	cpus_sz = snprintf(cpus_buf, sizeof(cpus_buf), "%*pb",
30 			   cpumask_pr_args(to_cpumask(pd->cpus)));
31 
32 	msg_sz = nla_total_size(0) +			/* EM_A_PDS_PD */
33 		 nla_total_size(sizeof(u32)) +		/* EM_A_PD_PD_ID */
34 		 nla_total_size_64bit(sizeof(u64)) +	/* EM_A_PD_FLAGS */
35 		 nla_total_size(cpus_sz);		/* EM_A_PD_CPUS */
36 
37 	*tot_msg_sz += nlmsg_total_size(genlmsg_msg_size(msg_sz));
38 	return 0;
39 }
40 
41 static int __em_nl_get_pd(struct em_perf_domain *pd, void *data)
42 {
43 	char cpus_buf[EM_A_PD_CPUS_LEN];
44 	struct sk_buff *msg = data;
45 	struct nlattr *entry;
46 
47 	entry = nla_nest_start(msg, EM_A_PDS_PD);
48 	if (!entry)
49 		goto out_cancel_nest;
50 
51 	if (nla_put_u32(msg, EM_A_PD_PD_ID, pd->id))
52 		goto out_cancel_nest;
53 
54 	if (nla_put_u64_64bit(msg, EM_A_PD_FLAGS, pd->flags, EM_A_PD_PAD))
55 		goto out_cancel_nest;
56 
57 	snprintf(cpus_buf, sizeof(cpus_buf), "%*pb",
58 		 cpumask_pr_args(to_cpumask(pd->cpus)));
59 	if (nla_put_string(msg, EM_A_PD_CPUS, cpus_buf))
60 		goto out_cancel_nest;
61 
62 	nla_nest_end(msg, entry);
63 
64 	return 0;
65 
66 out_cancel_nest:
67 	nla_nest_cancel(msg, entry);
68 
69 	return -EMSGSIZE;
70 }
71 
72 int em_nl_get_pds_doit(struct sk_buff *skb, struct genl_info *info)
73 {
74 	struct sk_buff *msg;
75 	void *hdr;
76 	int cmd = info->genlhdr->cmd;
77 	int ret = -EMSGSIZE, msg_sz = 0;
78 
79 	for_each_em_perf_domain(__em_nl_get_pd_size, &msg_sz);
80 
81 	msg = genlmsg_new(msg_sz, GFP_KERNEL);
82 	if (!msg)
83 		return -ENOMEM;
84 
85 	hdr = genlmsg_put_reply(msg, info, &em_nl_family, 0, cmd);
86 	if (!hdr)
87 		goto out_free_msg;
88 
89 	ret = for_each_em_perf_domain(__em_nl_get_pd, msg);
90 	if (ret)
91 		goto out_cancel_msg;
92 
93 	genlmsg_end(msg, hdr);
94 
95 	return genlmsg_reply(msg, info);
96 
97 out_cancel_msg:
98 	genlmsg_cancel(msg, hdr);
99 out_free_msg:
100 	nlmsg_free(msg);
101 
102 	return ret;
103 }
104 
105 static struct em_perf_domain *__em_nl_get_pd_table_id(struct nlattr **attrs)
106 {
107 	struct em_perf_domain *pd;
108 	int id;
109 
110 	if (!attrs[EM_A_PD_TABLE_PD_ID])
111 		return NULL;
112 
113 	id = nla_get_u32(attrs[EM_A_PD_TABLE_PD_ID]);
114 	pd = em_perf_domain_get_by_id(id);
115 	return pd;
116 }
117 
118 static int __em_nl_get_pd_table_size(const struct em_perf_domain *pd)
119 {
120 	int id_sz, ps_sz;
121 
122 	id_sz = nla_total_size(sizeof(u32));		/* EM_A_PD_TABLE_PD_ID */
123 	ps_sz = nla_total_size(0) +			/* EM_A_PD_TABLE_PS */
124 		nla_total_size_64bit(sizeof(u64)) +	/* EM_A_PS_PERFORMANCE */
125 		nla_total_size_64bit(sizeof(u64)) +	/* EM_A_PS_FREQUENCY */
126 		nla_total_size_64bit(sizeof(u64)) +	/* EM_A_PS_POWER */
127 		nla_total_size_64bit(sizeof(u64)) +	/* EM_A_PS_COST */
128 		nla_total_size_64bit(sizeof(u64));	/* EM_A_PS_FLAGS */
129 	ps_sz *= pd->nr_perf_states;
130 
131 	return nlmsg_total_size(genlmsg_msg_size(id_sz + ps_sz));
132 }
133 
134 static int __em_nl_get_pd_table(struct sk_buff *msg, const struct em_perf_domain *pd)
135 {
136 	struct em_perf_state *table, *ps;
137 	struct nlattr *entry;
138 	int i;
139 
140 	if (nla_put_u32(msg, EM_A_PD_TABLE_PD_ID, pd->id))
141 		goto out_err;
142 
143 	rcu_read_lock();
144 	table = em_perf_state_from_pd((struct em_perf_domain *)pd);
145 
146 	for (i = 0; i < pd->nr_perf_states; i++) {
147 		ps = &table[i];
148 
149 		entry = nla_nest_start(msg, EM_A_PD_TABLE_PS);
150 		if (!entry)
151 			goto out_unlock_ps;
152 
153 		if (nla_put_u64_64bit(msg, EM_A_PS_PERFORMANCE,
154 				      ps->performance, EM_A_PS_PAD))
155 			goto out_cancel_ps_nest;
156 		if (nla_put_u64_64bit(msg, EM_A_PS_FREQUENCY,
157 				      ps->frequency, EM_A_PS_PAD))
158 			goto out_cancel_ps_nest;
159 		if (nla_put_u64_64bit(msg, EM_A_PS_POWER,
160 				      ps->power, EM_A_PS_PAD))
161 			goto out_cancel_ps_nest;
162 		if (nla_put_u64_64bit(msg, EM_A_PS_COST,
163 				      ps->cost, EM_A_PS_PAD))
164 			goto out_cancel_ps_nest;
165 		if (nla_put_u64_64bit(msg, EM_A_PS_FLAGS,
166 				      ps->flags, EM_A_PS_PAD))
167 			goto out_cancel_ps_nest;
168 
169 		nla_nest_end(msg, entry);
170 	}
171 	rcu_read_unlock();
172 	return 0;
173 
174 out_cancel_ps_nest:
175 	nla_nest_cancel(msg, entry);
176 out_unlock_ps:
177 	rcu_read_unlock();
178 out_err:
179 	return -EMSGSIZE;
180 }
181 
182 int em_nl_get_pd_table_doit(struct sk_buff *skb, struct genl_info *info)
183 {
184 	int cmd = info->genlhdr->cmd;
185 	int msg_sz, ret = -EMSGSIZE;
186 	struct em_perf_domain *pd;
187 	struct sk_buff *msg;
188 	void *hdr;
189 
190 	pd = __em_nl_get_pd_table_id(info->attrs);
191 	if (!pd)
192 		return -EINVAL;
193 
194 	msg_sz = __em_nl_get_pd_table_size(pd);
195 
196 	msg = genlmsg_new(msg_sz, GFP_KERNEL);
197 	if (!msg)
198 		return -ENOMEM;
199 
200 	hdr = genlmsg_put_reply(msg, info, &em_nl_family, 0, cmd);
201 	if (!hdr)
202 		goto out_free_msg;
203 
204 	ret = __em_nl_get_pd_table(msg, pd);
205 	if (ret)
206 		goto out_free_msg;
207 
208 	genlmsg_end(msg, hdr);
209 	return genlmsg_reply(msg, info);
210 
211 out_free_msg:
212 	nlmsg_free(msg);
213 	return ret;
214 }
215 
216 
217 /**************************** Event encoding *********************************/
218 static void __em_notify_pd_table(const struct em_perf_domain *pd, int ntf_type)
219 {
220 	struct sk_buff *msg;
221 	int msg_sz, ret = -EMSGSIZE;
222 	void *hdr;
223 
224 	if (!genl_has_listeners(&em_nl_family, &init_net, EM_NLGRP_EVENT))
225 		return;
226 
227 	msg_sz = __em_nl_get_pd_table_size(pd);
228 
229 	msg = genlmsg_new(msg_sz, GFP_KERNEL);
230 	if (!msg)
231 		return;
232 
233 	hdr = genlmsg_put(msg, 0, 0, &em_nl_family, 0, ntf_type);
234 	if (!hdr)
235 		goto out_free_msg;
236 
237 	ret = __em_nl_get_pd_table(msg, pd);
238 	if (ret)
239 		goto out_free_msg;
240 
241 	genlmsg_end(msg, hdr);
242 
243 	genlmsg_multicast(&em_nl_family, msg, 0, EM_NLGRP_EVENT, GFP_KERNEL);
244 
245 	return;
246 
247 out_free_msg:
248 	nlmsg_free(msg);
249 	return;
250 }
251 
252 void em_notify_pd_created(const struct em_perf_domain *pd)
253 {
254 	__em_notify_pd_table(pd, EM_CMD_PD_CREATED);
255 }
256 
257 void em_notify_pd_updated(const struct em_perf_domain *pd)
258 {
259 	__em_notify_pd_table(pd, EM_CMD_PD_UPDATED);
260 }
261 
262 static int __em_notify_pd_deleted_size(const struct em_perf_domain *pd)
263 {
264 	int id_sz = nla_total_size(sizeof(u32)); /* EM_A_PD_TABLE_PD_ID */
265 
266 	return nlmsg_total_size(genlmsg_msg_size(id_sz));
267 }
268 
269 void em_notify_pd_deleted(const struct em_perf_domain *pd)
270 {
271 	struct sk_buff *msg;
272 	void *hdr;
273 	int msg_sz;
274 
275 	if (!genl_has_listeners(&em_nl_family, &init_net, EM_NLGRP_EVENT))
276 		return;
277 
278 	msg_sz = __em_notify_pd_deleted_size(pd);
279 
280 	msg = genlmsg_new(msg_sz, GFP_KERNEL);
281 	if (!msg)
282 		return;
283 
284 	hdr = genlmsg_put(msg, 0, 0, &em_nl_family, 0, EM_CMD_PD_DELETED);
285 	if (!hdr)
286 		goto out_free_msg;
287 
288 	if (nla_put_u32(msg, EM_A_PD_TABLE_PD_ID, pd->id)) {
289 		goto out_free_msg;
290 	}
291 
292 	genlmsg_end(msg, hdr);
293 
294 	genlmsg_multicast(&em_nl_family, msg, 0, EM_NLGRP_EVENT, GFP_KERNEL);
295 
296 	return;
297 
298 out_free_msg:
299 	nlmsg_free(msg);
300 	return;
301 }
302 
303 /**************************** Initialization *********************************/
304 static int __init em_netlink_init(void)
305 {
306 	return genl_register_family(&em_nl_family);
307 }
308 postcore_initcall(em_netlink_init);
309