---
# System prepended metadata

title: Linux 中斷處理 (ISR)
tags: [Plantform Driver]

---

### 中斷（Interrupt）

中斷處理主要是指當硬體（或軟體）發生事件時，CPU 會「打斷」目前的程式流程，轉去處理更緊急事件的機制，會跳去執行一段「中斷服務程式（ISR）」的機制，在此筆記會去詳細介紹中斷處理機制是如何進行的。

#### 中斷的流程
```
硬體事件（例如：裝置完成傳輸）
        ↓
CPU 停下目前工作
        ↓
呼叫中斷服務常式（ISR / top half）
        ↓
（可選）安排延遲執行（bottom half，如 tasklet 或 workqueue）
        ↓
CPU 回到原本的工作
```
#### 中斷的功能與特性
| 功能     | 說明                  |
| ------ | ------------------- |
| 即時響應   | 快速反應外部事件（如資料傳送完成）   |
| 提高效率   | CPU 不用忙等（busy wait） |
| 硬體驅動溝通 | 硬體透過中斷告訴系統「我準備好了」   |
| 減少延遲   | 取代輪詢（polling）方式     |

#### 驅動中的中斷範例
```javascript=
irqreturn_t my_isr(int irq, void *dev_id)
{
    printk(KERN_INFO "Interrupt received!\n");
    tasklet_schedule(&my_tasklet);  // 延遲處理
    return IRQ_HANDLED;
}
```



### 延遲執行（Delayed Execution）

延遲執行的意思是：

「把某些不需要立即做的工作，安排在稍後（或背景）再執行。」

它的目的是：

* 減少中斷處理時間（讓 ISR 越快越好）
* 分離「即時事件」與「後續處理」
* 提升系統整體效能與回應速度

常見用途:

在驅動程式中，延遲一段時間後再檢查硬體狀態。

延遲重新嘗試某個動作（例如：重試 I/O）。
延遲某些背景工作（例如清除 buffer、更新狀態）。


#### 延遲執行的幾種常見機制

| 機制                                | 執行上下文           | 可睡眠 | 適用情境         |
| --------------------------------- | --------------- | --- | ------------ |
| **Tasklet**                       | 軟中斷（Softirq）    | ❌   | 中斷下半部，快速處理   |
| **Workqueue**                     | Process context | ✅   | 可以睡眠或呼叫阻塞函式  |
| **Timer**                         | 軟中斷（Softirq）    | ❌   | 定時任務、timeout |
| **Delayed work**                  | Process context | ✅   | 延遲工作，可睡眠     |
| **msleep() / schedule_timeout()** | Process context | ✅   | 主動延遲執行       |


#### 兩者的關係：中斷 + 延遲執行

* 這兩者常常「搭配使用」！
* 常見模式：中斷上半部 + 延遲執行下半部
    | 部分                    | 名稱                  | 功能            | 特性         |
    | --------------------- | ------------------- | ------------- | ---------- |
    | **上半部 (Top Half)**    | ISR（中斷服務常式）         | 立即響應硬體事件、讀取狀態 | 快速、不能睡眠    |
    | **下半部 (Bottom Half)** | tasklet / workqueue | 延遲處理大量或耗時工作   | 可以延遲、部分可睡眠 |

#### 驅動中的延遲執行範例

1. 中斷 + tasklet
    ```javascript=
    #include <linux/module.h>
    #include <linux/interrupt.h>

    static struct tasklet_struct my_tasklet;

    void my_tasklet_func(unsigned long data)
    {
        printk(KERN_INFO "Tasklet: process deferred work\n");
    }

    irqreturn_t my_isr(int irq, void *dev_id)
    {
        printk(KERN_INFO "ISR: interrupt occurred\n");
        tasklet_schedule(&my_tasklet); // 延遲執行
        return IRQ_HANDLED;
    }

    static int __init my_init(void)
    {
        tasklet_init(&my_tasklet, my_tasklet_func, 0);
        request_irq(10, my_isr, IRQF_SHARED, "my_irq", &my_tasklet);
        return 0;
    }

    static void __exit my_exit(void)
    {
        tasklet_kill(&my_tasklet);
        free_irq(10, &my_tasklet);
    }

    module_init(my_init);
    module_exit(my_exit);
    MODULE_LICENSE("GPL");
    ```

    🧠 運作流程：

    * 硬體觸發中斷 IRQ 10
    * my_isr() 執行（上半部），排程 tasklet
    * 核心稍後執行 my_tasklet_func()（下半部）

2. Busy Wait
```javascript=
for( n1=0; n1<10; n1++ )
{
    printk(KERN_ALERT "n1=%d \r\n", n1 );  // 1Sec

    j = jiffies + 1 * HZ;
    while (jiffzies < j) ;   // BusyWait
}
```
* jiffies 是一個不斷增長的全域變數，代表系統開機以來的「時脈 tick 數」。
* Busy Wait會讓CPU一直持續在等待，使用msleep()或者schedule_timeout()比較好

