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