// SPDX-License-Identifier: GPL-2.0 /* * * Generic netlink for energy model. * * Copyright (c) 2025 Valve Corporation. * Author: Changwoo Min */ #define pr_fmt(fmt) "energy_model: " fmt #include #include #include #include #include "em_netlink.h" #include "em_netlink_autogen.h" /*************************** Command encoding ********************************/ struct dump_ctx { int idx; int start; struct sk_buff *skb; struct netlink_callback *cb; }; static int __em_nl_get_pd_size(struct em_perf_domain *pd, void *data) { int nr_cpus, msg_sz, cpus_sz; int *tot_msg_sz = data; nr_cpus = cpumask_weight(to_cpumask(pd->cpus)); cpus_sz = nla_total_size_64bit(sizeof(u64)) * nr_cpus; msg_sz = nla_total_size(0) + /* DEV_ENERGYMODEL_A_PERF_DOMAINS_PERF_DOMAIN */ nla_total_size(sizeof(u32)) + /* DEV_ENERGYMODEL_A_PERF_DOMAIN_PERF_DOMAIN_ID */ nla_total_size_64bit(sizeof(u64)) + /* DEV_ENERGYMODEL_A_PERF_DOMAIN_FLAGS */ nla_total_size(cpus_sz); /* DEV_ENERGYMODEL_A_PERF_DOMAIN_CPUS */ *tot_msg_sz += nlmsg_total_size(genlmsg_msg_size(msg_sz)); return 0; } static int __em_nl_get_pd(struct em_perf_domain *pd, void *data) { struct sk_buff *msg = data; struct cpumask *cpumask; int cpu; if (nla_put_u32(msg, DEV_ENERGYMODEL_A_PERF_DOMAIN_PERF_DOMAIN_ID, pd->id)) goto out_cancel_nest; if (nla_put_u64_64bit(msg, DEV_ENERGYMODEL_A_PERF_DOMAIN_FLAGS, pd->flags, DEV_ENERGYMODEL_A_PERF_DOMAIN_PAD)) goto out_cancel_nest; cpumask = to_cpumask(pd->cpus); for_each_cpu(cpu, cpumask) { if (nla_put_u64_64bit(msg, DEV_ENERGYMODEL_A_PERF_DOMAIN_CPUS, cpu, DEV_ENERGYMODEL_A_PERF_DOMAIN_PAD)) goto out_cancel_nest; } return 0; out_cancel_nest: return -EMSGSIZE; } static int __em_nl_get_pd_for_dump(struct em_perf_domain *pd, void *data) { const struct genl_info *info; struct dump_ctx *ctx = data; void *hdr; int ret; if (ctx->idx++ < ctx->start) return 0; info = genl_info_dump(ctx->cb); hdr = genlmsg_iput(ctx->skb, info); if (!hdr) { genlmsg_cancel(ctx->skb, hdr); return -EMSGSIZE; } ret = __em_nl_get_pd(pd, ctx->skb); genlmsg_end(ctx->skb, hdr); return ret; } int dev_energymodel_nl_get_perf_domains_doit(struct sk_buff *skb, struct genl_info *info) { int id, ret = -EMSGSIZE, msg_sz = 0; int cmd = info->genlhdr->cmd; struct em_perf_domain *pd; struct sk_buff *msg; void *hdr; if (!info->attrs[DEV_ENERGYMODEL_A_PERF_DOMAIN_PERF_DOMAIN_ID]) return -EINVAL; id = nla_get_u32(info->attrs[DEV_ENERGYMODEL_A_PERF_DOMAIN_PERF_DOMAIN_ID]); pd = em_perf_domain_get_by_id(id); __em_nl_get_pd_size(pd, &msg_sz); msg = genlmsg_new(msg_sz, GFP_KERNEL); if (!msg) return -ENOMEM; hdr = genlmsg_put_reply(msg, info, &dev_energymodel_nl_family, 0, cmd); if (!hdr) goto out_free_msg; ret = __em_nl_get_pd(pd, msg); if (ret) goto out_cancel_msg; genlmsg_end(msg, hdr); return genlmsg_reply(msg, info); out_cancel_msg: genlmsg_cancel(msg, hdr); out_free_msg: nlmsg_free(msg); return ret; } int dev_energymodel_nl_get_perf_domains_dumpit(struct sk_buff *skb, struct netlink_callback *cb) { struct dump_ctx ctx = { .idx = 0, .start = cb->args[0], .skb = skb, .cb = cb, }; return for_each_em_perf_domain(__em_nl_get_pd_for_dump, &ctx); } static struct em_perf_domain *__em_nl_get_pd_table_id(struct nlattr **attrs) { struct em_perf_domain *pd; int id; if (!attrs[DEV_ENERGYMODEL_A_PERF_TABLE_PERF_DOMAIN_ID]) return NULL; id = nla_get_u32(attrs[DEV_ENERGYMODEL_A_PERF_TABLE_PERF_DOMAIN_ID]); pd = em_perf_domain_get_by_id(id); return pd; } static int __em_nl_get_pd_table_size(const struct em_perf_domain *pd) { int id_sz, ps_sz; id_sz = nla_total_size(sizeof(u32)); /* DEV_ENERGYMODEL_A_PERF_TABLE_PERF_DOMAIN_ID */ ps_sz = nla_total_size(0) + /* DEV_ENERGYMODEL_A_PERF_TABLE_PERF_STATE */ nla_total_size_64bit(sizeof(u64)) + /* DEV_ENERGYMODEL_A_PERF_STATE_PERFORMANCE */ nla_total_size_64bit(sizeof(u64)) + /* DEV_ENERGYMODEL_A_PERF_STATE_FREQUENCY */ nla_total_size_64bit(sizeof(u64)) + /* DEV_ENERGYMODEL_A_PERF_STATE_POWER */ nla_total_size_64bit(sizeof(u64)) + /* DEV_ENERGYMODEL_A_PERF_STATE_COST */ nla_total_size_64bit(sizeof(u64)); /* DEV_ENERGYMODEL_A_PERF_STATE_FLAGS */ ps_sz *= pd->nr_perf_states; return nlmsg_total_size(genlmsg_msg_size(id_sz + ps_sz)); } static int __em_nl_get_pd_table(struct sk_buff *msg, const struct em_perf_domain *pd) { struct em_perf_state *table, *ps; struct nlattr *entry; int i; if (nla_put_u32(msg, DEV_ENERGYMODEL_A_PERF_TABLE_PERF_DOMAIN_ID, pd->id)) goto out_err; rcu_read_lock(); table = em_perf_state_from_pd((struct em_perf_domain *)pd); for (i = 0; i < pd->nr_perf_states; i++) { ps = &table[i]; entry = nla_nest_start(msg, DEV_ENERGYMODEL_A_PERF_TABLE_PERF_STATE); if (!entry) goto out_unlock_ps; if (nla_put_u64_64bit(msg, DEV_ENERGYMODEL_A_PERF_STATE_PERFORMANCE, ps->performance, DEV_ENERGYMODEL_A_PERF_STATE_PAD)) goto out_cancel_ps_nest; if (nla_put_u64_64bit(msg, DEV_ENERGYMODEL_A_PERF_STATE_FREQUENCY, ps->frequency, DEV_ENERGYMODEL_A_PERF_STATE_PAD)) goto out_cancel_ps_nest; if (nla_put_u64_64bit(msg, DEV_ENERGYMODEL_A_PERF_STATE_POWER, ps->power, DEV_ENERGYMODEL_A_PERF_STATE_PAD)) goto out_cancel_ps_nest; if (nla_put_u64_64bit(msg, DEV_ENERGYMODEL_A_PERF_STATE_COST, ps->cost, DEV_ENERGYMODEL_A_PERF_STATE_PAD)) goto out_cancel_ps_nest; if (nla_put_u64_64bit(msg, DEV_ENERGYMODEL_A_PERF_STATE_FLAGS, ps->flags, DEV_ENERGYMODEL_A_PERF_STATE_PAD)) goto out_cancel_ps_nest; nla_nest_end(msg, entry); } rcu_read_unlock(); return 0; out_cancel_ps_nest: nla_nest_cancel(msg, entry); out_unlock_ps: rcu_read_unlock(); out_err: return -EMSGSIZE; } int dev_energymodel_nl_get_perf_table_doit(struct sk_buff *skb, struct genl_info *info) { int cmd = info->genlhdr->cmd; int msg_sz, ret = -EMSGSIZE; struct em_perf_domain *pd; struct sk_buff *msg; void *hdr; pd = __em_nl_get_pd_table_id(info->attrs); if (!pd) return -EINVAL; msg_sz = __em_nl_get_pd_table_size(pd); msg = genlmsg_new(msg_sz, GFP_KERNEL); if (!msg) return -ENOMEM; hdr = genlmsg_put_reply(msg, info, &dev_energymodel_nl_family, 0, cmd); if (!hdr) goto out_free_msg; ret = __em_nl_get_pd_table(msg, pd); if (ret) goto out_free_msg; genlmsg_end(msg, hdr); return genlmsg_reply(msg, info); out_free_msg: nlmsg_free(msg); return ret; } /**************************** Event encoding *********************************/ static void __em_notify_pd_table(const struct em_perf_domain *pd, int ntf_type) { struct sk_buff *msg; int msg_sz, ret = -EMSGSIZE; void *hdr; if (!genl_has_listeners(&dev_energymodel_nl_family, &init_net, DEV_ENERGYMODEL_NLGRP_EVENT)) return; msg_sz = __em_nl_get_pd_table_size(pd); msg = genlmsg_new(msg_sz, GFP_KERNEL); if (!msg) return; hdr = genlmsg_put(msg, 0, 0, &dev_energymodel_nl_family, 0, ntf_type); if (!hdr) goto out_free_msg; ret = __em_nl_get_pd_table(msg, pd); if (ret) goto out_free_msg; genlmsg_end(msg, hdr); genlmsg_multicast(&dev_energymodel_nl_family, msg, 0, DEV_ENERGYMODEL_NLGRP_EVENT, GFP_KERNEL); return; out_free_msg: nlmsg_free(msg); } void em_notify_pd_created(const struct em_perf_domain *pd) { __em_notify_pd_table(pd, DEV_ENERGYMODEL_CMD_PERF_DOMAIN_CREATED); } void em_notify_pd_updated(const struct em_perf_domain *pd) { __em_notify_pd_table(pd, DEV_ENERGYMODEL_CMD_PERF_DOMAIN_UPDATED); } static int __em_notify_pd_deleted_size(const struct em_perf_domain *pd) { int id_sz = nla_total_size(sizeof(u32)); /* DEV_ENERGYMODEL_A_PERF_TABLE_PERF_DOMAIN_ID */ return nlmsg_total_size(genlmsg_msg_size(id_sz)); } void em_notify_pd_deleted(const struct em_perf_domain *pd) { struct sk_buff *msg; void *hdr; int msg_sz; if (!genl_has_listeners(&dev_energymodel_nl_family, &init_net, DEV_ENERGYMODEL_NLGRP_EVENT)) return; msg_sz = __em_notify_pd_deleted_size(pd); msg = genlmsg_new(msg_sz, GFP_KERNEL); if (!msg) return; hdr = genlmsg_put(msg, 0, 0, &dev_energymodel_nl_family, 0, DEV_ENERGYMODEL_CMD_PERF_DOMAIN_DELETED); if (!hdr) goto out_free_msg; if (nla_put_u32(msg, DEV_ENERGYMODEL_A_PERF_TABLE_PERF_DOMAIN_ID, pd->id)) goto out_free_msg; genlmsg_end(msg, hdr); genlmsg_multicast(&dev_energymodel_nl_family, msg, 0, DEV_ENERGYMODEL_NLGRP_EVENT, GFP_KERNEL); return; out_free_msg: nlmsg_free(msg); } /**************************** Initialization *********************************/ static int __init em_netlink_init(void) { return genl_register_family(&dev_energymodel_nl_family); } postcore_initcall(em_netlink_init);