3. schedule()
```javascript=
for( n1=0; n1<10; n1++ )
{
    printk(KERN_ALERT "n1=%d \r\n", n1 );

    j = jiffies + 1 * HZ;
    while (jiffies < j)
        schedule();  // OS Ready/Running/Wait&Block
}
```
* schedule()執行會讓出 CPU 的使用權，讓其他可執行的 process 有機會被排程執行。

4. schedule_timeout

```javascript=
for( n1=0; n1<10; n1++ )
{
    printk(KERN_ALERT "n1=%d \r\n", n1 );

    set_current_state(TASK_INTERRUPTIBLE);
    schedule_timeout( 1*HZ );
}
```
* schedule_timeout() 會自動計算超時，時間到即喚醒，不需要其他事件。
* 1*HZ: 一秒後自動喚醒

4. 延遲工作隊列（Workqueue）
```javascript=
DECLARE_WAIT_QUEUE_HEAD(joey_wait_queue_head);
for( n1=0; n1<10; n1++ )
{
    printk(KERN_ALERT "n1=%d \r\n", n1 );
    wait_event_timeout( joey_wait_queue_head , 0, 1 * HZ );

}
```
* 使用queue進行Delay，用來管理「哪些 process 正在等待」
* 1 秒後自動被 scheduler 喚醒，程式繼續執行。

CPU 釋放：在這 1 秒睡眠期間，CPU 可以去執行其他 process，不會 busy wait。

4. mdelay（短暫延遲）

```javascript=
for( n1=0; n1<10; n1++ )
{
    printk(KERN_ALERT "n1=%d \r\n", n1 );
    mdelay( 1000 );
}// 1 秒後執行
```
* mdelay() 會完全占用 CPU，在延遲期間不會切換到其他 process，不能在長時間延遲使用。
* 適用於短暫延遲（通常在微秒到幾毫秒範圍）

---
### Tasklet

Tasklet 是 Linux Kernel 的 底半部 (Bottom Half) 機制之一，用來延後執行中斷處理的工作，減少中斷上下文的執行時間。

特點：

* 運行在 軟體中斷 (soft interrupt) 上下文。
* 可以被中斷觸發，但比硬體中斷 (Top Half) 執行慢。
    * 等硬體中斷結束後就會被呼叫
* 不能睡眠（sleep）。
* 適合做「簡單、快速」的延遲處理，例如：
    * 累計資料
    * 發送事件給使用者空間
    * 設定旗標、喚醒等待隊列


#### Tasklet 使用範例

假設我們要在中斷服務例程 (ISR) 中延後處理：
```javascript=
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/init.h>

MODULE_LICENSE("GPL");

static struct tasklet_struct my_tasklet;

void my_tasklet_func(unsigned long data)
{
    printk(KERN_ALERT "Tasklet running! data=%ld\n", data);
}

irqreturn_t my_ISR(int irq, void *dev_id)
{
    printk(KERN_ALERT "ISR triggered\n");
    tasklet_schedule(&my_tasklet); // 延後處理
    return IRQ_HANDLED;
}

static int __init my_module_init(void)
{
    printk(KERN_ALERT "Init module\n");
    tasklet_init(&my_tasklet, my_tasklet_func, 123);
    // 這裡通常會 request_irq()
    return 0;
}

static void __exit my_module_exit(void)
{
    tasklet_kill(&my_tasklet);
    printk(KERN_ALERT "Exit module\n");
}

module_init(my_module_init);
module_exit(my_module_exit);
```

執行順序：
1. 中斷發生 → my_ISR 立即執行。
2. my_ISR 呼叫 tasklet_schedule() → Tasklet 加入隊列。
3. 中斷完成 → 軟中斷上下文執行 Tasklet。
4. Tasklet 執行 my_tasklet_func()。

### 中斷處理

接下來會詳細介紹中斷處理如何進行的，中斷是一種 **異步事件通知機制**。
當外部裝置（例如鍵盤、網卡、UART、GPIO）發生事件時，會透過 IRQ 線
 (Interrupt ReQuest) 向 CPU 發送訊號。
CPU 收到後會：
* 暫停目前正在執行的工作；
* 保存現場 (context)；
* 轉去執行一段特定的程式：中斷服務例程 (ISR, Interrupt Service Routine)；
* 處理完畢後回到原本的程式繼續執行。

中斷處理的一個主要問題就是如何處理長時間的任務，中斷處理需要很快速地完成，且不能讓中斷阻塞太久。

Linux 為了解決這個問題把中斷處理分為兩個階段:
| 階段      | 名稱                  | 執行位置      | 特性       | 範例            |
| ------- | ------------------- | --------- | -------- | ------------- |
| **上半部** | ISR (Top Half)      | 中斷發生時立即執行 | 速度快、不能睡眠 | 讀取硬體暫存器、清中斷旗標 |
| **下半部** | Tasklet / Workqueue | 稍後再執行     | 可處理較長任務  | 資料處理、通知使用者空間  |


如果 ISR 處理花太多時間，系統即會發生：

* CPU 佔用太久；
* 其他中斷被延遲；
* 整體延遲上升。

👉 因此：

