am33xx linux中斷處理流程
運(yùn)行環(huán)境
kernel 5.10
CPU Ti am33xx
linux中斷的3個(gè)結(jié)構(gòu)體
struct irq_desc {
struct irq_common_data irq_common_data;
struct irq_data irq_data;
unsigned int __percpu *kstat_irqs;
irq_flow_handler_t handle_irq;
struct irqaction *action; /* IRQ action list */ // 里面存放了用戶注冊(cè)的中斷服務(wù)代碼
unsigned int status_use_accessors;
unsigned int core_internal_state__do_not_mess_with_it;
unsigned int depth; /* nested irq disables */
unsigned int wake_depth; /* nested wake enables */
unsigned int tot_count;
unsigned int irq_count; /* For detecting broken IRQs */
unsigned long last_unhandled; /* Aging timer for unhandled count */
unsigned int irqs_unhandled;
atomic_t threads_handled;
int threads_handled_last;
raw_spinlock_t lock;
struct cpumask *percpu_enabled;
const struct cpumask *percpu_affinity;
#ifdef CONFIG_SMP
const struct cpumask *affinity_hint;
struct irq_affinity_notify *affinity_notify;
#ifdef CONFIG_GENERIC_PENDING_IRQ
cpumask_var_t pending_mask;
#endif
#endif
unsigned long threads_oneshot;
atomic_t threads_active;
wait_queue_head_t wait_for_threads;
#ifdef CONFIG_PM_SLEEP
unsigned int nr_actions;
unsigned int no_suspend_depth;
unsigned int cond_suspend_depth;
unsigned int force_resume_depth;
#endif
#ifdef CONFIG_PROC_FS
struct proc_dir_entry *dir;
#endif
#ifdef CONFIG_GENERIC_IRQ_DEBUGFS
struct dentry *debugfs_file;
const char *dev_name;
#endif
#ifdef CONFIG_SPARSE_IRQ
struct rcu_head rcu;
struct kobject kobj;
#endif
struct mutex request_mutex;
int parent_irq;
struct module *owner;
const char *name;
} ____cacheline_internodealigned_in_smp;
struct irqaction {
irq_handler_t handler; // 用戶注冊(cè)的中斷服務(wù)代碼, 中斷發(fā)生時(shí)就會(huì)運(yùn)行這個(gè)中斷處理函數(shù)
void *dev_id;
void __percpu *percpu_dev_id;
struct irqaction *next;
irq_handler_t thread_fn;
struct task_struct *thread;
struct irqaction *secondary;
unsigned int irq; //hw中斷號(hào),
unsigned int flags; //中斷標(biāo)志,注冊(cè)時(shí)設(shè)置,比如上升沿中斷,下降沿中斷等
unsigned long thread_flags;
unsigned long thread_mask;
const char *name; //中斷名稱,產(chǎn)生中斷的硬件的名字
struct proc_dir_entry *dir; // proc/irq/
} ____cacheline_internodealigned_in_smp;
struct irq_data {
u32 mask;
unsigned int irq;
unsigned long hwirq;
struct irq_common_data *common;
struct irq_chip *chip; // 芯片端的中斷處理函數(shù)
struct irq_domain *domain; // 代表一個(gè)interrupt controller 設(shè)備樹(shù)中的INTC
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
struct irq_data *parent_data;
#endif
void *chip_data;
};
整體框圖如下:


