linux/drivers/interconnect/mediatek/icc-emi.c
Nicolas Frattaroli 6ffd02b822 interconnect: mediatek: Aggregate bandwidth with saturating add
By using a regular non-overflow-checking add, the MediaTek icc-emi
driver will happy wrap at U32_MAX + 1 to 0. As it's common for the
interconnect core to fill in INT_MAX values, this is not a hypothetical
situation, but something that actually happens in regular use. This
would be pretty disasterous if anything used this driver.

Replace the addition with an overflow-checked addition from overflow.h,
and saturate to U32_MAX if an overflow is detected.

Fixes: b45293799f ("interconnect: mediatek: Add MediaTek MT8183/8195 EMI Interconnect driver")
Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
Link: https://lore.kernel.org/r/20251124-mt8196-dvfsrc-v2-13-d9c1334db9f3@collabora.com
Signed-off-by: Georgi Djakov <djakov@kernel.org>
2025-12-21 13:59:54 +02:00

157 lines
3.6 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* MediaTek External Memory Interface (EMI) Interconnect driver
*
* Copyright (c) 2021 MediaTek Inc.
* Copyright (c) 2024 Collabora Ltd.
* AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
*/
#include <linux/interconnect.h>
#include <linux/interconnect-provider.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/overflow.h>
#include <linux/platform_device.h>
#include <linux/soc/mediatek/dvfsrc.h>
#include "icc-emi.h"
static int mtk_emi_icc_aggregate(struct icc_node *node, u32 tag, u32 avg_bw,
u32 peak_bw, u32 *agg_avg, u32 *agg_peak)
{
struct mtk_icc_node *in = node->data;
if (check_add_overflow(*agg_avg, avg_bw, agg_avg))
*agg_avg = U32_MAX;
*agg_peak = max_t(u32, *agg_peak, peak_bw);
in->sum_avg = *agg_avg;
in->max_peak = *agg_peak;
return 0;
}
static int mtk_emi_icc_set(struct icc_node *src, struct icc_node *dst)
{
struct mtk_icc_node *node = dst->data;
struct device *dev;
int ret;
if (unlikely(!src->provider))
return -EINVAL;
dev = src->provider->dev->parent;
switch (node->ep) {
case 0:
break;
case 1:
ret = mtk_dvfsrc_send_request(dev, MTK_DVFSRC_CMD_PEAK_BW, node->max_peak);
if (ret) {
dev_err(dev, "Cannot send peak bw request: %d\n", ret);
return ret;
}
ret = mtk_dvfsrc_send_request(dev, MTK_DVFSRC_CMD_BW, node->sum_avg);
if (ret) {
dev_err(dev, "Cannot send bw request: %d\n", ret);
return ret;
}
break;
case 2:
ret = mtk_dvfsrc_send_request(dev, MTK_DVFSRC_CMD_HRT_BW, node->sum_avg);
if (ret) {
dev_err(dev, "Cannot send HRT bw request: %d\n", ret);
return ret;
}
break;
default:
dev_err(src->provider->dev, "Unknown endpoint %u\n", node->ep);
return -EINVAL;
}
return 0;
}
int mtk_emi_icc_probe(struct platform_device *pdev)
{
const struct mtk_icc_desc *desc;
struct device *dev = &pdev->dev;
struct icc_node *node;
struct icc_onecell_data *data;
struct icc_provider *provider;
struct mtk_icc_node **mnodes;
int i, j, ret;
desc = of_device_get_match_data(dev);
if (!desc)
return -EINVAL;
mnodes = desc->nodes;
provider = devm_kzalloc(dev, sizeof(*provider), GFP_KERNEL);
if (!provider)
return -ENOMEM;
data = devm_kzalloc(dev, struct_size(data, nodes, desc->num_nodes), GFP_KERNEL);
if (!data)
return -ENOMEM;
provider->dev = dev;
provider->set = mtk_emi_icc_set;
provider->aggregate = mtk_emi_icc_aggregate;
provider->xlate = of_icc_xlate_onecell;
INIT_LIST_HEAD(&provider->nodes);
provider->data = data;
for (i = 0; i < desc->num_nodes; i++) {
if (!mnodes[i])
continue;
node = icc_node_create(mnodes[i]->id);
if (IS_ERR(node)) {
ret = PTR_ERR(node);
goto err;
}
node->name = mnodes[i]->name;
node->data = mnodes[i];
icc_node_add(node, provider);
for (j = 0; j < mnodes[i]->num_links; j++)
icc_link_create(node, mnodes[i]->links[j]);
data->nodes[i] = node;
}
data->num_nodes = desc->num_nodes;
ret = icc_provider_register(provider);
if (ret)
goto err;
platform_set_drvdata(pdev, provider);
return 0;
err:
icc_nodes_remove(provider);
return ret;
}
EXPORT_SYMBOL_GPL(mtk_emi_icc_probe);
void mtk_emi_icc_remove(struct platform_device *pdev)
{
struct icc_provider *provider = platform_get_drvdata(pdev);
icc_provider_deregister(provider);
icc_nodes_remove(provider);
}
EXPORT_SYMBOL_GPL(mtk_emi_icc_remove);
MODULE_AUTHOR("AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>");
MODULE_AUTHOR("Henry Chen <henryc.chen@mediatek.com>");
MODULE_DESCRIPTION("MediaTek External Memory Interface interconnect driver");
MODULE_LICENSE("GPL");