Linux 核心中的中斷處理是驅動程式開發的關鍵環節。本文以 sleeping device 模組為例,逐步講解如何在驅動程式中有效處理中斷。首先,我們定義了一個私有結構體 key_priv,用於儲存裝置資訊,包含 GPIO 描述符、中斷號和等待佇列頭等。接著,在 my_probe 函式中,初始化等待佇列,取得中斷號,並使用 devm_request_irq 註冊中斷處理函式 hello_keys_isr。在 hello_keys_isr 中,讀取 GPIO 狀態,將按鈕按下或釋放的狀態寫入環形緩衝區,並喚醒等待中的行程。my_dev_read 函式則讓使用者空間行程睡眠,直到緩衝區有資料可讀,再將資料複製到使用者空間。最後,透過裝置樹設定和平台驅動程式註冊,完成驅動程式與硬體的匹配。
深入解析裝置驅動程式中的中斷處理
在Linux裝置驅動程式開發中,中斷處理是一項至關重要的技術。本章節將詳細探討如何在裝置驅動程式中有效地處理中斷,並透過一個具體的範例——「sleeping device」模組,來展示相關的實作細節。
私有結構體的設計
首先,我們需要定義一個私有結構體 struct key_priv,用於儲存按鈕裝置的特定資訊。這個結構體包含以下幾個關鍵欄位:
struct device *dev:指向裝置結構的指標。struct gpio_desc *gpio:與按鈕相關聯的GPIO描述符指標。struct miscdevice int_miscdevice:用於處理字元裝置的雜項裝置結構。wait_queue_head_t wq_data_available:等待佇列頭,用於在中斷發生時喚醒等待中的行程。int irq:Linux中斷號。
struct key_priv {
struct device *dev;
struct gpio_desc *gpio;
struct miscdevice int_miscdevice;
wait_queue_head_t wq_data_available;
int irq;
};
程式碼解析:
struct key_priv結構體用於儲存裝置的私有資料,包括裝置結構指標、GPIO描述符、雜項裝置結構、等待佇列頭以及中斷號。- 這種設計使得驅動程式能夠有效地管理裝置的相關資源和狀態。
Probe函式的實作
在my_probe函式中,我們執行以下關鍵操作:
- 初始化等待佇列頭。
- 從裝置樹中取得中斷號。
- 使用
devm_request_irq函式分配中斷線,並註冊中斷處理函式hello_keys_isr。 - 初始化並註冊雜項裝置。
static int __init my_probe(struct platform_device *pdev)
{
struct key_priv *priv;
struct device *dev = &pdev->dev;
priv = devm_kzalloc(dev, sizeof(struct key_priv), GFP_KERNEL);
priv->dev = dev;
platform_set_drvdata(pdev, priv);
init_waitqueue_head(&priv->wq_data_available);
priv->gpio = devm_gpiod_get(dev, NULL, GPIOD_IN);
priv->irq = gpiod_to_irq(priv->gpio);
priv->irq = platform_get_irq(pdev, 0);
devm_request_irq(dev, priv->irq, hello_keys_isr,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
HELLO_KEYS_NAME, priv);
priv->int_miscdevice.name = "mydev";
priv->int_miscdevice.minor = MISC_DYNAMIC_MINOR;
priv->int_miscdevice.fops = &my_dev_fops;
misc_register(&priv->int_miscdevice);
return 0;
}
程式碼解析:
- 在
my_probe函式中,我們首先分配並初始化struct key_priv結構體。 - 使用
devm_gpiod_get和gpiod_to_irq或platform_get_irq取得中斷號。 - 呼叫
devm_request_irq註冊中斷處理函式hello_keys_isr,並設定觸發條件為上升沿和下降沿。 - 初始化並註冊雜項裝置,使其可供使用者空間存取。
中斷處理函式的實作
中斷處理函式hello_keys_isr的主要任務是:
- 讀取GPIO輸入值,判斷按鈕是被按下還是釋放。
- 將對應的字元(‘P’或’R’)存入緩衝區。
- 喚醒等待中的行程。
static irqreturn_t hello_keys_isr(int irq, void *data)
{
int val;
struct key_priv *priv = data;
dev_info(priv->dev, "interrupt received. key: %s\n", HELLO_KEYS_NAME);
val = gpiod_get_value(priv->gpio);
dev_info(priv->dev, "Button state: 0x%08X\n", val);
if (val == 1)
hello_keys_buf[buf_wr++] = 'P';
else
hello_keys_buf[buf_wr++] = 'R';
if (buf_wr >= MAX_KEY_STATES)
buf_wr = 0;
wake_up_interruptible(&priv->wq_data_available);
return IRQ_HANDLED;
}
程式碼解析:
- 在中斷處理函式中,首先讀取GPIO的值以判斷按鈕狀態。
- 根據按鈕狀態,將對應的字元存入環形緩衝區
hello_keys_buf。 - 使用
wake_up_interruptible喚醒等待中的行程,使其可以處理新的資料。
使用者空間讀取操作的實作
在my_dev_read函式中,我們實作了使用者空間讀取操作的處理邏輯:
- 將行程置於睡眠狀態,直到緩衝區中有可用資料。
- 將資料從核心空間複製到使用者空間。
static int my_dev_read(struct file *file, char __user *buff,
size_t count, loff_t *off)
{
int ret_val;
char ch[2];
struct key_priv *priv = container_of(file->private_data,
struct key_priv, int_miscdevice);
wait_event_interruptible(priv->wq_data_available, buf_wr != buf_rd);
ch[0] = hello_keys_buf[buf_rd];
ch[1] = '\n';
copy_to_user(buff, &ch, 2);
buf_rd++;
if (buf_rd >= MAX_KEY_STATES)
buf_rd = 0;
*off += 1;
return 2;
}
程式碼解析:
- 使用
wait_event_interruptible將行程置於睡眠狀態,直到緩衝區中有新資料可用。 - 將資料從核心空間緩衝區複製到使用者空間,並更新讀取索引。
- 傳回讀取的位元組數。
裝置樹與驅動程式的匹配
為了使驅動程式能夠正確匹配裝置,我們需要在裝置樹中定義相容的裝置,並在驅動程式中註冊對應的裝置ID表:
static const struct of_device_id my_of_ids[] = {
{ .compatible = "arrow,intkeywait"},
{},
};
MODULE_DEVICE_TABLE(of, my_of_ids);
程式碼解析:
- 定義了裝置樹中相容的裝置ID,使驅動程式能夠正確匹配對應的硬體裝置。
平台驅動程式的註冊
最後,我們需要註冊平台驅動程式,使其能夠在系統啟動時被正確載入:
static struct platform_driver my_platform_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "intkeywait",
.of_match_table = my_of_ids,
.owner = THIS_MODULE,
}
};
module_platform_driver(my_platform_driver);
程式碼解析:
- 定義並註冊了平台驅動程式結構,指定了
probe和remove函式,以及裝置樹匹配表。
裝置驅動程式中的中斷處理
在Linux核心中,中斷處理是一項重要的功能,尤其是在裝置驅動程式的開發中。本章節將探討如何在裝置驅動程式中處理中斷,並提供具體的例項來說明相關技術。
中斷處理的基本概念
中斷是硬體裝置用來通知CPU發生特定事件的訊號。當中斷發生時,CPU會暫停目前的任務,並執行中斷處理程式(ISR,Interrupt Service Routine)。在裝置驅動程式中,正確地處理中斷是確保系統穩定性和效能的關鍵。
實作範例:按鍵中斷處理
以下是一個具體的範例,展示如何在Linux裝置驅動程式中處理按鍵中斷。這個範例使用了一個平台驅動程式,當按鍵被按下或釋放時,會觸發中斷。
程式碼解析
首先,我們來看看中斷處理程式hello_keys_isr的實作:
static irqreturn_t hello_keys_isr(int irq, void *data)
{
struct key_priv *priv = data;
dev_info(priv->dev, "interrupt received. key: %s\n", HELLO_KEYS_NAME);
val = gpiod_get_value(priv->gpio);
dev_info(priv->dev, "Button state: 0x%08X\n", val);
if (val == 1)
hello_keys_buf[buf_wr++] = 'P';
else
hello_keys_buf[buf_wr++] = 'R';
if (buf_wr >= MAX_KEY_STATES)
buf_wr = 0;
/* 喚醒行程 */
wake_up_interruptible(&priv->wq_data_available);
return IRQ_HANDLED;
}
內容解密:
struct key_priv *priv = data;:取得與中斷相關的私有資料結構。dev_info(priv->dev, "interrupt received. key: %s\n", HELLO_KEYS_NAME);:輸出中斷接收的資訊。val = gpiod_get_value(priv->gpio);:讀取按鍵目前的狀態。- 根據按鍵狀態,將 ‘P’ 或 ‘R’ 寫入緩衝區
hello_keys_buf,分別代表按下和釋放。 wake_up_interruptible(&priv->wq_data_available);:喚醒等待在wq_data_available佇列上的行程。
接著,我們來看讀取裝置檔案的函式my_dev_read:
static int my_dev_read(struct file *file, char __user *buff, size_t count, loff_t *off)
{
int ret_val;
char ch[2];
struct key_priv *priv;
priv = container_of(file->private_data, struct key_priv, int_miscdevice);
dev_info(priv->dev, "mydev_read_file entered\n");
ret_val = wait_event_interruptible(priv->wq_data_available, buf_wr != buf_rd);
if(ret_val)
return ret_val;
/* 將資料傳送到使用者應用程式 */
ch[0] = hello_keys_buf[buf_rd];
ch[1] = '\n';
if(copy_to_user(buff, &ch, 2)) {
return -EFAULT;
}
buf_rd++;
if(buf_rd >= MAX_KEY_STATES)
buf_rd = 0;
*off+=1;
return 2;
}
內容解密:
ret_val = wait_event_interruptible(priv->wq_data_available, buf_wr != buf_rd);:讓行程等待,直到緩衝區中有新的資料。- 將緩衝區中的資料複製到使用者空間,並更新讀取索引
buf_rd。 - 傳回傳送的位元組數。
平台驅動程式註冊與初始化
在驅動程式初始化時,我們需要註冊平台驅動程式並設定相關的回呼函式:
static struct platform_driver my_platform_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "intkeywait",
.of_match_table = my_of_ids,
.owner = THIS_MODULE,
}
};
module_platform_driver(my_platform_driver);
內容解密:
.probe = my_probe:當裝置匹配時,呼叫my_probe函式進行初始化。.remove = my_remove:當裝置移除時,呼叫my_remove函式進行清理。.of_match_table = my_of_ids:指定相容的裝置樹節點。
實驗結果
載入模組後,可以觀察到按鍵事件被正確捕捉並記錄:
root@raspberrypi:/home/pi# insmod int_rpi3_key_wait.ko
intkeywait soc:int_key_wait: my_probe() function is called.
intkeywait soc:int_key_wait: The IRQ number is: 166
intkeywait soc:int_key_wait: IRQ_using_platform_get_irq: 166
intkeywait soc:int_key_wait: my_probe() function is exited.
root@raspberrypi:/home/pi# cat /dev/mydev > states
intkeywait soc:int_key_wait: mydev_read_file entered
intkeywait soc:int_key_wait: interrupt received. key: PB_USER
intkeywait soc:int_key_wait: Button state: 0x00000001
...
檢查記錄的按鍵狀態:
root@raspberrypi:/home/pi# cat states
P
R
P
R
P
R
這個範例展示瞭如何在Linux裝置驅動程式中處理按鍵中斷,並將事件記錄傳送到使用者空間。透過這個例子,我們可以看到中斷處理和緩衝區管理的實作細節。