From a99aacfb67df669aafd16bef787b68fb58442e9a Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 18 Aug 2026 22:50:44 +0100 Subject: [PATCH 01/15] regmap: Allow a base register to be specified for the RAM regmap To allow testing of register maps with very large register values add support for specifying base address to the RAM regmap. Since the config struct is shared with the raw RAM regmap also update that, though we will not use it. Link: https://patch.msgid.link/20260818-regmap-kunit-cache-high-bit-v1-1-7e790676dea3@kernel.org Signed-off-by: Mark Brown --- drivers/base/regmap/internal.h | 1 + drivers/base/regmap/regmap-ram.c | 26 ++++++++++++++++++++------ drivers/base/regmap/regmap-raw-ram.c | 21 +++++++++++++-------- 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/drivers/base/regmap/internal.h b/drivers/base/regmap/internal.h index a6e4689000af..c73036744468 100644 --- a/drivers/base/regmap/internal.h +++ b/drivers/base/regmap/internal.h @@ -322,6 +322,7 @@ static inline unsigned int regcache_get_index_by_order(const struct regmap *map, } struct regmap_ram_data { + unsigned int base_reg; unsigned int *vals; /* Allocatd by caller */ bool *read; bool *written; diff --git a/drivers/base/regmap/regmap-ram.c b/drivers/base/regmap/regmap-ram.c index c7356b0d8c83..a666bfb4d667 100644 --- a/drivers/base/regmap/regmap-ram.c +++ b/drivers/base/regmap/regmap-ram.c @@ -20,8 +20,11 @@ static int regmap_ram_write(void *context, unsigned int reg, unsigned int val) { struct regmap_ram_data *data = context; - data->vals[reg] = val; - data->written[reg] = true; + if (reg < data->base_reg) + return -EINVAL; + + data->vals[reg - data->base_reg] = val; + data->written[reg - data->base_reg] = true; return 0; } @@ -30,8 +33,11 @@ static int regmap_ram_read(void *context, unsigned int reg, unsigned int *val) { struct regmap_ram_data *data = context; - *val = data->vals[reg]; - data->read[reg] = true; + if (reg < data->base_reg) + return -EINVAL; + + *val = data->vals[reg - data->base_reg]; + data->read[reg - data->base_reg] = true; return 0; } @@ -60,17 +66,25 @@ struct regmap *__regmap_init_ram(struct device *dev, const char *lock_name) { struct regmap *map; + unsigned int num_regs; if (!config->max_register) { pr_crit("No max_register specified for RAM regmap\n"); return ERR_PTR(-EINVAL); } - data->read = kzalloc_objs(bool, config->max_register + 1); + if (data->base_reg > config->max_register) { + pr_crit("base_reg above max_register for RAM regmap\n"); + return ERR_PTR(-EINVAL); + } + + num_regs = config->max_register - data->base_reg + 1; + + data->read = kzalloc_objs(bool, num_regs); if (!data->read) return ERR_PTR(-ENOMEM); - data->written = kzalloc_objs(bool, config->max_register + 1); + data->written = kzalloc_objs(bool, num_regs); if (!data->written) { kfree(data->read); return ERR_PTR(-ENOMEM); diff --git a/drivers/base/regmap/regmap-raw-ram.c b/drivers/base/regmap/regmap-raw-ram.c index 60d6e95cdd1b..c63c464549f9 100644 --- a/drivers/base/regmap/regmap-raw-ram.c +++ b/drivers/base/regmap/regmap-raw-ram.c @@ -16,14 +16,17 @@ #include "internal.h" -static unsigned int decode_reg(enum regmap_endian endian, const void *reg) +static unsigned int decode_reg(struct regmap_ram_data *data, const void *reg) { const u16 *r = reg; + unsigned int decode; - if (endian == REGMAP_ENDIAN_BIG) - return be16_to_cpu(*r); + if (data->reg_endian == REGMAP_ENDIAN_BIG) + decode = be16_to_cpu(*r); else - return le16_to_cpu(*r); + decode = le16_to_cpu(*r); + + return decode - data->base_reg; } static int regmap_raw_ram_gather_write(void *context, @@ -40,7 +43,7 @@ static int regmap_raw_ram_gather_write(void *context, if (val_len % 2) return -EINVAL; - r = decode_reg(data->reg_endian, reg); + r = decode_reg(data, reg); if (data->noinc_reg && data->noinc_reg(data, r)) { memcpy(&our_buf[r], val + val_len - 2, 2); data->written[r] = true; @@ -74,7 +77,7 @@ static int regmap_raw_ram_read(void *context, if (val_len % 2) return -EINVAL; - r = decode_reg(data->reg_endian, reg); + r = decode_reg(data, reg); if (data->noinc_reg && data->noinc_reg(data, r)) { for (i = 0; i < val_len; i += 2) memcpy(val + i, &our_buf[r], 2); @@ -114,6 +117,7 @@ struct regmap *__regmap_init_raw_ram(struct device *dev, const char *lock_name) { struct regmap *map; + unsigned int regs; if (config->reg_bits != 16) return ERR_PTR(-EINVAL); @@ -123,11 +127,12 @@ struct regmap *__regmap_init_raw_ram(struct device *dev, return ERR_PTR(-EINVAL); } - data->read = kzalloc_objs(bool, config->max_register + 1); + regs = config->max_register - data->base_reg + 1; + data->read = kzalloc_objs(bool, regs); if (!data->read) return ERR_PTR(-ENOMEM); - data->written = kzalloc_objs(bool, config->max_register + 1); + data->written = kzalloc_objs(bool, regs); if (!data->written) return ERR_PTR(-ENOMEM); From b26da6db3da90a2ad84f686c6635c45697c3fb06 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 18 Aug 2026 22:50:45 +0100 Subject: [PATCH 02/15] regmap: Test rbtree and maple caches for very high register numbers We've had bugs in the cache code with handling of register numbers with the top bit set before, add some test cases for this. It's not really worth fixing up all the individual tests to support a base address, instead write a specific test that reproduces a bunch of the coverge. Run this on the rbtree and maple caches which are the only ones that can reasonably support this case. Link: https://patch.msgid.link/20260818-regmap-kunit-cache-high-bit-v1-2-7e790676dea3@kernel.org Signed-off-by: Mark Brown --- drivers/base/regmap/regmap-kunit.c | 98 +++++++++++++++++++++++++++++- 1 file changed, 95 insertions(+), 3 deletions(-) diff --git a/drivers/base/regmap/regmap-kunit.c b/drivers/base/regmap/regmap-kunit.c index 2999d9c185d5..c78772b660d6 100644 --- a/drivers/base/regmap/regmap-kunit.c +++ b/drivers/base/regmap/regmap-kunit.c @@ -11,6 +11,8 @@ #define BLOCK_TEST_SIZE 12 +#define HIGH_BIT_REG ((UINT_MAX / 2) - 3) + KUNIT_DEFINE_ACTION_WRAPPER(regmap_exit_action, regmap_exit, struct regmap *); struct regmap_test_priv { @@ -23,6 +25,7 @@ struct regmap_test_param { enum regcache_type cache; enum regmap_endian val_endian; + unsigned int base_reg; unsigned int from_reg; bool fast_io; }; @@ -204,7 +207,8 @@ static struct regmap *gen_regmap(struct kunit *test, config->max_register += (BLOCK_TEST_SIZE * config->reg_stride); } - size = array_size(config->max_register + 1, sizeof(*buf)); + size = array_size(config->max_register - param->base_reg + 1, + sizeof(*buf)); buf = kmalloc(size, GFP_KERNEL); if (!buf) return ERR_PTR(-ENOMEM); @@ -215,6 +219,7 @@ static struct regmap *gen_regmap(struct kunit *test, if (!(*data)) goto out_free; (*data)->vals = buf; + (*data)->base_reg = param->base_reg; if (config->num_reg_defaults) { defaults = kunit_kcalloc(test, @@ -227,8 +232,8 @@ static struct regmap *gen_regmap(struct kunit *test, config->reg_defaults = defaults; for (i = 0; i < config->num_reg_defaults; i++) { - defaults[i].reg = param->from_reg + (i * config->reg_stride); - defaults[i].def = buf[param->from_reg + (i * config->reg_stride)]; + defaults[i].reg = param->from_reg - param->base_reg + (i * config->reg_stride); + defaults[i].def = buf[param->from_reg - param->base_reg + (i * config->reg_stride)]; } } @@ -1648,6 +1653,92 @@ static void cache_write_zero(struct kunit *test) KUNIT_ASSERT_FALSE(test, regcache_reg_cached(map, 1)); } +static const struct regmap_test_param high_bit_cache_types_list[] = { + { .cache = REGCACHE_RBTREE, .from_reg = HIGH_BIT_REG, + .base_reg = HIGH_BIT_REG }, + { .cache = REGCACHE_MAPLE, .from_reg = HIGH_BIT_REG, + .base_reg = HIGH_BIT_REG }, +}; + +KUNIT_ARRAY_PARAM(high_bit_cache_types, high_bit_cache_types_list, param_to_desc); + +/* + * Dynamically allocated cache types should support registers with the + * high bit set, the backend will return an error for any operation on + * a register below the expected window. + */ +static void cache_high_bit_reg(struct kunit *test) +{ + const struct regmap_test_param *param = test->param_value; + struct regmap_ram_data *data; + struct regmap_config config; + struct regmap *map; + unsigned int val[BLOCK_TEST_SIZE], rval; + int i; + + config = test_regmap_config; + + map = gen_regmap(test, &config, &data); + KUNIT_ASSERT_FALSE(test, IS_ERR(map)); + if (IS_ERR(map)) + return; + + get_random_bytes(&val, sizeof(val)); + + /* No defaults so no registers cached */ + for (i = 0; i < BLOCK_TEST_SIZE; i++) + KUNIT_EXPECT_FALSE(test, regcache_reg_cached(map, param->from_reg + i)); + + /* Writes should reach the device */ + for (i = 0; i < BLOCK_TEST_SIZE; i++) + KUNIT_EXPECT_EQ(test, 0, regmap_write(map, param->from_reg + i, + val[i])); + for (i = 0; i < BLOCK_TEST_SIZE; i++) { + KUNIT_EXPECT_TRUE(test, data->written[i]); + KUNIT_EXPECT_EQ(test, val[i], data->vals[i]); + } + + /* Reads should be satisfied from the cache */ + for (i = 0; i < BLOCK_TEST_SIZE; i++) { + KUNIT_EXPECT_TRUE(test, regcache_reg_cached(map, param->from_reg + i)); + KUNIT_EXPECT_EQ(test, 0, regmap_read(map, param->from_reg + i, + &rval)); + KUNIT_EXPECT_EQ(test, val[i], rval); + KUNIT_EXPECT_FALSE(test, data->read[i]); + } + + /* Trash the data on the device then resync */ + regcache_mark_dirty(map); + for (i = 0; i < BLOCK_TEST_SIZE; i++) { + data->vals[i] = 0; + data->written[i] = false; + } + KUNIT_EXPECT_EQ(test, 0, regcache_sync(map)); + + /* Did we just write the correct data out? */ + for (i = 0; i < BLOCK_TEST_SIZE; i++) { + KUNIT_EXPECT_TRUE(test, data->written[i]); + KUNIT_EXPECT_EQ(test, val[i], data->vals[i]); + } + + /* Drop some registers */ + KUNIT_EXPECT_EQ(test, 0, regcache_drop_region(map, param->from_reg + 3, + param->from_reg + 5)); + for (i = 0; i < BLOCK_TEST_SIZE; i++) + KUNIT_EXPECT_EQ(test, i < 3 || i > 5, + regcache_reg_cached(map, param->from_reg + i)); + + /* Reread and check only the dropped registers hit the device */ + for (i = 0; i < BLOCK_TEST_SIZE; i++) + data->read[i] = false; + for (i = 0; i < BLOCK_TEST_SIZE; i++) { + KUNIT_EXPECT_EQ(test, 0, regmap_read(map, param->from_reg + i, + &rval)); + KUNIT_EXPECT_EQ(test, val[i], rval); + KUNIT_EXPECT_EQ(test, i >= 3 && i <= 5, data->read[i]); + } +} + /* Check that caching the window register works with sync */ static void cache_range_window_reg(struct kunit *test) { @@ -2168,6 +2259,7 @@ static struct kunit_case regmap_test_cases[] = { KUNIT_CASE_PARAM(cache_drop_all_and_sync_has_defaults, sparse_cache_types_gen_params), KUNIT_CASE_PARAM(cache_present, sparse_cache_types_gen_params), KUNIT_CASE_PARAM(cache_write_zero, sparse_cache_types_gen_params), + KUNIT_CASE_PARAM(cache_high_bit_reg, high_bit_cache_types_gen_params), KUNIT_CASE_PARAM(cache_range_window_reg, real_cache_types_only_gen_params), KUNIT_CASE_PARAM(raw_read_defaults_single, raw_test_types_gen_params), From 8e51dd6db0bc7fa888326c913edcb8fe8c1fdc16 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Fri, 4 Sep 2026 16:31:09 +0530 Subject: [PATCH 03/15] regmap: fix typo "asynchrnous" in comment Correct "asynchrnous" to "asynchronous", reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Link: https://patch.msgid.link/20260904110110.8823-1-hemanth.selam@gmail.com Signed-off-by: Mark Brown --- drivers/base/regmap/regmap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index e6e022b02637..98184a00097a 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -2722,7 +2722,7 @@ EXPORT_SYMBOL_GPL(regmap_multi_reg_write_bypassed); * If supported by the underlying bus the write will be scheduled * asynchronously, helping maximise I/O speed on higher speed buses * like SPI. regmap_async_complete() can be called to ensure that all - * asynchrnous writes have been completed. + * asynchronous writes have been completed. * * A value of zero will be returned on success, a negative errno will * be returned in error cases. From 80328b41ea2887229fbdc1bd03357ae0a9fa43f2 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Thu, 24 Sep 2026 14:30:52 +0800 Subject: [PATCH 04/15] regmap: convert lock/unlock to a scoped guard The regmap lock and unlock callbacks are invoked directly as map->lock(map->lock_arg) / map->unlock(map->lock_arg) at every call site. This open-coded pattern requires manual unlocking on every return path, which spreads goto out_unlock chains and duplicated unlock statements throughout the code and is an easy place to leak the lock on an error path. Define a scoped guard for the regmap lock in internal.h. The lock and unlock callbacks are chosen at init time (mutex, spinlock, raw spinlock, hwspinlock or none) and return void, so an unconditional DEFINE_GUARD() is sufficient. Convert all lock/unlock users in regmap.c to guard(regmap)() for function-scope critical sections and scoped_guard(regmap, ...) where work must run outside the lock (e.g. regmap_register_patch() calling regmap_async_complete()). This drops every manual unlock and the associated goto out_unlock labels with no functional change. Assisted-by: LLM Signed-off-by: Peng Fan Link: https://patch.msgid.link/20260924-regmap-lock-guard-v3-1-8a6127223c16@nxp.com Signed-off-by: Mark Brown --- drivers/base/regmap/internal.h | 11 ++ drivers/base/regmap/regmap.c | 182 +++++++++++---------------------- 2 files changed, 70 insertions(+), 123 deletions(-) diff --git a/drivers/base/regmap/internal.h b/drivers/base/regmap/internal.h index a6e4689000af..0a17fe3e8cf4 100644 --- a/drivers/base/regmap/internal.h +++ b/drivers/base/regmap/internal.h @@ -10,6 +10,7 @@ #ifndef _REGMAP_INTERNAL_H #define _REGMAP_INTERNAL_H +#include #include #include #include @@ -185,6 +186,16 @@ struct regmap { struct hwspinlock *hwlock; }; +/* + * Scoped guard for the regmap lock. The lock/unlock callbacks are selected + * at init time (mutex, spinlock, raw spinlock, hwspinlock or none) and never + * fail, so an unconditional guard is sufficient. Use with guard(regmap)(map) + * or scoped_guard(regmap, map) { ... }. + */ +DEFINE_GUARD(regmap, struct regmap *, + _T->lock(_T->lock_arg), + _T->unlock(_T->lock_arg)) + struct regcache_ops { const char *name; enum regcache_type type; diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index e6e022b02637..f3a269f32297 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -115,9 +115,8 @@ bool regmap_cached(struct regmap *map, unsigned int reg) if (map->max_register_is_set && reg > map->max_register) return false; - map->lock(map->lock_arg); - ret = regcache_read(map, reg, &val); - map->unlock(map->lock_arg); + scoped_guard(regmap, map) + ret = regcache_read(map, reg, &val); if (ret) return false; @@ -1977,18 +1976,12 @@ int _regmap_write(struct regmap *map, unsigned int reg, */ int regmap_write(struct regmap *map, unsigned int reg, unsigned int val) { - int ret; - if (!IS_ALIGNED(reg, map->reg_stride)) return -EINVAL; - map->lock(map->lock_arg); + guard(regmap)(map); - ret = _regmap_write(map, reg, val); - - map->unlock(map->lock_arg); - - return ret; + return _regmap_write(map, reg, val); } EXPORT_SYMBOL_GPL(regmap_write); @@ -2009,7 +2002,7 @@ int regmap_write_async(struct regmap *map, unsigned int reg, unsigned int val) if (!IS_ALIGNED(reg, map->reg_stride)) return -EINVAL; - map->lock(map->lock_arg); + guard(regmap)(map); map->async = true; @@ -2017,8 +2010,6 @@ int regmap_write_async(struct regmap *map, unsigned int reg, unsigned int val) map->async = false; - map->unlock(map->lock_arg); - return ret; } EXPORT_SYMBOL_GPL(regmap_write_async); @@ -2080,20 +2071,14 @@ int _regmap_raw_write(struct regmap *map, unsigned int reg, int regmap_raw_write(struct regmap *map, unsigned int reg, const void *val, size_t val_len) { - int ret; - if (!regmap_can_raw_write(map)) return -EINVAL; if (val_len % map->format.val_bytes) return -EINVAL; - map->lock(map->lock_arg); + guard(regmap)(map); - ret = _regmap_raw_write(map, reg, val, val_len, false); - - map->unlock(map->lock_arg); - - return ret; + return _regmap_raw_write(map, reg, val, val_len, false); } EXPORT_SYMBOL_GPL(regmap_raw_write); @@ -2211,21 +2196,17 @@ int regmap_noinc_write(struct regmap *map, unsigned int reg, if (val_len == 0) return -EINVAL; - map->lock(map->lock_arg); + guard(regmap)(map); - if (!regmap_volatile(map, reg) || !regmap_writeable_noinc(map, reg)) { - ret = -EINVAL; - goto out_unlock; - } + if (!regmap_volatile(map, reg) || !regmap_writeable_noinc(map, reg)) + return -EINVAL; /* * Use the accelerated operation if we can. The val drops the const * typing in order to facilitate code reuse in regmap_noinc_readwrite(). */ - if (map->bus->reg_noinc_write) { - ret = regmap_noinc_readwrite(map, reg, (void *)val, val_len, true); - goto out_unlock; - } + if (map->bus->reg_noinc_write) + return regmap_noinc_readwrite(map, reg, (void *)val, val_len, true); while (val_len) { if (map->max_raw_write && map->max_raw_write < val_len) @@ -2234,14 +2215,12 @@ int regmap_noinc_write(struct regmap *map, unsigned int reg, write_len = val_len; ret = _regmap_raw_write(map, reg, val, write_len, true); if (ret) - goto out_unlock; + return ret; val = ((u8 *)val) + write_len; val_len -= write_len; } -out_unlock: - map->unlock(map->lock_arg); - return ret; + return 0; } EXPORT_SYMBOL_GPL(regmap_noinc_write); @@ -2357,7 +2336,8 @@ int regmap_bulk_write(struct regmap *map, unsigned int reg, const void *val, * single write operations. */ if (!map->write || !map->format.parse_inplace) { - map->lock(map->lock_arg); + guard(regmap)(map); + for (i = 0; i < val_count; i++) { unsigned int ival; @@ -2372,18 +2352,15 @@ int regmap_bulk_write(struct regmap *map, unsigned int reg, const void *val, ival = *(u32 *)(val + (i * val_bytes)); break; default: - ret = -EINVAL; - goto out; + return -EINVAL; } ret = _regmap_write(map, reg + regmap_get_offset(map, i), ival); if (ret != 0) - goto out; + return ret; } -out: - map->unlock(map->lock_arg); } else { void *wval; @@ -2653,15 +2630,9 @@ static int _regmap_multi_reg_write(struct regmap *map, int regmap_multi_reg_write(struct regmap *map, const struct reg_sequence *regs, int num_regs) { - int ret; + guard(regmap)(map); - map->lock(map->lock_arg); - - ret = _regmap_multi_reg_write(map, regs, num_regs); - - map->unlock(map->lock_arg); - - return ret; + return _regmap_multi_reg_write(map, regs, num_regs); } EXPORT_SYMBOL_GPL(regmap_multi_reg_write); @@ -2690,7 +2661,7 @@ int regmap_multi_reg_write_bypassed(struct regmap *map, int ret; bool bypass; - map->lock(map->lock_arg); + guard(regmap)(map); bypass = map->cache_bypass; map->cache_bypass = true; @@ -2699,8 +2670,6 @@ int regmap_multi_reg_write_bypassed(struct regmap *map, map->cache_bypass = bypass; - map->unlock(map->lock_arg); - return ret; } EXPORT_SYMBOL_GPL(regmap_multi_reg_write_bypassed); @@ -2737,7 +2706,7 @@ int regmap_raw_write_async(struct regmap *map, unsigned int reg, if (!IS_ALIGNED(reg, map->reg_stride)) return -EINVAL; - map->lock(map->lock_arg); + guard(regmap)(map); map->async = true; @@ -2745,8 +2714,6 @@ int regmap_raw_write_async(struct regmap *map, unsigned int reg, map->async = false; - map->unlock(map->lock_arg); - return ret; } EXPORT_SYMBOL_GPL(regmap_raw_write_async); @@ -2863,18 +2830,12 @@ static int _regmap_read(struct regmap *map, unsigned int reg, */ int regmap_read(struct regmap *map, unsigned int reg, unsigned int *val) { - int ret; - if (!IS_ALIGNED(reg, map->reg_stride)) return -EINVAL; - map->lock(map->lock_arg); + guard(regmap)(map); - ret = _regmap_read(map, reg, val); - - map->unlock(map->lock_arg); - - return ret; + return _regmap_read(map, reg, val); } EXPORT_SYMBOL_GPL(regmap_read); @@ -2897,7 +2858,7 @@ int regmap_read_bypassed(struct regmap *map, unsigned int reg, unsigned int *val if (!IS_ALIGNED(reg, map->reg_stride)) return -EINVAL; - map->lock(map->lock_arg); + guard(regmap)(map); bypass = map->cache_bypass; cache_only = map->cache_only; @@ -2909,8 +2870,6 @@ int regmap_read_bypassed(struct regmap *map, unsigned int reg, unsigned int *val map->cache_bypass = bypass; map->cache_only = cache_only; - map->unlock(map->lock_arg); - return ret; } EXPORT_SYMBOL_GPL(regmap_read_bypassed); @@ -2941,22 +2900,18 @@ int regmap_raw_read(struct regmap *map, unsigned int reg, void *val, if (val_count == 0) return -EINVAL; - map->lock(map->lock_arg); + guard(regmap)(map); if (regmap_volatile_range(map, reg, val_count) || map->cache_bypass || map->cache_type == REGCACHE_NONE) { size_t chunk_count, chunk_bytes; size_t chunk_regs = val_count; - if (!map->cache_bypass && map->cache_only) { - ret = -EBUSY; - goto out; - } + if (!map->cache_bypass && map->cache_only) + return -EBUSY; - if (!map->read) { - ret = -ENOTSUPP; - goto out; - } + if (!map->read) + return -ENOTSUPP; if (map->use_single_read) chunk_regs = 1; @@ -2970,7 +2925,7 @@ int regmap_raw_read(struct regmap *map, unsigned int reg, void *val, for (i = 0; i < chunk_count; i++) { ret = _regmap_raw_read(map, reg, val, chunk_bytes, false); if (ret != 0) - goto out; + return ret; reg += regmap_get_offset(map, chunk_regs); val += chunk_bytes; @@ -2981,7 +2936,7 @@ int regmap_raw_read(struct regmap *map, unsigned int reg, void *val, if (val_len) { ret = _regmap_raw_read(map, reg, val, val_len, false); if (ret != 0) - goto out; + return ret; } } else { /* Otherwise go word by word for the cache; should be low @@ -2991,16 +2946,13 @@ int regmap_raw_read(struct regmap *map, unsigned int reg, void *val, ret = _regmap_read(map, reg + regmap_get_offset(map, i), &v); if (ret != 0) - goto out; + return ret; map->format.format_val(val + (i * val_bytes), v, 0); } } - out: - map->unlock(map->lock_arg); - - return ret; + return 0; } EXPORT_SYMBOL_GPL(regmap_raw_read); @@ -3041,12 +2993,10 @@ int regmap_noinc_read(struct regmap *map, unsigned int reg, if (val_len == 0) return -EINVAL; - map->lock(map->lock_arg); + guard(regmap)(map); - if (!regmap_volatile(map, reg) || !regmap_readable_noinc(map, reg)) { - ret = -EINVAL; - goto out_unlock; - } + if (!regmap_volatile(map, reg) || !regmap_readable_noinc(map, reg)) + return -EINVAL; /* * We have not defined the FIFO semantics for cache, as the @@ -3054,16 +3004,12 @@ int regmap_noinc_read(struct regmap *map, unsigned int reg, * written value? Just avoid this by always reading the FIFO * even when using cache. Cache only will not work. */ - if (!map->cache_bypass && map->cache_only) { - ret = -EBUSY; - goto out_unlock; - } + if (!map->cache_bypass && map->cache_only) + return -EBUSY; /* Use the accelerated operation if we can */ - if (map->bus->reg_noinc_read) { - ret = regmap_noinc_readwrite(map, reg, val, val_len, false); - goto out_unlock; - } + if (map->bus->reg_noinc_read) + return regmap_noinc_readwrite(map, reg, val, val_len, false); while (val_len) { if (map->max_raw_read && map->max_raw_read < val_len) @@ -3072,14 +3018,12 @@ int regmap_noinc_read(struct regmap *map, unsigned int reg, read_len = val_len; ret = _regmap_raw_read(map, reg, val, read_len, true); if (ret) - goto out_unlock; + return ret; val = ((u8 *)val) + read_len; val_len -= read_len; } -out_unlock: - map->unlock(map->lock_arg); - return ret; + return 0; } EXPORT_SYMBOL_GPL(regmap_noinc_read); @@ -3149,22 +3093,20 @@ static int _regmap_bulk_read(struct regmap *map, unsigned int reg, u8 *u8 = val; int ret, i; - map->lock(map->lock_arg); + guard(regmap)(map); for (i = 0; i < val_count; i++) { unsigned int ival; if (regs) { - if (!IS_ALIGNED(regs[i], map->reg_stride)) { - ret = -EINVAL; - goto out; - } + if (!IS_ALIGNED(regs[i], map->reg_stride)) + return -EINVAL; ret = _regmap_read(map, regs[i], &ival); } else { ret = _regmap_read(map, reg + regmap_get_offset(map, i), &ival); } if (ret != 0) - goto out; + return ret; switch (map->format.val_bytes) { case 4: @@ -3177,13 +3119,11 @@ static int _regmap_bulk_read(struct regmap *map, unsigned int reg, u8[i] = ival; break; default: - ret = -EINVAL; - goto out; + return -EINVAL; } } -out: - map->unlock(map->lock_arg); - return ret; + + return 0; } /** @@ -3310,7 +3250,7 @@ int regmap_update_bits_base(struct regmap *map, unsigned int reg, { int ret; - map->lock(map->lock_arg); + guard(regmap)(map); map->async = async; @@ -3318,8 +3258,6 @@ int regmap_update_bits_base(struct regmap *map, unsigned int reg, map->async = false; - map->unlock(map->lock_arg); - return ret; } EXPORT_SYMBOL_GPL(regmap_update_bits_base); @@ -3452,19 +3390,17 @@ int regmap_register_patch(struct regmap *map, const struct reg_sequence *regs, return -ENOMEM; } - map->lock(map->lock_arg); + scoped_guard(regmap, map) { + bypass = map->cache_bypass; - bypass = map->cache_bypass; + map->cache_bypass = true; + map->async = true; - map->cache_bypass = true; - map->async = true; + ret = _regmap_multi_reg_write(map, regs, num_regs); - ret = _regmap_multi_reg_write(map, regs, num_regs); - - map->async = false; - map->cache_bypass = bypass; - - map->unlock(map->lock_arg); + map->async = false; + map->cache_bypass = bypass; + } regmap_async_complete(map); From 23f1120dcbea0089b63f2e4afd671b4b012864cb Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Thu, 24 Sep 2026 14:30:53 +0800 Subject: [PATCH 05/15] regcache: use the regmap scoped lock guard Convert the open-coded map->lock()/map->unlock() users in regcache.c to the regmap scoped guard introduced for regmap.c. Use scoped_guard(regmap, ...) in regcache_exit(), where the locked region is a subsection of the function, and guard(regmap)() for the function-scope critical sections. regcache_init() keeps explicit map->lock()/map->unlock() calls: it has a goto err_* cleanup ladder and mixing goto with cleanup helpers in the same function is not allowed by cleanup.h. regcache_sync() and regcache_sync_region() are left as-is: they already use a single goto out unlock path, so converting them would require either mixing a goto with a scoped_guard scope or restructuring their control flow, neither of which is an improvement. No functional change. Assisted-by: LLM Signed-off-by: Peng Fan Link: https://patch.msgid.link/20260924-regmap-lock-guard-v3-2-8a6127223c16@nxp.com Signed-off-by: Mark Brown --- drivers/base/regmap/regcache.c | 28 ++++++++-------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c index 0d58d900a2ca..4c80db5d6f6f 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -281,9 +281,8 @@ void regcache_exit(struct regmap *map) if (map->cache_ops->exit) { dev_dbg(map->dev, "Destroying %s cache\n", map->cache_ops->name); - map->lock(map->lock_arg); - map->cache_ops->exit(map); - map->unlock(map->lock_arg); + scoped_guard(regmap, map) + map->cache_ops->exit(map); } kfree(map->reg_defaults); @@ -584,20 +583,14 @@ EXPORT_SYMBOL_GPL(regcache_sync_region); int regcache_drop_region(struct regmap *map, unsigned int min, unsigned int max) { - int ret = 0; - if (!map->cache_ops || !map->cache_ops->drop) return -EINVAL; - map->lock(map->lock_arg); + guard(regmap)(map); trace_regcache_drop_region(map, min, max); - ret = map->cache_ops->drop(map, min, max); - - map->unlock(map->lock_arg); - - return ret; + return map->cache_ops->drop(map, min, max); } EXPORT_SYMBOL_GPL(regcache_drop_region); @@ -615,12 +608,11 @@ EXPORT_SYMBOL_GPL(regcache_drop_region); */ void regcache_cache_only(struct regmap *map, bool enable) { - map->lock(map->lock_arg); + guard(regmap)(map); WARN_ON(map->cache_type != REGCACHE_NONE && map->cache_bypass && enable); map->cache_only = enable; trace_regmap_cache_only(map, enable); - map->unlock(map->lock_arg); } EXPORT_SYMBOL_GPL(regcache_cache_only); @@ -639,10 +631,9 @@ EXPORT_SYMBOL_GPL(regcache_cache_only); */ void regcache_mark_dirty(struct regmap *map) { - map->lock(map->lock_arg); + guard(regmap)(map); map->cache_dirty = true; map->no_sync_defaults = true; - map->unlock(map->lock_arg); } EXPORT_SYMBOL_GPL(regcache_mark_dirty); @@ -659,11 +650,10 @@ EXPORT_SYMBOL_GPL(regcache_mark_dirty); */ void regcache_cache_bypass(struct regmap *map, bool enable) { - map->lock(map->lock_arg); + guard(regmap)(map); WARN_ON(map->cache_only && enable); map->cache_bypass = enable; trace_regmap_cache_bypass(map, enable); - map->unlock(map->lock_arg); } EXPORT_SYMBOL_GPL(regcache_cache_bypass); @@ -680,12 +670,10 @@ bool regcache_reg_cached(struct regmap *map, unsigned int reg) unsigned int val; int ret; - map->lock(map->lock_arg); + guard(regmap)(map); ret = regcache_read(map, reg, &val); - map->unlock(map->lock_arg); - return ret == 0; } EXPORT_SYMBOL_GPL(regcache_reg_cached); From 555a1bd78bbc234342a8f3324a2487a56a118749 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Thu, 24 Sep 2026 14:30:54 +0800 Subject: [PATCH 06/15] regcache: rbtree: use the regmap scoped lock guard Convert the open-coded map->lock()/map->unlock() pair in rbtree_show() to guard(regmap)(). The locked region spans the whole function body up to the single return, so a function-scope guard drops the manual unlock with no functional change. Assisted-by: LLM Signed-off-by: Peng Fan Link: https://patch.msgid.link/20260924-regmap-lock-guard-v3-3-8a6127223c16@nxp.com Signed-off-by: Mark Brown --- drivers/base/regmap/regcache-rbtree.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/base/regmap/regcache-rbtree.c b/drivers/base/regmap/regcache-rbtree.c index 520d5f8ba3cd..e2feed6dbecf 100644 --- a/drivers/base/regmap/regcache-rbtree.c +++ b/drivers/base/regmap/regcache-rbtree.c @@ -141,7 +141,7 @@ static int rbtree_show(struct seq_file *s, void *ignored) int registers = 0; int this_registers, average; - map->lock(map->lock_arg); + guard(regmap)(map); mem_size = sizeof(*rbtree_ctx); @@ -168,8 +168,6 @@ static int rbtree_show(struct seq_file *s, void *ignored) seq_printf(s, "%d nodes, %d registers, average %d registers, used %zu bytes\n", nodes, registers, average, mem_size); - map->unlock(map->lock_arg); - return 0; } From 9999d5a07ee037d4653c7231884b0a1e45be801c Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Thu, 24 Sep 2026 14:30:55 +0800 Subject: [PATCH 07/15] regmap: debugfs: use the regmap scoped lock guard Convert the open-coded map->lock()/map->unlock() users in the debugfs cache_only and cache_bypass write handlers to the regmap scoped guard. regmap_cache_bypass_write_file() uses guard(regmap)() since the locked region runs to the return. regmap_cache_only_write_file() uses scoped_guard(regmap, ...) because the subsequent regcache_sync() must run with the lock released - it takes the regmap lock itself - so it stays outside the guarded scope exactly as before. No functional change. Assisted-by: LLM Signed-off-by: Peng Fan Link: https://patch.msgid.link/20260924-regmap-lock-guard-v3-4-8a6127223c16@nxp.com Signed-off-by: Mark Brown --- drivers/base/regmap/regmap-debugfs.c | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/drivers/base/regmap/regmap-debugfs.c b/drivers/base/regmap/regmap-debugfs.c index 18f1c60749fe..ec207548a6bb 100644 --- a/drivers/base/regmap/regmap-debugfs.c +++ b/drivers/base/regmap/regmap-debugfs.c @@ -471,18 +471,16 @@ static ssize_t regmap_cache_only_write_file(struct file *file, if (err) return count; - map->lock(map->lock_arg); - - if (new_val && !map->cache_only) { - dev_warn(map->dev, "debugfs cache_only=Y forced\n"); - add_taint(TAINT_USER, LOCKDEP_STILL_OK); - } else if (!new_val && map->cache_only) { - dev_warn(map->dev, "debugfs cache_only=N forced: syncing cache\n"); - require_sync = true; + scoped_guard(regmap, map) { + if (new_val && !map->cache_only) { + dev_warn(map->dev, "debugfs cache_only=Y forced\n"); + add_taint(TAINT_USER, LOCKDEP_STILL_OK); + } else if (!new_val && map->cache_only) { + dev_warn(map->dev, "debugfs cache_only=N forced: syncing cache\n"); + require_sync = true; + } + map->cache_only = new_val; } - map->cache_only = new_val; - - map->unlock(map->lock_arg); if (require_sync) { err = regcache_sync(map); @@ -513,7 +511,7 @@ static ssize_t regmap_cache_bypass_write_file(struct file *file, if (err) return count; - map->lock(map->lock_arg); + guard(regmap)(map); if (new_val && !map->cache_bypass) { dev_warn(map->dev, "debugfs cache_bypass=Y forced\n"); @@ -523,8 +521,6 @@ static ssize_t regmap_cache_bypass_write_file(struct file *file, } map->cache_bypass = new_val; - map->unlock(map->lock_arg); - return count; } From 81556a7aec2254ffb3a35284dad5db5d0cf2f1c7 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Fri, 2 Oct 2026 21:11:17 +0800 Subject: [PATCH 08/15] regcache: extract locked helpers to replace open-coded lock/unlock pairs The previous patch ("regcache: use the regmap scoped lock guard") deliberately left regcache_init() with explicit map->lock()/unlock() calls because the function has a goto err_* cleanup ladder and mixing goto with cleanup helpers in the same function is discouraged by cleanup.h. Solve this by extracting two static helpers: regcache_locked_op() - acquires the regmap lock via guard(regmap), invokes an int-returning cache_ops callback (init, populate), and returns its result. Returns 0 when the callback is NULL. regcache_locked_exit() - acquires the regmap lock via guard(regmap) and invokes the void-returning exit callback. Returns immediately when the callback is NULL. Because the lock scope is now entirely inside the helper functions, it never crosses a goto target in the caller, so the cleanup.h concern does not apply. Convert all four remaining open-coded lock/unlock sites in regcache_init() (init, populate, error-path exit) and the scoped_guard in regcache_exit() to use these helpers. No functional change. Assisted-by: LLM Signed-off-by: Peng Fan Link: https://patch.msgid.link/20261002-regmap-lock-2nd-v2-1-e172b0501c52@nxp.com Signed-off-by: Mark Brown --- drivers/base/regmap/regcache.c | 57 +++++++++++++++++----------------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c index 4c80db5d6f6f..ebfa27bb6f54 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -121,6 +121,28 @@ static void regcache_hw_exit(struct regmap *map) kfree(map->reg_defaults_raw); } +static int regcache_locked_op(struct regmap *map, + int (*op)(struct regmap *map), + const char *action) +{ + if (!op) + return 0; + + dev_dbg(map->dev, "%s %s cache\n", action, map->cache_ops->name); + guard(regmap)(map); + return op(map); +} + +static void regcache_locked_exit(struct regmap *map) +{ + if (!map->cache_ops->exit) + return; + + dev_dbg(map->dev, "Destroying %s cache\n", map->cache_ops->name); + guard(regmap)(map); + map->cache_ops->exit(map); +} + int regcache_init(struct regmap *map, const struct regmap_config *config) { bool sort_defaults = false; @@ -222,15 +244,9 @@ int regcache_init(struct regmap *map, const struct regmap_config *config) map->max_register_is_set = true; } - if (map->cache_ops->init) { - dev_dbg(map->dev, "Initializing %s cache\n", - map->cache_ops->name); - map->lock(map->lock_arg); - ret = map->cache_ops->init(map); - map->unlock(map->lock_arg); - if (ret) - goto err_free_reg_defaults; - } + ret = regcache_locked_op(map, map->cache_ops->init, "Initializing"); + if (ret) + goto err_free_reg_defaults; /* * Some devices such as PMICs don't have cache defaults, @@ -243,12 +259,8 @@ int regcache_init(struct regmap *map, const struct regmap_config *config) goto err_exit; } - if (map->cache_ops->populate && - (map->num_reg_defaults || map->reg_default_cb)) { - dev_dbg(map->dev, "Populating %s cache\n", map->cache_ops->name); - map->lock(map->lock_arg); - ret = map->cache_ops->populate(map); - map->unlock(map->lock_arg); + if (map->num_reg_defaults || map->reg_default_cb) { + ret = regcache_locked_op(map, map->cache_ops->populate, "Populating"); if (ret) goto err_free; } @@ -257,12 +269,7 @@ int regcache_init(struct regmap *map, const struct regmap_config *config) err_free: regcache_hw_exit(map); err_exit: - if (map->cache_ops->exit) { - dev_dbg(map->dev, "Destroying %s cache\n", map->cache_ops->name); - map->lock(map->lock_arg); - map->cache_ops->exit(map); - map->unlock(map->lock_arg); - } + regcache_locked_exit(map); err_free_reg_defaults: kfree(map->reg_defaults); @@ -277,13 +284,7 @@ void regcache_exit(struct regmap *map) BUG_ON(!map->cache_ops); regcache_hw_exit(map); - - if (map->cache_ops->exit) { - dev_dbg(map->dev, "Destroying %s cache\n", - map->cache_ops->name); - scoped_guard(regmap, map) - map->cache_ops->exit(map); - } + regcache_locked_exit(map); kfree(map->reg_defaults); } From 7ca8aa98e2ba76274b6e4e20390f740fd1207fd8 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Fri, 2 Oct 2026 21:11:18 +0800 Subject: [PATCH 09/15] regcache: extract __regcache_sync() to deduplicate sync dispatch regcache_sync() and regcache_sync_region() both open-code the same pattern: check cache_dirty, then dispatch to either cache_ops->sync() or regcache_default_sync(). Extract this into a static __regcache_sync() helper that both callers share. The helper checks cache_dirty and returns 0 early when the cache is clean, dispatches to the backend sync callback when present, and falls back to regcache_default_sync() otherwise. In regcache_sync_region(), the cache_dirty early-exit previously skipped setting map->async. With the helper, map->async is set to true before the call and restored to false unconditionally afterward, so when cache_dirty is false the flag is toggled and immediately restored - functionally equivalent. No functional change. Assisted-by: LLM Signed-off-by: Peng Fan Link: https://patch.msgid.link/20261002-regmap-lock-2nd-v2-2-e172b0501c52@nxp.com Signed-off-by: Mark Brown --- drivers/base/regmap/regcache.c | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c index ebfa27bb6f54..b486cd826b3c 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -22,6 +22,8 @@ static const struct regcache_ops *cache_types[] = { ®cache_flat_ops, }; +static int regcache_default_sync(struct regmap *map, unsigned int min, unsigned int max); + static int regcache_defaults_cmp(const void *a, const void *b) { const struct reg_default *x = a; @@ -143,6 +145,17 @@ static void regcache_locked_exit(struct regmap *map) map->cache_ops->exit(map); } +static int __regcache_sync(struct regmap *map, unsigned int min, unsigned int max) +{ + if (!map->cache_dirty) + return 0; + + if (map->cache_ops->sync) + return map->cache_ops->sync(map, min, max); + + return regcache_default_sync(map, min, max); +} + int regcache_init(struct regmap *map, const struct regmap_config *config) { bool sort_defaults = false; @@ -461,11 +474,7 @@ int regcache_sync(struct regmap *map) } map->cache_bypass = false; - if (map->cache_ops->sync) - sync_ret = map->cache_ops->sync(map, 0, map->max_register); - else - sync_ret = regcache_default_sync(map, 0, map->max_register); - + sync_ret = __regcache_sync(map, 0, map->max_register); if (sync_ret == 0) map->cache_dirty = false; @@ -545,17 +554,10 @@ int regcache_sync_region(struct regmap *map, unsigned int min, trace_regcache_sync(map, name, "start region"); - if (!map->cache_dirty) - goto out; - map->async = true; - if (map->cache_ops->sync) - ret = map->cache_ops->sync(map, min, max); - else - ret = regcache_default_sync(map, min, max); + ret = __regcache_sync(map, min, max); -out: /* Restore the bypass state */ map->cache_bypass = bypass; map->async = false; From 4e1b5ccf1eabdec851a443eab1b26118c5096482 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Fri, 2 Oct 2026 21:11:19 +0800 Subject: [PATCH 10/15] regcache: simplify control flow and reduce nesting Move the count guard into regcache_hw_init() so callers don't need to check before calling, and flatten conditional blocks that wrap a single call by using early returns instead. Merge the two separate reg_defaults / num_reg_defaults sanity checks into a single expression, and drop unnecessary else-after-return in regcache_lookup_reg() and regcache_sync_block(). No functional change. Assisted-by: LLM Signed-off-by: Peng Fan Link: https://patch.msgid.link/20261002-regmap-lock-2nd-v2-3-e172b0501c52@nxp.com Signed-off-by: Mark Brown --- drivers/base/regmap/regcache.c | 50 +++++++++++++++------------------- 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c index b486cd826b3c..136d6adf3120 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -58,12 +58,15 @@ static int regcache_count_cacheable_registers(struct regmap *map) return count; } -static int regcache_hw_init(struct regmap *map) +static int regcache_hw_init(struct regmap *map, int count) { int ret; unsigned int reg, val; void *tmp_buf; + if (!count) + return 0; + if (!map->reg_defaults_raw) { bool cache_bypass = map->cache_bypass; dev_dbg(map->dev, "No cache defaults, reading back from HW\n"); @@ -174,15 +177,8 @@ int regcache_init(struct regmap *map, const struct regmap_config *config) return 0; } - if (config->reg_defaults && !config->num_reg_defaults) { - dev_err(map->dev, - "Register defaults are set without the number!\n"); - return -EINVAL; - } - - if (config->num_reg_defaults && !config->reg_defaults) { - dev_err(map->dev, - "Register defaults number are set without the reg!\n"); + if (!!config->reg_defaults != !!config->num_reg_defaults) { + dev_err(map->dev, "reg_defaults and num_reg_defaults must both be specified\n"); return -EINVAL; } @@ -215,9 +211,7 @@ int regcache_init(struct regmap *map, const struct regmap_config *config) map->cache = NULL; map->cache_ops = cache_types[i]; - if (!map->cache_ops->read || - !map->cache_ops->write || - !map->cache_ops->name) + if (!map->cache_ops->read || !map->cache_ops->write || !map->cache_ops->name) return -EINVAL; /* We still need to ensure that the reg_defaults @@ -266,17 +260,17 @@ int regcache_init(struct regmap *map, const struct regmap_config *config) * we cope with this by reading back the HW registers and * crafting the cache defaults by hand. */ - if (count) { - ret = regcache_hw_init(map); - if (ret) - goto err_exit; - } + ret = regcache_hw_init(map, count); + if (ret) + goto err_exit; + + if (!map->num_reg_defaults && !map->reg_default_cb) + return 0; + + ret = regcache_locked_op(map, map->cache_ops->populate, "Populating"); + if (ret) + goto err_free; - if (map->num_reg_defaults || map->reg_default_cb) { - ret = regcache_locked_op(map, map->cache_ops->populate, "Populating"); - if (ret) - goto err_free; - } return 0; err_free: @@ -762,8 +756,8 @@ int regcache_lookup_reg(struct regmap *map, unsigned int reg) if (r) return r - map->reg_defaults; - else - return -ENOENT; + + return -ENOENT; } static bool regcache_reg_present(unsigned long *cache_present, unsigned int idx) @@ -900,7 +894,7 @@ int regcache_sync_block(struct regmap *map, void *block, if (regmap_can_raw_write(map) && !map->use_single_write) return regcache_sync_block_raw(map, block, cache_present, block_base, start, end); - else - return regcache_sync_block_single(map, block, cache_present, - block_base, start, end); + + return regcache_sync_block_single(map, block, cache_present, + block_base, start, end); } From 8a889c83e478074f90335791f7d2c8e2dc5b0552 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Fri, 2 Oct 2026 21:11:20 +0800 Subject: [PATCH 11/15] regmap: use krealloc_array() in regmap_register_patch() Replace krealloc() with krealloc_array() in regmap_register_patch() to gain the built-in overflow check on the element-count * element-size multiplication, and switch to the early-return error style to reduce nesting. Assisted-by: LLM Signed-off-by: Peng Fan Link: https://patch.msgid.link/20261002-regmap-lock-2nd-v2-4-e172b0501c52@nxp.com Signed-off-by: Mark Brown --- drivers/base/regmap/regmap.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index 0b40e1e7817c..b9830afa6d8e 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -3379,16 +3379,13 @@ int regmap_register_patch(struct regmap *map, const struct reg_sequence *regs, num_regs)) return 0; - p = krealloc(map->patch, - sizeof(struct reg_sequence) * (map->patch_regs + num_regs), - GFP_KERNEL); - if (p) { - memcpy(p + map->patch_regs, regs, num_regs * sizeof(*regs)); - map->patch = p; - map->patch_regs += num_regs; - } else { + p = krealloc_array(map->patch, map->patch_regs + num_regs, sizeof(*p), GFP_KERNEL); + if (!p) return -ENOMEM; - } + + memcpy(p + map->patch_regs, regs, num_regs * sizeof(*regs)); + map->patch = p; + map->patch_regs += num_regs; scoped_guard(regmap, map) { bypass = map->cache_bypass; From ca685a42b0fda83037a92e3fd58763aa033c124b Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Fri, 2 Oct 2026 21:11:21 +0800 Subject: [PATCH 12/15] regmap: fix potential double-free of map->reg_defaults on cache reinit regcache_exit() frees map->reg_defaults but does not NULL the pointer. If regmap_reinit_cache() is later called with a config that has neither reg_defaults nor num_reg_defaults_raw, regcache_init() skips both allocation branches and leaves the stale pointer in place. Should cache_ops->init then fail, the err_free_reg_defaults error path calls kfree(map->reg_defaults) a second time. NULL the pointer after freeing so the error-path kfree() is a harmless no-op. Assisted-by: LLM Signed-off-by: Peng Fan Link: https://patch.msgid.link/20261002-regmap-lock-2nd-v2-5-e172b0501c52@nxp.com Signed-off-by: Mark Brown --- drivers/base/regmap/regcache.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c index 136d6adf3120..3b1c2f28d585 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -294,6 +294,7 @@ void regcache_exit(struct regmap *map) regcache_locked_exit(map); kfree(map->reg_defaults); + map->reg_defaults = NULL; } /** From d64967dee82b117f3fcc4da71ccf5b9258670869 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Fri, 2 Oct 2026 21:11:22 +0800 Subject: [PATCH 13/15] regmap: debugfs: Use __free(kfree) in regmap_name_read_file() Use the __free(kfree) cleanup helper for the temporary buffer allocated by regmap_name_read_file(). Remove the explicit kfree() calls from the error and success paths. No functional change. Signed-off-by: Peng Fan Link: https://patch.msgid.link/20261002-regmap-lock-2nd-v2-6-e172b0501c52@nxp.com Signed-off-by: Mark Brown --- drivers/base/regmap/regmap-debugfs.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/drivers/base/regmap/regmap-debugfs.c b/drivers/base/regmap/regmap-debugfs.c index ec207548a6bb..0a9c27426f15 100644 --- a/drivers/base/regmap/regmap-debugfs.c +++ b/drivers/base/regmap/regmap-debugfs.c @@ -38,10 +38,9 @@ static ssize_t regmap_name_read_file(struct file *file, { struct regmap *map = file->private_data; const char *name = "nodev"; + char *buf __free(kfree) = kmalloc(PAGE_SIZE, GFP_KERNEL); int ret; - char *buf; - buf = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!buf) return -ENOMEM; @@ -49,14 +48,10 @@ static ssize_t regmap_name_read_file(struct file *file, name = map->dev->driver->name; ret = snprintf(buf, PAGE_SIZE, "%s\n", name); - if (ret >= PAGE_SIZE) { - kfree(buf); + if (ret >= PAGE_SIZE) return ret; - } - ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret); - kfree(buf); - return ret; + return simple_read_from_buffer(user_buf, count, ppos, buf, ret); } static const struct file_operations regmap_name_fops = { From a93de1d65c6b5e4d493640be7f1429faad47a584 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Fri, 2 Oct 2026 21:11:23 +0800 Subject: [PATCH 14/15] regmap: debugfs: Use guard to simplify code Replace open-coded mutex_lock()/unlock() pairs with guard() and scoped_guard() across the debugfs code. - regmap_debugfs_get_dump_start(): guard(mutex) replaces three unlock sites (kzalloc failure, cache hit, end-of-function). - regmap_next_readable_reg(): early return for the common case and scoped_guard for the cache fallback path. - regmap_reg_ranges_read_file(): scoped_guard wraps the cache iteration loop. - regmap_debugfs_init(): scoped_guard for single list_add. - regmap_debugfs_exit(): flatten if/else into early return and scoped_guard for the early-list cleanup. - regmap_debugfs_initcall(): guard(mutex) held to function end. No functional change. Signed-off-by: Peng Fan Link: https://patch.msgid.link/20261002-regmap-lock-2nd-v2-7-e172b0501c52@nxp.com Signed-off-by: Mark Brown --- drivers/base/regmap/regmap-debugfs.c | 63 +++++++++++++--------------- 1 file changed, 29 insertions(+), 34 deletions(-) diff --git a/drivers/base/regmap/regmap-debugfs.c b/drivers/base/regmap/regmap-debugfs.c index 0a9c27426f15..b2ae52a21331 100644 --- a/drivers/base/regmap/regmap-debugfs.c +++ b/drivers/base/regmap/regmap-debugfs.c @@ -107,7 +107,7 @@ static unsigned int regmap_debugfs_get_dump_start(struct regmap *map, * If we don't have a cache build one so we don't have to do a * linear scan each time. */ - mutex_lock(&map->cache_lock); + guard(mutex)(&map->cache_lock); i = base; if (list_empty(&map->debugfs_off_cache)) { for (; i <= map->max_register; i += map->reg_stride) { @@ -129,7 +129,6 @@ static unsigned int regmap_debugfs_get_dump_start(struct regmap *map, c = kzalloc_obj(*c); if (!c) { regmap_debugfs_free_dump_cache(map); - mutex_unlock(&map->cache_lock); return base; } c->min = p; @@ -162,14 +161,12 @@ static unsigned int regmap_debugfs_get_dump_start(struct regmap *map, fpos_offset = from - c->min; reg_offset = fpos_offset / map->debugfs_tot_len; *pos = c->min + (reg_offset * map->debugfs_tot_len); - mutex_unlock(&map->cache_lock); return c->base_reg + (reg_offset * map->reg_stride); } *pos = c->max; ret = c->max_reg; } - mutex_unlock(&map->cache_lock); return ret; } @@ -191,10 +188,10 @@ static int regmap_next_readable_reg(struct regmap *map, int reg) struct regmap_debugfs_off_cache *c; int ret = -EINVAL; - if (regmap_printable(map, reg + map->reg_stride)) { - ret = reg + map->reg_stride; - } else { - mutex_lock(&map->cache_lock); + if (regmap_printable(map, reg + map->reg_stride)) + return reg + map->reg_stride; + + scoped_guard(mutex, &map->cache_lock) { list_for_each_entry(c, &map->debugfs_off_cache, list) { if (reg > c->max_reg) continue; @@ -203,8 +200,8 @@ static int regmap_next_readable_reg(struct regmap *map, int reg) break; } } - mutex_unlock(&map->cache_lock); } + return ret; } @@ -393,19 +390,19 @@ static ssize_t regmap_reg_ranges_read_file(struct file *file, /* Reset file pointer as the fixed-format of the `registers' * file is not compatible with the `range' file */ p = 0; - mutex_lock(&map->cache_lock); - list_for_each_entry(c, &map->debugfs_off_cache, list) { - entry_len = snprintf(entry, PAGE_SIZE, "%x-%x\n", - c->base_reg, c->max_reg); - if (p >= *ppos) { - if (buf_pos + entry_len > count) - break; - memcpy(buf + buf_pos, entry, entry_len); - buf_pos += entry_len; + scoped_guard(mutex, &map->cache_lock) { + list_for_each_entry(c, &map->debugfs_off_cache, list) { + entry_len = snprintf(entry, PAGE_SIZE, "%x-%x\n", + c->base_reg, c->max_reg); + if (p >= *ppos) { + if (buf_pos + entry_len > count) + break; + memcpy(buf + buf_pos, entry, entry_len); + buf_pos += entry_len; + } + p += entry_len; } - p += entry_len; } - mutex_unlock(&map->cache_lock); kfree(entry); ret = buf_pos; @@ -552,9 +549,8 @@ void regmap_debugfs_init(struct regmap *map) if (!node) return; node->map = map; - mutex_lock(®map_debugfs_early_lock); - list_add(&node->link, ®map_debugfs_early_list); - mutex_unlock(®map_debugfs_early_lock); + scoped_guard(mutex, ®map_debugfs_early_lock) + list_add(&node->link, ®map_debugfs_early_list); return; } @@ -653,29 +649,29 @@ void regmap_debugfs_init(struct regmap *map) void regmap_debugfs_exit(struct regmap *map) { + struct regmap_debugfs_node *node, *tmp; + if (map->debugfs) { debugfs_remove_recursive(map->debugfs); - mutex_lock(&map->cache_lock); - regmap_debugfs_free_dump_cache(map); - mutex_unlock(&map->cache_lock); + scoped_guard(mutex, &map->cache_lock) + regmap_debugfs_free_dump_cache(map); if (map->debugfs_dummy_id >= 0) { ida_free(&dummy_ida, map->debugfs_dummy_id); map->debugfs_dummy_id = -1; } kfree(map->debugfs_name); map->debugfs_name = NULL; - } else { - struct regmap_debugfs_node *node, *tmp; - mutex_lock(®map_debugfs_early_lock); - list_for_each_entry_safe(node, tmp, ®map_debugfs_early_list, - link) { + return; + } + + scoped_guard(mutex, ®map_debugfs_early_lock) { + list_for_each_entry_safe(node, tmp, ®map_debugfs_early_list, link) { if (node->map == map) { list_del(&node->link); kfree(node); } } - mutex_unlock(®map_debugfs_early_lock); } } @@ -685,11 +681,10 @@ void regmap_debugfs_initcall(void) regmap_debugfs_root = debugfs_create_dir("regmap", NULL); - mutex_lock(®map_debugfs_early_lock); + guard(mutex)(®map_debugfs_early_lock); list_for_each_entry_safe(node, tmp, ®map_debugfs_early_list, link) { regmap_debugfs_init(node->map); list_del(&node->link); kfree(node); } - mutex_unlock(®map_debugfs_early_lock); } From f52260f49b107fddbcf7c75f9cf2347a5031f0f7 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Thu, 8 Oct 2026 08:49:45 +0800 Subject: [PATCH 15/15] regmap: debugfs: Use __free(kfree) for regmap_reg_ranges_read_file Convert buf and entry in regmap_reg_ranges_read_file() from manual kfree() to __free(kfree) scope-based cleanup. This eliminates the goto error path and the manual kfree(buf) on entry allocation failure, simplifying the function to use straight-line returns. Signed-off-by: Peng Fan Link: https://patch.msgid.link/20261008-regmap-debugfs-fix-v1-1-3df2f25c01b6@nxp.com Signed-off-by: Mark Brown --- drivers/base/regmap/regmap-debugfs.c | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/drivers/base/regmap/regmap-debugfs.c b/drivers/base/regmap/regmap-debugfs.c index b2ae52a21331..b62a5dce027b 100644 --- a/drivers/base/regmap/regmap-debugfs.c +++ b/drivers/base/regmap/regmap-debugfs.c @@ -358,8 +358,6 @@ static ssize_t regmap_reg_ranges_read_file(struct file *file, struct regmap_debugfs_off_cache *c; loff_t p = 0; size_t buf_pos = 0; - char *buf; - char *entry; int ret; unsigned int entry_len; @@ -369,15 +367,13 @@ static ssize_t regmap_reg_ranges_read_file(struct file *file, if (count > (PAGE_SIZE << MAX_PAGE_ORDER)) count = PAGE_SIZE << MAX_PAGE_ORDER; - buf = kmalloc(count, GFP_KERNEL); + char *buf __free(kfree) = kmalloc(count, GFP_KERNEL); if (!buf) return -ENOMEM; - entry = kmalloc(PAGE_SIZE, GFP_KERNEL); - if (!entry) { - kfree(buf); + char *entry __free(kfree) = kmalloc(PAGE_SIZE, GFP_KERNEL); + if (!entry) return -ENOMEM; - } /* While we are at it, build the register dump cache * now so the read() operation on the `registers' file @@ -404,17 +400,13 @@ static ssize_t regmap_reg_ranges_read_file(struct file *file, } } - kfree(entry); ret = buf_pos; - if (copy_to_user(user_buf, buf, buf_pos)) { - ret = -EFAULT; - goto out_buf; - } + if (copy_to_user(user_buf, buf, buf_pos)) + return -EFAULT; *ppos += buf_pos; -out_buf: - kfree(buf); + return ret; }