linux-stable/drivers/clk/clk-loongson2.c
Gustavo A. R. Silva 02fb4f0084 clk: clk-loongson2: Fix potential buffer overflow in flexible-array member access
Flexible-array member `hws` in `struct clk_hw_onecell_data` is annotated
with the `counted_by()` attribute. This means that when memory is
allocated for this array, the _counter_, which in this case is member
`num` in the flexible structure, should be set to the maximum number of
elements the flexible array can contain, or fewer.

In this case, the total number of elements for the flexible array is
determined by variable `clks_num` when allocating heap space via
`devm_kzalloc()`, as shown below:

289         struct loongson2_clk_provider *clp;
	...
296         for (p = data; p->name; p++)
297                 clks_num++;
298
299         clp = devm_kzalloc(dev, struct_size(clp, clk_data.hws, clks_num),
300                            GFP_KERNEL);

So, `clp->clk_data.num` should be set to `clks_num` or less, and not
exceed `clks_num`, as is currently the case. Otherwise, if data is
written into `clp->clk_data.hws[clks_num]`, the instrumentation
provided by the compiler won't detect the overflow, leading to a
memory corruption bug at runtime.

Fix this issue by setting `clp->clk_data.num` to `clks_num`.

Fixes: 9796ec0bd0 ("clk: clk-loongson2: Refactor driver for adding new platforms")
Cc: stable@vger.kernel.org
Signed-off-by: Gustavo A. R. Silva <gustavoars@kernel.org>
Link: https://lore.kernel.org/r/ZzaN5MpmMr0hwHw9@kspp
Signed-off-by: Stephen Boyd <sboyd@kernel.org>
2024-11-18 19:51:28 -08:00

