Linux 中斷之中斷處理淺析

  1. 中斷的概念

中斷是指在 CPU 正常運行期間,由於內外部事件或由程序預先安排的事件引起的 CPU 暫時停止正在運行的程序,轉而爲該內部或外部事件或預先安排的事件服務的程序中去,服務完畢後再返回去繼續運行被暫時中斷的程序。Linux 中通常分爲外部中斷(又叫硬件中斷)和內部中斷(又叫異常)。

軟件對硬件進行配置後,軟件期望等待硬件的某種狀態(比如,收到了數據),這裏有兩種方式,一種是輪詢(polling):CPU 不斷的去讀硬件狀態。另一種是當硬件完成某種事件後,給 CPU 一箇中斷,讓 CPU 停下手上的事情,去處理這個中斷。很顯然,中斷的交互方式提高了系統的吞吐。

當 CPU 收到一箇中斷 (IRQ)的時候,會去執行該中斷對應的處理函數(ISR)。普通情況下,會有一箇中斷向量表,向量表中定義了 CPU 對應的每一個外設資源的中斷處理程序的入口,當發生對應的中斷的時候, CPU 直接跳轉到這個入口執行程序。也就是中斷上下文。(注意:中斷上下文中,不可阻塞睡眠)。

  1. Linux 中斷 top/bottom

玩過 MCU 的人都知道,中斷服務程序的設計最好是快速完成任務並退出,因爲此刻系統處於被中斷中。但是在 ISR 中又有一些必須完成的事情,比如:清中斷標誌,讀 / 寫數據,寄存器操作等。

在 Linux 中,同樣也是這個要求,希望儘快的完成 ISR。但事與願違,有些 ISR 中任務繁重,會消耗很多時間,導致響應速度變差。Linux 中針對這種情況,將中斷分爲了兩部分:

  1. 上半部(top half):收到一箇中斷,立即執行,有嚴格的時間限制,只做一些必要的工作,比如:應答,復位等。這些工作都是在所有中斷被禁止的情況下完成的。

  2. 底半部(bottom half):能夠被推遲到後面完成的任務會在底半部進行。在適合的時機,下半部會被開中斷執行。

  3. 中斷處理程序


驅動程序可以使用接口:

request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
	    const char *name, void *dev)

像系統申請註冊一箇中斷處理程序。

其中的參數:

中斷標誌 flag 的含義:

調用 request _irq 成功執行返回 0。常見錯誤是 -EBUSY,表示給定的中斷線已經在使用(或者沒有指定 IRQF_SHARED)

注意:request_irq 函數可能引起睡眠,所以不允許在中斷上下文或者不允許睡眠的代碼中調用。

釋放中斷:

const void *free_irq(unsigned int irq, void *dev_id)

用於釋放中斷處理函數。

注意:Linux 中的中斷處理程序是無須重入的。當給定的中斷處理程序正在執行的時候,其中斷線在所有的處理器上都會被屏蔽掉,以防在同一個中斷線上又接收到另一個新的中斷。通常情況下,除了該中斷的其他中斷都是打開的,也就是說其他的中斷線上的重點都能夠被處理,但是當前的中斷線總是被禁止的,故,同一個中斷處理程序是絕對不會被自己嵌套的。

  1. 中斷上下文

與進程上下文不一樣,內核執行中斷服務程序的時候,處於中斷上下文。中斷處理程序並沒有自己的獨立的棧,而是使用了內核棧,其大小一般是有限制的(32bit 機器 8KB)。所以其必須短小精悍。同時中斷服務程序是打斷了正常的程序流程,這一點上也必須保證快速的執行。同時中斷上下文中是不允許睡眠,阻塞的。

中斷上下文不能睡眠的原因是:

