| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * | |
| * Generic netlink for energy model. | |
| * | |
| * Copyright (c) 2025 Valve Corporation. | |
| * Author: Changwoo Min <changwoo@igalia.com> | |
| */ | |
| #define pr_fmt(fmt) "energy_model: " fmt | |
| #include <linux/energy_model.h> | |
| #include <net/sock.h> | |
| #include <net/genetlink.h> | |
| #include <uapi/linux/dev_energymodel.h> | |
| #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); | |
| if (!pd) | |
| return -EINVAL; | |
| __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); |