返回文章列表

Linux 核心 GPIO 控制器 IRQ 網域處理

本文探討 Linux 核心如何處理 GPIO 控制器的 IRQ 網域,包含 IRQ 資料結構、IRQ 網域、建立和註冊 IRQ 網域、建立 IRQ 對映以及 CHAINED GPIO 中斷控制器等核心概念。同時,文章也探討了 GPIO 中斷處理在裝置驅動程式中的應用,包括階層式 GPIO

Linux 核心 嵌入式系統

Linux 核心利用 IRQ 網域機制管理 GPIO 中斷,將硬體 IRQ 號對映到 Linux IRQ 號,特別在多個中斷控制器的情況下更顯重要。irq_data 結構儲存中斷資訊,包含 Linux IRQ 號 (irq)、硬體 IRQ 號 (hwirq)、中斷控制器硬體存取 (chip) 和 IRQ 網域 (domain)。IRQ 網域由 irq_domain 結構表示,包含網域名稱 (name)、操作函式 (ops)、主機資料 (host_data) 和旗標 (flags)。驅動程式可使用 irq_domain_add_*() 函式建立和註冊 IRQ 網域,例如 irq_domain_add_linear() 建立線性對映網域。irq_create_mapping() 函式將硬體 IRQ 號對映到 Linux IRQ 號,通常在 gpio_chip.to_irq() 回呼函式中使用。CHAINED GPIO 中斷控制器常見於嵌入式系統,它們註冊到父 IRQ 處理器,並在父 IRQ 觸發時執行自身的中斷處理函式,使用 chained_irq_enter()chained_irq_exit() 函式管理鏈式中斷處理流程。

Linux 核心中 GPIO 控制器的 IRQ 網域處理

在 Linux 核心中,中斷處理是一項重要的功能,尤其是在處理 GPIO(通用輸入/輸出)控制器時。GPIO 控制器不僅可以作為輸入/輸出介面,還可以作為中斷控制器。在這種情況下,瞭解 Linux 核心如何處理 IRQ(中斷請求)網域對於開發 GPIO 控制器驅動程式至關重要。

IRQ 資料結構

IRQ 資料結構是 Linux 核心中斷處理的核心。struct irq_data 是其中一個關鍵的資料結構,它包含了與中斷相關的重要資訊。

struct irq_data {
    u32 mask;
    unsigned int irq; /* Linux IRQ 號 */
    unsigned long hwirq; /* 硬體 IRQ 號 */
    struct irq_common_data *common;
    struct irq_chip *chip; /* 低階中斷控制器硬體存取 */
    struct irq_domain *domain;
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
    struct irq_data *parent_data;
#endif
    void *chip_data;
};

內容解密:

  • irq:代表 Linux 核心內部使用的 IRQ 號。
  • hwirq:代表硬體 IRQ 號,即硬體中斷控制器的中斷號。
  • chip:指向 struct irq_chip,負責低階中斷控制器的硬體存取。
  • domain:指向 struct irq_domain,代表中斷控制器的網域。

IRQ 網域

IRQ 網域是 Linux 核心用於對映硬體 IRQ 號到 Linux 內部 IRQ 號的機制。當系統有多個中斷控制器時,這種對映機制尤為重要。

struct irq_domain {
    struct list_head link;
    const char *name;
    const struct irq_domain_ops *ops;
    void *host_data;
    unsigned int flags;
    /* 可選資料 */
    struct fwnode_handle *fwnode;
    enum irq_domain_bus_token bus_token;
    struct irq_domain_chip_generic *gc;
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
    struct irq_domain *parent;
#endif
    /* 反向對映資料 */
    irq_hw_number_t hwirq_max;
    unsigned int revmap_direct_max_irq;
    unsigned int revmap_size;
    struct radix_tree_root revmap_tree;
    unsigned int linear_revmap[];
};

內容解密:

  • name:IRQ 網域的名稱。
  • ops:指向 struct irq_domain_ops,定義了網域的操作函式。
  • host_data:儲存主機資料的指標,通常用於儲存與特定硬體相關的資料。
  • flags:網域的旗標,用於表示網域的屬性或狀態。

建立和註冊 IRQ 網域

中斷控制器驅動程式可以透過呼叫 irq_domain_add_*() 函式來分配和註冊一個 IRQ 網域。其中,irq_domain_add_linear() 是常用的函式,用於建立線性對映的 IRQ 網域。

struct irq_domain *irq_domain_add_linear(struct device_node *of_node,
                                         unsigned int size,
                                         const struct irq_domain_ops *ops,
                                         void *host_data)
{
    return __irq_domain_add(of_node_to_fwnode(of_node), size,
                            size, 0, ops, host_data);
}

內容解密:

  • of_node:指向裝置樹節點的指標,代表中斷控制器。
  • size:網域中的中斷數量,例如 GPIO 輸入的數量。
  • ops:網域的操作函式,用於對映/解除對映。
  • host_data:控制器私有資料的指標。