1、 中斷處理的時候, 不應該發生進程切換,因爲在中斷 context 中,唯一能打斷當前中斷 handler 的只有更高優先級的中斷,它不會被進程打斷,如果在 中斷 context 中休眠,則沒有辦法喚醒它,因爲所有的 wake_up_xxx 都是針對某個進程而言的,而在中斷 context 中,沒有進程的概念,沒 有一個 task_struct(這點對於 softirq 和 tasklet 一樣),因此真的休眠了,比如調用了會導致 block 的例程,內核幾乎肯定會死。

2、schedule() 在切換進程時,保存當前的進程上下文(CPU 寄存器的值、進程的狀態以及堆棧中的內容),以便以後恢復此進程運行。中斷髮生後,內核會先保存當前被中斷的進程上下文(在調用中斷處理程序後恢復);

但在中斷處理程序裏,CPU 寄存器的值肯定已經變化了吧(最重要的程序計數器 PC、堆棧 SP 等),如果此時因爲睡眠或阻塞操作調用了 schedule(),則保存的進程上下文就不是當前的進程 context 了. 所以不可以在中斷處理程序中調用 schedule()。

3、內核中 schedule() 函數本身在進來的時候判斷是否處於中斷上下文:

if(unlikely(in_interrupt()))

BUG();

因此,強行調用 schedule() 的結果就是內核 BUG。

4、中斷 handler 會使用被中斷的進程內核堆棧,但不會對它有任何影響,因爲 handler 使用完後會完全清除它使用的那部分堆棧,恢復被中斷前的原貌。

5、處於中斷 context 時候,內核是不可搶佔的。因此,如果休眠,則內核一定掛起。

  1. 舉例

比如 RTC 驅動程序 (drivers/char/rtc.c)。在 RTC 驅動的初始化階段,會調用到 rtc_init 函數:

module_init(rtc_init);

在這個初始化函數中調用到了 request_irq 用於申請中斷資源,並註冊服務程序:

static int __init rtc_init(void)
{
...
    rtc_int_handler_ptr = rtc_interrupt;
...
    request_irq(RTC_IRQ, rtc_int_handler_ptr, 0, "rtc", NULL)
...
}

RTC_IRQ 是中斷號,和處理器綁定。

rtc_interrupt 是中斷處理程序:

static irqreturn_t rtc_interrupt(int irq, void *dev_id)
{
	/*
	 *	Can be an alarm interrupt, update complete interrupt,
	 *	or a periodic interrupt. We store the status in the
	 *	low byte and the number of interrupts received since
	 *	the last read in the remainder of rtc_irq_data.
	 */
 
	spin_lock(&rtc_lock);
	rtc_irq_data += 0x100;
	rtc_irq_data &= ~0xff;
	if (is_hpet_enabled()) {
		/*
		 * In this case it is HPET RTC interrupt handler
		 * calling us, with the interrupt information
		 * passed as arg1, instead of irq.
		 */
		rtc_irq_data |= (unsigned long)irq & 0xF0;
	} else {
		rtc_irq_data |= (CMOS_READ(RTC_INTR_FLAGS) & 0xF0);
	}
 
	if (rtc_status & RTC_TIMER_ON)
		mod_timer(&rtc_irq_timer, jiffies + HZ/rtc_freq + 2*HZ/100);
 
	spin_unlock(&rtc_lock);
 
	wake_up_interruptible(&rtc_wait);
 
	kill_fasync(&rtc_async_queue, SIGIO, POLL_IN);
 
	return IRQ_HANDLED;
}

每次收到 RTC 中斷,就會調用進這個函數。

  1. 中斷處理流程

發生中斷時,CPU 執行異常向量 vector_irq 的代碼, 即異常向量表中的中斷異常的代碼,它是一個跳轉指令,跳去執行真正的中斷處理程序,在 vector_irq 裏面,最終會調用中斷處理的總入口函數。

C 語言的入口爲 :asm_do_IRQ(unsigned int irq, struct pt_regs *regs)

asmlinkage void __exception_irq_entry
asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
	handle_IRQ(irq, regs);
}