linux硬件中斷處理流程
1. 外設(shè)產(chǎn)生中斷
2. CPU觸發(fā)中斷
3. 跳轉(zhuǎn)到異常向量入口:
保存現(xiàn)場(chǎng)
執(zhí)行中斷服務(wù)
恢復(fù)現(xiàn)場(chǎng)
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
我們主要關(guān)注如何執(zhí)行中斷服務(wù),在arch\arm\kernel\entry-armv.S中,有如下幾條語(yǔ)句
.macro irq_handler #ifdef CONFIG_GENERIC_IRQ_MULTI_HANDLER ldr r1, =handle_arch_irq mov r0, sp badr lr, 9997f ldr pc, [r1] #else arch_irq_handler_default #endif
其中 handle_arch_irq 即為CPU處理中斷時(shí)會(huì)調(diào)用到的硬件中斷服務(wù)入口,handle_arch_irq在 kernel/irq/handle.c 中被賦值
#ifdef CONFIG_GENERIC_IRQ_MULTI_HANDLER
#ifndef CONFIG_IRQCHIP_XILINX_INTC_MODULE_SUPPORT_EXPERIMENTAL
int __init set_handle_irq(void (*handle_irq)(struct pt_regs *))
#else
int set_handle_irq(void (*handle_irq)(struct pt_regs *))
#endif
{
if (handle_arch_irq)
return -EBUSY;
handle_arch_irq = handle_irq;
return 0;
}
#endif
set_handle_irq 被誰(shuí)調(diào)用?主要由CPU廠家的BSP工程師調(diào)用,比如TI的AM33xx系列平臺(tái),在 drivers/irqchip/irq-omap-intc.c 中的以下接口調(diào)用
static int __init intc_of_init(struct device_node *node,
struct device_node *parent)
{
int ret;
omap_nr_pending = 3;
omap_nr_irqs = 96;
if (WARN_ON(!node))
return -ENODEV;
if (of_device_is_compatible(node, "ti,dm814-intc") ||
of_device_is_compatible(node, "ti,dm816-intc") ||
of_device_is_compatible(node, "ti,am33xx-intc")) {
omap_nr_irqs = 128;
omap_nr_pending = 4;
}
ret = omap_init_irq(-1, of_node_get(node));
if (ret < 0)
return ret;
set_handle_irq(omap_intc_handle_irq); // 設(shè)置硬中斷服務(wù)函數(shù)入口
return 0;
}
omap_intc_handle_irq便是am33xx觸發(fā)中斷后,需要響應(yīng)的中斷服務(wù)入口,該入口做了哪些工作?
static asmlinkage void __exception_irq_entry
omap_intc_handle_irq(struct pt_regs *regs)
{
extern unsigned long irq_err_count;
u32 irqnr;
irqnr = intc_readl(INTC_SIR); // 獲取硬件中斷號(hào)
/*
* A spurious IRQ can result if interrupt that triggered the
* sorting is no longer active during the sorting (10 INTC
* functional clock cycles after interrupt assertion). Or a
* change in interrupt mask affected the result during sorting
* time. There is no special handling required except ignoring
* the SIR register value just read and retrying.
* See section 6.2.5 of AM335x TRM Literature Number: SPRUH73K
*
* Many a times, a spurious interrupt situation has been fixed
* by adding a flush for the posted write acking the IRQ in
* the device driver. Typically, this is going be the device
* driver whose interrupt was handled just before the spurious
* IRQ occurred. Pay attention to those device drivers if you
* run into hitting the spurious IRQ condition below.
*/
if (unlikely((irqnr & SPURIOUSIRQ_MASK) == SPURIOUSIRQ_MASK)) {
pr_err_once("%s: spurious irq!\n", __func__);
irq_err_count++;
omap_ack_irq(NULL);
return;
}
irqnr &= ACTIVEIRQ_MASK;
handle_domain_irq(domain, irqnr, regs); // 根據(jù)硬件中斷號(hào),在domain鏈表中查找映射好的IRQ number, 實(shí)現(xiàn)原理是怎樣的?
}
domain是如何實(shí)現(xiàn)HW 中斷號(hào)和 IRQ number映射的? 在解析設(shè)備樹(shù)的時(shí)候,會(huì)調(diào)用 irq_of_parse_and_map 接口創(chuàng)建映射;詳情參考 linux中斷號(hào)映射
handle_domain_irq 調(diào)用了 __handle_domain_irq, 在__handle_domain_irq中,開(kāi)始調(diào)用硬中斷服務(wù)函數(shù),也就是中斷上半部
int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq,
bool lookup, struct pt_regs *regs)
{
struct pt_regs *old_regs = set_irq_regs(regs);
unsigned int irq = hwirq;
int ret = 0;
irq_enter();
#ifdef CONFIG_IRQ_DOMAIN
if (lookup)
irq = irq_find_mapping(domain, hwirq); // 根據(jù)硬中斷號(hào),在映射表中找到對(duì)應(yīng)的 IRQ number
#endif
/*
* Some hardware gives randomly wrong interrupts. Rather
* than crashing, do something sensible.
*/
if (unlikely(!irq || irq >= nr_irqs)) {
ack_bad_irq(irq);
ret = -EINVAL;
} else {
generic_handle_irq(irq); // 根據(jù)IRQ number,查找中斷服務(wù),并執(zhí)行
}
irq_exit(); // 退出硬件中斷服務(wù),然后 invoke_softirq(),再 __do_softirq(),喚醒softirq隊(duì)列,進(jìn)入下半部開(kāi)始執(zhí)行
set_irq_regs(old_regs);
return ret;
}
他會(huì)調(diào)用 generic_handle_irq(irq) , 以下為 generic_handle_irq 代碼
int generic_handle_irq(unsigned int irq)
{
struct irq_desc *desc = irq_to_desc(irq); // 根據(jù)IRQ number 找到 中斷描述符
struct irq_data *data;
if (!desc)
return -EINVAL;
data = irq_desc_get_irq_data(desc);
if (WARN_ON_ONCE(!in_irq() && handle_enforce_irqctx(data)))
return -EPERM;
generic_handle_irq_desc(desc); // 根據(jù)中斷描述符,找到對(duì)應(yīng)的 handler
return 0;
}
generic_handle_irq_desc 會(huì)調(diào)用 desc結(jié)構(gòu)體中的 hande_irq 成員: desc->handle_irq(desc); 他指向哪 ?
desc->handle_irq(desc) 最后會(huì)調(diào)用到 irqaction 結(jié)構(gòu)體中的 handler 成員,也就是用戶通過(guò) request_irq (request_irq詳解) 接口注冊(cè)的中斷服務(wù)程序,附上網(wǎng)上找的一份簡(jiǎn)單示意圖(kernel2.6)

