在嵌入式 Linux 開發中,根據樹莓派的驅動程式開發是入門和實踐的理想選擇。本文從建置嵌入式 Linux 系統開始,逐步引導讀者瞭解驅動程式的基本概念,並透過 helloworld 等簡單範例,帶領讀者實際操作。接著,文章探討了 Linux 裝置和驅動程式模型,包含 Bus、裝置、驅動程式以及 sysfs 檔案系統的互動關係。此外,文章也涵蓋了字元驅動程式、平台驅動程式和 I2C 客戶端驅動程式的開發方法,並以程式碼範例說明如何註冊和使用這些驅動程式。最後,文章講解了 Linux 核心中的中斷處理機制,包含 IRQ Domain、裝置樹中斷設定以及如何在驅動程式中請求和釋放中斷,並以按鈕中斷和睡眠裝置為例,示範如何在實際應用中處理中斷和使用等待佇列。
使用 Raspberry Pi 進行 Linux 驅動程式開發實務實驗室
簡介
在嵌入式系統領域中,Linux 已經成為不可或缺的作業系統,而樹莓派(Raspberry Pi)則是學習和開發 Linux 驅動程式的理想平台。本文將介紹使用樹莓派進行 Linux 驅動程式開發的基礎知識和實務操作。
建置嵌入式 Linux 系統
要進行 Linux 驅動程式開發,首先需要建置一個嵌入式 Linux 系統。這個系統主要由 Bootloader、Linux 核心、系統呼叫介面、C 執行期函式庫、系統共用函式庫和根檔案系統組成。
Bootloader
Bootloader 是系統啟動的第一步,負責初始化硬體和載入 Linux 核心。
Linux 核心
Linux 核心是作業系統的核心,負責管理硬體資源和提供系統呼叫介面。
系統呼叫介面和 C 執行期函式庫
系統呼叫介面提供了應用程式與核心之間的互動介面,而 C 執行期函式庫則提供了基本的程式設計介面。
系統共用函式庫
系統共用函式庫提供了一些常用的函式,如數學運算、字串處理等。
根檔案系統
根檔案系統包含了作業系統的基本檔案和目錄結構。
樹莓派上的 Linux 驅動程式開發
樹莓派是一個非常適合用於學習和開發 Linux 驅動程式的平台。下面將介紹如何在樹莓派上建置 Linux 驅動程式開發環境。
連線和設定硬體
首先,需要連線和設定樹莓派的硬體,包括連線電源、顯示器、鍵盤和滑鼠等。
設定乙太網路通訊
接下來,需要設定乙太網路通訊,以便能夠將檔案傳輸到樹莓派上。
複製檔案到樹莓派
可以使用 SCP 或其他檔案傳輸工具將檔案複製到樹莓派上。
建置 Linux 核心
建置 Linux 核心需要下載核心原始碼、設定核心選項和編譯核心等步驟。
Linux 裝置和驅動程式模型
Linux 裝置和驅動程式模型是 Linux 核心的重要組成部分。下面將介紹這個模型的基礎知識。
Bus 核心驅動程式
Bus 核心驅動程式負責管理 Bus 上的裝置。
Bus 控制驅動程式
Bus 控制驅動程式負責控制 Bus 的操作。
裝置驅動程式
裝置驅動程式負責管理特定的裝置。
裝置
裝置是指連線到系統上的硬體裝置。
sysfs 檔案系統
sysfs 檔案系統提供了對裝置和驅動程式的存取介面。
kobject 基礎設施
kobject 基礎設施提供了對物件的管理和參考計數等功能。
簡單的驅動程式範例
下面將介紹一些簡單的驅動程式範例,包括 “helloworld” 模組和 “helloworld with parameters” 模組。
LAB 3.1: “helloworld” 模組
// helloworld_rpi3.c
#include <linux/module.h>
#include <linux/init.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alberto Liberal");
MODULE_DESCRIPTION("A simple hello world module");
static int __init helloworld_init(void) {
printk(KERN_INFO "Hello World!\n");
return 0;
}
static void __exit helloworld_exit(void) {
printk(KERN_INFO "Goodbye World!\n");
}
module_init(helloworld_init);
module_exit(helloworld_exit);
# Makefile
obj-m += helloworld_rpi3.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
#### 內容解密:
helloworld_rpi3.c程式碼中,首先引入必要的頭檔,包括linux/module.h和linux/init.h。- 使用
MODULE_LICENSE、MODULE_AUTHOR和MODULE_DESCRIPTION巨集來定義模組的相關資訊。 - 定義
helloworld_init和helloworld_exit函式,分別在模組初始化和離開時被呼叫。 - 在
helloworld_init函式中,使用printk函式輸出 “Hello World!” 到核心日誌中。 - 在
helloworld_exit函式中,使用printk函式輸出 “Goodbye World!” 到核心日誌中。 - 使用
module_init和module_exit巨集來註冊helloworld_init和helloworld_exit函式。
LAB 3.2: “helloworld with parameters” 模組
// helloworld_rpi3_with_parameters.c
#include <linux/module.h>
#include <linux/init.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alberto Liberal");
MODULE_DESCRIPTION("A simple hello world module with parameters");
static char *name = "world";
module_param(name, charp, S_IRUGO);
MODULE_PARM_DESC(name, "The name to display");
static int __init helloworld_init(void) {
printk(KERN_INFO "Hello %s!\n", name);
return 0;
}
static void __exit helloworld_exit(void) {
printk(KERN_INFO "Goodbye %s!\n", name);
}
module_init(helloworld_init);
module_exit(helloworld_exit);
# Makefile
obj-m += helloworld_rpi3_with_parameters.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
#### 內容解密:
helloworld_rpi3_with_parameters.c程式碼中,首先引入必要的頭檔,包括linux/module.h和linux/init.h。- 使用
MODULE_LICENSE、MODULE_AUTHOR和MODULE_DESCRIPTION巨集來定義模組的相關資訊。 - 定義一個字串變數
name,並使用module_param巨集來定義一個模組引數。 - 在
helloworld_init函式中,使用printk函式輸出 “Hello %s!” 到核心日誌中,其中%s被替換為name的值。 - 在
helloworld_exit函式中,使用printk函式輸出 “Goodbye %s!” 到核心日誌中,其中%s被替換為name的值。 - 使用
module_init和module_exit巨集來註冊helloworld_init和helloworld_exit函式。
Linux 核心驅動程式開發
第 4 章:字元驅動程式
字元驅動程式是 Linux 核心中的一種驅動程式,用於控制字元裝置。字元裝置是指那些以字元為單位進行資料傳輸的裝置,例如終端機、序列埠等。
LAB 4.1: “helloworld character” 模組
這個實驗室將指導您如何建立一個簡單的字元驅動程式。我們將建立一個名為 “helloworld character” 的模組,該模組將註冊一個字元裝置並提供一些基本的功能。
註冊和登出字元裝置
要註冊一個字元裝置,我們需要使用 register_chrdev 函式。該函式需要三個引數:主裝置號、裝置名稱和檔案操作結構。
int major = register_chrdev(0, "helloworld_char", &helloworld_fops);
if (major < 0) {
printk(KERN_ERR "Failed to register character device\n");
return major;
}
內容解密:
register_chrdev函式用於註冊字元裝置。- 第一個引數是請求的主裝置號,如果設為 0,核心將自動分配一個主裝置號。
- 第二個引數是裝置名稱,這裡是 “helloworld_char”。
- 第三個引數是檔案操作結構,定義了對裝置的操作。
使用 devtmpfs 建立裝置檔案
devtmpfs 是一個虛擬檔案系統,用於自動建立裝置檔案。我們可以使用以下命令來掛載 devtmpfs:
mount -t devtmpfs devtmpfs /dev
內容解密:
mount命令用於掛載檔案系統。-t devtmpfs指定了要掛載的檔案系統型別。devtmpfs是要掛載的檔案系統名稱。/dev是掛載點。
第 5 章:平台驅動程式
平台驅動程式是 Linux 核心中的一種驅動程式,用於控制平台特定的硬體。平台驅動程式通常用於控制嵌入式系統中的硬體。
LAB 5.1: “platform device” 模組
這個實驗室將指導您如何建立一個簡單的平台驅動程式。我們將建立一個名為 “platform device” 的模組,該模組將註冊一個平台裝置並提供一些基本的功能。
平台驅動程式資源
平台驅動程式可以使用 platform_get_resource 函式來取得資源。該函式需要三個引數:平台裝置、資源型別和資源索引。
struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "Failed to get memory resource\n");
return -ENODEV;
}
內容解密:
platform_get_resource函式用於取得平台裝置的資源。- 第一個引數是平台裝置結構。
- 第二個引數是資源型別,這裡是
IORESOURCE_MEM,表示記憶體資源。 - 第三個引數是資源索引。
第 6 章:I2C 客戶端驅動程式
I2C 客戶端驅動程式是 Linux 核心中的一種驅動程式,用於控制 I2C 匯流排上的裝置。
LAB 6.1: “I2C I/O expander device” 模組
這個實驗室將指導您如何建立一個簡單的 I2C 客戶端驅動程式。我們將建立一個名為 “I2C I/O expander device” 的模組,該模組將註冊一個 I2C 客戶端並提供一些基本的功能。
I2C 客戶端驅動程式註冊
要註冊一個 I2C 客戶端驅動程式,我們需要使用 i2c_register_driver 函式。該函式需要一個引數:I2C 驅動程式結構。
int ret = i2c_register_driver(&io_expander_driver);
if (ret != 0) {
printk(KERN_ERR "Failed to register I2C driver\n");
return ret;
}
內容解密:
i2c_register_driver函式用於註冊 I2C 客戶端驅動程式。io_expander_driver是 I2C 驅動程式結構,定義了對 I2C 裝置的操作。
第7章:裝置驅動程式中的中斷處理
在Linux裝置驅動程式開發中,中斷處理是一個重要的議題。本章將探討Linux核心的中斷處理機制,並透過一系列實驗室練習(LAB)來演示如何在裝置驅動程式中處理中斷。
Linux核心IRQ網域(IRQ Domain)為GPIO控制器
Linux核心使用IRQ網域(IRQ Domain)來管理中斷請求(IRQ)。對於GPIO控制器來說,IRQ網域提供了一種將硬體中斷號對映到Linux核心中斷號的機制。這使得GPIO控制器可以產生中斷,並由Linux核心進行處理。
裝置樹(Device Tree)中斷處理
裝置樹是一種描述硬體裝置的資料結構。在裝置樹中,可以描述裝置的中斷請求(IRQ)。Linux核心可以透過裝置樹來取得裝置的中斷請求資訊,並進行相應的處理。
在Linux裝置驅動程式中請求中斷
要在Linux裝置驅動程式中請求中斷,需要使用核心提供的API函式。這些函式包括request_irq()、free_irq()等。驅動程式開發者需要註冊中斷處理函式,以便在中斷發生時進行處理。
LAB 7.1: “按鈕中斷裝置"模組
本實驗室練習演示瞭如何開發一個按鈕中斷裝置的驅動程式。該驅動程式使用GPIO控制器產生中斷,並在按鈕被按下時觸發中斷處理函式。
硬體描述
本實驗室練習使用Raspberry Pi 3開發板,並連線一個按鈕到GPIO引腳。
裝置樹描述
在裝置樹中,需要描述按鈕裝置的中斷請求(IRQ)。以下是一個示例:
button {
compatible = "rpi3-button";
interrupts = <&gpio 17 IRQ_TYPE_EDGE_FALLING>;
};
程式碼描述
以下是按鈕中斷裝置驅動程式的程式碼片段:
// Listing 7-1: int_rpi3_key.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#define DEVICE_NAME "rpi3-button"
static int major_number;
static struct class* button_class;
static irqreturn_t button_isr(int irq, void *dev_id) {
printk(KERN_INFO "Button pressed!\n");
return IRQ_HANDLED;
}
static int __init button_init(void) {
// 註冊中斷處理函式
if (request_irq(irq_number, button_isr, IRQF_SHARED, DEVICE_NAME, NULL)) {
printk(KERN_ERR "Failed to request IRQ!\n");
return -1;
}
return 0;
}
static void __exit button_exit(void) {
// 釋放中斷請求
free_irq(irq_number, NULL);
}
module_init(button_init);
module_exit(button_exit);
內容解密:
request_irq()函式用於註冊中斷處理函式,引數包括中斷號、處理函式、中斷旗標、裝置名稱等。button_isr()是中斷處理函式,當按鈕被按下時觸發。free_irq()函式用於釋放中斷請求。
示範
編譯並載入驅動程式模組後,可以透過按下按鈕來觸發中斷處理函式。可以使用dmesg命令檢視輸出資訊。
延遲工作(Deferred Work)
Linux核心提供了多種延遲工作的機制,包括軟中斷(Softirqs)、Tasklets、定時器(Timers)等。這些機制可以用於在非中斷上下文中執行任務。
鎖定(Locking)在核心中的重要性
在多核系統中,鎖定機制對於保護分享資源至關重要。Linux核心提供了多種鎖定機制,包括自旋鎖(Spinlocks)、互斥鎖(Mutexes)等。
LAB 7.2: “睡眠裝置"模組
本實驗室練習演示瞭如何開發一個睡眠裝置的驅動程式。該驅動程式使用等待佇列(Wait Queue)來實作睡眠功能。
裝置樹描述
在裝置樹中,需要描述睡眠裝置的中斷請求(IRQ)。以下是一個示例:
sleeping_device {
compatible = "rpi3-sleeping-device";
interrupts = <&gpio 17 IRQ_TYPE_EDGE_FALLING>;
};
程式碼描述
以下是睡眠裝置驅動程式的程式碼片段:
// Listing 7-2: int_rpi3_key_wait.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/wait.h>
#define DEVICE_NAME "rpi3-sleeping-device"
static wait_queue_head_t wait_queue;
static int __init sleeping_device_init(void) {
init_waitqueue_head(&wait_queue);
return 0;
}
static ssize_t sleeping_device_read(struct file *file, char __user *buffer, size_t length, loff_t *offset) {
wait_event_interruptible(wait_queue, condition);
return 0;
}
static irqreturn_t sleeping_device_isr(int irq, void *dev_id) {
wake_up_interruptible(&wait_queue);
return IRQ_HANDLED;
}
內容解密:
wait_event_interruptible()函式用於使執行緒進入睡眠狀態,直到條件滿足。wake_up_interruptible()函式用於喚醒睡眠中的執行緒。