該函數的入參 irq 爲中斷號。

asm_do_IRQ -> handle_IRQ

void handle_IRQ(unsigned int irq, struct pt_regs *regs)
{
	__handle_domain_irq(NULL, irq, false, regs);
}

handle_IRQ -> __handle_domain_irq

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);
#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);
	}
 
	irq_exit();
	set_irq_regs(old_regs);
	return ret;
}

這裏請注意:

先調用了 irq_enter 標記進入了硬件中斷:

irq_enter 是更新一些系統的統計信息,同時在__irq_enter 宏中禁止了進程的搶佔。雖然在產生 IRQ 時,ARM 會自動把 CPSR 中的 I 位置位,禁止新的 IRQ 請求,直到中斷控制轉到相應的流控層後才通過 local_irq_enable() 打開。那爲何還要禁止搶佔?這是因爲要考慮中斷嵌套的問題,一旦流控層或驅動程序主動通過 local_irq_enable 打開了 IRQ,而此時該中斷還沒處理完成,新的 irq 請求到達,這時代碼會再次進入 irq_enter,在本次嵌套中斷返回時,內核不希望進行搶佔調度,而是要等到最外層的中斷處理完成後才做出調度動作,所以纔有了禁止搶佔這一處理

再調用 generic_handle_irq

最後調用 irq_exit 刪除進入硬件中斷的標記

__handle_domain_irq -> generic_handle_irq

int generic_handle_irq(unsigned int irq)
{
	struct irq_desc *desc = irq_to_desc(irq);
 
	if (!desc)
		return -EINVAL;
	generic_handle_irq_desc(desc);
	return 0;
}
EXPORT_SYMBOL_GPL(generic_handle_irq);

首先在函數 irq_to_desc 中根據發生中斷的中斷號,去取出它的 irq_desc 中斷描述結構,然後調用 generic_handle_irq_desc:

static inline void generic_handle_irq_desc(struct irq_desc *desc)
{
	desc->handle_irq(desc);
}

這裏調用了 handle_irq 函數。

所以,在上述流程中,還需要分析 irq_to_desc  流程:

struct irq_desc *irq_to_desc(unsigned int irq)
{
	return (irq < NR_IRQS) ? irq_desc + irq : NULL;
}
EXPORT_SYMBOL(irq_to_desc);

NR_IRQS 是支持的總的中斷個數,當然,irq 不能夠大於這個數目。所以返回 irq_desc + irq。

irq_desc 是一個全局的數組:

struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
	[0 ... NR_IRQS-1] = {
		.handle_irq	= handle_bad_irq,
		.depth		= 1,
		.lock		= __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
	}
};

這裏是這個數組的初始化的地方。所有的 handle_irq 函數都被初始化成爲了 handle_bad_irq。

細心的觀衆可能發現了,調用這個 desc->handle_irq(desc) 函數,並不是咱們註冊進去的中斷處理函數啊,因爲兩個函數的原型定義都不一樣。這個 handle_irq 是 irq_flow_handler_t 類型,而我們註冊進去的服務程序是 irq_handler_t,這兩個明顯不是同一個東西,所以這裏我們還需要繼續分析。

6.1 中斷相關的數據結構

Linux 中斷相關的數據結構有 3 個

irq_desc 結構如下

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;
#ifdef CONFIG_IRQ_PREFLOW_FASTEOI
	irq_preflow_handler_t	preflow_handler;
#endif
	struct irqaction	*action;	/* IRQ action list */
	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		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;

irqaction 結構如下:

/**
 * struct irqaction - per interrupt action descriptor
 * @handler:	interrupt handler function
 * @name:	name of the device
 * @dev_id:	cookie to identify the device
 * @percpu_dev_id:	cookie to identify the device
 * @next:	pointer to the next irqaction for shared interrupts
 * @irq:	interrupt number
 * @flags:	flags (see IRQF_* above)
 * @thread_fn:	interrupt handler function for threaded interrupts
 * @thread:	thread pointer for threaded interrupts
 * @secondary:	pointer to secondary irqaction (force threading)
 * @thread_flags:	flags related to @thread
 * @thread_mask:	bitmask for keeping track of @thread activity
 * @dir:	pointer to the proc/irq/NN/name entry
 */
