从零开始之驱动发开、linux驱动(十三、共享中断原理分析和新的共享中断实现方式)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_16777851/article/details/82696052

这里我们先要说明一下,那些情况下是可以使用共享中断的,那些情况是不能使用共享中断的。

先说一下那些情况可以用共享中断。

必要条件:

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);

猜你喜欢

转载自blog.csdn.net/qq_16777851/article/details/82696052