涉及的图片请参考下面提到的两个参考文档,在此后期补充贴上图片
此篇文档用于透析PowerPC的中断子系统构架,由于中断子系统较为庞杂,故有不懂之处另请借助其他辅助资料。以下文档的安排为PowerPC e300中断子系统硬件构架、PowerPC中断子系统启动过程、PowerPC request_irq流程及PowerPC中断响应流程。
1、 PowerPC e300中断子系统硬件构架
下面对其进行概述,具体可以参考《MPC8313E PowerQUICC II Pro Integrated Processor Family ReferenceManual.pdf》及《Programming Environments Manual for 32-Bit Implementations of the PowerPC™Architecture.pdf》
对于PowerPCe300的中断处理系统,分为四种中断类型,分别如下表
对应于该四种类型的不同中断的中断向量表分布如下:
对于上面中断类型又分为如下优先级:
与我们linux系统开发人员关系密切的中断优先级为3、5、6,分别对应的用处在系统内部中断,外部中断(IRQx)和系统时钟jiffes相关。
我们选用的MPC8313E,采用IPIC中断控制器模块,其可以支持级联中断,对于一般的PowerPC构架设计不会使用级联中断(此句话摘录于freescale IPIC文档)。
IPIC对中断采用最高级中断可动态调节,中断可以分组亦可以独立的管理模式。其对外部中断支持ipic_edge_irq_chip和ipic_level_irq_chip模式,系统内部仅支持ipic_level_irq_chip模式。这两种处理模式在内核代码中再进行细述,此处从略。
以下贴上IPIC模块框架图:
2、 PowerPC中断子系统启动过程
根据《PowerPCdevtree.pdf》,从start_kernel开始分析后,我们留出了中断的初始化流程,下面我们接上该文档继续跟踪中断子系统的执行流程。
通常我们中断系统的irq_desc都以静态表进行分布,其由NR_IRQS来定义该数组的大小,由于整个系统中,很可能只需要个别的中断,故造成很大的irq_desc数组的空洞,造成内存的浪费,故现在提出利用radix_tree动态管理irq_desc数组的大小,我们采用用一个radix tree来保存中断描述符(interrupt作为索引)。这时候,每一个中断描述符都是动态分配,然后插入到radix tree中。我们就选用此种模式。在.config文件下可见CONFIG_SPARSE_IRQ=y。
下面我们则沿着CONFIG_SPARSE_IRQ=y的路线往下走。
start_kernel-> early_irq_init:根据配置的NR_IRQS_LEGACY即CPU核心数分配irq_desc内存,并进行初始化后,加入到radix_tree中。之后调用arch_early_irq_init对irq_desc->status设置为为请求状态(即尚无中断绑定该irq_desc)。
start_kernel->init_IRQ:之后调用ppc_md.init_IRQ,调用到mpc831x_rdb_init_IRQ,完成后,其对每个CPU进行softirqd的创建,对于softirqd软中断,其只有在网卡驱动中我们会调用或调用开发在其之上的tasklet机制,不过我们大部分RISC结构的SOC,对于中断上半段下半段的利用都不是很透彻,大部分的中断处理都在上半段直接完成,不需要进行后半段的调用跟进。softirqd还会在高精度定时器中使用。其对于声卡等对时间追加精准时间戳的驱动有较高应用。在clock_source精度达到400标准,诸如i5类似的芯片后,其能实现微秒级延时。言归正传,我们接着分析mpc831x_rdb_init_IRQ。
mpc831x_rdb_init_IRQ:
static void __init mpc831x_rdb_init_IRQ(void)
{
struct device_node *np;
np =of_find_node_by_type(NULL, "ipic");
if (!np)
return;
ipic_init(np, 0);
/* Initialize thedefault interrupt mapping priorities,
* in case the boot rom changed something onus.
*/
ipic_set_default_priority();
}
我们得知其获取DTB对应的devicetree中ipic节点,之后调用ipic_init和ipic_set_default_priority。在ipic_init中将所有的中断都设置为分组优先级模式,我们如果需要使用HPI(highest priority interrupt)则可以在ipic_init中设置,且设置默认的中断处理需要使用的default_irq_host。在ipic_set_default_ priority中我们将所有的分组中的中断的优先级都依次设置为01234567。具体可以对照文档进行比对。此处仅做指导性阅读总结。
至此,内核对于我们的中断初始化完成,其后各自模块进行中断处理函数注册等都隶属于各驱动模块的事务,对于head_32.S中对于中断向量表的设置,我们放在第四节。
3、 驱动模块从DTB中获取irq号的流程
我们从of_irq_to_resource开始入手分析,驱动模块也可不必调用该接口而直接调用下面细述的核心函数获取对应的软中断号,实际上of_irq_to_resource也是核心函数irq_of_parse_and_map的外裹接口。
下面我们着重分析irq_of_parse_and_map,其也是引入DTB而跟进的中断号获取方式,而不像以前获取dev的platform_data后,继续获取其IRQ资源即可得到软中断号那样通俗。至于不通俗为啥还继续引入,且在将来取代platform_data,请另行查找相关文档。
unsigned int irq_of_parse_and_map(struct device_node *dev,int index)
{
struct of_irq oirq;
if(of_irq_map_one(dev, index, &oirq))
returnNO_IRQ;
returnirq_create_of_mapping(oirq.controller, oirq.specifier,
oirq.size);
}
我们接着跟踪of_irq_map_one和irq_create_of_mapping:
int of_irq_map_one(struct device_node *device, int index,struct of_irq *out_irq)
{
struct device_node*p;
const u32 *intspec,*tmp, *addr;
u32 intsize, intlen;
int res = -EINVAL;
DBG("of_irq_map_one:dev=%s, index=%d\n", device->full_name, index);
/* OldWorld mac stuffis "special", handle out of line */
if(of_irq_workarounds & OF_IMAP_OLDWORLD_MAC)
return of_irq_map_oldworld(device,index, out_irq);
/* Get the interruptsproperty */
intspec =of_get_property(device, "interrupts", &intlen);
if (intspec == NULL)
return-EINVAL;
intlen /=sizeof(u32);
/* Get the regproperty (if any) */
addr = of_get_property(device,"reg", NULL);
/* Look for theinterrupt parent. */
p =of_irq_find_parent(device);
if (p == NULL)
return-EINVAL;
/* Get size ofinterrupt specifier */
tmp =of_get_property(p, "#interrupt-cells", NULL);
if (tmp == NULL)
goto out;
intsize = *tmp;
DBG("intsize=%d intlen=%d\n", intsize, intlen);
/* Check index */
if ((index + 1) *intsize > intlen)
goto out;
/* Get new specifierand map it */
res =of_irq_map_raw(p, intspec + index * intsize, intsize,
addr, out_irq);
out:
of_node_put(p);
return res;
}
其主要对驱动模块中断的中断控制器属性内容进行获取,用以区分下面该驱动模块对应的device node中interrupt属性中内容的分布,并调用of_irq_map_raw。of_irq_map_raw则会获取其对应的软中断号、和其附属的中断控制器。
我们继续分析irq_create_of_mapping:
unsigned int irq_create_of_mapping(struct device_node*controller,
const u32 *intspec, unsigned int intsize)
{
struct irq_host*host;
irq_hw_number_thwirq;
unsigned int type =IRQ_TYPE_NONE;
unsigned int virq;
if (controller ==NULL)
host =irq_default_host;
else
host =irq_find_host(controller);
if (host == NULL) {
printk(KERN_WARNING"irq: no irq host found for %s !\n",
controller->full_name);
returnNO_IRQ;
}
/* If host has notranslation, then we assume interrupt line */
if(host->ops->xlate == NULL)
hwirq =intspec[0];
else {
if(host->ops->xlate(host, controller, intspec, intsize,
&hwirq, &type))
returnNO_IRQ;
}
/* Create mapping */
virq =irq_create_mapping(host, hwirq);
if (virq == NO_IRQ)
return virq;
/* Set type ifspecified and different than the current one */
if (type !=IRQ_TYPE_NONE &&
type != (irq_to_desc(virq)->status &IRQF_TRIGGER_MASK))
set_irq_type(virq,type);
return virq;
}
其先找到中断控制器对应的irq_host,调用host->ops->xlate,进行软中断号和硬件中断号映射,并返回硬件中断号,再调用irq_create_mapping。
unsigned int irq_create_mapping(struct irq_host *host,
irq_hw_number_thwirq)
{
unsigned int virq,hint;
pr_debug("irq:irq_create_mapping(0x%p, 0x%lx)\n", host, hwirq);
/* Look for defaulthost if nececssary */
if (host == NULL)
host =irq_default_host;
if (host == NULL) {
printk(KERN_WARNING"irq_create_mapping called for"
" NULL host, hwirq=%lx\n",hwirq);
WARN_ON(1);
returnNO_IRQ;
}
pr_debug("irq:-> using host @%p\n", host);
/* Check if mapping alreadyexist, if it does, call
* host->ops->map() to update the flags
*/
virq =irq_find_mapping(host, hwirq);
if (virq != NO_IRQ) {
if(host->ops->remap)
host->ops->remap(host,virq, hwirq);
pr_debug("irq:-> existing mapping on virq %d\n", virq);
return virq;
}
/* Get a virtualinterrupt number */
if(host->revmap_type == IRQ_HOST_MAP_LEGACY) {
/* Handlelegacy */
virq =(unsigned int)hwirq;
if (virq ==0 || virq >= NUM_ISA_INTERRUPTS)
returnNO_IRQ;
return virq;
} else {
/* Allocate a virtual interrupt number*/
hint = hwirq% irq_virq_count;
virq =irq_alloc_virt(host, 1, hint);
if (virq ==NO_IRQ) {
pr_debug("irq:-> virq allocation failed\n");
returnNO_IRQ;
}
}
if(irq_setup_virq(host, virq, hwirq))
return NO_IRQ;
printk(KERN_DEBUG"irq: irq %lu on host %s mapped to virtual irq %u\n",
hwirq,host->of_node ? host->of_node->full_name : "null", virq);
return virq;
}
其调用irq_find_mapping,检查是否该硬件中断号已经注册过了,如果没有注册过,接下来调用irq_alloc_virt则是在irq_map中注册中断号,上面提到过我们是使用radix_tree动态管理的irq_map,我们所使用的软件中断号和硬件中断号都是一一对应的。最后调用irq_setup_virq,
static int irq_setup_virq(struct irq_host *host, unsigned intvirq,
irq_hw_number_t hwirq)
{
struct irq_desc*desc;
desc =irq_to_desc_alloc_node(virq, 0);
if (!desc) {
pr_debug("irq:-> allocating desc failed\n");
goto error;
}
/* ClearIRQ_NOREQUEST flag */
desc->status&= ~IRQ_NOREQUEST;
/* map it */
smp_wmb();
irq_map[virq].hwirq =hwirq;
smp_mb();
if(host->ops->map(host, virq, hwirq)) {
pr_debug("irq:-> mapping failed, freeing\n");
goto error;
}
return 0;
error:
irq_free_virt(virq,1);
return -1;
}
其先为软中断号分配,对应的irq_desc,之后开始设置对应的irq_desc等一些成员的初始化,其调用
host->ops->remap,我们知道我们MPC8313E的irq_host为ipic,则其会调用到ipic_host_map,其会设置中断的处理类型为handle_level_irq,其他的则是一些初始化,MPC8313E只有外部中断支持handle_edge_irq。对于他们的区别,则是他们对应于重复响应的同种中断的处理方式不一样,level则会选择直接丢弃,edge则会选择缓存一次,并在中断中追加一次该中断的处理。
4、 PowerPC request_irq流程
在2.6后期版本逐渐对于request_irq进行了取代工作,在3.0及之后的内核中就很难找到request_irq这个接口,鄙人当初阅读的版本为kernel 3.2.1。此处也给出request_irq的替代接口,其为request_thread_irq。
先贴上irq_desc的结构
struct irq_desc {
unsigned int irq;
irq_flow_handler_t handle_irq;
struct irq_chip *chip;
void *handler_data;
void *chip_data;
struct irqaction *action; /*IRQ action list */
unsigned int status; /* IRQ status */
} ;
为节约空间,删除一些不影响我们概要性解析的成员。
int request_threaded_irq(unsigned int irq, irq_handler_t handler,
irq_handler_t thread_fn, unsigned longirqflags,
const char *devname, void *dev_id)
{
struct irqaction *action;
struct irq_desc *desc;
int retval;
desc = irq_to_desc(irq);
if (!handler) {
if (!thread_fn)
return-EINVAL;
handler =irq_default_primary_handler;
}
action = kzalloc(sizeof(structirqaction), GFP_KERNEL);
if (!action)
return -ENOMEM;
action->handler = handler;
action->thread_fn =thread_fn;
action->flags = irqflags;
action->name = devname;
action->dev_id = dev_id;
retval = __setup_irq(irq,desc, action);
returnretval;
}
为节约空间,已经滤除掉合法性检查及临界区代码。
我们看到该函数先获取对应irq_desc指针,之后分配irqaction空间,并对其handler和thread_fn进行设置,并调用__set_irq将该irqaction挂在到irq_desc的action链表中,并为该irqaction创建内核线程。在我们后面解析的action->handler后,如果返回IRQ_WAKE_THREAD,会激活内核线程执行action->thread_fn,这就是我们所说的中断处理的下半段了,对于下半段的方法分为内核线程,softirqd,tasklet。但是实际上我们大多数都会看到的返回IRQ_HANDLED。直接从上半段结束中断处理。
此处指细述该接口,对于中断响应的整体流程,留作下节。
5、 PowerPC中断响应流程
在head32.S中
EXCEPTION(0x500,HardwareInterrupt, do_IRQ, EXC_XFER_LITE)
0x500处用于处理我们硬件中断的中断向量表,且最终运行的c语言的接口为do_IRQ,
我们跟踪do_IRQ:
void do_IRQ(struct pt_regs *regs)
{
irq =ppc_md.get_irq();
if (irq != NO_IRQ&& irq != NO_IRQ_IGNORE)
handle_one_irq(irq);
}
去掉无关代码,其功能则为获取软件中断号,并调用handle_one_irq处理。
Ppc_md.get_irq指向的函数是ipic_get_irq,其从硬件中断寄存器中获取当前最高优先级的中断,并调用irq_linear_revmap获取硬件中断号对应的软件中断号。
handle_one_irq则是generic_handle_irq的外裹函数,generic_handle_irq也是generic_handle_irq_desc的外裹函数,但是其内部也根据irq软件中断号获取对应的irq_desc后调用generic_handle_irq_desc,generic_handle_irq_desc则调用irq_desc->handler_irq,这事这个handle_irq就是我们之前设置过的handle_level_irq和handle_edge_irq,我们选取handle_level_irq进行分析,他们的区别在第三节已做介绍。
handle_level_irq(unsigned int irq, struct irq_desc *desc)
{
struct irqaction*action;
irqreturn_taction_ret;
raw_spin_lock(&desc->lock);
mask_ack_irq(desc,irq);
if(unlikely(desc->status & IRQ_INPROGRESS))
gotoout_unlock;
desc->status &=~(IRQ_REPLAY | IRQ_WAITING);
kstat_incr_irqs_this_cpu(irq,desc);
/*
* If its disabled or no action available
* keep it masked and get out of here
*/
action =desc->action;
if (unlikely(!action|| (desc->status & IRQ_DISABLED)))
gotoout_unlock;
desc->status |= IRQ_INPROGRESS;
raw_spin_unlock(&desc->lock);
action_ret =handle_IRQ_event(irq, action);
if (!noirqdebug)
note_interrupt(irq,desc, action_ret);
raw_spin_lock(&desc->lock);
desc->status &=~IRQ_INPROGRESS;
if (!(desc->status& (IRQ_DISABLED | IRQ_ONESHOT)))
unmask_irq(desc,irq);
out_unlock:
raw_spin_unlock(&desc->lock);
}
该函数主要获取irq_desc的irqaction链表头并调用hanle_IRQ_event。
irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction*action)
{
irqreturn_t ret,retval = IRQ_NONE;
unsigned int status =0;
if (!(action->flags& IRQF_DISABLED))
local_irq_enable_in_hardirq();
do {
trace_irq_handler_entry(irq,action);
ret =action->handler(irq, action->dev_id);
trace_irq_handler_exit(irq,action, ret);
switch (ret){
case IRQ_WAKE_THREAD:
/*
* Set result to handled so the spurious check
* does not trigger.
*/
ret= IRQ_HANDLED;
/*
* Catch drivers which return WAKE_THREAD but
* did not set up a thread function
*/
if(unlikely(!action->thread_fn)){
warn_no_thread(irq,action);
break;
}
/*
* Wake up the handler thread for this
* action. In case the thread crashed and was
* killed we just pretend that we handled the
* interrupt. The hardirq handler above has
* disabled the device interrupt, so no irq
* storm is lurking.
*/
if(likely(!test_bit(IRQTF_DIED,
&action->thread_flags))) {
set_bit(IRQTF_RUNTHREAD,&action->thread_flags);
wake_up_process(action->thread);
}
/*Fall through to add to randomness */
caseIRQ_HANDLED:
status|= action->flags;
break;
default:
break;
}
retval |=ret;
action =action->next;
} while (action);
if (status &IRQF_SAMPLE_RANDOM)
add_interrupt_randomness(irq);
local_irq_disable();
return retval;
}
该函数对irqaction链表做循环调用,这就是我们通常在linux内核书籍上说道的中断是可以共享,实际上看代码也复杂,当初理解就是那么比较绕口。其依次处理每个action->handler,也就是我们通过request_irq注册进去的中断处理程序,中断处理程序返回值若为IRQ_WAKE_THREAD时,则会激活内核线程。进行中断下半段的任务执行。