377 lines · 8.8 KB · C
RawDownload
// 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));} staticint __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);