struct irqaction {
	irq_handler_t		handler;
	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;
	unsigned int		flags;
	unsigned long		thread_flags;
	unsigned long		thread_mask;
	const char		*name;
	struct proc_dir_entry	*dir;
} ____cacheline_internodealigned_in_smp;

irq_chip 描述如下:

/**
 * struct irq_chip - hardware interrupt chip descriptor
 *
 * @parent_device:	pointer to parent device for irqchip
 * @name:		name for /proc/interrupts
 * @irq_startup:	start up the interrupt (defaults to ->enable if NULL)
 * @irq_shutdown:	shut down the interrupt (defaults to ->disable if NULL)
 * @irq_enable:		enable the interrupt (defaults to chip->unmask if NULL)
 * @irq_disable:	disable the interrupt
 * @irq_ack:		start of a new interrupt
 * @irq_mask:		mask an interrupt source
 * @irq_mask_ack:	ack and mask an interrupt source
 * @irq_unmask:		unmask an interrupt source
 * @irq_eoi:		end of interrupt
 * @irq_set_affinity:	Set the CPU affinity on SMP machines. If the force
 *			argument is true, it tells the driver to
 *			unconditionally apply the affinity setting. Sanity
 *			checks against the supplied affinity mask are not
 *			required. This is used for CPU hotplug where the
 *			target CPU is not yet set in the cpu_online_mask.
 * @irq_retrigger:	resend an IRQ to the CPU
 * @irq_set_type:	set the flow type (IRQ_TYPE_LEVEL/etc.) of an IRQ
 * @irq_set_wake:	enable/disable power-management wake-on of an IRQ
 * @irq_bus_lock:	function to lock access to slow bus (i2c) chips
 * @irq_bus_sync_unlock:function to sync and unlock slow bus (i2c) chips
 * @irq_cpu_online:	configure an interrupt source for a secondary CPU
 * @irq_cpu_offline:	un-configure an interrupt source for a secondary CPU
 * @irq_suspend:	function called from core code on suspend once per
 *			chip, when one or more interrupts are installed
 * @irq_resume:		function called from core code on resume once per chip,
 *			when one ore more interrupts are installed
 * @irq_pm_shutdown:	function called from core code on shutdown once per chip
 * @irq_calc_mask:	Optional function to set irq_data.mask for special cases
 * @irq_print_chip:	optional to print special chip info in show_interrupts
 * @irq_request_resources:	optional to request resources before calling
 *				any other callback related to this irq
 * @irq_release_resources:	optional to release resources acquired with
 *				irq_request_resources
 * @irq_compose_msi_msg:	optional to compose message content for MSI
 * @irq_write_msi_msg:	optional to write message content for MSI
 * @irq_get_irqchip_state:	return the internal state of an interrupt
 * @irq_set_irqchip_state:	set the internal state of a interrupt
 * @irq_set_vcpu_affinity:	optional to target a vCPU in a virtual machine
 * @ipi_send_single:	send a single IPI to destination cpus
 * @ipi_send_mask:	send an IPI to destination cpus in cpumask
 * @flags:		chip specific flags
 */
struct irq_chip {
	struct device	*parent_device;
	const char	*name;
	unsigned int	(*irq_startup)(struct irq_data *data);
	void		(*irq_shutdown)(struct irq_data *data);
	void		(*irq_enable)(struct irq_data *data);
	void		(*irq_disable)(struct irq_data *data);
 
	void		(*irq_ack)(struct irq_data *data);
	void		(*irq_mask)(struct irq_data *data);
	void		(*irq_mask_ack)(struct irq_data *data);
	void		(*irq_unmask)(struct irq_data *data);
	void		(*irq_eoi)(struct irq_data *data);
 
