这里我们先要说明一下,那些情况下是可以使用共享中断的,那些情况是不能使用共享中断的。
先说一下那些情况可以用共享中断。
必要条件:
1.硬件支持
可选条件
1.参数支持
这里先说硬件支持。
比如假设我们想用我们的外部中断9做共享中断,可以看到它只针对一个GPIO管脚,GPH1_1。
如果使用EINT9,做共享中断,假设被注册来三个中断函数。
当某一个发生中断,执行EINT9的irq_desc数组里面的链表中的我们注册的中断函数,我们无法判断它到底属于哪个外设的。
所以这种类似EINT9的中断就无法作为共享中断使用。
我们再看EINT16_31,它虽然只有一个中断号16,假设它被注册成共享中断。
假设发生了中断,只要每个注册的中断处理函数中读取寄存器,查看到底是外部中断16~31中的那个发生了中断,就能知道是那个中断了。
static irqreturn_t irq_handler(int irq, void *dev_id)
{
unsigned int reg;
reg = read_register(中断状态寄存器);
/* 表示判断外部中断状态寄存器的第18号中断的挂起标志位,0x+++++表示EINT_18的状态位为1,其它为0 */
if(!(reg & 0x+++++))
{
/* 不是,则返回该标号 */
rerurn IRQ_NONE;
}
/* 是本中断,则中断处理 */
.....
/* 最后返回处理完毕 */
retutn IRQ_HANDLED;
}
static const struct file_operations xxxx_file_operation = {
.owner = THIS_MODULE,
.open = xxxx_drv_open,
.read = xxxx_drv_read,
.release = xxxx_drv_close,
};
/* 当前设备假设用的外部中断18 */
int xxxx_init(void)
{
request_irq(IRQ_EINT16_31, irq_handler, flag, "irq-name",& xxxx_file_operation );
....
}
上面这种就可以使用共享中断处理。
下面说,关于参数 void * dev_id
它是标志是那个dev的,同一中断里的所有共享中断的必须不同,最主要是释放action时使用,也可以有一些其它的用途。
/* eint15处理 */
static irqreturn_t aaaa_irq_handler(int irq, void *dev_id)
{
unsigned int reg;
reg = read_register(中断状态寄存器);
if(判断是不是外部中断25)
{
/* 不是,则返回该标号 */
rerurn IRQ_NONE;
}
/* 是18中断,则中断处理 */
.....
本函数的dev_id是cdev
/* 最后返回处理完毕 */
retutn IRQ_HANDLED;
}
/* eint25处理 */
static irqreturn_t bbbb_irq_handler(int irq, void *dev_id)
{
unsigned int reg;
reg = read_register(中断状态寄存器);
if(判断是不是外部中断18)
{
/* 不是,则返回该标号 */
rerurn IRQ_NONE;
}
/* 是eint25中断,则中断处理 */
.....
本函数的dev_id是aaaa_file_operation
/* 最后返回处理完毕 */
retutn IRQ_HANDLED;
}
static const struct file_operations aaaa_file_operation = {
.owner = THIS_MODULE,
.open = aaaa_drv_open,
.read = aaaa_drv_read,
.release = aaaa_drv_close,
};
static struct led_classdev cdev;
int xxxx_init(void)
{
/* a这种情况用外部中断18 */
request_irq(IRQ_EINT16_31, aaaa_irq_handler, flag, "irq-name1",& aaaa_file_operation );
/* b这种情况用外部中断25 */
request_irq(IRQ_EINT16_31, bbbb_irq_handler, flag, "irq-name1",& cdev );
....
}
共享中断的使用略微有些麻烦。所以linux系统新一些的版本改进了中断框架,在中断框架中实现了共享中断的功能。
让使用者可以和使用普通中断一样使用共享中断。目前不建议使用老的共享中断,而是使用系统提供的标准普通接口来使用,而不用共享标志flag。
在我的中断框架的这篇文章中说了,S5PV210的共享中断linux系统给做了48个,其中有16个是EINT16~32,另外32个是留给gpiolib的
中断EINT16~32系统作为新的中断方式的实现,在下面文章。
https://blog.csdn.net/qq_16777851/article/details/82564302
我这边总结一下:
1.共享中段在irq_desc数组中单独的中断后面实现。
2.下面函数注册
/* 使用下面代码填充到irq_desc,的单独中断的后面开始的位置 */
for (irq = IRQ_EINT(16); irq <= IRQ_EINT(31); irq++) {
irq_set_chip_and_handler(irq, &s5p_irq_eint, handle_level_irq);
set_irq_flags(irq, IRQF_VALID);
}
/* 使用该句注册真正的共享的中断的处理函数,s5p_irq_demux_eint16_31函数中读状态寄存器
* 判断哪个中断了,找到irq_desc[IRQ_EINT(xx)]项,调用该项执行action中的中断处理函数就可以
*/
irq_set_chained_handler(IRQ_EINT16_31, s5p_irq_demux_eint16_31);
/* 注意大于等于16,就已经是在irq_desc数组后面位置了 */
#define IRQ_EINT(x) ((x) < 16 ? ((x) + S5P_EINT_BASE1) \
: ((x) - 16 + S5P_EINT_BASE2))
static void s5p_irq_demux_eint16_31(unsigned int irq, struct irq_desc *desc)
{
s5p_irq_demux_eint(IRQ_EINT(16)); /* 从16~23共8个 */
s5p_irq_demux_eint(IRQ_EINT(24)); /* 从24~31共8个 */
}
/* s5p_irq_demux_eint
*
* This function demuxes the IRQ from the group0 external interrupts,
* from EINTs 16 to 31. It is designed to be inlined into the specific
* handler s5p_irq_demux_eintX_Y.
*
* Each EINT pend/mask registers handle eight of them.
*/
static inline void s5p_irq_demux_eint(unsigned int start)
{
u32 status = __raw_readl(S5P_EINT_PEND(EINT_REG_NR(start))); /* 读中断挂起寄存器 */
u32 mask = __raw_readl(S5P_EINT_MASK(EINT_REG_NR(start))); /* 读屏蔽寄存器 */
unsigned int irq;
status &= ~mask;
status &= 0xff;
/* 如果没有屏蔽中断,则执行当前所有中断位挂起的中断函数 */
while (status) {
irq = fls(status) - 1; /* 找到从bit0到bit7,返回输入参数的最高有效bit位(从低位往左数最后的有效bit位)的序号 */
generic_handle_irq(irq + start); /* IRQ_EINT(x) + irq = 真正的中断号,传入,执行中断处理函数 */
status &= ~(1 << irq); /* 清除已经执行完的中断标记(可能有多个外部中断,所以每次都是清除已经执行了中断处理程序的那一个) */
}
}
/**
* fls - find last (most-significant) bit set
* @x: the word to search
*
* This is defined the same way as ffs.
* Note fls(0) = 0, fls(1) = 1, fls(0x80000000) = 32.
*/
/* 可以看到上面函数的注释0x80000000,的第31bit是1,所以是32
* 我们如果是0x24,则 fls(0x42) = 3,因为2是低端第一个1所在的bit
static __always_inline int fls(int x);
gpiolib之所以也做了共享中断,必须是要硬件支持
我们的硬件中断30,就支持所有固定的32个我们称之为EINT的中断之外的其它GPIO中断。
可以看每个引脚的寄存器都有设置为中断引脚的功能。
每个引脚都可以被设置各种触发类型,也都有自己的中断mask和pend位,和EINT的几乎完全一样。
可以看到理论上所有GPIO可以做中断,gpiolib库位我们预留了32个位置,他们可以是任何GPIO引脚。
某个GPIO只有使用gpiolib库提供的函数,注册这个GPIO为中断时,会动态的为其找一个在32个gpiolib库预留的位置中没用的一个,
设置为中断形式(预留32个(以组的形式实现),而不向前面的16个外部共享中断那样,所有中断都预留主要是GPIO太多,全部预留太浪费内存了【绝大多数情况我们可能都不会使用非eint外的gpio做中断】。我们内核默认没配使用基数树,如果配置为基数树的方式动态注册使用中断,则所有的中断使用都是使用动态分配的方式以树的形式存储,而不是一次性定义很大的irq_desc数组,这样会更智能一些(主要是我们不可能用完所有中断,所有的都用数组定义肯定大多数内存被浪费了))
它是在gpiolib库初始化时被注册。
使用gpio中断,注册的接口
int __init s5p_register_gpio_interrupt(int pin)
{
struct samsung_gpio_chip *my_chip = samsung_gpiolib_getchip(pin);
int offset, group;
int ret;
if (!my_chip)
return -EINVAL;
offset = pin - my_chip->chip.base;
group = my_chip->group;
/* check if the group has been already registered */
if (my_chip->irq_base)
goto success;
/* register gpio group,这里注意一点,这里是以组的形式注册如GPA0,GPA1,GPB,GPC等 */
ret = s5p_gpioint_add(my_chip);
if (ret == 0) {
my_chip->chip.to_irq = samsung_gpiolib_to_irq;
printk(KERN_INFO "Registered interrupt support for gpio group %d.\n",
group);
goto success;
}
return ret;
success:
my_chip->bitmap_gpio_int |= BIT(offset);
return my_chip->irq_base + offset; /* 返回值是中断号 */
}
具体的注册组我就不单独分析了,大概说一下做了哪些事就行,
和执行代码我就不单独分析了。
1.先通过GPIO号找到对应的组,查看组内是否已经有被注册的,如果没有,则申请chip,注册s5p_gpioint_handler到这个组,到时候中断会执行s5p_gpioint_handler。
2.注册通用的中断函数handle_level_irq
3.设置chip填充各种参数等,同一个组不会重复申请注册s5p_gpioint_handler等等
static __init int s5p_gpioint_add(struct samsung_gpio_chip *chip)
{
static int used_gpioint_groups = 0;
int group = chip->group;
struct s5p_gpioint_bank *b, *bank = NULL;
struct irq_chip_generic *gc;
struct irq_chip_type *ct;
if (used_gpioint_groups >= S5P_GPIOINT_GROUP_COUNT)
return -ENOMEM;
list_for_each_entry(b, &banks, list) {
if (group >= b->start && group < b->start + b->nr_groups) {
bank = b;
break;
}
}
if (!bank)
return -EINVAL;
if (!bank->handler) {
bank->chips = kzalloc(sizeof(struct samsung_gpio_chip *) *
bank->nr_groups, GFP_KERNEL);
if (!bank->chips)
return -ENOMEM;
irq_set_chained_handler(bank->irq, s5p_gpioint_handler);
irq_set_handler_data(bank->irq, bank);
bank->handler = s5p_gpioint_handler;
printk(KERN_INFO "Registered chained gpio int handler for interrupt %d.\n",
bank->irq);
}
/*
* chained GPIO irq has been successfully registered, allocate new gpio
* int group and assign irq nubmers
*/
chip->irq_base = S5P_GPIOINT_BASE +
used_gpioint_groups * S5P_GPIOINT_GROUP_SIZE;
used_gpioint_groups++;
bank->chips[group - bank->start] = chip;
gc = irq_alloc_generic_chip("s5p_gpioint", 1, chip->irq_base,
GPIO_BASE(chip),
handle_level_irq);
if (!gc)
return -ENOMEM;
ct = gc->chip_types;
ct->chip.irq_ack = irq_gc_ack_set_bit;
ct->chip.irq_mask = irq_gc_mask_set_bit;
ct->chip.irq_unmask = irq_gc_mask_clr_bit;
ct->chip.irq_set_type = s5p_gpioint_set_type,
ct->regs.ack = PEND_OFFSET + REG_OFFSET(group - bank->start);
ct->regs.mask = MASK_OFFSET + REG_OFFSET(group - bank->start);
ct->regs.type = CON_OFFSET + REG_OFFSET(group - bank->start);
irq_setup_generic_chip(gc, IRQ_MSK(chip->chip.ngpio),
IRQ_GC_INIT_MASK_CACHE,
IRQ_NOREQUEST | IRQ_NOPROBE, 0);
return 0;
}
gpio中断函数
/* */
static void s5p_gpioint_handler(unsigned int irq, struct irq_desc *desc)
{
struct s5p_gpioint_bank *bank = irq_get_handler_data(irq);
int group, pend_offset, mask_offset;
unsigned int pend, mask;
struct irq_chip *chip = irq_get_chip(irq);
chained_irq_enter(chip, desc);
/* 检查每一个group */
for (group = 0; group < bank->nr_groups; group++) {
struct samsung_gpio_chip *chip = bank->chips[group];
if (!chip)
continue;
pend_offset = REG_OFFSET(group);
pend = __raw_readl(GPIO_BASE(chip) + PEND_OFFSET + pend_offset);
if (!pend) /* 该组寄存器要是没任何挂起,直接检查下一个组的 */
continue;
/* 中断屏蔽的个别寄存器把挂起位清掉 */
mask_offset = REG_OFFSET(group);
mask = __raw_readl(GPIO_BASE(chip) + MASK_OFFSET + mask_offset);
pend &= ~mask;
/* 执行组中所有的挂起函数 */
while (pend) {
int offset = fls(pend) - 1;
int real_irq = chip->irq_base + offset;
generic_handle_irq(real_irq); /* 执行desc->handle_irq(irq, desc); */
pend &= ~BIT(offset);
}
}
chained_irq_exit(chip, desc);
}
可以发现和外部中断的eint16~eint31的实现基本类似的。
唯一就是使用外部中断可以使用下面形式注册
request_irq(IRQ_EINT(19), irq_handler, IRQF_TRIGGER_NONE, "eint19", NULL);
而gpio的要先注册得到中断号,后申请中断
int irq;
irq = s5p_register_gpio_interrupt(S5PV210_GPA1(3));
if(irq < 0)
{
/* 出错 */
}
request_irq(irq , irq_handler, IRQF_TRIGGER_NONE, "S5PV210_GPA1(3)", NULL);