### Linux 驅動程式介紹
在此筆記中會先粗略地介紹Linux驅動程式的相關基礎觀念,主要是我在進行 Linux Driver相關開發時的學習紀錄。
驅動程式(Driver)是 Kernel(核心空間)與 User Space(使用者空間)之間的橋樑。
* Linux 系統可以想像成三個層級:
```
┌─────────────────────────────┐
│ User Space │ ← 應用程式區域
│ (App, Shell, Libraries) │
└────────────┬────────────────┘
│ system call (read, write, ioctl...)
┌────────────┴────────────────┐
│ Kernel Space │ ← 核心空間
│ (Drivers, Scheduler, MMU) │
└────────────┬────────────────┘
│
┌────────────┴────────────────┐
│ Hardware │ ← 實體硬體
│ (CPU, GPIO, UART, etc.) │
└─────────────────────────────┘
```
| 層級 | 功能 | 舉例 |
| -------- | -------- | -------- |
| User Space | 執行應用程式。透過系統呼叫 (system call) 存取資源 | cat /dev/led、echo 1 > /dev/gpio17 |
| Kernel Space | 控制硬體的驅動程式 (Driver) | GPIO driver、UART driver、USB driver |
| Hardware | 實際裝置、暫存器、匯流排 (bus) | 晶片上的 peripheral、GPIO pin |
* 驅動程式負責做為底層以及上層的溝通橋樑:
* 將上層的抽象操作(例如 read/write)轉換成實際硬體操作(例如寄存器寫入)
* 將硬體事件(如中斷、資料輸入)回報給 Kernel 或 User Space
* 透過驅動與硬體互動的流程範例:
* 在這個過程中,應用程式只是「寫一個檔案」
* 驅動程式把這個動作轉成「寫入暫存器」
* Kernel 幫你隔離硬體細節
```
User Program:
echo 1 > /dev/myled ← write()
│
▼
Kernel:
myled_write() ← 驅動程式的 file_operations.write()
iowrite32(1, gpio_addr) ← 寫入硬體暫存器
│
▼
Hardware:
LED 亮起
```
---
### Linux中驅動程式的種類
| 類型 | 說明 | 例子 |
| -------- | -------- | -------- |
| Character Driver | 以 byte 為單位存取 | ART、GPIO、I2C|
| Block Driver | 以區塊 (block) 存取 | SD card、eMMC |
| Network Driver | 提供封包傳輸介面 | Ethernet、Wi-Fi |
| Platform Driver | 用於嵌入式 SoC 裝置 | GPIO controller、SPI device |
---
### Linux Kernel Module
Linux模組(Kernal Module)是一種可以動態載入與卸載的核心程式碼,用以開發Driver,讓開發者無須重新編譯整個核心就能擴充功能。
它通常是:
* 一個 .ko 檔(kernel object)
* 包含驅動程式、檔案系統、或其他核心功能
* 可用 insmod / rmmod 命令載入與移除
目的:
* 讓驅動程式能在「系統運行中」被載入,而不必重新開機或重編 kernel。
#### 基本 Module 架構:
* 一個最簡單的 Linux kernel module 會長這樣:
```javascript=
#include <linux/module.h>
#include <linux/init.h>
MODULE_LICENSE("Dual BSD/GPL");
static int my_init_module(void)
{
printk(KERN_ALERT "Hello, world\n");
return 0;
}
static void my_cleanup_module(void)
{
printk(KERN_ALERT "GoodBye, Happy world\n");
}
// insmod
module_init(my_init_module);
// rmmod
module_exit(my_cleanup_module);
```

#### 編譯方式
* 建立一個 Makefile:
指定目標架構為是 Orange Pi(ARM64 架構)進行開發,並呼叫 kernel 的 build system 來編譯這個目錄底下的模組產生 Driver (.ko)。。
```
.PHONY:clean
ifeq ($(KERNELRELEASE),)
# Assume the source tree is where the running kernel was built
# You should set KERNELDIR in the environment if it's elsewhere
#/home/cyh/orangepi-build/kernel/orange-pi-5.4-sun50iw9
KERNELDIR ?= /home/cyh/orangepi-build/kernel/orange-pi-5.4-sun50iw9
# The current directory is passed to sub-makes as argument
PWD := $(shell pwd)
modules:
$(MAKE) ARCH=arm64 CROSS_COMPILE=aarch64-none-linux-gnu- -C $(KERNELDIR) M=$(PWD) modules
else
obj-m := MyModule.o
endif
clean:
rm -f *.o
rm -f *.ko *.cmd
rm -f *.mod.*
rm -f *.mod
rm -f [mM]odule*
```
* 編譯指令:
`make`
會產生:`MyModule.ko`
#### 載入與移除模組
```
sudo insmod mymodule.ko # 載入模組
sudo rmmod mymodule # 移除模組
dmesg | tail # 查看核心訊息
```
輸出示例:
```
[ 123.456] MyModule: init
[ 130.789] MyModule: exit
```
#### 常用巨集與函式
| 名稱 | 功能 |
| -------- | -------- |
| module_init() | 指定模組載入時要呼叫的函式 |
| module_init() | 指定模組載入時要呼叫的函式 |
| module_exit() | 指定模組卸載時要呼叫的函式 |
| MODULE_LICENSE() | 宣告授權(GPL 非常重要) |
| MODULE_AUTHOR() | 作者資訊 |
| MODULE_DESCRIPTION() | 模組描述 |
| printk() | 核心空間的輸出函式(類似 printf)|
#### 模組載入的生命週期
```
insmod mydriver.ko
↓
module_init() → 初始化驅動(註冊裝置、申請資源)
↓
使用中(open/read/write)
↓
rmmod mydriver
↓
module_exit() → 釋放資源、登出裝置
```