本涵蓋樹莓派系統的完整建置流程,從硬體連線、網路設定到 Linux 核心編譯與模組佈署,以及 Linux 裝置與驅動程式模型的基礎概念。文章將逐步說明如何設定硬體、組態網路、編譯核心、安裝模組,以及如何透過 UART 和 SSH 進行遠端連線與更新。此外,也探討了 Linux 裝置與驅動程式模型的關鍵資料結構,例如 bus_typedevice_driverdevice,以及它們在核心中的作用和相互關係,並提供程式碼範例說明如何在核心程式碼中定義和使用這些結構。

樹莓派系統建置與設定

硬體連線與初始設定

在開始使用樹莓派之前,必須按照正確的順序連線硬體,以確保所有元件的安全。以下是具體步驟:

  1. 將已安裝樹莓派作業系統的Micro SD卡插入樹莓派主機板底部的Micro SD卡插槽中。
  2. 使用HDMI線將螢幕連線到樹莓派的HDMI埠,同時可將滑鼠和鍵盤連線到USB埠。
  3. 使用網路線將樹莓派的乙太網路埠連線到主機PC的乙太網路插座。

序列主機台的連線與使用

序列主機台是除錯和檢視系統日誌的重要工具。需將USB to TTL序列線連線到裝置的UART針腳。

電源供應與系統啟動

將USB電源供應器插入插座並連線到樹莓派的電源埠。啟動後,紅色LED燈會亮起,表示樹莓派已接通電源。隨後,螢幕上會出現樹莓派的啟動畫面,最終進入樹莓派作業系統桌面。

初始設定與指令操作

預設的使用者名稱為pi,密碼為raspberry。可透過執行uname -r指令來查詢目前啟動的核心版本。

pi@raspberrypi:~$ uname -r
5.4.79-v7+

若需重新啟動系統,可執行:

pi@raspberrypi:~$ sudo reboot

內容解密:

  • uname -r用於查詢目前系統的核心版本。
  • sudo reboot用於重新啟動樹莓派系統,需要管理員許可權。

設定乙太網路通訊

  1. 在主機端,編輯網路連線設定,將有線連線的IPv4設定為手動,並設定靜態IP位址,例如10.0.0.1,子網路遮罩為255.255.255.0,閘道設為none0.0.0.0
  2. 在樹莓派上設定eth0介面的IP位址為10.0.0.10
    pi@raspberrypi:~$ sudo ifconfig eth0 10.0.0.10 netmask 255.255.255.0
    
  3. 啟動SSH服務,以便遠端存取樹莓派:
    pi@raspberrypi:~# sudo /etc/init.d/ssh restart
    

內容解密:

  • ifconfig指令用於設定網路介面的IP位址和子網路遮罩。
  • sudo /etc/init.d/ssh restart用於重新啟動SSH服務,使其生效。

編譯Linux核心

可採用本地編譯或跨平台編譯兩種方式。以下介紹跨平台編譯方法:

  1. 安裝必要的工具和依賴套件:
    ~$ sudo apt install git bc bison flex libssl-dev make libc6-dev libncurses5-dev
    
  2. 下載核心原始碼:
    ~/linux_rpi3$ git clone --depth=1 -b rpi-5.4.y https://github.com/raspberrypi/linux
    
  3. 安裝32位元工具鏈並進行核心編譯:
    ~/linux_rpi3/linux$ KERNEL=kernel7
    ~/linux_rpi3/linux$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- bcm2709_defconfig
    
  4. 設定核心選項:
    ~/linux_rpi3/linux$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
    
    在選單中啟用SPI支援和使用者模式SPI裝置驅動程式支援,以及工業IO支援。

內容解密:

  • git clone指令用於下載指定的核心原始碼分支。
  • make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- bcm2709_defconfig用於設定核心的預設組態。
  • make menuconfig用於進入互動式選單以設定核心選項。

建構系統:Linux 核心編譯與 Raspberry Pi 驅動程式開發

在進行 Linux 驅動程式開發時,瞭解 Linux 核心編譯流程是至關重要的第一步。本章節將介紹如何為 Raspberry Pi 編譯 Linux 核心,並將其佈署到裝置上。

組態 Linux 核心

首先,需要對 Linux 核心進行組態,以啟用所需的驅動程式和功能。可以使用 make menuconfig 指令進入互動式組態介面。

~/linux_rpi3/linux$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig

在組態介面中,需要啟用以下選項:

工業 I/O 緩衝區(Industrial I/O buffering)根據 kfifo

-*- Industrial I/O buffering based on kfifo

透過 configfs 啟用 IIO 組態

<*> Enable IIO configuration via configfs

