66fbfb35da
Problem can be reproduced by unloading snd_soc_simple_card, because in
devm_get_clk_from_child() devres data is allocated as `struct clk`, but
devm_clk_release() expects devres data to be `struct devm_clk_state`.
KASAN report:
==================================================================
BUG: KASAN: slab-out-of-bounds in devm_clk_release+0x20/0x54
Read of size 8 at addr ffffff800ee09688 by task (udev-worker)/287
Call trace:
dump_backtrace+0xe8/0x11c
show_stack+0x1c/0x30
dump_stack_lvl+0x60/0x78
print_report+0x150/0x450
kasan_report+0xa8/0xf0
__asan_load8+0x78/0xa0
devm_clk_release+0x20/0x54
release_nodes+0x84/0x120
devres_release_all+0x144/0x210
device_unbind_cleanup+0x1c/0xac
really_probe+0x2f0/0x5b0
__driver_probe_device+0xc0/0x1f0
driver_probe_device+0x68/0x120
__driver_attach+0x140/0x294
bus_for_each_dev+0xec/0x160
driver_attach+0x38/0x44
bus_add_driver+0x24c/0x300
driver_register+0xf0/0x210
__platform_driver_register+0x48/0x54
asoc_simple_card_init+0x24/0x1000 [snd_soc_simple_card]
do_one_initcall+0xac/0x340
do_init_module+0xd0/0x300
load_module+0x2ba4/0x3100
__do_sys_init_module+0x2c8/0x300
__arm64_sys_init_module+0x48/0x5c
invoke_syscall+0x64/0x190
el0_svc_common.constprop.0+0x124/0x154
do_el0_svc+0x44/0xdc
el0_svc+0x14/0x50
el0t_64_sync_handler+0xec/0x11c
el0t_64_sync+0x14c/0x150
Allocated by task 287:
kasan_save_stack+0x38/0x60
kasan_set_track+0x28/0x40
kasan_save_alloc_info+0x20/0x30
__kasan_kmalloc+0xac/0xb0
__kmalloc_node_track_caller+0x6c/0x1c4
__devres_alloc_node+0x44/0xb4
devm_get_clk_from_child+0x44/0xa0
asoc_simple_parse_clk+0x1b8/0x1dc [snd_soc_simple_card_utils]
simple_parse_node.isra.0+0x1ec/0x230 [snd_soc_simple_card]
simple_dai_link_of+0x1bc/0x334 [snd_soc_simple_card]
__simple_for_each_link+0x2ec/0x320 [snd_soc_simple_card]
asoc_simple_probe+0x468/0x4dc [snd_soc_simple_card]
platform_probe+0x90/0xf0
really_probe+0x118/0x5b0
__driver_probe_device+0xc0/0x1f0
driver_probe_device+0x68/0x120
__driver_attach+0x140/0x294
bus_for_each_dev+0xec/0x160
driver_attach+0x38/0x44
bus_add_driver+0x24c/0x300
driver_register+0xf0/0x210
__platform_driver_register+0x48/0x54
asoc_simple_card_init+0x24/0x1000 [snd_soc_simple_card]
do_one_initcall+0xac/0x340
do_init_module+0xd0/0x300
load_module+0x2ba4/0x3100
__do_sys_init_module+0x2c8/0x300
__arm64_sys_init_module+0x48/0x5c
invoke_syscall+0x64/0x190
el0_svc_common.constprop.0+0x124/0x154
do_el0_svc+0x44/0xdc
el0_svc+0x14/0x50
el0t_64_sync_handler+0xec/0x11c
el0t_64_sync+0x14c/0x150
The buggy address belongs to the object at ffffff800ee09600
which belongs to the cache kmalloc-256 of size 256
The buggy address is located 136 bytes inside of
256-byte region [ffffff800ee09600, ffffff800ee09700)
The buggy address belongs to the physical page:
page:000000002d97303b refcount:1 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x4ee08
head:000000002d97303b order:1 compound_mapcount:0 compound_pincount:0
flags: 0x10200(slab|head|zone=0)
raw: 0000000000010200 0000000000000000 dead000000000122 ffffff8002c02480
raw: 0000000000000000 0000000080100010 00000001ffffffff 0000000000000000
page dumped because: kasan: bad access detected
Memory state around the buggy address:
ffffff800ee09580: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
ffffff800ee09600: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
>ffffff800ee09680: 00 fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
^
ffffff800ee09700: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
ffffff800ee09780: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
==================================================================
Fixes: abae8e57e4
("clk: generalize devm_clk_get() a bit")
Signed-off-by: Andrey Skvortsov <andrej.skvortzov@gmail.com>
Link: https://lore.kernel.org/r/20230805084847.3110586-1-andrej.skvortzov@gmail.com
Signed-off-by: Stephen Boyd <sboyd@kernel.org>
226 lines
4.8 KiB
C
226 lines
4.8 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
#include <linux/clk.h>
|
|
#include <linux/device.h>
|
|
#include <linux/export.h>
|
|
#include <linux/gfp.h>
|
|
|
|
struct devm_clk_state {
|
|
struct clk *clk;
|
|
void (*exit)(struct clk *clk);
|
|
};
|
|
|
|
static void devm_clk_release(struct device *dev, void *res)
|
|
{
|
|
struct devm_clk_state *state = res;
|
|
|
|
if (state->exit)
|
|
state->exit(state->clk);
|
|
|
|
clk_put(state->clk);
|
|
}
|
|
|
|
static struct clk *__devm_clk_get(struct device *dev, const char *id,
|
|
struct clk *(*get)(struct device *dev, const char *id),
|
|
int (*init)(struct clk *clk),
|
|
void (*exit)(struct clk *clk))
|
|
{
|
|
struct devm_clk_state *state;
|
|
struct clk *clk;
|
|
int ret;
|
|
|
|
state = devres_alloc(devm_clk_release, sizeof(*state), GFP_KERNEL);
|
|
if (!state)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
clk = get(dev, id);
|
|
if (IS_ERR(clk)) {
|
|
ret = PTR_ERR(clk);
|
|
goto err_clk_get;
|
|
}
|
|
|
|
if (init) {
|
|
ret = init(clk);
|
|
if (ret)
|
|
goto err_clk_init;
|
|
}
|
|
|
|
state->clk = clk;
|
|
state->exit = exit;
|
|
|
|
devres_add(dev, state);
|
|
|
|
return clk;
|
|
|
|
err_clk_init:
|
|
|
|
clk_put(clk);
|
|
err_clk_get:
|
|
|
|
devres_free(state);
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
struct clk *devm_clk_get(struct device *dev, const char *id)
|
|
{
|
|
return __devm_clk_get(dev, id, clk_get, NULL, NULL);
|
|
}
|
|
EXPORT_SYMBOL(devm_clk_get);
|
|
|
|
struct clk *devm_clk_get_prepared(struct device *dev, const char *id)
|
|
{
|
|
return __devm_clk_get(dev, id, clk_get, clk_prepare, clk_unprepare);
|
|
}
|
|
EXPORT_SYMBOL_GPL(devm_clk_get_prepared);
|
|
|
|
struct clk *devm_clk_get_enabled(struct device *dev, const char *id)
|
|
{
|
|
return __devm_clk_get(dev, id, clk_get,
|
|
clk_prepare_enable, clk_disable_unprepare);
|
|
}
|
|
EXPORT_SYMBOL_GPL(devm_clk_get_enabled);
|
|
|
|
struct clk *devm_clk_get_optional(struct device *dev, const char *id)
|
|
{
|
|
return __devm_clk_get(dev, id, clk_get_optional, NULL, NULL);
|
|
}
|
|
EXPORT_SYMBOL(devm_clk_get_optional);
|
|
|
|
struct clk *devm_clk_get_optional_prepared(struct device *dev, const char *id)
|
|
{
|
|
return __devm_clk_get(dev, id, clk_get_optional,
|
|
clk_prepare, clk_unprepare);
|
|
}
|
|
EXPORT_SYMBOL_GPL(devm_clk_get_optional_prepared);
|
|
|
|
struct clk *devm_clk_get_optional_enabled(struct device *dev, const char *id)
|
|
{
|
|
return __devm_clk_get(dev, id, clk_get_optional,
|
|
clk_prepare_enable, clk_disable_unprepare);
|
|
}
|
|
EXPORT_SYMBOL_GPL(devm_clk_get_optional_enabled);
|
|
|
|
struct clk_bulk_devres {
|
|
struct clk_bulk_data *clks;
|
|
int num_clks;
|
|
};
|
|
|
|
static void devm_clk_bulk_release(struct device *dev, void *res)
|
|
{
|
|
struct clk_bulk_devres *devres = res;
|
|
|
|
clk_bulk_put(devres->num_clks, devres->clks);
|
|
}
|
|
|
|
static int __devm_clk_bulk_get(struct device *dev, int num_clks,
|
|
struct clk_bulk_data *clks, bool optional)
|
|
{
|
|
struct clk_bulk_devres *devres;
|
|
int ret;
|
|
|
|
devres = devres_alloc(devm_clk_bulk_release,
|
|
sizeof(*devres), GFP_KERNEL);
|
|
if (!devres)
|
|
return -ENOMEM;
|
|
|
|
if (optional)
|
|
ret = clk_bulk_get_optional(dev, num_clks, clks);
|
|
else
|
|
ret = clk_bulk_get(dev, num_clks, clks);
|
|
if (!ret) {
|
|
devres->clks = clks;
|
|
devres->num_clks = num_clks;
|
|
devres_add(dev, devres);
|
|
} else {
|
|
devres_free(devres);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int __must_check devm_clk_bulk_get(struct device *dev, int num_clks,
|
|
struct clk_bulk_data *clks)
|
|
{
|
|
return __devm_clk_bulk_get(dev, num_clks, clks, false);
|
|
}
|
|
EXPORT_SYMBOL_GPL(devm_clk_bulk_get);
|
|
|
|
int __must_check devm_clk_bulk_get_optional(struct device *dev, int num_clks,
|
|
struct clk_bulk_data *clks)
|
|
{
|
|
return __devm_clk_bulk_get(dev, num_clks, clks, true);
|
|
}
|
|
EXPORT_SYMBOL_GPL(devm_clk_bulk_get_optional);
|
|
|
|
static void devm_clk_bulk_release_all(struct device *dev, void *res)
|
|
{
|
|
struct clk_bulk_devres *devres = res;
|
|
|
|
clk_bulk_put_all(devres->num_clks, devres->clks);
|
|
}
|
|
|
|
int __must_check devm_clk_bulk_get_all(struct device *dev,
|
|
struct clk_bulk_data **clks)
|
|
{
|
|
struct clk_bulk_devres *devres;
|
|
int ret;
|
|
|
|
devres = devres_alloc(devm_clk_bulk_release_all,
|
|
sizeof(*devres), GFP_KERNEL);
|
|
if (!devres)
|
|
return -ENOMEM;
|
|
|
|
ret = clk_bulk_get_all(dev, &devres->clks);
|
|
if (ret > 0) {
|
|
*clks = devres->clks;
|
|
devres->num_clks = ret;
|
|
devres_add(dev, devres);
|
|
} else {
|
|
devres_free(devres);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(devm_clk_bulk_get_all);
|
|
|
|
static int devm_clk_match(struct device *dev, void *res, void *data)
|
|
{
|
|
struct clk **c = res;
|
|
if (!c || !*c) {
|
|
WARN_ON(!c || !*c);
|
|
return 0;
|
|
}
|
|
return *c == data;
|
|
}
|
|
|
|
void devm_clk_put(struct device *dev, struct clk *clk)
|
|
{
|
|
int ret;
|
|
|
|
ret = devres_release(dev, devm_clk_release, devm_clk_match, clk);
|
|
|
|
WARN_ON(ret);
|
|
}
|
|
EXPORT_SYMBOL(devm_clk_put);
|
|
|
|
struct clk *devm_get_clk_from_child(struct device *dev,
|
|
struct device_node *np, const char *con_id)
|
|
{
|
|
struct devm_clk_state *state;
|
|
struct clk *clk;
|
|
|
|
state = devres_alloc(devm_clk_release, sizeof(*state), GFP_KERNEL);
|
|
if (!state)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
clk = of_clk_get_by_name(np, con_id);
|
|
if (!IS_ERR(clk)) {
|
|
state->clk = clk;
|
|
devres_add(dev, state);
|
|
} else {
|
|
devres_free(state);
|
|
}
|
|
|
|
return clk;
|
|
}
|
|
EXPORT_SYMBOL(devm_get_clk_from_child);
|