desc->handle_irq(desc) 是如何關(guān)聯(lián)到 irqaction 結(jié)構(gòu)體中的 handler 成員?
先從設(shè)備端看:
拿AM33xx GPIO舉例:
在 gpio-omap.c 中的 omap_gpio_probe 里面,會(huì)對(duì) irq_chip 結(jié)構(gòu)體的 irq_set_type 賦值 omap_gpio_irq_type,然后將 am33xx芯片端的gpio 中斷處理相關(guān)的函數(shù)注冊(cè)到linux kernel,kernel調(diào)用 desc->handle_irq(desc) 時(shí),即可調(diào)用到 am33xx xinp端的 gpio 中斷處理函數(shù)(具體細(xì)節(jié)接下來(lái)分析);
omap_gpio_irq_type 函數(shù)如下:
static int omap_gpio_irq_type(struct irq_data *d, unsigned type)
{
struct gpio_bank *bank = omap_irq_data_get_bank(d);
int retval;
unsigned long flags;
unsigned offset = d->hwirq;
if (type & ~IRQ_TYPE_SENSE_MASK)
return -EINVAL;
if (!bank->regs->leveldetect0 &&
(type & (IRQ_TYPE_LEVEL_LOW|IRQ_TYPE_LEVEL_HIGH)))
return -EINVAL;
raw_spin_lock_irqsave(&bank->lock, flags);
retval = omap_set_gpio_triggering(bank, offset, type);
if (retval) {
raw_spin_unlock_irqrestore(&bank->lock, flags);
goto error;
}
omap_gpio_init_irq(bank, offset);
if (!omap_gpio_is_input(bank, offset)) {
raw_spin_unlock_irqrestore(&bank->lock, flags);
retval = -EINVAL;
goto error;
}
raw_spin_unlock_irqrestore(&bank->lock, flags);
if (type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH))
irq_set_handler_locked(d, handle_level_irq); // 電平觸發(fā)中斷
else if (type & (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING))
/*
* Edge IRQs are already cleared/acked in irq_handler and
* not need to be masked, as result handle_edge_irq()
* logic is excessed here and may cause lose of interrupts.
* So just use handle_simple_irq.
*/
irq_set_handler_locked(d, handle_simple_irq); // 邊沿觸發(fā)中斷
return 0;
error:
return retval;
}
該函數(shù)主要是設(shè)置了 gpio 電平觸發(fā), 邊沿觸發(fā)的中斷處理服務(wù),先拿邊沿觸發(fā)分析,看看 handle_simple_irq 函數(shù)是如何一步步調(diào)用到 irqaction 結(jié)構(gòu)體中的 handler 成員的(也就是用戶通過(guò)request_irq 申請(qǐng)的中斷服務(wù)):
void handle_simple_irq(struct irq_desc *desc)
{
raw_spin_lock(&desc->lock);
if (!irq_may_run(desc))
goto out_unlock;
desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);
if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data))) {
desc->istate |= IRQS_PENDING;
goto out_unlock;
}
kstat_incr_irqs_this_cpu(desc); // 設(shè)置處理標(biāo)記,避免多次重復(fù)進(jìn)入中斷處理
handle_irq_event(desc); // 中斷服務(wù)相關(guān)
out_unlock:
raw_spin_unlock(&desc->lock);
}
再來(lái)看看 handle_irq_event(desc) 做了啥 irqreturn_t handle_irq_event(struct irq_desc *desc)
{
irqreturn_t ret;
desc->istate &= ~IRQS_PENDING;
irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);
raw_spin_unlock(&desc->lock);
ret = handle_irq_event_percpu(desc); // 進(jìn)入中斷服務(wù)
raw_spin_lock(&desc->lock);
irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);
return ret;
}
irqreturn_t handle_irq_event_percpu(struct irq_desc *desc)
{
irqreturn_t retval;
unsigned int flags = 0;
retval = __handle_irq_event_percpu(desc, &flags); // 進(jìn)入中斷服務(wù)
add_interrupt_randomness(desc->irq_data.irq);
if (!noirqdebug)
note_interrupt(desc, retval);
return retval;
}
接下來(lái)就真正調(diào)用到了 irqaction 結(jié)構(gòu)體中的 handler 成員
irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc, unsigned int *flags)
{
irqreturn_t retval = IRQ_NONE;
unsigned int irq = desc->irq_data.irq;
struct irqaction *action;
record_irq_time(desc);
for_each_action_of_desc(desc, action) { // 輪詢?cè)撝袛嗝枋龇锩娴乃?action
irqreturn_t res;
/*
* If this IRQ would be threaded under force_irqthreads, mark it so.
*/
if (irq_settings_can_thread(desc) &&
!(action->flags & (IRQF_NO_THREAD | IRQF_PERCPU | IRQF_ONESHOT)))
lockdep_hardirq_threaded();
trace_irq_handler_entry(irq, action);
res = action->handler(irq, action->dev_id); // 調(diào)用用戶注冊(cè)的中斷服務(wù)
trace_irq_handler_exit(irq, action, res);
if (WARN_ONCE(!irqs_disabled(),"irq %u handler %pS enabled interrupts\n",
irq, action->handler))
local_irq_disable();
switch (res) {
case IRQ_WAKE_THREAD:
/*
* 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;
}
__irq_wake_thread(desc, action);
fallthrough; /* to add to randomness */
case IRQ_HANDLED:
*flags |= action->flags;
break;
default:
break;
}
retval |= res;
}
return retval;
}
omap_gpio_probe 是如何將 irq_chip 關(guān)聯(lián)到 irq_desc 結(jié)構(gòu)體的, 也就是說(shuō) kernel調(diào)用 desc->handle_irq(desc) 時(shí),怎么就能調(diào)用到 handle_simple_irq?后續(xù)繼續(xù)分析!

浙公網(wǎng)安備 33010602011771號(hào)