啟用觸發取樣支援(Triggered Sampling Support)

-*- Enable triggered sampling support

啟用軟體 IIO 裝置支援

<*> Enable software IIO device support

啟用軟體觸發器支援

<*> Enable software triggers support

儲存組態並離開 menuconfig

編譯 Linux 核心與模組

完成組態後,可以使用以下指令編譯 Linux 核心、裝置樹檔案和模組:

~/linux_rpi3/linux$ make -j4 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zImage modules dtbs

將核心與模組佈署到 Raspberry Pi

編譯完成後,需要將核心和模組複製到 Raspberry Pi。首先,將 Micro SD 卡插入主機,並掛載相關分割區:

~$ lsblk
~$ mkdir ~/mnt
~$ mkdir ~/mnt/fat32
~$ mkdir ~/mnt/ext4
~$ sudo mount /dev/mmcblk0p1 ~/mnt/fat32
~$ sudo mount /dev/mmcblk0p2 ~/mnt/ext4/

然後,安裝模組並更新核心和裝置樹檔案:

~/linux_rpi3/linux$ sudo env PATH=$PATH make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- INSTALL_MOD_PATH=~/mnt/ext4 modules_install
~/linux_rpi3/linux$ sudo cp ~/mnt/fat32/kernel7.img ~/mnt/fat32/kernel7-backup.img
~/linux_rpi3/linux$ sudo cp arch/arm/boot/zImage ~/mnt/fat32/kernel7.img
~/linux_rpi3/linux$ sudo cp arch/arm/boot/dts/*.dtb ~/mnt/fat32/
~/linux_rpi3/linux$ sudo cp arch/arm/boot/dts/overlays/*.dtb* ~/mnt/fat32/overlays/
~/linux_rpi3/linux$ sudo cp arch/arm/boot/dts/overlays/README ~/mnt/fat32/overlays/
~$ sudo umount ~/mnt/fat32
~$ sudo umount ~/mnt/ext4

驗證核心版本

將 Micro SD 卡插入 Raspberry Pi,並連線 UART 至主機。啟動 Minicom 工具以檢視登入主控台:

~$ sudo minicom –s

設定 baud rate 為 115200,並登入系統。使用 uname -r 指令檢查核心版本:

pi@raspberrypi:~$ uname -r
5.4.83-v7+

遠端更新核心與裝置樹檔案

若需要遠端更新核心或裝置樹檔案,可以使用 SCP 協定:

~/linux_rpi3/linux$ scp arch/arm/boot/zImage root@10.0.0.10:/boot/kernel7.img
~/linux_rpi3/linux$ scp arch/arm/boot/dts/bcm2710-rpi-3-b.dtb root@10.0.0.10:/boot/

或者,針對 Raspberry Pi 3 Model B+:

~/linux_rpi3/linux$ scp arch/arm/boot/dts/bcm2710-rpi-3-b-plus.dtb root@10.0.0.10:/boot/

Linux 裝置與驅動程式模型

Linux 裝置與驅動程式模型是開發 Linux 驅動程式的核心。本章節將介紹該模型的基礎概念和組織結構。

模型概述

Linux 裝置與驅動程式模型提供了一種統一的方式來表示裝置和驅動程式之間的關係。它具有以下優點:

  • 最小化程式碼重複
  • 清晰的程式碼組織,將裝置驅動程式與控制器驅動程式分離,硬體描述與驅動程式本身分離等
  • 能夠確定系統中的所有裝置,檢視其狀態和電源狀態,瞭解其所連線的匯流排,並確定負責的驅動程式
  • 能夠生成系統的完整和有效的裝置結構樹,包括所有匯流排和互連
  • 能夠將裝置與驅動程式連結起來,反之亦然
  • 根據裝置型別(類別)進行分類別,例如輸入裝置,而無需瞭解實體裝置拓撲結構

主要資料結構

該模型圍繞三個主要資料結構組織:

  1. bus_type 結構,代表一種匯流排型別(例如 USB、PCI、I2C)
  2. device_driver 結構,代表能夠處理某種匯流排上特定裝置的驅動程式
  3. device 結構,代表連線到匯流排的裝置

程式碼範例:定義 bus_type 結構

struct bus_type {
    const char              *name;
    const char              *dev_name;
    struct device           *dev_root;
    struct device_attribute *dev_attrs;
    const struct attribute_group **bus_groups;
    const struct attribute_group **dev_groups;
    const struct attribute_group **drv_groups;
    int (*match)(struct device *dev, struct device_driver *drv);
    int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
    int (*probe)(struct device *dev);
    int (*remove)(struct device *dev);
    void (*shutdown)(struct device *dev);
    int (*suspend)(struct device *dev, pm_message_t state);
    int (*resume)(struct device *dev);
    const struct dev_pm_ops *pm;
    struct iommu_ops *iommu_ops;
    struct subsys_private *p;
    struct lock_class_key lock_key;
};

內容解密:

此結構定義了 bus_type 的屬性與方法,用於表示和管理不同型別的匯流排。其中包括名稱、裝置屬性、匹配與事件處理函式等關鍵資訊。

Linux 裝置和驅動程式模型 第二章

匯流排核心驅動程式

在 Linux 核心中,每一種被支援的匯流排都有一個通用的匯流排核心驅動程式。匯流排是處理器與一個或多個裝置之間的通道。根據裝置模型的定義,所有裝置都透過匯流排連線,即使是內部虛擬的「平台」匯流排。

匯流排核心驅動程式分配一個 bus_type 結構並將其註冊到核心的匯流排型別列表中。bus_type 結構(在核心原始碼樹的 include/linux/device.h 中宣告)代表一種匯流排型別(USB、PCI、I2C 等)。匯流排在系統中的註冊是透過使用 bus_register() 函式完成的。

bus_type 結構

struct bus_type {
    const char *name;
    const char *dev_name;
    struct device *dev_root;
    struct device_attribute *dev_attrs;
    const struct attribute_group **bus_groups;
    const struct attribute_group **dev_groups;
    const struct attribute_group **drv_groups;
    int (*match)(struct device *dev, struct device_driver *drv);
    int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
    int (*probe)(struct device *dev);
    int (*remove)(struct device *dev);
    void (*shutdown)(struct device *dev);
    int (*online)(struct device *dev);
    int (*offline)(struct device *dev);
    int (*suspend)(struct device *dev, pm_message_t state);
    int (*resume)(struct device *dev);
    const struct dev_pm_ops *pm;
    struct iommu_ops *iommu_ops;
    struct subsys_private *p;
    struct lock_class_key lock_key;
};

匯流排註冊

新的匯流排必須透過呼叫 bus_register() 進行註冊,該函式在核心原始碼樹的 drivers/base/platform.c 中定義。

struct bus_type platform_bus_type = {
    .name = "platform",
    .dev_groups = platform_dev_groups,
    .match = platform_match,
    .uevent = platform_uevent,
    .pm = &platform_dev_pm_ops,
};
EXPORT_SYMBOL_GPL(platform_bus_type);

int __init platform_bus_init(void)
{
    int error;
    early_platform_cleanup();
    error = device_register(&platform_bus);
    if (error)
        return error;
    error = bus_register(&platform_bus_type);
    if (error)
        device_unregister(&platform_bus);
    return error;
}

subsys_private 結構

bus_type 結構的成員之一是指向 subsys_private 結構的指標,該結構在核心原始碼樹的 drivers/base/base.h 中宣告。

struct subsys_private {
    struct kset subsys;
    struct kset *devices_kset;
    struct list_head interfaces;
    struct mutex mutex;
    struct kset *drivers_kset;
    struct klist klist_devices;
    struct klist klist_drivers;
    struct blocking_notifier_head bus_notifier;
    unsigned int drivers_autoprobe:1;
    struct bus_type *bus;
    struct kset glue_dirs;
    struct class *class;
};

匯流排驅動程式的功能

匯流排驅動程式註冊一個匯流排到系統中,並:

  1. 允許註冊匯流排控制器驅動程式,其作用是檢測裝置並組態其資源。
  2. 允許註冊裝置驅動程式。
  3. 將裝置和驅動程式進行匹配。

匯流排控制器驅動程式

對於特定的匯流排型別,可能有多個不同供應商提供的控制器。每個控制器都需要對應的匯流排控制器驅動程式。匯流排控制器驅動程式在裝置模型維護中的角色與其他裝置驅動程式類別似,透過使用 driver_register() 函式向其匯流排註冊。在大多數情況下,這些匯流排控制器裝置是在核心初始化期間透過呼叫 of_platform_populate() 發現的,該函式遍歷裝置樹,找到並註冊這些「平台控制器裝置」到平台匯流排上。

裝置驅動程式

每個裝置驅動程式透過使用 driver_register() 向匯流排核心驅動程式註冊。之後,裝置模型核心嘗試將其與裝置進行繫結。當檢測到可以由特定驅動程式處理的裝置時,將呼叫該驅動程式的 probe() 成員,並可以從裝置樹中檢索裝置組態資料。

驅動程式繫結裝置的時機

  1. 當驅動程式註冊時(如果裝置已經存在)。
  2. 當裝置建立時(如果驅動程式已經註冊)。