Linux字符设备驱动 -- regmap子系统

环境

linux 4.9
armv8

写一个复杂设备驱动时使用了regmap子系统,资料很少,所以只能自己去研究子系统的实现,大部分分析都在注释中,当做笔记。

一、关于regmap子系统

关于该系统的作用,可以看这些文章:

简而言之,就是regmap子系统把一些低速接口的api函数给平台化了。之前如果要使用i2c,spi,中断irq等等都有不同的接口,但是现在只需要几个通用api就可以满足,比如说:

  • regmap_write()
  • regmap_read()
  • regmap_upadte_bits()

使用方法也很简单,很多资料,教程都有,如何初始化,如何配置等等。除此之外,regmap子系统还提供了缓存机制,可以选择哪些数据可以缓存,以及缓存方式。

这样做优化,比如说你可以设置i2c/spi等设备的哪些寄存器的值启用缓存,那么当我使用write更新了该寄存器之后,再使用read,就没必要再通过协议去寄存器中读取,而是直接去缓存中读取该寄存器的值就可以了。这片缓存区是由regmap子系统提供的,可以选择用数组的方式进行缓存,也可以生成红黑树来进行缓存。

本篇文章主要分析这个框架是怎么实现的,为什么能这么使用。

二、regmap-i2c初始化

如果想使用i2c通信,那就要把regmap初始化为i2c通信模式,需要:

  1. 创建一个struct regmap结构体指针
  2. 创建一个struct regmap_config结构体并初始化其中所需要的域。
  3. 调用devm_regmap_init_i2c()函数,该函数根据regmap_config中配置的信息初始化regmap结构体
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);
}

该函数就做了两件事:

  1. 为regmap选择bus,我把他当做是选择通信协议(i2c也有好几种不同的协议)。
  2. 根据struct regmap_config配置信息和选择的bus,初始化struct regmap。

2.1 regmap_get_i2c_bus()

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 &regmap_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 &regmap_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 &regmap_smbus_word;
		case REGMAP_ENDIAN_BIG:
			return &regmap_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 &regmap_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时,最终会调用到上述绑定的通信函数中。

2.2 __devm_regmap_init()

在选择完通信协议函数后,就需要使用选择的通信协议函数和配置参数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与irq

regmap不止提供了平台化i2c、spi协议的功能,还能平台化irq。对于一些多功能设备来讲,内部有多个中断源,但中断触发引脚只有一个。为了实现驱动程序的跨平台,不希望这些中断源的irq被硬编码在板级代码中,所以可以使用regmap的irq接口来管理。

在regmap的irq初始化过程中,涉及到了irq子系统的种种,最好了解过irq子系统的一些机制才能搞清楚regmap是如何赋予一个复杂设备irq功能的。

在此之前,可以先思考一下,对于内部有多个中断源,但是只有一个中断接口接到上级控制器的设备,应该怎么处理呢?或者说,应该怎么判断到底是哪个中断源引起了中断呢?一般有四种方法:

  1. 单一中断:只为这个设备申请一个virq,然后在该virq中读取中断状态寄存器(一般这种设备都有中断状态寄存器)判断真正的中断源,然后通过switch case或类似的方式,去实现不同的中断处理。
  2. 共享中断:以相同的virq多次申请中断服务,那么该virq对应的struct irq_desc的action域就会有多个irq action实例。中断发生时,会遍历action链表逐个执行action实例中我们注册的handler回调函数,可以根据handler回调的不同返回值(我们自己设置)去唤醒不同的中断线程。使用共享中断需要注册中断时加入 IRQF_SHARED标志。
  3. 控制器级联:只是看到有这样的处理方法,但是没找到真正这样做的实例,后续再研究研究
  4. 中断线程嵌套:regmap的对这种设备的中断处理,使用的就是这种模式。把子中断分发处理过程,放到了父中断申请的中断线程中进行,也就是说,为唯一的父中断,调用request_thread_irq()函数申请时,放到父中断传入的参数thread_fn中,完成子中断分发。

这里先做个总结,总结里提到的具体的结构体,函数接口等接下来都会慢慢分析。因为我对中断控制器的原理,不是很熟悉,了解的不深,所以接下来的分析可能有误,只是说一下我的分析过程

首先说一下如何使用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);	

该接口需要传入的参数:

  1. 一个经过初始化的struct regmap结构体
  2. 设备的中断号,一般情况传入设备树解析设备生成的设备节点的irq即可
  3. 中断标志
  4. irq_base,如果irq_base不为0,则会使用irq_domain_add_legacy()的方式注册irq_domain,否则使用irq_domain_add_linear()的方式注册irq_domain。我们传入-1
  5. 一个struct regmap_irq_chip结构体,需要提前静态定义好,是regmap中断的配置信息
  6. 一个struct regmap_irq_chip_data结构体,会在该函数中被初始化。

重点在于第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 = <gpio xx IRQ_TYPE_EDGE_FALLING>;)
	unsigned int type_falling_mask;	// g, 下降沿中断的掩码 
};

对于我使用的设备来讲,设备关于中断的寄存器有四个,分别是:

  • 中断状态寄存器1,地址:0x48
  • 中断状态寄存器2,地址:0x49
  • 中断使能寄存器1,地址:0x40
  • 中断使能寄存器2,地址:0x41

这里后续放几张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,
						  &regmap_domain_ops, d);
	else
		d->domain = irq_domain_add_linear(map->dev->of_node,
						  chip->num_irqs,
						  &regmap_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. 传入的参数irq_base不为0,则为hwirq申请对应的virq和struct irq_desc(中断描述符)
  2. 初始化struct regmap_irq_chip_data的参数。主要想分析这个函数与irq子系统的关联,所以这部分工作不去细致的分析
  3. 创建并注册irq_domain,意味着该设备变成了一个controller(中断控制器)
  4. 调用request_threaded_irq()申请中断处理函数

着重分析一下第1,2,4步的工作

3.1 申请中断描述符irq_alloc_descs()

传入参数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的起始号码。

3.2 为设备申请irq_domian,建立hwirq与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函数会打开每一个子设备(每一个virq)中断线程中断嵌套特性,为后续使用中断嵌套做准备。

3.3 request_threaded_irq()函数注册中断处理函数

之前注册中断处理函数的时候,很少使用这个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.4irq_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()实现过程的分析,有时间再整理一下。

  • 19
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值