	int		(*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool force);
	int		(*irq_retrigger)(struct irq_data *data);
	int		(*irq_set_type)(struct irq_data *data, unsigned int flow_type);
	int		(*irq_set_wake)(struct irq_data *data, unsigned int on);
 
	void		(*irq_bus_lock)(struct irq_data *data);
	void		(*irq_bus_sync_unlock)(struct irq_data *data);
 
	void		(*irq_cpu_online)(struct irq_data *data);
	void		(*irq_cpu_offline)(struct irq_data *data);
 
	void		(*irq_suspend)(struct irq_data *data);
	void		(*irq_resume)(struct irq_data *data);
	void		(*irq_pm_shutdown)(struct irq_data *data);
 
	void		(*irq_calc_mask)(struct irq_data *data);
 
	void		(*irq_print_chip)(struct irq_data *data, struct seq_file *p);
	int		(*irq_request_resources)(struct irq_data *data);
	void		(*irq_release_resources)(struct irq_data *data);
 
	void		(*irq_compose_msi_msg)(struct irq_data *data, struct msi_msg *msg);
	void		(*irq_write_msi_msg)(struct irq_data *data, struct msi_msg *msg);
 
	int		(*irq_get_irqchip_state)(struct irq_data *data, enum irqchip_irq_state which, bool *state);
	int		(*irq_set_irqchip_state)(struct irq_data *data, enum irqchip_irq_state which, bool state);
 
	int		(*irq_set_vcpu_affinity)(struct irq_data *data, void *vcpu_info);
 
	void		(*ipi_send_single)(struct irq_data *data, unsigned int cpu);
	void		(*ipi_send_mask)(struct irq_data *data, const struct cpumask *dest);
 
	unsigned long	flags;
};

irq_chip 是一串和芯片相關的函數指針,這裏定義的非常的全面,基本上和 IRQ 相關的可能出現的操作都全部定義進去了,具體根據不同的芯片,需要在不同的芯片的地方去初始化這個結構,然後這個結構會嵌入到通用的 IRQ 處理軟件中去使用,使得軟件處理邏輯和芯片邏輯完全的分開。

好,我們接下來繼續前進。

6.2 初始化 Chip 相關的 IRQ

衆所周知,啓動的時候,C 語言從 start_kernel 開始,在這裏面,調用了和 machine 相關的 IRQ 的初始化 init_IRQ():

asmlinkage __visible void __init start_kernel(void)
{
	char *command_line;
	char *after_dashes;
 
.....
 
    early_irq_init();
    init_IRQ();
 
.....
 
}

在 init_IRQ 中,調用了 machine_desc->init_irq():

void __init init_IRQ(void)
{
	int ret;
 
	if (IS_ENABLED(CONFIG_OF) && !machine_desc->init_irq)
		irqchip_init();
	else
		machine_desc->init_irq();
 
	if (IS_ENABLED(CONFIG_OF) && IS_ENABLED(CONFIG_CACHE_L2X0) &&
	    (machine_desc->l2c_aux_mask || machine_desc->l2c_aux_val)) {
		if (!outer_cache.write_sec)
			outer_cache.write_sec = machine_desc->l2c_write_sec;
		ret = l2x0_of_init(machine_desc->l2c_aux_val,
				   machine_desc->l2c_aux_mask);
		if (ret && ret != -ENODEV)
			pr_err("L2C: failed to init: %d\n", ret);
	}
 
	uniphier_cache_init();
}

machine_desc->init_irq() 完成對中斷控制器的初始化,爲每個 irq_desc 結構安裝合適的流控 handler,爲每個 irq_desc 結構安裝 irq_chip 指針,使他指向正確的中斷控制器所對應的 irq_chip 結構的實例,同時,如果該平臺中的中斷線有多路複用(多箇中斷公用一個 irq 中斷線)的情況,還應該初始化 irq_desc 中相應的字段和標誌,以便實現中斷控制器的級聯。

