天天看點

Linux 核心中斷與驅動中應用

中斷分類

中斷可分為同步(synchronous)中斷和異步(asynchronous)中斷:

  1. 同步中斷是當指令執行時由 CPU 控制單元産生,之是以稱為同步,是因為隻有在一條指令執行完畢後 CPU 才會發出中斷,而不是發生在代碼指令執行期間,比如系統調用。
  2. 異步中斷是指由其他硬體裝置依照 CPU 時鐘信号随機産生,即意味着中斷能夠在指令之間發生,例如鍵盤中斷。

    根據 Intel 官方資料,同步中斷稱為異常(exception),異步中斷被稱為中斷(interrupt)。

    中斷可分為可屏蔽中斷(Maskable interrupt)和非屏蔽中斷(Nomaskable interrupt)。

    異常可分為故障(fault)、陷阱(trap)、終止(abort)三類。

    1). 核心中斷

    Linux-3.14.x 核心将所有的中斷統一編号,使用一個irq_desc[NR_IRQS]的結構體數組來描述這些中斷:每個數組項對應着一個中斷源,記錄了中斷的入口處理函數(不是使用者注冊的處理函數)、中斷标記,

    并提供了中斷的底層硬體通路函數(中斷清除、屏蔽、使能)。另外,通過這個結構體數組項成員action,能夠找到使用者注冊的中斷處理函數。

    1.1) 中斷源描述符結構體

struct irq_desc {
	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;
#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_PROC_FS
	struct proc_dir_entry	*dir;
#endif
	int			parent_irq;
	struct module		*owner;
	const char		*name;
} ____cacheline_internodealigned_in_smp;
           

1.2) 使用者中斷處理函數鍊

/**
 * 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
 * @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;			/** 使用者 request_irq 中斷申請建立新節點 */
	irq_handler_t		thread_fn;
	struct task_struct	*thread;
	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_desc[NR_IRQS]這個資料結構;

使用者注冊中斷就是構造action連結清單;使用者解除安裝中斷就是從action連結清單中去除對應的項。

2). 使用者中斷程式應用說明(此處使用者相對于核心說,即驅動程式)。

2.1)Linux中斷進行中的頂半部和底半部機制

由于中斷服務程式的執行并不存在于程序上下文,是以,要求中斷服務程式的時間盡可能的短。

為了在中斷執行事件盡可能短和中斷處理需完成大量耗時工作之間找到一個平衡點,Linux将中斷處理分為兩個部分:頂半部(top half)和底半部(bottom half)。

頂半部完成盡可能少的比較緊急的功能,它往往隻是簡單地讀取寄存器中的中斷狀态并清除中斷标志後進行“登記中斷”的工作。

“登記”意味着将底半部的處理程式挂載到該裝置的底半部指向隊列中去。底半部作為工作重心,完成中斷事件的絕大多數任務。

2.2)頂半部和底半部劃分原則

(1) 如果一個任務對時間非常敏感,将其放在頂半部中執行;

(2) 如果一個任務和硬體有關,将其放在頂半部中執行;

(3) 如果一個任務要保證不被其他中斷打斷,将其放在頂半部中執行;

(4) 如果中斷要處理的工作本身很少,所有的工作可在頂半部全部完成;

(5) 其他所有任務,考慮放置在底半部執行。

2.3) 底半部中斷處理兩種政策,應用特征:

A). tasklet,最主要的是考慮支援SMP,提高SMP多個cpu的使用率;不同的tasklet可以在不同的cpu上運作。 但是tasklet屬于中斷上下文,是以不能被阻塞,不能睡眠,不可被打斷。

B). workqueue的突出特點是下半部會交給worker thead,是以下半部處于程序上下文,可以被重新排程,可以阻塞,也可以睡眠。

2.4) 應用方法

A).tasklet,可參考核心源碼: /drivers/media/platform/vino.c 檔案

/* 定義tasklet和底半部處理函數并關聯 */
void xxx_tasklet(unsigned long data);
//定義一個名為 xxx_tasklet 的 struct tasklet 并将其與 xxx_tasklet_func 綁定,data為傳入 xxx_tasklet_func的參數
DECLARE_TASKLET(xxx_tasklet, xxx_tasklet_func, data);

/** 底半部中斷處理回調函數 */
void xxx_tasklet_func()
{
    /* 中斷函數處理具體操作 */
}
/* 中斷處理頂半部 */
irqreturn xxx_interrupt(int irq, void *dev_id)
{
    //do something
    tasklet_schedule(&xxx_tasklet);//調用tasklet_schedule()函數,注冊到 schedule 鍊中.運作在軟中斷上下文.
    //do something
   return IRQ_HANDLED;
}

/* 裝置驅動子產品 init */
int __init xxx_init(void)
{
    ...
    /* 申請裝置中斷 */
    result = request_irq(xxx_irq, xxx_interrupt, IRQF_DISABLED, "xxx", NULL);
    ...
    return 0;
}
/* 裝置驅動子產品exit */
void __exit xxx_exit(void)
{
    ...
    /* 釋放中斷 */
    free_irq(xxx_irq, NULL);
}

module_init(xxx_init); /** 驅動程式挂載 */
module_exit(xxx_exit);
           