上半部只做必要的工作（如：清除硬體旗標）；

下半部才做耗時的動作（如：拷貝資料、通知應用程式）。
  * Tasklet / Workqueue為先前所介紹過的延遲執行功能

#### ISR (上半部) 的特性
```javascript
irqreturn_t my_isr(int irq, void *dev_id);
```
特性：

* 不可睡眠（不能呼叫可能阻塞的函式，例如 copy_to_user() 或 msleep()）
* 執行時間要短
* 通常會排程一個下半部

#### 註冊與釋放中斷
註冊中斷
```javascript
int request_irq(unsigned int irq, irq_handler_t handler,
                unsigned long flags, const char *name, void *dev);
```
| 參數        | 說明                              |
| --------- | ------------------------------- |
| `irq`     | 中斷號碼 (由硬體定義，如 GPIO IRQ、PCI IRQ) |
| `handler` | 安裝的處理 IRQ 函數指標                     |
| `flags`   | 例如 `IRQF_SHARED` (可共用 IRQ)      |
| `name`    | 出現在 `/proc/interrupts`          |
| `dev`     | 用來識別中斷來源的指標 (通常是裝置結構體)          |
* 當中斷發生時會將 (void*)dev 指標傳給 IRQ 處理函數，通常是一個結構。
* 報告的中斷顯示在 `/proc/interrupts`

釋放中斷
```javascript
void free_irq(unsigned int irq, void *dev);
```

常見中斷旗標 (flags)
| 旗標                     | 意義                     |
| ---------------------- | ---------------------- |
| `IRQF_SHARED`          | 可共用同一 IRQ (常見於 PCI 裝置) |
| `IRQF_TRIGGER_RISING`  | 上升沿觸發                  |
| `IRQF_TRIGGER_FALLING` | 下降沿觸發                  |
| `IRQF_TRIGGER_HIGH`    | 高電位觸發                  |
| `IRQF_TRIGGER_LOW`     | 低電位觸發                  |

* 若是 GPIO 中斷，需搭配 gpio_to_irq(pin) 轉換。

#### 實際 IRQ 流程圖
```
硬體中斷發生
     │
     ▼
  CPU 暫停現有程序
     │
     ▼
  進入 ISR (Top Half)
     │
     ├─ 清除硬體旗標
     ├─ 排程 tasklet/workqueue
     ▼
  結束 ISR → 回到原程式
     │
     ▼
 tasklet/workqueue 執行 (Bottom Half)

```

#### 以 GPIO 為例所建立的範例
```javascript =
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/slab.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Joey");
MODULE_DESCRIPTION("GPIO IRQ Example");

#define GPIO_NUM 17  // 假設使用 GPIO17 (依實際板子修改)

static int irq_num;
static struct tasklet_struct my_tasklet;

// Tasklet 底半部
static void my_tasklet_func(unsigned long data)
{
    printk(KERN_INFO "Tasklet: 處理 GPIO IRQ 底半部, message=%s\n", (char *)data);
}

// ISR 上半部
static irqreturn_t my_gpio_isr(int irq, void *dev_id)
{
    printk(KERN_INFO "ISR: GPIO 中斷觸發! IRQ=%d\n", irq);
    tasklet_schedule(&my_tasklet); // 排程底半部
    return IRQ_HANDLED;
}

static int __init my_gpio_irq_init(void)
{
    int ret;
    char *msg = kmalloc(32, GFP_KERNEL);
    strcpy(msg, "Hello from GPIO IRQ");

    // 1. 申請 GPIO
    ret = gpio_request(GPIO_NUM, "my_gpio_irq");
    if (ret) {
        printk(KERN_ERR "gpio_request 失敗\n");
        return ret;
    }

    // 2. 設為輸入模式
    gpio_direction_input(GPIO_NUM);

    // 3. 轉換成 IRQ 號碼
    irq_num = gpio_to_irq(GPIO_NUM);
    if (irq_num < 0) {
        printk(KERN_ERR "gpio_to_irq 失敗\n");
        gpio_free(GPIO_NUM);
        return irq_num;
    }

    // 4. 初始化 Tasklet
    tasklet_init(&my_tasklet, my_tasklet_func, (unsigned long)msg);

    // 5. 註冊中斷 (上升沿觸發)
    ret = request_irq(irq_num, my_gpio_isr, IRQF_TRIGGER_RISING, "gpio_irq_handler", NULL);
    if (ret) {
        printk(KERN_ERR "request_irq 失敗\n");
        gpio_free(GPIO_NUM);
        kfree(msg);
        return ret;
    }

    printk(KERN_INFO "GPIO IRQ 模組載入成功: GPIO=%d, IRQ=%d\n", GPIO_NUM, irq_num);
    return 0;
}

static void __exit my_gpio_irq_exit(void)
{
    free_irq(irq_num, NULL);
    tasklet_kill(&my_tasklet);
    gpio_free(GPIO_NUM);
    printk(KERN_INFO "GPIO IRQ 模組卸載完成\n");
}

module_init(my_gpio_irq_init);
module_exit(my_gpio_irq_exit);
```
