当前位置: 首页 > news >正文

linux-2.6.26内核中ARM中断实现详解(3)

四、中断处理模型

要想弄清楚desc->handle_irq(irq, desc)和我们注册的中断有什么关联,就要了解中断处理模型了。

4.1 中断处理模型结构

中断处理模型如下图所示,

其中NR_IRQS表示最大的中断号,在include/asm/arch/irq.h中定义。

irq_desc[]是一个指向irq_desc_t结构的数组, irq_desc_t结构是各个设备中断服务例程的描述符。Irq_desc_t结构体中的成员action指向该中断号对应的irqaction结构体链表。Irqaction结构体定义在include/linux/interrupt.h中,如下:

truct irqaction {
irq_handler_t handler; //中断处理函数,注册时提供
unsigned long flags; //中断标志,注册时提供
cpumask_t mask; //中断掩码
const char *name; //中断名称
void *dev_id; //设备id,本文后面部分介绍中断共享时会详细说明这个参数的作用
struct irqaction *next; //如果有中断共享,则继续执行,
int irq; //中断号,注册时提供
struct proc_dir_entry *dir; //指向IRQn相关的/proc/irq/n目录的描述符
};

在注册中断号为irq的中断服务程序时,系统会根据注册参数封装相应的irqaction结构体。并把中断号为irq的irqaction结构体写入irq_desc [irq]->action。这样就把设备的中断请求号与该设备的中断服务例程irqaction联系在一起了。样当CPU接收到中断请求后,就可以根据中断号通过irq_desc []找到该设备的中断服务程序。

4.2 中断共享的处理模型

共享中断的不同设备的iqraction结构体都会添加进该中断号对应的irq_desc结构体的action成员所指向的irqaction链表内。当内核发生中断时,它会依次调用该链表内所有的handler函数。因此,若驱动程序需要使用共享中断机制,其中断处理函数必须有能力识别是否是自己的硬件产生了中断。通常是通过读取该硬件设备提供的中断flag标志位进行判断。也就是说不是任何设备都可以做为中断共享源的,它必须能够通过的它的中断flag判断出是否发生了中断。
中断共享的注册方法是:

int request_irq(unsigned int irq, irq_handler_t handler,
IRQF_SHARED, const char *devname, void *dev_id)

很多权威资料中都提到,中断共享注册时的注册函数中的dev_id参数是必不可少的,并且dev_id的值必须唯一。那么这里提供唯一的dev_id值的究竟是做什么用的?

根据我们前面中断模型的知识,可以看出发生中断时,内核并不判断究竟是共享中断线上的哪个设备产生了中断,它会循环执行所有该中断线上注册的中断处理函数(即irqaction->handler函数)。因此irqaction->handler函数有责任识别出是否是自己的硬件设备产生了中断,然后再执行该中断处理函数。通常是通过读取该硬件设备提供的中断flag标志位进行判断。那既然kernel循环执行该中断线上注册的所有irqaction->handler函数,把识别究竟是哪个硬件设备产生了中断这件事交给中断处理函数本身去做,那request_irq的dev_id参数究竟是做什么用的?

很多资料中都建议将设备结构指针作为dev_id参数。在中断到来时,迅速地根据硬件寄存器中的信息比照传入的dev_id参数判断是否是本设备的中断,若不是,应迅速返回。这样的说法没有问题,也是我们编程时都遵循的方法。但事实上并不能够说明为什么中断共享必须要设置dev_id。

下面解释一下dev_id参数为什么必须的,而且是必须唯一的。

当调用free_irq注销中断处理函数时(通常卸载驱动时其中断处理函数也会被注销掉),因为dev_id是唯一的,所以可以通过它来判断从共享中断线上的多个中断处理程序中删除指定的一个。如果没有这个参数,那么kernel不可能知道给定的中断线上到底要删除哪一个处理程序。

注销函数定义在Kernel/irq/manage.c中定义:
void free_irq(unsigned int irq, void *dev_id)