建立 IRQ 對映

當需要將硬體 IRQ 號對映到 Linux IRQ 號時,可以呼叫 irq_create_mapping() 函式。

unsigned int irq_create_mapping(struct irq_domain *domain,
                                irq_hw_number_t hwirq)
{
    [...]
}

內容解密:

  • domain:指向 IRQ 網域的指標。
  • hwirq:硬體 IRQ 號。

在 GPIO 控制器的驅動程式中,irq_create_mapping() 可以在 gpio_chip.to_irq() 回呼函式中被呼叫,以取得與 GPIO 引腳相關聯的 Linux IRQ 號。

CHAINED GPIO 中斷控制器

CHAINED GPIO 中斷控制器是一種嵌入式系統中常見的中斷控制器型別。它們通常會註冊到父 IRQ 處理器,並在父 IRQ 處理器被呼叫時立即執行自己的中斷處理函式。

static void gpio_irq_handler()
{
    chained_irq_enter(...);
    generic_handle_irq(...);
    chained_irq_exit(...);
}

內容解密:

  • chained_irq_enter()chained_irq_exit():用於進入和離開鏈式中斷處理的函式。
  • generic_handle_irq():用於處理一般的中斷。

綜上所述,Linux 核心透過 IRQ 網域機制有效地管理了 GPIO 控制器的中斷處理。這種機制使得開發人員能夠編寫出更具可移植性和可擴充套件性的驅動程式。

GPIO 中斷處理在裝置驅動程式中的應用

GPIO 中斷處理機制

在 Linux 核心中,GPIO(General Purpose Input/Output)中斷處理是裝置驅動程式中的一個重要環節。GPIO 中斷允許裝置在特定事件發生時通知處理器,從而實作高效的事件驅動處理。

1. 階層式 GPIO irqchips

階層式 GPIO irqchips 是一種常見的 GPIO 中斷處理架構。在這種架構中,GPIO 中斷控制器(irqchip)被階層式地組織起來,以實作對多個 GPIO 線的中斷處理。

struct gpio_irq_chip *girq;
girq = &pc->gpio_chip.irq;
girq->chip = &bcm2835_gpio_irq_chip;
girq->parent_handler = bcm2835_gpio_irq_handler;
girq->num_parents = BCM2835_NUM_IRQS;
girq->parents = devm_kcalloc(dev, BCM2835_NUM_IRQS, sizeof(*girq->parents), GFP_KERNEL);
if (!girq->parents)
    return -ENOMEM;

for (i = 0; i < BCM2835_NUM_IRQS; i++)
    girq->parents[i] = irq_of_parse_and_map(np, i);
girq->default_type = IRQ_TYPE_NONE;
girq->handler = handle_level_irq;

內容解密:

  • struct gpio_irq_chip *girq;:宣告一個指向 gpio_irq_chip 結構的指標,用於設定 GPIO 中斷控制器的相關屬性。
  • girq->chip = &bcm2835_gpio_irq_chip;:設定 GPIO 中斷控制器的晶片特定操作函式。
  • girq->parent_handler = bcm2835_gpio_irq_handler;:設定父級中斷處理函式,當中斷發生時會呼叫此函式。
  • girq->parents:分配記憶體以儲存父級中斷請求(IRQ)的相關資訊。
  • irq_of_parse_and_map(np, i):解析並對映裝置樹中的中斷請求。

中斷處理流程

當 GPIO 中斷發生時,bcm2835_gpio_irq_handler 函式會被呼叫,以處理對應的 GPIO 中斷事件。

static void bcm2835_gpio_irq_handler(struct irq_desc *desc)
{
    struct gpio_chip *chip = irq_desc_get_handler_data(desc);
    struct bcm2835_pinctrl *pc = gpiochip_get_data(chip);
    // ...
    chained_irq_enter(host_chip, desc);
    switch (group) {
    case 0: /* IRQ0 covers GPIOs 0-27 */
        bcm2835_gpio_irq_handle_bank(pc, 0, 0x0fffffff);
        break;
    // ...
    }
    chained_irq_exit(host_chip, desc);
}

內容解密:

  • struct gpio_chip *chip = irq_desc_get_handler_data(desc);:取得與中斷描述符相關聯的 GPIO 晶片資料。
  • chained_irq_enter(host_chip, desc);:進入階層式中斷處理流程。
  • bcm2835_gpio_irq_handle_bank(pc, 0, 0x0fffffff);:處理特定 bank 的 GPIO 中斷事件。

巢狀執行緒 GPIO irqchips

對於某些外接的 GPIO 擴充套件器,由於其需要透過慢速匯流排讀取中斷狀態,因此需要使用巢狀執行緒來處理中斷事件。

struct my_gpio {
    struct gpio_chip gc;
    struct irq_chip irq;
};
int irq; /* from platform etc */