這裏初始化的時候回調用到具體的芯片相關的中斷初始化的地方。

例如:

int __init s5p_init_irq_eint(void)
{
	int irq;
 
	for (irq = IRQ_EINT(0); irq <= IRQ_EINT(15); irq++)
		irq_set_chip(irq, &s5p_irq_vic_eint);
 
	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);
	}
 
	irq_set_chained_handler(IRQ_EINT16_31, s5p_irq_demux_eint16_31);
	return 0;
}

而在這些裏面,都回去調用類似於:

void
irq_set_chip_and_handler_name(unsigned int irq, struct irq_chip *chip,
			      irq_flow_handler_t handle, const char *name);
 
irq_set_handler(unsigned int irq, irq_flow_handler_t handle)
{
	__irq_set_handler(irq, handle, 0, NULL);
}
 
static inline void
irq_set_chained_handler(unsigned int irq, irq_flow_handler_t handle)
{
	__irq_set_handler(irq, handle, 1, NULL);
}
 
void
irq_set_chained_handler_and_data(unsigned int irq, irq_flow_handler_t handle,
				 void *data);

這些函數定義在 include/linux/irq.h 文件。是對芯片初始化的時候可見的 APIs,用於指定中斷 “流控” 中的 :

irq_flow_handler_t handle

也就是中斷來的時候,最後那個函數調用。

中斷流控函數,分幾種,電平觸發的中斷,邊沿觸發的,等:

/*
 * Built-in IRQ handlers for various IRQ types,
 * callable via desc->handle_irq()
 */
extern void handle_level_irq(struct irq_desc *desc);
extern void handle_fasteoi_irq(struct irq_desc *desc);
extern void handle_edge_irq(struct irq_desc *desc);
extern void handle_edge_eoi_irq(struct irq_desc *desc);
extern void handle_simple_irq(struct irq_desc *desc);
extern void handle_untracked_irq(struct irq_desc *desc);
extern void handle_percpu_irq(struct irq_desc *desc);
extern void handle_percpu_devid_irq(struct irq_desc *desc);
extern void handle_bad_irq(struct irq_desc *desc);
extern void handle_nested_irq(unsigned int irq);

而在這些處理函數里,會去調用到 :handle_irq_event

比如:

/**
 *	handle_level_irq - Level type irq handler
 *	@desc:	the interrupt description structure for this irq
 *
 *	Level type interrupts are active as long as the hardware line has
 *	the active level. This may require to mask the interrupt and unmask
 *	it after the associated handler has acknowledged the device, so the
 *	interrupt line is back to inactive.
 */
void handle_level_irq(struct irq_desc *desc)
{
	raw_spin_lock(&desc->lock);
	mask_ack_irq(desc);
 
	if (!irq_may_run(desc))
		goto out_unlock;
 
	desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);
 
	/*
	 * If its disabled or no action available
	 * keep it masked and get out of here
	 */
	if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data))) {
		desc->istate |= IRQS_PENDING;
		goto out_unlock;
	}
 
	kstat_incr_irqs_this_cpu(desc);
	handle_irq_event(desc);
 
	cond_unmask_irq(desc);
 
out_unlock:
	raw_spin_unlock(&desc->lock);
}

而這個 handle_irq_event 則是調用了處理,handle_irq_event_percpu:

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);
 
	raw_spin_lock(&desc->lock);
	irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);
	return ret;
}

handle_irq_event_percpu->__handle_irq_event_percpu-> 【action->handler()】

這裏終於看到了調用 的地方了,就是咱們通過 request_irq 註冊進去的函數

  1. /proc/interrupts

這個 proc 下放置了對應中斷號的中斷次數和對應的 dev-name

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/i4UVeP3a7W2SiHesCxJYOQ