五、S3C2410子中断的注册的实现

5.1 S3C2410子中断注册问题的提出

参看3.5节中判断中断号的方法,可以看到只是通过S3C2410中断控制器中的INTOFFSET寄存器来判断的。对于INTPND中的EINT4_7、EINT8_23、INT_UART0、INT_ADC 等带有子中断的向量,INTOFFSET无法判断出具体的中断号。平台留给我们的注册方法如下:

在include/asm/arch/irqs.h中有类似如下定义:

/* interrupts generated from the external interrupts sources */
#define IRQ_EINT4 S3C2410_IRQ(32) /* 48 */
#define IRQ_EINT5 S3C2410_IRQ(33)
#define IRQ_EINT6 S3C2410_IRQ(34)
#define IRQ_EINT7 S3C2410_IRQ(35)
#define IRQ_EINT8 S3C2410_IRQ(36)
#define IRQ_EINT9 S3C2410_IRQ(37)
#define IRQ_EINT10 S3C2410_IRQ(38)
#define IRQ_EINT11 S3C2410_IRQ(39)
#define IRQ_EINT12 S3C2410_IRQ(40)
#define IRQ_EINT13 S3C2410_IRQ(41)
#define IRQ_EINT14 S3C2410_IRQ(42)
#define IRQ_EINT15 S3C2410_IRQ(43)
#define IRQ_EINT16 S3C2410_IRQ(44)
#define IRQ_EINT17 S3C2410_IRQ(45)
#define IRQ_EINT18 S3C2410_IRQ(46)
#define IRQ_EINT19 S3C2410_IRQ(47)
#define IRQ_EINT20 S3C2410_IRQ(48) /* 64 */
#define IRQ_EINT21 S3C2410_IRQ(49)
#define IRQ_EINT22 S3C2410_IRQ(50)
#define IRQ_EINT23 S3C2410_IRQ(51)

可以看到平台为每种子中断都定义了中断号,如果你想实现EINT10的中断注册,直接按照IRQ_EINT10这个中断号注册都可以了。那么平台代码是如何实现这部分中断注册的呢?

5.2 S3C2410子中断注册问题的解决

/*arch/arm/plat-s3c24xx/irq.c*/
void __init s3c24xx_init_irq(void)
{……
set_irq_chained_handler(IRQ_EINT4t7, s3c_irq_demux_extint4t7);
set_irq_chained_handler(IRQ_EINT8t23, s3c_irq_demux_extint8);
set_irq_chained_handler(IRQ_UART0, s3c_irq_demux_uart0);
set_irq_chained_handler(IRQ_UART1, s3c_irq_demux_uart1);
set_irq_chained_handler(IRQ_UART2, s3c_irq_demux_uart2);
set_irq_chained_handler(IRQ_ADCPARENT, s3c_irq_demux_adc);

……
}

平台在初始化时会调用到s3c24xx_init_irq,在此函数中实现了对EINT4_7、EINT8_23、INT_UART0、INT_ADC等中断的注册。下面看看这些带有子中断的中断号对应的处理函数的内容。以IRQ_EINT4t7为例,其它情况类似。

/*arch/arm/plat-s3c24xx/irq.c*/
s3c_irq_demux_extint4t7(unsigned int irq,
struct irq_desc *desc)
{
unsigned long eintpnd = __raw_readl(S3C24XX_EINTPEND);
unsigned long eintmsk = __raw_readl(S3C24XX_EINTMASK);
eintpnd &= ~eintmsk;
eintpnd &= 0xff; /* only lower irqs */

/* eintpnd中可以有多个位同时置1,这一点和intpnd的只能有1个位置1是不一样的 */
while (eintpnd) { //循环执行所有置位的子中断
irq = __ffs(eintpnd); //算出第一个不为0的位,类似arm v5后的clz前导0的作用
eintpnd &= ~(1<<irq);//清除相应的位
irq += (IRQ_EINT4 - 4);//算出对应的中断号
desc_handle_irq(irq, irq_desc + irq);//执行对应子中断的注册函数
}
}