B).workqueue,可參考核心源碼:drivers/rtc/rtc-rx8010.c

/* 定義工作隊列和底半部函數 */
struct work_struct xxx_wq;
void xxx_do_work(unsigned long);

/* 中斷處理底半部 */
void xxx_work(unsigned long)
{
  /* do something */
}
/* 中斷處理頂半部 */
irqreturn_t xxx_interrupt(int irq, void *dev_id)
{
  ...
  schedule_work(&xxx_wq);   			/**@note 注冊到 workqueue 鍊中.運作線上程上下文 */
  ...
  return IRQ_HANDLED;
}
/* 裝置驅動子產品 init */
int __init xxx_init(void)
{
    ...
    /* 申請裝置中斷 */
    result = request_irq(xxx_irq, xxx_interrupt, IRQF_DISABLED, "xxx", NULL);
  /* 初始化工作隊列 */
  INIT_WORK(&xxx_wq, xxx_do_work);		/** 關聯工作隊列和底半部函數 */
    ...
    return 0;
}
/* 裝置驅動子產品exit,釋放 中斷 */
void __exit xxx_exit(void)
{
    ...
    /* 釋放中斷 */
    free_irq(xxx_irq, NULL);
}
           

執行個體摘錄:

(1). rx8010 裝置結構說明

MODULE_DEVICE_TABLE(i2c, rx8010_id);
struct rx8010_data {
	struct i2c_client *client;
	struct rtc_device *rtc;
	struct work_struct work;			/** @note workqueue 隊列 */
	u8 ctrlreg;
	unsigned exiting:1;
};

           

(2). 中斷處理頂半部

//----------------------------------------------------------------------
// rx8010_irq()
// irq handler
//  中斷處理頂半部 
//----------------------------------------------------------------------
static irqreturn_t rx8010_irq(int irq, void *dev_id)
{
	struct i2c_client *client = dev_id;
	struct rx8010_data *rx8010 = i2c_get_clientdata(client);
	disable_irq_nosync(irq);	
	/**
	 *  schedule_work()函數使系統在适當的時候排程運作底半部
	 *
	 */
	schedule_work(&rx8010->work); /** register struct work into queue */
	return IRQ_HANDLED;
}
           

(3). 裝置probe 函數

//----------------------------------------------------------------------
// rx8010_probe()
// probe routine for the rx8010 driver
//
// Todo: - maybe change kzalloc to use devm_kzalloc
//       - 
//----------------------------------------------------------------------

static int rx8010_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
	struct rx8010_data *rx8010;
	int err, need_reset = 0;
	u8 ram[1];
	u8 ramval[1];
	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_I2C_BLOCK)) {
	dev_err(&adapter->dev, "doesn't support required functionality\n");
		err = -EIO;
		goto errout;
	}

	rx8010 = kzalloc(sizeof(*rx8010), GFP_KERNEL);
	if (!rx8010) {
		dev_err(&adapter->dev, "failed to alloc memory\n");
		err = -ENOMEM;
		goto errout;
	}

	rx8010->client = client;
	i2c_set_clientdata(client, rx8010);
	
	/** 
		注冊底部中斷 workqueue 處理政策 
	**/
	INIT_WORK(&rx8010->work, rx8010_work);	/** @brief rx8010_work */
	
	err = rx8010_read_regs(rx8010->client, 0x20, 1, ram);
	if(err)
		goto errout_free;

	err = rx8010_init_client(client, &need_reset);
	if (err)
		goto errout_free;
    
	.....
	
	if (client->irq > 0) {
		dev_info(&client->dev, "IRQ %d supplied\n", client->irq);
		
		/** 
			request_irq 申請中斷 
		**/
		err = devm_request_threaded_irq(&client->dev,client->irq, NULL, rx8010_irq, IRQF_TRIGGER_LOW | IRQF_ONESHOT,"rx8010", client);
		
		if (err) {
			dev_err(&client->dev, "unable to request IRQ\n");
			goto errout_reg;
		}
	}		
	rx8010->rtc->irq_freq = 1;
	rx8010->rtc->max_user_freq = 1;
		
	return 0;

errout_reg:
	rtc_device_unregister(rx8010->rtc);

errout_free:
	kfree(rx8010);

errout:
	dev_err(&adapter->dev, "probing for rx8010 failed\n");
	return err;
}

           

(4). 中斷處理函數

static void rx8010_work(struct work_struct *work)
{
	struct rx8010_data *rx8010 = container_of(work, struct rx8010_data, work);
	struct i2c_client *client = rx8010->client;
	struct mutex *lock = &rx8010->rtc->ops_lock;
	u8 status;
	mutex_lock(lock);
	
	/** 
	 *  .... 
	*/

	mutex_unlock(lock);
}

           

(5). 驅動程式挂載

static struct i2c_driver rx8010_driver = {
	.driver = {
		.name = "rtc-rx8010",
		.owner = THIS_MODULE,
	},
	.probe		= rx8010_probe,
	.remove		= rx8010_remove,
	.id_table	= rx8010_id,
};
module_i2c_driver(rx8010_driver);
MODULE_AUTHOR("Dennis Henderson <[email protected]>");
MODULE_DESCRIPTION("RX8010 SJ RTC driver");
MODULE_LICENSE("GPL");
           

繼續閱讀