linux 4.9
armv8
写一个复杂设备驱动时使用了regmap子系统,资料很少,所以只能自己去研究子系统的实现,大部分分析都在注释中,当做笔记。
关于该系统的作用,可以看这些文章:
简而言之,就是regmap子系统把一些低速接口的api函数给平台化了。之前如果要使用i2c,spi,中断irq等等都有不同的接口,但是现在只需要几个通用api就可以满足,比如说:
使用方法也很简单,很多资料,教程都有,如何初始化,如何配置等等。除此之外,regmap子系统还提供了缓存机制,可以选择哪些数据可以缓存,以及缓存方式。
这样做优化,比如说你可以设置i2c/spi等设备的哪些寄存器的值启用缓存,那么当我使用write更新了该寄存器之后,再使用read,就没必要再通过协议去寄存器中读取,而是直接去缓存中读取该寄存器的值就可以了。这片缓存区是由regmap子系统提供的,可以选择用数组的方式进行缓存,也可以生成红黑树来进行缓存。
本篇文章主要分析这个框架是怎么实现的,为什么能这么使用。
如果想使用i2c通信,那就要把regmap初始化为i2c通信模式,需要:
struct regmap = devm_regmap_init_i2c(struct i2c_client, struct regmap_config)
struct regmap结构体会保存很多regmap框架的信息,后续使用regmap_write()等函数都需要传入初始化后的结构体。
在此之前,有必要分析一下struct regmap_config这个结构体,配置regmap最主要的信息都来自这里:
include/linux/regmap.h:
struct regmap_config {
const char *name;
int reg_bits; // g, 寄存器bit数,说明一个寄存器要用8bit表示
int reg_stride;
int pad_bits;
int val_bits; // g, 一个数据要用8bit表示
bool (*writeable_reg)(struct device *dev, unsigned int reg); // g, 自己注册的可写检查函数,如果存在,则会再写入reg之前,会调用该函数检查寄存器是否可写
bool (*readable_reg)(struct device *dev, unsigned int reg); // g, 同上
bool (*volatile_reg)(struct device *dev, unsigned int reg); // g, 寄存器缓存控制函数,请看volatile_table表的注释
bool (*precious_reg)(struct device *dev, unsigned int reg); // g, 要求寄存器数值维持在一个数值范围才正确,maintain一个数值准确表
regmap_lock lock;
regmap_unlock unlock;
void *lock_arg;
// g, 有些设备的读写很复杂,不只是单纯按照i2c/spi协议进行读写,如果存在这种情况要手写对应的读写函数,注册到下面这俩。在读写时会优先使用自己注册的读写函数
int (*reg_read)(void *context, unsigned int reg, unsigned int *val);
int (*reg_write)(void *context, unsigned int reg, unsigned int val);
bool fast_io;
unsigned int max_register; // g, 最大寄存器地址,防止访问越界
// g, 如果没有注册writeable_reg函数,就要使用下面两个表来判断寄存器是否可以写,该表只有来个成员变量:start和end,地址在这个范围内的表示可以写
const struct regmap_access_table *wr_table;
const struct regmap_access_table *rd_table;
// g, 哪些寄存器可以缓存(volatile_table->yes_range域),哪些不可以(volatile_table->no_range域)。可要求读写立即生效的寄存器范围,no_range域范围中的reg不可以被cache
// g, regmap的缓存机制会把一些寄存器缓存到内存中,在读这些寄存器的时候就可以去缓存中去读。但是写的时候仍然要向物理设备中写入,同时更新缓存,所以缓存只影响读的性能
// g, 关于这个写缓存,看到一种不一样的说法:对于写操作,待cache存在一定数据量或者超出时间后写入物理寄存器,但降低实时性。具体什么情况还得有空看一下源码
const struct regmap_access_table *volatile_table;
const struct regmap_access_table *precious_table;
const struct reg_default *reg_defaults;
unsigned int num_reg_defaults;
enum regcache_type cache_type; // g, 若启用缓存,该域决定使用哪种缓存方式
const void *reg_defaults_raw;
unsigned int num_reg_defaults_raw;
unsigned long read_flag_mask; // g, 如果有需要,可以设置该掩码。那么regmap在发送读数据时就会把数据先 & mask再发送(有的spi设备只需要7位数据并且要求最高位必须为1时就有用了)
unsigned long write_flag_mask;
bool use_single_rw;
bool can_multi_write;
enum regmap_endian reg_format_endian; // g, 寄存器地址大小端,大于8位时需设置
enum regmap_endian val_format_endian; // g, 寄存器值大小端,大于8位时需设置
const struct regmap_range_cfg *ranges;
unsigned int num_ranges;
};
具体要启用哪些配置,需要按照需求来配,比如说:
// g, 配置方式:
static const struct regmap_range i2cdev_writeable_ranges[] = {
regmap_reg_range(可写寄存器起始地址, 可写寄存器结束地址),
};
static const struct regmap_range i2cdev_volatile_table[] = {
regmap_reg_range(可缓存寄存器起始地址, 可缓存寄存器结束地址),
};
static const struct regmap_access_table i2cdev_writeable_table = {
.yes_ranges = i2cdev_writeable_ranges, // g, 配置可写寄存器范围
.n_yes_ranges = ARRAY_SIZE(i2cdev_writeable_ranges), // g, 可写寄存器个数
// g, 没有配置不可写的范围,所以后续使用regmap_write时只会检查要写入的寄存器是否再可写寄存器范围内
};
static const struct regmap_access_table i2cdev_volatile_table = {
.yes_ranges = i2cdev_volatile_ranges, // g, 配置可缓存寄存器范围
.n_yes_ranges = ARRAY_SIZE(i2cdev_volatile_ranges), // g, 可缓存寄存器个数
// g, 没有配置不可写的范围
};
/* --------- 配置结构体----------------------*/
static const struct regmap_config i2cdev_regmap_config = {
.reg_bits = 8, // g, 配置寄存器地址由8bit表示
.val_bits = 8, // g, 配置一次传输的value为8bit
.wr_table = &i2cdev_writeable_table, // g, 配置可写/不可写寄存器的范围
.volatile_table = &i2cdev_volatile_table, // g, 配置可以缓存/不可以缓存的寄存器范围
.max_register = 最大寄存器地址,
.cache_type = REGCACHE_RBTREE, // g, 使用红黑树进行缓存
};
接下来就可以调用devm_regmap_init_i2c()来初始化regmap了:
struct i2c_client *i2c = i2c设备; // g, 挂在i2c bus的driver的probe中会传入匹配到的i2c设备的。
struct regmap *regmap = devm_regmap_init_i2c(i2c, i2cdev_regmap_config );
include/linux/regmap.h:
// g, 该宏__regmap_lockdep_wrapper会执行__devm_regmap_init_i2c(i2c, config, NULL, NULL)
#define devm_regmap_init_i2c(i2c, config) \
__regmap_lockdep_wrapper(__devm_regmap_init_i2c, #config, \
i2c, config)
最终会执行__devm_regmap_init_i2c(i2c, config, NULL, NULL):
drivers/base/regmap/regmap-i2c.c:
struct regmap *__devm_regmap_init_i2c(struct i2c_client *i2c,
const struct regmap_config *config,
struct lock_class_key *lock_key,
const char *lock_name)
{
// g, 该函数初始化i2c_bus,bus中可以提供.read, .write等函数,后续的regmap_write()等接口都会调用到map->bus->read/write
const struct regmap_bus *bus = regmap_get_i2c_bus(i2c, config);
if (IS_ERR(bus))
return ERR_CAST(bus);
// g, 该函数会初始化regmap的多个域,如 map->dev = dev; map->bus = bus;其余的域都是通过config进行初始化,与config中的各个域名几乎一模一样
// g, 其中关键函数有三个:regmap->reg_read(), regmap->reg_write(), regmao->rreg_update_bits(),但是这三个函数具体调用了什么先不看
// g, 在使用regmap时,regmap_read()会调用cache/map->reg_read, regmap_update_btits()会调用map->reg_update_bits(如果有的话) or _regmap_write->[map->reg_write]
// g, 对于map->reg_read()和map->reg_write(),并不是简单的调用map->bus->read/write等,而是会根据config提供的value_bits,reg_bits等做很多前后处理以及优化之后再调用bus提供的函数,很复杂
// g, 这里知道通过该函数可以设置i2c通信的参数,并且最终一定会调用到map->bus->read/write就可以了
return __devm_regmap_init(&i2c->dev, bus, &i2c->dev, config,
lock_key, lock_name);
}
该函数就做了两件事:
regmap作为一个平台化的工作,并且是一个出现在i2c、spi等子系统之后的子系统,肯定不会造轮子的。这一点从选择bus的函数regmap_get_i2c_bus()中就能看到:
drivers/base/regmap/regmap-i2c.c:
static const struct regmap_bus *regmap_get_i2c_bus(struct i2c_client *i2c,
const struct regmap_config *config)
{
// g, 该函数比较i2c->adapter->algo->functionality(adap)与后面的宏是否相等来判断该i2c总线使用什么i2c传输算法
// g, functionality()函数由总线设备提供,要求能通过该函数获得设备使用什么i2c协议
// g, 比如说有些设备支持普通i2c协议)(I2C_FUNC_I2C),有些设备支持i2c协议子集smbus
if (i2c_check_functionality(i2c->adapter, I2C_FUNC_I2C))
// g, 关于这个regmap_i2c,是一个静态结构体,提供了.write,.read等regmap框架的i2c读写函数
// g, 其底层实现仍然是使用i2c_transfer(),i2c_master_send()等i2c总线函数
return ®map_i2c;
// g, 也有可能i2c adapter支持的i2c的子集协议:SMBus,该协议明确了数据的传输格式,是面向命令的,我认为面向命令就是类似于CAN,指明每一帧是做什么的
else if (config->val_bits == 8 && config->reg_bits == 8 &&
i2c_check_functionality(i2c->adapter,
I2C_FUNC_SMBUS_I2C_BLOCK)) // g, 块写入/读取协议
return ®map_i2c_smbus_i2c_block;
else if (config->val_bits == 16 && config->reg_bits == 8 &&
i2c_check_functionality(i2c->adapter,
I2C_FUNC_SMBUS_WORD_DATA))
switch (regmap_get_val_endian(&i2c->dev, NULL, config)) {
case REGMAP_ENDIAN_LITTLE:
return ®map_smbus_word;
case REGMAP_ENDIAN_BIG:
return ®map_smbus_word_swapped;
default: /* everything else is not supported */
break;
}
else if (config->val_bits == 8 && config->reg_bits == 8 &&
i2c_check_functionality(i2c->adapter,
I2C_FUNC_SMBUS_BYTE_DATA))
return ®map_smbus_byte;
return ERR_PTR(-ENOTSUPP);
}
该函数根据adapter的类型,来选择合适的通信协议以及对应的通信函数。不考虑SMBus,就以一般i2c通信协议来说,最终选择的函数集合为:
drivers/base/regmap/regmap-i2c.c:
static struct regmap_bus regmap_i2c = {
.write = regmap_i2c_write,
.gather_write = regmap_i2c_gather_write,
.read = regmap_i2c_read,
.reg_format_endian_default = REGMAP_ENDIAN_BIG,
.val_format_endian_default = REGMAP_ENDIAN_BIG,
};
最终实现的函数,其实就是套皮的i2c通信函数,借助了i2c子系统。以write/read函数regmap_i2c_write/regmap_i2c_read为例:
drivers/base/regmap/regmap-i2c.c:
static int regmap_i2c_write(void *context, const void *data, size_t count)
{
struct device *dev = context;
struct i2c_client *i2c = to_i2c_client(dev);
int ret;
ret = i2c_master_send(i2c, data, count); // g, 套皮i2c
if (ret == count)
return 0;
else if (ret < 0)
return ret;
else
return -EIO;
}
...
static int regmap_i2c_read(void *context,
const void *reg, size_t reg_size,
void *val, size_t val_size)
{
struct device *dev = context;
struct i2c_client *i2c = to_i2c_client(dev);
struct i2c_msg xfer[2];
int ret;
xfer[0].addr = i2c->addr;
xfer[0].flags = 0;
xfer[0].len = reg_size;
xfer[0].buf = (void *)reg;
xfer[1].addr = i2c->addr;
xfer[1].flags = I2C_M_RD;
xfer[1].len = val_size;
xfer[1].buf = val;
ret = i2c_transfer(i2c->adapter, xfer, 2); // g, 套皮i2c
if (ret == 2)
return 0;
else if (ret < 0)
return ret;
else
return -EIO;
}
当我们使用regmap_write()/regmap_read()等regmap通信API时,最终会调用到上述绑定的通信函数中。
在选择完通信协议函数后,就需要使用选择的通信协议函数和配置参数struct regmap_config来初始化struct regmap了,执行这些工作的函数为__devm_regmap_init():
drivers/base/regmap/regmap.c:
struct regmap *__devm_regmap_init(struct device *dev,
const struct regmap_bus *bus,
void *bus_context,
const struct regmap_config *config,
struct lock_class_key *lock_key,
const char *lock_name)
{
struct regmap **ptr, *regmap;
ptr = devres_alloc(devm_regmap_release, sizeof(*ptr), GFP_KERNEL); // g, 添加的dev resource中,最终也会随dev的unregister而释放
if (!ptr)
return ERR_PTR(-ENOMEM);
regmap = __regmap_init(dev, bus, bus_context, config, // g, 初始化regmap
lock_key, lock_name);
if (!IS_ERR(regmap)) {
*ptr = regmap;
devres_add(dev, ptr);
} else {
devres_free(ptr);
}
return regmap;
}
其中regmap是作为dev resource绑定到dev->devres_head链表中去的,最终会在unregister的时候释放资源。
完成初始化工作的是函数__regmap_init():
struct regmap *__regmap_init(struct device *dev,
const struct regmap_bus *bus,
void *bus_context,
const struct regmap_config *config,
struct lock_class_key *lock_key,
const char *lock_name)
{
struct regmap *map;
int ret = -EINVAL;
enum regmap_endian reg_endian, val_endian;
int i, j;
if (!config)
goto err;
map = kzalloc(sizeof(*map), GFP_KERNEL);
if (map == NULL) {
ret = -ENOMEM;
goto err;
}
if (config->lock && config->unlock) {
map->lock = config->lock;
map->unlock = config->unlock;
map->lock_arg = config->lock_arg;
} else {
if ((bus && bus->fast_io) ||
config->fast_io) {
spin_lock_init(&map->spinlock);
map->lock = regmap_lock_spinlock;
map->unlock = regmap_unlock_spinlock;
lockdep_set_class_and_name(&map->spinlock,
lock_key, lock_name);
} else {
mutex_init(&map->mutex);
map->lock = regmap_lock_mutex;
map->unlock = regmap_unlock_mutex;
lockdep_set_class_and_name(&map->mutex,
lock_key, lock_name);
}
map->lock_arg = map;
}
/*
* When we write in fast-paths with regmap_bulk_write() don't allocate
* scratch buffers with sleeping allocations.
*/
if ((bus && bus->fast_io) || config->fast_io)
map->alloc_flags = GFP_ATOMIC;
else
map->alloc_flags = GFP_KERNEL;
map->format.reg_bytes = DIV_ROUND_UP(config->reg_bits, 8);
map->format.pad_bytes = config->pad_bits / 8;
map->format.val_bytes = DIV_ROUND_UP(config->val_bits, 8);
map->format.buf_size = DIV_ROUND_UP(config->reg_bits +
config->val_bits + config->pad_bits, 8);
map->reg_shift = config->pad_bits % 8; // g, 0
if (config->reg_stride)
map->reg_stride = config->reg_stride;
else
map->reg_stride = 1;
if (is_power_of_2(map->reg_stride))
map->reg_stride_order = ilog2(map->reg_stride);
else
map->reg_stride_order = -1;
map->use_single_read = config->use_single_rw || !bus || !bus->read;
map->use_single_write = config->use_single_rw || !bus || !bus->write;
map->can_multi_write = config->can_multi_write && bus && bus->write;
if (bus) {
map->max_raw_read = bus->max_raw_read;
map->max_raw_write = bus->max_raw_write;
}
map->dev = dev; // g, 设置为&i2c->dev
map->bus = bus;
map->bus_context = bus_context;
map->max_register = config->max_register;
map->wr_table = config->wr_table;
map->rd_table = config->rd_table;
map->volatile_table = config->volatile_table;
map->precious_table = config->precious_table;
map->writeable_reg = config->writeable_reg;
map->readable_reg = config->readable_reg;
map->volatile_reg = config->volatile_reg;
map->precious_reg = config->precious_reg;
map->cache_type = config->cache_type;
map->name = config->name;
spin_lock_init(&map->async_lock);
INIT_LIST_HEAD(&map->async_list);
INIT_LIST_HEAD(&map->async_free);
init_waitqueue_head(&map->async_waitq);
if (config->read_flag_mask || config->write_flag_mask) {
map->read_flag_mask = config->read_flag_mask;
map->write_flag_mask = config->write_flag_mask;
} else if (bus) {
map->read_flag_mask = bus->read_flag_mask;
}
if (!bus) { // g, 对于i2c设备来说一般adapter只会是i2c协议或者子集协议SMBus,所以一般都会有对应的regmap_bus
map->reg_read = config->reg_read;
map->reg_write = config->reg_write;
map->defer_caching = false;
goto skip_format_initialization;
} else if (!bus->read || !bus->write) { // g, 两个只要有一个不存在,就要走这里,不存在的就为NULL
map->reg_read = _regmap_bus_reg_read;
map->reg_write = _regmap_bus_reg_write;
map->defer_caching = false;
goto skip_format_initialization;
} else { // g, 如果两个都存在
map->reg_read = _regmap_bus_read; // g, 最终会调用map->bus->read
map->reg_update_bits = bus->reg_update_bits; // g, 可能为空,有的regmap_bus没有提供该函数,如果没有提供的话,后续使用regmap->reg_update_bits()就会先read,然后置位bit,再write这样操作
}
reg_endian = regmap_get_reg_endian(bus, config);
val_endian = regmap_get_val_endian(dev, bus, config);
switch (config->reg_bits + map->reg_shift) { // 8 + 0
case 2:
switch (config->val_bits) {
case 6:
map->format.format_write = regmap_format_2_6_write;
break;
default:
goto err_map;
}
break;
case 4:
switch (config->val_bits) {
case 12:
map->format.format_write = regmap_format_4_12_write;
break;
default:
goto err_map;
}
break;
case 7:
switch (config->val_bits) {
case 9:
map->format.format_write = regmap_format_7_9_write;
break;
default:
goto err_map;
}
break;
case 10:
switch (config->val_bits) {
case 14:
map->format.format_write = regmap_format_10_14_write;
break;
default:
goto err_map;
}
break;
case 8:
map->format.format_reg = regmap_format_8;
break;
case 16:
switch (reg_endian) {
case REGMAP_ENDIAN_BIG:
map->format.format_reg = regmap_format_16_be;
break;
case REGMAP_ENDIAN_LITTLE:
map->format.format_reg = regmap_format_16_le;
break;
case REGMAP_ENDIAN_NATIVE:
map->format.format_reg = regmap_format_16_native;
break;
default:
goto err_map;
}
break;
case 24:
if (reg_endian != REGMAP_ENDIAN_BIG)
goto err_map;
map->format.format_reg = regmap_format_24;
break;
case 32:
switch (reg_endian) {
case REGMAP_ENDIAN_BIG:
map->format.format_reg = regmap_format_32_be;
break;
case REGMAP_ENDIAN_LITTLE:
map->format.format_reg = regmap_format_32_le;
break;
case REGMAP_ENDIAN_NATIVE:
map->format.format_reg = regmap_format_32_native;
break;
default:
goto err_map;
}
break;
#ifdef CONFIG_64BIT
case 64:
switch (reg_endian) {
case REGMAP_ENDIAN_BIG:
map->format.format_reg = regmap_format_64_be;
break;
case REGMAP_ENDIAN_LITTLE:
map->format.format_reg = regmap_format_64_le;
break;
case REGMAP_ENDIAN_NATIVE:
map->format.format_reg = regmap_format_64_native;
break;
default:
goto err_map;
}
break;
#endif
default:
goto err_map;
}
if (val_endian == REGMAP_ENDIAN_NATIVE)
map->format.parse_inplace = regmap_parse_inplace_noop;
switch (config->val_bits) {
case 8:
map->format.format_val = regmap_format_8; // g, config->val_bits = 8这里
map->format.parse_val = regmap_parse_8;
map->format.parse_inplace = regmap_parse_inplace_noop;
break;
case 16:
switch (val_endian) {
case REGMAP_ENDIAN_BIG:
map->format.format_val = regmap_format_16_be;
map->format.parse_val = regmap_parse_16_be;
map->format.parse_inplace = regmap_parse_16_be_inplace;
break;
case REGMAP_ENDIAN_LITTLE:
map->format.format_val = regmap_format_16_le;
map->format.parse_val = regmap_parse_16_le;
map->format.parse_inplace = regmap_parse_16_le_inplace;
break;
case REGMAP_ENDIAN_NATIVE:
map->format.format_val = regmap_format_16_native;
map->format.parse_val = regmap_parse_16_native;
break;
default:
goto err_map;
}
break;
case 24:
if (val_endian != REGMAP_ENDIAN_BIG)
goto err_map;
map->format.format_val = regmap_format_24;
map->format.parse_val = regmap_parse_24;
break;
case 32:
switch (val_endian) {
case REGMAP_ENDIAN_BIG:
map->format.format_val = regmap_format_32_be;
map->format.parse_val = regmap_parse_32_be;
map->format.parse_inplace = regmap_parse_32_be_inplace;
break;
case REGMAP_ENDIAN_LITTLE:
map->format.format_val = regmap_format_32_le;
map->format.parse_val = regmap_parse_32_le;
map->format.parse_inplace = regmap_parse_32_le_inplace;
break;
case REGMAP_ENDIAN_NATIVE:
map->format.format_val = regmap_format_32_native;
map->format.parse_val = regmap_parse_32_native;
break;
default:
goto err_map;
}
break;
#ifdef CONFIG_64BIT
case 64:
switch (val_endian) {
case REGMAP_ENDIAN_BIG:
map->format.format_val = regmap_format_64_be;
map->format.parse_val = regmap_parse_64_be;
map->format.parse_inplace = regmap_parse_64_be_inplace;
break;
case REGMAP_ENDIAN_LITTLE:
map->format.format_val = regmap_format_64_le;
map->format.parse_val = regmap_parse_64_le;
map->format.parse_inplace = regmap_parse_64_le_inplace;
break;
case REGMAP_ENDIAN_NATIVE:
map->format.format_val = regmap_format_64_native;
map->format.parse_val = regmap_parse_64_native;
break;
default:
goto err_map;
}
break;
#endif
}
if (map->format.format_write) {
if ((reg_endian != REGMAP_ENDIAN_BIG) ||
(val_endian != REGMAP_ENDIAN_BIG))
goto err_map;
map->use_single_write = true;
}
if (!map->format.format_write &&
!(map->format.format_reg && map->format.format_val))
goto err_map;
map->work_buf = kzalloc(map->format.buf_size, GFP_KERNEL);
if (map->work_buf == NULL) {
ret = -ENOMEM;
goto err_map;
}
if (map->format.format_write) {
map->defer_caching = false;
map->reg_write = _regmap_bus_formatted_write;
} else if (map->format.format_val) { // g, 因为config->val_bits = 8,所以format_val被设置为regmap_format_8
map->defer_caching = true;
map->reg_write = _regmap_bus_raw_write; // g, 所以最终的reg_write是这个函数,这个函数有点复杂
}
skip_format_initialization:
map->range_tree = RB_ROOT;
for (i = 0; i < config->num_ranges; i++) {
const struct regmap_range_cfg *range_cfg = &config->ranges[i];
struct regmap_range_node *new;
/* Sanity check */
if (range_cfg->range_max < range_cfg->range_min) {
dev_err(map->dev, "Invalid range %d: %d < %d\n", i,
range_cfg->range_max, range_cfg->range_min);
goto err_range;
}
if (range_cfg->range_max > map->max_register) {
dev_err(map->dev, "Invalid range %d: %d > %d\n", i,
range_cfg->range_max, map->max_register);
goto err_range;
}
if (range_cfg->selector_reg > map->max_register) {
dev_err(map->dev,
"Invalid range %d: selector out of map\n", i);
goto err_range;
}
if (range_cfg->window_len == 0) {
dev_err(map->dev, "Invalid range %d: window_len 0\n",
i);
goto err_range;
}
/* Make sure, that this register range has no selector
or data window within its boundary */
for (j = 0; j < config->num_ranges; j++) {
unsigned sel_reg = config->ranges[j].selector_reg;
unsigned win_min = config->ranges[j].window_start;
unsigned win_max = win_min +
config->ranges[j].window_len - 1;
/* Allow data window inside its own virtual range */
if (j == i)
continue;
if (range_cfg->range_min <= sel_reg &&
sel_reg <= range_cfg->range_max) {
dev_err(map->dev,
"Range %d: selector for %d in window\n",
i, j);
goto err_range;
}
if (!(win_max < range_cfg->range_min ||
win_min > range_cfg->range_max)) {
dev_err(map->dev,
"Range %d: window for %d in window\n",
i, j);
goto err_range;
}
}
new = kzalloc(sizeof(*new), GFP_KERNEL);
if (new == NULL) {
ret = -ENOMEM;
goto err_range;
}
new->map = map;
new->name = range_cfg->name;
new->range_min = range_cfg->range_min;
new->range_max = range_cfg->range_max;
new->selector_reg = range_cfg->selector_reg;
new->selector_mask = range_cfg->selector_mask;
new->selector_shift = range_cfg->selector_shift;
new->window_start = range_cfg->window_start;
new->window_len = range_cfg->window_len;
if (!_regmap_range_add(map, new)) {
dev_err(map->dev, "Failed to add range %d\n", i);
kfree(new);
goto err_range;
}
if (map->selector_work_buf == NULL) {
map->selector_work_buf =
kzalloc(map->format.buf_size, GFP_KERNEL);
if (map->selector_work_buf == NULL) {
ret = -ENOMEM;
goto err_range;
}
}
}
ret = regcache_init(map, config);
if (ret != 0)
goto err_range;
if (dev) {
ret = regmap_attach_dev(dev, map, config);
if (ret != 0)
goto err_regcache;
}
return map;
err_regcache:
regcache_exit(map);
err_range:
regmap_range_exit(map);
kfree(map->work_buf);
err_map:
kfree(map);
err:
return ERR_PTR(ret);
}
这个函数还是比较复杂的,但是工作倒是很简单,就是初始化struct regmap的各个域,如可写/可读/可缓存寄存器区间,缓存方式,寄存器bit数,数据bit数等等这些我们在struct regmap_config结构体中见过的选项。
其中最重要的无非就是实现了regmap->regmap_read/regmap->regamp_write/regmap->regmap_update_bits等通信API函数。
这些API的实现,并不一定是直接显式调用了刚刚获取到的regmap_bus->api,而是会在其中穿插各种检查操作(比如对传入的寄存器是否可写/可读先进行检查)、缓存优化操作(该寄存器是否位于可缓存寄存器区间,如果是的话直接从rbtree的cache中读取)等等。
但是一旦进行到通信环节(检查也没出错,也没从缓存中去获取寄存器值),一定会调用到regmap_bus中提供的通信API。可以直接在内核中加dump_stack看下函数调用栈就知道了,以regmap_write()为例:
[ 192.783925] Call trace:
[ 192.783943] [<ffffff80080896d0>] dump_backtrace+0x0/0x210
[ 192.783953] [<ffffff8008089904>] show_stack+0x24/0x30
[ 192.783964] [<ffffff8008344b6c>] dump_stack+0x88/0xb0
[ 192.783977] [<ffffff800843bff0>] regmap_i2c_write+0x28/0x64 // g, 调用到regmap_i2c_write
[ 192.783986] [<ffffff80084376ec>] _regmap_raw_write+0x470/0x714
[ 192.783995] [<ffffff8008437a04>] _regmap_bus_raw_write+0x74/0x84
[ 192.784004] [<ffffff80084367e4>] _regmap_write+0xb0/0x150
[ 192.784012] [<ffffff8008437d44>] regmap_write+0x50/0x78
最终一定会调用到regmap_i2c_write()。这也说明该设备挂载的i2c总线,其i2c->adapter使用的是 I2C_FUNC_I2C协议,就是一般的I2C协议。
在这里需要对regmap_update_bits()这个api分析一下,因为这个api使用比较频繁,作用就是更新某个寄存器中的某一位。该函数最终会调用到_regmap_update_bits(map, reg, mask, val):
static int _regmap_update_bits(struct regmap *map, unsigned int reg,
unsigned int mask, unsigned int val,
bool *change, bool force_write)
{
int ret;
unsigned int tmp, orig;
if (change)
*change = false;
// g, regmap初始化时获取的map没有提供reg_update_bits,所以用else后面的regmap默认的处理update_bits的函数
if (regmap_volatile(map, reg) && map->reg_update_bits) {
ret = map->reg_update_bits(map->bus_context, reg, mask, val);
if (ret == 0 && change)
*change = true;
} else {
// g, 走下面的流程
ret = _regmap_read(map, reg, &orig);
if (ret != 0)
return ret;
tmp = orig & ~mask;
tmp |= val & mask;
if (force_write || (tmp != orig)) {
ret = _regmap_write(map, reg, tmp);
if (ret == 0 && change)
*change = true;
}
}
return ret;
}
regmap默认的更新某个寄存器的某个bit的处理方式,就是先读取寄存器,然后修改该位,再写回。最终寄存器中的结果与传入参数的关系是:
reg = (orig & ~mask) | (val & mask)
至此就完成了regmap-i2c功能的初始化,现在完全可以用regmap提供的api实现i2c通信。如果想实现spi通信需要按照spi初始化的方法,调用devm_regmap_init_spi()。
regmap不止提供了平台化i2c、spi协议的功能,还能平台化irq。对于一些多功能设备来讲,内部有多个中断源,但中断触发引脚只有一个。为了实现驱动程序的跨平台,不希望这些中断源的irq被硬编码在板级代码中,所以可以使用regmap的irq接口来管理。
在regmap的irq初始化过程中,涉及到了irq子系统的种种,最好了解过irq子系统的一些机制才能搞清楚regmap是如何赋予一个复杂设备irq功能的。
在此之前,可以先思考一下,对于内部有多个中断源,但是只有一个中断接口接到上级控制器的设备,应该怎么处理呢?或者说,应该怎么判断到底是哪个中断源引起了中断呢?一般有四种方法:
这里先做个总结,总结里提到的具体的结构体,函数接口等接下来都会慢慢分析。因为我对中断控制器的原理,不是很熟悉,了解的不深,所以接下来的分析可能有误,只是说一下我的分析过程
首先说一下如何使用regmap注册中断。接上一节分析的regmap-i2c,调用了devm_regmap_init_i2c()函数将regmap的通信方式初始化为i2c通信之后,返回了一个struct regmap指针:
struct regmap *regmap = devm_regmap_init_i2c(i2c, i2cdev_regmap_config );
现在需要调用另外一个接口,来将设备注册为一个中断控制器,也就是设备树中的controller设备:
struct i2c_client *i2c = i2c设备; // g, 设备树解析生成的i2c设备
ret = regmap_add_irq_chip(regmap, i2c->irq,
IRQF_ONESHOT | IRQF_SHARED, -1,
regmap_irq_chip,
regmap_irq_chipdata);
该接口需要传入的参数:
重点在于第5点,该结构体类似于初始化i2c通信时传入的结构体struct regmap_config,都是存储配置信息的,该结构体定义如下:
struct regmap_irq_chip {
const char *name; // g, 中断控制器(IRQ controller)的名字,现在这个设备要被注册为一个controller了
unsigned int status_base; // g, 状态寄存器基址
unsigned int mask_base; // g, 掩码寄存器基址,每一个中断源对应一个bit,掩码寄存器的主要作用是用来屏蔽某一个中断
unsigned int unmask_base;
unsigned int ack_base;
unsigned int wake_base;
unsigned int type_base;
unsigned int irq_reg_stride;
bool init_ack_masked:1;
bool mask_invert:1;
bool use_ack:1;
bool ack_invert:1;
bool wake_invert:1;
bool runtime_pm:1;
bool type_invert:1;
int num_regs;
const struct regmap_irq *irqs; // g, 关键结构体,描述了中断信息
int num_irqs;
int num_type_reg;
unsigned int type_reg_stride;
int (*handle_pre_irq)(void *irq_drv_data);
int (*handle_post_irq)(void *irq_drv_data);
void *irq_drv_data;
};
其中最重要的一个域是irqs,是一个struct regmap_irq结构体:
// g, 中断状态寄存器,如果是8位的话,就可以存储8个中断源,所以需要有mask掩码(只有8位中的1位被置1),来判断产生的中断是哪个中断
struct regmap_irq {
unsigned int reg_offset; // g, 相对于基址的偏移量
unsigned int mask; // g, 是用于标记/控制此中断状态寄存器的掩码,通常来讲,&该掩码的结果就是某个中断源的状态。
unsigned int type_reg_offset; // g, 没看明白,应该是有的PIC中为每个中断还设置了个type位,但是GIC有吗?没有吧,所以这个结构体中的域应该是选配的
unsigned int type_rising_mask; // g, 上升沿中断的掩码,对应上升沿中断(设备树中:interrupts = ;)
unsigned int type_falling_mask; // g, 下降沿中断的掩码
};
对于我使用的设备来讲,设备关于中断的寄存器有四个,分别是:
这里后续放几张PMIC的dataset图,截取中断使能/状态寄存器的基址图辅助展示
那我就需要静态创建对应的配置结构体:
static const struct regmap_irq i2cdev_regmap_irqs[] = {
[0] = {.reg_offset = 0, .mask = BIT(0)},
[1] = {.reg_offset = 0, .mask = BIT(1)},
// g, [2]不需要,初始化为0
[3] = {.reg_offset = 0, .mask = BIT(3)},
[4] = {.reg_offset = 0, .mask = BIT(4)},
[5] = {.reg_offset = 0, .mask = BIT(5)},
[6] = {.reg_offset = 0, .mask = BIT(6)},
[7] = {.reg_offset = 0, .mask = BIT(7)},
[8] = {.reg_offset = 1, .mask = BIT(0)},
[9] = {.reg_offset = 1, .mask = BIT(1)},
// g, 不需要,会被初始化为0
[12] = {.reg_offset = 1, .mask = BIT(4)},
[13] = {.reg_offset = 1, .mask = BIT(5)},
[14] = {.reg_offset = 1, .mask = BIT(6)},
// g, 不需要,会被初始化为0
...
// g, 最终会用 mask_base + reg_offset这个寄存器中来 & .mask
};
static const struct regmap_irq_chip i2cdev_regmap_irq_chip = {
.name = "i2cdevirq",
.status_base = 0x48, // g, 0x48,状态寄存器基址,需要通过状态寄存器获取真正的中断向量号
.ack_base = 0x48, // g, 0x48,ack寄存器基址,有些中断控制器好像是通过ACK寄存器来获取中断向量号的,比如powerpc用的MPIC?
.mask_base = 0x40, // g, 0x40,中断enable/disable寄存器基址
.mask_invert = true, // g, 从初始化函数中来看,该域为true会将中断控制寄存器初始化并且全部清0,所有中断全部disable
.init_ack_masked = true, // g, 从初始化函数中来看,该域为true会初始化中断状态寄存器,如果ack_invert域为true(我们没设置),则全部清0,否则全部置1
.irqs = i2cdev_regmap_irqs, // g, 中断号对应关系
.num_irqs = ARRAY_SIZE(i2cdev_regmap_irqs),
.num_regs = 2,
};
回到regmap_add_irq_chip()函数本身,分析初始化过程:
int regmap_add_irq_chip(struct regmap *map, int irq, int irq_flags,
int irq_base, const struct regmap_irq_chip *chip,
struct regmap_irq_chip_data **data)
{
struct regmap_irq_chip_data *d;
int i;
int ret = -ENOMEM;
u32 reg;
u32 unmask_offset;
if (chip->num_regs <= 0)
return -EINVAL;
// g, 检测中断配置中的reg地址的offset是否配置错误,一共16个子中断
// g, 一开始这里分析的时候犯了个错误,c初始化结构体的时候如果[1],[3],..,虽然跳过了[2],但是只是初始化为0,仍然占用size
for (i = 0; i < chip->num_irqs; i++) {
if (chip->irqs[i].reg_offset % map->reg_stride) // g, 在初始化regmap时如果config没有设置stride,默认为1
return -EINVAL;
if (chip->irqs[i].reg_offset / map->reg_stride >=
chip->num_regs)
return -EINVAL;
}
if (irq_base) { // g, irq_base = -1
// g, 此宏用来申请irq编号(virq,虚拟中断号),以及申请irq_desc结构体(中断描述符)
// g, 每一个irq_desc对应一个virq,保存了该irq的所有信息。这里我们分配了chip->num_irqs个子中断号以及对应的irq_desc
// g, 该宏最终返回的值为start,也就是真正申请的这一段virq的起始编号,从现在开始irq_base = start
// g, in
irq_base = irq_alloc_descs(irq_base, 0, chip->num_irqs, 0);
if (irq_base < 0) {
dev_warn(map->dev, "Failed to allocate IRQs: %d\n",
irq_base);
return irq_base;
}
}
d = kzalloc(sizeof(*d), GFP_KERNEL);
if (!d)
return -ENOMEM;
d->status_buf = kcalloc(chip->num_regs, sizeof(unsigned int),
GFP_KERNEL);
if (!d->status_buf)
goto err_alloc;
d->mask_buf = kcalloc(chip->num_regs, sizeof(unsigned int),
GFP_KERNEL);
if (!d->mask_buf)
goto err_alloc;
d->mask_buf_def = kcalloc(chip->num_regs, sizeof(unsigned int),
GFP_KERNEL);
if (!d->mask_buf_def)
goto err_alloc;
if (chip->wake_base) {
d->wake_buf = kcalloc(chip->num_regs, sizeof(unsigned int),
GFP_KERNEL);
if (!d->wake_buf)
goto err_alloc;
}
if (chip->num_type_reg) {
d->type_buf_def = kcalloc(chip->num_type_reg,
sizeof(unsigned int), GFP_KERNEL);
if (!d->type_buf_def)
goto err_alloc;
d->type_buf = kcalloc(chip->num_type_reg, sizeof(unsigned int),
GFP_KERNEL);
if (!d->type_buf)
goto err_alloc;
}
d->irq_chip = regmap_irq_chip;
d->irq_chip.name = chip->name;
d->irq = irq;
d->map = map;
d->chip = chip;
d->irq_base = irq_base;
if (chip->irq_reg_stride)
d->irq_reg_stride = chip->irq_reg_stride;
else
d->irq_reg_stride = 1;
if (chip->type_reg_stride)
d->type_reg_stride = chip->type_reg_stride;
else
d->type_reg_stride = 1;
if (!map->use_single_read && map->reg_stride == 1 &&
d->irq_reg_stride == 1) {
d->status_reg_buf = kmalloc_array(chip->num_regs,
map->format.val_bytes,
GFP_KERNEL);
if (!d->status_reg_buf)
goto err_alloc;
}
mutex_init(&d->lock);
for (i = 0; i < chip->num_irqs; i++)
d->mask_buf_def[chip->irqs[i].reg_offset / map->reg_stride]
|= chip->irqs[i].mask; // g, d->mask_buf_def的默认值会设置为传入的chip->irqs[i].mask,也就是BIT(0),BIT(1),...
/* Mask all the interrupts by default */
for (i = 0; i < chip->num_regs; i++) {
d->mask_buf[i] = d->mask_buf_def[i];
reg = chip->mask_base +
(i * map->reg_stride * d->irq_reg_stride);
if (chip->mask_invert) // g, mask_invert域为true,reg = (orig & ~mask) | (val & mask)
ret = regmap_update_bits(map, reg,
d->mask_buf[i], ~d->mask_buf[i]); // g, reg = (orig & ~BIT(x)) | (~BIT(x) & BIT(x)),相当于清空该位
else if (d->chip->unmask_base) {
unmask_offset = d->chip->unmask_base -
d->chip->mask_base;
ret = regmap_update_bits(d->map,
reg + unmask_offset,
d->mask_buf[i],
d->mask_buf[i]);
} else
ret = regmap_update_bits(map, reg,
d->mask_buf[i], d->mask_buf[i]);
if (ret != 0) {
dev_err(map->dev, "Failed to set masks in 0x%x: %d\n",
reg, ret);
goto err_alloc;
}
if (!chip->init_ack_masked) // g, init_ack_masked域不为空,所以不会执行continue,会继续向下执行
continue;
/* Ack masked but set interrupts */
reg = chip->status_base +
(i * map->reg_stride * d->irq_reg_stride);
ret = regmap_read(map, reg, &d->status_buf[i]);
if (ret != 0) {
dev_err(map->dev, "Failed to read IRQ status: %d\n",
ret);
goto err_alloc;
}
if (d->status_buf[i] && (chip->ack_base || chip->use_ack)) {
reg = chip->ack_base +
(i * map->reg_stride * d->irq_reg_stride);
if (chip->ack_invert)
ret = regmap_write(map, reg,
~(d->status_buf[i] & d->mask_buf[i]));
else
ret = regmap_write(map, reg,
d->status_buf[i] & d->mask_buf[i]);
if (ret != 0) {
dev_err(map->dev, "Failed to ack 0x%x: %d\n",
reg, ret);
goto err_alloc;
}
}
}
/* Wake is disabled by default */
if (d->wake_buf) {
for (i = 0; i < chip->num_regs; i++) {
d->wake_buf[i] = d->mask_buf_def[i];
reg = chip->wake_base +
(i * map->reg_stride * d->irq_reg_stride);
if (chip->wake_invert)
ret = regmap_update_bits(map, reg,
d->mask_buf_def[i],
0);
else
ret = regmap_update_bits(map, reg,
d->mask_buf_def[i],
d->wake_buf[i]);
if (ret != 0) {
dev_err(map->dev, "Failed to set masks in 0x%x: %d\n",
reg, ret);
goto err_alloc;
}
}
}
if (chip->num_type_reg) {
for (i = 0; i < chip->num_irqs; i++) {
reg = chip->irqs[i].type_reg_offset / map->reg_stride;
d->type_buf_def[reg] |= chip->irqs[i].type_rising_mask |
chip->irqs[i].type_falling_mask;
}
for (i = 0; i < chip->num_type_reg; ++i) {
if (!d->type_buf_def[i])
continue;
reg = chip->type_base +
(i * map->reg_stride * d->type_reg_stride);
if (chip->type_invert)
ret = regmap_update_bits(map, reg,
d->type_buf_def[i], 0xFF);
else
ret = regmap_update_bits(map, reg,
d->type_buf_def[i], 0x0);
if (ret != 0) {
dev_err(map->dev,
"Failed to set type in 0x%x: %x\n",
reg, ret);
goto err_alloc;
}
}
}
if (irq_base)
// g, map->dev = &i2c->dev
// g, 下面这个接口会实现irq_domain的创建,初始化刚刚申请的virq对应的irq_desc->ira_data
// g, 并且会在该domain中建立virq域hwirq的映射关系
// g, in
d->domain = irq_domain_add_legacy(map->dev->of_node,
chip->num_irqs, irq_base, 0,
®map_domain_ops, d);
else
d->domain = irq_domain_add_linear(map->dev->of_node,
chip->num_irqs,
®map_domain_ops, d);
if (!d->domain) {
dev_err(map->dev, "Failed to create IRQ domain\n");
ret = -ENOMEM;
goto err_alloc;
}
// g, 这个irq是整个pmic的irq
// g, 使用了中断线程嵌套模式来处理子中断,下半部会直接处理子中断注册的thread_fn
// g, 当有中断的时候最终会在下半部执行regmap_irq_thread
// g, in
ret = request_threaded_irq(irq, NULL, regmap_irq_thread,
irq_flags | IRQF_ONESHOT,
chip->name, d);
if (ret != 0) {
dev_err(map->dev, "Failed to request IRQ %d for %s: %d\n",
irq, chip->name, ret);
goto err_domain;
}
*data = d;
return 0;
err_domain:
/* Should really dispose of the domain but... */
err_alloc:
kfree(d->type_buf);
kfree(d->type_buf_def);
kfree(d->wake_buf);
kfree(d->mask_buf_def);
kfree(d->mask_buf);
kfree(d->status_buf);
kfree(d->status_reg_buf);
kfree(d);
return ret;
}
该函数的主要工作就是:
着重分析一下第1,2,4步的工作
传入参数irq_base不为0,则首先就是要为hwirq申请系统中对应的virq,以及对应的中断描述符。
hwirq就是硬件中断号,对于一个irq_domain来说,你想定义为多少就定义为多少,就默认从0开始,一直递增就好了。对于多功能设备,现在已经作为一个controller,其上的每一个中断源都需要分配一个硬件中断号。但是硬件中断号对应的virq(软件中断号,也可以说是虚拟中断号),不能乱搞,系统只有一个,这个号码不能冲突。
所以需要irq_alloc_descs()来分配:
#define irq_alloc_descs(irq, from, cnt, node) \
__irq_alloc_descs(irq, from, cnt, node, THIS_MODULE, NULL)
------>
// g, 参数:-1, 0, chip->num_irqs, 0, THIS_MODULE, NULL
__irq_alloc_descs(int irq, unsigned int from, unsigned int cnt, int node,
struct module *owner, const struct cpumask *affinity)
{
int start, ret;
if (!cnt)
return -EINVAL;
if (irq >= 0) {
if (from > irq)
return -EINVAL;
from = irq;
} else {
/*
* For interrupts which are freely allocated the
* architecture can force a lower bound to the @from
* argument. x86 uses this to exclude the GSI space.
*/
// g, 跟体系架构相关,判断最小从哪个中断号开始。x86和s390架构都是有要求的
// g, arm架构没有要求,返回仍然为from
from = arch_dynirq_lower_bound(from);
}
mutex_lock(&sparse_irq_lock);
// g, virq用bitmap控制,把cnt个virq加入到静态创建的一个bitmap,每次分配都会从bitmap中找到空闲的bit。
// g, 这说明什么?说明整个linux下,所有中断申请的virq,不可能重复。
start = bitmap_find_next_zero_area(allocated_irqs, IRQ_BITMAP_BITS, // g, 开启radix tree,则virq上限为16 + 8196
from, cnt, 0);
ret = -EEXIST;
if (irq >=0 && start != irq)
goto unlock;
if (start + cnt > nr_irqs) { // g, nr_irqs = NR_IRQS,当不启用radix tree时,该宏决定了irq_descrition数组的成员个数
// g, 只有定义了 CONFIG_SPARSE_IRQ 使用radix tree的时候才会去扩容。
// g, 否则这个函数不起作用,也就是说超过数组最大index之后就没办法再扩容了
ret = irq_expand_nr_irqs(start + cnt); // g, 已经在.config中开了CONFIG_SPARSE_IRQ,这里只会对是否超出了bitmap作检查
if (ret)
goto unlock;
}
// g, 如果使用了基数树,那么该函数会申请一个irq_desc并插入到tree中,而且还会加入到sysfs中。执行该任务的函数为__radix_tree_lookup()
// g, 如果没有使用radix tree,则从irq_desc[NR_IRQS]数组中分配,则不得超过NR_IRQS范围
ret = alloc_descs(start, cnt, node, affinity, owner); // g, alloc_descs的返回值就是start
unlock:
mutex_unlock(&sparse_irq_lock);
return ret;
}
virq号码由一个bitmap来记录,能申请的号码取决于你是否开启了CONFIG_SPARSE_IRQ这个宏。这个宏非常关键,决定了你能从系统中申请多少个virq以及对应的struct irq_desc。如果没有开启该宏,所有struct irq_desc以数组的形式组织在一起,可能只有十几个、几十个(取决于宏NR_IRQS)。如果开启该宏,则组织方式是radix tree,可以有几千个。这里就不详细展开说了。
总之,该函数会申请num(传入参数,也是实际的硬件中断源数)个连续的virq和struct irq_desc(中断描述符),并返回连续virq的起始号码。
申请了num个virq号码之后,就需要建立hwirq与virq之间的联系了。regmap_add_irq_chip()根据传入的参数irq_base决定使用irq_domain_add_legacy()函数或者irq_domain_add_linear()函数申请struct irq_domain。
struct irq_domain可以认为是描述一个controller信息的结构体,每个中断控制器controller都会有一个对应的struct irq_domain,并根据实际硬件上的连接,存在层级关系。
irq_domain_add_legacy()函数和irq_domain_add_linear()函数的区别就在于,在申请irq_domain的时候,要不要直接建立hwirq与virq的联系。前者会直接建立,而后者不会直接建立,会在后续使用的时候动态建立。传入的参数irq_base不为0,则使用irq_domain_add_legacy()函数,因为irq_base不为0已经申请了virq,所以可以直接建立:
struct irq_domain *irq_domain_add_legacy(struct device_node *of_node, // g, i2c_client下面的node
unsigned int size, // g, chip->num_irqs
unsigned int first_irq, // g, ira_base,也就是start
irq_hw_number_t first_hwirq, // g, 0,硬件中断号起始,这也说明了每个controller硬件中断号可能重复
const struct irq_domain_ops *ops, // g, regmap_domain_ops
void *host_data) // g, data
{
struct irq_domain *domain;
// g, of_node_to_fwnode会返回node->fwnode,fwnode其实就是of_node,可以认为是一种通用node,既可以表示of_node(多用在arm),也可以表示acpi node(多在x86上)
// g, 下面这个函数会分配一个struct irq_domain,然后使用传入的信息进行填充,并把新分配的struct irq_domain添加到全局链表irq_domain_list中
// g, 参数分别对应:fwnode, size, hwirq_max(最大子中断号), direct_max(hwirq和virq 1:1映射支持的最大数量), ops, host_data
// g, in
domain = __irq_domain_add(of_node_to_fwnode(of_node), first_hwirq + size,
first_hwirq + size, 0, ops, host_data);
// g, 比irq_domain_add_domain多了下面这一步:
// g, 如果创建domain成功,则会:
// g, 1. 初始化virq对应的irq_desc->irq_data的一些域,并且建立在domain中,hwirq<-->virq的映射表
// g, 2. 映射表为domain->linear_revmap,该表的表现形式就是domain->linear_revmap[hwirq] = virq
// g, 3. 这里就体现了,虽然整个系统中每个controller的硬件中断号可能重复,但是其映射的virq并不会重复。
// 每一张映射表都隶属于一个strut irq_domain,每一个controller有单独的irq_domain
if (domain)
irq_domain_associate_many(domain, first_irq, first_hwirq, size);
return domain;
}
第一步,申请struct irq_domain:
struct irq_domain *__irq_domain_add(struct fwnode_handle *fwnode, int size,
irq_hw_number_t hwirq_max, int direct_max,
const struct irq_domain_ops *ops,
void *host_data)
{
struct device_node *of_node = to_of_node(fwnode);
struct irq_domain *domain;
domain = kzalloc_node(sizeof(*domain) + (sizeof(unsigned int) * size),
GFP_KERNEL, of_node_to_nid(of_node));
if (WARN_ON(!domain))
return NULL;
of_node_get(of_node);
/* Fill structure */
INIT_RADIX_TREE(&domain->revmap_tree, GFP_KERNEL);
domain->ops = ops;
domain->host_data = host_data;
domain->fwnode = fwnode;
domain->hwirq_max = hwirq_max;
domain->revmap_size = size;
domain->revmap_direct_max_irq = direct_max;
// g, 如果开启了CONFIG_IRQ_DOMAIN_HIERARCHY(中断级联控制),且domain->ops->alloc存在的话
// g, 会设置domain->flags | IRQ_DOMAIN_FLAG_HIERARCHY
irq_domain_check_hierarchy(domain);
mutex_lock(&irq_domain_mutex);
list_add(&domain->link, &irq_domain_list); // g, 加入全局链表
mutex_unlock(&irq_domain_mutex);
pr_debug("Added domain %s\n", domain->name);
return domain;
}
第二步,建立hwirq与virq的联系:
void irq_domain_associate_many(struct irq_domain *domain, unsigned int irq_base,
irq_hw_number_t hwirq_base, int count)
{
struct device_node *of_node;
int i;
// g, 拿到to_of_node(d->fwnode)
of_node = irq_domain_get_of_node(domain);
pr_debug("%s(%s, irqbase=%i, hwbase=%i, count=%i)\n", __func__,
of_node_full_name(of_node), irq_base, (int)hwirq_base, count);
// g, 会建立virq与hwirq的映射:domain->linear_revmap[hwirq] = virq
// g, 并且会初始化virq对应的irq_desc的irq->descirq_data
for (i = 0; i < count; i++) {
irq_domain_associate(domain, irq_base + i, hwirq_base + i);
}
}
---->
int irq_domain_associate(struct irq_domain *domain, unsigned int virq,
irq_hw_number_t hwirq)
{
// g, 拿到virq对应的irq_desc->irq_data,这个irq_data在之前并没有被初始化,所以需要在这里进行初始化
// g, 也就是说,irq_desc的一些信息,是需要在与domain建立联系的时候才会初始化
struct irq_data *irq_data = irq_get_irq_data(virq);
int ret;
if (WARN(hwirq >= domain->hwirq_max,
"error: hwirq 0x%x is too large for %s\n", (int)hwirq, domain->name))
return -EINVAL;
if (WARN(!irq_data, "error: virq%i is not allocated", virq))
return -EINVAL;
if (WARN(irq_data->domain, "error: virq%i is already associated", virq))
return -EINVAL;
mutex_lock(&irq_domain_mutex);
irq_data->hwirq = hwirq;
irq_data->domain = domain;
// g, ops中是有map函数,被设置为了 regmap_irq_map 的,所以要调用一下,该函数是中断嵌套调用nest的关键!!!
if (domain->ops->map) {
ret = domain->ops->map(domain, virq, hwirq);
if (ret != 0) {
/*
* If map() returns -EPERM, this interrupt is protected
* by the firmware or some other service and shall not
* be mapped. Don't bother telling the user about it.
*/
if (ret != -EPERM) {
pr_info("%s didn't like hwirq-0x%lx to VIRQ%i mapping (rc=%d)\n",
domain->name, hwirq, virq, ret);
}
irq_data->domain = NULL;
irq_data->hwirq = 0;
mutex_unlock(&irq_domain_mutex);
return ret;
}
/* If not already assigned, give the domain the chip's name */
if (!domain->name && irq_data->chip)
domain->name = irq_data->chip->name;
}
if (hwirq < domain->revmap_size) {
domain->linear_revmap[hwirq] = virq; // g, 建立hwirq与virq之间的映射关系
} else {
mutex_lock(&revmap_trees_mutex);
radix_tree_insert(&domain->revmap_tree, hwirq, irq_data);
mutex_unlock(&revmap_trees_mutex);
}
mutex_unlock(&irq_domain_mutex);
irq_clear_status_flags(virq, IRQ_NOREQUEST);
return 0;
}
注意,这里在建立hwirq与virq的联系的时候,会调用传入的ops->map函数,该函数是regmap实现中断嵌套控制的关键。上层传入的ops指向:
static const struct irq_domain_ops regmap_domain_ops = {
.map = regmap_irq_map, // g, 这个函数很关键, g, in
.xlate = irq_domain_xlate_twocell,
};
它实现的map为:
// g, 该函数会为每一个子中断irq设置子控制器的实例和控制刘
static int regmap_irq_map(struct irq_domain *h, unsigned int virq,
irq_hw_number_t hw)
{
struct regmap_irq_chip_data *data = h->host_data;
irq_set_chip_data(virq, data);
irq_set_chip(virq, &data->irq_chip);
irq_set_nested_thread(virq, 1); // g, 设置子设备中断线程嵌套特性打开
irq_set_parent(virq, data->irq);
irq_set_noprobe(virq);
return 0;
}
这个map函数会通过irq_set_nested_thread()函数打开每一个子设备(每一个virq)中断线程中断嵌套特性,为后续使用中断嵌套做准备。
之前注册中断处理函数的时候,很少使用这个api。但是这个更好用,会把下半部的工作线程化。这个可以直接创建一个FIFO调度策略的内核线程,来处理后半部,相当于设置了一个工作队列。
具体使用方法,传入参数什么含义,就不展开讲了,贴一个讲该API的用法的博客:
这里重点讲一下实现过程,以及与regmap的关系。
可以先提前看一下这篇博客:
分析结果:
当中断出现的时候,我们就以gicv3来说,从cpu->gic->自己的处理函数其调用栈:
el0_irq(汇编)
->irq_handler(macro宏)
->handle_arch_irq(c语言,与架构相关)
在这之前,不同中断控制器架构会会通过set_handle_irq(),使handle_arch_irq指向自己的handle函数。
注册过程:
start_kernel()
->setup_arch()
->...
->irqchip_init()
->of_irq_init(__irqchip_of_table)
__irqchip_of_table段保存着使用IRQCHIP_DECLARE()宏声明的信息,该宏声明一个of_id_table表,表中data域的函数指针会被调用
本平台:
IRQCHIP_DECLARE(cortex_a15_gic, "arm,cortex-a15-gic", gic_of_init),表被保存在__section(__irqchip_of_table)
->解析设备树,device-driver匹配
->of_irq_init()
->gic_of_init()
->__gic_init_bases()
->set_handle_irq(gic_handle_irq) + 建立第一个struct irq_domain(gic对应的domain),现在handle_arch_irq指向了gic_handle_irq
---------------------------- boot阶段的中断控制器处理与注册工作,这一层只与架构相关 --------------------------------
接下来当一个中断到来时:
el0_irq
->handle_arch_irq(= gic_handle_irq)
->gic_handle_irq(全志自己实现的gic_handle_irq,这里会读取硬件寄存器从而获取hwirq)
->handle_domain_irq->__handle_domain_irq(根据domain 和 hwirq获取映射好的virq)
->generic_handle_irq(在这里找到virq对应的irq_desc)
->generic_handle_irq_desc
->desc->handle_irq
----------------------------- 这一层是中断处理层的一部分,既跟架构相关,又跟irq中断子系统相关 ------------------------
gic_irq_domain_map->
->irq_domain_set_info
->__irq_set_handler
->__irq_do_set_handler
->desc->handle_irq = handle;
handle有四种:处理电平触发类型的中断handler(handle_level_irq)
处理边缘触发类型的中断handler(handle_edge_irq)
处理简单类型的中断handler(handle_simple_irq)
处理EOI类型的中断handler(handle_fasteoi_irq)
以处理边缘edge触发为例:
->handle_edge_irq
->handle_irq_event
->handle_irq_event_percpu
->__handle_irq_event_percpu
->res = action->handler(irq, action->dev_id);
->此时上半部就执行完了
->如果thread_fn不为NULL,则执行: __irq_wake_thread()
->test_and_set_bit(IRQTF_RUNTHREAD, &action->thread_flags)
->wake_up_process(action->thread), action->thread会在创建内核线程时被设置为新创建的线程的task_struct
-->action->handler和action->thread_fn是两个关键函数,分别对应一个中断的上下半部,由我们自己调用 request_threaded_irq 注册,接下来会讲一下该函数的实现方式
-------------------------- 这一层是中断注册层 + 中断处理层的剩余部分,只与irq中断子系统相关 ---------------------------
thread_fn由__irq_wake_thread()唤醒跳出的循环
从上面可以看到中断最终都会调用到我们为某一个virq申请的中断处理函数,但是又可以分为两种情况:
1. 非中断线程嵌套,也就是没有设置desc->status_use_accessors的_IRQ_NESTED_THREAD标志位,非nested。
1.1 此时会注册action->handler,也就是request_threaded_irq()注册的handler,就是上半部,如果想要开启下半部,则需要传入thread_fn函数,作为下半部的处理函数。
1.2 如果开启了下半部,则会在下半部处理中设置标志位IRQTF_RUNTHREAD,上半部结束后通过set_bit IRQTF_RUNTHREAD这个flag唤醒下半部,然后等待调度运行。
1.3 具体原理是:如果request_threaded_irq()中为thread_fn传入了不为NULL的参数,则会创建一个内核线程,执行irq_thread()函数来处理下半部,调度策略SCHED_FIFO,优先级还是比较高的。
1.4 在irq_thread()中会轮询等待(会schedule)一个标志位IRQTF_RUNTHREAD,当该标志位被设置后,该内核线程才会继续执行,从而调用到传入的thread_fn。
1.5 设置该标志位会在中断处理函数上半部执行完后,通过调用test_and_set_bit()设置,也就是在action->handler()执行完后设置。
1.6 这也是一种提供下半部的方式,可以代替工作队列,但是无法代替tasklet,因为这个下半部线程是可以block的。
2.中断线程嵌套,nested,regmap对多功能设备的的中断处理使用了这种方法,子中断的virq对应的irq_desc已经被设置了nested标志。
2.1 被设置有nested flag的中断号,在使用request_threaded_irq()注册的时候,不会注册handler,仅会用一个没有任何功能的默认handler代替。
2.2 只会注册thread_fn,但是不会再注册一个新的内核线程来处理所谓的下半部。通常是在父中断的处理函数中会进行这样的操作,对于regmap来说,就是:
2.3 还有一点就是,带有nested标志的子中断的handler函数永远不会被调用,都是这么用的。父中断的中断处理函数中应直接调用handle_nested_irq(参数:子中断virq)
2.4 具体过程就是:
GIC等处理函数
->接到GIC/NVM等中断控制器上的父中断
->上半部没有,下半部执行regmap_irq_thread()->
->根据多功能设备的中断状态寄存器,判断产生了什么中断
->handle_nested_irq(传入virq)
->irq_to_desc(virq),获取到virq对应的action
->[action->thread_fn()]
3. 如何实现中断线程嵌套?需要:
3.1 父设备,为子设备申请中断号
3.2 父设备,在为子设备申请子中断号时,设置nested标志
3.3 父设备,申请父中断,也就是多功能设备的中断,使用request_threaded_irq()申请中断,不使用handle,只使用thread_fn,并在thread_fn中调用handle_nested_irq()函数
3.3.1 为什么父设备的中断也要用线程处理?因为这里的父设备不是GIC,却仍然要承担GIC的工作(控制子中断),他没GIC那么快的响应时间(毕竟GIC直连CPU)
如果我这个MFD还要用handler,然后再在MFD的handler中再去处理子中断,那得多慢阿,GIC得一直等着他返回,中断本来就希望快速。而且PMIC也不是啥实时要求特别高的设备。
------------------ 以上三步, 其实就是regmap的处理逻辑 --------------------
3.4 子设备,申请中断,可以直接使用devm_request_any_context_irq()来申请。被设置为nested标志的中断号,不会再创建内核线程。
3.4.1 就比如PMIC 按键驱动 axp20x_pek_probe申请的中断那样,就是用这个函数申请的
具体的函数request_threaded_irq()和devm_request_any_context_irq()实现过程的分析,有时间再整理一下。