內容解密:

  • struct my_gpio:定義一個包含 GPIO 晶片和中斷晶片的結構,用於管理 GPIO 和中斷相關的操作。
  • gpiochip_irqchip_add_nested():為 GPIO 晶片新增巢狀中斷晶片,使其能夠處理來自子裝置的中斷請求。

中斷處理在裝置驅動程式中的應用

在 Linux 驅動程式開發中,中斷處理是一項至關重要的技術。本章節將探討如何在裝置驅動程式中有效地處理中斷,包括動態設定中斷控制器、使用裝置樹(Device Tree)描述中斷連線,以及如何在 Linux 驅動程式中請求和處理中斷。

動態設定中斷控制器

在驅動程式中,動態設定中斷控制器是一個常見的需求。以下是一個範例程式碼,展示瞭如何在驅動程式中設定中斷控制器:

struct my_gpio *g;
struct gpio_irq_chip *girq;

/* 動態設定 irqchip */
g->irq.name = "my_gpio_irq";
g->irq.irq_ack = my_gpio_ack_irq;
g->irq.irq_mask = my_gpio_mask_irq;
g->irq.irq_unmask = my_gpio_unmask_irq;
g->irq.irq_set_type = my_gpio_set_irq_type;

ret = devm_request_threaded_irq(dev, irq, NULL, irq_thread_fn, IRQF_ONESHOT, "my-chip", g);
if (ret < 0)
    return ret;

/* 取得 gpio_irq_chip 的指標 */
girq = &g->gc.irq;
girq->chip = &g->irq;

/* 這將允許我們在驅動程式中處理父 IRQ */
girq->parent_handler = NULL;
girq->num_parents = 0;
girq->parents = NULL;
girq->default_type = IRQ_TYPE_NONE;
girq->handler = handle_bad_irq;

return devm_gpiochip_add_data(dev, &g->gc, g);

內容解密:

  1. g->irq.name = "my_gpio_irq";:設定中斷控制器的名稱。
  2. devm_request_threaded_irq():請求一個執行緒化的中斷,irq_thread_fn 是中斷執行緒函式。
  3. girq->chip = &g->irq;:將 g->irq 指派給 girq->chip,以便在驅動程式中使用。
  4. devm_gpiochip_add_data():註冊 GPIO chip。

裝置樹中的中斷處理

裝置樹是一種描述硬體結構的資料結構。在裝置樹中,中斷連線是透過四個屬性來描述的:interrupt-controller#interrupt-cellsinterrupt-parentinterrupts

以下是一個範例,展示瞭如何在裝置樹中描述中斷連線:

gpio: gpio@7e200000 {
    compatible = "brcm,bcm2835-gpio";
    reg = <0x7e200000 0xb4>;
    interrupts = <2 17>, <2 18>, <2 19>, <2 20>;
    gpio-controller;
    #gpio-cells = <2>;
    interrupt-controller;
    #interrupt-cells = <2>;
    [...]
};

Accel: ADXL345@0 {
    compatible = "arrow,adxl345";
    spi-max-frequency = <5000000>;
    spi-cpol;
    spi-cpha;
    reg = <0>;
    pinctrl-0 = <&accel_int_pin>;
    int-gpios = <&gpio 23 0>;
    interrupts = <23 1>;
    interrupt-parent = <&gpio>;
};

內容解密:

  1. interrupt-controller:宣告該節點為中斷控制器。
  2. #interrupt-cells:指定子裝置節點的 interrupts 屬性中的 cell 數量。
  3. interrupt-parent:指定該裝置的中斷控制器父節點。
  4. interrupts:列出該裝置的中斷訊號。

在 Linux 裝置驅動程式中請求中斷

在 Linux 驅動程式中,請求中斷是透過 request_irq()devm_request_irq() 函式來完成的。以下是一個範例:

devm_request_irq(struct device *dev, unsigned int irq, irq_handler_t handler,
                 unsigned long irqflags, const char *devname, void *dev_id)
{
    return devm_request_threaded_irq(dev, irq, handler, NULL, irqflags,
                                     devname, dev_id);
}

內容解密:

  1. devm_request_irq():請求一個中斷,並自動在裝置或模組釋放時釋放該中斷。
  2. handler:中斷處理函式,當中斷發生時被呼叫。
  3. irqflags:指定中斷的屬性,例如是否分享中斷線等。
  4. dev_id:通常是一個指向驅動程式私有資料的指標。

中斷處理函式

中斷處理函式是當中斷發生時被呼叫的函式。其原型如下:

irqreturn_t (*handler)(int irq_no, void *dev_id);

內容解密:

  1. irq_no:Linux IRQ 號碼。
  2. dev_id:請求中斷時傳遞的私有資料指標。
  3. 中斷處理函式必須傳回一個 irqreturn_t 型別的值,表示中斷處理的結果。

本章節介紹了在 Linux 裝置驅動程式中處理中斷的基本方法,包括動態設定中斷控制器、使用裝置樹描述中斷連線,以及請求和處理中斷。這些技術對於開發高效、穩定的 Linux 裝置驅動程式至關重要。