376 lines
12 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* Author: Yinbo Zhu <zhuyinbo@loongson.cn>
* Copyright (C) 2022-2023 Loongson Technology Corporation Limited
*/
#include <linux/err.h>
#include <linux/init.h>
#include <linux/clk-provider.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/io-64-nonatomic-lo-hi.h>
#include <dt-bindings/clock/loongson,ls2k-clk.h>
static const struct clk_parent_data pdata[] = {
{ .fw_name = "ref_100m", },
};
enum loongson2_clk_type {
CLK_TYPE_PLL,
CLK_TYPE_SCALE,
CLK_TYPE_DIVIDER,
CLK_TYPE_GATE,
CLK_TYPE_FIXED,
CLK_TYPE_NONE,
};
struct loongson2_clk_provider {
void __iomem *base;
struct device *dev;
spinlock_t clk_lock; /* protect access to DIV registers */
/* Must be last --ends in a flexible-array member. */
struct clk_hw_onecell_data clk_data;
};
struct loongson2_clk_data {
struct clk_hw hw;
void __iomem *reg;
u8 div_shift;
u8 div_width;
u8 mult_shift;
u8 mult_width;
};
struct loongson2_clk_board_info {
u8 id;
enum loongson2_clk_type type;
const char *name;
const char *parent_name;
unsigned long fixed_rate;
u8 reg_offset;
u8 div_shift;
u8 div_width;
u8 mult_shift;
u8 mult_width;
u8 bit_idx;
};
#define CLK_DIV(_id, _name, _pname, _offset, _dshift, _dwidth) \
{ \
.id = _id, \
.type = CLK_TYPE_DIVIDER, \
.name = _name, \
.parent_name = _pname, \
.reg_offset = _offset, \
.div_shift = _dshift, \
.div_width = _dwidth, \
}
#define CLK_PLL(_id, _name, _offset, _mshift, _mwidth, \
_dshift, _dwidth) \
{ \
.id = _id, \
.type = CLK_TYPE_PLL, \
.name = _name, \
.parent_name = NULL, \
.reg_offset = _offset, \
.mult_shift = _mshift, \
.mult_width = _mwidth, \
.div_shift = _dshift, \
.div_width = _dwidth, \
}
#define CLK_SCALE(_id, _name, _pname, _offset, \
_dshift, _dwidth) \
{ \
.id = _id, \
.type = CLK_TYPE_SCALE, \
.name = _name, \
.parent_name = _pname, \
.reg_offset = _offset, \
.div_shift = _dshift, \
.div_width = _dwidth, \
}
#define CLK_GATE(_id, _name, _pname, _offset, _bidx) \
{ \
.id = _id, \
.type = CLK_TYPE_GATE, \
.name = _name, \
.parent_name = _pname, \
.reg_offset = _offset, \
.bit_idx = _bidx, \
}
#define CLK_FIXED(_id, _name, _pname, _rate) \
{ \
.id = _id, \
.type = CLK_TYPE_FIXED, \
.name = _name, \
.parent_name = _pname, \
.fixed_rate = _rate, \
}
static const struct loongson2_clk_board_info ls2k0500_clks[] = {
CLK_PLL(LOONGSON2_NODE_PLL, "pll_node", 0, 16, 8, 8, 6),
CLK_PLL(LOONGSON2_DDR_PLL, "pll_ddr", 0x8, 16, 8, 8, 6),
CLK_PLL(LOONGSON2_DC_PLL, "pll_soc", 0x10, 16, 8, 8, 6),
CLK_PLL(LOONGSON2_PIX0_PLL, "pll_pix0", 0x18, 16, 8, 8, 6),
CLK_PLL(LOONGSON2_PIX1_PLL, "pll_pix1", 0x20, 16, 8, 8, 6),
CLK_DIV(LOONGSON2_NODE_CLK, "clk_node", "pll_node", 0, 24, 6),
CLK_DIV(LOONGSON2_DDR_CLK, "clk_ddr", "pll_ddr", 0x8, 24, 6),
CLK_DIV(LOONGSON2_HDA_CLK, "clk_hda", "pll_ddr", 0xc, 8, 6),
CLK_DIV(LOONGSON2_GPU_CLK, "clk_gpu", "pll_soc", 0x10, 24, 6),
CLK_DIV(LOONGSON2_DC_CLK, "clk_sb", "pll_soc", 0x14, 0, 6),
CLK_DIV(LOONGSON2_GMAC_CLK, "clk_gmac", "pll_soc", 0x14, 8, 6),
CLK_DIV(LOONGSON2_PIX0_CLK, "clk_pix0", "pll_pix0", 0x18, 24, 6),
CLK_DIV(LOONGSON2_PIX1_CLK, "clk_pix1", "pll_pix1", 0x20, 24, 6),
CLK_SCALE(LOONGSON2_BOOT_CLK, "clk_boot", "clk_sb", 0x28, 8, 3),
CLK_SCALE(LOONGSON2_SATA_CLK, "clk_sata", "clk_sb", 0x28, 12, 3),
CLK_SCALE(LOONGSON2_USB_CLK, "clk_usb", "clk_sb", 0x28, 16, 3),
CLK_SCALE(LOONGSON2_APB_CLK, "clk_apb", "clk_sb", 0x28, 20, 3),
{ /* Sentinel */ },
};
static const struct loongson2_clk_board_info ls2k1000_clks[] = {
CLK_PLL(LOONGSON2_NODE_PLL, "pll_node", 0, 32, 10, 26, 6),
CLK_PLL(LOONGSON2_DDR_PLL, "pll_ddr", 0x10, 32, 10, 26, 6),
CLK_PLL(LOONGSON2_DC_PLL, "pll_dc", 0x20, 32, 10, 26, 6),
CLK_PLL(LOONGSON2_PIX0_PLL, "pll_pix0", 0x30, 32, 10, 26, 6),
CLK_PLL(LOONGSON2_PIX1_PLL, "pll_pix1", 0x40, 32, 10, 26, 6),
CLK_DIV(LOONGSON2_NODE_CLK, "clk_node", "pll_node", 0x8, 0, 6),
CLK_DIV(LOONGSON2_DDR_CLK, "clk_ddr", "pll_ddr", 0x18, 0, 6),
CLK_DIV(LOONGSON2_GPU_CLK, "clk_gpu", "pll_ddr", 0x18, 22, 6),
/*
* The hda clk divisor in the upper 32bits and the clk-prodiver
* layer code doesn't support 64bit io operation thus a conversion
* is required that subtract shift by 32 and add 4byte to the hda
* address
*/
CLK_DIV(LOONGSON2_HDA_CLK, "clk_hda", "pll_ddr", 0x22, 12, 7),
CLK_DIV(LOONGSON2_DC_CLK, "clk_dc", "pll_dc", 0x28, 0, 6),
CLK_DIV(LOONGSON2_GMAC_CLK, "clk_gmac", "pll_dc", 0x28, 22, 6),
CLK_DIV(LOONGSON2_PIX0_CLK, "clk_pix0", "pll_pix0", 0x38, 0, 6),
CLK_DIV(LOONGSON2_PIX1_CLK, "clk_pix1", "pll_pix1", 0x38, 0, 6),
CLK_SCALE(LOONGSON2_BOOT_CLK, "clk_boot", NULL, 0x50, 8, 3),
CLK_SCALE(LOONGSON2_SATA_CLK, "clk_sata", "clk_gmac", 0x50, 12, 3),
CLK_SCALE(LOONGSON2_USB_CLK, "clk_usb", "clk_gmac", 0x50, 16, 3),
CLK_SCALE(LOONGSON2_APB_CLK, "clk_apb", "clk_gmac", 0x50, 20, 3),
{ /* Sentinel */ },
};
static const struct loongson2_clk_board_info ls2k2000_clks[] = {
CLK_PLL(LOONGSON2_DC_PLL, "pll_0", 0, 21, 9, 32, 6),
CLK_PLL(LOONGSON2_DDR_PLL, "pll_1", 0x10, 21, 9, 32, 6),
CLK_PLL(LOONGSON2_NODE_PLL, "pll_2", 0x20, 21, 9, 32, 6),
CLK_PLL(LOONGSON2_PIX0_PLL, "pll_pix0", 0x30, 21, 9, 32, 6),
CLK_PLL(LOONGSON2_PIX1_PLL, "pll_pix1", 0x40, 21, 9, 32, 6),
CLK_GATE(LOONGSON2_OUT0_GATE, "out0_gate", "pll_0", 0, 40),
CLK_GATE(LOONGSON2_GMAC_GATE, "gmac_gate", "pll_0", 0, 41),
CLK_GATE(LOONGSON2_RIO_GATE, "rio_gate", "pll_0", 0, 42),
CLK_GATE(LOONGSON2_DC_GATE, "dc_gate", "pll_1", 0x10, 40),
CLK_GATE(LOONGSON2_DDR_GATE, "ddr_gate", "pll_1", 0x10, 41),
CLK_GATE(LOONGSON2_GPU_GATE, "gpu_gate", "pll_1", 0x10, 42),
CLK_GATE(LOONGSON2_HDA_GATE, "hda_gate", "pll_2", 0x20, 40),
CLK_GATE(LOONGSON2_NODE_GATE, "node_gate", "pll_2", 0x20, 41),
CLK_GATE(LOONGSON2_EMMC_GATE, "emmc_gate", "pll_2", 0x20, 42),
CLK_GATE(LOONGSON2_PIX0_GATE, "pix0_gate", "pll_pix0", 0x30, 40),
CLK_GATE(LOONGSON2_PIX1_GATE, "pix1_gate", "pll_pix1", 0x40, 40),
CLK_DIV(LOONGSON2_OUT0_CLK, "clk_out0", "out0_gate", 0, 0, 6),
CLK_DIV(LOONGSON2_GMAC_CLK, "clk_gmac", "gmac_gate", 0, 7, 6),
CLK_DIV(LOONGSON2_RIO_CLK, "clk_rio", "rio_gate", 0, 14, 6),
CLK_DIV(LOONGSON2_DC_CLK, "clk_dc", "dc_gate", 0x10, 0, 6),
CLK_DIV(LOONGSON2_GPU_CLK, "clk_gpu", "gpu_gate", 0x10, 7, 6),
CLK_DIV(LOONGSON2_DDR_CLK, "clk_ddr", "ddr_gate", 0x10, 14, 6),
CLK_DIV(LOONGSON2_HDA_CLK, "clk_hda", "hda_gate", 0x20, 0, 6),
CLK_DIV(LOONGSON2_NODE_CLK, "clk_node", "node_gate", 0x20, 7, 6),
CLK_DIV(LOONGSON2_EMMC_CLK, "clk_emmc", "emmc_gate", 0x20, 14, 6),
CLK_DIV(LOONGSON2_PIX0_CLK, "clk_pix0", "pll_pix0", 0x30, 0, 6),
CLK_DIV(LOONGSON2_PIX1_CLK, "clk_pix1", "pll_pix1", 0x40, 0, 6),
CLK_SCALE(LOONGSON2_SATA_CLK, "clk_sata", "clk_out0", 0x50, 12, 3),
CLK_SCALE(LOONGSON2_USB_CLK, "clk_usb", "clk_out0", 0x50, 16, 3),
CLK_SCALE(LOONGSON2_APB_CLK, "clk_apb", "clk_node", 0x50, 20, 3),
CLK_SCALE(LOONGSON2_BOOT_CLK, "clk_boot", NULL, 0x50, 23, 3),
CLK_SCALE(LOONGSON2_DES_CLK, "clk_des", "clk_node", 0x50, 40, 3),
CLK_SCALE(LOONGSON2_I2S_CLK, "clk_i2s", "clk_node", 0x50, 44, 3),
CLK_FIXED(LOONGSON2_MISC_CLK, "clk_misc", NULL, 50000000),
{ /* Sentinel */ },
};
static inline struct loongson2_clk_data *to_loongson2_clk(struct clk_hw *hw)
{
return container_of(hw, struct loongson2_clk_data, hw);
}
static inline unsigned long loongson2_rate_part(u64 val, u8 shift, u8 width)
{
return (val & GENMASK(shift + width - 1, shift)) >> shift;
}
static unsigned long loongson2_pll_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
u64 val, mult, div;
struct loongson2_clk_data *clk = to_loongson2_clk(hw);
val = readq(clk->reg);
mult = loongson2_rate_part(val, clk->mult_shift, clk->mult_width);
div = loongson2_rate_part(val, clk->div_shift, clk->div_width);
return div_u64((u64)parent_rate * mult, div);
}
static const struct clk_ops loongson2_pll_recalc_ops = {
.recalc_rate = loongson2_pll_recalc_rate,
};
static unsigned long loongson2_freqscale_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
u64 val, mult;
struct loongson2_clk_data *clk = to_loongson2_clk(hw);
val = readq(clk->reg);
mult = loongson2_rate_part(val, clk->div_shift, clk->div_width) + 1;
return div_u64((u64)parent_rate * mult, 8);
}
static const struct clk_ops loongson2_freqscale_recalc_ops = {
.recalc_rate = loongson2_freqscale_recalc_rate,
};
static struct clk_hw *loongson2_clk_register(struct loongson2_clk_provider *clp,
const struct loongson2_clk_board_info *cld,
const struct clk_ops *ops)
{
int ret;
struct clk_hw *hw;
struct loongson2_clk_data *clk;
struct clk_init_data init = { };
clk = devm_kzalloc(clp->dev, sizeof(*clk), GFP_KERNEL);
if (!clk)
return ERR_PTR(-ENOMEM);
init.name = cld->name;
init.ops = ops;
init.flags = 0;
init.num_parents = 1;
if (!cld->parent_name)
init.parent_data = pdata;
else
init.parent_names = &cld->parent_name;
clk->reg = clp->base + cld->reg_offset;
clk->div_shift = cld->div_shift;
clk->div_width = cld->div_width;
clk->mult_shift = cld->mult_shift;
clk->mult_width = cld->mult_width;
clk->hw.init = &init;
hw = &clk->hw;
ret = devm_clk_hw_register(clp->dev, hw);
if (ret)
clk = ERR_PTR(ret);
return hw;
}
static int loongson2_clk_probe(struct platform_device *pdev)
{
int i, clks_num = 0;
struct clk_hw *hw;
struct device *dev = &pdev->dev;
struct loongson2_clk_provider *clp;
const struct loongson2_clk_board_info *p, *data;
data = device_get_match_data(dev);
if (!data)
return -EINVAL;
for (p = data; p->name; p++)
clks_num++;
clp = devm_kzalloc(dev, struct_size(clp, clk_data.hws, clks_num),
GFP_KERNEL);
if (!clp)
return -ENOMEM;
clp->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(clp->base))
return PTR_ERR(clp->base);
spin_lock_init(&clp->clk_lock);
clp->clk_data.num = clks_num;
clp->dev = dev;
for (i = 0; i < clks_num; i++) {
p = &data[i];
switch (p->type) {
case CLK_TYPE_PLL:
hw = loongson2_clk_register(clp, p,
&loongson2_pll_recalc_ops);
break;
case CLK_TYPE_SCALE:
hw = loongson2_clk_register(clp, p,
&loongson2_freqscale_recalc_ops);
break;
case CLK_TYPE_DIVIDER:
hw = devm_clk_hw_register_divider(dev, p->name,
p->parent_name, 0,
clp->base + p->reg_offset,
p->div_shift, p->div_width,
CLK_DIVIDER_ONE_BASED,
&clp->clk_lock);
break;
case CLK_TYPE_GATE:
hw = devm_clk_hw_register_gate(dev, p->name, p->parent_name, 0,
clp->base + p->reg_offset,
p->bit_idx, 0,
&clp->clk_lock);
break;
case CLK_TYPE_FIXED:
hw = clk_hw_register_fixed_rate_parent_data(dev, p->name, pdata,
0, p->fixed_rate);
break;
default:
return dev_err_probe(dev, -EINVAL, "Invalid clk type\n");
}
if (IS_ERR(hw))
return dev_err_probe(dev, PTR_ERR(hw),
"Register clk: %s, type: %u failed!\n",
p->name, p->type);
clp->clk_data.hws[p->id] = hw;
}
return devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, &clp->clk_data);
}
static const struct of_device_id loongson2_clk_match_table[] = {
{ .compatible = "loongson,ls2k0500-clk", .data = &ls2k0500_clks },
{ .compatible = "loongson,ls2k-clk", .data = &ls2k1000_clks },
{ .compatible = "loongson,ls2k2000-clk", .data = &ls2k2000_clks },
{ }
};
MODULE_DEVICE_TABLE(of, loongson2_clk_match_table);
static struct platform_driver loongson2_clk_driver = {
.probe = loongson2_clk_probe,
.driver = {
.name = "loongson2-clk",
.of_match_table = loongson2_clk_match_table,
},
};
module_platform_driver(loongson2_clk_driver);
MODULE_DESCRIPTION("Loongson2 clock driver");
MODULE_AUTHOR("Loongson Technology Corporation Limited");
MODULE_LICENSE("GPL");