free linux kernel development course
free linux device drivers course
free embedded linux course
In this free Linux kernel development course lecture, we continue the regmap access table linux kernel topic from where the previous lecture on the Regmap API left off. Here we look at how regmap decides which registers can be written, read, or must never be cached, using a small set of optional tables and callbacks defined inside struct regmap_config. By the end of this lecture you will be able to write a real kernel 6.x driver that restricts register access using range tables instead of writing a callback function for every single register.
What You Will Learn
- Why regmap needs read/write/volatile permission checks at all
- How
struct regmap_access_tableworks withyes_rangesandno_ranges - The role of
max_registerin bounding valid register addresses - When and why a driver needs a custom
reg_read/reg_writecallback - How to build and test an original access-table based I2C driver on kernel 6.x
Prerequisites
- Basic knowledge of I2C client drivers (probe/remove, device tree matching)
- Familiarity with
struct regmap_configandstruct regmapfrom the previous lecture - A working kernel 6.x build environment (kernel headers + cross toolchain or QEMU)
Why Regmap Needs Access Control
Not every register on a real chip behaves the same way. Some registers are read-only status registers, some are write-only command registers, and some must never be read back because reading them clears an interrupt flag. Regmap needs a way to know this before it lets a driver touch hardware, otherwise a buggy driver could corrupt device state or the register cache. There are two ways to describe this to regmap: a per-register callback function, or a table of valid address ranges. Callbacks are useful when the rule is complex (for example “even addresses are read-only”). Tables are simpler and are the right choice when access rules are a handful of contiguous address ranges, which is the common case for most chips.
struct regmap_access_table Explained
When a driver does not provide a writeable_reg, readable_reg, or volatile_reg callback, regmap falls back to checking a table instead. This table is described by struct regmap_access_table, which holds two arrays of address ranges:
| Field | Meaning |
|---|---|
yes_ranges |
Array of struct regmap_range entries that ARE allowed |
n_yes_ranges |
Number of entries in yes_ranges |
no_ranges |
Array of struct regmap_range entries that are explicitly NOT allowed |
n_no_ranges |
Number of entries in no_ranges |
Note: On modern kernels the fields are named yes_ranges and no_ranges (plural, array form), not a single yes_range/no_range pointer. Always check include/linux/regmap.h in the kernel tree you are targeting, since these low-level struct field names can shift between kernel releases.
Each entry inside those arrays is a struct regmap_range, holding just a range_min and range_max register address. A register is considered part of a range if its address falls between range_min and range_max inclusive.
wr_table, rd_table, volatile_table
Once you have built one or more regmap_access_table structures, you attach them to struct regmap_config using three optional fields:
wr_table— used instead of awriteable_regcallback to decide which registers accept writesrd_table— used instead of areadable_regcallback to decide which registers accept readsvolatile_table— used instead of avolatile_regcallback to decide which registers must always be read from hardware and never served from the regmap cache
The lookup logic regmap applies is straightforward:
- If a register address is present in
no_ranges, access is denied. - Else if the register address is present in
yes_ranges, access is allowed. - Else, access is denied (fail closed by default).
max_register: Bounding The Address Space
The max_register field is a simple safety net. It tells regmap the highest valid register address that exists on the chip. Any access above this address is rejected immediately with -EIO, before regmap even checks tables or callbacks. This is useful for catching typos and off-by-one bugs in your own driver code early, and it also bounds how much memory the register cache allocates when you use a flat cache type.
Custom reg_read and reg_write Callbacks
Most I2C and SPI devices support regmap’s built-in bus transport, so regmap can generate the raw bus transaction for you automatically. Some devices, however, use a non-standard access sequence — for example a device that requires a special unlock sequence before every register write, or a device connected over a custom low-level bus that regmap does not natively support. For these cases, struct regmap_config exposes two function pointers:
int (*reg_read)(void *context, unsigned int reg, unsigned int *val);
int (*reg_write)(void *context, unsigned int reg, unsigned int val);
When these are set, regmap calls your function directly instead of generating an I2C/SPI transaction itself. This is an advanced escape hatch — the vast majority of drivers never need to set these, since devm_regmap_init_i2c() and devm_regmap_init_spi() already know how to talk to the bus correctly.
Original Driver Example: ep_regmap_range_demo
The following original example builds an I2C driver for kernel 6.x that uses wr_table to allow writes only to a small configuration register range, while a status register range stays read-only. This is a complete, compilable pattern, not copied from any book.
// ep_regmap_range_demo.c
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/regmap.h>
#include <linux/of.h>
#define EP_CFG_REG_MIN 0x00
#define EP_CFG_REG_MAX 0x03
#define EP_STATUS_REG_MIN 0x10
#define EP_STATUS_REG_MAX 0x13
static const struct regmap_range ep_wr_yes_ranges[] = {
{ .range_min = EP_CFG_REG_MIN, .range_max = EP_CFG_REG_MAX },
};
static const struct regmap_access_table ep_wr_table = {
.yes_ranges = ep_wr_yes_ranges,
.n_yes_ranges = ARRAY_SIZE(ep_wr_yes_ranges),
};
static const struct regmap_range ep_rd_yes_ranges[] = {
{ .range_min = EP_CFG_REG_MIN, .range_max = EP_CFG_REG_MAX },
{ .range_min = EP_STATUS_REG_MIN, .range_max = EP_STATUS_REG_MAX },
};
static const struct regmap_access_table ep_rd_table = {
.yes_ranges = ep_rd_yes_ranges,
.n_yes_ranges = ARRAY_SIZE(ep_rd_yes_ranges),
};
static const struct regmap_config ep_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = EP_STATUS_REG_MAX,
.wr_table = &ep_wr_table,
.rd_table = &ep_rd_table,
.cache_type = REGCACHE_RBTREE,
};
static int ep_regmap_range_probe(struct i2c_client *client)
{
struct regmap *map;
map = devm_regmap_init_i2c(client, &ep_regmap_config);
if (IS_ERR(map)) {
dev_err(&client->dev, "regmap init failed: %ld\n", PTR_ERR(map));
return PTR_ERR(map);
}
i2c_set_clientdata(client, map);
dev_info(&client->dev, "ep_regmap_range_demo probed successfully\n");
return 0;
}
static void ep_regmap_range_remove(struct i2c_client *client)
{
dev_info(&client->dev, "ep_regmap_range_demo removed\n");
}
static const struct of_device_id ep_regmap_of_match[] = {
{ .compatible = "ep,regmap-range-demo" },
{ }
};
MODULE_DEVICE_TABLE(of, ep_regmap_of_match);
static struct i2c_driver ep_regmap_range_driver = {
.driver = {
.name = "ep_regmap_range_demo",
.of_match_table = ep_regmap_of_match,
},
.probe = ep_regmap_range_probe,
.remove = ep_regmap_range_remove,
};
module_i2c_driver(ep_regmap_range_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("EmbeddedPathashala");
MODULE_DESCRIPTION("Original regmap access-table demo driver");
Notice the driver uses the modern kernel 6.3+ single-argument probe(struct i2c_client *client) signature and a void-returning remove(), and it never calls regmap_exit() because devm_regmap_init_i2c() ties the regmap’s lifetime to the device automatically.
Matching Device Tree Node
ep_sensor@50 {
compatible = "ep,regmap-range-demo";
reg = <0x50>;
};
Build And Test Walkthrough
$ make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
$ sudo insmod ep_regmap_range_demo.ko
$ dmesg | tail -n 3
Expected output:
[ 102.334521] i2c i2c-1: ep_regmap_range_demo probed successfully
If you attempt to write to a register above 0x03 from a small test path inside your driver (for example calling regmap_write(map, 0x11, 0x1)), the kernel log will show a rejected access:
[ 105.812004] ep_regmap_range_demo: regmap_write failed: -5
-5 is -EIO, confirming the write table is correctly blocking the status range.
Common Mistakes
| Mistake | Result |
|---|---|
| Setting both a callback and a table for the same check | Callback always wins; the table is silently ignored |
Forgetting max_register |
Out-of-range register accesses are not caught early |
Using old field names yes_range/no_range |
Build failure — modern kernels use yes_ranges/no_ranges |
Calling regmap_exit() on a devm-allocated regmap |
Double free at driver unbind |
Best Practices
- Prefer tables over callbacks whenever the access rule is a simple set of address ranges — tables are easier to read and maintain
- Always set
max_registereven when using tables, as an extra safety boundary - Always confirm current field names against the kernel version you are targeting
- Use
devm_regmap_init_i2c()/devm_regmap_init_spi()so you never need manual cleanup
Summary / Key Takeaways
regmap_access_tablelets you describe register access rules as address ranges instead of writing callback functionswr_table,rd_table, andvolatile_tableare the three optional table fields insidestruct regmap_configmax_registeris a simple upper bound check performed before any table or callback lookupreg_read/reg_writeare an advanced escape hatch for devices that do not fit the standard I2C/SPI transport
Conclusion
Access tables are one of the most practical parts of the regmap API because most real chips group their registers into a handful of readable/writable ranges rather than requiring per-register logic. Once you understand regmap_access_table, you can describe an entire chip’s access rules in a few lines instead of a large callback function. In the next lecture we move on to regmap initialization details for I2C and SPI, and the core regmap_read()/regmap_write()/regmap_update_bits() device access functions.
FAQ: Regmap Access Tables
What is the difference between a callback and an access table in regmap?
A callback is a function you write that returns true or false for a given register address. A table is a static list of address ranges. Tables are simpler for straightforward range-based rules; callbacks are needed for more complex logic.
Can I use both wr_table and writeable_reg together?
You can set both, but regmap only uses the callback if it is non-NULL. The table is only consulted when the corresponding callback pointer is NULL.
What happens if a register is in neither yes_ranges nor no_ranges?
Access is denied by default. Regmap only allows a register if it can positively confirm the register is inside a yes range.
Do I need volatile_table if I am not using a register cache?
No. volatile_table only matters when cache_type is not REGCACHE_NONE, since it controls which registers must bypass the cache.
Is max_register mandatory?
No, it is optional, but it is strongly recommended since it gives regmap an early, cheap boundary check before any table lookup happens.
When would I actually need a custom reg_read/reg_write callback?
Only for devices that cannot be accessed through a plain I2C or SPI transaction, such as chips needing a special unlock sequence or a custom bus protocol regmap does not know about natively.
Do access tables work the same way for SPI devices?
Yes. Access tables are part of struct regmap_config, which is shared across every bus type regmap supports, including I2C and SPI.
Continue The Free Linux Kernel Development Course
Master the regmap API step by step with original, kernel 6.x driver examples.