从上面的函数可以看出子中断是如何注册及被调用到的。有人可能会问为何不在include/asm/arch-s3c2410/entry-macro.s 文件中get_irqnr_and_base函数判断中断号时,直接算出对应的子中断号,就可以直接找到子中断处理了呢?

原因是: get_irqnr_and_base是平台给系统提供的函数,对于多个子中断同时置位的情况无法通过一个值返回(因为子中断中,如eintpnd是可以多个位同时置位的))。而intpnd则没有这个问题。 (作者:刘洪涛,华清远见嵌入式学院金牌讲师

相关文章:

  • Azure设计模式之索引表
  • 你该如何填报高考志愿?
  • 网管师与网管员和 网络工程师的区别
  • Azure设计模式之领导者选举
  • 如何从零开始开发一款嵌入式产品(20年的嵌入式经验)
  • Azure设计模式之实例化视图
  • Azure设计模式之管道过滤器模式
  • 用系统函数获取当前季度
  • Azure设计模式之优先消息队列
  • Azure应用部署方式对比
  • BadImageFormatException or An attempt was made to load a program with an incorrect format
  • 获取中国标准时间(由美国官方时间折算)
  • C# 使用ExcelReader 上传excel
  • Nebula3 Render Application Wizard
  • 使用red5+ffmpeg读取ip摄像头流并广播rtmp
  • IP路由与转发
  • Java的Interrupt与线程中断
  • java第三方包学习之lombok
  • Median of Two Sorted Arrays
  • Object.assign方法不能实现深复制
  • rc-form之最单纯情况
  • React 快速上手 - 07 前端路由 react-router
  • Spring技术内幕笔记(2):Spring MVC 与 Web
  • storm drpc实例
  • Terraform入门 - 1. 安装Terraform
  • 包装类对象
  • 高度不固定时垂直居中
  • 聊一聊前端的监控
  • 推荐一款sublime text 3 支持JSX和es201x 代码格式化的插件
  • ​​​​​​​​​​​​​​汽车网络信息安全分析方法论
  • ​Linux·i2c驱动架构​
  • #Linux(帮助手册)
  • (JS基础)String 类型
  • (分布式缓存)Redis分片集群
  • (简单有案例)前端实现主题切换、动态换肤的两种简单方式
  • (转)Windows2003安全设置/维护
  • (转贴)用VML开发工作流设计器 UCML.NET工作流管理系统
  • .net core 3.0 linux,.NET Core 3.0 的新增功能
  • .NET DataGridView数据绑定说明
  • .net framework4与其client profile版本的区别
  • .NET 程序如何获取图片的宽高(框架自带多种方法的不同性能)
  • .net 使用ajax控件后如何调用前端脚本
  • .NET/C# 编译期能确定的字符串会在字符串暂存池中不会被 GC 垃圾回收掉
  • .net用HTML开发怎么调试,如何使用ASP.NET MVC在调试中查看控制器生成的html?
  • @Autowired 与@Resource的区别
  • [ C++ ] template 模板进阶 (特化,分离编译)
  • [ 隧道技术 ] 反弹shell的集中常见方式(二)bash反弹shell
  • []FET-430SIM508 研究日志 11.3.31
  • [ACM] hdu 1201 18岁生日
  • [Android]创建TabBar
  • [C#]获取指定文件夹下的所有文件名(递归)
  • [CISCN2021 Quals]upload(PNG-IDAT块嵌入马)
  • [DAU-FI Net开源 | Dual Attention UNet+特征融合+Sobel和Canny等算子解决语义分割痛点]
  • [docker] Docker的私有仓库部署——Harbor
  • [excel与dict] python 读取excel内容并放入字典、将字典内容写入 excel文件